|
8 | 8 | import logging |
9 | 9 | from typing import Any, Dict, Optional, Tuple |
10 | 10 |
|
| 11 | +from opentelemetry.trace import Span |
| 12 | + |
11 | 13 | from agentops.instrumentation.providers.openai.utils import is_openai_v1 |
12 | 14 | from agentops.instrumentation.providers.openai.wrappers.shared import ( |
13 | 15 | model_as_dict, |
14 | 16 | should_send_prompts, |
15 | 17 | ) |
16 | 18 | from agentops.instrumentation.common.attributes import AttributeMap |
17 | 19 | from agentops.semconv import SpanAttributes, LLMRequestTypeValues |
| 20 | +from agentops.semconv.tool import ToolAttributes |
| 21 | +from agentops.semconv.span_kinds import AgentOpsSpanKindValues |
| 22 | + |
| 23 | +from opentelemetry import context as context_api |
| 24 | +from opentelemetry.trace import SpanKind, Status, StatusCode, get_tracer |
18 | 25 |
|
19 | 26 | logger = logging.getLogger(__name__) |
20 | 27 |
|
21 | 28 | LLM_REQUEST_TYPE = LLMRequestTypeValues.CHAT |
22 | 29 |
|
23 | 30 |
|
| 31 | +def _create_tool_span(parent_span, tool_call_data): |
| 32 | + """ |
| 33 | + Create a distinct span for each tool call. |
| 34 | +
|
| 35 | + Args: |
| 36 | + parent_span: The parent LLM span |
| 37 | + tool_call_data: The tool call data dictionary |
| 38 | + """ |
| 39 | + # Get the tracer for this module |
| 40 | + tracer = get_tracer(__name__) |
| 41 | + |
| 42 | + # Create a child span for the tool call |
| 43 | + with tracer.start_as_current_span( |
| 44 | + name=f"tool_call.{tool_call_data['function']['name']}", |
| 45 | + kind=SpanKind.INTERNAL, |
| 46 | + context=context_api.set_value("current_span", parent_span), |
| 47 | + ) as tool_span: |
| 48 | + # Set the span kind to TOOL |
| 49 | + tool_span.set_attribute("agentops.span.kind", AgentOpsSpanKindValues.TOOL) |
| 50 | + |
| 51 | + # Set tool-specific attributes |
| 52 | + tool_span.set_attribute(ToolAttributes.TOOL_NAME, tool_call_data["function"]["name"]) |
| 53 | + tool_span.set_attribute(ToolAttributes.TOOL_PARAMETERS, tool_call_data["function"]["arguments"]) |
| 54 | + tool_span.set_attribute("tool.call.id", tool_call_data["id"]) |
| 55 | + tool_span.set_attribute("tool.call.type", tool_call_data["type"]) |
| 56 | + |
| 57 | + # Set status to OK for successful tool call creation |
| 58 | + tool_span.set_status(Status(StatusCode.OK)) |
| 59 | + |
| 60 | + |
24 | 61 | def handle_chat_attributes( |
25 | 62 | args: Optional[Tuple] = None, |
26 | 63 | kwargs: Optional[Dict] = None, |
27 | 64 | return_value: Optional[Any] = None, |
| 65 | + span: Optional[Span] = None, |
28 | 66 | ) -> AttributeMap: |
29 | 67 | """Extract attributes from chat completion calls. |
30 | 68 |
|
31 | 69 | This function is designed to work with the common wrapper pattern, |
32 | 70 | extracting attributes from the method arguments and return value. |
| 71 | +
|
| 72 | + Args: |
| 73 | + args: Method arguments (not used in this implementation) |
| 74 | + kwargs: Method keyword arguments |
| 75 | + return_value: Method return value |
| 76 | + span: The parent span for creating tool spans |
33 | 77 | """ |
34 | 78 | attributes = { |
35 | 79 | SpanAttributes.LLM_REQUEST_TYPE: LLM_REQUEST_TYPE.value, |
@@ -191,12 +235,20 @@ def handle_chat_attributes( |
191 | 235 | # Tool calls |
192 | 236 | if "tool_calls" in message: |
193 | 237 | tool_calls = message["tool_calls"] |
194 | | - if tool_calls: # Check if tool_calls is not None |
| 238 | + if tool_calls and span is not None: |
195 | 239 | for i, tool_call in enumerate(tool_calls): |
| 240 | + # Convert tool_call to the format expected by _create_tool_span |
196 | 241 | function = tool_call.get("function", {}) |
197 | | - attributes[f"{prefix}.tool_calls.{i}.id"] = tool_call.get("id") |
198 | | - attributes[f"{prefix}.tool_calls.{i}.name"] = function.get("name") |
199 | | - attributes[f"{prefix}.tool_calls.{i}.arguments"] = function.get("arguments") |
| 242 | + tool_call_data = { |
| 243 | + "id": tool_call.get("id", ""), |
| 244 | + "type": tool_call.get("type", "function"), |
| 245 | + "function": { |
| 246 | + "name": function.get("name", ""), |
| 247 | + "arguments": function.get("arguments", ""), |
| 248 | + }, |
| 249 | + } |
| 250 | + # Create a child span for this tool call |
| 251 | + _create_tool_span(span, tool_call_data) |
200 | 252 |
|
201 | 253 | # Prompt filter results |
202 | 254 | if "prompt_filter_results" in response_dict: |
|
0 commit comments