Skip to content

api: anthropic messages schema tracing#1843

Merged
nacx merged 14 commits intoenvoyproxy:mainfrom
changminbark:anthropic-messages-schema-tracing
Feb 26, 2026
Merged

api: anthropic messages schema tracing#1843
nacx merged 14 commits intoenvoyproxy:mainfrom
changminbark:anthropic-messages-schema-tracing

Conversation

@changminbark
Copy link
Contributor

@changminbark changminbark commented Feb 9, 2026

Description
This PR adds more struct definitions and their corresponding JSONMarshal and JSONUnmarshal methods according to the Anthropic Messages API. These are metrics that are provided in the latest Anthropic messages API. Currently, these metrics/metadata are not being tracked for tracing purposes. Eventually, these metrics should be used in openinference tracing as seen in

// TODO: support for other content types.

It does not introduce any tracing as that should be opened in a new issue. This means that another issue should be opened to include tracking these metrics.

Related Issues/PRs (if applicable)

Fixes #1799

Special notes for reviewers (if applicable)
Claude code was used to write most of the code here, but I manually checked all of code that it generated. One thing that I noticed is that some of the previous API schema for Anthropic implemented in this repository were moved to beta status by Anthropic. This is something that the maintainers should discuss.

  1. https://platform.claude.com/docs/en/api/messages#body-messages-content
  2. https://platform.claude.com/docs/en/api/messages/create
  3. https://platform.claude.com/docs/en/api/beta/messages#body-messages-content
  4. https://platform.claude.com/docs/en/api/beta/messages/create

Signed-off-by: Chang Min <changminbark@gmail.com>
Signed-off-by: Chang Min <changminbark@gmail.com>
Signed-off-by: Chang Min <changminbark@gmail.com>
Signed-off-by: Chang Min <changminbark@gmail.com>
Signed-off-by: Chang Min <changminbark@gmail.com>
Signed-off-by: Chang Min <changminbark@gmail.com>
@changminbark changminbark requested a review from a team as a code owner February 9, 2026 15:13
@dosubot dosubot bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Feb 9, 2026
@aabchoo aabchoo changed the title api: Anthropic messages schema tracing api: anthropic messages schema tracing Feb 9, 2026
@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:XL This PR changes 500-999 lines, ignoring generated files. labels Feb 13, 2026
Signed-off-by: Chang Min <changminbark@gmail.com>
@changminbark changminbark force-pushed the anthropic-messages-schema-tracing branch from 942c014 to b3a828b Compare February 20, 2026 19:17
Copy link
Member

@nacx nacx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM.

Looking at the model I see that for several structs (toolunion, for example, not all the existing properties are added to the structs. Is there any reason why some properties ahve been added and others have been left out?

@changminbark
Copy link
Contributor Author

changminbark commented Feb 24, 2026

@nacx I wanted to confirm with the new Anthropic beta messages API and how we should deal with that. Some of the old fields from the schema were migrated to beta status by Anthropic. Should I go ahead and implement the rest of the structs?

@codecov-commenter
Copy link

codecov-commenter commented Feb 24, 2026

Codecov Report

❌ Patch coverage is 99.31973% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.20%. Comparing base (505bc4d) to head (889ee14).

Files with missing lines Patch % Lines
internal/apischema/anthropic/anthropic.go 99.31% 1 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1843      +/-   ##
==========================================
+ Coverage   83.71%   84.20%   +0.49%     
==========================================
  Files         126      126              
  Lines       16657    17075     +418     
==========================================
+ Hits        13944    14378     +434     
+ Misses       1811     1803       -8     
+ Partials      902      894       -8     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…, ImageBlockParam, SearchResultBlockParam

Signed-off-by: Chang Min <changminbark@gmail.com>
@changminbark
Copy link
Contributor Author

@nacx I went ahead and implemented every schema parameter (mainly using Claude Code since it is simple struct defining and UnmarshalJSON/MarshalJSON). I checked through everything and it looks good. Some of the MarshalJSON functions use value receivers instead of pointer receivers because they are required fields in the parent structs (does not make sense to make them pointers as that would imply that they are optional fields).

I am unsure about how to run the code coverage test but I can add more unit tests if necessary.

@changminbark changminbark requested a review from nacx February 24, 2026 23:26
@ehfd
Copy link

ehfd commented Feb 26, 2026

@nacx Can the CI be approved?

@changminbark
Copy link
Contributor Author

changminbark commented Feb 26, 2026

@gavrissh I don't think the failing e2e tests are due to my commits. Or am I wrong?

image

@nacx
Copy link
Member

nacx commented Feb 26, 2026

/retest

@nacx nacx enabled auto-merge (squash) February 26, 2026 17:58
@nacx nacx merged commit 4d612e0 into envoyproxy:main Feb 26, 2026
55 of 57 checks passed
@adirags
Copy link

adirags commented Feb 28, 2026

I tried hitting an EAIGW with this change from a Claude Code client and was getting

time=2026-02-28T02:01:43.036Z level=ERROR msg="returning user-facing error for malformed request" request_id=9bfc5c6c-1c51-4e8e-aec1-cc5d64d07baa is_upstream_filter=false isUpstreamFilter=false error="malformed request: failed to parse JSON for /v1/messages"

I'm running the claude code CLI from a linux machine

$ claude --version
2.1.62 (Claude Code)
-----
❯ hi
  ⎿  API Error: 400 {"type":"error","error":{"type":"BadRequest","code":"400","message":"malformed request: failed to
     parse JSON for /v1/messages"}}

I added debugs and was able to narrow the issue down to the (*ToolUnion).UnmarshalJSON() function, where we were hitting return errors.New("missing type field in tool").

I haven't read the anthropic spec, but do we need to allow for clients coming in without the "type" field as well? I'm not sure if the behavior differs on Claude Code across versions, or clients
Can we treat the tool as "custom" if the type is not provided?

        if !typ.Exists() {
-               return errors.New("missing type field in tool")
+               // No type field: treat as a custom (user-defined) tool
+               var tool Tool
+               if err := json.Unmarshal(data, &tool); err != nil {
+                       return fmt.Errorf("failed to unmarshal tool: %w", err)
+               }
+               t.Tool = &tool
+               return nil
        }

Or even just fall through the the default case and do nothing?

@ehfd
Copy link

ehfd commented Feb 28, 2026

@adirags
I think we can follow up in another issue. I need to troubleshoot something else as well.

@changminbark
Copy link
Contributor Author

@adirags How is your Claude Code set up? What model server/upstream is the EAIGW pointing to? There is a new PR for helping translate Anthropic requests to OpenAI compatible schema and back #1878. It might be best to try it out again after that PR is merged. If it still does not work then, I think it's better to open a new issue.

@adirags
Copy link

adirags commented Mar 2, 2026

@changminbark, I had the EAIGW pointing to a Sonnet 4.6 model on Vertex AI.
I can retry once #1878 is merged. Thanks

nacx added a commit that referenced this pull request Mar 2, 2026
…1878)

**Description**

This commit adds a translator that will convert a request sent to
`/anthropic/v1/messages` and `/v1/messages` endpoints for OpenAI schema
backends. It does not matter whether the OpenAI schema backend natively
supports the endpoint (e.g. vLLM) as translating should be a light/fast
enough process. This approach is also more versatile and future-proof
than just passing through the Anthropic Message Request to a backend
that natively supports it. It also follows the already-existing
structure for adding translators, path processor factories, and schema
translation.

A major example use case would be using AI Gateway to route requests
from Claude Code to several AI backends like locally hosted vLLM models
with LoRA adapters.

NOTE: vLLM is only used for local testing as I do not have access to
compute. The intended goal for this PR is to support any OpenAI
compatible backend/services using an Anthropic interface.

**Related Issues/PRs (if applicable)**

Fixes #1372 
Fixes #1867 

**Special notes for reviewers (if applicable)**
Claude Code was used to write most of the tests but were verified. It
would also be nice if the maintainers could review the other PR #1843 as
some of the Anthropic apischema here can be updated once #1843 is
merged.

<details>

<summary>Functional Test Results</summary>

Test for anthropic endpoints for OpenAI schema backends that natively
support it:
```
$ curl -v http://localhost:8080/v1/messages   -H "Content-Type: application/json"   -d '{
    "model": "Qwen/Qwen2.5-0.5B-Instruct",
    "messages": [
      {"role": "user", "content": "Say hello!"}
    ],
    "max_tokens": 100
  }'
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8080...
* Connected to localhost (::1) port 8080
> POST /v1/messages HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.5.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 143
> 
< HTTP/1.1 200 OK
< date: Fri, 20 Feb 2026 18:46:04 GMT
< server: uvicorn
< content-type: application/json
< content-length: 331
< 
* Connection #0 to host localhost left intact
{"id":"chatcmpl-36ec5b3d-4273-41e9-966b-ed742f7a93d1","type":"message","role":"assistant","content":[{"type":"text","text":"Hello! How can I assist you today?"}],"model":"Qwen/Qwen2.5-0.5B-Instruct","stop_reason":"end_turn","usage":{"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"input_tokens":32,"output_tokens":10}}
```
```
$ curl -v http://localhost:8080/anthropic/v1/messages   -H "Content-Type: application/json"   -d '{
    "model": "Qwen/Qwen2.5-0.5B-Instruct",
    "messages": [
      {"role": "user", "content": "Say hello!"}
    ],
    "max_tokens": 100
  }'
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8080...
* Connected to localhost (::1) port 8080
> POST /anthropic/v1/messages HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.5.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 143
> 
< HTTP/1.1 200 OK
< date: Fri, 20 Feb 2026 18:46:44 GMT
< server: uvicorn
< content-type: application/json
< content-length: 331
< 
* Connection #0 to host localhost left intact
{"id":"chatcmpl-f639ff32-4f89-48c5-b5b1-56878e641da6","type":"message","role":"assistant","content":[{"type":"text","text":"Hello! How can I assist you today?"}],"model":"Qwen/Qwen2.5-0.5B-Instruct","stop_reason":"end_turn","usage":{"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"input_tokens":32,"output_tokens":10}}
```

Port Forward logs
```
$ kubectl port-forward -n envoy-gateway-system svc/$ENVOY_SERVICE 8080:80
Forwarding from 127.0.0.1:8080 -> 10080
Forwarding from [::1]:8080 -> 10080
Handling connection for 8080
Handling connection for 8080
Handling connection for 8080
```

vLLM Logs (for both requests)
```
(APIServer pid=141923) INFO:     Started server process [141923]
(APIServer pid=141923) INFO:     Waiting for application startup.
(APIServer pid=141923) INFO:     Application startup complete.
(APIServer pid=141923) INFO:     172.18.0.2:46854 - "POST /v1/chat/completions HTTP/1.1" 200 OK
(APIServer pid=141923) INFO 02-20 13:46:05 [loggers.py:257] Engine 000: Avg prompt throughput: 3.2 tokens/s, Avg generation throughput: 1.0 tokens/s, Running: 0 reqs, Waiting: 0 reqs, GPU KV cache usage: 0.0%, Prefix cache hit rate: 0.0%
(APIServer pid=141923) INFO 02-20 13:46:15 [loggers.py:257] Engine 000: Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 0.0 tokens/s, Running: 0 reqs, Waiting: 0 reqs, GPU KV cache usage: 0.0%, Prefix cache hit rate: 0.0%
(APIServer pid=141923) INFO:     172.18.0.2:47216 - "POST /v1/chat/completions HTTP/1.1" 200 OK
(APIServer pid=141923) INFO 02-20 13:46:45 [loggers.py:257] Engine 000: Avg prompt throughput: 3.2 tokens/s, Avg generation throughput: 1.0 tokens/s, Running: 0 reqs, Waiting: 0 reqs, GPU KV cache usage: 0.0%, Prefix cache hit rate: 25.0%
(APIServer pid=141923) INFO 02-20 13:46:55 [loggers.py:257] Engine 000: Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 0.0 tokens/s, Running: 0 reqs, Waiting: 0 reqs, GPU KV cache usage: 0.0%, Prefix cache hit rate: 25.0%
```

</details>

---------

Signed-off-by: Chang Min <changminbark@gmail.com>
Co-authored-by: Ignasi Barrera <ignasi@tetrate.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Complete Anthropic /messages schema and tracing

6 participants