xgae 0.1.9__py3-none-any.whl → 0.1.12__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/__init__.py +4 -0
- xgae/cli_app.py +85 -0
- xgae/engine/engine_base.py +3 -3
- xgae/engine/mcp_tool_box.py +4 -4
- xgae/engine/responser/non_stream_responser.py +33 -38
- xgae/engine/responser/responser_base.py +42 -40
- xgae/engine/responser/stream_responser.py +95 -782
- xgae/engine/task_engine.py +98 -50
- xgae/engine/task_langfuse.py +8 -6
- xgae/tools/without_general_tools_app.py +2 -3
- xgae/utils/__init__.py +2 -2
- xgae/utils/json_helpers.py +2 -2
- xgae/utils/llm_client.py +42 -32
- xgae/utils/setup_env.py +4 -3
- {xgae-0.1.9.dist-info → xgae-0.1.12.dist-info}/METADATA +1 -1
- xgae-0.1.12.dist-info/RECORD +21 -0
- {xgae-0.1.9.dist-info → xgae-0.1.12.dist-info}/entry_points.txt +1 -0
- xgae-0.1.9.dist-info/RECORD +0 -20
- {xgae-0.1.9.dist-info → xgae-0.1.12.dist-info}/WHEEL +0 -0
xgae/__init__.py
CHANGED
xgae/cli_app.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from xgae.engine.mcp_tool_box import XGAMcpToolBox
|
|
5
|
+
from xgae.engine.task_engine import XGATaskEngine
|
|
6
|
+
|
|
7
|
+
from xgae.utils.misc import read_file
|
|
8
|
+
|
|
9
|
+
from xgae.utils.setup_env import setup_langfuse, setup_env_logging
|
|
10
|
+
|
|
11
|
+
setup_env_logging()
|
|
12
|
+
langfuse = setup_langfuse()
|
|
13
|
+
|
|
14
|
+
def get_user_message(question)-> str:
|
|
15
|
+
while True:
|
|
16
|
+
user_message = input(f"\n💬 {question}: ")
|
|
17
|
+
if user_message.lower() == 'exit':
|
|
18
|
+
print("\n====== Extreme General Agent Engine CLI EXIT ======")
|
|
19
|
+
sys.exit()
|
|
20
|
+
|
|
21
|
+
if not user_message.strip():
|
|
22
|
+
print("\nuser message is empty, input agin !!!\n")
|
|
23
|
+
continue
|
|
24
|
+
|
|
25
|
+
return user_message
|
|
26
|
+
|
|
27
|
+
async def cli() -> None:
|
|
28
|
+
await asyncio.sleep(1)
|
|
29
|
+
print("\n====== Extreme General Agent Engine CLI START ======")
|
|
30
|
+
user_message = input("\n💬 Start Custom MCP Server and Load User Prompt (Yes/No): ")
|
|
31
|
+
tool_box = None
|
|
32
|
+
system_prompt = None
|
|
33
|
+
general_tools = []
|
|
34
|
+
custom_tools = []
|
|
35
|
+
if user_message.lower() == 'yes':
|
|
36
|
+
print(f"--- Start Custom MCP Server in custom_servers.json")
|
|
37
|
+
print(f"--- Load User Prompt in example/fault_user_prompt.txt")
|
|
38
|
+
tool_box = XGAMcpToolBox(custom_mcp_server_file="mcpservers/custom_servers.json")
|
|
39
|
+
system_prompt = read_file("templates/example/fault_user_prompt.txt")
|
|
40
|
+
custom_tools = ["*"]
|
|
41
|
+
else:
|
|
42
|
+
print(f"--- Start General Agent Server")
|
|
43
|
+
print(f"--- Load System Prompt")
|
|
44
|
+
general_tools = ["*"]
|
|
45
|
+
|
|
46
|
+
while True:
|
|
47
|
+
user_message = get_user_message("Enter your message (or 'exit' to quit)")
|
|
48
|
+
|
|
49
|
+
print("\n🔄 Running XGA Engine ...\n")
|
|
50
|
+
engine = XGATaskEngine(tool_box=tool_box,
|
|
51
|
+
general_tools=general_tools,
|
|
52
|
+
custom_tools=custom_tools,
|
|
53
|
+
system_prompt=system_prompt)
|
|
54
|
+
|
|
55
|
+
# Two task run in same langfuse trace
|
|
56
|
+
trace_id = langfuse.trace(name="xgae_cli").trace_id
|
|
57
|
+
|
|
58
|
+
final_result = await engine.run_task_with_final_answer(
|
|
59
|
+
task_message={"role": "user", "content": user_message},
|
|
60
|
+
trace_id=trace_id
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if final_result["type"] == "ask":
|
|
64
|
+
await asyncio.sleep(1)
|
|
65
|
+
print(f"\n📌 ASK INFO: {final_result['content']}")
|
|
66
|
+
user_message = get_user_message("Enter ASK information (or 'exit' to quit)")
|
|
67
|
+
final_result = await engine.run_task_with_final_answer(
|
|
68
|
+
task_message={"role": "user", "content": user_message},
|
|
69
|
+
trace_id=trace_id
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
await asyncio.sleep(1)
|
|
73
|
+
result_prefix = "✅" if final_result["type"] == "answer" else "❌"
|
|
74
|
+
if final_result["type"] == "ask":
|
|
75
|
+
print("\n *** IMPORTANT: XGA CLI only support showing ONE TURN ASK !")
|
|
76
|
+
result_prefix = "⚠️"
|
|
77
|
+
print(f"\n {result_prefix} FINAL RESULT: {final_result['content']}")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def main():
|
|
81
|
+
asyncio.run(cli())
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
main()
|
xgae/engine/engine_base.py
CHANGED
|
@@ -6,14 +6,14 @@ class XGAError(Exception):
|
|
|
6
6
|
"""Custom exception for errors in the XGA system."""
|
|
7
7
|
pass
|
|
8
8
|
|
|
9
|
-
XGAMsgStatusType = Literal["error", "finish", "
|
|
10
|
-
XGAResponseMsgType = Literal["user", "status", "tool", "assistant", "
|
|
9
|
+
XGAMsgStatusType = Literal["error", "finish", "tool_started", "tool_completed", "tool_error", "tool_failed"]
|
|
10
|
+
XGAResponseMsgType = Literal["user", "status", "tool", "assistant", "assistant_chunk"]
|
|
11
11
|
|
|
12
12
|
class XGAResponseMessage(TypedDict, total=False):
|
|
13
13
|
message_id: str
|
|
14
14
|
type: XGAResponseMsgType
|
|
15
15
|
is_llm_message: bool
|
|
16
|
-
content: Union[Dict[str, Any],
|
|
16
|
+
content: Union[Dict[str, Any], str]
|
|
17
17
|
metadata: Dict[str, Any]
|
|
18
18
|
|
|
19
19
|
class XGATaskResult(TypedDict, total=False):
|
xgae/engine/mcp_tool_box.py
CHANGED
|
@@ -124,11 +124,11 @@ class XGAMcpToolBox(XGAToolBox):
|
|
|
124
124
|
result = XGAToolResult(success=True, output=str(tool_result))
|
|
125
125
|
except Exception as e:
|
|
126
126
|
error = f"Call mcp tool '{tool_name}' error: {str(e)}"
|
|
127
|
-
logging.error(f"
|
|
127
|
+
logging.error(f"McpToolBox call_tool: {error}")
|
|
128
128
|
result = XGAToolResult(success=False, output=error)
|
|
129
129
|
else:
|
|
130
130
|
error = f"No MCP tool found with name: {tool_name}"
|
|
131
|
-
logging.info(f"
|
|
131
|
+
logging.info(f"McpToolBox call_tool: error={error}")
|
|
132
132
|
result = XGAToolResult(success=False, output=error)
|
|
133
133
|
|
|
134
134
|
return result
|
|
@@ -179,11 +179,11 @@ class XGAMcpToolBox(XGAToolBox):
|
|
|
179
179
|
|
|
180
180
|
return server_config
|
|
181
181
|
else:
|
|
182
|
-
logging.warning("MCP servers config file not found at:
|
|
182
|
+
logging.warning(f"McpToolBox load_mcp_servers_config: MCP servers config file not found at: {mcp_config_path}")
|
|
183
183
|
return {"mcpServers": {}}
|
|
184
184
|
|
|
185
185
|
except Exception as e:
|
|
186
|
-
logging.error("Failed to load MCP servers config:
|
|
186
|
+
logging.error(f"McpToolBox load_mcp_servers_config: Failed to load MCP servers config: {e}")
|
|
187
187
|
return {"mcpServers": {}}
|
|
188
188
|
|
|
189
189
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
3
|
from typing import List, Dict, Any, AsyncGenerator, override,Optional
|
|
4
|
+
|
|
5
|
+
from xgae.utils import handle_error
|
|
4
6
|
from xgae.utils.json_helpers import format_for_yield
|
|
5
7
|
|
|
6
8
|
from xgae.engine.responser.responser_base import TaskResponseProcessor, TaskResponserContext, TaskRunContinuousState
|
|
@@ -11,22 +13,20 @@ class NonStreamTaskResponser(TaskResponseProcessor):
|
|
|
11
13
|
super().__init__(response_context)
|
|
12
14
|
|
|
13
15
|
@override
|
|
14
|
-
async def process_response(self,
|
|
15
|
-
|
|
16
|
+
async def process_response(self,
|
|
17
|
+
llm_response: Any,prompt_messages: List[Dict[str, Any]],
|
|
18
|
+
continuous_state: TaskRunContinuousState
|
|
19
|
+
) -> AsyncGenerator[Dict[str, Any], None]:
|
|
16
20
|
llm_content = ""
|
|
17
21
|
parsed_xml_data = []
|
|
18
22
|
finish_reason = None
|
|
19
23
|
llm_count = continuous_state.get("auto_continue_count")
|
|
20
24
|
|
|
21
25
|
try:
|
|
22
|
-
# Extract finish_reason, content, tool calls
|
|
23
26
|
if hasattr(llm_response, 'choices') and llm_response.choices:
|
|
24
27
|
if hasattr(llm_response.choices[0], 'finish_reason'):
|
|
25
28
|
finish_reason = llm_response.choices[0].finish_reason
|
|
26
|
-
logging.info(f"
|
|
27
|
-
|
|
28
|
-
self.root_span.event(name=f"non_stream_processor_start[{self.task_no}]({llm_count})", level="DEFAULT",
|
|
29
|
-
status_message=(f"finish_reason={finish_reason}, tool_exec_strategy={self.tool_execution_strategy}"))
|
|
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
|
|
32
32
|
if response_message:
|
|
@@ -35,27 +35,23 @@ 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"
|
|
39
|
-
xml_chunks = self._extract_xml_chunks(llm_content)[:self.max_xml_tool_calls]
|
|
40
|
-
if xml_chunks:
|
|
41
|
-
last_chunk = xml_chunks[-1]
|
|
42
|
-
last_chunk_pos = llm_content.find(last_chunk)
|
|
43
|
-
if last_chunk_pos >= 0:
|
|
44
|
-
llm_content = llm_content[:last_chunk_pos + len(last_chunk)]
|
|
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}")
|
|
45
39
|
parsed_xml_data = parsed_xml_data[:self.max_xml_tool_calls]
|
|
46
40
|
finish_reason = "xml_tool_limit_reached"
|
|
47
|
-
logging.info(f"NonStreamTask:LLM response finish_reason={finish_reason}")
|
|
48
|
-
else:
|
|
49
|
-
logging.warning(f"NonStreamTask:LLM response_message is empty")
|
|
50
41
|
|
|
51
|
-
|
|
52
|
-
|
|
42
|
+
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
|
+
f"parsed_xml_data_len={len(parsed_xml_data)}, llm_content_len={len(llm_content)}")
|
|
45
|
+
|
|
46
|
+
if len(llm_content) == 0:
|
|
47
|
+
logging.warning(f"NonStreamResp: LLM response_message llm_content is empty")
|
|
48
|
+
|
|
49
|
+
message_data = {"role": "assistant", "content": llm_content}
|
|
50
|
+
assistant_msg = self.add_response_message(type="assistant", content=message_data, is_llm_message=True)
|
|
53
51
|
yield assistant_msg
|
|
54
52
|
|
|
55
53
|
tool_calls_to_execute = [item['tool_call'] for item in parsed_xml_data]
|
|
56
54
|
if len(tool_calls_to_execute) > 0:
|
|
57
|
-
logging.info(f"NonStreamTask:Executing {len(tool_calls_to_execute)} tools with strategy: {self.tool_execution_strategy}")
|
|
58
|
-
|
|
59
55
|
tool_results = await self._execute_tools(tool_calls_to_execute, self.tool_execution_strategy)
|
|
60
56
|
|
|
61
57
|
tool_index = 0
|
|
@@ -81,28 +77,27 @@ class NonStreamTaskResponser(TaskResponseProcessor):
|
|
|
81
77
|
if tool_completed_msg["metadata"].get("agent_should_terminate") == "true":
|
|
82
78
|
finish_reason = "completed"
|
|
83
79
|
break
|
|
80
|
+
|
|
84
81
|
tool_index += 1
|
|
82
|
+
else:
|
|
83
|
+
finish_reason = "non_tool_call"
|
|
84
|
+
logging.warning(f"NonStreamResp: tool_calls is empty, No Tool need to call !")
|
|
85
85
|
|
|
86
86
|
if finish_reason:
|
|
87
87
|
finish_content = {"status_type": "finish", "finish_reason": finish_reason}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
yield format_for_yield(finish_msg_obj)
|
|
91
|
-
|
|
88
|
+
finish_msg = self.add_response_message(type="status", content=finish_content, is_llm_message=False)
|
|
89
|
+
yield format_for_yield(finish_msg)
|
|
92
90
|
except Exception as e:
|
|
93
|
-
logging.error(f"
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
logging.critical(f"NonStreamTask: Re-raising error to stop further processing: {str(e)}")
|
|
104
|
-
self.root_span.event(name="re_raising_error_to_stop_further_processing", level="CRITICAL",
|
|
105
|
-
status_message=(f"Re-raising error to stop further processing: {str(e)}"))
|
|
91
|
+
logging.error(f"NonStreamResp: Process response llm_content: {llm_content}")
|
|
92
|
+
handle_error(e)
|
|
93
|
+
self.root_span.event(name="non_stream_process_response_error", level="ERROR",
|
|
94
|
+
status_message=f"Process non-streaming response error: {e}",
|
|
95
|
+
metadata={"content": llm_content})
|
|
96
|
+
|
|
97
|
+
content = {"role": "system", "status_type": "error", "message": f"Process non-streaming response error: {e}"}
|
|
98
|
+
error_msg = self.add_response_message(type="status", content=content, is_llm_message=False)
|
|
99
|
+
yield format_for_yield(error_msg)
|
|
100
|
+
|
|
106
101
|
raise # Use bare 'raise' to preserve the original exception with its traceback
|
|
107
102
|
|
|
108
103
|
|
|
@@ -6,6 +6,7 @@ from abc import ABC, abstractmethod
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from typing import List, Dict, Any, Optional, Tuple, Union, Literal, Callable, TypedDict, AsyncGenerator
|
|
8
8
|
|
|
9
|
+
from xgae.utils import handle_error
|
|
9
10
|
from xgae.utils.json_helpers import safe_json_parse
|
|
10
11
|
from xgae.utils.xml_tool_parser import XMLToolParser
|
|
11
12
|
|
|
@@ -26,9 +27,11 @@ class TaskResponserContext(TypedDict, total=False):
|
|
|
26
27
|
task_no: int
|
|
27
28
|
model_name: str
|
|
28
29
|
max_xml_tool_calls: int # LLM generate max_xml_tool limit, 0 is no limit
|
|
30
|
+
use_assistant_chunk_msg: bool
|
|
29
31
|
tool_execution_strategy: ToolExecutionStrategy
|
|
30
32
|
xml_adding_strategy: XmlAddingStrategy
|
|
31
33
|
add_response_msg_func: Callable
|
|
34
|
+
create_response_msg_func: Callable
|
|
32
35
|
tool_box: XGAToolBox
|
|
33
36
|
task_langfuse: XGATaskLangFuse
|
|
34
37
|
|
|
@@ -37,6 +40,7 @@ class TaskRunContinuousState(TypedDict, total=False):
|
|
|
37
40
|
accumulated_content: str
|
|
38
41
|
auto_continue_count: int
|
|
39
42
|
auto_continue: bool
|
|
43
|
+
assistant_msg_sequence: int
|
|
40
44
|
|
|
41
45
|
|
|
42
46
|
@dataclass
|
|
@@ -66,6 +70,7 @@ class TaskResponseProcessor(ABC):
|
|
|
66
70
|
task_langfuse = response_context.get("task_langfuse")
|
|
67
71
|
self.root_span = task_langfuse.root_span
|
|
68
72
|
self.add_response_message = response_context.get("add_response_msg_func")
|
|
73
|
+
self.create_response_message = response_context.get("create_response_msg_func")
|
|
69
74
|
|
|
70
75
|
self.tool_box = response_context.get("tool_box")
|
|
71
76
|
self.xml_parser = XMLToolParser()
|
|
@@ -75,7 +80,7 @@ class TaskResponseProcessor(ABC):
|
|
|
75
80
|
async def process_response(self,
|
|
76
81
|
llm_response: AsyncGenerator,
|
|
77
82
|
prompt_messages: List[Dict[str, Any]],
|
|
78
|
-
continuous_state:
|
|
83
|
+
continuous_state: TaskRunContinuousState
|
|
79
84
|
) -> AsyncGenerator[Dict[str, Any], None]:
|
|
80
85
|
pass
|
|
81
86
|
|
|
@@ -119,9 +124,8 @@ class TaskResponseProcessor(ABC):
|
|
|
119
124
|
|
|
120
125
|
# Find the earliest occurrence of any registered tool function name
|
|
121
126
|
# Check for available function names
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
for func_name in available_functions:
|
|
127
|
+
available_func_names = self.tool_box.get_task_tool_names(self.task_id)
|
|
128
|
+
for func_name in available_func_names:
|
|
125
129
|
# Convert function name to potential tag name (underscore to dash)
|
|
126
130
|
tag_name = func_name.replace('_', '-')
|
|
127
131
|
start_pattern = f'<{tag_name}'
|
|
@@ -169,12 +173,11 @@ class TaskResponseProcessor(ABC):
|
|
|
169
173
|
break
|
|
170
174
|
|
|
171
175
|
pos = max(pos + 1, current_pos)
|
|
172
|
-
|
|
173
176
|
except Exception as e:
|
|
174
|
-
logging.error(f"Error extracting XML chunks: {
|
|
175
|
-
|
|
176
|
-
self.root_span.event(name="
|
|
177
|
-
|
|
177
|
+
logging.error(f"TaskProcessor extract_xml_chunks: Error extracting XML chunks: {content}")
|
|
178
|
+
handle_error(e)
|
|
179
|
+
self.root_span.event(name="task_process_extract_xml_chunk_error", level="ERROR",
|
|
180
|
+
status_message=(f"Error extracting XML chunks: {e}"), metadata={"content": content})
|
|
178
181
|
|
|
179
182
|
return chunks
|
|
180
183
|
|
|
@@ -193,14 +196,14 @@ class TaskResponseProcessor(ABC):
|
|
|
193
196
|
parsed_calls = self.xml_parser.parse_content(xml_chunk)
|
|
194
197
|
|
|
195
198
|
if not parsed_calls:
|
|
196
|
-
logging.error(f"No tool calls found in XML chunk: {xml_chunk}")
|
|
197
|
-
return
|
|
199
|
+
logging.error(f"TaskProcessor parse_xml_tool_call: No tool calls found in XML chunk: {xml_chunk}")
|
|
200
|
+
return
|
|
198
201
|
|
|
199
202
|
# Take the first tool call (should only be one per chunk)
|
|
200
203
|
xml_tool_call = parsed_calls[0]
|
|
201
204
|
if not xml_tool_call.function_name:
|
|
202
|
-
logging.error(f"xml_tool_call function name is empty: {xml_tool_call}")
|
|
203
|
-
return
|
|
205
|
+
logging.error(f"TaskProcessor parse_xml_tool_call: xml_tool_call function name is empty: {xml_tool_call}")
|
|
206
|
+
return
|
|
204
207
|
|
|
205
208
|
# Convert to the expected format
|
|
206
209
|
tool_call = {
|
|
@@ -217,15 +220,12 @@ class TaskResponseProcessor(ABC):
|
|
|
217
220
|
return tool_call, parsing_details
|
|
218
221
|
|
|
219
222
|
# If not the expected <function_calls><invoke> format, return None
|
|
220
|
-
logging.error(f"XML chunk does not contain expected <function_calls><invoke> format: {xml_chunk}")
|
|
221
|
-
return None
|
|
222
|
-
|
|
223
|
+
logging.error(f"TaskProcessor parse_xml_tool_call: XML chunk does not contain expected <function_calls><invoke> format: {xml_chunk}")
|
|
223
224
|
except Exception as e:
|
|
224
|
-
logging.error(f"Error parsing XML chunk: {
|
|
225
|
-
|
|
226
|
-
self.root_span.event(name="
|
|
227
|
-
|
|
228
|
-
return None
|
|
225
|
+
logging.error(f"TaskProcessor parse_xml_tool_call: Error parsing XML chunk: {xml_chunk}")
|
|
226
|
+
handle_error(e)
|
|
227
|
+
self.root_span.event(name="task_process_parsing_xml_chunk_error", level="ERROR",
|
|
228
|
+
status_message=(f"Error parsing XML chunk: {e}"), metadata={"xml_chunk": xml_chunk})
|
|
229
229
|
|
|
230
230
|
def _parse_xml_tool_calls(self, content: str) -> List[Dict[str, Any]]:
|
|
231
231
|
"""Parse XML tool calls from content string.
|
|
@@ -234,7 +234,7 @@ class TaskResponseProcessor(ABC):
|
|
|
234
234
|
List of dictionaries, each containing {'tool_call': ..., 'parsing_details': ...}
|
|
235
235
|
"""
|
|
236
236
|
parsed_data = []
|
|
237
|
-
|
|
237
|
+
xml_chunk = None
|
|
238
238
|
try:
|
|
239
239
|
xml_chunks = self._extract_xml_chunks(content)
|
|
240
240
|
|
|
@@ -246,23 +246,23 @@ class TaskResponseProcessor(ABC):
|
|
|
246
246
|
"tool_call": tool_call,
|
|
247
247
|
"parsing_details": parsing_details
|
|
248
248
|
})
|
|
249
|
-
|
|
250
249
|
except Exception as e:
|
|
251
|
-
logging.
|
|
252
|
-
|
|
253
|
-
|
|
250
|
+
logging.warning(f"TaskProcessor parse_xml_tool_calls: Error parsing XML tool calls, xml_chunk: {xml_chunk}")
|
|
251
|
+
handle_error(e)
|
|
252
|
+
self.root_span.event(name="task_process_parse_xml_tool_calls_error", level="ERROR",
|
|
253
|
+
status_message=(f"Error parsing XML tool calls: {e}"), metadata={"content": xml_chunk})
|
|
254
254
|
|
|
255
255
|
return parsed_data
|
|
256
256
|
|
|
257
257
|
|
|
258
258
|
async def _execute_tool(self, tool_call: Dict[str, Any]) -> XGAToolResult:
|
|
259
259
|
"""Execute a single tool call and return the result."""
|
|
260
|
-
|
|
260
|
+
function_name = tool_call.get("function_name", "empty_function")
|
|
261
|
+
exec_tool_span = self.root_span.span(name=f"execute_tool.{function_name}", input=tool_call["arguments"])
|
|
261
262
|
try:
|
|
262
|
-
function_name = tool_call["function_name"]
|
|
263
263
|
arguments = tool_call["arguments"]
|
|
264
264
|
|
|
265
|
-
logging.info(f"Executing tool: {function_name} with arguments: {arguments}")
|
|
265
|
+
logging.info(f"TaskProcessor execute_tool: Executing tool: {function_name} with arguments: {arguments}")
|
|
266
266
|
|
|
267
267
|
if isinstance(arguments, str):
|
|
268
268
|
try:
|
|
@@ -275,17 +275,19 @@ class TaskResponseProcessor(ABC):
|
|
|
275
275
|
if function_name in available_tool_names:
|
|
276
276
|
result = await self.tool_box.call_tool(self.task_id, function_name, arguments)
|
|
277
277
|
else:
|
|
278
|
-
logging.error(f"Tool function '{function_name}' not found in
|
|
278
|
+
logging.error(f"TaskProcessor execute_tool: Tool function '{function_name}' not found in toolbox")
|
|
279
279
|
result = XGAToolResult(success=False, output=f"Tool function '{function_name}' not found")
|
|
280
|
-
|
|
280
|
+
|
|
281
|
+
logging.info(f"TaskProcessor execute_tool: Tool execution complete: {function_name} -> {result}")
|
|
281
282
|
exec_tool_span.update(status_message="tool_executed", output=result)
|
|
282
283
|
|
|
283
284
|
return result
|
|
284
285
|
except Exception as e:
|
|
285
|
-
logging.error(f"Error executing tool {
|
|
286
|
+
logging.error(f"TaskProcessor execute_tool: Error executing tool {function_name}")
|
|
286
287
|
|
|
287
|
-
exec_tool_span.update(status_message="
|
|
288
|
-
|
|
288
|
+
exec_tool_span.update(status_message="task_process_tool_exec_error", level="ERROR",
|
|
289
|
+
output=f"Error executing tool {function_name}, error: {str(e)}")
|
|
290
|
+
return XGAToolResult(success=False, output=f"Executing tool {function_name}, error: {str(e)}")
|
|
289
291
|
|
|
290
292
|
async def _execute_tools(
|
|
291
293
|
self,
|
|
@@ -319,7 +321,7 @@ class TaskResponseProcessor(ABC):
|
|
|
319
321
|
return []
|
|
320
322
|
tool_names = [t.get('function_name', 'unknown') for t in tool_calls]
|
|
321
323
|
logging.info(f"Executing {len(tool_calls)} tools sequentially: {tool_names}")
|
|
322
|
-
self.root_span.event(name="
|
|
324
|
+
self.root_span.event(name="task_process_executing_tools_sequentially", level="DEFAULT",
|
|
323
325
|
status_message=(f"Executing {len(tool_calls)} tools sequentially: {tool_names}"))
|
|
324
326
|
|
|
325
327
|
results = []
|
|
@@ -341,7 +343,7 @@ class TaskResponseProcessor(ABC):
|
|
|
341
343
|
|
|
342
344
|
except Exception as e:
|
|
343
345
|
logging.error(f"Error executing tool {tool_name}: {str(e)}")
|
|
344
|
-
self.root_span.event(name="
|
|
346
|
+
self.root_span.event(name="task_process_error_executing_tool", level="ERROR",
|
|
345
347
|
status_message=(f"Error executing tool {tool_name}: {str(e)}"))
|
|
346
348
|
error_result = XGAToolResult(success=False, output=f"Error executing tool: {str(e)}")
|
|
347
349
|
results.append((tool_call, error_result))
|
|
@@ -373,7 +375,7 @@ class TaskResponseProcessor(ABC):
|
|
|
373
375
|
for i, (tool_call, result) in enumerate(zip(tool_calls, results)):
|
|
374
376
|
if isinstance(result, Exception):
|
|
375
377
|
logging.error(f"Error executing tool {tool_call.get('function_name', 'unknown')}: {str(result)}")
|
|
376
|
-
self.root_span.event(name="
|
|
378
|
+
self.root_span.event(name="task_process_error_executing_tool", level="ERROR", status_message=(
|
|
377
379
|
f"Error executing tool {tool_call.get('function_name', 'unknown')}: {str(result)}"))
|
|
378
380
|
# Create error result
|
|
379
381
|
error_result = XGAToolResult(success=False, output=f"Error executing tool: {str(result)}")
|
|
@@ -388,7 +390,7 @@ class TaskResponseProcessor(ABC):
|
|
|
388
390
|
|
|
389
391
|
except Exception as e:
|
|
390
392
|
logging.error(f"Error in parallel tool execution: {str(e)}", exc_info=True)
|
|
391
|
-
self.root_span.event(name="
|
|
393
|
+
self.root_span.event(name="task_process_error_in_parallel_tool_execution", level="ERROR",
|
|
392
394
|
status_message=(f"Error in parallel tool execution: {str(e)}"))
|
|
393
395
|
# Return error results for all tools if the gather itself fails
|
|
394
396
|
return [(tool_call, XGAToolResult(success=False, output=f"Execution error: {str(e)}"))
|
|
@@ -459,7 +461,7 @@ class TaskResponseProcessor(ABC):
|
|
|
459
461
|
return message_obj # Return the modified message object
|
|
460
462
|
except Exception as e:
|
|
461
463
|
logging.error(f"Error adding tool result: {str(e)}", exc_info=True)
|
|
462
|
-
self.root_span.event(name="
|
|
464
|
+
self.root_span.event(name="task_process_error_adding_tool_result", level="ERROR",
|
|
463
465
|
status_message=(f"Error adding tool result: {str(e)}"),
|
|
464
466
|
metadata={"tool_call": tool_call, "result": result, "strategy": strategy,
|
|
465
467
|
"assistant_message_id": assistant_message_id,
|
|
@@ -479,7 +481,7 @@ class TaskResponseProcessor(ABC):
|
|
|
479
481
|
return message_obj # Return the full message object
|
|
480
482
|
except Exception as e2:
|
|
481
483
|
logging.error(f"Failed even with fallback message: {str(e2)}", exc_info=True)
|
|
482
|
-
self.root_span.event(name="
|
|
484
|
+
self.root_span.event(name="task_process_failed_even_with_fallback_message", level="ERROR",
|
|
483
485
|
status_message=(f"Failed even with fallback message: {str(e2)}"),
|
|
484
486
|
metadata={"tool_call": tool_call, "result": result, "strategy": strategy,
|
|
485
487
|
"assistant_message_id": assistant_message_id,
|