Skip to content

Commit 56f3b08

Browse files
mxiamxiayiyuan-he
andauthored
Add support for OTel GenAI Semantic Convention patterns in LLO handler (#460)
*Description of changes:* Handle specific span events by recognizing them via event.name. Move their associated attributes into Log events (as content) and remove them from the original Span Event. * gen_ai.assistant.message (output) * gen_ai.tool.message (input) * gen_ai.user.message (input) * gen_ai.system.message (input) * gen_ai.choice (output) ### Test ``` { "resource": { "attributes": { "aws.local.service": "langgraph-min", "aws.service.type": "gen_ai_agent", "telemetry.sdk.language": "python", "service.name": "langgraph-min", "cloud.resource_id": "fastapi-app", "aws.log.group.names": "test_trace-min-4", "telemetry.sdk.version": "1.33.1", "telemetry.auto.version": "0.11.0.dev0-aws", "telemetry.sdk.name": "opentelemetry" } }, "scope": { "name": "strands.telemetry.tracer" }, "timeUnixNano": 1756004845660372000, "observedTimeUnixNano": 1756004845662834000, "severityNumber": 9, "severityText": "", "body": { "output": { "messages": [ { "content": { "message": "# Weather Forecast for New York City 🌤️\n\nHere's the current weather forecast for New York City (Manhattan area):\n\n## **Current Conditions (Tonight - Saturday Night, Aug 23)**\n🌙 **Tonight**: Partly Cloudy \n- **Temperature**: Low around 70°F \n- **Wind**: South 7-13 mph \n- **Precipitation**: 0% chance \n\n## **This Weekend**\n\n### **Sunday (Aug 24)**\n🌤️ **Partly Sunny** \n- **High**: 77°F \n- **Wind**: South 7-16 mph \n- **Precipitation**: 10% chance \n\n### **Sunday Night** \n🌧️ **Chance Rain Showers** (after 8pm) \n- **Low**: 72°F \n- **Wind**: South 6-15 mph \n- **Precipitation**: 40% chance \n\n### **Monday (Aug 25)** \n🌦️ **Chance Rain Showers** \n- **High**: 80°F \n- **Wind**: Southwest 6-10 mph \n- **Precipitation**: 40% chance \n\n## **Looking Ahead (Tuesday - Saturday)**\n\n**Tuesday**: ☀️ Sunny, High 76°F - Beautiful day! \n**Wednesday**: 🌤️ Mostly Sunny, High 73°F \n**Thursday**: 🌤️ Mostly Sunny, High 76°F \n**Friday**: 🌤️ Mostly Sunny, High 76°F \n**Saturday**: 🌤️ Mostly Sunny, High 75°F \n\n## **Key Highlights**\n- **Pleasant temperatures** in the mid-70s to near 80°F\n- **Possible rain** Sunday night through Monday morning\n- **Great weather** returning Tuesday with sunny skies\n- **Comfortable humidity** levels expected\n- **Light to moderate winds** throughout the period\n\nThis forecast shows typical late August weather for NYC with comfortable temperatures and mostly pleasant conditions, with just a brief chance of showers early in the week before clearing up nicely!\n", "finish_reason": "end_turn" }, "role": "assistant" } ] }, "input": { "messages": [ { "content": "You are a weather assistant with HTTP capabilities. You can:\n\n1. Make HTTP requests to the National Weather Service API\n2. Process and display weather forecast data\n3. Provide weather information for locations in the United States\n\nWhen retrieving weather information:\n1. First get the coordinates or grid information using https://api.weather.gov/points/{latitude},{longitude} or https://api.weather.gov/points/{zipcode}\n2. Then use the returned forecast URL to get the actual forecast\n\nWhen displaying responses:\n- Format weather data in a human-readable way\n- Highlight important information like temperature, precipitation, and alerts\n- Handle errors appropriately\n- Convert technical terms to user-friendly language\n\nAlways explain the weather conditions clearly and provide context for the forecast.\n", "role": "system" }, { "content": { "content": "[{\"text\": \"What is the weather in New York?\"}]" }, "role": "user" } ] } }, "attributes": { "event.name": "strands.telemetry.tracer" }, "flags": 1, "traceId": "68aa81cd356fb8ea277cca6fbd34640d", "spanId": "5a3802fa510a2725" } ``` By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. Co-authored-by: Michael He <[email protected]>
1 parent 9e094eb commit 56f3b08

File tree

3 files changed

+391
-3
lines changed

3 files changed

+391
-3
lines changed

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/llo_handler.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
ROLE_SYSTEM = "system"
1717
ROLE_USER = "user"
1818
ROLE_ASSISTANT = "assistant"
19+
ROLE_TOOL = "tool"
1920

2021
_logger = logging.getLogger(__name__)
2122

@@ -137,6 +138,35 @@ class PatternConfig(TypedDict, total=False):
137138
"role": ROLE_USER,
138139
"source": "prompt",
139140
},
141+
# OTel GenAI Semantic Convention used by the latest Strands SDK
142+
# References:
143+
# - OTel GenAI SemConv: https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-events/
144+
# - Strands SDK PR(introduced in v0.1.9): https://github.com/strands-agents/sdk-python/pull/319
145+
"gen_ai.user.message": {
146+
"type": PatternType.DIRECT,
147+
"role": ROLE_USER,
148+
"source": "prompt",
149+
},
150+
"gen_ai.assistant.message": {
151+
"type": PatternType.DIRECT,
152+
"role": ROLE_ASSISTANT,
153+
"source": "output",
154+
},
155+
"gen_ai.system.message": {
156+
"type": PatternType.DIRECT,
157+
"role": ROLE_SYSTEM,
158+
"source": "prompt",
159+
},
160+
"gen_ai.tool.message": {
161+
"type": PatternType.DIRECT,
162+
"role": ROLE_TOOL,
163+
"source": "prompt",
164+
},
165+
"gen_ai.choice": {
166+
"type": PatternType.DIRECT,
167+
"role": ROLE_ASSISTANT,
168+
"source": "output",
169+
},
140170
}
141171

142172

@@ -214,6 +244,7 @@ def _collect_all_llo_messages(self, span: ReadableSpan, attributes: types.Attrib
214244
for attr_key, value in attributes.items():
215245
if attr_key in self._exact_match_patterns:
216246
config = self._pattern_configs[attr_key]
247+
217248
messages.append(
218249
{"content": value, "role": config.get("role", "unknown"), "source": config.get("source", "unknown")}
219250
)
@@ -279,6 +310,12 @@ def _collect_llo_attributes_from_span(self, span: ReadableSpan) -> Dict[str, Any
279310
# Collect from span events
280311
if span.events:
281312
for event in span.events:
313+
# Check if event name itself is an LLO pattern (e.g., "gen_ai.user.message")
314+
if self._is_llo_attribute(event.name):
315+
# Put all event attributes as the content as LLO in log event
316+
all_llo_attributes[event.name] = dict(event.attributes) if event.attributes else {}
317+
318+
# Also check traditional pattern - LLO attributes within event attributes
282319
if event.attributes:
283320
for key, value in event.attributes.items():
284321
if self._is_llo_attribute(key):
@@ -372,6 +409,10 @@ def _filter_span_events(self, span: ReadableSpan) -> None:
372409
updated_events = []
373410

374411
for event in span.events:
412+
# Skip entire event if event name is an LLO pattern
413+
if self._is_llo_attribute(event.name):
414+
continue
415+
375416
if not event.attributes:
376417
updated_events.append(event)
377418
continue
@@ -417,7 +458,7 @@ def _group_messages_by_type(self, messages: List[Dict[str, Any]]) -> Dict[str, L
417458
elif role == ROLE_ASSISTANT:
418459
output_messages.append(formatted_message)
419460
else:
420-
# Route based on source for non-standard roles
461+
# Route based on source for non-standard roles including tool
421462
if any(key in message.get("source", "") for key in ["completion", "output", "result"]):
422463
output_messages.append(formatted_message)
423464
else:

0 commit comments

Comments
 (0)