# Ahnlich — full documentation --- # FILE: ahnlich-in-production/ahnlich-in-production.md --- title: ⚡Ahnlich in production sidebar_position: 50 --- # Ahnlich in Production Ahnlich is production-ready and designed for deployment at scale. This section covers everything you need to run Ahnlich in production environments. ## What You'll Learn - **[Deployment](/docs/ahnlich-in-production/deployment)** - Docker-based deployment strategies for cloud and on-premise - **[Kubernetes (Helm)](/docs/ahnlich-in-production/kubernetes)** - Deploy DB and AI on Kubernetes with the official Helm charts - **[Tracing](/docs/ahnlich-in-production/tracing)** - Distributed tracing setup for observability ## Architecture Overview A typical production setup consists of: ``` ┌─────────────┐ │ Clients │ └──────┬──────┘ │ ├──────────────────────┐ │ │ ▼ ▼ ┌─────────────┐ ┌─────────────┐ │ Ahnlich AI │───────>│ Ahnlich DB │ │ (Port 1370)│ │ (Port 1369) │ └─────────────┘ └─────────────┘ ``` - **Ahnlich DB** handles vector storage and similarity search - **Ahnlich AI** transforms inputs (text/images) into embeddings - Both services communicate over gRPC ## Key Features for Production ### Persistence Both services support disk persistence to survive restarts: - Configurable intervals for snapshots - Automatic recovery on startup ### Performance - In-memory operations for low latency - Batch processing support - Configurable model batch sizes ### Observability - Distributed tracing with OpenTelemetry - Integration with Jaeger and other collectors - Request/response logging ### Scalability - Horizontal scaling via multiple instances - Load balancing support - Model caching to reduce startup time ## Quick Start Get started with Docker Compose: ```bash curl -O https://raw.githubusercontent.com/deven96/ahnlich/main/docker-compose.yml docker-compose up -d ``` This starts both services with persistence enabled. ## Next Steps 1. **[Deploy to Production](/docs/ahnlich-in-production/deployment)** - Choose your deployment platform 2. **[Enable Tracing](/docs/ahnlich-in-production/tracing)** - Set up observability 3. Review the [CLI reference](/docs/components/ahnlich-cli) for configuration options --- # FILE: ahnlich-in-production/authentication.md --- title: Authentication sidebar_position: 5 --- # Authentication Ahnlich supports API key authentication with TLS encryption for secure production deployments. ## Overview Authentication in Ahnlich uses: - **Bearer token authentication** via the `authorization` header - **TLS encryption** for secure transport (required when auth is enabled) - **SHA-256 hashed API keys** stored in a TOML config file ## Server Configuration ### Enable Authentication Start the server with authentication enabled: ```bash ahnlich-db run \ --host 0.0.0.0 \ --enable-auth \ --auth-config /path/to/auth.toml \ --tls-cert /path/to/server.crt \ --tls-key /path/to/server.key ``` The same flags apply to `ahnlich-ai`: ```bash ahnlich-ai run \ --host 0.0.0.0 \ --db-host ahnlich_db \ --enable-auth \ --auth-config /path/to/auth.toml \ --tls-cert /path/to/server.crt \ --tls-key /path/to/server.key ``` ### CLI Flags | Flag | Description | |------|-------------| | `--enable-auth` | Enable authentication (requires TLS) | | `--auth-config ` | Path to authentication config file (TOML format) | | `--tls-cert ` | Path to TLS certificate file (PEM format) | | `--tls-key ` | Path to TLS private key file (PEM format) | ## Auth Config File Create a TOML file with usernames and SHA-256 hashed API keys: ```toml # auth.toml [users] alice = "5e884898da28047d55d0cb6af8e6d0df01c52e4b0c3c75c4cc11e8f9e6b0e4a2" bob = "a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3" [security] min_key_length = 16 ``` ### Generate API Key Hashes Use any SHA-256 tool to hash your API keys: ```bash # Using openssl echo -n "my_super_secret_key" | openssl dgst -sha256 # Using sha256sum echo -n "my_super_secret_key" | sha256sum ``` The output hash (without the filename suffix) goes in the config file. ### Security Settings | Setting | Default | Description | |---------|---------|-------------| | `min_key_length` | 16 | Minimum API key length (before hashing) | ## Client Authentication ### Header Format All authenticated requests must include the `authorization` header: ``` authorization: Bearer : ``` The API key is sent in plain text over TLS. The server hashes it and compares against the stored hash. ### Node.js ```ts import * as fs from "fs"; import { createDbClient } from "ahnlich-client-node"; const client = createDbClient("127.0.0.1:1369", { caCert: fs.readFileSync("ca.crt"), auth: { username: "alice", apiKey: "my_super_secret_key" }, }); ``` The `caCert` option provides the CA certificate for TLS verification. This is required when the server uses a self-signed certificate or a private CA. ### Python ```python import asyncio import ssl from grpclib.client import Channel async def create_authenticated_client(): # Create SSL context with CA certificate ssl_context = ssl.create_default_context() ssl_context.load_verify_locations("ca.crt") # Create channel with TLS channel = Channel( host="127.0.0.1", port=1369, ssl=ssl_context ) # Add authorization metadata to requests metadata = {"authorization": "Bearer alice:my_super_secret_key"} return channel, metadata ``` ### Go ```go import ( "context" "crypto/tls" "crypto/x509" "os" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" ) func createAuthenticatedClient(ctx context.Context) (*grpc.ClientConn, error) { // Load CA certificate caCert, err := os.ReadFile("ca.crt") if err != nil { return nil, err } certPool := x509.NewCertPool() certPool.AppendCertsFromPEM(caCert) // Create TLS credentials tlsConfig := &tls.Config{RootCAs: certPool} creds := credentials.NewTLS(tlsConfig) // Connect with TLS conn, err := grpc.DialContext(ctx, "127.0.0.1:1369", grpc.WithTransportCredentials(creds), grpc.WithBlock(), ) if err != nil { return nil, err } return conn, nil } // Add auth header to context for each request func withAuth(ctx context.Context) context.Context { return metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer alice:my_super_secret_key", ) } ``` ### Rust ```rust use tonic::transport::{Certificate, Channel, ClientTlsConfig}; use tonic::Request; async fn create_authenticated_client() -> Result> { let ca_cert = std::fs::read_to_string("ca.crt")?; let ca = Certificate::from_pem(ca_cert); let tls = ClientTlsConfig::new().ca_certificate(ca); let channel = Channel::from_static("https://127.0.0.1:1369") .tls_config(tls)? .connect() .await?; Ok(channel) } // Add auth header to requests fn with_auth(mut request: Request) -> Request { request.metadata_mut().insert( "authorization", "Bearer alice:my_super_secret_key".parse().unwrap(), ); request } ``` ## Docker Compose with Auth ```yaml version: "3.8" services: ahnlich_db: image: ghcr.io/deven96/ahnlich-db:latest command: > ahnlich-db run --host 0.0.0.0 --enable-auth --auth-config /etc/ahnlich/auth.toml --tls-cert /etc/ahnlich/server.crt --tls-key /etc/ahnlich/server.key --enable-persistence --persist-location /root/.ahnlich/data/db.dat ports: - "1369:1369" volumes: - ./data:/root/.ahnlich/data - ./certs:/etc/ahnlich:ro ``` ## TLS Certificate Setup ### Generate Self-Signed Certificates For development or internal use: ```bash # Generate CA key and certificate openssl genrsa -out ca.key 4096 openssl req -new -x509 -days 365 -key ca.key -out ca.crt \ -subj "/CN=Ahnlich CA" # Generate server key and CSR openssl genrsa -out server.key 2048 openssl req -new -key server.key -out server.csr \ -subj "/CN=localhost" # Sign server certificate with CA openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key \ -CAcreateserial -out server.crt # Clean up CSR rm server.csr ``` ### Production Certificates For production, use certificates from a trusted CA (Let's Encrypt, DigiCert, etc.) or your organization's internal CA. ## Error Responses | Error | Cause | |-------|-------| | `Missing authorization header` | No `authorization` header provided | | `Invalid authorization format` | Header doesn't match `Bearer username:api_key` | | `Invalid credentials` | Username not found or API key doesn't match | | `API key too short` | Key shorter than `min_key_length` | ## Security Best Practices 1. **Use strong API keys**: At least 32 characters with mixed case, numbers, and symbols 2. **Rotate keys periodically**: Update the auth config and restart the server 3. **Protect the auth config**: Restrict file permissions (`chmod 600 auth.toml`) 4. **Use trusted TLS certificates**: Avoid self-signed certs in production 5. **Monitor failed auth attempts**: Check server logs for unauthorized access attempts --- # FILE: ahnlich-in-production/deployment.md --- title: Deployment sidebar_position: 10 --- # Deployment Ahnlich consists of two services that work together: - **ahnlich-db**: In-memory vector store with exact similarity search - **ahnlich-ai**: AI proxy that transforms raw inputs (text/image) into embeddings The recommended production setup runs both services using Docker. ## Official Docker Images Ahnlich provides prebuilt images on [GitHub Container Registry](https://github.com/deven96/ahnlich/pkgs/container/ahnlich-db): - **DB**: `ghcr.io/deven96/ahnlich-db:latest` - **AI**: `ghcr.io/deven96/ahnlich-ai:latest` ## Docker Compose Setup The easiest deployment for local or cloud use: ```yaml version: "3.8" services: ahnlich_db: image: ghcr.io/deven96/ahnlich-db:latest command: > ahnlich-db run --host 0.0.0.0 --enable-persistence --persist-location /root/.ahnlich/data/db.dat --persistence-interval 300 ports: - "1369:1369" volumes: - ./data:/root/.ahnlich/data ahnlich_ai: image: ghcr.io/deven96/ahnlich-ai:latest command: > ahnlich-ai run --host 0.0.0.0 --db-host ahnlich_db --enable-persistence --persist-location /root/.ahnlich/data/ai.dat --persistence-interval 300 ports: - "1370:1370" volumes: - ./data:/root/.ahnlich/data - ./ahnlich_ai_model_cache:/root/.ahnlich/models ``` This configuration: - Enables disk persistence (data survives restarts) - Maps ports 1369 (DB) and 1370 (AI) - Caches AI models across restarts ## Persistence Without persistence, all data is in-memory and lost on restart. To enable: ```bash --enable-persistence --persist-location /root/.ahnlich/data/db.dat --persistence-interval 300 # seconds ``` Mount the persist location to a host volume: ```yaml volumes: - ./data:/root/.ahnlich/data ``` ## Cloud Deployments ### AWS EC2 1. Launch EC2 instance 2. Install Docker 3. Run DB: ```bash docker run -d \ --name ahnlich_db \ -p 1369:1369 \ -v /data/ahnlich:/root/.ahnlich/data \ ghcr.io/deven96/ahnlich-db:latest \ ahnlich-db run --host 0.0.0.0 \ --enable-persistence \ --persist-location /root/.ahnlich/data/db.dat ``` 4. Run AI: ```bash docker run -d \ --name ahnlich_ai \ -p 1370:1370 \ --link ahnlich_db \ -v /data/ahnlich:/root/.ahnlich/data \ -v /data/models:/root/.ahnlich/models \ ghcr.io/deven96/ahnlich-ai:latest \ ahnlich-ai run --host 0.0.0.0 \ --db-host ahnlich_db \ --enable-persistence \ --persist-location /root/.ahnlich/data/ai.dat ``` Open ports 1369 and 1370 in your security group. ### GCP Compute Engine 1. Create VM instance 2. Install Docker 3. Follow same Docker commands as AWS EC2 4. Create firewall rules for TCP ports 1369 and 1370 5. Mount a persistent disk to `/data` for persistence ### Coolify [Coolify](https://coolify.io/) is a self-hosted PaaS supporting Docker images. **Steps:** 1. Create new app → Docker Image 2. Set images: - DB: `ghcr.io/deven96/ahnlich-db:latest` - AI: `ghcr.io/deven96/ahnlich-ai:latest` 3. Configure run commands: - DB: `ahnlich-db run --host 0.0.0.0 --enable-persistence --persist-location /root/.ahnlich/data/db.dat` - AI: `ahnlich-ai run --host 0.0.0.0 --db-host ahnlich_db --enable-persistence --persist-location /root/.ahnlich/data/ai.dat` 4. Mount volumes: - `/root/.ahnlich/data` (persistence) - `/root/.ahnlich/models` (AI model cache) 5. Expose ports 1369 and 1370 ### Google Cloud Run Cloud Run supports gRPC containers with these requirements: - Containers listen on `$PORT` (use `--port $PORT`) - Expose endpoints over HTTPS (port 443) - Configure `ahnlich-ai` with `--db-host ` See [Cloud Run gRPC Guide](https://cloud.google.com/run/docs/triggering/grpc) ## Production Checklist | Item | Recommendation | |------|----------------| | Ports | Expose 1369 (DB) and 1370 (AI) | | DB Connection | `ahnlich-ai` must use `--db-host` with reachable address | | Persistence | Enable with `--enable-persistence` and bind volumes | | Model Caching | Mount `/root/.ahnlich/models` for AI | | Tracing | Optional: `--enable-tracing --otel-endpoint ` | | Security | Use TLS via proxy/load balancer for external exposure | ## References - [Ahnlich GitHub](https://github.com/deven96/ahnlich) - [Docker Images](https://github.com/deven96/ahnlich/pkgs/container/ahnlich-db) - [Docker Compose Example](https://github.com/deven96/ahnlich/blob/main/docker-compose.yml) - [Coolify Docs](https://docs.coolify.io/) - [AWS EC2 Docker](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) - [Cloud Run gRPC](https://cloud.google.com/run/docs/triggering/grpc) --- # FILE: ahnlich-in-production/kubernetes.md --- title: Kubernetes (Helm) sidebar_position: 15 --- # Kubernetes (Helm) Ahnlich ships official Helm charts for running both services on Kubernetes: - **`ahnlich-db`** — the in-memory vector store (StatefulSet, client gRPC on port `1369`) - **`ahnlich-ai`** — the embedding/AI proxy that sits in front of the DB (StatefulSet, client gRPC on port `1370`) - **`ahnlich`** — an **umbrella** chart that deploys both, pre-wired so the AI proxy talks to the DB out of the box, with an optional in-cluster tracing backend Reach for Kubernetes when you want self-healing pods, rolling upgrades, persistent volumes managed by the cluster, and horizontal scaling. For a single box or local experimentation, the [Docker Compose setup](./deployment) is simpler. :::note Published to an OCI registry The charts are published to `oci://ghcr.io/deven96/charts` and listed on [ArtifactHub](https://artifacthub.io/packages/search?ts_query_web=ahnlich). Helm pulls them on demand — no `git clone` or `helm repo add` needed. ::: ## Prerequisites - A running Kubernetes cluster and a `kubectl` context pointing at it. Any cluster works — [Rancher Desktop](https://rancherdesktop.io/), kind, or minikube locally; EKS / GKE / AKS in production. - [Helm](https://helm.sh/docs/intro/install/) 3.8+ (or 4.x). - Kubernetes 1.20+ (the Services set `appProtocol: grpc`; the Gateway API path additionally needs the Gateway API CRDs and a controller — see [External access](#external-access)). - `kubectl`, configured for the target cluster: ```bash kubectl config current-context kubectl get nodes ``` ## The charts Helm pulls the charts straight from the OCI registry — nothing to clone or vendor. Reference them by their OCI URL: | Chart | OCI URL | |---|---| | Umbrella (both services) | `oci://ghcr.io/deven96/charts/ahnlich` | | DB only | `oci://ghcr.io/deven96/charts/ahnlich-db` | | AI only | `oci://ghcr.io/deven96/charts/ahnlich-ai` | Add `--version ` to pin a specific chart version; without it, Helm uses the latest. ## Install (Umbrella Chart) The umbrella is the recommended path: it installs `ahnlich-db` and `ahnlich-ai` together and wires the AI proxy to the DB automatically. ```bash kubectl create namespace ahnlich helm install ahnlich oci://ghcr.io/deven96/charts/ahnlich \ --namespace ahnlich \ --set 'ahnlich-ai.models.supported={all-minilm-l6-v2}' ``` :::warning The release name must be `ahnlich` The umbrella's default DB wiring (`ahnlich-ai.db.host=ahnlich-ahnlich-db`) assumes the release is named `ahnlich`. Install under any other name and the chart **fails at template time** with the exact override to use. To use a different name, also pass `--set ahnlich-ai.db.host=-ahnlich-db`. ::: :::tip Keep the first install fast The AI proxy downloads its embedding models on first start. The example overrides `ahnlich-ai.models.supported` to just `all-minilm-l6-v2` so the initial model pull is quick. Drop the `--set` to get the chart default (`all-minilm-l6-v2`, `resnet-50`), or list whatever models you need. ::: The AI pod blocks on a `wait-for-db` init container until the DB is reachable, so it is normal for `ahnlich-ahnlich-ai-0` to sit in `Init:0/1` for a moment while the DB comes up and the model downloads. Watch progress with: ```bash kubectl get pods -n ahnlich -w ``` ## Verify the Deployment Once both StatefulSets report ready: ```bash kubectl get pods,sts -n ahnlich ``` Send a `PING` to each service from inside its pod: ```bash # DB kubectl exec -n ahnlich sts/ahnlich-ahnlich-db -- \ sh -c "echo PING | ahnlich-cli ahnlich --agent db --host 127.0.0.1 --port 1369 --no-interactive" # AI kubectl exec -n ahnlich sts/ahnlich-ahnlich-ai -- \ sh -c "echo PING | ahnlich-cli ahnlich --agent ai --host 127.0.0.1 --port 1370 --no-interactive" ``` Prove the full AI → DB path by creating a store through the AI proxy and confirming it exists in the DB: ```bash # Create a store via AI (AI proxies the write to DB) kubectl exec -n ahnlich sts/ahnlich-ahnlich-ai -- \ sh -c "echo 'CREATESTORE smoke QUERYMODEL all-minilm-l6-v2 INDEXMODEL all-minilm-l6-v2' | ahnlich-cli ahnlich --agent ai --host 127.0.0.1 --port 1370 --no-interactive" # Confirm it landed in DB kubectl exec -n ahnlich sts/ahnlich-ahnlich-db -- \ sh -c "echo LISTSTORES | ahnlich-cli ahnlich --agent db --host 127.0.0.1 --port 1369 --no-interactive" ``` The `LISTSTORES` output should include `smoke`. ## Connect from Clients **In-cluster** — other workloads in the cluster reach the services by their Service DNS: ``` ahnlich-ahnlich-db.ahnlich.svc.cluster.local:1369 ahnlich-ahnlich-ai.ahnlich.svc.cluster.local:1370 ``` Point an [Ahnlich client](/docs/client-libraries) at the AI proxy's address (or the DB address for raw vector operations). **From your laptop** — port-forward for local testing: ```bash kubectl port-forward -n ahnlich svc/ahnlich-ahnlich-ai 1370:1370 # now connect a client to 127.0.0.1:1370 ``` For permanent external access, see [External access](#external-access) below. ## Configuration Override any value with `--set key=value` or, better for real deployments, a values file: ```bash helm install ahnlich oci://ghcr.io/deven96/charts/ahnlich -n ahnlich -f my-values.yaml ``` Umbrella values are namespaced per sub-chart — set DB values under `ahnlich-db.*` and AI values under `ahnlich-ai.*`, e.g. `--set ahnlich-db.persistence.size=50Gi`. Common knobs: | What | Key | Default | |------|-----|---------| | DB snapshot PVC size | `ahnlich-db.persistence.size` | `10Gi` | | AI model cache PVC size | `ahnlich-ai.models.cache.size` | `20Gi` | | PVC StorageClass | `ahnlich-db.persistence.storageClass`, `ahnlich-ai.models.cache.storageClass` | `""` (cluster default) | | DB snapshot interval (ms) | `ahnlich-db.persistence.intervalMs` | `30000` | | Models served by AI | `ahnlich-ai.models.supported` | `[all-minilm-l6-v2, resnet-50]` | | Container resources | `ahnlich-db.resources`, `ahnlich-ai.resources` | `{}` (unset) | | Log level | `ahnlich-db.logLevel`, `ahnlich-ai.logLevel` | binary default (`info,hf_hub=warn`) | | Extra env vars | `ahnlich-db.env`, `ahnlich-ai.env` | `[]` | | Bulk env from ConfigMap/Secret | `ahnlich-db.envFrom`, `ahnlich-ai.envFrom` | `[]` | Persistence is **on by default** for both services: each writes a periodic snapshot (`db.dat` / `ai.dat`) to its PVC every `persistence.intervalMs` (default 30s) and reloads it on startup. Data survives pod restarts, but an ungraceful kill can lose up to one interval's worth of the most recent writes. The full set of values, with descriptions, lives in each chart's README: - [`ahnlich-db` values](https://github.com/deven96/ahnlich/blob/main/charts/ahnlich-db/README.md) - [`ahnlich-ai` values](https://github.com/deven96/ahnlich/blob/main/charts/ahnlich-ai/README.md) - [`ahnlich` (umbrella) values](https://github.com/deven96/ahnlich/blob/main/charts/ahnlich/README.md) :::note GPU for the AI service `ahnlich-ai`'s image bundles the CUDA ONNX Runtime. To run embeddings on GPU nodes, add `nvidia.com/gpu` to `ahnlich-ai.resources.limits` and a matching `ahnlich-ai.nodeSelector` (the cluster needs the NVIDIA device plugin). ::: :::note Log level is `--log-level`, not `RUST_LOG` Set verbosity via `ahnlich-db.logLevel` / `ahnlich-ai.logLevel` (mapped to the binary's `--log-level`). Setting `RUST_LOG` through `env` has no effect. ::: ## External Access The umbrella imposes no global external-access pattern; each sub-chart's `service.type`, `ingress.*`, and `gateway.*` blocks work independently and apply to **both** `ahnlich-db` and `ahnlich-ai`. Typically you expose **only the AI proxy** (it fronts the DB), but the same knobs expose the DB on `1369` if you need raw vector access from outside. ### LoadBalancer Service (Simplest) ```bash helm upgrade ahnlich oci://ghcr.io/deven96/charts/ahnlich -n ahnlich --reuse-values \ --set ahnlich-ai.service.type=LoadBalancer ``` Your cloud provider assigns an external IP (locally, Rancher Desktop / k3s `servicelb` uses the node IP). Connect a gRPC client to `:1370`. Set `ahnlich-db.service.type=LoadBalancer` likewise to expose DB on `1369`. ### Gateway API (GRPCRoute) — Recommended for gRPC :::info Requirements — what you provide vs. what the chart provides When you set `gateway.enabled=true`, the chart contributes **only a `GRPCRoute`** that attaches the `ahnlich-ai` / `ahnlich-db` Service to a Gateway you already run. The chart does **not** install a gateway, and intentionally so — the gateway is shared cluster infrastructure that you own and operate. Before enabling it, your cluster must already have: 1. **A Gateway API controller** — the component that actually proxies traffic (Envoy Gateway, NGINX Gateway Fabric, Contour, Istio, or your cloud's implementation). 2. **The Gateway API CRDs** (`gateway.networking.k8s.io`) — normally installed with the controller. 3. **A `GatewayClass`** registered by that controller. 4. **A `Gateway`** with a listener on the port your clients connect to, using a gRPC-capable protocol (`HTTP` for h2c, or `HTTPS` with TLS). The chart binds to that Gateway through `gateway.parentRefs` (and `sectionName` to pick a listener). If any of the above is missing, the `GRPCRoute` is created but stays unattached and no traffic flows. If you don't run a Gateway, use the [LoadBalancer Service](#loadbalancer-service-simplest) path instead. ::: The walkthrough below uses **Envoy Gateway** as a concrete, working example; any conformant controller follows the same shape (install controller → `GatewayClass` → `Gateway` → enable the chart's route). 1. Install a controller: ```bash helm install eg oci://docker.io/envoyproxy/gateway-helm \ -n envoy-gateway-system --create-namespace kubectl wait --for=condition=available deploy --all -n envoy-gateway-system --timeout=180s ``` 2. Create a `GatewayClass` and a `Gateway` with a gRPC (HTTP/h2c) listener on the AI port: ```yaml # gateway.yaml apiVersion: gateway.networking.k8s.io/v1 kind: GatewayClass metadata: name: eg spec: controllerName: gateway.envoyproxy.io/gatewayclass-controller --- apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: ahnlich-eg namespace: ahnlich spec: gatewayClassName: eg listeners: - name: ai protocol: HTTP # h2c — gRPC over cleartext HTTP/2 port: 1370 allowedRoutes: namespaces: from: Same ``` ```bash kubectl apply -f gateway.yaml ``` 3. Point the chart's `GRPCRoute` at that listener: ```bash helm upgrade ahnlich oci://ghcr.io/deven96/charts/ahnlich -n ahnlich --reuse-values \ --set ahnlich-ai.service.type=ClusterIP \ --set ahnlich-ai.gateway.enabled=true \ --set 'ahnlich-ai.gateway.parentRefs[0].name=ahnlich-eg' \ --set 'ahnlich-ai.gateway.parentRefs[0].sectionName=ai' ``` `sectionName` binds the route to a named listener. We set it explicitly here; it is strictly required only when the Gateway has multiple listeners (e.g. a DB `1369` and an AI `1370` listener side by side). Keep the Service at `ClusterIP` so it doesn't contend for the node port the Gateway now owns. :::note Overriding a multi-Gateway `parentRefs` `parentRefs` is a list with a one-element default. Under `--set` / `--reuse-values` Helm merges it by index (it patches `[0]`, it does not replace the list). To attach to several parent Gateways, pass the whole list via `-f values.yaml` instead of `--set`. ::: 4. Verify and connect: ```bash kubectl get gateway ahnlich-eg -n ahnlich # PROGRAMMED=True, ADDRESS assigned kubectl get grpcroute -n ahnlich # Accepted / ResolvedRefs True ``` Point your client at the Gateway's address on the listener port. ### Ingress ```bash helm upgrade ahnlich oci://ghcr.io/deven96/charts/ahnlich -n ahnlich --reuse-values \ --set ahnlich-ai.ingress.enabled=true \ --set ahnlich-ai.ingress.className= \ --set ahnlich-ai.ingress.host=ai.example.com ``` :::warning Plaintext gRPC over Ingress does not work — use TLS/h2c, or prefer Gateway API Ahnlich speaks gRPC (HTTP/2). Through a typical Ingress controller without h2c/TLS the controller answers HTTP/1.1 and the client fails with an `invalid compression flag` / `500` error. For real use, enable TLS (`ahnlich-ai.ingress.tls.enabled=true`, with a cert via cert-manager or a Secret) and set the controller's gRPC backend hint — for nginx-ingress that is `nginx.ingress.kubernetes.io/backend-protocol: GRPC`. The **Gateway API path above is the more reliable choice for gRPC.** `ingress` and `gateway` are mutually exclusive. ::: ## Tracing Enable the bundled in-cluster Jaeger backend and send both services' traces to it: ```bash helm upgrade ahnlich oci://ghcr.io/deven96/charts/ahnlich -n ahnlich --reuse-values \ --set tracing.enabled=true \ --set tracing.backend.enabled=true \ --set ahnlich-db.tracing.enabled=true \ --set ahnlich-ai.tracing.enabled=true # open the Jaeger UI kubectl port-forward -n ahnlich svc/ahnlich-tracing-backend 16686:16686 ``` :::note Bundled backend is dev/demo only The bundled Jaeger all-in-one runs as a single replica with in-memory span storage and no auth. For production, set `tracing.backend.enabled=false` and point `ahnlich-db.tracing.otelEndpoint` / `ahnlich-ai.tracing.otelEndpoint` at your own OTLP collector (Tempo, Honeycomb, an OpenTelemetry Collector, etc.). ::: ## Installing the Sub-Charts Individually You don't have to use the umbrella. Install either service on its own — useful when `ahnlich-db` and `ahnlich-ai` live in different namespaces or you manage them separately. ```bash # DB only helm install my-db oci://ghcr.io/deven96/charts/ahnlich-db -n ahnlich # AI only — db.host is required and must resolve to a reachable DB Service helm install my-ai oci://ghcr.io/deven96/charts/ahnlich-ai -n ahnlich \ --set db.host=my-db \ --set 'models.supported={all-minilm-l6-v2}' ``` With the standalone charts there is no enforced release name; the AI's `db.host` is whatever you point it at. ## Operations **Upgrade** — change values and re-apply: ```bash helm upgrade ahnlich oci://ghcr.io/deven96/charts/ahnlich -n ahnlich --reuse-values --set ahnlich-db.persistence.size=50Gi ``` **Uninstall** — and clean up the PVCs (they are retained by default, holding the cached models and persisted data): ```bash helm uninstall ahnlich -n ahnlich kubectl delete pvc -n ahnlich -l app.kubernetes.io/instance=ahnlich ``` Operational caveats: - **Toggling `persistence.enabled` between upgrades fails.** A StatefulSet's `volumeClaimTemplates` is immutable. Flipping persistence on or off requires `helm uninstall` then a fresh install (then clean up orphaned PVCs as above). - **`:latest` images pull `Always`.** When an image tag is `latest`, the chart sets `imagePullPolicy: Always` so `helm upgrade` actually pulls the newest digest. Pin a real tag (`--set ahnlich-db.image.tag=`) for reproducible deploys; tagged images default to `IfNotPresent`. ## Cluster (Raft) Mode — In Development The charts already carry the wiring for multi-replica Raft clusters — a headless Service, per-replica RocksDB volumes, a PodDisruptionBudget, and bootstrap/join logic — enabled like this: ```bash # NOT yet functional — shown for reference only helm install ahnlich oci://ghcr.io/deven96/charts/ahnlich -n ahnlich \ --set ahnlich-db.cluster.enabled=true \ --set ahnlich-ai.cluster.enabled=true \ --set 'ahnlich-ai.models.supported={all-minilm-l6-v2}' ``` :::warning Not ready for use Cluster/Raft mode is still in development. The server binary does not yet accept the `--cluster-*` flags the chart passes, so enabling it will not produce a working cluster. It is also excluded from the project's automated tests. Run the standalone (default) mode for now and track the repository for cluster-mode availability. ::: ## Troubleshooting - **AI pod stuck in `Init:0/1` (`wait-for-db`).** The init container blocks until the DB Service port is reachable. Check the DB pod is running and that `ahnlich-ai.db.host` resolves to the DB Service (`-ahnlich-db` under the umbrella). Inspect with `kubectl logs -n ahnlich ahnlich-ahnlich-ai-0 -c wait-for-db`. - **`helm install` fails with a release-name error.** You installed under a name other than `ahnlich` without overriding `ahnlich-ai.db.host`. Use the override the error message prints, or name the release `ahnlich`. - **AI takes a long time to become ready on first start.** It is downloading models. Restrict `ahnlich-ai.models.supported` to only what you need, and give the model cache PVC (`ahnlich-ai.models.cache.size`) enough room. The cache persists across restarts. - **AI pod stays `Running` but not `Ready` for a while on first install.** Expected: a `startupProbe` holds liveness and readiness until the embedding model has downloaded and the server answers a `PING`, so the pod is **not** killed mid-download. The default tolerance is 5 minutes (`ahnlich-ai.probes.startup.periodSeconds` × `failureThreshold`). If a large model or a slow network needs longer, raise `ahnlich-ai.probes.startup.failureThreshold` — otherwise the pod restarts once the window is exceeded. The model cache persists on the PVC, so later restarts are fast. - **Inspect a wedged install:** ```bash kubectl get pods,svc,pvc,events -n ahnlich kubectl logs -n ahnlich sts/ahnlich-ahnlich-db --tail 200 kubectl logs -n ahnlich sts/ahnlich-ahnlich-ai --tail 200 ``` ## References - [Ahnlich GitHub](https://github.com/deven96/ahnlich) - [Charts on ArtifactHub](https://artifacthub.io/packages/search?ts_query_web=ahnlich) - [Chart source (`charts/`)](https://github.com/deven96/ahnlich/tree/main/charts) - [Docker deployment](./deployment) - [Distributed tracing](./tracing) - [Client libraries](/docs/client-libraries) --- # FILE: ahnlich-in-production/tracing.md --- title: Tracing sidebar_position: 20 --- # Distributed Tracing Ahnlich supports distributed tracing using OpenTelemetry for production observability. ## Quick Enable Add these flags to enable tracing: ```bash # For Ahnlich DB ahnlich-db run \ --enable-tracing \ --otel-endpoint http://jaeger:4317 # For Ahnlich AI ahnlich-ai run \ --enable-tracing \ --otel-endpoint http://jaeger:4317 ``` ## Docker Compose Example ```yaml version: "3.8" services: jaeger: image: jaegertracing/all-in-one:latest ports: - "16686:16686" # Jaeger UI - "4317:4317" # OTLP gRPC receiver ahnlich_db: image: ghcr.io/deven96/ahnlich-db:latest command: > ahnlich-db run --host 0.0.0.0 --enable-tracing --otel-endpoint http://jaeger:4317 ports: - "1369:1369" ahnlich_ai: image: ghcr.io/deven96/ahnlich-ai:latest command: > ahnlich-ai run --host 0.0.0.0 --db-host ahnlich_db --enable-tracing --otel-endpoint http://jaeger:4317 ports: - "1370:1370" ``` Access Jaeger UI at `http://localhost:16686` ## What Gets Traced - Client requests and responses - DB operations (similarity search, indexing) - AI model inference (embedding generation) - Inter-service communication (AI → DB) ## Detailed Documentation For comprehensive tracing setup and configuration: - **[Distributed Tracing Overview](/docs/components/distributed-tracing)** - Architecture and concepts - **[Using Jaeger](/docs/components/distributed-tracing/using-jaeger)** - Jaeger setup guide - **[Tracing in Ahnlich DB](/docs/components/distributed-tracing/ahnlich-db)** - DB-specific configuration - **[Tracing in Ahnlich AI](/docs/components/distributed-tracing/ahnlich-ai)** - AI-specific configuration ## Production Tips 1. **Use a dedicated collector** - Don't send traces directly to Jaeger in production 2. **Sample traces** - Configure sampling to reduce overhead (not yet configurable, coming soon) 3. **Aggregate traces** - Use centralized tracing backends (Jaeger, Tempo, Honeycomb) 4. **Monitor overhead** - Tracing adds ~5-10% latency overhead --- # FILE: architecture.md --- title: Architecture sidebar_position: 60 --- # Ahnlich Architecture **Status**: *Alpha / testing – subject to breaking changes.*** Ahnlich is split into two independent, network‑accessible services that work in tandem: - ahnlich‑ai – **the Intelligence Layer** - ahnlich‑db – **the Vector Store Layer** Clients can speak to either layer through gRPC/HTTP or the bundled CLI/SDKs. The AI layer adds automated embedding and model management on top of the raw vector store exposed by the DB layer. ## 📦 1. High‑Level Design ```mermaid flowchart TD subgraph ai [ahnlich‑ai] direction TB AIClient["AI Client"] StoreHandlerAI["Store Handler"] StoreA_AI["Store A"] ModelNode["Index Model → Model B
Query Model → Model A
Pre‑process"] PersistenceAI[(Persistence)] AIClient --> |"original + metadata"| StoreHandlerAI StoreHandlerAI --> StoreA_AI StoreA_AI --> ModelNode ModelNode -.-> PersistenceAI end subgraph db [ahnlich‑db] direction TB DBClient["DB Client"] StoreHandlerDB["Store Handler"] StoreA_DB["Store A"] PersistenceDB[(Persistence)] DBClient --> |"DB query"| StoreHandlerDB StoreHandlerDB --> StoreA_DB StoreA_DB -.-> PersistenceDB end %% Inter‑service calls StoreHandlerAI -.-> |"Set: vector + metadata"| StoreHandlerDB StoreHandlerAI -.-> |"GetSimN: vector"| StoreHandlerDB StoreHandlerDB -.-> |"Top‑N results"| StoreHandlerAI ``` ### Analogy to Kafka | Kafka | Ahnlich | | ----- | ----- | | **Producer** | AI Client / DB Client | | **Broker** | ahnlich‑ai & ahnlich‑db services | | **Topic / Partition** | Store (logical namespace) | | **Message** | Vector + metadata | | **Consumer** | Client fetching GetSimN | ## 2. Key Components ### 2.1 `ahnlich‑ai` – Intelligence Layer | Sub‑component | Responsibility | | ----- | ----- | | **AI Client API** | External gRPC/HTTP endpoints – accepts raw documents (text, images…) & metadata. | | **Store Handler** | Maps incoming requests to a Store; maintains per‑store configuration (models, preprocess pipeline). | | **Store** | Logical namespace. Each holds a pair of ONNX models (Index & Query) plus preprocessing logic. | | **Model Node** | Executes preprocessing → model inference → produces embedding. | | **Optional Persistence** | Periodic snapshots of store metadata & model cache to disk. | ### 2.2 `ahnlich‑db` – Vector Store Layer | Sub‑component | Responsibility | | ----- | ----- | | **DB Client API** | Accepts vector‑level commands: SET, GETSIMN, CREATESTORE, etc. | | **Store Handler** | Routes to correct Store; enforces isolation; coordinates concurrent reads/writes. | | **Store (Vector Index)** | In‑memory index (brute‑force or KD‑Tree) plus metadata map. Supports cosine & Euclidean similarity. | | **Filter Engine** | Applies boolean predicates on metadata during query. | | **Optional Persistence** | Snapshots vectors & metadata to on‑disk binary file for warm restarts. | ## 3. Data Flow ### 3.1 Indexing (Write) Path 1. **Client** ➜ AI Layer – Sends raw document + metadata. 2. **Preprocessing & Embedding** – AI layer cleans input, runs Index Model to yield vector. 3. **AI ➜ DB** – Issues SET carrying vector & metadata. 4. **DB Store** – Writes vector into index, stores metadata. ### 3.2 Similarity Query Path 1. **Client ➜ AI Layer** – Provides search text/image. 2. **Embedding** – AI layer runs Query Model to create search vector. 3. **AI ➜ DB (GETSIMN)** – Vector + algorithm + optional predicate. 4. **DB** – Computes distance, applies metadata filter, returns Top‑N IDs & scores. 5. **AI Layer** – (Optional) post‑processes or joins additional metadata before responding to client. ### 3.3 Direct DB Access Advanced users can bypass AI and push pre‑computed vectors directly into ahnlich‑db for maximum control. ## 4 Persistence & Durability - **Opt‑in via** --enable-persistence. - **Snapshot interval** configurable (--persistence-interval, default 300 s). - **DB** writes a flat binary file; **AI** persists model cache & store manifests. - On startup each service checks for the snapshot file and hydrates memory before accepting traffic. - No replication yet; Ahnlich currently targets single‑node or shared‑nothing sharded deployments. ## 5. Scaling & Deployment Topologies | Pattern | How it works | When to use | | ----- | ----- | ----- | | **Single‑Node** | One `ahnlich‑ai` & one `ahnlich‑db` container (shown in README Compose). | Prototyping, local dev. | | **Vertical Scaling** | Give DB more RAM/CPU; mount NVIDIA GPU for AI layer. | Medium workloads where a single node still fits in memory. | | **Store‑Level Sharding** | Run multiple DB instances, each owning a subset of Stores; fronted by one AI layer. | Multi‑tenant SaaS or very large corpora. | | **Function Sharding** | Isolate heavy NLP image pipelines by model type: one AI instance per model group. | Heterogeneous workloads, GPU scheduling. | **Roadmap**: cluster‑wide replication & consistent hashing for transparent sharding. ## 6. Observability - Both services instrumented with **OpenTelemetry**; enable with --enable-tracing and send spans to Jaeger, Prometheus, etc. - Internal metrics: query latency, index size, RAM usage, model inference time. ## 7. Extensibility - **Add a new similarity metric** – implement SimAlgorithm trait in ahnlich‑db. - **Bring your own model** – point ahnlich‑ai to an ONNX file or HuggingFace repo via --supported-models. - **Custom predicates** – extend the predicate DSL to support regex or full‑text. ## 8. Security Considerations Currently no built‑in auth. Recommend placing behind an API gateway or reverse proxy that enforces: - JWT / OAuth 2 bearer tokens. - Mutual TLS between AI ⇄ DB if running across hosts. ## 9. Limitations (July 2025) - No distributed consensus – durability limited to local snapshots. - Single‑writer per Store lock may become a bottleneck under heavy concurrent writes. - Model hot‑swap requires store recreation. ## 🔍 Summary *Ahnlich decouples vector intelligence* (embedding generation, model lifecycle) from vector persistence & retrieval. This split allows you to scale and tune each layer independently while keeping a simple mental model—much like Kafka separates producers, brokers, and consumers around an immutable log. --- # FILE: client-libraries/client-libraries.mdx --- title: Client libraries --- import {ClientsFigure} from '@site/src/components/OverviewFigures'; import CustomDocCard from '@site/src/components/CustomDocCard'; import rustLogo from '@site/static/img/rust-logo.png'; import rustLogoLight from '@site/static/img/Rust.png'; import pythonLogo from '@site/static/img/python-logo.png' import pythonLogoLight from '@site/static/img/python_logo.png' import goLogo from '@site/static/img/go.png' import nodeLogo from '@site/static/img/nodejs-logo.png' export const components = [ { title: "Python", logoLight: pythonLogoLight, logoDark: pythonLogo, link: "/docs/client-libraries/python" }, { title: 'Node.js', logoLight: nodeLogo, logoDark: nodeLogo, link: "/docs/client-libraries/node" }, { title: 'Go', logoLight: goLogo, logoDark: goLogo, link: "/docs/client-libraries/go" }, { title: 'Rust', logoLight: rustLogoLight, logoDark: rustLogo, link: "/docs/client-libraries/rust" } ]; # Client libraries **Client libraries let you use Ahnlich from your own code, in the language you already work in.** Instead of typing commands in a terminal, you call functions like `create_store(...)` and `get_sim_n(...)` directly from your app. Every client does the same thing under the hood: it packages your request and sends it to the Ahnlich **DB** and **AI** servers over **gRPC** (a fast, typed way for programs to talk to each other). Because they all share one protocol, the ideas you learn in one language carry straight over to the others — only the syntax changes. ## Pick your language
{components.map((component) => ( ))}
--- # FILE: client-libraries/go/bulk-requests.md --- title: Bulk Requests sidebar_position: 4 --- # Bulk Requests — DB Pipeline ## Description In scenarios where multiple operations need to be performed against the database in a single workflow, executing each request independently can introduce unnecessary network overhead and increase response time. To address this, the DB service provides a pipeline mechanism, which allows clients to bundle several queries together and send them as one request. This improves efficiency, ensures that queries are executed in a defined sequence, and guarantees that responses are returned in the same order as the submitted queries. Pipelines are especially useful in workloads where **set-and-retrieve** or **batch query patterns** are common. ## Source Code Example ```go func examplePipelineDB() error { c.ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() conn, err := grpc.DialContext(c.ctx, proc.ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } defer conn.Close() client := dbsvc.NewDBServiceClient(conn) // Build pipeline queries setQ := &pipeline.DBQuery{Query: &pipeline.DBQuery_Set{Set: &dbquery.Set{ Store: "my_store", Inputs: []*keyval.DbStoreEntry{/* ... */}, }}} getQ := &pipeline.DBQuery{Query: &pipeline.DBQuery_GetKey{GetKey: &dbquery.GetKey{ Store: "my_store", Keys: []*keyval.StoreKey{/* ... */}, }}} // Execute pipeline req := &pipeline.DBRequestPipeline{Queries: []*pipeline.DBQuery{setQ, getQ}} resp, err := client.Pipeline(c.ctx, req) if err != nil { return err } fmt.Println("Pipeline responses:", resp.Responses) return nil } ``` ## Behavior - Pipelines minimize network calls by sending multiple queries in a single request. - Queries are executed in **order**, and results are returned in the same sequence. - Both successful responses and errors are included, aligned with their respective queries. - This approach optimizes performance and makes workflows more efficient when dealing with multiple dependent queries. # Bulk Requests — AI Pipeline Just like the DB service, the AI service also supports **pipelines** for combining multiple operations into a single request. This mechanism is particularly useful when you want to both **insert embeddings** and **query for similarities** in one sequence. By batching operations together, pipelines reduce **network latency**, improve throughput, and ensure that results are returned in the same order as the queries were issued. This is valuable in AI workflows where you might first store **embeddings** (e.g., text, image, or other vectorized data) and immediately **retrieve the most similar entries** from the store. ## Source Code Example ```go func examplePipelineAI() error { c.ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() conn, err := grpc.DialContext(c.ctx, proc.ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return fmt.Errorf("failed to dial AI server %q: %w", ServerAddr, err) } defer conn.Close() client := aisvc.NewAIServiceClient(conn) // Build pipeline queries setQ := &pipeline.AIQuery{Query: &pipeline.AIQuery_Set{Set: &aiquery.Set{ Store: "ai_store", Inputs: []*keyval.AiStoreEntry{/* ... */}, }}} simQ := &pipeline.AIQuery{Query: &pipeline.AIQuery_GetSimN{GetSimN: &aiquery.GetSimN{ Store: "ai_store", SearchInput: &keyval.StoreInput{Value: &keyval.StoreInput_RawString{RawString: "X"}}, ClosestN: 3, }}} // Execute pipeline req := &pipeline.AIRequestPipeline{Queries: []*pipeline.AIQuery{setQ, simQ}} resp, err := client.Pipeline(c.ctx, req) if err != nil { return err } fmt.Println("AI pipeline responses:", resp.Responses) return nil } ``` ## Behavior and expectations - Both `Set` and `GetSimN` are executed as part of the **same pipeline request**. - Responses are returned in the **same sequence** as the queries (`Set - GetSimN`). - If one query fails, its error is captured in the response while others continue execution. - Pipelines improve efficiency by minimizing round-trips to the AI server. - Useful in workflows where embeddings are created or updated and then immediately used for **similarity search**. ## Development & Testing The Go SDK provides a set of Makefile targets and CI/CD workflows to simplify development, testing, and release management. These commands are primarily used by developers working on the SDK itself (rather than SDK consumers). ### Local Development Commands ### Install Dependencies `make install-dependencies` - Installs all required Go modules and external tools needed to build and test the SDK. ### Format Code `make format` - Runs formatting tools to enforce a consistent code style across the codebase. ### Run Tests (Sequential) `make test` - Executes all unit and integration tests sequentially to validate correctness of implementations. ### Lint Check `make lint-check` - Runs static analysis tools to ensure code quality, style consistency, and to catch potential issues before release. ## Deploy to GitHub Releases The SDK uses GitHub Actions CI/CD to automate release publishing. Deployment steps: 1. **Bump version** in go.mod to reflect the new release. **Create a Git tag** using semantic versioning format: `git tag vX.Y.Z` `git push origin vX.Y.Z` Pushing the tag triggers the **CI/CD pipeline**, which: - Builds the SDK - Runs all tests - Publishes the release artifacts to GitHub Releases --- # FILE: client-libraries/go/convert-to-embeddings.md --- title: Convert to Embeddings sidebar_position: 20 --- # Convert to Embeddings The `ConvertStoreInputToEmbeddings` RPC converts raw inputs (text, images, audio) into embeddings using a specified AI model, without storing them in a database. ## Method Signature ```go ConvertStoreInputToEmbeddings( ctx context.Context, in *ConvertStoreInputToEmbeddings, opts ...grpc.CallOption, ) (*StoreInputToEmbeddingsList, error) ``` ## Parameters The `ConvertStoreInputToEmbeddings` request contains: - `StoreInputs`: Slice of inputs to convert (text, images, or audio) - `PreprocessAction`: How to preprocess inputs - `Model`: AI model to use for conversion - `ExecutionProvider` (optional): Execution provider (CPU, CUDA, etc.) - `ModelParams` (optional): Model-specific parameters ## Basic Example ```go package main import ( "context" "fmt" "log" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" pb "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/models" "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/preprocess" "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" ) func main() { conn, err := grpc.Dial("localhost:1370", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Fatalf("Failed to connect: %v", err) } defer conn.Close() client := ai_service.NewAIServiceClient(conn) ctx := context.Background() inputs := []*keyval.StoreInput{ {Value: &keyval.StoreInput_RawString{RawString: "Hello world"}}, {Value: &keyval.StoreInput_RawString{RawString: "Goodbye world"}}, } response, err := client.ConvertStoreInputToEmbeddings(ctx, &pb.ConvertStoreInputToEmbeddings{ StoreInputs: inputs, PreprocessAction: preprocess.PreprocessAction_NoPreprocessing, Model: models.AiModel_ALL_MINI_LM_L6_V2, }) if err != nil { log.Fatalf("Failed to convert: %v", err) } // Access embeddings for _, item := range response.Values { if single := item.GetSingle(); single != nil { if embedding := single.GetEmbedding(); embedding != nil { fmt.Printf("Embedding size: %d\n", len(embedding.Key)) } } } } ``` ## Face Detection with Metadata (Buffalo-L / SFace) Starting from version 0.2.2, face detection models return **bounding box metadata** alongside embeddings: ```go package main import ( "context" "fmt" "log" "os" "strconv" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" pb "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/models" "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/preprocess" "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" ) func main() { conn, err := grpc.Dial("localhost:1370", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Fatalf("Failed to connect: %v", err) } defer conn.Close() client := ai_service.NewAIServiceClient(conn) ctx := context.Background() // Load image imageBytes, err := os.ReadFile("group_photo.jpg") if err != nil { log.Fatalf("Failed to read image: %v", err) } inputs := []*keyval.StoreInput{ {Value: &keyval.StoreInput_Image{Image: imageBytes}}, } response, err := client.ConvertStoreInputToEmbeddings(ctx, &pb.ConvertStoreInputToEmbeddings{ StoreInputs: inputs, PreprocessAction: preprocess.PreprocessAction_ModelPreprocessing, Model: models.AiModel_BUFFALO_L, }) if err != nil { log.Fatalf("Failed to convert: %v", err) } // Process each detected face for _, item := range response.Values { if multiple := item.GetMultiple(); multiple != nil { fmt.Printf("Detected %d faces\n", len(multiple.Embeddings)) for _, faceData := range multiple.Embeddings { // Access embedding if embedding := faceData.GetEmbedding(); embedding != nil { fmt.Printf("Embedding size: %d\n", len(embedding.Key)) } // Access bounding box metadata if metadata := faceData.GetMetadata(); metadata != nil { if bboxX1, ok := metadata.Value["bbox_x1"]; ok { if rawStr := bboxX1.GetValue().(*keyval.MetadataValue_RawString); rawStr != nil { x1, _ := strconv.ParseFloat(rawStr.RawString, 32) fmt.Printf("Face bbox x1: %.3f\n", x1) } } if confidence, ok := metadata.Value["confidence"]; ok { if rawStr := confidence.GetValue().(*keyval.MetadataValue_RawString); rawStr != nil { conf, _ := strconv.ParseFloat(rawStr.RawString, 32) fmt.Printf("Confidence: %.3f\n", conf) } } } } } } } ``` ## Metadata Fields (Face Detection Models) For Buffalo-L and SFace models, each detected face includes: | Field | Type | Range | Description | |-------|------|-------|-------------| | `bbox_x1` | float32 | 0.0-1.0 | Normalized x-coordinate of top-left corner | | `bbox_y1` | float32 | 0.0-1.0 | Normalized y-coordinate of top-left corner | | `bbox_x2` | float32 | 0.0-1.0 | Normalized x-coordinate of bottom-right corner | | `bbox_y2` | float32 | 0.0-1.0 | Normalized y-coordinate of bottom-right corner | | `confidence` | float32 | 0.0-1.0 | Detection confidence score | Coordinates are normalized to 0-1 range. To convert to pixel coordinates: ```go import "image" img, _, err := image.Decode(file) bounds := img.Bounds() width := bounds.Dx() height := bounds.Dy() pixelX1 := int(bboxX1 * float32(width)) pixelY1 := int(bboxY1 * float32(height)) ``` ## Using Model Parameters Face detection models support tuning via `ModelParams`: ```go response, err := client.ConvertStoreInputToEmbeddings(ctx, &pb.ConvertStoreInputToEmbeddings{ StoreInputs: inputs, PreprocessAction: preprocess.PreprocessAction_ModelPreprocessing, Model: models.AiModel_BUFFALO_L, ModelParams: map[string]string{ "confidence_threshold": "0.9", // Higher = fewer faces }, }) ``` ## Response Structure ```go type StoreInputToEmbeddingsList struct { Values []*SingleInputToEmbedding } type SingleInputToEmbedding struct { Input *StoreInput // Variant is one of: // - *SingleInputToEmbedding_Single (for text/image models) // - *SingleInputToEmbedding_Multiple (for face detection) } type EmbeddingWithMetadata struct { Embedding *StoreKey // The embedding vector Metadata *StoreValue // Optional metadata (e.g., bounding boxes) } type MultipleEmbedding struct { Embeddings []*EmbeddingWithMetadata // One per detected face } ``` ## Use Cases - **Testing models**: Quickly test how different inputs are embedded - **Batch processing**: Generate embeddings for analysis without storage - **Face detection**: Extract face locations and embeddings from photos - **Quality control**: Filter low-confidence detections before storage - **Visualization**: Draw bounding boxes on images using metadata --- # FILE: client-libraries/go/go-specific-resources.md --- title: Go Specific Resources sidebar_posiiton: 1 --- ## QuickStart - Setup Follow these steps to set up the Ahnlich Go SDK on your local machine. ## Install Go ```go # Check if you already have Go installed: go version ``` _Example output:_ ```go go version go1.20.5 linux/amd64 ``` If Go is not installed, follow the steps for your operating system: ### macOS ```go # Install using Homebrew: brew install go ``` ```go # Verify the installation: go version ``` ### Windows 1. Download the MSI installer from the official Go website. 2. Run the installer and follow the setup wizard. **Open Command Prompt or PowerShell and check:** ```go go version ``` ### Linux Download the Go tarball from the official Go website. **Source Code Source Code Example:** ```go # Download Go wget https://go.dev/dl/go1.20.5.linux-amd64.tar.gz # Extract to /usr/local sudo tar -C /usr/local -xzf go1.20.5.linux-amd64.tar.gz # Add Go to your PATH (append this line to ~/.bashrc or ~/.zshrc) export PATH=$PATH:/usr/local/go/bin # Reload your shell (or run: source ~/.bashrc) and verify installation go version ``` ## Install the Ahnlich Go SDK The Ahnlich Go SDK provides the client libraries you’ll use to connect with Ahnlich DB (vector database) and Ahnlich AI (semantic embedding/search). You can add it to your project in two ways: ```go Install with go get ``` Run the following command in your project directory: ```go Using go get github.com/deven96/ahnlich/sdk/ahnlich-client-go@vX.Y.Z ``` - Replace vX.Y.Z with the latest version of the SDK. - This will install support for both DB and AI request/response types. - After running it, you should see the SDK listed in your go.mod file. Or Add it manually in **go.mod** Open your go.mod file and add the following line inside the require block: ```go require ( github.com/deven96/ahnlich/sdk/ahnlich-client-go vX.Y.Z ) ``` - Again, replace vX.Y.Z with the desired version. - Once added, **run:** ```go go mod tidy ``` This will download the SDK and clean up your module dependencies. ### What’s Included in the SDK? The Ahnlich Go SDK contains everything you need to work with both the DB service and the AI service. It provides strongly-typed request and response objects that you can use to interact with Ahnlich DB AND AI. These types let you perform operations such as: #### Request-DB for: - Creating and deleting stores - Inserting and updating vectors - Running similarity search queries - Managing filters and predicates for advanced search #### Request-AI for: - Generating vector embeddings from text or binary data - Running embedding-based queries - Using AI-powered similarity for enhanced search #### With these request/response types, you can build Go applications that: - Use **Ahnlich DB** for exact vector similarity search. - Use **Ahnlich AI** for semantic embeddings and intelligent search. - Or combine both for hybrid workflows. Once installed, you can start building Go applications that use Ahnlich DB for vector search and Ahnlich AI for semantic embeddings. ### Package Information This module provides: - **gRPC service** stubs for both **DB** and **AI**. - **Pipeline utilities** for batching RPC calls efficiently. #### DB gRPC types All DB request/response messages live under: _Example_ ```go import "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db" ``` - These cover operations like: ... Creating and deleting stores ... Inserting and updating vectors ... Running similarity searches #### AI gRPC types All AI request/response messages live under: ```go import "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai" ``` - These cover operations like: ... Embedding text or binary data ... Querying embeddings ... Powering semantic search - Pipeline Utilities Utilities for **batching RPC calls**, allowing efficient handling of multiple requests in a single operation. ## Initialization The Ahnlich Go SDK provides client implementations for both **DB** and **AI** services. Before issuing queries or embedding requests, you must first initialize a client connection over gRPC. ### Client Setup Both DB and AI clients share a similar initialization pattern: - Define the **server address** (default `DB: 127.0.0.1:1369`, `AI: 127.0.0.1:1370`). - Use `grpc.DialContext` with insecure credentials for local development. - Create a new service client from the generated gRPC stubs. - Always close the client connection when finished. This ensures proper resource management and clean shutdown. ### DB Client The DB client allows you to connect to an Ahnlich DB instance and perform vector operations such as creating stores, inserting embeddings, or querying for similarity. package example
Click to expand source code ```go import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" algorithm "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/algorithm/algorithms" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" predicates "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/predicates" ) const ServerAddr = "127.0.0.1:1369" // ExampleDBClient holds the gRPC connection and client. type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient } // NewDBClient connects to the Ahnlich DB server. func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.DialContext( ctx, ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock(), ) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client}, nil } // Close closes the gRPC connection. func (c *ExampleDBClient) Close() error { return c.conn.Close() } ```
### AI Client The AI client connects to the Ahnlich AI service, which handles embedding generation and semantic queries.
Click to expand source code ```go package example import ( "context" "fmt" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" algorithm "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/algorithm/algorithms" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aimodel "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/models" keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" preprocess "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/preprocess" predicates "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/predicates" ) const AIAddr = "127.0.0.1:1370" // ExampleAIClient holds the gRPC connection and AI client. type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient } // NewAIClient connects to the Ahnlich AI server. func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext( ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock(), ) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client}, nil } // Close closes the gRPC connection. func (c *ExampleAIClient) Close() error { return c.conn.Close() } ```
### Connection Pooling Go’s gRPC client reuses connections under the hood. This means you can safely create multiple clients from a single connection without additional overhead. For applications that require higher throughput or advanced resource control, you can manage your own pool of gRPC connections. This allows you to balance requests across multiple servers and fine-tune concurrency limits. --- # FILE: client-libraries/go/go.md --- title: Go sidebar_position: 10 --- # ⚙️ Go SDK Build Ahnlich Applications with the Go SDK – Vector Storage, Search, and AI tooling. ## Structure ### [Go Specific Resources](/docs/client-libraries/go/go-specific-resources) - Build Ahnlich Applications with the Go SDK - Ahnlich Go Technical Resources - Go SDK Quickstart - Setup Guide - Package Information - Server Response - Initialization ### [Requests – DB](/docs/client-libraries/go/request-db)
#### [Ping](/docs/client-libraries/go/request-db/ping) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Info Server](/docs/client-libraries/go/request-db/info-server) * Description * Source Code Example * Parameters * Returns * Behavior
#### [List Stores](/docs/client-libraries/go/request-db/list-stores) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Create Store](/docs/client-libraries/go/request-db/create-store) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Set](/docs/client-libraries/go/request-db/set) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Upsert](/docs/client-libraries/go/request-db/upsert) * Description * Source Code Example * Parameters * Returns * Behavior
#### [GetSimN](/docs/client-libraries/go/request-db/get-simn) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Get Key](/docs/client-libraries/go/request-db/get-key) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Get By Predicate](/docs/client-libraries/go/request-db/get-by-predicate) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Create Predicate Index](/docs/client-libraries/go/request-db/create-predicate-index) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Drop Predicate Index](/docs/client-libraries/go/request-db/drop-predicate-index) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Delete Key](/docs/client-libraries/go/request-db/delete-key) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Drop Store](/docs/client-libraries/go/request-db/drop-store) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Create Non Linear Algorithm Index](/docs/client-libraries/go/request-db/create-non-linear-algx) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Drop Non Linear Algorithm Index](/docs/client-libraries/go/request-db/drop-non-linear-algx) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Delete Predicate](/docs/client-libraries/go/request-db/delete-predicate) * Description * Source Code Example * Parameters * Returns * Behavior
### [Request – AI](/docs/client-libraries/go/request-ai)
#### [Ping](/docs/client-libraries/go/request-ai/ping) - Description - Source Code Example - Parameters - Returns - Behavior
#### [Info Server](/docs/client-libraries/go/request-ai/info-server) - Description - Source Code Example - Parameters - Returns - Behavior
#### [List Stores](/docs/client-libraries/go/request-ai/list-stores) - Description - Source Code Example - Parameters - Returns - Behavior
#### [Create Store](/docs/client-libraries/go/request-ai/create-store) - Description - Source Code Example - Parameters - Returns - Behavior
#### [Set](/docs/client-libraries/go/request-ai/set) - Description - Source Code Example - Parameters - Returns - Behavior
#### [Upsert](/docs/client-libraries/go/request-ai/upsert) - Description - Source Code Example - Parameters - Returns - Behavior
#### [GetSimN](/docs/client-libraries/go/request-ai/get-simn) - Description - Source Code Example - Parameters - Returns - Behavior
#### [Get By Predicate](/docs/client-libraries/go/request-ai/get-by-predicate) - Description - Source Code Example - Parameters - Returns - Behavior
#### [Create Predicate Index](/docs/client-libraries/go/request-ai/create-predicate-index) - Description - Source Code Example - Parameters - Returns - Behavior
#### [Drop Predicate Index](/docs/client-libraries/go/request-ai/drop-predicate-index) - Description - Source Code Example - Parameters - Returns - Behavior
#### [Delete Key](/docs/client-libraries/go/request-ai/delete-key) - Description - Source Code Example - Parameters - Returns - Behavior
#### [Drop Store](/docs/client-libraries/go/request-ai/drop-store) - Description - Source Code Example - Parameters - Returns - Behavior
#### [Create Non Linear Algorithm Index](/docs/client-libraries/go/request-ai/create-non-linear-algx) - Description - Source Code Example - Parameters - Returns - Behavior
#### [Drop Non Linear Algorithm Index](/docs/client-libraries/go/request-ai/drop-non-linear-algx) - Description - Source Code Example - Parameters - Returns - Behavior
### [Bulk Requests](/docs/client-libraries/go/bulk-requests) - Description - Source Code Example - Parameters - Returns - Behavior ### [Development & Testing](/docs/client-libraries/go/bulk-requests) - Description ### [Deploy to Github Releases](/docs/client-libraries/go/bulk-requests) - Description ### [Type Meanings](/docs/client-libraries/go/type-meanings) - Store Key - Store Value - Store Predicates (Predicate Indices) - Predicates - PredicateConditions - MetadataValue - Search Input - AIModels - AIStoreType --- # FILE: client-libraries/go/request-ai/create-non-linear-algx.md --- title: Create Non-Linear algorithm Index --- # Create Non-Linear algorithm Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `Create Non Linear Algorithm Index` request allows the AI service to build specialized non-linear search indices (e.g., HNSW) on top of vector embeddings that have already been stored in an AI-managed store. Each index type is specified using a `NonLinearIndex` message with a `HNSWConfig`. Non-linear indices are essential when scaling similarity search, as they provide faster and more efficient retrieval of high-dimensional vectors compared to brute-force search. When using this API, you explicitly tell the AI proxy to create an auxiliary search structure that accelerates queries. The AI system first embeds the raw input (e.g., text, image), stores the vectors in the underlying DB, and then attaches the non-linear algorithm index for faster retrieval. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" nonlinear "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/algorithm/nonlinear" ) const AIAddr = "127.0.0.1:1370" // ExampleAIClient wraps the connection + AIService client func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } // NewAIClient connects to the AI server func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock(), ) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- CreateNonLinearAlgorithmIndex ---- func (c *ExampleAIClient) exampleCreateNonLinearIndexAI() error { // Create an HNSW index (with optional config) _, err := c.client.CreateNonLinearAlgorithmIndex(c.ctx, &aiquery.CreateNonLinearAlgorithmIndex{ Store: "ai_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted NonLinearIndices: []*nonlinear.NonLinearIndex{ {Index: &nonlinear.NonLinearIndex_Hnsw{Hnsw: &nonlinear.HNSWConfig{}}}, }, }) if err != nil { return err } fmt.Println(" Successfully created NonLinearAlgorithm index: HNSW on store ai_store") return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf(" Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleCreateNonLinearIndexAI(); err != nil { log.Fatalf(" CreateNonLinearAlgorithmIndex failed: %v", err) } } ```
## Behavior If the store already has embeddings, the index is constructed on them. Future inserts will also be indexed automatically. --- # FILE: client-libraries/go/request-ai/create-predicate-index.md --- title: Create Predicate Index --- # Create Predicate Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `CreatePredicateIndex` request enhances retrieval efficiency by building indexes on specified metadata fields within an AI-managed store. By indexing metadata keys, subsequent GetByPredicate requests can be executed more efficiently, especially on large datasets. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" ) const AIAddr = "127.0.0.1:1370" func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- CreatePredIndex ---- func (c *ExampleAIClient) exampleCreatePredIndexAI() error { _, err := c.client.CreatePredIndex(c.ctx, &aiquery.CreatePredIndex{ Store: "ai_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Predicates: []string{"f"}, }) if err != nil { return err } fmt.Println(`Predicate index created successfully with logic: Predicates: []string{"f"},`) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf(" Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleCreatePredIndexAI(); err != nil { log.Fatalf(" CreatePredIndex failed: %v", err) } } ```
## Behavior - After this index is created, queries filtering on the `"f"` field (e.g., via `GetByPredicate`) are **faster** and more scalable. - Multiple predicate fields can be indexed by including them in the `Predicates` array. - Attempting to recreate an existing index may return an error depending on configuration. - Predicate indexes complement embedding-based retrieval, enabling **hybrid query patterns** that combine metadata filtering with similarity search. --- # FILE: client-libraries/go/request-ai/create-store.md --- title: Create Store --- # Create Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description `CreateStore` is a **provisioning request** used to define a new AI-backed store within the Ahnlich AI proxy. Unlike the raw DB `CreateStore`, this AI-specific request requires model selection for **both indexing** and **querying**. These models determine how raw inputs (e.g., text, images) are embedded and how queries against the store are interpreted. Key concepts: - **IndexModel**: the embedding model used to transform stored data into vector form. - **QueryModel**: the embedding model used to transform incoming queries into vector form before comparison. - **Store Name**: a unique identifier for the logical collection of embeddings. This design allows developers to separate how data is stored vs. how queries are expressed. In most cases, the same model is chosen for both roles (as in the example), but they can differ if needed for domain-specific optimization. ## Use cases: - Creating a dedicated store for text embeddings using a general-purpose model. - Initializing multiple stores with different models for experimentation. - Supporting hybrid workflows where one model indexes the data and another interprets queries. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aimodel "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/models" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" nonlinear "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/algorithm/nonlinear" ) const AIAddr = "127.0.0.1:1370" // ExampleAIClient holds the gRPC connection and AI client. func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } // NewAIClient connects to the AI server and returns a client. func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } // Close closes the gRPC connection. func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- CreateStore Example ---- // Create a new store for AI operations. func (c *ExampleAIClient) exampleCreateStoreAI() error { _, err := c.client.CreateStore(c.ctx, &aiquery.CreateStore{ Store: "ai_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted QueryModel: aimodel.AIModel_ALL_MINI_LM_L6_V2, IndexModel: aimodel.AIModel_ALL_MINI_LM_L6_V2, Predicates: []string{}, // Optional: metadata fields to index for filtering NonLinearIndices: []*nonlinear.NonLinearIndex{}, // Optional: non-linear algorithms (e.g., HNSW) for faster search ErrorIfExists: true, // Return error if store already exists StoreOriginal: true, // Store original input (needed for key deletion) }) if err != nil { return err } fmt.Println(" AI Store created: ai_store01") return nil } // ---- MAIN ---- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf("Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleCreateStoreAI(); err != nil { log.Fatalf("CreateStore failed: %v", err) } } ```
Behavior - On success, the AI proxy registers a new AI-backed store configured with the provided `IndexModel` and `QueryModel` (the models determine how raw inputs are converted to embeddings for indexing and querying). - The `QueryModel` and `IndexModel` fields use the `aimodel` enum values supplied in the request; the proxy interprets those enums according to its supported model set. - Errors indicate the operation did not complete (for example, validation failure, naming conflict, or server-side error). The exact failure modes and error payloads are determined by the AI proxy implementation. --- # FILE: client-libraries/go/request-ai/delete-key.md --- title: Delete Key --- # Delete Key ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `DeleteKey` request removes specific entries from an AI-managed store using their **input keys**. Unlike dropping the store entirely, this operation targets only selected records. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" ) const AIAddr = "127.0.0.1:1370" // ExampleAIClient wraps the connection + AIService client func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } // NewAIClient creates and connects the AI client func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock(), ) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- DelKey ---- func (c *ExampleAIClient) exampleDeleteKeyAI() error { _, err := c.client.DelKey(c.ctx, &aiquery.DelKey{ Store: "ai_store01", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Keys: []*keyval.StoreInput{ {Value: &keyval.StoreInput_RawString{RawString: "X"}}, }, }) if err != nil { return err } fmt.Println(" Successfully deleted key 'X' from store ai_store") return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf(" Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleDeleteKeyAI(); err != nil { log.Fatalf(" DeleteKey failed: %v", err) } } ```
## Behavior - Only the entries associated with the provided key(s) are removed. - Supports deleting multiple keys at once by passing more StoreInput values. - If a key does not exist, behavior depends on configuration—it may silently ignore or return an error. - Useful for targeted cleanup without affecting the rest of the store. --- # FILE: client-libraries/go/request-ai/delete-predicate.md --- title: Delete Predicate --- # Delete Predicate ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `DeletePredicate` request removes entries from an AI store that match a given predicate condition. This is a passthrough operation to the underlying DB service. Instead of deleting by vector key, this operation lets you **filter deletions based on metadata values** (e.g., labels, tags, or custom attributes). ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" predicates "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/predicates" ) const ServerAddr = "127.0.0.1:1370" func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", ServerAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } // -------------------- Delete Predicate -------------------- func (c *ExampleAIClient) exampleDeletePredicate() error { condition := &predicates.PredicateCondition{ Kind: &predicates.PredicateCondition_Value{ Value: &predicates.Predicate{ Kind: &predicates.Predicate_Equals{ Equals: &predicates.Equals{ Key: "category", Value: &metadata.MetadataValue{ Value: &metadata.MetadataValue_RawString{ RawString: "archived", }, }, }, }, }, }, } _, err := c.client.DelPred(c.ctx, &aiquery.DelPred{ Store: "my_ai_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Condition: condition, }) if err != nil { return err } fmt.Println("Deleted entries matching predicate from AI store: my_ai_store") return nil } // -------------------- Main -------------------- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf("Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleDeletePredicate(); err != nil { log.Fatalf("DeletePredicate failed: %v", err) } } ```
## What the code does - Builds a predicate condition to match all entries where category == "archived". - Sends a DelPred request against the AI store "my_ai_store". - This is a passthrough to the underlying DB service, removing all vectors/entries that satisfy the predicate. --- # FILE: client-libraries/go/request-ai/drop-non-linear-algx.md --- title: Drop Non-Linear Algorithm Index --- # Drop Non-Linear Algorithm Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `Drop Non Linear Algorithm Index` request removes a previously created non-linear index from a store. This operation is important when you want to reclaim resources, update to a different algorithm, or revert to default brute-force scanning for search. By dropping the index, the AI proxy no longer uses the HNSW (or any other specified algorithm) for accelerating queries. Instead, searches will fall back to direct vector comparisons. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" nonlinear "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/algorithm/nonlinear" ) const AIAddr = "127.0.0.1:1370" // ExampleAIClient wraps the connection + AIService client func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } // NewAIClient connects to the AI server func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock(), ) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- DropNonLinearAlgorithmIndex standalone ---- func (c *ExampleAIClient) exampleDropNonLinearIndexAI() error { _, err := c.client.DropNonLinearAlgorithmIndex(c.ctx, &aiquery.DropNonLinearAlgorithmIndex{ Store: "ai_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted NonLinearIndices: []nonlinear.NonLinearAlgorithm{nonlinear.NonLinearAlgorithm_HNSW}, ErrorIfNotExists: true, }) if err != nil { return err } fmt.Println(" Successfully dropped NonLinearAlgorithm index: HNSW from store ai_store") return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf(" Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleDropNonLinearIndexAI(); err != nil { log.Fatalf(" DropNonLinearAlgorithmIndex failed: %v", err) } } ```
## Behavior: - **Target Store**: Operates on `"ai_store"`. - **Drop Target**: Removes the **HNSW** index from the store. - **Error Handling**: With `ErrorIfNotExists`: `true`, the request will fail if no such index exists. - **Effect**: Queries against the store will **no longer benefit from HNSW acceleration**, reverting to full-scan or other available indices. --- # FILE: client-libraries/go/request-ai/drop-predicate-index.md --- title: Drop Predicate Index --- # Drop Predicate Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `DropPredicateIndex` request removes an existing predicate index from a given AI-managed store. Predicate indexes are used to accelerate metadata-based queries (`GetByPredicate`), and dropping them reverts queries to a slower scan-based evaluation. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" ) const AIAddr = "127.0.0.1:1370" // ExampleAIClient wraps the connection + AIService client func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } // NewAIClient creates and connects the AI client func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock(), ) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- DropPredIndex standalone ---- func (c *ExampleAIClient) exampleDropPredicateIndexAI() error { _, err := c.client.DropPredIndex(c.ctx, &aiquery.DropPredIndex{ Store: "ai_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Predicates: []string{"f"}, ErrorIfNotExists: true, }) if err != nil { return err } fmt.Println(" Successfully dropped predicate index for Predicates: [\"f\"] in store ai_store") return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf(" Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleDropPredicateIndexAI(); err != nil { log.Fatalf(" DropPredIndex failed: %v", err) } } ```
## Behavior - After removal, queries filtering on `"f"` remain valid but may become slower due to lack of indexing. - Safe for cleaning up unused indexes. - If `ErrorIfNotExists` is set to `false`, the operation is idempotent (no error even if index is missing). - Recommended when predicate-based queries on `"f"` are no longer needed. --- # FILE: client-libraries/go/request-ai/drop-store.md --- title: Drop Store --- # Drop Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `DropStore` request deletes an entire AI-managed store, including its embeddings, metadata, and indexes. This is a destructive operation and should be used when a store is no longer required. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" ) const AIAddr = "127.0.0.1:1370" // ExampleAIClient wraps the connection + AIService client func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } // NewAIClient creates and connects the AI client func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock(), ) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- DropStore ---- func (c *ExampleAIClient) exampleDropStoreAI() error { _, err := c.client.DropStore(c.ctx, &aiquery.DropStore{ Store: "ai_store01", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted ErrorIfNotExists: true, }) if err != nil { return err } fmt.Println(" Successfully dropped store: ai_store") return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf(" Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleDropStoreAI(); err != nil { log.Fatalf(" DropStore failed: %v", err) } } ```
## Behavior - Permanently deletes all embeddings, metadata, and indexes in the store. - After execution, any queries targeting `"ai_store"` will fail until the store is recreated. - Useful for reclaiming resources or resetting the environment. - If `ErrorIfNotExists` is set to `false`, the operation becomes idempotent (no error on missing stores). --- # FILE: client-libraries/go/request-ai/get-by-predicate.md --- title: Get by Predicate --- # Get by Predicate ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `GetByPredicate` request-ai retrieves entries from an AI-managed store by applying **metadata-based filtering**. Unlike similarity search (`GetSimN`), which operates on vector embeddings, predicate queries operate on **stored metadata attributes**. This enables developers to fetch records that satisfy specific metadata conditions, regardless of their embedding similarity. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" predicates "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/predicates" ) const AIAddr = "127.0.0.1:1370" func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- GetByPredicate standalone ---- func (c *ExampleAIClient) exampleGetByPredicateAI() error { cond := &predicates.PredicateCondition{ Kind: &predicates.PredicateCondition_Value{ Value: &predicates.Predicate{ Kind: &predicates.Predicate_Equals{ Equals: &predicates.Equals{ Key: "f", Value: &metadata.MetadataValue{ Value: &metadata.MetadataValue_RawString{RawString: "v"}, }, }, }, }, }, } resp, err := c.client.GetPred(c.ctx, &aiquery.GetPred{ Store: "ai_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Condition: cond, }) if err != nil { return err } fmt.Println(" AI GetByPredicate Response:", resp.Entries) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf(" Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleGetByPredicateAI(); err != nil { log.Fatalf(" GetByPredicate failed: %v", err) } } ```
## What the code does - Constructs a **predicate condition** that checks if the metadata field `"f"` equals the raw string `"v"`. - Issues a `GetByPredicate` request against "`ai_store`". - On success, prints the retrieved entries (`resp.Entries`); otherwise, returns the error. ## Behavior - The AI proxy evaluates the condition against stored entries’ metadata. - Only records with metadata key `"f"` equal to `"v"` are returned. - Does not perform embedding similarity—this is purely metadata-based retrieval. - If no matching entries exist, the response is valid but empty. - Useful for filtering results in combination with embedding-based searches. --- # FILE: client-libraries/go/request-ai/get-key.md --- title: Get Key --- # Get Key ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `GetKey` request retrieves specific entries from an AI store by their keys. Unlike similarity search, this is a **direct lookup** operation that returns exact matches for the provided keys along with their metadata and stored values. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" ) const AIAddr = "127.0.0.1:1370" func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- GetKey Example ---- func (c *ExampleAIClient) exampleGetKey() error { resp, err := c.client.GetKey(c.ctx, &aiquery.GetKey{ Store: "ai_store01", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Keys: []*keyval.StoreInput{ {Value: &keyval.StoreInput_RawString{RawString: "Adidas Yeezy"}}, {Value: &keyval.StoreInput_RawString{RawString: "Nike Air Jordans"}}, }, }) if err != nil { return err } fmt.Println("GetKey Results:") for _, entry := range resp.Entries { fmt.Printf(" Key: %v\n", entry.Key) fmt.Printf(" Value: %v\n", entry.Value) } return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf("Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleGetKey(); err != nil { log.Fatalf("GetKey failed: %v", err) } } ```
## Behavior - **Store** – The target AI store that must already exist. - **Keys** – List of `StoreInput` keys to retrieve. Each key is the original input (text/image) that was used when storing the data. - The response contains entries with the stored data and metadata for each found key. - If a key doesn't exist in the store, it won't appear in the results (no error is returned for missing keys). - This is useful for retrieving known items by their exact key, such as looking up specific documents or images you previously stored. --- # FILE: client-libraries/go/request-ai/get-simn.md --- title: GetSimN --- # GetSimN ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `GetSimN` request-ai performs a **nearest-neighbor search** using a **raw query input**. The AI proxy embeds the raw query with the store’s configured **QueryModel**, sends the similarity search to the DB, and returns the top-N matches. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" algorithms "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/algorithm/algorithms" preprocess "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/preprocess" predicates "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/predicates" ) const AIAddr = "127.0.0.1:1370" func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } // Helper to unwrap Key (StoreInput) func unwrapKey(k *keyval.StoreInput) string { if k == nil { return "" } switch v := k.Value.(type) { case *keyval.StoreInput_RawString: return v.RawString default: return fmt.Sprintf("%v", v) } } // Helper to unwrap Value (StoreValue) func unwrapValue(v *keyval.StoreValue) map[string]string { result := make(map[string]string) if v == nil { return result } for k, val := range v.Value { switch mv := val.Value.(type) { case *metadata.MetadataValue_RawString: result[k] = mv.RawString default: result[k] = fmt.Sprintf("%v", mv) } } return result } // ---- GetSimN ---- func (c *ExampleAIClient) exampleGetSimNAI() error { resp, err := c.client.GetSimN(c.ctx, &aiquery.GetSimN{ Store: "ai_store01", // must already exist and have data Schema: stringPtr("analytics"), // Optional: defaults to public when omitted SearchInput: &keyval.StoreInput{Value: &keyval.StoreInput_RawString{RawString: "X"}}, Condition: nil, // Optional: filter results using predicates ClosestN: 3, Algorithm: algorithms.Algorithm_CosineSimilarity, PreprocessAction: preprocess.PreprocessAction_ModelPreprocessing, // Apply model's preprocessing ExecutionProvider: nil, // Optional: e.g., ExecutionProvider_CUDA for GPU acceleration }) if err != nil { return err } fmt.Println(" AI GetSimN Response:") for i, entry := range resp.Entries { fmt.Printf(" #%d Key=%s Value=%v\n", i+1, unwrapKey(entry.Key), unwrapValue(entry.Value)) } return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf(" Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleGetSimNAI(); err != nil { log.Fatalf(" GetSimN failed: %v", err) } } ```
## Behavior - **Store** – The target AI store that must already exist. - **SearchInput** – Raw input (text or image) that gets embedded using the store's QueryModel. - **Condition** – Optional predicate filter to restrict which vectors are considered. Set to `nil` to search all vectors. See [Predicates documentation](/docs/components/predicates). - **ClosestN** – Number of top matches to return. - **Algorithm** – Similarity metric to use (`CosineSimilarity`, `EuclideanDistance`, `DotProductSimilarity`). - **PreprocessAction** – Controls input preprocessing: - `ModelPreprocessing` – Apply model's built-in preprocessing (recommended for most cases) - `NoPreprocessing` – Skip preprocessing (use if you've already preprocessed the input) - **ExecutionProvider** – Optional hardware acceleration (e.g., `CUDA`, `TensorRT`, `CoreML`). Set to `nil` to use default CPU execution. - The AI proxy **embeds** the raw `SearchInput` with the store's **QueryModel** and forwards the search to the DB. - The response contains up to `ClosestN` matching entries (`resp.Entries`) from the store (entries include stored vectors' associated data/metadata). - Results depend on what has been previously ingested (e.g., via AI `Set` or direct DB ingestion). - If no neighbors are found, the call succeeds with an empty result set; failures return a non-nil error. --- # FILE: client-libraries/go/request-ai/get-store.md --- title: Get Store --- # Get Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `GetStore` request retrieves detailed information about a specific AI store by name, including the configured models and optional underlying DB store information. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" ) const AIAddr = "127.0.0.1:1370" func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } func (c *ExampleAIClient) exampleGetStore() error { resp, err := c.client.GetStore(c.ctx, &aiquery.GetStore{ Store: "ai_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted }) if err != nil { return err } fmt.Printf("Store name: %s\n", resp.Name) fmt.Printf("Query model: %v\n", resp.QueryModel) fmt.Printf("Index model: %v\n", resp.IndexModel) fmt.Printf("Embedding size: %d\n", resp.EmbeddingSize) fmt.Printf("Dimension: %d\n", resp.Dimension) fmt.Printf("Predicate indices: %v\n", resp.PredicateIndices) if resp.DbInfo != nil { fmt.Printf("DB store size: %d bytes\n", resp.DbInfo.SizeInBytes) } return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf("Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleGetStore(); err != nil { log.Fatalf("GetStore failed: %v", err) } } ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `Store` | `string` | Yes | The name of the AI store to retrieve | ## Response: AIStoreInfo | Field | Type | Description | |-------|------|-------------| | `Name` | `string` | Store name | | `QueryModel` | `AIModel` | AI model used for query embeddings | | `IndexModel` | `AIModel` | AI model used for index embeddings | | `EmbeddingSize` | `uint64` | Number of stored embeddings | | `Dimension` | `uint32` | Vector dimension (determined by model) | | `PredicateIndices` | `[]string` | List of indexed predicate keys | | `DbInfo` | `*StoreInfo` | Underlying DB store info (optional) | ## Notes - Returns an error if the store does not exist - The `DbInfo` field is present when the AI proxy is connected to a DB instance - Use `ListStores` to get information about AI stores in a schema --- # FILE: client-libraries/go/request-ai/info-server.md --- title: Info Server --- # Info Server ## Description `InfoServer` is a lightweight **introspection request** that allows a client to query the **Ahnlich AI proxy** for its current status and metadata. This request does not modify any state; rather, it provides insights into the AI proxy’s operational environment. This can include details such as: - Server identification and build information. - Current runtime configuration (as exposed by the server). - Status checks to confirm the AI service is alive and responsive. Developers typically use this request in monitoring or debugging workflows to validate that the AI proxy is online and correctly configured before sending heavier workloads (like store creation or query execution). ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" ) const AIAddr = "127.0.0.1:1370" // ExampleAIClient holds the gRPC connection and AI client. type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } // NewAIClient connects to the AI server and returns a client. func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } // Close closes the gRPC connection. func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- InfoServer Example ---- // Retrieve server information such as address, version, limits. func (c *ExampleAIClient) exampleInfoServerAI() error { resp, err := c.client.InfoServer(c.ctx, &aiquery.InfoServer{}) if err != nil { return err } fmt.Println(" AI InfoServer:", resp) return nil } // ---- MAIN ---- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf("Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleInfoServerAI(); err != nil { log.Fatalf("InfoServer failed: %v", err) } } ```
## Behavior - The call is **read-only** and returns whatever server-level metadata the AI proxy exposes (identifiers, status/configuration details as provided by the service). - A successful call yields a populated `resp`; the exact fields depend on the AI proxy’s implementation. - A non-nil error indicates the RPC failed (network, server-side error, or other RPC-level failure). - The call is intended to be lightweight and non-destructive. --- # FILE: client-libraries/go/request-ai/list-connected-clients.md --- title: List Connected Clients --- # List Connected Clients ## Description The `ListConnectedClients` request allows you to query the AI server for all currently connected clients. This is particularly useful for **monitoring, debugging,** and ensuring that multiple services or developers interacting with the same Ahnlich AI instance are tracked properly. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" ) const ServerAddr = "127.0.0.1:1370" type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", ServerAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } func (c *ExampleAIClient) exampleListConnectedClients() error { resp, err := c.client.ListClients(c.ctx, &aiquery.ListClients{}) if err != nil { return err } fmt.Println("Connected Clients:", resp.Clients) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf("Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleListConnectedClients(); err != nil { log.Fatalf("ListConnectedClients failed: %v", err) } } ```
This method requests the list of all currently connected clients to the Ahnlich AI server. The response contains client information including addresses. --- # FILE: client-libraries/go/request-ai/list-stores.md --- title: List Stores --- # List Stores ## Schema `ListStores` accepts an optional `schema` field. When it is omitted, the server lists stores in `public` only; it does not list stores across every schema. Set `schema` to list stores in another schema. ## Description `ListStores` provides a **catalog of AI-managed stores** currently registered in the Ahnlich AI proxy. Each store represents a logical grouping where raw inputs (text, images, etc.) are converted into embeddings using the configured models and then indexed for retrieval. This request allows developers to: - Discover all existing stores available for AI-based operations. - Verify that newly created stores are correctly registered and visible. - Audit what data partitions are accessible through the AI proxy at a given time. It is especially useful in **multi-tenant scenarios**, where different applications or teams may own separate stores, and clients need visibility into what stores exist before performing further operations. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" ) const AIAddr = "127.0.0.1:1370" // ExampleAIClient holds the gRPC connection and AI client. func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } // NewAIClient connects to the AI server and returns a client. func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } // Close closes the gRPC connection. func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- ListStores Example ---- // List stores in the analytics schema on the AI server. func (c *ExampleAIClient) exampleListStoresAI() error { resp, err := c.client.ListStores(c.ctx, &aiquery.ListStores{Schema: stringPtr("analytics")}) if err != nil { return err } fmt.Println(" AI Stores:", resp.Stores) return nil } // ---- MAIN ---- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf("Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleListStoresAI(); err != nil { log.Fatalf("ListStores failed: %v", err) } } ```
## Behavior - The response represents the set of AI-managed stores the proxy reports at the time of the request. - An empty **resp.Stores** means the AI proxy currently reports no configured stores. - The server’s list is authoritative for that moment in time; contents may change between calls. - A non-nil error indicates the request failed and no valid store list was returned. --- # FILE: client-libraries/go/request-ai/ping.md --- title: Ping --- # Ping ## Description The `Ping` request verifies basic connectivity and reachability between your Go client and the **Ahnlich AI proxy**. It’s the simplest call you can make to confirm the AI service is available before sending heavier requests (for example, embedding or create-store operations). ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" ) const AIAddr = "127.0.0.1:1370" // ExampleAIClient holds the gRPC connection and AI client. type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } // NewAIClient connects to the AI server and returns a client. func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } // Close closes the gRPC connection. func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- Ping Example ---- func (c *ExampleAIClient) examplePingAI() error { resp, err := c.client.Ping(c.ctx, &aiquery.Ping{}) if err != nil { return err } fmt.Println(" AI Ping:", resp) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf("Failed to create AI client: %v", err) } defer client.Close() if err := client.examplePingAI(); err != nil { log.Fatalf("Ping failed: %v", err) } } ```
## Behavior A successful call indicates the client can reach and receive a response from the AI proxy. A failure (non-nil err) typically indicates connectivity problems or that the AI proxy is not currently accepting requests. The call should be fast and lightweight — suitable for frequent checks if needed, but treat it as an RPC (avoid unnecessarily tight polling loops). --- # FILE: client-libraries/go/request-ai/purge-stores.md --- title: Purge Stores --- # Purge Stores ## Description Deletes **all vector stores** managed by the AI server, including all embeddings and associated metadata. This is a destructive operation that resets the AI service state, typically used during testing, cleanup, or when starting fresh with new datasets. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" ) const ServerAddr = "127.0.0.1:1370" type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", ServerAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } func (c *ExampleAIClient) examplePurgeStores() error { resp, err := c.client.PurgeStores(c.ctx, &aiquery.PurgeStores{}) if err != nil { return err } fmt.Printf("Purged stores. Deleted count: %d\n", resp.DeletedCount) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf("Failed to create AI client: %v", err) } defer client.Close() if err := client.examplePurgeStores(); err != nil { log.Fatalf("PurgeStores failed: %v", err) } } ```
:::warning This operation is **irreversible**. All stores and their data will be permanently deleted. Use with caution in production environments. ::: --- # FILE: client-libraries/go/request-ai/request-ai.md --- title: Request AI sidebar_posiiton: 3 --- # Request - AI Ahnlich Request-ai: AI proxy to communicate with ahnlich-db, receiving raw input, transforming it into embeddings, and storing those embeddings within the DB. It extends DB capabilities by allowing developers to issue queries to the same store using raw inputs such as images or text. The AI proxy features multiple off-the-shelf models which can be selected for both store indexing and query time. Ahnlich Request-AI acts as a bridge between raw developer inputs (text, images, etc.) and the vector store. Instead of requiring clients to precompute embeddings, the AI proxy accepts raw inputs, runs a model to create embeddings, and persists those embeddings into the target Ahnlich DB store. Once stored, the same store can be queried using raw input — the proxy will convert the query into an embedding and run the search on the DB. ## When to use Ahnlich Request AI - Simplify client logic: let the proxy handle embedding generation so clients send raw content (text/images) rather than precomputed vectors. - Faster prototyping: quickly add semantic search by selecting an off-the-shelf model without changing client code. - Model selection flexibility: pick different models for indexing and querying to suit accuracy/latency tradeoffs. ## How it works 1. Receive raw input — the AI proxy accepts raw payloads (for example, text or image data). 2. Transform to embeddings — the proxy runs the selected model to generate vector embeddings for the input. 3. Store in DB — embeddings plus any associated metadata are stored in the specified Ahnlich DB store. 4. Query with raw input — when you issue a query using raw input, the proxy repeats steps 1–3 for the query and forwards results. ## Model configuration When creating or configuring a store via the AI proxy, you supply two model identifiers: - INDEXMODEL — the model used to generate embeddings for items that will be stored (indexing). - QUERYMODEL — the model used to transform incoming raw queries into embeddings for search. *Example (CLI-style):* ```go CREATESTORE my_store QUERYMODEL all-minilm-l6-v2 INDEXMODEL all-minilm-l6-v2 ``` Structured example (provided): ```go create_store( store="my_store", index_model="all-minilm-l6-v2", query_model="all-minilm-l6-v2", ) ``` ## Behavior and expectations - The AI proxy generates embeddings on behalf of the client and persists them into the DB, so clients do not need to manage embedding computation. - The proxy supports multiple off-the-shelf models; you select the model identifiers when creating or configuring a store. - After indexing, the same store can be queried using raw input; the proxy will convert the query into an embedding using the configured `query_model` and forward the similarity request to the DB. - Choosing different `index_model` and `query_model` is supported (the example uses the same model for both), enabling flexibility in balancing index-time embedding quality vs. query-time performance. ## Source Code Example Create a store named my_store and select the same model for both indexing and querying: ```go CREATESTORE my_store QUERYMODEL all-minilm-l6-v2 INDEXMODEL all-minilm-l6-v2 ``` Equivalent in structured form: ```go create_store( store="my_store", index_model="all-minilm-l6-v2", query_model="all-minilm-l6-v2", ) ``` Below is a breakdown of common AI request examples: * [Ping](/docs/client-libraries/go/request-db/ping) * [Info Server](/docs/client-libraries/go/request-db/info-server) * [List Stores](/docs/client-libraries/go/request-db/list-stores) * [Create Store](/docs/client-libraries/go/request-db/create-store) * [Set](/docs/client-libraries/go/request-db/set) * [Upsert](/docs/client-libraries/go/request-ai/upsert) * [GetSimN](/docs/client-libraries/go/request-db/get-simn) * [Get By Predicate](/docs/client-libraries/go/request-db/get-by-predicate) * [Create Predicate Index](/docs/client-libraries/go/request-db/create-predicate-index) * [Drop Predicate Index](/docs/client-libraries/go/request-db/drop-predicate-index) * [Delete Key](/docs/client-libraries/go/request-db/delete-key) * [Drop Store](/docs/client-libraries/go/request-db/drop-store) * [Create Non Linear Algorithm Index](/docs/client-libraries/go/request-db/create-non-linear-algx) * [Drop Non Linear Algorithm Index](/docs/client-libraries/go/request-db/drop-non-linear-algx) --- # FILE: client-libraries/go/request-ai/set.md --- title: Set --- # Set ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `Set` request-ai ingests raw inputs (e.g., text) through the Ahnlich AI proxy. The proxy converts each raw input into an embedding using the store’s configured IndexModel and persists the result—together with any provided metadata—into the underlying Ahnlich DB store. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" preprocess "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/preprocess" ) const AIAddr = "127.0.0.1:1370" func stringPtr(value string) *string { return &value } // ---- Standalone Set Example ---- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() // connect to AI server conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock(), ) if err != nil { log.Fatalf(" Failed to connect to AI server: %v", err) } defer conn.Close() client := aisvc.NewAIServiceClient(conn) // prepare key/value input inputs := []*keyval.AiStoreEntry{ { Key: &keyval.StoreInput{ Value: &keyval.StoreInput_RawString{RawString: "X"}, }, Value: &keyval.StoreValue{ Value: map[string]*metadata.MetadataValue{ "f": {Value: &metadata.MetadataValue_RawString{RawString: "v"}}, }, }, }, } // perform Set operation _, err = client.Set(ctx, &aiquery.Set{ Store: "ai_store", // must already exist Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Inputs: inputs, PreprocessAction: preprocess.PreprocessAction_NoPreprocessing, ExecutionProvider: nil, // Optional: e.g., ExecutionProvider_CUDA for GPU acceleration }) if err != nil { log.Fatalf(" Set failed: %v", err) } fmt.Println(" Successfully inserted key/value into ai_store01") } ```
## Behavior - On success, the AI proxy **embeds** the raw input using the store’s **IndexModel** and **stores** the embedding plus metadata in the DB-backed store. - Multiple entries can be ingested by adding more items to `Inputs`. - The target store (`"ai_store"`) must already exist and be configured with models in the AI proxy. - If the request fails (e.g., store not found or server error), a non-nil error is returned. --- # FILE: client-libraries/go/request-ai/upsert.md --- title: Upsert --- # Upsert ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `Upsert` request updates a single entry matching a predicate condition in an AI store. The AI proxy automatically merges metadata and can re-embed new inputs. Fields: - `Store` - store name - `Condition` - predicate that must match exactly one entry - `NewInput` (optional) - new raw input (text/image) to re-embed - `NewValue` (optional) - metadata to update (always merged by AI proxy) - `PreprocessAction` - how to preprocess new input - `ExecutionProvider` (optional) - hardware acceleration (e.g., CUDA) - `ModelParams` - optional runtime parameters for the model - `Schema` (optional) - schema namespace ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" predicates "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/predicates" preprocess "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/preprocess" ) const AIAddr = "127.0.0.1:1370" func stringPtr(value string) *string { return &value } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock(), ) if err != nil { log.Fatalf("Failed to connect: %v", err) } defer conn.Close() client := aisvc.NewAIServiceClient(conn) condition := &predicates.PredicateCondition{ Kind: &predicates.PredicateCondition_Value{ Value: &predicates.Predicate{ Kind: &predicates.Predicate_Equals{ Equals: &predicates.Equals{ Key: "filename", Value: &metadata.MetadataValue{ Value: &metadata.MetadataValue_RawString{RawString: "photo.jpg"}, }, }, }, }, }, } newValue := &keyval.StoreValue{ Value: map[string]*metadata.MetadataValue{ "tags": {Value: &metadata.MetadataValue_RawString{RawString: "cat,outdoors"}}, }, } resp, err := client.Upsert(ctx, &aiquery.Upsert{ Store: "images", Schema: stringPtr("media"), Condition: condition, NewInput: nil, // Optional: new image/text to re-embed NewValue: newValue, PreprocessAction: preprocess.PreprocessAction_NoPreprocessing, ExecutionProvider: nil, ModelParams: map[string]string{}, }) if err != nil { log.Fatalf("Upsert failed: %v", err) } fmt.Printf("Updated: %d\n", resp.Upsert.Updated) } ```
## Behavior - AI proxy always merges metadata (preserves AI-generated fields) - Errors if the predicate matches 0 or multiple entries - Re-embeds input if `NewInput` is provided - Returns upsert counts (inserted: 0, updated: 1 on success) --- # FILE: client-libraries/go/request-db/create-non-linear-algx.md --- title: Create Non-Linear Algorithm Index --- # Create Non-Linear Algorithm Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `CreateNonLinearAlgorithmIndex` request allows you to build specialized indices for **non-linear similarity search algorithms**. Unlike linear approaches (such as cosine or Euclidean), non-linear algorithms (like HNSW) are optimized for faster and more scalable vector searches, especially as the dataset grows in size. Each index type is specified using a `NonLinearIndex` message with a `HNSWConfig`. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" nonlinear "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/algorithm/nonlinear" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.DialContext(ctx, ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } func (c *ExampleDBClient) exampleCreateNonLinearAlgoIndex() error { // Create an HNSW index (with optional config) _, err := c.client.CreateNonLinearAlgorithmIndex(c.ctx, &dbquery.CreateNonLinearAlgorithmIndex{ Store: "my_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted NonLinearIndices: []*nonlinear.NonLinearIndex{ {Index: &nonlinear.NonLinearIndex_Hnsw{Hnsw: &nonlinear.HNSWConfig{}}}, }, }) return err } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleCreateNonLinearAlgoIndex(); err != nil { log.Fatalf("CreateNonLinearAlgoIndex failed: %v", err) } fmt.Println("Created Non-Linear Algorithm Index for 'my_store'") } ```
--- # FILE: client-libraries/go/request-db/create-predicate-index.md --- title: Create Predicate Index --- # Create Predicate Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description `CreatePredicateIndex` allows you to create indexes on metadata fields inside a store. Predicate indexes speed up queries like `GetByPredicate`, especially when you frequently filter by the same metadata key. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.DialContext(ctx, ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } // -------------------- Create Predicate Index -------------------- func (c *ExampleDBClient) exampleCreatePredicateIndex() error { _, err := c.client.CreatePredIndex(c.ctx, &dbquery.CreatePredIndex{ Store: "my_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Predicates: []string{"label"}, }) if err != nil { return err } fmt.Println("Predicate index created for store: my_store") return nil } // -------------------- Main -------------------- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleCreatePredicateIndex(); err != nil { log.Fatalf("CreatePredicateIndex failed: %v", err) } } ```
## What the code does - Creates a **predicate index** on the `label` metadata key within the store `my_store`. - Once created, queries such as `GetByPredicate` that check `WHERE label = "X"` will run much faster. - Returns any error encountered when creating the index (e.g., if it already exists). --- # FILE: client-libraries/go/request-db/create-store.md --- title: Create Store --- # Create Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description `CreateStore` creates a new vector store on the Ahnlich DB server. A store is the fundamental unit of organization for embeddings and their associated metadata. Each store is defined by its **schema**, **name**, and **dimension**, which specifies the length of vectors that can be inserted. ## Behavior - A store must have a **unique name within its schema**. If you try to create a store with an existing name in the same schema and `ErrorIfExists` is true, the server will reject it. - **Schema** - Optional namespace for the store. When omitted, the server creates the store in `public`. Set `Schema` to create the store in another schema such as `analytics`. - The **dimension parameter** is mandatory and must match the size of vectors you plan to insert. - **CreatePredicates** - Optional list of metadata field names to enable predicate-based filtering. Leave empty if you don't need metadata filtering. - **NonLinearIndices** - Optional list of non-linear algorithms for faster approximate search. Leave empty to use only linear search. - **ErrorIfExists** - Controls behavior when store already exists. Set to `true` to get an error, `false` to silently skip creation. - Once created, a store can be queried, listed, and populated with embeddings. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" nonlinear "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/algorithm/nonlinear" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.DialContext(ctx, ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } // CreateStore example func (c *ExampleDBClient) exampleCreateStore(store string, dimension int32) error { _, err := c.client.CreateStore(c.ctx, &dbquery.CreateStore{ Store: store, Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Dimension: uint32(dimension), CreatePredicates: []string{}, // Optional: list of metadata fields to index for filtering NonLinearIndices: []*nonlinear.NonLinearIndex{}, // Optional: non-linear algorithms (e.g., HNSW) for faster search ErrorIfExists: true, // Return error if store already exists }) if err != nil { return err } fmt.Printf("Created store: %s (dimension: %d)\n", store, dimension) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() storeName := "my_stores" if err := client.exampleCreateStore(storeName, 4); err != nil { log.Fatalf("CreateStore failed: %v", err) } } ```
This method requests the creation of a store named `"my_stores"` in the `analytics` schema with a vector dimensionality of 4. The response is ignored in this example, but the absence of an error indicates success. --- # FILE: client-libraries/go/request-db/delete-key.md --- title: Delete Key --- # Delete Key ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `DeleteKey` request removes one or more vector entries from a store by explicitly specifying their keys. Unlike `DropStore`, which deletes the entire store, `DeleteKey` provides **granular control**, allowing you to delete only specific embeddings while keeping the rest of the store intact. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.DialContext(ctx, ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } // -------------------- Delete Key -------------------- func (c *ExampleDBClient) exampleDeleteKey() error { _, err := c.client.DelKey(c.ctx, &dbquery.DelKey{ Store: "my_stores", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Keys: []*keyval.StoreKey{{Key: []float32{1, 2, 3, 4}}}, }) if err != nil { return err } fmt.Println("Deleted key from store: my_stores") return nil } // -------------------- Main -------------------- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleDeleteKey(); err != nil { log.Fatalf("DeleteKey failed: %v", err) } } ```
--- # FILE: client-libraries/go/request-db/delete-predicate.md --- title: Delete Predicate --- # Delete Predicate ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `DeletePredicate` request removes entries from a store that match a given predicate condition. Instead of deleting by vector key, this operation lets you **filter deletions based on metadata values** (e.g., labels, tags, or custom attributes). ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" predicates "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/predicates" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.DialContext(ctx, ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } // -------------------- Delete Predicate -------------------- func (c *ExampleDBClient) exampleDeletePredicate() error { condition := &predicates.PredicateCondition{ Kind: &predicates.PredicateCondition_Value{ Value: &predicates.Predicate{ Kind: &predicates.Predicate_Equals{ Equals: &predicates.Equals{ Key: "label", Value: &metadata.MetadataValue{ Value: &metadata.MetadataValue_RawString{ RawString: "A", }, }, }, }, }, }, } _, err := c.client.DelPred(c.ctx, &dbquery.DelPred{ Store: "my_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Condition: condition, }) if err != nil { return err } fmt.Println("Deleted entries matching predicate from store: my_store") return nil } // -------------------- Main -------------------- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleDeletePredicate(); err != nil { log.Fatalf("DeletePredicate failed: %v", err) } } ```
## What the code does - Builds a predicate condition to match all entries where label == "A". - Sends a DelPred request against the store "my_store". - Removes all vectors/entries that satisfy the predicate. --- # FILE: client-libraries/go/request-db/drop-non-linear-algx.md --- title: Drop Non-Linear Algorithm Index --- # Drop Non-Linear Algorithm Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `DropNonLinearAlgorithmIndex` request removes previously created non-linear indices (e.g., HNSW) from a store. This is useful when you no longer need a specific algorithm index or want to reclaim resources used for maintaining it. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" nonlinear "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/algorithm/nonlinear" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.DialContext(ctx, ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } func (c *ExampleDBClient) exampleDropNonLinearAlgoIndex() error { _, err := c.client.DropNonLinearAlgorithmIndex(c.ctx, &dbquery.DropNonLinearAlgorithmIndex{ Store: "my_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted NonLinearIndices: []nonlinear.NonLinearAlgorithm{nonlinear.NonLinearAlgorithm_HNSW}, ErrorIfNotExists: true, }) return err } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleDropNonLinearAlgoIndex(); err != nil { log.Fatalf("DropNonLinearAlgoIndex failed: %v", err) } fmt.Println("Dropped Non-Linear Algorithm Index for 'my_stores'") } ```
--- # FILE: client-libraries/go/request-db/drop-predicate-index.md --- title: Drop Predicate Index --- # Drop Predicate Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description `DropPredicateIndex` removes an existing index from one or more metadata fields. This operation is useful if a field is no longer used for filtering, or if you want to reduce memory overhead. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.DialContext(ctx, ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } // -------------------- Drop Predicate Index -------------------- func (c *ExampleDBClient) exampleDropPredicateIndex() error { _, err := c.client.DropPredIndex(c.ctx, &dbquery.DropPredIndex{ Store: "my_stores", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Predicates: []string{"label"}, ErrorIfNotExists: true, }) if err != nil { return err } fmt.Println("Dropped predicate index for store: my_stores") return nil } // -------------------- Main -------------------- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleDropPredicateIndex(); err != nil { log.Fatalf("DropPredicateIndex failed: %v", err) } } ```
--- # FILE: client-libraries/go/request-db/drop-store.md --- title: Drop Store --- # Drop Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `DropStore` request deletes an entire store from the database. Once a store is dropped, all of its vectors, embeddings, and associated metadata are permanently removed. This operation is **destructive** and cannot be undone. ## Parameters - **Store** - Name of the store to delete - **ErrorIfNotExists** - Controls behavior when store doesn't exist: - `true` - Returns an error if the store is not found - `false` - Silently succeeds even if store doesn't exist ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.DialContext(ctx, ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } // -------------------- Drop Store -------------------- func (c *ExampleDBClient) exampleDropStore() error { _, err := c.client.DropStore(c.ctx, &dbquery.DropStore{ Store: "my_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted ErrorIfNotExists: true, // Return error if store doesn't exist }) if err != nil { return err } fmt.Println("Dropped store: my_store") return nil } // -------------------- Main -------------------- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleDropStore(); err != nil { log.Fatalf("DropStore failed: %v", err) } } ```
--- # FILE: client-libraries/go/request-db/get-by-predicate.md --- title: Get by Predicate --- # Get by Predicate ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description `GetByPredicate` returns entries whose **metadata** matches a specified condition. Unlike similarity search, this is a **metadata-only** lookup (e.g., “all items where `label == "A"`”). Use it to filter or audit data based on tags, categories, or other metadata fields you maintain alongside vectors. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" predicates "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/predicates" ) const ServerAddr = "127.0.0.1:1369" // ExampleDBClient holds the gRPC connection, client, and context. func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } // NewDBClient connects to the Ahnlich DB server. func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.DialContext(ctx, ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } // Close closes the gRPC connection. func (c *ExampleDBClient) Close() error { return c.conn.Close() } // -------------------- Get By Predicate -------------------- func (c *ExampleDBClient) exampleGetByPredicate() error { // Build a PredicateCondition for "label == A" cond := &predicates.PredicateCondition{ Kind: &predicates.PredicateCondition_Value{ Value: &predicates.Predicate{ Kind: &predicates.Predicate_Equals{ Equals: &predicates.Equals{ Key: "label", Value: &metadata.MetadataValue{ Value: &metadata.MetadataValue_RawString{RawString: "A"}, }, }, }, }, }, } // Call GetPred with the condition resp, err := c.client.GetPred(c.ctx, &dbquery.GetPred{ Store: "my_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Condition: cond, }) if err != nil { return err } fmt.Println("GetByPredicate Results:", resp.Entries) return nil } // -------------------- Main -------------------- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleGetByPredicate(); err != nil { log.Fatalf("GetByPredicate failed: %v", err) } } ```
## What the code does - Builds a predicate condition equivalent to: **WHERE** `label` **==** `"A"`. - Sends the request to store `my_store` and prints matching entries (`resp.Entries`). - Returns any RPC error to the caller. --- # FILE: client-libraries/go/request-db/get-key.md --- title: GetKey --- # GetKey ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `GetKey` request is used to retrieve **specific entries** from a store by providing the exact vector keys. Unlike GetSimN, which searches for approximate or closest matches, GetKey performs a **direct lookup** based on the stored vectors. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" ) const ServerAddr = "127.0.0.1:1369" // ExampleDBClient holds the gRPC connection, client, and context. func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } // NewDBClient connects to the Ahnlich DB server. func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.DialContext( ctx, ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock(), ) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } // Close closes the gRPC connection. func (c *ExampleDBClient) Close() error { return c.conn.Close() } // -------------------- GetKey -------------------- func (c *ExampleDBClient) exampleGetKey() error { resp, err := c.client.GetKey(c.ctx, &dbquery.GetKey{ Store: "my_stores", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Keys: []*keyval.StoreKey{{Key: []float32{1, 2, 3, 4}}}, }) if err != nil { return err } fmt.Println("GetKey Results:") for _, entry := range resp.Entries { fmt.Println(" - Key:", entry.Key.Key, "Value:", entry.Value.Value) } return nil } // -------------------- Main -------------------- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleGetKey(); err != nil { log.Fatalf("GetKey failed: %v", err) } } ```
- **Store** – Targets the `my_store` vector store. - **Keys** – A slice of vector keys to look up. In this case, the request asks for the entry with key `[1, 2, 3, 4]`. - **Response** – If the key exists in the store, the server returns the corresponding entries (vector + metadata) inside `resp.Entries`. This operation is especially useful when you need **exact retrieval** of vectors and their metadata, such as fetching embeddings for validation, re-indexing, or checking consistency of stored data. --- # FILE: client-libraries/go/request-db/get-simn.md --- title: GetSimN --- # GetSimN ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `GetSimN` request performs a **similarity search** against a specific store. It retrieves the top-N closest vectors to a given input vector. This operation is essential for applications that depend on **nearest neighbor lookups** such as recommendation systems, semantic search, and clustering. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" algorithms "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/algorithm/algorithms" predicates "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/predicates" ) const ServerAddr = "127.0.0.1:1369" // ExampleDBClient holds the gRPC connection, client, and context. func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } // NewDBClient connects to the Ahnlich DB server. func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.DialContext( ctx, ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock(), ) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } // Close closes the gRPC connection. func (c *ExampleDBClient) Close() error { return c.conn.Close() } // -------------------- GetSimN -------------------- func (c *ExampleDBClient) exampleGetSimN() error { resp, err := c.client.GetSimN(c.ctx, &dbquery.GetSimN{ Store: "my_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted SearchInput: &keyval.StoreKey{Key: []float32{1, 2, 3, 4}}, ClosestN: 3, Algorithm: algorithms.Algorithm_CosineSimilarity, Condition: nil, // Optional: filter results using predicates }) if err != nil { return err } fmt.Println("GetSimN Results:") for _, entry := range resp.Entries { fmt.Println(" - Key:", entry.Key.Key, "Value:", entry.Value.Value) } return nil } // -------------------- Main -------------------- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleGetSimN(); err != nil { log.Fatalf("GetSimN failed: %v", err) } } ```
- **Store** – The request targets `my_store`, which must already exist. - **SearchInput** – A vector `[1, 2, 3, 4]` is used as the query input. This must match the dimensionality of the store. - **ClosestN** – The request asks for the 3 most similar vectors. - **Algorithm** – Uses `CosineSimilarity` to compute vector similarity. Other options: `EuclideanDistance`, `DotProductSimilarity`. - **Condition** – Optional predicate filter to restrict which vectors are considered in the search. Set to `nil` to search all vectors. See [Predicates documentation](/docs/components/predicates) for filtering examples. - **Response** – The server returns the top matches as `resp.Entries`, including both the stored vectors and any metadata associated with them. This makes `GetSimN` a fundamental query for retrieving entries most similar to a given embedding while leveraging the similarity algorithms supported by Ahnlich DB. --- # FILE: client-libraries/go/request-db/get-store.md --- title: Get Store --- # Get Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `GetStore` request retrieves detailed information about a specific store by name. This is useful for inspecting store configuration, checking dimensions, or monitoring size. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.DialContext(ctx, ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } func (c *ExampleDBClient) exampleGetStore() error { resp, err := c.client.GetStore(c.ctx, &dbquery.GetStore{ Store: "my_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted }) if err != nil { return err } fmt.Printf("Store name: %s\n", resp.Name) fmt.Printf("Number of entries: %d\n", resp.Len) fmt.Printf("Size in bytes: %d\n", resp.SizeInBytes) fmt.Printf("Dimension: %d\n", resp.Dimension) fmt.Printf("Predicate indices: %v\n", resp.PredicateIndices) fmt.Printf("Non-linear indices: %v\n", resp.NonLinearIndices) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleGetStore(); err != nil { log.Fatalf("GetStore failed: %v", err) } } ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `Store` | `string` | Yes | The name of the store to retrieve | ## Response: StoreInfo | Field | Type | Description | |-------|------|-------------| | `Name` | `string` | Store name | | `Len` | `uint64` | Number of entries in the store | | `SizeInBytes` | `uint64` | Total size of the store in bytes | | `Dimension` | `uint32` | Vector dimension | | `PredicateIndices` | `[]string` | List of indexed predicate keys | | `NonLinearIndices` | `[]*NonLinearIndex` | List of non-linear algorithm indices | ## Notes - Returns an error if the store does not exist - Use `ListStores` to get information about stores in a schema - The `SizeInBytes` field is useful for monitoring memory usage --- # FILE: client-libraries/go/request-db/info-server.md --- title: Info Server sidebar_posiiton: 2 --- # Info Server ## Description The `InfoServer` request retrieves metadata about the running Ahnlich DB server. Whereas `Ping` only checks connectivity, `InfoServer` provides richer diagnostic details. It allows developers and operators to understand the state and configuration of the server they are connected to. Common use cases include: - **Cluster diagnostics** – Retrieve server-specific information such as version, uptime, or available features. - **Environment verification** – Confirm that the client is connected to the intended server instance (useful in multi-node or distributed deployments). - **Monitoring and logging** – Integrate server details into dashboards or log pipelines to track health and consistency. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" ) const ServerAddr = "127.0.0.1:1369" type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.DialContext(ctx, ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } // InfoServer example func (c *ExampleDBClient) exampleInfoServer() error { resp, err := c.client.InfoServer(c.ctx, &dbquery.InfoServer{}) if err != nil { return err } fmt.Println("InfoServer:", resp) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleInfoServer(); err != nil { log.Fatalf("InfoServer failed: %v", err) } } ```
This method defines exampleInfoServer on the ExampleDBClient type. It calls the InfoServer RPC and prints the server’s response. Any error encountered during the call is returned to the caller. --- # FILE: client-libraries/go/request-db/list-connected-clients.md --- title: List Connected Clients --- # List Connected Clients ## Description The `ListConnectedClients` request allows you to query the database server for all currently connected clients. This is particularly useful for **monitoring, debugging,** and ensuring that multiple services or developers interacting with the same Ahnlich DB instance are tracked properly. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" ) const ServerAddr = "127.0.0.1:1369" type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.DialContext(ctx, ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } func (c *ExampleDBClient) exampleListConnectedClients() error { resp, err := c.client.ListClients(c.ctx, &dbquery.ListClients{}) if err != nil { return err } fmt.Println("Connected Clients:", resp.Clients) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleListConnectedClients(); err != nil { log.Fatalf("ListConnectedClients failed: %v", err) } } ```
This method requests the creation of a store named `"my_store"` with a vector dimensionality of 4. The response is ignored in this example, but the absence of an error indicates success. --- # FILE: client-libraries/go/request-db/list-stores.md --- title: List Stores --- # List Stores ## Schema `ListStores` accepts an optional `schema` field. When it is omitted, the server lists stores in `public` only; it does not list stores across every schema. Set `schema` to list stores in another schema. ## Description `ListStores` returns the set of vector stores currently available on the Ahnlich DB server. Use it to **discover** what stores exist and to **validate** that a target store is present before you attempt writes or similarity queries. ## Behavior - The call returns a collection of store metadata from the server at the time of the request. Each store entry includes: name, entry count, size in bytes, and non-linear index configurations (HNSW parameters) if any are active. - Treat the result as an **enumeration**: don’t assume ordering or uniqueness semantics beyond what the server provides. - An **empty list** simply means no stores are present yet on that server. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.DialContext(ctx, ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } // ListStores example func (c *ExampleDBClient) exampleListStores() error { resp, err := c.client.ListStores(c.ctx, &dbquery.ListStores{ Schema: stringPtr("analytics"), }) if err != nil { return err } fmt.Println("Stores:", resp.Stores) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleListStores(); err != nil { log.Fatalf("ListStores failed: %v", err) } } ```
This method sends a `ListStores` request and prints the returned set of stores via `resp.Stores`. If the RPC fails, the returned error indicates the call did not succeed. --- # FILE: client-libraries/go/request-db/ping.md --- title: Ping sidebar_posiiton: 1 --- # Ping ## Description The `Ping` request is the simplest way to verify connectivity between your Go client and an active **Ahnlich DB server**. When a client calls `Ping`, the server responds immediately if it is reachable and healthy. This is useful in a variety of scenarios: - **Connection health check** – Before performing more expensive operations such as storing or searching vectors, you can ensure the server is running and ready to process requests. - **Service monitoring** – Can be integrated into a heartbeat system to periodically confirm the DB server is online. - **Debugging setup** – Helpful for quickly confirming that your gRPC connection and server configuration are correct. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" ) const ServerAddr = "127.0.0.1:1369" type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.DialContext(ctx, ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } func (c *ExampleDBClient) examplePingDB() error { resp, err := c.client.Ping(c.ctx, &dbquery.Ping{}) if err != nil { return err } fmt.Println("Ping:", resp) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.examplePingDB(); err != nil { log.Fatalf("Ping failed: %v", err) } } ```
This example defines an `examplePingDB` method on the `ExampleDBClient` type. It sends a `Ping` request to the DB service using the `dbquery.Ping{}` message and prints the response. If the server is unavailable or the request fails, an error is returned. --- # FILE: client-libraries/go/request-db/request-db.md --- title: Request DB sidebar_posiiton: 2 --- # Requests – DB The **Ahnlich DB** is an in-memory vector key–value store designed for storing embeddings or vectors alongside their metadata (key–value maps). It provides AI/ML engineers with the ability to: - Store and retrieve embeddings. - Search for similar vectors using linear similarity algorithms (Cosine, Euclidean). - Perform searches with non-linear similarity algorithms (such as HNSW). - Filter results based on metadata values. This makes it possible to build intelligent search and recommendation systems that combine vector similarity with metadata-based filtering. _Example_ A query to retrieve the 2 most similar vectors to `[0.2, 0.1]` from the store `my_store`, using cosine similarity, while excluding any items where the metadata field `page` is equal to `"hidden"`: ```go GETSIMN 2 WITH [0.2, 0.1] USING cosinesimilarity IN my_store WHERE (page != hidden) ``` Below is a breakdown of common DB request examples: - [Ping](/docs/client-libraries/go/request-db/ping) - [Info Server](/docs/client-libraries/go/request-db/info-server) - [List Stores](/docs/client-libraries/go/request-db/list-stores) - [Create Store](/docs/client-libraries/go/request-db/create-store) - [Set](/docs/client-libraries/go/request-db/set) - [Upsert](/docs/client-libraries/go/request-db/upsert) - [GetSimN](/docs/client-libraries/go/request-db/get-simn) - [Get Key](/docs/client-libraries/go/request-db/get-key) - [Get By Predicate](/docs/client-libraries/go/request-db/get-by-predicate) - [Create Predicate Index](/docs/client-libraries/go/request-db/create-predicate-index) - [Drop Predicate Index](/docs/client-libraries/go/request-db/drop-predicate-index) - [Delete Key](/docs/client-libraries/go/request-db/delete-key) - [Drop Store](/docs/client-libraries/go/request-db/drop-store) - [List Connected Clients](/docs/client-libraries/go/request-db/list-connected-clients) - [Create Non Linear Algorithm Index](/docs/client-libraries/go/request-db/create-non-linear-algx) - [Drop Non Linear Algorithm Index](/docs/client-libraries/go/request-db/drop-non-linear-algx) - [Delete Predicate](/docs/client-libraries/go/request-db/delete-predicate) --- # FILE: client-libraries/go/request-db/set.md --- title: Set --- # Set ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `Set` request is used to **insert or update entries** (vectors with associated metadata) into a given store. This is one of the core operations of Ahnlich DB, as it establishes the embeddings that can later be queried for similarity search. Each entry in a store consists of two parts: 1. **Key** – the vector itself (a fixed-dimension embedding). 2. **Value** – metadata stored alongside the vector, represented as a key-value map. Metadata enables additional filtering and querying beyond pure vector similarity. ## Behavior - The vector provided in `StoreKey` **must match the store’s dimension**. For example, a 4-dimensional store only accepts `[1, 2, 3, 4]` shaped vectors. - Metadata values can be of different types (strings, numbers, booleans). In the example, a string label `"A"` is stored. - Multiple entries can be inserted in a single request by batching them in the `Inputs` field. - If a key already exists in the store, calling Set again will **update** its value. ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.DialContext(ctx, ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } // Set example func (c *ExampleDBClient) exampleSet(store string) error { entries := []*keyval.DbStoreEntry{ { Key: &keyval.StoreKey{Key: []float32{1, 2, 3, 4}}, Value: &keyval.StoreValue{ Value: map[string]*metadata.MetadataValue{ "label": { Value: &metadata.MetadataValue_RawString{RawString: "A"}, }, }, }, }, } _, err := c.client.Set(c.ctx, &dbquery.Set{Store: store, Schema: stringPtr("analytics"), Inputs: entries}) if err != nil { return err } fmt.Println("Inserted entry into store:", store) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() storeName := "my_store" if err := client.exampleSet(storeName); err != nil { log.Fatalf("Set failed: %v", err) } } ```
This inserts a single vector `[1, 2, 3, 4]` into the store `my_store` with metadata `{ "label": "A" }`. --- # FILE: client-libraries/go/request-db/upsert.md --- title: Upsert --- # Upsert ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. ## Description The `Upsert` request updates a single entry matching a predicate condition. It errors if the predicate matches 0 or multiple entries. Fields: - `Store` - store name - `Condition` - predicate that must match exactly one entry - `NewKey` (optional) - new vector to replace existing key - `NewValue` (optional) - metadata to update - `MergeMetadata` - if true, merges new metadata into existing (default: false replaces entirely) - `Schema` (optional) - schema namespace ## Source Code Example
Click to expand source code ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" predicates "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/predicates" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() conn, err := grpc.DialContext(ctx, ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { log.Fatalf("failed to dial: %v", err) } defer conn.Close() client := dbsvc.NewDBServiceClient(conn) condition := &predicates.PredicateCondition{ Kind: &predicates.PredicateCondition_Value{ Value: &predicates.Predicate{ Kind: &predicates.Predicate_Equals{ Equals: &predicates.Equals{ Key: "id", Value: &metadata.MetadataValue{ Value: &metadata.MetadataValue_RawString{RawString: "123"}, }, }, }, }, }, } newValue := &keyval.StoreValue{ Value: map[string]*metadata.MetadataValue{ "status": {Value: &metadata.MetadataValue_RawString{RawString: "published"}}, }, } resp, err := client.Upsert(ctx, &dbquery.Upsert{ Store: "my_store", Schema: stringPtr("analytics"), Condition: condition, NewValue: newValue, MergeMetadata: true, }) if err != nil { log.Fatalf("Upsert failed: %v", err) } fmt.Printf("Updated: %d\n", resp.Upsert.Updated) } ```
This updates a single entry where `id = "123"`, merging the new status into existing metadata. --- # FILE: client-libraries/go/type-meanings.md --- title: Type Meanings sidebar_posiiton: 5 --- # Type Meanings Key concepts and their meanings in the SDK: ### 1. StoreKey - DB: represented as a raw `[]float32` vector. - AI: represented as a `StoreInput` object, which may be raw text, numbers, or serialized embeddings. ### 2. StoreValue - A `map` of metadata fields associated with a stored entry (e.g., labels, tags, or custom attributes). ### 3. Predicates - Filtering conditions that restrict which stored values are retrieved or indexed. Useful for constrained similarity search. ### 4. Pipeline - A batch RPC builder that allows combining multiple requests (such as `Set`, `GetKey`, `GetSimN`, etc.) into a single execution for efficiency. This section provides developers with clear guidance for **building, testing, and releasing** the SDK, as well as an understanding of **core data types** used in both DB and AI contexts. --- # FILE: client-libraries/node/convert-to-embeddings.md --- title: Convert to Embeddings sidebar_position: 20 --- # Convert to Embeddings The `convertStoreInputToEmbeddings` method converts raw inputs (text, images, audio) into embeddings using a specified AI model, without storing them in a database. ## Method Signature ```typescript async convertStoreInputToEmbeddings( request: ConvertStoreInputToEmbeddingsRequest ): Promise ``` ## Parameters The `ConvertStoreInputToEmbeddingsRequest` contains: - `storeInputs`: Array of inputs to convert (text, images, or audio) - `preprocessAction`: How to preprocess inputs - `model`: AI model to use for conversion - `executionProvider` (optional): Execution provider (CPU, CUDA, etc.) - `modelParams` (optional): Model-specific parameters ## Basic Example ```typescript import { createPromiseClient } from "@connectrpc/connect"; import { createConnectTransport } from "@connectrpc/connect-node"; import { AiService } from "./grpc/services/ai_service_connect"; import { AiModel, PreprocessAction } from "./grpc/ai"; const transport = createConnectTransport({ baseUrl: "http://localhost:1370", httpVersion: "2", }); const client = createPromiseClient(AiService, transport); const inputs = [ { rawString: "Hello world" }, { rawString: "Goodbye world" }, ]; const response = await client.convertStoreInputToEmbeddings({ storeInputs: inputs, preprocessAction: PreprocessAction.NO_PREPROCESSING, model: AiModel.ALL_MINI_LM_L6_V2, }); // Access embeddings for (const item of response.values) { if (item.variant.case === "single") { const embedding = item.variant.value.embedding; console.log(`Embedding size: ${embedding.key.length}`); } } ``` ## Face Detection with Metadata (Buffalo-L / SFace) Starting from version 0.2.1, face detection models return **bounding box metadata** alongside embeddings: ```typescript import { createPromiseClient } from "@connectrpc/connect"; import { createConnectTransport } from "@connectrpc/connect-node"; import { AiService } from "./grpc/services/ai_service_connect"; import { AiModel, PreprocessAction } from "./grpc/ai"; import * as fs from "fs"; const transport = createConnectTransport({ baseUrl: "http://localhost:1370", httpVersion: "2", }); const client = createPromiseClient(AiService, transport); // Load image const imageBytes = fs.readFileSync("group_photo.jpg"); const inputs = [{ image: imageBytes }]; const response = await client.convertStoreInputToEmbeddings({ storeInputs: inputs, preprocessAction: PreprocessAction.MODEL_PREPROCESSING, model: AiModel.BUFFALO_L, }); // Process each detected face for (const item of response.values) { if (item.variant.case === "multiple") { const faces = item.variant.value.embeddings; console.log(`Detected ${faces.length} faces`); for (const faceData of faces) { // Access embedding const embedding = faceData.embedding!.key; // Float32Array console.log(`Embedding size: ${embedding.length}`); // Access bounding box metadata if (faceData.metadata) { const metadata = faceData.metadata.value; const bboxX1 = parseFloat(metadata.bbox_x1!.value!.value as string); const bboxY1 = parseFloat(metadata.bbox_y1!.value!.value as string); const bboxX2 = parseFloat(metadata.bbox_x2!.value!.value as string); const bboxY2 = parseFloat(metadata.bbox_y2!.value!.value as string); const confidence = parseFloat(metadata.confidence!.value!.value as string); console.log(`Face at (${bboxX1.toFixed(3)}, ${bboxY1.toFixed(3)}) ` + `to (${bboxX2.toFixed(3)}, ${bboxY2.toFixed(3)})`); console.log(`Confidence: ${confidence.toFixed(3)}`); } } } } ``` ## Metadata Fields (Face Detection Models) For Buffalo-L and SFace models, each detected face includes: | Field | Type | Range | Description | |-------|------|-------|-------------| | `bbox_x1` | number | 0.0-1.0 | Normalized x-coordinate of top-left corner | | `bbox_y1` | number | 0.0-1.0 | Normalized y-coordinate of top-left corner | | `bbox_x2` | number | 0.0-1.0 | Normalized x-coordinate of bottom-right corner | | `bbox_y2` | number | 0.0-1.0 | Normalized y-coordinate of bottom-right corner | | `confidence` | number | 0.0-1.0 | Detection confidence score | Coordinates are normalized to 0-1 range. To convert to pixel coordinates: ```typescript import sharp from "sharp"; const img = sharp("photo.jpg"); const { width, height } = await img.metadata(); const pixelX1 = Math.round(bboxX1 * width!); const pixelY1 = Math.round(bboxY1 * height!); const pixelX2 = Math.round(bboxX2 * width!); const pixelY2 = Math.round(bboxY2 * height!); ``` ## Using Model Parameters Face detection models support tuning via `modelParams`: ```typescript const response = await client.convertStoreInputToEmbeddings({ storeInputs: inputs, preprocessAction: PreprocessAction.MODEL_PREPROCESSING, model: AiModel.BUFFALO_L, modelParams: { confidence_threshold: "0.9" // Higher = fewer faces }, }); ``` ## Response Structure ```typescript interface StoreInputToEmbeddingsList { values: SingleInputToEmbedding[]; } interface SingleInputToEmbedding { input?: StoreInput; // Original input variant: { case: "single" | "multiple"; value: EmbeddingWithMetadata | MultipleEmbedding; }; } interface EmbeddingWithMetadata { embedding?: StoreKey; // The embedding vector metadata?: StoreValue; // Optional metadata (e.g., bounding boxes) } interface MultipleEmbedding { embeddings: EmbeddingWithMetadata[]; // One per detected face } ``` ## Use Cases - **Testing models**: Quickly test how different inputs are embedded - **Batch processing**: Generate embeddings for analysis without storage - **Face detection**: Extract face locations and embeddings from photos - **Quality control**: Filter low-confidence detections before storage - **Visualization**: Draw bounding boxes on images using metadata --- # FILE: client-libraries/node/node-specific-resources.md --- title: Node.js Specific Resources sidebar_position: 1 --- ## QuickStart Set up your local development environment to start building with Ahnlich using Node.js/TypeScript. ## Install Node.js Go to the official [Node.js website](https://nodejs.org/) and download the LTS version. Ensure that Node.js is installed on your system, and verify its version by running the following command in your terminal: ```bash node -v ``` *Example output:* ``` v20.11.0 ``` Also verify npm is available: ```bash npm -v ``` ## Set Up a Node.js Project Create a new directory for your project and initialize it: ```bash mkdir ahnlich-app cd ahnlich-app npm init -y ``` If you're using TypeScript (recommended), install TypeScript and initialize it: ```bash npm install typescript @types/node --save-dev npx tsc --init ``` ## Install the Ahnlich Client Node Install the Ahnlich Node.js client SDK using npm: ```bash npm install ahnlich-client-node ``` Or using yarn: ```bash yarn add ahnlich-client-node ``` Or using pnpm: ```bash pnpm add ahnlich-client-node ``` ## Package Information The Ahnlich Node.js client provides a gRPC-based SDK for interacting with Ahnlich-DB (vector storage, similarity search) and Ahnlich-AI (semantic models). ### Features * gRPC service clients for DB and AI via [`@connectrpc/connect`](https://connectrpc.com/) * TypeScript types generated from the Ahnlich `.proto` definitions * Optional auth (TLS + bearer token) and trace ID support ### Modules * `ahnlich-client-node` - Main package export with `createDbClient` and `createAiClient` * `ahnlich-client-node/grpc/db/query_pb` - DB query types (Ping, CreateStore, Set, etc.) * `ahnlich-client-node/grpc/ai/query_pb` - AI query types * `ahnlich-client-node/grpc/keyval_pb` - Key-value types (StoreKey, StoreValue, StoreInput, etc.) * `ahnlich-client-node/grpc/metadata_pb` - Metadata types (MetadataValue) * `ahnlich-client-node/grpc/predicate_pb` - Predicate types (PredicateCondition, Equals, etc.) * `ahnlich-client-node/grpc/algorithm/algorithm_pb` - Algorithm types (Algorithm enum) * `ahnlich-client-node/grpc/algorithm/nonlinear_pb` - Non-linear algorithm types (HNSWConfig) * `ahnlich-client-node/grpc/ai/models_pb` - AI model types (AIModel enum) ### Initialization Every request starts by creating a client connection to the Ahnlich server. #### DB Client ```ts import { createDbClient } from "ahnlich-client-node"; const client = createDbClient("127.0.0.1:1369"); // Client is now ready for requests const response = await client.ping(new Ping()); ``` #### AI Client ```ts import { createAiClient } from "ahnlich-client-node"; const client = createAiClient("127.0.0.1:1370"); // Client is now ready for requests const response = await client.ping(new Ping()); ``` ### With Authentication When the server is started with `--enable-auth`, pass a CA certificate and credentials: ```ts import * as fs from "fs"; import { createDbClient } from "ahnlich-client-node"; const client = createDbClient("127.0.0.1:1369", { caCert: fs.readFileSync("ca.crt"), auth: { username: "myuser", apiKey: "mykey" }, }); ``` ### With Tracing Pass a trace ID to correlate requests across services: ```ts import { createDbClient } from "ahnlich-client-node"; const client = createDbClient("127.0.0.1:1369", { traceId: "00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01", }); ``` This sets the `ahnlich-trace-id` header on every request. --- # FILE: client-libraries/node/node.md --- title: Node.js sidebar_position: 15 --- # Node.js SDK Build Ahnlich Applications with the Node.js/TypeScript SDK - Vector Storage, Search, and AI tooling via gRPC. ## Structure ### [Node.js Specific Resources](/docs/client-libraries/node/node-specific-resources) * Build Ahnlich Applications with the Node.js SDK * Ahnlich Node.js Technical Resources * Node.js SDK Quickstart - Setup Guide * Package Information * Initialization ### [Request - DB](/docs/client-libraries/node/request-db)
#### [Ping](/docs/client-libraries/node/request-db/ping) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Info Server](/docs/client-libraries/node/request-db/info-server) * Description * Source Code Example * Parameters * Returns * Behavior
#### [List Stores](/docs/client-libraries/node/request-db/list-stores) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Get Store](/docs/client-libraries/node/request-db/get-store) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Create Store](/docs/client-libraries/node/request-db/create-store) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Set](/docs/client-libraries/node/request-db/set) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Upsert](/docs/client-libraries/node/request-db/upsert) * Description * Source Code Example * Parameters * Returns * Behavior
#### [GetSimN](/docs/client-libraries/node/request-db/get-simn) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Get Key](/docs/client-libraries/node/request-db/get-key) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Get By Predicate](/docs/client-libraries/node/request-db/get-by-predicate) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Create Predicate Index](/docs/client-libraries/node/request-db/create-predicate-index) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Drop Predicate Index](/docs/client-libraries/node/request-db/drop-predicate-index) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Create Non Linear Algorithm Index](/docs/client-libraries/node/request-db/create-non-linear-algx) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Drop Non Linear Algorithm Index](/docs/client-libraries/node/request-db/drop-non-linear-algx) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Delete Key](/docs/client-libraries/node/request-db/delete-key) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Delete Predicate](/docs/client-libraries/node/request-db/delete-predicate) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Drop Store](/docs/client-libraries/node/request-db/drop-store) * Description * Source Code Example * Parameters * Returns * Behavior
#### [List Connected Clients](/docs/client-libraries/node/request-db/list-connected-clients) * Description * Source Code Example * Parameters * Returns * Behavior
### [Request - AI](/docs/client-libraries/node/request-ai)
#### [Ping](/docs/client-libraries/node/request-ai/ping) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Info Server](/docs/client-libraries/node/request-ai/info-server) * Description * Source Code Example * Parameters * Returns * Behavior
#### [List Stores](/docs/client-libraries/node/request-ai/list-stores) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Get Store](/docs/client-libraries/node/request-ai/get-store) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Create Store](/docs/client-libraries/node/request-ai/create-store) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Set](/docs/client-libraries/node/request-ai/set) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Upsert](/docs/client-libraries/node/request-ai/upsert) * Description * Source Code Example * Parameters * Returns * Behavior
#### [GetSimN](/docs/client-libraries/node/request-ai/get-simn) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Get By Predicate](/docs/client-libraries/node/request-ai/get-by-predicate) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Create Predicate Index](/docs/client-libraries/node/request-ai/create-predicate-index) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Drop Predicate Index](/docs/client-libraries/node/request-ai/drop-predicate-index) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Create Non Linear Algorithm Index](/docs/client-libraries/node/request-ai/create-non-linear-algx) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Drop Non Linear Algorithm Index](/docs/client-libraries/node/request-ai/drop-non-linear-algx) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Delete Key](/docs/client-libraries/node/request-ai/delete-key) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Drop Store](/docs/client-libraries/node/request-ai/drop-store) * Description * Source Code Example * Parameters * Returns * Behavior
### [Tracing](/docs/client-libraries/node/tracing) * Description * Source Code Example ### [Type Meanings](/docs/client-libraries/node/type-meanings) * Store Key * Store Value * Store Input * Store Predicates (Predicate Indices) * Predicates * PredicateConditions * MetadataValue * AIModels --- # FILE: client-libraries/node/request-ai/create-non-linear-algx.md --- title: Create Non Linear Algorithm Index sidebar_position: 11 --- # Create Non Linear Algorithm Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Creates a non-linear index (HNSW) to speed up similarity searches in an AI store. ## Create an HNSW Index
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { CreateNonLinearAlgorithmIndex } from "ahnlich-client-node/grpc/ai/query_pb"; import { NonLinearIndex, HNSWConfig } from "ahnlich-client-node/grpc/algorithm/nonlinear_pb"; async function createHNSWIndex() { const client = createAiClient("127.0.0.1:1370"); await client.createNonLinearAlgorithmIndex( new CreateNonLinearAlgorithmIndex({ store: "ai_store", schema: "analytics", nonLinearIndices: [ new NonLinearIndex({ index: { case: "hnsw", value: new HNSWConfig() }, }), ], }) ); console.log("HNSW index created"); } createHNSWIndex(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the AI store | | `nonLinearIndices` | `NonLinearIndex[]` | Yes | List of indices to create | --- # FILE: client-libraries/node/request-ai/create-predicate-index.md --- title: Create Predicate Index sidebar_position: 9 --- # Create Predicate Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Creates an index on metadata fields to optimize predicate-based queries in an AI store.
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { CreatePredIndex } from "ahnlich-client-node/grpc/ai/query_pb"; async function createPredicateIndex() { const client = createAiClient("127.0.0.1:1370"); await client.createPredIndex( new CreatePredIndex({ store: "ai_store", schema: "analytics", predicates: ["brand", "category"], }) ); console.log("Predicate indices created"); } createPredicateIndex(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the AI store | | `predicates` | `string[]` | Yes | List of metadata keys to index | --- # FILE: client-libraries/node/request-ai/create-store.md --- title: Create Store sidebar_position: 5 --- # Create Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The CreateStore request creates a new AI store with specified AI models. * **Input**: Store name, query model, index model, optional predicates, and configuration flags. * **Behavior**: Creates a new AI store that automatically generates embeddings using the specified models. * **Response**: Confirmation of store creation.
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { CreateStore } from "ahnlich-client-node/grpc/ai/query_pb"; import { AIModel } from "ahnlich-client-node/grpc/ai/models_pb"; async function createStore() { const client = createAiClient("127.0.0.1:1370"); await client.createStore( new CreateStore({ store: "ai_store", schema: "analytics", queryModel: AIModel.ALL_MINI_LM_L6_V2, indexModel: AIModel.ALL_MINI_LM_L6_V2, predicates: ["brand", "category"], errorIfExists: true, storeOriginal: true, }) ); console.log("AI store created successfully"); } createStore(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name for the new AI store | | `queryModel` | `AIModel` | Yes | AI model for embedding queries | | `indexModel` | `AIModel` | Yes | AI model for embedding stored data | | `predicates` | `string[]` | No | List of predicate keys to index | | `errorIfExists` | `boolean` | No | If `true`, throws error if store exists | | `storeOriginal` | `boolean` | No | If `true`, stores original input alongside embeddings | ## Available AI Models | Model | Type | Description | |-------|------|-------------| | `AIModel.ALL_MINI_LM_L6_V2` | Text | Sentence transformer for text embeddings | | `AIModel.RESNET50` | Image | Image classification and embeddings | | `AIModel.CLIP_VIT_B32` | Multimodal | Text and image embeddings | | `AIModel.BUFFALO_L` | Face | Face detection and recognition | | `AIModel.SFACE_YUNET` | Face | Face detection with YuNet | ## Example with Image Model
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { CreateStore } from "ahnlich-client-node/grpc/ai/query_pb"; import { AIModel } from "ahnlich-client-node/grpc/ai/models_pb"; async function createImageStore() { const client = createAiClient("127.0.0.1:1370"); await client.createStore( new CreateStore({ store: "image_store", schema: "analytics", queryModel: AIModel.RESNET50, indexModel: AIModel.RESNET50, predicates: ["filename", "category"], errorIfExists: true, storeOriginal: true, }) ); console.log("Image store created successfully"); } createImageStore(); ```
## Notes - The query and index models typically should be the same for consistent similarity - `storeOriginal: true` is useful for retrieving the original text/image in results - See [Type Meanings](/docs/client-libraries/node/type-meanings) for more details on AI models --- # FILE: client-libraries/node/request-ai/delete-key.md --- title: Delete Key sidebar_position: 13 --- # Delete Key ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Deletes entries from an AI store by their original input.
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { DelKey } from "ahnlich-client-node/grpc/ai/query_pb"; import { StoreInput } from "ahnlich-client-node/grpc/keyval_pb"; async function deleteKey() { const client = createAiClient("127.0.0.1:1370"); await client.delKey( new DelKey({ store: "ai_store", schema: "analytics", keys: [ new StoreInput({ value: { case: "rawString", value: "Jordan One" } }), ], }) ); console.log("Entry deleted"); } deleteKey(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the AI store | | `keys` | `StoreInput[]` | Yes | Array of inputs to delete | ## Notes - For AI stores, keys are the original inputs (text or binary), not vectors - Non-existent keys are silently ignored --- # FILE: client-libraries/node/request-ai/delete-predicate.md --- title: Delete Predicate --- # Delete Predicate ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. This request removes all entries in a store that match a specified predicate condition. * **Input:** * `store`: the name of the store. * `condition`: a logical predicate that filters which entries should be deleted. * **Behavior:** Instead of deleting by a specific key, the server scans the store and deletes all entries that satisfy the predicate condition. * **Response:** * `deletedCount` - the number of items successfully deleted.
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { DelPred } from "ahnlich-client-node/grpc/ai/query_pb"; import { PredicateCondition, Predicate, Equals } from "ahnlich-client-node/grpc/predicate_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; async function deletePredicate() { const client = createAiClient("127.0.0.1:1370"); // Create a predicate condition to match entries where "category" equals "outdated" // Using oneof discriminated unions with { case: "...", value: ... } pattern const condition = new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "category", value: new MetadataValue({ value: { case: "rawString", value: "outdated" } }) }) } }) } }); const response = await client.delPred( new DelPred({ store: "my_store", schema: "analytics", condition: condition }) ); console.log(`Deleted ${response.deletedCount} items`); } deletePredicate(); ```
--- # FILE: client-libraries/node/request-ai/drop-non-linear-algx.md --- title: Drop Non Linear Algorithm Index sidebar_position: 12 --- # Drop Non Linear Algorithm Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Removes a non-linear index (HNSW) from an AI store.
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { DropNonLinearAlgorithmIndex } from "ahnlich-client-node/grpc/ai/query_pb"; import { NonLinearAlgorithm } from "ahnlich-client-node/grpc/algorithm/nonlinear_pb"; async function dropNonLinearIndex() { const client = createAiClient("127.0.0.1:1370"); await client.dropNonLinearAlgorithmIndex( new DropNonLinearAlgorithmIndex({ store: "ai_store", schema: "analytics", nonLinearIndices: [NonLinearAlgorithm.HNSW], errorIfNotExists: true, }) ); console.log("HNSW index dropped"); } dropNonLinearIndex(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the AI store | | `nonLinearIndices` | `NonLinearAlgorithm[]` | Yes | List of index types to remove | | `errorIfNotExists` | `boolean` | No | If `true`, throws error if index doesn't exist | --- # FILE: client-libraries/node/request-ai/drop-predicate-index.md --- title: Drop Predicate Index sidebar_position: 10 --- # Drop Predicate Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Removes an existing predicate index from an AI store.
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { DropPredIndex } from "ahnlich-client-node/grpc/ai/query_pb"; async function dropPredicateIndex() { const client = createAiClient("127.0.0.1:1370"); await client.dropPredIndex( new DropPredIndex({ store: "ai_store", schema: "analytics", predicates: ["brand"], errorIfNotExists: true, }) ); console.log("Predicate index dropped"); } dropPredicateIndex(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the AI store | | `predicates` | `string[]` | Yes | List of predicate indices to remove | | `errorIfNotExists` | `boolean` | No | If `true`, throws error if index doesn't exist | --- # FILE: client-libraries/node/request-ai/drop-store.md --- title: Drop Store sidebar_position: 14 --- # Drop Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Deletes an entire AI store and all its data.
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { DropStore } from "ahnlich-client-node/grpc/ai/query_pb"; async function dropStore() { const client = createAiClient("127.0.0.1:1370"); await client.dropStore( new DropStore({ store: "ai_store", schema: "analytics", errorIfNotExists: true, }) ); console.log("AI store dropped"); } dropStore(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the AI store to delete | | `errorIfNotExists` | `boolean` | No | If `true`, throws error if store doesn't exist | ## Notes - **This operation is irreversible** - All data, embeddings, and indices are permanently deleted --- # FILE: client-libraries/node/request-ai/get-by-predicate.md --- title: Get By Predicate sidebar_position: 8 --- # Get By Predicate ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The GetPred request retrieves entries from an AI store that match a specified predicate condition. * **Input**: Store name and predicate condition. * **Behavior**: Returns all entries whose metadata matches the predicate condition. * **Response**: A list of matching entries.
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { GetPred } from "ahnlich-client-node/grpc/ai/query_pb"; import { PredicateCondition, Predicate, Equals } from "ahnlich-client-node/grpc/predicate_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; async function getByPredicate() { const client = createAiClient("127.0.0.1:1370"); const response = await client.getPred( new GetPred({ store: "ai_store", schema: "analytics", condition: new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "brand", value: new MetadataValue({ value: { case: "rawString", value: "Nike" } }), }), }, }), }, }), }) ); console.log(`Found ${response.entries.length} Nike products`); } getByPredicate(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the AI store | | `condition` | `PredicateCondition` | Yes | The filter condition | ## Notes - Same predicate structure as DB operations - For optimal performance, create predicate indices on frequently filtered fields - See [Type Meanings](/docs/client-libraries/node/type-meanings) for predicate details --- # FILE: client-libraries/node/request-ai/get-key.md --- title: GetKey --- # GetKey ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The GetKey request retrieves entries from a store based on exact input matches. * **Input**: * `store`: the store name. * `keys`: the exact inputs (text/image) you want to retrieve. * **Behavior**: Finds the stored entries that match the inputs exactly. * **Response**: Returns the entries (input + metadata) if found.
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { GetKey } from "ahnlich-client-node/grpc/ai/query_pb"; import { StoreInput } from "ahnlich-client-node/grpc/keyval_pb"; async function getKey() { const client = createAiClient("127.0.0.1:1370"); // Create the input to look up (using oneof discriminated union) const storeInput = new StoreInput({ value: { case: "rawString", value: "Your search text" } }); const response = await client.getKey( new GetKey({ store: "my_store", schema: "analytics", keys: [storeInput] }) ); // response.entries contains matching (key, value) pairs for (const entry of response.entries) { console.log("Found entry:", entry); } } getKey(); ```
--- # FILE: client-libraries/node/request-ai/get-simn.md --- title: GetSimN sidebar_position: 7 --- # GetSimN ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The GetSimN request performs semantic similarity search, finding entries most similar to a query input. * **Input**: Store name, query input, number of results, and similarity algorithm. * **Behavior**: Embeds the query using the store's query model and finds the N most similar entries. * **Response**: A list of entries with their similarity scores.
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { GetSimN } from "ahnlich-client-node/grpc/ai/query_pb"; import { StoreInput } from "ahnlich-client-node/grpc/keyval_pb"; import { Algorithm } from "ahnlich-client-node/grpc/algorithm/algorithm_pb"; async function getSimN() { const client = createAiClient("127.0.0.1:1370"); const response = await client.getSimN( new GetSimN({ store: "ai_store", schema: "analytics", searchInput: new StoreInput({ value: { case: "rawString", value: "Jordan" } }), closestN: 3, algorithm: Algorithm.COSINE_SIMILARITY, }) ); console.log(response.entries); for (const entry of response.entries) { console.log(`Input: ${entry.input?.value}`); console.log(`Similarity: ${entry.similarity}`); console.log(`Metadata: ${JSON.stringify(entry.value?.value)}`); } } getSimN(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the AI store | | `searchInput` | `StoreInput` | Yes | The query input (text or image) | | `closestN` | `number` | Yes | Number of similar entries to return | | `algorithm` | `Algorithm` | Yes | Similarity algorithm to use | | `condition` | `PredicateCondition` | No | Optional filter condition | ## Example with Text Search
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { GetSimN } from "ahnlich-client-node/grpc/ai/query_pb"; import { StoreInput } from "ahnlich-client-node/grpc/keyval_pb"; import { Algorithm } from "ahnlich-client-node/grpc/algorithm/algorithm_pb"; async function semanticSearch() { const client = createAiClient("127.0.0.1:1370"); // Search for shoes similar to "comfortable running shoes" const response = await client.getSimN( new GetSimN({ store: "products", schema: "analytics", searchInput: new StoreInput({ value: { case: "rawString", value: "comfortable running shoes" }, }), closestN: 5, algorithm: Algorithm.COSINE_SIMILARITY, }) ); console.log("Top 5 similar products:"); for (const entry of response.entries) { console.log(`- ${entry.input?.value} (similarity: ${entry.similarity})`); } } semanticSearch(); ```
## Example with Predicate Filter
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { GetSimN } from "ahnlich-client-node/grpc/ai/query_pb"; import { StoreInput } from "ahnlich-client-node/grpc/keyval_pb"; import { Algorithm } from "ahnlich-client-node/grpc/algorithm/algorithm_pb"; import { PredicateCondition, Predicate, Equals } from "ahnlich-client-node/grpc/predicate_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; async function searchWithFilter() { const client = createAiClient("127.0.0.1:1370"); // Search for Nike products similar to "basketball shoes" const response = await client.getSimN( new GetSimN({ store: "products", schema: "analytics", searchInput: new StoreInput({ value: { case: "rawString", value: "basketball shoes" }, }), closestN: 5, algorithm: Algorithm.COSINE_SIMILARITY, condition: new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "brand", value: new MetadataValue({ value: { case: "rawString", value: "Nike" } }), }), }, }), }, }), }) ); console.log("Top Nike basketball-related products:"); for (const entry of response.entries) { console.log(`- ${entry.input?.value}`); } } searchWithFilter(); ```
## Notes - The query input is automatically embedded using the store's query model - Semantic search finds conceptually similar results, not just keyword matches - Use predicate filters to narrow results by metadata --- # FILE: client-libraries/node/request-ai/get-store.md --- title: Get Store sidebar_position: 4 --- # Get Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The GetStore request retrieves detailed information about a single AI store by its name. * **Input**: Store name. * **Behavior**: The server returns detailed information about the specified AI store. * **Response**: AI store information including models, dimension, and indices.
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { GetStore } from "ahnlich-client-node/grpc/ai/query_pb"; async function getStore() { const client = createAiClient("127.0.0.1:1370"); const response = await client.getStore( new GetStore({ store: "ai_store", schema: "analytics" }) ); console.log(response.name); // Store name console.log(response.queryModel); // AI model used for querying console.log(response.indexModel); // AI model used for indexing console.log(response.embeddingSize); // Number of stored embeddings console.log(response.dimension); // Vector dimension console.log(response.predicateIndices); // Indexed predicate keys console.log(response.dbInfo); // Optional DB store info } getStore(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the AI store to retrieve | ## Response Fields | Field | Type | Description | |-------|------|-------------| | `name` | `string` | The name of the store | | `queryModel` | `AIModel` | AI model used for query embedding | | `indexModel` | `AIModel` | AI model used for indexing | | `embeddingSize` | `number` | Number of stored embeddings | | `dimension` | `number` | Vector dimension | | `predicateIndices` | `string[]` | List of indexed predicate keys | | `dbInfo` | `StoreInfo` | Optional underlying DB store info | --- # FILE: client-libraries/node/request-ai/info-server.md --- title: Info Server sidebar_position: 2 --- # Info Server The InfoServer request retrieves metadata about the Ahnlich AI server. * **Input**: No arguments required. * **Behavior**: The client requests server information. * **Response**: Server information including version and supported models.
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { InfoServer } from "ahnlich-client-node/grpc/ai/query_pb"; async function infoServer() { const client = createAiClient("127.0.0.1:1370"); const response = await client.infoServer(new InfoServer()); console.log(response.info?.version); } infoServer(); ```
--- # FILE: client-libraries/node/request-ai/list-connected-clients.md --- title: List Connected Clients --- # List Connected Clients The ListClients request retrieves a list of all clients currently connected to the Ahnlich AI server. * **Input**: No arguments required. * **Behavior**: The server returns information about all active client connections. * **Response**: A list of connected client information.
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { ListClients } from "ahnlich-client-node/grpc/ai/query_pb"; async function listConnectedClients() { const client = createAiClient("127.0.0.1:1370"); const response = await client.listClients(new ListClients()); console.log(response.clients); // Iterate over connected clients for (const clientInfo of response.clients) { console.log(`Client: ${clientInfo.address}`); } } listConnectedClients(); ```
--- # FILE: client-libraries/node/request-ai/list-stores.md --- title: List Stores sidebar_position: 3 --- # List Stores ## Schema `ListStores` accepts an optional `schema` field. When it is omitted, the server lists stores in `public` only; it does not list stores across every schema. Set `schema` to list stores in another schema. The ListStores request retrieves AI stores from one schema on the Ahnlich AI server. When `schema` is omitted, that schema is `public`. * **Input**: Optional `schema` field. * **Behavior**: The server returns AI stores in the requested schema. * **Response**: A list of AI store information.
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { ListStores } from "ahnlich-client-node/grpc/ai/query_pb"; async function listStores() { const client = createAiClient("127.0.0.1:1370"); const response = await client.listStores(new ListStores({ schema: "analytics" })); console.log(response.stores.map((s) => s.name)); for (const store of response.stores) { console.log(`Store: ${store.name}`); console.log(` Query Model: ${store.queryModel}`); console.log(` Index Model: ${store.indexModel}`); console.log(` Embedding Size: ${store.embeddingSize}`); } } listStores(); ```
--- # FILE: client-libraries/node/request-ai/ping.md --- title: Ping sidebar_position: 1 --- # Ping The Ping request is used to test the connectivity between the Node.js client and the Ahnlich AI server. * **Input**: No arguments required. * **Behavior**: The client sends a ping message to the AI server, and the server responds with a Pong. * **Response**: A Pong message confirming connectivity.
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { Ping } from "ahnlich-client-node/grpc/ai/query_pb"; async function ping() { const client = createAiClient("127.0.0.1:1370"); const response = await client.ping(new Ping()); console.log(response); // Pong } ping(); ```
--- # FILE: client-libraries/node/request-ai/purge-stores.md --- title: Purge Stores --- # Purge Stores Deletes **all vector stores** managed by the AI server, including all embeddings and associated metadata. This is a destructive operation that resets the AI service state, typically used during testing, cleanup, or when starting fresh with new datasets. * **Input**: No arguments required. * **Behavior**: Removes all stores and their contents from the AI server. * **Response**: Confirmation of deletion with count of deleted stores.
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { PurgeStores } from "ahnlich-client-node/grpc/ai/query_pb"; async function purgeStores() { const client = createAiClient("127.0.0.1:1370"); const response = await client.purgeStores(new PurgeStores()); console.log(`Purged stores. Deleted count: ${response.deletedCount}`); } purgeStores(); ```
:::warning This operation is **irreversible**. All stores and their data will be permanently deleted. Use with caution in production environments. ::: --- # FILE: client-libraries/node/request-ai/request-ai.md --- title: Request AI sidebar_position: 1 --- # Request AI This section covers all available AI operations for the Node.js SDK when interacting with Ahnlich AI. Ahnlich AI is the semantic layer that provides: * Automatic embedding generation using AI models * Text and image similarity search * Semantic understanding without manual vector management ## Available Operations ### Server Operations - [Ping](/docs/client-libraries/node/request-ai/ping) - Health check - [Info Server](/docs/client-libraries/node/request-ai/info-server) - Get server information ### Store Operations - [List Stores](/docs/client-libraries/node/request-ai/list-stores) - List AI stores in a schema - [Get Store](/docs/client-libraries/node/request-ai/get-store) - Get details of a specific AI store - [Create Store](/docs/client-libraries/node/request-ai/create-store) - Create a new AI store - [Drop Store](/docs/client-libraries/node/request-ai/drop-store) - Delete an AI store ### Data Operations - [Set](/docs/client-libraries/node/request-ai/set) - Insert entries (auto-generates embeddings) - [Upsert](/docs/client-libraries/node/request-ai/upsert) - Update a single entry matching a predicate - [GetSimN](/docs/client-libraries/node/request-ai/get-simn) - Semantic similarity search - [Get By Predicate](/docs/client-libraries/node/request-ai/get-by-predicate) - Filter entries by metadata - [Delete Key](/docs/client-libraries/node/request-ai/delete-key) - Delete entries by input ### Index Operations - [Create Predicate Index](/docs/client-libraries/node/request-ai/create-predicate-index) - Create metadata index - [Drop Predicate Index](/docs/client-libraries/node/request-ai/drop-predicate-index) - Remove metadata index - [Create Non Linear Algorithm Index](/docs/client-libraries/node/request-ai/create-non-linear-algx) - Create HNSW index - [Drop Non Linear Algorithm Index](/docs/client-libraries/node/request-ai/drop-non-linear-algx) - Remove HNSW index ## Key Differences from DB - **Automatic embeddings**: You provide text/images, AI generates vectors - **Semantic search**: Search by meaning, not exact vectors - **Model selection**: Choose from supported AI models (MiniLM, ResNet, CLIP, etc.) --- # FILE: client-libraries/node/request-ai/set.md --- title: Set sidebar_position: 6 --- # Set ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The Set request inserts entries into an AI store. The AI server automatically generates embeddings for the provided inputs. * **Input**: Store name, array of entries (input-value pairs), and preprocessing options. * **Behavior**: Generates embeddings for each input and stores them with associated metadata. * **Response**: Confirmation of the operation.
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { Set } from "ahnlich-client-node/grpc/ai/query_pb"; import { AiStoreEntry, StoreInput, StoreValue } from "ahnlich-client-node/grpc/keyval_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; import { PreprocessAction } from "ahnlich-client-node/grpc/ai/preprocess_pb"; async function setEntries() { const client = createAiClient("127.0.0.1:1370"); await client.set( new Set({ store: "ai_store", schema: "analytics", inputs: [ new AiStoreEntry({ key: new StoreInput({ value: { case: "rawString", value: "Jordan One" } }), value: new StoreValue({ value: { brand: new MetadataValue({ value: { case: "rawString", value: "Nike" } }), }, }), }), ], preprocessAction: PreprocessAction.NO_PREPROCESSING, }) ); console.log("Entry inserted successfully"); } setEntries(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the AI store | | `inputs` | `AiStoreEntry[]` | Yes | Array of entries to insert | | `preprocessAction` | `PreprocessAction` | No | Preprocessing to apply to inputs | ## AiStoreEntry Structure | Field | Type | Description | |-------|------|-------------| | `key` | `StoreInput` | The input (text or binary) to embed | | `value` | `StoreValue` | Metadata associated with the entry | ## StoreInput Types | Type | Description | |------|-------------| | `rawString` | Text input for text models | | `image` | Binary image data for image models | ## Example with Multiple Text Entries
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { Set } from "ahnlich-client-node/grpc/ai/query_pb"; import { AiStoreEntry, StoreInput, StoreValue } from "ahnlich-client-node/grpc/keyval_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; import { PreprocessAction } from "ahnlich-client-node/grpc/ai/preprocess_pb"; async function setMultipleEntries() { const client = createAiClient("127.0.0.1:1370"); const products = [ { name: "Air Jordan 1", brand: "Nike", category: "Basketball" }, { name: "Ultraboost", brand: "Adidas", category: "Running" }, { name: "Chuck Taylor", brand: "Converse", category: "Casual" }, ]; await client.set( new Set({ store: "products", schema: "analytics", inputs: products.map( (p) => new AiStoreEntry({ key: new StoreInput({ value: { case: "rawString", value: p.name } }), value: new StoreValue({ value: { brand: new MetadataValue({ value: { case: "rawString", value: p.brand } }), category: new MetadataValue({ value: { case: "rawString", value: p.category } }), }, }), }) ), preprocessAction: PreprocessAction.NO_PREPROCESSING, }) ); } setMultipleEntries(); ```
## Example with Binary Image
Click to expand source code ```ts import * as fs from "fs"; import { createAiClient } from "ahnlich-client-node"; import { Set } from "ahnlich-client-node/grpc/ai/query_pb"; import { AiStoreEntry, StoreInput, StoreValue } from "ahnlich-client-node/grpc/keyval_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; import { PreprocessAction } from "ahnlich-client-node/grpc/ai/preprocess_pb"; async function setImageEntry() { const client = createAiClient("127.0.0.1:1370"); const imageData = fs.readFileSync("./product.jpg"); await client.set( new Set({ store: "image_store", schema: "analytics", inputs: [ new AiStoreEntry({ key: new StoreInput({ value: { case: "image", value: imageData } }), value: new StoreValue({ value: { filename: new MetadataValue({ value: { case: "rawString", value: "product.jpg" } }), }, }), }), ], preprocessAction: PreprocessAction.NO_PREPROCESSING, }) ); } setImageEntry(); ```
--- # FILE: client-libraries/node/request-ai/upsert.md --- title: Upsert --- # Upsert ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The Upsert request updates a single entry matching a predicate condition in an AI store. The AI service automatically merges metadata and can re-embed new inputs. * **Input**: Store name, predicate condition, optional new input/value, preprocessing options. * **Behavior**: Updates exactly one matching entry. AI proxy always merges metadata. Errors on 0 or multiple matches. * **Response**: Upsert counts (inserted and updated).
Click to expand source code ```ts import { createAiClient } from "ahnlich-client-node"; import { Upsert } from "ahnlich-client-node/grpc/ai/query_pb"; import { StoreValue } from "ahnlich-client-node/grpc/keyval_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; import { PredicateCondition, Predicate, Equals } from "ahnlich-client-node/grpc/predicates_pb"; import { PreprocessAction } from "ahnlich-client-node/grpc/ai/preprocess_pb"; async function upsertEntry() { const client = createAiClient("127.0.0.1:1370"); const condition = new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "filename", value: new MetadataValue({ value: { case: "rawString", value: "photo.jpg" } }), }), }, }), }, }); const newValue = new StoreValue({ value: { tags: new MetadataValue({ value: { case: "rawString", value: "cat,outdoors" } }), }, }); const response = await client.upsert( new Upsert({ store: "images", schema: "media", condition, newValue, preprocessAction: PreprocessAction.NO_PREPROCESSING, }) ); console.log("Updated:", response.upsert?.updated); } upsertEntry(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the AI store | | `condition` | `PredicateCondition` | Yes | Must match exactly one entry | | `newInput` | `StoreInput` | No | New input (text/image) to re-embed | | `newValue` | `StoreValue` | No | Metadata to update (always merged) | | `preprocessAction` | `PreprocessAction` | No | Preprocessing for new input | | `executionProvider` | `ExecutionProvider` | No | Hardware acceleration (e.g., CUDA) | | `modelParams` | `Record` | No | Runtime model parameters | | `schema` | `string` | No | Schema namespace (defaults to "public") | ## Behavior - AI proxy always merges metadata (preserves AI-generated fields) - Predicate must match exactly one entry - Re-embeds input if `newInput` is provided - Errors if 0 or multiple entries match - Updated entries are immediately available for similarity search --- # FILE: client-libraries/node/request-db/create-non-linear-algx.md --- title: Create Non Linear Algorithm Index sidebar_position: 13 --- # Create Non Linear Algorithm Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The CreateNonLinearAlgorithmIndex request creates a non-linear index (HNSW) to speed up similarity searches. * **Input**: Store name and list of non-linear indices to create. * **Behavior**: Builds the specified non-linear index structure for faster similarity queries. * **Response**: Confirmation of index creation. ## Create an HNSW Index
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { CreateNonLinearAlgorithmIndex } from "ahnlich-client-node/grpc/db/query_pb"; import { NonLinearIndex, HNSWConfig } from "ahnlich-client-node/grpc/algorithm/nonlinear_pb"; async function createHNSWIndex() { const client = createDbClient("127.0.0.1:1369"); await client.createNonLinearAlgorithmIndex( new CreateNonLinearAlgorithmIndex({ store: "my_store", schema: "analytics", nonLinearIndices: [ new NonLinearIndex({ index: { case: "hnsw", value: new HNSWConfig() }, }), ], }) ); console.log("HNSW index created successfully"); } createHNSWIndex(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the store | | `nonLinearIndices` | `NonLinearIndex[]` | Yes | List of indices to create | ## Index Types | Type | Description | Best For | |------|-------------|----------| | `HNSW` | Hierarchical Navigable Small World | High dimensions, approximate but fast | ## Notes - Non-linear indices dramatically improve [GetSimN](/docs/client-libraries/node/request-db/get-simn) performance on large stores - HNSW is generally recommended for high-dimensional embeddings (128+) - Building indices takes time and memory proportional to store size - You can have multiple index types on the same store --- # FILE: client-libraries/node/request-db/create-predicate-index.md --- title: Create Predicate Index sidebar_position: 11 --- # Create Predicate Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The CreatePredIndex request creates an index on metadata fields to optimize predicate-based queries. * **Input**: Store name and list of predicate keys to index. * **Behavior**: Creates indices on the specified metadata fields for faster filtering. * **Response**: Confirmation of index creation.
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { CreatePredIndex } from "ahnlich-client-node/grpc/db/query_pb"; async function createPredicateIndex() { const client = createDbClient("127.0.0.1:1369"); await client.createPredIndex( new CreatePredIndex({ store: "my_store", schema: "analytics", predicates: ["label", "category"], }) ); console.log("Predicate indices created successfully"); } createPredicateIndex(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the store | | `predicates` | `string[]` | Yes | List of metadata keys to index | ## Notes - Indices significantly speed up predicate-based queries ([GetPred](/docs/client-libraries/node/request-db/get-by-predicate), filtered [GetSimN](/docs/client-libraries/node/request-db/get-simn)) - Creating indices has a one-time cost but improves query performance - Indices can also be specified during [store creation](/docs/client-libraries/node/request-db/create-store) --- # FILE: client-libraries/node/request-db/create-store.md --- title: Create Store sidebar_position: 6 --- # Create Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The CreateStore request creates a new vector store on the Ahnlich DB server. * **Input**: Store name, dimension, optional predicates, and error handling flag. * **Behavior**: Creates a new store with the specified configuration. The dimension is fixed at creation - all inserted vectors must match it. * **Response**: Confirmation of store creation.
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { CreateStore } from "ahnlich-client-node/grpc/db/query_pb"; async function createStore() { const client = createDbClient("127.0.0.1:1369"); await client.createStore( new CreateStore({ store: "my_store", schema: "analytics", dimension: 4, predicates: ["label", "category"], errorIfExists: true, }) ); console.log("Store created successfully"); } createStore(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name for the new store | | `dimension` | `number` | Yes | Vector dimension (all vectors must match this) | | `predicates` | `string[]` | No | List of predicate keys to index | | `errorIfExists` | `boolean` | No | If `true`, throws error if store exists; if `false`, silently ignores | ## Example with All Options
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { CreateStore } from "ahnlich-client-node/grpc/db/query_pb"; async function createStoreWithOptions() { const client = createDbClient("127.0.0.1:1369"); // Create a store for 128-dimensional embeddings // with predicate indices on "title" and "author" await client.createStore( new CreateStore({ store: "book_embeddings", schema: "analytics", dimension: 128, predicates: ["title", "author", "genre"], errorIfExists: false, // Don't error if already exists }) ); } createStoreWithOptions(); ```
## Notes - Store dimension cannot be changed after creation - Predicate indices can be added later using [Create Predicate Index](/docs/client-libraries/node/request-db/create-predicate-index) - Non-linear indices (HNSW) can be added using [Create Non Linear Algorithm Index](/docs/client-libraries/node/request-db/create-non-linear-algx) --- # FILE: client-libraries/node/request-db/delete-key.md --- title: Delete Key sidebar_position: 15 --- # Delete Key ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The DelKey request deletes entries from a store by their exact vector keys. * **Input**: Store name and array of keys to delete. * **Behavior**: Removes entries that exactly match the provided keys. * **Response**: Confirmation of deletion.
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { DelKey } from "ahnlich-client-node/grpc/db/query_pb"; import { StoreKey } from "ahnlich-client-node/grpc/keyval_pb"; async function deleteKey() { const client = createDbClient("127.0.0.1:1369"); await client.delKey( new DelKey({ store: "my_store", schema: "analytics", keys: [new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] })], }) ); console.log("Entry deleted successfully"); } deleteKey(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the store | | `keys` | `StoreKey[]` | Yes | Array of vector keys to delete | ## Example with Multiple Keys
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { DelKey } from "ahnlich-client-node/grpc/db/query_pb"; import { StoreKey } from "ahnlich-client-node/grpc/keyval_pb"; async function deleteMultipleKeys() { const client = createDbClient("127.0.0.1:1369"); await client.delKey( new DelKey({ store: "my_store", schema: "analytics", keys: [ new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] }), new StoreKey({ key: [5.0, 6.0, 7.0, 8.0] }), new StoreKey({ key: [9.0, 10.0, 11.0, 12.0] }), ], }) ); console.log("Entries deleted successfully"); } deleteMultipleKeys(); ```
## Notes - Keys must exactly match stored vectors - Non-existent keys are silently ignored - For bulk deletion by metadata, use [Delete Predicate](/docs/client-libraries/node/request-db/delete-predicate) --- # FILE: client-libraries/node/request-db/delete-predicate.md --- title: Delete Predicate sidebar_position: 16 --- # Delete Predicate ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The DelPred request deletes all entries from a store that match a specified predicate condition. * **Input**: Store name and predicate condition. * **Behavior**: Removes all entries whose metadata matches the predicate. * **Response**: Confirmation of deletion with count of deleted entries.
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { DelPred } from "ahnlich-client-node/grpc/db/query_pb"; import { PredicateCondition, Predicate, Equals } from "ahnlich-client-node/grpc/predicate_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; async function deletePredicate() { const client = createDbClient("127.0.0.1:1369"); const response = await client.delPred( new DelPred({ store: "my_store", schema: "analytics", condition: new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "status", value: new MetadataValue({ value: { case: "rawString", value: "archived" } }), }), }, }), }, }), }) ); console.log(`Deleted ${response.deleted} entries`); } deletePredicate(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the store | | `condition` | `PredicateCondition` | Yes | The filter condition for deletion | ## Example with Complex Condition
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { DelPred } from "ahnlich-client-node/grpc/db/query_pb"; import { PredicateCondition, Predicate, Equals, AndCondition } from "ahnlich-client-node/grpc/predicate_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; async function deleteWithComplexCondition() { const client = createDbClient("127.0.0.1:1369"); // Delete all archived items in category "temp" const response = await client.delPred( new DelPred({ store: "my_store", schema: "analytics", condition: new PredicateCondition({ kind: { case: "and", value: new AndCondition({ left: new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "status", value: new MetadataValue({ value: { case: "rawString", value: "archived" } }), }), }, }), }, }), right: new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "category", value: new MetadataValue({ value: { case: "rawString", value: "temp" } }), }), }, }), }, }), }), }, }), }) ); console.log(`Deleted ${response.deleted} archived temp entries`); } deleteWithComplexCondition(); ```
## Notes - Use with caution - this operation permanently deletes data - For deleting specific entries, use [Delete Key](/docs/client-libraries/node/request-db/delete-key) - Predicate indices improve the performance of predicate-based deletions --- # FILE: client-libraries/node/request-db/drop-non-linear-algx.md --- title: Drop Non Linear Algorithm Index sidebar_position: 14 --- # Drop Non Linear Algorithm Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The DropNonLinearAlgorithmIndex request removes a non-linear index (HNSW) from a store. * **Input**: Store name, list of index types to remove, and error handling flag. * **Behavior**: Removes the specified non-linear indices. * **Response**: Confirmation of index removal.
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { DropNonLinearAlgorithmIndex } from "ahnlich-client-node/grpc/db/query_pb"; import { NonLinearAlgorithm } from "ahnlich-client-node/grpc/algorithm/nonlinear_pb"; async function dropNonLinearIndex() { const client = createDbClient("127.0.0.1:1369"); await client.dropNonLinearAlgorithmIndex( new DropNonLinearAlgorithmIndex({ store: "my_store", schema: "analytics", nonLinearIndices: [NonLinearAlgorithm.HNSW], errorIfNotExists: true, }) ); console.log("HNSW index dropped successfully"); } dropNonLinearIndex(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the store | | `nonLinearIndices` | `NonLinearAlgorithm[]` | Yes | List of index types to remove | | `errorIfNotExists` | `boolean` | No | If `true`, throws error if index doesn't exist | ## Available NonLinearAlgorithm Values | Value | Description | |-------|-------------| | `NonLinearAlgorithm.HNSW` | Hierarchical Navigable Small World | ## Notes - Dropping an index does not delete the underlying data - Similarity searches will fall back to linear scan after dropping indices - You may want to rebuild indices with different configurations --- # FILE: client-libraries/node/request-db/drop-predicate-index.md --- title: Drop Predicate Index sidebar_position: 12 --- # Drop Predicate Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The DropPredIndex request removes an existing predicate index from a store. * **Input**: Store name, list of predicate keys to remove, and error handling flag. * **Behavior**: Removes the specified predicate indices. * **Response**: Confirmation of index removal.
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { DropPredIndex } from "ahnlich-client-node/grpc/db/query_pb"; async function dropPredicateIndex() { const client = createDbClient("127.0.0.1:1369"); await client.dropPredIndex( new DropPredIndex({ store: "my_store", schema: "analytics", predicates: ["label"], errorIfNotExists: true, }) ); console.log("Predicate index dropped successfully"); } dropPredicateIndex(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the store | | `predicates` | `string[]` | Yes | List of predicate indices to remove | | `errorIfNotExists` | `boolean` | No | If `true`, throws error if index doesn't exist | ## Notes - Dropping an index does not delete the underlying data - After dropping, predicate queries on that field will be slower (full scan) - Consider keeping indices for frequently filtered fields --- # FILE: client-libraries/node/request-db/drop-store.md --- title: Drop Store sidebar_position: 17 --- # Drop Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The DropStore request deletes an entire store and all its data. * **Input**: Store name and error handling flag. * **Behavior**: Permanently removes the store and all entries. * **Response**: Confirmation of store deletion.
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { DropStore } from "ahnlich-client-node/grpc/db/query_pb"; async function dropStore() { const client = createDbClient("127.0.0.1:1369"); await client.dropStore( new DropStore({ store: "my_store", schema: "analytics", errorIfNotExists: true, }) ); console.log("Store dropped successfully"); } dropStore(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the store to delete | | `errorIfNotExists` | `boolean` | No | If `true`, throws error if store doesn't exist | ## Notes - **This operation is irreversible** - all data in the store will be permanently deleted - All indices (predicate and non-linear) are also removed - Use `errorIfNotExists: false` for idempotent cleanup scripts --- # FILE: client-libraries/node/request-db/get-by-predicate.md --- title: Get By Predicate sidebar_position: 10 --- # Get By Predicate ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The GetPred request retrieves entries from a store that match a specified predicate condition on their metadata. * **Input**: Store name and predicate condition. * **Behavior**: Returns all entries whose metadata matches the predicate condition. * **Response**: A list of matching entries.
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { GetPred } from "ahnlich-client-node/grpc/db/query_pb"; import { PredicateCondition, Predicate, Equals } from "ahnlich-client-node/grpc/predicate_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; async function getByPredicate() { const client = createDbClient("127.0.0.1:1369"); const response = await client.getPred( new GetPred({ store: "my_store", schema: "analytics", condition: new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "label", value: new MetadataValue({ value: { case: "rawString", value: "A" } }), }), }, }), }, }), }) ); console.log(`Found ${response.entries.length} entries with label='A'`); } getByPredicate(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the store | | `condition` | `PredicateCondition` | Yes | The filter condition | ## Available Predicates | Predicate | Description | |-----------|-------------| | `Equals` | Match exact value | | `NotEquals` | Exclude exact value | | `In` | Match if value is in a given set | | `NotIn` | Match if value is not in a given set | | `Contains` | Match if string contains substring | | `NotContains` | Match if string does not contain substring | ## Example with NotEquals
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { GetPred } from "ahnlich-client-node/grpc/db/query_pb"; import { PredicateCondition, Predicate, NotEquals } from "ahnlich-client-node/grpc/predicate_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; async function getNotEqualsPredicate() { const client = createDbClient("127.0.0.1:1369"); const response = await client.getPred( new GetPred({ store: "my_store", schema: "analytics", condition: new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "notEquals", value: new NotEquals({ key: "status", value: new MetadataValue({ value: { case: "rawString", value: "archived" } }), }), }, }), }, }), }) ); console.log(`Found ${response.entries.length} non-archived entries`); } getNotEqualsPredicate(); ```
## Example with AND Condition
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { GetPred } from "ahnlich-client-node/grpc/db/query_pb"; import { PredicateCondition, Predicate, Equals, AndCondition } from "ahnlich-client-node/grpc/predicate_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; async function getWithAndCondition() { const client = createDbClient("127.0.0.1:1369"); const response = await client.getPred( new GetPred({ store: "my_store", schema: "analytics", condition: new PredicateCondition({ kind: { case: "and", value: new AndCondition({ left: new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "category", value: new MetadataValue({ value: { case: "rawString", value: "electronics" } }), }), }, }), }, }), right: new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "brand", value: new MetadataValue({ value: { case: "rawString", value: "Apple" } }), }), }, }), }, }), }), }, }), }) ); console.log(`Found ${response.entries.length} Apple electronics`); } getWithAndCondition(); ```
## Notes - For optimal performance, create predicate indices on frequently filtered fields - Complex conditions (AND/OR) are supported for advanced filtering - See [Type Meanings](/docs/client-libraries/node/type-meanings) for more details on predicates --- # FILE: client-libraries/node/request-db/get-key.md --- title: Get Key sidebar_position: 9 --- # Get Key ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The GetKey request retrieves entries from a store by their exact vector keys. * **Input**: Store name and array of keys to retrieve. * **Behavior**: Returns entries that exactly match the provided keys. * **Response**: A list of matching entries with their values.
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { GetKey } from "ahnlich-client-node/grpc/db/query_pb"; import { StoreKey } from "ahnlich-client-node/grpc/keyval_pb"; async function getKey() { const client = createDbClient("127.0.0.1:1369"); const response = await client.getKey( new GetKey({ store: "my_store", schema: "analytics", keys: [new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] })], }) ); console.log(response.entries); // Iterate over results for (const entry of response.entries) { console.log(`Key: ${entry.key?.key}`); console.log(`Value: ${JSON.stringify(entry.value?.value)}`); } } getKey(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the store | | `keys` | `StoreKey[]` | Yes | Array of vector keys to retrieve | ## Example with Multiple Keys
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { GetKey } from "ahnlich-client-node/grpc/db/query_pb"; import { StoreKey } from "ahnlich-client-node/grpc/keyval_pb"; async function getMultipleKeys() { const client = createDbClient("127.0.0.1:1369"); const response = await client.getKey( new GetKey({ store: "my_store", schema: "analytics", keys: [ new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] }), new StoreKey({ key: [5.0, 6.0, 7.0, 8.0] }), new StoreKey({ key: [9.0, 10.0, 11.0, 12.0] }), ], }) ); console.log(`Retrieved ${response.entries.length} entries`); } getMultipleKeys(); ```
## Notes - Keys must exactly match stored vectors (including floating-point precision) - If a key is not found, it will simply not appear in the results - For similarity-based retrieval, use [GetSimN](/docs/client-libraries/node/request-db/get-simn) instead --- # FILE: client-libraries/node/request-db/get-simn.md --- title: GetSimN sidebar_position: 8 --- # GetSimN ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The GetSimN request finds the N closest (most similar) entries to a query vector using the specified similarity algorithm. * **Input**: Store name, query vector, number of results, and similarity algorithm. * **Behavior**: Performs a similarity search and returns the closest N entries. * **Response**: A list of entries with their similarity scores.
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { GetSimN } from "ahnlich-client-node/grpc/db/query_pb"; import { StoreKey } from "ahnlich-client-node/grpc/keyval_pb"; import { Algorithm } from "ahnlich-client-node/grpc/algorithm/algorithm_pb"; async function getSimN() { const client = createDbClient("127.0.0.1:1369"); const response = await client.getSimN( new GetSimN({ store: "my_store", schema: "analytics", searchInput: new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] }), closestN: 3, algorithm: Algorithm.COSINE_SIMILARITY, }) ); console.log(response.entries); // Iterate over results for (const entry of response.entries) { console.log(`Key: ${entry.key?.key}`); console.log(`Similarity: ${entry.similarity}`); console.log(`Value: ${JSON.stringify(entry.value?.value)}`); } } getSimN(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the store | | `searchInput` | `StoreKey` | Yes | The query vector | | `closestN` | `number` | Yes | Number of similar entries to return | | `algorithm` | `Algorithm` | Yes | Similarity algorithm to use | | `condition` | `PredicateCondition` | No | Optional filter condition | ## Available Algorithms | Algorithm | Description | |-----------|-------------| | `Algorithm.COSINE_SIMILARITY` | Cosine similarity (good for text embeddings) | | `Algorithm.EUCLIDEAN_DISTANCE` | Euclidean distance (L2 norm) | | `Algorithm.DOT_PRODUCT` | Dot product similarity | ## Example with Predicate Filter
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { GetSimN } from "ahnlich-client-node/grpc/db/query_pb"; import { StoreKey } from "ahnlich-client-node/grpc/keyval_pb"; import { Algorithm } from "ahnlich-client-node/grpc/algorithm/algorithm_pb"; import { PredicateCondition, Predicate, Equals } from "ahnlich-client-node/grpc/predicate_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; async function getSimNWithFilter() { const client = createDbClient("127.0.0.1:1369"); const response = await client.getSimN( new GetSimN({ store: "my_store", schema: "analytics", searchInput: new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] }), closestN: 5, algorithm: Algorithm.COSINE_SIMILARITY, condition: new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "category", value: new MetadataValue({ value: { case: "rawString", value: "electronics" } }), }), }, }), }, }), }) ); console.log(`Found ${response.entries.length} similar entries in category 'electronics'`); } getSimNWithFilter(); ```
## Notes - The query vector dimension must match the store dimension - Non-linear indices (HNSW) can significantly speed up searches on large stores - When using predicate filters, ensure the filter key has a predicate index for optimal performance --- # FILE: client-libraries/node/request-db/get-store.md --- title: Get Store sidebar_position: 5 --- # Get Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The GetStore request retrieves detailed information about a single store by its name. * **Input**: Store name. * **Behavior**: The server returns detailed information about the specified store. * **Response**: Store information including name, dimension, indices, and size.
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { GetStore } from "ahnlich-client-node/grpc/db/query_pb"; async function getStore() { const client = createDbClient("127.0.0.1:1369"); const response = await client.getStore( new GetStore({ store: "my_store", schema: "analytics" }) ); console.log(response.name); // Store name console.log(response.dimension); // Vector dimension console.log(response.predicateIndices); // Indexed predicate keys console.log(response.nonLinearIndices); // Non-linear algorithm indices console.log(response.len); // Number of entries console.log(response.sizeInBytes); // Size on disk } getStore(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the store to retrieve | ## Response Fields | Field | Type | Description | |-------|------|-------------| | `name` | `string` | The name of the store | | `dimension` | `number` | Vector dimension for this store | | `len` | `number` | Number of entries in the store | | `sizeInBytes` | `bigint` | Total size of the store in bytes | | `predicateIndices` | `string[]` | List of indexed predicate keys | | `nonLinearIndices` | `NonLinearAlgorithm[]` | List of non-linear indices | --- # FILE: client-libraries/node/request-db/info-server.md --- title: Info Server sidebar_position: 2 --- # Info Server The InfoServer request retrieves metadata about the Ahnlich DB server, including version information and other server details. * **Input**: No arguments required. * **Behavior**: The client requests server information, and the server responds with its metadata. * **Response**: Server information including version.
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { InfoServer } from "ahnlich-client-node/grpc/db/query_pb"; async function infoServer() { const client = createDbClient("127.0.0.1:1369"); const response = await client.infoServer(new InfoServer()); console.log(response.info?.version); // Server version console.log(response.info?.address); // Server address console.log(response.info?.numStores); // Number of stores } infoServer(); ```
--- # FILE: client-libraries/node/request-db/list-connected-clients.md --- title: List Connected Clients sidebar_position: 3 --- # List Connected Clients The ListClients request retrieves a list of all clients currently connected to the Ahnlich DB server. * **Input**: No arguments required. * **Behavior**: The server returns information about all active client connections. * **Response**: A list of connected client information.
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { ListClients } from "ahnlich-client-node/grpc/db/query_pb"; async function listConnectedClients() { const client = createDbClient("127.0.0.1:1369"); const response = await client.listClients(new ListClients()); console.log(response.clients); // Iterate over connected clients for (const clientInfo of response.clients) { console.log(`Client: ${clientInfo.address}`); } } listConnectedClients(); ```
--- # FILE: client-libraries/node/request-db/list-stores.md --- title: List Stores sidebar_position: 4 --- # List Stores ## Schema `ListStores` accepts an optional `schema` field. When it is omitted, the server lists stores in `public` only; it does not list stores across every schema. Set `schema` to list stores in another schema. The ListStores request retrieves vector stores from one schema on the Ahnlich DB server. When `schema` is omitted, that schema is `public`. * **Input**: Optional `schema` field. * **Behavior**: The server returns stores in the requested schema, including their names, dimensions, and indices. * **Response**: A list of `StoreInfo` objects containing store metadata.
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { ListStores } from "ahnlich-client-node/grpc/db/query_pb"; async function listStores() { const client = createDbClient("127.0.0.1:1369"); const response = await client.listStores(new ListStores({ schema: "analytics" })); // Get store names console.log(response.stores.map((s) => s.name)); // Iterate over stores with full details for (const store of response.stores) { console.log(`Store: ${store.name}`); console.log(` Dimension: ${store.dimension}`); console.log(` Entries: ${store.len}`); console.log(` Size: ${store.sizeInBytes} bytes`); console.log(` Predicate Indices: ${store.predicateIndices}`); console.log(` Non-Linear Indices: ${store.nonLinearIndices}`); } } listStores(); ```
## StoreInfo Fields Each `StoreInfo` object contains: | Field | Type | Description | |-------|------|-------------| | `name` | `string` | The name of the store | | `dimension` | `number` | Vector dimension for this store | | `len` | `number` | Number of entries in the store | | `sizeInBytes` | `bigint` | Total size of the store in bytes | | `predicateIndices` | `string[]` | List of indexed predicate keys | | `nonLinearIndices` | `NonLinearAlgorithm[]` | List of non-linear indices (HNSW) | --- # FILE: client-libraries/node/request-db/ping.md --- title: Ping sidebar_position: 1 --- # Ping The Ping request is used to test the connectivity between the Node.js client and the Ahnlich DB server. It acts as a health check to confirm that the DB service is up and running, and it is also useful for debugging or monitoring setups. * **Input**: No arguments are required. You may pass optional metadata, such as tracing IDs, for observability and distributed tracing. * **Behavior**: The client sends a simple ping message to the DB server, and the server responds with a Pong. * **Response**: A Pong message confirming connectivity.
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { Ping } from "ahnlich-client-node/grpc/db/query_pb"; async function ping() { // Initialize client const client = createDbClient("127.0.0.1:1369"); // Make request const response = await client.ping(new Ping()); console.log(response); // Pong } ping(); ```
### With Tracing
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { Ping } from "ahnlich-client-node/grpc/db/query_pb"; async function pingWithTracing() { // Initialize client with trace ID const client = createDbClient("127.0.0.1:1369", { traceId: "00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01", }); // Make request - trace ID is automatically included const response = await client.ping(new Ping()); console.log(response); // Pong } pingWithTracing(); ```
--- # FILE: client-libraries/node/request-db/request-db.md --- title: Request DB sidebar_position: 1 --- # Request DB This section covers all available DB (Database) operations for the Node.js SDK when interacting with Ahnlich DB. Ahnlich DB is the core vector storage engine that provides: * Vector storage with configurable dimensions * Similarity search using various algorithms * Metadata filtering with predicates * Non-linear indexing (HNSW) for faster searches ## Available Operations ### Server Operations - [Ping](/docs/client-libraries/node/request-db/ping) - Health check - [Info Server](/docs/client-libraries/node/request-db/info-server) - Get server information - [List Connected Clients](/docs/client-libraries/node/request-db/list-connected-clients) - List all connected clients ### Store Operations - [List Stores](/docs/client-libraries/node/request-db/list-stores) - List stores in a schema - [Get Store](/docs/client-libraries/node/request-db/get-store) - Get details of a specific store - [Create Store](/docs/client-libraries/node/request-db/create-store) - Create a new store - [Drop Store](/docs/client-libraries/node/request-db/drop-store) - Delete a store ### Data Operations - [Set](/docs/client-libraries/node/request-db/set) - Insert or update entries - [Upsert](/docs/client-libraries/node/request-db/upsert) - Update a single entry matching a predicate - [Get Key](/docs/client-libraries/node/request-db/get-key) - Retrieve entries by key - [GetSimN](/docs/client-libraries/node/request-db/get-simn) - Find N most similar entries - [Get By Predicate](/docs/client-libraries/node/request-db/get-by-predicate) - Filter entries by metadata - [Delete Key](/docs/client-libraries/node/request-db/delete-key) - Delete entries by key - [Delete Predicate](/docs/client-libraries/node/request-db/delete-predicate) - Delete entries matching a predicate ### Index Operations - [Create Predicate Index](/docs/client-libraries/node/request-db/create-predicate-index) - Create metadata index - [Drop Predicate Index](/docs/client-libraries/node/request-db/drop-predicate-index) - Remove metadata index - [Create Non Linear Algorithm Index](/docs/client-libraries/node/request-db/create-non-linear-algx) - Create HNSW index - [Drop Non Linear Algorithm Index](/docs/client-libraries/node/request-db/drop-non-linear-algx) - Remove HNSW index --- # FILE: client-libraries/node/request-db/set.md --- title: Set sidebar_position: 7 --- # Set ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The Set request inserts or updates entries in a store. Each entry consists of a vector key and associated metadata value. * **Input**: Store name and array of entries (key-value pairs). * **Behavior**: Inserts new entries or updates existing ones if the key already exists. * **Response**: Confirmation of the operation.
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { Set } from "ahnlich-client-node/grpc/db/query_pb"; import { DbStoreEntry, StoreKey, StoreValue } from "ahnlich-client-node/grpc/keyval_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; async function setEntries() { const client = createDbClient("127.0.0.1:1369"); await client.set( new Set({ store: "my_store", schema: "analytics", inputs: [ new DbStoreEntry({ key: new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] }), value: new StoreValue({ value: { label: new MetadataValue({ value: { case: "rawString", value: "A" } }), }, }), }), ], }) ); console.log("Entry inserted successfully"); } setEntries(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the store | | `inputs` | `DbStoreEntry[]` | Yes | Array of entries to insert/update | ## DbStoreEntry Structure Each entry consists of: | Field | Type | Description | |-------|------|-------------| | `key` | `StoreKey` | The vector key (must match store dimension) | | `value` | `StoreValue` | Metadata associated with the vector | ## Example with Multiple Entries
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { Set } from "ahnlich-client-node/grpc/db/query_pb"; import { DbStoreEntry, StoreKey, StoreValue } from "ahnlich-client-node/grpc/keyval_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; async function setMultipleEntries() { const client = createDbClient("127.0.0.1:1369"); await client.set( new Set({ store: "my_store", schema: "analytics", inputs: [ new DbStoreEntry({ key: new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] }), value: new StoreValue({ value: { label: new MetadataValue({ value: { case: "rawString", value: "First" } }), category: new MetadataValue({ value: { case: "rawString", value: "A" } }), }, }), }), new DbStoreEntry({ key: new StoreKey({ key: [5.0, 6.0, 7.0, 8.0] }), value: new StoreValue({ value: { label: new MetadataValue({ value: { case: "rawString", value: "Second" } }), category: new MetadataValue({ value: { case: "rawString", value: "B" } }), }, }), }), ], }) ); } setMultipleEntries(); ```
## Example with Binary Metadata
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { Set } from "ahnlich-client-node/grpc/db/query_pb"; import { DbStoreEntry, StoreKey, StoreValue } from "ahnlich-client-node/grpc/keyval_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; async function setWithBinaryMetadata() { const client = createDbClient("127.0.0.1:1369"); const binaryData = new Uint8Array([0x01, 0x02, 0x03, 0x04]); await client.set( new Set({ store: "my_store", schema: "analytics", inputs: [ new DbStoreEntry({ key: new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] }), value: new StoreValue({ value: { label: new MetadataValue({ value: { case: "rawString", value: "with binary" } }), image: new MetadataValue({ value: { case: "image", value: binaryData } }), }, }), }), ], }) ); } setWithBinaryMetadata(); ```
--- # FILE: client-libraries/node/request-db/upsert.md --- title: Upsert --- # Upsert ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The Upsert request updates a single entry matching a predicate condition. It errors if the predicate matches 0 or multiple entries. * **Input**: Store name, predicate condition, optional new key/value, merge flag. * **Behavior**: Updates exactly one matching entry. Errors on 0 or multiple matches. * **Response**: Upsert counts (inserted and updated).
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { Upsert } from "ahnlich-client-node/grpc/db/query_pb"; import { StoreValue } from "ahnlich-client-node/grpc/keyval_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; import { PredicateCondition, Predicate, Equals } from "ahnlich-client-node/grpc/predicates_pb"; async function upsertEntry() { const client = createDbClient("127.0.0.1:1369"); const condition = new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "id", value: new MetadataValue({ value: { case: "rawString", value: "123" } }), }), }, }), }, }); const newValue = new StoreValue({ value: { status: new MetadataValue({ value: { case: "rawString", value: "published" } }), }, }); const response = await client.upsert( new Upsert({ store: "my_store", schema: "analytics", condition, newValue, mergeMetadata: true, }) ); console.log("Updated:", response.upsert?.updated); } upsertEntry(); ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `string` | Yes | The name of the store | | `condition` | `PredicateCondition` | Yes | Must match exactly one entry | | `newKey` | `StoreKey` | No | New vector to replace existing key | | `newValue` | `StoreValue` | No | Metadata to update | | `mergeMetadata` | `boolean` | No | If true, merges metadata. If false, replaces (default: false) | | `schema` | `string` | No | Schema namespace (defaults to "public") | ## Behavior - Predicate must match exactly one entry - `mergeMetadata: true` merges new metadata into existing fields - `mergeMetadata: false` replaces metadata entirely - Errors if 0 or multiple entries match - Updated entries are immediately available for queries --- # FILE: client-libraries/node/tracing.md --- title: Tracing sidebar_position: 5 --- # Tracing The Node.js SDK supports distributed tracing using W3C Trace Context format. This allows you to correlate requests across services for debugging and monitoring. ## Setting Up Tracing Pass a W3C trace ID when creating the client:
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; const client = createDbClient("127.0.0.1:1369", { traceId: "00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01", }); // All requests from this client will include the trace ID const response = await client.ping(new Ping()); ```
## Trace ID Format The trace ID follows the W3C Trace Context format: ``` version-trace_id-parent_id-trace_flags ``` Example: `00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01` | Part | Description | |------|-------------| | `00` | Version (always `00` for W3C format) | | `80e1afed08e019fc1110464cfa66635c` | 32-character hex trace ID | | `7a085853722dc6d2` | 16-character hex parent span ID | | `01` | Trace flags (01 = sampled) | ## Using with OpenTelemetry
Click to expand source code ```ts import { createDbClient } from "ahnlich-client-node"; import { trace, context } from "@opentelemetry/api"; async function tracedOperation() { const tracer = trace.getTracer("ahnlich-app"); return tracer.startActiveSpan("db-operation", async (span) => { // Get the current trace context const spanContext = span.spanContext(); const traceId = `00-${spanContext.traceId}-${spanContext.spanId}-01`; // Create client with trace ID const client = createDbClient("127.0.0.1:1369", { traceId, }); try { const response = await client.listStores(new ListStores()); return response; } finally { span.end(); } }); } ```
## Header Details When a trace ID is provided, the SDK sets the `ahnlich-trace-id` header on every request. This header is used by the Ahnlich server for distributed tracing and logging. ## Notes - Trace IDs are optional but recommended for production systems - The same trace ID can be used across multiple clients to correlate requests - See [Distributed Tracing](/docs/components/distributed-tracing) for server-side setup --- # FILE: client-libraries/node/type-meanings.md --- title: Type Meanings sidebar_position: 6 --- # Type Meanings The following terms are fundamental to how **Ahnlich requests** are structured and processed in the Node.js SDK. ## StoreKey A one-dimensional `float32` vector that uniquely identifies an item in a DB store. * Functions like a **primary key** in a database * Vector dimension must match the store's configured dimension * Used in DB operations (Set, GetKey, DelKey, GetSimN) ```ts import { StoreKey } from "ahnlich-client-node/grpc/keyval_pb"; const key = new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] }); ``` ## StoreValue A map of string keys to `MetadataValue` containing the payload associated with an entry. * Stores metadata like titles, descriptions, categories * Can contain both text and binary data ```ts import { StoreValue } from "ahnlich-client-node/grpc/keyval_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; const value = new StoreValue({ value: { label: new MetadataValue({ value: { case: "rawString", value: "Product A" } }), category: new MetadataValue({ value: { case: "rawString", value: "Electronics" } }), }, }); ``` ## StoreInput A raw string or binary blob accepted by the AI proxy for automatic embedding generation. * Used in AI operations instead of StoreKey * The AI server converts inputs to embeddings automatically ```ts import { StoreInput } from "ahnlich-client-node/grpc/keyval_pb"; // Text input const textInput = new StoreInput({ value: { case: "rawString", value: "Hello world" }, }); // Binary input (e.g., image) const imageInput = new StoreInput({ value: { case: "image", value: new Uint8Array([...]) }, }); ``` ## Predicates Operations that define how filtering is performed on metadata. | Predicate | Description | Example | |-----------|-------------|---------| | `Equals` | Match exact value | `label = "A"` | | `NotEquals` | Exclude exact value | `status != "archived"` | | `In` | Match if value in set | `category IN ["A", "B"]` | | `NotIn` | Match if value not in set | `status NOT IN ["deleted"]` | | `Contains` | String contains substring | `name CONTAINS "shoe"` | | `NotContains` | String doesn't contain | `name NOT CONTAINS "test"` | ```ts import { Predicate, Equals } from "ahnlich-client-node/grpc/predicate_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; const predicate = new Predicate({ kind: { case: "equals", value: new Equals({ key: "category", value: new MetadataValue({ value: { case: "rawString", value: "shoes" } }), }), }, }); ``` ## PredicateCondition Conditions that wrap predicates and allow combining them logically. * Can represent a single predicate * Can combine predicates with `AND` or `OR` ### Single Predicate ```ts import { PredicateCondition, Predicate, Equals } from "ahnlich-client-node/grpc/predicate_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; const condition = new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "brand", value: new MetadataValue({ value: { case: "rawString", value: "Nike" } }), }), }, }), }, }); ``` ### AND Condition ```ts import { PredicateCondition, Predicate, Equals, AndCondition } from "ahnlich-client-node/grpc/predicate_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; const condition = new PredicateCondition({ kind: { case: "and", value: new AndCondition({ left: new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "brand", value: new MetadataValue({ value: { case: "rawString", value: "Nike" } }), }), }, }), }, }), right: new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "category", value: new MetadataValue({ value: { case: "rawString", value: "Running" } }), }), }, }), }, }), }), }, }); ``` ## MetadataValue The container used inside predicates and store values to hold data. | Type | Description | |------|-------------| | `rawString` | Text data | | `image` | Binary image data | ```ts import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; // String value const textValue = new MetadataValue({ value: { case: "rawString", value: "Hello" }, }); // Binary value const binaryValue = new MetadataValue({ value: { case: "image", value: new Uint8Array([0x01, 0x02, 0x03]) }, }); ``` ## AIModel The set of supported AI models within Ahnlich AI. | Model | Type | Description | |-------|------|-------------| | `ALL_MINI_LM_L6_V2` | Text | Sentence transformer (384 dimensions) | | `BGE_BASE_EN_V1_5` | Text | BAAI text embeddings | | `BGE_LARGE_EN_V1_5` | Text | BAAI large text embeddings | | `RESNET50` | Image | Image classification | | `CLIP_VIT_B32` | Multimodal | Text and image embeddings | | `BUFFALO_L` | Face | Face detection/recognition | | `SFACE_YUNET` | Face | Face detection with YuNet | | `CLAP` | Audio | Audio embeddings | ```ts import { AIModel } from "ahnlich-client-node/grpc/ai/models_pb"; const model = AIModel.ALL_MINI_LM_L6_V2; ``` ## Algorithm Similarity algorithms for vector search. | Algorithm | Description | |-----------|-------------| | `COSINE_SIMILARITY` | Cosine similarity (good for text) | | `EUCLIDEAN_DISTANCE` | Euclidean distance (L2 norm) | | `DOT_PRODUCT` | Dot product similarity | ```ts import { Algorithm } from "ahnlich-client-node/grpc/algorithm/algorithm_pb"; const algo = Algorithm.COSINE_SIMILARITY; ``` ## NonLinearAlgorithm Non-linear indexing algorithms for faster similarity search. | Algorithm | Description | |-----------|-------------| | `HNSW` | Hierarchical Navigable Small World (high dimensions) | ```ts import { NonLinearAlgorithm, HNSWConfig } from "ahnlich-client-node/grpc/algorithm/nonlinear_pb"; const algo = NonLinearAlgorithm.HNSW; ``` --- # FILE: client-libraries/python/bulk-requests.md --- title: Bulk Requests sidebar_position: 4 --- # Bulk Requests The AI client supports **bulk requests**, allowing you to send multiple operations at once. Instead of sending each request individually, you can batch them using a **pipeline builder**. Bulk requests are executed **sequentially** in the order they were added. The client automatically collects all responses and returns them as a single aggregated result. ## Source Code
Click to expand ```py from ahnlich_client_py import AhnlichAIClient client = AhnlichAIClient(address="127.0.0.1", port=1370) # Create a pipeline builder request_builder = client.pipeline() # Queue multiple requests request_builder.ping() request_builder.info_server() request_builder.list_clients() request_builder.list_stores() # Execute the pipeline response = client.exec() ```
## Explanation * **Pipeline builder**: Collects multiple requests before sending them to the AI service. * **Sequential execution**: Requests are executed one after the other, preserving the order in which they were added. * **Aggregated response**: The result is a list of individual responses, one for each request in the pipeline. * **Use case**: * Reduce network overhead by batching requests. * Efficiently run related queries in one execution cycle. * Simplify client code when multiple calls are always needed together. Example responses might include: * A `Ping` acknowledgment. * Server info metadata. * Connected clients. * Available stores. --- # FILE: client-libraries/python/python-specific-resources.md --- title: Python Specific Resources sidebar_posiiton: 1 --- ## QuickStart Set up your local development environment to start building with Ahnlich ## Install Python Go to the official [Python website](https://www.python.org/downloads/) Ensure that Python is installed on your system, and verify its version by running the following command in your terminal: ``` python3 -V ``` *Example output:* ``` Python 3.13.3 ``` ## Set Up a Local Python Environment Use a Python virtual environment to ensure your Ahnlich client python sdk runs in an isolated and consistent setup using the command to Create a virtual environment: ``` python3 -m venv env ``` and activate the virtual environment using: ``` source env/bin/activate ``` ## Install the Ahnlich Client PY You can install the Ahnlich Python client sdk using either Poetry or pip. Choose one of the methods below: ### Install with Poetry If you don’t already have Poetry installed, set it up by running: ``` curl -sSL https://install.python-poetry.org | python3 - ``` Ensure Poetry is available in your PATH: ``` export PATH="$HOME/.local/bin:$PATH" ``` Install the Ahnlich Python client: ``` poetry add ahnlich-client-py ``` ### Install with pip If you don’t have pip installed, first install it by running: ``` sudo apt update sudo apt install python3-pip -y ``` On macOS, you can install pip with `brew install python3` or by downloading Python from python.org Once pip is available, install the Ahnlich Python client sdk using: ``` pip3 install ahnlich-client-py ``` ## Package Information The Ahnlich Python client provides a gRPC-based SDK for interacting with Ahnlich-DB (vector storage, similarity search) and Ahnlich-AI (semantic models). ### Modules * grpclib – async gRPC client library. * ahnlich_client_py.grpc.db.server – contains server responses for all DB queries. * ahnlich_client_py.grpc.ai.server – contains server responses for AI queries. ### Initialization Every request starts by creating a client connection to the Ahnlich server: ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services import db_service async def init_client(): async with Channel(host="127.0.0.1", port=1369) as channel: client = db_service.DbServiceStub(channel) # client is now ready for requests if __name__ == "__main__": asyncio.run(init_client()) ``` --- # FILE: client-libraries/python/python.md --- title: Python sidebar_position: 20 --- # Python SDK Build Ahnlich Applications with the Python SDK – Vector Storage, Search, and AI tooling. ## Structure ### [Python Specific Resources](/docs/client-libraries/python/python-specific-resources) * Build Ahnlich Applications with the Python SDK * Ahnlich Python Technical Resources * Python SDK Quickstart - Setup Guide * Package Information * Server Response * Initialization ### [Request – DB](/docs/client-libraries/python/request-db)
#### [Ping](/docs/client-libraries/python/request-db/ping) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Info Server](/docs/client-libraries/python/request-db/info-server) * Description * Source Code Example * Parameters * Returns * Behavior
#### [List Stores](/docs/client-libraries/python/request-db/list-stores) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Create Store](/docs/client-libraries/python/request-db/create-store) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Set](/docs/client-libraries/python/request-db/set) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Upsert](/docs/client-libraries/python/request-db/upsert) * Description * Source Code Example * Parameters * Returns * Behavior
#### [GetSimN](/docs/client-libraries/python/request-db/get-simn) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Get Key](/docs/client-libraries/python/request-db/get-key) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Get By Predicate](/docs/client-libraries/python/request-db/get-by-predicate) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Create Predicate Index](/docs/client-libraries/python/request-db/create-predicate-index) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Drop Predicate Index](/docs/client-libraries/python/request-db/drop-predicate-index) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Delete Key](/docs/client-libraries/python/request-db/delete-key) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Drop Store](/docs/client-libraries/python/request-db/drop-store) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Create Non Linear Algorithm Index](/docs/client-libraries/python/request-db/create-non-linear-algx) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Drop Non Linear Algorithm Index](/docs/client-libraries/python/request-db/drop-non-linear-algx) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Delete Predicate](/docs/client-libraries/python/request-db/delete-predicate) * Description * Source Code Example * Parameters * Returns * Behavior
### [Request – AI](/docs/client-libraries/python/request-ai)
#### [Ping](/docs/client-libraries/python/request-ai/ping) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Info Server](/docs/client-libraries/python/request-ai/info-server) * Description * Source Code Example * Parameters * Returns * Behavior
#### [List Stores](/docs/client-libraries/python/request-ai/list-stores) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Create Store](/docs/client-libraries/python/request-ai/create-store) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Set](/docs/client-libraries/python/request-ai/set) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Upsert](/docs/client-libraries/python/request-ai/upsert) * Description * Source Code Example * Parameters * Returns * Behavior
#### [GetSimN](/docs/client-libraries/python/request-ai/get-simn) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Get By Predicate](/docs/client-libraries/python/request-ai/get-by-predicate) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Create Predicate Index](/docs/client-libraries/python/request-ai/create-predicate-index) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Drop Predicate Index](/docs/client-libraries/python/request-ai/drop-predicate-index) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Delete Key](/docs/client-libraries/python/request-ai/delete-key) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Drop Store](/docs/client-libraries/python/request-ai/drop-store) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Create Non Linear Algorithm Index](/docs/client-libraries/python/request-ai/create-non-linear-algx) * Description * Source Code Example * Parameters * Returns * Behavior
#### [Drop Non Linear Algorithm Index](/docs/client-libraries/python/request-ai/drop-non-linear-algx) * Description * Source Code Example * Parameters * Returns * Behavior
### [Bulk Requests](/docs/client-libraries/python/bulk-requests) * Description * Source Code Example ### [Type Meanings](/docs/client-libraries/python/type-meanings) * Store Key * Store Value * Store Predicates (Predicate Indices) * Predicates * PredicateConditions * MetadataValue * Search Input * AIModels * AIStoreType --- # FILE: client-libraries/python/request-ai/convert-to-embeddings.md --- title: Convert to Embeddings sidebar_position: 20 --- # Convert to Embeddings The `convert_store_input_to_embeddings` method converts raw inputs (text, images, audio) into embeddings using a specified AI model, without storing them in a database. ## Method Signature ```python async def convert_store_input_to_embeddings( self, request: ConvertStoreInputToEmbeddings ) -> StoreInputToEmbeddingsList ``` ## Parameters The `ConvertStoreInputToEmbeddings` request contains: - `store_inputs`: List of inputs to convert (text, images, or audio) - `preprocess_action`: How to preprocess inputs - `model`: AI model to use for conversion - `execution_provider` (optional): Execution provider (CPU, CUDA, etc.) - `model_params` (optional): Model-specific parameters ## Basic Example ```python from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc.ai.models import AiModel from ahnlich_client_py.grpc.ai import preprocess from ahnlich_client_py.grpc import keyval async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) inputs = [ keyval.StoreInput(raw_string="Hello world"), keyval.StoreInput(raw_string="Goodbye world"), ] response = await client.convert_store_input_to_embeddings( ai_query.ConvertStoreInputToEmbeddings( store_inputs=inputs, preprocess_action=preprocess.PreprocessAction.NoPreprocessing, model=AiModel.ALL_MINI_LM_L6_V2, ) ) # Access embeddings for item in response.values: if item.single: print(f"Embedding size: {len(item.single.embedding.key)}") ``` ## Face Detection with Metadata (Buffalo-L / SFace) Starting from version 0.2.1, face detection models return **bounding box metadata** alongside embeddings: ```python from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc.ai.models import AiModel from ahnlich_client_py.grpc.ai import preprocess from ahnlich_client_py.grpc import keyval async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) # Load image with open("group_photo.jpg", "rb") as f: image_bytes = f.read() inputs = [keyval.StoreInput(image=image_bytes)] response = await client.convert_store_input_to_embeddings( ai_query.ConvertStoreInputToEmbeddings( store_inputs=inputs, preprocess_action=preprocess.PreprocessAction.ModelPreprocessing, model=AiModel.BUFFALO_L, ) ) # Process each detected face for item in response.values: if item.multiple: print(f"Detected {len(item.multiple.embeddings)} faces") for face_data in item.multiple.embeddings: # Access embedding embedding = face_data.embedding.key # 512-dim for Buffalo_L print(f"Embedding size: {len(embedding)}") # Access bounding box metadata if face_data.metadata: metadata = face_data.metadata.value bbox_x1 = float(metadata["bbox_x1"].value) bbox_y1 = float(metadata["bbox_y1"].value) bbox_x2 = float(metadata["bbox_x2"].value) bbox_y2 = float(metadata["bbox_y2"].value) confidence = float(metadata["confidence"].value) print(f"Face at ({bbox_x1:.3f}, {bbox_y1:.3f}) " f"to ({bbox_x2:.3f}, {bbox_y2:.3f})") print(f"Confidence: {confidence:.3f}") ``` ## Metadata Fields (Face Detection Models) For Buffalo-L and SFace models, each detected face includes: | Field | Type | Range | Description | |-------|------|-------|-------------| | `bbox_x1` | float | 0.0-1.0 | Normalized x-coordinate of top-left corner | | `bbox_y1` | float | 0.0-1.0 | Normalized y-coordinate of top-left corner | | `bbox_x2` | float | 0.0-1.0 | Normalized x-coordinate of bottom-right corner | | `bbox_y2` | float | 0.0-1.0 | Normalized y-coordinate of bottom-right corner | | `confidence` | float | 0.0-1.0 | Detection confidence score | Coordinates are normalized to 0-1 range. To convert to pixel coordinates: ```python from PIL import Image img = Image.open("photo.jpg") width, height = img.size pixel_x1 = int(bbox_x1 * width) pixel_y1 = int(bbox_y1 * height) pixel_x2 = int(bbox_x2 * width) pixel_y2 = int(bbox_y2 * height) ``` ## Using Model Parameters Face detection models support tuning via `model_params`: ```python response = await client.convert_store_input_to_embeddings( ai_query.ConvertStoreInputToEmbeddings( store_inputs=inputs, preprocess_action=preprocess.PreprocessAction.ModelPreprocessing, model=AiModel.BUFFALO_L, model_params={"confidence_threshold": "0.9"}, # Higher = fewer faces ) ) ``` ## Response Structure ```python class StoreInputToEmbeddingsList: values: List[SingleInputToEmbedding] class SingleInputToEmbedding: input: StoreInput # Original input single: EmbeddingWithMetadata # For text/image models multiple: MultipleEmbedding # For face detection models class EmbeddingWithMetadata: embedding: StoreKey # The embedding vector metadata: StoreValue # Optional metadata (e.g., bounding boxes) class MultipleEmbedding: embeddings: List[EmbeddingWithMetadata] # One per detected face ``` ## Use Cases - **Testing models**: Quickly test how different inputs are embedded - **Batch processing**: Generate embeddings for analysis without storage - **Face detection**: Extract face locations and embeddings from photos - **Quality control**: Filter low-confidence detections before storage - **Visualization**: Draw bounding boxes on images using metadata --- # FILE: client-libraries/python/request-ai/create-non-linear-algx.md --- title: Create Non-Linear algorithm Index --- # Create Non-Linear algorithm Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The `Create Non Linear Algorithm Index` operation builds an index structure for non-linear search algorithms, such as HNSW. These indexes enable faster query performance in high-dimensional vector spaces by avoiding brute-force scans. This operation is typically used when: * You want to optimize search performance for similarity lookups. * You are initializing a new store and need efficient query structures. Each index type is specified using a `NonLinearIndex` message with a `HnswConfig`.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc.algorithm.nonlinear import NonLinearIndex, HnswConfig async def create_non_linear_algorithm_index(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) # Create an HNSW index (with optional config) response = await client.create_non_linear_algorithm_index( ai_query.CreateNonLinearAlgorithmIndex( store="test store", schema="analytics", non_linear_indices=[NonLinearIndex(hnsw=HnswConfig())], ) ) if __name__ == "__main__": asyncio.run(create_non_linear_algorithm_index()) ```
## Behavior * **Index does not exist** - The index for the given algorithm(s) is created successfully. * **Index already exists** - The request completes without creating a duplicate index (idempotent). ## Notes * Non-linear indexes are designed to improve query performance but may require additional memory. * You can create indices for multiple algorithms by listing them under `non_linear_indices=[...]`. * This operation only creates the index; it does not insert or modify store data. --- # FILE: client-libraries/python/request-ai/create-predicate-index.md --- title: Create Predicate Index --- # Create Predicate Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Predicate indices allow the AI store to efficiently filter results based on metadata fields. Use this operation to define which metadata keys should be indexed for faster query operations like `GetPred`. * `store` – Name of the AI store to create predicate indices on. * `predicates` – List of metadata fields to index. The response confirms the creation of indices.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query async def create_predicate_index(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.create_pred_index( ai_query.CreatePredIndex( store="test store", schema="analytics", predicates=["job", "rank"] ) ) print(response) # CreateIndex(created_indexes=1) if __name__ == "__main__": asyncio.run(create_predicate_index()) ```
* Use this function after creating a store or inserting entries to optimize predicate-based queries. * Only indexed predicates can be efficiently queried in `GetPred`. --- # FILE: client-libraries/python/request-ai/create-store.md --- title: Create Store --- # Create Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The `CreateStore` request is used to **initialize a new AI-powered store**. Unlike the DB client (which deals with raw vector dimensions), the AI client lets you specify **pretrained AI models** to handle embedding generation and indexing. This means you don’t have to manage vectors manually — the AI service will automatically embed inputs using the selected models.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc.ai.models import AiModel async def create_store(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.create_store( ai_query.CreateStore( store="test store", schema="analytics", query_model=AiModel.ALL_MINI_LM_L6_V2, index_model=AiModel.ALL_MINI_LM_L6_V2, predicates=["job"], non_linear_indices=[], # Optional: non-linear algorithms for faster search error_if_exists=True, # Store original controls if we choose to store the raw inputs # within the DB in order to be able to retrieve the originals again # during query, else only store values are returned store_original=True ) ) print(response) # Unit() if __name__ == "__main__": asyncio.run(create_store()) ```
## Key Notes * `query_model` - model used for encoding query inputs during searches. * `index_model` - model used for encoding stored data vectors. * `predicates` - metadata fields that can be filtered against (e.g., "`job`"). * `non_linear_indices` - list of non-linear algorithms for approximate search (can be empty list). * `store_original` - if `True`, the original raw input is stored alongside embeddings for later retrieval. * **Response** - returns `Unit()` on success. This request is critical in AI workflows because it allows you to: * Configure **semantic stores** with specialized embedding models. * Decide whether to preserve raw input text/images for retrieval. * Build **intelligent**, **AI-driven search** and **recommendation systems** without managing embeddings manually. --- # FILE: client-libraries/python/request-ai/delete-key.md --- title: Delete Key --- # Delete Key ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. How to delete keys and their associated values from a store using the Ahnlich AI Client. The `Delete Key` operation removes one or more entries from a store. Each key uniquely identifies a vector-value pair in the store, and deleting it permanently removes both the key and the stored value. This operation is useful when: * You want to remove outdated or irrelevant data. * You need to clean up test data. * You want to maintain store integrity by pruning unused keys. If the specified key does not exist, the behavior depends on the server configuration. In general, no changes are made, and the request safely returns without errors.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc import keyval async def drop_key(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.del_key( ai_query.DelKey( store="test store 1", schema="analytics", keys=[keyval.StoreInput(raw_string="Custom Made Jordan 4")] ) ) print(response) # Del() if __name__ == "__main__": asyncio.run(drop_key()) ```
## Behavior * **Key match found** → The key and its associated value are permanently removed. * **Key not found** → No action is performed; the request completes without altering the store. ## Source Code Example In the context of the rest of the application code:
Click to expand code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query async def trace_id(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) # Prepare tracing metadata tracing_id = "00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01" metadata = {"ahnlich-trace-id": tracing_id} # Make request with metadata response = await client.ping( ai_query.Ping(), metadata=metadata ) print(response) # Pong() if __name__ == "__main__": asyncio.run(trace_id()) ```
--- # FILE: client-libraries/python/request-ai/delete-predicate.md --- title: Delete Predicate --- # Delete Predicate ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. This request removes all entries in an AI store that match a specified predicate condition. * **Input:** * `store`: the name of the AI store. * `condition`: a logical predicate that filters which entries should be deleted. * **Behavior:** This is a passthrough operation to the underlying DB service. Instead of deleting by a specific key, the server scans the store and deletes all entries that satisfy the predicate condition. In this example, it deletes all entries where the metadata field "`category`" equals "`archived`". * **Response:** * `deleted_count` → the number of items successfully deleted.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc import predicates, metadata from ahnlich_client_py.grpc.ai.server import Del async def delete_predicate(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) condition = predicates.PredicateCondition( value=predicates.Predicate( equals=predicates.Equals( key="category", value=metadata.MetadataValue(raw_string="archived") ) ) ) response = await client.del_pred( ai_query.DelPred( store="my_ai_store", schema="analytics", condition=condition ) ) # response.deleted_count shows how many items were deleted if __name__ == "__main__": asyncio.run(delete_predicate()) ```
--- # FILE: client-libraries/python/request-ai/drop-non-linear-algx.md --- title: Drop Non-Linear Algorithm Index --- # Drop Non-Linear Algorithm Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The `Drop Non Linear Algorithm` Index operation removes an index that was previously created for non-linear algorithms such as HNSW. These indices are typically used to accelerate similarity searches in high-dimensional spaces. This operation is useful when: * An index is no longer needed and you want to free up system resources. * You need to replace an index with a different algorithm type. * You are resetting the store to a clean state. If the specified index does not exist, the request will fail if `error_if_not_exists=True` is set. Otherwise, the call will safely complete without error.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc.algorithm.nonlinear import NonLinearAlgorithm async def drop_non_linear_algorithm_index(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.drop_non_linear_algorithm_index( ai_query.DropNonLinearAlgorithmIndex( store="test store", schema="analytics", non_linear_indices=[NonLinearAlgorithm.HNSW], error_if_not_exists=True ) ) print(response) # Del(deleted_count=1) if __name__ == "__main__": asyncio.run(drop_non_linear_algorithm_index()) ```
## Behavior * **Index exists** → The specified algorithm index is removed from the store. * **Index does not exist** → * If `error_if_not_exists=True`, the operation raises an error. * If `error_if_not_exists=False`, the request completes successfully without changes. --- # FILE: client-libraries/python/request-ai/drop-predicate-index.md --- title: Drop Predicate Index --- # Drop Predicate Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Predicate index allow for efficient querying based on metadata fields. You can drop either a **single predicate index** or **multiple predicate indices** depending on your needs. * **Single Predicate Index**: Specify a single predicate in the `predicates` list, e.g., `["job"]`. This will remove the index associated with that one metadata field. * **Multiple Predicate Indices**: Include multiple predicates in the list, e.g., `["job", "rank"]`. All listed indices will be dropped in a single request. **Note**: The `error_if_not_exists` flag ensures that an error is thrown if the index does not exist. The `response.deleted_count` property shows how many indices were actually removed.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query async def drop_predicate_index(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.drop_pred_index( ai_query.DropPredIndex( store="test store", schema="analytics", predicates=["job"], error_if_not_exists=True ) ) print(response) # Del(deleted_count=1) if __name__ == "__main__": asyncio.run(drop_predicate_index()) ```
This approach allows you to maintain flexibility, dropping either one or many predicate indices in a single operation, while keeping your store optimized for AI queries. --- # FILE: client-libraries/python/request-ai/drop-store.md --- title: Drop Store --- # Drop Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The **Drop Store** request removes an entire AI store and all its contents. Use this when you no longer need the vector store or want to clean up your environment. **Inputs**: * `store` – Name of the AI store to drop. * `error_if_not_exists` – If `True`, the request will fail if the store does not exist. **Response**: Returns a response containing the number of stores deleted (`deleted_count`).
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query async def drop_store(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.drop_store( ai_query.DropStore( store="test store", schema="analytics", error_if_not_exists=True ) ) print(response) # Del(deleted_count=1) if __name__ == "__main__": asyncio.run(drop_store()) ```
--- # FILE: client-libraries/python/request-ai/get-by-predicate.md --- title: Get by Predicate --- # Get by Predicate ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. **GetPred** works similarly to `Get_key`, but instead of querying by a single key, it returns results that match the defined conditions. This allows filtering AI store entries by metadata values. * `store` – Name of the AI store to query. * `condition` – Predicate condition that defines which entries to return. This can include equality, range, or custom predicate logic. The result contains a list of entries matching the predicate.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc import predicates, metadata async def get_by_predicate(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) condition = predicates.PredicateCondition( value=predicates.Predicate( equals=predicates.Equals( key="brand", value=metadata.MetadataValue(raw_string="Nike") ) ) ) response = await client.get_pred( ai_query.GetPred( store="test store 1", schema="analytics", condition=condition ) ) print(response) #Get(entries=[GetEntry(key=StoreInput(raw_string='Jordan One'), value=StoreValue(value={'brand': MetadataValue(raw_string='Nike')}))]) if __name__ == "__main__": asyncio.run(get_by_predicate()) ```
* The predicate condition can be extended to other metadata fields beyond "`brand`". * This request is specifically designed for AI store queries. --- # FILE: client-libraries/python/request-ai/get-key.md --- title: Get Key --- # Get Key ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The `GetKey` request retrieves specific entries from an AI store by their exact keys. This is a **direct lookup** operation that returns the stored data and metadata for the provided keys. ## Parameters * `store` – Name of the AI store to query * `keys` – List of `StoreInput` keys to retrieve (the original inputs used when storing) ## Behavior - Performs exact key lookup (not similarity search) - Returns entries with stored data and metadata for each found key - Missing keys are silently skipped (no error for non-existent keys) - Useful for retrieving known items you previously stored
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc import keyval async def get_key(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.get_key( ai_query.GetKey( store="test store 1", schema="analytics", keys=[ keyval.StoreInput(raw_string="Adidas Yeezy"), keyval.StoreInput(raw_string="Nike Air Jordans"), ] ) ) # Response contains entries for each found key for entry in response.entries: print(f"Key: {entry.key}") print(f"Value: {entry.value}") if __name__ == "__main__": asyncio.run(get_key()) ```
## Response Returns a list of entries, where each entry contains: - `key` – The original `StoreInput` that was stored - `value` – The associated metadata (StoreValue) If a requested key doesn't exist in the store, it won't appear in the results. --- # FILE: client-libraries/python/request-ai/get-simn.md --- title: GetSimN --- # GetSimN ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. How to retrieve the top N most similar entries from an AI store using the Ahnlich Python SDK. **GetSimN** returns an array of tuples (`store_key`, `store_value`, `similarity_score`) of the maximum specified `N`. This allows you to perform similarity searches against the stored AI embeddings. * `store` – Name of the AI store to query. * `search_input` – Query input (string or vector). * `closest_n` – Maximum number of similar results to return (must be > 0). * `algorithm` – Similarity algorithm to use (e.g., Cosine Similarity). * `condition` – Optional predicate condition to filter results. See [Predicates documentation](/docs/components/predicates). * `preprocess_action` – Controls input preprocessing: - `PreprocessAction.ModelPreprocessing` – Apply model's built-in preprocessing (recommended) - `PreprocessAction.NoPreprocessing` – Skip preprocessing (use if you've already preprocessed the input) * `execution_provider` – Optional hardware acceleration (e.g., CUDA, TensorRT, CoreML). Set to `None` to use default CPU execution. * `model_params` – Optional dictionary of runtime parameters for the AI model (`Dict[str, str]`). Used by face detection models (Buffalo\_L, SFace+YuNet) to control behavior like `confidence_threshold`. Pass an empty dict `{}` to use model defaults. See [Model Parameters](/docs/components/ahnlich-ai/advanced#model-parameters-model_params) for details. The result contains a list of entries with similarity scores. Source code in the context of the rest of the application code.
Click to expand ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc import keyval from ahnlich_client_py.grpc.algorithm import algorithms from ahnlich_client_py.grpc.ai.preprocess import PreprocessAction async def get_sim_n(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.get_sim_n( ai_query.GetSimN( store="test store 1", schema="analytics", search_input=keyval.StoreInput(raw_string="Jordan"), condition=None, # Optional predicate condition closest_n=3, algorithm=algorithms.Algorithm.CosineSimilarity, preprocess_action=PreprocessAction.ModelPreprocessing, # Apply model's preprocessing execution_provider=None, # Optional execution provider model_params={} # Optional: runtime model parameters (e.g., {"confidence_threshold": "0.9"} for face detection) ) ) # Response contains entries with similarity scores for entry in response.entries: print(f"Key: {entry.key.raw_string}") print(f"Score: {entry.similarity}") print(f"Value: {entry.value}") # Key: Jordan One # Score: Similarity(value=0.858908474445343) # Value: StoreValue(value={'brand': MetadataValue(raw_string='Nike')}) # Key: Yeezey # Score: Similarity(value=0.21911849081516266) # Value: StoreValue(value={'brand': MetadataValue(raw_string='Adidas')}) if __name__ == "__main__": asyncio.run(get_sim_n()) ```
* `closest_n` must always be a non-zero integer. * This request is designed specifically for AI store queries. --- # FILE: client-libraries/python/request-ai/get-store.md --- title: Get Store --- # Get Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Returns detailed information about a specific AI store by name. * **Input**: Store name. * **Behavior**: Retrieves metadata and configuration for the specified AI store, including model information. * **Response**: AI store information including models, dimension, and optional DB store info.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query async def get_ai_store_info(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.get_store( ai_query.GetStore(store="ai_store", schema="analytics") ) print(f"Store name: {response.name}") print(f"Query model: {response.query_model}") print(f"Index model: {response.index_model}") print(f"Embedding size: {response.embedding_size}") print(f"Dimension: {response.dimension}") print(f"Predicate indices: {response.predicate_indices}") if response.db_info: print(f"DB store size: {response.db_info.size_in_bytes} bytes") if __name__ == "__main__": asyncio.run(get_ai_store_info()) ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `str` | Yes | The name of the AI store to retrieve | | `schema` | `str` | No | Schema containing the AI store. Defaults to `public` when omitted | ## Response: AiStoreInfo | Field | Type | Description | |-------|------|-------------| | `name` | `str` | Store name | | `query_model` | `AiModel` | AI model used for query embeddings | | `index_model` | `AiModel` | AI model used for index embeddings | | `embedding_size` | `int` | Number of stored embeddings | | `dimension` | `int` | Vector dimension (determined by model) | | `predicate_indices` | `List[str]` | List of indexed predicate keys | | `db_info` | `Optional[StoreInfo]` | Underlying DB store info (when AI is connected to DB) | ## Notes - Returns an error if the store does not exist - The `db_info` field is present when the AI proxy is connected to a DB instance - Use `ListStores` to get information about AI stores in a schema --- # FILE: client-libraries/python/request-ai/info-server.md --- title: Info Server --- # Info Server How to request server information from the Ahnlich AI Service using the Python client. AI Requests are the fundamental way to interact with the Ahnlich AI backend. They provide low-level functionality such as checking availability, inspecting runtime configuration, and requesting embeddings or search results. In the Ahnlich Python client programming model, AI requests are made through stubs generated from the gRPC definitions. Each request must be wrapped in an `async` function, as communication is asynchronous. The following example demonstrates how to make an Info Server request.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query async def info_server(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.info_server(ai_query.InfoServer()) print(response) #InfoServer(info=ServerInfo(address='Ok(0.0.0.0:1370)', version='0.1.0', limit=10073741824, remaining=10067931251)) if __name__ == "__main__": asyncio.run(info_server()) ```
## Define Request Parameters The `InfoServer` request does not require any parameters. It is used primarily for **diagnostics and service discovery**, returning metadata such as: * Server version * Supported models * Available features * Runtime configuration details ## Define Response Handling The response from `info_server()` is serializable and can be logged or stored. It should be treated as **read-only diagnostic data** and is most commonly used when: * Debugging compatibility issues * Verifying that the AI service is initialized * Inspecting model availability before embedding or search requests --- # FILE: client-libraries/python/request-ai/list-connected-clients.md --- title: List Connected Clients --- # List Connected Clients The ListClients request retrieves a list of all clients currently connected to the Ahnlich AI server. * **Input**: No arguments required. * **Behavior**: The server returns information about all active client connections. * **Response**: A list of connected client information including client addresses.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query async def list_connected_clients(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.list_clients(ai_query.ListClients()) # response.clients contains information about connected clients for client_info in response.clients: print(f"Connected client: {client_info.address}") if __name__ == "__main__": asyncio.run(list_connected_clients()) ```
--- # FILE: client-libraries/python/request-ai/list-stores.md --- title: List Stores --- # List Stores ## Schema `ListStores` accepts an optional `schema` field. When it is omitted, the server lists stores in `public` only; it does not list stores across every schema. Set `schema` to list stores in another schema. How to request a **list of available vector stores** from the Ahnlich AI Service using the Python client. In Ahnlich, vector stores are the fundamental units that organize data for semantic search, embeddings, and AI-driven retrieval. The **List Stores** request allows developers to discover which stores are currently registered and available to query. ## Source Code Example In the context of the rest of the application code:
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query async def list_stores(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.list_stores(ai_query.ListStores(schema="analytics")) print(response) #StoreList(stores=[AiStoreInfo(name='test store', embedding_size=384)]) if __name__ == "__main__": asyncio.run(list_stores()) ```
## Define Request Parameters The `ListStores` request does not take any required parameters. It queries the **AI service registry** and returns metadata about all accessible stores. ## Define Response Handling The response provides a structured list of stores, where each entry typically contains: * **Store name** (unique identifier) * **Configuration details** (embedding dimensions, indexing strategy, etc.) * **Associated algorithms** (if applicable) This allows developers to dynamically discover stores at runtime without hardcoding store names. ## Customize Usage `ListStores` is useful for: * **Dynamic discovery**: Applications can adapt to whatever stores exist at runtime. * **Debugging**: Confirming that a store was successfully created and registered. * **Observability**: Displaying available stores in admin dashboards. The **List Stores** request is often used as a precursor to **querying embeddings** or similarity search, since it ensures the target store exists before making downstream calls. --- # FILE: client-libraries/python/request-ai/ping.md --- title: Ping --- # Ping The Ping request verifies connectivity with the AI Service. It works just like the DB Ping, but instead communicates with the AI server (running on port 1370 by default). * **Input**: * No additional fields are required. * **Behavior**: Sends a lightweight request to check if the AI Service is responsive. * **Response**: * Returns a `Pong` message if the AI service is alive.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query async def ping(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.ping(ai_query.Ping()) print(response) #Pong() if __name__ == "__main__": asyncio.run(ping()) ```
--- # FILE: client-libraries/python/request-ai/purge-stores.md --- title: Purge Stores --- # Purge Stores Deletes **all vector stores** managed by the AI server, including all embeddings and associated metadata. This is a destructive operation that resets the AI service state, typically used during testing, cleanup, or when starting fresh with new datasets. * **Input**: No arguments required. * **Behavior**: Removes all stores and their contents from the AI server. * **Response**: Confirmation of deletion with count of deleted stores.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query async def purge_stores(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.purge_stores(ai_query.PurgeStores()) print(f"Purged stores. Deleted count: {response.deleted_count}") if __name__ == "__main__": asyncio.run(purge_stores()) ```
:::warning This operation is **irreversible**. All stores and their data will be permanently deleted. Use with caution in production environments. ::: --- # FILE: client-libraries/python/request-ai/request-ai.md --- title: Request AI sidebar_posiiton: 3 --- # Request AI The **Ahnlich AI Client** provides intelligent services that extend the capabilities of the DB client. While the DB client is optimized for storing vectors, managing stores, and retrieving them efficiently, the AI client is responsible for **embedding generation**, **preprocessing**, and **querying using raw inputs**. ## Key Features * **Raw input support**: Accepts text or images as input. * **Embedding generation**: Automatically transforms raw input into embeddings using AI models. * **AI proxy**: Communicates with Ahnlich-DB to persist embeddings and metadata. * **Query with raw input**: No need to manually generate vectors just pass text or images. * **Flexible models**: Choose different models for indexing vs querying. * **Metadata integration**: Store structured information alongside embeddings. * **Bulk requests**: Chain multiple operations (`ping`, `list_stores`, etc.) for efficiency. ## Supported Raw Inputs * **Text** - sentences, titles, product descriptions, etc. * **Images** - passed as binary (`u8` list). * **Mixed stores** depending on the selected AI model. ## Models * **Index Model** - Used when adding new items (generates embeddings). * **Query Model** - Used when searching (generates query embeddings). * You may configure both models separately, but they should be compatible. ## Create a Store ```py create_store( store="my_store", index_model="all-minilm-l6-v2", query_model="all-minilm-l6-v2", ) ``` ## Insert Raw Input
Expand code ```py response = await client.set( ai_query.Set( store="my_store", inputs=[ keyval.AiStoreEntry( key=keyval.StoreInput(raw_string="Jordan One"), value=keyval.StoreValue( value={"brand": metadata.MetadataValue(raw_string="Nike")} ), ), keyval.AiStoreEntry( key=keyval.StoreInput(raw_string="Yeezey"), value=keyval.StoreValue( value={"brand": metadata.MetadataValue(raw_string="Adidas")} ), ) ], preprocess_action=preprocess.PreprocessAction.NoPreprocessing ) ) ```
## Query with Raw Input
Expand code ```py response = await client.get_sim_n( ai_query.GetSimN( store="my_store", search_input=keyval.StoreInput(raw_string="Jordan"), closest_n=3, algorithm=algorithms.Algorithm.COSINE_SIMILARITY, ) ) for entry in response.entries: print(f"Key: {entry.key.raw_string}, Score: {entry.score}") ```
Below is a breakdown of common AI request examples: * [Ping](/docs/client-libraries/python/request-ai/ping) * [Info Server](/docs/client-libraries/python/request-ai/info-server) * [List Stores](/docs/client-libraries/python/request-ai/list-stores) * [Create Store](/docs/client-libraries/python/request-ai/create-store) * [Set](/docs/client-libraries/python/request-ai/set) * [Upsert](/docs/client-libraries/python/request-ai/upsert) * [GetSimN](/docs/client-libraries/python/request-ai/get-simn) * [Get By Predicate](/docs/client-libraries/python/request-ai/get-by-predicate) * [Create Predicate Index](/docs/client-libraries/python/request-ai/create-predicate-index) * [Drop Predicate Index](/docs/client-libraries/python/request-ai/drop-predicate-index) * [Delete Key](/docs/client-libraries/python/request-ai/delete-key) * [Drop Store](/docs/client-libraries/python/request-ai/drop-store) * [Create Non Linear Algorithm Index](/docs/client-libraries/python/request-ai/create-non-linear-algx) * [Drop Non Linear Algorithm Index](/docs/client-libraries/python/request-ai/drop-non-linear-algx) --- # FILE: client-libraries/python/request-ai/set.md --- title: Set --- # Set ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The `Set` request is used to **insert or update entries** inside an AI-powered store. Unlike the DB client (which expects raw vectors), the AI client allows you to store **raw strings or other inputs** directly. The AI service will automatically **embed these inputs** using the store’s configured models.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc import keyval, metadata from ahnlich_client_py.grpc.ai import preprocess async def sets(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.set( ai_query.Set( store="test store", schema="analytics", inputs=[ keyval.AiStoreEntry( key=keyval.StoreInput(raw_string="Jordan One"), value=keyval.StoreValue( value={"brand": metadata.MetadataValue(raw_string="Nike")} ), ), keyval.AiStoreEntry( key=keyval.StoreInput(raw_string="Yeezey"), value=keyval.StoreValue( value={"brand": metadata.MetadataValue(raw_string="Adidas")} ), ) ], preprocess_action=preprocess.PreprocessAction.NoPreprocessing, execution_provider=None, # Optional: e.g., ExecutionProvider.CUDA for GPU acceleration model_params={} # Optional: runtime model parameters (e.g., {"confidence_threshold": "0.9"} for face detection) ) ) print(response) #Set(upsert=StoreUpsert(inserted=2)) if __name__ == "__main__": asyncio.run(sets()) ```
## Key Notes * **`inputs`** - list of entries to be stored. * Each entry has: * **`key`** - the raw input (e.g., "Jordan One") that gets embedded by the AI model. * **`value`** - metadata associated with the key (e.g., `"brand": Nike`). * **`preprocess_action`** - defines how inputs are preprocessed before embedding. * `NoPreprocessing` - raw text is passed as-is to the embedding model. * Other preprocessing options (like normalization or tokenization) can be applied depending on the use case. * **`execution_provider`** - Optional hardware acceleration for model inference (e.g., `CUDA`, `TensorRT`, `CoreML`). Set to `None` for default CPU execution. * **`model_params`** - Optional dictionary of runtime parameters for the AI model (`Dict[str, str]`). Used by face detection models (Buffalo\_L, SFace+YuNet) to control behavior like `confidence_threshold`. Pass an empty dict `{}` to use model defaults. See [Model Parameters](/docs/components/ahnlich-ai/advanced#model-parameters-model_params) for details. * **Response** → returns counts of inserted vs. updated items (`upsert counts`). --- # FILE: client-libraries/python/request-ai/upsert.md --- title: Upsert --- # Upsert ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The `Upsert` request updates a single entry matching a predicate condition in an AI store. The AI service automatically merges metadata, preserving AI-generated fields.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc import keyval, metadata, predicates from ahnlich_client_py.grpc.ai import preprocess async def upsert(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) condition = predicates.PredicateCondition( value=predicates.Predicate( equals=predicates.Equals( key="filename", value=metadata.MetadataValue(raw_string="photo.jpg") ) ) ) new_value = keyval.StoreValue( value={"tags": metadata.MetadataValue(raw_string="cat,outdoors")} ) response = await client.upsert( ai_query.Upsert( store="images", schema="media", condition=condition, new_input=None, # Optional: new image/text to re-embed new_value=new_value, preprocess_action=preprocess.PreprocessAction.NoPreprocessing, execution_provider=None, model_params={} ) ) print(response) #Set(upsert=StoreUpsert(updated=1, inserted=0)) if __name__ == "__main__": asyncio.run(upsert()) ```
## Key Notes * **`condition`** - predicate that must match exactly one entry. * **`new_input`** (optional) - new raw input to re-embed (e.g., updated text or image). * **`new_value`** (optional) - metadata to update. Always merged with existing metadata. * **`preprocess_action`** - how inputs are preprocessed before embedding. * **`execution_provider`** - Optional hardware acceleration (e.g., `CUDA`). * **`model_params`** - Optional runtime parameters for the AI model. * **Behavior** - AI proxy always merges metadata, preserving AI-generated fields. Errors if 0 or multiple entries match. * **Response** → returns upsert counts (inserted: 0, updated: 1). --- # FILE: client-libraries/python/request-db/create-non-linear-algx.md --- title: Create Non-Linear Algorithm Index --- # Create Non-Linear Algorithm Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Creating non-linear algorithm indexes allows you to optimize query execution based on spatial or high-dimensional data structures. Ahnlich supports the following non-linear algorithm indexes: - **HNSW** (Hierarchical Navigable Small World) — approximate nearest-neighbor search with configurable accuracy/speed tradeoff. Non-linear algorithm indexes improve query performance by pre-structuring the data, but depending on the algorithm, there may be tradeoffs between query time and memory consumption. In the Ahnlich client, you can create a non-linear algorithm index by calling the `create_non_linear_algorithm_index` RPC via the `DbServiceStub`. Each index type is specified using a `NonLinearIndex` message with a `HnswConfig`. ## Define a Client and Call the API The following example shows how to initialize a client, request index creation, and inspect the server’s response.
Click to expand code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc.algorithm.nonlinear import NonLinearIndex, HnswConfig async def create_non_linear_algo_index(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) # Create an HNSW index (with optional config) response = await client.create_non_linear_algorithm_index( db_query.CreateNonLinearAlgorithmIndex( store="test store 003", schema="analytics", non_linear_indices=[NonLinearIndex(hnsw=HnswConfig())] ) ) # response.created_indexes shows how many indexes were created print(response) if __name__ == "__main__": asyncio.run(create_non_linear_algo_index()) ```
--- # FILE: client-libraries/python/request-db/create-predicate-index.md --- title: Create Predicate Index --- # Create Predicate Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The CreatePredIndex request creates an index on one or more metadata fields. Indexes make predicate queries (e.g. GetPred) faster and more efficient. * **Input**: * `store`: store name. * `predicates`: list of metadata fields to index. * **Behavior**: Adds a new index on the specified fields. * **Response**: Returns how many indexes were successfully created.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query async def create_predicate_index(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) response = await client.create_pred_index( db_query.CreatePredIndex( store="test store", schema="analytics", predicates=["job", "rank"] ) ) # response.created_indexes shows how many indexes were created print(response) if __name__ =="__main__": asyncio.run(create_predicate_index()) ```
--- # FILE: client-libraries/python/request-db/create-store.md --- title: Create Store --- # Create Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. A Store in Ahnlich is like a logical dataset or collection. Each store holds vectors and their associated metadata, allowing you to organize data by application, environment, or project. * **Behavior**: Creates a new isolated vector store. Multiple stores can coexist, enabling different workloads. * **Parameters**: - `store`: Unique name for the store - `dimension`: Vector dimensionality (all vectors must match this) - `create_predicates`: List of metadata field names to enable filtering (can be empty) - `non_linear_indices`: List of non-linear algorithms for approximate search (can be empty) - `error_if_exists`: If True, returns error when store already exists * **Response**: A confirmation message (Unit).
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc.db.server import Unit async def create_store(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) response = await client.create_store( db_query.CreateStore( store="test store 006", schema="analytics", dimension=5, # Fixed vector dimension create_predicates=["job"], # Index these metadata fields non_linear_indices=[], # Optional: non-linear algorithms for faster search error_if_exists=True ) ) # response is Unit() on success # All store_keys must match this dimension # Example valid key: valid_key = [1.0, 2.0, 3.0, 4.0, 5.0] # length = 5 assert isinstance(response, Unit) if __name__ == "__main__": asyncio.run(create_store()) ```
--- # FILE: client-libraries/python/request-db/delete-key.md --- title: Delete Key --- # Delete Key ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. This request removes one or more **keys** (and their associated values) from a store. * **Input:** * `store`: the name of the store. * `keys`: a list of vectors (wrapped in StoreKey) that identify the entries to be deleted. * **Behavior:** The server looks up the given keys in the store. If found, the entries are removed. If a key does not exist, it is ignored. * **Response:** * `deleted_count` → the number of keys successfully deleted.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc import keyval async def delete_key(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) store_key = keyval.StoreKey(key=[5.0, 3.0, 4.0, 3.9, 4.9]) response = await client.del_key( db_query.DelKey( store="test store 002", schema="analytics", keys=[store_key] ) ) # response.deleted_count shows how many items were deleted print(response) if __name__ == "__main__": asyncio.run(delete_key()) ```
--- # FILE: client-libraries/python/request-db/delete-predicate.md --- title: Delete Predicate --- # Delete Predicate ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. This request removes all entries in a store that match a specified predicate condition. * **Input:** * `store`: the name of the store. * `condition`: a logical predicate that filters which entries should be deleted. * **Behavior:** Instead of deleting by a specific key, the server scans the store and deletes all entries that satisfy the predicate condition. In this example, it deletes all entries where the metadata field "`job`" equals "`sorcerer`". * **Response:** * `deleted_count` → the number of items successfully deleted.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc import predicates, metadata from ahnlich_client_py.grpc.db.server import Del async def delete_predicate(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) condition = predicates.PredicateCondition( value=predicates.Predicate( equals=predicates.Equals( key="job", value=metadata.MetadataValue(raw_string="sorcerer") ) ) ) response = await client.del_pred( db_query.DelPred( store="test store 003", schema="analytics", condition=condition ) ) # response.deleted_count shows how many items were deleted if __name__ == "__main__": asyncio.run(delete_predicate()) ```
--- # FILE: client-libraries/python/request-db/drop-non-linear-algx.md --- title: Drop Non-Linear Algorithm Index --- # Drop Non-Linear Algorithm Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. This request removes one or more **non-linear algorithm indexes** from a store. Non-linear indexes (like **HNSW**) are used to accelerate similarity searches. **Input:** * `store`: the name of the store. * `non_linear_indices`: list of algorithms to drop (e.g., `HNSW`). * `error_if_not_exists`: * `True`: raises an error if the index does not exist. * `False`: silently ignores missing indexes. * **Behavior:** The server attempts to remove the specified algorithm indexes from the store. * **Response:** * `deleted_count` - the number of indexes successfully removed.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc.algorithm.nonlinear import NonLinearAlgorithm async def drop_non_linear_algo(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) response = await client.drop_non_linear_algorithm_index( db_query.DropNonLinearAlgorithmIndex( store="test store 003", schema="analytics", non_linear_indices=[NonLinearAlgorithm.HNSW], error_if_not_exists=True ) ) # response.deleted_count shows how many indexes were removed print(response) if __name__ == "__main__": asyncio.run(drop_non_linear_algo()) ```
**When to use**: * If you want to **rebuild indexes** with a different algorithm. * If an index is no longer needed and you want to **free resources**. --- # FILE: client-libraries/python/request-db/drop-predicate-index.md --- title: Drop Predicate Index --- # Drop Predicate Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The DropPredIndex request removes an index from one or more metadata fields. This should be used when an index is no longer needed or to reduce storage overhead. * **Input**: * `store`: store name. * `predicates`: list of metadata fields whose indexes should be dropped. * `error_if_not_exists`: if `True`, raises error if the index does not exist. * **Behavior**: Deletes the index, but the underlying data remains. * **Response**: Returns how many indexes were deleted.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc.db.server import Del async def drop_predicate_index(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) response = await client.drop_pred_index( db_query.DropPredIndex( store="test store", schema="analytics", predicates=["job"], error_if_not_exists=True ) ) # response.deleted_count shows how many indexes were removed print(response) if __name__ == "__main__": asyncio.run(drop_predicate_index()) ```
--- # FILE: client-libraries/python/request-db/drop-store.md --- title: Drop Store --- # Drop Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The Drop Store request permanently deletes a store and all its contents. Use this carefully, as the operation is destructive and cannot be undone. * **Behavior:** Removes the store and its data from the DB engine. * **Response:** Confirmation response indicating deletion.
Click to Expand Source Code ```py import asyncio from grpclib.client import Channel from grpclib.exceptions import GRPCError from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc.db.server import Del async def drop_store(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) response = await client.drop_store( db_query.DropStore( store="test store", schema="analytics", error_if_not_exists=True ) ) # response contains deleted_count if __name__ == "__main__": asyncio.run(drop_store()) ```
--- # FILE: client-libraries/python/request-db/get-by-predicate.md --- title: Get by Predicate --- # Get by Predicate ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The **GetPred** request retrieves entries based on **metadata conditions (predicates)**. Instead of matching vectors, it filters results using key/value metadata. * **Input**: * `store`: store name. * `condition`: a predicate condition, e.g. `equals`, `greater_than`, etc. * **Behavior**: Evaluates the predicate against metadata fields and returns all entries that satisfy the condition. * **Response**: List of matching entries.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc import predicates, metadata async def get_predicate(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) condition = predicates.PredicateCondition( value=predicates.Predicate( equals=predicates.Equals( key="job", value=metadata.MetadataValue(raw_string="sorcerer") ) ) ) response = await client.get_pred( db_query.GetPred( store="test store", schema="analytics", condition=condition ) ) # response.entries contains matching items print(response) if __name__ == "__main__": asyncio.run(get_predicate()) ```
--- # FILE: client-libraries/python/request-db/get-key.md --- title: GetKey --- # GetKey ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The GetKey request retrieves entries from a store based on an exact vector key match. * **Input**: * `store`: the store name. * `key`: the exact vector you want to retrieve. * **Behavior**: Finds the stored entry that matches the vector key exactly. * **Response**: Returns the entry (vector + metadata) if found.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc import keyval from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc.db.server import Get async def get_key(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) lookup_key = keyval.StoreKey(key=[5.0, 3.0, 4.0, 3.9, 4.9]) # Your lookup vector response = await client.get_key( db_query.GetKey( store="customer_profiles", schema="analytics", keys=[lookup_key] ) ) # response.entries contains matching (key, value) pairs if __name__ == "__main__": asyncio.run(get_key()) ```
--- # FILE: client-libraries/python/request-db/get-simn.md --- title: GetSimN --- # GetSimN ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The GetSimN request performs a similarity search. It retrieves the N closest vectors to a given query vector. * **Input**: * `store`: store name. * `search_input`: the query vector (`StoreKey`). * `closest_n`: number of results to return (> 0). * `algorithm`: similarity metric (e.g. CosineSimilarity, EuclideanDistance). * `condition`: optional predicate filter to restrict which vectors are considered. Set to `None` to search all vectors. See [Predicates documentation](/docs/components/predicates) for filtering examples. * **Behavior**: The server compares the query vector with stored vectors using the chosen similarity metric. * **Response**: A list of entries with: * `key` (vector), * `value` (metadata), * `score` (similarity measure).
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc import keyval, predicates from ahnlich_client_py.grpc.algorithm.algorithms import Algorithm async def get_simn(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) search_key = keyval.StoreKey(key=[5.0, 5.1, 3.4, 5.1, 4.9]) response = await client.get_sim_n( db_query.GetSimN( store="test store", schema="analytics", search_input=search_key, closest_n=3, algorithm=Algorithm.CosineSimilarity, condition=None # Optional: filter results using predicates ) ) print(response.entries) # [(key, value, score), ...] if __name__ == "__main__": asyncio.run(get_simn()) ```
--- # FILE: client-libraries/python/request-db/get-store.md --- title: Get Store --- # Get Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Returns detailed information about a specific store by name. * **Input**: Store name. * **Behavior**: Retrieves metadata and configuration for the specified store. * **Response**: Store information including name, size, dimension, and indices.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query async def get_store_info(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) response = await client.get_store( db_query.GetStore(store="my_store", schema="analytics") ) print(f"Store name: {response.name}") print(f"Number of entries: {response.len}") print(f"Size in bytes: {response.size_in_bytes}") print(f"Dimension: {response.dimension}") print(f"Predicate indices: {response.predicate_indices}") print(f"Non-linear indices: {response.non_linear_indices}") if __name__ == "__main__": asyncio.run(get_store_info()) ```
## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `store` | `str` | Yes | The name of the store to retrieve | | `schema` | `str` | No | Schema containing the store. Defaults to `public` when omitted | ## Response: StoreInfo | Field | Type | Description | |-------|------|-------------| | `name` | `str` | Store name | | `len` | `int` | Number of entries in the store | | `size_in_bytes` | `int` | Total size of the store in bytes | | `dimension` | `int` | Vector dimension | | `predicate_indices` | `List[str]` | List of indexed predicate keys | | `non_linear_indices` | `List[NonLinearIndex]` | List of non-linear algorithm indices (HNSW) | ## Notes - Returns an error if the store does not exist - Use `ListStores` to get information about stores in a schema - The `size_in_bytes` field is useful for monitoring memory usage --- # FILE: client-libraries/python/request-db/info-server.md --- title: Info Server sidebar_posiiton: 2 --- # Info Server The Info Server request retrieves metadata about the running DB server, including the binary version and server type (DB, AI, or Hybrid). This is useful for environment validation, feature gating, and diagnostics. ## Behavior * The client sends an InfoServer request. * The server responds with version and type metadata. * Clients can use this to validate they are connected to the correct server role.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc.server_types import ServerType async def info_server(): """Test server version""" async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) response = await client.info_server(db_query.InfoServer()) # response contains server version and type print(f"Server version: {response.info.version}") if __name__ == "__main__": asyncio.run(info_server()) ```
--- # FILE: client-libraries/python/request-db/list-connected-clients.md --- title: List Connected Clients --- # List Connected Clients The ListClients request retrieves a list of all clients currently connected to the Ahnlich DB server. * **Input**: No arguments required. * **Behavior**: The server returns information about all active client connections. * **Response**: A list of connected client information including client addresses.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query async def list_connected_clients(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) response = await client.list_clients(db_query.ListClients()) # response.clients contains information about connected clients for client_info in response.clients: print(f"Connected client: {client_info.address}") if __name__ == "__main__": asyncio.run(list_connected_clients()) ```
--- # FILE: client-libraries/python/request-db/list-stores.md --- title: List Stores --- # List Stores ## Schema `ListStores` accepts an optional `schema` field. When it is omitted, the server lists stores in `public` only; it does not list stores across every schema. Set `schema` to list stores in another schema. The **List Stores** request retrieves the set of Stores currently registered on the DB server. Each Store corresponds to a logical container of vectors. This operation is commonly used for **introspection**, **administrative tooling**, and **debugging**. ## Behavior * The client sends a ListStores request. * The server responds with a collection of registered Stores. Each store entry includes: name, entry count, size in bytes, and non-linear index configurations (HNSW parameters) if any are active. * An empty list means no Stores have been created yet.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query async def list_stores(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) tracing_id = "00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01" response = await client.list_stores( db_query.ListStores(schema="analytics"), metadata={"ahnlich-trace-id": tracing_id} ) print(f"Stores: {[store.name for store in response.stores]}") if __name__ == "__main__": asyncio.run(list_stores()) ```
--- # FILE: client-libraries/python/request-db/ping.md --- title: Ping sidebar_posiiton: 1 --- # Ping The Ping request is used to test the connectivity between the Python client and the Ahnlich DB server. It acts as a health check to confirm that the DB service is up and running, and it is also useful for debugging or monitoring setups. * **Input**: No arguments are required. You may pass optional metadata, such as tracing IDs, for observability and distributed tracing. * **Behavior**: The client sends a simple ping message to the DB server, and the server responds with a Pong. * **Response**: A Pong message confirming connectivity.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc.db.server import Pong async def Ping(): """ Test ping """ # Initialize client async with Channel(host="127.0.0.1", port=1369) as channel: db_client = DbServiceStub(channel) # Prepare tracing metadata tracing_id = "00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01" metadata = {"ahnlich-trace-id": tracing_id} # Make request with metadata response = await db_client.ping( db_query.Ping(), metadata=metadata ) if __name__ == "__main__": asyncio.run(Ping()) ```
--- # FILE: client-libraries/python/request-db/request-db.md --- title: Request DB sidebar_posiiton: 2 --- The Ahnlich DB Client is the foundation of the system, designed to efficiently manage, query, and retrieve vector embeddings. It provides all the tools needed to build applications that rely on vector similarity, metadata filtering, and structured querying. In simple terms: * **DB Service**: Where your vectors are stored, indexed, and retrieved. * It supports CRUD operations, predicate-based queries, and advanced indexing strategies for optimal performance. ## Core DB Features ### Stores – Logical Containers for Vectors A store is the primary container for vectors. Each store groups together embeddings and their metadata. You can think of it as a dataset or collection, tailored for a specific use case (e.g., product catalog, documents, images). ### Vector CRUD Operations * **Create / Insert** vectors into a store. * **Get** vectors by key or predicate conditions. * **Delete** vectors by key or predicate. This gives you full lifecycle control over your vector data. ### Predicate Filtering The DB supports predicate-based filtering. This allows you to retrieve or delete entries that match certain metadata conditions (e.g., job = "sorcerer"). ### Indexing For efficiency, the DB provides indexing strategies: * **Predicate Indexes** → Speed up queries on metadata conditions. * **Non-Linear Algorithm Indexes** → Enable optimized similarity searches (e.g., HNSW). Indexes ensure that even at scale, searches and lookups remain fast. ### Tracing and Observability All DB requests support metadata injection such as trace IDs. This makes it easy to track and monitor requests in distributed systems. ## Example DB Requests The DB client exposes a variety of request methods. Some of the most common include: * **Ping** - Check service availability. * **Get by Key** - Retrieve specific embeddings. * **Get by Predicate** - Query vectors based on metadata conditions. * **Create / Drop Indexes** - Manage performance optimizations. * **Delete by Key or Predicate** - Remove entries from a store. Each operation is asynchronous, ensuring high throughput for large-scale applications. ## Summary The Ahnlich DB Client provides: * A structured way to store and organize embeddings into logical stores. * CRUD operations on vectors with rich filtering capabilities. * Predicate and algorithm indexing for performance at scale. * Built-in tracing support for observability. It serves as the data backbone of Ahnlich, working hand-in-hand with the AI client to deliver semantic search and retrieval. Below is a break down common DB request examples: * [Ping](/docs/client-libraries/python/request-db/ping) * [Info Server](/docs/client-libraries/python/request-db/info-server) * [List Stores](/docs/client-libraries/python/request-db/list-stores) * [Create Store](/docs/client-libraries/python/request-db/create-store) * [Set](/docs/client-libraries/python/request-db/set) * [Upsert](/docs/client-libraries/python/request-db/upsert) * [GetSimN](/docs/client-libraries/python/request-db/get-simn) * [Get Key](/docs/client-libraries/python/request-db/get-key) * [Get By Predicate](/docs/client-libraries/python/request-db/get-by-predicate) * [Create Predicate Index](/docs/client-libraries/python/request-db/create-predicate-index) * [Drop Predicate Index](/docs/client-libraries/python/request-db/drop-predicate-index) * [Delete Key](/docs/client-libraries/python/request-db/delete-key) * [Drop Store](/docs/client-libraries/python/request-db/drop-store) * [Create Non Linear Algorithm Index](/docs/client-libraries/python/request-db/create-non-linear-algx) * [Drop Non Linear Algorithm Index](/docs/client-libraries/python/request-db/drop-non-linear-algx) * [Delete Predicate](/docs/client-libraries/python/request-db/delete-predicate) --- # FILE: client-libraries/python/request-db/set.md --- title: Set --- # Set ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The Set request inserts or updates vector entries inside a store. Each entry is defined by: * **StoreKey**: the vector itself (list of floats). * **StoreValue**: metadata (key-value pairs) describing the vector. * **Input**: * `store`: the store name. * `inputs`: list of entries (StoreKey, StoreValue). * **Behavior**: If the vector already exists, it updates the metadata. Otherwise, it inserts a new entry. * **Response**: A confirmation response indicating success.
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc import keyval, metadata from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query async def set(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) store_key = keyval.StoreKey(key=[5.0, 3.0, 4.0, 3.9, 4.9]) store_value = keyval.StoreValue( value={"rank": metadata.MetadataValue(raw_string="chunin")} ) response = await client.set( db_query.Set( store="test store", schema="analytics", inputs=[keyval.DbStoreEntry(key=store_key, value=store_value)] ) ) if __name__ == "__main__": asyncio.run(set()) ```
--- # FILE: client-libraries/python/request-db/upsert.md --- title: Upsert --- # Upsert ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The Upsert request updates a single entry matching a predicate condition: * **Input**: * `store`: the store name. * `condition`: predicate that must match exactly one entry. * `new_key` (optional): new vector to replace the existing key. * `new_value` (optional): metadata to update. * `merge_metadata` (optional): if True, merges new metadata into existing (default: False replaces entirely). * **Behavior**: Updates the matched entry. Errors if 0 or multiple entries match the predicate. * **Response**: A Set response with upsert counts (inserted: 0, updated: 1).
Click to expand source code ```py import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc import keyval, metadata, predicates from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query async def upsert(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) condition = predicates.PredicateCondition( value=predicates.Predicate( equals=predicates.Equals( key="id", value=metadata.MetadataValue(raw_string="123") ) ) ) new_value = keyval.StoreValue( value={"status": metadata.MetadataValue(raw_string="published")} ) response = await client.upsert( db_query.Upsert( store="test store", schema="analytics", condition=condition, new_value=new_value, merge_metadata=True ) ) if __name__ == "__main__": asyncio.run(upsert()) ```
--- # FILE: client-libraries/python/type-meanings.md --- title: Type Meanings sidebar_posiiton: 5 --- # Type Meanings The following terms are fundamental to how **Ahnlich AI requests** are structured and processed. ## Store Key A one-dimensional vector that uniquely identifies an item in the store. * Functions like a **primary key** in a database. * Ensures that every stored entry has a distinct handle for retrieval and indexing. * Example: a numerical vector representing an embedding for a product image. ## Store Value A dictionary containing texts or binary data associated with a `StoreKey`. * Stores the **payload** of information that can be retrieved, searched, or filtered. * May include metadata such as titles, descriptions, or binary content (like embeddings, files, or serialized objects). * Think of it as the "body" of the data linked to the store key. ## Store Predicates (Predicate Indices) Special indices built on top of `StoreValue` fields to make filtering more efficient. * They **optimize lookups** by pre-indexing specific fields. * Useful when you need fast filtering by metadata like "`job`" or "`rank`". * Without them, searches would be slower since the system would need to scan every entry. ## Predicates Operations that define how filtering is performed on data. * Examples include: * `Equals` → match exact values. * `NotEquals` → exclude values. * `In` → match if value is in a given set. * `NotIn` → match if value is not in a given set. * They are always tied to a **key** in a `StoreValue` and evaluated against a **metadata value**. * Provide the basic building blocks for query logic. ## PredicateConditions Conditions that **wrap predicates** and allow combining them logically. * A `PredicateCondition` can represent: * A **single predicate** (just one filter condition). * A **compound condition** using `AND` or `OR`. * This makes it possible to construct **complex filters**, e.g., “all sorcerers who are chunin rank.” ### Example – single predicate condition: ```py condition = predicates.PredicateCondition( value=predicates.Predicate( equals=predicates.Equals( key="job", value=metadata.MetadataValue(raw_string="sorcerer") ) ) ) ``` ### Example – binary metadata value: ```py condition = predicates.PredicateCondition( value=predicates.Predicate( equals=predicates.Equals( key="rank", value=metadata.MetadataValue(image=b'\x02\x02\x03\x04\x05\x06\x07') ) ) ) ``` ### Example – compound condition with `AND`: ```py condition = predicates.PredicateCondition( and_=predicates.AndCondition( left=predicates.PredicateCondition( value=predicates.Predicate( equals=predicates.Equals( key="job", value=metadata.MetadataValue(raw_string="sorcerer") ) ) ), right=predicates.PredicateCondition( value=predicates.Predicate( equals=predicates.Equals( key="rank", value=metadata.MetadataValue(raw_string="chunin") ) ) ) ) ) ``` ## MetadataValue The container used inside predicates to hold values. * Supports both **raw strings** (like "`sorcerer`") and **binary vectors** (lists of bytes/integers). * This makes it versatile enough to handle both **structured text metadata** and **embeddings or binary payloads**. ## Search Input The query input sent to Ahnlich AI for processing. * Can be a **string** (text input, e.g., "`What is AI?`") or a **binary file** (like an image or audio file). * The type of input depends on the **AI model** and the **store configuration** (string vs. binary store). ## AIModels The set of supported AI models within Ahnlich AI. * Each model determines the **type of input** it can process (e.g., text-only, image, multimodal). * Choosing the right model ensures that the search input is properly understood and processed. ## Model Parameters (`model_params`) A dictionary (`Dict[str, str]`) of optional runtime parameters passed to an AI model during inference. Available on `Set`, `GetSimN`, and `ConvertStoreInputToEmbeddings` requests. * Allows **tuning model behavior at query time** without changing the store configuration. * Models that don't support any parameters simply **ignore** this field. * Currently supported by **face detection models** only: * **Buffalo\_L** — accepts `confidence_threshold` (default: `0.5`) * **SFace+YuNet** — accepts `confidence_threshold` (default: `0.6`) * Text, image, and audio embedding models (MiniLM, BGE, ResNet, CLIP, CLAP) do not use `model_params`. ### Example — default parameters: ```py model_params = {} # uses model defaults ``` ### Example — custom confidence threshold for face detection: ```py model_params = {"confidence_threshold": "0.9"} # stricter face detection ``` See [Model Parameters](/docs/components/ahnlich-ai/advanced#model-parameters-model_params) for the full reference. ## AIStoreType Defines the type of store being created. * **String Store** - optimized for textual inputs and queries. * **Binary Store** - optimized for binary data like embeddings, images, or raw vectors. * Must be chosen carefully depending on whether you are working with **text-based AI models** or **binary models**. --- # FILE: client-libraries/rust/distributed-tracing.md --- title: Distributed Tracing --- # Distributed Tracing The clients support **W3C Trace Context** via optional `traceparent` headers, enabling distributed tracing across microservices. ## Usage * Many client methods accept an `Option` parameter named `tracing_id`. * Passing a `Some(trace_id)` propagates context to downstream services for observability. * Leaving it as `None` will execute the request without attaching tracing metadata. ## Benefits * Provides full request visibility across DB and AI pipelines. * Facilitates debugging and performance monitoring in distributed systems. * Supports correlation of requests across multiple services and pipelines. --- # FILE: client-libraries/rust/pipeline.md --- title: Pipeline --- # Pipeline Creates a new **AI pipeline** for batching multiple operations in the **AI client**. Pipelines allow you to queue multiple requests (e.g., `set`, `get_sim_n`, `get_key`) and execute them in sequence, ensuring consistent ordering and efficient handling of AI service calls. This is particularly useful for bulk processing or workflows that require multiple embeddings or queries to be executed together. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::ai::{AiClient, AiPipeline}; use ahnlich_client_rs::error::AhnlichError; use tokio; #[tokio::main] async fn main() -> Result<(), AhnlichError> { // Replace with your AI server address let addr = "127.0.0.1:1370".to_string(); // Initialize the client let client = AiClient::new(addr).await?; // Create a pipeline let mut pipeline = client.pipeline(None); // Add some example queries pipeline.ping(); // Ping the server pipeline.list_stores(); // List existing stores // Execute the pipeline let response = pipeline.exec().await?; println!("Pipeline response: {:#?}", response); Ok(()) } ```
## Parameters * `tracing_id: Option` — Optional trace parent ID for observability and distributed tracing. ## Returns * `AiPipeline` — A new pipeline instance that can queue AI client operations and execute them sequentially. ## Behavior (explains the code, brief) * Initializes an empty `queries` vector to hold pending operations. * Clones the AI client for use within the pipeline. * Attaches optional tracing metadata for observability. * Returns a fully initialized `AiPipeline` object ready for queuing operations. --- # FILE: client-libraries/rust/request-ai/convert-to-embeddings.md --- title: Convert to Embeddings sidebar_position: 20 --- # Convert to Embeddings The `convert_to_embeddings` method converts raw inputs (text, images, audio) into embeddings using a specified AI model, without storing them in a database. This is useful for generating embeddings on-demand or testing model behavior. ## Method Signature ```rust pub async fn convert_to_embeddings( &mut self, store_name: String, inputs: Vec, preprocess_action: PreprocessAction, execution_provider: Option, model_params: HashMap, ) -> Result ``` ## Parameters - `store_name`: Name of the store (determines which model to use) - `inputs`: Vector of inputs to convert (text, images, or audio) - `preprocess_action`: How to preprocess inputs (`NoPreprocessing` or `ModelPreprocessing`) - `execution_provider`: Optional execution provider (CPU, CUDA, etc.) - `model_params`: Optional model-specific parameters (e.g., `confidence_threshold` for face detection) ## Basic Example ```rust use ahnlich_client_rs::prelude::*; use std::collections::HashMap; let inputs = vec![ StoreInput { value: Some(store_input::Value::RawString("Hello world".to_string())), }, ]; let response = client .convert_to_embeddings( "my_store".to_string(), inputs, PreprocessAction::NoPreprocessing, None, HashMap::new(), ) .await?; // Access embeddings for item in response.values { if let Some(Variant::Single(embedding_with_meta)) = item.variant { if let Some(embedding) = embedding_with_meta.embedding { println!("Embedding dimensions: {}", embedding.key.len()); } } } ``` ## Face Detection with Metadata (Buffalo-L / SFace) Starting from version 0.2.2, face detection models return **bounding box metadata** alongside embeddings: ```rust use ahnlich_client_rs::prelude::*; use std::collections::HashMap; // Load image bytes let image_bytes = std::fs::read("group_photo.jpg")?; let inputs = vec![StoreInput { value: Some(store_input::Value::Image(image_bytes)), }]; let response = client .convert_to_embeddings( "faces_store".to_string(), inputs, PreprocessAction::ModelPreprocessing, None, HashMap::new(), ) .await?; // Process each detected face for item in response.values { if let Some(Variant::Multiple(multi)) = item.variant { println!("Detected {} faces", multi.embeddings.len()); for face in multi.embeddings { // Access embedding if let Some(embedding) = &face.embedding { println!("Embedding size: {}", embedding.key.len()); } // Access bounding box metadata if let Some(metadata) = &face.metadata { if let Some(bbox_x1) = metadata.value.get("bbox_x1") { if let Some(metadata_value::Value::RawString(x1_str)) = &bbox_x1.value { let x1: f32 = x1_str.parse().unwrap(); println!("Face detected at x1: {}", x1); } } if let Some(confidence) = metadata.value.get("confidence") { if let Some(metadata_value::Value::RawString(conf_str)) = &confidence.value { let conf: f32 = conf_str.parse().unwrap(); println!("Detection confidence: {}", conf); } } } } } } ``` ## Metadata Fields (Face Detection Models) For Buffalo-L and SFace models, each detected face includes: | Field | Type | Range | Description | |-------|------|-------|-------------| | `bbox_x1` | f32 | 0.0-1.0 | Normalized x-coordinate of top-left corner | | `bbox_y1` | f32 | 0.0-1.0 | Normalized y-coordinate of top-left corner | | `bbox_x2` | f32 | 0.0-1.0 | Normalized x-coordinate of bottom-right corner | | `bbox_y2` | f32 | 0.0-1.0 | Normalized y-coordinate of bottom-right corner | | `confidence` | f32 | 0.0-1.0 | Detection confidence score | Coordinates are normalized to 0-1 range. To convert to pixel coordinates: ```rust let pixel_x1 = bbox_x1 * image_width as f32; let pixel_y1 = bbox_y1 * image_height as f32; ``` ## Using Model Parameters Face detection models support tuning via `model_params`: ```rust let mut model_params = HashMap::new(); model_params.insert("confidence_threshold".to_string(), "0.9".to_string()); let response = client .convert_to_embeddings( "faces_store".to_string(), inputs, PreprocessAction::ModelPreprocessing, None, model_params, // Higher threshold = fewer but more confident detections ) .await?; ``` ## Response Structure The response contains a `StoreInputToEmbeddingsList` with a vector of `SingleInputToEmbedding`: ```rust pub struct SingleInputToEmbedding { pub input: Option, // Original input pub variant: Option, // OneToOne or OneToMany } pub enum Variant { Single(EmbeddingWithMetadata), // For text/image models Multiple(MultipleEmbedding), // For face detection } pub struct EmbeddingWithMetadata { pub embedding: Option, // The embedding vector pub metadata: Option, // Optional metadata } ``` ## Use Cases - **Testing models**: Quickly test how different inputs are embedded - **Batch processing**: Generate embeddings for analysis without storage - **Face detection**: Extract face locations and embeddings from photos - **Quality control**: Filter low-confidence detections before storage --- # FILE: client-libraries/rust/request-ai/create-non-linear-algx.md --- title: Create Non-Linear Algorithm Index --- # Create Non-Linear Algorithm Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Creates a **non-linear algorithm index** on a vector store within the **AI service** to optimize similarity search performance. These indexes (HNSW) accelerate nearest-neighbor and semantic searches over large embedding datasets, making retrieval faster and more efficient. Each index type is specified using a `NonLinearIndex` message with a `HnswConfig`. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_types::ai::query::CreateNonLinearAlgorithmIndex; use ahnlich_types::algorithm::nonlinear::{NonLinearIndex, non_linear_index, HnswConfig}; use tokio; #[tokio::main] async fn main() -> Result<(), Box> { let client = AiClient::new("http://127.0.0.1:1370".to_string()).await?; // Create an HNSW index (with default config) let params = CreateNonLinearAlgorithmIndex { store: "MyStore".to_string(), schema: Some("analytics".to_string()), non_linear_indices: vec![ NonLinearIndex { index: Some(non_linear_index::Index::Hnsw(HnswConfig::default())), }, ], }; let result = client.create_non_linear_algorithm_index(params, None).await?; println!("Created non-linear indices: {:?}", result); Ok(()) } ```
## Parameters * `params: CreateNonLinearAlgorithmIndex` — Specifies the target store and algorithm parameters for building the non-linear index. ## Returns * `Ok(CreateIndex)` — Confirmation that the non-linear algorithm index was successfully created, including index details. * `Err(AhnlichError)` — Returned if index creation fails due to invalid parameters, store issues, or server errors. ## Behavior (explains the code, brief) * Wraps the `CreateNonLinearAlgorithmIndex` input in a `tonic::Request`. * Attaches tracing metadata if provided. * Calls the AI service’s `create_non_linear_algorithm_index` RPC endpoint. * Awaits the response and extracts the result. * Returns a `CreateIndex` object with details of the created index. --- # FILE: client-libraries/rust/request-ai/create-predicate-index.md --- title: Create Predicate Index --- # Create Predicate Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Creates a predicate index in the AI service to optimize filtered embedding queries. Speeds up retrieval of embeddings based on metadata constraints, improving the performance of `get_pred` operations. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_client_rs::error::AhnlichError; use ahnlich_types::ai::query::CreatePredIndex; use ahnlich_types::ai::server::CreateIndex; #[tokio::main] async fn main() -> Result<(), AhnlichError> { // Connect to the AI service let ai_client = AiClient::new("http://127.0.0.1:1370".to_string()) .await .expect("Failed to connect AI client"); // Define which store and which predicates to index let params = CreatePredIndex { store: "Deven Kicks".to_string(), schema: Some("analytics".to_string()), predicates: vec!["Brand".to_string(), "Vintage".to_string()], }; // Call the API let response: CreateIndex = ai_client.create_pred_index(params, None).await?; println!(" Created predicate indexes: {:?}", response); Ok(()) } ```
## Parameters * `params: CreatePredIndex` — Specifies the store and the metadata fields to index for predicate-based queries. * `tracing_id: Option` — Optional trace parent ID for observability and distributed tracing. ## Returns * `Ok(CreateIndex)` — Confirmation that the predicate index was successfully created, along with index details. * `Err(AhnlichError)` — Returned if index creation fails due to invalid parameters, store issues, or server errors. ## Behavior (explains the code, brief) * Wraps the `CreatePredIndex` input in a `tonic::Request`. * Adds optional tracing metadata. * Calls the AI/DB service’s `create_pred_index` RPC endpoint. * Waits for the response and extracts the index creation result. * Returns the newly created index information. --- # FILE: client-libraries/rust/request-ai/create-store.md --- title: Create Store --- # Create Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Creates a new vector store within the AI service. A store acts as a container for embeddings and metadata, enabling structured organization of data for similarity search and retrieval tasks. This is typically the first step before inserting embeddings or performing queries against a specific dataset. ## Source Code Example
Click to expand ```rust use ahnlich_types::ai::query::CreateStore; use ahnlich_client_rs::ai::AiClient; use ahnlich_types::ai::models::AiModel; #[tokio::main] async fn main() -> Result<(), Box> { let client = AiClient::new("http://127.0.0.1:1370".to_string()).await?; // Create a new AI store let create_params = CreateStore { store: "Deven Kicks".to_string(), schema: Some("analytics".to_string()), index_model: AiModel::AllMiniLmL6V2 as i32, query_model: AiModel::AllMiniLmL6V2 as i32, predicates: vec![], non_linear_indices: vec![], error_if_exists: true, store_original: true, }; let result = client.create_store(create_params, None).await?; println!("Store created: {:?}", result); Ok(()) } ```
## Parameters * `params: CreateStore` — Input parameters defining the new store (e.g., store name, configuration options). * `tracing_id: Option` — Optional trace parent for distributed tracing, included in the request if provided. ## Returns * `Ok(Unit)` — A confirmation response indicating that the store was successfully created. * `Err(AhnlichError)` — If creation fails due to invalid parameters, a name conflict, or service errors. ## Behavior (explains the code, brief) * Wraps the `CreateStore` parameters in a `tonic::Request`. * Attaches the tracing ID if provided for observability. * Invokes the `create_store` RPC on the AI client. * Awaits the server’s response and extracts the result. * Returns `Unit` to signal successful completion. --- # FILE: client-libraries/rust/request-ai/delete-key.md --- title: Delete Key --- # Delete Key ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Deletes a specific embedding and its associated metadata from a vector store in the **AI service**. This operation is useful for removing obsolete or incorrect embeddings, ensuring that similarity searches and AI queries return only relevant results. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_client_rs::error::AhnlichError; use ahnlich_types::ai::query::DelKey; use ahnlich_types::ai::server::Del; use ahnlich_types::keyval::StoreInput; use ahnlich_types::keyval::store_input::Value; #[tokio::main] async fn main() -> Result<(), AhnlichError> { // Connect to server let ai_client = AiClient::new("http://127.0.0.1:1370".to_string()) .await .expect(" Failed to connect AI client"); // Define key to delete let params = DelKey { store: "Deven Kicks".to_string(), schema: Some("analytics".to_string()), keys: vec![StoreInput { value: Some(Value::RawString("Nike Air Jordans".to_string())), }], }; // Call delete let response: Del = ai_client.del_key(params, None).await?; println!(" Deleted key result: {:?}", response); Ok(()) } ```
## Parameters * `params: DelKey` — Specifies the store and the unique key of the embedding to remove. * `tracing_id: Option` — Optional trace parent ID for observability and distributed tracing. ## Returns * `Ok(Del)` — Confirmation that the embedding and metadata were successfully deleted. * `Err(AhnlichError)` — Returned if the key does not exist, the store is unavailable, or the deletion fails. ## Behavior (explains the code, brief) * Wraps the `DelKey` parameters in a `tonic::Request`. * Attaches tracing metadata if provided. * Sends the deletion request to the AI service via RPC. * Awaits the response and extracts the result. * Returns a `Del` object indicating successful deletion. --- # FILE: client-libraries/rust/request-ai/delete-predicate.md --- title: Delete Predicate --- # Delete Predicate ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Removes one or more records from an AI store based on a predicate condition. This is a passthrough operation to the underlying DB service. Instead of targeting records by key, this operation evaluates a logical filter against stored metadata or values and deletes all matching entries. It provides a flexible way to bulk-remove records without knowing their exact keys. ## Source Code Example
Click to expand ```rust use ahnlich_types::ai::query::DelPred; use ahnlich_types::ai::server::Del; use ahnlich_types::predicates::{Predicate, PredicateCondition, predicate::Kind as PredicateKind, predicate_condition::Kind as PredicateConditionKind}; use ahnlich_client_rs::ai::AiClient; use ahnlich_types::metadata::{MetadataValue, metadata_value::Value}; #[tokio::main] async fn main() -> Result<(), Box> { // gRPC server address let addr = "http://127.0.0.1:1370".to_string(); let ai_client = AiClient::new(addr).await?; // Define the predicate condition to delete let condition = PredicateCondition { kind: Some(PredicateConditionKind::Value(Predicate { kind: Some(PredicateKind::Equals(ahnlich_types::predicates::Equals { key: "category".into(), value: Some(MetadataValue { value: Some(Value::RawString("archived".into())), }), })), })), }; // Parameters for deleting predicate let params = DelPred { store: "my_ai_store".to_string(), schema: Some("analytics".to_string()), condition: Some(condition), }; // Call delete predicate match ai_client.del_pred(params, None).await { Ok(Del { deleted_count }) => { println!("Successfully deleted {} record(s).", deleted_count); } Err(e) => { eprintln!("Error deleting predicate: {:?}", e); } } Ok(()) } ```
## Parameters * `params: DelPred` — Defines the predicate filter used to match records for deletion. * `tracing_id: Option` — Optional trace identifier for monitoring and observability. ## Returns * `Del` — Confirmation of the deletion request, including the count of deleted items. * `AhnlichError` — If the predicate is invalid or the operation fails. ## Behavior * This is a passthrough operation to the underlying DB service. * Wraps the predicate parameters in a request object. * Propagates tracing context if available. * Executes the delete operation against the AI service. * Returns confirmation once the matching records are deleted. --- # FILE: client-libraries/rust/request-ai/drop-non-linear-algx.md --- title: Drop Non-Linear Algorithm Index --- # Drop Non-Linear Algorithm Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Removes a **non-linear algorithm index** from a vector store in the **AI service**. This operation is useful when the index is no longer needed, when changing algorithm parameters, or when rebuilding the index for updated embedding datasets. Removing unused indexes helps maintain storage efficiency and avoids unnecessary overhead during similarity searches. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_types::ai::query::DropNonLinearAlgorithmIndex; use ahnlich_types::algorithm::nonlinear::NonLinearAlgorithm; use tokio; #[tokio::main] async fn main() -> Result<(), Box> { // Connect to AI server let client = AiClient::new("http://127.0.0.1:1370".to_string()).await?; // Set parameters for dropping the non-linear index let params = DropNonLinearAlgorithmIndex { store: "MyStore".to_string(), schema: Some("analytics".to_string()), non_linear_indices: vec![NonLinearAlgorithm::Hnsw as i32], error_if_not_exists: true, // error if the index doesn't exist }; // Execute drop request let result = client .drop_non_linear_algorithm_index(params, None) .await?; println!("Dropped non-linear indices: {:?}", result); Ok(()) } ```
## Parameters * `params: DropNonLinearAlgorithmIndex` — Specifies the store and the non-linear index to remove. ## Returns * `Ok(Del)` — Confirmation that the non-linear algorithm index was successfully deleted. * `Err(AhnlichError)` — Returned if the index does not exist, the store is unavailable, or the operation fails. ## Behavior (explains the code, brief) * Wraps the `DropNonLinearAlgorithmIndex` parameters in a `tonic::Request`. * Attaches optional tracing metadata. * Sends the request to the AI service via RPC. * Awaits the response and extracts the result. * Returns a `Del` object indicating successful deletion of the index. --- # FILE: client-libraries/rust/request-ai/drop-predicate-index.md --- title: Drop Predicate Index --- # Drop Predicate Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Removes a **predicate index** used by the AI service to optimize filtered embedding queries. This operation is useful when certain metadata-based filters are no longer needed for semantic search, or when the index must be rebuilt due to changes in input fields. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_client_rs::error::AhnlichError; use ahnlich_types::ai::query::DropPredIndex; use ahnlich_types::ai::server::Del; #[tokio::main] async fn main() -> Result<(), AhnlichError> { // Connect to the AI service let ai_client = AiClient::new("http://127.0.0.1:1370".to_string()) .await .expect("Failed to connect AI client"); // Define which store and which predicate index to drop let params = DropPredIndex { store: "Deven Kicks".to_string(), schema: Some("analytics".to_string()), predicates: vec!["Brand".to_string()], error_if_not_exists: true, // 👈 required field, prevents silent no-op }; // Call the API let response: Del = ai_client.drop_pred_index(params, None).await?; println!(" Dropped predicate index result: {:?}", response); Ok(()) } ```
## Parameters * `params: DropPredIndex` — Specifies the store and the predicate index to remove. * `tracing_id: Option` — Optional trace parent ID for observability and distributed tracing. ## Returns * `Ok(Del)` — Confirmation that the predicate index was successfully removed. * `Err(AhnlichError)` — Returned if the index does not exist, the store is unavailable, or the request fails. ## Behavior (explains the code, brief) * Wraps the `DropPredIndex` parameters in a `tonic::Request`. * Attaches optional tracing metadata. * Sends the request to the AI service. * Awaits the response and extracts the result. * Returns a `Del` object confirming the deletion. --- # FILE: client-libraries/rust/request-ai/drop-store.md --- title: Drop Store --- # Drop Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Deletes an entire vector store from the **AI service**, including all embeddings and their associated metadata. This is a destructive operation and should be used with caution. Dropping a store is useful when the store is no longer needed, when cleaning up unused resources, or when resetting a dataset for fresh ingestion. ## Source Code Example
Click to expand ```rust use ahnlich_types::ai::query::DropStore; use ahnlich_client_rs::ai::AiClient; #[tokio::main] async fn main() -> Result<(), Box> { let client = AiClient::new("http://127.0.0.1:1370".to_string()).await?; let drop_params = DropStore { store: "Deven Kicks".to_string(), schema: Some("analytics".to_string()), error_if_not_exists: true, }; let result = client.drop_store(drop_params, None).await?; println!("Deleted count: {}", result.deleted_count); Ok(()) } ```
## Parameters * `params: DropStore` — Specifies the store to remove. * `tracing_id: Option` — Optional trace parent ID for observability and distributed tracing. ## Returns * `Ok(Del)` — Confirmation that the store and all its contents were successfully deleted. * `Err(AhnlichError)` — Returned if the store does not exist, is in use, or the deletion fails. ## Behavior (explains the code, brief) * Wraps the `DropStore` parameters in a `tonic::Request`. * Attaches optional tracing metadata. * Sends the request to the AI service’s RPC endpoint. * Awaits the server response and extracts the result. * Returns a `Del` object confirming the store’s removal. --- # FILE: client-libraries/rust/request-ai/get-by-predicate.md --- title: Get By Predicate --- # Get By Predicate ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Retrieves records from a vector store that satisfy a given predicate filter. Supports filtered semantic queries where embeddings must meet metadata-based conditions in addition to similarity searches. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_client_rs::error::AhnlichError; use ahnlich_client_rs::prelude::StoreName; use ahnlich_types::{ ai::query::GetPred, predicates::{Predicate, PredicateCondition, predicate::Kind as PredicateKind, predicate_condition::Kind as PredicateConditionKind, Equals}, metadata::{MetadataValue, metadata_value::Value as MValue}, }; use tokio; #[tokio::main] async fn main() -> Result<(), AhnlichError> { // Connect to AI server let ai_client = AiClient::new("http://127.0.0.1:1370".to_string()) .await .expect("Failed to connect AI client"); // Define store and metadata condition let store_name = StoreName { value: "Deven Kicks".to_string() }; let matching_metadatakey = "Brand".to_string(); let matching_metadatavalue = MetadataValue { value: Some(MValue::RawString("Nike".into())) }; // Build predicate condition let condition = PredicateCondition { kind: Some(PredicateConditionKind::Value(Predicate { kind: Some(PredicateKind::Equals(Equals { key: matching_metadatakey.clone(), value: Some(matching_metadatavalue.clone()), })), })), }; let get_pred_params = GetPred { store: store_name.value.clone(), schema: Some("analytics".to_string()), condition: Some(condition), }; // Call get_pred let response = ai_client.get_pred(get_pred_params, None).await?; println!("Matching entries:"); for entry in response.entries { println!("{:?}", entry); } Ok(()) } ```
## Parameters * `params: GetPred` — Contains the store name and predicate expression used to filter records. * `tracing_id: Option` — Optional trace parent ID for distributed tracing and observability. ## Returns * `Ok(Get)` — A collection of records (embeddings + metadata) that satisfy the predicate filter. * `Err(AhnlichError)` — Returned if the store cannot be queried, the predicate is invalid, or the request fails. ## Behavior * Constructs a `tonic::Request` from the `GetPred` parameters. * Attaches tracing metadata when provided. * Calls the AI service’s `get_pred` RPC endpoint. * Waits for the server response and extracts the result. * Returns the retrieved records packaged in a `Get` object. --- # FILE: client-libraries/rust/request-ai/get-key.md --- title: Get Key --- # Get Key ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Fetches a record from a vector store by its unique key. This provides a deterministic lookup of a specific embedding and its metadata, useful for retrieving known vectors or verifying insertion results. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_client_rs::error::AhnlichError; use ahnlich_types::ai::query::GetKey; use ahnlich_types::keyval::store_input::Value; use ahnlich_types::keyval::StoreInput; #[tokio::main] async fn main() -> Result<(), AhnlichError> { let addr = "127.0.0.1:1370"; let client = AiClient::new(addr.to_string()).await?; let keys = vec![ StoreInput { value: Some(Value::RawString("Adidas Yeezy".to_string())) }, StoreInput { value: Some(Value::RawString("Nike Air Jordans".to_string())) }, ]; let params = GetKey { store: "Main0".to_string(), schema: Some("analytics".to_string()), keys, // directly pass the Vec }; let result = client.get_key(params, None).await?; for entry in result.entries { if let Some(k) = entry.key { println!("Key retrieved: {:?}", k.value); } } Ok(()) } ```
## Parameters * `params: GetKey` — The input containing the store name and the unique key of the record to retrieve. * `tracing_id: Option` — Optional trace parent ID for distributed observability and tracing. ## Returns * `Ok(Get)` — Contains the retrieved record, including its vector embedding and associated metadata. * `Err(AhnlichError)` — Returned if the key does not exist, the store is unavailable, or the request fails. ## Behavior (explains the code, brief) * Wraps the `GetKey` request parameters in a `tonic::Request`. * Attaches trace propagation metadata if provided. * Forwards the request to the AI service’s `get_key` RPC endpoint. * Awaits the response and unwraps the result. * Returns the `Get` object with the stored vector and metadata. --- # FILE: client-libraries/rust/request-ai/get-simn.md --- title: Get Sim N --- # Get Sim N ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Performs a **similarity search** in a vector store, retrieving the top-N most similar embeddings to a given query vector. This operation is the core of the AI client’s retrieval capability, enabling semantic search, recommendation, and nearest-neighbor lookups. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_client_rs::error::AhnlichError; use ahnlich_types::ai::preprocess::PreprocessAction; use ahnlich_types::ai::query::GetSimN; use ahnlich_types::algorithm::algorithms::Algorithm; use ahnlich_types::keyval::StoreInput; use ahnlich_types::keyval::store_input::Value; use std::collections::HashMap; #[tokio::main] async fn main() -> Result<(), AhnlichError> { // Connect to AI server let addr = "127.0.0.1:1370"; let client = AiClient::new(addr.to_string()).await?; // Prepare the search input let search_input = StoreInput { value: Some(Value::RawString("example query".into())), }; // Construct GetSimN parameters let params = GetSimN { store: "Main0".to_string(), schema: Some("analytics".to_string()), search_input: Some(search_input), closest_n: 3, // number of similar entries to retrieve algorithm: Algorithm::CosineSimilarity as i32, execution_provider: None, preprocess_action: PreprocessAction::NoPreprocessing as i32, condition: None, model_params: HashMap::new(), }; // Run the GetSimN command let res = client.get_sim_n(params, None).await?; println!("GetSimN result: {:?}", res); Ok(()) } ```
## Parameters * `params: GetSimN` — The query input, including the target vector and configuration such as the number of neighbors (`N`) to return and optional filters. * `model_params: HashMap` — Optional runtime parameters for the AI model. For face detection models (Buffalo\_L, SFace+YuNet), supports `"confidence_threshold"` to control minimum detection confidence. Pass an empty `HashMap` to use model defaults. See [Model Parameters](/docs/components/ahnlich-ai/advanced#model-parameters-model_params) for details. * `tracing_id: Option` — Optional trace parent ID for distributed observability across services. ## Returns * `Ok(GetSimNResult)` — Contains the ranked list of the top-N closest embeddings along with their similarity scores and associated metadata. * `Err(AhnlichError)` — Returned when the query fails, parameters are invalid, or the service encounters an error. ## Behavior (explains the code, brief) * Wraps the `GetSimN` query in a `tonic::Request`. * Adds trace propagation metadata if provided. * Sends the request to the AI service’s `get_sim_n` RPC method. * Waits for the response and unwraps the result. * Returns a `GetSimNResult` with the similarity matches. --- # FILE: client-libraries/rust/request-ai/get-store.md --- title: Get Store --- # Get Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Retrieves detailed information about a specific AI store by name, including the configured models and optional underlying DB store information. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::ai::AiClient; #[tokio::main] async fn main() -> Result<(), Box> { let ai_client = AiClient::new("127.0.0.1:1370".to_string()).await?; let tracing_id: Option = None; let store_info = ai_client .get_store_with_schema( "ai_store".to_string(), Some("analytics".to_string()), tracing_id, ) .await?; println!("Store name: {}", store_info.name); println!("Query model: {:?}", store_info.query_model); println!("Index model: {:?}", store_info.index_model); println!("Embedding size: {}", store_info.embedding_size); println!("Dimension: {}", store_info.dimension); println!("Predicate indices: {:?}", store_info.predicate_indices); if let Some(db_info) = &store_info.db_info { println!("DB store size: {} bytes", db_info.size_in_bytes); } Ok(()) } ```
## Parameters * `store: String` — The name of the AI store to retrieve. * `schema: Option` — Schema containing the AI store. Omit it to use `public`. * `tracing_id: Option` — Optional trace context for observability. ## Returns * `AiStoreInfo` — Detailed information about the AI store. * `AhnlichError` — If the store does not exist or the request fails. ## AiStoreInfo Fields | Field | Type | Description | |-------|------|-------------| | `name` | `String` | Store name | | `query_model` | `AiModel` | AI model used for query embeddings | | `index_model` | `AiModel` | AI model used for index embeddings | | `embedding_size` | `u64` | Number of stored embeddings | | `dimension` | `u32` | Vector dimension (determined by model) | | `predicate_indices` | `Vec` | List of indexed predicate keys | | `db_info` | `Option` | Underlying DB store info (when connected) | ## Behavior * Sends a request to retrieve AI store metadata by name. * Returns an error if the store does not exist. * The `db_info` field is present when the AI proxy is connected to a DB instance. ## Notes - Use `list_stores` to get information about AI stores in a schema - The model fields indicate which embedding models are used for indexing and querying --- # FILE: client-libraries/rust/request-ai/info-server.md --- title: Info Server --- # Info Server Retrieves detailed information about the Ahnlich AI service server, including metadata such as version, build information, and runtime configuration. This call is useful for diagnostics, compatibility checks, and ensuring the AI service is running as expected. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::ai::AiClient; // <-- note the `ai::` path use ahnlich_client_rs::error::AhnlichError; use ahnlich_types::shared::info::ServerInfo; #[tokio::main] async fn main() -> Result<(), AhnlichError> { let addr = "127.0.0.1:1370"; let client = AiClient::new(addr.to_string()).await?; // Direct info_server call let server_info: ServerInfo = client.info_server(None).await?; println!("Server Info: {:?}", server_info); // Using pipeline let mut pipeline = client.pipeline(None); pipeline.info_server(); let pipeline_result = pipeline.exec().await?; println!("Pipeline Server Info: {:?}", pipeline_result); Ok(()) } ```
## Parameters * `tracing_id: Option` — Optional trace parent attached to propagate observability metadata with the request. ## Returns * `Ok(ServerInfo)` — Metadata describing the AI service server (e.g., version, configuration). * `Err(AhnlichError)` — If the server cannot be reached, request fails, or metadata is missing. ## Behavior (explains the code, brief) * Builds a `tonic::Request` with an empty `InfoServer {}` message. * Propagates tracing context if provided. * Calls the remote `info_server` RPC using the cloned client. * Awaits and unwraps the server response. * Extracts the `info` field, returning it as `ServerInfo`. * Ensures the `info` is not `None` with `.expect()`, which will panic if missing (a server contract guarantee). --- # FILE: client-libraries/rust/request-ai/list-connected-clients.md --- title: List Connected Clients --- # List Connected Clients Retrieves a list of clients currently connected to the **AI service**. This provides visibility into active sessions, useful for monitoring, debugging, and coordinating multi-client AI workloads. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_client_rs::error::AhnlichError; #[tokio::main] async fn main() -> Result<(), AhnlichError> { // Connect to AI server let addr = "127.0.0.1:1370".to_string(); let ai_client = AiClient::new(addr).await?; // Fetch the list of connected clients let clients = ai_client.list_clients(None).await?; // Print the clients in a readable way println!("Connected clients:"); for (i, client) in clients.clients.iter().enumerate() { println!("{}. {:?}", i + 1, client); } Ok(()) } ```
## Returns * `Ok(ClientList)` — A structured list of active AI client connections. * `Err(AhnlichError)` — Returned if the request fails due to connectivity issues or service errors. ## Behavior (explains the code, brief) * Wraps an empty `ListClients {}` request in a `tonic::Request`. * Adds trace metadata if provided. * Calls the AI service’s `list_clients` RPC. * Awaits the response and extracts the list of connected clients. --- # FILE: client-libraries/rust/request-ai/list-stores.md --- title: List Store --- # List Stores ## Schema `ListStores` accepts an optional `schema` field. When it is omitted, the server lists stores in `public` only; it does not list stores across every schema. Set `schema` to list stores in another schema. Retrieves vector stores from one schema currently managed by the **AI service**. Each store represents a logical container for embeddings and their associated metadata. This operation is useful for exploring available stores before performing read or write operations. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_client_rs::error::AhnlichError; #[tokio::main] async fn main() -> Result<(), AhnlichError> { let addr = "127.0.0.1:1370"; let client = AiClient::new(addr.to_string()).await?; let stores = client .list_stores_with_schema(Some("analytics".to_string()), None) .await?; println!("Stores: {:?}", stores); Ok(()) } ```
## Returns * `Ok(StoreList)` — A structured list of available vector stores managed by the AI service. * `Err(AhnlichError)` — If the request fails due to connectivity issues, authorization errors, or service unavailability. ## Behavior (explains the code, brief) * Creates a `tonic::Request` wrapping an empty `ListStores { schema: Some("analytics".to_string()) }` message. * Adds a tracing ID if provided for observability. * Calls the remote `list_stores` RPC using the AI client. * Awaits the result and unwraps the server’s response. * Returns the `StoreList` object containing store metadata. Each `AiStoreInfo` includes an optional `db_info` field with the underlying DB store information when the AI service is connected to a DB instance. --- # FILE: client-libraries/rust/request-ai/new.md --- title: New --- # New Initializes a new **AI client** instance that can communicate with the Ahnlich **AI service**. This method sets up the gRPC connection and prepares the client for performing operations such as embedding insertions, similarity searches, and pipelines. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_client_rs::error::AhnlichError; use tokio; #[tokio::main] async fn main() -> Result<(), AhnlichError> { let addr = "127.0.0.1:1370".to_string(); match AiClient::new(addr).await { Ok(_client) => { println!("AiClient successfully created!"); Ok(()) } Err(e) => { eprintln!("Failed to create AiClient: {:?}", e); Err(e) } } } ```
## Parameters * `addr: String` — The address of the AI service (e.g., `"127.0.0.1:1370"`). If the scheme (`http://` or `https://`) is not provided, it defaults to `http://`. ## Returns * `Ok(AiClient)` — A fully initialized AI client ready to perform requests. * `Err(AhnlichError)` — Returned if the connection fails or the provided address is invalid. ## Behavior (explains the code, brief) * Ensures the address includes a valid HTTP/HTTPS scheme. * Creates a gRPC `Channel` from the given address. * Connects to the AI service using `AiServiceClient::connect`. * Wraps the client in an `AiClient` struct and returns it. --- # FILE: client-libraries/rust/request-ai/ping.md --- title: Ping --- # Ping Checks connectivity with the **Ahnlich AI** service and verifies the server is reachable over the current gRPC channel. Use this lightweight call for health checks or to validate that the AI client can communicate with the service before issuing embedding or inference requests. ## Source Code Example
Click to expand ```rust // src/bin/pingai.rs use ahnlich_client_rs::ai::AiClient; // AiClient path use ahnlich_client_rs::error::AhnlichError; // Error type use ahnlich_types::ai::pipeline::AiResponsePipeline; #[tokio::main] async fn main() -> Result<(), AhnlichError> { // AI server address let addr = "http://127.0.0.1:1370"; // Initialize the AI client let ai_client = AiClient::new(addr.to_string()).await?; // Simple ping request let pong = ai_client.ping(None).await?; println!("AI Server Pong received: {:?}", pong); // Using a pipeline to send a ping let mut pipeline = ai_client.pipeline(None); pipeline.ping(); let res: AiResponsePipeline = pipeline.exec().await?; println!("Pipeline response: {:?}", res); Ok(()) } ```
## Parameters * `tracing_id: Option` — Optional trace parent used to propagate observability metadata with the request. ## Returns * `Ok(Pong)` — A lightweight acknowledgement from the server indicating connectivity. * `Err(AhnlichError)` — If the request fails due to transport, server, or authentication errors. ## Behavior (explains the code, brief) * Constructs a `tonic::Request` carrying an empty `Ping {}` message. * Calls `add_trace_parent(&mut req, tracing_id)` to attach the optional tracing context to the gRPC metadata. * Uses a cloned gRPC client to call the remote `ping` RPC, awaits the response, and returns the inner `Pong` payload. --- # FILE: client-libraries/rust/request-ai/purge-stores.md --- title: Purge Stores --- # Purge Stores Deletes **all vector stores** managed by the **AI client**, including all embeddings and associated metadata. This is a destructive operation that resets the AI service state, typically used during testing, cleanup, or when starting fresh with new datasets. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::ai::{AiClient, AiPipeline}; use ahnlich_types::ai::models::AiModel; use ahnlich_types::ai::query::CreateStore; use ahnlich_types::ai::pipeline::AiResponsePipeline; use tokio::time::Duration; #[tokio::main] async fn main() { // Initialize AI client (replace with your server address) let ai_client = AiClient::new("http://127.0.0.1:1370".to_string()) .await .expect("Could not connect to AI client"); // Create a new pipeline let mut pipeline = ai_client.pipeline(None); // Example: create a test store let store = CreateStore { store: "TestStore".to_string(), index_model: AiModel::AllMiniLmL6V2 as i32, query_model: AiModel::AllMiniLmL6V2 as i32, predicates: vec![], non_linear_indices: vec![], error_if_exists: true, store_original: true, }; pipeline.create_store(store); // You can add more pipeline actions here // Example: purge all stores pipeline.purge_stores(); // Execute pipeline let res: AiResponsePipeline = pipeline.exec().await.expect("Pipeline execution failed"); println!("Pipeline result: {res:#?}"); } ```
## Returns * `Ok(Del)` — Confirmation that all stores and their contents were successfully deleted. * `Err(AhnlichError)` — Returned if the operation fails due to service errors or connectivity issues. ## Behavior (explains the code, brief) * Wraps an empty `PurgeStores {}` request in a `tonic::Request`. * Attaches optional tracing metadata. * Sends the request to the AI service via RPC. * Awaits the server response and extracts the result. * Returns a `Del` object indicating successful deletion of all stores. --- # FILE: client-libraries/rust/request-ai/request-ai.md --- title: Request AI --- # Request AI The **Request AI client** provides a set of operations for interacting with the **Ahnlich AI service**, which complements the DB client by handling the generation, transformation, and interpretation of embeddings. Instead of managing storage or retrieval directly, the AI client focuses on **creating meaningful vector representations** from raw data and enabling higher-level reasoning tasks. Just like the DB client, each operation follows a consistent execution pattern: * **Request preparation** — Input parameters are wrapped in a `tonic::Request` object. * **Tracing propagation** — If a `tracing_id` is provided, it is attached for observability. * **Execution** — The client forwards the request to the AI service. * **Response handling** — The response is unwrapped and returned in a typed result. ## Capabilities With Request AI, you can: * **Generate embeddings** — Convert text, documents, or structured input into dense vector representations. * **Interpret embeddings** — Extract semantic meaning or similarity insights from stored vectors. * **Support hybrid workflows** — Combine AI-generated embeddings with DB operations for efficient similarity search. * **Metadata augmentation** — Enrich vectors with contextual or domain-specific annotations before persistence. * **Batch processing** — Process multiple inputs in a single request for efficiency. ## Behavior All Request AI operations are designed to: * **Ensure consistency** — The same input will always yield the same embedding for reproducibility. * **Support idempotency** — Repeated requests with identical input and parameters return identical results. * **Handle concurrency** — Multiple requests can be executed in parallel, ensuring scalability under load. * **Propagate observability** — Optional tracing IDs allow for debugging and performance monitoring in distributed systems. Below are the operations for generating embeddings, interpreting inputs, and integrating AI-driven vectors into the Ahnlich ecosystem. * [Ping](/docs/client-libraries/rust/request-ai/ping) * [Info Server](/docs/client-libraries/rust/request-ai/info-server) * [List Stores](/docs/client-libraries/rust/request-ai/list-stores) * [Create Store](/docs/client-libraries/rust/request-ai/create-store) * [Set](/docs/client-libraries/rust/request-ai/set) * [Upsert](/docs/client-libraries/rust/request-ai/upsert) * [Get Sim N](/docs/client-libraries/rust/request-ai/get-simn) * [Get Key](/docs/client-libraries/rust/request-ai/get-key) * [Get by Predicate](/docs/client-libraries/rust/request-ai/get-by-predicate) * [Create Predicate Index](/docs/client-libraries/rust/request-ai/create-predicate-index) * [Drop Predicate Index](/docs/client-libraries/rust/request-ai/drop-predicate-index) * [Delete Key](/docs/client-libraries/rust/request-ai/delete-key) * [Drop Store](/docs/client-libraries/rust/request-ai/drop-store) * [List Connected Clients](/docs/client-libraries/rust/request-ai/list-connected-clients) * [Create Non-Linear Algorithm Index](/docs/client-libraries/rust/request-ai/create-non-linear-algx) * [Drop Non-Linear Algorithm Index](/docs/client-libraries/rust/request-ai/drop-non-linear-algx) * [New](/docs/client-libraries/rust/request-ai/new) * [Purge Stores](/docs/client-libraries/rust/request-ai/purge-stores) --- # FILE: client-libraries/rust/request-ai/set.md --- title: Set --- # Set ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Inserts or updates embeddings and their associated metadata into a vector store managed by the **AI service**. This operation is central to populating a store with new data or refreshing existing entries to keep the dataset consistent and relevant for similarity search. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_client_rs::error::AhnlichError; use ahnlich_types::ai::preprocess::PreprocessAction; use ahnlich_types::ai::query::Set; use ahnlich_types::keyval::{AiStoreEntry, StoreInput, StoreValue}; use ahnlich_types::keyval::store_input::Value; use std::collections::HashMap; #[tokio::main] async fn main() -> Result<(), AhnlichError> { // Connect to AI server let addr = "127.0.0.1:1370"; let client = AiClient::new(addr.to_string()).await?; // Prepare data for Set let set_params = Set { store: "Main0".to_string(), schema: Some("analytics".to_string()), execution_provider: None, preprocess_action: PreprocessAction::NoPreprocessing as i32, inputs: vec![ AiStoreEntry { key: Some(StoreInput { value: Some(Value::RawString("Adidas Yeezy".into())) }), value: Some(StoreValue { value: HashMap::new() }), }, AiStoreEntry { key: Some(StoreInput { value: Some(Value::RawString("Nike Air Jordans".into())) }), value: Some(StoreValue { value: HashMap::new() }), }, ], model_params: HashMap::new(), }; // Run the set command let res = client.set(set_params, None).await?; println!("Inserted entries: {:?}", res.upsert); Ok(()) } ```
## Parameters * `params: Set` — The embeddings and metadata to be stored, including the store key and value. If a key already exists, this call updates the associated record. * `model_params: HashMap` — Optional runtime parameters for the AI model. For face detection models (Buffalo\_L, SFace+YuNet), supports `"confidence_threshold"` to control minimum detection confidence. Pass an empty `HashMap` to use model defaults. See [Model Parameters](/docs/components/ahnlich-ai/advanced#model-parameters-model_params) for details. ## Returns * `Ok(SetResult)` — Contains the outcome of the insertion or update (e.g., confirmation of success, affected keys). * `Err(AhnlichError)` — If the operation fails due to invalid input, conflicts, or server-side errors. ## Behavior (explains the code, brief) * Prepares the `Set` request payload inside a `tonic::Request`. * Adds tracing context if provided. * Sends the request via the AI client’s `set` RPC method. * Waits for the response and extracts the typed result. * Returns a `SetResult` indicating success or failure. --- # FILE: client-libraries/rust/request-ai/upsert.md --- title: Upsert --- # Upsert ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Updates a single entry matching a predicate condition in an AI-powered store. The AI service automatically merges metadata and can re-embed new inputs. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_client_rs::error::AhnlichError; use ahnlich_types::ai::preprocess::PreprocessAction; use ahnlich_types::ai::query::Upsert; use ahnlich_types::keyval::{StoreInput, StoreValue}; use ahnlich_types::metadata::{MetadataValue, metadata_value::Value}; use ahnlich_types::predicates::{Predicate, PredicateCondition, predicate_condition::Kind, predicate::Kind as PredKind}; use std::collections::HashMap; #[tokio::main] async fn main() -> Result<(), AhnlichError> { // Connect to AI server let addr = "127.0.0.1:1370"; let client = AiClient::new(addr.to_string()).await?; // Construct predicate condition let condition = PredicateCondition { kind: Some(Kind::Value(Predicate { kind: Some(PredKind::Equals(ahnlich_types::predicates::Equals { key: "filename".to_string(), value: Some(MetadataValue { value: Some(Value::RawString("photo.jpg".to_string())), }), })), })), }; // New metadata to merge let new_value = Some(StoreValue { value: HashMap::from_iter([( "tags".into(), MetadataValue { value: Some(Value::RawString("cat,outdoors".into())), }, )]), }); let params = Upsert { store: "images".to_string(), schema: Some("media".to_string()), condition: Some(condition), new_input: None, // Optional: new image/text to re-embed new_value, preprocess_action: PreprocessAction::NoPreprocessing as i32, execution_provider: None, model_params: HashMap::new(), }; // Run the upsert command let res = client.upsert(params, None).await?; println!("Upsert result: {:?}", res.upsert); Ok(()) } ```
## Parameters * `params: Upsert` — The update operation: * `store` — The name of the target store. * `condition` — Predicate that must match exactly one entry. * `new_input` (optional) — New raw input (text/image) to re-embed. * `new_value` (optional) — Metadata to update (always merged by AI proxy). * `preprocess_action` — How to preprocess new input. * `execution_provider` (optional) — Hardware acceleration (e.g., CUDA). * `model_params` — Optional runtime parameters for the AI model. * `schema` (optional) — Schema namespace (defaults to "public"). * `tracing_id: Option` — An optional tracing identifier. ## Returns * `Ok(Set)` — Contains upsert counts (inserted and updated). * `Err(AhnlichError)` — Returned if: * Predicate matches 0 or multiple entries. * Store does not exist. * Invalid input format. * Transport or server-side errors. ## Behavior * AI proxy always merges metadata (preserves AI-generated fields). * Predicate must match exactly one entry (errors on 0 or multiple matches). * Re-embeds input if `new_input` is provided. * Updated entries are immediately available for similarity search. --- # FILE: client-libraries/rust/request-db/create-non-linear-algx.md --- title: Create Non-Linear Algorithm Index --- # Create Non-Linear Algorithm Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Creates a non-linear algorithm index on a store to optimize vector similarity searches beyond basic linear methods. Non-linear indexes (HNSW) improve query performance and scalability when working with large vector datasets. Each index type is specified using a `NonLinearIndex` message with a `HnswConfig`. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::db::DbClient; use ahnlich_types::db::query::CreateNonLinearAlgorithmIndex; use ahnlich_types::algorithm::nonlinear::{NonLinearIndex, non_linear_index, HnswConfig}; #[tokio::main] async fn main() -> Result<(), Box> { let db_client = DbClient::new("127.0.0.1:1369".to_string()).await?; // Create an HNSW index (with default config) on the "Main" store let params = CreateNonLinearAlgorithmIndex { store: "Main".to_string(), schema: Some("analytics".to_string()), non_linear_indices: vec![ NonLinearIndex { index: Some(non_linear_index::Index::Hnsw(HnswConfig::default())), }, ], }; let result = db_client.create_non_linear_algorithm_index(params, None).await?; println!("Created non-linear indices: {:?}", result); Ok(()) } ```
## Parameters * `params: CreateNonLinearAlgorithmIndex` — Defines the target store and configuration for the non-linear index. * `tracing_id: Option` — Optional trace context for observability. ## Returns * `CreateIndex` — Confirmation and details of the newly created index. * `AhnlichError` — If the request fails due to invalid parameters or server errors. ## Behavior * Builds a request with the provided `params`. * Attaches optional tracing metadata for distributed tracing. * Sends the request to the DB service to create the index. --- # FILE: client-libraries/rust/request-db/create-predicate-index.md --- title: Create Predicate Index --- # Create Predicate Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Creates an index on a predicate field in a store. Predicate indexes allow efficient filtering and retrieval of vectors based on metadata conditions, improving query performance for repeated predicate lookups. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::db::DbClient; use ahnlich_types::db::query::CreatePredIndex; #[tokio::main] async fn main() -> Result<(), Box> { // connect to DB server let db_client = DbClient::new("127.0.0.1:1369".to_string()).await?; let tracing_id: Option = None; // Create predicate index request let params = CreatePredIndex { store: "Main".to_string(), schema: Some("analytics".to_string()), predicates: vec!["role".to_string()], // metadata field names to index }; // Call the client match db_client.create_pred_index(params, tracing_id).await { Ok(result) => { println!("Created predicate index: {:?}", result); } Err(err) => { eprintln!("Error creating predicate index: {:?}", err); } } Ok(()) } ```
## Parameters * `params: CreatePredIndex` — Defines the target store and the predicate field to be indexed. ## Returns * `CreateIndex` — Response confirming that the index has been successfully created. * `AhnlichError` — Returned if the store does not exist, the field is invalid, or index creation fails. ## Behavior * Wraps the provided `CreatePredIndex` parameters into a gRPC request. * Adds optional tracing metadata to the request for debugging or monitoring. * Invokes the `create_pred_index` RPC on the Ahnlich server via the cloned gRPC client. * Awaits the server’s response and extracts the `CreateIndex` result using `.into_inner()`. --- # FILE: client-libraries/rust/request-db/create-store.md --- title: Create Store --- # Create Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Creates a new vector store within the Ahnlich DB service. A store is the primary container for vectors and metadata, and all vector operations must be scoped to a specific store. This request is essential for initializing logical partitions of data before inserting or querying vectors. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::db::DbClient; use ahnlich_types::db::query::CreateStore; #[tokio::main] async fn main() -> Result<(), Box> { // Connect to server let db_client = DbClient::new("127.0.0.1:1369".to_string()).await?; let tracing_id: Option = None; // Define parameters for store creation let params = CreateStore { store: "Main".to_string(), schema: Some("analytics".to_string()), dimension: 3, create_predicates: vec!["role".to_string()], non_linear_indices: vec![], error_if_exists: true, }; // Call create_store match db_client.create_store(params, tracing_id).await { Ok(res) => { println!("Store created successfully: {:?}", res); } Err(err) => { eprintln!("Error creating store: {:?}", err); } } Ok(()) } ```
## Parameters * `params: CreateStore` – The configuration for the new store. This includes required fields such as the store name, vector dimensionality, and optional indexing options.. ## Returns * `Ok(Unit)` – Indicates that the store was successfully created. * `Err(AhnlichError)` – Returned if the request fails. Common failure cases include: * A store with the same name already exists. * Invalid configuration parameters (e.g., mismatched dimensions). * Server-side or transport-level errors. ## Behavior * This is a write operation and will allocate resources on the server. * Once created, the store immediately becomes available for other operations such as `Set`, `Get Sim N`, or predicate-based queries. * Tracing information, if provided, is propagated through the request for monitoring and debugging. ## Usage considerations * Use `List Stores` after creation to verify that the store has been registered successfully. The response includes the non-linear index configurations (HNSW parameters) for each store. * Stores are persistent until explicitly removed using `Drop Store`. * Proper planning of store dimensions and indexing strategy is recommended, as these cannot be trivially changed after creation. * When creating a store with an HNSW index, you can pass configuration parameters such as `ef_construction`, `maximum_connections`, `maximum_connections_zero`, `distance` metric, `extend_candidates`, and `keep_pruned_connections` via `HnswConfig`. If not specified, sensible defaults are used. * A store cannot have duplicate non-linear indices of the same type. Attempting to add an HNSW index to a store that already has one will be silently ignored (idempotent). --- # FILE: client-libraries/rust/request-db/delete-key.md --- title: Delete Key --- # Delete Key ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Removes a single key and its associated vector and metadata from a store. This operation permanently deletes the entry, ensuring it is no longer retrievable in similarity searches or direct lookups. Use this to manage lifecycle of individual records without affecting the rest of the store. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::db::DbClient; use ahnlich_types::db::query::DelKey; use ahnlich_types::keyval::StoreKey; use tokio; #[tokio::main] async fn main() -> Result<(), Box> { let addr = "http://127.0.0.1:1369"; // adjust if your server runs elsewhere let client = DbClient::new(addr.to_string()).await?; // Delete a specific key from store "Main" (dimension must match) let del_key_params = DelKey { store: "Main".to_string(), schema: Some("analytics".to_string()), keys: vec![StoreKey { key: vec![1.0, 1.1, 1.2], // ✅ matches dimension=3 }], }; match client.del_key(del_key_params, None).await { Ok(result) => println!("Deleted count: {:?}", result.deleted_count), Err(e) => eprintln!("Error: {:?}", e), } Ok(()) } ```
## Parameters * `params: DelKey` — Identifies the store and the specific key to delete. * `tracing_id: Option` — Optional trace context for observability. ## Returns * `Del` — Confirmation that the key was successfully removed. * `AhnlichError` — If the key or store does not exist. ## Behavior * Builds a gRPC request targeting the key specified. * Adds optional trace metadata for distributed tracing. * Executes the `del_key` RPC, removing the vector and metadata tied to the key. --- # FILE: client-libraries/rust/request-db/delete-predicate.md --- title: Delete Predicate --- # Delete Predicate ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Removes one or more records from a store based on a predicate condition. Instead of targeting records by key, this operation evaluates a logical filter against stored metadata or values and deletes all matching entries. It provides a flexible way to bulk-remove records without knowing their exact keys. ## Source Code Example
Click to expand ```rust use ahnlich_types::db::query::DelPred; use ahnlich_types::db::server::Del; use ahnlich_types::predicates::{Predicate, PredicateCondition, predicate::Kind as PredicateKind, predicate_condition::Kind as PredicateConditionKind}; use ahnlich_client_rs::db::DbClient; use ahnlich_types::metadata::{MetadataValue, metadata_value::Value}; use std::collections::HashMap; #[tokio::main] async fn main() -> Result<(), Box> { // gRPC server address let addr = "http://127.0.0.1:1369".to_string(); let db_client = DbClient::new(addr).await?; // Define the predicate condition to delete let condition = PredicateCondition { kind: Some(PredicateConditionKind::Value(Predicate { kind: Some(PredicateKind::Equals(ahnlich_types::predicates::Equals { key: "medal".into(), value: Some(MetadataValue { value: Some(Value::RawString("gold".into())), }), })), })), }; // Parameters for deleting predicate let params = DelPred { store: "Main".to_string(), // your store name schema: Some("analytics".to_string()), condition: Some(condition), }; // Call delete predicate match db_client.del_pred(params, None).await { Ok(Del { deleted_count }) => { println!("Successfully deleted {} predicate(s).", deleted_count); } Err(e) => { eprintln!("Error deleting predicate: {:?}", e); } } Ok(()) } ```
## Parameters * `params: DelPred` — Defines the predicate filter used to match records for deletion. * `tracing_id: Option` — Optional trace identifier for monitoring and observability. ## Returns * `Del` — Confirmation of the deletion request, including details of what was removed. * `AhnlichError` — If the predicate is invalid or no matching records are found. ## Behavior * Wraps the predicate parameters in a request object. * Propagates tracing context if available. * Executes the delete operation against the DB service. * Returns confirmation once the matching records are deleted. --- # FILE: client-libraries/rust/request-db/drop-non-linear-algx.md --- title: Drop Non-Linear Algorithm Index --- # Drop Non-Linear Algorithm Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Removes an existing non-linear algorithm index from a store. This operation is useful when an index is no longer needed, when switching to a different indexing strategy, or during cleanup of store resources. Dropping the index reverts the store back to standard linear search behavior unless another index exists. ## Source Code Example
Click to expand ```rust use ahnlich_types::db::query::DropNonLinearAlgorithmIndex; use ahnlich_types::algorithm::nonlinear::NonLinearAlgorithm; use ahnlich_types::db::server::Del; use ahnlich_client_rs::db::DbClient; #[tokio::main] async fn main() -> Result<(), Box> { let addr = "http://127.0.0.1:1369".to_string(); let db_client = DbClient::new(addr).await?; let params = DropNonLinearAlgorithmIndex { store: "Main".to_string(), schema: Some("analytics".to_string()), non_linear_indices: vec![NonLinearAlgorithm::Hnsw as i32], error_if_not_exists: false, }; match db_client .drop_non_linear_algorithm_index(params, None) .await { Ok(Del { deleted_count }) => { println!("Successfully dropped {} non-linear index(es).", deleted_count); } Err(e) => { eprintln!("Error dropping non-linear index: {:?}", e); } } Ok(()) } ```
## Parameters * `params: DropNonLinearAlgorithmIndex` — Specifies the target store and index to be dropped. * `tracing_id: Option` — Optional trace identifier for observability. ## Returns * `Del` — Confirmation that the index has been dropped. * `AhnlichError` — If the operation fails due to invalid parameters or missing index. ## Behavior * Builds a request with the given `params`. * Adds tracing information when provided. * Sends the request to the DB service to drop the index. * Returns confirmation once the index has been removed. * Cleaning up unused indexes. * Switching from non-linear search back to linear search. * Replacing an old index with a newly configured one. --- # FILE: client-libraries/rust/request-db/drop-predicate-index.md --- title: Drop Predicate Index --- # Drop Predicate Index ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Removes an existing predicate index from a store. This operation cleans up indexes that are no longer needed for query acceleration. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::db::DbClient; use ahnlich_types::db::query::DropPredIndex; use tokio; #[tokio::main] async fn main() -> Result<(), Box> { let addr = "http://127.0.0.1:1369"; let client = DbClient::new(addr.to_string()).await?; // Drop the "role" predicate index from store "Main" let drop_index_params = DropPredIndex { store: "Main".to_string(), schema: Some("analytics".to_string()), predicates: vec!["role".to_string()], error_if_not_exists: true, // fail if it doesn't exist }; match client.drop_pred_index(drop_index_params, None).await { Ok(result) => println!("Dropped predicate index: {:?}", result), Err(e) => eprintln!("Error: {:?}", e), } Ok(()) } ```
## Parameters * `params: DropPredIndex` — Identifies the store and the predicate index to remove. ## Returns * `Del` — Confirmation of index removal. * `AhnlichError` — If the store or index does not exist. ## Behavior * Builds a gRPC request with the target index details. * Attaches optional trace metadata. * Executes the `drop_pred_index` RPC to delete the index. --- # FILE: client-libraries/rust/request-db/drop-store.md --- title: Drop Store --- # Drop Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Deletes an entire store from the database, including all vectors, keys, and associated metadata. This is a destructive operation and cannot be reversed—once a store is dropped, all of its contents are permanently removed. ## Source Code Example
Click to expand ```rust use ahnlich_types::db::query::DropStore; use ahnlich_client_rs::db::DbClient; #[tokio::main] async fn main() -> Result<(), Box> { // Connect to the DB server let client = DbClient::new("http://127.0.0.1:1369".to_string()).await?; // Prepare drop store parameters let drop_params = DropStore { store: "MyStore".to_string(), schema: Some("analytics".to_string()), error_if_not_exists: true, // Required field }; // Execute the drop store request let result = client.drop_store(drop_params, None).await?; println!("Deleted count: {}", result.deleted_count); Ok(()) } ```
## Parameters * `params: DropStore` — Identifies the store to be deleted. * `tracing_id: Option` — Optional trace context for observability. ## Returns * `Del` — Confirmation that the store was successfully dropped. * `AhnlichError` — If the store does not exist or the operation fails. ## Behavior * Builds a gRPC request with the `DropStore` parameters. * Attaches tracing metadata if provided. * Calls the DB client to drop the specified store and remove all its contents. --- # FILE: client-libraries/rust/request-db/get-by-predicate.md --- title: Get By Predicate --- # Get By Predicate ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Retrieve one or more stored vectors and their associated metadata from a store by applying a predicate filter. Unlike `Get Key`, which retrieves a single item by its unique key, `Get by Predicate` allows querying based on conditions defined on metadata fields (for example, "all items where `category = book`"). This is useful for flexible filtering, targeted queries, or conditional retrieval. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::db::DbClient; use ahnlich_types::{ db::query::GetPred, metadata::{MetadataValue, metadata_value::Value}, predicates::{ Predicate, PredicateCondition, predicate::Kind as PredicateKind, predicate_condition::Kind as PredicateConditionKind, Equals, }, }; use tokio; #[tokio::main] async fn main() -> Result<(), Box> { let addr = "http://127.0.0.1:1369"; let client = DbClient::new(addr.to_string()).await?; let condition = PredicateCondition { kind: Some(PredicateConditionKind::Value(Predicate { kind: Some(PredicateKind::Equals(Equals { key: "role".into(), value: Some(MetadataValue { value: Some(Value::RawString("admin".into())), }), })), })), }; let get_pred_params = GetPred { store: "Main".to_string(), schema: Some("analytics".to_string()), condition: Some(condition), }; let result = client.get_pred(get_pred_params, None).await?; println!("Fetched rows: {:#?}", result); Ok(()) } ```
## Parameters * `params: GetPred` — Contains the store name and predicate condition used to filter results. * `tracing_id: Option` — Optional trace context for observability, attached to the request if provided. ## Returns * `Get` — Response with all matched vectors and metadata. * `AhnlichError` — Error if the store is missing, the predicate is invalid, or the server cannot complete the request. ## Behavior * Builds a gRPC request from the given `GetPred` parameters. * Attaches optional tracing metadata using `add_trace_parent`. * Calls the `get_pred` RPC on the server through the cloned client. * Waits asynchronously for the server’s response and extracts the inner `Get` payload with `.into_inner()`. --- # FILE: client-libraries/rust/request-db/get-key.md --- title: Get Key --- # Get Key ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Retrieve a single stored vector (and its associated metadata) by key from a specified store. Use this request to fetch the exact item you previously inserted with `Set` or to validate the contents of a given key. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::db::DbClient; use ahnlich_types::{ db::query::GetKey, keyval::StoreKey, }; use tokio; #[tokio::main] async fn main() -> Result<(), Box> { // point to your DB server (default is 1369, adjust if needed) let addr = "http://127.0.0.1:1369"; let client = DbClient::new(addr.to_string()).await?; // example: look up a key from store "Main" let get_key_params = GetKey { store: "Main".to_string(), schema: Some("analytics".to_string()), keys: vec![ StoreKey { key: vec![1.2, 1.3, 1.4], // must match a previously Set key }, ], }; match client.get_key(get_key_params, None).await { Ok(result) => { println!("Fetched: {:#?}", result); } Err(e) => { eprintln!("Error fetching key: {:?}", e); } } Ok(()) } ```
## Parameters * `params: GetKey` — Request payload identifying the target store and key. Depending on your server proto, this typically includes: * `store` (required) — The store name to query. * `key` (required) — The unique identifier for the vector to retrieve. * Optional flags (server-dependent) to control returned fields (for example, whether the raw vector or metadata should be included). * `tracing_id: Option` — Optional trace parent to propagate observability context. When provided, `add_trace_parent` attaches tracing metadata to the outgoing gRPC request. ## Returns * `Ok(Get)` — A `Get` response containing the requested payload. This commonly includes the vector data, any stored metadata, and the key. The exact fields are defined by the crate’s Get type and server proto. * `Err(AhnlichError)` — Failure to fetch the key. Typical error cases: * Target store not found. * Key not found (missing). * Permission or authentication failures. * Transport-level errors (connection, timeout). * Any server-side validation or internal error. ## Behavior * Read-only RPC with no side effects on server state. * The request is synchronous in the sense that it returns the current stored value at the time of the call. * Tracing metadata (if provided) is propagated for observability and debugging. * The code returns the inner response (`into_inner()`), delivering the `Get` payload directly to the caller. --- # FILE: client-libraries/rust/request-db/get-simn.md --- title: Get Sim N --- # Get Sim N ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The `GetSimN` request retrieves the **top-N most similar vectors** to a given query vector from a specified store. This is the core similarity search operation in Ahnlich DB, allowing you to find nearest neighbors by vector distance. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::db::DbClient; use ahnlich_types::{ algorithm::algorithms::Algorithm, db::query::GetSimN, keyval::StoreKey, }; use tokio; #[tokio::main] async fn main() -> Result<(), Box> { // connect to server let addr = "http://127.0.0.1:1369"; // adjust to your server address let db_client = DbClient::new(addr.to_string()).await?; // prepare parameters let params = GetSimN { store: "Main".to_string(), schema: Some("analytics".to_string()), search_input: Some(StoreKey { key: vec![1.0, 2.0, 3.0] }), closest_n: 2, algorithm: Algorithm::EuclideanDistance as i32, condition: None, }; // call get_sim_n let response = db_client.get_sim_n(params, None).await?; println!("Response: {:?}", response); Ok(()) } ```
## Parameters * `params: GetSimN` – Defines the similarity search query. This includes: * `store` – The name of the target store. * `vector` – The query vector (`Vec`) used to compute similarity. * `n` – The number of nearest neighbors to return. * `predicate` (optional) – A filter expression applied to metadata before similarity is calculated. * `tracing_id: Option` – Optional identifier for distributed tracing. ## Returns * `Ok(GetSimNResult)` – A structured result containing: * `matches` – A ranked list of vectors with associated similarity scores, keys, and metadata. * `count` – The number of results returned (up to `n`). * `Err(AhnlichError)` – Possible error cases: * Store not found. * Query vector does not match store dimensionality. * Invalid or malformed predicate filter. * Transport or server-side errors. ## Behavior * The search is **approximate or exact** depending on the index configuration of the store. * Returned results are sorted in descending order of similarity (most similar first). * If a predicate filter is provided, only vectors satisfying the filter are considered for similarity ranking. * Results include both the vector key and any metadata if available. --- # FILE: client-libraries/rust/request-db/get-store.md --- title: Get Store --- # Get Store ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. Retrieves detailed information about a specific store by name. Returns metadata including dimensions, size, and configured indices. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::db::DbClient; #[tokio::main] async fn main() -> Result<(), Box> { let db_client = DbClient::new("127.0.0.1:1369".to_string()).await?; let tracing_id: Option = None; let store_info = db_client .get_store_with_schema( "my_store".to_string(), Some("analytics".to_string()), tracing_id, ) .await?; println!("Store name: {}", store_info.name); println!("Number of entries: {}", store_info.len); println!("Size in bytes: {}", store_info.size_in_bytes); println!("Dimension: {}", store_info.dimension); println!("Predicate indices: {:?}", store_info.predicate_indices); println!("Non-linear indices: {:?}", store_info.non_linear_indices); Ok(()) } ```
## Parameters * `store: String` — The name of the store to retrieve. * `schema: Option` — Schema containing the store. Omit it to use `public`. * `tracing_id: Option` — Optional trace context for observability. ## Returns * `StoreInfo` — Detailed information about the store. * `AhnlichError` — If the store does not exist or the request fails. ## StoreInfo Fields | Field | Type | Description | |-------|------|-------------| | `name` | `String` | Store name | | `len` | `u64` | Number of entries in the store | | `size_in_bytes` | `u64` | Total size of the store in bytes | | `dimension` | `u32` | Vector dimension | | `predicate_indices` | `Vec` | List of indexed predicate keys | | `non_linear_indices` | `Vec` | List of non-linear algorithm indices | ## Behavior * Sends a request to retrieve store metadata by name. * Returns an error if the store does not exist. * Useful for inspecting store configuration before operations. ## Notes - Use `list_stores` to get information about stores in a schema - The `size_in_bytes` field is useful for monitoring memory usage --- # FILE: client-libraries/rust/request-db/info-server.md --- title: Info Server --- # Info Server Retrieves server information for the connected Ahnlich service over the current gRPC channel. Use this to confirm service identity and inspect basic runtime details before issuing data operations or when troubleshooting. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::db::DbClient; #[tokio::main] async fn main() -> Result<(), Box> { // Connect to your ahnlich-db server let db_client = DbClient::new("127.0.0.1:1369".to_string()).await?; let tracing_id: Option = None; // Call info_server and print the result let info = db_client.info_server(tracing_id).await?; println!("Server info: {:?}", info); Ok(()) } ```
## Parameters * `tracing_id: Option` — Optional tracing context propagated with the request. ## Behavior * Sends a lightweight, read-only RPC; it has no side effects. * Tracing metadata is attached when `tracing_id` is provided. * Expects the response to include server info; the inner payload is unwrapped. --- # FILE: client-libraries/rust/request-db/list-connected-clients.md --- title: List Connected Clients --- # List Connected Clients Retrieves a list of all clients currently connected to the database service. This is useful for monitoring active sessions, debugging connectivity issues, and gaining visibility into which applications are using the DB. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::db::DbClient; use ahnlich_client_rs::error::AhnlichError; #[tokio::main] async fn main() -> Result<(), AhnlichError> { // Set the DB server address let addr = "127.0.0.1:1369".to_string(); // Initialize the DB client let db_client = DbClient::new(addr).await?; // Fetch the list of connected clients let clients = db_client.list_clients(None).await?; // Print the clients in a readable way println!("Connected clients:"); for (i, client) in clients.clients.iter().enumerate() { println!("{}. {:?}", i + 1, client); } Ok(()) } ```
## Parameters * `tracing_id: Option` — Optional trace context for observability. ## Returns * `ClientList` — A structured list of connected clients with their identifiers and metadata. * `AhnlichError` — If the request fails due to communication or server-side errors. ## Behavior * Constructs a `ListClients` request. * Adds optional tracing information. * Queries the DB service for all currently connected clients and returns the result. --- # FILE: client-libraries/rust/request-db/list-stores.md --- title: List Stores --- # List Stores ## Schema `ListStores` accepts an optional `schema` field. When it is omitted, the server lists stores in `public` only; it does not list stores across every schema. Set `schema` to list stores in another schema. Returns the list of vector stores registered in the connected Ahnlich DB service. This request is typically used to discover available stores before performing store-scoped operations such as creating, dropping, or inserting vectors. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::db::DbClient; #[tokio::main] async fn main() -> Result<(), Box> { // Connect to your running ahnlich-db instance let db_client = DbClient::new("127.0.0.1:1369".to_string()).await?; let tracing_id: Option = None; // Call list_stores and print the result let stores = db_client .list_stores_with_schema(Some("analytics".to_string()), tracing_id) .await?; println!("Stores: {:?}", stores); Ok(()) } ```
## Parameters * `tracing_id: Option` – Optional tracing context propagated with the request. ## Returns * `Ok(StoreList)` – Contains metadata for each store available on the server. Each `StoreInfo` includes: * `name` – The store identifier. * `len` – Number of entries in the store. * `size_in_bytes` – Total memory footprint of the store. * `non_linear_indices` – List of non-linear index configurations (HNSW with full config parameters) active on the store. Empty if no non-linear indices are configured. * `Err(AhnlichError)` – Returned when the request cannot be completed (e.g., transport or server error). ## Behavior * Executes a read-only RPC with no side effects. * Responses are deterministic: the server returns all currently known stores. * If no stores exist, the response will contain an empty list. * For stores with HNSW indices, the returned configuration includes: distance metric, ef_construction, maximum_connections, maximum_connections_zero, extend_candidates, and keep_pruned_connections. --- # FILE: client-libraries/rust/request-db/ping.md --- title: Ping --- # Ping Checks connectivity with the Ahnlich service and verifies that the server can accept requests over the current gRPC channel. Useful for health checks, readiness probes, or establishing a baseline before issuing data operations. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::db::DbClient; #[tokio::main] async fn main() -> Result<(), Box> { // Connect to your running ahnlich-db instance let db_client = DbClient::new("127.0.0.1:1369".to_string()).await?; // Optional tracing ID (can be None if you don’t use tracing) let tracing_id: Option = None; // Call ping and print the response let res = db_client.ping(tracing_id).await?; println!("Ping response: {:?}", res); Ok(()) } ```
## Parameters * `tracing_id: Option` – Optional tracing context to propagate to the server. * Pass `Some(String)` to enable distributed tracing for this call. * Pass `None` to omit tracing metadata. ## Returns * `Ok(Pong)` – A server “pong” response indicating the service is reachable. * `Err(AhnlichError)` – The request could not be completed (e.g., transport error, server error). ## Behavior * Executes a lightweight RPC with no side effects. * Safe to call at startup, during liveness/readiness checks, or before building pipelines. * Works identically on both **DB** and **AI** clients. --- # FILE: client-libraries/rust/request-db/request-db.md --- title: request-db --- # Request DB The **Request DB** provides a set of operations for managing vector stores and interacting with their contents. These APIs wrap low-level gRPC calls in a convenient Rust interface, handling request construction, tracing propagation, and response parsing. Together, they enable you to create, query, update, and delete records within the database in a structured and reliable way. Each operation follows the same execution pattern: * **Request preparation** — Input parameters are wrapped in a `tonic::Request` object. * **Execution** — The client forwards the request to the DB service. * **Response handling** — The response is unwrapped and returned in a typed result. ## Capabilities With Request DB, you can: * **Manage stores** — Create, list, and drop vector stores. * **Insert and update data** — Use `set` to add or modify records. * **Query data** — Fetch by key, by predicate, or by similarity (`get_key`, `get_pred`, `get_sim_n`). * **Delete data** — Remove records by key or predicate (`del_key`, `del_pred`). * **Index management** — Create and drop indexes for predicate and algorithm-based queries. * **Server & client metadata** — Retrieve cluster information (`info_server`, `list_clients`). ## Behavior All Request DB operations are designed to: * **Ensure consistency** — Calls execute atomically on the server side. * **Support idempotency** — Repeated calls with the same parameters will yield consistent results. * **Handle concurrency** — Multiple clients can safely read and write without corrupting data. * **Propagate observability** — Optional tracing IDs allow for full request tracing in distributed environments. Below are the operations for managing vector stores, storing and retrieving vectors, performing similarity queries, and handling indexes in the Ahnlich database. * [Ping](/docs/client-libraries/rust/request-db/ping) * [Info Server](/docs/client-libraries/rust/request-db/info-server) * [List Stores](/docs/client-libraries/rust/request-db/list-stores) * [Create Store](/docs/client-libraries/rust/request-db/create-store) * [Set](/docs/client-libraries/rust/request-db/set) * [Upsert](/docs/client-libraries/rust/request-db/upsert) * [Get Sim N](/docs/client-libraries/rust/request-db/get-simn) * [Get Key](/docs/client-libraries/rust/request-db/get-key) * [Get by Predicate](/docs/client-libraries/rust/request-db/get-by-predicate) * [Create Predicate Index](/docs/client-libraries/rust/request-db/create-predicate-index) * [Drop Predicate Index](/docs/client-libraries/rust/request-db/drop-predicate-index) * [Delete Key](/docs/client-libraries/rust/request-db/delete-key) * [Drop Store](/docs/client-libraries/rust/request-db/drop-store) * [List Connected Clients](/docs/client-libraries/rust/request-db/list-connected-clients) * [Create Non-Linear Algorithm Index](/docs/client-libraries/rust/request-db/create-non-linear-algx) * [Drop Non-Linear Algorithm Index](/docs/client-libraries/rust/request-db/drop-non-linear-algx) * [Delete Predicate](/docs/client-libraries/rust/request-db/delete-predicate) --- # FILE: client-libraries/rust/request-db/set.md --- title: Set --- # Set ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The `Set` request inserts or updates a vector in a given store. Each vector is stored alongside optional metadata and a unique key. If a key already exists in the store, calling `Set` with the same key will overwrite the existing vector and metadata. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::db::DbClient; use ahnlich_types::{ db::query::Set, keyval::{DbStoreEntry, StoreKey, StoreValue}, metadata::{MetadataValue, metadata_value::Value}, }; use std::collections::HashMap; #[tokio::main] async fn main() -> Result<(), Box> { // Connect to DB server let db_client = DbClient::new("127.0.0.1:1369".to_string()).await?; let tracing_id: Option = None; // Construct inputs for the "set" let inputs = vec![DbStoreEntry { key: Some(StoreKey { key: vec![0.5, 0.2, 0.9], // must match store dimension }), value: Some(StoreValue { value: HashMap::from_iter([( "role".into(), MetadataValue { value: Some(Value::RawString("admin".into())), }, )]), }), }]; let params = Set { store: "Main".to_string(), // store must already exist schema: Some("analytics".to_string()), inputs, }; // Call set match db_client.set(params, tracing_id).await { Ok(result) => { println!("Set operation result: {:?}", result); } Err(err) => { eprintln!("Error inserting vector: {:?}", err); } } Ok(()) } ```
## Parameters * `params: Set` – Defines the data to be stored. This includes: * `store` – The name of the target store. * `key` – A unique identifier for the vector. * `vector` – The vector data (as `Vec`). * `metadata` – Optional key–value pairs to annotate the vector (e.g., labels, categories). * `tracing_id: Option` – An optional tracing identifier for distributed observability. ## Returns * `Ok(SetResult)` – Contains information about the completed operation, such as success status and assigned identifiers. * `Err(AhnlichError)` – Returned if the operation fails. Common error cases include: * Target store does not exist. * Provided vector does not match the dimensionality of the store. * Invalid key or malformed metadata. * Transport or server-side errors. ## Behavior * `Set` is an upsert operation: it will insert if the key does not exist, or update if it already exists. * Vectors written with `Set` are immediately available for similarity searches (e.g., `Get Sim N`) or retrieval by key. * Metadata can be leveraged in predicate-based queries if a predicate index is defined. * Ensure consistent key usage to avoid unintended overwrites. --- # FILE: client-libraries/rust/request-db/upsert.md --- title: Upsert --- # Upsert ## Schema This request accepts an optional `schema` field. When it is omitted, the server uses the `public` schema. Set `schema` to target a store in another schema. The `Upsert` request updates a single entry that matches a predicate condition. It errors if the predicate matches 0 or multiple entries. ## Source Code Example
Click to expand ```rust use ahnlich_client_rs::db::DbClient; use ahnlich_types::{ db::query::Upsert, keyval::{StoreKey, StoreValue}, metadata::{MetadataValue, metadata_value::Value}, predicates::{Predicate, PredicateCondition, predicate_condition::Kind, predicate::Kind as PredKind}, }; use std::collections::HashMap; #[tokio::main] async fn main() -> Result<(), Box> { // Connect to DB server let db_client = DbClient::new("127.0.0.1:1369".to_string()).await?; let tracing_id: Option = None; // Construct predicate condition let condition = PredicateCondition { kind: Some(Kind::Value(Predicate { kind: Some(PredKind::Equals(ahnlich_types::predicates::Equals { key: "id".to_string(), value: Some(MetadataValue { value: Some(Value::RawString("123".to_string())), }), })), })), }; // New metadata to merge/replace let new_value = Some(StoreValue { value: HashMap::from_iter([( "status".into(), MetadataValue { value: Some(Value::RawString("published".into())), }, )]), }); let params = Upsert { store: "my_store".to_string(), schema: Some("analytics".to_string()), condition: Some(condition), new_key: None, // Optional: new vector new_value, merge_metadata: true, // Merge instead of replace }; // Call upsert match db_client.upsert(params, tracing_id).await { Ok(result) => { println!("Upsert result: {:?}", result); } Err(err) => { eprintln!("Error in upsert: {:?}", err); } } Ok(()) } ```
## Parameters * `params: Upsert` – Defines the update operation: * `store` – The name of the target store. * `condition` – Predicate that must match exactly one entry. * `new_key` (optional) – New vector to replace existing key. * `new_value` (optional) – Metadata to update. * `merge_metadata` – If true, merges new metadata into existing. If false, replaces entirely. * `schema` (optional) – Schema namespace (defaults to "public"). * `tracing_id: Option` – An optional tracing identifier. ## Returns * `Ok(Set)` – Contains upsert counts (inserted and updated). * `Err(AhnlichError)` – Returned if: * Predicate matches 0 or multiple entries. * Store does not exist. * Invalid vector dimension. * Transport or server-side errors. ## Behavior * Predicate must match exactly one entry (errors on 0 or multiple matches). * `merge_metadata: true` merges new metadata into existing fields. * `merge_metadata: false` replaces metadata entirely. * Updated entries are immediately available for queries. --- # FILE: client-libraries/rust/rust-specific-resources.md --- title: Rust Specific Resources --- # Quickstart - Setup Guide Before installing ahnlich_client_rs, you need Rust and Cargo set up on your system. ## Windows Install Visual C++ Build Tools (needed by many crates) ``` winget install --id Microsoft.VisualStudio.2022.BuildTools -e --source winget ``` In the installer UI, check "Desktop development with C++" and finish Install Rust (rustup + stable toolchain) ``` winget install --id Rustlang.Rustup -e ``` #Open a NEW terminal so PATH updates, then verify ``` rustc --version # check Rust version ``` ``` cargo --version # check Cargo version ``` ## macOS Install compiler & build tools ``` xcode-select --install # one-time setup ``` Install Rust (rustup + stable toolchain) ``` curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` Load Cargo into the shell (new terminals do this automatically) ``` source "$HOME/.cargo/env" ``` Verify installation ``` rustc --version ``` ``` cargo --version ``` ## Linux (Debian/Ubuntu example) Install prerequisites ``` sudo apt update ``` ``` sudo apt install -y build-essential pkg-config libssl-dev curl ``` Install Rust (rustup + stable toolchain) ``` curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` Load Cargo into the shell ``` source "$HOME/.cargo/env" ``` Verify installation ``` rustc --version ``` ``` cargo --version ``` ## Quick Sanity Check (All Platforms) Create and run a starter app ``` cargo new hello-rust ``` ``` cd hello-rust ``` ``` cargo run # should print "Hello, world!" ``` ## Install the SDK ``` cargo add ahnlich_client_rs ``` ## Whats Included in the SDK The ahnlich_client_rs crate provides an idiomatic Rust client for Ahnlich’s Vector Database (DB) and AI services. It enables developers to integrate similarity search and AI-powered embedding workflows directly into Rust applications. ### Capabilities * **DB Client** * Store vectors (`StoreKey: Vec`) and metadata (`StoreValue: HashMap`). * Query for nearest neighbors with filtering (`Predicates`). * Manage stores (create, list, delete). * **AI Client** * Generate embeddings from raw inputs (text, JSON, or other supported formats). * Interpret embeddings for similarity, clustering, or semantic tasks. * Designed to complement the DB client by producing vectors ready for indexing and search. ### Clients This crate exposes two primary client modules for interacting with Ahnlich services: * `db` — for interacting with the **Vector Database (DB)**. * `ai` — for interacting with the **AI Service**. Both clients support: * **Direct method calls** for simple operations. * **Pipeline builders** for batching multiple commands and receiving results in sequence. ### DB Client The DB Client is used to manage vector stores and perform similarity queries. It provides methods for: * Creating and deleting stores. * Storing vectors and associated metadata. * Querying nearest neighbors with filters and predicates. * Running operations in pipelines for higher throughput. #### Source Code Example: DB Client ```rust use ahnlich_client_rs::db::DbClient; #[tokio::main] async fn main() -> Result<(), Box> { let db_client = DbClient::new("127.0.0.1:1369".to_string()).await?; let tracing_id: Option = None; db_client.ping(tracing_id).await?; Ok(()) } ``` ### AI Client The **AI Client** is used to generate and interpret embeddings. It provides methods for: * Creating embeddings from raw input (e.g., text, JSON). * Sending embedding requests in pipelines for batch workloads. * Integrating directly with the DB Client by producing vectors ready for storage. #### Source Code Example: AI Client ```rust use ahnlich_client_rs::ai::AiClient; #[tokio::main] async fn main() -> Result<(), Box> { let ai_client = AiClient::new("127.0.0.1:1370".to_string()).await?; let tracing_id: Option = None; ai_client.ping(tracing_id).await?; Ok(()) } ``` ## Pipelines Pipelines enable multiple ordered operations to be issued in a batch. This ensures: * **Sequential execution** — operations are applied in the order they are added. * **Consistent read-after-write semantics** — queries can safely depend on previous writes. * **Reduced overhead** — fewer gRPC round-trips compared to issuing requests individually. Both the **DB Client** and **AI Client** provide pipeline builders, making it possible to group multiple commands into a single request stream. #### Source Code Example: DB Pipeline ```rust use ahnlich_client_rs::db::DbClient; #[tokio::main] async fn main() -> Result<(), Box> { let db_client = DbClient::new("127.0.0.1:1369".to_string()).await?; let tracing_id: Option = None; let mut pipeline = db_client.pipeline(tracing_id); pipeline.list_clients(); pipeline.list_stores(); let responses = pipeline.exec().await?; Ok(()) } ``` In this example: * A new pipeline is created from the `DbClient`. * Two operations are enqueued: * `list_clients()` * `list_stores()` * `exec()` executes the pipeline, returning the responses in the same order the operations were added. --- # FILE: client-libraries/rust/rust.md --- title: Rust sidebar_position: 20 --- # Rust SDK Build Ahnlich Applications with the Rust SDK – Vector Storage, Search, and AI tooling. ## [Rust-Specific Resources](/docs/client-libraries/rust/rust-specific-resources) * Build Ahnlich Applications with the Rust SDK * Ahnlich Rust Technical Resources * Rust SDK Quickstart – Setup Guide ## Overview ### Clients * `db` — Vector Database client * `ai` — AI Service client ### Pipelines * Multiple ordered operations in batch * Sequential execution, consistent reads * Reduced gRPC round-trips ## What’s Included in the SDK ### Capabilities #### DB Client * Store vectors and metadata * Query nearest neighbors with filters * Manage stores #### AI Client * Generate embeddings from raw inputs * Interpret embeddings for similarity/clustering * Complement DB client ### Typical Workflow * AI Service → Generate embeddings * DB Service → Store embeddings * Response → Ordered results ## [Request – DB (Detailed)](/docs/client-libraries/rust/request-db) ### [Ping](/docs/client-libraries/rust/request-db/ping) * Description * Source Code Example * Parameters * Returns * Behavior ### [Info Server](/docs/client-libraries/rust/request-db/info-server) * Description * Source Code Example * Parameters * Behavior * When to use ### [List Stores](/docs/client-libraries/rust/request-db/list-stores) * Description * Source Code Example * Parameters * Returns * Behavior ### [Create Store](/docs/client-libraries/rust/request-db/create-store) * Description * Source Code Example * Parameters * Returns * Behavior ### [Set](/docs/client-libraries/rust/request-db/set) * Description * Source Code Example * Parameters * Returns * Behavior ### [Upsert](/docs/client-libraries/rust/request-db/upsert) * Description * Source Code Example * Parameters * Returns * Behavior ### [Get Sim N](/docs/client-libraries/rust/request-db/get-simn) * Description * Source Code Example * Parameters * Returns * Behavior ### [Get Key](/docs/client-libraries/rust/request-db/get-key) * Description * Source Code Example * Parameters * Returns * Behavior ### [Get by Predicate](/docs/client-libraries/rust/request-db/get-by-predicate) * Description * Source Code Example * Parameters * Returns * Behavior ### [Create Predicate Index](/docs/client-libraries/rust/request-db/create-predicate-index) * Description * Source Code Example * Parameters * Returns * Behavior ### [Drop Predicate Index](/docs/client-libraries/rust/request-db/drop-predicate-index) * Description * Source Code Example * Parameters * Returns * Behavior ### [Delete Key](/docs/client-libraries/rust/request-db/delete-key) * Description * Source Code Example * Parameters * Returns * Behavior ### [Drop Store](/docs/client-libraries/rust/request-db/drop-store) * Description * Source Code Example * Parameters * Returns * Behavior ### [List Connected Clients](/docs/client-libraries/rust/request-db/list-connected-clients) * Description * Source Code Example * Parameters * Returns * Behavior ### [Create Non-Linear Algorithm Index](/docs/client-libraries/rust/request-db/create-non-linear-algx) * Description * Source Code Example * Parameters * Returns * Behavior ### [Drop Non-Linear Algorithm Index](/docs/client-libraries/rust/request-db/drop-non-linear-algx) * Description * Source Code Example * Parameters * Returns * Behavior ### [Delete Predicate](/docs/client-libraries/rust/request-db/delete-predicate) * Description * Source Code Example * Parameters * Returns * Behavior ## [Request – AI (Detailed)](/docs/client-libraries/rust/request-ai) ### [Ping](/docs/client-libraries/rust/request-ai/ping) * Description * Source Code Example * Parameters * Returns * Behavior ### [Info Server](/docs/client-libraries/rust/request-ai/info-server) * Description * Source Code Example * Parameters * Returns * Behavior ### [List Stores](/docs/client-libraries/rust/request-ai/list-stores) * Description * Source Code Example * Parameters * Returns * Behavior ### [Create Store](/docs/client-libraries/rust/request-ai/create-store) * Description * Source Code Example * Parameters * Returns * Behavior ### [Set](/docs/client-libraries/rust/request-ai/set) * Description * Source Code Example * Parameters * Returns * Behavior ### [Upsert](/docs/client-libraries/rust/request-ai/upsert) * Description * Source Code Example * Parameters * Returns * Behavior ### [Get Sim N](/docs/client-libraries/rust/request-ai/get-simn) * Description * Source Code Example * Parameters * Returns * Behavior ### [Get Key](/docs/client-libraries/rust/request-ai/get-key) * Description * Source Code Example * Parameters * Returns * Behavior ### [Get by Predicate](/docs/client-libraries/rust/request-ai/get-by-predicate) * Description * Source Code Example * Parameters * Returns * Behavior ### [Create Predicate Index](/docs/client-libraries/rust/request-ai/create-predicate-index) * Description * Source Code Example * Parameters * Returns * Behavior ### [Drop Predicate Index](/docs/client-libraries/rust/request-ai/drop-predicate-index) * Description * Source Code Example * Parameters * Returns * Behavior ### [Delete Key](/docs/client-libraries/rust/request-ai/delete-key) * Description * Source Code Example * Parameters * Returns * Behavior ### [Drop Store](/docs/client-libraries/rust/request-ai/drop-store) * Description * Source Code Example * Parameters * Returns * Behavior ### [List Connected Clients](/docs/client-libraries/rust/request-ai/list-connected-clients) * Description * Source Code Example * Returns * Behavior ### [Create Non-Linear Algorithm Index](/docs/client-libraries/rust/request-ai/create-non-linear-algx) * Description * Source Code Example * Parameters * Returns * Behavior ### [Drop Non-Linear Algorithm Index](/docs/client-libraries/rust/request-ai/drop-non-linear-algx) * Description * Source Code Example * Parameters * Returns * Behavior ### [New](/docs/client-libraries/rust/request-ai/new) * Description * Source Code Example * Parameters * Returns * Behavior ### [Purge Stores](/docs/client-libraries/rust/request-ai/purge-stores) * Description * Source Code Example * Returns * Behavior ## [Pipeline](/docs/client-libraries/rust/pipeline) * Description * Source Code Example * Parameters * Returns * Behavior ## [Types & Utilities](/docs/client-libraries/rust/types-and-utilities) * Description * Usage * Details ## [Testing](/docs/client-libraries/rust/testing) * Description * Key Test Scenarios * Purpose ## [Distributed Tracing](/docs/client-libraries/rust/distributed-tracing) * Description * Usage * Benefits --- # FILE: client-libraries/rust/testing.md --- title: Testing --- # Testing The crate includes example tests in the repository that demonstrate practical usage of both the DB and AI clients. These tests provide guidance on real-world scenarios and validate expected behavior. ## Key Test Scenarios * Creating and managing stores. * Setting and retrieving embeddings and metadata. * Listing connected clients and available stores. * Predicate-based queries for filtered retrieval. * Testing pipelines with multiple requests executed sequentially. * Validating behavior with image embeddings and structured metadata. ## Purpose These tests ensure reliability and consistency for operations in both DB and AI pipelines and serve as reference implementations for developers integrating the Ahnlich clients into their workflows. --- # FILE: client-libraries/rust/types-and-utilities.md --- title: Type & Utilities --- # Type & Utilities The crate re-exports commonly used parameter and result types through the `prelude` module, simplifying imports across your Rust project. This allows you to access frequently used structures, enums, and request builders without importing them individually from multiple modules. ## Details * All core types for AI and DB operations are accessible via the prelude. * For complete type definitions, including structs, enums, and request builders, refer to the **`ahnlich_types` crate**. * This abstraction improves readability and reduces boilerplate in your Rust code when working with Ahnlich clients. --- # FILE: client-libraries/schema-support.md --- title: Schema Support --- # Schema Support Generated clients expose schema support directly from the protobuf API. For DB and AI, every request that targets a store by name accepts an optional `schema` field. This includes store lifecycle commands, data commands, retrieval commands such as `GetSimN`, and index commands. If `schema` is omitted, the server uses `public`. `ListStores` without a schema returns only stores in `public`; it does not list stores from every schema. Both DB and AI clients also include `DropSchema`, which removes a non-public schema and all stores inside it. The `public` schema cannot be dropped. ## Go ```go schema := "analytics" _, err := client.CreateStore(ctx, &dbquery.CreateStore{ Store: "articles", Dimension: 384, Schema: &schema, }) _, err = client.Set(ctx, &dbquery.Set{ Store: "articles", Inputs: entries, Schema: &schema, }) matches, err := client.GetSimN(ctx, &dbquery.GetSimN{ Store: "articles", SearchInput: queryVector, ClosestN: 5, Algorithm: algorithm.Algorithm_CosineSimilarity, Schema: &schema, }) stores, err := client.ListStores(ctx, &dbquery.ListStores{Schema: &schema}) _, err = client.DropSchema(ctx, &dbquery.DropSchema{Schema: schema}) ``` ## Python ```py schema = "analytics" await client.create_store( db_query.CreateStore(store="articles", dimension=384, schema=schema) ) await client.set(db_query.Set(store="articles", inputs=entries, schema=schema)) matches = await client.get_sim_n( db_query.GetSimN( store="articles", search_input=query_vector, closest_n=5, algorithm=db_algorithm.Algorithm.CosineSimilarity, schema=schema, ) ) stores = await client.list_stores(db_query.ListStores(schema=schema)) await client.drop_schema(db_query.DropSchema(schema=schema)) ``` ## Node.js ```ts const schema = "analytics"; await client.createStore( new CreateStore({ store: "articles", dimension: 384, schema }), ); await client.set(new Set({ store: "articles", inputs: entries, schema })); const matches = await client.getSimN( new GetSimN({ store: "articles", searchInput: queryVector, closestN: BigInt(5), algorithm: Algorithm.COSINE_SIMILARITY, schema, }), ); const stores = await client.listStores(new ListStores({ schema })); await client.dropSchema(new DropSchema({ schema })); ``` --- # FILE: community.md --- title: Community sidebar_position: 70 --- # Community Join the Ahnlich community to connect with other users, ask questions, share ideas, and stay updated on the latest developments! ## 💬 WhatsApp Community Connect with other Ahnlich users in real-time on our WhatsApp community: [**Join WhatsApp Community**](https://chat.whatsapp.com/E4CP7VZ1lNH9dJUxpsZVvD) Get help, share your use cases, discuss best practices, and stay in touch with the community. --- ## 💻 GitHub Discussions For deeper technical discussions, feature requests, and Q&A: [**GitHub Discussions**](https://github.com/deven96/ahnlich/discussions) - Ask questions and get help from maintainers and community members - Share feature ideas and feedback - Discuss architecture and implementation details - Showcase your projects built with Ahnlich --- ## 🐛 Report Issues Found a bug or have a feature request? Open an issue on GitHub: [**GitHub Issues**](https://github.com/deven96/ahnlich/issues) --- ## 🤝 Contributing Want to contribute to Ahnlich? Check out our contributing guide: [**Contributing Guide**](https://github.com/deven96/ahnlich/blob/main/CONTRIBUTING.md) We welcome contributions of all kinds - code, documentation, examples, and more! --- # FILE: components/ahnlich-ai/advanced.md --- title: Advanced sidebar_position: 30 --- # Advanced Unlike Ahnlich DB, which is concerned with similarity algorithms and indexing, **Ahnlich AI focuses on embedding generation**. The service introduces **model-aware stores**, where you define the embedding models used for both data insertion (indexing) and querying. This abstraction lets developers work directly with raw inputs (text or images) while the AI proxy handles embedding generation. ## Supported Models Ahnlich AI includes several pre-trained models that can be configured depending on your workload. These cover both **text embeddings** and **image embeddings**: | Model Name | String Name | Type | Max Input | Embedding Dim | Description | | ----- | ----- | ----- | ----- | ----- | ----- | | ALL\_MINI\_LM\_L6\_V2 | all-minilm-l6-v2 | Text | 256 tokens | 384 | Lightweight sentence transformer. Fast and memory-efficient, ideal for semantic similarity in applications like FAQ search or chatbots. | | ALL\_MINI\_LM\_L12\_V2 | all-minilm-l12-v2 | Text | 256 tokens | 384 | Larger variant of MiniLM. Higher accuracy for nuanced text similarity tasks, but with increased compute requirements. | | BGE\_BASE\_EN\_V15 | bge-base-en-v1.5 | Text | 512 tokens | 768 | Base version of the BGE (English v1.5) model. Balanced performance and speed, suitable for production-scale applications. | | BGE\_LARGE\_EN\_V15 | bge-large-en-v1.5 | Text | 512 tokens | 1024 | High-accuracy embedding model for semantic search and retrieval. Best choice when precision is more important than latency. | | JINA\_CODE\_V2 | jina-embeddings-v2-base-code | Text (Code) | 8192 tokens | 768 | Specialized code embedding model supporting 30+ programming languages. Optimized for code search, documentation lookup, and code-to-text retrieval. | | RESNET50 | resnet-50 | Image | 224x224 px | 2048 | Convolutional Neural Network (CNN) for extracting embeddings from images. Useful for content-based image retrieval and clustering. | | CLIP\_VIT\_B32\_IMAGE | clip-vit-b32-image | Image | 224x224 px | 512 | Vision Transformer encoder from the CLIP model. Produces embeddings aligned with its paired text encoder for multimodal tasks. | | CLIP\_VIT\_B32\_TEXT | clip-vit-b32-text | Text | 77 tokens | 512 | Text encoder from CLIP. Designed to map textual inputs into the same space as CLIP image embeddings for text-to-image or image-to-text search. | | BUFFALO\_L | buffalo-l | Image (Face) | 640x640 px | 512 | Face detection and recognition model. Detects faces in images and generates embeddings for each detected face. **Non-commercial use only.** | | SFACE\_YUNET | sface-yunet | Image (Face) | 640x640 px | 128 | Lightweight face detection (YuNet) + recognition (SFace) pipeline. Apache 2.0 / MIT licensed - commercially usable. | | CLAP\_AUDIO | clap-audio | Audio | 10 sec max | 512 | Audio encoder from the CLAP model. Produces embeddings from audio inputs for audio similarity search and audio-to-text retrieval. | | CLAP\_TEXT | clap-text | Text | 512 tokens | 512 | Text encoder from the CLAP model. Maps textual descriptions into the same embedding space as CLAP audio embeddings for text-to-audio search. | ## Model Constraints ### Audio Models (CLAP) | Constraint | Value | Notes | | ----- | ----- | ----- | | Max duration | 10 seconds | Longer clips will error with `AudioTooLongError` | | Sample rate | 48 kHz | Audio is automatically resampled | | Max samples | 480,000 | 48,000 Hz × 10 seconds | | Preprocessing | Required | `NoPreprocessing` not supported - always use `ModelPreprocessing` | ### Face Models (Buffalo\_L, SFace+YuNet) | Constraint | Value | Notes | | ----- | ----- | ----- | | Input size | 640x640 px | Images are resized internally | | Face alignment | 112x112 px | Standard ArcFace alignment | | Embedding mode | OneToMany | Returns one embedding per detected face | | Preprocessing | Required | `NoPreprocessing` not supported | | Query constraint | Single face | Query images must contain exactly 1 face | ### Cross-Modal Compatibility | Model Pair | Shared Dim | Use Case | | ----- | ----- | ----- | | `clip-vit-b32-text` + `clip-vit-b32-image` | 512 | Text-to-image / image-to-text search | | `clap-text` + `clap-audio` | 512 | Text-to-audio / audio-to-text search | ## Supported Input Types | Input Type | Description | | ----- | ----- | | RAW\_STRING | Accepts natural text (sentences, paragraphs). Transformed into embeddings via a selected text-based model. | | IMAGE | Accepts image files as input. Converted into embeddings via a selected image-based model (e.g., ResNet or CLIP). | | AUDIO | Accepts audio data as input. Converted into embeddings via an audio-based model (e.g., CLAP Audio). | ## Example – Creating a Model-Aware Store ``` CREATESTORE my_store QUERYMODEL all-minilm-l6-v2 INDEXMODEL all-minilm-l6-v2 ``` - **index_model** - defines how inserted data is embedded before being stored in Ahnlich DB. - **query_model** - defines how queries are embedded at search time. - Both models must output embeddings of the **same dimensionality** to ensure compatibility. ## Choosing the Right Model | Model | Best Use Case | | ----- | ----- | | MiniLM (L6/L12) | Fast, efficient semantic similarity (FAQs, chatbots). | | BGE (Base/Large) | High semantic accuracy for production-scale applications. | | Jina Code V2 | Code search, documentation retrieval, and semantic code similarity across 30+ languages. | | ResNet50 | Image-to-image similarity and clustering. | | CLIP (Text+Image) | Multimodal retrieval (text-to-image / image-to-text search). | | Buffalo\_L | Face detection and recognition in images (e.g., group photos, ID verification). | | SFace+YuNet | Lightweight face detection and recognition (e.g., real-time face matching). | | CLAP (Audio+Text) | Audio similarity search and text-to-audio retrieval. | ## Code Search with Jina Code V2 The Jina Code V2 model is specifically designed for semantic code search across 30+ programming languages. It excels at: - Finding code snippets by natural language description - Searching for similar code patterns - Linking documentation to code - Code-to-code similarity search ### Creating a Code Search Store ``` CREATESTORE code_repo QUERYMODEL jina-embeddings-v2-base-code INDEXMODEL jina-embeddings-v2-base-code ``` ### Indexing Code Snippets **Rust**: ```rust use ahnlich_client_rs::prelude::*; let code_snippets = vec![ StoreInput::RawString("fn fibonacci(n: u32) -> u32 { if n <= 1 { n } else { fibonacci(n-1) + fibonacci(n-2) } }".to_string()), StoreInput::RawString("def binary_search(arr, target): left, right = 0, len(arr) - 1; while left <= right: ...".to_string()), StoreInput::RawString("function quickSort(arr) { if (arr.length <= 1) return arr; const pivot = arr[0]; ...".to_string()), ]; let metadata = vec![ StoreValue::from([("language", "rust"), ("file", "algorithms.rs")]), StoreValue::from([("language", "python"), ("file", "search.py")]), StoreValue::from([("language", "javascript"), ("file", "sort.js")]), ]; client.set( "code_repo".to_string(), code_snippets, PreprocessAction::ModelPreprocessing, None, HashMap::new(), ).await?; ``` **Python**: ```python from ahnlich_client_py import AhnlichAIClient from ahnlich_client_py.ai_query import Set, StoreInput code_snippets = [ StoreInput(raw_string="fn fibonacci(n: u32) -> u32 { if n <= 1 { n } else { fibonacci(n-1) + fibonacci(n-2) } }"), StoreInput(raw_string="def binary_search(arr, target): left, right = 0, len(arr) - 1; while left <= right: ..."), StoreInput(raw_string="function quickSort(arr) { if (arr.length <= 1) return arr; const pivot = arr[0]; ..."), ] await client.set( Set( store="code_repo", inputs=code_snippets, preprocess_action=PreprocessAction.ModelPreprocessing, ) ) ``` ### Searching with Natural Language **Rust**: ```rust // Search using natural language query let query = vec![StoreInput::RawString("implement recursive fibonacci sequence".to_string())]; let results = client.get_sim_n( "code_repo".to_string(), query, Condition::new(NonLinearAlgorithm::CosineSimilarity), 5, // top 5 results PreprocessAction::ModelPreprocessing, None, HashMap::new(), ).await?; // Results will contain the Rust fibonacci function with highest similarity ``` **Python**: ```python # Search using natural language query from ahnlich_client_py.ai_query import GetSimN query = [StoreInput(raw_string="implement recursive fibonacci sequence")] results = await client.get_sim_n( GetSimN( store="code_repo", search_input=query, condition=Condition.with_algorithm(NonLinearAlgorithm.CosineSimilarity), closest_n=5, preprocess_action=PreprocessAction.ModelPreprocessing, ) ) ``` ### Searching with Code **Rust**: ```rust // Find similar code patterns let code_query = vec![StoreInput::RawString( "def fib(n): return n if n <= 1 else fib(n-1) + fib(n-2)".to_string() )]; let results = client.get_sim_n( "code_repo".to_string(), code_query, Condition::new(NonLinearAlgorithm::CosineSimilarity), 3, PreprocessAction::ModelPreprocessing, None, HashMap::new(), ).await?; // Will find the Rust fibonacci implementation despite being in a different language ``` **Python**: ```python # Find similar code patterns code_query = [StoreInput(raw_string="def fib(n): return n if n <= 1 else fib(n-1) + fib(n-2)")] results = await client.get_sim_n( GetSimN( store="code_repo", search_input=code_query, condition=Condition.with_algorithm(NonLinearAlgorithm.CosineSimilarity), closest_n=3, preprocess_action=PreprocessAction.ModelPreprocessing, ) ) ``` ### Use Cases - **Documentation Search**: Index code examples and search with natural language questions - **Code Discovery**: Find similar implementations across different programming languages - **Refactoring Detection**: Identify duplicate or similar code patterns - **Code Review Assistance**: Find related code snippets for context - **IDE Integration**: Power semantic code search in development tools ## Model Parameters (`model_params`) Some AI models accept optional runtime parameters via `model_params` — a `map` field available on `Set`, `GetSimN`, and `ConvertStoreInputToEmbeddings` requests. These parameters let you tune model behavior at inference time without changing store configuration. When `model_params` is empty (or omitted), models use their built-in defaults. Models that don't support any parameters simply ignore the field. ### Supported Parameters by Model | Model | Parameter | Type | Default | Description | | ----- | ----- | ----- | ----- | ----- | | **Buffalo\_L** | `confidence_threshold` | float (0.0–1.0) | `0.5` | Minimum detection confidence for a face to be included. Higher values = fewer but more confident detections. | | **Buffalo\_L** | `attributes` | string (comma-separated) | (empty) | Optional attributes to compute. Use `genderage` to enable age and gender predictions. When omitted, only face embeddings and bounding boxes are computed. | | **SFace+YuNet** | `confidence_threshold` | float (0.0–1.0) | `0.6` | Minimum detection confidence for a face to be included. Higher values = fewer but more confident detections. | Text embedding models (MiniLM, BGE), image models (ResNet, CLIP), and audio models (CLAP) do not currently use `model_params`. ### Usage Examples **Rust** — setting a high confidence threshold for face detection: ```rust use std::collections::HashMap; let mut model_params = HashMap::new(); model_params.insert("confidence_threshold".to_string(), "0.9".to_string()); let set_params = Set { store: "faces_store".to_string(), inputs: vec![/* ... */], preprocess_action: PreprocessAction::NoPreprocessing as i32, execution_provider: None, model_params, }; ``` **Python** — using default parameters (empty dict): ```python await client.set( ai_query.Set( store="faces_store", inputs=[...], preprocess_action=preprocess.PreprocessAction.NoPreprocessing, model_params={} # uses model defaults ) ) ``` **Python** — custom confidence threshold: ```python await client.set( ai_query.Set( store="faces_store", inputs=[...], preprocess_action=preprocess.PreprocessAction.NoPreprocessing, model_params={"confidence_threshold": "0.9"} ) ) ``` ### When to Tune `model_params` - **Inclusive detection** (e.g., group photos where you want all faces): Use a lower threshold like `0.3` - **Standard detection** (balanced): Use the model default (`0.5` for Buffalo\_L, `0.6` for SFace+YuNet) - **Strict detection** (e.g., ID verification where only clear faces matter): Use a higher threshold like `0.9` ## Embedding Metadata Starting from version 0.2.2, face detection models (Buffalo\_L and SFace+YuNet) return **bounding box metadata** alongside embeddings. This allows you to access face location and confidence information without re-running detection. ### Metadata Fields (Face Detection Models) For each detected face, the following metadata is automatically included: | Field | Type | Range | Description | | ----- | ----- | ----- | ----- | | `bbox_x1` | float | 0.0–1.0 | Normalized x-coordinate of top-left corner | | `bbox_y1` | float | 0.0–1.0 | Normalized y-coordinate of top-left corner | | `bbox_x2` | float | 0.0–1.0 | Normalized x-coordinate of bottom-right corner | | `bbox_y2` | float | 0.0–1.0 | Normalized y-coordinate of bottom-right corner | | `confidence` | float | 0.0–1.0 | Detection confidence score | **Buffalo\_L only** — the following fields are included when `attributes=genderage` is specified: | Field | Type | Range | Description | | ----- | ----- | ----- | ----- | | `gender_female_prob` | float | 0.0–1.0 | Probability of female gender | | `gender_male_prob` | float | 0.0–1.0 | Probability of male gender | | `age` | float | 0.0–100.0 | Predicted age in years | **Coordinates are normalized** to the 0-1 range, making them independent of the original image resolution. To convert to pixel coordinates, multiply by the image width/height: ``` pixel_x1 = bbox_x1 * image_width pixel_y1 = bbox_y1 * image_height ``` ### Metadata Storage When you insert images using face detection models: - Embeddings are stored in Ahnlich DB as usual - Metadata (bounding boxes, confidence) is merged into the `StoreValue` for each face - Metadata is returned in `GetSimN`, `GetPred`, and `ConvertStoreInputToEmbeddings` responses ### API Response Structure The `ConvertStoreInputToEmbeddings` API returns `EmbeddingWithMetadata` for face models: ```protobuf message EmbeddingWithMetadata { keyval.StoreKey embedding = 1; // The face embedding vector optional keyval.StoreValue metadata = 2; // Bounding box + confidence } ``` For OneToMany models (face detection), multiple `EmbeddingWithMetadata` objects are returned—one per detected face. ### Usage Examples **Rust** — accessing bounding box metadata: ```rust use ahnlich_client_rs::prelude::*; let response = client.convert_to_embeddings( store_name, vec![StoreInput::Image(image_bytes)], PreprocessAction::ModelPreprocessing, None, HashMap::new(), ).await?; // For face detection models, variant is OneToMany if let Some(Variant::Multiple(multi)) = &response.values[0].variant { for face in &multi.embeddings { if let Some(embedding) = &face.embedding { println!("Embedding dimensions: {}", embedding.key.len()); } if let Some(metadata) = &face.metadata { let bbox_x1 = metadata.value.get("bbox_x1").unwrap(); let bbox_y1 = metadata.value.get("bbox_y1").unwrap(); let confidence = metadata.value.get("confidence").unwrap(); println!("Face at ({}, {}) with confidence {}", bbox_x1, bbox_y1, confidence); } } } ``` **Python** — accessing bounding box metadata: ```python from ahnlich_client_py import AhnlichAIClient response = await client.convert_store_input_to_embeddings( store="faces_store", inputs=[image_bytes], preprocess_action=PreprocessAction.ModelPreprocessing, ) # Each face has embedding + metadata for face_data in response.values[0].multiple.embeddings: embedding = face_data.embedding.key # 512-dim vector for Buffalo_L metadata = face_data.metadata.value bbox_x1 = float(metadata["bbox_x1"].value) bbox_y1 = float(metadata["bbox_y1"].value) confidence = float(metadata["confidence"].value) print(f"Face at ({bbox_x1}, {bbox_y1}) with confidence {confidence}") ``` **TypeScript** — accessing bounding box metadata: ```typescript import { AhnlichAIClient } from '@deven96/ahnlich-client-node'; const response = await client.convertStoreInputToEmbeddings({ store: "faces_store", inputs: [{ image: imageBytes }], preprocessAction: PreprocessAction.MODEL_PREPROCESSING, }); // Each detected face has embedding + metadata for (const faceData of response.values[0].multiple.embeddings) { const embedding = faceData.embedding.key; // Float32Array const metadata = faceData.metadata.value; const bboxX1 = parseFloat(metadata.bbox_x1.value); const bboxY1 = parseFloat(metadata.bbox_y1.value); const confidence = parseFloat(metadata.confidence.value); console.log(`Face at (${bboxX1}, ${bboxY1}) with confidence ${confidence}`); } ``` ### Gender and Age Predictions (Buffalo_L) Buffalo_L can compute **age and gender predictions** for each detected face by setting `attributes=genderage` in `model_params`. This adds three additional metadata fields per face: `gender_female_prob`, `gender_male_prob`, and `age`. **Rust** — enabling gender and age predictions: ```rust use std::collections::HashMap; use ahnlich_client_rs::prelude::*; let mut model_params = HashMap::new(); model_params.insert("attributes".to_string(), "genderage".to_string()); let response = client.convert_to_embeddings( store_name, vec![StoreInput::Image(image_bytes)], PreprocessAction::ModelPreprocessing, None, model_params, ).await?; // Access gender/age metadata if let Some(Variant::Multiple(multi)) = &response.values[0].variant { for face in &multi.embeddings { if let Some(metadata) = &face.metadata { let female_prob = metadata.value.get("gender_female_prob").unwrap(); let male_prob = metadata.value.get("gender_male_prob").unwrap(); let age = metadata.value.get("age").unwrap(); println!("Age: {}, Female: {}, Male: {}", age, female_prob, male_prob); } } } ``` **Python** — enabling gender and age predictions: ```python from ahnlich_client_py import AhnlichAIClient response = await client.convert_store_input_to_embeddings( store="faces_store", inputs=[image_bytes], preprocess_action=PreprocessAction.ModelPreprocessing, model_params={"attributes": "genderage"} ) # Access gender/age metadata for face_data in response.values[0].multiple.embeddings: metadata = face_data.metadata.value female_prob = float(metadata["gender_female_prob"].value) male_prob = float(metadata["gender_male_prob"].value) age = float(metadata["age"].value) print(f"Age: {age}, Female: {female_prob}, Male: {male_prob}") ``` **TypeScript** — enabling gender and age predictions: ```typescript import { AhnlichAIClient } from '@deven96/ahnlich-client-node'; const response = await client.convertStoreInputToEmbeddings({ store: "faces_store", inputs: [{ image: imageBytes }], preprocessAction: PreprocessAction.MODEL_PREPROCESSING, modelParams: { attributes: "genderage" } }); // Access gender/age metadata for (const faceData of response.values[0].multiple.embeddings) { const metadata = faceData.metadata.value; const femaleProb = parseFloat(metadata.gender_female_prob.value); const maleProb = parseFloat(metadata.gender_male_prob.value); const age = parseFloat(metadata.age.value); console.log(`Age: ${age}, Female: ${femaleProb}, Male: ${maleProb}`); } ``` ### Use Cases for Metadata - **Face cropping**: Use bounding boxes to extract face regions from original images - **Visualization**: Draw bounding boxes on images to show detected faces - **Quality filtering**: Filter results by confidence score (e.g., only faces with confidence > 0.8) - **Spatial queries**: Find faces in specific image regions (e.g., "faces in the top-left quadrant") - **Deduplication**: Identify overlapping detections using bounding box coordinates - **Demographic analysis** (Buffalo_L with `attributes=genderage`): - Age-based filtering (e.g., "find faces that appear under 18") - Gender distribution analysis in group photos - Age group clustering (children, adults, elderly) - Demographic insights for audience analysis ### Models Without Metadata Text and image embedding models (MiniLM, BGE, ResNet, CLIP) do **not** return metadata. The `metadata` field will be `None` or empty for these models. --- # FILE: components/ahnlich-ai/ahnlich-ai.mdx --- title: Ahnlich AI proxy sidebar_position: 30 --- import {AiProxyFigure} from '@site/src/components/OverviewFigures'; # Ahnlich AI proxy **The AI proxy lets you work in text, images, and audio instead of numbers.** [Ahnlich DB](/docs/components/ahnlich-db) stores and searches _vectors_ — but where do those vectors come from? Normally you'd have to run a machine-learning model yourself to turn a sentence or an image into numbers before saving it. That's the fiddly part. The **Ahnlich AI proxy sits in front of the database and does that step for you.** You hand it raw text, an image, or audio; it picks the right model, produces the vector, and stores or searches it in Ahnlich DB — automatically. ## Why it helps Without the proxy you'd write code to load a model, embed your input, keep the model versions in sync between saving and searching, and only then call the database. The proxy removes all of that. You send: ```bash INSERT "The rise of renewable energy storage" INTO article_store ``` …and behind the scenes it becomes a vector and lands in the database. When you search, you again send plain text — the proxy embeds your query the same way, so the comparison is fair. ## The one key idea: model-aware stores When you create a store through the AI proxy, you choose **two models**: | Model | When it runs | Its job | | --- | --- | --- | | **Index model** | when you _add_ data | turn each stored item into a vector | | **Query model** | when you _search_ | turn each query into a vector | There's a single rule: **both models must output vectors of the same size** (dimension), so stored items and queries live in the same space and can be compared. ```bash CREATESTORE my_store QUERYMODEL all-minilm-l6-v2 INDEXMODEL all-minilm-l6-v2 ``` ## Text, images, and audio Because the index and query models are chosen separately, you can mix modalities — text, images, or audio — as long as the two models output the same dimension: - **Text → text** (same model both sides): classic semantic search over documents. ```bash CREATESTORE product_store QUERYMODEL all-minilm-l6-v2 INDEXMODEL all-minilm-l6-v2 ``` - **Text → image** (different, compatible models): index photos, then search them with words like _"blue denim jacket"_. ```bash CREATESTORE image_store QUERYMODEL all-minilm-l6-v2 INDEXMODEL resnet-50 ``` ## Ready-to-use models The proxy ships with several **off-the-shelf models** for text and images (MiniLM, BGE variants, ResNet-50, CLIP, and more). You don't install or fine-tune anything — you just name the model when creating a store. See [Supported models](/docs/components/ahnlich-ai/advanced#supported-models) for the full list. ## Searching in plain language At query time you provide natural input, not vectors: ```bash GETSIMN 3 WITH ["climate change effects on agriculture"] USING cosinesimilarity IN news_store ``` The proxy embeds your query, asks Ahnlich DB for the nearest stored vectors, and returns the matching items. ## Where to go next - [Quickstart: Ahnlich AI](/docs/getting-started/quickstart-ai) — the recommended starting point. - [Command reference](/docs/components/ahnlich-ai/reference) — every command. - [Supported models](/docs/components/ahnlich-ai/advanced#supported-models) — text and image models you can use. - [Advanced](/docs/components/ahnlich-ai/advanced) — model parameters, input types, and more. --- # FILE: components/ahnlich-ai/deep-dive.md --- title: Deeper Dive --- # Deeper Dive ## 1. Commands via the AI Proxy Ahnlich AI acts as a proxy service that abstracts away the complexity of generating embeddings. Instead of supplying raw vectors (as with Ahnlich DB), developers can submit natural inputs such as text, and the AI proxy will: - Generate embeddings using the configured model(s). - Forward the transformed embeddings to Ahnlich DB for storage or similarity queries. ### Examples of Commands #### Ping the Server - `PING` Verifies that the Ahnlich AI service is running. #### Server Info - `INFOSERVER` Retrieves information about the AI proxy (status, active models, connected DB). #### List Stores - `LISTSTORES` - `LISTSTORES SCHEMA media` Returns stores managed through the AI proxy in the selected schema. If no schema is supplied, only `public` stores are returned. #### Create a Store with Models - `CREATESTORE my_store QUERYMODEL all-minilm-l6-v2 INDEXMODEL all-minilm-l6-v2` This creates a store named `my_store` where: - **index_model** → generates embeddings for stored data. - **query_model** → generates embeddings for queries. #### Insert Text into Store - `SET doc1 "Renewable energy storage is key to sustainability." IN my_store` Raw text is automatically converted into embeddings before being sent to Ahnlich DB. #### Similarity Search with Natural Query - `GETSIMN 3 WITH [solar battery systems] USING cosinesimilarity IN my_store` The AI proxy embeds the query "`solar battery systems`", forwards it to Ahnlich DB, and retrieves the top 3 most similar entries. #### Query by Predicate - `GET BY PREDICATE (category = "energy") IN my_store` Filters results using metadata conditions. #### Create Predicate Index - `CREATEPREDICATEINDEX category IN my_store` Optimizes queries based on the category field. #### Drop Predicate Index - `DROPPREDICATEINDEX category IN my_store` Removes an existing predicate index. #### Create Non-Linear Algorithm Index - `CREATENONLINEARALGORITHM INDEX hnsw IN my_store` Enables advanced search indexing strategies (e.g., HNSW). #### Drop Non-Linear Algorithm Index - `DROPNONLINEARALGORITHMINDEX (hnsw) IN my_store` Removes a non-linear algorithm index. #### Delete by Key - `DELETEKEY doc1 IN my_store` Deletes a specific entry (doc1) from the store. #### Drop Store - `DROPSTORE my_store` Deletes the entire store and its data. ## 2. How Ahnlich AI Reuses and Interacts with Ahnlich DB The interaction model is two-tiered: - **Input Transformation** - The AI proxy transforms raw input (e.g., "renewable energy") into a vector embedding using the configured model. - Store Linkage - Each store is bound to an **index_model** (for embedding inserted data) and a **query_model** (for embedding search queries). - This enables dual-encoder setups where different models can be used for indexing vs. querying. - Delegation to Ahnlich DB - After embedding generation, commands are translated into their Ahnlich DB equivalents. ### Example: - `GETSIMN 3 WITH [renewable energy storage] USING cosinesimilarity IN article_store` → The AI proxy embeds the query and calls DB: - `GETSIMN 3 WITH [0.23, 0.91, -0.44, ...] USING cosinesimilarity IN article_store` ## 3. Supported Modalities and Models Depending on your setup, Ahnlich AI supports different modalities of input: - **Text** - Embeddings generated from models like `all-minilm-l6-v2`. - Optimized for semantic similarity, clustering, and NLP tasks. - **Dual Encoders (if installed)** - Support for cases where different models handle queries vs. indexed data. - Useful in retrieval systems where query understanding and corpus representation require different embedding strategies. Important: When creating a store, you must explicitly define both: - `CREATESTORE my_store QUERYMODEL all-minilm-l6-v2 INDEXMODEL all-minilm-l6-v2` ## 4. Advanced – Running on Non-CPU Devices By default, **Ahnlich AI** runs on **CPU** for maximum portability. For production-scale or latency-sensitive workloads, it can leverage specialized **execution providers** to offload embedding generation onto accelerators such as GPUs or Apple’s CoreML devices. ## Supported Execution Providers Ahnlich AI can leverage multiple execution backends for model inference. By default, models run on **CPU execution** unless otherwise specified. | Provider | Platform | Description | | ----- | ----- | ----- | | **CPU (Default)** | All platforms | Runs models on CPU by default. Portable and easy to deploy, but slower for large-scale or real-time queries. | | **CUDA** | NVIDIA GPUs (Linux/Windows) | Runs models on CUDA-enabled GPUs. Requires `>= CUDA v12`. You may also need: `bash sudo apt install libcudnn9-dev-cuda-12` Best for batch queries or high-throughput NLP. | | **TensorRT** | NVIDIA GPUs | NVIDIA’s optimized inference runtime. Provides lower latency than CUDA alone, especially for large models. | | **CoreML** | macOS / iOS (M1/M2) | Apple’s ML framework for Apple Silicon. Not advised for NLP models due to the high dimensionality of embeddings. | | **DirectML** | Windows | Hardware-accelerated inference on Windows devices. Offers broad GPU compatibility. | ## Example – Overriding Execution Provider By default, Ahnlich AI runs with `CPUExecutionProvider`. You can override this when starting the engine or running a query. ### Rust Example ```rust // Override the execution provider when performing similarity search let params = GetSimN { store: "my_store".to_string(), search_input: Some(search_input), closest_n: 3, algorithm: Algorithm::CosineSimilarity as i32, preprocess_action: PreprocessAction::ModelPreprocessing as i32, execution_provider: Some(ExecutionProvider::Cuda as i32), // Override default CPU provider condition: None, model_params: HashMap::new(), }; ``` ## Switching Providers - **CPU Execution (default)**: Portable and easy to deploy across environments. - **GPU / Accelerators**: Use CUDA, TensorRT, DirectML, or CoreML for higher throughput and lower latency. --- # FILE: components/ahnlich-ai/overview.md --- title: Overview sidebar_position: 10 --- # Overview --- # FILE: components/ahnlich-ai/reference/create-non-linear-index.md --- title: CREATENONLINEARALGORITHMINDEX sidebar_label: CREATENONLINEARALGORITHMINDEX --- # CREATENONLINEARALGORITHMINDEX Build a [vector index](/docs/concepts/vector-index) — `hnsw` — for faster similarity search. ## Syntax ```bash CREATENONLINEARALGORITHMINDEX () IN CREATENONLINEARALGORITHMINDEX () IN SCHEMA media ``` ## Example ```bash CREATENONLINEARALGORITHMINDEX (hnsw) IN geo_store CREATENONLINEARALGORITHMINDEX (hnsw) IN geo_store ``` --- See [SDK examples](/docs/stores/create-non-linear-algx) (choose the **AI proxy** tab) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-ai/reference/create-predicate-index.md --- title: CREATEPREDICATEINDEX sidebar_label: CREATEPREDICATEINDEX --- # CREATEPREDICATEINDEX Index a metadata field to speed up predicate queries. ## Syntax ```bash CREATEPREDICATEINDEX IN CREATEPREDICATEINDEX IN SCHEMA media ``` ## Example ```bash CREATEPREDICATEINDEX category IN article_store ``` --- See [SDK examples](/docs/stores/create-predicate-index) (choose the **AI proxy** tab) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-ai/reference/create-store.md --- title: CREATESTORE sidebar_label: CREATESTORE --- # CREATESTORE Create an AI store with an **index model** (embeds stored data) and a **query model** (embeds searches) — usually the same model. ## Syntax ```bash CREATESTORE QUERYMODEL INDEXMODEL CREATESTORE QUERYMODEL INDEXMODEL SCHEMA media ``` ## Parameters - The bundled model is `all-minilm-l6-v2` (text). ## Example ```bash CREATESTORE my_store QUERYMODEL all-minilm-l6-v2 INDEXMODEL all-minilm-l6-v2 ``` --- See [SDK examples](/docs/stores/create-store) (choose the **AI proxy** tab) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-ai/reference/delete-key.md --- title: DELETEKEY sidebar_label: DELETEKEY --- # DELETEKEY Remove a specific key from a store. ## Syntax ```bash DELETEKEY IN DELETEKEY IN SCHEMA media ``` ## Example ```bash DELETEKEY doc1 IN article_store ``` --- See [SDK examples](/docs/stores/delete-key) (choose the **AI proxy** tab) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-ai/reference/drop-non-linear-index.md --- title: DROPNONLINEARALGORITHMINDEX sidebar_label: DROPNONLINEARALGORITHMINDEX --- # DROPNONLINEARALGORITHMINDEX Drop a previously created non-linear index. ## Syntax ```bash DROPNONLINEARALGORITHMINDEX () IN DROPNONLINEARALGORITHMINDEX () IN SCHEMA media ``` ## Example ```bash DROPNONLINEARALGORITHMINDEX (hnsw) IN geo_store ``` --- See [SDK examples](/docs/stores/drop-non-linear-algx) (choose the **AI proxy** tab) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-ai/reference/drop-predicate-index.md --- title: DROPPREDICATEINDEX sidebar_label: DROPPREDICATEINDEX --- # DROPPREDICATEINDEX Remove a previously created metadata index. ## Syntax ```bash DROPPREDICATEINDEX IN DROPPREDICATEINDEX IN SCHEMA media ``` ## Example ```bash DROPPREDICATEINDEX category IN article_store ``` --- See [SDK examples](/docs/stores/drop-predicate-index) (choose the **AI proxy** tab) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-ai/reference/drop-schema.md --- title: DROPSCHEMA sidebar_label: DROPSCHEMA --- # DROPSCHEMA Drop a non-public schema and all AI stores inside it. Ahnlich AI also drops the backing DB schema first. `public` cannot be dropped. ## Syntax ```bash DROPSCHEMA ``` ## Example ```bash DROPSCHEMA media ``` --- See [SDK examples](/docs/concepts/schemas) (choose the **AI proxy** tab) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-ai/reference/drop-store.md --- title: DROPSTORE sidebar_label: DROPSTORE --- # DROPSTORE Permanently remove an AI store and its contents. `IF EXISTS` avoids an error if it's already gone. ## Syntax ```bash DROPSTORE DROPSTORE IF EXISTS SCHEMA media ``` ## Example ```bash DROPSTORE article_store IF EXISTS ``` --- See [SDK examples](/docs/stores/drop-store) (choose the **AI proxy** tab) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-ai/reference/get-by-predicate.md --- title: GETPRED sidebar_label: GETPRED --- # GETPRED Retrieve every entry that satisfies a metadata condition. ## Syntax ```bash GETPRED () IN GETPRED () IN SCHEMA media ``` ## Example ```bash GETPRED (category = "news") IN article_store ``` --- See [SDK examples](/docs/stores/get-by-predicate) (choose the **AI proxy** tab) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-ai/reference/get-key.md --- title: GETKEY sidebar_label: GETKEY --- # GETKEY Retrieve items by their original input key — available when the store preserves originals (`STOREORIGINAL`). ## Syntax ```bash GETKEY IN GETKEY IN SCHEMA media ``` ## Example ```bash GETKEY doc1 IN article_store ``` --- See [SDK examples](/docs/stores/get-key) (choose the **AI proxy** tab) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-ai/reference/get-sim-n.md --- title: GETSIMN sidebar_label: GETSIMN --- # GETSIMN Retrieve the top **N** entries most similar to a **raw input** (embedded on the fly). Optionally filter with a predicate. ## Syntax ```bash GETSIMN WITH [] USING IN WHERE () GETSIMN WITH [] USING IN SCHEMA media WHERE () ``` ## Example ```bash GETSIMN 3 WITH [renewable energy storage] USING cosinesimilarity IN article_store WHERE (category != sports) ``` --- See [SDK examples](/docs/stores/get-simn) (choose the **AI proxy** tab) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-ai/reference/get-store.md --- title: GETSTORE sidebar_label: GETSTORE --- # GETSTORE Detailed info about a store — its query model, index model, embedding size, and (when connected to a DB instance) the backing `db_info`. Errors if the store doesn't exist. ## Syntax ```bash GETSTORE GETSTORE SCHEMA media ``` ## Example ```bash GETSTORE my_store ``` --- See [SDK examples](/docs/stores/get-store) (choose the **AI proxy** tab) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-ai/reference/infoserver.md --- title: INFOSERVER sidebar_label: INFOSERVER --- # INFOSERVER Return runtime info such as version, uptime, and the models available. ## Syntax ```bash INFOSERVER ``` ## Example ```bash INFOSERVER ``` --- See [SDK examples](/docs/stores/info-server) (choose the **AI proxy** tab) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-ai/reference/list-stores.md --- title: LISTSTORES sidebar_label: LISTSTORES --- # LISTSTORES Display AI stores in a schema. Omit `SCHEMA` to list `public` only. ## Syntax ```bash LISTSTORES LISTSTORES SCHEMA media ``` ## Example ```bash LISTSTORES SCHEMA media ``` --- See [SDK examples](/docs/stores/list-stores) (choose the **AI proxy** tab) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-ai/reference/ping.md --- title: PING sidebar_label: PING --- # PING Check whether the Ahnlich AI service is up and running. ## Syntax ```bash PING ``` ## Example ```bash PING # → PONG ``` --- See [SDK examples](/docs/stores/ping) (choose the **AI proxy** tab) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-ai/reference/sdk-mapping.mdx --- title: Command → SDK map sidebar_label: Command → SDK map --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Command → SDK map Everything you can type as a **DSL command** (in the CLI) has a matching **method call** in each client library. With the AI proxy the big difference from raw DB usage is that you pass **raw text** (or images/audio) instead of vectors — the proxy embeds it for you either way, CLI or SDK. Pick your language once with the tabs below and it stays selected across the page. :::note Signatures here are **simplified** to show the shape of each call. For full, runnable examples, follow each command's [Stores operation page](/docs/stores) and choose the **AI proxy** tab. ::: ## One operation, every language Creating a **model-aware store** (an index model to embed stored items, a query model to embed searches): ```bash CREATESTORE article_store QUERYMODEL all-minilm-l6-v2 INDEXMODEL all-minilm-l6-v2 ``` ```python client.create_store("article_store", index_model="all-minilm-l6-v2", query_model="all-minilm-l6-v2") ``` ```javascript await client.createStore("article_store", "all-minilm-l6-v2", "all-minilm-l6-v2"); ``` ```go client.CreateStore(ctx, "article_store", "all-minilm-l6-v2", "all-minilm-l6-v2") ``` ```rust client.create_store("article_store", "all-minilm-l6-v2", "all-minilm-l6-v2")?; ``` ## Full command map #### Server & system | Command | Python | | --- | --- | | `PING` | `client.ping()` | | `INFO SERVER` | `client.info_server()` | #### Store management | Command | Python | | --- | --- | | `LIST STORES` | `client.list_stores()` | | `GETSTORE article_store` | `client.get_store("article_store")` | | `CREATESTORE … QUERYMODEL … INDEXMODEL …` | `client.create_store("article_store", index_model="all-minilm-l6-v2", query_model="all-minilm-l6-v2")` | | `DROPSTORE article_store` | `client.drop_store("article_store")` | #### Data operations | Command | Python | | --- | --- | | `SET doc1 "…text…" WITH {...} IN article_store` | `client.set("article_store", "doc1", "The future of AI in healthcare", {"category": "news"})` | | `DELKEY doc1 IN article_store` | `client.delete_key("article_store", "doc1")` | #### Query & retrieval | Command | Python | | --- | --- | | `GETSIMN 3 WITH ["…text…"] USING cosine IN article_store` | `client.get_sim_n("article_store", "renewable energy storage", 3, "cosine", predicate="category!='sports'")` | | `GETKEY doc1 IN article_store` | `client.get_key("article_store", "doc1")` | | `GETPRED (category='news') IN article_store` | `client.get_by_predicate("article_store", "category='news'")` | #### Index management | Command | Python | | --- | --- | | `CREATEPREDINDEX (category) IN article_store` | `client.create_predicate_index("article_store", "category")` | | `DROPPREDINDEX (category) IN article_store` | `client.drop_predicate_index("article_store", "category")` | | `CREATENONLINEARALGORITHMINDEX (hnsw) IN article_store` | `client.create_non_linear_algorithm_index("article_store", NonLinearIndex(hnsw=HNSWConfig()))` | | `DROPNONLINEARALGORITHMINDEX (hnsw) IN article_store` | `client.drop_non_linear_algorithm_index("article_store", "hnsw")` | #### Server & system | Command | Node.js | | --- | --- | | `PING` | `await client.ping()` | | `INFO SERVER` | `await client.infoServer()` | #### Store management | Command | Node.js | | --- | --- | | `LIST STORES` | `await client.listStores()` | | `GETSTORE article_store` | `await client.getStore("article_store")` | | `CREATESTORE … QUERYMODEL … INDEXMODEL …` | `await client.createStore("article_store", "all-minilm-l6-v2", "all-minilm-l6-v2")` | | `DROPSTORE article_store` | `await client.dropStore("article_store")` | #### Data operations | Command | Node.js | | --- | --- | | `SET doc1 "…text…" WITH {...} IN article_store` | `await client.set("article_store", "doc1", "The future of AI in healthcare", {category: "news"})` | | `DELKEY doc1 IN article_store` | `await client.delKey("article_store", "doc1")` | #### Query & retrieval | Command | Node.js | | --- | --- | | `GETSIMN 3 WITH ["…text…"] USING cosine IN article_store` | `await client.getSimN("article_store", "renewable energy storage", 3, "cosine", "category!='sports'")` | | `GETKEY doc1 IN article_store` | `await client.getKey("article_store", "doc1")` | | `GETPRED (category='news') IN article_store` | `await client.getPred("article_store", "category='news'")` | #### Index management | Command | Node.js | | --- | --- | | `CREATEPREDINDEX (category) IN article_store` | `await client.createPredIndex("article_store", "category")` | | `DROPPREDINDEX (category) IN article_store` | `await client.dropPredIndex("article_store", "category")` | | `CREATENONLINEARALGORITHMINDEX (hnsw) IN article_store` | `await client.createNonLinearAlgorithmIndex("article_store", hnsw)` | | `DROPNONLINEARALGORITHMINDEX (hnsw) IN article_store` | `await client.dropNonLinearAlgorithmIndex("article_store", "hnsw")` | #### Server & system | Command | Go | | --- | --- | | `PING` | `client.Ping(ctx)` | | `INFO SERVER` | `client.InfoServer(ctx)` | #### Store management | Command | Go | | --- | --- | | `LIST STORES` | `client.ListStores(ctx)` | | `GETSTORE article_store` | `client.GetStore(ctx, "article_store")` | | `CREATESTORE … QUERYMODEL … INDEXMODEL …` | `client.CreateStore(ctx, "article_store", "all-minilm-l6-v2", "all-minilm-l6-v2")` | | `DROPSTORE article_store` | `client.DropStore(ctx, "article_store")` | #### Data operations | Command | Go | | --- | --- | | `SET doc1 "…text…" WITH {...} IN article_store` | `client.Set(ctx, "article_store", "doc1", "The future of AI in healthcare", map[string]string{"category": "news"})` | | `DELKEY doc1 IN article_store` | `client.DeleteKey(ctx, "article_store", "doc1")` | #### Query & retrieval | Command | Go | | --- | --- | | `GETSIMN 3 WITH ["…text…"] USING cosine IN article_store` | `client.GetSimN(ctx, "article_store", "renewable energy storage", 3, "cosine", "category!='sports'")` | | `GETKEY doc1 IN article_store` | `client.GetKey(ctx, "article_store", "doc1")` | | `GETPRED (category='news') IN article_store` | `client.GetByPredicate(ctx, "article_store", "category='news'")` | #### Index management | Command | Go | | --- | --- | | `CREATEPREDINDEX (category) IN article_store` | `client.CreatePredicateIndex(ctx, "article_store", "category")` | | `DROPPREDINDEX (category) IN article_store` | `client.DropPredicateIndex(ctx, "article_store", "category")` | | `CREATENONLINEARALGORITHMINDEX (hnsw) IN article_store` | `client.CreateNonLinearAlgorithmIndex(ctx, "article_store", NonLinearIndex_Hnsw)` | | `DROPNONLINEARALGORITHMINDEX (hnsw) IN article_store` | `client.DropNonLinearAlgorithmIndex(ctx, "article_store", "hnsw")` | #### Server & system | Command | Rust | | --- | --- | | `PING` | `client.ping()?;` | | `INFO SERVER` | `client.info_server()?;` | #### Store management | Command | Rust | | --- | --- | | `LIST STORES` | `client.list_stores()?;` | | `GETSTORE article_store` | `client.get_store("article_store")?;` | | `CREATESTORE … QUERYMODEL … INDEXMODEL …` | `client.create_store("article_store", "all-minilm-l6-v2", "all-minilm-l6-v2")?;` | | `DROPSTORE article_store` | `client.drop_store("article_store")?;` | #### Data operations | Command | Rust | | --- | --- | | `SET doc1 "…text…" WITH {...} IN article_store` | `client.set("article_store", "doc1", "The future of AI in healthcare", hashmap!{"category" => "news"})?;` | | `DELKEY doc1 IN article_store` | `client.delete_key("article_store", "doc1")?;` | #### Query & retrieval | Command | Rust | | --- | --- | | `GETSIMN 3 WITH ["…text…"] USING cosine IN article_store` | `client.get_sim_n("article_store", "renewable energy storage", 3, "cosine", Some("category!='sports'"))?;` | | `GETKEY doc1 IN article_store` | `client.get_key("article_store", "doc1")?;` | | `GETPRED (category='news') IN article_store` | `client.get_by_predicate("article_store", "category='news'")?;` | #### Index management | Command | Rust | | --- | --- | | `CREATEPREDINDEX (category) IN article_store` | `client.create_predicate_index("article_store", "category")?;` | | `DROPPREDINDEX (category) IN article_store` | `client.drop_predicate_index("article_store", "category")?;` | | `CREATENONLINEARALGORITHMINDEX (hnsw) IN article_store` | `client.create_non_linear_algorithm_index("article_store", NonLinearIndex { hnsw })?;` | | `DROPNONLINEARALGORITHMINDEX (hnsw) IN article_store` | `client.drop_non_linear_algorithm_index("article_store", "hnsw")?;` | ## Next steps - [Command reference](/docs/components/ahnlich-ai/reference) — what each command does. - [Client libraries](/docs/client-libraries) — install and set up your SDK. - [Stores operations](/docs/stores) — full runnable examples per operation. --- # FILE: components/ahnlich-ai/reference/set.md --- title: SET sidebar_label: SET --- # SET Insert **raw input** (text or an image) into a store — Ahnlich AI embeds it for you. Add metadata as key–value pairs. ## Syntax ```bash SET "" WITH {"":""} IN SET "" WITH { ... } IN SCHEMA media ``` ## Example ```bash SET doc1 "The future of AI in healthcare" WITH {"category":"news"} IN article_store ``` --- See [SDK examples](/docs/stores/set) (choose the **AI proxy** tab) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-ai/reference/upsert.md --- title: UPSERT sidebar_label: UPSERT --- # UPSERT Update a single entry matched by a predicate. Always **merges** metadata (preserving AI-generated fields). Errors if zero or multiple entries match. ## Syntax ```bash UPSERT [KEY ] [VALUE ] IN WHERE () [PREPROCESSACTION ] ``` ## Parameters - `KEY ` — new text/image to re-embed. - `VALUE ` — metadata to merge. - `PREPROCESSACTION` — how to prepare input before embedding. - `WHERE ()` — must match exactly one entry. ## Example ```bash UPSERT VALUE {tags: cat,outdoors} IN images WHERE (filename = photo.jpg) UPSERT KEY [new text] VALUE {author: Jane} IN docs WHERE (id = 100) ``` --- See [SDK examples](/docs/stores/upsert) (choose the **AI proxy** tab) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-ai/reference.md --- title: Command reference sidebar_label: Overview sidebar_position: 20 --- # AI command reference Ahnlich AI is the proxy that turns **raw input** — text or images — into embeddings and manages them in vector stores backed by [Ahnlich DB](/docs/components/ahnlich-db). Every command it understands is listed below, one page each. The bundled model is `all-minilm-l6-v2` (text). For full, runnable **SDK** examples, each page links to its matching [Stores](/docs/stores) operation — pick the **AI proxy** tab. ## Server & system - [PING](./reference/ping) · [INFOSERVER](./reference/infoserver) ## Store management - [LISTSTORES](./reference/list-stores) · [GETSTORE](./reference/get-store) · [CREATESTORE](./reference/create-store) · [DROPSTORE](./reference/drop-store) · [DROPSCHEMA](./reference/drop-schema) ## Data operations - [SET](./reference/set) · [UPSERT](./reference/upsert) · [DELETEKEY](./reference/delete-key) ## Query & retrieval - [GETSIMN](./reference/get-sim-n) · [GETPRED](./reference/get-by-predicate) · [GETKEY](./reference/get-key) ## Index management - [CREATEPREDICATEINDEX](./reference/create-predicate-index) · [DROPPREDICATEINDEX](./reference/drop-predicate-index) · [CREATENONLINEARALGORITHMINDEX](./reference/create-non-linear-index) · [DROPNONLINEARALGORITHMINDEX](./reference/drop-non-linear-index) ## Reference - [Command → SDK map](./reference/sdk-mapping) --- **How Ahnlich AI differs from Ahnlich DB:** you send **text/images**, not vectors — the proxy embeds them with the store's models. The commands otherwise mirror [Ahnlich DB](/docs/components/ahnlich-db/reference). --- # FILE: components/ahnlich-ai/setup-config.mdx --- title: Setup & Configuration --- import {InstallFigure} from '@site/src/components/OverviewFigures'; # Setup & Configuration **Goal: get an Ahnlich AI proxy running and connected to Ahnlich DB.** The AI proxy turns your raw text, images, and audio into vectors, then hands them to the database — so it always runs *alongside* an Ahnlich DB server (via `--db-url`). Install it the same three ways as the database: :::tip First time here? Install and start [Ahnlich DB](/docs/components/ahnlich-db/installation) first — the AI proxy needs a database to talk to. The Docker Compose example below starts both together. ::: ## Installation ### 1. Download Binaries Prebuilt binaries are available from [GitHub Releases](https://github.com/deven96/ahnlich/releases). Pick the file that matches your OS and chip (on macOS, run `uname -m` — `arm64` = Apple Silicon, `x86_64` = Intel). #### Download the binary (macOS Apple Silicon shown): ```bash curl -L -O "https://github.com/deven96/ahnlich/releases/download/bin%2Fai%2F0.3.0/aarch64-apple-darwin-ahnlich-ai.tar.gz" ``` #### Extract the archive: ```bash tar -xvzf *-ahnlich-ai.tar.gz ``` #### Run the binary: ```bash ./ahnlich-ai ``` > On macOS, if Gatekeeper blocks the unsigned binary, clear the quarantine flag > first: `xattr -d com.apple.quarantine ./ahnlich-ai` ### 2. Using Docker Ahnlich AI also ships as a Docker image: ``` docker pull ghcr.io/deven96/ahnlich-ai:latest ``` Run with: ``` docker run --rm -p 1370:1370 \ --network ahnlich-net \ --name ahnlich-ai \ ghcr.io/deven96/ahnlich-ai:latest \ ahnlich-ai run --port 1370 --db-url http://ahnlich-db:1369 ``` ### 3. Example Docker Compose Ahnlich AI can be orchestrated with docker-compose, typically alongside Ahnlich DB. ``` services: ahnlich_ai: image: ghcr.io/deven96/ahnlich-ai:latest command: > "ahnlich-ai run --host 0.0.0.0 --db-url http://ahnlich_db:1369 --enable-tracing --otel-endpoint http://jaeger:4317" ports: - "1370:1370" ahnlich_db: image: ghcr.io/deven96/ahnlich-db:latest command: > "ahnlich-db run --host 0.0.0.0 --enable-tracing" ports: - "1369:1369" ``` #### Optional Jaeger service for tracing ``` jaeger: image: jaegertracing/all-in-one:${JAEGER_VERSION:-latest} ports: - "16686:16686" - "4317:4317" - "4318:4318" ``` ## Configuration Options Ahnlich AI can be customized using runtime flags: - `--host ` – Specify listening host (default: 0.0.0.0). - `--port ` – Specify server port (default: 1370). - `--enable-tracing` – Enable telemetry tracing with OpenTelemetry. - `--otel-endpoint ` – OpenTelemetry endpoint (e.g., Jaeger). - `CREATESTORE my_store QUERYMODEL all-minilm-l6-v2 INDEXMODEL all-minilm-l6-v2` ## Quick Start Example Start Ahnlich AI (with a linked DB): ``` ./ahnlich-ai run --host 0.0.0.0 --port 1370 --db-url http://localhost:1369 ``` Create a model-aware store: ``` CREATESTORE my_store QUERYMODEL all-minilm-l6-v2 INDEXMODEL all-minilm-l6-v2 ``` Insert raw input (text, image, or audio): ``` INSERT "The rise of renewable energy storage solutions" INTO my_store ``` Run a semantic query: ``` SEARCH "climate change effects on agriculture" IN my_store ``` --- # FILE: components/ahnlich-ai/use-cases.mdx --- title: Use Cases --- import {UseCasesFigure} from '@site/src/components/OverviewFigures'; # Use Cases The AI proxy's superpower is that **you never touch vectors** — you send raw text, images, or audio and it does the embedding for you. Every example below is the same recipe: raw input in, nearest matches out, with optional metadata filtering. ## 1. Semantic Search with Raw Input A news website integrates Ahnlich AI with `all-minilm-l6-v2`. Instead of keyword search, users type: ``` SEARCH "climate change and food security" IN news_store WHERE (topic != sports) ``` - The text query is embedded automatically. - Stored articles are embedded consistently. - Ahnlich DB returns top semantic matches, filtering irrelevant categories. This provides **conceptual search** rather than exact word matching. ## 2. Cross-Modal Search (Text ↔ Image) A fashion platform configures: - Index Model = `resnet-50` (for product images) - Query Model = `all-minilm-l6-v2` (for user text queries) When a user searches for a product: ``` GETSIMN 5 WITH [red summer dress] USING cosinesimilarity IN fashion_store ``` Ahnlich AI embeds the **text query** and compares it against **image embeddings** in the store. This allows retrieving visually similar dresses from the catalog — without the store owner needing to manually tag the images. ## 3. Personalized Recommendations Ahnlich AI can also transform **user profiles** or **behaviors into embeddings** automatically. Example: an e-commerce platform using `product_store`: ``` GETSIMN 5 WITH [eco-friendly home products] USING cosinesimilarity IN product_store WHERE (status = in_stock) ``` Here: - The **user query** is embedded. - It’s matched against **product embeddings**. - The results are filtered using the **predicate** `(status = in_stock)`. This enables **real-time, personalized product recommendations** tailored to availability. ## 4. Multimodal Applications in Healthcare Ahnlich AI does not support mixing image and text embeddings in a single store. Each store is model-aware and tied to one input type (text or image). For workflows such as CT scans and radiology reports, the recommended approach is to create **two separate stores** and link them using metadata fields like `patient_id` or `report_id`. ### Pattern A Two Stores with Metadata Linking
Click to expand ``` create an image store CREATESTORE ct_image_store QUERYMODEL resnet-50 INDEXMODEL resnet-50 STOREORIGINAL; CREATEPREDINDEX (patient_id, report_id) IN ct_image_store; create a text report store CREATESTORE report_store QUERYMODEL all-minilm-l6-v2 INDEXMODEL all-minilm-l6-v2 STOREORIGINAL; CREATEPREDINDEX (patient_id, report_id) IN report_store; insert CT scan SET (([], {patient_id: "P123", report_id: "R789"})) IN ct_image_store; insert report SET ((["Findings: ground-glass opacities ..."], {patient_id: "P123", report_id: "R789"})) IN report_store; query CT scans GETSIMN 5 WITH [] USING cosinesimilarity IN ct_image_store; fetch linked report GETPRED (report_id = "R789") IN report_store; ```
Here, similarity search on `ct_image_store` finds related scans, and `report_id` links results to the associated report in `report_store`. ### Pattern B — Cross-Modal Matching with CLIP
Click to expand ``` -- create a cross-modal store (text ↔ image) CREATESTORE clip_image_store QUERYMODEL clip-vit-b32-text INDEXMODEL clip-vit-b32-image STOREORIGINAL CREATEPREDINDEX (patient_id, report_id) IN clip_image_store; insert CT scan SET (([], {patient_id: "P123", report_id: "R789"})) IN clip_image_store; query with text (embedded via CLIP text model) GETSIMN 5 WITH ["ground-glass opacity in left lung"] USING cosinesimilarity IN clip_image_store ```
## 5. Real-Time Assistance A chatbot connected to Ahnlich AI can provide real-time recommendations by retrieving similar past support tickets. Example flow: 1. The user submits a query: `"I need help with my billing issue"` 2. The query is converted into embeddings automatically. 3. Retrieve the top N similar past tickets using `GETSIMN`: ``` GETSIMN 5 WITH ["I need help with my billing issue"] USING cosinesimilarity IN support_store ``` 4. Optionally, filter results using metadata with `GETPRED`: ``` GETPRED (resolved = true) IN support_store ``` This workflow allows the chatbot to return relevant historical solutions with low latency, using similarity search combined with predicate filtering. --- # FILE: components/ahnlich-cli/ahnlich-cli.mdx --- title: Ahnlich CLI sidebar_position: 10 --- import {CliFigure} from '@site/src/components/OverviewFigures'; # Ahnlich CLI **The CLI lets you talk to Ahnlich by typing commands in your terminal — no code required.** You type a short instruction, press Enter, and the server answers right away. It's the fastest way to try Ahnlich and see how it behaves. Under the hood it speaks a small, readable command language (a **Domain-Specific Language, or DSL**) like `PING`, `CREATESTORE books`, and `GETSIMN 3 …`. Each command goes straight to the **Ahnlich DB** or **Ahnlich AI** server and the response comes back in the same window. Think of the CLI as your **playground** for exploring Ahnlich: - **Test queries quickly** without setting up an SDK project - **Experiment with similarity search** and vector operations interactively - **Prototype pipelines** for embedding, storage, and retrieval - **Debug servers locally** before moving to production ## Your first commands Start an interactive session against the DB server and try a few commands: ```bash ahnlich-cli ahnlich --agent db --host 127.0.0.1 --port 1369 ``` ```text > PING < PONG > CREATESTORE books DIMENSION 3 < OK > LISTSTORES < books (0 entries) ``` - `--agent db` talks to Ahnlich DB (port `1369`); use `--agent ai` for the AI proxy (port `1370`). - Type `PING` first — if you get `PONG`, you're connected. > **When _not_ to use the CLI:** it's built for exploring and debugging, not for > production apps. For real integrations use the > [client libraries](/docs/client-libraries) (Python, Node.js, Go, Rust), which > give you richer APIs and proper error handling. --- ## Non-Interactive Mode The CLI supports a **non-interactive mode** via the `--no-interactive` flag, which is ideal for: - **Docker health checks** - Verify server availability in container orchestration - **CI/CD pipelines** - Automate testing and deployment workflows - **Shell scripts** - Integrate Ahnlich commands into automation scripts - **Monitoring systems** - Programmatically check server health and status ### Usage In non-interactive mode, the CLI reads commands from **stdin** and exits immediately after processing: ```bash # Single command via echo echo 'PING' | ahnlich-cli ahnlich --agent db --host 127.0.0.1 --port 1369 --no-interactive # Multiple commands via heredoc ahnlich-cli ahnlich --agent db --host 127.0.0.1 --port 1369 --no-interactive < :::note The CLI is a *client* — it needs a server to talk to. Make sure [Ahnlich DB](/docs/components/ahnlich-db/installation) (or the [AI proxy](/docs/components/ahnlich-ai/setup-config)) is running first. ::: ## Download Binaries Prebuilt binaries are available on [GitHub Releases](https://github.com/deven96/ahnlich/releases). > `wget` is not installed on macOS by default, so the examples below use `curl` (built in). Check your Mac's chip with `uname -m` — `arm64` means Apple Silicon, `x86_64` means Intel. ### Download the binary **macOS — Apple Silicon (M1/M2/M3/M4):** ```bash curl -L -O "https://github.com/deven96/ahnlich/releases/download/bin%2Fcli%2F0.2.1/aarch64-apple-darwin-ahnlich-cli.tar.gz" ``` **macOS — Intel:** ```bash curl -L -O "https://github.com/deven96/ahnlich/releases/download/bin%2Fcli%2F0.2.1/x86_64-apple-darwin-ahnlich-cli.tar.gz" ``` **Linux — x86_64:** ```bash curl -L -O "https://github.com/deven96/ahnlich/releases/download/bin%2Fcli%2F0.2.1/x86_64-unknown-linux-gnu-ahnlich-cli.tar.gz" ``` **Linux — ARM64:** ```bash curl -L -O "https://github.com/deven96/ahnlich/releases/download/bin%2Fcli%2F0.2.1/aarch64-unknown-linux-gnu-ahnlich-cli.tar.gz" ``` ### Extract the Archive #### Extract the downloaded archive ```bash tar -xvzf .tar.gz ``` #### Move the binary to a directory in your PATH (optional) ```bash sudo mv ahnlich-cli /usr/local/bin/ ``` #### Verify installation ```bash ahnlich-cli --version ``` #### Example: Run CLI against DB server First run the DB server: ```bash ahnlich-db run ``` Then run the CLI against the DB server: ```bash ahnlich-cli ahnlich --agent db --host 127.0.0.1 --port 1369 ``` ### Run from Source (Development Mode) Clone the repo and run from the **workspace root**: #### Clone the repo ```bash git clone https://github.com/deven96/ahnlich.git cd ahnlich ``` #### Run the DB Server ```bash cargo run -p db --bin ahnlich-db run ``` #### Run the AI Server ```bash cargo run -p ai --bin ahnlich-ai run ``` #### Run the CLI ```bash cargo run -p cli --bin ahnlich-cli -- ahnlich --agent db --host 127.0.0.1 --port 1369 ``` Replace `db` with `ai` to connect to the AI server ### Running the CLI General command format: ```bash ahnlich-cli ahnlich --agent --host --port ``` - `agent` → `ai` or `db` - `host` → defaults to `127.0.0.1` - `port` → defaults: `1370` (AI), `1369` (DB) #### Example Usage ##### Connect to DB Agent ```bash ahnlich-cli ahnlich --agent db --host 127.0.0.1 --port 1369 ``` ##### Connect to AI Agent ```bash ahnlich-cli ahnlich --agent ai --host 127.0.0.1 --port 1370 ``` --- # FILE: components/ahnlich-db/advanced/choosing-and-performance.md --- title: Choosing & performance sidebar_label: Choosing & performance --- # Choosing an algorithm & performance How the [similarity algorithms](/docs/components/ahnlich-db/advanced/similarity-algorithms) compare, and how they behave under real workloads. ## Choosing the right algorithm | Algorithm | Best for | Pros | Cons | | --- | --- | --- | --- | | Cosine similarity | NLP, semantic search | Ignores magnitude, fast | Not for magnitude-based data | | Euclidean distance | Images, structured numeric features | Intuitive, uses magnitude | Slower in very high dims | | HNSW | High-dim, large-scale datasets | Fast ANN, tunable recall/speed | Approximate, higher memory | ## Performance & trade-offs Ahnlich DB is optimized for real-time similarity search, but algorithms behave differently by **data size, dimensionality, and query type**. ### Cosine similarity - **Speed:** very fast (linear scan). **Accuracy:** high for semantic embeddings. **Memory:** moderate (normalization). - *Benchmark (example):* 1M text embeddings (768-dim BERT) → ~15 ms avg latency (16-core CPU, in-memory), 95% recall@10 vs brute-force. ### Euclidean distance - **Speed:** similar to cosine, slightly heavier math. **Accuracy:** high when magnitude matters. **Memory:** higher if embeddings aren't normalized. - *Benchmark:* 5M product images (512-dim CLIP) → ~25 ms, 93% recall@10. ### HNSW - **Speed:** sub-millisecond even on large datasets. **Accuracy:** approximate but tunable. **Memory:** higher (graph structure). - *Benchmark:* 10K SIFT vectors (128-dim) → <1 ms, 90%+ recall@50 (default), higher when tuned. - **Limitation:** approximate; quality depends on configuration. ## Summary | Algorithm | Speed | Accuracy | Best use case | Weakness | | --- | --- | --- | --- | --- | | Cosine similarity | Fast | High (95%) | Semantic search (NLP, docs) | Ignores magnitude | | Euclidean distance | Moderate | High (93%) | Image search, recommendations | Slower in high dims | | HNSW | Ultra-fast (any dim) | Tunable (80–99%) | Large-scale high-dim search | Approximate, more memory | ## Related - [Similarity algorithms](/docs/components/ahnlich-db/advanced/similarity-algorithms) - [Command deep dive](/docs/components/ahnlich-db/advanced/command-deep-dive) --- # FILE: components/ahnlich-db/advanced/command-deep-dive.md --- title: Command deep dive sidebar_label: Command deep dive --- # Command deep dive A power-user's walkthrough of how commands behave in practice. For the full per-command reference, see [Command reference](/docs/components/ahnlich-db/reference). ## Server management **PING** — test whether the DB is alive. Essential for monitoring. ```text > PING < PONG ``` **INFO SERVER** — server metadata: version, uptime, active stores, tracing status. ```text > INFO SERVER < {"version":"0.0.2","uptime":"3h45m","stores":["docs","images"]} ``` **LIST CONNECTED CLIENTS** — all clients with IP and connection status; useful for debugging distributed workloads. ## Store lifecycle **LIST STORES** — the stores available, with name, entry count, size in bytes, and any non-linear index configuration. **CREATE STORE ``** — a new container for vectors + metadata. Optionally accepts non-linear index configs (HNSW parameters). ```text > CREATE STORE articles < OK ``` **DROP STORE ``** — deletes permanently. Data can't be recovered unless [persistence](/docs/components/persistence-in-ahnlich) is enabled. ## Vector operations **SET** — insert or overwrite a vector + metadata. ```text > SET doc1 [0.12, 0.33, 0.44] WITH {"topic":"ai","visibility":"public"} < OK ``` **GET KEY** — retrieve a vector and metadata by key. ```text > GET KEY doc1 < {"vector":[0.12,0.33,0.44],"metadata":{"topic":"ai","visibility":"public"}} ``` **DELETE KEY** — remove a vector completely. ## Querying & filtering **GET SIM N** — the core similarity query. Finds the N closest vectors, supports linear (`cosine`, `euclidean`) and non-linear (`hnsw`), and can apply metadata filters. ```text > GETSIMN 3 WITH [0.2,0.1,0.7] USING cosinesimilarity IN articles WHERE (visibility = "public") < [{"key":"doc5","score":0.92},{"key":"doc3","score":0.89},{"key":"doc7","score":0.87}] ``` **GET BY PREDICATE** — filter on metadata without similarity search. ```text > GET BY PREDICATE topic = "ai" IN articles ``` **DELETE PREDICATE** — bulk delete by metadata. ```text > DELETE PREDICATE visibility = "hidden" IN articles ``` ## Indexes **CREATE / DROP PREDICATE INDEX** — speed up (or clean up) metadata filtering. ```text > CREATE PREDICATE INDEX ON articles(topic) ``` **CREATE / DROP NON LINEAR ALGORITHM INDEX** — build or remove an HNSW index for nearest-neighbour queries. ```text > CREATE NON LINEAR ALGORITHM INDEX hnsw ON semantic_store ``` ## Related - [Command reference](/docs/components/ahnlich-db/reference) - [End-to-end flow](/docs/components/ahnlich-db/advanced/end-to-end-flow) --- # FILE: components/ahnlich-db/advanced/end-to-end-flow.md --- title: End-to-end flow sidebar_label: End-to-end flow --- # End-to-end flow Putting the pieces together — create a store, insert data, build indexes, and query. ## 1. Create a store ```bash CREATE STORE articles ``` ## 2. Insert data ```bash SET doc1 [0.12,0.33,0.44] WITH {"topic":"ai","visibility":"public"} SET doc2 [0.50,0.61,0.11] WITH {"topic":"finance","visibility":"public"} ``` ## 3. Build indexes ```bash CREATE PREDICATE INDEX ON articles(topic) ``` ## 4. Run queries ```bash GETSIMN 3 WITH [0.20,0.10,0.70] USING cosinesimilarity IN articles WHERE (topic="ai") ``` ## Related - [Command reference](/docs/components/ahnlich-db/reference) - [Similarity algorithms](/docs/components/ahnlich-db/advanced/similarity-algorithms) - [Quickstart: Ahnlich DB](/docs/getting-started/quickstart-db) --- # FILE: components/ahnlich-db/advanced/similarity-algorithms.md --- title: Similarity algorithms sidebar_label: Similarity algorithms --- # Similarity algorithms Ahnlich DB compares vectors with several algorithms — two **linear** metrics and a **non-linear** index (HNSW). The right one depends on your data and query needs. For the plain-language concepts, see [Similarity metrics](/docs/concepts/similarity-metrics) and [Vector index](/docs/concepts/vector-index); this page is the DB-specific, command-level detail. ## Cosine similarity (linear) Measures the cosine of the **angle** between two vectors — orientation, not magnitude. **Use it for** text embeddings and other high-dimensional data where magnitude doesn't matter. ```bash GETSIMN 3 WITH [0.25, 0.88] USING cosinesimilarity IN my_store ``` *Example:* query "What's the capital of France?" against document embeddings — cosine retrieves the doc most semantically aligned with "Paris". ## Euclidean distance (linear) Measures the straight-line (L2) **distance** between two vectors — magnitude matters. **Use it for** image embeddings and recommendation engines where closeness in feature space is meaningful. ```bash GETSIMN 5 WITH [0.12, 0.45] USING euclidean IN image_store ``` *Example:* searching for visually similar product images — a handbag photo returns similar handbags. ## HNSW (non-linear) A graph-based **approximate** nearest-neighbour (ANN) search that narrows candidates through hierarchical layers. **Use it for** high-dimensional embeddings (100+ dims) and large-scale datasets where a small recall trade-off buys big speed gains — semantic search, recommendations, and image retrieval at scale. ### Configuration | Parameter | Default | Description | | --- | --- | --- | | `ef_construction` | 100 | Search breadth while building. Higher = better recall, slower inserts. | | `maximum_connections` (M) | 48 | Max connections per node above layer 0. Higher = more memory, better recall. | | `maximum_connections_zero` | 96 | Max connections at layer 0 (typically 2×M). | | `extend_candidates` | false | Expand the candidate pool with neighbours' neighbours. | | `keep_pruned_connections` | false | Retain pruned connections for higher connectivity. | | `distance` | Euclidean | `Euclidean`, `Cosine`, or `DotProduct`. | ```bash CREATE NON LINEAR ALGORITHM INDEX hnsw IN semantic_store ``` ```python db_client.create_store( store="semantic_store", dimension=384, create_predicates=["category"], non_linear_indices=[ NonLinearIndex(index=HnswConfig( distance=DistanceMetric.Cosine, ef_construction=200, maximum_connections=32, maximum_connections_zero=64, )) ], error_if_exists=True, ) ``` ```bash GETSIMN 10 WITH [0.12, 0.45, ...] USING hnsw IN semantic_store ``` ### Tuning tips - **Low recall?** Increase `ef_construction` and `maximum_connections` for a denser graph. - **Slow inserts?** Decrease `ef_construction` for faster builds at the cost of recall. - **Memory constrained?** Lower `maximum_connections`. - **Bad config?** Drop the index and recreate it — existing data is re-indexed automatically. ## Related - [Choosing & performance](/docs/components/ahnlich-db/advanced/choosing-and-performance) - [Vector index](/docs/concepts/vector-index) - [Similarity metrics](/docs/concepts/similarity-metrics) --- # FILE: components/ahnlich-db/advanced.md --- title: Advanced sidebar_position: 30 --- # Advanced Deeper, power-user material for Ahnlich DB — how similarity algorithms work at the command level, how to choose between them, and how the pieces fit together end-to-end. ## In this section - **[Similarity algorithms](/docs/components/ahnlich-db/advanced/similarity-algorithms)** — the linear metrics (cosine, euclidean) and the non-linear HNSW index, with configuration and tuning. - **[Choosing & performance](/docs/components/ahnlich-db/advanced/choosing-and-performance)** — comparison tables and benchmark-style trade-offs by data size and dimensionality. - **[Command deep dive](/docs/components/ahnlich-db/advanced/command-deep-dive)** — how each command behaves in practice, for power users. - **[End-to-end flow](/docs/components/ahnlich-db/advanced/end-to-end-flow)** — create a store, insert data, build indexes, and query, start to finish. ## Related - [Command reference](/docs/components/ahnlich-db/reference) - [Vector index](/docs/concepts/vector-index) - [Similarity metrics](/docs/concepts/similarity-metrics) --- # FILE: components/ahnlich-db/ahnlich-db.mdx --- title: Ahnlich DB sidebar_position: 10 --- import {DbOverviewFigure} from '@site/src/components/OverviewFigures'; # Ahnlich DB **Ahnlich DB is a database for _meaning_.** A normal database is great at exact matches — "find the user whose email is `x`". Ahnlich DB is built for a different question: **"find the things that are _most similar_ to this one."** Similar photos, similar products, documents about the same topic, songs with the same vibe. It does this by storing your data as **vectors** (lists of numbers that capture meaning) and finding the ones that sit closest together. It keeps everything **in memory**, so answers come back in milliseconds. ## The two things you store Every item you put in Ahnlich DB has two parts: 1. **A vector** — a list of numbers, e.g. `[0.12, 0.98, 0.41, …]`. A machine-learning model produces this from your raw data (a sentence, an image, a product). Items that _mean_ similar things end up with vectors that are close together. New to this idea? Start with [Vectors & embeddings](/docs/concepts/vectors). 2. **Metadata** — plain key–value labels attached to the vector, like `{"genre": "sci-fi", "year": 1968}`. You use these to filter results. If you don't want to generate vectors yourself, the [Ahnlich AI proxy](/docs/components/ahnlich-ai) does it for you — you send text, images, or audio and it handles the numbers. ## What you can do with it ### Find the nearest matches (similarity search) Give Ahnlich DB a vector and ask for the `N` closest items. This powers semantic search, recommendations, and "more like this" features. ```bash GETSIMN 3 WITH [0.23, 0.91, -0.44] USING cosinesimilarity IN article_store ``` > Returns the 3 most similar articles. `cosinesimilarity` is one way to measure > "closeness" — see [Similarity metrics](/docs/concepts/similarity-metrics). ### Filter by metadata at the same time Combine "closest" with "matches these labels", so results are relevant _and_ correct. ```bash GETSIMN 5 WITH [0.11, 0.75, -0.32] USING euclideandistance IN music_store WHERE (genre = "jazz") ``` > The 5 most similar songs — but only jazz ones. ### Stay fast as you grow Because everything lives in memory, lookups are quick enough for live features like chat assistants or real-time recommendations. When a store gets large, you can add a [vector index (HNSW)](/docs/concepts/vector-index) to keep searches fast. ## A first end-to-end example ```bash CREATESTORE books DIMENSION 3 # 1. make a store for 3-number vectors SET book1 [0.12, 0.98, 0.41] WITH {"genre": "sci-fi"} # 2. add an item GETSIMN 2 WITH [0.10, 0.95, 0.44] USING cosinesimilarity IN books # 3. search ``` That's the whole loop: **create a store → add vectors → search for the nearest ones.** ## Where to go next - [Quickstart: Ahnlich DB](/docs/getting-started/quickstart-db) — a hands-on walkthrough with a relatable example. - [Command reference](/docs/components/ahnlich-db/reference) — every command, explained. - [Concepts](/docs/concepts/data-model) — the ideas (vectors, metadata, similarity, indexes) in plain language. - [Advanced](/docs/components/ahnlich-db/advanced) — algorithms, tuning, and performance. --- # FILE: components/ahnlich-db/installation.mdx --- title: Installation sidebar_position: 20 --- import {InstallFigure} from '@site/src/components/OverviewFigures'; # Installation **Goal: get an Ahnlich DB server running on your machine.** Once it's running and answers `PING` with `PONG`, you're ready to create stores and run searches. There are three ways to install it — pick whichever suits you: | Option | Best if you… | Where | | --- | --- | --- | | **Download a binary** | just want it running fast | [below](#1-download-binaries) | | **Docker** | prefer an isolated, reproducible setup | [below](#2-using-docker) | | **Build from source** | are developing on Ahnlich itself | [CLI install → from source](/docs/components/ahnlich-cli/installation) | The rest of this page walks through each option, then a quick smoke test at the end. ### 1. Download Binaries Prebuilt binaries are available from [GitHub Releases](https://github.com/deven96/ahnlich/releases). > `wget` is not installed on macOS by default, so the examples below use `curl` (built in). Check your Mac's chip with `uname -m` — `arm64` means Apple Silicon, `x86_64` means Intel. #### Download the binary **macOS — Apple Silicon (M1/M2/M3/M4):** ```bash curl -L -O "https://github.com/deven96/ahnlich/releases/download/bin%2Fdb%2F0.2.2/aarch64-apple-darwin-ahnlich-db.tar.gz" ``` **macOS — Intel:** ```bash curl -L -O "https://github.com/deven96/ahnlich/releases/download/bin%2Fdb%2F0.2.2/x86_64-apple-darwin-ahnlich-db.tar.gz" ``` **Linux — x86_64:** ```bash curl -L -O "https://github.com/deven96/ahnlich/releases/download/bin%2Fdb%2F0.2.2/x86_64-unknown-linux-gnu-ahnlich-db.tar.gz" ``` **Linux — ARM64:** ```bash curl -L -O "https://github.com/deven96/ahnlich/releases/download/bin%2Fdb%2F0.2.2/aarch64-unknown-linux-gnu-ahnlich-db.tar.gz" ``` #### Extract the archive (use the file you downloaded): ```bash tar -xvzf *-ahnlich-db.tar.gz ``` #### Run the binary: ```bash ./ahnlich-db ``` > On macOS, if Gatekeeper blocks the unsigned binary, clear the quarantine flag first: `xattr -d com.apple.quarantine ./ahnlich-db` ### 2. Using Docker Ahnlich DB also ships as a Docker image: ``` docker pull ghcr.io/deven96/ahnlich-db:latest ``` #### Run with: ``` docker run --rm -p 1369:1369 \ --name ahnlich-db \ ghcr.io/deven96/ahnlich-db:latest \ ahnlich-db run --port 1369 ``` ### 3. Example Docker Compose You can orchestrate Ahnlich DB with **docker-compose**. #### Basic (with tracing enabled): ``` services: ahnlich_db: image: ghcr.io/deven96/ahnlich-db:latest command: > "ahnlich-db run --host 0.0.0.0 --enable-tracing --otel-endpoint http://jaeger:4317" ports: - "1369:1369" # Optional Jaeger service for tracing jaeger: image: jaegertracing/all-in-one:${JAEGER_VERSION:-latest} ports: - "16686:16686" - "4317:4317" - "4318:4318" ``` #### With Persistence: ``` services: ahnlich_db: image: ghcr.io/deven96/ahnlich-db:latest command: > "ahnlich-db run --host 0.0.0.0 --enable-persistence --persist-location /root/.ahnlich/data/db.dat --persistence-interval 300" ports: - "1369:1369" volumes: - "./data/:/root/.ahnlich/data" # Persistence Location ``` #### Configuration Options Ahnlich DB can be customized using runtime flags: - `--host ` - Specify listening host (default: `0.0.0.0`). - `--port ` - Specify server port (default: `1369`). - `--enable-tracing` - Enable telemetry tracing with OpenTelemetry. - `--otel-endpoint ` - OpenTelemetry endpoint (e.g., Jaeger). - `--enable-persistence` - Enable snapshot persistence to disk. - `--persist-location ` - File location for persistence (default: ~/.ahnlich/data/db.dat). - `--persistence-interval ` - Interval in seconds between snapshots. ### Quick Start Example #### Start a simple database: ``` ./ahnlich-db run --host 0.0.0.0 --port 1369 ``` #### Create a store: ``` CREATESTORE my_store DIMENSION 2 ``` #### Insert a vector with metadata: ``` SET [0.2, 0.1] WITH { "page": "home" } IN my_store ``` #### Run a similarity search: ``` GETSIMN 2 WITH [0.2, 0.1] USING cosinesimilarity IN my_store WHERE (page != hidden) ``` --- # FILE: components/ahnlich-db/reference/create-non-linear-index.md --- title: CREATE NON LINEAR ALGORITHM INDEX sidebar_label: CREATE NON LINEAR ALGORITHM INDEX --- # CREATE NON LINEAR ALGORITHM INDEX Build a [vector index](/docs/concepts/vector-index) — `hnsw` (approximate, high-dim) — to accelerate similarity search. ## Syntax ```bash CREATE NON LINEAR ALGORITHM INDEX IN CREATE NON LINEAR ALGORITHM INDEX IN SCHEMA analytics ``` ## Example ```bash CREATE NON LINEAR ALGORITHM INDEX hnsw IN my_store ``` --- See [SDK examples](/docs/stores/create-non-linear-algx) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-db/reference/create-predicate-index.md --- title: CREATE PREDICATE INDEX sidebar_label: CREATE PREDICATE INDEX --- # CREATE PREDICATE INDEX Index a metadata field so predicate filters run as a direct lookup instead of a full scan. ## Syntax ```bash CREATE PREDICATE INDEX IN CREATE PREDICATE INDEX IN SCHEMA analytics ``` ## Example ```bash CREATE PREDICATE INDEX category IN my_store ``` --- See [SDK examples](/docs/stores/create-predicate-index) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-db/reference/create-store.md --- title: CREATESTORE sidebar_label: CREATESTORE --- # CREATESTORE Create a new store with a fixed **dimension** — the length every vector in it must have. ## Syntax ```bash CREATESTORE DIMENSION CREATESTORE DIMENSION SCHEMA analytics ``` ## Example ```bash CREATESTORE my_store DIMENSION 128 ``` --- See [SDK examples](/docs/stores/create-store) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-db/reference/delete-key.md --- title: DELETE KEY sidebar_label: DELETE KEY --- # DELETE KEY Delete a vector by its exact key. ## Syntax ```bash DELETE KEY IN DELETE KEY IN SCHEMA analytics ``` ## Example ```bash DELETE KEY doc1 IN my_store ``` --- See [SDK examples](/docs/stores/delete-key) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-db/reference/delete-predicate.md --- title: DELETE PREDICATE sidebar_label: DELETE PREDICATE --- # DELETE PREDICATE Delete **all** vectors whose metadata matches a predicate. ## Syntax ```bash DELETE PREDICATE IN DELETE PREDICATE IN SCHEMA analytics ``` ## Example ```bash DELETE PREDICATE (category = "archive") IN my_store ``` --- See [SDK examples](/docs/stores/delete-predicate) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-db/reference/drop-non-linear-index.md --- title: DROP NON LINEAR ALGORITHM INDEX sidebar_label: DROP NON LINEAR ALGORITHM INDEX --- # DROP NON LINEAR ALGORITHM INDEX Remove a non-linear index. Similarity search still works, falling back to a linear scan. ## Syntax ```bash DROP NON LINEAR ALGORITHM INDEX IN DROP NON LINEAR ALGORITHM INDEX IN SCHEMA analytics ``` ## Example ```bash DROP NON LINEAR ALGORITHM INDEX hnsw IN my_store ``` --- See [SDK examples](/docs/stores/create-non-linear-algx) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-db/reference/drop-predicate-index.md --- title: DROP PREDICATE INDEX sidebar_label: DROP PREDICATE INDEX --- # DROP PREDICATE INDEX Remove a predicate index. The data stays; queries just lose the speed-up. ## Syntax ```bash DROP PREDICATE INDEX IN DROP PREDICATE INDEX IN SCHEMA analytics ``` ## Example ```bash DROP PREDICATE INDEX category IN my_store ``` --- See [SDK examples](/docs/stores/drop-predicate-index) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-db/reference/drop-schema.md --- title: DROPSCHEMA sidebar_label: DROPSCHEMA --- # DROPSCHEMA Remove a non-public schema **and every store inside it**. The `public` schema cannot be dropped. ## Syntax ```bash DROPSCHEMA ``` ## Example ```bash DROPSCHEMA analytics ``` --- See [SDK examples](/docs/concepts/schemas) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-db/reference/drop-store.md --- title: DROPSTORE sidebar_label: DROPSTORE --- # DROPSTORE Permanently delete a store and all its contents. Add `IF EXISTS` to avoid an error when it's already gone. ## Syntax ```bash DROPSTORE DROPSTORE IF EXISTS SCHEMA analytics ``` ## Example ```bash DROPSTORE my_store IF EXISTS ``` --- See [SDK examples](/docs/stores/drop-store) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-db/reference/get-by-predicate.md --- title: GET BY PREDICATE sidebar_label: GET BY PREDICATE --- # GET BY PREDICATE Retrieve every entry whose metadata satisfies a predicate — filtering by attributes, not by vector. ## Syntax ```bash GET BY PREDICATE () IN GET BY PREDICATE () IN SCHEMA analytics ``` ## Example ```bash GET BY PREDICATE (lang = "en") IN my_store ``` --- See [SDK examples](/docs/stores/get-by-predicate) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-db/reference/get-key.md --- title: GETKEY sidebar_label: GETKEY --- # GETKEY Retrieve a vector and its metadata by exact key — no similarity ranking. ## Syntax ```bash GETKEY ([, ]) IN GETKEY ([, ]) IN SCHEMA analytics ``` ## Example ```bash GETKEY ([1.0, 2.0]) IN my_store ``` --- See [SDK examples](/docs/stores/get-key) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-db/reference/get-sim-n.md --- title: GETSIMN sidebar_label: GETSIMN --- # GETSIMN Find the **N** most similar vectors to an input vector, ranked by a similarity metric. Optionally restrict candidates with a predicate. ## Syntax ```bash GETSIMN WITH [, ...] USING IN WHERE () ``` ## Parameters - `USING` picks the algorithm — see [Vector index](/docs/concepts/vector-index) and [Similarity metrics](/docs/concepts/similarity-metrics). ## Example ```bash GETSIMN 3 WITH [0.25, 0.88] USING cosinesimilarity IN my_store WHERE (category != "draft") ``` --- See [SDK examples](/docs/stores/get-simn) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-db/reference/get-store.md --- title: GETSTORE sidebar_label: GETSTORE --- # GETSTORE Get the same details as LISTSTORES, but for a single store. Errors if it doesn't exist. ## Syntax ```bash GETSTORE GETSTORE SCHEMA analytics ``` ## Example ```bash GETSTORE my_store ``` --- See [SDK examples](/docs/stores/get-store) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-db/reference/infoserver.md --- title: INFOSERVER sidebar_label: INFOSERVER --- # INFOSERVER Retrieve server metadata — version, uptime, and memory usage. ## Syntax ```bash INFOSERVER ``` ## Example ```bash INFOSERVER ``` --- See [SDK examples](/docs/stores/info-server) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-db/reference/list-connected-clients.md --- title: LIST CONNECTED CLIENTS sidebar_label: LIST CONNECTED CLIENTS --- # LIST CONNECTED CLIENTS List the clients currently connected to the server, including their addresses. ## Syntax ```bash LIST CONNECTED CLIENTS ``` ## Example ```bash LIST CONNECTED CLIENTS ``` --- See [SDK examples](/docs/stores/list-connected-clients) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-db/reference/list-stores.md --- title: LISTSTORES sidebar_label: LISTSTORES --- # LISTSTORES Show the stores in a schema — name, entry count, size, predicate indices, dimension, and non-linear index config. ## Syntax ```bash LISTSTORES LISTSTORES SCHEMA analytics ``` ## Parameters - If `SCHEMA` is omitted, only stores in `public` are returned. ## Example ```bash LISTSTORES ``` --- See [SDK examples](/docs/stores/list-stores) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-db/reference/ping.md --- title: PING sidebar_label: PING --- # PING Check that the server is alive and responding. ## Syntax ```bash PING ``` ## Example ```bash PING # → PONG ``` --- See [SDK examples](/docs/stores/ping) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-db/reference/sdk-mapping.mdx --- title: Command → SDK map sidebar_label: Command → SDK map --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Command → SDK map Everything you can type as a **DSL command** (in the CLI) has a matching **method call** in each client library. They talk to the exact same server and do the exact same thing — the CLI is just handy for exploring, and the SDKs are how you build. This page is the **Rosetta Stone**: find a command, see its equivalent in your language. Pick your language once with the tabs below and it stays selected across the whole page. :::note Signatures here are **simplified** to show the shape of each call. For full, runnable examples (imports, request objects, error handling), follow each command's [Stores operation page](/docs/stores) and choose the **Database engine** tab. ::: ## One operation, every language The same "create a store" operation, side by side: ```bash CREATESTORE my_store DIMENSION 128 ``` ```python client.create_store("my_store", 128) ``` ```javascript await client.createStore("my_store", 128); ``` ```go client.CreateStore(ctx, "my_store", 128) ``` ```rust client.create_store("my_store", 128)?; ``` ## Full command map #### Server & system | Command | Python | | --- | --- | | `PING` | `client.ping()` | | `INFO SERVER` | `client.info_server()` | | `LIST CONNECTED CLIENTS` | `client.list_clients()` | #### Store management | Command | Python | | --- | --- | | `LIST STORES` | `client.list_stores()` | | `GETSTORE my_store` | `client.get_store("my_store")` | | `CREATESTORE my_store DIMENSION 128` | `client.create_store("my_store", 128)` | | `DROPSTORE my_store` | `client.drop_store("my_store")` | #### Data operations | Command | Python | | --- | --- | | `SET doc1 [0.25,0.88] WITH {...} IN my_store` | `client.set("my_store", "doc1", [0.25, 0.88], {"category": "news"})` | | `DELKEY doc1 IN my_store` | `client.delete_key("my_store", "doc1")` | | `DELPRED (category='archive') IN my_store` | `client.delete_predicate("my_store", "category='archive'")` | #### Query & retrieval | Command | Python | | --- | --- | | `GETSIMN 3 WITH [...] USING cosine IN my_store` | `client.get_sim_n("my_store", [0.25, 0.88], 3, "cosine", predicate="lang='en'")` | | `GETKEY doc1 IN my_store` | `client.get_key("my_store", "doc1")` | | `GETPRED (lang='en') IN my_store` | `client.get_by_predicate("my_store", "lang='en'")` | #### Index management | Command | Python | | --- | --- | | `CREATEPREDINDEX (category) IN my_store` | `client.create_predicate_index("my_store", "category")` | | `DROPPREDINDEX (category) IN my_store` | `client.drop_predicate_index("my_store", "category")` | | `CREATENONLINEARALGORITHMINDEX (hnsw) IN my_store` | `client.create_non_linear_algorithm_index("my_store", NonLinearIndex(hnsw=HNSWConfig()))` | | `DROPNONLINEARALGORITHMINDEX (hnsw) IN my_store` | `client.drop_non_linear_algorithm_index("my_store", "hnsw")` | #### Server & system | Command | Node.js | | --- | --- | | `PING` | `await client.ping()` | | `INFO SERVER` | `await client.infoServer()` | | `LIST CONNECTED CLIENTS` | `await client.listClients()` | #### Store management | Command | Node.js | | --- | --- | | `LIST STORES` | `await client.listStores()` | | `GETSTORE my_store` | `await client.getStore("my_store")` | | `CREATESTORE my_store DIMENSION 128` | `await client.createStore("my_store", 128)` | | `DROPSTORE my_store` | `await client.dropStore("my_store")` | #### Data operations | Command | Node.js | | --- | --- | | `SET doc1 [0.25,0.88] WITH {...} IN my_store` | `await client.set("my_store", "doc1", [0.25, 0.88], {category: "news"})` | | `DELKEY doc1 IN my_store` | `await client.delKey("my_store", "doc1")` | | `DELPRED (category='archive') IN my_store` | `await client.delPred("my_store", "category='archive'")` | #### Query & retrieval | Command | Node.js | | --- | --- | | `GETSIMN 3 WITH [...] USING cosine IN my_store` | `await client.getSimN("my_store", [0.25, 0.88], 3, "cosine", "lang='en'")` | | `GETKEY doc1 IN my_store` | `await client.getKey("my_store", "doc1")` | | `GETPRED (lang='en') IN my_store` | `await client.getPred("my_store", "lang='en'")` | #### Index management | Command | Node.js | | --- | --- | | `CREATEPREDINDEX (category) IN my_store` | `await client.createPredIndex("my_store", "category")` | | `DROPPREDINDEX (category) IN my_store` | `await client.dropPredIndex("my_store", "category")` | | `CREATENONLINEARALGORITHMINDEX (hnsw) IN my_store` | `await client.createNonLinearAlgorithmIndex("my_store", hnsw)` | | `DROPNONLINEARALGORITHMINDEX (hnsw) IN my_store` | `await client.dropNonLinearAlgorithmIndex("my_store", "hnsw")` | #### Server & system | Command | Go | | --- | --- | | `PING` | `client.Ping(ctx)` | | `INFO SERVER` | `client.InfoServer(ctx)` | | `LIST CONNECTED CLIENTS` | `client.ListClients(ctx)` | #### Store management | Command | Go | | --- | --- | | `LIST STORES` | `client.ListStores(ctx)` | | `GETSTORE my_store` | `client.GetStore(ctx, "my_store")` | | `CREATESTORE my_store DIMENSION 128` | `client.CreateStore(ctx, "my_store", 128)` | | `DROPSTORE my_store` | `client.DropStore(ctx, "my_store")` | #### Data operations | Command | Go | | --- | --- | | `SET doc1 [0.25,0.88] WITH {...} IN my_store` | `client.Set(ctx, "my_store", "doc1", []float64{0.25, 0.88}, map[string]string{"category": "news"})` | | `DELKEY doc1 IN my_store` | `client.DeleteKey(ctx, "my_store", "doc1")` | | `DELPRED (category='archive') IN my_store` | `client.DeletePredicate(ctx, "my_store", "category='archive'")` | #### Query & retrieval | Command | Go | | --- | --- | | `GETSIMN 3 WITH [...] USING cosine IN my_store` | `client.GetSimN(ctx, "my_store", []float64{0.25, 0.88}, 3, "cosine", "lang='en'")` | | `GETKEY doc1 IN my_store` | `client.GetKey(ctx, "my_store", "doc1")` | | `GETPRED (lang='en') IN my_store` | `client.GetByPredicate(ctx, "my_store", "lang='en'")` | #### Index management | Command | Go | | --- | --- | | `CREATEPREDINDEX (category) IN my_store` | `client.CreatePredicateIndex(ctx, "my_store", "category")` | | `DROPPREDINDEX (category) IN my_store` | `client.DropPredicateIndex(ctx, "my_store", "category")` | | `CREATENONLINEARALGORITHMINDEX (hnsw) IN my_store` | `client.CreateNonLinearAlgorithmIndex(ctx, "my_store", NonLinearIndex_Hnsw)` | | `DROPNONLINEARALGORITHMINDEX (hnsw) IN my_store` | `client.DropNonLinearAlgorithmIndex(ctx, "my_store", "hnsw")` | #### Server & system | Command | Rust | | --- | --- | | `PING` | `client.ping()?;` | | `INFO SERVER` | `client.info_server()?;` | | `LIST CONNECTED CLIENTS` | `client.list_clients()?;` | #### Store management | Command | Rust | | --- | --- | | `LIST STORES` | `client.list_stores()?;` | | `GETSTORE my_store` | `client.get_store("my_store")?;` | | `CREATESTORE my_store DIMENSION 128` | `client.create_store("my_store", 128)?;` | | `DROPSTORE my_store` | `client.drop_store("my_store")?;` | #### Data operations | Command | Rust | | --- | --- | | `SET doc1 [0.25,0.88] WITH {...} IN my_store` | `client.set("my_store", "doc1", vec![0.25, 0.88], hashmap!{"category" => "news"})?;` | | `DELKEY doc1 IN my_store` | `client.delete_key("my_store", "doc1")?;` | | `DELPRED (category='archive') IN my_store` | `client.delete_predicate("my_store", "category='archive'")?;` | #### Query & retrieval | Command | Rust | | --- | --- | | `GETSIMN 3 WITH [...] USING cosine IN my_store` | `client.get_sim_n("my_store", vec![0.25, 0.88], 3, "cosine", Some("lang='en'"))?;` | | `GETKEY doc1 IN my_store` | `client.get_key("my_store", "doc1")?;` | | `GETPRED (lang='en') IN my_store` | `client.get_by_predicate("my_store", "lang='en'")?;` | #### Index management | Command | Rust | | --- | --- | | `CREATEPREDINDEX (category) IN my_store` | `client.create_predicate_index("my_store", "category")?;` | | `DROPPREDINDEX (category) IN my_store` | `client.drop_predicate_index("my_store", "category")?;` | | `CREATENONLINEARALGORITHMINDEX (hnsw) IN my_store` | `client.create_non_linear_algorithm_index("my_store", NonLinearIndex { hnsw })?;` | | `DROPNONLINEARALGORITHMINDEX (hnsw) IN my_store` | `client.drop_non_linear_algorithm_index("my_store", "hnsw")?;` | ## Next steps - [Command reference](/docs/components/ahnlich-db/reference) — what each command does. - [Client libraries](/docs/client-libraries) — install and set up your SDK. - [Stores operations](/docs/stores) — full runnable examples per operation. --- # FILE: components/ahnlich-db/reference/set.md --- title: SET sidebar_label: SET --- # SET Insert or update one or more vectors with metadata. Re-inserting an existing key overwrites its value. ## Syntax ```bash SET [, ...] WITH { "": "", ... } IN SET [, ...] WITH { ... } IN SCHEMA analytics ``` ## Parameters - The vector length must match the store's dimension. ## Example ```bash SET doc1 [0.25, 0.88] WITH { "category": "news", "lang": "en" } IN my_store ``` --- See [SDK examples](/docs/stores/set) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-db/reference/upsert.md --- title: UPSERT sidebar_label: UPSERT --- # UPSERT Update a **single** entry matched by a predicate — replace its vector, its metadata, or both. Errors if zero or multiple entries match. ## Syntax ```bash UPSERT [MERGE] [KEY ] [VALUE ] IN [SCHEMA ] WHERE () ``` ## Parameters - `MERGE` — merge new metadata into existing fields (default: replace). - `KEY ` — a new vector to replace the matched key. - `VALUE ` — metadata to update. - `WHERE ()` — must match exactly one entry. ## Example ```bash UPSERT VALUE {status: published} IN articles WHERE (id = 123) UPSERT MERGE VALUE {updated_at: 2026-07-02} IN articles WHERE (id = 123) UPSERT KEY [0.1, 0.2, 0.3] IN embeddings WHERE (doc_id = abc) ``` --- See [SDK examples](/docs/stores/upsert) for Python, Node, Go, and Rust. --- # FILE: components/ahnlich-db/reference.md --- title: Command reference sidebar_label: Overview sidebar_position: 30 --- # DB command reference Every command Ahnlich DB understands, one page each, grouped by what it does. These are the **text-query / CLI** forms; for full, runnable **SDK** examples in Python, Node, Go, and Rust, each page links to its matching [Stores](/docs/stores) operation. ## Server & system - [PING](./reference/ping) · [INFOSERVER](./reference/infoserver) · [LIST CONNECTED CLIENTS](./reference/list-connected-clients) ## Store management - [LISTSTORES](./reference/list-stores) · [GETSTORE](./reference/get-store) · [CREATESTORE](./reference/create-store) · [DROPSTORE](./reference/drop-store) · [DROPSCHEMA](./reference/drop-schema) ## Data operations - [SET](./reference/set) · [UPSERT](./reference/upsert) · [DELETE KEY](./reference/delete-key) · [DELETE PREDICATE](./reference/delete-predicate) ## Query & retrieval - [GETSIMN](./reference/get-sim-n) · [GETKEY](./reference/get-key) · [GET BY PREDICATE](./reference/get-by-predicate) ## Index management - [CREATE PREDICATE INDEX](./reference/create-predicate-index) · [DROP PREDICATE INDEX](./reference/drop-predicate-index) · [CREATE NON LINEAR ALGORITHM INDEX](./reference/create-non-linear-index) · [DROP NON LINEAR ALGORITHM INDEX](./reference/drop-non-linear-index) ## Reference - [Command → SDK map](./reference/sdk-mapping) — every command next to its Rust / Python / Go call. --- **How to read this reference** - `UPPERCASE` is a keyword; `` are values you supply. - `[SCHEMA ]` is optional — omit it to target the `public` [schema](/docs/concepts/schemas). - Every command works over the text query interface, the [CLI](/docs/components/ahnlich-cli), or a client SDK. --- # FILE: components/ahnlich-db/use-cases.mdx --- title: Use Cases sidebar_position: 30 --- import {UseCasesFigure} from '@site/src/components/OverviewFigures'; # Use Cases Ahnlich DB looks flexible, but almost everything you'd build with it is **the same recipe**: turn your data into vectors, find the nearest ones, and (optionally) filter by metadata. Once that clicks, the examples below are just variations on that single idea. ### 1. Semantic Search Semantic search goes beyond keyword matching it finds items that are **conceptually similar** to a query. With **Ahnlich DB**, embeddings generated by NLP models (e.g., sentence-transformers, OpenAI, BERT) can be stored and queried. **Example:** A news website wants to allow readers to search for articles by meaning, not just keywords. - **Query:** “climate change effects on agriculture” - **Stored vectors:** embeddings of articles from the archive. - **Ahnlich DB** retrieves the most similar vectors using **cosine similarity**, filtering out irrelevant categories (e.g., `topic != sports`). ``` GETSIMN 5 WITH [0.42, -0.13, 0.76, ...] USING cosinesimilarity IN news_store WHERE (topic != sports) ``` This returns the top 5 most semantically similar articles, even if the exact keywords differ. ### 2. Recommendation Engines Recommendation systems depend on finding similar users or items. By storing user embeddings and item embeddings, **Ahnlich DB** can power **real-time recommendations**. **Example:** An e-commerce platform wants to recommend products to a user based on their browsing history. - **User embedding:** **[0.24, 0.15, 0.93, ...]** (from their profile + past behavior). - **Product embeddings:** vectors representing catalog items. - **Query:** Find the 10 most similar products to the user’s embedding. ``` get_sim_n( store="product_store", search_input=[0.24, 0.15, 0.93, ...], closest_n=10, algorithm=CosineSimilarity, condition=Predicate::NotEquals { key="status", value="out_of_stock", } ) ``` This returns **personalized recommendations**, excluding unavailable items. ### 3. Clustering & Exploration Clustering groups together vectors that are close in high-dimensional space. **Ahnlich DB** makes this possible by serving as a **backbone for unsupervised learning workflows**. **Example:** A research lab stores embeddings of thousands of genomic sequences. To discover patterns: - Query vectors representing sequences with certain traits. - Run similarity searches to group them into clusters. - Use metadata filters (`species = human`) to narrow analysis. This allows scientists to explore relationships between data points without explicit labels. ### 4. Filtering with Context **Ahnlich DB** allows developers to combine **vector similarity search** with **predicate-based filtering** to return results that are both semantically relevant and contextually constrained. **Example:** A video platform wants to recommend “similar” videos but only those uploaded in the last 12 months. ``` find top-8 similar videos GETSIMN 8 WITH [0.89, -0.33, 0.55, ...] USING euclideandistance IN video_store; filter results by upload_date GETPRED (upload_date = 2024-09-01) IN video_store ``` In this workflow, GETSIMN identifies the most similar vectors, while `GETPRED` applies metadata conditions. By chaining them together, applications ensure that retrieved results are not only “similar” but also satisfy real-world constraints such as **time windows, categories, or user-specific rules**. ### 5. Cross-Domain Retrieval Since embeddings can represent any modality—text, images, audio—**Ahnlich DB** supports **multi-modal retrieval**. Example: A fashion company wants customers to upload a **photo of an outfit** and find similar products in their catalog. - Image converted into an embedding. - Query against the vector database (`fashion_store`). - Metadata filter ensures only items in stock are suggested. ``` GETSIMN 3 WITH [0.31, 0.88, 0.64, ...] USING cosinesimilarity IN fashion_store WHERE (availability = "in_stock") This enables image-to-product search, a powerful e-commerce feature. ``` --- # FILE: components/components.mdx --- id: components title: 🧩 Components --- import CustomDocCard from '@site/src/components/CustomDocCard'; export const components = [ { title: "Ahnlich CLI", icon: "terminal", link: "/docs/components/ahnlich-cli" }, { title: 'Ahnlich DB', icon: "database", link: "/docs/components/ahnlich-db" }, { title: 'Ahnlich AI', icon: "sparkles", link: "/docs/components/ahnlich-ai" }, { title: 'Persistence in Ahnlich', icon: "save", link: "/docs/components/persistence-in-ahnlich" }, { title: 'Distributed Tracing', icon: "branch", link: "/docs/components/distributed-tracing" } ]; # Components Ahnlich is a suite of tools that can be used to build a variety of applications. This page lists the components that are available in Ahnlich.
{components.map((component) => ( ))}
--- # FILE: components/distributed-tracing/ahnlich-ai.md --- title: Tracing in Ahnlich AI --- # Tracing in Ahnlich AI ### Why would you want to trace? With AI tracing, you can: - Measure **embedding computation** times for different models (e.g., `resnet-50` vs `all-MiniLM`). - Track full **similarity searches** (`GETSIMN`) including preprocessing and DB lookups. - Debug performance issues with **execution providers** or **index algorithms**. - Correlate AI spans with DB spans for full pipeline visibility. ### How we support distributed tracing - Enable tracing: `--enable-tracing`. - Forward spans: `--otel-endpoint http://jaeger:4317`. - Each AI action generates spans for: - Preprocessing - Embedding generation - Similarity algorithms - Downstream DB calls ### Example docker-compose.yaml ``` ahnlich_ai: image: ghcr.io/deven96/ahnlich-ai:latest command: > "ahnlich-ai run --db-host ahnlich_db --host 0.0.0.0 \ --supported-models all-minilm-l6-v2,resnet-50 \ --enable-tracing \ --otel-endpoint http://jaeger:4317" ports: - "1370:1370" ``` ## Viewing AI Traces in Jaeger 1. Open http://localhost:16686. 2. Select `service = ahnlich_ai`. 3. Click **Find Traces**. 4. Example trace breakdown for `GETSIMN`: - Span 1: Request received by AI. - Span 2: Preprocessing (`RawString → embedding input`). - Span 3: Embedding model execution (`resnet-50`). - Span 4: Similarity algorithm execution (`cosinesimilarity`). - Span 5: Predicate filtering in **Ahnlich DB** (cross-service span). This gives you a **timeline view** of the query, where each bar represents time spent in different stages. --- # FILE: components/distributed-tracing/ahnlich-db.md --- title: Tracing in Ahnlich DB --- # Tracing in Ahnlich DB ### Why would you want to trace? With DB tracing, you can: - See **latency** of operations like `SET`, `GETPRED`, `DROPSTORE`. - Debug issues in **index updates** and **store lifecycle events**. - Verify persistence operations and background tasks. - Observe how the DB behaves when called by AI for predicate filtering. ### How we support distributed tracing - Enable tracing: `--enable-tracing`. - Configure endpoint: `--otel-endpoint http://jaeger:4317.` - Each DB action generates **spans** with metadata (execution time, error info, input sizes). ### Example docker-compose.yaml ``` ahnlich_db: image: ghcr.io/deven96/ahnlich-db:latest command: > "ahnlich-db run --host 0.0.0.0 \ --enable-tracing \ --otel-endpoint http://jaeger:4317" ports: - "1369:1369" ``` ## Viewing DB Traces in Jaeger 1. Open http://localhost:16686 in your browser. 2. Select `service = ahnlich_db`. 3. Click **Find Traces**. 4. You’ll see entries for DB actions (e.g., `CREATESTORE`, `GETPRED`). 5. Click a trace → expand spans to view details such as: - Query time - Predicate filtering logic - Errors or warnings --- # FILE: components/distributed-tracing/distributed-tracing.md --- title: 🕸️ Distributed Tracing --- # Distributed Tracing in Ahnlich Distributed tracing allows you to track requests across Ahnlich DB and Ahnlich AI. Since many queries flow from the AI service into the DB, tracing provides end-to-end visibility of what happens inside the pipeline. This is critical for diagnosing bottlenecks, monitoring performance, and ensuring reliability in production. Both **Ahnlich DB** and **Ahnlich AI** support tracing through **OpenTelemetry (OTel)**. You can send spans to a backend like **Jaeger** for visualization. --- # FILE: components/distributed-tracing/using-jaeger.md --- title: Using Jaeger --- # Using Jaeger all-in-one Jaeger is a popular open-source **distributed tracing system**. We use the **all-in-one Docker image** to collect and visualize traces for both DB and AI operations. ### Docker Compose snippet: ```docker jaeger: image: jaegertracing/all-in-one:latest ports: - "16686:16686" # UI - "4317:4317" # OTLP gRPC ``` ### Steps 1. Make sure your other services are running (ahnlich-db on 1369, ahnlich-ai on 1370). 2. Run Jaeger with: ``` docker-compose up -d jaeger ``` 3. Open the **Jaeger UI** in your browser: http://localhost:16686 4. Select the service **tracing-client** to see traces from your Python workflow. ## Example AI & DB Queries (from CLI) ### DB Query Example ``` CREATESTORE db_store_20250915143036 DIMENSION 512 SET (([0.1, 0.1, ..., 0.1], {text: "This is the life of Alice"})) IN db_store_20250915143036 LISTSTORES ``` ### AI Query Example ``` CREATESTORE ai_store_20250915143036 QUERYMODEL all-minilm-l6-v2 INDEXMODEL all-minilm-l6-v2 PREDICATES (author, category) SET ((["Jordan One"], {brand: Nike}), (["Yeezey"], {brand: Adidas})) IN ai_store_20250915143036 GETSIMN 4 WITH [Jordan One] USING cosinesimilarity IN ai_store_20250915143036 LISTSTORES ``` These queries **create stores**, **insert data**, and **query similarity** the operations we’ll automate in code. ## Program Implementing the Queries
Click to Expand Code ```python import asyncio import logging from datetime import datetime from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc.ai import preprocess from ahnlich_client_py.grpc.algorithm import algorithms from ahnlich_client_py.grpc import keyval, metadata from ahnlich_client_py.grpc.db import query as db_query from opentelemetry import trace from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter # ------------------------- # Logging Setup # ------------------------- logging.basicConfig(level=logging.INFO) logger = logging.getLogger("tracing") # ------------------------- # OpenTelemetry Setup # ------------------------- resource = Resource.create({"service.name": "tracing-client"}) provider = TracerProvider(resource=resource) exporter = OTLPSpanExporter(endpoint="http://localhost:4317", insecure=True) provider.add_span_processor(BatchSpanProcessor(exporter)) trace.set_tracer_provider(provider) tracer = trace.get_tracer(__name__) # ------------------------- # Generate unique store names # ------------------------- timestamp = datetime.now().strftime("%Y%m%d%H%M%S") db_store_name = f"db_store_{timestamp}" ai_store_name = f"ai_store_{timestamp}" # ------------------------- # Main async workflow # ------------------------- async def main(): async with Channel(host="127.0.0.1", port=1369) as db_channel, \ Channel(host="127.0.0.1", port=1370) as ai_channel: db_client = DbServiceStub(db_channel) ai_client = AiServiceStub(ai_channel) text = "This is the life of Alice" trace_id = f"similarity-workflow-{timestamp}" logger.info("[tracing] started similarity-workflow, trace_id=%s", trace_id) # ------------------------- # Create DB Store # ------------------------- with tracer.start_as_current_span("db.create_store"): try: create_db_req = db_query.CreateStore( store=db_store_name, dimension=512, create_predicates=[], non_linear_indices=[], error_if_exists=False ) await db_client.create_store(create_db_req) logger.info("DB Store created: %s", db_store_name) except Exception as e: logger.error("Failed to create DB store: %s", e) # ------------------------- # Insert DB Entry # ------------------------- with tracer.start_as_current_span("db.insert_entry"): try: vector = [0.1] * 512 # List of floats entry = keyval.DbStoreEntry( key=keyval.StoreKey(key=vector), value=keyval.StoreValue( value={"text": metadata.MetadataValue(raw_string=text)} ) ) set_req = db_query.Set(store=db_store_name, inputs=[entry]) await db_client.set(set_req) logger.info("Inserted entry into DB store") except Exception as e: logger.error("Failed to insert DB entries: %s", e) # ------------------------- # Create AI Store # ------------------------- with tracer.start_as_current_span("ai.create_store"): try: from ahnlich_client_py.grpc.ai.models import AiModel create_ai_req = ai_query.CreateStore( store=ai_store_name, query_model=AiModel.ALL_MINI_LM_L6_V2, index_model=AiModel.ALL_MINI_LM_L6_V2, predicates=["author", "category"], error_if_exists=False, store_original=True ) await ai_client.create_store(create_ai_req) logger.info("AI Store created: %s", ai_store_name) except Exception as e: logger.error("Failed to create AI store: %s", e) # ------------------------- # Insert AI Entries # ------------------------- with tracer.start_as_current_span("ai.insert_entries"): try: ai_entries = [ keyval.AiStoreEntry( key=keyval.StoreInput(raw_string="Jordan One"), value=keyval.StoreValue( value={"brand": metadata.MetadataValue(raw_string="Nike")} ), ), keyval.AiStoreEntry( key=keyval.StoreInput(raw_string="Yeezey"), value=keyval.StoreValue( value={"brand": metadata.MetadataValue(raw_string="Adidas")} ), ) ] set_ai_req = ai_query.Set( store=ai_store_name, inputs=ai_entries, preprocess_action=preprocess.PreprocessAction.NoPreprocessing ) await ai_client.set(set_ai_req) logger.info("Inserted entries into AI store") except Exception as e: logger.error("Failed to insert AI entries: %s", e) # ------------------------- # AI Similarity Query # ------------------------- with tracer.start_as_current_span("ai.get_sim_n"): try: search_input = keyval.StoreInput(raw_string="Jordan One") ai_sim_req = ai_query.GetSimN( store=ai_store_name, search_input=search_input, closest_n=4, algorithm=algorithms.Algorithm.CosineSimilarity, preprocess_action=preprocess.PreprocessAction.NoPreprocessing ) ai_response = await ai_client.get_sim_n(ai_sim_req) logger.info("AI similarity response received") print(ai_response) except Exception as e: logger.error("AI similarity call failed: %s", e) # ------------------------- # List DB Stores # ------------------------- with tracer.start_as_current_span("db.list_stores"): try: stores = await db_client.list_stores(db_query.ListStores()) logger.info("DB Stores: %s", stores) except Exception as e: logger.error("Failed to list DB stores: %s", e) # ------------------------- # List AI Stores # ------------------------- with tracer.start_as_current_span("ai.list_stores"): try: ai_stores = await ai_client.list_stores(ai_query.ListStores()) logger.info("AI Stores: %s", ai_stores) except Exception as e: logger.error("Failed to list AI stores: %s", e) if __name__ == "__main__": asyncio.run(main()) ```
## Code Explanation 1. **OpenTelemetry Setup** - Creates a **tracer provider**, sets **service name** as `tracing-client`, and exports spans to **Jaeger via OTLP gRPC**. 2. **Unique Store Names** - Ensures every workflow run has new DB and AI stores using a timestamp. 3. **DB Operations** - `CreateStore` → Creates a vector store. - `Set` → Inserts a numeric vector with metadata. - `ListStores` → Retrieves DB stores in the requested schema, or `public` when schema is omitted. 4. **AI Operations** - `CreateStore` → Creates an embedding AI store. - `Set` → Inserts text entries with metadata. - `GetSimN` → Queries the AI store for closest matches using cosine similarity. - `ListStores` → Retrieves AI stores in the requested schema, or `public` when schema is omitted. 5. **Tracing Spans** - Every operation wrapped in `tracer.start_as_current_span()` generates a **trace span in Jaeger**, giving **start/end time**, **logs**, and **events**. ## Viewing Spans in Jaeger 1. Open **Jaeger UI**: http://localhost:16686 2. In **Service**, select `tracing-client`. 3. Click **Find Traces** You’ll see spans for each step: - `db.create_store` - `ai.create_store` - `ai.get_sim_n` - `db.list_stores` - `ai.list_stores` 4. Expand each span to see: - **Start/end times** - **Logs and events** - Metadata from the operation Example spans captured from a run might include `DB Store created: db_store_20250915143036` and `AI similarity` response received. ![Screenshot 1](/img/docs/jaeger-1.png) ![Screenshot 2](/img/docs/jaeger-2.png) ![Screenshot 3](/img/docs/jaeger-3.png) ![Screenshot 4](/img/docs/jaeger-4.png) ![Screenshot 5](/img/docs/jaeger-5.png) ![Screenshot 6](/img/docs/jaeger-6.png) ![Screenshot 7](/img/docs/jaeger-7.png) ![Screenshot 8](/img/docs/jaeger-8.png) --- # FILE: components/persistence-in-ahnlich/persistence-for-ahnlich-ai.md --- title: Persistence for Ahnlich AI sidebar_position: 30 --- # Persistence for Ahnlich AI ### Why need persistence? Ahnlich AI builds on DB by adding embeddings, vector similarity search, and model-driven queries. Without persistence: - AI stores and embeddings vanish after restarts. - Indices must be recomputed, delaying queries. - Models re-download weights on each startup. Persistence ensures that: - Embeddings and indices survive container restarts. - Models remain cached locally for faster startup. - Production workloads avoid costly recomputation. ### Configuring persistence from in-memory mode to disk Enable persistence for AI with the following configuration: ``` ahnlich_ai: image: ghcr.io/deven96/ahnlich-ai:latest command: > "ahnlich-ai run --db-host ahnlich_db --host 0.0.0.0 \ --supported-models all-minilm-l6-v2,resnet-50 \ --enable-persistence --persist-location /root/.ahnlich/data/ai.dat \ --persistence-interval 300" ports: - "1370:1370" volumes: - "./ahnlich_ai_model_cache:/root/.ahnlich/models" # Model cache storage - "./data/:/root/.ahnlich/data" # Persistence Location ``` - `--enable-persistence` → activates persistence for embeddings and AI stores. - `--persist-location` → file path for AI persistence (e.g., `ai.dat`). - `--persistence-interval` → interval (in seconds) for saving snapshots. - `./ahnlich_ai_model_cache` → stores model weights persistently. - `./data/` → keeps AI persistence files safe. ### Loading from persistence On restart, Ahnlich AI automatically restores state from `ai.dat`. - All embeddings, similarity indices, and AI stores are reloaded. - Cached models are loaded from `./ahnlich_ai_model_cache`, avoiding redownloads. - Commands like `LISTSTORES`, `GETSIMN`, and `GETPRED` can confirm successful recovery. - Recovery from backups is as easy as restoring the persistence file and model cache. --- # FILE: components/persistence-in-ahnlich/persistence-for-ahnlich-db.md --- title: Persistence for Ahnlich DB sidebar_position: 30 --- # Persistence for Ahnlich DB ### Why need persistence? Ahnlich DB manages structured data like keys, values, and predicates. Running it in-memory only means: - Stored data disappears after restarts. - Predicate indexes must be rebuilt manually. - Any production state is volatile. Enabling persistence ensures: - Data and indexes survive restarts or container upgrades. - Your stores can be backed up and restored easily. - Mission-critical applications don’t lose their data. ### Configuring persistence from in-memory mode to disk Enable persistence by starting the DB service with persistence flags: ``` ahnlich_db: image: ghcr.io/deven96/ahnlich-db:latest command: > "ahnlich-db run --host 0.0.0.0 \ --enable-persistence --persist-location /root/.ahnlich/data/db.dat \ --persistence-interval 300" ports: - "1369:1369" volumes: - "./data/:/root/.ahnlich/data" # Persistence Location ``` - `--enable-persistence` → turns on persistence. - `--persist-location` → file path to store snapshots (e.g., `db.dat`). - `--persistence-interval` → snapshot interval in seconds (default: 300). - `volumes` → ensures snapshots persist outside container lifecycle. ### Loading from persistence When restarted, the DB automatically loads the latest snapshot from `db.dat`. - All stores, keys, and predicates return to their previous state. - No manual re-indexing is required. - You can back up or restore DB state simply by copying the persistence file. --- # FILE: components/persistence-in-ahnlich/persistence-in-ahnlich.md --- title: ♾️ Persistence In Ahnlich sidebar_position: 10 --- # Persistence in Ahnlich ## Overview By default, both **Ahnlich DB** and **Ahnlich AI** services run in **in-memory mode**, meaning all data (keys, predicates, embeddings, and indices) is stored only in RAM. This is fast for retrieval, but it also means: - All data is lost when the service restarts. - Stores and indices must be recreated after every shutdown. - Models and embeddings need to be recomputed repeatedly. **Persistence** solves this by periodically saving data to disk and reloading it on startup. This ensures that **your DB and AI services are fault-tolerant, production-ready, and stateful across restarts**. --- # FILE: components/predicates/predicates.md --- title: 🔍 Predicates sidebar_position: 40 --- # Predicates Predicates in Ahnlich are **metadata-based filters** that allow you to query and filter stored data beyond just similarity search. They work alongside vector similarity to enable powerful hybrid queries. ## Overview When you store data in Ahnlich (both DB and AI), each entry consists of: - **Key**: The vector embedding (or raw key for DB stores) - **Value**: Metadata as key-value pairs (e.g., `{"author": "Alice", "category": "tech"}`) Predicates let you filter results based on this metadata. ## Core Concepts ### Predicate A **predicate** is a single comparison operation on a metadata field. Ahnlich supports four operators: | Operator | Description | Example | |----------|-------------|---------| | `EQUALS` | Exact match | `author = "Alice"` | | `NOT EQUALS` | Not equal | `status != "draft"` | | `IN` | Value in list | `category IN ["tech", "science"]` | | `NOT IN` | Value not in list | `priority NOT IN ["low", "spam"]` | ### Predicate Condition A **predicate condition** combines predicates using logical operators: | Operator | Description | Example | |----------|-------------|---------| | `AND` | Both conditions must be true | `(author = "Alice") AND (category = "tech")` | | `OR` | Either condition must be true | `(status = "published") OR (priority = "high")` | Conditions can be nested to create complex queries: ``` (author = "Alice" AND category = "tech") OR (priority = "high") ``` ### Predicate Index A **predicate index** optimizes queries on specific metadata fields. Without an index, Ahnlich scans all entries. With an index, lookups are much faster. **Creating an index:** ``` CREATEPREDINDEX (author, category) IN my_store ``` **Dropping an index:** ``` DROPPREDINDEX (category) IN my_store ``` Indexes are idempotent - creating an existing index won't error, it just adds new ones. ## Supported Data Types Predicates work with metadata values of type: - **String** - Text values like `"Alice"`, `"tech"`, `"published"` - **Binary** - Raw bytes (for image hashes, etc.) ## Examples ### Simple Predicate Query Find all entries where author is "Alice": ``` GETPRED (author = "Alice") IN my_store ``` ### Complex Condition Find tech articles by Alice or Bob: ``` GETPRED (((author = Alice) OR (author = Bob)) AND (category = tech)) IN my_store ``` ### Hybrid Query (Similarity + Predicate) Find similar documents, filtered by author: **Ahnlich AI:** ``` GETSIMN 5 WITH [machine learning tutorial] USING cosinesimilarity IN my_store WHERE (author = Alice) ``` **Ahnlich DB:** ``` GETSIMN 5 WITH [0.1, 0.2, ...] USING cosinesimilarity IN my_store WHERE (author = "Alice") ``` This combines vector similarity with metadata filtering. ### Using IN Operator Find entries with multiple allowed values: ``` GETPRED (category IN (tech, science, ai)) IN my_store ``` ### Delete by Predicate Remove all draft entries: ``` DELPRED (status = "draft") IN my_store ``` ## Best Practices ### 1. Create Indexes for Frequently Queried Fields If you often filter by `author`, create an index: ``` CREATEPREDINDEX (author) IN my_store ``` ### 2. Declare Predicates at Store Creation Specify expected predicates upfront: **Ahnlich AI:** ``` CREATESTORE my_store QUERYMODEL all-minilm-l6-v2 INDEXMODEL all-minilm-l6-v2 PREDICATES (author, category, status) ``` **Ahnlich DB:** ``` CREATESTORE my_store DIMENSION 384 CREATEPREDINDEX (author, category, status) ``` ### 3. Keep Metadata Lightweight Predicates are for filtering, not bulk data storage. Keep values small: - ✅ Good: `{"author": "Alice", "category": "tech"}` - ❌ Bad: `{"content": ""}` ### 4. Use AND/OR Efficiently - Use `AND` when both conditions must match (narrows results) - Use `OR` when either condition matches (broadens results) ### 5. Index High-Cardinality Fields If a field has many unique values (like `author`), index it. Low-cardinality fields (like `status` with only 2-3 values) benefit less from indexing. ## CLI Command Reference | Command | Description | |---------|-------------| | `CREATEPREDINDEX` | Create index on metadata fields | | `DROPPREDINDEX` | Remove index from fields | | `GETPRED` | Query by predicate only | | `GETSIMN ... WHERE` | Similarity search filtered by predicate | | `DELPRED` | Delete entries matching predicate | See detailed examples in: - [AI CLI Commands](../ahnlich-cli/ai-commands) - [DB CLI Commands](../ahnlich-cli/db-commands) ## Limitations - Predicates only support **equality-based comparisons** (no regex, range queries, or full-text search yet) - Index updates are **synchronous** - creating an index on a large store may take time - No support for **numeric comparisons** (e.g., `age > 18`) - use string equality as a workaround ## Advanced: Predicate Internals Under the hood, predicates are structured as: ``` PredicateCondition: - Value(Predicate) // Single predicate - And(left, right) // Logical AND - Or(left, right) // Logical OR Predicate: - Equals(key, value) - NotEquals(key, value) - In(key, [values]) - NotIn(key, [values]) ``` This structure allows nesting and complex boolean logic while keeping the implementation efficient. --- # FILE: components/predicates/quick-reference.md --- title: Quick Reference sidebar_position: 10 --- # Predicates Quick Reference ## Operators ### Comparison Operators | Operator | Syntax | Example | |----------|--------|---------| | Equals | `key = value` | `author = "Alice"` | | Not Equals | `key != value` | `status != "draft"` | | In | `key IN [values]` | `category IN ["tech", "ai"]` | | Not In | `key NOT IN [values]` | `priority NOT IN ["low"]` | ### Logical Operators | Operator | Syntax | Example | |----------|--------|---------| | AND | `(condition) AND (condition)` | `(author = "Alice") AND (category = "tech")` | | OR | `(condition) OR (condition)` | `(status = "draft") OR (status = "review")` | ## Common Patterns ### Single Field Filter ``` GETPRED (category = "tech") IN articles ``` ### Multiple Field Filter (AND) ``` GETPRED ((author = Alice) AND (status = published)) IN articles ``` ### Multiple Field Filter (OR) ``` GETPRED ((priority = high) OR (priority = urgent)) IN tasks ``` ### Using IN for Multiple Values ``` GETPRED (category IN (tech, science, ai)) IN articles ``` ### Nested Conditions ``` GETPRED ((author = "Alice") OR (author = "Bob")) AND (category = "tech") IN articles ``` ### Hybrid Query (Similarity + Filter) **AI:** ``` GETSIMN 10 WITH [machine learning] USING cosinesimilarity IN articles WHERE (author = Alice) ``` **DB:** ``` GETSIMN 10 WITH [0.1, 0.2, 0.3, ...] USING cosinesimilarity IN articles WHERE (category = "tech") ``` ## Index Management ### Create Index ``` CREATEPREDINDEX (author, category, status) IN my_store ``` ### Drop Index ``` DROPPREDINDEX (category) IN my_store ``` ### Drop Index with Error Handling ``` DROPPREDINDEX (category) IN my_store ``` ## Store Creation with Predicates ### Ahnlich AI ``` CREATESTORE my_store QUERYMODEL all-minilm-l6-v2 INDEXMODEL all-minilm-l6-v2 PREDICATES (author, category, status) ``` ### Ahnlich DB ``` CREATESTORE my_store DIMENSION 384 CREATEPREDINDEX (author, category, status) IN my_store ``` ## Delete by Predicate ### Simple Delete ``` DELPRED (status = "deleted") IN articles ``` ### Complex Delete ``` DELPRED (status = "draft") AND (last_modified = "2024-01-01") IN articles ``` ## Tips ✅ **Do:** - Create indexes for frequently filtered fields - Use `IN` for multiple values instead of chaining `OR` - Keep metadata values small and simple - Declare predicates when creating stores ❌ **Don't:** - Store large text in metadata (use for filtering only) - Create indexes on rarely used fields - Forget to index high-cardinality fields (many unique values) ## Syntax Cheat Sheet ``` Predicate: key = "value" key != "value" key IN ["val1", "val2"] key NOT IN ["val1"] PredicateCondition: (predicate) (condition) AND (condition) (condition) OR (condition) ((cond1) AND (cond2)) OR (cond3) # Nested Commands: CREATEPREDINDEX (field1, field2) IN store DROPPREDINDEX (field1) IN store GETPRED (field1 = value1) IN store GETSIMN 5 WITH [0.1, 0.2, 0.3] USING cosinesimilarity IN store WHERE (field1 = value1) DELPRED (condition) IN store ``` --- # FILE: components/schemas/schemas.md --- title: Schemas sidebar_position: 40 --- # Schemas Schemas provide logical separation for stores in Ahnlich DB and Ahnlich AI. A schema is a namespace: the same store name can exist in different schemas, and store names only need to be unique within their schema. The default schema is `public`. When a command or generated client request leaves `schema` unset, Ahnlich resolves that request against `public`. An omitted schema does not search or list stores across every schema. The `public` schema always exists and cannot be dropped. ## Supported Commands Schema support is available on these DB and AI store management commands: | Command | Schema behavior | | --- | --- | | `CREATESTORE ... SCHEMA ` | Creates the store in the named schema. Omitting `SCHEMA` creates it in `public`. | | `GETSTORE SCHEMA ` | Reads a store from the named schema. Omitting `SCHEMA` reads from `public`. | | `DROPSTORE IF EXISTS SCHEMA ` | Drops a store from the named schema. Omitting `SCHEMA` drops from `public`. | | `LISTSTORES SCHEMA ` | Lists stores in the named schema. Omitting `SCHEMA` lists only `public`. | | `DROPSCHEMA ` | Drops a non-public schema and all stores in it. | ## DB Examples ```text CREATESTORE articles DIMENSION 384 PREDICATES (author, category) SCHEMA analytics LISTSTORES SCHEMA analytics GETSTORE articles SCHEMA analytics DROPSTORE articles IF EXISTS SCHEMA analytics DROPSCHEMA analytics ``` ## AI Examples ```text CREATESTORE images QUERYMODEL clip-vit-b32-image INDEXMODEL clip-vit-b32-image STOREORIGINAL SCHEMA media LISTSTORES SCHEMA media GETSTORE images SCHEMA media DROPSTORE images IF EXISTS SCHEMA media DROPSCHEMA media ``` ## Generated Clients The generated Go, Python, and Node.js clients expose `schema` on `CreateStore`, `GetStore`, `DropStore`, and `ListStores`, plus a `DropSchema` request/RPC for DB and AI. ### Go ```go schema := "analytics" _, err := client.CreateStore(ctx, &dbquery.CreateStore{ Store: "articles", Dimension: 384, Schema: &schema, }) stores, err := client.ListStores(ctx, &dbquery.ListStores{Schema: &schema}) _, err = client.DropSchema(ctx, &dbquery.DropSchema{Schema: schema}) ``` ### Python ```py schema = "analytics" await client.create_store( db_query.CreateStore(store="articles", dimension=384, schema=schema) ) stores = await client.list_stores(db_query.ListStores(schema=schema)) await client.drop_schema(db_query.DropSchema(schema=schema)) ``` ### Node.js ```ts const schema = "analytics"; await client.createStore( new CreateStore({ store: "articles", dimension: 384, schema }), ); const stores = await client.listStores(new ListStores({ schema })); await client.dropSchema(new DropSchema({ schema })); ``` ## AI and DB Coordination Ahnlich AI stores are backed by DB stores. When AI creates or drops a store with a schema, the backing DB request uses the same schema. Dropping a schema through AI removes the backing DB schema first, then removes AI stores in that schema. `PurgeStores` is intentionally broad and removes AI stores across schemas, including their backing DB stores. --- # FILE: concepts/ai-and-embeddings.mdx --- title: AI & embeddings sidebar_label: AI & embeddings --- import {AIStoreDiagram} from '@site/src/components/ConceptFigures'; # AI & embeddings An **AI store** lets you work with raw **text or images** instead of vectors. Ahnlich AI runs an embedding model for you, turns each input into a [vector](/docs/concepts/vectors), and stores it — so you get semantic search without running a model yourself. ## DB store vs AI store - **[Vector DB store](/docs/concepts/data-model)** (port 1369) — you compute vectors and send them. - **AI store** (port 1370) — you send `raw_string` or `raw_binary` inputs and the server embeds them. Both expose the [same operations](/docs/stores); only the input type differs — the **Vector DB / AI** switch on each operation page shows both. ## Index model & query model An AI store is created with two models: - **index model** — embeds the data you store. - **query model** — embeds incoming search queries. They're usually the **same** model (e.g. `all-minilm-l6-v2` for text, `resnet-50` for images) so stored data and queries share one vector space. Ahnlich ships several models; pick one that matches your modality. ```bash CREATESTORE my_store QUERYMODEL all-minilm-l6-v2 INDEXMODEL all-minilm-l6-v2 \ PREDICATES (author, category) STOREORIGINAL ``` ## Preprocessing & original data - **Preprocess action** — controls how inputs are prepared before embedding (e.g. truncating or padding to the model's expected size vs. erroring on a mismatch). - **Store original** (`STOREORIGINAL`) — keep the raw text/image alongside its vector so results can return the source, not just the embedding. ## Related - [Ahnlich AI component](/docs/components/ahnlich-ai) - [Vectors](/docs/concepts/vectors) - [Insert data](/docs/stores/set) --- # FILE: concepts/concepts.mdx --- id: concepts title: 📚 Concepts --- import CustomDocCard from '@site/src/components/CustomDocCard'; import {BigPictureDiagram} from '@site/src/components/ConceptFigures'; export const concepts = [ {title: 'Data model', icon: 'cube', link: '/docs/concepts/data-model'}, {title: 'Vectors', icon: 'vector', link: '/docs/concepts/vectors'}, {title: 'Schemas', icon: 'folder', link: '/docs/concepts/schemas'}, {title: 'Metadata', icon: 'tag', link: '/docs/concepts/metadata'}, {title: 'Similarity metrics', icon: 'target', link: '/docs/concepts/similarity-metrics'}, {title: 'Vector index', icon: 'layers', link: '/docs/concepts/vector-index'}, {title: 'AI & embeddings', icon: 'sparkles', link: '/docs/concepts/ai-and-embeddings'}, ]; # Concepts The ideas behind Ahnlich, in plain terms. Read these once and the rest of the docs — the [store operations](/docs/stores) and the client references — will click into place.
{concepts.map((c) => ( ))}
--- # FILE: concepts/data-model.mdx --- title: Data model sidebar_label: Data model --- import {DataModelDiagram} from '@site/src/components/ConceptFigures'; # Data model Ahnlich organizes everything into **stores**. Understanding the store and the shape of what goes inside it is most of what you need to be productive. ## Store A [**store**](/docs/stores) is a named container for your data — the equivalent of a *collection* in a document database or a *table* in a relational one. A store fixes two things up front: - a **dimension** — the length every vector in it must have, and - an optional set of **predicates** — the metadata fields you plan to filter on. Many stores can coexist on one server, each isolated from the others, so you can separate data by application, environment, or tenant. ## Entry: key + value Each item in a store is an **entry** made of two parts: - **Key** — a [vector](/docs/concepts/vectors) (`StoreKey`), a list of floats whose length matches the store's dimension. This is what similarity search compares against. - **Value** — a [`StoreValue`](/docs/concepts/metadata): a map of metadata fields (predicates) describing the entry, e.g. `{author: "Asimov", genre: "SciFi"}`. ## Two kinds of store | | You provide | Port | Vectors are… | | --- | --- | --- | --- | | **Vector DB store** | raw vectors | 1369 | supplied by you | | **AI store** | text or images | 1370 | [generated for you](/docs/concepts/ai-and-embeddings) | The [operations](/docs/stores) — insert, similarity search, filtering — are the same for both; only the input differs. ## Related - [Stores & operations](/docs/stores) - [Vectors](/docs/concepts/vectors) - [Schemas](/docs/concepts/schemas) --- # FILE: concepts/metadata.mdx --- title: Metadata sidebar_label: Metadata --- import {MetadataDiagram, MetadataFilterDiagram} from '@site/src/components/ConceptFigures'; # Metadata Every entry in a [store](/docs/concepts/data-model) has two halves: a **key** (the [vector](/docs/concepts/vectors)) and a **value**. That value is the entry's **metadata** — a set of named fields describing it, stored in a `StoreValue`. Where the vector captures *meaning*, metadata captures *facts*: who wrote it, what category it belongs to, when it was created, the original text or image. It's how you keep the human-readable context next to the vector. ## Fields and values Metadata is a map from **field names** to **values**: ```text { author: "Asimov", genre: "SciFi", tags: "epic, space" } ``` - **Field name** — a label you choose (`author`, `genre`, `category`, …). - **Value** — the data for that field. Values can be **text** (`raw_string`) or **binary** such as an image (`raw_binary`). You decide the shape of your metadata; Ahnlich doesn't impose a fixed schema on the fields inside a value. ## What metadata is for Metadata earns its place in three ways: 1. **Context on results** — search returns the vector *and* its metadata, so you get back the author, title, or original text — not just an opaque list of numbers. 2. **Filtering** — select entries by their fields with [Get by predicate](/docs/stores/get-by-predicate) (e.g. every entry where `author = "Asimov"`). A *predicate* is simply a condition over metadata; see the [predicates reference](/docs/components/predicates) for the full grammar. 3. **Narrowing similarity search** — pass a predicate as the `condition` on [Similarity search](/docs/stores/get-simn) to rank only the vectors that also match the metadata — "nearest neighbours *where* `genre = SciFi`". ## Making metadata fast Filtering scans metadata, which slows down on large stores. Tell Ahnlich which fields you filter on so it can index them: - declare them as `create_predicates` when you [create the store](/docs/stores/create-store), or - add a [predicate index](/docs/stores/create-predicate-index) later. An index turns a full scan into a direct lookup. ## Related - [Data model](/docs/concepts/data-model) - [Get by predicate](/docs/stores/get-by-predicate) - [Create predicate index](/docs/stores/create-predicate-index) - [Predicates reference](/docs/components/predicates) --- # FILE: concepts/schemas.mdx --- title: Schemas sidebar_label: Schemas --- import {SchemaDiagram} from '@site/src/components/ConceptFigures'; # Schemas A **schema** is a namespace for stores — think of it as a *folder of stores*. It groups and isolates them, similar to a schema in a relational database. Every store lives in exactly one schema. ## The `public` schema If you never mention a schema, everything happens in the built-in **`public`** schema. Most requests accept an optional `schema` field; omit it and the server uses `public`. ```python # these target the public schema client.create_store(db_query.CreateStore(store="my_store", dimension=4, ...)) # these target the "analytics" schema client.create_store(db_query.CreateStore(store="my_store", schema="analytics", dimension=4, ...)) ``` ## Why use custom schemas - **Isolation** — the same store name can exist in different schemas without clashing (`analytics.my_store` vs `public.my_store`). - **Multi-tenancy / environments** — give each tenant, team, or environment its own schema. - **Bulk cleanup** — dropping a schema removes every store inside it in one step. ## Rules of thumb - The `public` schema always exists and **cannot be dropped**. - [`ListStores`](/docs/stores/list-stores) lists one schema at a time (defaults to `public`) — it does not span every schema. - Operations that target a store take the same optional `schema` field, so moving an example to another schema is a one-field change. ## Related - [Data model](/docs/concepts/data-model) - [Create a store](/docs/stores/create-store) - [List stores](/docs/stores/list-stores) --- # FILE: concepts/similarity-metrics.mdx --- title: Similarity metrics sidebar_label: Similarity metrics --- import {MetricsDiagram} from '@site/src/components/ConceptFigures'; # Similarity metrics A **similarity metric** (or distance metric) is the rule Ahnlich uses to decide how *close* two vectors are during a [similarity search](/docs/stores/get-simn). You pick the metric per query via the `algorithm` field. ## The metrics | Metric | Measures | Good when | | --- | --- | --- | | **Cosine similarity** | the **angle** between vectors (ignores magnitude) | text/semantic embeddings, where direction matters more than length | | **Euclidean distance** | the straight-line **distance** between points | vectors where magnitude is meaningful (e.g. spatial data) | | **Dot product** | angle **and** magnitude together | when larger vectors should score higher (some recommender setups) | ## Choosing one - Start with **cosine similarity** for embeddings from language or vision models — it's the most common default and is unaffected by vector length. - Use **Euclidean** when the absolute position of a vector carries meaning. - Use **dot product** when your model was trained to encode relevance in the vector's magnitude. The metric only affects **ranking** — it doesn't change what's stored, so you can issue different queries against the same store with different metrics. ## Related - [Similarity search](/docs/stores/get-simn) - [Vectors](/docs/concepts/vectors) - [Indexes](/docs/concepts/vector-index) --- # FILE: concepts/vector-index/exact-search.mdx --- title: Exact search sidebar_label: Exact search --- import {ExactScanFigure} from '@site/src/components/ConceptFigures'; # Exact search Imagine looking for a book by checking **every single book** in the library, one by one. You'll always find it — but the more books there are, the longer it takes. That's exact search (also called a *brute-force* or *flat* scan), and it's what Ahnlich does when a store has **no [vector index](/docs/concepts/vector-index)**. ## How it works For each search, Ahnlich measures the distance from your query [vector](/docs/concepts/vectors) to **every** stored vector using the chosen [similarity metric](/docs/concepts/similarity-metrics), then returns the closest. No structure to build, no shortcuts taken. ## What it's like - ✅ **Always exact** — you get the true nearest neighbours, never a guess. - ✅ **Zero setup** — nothing to build or tune; it just works. - ⚠️ **Gets slower with size** — the work grows in a straight line with the number of vectors, so a store with 10× the data takes ~10× longer to search. ## When you'd use it - **Small or medium collections** — a few thousand items where a full scan is already instant. - **Correctness matters more than speed** — deduplication, matching against a reference set, anything where a missed result is unacceptable. - **Prototyping** — start here, measure, and only add an index if search actually feels slow. ## Real-world examples - A blog with a few hundred articles offering "related posts." - Matching an uploaded logo against a small brand library for exact duplicates. - A demo or MVP before you have real scale. ## Related - [HNSW](/docs/concepts/vector-index/hnsw) — for large, high-dimensional data. - [Vector index overview](/docs/concepts/vector-index) --- # FILE: concepts/vector-index/hnsw.mdx --- title: HNSW sidebar_label: HNSW --- import {HnswFigure} from '@site/src/components/ConceptFigures'; # HNSW To find someone in a huge city, you don't knock on every door — you ask a well-connected friend who points you a few hops closer, then a local who knows the exact street. **HNSW** (Hierarchical Navigable Small World) works the same way: it builds a **network of shortcuts** over your [vectors](/docs/concepts/vectors) so a search reaches the neighbourhood in a few jumps instead of scanning everything. ## How it works HNSW stacks several layers. The **top layer** is sparse with long-range links — you travel far, fast. Each layer down is denser and more local. A search starts at the top, hops toward the target, then drops into lower layers to refine — arriving at the nearest neighbours in a handful of steps. ## What it's like - ⚡ **Fast at scale** — searches stay quick even with millions of vectors. - 📐 **Built for high dimensions** — the hundreds/thousands of numbers that language and vision models produce. - 🎯 **Approximate** — it may occasionally miss a true nearest neighbour in exchange for a huge speed-up. Fine for almost all semantic search. - 🎛️ **Tunable** — parameters trade accuracy against speed and memory. ## When you'd use it - **Large stores of high-dimensional embeddings** where a full [scan](/docs/concepts/vector-index/exact-search) is too slow. - Semantic search where **approximate** results are acceptable — the default choice for most AI workloads. ## Real-world examples - **Semantic search / RAG** — searching millions of document embeddings for a chatbot or knowledge base. - **Recommendations** — "users who liked this also liked…" over item embeddings. - **Image search** — finding visually similar products across a big catalogue. ## Adding one Create it with [Create non-linear index](/docs/stores/create-non-linear-algx) using an `HnswConfig` (tune `ef_construction`, `maximum_connections`, and the distance metric). ## Related - [Exact search](/docs/concepts/vector-index/exact-search) - [Vector index overview](/docs/concepts/vector-index) --- # FILE: concepts/vector-index.mdx --- title: Vector index sidebar_label: Overview --- import {IndexDiagram} from '@site/src/components/ConceptFigures'; # Vector index A **vector index** is a data structure that makes [similarity search](/docs/concepts/similarity-metrics) fast by avoiding a full scan. By default Ahnlich compares your query against **every** stored [vector](/docs/concepts/vectors); a vector index pre-structures the vectors so only a fraction need to be checked. Ahnlich offers two approaches, from exact-but-linear to approximate-but-fast: - **[Exact search](/docs/concepts/vector-index/exact-search)** — no index; always exact, but cost grows with the store. - **[HNSW](/docs/concepts/vector-index/hnsw)** — approximate, scales to high-dimensional embeddings. ## Choosing an index | Situation | Use | | --- | --- | | Small store, or you need exact results | [**Exact scan**](/docs/concepts/vector-index/exact-search) (no index) | | Large store of high-dimensional embeddings | [**HNSW**](/docs/concepts/vector-index/hnsw) | An index costs extra memory and build time, and approximate indexes (HNSW) trade a little recall for speed — so add one when search latency actually matters. ## Adding a vector index Create one with [Create non-linear index](/docs/stores/create-non-linear-algx) (or seed it at [store creation](/docs/stores/create-store)); remove it with [Drop non-linear index](/docs/stores/drop-non-linear-algx). > Filtering by **metadata** instead of vectors? That's a different structure — see > [predicate indexes](/docs/concepts/metadata#making-metadata-fast). ## Related - [Similarity metrics](/docs/concepts/similarity-metrics) - [Similarity search](/docs/stores/get-simn) - [Metadata](/docs/concepts/metadata) --- # FILE: concepts/vectors.mdx --- title: Vectors sidebar_label: Vectors --- import {EmbeddingPipeline, SimilaritySpace} from '@site/src/components/ConceptFigures'; # Vectors A **vector** (or *embedding*) is a list of numbers generated by an embedding model to capture the *semantic essence* of unstructured data — text, an image, audio. The key idea: **semantically similar items produce similar vectors**, so you can compare *meaning* instead of matching exact keywords. ## Similar meaning, similar vector Two paraphrases of the same sentence, or two photos of the same object, land close together in vector space; unrelated things land far apart. That closeness is exactly what Ahnlich searches on. ```text "a fast red car" → [0.91, 0.12, 0.44, 0.03] "a speedy crimson automobile" → [0.89, 0.15, 0.41, 0.05] ← close ⇒ similar "a bowl of soup" → [0.02, 0.77, 0.10, 0.63] ← far ⇒ unrelated ``` ## How vectors flow through Ahnlich Working with vectors is a three-step loop: 1. **Store** — turn your data into vectors and [insert](/docs/stores/set) them into a [store](/docs/concepts/data-model). Compute the vectors yourself, or send raw text/images to an [AI store](/docs/concepts/ai-and-embeddings) and let Ahnlich embed them. 2. **Search** — turn your query into a vector *with the same model*, then run a [similarity search](/docs/stores/get-simn). 3. **Retrieve** — Ahnlich ranks stored vectors by the chosen [similarity metric](/docs/concepts/similarity-metrics) and returns the nearest, optionally narrowed by [metadata predicates](/docs/concepts/metadata). At scale, an [index](/docs/concepts/vector-index) keeps step 3 fast so you don't compare against every vector. ## Dimension The **dimension** is the length of the vector. It's fixed when you [create a store](/docs/stores/create-store), and every key you [insert](/docs/stores/set) must have exactly that many values — a 4-dimensional store only accepts 4-number keys. The dimension is set by whatever produced the vector (your embedding model, feature extractor, etc.). ## Dense vectors Ahnlich works with **dense vectors**: fixed-length lists of real numbers where (nearly) every dimension carries meaning — for example, a 384-dimensional embedding from a language model. Dense vectors are semantically rich but not human-readable; a single number rarely means anything on its own. > Some systems also use *sparse* vectors — very high-dimensional, mostly-zero > vectors where each non-zero slot is a keyword weight (interpretable, but blind to > synonyms). Ahnlich focuses on dense vectors. ## Match the metric to the model Embedding models are trained for a particular notion of distance, so your [similarity metric](/docs/concepts/similarity-metrics) should match how the model was trained — cosine similarity for most text/vision embeddings, Euclidean or dot product where magnitude matters. Using the wrong metric quietly degrades result quality. ## Example: image search Convert each product photo into a vector that captures its visual features — shape, colour, object type. Similar images get nearby vectors, so a new photo surfaces visually similar products, and the *same* item is recognised across different lighting or angles — all without a single matching filename or tag. ## Related - [Similarity metrics](/docs/concepts/similarity-metrics) - [AI & embeddings](/docs/concepts/ai-and-embeddings) - [Indexes](/docs/concepts/vector-index) - [Similarity search](/docs/stores/get-simn) --- # FILE: getting-started/ai-friendly.mdx --- title: AI-friendly docs sidebar_label: AI-friendly description: Use the Ahnlich docs with your AI assistant — llms.txt, a full-text dump, and a ready-to-paste prompt. --- # AI-friendly docs Building with an AI assistant — Claude, ChatGPT, Cursor, Copilot? These docs are made to be read by machines as well as people. Point your assistant at the endpoints below and it can pull in exactly the pages it needs. ## Copy & paste this prompt Give your assistant this once, and let it fetch the docs on demand: ```text Read https://ahnlich.dev/llms.txt to get the documentation index for Ahnlich (a Rust-native, AI-powered in-memory vector database). Fetch the relevant pages as needed to complete the tasks I give you. ``` That's usually the best approach — the model reads only what's relevant instead of you pasting whole pages. ## Machine-readable endpoints - **[`/llms.txt`](pathname:///llms.txt)** — a concise, grouped **index** of every docs page (title, link, and summary), following the [llms.txt](https://llmstxt.org) convention. Small enough to drop into any chat. - **[`/llms-full.txt`](pathname:///llms-full.txt)** — the **entire documentation** as one plain-text file. Paste it into a large-context model when you want it to have everything at once. ## Copy any page Every page is just Markdown under the hood — select and copy the content straight into a chat, or open it in your browser's reader view for a clean version. ## Good starting points for an assistant - [Quickstart](/docs/getting-started/quickstart) — connect → create → insert → search - [Concepts](/docs/concepts) — vectors, metadata, similarity, indexes - [Stores](/docs/stores) — the full operation reference - [Client Libraries](/docs/client-libraries) — per-language API docs Found something an assistant got wrong? [Open an issue](https://github.com/deven96/ahnlich/issues). --- # FILE: getting-started/comparison-with-other-tools.md --- title: Comparison with other tools sidebar_position: 30 --- # Comparison with other tools Most vector databases — **Pinecone**, **Weaviate**, **Milvus** — are built for cloud-scale, managed production workloads. **Ahnlich** optimizes for the other end: **local-first development, fast iteration, and built-in embeddings**, with a roadmap toward clustering and stronger persistence. :::tip TL;DR - **Pick Ahnlich** to build and iterate fast, embed text/images out of the box, and run everything locally with no external services. - **Pick Pinecone / Weaviate / Milvus** when you need managed hosting, horizontal scale to billions of vectors, or advanced ANN tuning today. ::: ## At a glance | | Ahnlich | Pinecone | Weaviate | Milvus | | --- | --- | --- | --- | --- | | **Runs locally** | ✅ single binary / Docker | ❌ cloud SaaS | ✅ self-host / K8s | ✅ self-host / K8s | | **Kubernetes** | ✅ official Helm charts | ❌ managed only | ✅ Helm chart / operator | ✅ Helm chart / operator | | **Setup** | Seconds (local) · one `helm install` (K8s) | Managed signup | Cluster config | Cluster config | | **Built-in embeddings** | ✅ AI proxy (text + image) | ❌ external provider | ⚠️ via modules | ❌ external provider | | **Search** | Cosine · L2 · Dot; ANN (HNSW) | ANN (HNSW) | ANN (HNSW, IVF, PQ) | ANN (HNSW, IVF, PQ, DiskANN) | | **Persistence** | In-memory or file-based | Always persistent | Persistent | Persistent | | **Scaling** | Single node (clustering in progress) | Multi-region | Replicated | Petabyte-scale | | **Clients** | Python · Rust · Node · Go + CLI | REST / gRPC | REST / GraphQL / Python | REST / multi-language | | **Best for** | Local dev, prototyping, embedded AI | Managed cloud | Hybrid structured + vector | Massive-scale search | ✅ built-in · ⚠️ partial / via add-ons · ❌ not available ## Why choose Ahnlich - **Developer-first.** A fast CLI, documented SDKs, and clear APIs — visibility and control across the whole vector + AI workflow. - **Local-first, with a path to scale.** One command to run locally, and official [**Helm charts**](../ahnlich-in-production/kubernetes) when you're ready for Kubernetes — self-healing pods, rolling upgrades, and cluster-managed volumes. Clustering, replication, and stronger persistence are on the roadmap. - **Batteries-included AI.** A native proxy embeds text and images for you, so there's no separate model service to wire up (external providers still work). - **Multi-language.** Official clients for Python, Rust, Node, and Go, plus the CLI. ## When another tool fits better | Choose… | If you need… | | --- | --- | | **Pinecone** | A fully managed cloud DB with SLAs, monitoring, and zero infra to run. | | **Weaviate** | Hybrid search over structured + vector data, GraphQL, modular integrations. | | **Milvus** | Billions of vectors, GPU acceleration, and advanced distributed ANN tuning. | ## Which should I use? | Scenario | Recommended | | --- | --- | | Building AI apps or experimenting with embeddings | **Ahnlich** | | Local development with CLI + SDK workflows | **Ahnlich** | | Fully managed cloud with SLAs | Pinecone | | Combining structured data with vector queries | Weaviate | | Scaling to billions of vectors with GPUs | Milvus | | Preparing for distributed deployment | Ahnlich *(clustering in progress)* | Ready to try it? Head to the [**Quickstart**](./quickstart). --- # FILE: getting-started/getting-started.md --- id: getting-started title: 🚀 Getting started --- import CustomDocCard from '@site/src/components/CustomDocCard'; export const components = [ { title: "Installation", icon: "📦", link: "/docs/getting-started/installation" }, { title: 'Usage', icon: "🔨", link: "/docs/getting-started/usage" }, { title: 'Comparison With Other Tools', icon: "⚖️", link: "/docs/getting-started/comparison-with-other-tools" }, { title: 'Next Steps', icon: "➡️", link: "/docs/getting-started/next-steps" } ]; # 🚀 Getting Started with Ahnlich
{components.map((component) => ( ))}
--- # FILE: getting-started/installation.md --- title: Installation sidebar_position: 10 description: Install Ahnlich with Docker, pre-built binaries, or from source. --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Installation Ahnlich ships two services — the **DB engine** (`ahnlich-db`) and the **AI proxy** (`ahnlich-ai`) — plus an interactive **CLI** (`ahnlich-cli`). Pick the install method that fits your environment; the tabs below stay in sync across the page. Best for isolated sandboxes, CI, and quick testing — the only dependency is Docker. Pull the latest official images: ```bash docker pull ghcr.io/deven96/ahnlich-db:latest docker pull ghcr.io/deven96/ahnlich-ai:latest ``` Run both services with their default ports (DB → `1369`, AI → `1370`): ```bash docker run -d --name ahnlich-db -p 1369:1369 ghcr.io/deven96/ahnlich-db:latest docker run -d --name ahnlich-ai -p 1370:1370 ghcr.io/deven96/ahnlich-ai:latest ``` :::tip For advanced setups — tracing, persistence, and model caching — use the example [`docker-compose.yml`](https://github.com/deven96/ahnlich/blob/main/docker-compose.yml) in the main repository. ::: Great for servers without Rust or Docker. Download OS-specific binaries for `db`, `ai`, and `cli` from the [GitHub Releases page](https://github.com/deven96/ahnlich/releases). Example for a Linux (`x86_64-unknown-linux-gnu`) environment — download and extract all three binaries: ```bash # Download the db, ai, and cli binaries for your version/platform wget -L https://github.com/deven96/ahnlich/releases/download/bin%2Fdb%2F0.2.2/x86_64-unknown-linux-gnu-ahnlich-db.tar.gz wget -L https://github.com/deven96/ahnlich/releases/download/bin%2Fai%2F0.3.0/x86_64-unknown-linux-gnu-ahnlich-ai.tar.gz wget -L https://github.com/deven96/ahnlich/releases/download/bin%2Fcli%2F0.2.1/x86_64-unknown-linux-gnu-ahnlich-cli.tar.gz # Extract each archive tar -xvzf x86_64-unknown-linux-gnu-ahnlich-db.tar.gz tar -xvzf x86_64-unknown-linux-gnu-ahnlich-ai.tar.gz tar -xvzf x86_64-unknown-linux-gnu-ahnlich-cli.tar.gz # Make the binaries executable chmod +x ahnlich-db ahnlich-ai ahnlich-cli ``` Then start the services (DB → `1369`, AI → `1370`): ```bash # Start the database engine ./ahnlich-db run --port 1369 # Start the AI proxy, pointing it at the DB ./ahnlich-ai run --db-host 127.0.0.1 --port 1370 --supported-models all-minilm-l6-v2 # Add --help to any binary to see all flags ./ahnlich-ai --help ``` :::note **macOS** builds are available too (both Apple Silicon and Intel) — see the [Releases page](https://github.com/deven96/ahnlich/releases) for the matching `apple-darwin` archives. **Windows support is coming soon.** ::: For developers and Rust contributors who want to review code, customize defaults, or stay on the cutting edge. Requires the Rust toolchain via [rustup.rs](https://rustup.rs/). ```bash git clone https://github.com/deven96/ahnlich.git cd ahnlich cargo build --release --bin ahnlich-db cargo build --release --bin ahnlich-ai cargo build --release --bin ahnlich-cli ``` The executables land in `target/release/`. Move them into your `$PATH` or run them directly: ```bash ./target/release/ahnlich-db --help ./target/release/ahnlich-ai --help ./target/release/ahnlich-cli --help ``` ## Which method should I use? | Method | External dependencies | Best for | Upgrade workflow | | --- | --- | --- | --- | | **Docker** | Docker only | Isolated sandbox, CI, testing | `docker pull ghcr.io/deven96/ahnlich-*` | | **Official binaries** | None (just a download tool) | Servers without Rust or Docker | Download updated files from Releases | | **Source (Cargo)** | Rust toolchain | Custom builds, contributions | `git pull && cargo build` | ## Post-installation checklist
- **Permissions** — for binaries, add execution rights with `chmod +x `. - **Ports** — `ahnlich-db` defaults to `1369`, `ahnlich-ai` to `1370`. Override with the `--host` and `--port` flags. - **Upgrades** — keep each install method up to date: - **Docker** — pull the `:latest` tag. - **Binaries** — download again from the Releases page. - **Source** — run `git pull && cargo build`.
:::info Next step With the services running, head to the [**Quickstart**](./quickstart) to create your first store and run a semantic search. ::: ## Uninstalling ```bash docker compose down -v docker rmi ghcr.io/deven96/ahnlich-ai:latest ghcr.io/deven96/ahnlich-db:latest ``` ```bash rm -f ahnlich-db ahnlich-ai ahnlich-cli ``` ```bash cargo clean # remove build artifacts rm -rf target/release/ahnlich-* ``` ## Helpful links - 🏠 [Main repository & documentation](https://github.com/deven96/ahnlich) - 📦 [Releases page for downloading binaries](https://github.com/deven96/ahnlich/releases) - 🧾 [Example Docker Compose file](https://github.com/deven96/ahnlich/blob/main/docker-compose.yml) - 📖 [Full README (installation & usage guidance)](https://github.com/deven96/ahnlich/blob/main/README.md) --- # FILE: getting-started/next-steps.md --- title: Next Steps sidebar_position: 40 --- import CustomDocCard from '@site/src/components/CustomDocCard'; export const components = [ { title: "Architecture", icon: "🏛️", link: "/docs/architecture", description: 'Learn about the architecture of Ahnlich' }, { title: 'Guides', icon: "📘", link: "/docs/guides", description: 'Use cases and examples with Ahnlich' }, { title: 'Community', icon: "🌍", link: "/docs/community", description: 'Get involved with Ahnlich' } ]; # Next steps
{components.map((component) => ( ))}
--- # FILE: getting-started/quickstart-ai.mdx --- title: 'Quickstart: Ahnlich AI' sidebar_label: Ahnlich AI description: Go from zero to semantic search over text in a few minutes — the AI proxy embeds your data for you. --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Quickstart: Ahnlich AI Send **plain text** (or images) and let Ahnlich do the vector math for you — this is the gentlest way in, with no need to understand embeddings or dimensions. Pick your language — or the **CLI** for a no-code path — once below, and every sample on this page follows your choice. > **Bringing your own vectors instead?** Use the > [Ahnlich DB quickstart](./quickstart-db) — same flow, but you supply the vectors. :::note Prerequisites You need the **Ahnlich AI proxy** running (it embeds text for you automatically). The fastest way is Docker: ```bash docker run -d --name ahnlich-ai -p 1370:1370 ghcr.io/deven96/ahnlich-ai:latest ``` See [Installation](./installation) for binaries and other options. ::: ## Install the client ```bash pip install ahnlich-client-py ``` ```bash cargo add ahnlich_client_rs ``` ```bash npm install ahnlich-client-node ``` ```bash go get github.com/deven96/ahnlich/sdk/ahnlich-client-go ``` ```bash # Download the ahnlich-cli binary (see Installation for all platforms/options) wget -L https://github.com/deven96/ahnlich/releases/download/bin%2Fcli%2F0.2.1/x86_64-unknown-linux-gnu-ahnlich-cli.tar.gz tar -xvzf x86_64-unknown-linux-gnu-ahnlich-cli.tar.gz && chmod +x ahnlich-cli ``` ## 1. Connect Open a client against the AI proxy. No boilerplate, no config. ```python title="connect.py" import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub async def main(): # Open a gRPC channel to the AI proxy (default port 1370). # `async with` closes the connection automatically when you're done. async with Channel(host="127.0.0.1", port=1370) as channel: # The stub is your typed client — every Ahnlich call goes through it. client = AiServiceStub(channel) # ready to talk to Ahnlich ``` ```rust title="connect.rs" use ahnlich_client_rs::ai::AiClient; #[tokio::main] async fn main() -> Result<(), Box> { // Connect to the AI proxy (host:port). The client is cheap to clone // and safe to share across tasks. let client = AiClient::new("127.0.0.1:1370".to_string()).await?; // ready to talk to Ahnlich Ok(()) } ``` ```typescript title="connect.ts" import { createAiClient } from "ahnlich-client-node"; // Connect to the AI proxy (host:port). Reuse this client for every call. const client = createAiClient("127.0.0.1:1370"); // ready to talk to Ahnlich ``` ```go title="connect.go" import ( "context" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" ) // Dial the AI proxy (host:port). Use insecure credentials for local dev. conn, err := grpc.DialContext(ctx, "127.0.0.1:1370", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { log.Fatalf("failed to connect: %v", err) } defer conn.Close() // close the connection when main returns // The typed client — every Ahnlich call goes through it. client := aisvc.NewAIServiceClient(conn) ``` Open the interactive shell against the AI proxy — every command in the following steps runs inside it. ```bash ahnlich-cli ahnlich --agent ai --host 127.0.0.1 --port 1370 ``` ## 2. Create a store Pick an embedding model and let Ahnlich handle the vectors for you. ```python title="create_store.py" from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc.ai.models import AiModel await client.create_store(ai_query.CreateStore( store="books", # name of the store index_model=AiModel.ALL_MINI_LM_L6_V2, # model used to embed stored data query_model=AiModel.ALL_MINI_LM_L6_V2, # model used to embed search queries predicates=["author", "genre"], # metadata fields you can filter on store_original=True, # keep the raw text alongside vectors error_if_exists=True, # fail if a store with this name exists )) ``` ```rust title="create_store.rs" use ahnlich_types::ai::query::CreateStore; use ahnlich_types::ai::models::AiModel; client.create_store(CreateStore { store: "books".to_string(), schema: None, index_model: AiModel::AllMiniLmL6V2 as i32, // embeds stored data query_model: AiModel::AllMiniLmL6V2 as i32, // embeds search queries predicates: vec!["author".into(), "genre".into()], // filterable metadata fields non_linear_indices: vec![], // optional ANN indexes (e.g. hnsw) error_if_exists: true, // fail if the store already exists store_original: true, // keep the raw text alongside vectors }, None).await?; ``` ```typescript title="create_store.ts" import { CreateStore } from "ahnlich-client-node/grpc/ai/query_pb"; import { AIModel } from "ahnlich-client-node/grpc/ai/models_pb"; await client.createStore( new CreateStore({ store: "books", queryModel: AIModel.ALL_MINI_LM_L6_V2, // embeds search queries indexModel: AIModel.ALL_MINI_LM_L6_V2, // embeds stored data predicates: ["author", "genre"], // filterable metadata fields errorIfExists: true, // fail if the store already exists storeOriginal: true, // keep the raw text alongside vectors }) ); ``` ```go title="create_store.go" import ( aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aimodel "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/models" ) _, err = client.CreateStore(ctx, &aiquery.CreateStore{ Store: "books", QueryModel: aimodel.AIModel_ALL_MINI_LM_L6_V2, // embeds search queries IndexModel: aimodel.AIModel_ALL_MINI_LM_L6_V2, // embeds stored data Predicates: []string{"author", "genre"}, // filterable metadata fields ErrorIfExists: true, // fail if the store exists StoreOriginal: true, // keep the raw text too }) ``` ```bash CREATESTORE books QUERYMODEL all-minilm-l6-v2 INDEXMODEL all-minilm-l6-v2 PREDICATES (author, genre) ``` :::tip `predicates` are metadata fields you can later filter on (e.g. `author`, `genre`). Field names must match exactly between insert and query. ::: ## 3. Insert data Send raw text with metadata. Embeddings are generated automatically. ```python title="insert.py" from ahnlich_client_py.grpc import keyval, metadata from ahnlich_client_py.grpc.ai import preprocess await client.set(ai_query.Set( store="books", inputs=[ # Each entry = the text to embed (key) + its metadata (value). keyval.AiStoreEntry( key=keyval.StoreInput(raw_string="A galactic empire in decline..."), value=keyval.StoreValue(value={ # These keys must match the store's `predicates` to be filterable. "genre": metadata.MetadataValue(raw_string="SciFi"), "author": metadata.MetadataValue(raw_string="Asimov"), }), ) ], # ModelPreprocessing lets the proxy tokenize/normalize the text before embedding. preprocess_action=preprocess.PreprocessAction.ModelPreprocessing, )) ``` ```rust title="insert.rs" use ahnlich_types::ai::query::Set; use ahnlich_types::ai::preprocess::PreprocessAction; use ahnlich_types::keyval::{AiStoreEntry, StoreInput, StoreValue}; use ahnlich_types::keyval::store_input::Value; use ahnlich_types::metadata::{MetadataValue, metadata_value::Value as MValue}; use std::collections::HashMap; // Metadata keys must match the store's `predicates` to be filterable later. let mut metadata = HashMap::new(); metadata.insert("genre".to_string(), MetadataValue { value: Some(MValue::RawString("SciFi".into())) }); client.set(Set { store: "books".to_string(), schema: None, execution_provider: None, // Let the proxy preprocess the text before embedding. preprocess_action: PreprocessAction::ModelPreprocessing as i32, inputs: vec![AiStoreEntry { // key = the text to embed, value = its metadata. key: Some(StoreInput { value: Some(Value::RawString("A galactic empire in decline...".into())) }), value: Some(StoreValue { value: metadata }), }], model_params: HashMap::new(), }, None).await?; ``` ```typescript title="insert.ts" import { Set } from "ahnlich-client-node/grpc/ai/query_pb"; import { AiStoreEntry, StoreInput, StoreValue } from "ahnlich-client-node/grpc/keyval_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; import { PreprocessAction } from "ahnlich-client-node/grpc/ai/preprocess_pb"; await client.set( new Set({ store: "books", inputs: [ new AiStoreEntry({ // key = the text to embed, value = its metadata. key: new StoreInput({ value: { case: "rawString", value: "A galactic empire in decline..." } }), value: new StoreValue({ value: { // These keys must match the store's predicates to be filterable. genre: new MetadataValue({ value: { case: "rawString", value: "SciFi" } }), author: new MetadataValue({ value: { case: "rawString", value: "Asimov" } }), }, }), }), ], // Let the proxy preprocess the text before embedding. preprocessAction: PreprocessAction.MODEL_PREPROCESSING, }) ); ``` ```go title="insert.go" import ( keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" preprocess "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/preprocess" ) _, err = client.Set(ctx, &aiquery.Set{ Store: "books", Inputs: []*keyval.AiStoreEntry{{ // Key = the text to embed. Key: &keyval.StoreInput{Value: &keyval.StoreInput_RawString{ RawString: "A galactic empire in decline..."}}, // Value = metadata; keys must match the store's predicates to filter on. Value: &keyval.StoreValue{Value: map[string]*metadata.MetadataValue{ "genre": {Value: &metadata.MetadataValue_RawString{RawString: "SciFi"}}, "author": {Value: &metadata.MetadataValue_RawString{RawString: "Asimov"}}, }}, }}, // Let the proxy preprocess the text before embedding. PreprocessAction: preprocess.PreprocessAction_ModelPreprocessing, }) ``` ```bash SET (([A galactic empire in decline...], {genre: SciFi, author: Asimov})) IN books PREPROCESSACTION modelpreprocessing ``` ## 4. Update data UPSERT finds a single matching entry using a predicate condition and updates its raw input or metadata. ```python title="upsert.py" from ahnlich_client_py.grpc import ai_query, keyval, metadata, predicates from ahnlich_client_py.grpc.ai import preprocess # Update the book with genre "SciFi" - change its author metadata await client.upsert(ai_query.Upsert( store="books", # New text to embed (replaces the old key) new_input=keyval.StoreInput(raw_string="The Foundation trilogy explores psychohistory"), # New metadata (merges with existing by default in AI) new_value=keyval.StoreValue(value={ "author": metadata.MetadataValue(raw_string="Isaac Asimov"), }), # Find the entry to update using a predicate condition=predicates.PredicateCondition(predicates=[ predicates.Predicate(key="genre", value=metadata.MetadataValue(raw_string="SciFi")) ]), preprocess_action=preprocess.PreprocessAction.ModelPreprocessing, )) ``` ```rust title="upsert.rs" use ahnlich_types::ai::query::Upsert; use ahnlich_types::ai::preprocess::PreprocessAction; use ahnlich_types::keyval::{StoreInput, StoreValue}; use ahnlich_types::keyval::store_input::Value; use ahnlich_types::metadata::{MetadataValue, metadata_value::Value as MValue}; use ahnlich_types::predicate::{Predicate, PredicateCondition}; use std::collections::HashMap; let mut new_metadata = HashMap::new(); new_metadata.insert("author".to_string(), MetadataValue { value: Some(MValue::RawString("Isaac Asimov".into())) }); client.upsert(Upsert { store: "books".to_string(), schema: None, execution_provider: None, // New text to embed new_input: Some(StoreInput { value: Some(Value::RawString("The Foundation trilogy explores psychohistory".into())) }), // New metadata (merges automatically in AI) new_value: Some(StoreValue { value: new_metadata }), // Predicate to find entry condition: Some(PredicateCondition { predicates: vec![Predicate { key: "genre".to_string(), value: Some(MetadataValue { value: Some(MValue::RawString("SciFi".into())) }), }], }), preprocess_action: PreprocessAction::ModelPreprocessing as i32, model_params: HashMap::new(), }, None).await?; ``` ```typescript title="upsert.ts" import { Upsert } from "ahnlich-client-node/grpc/ai/query_pb"; import { StoreInput, StoreValue } from "ahnlich-client-node/grpc/keyval_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; import { PreprocessAction } from "ahnlich-client-node/grpc/ai/preprocess_pb"; import { Predicate, PredicateCondition } from "ahnlich-client-node/grpc/predicate_pb"; await client.upsert( new Upsert({ store: "books", // New text to embed newInput: new StoreInput({ value: { case: "rawString", value: "The Foundation trilogy explores psychohistory" } }), // New metadata (merges automatically in AI) newValue: new StoreValue({ value: { author: new MetadataValue({ value: { case: "rawString", value: "Isaac Asimov" } }), }, }), // Find entry by predicate condition: new PredicateCondition({ predicates: [ new Predicate({ key: "genre", value: new MetadataValue({ value: { case: "rawString", value: "SciFi" } }), }), ], }), preprocessAction: PreprocessAction.MODEL_PREPROCESSING, }) ); ``` ```go title="upsert.go" import ( keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" predicates "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/predicate" preprocess "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/preprocess" ) _, err = client.Upsert(ctx, &aiquery.Upsert{ Store: "books", // New text to embed NewInput: &keyval.StoreInput{Value: &keyval.StoreInput_RawString{ RawString: "The Foundation trilogy explores psychohistory"}}, // New metadata (merges automatically in AI) NewValue: &keyval.StoreValue{Value: map[string]*metadata.MetadataValue{ "author": {Value: &metadata.MetadataValue_RawString{RawString: "Isaac Asimov"}}, }}, // Find entry by predicate Condition: &predicates.PredicateCondition{Predicates: []*predicates.Predicate{{ Key: "genre", Value: &metadata.MetadataValue{Value: &metadata.MetadataValue_RawString{RawString: "SciFi"}}, }}}, PreprocessAction: preprocess.PreprocessAction_ModelPreprocessing, }) ``` ```bash UPSERT NEWINPUT [The Foundation trilogy explores psychohistory] NEWVALUE {author: Isaac Asimov} WHERE (genre = SciFi) IN books ``` ## 5. Search Query by meaning, with results ranked by similarity. ```python title="search.py" from ahnlich_client_py.grpc.algorithm import algorithms from ahnlich_client_py.grpc.ai.preprocess import PreprocessAction # Search by meaning — the query text is embedded with the store's query_model. response = await client.get_sim_n(ai_query.GetSimN( store="books", search_input=keyval.StoreInput(raw_string="space opera classics"), closest_n=3, # return the 3 nearest matches algorithm=algorithms.Algorithm.CosineSimilarity, # distance metric preprocess_action=PreprocessAction.ModelPreprocessing, )) # Results are ranked most-similar first. for entry in response.entries: print(entry.key.raw_string, entry.similarity.value) ``` ```rust title="search.rs" use ahnlich_types::ai::query::GetSimN; use ahnlich_types::algorithm::algorithms::Algorithm; let res = client.get_sim_n(GetSimN { store: "books".to_string(), schema: None, // The query text is embedded with the store's query_model. search_input: Some(StoreInput { value: Some(Value::RawString("space opera classics".into())), }), closest_n: 3, // return the 3 nearest matches algorithm: Algorithm::CosineSimilarity as i32, // distance metric execution_provider: None, preprocess_action: PreprocessAction::ModelPreprocessing as i32, condition: None, // optional metadata filter (WHERE) model_params: HashMap::new(), }, None).await?; // Results are ranked most-similar first. println!("{:?}", res.entries); ``` ```typescript title="search.ts" import { GetSimN } from "ahnlich-client-node/grpc/ai/query_pb"; import { Algorithm } from "ahnlich-client-node/grpc/algorithm/algorithm_pb"; // Search by meaning — the query text is embedded with the store's query model. const response = await client.getSimN( new GetSimN({ store: "books", searchInput: new StoreInput({ value: { case: "rawString", value: "space opera classics" } }), closestN: 3, // return the 3 nearest matches algorithm: Algorithm.COSINE_SIMILARITY, // distance metric }) ); // Results are ranked most-similar first. for (const entry of response.entries) { console.log(entry.input?.value, entry.similarity); } ``` ```go title="search.go" import ( algorithms "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/algorithm/algorithms" ) // Search by meaning — the query text is embedded with the store's query model. resp, err := client.GetSimN(ctx, &aiquery.GetSimN{ Store: "books", SearchInput: &keyval.StoreInput{Value: &keyval.StoreInput_RawString{RawString: "space opera classics"}}, ClosestN: 3, // return the 3 nearest matches Algorithm: algorithms.Algorithm_CosineSimilarity, // distance metric PreprocessAction: preprocess.PreprocessAction_ModelPreprocessing, }) // Results are ranked most-similar first. for _, entry := range resp.Entries { fmt.Println(entry.Key, entry.Similarity) } ``` ```bash GETSIMN 3 WITH [space opera classics] USING cosinesimilarity IN books WHERE (genre = SciFi) ``` You now have semantic search working end to end. The sections below cover the rest of the everyday operations. ## 6. Filter your search Combine similarity with a metadata condition — only return matches where a predicate holds (e.g. `genre = SciFi`). ```python title="filter.py" from ahnlich_client_py.grpc import predicates # genre = "SciFi" condition = predicates.PredicateCondition( value=predicates.Predicate( equals=predicates.Equals( key="genre", value=metadata.MetadataValue(raw_string="SciFi"), ) ) ) response = await client.get_sim_n(ai_query.GetSimN( store="books", search_input=keyval.StoreInput(raw_string="space opera classics"), closest_n=3, algorithm=algorithms.Algorithm.CosineSimilarity, condition=condition, # only entries where genre = SciFi preprocess_action=PreprocessAction.ModelPreprocessing, )) ``` ```rust title="filter.rs" use ahnlich_types::predicates::{ Predicate, PredicateCondition, Equals, predicate, predicate_condition, }; // genre = "SciFi" let condition = PredicateCondition { kind: Some(predicate_condition::Kind::Value(Predicate { kind: Some(predicate::Kind::Equals(Equals { key: "genre".to_string(), value: Some(MetadataValue { value: Some(MValue::RawString("SciFi".into())) }), })), })), }; let res = client.get_sim_n(GetSimN { store: "books".to_string(), search_input: Some(StoreInput { value: Some(Value::RawString("space opera classics".into())), }), closest_n: 3, algorithm: Algorithm::CosineSimilarity as i32, condition: Some(condition), // only entries where genre = SciFi preprocess_action: PreprocessAction::ModelPreprocessing as i32, schema: None, execution_provider: None, model_params: HashMap::new(), }, None).await?; ``` ```typescript title="filter.ts" import { PredicateCondition, Predicate, Equals } from "ahnlich-client-node/grpc/predicates_pb"; // genre = "SciFi" const condition = new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "genre", value: new MetadataValue({ value: { case: "rawString", value: "SciFi" } }), }), }, }), }, }); const response = await client.getSimN( new GetSimN({ store: "books", searchInput: new StoreInput({ value: { case: "rawString", value: "space opera classics" } }), closestN: 3, algorithm: Algorithm.COSINE_SIMILARITY, condition, // only entries where genre = SciFi }) ); ``` ```go title="filter.go" import ( predicates "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/predicates" ) // genre = "SciFi" condition := &predicates.PredicateCondition{Kind: &predicates.PredicateCondition_Value{ Value: &predicates.Predicate{Kind: &predicates.Predicate_Equals{ Equals: &predicates.Equals{ Key: "genre", Value: &metadata.MetadataValue{Value: &metadata.MetadataValue_RawString{RawString: "SciFi"}}, }}}}} resp, err := client.GetSimN(ctx, &aiquery.GetSimN{ Store: "books", SearchInput: &keyval.StoreInput{Value: &keyval.StoreInput_RawString{RawString: "space opera classics"}}, ClosestN: 3, Algorithm: algorithms.Algorithm_CosineSimilarity, Condition: condition, // only entries where genre = SciFi PreprocessAction: preprocess.PreprocessAction_ModelPreprocessing, }) ``` ```bash GETSIMN 3 WITH [space opera classics] USING cosinesimilarity IN books WHERE (genre = SciFi) ``` :::tip A metadata field must be declared as a **predicate** on the store (or indexed with `CREATEPREDINDEX`) before you can filter on it. See step 8. ::: ## 7. Retrieve by key Fetch entries by their original input — handy when you already know the exact text. Requires the store to have been created with `store_original = true`. ```python title="get_key.py" response = await client.get_key(ai_query.GetKey( store="books", keys=[keyval.StoreInput(raw_string="A galactic empire in decline...")], )) for entry in response.entries: print(entry.key.raw_string, entry.value.value) ``` ```rust title="get_key.rs" use ahnlich_types::ai::query::GetKey; let res = client.get_key(GetKey { store: "books".to_string(), keys: vec![StoreInput { value: Some(Value::RawString("A galactic empire in decline...".into())), }], schema: None, }, None).await?; println!("{:?}", res.entries); ``` ```typescript title="get_key.ts" import { GetKey } from "ahnlich-client-node/grpc/ai/query_pb"; const response = await client.getKey( new GetKey({ store: "books", keys: [new StoreInput({ value: { case: "rawString", value: "A galactic empire in decline..." } })], }) ); ``` ```go title="get_key.go" resp, err := client.GetKey(ctx, &aiquery.GetKey{ Store: "books", Keys: []*keyval.StoreInput{ {Value: &keyval.StoreInput_RawString{RawString: "A galactic empire in decline..."}}, }, }) ``` ```bash GETKEY ([A galactic empire in decline...]) IN books ``` ## 8. Speed up filters with an index Create a predicate index so metadata filters (step 6) stay fast as your store grows. You can add predicates that weren't declared at creation time. ```python title="index.py" await client.create_pred_index(ai_query.CreatePredIndex( store="books", predicates=["author", "genre"], )) ``` ```rust title="index.rs" use ahnlich_types::ai::query::CreatePredIndex; client.create_pred_index(CreatePredIndex { store: "books".to_string(), predicates: vec!["author".into(), "genre".into()], schema: None, }, None).await?; ``` ```typescript title="index.ts" import { CreatePredIndex } from "ahnlich-client-node/grpc/ai/query_pb"; await client.createPredIndex( new CreatePredIndex({ store: "books", predicates: ["author", "genre"] }) ); ``` ```go title="index.go" _, err = client.CreatePredIndex(ctx, &aiquery.CreatePredIndex{ Store: "books", Predicates: []string{"author", "genre"}, }) ``` ```bash CREATEPREDINDEX (author, genre) IN books ``` ## 9. Inspect your stores List every store on the server, along with its size, dimensions, and models. ```python title="list_stores.py" stores = await client.list_stores(ai_query.ListStores()) for store in stores.stores: print(store.name, store.size_in_bytes) ``` ```rust title="list_stores.rs" use ahnlich_types::ai::query::ListStores; let stores = client.list_stores(ListStores { schema: None }, None).await?; println!("{:?}", stores.stores); ``` ```typescript title="list_stores.ts" import { ListStores } from "ahnlich-client-node/grpc/ai/query_pb"; const stores = await client.listStores(new ListStores({})); console.log(stores.stores); ``` ```go title="list_stores.go" stores, err := client.ListStores(ctx, &aiquery.ListStores{}) fmt.Println(stores.Stores) ``` ```bash LISTSTORES ``` ## 10. Clean up Delete individual entries with **del key**, or remove the whole store with **drop store**. ```python title="cleanup.py" # delete a single entry by its key await client.del_key(ai_query.DelKey( store="books", keys=[keyval.StoreInput(raw_string="A galactic empire in decline...")], )) # or drop the entire store await client.drop_store(ai_query.DropStore(store="books", error_if_not_exists=True)) ``` ```rust title="cleanup.rs" use ahnlich_types::ai::query::{DelKey, DropStore}; client.del_key(DelKey { store: "books".to_string(), keys: vec![StoreInput { value: Some(Value::RawString("A galactic empire in decline...".into())) }], schema: None, }, None).await?; client.drop_store(DropStore { store: "books".to_string(), error_if_not_exists: true, schema: None, }, None).await?; ``` ```typescript title="cleanup.ts" import { DelKey, DropStore } from "ahnlich-client-node/grpc/ai/query_pb"; await client.delKey( new DelKey({ store: "books", keys: [new StoreInput({ value: { case: "rawString", value: "A galactic empire in decline..." } })], }) ); await client.dropStore(new DropStore({ store: "books", errorIfNotExists: true })); ``` ```go title="cleanup.go" _, err = client.DelKey(ctx, &aiquery.DelKey{ Store: "books", Keys: []*keyval.StoreInput{{Value: &keyval.StoreInput_RawString{RawString: "A galactic empire in decline..."}}}, }) _, err = client.DropStore(ctx, &aiquery.DropStore{Store: "books", ErrorIfNotExists: true}) ``` ```bash DELKEY ([A galactic empire in decline...]) IN books DROPSTORE books IF EXISTS ``` That's the full lifecycle — create, populate, search, filter, and clean up. ## Where to next? - **[Usage](./usage)** — drive Ahnlich from the CLI, plus the full command reference. - **[Client Libraries](/docs/client-libraries)** — deeper SDK docs for each language. - **[Components](/docs/components)** — how the DB, AI proxy, and CLI fit together. Found a bug or something unclear? [Open an issue](https://github.com/deven96/ahnlich/issues). {/* ============================================================================ DUPLICATE COPY BELOW — leftover from the homepage/docs redesign merge conflict. This is an older, shorter version of the page (sections 1-4 only, no CLI tab). Commented out so the MDX build passes. Kept for reference; safe to delete. ============================================================================ --- title: ⚡ Quickstart sidebar_position: 15 description: Go from zero to semantic search in a few minutes with the Ahnlich client SDK in your language of choice. --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Quickstart Go from a running server to your first semantic search in a few minutes. Pick your language once below — every code sample on this page follows your choice. :::note Prerequisites You need the **Ahnlich AI proxy** running (it embeds text for you automatically). The fastest way is Docker: ```bash docker run -d --name ahnlich-ai -p 1370:1370 ghcr.io/deven96/ahnlich-ai:latest ``` See [Installation](./installation) for binaries and other options. ::: ## Install the client ```bash pip install ahnlich-client-py ``` ```bash cargo add ahnlich_client_rs ``` ```bash npm install ahnlich-client-node ``` ```bash go get github.com/deven96/ahnlich/sdk/ahnlich-client-go ``` ## 1. Connect Open a client against the AI proxy. No boilerplate, no config. ```python title="connect.py" import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub async def main(): # Open a gRPC channel to the AI proxy (default port 1370). # `async with` closes the connection automatically when you're done. async with Channel(host="127.0.0.1", port=1370) as channel: # The stub is your typed client — every Ahnlich call goes through it. client = AiServiceStub(channel) # ready to talk to Ahnlich ``` ```rust title="connect.rs" use ahnlich_client_rs::ai::AiClient; #[tokio::main] async fn main() -> Result<(), Box> { // Connect to the AI proxy (host:port). The client is cheap to clone // and safe to share across tasks. let client = AiClient::new("127.0.0.1:1370".to_string()).await?; // ready to talk to Ahnlich Ok(()) } ``` ```typescript title="connect.ts" import { createAiClient } from "ahnlich-client-node"; // Connect to the AI proxy (host:port). Reuse this client for every call. const client = createAiClient("127.0.0.1:1370"); // ready to talk to Ahnlich ``` ```go title="connect.go" import ( "context" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" ) // Dial the AI proxy (host:port). Use insecure credentials for local dev. conn, err := grpc.DialContext(ctx, "127.0.0.1:1370", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { log.Fatalf("failed to connect: %v", err) } defer conn.Close() // close the connection when main returns // The typed client — every Ahnlich call goes through it. client := aisvc.NewAIServiceClient(conn) ``` ## 2. Create a store Pick an embedding model and let Ahnlich handle the vectors for you. ```python title="create_store.py" from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc.ai.models import AiModel await client.create_store(ai_query.CreateStore( store="books", # name of the store index_model=AiModel.ALL_MINI_LM_L6_V2, # model used to embed stored data query_model=AiModel.ALL_MINI_LM_L6_V2, # model used to embed search queries predicates=["author", "genre"], # metadata fields you can filter on store_original=True, # keep the raw text alongside vectors error_if_exists=True, # fail if a store with this name exists )) ``` ```rust title="create_store.rs" use ahnlich_types::ai::query::CreateStore; use ahnlich_types::ai::models::AiModel; client.create_store(CreateStore { store: "books".to_string(), schema: None, index_model: AiModel::AllMiniLmL6V2 as i32, // embeds stored data query_model: AiModel::AllMiniLmL6V2 as i32, // embeds search queries predicates: vec!["author".into(), "genre".into()], // filterable metadata fields non_linear_indices: vec![], // optional ANN indexes (e.g. hnsw) error_if_exists: true, // fail if the store already exists store_original: true, // keep the raw text alongside vectors }, None).await?; ``` ```typescript title="create_store.ts" import { CreateStore } from "ahnlich-client-node/grpc/ai/query_pb"; import { AIModel } from "ahnlich-client-node/grpc/ai/models_pb"; await client.createStore( new CreateStore({ store: "books", queryModel: AIModel.ALL_MINI_LM_L6_V2, // embeds search queries indexModel: AIModel.ALL_MINI_LM_L6_V2, // embeds stored data predicates: ["author", "genre"], // filterable metadata fields errorIfExists: true, // fail if the store already exists storeOriginal: true, // keep the raw text alongside vectors }) ); ``` ```go title="create_store.go" import ( aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aimodel "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/models" ) _, err = client.CreateStore(ctx, &aiquery.CreateStore{ Store: "books", QueryModel: aimodel.AIModel_ALL_MINI_LM_L6_V2, // embeds search queries IndexModel: aimodel.AIModel_ALL_MINI_LM_L6_V2, // embeds stored data Predicates: []string{"author", "genre"}, // filterable metadata fields ErrorIfExists: true, // fail if the store exists StoreOriginal: true, // keep the raw text too }) ``` :::tip `predicates` are metadata fields you can later filter on (e.g. `author`, `genre`). Field names must match exactly between insert and query. ::: ## 3. Insert data Send raw text with metadata. Embeddings are generated automatically. ```python title="insert.py" from ahnlich_client_py.grpc import keyval, metadata from ahnlich_client_py.grpc.ai import preprocess await client.set(ai_query.Set( store="books", inputs=[ # Each entry = the text to embed (key) + its metadata (value). keyval.AiStoreEntry( key=keyval.StoreInput(raw_string="A galactic empire in decline..."), value=keyval.StoreValue(value={ # These keys must match the store's `predicates` to be filterable. "genre": metadata.MetadataValue(raw_string="SciFi"), "author": metadata.MetadataValue(raw_string="Asimov"), }), ) ], # ModelPreprocessing lets the proxy tokenize/normalize the text before embedding. preprocess_action=preprocess.PreprocessAction.ModelPreprocessing, )) ``` ```rust title="insert.rs" use ahnlich_types::ai::query::Set; use ahnlich_types::ai::preprocess::PreprocessAction; use ahnlich_types::keyval::{AiStoreEntry, StoreInput, StoreValue}; use ahnlich_types::keyval::store_input::Value; use ahnlich_types::metadata::{MetadataValue, metadata_value::Value as MValue}; use std::collections::HashMap; // Metadata keys must match the store's `predicates` to be filterable later. let mut metadata = HashMap::new(); metadata.insert("genre".to_string(), MetadataValue { value: Some(MValue::RawString("SciFi".into())) }); client.set(Set { store: "books".to_string(), schema: None, execution_provider: None, // Let the proxy preprocess the text before embedding. preprocess_action: PreprocessAction::ModelPreprocessing as i32, inputs: vec![AiStoreEntry { // key = the text to embed, value = its metadata. key: Some(StoreInput { value: Some(Value::RawString("A galactic empire in decline...".into())) }), value: Some(StoreValue { value: metadata }), }], model_params: HashMap::new(), }, None).await?; ``` ```typescript title="insert.ts" import { Set } from "ahnlich-client-node/grpc/ai/query_pb"; import { AiStoreEntry, StoreInput, StoreValue } from "ahnlich-client-node/grpc/keyval_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; import { PreprocessAction } from "ahnlich-client-node/grpc/ai/preprocess_pb"; await client.set( new Set({ store: "books", inputs: [ new AiStoreEntry({ // key = the text to embed, value = its metadata. key: new StoreInput({ value: { case: "rawString", value: "A galactic empire in decline..." } }), value: new StoreValue({ value: { // These keys must match the store's predicates to be filterable. genre: new MetadataValue({ value: { case: "rawString", value: "SciFi" } }), author: new MetadataValue({ value: { case: "rawString", value: "Asimov" } }), }, }), }), ], // Let the proxy preprocess the text before embedding. preprocessAction: PreprocessAction.MODEL_PREPROCESSING, }) ); ``` ```go title="insert.go" import ( keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" preprocess "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/preprocess" ) _, err = client.Set(ctx, &aiquery.Set{ Store: "books", Inputs: []*keyval.AiStoreEntry{{ // Key = the text to embed. Key: &keyval.StoreInput{Value: &keyval.StoreInput_RawString{ RawString: "A galactic empire in decline..."}}, // Value = metadata; keys must match the store's predicates to filter on. Value: &keyval.StoreValue{Value: map[string]*metadata.MetadataValue{ "genre": {Value: &metadata.MetadataValue_RawString{RawString: "SciFi"}}, "author": {Value: &metadata.MetadataValue_RawString{RawString: "Asimov"}}, }}, }}, // Let the proxy preprocess the text before embedding. PreprocessAction: preprocess.PreprocessAction_ModelPreprocessing, }) ``` ## 4. Search Query by meaning, with results ranked by similarity. ```python title="search.py" from ahnlich_client_py.grpc.algorithm import algorithms from ahnlich_client_py.grpc.ai.preprocess import PreprocessAction # Search by meaning — the query text is embedded with the store's query_model. response = await client.get_sim_n(ai_query.GetSimN( store="books", search_input=keyval.StoreInput(raw_string="space opera classics"), closest_n=3, # return the 3 nearest matches algorithm=algorithms.Algorithm.CosineSimilarity, # distance metric preprocess_action=PreprocessAction.ModelPreprocessing, )) # Results are ranked most-similar first. for entry in response.entries: print(entry.key.raw_string, entry.similarity.value) ``` ```rust title="search.rs" use ahnlich_types::ai::query::GetSimN; use ahnlich_types::algorithm::algorithms::Algorithm; let res = client.get_sim_n(GetSimN { store: "books".to_string(), schema: None, // The query text is embedded with the store's query_model. search_input: Some(StoreInput { value: Some(Value::RawString("space opera classics".into())), }), closest_n: 3, // return the 3 nearest matches algorithm: Algorithm::CosineSimilarity as i32, // distance metric execution_provider: None, preprocess_action: PreprocessAction::ModelPreprocessing as i32, condition: None, // optional metadata filter (WHERE) model_params: HashMap::new(), }, None).await?; // Results are ranked most-similar first. println!("{:?}", res.entries); ``` ```typescript title="search.ts" import { GetSimN } from "ahnlich-client-node/grpc/ai/query_pb"; import { Algorithm } from "ahnlich-client-node/grpc/algorithm/algorithm_pb"; // Search by meaning — the query text is embedded with the store's query model. const response = await client.getSimN( new GetSimN({ store: "books", searchInput: new StoreInput({ value: { case: "rawString", value: "space opera classics" } }), closestN: 3, // return the 3 nearest matches algorithm: Algorithm.COSINE_SIMILARITY, // distance metric }) ); // Results are ranked most-similar first. for (const entry of response.entries) { console.log(entry.input?.value, entry.similarity); } ``` ```go title="search.go" import ( algorithms "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/algorithm/algorithms" ) // Search by meaning — the query text is embedded with the store's query model. resp, err := client.GetSimN(ctx, &aiquery.GetSimN{ Store: "books", SearchInput: &keyval.StoreInput{Value: &keyval.StoreInput_RawString{RawString: "space opera classics"}}, ClosestN: 3, // return the 3 nearest matches Algorithm: algorithms.Algorithm_CosineSimilarity, // distance metric PreprocessAction: preprocess.PreprocessAction_ModelPreprocessing, }) // Results are ranked most-similar first. for _, entry := range resp.Entries { fmt.Println(entry.Key, entry.Similarity) } ``` That's it — you now have semantic search working end to end. 🎉 ## Where to next? - **[Usage](./usage)** — drive Ahnlich from the CLI, plus the full command reference. - **[Client Libraries](/docs/client-libraries)** — deeper SDK docs for each language. - **[Components](/docs/components)** — how the DB, AI proxy, and CLI fit together. Found a bug or something unclear? [Open an issue](https://github.com/deven96/ahnlich/issues). */} --- # FILE: getting-started/quickstart-db.mdx --- title: 'Quickstart: Ahnlich DB' sidebar_label: Ahnlich DB description: Store and search your own vectors with Ahnlich DB — a movie library in a few minutes. --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Quickstart: Ahnlich DB Use **Ahnlich DB** when you already have **vectors** — embeddings you've computed with your own model — and want a fast place to store and search them. Ahnlich indexes and ranks them; you bring the numbers. We'll build a tiny **movie library**: each movie is a vector (its embedding) plus some metadata (`title`, `genre`). To keep things readable we use 4-number vectors, but the flow is identical for 384- or 1536-dimensional embeddings. Pick your language — or the **CLI** — once below, and every sample follows your choice. > **New to vector search?** The [Ahnlich AI quickstart](./quickstart-ai) is gentler > — you send text and it computes the vectors for you. :::note Prerequisites You need the **Ahnlich DB server** running. The fastest way is Docker: ```bash docker run -d --name ahnlich-db -p 1369:1369 ghcr.io/deven96/ahnlich-db:latest ``` See [Installation](./installation) for binaries and other options. ::: ## Install the client ```bash pip install ahnlich-client-py ``` ```bash cargo add ahnlich_client_rs ``` ```bash npm install ahnlich-client-node ``` ```bash go get github.com/deven96/ahnlich/sdk/ahnlich-client-go ``` ```bash # Download the ahnlich-cli binary (see Installation for all platforms) curl -L -O "https://github.com/deven96/ahnlich/releases/download/bin%2Fcli%2F0.2.1/x86_64-apple-darwin-ahnlich-cli.tar.gz" tar -xvzf x86_64-apple-darwin-ahnlich-cli.tar.gz && chmod +x ahnlich-cli ``` ## 1. Connect Open a client against the DB server (default port **1369**). ```python title="connect.py" import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub async def main(): # Open a gRPC channel to the DB server (default port 1369). # `async with` closes the connection automatically when you're done. async with Channel(host="127.0.0.1", port=1369) as channel: # The stub is your typed client — every Ahnlich call goes through it. client = DbServiceStub(channel) # ready to talk to Ahnlich DB ``` ```rust title="connect.rs" use ahnlich_client_rs::db::DbClient; #[tokio::main] async fn main() -> Result<(), Box> { // Connect to the DB server (host:port). Cheap to clone, safe to share. let client = DbClient::new("127.0.0.1:1369".to_string()).await?; // ready to talk to Ahnlich DB Ok(()) } ``` ```typescript title="connect.ts" import { createDbClient } from "ahnlich-client-node"; // Connect to the DB server (host:port). Reuse this client for every call. const client = createDbClient("127.0.0.1:1369"); // ready to talk to Ahnlich DB ``` ```go title="connect.go" import ( "context" "log" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" ) // grpc.NewClient is lazy — it doesn't dial until the first request. conn, err := grpc.NewClient("127.0.0.1:1369", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Fatalf("connect: %v", err) } defer conn.Close() client := dbsvc.NewDBServiceClient(conn) ctx := context.Background() ``` ```bash # Point the CLI at the DB server; --agent db selects the vector database. ahnlich-cli ahnlich --agent db --host 127.0.0.1 --port 1369 ``` ## 2. Create a store A [store](/docs/concepts/data-model) fixes the **dimension** every vector must have, and the metadata [predicates](/docs/concepts/metadata) you'll filter on. ```python title="create_store.py" from ahnlich_client_py.grpc.db import query as db_query await client.create_store(db_query.CreateStore( store="movies", # name of the store dimension=4, # every vector must be exactly this long create_predicates=["title", "genre"], # metadata fields you can filter on non_linear_indices=[], # optional ANN indexes (e.g. hnsw) — later error_if_exists=True, # fail if a store with this name already exists )) ``` ```rust title="create_store.rs" use ahnlich_types::db::query::CreateStore; client.create_store(CreateStore { store: "movies".to_string(), schema: None, dimension: 4, // fixed vector length create_predicates: vec!["title".into(), "genre".into()], // filterable fields non_linear_indices: vec![], // optional ANN indexes error_if_exists: true, // fail if the store already exists }, None).await?; ``` ```typescript title="create_store.ts" import { CreateStore } from "ahnlich-client-node/grpc/db/query_pb"; await client.createStore( new CreateStore({ store: "movies", dimension: 4, // fixed vector length predicates: ["title", "genre"], // filterable metadata fields errorIfExists: true, // fail if the store already exists }) ); ``` ```go title="create_store.go" import dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" _, err = client.CreateStore(ctx, &dbquery.CreateStore{ Store: "movies", Dimension: 4, // fixed vector length CreatePredicates: []string{"title", "genre"}, // filterable metadata fields ErrorIfExists: true, // fail if the store exists }) ``` ```bash CREATESTORE movies DIMENSION 4 PREDICATES (title, genre) ``` :::tip `dimension` is fixed for the life of the store — it must match the length of the vectors your model produces. `predicates` are the metadata fields you'll filter on later; field names must match exactly between insert and query. ::: ## 3. Insert your movies Each entry is a **key** (the movie's vector) and a **value** (its metadata). ```python title="insert.py" from ahnlich_client_py.grpc import keyval, metadata await client.set(db_query.Set( store="movies", inputs=[ # Each DbStoreEntry pairs a vector (key) with its metadata (value). keyval.DbStoreEntry( key=keyval.StoreKey(key=[0.10, 0.92, 0.05, 0.33]), value=keyval.StoreValue(value={ "title": metadata.MetadataValue(raw_string="Inception"), "genre": metadata.MetadataValue(raw_string="SciFi"), }), ), keyval.DbStoreEntry( key=keyval.StoreKey(key=[0.14, 0.88, 0.09, 0.40]), value=keyval.StoreValue(value={ "title": metadata.MetadataValue(raw_string="Interstellar"), "genre": metadata.MetadataValue(raw_string="SciFi"), }), ), keyval.DbStoreEntry( key=keyval.StoreKey(key=[0.85, 0.12, 0.40, 0.08]), value=keyval.StoreValue(value={ "title": metadata.MetadataValue(raw_string="Pride & Prejudice"), "genre": metadata.MetadataValue(raw_string="Romance"), }), ), ], )) ``` ```rust title="insert.rs" use ahnlich_types::db::query::Set; use ahnlich_types::keyval::{DbStoreEntry, StoreKey, StoreValue}; use ahnlich_types::metadata::{MetadataValue, metadata_value::Value}; use std::collections::HashMap; // Helper: build a {title, genre} metadata map. fn meta(title: &str, genre: &str) -> HashMap { HashMap::from_iter([ ("title".into(), MetadataValue { value: Some(Value::RawString(title.into())) }), ("genre".into(), MetadataValue { value: Some(Value::RawString(genre.into())) }), ]) } client.set(Set { store: "movies".to_string(), schema: None, inputs: vec![ DbStoreEntry { key: Some(StoreKey { key: vec![0.10, 0.92, 0.05, 0.33] }), value: Some(StoreValue { value: meta("Inception", "SciFi") }), }, DbStoreEntry { key: Some(StoreKey { key: vec![0.14, 0.88, 0.09, 0.40] }), value: Some(StoreValue { value: meta("Interstellar", "SciFi") }), }, DbStoreEntry { key: Some(StoreKey { key: vec![0.85, 0.12, 0.40, 0.08] }), value: Some(StoreValue { value: meta("Pride & Prejudice", "Romance") }), }, ], }, None).await?; ``` ```typescript title="insert.ts" import { Set } from "ahnlich-client-node/grpc/db/query_pb"; import { DbStoreEntry, StoreKey, StoreValue } from "ahnlich-client-node/grpc/keyval_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; // Helper: build a {title, genre} metadata map. const meta = (title: string, genre: string) => ({ title: new MetadataValue({ value: { case: "rawString", value: title } }), genre: new MetadataValue({ value: { case: "rawString", value: genre } }), }); await client.set(new Set({ store: "movies", inputs: [ new DbStoreEntry({ key: new StoreKey({ key: [0.10, 0.92, 0.05, 0.33] }), value: new StoreValue({ value: meta("Inception", "SciFi") }), }), new DbStoreEntry({ key: new StoreKey({ key: [0.14, 0.88, 0.09, 0.40] }), value: new StoreValue({ value: meta("Interstellar", "SciFi") }), }), new DbStoreEntry({ key: new StoreKey({ key: [0.85, 0.12, 0.40, 0.08] }), value: new StoreValue({ value: meta("Pride & Prejudice", "Romance") }), }), ], })); ``` ```go title="insert.go" import ( keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" ) // meta builds a {title, genre} metadata map. meta := func(title, genre string) map[string]*metadata.MetadataValue { return map[string]*metadata.MetadataValue{ "title": {Value: &metadata.MetadataValue_RawString{RawString: title}}, "genre": {Value: &metadata.MetadataValue_RawString{RawString: genre}}, } } _, err = client.Set(ctx, &dbquery.Set{ Store: "movies", Inputs: []*keyval.DbStoreEntry{ {Key: &keyval.StoreKey{Key: []float32{0.10, 0.92, 0.05, 0.33}}, Value: &keyval.StoreValue{Value: meta("Inception", "SciFi")}}, {Key: &keyval.StoreKey{Key: []float32{0.14, 0.88, 0.09, 0.40}}, Value: &keyval.StoreValue{Value: meta("Interstellar", "SciFi")}}, {Key: &keyval.StoreKey{Key: []float32{0.85, 0.12, 0.40, 0.08}}, Value: &keyval.StoreValue{Value: meta("Pride & Prejudice", "Romance")}}, }, }) ``` ```bash SET ( ([0.10, 0.92, 0.05, 0.33], {title: Inception, genre: SciFi}), ([0.14, 0.88, 0.09, 0.40], {title: Interstellar, genre: SciFi}), ([0.85, 0.12, 0.40, 0.08], {title: Pride & Prejudice, genre: Romance}) ) IN movies ``` ## 4. Update a movie `Upsert` finds a single entry with a [predicate](/docs/concepts/metadata) and updates it. Here we add a `rating` to *Inception* (`merge_metadata` keeps the existing fields). ```python title="upsert.py" from ahnlich_client_py.grpc import predicates await client.upsert(db_query.Upsert( store="movies", # Match exactly one entry: the movie titled "Inception". condition=predicates.PredicateCondition( value=predicates.Predicate( equals=predicates.Equals( key="title", value=metadata.MetadataValue(raw_string="Inception"), ) ) ), # New metadata to apply. new_value=keyval.StoreValue(value={ "rating": metadata.MetadataValue(raw_string="8.8"), }), merge_metadata=True, # keep existing fields (title, genre); just add rating )) ``` ```rust title="upsert.rs" use ahnlich_types::db::query::Upsert; use ahnlich_types::predicates::{Predicate, PredicateCondition, Equals, predicate, predicate_condition}; client.upsert(Upsert { store: "movies".to_string(), schema: None, // Match the movie titled "Inception". condition: Some(PredicateCondition { kind: Some(predicate_condition::Kind::Value(Predicate { kind: Some(predicate::Kind::Equals(Equals { key: "title".to_string(), value: Some(MetadataValue { value: Some(Value::RawString("Inception".into())) }), })), })), }), new_value: Some(StoreValue { value: HashMap::from_iter([( "rating".into(), MetadataValue { value: Some(Value::RawString("8.8".into())) }, )]) }), merge_metadata: true, // keep title & genre, just add rating }, None).await?; ``` ```typescript title="upsert.ts" import { Upsert } from "ahnlich-client-node/grpc/db/query_pb"; import { PredicateCondition, Predicate, Equals } from "ahnlich-client-node/grpc/predicates_pb"; await client.upsert(new Upsert({ store: "movies", // Match the movie titled "Inception". condition: new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "title", value: new MetadataValue({ value: { case: "rawString", value: "Inception" } }), }) }, }) }, }), newValue: new StoreValue({ value: { rating: new MetadataValue({ value: { case: "rawString", value: "8.8" } }), } }), mergeMetadata: true, // keep title & genre, just add rating })); ``` ```go title="upsert.go" import predicates "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/predicates" _, err = client.Upsert(ctx, &dbquery.Upsert{ Store: "movies", // Match the movie titled "Inception". Condition: &predicates.PredicateCondition{ Kind: &predicates.PredicateCondition_Value{Value: &predicates.Predicate{ Kind: &predicates.Predicate_Equals{Equals: &predicates.Equals{ Key: "title", Value: &metadata.MetadataValue{Value: &metadata.MetadataValue_RawString{RawString: "Inception"}}, }}, }}, }, NewValue: &keyval.StoreValue{Value: map[string]*metadata.MetadataValue{ "rating": {Value: &metadata.MetadataValue_RawString{RawString: "8.8"}}, }}, MergeMetadata: true, // keep title & genre, just add rating }) ``` ```bash UPSERT MERGE VALUE {rating: 8.8} IN movies WHERE (title = Inception) ``` ## 5. Search by similarity Find the movies closest to a **query vector** (an embedding of what you're looking for), ranked by a [similarity metric](/docs/concepts/similarity-metrics). ```python title="search.py" from ahnlich_client_py.grpc.algorithm import algorithms # A vector for "a mind-bending sci-fi thriller" (from your own model). response = await client.get_sim_n(db_query.GetSimN( store="movies", search_input=keyval.StoreKey(key=[0.12, 0.90, 0.07, 0.35]), closest_n=2, # return the 2 nearest movies algorithm=algorithms.Algorithm.CosineSimilarity, )) # Results are ranked most-similar first. for entry in response.entries: title = entry.value.value["title"].raw_string print(title, entry.similarity.value) ``` ```rust title="search.rs" use ahnlich_types::algorithm::algorithms::Algorithm; use ahnlich_types::db::query::GetSimN; let response = client.get_sim_n(GetSimN { store: "movies".to_string(), schema: None, // Query vector for "a mind-bending sci-fi thriller". search_input: Some(StoreKey { key: vec![0.12, 0.90, 0.07, 0.35] }), closest_n: 2, algorithm: Algorithm::CosineSimilarity as i32, condition: None, // no metadata filter (see step 6) }, None).await?; println!("{:?}", response.entries); ``` ```typescript title="search.ts" import { GetSimN } from "ahnlich-client-node/grpc/db/query_pb"; import { Algorithm } from "ahnlich-client-node/grpc/algorithm/algorithm_pb"; const response = await client.getSimN(new GetSimN({ store: "movies", searchInput: new StoreKey({ key: [0.12, 0.90, 0.07, 0.35] }), closestN: 2, algorithm: Algorithm.COSINE_SIMILARITY, })); for (const entry of response.entries) { console.log(entry.value?.value.title, entry.similarity); } ``` ```go title="search.go" import algorithms "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/algorithm/algorithms" resp, err := client.GetSimN(ctx, &dbquery.GetSimN{ Store: "movies", SearchInput: &keyval.StoreKey{Key: []float32{0.12, 0.90, 0.07, 0.35}}, ClosestN: 2, Algorithm: algorithms.Algorithm_CosineSimilarity, }) ``` ```bash GETSIMN 2 WITH ([0.12, 0.90, 0.07, 0.35]) USING cosinesimilarity IN movies ``` The two SciFi movies (*Inception*, *Interstellar*) come back ahead of the romance. ## 6. Filter your search Pass a [predicate](/docs/concepts/metadata) as the `condition` to only rank vectors whose metadata matches — "nearest sci-fi movies". ```python title="filter.py" # genre = "SciFi" condition = predicates.PredicateCondition( value=predicates.Predicate( equals=predicates.Equals( key="genre", value=metadata.MetadataValue(raw_string="SciFi"), ) ) ) response = await client.get_sim_n(db_query.GetSimN( store="movies", search_input=keyval.StoreKey(key=[0.12, 0.90, 0.07, 0.35]), closest_n=3, algorithm=algorithms.Algorithm.CosineSimilarity, condition=condition, # only entries where genre = SciFi )) ``` ```rust title="filter.rs" use ahnlich_types::predicates::{Predicate, PredicateCondition, Equals, predicate, predicate_condition}; // genre = "SciFi" let condition = PredicateCondition { kind: Some(predicate_condition::Kind::Value(Predicate { kind: Some(predicate::Kind::Equals(Equals { key: "genre".to_string(), value: Some(MetadataValue { value: Some(Value::RawString("SciFi".into())) }), })), })), }; let response = client.get_sim_n(GetSimN { store: "movies".to_string(), schema: None, search_input: Some(StoreKey { key: vec![0.12, 0.90, 0.07, 0.35] }), closest_n: 3, algorithm: Algorithm::CosineSimilarity as i32, condition: Some(condition), // only entries where genre = SciFi }, None).await?; ``` ```typescript title="filter.ts" import { PredicateCondition, Predicate, Equals } from "ahnlich-client-node/grpc/predicates_pb"; // genre = "SciFi" const condition = new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "genre", value: new MetadataValue({ value: { case: "rawString", value: "SciFi" } }), }) }, }) }, }); const response = await client.getSimN(new GetSimN({ store: "movies", searchInput: new StoreKey({ key: [0.12, 0.90, 0.07, 0.35] }), closestN: 3, algorithm: Algorithm.COSINE_SIMILARITY, condition, // only entries where genre = SciFi })); ``` ```go title="filter.go" import predicates "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/predicates" // genre = "SciFi" condition := &predicates.PredicateCondition{ Kind: &predicates.PredicateCondition_Value{Value: &predicates.Predicate{ Kind: &predicates.Predicate_Equals{Equals: &predicates.Equals{ Key: "genre", Value: &metadata.MetadataValue{Value: &metadata.MetadataValue_RawString{RawString: "SciFi"}}, }}, }}, } resp, err := client.GetSimN(ctx, &dbquery.GetSimN{ Store: "movies", SearchInput: &keyval.StoreKey{Key: []float32{0.12, 0.90, 0.07, 0.35}}, ClosestN: 3, Algorithm: algorithms.Algorithm_CosineSimilarity, Condition: condition, // only entries where genre = SciFi }) ``` ```bash GETSIMN 3 WITH ([0.12, 0.90, 0.07, 0.35]) USING cosinesimilarity IN movies WHERE (genre = SciFi) ``` ## 7. Retrieve by key Already know the exact vector? [Get by key](/docs/stores/get-key) fetches it directly — no similarity ranking. ```python title="get_key.py" response = await client.get_key(db_query.GetKey( store="movies", keys=[keyval.StoreKey(key=[0.10, 0.92, 0.05, 0.33])], # Inception's vector )) for entry in response.entries: print(entry.value.value["title"].raw_string) ``` ```rust title="get_key.rs" use ahnlich_types::db::query::GetKey; let response = client.get_key(GetKey { store: "movies".to_string(), schema: None, keys: vec![StoreKey { key: vec![0.10, 0.92, 0.05, 0.33] }], }, None).await?; println!("{:?}", response.entries); ``` ```typescript title="get_key.ts" import { GetKey } from "ahnlich-client-node/grpc/db/query_pb"; const response = await client.getKey(new GetKey({ store: "movies", keys: [new StoreKey({ key: [0.10, 0.92, 0.05, 0.33] })], })); console.log(response.entries); ``` ```go title="get_key.go" resp, err := client.GetKey(ctx, &dbquery.GetKey{ Store: "movies", Keys: []*keyval.StoreKey{{Key: []float32{0.10, 0.92, 0.05, 0.33}}}, }) ``` ```bash GETKEY ([0.10, 0.92, 0.05, 0.33]) IN movies ``` ## 8. Speed up filters with an index On a large store, filtering scans every entry. A [predicate index](/docs/stores/create-predicate-index) turns that into a direct lookup. ```python title="index.py" await client.create_pred_index(db_query.CreatePredIndex( store="movies", predicates=["genre"], # index the field you filter on most )) ``` ```rust title="index.rs" use ahnlich_types::db::query::CreatePredIndex; client.create_pred_index(CreatePredIndex { store: "movies".to_string(), schema: None, predicates: vec!["genre".to_string()], }, None).await?; ``` ```typescript title="index.ts" import { CreatePredIndex } from "ahnlich-client-node/grpc/db/query_pb"; await client.createPredIndex(new CreatePredIndex({ store: "movies", predicates: ["genre"], })); ``` ```go title="index.go" _, err = client.CreatePredIndex(ctx, &dbquery.CreatePredIndex{ Store: "movies", Predicates: []string{"genre"}, }) ``` ```bash CREATEPREDINDEX (genre) IN movies ``` ## 9. Inspect your stores [List stores](/docs/stores/list-stores) to see what's registered, with each store's entry count and size. ```python title="list_stores.py" response = await client.list_stores(db_query.ListStores()) for store in response.stores: print(store.name, store.len) ``` ```rust title="list_stores.rs" use ahnlich_types::db::query::ListStores; let response = client.list_stores(ListStores { schema: None }, None).await?; println!("{:?}", response.stores); ``` ```typescript title="list_stores.ts" import { ListStores } from "ahnlich-client-node/grpc/db/query_pb"; const response = await client.listStores(new ListStores({})); console.log(response.stores); ``` ```go title="list_stores.go" resp, err := client.ListStores(ctx, &dbquery.ListStores{}) ``` ```bash LISTSTORES ``` ## 10. Clean up Remove a single entry with [delete by key](/docs/stores/delete-key), or drop the whole [store](/docs/stores/drop-store). ```python title="cleanup.py" # Remove one movie by its key. await client.del_key(db_query.DelKey( store="movies", keys=[keyval.StoreKey(key=[0.85, 0.12, 0.40, 0.08])], # Pride & Prejudice )) # ...or drop the entire store. await client.drop_store(db_query.DropStore( store="movies", error_if_not_exists=True, )) ``` ```rust title="cleanup.rs" use ahnlich_types::db::query::{DelKey, DropStore}; // Remove one movie by its key. client.del_key(DelKey { store: "movies".to_string(), schema: None, keys: vec![StoreKey { key: vec![0.85, 0.12, 0.40, 0.08] }], }, None).await?; // ...or drop the entire store. client.drop_store(DropStore { store: "movies".to_string(), schema: None, error_if_not_exists: true, }, None).await?; ``` ```typescript title="cleanup.ts" import { DelKey, DropStore } from "ahnlich-client-node/grpc/db/query_pb"; // Remove one movie by its key. await client.delKey(new DelKey({ store: "movies", keys: [new StoreKey({ key: [0.85, 0.12, 0.40, 0.08] })], })); // ...or drop the entire store. await client.dropStore(new DropStore({ store: "movies", errorIfNotExists: true })); ``` ```go title="cleanup.go" // Remove one movie by its key. _, err = client.DelKey(ctx, &dbquery.DelKey{ Store: "movies", Keys: []*keyval.StoreKey{{Key: []float32{0.85, 0.12, 0.40, 0.08}}}, }) // ...or drop the entire store. _, err = client.DropStore(ctx, &dbquery.DropStore{Store: "movies", ErrorIfNotExists: true}) ``` ```bash DELETEKEY ([0.85, 0.12, 0.40, 0.08]) IN movies DROPSTORE movies IF EXISTS ``` ## Where to next? - **[Concepts](/docs/concepts)** — vectors, metadata, similarity metrics, and indexes explained. - **[Stores](/docs/stores)** — the full reference for every DB operation. - **[Client Libraries](/docs/client-libraries)** — deeper SDK docs for each language. Found a bug or something unclear? [Open an issue](https://github.com/deven96/ahnlich/issues). --- # FILE: getting-started/quickstart.mdx --- title: Quickstart sidebar_position: 15 description: Go from zero to your first semantic search in a few minutes. --- import QuickstartChooser from '@site/src/components/QuickstartChooser'; # Quickstart Go from a running server to your first semantic search in a few minutes. There are two ways to use Ahnlich — pick the one that fits your data: Both follow the same shape — **connect → create a store → insert → search** — so once you've done one, the other feels familiar. --- # FILE: getting-started/usage.md --- title: Using the CLI sidebar_position: 20 --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Using the CLI `ahnlich-cli` is an interactive shell for talking to Ahnlich **without writing any code**. It's the fastest way to explore your data, run admin tasks, and sanity-check queries. :::tip When to use what - **Building an app?** Use a client SDK — see the [Quickstart](./quickstart). - **Need to install or run the servers?** See [Installation](./installation). - **Exploring or administering?** You're in the right place. ::: ## Open the CLI Make sure a server is running first (see [Installation](./installation)), then connect the CLI to it. The `--agent` flag picks which service you're talking to: Talk directly to the vector store — you provide raw vectors yourself. ```bash ahnlich-cli ahnlich --agent db --host 127.0.0.1 --port 1369 ``` Talk to the AI proxy — send raw text/images and it embeds them for you. ```bash ahnlich-cli ahnlich --agent ai --host 127.0.0.1 --port 1370 ``` ## The command language Ahnlich uses a **declarative, SQL-like command style**. Most commands follow this shape: ```bash IN [WHERE ()] ``` Commands are case-insensitive and can be chained with `;`. Both agents share the same grammar — the only difference is that the **DB engine** takes raw vectors while the **AI proxy** takes raw text/images. Pick an agent below; the tabs stay in sync with the connection command above. #### Create a store ```bash CREATESTORE test_store DIMENSION 2 PREDICATES (author, country) ``` #### Insert data ```bash SET (([1.0, 2.1], {name: Haks, category: dev}), ([3.1, 4.8], {name: Deven, category: dev})) IN test_store ``` #### Retrieve by key ```bash GETKEY ([1.0, 2.0], [3.0, 4.0]) IN test_store ``` #### Search by similarity ```bash GETSIMN 2 WITH [1.0, 2.0] USING cosinesimilarity IN test_store WHERE (category = dev) ``` #### Chain commands ```bash GETKEY ([1.0, 2.0]) IN test_store; CREATEPREDINDEX (name, category) IN test_store ``` #### Create a store ```bash CREATESTORE my_store QUERYMODEL all-minilm-l6-v2 INDEXMODEL all-minilm-l6-v2 PREDICATES (author, category) ``` #### Insert data ```bash SET (([This is the life of Haks], {name: Haks, category: dev}), ([This is the life of Deven], {name: Deven, category: dev})) IN my_store PREPROCESSACTION nopreprocessing ``` #### Search by meaning ```bash GETSIMN 2 WITH [life of deven] USING cosinesimilarity IN my_store WHERE (category = dev) ``` :::info You pass **raw text** — the AI proxy embeds it automatically before comparing it against the stored vectors. ::: ## Command reference | Command | Description | | --- | --- | | `PING` | Check if the server is responsive | | `LISTCLIENTS` | List active connections | | `LISTSTORES [SCHEMA ]` | List stores in a schema (defaults to `public`) | | `INFOSERVER` | Get server metadata / version | | `CREATESTORE DIMENSION ...` | Create a vector store | | `CREATEPREDINDEX (k1, k2) IN ` | Create a predicate (metadata) index | | `SET (...) IN ` | Insert one or more vectors | | `UPSERT NEWKEY [] NEWVALUE {k:v} WHERE () IN ` | Update a single entry matching predicate | | `GETKEY () IN ` | Retrieve entries by exact key | | `GETSIMN WITH [] USING IN WHERE ()` | Query nearest neighbors | | `DROPSTORE IF EXISTS` | Delete a store | | Command | Description | | --- | --- | | `PING` | Check if the server is responsive | | `LISTSTORES` | List all stores | | `INFOSERVER` | Get server metadata / version | | `CREATESTORE QUERYMODEL INDEXMODEL ...` | Create an AI store bound to embedding models | | `CREATEPREDINDEX (k1, k2) IN ` | Create a predicate (metadata) index | | `SET (...) IN PREPROCESSACTION ` | Insert raw text/images (embedded automatically) | | `UPSERT NEWINPUT [] NEWVALUE {k:v} WHERE () IN ` | Update a single entry matching predicate | | `GETSIMN WITH [] USING IN WHERE ()` | Query nearest neighbors by meaning | | `DROPSTORE IF EXISTS` | Delete a store | Supported metrics for `GETSIMN`: `cosinesimilarity`, `euclideandistance`, and `dotproductsimilarity`. --- # FILE: guides/index.mdx --- sidebar_exclude: true slug: /guides className: centeredPage --- import GuidesCard from '@site/src/components/GuidesCard'; import rustLogo from '@site/static/img/rust-logo.png'; import rustLogoLight from '@site/static/img/Rust.png'; import pythonLogo from '@site/static/img/python-logo.png' import pythonLogoLight from '@site/static/img/python_logo.png' # Guides Discover in-depth tutorials and real-world examples built using Ahnlich. Each guide walks through full end-to-end scenarios that demonstrate how to build semantic search applications with Rust and Python.
--- # FILE: guides/python.md --- title: Python Book Search --- **Source**: [examples/python/book‑search](https://github.com/deven96/ahnlich/tree/main/examples/python/book-search) This walkthrough guides building a **textual book similarity search** tool using Python: - Creating a DB or AI Store for embedding book descriptions - Ingesting book titles and summaries as raw text - Querying similar books based on semantic relevance - Filtering results by metadata like genre or author - Displaying matched books with scores and metadata ## 🔧 What you’ll learn 1. Async setup using `grpclib` and `ahnlich-client-py`. 2. Creating a store and ingesting raw book data. 3. Running `get_sim_n()` on text queries like "space opera classics". 4. Filtering responses by metadata predicates (e.g. `author == "Asimov"`). 5. Retrieving stored metadata and original summaries for output. ## 💡 Highlighted snippet ```python from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc.ai.models import AiModel from ahnlich_client_py.grpc.ai import preprocess from ahnlich_client_py.grpc import keyval, metadata async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) await client.create_store(ai_query.CreateStore( store="books", index_model=AiModel.ALL_MINI_LM_L6_V2, query_model=AiModel.ALL_MINI_LM_L6_V2, predicates=["author", "genre"], non_linear_indices=[], store_original=True, error_if_exists=True )) # ingest books await client.set(ai_query.Set( store="books", inputs=[ keyval.AiStoreEntry( key=keyval.StoreInput(raw_string="Dune summary..."), value=keyval.StoreValue(value={"genre": metadata.MetadataValue(raw_string="SciFi")}) ) ], preprocess_action=preprocess.PreprocessAction.NoPreprocessing )) ``` ## ➕ Try it yourself - Clone the example project - Run both Ahnlich services locally - Adjust the book dataset (titles / summaries / metadata) - Use the Python script to test querying with new text prompts ## 🧠 Why these guides matter - Show **real usage** of the Go/Python/Rust SDKs in fully working apps - Let you **adapt the patterns** for your domain (images, documents, etc.) - Emphasize **semantic similarity + metadata filtering** capabilities - Provide production-style, real-world code to build upon Check out the source on GitHub and follow along as each guide walks you step-by-step through store creation, ingestion, search, and result processing. ## 📌 Next Steps - Browse the [Rust Image Search](https://github.com/deven96/ahnlich/tree/main/examples/rust/image-search) repo - Browse the [Python Book Search](https://github.com/deven96/ahnlich/tree/main/examples/python/book-search) repo - Start customizing: swap embeddings, apply new metadata, or change query logic - Use these patterns as templates for your own semantic search pipelines --- # FILE: guides/rust.md --- title: Rust Image Search --- **Source**: [examples/rust/image‑search](https://github.com/deven96/ahnlich/tree/main/examples/rust/image-search) This guide demonstrates building an **image-based similarity search** application using the Rust SDK. It covers: - Initializing `ahnlich-db` and `ahnlich-ai` stores - Ingesting image files as raw bytes into an **AI Store** - Running similarity queries based on image embeddings - Using metadata (e.g. tags) for filtering results - Returning both vector similarity scores and original image references ## 🔧 What you’ll learn 1. Setting up an **AI Store** via Rust client. 2. Feeding images (byte streams) into the store. 3. Querying the store with a new image file to retrieve visually similar results. 4. Applying metadata filters (e.g. `tag == "nature"`). 5. Understanding handling of raw input retention for display. ## 💡 Highlighted snippet ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_types::ai::query::{CreateStore, Set}; use ahnlich_types::ai::models::AiModel; use ahnlich_types::ai::preprocess::PreprocessAction; use ahnlich_types::keyval::{AiStoreEntry, StoreInput, StoreValue}; use ahnlich_types::keyval::store_input::Value; use ahnlich_types::metadata::{MetadataValue, metadata_value::Value as MValue}; use std::collections::HashMap; let tracing_id = None; let ai_client = AiClient::new("127.0.0.1:1370".to_string()).await?; // create a store for images ai_client.create_store(CreateStore { store: "image_store".to_string(), index_model: AiModel::ClipVitB32Image as i32, query_model: AiModel::ClipVitB32Text as i32, predicates: vec!["tag".to_string()], non_linear_indices: vec![], store_original: true, error_if_exists: true, }, tracing_id.clone()).await?; // ingest image bytes with metadata ai_client.set(Set { store: "image_store".to_string(), inputs: vec![ AiStoreEntry { key: Some(StoreInput { value: Some(Value::Image(img_bytes)) }), value: Some(StoreValue { value: HashMap::from([("tag".to_string(), MetadataValue { value: Some(MValue::RawString("nature".into())), })]), }), }, ], preprocess_action: PreprocessAction::NoPreprocessing as i32, execution_provider: None, model_params: HashMap::new(), }, tracing_id.clone()).await?; ``` ## ➕ Try it yourself - Clone the example repository - Launch `ahnlich-db` and `ahnlich-ai` locally via Docker or binaries - Modify the image folder path and metadata tags - Build and run the Rust example app to visualize results --- # FILE: index.md --- title: Overview slug: / --- import {Redirect} from '@docusaurus/router'; --- # FILE: overview.md --- title: What is Ahnlich? sidebar_label: What is Ahnlich? sidebar_position: 10 --- # What is Ahnlich? **Ahnlich is an in-memory vector database with a built-in AI proxy** that embeds your text and images for you. Point it at raw content, search by meaning, and get ranked results — all from a single binary with no external services. ```bash docker run -d -p 1370:1370 ghcr.io/deven96/ahnlich-ai:latest ``` Ready to try it? Jump straight to the [**Quickstart**](./getting-started/quickstart). ## Why Ahnlich? - **No embedding pipeline to build.** Send raw text or images; the AI proxy embeds and stores them automatically. No separate model server to run. - **Runs anywhere, instantly.** A self-contained binary — no cluster, no managed service, no cloud dependency. Great for local dev, prototypes, and the edge. - **Fast semantic search.** RAM-resident vectors with Cosine, Euclidean (L2), or Dot Product similarity. - **Filter while you search.** Attach metadata (author, genre, timestamps…) and combine similarity with metadata conditions in one query. - **Update in place.** Add, change, or delete vectors on the fly — no full index rebuilds. - **Scales when you need it.** Approximate search via HNSW indexes for large datasets. - **Use your language.** Native clients for **Python, Rust, Node, and Go**, plus an interactive CLI. ## What can I build with it? - **Semantic document & FAQ search** — find content by meaning, not keywords. - **RAG chat memory** — fetch the most relevant context to enrich LLM prompts. - **Recommendations** — turn users, products, or docs into vectors and rank by similarity plus metadata. - **Code & log search** — surface meaningfully similar snippets, not exact matches. ## How it fits together Ahnlich ships two services and a CLI: | Component | What it does | | --- | --- | | **`ahnlich-db`** | The in-memory vector store — holds vectors and metadata, runs similarity search. | | **`ahnlich-ai`** | The AI proxy — turns raw text/images into embeddings, then talks to the DB for you. | | **`ahnlich-cli`** | An interactive shell for creating stores, inserting data, and querying. | Use `ahnlich-ai` when you want automatic embeddings, or talk to `ahnlich-db` directly if you already have your own vectors. ## Next steps - [**Quickstart**](./getting-started/quickstart) — first store and search in minutes. - [**Installation**](./getting-started/installation) — Docker, binaries, or source. - [**Client Libraries**](./client-libraries) — SDK docs for each language. --- # FILE: reference/configuration.md --- title: Configuration Reference sidebar_position: 20 --- # Configuration Reference Complete reference for all configuration options in Ahnlich DB and AI. ## Environment Variables **Note:** Ahnlich currently does not use environment variables for configuration. All settings must be provided via CLI flags. ## Configuration Files **Note:** Ahnlich does not currently support configuration files. All settings are provided as command-line arguments. --- ## Database Server (ahnlich-db) Start the database server with: ```bash ahnlich-db run [OPTIONS] ``` ### Server Options #### `--host` - **Type:** String - **Default:** `"127.0.0.1"` - **Description:** Server host address to bind to - **Examples:** ```bash --host 127.0.0.1 # Localhost only --host 0.0.0.0 # All interfaces --host 192.168.1.10 # Specific IP ``` #### `--port` - **Type:** u16 (unsigned 16-bit integer) - **Default:** `1369` - **Description:** Database server port - **Examples:** ```bash --port 1369 # Default --port 8080 # Custom port ``` --- ### Memory Management #### `--allocator-size` - **Type:** usize (bytes) - **Default:** `10,737,418,240` (10 GiB) - **Minimum:** `10,485,760` (10 MiB) - **Description:** Global allocator size in bytes - maximum memory for vector storage - **Validation:** - Must be ≥ 10 MiB - Should be >2x persistence file size if persistence is enabled - **Examples:** ```bash --allocator-size 10737418240 # 10 GiB (default) --allocator-size 21474836480 # 20 GiB --allocator-size 107374182400 # 100 GiB ``` - **Calculation Helper:** ``` 1 GiB = 1,073,741,824 bytes 1 MiB = 1,048,576 bytes ``` #### `--message-size` - **Type:** usize (bytes) - **Default:** `10,485,760` (10 MiB) - **Description:** Maximum gRPC message size - **Examples:** ```bash --message-size 10485760 # 10 MiB (default) --message-size 104857600 # 100 MiB ``` --- ### Persistence #### `--enable-persistence` - **Type:** bool (flag) - **Default:** `false` - **Description:** Enable data persistence to disk - **Example:** ```bash --enable-persistence ``` #### `--persist-location` - **Type:** PathBuf (file path) - **Default:** None - **Required:** Yes, if `--enable-persistence` is set - **Description:** Path to persistence file - **Examples:** ```bash --persist-location /var/lib/ahnlich/db.dat --persist-location ~/ahnlich-data/db.dat --persist-location ./data/persistence.dat ``` #### `--persistence-interval` - **Type:** u64 (milliseconds) - **Default:** `300000` (5 minutes) - **Description:** How often to save data to disk (in milliseconds) - **Examples:** ```bash --persistence-interval 300000 # 5 minutes (default) --persistence-interval 60000 # 1 minute --persistence-interval 600000 # 10 minutes ``` - **Calculation Helper:** ``` 1 second = 1,000 ms 1 minute = 60,000 ms 5 minutes = 300,000 ms ``` #### `--fail-on-startup-if-persist-load-fails` - **Type:** bool (flag) - **Default:** `false` - **Description:** Whether to crash on startup if persistence load fails - **Examples:** ```bash --fail-on-startup-if-persist-load-fails # Fail loudly # Omit flag to continue without persistence on load failure ``` --- ### Networking #### `--maximum-clients` - **Type:** usize - **Default:** `1000` - **Description:** Maximum concurrent client connections - **Examples:** ```bash --maximum-clients 1000 # Default --maximum-clients 5000 # Higher limit --maximum-clients 100 # Lower limit ``` --- ### Performance #### `--threadpool-size` - **Type:** usize - **Default:** `16` - **Description:** CPU thread pool size for request handling - **Recommendation:** Set to number of CPU cores or slightly higher - **Examples:** ```bash --threadpool-size 16 # Default --threadpool-size 32 # For 32-core system --threadpool-size 8 # For 8-core system ``` --- ### Observability #### `--enable-tracing` - **Type:** bool (flag) - **Default:** `false` - **Description:** Enable OpenTelemetry distributed tracing - **Example:** ```bash --enable-tracing ``` #### `--otel-endpoint` - **Type:** String (URL) - **Default:** None - **Required:** Yes, if `--enable-tracing` is set - **Description:** OpenTelemetry collector endpoint (gRPC) - **Examples:** ```bash --otel-endpoint http://localhost:4317 --otel-endpoint http://jaeger:4317 --otel-endpoint http://192.168.1.10:4317 ``` #### `--log-level` - **Type:** String (log filter) - **Default:** `"info,hf_hub=warn"` - **Description:** Log level configuration using env_logger syntax - **Valid Levels:** `error`, `warn`, `info`, `debug`, `trace` - **Examples:** ```bash --log-level info # All modules: info --log-level debug # All modules: debug --log-level "info,ahnlich_db=debug" # DB debug, others info --log-level "warn,ahnlich_db=trace" # DB trace, others warn --log-level "error,hf_hub=off" # Silence HuggingFace logs ``` --- ### Complete DB Example ```bash ahnlich-db run \ --host 0.0.0.0 \ --port 1369 \ --allocator-size 21474836480 \ --enable-persistence \ --persist-location /var/lib/ahnlich/db.dat \ --persistence-interval 300000 \ --maximum-clients 2000 \ --threadpool-size 32 \ --enable-tracing \ --otel-endpoint http://jaeger:4317 \ --log-level "info,ahnlich_db=debug" ``` --- ## AI Proxy Server (ahnlich-ai) Start the AI proxy with: ```bash ahnlich-ai run [OPTIONS] ``` ### Server Options #### `--host` Same as DB server (default: `"127.0.0.1"`) #### `--port` - **Type:** u16 - **Default:** `1370` - **Description:** AI proxy server port - **Example:** ```bash --port 1370 # Default --port 8081 # Custom port ``` --- ### Database Connection #### `--without-db` - **Type:** bool (flag) - **Default:** `false` - **Description:** Start AI proxy without connecting to database (standalone mode) - **Conflicts With:** `--db-host`, `--db-port`, `--db-https`, `--db-client-pool-size` - **Example:** ```bash ahnlich-ai run --without-db ``` #### `--db-host` - **Type:** String - **Default:** `"127.0.0.1"` - **Description:** Ahnlich Database host to connect to - **Conflicts With:** `--without-db` - **Examples:** ```bash --db-host 127.0.0.1 --db-host ahnlich-db # Docker service name --db-host 192.168.1.10 ``` #### `--db-port` - **Type:** u16 - **Default:** `1369` - **Description:** Ahnlich Database port - **Conflicts With:** `--without-db` - **Example:** ```bash --db-port 1369 # Default --db-port 1400 # Custom DB port ``` #### `--db-https` - **Type:** bool (flag) - **Default:** `false` - **Description:** Use HTTPS for database connection - **Conflicts With:** `--without-db` - **Example:** ```bash --db-https # Use https:// instead of http:// ``` #### `--db-client-pool-size` - **Type:** usize - **Default:** `10` - **Description:** Number of database client connections in the pool - **Conflicts With:** `--without-db` - **Recommendation:** Increase for high-concurrency scenarios - **Examples:** ```bash --db-client-pool-size 10 # Default --db-client-pool-size 50 # Higher concurrency ``` --- ### AI Models #### `--supported-models` - **Type:** Comma-separated list - **Default:** All models (see table below) - **Description:** Which AI models to load and support - **Examples:** ```bash # Load only specific models --supported-models all-minilm-l6-v2,resnet-50 # Load text models only --supported-models all-minilm-l6-v2,all-minilm-l12-v2,bge-base-en-v1.5 # Load all models (default) # Omit flag or list all ``` **Supported Models:** | Model Name | Type | Max Input | Image/Audio Size | Embedding Dim | Use Case | |------------|------|-----------|------------------|---------------|----------| | `all-minilm-l6-v2` | Text | 256 tokens | N/A | 384 | Fast sentence embeddings | | `all-minilm-l12-v2` | Text | 256 tokens | N/A | 384 | Better sentence embeddings | | `bge-base-en-v1.5` | Text | 512 tokens | N/A | 768 | General text embedding | | `bge-large-en-v1.5` | Text | 512 tokens | N/A | 1024 | High-quality text embedding | | `resnet-50` | Image | N/A | 224x224 px | 2048 | Image classification features | | `clip-vit-b32-image` | Image | N/A | 224x224 px | 512 | Visual embeddings | | `clip-vit-b32-text` | Text | 77 tokens | N/A | 512 | Text for image-text matching | | `clap-audio` | Audio | 10 sec | 48kHz sample rate | 512 | Audio similarity search | | `clap-text` | Text | 512 tokens | N/A | 512 | Text-to-audio retrieval | | `buffalo-l` | Face | N/A | 640x640 px input | 512 | Face detection & recognition | | `sface-yunet` | Face | N/A | 640x640 px input | 128 | Lightweight face recognition | **Model Constraints:** | Model | Constraint | Details | |-------|------------|---------| | `clap-audio` | Max duration | 10 seconds (480,000 samples at 48kHz) | | `clap-audio` | Sample rate | Resampled to 48kHz | | `buffalo-l` | Input size | 640x640 px (resized internally) | | `buffalo-l` | Default confidence | 0.5 | | `buffalo-l` | License | **Non-commercial use only** | | `sface-yunet` | Input size | 640x640 px (resized internally) | | `sface-yunet` | Default confidence | 0.6 | | `sface-yunet` | License | Apache 2.0 / MIT (commercial OK) | **Notes:** - Face models return **multiple embeddings** (one per detected face) - they use OneToMany mode - Audio and face models **require** `ModelPreprocessing` - `NoPreprocessing` is not supported - CLAP audio/text share the same 512-dim embedding space for cross-modal search #### `--ai-model-idle-time` - **Type:** u64 (seconds) - **Default:** `300` (5 minutes) - **Description:** How long to keep models in memory before unloading (when idle) - **Examples:** ```bash --ai-model-idle-time 300 # 5 minutes (default) --ai-model-idle-time 600 # 10 minutes --ai-model-idle-time 60 # 1 minute --ai-model-idle-time 0 # Never unload ``` #### `--model-cache-location` - **Type:** PathBuf (directory path) - **Default:** `~/.ahnlich/models` - **Description:** Directory where model artifacts are cached - **Examples:** ```bash --model-cache-location ~/.ahnlich/models # Default --model-cache-location /var/lib/ahnlich/models --model-cache-location ./models ``` - **Note:** Models are downloaded from HuggingFace Hub on first use --- ### Performance Options #### `--session-profiling` - **Type:** bool (flag) - **Default:** `false` - **Description:** Enable ONNX Runtime session profiling - **Use:** For performance debugging and optimization - **Example:** ```bash --session-profiling ``` #### `--enable-streaming` - **Type:** bool (flag) - **Default:** `false` - **Description:** Decode images in chunks (reduces memory by 10x but 40% slower) - **Use:** When processing many large images with limited memory - **Example:** ```bash --enable-streaming ``` --- ### Memory, Persistence, Networking, Observability AI proxy supports all the same options as DB server: - `--allocator-size` - `--message-size` - `--enable-persistence` - `--persist-location` - `--persistence-interval` - `--fail-on-startup-if-persist-load-fails` - `--maximum-clients` - `--threadpool-size` - `--enable-tracing` - `--otel-endpoint` - `--log-level` See DB Server section above for details. --- ### Complete AI Example ```bash ahnlich-ai run \ --host 0.0.0.0 \ --port 1370 \ --db-host ahnlich-db \ --db-port 1369 \ --db-client-pool-size 20 \ --supported-models all-minilm-l6-v2,bge-base-en-v1.5,resnet-50 \ --ai-model-idle-time 600 \ --model-cache-location /var/lib/ahnlich/models \ --enable-streaming \ --allocator-size 21474836480 \ --enable-persistence \ --persist-location /var/lib/ahnlich/ai.dat \ --maximum-clients 2000 \ --enable-tracing \ --otel-endpoint http://jaeger:4317 \ --log-level "info,ahnlich_ai=debug,hf_hub=warn" ``` --- ## CLI Client (ahnlich) Interactive CLI tool for querying Ahnlich: ```bash ahnlich [OPTIONS] ``` ### Options #### `--agent` - **Type:** Enum (DB or AI) - **Required:** Yes - **Description:** Which server type to connect to - **Valid Values:** `DB`, `AI` - **Examples:** ```bash ahnlich --agent DB ahnlich --agent AI ``` #### `--host` - **Type:** String - **Default:** `"127.0.0.1"` - **Description:** Server host to connect to - **Examples:** ```bash --host 127.0.0.1 --host localhost --host ahnlich-db ``` #### `--port` - **Type:** u16 - **Default:** Auto-selected based on agent (DB=1369, AI=1370) - **Description:** Server port to connect to - **Examples:** ```bash # Defaults ahnlich --agent DB # Connects to :1369 ahnlich --agent AI # Connects to :1370 # Custom ports ahnlich --agent DB --port 8080 ahnlich --agent AI --port 8081 ``` ### CLI Examples ```bash # Connect to DB locally ahnlich --agent DB --host 127.0.0.1 --port 1369 # Connect to AI proxy ahnlich --agent AI --host 127.0.0.1 --port 1370 # Connect to remote server ahnlich --agent DB --host 192.168.1.10 --port 1369 ``` --- ## Algorithm Configuration ### Similarity Algorithms Used in `GetSimN` and similar operations: | Algorithm | Type | Use Case | Characteristics | |-----------|------|----------|-----------------| | `EuclideanDistance` | Linear | Absolute distance | Best for comparing magnitudes | | `DotProductSimilarity` | Linear | Fast comparison | Best when vectors are normalized | | `CosineSimilarity` | Linear | Direction-based | Best for normalized vectors, ignores magnitude | | `HNSW` | Non-linear | Approximate nearest neighbor | Best for large-scale similarity search with configurable accuracy/speed tradeoff | **Usage:** ``` # Linear algorithms - available by default GETSIMN 10 WITH [1.0, 2.0, 3.0] USING cosinesimilarity IN my_store # Non-linear - must be created CREATESTORE my_store DIMENSION 128 NONLINEARALGORITHMINDEX (HNSW) GETSIMN 10 WITH [1.0, 2.0, 3.0] USING hnsw IN my_store ``` --- ### Preprocessing Options For AI queries: | Option | Description | When to Use | |--------|-------------|-------------| | `NoPreprocessing` | Skip preprocessing | Input already preprocessed | | `ModelPreprocessing` | Apply model's preprocessing | Raw inputs (recommended) | **Usage:** ```python Set( store="my_store", inputs=[...], preprocess_action=PreprocessAction.ModelPreprocessing, ) ``` --- ### Execution Providers Hardware acceleration for AI models: | Provider | Description | Requirements | |----------|-------------|--------------| | `TensorRT` | NVIDIA TensorRT | CUDA ≥12, TensorRT | | `CUDA` | NVIDIA CUDA | CUDA ≥12, libcudnn9 | | `DirectML` | DirectX ML | Windows, DirectX 12 | | `CoreML` | Apple CoreML | macOS, Apple Silicon (not recommended for NLP) | **Usage:** ```python GetSimN( store="my_store", search_input=..., closest_n=10, algorithm=Algorithm.CosineSimilarity, preprocess_action=PreprocessAction.ModelPreprocessing, execution_provider=ExecutionProvider.CUDA, # GPU acceleration ) ``` --- ## Validation Rules ### Allocator Size - Minimum: 10 MiB (10,485,760 bytes) - With persistence: Must be >2x persistence file size - Recommended: Based on expected data volume ### Persistence - `--persist-location` required if `--enable-persistence` is set - Parent directory must exist and be writable - File should be on fast storage (SSD recommended) ### Tracing - `--otel-endpoint` required if `--enable-tracing` is set - Endpoint must be accessible (network connectivity) - Use gRPC endpoint (not HTTP) ### Database Connection (AI) - Cannot use `--without-db` with `--db-*` flags - DB must be running before AI proxy (unless `--without-db`) - DB port must be reachable from AI proxy --- ## Configuration Best Practices ### Production Deployment ```bash # Database ahnlich-db run \ --host 0.0.0.0 \ --port 1369 \ --allocator-size 53687091200 \ # 50 GiB for large datasets --enable-persistence \ --persist-location /mnt/data/db.dat \ --persistence-interval 300000 \ --maximum-clients 5000 \ --threadpool-size 64 \ # Match CPU cores --enable-tracing \ --otel-endpoint http://jaeger:4317 \ --log-level "info,ahnlich_db=info" # AI Proxy ahnlich-ai run \ --host 0.0.0.0 \ --port 1370 \ --db-host ahnlich-db \ --db-port 1369 \ --db-client-pool-size 50 \ --supported-models all-minilm-l6-v2,bge-base-en-v1.5 \ # Only needed models --ai-model-idle-time 600 \ --model-cache-location /mnt/models \ --enable-streaming \ # For image workloads --allocator-size 53687091200 \ --enable-persistence \ --persist-location /mnt/data/ai.dat \ --maximum-clients 5000 \ --enable-tracing \ --otel-endpoint http://jaeger:4317 ``` ### Development ```bash # Simple local setup ahnlich-db run --log-level debug ahnlich-ai run \ --db-host 127.0.0.1 \ --supported-models all-minilm-l6-v2 \ # Just one model for testing --log-level debug ``` --- ## Docker Compose Configuration See [Production Deployment](/docs/ahnlich-in-production/deployment) for complete Docker Compose examples. --- ## See Also - [Error Codes Reference](/docs/reference/error-codes) - Understanding error messages - [Troubleshooting](/docs/troubleshooting/common-issues) - Common configuration issues - [Production Deployment](/docs/ahnlich-in-production/deployment) - Docker and cloud deployment --- # FILE: reference/error-codes.md --- title: Error Codes Reference sidebar_position: 10 --- # Error Codes Reference This page documents all error codes, messages, and their solutions in Ahnlich DB and AI. ## gRPC Status Codes Ahnlich uses standard gRPC status codes for error responses: | gRPC Code | HTTP Code | When Used | |-----------|-----------|-----------| | `NotFound` | 404 | Store, predicate, or index not found | | `AlreadyExists` | 409 | Store already exists | | `InvalidArgument` | 400 | Invalid input, dimension mismatch, type errors | | `ResourceExhausted` | 429 | Memory allocation failures | | `FailedPrecondition` | 400 | Missing dependencies or prerequisites | | `OutOfRange` | 400 | Token limits, invalid ranges | | `Internal` | 500 | Model errors, runtime failures | --- ## Database (ahnlich-db) Errors ### Store Errors #### StoreNotFound **Error Message:** `Store "store_name" not found` **gRPC Code:** `NotFound` **Cause:** Attempting to access a store that doesn't exist. **Solution:** - Create the store first using `CREATESTORE` - Verify store name spelling - Use `LISTSTORES` to check available stores - Check if persistence file loaded correctly on restart **Example:** ``` LISTSTORES CREATESTORE mystore DIMENSION 128 ``` --- #### StoreAlreadyExists **Error Message:** `Store "store_name" already exists` **gRPC Code:** `AlreadyExists` **Cause:** Creating a store when one with that name already exists and `error_if_exists=true`. **Solution:** - Use a different store name - Set `error_if_exists=false` to silently skip creation - Drop the existing store first if you want to recreate it **Example:** ```rust CreateStore { store: "my_store".to_string(), dimension: 128, create_predicates: vec![], non_linear_indices: vec![], error_if_exists: false, // Won't error if exists } ``` --- #### StoreDimensionMismatch **Error Message:** `Store dimension is [128], input dimension of [256] was specified` **gRPC Code:** `InvalidArgument` **Cause:** Vector dimensions don't match the store's configured dimension. **Solution:** - Ensure all vectors match store dimension - Check embedding model output dimensions - For AI stores, verify query and index models have same embedding size **Common Model Dimensions:** - `all-minilm-l6-v2`: 384 - `all-minilm-l12-v2`: 384 - `bge-base-en-v1.5`: 768 - `bge-large-en-v1.5`: 1024 - `resnet-50`: 2048 - `clip-vit-b32-*`: 512 - `clap-audio` / `clap-text`: 512 - `buffalo-l`: 512 - `sface-yunet`: 128 --- ### Index Errors #### PredicateNotFound **Error Message:** `Predicate "field_name" not found in store, attempt CREATEPREDINDEX with predicate` **gRPC Code:** `NotFound` **Cause:** Querying using a predicate that hasn't been indexed. **Solution:** ``` CREATEPREDINDEX (field_name) IN my_store ``` Or include predicates when creating store: ``` CREATESTORE my_store DIMENSION 128 PREDICATES (author, category) ``` --- #### NonLinearIndexNotFound **Error Message:** `Non linear algorithm HNSW not found in store, create store with support` **gRPC Code:** `NotFound` **Cause:** Attempting to use a non-linear algorithm (HNSW) not created with the store. **Solution:** ``` CREATE_NON_LINEAR_ALGORITHM_INDEX my_store NONLINEARALGORITHMINDEX (HNSW) ``` Or include when creating store: ``` CREATESTORE my_store DIMENSION 128 NONLINEARALGORITHMINDEX (HNSW) ``` --- ### Resource Errors #### Allocation **Error Message:** `allocation error: CapacityOverflow` **gRPC Code:** `ResourceExhausted` **Cause:** Memory allocation failed - hit the `--allocator-size` limit. **Solution:** - Increase `--allocator-size` when starting server - Reduce batch sizes - Monitor memory usage - For images, use `--enable-streaming` in AI proxy **Example:** ```bash ahnlich-db run --allocator-size 21474836480 # 20 GiB ``` --- ## AI Proxy (ahnlich-ai) Errors ### Store Errors #### StoreNotFound / StoreAlreadyExists Same as DB errors above, but for AI stores. --- #### StoreTypeMismatchError **Error Message:** `Cannot query Input. Store expects [RawString], input type [Image] was provided` **gRPC Code:** `InvalidArgument` **Cause:** Sending wrong input type (text to image model or vice versa). **Solution:** - Use text (RawString) for text models: `all-minilm-*`, `bge-*`, `clip-vit-b32-text` - Use images (Image bytes) for image models: `resnet-50`, `clip-vit-b32-image` --- ### Input Errors #### InputNotSpecified **Error Message:** `"search_input" input not specified` **gRPC Code:** `InvalidArgument` **Cause:** Required input field is missing or empty. **Solution:** Provide the required input (text or image). --- #### TokenExceededError **Error Message:** `Max Token Exceeded. Model Expects [256], input type was [512]` **gRPC Code:** `OutOfRange` **Cause:** Input text exceeds model's maximum token limit. **Token Limits:** - `all-minilm-l6-v2`: 256 tokens - `all-minilm-l12-v2`: 256 tokens - `bge-base-en-v1.5`: 512 tokens - `bge-large-en-v1.5`: 512 tokens - `clip-vit-b32-text`: 77 tokens **Solution:** - Truncate text to fit within limit - Split long documents into chunks - Use model with larger token limit (BGE models support 512 tokens) **Example:** ```python # Truncate text text = long_text[:500] # Rough approximation # Or split into chunks chunks = [text[i:i+500] for i in range(0, len(text), 500)] ``` --- #### ImageDimensionsMismatchError **Error Message:** `Image Dimensions [(512, 512)] does not match the expected model dimensions [(224, 224)]` **gRPC Code:** `InvalidArgument` **Cause:** Image size doesn't match model requirements. **Model Requirements:** - `resnet-50`: 224x224 pixels - `clip-vit-b32-image`: 224x224 pixels **Solution:** - Resize images to 224x224 before sending - Use `PreprocessAction.ModelPreprocessing` to auto-resize **Example (Python):** ```python from PIL import Image img = Image.open("photo.jpg") img = img.resize((224, 224)) ``` --- #### ReservedError **Error Message:** `Reserved key "_ahnlich_input_key" used` **gRPC Code:** `InvalidArgument` **Cause:** Using reserved metadata key `_ahnlich_input_key`. **Solution:** Avoid using `_ahnlich_input_key` in your metadata - this key is reserved for internal use when `store_original=true`. --- ### Model Errors #### AIModelNotInitialized **Error Message:** `index_model or query_model not selected or loaded during aiproxy startup` **gRPC Code:** `Internal` **Cause:** Models not loaded at AI proxy startup. **Solution:** - Ensure models are specified in `--supported-models` - Check model cache location has write permissions - Verify network connectivity for model downloads (first use) - Check disk space in model cache directory **Example:** ```bash ahnlich-ai run \ --supported-models all-minilm-l6-v2,resnet-50 \ --model-cache-location /path/to/models ``` --- #### AIModelNotSupported **Error Message:** `index_model or query_model "model_name" not supported` **gRPC Code:** `Internal` **Cause:** Using a model not in the supported models list. **Supported Models:** - `all-minilm-l6-v2` (Text, 384-dim) - `all-minilm-l12-v2` (Text, 384-dim) - `bge-base-en-v1.5` (Text, 768-dim) - `bge-large-en-v1.5` (Text, 1024-dim) - `resnet-50` (Image, 2048-dim) - `clip-vit-b32-image` (Image, 512-dim) - `clip-vit-b32-text` (Text, 512-dim) - `clap-audio` (Audio, 512-dim) - `clap-text` (Text, 512-dim) - `buffalo-l` (Face, 512-dim) - `sface-yunet` (Face, 128-dim) **Solution:** Use one of the supported models above. --- #### DimensionsMismatchError **Error Message:** `Dimensions Mismatch between index [768], and Query [1024] Models` **gRPC Code:** `InvalidArgument` **Cause:** Index and query models have different embedding dimensions. **Solution:** Use compatible models with same embedding dimensions: - AllMiniLM (L6/L12): both 384-dim - BGE-Base: 768-dim - BGE-Large: 1024-dim - ClipVit-B32: both (text/image) 512-dim - CLAP: both (audio/text) 512-dim - Buffalo_L: 512-dim - SFace+YuNet: 128-dim --- #### ModelInitializationError **Error Message:** `Error initializing a model thread: failed to download model` **gRPC Code:** `Internal` **Cause:** Model initialization failed during download or loading. **Solutions:** - Check internet connectivity (models download from HuggingFace on first use) - Verify disk space in model cache location - Check firewall allows HuggingFace Hub access - Clear corrupted model cache and retry **Cache Location:** Default `~/.ahnlich/models` --- ### Database Connection Errors #### DatabaseClientError **Error Message:** `Proxy Errored with connection refused` **gRPC Code:** `FailedPrecondition` **Cause:** AI proxy cannot connect to database. **Solution:** - Ensure database is running before starting AI proxy - Verify `--db-host` and `--db-port` settings - Check firewall rules - For standalone mode, use `--without-db` flag **Example:** ```bash # Start DB first ahnlich-db run --port 1369 # Then start AI ahnlich-ai run --db-host 127.0.0.1 --db-port 1369 ``` --- ### Operation Errors #### DelKeyError **Error Message:** `Cannot call DelKey on store with store_original as false` **gRPC Code:** `FailedPrecondition` **Cause:** Attempting to delete keys when `store_original=false`. **Solution:** Recreate store with `store_original=true` if you need to delete original inputs: ```python CreateStore( store="my_store", query_model=AiModel.ALL_MINI_LM_L6_V2, index_model=AiModel.ALL_MINI_LM_L6_V2, store_original=True, # Required for DelKey ) ``` --- ### Image Processing Errors #### ImageBytesDecodeError **Error Message:** `Bytes could not be successfully decoded into an image` **gRPC Code:** `Internal` **Cause:** Invalid or corrupted image bytes. **Solution:** - Use standard image formats: JPEG, PNG, BMP, GIF - Validate image files aren't corrupted - Ensure proper encoding if using base64 --- #### ImageNonzeroDimensionError **Error Message:** `Image can't have zero value in any dimension. Found height: 0, width: 100` **gRPC Code:** `Internal` **Cause:** Image has zero width or height. **Solution:** Validate image dimensions before sending - both width and height must be > 0. --- ### Audio Processing Errors #### AudioBytesDecodeError **Error Message:** `Bytes could not be successfully decoded into audio:
` **gRPC Code:** `Internal` **Cause:** Invalid or corrupted audio bytes, or unsupported audio format. **Solution:** - Use supported audio formats: WAV, MP3, FLAC, OGG - Validate audio files aren't corrupted - Ensure proper encoding --- #### AudioTooLongError **Error Message:** `Audio input is too long (15000ms). Model accepts at most 10000ms per clip. Trim or split your audio before indexing.` **gRPC Code:** `InvalidArgument` **Cause:** Audio clip exceeds the 10-second maximum duration for CLAP models. **Solution:** - Trim audio to 10 seconds or less - Split longer audio into multiple clips - Use audio editing tools to extract relevant segments **Example (Python with pydub):** ```python from pydub import AudioSegment audio = AudioSegment.from_file("long_audio.wav") # Take first 10 seconds clip = audio[:10000] # milliseconds clip.export("short_clip.wav", format="wav") ``` --- #### AudioNoPreprocessingError **Error Message:** `NoPreprocessing is not supported for audio inputs. Audio requires decoding, resampling, and log-Mel spectrogram conversion before it can be passed to the model.` **gRPC Code:** `InvalidArgument` **Cause:** Attempted to use `NoPreprocessing` with audio input. **Solution:** Always use `ModelPreprocessing` for audio inputs: ```python Set( store="audio_store", inputs=[...], preprocess_action=PreprocessAction.ModelPreprocessing, # Required for audio ) ``` --- #### AudioResampleError **Error Message:** `Audio could not be resampled:
` **gRPC Code:** `Internal` **Cause:** Failed to resample audio to required 48kHz sample rate. **Solution:** - Pre-convert audio to 48kHz before sending - Ensure audio file is not corrupted --- ### Face Recognition Errors #### FaceModelNoPreprocessingError **Error Message:** `NoPreprocessing is not supported for face recognition models. Face models require multi-stage detection and alignment that cannot be bypassed.` **gRPC Code:** `InvalidArgument` **Cause:** Attempted to use `NoPreprocessing` with face detection models (Buffalo_L or SFace+YuNet). **Solution:** Always use `ModelPreprocessing` for face models: ```python Set( store="faces_store", inputs=[...], preprocess_action=PreprocessAction.ModelPreprocessing, # Required for face models ) ``` --- #### MultipleEmbeddingsForQuery **Error Message:** `Query input produced 3 embeddings - query input must produce exactly 1 embedding` **gRPC Code:** `InvalidArgument` **Cause:** Face detection found multiple faces in a query image, but queries must produce exactly one embedding. **Solution:** - For queries, use images with exactly one face - Crop the image to contain only the target face - For indexing multiple faces, use `Set` (which supports multiple embeddings per image) --- ## Client Errors ### InvalidURI **Error Message:** `Invalid URI "invalid-uri"` **gRPC Code:** `InvalidArgument` **Cause:** Malformed connection URI. **Solution:** Use valid URI format: ``` http://127.0.0.1:1369 (DB) http://127.0.0.1:1370 (AI) ``` --- ### Tonic (Transport Error) **Error Message:** `Transport issues with tonic: connection error` **gRPC Code:** `Internal` **Cause:** Network or transport layer error. **Solutions:** - Check server is running and accessible - Verify network connectivity - Check firewall rules - Ensure correct host/port configuration --- ## DSL/CLI Errors ### UnsupportedAlgorithm **Error Message:** `Found unsupported algorithm "invalid_algo"` **Cause:** Unknown algorithm name in DSL command. **Valid Algorithms:** - `EuclideanDistance` - `DotProductSimilarity` - `CosineSimilarity` - `HNSW` --- ### UnsupportedAIModel **Error Message:** `Found unsupported ai model "invalid_model"` **Cause:** Unknown AI model name. **Solution:** Use one of the 11 supported models: - Text: `all-minilm-l6-v2`, `all-minilm-l12-v2`, `bge-base-en-v1.5`, `bge-large-en-v1.5`, `clip-vit-b32-text`, `clap-text` - Image: `resnet-50`, `clip-vit-b32-image` - Face: `buffalo-l`, `sface-yunet` - Audio: `clap-audio` --- ### UnsupportedPreprocessingMode **Error Message:** `Unexpected preprocessing mode` **Cause:** Invalid preprocessing option. **Valid Options:** - `NoPreprocessing` - `ModelPreprocessing` --- ## Common Error Combinations ### "Connection Refused" + "StoreNotFound" Indicates server connectivity issues. Check: 1. Server is running 2. Host/port configuration is correct 3. Firewall allows connections ### "DimensionMismatch" + AI Store Check that query_model and index_model have same embedding dimensions. ### "TokenExceeded" + Long Text Split text into smaller chunks or use model with larger token limit. --- ## Getting Help If you encounter an error not listed here: 1. Check server logs for detailed stack traces 2. Enable tracing: `--enable-tracing --otel-endpoint http://jaeger:4317` 3. Report issues: [GitHub Issues](https://github.com/deven96/ahnlich/issues) 4. Ask in: [WhatsApp Community](https://chat.whatsapp.com/E4CP7VZ1lNH9dJUxpsZVvD) --- # FILE: stores/create-non-linear-algx.mdx --- title: Create non-linear index sidebar_label: Create non-linear index --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Create non-linear index Add a non-linear index (HNSW) to accelerate [similarity search](/docs/stores/get-simn) on a [store](/docs/stores). > Every operation works on both **Vector DB stores** (you supply raw vectors) and **AI stores** (you supply text or images and [Ahnlich AI](/docs/components/ahnlich-ai) generates the embeddings for you). Use the **Vector DB / AI** switch in the sample below. ## Parameters | Parameter | Type | Description | | --- | --- | --- | | `store` | string | Store to index. | | `non_linear_indices` | list | Index specs — an `HnswConfig` per index. | | `schema` | string · optional | Schema to target. Defaults to `public`. | > All requests accept an optional `schema` field. When omitted, the server uses the `public` schema. ## Behavior - **HNSW** — approximate nearest-neighbour with a configurable accuracy/speed tradeoff. - Pre-structuring the data speeds up queries but uses extra memory. ## Sample query ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc.algorithm.nonlinear import NonLinearIndex, HnswConfig async def create_non_linear_algo_index(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) # Create an HNSW index (with optional config) response = await client.create_non_linear_algorithm_index( db_query.CreateNonLinearAlgorithmIndex( store="my_store", schema="analytics", non_linear_indices=[NonLinearIndex(hnsw=HnswConfig())] ) ) # response.created_indexes shows how many indexes were created print(response) if __name__ == "__main__": asyncio.run(create_non_linear_algo_index()) ``` ```typescript import { createDbClient } from "ahnlich-client-node"; import { CreateNonLinearAlgorithmIndex } from "ahnlich-client-node/grpc/db/query_pb"; import { NonLinearIndex, HNSWConfig } from "ahnlich-client-node/grpc/algorithm/nonlinear_pb"; async function createHNSWIndex() { const client = createDbClient("127.0.0.1:1369"); await client.createNonLinearAlgorithmIndex( new CreateNonLinearAlgorithmIndex({ store: "my_store", schema: "analytics", nonLinearIndices: [ new NonLinearIndex({ index: { case: "hnsw", value: new HNSWConfig() }, }), ], }) ); console.log("HNSW index created successfully"); } createHNSWIndex(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" nonlinear "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/algorithm/nonlinear" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.NewClient(ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } func (c *ExampleDBClient) exampleCreateNonLinearAlgoIndex() error { // Create an HNSW index (with optional config) _, err := c.client.CreateNonLinearAlgorithmIndex(c.ctx, &dbquery.CreateNonLinearAlgorithmIndex{ Store: "my_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted NonLinearIndices: []*nonlinear.NonLinearIndex{ {Index: &nonlinear.NonLinearIndex_Hnsw{Hnsw: &nonlinear.HNSWConfig{}}}, }, }) return err } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleCreateNonLinearAlgoIndex(); err != nil { log.Fatalf("CreateNonLinearAlgoIndex failed: %v", err) } fmt.Println("Created Non-Linear Algorithm Index for 'my_store'") } ``` ```rust use ahnlich_client_rs::db::DbClient; use ahnlich_types::db::query::CreateNonLinearAlgorithmIndex; use ahnlich_types::algorithm::nonlinear::{NonLinearIndex, non_linear_index, HnswConfig}; #[tokio::main] async fn main() -> Result<(), Box> { let db_client = DbClient::new("127.0.0.1:1369".to_string()).await?; // Create an HNSW index on the "my_store" store (with default config) let params = CreateNonLinearAlgorithmIndex { store: "my_store".to_string(), schema: Some("analytics".to_string()), non_linear_indices: vec![ NonLinearIndex { index: Some(non_linear_index::Index::Hnsw(HnswConfig::default())), }, ], }; let result = db_client.create_non_linear_algorithm_index(params, None).await?; println!("Created non-linear indices: {:?}", result); Ok(()) } ``` ```bash CREATENONLINEARALGORITHMINDEX (hnsw) IN my_store ``` ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc.algorithm.nonlinear import NonLinearIndex, HnswConfig async def create_non_linear_algorithm_index(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) # Create an HNSW index (with optional config) response = await client.create_non_linear_algorithm_index( ai_query.CreateNonLinearAlgorithmIndex( store="my_store", schema="analytics", non_linear_indices=[NonLinearIndex(hnsw=HnswConfig())], ) ) if __name__ == "__main__": asyncio.run(create_non_linear_algorithm_index()) ``` ```typescript import { createAiClient } from "ahnlich-client-node"; import { CreateNonLinearAlgorithmIndex } from "ahnlich-client-node/grpc/ai/query_pb"; import { NonLinearIndex, HNSWConfig } from "ahnlich-client-node/grpc/algorithm/nonlinear_pb"; async function createHNSWIndex() { const client = createAiClient("127.0.0.1:1370"); await client.createNonLinearAlgorithmIndex( new CreateNonLinearAlgorithmIndex({ store: "ai_store", schema: "analytics", nonLinearIndices: [ new NonLinearIndex({ index: { case: "hnsw", value: new HNSWConfig() }, }), ], }) ); console.log("HNSW index created"); } createHNSWIndex(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" nonlinear "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/algorithm/nonlinear" ) const AIAddr = "127.0.0.1:1370" // ExampleAIClient wraps the connection + AIService client func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } // NewAIClient connects to the AI server func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock(), ) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- CreateNonLinearAlgorithmIndex ---- func (c *ExampleAIClient) exampleCreateNonLinearIndexAI() error { // Create an HNSW index (with optional config) _, err := c.client.CreateNonLinearAlgorithmIndex(c.ctx, &aiquery.CreateNonLinearAlgorithmIndex{ Store: "ai_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted NonLinearIndices: []*nonlinear.NonLinearIndex{ {Index: &nonlinear.NonLinearIndex_Hnsw{Hnsw: &nonlinear.HNSWConfig{}}}, }, }) if err != nil { return err } fmt.Println(" Successfully created NonLinearAlgorithm index: HNSW on store ai_store") return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf(" Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleCreateNonLinearIndexAI(); err != nil { log.Fatalf(" CreateNonLinearAlgorithmIndex failed: %v", err) } } ``` ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_types::ai::query::CreateNonLinearAlgorithmIndex; use ahnlich_types::algorithm::nonlinear::{NonLinearIndex, non_linear_index, HnswConfig}; use tokio; #[tokio::main] async fn main() -> Result<(), Box> { let client = AiClient::new("http://127.0.0.1:1370".to_string()).await?; // Create an HNSW index (with default config) let params = CreateNonLinearAlgorithmIndex { store: "MyStore".to_string(), schema: Some("analytics".to_string()), non_linear_indices: vec![ NonLinearIndex { index: Some(non_linear_index::Index::Hnsw(HnswConfig::default())), }, ], }; let result = client.create_non_linear_algorithm_index(params, None).await?; println!("Created non-linear indices: {:?}", result); Ok(()) } ``` ```bash CREATENONLINEARALGORITHMINDEX (hnsw) IN my_store ``` ## Response The number of indexes created. ## Related - [Similarity search](/docs/stores/get-simn) - [Drop non-linear index](/docs/stores/drop-non-linear-algx) --- # FILE: stores/create-predicate-index.mdx --- title: Create predicate index sidebar_label: Create predicate index --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Create predicate index Index one or more [metadata](/docs/concepts/metadata) fields so [predicate queries](/docs/stores/get-by-predicate) run faster. > Every operation works on both **Vector DB stores** (you supply raw vectors) and **AI stores** (you supply text or images and [Ahnlich AI](/docs/components/ahnlich-ai) generates the embeddings for you). Use the **Vector DB / AI** switch in the sample below. ## Parameters | Parameter | Type | Description | | --- | --- | --- | | `store` | string | Store to index. | | `predicates` | list | Metadata fields to index. | | `schema` | string · optional | Schema to target. Defaults to `public`. | > All requests accept an optional `schema` field. When omitted, the server uses the `public` schema. ## Behavior - Speeds up [Get by predicate](/docs/stores/get-by-predicate) and predicate filters used in [similarity search](/docs/stores/get-simn). - Indexing existing fields is safe — it builds over data already stored. ## Sample query ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query async def create_predicate_index(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) response = await client.create_pred_index( db_query.CreatePredIndex( store="my_store", schema="analytics", predicates=["label", "category"] ) ) # response.created_indexes shows how many indexes were created print(response) if __name__ =="__main__": asyncio.run(create_predicate_index()) ``` ```typescript import { createDbClient } from "ahnlich-client-node"; import { CreatePredIndex } from "ahnlich-client-node/grpc/db/query_pb"; async function createPredicateIndex() { const client = createDbClient("127.0.0.1:1369"); await client.createPredIndex( new CreatePredIndex({ store: "my_store", schema: "analytics", predicates: ["label", "category"], }) ); console.log("Predicate indices created successfully"); } createPredicateIndex(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.NewClient(ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } // -------------------- Create Predicate Index -------------------- func (c *ExampleDBClient) exampleCreatePredicateIndex() error { _, err := c.client.CreatePredIndex(c.ctx, &dbquery.CreatePredIndex{ Store: "my_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Predicates: []string{"label"}, }) if err != nil { return err } fmt.Println("Predicate index created for store: my_store") return nil } // -------------------- Main -------------------- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleCreatePredicateIndex(); err != nil { log.Fatalf("CreatePredicateIndex failed: %v", err) } } ``` ```rust use ahnlich_client_rs::db::DbClient; use ahnlich_types::db::query::CreatePredIndex; #[tokio::main] async fn main() -> Result<(), Box> { // connect to DB server let db_client = DbClient::new("127.0.0.1:1369".to_string()).await?; let tracing_id: Option = None; // Create predicate index request let params = CreatePredIndex { store: "my_store".to_string(), schema: Some("analytics".to_string()), predicates: vec!["label".to_string()], // metadata field names to index }; // Call the client match db_client.create_pred_index(params, tracing_id).await { Ok(result) => { println!("Created predicate index: {:?}", result); } Err(err) => { eprintln!("Error creating predicate index: {:?}", err); } } Ok(()) } ``` ```bash CREATEPREDINDEX (author, category) IN my_store ``` ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query async def create_predicate_index(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.create_pred_index( ai_query.CreatePredIndex( store="my_store", schema="analytics", predicates=["job", "rank"] ) ) print(response) # CreateIndex(created_indexes=1) if __name__ == "__main__": asyncio.run(create_predicate_index()) ``` ```typescript import { createAiClient } from "ahnlich-client-node"; import { CreatePredIndex } from "ahnlich-client-node/grpc/ai/query_pb"; async function createPredicateIndex() { const client = createAiClient("127.0.0.1:1370"); await client.createPredIndex( new CreatePredIndex({ store: "ai_store", schema: "analytics", predicates: ["brand", "category"], }) ); console.log("Predicate indices created"); } createPredicateIndex(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" ) const AIAddr = "127.0.0.1:1370" func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- CreatePredIndex ---- func (c *ExampleAIClient) exampleCreatePredIndexAI() error { _, err := c.client.CreatePredIndex(c.ctx, &aiquery.CreatePredIndex{ Store: "ai_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Predicates: []string{"f"}, }) if err != nil { return err } fmt.Println(`Predicate index created successfully with logic: Predicates: []string{"f"},`) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf(" Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleCreatePredIndexAI(); err != nil { log.Fatalf(" CreatePredIndex failed: %v", err) } } ``` ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_client_rs::error::AhnlichError; use ahnlich_types::ai::query::CreatePredIndex; use ahnlich_types::ai::server::CreateIndex; #[tokio::main] async fn main() -> Result<(), AhnlichError> { // Connect to the AI service let ai_client = AiClient::new("http://127.0.0.1:1370".to_string()) .await .expect("Failed to connect AI client"); // Define which store and which predicates to index let params = CreatePredIndex { store: "Deven Kicks".to_string(), schema: Some("analytics".to_string()), predicates: vec!["Brand".to_string(), "Vintage".to_string()], }; // Call the API let response: CreateIndex = ai_client.create_pred_index(params, None).await?; println!(" Created predicate indexes: {:?}", response); Ok(()) } ``` ```bash CREATEPREDINDEX (label, category) IN my_store ``` ## Response The number of indexes successfully created. ## Related - [Get by predicate](/docs/stores/get-by-predicate) - [Drop predicate index](/docs/stores/drop-predicate-index) - [Predicates](/docs/components/predicates) --- # FILE: stores/create-store.mdx --- title: Create a store sidebar_label: Create a store --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Create a store Create a new, isolated [store](/docs/stores) — Ahnlich's container for vectors and their [metadata](/docs/concepts/metadata), like a *collection* in a document database or a *table* in a relational one. > Every operation works on both **Vector DB stores** (you supply raw vectors) and **AI stores** (you supply text or images and [Ahnlich AI](/docs/components/ahnlich-ai) generates the embeddings for you). Use the **Vector DB / AI** switch in the sample below. ## Parameters | Parameter | Type | Description | | --- | --- | --- | | `store` | string | Unique name for the store. | | `dimension` | int | Vector dimensionality — every key must match this length. | | `create_predicates` | list · optional | Metadata fields to index for filtering (seeds a [predicate index](/docs/stores/create-predicate-index)). | | `non_linear_indices` | list · optional | Non-linear algorithms for approximate search (see [non-linear index](/docs/stores/create-non-linear-algx)). | | `error_if_exists` | bool | If `true`, returns an error when the store already exists. | > All requests accept an optional `schema` field. When omitted, the server uses the `public` schema. ## Behavior - Creates a new isolated store; many stores can coexist for different workloads. - The `dimension` is **fixed at creation** — every vector key must match it. - You can seed [predicate](/docs/stores/create-predicate-index) and [non-linear](/docs/stores/create-non-linear-algx) indexes here, or add them later. ## Sample query ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc.db.server import Unit async def create_store(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) response = await client.create_store( db_query.CreateStore( store="my_store", schema="analytics", dimension=4, # Fixed vector dimension create_predicates=["label", "category"], # Index these metadata fields non_linear_indices=[], # Optional: non-linear algorithms for faster search error_if_exists=True ) ) # response is Unit() on success # All store_keys must match this dimension # Example valid key: valid_key = [1.0, 2.0, 3.0, 4.0, 5.0] # length = 5 assert isinstance(response, Unit) if __name__ == "__main__": asyncio.run(create_store()) ``` ```typescript import { createDbClient } from "ahnlich-client-node"; import { CreateStore } from "ahnlich-client-node/grpc/db/query_pb"; async function createStore() { const client = createDbClient("127.0.0.1:1369"); await client.createStore( new CreateStore({ store: "my_store", schema: "analytics", dimension: 4, predicates: ["label", "category"], errorIfExists: true, }) ); console.log("Store created successfully"); } createStore(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" nonlinear "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/algorithm/nonlinear" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.NewClient(ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } // CreateStore example func (c *ExampleDBClient) exampleCreateStore(store string, dimension int32) error { _, err := c.client.CreateStore(c.ctx, &dbquery.CreateStore{ Store: store, Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Dimension: uint32(dimension), CreatePredicates: []string{}, // Optional: list of metadata fields to index for filtering NonLinearIndices: []*nonlinear.NonLinearIndex{}, // Optional: non-linear algorithms (e.g., HNSW) for faster search ErrorIfExists: true, // Return error if store already exists }) if err != nil { return err } fmt.Printf("Created store: %s (dimension: %d)\n", store, dimension) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() storeName := "my_store" if err := client.exampleCreateStore(storeName, 4); err != nil { log.Fatalf("CreateStore failed: %v", err) } } ``` ```rust use ahnlich_client_rs::db::DbClient; use ahnlich_types::db::query::CreateStore; #[tokio::main] async fn main() -> Result<(), Box> { // Connect to server let db_client = DbClient::new("127.0.0.1:1369".to_string()).await?; let tracing_id: Option = None; // Define parameters for store creation let params = CreateStore { store: "my_store".to_string(), schema: Some("analytics".to_string()), dimension: 4, create_predicates: vec!["label".to_string(), "category".to_string()], non_linear_indices: vec![], error_if_exists: true, }; // Call create_store match db_client.create_store(params, tracing_id).await { Ok(res) => { println!("Store created successfully: {:?}", res); } Err(err) => { eprintln!("Error creating store: {:?}", err); } } Ok(()) } ``` ```bash CREATESTORE my_store DIMENSION 4 PREDICATES (label, category) ``` ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc.ai.models import AiModel async def create_store(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.create_store( ai_query.CreateStore( store="my_store", schema="analytics", query_model=AiModel.ALL_MINI_LM_L6_V2, index_model=AiModel.ALL_MINI_LM_L6_V2, predicates=["job"], non_linear_indices=[], # Optional: non-linear algorithms for faster search error_if_exists=True, # Store original controls if we choose to store the raw inputs # within the DB in order to be able to retrieve the originals again # during query, else only store values are returned store_original=True ) ) print(response) # Unit() if __name__ == "__main__": asyncio.run(create_store()) ``` ```typescript import { createAiClient } from "ahnlich-client-node"; import { CreateStore } from "ahnlich-client-node/grpc/ai/query_pb"; import { AIModel } from "ahnlich-client-node/grpc/ai/models_pb"; async function createStore() { const client = createAiClient("127.0.0.1:1370"); await client.createStore( new CreateStore({ store: "ai_store", schema: "analytics", queryModel: AIModel.ALL_MINI_LM_L6_V2, indexModel: AIModel.ALL_MINI_LM_L6_V2, predicates: ["brand", "category"], errorIfExists: true, storeOriginal: true, }) ); console.log("AI store created successfully"); } createStore(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aimodel "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/models" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" nonlinear "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/algorithm/nonlinear" ) const AIAddr = "127.0.0.1:1370" // ExampleAIClient holds the gRPC connection and AI client. func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } // NewAIClient connects to the AI server and returns a client. func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } // Close closes the gRPC connection. func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- CreateStore Example ---- // Create a new store for AI operations. func (c *ExampleAIClient) exampleCreateStoreAI() error { _, err := c.client.CreateStore(c.ctx, &aiquery.CreateStore{ Store: "ai_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted QueryModel: aimodel.AIModel_ALL_MINI_LM_L6_V2, IndexModel: aimodel.AIModel_ALL_MINI_LM_L6_V2, Predicates: []string{}, // Optional: metadata fields to index for filtering NonLinearIndices: []*nonlinear.NonLinearIndex{}, // Optional: non-linear algorithms (e.g., HNSW) for faster search ErrorIfExists: true, // Return error if store already exists StoreOriginal: true, // Store original input (needed for key deletion) }) if err != nil { return err } fmt.Println(" AI Store created: ai_store01") return nil } // ---- MAIN ---- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf("Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleCreateStoreAI(); err != nil { log.Fatalf("CreateStore failed: %v", err) } } ``` ```rust use ahnlich_types::ai::query::CreateStore; use ahnlich_client_rs::ai::AiClient; use ahnlich_types::ai::models::AiModel; #[tokio::main] async fn main() -> Result<(), Box> { let client = AiClient::new("http://127.0.0.1:1370".to_string()).await?; // Create a new AI store let create_params = CreateStore { store: "Deven Kicks".to_string(), schema: Some("analytics".to_string()), index_model: AiModel::AllMiniLmL6V2 as i32, query_model: AiModel::AllMiniLmL6V2 as i32, predicates: vec![], non_linear_indices: vec![], error_if_exists: true, store_original: true, }; let result = client.create_store(create_params, None).await?; println!("Store created: {:?}", result); Ok(()) } ``` ```bash CREATESTORE my_store QUERYMODEL all-minilm-l6-v2 INDEXMODEL all-minilm-l6-v2 PREDICATES (label, category) STOREORIGINAL ``` ## Response A confirmation message (`Unit`). ## Related - [Insert data](/docs/stores/set) - [Create predicate index](/docs/stores/create-predicate-index) - [Create non-linear index](/docs/stores/create-non-linear-algx) --- # FILE: stores/delete-key.mdx --- title: Delete by key sidebar_label: Delete by key --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Delete by key Delete entries by an exact vector-key match. > Every operation works on both **Vector DB stores** (you supply raw vectors) and **AI stores** (you supply text or images and [Ahnlich AI](/docs/components/ahnlich-ai) generates the embeddings for you). Use the **Vector DB / AI** switch in the sample below. ## Parameters | Parameter | Type | Description | | --- | --- | --- | | `store` | string | Name of the store to delete from. | | `keys` | list of vectors | The exact vector keys to remove. | | `schema` | string · optional | Schema to target. Defaults to `public`. | > All requests accept an optional `schema` field. When omitted, the server uses the `public` schema. ## Behavior - Removes only the entries whose key matches **exactly**. - Keys that aren't present are silently ignored. ## Sample query ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc import keyval async def delete_key(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) store_key = keyval.StoreKey(key=[1.0, 2.0, 3.0, 4.0]) response = await client.del_key( db_query.DelKey( store="my_store", schema="analytics", keys=[store_key] ) ) # response.deleted_count shows how many items were deleted print(response) if __name__ == "__main__": asyncio.run(delete_key()) ``` ```typescript import { createDbClient } from "ahnlich-client-node"; import { DelKey } from "ahnlich-client-node/grpc/db/query_pb"; import { StoreKey } from "ahnlich-client-node/grpc/keyval_pb"; async function deleteKey() { const client = createDbClient("127.0.0.1:1369"); await client.delKey( new DelKey({ store: "my_store", schema: "analytics", keys: [new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] })], }) ); console.log("Entry deleted successfully"); } deleteKey(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.NewClient(ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } // -------------------- Delete Key -------------------- func (c *ExampleDBClient) exampleDeleteKey() error { _, err := c.client.DelKey(c.ctx, &dbquery.DelKey{ Store: "my_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Keys: []*keyval.StoreKey{{Key: []float32{1, 2, 3, 4}}}, }) if err != nil { return err } fmt.Println("Deleted key from store: my_store") return nil } // -------------------- Main -------------------- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleDeleteKey(); err != nil { log.Fatalf("DeleteKey failed: %v", err) } } ``` ```rust use ahnlich_client_rs::db::DbClient; use ahnlich_types::db::query::DelKey; use ahnlich_types::keyval::StoreKey; use tokio; #[tokio::main] async fn main() -> Result<(), Box> { let addr = "http://127.0.0.1:1369"; // adjust if your server runs elsewhere let client = DbClient::new(addr.to_string()).await?; // Delete a specific key from store "my_store" (dimension must match) let del_key_params = DelKey { store: "my_store".to_string(), schema: Some("analytics".to_string()), keys: vec![StoreKey { key: vec![1.0, 1.1, 1.2], // ✅ matches dimension=4 }], }; match client.del_key(del_key_params, None).await { Ok(result) => println!("Deleted count: {:?}", result.deleted_count), Err(e) => eprintln!("Error: {:?}", e), } Ok(()) } ``` ```bash DELETEKEY (key1) IN my_store ``` ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc import keyval async def drop_key(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.del_key( ai_query.DelKey( store="my_store", schema="analytics", keys=[keyval.StoreInput(raw_string="Custom Made Jordan 4")] ) ) print(response) # Del() if __name__ == "__main__": asyncio.run(drop_key()) ``` ```typescript import { createAiClient } from "ahnlich-client-node"; import { DelKey } from "ahnlich-client-node/grpc/ai/query_pb"; import { StoreInput } from "ahnlich-client-node/grpc/keyval_pb"; async function deleteKey() { const client = createAiClient("127.0.0.1:1370"); await client.delKey( new DelKey({ store: "ai_store", schema: "analytics", keys: [ new StoreInput({ value: { case: "rawString", value: "Jordan One" } }), ], }) ); console.log("Entry deleted"); } deleteKey(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" ) const AIAddr = "127.0.0.1:1370" // ExampleAIClient wraps the connection + AIService client func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } // NewAIClient creates and connects the AI client func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock(), ) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- DelKey ---- func (c *ExampleAIClient) exampleDeleteKeyAI() error { _, err := c.client.DelKey(c.ctx, &aiquery.DelKey{ Store: "ai_store01", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Keys: []*keyval.StoreInput{ {Value: &keyval.StoreInput_RawString{RawString: "X"}}, }, }) if err != nil { return err } fmt.Println(" Successfully deleted key 'X' from store ai_store") return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf(" Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleDeleteKeyAI(); err != nil { log.Fatalf(" DeleteKey failed: %v", err) } } ``` ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_client_rs::error::AhnlichError; use ahnlich_types::ai::query::DelKey; use ahnlich_types::ai::server::Del; use ahnlich_types::keyval::StoreInput; use ahnlich_types::keyval::store_input::Value; #[tokio::main] async fn main() -> Result<(), AhnlichError> { // Connect to server let ai_client = AiClient::new("http://127.0.0.1:1370".to_string()) .await .expect(" Failed to connect AI client"); // Define key to delete let params = DelKey { store: "Deven Kicks".to_string(), schema: Some("analytics".to_string()), keys: vec![StoreInput { value: Some(Value::RawString("Nike Air Jordans".to_string())), }], }; // Call delete let response: Del = ai_client.del_key(params, None).await?; println!(" Deleted key result: {:?}", response); Ok(()) } ``` ```bash DELETEKEY ([This is a sentence]) IN my_store ``` ## Response The number of entries deleted. ## Related - [Delete by predicate](/docs/stores/delete-predicate) - [Get by key](/docs/stores/get-key) --- # FILE: stores/delete-predicate.mdx --- title: Delete by predicate sidebar_label: Delete by predicate --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Delete by predicate Delete every entry that matches a **metadata condition**. > Every operation works on both **Vector DB stores** (you supply raw vectors) and **AI stores** (you supply text or images and [Ahnlich AI](/docs/components/ahnlich-ai) generates the embeddings for you). Use the **Vector DB / AI** switch in the sample below. ## Parameters | Parameter | Type | Description | | --- | --- | --- | | `store` | string | Name of the store to delete from. | | `condition` | predicate | A [metadata](/docs/concepts/metadata) predicate identifying the entries to remove. | | `schema` | string · optional | Schema to target. Defaults to `public`. | > All requests accept an optional `schema` field. When omitted, the server uses the `public` schema. ## Behavior - Deletes **all** entries satisfying the predicate in one call. - Use a narrow predicate — there is no automatic confirmation step. ## Sample query ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc import predicates, metadata from ahnlich_client_py.grpc.db.server import Del async def delete_predicate(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) condition = predicates.PredicateCondition( value=predicates.Predicate( equals=predicates.Equals( key="label", value=metadata.MetadataValue(raw_string="sorcerer") ) ) ) response = await client.del_pred( db_query.DelPred( store="my_store", schema="analytics", condition=condition ) ) # response.deleted_count shows how many items were deleted if __name__ == "__main__": asyncio.run(delete_predicate()) ``` ```typescript import { createDbClient } from "ahnlich-client-node"; import { DelPred } from "ahnlich-client-node/grpc/db/query_pb"; import { PredicateCondition, Predicate, Equals } from "ahnlich-client-node/grpc/predicate_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; async function deletePredicate() { const client = createDbClient("127.0.0.1:1369"); const response = await client.delPred( new DelPred({ store: "my_store", schema: "analytics", condition: new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "status", value: new MetadataValue({ value: { case: "rawString", value: "archived" } }), }), }, }), }, }), }) ); console.log(`Deleted ${response.deleted} entries`); } deletePredicate(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" predicates "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/predicates" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.NewClient(ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } // -------------------- Delete Predicate -------------------- func (c *ExampleDBClient) exampleDeletePredicate() error { condition := &predicates.PredicateCondition{ Kind: &predicates.PredicateCondition_Value{ Value: &predicates.Predicate{ Kind: &predicates.Predicate_Equals{ Equals: &predicates.Equals{ Key: "label", Value: &metadata.MetadataValue{ Value: &metadata.MetadataValue_RawString{ RawString: "A", }, }, }, }, }, }, } _, err := c.client.DelPred(c.ctx, &dbquery.DelPred{ Store: "my_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Condition: condition, }) if err != nil { return err } fmt.Println("Deleted entries matching predicate from store: my_store") return nil } // -------------------- Main -------------------- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleDeletePredicate(); err != nil { log.Fatalf("DeletePredicate failed: %v", err) } } ``` ```rust use ahnlich_types::db::query::DelPred; use ahnlich_types::db::server::Del; use ahnlich_types::predicates::{Predicate, PredicateCondition, predicate::Kind as PredicateKind, predicate_condition::Kind as PredicateConditionKind}; use ahnlich_client_rs::db::DbClient; use ahnlich_types::metadata::{MetadataValue, metadata_value::Value}; use std::collections::HashMap; #[tokio::main] async fn main() -> Result<(), Box> { // gRPC server address let addr = "http://127.0.0.1:1369".to_string(); let db_client = DbClient::new(addr).await?; // Define the predicate condition to delete let condition = PredicateCondition { kind: Some(PredicateConditionKind::Value(Predicate { kind: Some(PredicateKind::Equals(ahnlich_types::predicates::Equals { key: "medal".into(), value: Some(MetadataValue { value: Some(Value::RawString("gold".into())), }), })), })), }; // Parameters for deleting predicate let params = DelPred { store: "my_store".to_string(), // your store name schema: Some("analytics".to_string()), condition: Some(condition), }; // Call delete predicate match db_client.del_pred(params, None).await { Ok(Del { deleted_count }) => { println!("Successfully deleted {} predicate(s).", deleted_count); } Err(e) => { eprintln!("Error deleting predicate: {:?}", e); } } Ok(()) } ``` This operation isn't exposed as a standalone CLI command — use one of the client libraries above. ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc import predicates, metadata from ahnlich_client_py.grpc.ai.server import Del async def delete_predicate(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) condition = predicates.PredicateCondition( value=predicates.Predicate( equals=predicates.Equals( key="category", value=metadata.MetadataValue(raw_string="archived") ) ) ) response = await client.del_pred( ai_query.DelPred( store="my_ai_store", schema="analytics", condition=condition ) ) # response.deleted_count shows how many items were deleted if __name__ == "__main__": asyncio.run(delete_predicate()) ``` ```typescript import { createAiClient } from "ahnlich-client-node"; import { DelPred } from "ahnlich-client-node/grpc/ai/query_pb"; import { PredicateCondition, Predicate, Equals } from "ahnlich-client-node/grpc/predicate_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; async function deletePredicate() { const client = createAiClient("127.0.0.1:1370"); // Create a predicate condition to match entries where "category" equals "outdated" // Using oneof discriminated unions with { case: "...", value: ... } pattern const condition = new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "category", value: new MetadataValue({ value: { case: "rawString", value: "outdated" } }) }) } }) } }); const response = await client.delPred( new DelPred({ store: "my_store", schema: "analytics", condition: condition }) ); console.log(`Deleted ${response.deletedCount} items`); } deletePredicate(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" predicates "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/predicates" ) const ServerAddr = "127.0.0.1:1370" func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.NewClient(ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", ServerAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } // -------------------- Delete Predicate -------------------- func (c *ExampleAIClient) exampleDeletePredicate() error { condition := &predicates.PredicateCondition{ Kind: &predicates.PredicateCondition_Value{ Value: &predicates.Predicate{ Kind: &predicates.Predicate_Equals{ Equals: &predicates.Equals{ Key: "category", Value: &metadata.MetadataValue{ Value: &metadata.MetadataValue_RawString{ RawString: "archived", }, }, }, }, }, }, } _, err := c.client.DelPred(c.ctx, &aiquery.DelPred{ Store: "my_ai_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Condition: condition, }) if err != nil { return err } fmt.Println("Deleted entries matching predicate from AI store: my_ai_store") return nil } // -------------------- Main -------------------- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf("Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleDeletePredicate(); err != nil { log.Fatalf("DeletePredicate failed: %v", err) } } ``` ```rust use ahnlich_types::ai::query::DelPred; use ahnlich_types::ai::server::Del; use ahnlich_types::predicates::{Predicate, PredicateCondition, predicate::Kind as PredicateKind, predicate_condition::Kind as PredicateConditionKind}; use ahnlich_client_rs::ai::AiClient; use ahnlich_types::metadata::{MetadataValue, metadata_value::Value}; #[tokio::main] async fn main() -> Result<(), Box> { // gRPC server address let addr = "http://127.0.0.1:1370".to_string(); let ai_client = AiClient::new(addr).await?; // Define the predicate condition to delete let condition = PredicateCondition { kind: Some(PredicateConditionKind::Value(Predicate { kind: Some(PredicateKind::Equals(ahnlich_types::predicates::Equals { key: "category".into(), value: Some(MetadataValue { value: Some(Value::RawString("archived".into())), }), })), })), }; // Parameters for deleting predicate let params = DelPred { store: "my_ai_store".to_string(), schema: Some("analytics".to_string()), condition: Some(condition), }; // Call delete predicate match ai_client.del_pred(params, None).await { Ok(Del { deleted_count }) => { println!("Successfully deleted {} record(s).", deleted_count); } Err(e) => { eprintln!("Error deleting predicate: {:?}", e); } } Ok(()) } ``` This operation isn't exposed as a standalone CLI command — use one of the client libraries above. ## Response The number of entries deleted. ## Related - [Delete by key](/docs/stores/delete-key) - [Get by predicate](/docs/stores/get-by-predicate) --- # FILE: stores/drop-non-linear-algx.mdx --- title: Drop non-linear index sidebar_label: Drop non-linear index --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Drop non-linear index Remove a [non-linear index](/docs/stores/create-non-linear-algx) (HNSW) from a store. > Every operation works on both **Vector DB stores** (you supply raw vectors) and **AI stores** (you supply text or images and [Ahnlich AI](/docs/components/ahnlich-ai) generates the embeddings for you). Use the **Vector DB / AI** switch in the sample below. ## Parameters | Parameter | Type | Description | | --- | --- | --- | | `store` | string | Store to modify. | | `non_linear_indices` | list | The index types to drop. | | `schema` | string · optional | Schema to target. Defaults to `public`. | > All requests accept an optional `schema` field. When omitted, the server uses the `public` schema. ## Behavior - Deletes the index only — stored vectors and [metadata](/docs/concepts/metadata) are untouched. - [Similarity search](/docs/stores/get-simn) still works, falling back to a linear scan. ## Sample query ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc.algorithm.nonlinear import NonLinearAlgorithm async def drop_non_linear_algo(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) response = await client.drop_non_linear_algorithm_index( db_query.DropNonLinearAlgorithmIndex( store="my_store", schema="analytics", non_linear_indices=[NonLinearAlgorithm.HNSW], error_if_not_exists=True ) ) # response.deleted_count shows how many indexes were removed print(response) if __name__ == "__main__": asyncio.run(drop_non_linear_algo()) ``` ```typescript import { createDbClient } from "ahnlich-client-node"; import { DropNonLinearAlgorithmIndex } from "ahnlich-client-node/grpc/db/query_pb"; import { NonLinearAlgorithm } from "ahnlich-client-node/grpc/algorithm/nonlinear_pb"; async function dropNonLinearIndex() { const client = createDbClient("127.0.0.1:1369"); await client.dropNonLinearAlgorithmIndex( new DropNonLinearAlgorithmIndex({ store: "my_store", schema: "analytics", nonLinearIndices: [NonLinearAlgorithm.HNSW], errorIfNotExists: true, }) ); console.log("HNSW index dropped successfully"); } dropNonLinearIndex(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" nonlinear "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/algorithm/nonlinear" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.NewClient(ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } func (c *ExampleDBClient) exampleDropNonLinearAlgoIndex() error { _, err := c.client.DropNonLinearAlgorithmIndex(c.ctx, &dbquery.DropNonLinearAlgorithmIndex{ Store: "my_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted NonLinearIndices: []nonlinear.NonLinearAlgorithm{nonlinear.NonLinearAlgorithm_HNSW}, ErrorIfNotExists: true, }) return err } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleDropNonLinearAlgoIndex(); err != nil { log.Fatalf("DropNonLinearAlgoIndex failed: %v", err) } fmt.Println("Dropped Non-Linear Algorithm Index for 'my_store'") } ``` ```rust use ahnlich_types::db::query::DropNonLinearAlgorithmIndex; use ahnlich_types::algorithm::nonlinear::NonLinearAlgorithm; use ahnlich_types::db::server::Del; use ahnlich_client_rs::db::DbClient; #[tokio::main] async fn main() -> Result<(), Box> { let addr = "http://127.0.0.1:1369".to_string(); let db_client = DbClient::new(addr).await?; let params = DropNonLinearAlgorithmIndex { store: "my_store".to_string(), schema: Some("analytics".to_string()), non_linear_indices: vec![NonLinearAlgorithm::Hnsw as i32], error_if_not_exists: false, }; match db_client .drop_non_linear_algorithm_index(params, None) .await { Ok(Del { deleted_count }) => { println!("Successfully dropped {} non-linear index(es).", deleted_count); } Err(e) => { eprintln!("Error dropping non-linear index: {:?}", e); } } Ok(()) } ``` ```bash DROPNONLINEARALGORITHMINDEX (hnsw) IN my_store ``` ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc.algorithm.nonlinear import NonLinearAlgorithm async def drop_non_linear_algorithm_index(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.drop_non_linear_algorithm_index( ai_query.DropNonLinearAlgorithmIndex( store="my_store", schema="analytics", non_linear_indices=[NonLinearAlgorithm.HNSW], error_if_not_exists=True ) ) print(response) # Del(deleted_count=1) if __name__ == "__main__": asyncio.run(drop_non_linear_algorithm_index()) ``` ```typescript import { createAiClient } from "ahnlich-client-node"; import { DropNonLinearAlgorithmIndex } from "ahnlich-client-node/grpc/ai/query_pb"; import { NonLinearAlgorithm } from "ahnlich-client-node/grpc/algorithm/nonlinear_pb"; async function dropNonLinearIndex() { const client = createAiClient("127.0.0.1:1370"); await client.dropNonLinearAlgorithmIndex( new DropNonLinearAlgorithmIndex({ store: "ai_store", schema: "analytics", nonLinearIndices: [NonLinearAlgorithm.HNSW], errorIfNotExists: true, }) ); console.log("HNSW index dropped"); } dropNonLinearIndex(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" nonlinear "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/algorithm/nonlinear" ) const AIAddr = "127.0.0.1:1370" // ExampleAIClient wraps the connection + AIService client func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } // NewAIClient connects to the AI server func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock(), ) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- DropNonLinearAlgorithmIndex standalone ---- func (c *ExampleAIClient) exampleDropNonLinearIndexAI() error { _, err := c.client.DropNonLinearAlgorithmIndex(c.ctx, &aiquery.DropNonLinearAlgorithmIndex{ Store: "ai_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted NonLinearIndices: []nonlinear.NonLinearAlgorithm{nonlinear.NonLinearAlgorithm_HNSW}, ErrorIfNotExists: true, }) if err != nil { return err } fmt.Println(" Successfully dropped NonLinearAlgorithm index: HNSW from store ai_store") return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf(" Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleDropNonLinearIndexAI(); err != nil { log.Fatalf(" DropNonLinearAlgorithmIndex failed: %v", err) } } ``` ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_types::ai::query::DropNonLinearAlgorithmIndex; use ahnlich_types::algorithm::nonlinear::NonLinearAlgorithm; use tokio; #[tokio::main] async fn main() -> Result<(), Box> { // Connect to AI server let client = AiClient::new("http://127.0.0.1:1370".to_string()).await?; // Set parameters for dropping the non-linear index let params = DropNonLinearAlgorithmIndex { store: "MyStore".to_string(), schema: Some("analytics".to_string()), non_linear_indices: vec![NonLinearAlgorithm::Hnsw as i32], error_if_not_exists: true, // error if the index doesn't exist }; // Execute drop request let result = client .drop_non_linear_algorithm_index(params, None) .await?; println!("Dropped non-linear indices: {:?}", result); Ok(()) } ``` ```bash DROPNONLINEARALGORITHMINDEX (hnsw) IN my_store ``` ## Response The number of indexes deleted. ## Related - [Create non-linear index](/docs/stores/create-non-linear-algx) - [Similarity search](/docs/stores/get-simn) --- # FILE: stores/drop-predicate-index.mdx --- title: Drop predicate index sidebar_label: Drop predicate index --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Drop predicate index Remove a [predicate index](/docs/stores/create-predicate-index) when it's no longer needed or to reduce storage overhead. > Every operation works on both **Vector DB stores** (you supply raw vectors) and **AI stores** (you supply text or images and [Ahnlich AI](/docs/components/ahnlich-ai) generates the embeddings for you). Use the **Vector DB / AI** switch in the sample below. ## Parameters | Parameter | Type | Description | | --- | --- | --- | | `store` | string | Store to modify. | | `predicates` | list | Metadata fields whose indexes should be dropped. | | `error_if_not_exists` | bool · optional | If `true`, errors when the index doesn't exist. | | `schema` | string · optional | Schema to target. Defaults to `public`. | > All requests accept an optional `schema` field. When omitted, the server uses the `public` schema. ## Behavior - Deletes the index only — the underlying entries and [metadata](/docs/concepts/metadata) remain. - Queries on the field still work, just without the index speed-up. ## Sample query ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc.db.server import Del async def drop_predicate_index(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) response = await client.drop_pred_index( db_query.DropPredIndex( store="my_store", schema="analytics", predicates=["label"], error_if_not_exists=True ) ) # response.deleted_count shows how many indexes were removed print(response) if __name__ == "__main__": asyncio.run(drop_predicate_index()) ``` ```typescript import { createDbClient } from "ahnlich-client-node"; import { DropPredIndex } from "ahnlich-client-node/grpc/db/query_pb"; async function dropPredicateIndex() { const client = createDbClient("127.0.0.1:1369"); await client.dropPredIndex( new DropPredIndex({ store: "my_store", schema: "analytics", predicates: ["label"], errorIfNotExists: true, }) ); console.log("Predicate index dropped successfully"); } dropPredicateIndex(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.NewClient(ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } // -------------------- Drop Predicate Index -------------------- func (c *ExampleDBClient) exampleDropPredicateIndex() error { _, err := c.client.DropPredIndex(c.ctx, &dbquery.DropPredIndex{ Store: "my_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Predicates: []string{"label"}, ErrorIfNotExists: true, }) if err != nil { return err } fmt.Println("Dropped predicate index for store: my_store") return nil } // -------------------- Main -------------------- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleDropPredicateIndex(); err != nil { log.Fatalf("DropPredicateIndex failed: %v", err) } } ``` ```rust use ahnlich_client_rs::db::DbClient; use ahnlich_types::db::query::DropPredIndex; use tokio; #[tokio::main] async fn main() -> Result<(), Box> { let addr = "http://127.0.0.1:1369"; let client = DbClient::new(addr.to_string()).await?; // Drop the "label" predicate index from store "my_store" let drop_index_params = DropPredIndex { store: "my_store".to_string(), schema: Some("analytics".to_string()), predicates: vec!["label".to_string()], error_if_not_exists: true, // fail if it doesn't exist }; match client.drop_pred_index(drop_index_params, None).await { Ok(result) => println!("Dropped predicate index: {:?}", result), Err(e) => eprintln!("Error: {:?}", e), } Ok(()) } ``` ```bash DROPPREDINDEX (category) IN my_store ``` ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query async def drop_predicate_index(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.drop_pred_index( ai_query.DropPredIndex( store="my_store", schema="analytics", predicates=["job"], error_if_not_exists=True ) ) print(response) # Del(deleted_count=1) if __name__ == "__main__": asyncio.run(drop_predicate_index()) ``` ```typescript import { createAiClient } from "ahnlich-client-node"; import { DropPredIndex } from "ahnlich-client-node/grpc/ai/query_pb"; async function dropPredicateIndex() { const client = createAiClient("127.0.0.1:1370"); await client.dropPredIndex( new DropPredIndex({ store: "ai_store", schema: "analytics", predicates: ["brand"], errorIfNotExists: true, }) ); console.log("Predicate index dropped"); } dropPredicateIndex(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" ) const AIAddr = "127.0.0.1:1370" // ExampleAIClient wraps the connection + AIService client func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } // NewAIClient creates and connects the AI client func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock(), ) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- DropPredIndex standalone ---- func (c *ExampleAIClient) exampleDropPredicateIndexAI() error { _, err := c.client.DropPredIndex(c.ctx, &aiquery.DropPredIndex{ Store: "ai_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Predicates: []string{"f"}, ErrorIfNotExists: true, }) if err != nil { return err } fmt.Println(" Successfully dropped predicate index for Predicates: [\"f\"] in store ai_store") return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf(" Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleDropPredicateIndexAI(); err != nil { log.Fatalf(" DropPredIndex failed: %v", err) } } ``` ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_client_rs::error::AhnlichError; use ahnlich_types::ai::query::DropPredIndex; use ahnlich_types::ai::server::Del; #[tokio::main] async fn main() -> Result<(), AhnlichError> { // Connect to the AI service let ai_client = AiClient::new("http://127.0.0.1:1370".to_string()) .await .expect("Failed to connect AI client"); // Define which store and which predicate index to drop let params = DropPredIndex { store: "Deven Kicks".to_string(), schema: Some("analytics".to_string()), predicates: vec!["Brand".to_string()], error_if_not_exists: true, // 👈 required field, prevents silent no-op }; // Call the API let response: Del = ai_client.drop_pred_index(params, None).await?; println!(" Dropped predicate index result: {:?}", response); Ok(()) } ``` ```bash DROPPREDINDEX (category) IN my_store ``` ## Response The number of indexes deleted. ## Related - [Create predicate index](/docs/stores/create-predicate-index) - [Get by predicate](/docs/stores/get-by-predicate) --- # FILE: stores/drop-store.mdx --- title: Drop a store sidebar_label: Drop a store --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Drop a store Permanently delete a [store](/docs/stores) and **all** its contents. This is destructive and cannot be undone. > Every operation works on both **Vector DB stores** (you supply raw vectors) and **AI stores** (you supply text or images and [Ahnlich AI](/docs/components/ahnlich-ai) generates the embeddings for you). Use the **Vector DB / AI** switch in the sample below. ## Parameters | Parameter | Type | Description | | --- | --- | --- | | `store` | string | Name of the store to delete. | | `error_if_not_exists` | bool · optional | If `true`, errors when the store doesn't exist. | | `schema` | string · optional | Schema to target. Defaults to `public`. | > All requests accept an optional `schema` field. When omitted, the server uses the `public` schema. ## Behavior - Removes the store and its data from the engine. - Pair with `error_if_not_exists` to make missing-store deletes a hard error. ## Sample query ```python import asyncio from grpclib.client import Channel from grpclib.exceptions import GRPCError from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc.db.server import Del async def drop_store(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) response = await client.drop_store( db_query.DropStore( store="my_store", schema="analytics", error_if_not_exists=True ) ) # response contains deleted_count if __name__ == "__main__": asyncio.run(drop_store()) ``` ```typescript import { createDbClient } from "ahnlich-client-node"; import { DropStore } from "ahnlich-client-node/grpc/db/query_pb"; async function dropStore() { const client = createDbClient("127.0.0.1:1369"); await client.dropStore( new DropStore({ store: "my_store", schema: "analytics", errorIfNotExists: true, }) ); console.log("Store dropped successfully"); } dropStore(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.NewClient(ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } // -------------------- Drop Store -------------------- func (c *ExampleDBClient) exampleDropStore() error { _, err := c.client.DropStore(c.ctx, &dbquery.DropStore{ Store: "my_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted ErrorIfNotExists: true, // Return error if store doesn't exist }) if err != nil { return err } fmt.Println("Dropped store: my_store") return nil } // -------------------- Main -------------------- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleDropStore(); err != nil { log.Fatalf("DropStore failed: %v", err) } } ``` ```rust use ahnlich_types::db::query::DropStore; use ahnlich_client_rs::db::DbClient; #[tokio::main] async fn main() -> Result<(), Box> { // Connect to the DB server let client = DbClient::new("http://127.0.0.1:1369".to_string()).await?; // Prepare drop store parameters let drop_params = DropStore { store: "MyStore".to_string(), schema: Some("analytics".to_string()), error_if_not_exists: true, // Required field }; // Execute the drop store request let result = client.drop_store(drop_params, None).await?; println!("Deleted count: {}", result.deleted_count); Ok(()) } ``` ```bash DROPSTORE my_store IF EXISTS ``` ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query async def drop_store(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.drop_store( ai_query.DropStore( store="my_store", schema="analytics", error_if_not_exists=True ) ) print(response) # Del(deleted_count=1) if __name__ == "__main__": asyncio.run(drop_store()) ``` ```typescript import { createAiClient } from "ahnlich-client-node"; import { DropStore } from "ahnlich-client-node/grpc/ai/query_pb"; async function dropStore() { const client = createAiClient("127.0.0.1:1370"); await client.dropStore( new DropStore({ store: "ai_store", schema: "analytics", errorIfNotExists: true, }) ); console.log("AI store dropped"); } dropStore(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" ) const AIAddr = "127.0.0.1:1370" // ExampleAIClient wraps the connection + AIService client func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } // NewAIClient creates and connects the AI client func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock(), ) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- DropStore ---- func (c *ExampleAIClient) exampleDropStoreAI() error { _, err := c.client.DropStore(c.ctx, &aiquery.DropStore{ Store: "ai_store01", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted ErrorIfNotExists: true, }) if err != nil { return err } fmt.Println(" Successfully dropped store: ai_store") return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf(" Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleDropStoreAI(); err != nil { log.Fatalf(" DropStore failed: %v", err) } } ``` ```rust use ahnlich_types::ai::query::DropStore; use ahnlich_client_rs::ai::AiClient; #[tokio::main] async fn main() -> Result<(), Box> { let client = AiClient::new("http://127.0.0.1:1370".to_string()).await?; let drop_params = DropStore { store: "Deven Kicks".to_string(), schema: Some("analytics".to_string()), error_if_not_exists: true, }; let result = client.drop_store(drop_params, None).await?; println!("Deleted count: {}", result.deleted_count); Ok(()) } ``` ```bash DROPSTORE my_store IF EXISTS ``` ## Response A confirmation response with the deleted count. ## Related - [Create a store](/docs/stores/create-store) - [Delete by key](/docs/stores/delete-key) --- # FILE: stores/get-by-predicate.mdx --- title: Get by predicate sidebar_label: Get by predicate --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Get by predicate Retrieve entries by **metadata conditions** instead of by vector. > Every operation works on both **Vector DB stores** (you supply raw vectors) and **AI stores** (you supply text or images and [Ahnlich AI](/docs/components/ahnlich-ai) generates the embeddings for you). Use the **Vector DB / AI** switch in the sample below. ## Parameters | Parameter | Type | Description | | --- | --- | --- | | `store` | string | Name of the store to read from. | | `condition` | predicate | A [metadata](/docs/concepts/metadata) predicate, e.g. `equals`, `greater_than`. Combine with `and`/`or`. | | `schema` | string · optional | Schema to target. Defaults to `public`. | > All requests accept an optional `schema` field. When omitted, the server uses the `public` schema. ## Behavior - Evaluates the predicate against metadata fields and returns **every** entry that satisfies it. - Create a [predicate index](/docs/stores/create-predicate-index) on the queried fields to keep this fast. ## Sample query ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc import predicates, metadata async def get_predicate(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) condition = predicates.PredicateCondition( value=predicates.Predicate( equals=predicates.Equals( key="label", value=metadata.MetadataValue(raw_string="sorcerer") ) ) ) response = await client.get_pred( db_query.GetPred( store="my_store", schema="analytics", condition=condition ) ) # response.entries contains matching items print(response) if __name__ == "__main__": asyncio.run(get_predicate()) ``` ```typescript import { createDbClient } from "ahnlich-client-node"; import { GetPred } from "ahnlich-client-node/grpc/db/query_pb"; import { PredicateCondition, Predicate, Equals } from "ahnlich-client-node/grpc/predicate_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; async function getByPredicate() { const client = createDbClient("127.0.0.1:1369"); const response = await client.getPred( new GetPred({ store: "my_store", schema: "analytics", condition: new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "label", value: new MetadataValue({ value: { case: "rawString", value: "A" } }), }), }, }), }, }), }) ); console.log(`Found ${response.entries.length} entries with label='A'`); } getByPredicate(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" predicates "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/predicates" ) const ServerAddr = "127.0.0.1:1369" // ExampleDBClient holds the gRPC connection, client, and context. func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } // NewDBClient connects to the Ahnlich DB server. func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.NewClient(ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } // Close closes the gRPC connection. func (c *ExampleDBClient) Close() error { return c.conn.Close() } // -------------------- Get By Predicate -------------------- func (c *ExampleDBClient) exampleGetByPredicate() error { // Build a PredicateCondition for "label == A" cond := &predicates.PredicateCondition{ Kind: &predicates.PredicateCondition_Value{ Value: &predicates.Predicate{ Kind: &predicates.Predicate_Equals{ Equals: &predicates.Equals{ Key: "label", Value: &metadata.MetadataValue{ Value: &metadata.MetadataValue_RawString{RawString: "A"}, }, }, }, }, }, } // Call GetPred with the condition resp, err := c.client.GetPred(c.ctx, &dbquery.GetPred{ Store: "my_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Condition: cond, }) if err != nil { return err } fmt.Println("GetByPredicate Results:", resp.Entries) return nil } // -------------------- Main -------------------- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleGetByPredicate(); err != nil { log.Fatalf("GetByPredicate failed: %v", err) } } ``` ```rust use ahnlich_client_rs::db::DbClient; use ahnlich_types::{ db::query::GetPred, metadata::{MetadataValue, metadata_value::Value}, predicates::{ Predicate, PredicateCondition, predicate::Kind as PredicateKind, predicate_condition::Kind as PredicateConditionKind, Equals, }, }; use tokio; #[tokio::main] async fn main() -> Result<(), Box> { let addr = "http://127.0.0.1:1369"; let client = DbClient::new(addr.to_string()).await?; let condition = PredicateCondition { kind: Some(PredicateConditionKind::Value(Predicate { kind: Some(PredicateKind::Equals(Equals { key: "label".into(), value: Some(MetadataValue { value: Some(Value::RawString("A".into())), }), })), })), }; let get_pred_params = GetPred { store: "my_store".to_string(), schema: Some("analytics".to_string()), condition: Some(condition), }; let result = client.get_pred(get_pred_params, None).await?; println!("Fetched rows: {:#?}", result); Ok(()) } ``` ```bash GETPRED (author = Alice) IN my_store ``` ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc import predicates, metadata async def get_by_predicate(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) condition = predicates.PredicateCondition( value=predicates.Predicate( equals=predicates.Equals( key="brand", value=metadata.MetadataValue(raw_string="Nike") ) ) ) response = await client.get_pred( ai_query.GetPred( store="my_store", schema="analytics", condition=condition ) ) print(response) #Get(entries=[GetEntry(key=StoreInput(raw_string='Jordan One'), value=StoreValue(value={'brand': MetadataValue(raw_string='Nike')}))]) if __name__ == "__main__": asyncio.run(get_by_predicate()) ``` ```typescript import { createAiClient } from "ahnlich-client-node"; import { GetPred } from "ahnlich-client-node/grpc/ai/query_pb"; import { PredicateCondition, Predicate, Equals } from "ahnlich-client-node/grpc/predicate_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; async function getByPredicate() { const client = createAiClient("127.0.0.1:1370"); const response = await client.getPred( new GetPred({ store: "ai_store", schema: "analytics", condition: new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "brand", value: new MetadataValue({ value: { case: "rawString", value: "Nike" } }), }), }, }), }, }), }) ); console.log(`Found ${response.entries.length} Nike products`); } getByPredicate(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" predicates "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/predicates" ) const AIAddr = "127.0.0.1:1370" func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- GetByPredicate standalone ---- func (c *ExampleAIClient) exampleGetByPredicateAI() error { cond := &predicates.PredicateCondition{ Kind: &predicates.PredicateCondition_Value{ Value: &predicates.Predicate{ Kind: &predicates.Predicate_Equals{ Equals: &predicates.Equals{ Key: "f", Value: &metadata.MetadataValue{ Value: &metadata.MetadataValue_RawString{RawString: "v"}, }, }, }, }, }, } resp, err := c.client.GetPred(c.ctx, &aiquery.GetPred{ Store: "ai_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Condition: cond, }) if err != nil { return err } fmt.Println(" AI GetByPredicate Response:", resp.Entries) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf(" Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleGetByPredicateAI(); err != nil { log.Fatalf(" GetByPredicate failed: %v", err) } } ``` ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_client_rs::error::AhnlichError; use ahnlich_client_rs::prelude::StoreName; use ahnlich_types::{ ai::query::GetPred, predicates::{Predicate, PredicateCondition, predicate::Kind as PredicateKind, predicate_condition::Kind as PredicateConditionKind, Equals}, metadata::{MetadataValue, metadata_value::Value as MValue}, }; use tokio; #[tokio::main] async fn main() -> Result<(), AhnlichError> { // Connect to AI server let ai_client = AiClient::new("http://127.0.0.1:1370".to_string()) .await .expect("Failed to connect AI client"); // Define store and metadata condition let store_name = StoreName { value: "Deven Kicks".to_string() }; let matching_metadatakey = "Brand".to_string(); let matching_metadatavalue = MetadataValue { value: Some(MValue::RawString("Nike".into())) }; // Build predicate condition let condition = PredicateCondition { kind: Some(PredicateConditionKind::Value(Predicate { kind: Some(PredicateKind::Equals(Equals { key: matching_metadatakey.clone(), value: Some(matching_metadatavalue.clone()), })), })), }; let get_pred_params = GetPred { store: store_name.value.clone(), schema: Some("analytics".to_string()), condition: Some(condition), }; // Call get_pred let response = ai_client.get_pred(get_pred_params, None).await?; println!("Matching entries:"); for entry in response.entries { println!("{:?}", entry); } Ok(()) } ``` ```bash GETPRED (author = Alice) IN my_store ``` ## Response A list of all matching entries (vector + metadata). ## Related - [Predicates](/docs/components/predicates) - [Create predicate index](/docs/stores/create-predicate-index) - [Similarity search](/docs/stores/get-simn) --- # FILE: stores/get-key.mdx --- title: Get by key sidebar_label: Get by key --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Get by key Retrieve entries by an exact vector-key match. > Every operation works on both **Vector DB stores** (you supply raw vectors) and **AI stores** (you supply text or images and [Ahnlich AI](/docs/components/ahnlich-ai) generates the embeddings for you). Use the **Vector DB / AI** switch in the sample below. ## Parameters | Parameter | Type | Description | | --- | --- | --- | | `store` | string | Name of the store to read from. | | `keys` | list of vectors | The exact vector keys to look up. | | `schema` | string · optional | Schema to target. Defaults to `public`. | > All requests accept an optional `schema` field. When omitted, the server uses the `public` schema. ## Behavior - Matches the stored entry whose key vector is **exactly** equal to the one supplied. - Returns nothing for keys that aren't present — it does not do nearest-neighbour matching (use [Similarity search](/docs/stores/get-simn) for that). ## Sample query ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc import keyval from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc.db.server import Get async def get_key(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) lookup_key = keyval.StoreKey(key=[1.0, 2.0, 3.0, 4.0]) # Your lookup vector response = await client.get_key( db_query.GetKey( store="my_store", schema="analytics", keys=[lookup_key] ) ) # response.entries contains matching (key, value) pairs if __name__ == "__main__": asyncio.run(get_key()) ``` ```typescript import { createDbClient } from "ahnlich-client-node"; import { GetKey } from "ahnlich-client-node/grpc/db/query_pb"; import { StoreKey } from "ahnlich-client-node/grpc/keyval_pb"; async function getKey() { const client = createDbClient("127.0.0.1:1369"); const response = await client.getKey( new GetKey({ store: "my_store", schema: "analytics", keys: [new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] })], }) ); console.log(response.entries); // Iterate over results for (const entry of response.entries) { console.log(`Key: ${entry.key?.key}`); console.log(`Value: ${JSON.stringify(entry.value?.value)}`); } } getKey(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" ) const ServerAddr = "127.0.0.1:1369" // ExampleDBClient holds the gRPC connection, client, and context. func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } // NewDBClient connects to the Ahnlich DB server. func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.NewClient( ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), ) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } // Close closes the gRPC connection. func (c *ExampleDBClient) Close() error { return c.conn.Close() } // -------------------- GetKey -------------------- func (c *ExampleDBClient) exampleGetKey() error { resp, err := c.client.GetKey(c.ctx, &dbquery.GetKey{ Store: "my_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Keys: []*keyval.StoreKey{{Key: []float32{1, 2, 3, 4}}}, }) if err != nil { return err } fmt.Println("GetKey Results:") for _, entry := range resp.Entries { fmt.Println(" - Key:", entry.Key.Key, "Value:", entry.Value.Value) } return nil } // -------------------- Main -------------------- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleGetKey(); err != nil { log.Fatalf("GetKey failed: %v", err) } } ``` ```rust use ahnlich_client_rs::db::DbClient; use ahnlich_types::{ db::query::GetKey, keyval::StoreKey, }; use tokio; #[tokio::main] async fn main() -> Result<(), Box> { // point to your DB server (default is 1369, adjust if needed) let addr = "http://127.0.0.1:1369"; let client = DbClient::new(addr.to_string()).await?; // example: look up a key from store "my_store" let get_key_params = GetKey { store: "my_store".to_string(), schema: Some("analytics".to_string()), keys: vec![ StoreKey { key: vec![1.2, 1.3, 1.4], // must match a previously Set key }, ], }; match client.get_key(get_key_params, None).await { Ok(result) => { println!("Fetched: {:#?}", result); } Err(e) => { eprintln!("Error fetching key: {:?}", e); } } Ok(()) } ``` ```bash GETKEY ([1.0, 2.0, 3.0, 4.0]) IN my_store ``` ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc import keyval async def get_key(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.get_key( ai_query.GetKey( store="my_store", schema="analytics", keys=[ keyval.StoreInput(raw_string="Adidas Yeezy"), keyval.StoreInput(raw_string="Nike Air Jordans"), ] ) ) # Response contains entries for each found key for entry in response.entries: print(f"Key: {entry.key}") print(f"Value: {entry.value}") if __name__ == "__main__": asyncio.run(get_key()) ``` ```typescript import { createAiClient } from "ahnlich-client-node"; import { GetKey } from "ahnlich-client-node/grpc/ai/query_pb"; import { StoreInput } from "ahnlich-client-node/grpc/keyval_pb"; async function getKey() { const client = createAiClient("127.0.0.1:1370"); // Create the input to look up (using oneof discriminated union) const storeInput = new StoreInput({ value: { case: "rawString", value: "Your search text" } }); const response = await client.getKey( new GetKey({ store: "my_store", schema: "analytics", keys: [storeInput] }) ); // response.entries contains matching (key, value) pairs for (const entry of response.entries) { console.log("Found entry:", entry); } } getKey(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" ) const AIAddr = "127.0.0.1:1370" func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- GetKey Example ---- func (c *ExampleAIClient) exampleGetKey() error { resp, err := c.client.GetKey(c.ctx, &aiquery.GetKey{ Store: "ai_store01", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Keys: []*keyval.StoreInput{ {Value: &keyval.StoreInput_RawString{RawString: "Adidas Yeezy"}}, {Value: &keyval.StoreInput_RawString{RawString: "Nike Air Jordans"}}, }, }) if err != nil { return err } fmt.Println("GetKey Results:") for _, entry := range resp.Entries { fmt.Printf(" Key: %v\n", entry.Key) fmt.Printf(" Value: %v\n", entry.Value) } return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf("Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleGetKey(); err != nil { log.Fatalf("GetKey failed: %v", err) } } ``` ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_client_rs::error::AhnlichError; use ahnlich_types::ai::query::GetKey; use ahnlich_types::keyval::store_input::Value; use ahnlich_types::keyval::StoreInput; #[tokio::main] async fn main() -> Result<(), AhnlichError> { let addr = "127.0.0.1:1370"; let client = AiClient::new(addr.to_string()).await?; let keys = vec![ StoreInput { value: Some(Value::RawString("Adidas Yeezy".to_string())) }, StoreInput { value: Some(Value::RawString("Nike Air Jordans".to_string())) }, ]; let params = GetKey { store: "Main0".to_string(), schema: Some("analytics".to_string()), keys, // directly pass the Vec }; let result = client.get_key(params, None).await?; for entry in result.entries { if let Some(k) = entry.key { println!("Key retrieved: {:?}", k.value); } } Ok(()) } ``` ```bash GETKEY ([This is a sentence]) IN my_store ``` ## Response The matching entries (vector + metadata). Empty if no key matches. ## Related - [Similarity search](/docs/stores/get-simn) - [Get by predicate](/docs/stores/get-by-predicate) --- # FILE: stores/get-simn.mdx --- title: Similarity search sidebar_label: Similarity search --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Similarity search Return the `closest_n` entries most similar to a query vector, ranked by a similarity metric. This is the core vector-search operation. > Every operation works on both **Vector DB stores** (you supply raw vectors) and **AI stores** (you supply text or images and [Ahnlich AI](/docs/components/ahnlich-ai) generates the embeddings for you). Use the **Vector DB / AI** switch in the sample below. ## Parameters | Parameter | Type | Description | | --- | --- | --- | | `store` | string | Name of the store to search. | | `search_input` | vector | The query vector (`StoreKey`). Must match the store's dimension. | | `closest_n` | int (> 0) | How many results to return. | | `algorithm` | enum | Similarity metric: `CosineSimilarity`, `EuclideanDistance`, or `DotProductSimilarity`. | | `condition` | predicate · optional | Restrict which vectors are considered. `None` searches all. See [Predicates](/docs/components/predicates). | | `schema` | string · optional | Schema to target. Defaults to `public`. | > All requests accept an optional `schema` field. When omitted, the server uses the `public` schema. ## Behavior - The server compares the query vector against stored vectors using the chosen metric. - An optional `condition` narrows candidates by [metadata](/docs/concepts/metadata) before ranking. - Results are ordered from most to least similar. ## Sample query ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc import keyval, predicates from ahnlich_client_py.grpc.algorithm.algorithms import Algorithm async def get_simn(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) search_key = keyval.StoreKey(key=[1.0, 2.0, 3.0, 4.0]) response = await client.get_sim_n( db_query.GetSimN( store="my_store", schema="analytics", search_input=search_key, closest_n=3, algorithm=Algorithm.CosineSimilarity, condition=None # Optional: filter results using predicates ) ) print(response.entries) # [(key, value, score), ...] if __name__ == "__main__": asyncio.run(get_simn()) ``` ```typescript import { createDbClient } from "ahnlich-client-node"; import { GetSimN } from "ahnlich-client-node/grpc/db/query_pb"; import { StoreKey } from "ahnlich-client-node/grpc/keyval_pb"; import { Algorithm } from "ahnlich-client-node/grpc/algorithm/algorithm_pb"; async function getSimN() { const client = createDbClient("127.0.0.1:1369"); const response = await client.getSimN( new GetSimN({ store: "my_store", schema: "analytics", searchInput: new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] }), closestN: 3, algorithm: Algorithm.COSINE_SIMILARITY, }) ); console.log(response.entries); // Iterate over results for (const entry of response.entries) { console.log(`Key: ${entry.key?.key}`); console.log(`Similarity: ${entry.similarity}`); console.log(`Value: ${JSON.stringify(entry.value?.value)}`); } } getSimN(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" algorithms "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/algorithm/algorithms" predicates "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/predicates" ) const ServerAddr = "127.0.0.1:1369" // ExampleDBClient holds the gRPC connection, client, and context. func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } // NewDBClient connects to the Ahnlich DB server. func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.NewClient( ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), ) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } // Close closes the gRPC connection. func (c *ExampleDBClient) Close() error { return c.conn.Close() } // -------------------- GetSimN -------------------- func (c *ExampleDBClient) exampleGetSimN() error { resp, err := c.client.GetSimN(c.ctx, &dbquery.GetSimN{ Store: "my_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted SearchInput: &keyval.StoreKey{Key: []float32{1, 2, 3, 4}}, ClosestN: 3, Algorithm: algorithms.Algorithm_CosineSimilarity, Condition: nil, // Optional: filter results using predicates }) if err != nil { return err } fmt.Println("GetSimN Results:") for _, entry := range resp.Entries { fmt.Println(" - Key:", entry.Key.Key, "Value:", entry.Value.Value) } return nil } // -------------------- Main -------------------- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleGetSimN(); err != nil { log.Fatalf("GetSimN failed: %v", err) } } ``` ```rust use ahnlich_client_rs::db::DbClient; use ahnlich_types::{ algorithm::algorithms::Algorithm, db::query::GetSimN, keyval::StoreKey, }; use tokio; #[tokio::main] async fn main() -> Result<(), Box> { // connect to server let addr = "http://127.0.0.1:1369"; // adjust to your server address let db_client = DbClient::new(addr.to_string()).await?; // prepare parameters let params = GetSimN { store: "my_store".to_string(), schema: Some("analytics".to_string()), search_input: Some(StoreKey { key: vec![1.0, 2.0, 3.0, 4.0] }), closest_n: 2, algorithm: Algorithm::EuclideanDistance as i32, condition: None, }; // call get_sim_n let response = db_client.get_sim_n(params, None).await?; println!("Response: {:?}", response); Ok(()) } ``` ```bash GETSIMN 3 WITH ([1.0, 2.0, 3.0, 4.0]) USING cosinesimilarity IN my_store ``` ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc import keyval from ahnlich_client_py.grpc.algorithm import algorithms from ahnlich_client_py.grpc.ai.preprocess import PreprocessAction async def get_sim_n(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.get_sim_n( ai_query.GetSimN( store="my_store", schema="analytics", search_input=keyval.StoreInput(raw_string="Jordan"), condition=None, # Optional predicate condition closest_n=3, algorithm=algorithms.Algorithm.CosineSimilarity, preprocess_action=PreprocessAction.ModelPreprocessing, # Apply model's preprocessing execution_provider=None, # Optional execution provider model_params={} # Optional: runtime model parameters (e.g., {"confidence_threshold": "0.9"} for face detection) ) ) # Response contains entries with similarity scores for entry in response.entries: print(f"Key: {entry.key.raw_string}") print(f"Score: {entry.similarity}") print(f"Value: {entry.value}") # Key: Jordan One # Score: Similarity(value=0.858908474445343) # Value: StoreValue(value={'brand': MetadataValue(raw_string='Nike')}) # Key: Yeezey # Score: Similarity(value=0.21911849081516266) # Value: StoreValue(value={'brand': MetadataValue(raw_string='Adidas')}) if __name__ == "__main__": asyncio.run(get_sim_n()) ``` ```typescript import { createAiClient } from "ahnlich-client-node"; import { GetSimN } from "ahnlich-client-node/grpc/ai/query_pb"; import { StoreInput } from "ahnlich-client-node/grpc/keyval_pb"; import { Algorithm } from "ahnlich-client-node/grpc/algorithm/algorithm_pb"; async function getSimN() { const client = createAiClient("127.0.0.1:1370"); const response = await client.getSimN( new GetSimN({ store: "ai_store", schema: "analytics", searchInput: new StoreInput({ value: { case: "rawString", value: "Jordan" } }), closestN: 3, algorithm: Algorithm.COSINE_SIMILARITY, }) ); console.log(response.entries); for (const entry of response.entries) { console.log(`Input: ${entry.input?.value}`); console.log(`Similarity: ${entry.similarity}`); console.log(`Metadata: ${JSON.stringify(entry.value?.value)}`); } } getSimN(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" algorithms "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/algorithm/algorithms" preprocess "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/preprocess" predicates "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/predicates" ) const AIAddr = "127.0.0.1:1370" func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } // Helper to unwrap Key (StoreInput) func unwrapKey(k *keyval.StoreInput) string { if k == nil { return "" } switch v := k.Value.(type) { case *keyval.StoreInput_RawString: return v.RawString default: return fmt.Sprintf("%v", v) } } // Helper to unwrap Value (StoreValue) func unwrapValue(v *keyval.StoreValue) map[string]string { result := make(map[string]string) if v == nil { return result } for k, val := range v.Value { switch mv := val.Value.(type) { case *metadata.MetadataValue_RawString: result[k] = mv.RawString default: result[k] = fmt.Sprintf("%v", mv) } } return result } // ---- GetSimN ---- func (c *ExampleAIClient) exampleGetSimNAI() error { resp, err := c.client.GetSimN(c.ctx, &aiquery.GetSimN{ Store: "ai_store01", // must already exist and have data Schema: stringPtr("analytics"), // Optional: defaults to public when omitted SearchInput: &keyval.StoreInput{Value: &keyval.StoreInput_RawString{RawString: "X"}}, Condition: nil, // Optional: filter results using predicates ClosestN: 3, Algorithm: algorithms.Algorithm_CosineSimilarity, PreprocessAction: preprocess.PreprocessAction_ModelPreprocessing, // Apply model's preprocessing ExecutionProvider: nil, // Optional: e.g., ExecutionProvider_CUDA for GPU acceleration }) if err != nil { return err } fmt.Println(" AI GetSimN Response:") for i, entry := range resp.Entries { fmt.Printf(" #%d Key=%s Value=%v\n", i+1, unwrapKey(entry.Key), unwrapValue(entry.Value)) } return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf(" Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleGetSimNAI(); err != nil { log.Fatalf(" GetSimN failed: %v", err) } } ``` ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_client_rs::error::AhnlichError; use ahnlich_types::ai::preprocess::PreprocessAction; use ahnlich_types::ai::query::GetSimN; use ahnlich_types::algorithm::algorithms::Algorithm; use ahnlich_types::keyval::StoreInput; use ahnlich_types::keyval::store_input::Value; use std::collections::HashMap; #[tokio::main] async fn main() -> Result<(), AhnlichError> { // Connect to AI server let addr = "127.0.0.1:1370"; let client = AiClient::new(addr.to_string()).await?; // Prepare the search input let search_input = StoreInput { value: Some(Value::RawString("example query".into())), }; // Construct GetSimN parameters let params = GetSimN { store: "Main0".to_string(), schema: Some("analytics".to_string()), search_input: Some(search_input), closest_n: 3, // number of similar entries to retrieve algorithm: Algorithm::CosineSimilarity as i32, execution_provider: None, preprocess_action: PreprocessAction::NoPreprocessing as i32, condition: None, model_params: HashMap::new(), }; // Run the GetSimN command let res = client.get_sim_n(params, None).await?; println!("GetSimN result: {:?}", res); Ok(()) } ``` ```bash GETSIMN 3 WITH [This is a sentence] USING cosinesimilarity IN my_store ``` ## Response A ranked list of entries, each with its `key` (vector), `value` (metadata), and similarity `score`. ## Related - [Get by key](/docs/stores/get-key) - [Get by predicate](/docs/stores/get-by-predicate) - [Predicates](/docs/components/predicates) --- # FILE: stores/get-store.mdx --- title: Get a store sidebar_label: Get a store --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Get a store Return detailed information about a single [store](/docs/stores) by name. > Every operation works on both **Vector DB stores** (you supply raw vectors) and **AI stores** (you supply text or images and [Ahnlich AI](/docs/components/ahnlich-ai) generates the embeddings for you). Use the **Vector DB / AI** switch in the sample below. ## Parameters | Parameter | Type | Description | | --- | --- | --- | | `store` | string | Name of the store to inspect. | | `schema` | string · optional | Schema to target. Defaults to `public`. | > All requests accept an optional `schema` field. When omitted, the server uses the `public` schema. ## Behavior - Retrieves [metadata](/docs/concepts/metadata) and configuration for the named store. ## Sample query ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query async def get_store_info(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) response = await client.get_store( db_query.GetStore(store="my_store", schema="analytics") ) print(f"Store name: {response.name}") print(f"Number of entries: {response.len}") print(f"Size in bytes: {response.size_in_bytes}") print(f"Dimension: {response.dimension}") print(f"Predicate indices: {response.predicate_indices}") print(f"Non-linear indices: {response.non_linear_indices}") if __name__ == "__main__": asyncio.run(get_store_info()) ``` ```typescript import { createDbClient } from "ahnlich-client-node"; import { GetStore } from "ahnlich-client-node/grpc/db/query_pb"; async function getStore() { const client = createDbClient("127.0.0.1:1369"); const response = await client.getStore( new GetStore({ store: "my_store", schema: "analytics" }) ); console.log(response.name); // Store name console.log(response.dimension); // Vector dimension console.log(response.predicateIndices); // Indexed predicate keys console.log(response.nonLinearIndices); // Non-linear algorithm indices console.log(response.len); // Number of entries console.log(response.sizeInBytes); // Size on disk } getStore(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.NewClient(ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } func (c *ExampleDBClient) exampleGetStore() error { resp, err := c.client.GetStore(c.ctx, &dbquery.GetStore{ Store: "my_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted }) if err != nil { return err } fmt.Printf("Store name: %s\n", resp.Name) fmt.Printf("Number of entries: %d\n", resp.Len) fmt.Printf("Size in bytes: %d\n", resp.SizeInBytes) fmt.Printf("Dimension: %d\n", resp.Dimension) fmt.Printf("Predicate indices: %v\n", resp.PredicateIndices) fmt.Printf("Non-linear indices: %v\n", resp.NonLinearIndices) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleGetStore(); err != nil { log.Fatalf("GetStore failed: %v", err) } } ``` ```rust use ahnlich_client_rs::db::DbClient; #[tokio::main] async fn main() -> Result<(), Box> { let db_client = DbClient::new("127.0.0.1:1369".to_string()).await?; let tracing_id: Option = None; let store_info = db_client .get_store_with_schema( "my_store".to_string(), Some("analytics".to_string()), tracing_id, ) .await?; println!("Store name: {}", store_info.name); println!("Number of entries: {}", store_info.len); println!("Size in bytes: {}", store_info.size_in_bytes); println!("Dimension: {}", store_info.dimension); println!("Predicate indices: {:?}", store_info.predicate_indices); println!("Non-linear indices: {:?}", store_info.non_linear_indices); Ok(()) } ``` This operation isn't exposed as a standalone CLI command — use one of the client libraries above. ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query async def get_ai_store_info(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.get_store( ai_query.GetStore(store="ai_store", schema="analytics") ) print(f"Store name: {response.name}") print(f"Query model: {response.query_model}") print(f"Index model: {response.index_model}") print(f"Embedding size: {response.embedding_size}") print(f"Dimension: {response.dimension}") print(f"Predicate indices: {response.predicate_indices}") if response.db_info: print(f"DB store size: {response.db_info.size_in_bytes} bytes") if __name__ == "__main__": asyncio.run(get_ai_store_info()) ``` ```typescript import { createAiClient } from "ahnlich-client-node"; import { GetStore } from "ahnlich-client-node/grpc/ai/query_pb"; async function getStore() { const client = createAiClient("127.0.0.1:1370"); const response = await client.getStore( new GetStore({ store: "ai_store", schema: "analytics" }) ); console.log(response.name); // Store name console.log(response.queryModel); // AI model used for querying console.log(response.indexModel); // AI model used for indexing console.log(response.embeddingSize); // Number of stored embeddings console.log(response.dimension); // Vector dimension console.log(response.predicateIndices); // Indexed predicate keys console.log(response.dbInfo); // Optional DB store info } getStore(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" ) const AIAddr = "127.0.0.1:1370" func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } func (c *ExampleAIClient) exampleGetStore() error { resp, err := c.client.GetStore(c.ctx, &aiquery.GetStore{ Store: "ai_store", Schema: stringPtr("analytics"), // Optional: defaults to public when omitted }) if err != nil { return err } fmt.Printf("Store name: %s\n", resp.Name) fmt.Printf("Query model: %v\n", resp.QueryModel) fmt.Printf("Index model: %v\n", resp.IndexModel) fmt.Printf("Embedding size: %d\n", resp.EmbeddingSize) fmt.Printf("Dimension: %d\n", resp.Dimension) fmt.Printf("Predicate indices: %v\n", resp.PredicateIndices) if resp.DbInfo != nil { fmt.Printf("DB store size: %d bytes\n", resp.DbInfo.SizeInBytes) } return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf("Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleGetStore(); err != nil { log.Fatalf("GetStore failed: %v", err) } } ``` ```rust use ahnlich_client_rs::ai::AiClient; #[tokio::main] async fn main() -> Result<(), Box> { let ai_client = AiClient::new("127.0.0.1:1370".to_string()).await?; let tracing_id: Option = None; let store_info = ai_client .get_store_with_schema( "ai_store".to_string(), Some("analytics".to_string()), tracing_id, ) .await?; println!("Store name: {}", store_info.name); println!("Query model: {:?}", store_info.query_model); println!("Index model: {:?}", store_info.index_model); println!("Embedding size: {}", store_info.embedding_size); println!("Dimension: {}", store_info.dimension); println!("Predicate indices: {:?}", store_info.predicate_indices); if let Some(db_info) = &store_info.db_info { println!("DB store size: {} bytes", db_info.size_in_bytes); } Ok(()) } ``` This operation isn't exposed as a standalone CLI command — use one of the client libraries above. ## Response Store information including name, size, dimension, and configured indices. ## Related - [List stores](/docs/stores/list-stores) - [Create a store](/docs/stores/create-store) --- # FILE: stores/info-server.mdx --- title: Server info sidebar_label: Server info --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Server info Retrieve [metadata](/docs/concepts/metadata) about the running server, including its binary **version** and **type** (DB, AI, or Hybrid). > Every operation works on both **Vector DB stores** (you supply raw vectors) and **AI stores** (you supply text or images and [Ahnlich AI](/docs/components/ahnlich-ai) generates the embeddings for you). Use the **Vector DB / AI** switch in the sample below. ## Parameters This request takes no arguments. ## Behavior - The server responds with version and type metadata. - Use it to validate you're connected to the correct server role — useful for environment checks, feature gating, and diagnostics. ## Sample query ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc.server_types import ServerType async def info_server(): """Test server version""" async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) response = await client.info_server(db_query.InfoServer()) # response contains server version and type print(f"Server version: {response.info.version}") if __name__ == "__main__": asyncio.run(info_server()) ``` ```typescript import { createDbClient } from "ahnlich-client-node"; import { InfoServer } from "ahnlich-client-node/grpc/db/query_pb"; async function infoServer() { const client = createDbClient("127.0.0.1:1369"); const response = await client.infoServer(new InfoServer()); console.log(response.info?.version); // Server version console.log(response.info?.address); // Server address console.log(response.info?.numStores); // Number of stores } infoServer(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" ) const ServerAddr = "127.0.0.1:1369" type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.NewClient(ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } // InfoServer example func (c *ExampleDBClient) exampleInfoServer() error { resp, err := c.client.InfoServer(c.ctx, &dbquery.InfoServer{}) if err != nil { return err } fmt.Println("InfoServer:", resp) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleInfoServer(); err != nil { log.Fatalf("InfoServer failed: %v", err) } } ``` ```rust use ahnlich_client_rs::db::DbClient; #[tokio::main] async fn main() -> Result<(), Box> { // Connect to your ahnlich-db server let db_client = DbClient::new("127.0.0.1:1369".to_string()).await?; let tracing_id: Option = None; // Call info_server and print the result let info = db_client.info_server(tracing_id).await?; println!("Server info: {:?}", info); Ok(()) } ``` ```bash INFOSERVER ``` ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query async def info_server(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.info_server(ai_query.InfoServer()) print(response) #InfoServer(info=ServerInfo(address='Ok(0.0.0.0:1370)', version='0.1.0', limit=10073741824, remaining=10067931251)) if __name__ == "__main__": asyncio.run(info_server()) ``` ```typescript import { createAiClient } from "ahnlich-client-node"; import { InfoServer } from "ahnlich-client-node/grpc/ai/query_pb"; async function infoServer() { const client = createAiClient("127.0.0.1:1370"); const response = await client.infoServer(new InfoServer()); console.log(response.info?.version); } infoServer(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" ) const AIAddr = "127.0.0.1:1370" // ExampleAIClient holds the gRPC connection and AI client. type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } // NewAIClient connects to the AI server and returns a client. func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } // Close closes the gRPC connection. func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- InfoServer Example ---- // Retrieve server information such as address, version, limits. func (c *ExampleAIClient) exampleInfoServerAI() error { resp, err := c.client.InfoServer(c.ctx, &aiquery.InfoServer{}) if err != nil { return err } fmt.Println(" AI InfoServer:", resp) return nil } // ---- MAIN ---- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf("Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleInfoServerAI(); err != nil { log.Fatalf("InfoServer failed: %v", err) } } ``` ```rust use ahnlich_client_rs::ai::AiClient; // <-- note the `ai::` path use ahnlich_client_rs::error::AhnlichError; use ahnlich_types::shared::info::ServerInfo; #[tokio::main] async fn main() -> Result<(), AhnlichError> { let addr = "127.0.0.1:1370"; let client = AiClient::new(addr.to_string()).await?; // Direct info_server call let server_info: ServerInfo = client.info_server(None).await?; println!("Server Info: {:?}", server_info); // Using pipeline let mut pipeline = client.pipeline(None); pipeline.info_server(); let pipeline_result = pipeline.exec().await?; println!("Pipeline Server Info: {:?}", pipeline_result); Ok(()) } ``` ```bash INFOSERVER ``` ## Response Server metadata including version and type. ## Related - [Ping the server](/docs/stores/ping) - [List connected clients](/docs/stores/list-connected-clients) --- # FILE: stores/list-connected-clients.mdx --- title: List connected clients sidebar_label: List connected clients --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # List connected clients List every client currently connected to the server. > Every operation works on both **Vector DB stores** (you supply raw vectors) and **AI stores** (you supply text or images and [Ahnlich AI](/docs/components/ahnlich-ai) generates the embeddings for you). Use the **Vector DB / AI** switch in the sample below. ## Parameters This request takes no arguments. ## Behavior - The server returns information about all active client connections, including their addresses. ## Sample query ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query async def list_connected_clients(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) response = await client.list_clients(db_query.ListClients()) # response.clients contains information about connected clients for client_info in response.clients: print(f"Connected client: {client_info.address}") if __name__ == "__main__": asyncio.run(list_connected_clients()) ``` ```typescript import { createDbClient } from "ahnlich-client-node"; import { ListClients } from "ahnlich-client-node/grpc/db/query_pb"; async function listConnectedClients() { const client = createDbClient("127.0.0.1:1369"); const response = await client.listClients(new ListClients()); console.log(response.clients); // Iterate over connected clients for (const clientInfo of response.clients) { console.log(`Client: ${clientInfo.address}`); } } listConnectedClients(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" ) const ServerAddr = "127.0.0.1:1369" type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.NewClient(ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } func (c *ExampleDBClient) exampleListConnectedClients() error { resp, err := c.client.ListClients(c.ctx, &dbquery.ListClients{}) if err != nil { return err } fmt.Println("Connected Clients:", resp.Clients) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleListConnectedClients(); err != nil { log.Fatalf("ListConnectedClients failed: %v", err) } } ``` ```rust use ahnlich_client_rs::db::DbClient; use ahnlich_client_rs::error::AhnlichError; #[tokio::main] async fn main() -> Result<(), AhnlichError> { // Set the DB server address let addr = "127.0.0.1:1369".to_string(); // Initialize the DB client let db_client = DbClient::new(addr).await?; // Fetch the list of connected clients let clients = db_client.list_clients(None).await?; // Print the clients in a readable way println!("Connected clients:"); for (i, client) in clients.clients.iter().enumerate() { println!("{}. {:?}", i + 1, client); } Ok(()) } ``` This operation isn't exposed as a standalone CLI command — use one of the client libraries above. ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query async def list_connected_clients(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.list_clients(ai_query.ListClients()) # response.clients contains information about connected clients for client_info in response.clients: print(f"Connected client: {client_info.address}") if __name__ == "__main__": asyncio.run(list_connected_clients()) ``` ```typescript import { createAiClient } from "ahnlich-client-node"; import { ListClients } from "ahnlich-client-node/grpc/ai/query_pb"; async function listConnectedClients() { const client = createAiClient("127.0.0.1:1370"); const response = await client.listClients(new ListClients()); console.log(response.clients); // Iterate over connected clients for (const clientInfo of response.clients) { console.log(`Client: ${clientInfo.address}`); } } listConnectedClients(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" ) const ServerAddr = "127.0.0.1:1370" type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.NewClient(ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", ServerAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleAIClient) Close() error { return c.conn.Close() } func (c *ExampleAIClient) exampleListConnectedClients() error { resp, err := c.client.ListClients(c.ctx, &aiquery.ListClients{}) if err != nil { return err } fmt.Println("Connected Clients:", resp.Clients) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf("Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleListConnectedClients(); err != nil { log.Fatalf("ListConnectedClients failed: %v", err) } } ``` ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_client_rs::error::AhnlichError; #[tokio::main] async fn main() -> Result<(), AhnlichError> { // Connect to AI server let addr = "127.0.0.1:1370".to_string(); let ai_client = AiClient::new(addr).await?; // Fetch the list of connected clients let clients = ai_client.list_clients(None).await?; // Print the clients in a readable way println!("Connected clients:"); for (i, client) in clients.clients.iter().enumerate() { println!("{}. {:?}", i + 1, client); } Ok(()) } ``` This operation isn't exposed as a standalone CLI command — use one of the client libraries above. ## Response A list of connected client information (e.g. client addresses). ## Related - [Ping the server](/docs/stores/ping) - [Server info](/docs/stores/info-server) --- # FILE: stores/list-stores.mdx --- title: List stores sidebar_label: List stores --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # List stores List the [stores](/docs/stores) currently registered on the server — handy for introspection, admin tooling, and debugging. > Every operation works on both **Vector DB stores** (you supply raw vectors) and **AI stores** (you supply text or images and [Ahnlich AI](/docs/components/ahnlich-ai) generates the embeddings for you). Use the **Vector DB / AI** switch in the sample below. ## Parameters | Parameter | Type | Description | | --- | --- | --- | | `schema` | string · optional | Schema to list. When omitted, lists the `public` schema only (not every schema). | > All requests accept an optional `schema` field. When omitted, the server uses the `public` schema. ## Behavior - Returns each store's **name**, **entry count**, **size in bytes**, and any active non-linear index configuration. - An empty list means no stores have been created yet. ## Sample query ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query async def list_stores(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) tracing_id = "00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01" response = await client.list_stores( db_query.ListStores(schema="analytics"), metadata={"ahnlich-trace-id": tracing_id} ) print(f"Stores: {[store.name for store in response.stores]}") if __name__ == "__main__": asyncio.run(list_stores()) ``` ```typescript import { createDbClient } from "ahnlich-client-node"; import { ListStores } from "ahnlich-client-node/grpc/db/query_pb"; async function listStores() { const client = createDbClient("127.0.0.1:1369"); const response = await client.listStores(new ListStores({ schema: "analytics" })); // Get store names console.log(response.stores.map((s) => s.name)); // Iterate over stores with full details for (const store of response.stores) { console.log(`Store: ${store.name}`); console.log(` Dimension: ${store.dimension}`); console.log(` Entries: ${store.len}`); console.log(` Size: ${store.sizeInBytes} bytes`); console.log(` Predicate Indices: ${store.predicateIndices}`); console.log(` Non-Linear Indices: ${store.nonLinearIndices}`); } } listStores(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.NewClient(ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } // ListStores example func (c *ExampleDBClient) exampleListStores() error { resp, err := c.client.ListStores(c.ctx, &dbquery.ListStores{ Schema: stringPtr("analytics"), }) if err != nil { return err } fmt.Println("Stores:", resp.Stores) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.exampleListStores(); err != nil { log.Fatalf("ListStores failed: %v", err) } } ``` ```rust use ahnlich_client_rs::db::DbClient; #[tokio::main] async fn main() -> Result<(), Box> { // Connect to your running ahnlich-db instance let db_client = DbClient::new("127.0.0.1:1369".to_string()).await?; let tracing_id: Option = None; // Call list_stores and print the result let stores = db_client .list_stores_with_schema(Some("analytics".to_string()), tracing_id) .await?; println!("Stores: {:?}", stores); Ok(()) } ``` ```bash LISTSTORES ``` ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query async def list_stores(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.list_stores(ai_query.ListStores(schema="analytics")) print(response) #StoreList(stores=[AiStoreInfo(name='my_store', embedding_size=384)]) if __name__ == "__main__": asyncio.run(list_stores()) ``` ```typescript import { createAiClient } from "ahnlich-client-node"; import { ListStores } from "ahnlich-client-node/grpc/ai/query_pb"; async function listStores() { const client = createAiClient("127.0.0.1:1370"); const response = await client.listStores(new ListStores({ schema: "analytics" })); console.log(response.stores.map((s) => s.name)); for (const store of response.stores) { console.log(`Store: ${store.name}`); console.log(` Query Model: ${store.queryModel}`); console.log(` Index Model: ${store.indexModel}`); console.log(` Embedding Size: ${store.embeddingSize}`); } } listStores(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" ) const AIAddr = "127.0.0.1:1370" // ExampleAIClient holds the gRPC connection and AI client. func stringPtr(value string) *string { return &value } type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } // NewAIClient connects to the AI server and returns a client. func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } // Close closes the gRPC connection. func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- ListStores Example ---- // List stores in the analytics schema on the AI server. func (c *ExampleAIClient) exampleListStoresAI() error { resp, err := c.client.ListStores(c.ctx, &aiquery.ListStores{Schema: stringPtr("analytics")}) if err != nil { return err } fmt.Println(" AI Stores:", resp.Stores) return nil } // ---- MAIN ---- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf("Failed to create AI client: %v", err) } defer client.Close() if err := client.exampleListStoresAI(); err != nil { log.Fatalf("ListStores failed: %v", err) } } ``` ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_client_rs::error::AhnlichError; #[tokio::main] async fn main() -> Result<(), AhnlichError> { let addr = "127.0.0.1:1370"; let client = AiClient::new(addr.to_string()).await?; let stores = client .list_stores_with_schema(Some("analytics".to_string()), None) .await?; println!("Stores: {:?}", stores); Ok(()) } ``` ```bash LISTSTORES ``` ## Response A collection of store summaries. ## Related - [Get a store](/docs/stores/get-store) - [Create a store](/docs/stores/create-store) --- # FILE: stores/ping.mdx --- title: Ping the server sidebar_label: Ping the server --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Ping the server Test connectivity to the server — a lightweight health check that confirms the service is up. > Every operation works on both **Vector DB stores** (you supply raw vectors) and **AI stores** (you supply text or images and [Ahnlich AI](/docs/components/ahnlich-ai) generates the embeddings for you). Use the **Vector DB / AI** switch in the sample below. ## Parameters This request takes no arguments. An optional tracing ID may be passed for [distributed tracing](/docs/components/distributed-tracing). ## Behavior - The client sends a ping message and the server replies with a **Pong**. - Handy for readiness/liveness probes, monitoring, and debugging connections. ## Sample query ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query from ahnlich_client_py.grpc.db.server import Pong async def Ping(): """ Test ping """ # Initialize client async with Channel(host="127.0.0.1", port=1369) as channel: db_client = DbServiceStub(channel) # Prepare tracing metadata tracing_id = "00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01" metadata = {"ahnlich-trace-id": tracing_id} # Make request with metadata response = await db_client.ping( db_query.Ping(), metadata=metadata ) if __name__ == "__main__": asyncio.run(Ping()) ``` ```typescript import { createDbClient } from "ahnlich-client-node"; import { Ping } from "ahnlich-client-node/grpc/db/query_pb"; async function ping() { // Initialize client const client = createDbClient("127.0.0.1:1369"); // Make request const response = await client.ping(new Ping()); console.log(response); // Pong } ping(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" ) const ServerAddr = "127.0.0.1:1369" type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.NewClient(ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } func (c *ExampleDBClient) examplePingDB() error { resp, err := c.client.Ping(c.ctx, &dbquery.Ping{}) if err != nil { return err } fmt.Println("Ping:", resp) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() if err := client.examplePingDB(); err != nil { log.Fatalf("Ping failed: %v", err) } } ``` ```rust use ahnlich_client_rs::db::DbClient; #[tokio::main] async fn main() -> Result<(), Box> { // Connect to your running ahnlich-db instance let db_client = DbClient::new("127.0.0.1:1369".to_string()).await?; // Optional tracing ID (can be None if you don’t use tracing) let tracing_id: Option = None; // Call ping and print the response let res = db_client.ping(tracing_id).await?; println!("Ping response: {:?}", res); Ok(()) } ``` ```bash PING ``` ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query async def ping(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.ping(ai_query.Ping()) print(response) #Pong() if __name__ == "__main__": asyncio.run(ping()) ``` ```typescript import { createAiClient } from "ahnlich-client-node"; import { Ping } from "ahnlich-client-node/grpc/ai/query_pb"; async function ping() { const client = createAiClient("127.0.0.1:1370"); const response = await client.ping(new Ping()); console.log(response); // Pong } ping(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" ) const AIAddr = "127.0.0.1:1370" // ExampleAIClient holds the gRPC connection and AI client. type ExampleAIClient struct { conn *grpc.ClientConn client aisvc.AIServiceClient ctx context.Context } // NewAIClient connects to the AI server and returns a client. func NewAIClient(ctx context.Context) (*ExampleAIClient, error) { conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to dial AI server %q: %w", AIAddr, err) } client := aisvc.NewAIServiceClient(conn) return &ExampleAIClient{conn: conn, client: client, ctx: ctx}, nil } // Close closes the gRPC connection. func (c *ExampleAIClient) Close() error { return c.conn.Close() } // ---- Ping Example ---- func (c *ExampleAIClient) examplePingAI() error { resp, err := c.client.Ping(c.ctx, &aiquery.Ping{}) if err != nil { return err } fmt.Println(" AI Ping:", resp) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewAIClient(ctx) if err != nil { log.Fatalf("Failed to create AI client: %v", err) } defer client.Close() if err := client.examplePingAI(); err != nil { log.Fatalf("Ping failed: %v", err) } } ``` ```rust // src/bin/pingai.rs use ahnlich_client_rs::ai::AiClient; // AiClient path use ahnlich_client_rs::error::AhnlichError; // Error type use ahnlich_types::ai::pipeline::AiResponsePipeline; #[tokio::main] async fn main() -> Result<(), AhnlichError> { // AI server address let addr = "http://127.0.0.1:1370"; // Initialize the AI client let ai_client = AiClient::new(addr.to_string()).await?; // Simple ping request let pong = ai_client.ping(None).await?; println!("AI Server Pong received: {:?}", pong); // Using a pipeline to send a ping let mut pipeline = ai_client.pipeline(None); pipeline.ping(); let res: AiResponsePipeline = pipeline.exec().await?; println!("Pipeline response: {:?}", res); Ok(()) } ``` ```bash PING ``` ## Response A `Pong` message confirming connectivity. ## Related - [Server info](/docs/stores/info-server) - [List connected clients](/docs/stores/list-connected-clients) --- # FILE: stores/set.mdx --- title: Insert data sidebar_label: Insert data --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Insert data Insert one or more entries into a store. Each entry pairs a **key** (the vector) with a **value** (its metadata). > Every operation works on both **Vector DB stores** (you supply raw vectors) and **AI stores** (you supply text or images and [Ahnlich AI](/docs/components/ahnlich-ai) generates the embeddings for you). Use the **Vector DB / AI** switch in the sample below. ## Parameters | Parameter | Type | Description | | --- | --- | --- | | `store` | string | Name of the target store. | | `inputs` | list of entries | Each entry is a `StoreKey` (vector, length must equal the store's dimension) and a `StoreValue` (map of [metadata](/docs/concepts/metadata) predicates). | | `schema` | string · optional | Schema to target. Defaults to `public`. | > All requests accept an optional `schema` field. When omitted, the server uses the `public` schema. ## Behavior - If a key already exists, its metadata is **updated**; otherwise a new entry is **inserted**. - Every key's vector length must match the store's `dimension`. - Insert many entries in a single call by passing multiple `inputs`. ## Sample query ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc import keyval, metadata from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query async def set(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) store_key = keyval.StoreKey(key=[1.0, 2.0, 3.0, 4.0]) store_value = keyval.StoreValue( value={"label": metadata.MetadataValue(raw_string="A")} ) response = await client.set( db_query.Set( store="my_store", schema="analytics", inputs=[keyval.DbStoreEntry(key=store_key, value=store_value)] ) ) if __name__ == "__main__": asyncio.run(set()) ``` ```typescript import { createDbClient } from "ahnlich-client-node"; import { Set } from "ahnlich-client-node/grpc/db/query_pb"; import { DbStoreEntry, StoreKey, StoreValue } from "ahnlich-client-node/grpc/keyval_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; async function setEntries() { const client = createDbClient("127.0.0.1:1369"); await client.set( new Set({ store: "my_store", schema: "analytics", inputs: [ new DbStoreEntry({ key: new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] }), value: new StoreValue({ value: { label: new MetadataValue({ value: { case: "rawString", value: "A" } }), }, }), }), ], }) ); console.log("Entry inserted successfully"); } setEntries(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } type ExampleDBClient struct { conn *grpc.ClientConn client dbsvc.DBServiceClient ctx context.Context } func NewDBClient(ctx context.Context) (*ExampleDBClient, error) { conn, err := grpc.NewClient(ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return nil, fmt.Errorf("failed to dial DB server %q: %w", ServerAddr, err) } client := dbsvc.NewDBServiceClient(conn) return &ExampleDBClient{conn: conn, client: client, ctx: ctx}, nil } func (c *ExampleDBClient) Close() error { return c.conn.Close() } // Set example func (c *ExampleDBClient) exampleSet(store string) error { entries := []*keyval.DbStoreEntry{ { Key: &keyval.StoreKey{Key: []float32{1, 2, 3, 4}}, Value: &keyval.StoreValue{ Value: map[string]*metadata.MetadataValue{ "label": { Value: &metadata.MetadataValue_RawString{RawString: "A"}, }, }, }, }, } _, err := c.client.Set(c.ctx, &dbquery.Set{Store: store, Schema: stringPtr("analytics"), Inputs: entries}) if err != nil { return err } fmt.Println("Inserted entry into store:", store) return nil } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := NewDBClient(ctx) if err != nil { log.Fatalf("Failed to create DB client: %v", err) } defer client.Close() storeName := "my_store" if err := client.exampleSet(storeName); err != nil { log.Fatalf("Set failed: %v", err) } } ``` ```rust use ahnlich_client_rs::db::DbClient; use ahnlich_types::{ db::query::Set, keyval::{DbStoreEntry, StoreKey, StoreValue}, metadata::{MetadataValue, metadata_value::Value}, }; use std::collections::HashMap; #[tokio::main] async fn main() -> Result<(), Box> { // Connect to DB server let db_client = DbClient::new("127.0.0.1:1369".to_string()).await?; let tracing_id: Option = None; // Construct inputs for the "set" let inputs = vec![DbStoreEntry { key: Some(StoreKey { key: vec![1.0, 2.0, 3.0, 4.0], // must match store dimension }), value: Some(StoreValue { value: HashMap::from_iter([( "label".into(), MetadataValue { value: Some(Value::RawString("A".into())), }, )]), }), }]; let params = Set { store: "my_store".to_string(), // store must already exist schema: Some("analytics".to_string()), inputs, }; // Call set match db_client.set(params, tracing_id).await { Ok(result) => { println!("Set operation result: {:?}", result); } Err(err) => { eprintln!("Error inserting vector: {:?}", err); } } Ok(()) } ``` ```bash SET (([1.0, 2.0, 3.0, 4.0], {label: A})) IN my_store ``` ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc import keyval, metadata from ahnlich_client_py.grpc.ai import preprocess async def sets(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) response = await client.set( ai_query.Set( store="my_store", schema="analytics", inputs=[ keyval.AiStoreEntry( key=keyval.StoreInput(raw_string="Jordan One"), value=keyval.StoreValue( value={"brand": metadata.MetadataValue(raw_string="Nike")} ), ), keyval.AiStoreEntry( key=keyval.StoreInput(raw_string="Yeezey"), value=keyval.StoreValue( value={"brand": metadata.MetadataValue(raw_string="Adidas")} ), ) ], preprocess_action=preprocess.PreprocessAction.NoPreprocessing, execution_provider=None, # Optional: e.g., ExecutionProvider.CUDA for GPU acceleration model_params={} # Optional: runtime model parameters (e.g., {"confidence_threshold": "0.9"} for face detection) ) ) print(response) #Set(upsert=StoreUpsert(inserted=2)) if __name__ == "__main__": asyncio.run(sets()) ``` ```typescript import { createAiClient } from "ahnlich-client-node"; import { Set } from "ahnlich-client-node/grpc/ai/query_pb"; import { AiStoreEntry, StoreInput, StoreValue } from "ahnlich-client-node/grpc/keyval_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; import { PreprocessAction } from "ahnlich-client-node/grpc/ai/preprocess_pb"; async function setEntries() { const client = createAiClient("127.0.0.1:1370"); await client.set( new Set({ store: "ai_store", schema: "analytics", inputs: [ new AiStoreEntry({ key: new StoreInput({ value: { case: "rawString", value: "Jordan One" } }), value: new StoreValue({ value: { brand: new MetadataValue({ value: { case: "rawString", value: "Nike" } }), }, }), }), ], preprocessAction: PreprocessAction.NO_PREPROCESSING, }) ); console.log("Entry inserted successfully"); } setEntries(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" preprocess "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/preprocess" ) const AIAddr = "127.0.0.1:1370" func stringPtr(value string) *string { return &value } // ---- Standalone Set Example ---- func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() // connect to AI server conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock(), ) if err != nil { log.Fatalf(" Failed to connect to AI server: %v", err) } defer conn.Close() client := aisvc.NewAIServiceClient(conn) // prepare key/value input inputs := []*keyval.AiStoreEntry{ { Key: &keyval.StoreInput{ Value: &keyval.StoreInput_RawString{RawString: "X"}, }, Value: &keyval.StoreValue{ Value: map[string]*metadata.MetadataValue{ "f": {Value: &metadata.MetadataValue_RawString{RawString: "v"}}, }, }, }, } // perform Set operation _, err = client.Set(ctx, &aiquery.Set{ Store: "ai_store", // must already exist Schema: stringPtr("analytics"), // Optional: defaults to public when omitted Inputs: inputs, PreprocessAction: preprocess.PreprocessAction_NoPreprocessing, ExecutionProvider: nil, // Optional: e.g., ExecutionProvider_CUDA for GPU acceleration }) if err != nil { log.Fatalf(" Set failed: %v", err) } fmt.Println(" Successfully inserted key/value into ai_store01") } ``` ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_client_rs::error::AhnlichError; use ahnlich_types::ai::preprocess::PreprocessAction; use ahnlich_types::ai::query::Set; use ahnlich_types::keyval::{AiStoreEntry, StoreInput, StoreValue}; use ahnlich_types::keyval::store_input::Value; use std::collections::HashMap; #[tokio::main] async fn main() -> Result<(), AhnlichError> { // Connect to AI server let addr = "127.0.0.1:1370"; let client = AiClient::new(addr.to_string()).await?; // Prepare data for Set let set_params = Set { store: "Main0".to_string(), schema: Some("analytics".to_string()), execution_provider: None, preprocess_action: PreprocessAction::NoPreprocessing as i32, inputs: vec![ AiStoreEntry { key: Some(StoreInput { value: Some(Value::RawString("Adidas Yeezy".into())) }), value: Some(StoreValue { value: HashMap::new() }), }, AiStoreEntry { key: Some(StoreInput { value: Some(Value::RawString("Nike Air Jordans".into())) }), value: Some(StoreValue { value: HashMap::new() }), }, ], model_params: HashMap::new(), }; // Run the set command let res = client.set(set_params, None).await?; println!("Inserted entries: {:?}", res.upsert); Ok(()) } ``` ```bash SET (([This is a sentence], {label: A})) IN my_store PREPROCESSACTION nopreprocessing ``` ## Response A confirmation response indicating success. ## Related - [Upsert data](/docs/stores/upsert) - [Get by key](/docs/stores/get-key) - [Similarity search](/docs/stores/get-simn) --- # FILE: stores/stores.mdx --- id: stores title: 🗂️ Stores --- import CustomDocCard from '@site/src/components/CustomDocCard'; export const serverOps = [ {title: 'Ping the server', icon: 'activity', link: '/docs/stores/ping'}, {title: 'Server info', icon: 'info', link: '/docs/stores/info-server'}, {title: 'List connected clients', icon: 'users', link: '/docs/stores/list-connected-clients'}, ]; export const storeOps = [ {title: 'Create a store', icon: 'plus', link: '/docs/stores/create-store'}, {title: 'List stores', icon: 'list', link: '/docs/stores/list-stores'}, {title: 'Get a store', icon: 'search', link: '/docs/stores/get-store'}, {title: 'Drop a store', icon: 'trash', link: '/docs/stores/drop-store'}, ]; export const indexOps = [ {title: 'Create predicate index', icon: 'filter', link: '/docs/stores/create-predicate-index'}, {title: 'Drop predicate index', icon: 'filter', link: '/docs/stores/drop-predicate-index'}, {title: 'Create non-linear index', icon: 'branch', link: '/docs/stores/create-non-linear-algx'}, {title: 'Drop non-linear index', icon: 'branch', link: '/docs/stores/drop-non-linear-algx'}, ]; export const dataOps = [ {title: 'Insert data', icon: 'plus', link: '/docs/stores/set'}, {title: 'Upsert data', icon: 'refresh', link: '/docs/stores/upsert'}, {title: 'Get by key', icon: 'key', link: '/docs/stores/get-key'}, {title: 'Similarity search', icon: 'target', link: '/docs/stores/get-simn'}, {title: 'Get by predicate', icon: 'filter', link: '/docs/stores/get-by-predicate'}, {title: 'Delete by key', icon: 'trash', link: '/docs/stores/delete-key'}, {title: 'Delete by predicate', icon: 'trash', link: '/docs/stores/delete-predicate'}, ]; # Stores A **store** is Ahnlich's main unit of organization — a named container for your vectors and their [metadata](/docs/concepts/metadata), much like a *collection* in a document database or a *table* in a relational one. You create a store, insert data into it, then query it by similarity or by metadata predicates. Every operation below is shown in **Python, Node.js, Go, Rust, and the CLI** — pick your language once and it stays selected across pages. ## Server operations Talk to the running server itself — health checks and metadata.
{serverOps.map((op) => ( ))}
## Store operations Manage the store containers themselves — their whole lifecycle.
{storeOps.map((op) => ( ))}
## Indexes Add or remove indexes to speed up predicate filters and nearest-neighbour search.
{indexOps.map((op) => ( ))}
## Data operations Read and write the entries inside a store.
{dataOps.map((op) => ( ))}
--- # FILE: stores/upsert.mdx --- title: Upsert data sidebar_label: Upsert data --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Upsert data Update a single existing entry matched by a predicate — replace its vector, its [metadata](/docs/concepts/metadata), or both. > Every operation works on both **Vector DB stores** (you supply raw vectors) and **AI stores** (you supply text or images and [Ahnlich AI](/docs/components/ahnlich-ai) generates the embeddings for you). Use the **Vector DB / AI** switch in the sample below. ## Parameters | Parameter | Type | Description | | --- | --- | --- | | `store` | string | Name of the target store. | | `condition` | predicate | A predicate that must match **exactly one** entry. | | `new_key` | vector · optional | New vector to replace the matched entry's key. | | `new_value` | metadata · optional | Metadata to update on the matched entry. | | `merge_metadata` | bool · optional | `true` merges into existing metadata; `false` (default) replaces it entirely. | | `schema` | string · optional | Schema to target. Defaults to `public`. | > All requests accept an optional `schema` field. When omitted, the server uses the `public` schema. ## Behavior - Errors if **zero or multiple** entries match the predicate — it targets a single entry. - Update the vector only, the metadata only, or both in one call. - With `merge_metadata: true`, unchanged fields are preserved. ## Sample query ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc import keyval, metadata, predicates from ahnlich_client_py.grpc.services.db_service import DbServiceStub from ahnlich_client_py.grpc.db import query as db_query async def upsert(): async with Channel(host="127.0.0.1", port=1369) as channel: client = DbServiceStub(channel) condition = predicates.PredicateCondition( value=predicates.Predicate( equals=predicates.Equals( key="id", value=metadata.MetadataValue(raw_string="123") ) ) ) new_value = keyval.StoreValue( value={"status": metadata.MetadataValue(raw_string="published")} ) response = await client.upsert( db_query.Upsert( store="my_store", schema="analytics", condition=condition, new_value=new_value, merge_metadata=True ) ) if __name__ == "__main__": asyncio.run(upsert()) ``` ```typescript import { createDbClient } from "ahnlich-client-node"; import { Upsert } from "ahnlich-client-node/grpc/db/query_pb"; import { StoreValue } from "ahnlich-client-node/grpc/keyval_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; import { PredicateCondition, Predicate, Equals } from "ahnlich-client-node/grpc/predicates_pb"; async function upsertEntry() { const client = createDbClient("127.0.0.1:1369"); const condition = new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "id", value: new MetadataValue({ value: { case: "rawString", value: "123" } }), }), }, }), }, }); const newValue = new StoreValue({ value: { status: new MetadataValue({ value: { case: "rawString", value: "published" } }), }, }); const response = await client.upsert( new Upsert({ store: "my_store", schema: "analytics", condition, newValue, mergeMetadata: true, }) ); console.log("Updated:", response.upsert?.updated); } upsertEntry(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" dbsvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service" dbquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" predicates "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/predicates" ) const ServerAddr = "127.0.0.1:1369" func stringPtr(value string) *string { return &value } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() conn, err := grpc.NewClient(ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Fatalf("failed to dial: %v", err) } defer conn.Close() client := dbsvc.NewDBServiceClient(conn) condition := &predicates.PredicateCondition{ Kind: &predicates.PredicateCondition_Value{ Value: &predicates.Predicate{ Kind: &predicates.Predicate_Equals{ Equals: &predicates.Equals{ Key: "id", Value: &metadata.MetadataValue{ Value: &metadata.MetadataValue_RawString{RawString: "123"}, }, }, }, }, }, } newValue := &keyval.StoreValue{ Value: map[string]*metadata.MetadataValue{ "status": {Value: &metadata.MetadataValue_RawString{RawString: "published"}}, }, } resp, err := client.Upsert(ctx, &dbquery.Upsert{ Store: "my_store", Schema: stringPtr("analytics"), Condition: condition, NewValue: newValue, MergeMetadata: true, }) if err != nil { log.Fatalf("Upsert failed: %v", err) } fmt.Printf("Updated: %d\n", resp.Upsert.Updated) } ``` ```rust use ahnlich_client_rs::db::DbClient; use ahnlich_types::{ db::query::Upsert, keyval::{StoreKey, StoreValue}, metadata::{MetadataValue, metadata_value::Value}, predicates::{Predicate, PredicateCondition, predicate_condition::Kind, predicate::Kind as PredKind}, }; use std::collections::HashMap; #[tokio::main] async fn main() -> Result<(), Box> { // Connect to DB server let db_client = DbClient::new("127.0.0.1:1369".to_string()).await?; let tracing_id: Option = None; // Construct predicate condition let condition = PredicateCondition { kind: Some(Kind::Value(Predicate { kind: Some(PredKind::Equals(ahnlich_types::predicates::Equals { key: "id".to_string(), value: Some(MetadataValue { value: Some(Value::RawString("123".to_string())), }), })), })), }; // New metadata to merge/replace let new_value = Some(StoreValue { value: HashMap::from_iter([( "status".into(), MetadataValue { value: Some(Value::RawString("published".into())), }, )]), }); let params = Upsert { store: "my_store".to_string(), schema: Some("analytics".to_string()), condition: Some(condition), new_key: None, // Optional: new vector new_value, merge_metadata: true, // Merge instead of replace }; // Call upsert match db_client.upsert(params, tracing_id).await { Ok(result) => { println!("Upsert result: {:?}", result); } Err(err) => { eprintln!("Error in upsert: {:?}", err); } } Ok(()) } ``` ```bash UPSERT VALUE {status: published} IN my_store WHERE (id = 123) ``` ```python import asyncio from grpclib.client import Channel from ahnlich_client_py.grpc.services.ai_service import AiServiceStub from ahnlich_client_py.grpc.ai import query as ai_query from ahnlich_client_py.grpc import keyval, metadata, predicates from ahnlich_client_py.grpc.ai import preprocess async def upsert(): async with Channel(host="127.0.0.1", port=1370) as channel: client = AiServiceStub(channel) condition = predicates.PredicateCondition( value=predicates.Predicate( equals=predicates.Equals( key="filename", value=metadata.MetadataValue(raw_string="photo.jpg") ) ) ) new_value = keyval.StoreValue( value={"tags": metadata.MetadataValue(raw_string="cat,outdoors")} ) response = await client.upsert( ai_query.Upsert( store="images", schema="media", condition=condition, new_input=None, # Optional: new image/text to re-embed new_value=new_value, preprocess_action=preprocess.PreprocessAction.NoPreprocessing, execution_provider=None, model_params={} ) ) print(response) #Set(upsert=StoreUpsert(updated=1, inserted=0)) if __name__ == "__main__": asyncio.run(upsert()) ``` ```typescript import { createAiClient } from "ahnlich-client-node"; import { Upsert } from "ahnlich-client-node/grpc/ai/query_pb"; import { StoreValue } from "ahnlich-client-node/grpc/keyval_pb"; import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb"; import { PredicateCondition, Predicate, Equals } from "ahnlich-client-node/grpc/predicates_pb"; import { PreprocessAction } from "ahnlich-client-node/grpc/ai/preprocess_pb"; async function upsertEntry() { const client = createAiClient("127.0.0.1:1370"); const condition = new PredicateCondition({ kind: { case: "value", value: new Predicate({ kind: { case: "equals", value: new Equals({ key: "filename", value: new MetadataValue({ value: { case: "rawString", value: "photo.jpg" } }), }), }, }), }, }); const newValue = new StoreValue({ value: { tags: new MetadataValue({ value: { case: "rawString", value: "cat,outdoors" } }), }, }); const response = await client.upsert( new Upsert({ store: "images", schema: "media", condition, newValue, preprocessAction: PreprocessAction.NO_PREPROCESSING, }) ); console.log("Updated:", response.upsert?.updated); } upsertEntry(); ``` ```go package main import ( "context" "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" aiquery "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/query" aisvc "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/ai_service" keyval "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/keyval" metadata "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/metadata" predicates "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/predicates" preprocess "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/ai/preprocess" ) const AIAddr = "127.0.0.1:1370" func stringPtr(value string) *string { return &value } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() conn, err := grpc.DialContext(ctx, AIAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock(), ) if err != nil { log.Fatalf("Failed to connect: %v", err) } defer conn.Close() client := aisvc.NewAIServiceClient(conn) condition := &predicates.PredicateCondition{ Kind: &predicates.PredicateCondition_Value{ Value: &predicates.Predicate{ Kind: &predicates.Predicate_Equals{ Equals: &predicates.Equals{ Key: "filename", Value: &metadata.MetadataValue{ Value: &metadata.MetadataValue_RawString{RawString: "photo.jpg"}, }, }, }, }, }, } newValue := &keyval.StoreValue{ Value: map[string]*metadata.MetadataValue{ "tags": {Value: &metadata.MetadataValue_RawString{RawString: "cat,outdoors"}}, }, } resp, err := client.Upsert(ctx, &aiquery.Upsert{ Store: "images", Schema: stringPtr("media"), Condition: condition, NewInput: nil, // Optional: new image/text to re-embed NewValue: newValue, PreprocessAction: preprocess.PreprocessAction_NoPreprocessing, ExecutionProvider: nil, ModelParams: map[string]string{}, }) if err != nil { log.Fatalf("Upsert failed: %v", err) } fmt.Printf("Updated: %d\n", resp.Upsert.Updated) } ``` ```rust use ahnlich_client_rs::ai::AiClient; use ahnlich_client_rs::error::AhnlichError; use ahnlich_types::ai::preprocess::PreprocessAction; use ahnlich_types::ai::query::Upsert; use ahnlich_types::keyval::{StoreInput, StoreValue}; use ahnlich_types::metadata::{MetadataValue, metadata_value::Value}; use ahnlich_types::predicates::{Predicate, PredicateCondition, predicate_condition::Kind, predicate::Kind as PredKind}; use std::collections::HashMap; #[tokio::main] async fn main() -> Result<(), AhnlichError> { // Connect to AI server let addr = "127.0.0.1:1370"; let client = AiClient::new(addr.to_string()).await?; // Construct predicate condition let condition = PredicateCondition { kind: Some(Kind::Value(Predicate { kind: Some(PredKind::Equals(ahnlich_types::predicates::Equals { key: "filename".to_string(), value: Some(MetadataValue { value: Some(Value::RawString("photo.jpg".to_string())), }), })), })), }; // New metadata to merge let new_value = Some(StoreValue { value: HashMap::from_iter([( "tags".into(), MetadataValue { value: Some(Value::RawString("cat,outdoors".into())), }, )]), }); let params = Upsert { store: "images".to_string(), schema: Some("media".to_string()), condition: Some(condition), new_input: None, // Optional: new image/text to re-embed new_value, preprocess_action: PreprocessAction::NoPreprocessing as i32, execution_provider: None, model_params: HashMap::new(), }; // Run the upsert command let res = client.upsert(params, None).await?; println!("Upsert result: {:?}", res.upsert); Ok(()) } ``` ```bash UPSERT VALUE {status: published} IN my_store WHERE (id = 123) ``` ## Response A Set response with upsert counts (e.g. `inserted: 0, updated: 1`). ## Related - [Insert data](/docs/stores/set) - [Get by predicate](/docs/stores/get-by-predicate) --- # FILE: troubleshooting/common-issues.md --- title: Common Issues sidebar_position: 10 --- # Troubleshooting Common Issues This guide covers the most common issues users encounter and how to resolve them. ## Memory and Performance Issues ### Out of Memory Errors **Symptoms:** ``` allocation error: CapacityOverflow Server crashes unexpectedly ``` **Causes:** - Hitting the `--allocator-size` limit - Large batch operations - Image processing without streaming enabled **Solutions:** 1. **Increase allocator size:** ```bash ahnlich-db run --allocator-size 21474836480 # 20 GiB (default is 10 GiB) ahnlich-ai run --allocator-size 21474836480 ``` 2. **Enable streaming for images (AI proxy):** ```bash ahnlich-ai run --enable-streaming # 10x less memory, 40% slower ``` 3. **Reduce batch sizes:** ```python # Instead of: large_batch = [entry1, entry2, ..., entry1000] client.set(Set(store="my_store", inputs=large_batch)) # Do this: batch_size = 100 for i in range(0, len(large_batch), batch_size): batch = large_batch[i:i+batch_size] client.set(Set(store="my_store", inputs=batch)) ``` 4. **Monitor memory usage:** ```bash # Check process memory ps aux | grep ahnlich # Monitor with top top -p $(pgrep ahnlich) ``` --- ### Slow Query Performance **Symptoms:** - Queries taking longer than expected - High CPU usage **Diagnostic Steps:** 1. **Enable tracing to identify bottlenecks:** ```bash ahnlich-db run --enable-tracing --otel-endpoint http://localhost:4317 ``` View traces in Jaeger UI at `http://localhost:16686` 2. **Check store size:** ``` INFOSERVER ``` 3. **Verify algorithm choice:** - Linear algorithms (Cosine, Euclidean, DotProduct) scale linearly with data size - Use `HNSW` for faster searches with large datasets: ``` CREATESTORE my_store DIMENSION 128 NONLINEARALGORITHMINDEX (HNSW) ``` **Solutions:** 1. **Use predicate indices for filtering:** ``` # Index frequently filtered fields CREATEPREDINDEX (category, author) IN my_store # Then filter efficiently GETPRED (category = science) IN my_store ``` 2. **Optimize batch operations:** ```python # Batch SET operations entries = [entry1, entry2, ..., entry100] client.set(Set(store="my_store", inputs=entries)) ``` 3. **Use appropriate similarity algorithm:** - **CosineSimilarity**: Best for normalized vectors, direction-based similarity - **EuclideanDistance**: Best for absolute distance measures - **DotProduct**: Fast when vectors are pre-normalized - **HNSW**: Best for large-scale approximate nearest neighbor searches 4. **Adjust thread pool size:** ```bash ahnlich-db run --threadpool-size 32 # Default: 16 ``` --- ## Connection Issues ### Cannot Connect to Server **Symptoms:** ``` connection refused Failed to dial server Transport issues with tonic ``` **Diagnostic Steps:** 1. **Check if server is running:** ```bash # Check DB curl http://localhost:1369 # or use telnet ps aux | grep ahnlich-db # Check AI curl http://localhost:1370 ps aux | grep ahnlich-ai ``` 2. **Verify port availability:** ```bash # Check if port is in use lsof -i :1369 lsof -i :1370 # Or with netstat netstat -tuln | grep 1369 ``` 3. **Check firewall rules:** ```bash # Ubuntu/Debian sudo ufw status sudo ufw allow 1369 sudo ufw allow 1370 # CentOS/RHEL sudo firewall-cmd --list-all sudo firewall-cmd --add-port=1369/tcp --permanent sudo firewall-cmd --reload ``` **Solutions:** 1. **Start server on all interfaces:** ```bash # Allow connections from any IP ahnlich-db run --host 0.0.0.0 --port 1369 ahnlich-ai run --host 0.0.0.0 --port 1370 ``` 2. **Check host/port configuration:** ```python # Correct client = DbClient("http://127.0.0.1:1369") # Wrong - missing protocol client = DbClient("127.0.0.1:1369") # Invalid URI error ``` 3. **Verify network connectivity:** ```bash # Test connectivity ping telnet 1369 ``` --- ### Maximum Clients Reached **Symptoms:** ``` Max Connected Clients Reached Connection rejected ``` **Cause:** Hit the `--maximum-clients` limit (default: 1000) **Solutions:** 1. **Increase client limit:** ```bash ahnlich-db run --maximum-clients 5000 ``` 2. **Check current connections:** ``` LISTCLIENTS ``` 3. **Implement connection pooling:** ```python # Reuse connections instead of creating new ones class ClientPool: def __init__(self, uri, pool_size=10): self.pool = [DbClient(uri) for _ in range(pool_size)] self.index = 0 def get_client(self): client = self.pool[self.index] self.index = (self.index + 1) % len(self.pool) return client ``` 4. **Close idle connections:** ```python async def cleanup(): await client.close() ``` --- ### AI Proxy Cannot Connect to Database **Symptoms:** ``` Proxy Errored with connection refused DatabaseClientError ``` **Diagnostic Steps:** 1. **Verify DB is running:** ```bash ps aux | grep ahnlich-db ``` 2. **Check DB host/port:** ```bash # See what DB is listening on netstat -tuln | grep 1369 ``` **Solutions:** 1. **Start DB before AI:** ```bash # Terminal 1 ahnlich-db run --port 1369 # Terminal 2 (wait for DB to start) ahnlich-ai run --db-host 127.0.0.1 --db-port 1369 ``` 2. **Verify connection settings:** ```bash # If DB is on different host ahnlich-ai run --db-host 192.168.1.10 --db-port 1369 # If DB uses non-default port ahnlich-ai run --db-port 1400 ``` 3. **For standalone mode (no DB):** ```bash ahnlich-ai run --without-db ``` 4. **Adjust connection pool:** ```bash ahnlich-ai run --db-client-pool-size 20 # Default: 10 ``` --- ## Data and Store Issues ### Store Not Found **Symptoms:** ``` Store "my_store" not found ``` **Diagnostic Steps:** 1. **List stores in the relevant schema:** ``` LISTSTORES SCHEMA public ``` 2. **Check store name spelling:** ``` # Store names are case-sensitive "MyStore" ≠ "mystore" ``` **Solutions:** 1. **Create the store:** ``` # DB CREATESTORE my_store DIMENSION 128 # AI CREATESTORE my_store QUERYMODEL all-minilm-l6-v2 INDEXMODEL all-minilm-l6-v2 ``` 2. **Check persistence loaded:** ```bash # If using persistence ahnlich-db run \ --enable-persistence \ --persist-location /path/to/data.dat \ --fail-on-startup-if-persist-load-fails true # Fail loudly if load fails ``` 3. **Verify correct server:** ```python # Make sure you're connecting to the right instance client = DbClient("http://localhost:1369") # Not a different instance ``` --- ### Dimension Mismatch Errors **Symptoms:** ``` Store dimension is [128], input dimension of [256] was specified ``` **Cause:** Vector dimensions don't match store configuration. **Solutions:** 1. **Check store dimension:** ``` INFOSERVER # Look at store details ``` 2. **For AI stores, verify model dimensions:** | Model | Embedding Dimension | |-------|---------------------| | all-minilm-l6-v2 | 384 | | all-minilm-l12-v2 | 384 | | bge-base-en-v1.5 | 768 | | bge-large-en-v1.5 | 1024 | | resnet-50 | 2048 | | clip-vit-b32-* | 512 | | clap-audio / clap-text | 512 | | buffalo-l | 512 | | sface-yunet | 128 | 3. **Match query and index models:** ```python # Both must have same dimensions CreateStore( store="my_store", query_model=AiModel.BGE_BASE_EN_V15, # 768-dim index_model=AiModel.BGE_BASE_EN_V15, # 768-dim (same) ) ``` 4. **Recreate store with correct dimension:** ``` DROPSTORE my_store IFTRUE CREATESTORE my_store DIMENSION 768 ``` --- ### Predicate Not Found **Symptoms:** ``` Predicate "author" not found in store ``` **Cause:** Querying by a predicate that wasn't indexed. **Solutions:** 1. **Create predicate index:** ``` CREATEPREDINDEX (author, category) IN my_store ``` 2. **Or include when creating store:** ``` CREATESTORE my_store DIMENSION 128 PREDICATES (author, category) ``` 3. **Verify predicates exist:** ``` INFOSERVER # Check store predicates ``` --- ## Model and AI Issues ### Model Not Loading **Symptoms:** ``` index_model or query_model not selected or loaded Error initializing a model thread Tokenizer for model failed to load ``` **Diagnostic Steps:** 1. **Check supported models:** ```bash ahnlich-ai run --supported-models all-minilm-l6-v2,resnet-50 ``` 2. **Verify model cache:** ```bash # Default location ls -la ~/.ahnlich/models # Custom location ahnlich-ai run --model-cache-location /path/to/models ``` 3. **Check disk space:** ```bash df -h ~/.ahnlich/models ``` 4. **Test network connectivity:** ```bash # Models download from HuggingFace curl https://huggingface.co ``` **Solutions:** 1. **Wait for initial download:** ```bash # First time loading a model downloads from HuggingFace # This can take several minutes depending on model size # Watch logs for progress ``` 2. **Clear corrupted cache:** ```bash rm -rf ~/.ahnlich/models/model_name # Restart server to re-download ``` 3. **Increase idle time:** ```bash # Keep models loaded longer ahnlich-ai run --ai-model-idle-time 600 # 10 minutes (default: 5 min) ``` 4. **Pre-download models:** ```bash # Download models before starting server python -c "from transformers import AutoModel; AutoModel.from_pretrained('sentence-transformers/all-MiniLM-L6-v2')" ``` --- ### Token Limit Exceeded **Symptoms:** ``` Max Token Exceeded. Model Expects [256], input type was [512] ``` **Cause:** Text input exceeds model's token limit. **Token Limits:** - all-minilm-*: 256 tokens - bge-*: 512 tokens - clip-vit-b32-text: 77 tokens - clap-text: 512 tokens **Solutions:** 1. **Truncate text:** ```python def truncate_text(text, max_length=200): words = text.split() return ' '.join(words[:max_length]) text = truncate_text(long_text) ``` 2. **Split into chunks:** ```python def chunk_text(text, chunk_size=200): words = text.split() return [' '.join(words[i:i+chunk_size]) for i in range(0, len(words), chunk_size)] chunks = chunk_text(long_document) for chunk in chunks: client.set(Set(store="docs", inputs=[...])) ``` 3. **Use model with larger limit:** ```python # Switch from AllMiniLM (256) to BGE (512) CreateStore( store="my_store", query_model=AiModel.BGE_BASE_EN_V15, # 512 tokens index_model=AiModel.BGE_BASE_EN_V15, ) ``` --- ### Image Dimension Errors **Symptoms:** ``` Image Dimensions [(512, 512)] does not match expected [(224, 224)] Image can't have zero dimension ``` **Cause:** Images not matching model requirements (224x224 pixels). **Solutions:** 1. **Resize images:** ```python from PIL import Image def prepare_image(image_path): img = Image.open(image_path) img = img.resize((224, 224)) return img.tobytes() image_bytes = prepare_image("photo.jpg") ``` 2. **Use model preprocessing:** ```python Set( store="my_store", inputs=[...], preprocess_action=PreprocessAction.ModelPreprocessing, # Auto-resize ) ``` 3. **Validate images before sending:** ```python def validate_image(image_bytes): img = Image.open(io.BytesIO(image_bytes)) if img.width == 0 or img.height == 0: raise ValueError("Invalid image dimensions") return img img = validate_image(image_bytes) ``` --- ### Audio Processing Errors **Symptoms:** ``` Audio input is too long (15000ms). Model accepts at most 10000ms per clip. NoPreprocessing is not supported for audio inputs. Bytes could not be successfully decoded into audio ``` **Causes:** - Audio clip exceeds 10-second limit - Using `NoPreprocessing` with audio - Corrupted or unsupported audio format **Solutions:** 1. **Trim audio to 10 seconds:** ```python from pydub import AudioSegment audio = AudioSegment.from_file("long_audio.wav") clip = audio[:10000] # First 10 seconds (in milliseconds) clip.export("trimmed.wav", format="wav") ``` 2. **Split long audio into chunks:** ```python def split_audio(audio_path, chunk_duration_ms=10000): audio = AudioSegment.from_file(audio_path) chunks = [] for i in range(0, len(audio), chunk_duration_ms): chunk = audio[i:i + chunk_duration_ms] chunks.append(chunk) return chunks ``` 3. **Always use ModelPreprocessing:** ```python Set( store="audio_store", inputs=[...], preprocess_action=PreprocessAction.ModelPreprocessing, # Required for audio ) ``` 4. **Use supported audio formats:** - WAV, MP3, FLAC, OGG - Audio is automatically resampled to 48kHz --- ### Face Recognition Errors **Symptoms:** ``` Query input produced 3 embeddings - query input must produce exactly 1 embedding NoPreprocessing is not supported for face recognition models ``` **Causes:** - Multiple faces detected in query image - Using `NoPreprocessing` with face models **Solutions:** 1. **For queries, use single-face images:** ```python # Crop to single face before querying from PIL import Image img = Image.open("group_photo.jpg") # Crop to region containing target face face_crop = img.crop((x1, y1, x2, y2)) face_crop.save("single_face.jpg") ``` 2. **Always use ModelPreprocessing:** ```python Set( store="faces_store", inputs=[...], preprocess_action=PreprocessAction.ModelPreprocessing, # Required ) ``` 3. **Adjust confidence threshold for detection:** ```python # For group photos with small faces, lower threshold GetSimN( store="faces_store", search_input=..., model_params={"confidence_threshold": "0.3"}, # More inclusive ) # For ID verification, raise threshold GetSimN( store="faces_store", search_input=..., model_params={"confidence_threshold": "0.9"}, # More strict ) ``` 4. **Choose the right face model:** - `buffalo-l`: Higher accuracy, 512-dim, **non-commercial only** - `sface-yunet`: Lighter, 128-dim, commercially usable (Apache/MIT) --- ## Persistence Issues ### Persistence File Won't Load **Symptoms:** ``` Failed to load persistence file Corruption detected ``` **Diagnostic Steps:** 1. **Check file permissions:** ```bash ls -l /path/to/persistence.dat ``` 2. **Verify file size vs allocator:** ```bash # File size du -h persistence.dat # Allocator must be >2x file size ``` **Solutions:** 1. **Increase allocator size:** ```bash # If persistence file is 5 GB, use at least 10 GB allocator ahnlich-db run \ --enable-persistence \ --persist-location /path/to/data.dat \ --allocator-size 10737418240 # 10 GB ``` 2. **Skip corrupted persistence:** ```bash ahnlich-db run \ --enable-persistence \ --persist-location /path/to/data.dat \ --fail-on-startup-if-persist-load-fails false # Continue without persistence ``` 3. **Backup and delete:** ```bash # Backup cp persistence.dat persistence.dat.backup # Start fresh rm persistence.dat ahnlich-db run --enable-persistence --persist-location persistence.dat ``` 4. **Check disk space:** ```bash df -h /path/to/persistence/ ``` --- ### Data Lost After Restart **Cause:** Persistence not enabled. **Solution:** Enable persistence when starting server: ```bash ahnlich-db run \ --enable-persistence \ --persist-location /var/lib/ahnlich/db.dat \ --persistence-interval 300000 # 5 minutes ``` --- ## Debugging Tips ### Enable Detailed Logging ```bash # Set log level ahnlich-db run --log-level debug # Or specific modules ahnlich-db run --log-level "info,ahnlich_db=debug,hf_hub=warn" ``` ### Enable Distributed Tracing ```bash # Start Jaeger docker run -d \ -p 16686:16686 \ -p 4317:4317 \ jaegertracing/all-in-one:latest # Start server with tracing ahnlich-db run \ --enable-tracing \ --otel-endpoint http://localhost:4317 # View traces at http://localhost:16686 ``` ### Use CLI for Testing ```bash # Interactive mode ahnlich --agent DB --host 127.0.0.1 --port 1369 # Test commands PING INFOSERVER LISTSTORES SCHEMA public ``` ### Check Server Health ```bash # Process status ps aux | grep ahnlich # Resource usage top -p $(pgrep ahnlich) # Network connections netstat -anp | grep ahnlich # Open files lsof -p $(pgrep ahnlich) ``` --- ## Getting More Help Still having issues? Try these resources: 1. **Check Error Codes**: [Error Codes Reference](/docs/reference/error-codes) 2. **Read Configuration Docs**: [Configuration Reference](/docs/reference/configuration) 3. **Enable Tracing**: See detailed request flow 4. **Community**: [WhatsApp Group](https://chat.whatsapp.com/E4CP7VZ1lNH9dJUxpsZVvD) 5. **GitHub**: [Report Issues](https://github.com/deven96/ahnlich/issues) When reporting issues, include: - Error messages (full text) - Server version - Configuration flags used - Steps to reproduce - Server logs (with `--log-level debug`)