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
|
@@ -3,7 +3,7 @@ import asyncio
|
|
|
3
3
|
from typing import List, Dict, Any, Optional, AsyncGenerator, override
|
|
4
4
|
|
|
5
5
|
from xgae.utils import log_trace
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
from xgae.engine.responser.responser_base import TaskResponseProcessor, TaskResponserContext, TaskRunContinuousState
|
|
8
8
|
|
|
9
9
|
|
|
@@ -17,27 +17,23 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
17
17
|
prompt_messages: List[Dict[str, Any]],
|
|
18
18
|
continuous_state: TaskRunContinuousState
|
|
19
19
|
) -> AsyncGenerator[Dict[str, Any], None]:
|
|
20
|
-
accumulated_content = continuous_state
|
|
21
|
-
auto_continue_count = continuous_state
|
|
22
|
-
can_auto_continue
|
|
23
|
-
|
|
20
|
+
accumulated_content = continuous_state['accumulated_content']
|
|
21
|
+
auto_continue_count = continuous_state['auto_continue_count']
|
|
22
|
+
can_auto_continue = continuous_state['auto_continue']
|
|
23
|
+
msg_sequence = continuous_state['assistant_msg_sequence']
|
|
24
|
+
|
|
25
|
+
use_assistant_chunk_msg = self.response_context["use_assistant_chunk_msg"]
|
|
24
26
|
|
|
25
27
|
finish_reason = None
|
|
26
28
|
should_auto_continue = False
|
|
27
|
-
sequence = continuous_state.get('assistant_msg_sequence', 0)
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
tool_results_buffer = [] # Store (tool_call, result, tool_index, context)
|
|
32
|
-
tool_index = 0
|
|
33
|
-
current_xml_content = accumulated_content # Track XML content for streaming detection
|
|
34
|
-
|
|
35
|
-
logging.info(f"=== StreamResp:tool_execute_on_stream={self.tool_execute_on_stream}, auto_continue_count={auto_continue_count}, accumulated_content_len={len(accumulated_content)}")
|
|
30
|
+
logging.info(f"=== StreamResp:Start Process Response, assistant_msg_sequence={msg_sequence}, "
|
|
31
|
+
f"accumulated_content_len={len(accumulated_content)}")
|
|
36
32
|
try:
|
|
37
33
|
async for llm_chunk in llm_response:
|
|
38
34
|
if hasattr(llm_chunk, 'choices') and llm_chunk.choices and hasattr(llm_chunk.choices[0],'finish_reason'):
|
|
39
35
|
if llm_chunk.choices[0].finish_reason:
|
|
40
|
-
finish_reason = llm_chunk.choices[0].finish_reason
|
|
36
|
+
finish_reason = llm_chunk.choices[0].finish_reason # LLM finish reason: ‘stop' , 'length'
|
|
41
37
|
logging.info(f"StreamResp:LLM chunk response finish_reason={finish_reason}")
|
|
42
38
|
|
|
43
39
|
if hasattr(llm_chunk, 'choices') and llm_chunk.choices:
|
|
@@ -46,71 +42,24 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
46
42
|
if llm_chunk_msg and hasattr(llm_chunk_msg, 'content') and llm_chunk_msg.content:
|
|
47
43
|
chunk_content = llm_chunk_msg.content
|
|
48
44
|
accumulated_content += chunk_content
|
|
49
|
-
current_xml_content += chunk_content #Track streaming XML content
|
|
50
45
|
|
|
51
46
|
xml_tool_call_count = len(self._extract_xml_chunks(accumulated_content))
|
|
52
|
-
if self.max_xml_tool_calls <= 0 or xml_tool_call_count
|
|
47
|
+
if self.max_xml_tool_calls <= 0 or xml_tool_call_count <= self.max_xml_tool_calls:
|
|
53
48
|
if use_assistant_chunk_msg:
|
|
54
49
|
message_data = {"role": "assistant", "content": chunk_content}
|
|
55
|
-
metadata = {"sequence":
|
|
56
|
-
assistant_chunk_msg = self.create_response_message(type="assistant_chunk",
|
|
57
|
-
|
|
50
|
+
metadata = {"sequence": msg_sequence}
|
|
51
|
+
assistant_chunk_msg = self.create_response_message(type="assistant_chunk",
|
|
52
|
+
content=message_data,
|
|
53
|
+
is_llm_message=True,
|
|
54
|
+
metadata=metadata)
|
|
58
55
|
yield assistant_chunk_msg
|
|
59
|
-
|
|
60
|
-
#Process XML tool calls during streaming
|
|
61
|
-
if self.tool_execute_on_stream:
|
|
62
|
-
xml_chunks = self._extract_xml_chunks(current_xml_content)
|
|
63
|
-
for xml_chunk in xml_chunks:
|
|
64
|
-
current_xml_content = current_xml_content.replace(xml_chunk, "", 1)
|
|
65
|
-
result = self._parse_xml_tool_call(xml_chunk)
|
|
66
|
-
if result:
|
|
67
|
-
tool_call, parsing_details = result
|
|
68
|
-
|
|
69
|
-
# Create tool context for streaming execution
|
|
70
|
-
tool_context = self._create_tool_context(tool_call, tool_index, None, parsing_details)
|
|
71
|
-
|
|
72
|
-
# Yield tool start status immediately
|
|
73
|
-
tool_start_msg = self._add_tool_start_message(tool_context)
|
|
74
|
-
if tool_start_msg:
|
|
75
|
-
yield format_for_yield(tool_start_msg)
|
|
76
|
-
yielded_tool_indices.add(tool_index)
|
|
77
|
-
|
|
78
|
-
# Create async execution task
|
|
79
|
-
execution_task = asyncio.create_task(self._execute_tool(tool_call))
|
|
80
|
-
pending_tool_executions.append({"task": execution_task,"tool_call": tool_call,"tool_index": tool_index,
|
|
81
|
-
"context": tool_context,"parsing_details": parsing_details})
|
|
82
|
-
tool_index += 1
|
|
83
|
-
|
|
84
|
-
sequence += 1
|
|
56
|
+
msg_sequence += 1
|
|
85
57
|
else:
|
|
86
58
|
finish_reason = "xml_tool_limit_reached"
|
|
59
|
+
logging.warning(f"StreamResp: Over XML Tool Limit, finish_reason='xml_tool_limit_reached', "
|
|
60
|
+
f"xml_tool_call_count={xml_tool_call_count}")
|
|
87
61
|
break
|
|
88
62
|
|
|
89
|
-
if len(accumulated_content) == 0:
|
|
90
|
-
logging.warning(f"StreamResp: LLM response_message content is empty")
|
|
91
|
-
|
|
92
|
-
# Wait for pending tool executions from streaming phase
|
|
93
|
-
if pending_tool_executions:
|
|
94
|
-
logging.info(f"Waiting for {len(pending_tool_executions)} pending streamed tool executions")
|
|
95
|
-
|
|
96
|
-
pending_tasks = [execution["task"] for execution in pending_tool_executions]
|
|
97
|
-
done, _ = await asyncio.wait(pending_tasks)
|
|
98
|
-
|
|
99
|
-
for execution in pending_tool_executions:
|
|
100
|
-
tool_idx = execution.get("tool_index", -1)
|
|
101
|
-
context = execution["context"]
|
|
102
|
-
|
|
103
|
-
try:
|
|
104
|
-
if execution["task"].done():
|
|
105
|
-
result = execution["task"].result()
|
|
106
|
-
context.result = result
|
|
107
|
-
tool_results_buffer.append((execution["tool_call"],result,tool_idx,context))
|
|
108
|
-
else:
|
|
109
|
-
logging.warning(f"Task for tool index {tool_idx} not done after wait.")
|
|
110
|
-
except Exception as e:
|
|
111
|
-
logging.error(f"Error getting result for pending tool execution {tool_idx}: {str(e)}")
|
|
112
|
-
context.error = e
|
|
113
|
-
|
|
114
63
|
if finish_reason == "xml_tool_limit_reached":
|
|
115
64
|
xml_chunks = self._extract_xml_chunks(accumulated_content)
|
|
116
65
|
if len(xml_chunks) > self.max_xml_tool_calls:
|
|
@@ -124,9 +73,9 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
124
73
|
should_auto_continue = (can_auto_continue and finish_reason == 'length')
|
|
125
74
|
|
|
126
75
|
self.root_span.event(name=f"stream_processor_start[{self.task_no}]({auto_continue_count})", level="DEFAULT",
|
|
127
|
-
status_message=f"finish_reason={finish_reason}, tool_exec_strategy={self.
|
|
128
|
-
f"parsed_xml_data_len={len(parsed_xml_data)},
|
|
129
|
-
f"should_auto_continue={should_auto_continue}
|
|
76
|
+
status_message=f"finish_reason={finish_reason}, tool_exec_strategy={self.tool_exec_strategy}, "
|
|
77
|
+
f"parsed_xml_data_len={len(parsed_xml_data)}, accumulated_content_len={len(accumulated_content)}, "
|
|
78
|
+
f"should_auto_continue={should_auto_continue}")
|
|
130
79
|
|
|
131
80
|
assistant_msg = None
|
|
132
81
|
if accumulated_content and not should_auto_continue:
|
|
@@ -134,116 +83,55 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
134
83
|
assistant_msg = self.add_response_message(type="assistant", content=message_data, is_llm_message=True)
|
|
135
84
|
yield assistant_msg
|
|
136
85
|
|
|
137
|
-
# Process results from both streaming and non-streaming executions
|
|
138
|
-
tool_calls_to_execute = [item['tool_call'] for item in parsed_xml_data]
|
|
139
|
-
|
|
140
|
-
# Update assistant_message_id for streaming tool contexts
|
|
141
86
|
assistant_msg_id = assistant_msg['message_id'] if assistant_msg else None
|
|
142
|
-
for execution in pending_tool_executions:
|
|
143
|
-
if not execution["context"].assistant_message_id:
|
|
144
|
-
execution["context"].assistant_message_id = assistant_msg_id
|
|
145
|
-
|
|
146
|
-
if len(tool_calls_to_execute) > 0:
|
|
147
|
-
if self.tool_execute_on_stream:
|
|
148
|
-
# Handle results from streaming executions + any remaining tools
|
|
149
|
-
remaining_tools = []
|
|
150
|
-
streamed_tool_indices = set()
|
|
151
|
-
|
|
152
|
-
# Identify which tools were already executed during streaming by index
|
|
153
|
-
for execution in pending_tool_executions:
|
|
154
|
-
streamed_tool_indices.add(execution["tool_index"])
|
|
155
|
-
|
|
156
|
-
# Find remaining tools that weren't executed during streaming
|
|
157
|
-
for i, parsed_item in enumerate(parsed_xml_data):
|
|
158
|
-
tool_call = parsed_item['tool_call']
|
|
159
|
-
tool_identifier = (tool_call.get('function_name', ''), str(tool_call.get('arguments', {})))
|
|
160
|
-
|
|
161
|
-
# Check if this tool was already executed during streaming
|
|
162
|
-
already_executed = False
|
|
163
|
-
for execution in pending_tool_executions:
|
|
164
|
-
exec_tool_call = execution["tool_call"]
|
|
165
|
-
exec_identifier = (exec_tool_call.get('function_name', ''),str(exec_tool_call.get('arguments', {})))
|
|
166
|
-
if tool_identifier == exec_identifier:
|
|
167
|
-
already_executed = True
|
|
168
|
-
break
|
|
169
|
-
|
|
170
|
-
if not already_executed:
|
|
171
|
-
remaining_tools.append((parsed_item['tool_call'], parsed_item['parsing_details'], tool_index))
|
|
172
|
-
tool_index += 1
|
|
173
|
-
|
|
174
|
-
# Execute remaining tools if any
|
|
175
|
-
if remaining_tools:
|
|
176
|
-
for tool_call, parsing_details, t_idx in remaining_tools:
|
|
177
|
-
tool_context = self._create_tool_context(tool_call, t_idx, assistant_msg_id,parsing_details)
|
|
178
|
-
|
|
179
|
-
tool_start_msg = self._add_tool_start_message(tool_context)
|
|
180
|
-
yield format_for_yield(tool_start_msg)
|
|
181
|
-
|
|
182
|
-
result = await self._execute_tool(tool_call)
|
|
183
|
-
tool_context.result = result
|
|
184
|
-
tool_results_buffer.append((tool_call, result, t_idx, tool_context))
|
|
185
|
-
|
|
186
|
-
# Process all tool results
|
|
187
|
-
for tool_call, result, t_idx, context in tool_results_buffer:
|
|
188
|
-
tool_message = self._add_tool_messsage(tool_call, result, self.xml_adding_strategy,assistant_msg_id,
|
|
189
|
-
getattr(context, 'parsing_details', None))
|
|
190
|
-
|
|
191
|
-
tool_completed_msg = self._add_tool_completed_message(context,tool_message['message_id'] if tool_message else None)
|
|
192
|
-
yield format_for_yield(tool_completed_msg)
|
|
193
|
-
|
|
194
|
-
if tool_message:
|
|
195
|
-
yield format_for_yield(tool_message)
|
|
196
|
-
|
|
197
|
-
if tool_completed_msg["metadata"].get("agent_should_terminate") == "true":
|
|
198
|
-
finish_reason = "completed"
|
|
199
|
-
break
|
|
200
|
-
else: # non-streaming execution
|
|
201
|
-
tool_results = await self._execute_tools(tool_calls_to_execute, self.tool_execution_strategy)
|
|
202
|
-
tool_index = 0
|
|
203
|
-
for i, (returned_tool_call, tool_result) in enumerate(tool_results):
|
|
204
|
-
parsed_xml_item = parsed_xml_data[i]
|
|
205
|
-
tool_call = parsed_xml_item['tool_call']
|
|
206
|
-
parsing_details = parsed_xml_item['parsing_details']
|
|
207
87
|
|
|
208
|
-
|
|
88
|
+
tool_calls_to_execute = [item['tool_call'] for item in parsed_xml_data]
|
|
89
|
+
if len(tool_calls_to_execute) > 0 and not should_auto_continue:
|
|
90
|
+
tool_results = await self._execute_tools(tool_calls_to_execute, self.tool_exec_strategy)
|
|
91
|
+
tool_index = 0
|
|
92
|
+
for i, (returned_tool_call, tool_result) in enumerate(tool_results):
|
|
93
|
+
parsed_xml_item = parsed_xml_data[i]
|
|
94
|
+
tool_call = parsed_xml_item['tool_call']
|
|
95
|
+
parsing_details = parsed_xml_item['parsing_details']
|
|
96
|
+
|
|
97
|
+
tool_context = self._create_tool_context(tool_call, tool_index, assistant_msg_id,parsing_details, tool_result)
|
|
209
98
|
|
|
210
|
-
|
|
211
|
-
|
|
99
|
+
tool_start_msg = self._add_tool_start_message(tool_context)
|
|
100
|
+
yield tool_start_msg
|
|
212
101
|
|
|
213
|
-
|
|
102
|
+
tool_message = self._add_tool_messsage(tool_call, tool_result, self.xml_adding_strategy,assistant_msg_id, parsing_details)
|
|
214
103
|
|
|
215
|
-
|
|
216
|
-
|
|
104
|
+
tool_completed_msg = self._add_tool_completed_message(tool_context, tool_message['message_id'])
|
|
105
|
+
yield tool_completed_msg
|
|
217
106
|
|
|
218
|
-
|
|
107
|
+
yield tool_message
|
|
219
108
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
109
|
+
if tool_context.function_name in ['ask', 'complete']:
|
|
110
|
+
finish_reason = "completed"
|
|
111
|
+
break
|
|
223
112
|
|
|
224
|
-
|
|
113
|
+
tool_index += 1
|
|
225
114
|
else:
|
|
226
115
|
finish_reason = "non_tool_call"
|
|
227
|
-
logging.warning(f"StreamResp:
|
|
116
|
+
logging.warning(f"StreamResp: finish_reason='non_tool_call', No Tool need to call !")
|
|
228
117
|
|
|
229
118
|
if finish_reason:
|
|
230
|
-
finish_content = {
|
|
119
|
+
finish_content = {'status_type': "finish", 'finish_reason': finish_reason}
|
|
231
120
|
finish_msg = self.add_response_message(type="status", content=finish_content, is_llm_message=False)
|
|
232
|
-
yield
|
|
121
|
+
yield finish_msg
|
|
233
122
|
except Exception as e:
|
|
234
123
|
trace = log_trace(e, f"StreamResp: Process response accumulated_content:\n {accumulated_content}")
|
|
235
124
|
self.root_span.event(name="stream_response_process_error", level="ERROR",
|
|
236
125
|
status_message=f"Process streaming response error: {e}",
|
|
237
126
|
metadata={"content": accumulated_content, "trace": trace})
|
|
238
127
|
|
|
239
|
-
content = {
|
|
128
|
+
content = {'role': "system", 'status_type': "error", 'message': f"Process streaming response error: {e}"}
|
|
240
129
|
error_msg = self.add_response_message(type="status", content=content, is_llm_message=False)
|
|
241
|
-
yield
|
|
130
|
+
yield error_msg
|
|
242
131
|
|
|
243
132
|
raise # Use bare 'raise' to preserve the original exception with its traceback
|
|
244
133
|
finally:
|
|
245
134
|
if should_auto_continue:
|
|
246
135
|
continuous_state['accumulated_content'] = accumulated_content
|
|
247
|
-
continuous_state['assistant_msg_sequence'] =
|
|
248
|
-
logging.warning(
|
|
249
|
-
f"StreamResp: Updated continuous state for auto-continue with {len(accumulated_content)} chars")
|
|
136
|
+
continuous_state['assistant_msg_sequence'] = msg_sequence
|
|
137
|
+
logging.warning(f"StreamResp: Updated continuous state for auto-continue with {len(accumulated_content)} chars")
|