Skip to content

refactor(llm): consolidate OpenAI-compatible providers onto one base (#11517) - #11747

Merged
mrveiss merged 1 commit into
Dev_new_guifrom
issue-11517
Jul 15, 2026
Merged

mrveiss merged 1 commit into
Dev_new_guifrom
issue-11517

Conversation

@mrveiss

@mrveiss mrveiss commented Jul 15, 2026

Copy link
Copy Markdown
Owner

Closes #11517

Thinking Path

Five near-identical OpenAI-compatible providers each hand-rolled request-building, response-mapping, tool-call parsing, and streaming — and had drifted. New OpenAICompatibleProvider(BaseProvider) owns the shared flow; each provider is a thin subclass carrying only its genuine deltas. Module paths, class names, and provider_name ids are unchanged (config compatibility), so registry/adapters/re-exports needed zero changes.

What Changed

  • New llm_shared/providers/openai_compatible.py (283 lines) replaces ~750 lines of duplicated flow across openai.py, groq.py, custom_openai.py, openrouter.py, nous_portal.py (net −381).
  • Per-provider deltas preserved as explicit hooks: OpenAI (SSOT key chain, reasoning-effort feat(chat): reasoning effort configuration — set effort level per conversation for reasoning models #9017, sorted params perf(llm): deterministic ordering of cache-relevant payload fields before serialization #7368), Groq (AsyncGroq client, historical top_p omission), CustomOpenAI (mandatory base_url, instance_name), OpenRouter (gateway URL, penalty forwarding), NousPortal (HF token chain, 2048 default max_tokens).
  • Drift bugs the shared flow fixes (all latent, verified by harness): openrouter/nous crashed with AttributeError on dict messages on EVERY call; nous passed nonexistent LLMResponse kwargs (TypeError, violating must-not-raise); openrouter/nous lacked tool wiring/finish_reason/usage propagation; openai/groq streams silently dropped top_p/stop/tools; openrouter/nous streams yielded literal "Error:" text into output instead of raising.

Verification

  • Seams: breaker untouched at BaseProvider._guarded_completion; _chat_completion_impl returns via LLMResponse.error; TestNoMethodLevelBreaker guard passes (repo rule: method-level breaker decorators are inert).
  • 135/139 provider tests pass real-loaded; the 4 failures proven pre-existing by replaying the OLD provider files through the identical harness (same failures). mistral/ollama 26 pass; startup imports 344 pass (agent + independent re-run). py_compile/flake8/black clean.
  • Independent: all 6 provider modules real-import cleanly in the worktree.
  • Note: these test files error at collection on clean base too (root conftest stubs llm_shared.providers) — the real-load runs are the stricter verification.

Model Used

Claude Fable 5 (subagent: general-purpose)

…11517)

One OpenAICompatibleProvider now owns the request-building, response-mapping,
tool-call parsing, streaming, and model-listing flow shared by the five
providers speaking the OpenAI chat-completions dialect. Each concrete provider
is a thin subclass carrying only its genuine deltas:

- openai: SSOT key fallback chain, optional base_url, reasoning-effort
  mapping (#9017), prompt-cache payload normalisation (#7368)
- groq: groq SDK client, historical omission of top_p
- custom_openai: mandatory base_url, permissive api_key default,
  per-instance provider naming
- openrouter: gateway base URL default, api_kwargs penalty forwarding,
  empty static model fallback
- nous: HF token chain, HF inference base URL, 2048 default max_tokens,
  api_kwargs penalty forwarding, NOUS_MODELS fallback

Drift bugs fixed by the shared flow (#10582-class divergence):
- openrouter/nous accessed .role/.content on dict messages (AttributeError
  on every call); nous passed nonexistent model_name=/provider_name= kwargs
  to LLMResponse (TypeError violating the must-not-raise contract)
- openrouter/nous gain tool wiring, finish_reason/processing_time/
  request_id/total_tokens population, and reasoning_content capture
- streaming now builds the same payload as non-streaming (top_p/stop/tools
  were silently dropped from openai/groq streams) and raises on error
  instead of yielding error text into the output stream (openrouter/nous)

Breaker seam untouched: circuit-breaker accounting stays in
BaseProvider._guarded_completion (GH#11488); _chat_completion_impl still
returns errors via LLMResponse.error. Module paths, class names, provider
ids, and registry/adapter construction sites are unchanged.
@github-actions

Copy link
Copy Markdown
Contributor

✅ SSOT Configuration Compliance: Passing

🎉 No hardcoded values detected that have SSOT config equivalents!

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