Support overriding default model provider in instrument_openai#1609
Support overriding default model provider in instrument_openai#1609yiphei wants to merge 30 commits intopydantic:mainfrom
Conversation
| return cast('ResponseT', response) | ||
|
|
||
| span.set_attribute('gen_ai.system', 'openai') | ||
| model_provider: str = cast(str, (span.attributes or {}).get('overridden_model_provider', "openai")) |
There was a problem hiding this comment.
span.attributes won't work when the span isn't recording
There was a problem hiding this comment.
@alexmojaki then how should i check if overridden_model_provider is set, or, as you proposed, check that gen_ai.system hasnt already been set upstream
There was a problem hiding this comment.
use getattr(span, 'attributes', None)
|
|
||
| span_data['async'] = is_async | ||
| if model_provider is not None: | ||
| span_data['overridden_model_provider'] = model_provider |
There was a problem hiding this comment.
rather just set gen_ai.system (and gen_ai.provider.name, the new attribute) here
| get_endpoint_config_fn: Callable[[Any], EndpointConfig], | ||
| on_response_fn: Callable[[Any, LogfireSpan], Any], | ||
| is_async_client_fn: Callable[[type[Any]], bool], | ||
| model_provider: str | None = None, |
| usage_data = extract_usage( | ||
| response_data, | ||
| provider_id='openai', | ||
| provider_id=model_provider, |
There was a problem hiding this comment.
i think leave this as is, since we're using the openai client we should be able to assume the shape of the usage.
|
@alexmojaki addressed your feedback. can you quickly review it again before i add tests? |
| return cast('ResponseT', response) | ||
|
|
||
| span.set_attribute('gen_ai.system', 'openai') | ||
| if getattr(span, 'attributes', None) is None or (span.attributes or {}).get('gen_ai.system', None) is None: |
There was a problem hiding this comment.
@alexmojaki type checking complained that span.attributes could be None, so i had to do (span.attributes or {})
There was a problem hiding this comment.
| if getattr(span, 'attributes', None) is None or (span.attributes or {}).get('gen_ai.system', None) is None: | |
| if not (getattr(span, 'attributes', None) or {}).get('gen_ai.system'): |
There was a problem hiding this comment.
doing this will incur the following type checker error
/Users/yifeiyan/Dev/logfire/logfire/_internal/integrations/llm_providers/openai.py:186:12 - error: Type of "get" is partially unknown
Type of "get" is "Any | Overload[(key: Unknown, default: None = None, /) -> (Unknown | None), (key: Unknown, default: Unknown, /) -> Unknown, (key: Unknown, default: _T@get, /) -> (Unknown | _T@get)]" (reportUnknownMemberType)
do you want me to suppress it or cast it? the most compact i can make it is
if (getattr(span, 'attributes', {})).get('gen_ai.system', None) is None:
no, please get CI passing first |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
@alexmojaki now everything passes except for code coverage and linter that complains about missing docstring for the new param. can you take a quick look now? |
|
no, please get CI passing first, and get a review from copilot if possible |
@alexmojaki the reason im nagging you is because this PR contains an API change of a public method (though a backwards compatible one). Usually, orgs are pretty protective about public API changes, so i dont like to do a much of work and then revert because they dislike the API changes. Therefore, all im asking you is a) to quickly confirm that the API change looks good, or b) to tell me that the specific API changes here are no big deal and should proceed without worries |
|
Yes it looks fine |
|
@alexmojaki all the tests have been added, and all the CI checks pass. Thanks for the patience. |
| return cast('ResponseT', response) | ||
|
|
||
| span.set_attribute('gen_ai.system', 'openai') | ||
| if getattr(span, 'attributes', None) is None or (span.attributes or {}).get('gen_ai.system', None) is None: |
There was a problem hiding this comment.
| if getattr(span, 'attributes', None) is None or (span.attributes or {}).get('gen_ai.system', None) is None: | |
| if not (getattr(span, 'attributes', None) or {}).get('gen_ai.system'): |
|
Note: starting recently, running the test suite locally always fails with the same following errors I did everything as outlined in https://github.com/pydantic/logfire/blob/main/CONTRIBUTING.md |
|
You probably have a |
|
@alexmojaki you didnt submit a review last time, so re-requesting your review via this comment |
There was a problem hiding this comment.
Pull request overview
This PR adds support for overriding the default model provider in instrument_openai to enable proper attribution and cost calculation for OpenAI-compatible providers like OpenRouter.
Changes:
- Added
override_providerparameter toinstrument_openai()method with comprehensive documentation - Modified
instrument_llm_provider()to accept and useoverride_providerto set thegen_ai.systemattribute - Updated
on_response()to conditionally setgen_ai.systemto 'openai' only when not already present
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
logfire/_internal/main.py |
Added override_provider parameter to instrument_openai() with documentation explaining its purpose and benefits |
logfire/_internal/integrations/llm_providers/llm_provider.py |
Added override_provider parameter and logic to set gen_ai.system in span_data when provided |
logfire/_internal/integrations/llm_providers/openai.py |
Modified on_response() to only set gen_ai.system='openai' when not already present, allowing override |
tests/test_llm_provider.py |
Added parametrized unit tests for sync and async clients with override_provider |
tests/otel_integrations/test_openai.py |
Added comprehensive integration tests covering sync, async, streaming, default behavior, and on_response behavior |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Please also set |
|
@alexmojaki done |
c702827 to
28e7ee0
Compare
Support custom system pt2
| 'gen_ai.system': 'openai', | ||
| PROVIDER_NAME: 'openai', |
There was a problem hiding this comment.
what about the override?
There was a problem hiding this comment.
@alexmojaki this happens before the override. this will be overridden
There was a problem hiding this comment.
then these lines of code are pointless. looks like PROVIDER_NAME: 'openai', was a mistake in the first place and there was always a better way.
There was a problem hiding this comment.
all these pairs of lines 'gen_ai.system': 'openai', PROVIDER_NAME: 'openai', should be removed, they seem to do nothing but add confusion. if tests don't pass without changes after that then it's a sign that overriding isn't working fully.
There was a problem hiding this comment.
@alexmojaki sorry, but i no longer have time for this PR. it has expanded way beyond the original scope and what i have time for. do what you want with this PR. I will just fork the repo and implement what i need directly there
Support overriding the default system provider of
openaiininstrument_openaito e.g.openrouter. This is necessary to get features like automatic cost computation. For more context: https://pydanticlogfire.slack.com/archives/C06EDRBSAH3/p1767644423267469?thread_ts=1759944637.702099&cid=C06EDRBSAH3Note:
genai_prices.calc_pricedoes not support extraction logic for Openrounter. So the try block that callsgenai_prices.calc_priceinon_responsewill error with the followingThis wont be addressed in this PR because: a) genai_prices is a separate package, so out of scope, and b) client-side computation is not necessary to see the cost info in the UI since that is also computed server-side, though you do lose explicit setting of
operation.costattributeRun this to check things works as expected