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.
- xgae/engine/mcp_tool_box.py +7 -6
- xgae/engine/prompt_builder.py +9 -8
- xgae/engine/responser/non_stream_responser.py +12 -11
- xgae/engine/responser/responser_base.py +76 -92
- xgae/engine/responser/stream_responser.py +67 -60
- xgae/engine/task_engine.py +92 -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.14.dist-info → xgae-0.1.15.dist-info}/METADATA +1 -1
- xgae-0.1.15.dist-info/RECORD +21 -0
- xgae-0.1.14.dist-info/RECORD +0 -21
- {xgae-0.1.14.dist-info → xgae-0.1.15.dist-info}/WHEEL +0 -0
- {xgae-0.1.14.dist-info → xgae-0.1.15.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,30 @@ 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
|
pending_tool_executions = []
|
|
30
31
|
yielded_tool_indices = set() # Track which tool statuses have been yielded
|
|
31
32
|
tool_results_buffer = [] # Store (tool_call, result, tool_index, context)
|
|
32
33
|
tool_index = 0
|
|
34
|
+
|
|
33
35
|
current_xml_content = accumulated_content # Track XML content for streaming detection
|
|
34
36
|
|
|
35
|
-
logging.info(f"=== StreamResp:
|
|
37
|
+
logging.info(f"=== StreamResp:tool_exec_on_stream={self.tool_exec_on_stream}, auto_continue_count={auto_continue_count}, "
|
|
38
|
+
f"accumulated_content_len={len(accumulated_content)}")
|
|
36
39
|
try:
|
|
37
40
|
async for llm_chunk in llm_response:
|
|
38
41
|
if hasattr(llm_chunk, 'choices') and llm_chunk.choices and hasattr(llm_chunk.choices[0],'finish_reason'):
|
|
39
42
|
if llm_chunk.choices[0].finish_reason:
|
|
40
|
-
finish_reason = llm_chunk.choices[0].finish_reason
|
|
43
|
+
finish_reason = llm_chunk.choices[0].finish_reason # LLM finish reason: ‘stop' , 'length'
|
|
41
44
|
logging.info(f"StreamResp:LLM chunk response finish_reason={finish_reason}")
|
|
42
45
|
|
|
43
46
|
if hasattr(llm_chunk, 'choices') and llm_chunk.choices:
|
|
@@ -52,13 +55,16 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
52
55
|
if self.max_xml_tool_calls <= 0 or xml_tool_call_count < self.max_xml_tool_calls:
|
|
53
56
|
if use_assistant_chunk_msg:
|
|
54
57
|
message_data = {"role": "assistant", "content": chunk_content}
|
|
55
|
-
metadata = {"sequence":
|
|
56
|
-
assistant_chunk_msg = self.create_response_message(type="assistant_chunk",
|
|
57
|
-
|
|
58
|
+
metadata = {"sequence": msg_sequence}
|
|
59
|
+
assistant_chunk_msg = self.create_response_message(type="assistant_chunk",
|
|
60
|
+
content=message_data,
|
|
61
|
+
is_llm_message=True,
|
|
62
|
+
metadata=metadata)
|
|
58
63
|
yield assistant_chunk_msg
|
|
64
|
+
msg_sequence += 1
|
|
59
65
|
|
|
60
66
|
#Process XML tool calls during streaming
|
|
61
|
-
if self.
|
|
67
|
+
if self.tool_exec_on_stream:
|
|
62
68
|
xml_chunks = self._extract_xml_chunks(current_xml_content)
|
|
63
69
|
for xml_chunk in xml_chunks:
|
|
64
70
|
current_xml_content = current_xml_content.replace(xml_chunk, "", 1)
|
|
@@ -71,45 +77,48 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
71
77
|
|
|
72
78
|
# Yield tool start status immediately
|
|
73
79
|
tool_start_msg = self._add_tool_start_message(tool_context)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
yielded_tool_indices.add(tool_index)
|
|
80
|
+
yield tool_start_msg
|
|
81
|
+
yielded_tool_indices.add(tool_index)
|
|
77
82
|
|
|
78
83
|
# Create async execution task
|
|
79
84
|
execution_task = asyncio.create_task(self._execute_tool(tool_call))
|
|
80
|
-
pending_tool_executions.append({
|
|
81
|
-
|
|
85
|
+
pending_tool_executions.append({'task': execution_task,
|
|
86
|
+
'tool_call': tool_call,
|
|
87
|
+
'tool_index': tool_index,
|
|
88
|
+
'tool_context': tool_context,
|
|
89
|
+
'parsing_details': parsing_details})
|
|
82
90
|
tool_index += 1
|
|
83
|
-
|
|
84
|
-
sequence += 1
|
|
85
91
|
else:
|
|
86
92
|
finish_reason = "xml_tool_limit_reached"
|
|
93
|
+
logging.warning(f"StreamResp: Over XML Tool Limit, finish_reason='xml_tool_limit_reached', "
|
|
94
|
+
f"xml_tool_call_count={xml_tool_call_count}")
|
|
87
95
|
break
|
|
96
|
+
# for chunk is end
|
|
88
97
|
|
|
89
98
|
if len(accumulated_content) == 0:
|
|
90
99
|
logging.warning(f"StreamResp: LLM response_message content is empty")
|
|
91
100
|
|
|
92
101
|
# Wait for pending tool executions from streaming phase
|
|
93
102
|
if pending_tool_executions:
|
|
94
|
-
logging.info(f"Waiting for {len(pending_tool_executions)} pending streamed tool executions")
|
|
103
|
+
logging.info(f"StreamResp: Waiting for {len(pending_tool_executions)} pending streamed tool executions")
|
|
95
104
|
|
|
96
|
-
pending_tasks = [execution[
|
|
105
|
+
pending_tasks = [execution['task'] for execution in pending_tool_executions]
|
|
97
106
|
done, _ = await asyncio.wait(pending_tasks)
|
|
98
107
|
|
|
99
|
-
for
|
|
100
|
-
|
|
101
|
-
|
|
108
|
+
for pend_tool_exec in pending_tool_executions:
|
|
109
|
+
pend_tool_index = pend_tool_exec['tool_index']
|
|
110
|
+
pend_tool_context = pend_tool_exec['tool_context']
|
|
102
111
|
|
|
103
112
|
try:
|
|
104
|
-
if
|
|
105
|
-
result =
|
|
106
|
-
|
|
107
|
-
tool_results_buffer.append((
|
|
113
|
+
if pend_tool_exec["task"].done():
|
|
114
|
+
result = pend_tool_exec['task'].result()
|
|
115
|
+
pend_tool_context.result = result
|
|
116
|
+
tool_results_buffer.append((pend_tool_exec["tool_call"], result, pend_tool_index, pend_tool_context))
|
|
108
117
|
else:
|
|
109
|
-
logging.warning(f"Task for tool index {
|
|
118
|
+
logging.warning(f"StreamResp: Task for tool index {pend_tool_index} is not done after wait.")
|
|
110
119
|
except Exception as e:
|
|
111
|
-
logging.error(f"Error getting result for pending tool execution {
|
|
112
|
-
|
|
120
|
+
logging.error(f"StreamResp: Error getting result for pending tool execution {pend_tool_index}: {str(e)}")
|
|
121
|
+
pend_tool_context.error = e
|
|
113
122
|
|
|
114
123
|
if finish_reason == "xml_tool_limit_reached":
|
|
115
124
|
xml_chunks = self._extract_xml_chunks(accumulated_content)
|
|
@@ -124,9 +133,9 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
124
133
|
should_auto_continue = (can_auto_continue and finish_reason == 'length')
|
|
125
134
|
|
|
126
135
|
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},
|
|
136
|
+
status_message=f"finish_reason={finish_reason}, tool_exec_strategy={self.tool_exec_strategy}, "
|
|
137
|
+
f"parsed_xml_data_len={len(parsed_xml_data)}, accumulated_content_len={len(accumulated_content)}, "
|
|
138
|
+
f"should_auto_continue={should_auto_continue}, pending_executions_len={len(pending_tool_executions)}")
|
|
130
139
|
|
|
131
140
|
assistant_msg = None
|
|
132
141
|
if accumulated_content and not should_auto_continue:
|
|
@@ -139,19 +148,19 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
139
148
|
|
|
140
149
|
# Update assistant_message_id for streaming tool contexts
|
|
141
150
|
assistant_msg_id = assistant_msg['message_id'] if assistant_msg else None
|
|
142
|
-
for
|
|
143
|
-
if not
|
|
144
|
-
|
|
151
|
+
for pend_tool_exec in pending_tool_executions:
|
|
152
|
+
if not pend_tool_exec["tool_context"].assistant_message_id:
|
|
153
|
+
pend_tool_exec["tool_context"].assistant_message_id = assistant_msg_id
|
|
145
154
|
|
|
146
155
|
if len(tool_calls_to_execute) > 0:
|
|
147
|
-
if self.
|
|
156
|
+
if self.tool_exec_on_stream:
|
|
148
157
|
# Handle results from streaming executions + any remaining tools
|
|
149
158
|
remaining_tools = []
|
|
150
159
|
streamed_tool_indices = set()
|
|
151
160
|
|
|
152
161
|
# Identify which tools were already executed during streaming by index
|
|
153
|
-
for
|
|
154
|
-
streamed_tool_indices.add(
|
|
162
|
+
for pend_tool_exec in pending_tool_executions:
|
|
163
|
+
streamed_tool_indices.add(pend_tool_exec["tool_index"])
|
|
155
164
|
|
|
156
165
|
# Find remaining tools that weren't executed during streaming
|
|
157
166
|
for i, parsed_item in enumerate(parsed_xml_data):
|
|
@@ -160,8 +169,8 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
160
169
|
|
|
161
170
|
# Check if this tool was already executed during streaming
|
|
162
171
|
already_executed = False
|
|
163
|
-
for
|
|
164
|
-
exec_tool_call =
|
|
172
|
+
for pend_tool_exec in pending_tool_executions:
|
|
173
|
+
exec_tool_call = pend_tool_exec["tool_call"]
|
|
165
174
|
exec_identifier = (exec_tool_call.get('function_name', ''),str(exec_tool_call.get('arguments', {})))
|
|
166
175
|
if tool_identifier == exec_identifier:
|
|
167
176
|
already_executed = True
|
|
@@ -177,28 +186,27 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
177
186
|
tool_context = self._create_tool_context(tool_call, t_idx, assistant_msg_id,parsing_details)
|
|
178
187
|
|
|
179
188
|
tool_start_msg = self._add_tool_start_message(tool_context)
|
|
180
|
-
yield
|
|
189
|
+
yield tool_start_msg
|
|
181
190
|
|
|
182
191
|
result = await self._execute_tool(tool_call)
|
|
183
192
|
tool_context.result = result
|
|
184
193
|
tool_results_buffer.append((tool_call, result, t_idx, tool_context))
|
|
185
194
|
|
|
186
195
|
# Process all tool results
|
|
187
|
-
for tool_call, result, t_idx,
|
|
196
|
+
for tool_call, result, t_idx, pend_tool_context in tool_results_buffer:
|
|
188
197
|
tool_message = self._add_tool_messsage(tool_call, result, self.xml_adding_strategy,assistant_msg_id,
|
|
189
|
-
getattr(
|
|
198
|
+
getattr(pend_tool_context, 'parsing_details', None))
|
|
190
199
|
|
|
191
|
-
tool_completed_msg = self._add_tool_completed_message(
|
|
192
|
-
yield
|
|
200
|
+
tool_completed_msg = self._add_tool_completed_message(pend_tool_context,tool_message['message_id'] if tool_message else None)
|
|
201
|
+
yield tool_completed_msg
|
|
193
202
|
|
|
194
|
-
|
|
195
|
-
yield format_for_yield(tool_message)
|
|
203
|
+
yield tool_message
|
|
196
204
|
|
|
197
|
-
if
|
|
205
|
+
if pend_tool_context.function_name in ['ask', 'complete']:
|
|
198
206
|
finish_reason = "completed"
|
|
199
207
|
break
|
|
200
208
|
else: # non-streaming execution
|
|
201
|
-
tool_results = await self._execute_tools(tool_calls_to_execute, self.
|
|
209
|
+
tool_results = await self._execute_tools(tool_calls_to_execute, self.tool_exec_strategy)
|
|
202
210
|
tool_index = 0
|
|
203
211
|
for i, (returned_tool_call, tool_result) in enumerate(tool_results):
|
|
204
212
|
parsed_xml_item = parsed_xml_data[i]
|
|
@@ -208,16 +216,16 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
208
216
|
tool_context = self._create_tool_context(tool_call, tool_index, assistant_msg_id,parsing_details, tool_result)
|
|
209
217
|
|
|
210
218
|
tool_start_msg = self._add_tool_start_message(tool_context)
|
|
211
|
-
yield
|
|
219
|
+
yield tool_start_msg
|
|
212
220
|
|
|
213
221
|
tool_message = self._add_tool_messsage(tool_call, tool_result, self.xml_adding_strategy,assistant_msg_id, parsing_details)
|
|
214
222
|
|
|
215
223
|
tool_completed_msg = self._add_tool_completed_message(tool_context, tool_message['message_id'])
|
|
216
|
-
yield
|
|
224
|
+
yield tool_completed_msg
|
|
217
225
|
|
|
218
|
-
yield
|
|
226
|
+
yield tool_message
|
|
219
227
|
|
|
220
|
-
if
|
|
228
|
+
if tool_context.function_name in ['ask', 'complete']:
|
|
221
229
|
finish_reason = "completed"
|
|
222
230
|
break
|
|
223
231
|
|
|
@@ -229,7 +237,7 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
229
237
|
if finish_reason:
|
|
230
238
|
finish_content = {"status_type": "finish", "finish_reason": finish_reason}
|
|
231
239
|
finish_msg = self.add_response_message(type="status", content=finish_content, is_llm_message=False)
|
|
232
|
-
yield
|
|
240
|
+
yield finish_msg
|
|
233
241
|
except Exception as e:
|
|
234
242
|
trace = log_trace(e, f"StreamResp: Process response accumulated_content:\n {accumulated_content}")
|
|
235
243
|
self.root_span.event(name="stream_response_process_error", level="ERROR",
|
|
@@ -238,12 +246,11 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
238
246
|
|
|
239
247
|
content = {"role": "system", "status_type": "error", "message": f"Process streaming response error: {e}"}
|
|
240
248
|
error_msg = self.add_response_message(type="status", content=content, is_llm_message=False)
|
|
241
|
-
yield
|
|
249
|
+
yield error_msg
|
|
242
250
|
|
|
243
251
|
raise # Use bare 'raise' to preserve the original exception with its traceback
|
|
244
252
|
finally:
|
|
245
253
|
if should_auto_continue:
|
|
246
254
|
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")
|
|
255
|
+
continuous_state['assistant_msg_sequence'] = msg_sequence
|
|
256
|
+
logging.warning(f"StreamResp: Updated continuous state for auto-continue with {len(accumulated_content)} chars")
|
xgae/engine/task_engine.py
CHANGED
|
@@ -7,7 +7,7 @@ from uuid import uuid4
|
|
|
7
7
|
|
|
8
8
|
from xgae.utils import log_trace, to_bool
|
|
9
9
|
from xgae.utils.llm_client import LLMClient, LLMConfig
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
|
|
12
12
|
from xgae.engine.engine_base import XGAResponseMsgType, XGAResponseMessage, XGAToolBox, XGATaskResult
|
|
13
13
|
from xgae.engine.task_langfuse import XGATaskLangFuse
|
|
@@ -28,32 +28,32 @@ class XGATaskEngine:
|
|
|
28
28
|
llm_config: Optional[LLMConfig] = None,
|
|
29
29
|
prompt_builder: Optional[XGAPromptBuilder] = None,
|
|
30
30
|
tool_box: Optional[XGAToolBox] = None):
|
|
31
|
-
self.task_id
|
|
32
|
-
self.agent_id
|
|
31
|
+
self.task_id = task_id if task_id else f"xga_task_{uuid4()}"
|
|
32
|
+
self.agent_id = agent_id
|
|
33
33
|
self.session_id = session_id
|
|
34
34
|
|
|
35
35
|
self.llm_client = LLMClient(llm_config)
|
|
36
36
|
self.model_name = self.llm_client.model_name
|
|
37
|
-
self.is_stream
|
|
37
|
+
self.is_stream = self.llm_client.is_stream
|
|
38
38
|
|
|
39
|
-
self.prompt_builder
|
|
40
|
-
self.tool_box: XGAToolBox
|
|
41
|
-
self.task_langfuse: XGATaskLangFuse = None
|
|
39
|
+
self.prompt_builder = prompt_builder or XGAPromptBuilder(system_prompt)
|
|
40
|
+
self.tool_box: XGAToolBox = tool_box or XGAMcpToolBox()
|
|
42
41
|
|
|
43
42
|
self.general_tools:List[str] = general_tools
|
|
44
|
-
self.custom_tools:List[str]
|
|
45
|
-
self.task_response_msgs: List[XGAResponseMessage] = []
|
|
43
|
+
self.custom_tools:List[str] = custom_tools
|
|
46
44
|
|
|
47
|
-
max_auto_run = max_auto_run if max_auto_run else int(os.getenv(
|
|
45
|
+
max_auto_run = max_auto_run if max_auto_run else int(os.getenv('MAX_AUTO_RUN', 15))
|
|
48
46
|
self.max_auto_run: int = 1 if max_auto_run <= 1 else max_auto_run
|
|
49
47
|
|
|
50
|
-
self.use_assistant_chunk_msg = to_bool(os.getenv(
|
|
48
|
+
self.use_assistant_chunk_msg = to_bool(os.getenv('USE_ASSISTANT_CHUNK_MSG', False))
|
|
51
49
|
self.tool_exec_parallel = True if tool_exec_parallel is None else tool_exec_parallel
|
|
52
50
|
|
|
53
51
|
self.task_no = -1
|
|
54
52
|
self.task_run_id :str = None
|
|
55
53
|
self.task_prompt :str = None
|
|
54
|
+
self.task_langfuse: XGATaskLangFuse = None
|
|
56
55
|
|
|
56
|
+
self.task_response_msgs: List[XGAResponseMessage] = []
|
|
57
57
|
|
|
58
58
|
async def run_task_with_final_answer(self,
|
|
59
59
|
task_message: Dict[str, Any],
|
|
@@ -126,14 +126,15 @@ class XGATaskEngine:
|
|
|
126
126
|
|
|
127
127
|
async def _run_task_auto(self) -> AsyncGenerator[Dict[str, Any], None]:
|
|
128
128
|
continuous_state: TaskRunContinuousState = {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
'accumulated_content' : "",
|
|
130
|
+
'auto_continue_count' : 0,
|
|
131
|
+
'auto_continue' : False if self.max_auto_run <= 1 else True,
|
|
132
|
+
'assistant_msg_sequence': 0
|
|
132
133
|
}
|
|
133
134
|
|
|
134
135
|
def update_continuous_state(_auto_continue_count, _auto_continue):
|
|
135
|
-
continuous_state[
|
|
136
|
-
continuous_state[
|
|
136
|
+
continuous_state['auto_continue_count'] = _auto_continue_count
|
|
137
|
+
continuous_state['auto_continue'] = _auto_continue
|
|
137
138
|
|
|
138
139
|
auto_continue_count = 0
|
|
139
140
|
auto_continue = True
|
|
@@ -144,28 +145,28 @@ class XGATaskEngine:
|
|
|
144
145
|
async for chunk in self._run_task_once(continuous_state):
|
|
145
146
|
yield chunk
|
|
146
147
|
try:
|
|
147
|
-
if chunk
|
|
148
|
-
|
|
149
|
-
status_type =
|
|
148
|
+
if chunk['type'] == "status":
|
|
149
|
+
status_content = chunk['content']
|
|
150
|
+
status_type = status_content['status_type']
|
|
150
151
|
if status_type == "error":
|
|
151
|
-
logging.error(f"TaskEngine run_task_auto: task_response error: {chunk.get('message'
|
|
152
|
+
logging.error(f"TaskEngine run_task_auto: task_response error: {chunk.get('message')}")
|
|
152
153
|
auto_continue = False
|
|
153
154
|
break
|
|
154
155
|
elif status_type == 'finish':
|
|
155
|
-
finish_reason =
|
|
156
|
-
if finish_reason ==
|
|
156
|
+
finish_reason = status_content['finish_reason']
|
|
157
|
+
if finish_reason == "completed":
|
|
157
158
|
logging.info(f"TaskEngine run_task_auto: Detected finish_reason='completed', TASK_COMPLETE Success !")
|
|
158
159
|
auto_continue = False
|
|
159
160
|
break
|
|
160
|
-
elif finish_reason ==
|
|
161
|
+
elif finish_reason == "xml_tool_limit_reached":
|
|
161
162
|
logging.warning(f"TaskEngine run_task_auto: Detected finish_reason='xml_tool_limit_reached', stop auto-continue")
|
|
162
163
|
auto_continue = False
|
|
163
164
|
break
|
|
164
|
-
elif finish_reason ==
|
|
165
|
+
elif finish_reason == "non_tool_call":
|
|
165
166
|
logging.warning(f"TaskEngine run_task_auto: Detected finish_reason='non_tool_call', stop auto-continue")
|
|
166
167
|
auto_continue = False
|
|
167
168
|
break
|
|
168
|
-
elif finish_reason
|
|
169
|
+
elif finish_reason in ["stop", "length"]: # 'length' occur on some LLM
|
|
169
170
|
auto_continue = True
|
|
170
171
|
auto_continue_count += 1
|
|
171
172
|
update_continuous_state(auto_continue_count, auto_continue)
|
|
@@ -176,18 +177,18 @@ class XGATaskEngine:
|
|
|
176
177
|
status_message=f"Task Engine parse chunk error: {parse_error}",
|
|
177
178
|
metadata={"content": chunk, "trace": trace})
|
|
178
179
|
|
|
179
|
-
|
|
180
|
-
error_msg = self.add_response_message(type="status", content=
|
|
181
|
-
yield
|
|
180
|
+
status_content = {"status_type": "error", "role": "system", "message": "Parse response chunk Error"}
|
|
181
|
+
error_msg = self.add_response_message(type="status", content=status_content, is_llm_message=False)
|
|
182
|
+
yield error_msg
|
|
182
183
|
except Exception as run_error:
|
|
183
184
|
trace = log_trace(run_error, "TaskEngine run_task_auto: Call task_run_once")
|
|
184
185
|
self.task_langfuse.root_span.event(name="engine_task_run_once_error", level="ERROR",
|
|
185
186
|
status_message=f"Call task_run_once error: {run_error}",
|
|
186
187
|
metadata={"trace": trace})
|
|
187
188
|
|
|
188
|
-
|
|
189
|
-
error_msg = self.add_response_message(type="status", content=
|
|
190
|
-
yield
|
|
189
|
+
status_content = {"status_type": "error", "role": "system", "message": "Call run_task_once error"}
|
|
190
|
+
error_msg = self.add_response_message(type="status", content=status_content, is_llm_message=False)
|
|
191
|
+
yield error_msg
|
|
191
192
|
|
|
192
193
|
|
|
193
194
|
async def _run_task_once(self, continuous_state: TaskRunContinuousState) -> AsyncGenerator[Dict[str, Any], None]:
|
|
@@ -206,8 +207,6 @@ class XGATaskEngine:
|
|
|
206
207
|
auto_count = continuous_state.get("auto_continue_count")
|
|
207
208
|
langfuse_metadata = self.task_langfuse.create_llm_langfuse_meta(auto_count)
|
|
208
209
|
|
|
209
|
-
self.task_langfuse.root_span.event(name="engine_start_create_completion", level="DEFAULT",
|
|
210
|
-
status_message=(f"Task Engine start create_completion llm_messages len={len(llm_messages)}"))
|
|
211
210
|
llm_response = await self.llm_client.create_completion(llm_messages, langfuse_metadata)
|
|
212
211
|
response_processor = self._create_response_processer()
|
|
213
212
|
|
|
@@ -222,44 +221,45 @@ class XGATaskEngine:
|
|
|
222
221
|
try:
|
|
223
222
|
finish_reason = ''
|
|
224
223
|
for chunk in reverse_chunks:
|
|
225
|
-
chunk_type = chunk
|
|
224
|
+
chunk_type = chunk['type']
|
|
226
225
|
if chunk_type == "status":
|
|
227
|
-
status_content =
|
|
228
|
-
status_type = status_content
|
|
226
|
+
status_content = chunk['content']
|
|
227
|
+
status_type = status_content['status_type']
|
|
229
228
|
if status_type == "error":
|
|
230
|
-
error = status_content
|
|
229
|
+
error = status_content['message']
|
|
231
230
|
final_result = XGATaskResult(type="error", content=error)
|
|
232
231
|
elif status_type == "finish":
|
|
233
|
-
finish_reason = status_content
|
|
234
|
-
elif chunk_type == "tool" and finish_reason in ['completed', 'stop', 'xml_tool_limit_reached']:
|
|
235
|
-
tool_content
|
|
232
|
+
finish_reason = status_content['finish_reason']
|
|
233
|
+
elif chunk_type == "tool" and finish_reason in ['completed', 'stop', 'xml_tool_limit_reached', 'length']:
|
|
234
|
+
tool_content= chunk['content']
|
|
236
235
|
tool_execution = tool_content.get('tool_execution')
|
|
237
236
|
tool_name = tool_execution.get('function_name')
|
|
238
237
|
if tool_name == "complete":
|
|
239
|
-
result_content = tool_execution[
|
|
240
|
-
attachments = tool_execution[
|
|
238
|
+
result_content = tool_execution['arguments'].get('text', "Task completed with no answer")
|
|
239
|
+
attachments = tool_execution['arguments'].get('attachments', None)
|
|
241
240
|
final_result = XGATaskResult(type="answer", content=result_content, attachments=attachments)
|
|
242
241
|
elif tool_name == "ask":
|
|
243
242
|
result_content = tool_execution["arguments"].get("text", "Task ask for more info")
|
|
244
243
|
attachments = tool_execution["arguments"].get("attachments", None)
|
|
245
244
|
final_result = XGATaskResult(type="ask", content=result_content, attachments=attachments)
|
|
246
245
|
else:
|
|
246
|
+
# finish reason 1) 'stop': auto run reach max_auto_run limit 2) 'xml_tool_limit_reached' 3) 'length': occur on some LLM
|
|
247
247
|
tool_result = tool_execution.get("result", None)
|
|
248
248
|
if tool_result is not None:
|
|
249
|
-
success = tool_result.get(
|
|
250
|
-
output = tool_result.get(
|
|
249
|
+
success = tool_result.get('success')
|
|
250
|
+
output = tool_result.get('output', '')
|
|
251
251
|
result_type = "answer" if success else "error"
|
|
252
252
|
result_content = f"Task execute '{tool_name}' {result_type}: {output}"
|
|
253
253
|
final_result = XGATaskResult(type=result_type, content=result_content)
|
|
254
|
-
elif chunk_type == "assistant" and finish_reason ==
|
|
255
|
-
assis_content = chunk
|
|
256
|
-
result_content = assis_content.get(
|
|
254
|
+
elif chunk_type == "assistant" and finish_reason == "non_tool_call":
|
|
255
|
+
assis_content = chunk['content']
|
|
256
|
+
result_content = assis_content.get('content', "LLM output is empty")
|
|
257
257
|
final_result = XGATaskResult(type="answer", content=result_content)
|
|
258
258
|
|
|
259
259
|
if final_result:
|
|
260
260
|
break
|
|
261
261
|
|
|
262
|
-
if final_result and finish_reason == "completed":
|
|
262
|
+
if final_result and (finish_reason == "completed" or finish_reason == "non_tool_call"):
|
|
263
263
|
logging.info(f"✅ FINAL_RESULT: finish_reason={finish_reason}, final_result={final_result}")
|
|
264
264
|
elif final_result is not None:
|
|
265
265
|
logging.warning(f"⚠️ FINAL_RESULT: finish_reason={finish_reason}, final_result={final_result}")
|
|
@@ -281,18 +281,18 @@ class XGATaskEngine:
|
|
|
281
281
|
is_llm_message: bool,
|
|
282
282
|
metadata: Optional[Dict[str, Any]]=None)-> XGAResponseMessage:
|
|
283
283
|
metadata = metadata or {}
|
|
284
|
-
metadata["task_id"]
|
|
284
|
+
metadata["task_id"] = self.task_id
|
|
285
285
|
metadata["task_run_id"] = self.task_run_id
|
|
286
|
-
metadata["trace_id"]
|
|
287
|
-
metadata["session_id"]
|
|
288
|
-
metadata["agent_id"]
|
|
286
|
+
metadata["trace_id"] = self.task_langfuse.trace_id
|
|
287
|
+
metadata["session_id"] = self.session_id
|
|
288
|
+
metadata["agent_id"] = self.agent_id
|
|
289
289
|
|
|
290
290
|
message = XGAResponseMessage(
|
|
291
|
-
message_id
|
|
292
|
-
type
|
|
293
|
-
is_llm_message=is_llm_message,
|
|
294
|
-
content
|
|
295
|
-
metadata
|
|
291
|
+
message_id = f"xga_msg_{uuid4()}",
|
|
292
|
+
type = type,
|
|
293
|
+
is_llm_message = is_llm_message,
|
|
294
|
+
content = content,
|
|
295
|
+
metadata = metadata
|
|
296
296
|
)
|
|
297
297
|
|
|
298
298
|
return message
|
|
@@ -308,12 +308,12 @@ class XGATaskEngine:
|
|
|
308
308
|
def get_history_llm_messages (self) -> List[Dict[str, Any]]:
|
|
309
309
|
llm_messages = []
|
|
310
310
|
for message in self.task_response_msgs:
|
|
311
|
-
if message[
|
|
311
|
+
if message['is_llm_message']:
|
|
312
312
|
llm_messages.append(message)
|
|
313
313
|
|
|
314
314
|
response_llm_contents = []
|
|
315
315
|
for llm_message in llm_messages:
|
|
316
|
-
content = llm_message[
|
|
316
|
+
content = llm_message['content']
|
|
317
317
|
if isinstance(content, str):
|
|
318
318
|
try:
|
|
319
319
|
_content = json.loads(content)
|
|
@@ -327,7 +327,7 @@ class XGATaskEngine:
|
|
|
327
327
|
|
|
328
328
|
def _create_response_processer(self) -> TaskResponseProcessor:
|
|
329
329
|
response_context = self._create_response_context()
|
|
330
|
-
is_stream = response_context
|
|
330
|
+
is_stream = response_context['is_stream']
|
|
331
331
|
if is_stream:
|
|
332
332
|
from xgae.engine.responser.stream_responser import StreamTaskResponser
|
|
333
333
|
return StreamTaskResponser(response_context)
|
|
@@ -337,20 +337,20 @@ class XGATaskEngine:
|
|
|
337
337
|
|
|
338
338
|
def _create_response_context(self) -> TaskResponserContext:
|
|
339
339
|
response_context: TaskResponserContext = {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
340
|
+
'is_stream' : self.is_stream,
|
|
341
|
+
'task_id' : self.task_id,
|
|
342
|
+
'task_run_id' : self.task_run_id,
|
|
343
|
+
'task_no' : self.task_no,
|
|
344
|
+
'model_name' : self.model_name,
|
|
345
|
+
'max_xml_tool_calls' : 0,
|
|
346
|
+
'use_assistant_chunk_msg' : self.use_assistant_chunk_msg,
|
|
347
|
+
'tool_exec_strategy' : "parallel" if self.tool_exec_parallel else "sequential",
|
|
348
|
+
'tool_exec_on_stream' : True,
|
|
349
|
+
'xml_adding_strategy' : "assistant_message", # user_message
|
|
350
|
+
'add_response_msg_func' : self.add_response_message,
|
|
351
|
+
'create_response_msg_func' : self.create_response_message,
|
|
352
|
+
'tool_box' : self.tool_box,
|
|
353
|
+
'task_langfuse' : self.task_langfuse,
|
|
354
354
|
}
|
|
355
355
|
return response_context
|
|
356
356
|
|
|
@@ -361,24 +361,24 @@ class XGATaskEngine:
|
|
|
361
361
|
|
|
362
362
|
def _logging_reponse_chunk(self, chunk, auto_count: int)-> None:
|
|
363
363
|
try:
|
|
364
|
-
chunk_type = chunk
|
|
364
|
+
chunk_type = chunk['type']
|
|
365
365
|
prefix = ""
|
|
366
|
-
if chunk_type ==
|
|
367
|
-
|
|
368
|
-
status_type =
|
|
366
|
+
if chunk_type == "status":
|
|
367
|
+
status_content = chunk['content']
|
|
368
|
+
status_type = status_content.get('status_type', "empty")
|
|
369
369
|
if status_type in ["tool_started", "tool_completed"]:
|
|
370
370
|
return
|
|
371
371
|
prefix = "-" + status_type
|
|
372
|
-
elif chunk_type ==
|
|
373
|
-
tool_content =
|
|
372
|
+
elif chunk_type == "tool":
|
|
373
|
+
tool_content = chunk['content']
|
|
374
374
|
tool_execution = tool_content.get('tool_execution')
|
|
375
375
|
tool_name = tool_execution.get('function_name')
|
|
376
376
|
prefix = "-" + tool_name
|
|
377
377
|
|
|
378
|
-
|
|
379
|
-
pretty_content =
|
|
380
|
-
if isinstance(
|
|
381
|
-
pretty_content = json.dumps(
|
|
378
|
+
status_content = chunk['content']
|
|
379
|
+
pretty_content = status_content
|
|
380
|
+
if isinstance(status_content, dict):
|
|
381
|
+
pretty_content = json.dumps(status_content, ensure_ascii=False, indent=2)
|
|
382
382
|
|
|
383
383
|
if chunk_type == "assistant_chunk":
|
|
384
384
|
logging.debug(f"TASK_RESP_CHUNK[{auto_count}]<{chunk_type}{prefix}> content: {pretty_content}")
|
|
@@ -401,15 +401,14 @@ if __name__ == "__main__":
|
|
|
401
401
|
tool_box = XGAMcpToolBox(custom_mcp_server_file="mcpservers/custom_servers.json")
|
|
402
402
|
system_prompt = read_file("templates/example/fault_user_prompt.txt")
|
|
403
403
|
engine = XGATaskEngine(tool_box=tool_box,
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
print("FINAL RESULT:", final_result)
|
|
404
|
+
custom_tools=["*"],
|
|
405
|
+
system_prompt=system_prompt,
|
|
406
|
+
session_id="session_1",
|
|
407
|
+
agent_id="agent_1",)
|
|
408
|
+
|
|
409
|
+
final_result = await engine.run_task_with_final_answer(task_message={'role': "user",
|
|
410
|
+
'content': "locate 10.0.0.1 fault and solution"})
|
|
411
|
+
print(f"FINAL RESULT:{final_result}")
|
|
413
412
|
|
|
414
413
|
|
|
415
414
|
asyncio.run(main())
|