xgae 0.1.7__py3-none-any.whl → 0.1.9__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/engine_base.py +2 -2
- xgae/engine/mcp_tool_box.py +7 -6
- xgae/engine/prompt_builder.py +1 -1
- xgae/engine/responser/non_stream_responser.py +8 -7
- xgae/engine/responser/responser_base.py +54 -56
- xgae/engine/responser/stream_responser.py +24 -25
- xgae/engine/task_engine.py +64 -61
- xgae/engine/task_langfuse.py +63 -0
- xgae/tools/without_general_tools_app.py +1 -1
- xgae/utils/__init__.py +7 -5
- xgae/utils/json_helpers.py +7 -13
- xgae/utils/llm_client.py +62 -39
- xgae/utils/misc.py +3 -1
- xgae/utils/setup_env.py +36 -33
- xgae/utils/xml_tool_parser.py +4 -80
- xgae-0.1.9.dist-info/METADATA +11 -0
- xgae-0.1.9.dist-info/RECORD +20 -0
- {xgae-0.1.7.dist-info → xgae-0.1.9.dist-info}/entry_points.txt +1 -0
- xgae-0.1.7.dist-info/METADATA +0 -11
- xgae-0.1.7.dist-info/RECORD +0 -19
- {xgae-0.1.7.dist-info → xgae-0.1.9.dist-info}/WHEEL +0 -0
xgae/engine/engine_base.py
CHANGED
|
@@ -7,7 +7,7 @@ class XGAError(Exception):
|
|
|
7
7
|
pass
|
|
8
8
|
|
|
9
9
|
XGAMsgStatusType = Literal["error", "finish", "tool_error", "tool_started", "tool_completed", "tool_failed", "thread_run_start", "thread_run_end", "assistant_response_start", "assistant_response_end"]
|
|
10
|
-
XGAResponseMsgType = Literal["user", "status", "tool", "assistant"]
|
|
10
|
+
XGAResponseMsgType = Literal["user", "status", "tool", "assistant", "assistant_complete"]
|
|
11
11
|
|
|
12
12
|
class XGAResponseMessage(TypedDict, total=False):
|
|
13
13
|
message_id: str
|
|
@@ -55,4 +55,4 @@ class XGAToolBox(ABC):
|
|
|
55
55
|
|
|
56
56
|
@abstractmethod
|
|
57
57
|
async def call_tool(self, task_id: str, tool_name: str, args: Optional[Dict[str, Any]] = None) -> XGAToolResult:
|
|
58
|
-
pass
|
|
58
|
+
pass
|
xgae/engine/mcp_tool_box.py
CHANGED
|
@@ -8,7 +8,6 @@ from langchain_mcp_adapters.client import MultiServerMCPClient
|
|
|
8
8
|
from langchain_mcp_adapters.tools import load_mcp_tools
|
|
9
9
|
|
|
10
10
|
from xgae.engine.engine_base import XGAError, XGAToolSchema, XGAToolBox, XGAToolResult
|
|
11
|
-
from xgae.utils import langfuse
|
|
12
11
|
|
|
13
12
|
class XGAMcpToolBox(XGAToolBox):
|
|
14
13
|
GENERAL_MCP_SERVER_NAME = "xga_general"
|
|
@@ -191,11 +190,16 @@ class XGAMcpToolBox(XGAToolBox):
|
|
|
191
190
|
if __name__ == "__main__":
|
|
192
191
|
import asyncio
|
|
193
192
|
from dataclasses import asdict
|
|
193
|
+
from xgae.utils.setup_env import setup_logging
|
|
194
|
+
|
|
195
|
+
setup_logging()
|
|
194
196
|
|
|
195
197
|
async def main():
|
|
198
|
+
## Before Run Exec: uv run custom_fault_tools
|
|
199
|
+
mcp_tool_box = XGAMcpToolBox(custom_mcp_server_file="mcpservers/custom_servers.json")
|
|
200
|
+
#mcp_tool_box = XGAMcpToolBox()
|
|
201
|
+
|
|
196
202
|
task_id = "task1"
|
|
197
|
-
#mcp_tool_box = XGAMcpToolBox(custom_mcp_server_file="mcpservers/custom_servers.json")
|
|
198
|
-
mcp_tool_box = XGAMcpToolBox()
|
|
199
203
|
await mcp_tool_box.load_mcp_tools_schema()
|
|
200
204
|
await mcp_tool_box.creat_task_tool_box(task_id=task_id, general_tools=["*"], custom_tools=["bomc_fault.*"])
|
|
201
205
|
tool_schemas = mcp_tool_box.get_task_tool_schemas(task_id, "general_tool")
|
|
@@ -210,9 +214,6 @@ if __name__ == "__main__":
|
|
|
210
214
|
print(asdict(tool_schema))
|
|
211
215
|
print()
|
|
212
216
|
|
|
213
|
-
result = await mcp_tool_box.call_tool(task_id=task_id, tool_name="web_search", args={"task_id": task_id, "query": "查询天津天气"})
|
|
214
|
-
print(f"call web_search result: {result}")
|
|
215
|
-
|
|
216
217
|
result = await mcp_tool_box.call_tool(task_id=task_id, tool_name="complete", args={"task_id": task_id})
|
|
217
218
|
print(f"call complete result: {result}")
|
|
218
219
|
|
xgae/engine/prompt_builder.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
3
|
from typing import List, Dict, Any, AsyncGenerator, override,Optional
|
|
4
|
+
from xgae.utils.json_helpers import format_for_yield
|
|
4
5
|
|
|
5
6
|
from xgae.engine.responser.responser_base import TaskResponseProcessor, TaskResponserContext, TaskRunContinuousState
|
|
6
|
-
|
|
7
|
-
from xgae.utils.json_helpers import format_for_yield
|
|
7
|
+
|
|
8
8
|
|
|
9
9
|
class NonStreamTaskResponser(TaskResponseProcessor):
|
|
10
10
|
def __init__(self, response_context: TaskResponserContext):
|
|
@@ -16,6 +16,7 @@ class NonStreamTaskResponser(TaskResponseProcessor):
|
|
|
16
16
|
llm_content = ""
|
|
17
17
|
parsed_xml_data = []
|
|
18
18
|
finish_reason = None
|
|
19
|
+
llm_count = continuous_state.get("auto_continue_count")
|
|
19
20
|
|
|
20
21
|
try:
|
|
21
22
|
# Extract finish_reason, content, tool calls
|
|
@@ -24,7 +25,7 @@ class NonStreamTaskResponser(TaskResponseProcessor):
|
|
|
24
25
|
finish_reason = llm_response.choices[0].finish_reason
|
|
25
26
|
logging.info(f"NonStreamTask:LLM response finish_reason={finish_reason}")
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
self.root_span.event(name=f"non_stream_processor_start[{self.task_no}]({llm_count})", level="DEFAULT",
|
|
28
29
|
status_message=(f"finish_reason={finish_reason}, tool_exec_strategy={self.tool_execution_strategy}"))
|
|
29
30
|
|
|
30
31
|
response_message = llm_response.choices[0].message if hasattr(llm_response.choices[0], 'message') else None
|
|
@@ -47,8 +48,8 @@ class NonStreamTaskResponser(TaskResponseProcessor):
|
|
|
47
48
|
else:
|
|
48
49
|
logging.warning(f"NonStreamTask:LLM response_message is empty")
|
|
49
50
|
|
|
50
|
-
message_data = {"role": "assistant", "content": llm_content
|
|
51
|
-
assistant_msg = self.add_response_message(type="
|
|
51
|
+
message_data = {"role": "assistant", "content": llm_content} # index=-1, full llm_content
|
|
52
|
+
assistant_msg = self.add_response_message(type="assistant_complete", content=message_data, is_llm_message=True)
|
|
52
53
|
yield assistant_msg
|
|
53
54
|
|
|
54
55
|
tool_calls_to_execute = [item['tool_call'] for item in parsed_xml_data]
|
|
@@ -90,7 +91,7 @@ class NonStreamTaskResponser(TaskResponseProcessor):
|
|
|
90
91
|
|
|
91
92
|
except Exception as e:
|
|
92
93
|
logging.error(f"NonStreamTask: Error processing non-streaming response: {llm_content}")
|
|
93
|
-
|
|
94
|
+
self.root_span.event(name="error_processing_non_streaming_response", level="ERROR",
|
|
94
95
|
status_message=(f"Error processing non-streaming response: {str(e)}"))
|
|
95
96
|
|
|
96
97
|
content = {"role": "system", "status_type": "error", "message": str(e)}
|
|
@@ -100,7 +101,7 @@ class NonStreamTaskResponser(TaskResponseProcessor):
|
|
|
100
101
|
|
|
101
102
|
# Re-raise the same exception (not a new one) to ensure proper error propagation
|
|
102
103
|
logging.critical(f"NonStreamTask: Re-raising error to stop further processing: {str(e)}")
|
|
103
|
-
|
|
104
|
+
self.root_span.event(name="re_raising_error_to_stop_further_processing", level="CRITICAL",
|
|
104
105
|
status_message=(f"Re-raising error to stop further processing: {str(e)}"))
|
|
105
106
|
raise # Use bare 'raise' to preserve the original exception with its traceback
|
|
106
107
|
|
|
@@ -6,11 +6,13 @@ from abc import ABC, abstractmethod
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from typing import List, Dict, Any, Optional, Tuple, Union, Literal, Callable, TypedDict, AsyncGenerator
|
|
8
8
|
|
|
9
|
-
from xgae.
|
|
10
|
-
from xgae.utils import langfuse
|
|
11
|
-
from xgae.utils.json_helpers import safe_json_parse,format_for_yield
|
|
9
|
+
from xgae.utils.json_helpers import safe_json_parse
|
|
12
10
|
from xgae.utils.xml_tool_parser import XMLToolParser
|
|
13
11
|
|
|
12
|
+
from xgae.engine.engine_base import XGAToolResult, XGAToolBox
|
|
13
|
+
from xgae.engine.task_langfuse import XGATaskLangFuse
|
|
14
|
+
|
|
15
|
+
|
|
14
16
|
# Type alias for XML result adding strategy
|
|
15
17
|
XmlAddingStrategy = Literal["user_message", "assistant_message", "inline_edit"]
|
|
16
18
|
|
|
@@ -21,14 +23,14 @@ class TaskResponserContext(TypedDict, total=False):
|
|
|
21
23
|
is_stream: bool
|
|
22
24
|
task_id: str
|
|
23
25
|
task_run_id: str
|
|
24
|
-
|
|
25
|
-
root_span_id: str
|
|
26
|
+
task_no: int
|
|
26
27
|
model_name: str
|
|
27
28
|
max_xml_tool_calls: int # LLM generate max_xml_tool limit, 0 is no limit
|
|
28
|
-
add_response_msg_func: Callable
|
|
29
|
-
tool_box: XGAToolBox
|
|
30
29
|
tool_execution_strategy: ToolExecutionStrategy
|
|
31
30
|
xml_adding_strategy: XmlAddingStrategy
|
|
31
|
+
add_response_msg_func: Callable
|
|
32
|
+
tool_box: XGAToolBox
|
|
33
|
+
task_langfuse: XGATaskLangFuse
|
|
32
34
|
|
|
33
35
|
|
|
34
36
|
class TaskRunContinuousState(TypedDict, total=False):
|
|
@@ -56,22 +58,19 @@ class TaskResponseProcessor(ABC):
|
|
|
56
58
|
|
|
57
59
|
self.task_id = response_context.get("task_id")
|
|
58
60
|
self.task_run_id = response_context.get("task_run_id")
|
|
61
|
+
self.task_no = response_context.get("task_no")
|
|
59
62
|
self.tool_execution_strategy = self.response_context.get("tool_execution_strategy", "parallel")
|
|
60
63
|
self.xml_adding_strategy = self.response_context.get("xml_adding_strategy", "user_message")
|
|
61
64
|
self.max_xml_tool_calls = self.response_context.get("max_xml_tool_calls", 0)
|
|
62
65
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"parent_span_id": self.response_context.get("root_span_id"),
|
|
66
|
-
}
|
|
67
|
-
|
|
66
|
+
task_langfuse = response_context.get("task_langfuse")
|
|
67
|
+
self.root_span = task_langfuse.root_span
|
|
68
68
|
self.add_response_message = response_context.get("add_response_msg_func")
|
|
69
69
|
|
|
70
70
|
self.tool_box = response_context.get("tool_box")
|
|
71
71
|
self.xml_parser = XMLToolParser()
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
|
|
75
74
|
@abstractmethod
|
|
76
75
|
async def process_response(self,
|
|
77
76
|
llm_response: AsyncGenerator,
|
|
@@ -174,7 +173,7 @@ class TaskResponseProcessor(ABC):
|
|
|
174
173
|
except Exception as e:
|
|
175
174
|
logging.error(f"Error extracting XML chunks: {e}")
|
|
176
175
|
logging.error(f"Content was: {content}")
|
|
177
|
-
|
|
176
|
+
self.root_span.event(name="error_extracting_xml_chunks", level="ERROR",
|
|
178
177
|
status_message=(f"Error extracting XML chunks: {e}"), metadata={"content": content})
|
|
179
178
|
|
|
180
179
|
return chunks
|
|
@@ -224,7 +223,7 @@ class TaskResponseProcessor(ABC):
|
|
|
224
223
|
except Exception as e:
|
|
225
224
|
logging.error(f"Error parsing XML chunk: {e}")
|
|
226
225
|
logging.error(f"XML chunk was: {xml_chunk}")
|
|
227
|
-
|
|
226
|
+
self.root_span.event(name="error_parsing_xml_chunk", level="ERROR",
|
|
228
227
|
status_message=(f"Error parsing XML chunk: {e}"), metadata={"xml_chunk": xml_chunk})
|
|
229
228
|
return None
|
|
230
229
|
|
|
@@ -250,7 +249,7 @@ class TaskResponseProcessor(ABC):
|
|
|
250
249
|
|
|
251
250
|
except Exception as e:
|
|
252
251
|
logging.error(f"Error parsing XML tool calls: {e}", exc_info=True)
|
|
253
|
-
|
|
252
|
+
self.root_span.event(name="error_parsing_xml_tool_calls", level="ERROR",
|
|
254
253
|
status_message=(f"Error parsing XML tool calls: {e}"), metadata={"content": content})
|
|
255
254
|
|
|
256
255
|
return parsed_data
|
|
@@ -258,36 +257,35 @@ class TaskResponseProcessor(ABC):
|
|
|
258
257
|
|
|
259
258
|
async def _execute_tool(self, tool_call: Dict[str, Any]) -> XGAToolResult:
|
|
260
259
|
"""Execute a single tool call and return the result."""
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
logging.error(f"Error executing tool {tool_call['function_name']}: {str(e)}", exc_info=True)
|
|
260
|
+
exec_tool_span = self.root_span.span(name=f"execute_tool.{tool_call['function_name']}", input=tool_call["arguments"])
|
|
261
|
+
try:
|
|
262
|
+
function_name = tool_call["function_name"]
|
|
263
|
+
arguments = tool_call["arguments"]
|
|
264
|
+
|
|
265
|
+
logging.info(f"Executing tool: {function_name} with arguments: {arguments}")
|
|
266
|
+
|
|
267
|
+
if isinstance(arguments, str):
|
|
268
|
+
try:
|
|
269
|
+
arguments = safe_json_parse(arguments)
|
|
270
|
+
except json.JSONDecodeError:
|
|
271
|
+
arguments = {"text": arguments} # @todo modify
|
|
272
|
+
|
|
273
|
+
result = None
|
|
274
|
+
available_tool_names = self.tool_box.get_task_tool_names(self.task_id)
|
|
275
|
+
if function_name in available_tool_names:
|
|
276
|
+
result = await self.tool_box.call_tool(self.task_id, function_name, arguments)
|
|
277
|
+
else:
|
|
278
|
+
logging.error(f"Tool function '{function_name}' not found in registry")
|
|
279
|
+
result = XGAToolResult(success=False, output=f"Tool function '{function_name}' not found")
|
|
280
|
+
logging.info(f"Tool execution complete: {function_name} -> {result}")
|
|
281
|
+
exec_tool_span.update(status_message="tool_executed", output=result)
|
|
282
|
+
|
|
283
|
+
return result
|
|
284
|
+
except Exception as e:
|
|
285
|
+
logging.error(f"Error executing tool {tool_call['function_name']}: {str(e)}", exc_info=True)
|
|
288
286
|
|
|
289
|
-
|
|
290
|
-
|
|
287
|
+
exec_tool_span.update(status_message="tool_execution_error", output=f"Error executing tool: {str(e)}", level="ERROR")
|
|
288
|
+
return XGAToolResult(success=False, output=f"Error executing tool: {str(e)}")
|
|
291
289
|
|
|
292
290
|
async def _execute_tools(
|
|
293
291
|
self,
|
|
@@ -321,7 +319,7 @@ class TaskResponseProcessor(ABC):
|
|
|
321
319
|
return []
|
|
322
320
|
tool_names = [t.get('function_name', 'unknown') for t in tool_calls]
|
|
323
321
|
logging.info(f"Executing {len(tool_calls)} tools sequentially: {tool_names}")
|
|
324
|
-
|
|
322
|
+
self.root_span.event(name="executing_tools_sequentially", level="DEFAULT",
|
|
325
323
|
status_message=(f"Executing {len(tool_calls)} tools sequentially: {tool_names}"))
|
|
326
324
|
|
|
327
325
|
results = []
|
|
@@ -337,19 +335,19 @@ class TaskResponseProcessor(ABC):
|
|
|
337
335
|
# Check if this is a terminating tool (ask or complete)
|
|
338
336
|
if tool_name in ['ask', 'complete']:
|
|
339
337
|
logging.info(f"Terminating tool '{tool_name}' executed. Stopping further tool execution.")
|
|
340
|
-
#
|
|
338
|
+
# self.root_span.event(name="terminating_tool_executed",
|
|
341
339
|
# level="DEFAULT", status_message=(f"Terminating tool '{tool_name}' executed. Stopping further tool execution."))
|
|
342
340
|
break # Stop executing remaining tools
|
|
343
341
|
|
|
344
342
|
except Exception as e:
|
|
345
343
|
logging.error(f"Error executing tool {tool_name}: {str(e)}")
|
|
346
|
-
|
|
344
|
+
self.root_span.event(name="error_executing_tool", level="ERROR",
|
|
347
345
|
status_message=(f"Error executing tool {tool_name}: {str(e)}"))
|
|
348
346
|
error_result = XGAToolResult(success=False, output=f"Error executing tool: {str(e)}")
|
|
349
347
|
results.append((tool_call, error_result))
|
|
350
348
|
|
|
351
349
|
logging.info(f"Sequential execution completed for {len(results)} tools (out of {len(tool_calls)} total)")
|
|
352
|
-
#
|
|
350
|
+
# self.root_span.event(name="sequential_execution_completed", level="DEFAULT",
|
|
353
351
|
# status_message=(f"Sequential execution completed for {len(results)} tools (out of {len(tool_calls)} total)"))
|
|
354
352
|
return results
|
|
355
353
|
|
|
@@ -361,7 +359,7 @@ class TaskResponseProcessor(ABC):
|
|
|
361
359
|
try:
|
|
362
360
|
tool_names = [t.get('function_name', 'unknown') for t in tool_calls]
|
|
363
361
|
logging.info(f"Executing {len(tool_calls)} tools in parallel: {tool_names}")
|
|
364
|
-
#
|
|
362
|
+
# self.root_span.event(name="executing_tools_in_parallel", level="DEFAULT",
|
|
365
363
|
# status_message=(f"Executing {len(tool_calls)} tools in parallel: {tool_names}"))
|
|
366
364
|
|
|
367
365
|
# Create tasks for all tool calls
|
|
@@ -375,7 +373,7 @@ class TaskResponseProcessor(ABC):
|
|
|
375
373
|
for i, (tool_call, result) in enumerate(zip(tool_calls, results)):
|
|
376
374
|
if isinstance(result, Exception):
|
|
377
375
|
logging.error(f"Error executing tool {tool_call.get('function_name', 'unknown')}: {str(result)}")
|
|
378
|
-
|
|
376
|
+
self.root_span.event(name="error_executing_tool", level="ERROR", status_message=(
|
|
379
377
|
f"Error executing tool {tool_call.get('function_name', 'unknown')}: {str(result)}"))
|
|
380
378
|
# Create error result
|
|
381
379
|
error_result = XGAToolResult(success=False, output=f"Error executing tool: {str(result)}")
|
|
@@ -384,13 +382,13 @@ class TaskResponseProcessor(ABC):
|
|
|
384
382
|
processed_results.append((tool_call, result))
|
|
385
383
|
|
|
386
384
|
logging.info(f"Parallel execution completed for {len(tool_calls)} tools")
|
|
387
|
-
#
|
|
385
|
+
# self.root_span.event(name="parallel_execution_completed", level="DEFAULT",
|
|
388
386
|
# status_message=(f"Parallel execution completed for {len(tool_calls)} tools"))
|
|
389
387
|
return processed_results
|
|
390
388
|
|
|
391
389
|
except Exception as e:
|
|
392
390
|
logging.error(f"Error in parallel tool execution: {str(e)}", exc_info=True)
|
|
393
|
-
|
|
391
|
+
self.root_span.event(name="error_in_parallel_tool_execution", level="ERROR",
|
|
394
392
|
status_message=(f"Error in parallel tool execution: {str(e)}"))
|
|
395
393
|
# Return error results for all tools if the gather itself fails
|
|
396
394
|
return [(tool_call, XGAToolResult(success=False, output=f"Execution error: {str(e)}"))
|
|
@@ -461,7 +459,7 @@ class TaskResponseProcessor(ABC):
|
|
|
461
459
|
return message_obj # Return the modified message object
|
|
462
460
|
except Exception as e:
|
|
463
461
|
logging.error(f"Error adding tool result: {str(e)}", exc_info=True)
|
|
464
|
-
|
|
462
|
+
self.root_span.event(name="error_adding_tool_result", level="ERROR",
|
|
465
463
|
status_message=(f"Error adding tool result: {str(e)}"),
|
|
466
464
|
metadata={"tool_call": tool_call, "result": result, "strategy": strategy,
|
|
467
465
|
"assistant_message_id": assistant_message_id,
|
|
@@ -481,7 +479,7 @@ class TaskResponseProcessor(ABC):
|
|
|
481
479
|
return message_obj # Return the full message object
|
|
482
480
|
except Exception as e2:
|
|
483
481
|
logging.error(f"Failed even with fallback message: {str(e2)}", exc_info=True)
|
|
484
|
-
|
|
482
|
+
self.root_span.event(name="failed_even_with_fallback_message", level="ERROR",
|
|
485
483
|
status_message=(f"Failed even with fallback message: {str(e2)}"),
|
|
486
484
|
metadata={"tool_call": tool_call, "result": result, "strategy": strategy,
|
|
487
485
|
"assistant_message_id": assistant_message_id,
|
|
@@ -589,7 +587,7 @@ class TaskResponseProcessor(ABC):
|
|
|
589
587
|
if context.function_name in ['ask', 'complete']:
|
|
590
588
|
metadata["agent_should_terminate"] = "true"
|
|
591
589
|
logging.info(f"Marking tool status for '{context.function_name}' with termination signal.")
|
|
592
|
-
#
|
|
590
|
+
# self.root_span.event(name="marking_tool_status_for_termination", level="DEFAULT", status_message=(
|
|
593
591
|
# f"Marking tool status for '{context.function_name}' with termination signal."))
|
|
594
592
|
# <<< END ADDED >>>
|
|
595
593
|
|
|
@@ -7,7 +7,6 @@ from dataclasses import dataclass
|
|
|
7
7
|
from datetime import datetime, timezone
|
|
8
8
|
from typing import List, Dict, Any, Optional, AsyncGenerator, override, Literal
|
|
9
9
|
|
|
10
|
-
from xgae.utils import langfuse
|
|
11
10
|
from xgae.engine.responser.responser_base import TaskResponseProcessor, TaskResponserContext,TaskRunContinuousState,XmlAddingStrategy,ToolExecutionStrategy
|
|
12
11
|
from xgae.utils.json_helpers import (
|
|
13
12
|
ensure_dict, safe_json_parse,
|
|
@@ -205,7 +204,7 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
205
204
|
__sequence += 1
|
|
206
205
|
else:
|
|
207
206
|
logging.info("XML tool call limit reached - not yielding more content chunks")
|
|
208
|
-
|
|
207
|
+
self.root_span.event(name="xml_tool_call_limit_reached", level="DEFAULT", status_message=(
|
|
209
208
|
f"XML tool call limit reached - not yielding more content chunks"))
|
|
210
209
|
|
|
211
210
|
# --- Process XML Tool Calls (if enabled and limit not reached) ---
|
|
@@ -317,7 +316,7 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
317
316
|
|
|
318
317
|
if finish_reason == "xml_tool_limit_reached":
|
|
319
318
|
logging.info("Stopping stream processing after loop due to XML tool call limit")
|
|
320
|
-
|
|
319
|
+
self.root_span.event(name="stopping_stream_processing_after_loop_due_to_xml_tool_call_limit",
|
|
321
320
|
level="DEFAULT", status_message=(
|
|
322
321
|
f"Stopping stream processing after loop due to XML tool call limit"))
|
|
323
322
|
break
|
|
@@ -352,18 +351,18 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
352
351
|
# f"🔥 Estimated tokens – prompt: {prompt_tokens}, "
|
|
353
352
|
# f"completion: {completion_tokens}, total: {prompt_tokens + completion_tokens}"
|
|
354
353
|
# )
|
|
355
|
-
|
|
354
|
+
self.root_span.event(name="usage_calculated_with_litellm_token_counter", level="DEFAULT",
|
|
356
355
|
status_message=(f"Usage calculated with litellm.token_counter"))
|
|
357
356
|
except Exception as e:
|
|
358
357
|
logging.warning(f"Failed to calculate usage: {str(e)}")
|
|
359
|
-
|
|
358
|
+
self.root_span.event(name="failed_to_calculate_usage", level="WARNING",
|
|
360
359
|
status_message=(f"Failed to calculate usage: {str(e)}"))
|
|
361
360
|
|
|
362
361
|
# Wait for pending tool executions from streaming phase
|
|
363
362
|
tool_results_buffer = [] # Stores (tool_call, result, tool_index, context)
|
|
364
363
|
if pending_tool_executions:
|
|
365
364
|
logging.info(f"Waiting for {len(pending_tool_executions)} pending streamed tool executions")
|
|
366
|
-
|
|
365
|
+
self.root_span.event(name="waiting_for_pending_streamed_tool_executions", level="DEFAULT", status_message=(
|
|
367
366
|
f"Waiting for {len(pending_tool_executions)} pending streamed tool executions"))
|
|
368
367
|
# ... (asyncio.wait logic) ...
|
|
369
368
|
pending_tasks = [execution["task"] for execution in pending_tool_executions]
|
|
@@ -387,19 +386,19 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
387
386
|
if tool_name in ['ask', 'complete']:
|
|
388
387
|
logging.info(
|
|
389
388
|
f"Terminating tool '{tool_name}' completed during streaming. Setting termination flag.")
|
|
390
|
-
|
|
389
|
+
self.root_span.event(name="terminating_tool_completed_during_streaming",
|
|
391
390
|
level="DEFAULT", status_message=(
|
|
392
391
|
f"Terminating tool '{tool_name}' completed during streaming. Setting termination flag."))
|
|
393
392
|
agent_should_terminate = True
|
|
394
393
|
|
|
395
394
|
else: # Should not happen with asyncio.wait
|
|
396
395
|
logging.warning(f"Task for tool index {tool_idx} not done after wait.")
|
|
397
|
-
|
|
396
|
+
self.root_span.event(name="task_for_tool_index_not_done_after_wait", level="WARNING",
|
|
398
397
|
status_message=(
|
|
399
398
|
f"Task for tool index {tool_idx} not done after wait."))
|
|
400
399
|
except Exception as e:
|
|
401
400
|
logging.error(f"Error getting result for pending tool execution {tool_idx}: {str(e)}")
|
|
402
|
-
|
|
401
|
+
self.root_span.event(name="error_getting_result_for_pending_tool_execution", level="ERROR",
|
|
403
402
|
status_message=(
|
|
404
403
|
f"Error getting result for pending tool execution {tool_idx}: {str(e)}"))
|
|
405
404
|
context.error = e
|
|
@@ -419,7 +418,7 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
419
418
|
if tool_name in ['ask', 'complete']:
|
|
420
419
|
logging.info(
|
|
421
420
|
f"Terminating tool '{tool_name}' completed during streaming. Setting termination flag.")
|
|
422
|
-
|
|
421
|
+
self.root_span.event(name="terminating_tool_completed_during_streaming", level="DEFAULT",
|
|
423
422
|
status_message=(
|
|
424
423
|
f"Terminating tool '{tool_name}' completed during streaming. Setting termination flag."))
|
|
425
424
|
agent_should_terminate = True
|
|
@@ -432,7 +431,7 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
432
431
|
except Exception as e:
|
|
433
432
|
logging.error(
|
|
434
433
|
f"Error getting result/yielding status for pending tool execution {tool_idx}: {str(e)}")
|
|
435
|
-
|
|
434
|
+
self.root_span.event(name="error_getting_result_yielding_status_for_pending_tool_execution",
|
|
436
435
|
level="ERROR", status_message=(
|
|
437
436
|
f"Error getting result/yielding status for pending tool execution {tool_idx}: {str(e)}"))
|
|
438
437
|
context.error = e
|
|
@@ -451,7 +450,7 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
451
450
|
if finish_msg_obj: yield format_for_yield(finish_msg_obj)
|
|
452
451
|
logging.info(
|
|
453
452
|
f"Stream finished with reason: xml_tool_limit_reached after {xml_tool_call_count} XML tool calls")
|
|
454
|
-
|
|
453
|
+
self.root_span.event(name="stream_finished_with_reason_xml_tool_limit_reached_after_xml_tool_calls",
|
|
455
454
|
level="DEFAULT", status_message=(
|
|
456
455
|
f"Stream finished with reason: xml_tool_limit_reached after {xml_tool_call_count} XML tool calls"))
|
|
457
456
|
|
|
@@ -501,7 +500,7 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
501
500
|
yield format_for_yield(yield_message)
|
|
502
501
|
else:
|
|
503
502
|
logging.error(f"Failed to save final assistant message for thread {thread_id}")
|
|
504
|
-
|
|
503
|
+
self.root_span.event(name="failed_to_save_final_assistant_message_for_thread", level="ERROR",
|
|
505
504
|
status_message=(f"Failed to save final assistant message for thread {thread_id}"))
|
|
506
505
|
# Save and yield an error status
|
|
507
506
|
err_content = {"role": "system", "status_type": "error",
|
|
@@ -569,7 +568,7 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
569
568
|
# Populate from buffer if executed on stream
|
|
570
569
|
if config.execute_on_stream and tool_results_buffer:
|
|
571
570
|
logging.info(f"Processing {len(tool_results_buffer)} buffered tool results")
|
|
572
|
-
|
|
571
|
+
self.root_span.event(name="processing_buffered_tool_results", level="DEFAULT",
|
|
573
572
|
status_message=(f"Processing {len(tool_results_buffer)} buffered tool results"))
|
|
574
573
|
for tool_call, result, tool_idx, context in tool_results_buffer:
|
|
575
574
|
if last_assistant_message_object: context.assistant_message_id = last_assistant_message_object[
|
|
@@ -580,7 +579,7 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
580
579
|
elif final_tool_calls_to_process and not config.execute_on_stream:
|
|
581
580
|
logging.info(
|
|
582
581
|
f"Executing {len(final_tool_calls_to_process)} tools ({config.tool_execution_strategy}) after stream")
|
|
583
|
-
|
|
582
|
+
self.root_span.event(name="executing_tools_after_stream", level="DEFAULT", status_message=(
|
|
584
583
|
f"Executing {len(final_tool_calls_to_process)} tools ({config.tool_execution_strategy}) after stream"))
|
|
585
584
|
results_list = await self._execute_tools(final_tool_calls_to_process,
|
|
586
585
|
config.tool_execution_strategy)
|
|
@@ -598,14 +597,14 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
598
597
|
tool_results_map[current_tool_idx] = (tc, res, context)
|
|
599
598
|
else:
|
|
600
599
|
logging.warning(f"Could not map result for tool index {current_tool_idx}")
|
|
601
|
-
|
|
600
|
+
self.root_span.event(name="could_not_map_result_for_tool_index", level="WARNING",
|
|
602
601
|
status_message=(f"Could not map result for tool index {current_tool_idx}"))
|
|
603
602
|
current_tool_idx += 1
|
|
604
603
|
|
|
605
604
|
# Save and Yield each result message
|
|
606
605
|
if tool_results_map:
|
|
607
606
|
logging.info(f"Saving and yielding {len(tool_results_map)} final tool result messages")
|
|
608
|
-
|
|
607
|
+
self.root_span.event(name="saving_and_yielding_final_tool_result_messages", level="DEFAULT",
|
|
609
608
|
status_message=(
|
|
610
609
|
f"Saving and yielding {len(tool_results_map)} final tool result messages"))
|
|
611
610
|
for tool_idx in sorted(tool_results_map.keys()):
|
|
@@ -640,7 +639,7 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
640
639
|
else:
|
|
641
640
|
logging.error(
|
|
642
641
|
f"Failed to save tool result for index {tool_idx}, not yielding result message.")
|
|
643
|
-
|
|
642
|
+
self.root_span.event(name="failed_to_save_tool_result_for_index", level="ERROR",
|
|
644
643
|
status_message=(
|
|
645
644
|
f"Failed to save tool result for index {tool_idx}, not yielding result message."))
|
|
646
645
|
# Optionally yield error status for saving failure?
|
|
@@ -658,7 +657,7 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
658
657
|
if agent_should_terminate:
|
|
659
658
|
logging.info(
|
|
660
659
|
"Agent termination requested after executing ask/complete tool. Stopping further processing.")
|
|
661
|
-
|
|
660
|
+
self.root_span.event(name="agent_termination_requested", level="DEFAULT",
|
|
662
661
|
status_message="Agent termination requested after executing ask/complete tool. Stopping further processing.")
|
|
663
662
|
|
|
664
663
|
# Set finish reason to indicate termination
|
|
@@ -719,7 +718,7 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
719
718
|
logging.info("Assistant response end saved for stream (before termination)")
|
|
720
719
|
except Exception as e:
|
|
721
720
|
logging.error(f"Error saving assistant response end for stream (before termination): {str(e)}")
|
|
722
|
-
|
|
721
|
+
self.root_span.event(name="error_saving_assistant_response_end_for_stream_before_termination",
|
|
723
722
|
level="ERROR", status_message=(
|
|
724
723
|
f"Error saving assistant response end for stream (before termination): {str(e)}"))
|
|
725
724
|
|
|
@@ -775,12 +774,12 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
775
774
|
logging.info("Assistant response end saved for stream")
|
|
776
775
|
except Exception as e:
|
|
777
776
|
logging.error(f"Error saving assistant response end for stream: {str(e)}")
|
|
778
|
-
|
|
777
|
+
self.root_span.event(name="error_saving_assistant_response_end_for_stream", level="ERROR",
|
|
779
778
|
status_message=(f"Error saving assistant response end for stream: {str(e)}"))
|
|
780
779
|
|
|
781
780
|
except Exception as e:
|
|
782
781
|
logging.error(f"Error processing stream: {str(e)}", exc_info=True)
|
|
783
|
-
|
|
782
|
+
self.root_span.event(name="error_processing_stream", level="ERROR",
|
|
784
783
|
status_message=(f"Error processing stream: {str(e)}"))
|
|
785
784
|
# Save and yield error status message
|
|
786
785
|
|
|
@@ -794,12 +793,12 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
794
793
|
if err_msg_obj: yield format_for_yield(err_msg_obj) # Yield the saved error message
|
|
795
794
|
# Re-raise the same exception (not a new one) to ensure proper error propagation
|
|
796
795
|
logging.critical(f"Re-raising error to stop further processing: {str(e)}")
|
|
797
|
-
|
|
796
|
+
self.root_span.event(name="re_raising_error_to_stop_further_processing", level="ERROR",
|
|
798
797
|
status_message=(f"Re-raising error to stop further processing: {str(e)}"))
|
|
799
798
|
else:
|
|
800
799
|
logging.error(f"AnthropicException - Overloaded detected - Falling back to OpenRouter: {str(e)}",
|
|
801
800
|
exc_info=True)
|
|
802
|
-
|
|
801
|
+
self.root_span.event(name="anthropic_exception_overloaded_detected", level="ERROR", status_message=(
|
|
803
802
|
f"AnthropicException - Overloaded detected - Falling back to OpenRouter: {str(e)}"))
|
|
804
803
|
raise # Use bare 'raise' to preserve the original exception with its traceback
|
|
805
804
|
|
|
@@ -822,5 +821,5 @@ class StreamTaskResponser(TaskResponseProcessor):
|
|
|
822
821
|
if end_msg_obj: yield format_for_yield(end_msg_obj)
|
|
823
822
|
except Exception as final_e:
|
|
824
823
|
logging.error(f"Error in finally block: {str(final_e)}", exc_info=True)
|
|
825
|
-
|
|
824
|
+
self.root_span.event(name="error_in_finally_block", level="ERROR",
|
|
826
825
|
status_message=(f"Error in finally block: {str(final_e)}"))
|