fix(ai): Ollama compatibility fixes for local-first usage (#1854 takeover)

Four of the five fixes from #1854, rebased onto current master:

1. budget-tracker: FREE_LOCAL_CHAT_PROVIDERS (ollama, llama-server)
   returns zero pricing for local chat providers so --max-cost bounded
   brainstorm/lsd no longer TX2 hard-fails on local inference. Sibling
   to FREE_LOCAL_EMBED_PROVIDERS / FREE_LOCAL_RERANK_PROVIDERS.
2. brainstorm/judges: coerce numeric idea ids (5 -> "Idea 05") before
   the string type check; some models (e.g. Kimi K2 via Ollama) emit
   numeric ids and previously had every idea silently rejected.
3. think: the buildGracefulMessage "no LLM available" text now points
   at chat_model / expansion_model / anthropic_api_key, not just the
   Anthropic key, since that sentinel can fire for any provider.
4. ollama recipe: add chat + expansion touchpoints (llama3.3, qwen3,
   deepseek-r1; supports_tools, zero cost) so recipes can route
   reasoning calls to Ollama.

The fifth fix from #1854 (scope the NO_ANTHROPIC_API_KEY warning to
the anthropic provider) was superseded on master by the #1698 probe
work: probeChatModel only returns 'unavailable' for anthropic-no-key,
so non-Anthropic providers already get MODEL_NOT_USABLE instead.

Co-authored-by: Luna <luna@starstudiolabs.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-07-22 18:53:58 -07:00
co-authored by Luna Claude Fable 5
parent 1a449bf501
commit 10e087bbaf
4 changed files with 37 additions and 1 deletions
+14
View File
@@ -33,6 +33,20 @@ export const ollama: Recipe = {
// OLLAMA_NUM_PARALLEL config; no static cap to declare. v0.32 (#779).
no_batch_cap: true,
},
chat: {
models: ['llama3.3', 'qwen3', 'deepseek-r1'],
supports_tools: true,
supports_subagent_loop: false,
supports_prompt_cache: false,
cost_per_1m_input_usd: 0,
cost_per_1m_output_usd: 0,
price_last_verified: '2026-06-02',
},
expansion: {
models: ['llama3.3', 'qwen3', 'deepseek-r1'],
cost_per_1m_tokens_usd: 0,
price_last_verified: '2026-06-02',
},
},
setup_hint: 'Install Ollama from https://ollama.ai, then `ollama pull nomic-embed-text` and `ollama serve`.',
};
+5
View File
@@ -361,6 +361,11 @@ function isAxisScoreInRange(n: unknown): n is number {
function validateIdeaShape(raw: unknown): { id: string; scores: JudgeAxisScores; note: string } | null {
if (typeof raw !== 'object' || raw === null) return null;
const r = raw as Record<string, unknown>;
// Some models (e.g. Kimi K2) return numeric ids instead of string ids like "Idea 01".
// Coerce to string for compatibility.
if (typeof r.id === 'number') {
r.id = 'Idea ' + String(r.id).padStart(2, '0');
}
if (typeof r.id !== 'string') return null;
const note = typeof r.note === 'string' ? r.note : '';
const s = r.scores;
+17
View File
@@ -155,6 +155,20 @@ const FREE_LOCAL_EMBED_PROVIDERS: ReadonlySet<string> = new Set([
'llama-server',
]);
/**
* Provider id prefixes whose chat models run on local inference (electricity,
* not API tokens) and so price at $0. Without this, a `--max-cost`-bounded
* brainstorm/lsd configured for a local chat provider TX2 hard-fails because
* lookupPricing has no entry for them. Matched against the provider half
* of the `provider:model` string.
*
* Sibling to FREE_LOCAL_EMBED_PROVIDERS and FREE_LOCAL_RERANK_PROVIDERS.
*/
const FREE_LOCAL_CHAT_PROVIDERS: ReadonlySet<string> = new Set([
'ollama',
'llama-server',
]);
/**
* Look up `modelId` in the chat or embedding pricing maps. Returns a
* per-1M-token price tuple, or null when unknown.
@@ -201,6 +215,9 @@ function lookupPricing(modelId: string, kind: BudgetKind): ModelPricing | null {
if (kind === 'rerank' && providerId && FREE_LOCAL_RERANK_PROVIDERS.has(providerId)) {
return { input: 0, output: 0 };
}
if (kind === 'chat' && providerId && FREE_LOCAL_CHAT_PROVIDERS.has(providerId)) {
return { input: 0, output: 0 };
}
return null;
}
+1 -1
View File
@@ -815,7 +815,7 @@ function buildGracefulMessage(modelStr: string): {
type: 'message',
role: 'assistant',
model: modelStr,
content: [{ type: 'text', text: '(no LLM available — set anthropic_api_key via gbrain config or ANTHROPIC_API_KEY env)' }],
content: [{ type: 'text', text: '(no LLM available — set chat_model, expansion_model, or anthropic_api_key via gbrain config)' }],
usage: { input_tokens: 0, output_tokens: 0 },
stop_reason: 'end_turn',
};