Skip to content

LLM providers

Every LLM call in Jarvis — capture triage, homelab proposals, RAG answers — goes through a single seam (agent-runtime/src/jarvis_agent/llm.py). Only one thing decides which provider that seam uses:

LLM_PROVIDER=stub

Providers

Provider LLM_PROVIDER Network Notes
Stub stub (default) none Offline, deterministic. No key required. complete() echoes the prompt; propose() returns an empty value per declared parameter type. Used automatically until you configure a real provider.
DeepSeek deepseek yes OpenAI-compatible API. Requires DEEPSEEK_API_KEY.

Env vars

Env var Required Default Purpose
LLM_PROVIDER no stub Selects the provider (stub or deepseek)
DEEPSEEK_API_KEY only for deepseek DeepSeek API key
DEEPSEEK_BASE_URL no https://api.deepseek.com OpenAI-compatible base URL
LLM_MODEL no deepseek-chat Model id passed to the provider

Switch to DeepSeek

Add the key to .env and restart the stack:

LLM_PROVIDER=deepseek
DEEPSEEK_API_KEY=sk-...

Leaving LLM_PROVIDER unset (or stub) keeps every module fully offline — no key needed, no request ever leaves the box.

Every cloud call is audited

The propose_audited chokepoint

Modules never call an LLMProvider directly for a "propose" step — every capture and homelab LLM call goes through jarvis_agent.audit.propose_audited, the single cloud chokepoint:

  • Fail-closed — if any item in the call is sensitive, the provider is never touched; propose_audited raises RedactionRefusal instead.
  • Redacted — the system and user text sent to the provider is passed through the shared redactor first.
  • One repair round — if the returned proposal fails validation, the provider is asked exactly once more, with the violations listed as a repair note.
  • Logged — every dispatched call is recorded to the caller's own *_llm_log collection (e.g. hl_llm_log, conv_llm_log), with the post-redaction payload preview and a token estimate — never the raw pre-redaction text.

Capture's poll and backfill paths make zero cloud calls — normalization and routing there are plain code, not LLM calls, so LLM_PROVIDER doesn't affect them at all.