quantalogic 0.59.3__py3-none-any.whl → 0.61.0__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.
Files changed (81) hide show
  1. quantalogic/agent.py +268 -24
  2. quantalogic/agent_config.py +5 -5
  3. quantalogic/agent_factory.py +2 -2
  4. quantalogic/codeact/__init__.py +0 -0
  5. quantalogic/codeact/agent.py +499 -0
  6. quantalogic/codeact/cli.py +232 -0
  7. quantalogic/codeact/constants.py +9 -0
  8. quantalogic/codeact/events.py +78 -0
  9. quantalogic/codeact/llm_util.py +76 -0
  10. quantalogic/codeact/prompts/error_format.j2 +11 -0
  11. quantalogic/codeact/prompts/generate_action.j2 +26 -0
  12. quantalogic/codeact/prompts/generate_program.j2 +39 -0
  13. quantalogic/codeact/prompts/response_format.j2 +11 -0
  14. quantalogic/codeact/tools_manager.py +135 -0
  15. quantalogic/codeact/utils.py +135 -0
  16. quantalogic/coding_agent.py +2 -2
  17. quantalogic/create_custom_agent.py +26 -78
  18. quantalogic/prompts/chat_system_prompt.j2 +10 -7
  19. quantalogic/prompts/code_2_system_prompt.j2 +190 -0
  20. quantalogic/prompts/code_system_prompt.j2 +142 -0
  21. quantalogic/prompts/doc_system_prompt.j2 +178 -0
  22. quantalogic/prompts/legal_2_system_prompt.j2 +218 -0
  23. quantalogic/prompts/legal_system_prompt.j2 +140 -0
  24. quantalogic/prompts/system_prompt.j2 +6 -2
  25. quantalogic/prompts/tools_prompt.j2 +2 -4
  26. quantalogic/prompts.py +23 -4
  27. quantalogic/python_interpreter/__init__.py +23 -0
  28. quantalogic/python_interpreter/assignment_visitors.py +63 -0
  29. quantalogic/python_interpreter/base_visitors.py +20 -0
  30. quantalogic/python_interpreter/class_visitors.py +22 -0
  31. quantalogic/python_interpreter/comprehension_visitors.py +172 -0
  32. quantalogic/python_interpreter/context_visitors.py +59 -0
  33. quantalogic/python_interpreter/control_flow_visitors.py +88 -0
  34. quantalogic/python_interpreter/exception_visitors.py +109 -0
  35. quantalogic/python_interpreter/exceptions.py +39 -0
  36. quantalogic/python_interpreter/execution.py +202 -0
  37. quantalogic/python_interpreter/function_utils.py +386 -0
  38. quantalogic/python_interpreter/function_visitors.py +209 -0
  39. quantalogic/python_interpreter/import_visitors.py +28 -0
  40. quantalogic/python_interpreter/interpreter_core.py +358 -0
  41. quantalogic/python_interpreter/literal_visitors.py +74 -0
  42. quantalogic/python_interpreter/misc_visitors.py +148 -0
  43. quantalogic/python_interpreter/operator_visitors.py +108 -0
  44. quantalogic/python_interpreter/scope.py +10 -0
  45. quantalogic/python_interpreter/visit_handlers.py +110 -0
  46. quantalogic/server/agent_server.py +1 -1
  47. quantalogic/tools/__init__.py +6 -3
  48. quantalogic/tools/action_gen.py +366 -0
  49. quantalogic/tools/duckduckgo_search_tool.py +1 -0
  50. quantalogic/tools/execute_bash_command_tool.py +114 -57
  51. quantalogic/tools/file_tracker_tool.py +49 -0
  52. quantalogic/tools/google_packages/google_news_tool.py +3 -0
  53. quantalogic/tools/image_generation/dalle_e.py +89 -137
  54. quantalogic/tools/python_tool.py +13 -0
  55. quantalogic/tools/rag_tool/__init__.py +2 -9
  56. quantalogic/tools/rag_tool/document_rag_sources_.py +728 -0
  57. quantalogic/tools/rag_tool/ocr_pdf_markdown.py +144 -0
  58. quantalogic/tools/replace_in_file_tool.py +1 -1
  59. quantalogic/tools/{search_definition_names.py → search_definition_names_tool.py} +2 -2
  60. quantalogic/tools/terminal_capture_tool.py +293 -0
  61. quantalogic/tools/tool.py +120 -22
  62. quantalogic/tools/utilities/__init__.py +2 -0
  63. quantalogic/tools/utilities/download_file_tool.py +3 -5
  64. quantalogic/tools/utilities/llm_tool.py +283 -0
  65. quantalogic/tools/utilities/selenium_tool.py +296 -0
  66. quantalogic/tools/utilities/vscode_tool.py +1 -1
  67. quantalogic/tools/web_navigation/__init__.py +5 -0
  68. quantalogic/tools/web_navigation/web_tool.py +145 -0
  69. quantalogic/tools/write_file_tool.py +72 -36
  70. quantalogic/utils/__init__.py +0 -1
  71. quantalogic/utils/test_python_interpreter.py +119 -0
  72. {quantalogic-0.59.3.dist-info → quantalogic-0.61.0.dist-info}/METADATA +7 -2
  73. {quantalogic-0.59.3.dist-info → quantalogic-0.61.0.dist-info}/RECORD +76 -35
  74. quantalogic/tools/rag_tool/document_metadata.py +0 -15
  75. quantalogic/tools/rag_tool/query_response.py +0 -20
  76. quantalogic/tools/rag_tool/rag_tool.py +0 -566
  77. quantalogic/tools/rag_tool/rag_tool_beta.py +0 -264
  78. quantalogic/utils/python_interpreter.py +0 -905
  79. {quantalogic-0.59.3.dist-info → quantalogic-0.61.0.dist-info}/LICENSE +0 -0
  80. {quantalogic-0.59.3.dist-info → quantalogic-0.61.0.dist-info}/WHEEL +0 -0
  81. {quantalogic-0.59.3.dist-info → quantalogic-0.61.0.dist-info}/entry_points.txt +0 -0
quantalogic/agent.py CHANGED
@@ -23,6 +23,7 @@ from quantalogic.utils import get_environment
23
23
  from quantalogic.utils.ask_user_validation import console_ask_for_user_validation
24
24
  from quantalogic.xml_parser import ToleranceXMLParser
25
25
  from quantalogic.xml_tool_parser import ToolParser
26
+ import uuid
26
27
 
27
28
  # Maximum ratio occupancy of the occupied memory
28
29
  MAX_OCCUPANCY = 90.0
@@ -78,7 +79,7 @@ class Agent(BaseModel):
78
79
  config: AgentConfig
79
80
  task_to_solve: str
80
81
  task_to_solve_summary: str = ""
81
- ask_for_user_validation: Callable[[str, str], Awaitable[bool]] = console_ask_for_user_validation
82
+ ask_for_user_validation: Callable[[str, str], bool] = console_ask_for_user_validation
82
83
  last_tool_call: dict[str, Any] = {} # Stores the last tool call information
83
84
  total_tokens: int = 0 # Total tokens in the conversation
84
85
  current_iteration: int = 0
@@ -91,6 +92,8 @@ class Agent(BaseModel):
91
92
  _model_name: str = PrivateAttr(default="")
92
93
  chat_system_prompt: str # Base persona prompt for chat mode
93
94
  tool_mode: Optional[str] = None # Tool or toolset to prioritize in chat mode
95
+ tracked_files: list[str] = [] # List to track files created or modified during execution
96
+ agent_mode: str = "react" # Default mode is ReAct
94
97
 
95
98
  def __init__(
96
99
  self,
@@ -98,7 +101,7 @@ class Agent(BaseModel):
98
101
  memory: AgentMemory = AgentMemory(),
99
102
  variable_store: VariableMemory = VariableMemory(),
100
103
  tools: list[Tool] = [TaskCompleteTool()],
101
- ask_for_user_validation: Callable[[str, str], Awaitable[bool]] = console_ask_for_user_validation,
104
+ ask_for_user_validation: Callable[[str, str], bool] = console_ask_for_user_validation,
102
105
  task_to_solve: str = "",
103
106
  specific_expertise: str = "General AI assistant with coding and problem-solving capabilities",
104
107
  get_environment: Callable[[], str] = get_environment,
@@ -107,6 +110,7 @@ class Agent(BaseModel):
107
110
  event_emitter: EventEmitter | None = None,
108
111
  chat_system_prompt: str | None = None,
109
112
  tool_mode: Optional[str] = None,
113
+ agent_mode: str = "react",
110
114
  ):
111
115
  """Initialize the agent with model, memory, tools, and configurations.
112
116
 
@@ -124,6 +128,7 @@ class Agent(BaseModel):
124
128
  event_emitter: EventEmitter instance for event handling
125
129
  chat_system_prompt: Optional base system prompt for chat mode persona
126
130
  tool_mode: Optional tool or toolset to prioritize in chat mode
131
+ agent_mode: Mode to use ("react" or "chat")
127
132
  """
128
133
  try:
129
134
  logger.debug("Initializing agent...")
@@ -140,8 +145,9 @@ class Agent(BaseModel):
140
145
  tools_markdown = tool_manager.to_markdown()
141
146
  logger.debug(f"Tools Markdown: {tools_markdown}")
142
147
 
148
+ logger.info(f"Agent mode: {agent_mode}")
143
149
  system_prompt_text = system_prompt(
144
- tools=tools_markdown, environment=environment, expertise=specific_expertise
150
+ tools=tools_markdown, environment=environment, expertise=specific_expertise, agent_mode=agent_mode
145
151
  )
146
152
  logger.debug(f"System prompt: {system_prompt_text}")
147
153
 
@@ -151,7 +157,7 @@ class Agent(BaseModel):
151
157
  system_prompt=system_prompt_text,
152
158
  )
153
159
 
154
- chat_system_prompt = chat_system_prompt or (
160
+ chat_system_prompt = chat_system_prompt or specific_expertise or (
155
161
  "You are a friendly, helpful AI assistant. Engage in natural conversation, "
156
162
  "answer questions, and use tools when explicitly requested or when they enhance your response."
157
163
  )
@@ -178,6 +184,7 @@ class Agent(BaseModel):
178
184
  max_tokens_working_memory=max_tokens_working_memory,
179
185
  chat_system_prompt=chat_system_prompt,
180
186
  tool_mode=tool_mode,
187
+ agent_mode=agent_mode,
181
188
  )
182
189
 
183
190
  self._model_name = model_name
@@ -301,7 +308,11 @@ class Agent(BaseModel):
301
308
  current_prompt = result.next_prompt
302
309
 
303
310
  if result.executed_tool == "task_complete":
304
- self._emit_event("task_complete", {"response": result.answer})
311
+ self._emit_event("task_complete", {
312
+ "response": result.answer,
313
+ "message": "Task execution completed",
314
+ "tracked_files": self.tracked_files if self.tracked_files else []
315
+ })
305
316
  answer = result.answer or ""
306
317
  done = True
307
318
 
@@ -316,7 +327,12 @@ class Agent(BaseModel):
316
327
  answer = f"Error: {str(e)}"
317
328
  done = True
318
329
 
319
- self._emit_event("task_solve_end")
330
+ task_solve_end_data = {
331
+ "result": answer,
332
+ "message": "Task execution completed",
333
+ "tracked_files": self.tracked_files if self.tracked_files else []
334
+ }
335
+ self._emit_event("task_solve_end", task_solve_end_data)
320
336
  return answer
321
337
 
322
338
  def chat(
@@ -374,6 +390,7 @@ class Agent(BaseModel):
374
390
 
375
391
  # Prepare chat system prompt with tool information
376
392
  tools_prompt = self._get_tools_names_prompt()
393
+ logger.debug(tools_prompt)
377
394
  if self.tool_mode:
378
395
  tools_prompt += f"\nPrioritized tool mode: {self.tool_mode}. Prefer tools related to {self.tool_mode} when applicable."
379
396
 
@@ -472,6 +489,170 @@ class Agent(BaseModel):
472
489
  self._emit_event("chat_response", {"response": response_content})
473
490
  return response_content
474
491
 
492
+
493
+ def chat_news_specific(
494
+ self,
495
+ message: str,
496
+ streaming: bool = False,
497
+ clear_memory: bool = False,
498
+ auto_tool_call: bool = True,
499
+ ) -> str:
500
+ """Engage in a conversational chat_news_specific with the user (synchronous version).
501
+
502
+ Ideal for synchronous applications. For asynchronous contexts, use `async_chat_news_specific`.
503
+
504
+ Args:
505
+ message: The user's input message
506
+ streaming: Whether to stream the response
507
+ clear_memory: Whether to clear memory before starting
508
+ auto_tool_call: Whether to automatically execute detected tool calls and interpret results
509
+
510
+ Returns:
511
+ The assistant's response
512
+ """
513
+ logger.debug(f"chat_news_specificting synchronously with message: {message}, auto_tool_call: {auto_tool_call}")
514
+ try:
515
+ loop = asyncio.get_event_loop()
516
+ except RuntimeError:
517
+ loop = asyncio.new_event_loop()
518
+ asyncio.set_event_loop(loop)
519
+
520
+ return loop.run_until_complete(self.async_chat_news_specific(message, streaming, clear_memory, auto_tool_call))
521
+
522
+ async def async_chat_news_specific(
523
+ self,
524
+ message: str,
525
+ streaming: bool = False,
526
+ clear_memory: bool = False,
527
+ auto_tool_call: bool = True,
528
+ ) -> str:
529
+ """Engage in a conversational chat with the user (asynchronous version).
530
+
531
+ Ideal for asynchronous applications. For synchronous contexts, use `chat`.
532
+
533
+ Args:
534
+ message: The user's input message
535
+ streaming: Whether to stream the response
536
+ clear_memory: Whether to clear memory before starting
537
+ auto_tool_call: Whether to automatically execute detected tool calls and interpret results
538
+
539
+ Returns:
540
+ The assistant's response
541
+ """
542
+ logger.debug(f"Chatting asynchronously with message: {message}, auto_tool_call: {auto_tool_call}")
543
+ if clear_memory:
544
+ self.clear_memory()
545
+
546
+ # Prepare chat system prompt with tool information
547
+ tools_prompt = self._get_tools_names_prompt()
548
+ logger.debug(tools_prompt)
549
+ if self.tool_mode:
550
+ tools_prompt += f"\nPrioritized tool mode: {self.tool_mode}. Prefer tools related to {self.tool_mode} when applicable."
551
+
552
+ full_chat_prompt = self._render_template(
553
+ 'chat_system_prompt.j2',
554
+ persona=self.chat_system_prompt,
555
+ tools_prompt=tools_prompt
556
+ )
557
+
558
+ if not self.memory.memory or self.memory.memory[0].role != "system":
559
+ self.memory.add(Message(role="system", content=full_chat_prompt))
560
+
561
+ self._emit_event("chat_start", {"message": message})
562
+
563
+ # Add user message to memory
564
+ self.memory.add(Message(role="user", content=message))
565
+ self._update_total_tokens(self.memory.memory, "")
566
+
567
+ # Iterative tool usage with auto-execution
568
+ current_prompt = message
569
+ response_content = ""
570
+ max_tool_iterations = 5 # Prevent infinite tool loops
571
+ tool_iteration = 0
572
+
573
+ while tool_iteration < max_tool_iterations:
574
+ try:
575
+ if streaming:
576
+ content = ""
577
+ # When streaming is enabled, the GenerativeModel._async_stream_response method
578
+ # already emits the stream_chunk events, so we don't need to emit them again here
579
+ async_stream = await self.model.async_generate_with_history(
580
+ messages_history=self.memory.memory,
581
+ prompt=current_prompt,
582
+ streaming=True,
583
+ )
584
+ # Just collect the chunks without re-emitting events
585
+ async for chunk in async_stream:
586
+ content += chunk
587
+ response = ResponseStats(
588
+ response=content,
589
+ usage=TokenUsage(prompt_tokens=0, completion_tokens=0, total_tokens=0),
590
+ model=self.model.model,
591
+ finish_reason="stop",
592
+ )
593
+ else:
594
+ response = await self.model.async_generate_with_history(
595
+ messages_history=self.memory.memory,
596
+ prompt=current_prompt,
597
+ streaming=False,
598
+ )
599
+ content = response.response
600
+
601
+ self.total_tokens = response.usage.total_tokens if not streaming else self.total_tokens
602
+
603
+ # Observe response for tool calls
604
+ observation = await self._async_observe_response(content)
605
+ if observation.executed_tool and auto_tool_call:
606
+ print("observation.executed_tool : ", observation.executed_tool)
607
+ # If any news tool is used, return immediately
608
+ if "googlenews" in observation.executed_tool.lower() or \
609
+ "duckduckgo" in observation.executed_tool.lower() or \
610
+ "duckduckgosearch" in observation.executed_tool.lower():
611
+ self._emit_event("chat_response", {"response": observation.next_prompt})
612
+ return observation.next_prompt
613
+ # Tool was executed; process result and continue
614
+ current_prompt = observation.next_prompt
615
+
616
+ # In chat mode, format the response with clear tool call visualization
617
+ if not self.task_to_solve.strip(): # We're in chat mode
618
+ # Format the response to clearly show the tool call and result
619
+ # Use a format that task_runner.py can parse and display nicely
620
+
621
+ # For a cleaner look, insert a special delimiter that task_runner.py can recognize
622
+ # to separate tool call from result
623
+ response_content = f"{content}\n\n__TOOL_RESULT_SEPARATOR__{observation.executed_tool}__\n{observation.next_prompt}"
624
+ else:
625
+ # In task mode, keep the original behavior
626
+ response_content = observation.next_prompt
627
+
628
+ tool_iteration += 1
629
+ self.memory.add(Message(role="assistant", content=content)) # Original tool call
630
+ self.memory.add(Message(role="user", content=observation.next_prompt)) # Tool result
631
+ logger.debug(f"Tool executed: {observation.executed_tool}, iteration: {tool_iteration}")
632
+ elif not observation.executed_tool and "<action>" in content and auto_tool_call:
633
+ # Detected malformed tool call attempt; provide feedback and exit loop
634
+ response_content = (
635
+ f"{content}\n\n⚠️ Error: Invalid tool call format detected. "
636
+ "Please use the exact XML structure as specified in the system prompt:\n"
637
+ "```xml\n<action>\n<tool_name>\n <parameter_name>value</parameter_name>\n</tool_name>\n</action>\n```"
638
+ )
639
+ break
640
+ else:
641
+ # No tool executed or auto_tool_call is False; final response
642
+ response_content = content
643
+ break
644
+
645
+ except Exception as e:
646
+ logger.error(f"Error during async chat: {str(e)}")
647
+ response_content = f"Error: {str(e)}"
648
+ break
649
+
650
+ self._update_session_memory(message, response_content)
651
+ self._emit_event("chat_response", {"response": response_content})
652
+ return response_content
653
+
654
+
655
+
475
656
  def _observe_response(self, content: str, iteration: int = 1) -> ObserveResponseResult:
476
657
  """Analyze the assistant's response and determine next steps (synchronous wrapper).
477
658
 
@@ -535,11 +716,16 @@ class Agent(BaseModel):
535
716
  if not executed_tool:
536
717
  return self._handle_tool_execution_failure(response)
537
718
 
719
+ # Track files when write_file_tool or writefile is used
720
+ if (tool_name in ["write_file_tool", "writefile", "edit_whole_content", "replace_in_file", "replaceinfile", "EditWholeContent"]) and "file_path" in arguments_with_values:
721
+ self._track_file(arguments_with_values["file_path"], tool_name)
722
+
538
723
  variable_name = self.variable_store.add(response)
539
724
  new_prompt = self._format_observation_response(response, executed_tool, variable_name, iteration)
540
725
 
541
726
  # In chat mode, don't set answer; in task mode, set answer only for task_complete
542
727
  is_task_complete_answer = executed_tool == "task_complete" and not is_chat_mode
728
+
543
729
  return ObserveResponseResult(
544
730
  next_prompt=new_prompt,
545
731
  executed_tool=executed_tool,
@@ -586,12 +772,12 @@ class Agent(BaseModel):
586
772
  logger.info(f"Tool '{tool_name}' requires validation.")
587
773
  validation_id = str(uuid.uuid4())
588
774
  logger.info(f"Validation ID: {validation_id}")
589
-
775
+
590
776
  self._emit_event(
591
777
  "tool_execute_validation_start",
592
778
  {
593
779
  "validation_id": validation_id,
594
- "tool_name": tool_name,
780
+ "tool_name": tool_name,
595
781
  "arguments": arguments_with_values
596
782
  },
597
783
  )
@@ -602,7 +788,7 @@ class Agent(BaseModel):
602
788
  + "\n".join([f" <{key}>{value}</{key}>" for key, value in arguments_with_values.items()])
603
789
  + "\n</arguments>\nYes or No"
604
790
  )
605
- permission_granted = await self.ask_for_user_validation(validation_id=validation_id, question=question_validation)
791
+ permission_granted = await self.ask_for_user_validation(validation_id, question_validation)
606
792
 
607
793
  self._emit_event(
608
794
  "tool_execute_validation_end",
@@ -639,13 +825,14 @@ class Agent(BaseModel):
639
825
  response = tool.execute(**converted_args)
640
826
 
641
827
  # Post-process tool response if needed
642
- response = self._post_process_tool_response(tool_name, response)
828
+ if (tool.need_post_process):
829
+ response = self._post_process_tool_response(tool_name, response)
643
830
 
644
831
  executed_tool = tool.name
645
832
  except Exception as e:
646
833
  response = f"Error executing tool: {tool_name}: {str(e)}\n"
647
834
  executed_tool = ""
648
-
835
+
649
836
  self._emit_event(
650
837
  "tool_execution_end", {"tool_name": tool_name, "arguments": arguments_with_values, "response": response}
651
838
  )
@@ -1311,21 +1498,45 @@ class Agent(BaseModel):
1311
1498
 
1312
1499
  required = "(required)" if param_info.get("required", False) else "(optional)"
1313
1500
  default = f" default: {param_info['default']}" if "default" in param_info else ""
1314
- param_desc = f"{param_name} {required}{default}"
1501
+ param_type = param_info.get("type", "string")
1502
+ param_desc = f"{param_name} ({param_type}) {required}{default}"
1315
1503
  params.append(param_desc)
1316
1504
  except Exception as e:
1317
1505
  logger.debug(f"Error parsing schema for {tool_name}: {str(e)}")
1318
-
1319
- # Special case for duckduckgo_tool
1320
- if tool_name == "duckduckgo_tool" and not any(p.startswith("max_results ") for p in params):
1321
- params.append("max_results (required) default: 5")
1322
-
1323
- # Special case for other search tools that might need max_results
1324
- if "search" in tool_name.lower() and not any(p.startswith("max_results ") for p in params):
1325
- params.append("max_results (optional) default: 5")
1326
-
1327
- param_str = ", ".join(params) if params else "No parameters required"
1328
- tool_descriptions.append(f"{tool_name}: {param_str}")
1506
+
1507
+ # Enhanced tool-specific parameter descriptions
1508
+ if tool_name == "googlenews":
1509
+ params = [
1510
+ "query (string, required) - The search query string",
1511
+ "language (string, optional) default: en - Language code (e.g., en, fr, es)",
1512
+ "period (string, optional) default: 7d - Time period (1d, 7d, 30d)",
1513
+ "max_results (integer, required) default: 5 - Number of results to return",
1514
+ "country (string, optional) default: US - Country code (e.g., US, GB, FR)",
1515
+ "sort_by (string, optional) default: relevance - Sort by (relevance, date)",
1516
+ "analyze (boolean, optional) default: False - Whether to analyze results"
1517
+ ]
1518
+ elif tool_name == "duckduckgosearch":
1519
+ params = [
1520
+ "query (string, required) - The search query string",
1521
+ "max_results (integer, required) default: 5 - Number of results to return",
1522
+ "time_period (string, optional) default: d - Time period (d: day, w: week, m: month)",
1523
+ "region (string, optional) default: wt-wt - Region code for search results"
1524
+ ]
1525
+ elif tool_name == "llm":
1526
+ params = [
1527
+ "system_prompt (string, required) - The persona or system prompt to guide the language model's behavior",
1528
+ "prompt (string, required) - The question to ask the language model. Supports interpolation with $var$ syntax",
1529
+ "temperature (float, required) default: 0.5 - Sampling temperature between 0.0 (no creativity) and 1.0 (full creativity)"
1530
+ ]
1531
+ elif tool_name == "task_complete":
1532
+ params = [
1533
+ "answer (string, required) - Your final answer or response to complete the task"
1534
+ ]
1535
+ elif "search" in tool_name.lower() and not params:
1536
+ params.append("max_results (integer, optional) default: 5 - Number of results to return")
1537
+
1538
+ param_str = "\n - ".join(params) if params else "No parameters required"
1539
+ tool_descriptions.append(f"{tool_name}:\n - {param_str}")
1329
1540
  except Exception as e:
1330
1541
  logger.debug(f"Error processing tool {tool_name}: {str(e)}")
1331
1542
  # Still include the tool in the list, but with minimal info
@@ -1468,4 +1679,37 @@ class Agent(BaseModel):
1468
1679
  return template.render(**kwargs)
1469
1680
  except Exception as e:
1470
1681
  logger.error(f"Error rendering template {template_name}: {str(e)}")
1471
- raise
1682
+ raise
1683
+
1684
+ def _track_file(self, file_path: str, tool_name: str) -> None:
1685
+ """Track files created or modified by tools.
1686
+
1687
+ Args:
1688
+ file_path: Path to the file to track
1689
+ tool_name: Name of the tool that created/modified the file
1690
+ """
1691
+ try:
1692
+ # Handle /tmp directory for write tools
1693
+ if tool_name in ["write_file_tool", "writefile", "edit_whole_content", "replace_in_file", "replaceinfile", "EditWholeContent"]:
1694
+ if not file_path.startswith("/tmp/"):
1695
+ file_path = os.path.join("/tmp", file_path.lstrip("/"))
1696
+
1697
+ # For other tools, ensure we have absolute path
1698
+ elif not os.path.isabs(file_path):
1699
+ file_path = os.path.abspath(os.path.join(os.getcwd(), file_path))
1700
+
1701
+ # Resolve any . or .. in the path
1702
+ tracked_path = os.path.realpath(file_path)
1703
+
1704
+ # For write tools, ensure path is in /tmp
1705
+ if tool_name in ["write_file_tool", "writefile"] and not tracked_path.startswith("/tmp/"):
1706
+ logger.warning(f"Attempted to track file outside /tmp: {tracked_path}")
1707
+ return
1708
+
1709
+ # Add to tracked files if not already present
1710
+ if tracked_path not in self.tracked_files:
1711
+ self.tracked_files.append(tracked_path)
1712
+ logger.debug(f"Added {tracked_path} to tracked files")
1713
+
1714
+ except Exception as e:
1715
+ logger.error(f"Error tracking file {file_path}: {str(e)}")
@@ -27,7 +27,7 @@ from quantalogic.tools import (
27
27
  ReadHTMLTool,
28
28
  ReplaceInFileTool,
29
29
  RipgrepTool,
30
- SearchDefinitionNames,
30
+ SearchDefinitionNamesTool,
31
31
  TaskCompleteTool,
32
32
  WikipediaSearchTool,
33
33
  WriteFileTool,
@@ -82,7 +82,7 @@ def create_agent(
82
82
  ExecuteBashCommandTool(),
83
83
  ReplaceInFileTool(),
84
84
  RipgrepTool(),
85
- SearchDefinitionNames(),
85
+ SearchDefinitionNamesTool(),
86
86
  MarkitdownTool(),
87
87
  LLMTool(model_name=model_name, on_token=console_print_token if not no_stream else None),
88
88
  DownloadHttpFileTool(),
@@ -138,7 +138,7 @@ def create_interpreter_agent(
138
138
  RipgrepTool(),
139
139
  PythonTool(),
140
140
  NodeJsTool(),
141
- SearchDefinitionNames(),
141
+ SearchDefinitionNamesTool(),
142
142
  MarkitdownTool(),
143
143
  LLMTool(model_name=model_name, on_token=console_print_token if not no_stream else None),
144
144
  DownloadHttpFileTool(),
@@ -185,7 +185,7 @@ def create_full_agent(
185
185
  RipgrepTool(),
186
186
  PythonTool(),
187
187
  NodeJsTool(),
188
- SearchDefinitionNames(),
188
+ SearchDefinitionNamesTool(),
189
189
  MarkitdownTool(),
190
190
  LLMTool(model_name=model_name, on_token=console_print_token if not no_stream else None),
191
191
  DownloadHttpFileTool(),
@@ -234,7 +234,7 @@ def create_basic_agent(
234
234
  TaskCompleteTool(),
235
235
  ListDirectoryTool(),
236
236
  ReadFileBlockTool(),
237
- SearchDefinitionNames(),
237
+ SearchDefinitionNamesTool(),
238
238
  ReadFileTool(),
239
239
  ReplaceInFileTool(),
240
240
  WriteFileTool(),
@@ -7,7 +7,7 @@ from quantalogic.agent_config import (
7
7
  DuckDuckGoSearchTool,
8
8
  NodeJsTool,
9
9
  PythonTool,
10
- SearchDefinitionNames,
10
+ SearchDefinitionNamesTool,
11
11
  TaskCompleteTool,
12
12
  WikipediaSearchTool,
13
13
  create_basic_agent,
@@ -107,7 +107,7 @@ def create_agent_for_mode(
107
107
  if tool_mode == "search":
108
108
  tools.extend([DuckDuckGoSearchTool(), WikipediaSearchTool()])
109
109
  elif tool_mode == "code":
110
- tools.extend([PythonTool(), NodeJsTool(), SearchDefinitionNames()])
110
+ tools.extend([PythonTool(), NodeJsTool(), SearchDefinitionNamesTool()])
111
111
  elif tool_mode in [t.name for t in tools]: # Specific tool name
112
112
  tools = [t for t in tools if t.name == tool_mode or isinstance(t, TaskCompleteTool)]
113
113
  else:
File without changes