quantalogic 0.2.18__py3-none-any.whl → 0.2.20__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.
quantalogic/agent.py CHANGED
@@ -56,8 +56,8 @@ class Agent(BaseModel):
56
56
 
57
57
  specific_expertise: str
58
58
  model: GenerativeModel
59
- memory: AgentMemory = AgentMemory()
60
- variable_store: VariableMemory = VariableMemory()
59
+ memory: AgentMemory = AgentMemory() # A list User / Assistant Messages
60
+ variable_store: VariableMemory = VariableMemory() # A dictionary of variables (var1: value1, var2: value2)
61
61
  tools: ToolManager = ToolManager()
62
62
  event_emitter: EventEmitter = EventEmitter()
63
63
  config: AgentConfig
@@ -78,13 +78,14 @@ class Agent(BaseModel):
78
78
  self,
79
79
  model_name: str = "",
80
80
  memory: AgentMemory = AgentMemory(),
81
+ variable_store: VariableMemory = VariableMemory(),
81
82
  tools: list[Tool] = [TaskCompleteTool()],
82
83
  ask_for_user_validation: Callable[[str], bool] = console_ask_for_user_validation,
83
84
  task_to_solve: str = "",
84
85
  specific_expertise: str = "General AI assistant with coding and problem-solving capabilities",
85
86
  get_environment: Callable[[], str] = get_environment,
86
- compact_every_n_iterations: int | None = None, # New parameter
87
- max_tokens_working_memory: int | None = None, # New parameter to set max working memory tokens
87
+ compact_every_n_iterations: int | None = None, # if set the memory will be compacted every n iterations
88
+ max_tokens_working_memory: int | None = None, # if set the memory will be compacted each time the max_tokens_working_memory is reached
88
89
  ):
89
90
  """Initialize the agent with model, memory, tools, and configurations."""
90
91
  try:
@@ -117,7 +118,7 @@ class Agent(BaseModel):
117
118
  super().__init__(
118
119
  model=GenerativeModel(model=model_name, event_emitter=event_emitter),
119
120
  memory=memory,
120
- variable_store=VariableMemory(),
121
+ variable_store=variable_store,
121
122
  tools=tool_manager,
122
123
  config=config,
123
124
  ask_for_user_validation=ask_for_user_validation,
@@ -422,7 +423,7 @@ class Agent(BaseModel):
422
423
  answer=None,
423
424
  )
424
425
 
425
- def _handle_repeated_tool_call(self, tool_name: str, arguments_with_values: dict) -> ObserveResponseResult:
426
+ def _handle_repeated_tool_call(self, tool_name: str, arguments_with_values: dict) -> (str,str):
426
427
  """Handle the case where a tool call is repeated."""
427
428
  repeat_count = self.last_tool_call.get("count", 0)
428
429
  error_message = (
@@ -436,11 +437,7 @@ class Agent(BaseModel):
436
437
  "3. Use a different tool or modify the arguments\n"
437
438
  "4. Ensure you're making progress towards the goal"
438
439
  )
439
- return ObserveResponseResult(
440
- next_prompt=error_message,
441
- executed_tool="",
442
- answer=None,
443
- )
440
+ return tool_name, error_message
444
441
 
445
442
  def _handle_tool_execution_failure(self, response: str) -> ObserveResponseResult:
446
443
  """Handle the case where tool execution fails."""
@@ -527,6 +524,13 @@ class Agent(BaseModel):
527
524
  tuple containing:
528
525
  - executed_tool name (str)
529
526
  - tool execution response (Any)
527
+
528
+ Note:
529
+ Predefined variable properties take precedence over dynamically provided values.
530
+ This ensures consistent behavior when tools have both predefined properties
531
+ and runtime-provided arguments. The precedence order is:
532
+ 1. Predefined properties from tool.get_injectable_properties_in_execution()
533
+ 2. Runtime-provided arguments from arguments_with_values
530
534
  """
531
535
  # Handle tool validation if required
532
536
  if tool.need_validation:
@@ -564,6 +568,19 @@ class Agent(BaseModel):
564
568
  arguments_with_values_interpolated = {
565
569
  key: self._interpolate_variables(value) for key, value in arguments_with_values.items()
566
570
  }
571
+ # test if tool need variables in context
572
+ if tool.need_variables:
573
+ # Inject variables into the tool if needed
574
+ arguments_with_values_interpolated["variables"] = self.variable_store
575
+ if tool.need_caller_context_memory:
576
+ # Inject caller context into the tool if needed
577
+ arguments_with_values_interpolated["caller_context_memory"] = self.memory.memory
578
+
579
+ # Add injectable variables
580
+ injectable_properties = tool.get_injectable_properties_in_execution()
581
+ for key, value in injectable_properties.items():
582
+ arguments_with_values_interpolated[key] = value
583
+
567
584
  # Call tool execute with named arguments
568
585
  response = tool.execute(**arguments_with_values_interpolated)
569
586
  executed_tool = tool.name
@@ -5,6 +5,7 @@ from quantalogic.tools import (
5
5
  EditWholeContentTool,
6
6
  ExecuteBashCommandTool,
7
7
  InputQuestionTool,
8
+ JinjaTool,
8
9
  ListDirectoryTool,
9
10
  LLMTool,
10
11
  LLMVisionTool,
@@ -72,6 +73,7 @@ def create_coding_agent(
72
73
  ExecuteBashCommandTool(),
73
74
  InputQuestionTool(),
74
75
  DuckDuckGoSearchTool(),
76
+ JinjaTool(),
75
77
  ]
76
78
 
77
79
  if vision_model_name:
@@ -85,6 +87,9 @@ def create_coding_agent(
85
87
  name="coding_consultant", # Handles implementation-level coding questions
86
88
  on_token=console_print_token if not no_stream else None,
87
89
  )
90
+ # Note: system_prompt is predefined in LLMTool properties and takes precedence over
91
+ # dynamically provided values. This ensures consistent behavior by prioritizing
92
+ # the predefined system prompt. See tool.py and agent.py for implementation details.
88
93
  )
89
94
  tools.append(
90
95
  LLMTool(
@@ -94,6 +99,14 @@ def create_coding_agent(
94
99
  on_token=console_print_token if not no_stream else None,
95
100
  )
96
101
  )
102
+ ## Add a generic LLMTool
103
+ tools.append(
104
+ LLMTool(
105
+ model_name=model_name,
106
+ name="llm_tool",
107
+ on_token=console_print_token if not no_stream else None,
108
+ )
109
+ )
97
110
 
98
111
  return Agent(
99
112
  model_name=model_name,
@@ -2,10 +2,12 @@
2
2
 
3
3
  from .agent_tool import AgentTool
4
4
  from .download_http_file_tool import DownloadHttpFileTool
5
+ from .duckduckgo_search_tool import DuckDuckGoSearchTool
5
6
  from .edit_whole_content_tool import EditWholeContentTool
6
7
  from .elixir_tool import ElixirTool
7
8
  from .execute_bash_command_tool import ExecuteBashCommandTool
8
9
  from .input_question_tool import InputQuestionTool
10
+ from .jinja_tool import JinjaTool
9
11
  from .list_directory_tool import ListDirectoryTool
10
12
  from .llm_tool import LLMTool
11
13
  from .llm_vision_tool import LLMVisionTool
@@ -18,7 +20,6 @@ from .replace_in_file_tool import ReplaceInFileTool
18
20
  from .ripgrep_tool import RipgrepTool
19
21
  from .search_definition_names import SearchDefinitionNames
20
22
  from .serpapi_search_tool import SerpApiSearchTool
21
- from .duckduckgo_search_tool import DuckDuckGoSearchTool
22
23
  from .task_complete_tool import TaskCompleteTool
23
24
  from .tool import Tool, ToolArgument
24
25
  from .unified_diff_tool import UnifiedDiffTool
@@ -51,4 +52,5 @@ __all__ = [
51
52
  "MarkitdownTool",
52
53
  "DownloadHttpFileTool",
53
54
  "EditWholeContentTool",
55
+ "JinjaTool",
54
56
  ]
@@ -0,0 +1,91 @@
1
+ """Tool for rendering Jinja templates with inline template string."""
2
+
3
+ from typing import Any, Dict, Optional
4
+
5
+ from jinja2 import Template
6
+ from loguru import logger
7
+ from pydantic import Field
8
+
9
+ from quantalogic.tools.tool import Tool, ToolArgument
10
+
11
+
12
+ class JinjaTool(Tool):
13
+ """Tool for rendering Jinja templates with inline template string."""
14
+
15
+ name: str = "jinja_tool"
16
+ description: str = (
17
+ "Renders an inline Jinja2 template string with a predefined context.\n"
18
+ "You can use the variables in the template just as var1, var2, etc.\n"
19
+ "Useful for simple calculations or string operations.\n"
20
+ )
21
+ arguments: list = [
22
+ ToolArgument(
23
+ name="inline_template",
24
+ arg_type="string",
25
+ description="Inline Jinja2 template string to render. Has access to variables in the context.",
26
+ required=True,
27
+ example="Hello, {{ var1 }}! You have {{ var2|length }} items.",
28
+ ),
29
+ ]
30
+ need_variables: bool = True
31
+
32
+ # Add context as a field with a default empty dictionary
33
+ context: Dict[str, Any] = Field(default_factory=dict)
34
+
35
+ def __init__(self):
36
+ """
37
+ Initialize JinjaTool with optional context.
38
+
39
+ Args:
40
+ context (dict, optional): Context dictionary for template rendering.
41
+ """
42
+ super().__init__()
43
+
44
+ def execute(self, inline_template: str,variables: Optional[Dict[str, Any]] = None) -> str:
45
+ """
46
+ Render an inline Jinja2 template with the predefined context.
47
+
48
+ Args:
49
+ inline_template (str): Inline template string to render.
50
+ variables (dict, optional): Additional variables to include in the context.
51
+
52
+ Returns:
53
+ str: Rendered template content.
54
+ """
55
+ try:
56
+ # Create Jinja2 template
57
+ template = Template(inline_template)
58
+
59
+ context = {}
60
+ if variables:
61
+ context.update(variables)
62
+
63
+ rendered_content = template.render(**context)
64
+
65
+ logger.info("Successfully rendered inline Jinja template")
66
+ return rendered_content
67
+
68
+ except Exception as e:
69
+ logger.error(f"Error rendering Jinja template: {e}")
70
+ raise
71
+
72
+
73
+ if __name__ == "__main__":
74
+ # Example of using JinjaTool with variables
75
+ tool = JinjaTool(context={
76
+ "var1": "World",
77
+ "var2": 42,
78
+ "var3": ["apple", "banana", "cherry"]
79
+ })
80
+
81
+ # Inline template demonstrating variable usage
82
+ template = "Hello {{ var1 }}! The answer is {{ var2 }}. Fruits: {% for fruit in var3 %}{{ fruit }}{% if not loop.last %}, {% endif %}{% endfor %}."
83
+
84
+ # Render the template
85
+ result = tool.execute(template)
86
+ print("Rendered Template:")
87
+ print(result)
88
+
89
+ # Print the tool's markdown representation
90
+ print("\nTool Markdown:")
91
+ print(tool.to_markdown())
@@ -167,3 +167,8 @@ if __name__ == "__main__":
167
167
  pirate_answer = pirate.execute(system_prompt=system_prompt, prompt=question, temperature=temperature)
168
168
  print("\n")
169
169
  print(f"Anwser: {pirate_answer}")
170
+
171
+ custom_tool = LLMTool(
172
+ model_name="openrouter/openai/gpt-4o-mini", system_prompt="You are a pirate.", on_token=console_print_token
173
+ )
174
+ print(custom_tool.to_markdown())
@@ -63,6 +63,20 @@ class SearchDefinitionNames(Tool):
63
63
  required=False,
64
64
  example="**/*.py",
65
65
  ),
66
+ ToolArgument(
67
+ name="page",
68
+ arg_type="int",
69
+ description="The page number to retrieve (1-based index).",
70
+ required=False,
71
+ example="1",
72
+ ),
73
+ ToolArgument(
74
+ name="page_size",
75
+ arg_type="int",
76
+ description="The number of results per page (default: 10).",
77
+ required=False,
78
+ example="10",
79
+ ),
66
80
  ]
67
81
 
68
82
  def _get_language_handler(self, language_name: str):
@@ -89,7 +103,7 @@ class SearchDefinitionNames(Tool):
89
103
  raise ValueError(f"Unsupported language: {language_name}")
90
104
 
91
105
  def execute(
92
- self, directory_path: str, language_name: str, file_pattern: str = "*", output_format: str = "text"
106
+ self, directory_path: str, language_name: str, file_pattern: str = "*", output_format: str = "text", page: int = 1, page_size: int = 10
93
107
  ) -> Union[str, Dict]:
94
108
  """Searches for definition names in a directory using Tree-sitter.
95
109
 
@@ -98,11 +112,19 @@ class SearchDefinitionNames(Tool):
98
112
  language_name (str): The Tree-sitter language name.
99
113
  file_pattern (str): Optional glob pattern to filter files (default: '*').
100
114
  output_format (str): Output format ('text', 'json', 'markdown').
115
+ page (int): The page number to retrieve (1-based index).
116
+ page_size (int): The number of results per page (default: 10).
101
117
 
102
118
  Returns:
103
119
  Union[str, Dict]: The search results in the specified format.
104
120
  """
105
121
  try:
122
+ # Validate pagination parameters
123
+ if page < 1:
124
+ raise ValueError("Page number must be a positive integer.")
125
+ if page_size < 1:
126
+ raise ValueError("Page size must be a positive integer.")
127
+
106
128
  # Set up Tree-sitter based on language
107
129
  language_handler = self._get_language_handler(language_name)
108
130
  parser = Parser(language_handler.get_language())
@@ -140,7 +162,21 @@ class SearchDefinitionNames(Tool):
140
162
  logger.warning(f"Error processing file {file_path}: {str(e)}")
141
163
  continue
142
164
 
143
- return self._format_results(results, directory_path, language_name, file_pattern, output_format)
165
+ # Apply pagination
166
+ start_index = (page - 1) * page_size
167
+ end_index = start_index + page_size
168
+ paginated_results = results[start_index:end_index]
169
+
170
+ return self._format_results(
171
+ paginated_results,
172
+ directory_path,
173
+ language_name,
174
+ file_pattern,
175
+ output_format,
176
+ page,
177
+ page_size,
178
+ len(results)
179
+ )
144
180
 
145
181
  except Exception as e:
146
182
  logger.error(f"Error during search: {str(e)}")
@@ -263,6 +299,9 @@ class SearchDefinitionNames(Tool):
263
299
  language_name: str,
264
300
  file_pattern: str,
265
301
  output_format: str = "text",
302
+ page: int = 1,
303
+ page_size: int = 10,
304
+ total_results: int = 0,
266
305
  ) -> Union[str, Dict]:
267
306
  """Formats the search results in the specified format.
268
307
 
@@ -272,6 +311,9 @@ class SearchDefinitionNames(Tool):
272
311
  language_name: The language that was searched.
273
312
  file_pattern: The file pattern that was used.
274
313
  output_format: The desired output format.
314
+ page: The page number.
315
+ page_size: The number of results per page.
316
+ total_results: The total number of results.
275
317
 
276
318
  Returns:
277
319
  Union[str, Dict]: The formatted results.
@@ -281,6 +323,9 @@ class SearchDefinitionNames(Tool):
281
323
  "directory": directory_path,
282
324
  "language": language_name,
283
325
  "file_pattern": file_pattern,
326
+ "page": page,
327
+ "page_size": page_size,
328
+ "total_results": total_results,
284
329
  "results": [],
285
330
  }
286
331
 
@@ -318,7 +363,10 @@ class SearchDefinitionNames(Tool):
318
363
  markdown = "# Search Results\n\n"
319
364
  markdown += f"- **Directory**: `{directory_path}`\n"
320
365
  markdown += f"- **Language**: `{language_name}`\n"
321
- markdown += f"- **File Pattern**: `{file_pattern}`\n\n"
366
+ markdown += f"- **File Pattern**: `{file_pattern}`\n"
367
+ markdown += f"- **Page**: {page}\n"
368
+ markdown += f"- **Page Size**: {page_size}\n"
369
+ markdown += f"- **Total Results**: {total_results}\n\n"
322
370
 
323
371
  for result in results:
324
372
  markdown += f"## File: {result['file_path']}\n"
@@ -354,7 +402,10 @@ class SearchDefinitionNames(Tool):
354
402
  text += "==============\n"
355
403
  text += f"Directory: {directory_path}\n"
356
404
  text += f"Language: {language_name}\n"
357
- text += f"File Pattern: {file_pattern}\n\n"
405
+ text += f"File Pattern: {file_pattern}\n"
406
+ text += f"Page: {page}\n"
407
+ text += f"Page Size: {page_size}\n"
408
+ text += f"Total Results: {total_results}\n\n"
358
409
 
359
410
  for result in results:
360
411
  text += f"File: {result['file_path']}\n"
quantalogic/tools/tool.py CHANGED
@@ -6,6 +6,7 @@ with type-validated arguments and execution methods.
6
6
 
7
7
  from typing import Any, Literal
8
8
 
9
+ from loguru import logger
9
10
  from pydantic import BaseModel, ConfigDict, Field, field_validator
10
11
 
11
12
 
@@ -27,9 +28,10 @@ class ToolArgument(BaseModel):
27
28
  )
28
29
  description: str | None = Field(default=None, description="A brief description of the argument.")
29
30
  required: bool = Field(default=False, description="Indicates if the argument is required.")
30
- default: str | None = Field(default=None, description="The default value for the argument. This parameter is required.")
31
+ default: str | None = Field(
32
+ default=None, description="The default value for the argument. This parameter is required."
33
+ )
31
34
  example: str | None = Field(default=None, description="An example value to illustrate the argument's usage.")
32
- need_validation: bool = Field(default=False, description="Indicates if the argument needs validation.")
33
35
 
34
36
 
35
37
  class ToolDefinition(BaseModel):
@@ -42,12 +44,49 @@ class ToolDefinition(BaseModel):
42
44
  need_validation: Flag to indicate if tool requires validation.
43
45
  """
44
46
 
45
- model_config = ConfigDict(extra="forbid", validate_assignment=True)
47
+ model_config = ConfigDict(extra="allow", validate_assignment=True)
46
48
 
47
49
  name: str = Field(..., description="The unique name of the tool.")
48
50
  description: str = Field(..., description="A brief description of what the tool does.")
49
51
  arguments: list[ToolArgument] = Field(default_factory=list, description="A list of arguments the tool accepts.")
50
- need_validation: bool = Field(default=False, description="Indicates if the tool needs validation.")
52
+ need_validation: bool = Field(
53
+ default=False,
54
+ description="When True, requires user confirmation before execution. Useful for tools that perform potentially destructive operations.",
55
+ )
56
+ need_variables: bool = Field(
57
+ default=False,
58
+ description="When True, provides access to the agent's variable store. Required for tools that need to interpolate variables (e.g., Jinja templates).",
59
+ )
60
+ need_caller_context_memory: bool = Field(
61
+ default=False,
62
+ description="When True, provides access to the agent's conversation history. Useful for tools that need context from previous interactions.",
63
+ )
64
+
65
+ def get_properties(self, exclude: list[str] | None = None) -> dict[str, Any]:
66
+ """Return a dictionary of all non-None properties, excluding Tool class fields and specified fields.
67
+
68
+ Args:
69
+ exclude: Optional list of field names to exclude from the result
70
+
71
+ Returns:
72
+ Dictionary of property names and values, excluding Tool class fields and specified fields.
73
+ """
74
+ exclude = exclude or []
75
+ tool_fields = {
76
+ "name",
77
+ "description",
78
+ "arguments",
79
+ "need_validation",
80
+ "need_variables",
81
+ "need_caller_context_memory",
82
+ }
83
+ properties = {}
84
+
85
+ for name, value in self.__dict__.items():
86
+ if name not in tool_fields and name not in exclude and value is not None and not name.startswith("_"):
87
+ properties[name] = value
88
+
89
+ return properties
51
90
 
52
91
  def to_json(self) -> str:
53
92
  """Convert the tool to a JSON string representation.
@@ -67,10 +106,21 @@ class ToolDefinition(BaseModel):
67
106
  markdown = f"`{self.name}`:\n"
68
107
  markdown += f"- **Description**: {self.description}\n\n"
69
108
 
109
+ properties_injectable = self.get_injectable_properties_in_execution()
110
+
70
111
  # Parameters section
71
112
  if self.arguments:
72
113
  markdown += "- **Parameters**:\n"
114
+ parameters = ""
73
115
  for arg in self.arguments:
116
+ # Skip if parameter name matches an object property with non-None value
117
+ # This enables automatic property injection during execution:
118
+ # When an object has a property matching an argument name,
119
+ # the agent will inject the property value at runtime,
120
+ # reducing manual input and improving flexibility
121
+ if properties_injectable.get(arg.name) is not None:
122
+ continue
123
+
74
124
  required_status = "required" if arg.required else "optional"
75
125
  # Prioritize example, then default, then create a generic description
76
126
  value_info = ""
@@ -79,12 +129,15 @@ class ToolDefinition(BaseModel):
79
129
  elif arg.default is not None:
80
130
  value_info = f" (default: `{arg.default}`)"
81
131
 
82
- markdown += (
132
+ parameters += (
83
133
  f" - `{arg.name}`: "
84
134
  f"({required_status}{value_info})\n"
85
- f" {arg.description or 'No description provided.'}\n"
135
+ f" {arg.description or ''}\n"
86
136
  )
87
- markdown += "\n"
137
+ if len(parameters) > 0:
138
+ markdown += parameters + "\n\n"
139
+ else:
140
+ markdown += "None\n\n"
88
141
 
89
142
  # Usage section with XML-style example
90
143
  markdown += "**Usage**:\n"
@@ -93,6 +146,8 @@ class ToolDefinition(BaseModel):
93
146
 
94
147
  # Generate example parameters
95
148
  for arg in self.arguments:
149
+ if properties_injectable.get(arg.name) is not None:
150
+ continue
96
151
  # Prioritize example, then default, then create a generic example
97
152
  example_value = arg.example or arg.default or f"Your {arg.name} here"
98
153
  markdown += f" <{arg.name}>{example_value}</{arg.name}>\n"
@@ -102,6 +157,18 @@ class ToolDefinition(BaseModel):
102
157
 
103
158
  return markdown
104
159
 
160
+ def get_non_injectable_arguments(self) -> list[ToolArgument]:
161
+ """Get arguments that cannot be injected from properties.
162
+
163
+ Returns:
164
+ List of ToolArgument instances that cannot be injected by the agent.
165
+ """
166
+ properties_injectable = self.get_injectable_properties_in_execution()
167
+
168
+ return [
169
+ arg for arg in self.arguments if properties_injectable.get(arg.name) is None
170
+ ]
171
+
105
172
 
106
173
  class Tool(ToolDefinition):
107
174
  """Extended class for tools with execution capabilities.
@@ -144,3 +211,43 @@ class Tool(ToolDefinition):
144
211
  A string representing the result of tool execution.
145
212
  """
146
213
  raise NotImplementedError("This method should be implemented by subclasses.")
214
+
215
+ def get_injectable_properties_in_execution(self) -> dict[str, Any]:
216
+ """Get injectable properties excluding tool arguments.
217
+
218
+ Returns:
219
+ A dictionary of property names and values, excluding tool arguments and None values.
220
+ """
221
+ # Get argument names from tool definition
222
+ argument_names = {arg.name for arg in self.arguments}
223
+
224
+ # Get properties excluding arguments and filter out None values
225
+ properties = self.get_properties(exclude=["arguments"])
226
+ return {
227
+ name: value
228
+ for name, value in properties.items()
229
+ if value is not None and name in argument_names
230
+ }
231
+
232
+
233
+ if __name__ == "__main__":
234
+ tool = Tool(name="my_tool", description="A simple tool", arguments=[ToolArgument(name="arg1", arg_type="string")])
235
+ print(tool.to_markdown())
236
+
237
+ class MyTool(Tool):
238
+ field1: str | None = Field(default=None, description="Field 1 description")
239
+
240
+ tool_with_fields = MyTool(
241
+ name="my_tool1", description="A simple tool", arguments=[ToolArgument(name="field1", arg_type="string")]
242
+ )
243
+ print(tool_with_fields.to_markdown())
244
+ print(tool_with_fields.get_injectable_properties_in_execution())
245
+
246
+ tool_with_fields_defined = MyTool(
247
+ name="my_tool2",
248
+ description="A simple tool2",
249
+ field1="field1_value",
250
+ arguments=[ToolArgument(name="field1", arg_type="string")],
251
+ )
252
+ print(tool_with_fields_defined.to_markdown())
253
+ print(tool_with_fields_defined.get_injectable_properties_in_execution())
@@ -76,15 +76,17 @@ class ToolParser:
76
76
  elements = self.xml_parser.extract_elements(xml_string, preserve_cdata=True)
77
77
  logger.debug(f"Extracted elements from XML: {elements}")
78
78
 
79
+ arguments = self.tool.get_non_injectable_arguments()
80
+
79
81
  # Check for required arguments
80
- for arg in self.tool.arguments:
82
+ for arg in arguments:
81
83
  if arg.required and arg.name not in elements:
82
84
  error_msg = f"argument {arg.name} not found"
83
85
  logger.error(f"Error extracting XML elements: {error_msg}")
84
86
  raise ValueError(f"Error extracting XML elements: {error_msg}")
85
87
 
86
88
  # Create and validate arguments dictionary
87
- argument_dict = {arg.name: elements.get(arg.name, "") for arg in self.tool.arguments}
89
+ argument_dict = {arg.name: elements.get(arg.name, "") for arg in arguments}
88
90
 
89
91
  # Validate using Pydantic model
90
92
  validated_args = ToolArguments(arguments=argument_dict)