InferCore is an open-source AI Inference Control Plane: a decision layer that sits above model serving and data plane systems.
It provides intelligent routing, cost-aware decisions, fallback and degrade orchestration, AI-native SLO signals, multi-tenant policy enforcement, and observability / scaling-signal exports.
InferCore is not a model server. It does not run token generation and does not replace vLLM, Triton, Ray Serve, or KServe.
Website: infercore.dev — product overview, blog, and links to this repository.
InferCore fills the missing system decision layer in typical serving stacks:
| Goal | Meaning |
|---|---|
| Route | Send different requests to different backends/models |
| Protect | Degrade gracefully on timeout, overload, or failure |
| Optimize | Trade off latency, quality, and cost with explicit policy |
| Signal | Export TTFT, TPOT, queue depth, fallback rate, and related AI-native metrics |
| Isolate | Tenant priority, quota, budget, and fairness |
request_type: rag; file-backed knowledge bases in v1.5)Client / SDK
↓
[ InferCore Gateway ]
↓
[ Request Normalizer ]
↓
[ Policy Engine ] ← tenant / budget / priority / guardrails
↓
┌──────────────────────────────────────────────────────────────────────────┐
│ Reliability · overload admission │
│ • reliability.overload.queue_limit + action: reject (503) | degrade │
│ • Degrade: skip cost optimization, annotate response; router sees │
│ overload state (queue depth / degrade) for routing hints │
└──────────────────────────────────────────────────────────────────────────┘
↓
[ Router Engine ] ← route rules / health-aware backend choice / cost-aware pick
↓
┌──────────────────────────────────────────────────────────────────────────┐
│ Optional RAG (request_type=rag, when configured) │
│ retrieve (KB) → rerank (e.g. noop) → merge into input.* │
└──────────────────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────────────────┐
│ Reliability · execution & fallback │
│ • ExecuteWithFallback: primary backend then fallback_rules chain │
│ • Triggers: timeout, backend_unhealthy, backend_error │
│ • Per-backend timeout_ms; bounded by gateway request_timeout_ms │
│ • Streaming: stream_fallback_enabled when options.stream │
└──────────────────────────────────────────────────────────────────────────┘
↓
[ Execution Adapter Layer ] ← OpenAI-compatible (e.g. OpenAI, vLLM) / Gemini / Mock / …
↓
Inference Backends
Parallel outputs:
- Metrics exporter (Prometheus text on /metrics)
- Trace / event exporters (configurable telemetry backends)
- In-memory AI SLO engine (bounded store; snapshots on responses)
- Scaling signals (/status.scaling_signals + infercore_scaling_* gauges)
POST /infer) — same endpoint for inference, RAG, and agent (preview).reliability.overload) may reject or degrade before routing.request_type is rag and knowledge_bases are configured, InferCore runs retrieve then rerank, and merges retrieved text into the payload (e.g. input.retrieved_context) before the model call./status, Prometheus).max_concurrency hints from config — intended for HPA/KEDA or custom autoscalers (per-replica unless you add shared state).This repository includes:
api/openapi.yaml)internal/inferexecdocs/ (architecture, observability, load testing, streaming, retrieval adapters, offline tooling, KB registry roadmap)POST /infer: request_type (inference | rag | agent), pipeline_ref (defaults: inference/basic:v1, rag/basic:v1, agent/basic:v0), and context (RAG: query, knowledge_base; agent: tool hints for policy).ledger.enabled and driver: memory, sqlite (path), or postgres (dsn). Persists full normalized AIRequest as ai_request_json (plus input/context JSON), policy_snapshot after routing, and per-step records for audit and replay.request_type: rag and list knowledge_bases with type: file (local demo), http (JSON microservice), opensearch / elasticsearch, or meilisearch. See docs/retrieval-adapters.md. Execution order: policy → overload admission → route → retrieve → rerank (rag.rerank.type, default noop) → chat completion. User text via input.text and/or context.query; chunks merge into input.retrieved_context.request_type: agent is accepted for agent-ready ingress and policy (tenant limits such as max_steps, max_tool_calls, allowed_tools when features.agent_enabled is true). There is no tool loop or agent executor in this release: the gateway returns 501 agent_not_implemented. Do not describe InferCore as a full “agent platform” yet.infercore serve # HTTP gateway (default when no subcommand)
infercore trace <request_id> # dump ledger request + steps JSON
infercore replay <request_id> --mode exact|current # single response JSON
infercore replay id1 id2 --mode current # batch: NDJSON lines (or --ids-file)
infercore ledger export-eval <request_id>... -o items.json # eval dataset from ai_request_json
infercore eval run --dataset examples/queries.json --pipeline inference/basic:v1
# With server API key auth (or env INFERCORE_API_KEY):
infercore eval run --dataset examples/queries.json --api-key "$INFERCORE_API_KEY"
Replay and ledger commands use the CLI and internal/replay; the HTTP gateway does not expose a replay API (see docs/offline-tooling.md).
infercore replay)exact — Uses the policy snapshot in the ledger to force the primary backend and disables fallback. RAG requests still re-run retrieve + rerank; if knowledge-base files or retrieval config changed, results may differ from the original online call. Routing does not use live health probes in this mode.current — Re-evaluates policy and router. The replay harness treats all backends as healthy (see internal/replay); production /infer uses cached health probes for routing, so routes can differ from a current replay on the same machine.When telemetry.tracing_enabled is true, OTLP/log exporters receive infer_request (whole handler) and per-step infer_step spans (step_type, backend, result).
Use this as a rough guide—not every deployment fits two buckets. Items reflect keys in configs/infercore.example.yaml.
| Area | Minimal (local demo / first run) | Production-oriented |
|---|---|---|
| Server | server.host / server.port; set server.request_timeout_ms to a value that covers your slowest expected path. |
Tune server.http.* timeouts if you use long-lived connections; align request_timeout_ms with upstream SLAs and load balancer idle timeouts. |
| Backends | At least one backend in backends with a valid type (mock is enough to learn the control plane). |
Real adapters (vllm, openai_compatible, …): correct endpoint, timeout_ms, max_concurrency, auth (api_key / headers), health_path for your provider. |
| Routing | routing.default_backend must name an entry in backends. Add routing.rules only when you need more than the default. |
Explicit rules per tenant/task, and verify default_backend as safe last resort. |
| Tenants | Optional; policy still runs—ensure any tenant_id you send in JSON is consistent with how you intend to use policy. |
Define tenants so quotas, classes, and limits match your product; keep tenant IDs stable across environments. |
| Reliability | Defaults (reliability.overload, fallback_rules) from the example are fine to start. |
Model fallback_rules on real failure modes; choose overload.action: reject vs degrade for your capacity story; set stream_fallback_enabled deliberately. |
| Telemetry | exporter: log and metrics on is enough to debug. |
OTLP (otlp-http / otlp-http-json) with otlp_endpoint, sensible batching; keep metrics_enabled / tracing_enabled aligned with observability cost. |
| Ledger | ledger.enabled: false or driver: memory for throwaway data. |
sqlite with a persistent path, or postgres with dsn, for audit, infercore trace / replay, and multi-replica shared state. |
| Ingress security | Leave infercore_api_key unset for local use. |
Set infercore_api_key (or INFERCORE_API_KEY); restrict network access; use the same key for infercore eval run --api-key / INFERCORE_API_KEY. |
| RAG | Omit knowledge_bases unless you call request_type: rag. |
File-backed KB paths mounted in containers; document context.knowledge_base and query fields for clients. |
| Health & SLO | Defaults for health_cache_ttl_ms / SLO store are fine. |
Tune health probe cadence vs routing churn; size slo store for your traffic if you rely on /status or metrics. |
Minimal viable config (conceptually): one backend + routing.default_backend pointing to it + valid YAML for telemetry exporter and reliability overload action. Everything else is layered behavior.
go run ./cmd/infercore
The service starts on :8080.
Use a custom config file:
INFERCORE_CONFIG=./configs/infercore.example.yaml go run ./cmd/infercore
Secrets in YAML: placeholders like ${OPENAI_API_KEY} are expanded at load time from the process environment (os.Getenv). Export the variable before starting, or prefix the command: OPENAI_API_KEY=sk-... infercore serve --config=.... Unset variables become empty strings. Only ${VAR} is supported (ASCII identifier after VAR).
make help # list targets
make all # fmt, vet, test
make test
make build # writes bin/infercore
make run # CONFIG=... optional (default configs/infercore.example.yaml)
make docker-build # image tag: infercore:local
docker run --rm -p 8080:8080 infercore:local
Mount your own config:
docker run --rm -p 8080:8080 \
-v "$(pwd)/configs/my.yaml:/app/config.yaml:ro" \
-e INFERCORE_CONFIG=/app/config.yaml \
-e OPENAI_API_KEY=... \
infercore:local
(If the YAML uses ${OPENAI_API_KEY}, pass it with -e so expansion works inside the container. Otherwise, skip -e OPENAI_API_KEY=... )
GitHub Actions workflow (on push/pull_request to main or master): .github/workflows/ci.yml runs go vet and go test ./....
Health:
curl -s http://localhost:8080/health
Status:
curl -s http://localhost:8080/status
Infer (plain inference):
curl -s -X POST http://localhost:8080/infer \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "team-a",
"task_type": "simple",
"priority": "normal",
"input": {"text": "Summarize this article"},
"options": {"stream": false, "max_tokens": 256}
}'
RAG (requires knowledge_bases in config, e.g. pointing at examples/kb):
```bash curl -s -X POST http://localhost:8080/infer \
$ claude mcp add InferCore \
-- python -m otcore.mcp_server <graph>