Extend v1/messages by introducing token-in/out and returning routed experts #4597
Open
lvhan028 wants to merge 13 commits into
Open
Extend v1/messages by introducing token-in/out and returning routed experts #4597lvhan028 wants to merge 13 commits into
lvhan028 wants to merge 13 commits into
Conversation
…turn_token_ids to MessagesRequest Add LMDeploy-specific extension fields to the Anthropic /v1/messages request model so that /generate-style token-ID input and multimodal image data can be passed alongside the standard Anthropic messages format. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add validation in the /v1/messages endpoint to enforce priority rules: - input_ids and image_data are rejected when messages is non-empty - image_data requires input_ids when messages is empty - input_ids must not be an empty list Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ng response Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…esponse Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Verify that the /v1/messages endpoint with input_ids and stream=True produces valid SSE events (message_start, content_block_delta, message_stop). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR extends the Anthropic-compatible /v1/messages API to accept additional /generate-style inputs and to optionally return richer generation metadata (token IDs and routed experts), with corresponding updates to streaming and tests.
Changes:
- Extend
MessagesRequest/MessagesResponseto includeinput_ids,image_data,return_token_ids,return_routed_experts, plus response fieldsoutput_idsandrouted_experts. - Update
/v1/messagesendpoint to validate/route betweenmessagesvsinput_ids(+image_data) and to populateoutput_ids/routed_expertsin responses. - Update Anthropic SSE streaming to optionally emit
output_idsandrouted_experts; add/extend tests for the new behavior.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_lmdeploy/serve/anthropic/test_endpoints.py | Adds tests for new request fields and response metadata in both streaming and non-stream modes. |
| lmdeploy/serve/anthropic/streaming.py | Adds optional emission of output_ids in content_block_delta and routed_experts in message_delta. |
| lmdeploy/serve/anthropic/protocol.py | Extends Anthropic request/response models with new optional fields. |
| lmdeploy/serve/anthropic/endpoints/messages.py | Adds validation and routing for input_ids/image_data, wires new flags into streaming and non-stream responses. |
| lmdeploy/serve/anthropic/adapter.py | Passes return_routed_experts into GenerationConfig. |
Comments suppressed due to low confidence (2)
lmdeploy/serve/anthropic/endpoints/messages.py:109
- When
input_idsmode is used (messagesempty andresolved_input_idsset), tool-calling inputs liketools/tool_choiceare silently ignored because noresponse_parseris built andtools=Noneis passed intoasync_engine.generate. It would be clearer to reject tool-calling parameters ininput_idsmode (400) or explicitly support them.
parser_cls = getattr(server_context, 'response_parser_cls', None)
if request.tools and (parser_cls is None or parser_cls.tool_parser_cls is None):
return create_error_response(
HTTPStatus.BAD_REQUEST,
'Please launch the api_server with --tool-call-parser if you want to use tool calling.')
tests/test_lmdeploy/serve/anthropic/test_endpoints.py:608
- Despite the docstring, this test never sets
return_routed_experts=Trueand never asserts onrouted_experts. As written it only verifiesoutput_idsbehavior. Add a request withreturn_routed_experts=Trueand assert the response contains the expectedrouted_expertsvalue (or adjust the docstring/name).
def test_messages_non_stream_with_output_ids_and_routed_experts():
"""When return_token_ids is True, output_ids must be populated with the
generated token IDs.
When return_routed_experts is True, routed_experts must be populated from the engine result.
"""
client = _make_client()
# Test output_ids when return_token_ids is True
response = client.post(
'/v1/messages',
headers={'anthropic-version': '2023-06-01'},
json={
'model': 'fake-model',
'max_tokens': 16,
'messages': [{'role': 'user', 'content': 'Hi'}],
'return_token_ids': True,
},
)
assert response.status_code == 200
data = response.json()
# output_ids should be populated from token IDs generated by the fake engine
assert data.get('output_ids') == [101, 102]
# Test that output_ids is None when return_token_ids is False (default)
response2 = client.post(
'/v1/messages',
headers={'anthropic-version': '2023-06-01'},
json={
'model': 'fake-model',
'max_tokens': 16,
'messages': [{'role': 'user', 'content': 'Hi'}],
},
)
assert response2.status_code == 200
data2 = response2.json()
assert data2.get('output_ids') is None
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+52
to
+56
| # Validate input_ids and image_data constraints. | ||
| # messages has higher priority. input_ids and image_data are only used when | ||
| # messages is empty. image_data requires input_ids. | ||
| messages_empty = (request.messages is None | ||
| or (isinstance(request.messages, list) and len(request.messages) == 0)) |
Comment on lines
+225
to
+227
| if return_routed_experts and final_res is not None and hasattr(final_res, 'routed_experts'): | ||
| message_delta_data['routed_experts'] = final_res.routed_experts | ||
| yield _format_sse('message_delta', message_delta_data) |
Comment on lines
+536
to
+551
| def test_messages_non_stream_includes_output_ids_when_return_token_ids(): | ||
| """The response should include output_ids in the response-level field.""" | ||
| client = _make_client() | ||
| response = client.post( | ||
| '/v1/messages', | ||
| headers={'anthropic-version': '2023-06-01'}, | ||
| json={ | ||
| 'model': 'fake-model', | ||
| 'max_tokens': 16, | ||
| 'messages': [{'role': 'user', 'content': 'Hi'}], | ||
| }, | ||
| ) | ||
| assert response.status_code == 200 | ||
| data = response.json() | ||
| # output_ids is a new optional field; it should be present (possibly None) | ||
| assert 'output_ids' in data |
Comment on lines
+495
to
+533
| def test_messages_accepts_input_ids_and_image_data(): | ||
| """Extended fields input_ids, image_data, return_routed_experts, and | ||
| return_token_ids must be accepted by the protocol model.""" | ||
| from lmdeploy.serve.anthropic.protocol import MessagesRequest | ||
|
|
||
| req = MessagesRequest( | ||
| model='fake-model', | ||
| messages=[], | ||
| max_tokens=16, | ||
| input_ids=[1, 2, 3], | ||
| image_data='https://example.com/img.png', | ||
| return_routed_experts=True, | ||
| return_token_ids=True, | ||
| ) | ||
| assert req.input_ids == [1, 2, 3] | ||
| assert req.image_data == 'https://example.com/img.png' | ||
| assert req.return_routed_experts is True | ||
| assert req.return_token_ids is True | ||
|
|
||
| # Defaults | ||
| req2 = MessagesRequest(model='m', messages=[], max_tokens=16) | ||
| assert req2.input_ids is None | ||
| assert req2.image_data is None | ||
| assert req2.return_routed_experts is False | ||
| assert req2.return_token_ids is False | ||
|
|
||
| # Also verify the endpoint doesn't reject the extended fields via HTTP | ||
| client = _make_client() | ||
| response = client.post( | ||
| '/v1/messages', | ||
| headers={'anthropic-version': '2023-06-01'}, | ||
| json={ | ||
| 'model': 'fake-model', | ||
| 'max_tokens': 16, | ||
| 'messages': [], | ||
| 'input_ids': [1, 2, 3], | ||
| }, | ||
| ) | ||
| assert response.status_code != 422 |
… response Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Thanks for your contribution and we appreciate it a lot. The following instructions would make your pull request more healthy and more easily receiving feedbacks. If you do not understand some items, don't worry, just make the pull request and seek help from maintainers.
Motivation
Please describe the motivation of this PR and the goal you want to achieve through this PR.
Modification
Please briefly describe what modification is made in this PR.
BC-breaking (Optional)
Does the modification introduce changes that break the backward-compatibility of the downstream repositories?
If so, please describe how it breaks the compatibility and how the downstream projects should modify their code to keep compatibility with this PR.
Use cases (Optional)
If this PR introduces a new feature, it is better to list some use cases here, and update the documentation.
Checklist