LearnNewsExamplesServices
Frontmatter
id12741
titleMake KB ask synthesis provider-routed and local-capable
stateClosed
labels
bugairegressionarchitecturemodel-experience
assigneesneo-opus-vega
createdAtJun 8, 2026, 9:59 AM
updatedAtJun 8, 2026, 3:20 PM
githubUrlhttps://github.com/neomjs/neo/issues/12741
authorneo-gpt
commentsCount1
parentIssue12740
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 8, 2026, 3:20 PM

Make KB ask synthesis provider-routed and local-capable

Closed v13.0.0/archive-v13-0-0-chunk-17 bugairegressionarchitecturemodel-experience
neo-gpt
neo-gpt commented on Jun 8, 2026, 9:59 AM

Context

The 2026-06-08 Gemini cost incident showed that Agent OS cheap synthesis surfaces must not default to remote paid calls just because a local process has GEMINI_API_KEY in its environment. Memory Core summary paths at least route through aiConfig.modelProvider; Knowledge Base ask synthesis does not.

V-B-A evidence:

  • ai/services/knowledge-base/SearchService.mjs:54 reads process.env.GEMINI_API_KEY directly.
  • ai/services/knowledge-base/SearchService.mjs:56 constructs new GoogleGenerativeAI(apiKey) directly.
  • ai/services/knowledge-base/SearchService.mjs:57 creates the model from aiConfig.modelName only, not a provider selector.
  • ai/services/knowledge-base/SearchService.mjs:174 throws GEMINI_API_KEY is required for RAG features. when the direct Gemini model is absent.
  • ai/services/knowledge-base/SearchService.mjs:235 calls this.model.generateContent(prompt) for synthesis.
  • ai/services/memory-core/SessionService.mjs:37 already has a provider-routed buildChatModel() precedent for Gemini, OpenAI-compatible, and Ollama chat paths.
  • ai/mcp/server/knowledge-base/config.template.mjs:305 still carries a KB-local modelName leaf for Gemini defaults, but no provider selector equivalent to Memory Core.

Live latest-open sweep: checked latest 60 open issues at 2026-06-08T07:57:30Z via live GitHub and GitHub Workflow MCP. Existing adjacent tickets are #12737 (return degraded refs when synthesis fails), #12719 (candidate weighting), #12715 (lexical-rescue I/O), #12450 (Memory Core query_summaries), and #12456 (AiConfig SSOT parent). Targeted live search for ask knowledge base provider configurable local capable returned no equivalent open issue.

Semantic duplicate sweep: used non-synthesis query_documents(type='ticket') instead of ask_knowledge_base because ask synthesis is the incident surface. It surfaced older KB/provider issues (#10097, #10103, #9893/#9900) but no open provider-routing leaf for KB ask synthesis.

The Problem

ask_knowledge_base is the default Neo knowledge tool, but its synthesis leg is still Gemini-only. That creates three problems:

  1. Local harnesses can spend remote Gemini tokens for every ask synthesis when a key is inherited.
  2. NEO_MODEL_PROVIDER=openAiCompatible or ollama does not make KB ask synthesis local, even though Memory Core summary paths can route locally.
  3. Removing the Gemini key degrades ask into an exception path instead of local synthesis or a controlled degraded response.

#12737 covers preserving ranked references when synthesis fails. This ticket covers the upstream routing problem: KB ask synthesis should be provider-configurable and local-capable by construction.

The Architectural Reality

  • Owning service: ai/services/knowledge-base/SearchService.mjs.
  • Current KB config: ai/mcp/server/knowledge-base/config.template.mjs owns KB-local knobs and currently only exposes modelName for Gemini synthesis.
  • Existing provider precedent: ai/services/memory-core/SessionService.mjs exports buildChatModel() and wraps OpenAI-compatible/Ollama providers into the same generateContent envelope used by downstream summary code.
  • ADR 0019 boundary: provider selection should read resolved AiConfig leaves at use sites and avoid parallel aliases unless a successor decision explicitly changes the SSOT.

The Fix

Route KB ask synthesis through a provider selector that supports local OpenAI-compatible/Ollama chat providers as well as explicit Gemini.

Expected implementation shape:

  • Add or reuse a provider selector for KB ask synthesis that defaults local for local harnesses and supports explicit remote Gemini opt-in.
  • Avoid duplicating Memory Core provider-wrapper logic if the existing buildChatModel() can be safely shared without creating an import-boundary violation.
  • Preserve #12737's degraded-reference behavior as a sibling contract: provider unavailability after retrieval should not erase ranked references.
  • Update focused KB SearchService tests to prove provider dispatch without live Gemini.
  • Keep retrieval/ranking behavior unchanged.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
SearchService.ask() synthesis provider SearchService.mjs + ADR 0019 provider-routing discipline Synthesis uses configured chat provider, with local-capable routing and explicit Gemini opt-in If no synthesis provider is available, return/compose the #12737 degraded refs path JSDoc on the widened provider contract Unit tests stub local provider and Gemini absence
Knowledge Base config template ai/mcp/server/knowledge-base/config.template.mjs KB ask synthesis has a provider selector or reads a Tier-1 provider leaf rather than Gemini-only modelName Existing modelName remains only for explicit Gemini mode Config JSDoc Config import test / focused spec
MCP ask_knowledge_base behavior Tool contract: synthesized answer plus references Local deployments can use ask_knowledge_base without a Gemini key Degraded refs returned when synthesis cannot run PR body and any OpenAPI schema updates if needed SearchService unit coverage
Cost-safety boundary #12740 incident epic Presence of GEMINI_API_KEY alone does not force remote KB ask synthesis Remote providers require explicit provider opt-in PR evidence Test with key set but local provider selected

Decision Record impact

aligned-with ADR 0019. If implementation finds KB needs a distinct kbSynthesisProvider leaf instead of the shared chat provider, that divergence must be justified against ADR 0019 in the PR before adding a second provider axis.

Acceptance Criteria

  • SearchService.ask() no longer constructs Gemini directly as its only synthesis model.
  • KB ask synthesis can run through a local OpenAI-compatible or Ollama provider without GEMINI_API_KEY.
  • Remote Gemini synthesis remains available only when explicitly configured for that role.
  • A test proves GEMINI_API_KEY presence alone does not override a configured local synthesis provider.
  • A test proves missing/unavailable synthesis falls through to the #12737 degraded-reference behavior rather than losing references.
  • Retrieval/no-result behavior remains unchanged.

Out of Scope

  • Query candidate weighting (#12719).
  • Lexical-rescue I/O cost (#12715).
  • Degraded-reference fallback itself (#12737), except as the fallback contract this ticket composes with.
  • Memory Core mini-summary provider defaults; those are a sibling #12740 leaf.
  • Removing Gemini support.

Avoided Traps

  • Treating GEMINI_API_KEY absence as the only safety mechanism. Keys can reappear in inherited harness environments.
  • Copying provider-wrapper logic into KB without checking the existing buildChatModel() precedent.
  • Bundling ranking changes into the provider-routing PR.

Related

Parent: #12740 Related: #12737, #12719, #12715, #12456

Origin Session ID: e8f07ef9-ef7e-4815-8ff4-7abe13720621 Retrieval Hint: "SearchService ask provider routed local capable GEMINI_API_KEY direct GoogleGenerativeAI"

tobiu referenced in commit 138d348 - "fix(knowledge-base): provider-route ask synthesis (#12741) (#12759) on Jun 8, 2026, 3:20 PM
tobiu closed this issue on Jun 8, 2026, 3:20 PM