Commit Graph
4 Commits
Author SHA1 Message Date
lukaszraczylo 730583d98e fix(integration): green gate with tracked streaming skip, gemini coverage, robust probe
Addresses coordinator review of the Task 15 integration suite: 1 critical
ruling, 5 important, 3 folded. make integration now exits 0.

- integration_test.go: TestStreamingIncrementalityProbe restructured —
  content assertion (5 deltas + [DONE], in order) always runs; the raw-TCP
  timing assertion (unchanged, >=3 gaps >=100ms) is gated behind
  INTEGRATION_STREAMING=1 with t.Skipf naming traefik/traefik#10269,
  verified to still genuinely fail when re-armed (not silently disabled).
- Makefile: integration-up/integration-wait moved from prerequisites into
  the integration recipe body as go test ./... -count=1
ok  	github.com/lukaszraczylo/traefik-llmgateway	6.065s sub-invocations with per-stage
  status capture, so integration-down always runs even when bring-up or
  the health wait fails; verified with a synthetic failure. Added
  .NOTPARALLEL (the integration targets are stateful).
- translate_gemini.go: fixed the same yaegi multi-value-assignment panic
  shape as the prior sse.go fix, on the live max_tokens path. Added a
  gemini chat completion assertion to integration_test.go (renamed
  TestUnifiedChatAllProviders) so this path now runs under real Yaegi.
- providers.go, translate_anthropic.go: documented the never-wrap
  contract on providerHTTPError/translateError (routes_unified.go asserts
  their concrete type directly; errors.As panics under yaegi on these).
- integration_test.go: TestCrossReplicaLimits now FLUSHALLs Redis first,
  for idempotence under integration-keep.
- mock/main.go: probe's header-terminator search now accumulates a
  rolling buffer across reads instead of only checking the latest one.
- Folded: hot-reload deadline 10s -> 20s; dynamic.yml's real-upstream
  baseUrl is now a template (dynamic.yml.tmpl) rendered via
  INTEGRATION_REAL_BASEURL (default https://llmgw.example.com), not a
  hardcoded-only hostname.

See task-15-report.md's addendum for full verification detail.
2026-08-20 13:46:45 +01:00
lukaszraczylo f71d9d0b50 fix(gemini,anthropic): base64 validation, escaped model paths, prompt-block mapping, unified unsupported-param semantics 2026-08-20 09:49:33 +01:00
lukaszraczylo 876d340054 fix(anthropic): merge parallel tool results, ordinal stream tool indices, array system content, strict stream decode
Review round 1 fixes (1 Critical + 5 Important) on the anthropic
adapter/translator.

- CRITICAL: consecutive role:"tool" messages each became their own
  Anthropic user turn. Anthropic requires alternating user/assistant
  turns and 400s on two consecutive same-role messages; parallel tool
  calls (one assistant message with N tool_calls followed by N
  role:"tool" messages) are the standard agentic transcript shape.
  anthropicRequestFromOpenAI now accumulates consecutive tool messages
  into one Anthropic user turn carrying all of their tool_result
  blocks (anthropicToolResultBlock replaces anthropicToolResultMessage).
  Added chat_parallel_tool_results fixture.
- Streaming tool_calls[].index passed Anthropic's raw content-block
  index through, so a tool_use block after a text block emitted
  index:1 instead of the required contiguous 0-based ordinal.
  anthropicStreamState now tracks a per-stream tool-call ordinal
  counter plus a content-block-index -> ordinal map, populated on
  content_block_start and consulted on input_json_delta. Added the
  stream_text_then_tool fixture (text block at index 0, tool_use at
  index 1) proving the emitted index is 0.
- A system message with array-form content ([{"type":"text",...}])
  was silently dropped (only a string content was handled). Added
  systemTextFromContentParts: text parts join with "\n"; a non-text
  part returns a *translateError instead of being dropped. Added
  chat_system_array_content (success) and
  chat_system_array_content_error fixtures.
- Malformed JSON on a recognized stream event (message_start,
  content_block_start, content_block_delta, message_delta) was
  swallowed as (nil, nil), which could silently corrupt a client's
  reassembled tool-call arguments. All four now return an error
  wrapping errUpstream; forwardStream already propagates that and
  withholds the terminal [DONE]. The unrecognized-event-type and
  error-event branches are unchanged. Added
  TestAnthropicStreamState_MalformedEventReturnsError.
- providerAdapter.injectAuth's doc comment claimed a universal
  empty-key no-op; updated to state the per-type divergence
  (openai-type: keyless is a no-op; anthropic-type: the constructor
  rejects an empty key, so injectAuth is never called with one).
- Added case-insensitive-Content-Type coverage for both adapters'
  streaming-vs-JSON gate (previously untested): one subtest each in
  provider_openai_test.go / provider_anthropic_test.go using a fake
  upstream that sends "Text/Event-Stream; charset=utf-8", asserting
  the streaming path (not the JSON fallback) is taken.

Gate: go test ./... -count=1 and -race both green; gofmt, go vet,
fieldalignment, golangci-lint (incl. gosec), govulncheck all clean.
2026-08-20 03:49:50 +01:00
lukaszraczylo fb7162ab71 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.
2026-08-20 03:37:15 +01:00