Skip to content

refactor(providers): route deepseek, xai, and ollama through the shared OpenAI-compatible core#486

Merged
SantiagoDePolonia merged 1 commit into
mainfrom
refactor/providers-shared-compat-core
Jul 6, 2026
Merged

refactor(providers): route deepseek, xai, and ollama through the shared OpenAI-compatible core#486
SantiagoDePolonia merged 1 commit into
mainfrom
refactor/providers-shared-compat-core

Conversation

@SantiagoDePolonia

@SantiagoDePolonia SantiagoDePolonia commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

What

Kills the "full re-implementation" provider style (architecture review §4, roadmap item 5 phase 1). deepseek, xai, and ollama were the last three providers hand-rolling llmclient transport for OpenAI-shaped endpoints — ~350 lines re-doing what openai.CompatibleProvider already owns, with real drift (see Behavior notes).

How

  • Two opt-in hooks on CompatibleProviderConfig so provider quirks stop forcing method overrides:
    • AdaptChatRequest func(*core.ChatRequest) (*core.ChatRequest, error) — typed request rewrite applied on chat + stream before dispatch. Because it runs inside the engine, Responses-via-chat translation picks it up automatically (removes the "override ChatCompletion ⇒ must also override Responses" trap).
    • ChatRequestHeaders func(ctx, *core.ChatRequest) http.Header — context-derived per-request headers on chat calls only.
  • deepseek → embeds *openai.ChatCompatible (its surface matches exactly: chat, models, responses-via-chat, embeddings-unsupported, passthrough). The reasoning-effort remap becomes the AdaptChatRequest hook. 199 → 101 lines.
  • xai → composes *openai.CompatibleProvider with explicit delegation, because xAI's upstream lacks audio, passthrough, and response lifecycle management — embedding cannot subtract methods, and the router discovers capabilities by interface assertion. The Grok conversation-affinity header (X-Grok-Conv-Id) becomes the ChatRequestHeaders hook; generated conversation IDs are hash-identical to before (the anchor never included the stream flag). 368 → 269 lines, all remaining lines are genuine novelty.
  • ollama → composes *openai.CompatibleProvider for the /v1 side; the native /api/embed client, batch stubs, dual-URL SetBaseURL, and CheckAvailability stay. Still no passthrough.
  • CompatibleProvider now documents the three sanctioned wrapper shapes (embed ChatCompatible for chat-centric / embed CompatibleProvider for full-surface / compose + delegate for partial-surface) so the next provider doesn't invent a fourth style.

User-visible impact

None intended. Wire behavior is preserved except two conservative normalizations the other ten OpenAI-compatible providers already had:

  • xai model listings now default empty object fields to list/model (fill-if-empty only).
  • ollama chat streams are wrapped by EnsureChatCompletionSSE — pass-through for genuine SSE, converts buffered-JSON replies from upstreams that ignore stream:true into one SSE chunk + [DONE] (protects the misconfigured-OpenAI-server-as-ollama case).
  • Edge note: deepseek/xai/ollama chat now goes through the shared engine's OpenAI reasoning-model name matcher (o<digit>*/gpt-5*max_completion_tokens, drop temperature), same as groq/vllm/bailian/azure/openrouter today. Real DeepSeek/Grok/Ollama model IDs never match; only a local model deliberately named like an OpenAI reasoning model would see it.

Provider capability surfaces (unchanged)

Verified method-set parity: deepseek gains only the GetBaseURL getter (not a capability interface); xai and ollama gain nothing (delegation, not embedding).

Tests

  • 3 new engine tests: adapter applied on chat+stream and non-mutating, adapter error aborts before dispatch, per-request headers on chat only.
  • Existing deepseek/xai/ollama suites (414/917/954 lines, httptest-driven) pass unchanged except two field renames (provider.clientprovider.compat).
  • Full suite, -race on internal/providers/..., lint 0 issues, pre-commit green.

Remaining phases (separate PRs): compress the groq/oracle/bailian/vllm delegation blocks (facet surfaces or hook migration for bailian's max_tokens adapter), then remove the 16 test-only NewWithHTTPClient constructors.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved compatibility for several AI providers, making chat, streaming, model listing, and response requests work more consistently.
    • Fixed provider behavior so unsupported features are handled more clearly.
  • Refactor

    • Updated provider handling to use a shared compatibility layer, which should make integrations more reliable and easier to maintain.

…ed OpenAI-compatible core

deepseek, xai, and ollama were the last providers hand-rolling llmclient
transport for OpenAI-shaped endpoints, re-implementing bodies that
openai.CompatibleProvider already owns and silently drifting from it
(no buffered-JSON SSE normalization on ollama streams, no models-list
object defaulting on xai).

- CompatibleProviderConfig gains two opt-in hooks so quirks stop forcing
  method overrides: AdaptChatRequest (typed request rewrite, picked up
  automatically by Responses-via-chat translation) and ChatRequestHeaders
  (context-derived per-request headers on chat calls).
- deepseek embeds *ChatCompatible (its surface matches exactly); the
  reasoning-effort remap moves into the AdaptChatRequest hook.
- xai composes *CompatibleProvider with explicit delegation, preserving
  its narrowed surface (no audio, passthrough, or response lifecycle
  management); the Grok conversation-affinity header becomes the
  ChatRequestHeaders hook.
- ollama composes *CompatibleProvider for the /v1 side; the native
  /api/embed client, batch stubs, and CheckAvailability stay.
- CompatibleProvider now documents the three sanctioned wrapper shapes
  (embed ChatCompatible / embed CompatibleProvider / compose + delegate)
  and why embedding must not be used to fake capabilities.

Wire behavior is preserved except two conservative normalizations the
other ten OpenAI-compatible providers already had: xai model listings
get empty object fields defaulted, and ollama chat streams are wrapped
by EnsureChatCompletionSSE (pass-through for genuine SSE; converts
buffered-JSON replies from upstreams that ignore stream:true).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fcaf8032-880f-4c59-b4be-fb4406aab831

📥 Commits

Reviewing files that changed from the base of the PR and between daa836c and f4bc291.

📒 Files selected for processing (8)
  • internal/providers/deepseek/deepseek.go
  • internal/providers/ollama/ollama.go
  • internal/providers/ollama/ollama_test.go
  • internal/providers/openai/compatible_provider.go
  • internal/providers/openai/compatible_provider_test.go
  • internal/providers/xai/realtime.go
  • internal/providers/xai/xai.go
  • internal/providers/xai/xai_test.go

📝 Walkthrough

Walkthrough

This PR refactors DeepSeek, Ollama, and xAI providers to delegate chat completion, streaming, model listing, responses, and other endpoints to a shared OpenAI-compatible adapter (openai.ChatCompatible/CompatibleProvider), which is extended with AdaptChatRequest and ChatRequestHeaders hooks. Related tests are updated accordingly.

Changes

Shared Compatible Provider Delegation

Layer / File(s) Summary
Adapter hooks: request adaptation and per-request headers
internal/providers/openai/compatible_provider.go, internal/providers/openai/compatible_provider_test.go
CompatibleProviderConfig/CompatibleProvider add AdaptChatRequest and ChatRequestHeaders hooks; ChatCompletion/StreamChatCompletion invoke them before sending, with new tests for rewriting, error aborts, and header scoping.
DeepSeek: embed ChatCompatible adapter
internal/providers/deepseek/deepseek.go
Provider embeds *openai.ChatCompatible; New/NewWithHTTPClient construct it with DeepSeek-specific config and AdaptChatRequest; direct chat/stream/models/responses/passthrough implementations removed; Embeddings still errors as unsupported.
Ollama: compat for chat/models, native client for embeddings
internal/providers/ollama/ollama.go, internal/providers/ollama/ollama_test.go
Provider gains compat *openai.CompatibleProvider replacing the old client; ChatCompletion, StreamChatCompletion, ListModels delegate to compat; nativeClient retained for embeddings/batch with new header wiring; tests assert compat non-nil.
xAI: full delegation to compat provider
internal/providers/xai/xai.go, internal/providers/xai/realtime.go, internal/providers/xai/xai_test.go
Provider replaces client with compat *openai.CompatibleProvider via compatibleConfig; chat, streaming, models, responses, embeddings, batch, and file endpoints delegate to compat; realtime URL uses compat.GetBaseURL(); tests assert compat non-nil.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant CompatibleProvider
  participant UpstreamAPI

  Caller->>CompatibleProvider: ChatCompletion(req)
  CompatibleProvider->>CompatibleProvider: adaptChatRequest(req)
  alt adaptation succeeds
    CompatibleProvider->>CompatibleProvider: chatHeaders(ctx, adaptedReq)
    CompatibleProvider->>UpstreamAPI: POST /chat/completions
    UpstreamAPI-->>CompatibleProvider: response
    CompatibleProvider-->>Caller: ChatResponse
  else adaptation fails
    CompatibleProvider-->>Caller: error
  end
Loading

Possibly related PRs

  • ENTERPILOT/GoModel#242: Adds a new provider built on the same AdaptChatRequest/ChatRequestHeaders hooks in compatible_provider.go that this PR introduces.
  • ENTERPILOT/GoModel#285: Established the standalone DeepSeek provider implementation that this PR now replaces with the openai.ChatCompatible delegation.
  • ENTERPILOT/GoModel#412: Modifies the same StreamChatCompletion path in the shared adapter that DeepSeek/Ollama/xAI now delegate through.

Poem

Three providers hopped into one warren tonight,
DeepSeek, Ollama, xAI — burrows aligned just right.
No more lonely clients digging their own tunnel deep,
One shared adapter now, headers and hooks to keep.
Thump thump! Tests pass, compat fields all true,
A rabbit's tail wags for the refactor anew. 🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main refactor: routing DeepSeek, xAI, and Ollama through the shared OpenAI-compatible core.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/providers-shared-compat-core

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 85.55556% with 13 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/providers/xai/xai.go 64.51% 11 Missing ⚠️
internal/providers/deepseek/deepseek.go 84.61% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented Jul 5, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

Safe to merge with low risk.

The updated providers keep their intended capability surfaces while sharing the common transport path. The new hooks are covered for chat, streaming, adapter errors, and header scoping. No blocking correctness or security issues were identified.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • The provider tests were executed and captured with the verbose command, timestamps, Go version, and package pass output, ending with EXIT_CODE 0.
  • The provider race tests were executed and captured with the verbose command, timestamps, Go version, and package pass output, ending with EXIT_CODE 0.

View all artifacts

T-Rex Ran code and verified through T-Rex

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Caller
participant Provider as DeepSeek/xAI/Ollama wrapper
participant Core as openai.CompatibleProvider
participant Hooks as Provider hooks
participant Upstream as OpenAI-compatible upstream

Caller->>Provider: ChatCompletion / StreamChatCompletion
Provider->>Core: delegate chat request
Core->>Hooks: AdaptChatRequest(req)
Hooks-->>Core: adapted req or original req
Core->>Core: chatRequestBody(adapted req)
Core->>Hooks: ChatRequestHeaders(ctx, req)
Hooks-->>Core: extra chat headers
Core->>Upstream: POST /chat/completions
Upstream-->>Core: JSON or stream
Core-->>Provider: normalized response / SSE-wrapped stream
Provider-->>Caller: result
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Caller
participant Provider as DeepSeek/xAI/Ollama wrapper
participant Core as openai.CompatibleProvider
participant Hooks as Provider hooks
participant Upstream as OpenAI-compatible upstream

Caller->>Provider: ChatCompletion / StreamChatCompletion
Provider->>Core: delegate chat request
Core->>Hooks: AdaptChatRequest(req)
Hooks-->>Core: adapted req or original req
Core->>Core: chatRequestBody(adapted req)
Core->>Hooks: ChatRequestHeaders(ctx, req)
Hooks-->>Core: extra chat headers
Core->>Upstream: POST /chat/completions
Upstream-->>Core: JSON or stream
Core-->>Provider: normalized response / SSE-wrapped stream
Provider-->>Caller: result
Loading

Reviews (1): Last reviewed commit: "refactor(providers): route deepseek, xai..." | Re-trigger Greptile

@SantiagoDePolonia SantiagoDePolonia merged commit cf613b6 into main Jul 6, 2026
21 checks passed
SantiagoDePolonia added a commit that referenced this pull request Jul 6, 2026
…er hook (#490)

* refactor(providers): batch/file facet surfaces and bailian chat-adapter hook

Phase 2 of the provider consolidation (follow-up to #486).

- New embeddable facets openai.BatchSurface and openai.FileSurface wrap
  *CompatibleProvider with exactly the core.NativeBatchProvider and
  core.NativeFileProvider method sets (compile-guarded). Partial-surface
  providers embed them instead of copying ten delegation methods each;
  the three verbatim copies in groq, bailian, and xai are gone.
- bailian's max_tokens -> max_completion_tokens mapping moves into the
  AdaptChatRequest hook, so ChatCompletion/StreamChatCompletion become
  plain delegation and Responses-via-chat picks the adaptation up
  through the engine instead of relying on method-override discipline.
  The raw passthrough-body adaptation is unchanged.
- bailian's file methods no longer re-stamp resp.Provider: the shared
  engine already stamps its configured provider name, so the second
  stamp was redundant.

Not done, deliberately: the openai-vs-vllm passthrough_semantics
"near-dupe" is two 9-line data tables whose every line differs
(provider-prefixed operations; vllm adds /completions), so there is
nothing to share.

Wire behavior is unchanged; all delegated bodies were verbatim copies
of the engine methods they now call.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(bailian): align Provider doc with embedded batch/file surfaces

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

2 participants