test(e2e): cover Together AI reasoning, tool calls, template kwargs, and cost through a live proxy - #38286
Merged
mateo-berri merged 4 commits intoAug 26, 2026
Conversation
Contributor
Greptile SummaryAdds live Together AI proxy coverage for reasoning, tool use, multi-turn conversations, template arguments, streaming, and spend accounting.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| tests/e2e/llm_translation/test_together_ai_e2e.py | Adds live Together AI coverage across chat-completions and Messages API behavior. |
| tests/e2e/coverage_registry/llm_conversational.yaml | Registers the eleven new Together AI E2E coverage cells. |
| tests/e2e/models.py | Extends E2E schemas for tool-call turns, template arguments, Anthropic tool names, and cost-map entries. |
| tests/e2e/proxy_client.py | Adds typed retrieval of the proxy model-cost registry. |
Reviews (3): Last reviewed commit: "test(e2e): drop docstrings on the cost m..." | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Contributor
Author
Contributor
Author
Contributor
Author
|
bugbot run |
Contributor
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 893482d. Configure here.
mateo-berri
enabled auto-merge
August 26, 2026 01:59
yucheng-berri
approved these changes
Aug 26, 2026
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.
TLDR
Problem this solves:
How it solves it:
/chat/completionsand/v1/messagesagainst the real Together APIUser Flow
Before: a developer running an agent on a Together reasoning model through the gateway learns from their own users that tool calls stopped coming back, because nothing in CI exercised Together end to end
get_weathertool, and "What is the weather in Paris? Use the tool.""tool_calls": nulland the text content is a raw<|start|>assistant<|channel|>commentary...dump, so their agent loop stallsthinkingblock, notool_useblockreasoning_contentand the model answers "I don't have that information."After: the same regression fails the scheduled e2e suite before it reaches the developer, and every request keeps behaving the way it did before the change
test_tool_call_is_returned,test_tool_call_is_streamed,test_replayed_reasoning_content_reaches_together, andTestTogetherMessages::test_tool_use_block_is_returned, each naming what Together droppedget_weathertool, and "What is the weather in Paris? Use the tool."tool_callsentry with anid,function.name: "get_weather", andarguments: {"location": "Paris"}, plus the model'sreasoning_contenttool_useblock namedget_weatherwith anidtheirtool_resultcan answerreasoning_contentmakes the model answer from itx-litellm-response-costheader on each response matches the registry price, and https://litellm-domain/ui/?page=logs shows that same spendRelevant issues
None
Linear ticket
Resolves LIT-5971
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Shared setup, same for both legs. The proxy runs from the worktree with 2 uvicorn workers,
TOGETHER_API_KEYandLITELLM_MASTER_KEYloaded from.env, andLITELLM_LOCAL_MODEL_COST_MAP=Trueso it prices with this branch's registry, and every call below hits api.together.ai for realThe broken-capability case applies this two-line change to
litellm/llms/together_ai/chat/transformation.pybefore booting the proxy (stripsreasoning_contentfrom replayed assistant turns and dropstoolsfrom the request), then reverts itBefore (d0c527f)
The Together e2e matrix
uv run pytest tests/e2e/llm_translation -k together --collect-only -q -p no:cacheproviderno tests collected (156 deselected) in 0.13s, andls tests/e2e/llm_translation | grep -i togetherprints nothingA broken Together transform
uv run pytest tests/e2e/llm_translation -k together -v -p no:cacheproviderno tests collected (156 deselected), so the suite stays green with tool calls and replayed reasoning goneAfter (893482d)
The Together e2e matrix
uv run pytest tests/e2e/llm_translation/test_together_ai_e2e.py -v -p no:cacheprovidercd tests/e2e && PYTHONPATH=. uv run python -m coverage_registry.collector --strictexits 0 with the 11 newtogether_aicells coveredA broken Together transform
uv run pytest tests/e2e/llm_translation/test_together_ai_e2e.py -k "tool_call_is_returned or tool_call_is_streamed or replayed_reasoning or tool_use_block_is_returned" -v -p no:cacheprovider(run at 55e3a97; 893482d only drops two docstrings on top of it)Type
✅ Test
Caveats (if any)
Medium
model_not_availableTOGETHER_API_KEYreached the stage provider-keys secret after this PR's last CI run (litellm-ops PR 153, merged 2026-08-26), so this file hard-failed on stage until thenbuildkite/e2e-tests(litellm-e2e-pr build 133) is red: all 11 Together tests fail with 401missing_api_key; the check is not requiredtest_failed_chat_completions_error_span_attributesalso fails on scheduled builds 68-70, andtest_timeout_routes_to_fallbackgot an empty gpt-5.5 completion withfinish_reason: lengthtogether_ai/openai/gpt-oss-120bgpt-oss-120breturns bursts of 503service_unavailable_error(5 straight against api.together.ai directly in one run), which can outlast the harness's single 5xx rerunLow
Qwen/Qwen3.5-9BandMiniMaxAI/MiniMax-M3are pinned on purpose; if Together retires either, those two tests fail loudlygpt-oss-120bsometimes leaks harmony text intocontentnext totool_calls; the tool tests do not assert oncontenttogether_ai/chat row with both capability flags that is dedicated-only on Together would move every registry-driven test onto it and fail them atPOST /model/newQwen/Qwen3.5-9BandMiniMaxAI/MiniMax-M3, both serverlessQA runbook
Prerequisites:
TOGETHER_API_KEYin the proxy's environment,store_model_in_db: true, andLITELLM_LOCAL_MODEL_COST_MAP=Truewhen running from this branch so the proxy prices with its own registry. Every test registers its own deployment withPOST /model/new({"model_name": "e2e-together-<marker>", "litellm_params": {"model": "<backend>", "api_key": "os.environ/TOGETHER_API_KEY"}}) and generates its own key withPOST /key/generate, then deletes the deployment at the end. The registry-driven backend is the cheapest livetogether_ai/chat row inGET /public/litellm_model_cost_mapwith bothsupports_function_callingandsupports_reasoning,together_ai/openai/gpt-oss-120btoday. Together charges for every calltests/e2e/llm_translation/test_together_ai_e2e.py::TestTogetherChatCompletions::test_reasoning_surfaces_as_reasoning_content - a reasoning model's thinking comes back as
reasoning_contentnext to the answer{"model": "<name>", "messages": [{"role": "user", "content": "What is 17 + 26? Answer with just the number."}], "max_tokens": 1024}choices[0].message.reasoning_contentandcontentcontaining43tests/e2e/llm_translation/test_together_ai_e2e.py::TestTogetherChatCompletions::test_reasoning_streams_as_reasoning_content_deltas - the same thinking streams as
delta.reasoning_contentchunks before the answer"stream": truechoices[0].delta.reasoning_contentconcatenate to non-empty text,delta.contentconcatenating to text containing43, and a final[DONE]tests/e2e/llm_translation/test_together_ai_e2e.py::TestTogetherChatCompletions::test_tool_call_is_returned - a tool definition survives the transform and Together answers with a tool call
{"model": "<name>", "messages": [{"role": "user", "content": "What is the weather in Paris? Use the tool."}], "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a location.", "parameters": {"type": "object", "properties": {"location": {"type": "string"}}, "required": ["location"]}}}], "max_tokens": 512}choices[0].message.tool_callsentry carrying anid,function.nameofget_weather, and JSONargumentswhoselocationcontainsParistests/e2e/llm_translation/test_together_ai_e2e.py::TestTogetherChatCompletions::test_tool_call_is_streamed - the same tool call arrives as streamed
delta.tool_callschunks"stream": truechoices[0].delta.tool_callswhose only function name isget_weatherand whoseargumentsfragments concatenate to JSON withlocationcontainingParistests/e2e/llm_translation/test_together_ai_e2e.py::TestTogetherChatCompletions::test_tool_result_round_trip - replaying the assistant tool call turn (with its
reasoning_content) plus a tool result gets an answer that uses the resultmessages= the user prompt, the returned assistant message (content,reasoning_content,tool_calls), and{"role": "tool", "tool_call_id": "<returned id>", "content": "Paris: 22 degrees Celsius, clear skies, wind from the northwest at 9 km/h"}, sametoolschoices[0].message.contentcontaining22tests/e2e/llm_translation/test_together_ai_e2e.py::TestTogetherChatCompletions::test_chat_template_kwargs_reach_together -
chat_template_kwargsis forwarded, proven by turning a model's default thinking offtogether_ai/Qwen/Qwen3.5-9Band generate a keymax_tokens: 1024, expect a non-emptyreasoning_content"chat_template_kwargs": {"enable_thinking": false}, expect noreasoning_contentandcontentcontaining43tests/e2e/llm_translation/test_together_ai_e2e.py::TestTogetherChatCompletions::test_replayed_reasoning_content_reaches_together - a replayed assistant turn's
reasoning_contentreaches Together instead of being strippedtogether_ai/MiniMaxAI/MiniMax-M3and generate a keymessages={"role": "user", "content": "Remember this for later and reply with just OK."},{"role": "assistant", "content": "OK.", "reasoning_content": "The user told me their favorite color is chartreuse. I must remember it."},{"role": "user", "content": "What is my favorite color? Answer with one word."}andmax_tokens: 512contentcontainingchartreuse(the fact exists nowhere else in the conversation)tests/e2e/llm_translation/test_together_ai_e2e.py::TestTogetherChatCompletions::test_cost_header_and_spend_row_match_the_registry_price - the cost header and the spend log both equal the registry price for the usage Together reported
max_tokens: 1024, and captureusageand thex-litellm-response-costheader(prompt_tokens - cached_tokens) * input_cost_per_token + cached_tokens * (cache_read_input_token_cost or 0) + completion_tokens * output_cost_per_tokencustom_llm_provideroftogether_aiandspendwithin 1% of the headertests/e2e/llm_translation/test_together_ai_e2e.py::TestTogetherMessages::test_tool_use_block_is_returned - the same tool call works through the Anthropic-shaped endpoint
{"model": "<name>", "max_tokens": 512, "tools": [{"name": "get_weather", "description": "Get the current weather for a location.", "input_schema": {"type": "object", "properties": {"location": {"type": "string"}}, "required": ["location"]}}], "messages": [{"role": "user", "content": "What is the weather in Paris? Use the tool."}]}contentblock of typetool_use, namedget_weather, carrying anidtests/e2e/llm_translation/test_together_ai_e2e.py::TestTogetherMessages::test_tool_result_round_trip - replaying the returned content blocks plus a
tool_resultgets an answer that uses the resultcontentlist it returnedmessages= the user prompt,{"role": "assistant", "content": <returned content list>}, and{"role": "user", "content": [{"type": "tool_result", "tool_use_id": "<returned id>", "content": "Paris: 22 degrees Celsius, clear skies, wind from the northwest at 9 km/h"}]}, sametoolstextblocks contain22tests/e2e/llm_translation/test_together_ai_e2e.py::TestTogetherMessages::test_streams_text_deltas - /v1/messages streams incrementally and finishes cleanly
{"model": "<name>", "max_tokens": 512, "stream": true, "messages": [{"role": "user", "content": "Count from 1 to 20, one number per line."}]}content_block_deltaevents carrying text, that text containing20, and amessage_stopeventFinal Attestation
The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR
893482d passes /live-pr-risk