Skip to content

Commit 6326c13

Browse files
alexmojakidmontagu
andauthored
Handle and test other OpenAI/Anthropic client methods (#312)
Co-authored-by: David Montague <[email protected]>
1 parent 8e2b7cd commit 6326c13

File tree

8 files changed

+145
-79
lines changed

8 files changed

+145
-79
lines changed

logfire/_internal/integrations/llm_providers/anthropic.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
from ...main import LogfireSpan
1515

16-
1716
__all__ = (
1817
'get_endpoint_config',
1918
'on_response',
@@ -25,8 +24,9 @@ def get_endpoint_config(options: FinalRequestOptions) -> EndpointConfig:
2524
"""Returns the endpoint config for Anthropic depending on the url."""
2625
url = options.url
2726
json_data = options.json_data
28-
if not isinstance(json_data, dict):
29-
raise ValueError('Expected `options.json_data` to be a dictionary')
27+
if not isinstance(json_data, dict): # pragma: no cover
28+
# Ensure that `{request_data[model]!r}` doesn't raise an error, just a warning about `model` missing.
29+
json_data = {}
3030

3131
if url == '/v1/messages':
3232
return EndpointConfig(
@@ -35,7 +35,11 @@ def get_endpoint_config(options: FinalRequestOptions) -> EndpointConfig:
3535
content_from_stream=content_from_messages,
3636
)
3737
else:
38-
raise ValueError(f'Unknown Anthropic API endpoint: `{url}`')
38+
return EndpointConfig(
39+
message_template='Anthropic API call to {url!r}',
40+
span_data={'request_data': json_data, 'url': url},
41+
content_from_stream=None,
42+
)
3943

4044

4145
def content_from_messages(chunk: anthropic.types.MessageStreamEvent) -> str | None:

logfire/_internal/integrations/llm_providers/llm_provider.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,7 @@ def _instrumentation_setup(**kwargs: Any) -> Any:
7676
if is_instrumentation_suppressed():
7777
return None, None, kwargs
7878

79-
options = kwargs['options']
80-
try:
81-
message_template, span_data, content_from_stream = get_endpoint_config_fn(options)
82-
except ValueError as exc:
83-
logfire_llm.warn(
84-
'Unable to instrument {suffix} API call: {error}', suffix=scope_suffix, error=str(exc), kwargs=kwargs
85-
)
86-
return None, None, kwargs
79+
message_template, span_data, content_from_stream = get_endpoint_config_fn(kwargs['options'])
8780

8881
span_data['async'] = is_async
8982

logfire/_internal/integrations/llm_providers/openai.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,11 @@
2828
def get_endpoint_config(options: FinalRequestOptions) -> EndpointConfig:
2929
"""Returns the endpoint config for OpenAI depending on the url."""
3030
url = options.url
31+
3132
json_data = options.json_data
32-
if not isinstance(json_data, dict):
33-
raise ValueError('Expected `options.json_data` to be a dictionary')
34-
if 'model' not in json_data:
35-
# all OpenAI API calls have a model AFAIK
36-
raise ValueError('`model` not found in request data')
33+
if not isinstance(json_data, dict): # pragma: no cover
34+
# Ensure that `{request_data[model]!r}` doesn't raise an error, just a warning about `model` missing.
35+
json_data = {}
3736

3837
if url == '/chat/completions':
3938
return EndpointConfig(
@@ -60,7 +59,11 @@ def get_endpoint_config(options: FinalRequestOptions) -> EndpointConfig:
6059
content_from_stream=None,
6160
)
6261
else:
63-
raise ValueError(f'Unknown OpenAI API endpoint: `{url}`')
62+
return EndpointConfig(
63+
message_template='OpenAI API call to {url!r}',
64+
span_data={'request_data': json_data, 'url': url},
65+
content_from_stream=None,
66+
)
6467

6568

6669
def content_from_completions(chunk: Completion | None) -> str | None:

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ dev-dependencies = [
8484
"aiohttp",
8585
"redis",
8686
"pymongo",
87-
"starlette",
8887
"fastapi",
8988
"Flask",
9089
"django",

requirements-dev.lock

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ mkdocstrings-python==1.10.5
168168
multidict==6.0.5
169169
# via aiohttp
170170
# via yarl
171-
mypy==1.10.1
171+
mypy==1.11.0
172172
mypy-extensions==1.0.0
173173
# via black
174174
# via mypy
@@ -177,7 +177,7 @@ nodeenv==1.9.1
177177
# via pyright
178178
numpy==2.0.0
179179
# via pandas
180-
openai==1.35.14
180+
openai==1.36.0
181181
opentelemetry-api==1.25.0
182182
# via opentelemetry-exporter-otlp-proto-http
183183
# via opentelemetry-instrumentation
@@ -324,7 +324,7 @@ pymdown-extensions==10.8.1
324324
# via mkdocstrings
325325
pymongo==4.8.0
326326
pyright==1.1.372
327-
pytest==8.2.2
327+
pytest==8.3.1
328328
# via pytest-django
329329
# via pytest-pretty
330330
pytest-django==4.8.0
@@ -363,8 +363,8 @@ rich==13.7.1
363363
# via logfire
364364
# via pytest-pretty
365365
# via typer
366-
ruff==0.5.2
367-
setuptools==71.0.1
366+
ruff==0.5.3
367+
setuptools==71.0.4
368368
# via opentelemetry-instrumentation
369369
shellingham==1.5.4
370370
# via typer
@@ -412,7 +412,7 @@ tzdata==2024.1
412412
# via pandas
413413
urllib3==2.2.2
414414
# via requests
415-
uvicorn==0.30.1
415+
uvicorn==0.30.3
416416
# via fastapi
417417
uvloop==0.19.0
418418
# via uvicorn

requirements.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ requests==2.32.3
5757
# via opentelemetry-exporter-otlp-proto-http
5858
rich==13.7.1
5959
# via logfire
60-
setuptools==71.0.1
60+
setuptools==71.0.4
6161
# via opentelemetry-instrumentation
6262
typing-extensions==4.12.2
6363
# via logfire

tests/otel_integrations/test_anthropic.py

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import httpx
88
import pydantic
99
import pytest
10-
from anthropic._models import FinalRequestOptions
1110
from anthropic.types import (
1211
Completion,
1312
Message,
@@ -24,7 +23,6 @@
2423
from inline_snapshot import snapshot
2524

2625
import logfire
27-
from logfire._internal.integrations.llm_providers.anthropic import get_endpoint_config
2826
from logfire.testing import TestExporter
2927

3028
ANY_ADAPTER = pydantic.TypeAdapter(Any) # type: ignore
@@ -491,35 +489,24 @@ def test_unknown_method(instrumented_client: anthropic.Anthropic, exporter: Test
491489
assert exporter.exported_spans_as_dict() == snapshot(
492490
[
493491
{
494-
'name': 'Unable to instrument {suffix} API call: {error}',
492+
'name': 'Anthropic API call to {url!r}',
495493
'context': {'is_remote': False, 'span_id': 1, 'trace_id': 1},
496494
'parent': None,
497495
'start_time': 1000000000,
498-
'end_time': 1000000000,
496+
'end_time': 2000000000,
499497
'attributes': {
500-
'logfire.span_type': 'log',
498+
'logfire.span_type': 'span',
501499
'logfire.tags': ('LLM',),
502-
'logfire.level_num': 13,
503-
'logfire.msg_template': 'Unable to instrument {suffix} API call: {error}',
504-
'logfire.msg': 'Unable to instrument Anthropic API call: Unknown Anthropic API endpoint: `/v1/complete`',
500+
'request_data': '{"max_tokens_to_sample":1000,"model":"claude-2.1","prompt":"prompt"}',
501+
'url': '/v1/complete',
502+
'async': False,
503+
'logfire.msg_template': 'Anthropic API call to {url!r}',
504+
'logfire.msg': "Anthropic API call to '/v1/complete'",
505505
'code.filepath': 'test_anthropic.py',
506506
'code.function': 'test_unknown_method',
507507
'code.lineno': 123,
508-
'error': 'Unknown Anthropic API endpoint: `/v1/complete`',
509-
'kwargs': IsStr(),
510508
'logfire.json_schema': IsStr(),
511-
'suffix': 'Anthropic',
512509
},
513510
}
514511
]
515512
)
516-
517-
518-
def test_get_endpoint_config_json_not_dict():
519-
with pytest.raises(ValueError, match='Expected `options.json_data` to be a dictionary'):
520-
get_endpoint_config(FinalRequestOptions(method='POST', url='...'))
521-
522-
523-
def test_get_endpoint_config_unknown_url():
524-
with pytest.raises(ValueError, match='Unknown Anthropic API endpoint: `/foobar/`'):
525-
get_endpoint_config(FinalRequestOptions(method='POST', url='/foobar/', json_data={'model': 'foobar'}))

0 commit comments

Comments
 (0)