5656
5757 # Or, manage the lifecycle manually
5858 tool = ToolCall(name="get_weather", arguments={"location": "Paris"})
59- handler.start_tool_call (tool)
59+ handler.start (tool)
6060 tool.tool_result = {"temp": 20}
61- handler.stop_tool_call (tool)
61+ handler.stop (tool)
6262"""
6363
6464from __future__ import annotations
8787 _apply_embedding_finish_attributes ,
8888 _apply_error_attributes ,
8989 _apply_llm_finish_attributes ,
90- _get_embedding_span_name ,
91- _get_llm_span_name ,
9290 _apply_tool_call_attributes ,
9391 _finish_tool_call_span ,
92+ _get_embedding_span_name ,
93+ _get_llm_span_name ,
9494 _get_tool_call_span_name ,
9595 _maybe_emit_llm_event ,
9696)
@@ -166,17 +166,25 @@ def _record_embedding_metrics(
166166
167167 def _start (self , invocation : _T ) -> _T :
168168 """Start a GenAI invocation and create a pending span entry."""
169+ span_kind = SpanKind .CLIENT
169170 if isinstance (invocation , LLMInvocation ):
170171 span_name = _get_llm_span_name (invocation )
171172 elif isinstance (invocation , EmbeddingInvocation ):
172173 span_name = _get_embedding_span_name (invocation )
174+ elif isinstance (invocation , ToolCall ):
175+ span_name = _get_tool_call_span_name (invocation )
176+ span_kind = SpanKind .INTERNAL
173177 else :
174178 span_name = ""
179+
175180 span = self ._tracer .start_span (
176181 name = span_name ,
177- kind = SpanKind . CLIENT ,
182+ kind = span_kind ,
178183 )
179- # Record a monotonic start timestamp (seconds) for duration
184+ if isinstance (invocation , ToolCall ):
185+ _apply_tool_call_attributes (
186+ span , invocation , capture_content = False
187+ )
180188 # calculation using timeit.default_timer.
181189 invocation .monotonic_start_s = timeit .default_timer ()
182190 invocation .span = span
@@ -200,6 +208,8 @@ def _stop(self, invocation: _T) -> _T:
200208 elif isinstance (invocation , EmbeddingInvocation ):
201209 _apply_embedding_finish_attributes (span , invocation )
202210 self ._record_embedding_metrics (invocation , span )
211+ elif isinstance (invocation , ToolCall ):
212+ _finish_tool_call_span (span , invocation , capture_content = True )
203213 finally :
204214 # Detach context and end span even if finishing fails
205215 otel_context .detach (invocation .context_token )
@@ -230,6 +240,10 @@ def _fail(self, invocation: _T, error: Error) -> _T:
230240 self ._record_embedding_metrics (
231241 invocation , span , error_type = error_type
232242 )
243+ elif isinstance (invocation , ToolCall ):
244+ invocation .error_type = error_type
245+ _finish_tool_call_span (span , invocation , capture_content = True )
246+ span .set_status (Status (StatusCode .ERROR , error .message ))
233247 finally :
234248 # Detach context and end span even if finishing fails
235249 otel_context .detach (invocation .context_token )
@@ -266,105 +280,6 @@ def fail_llm(
266280 """Fail an LLM invocation and end its span with error status."""
267281 return self ._fail (invocation , error )
268282
269- def start_tool_call (
270- self ,
271- tool_call : ToolCall ,
272- ) -> ToolCall :
273- """Start a tool call execution and create a span.
274-
275- Creates an execute_tool span per span.gen_ai.execute_tool.internal spec:
276- - Span kind: INTERNAL
277- - Span name: "execute_tool {tool_name}"
278- - Required attribute: gen_ai.operation.name = "execute_tool"
279-
280- Args:
281- tool_call: ToolCall instance to track
282-
283- Returns:
284- The same ToolCall with span and context_token set
285- """
286- # Create span with INTERNAL kind per spec
287- span = self ._tracer .start_span (
288- name = _get_tool_call_span_name (tool_call ),
289- kind = SpanKind .INTERNAL ,
290- )
291-
292- # Apply initial attributes (but not result yet)
293- # capture_content=False for start, only structure attributes
294- _apply_tool_call_attributes (span , tool_call , capture_content = False )
295-
296- # Record monotonic start time for duration calculation
297- tool_call .monotonic_start_s = timeit .default_timer ()
298-
299- # Attach to context
300- tool_call .span = span
301- tool_call .context_token = otel_context .attach (
302- set_span_in_context (span )
303- )
304-
305- return tool_call
306-
307- def stop_tool_call (self , tool_call : ToolCall ) -> ToolCall : # pylint: disable=no-self-use
308- """Finalize a tool call execution successfully.
309-
310- Applies final attributes including tool_result, sets OK status, and ends span.
311-
312- Args:
313- tool_call: ToolCall instance with span to finalize
314-
315- Returns:
316- The same ToolCall
317- """
318- if tool_call .context_token is None or tool_call .span is None :
319- # TODO: Provide feedback that this invocation was not started
320- return tool_call
321-
322- span = tool_call .span
323-
324- # Finalize span with result (capture_content=True allows result if mode permits)
325- _finish_tool_call_span (span , tool_call , capture_content = True )
326-
327- # Detach context and end span
328- otel_context .detach (tool_call .context_token )
329- span .end ()
330-
331- return tool_call
332-
333- def fail_tool_call ( # pylint: disable=no-self-use
334- self , tool_call : ToolCall , error : Error
335- ) -> ToolCall :
336- """Fail a tool call execution with error.
337-
338- Sets error attributes, ERROR status, and ends span.
339-
340- Args:
341- tool_call: ToolCall instance with span to fail
342- error: Error details
343-
344- Returns:
345- The same ToolCall
346- """
347- if tool_call .context_token is None or tool_call .span is None :
348- # TODO: Provide feedback that this invocation was not started
349- return tool_call
350-
351- span = tool_call .span
352-
353- # Set error_type on tool_call so it's included in attributes
354- tool_call .error_type = error .type .__qualname__
355-
356- # Finalize span with error
357- _finish_tool_call_span (span , tool_call , capture_content = True )
358-
359- # Apply additional error status with message
360- span .set_status (Status (StatusCode .ERROR , error .message ))
361-
362- # Detach context and end span
363- otel_context .detach (tool_call .context_token )
364- span .end ()
365-
366- return tool_call
367-
368283 @contextmanager
369284 def tool_call (
370285 self , tool_call : ToolCall | None = None
@@ -388,15 +303,13 @@ def tool_call(
388303 arguments = {},
389304 id = None ,
390305 )
391- self .start_tool_call (tool_call )
306+ self .start (tool_call )
392307 try :
393308 yield tool_call
394309 except Exception as exc :
395- self .fail_tool_call (
396- tool_call , Error (message = str (exc ), type = type (exc ))
397- )
310+ self .fail (tool_call , Error (message = str (exc ), type = type (exc )))
398311 raise
399- self .stop_tool_call (tool_call )
312+ self .stop (tool_call )
400313
401314 @contextmanager
402315 def llm (
0 commit comments