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.
- xgae/engine/mcp_tool_box.py +8 -7
- xgae/engine/prompt_builder.py +9 -8
- xgae/engine/responser/non_stream_responser.py +13 -13
- xgae/engine/responser/responser_base.py +174 -262
- xgae/engine/responser/stream_responser.py +165 -45
- xgae/engine/task_engine.py +93 -93
- xgae/engine/task_langfuse.py +11 -11
- xgae/utils/json_helpers.py +0 -29
- xgae/utils/llm_client.py +23 -23
- xgae/utils/xml_tool_parser.py +7 -7
- {xgae-0.1.13.dist-info → xgae-0.1.15.dist-info}/METADATA +1 -1
- xgae-0.1.15.dist-info/RECORD +21 -0
- {xgae-0.1.13.dist-info → xgae-0.1.15.dist-info}/entry_points.txt +1 -1
- xgae-0.1.13.dist-info/RECORD +0 -21
- {xgae-0.1.13.dist-info → xgae-0.1.15.dist-info}/WHEEL +0 -0
|
@@ -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
|
-
|
|
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
|
|
64
|
-
self.task_run_id
|
|
65
|
-
self.task_no
|
|
66
|
-
self.
|
|
67
|
-
self.
|
|
68
|
-
self.
|
|
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
|
-
|
|
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.
|
|
76
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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[
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
|
331
|
-
logging.
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
processed_results
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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
|
|
458
|
+
return structured_result
|
|
459
|
+
|
|
537
460
|
|
|
538
|
-
def _create_tool_context(self,
|
|
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
|
|
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
|
-
|
|
543
|
-
tool_call=tool_call,
|
|
544
|
-
tool_index=tool_index,
|
|
545
|
-
|
|
546
|
-
|
|
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
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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 {
|
|
499
|
+
message_text = f"Tool {context.function_name} {'completed successfully' if context.result.success else 'failed'}"
|
|
577
500
|
|
|
578
501
|
content = {
|
|
579
|
-
"
|
|
580
|
-
"
|
|
581
|
-
"
|
|
582
|
-
"
|
|
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
|
-
|
|
586
|
-
|
|
587
|
-
|
|
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 "
|
|
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
|
-
"
|
|
607
|
-
"
|
|
608
|
-
"
|
|
609
|
-
"
|
|
610
|
-
"
|
|
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
|
-
|
|
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
|
|