xgae 0.1.4__py3-none-any.whl → 0.1.6__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/{xga_base.py → engine_base.py} +12 -15
- xgae/engine/{xga_mcp_tool_box.py → mcp_tool_box.py} +6 -9
- xgae/engine/{xga_prompt_builder.py → prompt_builder.py} +3 -2
- xgae/engine/responser/non_stream_responser.py +110 -0
- xgae/engine/responser/{xga_responser_base.py → responser_base.py} +103 -228
- xgae/engine/responser/{xga_stream_responser.py → stream_responser.py} +94 -55
- xgae/engine/task_engine.py +360 -0
- xgae/utils/__init__.py +13 -0
- xgae/utils/{utils.py → misc.py} +0 -8
- xgae/utils/setup_env.py +51 -66
- xgae/utils/xml_tool_parser.py +4 -7
- {xgae-0.1.4.dist-info → xgae-0.1.6.dist-info}/METADATA +1 -1
- xgae-0.1.6.dist-info/RECORD +17 -0
- xgae/engine/responser/xga_non_stream_responser.py +0 -213
- xgae/engine/xga_engine.py +0 -278
- xgae-0.1.4.dist-info/RECORD +0 -16
- {xgae-0.1.4.dist-info → xgae-0.1.6.dist-info}/WHEEL +0 -0
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
|
-
|
|
5
|
-
from typing import List, Dict, Any, Optional, Tuple, Union, Literal, Callable,TypedDict,AsyncGenerator
|
|
4
|
+
|
|
6
5
|
from abc import ABC, abstractmethod
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import List, Dict, Any, Optional, Tuple, Union, Literal, Callable, TypedDict, AsyncGenerator
|
|
7
8
|
|
|
8
|
-
from xgae.engine.
|
|
9
|
-
|
|
10
|
-
from xgae.utils.json_helpers import
|
|
11
|
-
safe_json_parse,
|
|
12
|
-
format_for_yield
|
|
13
|
-
)
|
|
9
|
+
from xgae.engine.engine_base import XGAToolResult, XGAToolBox
|
|
10
|
+
from xgae.utils import langfuse
|
|
11
|
+
from xgae.utils.json_helpers import safe_json_parse,format_for_yield
|
|
14
12
|
from xgae.utils.xml_tool_parser import XMLToolParser
|
|
15
13
|
|
|
16
14
|
# Type alias for XML result adding strategy
|
|
@@ -19,36 +17,24 @@ XmlAddingStrategy = Literal["user_message", "assistant_message", "inline_edit"]
|
|
|
19
17
|
# Type alias for tool execution strategy
|
|
20
18
|
ToolExecutionStrategy = Literal["sequential", "parallel"]
|
|
21
19
|
|
|
22
|
-
class
|
|
20
|
+
class TaskResponserContext(TypedDict, total=False):
|
|
23
21
|
is_stream: bool
|
|
24
22
|
task_id: str
|
|
25
23
|
task_run_id: str
|
|
26
24
|
trace_id: str
|
|
27
25
|
model_name: str
|
|
28
|
-
max_xml_tool_calls: int
|
|
29
|
-
|
|
26
|
+
max_xml_tool_calls: int # LLM generate max_xml_tool limit, 0 is no limit
|
|
27
|
+
add_response_msg_func: Callable
|
|
30
28
|
tool_box: XGAToolBox
|
|
31
|
-
tool_execution_strategy:
|
|
32
|
-
xml_adding_strategy:
|
|
29
|
+
tool_execution_strategy: ToolExecutionStrategy
|
|
30
|
+
xml_adding_strategy: XmlAddingStrategy
|
|
33
31
|
|
|
34
32
|
|
|
35
33
|
class TaskRunContinuousState(TypedDict, total=False):
|
|
36
34
|
accumulated_content: str
|
|
37
35
|
auto_continue_count: int
|
|
38
36
|
auto_continue: bool
|
|
39
|
-
|
|
40
|
-
class Span:
|
|
41
|
-
def end(self, **kwargs):
|
|
42
|
-
pass
|
|
43
|
-
|
|
44
|
-
class Trace:
|
|
45
|
-
def event(self, **kwargs):
|
|
46
|
-
pass
|
|
47
|
-
|
|
48
|
-
def span(self, **kwargs):
|
|
49
|
-
return Span()
|
|
50
|
-
|
|
51
|
-
|
|
37
|
+
max_auto_run: int
|
|
52
38
|
|
|
53
39
|
@dataclass
|
|
54
40
|
class ToolExecutionContext:
|
|
@@ -63,62 +49,28 @@ class ToolExecutionContext:
|
|
|
63
49
|
parsing_details: Optional[Dict[str, Any]] = None
|
|
64
50
|
|
|
65
51
|
|
|
66
|
-
@dataclass
|
|
67
|
-
class ProcessorConfig:
|
|
68
|
-
"""
|
|
69
|
-
Configuration for response processing and tool execution.
|
|
70
|
-
|
|
71
|
-
This class controls how the LLM's responses are processed, including how tool calls
|
|
72
|
-
are detected, executed, and their results handled.
|
|
73
|
-
|
|
74
|
-
Attributes:
|
|
75
|
-
xml_tool_calling: Enable XML-based tool call detection (<tool>...</tool>)
|
|
76
|
-
native_tool_calling: Enable OpenAI-style function calling format
|
|
77
|
-
execute_tools: Whether to automatically execute detected tool calls
|
|
78
|
-
execute_on_stream: For streaming, execute tools as they appear vs. at the end
|
|
79
|
-
tool_execution_strategy: How to execute multiple tools ("sequential" or "parallel")
|
|
80
|
-
xml_adding_strategy: How to add XML tool results to the conversation
|
|
81
|
-
max_xml_tool_calls: Maximum number of XML tool calls to process (0 = no limit)
|
|
82
|
-
"""
|
|
83
|
-
|
|
84
|
-
xml_tool_calling: bool = True
|
|
85
|
-
native_tool_calling: bool = False
|
|
86
|
-
|
|
87
|
-
execute_tools: bool = True
|
|
88
|
-
execute_on_stream: bool = False
|
|
89
|
-
tool_execution_strategy: ToolExecutionStrategy = "sequential"
|
|
90
|
-
xml_adding_strategy: XmlAddingStrategy = "assistant_message"
|
|
91
|
-
max_xml_tool_calls: int = 0 # 0 means no limit
|
|
92
|
-
|
|
93
|
-
def __post_init__(self):
|
|
94
|
-
"""Validate configuration after initialization."""
|
|
95
|
-
if self.xml_tool_calling is False and self.native_tool_calling is False and self.execute_tools:
|
|
96
|
-
raise ValueError(
|
|
97
|
-
"At least one tool calling format (XML or native) must be enabled if execute_tools is True")
|
|
98
|
-
|
|
99
|
-
if self.xml_adding_strategy not in ["user_message", "assistant_message", "inline_edit"]:
|
|
100
|
-
raise ValueError("xml_adding_strategy must be 'user_message', 'assistant_message', or 'inline_edit'")
|
|
101
|
-
|
|
102
|
-
if self.max_xml_tool_calls < 0:
|
|
103
|
-
raise ValueError("max_xml_tool_calls must be a non-negative integer (0 = no limit)")
|
|
104
|
-
|
|
105
|
-
|
|
106
52
|
class TaskResponseProcessor(ABC):
|
|
107
|
-
def __init__(self, response_context:
|
|
53
|
+
def __init__(self, response_context: TaskResponserContext):
|
|
108
54
|
self.response_context = response_context
|
|
109
55
|
|
|
110
|
-
self.task_id = response_context.get("task_id"
|
|
111
|
-
self.task_run_id = response_context.get("task_run_id"
|
|
56
|
+
self.task_id = response_context.get("task_id")
|
|
57
|
+
self.task_run_id = response_context.get("task_run_id")
|
|
58
|
+
self.tool_execution_strategy = self.response_context.get("tool_execution_strategy", "parallel")
|
|
59
|
+
self.xml_adding_strategy = self.response_context.get("xml_adding_strategy", "user_message")
|
|
60
|
+
self.max_xml_tool_calls = self.response_context.get("max_xml_tool_calls", 0)
|
|
112
61
|
|
|
113
|
-
|
|
114
|
-
|
|
62
|
+
self.trace_context = {
|
|
63
|
+
"trace_id": self.response_context.get("trace_id"),
|
|
64
|
+
"parent_span_id": None
|
|
65
|
+
}
|
|
115
66
|
|
|
116
|
-
self.
|
|
117
|
-
self._add_message_with_agent_info = self.add_message
|
|
118
|
-
self.tool_box = response_context.get("tool_box")
|
|
67
|
+
self.add_response_message = response_context.get("add_response_msg_func")
|
|
119
68
|
|
|
69
|
+
self.tool_box = response_context.get("tool_box")
|
|
120
70
|
self.xml_parser = XMLToolParser()
|
|
121
71
|
|
|
72
|
+
|
|
73
|
+
|
|
122
74
|
@abstractmethod
|
|
123
75
|
async def process_response(self,
|
|
124
76
|
llm_response: AsyncGenerator,
|
|
@@ -127,16 +79,6 @@ class TaskResponseProcessor(ABC):
|
|
|
127
79
|
) -> AsyncGenerator[Dict[str, Any], None]:
|
|
128
80
|
pass
|
|
129
81
|
|
|
130
|
-
async def _yield_message(self, message_obj: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
|
131
|
-
"""Helper to yield a message with proper formatting.
|
|
132
|
-
|
|
133
|
-
Ensures that content and metadata are JSON strings for client compatibility.
|
|
134
|
-
"""
|
|
135
|
-
if message_obj:
|
|
136
|
-
return format_for_yield(message_obj)
|
|
137
|
-
return None
|
|
138
|
-
|
|
139
|
-
|
|
140
82
|
|
|
141
83
|
def _extract_xml_chunks(self, content: str) -> List[str]:
|
|
142
84
|
"""Extract complete XML chunks using start and end pattern matching."""
|
|
@@ -231,12 +173,12 @@ class TaskResponseProcessor(ABC):
|
|
|
231
173
|
except Exception as e:
|
|
232
174
|
logging.error(f"Error extracting XML chunks: {e}")
|
|
233
175
|
logging.error(f"Content was: {content}")
|
|
234
|
-
self.
|
|
176
|
+
langfuse.create_event(trace_context=self.trace_context, name="error_extracting_xml_chunks", level="ERROR",
|
|
235
177
|
status_message=(f"Error extracting XML chunks: {e}"), metadata={"content": content})
|
|
236
178
|
|
|
237
179
|
return chunks
|
|
238
180
|
|
|
239
|
-
def _parse_xml_tool_call(self, xml_chunk: str) ->
|
|
181
|
+
def _parse_xml_tool_call(self, xml_chunk: str) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
|
240
182
|
"""Parse XML chunk into tool call format and return parsing details.
|
|
241
183
|
|
|
242
184
|
Returns:
|
|
@@ -256,6 +198,9 @@ class TaskResponseProcessor(ABC):
|
|
|
256
198
|
|
|
257
199
|
# Take the first tool call (should only be one per chunk)
|
|
258
200
|
xml_tool_call = parsed_calls[0]
|
|
201
|
+
if not xml_tool_call.function_name:
|
|
202
|
+
logging.error(f"xml_tool_call function name is empty: {xml_tool_call}")
|
|
203
|
+
return None
|
|
259
204
|
|
|
260
205
|
# Convert to the expected format
|
|
261
206
|
tool_call = {
|
|
@@ -278,7 +223,7 @@ class TaskResponseProcessor(ABC):
|
|
|
278
223
|
except Exception as e:
|
|
279
224
|
logging.error(f"Error parsing XML chunk: {e}")
|
|
280
225
|
logging.error(f"XML chunk was: {xml_chunk}")
|
|
281
|
-
self.
|
|
226
|
+
langfuse.create_event(trace_context=self.trace_context, name="error_parsing_xml_chunk", level="ERROR",
|
|
282
227
|
status_message=(f"Error parsing XML chunk: {e}"), metadata={"xml_chunk": xml_chunk})
|
|
283
228
|
return None
|
|
284
229
|
|
|
@@ -304,21 +249,22 @@ class TaskResponseProcessor(ABC):
|
|
|
304
249
|
|
|
305
250
|
except Exception as e:
|
|
306
251
|
logging.error(f"Error parsing XML tool calls: {e}", exc_info=True)
|
|
307
|
-
self.
|
|
252
|
+
langfuse.create_event(trace_context=self.trace_context, name="error_parsing_xml_tool_calls", level="ERROR",
|
|
308
253
|
status_message=(f"Error parsing XML tool calls: {e}"), metadata={"content": content})
|
|
309
254
|
|
|
310
255
|
return parsed_data
|
|
311
256
|
|
|
312
|
-
|
|
257
|
+
|
|
313
258
|
async def _execute_tool(self, tool_call: Dict[str, Any]) -> XGAToolResult:
|
|
314
259
|
"""Execute a single tool call and return the result."""
|
|
315
|
-
span = self.
|
|
260
|
+
span = langfuse.start_span(trace_context=self.trace_context, name=f"execute_tool.{tool_call['function_name']}", input=tool_call["arguments"])
|
|
261
|
+
self.trace_context["parent_span_id"] = span.id
|
|
316
262
|
try:
|
|
317
263
|
function_name = tool_call["function_name"]
|
|
318
264
|
arguments = tool_call["arguments"]
|
|
319
265
|
|
|
320
266
|
logging.info(f"Executing tool: {function_name} with arguments: {arguments}")
|
|
321
|
-
self.
|
|
267
|
+
langfuse.create_event(trace_context=self.trace_context, name="executing_tool", level="DEFAULT",
|
|
322
268
|
status_message=(f"Executing tool: {function_name} with arguments: {arguments}"))
|
|
323
269
|
|
|
324
270
|
if isinstance(arguments, str):
|
|
@@ -327,21 +273,21 @@ class TaskResponseProcessor(ABC):
|
|
|
327
273
|
except json.JSONDecodeError:
|
|
328
274
|
arguments = {"text": arguments} # @todo modify
|
|
329
275
|
|
|
330
|
-
# Get available functions from tool registry
|
|
331
|
-
#available_functions = self.tool_registry.get_available_functions()
|
|
332
276
|
result = None
|
|
333
|
-
|
|
334
|
-
if function_name in
|
|
277
|
+
available_tool_names = self.tool_box.get_task_tool_names(self.task_id)
|
|
278
|
+
if function_name in available_tool_names:
|
|
335
279
|
result = await self.tool_box.call_tool(self.task_id, function_name, arguments)
|
|
336
280
|
else:
|
|
337
281
|
logging.error(f"Tool function '{function_name}' not found in registry")
|
|
338
282
|
result = XGAToolResult(success=False, output=f"Tool function '{function_name}' not found")
|
|
339
283
|
logging.info(f"Tool execution complete: {function_name} -> {result}")
|
|
340
|
-
|
|
284
|
+
langfuse.update_current_span(status_message="tool_executed", output=result)
|
|
285
|
+
|
|
341
286
|
return result
|
|
342
287
|
except Exception as e:
|
|
343
288
|
logging.error(f"Error executing tool {tool_call['function_name']}: {str(e)}", exc_info=True)
|
|
344
|
-
|
|
289
|
+
|
|
290
|
+
langfuse.update_current_span(status_message="tool_execution_error", output=f"Error executing tool: {str(e)}", level="ERROR")
|
|
345
291
|
return XGAToolResult(success=False, output=f"Error executing tool: {str(e)}")
|
|
346
292
|
|
|
347
293
|
async def _execute_tools(
|
|
@@ -350,7 +296,7 @@ class TaskResponseProcessor(ABC):
|
|
|
350
296
|
execution_strategy: ToolExecutionStrategy = "sequential"
|
|
351
297
|
) -> List[Tuple[Dict[str, Any], XGAToolResult]]:
|
|
352
298
|
logging.info(f"Executing {len(tool_calls)} tools with strategy: {execution_strategy}")
|
|
353
|
-
self.
|
|
299
|
+
langfuse.create_event(trace_context=self.trace_context, name="executing_tools_with_strategy", level="DEFAULT",
|
|
354
300
|
status_message=(f"Executing {len(tool_calls)} tools with strategy: {execution_strategy}"))
|
|
355
301
|
|
|
356
302
|
if execution_strategy == "sequential":
|
|
@@ -376,54 +322,42 @@ class TaskResponseProcessor(ABC):
|
|
|
376
322
|
"""
|
|
377
323
|
if not tool_calls:
|
|
378
324
|
return []
|
|
325
|
+
tool_names = [t.get('function_name', 'unknown') for t in tool_calls]
|
|
326
|
+
logging.info(f"Executing {len(tool_calls)} tools sequentially: {tool_names}")
|
|
327
|
+
langfuse.create_event(trace_context=self.trace_context, name="executing_tools_sequentially", level="DEFAULT",
|
|
328
|
+
status_message=(f"Executing {len(tool_calls)} tools sequentially: {tool_names}"))
|
|
379
329
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
status_message=(f"Executing {len(tool_calls)} tools sequentially: {tool_names}"))
|
|
385
|
-
|
|
386
|
-
results = []
|
|
387
|
-
for index, tool_call in enumerate(tool_calls):
|
|
388
|
-
tool_name = tool_call.get('function_name', 'unknown')
|
|
389
|
-
logging.debug(f"Executing tool {index + 1}/{len(tool_calls)}: {tool_name}")
|
|
330
|
+
results = []
|
|
331
|
+
for index, tool_call in enumerate(tool_calls):
|
|
332
|
+
tool_name = tool_call.get('function_name', 'unknown')
|
|
333
|
+
logging.debug(f"Executing tool {index + 1}/{len(tool_calls)}: {tool_name}")
|
|
390
334
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
335
|
+
try:
|
|
336
|
+
result = await self._execute_tool(tool_call)
|
|
337
|
+
results.append((tool_call, result))
|
|
338
|
+
logging.debug(f"Completed tool {tool_name} with success={result.success}")
|
|
339
|
+
|
|
340
|
+
# Check if this is a terminating tool (ask or complete)
|
|
341
|
+
if tool_name in ['ask', 'complete']:
|
|
342
|
+
logging.info(f"Terminating tool '{tool_name}' executed. Stopping further tool execution.")
|
|
343
|
+
langfuse.create_event(trace_context=self.trace_context, name="terminating_tool_executed",
|
|
344
|
+
level="DEFAULT", status_message=(
|
|
400
345
|
f"Terminating tool '{tool_name}' executed. Stopping further tool execution."))
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
except Exception as e:
|
|
404
|
-
logging.error(f"Error executing tool {tool_name}: {str(e)}")
|
|
405
|
-
self.trace.event(name="error_executing_tool", level="ERROR",
|
|
406
|
-
status_message=(f"Error executing tool {tool_name}: {str(e)}"))
|
|
407
|
-
error_result = XGAToolResult(success=False, output=f"Error executing tool: {str(e)}")
|
|
408
|
-
results.append((tool_call, error_result))
|
|
409
|
-
|
|
410
|
-
logging.info(f"Sequential execution completed for {len(results)} tools (out of {len(tool_calls)} total)")
|
|
411
|
-
self.trace.event(name="sequential_execution_completed", level="DEFAULT", status_message=(
|
|
412
|
-
f"Sequential execution completed for {len(results)} tools (out of {len(tool_calls)} total)"))
|
|
413
|
-
return results
|
|
346
|
+
break # Stop executing remaining tools
|
|
414
347
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
348
|
+
except Exception as e:
|
|
349
|
+
logging.error(f"Error executing tool {tool_name}: {str(e)}")
|
|
350
|
+
langfuse.create_event(trace_context=self.trace_context, name="error_executing_tool", level="ERROR",
|
|
351
|
+
status_message=(f"Error executing tool {tool_name}: {str(e)}"))
|
|
352
|
+
error_result = XGAToolResult(success=False, output=f"Error executing tool: {str(e)}")
|
|
353
|
+
results.append((tool_call, error_result))
|
|
421
354
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
355
|
+
logging.info(f"Sequential execution completed for {len(results)} tools (out of {len(tool_calls)} total)")
|
|
356
|
+
langfuse.create_event(trace_context=self.trace_context, name="sequential_execution_completed", level="DEFAULT",
|
|
357
|
+
status_message=(
|
|
358
|
+
f"Sequential execution completed for {len(results)} tools (out of {len(tool_calls)} total)"))
|
|
359
|
+
return results
|
|
425
360
|
|
|
426
|
-
return completed_results + error_results
|
|
427
361
|
|
|
428
362
|
async def _execute_tools_in_parallel(self, tool_calls: List[Dict[str, Any]]) -> List[Tuple[Dict[str, Any], XGAToolResult]]:
|
|
429
363
|
if not tool_calls:
|
|
@@ -432,7 +366,7 @@ class TaskResponseProcessor(ABC):
|
|
|
432
366
|
try:
|
|
433
367
|
tool_names = [t.get('function_name', 'unknown') for t in tool_calls]
|
|
434
368
|
logging.info(f"Executing {len(tool_calls)} tools in parallel: {tool_names}")
|
|
435
|
-
self.
|
|
369
|
+
langfuse.create_event(trace_context=self.trace_context, name="executing_tools_in_parallel", level="DEFAULT",
|
|
436
370
|
status_message=(f"Executing {len(tool_calls)} tools in parallel: {tool_names}"))
|
|
437
371
|
|
|
438
372
|
# Create tasks for all tool calls
|
|
@@ -446,7 +380,7 @@ class TaskResponseProcessor(ABC):
|
|
|
446
380
|
for i, (tool_call, result) in enumerate(zip(tool_calls, results)):
|
|
447
381
|
if isinstance(result, Exception):
|
|
448
382
|
logging.error(f"Error executing tool {tool_call.get('function_name', 'unknown')}: {str(result)}")
|
|
449
|
-
self.
|
|
383
|
+
langfuse.create_event(trace_context=self.trace_context, name="error_executing_tool", level="ERROR", status_message=(
|
|
450
384
|
f"Error executing tool {tool_call.get('function_name', 'unknown')}: {str(result)}"))
|
|
451
385
|
# Create error result
|
|
452
386
|
error_result = XGAToolResult(success=False, output=f"Error executing tool: {str(result)}")
|
|
@@ -455,24 +389,23 @@ class TaskResponseProcessor(ABC):
|
|
|
455
389
|
processed_results.append((tool_call, result))
|
|
456
390
|
|
|
457
391
|
logging.info(f"Parallel execution completed for {len(tool_calls)} tools")
|
|
458
|
-
self.
|
|
392
|
+
langfuse.create_event(trace_context=self.trace_context, name="parallel_execution_completed", level="DEFAULT",
|
|
459
393
|
status_message=(f"Parallel execution completed for {len(tool_calls)} tools"))
|
|
460
394
|
return processed_results
|
|
461
395
|
|
|
462
396
|
except Exception as e:
|
|
463
397
|
logging.error(f"Error in parallel tool execution: {str(e)}", exc_info=True)
|
|
464
|
-
self.
|
|
398
|
+
langfuse.create_event(trace_context=self.trace_context, name="error_in_parallel_tool_execution", level="ERROR",
|
|
465
399
|
status_message=(f"Error in parallel tool execution: {str(e)}"))
|
|
466
400
|
# Return error results for all tools if the gather itself fails
|
|
467
401
|
return [(tool_call, XGAToolResult(success=False, output=f"Execution error: {str(e)}"))
|
|
468
402
|
for tool_call in tool_calls]
|
|
469
403
|
|
|
470
|
-
def
|
|
404
|
+
def _add_tool_messsage(
|
|
471
405
|
self,
|
|
472
|
-
thread_id: str,
|
|
473
406
|
tool_call: Dict[str, Any],
|
|
474
407
|
result: XGAToolResult,
|
|
475
|
-
strategy:
|
|
408
|
+
strategy: XmlAddingStrategy = "assistant_message",
|
|
476
409
|
assistant_message_id: Optional[str] = None,
|
|
477
410
|
parsing_details: Optional[Dict[str, Any]] = None
|
|
478
411
|
) -> Optional[Dict[str, Any]]: # Return the full message object
|
|
@@ -484,63 +417,16 @@ class TaskResponseProcessor(ABC):
|
|
|
484
417
|
if assistant_message_id:
|
|
485
418
|
metadata["assistant_message_id"] = assistant_message_id
|
|
486
419
|
logging.info(f"Linking tool result to assistant message: {assistant_message_id}")
|
|
487
|
-
self.
|
|
420
|
+
langfuse.create_event(trace_context=self.trace_context, name="linking_tool_result_to_assistant_message", level="DEFAULT",
|
|
488
421
|
status_message=(f"Linking tool result to assistant message: {assistant_message_id}"))
|
|
489
422
|
|
|
490
423
|
# --- Add parsing details to metadata if available ---
|
|
491
424
|
if parsing_details:
|
|
492
425
|
metadata["parsing_details"] = parsing_details
|
|
493
426
|
logging.info("Adding parsing_details to tool result metadata")
|
|
494
|
-
self.
|
|
427
|
+
langfuse.create_event(trace_context=self.trace_context, name="adding_parsing_details_to_tool_result_metadata", level="DEFAULT",
|
|
495
428
|
status_message=(f"Adding parsing_details to tool result metadata"),
|
|
496
429
|
metadata={"parsing_details": parsing_details})
|
|
497
|
-
# ---
|
|
498
|
-
|
|
499
|
-
# Check if this is a native function call (has id field)
|
|
500
|
-
if "id" in tool_call:
|
|
501
|
-
# Format as a proper tool message according to OpenAI spec
|
|
502
|
-
function_name = tool_call.get("function_name", "")
|
|
503
|
-
|
|
504
|
-
# Format the tool result content - tool role needs string content
|
|
505
|
-
if isinstance(result, str):
|
|
506
|
-
content = result
|
|
507
|
-
elif hasattr(result, 'output'):
|
|
508
|
-
# If it's a XGAToolResult object
|
|
509
|
-
if isinstance(result.output, dict) or isinstance(result.output, list):
|
|
510
|
-
# If output is already a dict or list, convert to JSON string
|
|
511
|
-
content = json.dumps(result.output)
|
|
512
|
-
else:
|
|
513
|
-
# Otherwise just use the string representation
|
|
514
|
-
content = str(result.output)
|
|
515
|
-
else:
|
|
516
|
-
# Fallback to string representation of the whole result
|
|
517
|
-
content = str(result)
|
|
518
|
-
|
|
519
|
-
logging.info(f"Formatted tool result content: {content[:100]}...")
|
|
520
|
-
self.trace.event(name="formatted_tool_result_content", level="DEFAULT",
|
|
521
|
-
status_message=(f"Formatted tool result content: {content[:100]}..."))
|
|
522
|
-
|
|
523
|
-
# Create the tool response message with proper format
|
|
524
|
-
tool_message = {
|
|
525
|
-
"role": "tool",
|
|
526
|
-
"tool_call_id": tool_call["id"],
|
|
527
|
-
"name": function_name,
|
|
528
|
-
"content": content
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
logging.info(f"Adding native tool result for tool_call_id={tool_call['id']} with role=tool")
|
|
532
|
-
self.trace.event(name="adding_native_tool_result_for_tool_call_id", level="DEFAULT", status_message=(
|
|
533
|
-
f"Adding native tool result for tool_call_id={tool_call['id']} with role=tool"))
|
|
534
|
-
|
|
535
|
-
# Add as a tool message to the conversation history
|
|
536
|
-
# This makes the result visible to the LLM in the next turn
|
|
537
|
-
message_obj = self.add_message(
|
|
538
|
-
type="tool", # Special type for tool responses
|
|
539
|
-
content=tool_message,
|
|
540
|
-
is_llm_message=True,
|
|
541
|
-
metadata=metadata
|
|
542
|
-
)
|
|
543
|
-
return message_obj # Return the full message object
|
|
544
430
|
|
|
545
431
|
# For XML and other non-native tools, use the new structured format
|
|
546
432
|
# Determine message role based on strategy
|
|
@@ -566,7 +452,7 @@ class TaskResponseProcessor(ABC):
|
|
|
566
452
|
metadata = {}
|
|
567
453
|
metadata['frontend_content'] = structured_result_for_frontend
|
|
568
454
|
|
|
569
|
-
message_obj = self.
|
|
455
|
+
message_obj = self.add_response_message(
|
|
570
456
|
type="tool",
|
|
571
457
|
content=result_message_for_llm, # Save the LLM-friendly version
|
|
572
458
|
is_llm_message=True,
|
|
@@ -585,7 +471,7 @@ class TaskResponseProcessor(ABC):
|
|
|
585
471
|
return message_obj # Return the modified message object
|
|
586
472
|
except Exception as e:
|
|
587
473
|
logging.error(f"Error adding tool result: {str(e)}", exc_info=True)
|
|
588
|
-
self.
|
|
474
|
+
langfuse.create_event(trace_context=self.trace_context, name="error_adding_tool_result", level="ERROR",
|
|
589
475
|
status_message=(f"Error adding tool result: {str(e)}"),
|
|
590
476
|
metadata={"tool_call": tool_call, "result": result, "strategy": strategy,
|
|
591
477
|
"assistant_message_id": assistant_message_id,
|
|
@@ -596,7 +482,7 @@ class TaskResponseProcessor(ABC):
|
|
|
596
482
|
"role": "user",
|
|
597
483
|
"content": str(result)
|
|
598
484
|
}
|
|
599
|
-
message_obj = self.
|
|
485
|
+
message_obj = self.add_response_message(
|
|
600
486
|
type="tool",
|
|
601
487
|
content=fallback_message,
|
|
602
488
|
is_llm_message=True,
|
|
@@ -605,7 +491,7 @@ class TaskResponseProcessor(ABC):
|
|
|
605
491
|
return message_obj # Return the full message object
|
|
606
492
|
except Exception as e2:
|
|
607
493
|
logging.error(f"Failed even with fallback message: {str(e2)}", exc_info=True)
|
|
608
|
-
self.
|
|
494
|
+
langfuse.create_event(trace_context=self.trace_context, name="failed_even_with_fallback_message", level="ERROR",
|
|
609
495
|
status_message=(f"Failed even with fallback message: {str(e2)}"),
|
|
610
496
|
metadata={"tool_call": tool_call, "result": result, "strategy": strategy,
|
|
611
497
|
"assistant_message_id": assistant_message_id,
|
|
@@ -670,38 +556,29 @@ class TaskResponseProcessor(ABC):
|
|
|
670
556
|
)
|
|
671
557
|
|
|
672
558
|
# Set function_name and xml_tag_name fields
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
context.function_name = tool_call.get("function_name", tool_call["xml_tag_name"])
|
|
676
|
-
else:
|
|
677
|
-
# For non-XML tools, use function name directly
|
|
678
|
-
context.function_name = tool_call.get("function_name", "unknown")
|
|
679
|
-
context.xml_tag_name = None
|
|
559
|
+
context.xml_tag_name = tool_call["xml_tag_name"]
|
|
560
|
+
context.function_name = tool_call["function_name"]
|
|
680
561
|
|
|
681
562
|
return context
|
|
682
563
|
|
|
683
|
-
def
|
|
684
|
-
Optional[Dict[str, Any]]:
|
|
564
|
+
def _add_tool_start_message(self, context: ToolExecutionContext) -> Optional[Dict[str, Any]]:
|
|
685
565
|
"""Formats, saves, and returns a tool started status message."""
|
|
686
566
|
tool_name = context.xml_tag_name or context.function_name
|
|
687
567
|
content = {
|
|
688
568
|
"role": "assistant", "status_type": "tool_started",
|
|
689
569
|
"function_name": context.function_name, "xml_tag_name": context.xml_tag_name,
|
|
690
|
-
"message": f"Starting execution of {tool_name}", "tool_index": context.tool_index
|
|
691
|
-
"tool_call_id": context.tool_call.get("id") # Include tool_call ID if native
|
|
570
|
+
"message": f"Starting execution of {tool_name}", "tool_index": context.tool_index # Include tool_call ID if native
|
|
692
571
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
type="status", content=content, is_llm_message=False
|
|
572
|
+
|
|
573
|
+
return self.add_response_message(
|
|
574
|
+
type="status", content=content, is_llm_message=False
|
|
696
575
|
)
|
|
697
|
-
return saved_message_obj # Return the full object (or None if saving failed)
|
|
698
576
|
|
|
699
|
-
def
|
|
700
|
-
thread_id: str, thread_run_id: str) -> Optional[Dict[str, Any]]:
|
|
577
|
+
def _add_tool_completed_message(self, context: ToolExecutionContext, tool_message_id: Optional[str]) -> Optional[Dict[str, Any]]:
|
|
701
578
|
"""Formats, saves, and returns a tool completed/failed status message."""
|
|
702
579
|
if not context.result:
|
|
703
580
|
# Delegate to error saving if result is missing (e.g., execution failed)
|
|
704
|
-
return self.
|
|
581
|
+
return self._add_tool_error_message(context)
|
|
705
582
|
|
|
706
583
|
tool_name = context.xml_tag_name or context.function_name
|
|
707
584
|
status_type = "tool_completed" if context.result.success else "tool_failed"
|
|
@@ -713,7 +590,7 @@ class TaskResponseProcessor(ABC):
|
|
|
713
590
|
"message": message_text, "tool_index": context.tool_index,
|
|
714
591
|
"tool_call_id": context.tool_call.get("id")
|
|
715
592
|
}
|
|
716
|
-
metadata = {
|
|
593
|
+
metadata = {}
|
|
717
594
|
# Add the *actual* tool result message ID to the metadata if available and successful
|
|
718
595
|
if context.result.success and tool_message_id:
|
|
719
596
|
metadata["linked_tool_result_message_id"] = tool_message_id
|
|
@@ -722,17 +599,15 @@ class TaskResponseProcessor(ABC):
|
|
|
722
599
|
if context.function_name in ['ask', 'complete']:
|
|
723
600
|
metadata["agent_should_terminate"] = "true"
|
|
724
601
|
logging.info(f"Marking tool status for '{context.function_name}' with termination signal.")
|
|
725
|
-
self.
|
|
602
|
+
langfuse.create_event(trace_context=self.trace_context, name="marking_tool_status_for_termination", level="DEFAULT", status_message=(
|
|
726
603
|
f"Marking tool status for '{context.function_name}' with termination signal."))
|
|
727
604
|
# <<< END ADDED >>>
|
|
728
605
|
|
|
729
|
-
|
|
606
|
+
return self.add_response_message(
|
|
730
607
|
type="status", content=content, is_llm_message=False, metadata=metadata
|
|
731
608
|
)
|
|
732
|
-
return saved_message_obj
|
|
733
609
|
|
|
734
|
-
def
|
|
735
|
-
Optional[Dict[str, Any]]:
|
|
610
|
+
def _add_tool_error_message(self, context: ToolExecutionContext) -> Optional[Dict[str, Any]]:
|
|
736
611
|
"""Formats, saves, and returns a tool error status message."""
|
|
737
612
|
error_msg = str(context.error) if context.error else "Unknown error during tool execution"
|
|
738
613
|
tool_name = context.xml_tag_name or context.function_name
|
|
@@ -743,9 +618,9 @@ class TaskResponseProcessor(ABC):
|
|
|
743
618
|
"tool_index": context.tool_index,
|
|
744
619
|
"tool_call_id": context.tool_call.get("id")
|
|
745
620
|
}
|
|
746
|
-
|
|
621
|
+
|
|
747
622
|
# Save the status message with is_llm_message=False
|
|
748
|
-
|
|
749
|
-
type="status", content=content, is_llm_message=False
|
|
623
|
+
return self.add_response_message(
|
|
624
|
+
type="status", content=content, is_llm_message=False
|
|
750
625
|
)
|
|
751
|
-
|
|
626
|
+
|