mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-12 04:54:45 +00:00
feat: anthropic adapter with bidirectional translation incl streaming
Implements Task 9: the anthropic-type providerAdapter and its
request/response/stream translation to and from OpenAI's wire shape.
- translate_anthropic.go: anthropicRequestFromOpenAI (system messages
joined, content blocks incl. base64 data-URI images, tool_calls ->
tool_use, tool messages -> tool_result, max_tokens defaulting,
stop/stop_sequences, tool_choice incl. the none-drops-tools rule,
n>1/logit_bias/logprobs rejected), openAIResponseFromAnthropic
(content blocks joined/tool_calls re-marshaled, stop_reason mapping,
usage), and anthropicStreamState.translate mapping each SSE event to
zero or more OpenAI chat.completion.chunk payloads. translateError
carries a notSupported flag so the caller (Task 12) can tell a 400
from a 501.
- provider_anthropic.go: anthropicAdapter implementing providerAdapter.
newAnthropicAdapter errors on an empty API key per ruling (c), unlike
openai-type's keyless ruling. Headers: x-api-key, anthropic-version.
embeddings always returns a notSupported translateError without
calling upstream (Anthropic has no embeddings endpoint). listModels
GETs /v1/models and returns any non-2xx (incl. 404) as-is.
- providers.go: wires the anthropic buildAdapters case to
newAnthropicAdapter, propagating its constructor error.
- provider_openai_test.go: replaces the now-stale placeholder
TestBuildAdapters subtest with one asserting a keyed anthropic
provider builds successfully and a keyless one errors.
- translate_anthropic_test.go: golden-file harness walking
testdata/anthropic/{chat,resp}_*_in.json against sibling _want.json
(success) or _wanterr.json (*translateError) fixtures, plus a stream
harness driving each *.sse fixture through anthropicStreamState and
asserting the chunk list and usage().
- provider_anthropic_test.go: httptest-backed adapter wiring tests
(streaming + non-streaming), asserting OpenAI-shaped output on the
wire, per-event flushing, and the required auth headers.
Gate: go test ./... -count=1 and -race both green; gofmt, go vet,
fieldalignment, golangci-lint (incl. gosec) all clean.
This commit is contained in:
+13
-7
@@ -172,12 +172,14 @@ func upstreamJSON(ctx context.Context, client *http.Client, method, url string,
|
||||
|
||||
// buildAdapters resolves cfg.Providers into a map of providerAdapter keyed
|
||||
// by provider name. For each provider it resolves APIKey via resolveSecret
|
||||
// (propagating that error, but treating a resolved empty key as a valid
|
||||
// keyless upstream rather than an error) and defaults BaseURL by provider
|
||||
// type when the config omits one, trimming any trailing slash either way.
|
||||
// An unknown provider type is a config error. A configured anthropic or
|
||||
// gemini provider currently errors too — Tasks 9 and 10 fill in those
|
||||
// switch cases; every other case here is unaffected by that.
|
||||
// (propagating that error) and defaults BaseURL by provider type when the
|
||||
// config omits one, trimming any trailing slash either way. An unknown
|
||||
// provider type is a config error. A resolved empty key is a valid
|
||||
// keyless upstream for openai-type providers, but a constructor error for
|
||||
// anthropic-type ones — newAnthropicAdapter enforces that, per ruling (c);
|
||||
// this function just propagates whatever error it returns. A configured
|
||||
// gemini provider currently errors too — Task 10 fills in that switch
|
||||
// case.
|
||||
func buildAdapters(cfg *Config) (map[string]providerAdapter, error) {
|
||||
adapters := make(map[string]providerAdapter, len(cfg.Providers))
|
||||
for name, pc := range cfg.Providers {
|
||||
@@ -195,7 +197,11 @@ func buildAdapters(cfg *Config) (map[string]providerAdapter, error) {
|
||||
case providerTypeOpenAI:
|
||||
adapters[name] = newOpenAIAdapter(name, base, apiKey)
|
||||
case providerTypeAnthropic:
|
||||
return nil, fmt.Errorf("llmgateway: provider %q: type %q not yet implemented", name, pc.Type)
|
||||
a, err := newAnthropicAdapter(name, base, apiKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
adapters[name] = a
|
||||
case providerTypeGemini:
|
||||
return nil, fmt.Errorf("llmgateway: provider %q: type %q not yet implemented", name, pc.Type)
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user