xgae 0.1.14__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.

@@ -17,16 +17,17 @@ class XGAMcpToolBox(XGAToolBox):
17
17
  custom_mcp_server_config: Optional[Dict[str, Any]] = None
18
18
  ):
19
19
  general_mcp_server_config = self._load_mcp_servers_config("mcpservers/xga_server.json")
20
- tool_box_mcp_server_config = general_mcp_server_config.get("mcpServers", {})
20
+ tool_box_mcp_server_config = general_mcp_server_config.get('mcpServers', {})
21
21
 
22
22
  if custom_mcp_server_config:
23
23
  tool_box_mcp_server_config.update(custom_mcp_server_config)
24
24
  elif custom_mcp_server_file:
25
25
  custom_mcp_server_config = self._load_mcp_servers_config(custom_mcp_server_file)
26
- custom_mcp_server_config = custom_mcp_server_config.get("mcpServers", {})
26
+ custom_mcp_server_config = custom_mcp_server_config.get('mcpServers', {})
27
27
  tool_box_mcp_server_config.update(custom_mcp_server_config)
28
28
 
29
29
  self._mcp_client = MultiServerMCPClient(tool_box_mcp_server_config)
30
+
30
31
  self.mcp_server_names: List[str] = [server_name for server_name in tool_box_mcp_server_config]
31
32
  self.mcp_tool_schemas: Dict[str, List[XGAToolSchema]] = {}
32
33
  self.task_tool_schemas: Dict[str, Dict[str,XGAToolSchema]] = {}
@@ -178,18 +179,18 @@ class XGAMcpToolBox(XGAToolBox):
178
179
  for server_name, server_info in server_config["mcpServers"].items():
179
180
  if "transport" not in server_info:
180
181
  if "url" in server_info:
181
- server_info["transport"] = "streamable_http" if "mcp" in server_info["url"] else "sse"
182
+ server_info['transport'] = "streamable_http" if "mcp" in server_info['url'] else "sse"
182
183
  else:
183
- server_info["transport"] = "stdio"
184
+ server_info['transport'] = "stdio"
184
185
 
185
186
  return server_config
186
187
  else:
187
188
  logging.warning(f"McpToolBox load_mcp_servers_config: MCP servers config file not found at: {mcp_config_path}")
188
- return {"mcpServers": {}}
189
+ return {'mcpServers': {}}
189
190
 
190
191
  except Exception as e:
191
192
  logging.error(f"McpToolBox load_mcp_servers_config: Failed to load MCP servers config: {e}")
192
- return {"mcpServers": {}}
193
+ return {'mcpServers': {}}
193
194
 
194
195
 
195
196
  if __name__ == "__main__":
@@ -34,19 +34,20 @@ class XGAPromptBuilder():
34
34
  openai_schemas = []
35
35
  for tool_schema in tool_schemas:
36
36
  openai_schema = {}
37
- openai_schema["type"] = "function"
38
37
  openai_function = {}
39
- openai_schema["function"] = openai_function
38
+ openai_schema['type'] = "function"
39
+ openai_schema['function'] = openai_function
40
40
 
41
- openai_function["name"] = tool_schema.tool_name
42
- openai_function["description"] = tool_schema.description if tool_schema.description else 'No description available'
41
+ openai_function['name'] = tool_schema.tool_name
42
+ openai_function['description'] = tool_schema.description if tool_schema.description else 'No description available'
43
43
 
44
44
  openai_parameters = {}
45
+ openai_function['parameters'] = openai_parameters
46
+
45
47
  input_schema = tool_schema.input_schema
46
- openai_function["parameters"] = openai_parameters
47
- openai_parameters["type"] = input_schema["type"]
48
- openai_parameters["properties"] = input_schema.get("properties", {})
49
- openai_parameters["required"] = input_schema["required"]
48
+ openai_parameters['type'] = input_schema['type']
49
+ openai_parameters['properties'] = input_schema.get('properties', {})
50
+ openai_parameters['required'] = input_schema['required']
50
51
 
51
52
  openai_schemas.append(openai_schema)
52
53
 
@@ -3,7 +3,7 @@ import logging
3
3
  from typing import List, Dict, Any, AsyncGenerator, override,Optional
4
4
 
5
5
  from xgae.utils import log_trace
6
- from xgae.utils.json_helpers import format_for_yield
6
+
7
7
 
8
8
  from xgae.engine.responser.responser_base import TaskResponseProcessor, TaskResponserContext, TaskRunContinuousState
9
9
 
@@ -25,7 +25,7 @@ class NonStreamTaskResponser(TaskResponseProcessor):
25
25
  try:
26
26
  if hasattr(llm_response, 'choices') and llm_response.choices:
27
27
  if hasattr(llm_response.choices[0], 'finish_reason'):
28
- finish_reason = llm_response.choices[0].finish_reason
28
+ finish_reason = llm_response.choices[0].finish_reason # LLM finish reason: ‘stop' , 'length'
29
29
  logging.info(f"NonStreamResp: LLM response finish_reason={finish_reason}")
30
30
 
31
31
  response_message = llm_response.choices[0].message if hasattr(llm_response.choices[0], 'message') else None
@@ -35,12 +35,13 @@ class NonStreamTaskResponser(TaskResponseProcessor):
35
35
 
36
36
  parsed_xml_data = self._parse_xml_tool_calls(llm_content)
37
37
  if self.max_xml_tool_calls > 0 and len(parsed_xml_data) > self.max_xml_tool_calls:
38
- logging.warning(f"NonStreamResp: Truncate content, parsed_xml_data length={len(parsed_xml_data)} limit over max_xml_tool_calls={self.max_xml_tool_calls}")
38
+ logging.warning(f"NonStreamResp: Over XML Tool Limit, finish_reason='xml_tool_limit_reached', "
39
+ f"parsed_xml_data_len={len(parsed_xml_data)}")
39
40
  parsed_xml_data = parsed_xml_data[:self.max_xml_tool_calls]
40
41
  finish_reason = "xml_tool_limit_reached"
41
42
 
42
43
  self.root_span.event(name=f"non_stream_processor_start[{self.task_no}]({llm_count})", level="DEFAULT",
43
- status_message=f"finish_reason={finish_reason}, tool_exec_strategy={self.tool_execution_strategy}, "
44
+ status_message=f"finish_reason={finish_reason}, tool_exec_strategy={self.tool_exec_strategy}, "
44
45
  f"parsed_xml_data_len={len(parsed_xml_data)}, llm_content_len={len(llm_content)}")
45
46
 
46
47
  if len(llm_content) == 0:
@@ -52,7 +53,7 @@ class NonStreamTaskResponser(TaskResponseProcessor):
52
53
 
53
54
  tool_calls_to_execute = [item['tool_call'] for item in parsed_xml_data]
54
55
  if len(tool_calls_to_execute) > 0:
55
- tool_results = await self._execute_tools(tool_calls_to_execute, self.tool_execution_strategy)
56
+ tool_results = await self._execute_tools(tool_calls_to_execute, self.tool_exec_strategy)
56
57
 
57
58
  tool_index = 0
58
59
  for i, (returned_tool_call, tool_result) in enumerate(tool_results):
@@ -64,16 +65,16 @@ class NonStreamTaskResponser(TaskResponseProcessor):
64
65
  tool_context = self._create_tool_context(tool_call, tool_index, assistant_msg_id, parsing_details, tool_result)
65
66
 
66
67
  tool_start_msg = self._add_tool_start_message(tool_context)
67
- yield format_for_yield(tool_start_msg)
68
+ yield tool_start_msg
68
69
 
69
70
  tool_message = self._add_tool_messsage(tool_call, tool_result, self.xml_adding_strategy, assistant_msg_id, parsing_details)
70
71
 
71
72
  tool_completed_msg = self._add_tool_completed_message(tool_context, tool_message['message_id'])
72
- yield format_for_yield(tool_completed_msg)
73
+ yield tool_completed_msg
73
74
 
74
- yield format_for_yield(tool_message)
75
+ yield tool_message
75
76
 
76
- if tool_completed_msg["metadata"].get("agent_should_terminate") == "true":
77
+ if tool_context.function_name in ['ask', 'complete']:
77
78
  finish_reason = "completed"
78
79
  break
79
80
 
@@ -85,7 +86,7 @@ class NonStreamTaskResponser(TaskResponseProcessor):
85
86
  if finish_reason:
86
87
  finish_content = {"status_type": "finish", "finish_reason": finish_reason}
87
88
  finish_msg = self.add_response_message(type="status", content=finish_content, is_llm_message=False)
88
- yield format_for_yield(finish_msg)
89
+ yield finish_msg
89
90
  except Exception as e:
90
91
  trace = log_trace(e, f"NonStreamResp: Process response llm_content:\n {llm_content}")
91
92
  self.root_span.event(name="non_stream_process_response_error", level="ERROR",
@@ -94,7 +95,7 @@ class NonStreamTaskResponser(TaskResponseProcessor):
94
95
 
95
96
  content = {"role": "system", "status_type": "error", "message": f"Process non-streaming response error: {e}"}
96
97
  error_msg = self.add_response_message(type="status", content=content, is_llm_message=False)
97
- yield format_for_yield(error_msg)
98
+ yield error_msg
98
99
 
99
100
  raise # Use bare 'raise' to preserve the original exception with its traceback
100
101
 
@@ -28,8 +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
32
- tool_execute_on_stream: bool
31
+ tool_exec_strategy: ToolExecutionStrategy
32
+ tool_exec_on_stream: bool
33
33
  xml_adding_strategy: XmlAddingStrategy
34
34
  add_response_msg_func: Callable
35
35
  create_response_msg_func: Callable
@@ -49,9 +49,9 @@ class ToolExecutionContext:
49
49
  """Context for a tool execution including call details, result, and display info."""
50
50
  tool_call: Dict[str, Any]
51
51
  tool_index: int
52
- function_name: Optional[str] = None
52
+ function_name: str
53
+ xml_tag_name: str
53
54
  result: Optional[XGAToolResult] = None
54
- xml_tag_name: Optional[str] = None
55
55
  error: Optional[Exception] = None
56
56
  assistant_message_id: Optional[str] = None
57
57
  parsing_details: Optional[Dict[str, Any]] = None
@@ -61,21 +61,23 @@ class TaskResponseProcessor(ABC):
61
61
  def __init__(self, response_context: TaskResponserContext):
62
62
  self.response_context = response_context
63
63
 
64
- self.task_id = response_context.get("task_id")
65
- self.task_run_id = response_context.get("task_run_id")
66
- self.task_no = response_context.get("task_no")
67
- self.tool_execution_strategy = self.response_context.get("tool_execution_strategy", "parallel")
68
- self.xml_adding_strategy = self.response_context.get("xml_adding_strategy", "user_message")
69
- self.max_xml_tool_calls = self.response_context.get("max_xml_tool_calls", 0)
70
- self.tool_execute_on_stream = response_context.get("tool_execute_on_stream", False)
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']
71
71
 
72
- 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']
73
77
  self.root_span = task_langfuse.root_span
74
- self.add_response_message = response_context.get("add_response_msg_func")
75
- self.create_response_message = response_context.get("create_response_msg_func")
76
78
 
77
- self.tool_box = response_context.get("tool_box")
78
- self.xml_parser = XMLToolParser()
79
+ self.xml_parser = XMLToolParser()
80
+
79
81
 
80
82
 
81
83
  @abstractmethod
@@ -209,16 +211,16 @@ class TaskResponseProcessor(ABC):
209
211
 
210
212
  # Convert to the expected format
211
213
  tool_call = {
212
- "function_name": xml_tool_call.function_name,
213
- "xml_tag_name": xml_tool_call.function_name.replace('_', '-'), # For backwards compatibility
214
- "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
215
217
  }
216
218
 
217
219
  # Include the parsing details
218
220
  parsing_details = xml_tool_call.parsing_details
219
- parsing_details["raw_xml"] = xml_tool_call.raw_xml
221
+ parsing_details['raw_xml'] = xml_tool_call.raw_xml
220
222
 
221
- 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}")
222
224
  return tool_call, parsing_details
223
225
 
224
226
  # If not the expected <function_calls><invoke> format, return None
@@ -260,7 +262,7 @@ class TaskResponseProcessor(ABC):
260
262
 
261
263
  async def _execute_tool(self, tool_call: Dict[str, Any]) -> XGAToolResult:
262
264
  """Execute a single tool call and return the result."""
263
- function_name = tool_call.get("function_name", "empty_function")
265
+ function_name = tool_call['function_name']
264
266
  exec_tool_span = self.root_span.span(name=f"execute_tool.{function_name}", input=tool_call["arguments"])
265
267
  try:
266
268
  arguments = tool_call["arguments"]
@@ -328,7 +330,7 @@ class TaskResponseProcessor(ABC):
328
330
 
329
331
  results = []
330
332
  for index, tool_call in enumerate(tool_calls):
331
- tool_name = tool_call.get('function_name', 'unknown')
333
+ tool_name = tool_call['function_name']
332
334
  logging.info(f"TaskProcessor execute_tools_sequentially: Executing tool '{tool_name}', sequence={index + 1}/{tool_num}")
333
335
  result = await self._execute_tool(tool_call)
334
336
  results.append((tool_call, result))
@@ -358,7 +360,7 @@ class TaskResponseProcessor(ABC):
358
360
  logging.warning("TaskProcessor execute_tools_in_parallel: tool_calls is empty")
359
361
  return []
360
362
 
361
- tool_names = [t.get('function_name', 'unknown') for t in tool_calls]
363
+ tool_names = [t['function_name'] for t in tool_calls]
362
364
  tool_num = len(tool_calls)
363
365
  if tool_num > 1:
364
366
  logging.info(f"TaskProcessor execute_tools_in_parallel: Executing {tool_num} tools sequentially: {tool_names}")
@@ -384,17 +386,6 @@ class TaskResponseProcessor(ABC):
384
386
  assistant_message_id: Optional[str] = None,
385
387
  parsing_details: Optional[Dict[str, Any]] = None
386
388
  ) -> Optional[Dict[str, Any]]: # Return the full message object
387
- tool_message = None
388
-
389
- metadata = {}
390
- if assistant_message_id:
391
- metadata["assistant_message_id"] = assistant_message_id
392
-
393
- if parsing_details:
394
- metadata["parsing_details"] = parsing_details
395
-
396
- role = "user" if strategy == "user_message" else "assistant"
397
-
398
389
  # Create two versions of the structured result
399
390
  # Rich version for the frontend
400
391
  result_for_frontend = self._create_structured_tool_result(tool_call, result, parsing_details, for_llm=False)
@@ -403,21 +394,24 @@ class TaskResponseProcessor(ABC):
403
394
 
404
395
  # Add the message with the appropriate role to the conversation history
405
396
  # This allows the LLM to see the tool result in subsequent interactions
397
+ role = "user" if strategy == "user_message" else "assistant"
406
398
  content = {
407
- "role": role,
408
- "content": json.dumps(result_for_llm)
399
+ 'role': role,
400
+ 'content': json.dumps(result_for_llm)
409
401
  }
410
402
 
403
+ metadata = {}
404
+ if assistant_message_id:
405
+ metadata["assistant_message_id"] = assistant_message_id
406
+
407
+ if parsing_details:
408
+ metadata["parsing_details"] = parsing_details
409
+
411
410
  metadata['frontend_content'] = result_for_frontend
412
411
 
413
- tool_message = self.add_response_message(
414
- type="tool",
415
- content=content,
416
- is_llm_message=True,
417
- metadata=metadata
418
- )
412
+ tool_message = self.add_response_message(type="tool", content=content, is_llm_message=True, metadata=metadata)
419
413
 
420
- # Let's reconstruct the message for yielding.
414
+ # Let's result_for_frontend the message for yielding.
421
415
  yield_message = tool_message.copy()
422
416
  yield_message['content'] = result_for_frontend
423
417
 
@@ -429,7 +423,7 @@ class TaskResponseProcessor(ABC):
429
423
  result: XGAToolResult,
430
424
  parsing_details: Optional[Dict[str, Any]] = None,
431
425
  for_llm: bool = False) -> Dict[str, Any]:
432
- function_name = tool_call.get("function_name", "unknown")
426
+ function_name = tool_call['function_name']
433
427
  xml_tag_name = tool_call.get("xml_tag_name")
434
428
  arguments = tool_call.get("arguments", {})
435
429
  tool_call_id = tool_call.get("id")
@@ -449,15 +443,14 @@ class TaskResponseProcessor(ABC):
449
443
  output_to_use = output
450
444
 
451
445
  structured_result = {
452
- "tool_execution": {
453
- "function_name": function_name,
454
- "xml_tag_name": xml_tag_name,
455
- "tool_call_id": tool_call_id,
456
- "arguments": arguments,
457
- "result": {
458
- "success": result.success,
459
- "output": output_to_use,
460
- "error": None if result.success else result.output
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
461
454
  },
462
455
  }
463
456
  }
@@ -474,26 +467,25 @@ class TaskResponseProcessor(ABC):
474
467
  ) -> ToolExecutionContext:
475
468
  """Create a tool execution context with display name and parsing details populated."""
476
469
  return ToolExecutionContext(
477
- function_name=tool_call.get("function_name"),
478
- tool_call=tool_call,
479
- tool_index=tool_index,
480
- assistant_message_id=assistant_message_id,
481
- parsing_details=parsing_details,
482
- xml_tag_name=tool_call.get("xml_tag_name"),
483
- result=result,
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
484
477
  )
485
478
 
486
479
 
487
480
  def _add_tool_start_message(self, context: ToolExecutionContext) -> Optional[Dict[str, Any]]:
488
481
  """Formats, saves, and returns a tool started status message."""
489
- tool_name = context.xml_tag_name or context.function_name
490
482
  content = {
491
- "status_type": "tool_started",
492
- "role": "assistant",
493
- "function_name": context.function_name,
494
- "xml_tag_name": context.xml_tag_name,
495
- "message": f"Starting execution of {tool_name}",
496
- "tool_index": context.tool_index
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
497
489
  }
498
490
 
499
491
  return self.add_response_message(type="status", content=content, is_llm_message=False)
@@ -503,42 +495,34 @@ class TaskResponseProcessor(ABC):
503
495
  if not context.result:
504
496
  return self._add_tool_error_message(context)
505
497
 
506
- tool_name = context.xml_tag_name or context.function_name
507
498
  status_type = "tool_completed" if context.result.success else "tool_failed"
508
- 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'}"
509
500
 
510
501
  content = {
511
- "status_type": status_type,
512
- "role": "assistant",
513
- "function_name": context.function_name,
514
- "xml_tag_name": context.xml_tag_name,
515
- "message": message_text,
516
- "tool_index": context.tool_index,
517
- "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
518
508
  }
519
509
 
520
510
  metadata = {}
521
- # Add the *actual* tool result message ID to the metadata if available and successful
522
- if context.result.success and tool_message_id:
523
- metadata["linked_tool_result_message_id"] = tool_message_id
524
-
525
- if context.function_name in ['ask', 'complete']:
526
- metadata["agent_should_terminate"] = "true"
511
+ if tool_message_id:
512
+ metadata["tool_result_message_id"] = tool_message_id
527
513
 
528
514
  return self.add_response_message(type="status", content=content, is_llm_message=False, metadata=metadata)
529
515
 
530
516
  def _add_tool_error_message(self, context: ToolExecutionContext) -> Optional[Dict[str, Any]]:
531
517
  """Formats, saves, and returns a tool error status message."""
532
- error_msg = str(context.error) if context.error else "Unknown error during tool execution"
533
- tool_name = context.xml_tag_name or context.function_name
518
+ error_msg = str(context.error) if context.error else "Tool execution unknown exception"
534
519
  content = {
535
- "status_type": "tool_error",
536
- "role": "assistant",
537
- "function_name": context.function_name,
538
- "xml_tag_name": context.xml_tag_name,
539
- "message": f"Error executing tool {tool_name}: {error_msg}",
540
- "tool_index": context.tool_index,
541
- "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
542
526
  }
543
527
 
544
528
  return self.add_response_message(type="status", content=content, is_llm_message=False)