Repository navigation
fix(bedrock_mantle): send max_completion_tokens for gpt-5.6 and gpt-6 chat completions #43982
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
8892ea1
6cbd599
9891fcb
62a88f7
9a25f91
76b96e5
efa8e82
589c099
d4d2694
008996b
1e8767c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,156 @@ | ||
| """Live e2e for output-token limits on a Bedrock Mantle GPT-5.6 chat deployment. | ||
|
|
||
| A deployment registered with model_info.mode "chat" and an explicit Mantle | ||
| OpenAI-compatible api_base is served through Mantle chat completions. Mantle | ||
| rejects max_tokens for the GPT-5.x models there and only accepts | ||
| max_completion_tokens, so a caller that sets an output limit, or a deployment | ||
| that stores one, must still get a completion back instead of the upstream 400 | ||
| "Unsupported parameter: 'max_tokens' is not supported with this model." | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from collections.abc import Iterator | ||
| from typing import Final | ||
|
|
||
| import pytest | ||
| from pydantic import BaseModel | ||
| from e2e_config import unique_marker | ||
| from lifecycle import ResourceManager | ||
| from models import ( | ||
| ChatBody, | ||
| ChatMessage, | ||
| ChatStreamOptions, | ||
| ChatTool, | ||
| ChatToolFunction, | ||
| LiteLLMParamsBody, | ||
| ModelInfoBody, | ||
| ModelNewBody, | ||
| ) | ||
| from passthrough_client import PassthroughClient | ||
|
|
||
| pytestmark = pytest.mark.e2e | ||
|
|
||
| MANTLE_BACKEND: Final = "bedrock_mantle/openai.gpt-5.6-terra" | ||
| MANTLE_CHAT_API_BASE: Final = "https://bedrock-mantle.us-east-1.api.aws/openai/v1" | ||
| OUTPUT_LIMIT: Final = 1000 | ||
| WEATHER_TOOL: Final = ChatTool( | ||
| function=ChatToolFunction( | ||
| name="get_weather", | ||
| description="Get the weather for a city", | ||
| parameters={"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}, | ||
| ) | ||
| ) | ||
|
|
||
|
|
||
| def _register_mantle_chat_model( | ||
| client: PassthroughClient, resources: ResourceManager, max_tokens: int | None = None | ||
| ) -> str: | ||
| model = f"e2e-mantle-chat-output-limit-{unique_marker()}" | ||
| model_id = client.proxy.register_model( | ||
| ModelNewBody( | ||
| model_name=model, | ||
| litellm_params=LiteLLMParamsBody( | ||
| model=MANTLE_BACKEND, | ||
| custom_llm_provider="bedrock_mantle", | ||
| api_key="os.environ/AWS_BEARER_TOKEN_BEDROCK", | ||
| api_base=MANTLE_CHAT_API_BASE, | ||
| reasoning_effort="none", | ||
| max_tokens=max_tokens, | ||
| ), | ||
| model_info=ModelInfoBody(mode="chat", supported_endpoints=["/v1/chat/completions", "/v1/responses"]), | ||
| ) | ||
| ) | ||
| resources.defer(lambda: client.proxy.delete_model(model_id)) | ||
| return model | ||
|
|
||
|
|
||
| def _prompt() -> list[ChatMessage]: | ||
| return [ChatMessage(role="user", content="reply with one word")] | ||
|
|
||
|
|
||
| def _weather_prompt() -> list[ChatMessage]: | ||
| return [ChatMessage(role="user", content="What is the weather in Paris? Use the get_weather tool.")] | ||
|
|
||
|
|
||
| class _StreamToolCallFunction(BaseModel): | ||
| name: str | None = None | ||
| arguments: str | None = None | ||
|
|
||
|
|
||
| class _StreamToolCall(BaseModel): | ||
| function: _StreamToolCallFunction = _StreamToolCallFunction() | ||
|
|
||
|
|
||
| class _StreamDelta(BaseModel): | ||
| tool_calls: list[_StreamToolCall] | None = None | ||
|
|
||
|
|
||
| class _StreamChoice(BaseModel): | ||
| delta: _StreamDelta = _StreamDelta() | ||
|
|
||
|
|
||
| class _StreamChunk(BaseModel): | ||
| choices: list[_StreamChoice] = [] | ||
|
|
||
|
|
||
| class _WeatherArgs(BaseModel): | ||
| city: str | ||
|
|
||
|
|
||
| def _streamed_tool_calls(events: list[str]) -> Iterator[_StreamToolCall]: | ||
| for event in events: | ||
| for choice in _StreamChunk.model_validate_json(event).choices: | ||
| yield from choice.delta.tool_calls or [] | ||
|
|
||
|
|
||
| def _streamed_tool_call(events: list[str]) -> tuple[str, str]: | ||
| calls = tuple(_streamed_tool_calls(events)) | ||
| name = "".join(call.function.name or "" for call in calls) | ||
| arguments = "".join(call.function.arguments or "" for call in calls) | ||
| return name, arguments | ||
|
|
||
|
|
||
| class TestBedrockMantleChatOutputLimit: | ||
| def test_client_max_tokens_returns_completion(self, client: PassthroughClient, resources: ResourceManager) -> None: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. None of the three new tests declares File Used: Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! |
||
| model = _register_mantle_chat_model(client, resources) | ||
| result = client.proxy.transport.send( | ||
| "/chat/completions", | ||
| headers=client.proxy.transport.bearer(resources.key()), | ||
| json=ChatBody(model=model, messages=_prompt(), max_tokens=OUTPUT_LIMIT), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All three new chat tests use File Used: |
||
| ) | ||
| assert result.ok, f"chat call failed: {result.status_code} {result.body[:400]}" | ||
|
|
||
| def test_client_max_completion_tokens_returns_completion( | ||
| self, client: PassthroughClient, resources: ResourceManager | ||
| ) -> None: | ||
| model = _register_mantle_chat_model(client, resources) | ||
| result = client.proxy.transport.send( | ||
| "/chat/completions", | ||
| headers=client.proxy.transport.bearer(resources.key()), | ||
| json=ChatBody(model=model, messages=_prompt(), max_completion_tokens=OUTPUT_LIMIT), | ||
| ) | ||
| assert result.ok, f"chat call failed: {result.status_code} {result.body[:400]}" | ||
|
|
||
| def test_deployment_max_tokens_streams_tool_call_completion( | ||
| self, client: PassthroughClient, resources: ResourceManager | ||
| ) -> None: | ||
| model = _register_mantle_chat_model(client, resources, max_tokens=OUTPUT_LIMIT) | ||
| result = client.proxy.chat_stream( | ||
| resources.key(), | ||
| ChatBody( | ||
| model=model, | ||
| messages=_weather_prompt(), | ||
| stream=True, | ||
| stream_options=ChatStreamOptions(include_usage=True), | ||
| tools=[WEATHER_TOOL], | ||
| ), | ||
| ) | ||
| assert result.ok and result.stream_error is None and result.stream_done, ( | ||
| f"streamed chat call failed: {result.status_code} {result.stream_error} {result.body[:400]}" | ||
| ) | ||
|
greptile-apps[bot] marked this conversation as resolved.
Resolved
|
||
| name, arguments = _streamed_tool_call(result.stream_events) | ||
| assert name == "get_weather", f"streamed tool call named {name!r}: {result.stream_events[:5]}" | ||
|
greptile-apps[bot] marked this conversation as resolved.
Resolved
|
||
| assert _WeatherArgs.model_validate_json(arguments).city.strip(), ( | ||
| f"streamed tool call arguments missing city: {arguments!r}" | ||
| ) | ||
Uh oh!
There was an error while loading. Please reload this page.