quantalogic 0.2.8__py3-none-any.whl → 0.2.10__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
@@ -64,6 +64,7 @@ class Agent(BaseModel):
64
64
  event_emitter: EventEmitter = EventEmitter()
65
65
  config: AgentConfig
66
66
  task_to_solve: str
67
+ task_to_solve_summary: str = ""
67
68
  ask_for_user_validation: Callable[[str], bool] = console_ask_for_user_validation
68
69
  last_tool_call: dict[str, Any] = {} # Stores the last tool call information
69
70
  total_tokens: int = 0 # Total tokens in the conversation
@@ -137,6 +138,9 @@ class Agent(BaseModel):
137
138
  logger.debug(f"Solving task... {task}")
138
139
  self._reset_session(task_to_solve=task, max_iterations=max_iterations)
139
140
 
141
+ # Generate task summary
142
+ self.task_to_solve_summary = self._generate_task_summary(task)
143
+
140
144
  # Add system prompt to memory
141
145
  self.memory.add(Message(role="system", content=self.config.system_prompt))
142
146
 
@@ -166,7 +170,7 @@ class Agent(BaseModel):
166
170
  self._update_total_tokens(message_history=self.memory.memory, prompt=current_prompt)
167
171
 
168
172
  # Emit event: Task Think Start after updating total tokens
169
- self._emit_event("task_think_start")
173
+ self._emit_event("task_think_start", {"prompt": current_prompt})
170
174
 
171
175
  self._compact_memory_if_needed(current_prompt)
172
176
 
@@ -443,6 +447,10 @@ class Agent(BaseModel):
443
447
  "You must analyze this answer and evaluate what to do next to solve the task.\n"
444
448
  "If the step failed, take a step back and rethink your approach.\n"
445
449
  "\n"
450
+ "--- Task to solve summary ---\n"
451
+ "\n"
452
+ f"{self.task_to_solve_summary}"
453
+ "\n"
446
454
  "--- Format ---\n"
447
455
  "\n"
448
456
  "You MUST respond with exactly two XML blocks formatted in markdown:\n"
@@ -546,6 +554,7 @@ class Agent(BaseModel):
546
554
  "\n### Tools:\n"
547
555
  "-----------------------------\n"
548
556
  f"{self._get_tools_names_prompt()}\n"
557
+ "\n"
549
558
  "### Variables:\n"
550
559
  "-----------------------------\n"
551
560
  f"{self._get_variable_prompt()}\n"
@@ -574,7 +583,7 @@ class Agent(BaseModel):
574
583
  "\n"
575
584
  "Available variables:\n"
576
585
  "\n"
577
- f"{', '.join(self.variable_store.keys())}\n"
586
+ f"{', '.join(self.variable_store.keys())}\n" if len(self.variable_store.keys()) > 0 else "None\n"
578
587
  )
579
588
  return prompt_use_variables
580
589
 
@@ -619,6 +628,28 @@ class Agent(BaseModel):
619
628
  self.memory.memory = memory_copy
620
629
  return summary.response
621
630
 
631
+ def _generate_task_summary(self, content: str) -> str:
632
+ """Generate a concise summary of the given content using the generative model.
633
+
634
+ Args:
635
+ content (str): The content to summarize
636
+
637
+ Returns:
638
+ str: Generated summary
639
+ """
640
+ try:
641
+ prompt = (
642
+ "Rewrite this task in a precise, dense, and concise manner:\n"
643
+ f"{content}\n"
644
+ "Summary should be 2-3 sentences maximum. No extra comments should be added.\n"
645
+ )
646
+ result = self.model.generate(prompt=prompt)
647
+ logger.debug(f"Generated summary: {result.response}")
648
+ return result.response
649
+ except Exception as e:
650
+ logger.error(f"Error generating summary: {str(e)}")
651
+ return f"Summary generation failed: {str(e)}"
652
+
622
653
  def _update_session_memory(self, user_content: str, assistant_content: str) -> None:
623
654
  """
624
655
  Log session messages to memory and emit events.
@@ -639,3 +670,5 @@ class Agent(BaseModel):
639
670
  "session_add_message",
640
671
  {"role": "assistant", "content": assistant_content},
641
672
  )
673
+
674
+
@@ -13,6 +13,7 @@ from quantalogic.tools import (
13
13
  InputQuestionTool,
14
14
  ListDirectoryTool,
15
15
  LLMTool,
16
+ LLMVisionTool,
16
17
  MarkitdownTool,
17
18
  NodeJsTool,
18
19
  PythonTool,
@@ -28,109 +29,141 @@ from quantalogic.tools import (
28
29
  MODEL_NAME = "deepseek/deepseek-chat"
29
30
 
30
31
 
31
- def create_agent(model_name) -> Agent:
32
+ def create_agent(model_name: str, vision_model_name: str | None) -> Agent:
32
33
  """Create an agent with the specified model and tools.
33
34
 
34
35
  Args:
35
36
  model_name (str): Name of the model to use
37
+ vision_model_name (str | None): Name of the vision model to use
38
+
39
+ Returns:
40
+ Agent: An agent with the specified model and tools
36
41
  """
42
+ tools = [
43
+ TaskCompleteTool(),
44
+ ReadFileTool(),
45
+ ReadFileBlockTool(),
46
+ WriteFileTool(),
47
+ EditWholeContentTool(),
48
+ InputQuestionTool(),
49
+ ListDirectoryTool(),
50
+ ExecuteBashCommandTool(),
51
+ ReplaceInFileTool(),
52
+ RipgrepTool(),
53
+ SearchDefinitionNames(),
54
+ MarkitdownTool(),
55
+ LLMTool(model_name=model_name),
56
+ DownloadHttpFileTool(),
57
+ ]
58
+
59
+ if vision_model_name:
60
+ tools.append(LLMVisionTool(model_name=vision_model_name))
61
+
37
62
  return Agent(
38
63
  model_name=model_name,
39
- tools=[
40
- TaskCompleteTool(),
41
- ReadFileTool(),
42
- ReadFileBlockTool(),
43
- WriteFileTool(),
44
- EditWholeContentTool(),
45
- InputQuestionTool(),
46
- ListDirectoryTool(),
47
- ExecuteBashCommandTool(),
48
- ReplaceInFileTool(),
49
- RipgrepTool(),
50
- SearchDefinitionNames(),
51
- MarkitdownTool(),
52
- LLMTool(model_name=model_name),
53
- DownloadHttpFileTool(),
54
- ],
64
+ tools=tools,
55
65
  )
56
66
 
57
67
 
58
- def create_interpreter_agent(model_name: str) -> Agent:
68
+ def create_interpreter_agent(model_name: str, vision_model_name: str | None) -> Agent:
59
69
  """Create an interpreter agent with the specified model and tools.
60
70
 
61
71
  Args:
62
72
  model_name (str): Name of the model to use
63
- """
64
- return Agent(
65
- model_name=model_name,
66
- tools=[
67
- TaskCompleteTool(),
68
- ReadFileTool(),
69
- ReadFileBlockTool(),
70
- WriteFileTool(),
71
- EditWholeContentTool(),
72
- InputQuestionTool(),
73
- ListDirectoryTool(),
74
- ExecuteBashCommandTool(),
75
- ReplaceInFileTool(),
76
- RipgrepTool(),
77
- PythonTool(),
78
- NodeJsTool(),
79
- SearchDefinitionNames(),
80
- DownloadHttpFileTool(),
81
- ],
82
- )
83
-
73
+ vision_model_name (str | None): Name of the vision model to use
84
74
 
85
- def create_full_agent(model_name: str) -> Agent:
75
+ Returns:
76
+ Agent: An interpreter agent with the specified model and tools
77
+ """
78
+ tools = [
79
+ TaskCompleteTool(),
80
+ ReadFileTool(),
81
+ ReadFileBlockTool(),
82
+ WriteFileTool(),
83
+ EditWholeContentTool(),
84
+ InputQuestionTool(),
85
+ ListDirectoryTool(),
86
+ ExecuteBashCommandTool(),
87
+ ReplaceInFileTool(),
88
+ RipgrepTool(),
89
+ PythonTool(),
90
+ NodeJsTool(),
91
+ SearchDefinitionNames(),
92
+ MarkitdownTool(),
93
+ LLMTool(model_name=model_name),
94
+ DownloadHttpFileTool(),
95
+ ]
96
+ return Agent(model_name=model_name, tools=tools)
97
+
98
+
99
+ def create_full_agent(model_name: str, vision_model_name: str | None) -> Agent:
86
100
  """Create an agent with the specified model and many tools.
87
101
 
88
102
  Args:
89
103
  model_name (str): Name of the model to use
104
+ vision_model_name (str | None): Name of the vision model to use
105
+
106
+ Returns:
107
+ Agent: An agent with the specified model and tools
108
+
90
109
  """
110
+ tools=[
111
+ TaskCompleteTool(),
112
+ ReadFileTool(),
113
+ ReadFileBlockTool(),
114
+ WriteFileTool(),
115
+ EditWholeContentTool(),
116
+ InputQuestionTool(),
117
+ ListDirectoryTool(),
118
+ ExecuteBashCommandTool(),
119
+ ReplaceInFileTool(),
120
+ RipgrepTool(),
121
+ PythonTool(),
122
+ NodeJsTool(),
123
+ SearchDefinitionNames(),
124
+ MarkitdownTool(),
125
+ LLMTool(model_name=model_name),
126
+ DownloadHttpFileTool(),
127
+ ]
128
+
129
+ if vision_model_name:
130
+ tools.append(LLMVisionTool(model_name=vision_model_name))
131
+
91
132
  return Agent(
92
133
  model_name=model_name,
93
- tools=[
94
- TaskCompleteTool(),
95
- ReadFileTool(),
96
- ReadFileBlockTool(),
97
- WriteFileTool(),
98
- EditWholeContentTool(),
99
- InputQuestionTool(),
100
- ListDirectoryTool(),
101
- ExecuteBashCommandTool(),
102
- ReplaceInFileTool(),
103
- RipgrepTool(),
104
- PythonTool(),
105
- NodeJsTool(),
106
- SearchDefinitionNames(),
107
- MarkitdownTool(),
108
- LLMTool(model_name=model_name),
109
- DownloadHttpFileTool(),
110
- ],
134
+ tools=tools,
111
135
  )
112
136
 
113
137
 
114
- def create_orchestrator_agent(model_name: str) -> Agent:
138
+ def create_orchestrator_agent(model_name: str, vision_model_name: str | None = None) -> Agent:
115
139
  """Create an agent with the specified model and tools.
116
140
 
117
141
  Args:
118
142
  model_name (str): Name of the model to use
143
+ vision_model_name (str | None): Name of the vision model to use
144
+
145
+ Returns:
146
+ Agent: An agent with the specified model and tools
119
147
  """
120
148
  # Rebuild AgentTool to resolve forward references
121
149
  AgentTool.model_rebuild()
122
150
 
123
151
  coding_agent_instance = create_coding_agent(model_name)
124
152
 
153
+ tools = [
154
+ TaskCompleteTool(),
155
+ ListDirectoryTool(),
156
+ ReadFileBlockTool(),
157
+ RipgrepTool(),
158
+ SearchDefinitionNames(),
159
+ LLMTool(model_name=MODEL_NAME),
160
+ AgentTool(agent=coding_agent_instance, agent_role="software expert", name="coder_agent_tool"),
161
+ ]
162
+
163
+ if vision_model_name:
164
+ tools.append(LLMVisionTool(model_name=vision_model_name))
165
+
125
166
  return Agent(
126
167
  model_name=model_name,
127
- tools=[
128
- TaskCompleteTool(),
129
- ListDirectoryTool(),
130
- ReadFileBlockTool(),
131
- RipgrepTool(),
132
- SearchDefinitionNames(),
133
- LLMTool(model_name=MODEL_NAME),
134
- AgentTool(agent=coding_agent_instance, agent_role="software expert", name="coder_agent_tool"),
135
- ],
168
+ tools=tools,
136
169
  )
@@ -5,6 +5,7 @@ from quantalogic.tools import (
5
5
  InputQuestionTool,
6
6
  ListDirectoryTool,
7
7
  LLMTool,
8
+ LLMVisionTool,
8
9
  ReadFileBlockTool,
9
10
  ReadFileTool,
10
11
  ReplaceInFileTool,
@@ -17,11 +18,12 @@ from quantalogic.utils import get_coding_environment
17
18
  from quantalogic.utils.get_quantalogic_rules_content import get_quantalogic_rules_file_content
18
19
 
19
20
 
20
- def create_coding_agent(model_name: str, basic: bool = False) -> Agent:
21
+ def create_coding_agent(model_name: str,vision_model_name: str | None = None, basic: bool = False) -> Agent:
21
22
  """Creates and configures a coding agent with a comprehensive set of tools.
22
23
 
23
24
  Args:
24
25
  model_name (str): Name of the language model to use for the agent's core capabilities
26
+ vision_model_name (str | None): Name of the vision model to use for the agent's core capabilities
25
27
  basic (bool, optional): If True, the agent will be configured with a basic set of tools.
26
28
 
27
29
  Returns:
@@ -59,6 +61,9 @@ def create_coding_agent(model_name: str, basic: bool = False) -> Agent:
59
61
  InputQuestionTool(),
60
62
  ]
61
63
 
64
+ if vision_model_name:
65
+ tools.append(LLMVisionTool(model_name=vision_model_name))
66
+
62
67
  if not basic:
63
68
  tools.append(
64
69
  LLMTool(
@@ -5,16 +5,17 @@ from litellm import completion, exceptions, get_max_tokens, get_model_info, toke
5
5
  from loguru import logger
6
6
  from pydantic import BaseModel, Field, field_validator
7
7
 
8
- MIN_RETRIES = 3
8
+ MIN_RETRIES = 1
9
9
 
10
10
 
11
11
  class Message(BaseModel):
12
12
  """Represents a message in a conversation with a specific role and content."""
13
13
 
14
14
  role: str = Field(..., min_length=1)
15
- content: str = Field(..., min_length=1)
15
+ content: str | dict = Field(..., min_length=1)
16
+ image_url: str | None = Field(default=None, pattern=r"^https?://")
16
17
 
17
- @field_validator("role", "content")
18
+ @field_validator("role")
18
19
  @classmethod
19
20
  def validate_not_empty(cls, v: str) -> str:
20
21
  """Validate that the field is not empty or whitespace-only."""
@@ -22,6 +23,26 @@ class Message(BaseModel):
22
23
  raise ValueError("Field cannot be empty or whitespace-only")
23
24
  return v
24
25
 
26
+ @field_validator("content")
27
+ @classmethod
28
+ def validate_content(cls, v: str | dict) -> str | dict:
29
+ """Validate content based on its type."""
30
+ if isinstance(v, str):
31
+ if not v or not v.strip():
32
+ raise ValueError("Text content cannot be empty or whitespace-only")
33
+ elif isinstance(v, dict):
34
+ if not v.get("text") or not v.get("image_url"):
35
+ raise ValueError("Multimodal content must have both text and image_url")
36
+ return v
37
+
38
+ @field_validator("image_url")
39
+ @classmethod
40
+ def validate_image_url(cls, v: str | None) -> str | None:
41
+ """Validate image URL format if present."""
42
+ if v and not v.startswith(("http://", "https://")):
43
+ raise ValueError("Image URL must start with http:// or https://")
44
+ return v
45
+
25
46
 
26
47
  class TokenUsage(BaseModel):
27
48
  """Represents token usage statistics for a language model."""
@@ -59,6 +80,7 @@ class GenerativeModel:
59
80
  temperature: Sampling temperature between 0 and 1.
60
81
  Defaults to 0.7.
61
82
  """
83
+ logger.debug(f"Initializing GenerativeModel with model={model}, temperature={temperature}")
62
84
  self.model = model
63
85
  self.temperature = temperature
64
86
 
@@ -85,15 +107,16 @@ class GenerativeModel:
85
107
  )
86
108
 
87
109
  # Retry on specific retriable exceptions
88
- def generate_with_history(self, messages_history: list[Message], prompt: str) -> ResponseStats:
89
- """Generate a response with conversation history.
110
+ def generate_with_history(self, messages_history: list[Message], prompt: str, image_url: str | None = None) -> ResponseStats:
111
+ """Generate a response with conversation history and optional image.
90
112
 
91
- Generates a response based on previous conversation messages
92
- and a new user prompt.
113
+ Generates a response based on previous conversation messages,
114
+ a new user prompt, and an optional image URL.
93
115
 
94
116
  Args:
95
117
  messages_history: Previous conversation messages.
96
118
  prompt: Current user prompt.
119
+ image_url: Optional image URL for visual queries.
97
120
 
98
121
  Returns:
99
122
  Detailed response statistics.
@@ -105,7 +128,22 @@ class GenerativeModel:
105
128
  Exception: For other unexpected errors.
106
129
  """
107
130
  messages = [{"role": msg.role, "content": str(msg.content)} for msg in messages_history]
108
- messages.append({"role": "user", "content": str(prompt)})
131
+
132
+ if image_url:
133
+ messages.append({
134
+ "role": "user",
135
+ "content": [
136
+ {"type": "text", "text": str(prompt)},
137
+ {
138
+ "type": "image_url",
139
+ "image_url": {
140
+ "url": image_url
141
+ }
142
+ }
143
+ ]
144
+ })
145
+ else:
146
+ messages.append({"role": "user", "content": str(prompt)})
109
147
 
110
148
  try:
111
149
  logger.debug(f"Generating response for prompt: {prompt}")
@@ -140,9 +178,12 @@ class GenerativeModel:
140
178
  }
141
179
 
142
180
  logger.error("LLM Generation Error: {}", error_details)
181
+ logger.debug(f"Error details: {error_details}")
182
+ logger.debug(f"Model: {self.model}, Temperature: {self.temperature}")
143
183
 
144
184
  # Handle authentication and permission errors
145
185
  if isinstance(e, self.AUTH_EXCEPTIONS):
186
+ logger.debug("Authentication error occurred")
146
187
  raise openai.AuthenticationError(
147
188
  f"Authentication failed with provider {error_details['provider']}"
148
189
  ) from e
@@ -162,7 +203,7 @@ class GenerativeModel:
162
203
  # Wrap unknown errors in APIError
163
204
  raise openai.APIError(f"Unexpected error during generation: {str(e)}") from e
164
205
 
165
- def generate(self, prompt: str) -> ResponseStats:
206
+ def generate(self, prompt: str, image_url: str | None = None) -> ResponseStats:
166
207
  """Generate a response without conversation history.
167
208
 
168
209
  Generates a response for a single user prompt without
@@ -170,11 +211,12 @@ class GenerativeModel:
170
211
 
171
212
  Args:
172
213
  prompt: User prompt.
214
+ image_url: Optional image URL for visual queries.
173
215
 
174
216
  Returns:
175
217
  Detailed response statistics.
176
218
  """
177
- return self.generate_with_history([], prompt)
219
+ return self.generate_with_history([], prompt, image_url)
178
220
 
179
221
  def get_max_tokens(self) -> int:
180
222
  """Get the maximum number of tokens that can be generated by the model."""
@@ -182,8 +224,11 @@ class GenerativeModel:
182
224
 
183
225
  def token_counter(self, messages: list[Message]) -> int:
184
226
  """Count the number of tokens in a list of messages."""
227
+ logger.debug(f"Counting tokens for {len(messages)} messages using model {self.model}")
185
228
  litellm_messages = [{"role": msg.role, "content": str(msg.content)} for msg in messages]
186
- return token_counter(model=self.model, messages=litellm_messages)
229
+ token_count = token_counter(model=self.model, messages=litellm_messages)
230
+ logger.debug(f"Token count: {token_count}")
231
+ return token_count
187
232
 
188
233
  def token_counter_with_history(self, messages_history: list[Message], prompt: str) -> int:
189
234
  """Count the number of tokens in a list of messages and a prompt."""
@@ -193,12 +238,18 @@ class GenerativeModel:
193
238
 
194
239
  def get_model_info(self) -> dict | None:
195
240
  """Get information about the model."""
241
+ logger.debug(f"Retrieving model info for {self.model}")
196
242
  model_info = get_model_info(self.model)
197
243
 
198
244
  if not model_info:
199
- # Search without prefix "openrouter/"
245
+ logger.debug("Model info not found, trying without openrouter/ prefix")
200
246
  model_info = get_model_info(self.model.replace("openrouter/", ""))
201
247
 
248
+ if model_info:
249
+ logger.debug(f"Model info retrieved: {model_info.keys()}")
250
+ else:
251
+ logger.debug("No model info available")
252
+
202
253
  return model_info
203
254
 
204
255
  def get_model_max_input_tokens(self) -> int:
quantalogic/main.py CHANGED
@@ -33,20 +33,20 @@ from quantalogic.version import get_version # noqa: E402
33
33
  AGENT_MODES = ["code", "basic", "interpreter", "full", "code-basic"]
34
34
 
35
35
 
36
- def create_agent_for_mode(mode: str, model_name: str) -> Agent:
36
+ def create_agent_for_mode(mode: str, model_name: str, vision_model_name: str | None) -> Agent:
37
37
  """Create an agent based on the specified mode."""
38
38
  logger.debug(f"Creating agent for mode: {mode} with model: {model_name}")
39
39
  if mode == "code":
40
40
  logger.debug("Creating code agent without basic mode")
41
- return create_coding_agent(model_name, basic=False)
41
+ return create_coding_agent(model_name, vision_model_name, basic=False)
42
42
  if mode == "code-basic":
43
- return create_coding_agent(model_name, basic=True)
43
+ return create_coding_agent(model_name, vision_model_name, basic=True)
44
44
  elif mode == "basic":
45
- return create_orchestrator_agent(model_name)
45
+ return create_orchestrator_agent(model_name, vision_model_name)
46
46
  elif mode == "full":
47
- return create_full_agent(model_name)
47
+ return create_full_agent(model_name, vision_model_name)
48
48
  elif mode == "interpreter":
49
- return create_interpreter_agent(model_name)
49
+ return create_interpreter_agent(model_name, vision_model_name)
50
50
  else:
51
51
  raise ValueError(f"Unknown agent mode: {mode}")
52
52
 
@@ -54,13 +54,18 @@ def create_agent_for_mode(mode: str, model_name: str) -> Agent:
54
54
  def configure_logger(log_level: str) -> None:
55
55
  """Configure the logger with the specified log level and format."""
56
56
  logger.remove()
57
- logger.add(sys.stderr, level=log_level.upper(), format="{time} | {level} | {message}")
57
+ logger.add(
58
+ sys.stderr,
59
+ level=log_level.upper(),
60
+ format="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <cyan>{process}</cyan> | <magenta>{file}:{line}</magenta> | {message}",
61
+ )
58
62
  logger.info(f"Log level set to: {log_level}")
59
63
 
60
64
 
61
65
  def set_litellm_verbose(verbose_mode: bool) -> None:
62
66
  """Set the verbosity of the litellm library."""
63
67
  import litellm
68
+
64
69
  litellm.set_verbose = verbose_mode
65
70
 
66
71
 
@@ -87,7 +92,7 @@ def get_task_from_file(file_path: str) -> str:
87
92
  raise Exception(f"Unexpected error reading file: {e}")
88
93
 
89
94
 
90
- def display_welcome_message(console: Console, model_name: str) -> None:
95
+ def display_welcome_message(console: Console, model_name: str, vision_model_name: str | None) -> None:
91
96
  """Display the welcome message and instructions."""
92
97
  version = get_version()
93
98
  console.print(
@@ -97,9 +102,10 @@ def display_welcome_message(console: Console, model_name: str) -> None:
97
102
  "1. [bold]Describe your task[/bold]: Tell the AI what you need help with.\n"
98
103
  "2. [bold]Submit your task[/bold]: Press [bold]Enter[/bold] twice to send your request.\n\n"
99
104
  "3. [bold]Exit the app[/bold]: Leave the input blank and press [bold]Enter[/bold] twice to close the assistant.\n\n"
100
- f"[yellow]ℹ️ System Info:[/yellow]\n\n"
101
- f"- Version: {get_version()}\n"
102
- f"- Model: {model_name}\n\n"
105
+ f"[yellow] 🤖 System Info:[/yellow]\n\n"
106
+ "\n"
107
+ f"- Model: {model_name}\n"
108
+ f"- Vision Model: {vision_model_name}\n\n"
103
109
  "[bold magenta]💡 Pro Tips:[/bold magenta]\n\n"
104
110
  "- Be as specific as possible in your task description to get the best results!\n"
105
111
  "- Use clear and concise language when describing your task\n"
@@ -118,18 +124,38 @@ def display_welcome_message(console: Console, model_name: str) -> None:
118
124
  default=MODEL_NAME,
119
125
  help='Specify the model to use (litellm format, e.g. "openrouter/deepseek-chat").',
120
126
  )
121
- @click.option("--log", type=click.Choice(["info", "debug", "warning"]), default="info", help="Set logging level (info/debug/warning).")
127
+ @click.option(
128
+ "--log",
129
+ type=click.Choice(["info", "debug", "warning"]),
130
+ default="info",
131
+ help="Set logging level (info/debug/warning).",
132
+ )
122
133
  @click.option("--verbose", is_flag=True, help="Enable verbose output.")
123
134
  @click.option("--mode", type=click.Choice(AGENT_MODES), default="code", help="Agent mode (code/search/full).")
135
+ @click.option(
136
+ "--vision-model-name",
137
+ default=None,
138
+ help='Specify the vision model to use (litellm format, e.g. "openrouter/A/gpt-4o-mini").',
139
+ )
124
140
  @click.pass_context
125
- def cli(ctx: click.Context, version: bool, model_name: str, verbose: bool, mode: str, log: str) -> None:
141
+ def cli(
142
+ ctx: click.Context,
143
+ version: bool,
144
+ model_name: str,
145
+ verbose: bool,
146
+ mode: str,
147
+ log: str,
148
+ vision_model_name: str | None,
149
+ ) -> None:
126
150
  """QuantaLogic AI Assistant - A powerful AI tool for various tasks."""
127
151
  if version:
128
152
  console = Console()
129
153
  console.print(f"QuantaLogic version: {get_version()}")
130
154
  sys.exit(0)
131
155
  if ctx.invoked_subcommand is None:
132
- ctx.invoke(task, model_name=model_name, verbose=verbose, mode=mode, log=log)
156
+ ctx.invoke(
157
+ task, model_name=model_name, verbose=verbose, mode=mode, log=log, vision_model_name=vision_model_name
158
+ )
133
159
 
134
160
 
135
161
  @cli.command()
@@ -141,9 +167,27 @@ def cli(ctx: click.Context, version: bool, model_name: str, verbose: bool, mode:
141
167
  )
142
168
  @click.option("--verbose", is_flag=True, help="Enable verbose output.")
143
169
  @click.option("--mode", type=click.Choice(AGENT_MODES), default="code", help="Agent mode (code/search/full).")
144
- @click.option("--log", type=click.Choice(["info", "debug", "warning"]), default="info", help="Set logging level (info/debug/warning).")
170
+ @click.option(
171
+ "--log",
172
+ type=click.Choice(["info", "debug", "warning"]),
173
+ default="info",
174
+ help="Set logging level (info/debug/warning).",
175
+ )
176
+ @click.option(
177
+ "--vision-model-name",
178
+ default=None,
179
+ help='Specify the vision model to use (litellm format, e.g. "openrouter/openai/gpt-4o-mini").',
180
+ )
145
181
  @click.argument("task", required=False)
146
- def task(file: Optional[str], model_name: str, verbose: bool, mode: str, log: str, task: Optional[str]) -> None:
182
+ def task(
183
+ file: Optional[str],
184
+ model_name: str,
185
+ verbose: bool,
186
+ mode: str,
187
+ log: str,
188
+ vision_model_name: str | None,
189
+ task: Optional[str],
190
+ ) -> None:
147
191
  """Execute a task with the QuantaLogic AI Assistant."""
148
192
  console = Console()
149
193
  switch_verbose(verbose, log)
@@ -155,7 +199,7 @@ def task(file: Optional[str], model_name: str, verbose: bool, mode: str, log: st
155
199
  if task:
156
200
  task_content = task
157
201
  else:
158
- display_welcome_message(console, model_name)
202
+ display_welcome_message(console, model_name, vision_model_name)
159
203
  logger.info("Waiting for user input...")
160
204
  task_content = get_multiline_input(console).strip()
161
205
  logger.info(f"User input received. Task content: {task_content}")
@@ -177,7 +221,7 @@ def task(file: Optional[str], model_name: str, verbose: bool, mode: str, log: st
177
221
  sys.exit(0)
178
222
 
179
223
  logger.debug(f"Creating agent for mode: {mode} with model: {model_name}")
180
- agent = create_agent_for_mode(mode, model_name)
224
+ agent = create_agent_for_mode(mode, model_name, vision_model_name=vision_model_name)
181
225
  logger.debug(f"Created agent for mode: {mode} with model: {model_name}")
182
226
 
183
227
  events = [
quantalogic/prompts.py CHANGED
@@ -14,11 +14,10 @@ Tasks will be presented within XML tags:
14
14
  ### Response Protocol
15
15
  Every response must contain exactly two XML blocks:
16
16
 
17
- Be very concise and very precise in the <thinking> block
18
-
19
17
  1. Analysis Block:
20
18
  ```xml
21
19
  <thinking>
20
+ <!-- You must follow this precise format, be very concise and very precise -->
22
21
  <task_analysis_if_no_history>
23
22
  Only if no conversation history:
24
23
  * Rewrite the <task> and its context with your own words in detailed, clear, and specific manner.
@@ -51,20 +50,24 @@ Be very concise and very precise in the <thinking> block
51
50
  </result>
52
51
  </last_observation>
53
52
  <progess_analysis>
53
+ <!-- if there is a conversation history -->
54
54
  * Detail each step failed and completed so far.
55
55
  * Identify and evaluate any blockers or challenges to the progress of global task.
56
+ * Identify repetitions: if repeated steps, take a step back and rethink your approach.
56
57
  * Provide potential solutions, and if needed, suggest reevaluating the approach and the plan.
57
58
  </progess_analysis>
58
59
  <variables>
60
+ <!-- if there is a conversation history -->
59
61
  * List all variable names and concisely describe their current values.
60
62
  </variables>
61
63
  <next_steps>
62
64
  * Outline immediate actions required.
63
65
  * Justify tool selection and parameter choices.
64
- * Think about variable interpolation to minimize generation of tokens.
65
- * Consider alternatives if previous attempts were unsuccessful.
66
+ * Prefer variable interpolation if possible, to minimize generation of tokens.
67
+ * Consider alternatives, take a step back if previous attempts were unsuccessful to review the plan.
66
68
  </next_steps>
67
69
  <taskpad>
70
+ <!-- optional -->
68
71
  <note>Use this to record notes about intermediate steps.</note>
69
72
  </taskpad>
70
73
  </thinking>
@@ -73,25 +76,15 @@ Be very concise and very precise in the <thinking> block
73
76
  2. Action Block:
74
77
  ```xml
75
78
  <tool_name>
76
- <parameter1>value1</parameter1>
79
+ <!-- tool_name is the name of the tool from available tools -->
80
+ <parameter1>
81
+ <!-- Use variable interpolation to pass context to minimize generation of tokens, example: <content>$var1$<</content> -->
82
+ value1
83
+ </parameter1>
77
84
  <parameter2>value2</parameter2>
78
85
  </tool_name>
79
86
  ```
80
87
 
81
- ### Tool Usage Guidelines
82
- 1. Before Repeating a Tool Call:
83
- - Review previous results in detail.
84
- - State why a repeat is needed.
85
- - Adjust parameters if necessary.
86
- - Consider whether other tools are more appropriate.
87
- - Use variable interpolation to pass context to minimize generation of tokens, example: <toolname>$var1$<</toolname>
88
-
89
- 2. When Tool Calls Fail:
90
- - Examine the error message carefully.
91
- - Adjust parameters if needed.
92
- - Consider alternative tools.
93
- - Break down complex processes into smaller steps if necessary.
94
-
95
88
  ### Available Tools
96
89
  {tools}
97
90
 
@@ -8,6 +8,7 @@ from .execute_bash_command_tool import ExecuteBashCommandTool
8
8
  from .input_question_tool import InputQuestionTool
9
9
  from .list_directory_tool import ListDirectoryTool
10
10
  from .llm_tool import LLMTool
11
+ from .llm_vision_tool import LLMVisionTool
11
12
  from .markitdown_tool import MarkitdownTool
12
13
  from .nodejs_tool import NodeJsTool
13
14
  from .python_tool import PythonTool
@@ -30,6 +31,7 @@ __all__ = [
30
31
  "InputQuestionTool",
31
32
  "ListDirectoryTool",
32
33
  "LLMTool",
34
+ "LLMVisionTool",
33
35
  "ExecuteBashCommandTool",
34
36
  "PythonTool",
35
37
  "ElixirTool",
@@ -17,11 +17,11 @@ class LLMTool(Tool):
17
17
  description: str = Field(
18
18
  default=(
19
19
  "Generates answers to questions using a specified language model. "
20
- "Note: This tool operates in isolation and does not have access to: "
20
+ "Note: This tool operates in total isolation and does not have access to: "
21
21
  " - Memory: All context must be explicitly provided in the prompt. "
22
22
  " - File system."
23
23
  " - Variables: Any required variables should be interpolated into the prompt (e.g., $var1$). "
24
- " - Other tools: It cannot invoke or interact with other tools. "
24
+ " - No access to Tools, URL, file, or other external resources. "
25
25
  "Ensure all necessary information is included directly in your prompt."
26
26
  )
27
27
  )
@@ -0,0 +1,140 @@
1
+ """LLM Vision Tool for analyzing images using a language model."""
2
+
3
+ import logging
4
+ from typing import Optional
5
+
6
+ from pydantic import ConfigDict, Field
7
+
8
+ from quantalogic.generative_model import GenerativeModel, Message
9
+ from quantalogic.tools.tool import Tool, ToolArgument
10
+
11
+ #DEFAULT_MODEL_NAME = "ollama/llama3.2-vision"
12
+ DEFAULT_MODEL_NAME = "openrouter/openai/gpt-4o-mini"
13
+
14
+
15
+ class LLMVisionTool(Tool):
16
+ """Tool to analyze images using a specified language model."""
17
+
18
+ model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow")
19
+
20
+ name: str = Field(default="llm_vision_tool")
21
+ description: str = Field(
22
+ default=(
23
+ "Analyzes images and generates responses using a specified language model. "
24
+ "Supports multimodal input combining text and images."
25
+ )
26
+ )
27
+ arguments: list = Field(
28
+ default=[
29
+ ToolArgument(
30
+ name="system_prompt",
31
+ arg_type="string",
32
+ description="The system prompt to guide the model's behavior",
33
+ required=True,
34
+ example="You are an expert in image analysis and visual understanding.",
35
+ ),
36
+ ToolArgument(
37
+ name="prompt",
38
+ arg_type="string",
39
+ description="The question or instruction about the image",
40
+ required=True,
41
+ example="What is shown in this image?",
42
+ ),
43
+ ToolArgument(
44
+ name="image_url",
45
+ arg_type="string",
46
+ description="URL of the image to analyze",
47
+ required=True,
48
+ example="https://example.com/image.jpg",
49
+ ),
50
+ ToolArgument(
51
+ name="temperature",
52
+ arg_type="string",
53
+ description='Sampling temperature between "0.0" and "1.0"',
54
+ required=True,
55
+ default="0.7",
56
+ example="0.7",
57
+ ),
58
+ ]
59
+ )
60
+
61
+ model_name: str = Field(..., description="The name of the language model to use")
62
+ generative_model: Optional[GenerativeModel] = Field(default=None)
63
+
64
+ def model_post_init(self, __context):
65
+ """Initialize the generative model after model initialization."""
66
+ if self.generative_model is None:
67
+ self.generative_model = GenerativeModel(model=self.model_name)
68
+ logging.debug(f"Initialized LLMVisionTool with model: {self.model_name}")
69
+
70
+ def execute(
71
+ self,
72
+ system_prompt: str,
73
+ prompt: str,
74
+ image_url: str,
75
+ temperature: str = "0.7"
76
+ ) -> str:
77
+ """Execute the tool to analyze an image and generate a response.
78
+
79
+ Args:
80
+ system_prompt: The system prompt to guide the model
81
+ prompt: The question or instruction about the image
82
+ image_url: URL of the image to analyze
83
+ temperature: Sampling temperature
84
+
85
+ Returns:
86
+ The generated response
87
+
88
+ Raises:
89
+ ValueError: If temperature is invalid or image_url is malformed
90
+ Exception: If there's an error during response generation
91
+ """
92
+ try:
93
+ temp = float(temperature)
94
+ if not (0.0 <= temp <= 1.0):
95
+ raise ValueError("Temperature must be between 0 and 1.")
96
+ except ValueError as ve:
97
+ logging.error(f"Invalid temperature value: {temperature}")
98
+ raise ValueError(f"Invalid temperature value: {temperature}") from ve
99
+
100
+ if not image_url.startswith(("http://", "https://")):
101
+ raise ValueError("Image URL must start with http:// or https://")
102
+
103
+ # Prepare the messages history
104
+ messages_history = [
105
+ Message(role="system", content=system_prompt),
106
+ ]
107
+
108
+ if self.generative_model is None:
109
+ self.generative_model = GenerativeModel(model=self.model_name)
110
+
111
+ self.generative_model.temperature = temp
112
+
113
+ try:
114
+ response_stats = self.generative_model.generate_with_history(
115
+ messages_history=messages_history,
116
+ prompt=prompt,
117
+ image_url=image_url
118
+ )
119
+ response = response_stats.response.strip()
120
+ logging.info(f"Generated response: {response}")
121
+ return response
122
+ except Exception as e:
123
+ logging.error(f"Error generating response: {e}")
124
+ raise Exception(f"Error generating response: {e}") from e
125
+
126
+
127
+ if __name__ == "__main__":
128
+ # Example usage
129
+ tool = LLMVisionTool(model_name=DEFAULT_MODEL_NAME)
130
+ system_prompt = "You are a vision expert."
131
+ question = "What is shown in this image? Describe it with details."
132
+ image_url = "https://fastly.picsum.photos/id/767/200/300.jpg?hmac=j5YA1cRw-jS6fK3Mx2ooPwl2_TS3RSyLmFmiM9TqLC4"
133
+ temperature = "0.7"
134
+ answer = tool.execute(
135
+ system_prompt=system_prompt,
136
+ prompt=question,
137
+ image_url=image_url,
138
+ temperature=temperature
139
+ )
140
+ print(answer)
quantalogic/version.py CHANGED
@@ -1,4 +1,4 @@
1
1
 
2
- VERSION = "0.2.8"
2
+ VERSION = "0.2.10"
3
3
  def get_version() -> str:
4
4
  return VERSION
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: quantalogic
3
- Version: 0.2.8
3
+ Version: 0.2.10
4
4
  Summary: QuantaLogic ReAct Agents
5
5
  Author: Raphaël MANSUY
6
6
  Author-email: raphael.mansuy@gmail.com
@@ -123,8 +123,10 @@ Usage: quantalogic [OPTIONS] COMMAND [ARGS]...
123
123
 
124
124
  Options:
125
125
  --version Show version information.
126
- --model-name TEXT Specify the model to use (litellm format,
126
+ --model-name TEXT Specify the text model to use (litellm format,
127
127
  e.g. "openrouter/deepseek-chat").
128
+ --vision-model-name TEXT Specify the vision model to use (litellm format,
129
+ e.g. "openrouter/A/gpt-4o-mini").
128
130
  --log [info|debug|warning] Set logging level (info/debug/warning).
129
131
  --verbose Enable verbose output.
130
132
  --mode [code|basic|interpreter|full|code-basic]
@@ -385,7 +387,7 @@ By integrating these tools into its architecture, QuantaLogic allows agents to p
385
387
  | Script Execution | Python Tool, Node.js Tool, Elixir Tool |
386
388
  | File Operations | Read File Tool, Write File Tool, Edit Whole Content Tool, Replace In File Tool |
387
389
  | Code Analysis | Search Definition Names Tool, Ripgrep Tool |
388
- | Content Generation | LLM Tool |
390
+ | Content Generation | LLM Tool, LLMVisionTool |
389
391
  | Utility and Management | Download HTTP File Tool, List Directory Tool, Markitdown Tool, Unified Diff Tool |
390
392
 
391
393
  ---
@@ -681,7 +683,30 @@ print("Ripgrep Results:", output)
681
683
 
682
684
  ---
683
685
 
684
- ### 14. LLM Tool
686
+ #### 14. LLMVisionTool
687
+
688
+ The **LLMVisionTool** enables processing of visual inputs using vision-language models.
689
+
690
+ ##### Parameters
691
+
692
+ | Parameter | Type | Description | Example |
693
+ |----------------|---------|------------------------------------------------------------------------|--------------------------------------------|
694
+ | `image_path` | string | Path to the image file to process | `./path/to/image.png` |
695
+ | `prompt` | string | The question or instruction for the vision model | `Describe the contents of this image` |
696
+ | `temperature` | float | Sampling temperature between 0.0 and 1.0 | `0.7` |
697
+
698
+ ##### Example Usage
699
+ ```python
700
+ vision_tool = LLMVisionTool()
701
+ response = vision_tool.execute(
702
+ image_path="./path/to/image.png",
703
+ prompt="Describe the contents of this image",
704
+ temperature=0.7
705
+ )
706
+ print("Vision Model Response:", response)
707
+ ```
708
+
709
+ #### 15. LLM Tool
685
710
 
686
711
  The **LLM Tool** generates answers using a specified language model.
687
712
 
@@ -706,7 +731,7 @@ print("LLM Response:", response)
706
731
 
707
732
  ---
708
733
 
709
- ### 15. Download HTTP File Tool
734
+ ### 16. Download HTTP File Tool
710
735
 
711
736
  The **Download HTTP File Tool** downloads a file from a specified HTTP URL.
712
737
 
@@ -726,7 +751,7 @@ print(result)
726
751
 
727
752
  ---
728
753
 
729
- ### 16. List Directory Tool
754
+ ### 17. List Directory Tool
730
755
 
731
756
  The **List Directory Tool** lists files in a specified directory.
732
757
 
@@ -745,7 +770,7 @@ print("Directory Files:", result)
745
770
 
746
771
  ---
747
772
 
748
- ### 17. Markitdown Tool
773
+ ### 18. Markitdown Tool
749
774
 
750
775
  The **Markitdown Tool** processes markdown files, possibly for conversion or rendering.
751
776
 
@@ -762,26 +787,6 @@ result = markitdown_tool.execute(markdown_path="./path/to/file.md")
762
787
  print("Processed Markdown Output:", result)
763
788
  ```
764
789
 
765
- ---
766
-
767
- ### 18. Unified Diff Tool
768
-
769
- The **Unified Diff Tool** generates a unified diff between two texts or files.
770
-
771
- #### Parameters
772
-
773
- | Parameter | Type | Description | Example |
774
- |--------------|--------|------------------------------------------------|------------------------|
775
- | `original` | string | The original content or file path. | `old_text.txt` |
776
- | `updated` | string | The updated content or file path. | `new_text.txt` |
777
-
778
- #### Example Usage
779
- ```python
780
- diff_tool = UnifiedDiffTool()
781
- result = diff_tool.execute(original="old_text.txt", updated="new_text.txt")
782
- print("Unified Diff Output:", result)
783
- ```
784
-
785
790
 
786
791
  #### Creating Custom Tools
787
792
 
@@ -1,15 +1,15 @@
1
1
  quantalogic/__init__.py,sha256=HFk7_19UzHzYwvPzb9QTQ4w_lPwTTPda61AYb8qggZY,686
2
- quantalogic/agent.py,sha256=QBQc-C_h_DsstV2ntHETh8XywPAcY31-ThO2SiBqvB0,25443
3
- quantalogic/agent_config.py,sha256=sNuZ0Y02h3ra2hcjAE3-8Tt-NE8QSarRsTB0auTLgn0,3595
4
- quantalogic/coding_agent.py,sha256=ivHBn3hIDEt9V1LDXI4l-COeGXYVp_v_rrx9ng8sP2A,3214
2
+ quantalogic/agent.py,sha256=s6kmgH2XLhtg1ZBl1SckV7ayFXkR_acs305Z2l1O2Bg,26680
3
+ quantalogic/agent_config.py,sha256=Gm-i84dQF7CC1NUEtXpdw-qgZh5Jp1q4ni_SxMIxm-U,4518
4
+ quantalogic/coding_agent.py,sha256=qT24jneNLUH1zMQUWBBTYDIVjCLPNm1iCaXB0v3w9yc,3469
5
5
  quantalogic/event_emitter.py,sha256=jqot2g4JRXc88K6PW837Oqxbf7shZfO-xdPaUWmzupk,7901
6
- quantalogic/generative_model.py,sha256=JkZz7YBd-v9n5EURidKUddkCLNvNZaqMzo5PgE_ugmc,8398
6
+ quantalogic/generative_model.py,sha256=N5FjE0kMpKKdpP9_QEOZJ0rkOpYPaJWG91jzEyyovXA,10725
7
7
  quantalogic/interactive_text_editor.py,sha256=kYeTA2qej5kxtPvAUHy_Dr2MhrGQAyenLFpW9mU9Rmw,6855
8
- quantalogic/main.py,sha256=_oLhXNTDSH7Zfc1__jPgOvqZGpYH5VSjamdLwZ4xqDM,8698
8
+ quantalogic/main.py,sha256=0zsr8-BnDPaR3roagL-3RX71hf4N0PqcgnCnJDO-gII,9626
9
9
  quantalogic/memory.py,sha256=zbtRuM05jaS2lJll-92dt5JfYVLERnF_m_9xqp2x-k0,6304
10
10
  quantalogic/model_names.py,sha256=UZlz25zG9B2dpfwdw_e1Gw5qFsKQ7iME9FJh9Ts4u6s,938
11
11
  quantalogic/print_event.py,sha256=-4qZmFI2BTkXuGE9DoKm6Vs-GzK1F9WJGt9GqpRQlQQ,2175
12
- quantalogic/prompts.py,sha256=GWVWnwFBxcVtOPvm1rUr1VnWY0OVqaBwVU_pMNunH2k,3609
12
+ quantalogic/prompts.py,sha256=BHIST57DYcTeTb7rvV1QkGLt0_B8Wk8a_9tsnsN6suk,3547
13
13
  quantalogic/server/__init__.py,sha256=8sz_PYAUCrkM6JM5EAUeIzNM4NPW6j6UT72JVkc21WQ,91
14
14
  quantalogic/server/agent_server.py,sha256=GmglYf-LeVQQOdikMFDvPq1R0wKt6wIBpufW8XzP-iE,22489
15
15
  quantalogic/server/models.py,sha256=nVUGWElOsUw8QnRCGJylk25wCew_5gohe6nldYighUA,1322
@@ -19,7 +19,7 @@ quantalogic/server/static/js/event_visualizer.js,sha256=eFkkWyNZw3zOZlF18kxbfsWq
19
19
  quantalogic/server/static/js/quantalogic.js,sha256=x7TrlZGR1Y0WLK2DWl1xY847BhEWMPnL0Ua7KtOldUc,22311
20
20
  quantalogic/server/templates/index.html,sha256=nDnXJoQEm1vXbhXtgaYk0G5VXj0wwzE6KrqEDhHFpj4,7773
21
21
  quantalogic/tool_manager.py,sha256=FyghX2M_yGmdL7ovJR4ZGYIiBwkxA-bPjpI-y4IFx4Y,2421
22
- quantalogic/tools/__init__.py,sha256=VmvOHjCZmcVRnm5meMVBE8wMRmY1D7M2kxkrPeieWcI,1431
22
+ quantalogic/tools/__init__.py,sha256=OxZp1nWZC5EewMJJVEvP6Fd2RMFlpaIYLHqChXPG_6s,1495
23
23
  quantalogic/tools/agent_tool.py,sha256=qeRp74EBqPSGu6JNZMATGyDoSCzPo7EnB2rmCP5wsBE,3050
24
24
  quantalogic/tools/download_http_file_tool.py,sha256=wTfanbXjIRi5-qrbluuLvNmDNhvmYAnlMVb3dO8C2ss,2210
25
25
  quantalogic/tools/edit_whole_content_tool.py,sha256=nXmpAvojvqvAcqNMy1kUKZ1ocboky_ZcnCR4SNCSPgw,2360
@@ -37,7 +37,8 @@ quantalogic/tools/language_handlers/rust_handler.py,sha256=t_AqKVa3KVk6SVkq_UjUU
37
37
  quantalogic/tools/language_handlers/scala_handler.py,sha256=wr-cWOIFOc0UYwODmEtT6rV63Qf1NyNB_BLo23GLrvk,1281
38
38
  quantalogic/tools/language_handlers/typescript_handler.py,sha256=L4vuJMYxKO3_83dQhdwZ9fogauIV7rwoicRT0xLGfkQ,1738
39
39
  quantalogic/tools/list_directory_tool.py,sha256=8Hy38DelSh-mRqS_uDLpeBYoHLtEy5ji77xI-TJu3Ms,4176
40
- quantalogic/tools/llm_tool.py,sha256=_6cMM2gpX6Wc_rV5y8L6-w3PoRwZtoRl09ZlPAlOckw,5005
40
+ quantalogic/tools/llm_tool.py,sha256=ktwsg1LI3rSPS5XCwp-GAFiYCaXPSXRzUaqjOexPfEg,5011
41
+ quantalogic/tools/llm_vision_tool.py,sha256=S99pKpIi8WUPREXXPArsuA7iTdXzUtiXg32J9NKCvlc,5028
41
42
  quantalogic/tools/markitdown_tool.py,sha256=GHJMPdWmwF-CBu3vHWhy-kXJYRDluFkh18KN06yNHOc,4101
42
43
  quantalogic/tools/nodejs_tool.py,sha256=2VTkZgtyXmv2E18CVaml3CKZE28WL9Tbv2IVdziv8wA,19903
43
44
  quantalogic/tools/python_tool.py,sha256=t66ge3xXS55-wJkddnVU9210TuDVoRI0Y-rsZwWxYIk,18154
@@ -59,11 +60,11 @@ quantalogic/utils/get_quantalogic_rules_content.py,sha256=fnEFTyClXzpI0MLaM-gB9R
59
60
  quantalogic/utils/git_ls.py,sha256=_aXg2TwqYv9CoOrhQ1gqHCqu1j8wOVigQNWbGncSDlM,4361
60
61
  quantalogic/utils/read_file.py,sha256=tSRVHk8dIP4nNLL89v5kRki4hOTjVyjbmuEb2zwvwCY,2077
61
62
  quantalogic/utils/read_http_text_content.py,sha256=1nRLQ9DHP_fKrm0rIEJBF0ROmB78e4lct2hUzD2PAUk,4408
62
- quantalogic/version.py,sha256=En2YsrG-VTf0ISc2bW9hm7HFnvQTMuxJpfpAaapjJDI,64
63
+ quantalogic/version.py,sha256=rbj6GCYHf5rUvES_yzIMSiI_7I2h-zkrmZOjre8NI7A,65
63
64
  quantalogic/xml_parser.py,sha256=cTRorr5sVfkIzH72M0C-GQ9ROGPiz2FTT66U9ndjzhE,9538
64
65
  quantalogic/xml_tool_parser.py,sha256=lsVzClZBrZan7wjCuCKnGHWzksXI3VMy_vWthxu2_bo,3738
65
- quantalogic-0.2.8.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
66
- quantalogic-0.2.8.dist-info/METADATA,sha256=XISo9oYxS-YXKmE7Prszx8Y_b4Oc36FYNXKAzhILsE8,39180
67
- quantalogic-0.2.8.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
68
- quantalogic-0.2.8.dist-info/entry_points.txt,sha256=wgSq5SRU98yvlRHGEZD1Xn7sS5CSjH2RfUtTa6Qy28Q,52
69
- quantalogic-0.2.8.dist-info/RECORD,,
66
+ quantalogic-0.2.10.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
67
+ quantalogic-0.2.10.dist-info/METADATA,sha256=Hu3vVRYMVGIPJpoCxP_6S4XKbw6R5rKPxcvS5y4Xfnw,39747
68
+ quantalogic-0.2.10.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
69
+ quantalogic-0.2.10.dist-info/entry_points.txt,sha256=wgSq5SRU98yvlRHGEZD1Xn7sS5CSjH2RfUtTa6Qy28Q,52
70
+ quantalogic-0.2.10.dist-info/RECORD,,