Overview
SBproxy can run two AI-gateway features on local models instead of paid APIs: the embedding semantic cache, which vectorizes prompts to serve near-duplicate requests from cache, and prompt-injection v2, which classifies prompts for injection attempts. Both run on a self-contained inference engine with no Python and no native ONNX Runtime to install.
What makes it different
- No per-call cost. Embedding and classification happen on-box, not against a metered API. A semantic cache hit avoids the upstream completion entirely.
- No prompt egress. The prompt is vectorized locally and never leaves your network. This is the difference between a semantic cache you can run in a regulated VPC and one you cannot.
- Air-gap ready. Models are operator-supplied files you download once and pin by SHA-256. Nothing phones home.
- Two deployment modes. Run the model in a co-located sidecar (recommended, so a bad model can only restart the sidecar) or in-process for a true single binary.
- It pays for itself, visibly. The cache reports tokens and dollars saved against the same cost table used for spend, so a dashboard shows spend and savings side by side and they reconcile.
Models
The default embedding model is all-MiniLM-L6-v2 (384-dimensional, Apache-2.0, ~90 MB). The default classifier is protectai/deberta-v3-base-prompt-injection-v2 (Apache-2.0, ~70 MB int8). Both are small enough to bake into a container image or copy onto an air-gapped host. Download them once:
mkdir -p /var/lib/sbproxy/models/minilm
curl -fSL -o /var/lib/sbproxy/models/minilm/model.onnx \
https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/onnx/model.onnx
curl -fSL -o /var/lib/sbproxy/models/minilm/tokenizer.json \
https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/tokenizer.json
Run the sidecar
The sidecar serves both embed and classify over gRPC (TCP or a Unix domain socket). The proxy connects lazily, so the sidecar does not have to be up before the proxy starts:
sbproxy-classifier-sidecar \
--listen 127.0.0.1:9440 \
--embed-model all-MiniLM-L6-v2=/var/lib/sbproxy/models/minilm/model.onnx:/var/lib/sbproxy/models/minilm/tokenizer.json
For a co-located deployment, use --listen-uds /run/sbproxy/classifier.sock to skip the loopback TCP round trip.
Local semantic cache
Point the semantic cache at the sidecar with source: sidecar. On a miss the proxy vectorizes the prompt, scans the cache, and replays the closest cached response when cosine similarity meets threshold. If the sidecar is unreachable the lookup is treated as a miss and the request proceeds uncached, so the cache never wedges a request.
origins:
ai.example.com:
action:
type: ai_proxy
providers:
- name: openai
api_key: ${OPENAI_API_KEY}
models: [gpt-4o]
semantic_cache:
enabled: true
threshold: 0.85 # cosine similarity for a near-duplicate hit
ttl_secs: 3600
max_entries: 1024
source: sidecar
sidecar:
endpoint: http://127.0.0.1:9440
model: all-MiniLM-L6-v2
timeout_ms: 500
The semantic cache is configured on each AI origin under action.semantic_cache. The default source is provider, which calls an AI provider's /v1/embeddings API. Existing configs are unchanged.
First-class prompt-injection classification
Select the sidecar detector in the prompt_injection_v2 policy. The default detector is a zero-dependency regex pass; choosing sidecar runs the classifier:
origins:
ai.example.com:
action:
type: ai_proxy
providers:
- name: openai
api_key: ${OPENAI_API_KEY}
models: [gpt-4o]
policies:
- type: prompt_injection_v2
threshold: 0.8
action: block
detector: sidecar
detector_config:
endpoint: http://127.0.0.1:9440
model: prompt-injection
injection_label: INJECTION
timeout_ms: 250
fail_closed: false # a sidecar outage degrades to "clean" (allow)
In-process, single binary
For a single binary, run either feature in-process. This loads a model into the proxy's address space, so it is gated behind explicit config and a max_model_bytes guard. Prefer the sidecar for isolation.
origins:
ai.example.com:
action:
type: ai_proxy
providers:
- name: openai
api_key: ${OPENAI_API_KEY}
models: [gpt-4o]
semantic_cache:
enabled: true
threshold: 0.85
source: inprocess
inprocess:
model: all-MiniLM-L6-v2
model_path: /var/lib/sbproxy/models/minilm/model.onnx
tokenizer_path: /var/lib/sbproxy/models/minilm/tokenizer.json
max_model_bytes: 209715200 # 200 MB guard
Remote OpenAI-compatible embeddings
To vectorize through an OpenAI-compatible /v1/embeddings endpoint that is not one of the origin's chat providers, set source: openai. Point it at another sbproxy that fronts an embedding model, at OpenRouter, or at a hosted provider, each with its own URL and key. This source is not on-box, but it keeps the embedding endpoint decoupled from the chat providers.
origins:
ai.example.com:
action:
type: ai_proxy
providers:
- name: openai
api_key: ${OPENAI_API_KEY}
models: [gpt-4o]
semantic_cache:
enabled: true
threshold: 0.85
source: openai
openai:
base_url: https://openrouter.ai/api/v1 # or http://sbproxy.internal/v1
api_key: ${EMBEDDING_API_KEY}
model: text-embedding-3-small
timeout_ms: 2000
Auth defaults to Authorization: Bearer. For endpoints that expect a different header (Azure api-key, an x-api-key gateway), set auth_header and clear auth_prefix; endpoints that need extra headers, such as OpenRouter's HTTP-Referer and X-Title, take a headers list. On any embedding error the lookup degrades to an uncached upstream call.
Metrics
Local inference and the semantic cache emit metrics attributed per tenant:
sbproxy_semantic_cache_results_total{tenant,origin,source,result}- hit / miss / error rate by embedding source.sbproxy_inference_requests_total{kind,backend,model,result}- embed and classify call counts.sbproxy_inference_duration_seconds{kind,backend,model}- embed and classify latency.sbproxy_ai_tokens_saved_total{tenant,origin,model,kind}- tokens a cache hit avoided.sbproxy_ai_cost_saved_micros_total{tenant,origin,model}- micro-USD a cache hit avoided.
If near-duplicates score just under your threshold, watch sbproxy_ai_semantic_cache_similarity_bucket and tune threshold from the observed distribution.