xgae 0.1.5__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} +6 -9
- xgae/engine/{xga_mcp_tool_box.py → mcp_tool_box.py} +5 -5
- 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} +102 -186
- xgae/engine/responser/{xga_stream_responser.py → stream_responser.py} +51 -55
- xgae/engine/{xga_engine.py → task_engine.py} +86 -73
- 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.5.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 -216
- xgae-0.1.5.dist-info/RECORD +0 -16
- {xgae-0.1.5.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,35 +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
|
|
26
|
+
max_xml_tool_calls: int # LLM generate max_xml_tool limit, 0 is no limit
|
|
29
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
|
-
|
|
37
|
+
max_auto_run: int
|
|
51
38
|
|
|
52
39
|
@dataclass
|
|
53
40
|
class ToolExecutionContext:
|
|
@@ -63,21 +50,27 @@ class ToolExecutionContext:
|
|
|
63
50
|
|
|
64
51
|
|
|
65
52
|
class TaskResponseProcessor(ABC):
|
|
66
|
-
def __init__(self, response_context:
|
|
53
|
+
def __init__(self, response_context: TaskResponserContext):
|
|
67
54
|
self.response_context = response_context
|
|
68
55
|
|
|
69
|
-
self.task_id = response_context.get("task_id"
|
|
70
|
-
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)
|
|
71
61
|
|
|
72
|
-
|
|
73
|
-
|
|
62
|
+
self.trace_context = {
|
|
63
|
+
"trace_id": self.response_context.get("trace_id"),
|
|
64
|
+
"parent_span_id": None
|
|
65
|
+
}
|
|
74
66
|
|
|
75
|
-
self.
|
|
76
|
-
self._add_message_with_agent_info = self.add_message
|
|
77
|
-
self.tool_box = response_context.get("tool_box")
|
|
67
|
+
self.add_response_message = response_context.get("add_response_msg_func")
|
|
78
68
|
|
|
69
|
+
self.tool_box = response_context.get("tool_box")
|
|
79
70
|
self.xml_parser = XMLToolParser()
|
|
80
71
|
|
|
72
|
+
|
|
73
|
+
|
|
81
74
|
@abstractmethod
|
|
82
75
|
async def process_response(self,
|
|
83
76
|
llm_response: AsyncGenerator,
|
|
@@ -86,16 +79,6 @@ class TaskResponseProcessor(ABC):
|
|
|
86
79
|
) -> AsyncGenerator[Dict[str, Any], None]:
|
|
87
80
|
pass
|
|
88
81
|
|
|
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
82
|
|
|
100
83
|
def _extract_xml_chunks(self, content: str) -> List[str]:
|
|
101
84
|
"""Extract complete XML chunks using start and end pattern matching."""
|
|
@@ -190,12 +173,12 @@ class TaskResponseProcessor(ABC):
|
|
|
190
173
|
except Exception as e:
|
|
191
174
|
logging.error(f"Error extracting XML chunks: {e}")
|
|
192
175
|
logging.error(f"Content was: {content}")
|
|
193
|
-
self.
|
|
176
|
+
langfuse.create_event(trace_context=self.trace_context, name="error_extracting_xml_chunks", level="ERROR",
|
|
194
177
|
status_message=(f"Error extracting XML chunks: {e}"), metadata={"content": content})
|
|
195
178
|
|
|
196
179
|
return chunks
|
|
197
180
|
|
|
198
|
-
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]]:
|
|
199
182
|
"""Parse XML chunk into tool call format and return parsing details.
|
|
200
183
|
|
|
201
184
|
Returns:
|
|
@@ -215,6 +198,9 @@ class TaskResponseProcessor(ABC):
|
|
|
215
198
|
|
|
216
199
|
# Take the first tool call (should only be one per chunk)
|
|
217
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
|
|
218
204
|
|
|
219
205
|
# Convert to the expected format
|
|
220
206
|
tool_call = {
|
|
@@ -237,7 +223,7 @@ class TaskResponseProcessor(ABC):
|
|
|
237
223
|
except Exception as e:
|
|
238
224
|
logging.error(f"Error parsing XML chunk: {e}")
|
|
239
225
|
logging.error(f"XML chunk was: {xml_chunk}")
|
|
240
|
-
self.
|
|
226
|
+
langfuse.create_event(trace_context=self.trace_context, name="error_parsing_xml_chunk", level="ERROR",
|
|
241
227
|
status_message=(f"Error parsing XML chunk: {e}"), metadata={"xml_chunk": xml_chunk})
|
|
242
228
|
return None
|
|
243
229
|
|
|
@@ -263,21 +249,22 @@ class TaskResponseProcessor(ABC):
|
|
|
263
249
|
|
|
264
250
|
except Exception as e:
|
|
265
251
|
logging.error(f"Error parsing XML tool calls: {e}", exc_info=True)
|
|
266
|
-
self.
|
|
252
|
+
langfuse.create_event(trace_context=self.trace_context, name="error_parsing_xml_tool_calls", level="ERROR",
|
|
267
253
|
status_message=(f"Error parsing XML tool calls: {e}"), metadata={"content": content})
|
|
268
254
|
|
|
269
255
|
return parsed_data
|
|
270
256
|
|
|
271
|
-
|
|
257
|
+
|
|
272
258
|
async def _execute_tool(self, tool_call: Dict[str, Any]) -> XGAToolResult:
|
|
273
259
|
"""Execute a single tool call and return the result."""
|
|
274
|
-
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
|
|
275
262
|
try:
|
|
276
263
|
function_name = tool_call["function_name"]
|
|
277
264
|
arguments = tool_call["arguments"]
|
|
278
265
|
|
|
279
266
|
logging.info(f"Executing tool: {function_name} with arguments: {arguments}")
|
|
280
|
-
self.
|
|
267
|
+
langfuse.create_event(trace_context=self.trace_context, name="executing_tool", level="DEFAULT",
|
|
281
268
|
status_message=(f"Executing tool: {function_name} with arguments: {arguments}"))
|
|
282
269
|
|
|
283
270
|
if isinstance(arguments, str):
|
|
@@ -286,21 +273,21 @@ class TaskResponseProcessor(ABC):
|
|
|
286
273
|
except json.JSONDecodeError:
|
|
287
274
|
arguments = {"text": arguments} # @todo modify
|
|
288
275
|
|
|
289
|
-
# Get available functions from tool registry
|
|
290
|
-
#available_functions = self.tool_registry.get_available_functions()
|
|
291
276
|
result = None
|
|
292
|
-
|
|
293
|
-
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:
|
|
294
279
|
result = await self.tool_box.call_tool(self.task_id, function_name, arguments)
|
|
295
280
|
else:
|
|
296
281
|
logging.error(f"Tool function '{function_name}' not found in registry")
|
|
297
282
|
result = XGAToolResult(success=False, output=f"Tool function '{function_name}' not found")
|
|
298
283
|
logging.info(f"Tool execution complete: {function_name} -> {result}")
|
|
299
|
-
|
|
284
|
+
langfuse.update_current_span(status_message="tool_executed", output=result)
|
|
285
|
+
|
|
300
286
|
return result
|
|
301
287
|
except Exception as e:
|
|
302
288
|
logging.error(f"Error executing tool {tool_call['function_name']}: {str(e)}", exc_info=True)
|
|
303
|
-
|
|
289
|
+
|
|
290
|
+
langfuse.update_current_span(status_message="tool_execution_error", output=f"Error executing tool: {str(e)}", level="ERROR")
|
|
304
291
|
return XGAToolResult(success=False, output=f"Error executing tool: {str(e)}")
|
|
305
292
|
|
|
306
293
|
async def _execute_tools(
|
|
@@ -309,7 +296,7 @@ class TaskResponseProcessor(ABC):
|
|
|
309
296
|
execution_strategy: ToolExecutionStrategy = "sequential"
|
|
310
297
|
) -> List[Tuple[Dict[str, Any], XGAToolResult]]:
|
|
311
298
|
logging.info(f"Executing {len(tool_calls)} tools with strategy: {execution_strategy}")
|
|
312
|
-
self.
|
|
299
|
+
langfuse.create_event(trace_context=self.trace_context, name="executing_tools_with_strategy", level="DEFAULT",
|
|
313
300
|
status_message=(f"Executing {len(tool_calls)} tools with strategy: {execution_strategy}"))
|
|
314
301
|
|
|
315
302
|
if execution_strategy == "sequential":
|
|
@@ -335,54 +322,42 @@ class TaskResponseProcessor(ABC):
|
|
|
335
322
|
"""
|
|
336
323
|
if not tool_calls:
|
|
337
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}"))
|
|
338
329
|
|
|
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}")
|
|
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}")
|
|
349
334
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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=(
|
|
359
345
|
f"Terminating tool '{tool_name}' executed. Stopping further tool execution."))
|
|
360
|
-
|
|
346
|
+
break # Stop executing remaining tools
|
|
361
347
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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))
|
|
368
354
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
|
373
360
|
|
|
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]
|
|
380
|
-
|
|
381
|
-
# Add error results for remaining tools
|
|
382
|
-
error_results = [(tool, XGAToolResult(success=False, output=f"Execution error: {str(e)}"))
|
|
383
|
-
for tool in remaining_tools]
|
|
384
|
-
|
|
385
|
-
return completed_results + error_results
|
|
386
361
|
|
|
387
362
|
async def _execute_tools_in_parallel(self, tool_calls: List[Dict[str, Any]]) -> List[Tuple[Dict[str, Any], XGAToolResult]]:
|
|
388
363
|
if not tool_calls:
|
|
@@ -391,7 +366,7 @@ class TaskResponseProcessor(ABC):
|
|
|
391
366
|
try:
|
|
392
367
|
tool_names = [t.get('function_name', 'unknown') for t in tool_calls]
|
|
393
368
|
logging.info(f"Executing {len(tool_calls)} tools in parallel: {tool_names}")
|
|
394
|
-
self.
|
|
369
|
+
langfuse.create_event(trace_context=self.trace_context, name="executing_tools_in_parallel", level="DEFAULT",
|
|
395
370
|
status_message=(f"Executing {len(tool_calls)} tools in parallel: {tool_names}"))
|
|
396
371
|
|
|
397
372
|
# Create tasks for all tool calls
|
|
@@ -405,7 +380,7 @@ class TaskResponseProcessor(ABC):
|
|
|
405
380
|
for i, (tool_call, result) in enumerate(zip(tool_calls, results)):
|
|
406
381
|
if isinstance(result, Exception):
|
|
407
382
|
logging.error(f"Error executing tool {tool_call.get('function_name', 'unknown')}: {str(result)}")
|
|
408
|
-
self.
|
|
383
|
+
langfuse.create_event(trace_context=self.trace_context, name="error_executing_tool", level="ERROR", status_message=(
|
|
409
384
|
f"Error executing tool {tool_call.get('function_name', 'unknown')}: {str(result)}"))
|
|
410
385
|
# Create error result
|
|
411
386
|
error_result = XGAToolResult(success=False, output=f"Error executing tool: {str(result)}")
|
|
@@ -414,24 +389,23 @@ class TaskResponseProcessor(ABC):
|
|
|
414
389
|
processed_results.append((tool_call, result))
|
|
415
390
|
|
|
416
391
|
logging.info(f"Parallel execution completed for {len(tool_calls)} tools")
|
|
417
|
-
self.
|
|
392
|
+
langfuse.create_event(trace_context=self.trace_context, name="parallel_execution_completed", level="DEFAULT",
|
|
418
393
|
status_message=(f"Parallel execution completed for {len(tool_calls)} tools"))
|
|
419
394
|
return processed_results
|
|
420
395
|
|
|
421
396
|
except Exception as e:
|
|
422
397
|
logging.error(f"Error in parallel tool execution: {str(e)}", exc_info=True)
|
|
423
|
-
self.
|
|
398
|
+
langfuse.create_event(trace_context=self.trace_context, name="error_in_parallel_tool_execution", level="ERROR",
|
|
424
399
|
status_message=(f"Error in parallel tool execution: {str(e)}"))
|
|
425
400
|
# Return error results for all tools if the gather itself fails
|
|
426
401
|
return [(tool_call, XGAToolResult(success=False, output=f"Execution error: {str(e)}"))
|
|
427
402
|
for tool_call in tool_calls]
|
|
428
403
|
|
|
429
|
-
def
|
|
404
|
+
def _add_tool_messsage(
|
|
430
405
|
self,
|
|
431
|
-
thread_id: str,
|
|
432
406
|
tool_call: Dict[str, Any],
|
|
433
407
|
result: XGAToolResult,
|
|
434
|
-
strategy:
|
|
408
|
+
strategy: XmlAddingStrategy = "assistant_message",
|
|
435
409
|
assistant_message_id: Optional[str] = None,
|
|
436
410
|
parsing_details: Optional[Dict[str, Any]] = None
|
|
437
411
|
) -> Optional[Dict[str, Any]]: # Return the full message object
|
|
@@ -443,63 +417,16 @@ class TaskResponseProcessor(ABC):
|
|
|
443
417
|
if assistant_message_id:
|
|
444
418
|
metadata["assistant_message_id"] = assistant_message_id
|
|
445
419
|
logging.info(f"Linking tool result to assistant message: {assistant_message_id}")
|
|
446
|
-
self.
|
|
420
|
+
langfuse.create_event(trace_context=self.trace_context, name="linking_tool_result_to_assistant_message", level="DEFAULT",
|
|
447
421
|
status_message=(f"Linking tool result to assistant message: {assistant_message_id}"))
|
|
448
422
|
|
|
449
423
|
# --- Add parsing details to metadata if available ---
|
|
450
424
|
if parsing_details:
|
|
451
425
|
metadata["parsing_details"] = parsing_details
|
|
452
426
|
logging.info("Adding parsing_details to tool result metadata")
|
|
453
|
-
self.
|
|
427
|
+
langfuse.create_event(trace_context=self.trace_context, name="adding_parsing_details_to_tool_result_metadata", level="DEFAULT",
|
|
454
428
|
status_message=(f"Adding parsing_details to tool result metadata"),
|
|
455
429
|
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
430
|
|
|
504
431
|
# For XML and other non-native tools, use the new structured format
|
|
505
432
|
# Determine message role based on strategy
|
|
@@ -525,7 +452,7 @@ class TaskResponseProcessor(ABC):
|
|
|
525
452
|
metadata = {}
|
|
526
453
|
metadata['frontend_content'] = structured_result_for_frontend
|
|
527
454
|
|
|
528
|
-
message_obj = self.
|
|
455
|
+
message_obj = self.add_response_message(
|
|
529
456
|
type="tool",
|
|
530
457
|
content=result_message_for_llm, # Save the LLM-friendly version
|
|
531
458
|
is_llm_message=True,
|
|
@@ -544,7 +471,7 @@ class TaskResponseProcessor(ABC):
|
|
|
544
471
|
return message_obj # Return the modified message object
|
|
545
472
|
except Exception as e:
|
|
546
473
|
logging.error(f"Error adding tool result: {str(e)}", exc_info=True)
|
|
547
|
-
self.
|
|
474
|
+
langfuse.create_event(trace_context=self.trace_context, name="error_adding_tool_result", level="ERROR",
|
|
548
475
|
status_message=(f"Error adding tool result: {str(e)}"),
|
|
549
476
|
metadata={"tool_call": tool_call, "result": result, "strategy": strategy,
|
|
550
477
|
"assistant_message_id": assistant_message_id,
|
|
@@ -555,7 +482,7 @@ class TaskResponseProcessor(ABC):
|
|
|
555
482
|
"role": "user",
|
|
556
483
|
"content": str(result)
|
|
557
484
|
}
|
|
558
|
-
message_obj = self.
|
|
485
|
+
message_obj = self.add_response_message(
|
|
559
486
|
type="tool",
|
|
560
487
|
content=fallback_message,
|
|
561
488
|
is_llm_message=True,
|
|
@@ -564,7 +491,7 @@ class TaskResponseProcessor(ABC):
|
|
|
564
491
|
return message_obj # Return the full message object
|
|
565
492
|
except Exception as e2:
|
|
566
493
|
logging.error(f"Failed even with fallback message: {str(e2)}", exc_info=True)
|
|
567
|
-
self.
|
|
494
|
+
langfuse.create_event(trace_context=self.trace_context, name="failed_even_with_fallback_message", level="ERROR",
|
|
568
495
|
status_message=(f"Failed even with fallback message: {str(e2)}"),
|
|
569
496
|
metadata={"tool_call": tool_call, "result": result, "strategy": strategy,
|
|
570
497
|
"assistant_message_id": assistant_message_id,
|
|
@@ -629,38 +556,29 @@ class TaskResponseProcessor(ABC):
|
|
|
629
556
|
)
|
|
630
557
|
|
|
631
558
|
# 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
|
|
559
|
+
context.xml_tag_name = tool_call["xml_tag_name"]
|
|
560
|
+
context.function_name = tool_call["function_name"]
|
|
639
561
|
|
|
640
562
|
return context
|
|
641
563
|
|
|
642
|
-
def
|
|
643
|
-
Optional[Dict[str, Any]]:
|
|
564
|
+
def _add_tool_start_message(self, context: ToolExecutionContext) -> Optional[Dict[str, Any]]:
|
|
644
565
|
"""Formats, saves, and returns a tool started status message."""
|
|
645
566
|
tool_name = context.xml_tag_name or context.function_name
|
|
646
567
|
content = {
|
|
647
568
|
"role": "assistant", "status_type": "tool_started",
|
|
648
569
|
"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
|
|
570
|
+
"message": f"Starting execution of {tool_name}", "tool_index": context.tool_index # Include tool_call ID if native
|
|
651
571
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
type="status", content=content, is_llm_message=False
|
|
572
|
+
|
|
573
|
+
return self.add_response_message(
|
|
574
|
+
type="status", content=content, is_llm_message=False
|
|
655
575
|
)
|
|
656
|
-
return saved_message_obj # Return the full object (or None if saving failed)
|
|
657
576
|
|
|
658
|
-
def
|
|
659
|
-
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]]:
|
|
660
578
|
"""Formats, saves, and returns a tool completed/failed status message."""
|
|
661
579
|
if not context.result:
|
|
662
580
|
# Delegate to error saving if result is missing (e.g., execution failed)
|
|
663
|
-
return self.
|
|
581
|
+
return self._add_tool_error_message(context)
|
|
664
582
|
|
|
665
583
|
tool_name = context.xml_tag_name or context.function_name
|
|
666
584
|
status_type = "tool_completed" if context.result.success else "tool_failed"
|
|
@@ -672,7 +590,7 @@ class TaskResponseProcessor(ABC):
|
|
|
672
590
|
"message": message_text, "tool_index": context.tool_index,
|
|
673
591
|
"tool_call_id": context.tool_call.get("id")
|
|
674
592
|
}
|
|
675
|
-
metadata = {
|
|
593
|
+
metadata = {}
|
|
676
594
|
# Add the *actual* tool result message ID to the metadata if available and successful
|
|
677
595
|
if context.result.success and tool_message_id:
|
|
678
596
|
metadata["linked_tool_result_message_id"] = tool_message_id
|
|
@@ -681,17 +599,15 @@ class TaskResponseProcessor(ABC):
|
|
|
681
599
|
if context.function_name in ['ask', 'complete']:
|
|
682
600
|
metadata["agent_should_terminate"] = "true"
|
|
683
601
|
logging.info(f"Marking tool status for '{context.function_name}' with termination signal.")
|
|
684
|
-
self.
|
|
602
|
+
langfuse.create_event(trace_context=self.trace_context, name="marking_tool_status_for_termination", level="DEFAULT", status_message=(
|
|
685
603
|
f"Marking tool status for '{context.function_name}' with termination signal."))
|
|
686
604
|
# <<< END ADDED >>>
|
|
687
605
|
|
|
688
|
-
|
|
606
|
+
return self.add_response_message(
|
|
689
607
|
type="status", content=content, is_llm_message=False, metadata=metadata
|
|
690
608
|
)
|
|
691
|
-
return saved_message_obj
|
|
692
609
|
|
|
693
|
-
def
|
|
694
|
-
Optional[Dict[str, Any]]:
|
|
610
|
+
def _add_tool_error_message(self, context: ToolExecutionContext) -> Optional[Dict[str, Any]]:
|
|
695
611
|
"""Formats, saves, and returns a tool error status message."""
|
|
696
612
|
error_msg = str(context.error) if context.error else "Unknown error during tool execution"
|
|
697
613
|
tool_name = context.xml_tag_name or context.function_name
|
|
@@ -702,9 +618,9 @@ class TaskResponseProcessor(ABC):
|
|
|
702
618
|
"tool_index": context.tool_index,
|
|
703
619
|
"tool_call_id": context.tool_call.get("id")
|
|
704
620
|
}
|
|
705
|
-
|
|
621
|
+
|
|
706
622
|
# Save the status message with is_llm_message=False
|
|
707
|
-
|
|
708
|
-
type="status", content=content, is_llm_message=False
|
|
623
|
+
return self.add_response_message(
|
|
624
|
+
type="status", content=content, is_llm_message=False
|
|
709
625
|
)
|
|
710
|
-
|
|
626
|
+
|