Skip to content

Make the model a config string: LiteLLM replaces the Ollama SDK - #277

Merged
Sbussiso merged 1 commit into
masterfrom
agent-litellm
Sep 9, 2026
Merged

Make the model a config string: LiteLLM replaces the Ollama SDK#277
Sbussiso merged 1 commit into
masterfrom
agent-litellm

Conversation

@Sbussiso

@Sbussiso Sbussiso commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Phase 2. LLM_MODEL now selects the model — ollama_chat/qwen3.5:cloud today, anthropic/claude-sonnet-5 for a bring-your-own-key self-hoster, and eventually a SourceBox model behind an OpenAI-compatible endpoint. That last one is the point: this is the seam a custom model plugs into.

LiteLLM replaces the ollama SDK rather than sitting beside it — it speaks Ollama natively via ollama_chat/, so one provider path instead of two.

Existing deployments need no env changes

LLM_* wins if set; otherwise the OLLAMA_* trio is mapped onto it. Verified both ways, including that an override doesn't leak the Ollama host to a hosted provider.

OLLAMA_API_KEY stops being a required field, so there's a new validator that refuses to boot with no credential. That guard used to be a side effect of the field being mandatory — losing it would have turned a config mistake into a run that fetches work, calls the model, and errors.

The translation was the real work

I under-sold this earlier as "42 lines, a small swap." Ollama and OpenAI-shaped APIs disagree structurally:

Ollama OpenAI-shaped
tool result tool_name tool_call_id from the call
tool arguments dict JSON string
images images: [b64] on any message data: URI parts on a user message — a tool message can't carry one

llm.py now owns every provider-shaped detail; the loop builds messages only through its helpers. Swapping providers again touches one file.

16 tests cover the translation, because every failure mode in it is quiet: a wrongly-shaped message doesn't raise, it just means the model never saw the camera frame, or can't match a result to the call it made. The run completes, reports no_action, and looks fine.

Cost, accepted deliberately

+24 packages / ~160 MB (boto3 and tokenizers are hard requirements of litellm, not optional extras). The web process group carries that too, since Fly gives one image per app — the alternative is two images, which is the separate-app arrangement we just finished removing.

import litellm also costs ~2s, paid on every cold start against Command Center's 5s webhook timeout. Measuring that against the real wakeup path after deploy; the webhook is fire-and-forget with a re-fire loop, so it degrades rather than drops, but if it's tight I'll make the import lazy.

864 tests pass (848 + 16), ruff clean.

🤖 Generated with Claude Code

The agent hardcoded an Ollama client, so "which model runs Sentinel AI"
was a code change. It is now LLM_MODEL — `ollama_chat/qwen3.5:cloud`
today, `anthropic/claude-sonnet-5` for a bring-your-own-key self-hoster,
and eventually a SourceBox model behind an OpenAI-compatible endpoint.
That last one is the point: this is the seam a custom model plugs into.

LiteLLM replaces the ollama SDK outright rather than sitting beside it,
because it speaks Ollama natively via `ollama_chat/`. One provider path,
not two.

Existing deployments need NO env changes. LLM_* wins if set; otherwise
the OLLAMA_* trio is mapped onto it. Verified both directions, including
that an override does not leak the Ollama host to a hosted provider.
OLLAMA_API_KEY stops being a required field, so a new validator refuses
to boot with no credential at all — that check used to be a side effect
of the field being mandatory, and losing it would have turned a config
mistake into a run that fetches work, calls the model and errors.

The message translation is the real work, and I under-sold it earlier as
"42 lines". Ollama and OpenAI-shaped APIs disagree structurally:

  - tool results key off tool_call_id, not tool_name
  - tool arguments arrive as a JSON string, not a dict
  - images cannot ride on a `tool` message at all — they need a `user`
    message whose content is a parts list with a `data:` URI

So llm.py now owns every provider-shaped detail and the loop builds
messages only through its helpers. Swapping providers again touches one
file.

16 tests cover that translation because every failure mode in it is
QUIET: a wrongly-shaped message does not raise, it just means the model
never saw the camera frame or cannot match a result to the call it made.
The run completes, reports no_action, and looks fine.

Cost, accepted deliberately: +24 packages and ~160 MB, boto3 and
tokenizers included as hard requirements of litellm. The web process
group carries that too, since Fly gives one image per app. The
alternative is two images, which is the separate-app arrangement this
repo just finished removing. `import litellm` also costs ~2s, paid on
every cold start — measured against the wakeup path after deploy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Sbussiso
Sbussiso merged commit 8a8bfca into master Sep 9, 2026
12 checks passed
@Sbussiso
Sbussiso deleted the agent-litellm branch September 9, 2026 20:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant