|
16 | 16 | ROLE_SYSTEM = "system" |
17 | 17 | ROLE_USER = "user" |
18 | 18 | ROLE_ASSISTANT = "assistant" |
| 19 | +ROLE_TOOL = "tool" |
19 | 20 |
|
20 | 21 | _logger = logging.getLogger(__name__) |
21 | 22 |
|
@@ -137,6 +138,35 @@ class PatternConfig(TypedDict, total=False): |
137 | 138 | "role": ROLE_USER, |
138 | 139 | "source": "prompt", |
139 | 140 | }, |
| 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 | + }, |
140 | 170 | } |
141 | 171 |
|
142 | 172 |
|
@@ -214,6 +244,7 @@ def _collect_all_llo_messages(self, span: ReadableSpan, attributes: types.Attrib |
214 | 244 | for attr_key, value in attributes.items(): |
215 | 245 | if attr_key in self._exact_match_patterns: |
216 | 246 | config = self._pattern_configs[attr_key] |
| 247 | + |
217 | 248 | messages.append( |
218 | 249 | {"content": value, "role": config.get("role", "unknown"), "source": config.get("source", "unknown")} |
219 | 250 | ) |
@@ -279,6 +310,12 @@ def _collect_llo_attributes_from_span(self, span: ReadableSpan) -> Dict[str, Any |
279 | 310 | # Collect from span events |
280 | 311 | if span.events: |
281 | 312 | 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 |
282 | 319 | if event.attributes: |
283 | 320 | for key, value in event.attributes.items(): |
284 | 321 | if self._is_llo_attribute(key): |
@@ -372,6 +409,10 @@ def _filter_span_events(self, span: ReadableSpan) -> None: |
372 | 409 | updated_events = [] |
373 | 410 |
|
374 | 411 | 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 | + |
375 | 416 | if not event.attributes: |
376 | 417 | updated_events.append(event) |
377 | 418 | continue |
@@ -417,7 +458,7 @@ def _group_messages_by_type(self, messages: List[Dict[str, Any]]) -> Dict[str, L |
417 | 458 | elif role == ROLE_ASSISTANT: |
418 | 459 | output_messages.append(formatted_message) |
419 | 460 | else: |
420 | | - # Route based on source for non-standard roles |
| 461 | + # Route based on source for non-standard roles including tool |
421 | 462 | if any(key in message.get("source", "") for key in ["completion", "output", "result"]): |
422 | 463 | output_messages.append(formatted_message) |
423 | 464 | else: |
|
0 commit comments