xgae 0.1.5__py3-none-any.whl → 0.1.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of xgae might be problematic. Click here for more details.

@@ -1,16 +1,14 @@
1
1
  import asyncio
2
2
  import json
3
3
  import logging
4
- from dataclasses import dataclass
5
- from typing import List, Dict, Any, Optional, Tuple, Union, Literal, Callable,TypedDict,AsyncGenerator
4
+
6
5
  from abc import ABC, abstractmethod
6
+ from dataclasses import dataclass
7
+ from typing import List, Dict, Any, Optional, Tuple, Union, Literal, Callable, TypedDict, AsyncGenerator
7
8
 
8
- from xgae.engine.xga_base import XGAToolResult, XGAResponseMsg, XGAToolBox
9
- # from xgae.utils.setup_env import langfuse
10
- from xgae.utils.json_helpers import (
11
- safe_json_parse,
12
- format_for_yield
13
- )
9
+ from xgae.engine.engine_base import XGAToolResult, XGAToolBox
10
+ from xgae.utils import langfuse
11
+ from xgae.utils.json_helpers import safe_json_parse,format_for_yield
14
12
  from xgae.utils.xml_tool_parser import XMLToolParser
15
13
 
16
14
  # Type alias for XML result adding strategy
@@ -19,35 +17,24 @@ XmlAddingStrategy = Literal["user_message", "assistant_message", "inline_edit"]
19
17
  # Type alias for tool execution strategy
20
18
  ToolExecutionStrategy = Literal["sequential", "parallel"]
21
19
 
22
- class TaskResponseContext(TypedDict, total=False):
20
+ class TaskResponserContext(TypedDict, total=False):
23
21
  is_stream: bool
24
22
  task_id: str
25
23
  task_run_id: str
26
24
  trace_id: str
27
25
  model_name: str
28
- max_xml_tool_calls: int
26
+ max_xml_tool_calls: int # LLM generate max_xml_tool limit, 0 is no limit
29
27
  add_response_msg_func: Callable
30
28
  tool_box: XGAToolBox
31
- tool_execution_strategy: Literal["sequential", "parallel"]
32
- xml_adding_strategy: Literal["user_message", "assistant_message", "inline_edit"]
29
+ tool_execution_strategy: ToolExecutionStrategy
30
+ xml_adding_strategy: XmlAddingStrategy
33
31
 
34
32
 
35
33
  class TaskRunContinuousState(TypedDict, total=False):
36
34
  accumulated_content: str
37
35
  auto_continue_count: int
38
36
  auto_continue: bool
39
-
40
- class Span:
41
- def end(self, **kwargs):
42
- pass
43
-
44
- class Trace:
45
- def event(self, **kwargs):
46
- pass
47
-
48
- def span(self, **kwargs):
49
- return Span()
50
-
37
+ max_auto_run: int
51
38
 
52
39
  @dataclass
53
40
  class ToolExecutionContext:
@@ -63,21 +50,27 @@ class ToolExecutionContext:
63
50
 
64
51
 
65
52
  class TaskResponseProcessor(ABC):
66
- def __init__(self, response_context: TaskResponseContext):
53
+ def __init__(self, response_context: TaskResponserContext):
67
54
  self.response_context = response_context
68
55
 
69
- self.task_id = response_context.get("task_id", "")
70
- self.task_run_id = response_context.get("task_run_id", "")
56
+ self.task_id = response_context.get("task_id")
57
+ self.task_run_id = response_context.get("task_run_id")
58
+ self.tool_execution_strategy = self.response_context.get("tool_execution_strategy", "parallel")
59
+ self.xml_adding_strategy = self.response_context.get("xml_adding_strategy", "user_message")
60
+ self.max_xml_tool_calls = self.response_context.get("max_xml_tool_calls", 0)
71
61
 
72
- # Initialize the XML parser
73
- self.trace = Trace()
62
+ self.trace_context = {
63
+ "trace_id": self.response_context.get("trace_id"),
64
+ "parent_span_id": None
65
+ }
74
66
 
75
- self.add_message = response_context.get("add_context_msg")
76
- self._add_message_with_agent_info = self.add_message
77
- self.tool_box = response_context.get("tool_box")
67
+ self.add_response_message = response_context.get("add_response_msg_func")
78
68
 
69
+ self.tool_box = response_context.get("tool_box")
79
70
  self.xml_parser = XMLToolParser()
80
71
 
72
+
73
+
81
74
  @abstractmethod
82
75
  async def process_response(self,
83
76
  llm_response: AsyncGenerator,
@@ -86,16 +79,6 @@ class TaskResponseProcessor(ABC):
86
79
  ) -> AsyncGenerator[Dict[str, Any], None]:
87
80
  pass
88
81
 
89
- async def _yield_message(self, message_obj: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
90
- """Helper to yield a message with proper formatting.
91
-
92
- Ensures that content and metadata are JSON strings for client compatibility.
93
- """
94
- if message_obj:
95
- return format_for_yield(message_obj)
96
- return None
97
-
98
-
99
82
 
100
83
  def _extract_xml_chunks(self, content: str) -> List[str]:
101
84
  """Extract complete XML chunks using start and end pattern matching."""
@@ -190,12 +173,12 @@ class TaskResponseProcessor(ABC):
190
173
  except Exception as e:
191
174
  logging.error(f"Error extracting XML chunks: {e}")
192
175
  logging.error(f"Content was: {content}")
193
- self.trace.event(name="error_extracting_xml_chunks", level="ERROR",
176
+ langfuse.create_event(trace_context=self.trace_context, name="error_extracting_xml_chunks", level="ERROR",
194
177
  status_message=(f"Error extracting XML chunks: {e}"), metadata={"content": content})
195
178
 
196
179
  return chunks
197
180
 
198
- def _parse_xml_tool_call(self, xml_chunk: str) -> Optional[Tuple[Dict[str, Any], Dict[str, Any]]]:
181
+ def _parse_xml_tool_call(self, xml_chunk: str) -> Tuple[Dict[str, Any], Dict[str, Any]]:
199
182
  """Parse XML chunk into tool call format and return parsing details.
200
183
 
201
184
  Returns:
@@ -215,6 +198,9 @@ class TaskResponseProcessor(ABC):
215
198
 
216
199
  # Take the first tool call (should only be one per chunk)
217
200
  xml_tool_call = parsed_calls[0]
201
+ if not xml_tool_call.function_name:
202
+ logging.error(f"xml_tool_call function name is empty: {xml_tool_call}")
203
+ return None
218
204
 
219
205
  # Convert to the expected format
220
206
  tool_call = {
@@ -237,7 +223,7 @@ class TaskResponseProcessor(ABC):
237
223
  except Exception as e:
238
224
  logging.error(f"Error parsing XML chunk: {e}")
239
225
  logging.error(f"XML chunk was: {xml_chunk}")
240
- self.trace.event(name="error_parsing_xml_chunk", level="ERROR",
226
+ langfuse.create_event(trace_context=self.trace_context, name="error_parsing_xml_chunk", level="ERROR",
241
227
  status_message=(f"Error parsing XML chunk: {e}"), metadata={"xml_chunk": xml_chunk})
242
228
  return None
243
229
 
@@ -263,21 +249,22 @@ class TaskResponseProcessor(ABC):
263
249
 
264
250
  except Exception as e:
265
251
  logging.error(f"Error parsing XML tool calls: {e}", exc_info=True)
266
- self.trace.event(name="error_parsing_xml_tool_calls", level="ERROR",
252
+ langfuse.create_event(trace_context=self.trace_context, name="error_parsing_xml_tool_calls", level="ERROR",
267
253
  status_message=(f"Error parsing XML tool calls: {e}"), metadata={"content": content})
268
254
 
269
255
  return parsed_data
270
256
 
271
- # Tool execution methods
257
+
272
258
  async def _execute_tool(self, tool_call: Dict[str, Any]) -> XGAToolResult:
273
259
  """Execute a single tool call and return the result."""
274
- span = self.trace.span(name=f"execute_tool.{tool_call['function_name']}", input=tool_call["arguments"])
260
+ span = langfuse.start_span(trace_context=self.trace_context, name=f"execute_tool.{tool_call['function_name']}", input=tool_call["arguments"])
261
+ self.trace_context["parent_span_id"] = span.id
275
262
  try:
276
263
  function_name = tool_call["function_name"]
277
264
  arguments = tool_call["arguments"]
278
265
 
279
266
  logging.info(f"Executing tool: {function_name} with arguments: {arguments}")
280
- self.trace.event(name="executing_tool", level="DEFAULT",
267
+ langfuse.create_event(trace_context=self.trace_context, name="executing_tool", level="DEFAULT",
281
268
  status_message=(f"Executing tool: {function_name} with arguments: {arguments}"))
282
269
 
283
270
  if isinstance(arguments, str):
@@ -286,21 +273,21 @@ class TaskResponseProcessor(ABC):
286
273
  except json.JSONDecodeError:
287
274
  arguments = {"text": arguments} # @todo modify
288
275
 
289
- # Get available functions from tool registry
290
- #available_functions = self.tool_registry.get_available_functions()
291
276
  result = None
292
- available_function_names = self.tool_box.get_task_tool_names(self.task_id)
293
- if function_name in available_function_names:
277
+ available_tool_names = self.tool_box.get_task_tool_names(self.task_id)
278
+ if function_name in available_tool_names:
294
279
  result = await self.tool_box.call_tool(self.task_id, function_name, arguments)
295
280
  else:
296
281
  logging.error(f"Tool function '{function_name}' not found in registry")
297
282
  result = XGAToolResult(success=False, output=f"Tool function '{function_name}' not found")
298
283
  logging.info(f"Tool execution complete: {function_name} -> {result}")
299
- span.end(status_message="tool_executed", output=result)
284
+ langfuse.update_current_span(status_message="tool_executed", output=result)
285
+
300
286
  return result
301
287
  except Exception as e:
302
288
  logging.error(f"Error executing tool {tool_call['function_name']}: {str(e)}", exc_info=True)
303
- span.end(status_message="tool_execution_error", output=f"Error executing tool: {str(e)}", level="ERROR")
289
+
290
+ langfuse.update_current_span(status_message="tool_execution_error", output=f"Error executing tool: {str(e)}", level="ERROR")
304
291
  return XGAToolResult(success=False, output=f"Error executing tool: {str(e)}")
305
292
 
306
293
  async def _execute_tools(
@@ -309,7 +296,7 @@ class TaskResponseProcessor(ABC):
309
296
  execution_strategy: ToolExecutionStrategy = "sequential"
310
297
  ) -> List[Tuple[Dict[str, Any], XGAToolResult]]:
311
298
  logging.info(f"Executing {len(tool_calls)} tools with strategy: {execution_strategy}")
312
- self.trace.event(name="executing_tools_with_strategy", level="DEFAULT",
299
+ langfuse.create_event(trace_context=self.trace_context, name="executing_tools_with_strategy", level="DEFAULT",
313
300
  status_message=(f"Executing {len(tool_calls)} tools with strategy: {execution_strategy}"))
314
301
 
315
302
  if execution_strategy == "sequential":
@@ -335,54 +322,42 @@ class TaskResponseProcessor(ABC):
335
322
  """
336
323
  if not tool_calls:
337
324
  return []
325
+ tool_names = [t.get('function_name', 'unknown') for t in tool_calls]
326
+ logging.info(f"Executing {len(tool_calls)} tools sequentially: {tool_names}")
327
+ langfuse.create_event(trace_context=self.trace_context, name="executing_tools_sequentially", level="DEFAULT",
328
+ status_message=(f"Executing {len(tool_calls)} tools sequentially: {tool_names}"))
338
329
 
339
- try:
340
- tool_names = [t.get('function_name', 'unknown') for t in tool_calls]
341
- logging.info(f"Executing {len(tool_calls)} tools sequentially: {tool_names}")
342
- self.trace.event(name="executing_tools_sequentially", level="DEFAULT",
343
- status_message=(f"Executing {len(tool_calls)} tools sequentially: {tool_names}"))
344
-
345
- results = []
346
- for index, tool_call in enumerate(tool_calls):
347
- tool_name = tool_call.get('function_name', 'unknown')
348
- logging.debug(f"Executing tool {index + 1}/{len(tool_calls)}: {tool_name}")
330
+ results = []
331
+ for index, tool_call in enumerate(tool_calls):
332
+ tool_name = tool_call.get('function_name', 'unknown')
333
+ logging.debug(f"Executing tool {index + 1}/{len(tool_calls)}: {tool_name}")
349
334
 
350
- try:
351
- result = await self._execute_tool(tool_call)
352
- results.append((tool_call, result))
353
- logging.debug(f"Completed tool {tool_name} with success={result.success}")
354
-
355
- # Check if this is a terminating tool (ask or complete)
356
- if tool_name in ['ask', 'complete']:
357
- logging.info(f"Terminating tool '{tool_name}' executed. Stopping further tool execution.")
358
- self.trace.event(name="terminating_tool_executed", level="DEFAULT", status_message=(
335
+ try:
336
+ result = await self._execute_tool(tool_call)
337
+ results.append((tool_call, result))
338
+ logging.debug(f"Completed tool {tool_name} with success={result.success}")
339
+
340
+ # Check if this is a terminating tool (ask or complete)
341
+ if tool_name in ['ask', 'complete']:
342
+ logging.info(f"Terminating tool '{tool_name}' executed. Stopping further tool execution.")
343
+ langfuse.create_event(trace_context=self.trace_context, name="terminating_tool_executed",
344
+ level="DEFAULT", status_message=(
359
345
  f"Terminating tool '{tool_name}' executed. Stopping further tool execution."))
360
- break # Stop executing remaining tools
346
+ break # Stop executing remaining tools
361
347
 
362
- except Exception as e:
363
- logging.error(f"Error executing tool {tool_name}: {str(e)}")
364
- self.trace.event(name="error_executing_tool", level="ERROR",
365
- status_message=(f"Error executing tool {tool_name}: {str(e)}"))
366
- error_result = XGAToolResult(success=False, output=f"Error executing tool: {str(e)}")
367
- results.append((tool_call, error_result))
348
+ except Exception as e:
349
+ logging.error(f"Error executing tool {tool_name}: {str(e)}")
350
+ langfuse.create_event(trace_context=self.trace_context, name="error_executing_tool", level="ERROR",
351
+ status_message=(f"Error executing tool {tool_name}: {str(e)}"))
352
+ error_result = XGAToolResult(success=False, output=f"Error executing tool: {str(e)}")
353
+ results.append((tool_call, error_result))
368
354
 
369
- logging.info(f"Sequential execution completed for {len(results)} tools (out of {len(tool_calls)} total)")
370
- self.trace.event(name="sequential_execution_completed", level="DEFAULT", status_message=(
371
- f"Sequential execution completed for {len(results)} tools (out of {len(tool_calls)} total)"))
372
- return results
355
+ logging.info(f"Sequential execution completed for {len(results)} tools (out of {len(tool_calls)} total)")
356
+ langfuse.create_event(trace_context=self.trace_context, name="sequential_execution_completed", level="DEFAULT",
357
+ status_message=(
358
+ f"Sequential execution completed for {len(results)} tools (out of {len(tool_calls)} total)"))
359
+ return results
373
360
 
374
- except Exception as e:
375
- logging.error(f"Error in sequential tool execution: {str(e)}", exc_info=True)
376
- # Return partial results plus error results for remaining tools
377
- completed_results = results if 'results' in locals() else []
378
- completed_tool_names = [r[0].get('function_name', 'unknown') for r in completed_results]
379
- remaining_tools = [t for t in tool_calls if t.get('function_name', 'unknown') not in completed_tool_names]
380
-
381
- # Add error results for remaining tools
382
- error_results = [(tool, XGAToolResult(success=False, output=f"Execution error: {str(e)}"))
383
- for tool in remaining_tools]
384
-
385
- return completed_results + error_results
386
361
 
387
362
  async def _execute_tools_in_parallel(self, tool_calls: List[Dict[str, Any]]) -> List[Tuple[Dict[str, Any], XGAToolResult]]:
388
363
  if not tool_calls:
@@ -391,7 +366,7 @@ class TaskResponseProcessor(ABC):
391
366
  try:
392
367
  tool_names = [t.get('function_name', 'unknown') for t in tool_calls]
393
368
  logging.info(f"Executing {len(tool_calls)} tools in parallel: {tool_names}")
394
- self.trace.event(name="executing_tools_in_parallel", level="DEFAULT",
369
+ langfuse.create_event(trace_context=self.trace_context, name="executing_tools_in_parallel", level="DEFAULT",
395
370
  status_message=(f"Executing {len(tool_calls)} tools in parallel: {tool_names}"))
396
371
 
397
372
  # Create tasks for all tool calls
@@ -405,7 +380,7 @@ class TaskResponseProcessor(ABC):
405
380
  for i, (tool_call, result) in enumerate(zip(tool_calls, results)):
406
381
  if isinstance(result, Exception):
407
382
  logging.error(f"Error executing tool {tool_call.get('function_name', 'unknown')}: {str(result)}")
408
- self.trace.event(name="error_executing_tool", level="ERROR", status_message=(
383
+ langfuse.create_event(trace_context=self.trace_context, name="error_executing_tool", level="ERROR", status_message=(
409
384
  f"Error executing tool {tool_call.get('function_name', 'unknown')}: {str(result)}"))
410
385
  # Create error result
411
386
  error_result = XGAToolResult(success=False, output=f"Error executing tool: {str(result)}")
@@ -414,24 +389,23 @@ class TaskResponseProcessor(ABC):
414
389
  processed_results.append((tool_call, result))
415
390
 
416
391
  logging.info(f"Parallel execution completed for {len(tool_calls)} tools")
417
- self.trace.event(name="parallel_execution_completed", level="DEFAULT",
392
+ langfuse.create_event(trace_context=self.trace_context, name="parallel_execution_completed", level="DEFAULT",
418
393
  status_message=(f"Parallel execution completed for {len(tool_calls)} tools"))
419
394
  return processed_results
420
395
 
421
396
  except Exception as e:
422
397
  logging.error(f"Error in parallel tool execution: {str(e)}", exc_info=True)
423
- self.trace.event(name="error_in_parallel_tool_execution", level="ERROR",
398
+ langfuse.create_event(trace_context=self.trace_context, name="error_in_parallel_tool_execution", level="ERROR",
424
399
  status_message=(f"Error in parallel tool execution: {str(e)}"))
425
400
  # Return error results for all tools if the gather itself fails
426
401
  return [(tool_call, XGAToolResult(success=False, output=f"Execution error: {str(e)}"))
427
402
  for tool_call in tool_calls]
428
403
 
429
- def _add_tool_result(
404
+ def _add_tool_messsage(
430
405
  self,
431
- thread_id: str,
432
406
  tool_call: Dict[str, Any],
433
407
  result: XGAToolResult,
434
- strategy: Union[XmlAddingStrategy, str] = "assistant_message",
408
+ strategy: XmlAddingStrategy = "assistant_message",
435
409
  assistant_message_id: Optional[str] = None,
436
410
  parsing_details: Optional[Dict[str, Any]] = None
437
411
  ) -> Optional[Dict[str, Any]]: # Return the full message object
@@ -443,63 +417,16 @@ class TaskResponseProcessor(ABC):
443
417
  if assistant_message_id:
444
418
  metadata["assistant_message_id"] = assistant_message_id
445
419
  logging.info(f"Linking tool result to assistant message: {assistant_message_id}")
446
- self.trace.event(name="linking_tool_result_to_assistant_message", level="DEFAULT",
420
+ langfuse.create_event(trace_context=self.trace_context, name="linking_tool_result_to_assistant_message", level="DEFAULT",
447
421
  status_message=(f"Linking tool result to assistant message: {assistant_message_id}"))
448
422
 
449
423
  # --- Add parsing details to metadata if available ---
450
424
  if parsing_details:
451
425
  metadata["parsing_details"] = parsing_details
452
426
  logging.info("Adding parsing_details to tool result metadata")
453
- self.trace.event(name="adding_parsing_details_to_tool_result_metadata", level="DEFAULT",
427
+ langfuse.create_event(trace_context=self.trace_context, name="adding_parsing_details_to_tool_result_metadata", level="DEFAULT",
454
428
  status_message=(f"Adding parsing_details to tool result metadata"),
455
429
  metadata={"parsing_details": parsing_details})
456
- # ---
457
-
458
- # Check if this is a native function call (has id field)
459
- if "id" in tool_call:
460
- # Format as a proper tool message according to OpenAI spec
461
- function_name = tool_call.get("function_name", "")
462
-
463
- # Format the tool result content - tool role needs string content
464
- if isinstance(result, str):
465
- content = result
466
- elif hasattr(result, 'output'):
467
- # If it's a XGAToolResult object
468
- if isinstance(result.output, dict) or isinstance(result.output, list):
469
- # If output is already a dict or list, convert to JSON string
470
- content = json.dumps(result.output)
471
- else:
472
- # Otherwise just use the string representation
473
- content = str(result.output)
474
- else:
475
- # Fallback to string representation of the whole result
476
- content = str(result)
477
-
478
- logging.info(f"Formatted tool result content: {content[:100]}...")
479
- self.trace.event(name="formatted_tool_result_content", level="DEFAULT",
480
- status_message=(f"Formatted tool result content: {content[:100]}..."))
481
-
482
- # Create the tool response message with proper format
483
- tool_message = {
484
- "role": "tool",
485
- "tool_call_id": tool_call["id"],
486
- "name": function_name,
487
- "content": content
488
- }
489
-
490
- logging.info(f"Adding native tool result for tool_call_id={tool_call['id']} with role=tool")
491
- self.trace.event(name="adding_native_tool_result_for_tool_call_id", level="DEFAULT", status_message=(
492
- f"Adding native tool result for tool_call_id={tool_call['id']} with role=tool"))
493
-
494
- # Add as a tool message to the conversation history
495
- # This makes the result visible to the LLM in the next turn
496
- message_obj = self.add_message(
497
- type="tool", # Special type for tool responses
498
- content=tool_message,
499
- is_llm_message=True,
500
- metadata=metadata
501
- )
502
- return message_obj # Return the full message object
503
430
 
504
431
  # For XML and other non-native tools, use the new structured format
505
432
  # Determine message role based on strategy
@@ -525,7 +452,7 @@ class TaskResponseProcessor(ABC):
525
452
  metadata = {}
526
453
  metadata['frontend_content'] = structured_result_for_frontend
527
454
 
528
- message_obj = self._add_message_with_agent_info(
455
+ message_obj = self.add_response_message(
529
456
  type="tool",
530
457
  content=result_message_for_llm, # Save the LLM-friendly version
531
458
  is_llm_message=True,
@@ -544,7 +471,7 @@ class TaskResponseProcessor(ABC):
544
471
  return message_obj # Return the modified message object
545
472
  except Exception as e:
546
473
  logging.error(f"Error adding tool result: {str(e)}", exc_info=True)
547
- self.trace.event(name="error_adding_tool_result", level="ERROR",
474
+ langfuse.create_event(trace_context=self.trace_context, name="error_adding_tool_result", level="ERROR",
548
475
  status_message=(f"Error adding tool result: {str(e)}"),
549
476
  metadata={"tool_call": tool_call, "result": result, "strategy": strategy,
550
477
  "assistant_message_id": assistant_message_id,
@@ -555,7 +482,7 @@ class TaskResponseProcessor(ABC):
555
482
  "role": "user",
556
483
  "content": str(result)
557
484
  }
558
- message_obj = self.add_message(
485
+ message_obj = self.add_response_message(
559
486
  type="tool",
560
487
  content=fallback_message,
561
488
  is_llm_message=True,
@@ -564,7 +491,7 @@ class TaskResponseProcessor(ABC):
564
491
  return message_obj # Return the full message object
565
492
  except Exception as e2:
566
493
  logging.error(f"Failed even with fallback message: {str(e2)}", exc_info=True)
567
- self.trace.event(name="failed_even_with_fallback_message", level="ERROR",
494
+ langfuse.create_event(trace_context=self.trace_context, name="failed_even_with_fallback_message", level="ERROR",
568
495
  status_message=(f"Failed even with fallback message: {str(e2)}"),
569
496
  metadata={"tool_call": tool_call, "result": result, "strategy": strategy,
570
497
  "assistant_message_id": assistant_message_id,
@@ -629,38 +556,29 @@ class TaskResponseProcessor(ABC):
629
556
  )
630
557
 
631
558
  # Set function_name and xml_tag_name fields
632
- if "xml_tag_name" in tool_call:
633
- context.xml_tag_name = tool_call["xml_tag_name"]
634
- context.function_name = tool_call.get("function_name", tool_call["xml_tag_name"])
635
- else:
636
- # For non-XML tools, use function name directly
637
- context.function_name = tool_call.get("function_name", "unknown")
638
- context.xml_tag_name = None
559
+ context.xml_tag_name = tool_call["xml_tag_name"]
560
+ context.function_name = tool_call["function_name"]
639
561
 
640
562
  return context
641
563
 
642
- def _yield_and_save_tool_started(self, context: ToolExecutionContext, thread_id: str, thread_run_id: str) -> \
643
- Optional[Dict[str, Any]]:
564
+ def _add_tool_start_message(self, context: ToolExecutionContext) -> Optional[Dict[str, Any]]:
644
565
  """Formats, saves, and returns a tool started status message."""
645
566
  tool_name = context.xml_tag_name or context.function_name
646
567
  content = {
647
568
  "role": "assistant", "status_type": "tool_started",
648
569
  "function_name": context.function_name, "xml_tag_name": context.xml_tag_name,
649
- "message": f"Starting execution of {tool_name}", "tool_index": context.tool_index,
650
- "tool_call_id": context.tool_call.get("id") # Include tool_call ID if native
570
+ "message": f"Starting execution of {tool_name}", "tool_index": context.tool_index # Include tool_call ID if native
651
571
  }
652
- metadata = {"thread_run_id": thread_run_id}
653
- saved_message_obj = self.add_message(
654
- type="status", content=content, is_llm_message=False, metadata=metadata
572
+
573
+ return self.add_response_message(
574
+ type="status", content=content, is_llm_message=False
655
575
  )
656
- return saved_message_obj # Return the full object (or None if saving failed)
657
576
 
658
- def _yield_and_save_tool_completed(self, context: ToolExecutionContext, tool_message_id: Optional[str],
659
- thread_id: str, thread_run_id: str) -> Optional[Dict[str, Any]]:
577
+ def _add_tool_completed_message(self, context: ToolExecutionContext, tool_message_id: Optional[str]) -> Optional[Dict[str, Any]]:
660
578
  """Formats, saves, and returns a tool completed/failed status message."""
661
579
  if not context.result:
662
580
  # Delegate to error saving if result is missing (e.g., execution failed)
663
- return self._yield_and_save_tool_error(context, thread_id, thread_run_id)
581
+ return self._add_tool_error_message(context)
664
582
 
665
583
  tool_name = context.xml_tag_name or context.function_name
666
584
  status_type = "tool_completed" if context.result.success else "tool_failed"
@@ -672,7 +590,7 @@ class TaskResponseProcessor(ABC):
672
590
  "message": message_text, "tool_index": context.tool_index,
673
591
  "tool_call_id": context.tool_call.get("id")
674
592
  }
675
- metadata = {"thread_run_id": thread_run_id}
593
+ metadata = {}
676
594
  # Add the *actual* tool result message ID to the metadata if available and successful
677
595
  if context.result.success and tool_message_id:
678
596
  metadata["linked_tool_result_message_id"] = tool_message_id
@@ -681,17 +599,15 @@ class TaskResponseProcessor(ABC):
681
599
  if context.function_name in ['ask', 'complete']:
682
600
  metadata["agent_should_terminate"] = "true"
683
601
  logging.info(f"Marking tool status for '{context.function_name}' with termination signal.")
684
- self.trace.event(name="marking_tool_status_for_termination", level="DEFAULT", status_message=(
602
+ langfuse.create_event(trace_context=self.trace_context, name="marking_tool_status_for_termination", level="DEFAULT", status_message=(
685
603
  f"Marking tool status for '{context.function_name}' with termination signal."))
686
604
  # <<< END ADDED >>>
687
605
 
688
- saved_message_obj = self.add_message(
606
+ return self.add_response_message(
689
607
  type="status", content=content, is_llm_message=False, metadata=metadata
690
608
  )
691
- return saved_message_obj
692
609
 
693
- def _yield_and_save_tool_error(self, context: ToolExecutionContext, thread_id: str, thread_run_id: str) -> \
694
- Optional[Dict[str, Any]]:
610
+ def _add_tool_error_message(self, context: ToolExecutionContext) -> Optional[Dict[str, Any]]:
695
611
  """Formats, saves, and returns a tool error status message."""
696
612
  error_msg = str(context.error) if context.error else "Unknown error during tool execution"
697
613
  tool_name = context.xml_tag_name or context.function_name
@@ -702,9 +618,9 @@ class TaskResponseProcessor(ABC):
702
618
  "tool_index": context.tool_index,
703
619
  "tool_call_id": context.tool_call.get("id")
704
620
  }
705
- metadata = {"thread_run_id": thread_run_id}
621
+
706
622
  # Save the status message with is_llm_message=False
707
- saved_message_obj = self.add_message(
708
- type="status", content=content, is_llm_message=False, metadata=metadata
623
+ return self.add_response_message(
624
+ type="status", content=content, is_llm_message=False
709
625
  )
710
- return saved_message_obj
626
+