Skip to content

Commit ae44c57

Browse files
authored
feat: handle non serializable objects on RunResponse dict parsing (#3477)
- Updated RunResponse.to_dict() instead of .to_json() to try handling non-serializable fields asap - Added the cookbook I was using; maybe not relevant
1 parent 1b759b0 commit ae44c57

File tree

4 files changed

+85
-11
lines changed

4 files changed

+85
-11
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""
2+
This example shows how to instrument your agno agent with OpenInference and send traces to Langfuse,
3+
using an Agent with a response model.
4+
5+
1. Install dependencies: pip install openai langfuse opentelemetry-sdk opentelemetry-exporter-otlp openinference-instrumentation-agno
6+
2. Either self-host or sign up for an account at https://us.cloud.langfuse.com
7+
3. Set your Langfuse API key as an environment variables:
8+
- export LANGFUSE_PUBLIC_KEY=<your-key>
9+
- export LANGFUSE_SECRET_KEY=<your-key>
10+
"""
11+
12+
import base64
13+
import os
14+
from enum import Enum
15+
16+
from agno.agent import Agent
17+
from agno.models.openai import OpenAIChat
18+
from agno.tools.yfinance import YFinanceTools
19+
from openinference.instrumentation.agno import AgnoInstrumentor
20+
from opentelemetry import trace as trace_api
21+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
22+
from opentelemetry.sdk.trace import TracerProvider
23+
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
24+
from pydantic import BaseModel, Field
25+
26+
LANGFUSE_AUTH = base64.b64encode(
27+
f"{os.getenv('LANGFUSE_PUBLIC_KEY')}:{os.getenv('LANGFUSE_SECRET_KEY')}".encode()
28+
).decode()
29+
os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = (
30+
"https://us.cloud.langfuse.com/api/public/otel" # 🇺🇸 US data region
31+
)
32+
# os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"]="https://cloud.langfuse.com/api/public/otel" # 🇪🇺 EU data region
33+
# os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"]="http://localhost:3000/api/public/otel" # 🏠 Local deployment (>= v3.22.0)
34+
35+
os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = f"Authorization=Basic {LANGFUSE_AUTH}"
36+
37+
38+
tracer_provider = TracerProvider()
39+
tracer_provider.add_span_processor(SimpleSpanProcessor(OTLPSpanExporter()))
40+
trace_api.set_tracer_provider(tracer_provider=tracer_provider)
41+
42+
# Start instrumenting agno
43+
AgnoInstrumentor().instrument()
44+
45+
46+
class MarketArea(Enum):
47+
USA = "USA"
48+
UK = "UK"
49+
EU = "EU"
50+
ASIA = "ASIA"
51+
52+
53+
class StockPrice(BaseModel):
54+
price: str = Field(description="The price of the stock")
55+
symbol: str = Field(description="The symbol of the stock")
56+
date: str = Field(description="Current day")
57+
area: MarketArea
58+
59+
60+
agent = Agent(
61+
name="Stock Price Agent",
62+
model=OpenAIChat(id="gpt-4o-mini"),
63+
tools=[YFinanceTools()],
64+
instructions="You are a stock price agent. You check and return the current price of a stock.",
65+
debug_mode=True,
66+
response_model=StockPrice,
67+
)
68+
69+
agent.print_response("What is the current price of Tesla?")

libs/agno/agno/run/response.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def to_dict(self) -> Dict[str, Any]:
177177
_dict["citations"] = self.citations
178178

179179
if self.content and isinstance(self.content, BaseModel):
180-
_dict["content"] = self.content.model_dump(exclude_none=True)
180+
_dict["content"] = self.content.model_dump(exclude_none=True, mode="json")
181181

182182
if self.tools is not None:
183183
_dict["tools"] = []

libs/agno/agno/run/team.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def to_dict(self) -> Dict[str, Any]:
8181
_dict["citations"] = self.citations
8282

8383
if self.content and isinstance(self.content, BaseModel):
84-
_dict["content"] = self.content.model_dump(exclude_none=True)
84+
_dict["content"] = self.content.model_dump(exclude_none=True, mode="json")
8585

8686
if self.tools is not None:
8787
_dict["tools"] = []

libs/agno/tests/unit/reader/test_url_reader.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from unittest.mock import AsyncMock, Mock, patch
1+
from unittest.mock import Mock, patch
22

33
import httpx
44
import pytest
@@ -82,9 +82,11 @@ def test_read_url_request_error():
8282
def test_read_url_http_error():
8383
"""Test URL reading when fetch_with_retry raises HTTPStatusError"""
8484
url = "https://example.com"
85-
86-
with patch("agno.document.reader.url_reader.fetch_with_retry",
87-
side_effect=httpx.HTTPStatusError("404 Not Found", request=Mock(), response=Mock())):
85+
86+
with patch(
87+
"agno.document.reader.url_reader.fetch_with_retry",
88+
side_effect=httpx.HTTPStatusError("404 Not Found", request=Mock(), response=Mock()),
89+
):
8890
reader = URLReader()
8991
with pytest.raises(httpx.HTTPStatusError):
9092
reader.read(url)
@@ -146,7 +148,7 @@ async def test_async_read_url_with_proxy(mock_response):
146148
call_args = mock_fetch.call_args
147149
assert call_args[0][0] == url # First positional arg is url
148150
assert "client" in call_args[1] # client should be in kwargs
149-
151+
150152
assert len(documents) == 1
151153
assert documents[0].content == "Hello, World!"
152154

@@ -156,8 +158,9 @@ async def test_async_read_url_request_error():
156158
"""Test async URL reading when async_fetch_with_retry raises RequestError"""
157159
url = "https://example.com"
158160

159-
with patch("agno.document.reader.url_reader.async_fetch_with_retry",
160-
side_effect=httpx.RequestError("Connection failed")):
161+
with patch(
162+
"agno.document.reader.url_reader.async_fetch_with_retry", side_effect=httpx.RequestError("Connection failed")
163+
):
161164
reader = URLReader()
162165
with pytest.raises(httpx.RequestError):
163166
await reader.async_read(url)
@@ -168,8 +171,10 @@ async def test_async_read_url_http_error():
168171
"""Test async URL reading when async_fetch_with_retry raises HTTPStatusError"""
169172
url = "https://example.com"
170173

171-
with patch("agno.document.reader.url_reader.async_fetch_with_retry",
172-
side_effect=httpx.HTTPStatusError("404 Not Found", request=Mock(), response=Mock())):
174+
with patch(
175+
"agno.document.reader.url_reader.async_fetch_with_retry",
176+
side_effect=httpx.HTTPStatusError("404 Not Found", request=Mock(), response=Mock()),
177+
):
173178
reader = URLReader()
174179
with pytest.raises(httpx.HTTPStatusError):
175180
await reader.async_read(url)

0 commit comments

Comments
 (0)