Baseline for the Kong replacement on api.riotpiao.com. Brings the working tree under version control for the first time: gateway source, the task board that drives the agent runs, test fixtures, and K8s manifests. Anchor the gateway ignore rule to the repo root. Unanchored, "gateway" also matched the cmd/gateway/ source directory, so the program entrypoint was excluded from every commit. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2.8 KiB
2.8 KiB
2.11 — Anthropic non-streaming response translation (GREEN)
Phase: 2 — LLM surfaces Stage: GREEN Depends on: 2.10, 2.9
A non-streaming POST /llm/v1/messages gets an Anthropic Messages response, built
from whatever the upstream returned. Upstreams speak the OpenAI chat-completion shape;
the client on this surface must never see it.
- The response body is
{"id","type":"message","role":"assistant","content":[...], "model","stop_reason","stop_sequence","usage":{"input_tokens","output_tokens"}}withtypeliterallymessageandroleliterallyassistant modelechoes the model name the client sent, not an upstream-internal name- Upstream
finish_reasonstopbecomesstop_reasonend_turn, andlengthbecomesmax_tokens - A generation halted by a client-supplied stop sequence reports
stop_reasonstop_sequenceand puts the matched string instop_sequence; otherwisestop_sequenceis null and present, not omitted - Upstream
prompt_tokensbecomesusage.input_tokensandcompletion_tokensbecomesusage.output_tokens; no other usage fields are invented reasoning_content, which vLLM returns as a field separate fromcontentforreasoning, becomes athinkingcontent block that PRECEDES thetextblock- When
reasoning_contentis absent or empty, nothinkingblock is emitted andcontentholds only thetextblock - When
contentis empty butreasoning_contentis not, thethinkingblock is still returned rather than an emptycontentarray Content-Typeisapplication/json, and no OpenAI field name such aschoices,finish_reasonorobjectappears anywhere in the body
reasoning runs DeepSeek-R1-Distill-Qwen-32B under vLLM with
--reasoning-parser=deepseek_r1, which is why the reasoning text arrives as its own
field and maps cleanly onto a thinking block.
Verify
curl -s localhost:8080/llm/v1/messages -H 'content-type: application/json' \
-d '{"model":"reasoning","max_tokens":64,"messages":[{"role":"user","content":"hi"}]}'
# expected: 200, type=message, role=assistant, content[0].type=thinking, content[1].type=text
curl -s localhost:8080/llm/v1/messages -H 'content-type: application/json' \
-d '{"model":"reasoning","max_tokens":64,"messages":[{"role":"user","content":"hi"}]}' \
| grep -c -E '"choices"|"finish_reason"|"object"'
# expected: 0
curl -s localhost:8080/llm/v1/messages -H 'content-type: application/json' \
-d '{"model":"qwen2.5:3b-instruct","max_tokens":4,"messages":[{"role":"user","content":"count to fifty"}]}'
# expected: 200, stop_reason=max_tokens, usage has input_tokens and output_tokens only