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.

@@ -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,30 +145,30 @@ 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
- elif status_type == 'finish':
155
- finish_reason = content.get('finish_reason', None)
156
- if finish_reason == 'completed':
155
+ elif status_type == "finish":
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
- auto_continue = True
169
+ elif finish_reason in ["stop", "length"]: # 'length' occur on some LLM
170
170
  auto_continue_count += 1
171
+ auto_continue = True if auto_continue_count < self.max_auto_run else False
171
172
  update_continuous_state(auto_continue_count, auto_continue)
172
173
  logging.info(f"TaskEngine run_task_auto: Detected finish_reason='{finish_reason}', auto-continuing ({auto_continue_count}/{self.max_auto_run})")
173
174
  except Exception as parse_error:
@@ -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['success']
250
+ output = tool_result.get('output', '')
251
251
  result_type = "answer" if success else "error"
252
- result_content = f"Task execute '{tool_name}' {result_type}: {output}"
252
+ result_content = 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,19 @@ 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
+ 'xml_adding_strategy' : "assistant_message", # user_message
349
+ 'add_response_msg_func' : self.add_response_message,
350
+ 'create_response_msg_func' : self.create_response_message,
351
+ 'tool_box' : self.tool_box,
352
+ 'task_langfuse' : self.task_langfuse,
354
353
  }
355
354
  return response_context
356
355
 
@@ -361,24 +360,24 @@ class XGATaskEngine:
361
360
 
362
361
  def _logging_reponse_chunk(self, chunk, auto_count: int)-> None:
363
362
  try:
364
- chunk_type = chunk.get('type', 'unknown')
363
+ chunk_type = chunk['type']
365
364
  prefix = ""
366
- if chunk_type == 'status':
367
- content = json.loads(chunk.get('content', '{}'))
368
- status_type = content.get('status_type', "empty")
365
+ if chunk_type == "status":
366
+ status_content = chunk['content']
367
+ status_type = status_content.get('status_type', "empty")
369
368
  if status_type in ["tool_started", "tool_completed"]:
370
369
  return
371
370
  prefix = "-" + status_type
372
- elif chunk_type == 'tool':
373
- tool_content = json.loads(chunk.get('content', '{}'))
371
+ elif chunk_type == "tool":
372
+ tool_content = chunk['content']
374
373
  tool_execution = tool_content.get('tool_execution')
375
374
  tool_name = tool_execution.get('function_name')
376
375
  prefix = "-" + tool_name
377
376
 
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)
377
+ status_content = chunk['content']
378
+ pretty_content = status_content
379
+ if isinstance(status_content, dict):
380
+ pretty_content = json.dumps(status_content, ensure_ascii=False, indent=2)
382
381
 
383
382
  if chunk_type == "assistant_chunk":
384
383
  logging.debug(f"TASK_RESP_CHUNK[{auto_count}]<{chunk_type}{prefix}> content: {pretty_content}")
@@ -401,15 +400,14 @@ if __name__ == "__main__":
401
400
  tool_box = XGAMcpToolBox(custom_mcp_server_file="mcpservers/custom_servers.json")
402
401
  system_prompt = read_file("templates/example/fault_user_prompt.txt")
403
402
  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)
403
+ custom_tools=["*"],
404
+ system_prompt=system_prompt,
405
+ session_id="session_1",
406
+ agent_id="agent_1"
407
+ )
408
+ user_input = "locate 10.0.0.1 fault and solution"
409
+ final_result = await engine.run_task_with_final_answer(task_message={'role': "user", 'content': user_input})
410
+ print(f"FINAL RESULT:{final_result}")
413
411
 
414
412
 
415
413
  asyncio.run(main())
@@ -18,14 +18,14 @@ class XGATaskLangFuse:
18
18
  if XGATaskLangFuse.langfuse is None:
19
19
  XGATaskLangFuse.langfuse = setup_langfuse()
20
20
 
21
- self.session_id = session_id
22
- self.task_id = task_id
23
- self.task_run_id = task_run_id
24
- self.task_no = task_no
25
- self.agent_id = agent_id
21
+ self.session_id = session_id
22
+ self.task_id = task_id
23
+ self.task_run_id = task_run_id
24
+ self.task_no = task_no
25
+ self.agent_id = agent_id
26
26
 
27
- self.trace_id = None
28
- self.root_span = None
27
+ self.trace_id = None
28
+ self.root_span = None
29
29
  self.root_span_name = None
30
30
 
31
31
 
@@ -42,7 +42,7 @@ class XGATaskLangFuse:
42
42
  trace = XGATaskLangFuse.langfuse.trace(name="xga_task_engine")
43
43
  self.trace_id = trace.id
44
44
 
45
- metadata = {"task_id": self.task_id, "session_id": self.session_id, "agent_id": self.agent_id}
45
+ metadata = {'task_id': self.task_id, 'session_id': self.session_id, 'agent_id': self.agent_id}
46
46
  self.root_span = trace.span(id=self.task_run_id, name=root_span_name, input=task_message,metadata=metadata)
47
47
  self.root_span_name = root_span_name
48
48
 
@@ -58,8 +58,8 @@ class XGATaskLangFuse:
58
58
  generation_name = f"xga_task_engine_llm_completion[{self.task_no}]({llm_count})"
59
59
  generation_id = f"{self.task_run_id}({llm_count})"
60
60
  return LangfuseMetadata(
61
- generation_name=generation_name,
62
- generation_id=generation_id,
63
- existing_trace_id=self.trace_id,
64
- session_id=self.session_id,
61
+ generation_name = generation_name,
62
+ generation_id = generation_id,
63
+ existing_trace_id = self.trace_id,
64
+ session_id = self.session_id,
65
65
  )
@@ -32,11 +32,13 @@ async def ask(task_id: str,
32
32
  print(f"<XGAETools-ask>: task_id={task_id}, text={text}, attachments={attachments}")
33
33
  return XGAToolResult(success=True, output=str({"status": "Awaiting user response..."}))
34
34
 
35
+
35
36
  @mcp.tool(
36
37
  description="end task, destroy sandbox"
37
38
  )
38
39
  async def end_task(task_id: str) :
39
40
  print(f"<XGAETools-end_task> task_id: {task_id}")
41
+ return XGAToolResult(success=True, output="")
40
42
 
41
43
 
42
44
 
@@ -137,32 +137,3 @@ def to_json_string(value: Any) -> str:
137
137
  # For all other types, convert to JSON
138
138
  return json.dumps(value)
139
139
 
140
-
141
- def format_for_yield(message_object: Dict[str, Any]) -> Dict[str, Any]:
142
- """
143
- Format a message object for yielding, ensuring content and metadata are JSON strings.
144
-
145
- This maintains backward compatibility with clients expecting JSON strings
146
- while the database now stores proper objects.
147
-
148
- Args:
149
- message_object: The message object from the database
150
-
151
- Returns:
152
- Message object with content and metadata as JSON strings
153
- """
154
- if not message_object:
155
- return message_object
156
-
157
- # Create a copy to avoid modifying the original
158
- formatted = message_object.copy()
159
-
160
- # Ensure content is a JSON string
161
- if 'content' in formatted and not isinstance(formatted['content'], str):
162
- formatted['content'] = json.dumps(formatted['content'], ensure_ascii=False, indent=2)
163
-
164
- # Ensure metadata is a JSON string
165
- if 'metadata' in formatted and not isinstance(formatted['metadata'], str):
166
- formatted['metadata'] = json.dumps(formatted['metadata'], ensure_ascii=False, indent=2)
167
-
168
- return formatted
xgae/utils/llm_client.py CHANGED
@@ -51,34 +51,34 @@ class LLMClient:
51
51
  self._init_langfuse()
52
52
 
53
53
  llm_config = llm_config or LLMConfig()
54
- self.max_retries = int(os.getenv("LLM_MAX_RETRIES", 1))
54
+ self.max_retries = int(os.getenv('LLM_MAX_RETRIES', 1))
55
55
 
56
- env_llm_model = os.getenv("LLM_MODEL", "openai/qwen3-235b-a22b")
57
- env_llm_api_key = os.getenv("LLM_API_KEY")
58
- env_llm_api_base = os.getenv("LLM_API_BASE", "https://dashscope.aliyuncs.com/compatible-mode/v1")
59
- env_llm_max_tokens = int(os.getenv("LLM_MAX_TOKENS", 16384))
60
- env_llm_temperature = float(os.getenv("LLM_TEMPERATURE", 0.7))
61
- env_llm_stream = to_bool(os.getenv("LLM_STREAM", False))
62
- env_llm_enable_thinking = to_bool(os.getenv("LLM_ENABLE_THINKING", False))
56
+ env_llm_model = os.getenv('LLM_MODEL', "openai/qwen3-235b-a22b")
57
+ env_llm_api_key = os.getenv('LLM_API_KEY')
58
+ env_llm_api_base = os.getenv('LLM_API_BASE', "https://dashscope.aliyuncs.com/compatible-mode/v1")
59
+ env_llm_max_tokens = int(os.getenv('LLM_MAX_TOKENS', 16384))
60
+ env_llm_temperature = float(os.getenv('LLM_TEMPERATURE', 0.7))
61
+ env_llm_stream = to_bool(os.getenv('LLM_STREAM', False))
62
+ env_llm_enable_thinking = to_bool(os.getenv('LLM_ENABLE_THINKING', False))
63
63
 
64
64
  llm_config_params = {
65
- "model": llm_config.get("model", env_llm_model),
66
- "model_name": llm_config.get("model_name", env_llm_model),
67
- "model_id": llm_config.get("model_id", None),
68
- "api_key": llm_config.get("api_key", env_llm_api_key),
69
- "api_base": llm_config.get("api_base", env_llm_api_base),
70
- "temperature": llm_config.get("temperature", env_llm_temperature),
71
- "max_tokens": llm_config.get("max_tokens", env_llm_max_tokens),
72
- "stream": llm_config.get("stream", env_llm_stream),
73
- "enable_thinking": llm_config.get("enable_thinking", env_llm_enable_thinking),
74
- "reasoning_effort": llm_config.get("reasoning_effort", 'low'),
75
- "response_format": llm_config.get("response_format", None),
76
- "top_p": llm_config.get("top_p", None),
77
- "tools": None,
78
- "tool_choice": "none",
65
+ 'model': llm_config.get('model', env_llm_model),
66
+ 'model_name': llm_config.get('model_name', env_llm_model),
67
+ 'model_id': llm_config.get('model_id', None),
68
+ 'api_key': llm_config.get('api_key', env_llm_api_key),
69
+ 'api_base': llm_config.get('api_base', env_llm_api_base),
70
+ 'temperature': llm_config.get('temperature', env_llm_temperature),
71
+ 'max_tokens': llm_config.get('max_tokens', env_llm_max_tokens),
72
+ 'stream': llm_config.get('stream', env_llm_stream),
73
+ 'enable_thinking': llm_config.get('enable_thinking', env_llm_enable_thinking),
74
+ 'reasoning_effort': llm_config.get('reasoning_effort', "low"),
75
+ 'response_format': llm_config.get('response_format', None),
76
+ 'top_p': llm_config.get('top_p', None),
77
+ 'tools': None,
78
+ 'tool_choice': 'none',
79
79
  }
80
80
 
81
- self.model_name = llm_config_params["model_name"]
81
+ self.model_name = llm_config_params['model_name']
82
82
  self.is_stream = llm_config_params['stream']
83
83
 
84
84
  self.lite_llm_params = self._prepare_llm_params(llm_config_params)
@@ -6,11 +6,11 @@ the XML format with structured function_calls blocks.
6
6
  """
7
7
 
8
8
  import json
9
- import logging
10
9
  import re
11
10
  from dataclasses import dataclass
12
11
  from typing import List, Dict, Any, Optional, Tuple
13
12
 
13
+ from xgae.utils import log_trace
14
14
 
15
15
  @dataclass
16
16
  class XMLToolCall:
@@ -82,7 +82,7 @@ class XMLToolParser:
82
82
  if tool_call:
83
83
  tool_calls.append(tool_call)
84
84
  except Exception as e:
85
- logging.error(f"Error parsing invoke block for {function_name}: {e}")
85
+ log_trace(e, f"XMLToolParser: Error parsing function={function_name}, invoke_content:\n{invoke_content}")
86
86
 
87
87
  return tool_calls
88
88
 
@@ -95,8 +95,8 @@ class XMLToolParser:
95
95
  """Parse a single invoke block into an XMLToolCall."""
96
96
  parameters = {}
97
97
  parsing_details = {
98
- "function_name": function_name,
99
- "raw_parameters": {}
98
+ 'function_name': function_name,
99
+ 'raw_parameters': {}
100
100
  }
101
101
 
102
102
  param_matches = self.PARAMETER_PATTERN.findall(invoke_content)
@@ -104,7 +104,7 @@ class XMLToolParser:
104
104
  param_value = param_value.strip()
105
105
  parsed_value = self._parse_parameter_value(param_value)
106
106
  parameters[param_name] = parsed_value
107
- parsing_details["raw_parameters"][param_name] = param_value
107
+ parsing_details['raw_parameters'][param_name] = param_value
108
108
 
109
109
  # Extract the raw XML for this specific invoke
110
110
  invoke_pattern = re.compile(
@@ -115,10 +115,10 @@ class XMLToolParser:
115
115
  raw_xml = raw_xml_match.group(0) if raw_xml_match else f"<invoke name=\"{function_name}\">...</invoke>"
116
116
 
117
117
  return XMLToolCall(
118
- function_name=function_name,
119
- parameters=parameters,
120
- raw_xml=raw_xml,
121
- parsing_details=parsing_details
118
+ function_name = function_name,
119
+ parameters = parameters,
120
+ raw_xml = raw_xml,
121
+ parsing_details = parsing_details
122
122
  )
123
123
 
124
124
  def _parse_parameter_value(self, value: str) -> Any:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xgae
3
- Version: 0.1.14
3
+ Version: 0.1.16
4
4
  Summary: Extreme General Agent Engine
5
5
  Requires-Python: >=3.13
6
6
  Requires-Dist: colorlog==6.9.0
@@ -0,0 +1,21 @@
1
+ xgae/__init__.py,sha256=OEUd9y9AoGBd3xYerdTTpz9xl4NWkmXeq1a2eil7Qro,72
2
+ xgae/cli_app.py,sha256=ieTaS0b532P_8g9Mz2xda8TOZwYD2hKGnNZiarADAM0,3000
3
+ xgae/engine/engine_base.py,sha256=-QZqLRbQdwRUfbY4l3i7dFfMB-BL267a-wGZR9bMPLc,1662
4
+ xgae/engine/mcp_tool_box.py,sha256=iAWUWP_goHAEeYYMUvTKiMkR2VkEOoRtNmjLV9HaIUg,10415
5
+ xgae/engine/prompt_builder.py,sha256=dqv0xcB-UWQhqISbMCYCTM1ANtthY6xUe7sJ9vPRqQ4,4364
6
+ xgae/engine/task_engine.py,sha256=oEyDORfDqHn7MzodeLrbhITd8TIIZrDIouF09j0twf0,20940
7
+ xgae/engine/task_langfuse.py,sha256=n2bajsHq2Zt3jetel8cSlN2lo42mZgTmbR4Zbx9JvsM,2416
8
+ xgae/engine/responser/non_stream_responser.py,sha256=HkmeFBIoxORhnFVh6XT0i6ixfF5vNzvN7B_BP6FzPVM,5334
9
+ xgae/engine/responser/responser_base.py,sha256=eQ4E1p_PoQISrIOLmjapGRH_RRX-7LOY1P2SrDjvyTM,24624
10
+ xgae/engine/responser/stream_responser.py,sha256=P6IkPniGli8XNq_BVciHeMCJiE0k3lNokTvX1GqRRbc,8046
11
+ xgae/tools/without_general_tools_app.py,sha256=H3JrwA0u7BIKW69yYBPLDpPesklY5quQIMaSVyvJ4s8,3839
12
+ xgae/utils/__init__.py,sha256=ElaGS-zdeZeu6is41u3Ny7lkvhg7BDSK-jMNg9j6K5A,499
13
+ xgae/utils/json_helpers.py,sha256=WD4G5U9Dh8N6J9O0L5wGyqj-NHi09kcXHGdLD_26nlc,3607
14
+ xgae/utils/llm_client.py,sha256=mWRtvtSMk_8NuzFReT9x52ayHlCNVZMZAltD6TQ-xZ8,14404
15
+ xgae/utils/misc.py,sha256=aMWOvJ9VW52q-L9Lkjl1hvXqLwpJAmyxA-Z8jzqFG0U,907
16
+ xgae/utils/setup_env.py,sha256=MqNG0c2QQBDFU1kI8frxr9kB5d08Mmi3QZ1OoorgIa0,2662
17
+ xgae/utils/xml_tool_parser.py,sha256=Mb0d8kBrfyAEvUwW1Nqir-3BgxZRr0ZX3WymQouuFSo,4859
18
+ xgae-0.1.16.dist-info/METADATA,sha256=i9XjQf1XQ-yOhFfVzz7tnOJwLPQOQFjF2gy1XmN7zmY,310
19
+ xgae-0.1.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
20
+ xgae-0.1.16.dist-info/entry_points.txt,sha256=SWN01JNAncV0oApEvFzpH0wsXfnFlB1adCH4IrAJxGc,163
21
+ xgae-0.1.16.dist-info/RECORD,,
@@ -1,21 +0,0 @@
1
- xgae/__init__.py,sha256=OEUd9y9AoGBd3xYerdTTpz9xl4NWkmXeq1a2eil7Qro,72
2
- xgae/cli_app.py,sha256=vKuCIJw0gwXdtkT-QNCZKt2dE53thvTFwQr7nTgvaPY,3000
3
- xgae/engine/engine_base.py,sha256=-QZqLRbQdwRUfbY4l3i7dFfMB-BL267a-wGZR9bMPLc,1662
4
- xgae/engine/mcp_tool_box.py,sha256=ot1lpMw77e6VLe4UPCJw3TxcjIXBqzg_qphdmHmW2XQ,10414
5
- xgae/engine/prompt_builder.py,sha256=X9bS7YIms6LYplCpNHeUmi74xFP5MwFXmXNqOt1Xz-Q,4356
6
- xgae/engine/task_engine.py,sha256=ayv4DTwDArMRQ_CPBxt8tdk7E7JPB0Osb5jxlikMsxM,21052
7
- xgae/engine/task_langfuse.py,sha256=b0aJ_Di-WDcYzi0TFCvcKWxkBz7PYP2jx3N52OptQMs,2349
8
- xgae/engine/responser/non_stream_responser.py,sha256=_DkyBZ50N1Tm8K0W16MD5vgx2Eu_amt2LgfN_7Ej1Us,5542
9
- xgae/engine/responser/responser_base.py,sha256=mgioPh1atCd5PueumyxAN7eB87SB7wOSB_mLTI-6lIg,25250
10
- xgae/engine/responser/stream_responser.py,sha256=AVEs0pkLZNPzXcp5A1iFNjDb-aUy7_NWRqThCZqmIyQ,14852
11
- xgae/tools/without_general_tools_app.py,sha256=FGMV6njcOKwwfitc0j_nUov0RC-eWlhO1IP8_KHz1tQ,3788
12
- xgae/utils/__init__.py,sha256=ElaGS-zdeZeu6is41u3Ny7lkvhg7BDSK-jMNg9j6K5A,499
13
- xgae/utils/json_helpers.py,sha256=ubp-dOCeROnZv7JHARRdmDIO5Npdwzrt8AWo3SMv0kI,4705
14
- xgae/utils/llm_client.py,sha256=6e3kzx73QN6z2SYMQQFmrmODj2Rk-GPJYIxBcFZhMQE,14361
15
- xgae/utils/misc.py,sha256=aMWOvJ9VW52q-L9Lkjl1hvXqLwpJAmyxA-Z8jzqFG0U,907
16
- xgae/utils/setup_env.py,sha256=MqNG0c2QQBDFU1kI8frxr9kB5d08Mmi3QZ1OoorgIa0,2662
17
- xgae/utils/xml_tool_parser.py,sha256=I9xAZC_ElwBY19PNUq-WLXe9FSIJMeAv2Xs-VlajI7Y,4782
18
- xgae-0.1.14.dist-info/METADATA,sha256=WGVnO2sWvP3EM98tObwlafmkfIRHl2oSTUn5rBsZ3HM,310
19
- xgae-0.1.14.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
20
- xgae-0.1.14.dist-info/entry_points.txt,sha256=SWN01JNAncV0oApEvFzpH0wsXfnFlB1adCH4IrAJxGc,163
21
- xgae-0.1.14.dist-info/RECORD,,
File without changes