Skip to content

feat: multi-provider LLM SDK v0.1.0 (openai, gemini, deepseek, zai, kimi, anthropic) - #1

Merged
jkyberneees merged 2 commits into
mainfrom
feat/multi-provider-sdk
Sep 4, 2026
Merged

feat: multi-provider LLM SDK v0.1.0 (openai, gemini, deepseek, zai, kimi, anthropic)#1
jkyberneees merged 2 commits into
mainfrom
feat/multi-provider-sdk

Conversation

@jkyberneees

Copy link
Copy Markdown
Contributor

Summary

Canonical Go SDK for six LLM providers, extracted and hardened from odek's battle-tested internal client. Two full adversarial review cycles (contract, concurrency, wire-fidelity — every finding personally verified and fixed RED-first).

Highlights

  • 6 providers, one canonical API — OpenAI-format (OpenAI, DeepSeek, Z.ai, Kimi + custom gateways), native Gemini, native Anthropic
  • Streaming that behaves — idle watchdog + wall-clock deadline, abort-with-partial, retries that never duplicate partial output, premature-close detection, goroutine-leak-free parser
  • Learn-once fallbacks — per-provider, shared across clients: stream_options, streaming rejection, reasoning_effort+tools
  • Extended-thinking round-trip — Anthropic ThinkingSignature captured and replayed (unblocks thinking tool loops)
  • Canonical error taxonomy — ConfigError / APIError / RateLimitError (unwraps) / StreamAbortedError; unmapped finish reasons never leak
  • Dynamic model discovery — no static tables, per-provider pagination, TTL cache

Quality gates

  • go vet + golangci-lint (v2): clean
  • go test -race -count=1: green (98 tests)
  • Coverage: 97.7% of statements, including the zero-coverage-before streaming failure paths
  • CI: build+test matrix (ubuntu/macOS race+coverage, windows build/vet), golangci-lint v2.5.0

Test plan

  • Full race suite green locally
  • Lint clean
  • CI matrix green on this PR
  • Squash-merge, tag v0.1.0 (release workflow generates notes)

Holistic adversarial review of b2aff9b: 3 sequential agents (contract diff,
bug hunt, robustness/coverage/docs) + personal verification of every finding.

Streaming/SSE:
- parser goroutine no longer leaks on abort/cancel/timeout (pumpSSE now
  signals done; parser selects on send)
- SSE line cap actually works: accumulate ReadSlice fragments to the 1 MiB
  limit instead of dying at the 64 KiB bufio buffer
- partial-output failure is returned with its partial result and never
  retried (was silently retried, duplicating user-visible output)
- keepalive activity flag resets after data events

Retry semantics:
- buffered 429 whose Retry-After outlives the context surfaces as
  *RateLimitError (was a bare ctx error hiding the 429)
- streamRejected classifier narrowed: requires an explicit rejection
  phrase, so context-length 400s no longer permanently downgrade to buffered
- cancellation during backoff no longer mislabeled "retry exhausted"

Provider fidelity:
- anthropic ListModels paginates with after_id (page_id never advanced)
- o-series/gpt-5 receive max_completion_tokens instead of rejected max_tokens
- gemini trailing chunk without usageMetadata no longer wipes accumulated usage
- nested OpenAI error envelope parsed into APIError.Message/Code
- RateLimitError.Unwrap exposes Status/Retryable via errors.As
- learn-once fallbacks moved to Provider: shared by every Chat() client
  (each client used to re-pay the provider's rejection)

Registry:
- WithProvider overrides validated at wiring time like custom providers
- unauthenticated hint names the real env var (DEEPSEEK_API_KEY)

Tests: 18 new (RED-first), incl. CallStream failure orchestration suite that
was at zero coverage. Coverage 81.0% -> 83.4%. Lint clean (errcheck,
staticcheck S1016 conversions). Docs: README Errors contract extended,
PLAN typos fixed.
Second adversarial review cycle (2 sequential hunts + personal verification
of every finding, RED-first fixes):

Wire fidelity:
- Anthropic extended-thinking tool loops: ThinkingSignature added to
  Message/ChatResult; thinking blocks captured buffered + signature_delta
  streaming, replayed as first block with signature on request side
- zero-value/unknown message roles rejected loudly at the SDK boundary
  (was: dropped silently on Anthropic/Gemini, sent as user on OpenAI)
- Gemini: usage-only stream chunks no longer dropped; functionResponse
  name recovered from the assistant tool_call (loud error if unresolvable)
- canonical finish reasons: unmapped provider stop reasons map to ""
  (openai/anthropic/gemini), never leak raw values
- OpenAI finish-reason default aligned to canonical mapping

Streaming/retry:
- premature close (200 SSE headers, no completion signal) is a retryable
  error, never a silent empty success; after deltas -> partial + error
- stream path learns reasoning_effort:none like the buffered path
- learn-retry and effort-learn budget-guarded; (nil, nil) return hole closed
- SetRequestTimeout swapped via atomic pointer (data race fix)

Tests/docs:
- 45+ new tests (RED-first), coverage 83.4% -> 97.7%
- README rewritten: full contract, streaming guarantees, thinking
  signatures, thread safety, error classification
- PLAN.md implementation-status section added
@jkyberneees
jkyberneees merged commit f6811d3 into main Sep 4, 2026
4 checks passed
@jkyberneees
jkyberneees deleted the feat/multi-provider-sdk branch September 4, 2026 14:08
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