xgae 0.1.14__py3-none-any.whl → 0.1.16__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/cli_app.py +2 -2
- xgae/engine/mcp_tool_box.py +13 -12
- xgae/engine/prompt_builder.py +10 -10
- xgae/engine/responser/non_stream_responser.py +17 -19
- xgae/engine/responser/responser_base.py +79 -98
- xgae/engine/responser/stream_responser.py +49 -161
- xgae/engine/task_engine.py +94 -96
- xgae/engine/task_langfuse.py +12 -12
- xgae/tools/without_general_tools_app.py +2 -0
- xgae/utils/json_helpers.py +0 -29
- xgae/utils/llm_client.py +23 -23
- xgae/utils/xml_tool_parser.py +9 -9
- {xgae-0.1.14.dist-info → xgae-0.1.16.dist-info}/METADATA +1 -1
- xgae-0.1.16.dist-info/RECORD +21 -0
- xgae-0.1.14.dist-info/RECORD +0 -21
- {xgae-0.1.14.dist-info → xgae-0.1.16.dist-info}/WHEEL +0 -0
- {xgae-0.1.14.dist-info → xgae-0.1.16.dist-info}/entry_points.txt +0 -0
xgae/cli_app.py
CHANGED
|
@@ -56,7 +56,7 @@ async def cli() -> None:
|
|
|
56
56
|
trace_id = langfuse.trace(name="xgae_cli").trace_id
|
|
57
57
|
|
|
58
58
|
final_result = await engine.run_task_with_final_answer(
|
|
59
|
-
task_message={
|
|
59
|
+
task_message={'role': "user", 'content': user_message},
|
|
60
60
|
trace_id=trace_id
|
|
61
61
|
)
|
|
62
62
|
|
|
@@ -65,7 +65,7 @@ async def cli() -> None:
|
|
|
65
65
|
print(f"\n📌 ASK INFO: {final_result['content']}")
|
|
66
66
|
user_message = get_user_message("Enter ASK information (or 'exit' to quit)")
|
|
67
67
|
final_result = await engine.run_task_with_final_answer(
|
|
68
|
-
task_message={
|
|
68
|
+
task_message={'role': "user", 'content': user_message},
|
|
69
69
|
trace_id=trace_id
|
|
70
70
|
)
|
|
71
71
|
|
xgae/engine/mcp_tool_box.py
CHANGED
|
@@ -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(
|
|
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(
|
|
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]] = {}
|
|
@@ -77,7 +78,7 @@ class XGAMcpToolBox(XGAToolBox):
|
|
|
77
78
|
async def destroy_task_tool_box(self, task_id: str):
|
|
78
79
|
tool_schemas = self.get_task_tool_schemas(task_id, type="general_tool")
|
|
79
80
|
if len(tool_schemas) > 0:
|
|
80
|
-
await self.call_tool(task_id, "end_task", {
|
|
81
|
+
await self.call_tool(task_id, "end_task", {'task_id': task_id})
|
|
81
82
|
self.task_tool_schemas.pop(task_id, None)
|
|
82
83
|
|
|
83
84
|
@override
|
|
@@ -117,7 +118,7 @@ class XGAMcpToolBox(XGAToolBox):
|
|
|
117
118
|
if mcp_tool:
|
|
118
119
|
tool_args = args or {}
|
|
119
120
|
if server_name == self.GENERAL_MCP_SERVER_NAME:
|
|
120
|
-
tool_args = dict({
|
|
121
|
+
tool_args = dict({'task_id': task_id}, **tool_args)
|
|
121
122
|
is_general_tool = True
|
|
122
123
|
|
|
123
124
|
try:
|
|
@@ -151,9 +152,9 @@ class XGAMcpToolBox(XGAToolBox):
|
|
|
151
152
|
input_schema['properties'].pop("task_id", None)
|
|
152
153
|
if 'task_id' in input_schema['required']:
|
|
153
154
|
input_schema['required'].remove('task_id')
|
|
154
|
-
params_properties = input_schema.get(
|
|
155
|
+
params_properties = input_schema.get('properties', {})
|
|
155
156
|
for param_properties in params_properties.values():
|
|
156
|
-
param_properties.pop(
|
|
157
|
+
param_properties.pop('title', None)
|
|
157
158
|
|
|
158
159
|
metadata = tool.metadata or {}
|
|
159
160
|
tool_schema = XGAToolSchema(tool_name=tool.name,
|
|
@@ -172,24 +173,24 @@ class XGAMcpToolBox(XGAToolBox):
|
|
|
172
173
|
def _load_mcp_servers_config(mcp_config_path: str) -> Dict[str, Any]:
|
|
173
174
|
try:
|
|
174
175
|
if os.path.exists(mcp_config_path):
|
|
175
|
-
with open(mcp_config_path, 'r', encoding=
|
|
176
|
+
with open(mcp_config_path, 'r', encoding="utf-8") as f:
|
|
176
177
|
server_config = json.load(f)
|
|
177
178
|
|
|
178
|
-
for server_name, server_info in server_config[
|
|
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[
|
|
182
|
+
server_info['transport'] = "streamable_http" if "mcp" in server_info['url'] else "sse"
|
|
182
183
|
else:
|
|
183
|
-
server_info[
|
|
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 {
|
|
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 {
|
|
193
|
+
return {'mcpServers': {}}
|
|
193
194
|
|
|
194
195
|
|
|
195
196
|
if __name__ == "__main__":
|
xgae/engine/prompt_builder.py
CHANGED
|
@@ -34,19 +34,19 @@ 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[
|
|
40
|
-
|
|
41
|
-
openai_function["name"] = tool_schema.tool_name
|
|
42
|
-
openai_function["description"] = tool_schema.description if tool_schema.description else 'No description available'
|
|
38
|
+
openai_schema['type'] = "function"
|
|
39
|
+
openai_schema['function'] = openai_function
|
|
43
40
|
|
|
41
|
+
openai_function['name'] = tool_schema.tool_name
|
|
42
|
+
openai_function['description'] = tool_schema.description if tool_schema.description else 'No description available'
|
|
44
43
|
openai_parameters = {}
|
|
44
|
+
openai_function['parameters'] = openai_parameters
|
|
45
|
+
|
|
45
46
|
input_schema = tool_schema.input_schema
|
|
46
|
-
|
|
47
|
-
openai_parameters[
|
|
48
|
-
openai_parameters[
|
|
49
|
-
openai_parameters["required"] = input_schema["required"]
|
|
47
|
+
openai_parameters['type'] = input_schema['type']
|
|
48
|
+
openai_parameters['properties'] = input_schema.get('properties', {})
|
|
49
|
+
openai_parameters['required'] = input_schema['required']
|
|
50
50
|
|
|
51
51
|
openai_schemas.append(openai_schema)
|
|
52
52
|
|
|
@@ -69,7 +69,7 @@ class XGAPromptBuilder():
|
|
|
69
69
|
for tool_schema in tool_schemas:
|
|
70
70
|
description = tool_schema.description if tool_schema.description else 'No description available'
|
|
71
71
|
tool_info += f"- **{tool_schema.tool_name}**: {description}\n"
|
|
72
|
-
parameters = tool_schema.input_schema.get(
|
|
72
|
+
parameters = tool_schema.input_schema.get('properties', {})
|
|
73
73
|
tool_info += f" Parameters: {parameters}\n"
|
|
74
74
|
tool_prompt = tool_prompt.replace("{tool_schemas}", tool_info)
|
|
75
75
|
|
|
@@ -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
|
-
|
|
6
|
+
|
|
7
7
|
|
|
8
8
|
from xgae.engine.responser.responser_base import TaskResponseProcessor, TaskResponserContext, TaskRunContinuousState
|
|
9
9
|
|
|
@@ -20,12 +20,12 @@ class NonStreamTaskResponser(TaskResponseProcessor):
|
|
|
20
20
|
llm_content = ""
|
|
21
21
|
parsed_xml_data = []
|
|
22
22
|
finish_reason = None
|
|
23
|
-
|
|
23
|
+
auto_continue_count = continuous_state['auto_continue_count']
|
|
24
24
|
|
|
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,24 +35,22 @@ 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:
|
|
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
|
-
self.root_span.event(name=f"non_stream_processor_start[{self.task_no}]({
|
|
43
|
-
status_message=f"finish_reason={finish_reason}, tool_exec_strategy={self.
|
|
43
|
+
self.root_span.event(name=f"non_stream_processor_start[{self.task_no}]({auto_continue_count})", level="DEFAULT",
|
|
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
|
-
if len(llm_content) == 0:
|
|
47
|
-
logging.warning(f"NonStreamResp: LLM response_message llm_content is empty")
|
|
48
|
-
|
|
49
47
|
message_data = {"role": "assistant", "content": llm_content}
|
|
50
48
|
assistant_msg = self.add_response_message(type="assistant", content=message_data, is_llm_message=True)
|
|
51
49
|
yield assistant_msg
|
|
52
50
|
|
|
53
51
|
tool_calls_to_execute = [item['tool_call'] for item in parsed_xml_data]
|
|
54
52
|
if len(tool_calls_to_execute) > 0:
|
|
55
|
-
tool_results = await self._execute_tools(tool_calls_to_execute, self.
|
|
53
|
+
tool_results = await self._execute_tools(tool_calls_to_execute, self.tool_exec_strategy)
|
|
56
54
|
|
|
57
55
|
tool_index = 0
|
|
58
56
|
for i, (returned_tool_call, tool_result) in enumerate(tool_results):
|
|
@@ -64,37 +62,37 @@ class NonStreamTaskResponser(TaskResponseProcessor):
|
|
|
64
62
|
tool_context = self._create_tool_context(tool_call, tool_index, assistant_msg_id, parsing_details, tool_result)
|
|
65
63
|
|
|
66
64
|
tool_start_msg = self._add_tool_start_message(tool_context)
|
|
67
|
-
yield
|
|
65
|
+
yield tool_start_msg
|
|
68
66
|
|
|
69
67
|
tool_message = self._add_tool_messsage(tool_call, tool_result, self.xml_adding_strategy, assistant_msg_id, parsing_details)
|
|
70
68
|
|
|
71
69
|
tool_completed_msg = self._add_tool_completed_message(tool_context, tool_message['message_id'])
|
|
72
|
-
yield
|
|
70
|
+
yield tool_completed_msg
|
|
73
71
|
|
|
74
|
-
yield
|
|
72
|
+
yield tool_message
|
|
75
73
|
|
|
76
|
-
if
|
|
74
|
+
if tool_context.function_name in ['ask', 'complete']:
|
|
77
75
|
finish_reason = "completed"
|
|
78
76
|
break
|
|
79
77
|
|
|
80
78
|
tool_index += 1
|
|
81
79
|
else:
|
|
82
80
|
finish_reason = "non_tool_call"
|
|
83
|
-
logging.warning(f"NonStreamResp:
|
|
81
|
+
logging.warning(f"NonStreamResp: finish_reason='non_tool_call', No Tool need to call !")
|
|
84
82
|
|
|
85
83
|
if finish_reason:
|
|
86
|
-
finish_content = {
|
|
84
|
+
finish_content = {'status_type': "finish", 'finish_reason': finish_reason}
|
|
87
85
|
finish_msg = self.add_response_message(type="status", content=finish_content, is_llm_message=False)
|
|
88
|
-
yield
|
|
86
|
+
yield finish_msg
|
|
89
87
|
except Exception as e:
|
|
90
88
|
trace = log_trace(e, f"NonStreamResp: Process response llm_content:\n {llm_content}")
|
|
91
89
|
self.root_span.event(name="non_stream_process_response_error", level="ERROR",
|
|
92
90
|
status_message=f"Process non-streaming response error: {e}",
|
|
93
91
|
metadata={"content": llm_content, "trace": trace})
|
|
94
92
|
|
|
95
|
-
content = {
|
|
93
|
+
content = {'role': "system", 'status_type': "error", 'message': f"Process non-streaming response error: {e}"}
|
|
96
94
|
error_msg = self.add_response_message(type="status", content=content, is_llm_message=False)
|
|
97
|
-
yield
|
|
95
|
+
yield error_msg
|
|
98
96
|
|
|
99
97
|
raise # Use bare 'raise' to preserve the original exception with its traceback
|
|
100
98
|
|
|
@@ -28,8 +28,7 @@ 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
|
-
|
|
32
|
-
tool_execute_on_stream: bool
|
|
31
|
+
tool_exec_strategy: ToolExecutionStrategy
|
|
33
32
|
xml_adding_strategy: XmlAddingStrategy
|
|
34
33
|
add_response_msg_func: Callable
|
|
35
34
|
create_response_msg_func: Callable
|
|
@@ -49,9 +48,9 @@ class ToolExecutionContext:
|
|
|
49
48
|
"""Context for a tool execution including call details, result, and display info."""
|
|
50
49
|
tool_call: Dict[str, Any]
|
|
51
50
|
tool_index: int
|
|
52
|
-
function_name:
|
|
51
|
+
function_name: str
|
|
52
|
+
xml_tag_name: str
|
|
53
53
|
result: Optional[XGAToolResult] = None
|
|
54
|
-
xml_tag_name: Optional[str] = None
|
|
55
54
|
error: Optional[Exception] = None
|
|
56
55
|
assistant_message_id: Optional[str] = None
|
|
57
56
|
parsing_details: Optional[Dict[str, Any]] = None
|
|
@@ -61,21 +60,22 @@ class TaskResponseProcessor(ABC):
|
|
|
61
60
|
def __init__(self, response_context: TaskResponserContext):
|
|
62
61
|
self.response_context = response_context
|
|
63
62
|
|
|
64
|
-
self.task_id
|
|
65
|
-
self.task_run_id
|
|
66
|
-
self.task_no
|
|
67
|
-
self.
|
|
68
|
-
self.xml_adding_strategy
|
|
69
|
-
self.max_xml_tool_calls
|
|
70
|
-
self.tool_execute_on_stream = response_context.get("tool_execute_on_stream", False)
|
|
63
|
+
self.task_id = response_context['task_id']
|
|
64
|
+
self.task_run_id = response_context['task_run_id']
|
|
65
|
+
self.task_no = response_context['task_no']
|
|
66
|
+
self.tool_exec_strategy = response_context['tool_exec_strategy']
|
|
67
|
+
self.xml_adding_strategy = response_context['xml_adding_strategy']
|
|
68
|
+
self.max_xml_tool_calls = response_context['max_xml_tool_calls']
|
|
71
69
|
|
|
72
|
-
|
|
70
|
+
self.add_response_message = response_context['add_response_msg_func']
|
|
71
|
+
self.create_response_message = response_context['create_response_msg_func']
|
|
72
|
+
self.tool_box = response_context['tool_box']
|
|
73
|
+
|
|
74
|
+
task_langfuse = response_context['task_langfuse']
|
|
73
75
|
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
76
|
|
|
77
|
-
self.
|
|
78
|
-
|
|
77
|
+
self.xml_parser = XMLToolParser()
|
|
78
|
+
|
|
79
79
|
|
|
80
80
|
|
|
81
81
|
@abstractmethod
|
|
@@ -209,16 +209,16 @@ class TaskResponseProcessor(ABC):
|
|
|
209
209
|
|
|
210
210
|
# Convert to the expected format
|
|
211
211
|
tool_call = {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
|
215
215
|
}
|
|
216
216
|
|
|
217
217
|
# Include the parsing details
|
|
218
218
|
parsing_details = xml_tool_call.parsing_details
|
|
219
|
-
parsing_details[
|
|
219
|
+
parsing_details['raw_xml'] = xml_tool_call.raw_xml
|
|
220
220
|
|
|
221
|
-
logging.debug(f"Parsed new format tool call: {tool_call}")
|
|
221
|
+
logging.debug(f"TaskProcessor parse_xml_tool_call: Parsed new format tool call: {tool_call}")
|
|
222
222
|
return tool_call, parsing_details
|
|
223
223
|
|
|
224
224
|
# If not the expected <function_calls><invoke> format, return None
|
|
@@ -260,10 +260,10 @@ class TaskResponseProcessor(ABC):
|
|
|
260
260
|
|
|
261
261
|
async def _execute_tool(self, tool_call: Dict[str, Any]) -> XGAToolResult:
|
|
262
262
|
"""Execute a single tool call and return the result."""
|
|
263
|
-
function_name = tool_call
|
|
263
|
+
function_name = tool_call['function_name']
|
|
264
264
|
exec_tool_span = self.root_span.span(name=f"execute_tool.{function_name}", input=tool_call["arguments"])
|
|
265
265
|
try:
|
|
266
|
-
arguments = tool_call
|
|
266
|
+
arguments = tool_call.get('arguments', {})
|
|
267
267
|
if isinstance(arguments, str):
|
|
268
268
|
try:
|
|
269
269
|
arguments = safe_json_parse(arguments)
|
|
@@ -319,7 +319,7 @@ class TaskResponseProcessor(ABC):
|
|
|
319
319
|
logging.warning("TaskProcessor execute_tools_sequentially: tool_calls is empty")
|
|
320
320
|
return []
|
|
321
321
|
|
|
322
|
-
tool_names = [
|
|
322
|
+
tool_names = [tc['function_name'] for tc in tool_calls]
|
|
323
323
|
tool_num = len(tool_calls)
|
|
324
324
|
if tool_num > 1:
|
|
325
325
|
logging.info(f"TaskProcessor execute_tools_sequentially: Executing {tool_num} tools sequentially: {tool_names}")
|
|
@@ -328,13 +328,13 @@ class TaskResponseProcessor(ABC):
|
|
|
328
328
|
|
|
329
329
|
results = []
|
|
330
330
|
for index, tool_call in enumerate(tool_calls):
|
|
331
|
-
tool_name = tool_call
|
|
331
|
+
tool_name = tool_call['function_name']
|
|
332
332
|
logging.info(f"TaskProcessor execute_tools_sequentially: Executing tool '{tool_name}', sequence={index + 1}/{tool_num}")
|
|
333
333
|
result = await self._execute_tool(tool_call)
|
|
334
334
|
results.append((tool_call, result))
|
|
335
335
|
|
|
336
336
|
# Check if this is a terminating tool (ask or complete)
|
|
337
|
-
if tool_name in [
|
|
337
|
+
if tool_name in ["ask", "complete"]:
|
|
338
338
|
if len(results) < tool_num:
|
|
339
339
|
logging.info(f"TaskProcessor execute_tools_sequentially: Terminating tool '{tool_name}' executed, Stopping further tool execution.")
|
|
340
340
|
self.root_span.event(name="task_process_terminate_tool_executed", level="DEFAULT",
|
|
@@ -358,7 +358,7 @@ class TaskResponseProcessor(ABC):
|
|
|
358
358
|
logging.warning("TaskProcessor execute_tools_in_parallel: tool_calls is empty")
|
|
359
359
|
return []
|
|
360
360
|
|
|
361
|
-
tool_names = [
|
|
361
|
+
tool_names = [tc['function_name'] for tc in tool_calls]
|
|
362
362
|
tool_num = len(tool_calls)
|
|
363
363
|
if tool_num > 1:
|
|
364
364
|
logging.info(f"TaskProcessor execute_tools_in_parallel: Executing {tool_num} tools sequentially: {tool_names}")
|
|
@@ -384,17 +384,6 @@ class TaskResponseProcessor(ABC):
|
|
|
384
384
|
assistant_message_id: Optional[str] = None,
|
|
385
385
|
parsing_details: Optional[Dict[str, Any]] = None
|
|
386
386
|
) -> 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
387
|
# Create two versions of the structured result
|
|
399
388
|
# Rich version for the frontend
|
|
400
389
|
result_for_frontend = self._create_structured_tool_result(tool_call, result, parsing_details, for_llm=False)
|
|
@@ -403,21 +392,24 @@ class TaskResponseProcessor(ABC):
|
|
|
403
392
|
|
|
404
393
|
# Add the message with the appropriate role to the conversation history
|
|
405
394
|
# This allows the LLM to see the tool result in subsequent interactions
|
|
395
|
+
role = "user" if strategy == "user_message" else "assistant"
|
|
406
396
|
content = {
|
|
407
|
-
|
|
408
|
-
|
|
397
|
+
'role': role,
|
|
398
|
+
'content': json.dumps(result_for_llm)
|
|
409
399
|
}
|
|
410
400
|
|
|
401
|
+
metadata = {}
|
|
402
|
+
if assistant_message_id:
|
|
403
|
+
metadata['assistant_message_id'] = assistant_message_id
|
|
404
|
+
|
|
405
|
+
if parsing_details:
|
|
406
|
+
metadata['parsing_details'] = parsing_details
|
|
407
|
+
|
|
411
408
|
metadata['frontend_content'] = result_for_frontend
|
|
412
409
|
|
|
413
|
-
tool_message = self.add_response_message(
|
|
414
|
-
type="tool",
|
|
415
|
-
content=content,
|
|
416
|
-
is_llm_message=True,
|
|
417
|
-
metadata=metadata
|
|
418
|
-
)
|
|
410
|
+
tool_message = self.add_response_message(type="tool", content=content, is_llm_message=True, metadata=metadata)
|
|
419
411
|
|
|
420
|
-
# Let's
|
|
412
|
+
# Let's result_for_frontend the message for yielding.
|
|
421
413
|
yield_message = tool_message.copy()
|
|
422
414
|
yield_message['content'] = result_for_frontend
|
|
423
415
|
|
|
@@ -429,10 +421,9 @@ class TaskResponseProcessor(ABC):
|
|
|
429
421
|
result: XGAToolResult,
|
|
430
422
|
parsing_details: Optional[Dict[str, Any]] = None,
|
|
431
423
|
for_llm: bool = False) -> Dict[str, Any]:
|
|
432
|
-
function_name = tool_call
|
|
433
|
-
xml_tag_name = tool_call
|
|
434
|
-
arguments = tool_call.get(
|
|
435
|
-
tool_call_id = tool_call.get("id")
|
|
424
|
+
function_name = tool_call['function_name']
|
|
425
|
+
xml_tag_name = tool_call['xml_tag_name']
|
|
426
|
+
arguments = tool_call.get('arguments', {})
|
|
436
427
|
|
|
437
428
|
# Process the output - if it's a JSON string, parse it back to an object
|
|
438
429
|
output = result.output
|
|
@@ -449,15 +440,14 @@ class TaskResponseProcessor(ABC):
|
|
|
449
440
|
output_to_use = output
|
|
450
441
|
|
|
451
442
|
structured_result = {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
"error": None if result.success else result.output
|
|
443
|
+
'tool_execution': {
|
|
444
|
+
'function_name' : function_name,
|
|
445
|
+
'xml_tag_name' : xml_tag_name,
|
|
446
|
+
'arguments' : arguments,
|
|
447
|
+
'result' : {
|
|
448
|
+
'success' : result.success,
|
|
449
|
+
'output' : output_to_use,
|
|
450
|
+
'error' : None if result.success else result.output
|
|
461
451
|
},
|
|
462
452
|
}
|
|
463
453
|
}
|
|
@@ -474,26 +464,25 @@ class TaskResponseProcessor(ABC):
|
|
|
474
464
|
) -> ToolExecutionContext:
|
|
475
465
|
"""Create a tool execution context with display name and parsing details populated."""
|
|
476
466
|
return ToolExecutionContext(
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
result=result
|
|
467
|
+
tool_call = tool_call,
|
|
468
|
+
tool_index = tool_index,
|
|
469
|
+
function_name = tool_call['function_name'],
|
|
470
|
+
xml_tag_name = tool_call['xml_tag_name'],
|
|
471
|
+
assistant_message_id = assistant_message_id,
|
|
472
|
+
parsing_details = parsing_details,
|
|
473
|
+
result = result
|
|
484
474
|
)
|
|
485
475
|
|
|
486
476
|
|
|
487
477
|
def _add_tool_start_message(self, context: ToolExecutionContext) -> Optional[Dict[str, Any]]:
|
|
488
478
|
"""Formats, saves, and returns a tool started status message."""
|
|
489
|
-
tool_name = context.xml_tag_name or context.function_name
|
|
490
479
|
content = {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
480
|
+
'status_type' : "tool_started",
|
|
481
|
+
'role' : "assistant",
|
|
482
|
+
'function_name' : context.function_name,
|
|
483
|
+
'xml_tag_name' : context.xml_tag_name,
|
|
484
|
+
'message' : f"Starting execution of {context.function_name}",
|
|
485
|
+
'tool_index' : context.tool_index
|
|
497
486
|
}
|
|
498
487
|
|
|
499
488
|
return self.add_response_message(type="status", content=content, is_llm_message=False)
|
|
@@ -503,42 +492,34 @@ class TaskResponseProcessor(ABC):
|
|
|
503
492
|
if not context.result:
|
|
504
493
|
return self._add_tool_error_message(context)
|
|
505
494
|
|
|
506
|
-
tool_name = context.xml_tag_name or context.function_name
|
|
507
495
|
status_type = "tool_completed" if context.result.success else "tool_failed"
|
|
508
|
-
message_text = f"Tool {
|
|
496
|
+
message_text = f"Tool {context.function_name} {'completed successfully' if context.result.success else 'failed'}"
|
|
509
497
|
|
|
510
498
|
content = {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
"tool_call_id": context.tool_call.get("id")
|
|
499
|
+
'status_type' : status_type,
|
|
500
|
+
'role' : "assistant",
|
|
501
|
+
'function_name' : context.function_name,
|
|
502
|
+
'xml_tag_name' : context.xml_tag_name,
|
|
503
|
+
'message' : message_text,
|
|
504
|
+
'tool_index' : context.tool_index
|
|
518
505
|
}
|
|
519
506
|
|
|
520
507
|
metadata = {}
|
|
521
|
-
|
|
522
|
-
|
|
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"
|
|
508
|
+
if tool_message_id:
|
|
509
|
+
metadata['tool_result_message_id'] = tool_message_id
|
|
527
510
|
|
|
528
511
|
return self.add_response_message(type="status", content=content, is_llm_message=False, metadata=metadata)
|
|
529
512
|
|
|
530
513
|
def _add_tool_error_message(self, context: ToolExecutionContext) -> Optional[Dict[str, Any]]:
|
|
531
514
|
"""Formats, saves, and returns a tool error status message."""
|
|
532
|
-
error_msg = str(context.error) if context.error else "
|
|
533
|
-
tool_name = context.xml_tag_name or context.function_name
|
|
515
|
+
error_msg = str(context.error) if context.error else "Tool execution unknown exception"
|
|
534
516
|
content = {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
"tool_call_id": context.tool_call.get("id")
|
|
517
|
+
'status_type' : "tool_error",
|
|
518
|
+
'role' : "assistant",
|
|
519
|
+
'function_name' : context.function_name,
|
|
520
|
+
'xml_tag_name' : context.xml_tag_name,
|
|
521
|
+
'message' : f"Executing tool {context.function_name} exception: {error_msg}",
|
|
522
|
+
'tool_index' : context.tool_index
|
|
542
523
|
}
|
|
543
524
|
|
|
544
525
|
return self.add_response_message(type="status", content=content, is_llm_message=False)
|