xgae 0.1.13__py3-none-any.whl → 0.1.15__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.

@@ -28,7 +28,8 @@ class TaskResponserContext(TypedDict, total=False):
28
28
  model_name: str
29
29
  max_xml_tool_calls: int # LLM generate max_xml_tool limit, 0 is no limit
30
30
  use_assistant_chunk_msg: bool
31
- tool_execution_strategy: ToolExecutionStrategy
31
+ tool_exec_strategy: ToolExecutionStrategy
32
+ tool_exec_on_stream: bool
32
33
  xml_adding_strategy: XmlAddingStrategy
33
34
  add_response_msg_func: Callable
34
35
  create_response_msg_func: Callable
@@ -48,9 +49,9 @@ class ToolExecutionContext:
48
49
  """Context for a tool execution including call details, result, and display info."""
49
50
  tool_call: Dict[str, Any]
50
51
  tool_index: int
52
+ function_name: str
53
+ xml_tag_name: str
51
54
  result: Optional[XGAToolResult] = None
52
- function_name: Optional[str] = None
53
- xml_tag_name: Optional[str] = None
54
55
  error: Optional[Exception] = None
55
56
  assistant_message_id: Optional[str] = None
56
57
  parsing_details: Optional[Dict[str, Any]] = None
@@ -60,20 +61,23 @@ class TaskResponseProcessor(ABC):
60
61
  def __init__(self, response_context: TaskResponserContext):
61
62
  self.response_context = response_context
62
63
 
63
- self.task_id = response_context.get("task_id")
64
- self.task_run_id = response_context.get("task_run_id")
65
- self.task_no = response_context.get("task_no")
66
- self.tool_execution_strategy = self.response_context.get("tool_execution_strategy", "parallel")
67
- self.xml_adding_strategy = self.response_context.get("xml_adding_strategy", "user_message")
68
- self.max_xml_tool_calls = self.response_context.get("max_xml_tool_calls", 0)
64
+ self.task_id = response_context['task_id']
65
+ self.task_run_id = response_context['task_run_id']
66
+ self.task_no = response_context['task_no']
67
+ self.tool_exec_strategy = response_context['tool_exec_strategy']
68
+ self.tool_exec_on_stream = response_context['tool_exec_on_stream']
69
+ self.xml_adding_strategy = response_context['xml_adding_strategy']
70
+ self.max_xml_tool_calls = response_context['max_xml_tool_calls']
69
71
 
70
- task_langfuse = response_context.get("task_langfuse")
72
+ self.add_response_message = response_context['add_response_msg_func']
73
+ self.create_response_message = response_context['create_response_msg_func']
74
+ self.tool_box = response_context['tool_box']
75
+
76
+ task_langfuse = response_context['task_langfuse']
71
77
  self.root_span = task_langfuse.root_span
72
- self.add_response_message = response_context.get("add_response_msg_func")
73
- self.create_response_message = response_context.get("create_response_msg_func")
74
78
 
75
- self.tool_box = response_context.get("tool_box")
76
- self.xml_parser = XMLToolParser()
79
+ self.xml_parser = XMLToolParser()
80
+
77
81
 
78
82
 
79
83
  @abstractmethod
@@ -207,16 +211,16 @@ class TaskResponseProcessor(ABC):
207
211
 
208
212
  # Convert to the expected format
209
213
  tool_call = {
210
- "function_name": xml_tool_call.function_name,
211
- "xml_tag_name": xml_tool_call.function_name.replace('_', '-'), # For backwards compatibility
212
- "arguments": xml_tool_call.parameters
214
+ 'function_name' : xml_tool_call.function_name,
215
+ 'xml_tag_name' : xml_tool_call.function_name.replace("_", "-"), # For backwards compatibility
216
+ 'arguments' : xml_tool_call.parameters
213
217
  }
214
218
 
215
219
  # Include the parsing details
216
220
  parsing_details = xml_tool_call.parsing_details
217
- parsing_details["raw_xml"] = xml_tool_call.raw_xml
221
+ parsing_details['raw_xml'] = xml_tool_call.raw_xml
218
222
 
219
- logging.debug(f"Parsed new format tool call: {tool_call}")
223
+ logging.debug(f"TaskProcessor parse_xml_tool_call: Parsed new format tool call: {tool_call}")
220
224
  return tool_call, parsing_details
221
225
 
222
226
  # If not the expected <function_calls><invoke> format, return None
@@ -258,7 +262,7 @@ class TaskResponseProcessor(ABC):
258
262
 
259
263
  async def _execute_tool(self, tool_call: Dict[str, Any]) -> XGAToolResult:
260
264
  """Execute a single tool call and return the result."""
261
- function_name = tool_call.get("function_name", "empty_function")
265
+ function_name = tool_call['function_name']
262
266
  exec_tool_span = self.root_span.span(name=f"execute_tool.{function_name}", input=tool_call["arguments"])
263
267
  try:
264
268
  arguments = tool_call["arguments"]
@@ -291,11 +295,10 @@ class TaskResponseProcessor(ABC):
291
295
 
292
296
  return XGAToolResult(success=False, output=f"Executing tool {function_name}, error: {str(e)}")
293
297
 
298
+
294
299
  async def _execute_tools(self, tool_calls: List[Dict[str, Any]],
295
300
  execution_strategy: ToolExecutionStrategy = "sequential"
296
301
  ) -> List[Tuple[Dict[str, Any], XGAToolResult]]:
297
- logging.info(f"TaskProcessor execute_tools: Executing {len(tool_calls)} tools with strategy '{execution_strategy}'")
298
-
299
302
  if execution_strategy == "sequential":
300
303
  return await self._execute_tools_sequentially(tool_calls)
301
304
  elif execution_strategy == "parallel":
@@ -304,314 +307,223 @@ class TaskResponseProcessor(ABC):
304
307
  logging.warning(f"TaskProcessor execute_tools: Unknown execution strategy '{execution_strategy}', use sequential")
305
308
  return await self._execute_tools_sequentially(tool_calls)
306
309
 
307
- # @todo refact below code
310
+
308
311
  async def _execute_tools_sequentially(self, tool_calls: List[Dict[str, Any]]) -> List[Tuple[Dict[str, Any], XGAToolResult]]:
309
312
  """Execute tool calls sequentially and return results.
310
313
 
311
314
  This method executes tool calls one after another, waiting for each tool to complete
312
315
  before starting the next one. This is useful when tools have dependencies on each other.
313
316
 
314
- Args:
315
- tool_calls: List of tool calls to execute
316
-
317
317
  Returns:
318
318
  List of tuples containing the original tool call and its result
319
319
  """
320
320
  if not tool_calls:
321
+ logging.warning("TaskProcessor execute_tools_sequentially: tool_calls is empty")
321
322
  return []
322
323
 
323
324
  tool_names = [t.get('function_name', 'unknown') for t in tool_calls]
324
- logging.info(f"Executing {len(tool_calls)} tools sequentially: {tool_names}")
325
- self.root_span.event(name="task_process_executing_tools_sequentially", level="DEFAULT",
326
- status_message=(f"Executing {len(tool_calls)} tools sequentially: {tool_names}"))
325
+ tool_num = len(tool_calls)
326
+ if tool_num > 1:
327
+ logging.info(f"TaskProcessor execute_tools_sequentially: Executing {tool_num} tools sequentially: {tool_names}")
328
+ self.root_span.event(name="task_process_execute_tools_sequentially", level="DEFAULT",
329
+ status_message=f"Executing {len(tool_calls)} tools sequentially: {tool_names}")
327
330
 
328
331
  results = []
329
332
  for index, tool_call in enumerate(tool_calls):
330
- tool_name = tool_call.get('function_name', 'unknown')
331
- logging.debug(f"Executing tool {index + 1}/{len(tool_calls)}: {tool_name}")
332
-
333
- try:
334
- result = await self._execute_tool(tool_call)
335
- results.append((tool_call, result))
336
- logging.debug(f"Completed tool {tool_name} with success={result.success}")
337
-
338
- # Check if this is a terminating tool (ask or complete)
339
- if tool_name in ['ask', 'complete']:
340
- logging.info(f"Terminating tool '{tool_name}' executed. Stopping further tool execution.")
341
- # self.root_span.event(name="terminating_tool_executed",
342
- # level="DEFAULT", status_message=(f"Terminating tool '{tool_name}' executed. Stopping further tool execution."))
343
- break # Stop executing remaining tools
344
-
345
- except Exception as e:
346
- logging.error(f"Error executing tool {tool_name}: {str(e)}")
347
- self.root_span.event(name="task_process_error_executing_tool", level="ERROR",
348
- status_message=(f"Error executing tool {tool_name}: {str(e)}"))
349
- error_result = XGAToolResult(success=False, output=f"Error executing tool: {str(e)}")
350
- results.append((tool_call, error_result))
351
-
352
- logging.info(f"Sequential execution completed for {len(results)} tools (out of {len(tool_calls)} total)")
353
- # self.root_span.event(name="sequential_execution_completed", level="DEFAULT",
354
- # status_message=(f"Sequential execution completed for {len(results)} tools (out of {len(tool_calls)} total)"))
333
+ tool_name = tool_call['function_name']
334
+ logging.info(f"TaskProcessor execute_tools_sequentially: Executing tool '{tool_name}', sequence={index + 1}/{tool_num}")
335
+ result = await self._execute_tool(tool_call)
336
+ results.append((tool_call, result))
337
+
338
+ # Check if this is a terminating tool (ask or complete)
339
+ if tool_name in ['ask', 'complete']:
340
+ if len(results) < tool_num:
341
+ logging.info(f"TaskProcessor execute_tools_sequentially: Terminating tool '{tool_name}' executed, Stopping further tool execution.")
342
+ self.root_span.event(name="task_process_terminate_tool_executed", level="DEFAULT",
343
+ status_message=f"Terminating tool '{tool_name}' executed, Stopping further tool execution.")
344
+ break
345
+
346
+ logging.info(f"TaskProcessor execute_tools_sequentially: Execution completed for {len(results)} tools, total {tool_num} tools)")
355
347
  return results
356
348
 
357
349
 
358
350
  async def _execute_tools_in_parallel(self, tool_calls: List[Dict[str, Any]]) -> List[Tuple[Dict[str, Any], XGAToolResult]]:
351
+ """Execute tool calls in parallel and return results.
352
+
353
+ This method executes all tool calls simultaneously using asyncio.gather, which
354
+ can significantly improve performance when executing multiple independent tools.
355
+
356
+ Returns:
357
+ List of tuples containing the original tool call and its result
358
+ """
359
359
  if not tool_calls:
360
+ logging.warning("TaskProcessor execute_tools_in_parallel: tool_calls is empty")
360
361
  return []
361
362
 
362
- try:
363
- tool_names = [t.get('function_name', 'unknown') for t in tool_calls]
364
- logging.info(f"Executing {len(tool_calls)} tools in parallel: {tool_names}")
365
- # self.root_span.event(name="executing_tools_in_parallel", level="DEFAULT",
366
- # status_message=(f"Executing {len(tool_calls)} tools in parallel: {tool_names}"))
367
-
368
- # Create tasks for all tool calls
369
- tasks = [self._execute_tool(tool_call) for tool_call in tool_calls]
370
-
371
- # Execute all tasks concurrently with error handling
372
- results = await asyncio.gather(*tasks, return_exceptions=True)
373
-
374
- # Process results and handle any exceptions
375
- processed_results = []
376
- for i, (tool_call, result) in enumerate(zip(tool_calls, results)):
377
- if isinstance(result, Exception):
378
- logging.error(f"Error executing tool {tool_call.get('function_name', 'unknown')}: {str(result)}")
379
- self.root_span.event(name="task_process_error_executing_tool", level="ERROR", status_message=(
380
- f"Error executing tool {tool_call.get('function_name', 'unknown')}: {str(result)}"))
381
- # Create error result
382
- error_result = XGAToolResult(success=False, output=f"Error executing tool: {str(result)}")
383
- processed_results.append((tool_call, error_result))
384
- else:
385
- processed_results.append((tool_call, result))
386
-
387
- logging.info(f"Parallel execution completed for {len(tool_calls)} tools")
388
- # self.root_span.event(name="parallel_execution_completed", level="DEFAULT",
389
- # status_message=(f"Parallel execution completed for {len(tool_calls)} tools"))
390
- return processed_results
363
+ tool_names = [t['function_name'] for t in tool_calls]
364
+ tool_num = len(tool_calls)
365
+ if tool_num > 1:
366
+ logging.info(f"TaskProcessor execute_tools_in_parallel: Executing {tool_num} tools sequentially: {tool_names}")
367
+ self.root_span.event(name="task_process_execute_tools_parallel", level="DEFAULT",
368
+ status_message=f"Executing {len(tool_calls)} tools parallelly: {tool_names}")
369
+
370
+ # Execute all tasks concurrently with error handling
371
+ tasks = [self._execute_tool(tool_call) for tool_call in tool_calls]
372
+ results = await asyncio.gather(*tasks, return_exceptions=True)
373
+
374
+ processed_results = []
375
+ for i, (tool_call, result) in enumerate(zip(tool_calls, results)):
376
+ processed_results.append((tool_call, result))
377
+
378
+ logging.info(f"TaskProcessor execute_tools_in_parallel: Execution completed for {len(results)} tools, total {tool_num} tools)")
379
+ return processed_results
380
+
381
+
382
+ def _add_tool_messsage(self,
383
+ tool_call: Dict[str, Any],
384
+ result: XGAToolResult,
385
+ strategy: XmlAddingStrategy = "assistant_message",
386
+ assistant_message_id: Optional[str] = None,
387
+ parsing_details: Optional[Dict[str, Any]] = None
388
+ ) -> Optional[Dict[str, Any]]: # Return the full message object
389
+ # Create two versions of the structured result
390
+ # Rich version for the frontend
391
+ result_for_frontend = self._create_structured_tool_result(tool_call, result, parsing_details, for_llm=False)
392
+ # Concise version for the LLM
393
+ result_for_llm = self._create_structured_tool_result(tool_call, result, parsing_details, for_llm=True)
394
+
395
+ # Add the message with the appropriate role to the conversation history
396
+ # This allows the LLM to see the tool result in subsequent interactions
397
+ role = "user" if strategy == "user_message" else "assistant"
398
+ content = {
399
+ 'role': role,
400
+ 'content': json.dumps(result_for_llm)
401
+ }
391
402
 
392
- except Exception as e:
393
- logging.error(f"Error in parallel tool execution: {str(e)}", exc_info=True)
394
- self.root_span.event(name="task_process_error_in_parallel_tool_execution", level="ERROR",
395
- status_message=(f"Error in parallel tool execution: {str(e)}"))
396
- # Return error results for all tools if the gather itself fails
397
- return [(tool_call, XGAToolResult(success=False, output=f"Execution error: {str(e)}"))
398
- for tool_call in tool_calls]
399
-
400
- def _add_tool_messsage(
401
- self,
402
- tool_call: Dict[str, Any],
403
- result: XGAToolResult,
404
- strategy: XmlAddingStrategy = "assistant_message",
405
- assistant_message_id: Optional[str] = None,
406
- parsing_details: Optional[Dict[str, Any]] = None
407
- ) -> Optional[Dict[str, Any]]: # Return the full message object
408
- try:
409
- message_obj = None # Initialize message_obj
410
-
411
- # Create metadata with assistant_message_id if provided
412
- metadata = {}
413
- if assistant_message_id:
414
- metadata["assistant_message_id"] = assistant_message_id
415
- logging.info(f"Linking tool result to assistant message: {assistant_message_id}")
416
-
417
- # --- Add parsing details to metadata if available ---
418
- if parsing_details:
419
- metadata["parsing_details"] = parsing_details
420
- logging.info("Adding parsing_details to tool result metadata")
421
-
422
- # For XML and other non-native tools, use the new structured format
423
- # Determine message role based on strategy
424
- result_role = "user" if strategy == "user_message" else "assistant"
425
-
426
- # Create two versions of the structured result
427
- # 1. Rich version for the frontend
428
- structured_result_for_frontend = self._create_structured_tool_result(tool_call, result, parsing_details,
429
- for_llm=False)
430
- # 2. Concise version for the LLM
431
- structured_result_for_llm = self._create_structured_tool_result(tool_call, result, parsing_details,
432
- for_llm=True)
433
-
434
- # Add the message with the appropriate role to the conversation history
435
- # This allows the LLM to see the tool result in subsequent interactions
436
- result_message_for_llm = {
437
- "role": result_role,
438
- "content": json.dumps(structured_result_for_llm)
439
- }
403
+ metadata = {}
404
+ if assistant_message_id:
405
+ metadata["assistant_message_id"] = assistant_message_id
440
406
 
441
- # Add rich content to metadata for frontend use
442
- if metadata is None:
443
- metadata = {}
444
- metadata['frontend_content'] = structured_result_for_frontend
445
-
446
- message_obj = self.add_response_message(
447
- type="tool",
448
- content=result_message_for_llm, # Save the LLM-friendly version
449
- is_llm_message=True,
450
- metadata=metadata
451
- )
452
-
453
- # If the message was saved, modify it in-memory for the frontend before returning
454
- if message_obj:
455
- # The frontend expects the rich content in the 'content' field.
456
- # The DB has the rich content in metadata.frontend_content.
457
- # Let's reconstruct the message for yielding.
458
- message_for_yield = message_obj.copy()
459
- message_for_yield['content'] = structured_result_for_frontend
460
- return message_for_yield
461
-
462
- return message_obj # Return the modified message object
463
- except Exception as e:
464
- logging.error(f"Error adding tool result: {str(e)}", exc_info=True)
465
- self.root_span.event(name="task_process_error_adding_tool_result", level="ERROR",
466
- status_message=(f"Error adding tool result: {str(e)}"),
467
- metadata={"tool_call": tool_call, "result": result, "strategy": strategy,
468
- "assistant_message_id": assistant_message_id,
469
- "parsing_details": parsing_details})
470
- # Fallback to a simple message
471
- try:
472
- fallback_message = {
473
- "role": "user",
474
- "content": str(result)
475
- }
476
- message_obj = self.add_response_message(
477
- type="tool",
478
- content=fallback_message,
479
- is_llm_message=True,
480
- metadata={"assistant_message_id": assistant_message_id} if assistant_message_id else {}
481
- )
482
- return message_obj # Return the full message object
483
- except Exception as e2:
484
- logging.error(f"Failed even with fallback message: {str(e2)}", exc_info=True)
485
- self.root_span.event(name="task_process_failed_even_with_fallback_message", level="ERROR",
486
- status_message=(f"Failed even with fallback message: {str(e2)}"),
487
- metadata={"tool_call": tool_call, "result": result, "strategy": strategy,
488
- "assistant_message_id": assistant_message_id,
489
- "parsing_details": parsing_details})
490
- return None # Return None on error
491
-
492
- def _create_structured_tool_result(self, tool_call: Dict[str, Any], result: XGAToolResult,
493
- parsing_details: Optional[Dict[str, Any]] = None, for_llm: bool = False):
494
- function_name = tool_call.get("function_name", "unknown")
407
+ if parsing_details:
408
+ metadata["parsing_details"] = parsing_details
409
+
410
+ metadata['frontend_content'] = result_for_frontend
411
+
412
+ tool_message = self.add_response_message(type="tool", content=content, is_llm_message=True, metadata=metadata)
413
+
414
+ # Let's result_for_frontend the message for yielding.
415
+ yield_message = tool_message.copy()
416
+ yield_message['content'] = result_for_frontend
417
+
418
+ return yield_message
419
+
420
+
421
+ def _create_structured_tool_result(self,
422
+ tool_call: Dict[str, Any],
423
+ result: XGAToolResult,
424
+ parsing_details: Optional[Dict[str, Any]] = None,
425
+ for_llm: bool = False) -> Dict[str, Any]:
426
+ function_name = tool_call['function_name']
495
427
  xml_tag_name = tool_call.get("xml_tag_name")
496
428
  arguments = tool_call.get("arguments", {})
497
429
  tool_call_id = tool_call.get("id")
498
430
 
499
431
  # Process the output - if it's a JSON string, parse it back to an object
500
- output = result.output if hasattr(result, 'output') else str(result)
501
- if isinstance(output, str):
502
- try:
503
- # Try to parse as JSON to provide structured data to frontend
504
- parsed_output = safe_json_parse(output)
505
- # If parsing succeeded and we got a dict/list, use the parsed version
506
- if isinstance(parsed_output, (dict, list)):
507
- output = parsed_output
508
- # Otherwise keep the original string
509
- except Exception:
510
- # If parsing fails, keep the original string
511
- pass
432
+ output = result.output
433
+ parsed_output = safe_json_parse(output)
434
+ if isinstance(parsed_output, (dict, list)):
435
+ output = parsed_output
512
436
 
513
437
  output_to_use = output
514
438
  # If this is for the LLM and it's an edit_file tool, create a concise output
515
439
  if for_llm and function_name == 'edit_file' and isinstance(output, dict):
516
440
  # The frontend needs original_content and updated_content to render diffs.
517
441
  # The concise version for the LLM was causing issues.
518
- # We will now pass the full output, and rely on the ContextManager to truncate if needed.
442
+ # @todo We will now pass the full output, and rely on the ContextManager to truncate if needed.
519
443
  output_to_use = output
520
444
 
521
- # Create the structured result
522
- structured_result_v1 = {
523
- "tool_execution": {
524
- "function_name": function_name,
525
- "xml_tag_name": xml_tag_name,
526
- "tool_call_id": tool_call_id,
527
- "arguments": arguments,
528
- "result": {
529
- "success": result.success if hasattr(result, 'success') else True,
530
- "output": output_to_use, # This will be either rich or concise based on `for_llm`
531
- "error": getattr(result, 'error', None) if hasattr(result, 'error') else None
445
+ structured_result = {
446
+ 'tool_execution': {
447
+ 'function_name' : function_name,
448
+ 'xml_tag_name' : xml_tag_name,
449
+ 'arguments' : arguments,
450
+ 'result' : {
451
+ 'success' : result.success,
452
+ 'output' : output_to_use,
453
+ 'error' : None if result.success else result.output
532
454
  },
533
455
  }
534
456
  }
535
457
 
536
- return structured_result_v1
458
+ return structured_result
459
+
537
460
 
538
- def _create_tool_context(self, tool_call: Dict[str, Any], tool_index: int,
461
+ def _create_tool_context(self,
462
+ tool_call: Dict[str, Any],
463
+ tool_index: int,
539
464
  assistant_message_id: Optional[str] = None,
540
- parsing_details: Optional[Dict[str, Any]] = None) -> ToolExecutionContext:
465
+ parsing_details: Optional[Dict[str, Any]] = None,
466
+ result: Optional[XGAToolResult] = None,
467
+ ) -> ToolExecutionContext:
541
468
  """Create a tool execution context with display name and parsing details populated."""
542
- context = ToolExecutionContext(
543
- tool_call=tool_call,
544
- tool_index=tool_index,
545
- assistant_message_id=assistant_message_id,
546
- parsing_details=parsing_details
469
+ return ToolExecutionContext(
470
+ tool_call = tool_call,
471
+ tool_index = tool_index,
472
+ function_name = tool_call['function_name'],
473
+ xml_tag_name = tool_call['xml_tag_name'],
474
+ assistant_message_id = assistant_message_id,
475
+ parsing_details = parsing_details,
476
+ result = result
547
477
  )
548
478
 
549
- # Set function_name and xml_tag_name fields
550
- context.xml_tag_name = tool_call["xml_tag_name"]
551
- context.function_name = tool_call["function_name"]
552
-
553
- return context
554
479
 
555
480
  def _add_tool_start_message(self, context: ToolExecutionContext) -> Optional[Dict[str, Any]]:
556
481
  """Formats, saves, and returns a tool started status message."""
557
- tool_name = context.xml_tag_name or context.function_name
558
482
  content = {
559
- "role": "assistant", "status_type": "tool_started",
560
- "function_name": context.function_name, "xml_tag_name": context.xml_tag_name,
561
- "message": f"Starting execution of {tool_name}", "tool_index": context.tool_index # Include tool_call ID if native
483
+ 'status_type' : "tool_started",
484
+ 'role' : "assistant",
485
+ 'function_name' : context.function_name,
486
+ 'xml_tag_name' : context.xml_tag_name,
487
+ 'message' : f"Starting execution of {context.function_name}",
488
+ 'tool_index' : context.tool_index
562
489
  }
563
490
 
564
- return self.add_response_message(
565
- type="status", content=content, is_llm_message=False
566
- )
491
+ return self.add_response_message(type="status", content=content, is_llm_message=False)
567
492
 
568
493
  def _add_tool_completed_message(self, context: ToolExecutionContext, tool_message_id: Optional[str]) -> Optional[Dict[str, Any]]:
569
494
  """Formats, saves, and returns a tool completed/failed status message."""
570
495
  if not context.result:
571
- # Delegate to error saving if result is missing (e.g., execution failed)
572
496
  return self._add_tool_error_message(context)
573
497
 
574
- tool_name = context.xml_tag_name or context.function_name
575
498
  status_type = "tool_completed" if context.result.success else "tool_failed"
576
- message_text = f"Tool {tool_name} {'completed successfully' if context.result.success else 'failed'}"
499
+ message_text = f"Tool {context.function_name} {'completed successfully' if context.result.success else 'failed'}"
577
500
 
578
501
  content = {
579
- "role": "assistant", "status_type": status_type,
580
- "function_name": context.function_name, "xml_tag_name": context.xml_tag_name,
581
- "message": message_text, "tool_index": context.tool_index,
582
- "tool_call_id": context.tool_call.get("id")
502
+ "status_type" : status_type,
503
+ "role" : "assistant",
504
+ "function_name" : context.function_name,
505
+ "xml_tag_name" : context.xml_tag_name,
506
+ "message" : message_text,
507
+ "tool_index" : context.tool_index
583
508
  }
509
+
584
510
  metadata = {}
585
- # Add the *actual* tool result message ID to the metadata if available and successful
586
- if context.result.success and tool_message_id:
587
- metadata["linked_tool_result_message_id"] = tool_message_id
588
-
589
- # <<< ADDED: Signal if this is a terminating tool >>>
590
- if context.function_name in ['ask', 'complete']:
591
- metadata["agent_should_terminate"] = "true"
592
- logging.info(f"Marking tool status for '{context.function_name}' with termination signal.")
593
- # self.root_span.event(name="marking_tool_status_for_termination", level="DEFAULT", status_message=(
594
- # f"Marking tool status for '{context.function_name}' with termination signal."))
595
- # <<< END ADDED >>>
596
-
597
- return self.add_response_message(
598
- type="status", content=content, is_llm_message=False, metadata=metadata
599
- )
511
+ if tool_message_id:
512
+ metadata["tool_result_message_id"] = tool_message_id
513
+
514
+ return self.add_response_message(type="status", content=content, is_llm_message=False, metadata=metadata)
600
515
 
601
516
  def _add_tool_error_message(self, context: ToolExecutionContext) -> Optional[Dict[str, Any]]:
602
517
  """Formats, saves, and returns a tool error status message."""
603
- error_msg = str(context.error) if context.error else "Unknown error during tool execution"
604
- tool_name = context.xml_tag_name or context.function_name
518
+ error_msg = str(context.error) if context.error else "Tool execution unknown exception"
605
519
  content = {
606
- "role": "assistant", "status_type": "tool_error",
607
- "function_name": context.function_name, "xml_tag_name": context.xml_tag_name,
608
- "message": f"Error executing tool {tool_name}: {error_msg}",
609
- "tool_index": context.tool_index,
610
- "tool_call_id": context.tool_call.get("id")
520
+ "status_type" : "tool_error",
521
+ "role" : "assistant",
522
+ "function_name" : context.function_name,
523
+ "xml_tag_name" : context.xml_tag_name,
524
+ "message" : f"Executing tool {context.function_name} exception: {error_msg}",
525
+ "tool_index" : context.tool_index
611
526
  }
612
527
 
613
- # Save the status message with is_llm_message=False
614
- return self.add_response_message(
615
- type="status", content=content, is_llm_message=False
616
- )
528
+ return self.add_response_message(type="status", content=content, is_llm_message=False)
617
529