Skip to content

Commit cb2c068

Browse files
committed
fix issues with incorrect typing causing tests to fail
1 parent b151f9e commit cb2c068

File tree

10 files changed

+502
-335
lines changed

10 files changed

+502
-335
lines changed

.vscode/settings.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
21
{
32
"[python]": {
43
"editor.defaultFormatter": "ms-python.black-formatter",
54
},
65
"editor.formatOnSave": true,
6+
"python.testing.pytestArgs": [
7+
"src"
8+
],
9+
"python.testing.unittestEnabled": false,
10+
"python.testing.pytestEnabled": true,
711
}

src/examples/anthropic_example/completion.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
_ = load_dotenv(find_dotenv())
99

10-
langtrace.init()
10+
langtrace.init(write_spans_to_console=True)
1111

1212

1313
@with_langtrace_root_span("messages_create")

src/examples/openai_example/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ def run(self):
2020
chat_completion_example()
2121
embeddings_create_example()
2222
function_example()
23+
image_edit()

src/langtrace_python_sdk/instrumentation/anthropic/instrumentation.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@
2323
from opentelemetry.trace import get_tracer
2424
from wrapt import wrap_function_wrapper
2525
from typing import Any
26-
from src.langtrace_python_sdk.instrumentation.anthropic.patch import messages_create
26+
from langtrace_python_sdk.instrumentation.anthropic.patch import messages_create
2727

2828
logging.basicConfig(level=logging.FATAL)
2929

3030

31-
class AnthropicInstrumentation(BaseInstrumentor): # type: ignore[misc]
31+
class AnthropicInstrumentation(BaseInstrumentor): # type: ignore[misc]
3232
"""
3333
The AnthropicInstrumentation class represents the Anthropic instrumentation.
3434
"""
@@ -37,7 +37,7 @@ def instrumentation_dependencies(self) -> Collection[str]:
3737
return ["anthropic >= 0.19.1"]
3838

3939
def _instrument(self, **kwargs: dict[str, Any]) -> None:
40-
tracer_provider: TracerProvider = kwargs.get("tracer_provider") # type: ignore
40+
tracer_provider: TracerProvider = kwargs.get("tracer_provider") # type: ignore
4141
tracer = get_tracer(__name__, "", tracer_provider)
4242
version = importlib.metadata.version("anthropic")
4343

src/langtrace_python_sdk/instrumentation/anthropic/patch.py

Lines changed: 39 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -27,70 +27,47 @@
2727
get_llm_url,
2828
get_span_name,
2929
set_event_completion,
30-
set_event_completion_chunk,
3130
set_usage_attributes,
32-
set_span_attribute
31+
set_span_attribute,
3332
)
3433
from opentelemetry.trace import Span, Tracer, SpanKind
3534
from opentelemetry.trace.status import StatusCode
36-
from src.langtrace_python_sdk.constants.instrumentation.anthropic import APIS
37-
from src.langtrace_python_sdk.constants.instrumentation.common import SERVICE_PROVIDERS
38-
from src.langtrace_python_sdk.instrumentation.anthropic.types import StreamingResult, ResultType, MessagesCreateKwargs, ContentItem, Usage
39-
40-
def handle_streaming_response(result: StreamingResult, span: Span) -> Iterator[str]:
41-
result_content: List[str] = []
42-
span.add_event(Event.STREAM_START.value)
43-
input_tokens: int = 0
44-
output_tokens: int = 0
45-
try:
46-
for chunk in result:
47-
if chunk['message']["model"] is not None:
48-
span.set_attribute(
49-
SpanAttributes.LLM_RESPONSE_MODEL, chunk["message"]["model"]
50-
)
51-
content: str = ""
52-
if chunk["delta"].get("text") is not None:
53-
content = chunk["delta"]["text"] or ""
54-
result_content.append(content if len(content) > 0 else "")
55-
56-
if chunk["message"]["usage"] is not None:
57-
usage = chunk["message"]["usage"]
58-
input_tokens += usage.get("input_tokens", 0)
59-
output_tokens += usage.get("output_tokens", 0)
60-
61-
if content:
62-
set_event_completion_chunk(span, "".join(content))
63-
64-
yield content
65-
finally:
66-
span.add_event(Event.STREAM_END.value)
67-
set_usage_attributes(
68-
span, {"input_tokens": input_tokens, "output_tokens": output_tokens}
69-
)
70-
completion: List[Dict[str, str]] = [{"role": "assistant", "content": "".join(result_content)}]
71-
set_event_completion(span, completion)
35+
from langtrace_python_sdk.constants.instrumentation.anthropic import APIS
36+
from langtrace_python_sdk.constants.instrumentation.common import SERVICE_PROVIDERS
37+
from langtrace_python_sdk.instrumentation.anthropic.types import (
38+
StreamingResult,
39+
ResultType,
40+
MessagesCreateKwargs,
41+
ContentItem,
42+
Usage,
43+
)
7244

73-
span.set_status(StatusCode.OK)
74-
span.end()
7545

7646
def messages_create(version: str, tracer: Tracer) -> Callable[..., Any]:
7747
"""Wrap the `messages_create` method."""
7848

79-
def traced_method(wrapped: Callable[..., Any], instance: Any, args: List[Any], kwargs: MessagesCreateKwargs) -> Any:
49+
def traced_method(
50+
wrapped: Callable[..., Any],
51+
instance: Any,
52+
args: List[Any],
53+
kwargs: MessagesCreateKwargs,
54+
) -> Any:
8055
service_provider = SERVICE_PROVIDERS["ANTHROPIC"]
8156

8257
# Extract system from kwargs and attach as a role to the prompts
8358
prompts = kwargs.get("messages", [])
8459
system = kwargs.get("system")
8560
if system:
86-
prompts = [{"role": "system", "content": system}] + kwargs.get("messages", [])
61+
prompts = [{"role": "system", "content": system}] + kwargs.get(
62+
"messages", []
63+
)
8764
extraAttributes = get_extra_attributes()
8865
span_attributes = {
8966
**get_langtrace_attributes(version, service_provider),
9067
**get_llm_request_attributes(kwargs, prompts=prompts),
9168
**get_llm_url(instance),
9269
SpanAttributes.LLM_PATH: APIS["MESSAGES_CREATE"]["ENDPOINT"],
93-
**extraAttributes,
70+
**extraAttributes, # type: ignore
9471
}
9572

9673
attributes = LLMSpanAttributes(**span_attributes)
@@ -103,7 +80,7 @@ def traced_method(wrapped: Callable[..., Any], instance: Any, args: List[Any], k
10380
try:
10481
# Attempt to call the original method
10582
result = wrapped(*args, **kwargs)
106-
return set_response_attributes(result, span, kwargs)
83+
return set_response_attributes(result, span)
10784

10885
except Exception as err:
10986
# Record the exception in the span
@@ -114,77 +91,36 @@ def traced_method(wrapped: Callable[..., Any], instance: Any, args: List[Any], k
11491
span.end()
11592
raise
11693

117-
def handle_streaming_response(result: StreamingResult, span: Span) -> Iterator[str]:
118-
"""Process and yield streaming response chunks."""
119-
result_content: List[str] = []
120-
span.add_event(Event.STREAM_START.value)
121-
input_tokens: int = 0
122-
output_tokens: int = 0
123-
try:
124-
for chunk in result:
125-
span.set_attribute(
126-
SpanAttributes.LLM_RESPONSE_MODEL, chunk["message"]["model"] or ""
127-
)
128-
content: str = ""
129-
if hasattr(chunk, "delta") and chunk["delta"] is not None:
130-
content = chunk["delta"]["text"] or ""
131-
result_content.append(content if len(content) > 0 else "")
132-
if chunk["message"]["usage"] is not None:
133-
input_tokens += (
134-
chunk["message"]["usage"]["input_tokens"]
135-
if hasattr(chunk["message"]["usage"], "input_tokens")
136-
else 0
137-
)
138-
output_tokens += (
139-
chunk["message"]["usage"]["output_tokens"]
140-
if hasattr(chunk["message"]["usage"], "output_tokens")
141-
else 0
142-
)
143-
144-
if content:
145-
set_event_completion_chunk(span, "".join(content))
146-
147-
yield content
148-
finally:
149-
span.add_event(Event.STREAM_END.value)
150-
set_usage_attributes(
151-
span, {"input_tokens": input_tokens, "output_tokens": output_tokens}
152-
)
153-
completion = [{"role": "assistant", "content": "".join(result_content)}]
154-
set_event_completion(span, completion)
155-
156-
span.set_status(StatusCode.OK)
157-
span.end()
158-
159-
def set_response_attributes(result: Union[ResultType, StreamingResult], span: Span, kwargs: MessagesCreateKwargs) -> Any:
94+
def set_response_attributes(
95+
result: Union[ResultType, StreamingResult], span: Span
96+
) -> Any:
16097
if not isinstance(result, Iterator):
161-
if result["content"] is not None:
98+
if hasattr(result, "content") and result.content is not None:
16299
set_span_attribute(
163-
span, SpanAttributes.LLM_RESPONSE_MODEL, result["model"]
100+
span, SpanAttributes.LLM_RESPONSE_MODEL, result.model
164101
)
165-
content_item = result["content"][0]
166-
completion: List[ContentItem] = [
102+
content_item = result.content[0]
103+
completion = [
167104
{
168-
"role": result["role"] or "assistant",
169-
"content": content_item.get("text", ""),
170-
"type": content_item.get("type", ""),
105+
"role": result.role or "assistant",
106+
"content": content_item.text,
107+
"type": content_item.type,
171108
}
172109
]
173110
set_event_completion(span, completion)
174111

175-
else:
176-
responses: List[ContentItem] = []
177-
set_event_completion(span, responses)
178-
179-
if result["system_fingerprint"] is not None:
112+
if (
113+
hasattr(result, "system_fingerprint")
114+
and result.system_fingerprint is not None
115+
):
180116
span.set_attribute(
181117
SpanAttributes.LLM_SYSTEM_FINGERPRINT,
182-
result["system_fingerprint"],
118+
result.system_fingerprint,
183119
)
184120
# Get the usage
185-
if result["usage"] is not None:
186-
usage: Usage = result["usage"]
187-
set_usage_attributes(span, dict(usage))
121+
if hasattr(result, "usage") and result.usage is not None:
122+
usage = result.usage
123+
set_usage_attributes(span, vars(usage))
188124

189125
span.set_status(StatusCode.OK)
190126
span.end()

src/langtrace_python_sdk/instrumentation/anthropic/types.py

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,37 +12,94 @@
1212
"""
1313

1414
from typing import Dict, List, Optional, Iterator, TypedDict
15+
16+
1517
class MessagesCreateKwargs(TypedDict, total=False):
16-
system: str
18+
system: Optional[str]
1719
messages: List[Dict[str, str]]
1820

19-
class Usage(TypedDict, total=True):
21+
22+
class Usage:
2023
input_tokens: int
2124
output_tokens: int
2225

23-
class Message(TypedDict, total=True):
26+
def __init__(self, input_tokens: int, output_tokens: int):
27+
self.input_tokens = input_tokens
28+
self.output_tokens = output_tokens
29+
30+
31+
class Message:
32+
def __init__(
33+
self,
34+
id: str,
35+
model: Optional[str],
36+
usage: Optional[Usage],
37+
):
38+
self.id = id
39+
self.model = model
40+
self.usage = usage
41+
2442
model: Optional[str]
2543
usage: Optional[Usage]
2644

27-
class Delta(TypedDict, total=True):
45+
46+
class Delta:
2847
text: Optional[str]
2948

30-
class Chunk(TypedDict, total=True):
49+
def __init__(
50+
self,
51+
text: Optional[str],
52+
):
53+
self.text = text
54+
55+
56+
class Chunk:
3157
message: Message
3258
delta: Delta
3359

34-
class ContentItem(TypedDict, total=False):
60+
def __init__(
61+
self,
62+
message: Message,
63+
delta: Delta,
64+
):
65+
self.message = message
66+
self.delta = delta
67+
68+
69+
class ContentItem:
3570
role: str
3671
content: str
3772
text: str
3873
type: str
3974

40-
class ResultType(TypedDict, total=True):
75+
def __init__(self, role: str, content: str, text: str, type: str):
76+
self.role = role
77+
self.content = content
78+
self.text = text
79+
self.type = type
80+
81+
82+
class ResultType:
4183
model: Optional[str]
4284
role: Optional[str]
4385
content: List[ContentItem]
4486
system_fingerprint: Optional[str]
4587
usage: Optional[Usage]
4688

89+
def __init__(
90+
self,
91+
model: Optional[str],
92+
role: Optional[str],
93+
content: Optional[List[ContentItem]],
94+
system_fingerprint: Optional[str],
95+
usage: Optional[Usage],
96+
):
97+
self.model = model
98+
self.role = role
99+
self.content = content if content is not None else []
100+
self.system_fingerprint = system_fingerprint
101+
self.usage = usage
102+
103+
47104
# The result would be an iterator that yields these Chunk objects
48-
StreamingResult = Iterator[Chunk]
105+
StreamingResult = Iterator[Chunk]

0 commit comments

Comments
 (0)