xgae 0.1.5__py3-none-any.whl → 0.1.7__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} +6 -9
- xgae/engine/{xga_mcp_tool_box.py → mcp_tool_box.py} +11 -5
- xgae/engine/{xga_prompt_builder.py → prompt_builder.py} +3 -2
- xgae/engine/responser/non_stream_responser.py +108 -0
- xgae/engine/responser/{xga_responser_base.py → responser_base.py} +124 -218
- xgae/engine/responser/{xga_stream_responser.py → stream_responser.py} +51 -55
- xgae/engine/{xga_engine.py → task_engine.py} +166 -146
- xgae/tools/without_general_tools_app.py +48 -0
- xgae/utils/__init__.py +13 -0
- xgae/utils/llm_client.py +8 -3
- xgae/utils/{utils.py → misc.py} +0 -8
- xgae/utils/setup_env.py +53 -66
- xgae/utils/xml_tool_parser.py +4 -7
- {xgae-0.1.5.dist-info → xgae-0.1.7.dist-info}/METADATA +1 -1
- xgae-0.1.7.dist-info/RECORD +19 -0
- xgae-0.1.7.dist-info/entry_points.txt +2 -0
- xgae/engine/responser/xga_non_stream_responser.py +0 -216
- xgae-0.1.5.dist-info/RECORD +0 -16
- {xgae-0.1.5.dist-info → xgae-0.1.7.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,17 +17,18 @@ 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
|
|
25
|
+
root_span_id: str
|
|
27
26
|
model_name: str
|
|
28
|
-
max_xml_tool_calls: int
|
|
27
|
+
max_xml_tool_calls: int # LLM generate max_xml_tool limit, 0 is no limit
|
|
29
28
|
add_response_msg_func: Callable
|
|
30
29
|
tool_box: XGAToolBox
|
|
31
|
-
tool_execution_strategy:
|
|
32
|
-
xml_adding_strategy:
|
|
30
|
+
tool_execution_strategy: ToolExecutionStrategy
|
|
31
|
+
xml_adding_strategy: XmlAddingStrategy
|
|
33
32
|
|
|
34
33
|
|
|
35
34
|
class TaskRunContinuousState(TypedDict, total=False):
|
|
@@ -37,17 +36,6 @@ class TaskRunContinuousState(TypedDict, total=False):
|
|
|
37
36
|
auto_continue_count: int
|
|
38
37
|
auto_continue: bool
|
|
39
38
|
|
|
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
39
|
|
|
52
40
|
@dataclass
|
|
53
41
|
class ToolExecutionContext:
|
|
@@ -63,21 +51,27 @@ class ToolExecutionContext:
|
|
|
63
51
|
|
|
64
52
|
|
|
65
53
|
class TaskResponseProcessor(ABC):
|
|
66
|
-
def __init__(self, response_context:
|
|
54
|
+
def __init__(self, response_context: TaskResponserContext):
|
|
67
55
|
self.response_context = response_context
|
|
68
56
|
|
|
69
|
-
self.task_id = response_context.get("task_id"
|
|
70
|
-
self.task_run_id = response_context.get("task_run_id"
|
|
57
|
+
self.task_id = response_context.get("task_id")
|
|
58
|
+
self.task_run_id = response_context.get("task_run_id")
|
|
59
|
+
self.tool_execution_strategy = self.response_context.get("tool_execution_strategy", "parallel")
|
|
60
|
+
self.xml_adding_strategy = self.response_context.get("xml_adding_strategy", "user_message")
|
|
61
|
+
self.max_xml_tool_calls = self.response_context.get("max_xml_tool_calls", 0)
|
|
62
|
+
|
|
63
|
+
self.trace_context = {
|
|
64
|
+
"trace_id": self.response_context.get("trace_id"),
|
|
65
|
+
"parent_span_id": self.response_context.get("root_span_id"),
|
|
66
|
+
}
|
|
71
67
|
|
|
72
|
-
|
|
73
|
-
self.trace = Trace()
|
|
68
|
+
self.add_response_message = response_context.get("add_response_msg_func")
|
|
74
69
|
|
|
75
|
-
self.add_message = response_context.get("add_context_msg")
|
|
76
|
-
self._add_message_with_agent_info = self.add_message
|
|
77
70
|
self.tool_box = response_context.get("tool_box")
|
|
78
|
-
|
|
79
71
|
self.xml_parser = XMLToolParser()
|
|
80
72
|
|
|
73
|
+
|
|
74
|
+
|
|
81
75
|
@abstractmethod
|
|
82
76
|
async def process_response(self,
|
|
83
77
|
llm_response: AsyncGenerator,
|
|
@@ -86,16 +80,6 @@ class TaskResponseProcessor(ABC):
|
|
|
86
80
|
) -> AsyncGenerator[Dict[str, Any], None]:
|
|
87
81
|
pass
|
|
88
82
|
|
|
89
|
-
async def _yield_message(self, message_obj: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
|
90
|
-
"""Helper to yield a message with proper formatting.
|
|
91
|
-
|
|
92
|
-
Ensures that content and metadata are JSON strings for client compatibility.
|
|
93
|
-
"""
|
|
94
|
-
if message_obj:
|
|
95
|
-
return format_for_yield(message_obj)
|
|
96
|
-
return None
|
|
97
|
-
|
|
98
|
-
|
|
99
83
|
|
|
100
84
|
def _extract_xml_chunks(self, content: str) -> List[str]:
|
|
101
85
|
"""Extract complete XML chunks using start and end pattern matching."""
|
|
@@ -190,12 +174,12 @@ class TaskResponseProcessor(ABC):
|
|
|
190
174
|
except Exception as e:
|
|
191
175
|
logging.error(f"Error extracting XML chunks: {e}")
|
|
192
176
|
logging.error(f"Content was: {content}")
|
|
193
|
-
self.
|
|
177
|
+
langfuse.create_event(trace_context=self.trace_context, name="error_extracting_xml_chunks", level="ERROR",
|
|
194
178
|
status_message=(f"Error extracting XML chunks: {e}"), metadata={"content": content})
|
|
195
179
|
|
|
196
180
|
return chunks
|
|
197
181
|
|
|
198
|
-
def _parse_xml_tool_call(self, xml_chunk: str) ->
|
|
182
|
+
def _parse_xml_tool_call(self, xml_chunk: str) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
|
199
183
|
"""Parse XML chunk into tool call format and return parsing details.
|
|
200
184
|
|
|
201
185
|
Returns:
|
|
@@ -215,6 +199,9 @@ class TaskResponseProcessor(ABC):
|
|
|
215
199
|
|
|
216
200
|
# Take the first tool call (should only be one per chunk)
|
|
217
201
|
xml_tool_call = parsed_calls[0]
|
|
202
|
+
if not xml_tool_call.function_name:
|
|
203
|
+
logging.error(f"xml_tool_call function name is empty: {xml_tool_call}")
|
|
204
|
+
return None
|
|
218
205
|
|
|
219
206
|
# Convert to the expected format
|
|
220
207
|
tool_call = {
|
|
@@ -237,7 +224,7 @@ class TaskResponseProcessor(ABC):
|
|
|
237
224
|
except Exception as e:
|
|
238
225
|
logging.error(f"Error parsing XML chunk: {e}")
|
|
239
226
|
logging.error(f"XML chunk was: {xml_chunk}")
|
|
240
|
-
self.
|
|
227
|
+
langfuse.create_event(trace_context=self.trace_context, name="error_parsing_xml_chunk", level="ERROR",
|
|
241
228
|
status_message=(f"Error parsing XML chunk: {e}"), metadata={"xml_chunk": xml_chunk})
|
|
242
229
|
return None
|
|
243
230
|
|
|
@@ -263,45 +250,44 @@ class TaskResponseProcessor(ABC):
|
|
|
263
250
|
|
|
264
251
|
except Exception as e:
|
|
265
252
|
logging.error(f"Error parsing XML tool calls: {e}", exc_info=True)
|
|
266
|
-
self.
|
|
253
|
+
langfuse.create_event(trace_context=self.trace_context, name="error_parsing_xml_tool_calls", level="ERROR",
|
|
267
254
|
status_message=(f"Error parsing XML tool calls: {e}"), metadata={"content": content})
|
|
268
255
|
|
|
269
256
|
return parsed_data
|
|
270
257
|
|
|
271
|
-
|
|
258
|
+
|
|
272
259
|
async def _execute_tool(self, tool_call: Dict[str, Any]) -> XGAToolResult:
|
|
273
260
|
"""Execute a single tool call and return the result."""
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
return XGAToolResult(success=False, output=f"Error executing tool: {str(e)}")
|
|
261
|
+
with langfuse.start_as_current_span(trace_context=self.trace_context, name=f"execute_tool.{tool_call['function_name']}", input=tool_call["arguments"]
|
|
262
|
+
) as exec_tool_span:
|
|
263
|
+
try:
|
|
264
|
+
function_name = tool_call["function_name"]
|
|
265
|
+
arguments = tool_call["arguments"]
|
|
266
|
+
|
|
267
|
+
logging.info(f"Executing tool: {function_name} with arguments: {arguments}")
|
|
268
|
+
|
|
269
|
+
if isinstance(arguments, str):
|
|
270
|
+
try:
|
|
271
|
+
arguments = safe_json_parse(arguments)
|
|
272
|
+
except json.JSONDecodeError:
|
|
273
|
+
arguments = {"text": arguments} # @todo modify
|
|
274
|
+
|
|
275
|
+
result = None
|
|
276
|
+
available_tool_names = self.tool_box.get_task_tool_names(self.task_id)
|
|
277
|
+
if function_name in available_tool_names:
|
|
278
|
+
result = await self.tool_box.call_tool(self.task_id, function_name, arguments)
|
|
279
|
+
else:
|
|
280
|
+
logging.error(f"Tool function '{function_name}' not found in registry")
|
|
281
|
+
result = XGAToolResult(success=False, output=f"Tool function '{function_name}' not found")
|
|
282
|
+
logging.info(f"Tool execution complete: {function_name} -> {result}")
|
|
283
|
+
exec_tool_span.update(status_message="tool_executed", output=result)
|
|
284
|
+
|
|
285
|
+
return result
|
|
286
|
+
except Exception as e:
|
|
287
|
+
logging.error(f"Error executing tool {tool_call['function_name']}: {str(e)}", exc_info=True)
|
|
288
|
+
|
|
289
|
+
exec_tool_span.update(status_message="tool_execution_error", output=f"Error executing tool: {str(e)}", level="ERROR")
|
|
290
|
+
return XGAToolResult(success=False, output=f"Error executing tool: {str(e)}")
|
|
305
291
|
|
|
306
292
|
async def _execute_tools(
|
|
307
293
|
self,
|
|
@@ -309,8 +295,6 @@ class TaskResponseProcessor(ABC):
|
|
|
309
295
|
execution_strategy: ToolExecutionStrategy = "sequential"
|
|
310
296
|
) -> List[Tuple[Dict[str, Any], XGAToolResult]]:
|
|
311
297
|
logging.info(f"Executing {len(tool_calls)} tools with strategy: {execution_strategy}")
|
|
312
|
-
self.trace.event(name="executing_tools_with_strategy", level="DEFAULT",
|
|
313
|
-
status_message=(f"Executing {len(tool_calls)} tools with strategy: {execution_strategy}"))
|
|
314
298
|
|
|
315
299
|
if execution_strategy == "sequential":
|
|
316
300
|
return await self._execute_tools_sequentially(tool_calls)
|
|
@@ -335,54 +319,40 @@ class TaskResponseProcessor(ABC):
|
|
|
335
319
|
"""
|
|
336
320
|
if not tool_calls:
|
|
337
321
|
return []
|
|
322
|
+
tool_names = [t.get('function_name', 'unknown') for t in tool_calls]
|
|
323
|
+
logging.info(f"Executing {len(tool_calls)} tools sequentially: {tool_names}")
|
|
324
|
+
langfuse.create_event(trace_context=self.trace_context, name="executing_tools_sequentially", level="DEFAULT",
|
|
325
|
+
status_message=(f"Executing {len(tool_calls)} tools sequentially: {tool_names}"))
|
|
338
326
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
status_message=(f"Executing {len(tool_calls)} tools sequentially: {tool_names}"))
|
|
344
|
-
|
|
345
|
-
results = []
|
|
346
|
-
for index, tool_call in enumerate(tool_calls):
|
|
347
|
-
tool_name = tool_call.get('function_name', 'unknown')
|
|
348
|
-
logging.debug(f"Executing tool {index + 1}/{len(tool_calls)}: {tool_name}")
|
|
349
|
-
|
|
350
|
-
try:
|
|
351
|
-
result = await self._execute_tool(tool_call)
|
|
352
|
-
results.append((tool_call, result))
|
|
353
|
-
logging.debug(f"Completed tool {tool_name} with success={result.success}")
|
|
354
|
-
|
|
355
|
-
# Check if this is a terminating tool (ask or complete)
|
|
356
|
-
if tool_name in ['ask', 'complete']:
|
|
357
|
-
logging.info(f"Terminating tool '{tool_name}' executed. Stopping further tool execution.")
|
|
358
|
-
self.trace.event(name="terminating_tool_executed", level="DEFAULT", status_message=(
|
|
359
|
-
f"Terminating tool '{tool_name}' executed. Stopping further tool execution."))
|
|
360
|
-
break # Stop executing remaining tools
|
|
361
|
-
|
|
362
|
-
except Exception as e:
|
|
363
|
-
logging.error(f"Error executing tool {tool_name}: {str(e)}")
|
|
364
|
-
self.trace.event(name="error_executing_tool", level="ERROR",
|
|
365
|
-
status_message=(f"Error executing tool {tool_name}: {str(e)}"))
|
|
366
|
-
error_result = XGAToolResult(success=False, output=f"Error executing tool: {str(e)}")
|
|
367
|
-
results.append((tool_call, error_result))
|
|
368
|
-
|
|
369
|
-
logging.info(f"Sequential execution completed for {len(results)} tools (out of {len(tool_calls)} total)")
|
|
370
|
-
self.trace.event(name="sequential_execution_completed", level="DEFAULT", status_message=(
|
|
371
|
-
f"Sequential execution completed for {len(results)} tools (out of {len(tool_calls)} total)"))
|
|
372
|
-
return results
|
|
373
|
-
|
|
374
|
-
except Exception as e:
|
|
375
|
-
logging.error(f"Error in sequential tool execution: {str(e)}", exc_info=True)
|
|
376
|
-
# Return partial results plus error results for remaining tools
|
|
377
|
-
completed_results = results if 'results' in locals() else []
|
|
378
|
-
completed_tool_names = [r[0].get('function_name', 'unknown') for r in completed_results]
|
|
379
|
-
remaining_tools = [t for t in tool_calls if t.get('function_name', 'unknown') not in completed_tool_names]
|
|
327
|
+
results = []
|
|
328
|
+
for index, tool_call in enumerate(tool_calls):
|
|
329
|
+
tool_name = tool_call.get('function_name', 'unknown')
|
|
330
|
+
logging.debug(f"Executing tool {index + 1}/{len(tool_calls)}: {tool_name}")
|
|
380
331
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
332
|
+
try:
|
|
333
|
+
result = await self._execute_tool(tool_call)
|
|
334
|
+
results.append((tool_call, result))
|
|
335
|
+
logging.debug(f"Completed tool {tool_name} with success={result.success}")
|
|
336
|
+
|
|
337
|
+
# Check if this is a terminating tool (ask or complete)
|
|
338
|
+
if tool_name in ['ask', 'complete']:
|
|
339
|
+
logging.info(f"Terminating tool '{tool_name}' executed. Stopping further tool execution.")
|
|
340
|
+
# langfuse.create_event(trace_context=self.trace_context, name="terminating_tool_executed",
|
|
341
|
+
# level="DEFAULT", status_message=(f"Terminating tool '{tool_name}' executed. Stopping further tool execution."))
|
|
342
|
+
break # Stop executing remaining tools
|
|
343
|
+
|
|
344
|
+
except Exception as e:
|
|
345
|
+
logging.error(f"Error executing tool {tool_name}: {str(e)}")
|
|
346
|
+
langfuse.create_event(trace_context=self.trace_context, name="error_executing_tool", level="ERROR",
|
|
347
|
+
status_message=(f"Error executing tool {tool_name}: {str(e)}"))
|
|
348
|
+
error_result = XGAToolResult(success=False, output=f"Error executing tool: {str(e)}")
|
|
349
|
+
results.append((tool_call, error_result))
|
|
350
|
+
|
|
351
|
+
logging.info(f"Sequential execution completed for {len(results)} tools (out of {len(tool_calls)} total)")
|
|
352
|
+
# langfuse.create_event(trace_context=self.trace_context, name="sequential_execution_completed", level="DEFAULT",
|
|
353
|
+
# status_message=(f"Sequential execution completed for {len(results)} tools (out of {len(tool_calls)} total)"))
|
|
354
|
+
return results
|
|
384
355
|
|
|
385
|
-
return completed_results + error_results
|
|
386
356
|
|
|
387
357
|
async def _execute_tools_in_parallel(self, tool_calls: List[Dict[str, Any]]) -> List[Tuple[Dict[str, Any], XGAToolResult]]:
|
|
388
358
|
if not tool_calls:
|
|
@@ -391,8 +361,8 @@ class TaskResponseProcessor(ABC):
|
|
|
391
361
|
try:
|
|
392
362
|
tool_names = [t.get('function_name', 'unknown') for t in tool_calls]
|
|
393
363
|
logging.info(f"Executing {len(tool_calls)} tools in parallel: {tool_names}")
|
|
394
|
-
self.
|
|
395
|
-
|
|
364
|
+
# langfuse.create_event(trace_context=self.trace_context, name="executing_tools_in_parallel", level="DEFAULT",
|
|
365
|
+
# status_message=(f"Executing {len(tool_calls)} tools in parallel: {tool_names}"))
|
|
396
366
|
|
|
397
367
|
# Create tasks for all tool calls
|
|
398
368
|
tasks = [self._execute_tool(tool_call) for tool_call in tool_calls]
|
|
@@ -405,7 +375,7 @@ class TaskResponseProcessor(ABC):
|
|
|
405
375
|
for i, (tool_call, result) in enumerate(zip(tool_calls, results)):
|
|
406
376
|
if isinstance(result, Exception):
|
|
407
377
|
logging.error(f"Error executing tool {tool_call.get('function_name', 'unknown')}: {str(result)}")
|
|
408
|
-
self.
|
|
378
|
+
langfuse.create_event(trace_context=self.trace_context, name="error_executing_tool", level="ERROR", status_message=(
|
|
409
379
|
f"Error executing tool {tool_call.get('function_name', 'unknown')}: {str(result)}"))
|
|
410
380
|
# Create error result
|
|
411
381
|
error_result = XGAToolResult(success=False, output=f"Error executing tool: {str(result)}")
|
|
@@ -414,24 +384,23 @@ class TaskResponseProcessor(ABC):
|
|
|
414
384
|
processed_results.append((tool_call, result))
|
|
415
385
|
|
|
416
386
|
logging.info(f"Parallel execution completed for {len(tool_calls)} tools")
|
|
417
|
-
self.
|
|
418
|
-
|
|
387
|
+
# langfuse.create_event(trace_context=self.trace_context, name="parallel_execution_completed", level="DEFAULT",
|
|
388
|
+
# status_message=(f"Parallel execution completed for {len(tool_calls)} tools"))
|
|
419
389
|
return processed_results
|
|
420
390
|
|
|
421
391
|
except Exception as e:
|
|
422
392
|
logging.error(f"Error in parallel tool execution: {str(e)}", exc_info=True)
|
|
423
|
-
self.
|
|
393
|
+
langfuse.create_event(trace_context=self.trace_context, name="error_in_parallel_tool_execution", level="ERROR",
|
|
424
394
|
status_message=(f"Error in parallel tool execution: {str(e)}"))
|
|
425
395
|
# Return error results for all tools if the gather itself fails
|
|
426
396
|
return [(tool_call, XGAToolResult(success=False, output=f"Execution error: {str(e)}"))
|
|
427
397
|
for tool_call in tool_calls]
|
|
428
398
|
|
|
429
|
-
def
|
|
399
|
+
def _add_tool_messsage(
|
|
430
400
|
self,
|
|
431
|
-
thread_id: str,
|
|
432
401
|
tool_call: Dict[str, Any],
|
|
433
402
|
result: XGAToolResult,
|
|
434
|
-
strategy:
|
|
403
|
+
strategy: XmlAddingStrategy = "assistant_message",
|
|
435
404
|
assistant_message_id: Optional[str] = None,
|
|
436
405
|
parsing_details: Optional[Dict[str, Any]] = None
|
|
437
406
|
) -> Optional[Dict[str, Any]]: # Return the full message object
|
|
@@ -443,63 +412,11 @@ class TaskResponseProcessor(ABC):
|
|
|
443
412
|
if assistant_message_id:
|
|
444
413
|
metadata["assistant_message_id"] = assistant_message_id
|
|
445
414
|
logging.info(f"Linking tool result to assistant message: {assistant_message_id}")
|
|
446
|
-
self.trace.event(name="linking_tool_result_to_assistant_message", level="DEFAULT",
|
|
447
|
-
status_message=(f"Linking tool result to assistant message: {assistant_message_id}"))
|
|
448
415
|
|
|
449
416
|
# --- Add parsing details to metadata if available ---
|
|
450
417
|
if parsing_details:
|
|
451
418
|
metadata["parsing_details"] = parsing_details
|
|
452
419
|
logging.info("Adding parsing_details to tool result metadata")
|
|
453
|
-
self.trace.event(name="adding_parsing_details_to_tool_result_metadata", level="DEFAULT",
|
|
454
|
-
status_message=(f"Adding parsing_details to tool result metadata"),
|
|
455
|
-
metadata={"parsing_details": parsing_details})
|
|
456
|
-
# ---
|
|
457
|
-
|
|
458
|
-
# Check if this is a native function call (has id field)
|
|
459
|
-
if "id" in tool_call:
|
|
460
|
-
# Format as a proper tool message according to OpenAI spec
|
|
461
|
-
function_name = tool_call.get("function_name", "")
|
|
462
|
-
|
|
463
|
-
# Format the tool result content - tool role needs string content
|
|
464
|
-
if isinstance(result, str):
|
|
465
|
-
content = result
|
|
466
|
-
elif hasattr(result, 'output'):
|
|
467
|
-
# If it's a XGAToolResult object
|
|
468
|
-
if isinstance(result.output, dict) or isinstance(result.output, list):
|
|
469
|
-
# If output is already a dict or list, convert to JSON string
|
|
470
|
-
content = json.dumps(result.output)
|
|
471
|
-
else:
|
|
472
|
-
# Otherwise just use the string representation
|
|
473
|
-
content = str(result.output)
|
|
474
|
-
else:
|
|
475
|
-
# Fallback to string representation of the whole result
|
|
476
|
-
content = str(result)
|
|
477
|
-
|
|
478
|
-
logging.info(f"Formatted tool result content: {content[:100]}...")
|
|
479
|
-
self.trace.event(name="formatted_tool_result_content", level="DEFAULT",
|
|
480
|
-
status_message=(f"Formatted tool result content: {content[:100]}..."))
|
|
481
|
-
|
|
482
|
-
# Create the tool response message with proper format
|
|
483
|
-
tool_message = {
|
|
484
|
-
"role": "tool",
|
|
485
|
-
"tool_call_id": tool_call["id"],
|
|
486
|
-
"name": function_name,
|
|
487
|
-
"content": content
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
logging.info(f"Adding native tool result for tool_call_id={tool_call['id']} with role=tool")
|
|
491
|
-
self.trace.event(name="adding_native_tool_result_for_tool_call_id", level="DEFAULT", status_message=(
|
|
492
|
-
f"Adding native tool result for tool_call_id={tool_call['id']} with role=tool"))
|
|
493
|
-
|
|
494
|
-
# Add as a tool message to the conversation history
|
|
495
|
-
# This makes the result visible to the LLM in the next turn
|
|
496
|
-
message_obj = self.add_message(
|
|
497
|
-
type="tool", # Special type for tool responses
|
|
498
|
-
content=tool_message,
|
|
499
|
-
is_llm_message=True,
|
|
500
|
-
metadata=metadata
|
|
501
|
-
)
|
|
502
|
-
return message_obj # Return the full message object
|
|
503
420
|
|
|
504
421
|
# For XML and other non-native tools, use the new structured format
|
|
505
422
|
# Determine message role based on strategy
|
|
@@ -525,7 +442,7 @@ class TaskResponseProcessor(ABC):
|
|
|
525
442
|
metadata = {}
|
|
526
443
|
metadata['frontend_content'] = structured_result_for_frontend
|
|
527
444
|
|
|
528
|
-
message_obj = self.
|
|
445
|
+
message_obj = self.add_response_message(
|
|
529
446
|
type="tool",
|
|
530
447
|
content=result_message_for_llm, # Save the LLM-friendly version
|
|
531
448
|
is_llm_message=True,
|
|
@@ -544,7 +461,7 @@ class TaskResponseProcessor(ABC):
|
|
|
544
461
|
return message_obj # Return the modified message object
|
|
545
462
|
except Exception as e:
|
|
546
463
|
logging.error(f"Error adding tool result: {str(e)}", exc_info=True)
|
|
547
|
-
self.
|
|
464
|
+
langfuse.create_event(trace_context=self.trace_context, name="error_adding_tool_result", level="ERROR",
|
|
548
465
|
status_message=(f"Error adding tool result: {str(e)}"),
|
|
549
466
|
metadata={"tool_call": tool_call, "result": result, "strategy": strategy,
|
|
550
467
|
"assistant_message_id": assistant_message_id,
|
|
@@ -555,7 +472,7 @@ class TaskResponseProcessor(ABC):
|
|
|
555
472
|
"role": "user",
|
|
556
473
|
"content": str(result)
|
|
557
474
|
}
|
|
558
|
-
message_obj = self.
|
|
475
|
+
message_obj = self.add_response_message(
|
|
559
476
|
type="tool",
|
|
560
477
|
content=fallback_message,
|
|
561
478
|
is_llm_message=True,
|
|
@@ -564,7 +481,7 @@ class TaskResponseProcessor(ABC):
|
|
|
564
481
|
return message_obj # Return the full message object
|
|
565
482
|
except Exception as e2:
|
|
566
483
|
logging.error(f"Failed even with fallback message: {str(e2)}", exc_info=True)
|
|
567
|
-
self.
|
|
484
|
+
langfuse.create_event(trace_context=self.trace_context, name="failed_even_with_fallback_message", level="ERROR",
|
|
568
485
|
status_message=(f"Failed even with fallback message: {str(e2)}"),
|
|
569
486
|
metadata={"tool_call": tool_call, "result": result, "strategy": strategy,
|
|
570
487
|
"assistant_message_id": assistant_message_id,
|
|
@@ -629,38 +546,29 @@ class TaskResponseProcessor(ABC):
|
|
|
629
546
|
)
|
|
630
547
|
|
|
631
548
|
# Set function_name and xml_tag_name fields
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
context.function_name = tool_call.get("function_name", tool_call["xml_tag_name"])
|
|
635
|
-
else:
|
|
636
|
-
# For non-XML tools, use function name directly
|
|
637
|
-
context.function_name = tool_call.get("function_name", "unknown")
|
|
638
|
-
context.xml_tag_name = None
|
|
549
|
+
context.xml_tag_name = tool_call["xml_tag_name"]
|
|
550
|
+
context.function_name = tool_call["function_name"]
|
|
639
551
|
|
|
640
552
|
return context
|
|
641
553
|
|
|
642
|
-
def
|
|
643
|
-
Optional[Dict[str, Any]]:
|
|
554
|
+
def _add_tool_start_message(self, context: ToolExecutionContext) -> Optional[Dict[str, Any]]:
|
|
644
555
|
"""Formats, saves, and returns a tool started status message."""
|
|
645
556
|
tool_name = context.xml_tag_name or context.function_name
|
|
646
557
|
content = {
|
|
647
558
|
"role": "assistant", "status_type": "tool_started",
|
|
648
559
|
"function_name": context.function_name, "xml_tag_name": context.xml_tag_name,
|
|
649
|
-
"message": f"Starting execution of {tool_name}", "tool_index": context.tool_index
|
|
650
|
-
"tool_call_id": context.tool_call.get("id") # Include tool_call ID if native
|
|
560
|
+
"message": f"Starting execution of {tool_name}", "tool_index": context.tool_index # Include tool_call ID if native
|
|
651
561
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
type="status", content=content, is_llm_message=False
|
|
562
|
+
|
|
563
|
+
return self.add_response_message(
|
|
564
|
+
type="status", content=content, is_llm_message=False
|
|
655
565
|
)
|
|
656
|
-
return saved_message_obj # Return the full object (or None if saving failed)
|
|
657
566
|
|
|
658
|
-
def
|
|
659
|
-
thread_id: str, thread_run_id: str) -> Optional[Dict[str, Any]]:
|
|
567
|
+
def _add_tool_completed_message(self, context: ToolExecutionContext, tool_message_id: Optional[str]) -> Optional[Dict[str, Any]]:
|
|
660
568
|
"""Formats, saves, and returns a tool completed/failed status message."""
|
|
661
569
|
if not context.result:
|
|
662
570
|
# Delegate to error saving if result is missing (e.g., execution failed)
|
|
663
|
-
return self.
|
|
571
|
+
return self._add_tool_error_message(context)
|
|
664
572
|
|
|
665
573
|
tool_name = context.xml_tag_name or context.function_name
|
|
666
574
|
status_type = "tool_completed" if context.result.success else "tool_failed"
|
|
@@ -672,7 +580,7 @@ class TaskResponseProcessor(ABC):
|
|
|
672
580
|
"message": message_text, "tool_index": context.tool_index,
|
|
673
581
|
"tool_call_id": context.tool_call.get("id")
|
|
674
582
|
}
|
|
675
|
-
metadata = {
|
|
583
|
+
metadata = {}
|
|
676
584
|
# Add the *actual* tool result message ID to the metadata if available and successful
|
|
677
585
|
if context.result.success and tool_message_id:
|
|
678
586
|
metadata["linked_tool_result_message_id"] = tool_message_id
|
|
@@ -681,17 +589,15 @@ class TaskResponseProcessor(ABC):
|
|
|
681
589
|
if context.function_name in ['ask', 'complete']:
|
|
682
590
|
metadata["agent_should_terminate"] = "true"
|
|
683
591
|
logging.info(f"Marking tool status for '{context.function_name}' with termination signal.")
|
|
684
|
-
self.
|
|
685
|
-
|
|
592
|
+
# langfuse.create_event(trace_context=self.trace_context, name="marking_tool_status_for_termination", level="DEFAULT", status_message=(
|
|
593
|
+
# f"Marking tool status for '{context.function_name}' with termination signal."))
|
|
686
594
|
# <<< END ADDED >>>
|
|
687
595
|
|
|
688
|
-
|
|
596
|
+
return self.add_response_message(
|
|
689
597
|
type="status", content=content, is_llm_message=False, metadata=metadata
|
|
690
598
|
)
|
|
691
|
-
return saved_message_obj
|
|
692
599
|
|
|
693
|
-
def
|
|
694
|
-
Optional[Dict[str, Any]]:
|
|
600
|
+
def _add_tool_error_message(self, context: ToolExecutionContext) -> Optional[Dict[str, Any]]:
|
|
695
601
|
"""Formats, saves, and returns a tool error status message."""
|
|
696
602
|
error_msg = str(context.error) if context.error else "Unknown error during tool execution"
|
|
697
603
|
tool_name = context.xml_tag_name or context.function_name
|
|
@@ -702,9 +608,9 @@ class TaskResponseProcessor(ABC):
|
|
|
702
608
|
"tool_index": context.tool_index,
|
|
703
609
|
"tool_call_id": context.tool_call.get("id")
|
|
704
610
|
}
|
|
705
|
-
|
|
611
|
+
|
|
706
612
|
# Save the status message with is_llm_message=False
|
|
707
|
-
|
|
708
|
-
type="status", content=content, is_llm_message=False
|
|
613
|
+
return self.add_response_message(
|
|
614
|
+
type="status", content=content, is_llm_message=False
|
|
709
615
|
)
|
|
710
|
-
|
|
616
|
+
|