fix(anthropic): pass logging_obj to client.post for litellm_overhead_time_ms#24071
Conversation
…time_ms When LITELLM_DETAILED_TIMING=true, litellm_overhead_time_ms was null for Anthropic because the handler did not pass logging_obj to client.post(), so track_llm_api_timing could not set llm_api_duration_ms. Pass logging_obj=logging_obj at all four post() call sites (make_call, make_sync_call, acompletion, completion). Add test to ensure make_call passes logging_obj to client.post. Made-with: Cursor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR fixes a bug where Changes:
Observations:
Confidence Score: 4/5
|
| Filename | Overview |
|---|---|
| litellm/llms/anthropic/chat/handler.py | Passes logging_obj to all four client.post() / async_handler.post() call sites so the @track_llm_api_timing() decorator can extract it from kwargs and set llm_api_duration_ms on the logging object; change is minimal and correct. |
| tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_handler.py | Adds a mocked async test verifying make_call forwards logging_obj to client.post; test is correct and uses no real network calls, but only covers one of the four fixed call sites (async streaming); sync streaming and non-streaming paths remain untested. |
Sequence Diagram
sequenceDiagram
participant Caller
participant AnthropicChatCompletion as handler.py
participant HTTPHandler as AsyncHTTPHandler/HTTPHandler
participant Decorator as track_llm_api_timing()
Caller->>AnthropicChatCompletion: completion() / acompletion()
AnthropicChatCompletion->>HTTPHandler: client.post(..., logging_obj=logging_obj)
Note over HTTPHandler,Decorator: Decorator intercepts call
Decorator->>Decorator: start_time = now()<br/>logging_obj = kwargs.get("logging_obj")
Decorator->>HTTPHandler: execute post()
HTTPHandler-->>Decorator: response
Decorator->>Decorator: end_time = now()<br/>logging_obj.model_call_details["llm_api_duration_ms"] = duration_ms
Decorator-->>AnthropicChatCompletion: response
AnthropicChatCompletion-->>Caller: ModelResponse / StreamWrapper
Last reviewed commit: "fix(anthropic): pass..."
| mock_client.post.assert_called_once() | ||
| call_kwargs = mock_client.post.call_args[1] | ||
| assert call_kwargs.get("logging_obj") is logging_obj |
There was a problem hiding this comment.
Use
.kwargs instead of index-based call_args[1]
call_args[1] is the legacy tuple-indexing API for accessing keyword arguments on a call object. The modern, more readable approach is .kwargs. Both work, but the newer form is clearer and less likely to break if positional args are added.
| mock_client.post.assert_called_once() | |
| call_kwargs = mock_client.post.call_args[1] | |
| assert call_kwargs.get("logging_obj") is logging_obj | |
| call_kwargs = mock_client.post.call_args.kwargs | |
| assert call_kwargs.get("logging_obj") is logging_obj |
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!
| @pytest.mark.asyncio | ||
| async def test_make_call_passes_logging_obj_to_client_post(): | ||
| """make_call must pass logging_obj to client.post so track_llm_api_timing can set llm_api_duration_ms for litellm_overhead_time_ms.""" | ||
| mock_client = AsyncMock() | ||
| mock_response = MagicMock() | ||
| mock_response.aiter_lines = MagicMock(return_value=iter([b'data: {"type":"message_start"}\n', b'data: {"type":"message_delta"}\n'])) | ||
| mock_client.post.return_value = mock_response | ||
|
|
||
| logging_obj = MagicMock() | ||
|
|
||
| await make_call( | ||
| client=mock_client, | ||
| api_base="https://api.anthropic.com/v1/messages", | ||
| headers={}, | ||
| data="{}", | ||
| model="claude-3-5-haiku", | ||
| messages=[{"role": "user", "content": "Hi"}], | ||
| logging_obj=logging_obj, | ||
| timeout=60.0, | ||
| json_mode=False, | ||
| ) | ||
|
|
||
| mock_client.post.assert_called_once() | ||
| call_kwargs = mock_client.post.call_args[1] | ||
| assert call_kwargs.get("logging_obj") is logging_obj |
There was a problem hiding this comment.
Test only covers one of four fixed call sites
The PR fixes logging_obj being passed at four separate call sites in handler.py:
make_call(async streaming) — ✅ covered by this testmake_sync_call(sync streaming) — ❌ not testedacompletion_functionasync non-streaming — ❌ not testedcompletionsync non-streaming — ❌ not tested
Per the project's testing guidelines (at least 1 test is a hard requirement), this technically satisfies the bar, but adding tests for the sync path and non-streaming paths would prevent regressions where a future refactor accidentally drops logging_obj from one of those call sites.
Consider adding parallel tests for make_sync_call and the non-streaming paths.
a18215c
into
BerriAI:litellm_oss_staging_04_02_2026_p1
…time_ms (#24071) When LITELLM_DETAILED_TIMING=true, litellm_overhead_time_ms was null for Anthropic because the handler did not pass logging_obj to client.post(), so track_llm_api_timing could not set llm_api_duration_ms. Pass logging_obj=logging_obj at all four post() call sites (make_call, make_sync_call, acompletion, completion). Add test to ensure make_call passes logging_obj to client.post. Made-with: Cursor
* fix(vertex_ai): support pluggable (executable) credential_source for WIF auth (#24700) The WIF credential dispatch in load_auth() only handled identity_pool and aws credential types. When credential_source.executable was present (used for Azure Managed Identity via Workload Identity Federation), it fell through to identity_pool.Credentials which rejected it with MalformedError. Add dispatch to google.auth.pluggable.Credentials for executable-type credential sources, following the same pattern as the existing identity_pool and aws helpers. Fixes authentication for Azure Container Apps → GCP Vertex AI via WIF with executable credential sources. * feat(logging): add component and logger fields to JSON logs for 3rd p… (#24447) * feat(logging): add component and logger fields to JSON logs for 3rd party filtering * Let user-supplied extra fields win over auto-generated component/logger, tighten test assertions * Feat - Add organization into the metrics metadata for org_id & org_alias (#24440) * Add org_id and org_alias label names to Prometheus metric definitions * Add user_api_key_org_alias to StandardLoggingUserAPIKeyMetadata * Populate user_api_key_org_alias in pre-call metadata * Pass org_id and org_alias into per-request Prometheus metric labels * Add test for org labels on per-request Prometheus metrics * chore: resolve test mockdata * Address review: populate org_alias from DB view, add feature flag, use .get() for org metadata * Add org labels to failure path and verify flag behavior in test * Fix test: build flag-off enum_values without org fields * Gate org labels behind feature flag in get_labels() instead of static metric lists * Scope org label injection to metrics that carry team context, remove orphaned budget label defs, add test teardown * Use explicit metric allowlist for org label injection instead of team heuristic * Fix duplicate org label guard, move _org_label_metrics to class constant * Reset custom_prometheus_metadata_labels after duplicate label assertion * fix: emit org labels by default, remove flag, fix missing org_alias in all metadata paths * fix: emit org labels by default, no opt-in flag required * fix: write org_alias to metadata unconditionally in proxy_server.py * fix: 429s from batch creation being converted to 500 (#24703) * add us gov models (#24660) * add us gov models * added max tokens * Litellm dev 04 02 2026 p1 (#25052) * fix: replace hardcoded url * fix: Anthropic web search cost not tracked for Chat Completions The ModelResponse branch in response_object_includes_web_search_call() only checked url_citation annotations and prompt_tokens_details, missing Anthropic's server_tool_use.web_search_requests field. This caused _handle_web_search_cost() to never fire for Anthropic Claude models. Also routes vertex_ai/claude-* models to the Anthropic cost calculator instead of the Gemini one, since Claude on Vertex uses the same server_tool_use billing structure as the direct Anthropic API. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix(anthropic): pass logging_obj to client.post for litellm_overhead_time_ms (#24071) When LITELLM_DETAILED_TIMING=true, litellm_overhead_time_ms was null for Anthropic because the handler did not pass logging_obj to client.post(), so track_llm_api_timing could not set llm_api_duration_ms. Pass logging_obj=logging_obj at all four post() call sites (make_call, make_sync_call, acompletion, completion). Add test to ensure make_call passes logging_obj to client.post. Made-with: Cursor * sap - add additional parameters for grounding - additional parameter for grounding added for the sap provider * sap - fix models * (sap) add filtering, masking, translation SAP GEN AI Hub modules * (sap) add tests and docs for new SAP modules * (sap) add support of multiple modules config * (sap) code refactoring * (sap) rename file * test(): add safeguard tests * (sap) update tests * (sap) update docs, solve merge conflict in transformation.py * (sap) linter fix * (sap) Align embedding request transformation with current API * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) mock commit * (sap) run black formater * (sap) add literals to models, add negative tests, fix test for tool transformation * (sap) fix formating * (sap) fix models * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) commit for rerun bot review * (sap) minor improve * (sap) fix after bot review * (sap) lint fix * docs(sap): update documentation * fix(sap): change creds priority * fix(sap): change creds priority * fix(sap): fix sap creds unit test * fix(sap): linter fix * fix(sap): linter fix * linter fix * (sap) update logic of fetching creds, add additional tests * (sap) clean up code * (sap) fix after review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) add a possibility to put the service key by both variants * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) update test * (sap) update service key resolve function * (sap) run black formater * (sap) fix validate credentials, add negative tests for credential fetching * (sap) fix validate credentials, add negative tests for credential fetching * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) lint fix * (sap) lint fix * feat: support service_tier in gemini * chore: add a service_tier field mapping from openai to gemini * fix: use x-gemini-service-tier header in response * docs: add service_tier to gemini docs * chore: add defaut/standard mapping, and some tests * chore: tidying up some case insensitivity * chore: remove unnecessary guard * fix: remove redundant test file * fix: handle 'auto' case-insensitively * fix: return service_tier on final steamed chunk * chore: black * feat: enable supports_service_tier to gemini models * Fix get_standard_logging_metadata tests * Fix test_get_model_info_bedrock_models * Fix test_get_model_info_bedrock_models * Fix remaining tests * Fix mypy issues * Fix tests * Fix merge conflicts * Fix code qa * Fix code qa * Fix code qa * Fix greptile review --------- Co-authored-by: michelligabriele <gabriele.michelli@icloud.com> Co-authored-by: Josh <36064836+J-Byron@users.noreply.github.com> Co-authored-by: mubashir1osmani <mubashir.osmani777@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: milan-berri <milan@berri.ai> Co-authored-by: Alperen Kömürcü <alperen.koemuercue@sap.com> Co-authored-by: Vasilisa Parshikova <vasilisa.parshikova@sap.com> Co-authored-by: Lin Xu <lin.xu03@sap.com> Co-authored-by: Mark McDonald <macd@google.com> Co-authored-by: Sameer Kankute <sameer@berri.ai>


Relevant issues
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
tests/test_litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake test-unit(anthropic chat handler tests:poetry run pytest tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_handler.py -v)@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewDelays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
CI (LiteLLM team)
Branch creation CI run
Link:
CI run for the last commit
Link:
Merge / cherry-pick CI run
Links:
Type
🐛 Bug Fix
Changes
logging_objfrom the Anthropic chat handler into everyclient.post()/async_handler.post()call so the@track_llm_api_timing()decorator on the HTTP handler can setmodel_call_details["llm_api_duration_ms"]. Without it,litellm_overhead_time_msstayed null when using Anthropic withLITELLM_DETAILED_TIMING=true.litellm/llms/anthropic/chat/handler.py(4 call sites: async/sync streaming inmake_call/make_sync_call, async/sync non-streaming inacompletion/completion).test_make_call_passes_logging_obj_to_client_postintests/test_litellm/llms/anthropic/chat/test_anthropic_chat_handler.pyensuresmake_callpasseslogging_objtoclient.post.