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.

@@ -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
- from xgae.utils.json_helpers import format_for_yield
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.get('accumulated_content', "")
21
- auto_continue_count = continuous_state.get('auto_continue_count', 0)
22
- can_auto_continue = continuous_state.get("auto_continue", False)
23
- use_assistant_chunk_msg = self.response_context.get("use_assistant_chunk_msg")
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:tool_execute_on_stream={self.tool_execute_on_stream}, auto_continue_count={auto_continue_count}, accumulated_content_len={len(accumulated_content)}")
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": sequence}
56
- assistant_chunk_msg = self.create_response_message(type="assistant_chunk",content=message_data,
57
- is_llm_message=True,metadata=metadata)
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.tool_execute_on_stream:
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
- if tool_start_msg:
75
- yield format_for_yield(tool_start_msg)
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({"task": execution_task,"tool_call": tool_call,"tool_index": tool_index,
81
- "context": tool_context,"parsing_details": parsing_details})
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["task"] for execution in pending_tool_executions]
105
+ pending_tasks = [execution['task'] for execution in pending_tool_executions]
97
106
  done, _ = await asyncio.wait(pending_tasks)
98
107
 
99
- for execution in pending_tool_executions:
100
- tool_idx = execution.get("tool_index", -1)
101
- context = execution["context"]
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 execution["task"].done():
105
- result = execution["task"].result()
106
- context.result = result
107
- tool_results_buffer.append((execution["tool_call"],result,tool_idx,context))
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 {tool_idx} not done after wait.")
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 {tool_idx}: {str(e)}")
112
- context.error = e
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.tool_execution_strategy}, "
128
- f"parsed_xml_data_len={len(parsed_xml_data)}, accumulated_content={len(accumulated_content)}, "
129
- f"should_auto_continue={should_auto_continue}, pending_executions={len(pending_tool_executions)}")
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 execution in pending_tool_executions:
143
- if not execution["context"].assistant_message_id:
144
- execution["context"].assistant_message_id = assistant_msg_id
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.tool_execute_on_stream:
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 execution in pending_tool_executions:
154
- streamed_tool_indices.add(execution["tool_index"])
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 execution in pending_tool_executions:
164
- exec_tool_call = execution["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 format_for_yield(tool_start_msg)
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, context in tool_results_buffer:
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(context, 'parsing_details', None))
198
+ getattr(pend_tool_context, 'parsing_details', None))
190
199
 
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)
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
- if tool_message:
195
- yield format_for_yield(tool_message)
203
+ yield tool_message
196
204
 
197
- if tool_completed_msg["metadata"].get("agent_should_terminate") == "true":
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.tool_execution_strategy)
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 format_for_yield(tool_start_msg)
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 format_for_yield(tool_completed_msg)
224
+ yield tool_completed_msg
217
225
 
218
- yield format_for_yield(tool_message)
226
+ yield tool_message
219
227
 
220
- if tool_completed_msg["metadata"].get("agent_should_terminate") == "true":
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 format_for_yield(finish_msg)
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 format_for_yield(error_msg)
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'] = 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")
@@ -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
- from xgae.utils.json_helpers import format_for_yield
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 = task_id if task_id else f"xga_task_{uuid4()}"
32
- self.agent_id = 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 = self.llm_client.is_stream
37
+ self.is_stream = self.llm_client.is_stream
38
38
 
39
- self.prompt_builder = prompt_builder or XGAPromptBuilder(system_prompt)
40
- self.tool_box: XGAToolBox = tool_box or XGAMcpToolBox()
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] = custom_tools
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("MAX_AUTO_RUN", 15))
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("USE_ASSISTANT_CHUNK_MSG", False))
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
- "accumulated_content": "",
130
- "auto_continue_count": 0,
131
- "auto_continue": False if self.max_auto_run <= 1 else True
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["auto_continue_count"] = _auto_continue_count
136
- continuous_state["auto_continue"] = _auto_continue
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.get("type") == "status":
148
- content = json.loads(chunk.get('content', '{}'))
149
- status_type = content.get('status_type', None)
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', 'Unknown error')}")
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 = content.get('finish_reason', None)
156
- if finish_reason == 'completed':
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 == 'xml_tool_limit_reached':
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 == 'non_tool_call':
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 == 'stop' or finish_reason == 'length': # 'length' never occur
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
- content = {"role": "system", "status_type": "error", "message": "Parse response chunk Error"}
180
- error_msg = self.add_response_message(type="status", content=content, is_llm_message=False)
181
- yield format_for_yield(error_msg)
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
- content = {"role": "system", "status_type": "error", "message": "Call run_task_once error"}
189
- error_msg = self.add_response_message(type="status", content=content, is_llm_message=False)
190
- yield format_for_yield(error_msg)
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.get("type")
224
+ chunk_type = chunk['type']
226
225
  if chunk_type == "status":
227
- status_content = json.loads(chunk.get('content', '{}'))
228
- status_type = status_content.get('status_type', None)
226
+ status_content = chunk['content']
227
+ status_type = status_content['status_type']
229
228
  if status_type == "error":
230
- error = status_content.get('message', 'Unknown error')
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.get('finish_reason', None)
234
- elif chunk_type == "tool" and finish_reason in ['completed', 'stop', 'xml_tool_limit_reached']:
235
- tool_content = json.loads(chunk.get('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["arguments"].get("text", "Task completed with no answer")
240
- attachments = tool_execution["arguments"].get("attachments", None)
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("success")
250
- output = tool_result.get("output")
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 == 'non_tool_call':
255
- assis_content = chunk.get('content', {})
256
- result_content = assis_content.get("content", "LLM output is empty")
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"] = self.task_id
284
+ metadata["task_id"] = self.task_id
285
285
  metadata["task_run_id"] = self.task_run_id
286
- metadata["trace_id"] = self.task_langfuse.trace_id
287
- metadata["session_id"] = self.session_id
288
- metadata["agent_id"] = self.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 = f"xga_msg_{uuid4()}",
292
- type = type,
293
- is_llm_message=is_llm_message,
294
- content = content,
295
- metadata = 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["is_llm_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["content"]
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.get("is_stream", False)
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
- "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_execution_strategy": "parallel" if self.tool_exec_parallel else "sequential",
348
- "tool_execute_on_stream": False,
349
- "xml_adding_strategy": "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,
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.get('type', 'unknown')
364
+ chunk_type = chunk['type']
365
365
  prefix = ""
366
- if chunk_type == 'status':
367
- content = json.loads(chunk.get('content', '{}'))
368
- status_type = content.get('status_type', "empty")
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 == 'tool':
373
- tool_content = json.loads(chunk.get('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
- content = chunk.get('content', '')
379
- pretty_content = content
380
- if isinstance(content, dict):
381
- pretty_content = json.dumps(content, ensure_ascii=False, indent=2)
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
- general_tools=[],
405
- custom_tools=["*"],
406
- system_prompt=system_prompt,
407
- session_id="session_1",
408
- agent_id="agent_1",)
409
-
410
- final_result = await engine.run_task_with_final_answer(task_message={"role": "user",
411
- "content": "locate 10.0.0.1 fault and solution"})
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())