Skip to content

Commit 8f54a94

Browse files
committed
chore: add dotenv support to agno sample apps and update traceloop-sdk
1 parent f873655 commit 8f54a94

File tree

6 files changed

+92
-34
lines changed

6 files changed

+92
-34
lines changed

packages/sample-app/sample_app/agno_discussion_team.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
from agno.models.openai import OpenAIChat
55
from agno.team import Team
66
from traceloop.sdk import Traceloop
7-
from traceloop.sdk.decorators import workflow
7+
8+
from dotenv import load_dotenv
9+
10+
load_dotenv()
11+
812

913
Traceloop.init(app_name="agno_discussion_team")
1014

1115

12-
@workflow(name="agno_discussion_team_example")
1316
def run_discussion_team():
1417
"""
1518
A multi-agent discussion team where different experts discuss
@@ -24,12 +27,14 @@ def run_discussion_team():
2427
api_key=os.environ.get("OPENAI_API_KEY"),
2528
),
2629
add_name_to_context=True,
27-
instructions=dedent("""
30+
instructions=dedent(
31+
"""
2832
You are a technical expert focused on implementation details.
2933
Analyze topics from a technical feasibility perspective.
3034
Discuss technical challenges, solutions, and best practices.
3135
Keep responses concise and focused.
32-
"""),
36+
"""
37+
),
3338
)
3439

3540
business_expert = Agent(
@@ -40,12 +45,14 @@ def run_discussion_team():
4045
api_key=os.environ.get("OPENAI_API_KEY"),
4146
),
4247
add_name_to_context=True,
43-
instructions=dedent("""
48+
instructions=dedent(
49+
"""
4450
You are a business expert focused on practical value.
4551
Analyze topics from a business impact and ROI perspective.
4652
Discuss market implications, costs, and business benefits.
4753
Keep responses concise and focused.
48-
"""),
54+
"""
55+
),
4956
)
5057

5158
user_experience_expert = Agent(
@@ -56,12 +63,14 @@ def run_discussion_team():
5663
api_key=os.environ.get("OPENAI_API_KEY"),
5764
),
5865
add_name_to_context=True,
59-
instructions=dedent("""
66+
instructions=dedent(
67+
"""
6068
You are a user experience expert focused on usability.
6169
Analyze topics from an end-user perspective.
6270
Discuss user needs, pain points, and user satisfaction.
6371
Keep responses concise and focused.
64-
"""),
72+
"""
73+
),
6574
)
6675

6776
discussion_team = Team(

packages/sample-app/sample_app/agno_example.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
from traceloop.sdk import Traceloop
55
from traceloop.sdk.decorators import workflow
66

7+
from dotenv import load_dotenv
8+
9+
load_dotenv()
10+
711
Traceloop.init(app_name="agno_example")
812

913

@@ -22,7 +26,10 @@ def run_agent():
2226
),
2327
tools=[get_weather],
2428
description="An agent that helps with weather information",
25-
instructions=["Be helpful and concise", "Always use the weather tool when asked"],
29+
instructions=[
30+
"Be helpful and concise",
31+
"Always use the weather tool when asked",
32+
],
2633
)
2734

2835
print("Running agent with tool call...")

packages/sample-app/sample_app/agno_team_example.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from traceloop.sdk import Traceloop
66
from traceloop.sdk.decorators import workflow
77

8+
89
Traceloop.init(app_name="agno_team_example")
910

1011

packages/traceloop-sdk/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ opentelemetry-instrumentation-urllib3 = ">=0.59b0"
3636
opentelemetry-instrumentation-threading = ">=0.59b0"
3737
opentelemetry-instrumentation-redis = ">=0.59b0"
3838
opentelemetry-semantic-conventions-ai = "^0.4.13"
39+
opentelemetry-instrumentation-agno = { path = "../opentelemetry-instrumentation-agno", develop = true }
3940
opentelemetry-instrumentation-mistralai = { path = "../opentelemetry-instrumentation-mistralai", develop = true }
4041
opentelemetry-instrumentation-openai = { path = "../opentelemetry-instrumentation-openai", develop = true }
4142
opentelemetry-instrumentation-openai-agents = { path = "../opentelemetry-instrumentation-openai-agents", develop = true }

packages/traceloop-sdk/traceloop/sdk/instruments.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33

44
class Instruments(Enum):
5+
AGNO = "agno"
56
ALEPHALPHA = "alephalpha"
67
ANTHROPIC = "anthropic"
78
BEDROCK = "bedrock"

packages/traceloop-sdk/traceloop/sdk/tracing/tracing.py

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@
3535
from traceloop.sdk.utils import is_notebook
3636
from traceloop.sdk.utils.package_check import is_package_installed
3737
from typing import Callable, Dict, List, Optional, Set, Union
38-
from opentelemetry.semconv._incubating.attributes.gen_ai_attributes import GEN_AI_AGENT_NAME
38+
from opentelemetry.semconv._incubating.attributes.gen_ai_attributes import (
39+
GEN_AI_AGENT_NAME,
40+
)
3941

4042

4143
TRACER_NAME = "traceloop.tracer"
@@ -87,15 +89,19 @@ def __new__(
8789

8890
obj.__image_uploader = image_uploader
8991
obj.__resource = Resource.create(TracerWrapper.resource_attributes)
90-
obj.__tracer_provider = init_tracer_provider(resource=obj.__resource, sampler=sampler)
92+
obj.__tracer_provider = init_tracer_provider(
93+
resource=obj.__resource, sampler=sampler
94+
)
9195

9296
# Handle multiple processors case
9397
if processor is not None and isinstance(processor, list):
9498
obj.__spans_processors = []
9599
for proc in processor:
96100
original_on_start = proc.on_start
97101

98-
def chained_on_start(span, parent_context=None, orig=original_on_start):
102+
def chained_on_start(
103+
span, parent_context=None, orig=original_on_start
104+
):
99105
if orig:
100106
orig(span, parent_context)
101107
obj._span_processor_on_start(span, parent_context)
@@ -105,7 +111,9 @@ def chained_on_start(span, parent_context=None, orig=original_on_start):
105111

106112
obj.__tracer_provider.add_span_processor(proc)
107113

108-
Telemetry().capture("tracer:init", {"processor": "multiple", "count": len(processor)})
114+
Telemetry().capture(
115+
"tracer:init", {"processor": "multiple", "count": len(processor)}
116+
)
109117

110118
# Handle single processor case (backward compatibility)
111119
elif processor is not None:
@@ -142,8 +150,7 @@ def chained_on_start(span, parent_context=None, orig=original_on_start):
142150
)
143151

144152
obj.__spans_processor = get_default_span_processor(
145-
disable_batch=disable_batch,
146-
exporter=exporter
153+
disable_batch=disable_batch, exporter=exporter
147154
)
148155

149156
if span_postprocess_callback:
@@ -155,6 +162,7 @@ def wrapped_on_end(span):
155162
span_postprocess_callback(span)
156163
# Then call the original to ensure normal processing
157164
original_on_end(span)
165+
158166
obj.__spans_processor.on_end = wrapped_on_end
159167

160168
obj.__spans_processor.on_start = obj._span_processor_on_start
@@ -231,9 +239,9 @@ def set_disabled(cls, disabled: bool) -> None:
231239
cls.__disabled = disabled
232240

233241
def flush(self):
234-
if hasattr(self, '_TracerWrapper__spans_processor'):
242+
if hasattr(self, "_TracerWrapper__spans_processor"):
235243
self.__spans_processor.force_flush()
236-
elif hasattr(self, '_TracerWrapper__spans_processors'):
244+
elif hasattr(self, "_TracerWrapper__spans_processors"):
237245
for processor in self.__spans_processors:
238246
processor.force_flush()
239247

@@ -368,7 +376,9 @@ def default_span_processor_on_start(span: Span, parent_context: Context | None =
368376
)
369377

370378
prompt_template_variables = get_value("prompt_template_variables")
371-
if prompt_template_variables is not None and isinstance(prompt_template_variables, dict):
379+
if prompt_template_variables is not None and isinstance(
380+
prompt_template_variables, dict
381+
):
372382
for key, value in prompt_template_variables.items():
373383
span.set_attribute(
374384
f"{SpanAttributes.TRACELOOP_PROMPT_TEMPLATE_VARIABLES}.{key}",
@@ -380,7 +390,7 @@ def get_default_span_processor(
380390
disable_batch: bool = False,
381391
api_endpoint: Optional[str] = None,
382392
headers: Optional[Dict[str, str]] = None,
383-
exporter: Optional[SpanExporter] = None
393+
exporter: Optional[SpanExporter] = None,
384394
) -> SpanProcessor:
385395
"""
386396
Creates and returns the default Traceloop span processor.
@@ -409,7 +419,9 @@ def get_default_span_processor(
409419
return processor
410420

411421

412-
def init_tracer_provider(resource: Resource, sampler: Optional[Sampler] = None) -> TracerProvider:
422+
def init_tracer_provider(
423+
resource: Resource, sampler: Optional[Sampler] = None
424+
) -> TracerProvider:
413425
provider: TracerProvider = None
414426
default_provider: TracerProvider = get_tracer_provider()
415427

@@ -438,16 +450,17 @@ def init_instrumentations(
438450
):
439451
block_instruments = block_instruments or set()
440452
# explictly test for None since empty set is a False value
441-
instruments = instruments if instruments is not None else set(
442-
Instruments
443-
)
453+
instruments = instruments if instruments is not None else set(Instruments)
444454

445455
# Remove any instruments that were explicitly blocked
446456
instruments = instruments - block_instruments
447457

448458
instrument_set = False
449459
for instrument in instruments:
450-
if instrument == Instruments.ALEPHALPHA:
460+
if instrument == Instruments.AGNO:
461+
if init_agno_instrumentor():
462+
instrument_set = True
463+
elif instrument == Instruments.ALEPHALPHA:
451464
if init_alephalpha_instrumentor():
452465
instrument_set = True
453466
elif instrument == Instruments.ANTHROPIC:
@@ -468,7 +481,9 @@ def init_instrumentations(
468481
if init_crewai_instrumentor():
469482
instrument_set = True
470483
elif instrument == Instruments.GOOGLE_GENERATIVEAI:
471-
if init_google_generativeai_instrumentor(should_enrich_metrics, base64_image_uploader):
484+
if init_google_generativeai_instrumentor(
485+
should_enrich_metrics, base64_image_uploader
486+
):
472487
instrument_set = True
473488
elif instrument == Instruments.GROQ:
474489
if init_groq_instrumentor():
@@ -537,9 +552,7 @@ def init_instrumentations(
537552
if init_urllib3_instrumentor():
538553
instrument_set = True
539554
elif instrument == Instruments.VERTEXAI:
540-
if init_vertexai_instrumentor(
541-
should_enrich_metrics, base64_image_uploader
542-
):
555+
if init_vertexai_instrumentor(should_enrich_metrics, base64_image_uploader):
543556
instrument_set = True
544557
elif instrument == Instruments.WATSONX:
545558
if init_watsonx_instrumentor():
@@ -572,7 +585,8 @@ def init_instrumentations(
572585

573586

574587
def init_openai_instrumentor(
575-
should_enrich_metrics: bool, base64_image_uploader: Callable[[str, str, str, str], str]
588+
should_enrich_metrics: bool,
589+
base64_image_uploader: Callable[[str, str, str, str], str],
576590
):
577591
try:
578592
if is_package_installed("openai"):
@@ -596,7 +610,8 @@ def init_openai_instrumentor(
596610

597611

598612
def init_anthropic_instrumentor(
599-
should_enrich_metrics: bool, base64_image_uploader: Callable[[str, str, str, str], str]
613+
should_enrich_metrics: bool,
614+
base64_image_uploader: Callable[[str, str, str, str], str],
600615
):
601616
try:
602617
if is_package_installed("anthropic"):
@@ -656,7 +671,9 @@ def init_pinecone_instrumentor():
656671

657672
def init_qdrant_instrumentor():
658673
try:
659-
if is_package_installed("qdrant_client") or is_package_installed("qdrant-client"):
674+
if is_package_installed("qdrant_client") or is_package_installed(
675+
"qdrant-client"
676+
):
660677
Telemetry().capture("instrumentation:qdrant:init")
661678
from opentelemetry.instrumentation.qdrant import QdrantInstrumentor
662679

@@ -691,10 +708,13 @@ def init_chroma_instrumentor():
691708

692709

693710
def init_google_generativeai_instrumentor(
694-
should_enrich_metrics: bool, base64_image_uploader: Callable[[str, str, str, str], str]
711+
should_enrich_metrics: bool,
712+
base64_image_uploader: Callable[[str, str, str, str], str],
695713
):
696714
try:
697-
if is_package_installed("google-generativeai") or is_package_installed("google-genai"):
715+
if is_package_installed("google-generativeai") or is_package_installed(
716+
"google-genai"
717+
):
698718
Telemetry().capture("instrumentation:gemini:init")
699719
from opentelemetry.instrumentation.google_generativeai import (
700720
GoogleGenerativeAiInstrumentor,
@@ -954,7 +974,8 @@ def init_replicate_instrumentor():
954974

955975

956976
def init_vertexai_instrumentor(
957-
should_enrich_metrics: bool, base64_image_uploader: Callable[[str, str, str, str], str]
977+
should_enrich_metrics: bool,
978+
base64_image_uploader: Callable[[str, str, str, str], str],
958979
):
959980
try:
960981
if is_package_installed("google-cloud-aiplatform"):
@@ -1030,6 +1051,24 @@ def init_writer_instrumentor():
10301051
return False
10311052

10321053

1054+
def init_agno_instrumentor():
1055+
try:
1056+
if is_package_installed("agno"):
1057+
Telemetry().capture("instrumentation:agno:init")
1058+
from opentelemetry.instrumentation.agno import AgnoInstrumentor
1059+
1060+
instrumentor = AgnoInstrumentor(
1061+
exception_logger=lambda e: Telemetry().log_exception(e),
1062+
)
1063+
if not instrumentor.is_instrumented_by_opentelemetry:
1064+
instrumentor.instrument()
1065+
return True
1066+
except Exception as e:
1067+
logging.error(f"Error initializing Agno instrumentor: {e}")
1068+
Telemetry().log_exception(e)
1069+
return False
1070+
1071+
10331072
def init_alephalpha_instrumentor():
10341073
try:
10351074
if is_package_installed("aleph_alpha_client"):

0 commit comments

Comments
 (0)