Skip to content

Commit 55e1cbb

Browse files
1 parent 1ec75bb commit 55e1cbb

File tree

1 file changed

+132
-2
lines changed

1 file changed

+132
-2
lines changed

src/langsmith/trace-with-opentelemetry.mdx

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ For exception events:
412412

413413
### Trace using the LangSmith SDK
414414

415-
Use the LangSmith SDK's OpenTelemetry helper to configure export:
415+
Use the LangSmith SDK's OpenTelemetry helper to configure export. The following example [traces a Google ADK agent](/langsmith/trace-with-google-adk):
416416

417417
```python
418418
import asyncio
@@ -454,7 +454,137 @@ if __name__ == "__main__":
454454
You do not need to set OTEL environment variables or exporters. `configure()` wires them for LangSmith automatically; instrumentors (like `GoogleADKInstrumentor`) create the spans.
455455
</Note>
456456

457-
5. View the trace in your LangSmith dashboard ([example](https://smith.langchain.com/public/106f5bed-edca-4357-91a5-80089252c9ed/r)).
457+
Here is an [example](https://smith.langchain.com/public/d6d47eeb-511e-4fda-ad17-2caa7bd7150b/r) of what the resulting trace looks like in LangSmith.
458+
459+
### Add an attachment to a trace
460+
461+
LangSmith supports [attaching files to traces](/langsmith/upload-files-with-traces). This is useful when building an agent with multimodal inputs or outputs. Attachments are also supported when tracing with OpenTelemetry.
462+
463+
The example below [traces a Google ADK agent](/langsmith/trace-with-google-adk) and adds an attachment to the trace. It uses a combination of LangSmith's `OtelSpanProcessor` and a custom `AttachmentSpanProcessor` that uses [`on_end()`](https://opentelemetry-python.readthedocs.io/en/latest/sdk/trace.export.html#opentelemetry.sdk.trace.export.SimpleSpanProcessor.on_end) to add an image attachment to the parent span.
464+
465+
466+
```python
467+
import asyncio
468+
import base64
469+
import json
470+
from pathlib import Path
471+
from dotenv import load_dotenv
472+
from google.adk import Runner
473+
from google.adk.agents import LlmAgent
474+
from google.adk.sessions import InMemorySessionService
475+
from google.genai import types
476+
from opentelemetry import trace
477+
from opentelemetry.sdk.trace import TracerProvider, SpanProcessor
478+
from langsmith.integrations.otel import OtelSpanProcessor
479+
480+
481+
class AttachmentSpanProcessor(SpanProcessor):
482+
"""Custom SpanProcessor to add attachments to invocation spans."""
483+
484+
def __init__(self):
485+
self.attachment_data = None
486+
487+
def set_attachment(self, attachment_data):
488+
self.attachment_data = attachment_data
489+
490+
def on_end(self, span):
491+
if span.name == "invocation" and self.attachment_data:
492+
attachments_json = json.dumps([self.attachment_data])
493+
span._attributes["langsmith.attachments"] = attachments_json
494+
495+
load_dotenv()
496+
497+
# Set up TracerProvider manually
498+
provider = TracerProvider()
499+
trace.set_tracer_provider(provider)
500+
501+
# Add attachment processor FIRST (runs before LangSmith processor)
502+
attachment_processor = AttachmentSpanProcessor()
503+
provider.add_span_processor(attachment_processor)
504+
505+
# Add LangSmith processor SECOND (receives already-modified spans)
506+
langsmith_processor = OtelSpanProcessor(project="travel-assistant")
507+
provider.add_span_processor(langsmith_processor)
508+
509+
def get_flight_info(destination: str, departure_date: str) -> dict:
510+
"""Get flight information for a destination."""
511+
return {
512+
"destination": destination,
513+
"departure_date": departure_date,
514+
"price": "$450",
515+
"duration": "5h 30m",
516+
"airline": "Example Airways"
517+
}
518+
519+
def get_hotel_recommendations(city: str, check_in: str) -> dict:
520+
"""Get hotel recommendations for a city."""
521+
return {
522+
"city": city,
523+
"check_in": check_in,
524+
"hotels": [
525+
{"name": "Grand Plaza Hotel", "rating": 4.5, "price": "$120/night"},
526+
{"name": "City Center Inn", "rating": 4.2, "price": "$95/night"}
527+
]
528+
}
529+
530+
async def main():
531+
# Prepare the attachment
532+
receipt_path = Path("receipt-template-example.png")
533+
with open(receipt_path, "rb") as img_file:
534+
image_bytes = img_file.read()
535+
image_base64 = base64.b64encode(image_bytes).decode("ascii")
536+
537+
attachment_data = {
538+
"name": "receipt-template-example",
539+
"content": image_base64,
540+
"mime_type": "image/jpeg",
541+
}
542+
543+
attachment_processor.set_attachment(attachment_data)
544+
545+
# Create ADK agent
546+
agent = LlmAgent(
547+
name="travel_assistant",
548+
tools=[get_flight_info, get_hotel_recommendations],
549+
model="gemini-2.0-flash-exp",
550+
instruction="You are a helpful travel assistant that can help with flights and hotels.",
551+
)
552+
553+
# Set up session and runner
554+
session_service = InMemorySessionService()
555+
runner = Runner(
556+
app_name="travel_app",
557+
agent=agent,
558+
session_service=session_service
559+
)
560+
561+
await session_service.create_session(
562+
app_name="travel_app",
563+
user_id="traveler_456",
564+
session_id="session_789"
565+
)
566+
567+
# Send a message to the agent
568+
new_message = types.Content(
569+
parts=[types.Part(text="I need to book a flight to Paris for March 15th and find a good hotel.")],
570+
role="user",
571+
)
572+
573+
# Run the agent and process events
574+
events = runner.run(
575+
user_id="traveler_456",
576+
session_id="session_789",
577+
new_message=new_message,
578+
)
579+
580+
for event in events:
581+
print(event)
582+
583+
if __name__ == "__main__":
584+
asyncio.run(main())
585+
```
586+
Here is an [example](https://smith.langchain.com/public/9574f70a-b893-49fe-8c62-691bd114bf14/r) of what the resulting trace looks like in LangSmith.
587+
458588

459589
## Advanced configuration
460590

0 commit comments

Comments
 (0)