Files
homelab-frontend/tasks/2.13-anthropic-error-shape.md
T

51 lines
2.6 KiB
Markdown
Raw Normal View History

2026-08-19 20:52:13 -07:00
# 2.13 — Anthropic error shape on `/llm/*` (RED)
Phase: 2 — LLM surfaces
Stage: RED
Depends on: [2.10](2.10-anthropic-request-translation.md), [4.3](4.3-problem-json-errors.md)
`/v1/*` renders rejections as RFC 9457 `application/problem+json`. `/llm/*` must not.
The same underlying rejection gets two renderings, chosen purely by which surface
received the request. Write the failing tests against both renderings first.
- [ ] Every error response from a `/llm/*` path has the body
`{"type":"error","error":{"type":"...","message":"..."}}` and
`Content-Type: application/json`
- [ ] No `/llm/*` response ever carries `application/problem+json`, and no `/v1/*`
response ever carries the Anthropic error shape
- [ ] An unknown or missing `model` returns `invalid_request_error` with a message
naming the rejected value and listing the configured model names, derived from
the registry rather than hardcoded
- [ ] A request missing the required `max_tokens` returns `invalid_request_error`
naming `max_tokens`
- [ ] A body that is not valid JSON returns `invalid_request_error` with a message
distinguishable from the unknown-model case
- [ ] A request for an out-of-scope feature returns `invalid_request_error` naming the
feature, for example tools, images or prompt caching
- [ ] Exhausting the shared slot queue returns HTTP 429 with error type
`rate_limit_error` and a `Retry-After` header
- [ ] An upstream failure or timeout returns HTTP 5xx with error type `api_error`, and
the message leaks no upstream host, port or internal path
- [ ] No rejection reaches an upstream, and none falls back to a default model
- [ ] Each rejection is logged with its reason and its surface; the request body is
never logged
- [ ] The same malformed request sent to `/v1/chat/completions` and `/llm/v1/messages`
yields the same HTTP status with the two different body shapes
## Verify
```bash
curl -s -i localhost:8080/llm/v1/messages -H 'content-type: application/json' \
-d '{"model":"gpt-4","max_tokens":8,"messages":[]}'
# expected: 4xx, content-type application/json, body {"type":"error","error":{"type":"invalid_request_error",...}}
# listing reasoning, ornith:35b, qwen2.5:3b-instruct
curl -s -i localhost:8080/llm/v1/messages -H 'content-type: application/json' -d 'not json' \
| grep -i 'content-type'
# expected: application/json, never application/problem+json
curl -s -i localhost:8080/v1/chat/completions -H 'content-type: application/json' \
-d '{"model":"gpt-4","messages":[]}' | grep -i 'content-type'
# expected: application/problem+json, confirming the two surfaces render differently
```