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.

@@ -1,16 +1,14 @@
1
1
  import asyncio
2
2
  import json
3
3
  import logging
4
- from dataclasses import dataclass
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.xga_base import XGAToolResult, XGAContextMsg, XGAToolBox
9
- # from xgae.utils.setup_env import langfuse
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 TaskResponseContext(TypedDict, total=False):
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
- add_context_msg: Callable
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: Literal["sequential", "parallel"]
32
- xml_adding_strategy: Literal["user_message", "assistant_message", "inline_edit"]
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: TaskResponseContext):
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
- # Initialize the XML parser
114
- self.trace = Trace()
62
+ self.trace_context = {
63
+ "trace_id": self.response_context.get("trace_id"),
64
+ "parent_span_id": None
65
+ }
115
66
 
116
- self.add_message = response_context.get("add_context_msg")
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.trace.event(name="error_extracting_xml_chunks", level="ERROR",
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) -> Optional[Tuple[Dict[str, Any], Dict[str, Any]]]:
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.trace.event(name="error_parsing_xml_chunk", level="ERROR",
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.trace.event(name="error_parsing_xml_tool_calls", level="ERROR",
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
- # Tool execution methods
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.trace.span(name=f"execute_tool.{tool_call['function_name']}", input=tool_call["arguments"])
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.trace.event(name="executing_tool", level="DEFAULT",
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
- available_function_names = self.tool_box.get_task_tool_names(self.task_id)
334
- if function_name in available_function_names:
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
- span.end(status_message="tool_executed", output=result)
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
- span.end(status_message="tool_execution_error", output=f"Error executing tool: {str(e)}", level="ERROR")
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.trace.event(name="executing_tools_with_strategy", level="DEFAULT",
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
- try:
381
- tool_names = [t.get('function_name', 'unknown') for t in tool_calls]
382
- logging.info(f"Executing {len(tool_calls)} tools sequentially: {tool_names}")
383
- self.trace.event(name="executing_tools_sequentially", level="DEFAULT",
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
- try:
392
- result = await self._execute_tool(tool_call)
393
- results.append((tool_call, result))
394
- logging.debug(f"Completed tool {tool_name} with success={result.success}")
395
-
396
- # Check if this is a terminating tool (ask or complete)
397
- if tool_name in ['ask', 'complete']:
398
- logging.info(f"Terminating tool '{tool_name}' executed. Stopping further tool execution.")
399
- self.trace.event(name="terminating_tool_executed", level="DEFAULT", status_message=(
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
- break # Stop executing remaining tools
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
- except Exception as e:
416
- logging.error(f"Error in sequential tool execution: {str(e)}", exc_info=True)
417
- # Return partial results plus error results for remaining tools
418
- completed_results = results if 'results' in locals() else []
419
- completed_tool_names = [r[0].get('function_name', 'unknown') for r in completed_results]
420
- remaining_tools = [t for t in tool_calls if t.get('function_name', 'unknown') not in completed_tool_names]
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
- # Add error results for remaining tools
423
- error_results = [(tool, XGAToolResult(success=False, output=f"Execution error: {str(e)}"))
424
- for tool in remaining_tools]
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.trace.event(name="executing_tools_in_parallel", level="DEFAULT",
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.trace.event(name="error_executing_tool", level="ERROR", status_message=(
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.trace.event(name="parallel_execution_completed", level="DEFAULT",
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.trace.event(name="error_in_parallel_tool_execution", level="ERROR",
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 _add_tool_result(
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: Union[XmlAddingStrategy, str] = "assistant_message",
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.trace.event(name="linking_tool_result_to_assistant_message", level="DEFAULT",
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.trace.event(name="adding_parsing_details_to_tool_result_metadata", level="DEFAULT",
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._add_message_with_agent_info(
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.trace.event(name="error_adding_tool_result", level="ERROR",
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.add_message(
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.trace.event(name="failed_even_with_fallback_message", level="ERROR",
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
- if "xml_tag_name" in tool_call:
674
- context.xml_tag_name = tool_call["xml_tag_name"]
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 _yield_and_save_tool_started(self, context: ToolExecutionContext, thread_id: str, thread_run_id: str) -> \
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
- metadata = {"thread_run_id": thread_run_id}
694
- saved_message_obj = self.add_message(
695
- type="status", content=content, is_llm_message=False, metadata=metadata
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 _yield_and_save_tool_completed(self, context: ToolExecutionContext, tool_message_id: Optional[str],
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._yield_and_save_tool_error(context, thread_id, thread_run_id)
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 = {"thread_run_id": thread_run_id}
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.trace.event(name="marking_tool_status_for_termination", level="DEFAULT", status_message=(
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
- saved_message_obj = self.add_message(
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 _yield_and_save_tool_error(self, context: ToolExecutionContext, thread_id: str, thread_run_id: str) -> \
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
- metadata = {"thread_run_id": thread_run_id}
621
+
747
622
  # Save the status message with is_llm_message=False
748
- saved_message_obj = self.add_message(
749
- type="status", content=content, is_llm_message=False, metadata=metadata
623
+ return self.add_response_message(
624
+ type="status", content=content, is_llm_message=False
750
625
  )
751
- return saved_message_obj
626
+