quantalogic 0.2.14__tar.gz → 0.2.16__tar.gz
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-0.2.14 → quantalogic-0.2.16}/PKG-INFO +5 -2
- {quantalogic-0.2.14 → quantalogic-0.2.16}/README.md +3 -1
- {quantalogic-0.2.14 → quantalogic-0.2.16}/pyproject.toml +2 -1
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/agent.py +3 -3
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/agent_config.py +4 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/coding_agent.py +3 -1
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/generative_model.py +43 -15
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/main.py +43 -16
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/prompts.py +1 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/search_agent.py +24 -5
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/__init__.py +2 -0
- quantalogic-0.2.16/quantalogic/tools/duckduckgo_search_tool.py +214 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/markitdown_tool.py +36 -33
- {quantalogic-0.2.14 → quantalogic-0.2.16}/LICENSE +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/__init__.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/event_emitter.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/interactive_text_editor.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/memory.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/model_names.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/print_event.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/server/__init__.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/server/agent_server.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/server/models.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/server/routes.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/server/state.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/server/static/js/event_visualizer.js +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/server/static/js/quantalogic.js +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/server/templates/index.html +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tool_manager.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/agent_tool.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/download_http_file_tool.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/edit_whole_content_tool.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/elixir_tool.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/execute_bash_command_tool.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/input_question_tool.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/language_handlers/__init__.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/language_handlers/c_handler.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/language_handlers/cpp_handler.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/language_handlers/go_handler.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/language_handlers/java_handler.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/language_handlers/javascript_handler.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/language_handlers/python_handler.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/language_handlers/rust_handler.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/language_handlers/scala_handler.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/language_handlers/typescript_handler.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/list_directory_tool.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/llm_tool.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/llm_vision_tool.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/nodejs_tool.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/python_tool.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/read_file_block_tool.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/read_file_tool.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/replace_in_file_tool.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/ripgrep_tool.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/search_definition_names.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/serpapi_search_tool.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/task_complete_tool.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/tool.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/unified_diff_tool.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/wikipedia_search_tool.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/write_file_tool.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/utils/__init__.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/utils/ask_user_validation.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/utils/check_version.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/utils/download_http_file.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/utils/get_coding_environment.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/utils/get_environment.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/utils/get_quantalogic_rules_content.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/utils/git_ls.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/utils/read_file.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/utils/read_http_text_content.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/version.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/xml_parser.py +0 -0
- {quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/xml_tool_parser.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: quantalogic
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.16
|
4
4
|
Summary: QuantaLogic ReAct Agents
|
5
5
|
Author: Raphaël MANSUY
|
6
6
|
Author-email: raphael.mansuy@gmail.com
|
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.13
|
11
11
|
Requires-Dist: boto3 (>=1.35.86,<2.0.0)
|
12
12
|
Requires-Dist: click (>=8.1.8,<9.0.0)
|
13
|
+
Requires-Dist: duckduckgo-search (>=7.2.1,<8.0.0)
|
13
14
|
Requires-Dist: fastapi (>=0.115.6,<0.116.0)
|
14
15
|
Requires-Dist: google-auth (>=2.20.0,<3.0.0)
|
15
16
|
Requires-Dist: google-search-results (>=2.4.2,<3.0.0)
|
@@ -132,7 +133,8 @@ Options:
|
|
132
133
|
e.g. "openrouter/A/gpt-4o-mini").
|
133
134
|
--log [info|debug|warning] Set logging level (info/debug/warning).
|
134
135
|
--verbose Enable verbose output.
|
135
|
-
--
|
136
|
+
--max-iterations INTEGER Maximum iterations for task solving (default: 30).
|
137
|
+
--mode [code|basic|interpreter|full|code-basic|search|search-full]
|
136
138
|
Agent mode (code/search/full).
|
137
139
|
--help Show this message and exit.
|
138
140
|
|
@@ -153,6 +155,7 @@ task Execute a task with the QuantaLogic AI Assistant
|
|
153
155
|
- interpreter: Interactive code execution agent
|
154
156
|
- full: Full-featured agent with all capabilities
|
155
157
|
- code-basic: Coding agent with basic reasoning
|
158
|
+
- search: Web search agent with Wikipedia, DuckDuckGo and SERPApi integration
|
156
159
|
|
157
160
|
#### Task Execution
|
158
161
|
|
@@ -92,7 +92,8 @@ Options:
|
|
92
92
|
e.g. "openrouter/A/gpt-4o-mini").
|
93
93
|
--log [info|debug|warning] Set logging level (info/debug/warning).
|
94
94
|
--verbose Enable verbose output.
|
95
|
-
--
|
95
|
+
--max-iterations INTEGER Maximum iterations for task solving (default: 30).
|
96
|
+
--mode [code|basic|interpreter|full|code-basic|search|search-full]
|
96
97
|
Agent mode (code/search/full).
|
97
98
|
--help Show this message and exit.
|
98
99
|
|
@@ -113,6 +114,7 @@ task Execute a task with the QuantaLogic AI Assistant
|
|
113
114
|
- interpreter: Interactive code execution agent
|
114
115
|
- full: Full-featured agent with all capabilities
|
115
116
|
- code-basic: Coding agent with basic reasoning
|
117
|
+
- search: Web search agent with Wikipedia, DuckDuckGo and SERPApi integration
|
116
118
|
|
117
119
|
#### Task Execution
|
118
120
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "quantalogic"
|
3
|
-
version = "0.2.
|
3
|
+
version = "0.2.16"
|
4
4
|
description = "QuantaLogic ReAct Agents"
|
5
5
|
authors = ["Raphaël MANSUY <raphael.mansuy@gmail.com>"]
|
6
6
|
readme = "README.md"
|
@@ -36,6 +36,7 @@ toml = "^0.10.2"
|
|
36
36
|
types-requests = "^2.32.0.20241016"
|
37
37
|
google-search-results = "^2.4.2"
|
38
38
|
serpapi = "^0.1.5"
|
39
|
+
duckduckgo-search = "^7.2.1"
|
39
40
|
|
40
41
|
[tool.poetry.scripts]
|
41
42
|
quantalogic = "quantalogic.main:cli"
|
@@ -406,7 +406,7 @@ class Agent(BaseModel):
|
|
406
406
|
|
407
407
|
formatted_response = (
|
408
408
|
"\n"
|
409
|
-
f"--- Observations for iteration {iteration} ---\n"
|
409
|
+
f"--- Observations for iteration {iteration} / max {self.max_iterations} ---\n"
|
410
410
|
"\n"
|
411
411
|
f"\n --- Tool execution result stored in variable ${variable_name}$ --- \n"
|
412
412
|
"\n"
|
@@ -428,7 +428,7 @@ class Agent(BaseModel):
|
|
428
428
|
# Format the response message
|
429
429
|
formatted_response = (
|
430
430
|
"\n"
|
431
|
-
f"--- Observations for iteration {iteration} ---\n"
|
431
|
+
f"--- Observations for iteration {iteration} / max {self.max_iterations} ---\n"
|
432
432
|
"\n"
|
433
433
|
f"\n --- Tool execution result stored in variable ${variable_name}$ --- \n"
|
434
434
|
"\n"
|
@@ -440,7 +440,7 @@ class Agent(BaseModel):
|
|
440
440
|
"\n"
|
441
441
|
f"--- Variables --- \n"
|
442
442
|
"\n"
|
443
|
-
f"{self._get_variable_prompt()}"
|
443
|
+
f"{self._get_variable_prompt()}\n"
|
444
444
|
"\n"
|
445
445
|
"You must analyze this answer and evaluate what to do next to solve the task.\n"
|
446
446
|
"If the step failed, take a step back and rethink your approach.\n"
|
@@ -24,6 +24,8 @@ from quantalogic.tools import (
|
|
24
24
|
SearchDefinitionNames,
|
25
25
|
TaskCompleteTool,
|
26
26
|
WriteFileTool,
|
27
|
+
DuckDuckGoSearchTool,
|
28
|
+
WikipediaSearchTool,
|
27
29
|
)
|
28
30
|
|
29
31
|
MODEL_NAME = "deepseek/deepseek-chat"
|
@@ -124,6 +126,8 @@ def create_full_agent(model_name: str, vision_model_name: str | None) -> Agent:
|
|
124
126
|
MarkitdownTool(),
|
125
127
|
LLMTool(model_name=model_name),
|
126
128
|
DownloadHttpFileTool(),
|
129
|
+
WikipediaSearchTool(),
|
130
|
+
DuckDuckGoSearchTool(),
|
127
131
|
]
|
128
132
|
|
129
133
|
if vision_model_name:
|
@@ -1,5 +1,6 @@
|
|
1
1
|
from quantalogic.agent import Agent
|
2
2
|
from quantalogic.tools import (
|
3
|
+
DuckDuckGoSearchTool,
|
3
4
|
EditWholeContentTool,
|
4
5
|
ExecuteBashCommandTool,
|
5
6
|
InputQuestionTool,
|
@@ -59,6 +60,7 @@ def create_coding_agent(model_name: str, vision_model_name: str | None = None, b
|
|
59
60
|
ReadFileTool(),
|
60
61
|
ExecuteBashCommandTool(),
|
61
62
|
InputQuestionTool(),
|
63
|
+
DuckDuckGoSearchTool(),
|
62
64
|
]
|
63
65
|
|
64
66
|
if vision_model_name:
|
@@ -69,7 +71,7 @@ def create_coding_agent(model_name: str, vision_model_name: str | None = None, b
|
|
69
71
|
LLMTool(
|
70
72
|
model_name=model_name,
|
71
73
|
system_prompt="You are a software expert, your role is to answer coding questions.",
|
72
|
-
name="coding_consultant", # Handles implementation-level coding questions
|
74
|
+
name="coding_consultant", # Handles implementation-level coding questions
|
73
75
|
)
|
74
76
|
)
|
75
77
|
tools.append(
|
@@ -1,5 +1,7 @@
|
|
1
1
|
"""Generative model module for AI-powered text generation."""
|
2
2
|
|
3
|
+
import functools
|
4
|
+
|
3
5
|
import openai
|
4
6
|
from litellm import completion, exceptions, get_max_tokens, get_model_info, token_counter
|
5
7
|
from loguru import logger
|
@@ -83,6 +85,7 @@ class GenerativeModel:
|
|
83
85
|
logger.debug(f"Initializing GenerativeModel with model={model}, temperature={temperature}")
|
84
86
|
self.model = model
|
85
87
|
self.temperature = temperature
|
88
|
+
self._get_model_info_cached = functools.lru_cache(maxsize=32)(self._get_model_info_impl)
|
86
89
|
|
87
90
|
# Define retriable exceptions based on LiteLLM's exception mapping
|
88
91
|
RETRIABLE_EXCEPTIONS = (
|
@@ -235,21 +238,46 @@ class GenerativeModel:
|
|
235
238
|
litellm_messages.append({"role": "user", "content": str(prompt)})
|
236
239
|
return token_counter(model=self.model, messages=litellm_messages)
|
237
240
|
|
238
|
-
def
|
239
|
-
"""Get information about the model.
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
241
|
+
def _get_model_info_impl(self, model_name: str) -> dict:
|
242
|
+
"""Get information about the model with prefix fallback logic.
|
243
|
+
|
244
|
+
Attempts to find model info by progressively removing provider prefixes.
|
245
|
+
Raises ValueError if no valid model configuration is found.
|
246
|
+
Results are cached to improve performance.
|
247
|
+
|
248
|
+
Example:
|
249
|
+
openrouter/openai/gpt-4o-mini → openai/gpt-4o-mini → gpt-4o-mini
|
250
|
+
"""
|
251
|
+
original_model = model_name
|
252
|
+
|
253
|
+
while True:
|
254
|
+
try:
|
255
|
+
logger.debug(f"Attempting to retrieve model info for: {model_name}")
|
256
|
+
model_info = get_model_info(model_name)
|
257
|
+
if model_info:
|
258
|
+
logger.debug(f"Found model info for {model_name}: {model_info}")
|
259
|
+
return model_info
|
260
|
+
except Exception:
|
261
|
+
pass
|
262
|
+
|
263
|
+
# Try removing one prefix level
|
264
|
+
parts = model_name.split('/')
|
265
|
+
if len(parts) <= 1:
|
266
|
+
break
|
267
|
+
model_name = '/'.join(parts[1:])
|
268
|
+
|
269
|
+
error_msg = f"Could not find model info for {original_model} after trying: {self.model} → {model_name}"
|
270
|
+
logger.error(error_msg)
|
271
|
+
raise ValueError(error_msg)
|
272
|
+
|
273
|
+
def get_model_info(self, model_name: str = None) -> dict:
|
274
|
+
"""Get cached information about the model.
|
275
|
+
|
276
|
+
If no model name is provided, uses the current model.
|
277
|
+
"""
|
278
|
+
if model_name is None:
|
279
|
+
model_name = self.model
|
280
|
+
return self._get_model_info_cached(model_name)
|
253
281
|
|
254
282
|
def get_model_max_input_tokens(self) -> int:
|
255
283
|
"""Get the maximum number of input tokens for the model."""
|
@@ -32,9 +32,9 @@ from quantalogic.agent_config import ( # noqa: E402
|
|
32
32
|
)
|
33
33
|
from quantalogic.interactive_text_editor import get_multiline_input # noqa: E402
|
34
34
|
from quantalogic.print_event import console_print_events # noqa: E402
|
35
|
-
from quantalogic.search_agent import create_search_agent
|
35
|
+
from quantalogic.search_agent import create_search_agent # noqa: E402
|
36
36
|
|
37
|
-
AGENT_MODES = ["code", "basic", "interpreter", "full", "code-basic","search"]
|
37
|
+
AGENT_MODES = ["code", "basic", "interpreter", "full", "code-basic", "search", "search-full"]
|
38
38
|
|
39
39
|
|
40
40
|
def create_agent_for_mode(mode: str, model_name: str, vision_model_name: str | None) -> Agent:
|
@@ -53,9 +53,12 @@ def create_agent_for_mode(mode: str, model_name: str, vision_model_name: str | N
|
|
53
53
|
return create_interpreter_agent(model_name, vision_model_name)
|
54
54
|
elif mode == "search":
|
55
55
|
return create_search_agent(model_name)
|
56
|
+
if mode == "search-full":
|
57
|
+
return create_search_agent(model_name, mode_full=True)
|
56
58
|
else:
|
57
59
|
raise ValueError(f"Unknown agent mode: {mode}")
|
58
60
|
|
61
|
+
|
59
62
|
def check_new_version():
|
60
63
|
# Randomly check for updates (1 in 10 chance)
|
61
64
|
if random.randint(1, 10) == 1:
|
@@ -81,6 +84,7 @@ def check_new_version():
|
|
81
84
|
except Exception:
|
82
85
|
return
|
83
86
|
|
87
|
+
|
84
88
|
def configure_logger(log_level: str) -> None:
|
85
89
|
"""Configure the logger with the specified log level and format."""
|
86
90
|
logger.remove()
|
@@ -122,7 +126,9 @@ def get_task_from_file(file_path: str) -> str:
|
|
122
126
|
raise Exception(f"Unexpected error reading file: {e}")
|
123
127
|
|
124
128
|
|
125
|
-
def display_welcome_message(
|
129
|
+
def display_welcome_message(
|
130
|
+
console: Console, model_name: str, vision_model_name: str | None, max_iterations: int = 50
|
131
|
+
) -> None:
|
126
132
|
"""Display the welcome message and instructions."""
|
127
133
|
version = get_version()
|
128
134
|
console.print(
|
@@ -135,7 +141,8 @@ def display_welcome_message(console: Console, model_name: str, vision_model_name
|
|
135
141
|
f"[yellow] 🤖 System Info:[/yellow]\n\n"
|
136
142
|
"\n"
|
137
143
|
f"- Model: {model_name}\n"
|
138
|
-
f"- Vision Model: {vision_model_name}\n
|
144
|
+
f"- Vision Model: {vision_model_name}\n"
|
145
|
+
f"- Max Iterations: {max_iterations}\n\n"
|
139
146
|
"[bold magenta]💡 Pro Tips:[/bold magenta]\n\n"
|
140
147
|
"- Be as specific as possible in your task description to get the best results!\n"
|
141
148
|
"- Use clear and concise language when describing your task\n"
|
@@ -167,6 +174,12 @@ def display_welcome_message(console: Console, model_name: str, vision_model_name
|
|
167
174
|
default=None,
|
168
175
|
help='Specify the vision model to use (litellm format, e.g. "openrouter/A/gpt-4o-mini").',
|
169
176
|
)
|
177
|
+
@click.option(
|
178
|
+
"--max-iterations",
|
179
|
+
type=int,
|
180
|
+
default=30,
|
181
|
+
help="Maximum number of iterations for task solving (default: 30).",
|
182
|
+
)
|
170
183
|
@click.pass_context
|
171
184
|
def cli(
|
172
185
|
ctx: click.Context,
|
@@ -176,6 +189,7 @@ def cli(
|
|
176
189
|
mode: str,
|
177
190
|
log: str,
|
178
191
|
vision_model_name: str | None,
|
192
|
+
max_iterations: int,
|
179
193
|
) -> None:
|
180
194
|
"""QuantaLogic AI Assistant - A powerful AI tool for various tasks."""
|
181
195
|
if version:
|
@@ -184,7 +198,13 @@ def cli(
|
|
184
198
|
sys.exit(0)
|
185
199
|
if ctx.invoked_subcommand is None:
|
186
200
|
ctx.invoke(
|
187
|
-
task,
|
201
|
+
task,
|
202
|
+
model_name=model_name,
|
203
|
+
verbose=verbose,
|
204
|
+
mode=mode,
|
205
|
+
log=log,
|
206
|
+
vision_model_name=vision_model_name,
|
207
|
+
max_iterations=max_iterations,
|
188
208
|
)
|
189
209
|
|
190
210
|
|
@@ -208,6 +228,12 @@ def cli(
|
|
208
228
|
default=None,
|
209
229
|
help='Specify the vision model to use (litellm format, e.g. "openrouter/openai/gpt-4o-mini").',
|
210
230
|
)
|
231
|
+
@click.option(
|
232
|
+
"--max-iterations",
|
233
|
+
type=int,
|
234
|
+
default=30,
|
235
|
+
help="Maximum number of iterations for task solving (default: 30).",
|
236
|
+
)
|
211
237
|
@click.argument("task", required=False)
|
212
238
|
def task(
|
213
239
|
file: Optional[str],
|
@@ -217,12 +243,12 @@ def task(
|
|
217
243
|
log: str,
|
218
244
|
vision_model_name: str | None,
|
219
245
|
task: Optional[str],
|
246
|
+
max_iterations: int,
|
220
247
|
) -> None:
|
221
248
|
"""Execute a task with the QuantaLogic AI Assistant."""
|
222
249
|
console = Console()
|
223
250
|
switch_verbose(verbose, log)
|
224
251
|
|
225
|
-
|
226
252
|
try:
|
227
253
|
if file:
|
228
254
|
task_content = get_task_from_file(file)
|
@@ -231,7 +257,7 @@ def task(
|
|
231
257
|
check_new_version()
|
232
258
|
task_content = task
|
233
259
|
else:
|
234
|
-
display_welcome_message(console, model_name, vision_model_name)
|
260
|
+
display_welcome_message(console, model_name, vision_model_name, max_iterations=max_iterations)
|
235
261
|
check_new_version()
|
236
262
|
logger.debug("Waiting for user input...")
|
237
263
|
task_content = get_multiline_input(console).strip()
|
@@ -241,14 +267,13 @@ def task(
|
|
241
267
|
console.print("[yellow]No task provided. Exiting...[/yellow]")
|
242
268
|
sys.exit(2)
|
243
269
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
border_style="blue",
|
250
|
-
)
|
270
|
+
console.print(
|
271
|
+
Panel.fit(
|
272
|
+
f"[bold]Task to be submitted:[/bold]\n{task_content}",
|
273
|
+
title="[bold]Task Preview[/bold]",
|
274
|
+
border_style="blue",
|
251
275
|
)
|
276
|
+
)
|
252
277
|
if not Confirm.ask("[bold]Are you sure you want to submit this task?[/bold]"):
|
253
278
|
console.print("[yellow]Task submission cancelled. Exiting...[/yellow]")
|
254
279
|
sys.exit(0)
|
@@ -284,8 +309,10 @@ def task(
|
|
284
309
|
logger.debug("Registered event handlers for agent events with events: {events}")
|
285
310
|
|
286
311
|
logger.debug(f"Solving task with agent: {task_content}")
|
287
|
-
|
288
|
-
|
312
|
+
if max_iterations < 1:
|
313
|
+
raise ValueError("max_iterations must be greater than 0")
|
314
|
+
result = agent.solve_task(task=task_content, max_iterations=max_iterations)
|
315
|
+
logger.debug(f"Task solved with result: {result} using {max_iterations} iterations")
|
289
316
|
|
290
317
|
console.print(
|
291
318
|
Panel.fit(
|
@@ -1,16 +1,27 @@
|
|
1
1
|
from quantalogic.agent import Agent
|
2
|
-
from quantalogic.tools import
|
2
|
+
from quantalogic.tools import (
|
3
|
+
DuckDuckGoSearchTool,
|
4
|
+
InputQuestionTool,
|
5
|
+
MarkitdownTool,
|
6
|
+
ReadFileBlockTool,
|
7
|
+
ReadFileTool,
|
8
|
+
RipgrepTool,
|
9
|
+
SerpApiSearchTool,
|
10
|
+
TaskCompleteTool,
|
11
|
+
WikipediaSearchTool,
|
12
|
+
)
|
3
13
|
|
4
14
|
|
5
|
-
def create_search_agent(model_name: str) -> Agent:
|
6
|
-
"""Creates and configures a search agent with web and
|
15
|
+
def create_search_agent(model_name: str, mode_full: bool = False) -> Agent:
|
16
|
+
"""Creates and configures a search agent with web, knowledge, and privacy-focused search tools.
|
7
17
|
|
8
18
|
Args:
|
9
19
|
model_name (str): Name of the language model to use for the agent's core capabilities
|
20
|
+
mode_full (bool, optional): If True, the agent will be configured with a full set of tools.
|
10
21
|
|
11
22
|
Returns:
|
12
23
|
Agent: A fully configured search agent instance with:
|
13
|
-
- Web search capabilities (SerpAPI)
|
24
|
+
- Web search capabilities (SerpAPI, DuckDuckGo)
|
14
25
|
- Knowledge search capabilities (Wikipedia)
|
15
26
|
- Basic interaction tools
|
16
27
|
"""
|
@@ -21,7 +32,7 @@ def create_search_agent(model_name: str) -> Agent:
|
|
21
32
|
|
22
33
|
tools = [
|
23
34
|
# Search tools
|
24
|
-
|
35
|
+
DuckDuckGoSearchTool(), # Privacy-focused web search
|
25
36
|
WikipediaSearchTool(), # Knowledge search capabilities
|
26
37
|
# Basic interaction tools
|
27
38
|
TaskCompleteTool(), # Marks task completion
|
@@ -34,6 +45,14 @@ def create_search_agent(model_name: str) -> Agent:
|
|
34
45
|
RipgrepTool(), # Code search capabilities
|
35
46
|
]
|
36
47
|
|
48
|
+
if mode_full:
|
49
|
+
tools.extend(
|
50
|
+
[
|
51
|
+
# Search tools
|
52
|
+
SerpApiSearchTool(), # Web search capabilities
|
53
|
+
]
|
54
|
+
)
|
55
|
+
|
37
56
|
return Agent(
|
38
57
|
model_name=model_name,
|
39
58
|
tools=tools,
|
@@ -18,6 +18,7 @@ from .replace_in_file_tool import ReplaceInFileTool
|
|
18
18
|
from .ripgrep_tool import RipgrepTool
|
19
19
|
from .search_definition_names import SearchDefinitionNames
|
20
20
|
from .serpapi_search_tool import SerpApiSearchTool
|
21
|
+
from .duckduckgo_search_tool import DuckDuckGoSearchTool
|
21
22
|
from .task_complete_tool import TaskCompleteTool
|
22
23
|
from .tool import Tool, ToolArgument
|
23
24
|
from .unified_diff_tool import UnifiedDiffTool
|
@@ -27,6 +28,7 @@ from .write_file_tool import WriteFileTool
|
|
27
28
|
__all__ = [
|
28
29
|
"WikipediaSearchTool",
|
29
30
|
"SerpApiSearchTool",
|
31
|
+
"DuckDuckGoSearchTool",
|
30
32
|
"Tool",
|
31
33
|
"ToolArgument",
|
32
34
|
"TaskCompleteTool",
|
@@ -0,0 +1,214 @@
|
|
1
|
+
"""Tool for interacting with DuckDuckGo for search results."""
|
2
|
+
|
3
|
+
from duckduckgo_search import DDGS
|
4
|
+
|
5
|
+
from quantalogic.tools.tool import Tool, ToolArgument
|
6
|
+
|
7
|
+
|
8
|
+
class DuckDuckGoSearchTool(Tool):
|
9
|
+
"""Tool for retrieving search results from DuckDuckGo.
|
10
|
+
|
11
|
+
This tool provides a convenient interface to DuckDuckGo's search capabilities,
|
12
|
+
supporting multiple search types and structured JSON output.
|
13
|
+
|
14
|
+
Example usage:
|
15
|
+
```python
|
16
|
+
tool = DuckDuckGoSearchTool()
|
17
|
+
results = tool.execute(
|
18
|
+
query="machine learning",
|
19
|
+
search_type="text",
|
20
|
+
max_results=10,
|
21
|
+
region="us-en",
|
22
|
+
safesearch="moderate"
|
23
|
+
)
|
24
|
+
print(results)
|
25
|
+
```
|
26
|
+
|
27
|
+
The tool handles:
|
28
|
+
- Query validation
|
29
|
+
- API error handling
|
30
|
+
- Multiple search types (text, images, videos, news)
|
31
|
+
- Scope filtering (region, safesearch, timelimit)
|
32
|
+
- JSON result formatting
|
33
|
+
"""
|
34
|
+
|
35
|
+
name: str = "duckduckgo_tool"
|
36
|
+
description: str = (
|
37
|
+
"Retrieves search results from DuckDuckGo. "
|
38
|
+
"Provides structured output of search results."
|
39
|
+
)
|
40
|
+
arguments: list = [
|
41
|
+
ToolArgument(
|
42
|
+
name="query",
|
43
|
+
arg_type="string",
|
44
|
+
description="The search query to execute",
|
45
|
+
required=True,
|
46
|
+
example="machine learning",
|
47
|
+
),
|
48
|
+
ToolArgument(
|
49
|
+
name="max_results",
|
50
|
+
arg_type="int",
|
51
|
+
description="Maximum number of results to retrieve (1-50)",
|
52
|
+
required=True,
|
53
|
+
default="10",
|
54
|
+
example="20",
|
55
|
+
),
|
56
|
+
ToolArgument(
|
57
|
+
name="search_type",
|
58
|
+
arg_type="string",
|
59
|
+
description="Type of search to perform (text, images, videos, news)",
|
60
|
+
required=False,
|
61
|
+
default="text",
|
62
|
+
example="images",
|
63
|
+
),
|
64
|
+
ToolArgument(
|
65
|
+
name="region",
|
66
|
+
arg_type="string",
|
67
|
+
description="Region for search results (e.g., 'wt-wt', 'us-en')",
|
68
|
+
required=False,
|
69
|
+
default="wt-wt",
|
70
|
+
example="us-en",
|
71
|
+
),
|
72
|
+
ToolArgument(
|
73
|
+
name="safesearch",
|
74
|
+
arg_type="string",
|
75
|
+
description="Safesearch level ('on', 'moderate', 'off')",
|
76
|
+
required=False,
|
77
|
+
default="moderate",
|
78
|
+
example="moderate",
|
79
|
+
),
|
80
|
+
ToolArgument(
|
81
|
+
name="timelimit",
|
82
|
+
arg_type="string",
|
83
|
+
description="Time limit for results (e.g., 'd' for day, 'w' for week)",
|
84
|
+
required=False,
|
85
|
+
default=None,
|
86
|
+
example="d",
|
87
|
+
),
|
88
|
+
]
|
89
|
+
|
90
|
+
def execute(
|
91
|
+
self,
|
92
|
+
query: str,
|
93
|
+
max_results: int = 10,
|
94
|
+
search_type: str = "text",
|
95
|
+
region: str = "wt-wt",
|
96
|
+
safesearch: str = "moderate",
|
97
|
+
timelimit: str = None,
|
98
|
+
) -> str:
|
99
|
+
"""Execute a search query using DuckDuckGo and return results.
|
100
|
+
|
101
|
+
Args:
|
102
|
+
query: The search query to execute
|
103
|
+
max_results: Maximum number of results to retrieve (1-50)
|
104
|
+
search_type: Type of search to perform (text, images, videos, news)
|
105
|
+
region: Region for search results (e.g., "wt-wt", "us-en")
|
106
|
+
safesearch: Safesearch level ("on", "moderate", "off")
|
107
|
+
timelimit: Time limit for results (e.g., "d" for day, "w" for week)
|
108
|
+
|
109
|
+
Returns:
|
110
|
+
Pretty-printed JSON string of search results.
|
111
|
+
|
112
|
+
Raises:
|
113
|
+
ValueError: If any parameter is invalid
|
114
|
+
RuntimeError: If search fails
|
115
|
+
"""
|
116
|
+
# Handle empty string parameters by setting to defaults
|
117
|
+
query = str(query) if query else query
|
118
|
+
search_type = search_type if search_type else "text"
|
119
|
+
region = region if region else "wt-wt"
|
120
|
+
safesearch = safesearch if safesearch else "moderate"
|
121
|
+
timelimit = timelimit if timelimit else None
|
122
|
+
|
123
|
+
# Validate and convert query
|
124
|
+
if not query:
|
125
|
+
raise ValueError("Query must be a non-empty string")
|
126
|
+
try:
|
127
|
+
query = str(query)
|
128
|
+
except (TypeError, ValueError) as e:
|
129
|
+
raise ValueError(f"Query must be convertible to string: {str(e)}")
|
130
|
+
|
131
|
+
# Validate and convert max_results
|
132
|
+
try:
|
133
|
+
max_results = int(max_results)
|
134
|
+
if max_results < 1 or max_results > 50:
|
135
|
+
raise ValueError("Number of results must be between 1 and 50")
|
136
|
+
except (TypeError, ValueError) as e:
|
137
|
+
raise ValueError(f"Invalid number of results: {str(e)}")
|
138
|
+
|
139
|
+
# Validate search_type
|
140
|
+
if search_type not in ["text", "images", "videos", "news"]:
|
141
|
+
raise ValueError("search_type must be one of: text, images, videos, news")
|
142
|
+
|
143
|
+
# Validate safesearch
|
144
|
+
if safesearch not in ["on", "moderate", "off"]:
|
145
|
+
raise ValueError("safesearch must be one of: on, moderate, off")
|
146
|
+
|
147
|
+
try:
|
148
|
+
ddgs = DDGS()
|
149
|
+
|
150
|
+
# Perform the appropriate search based on search_type
|
151
|
+
if search_type == "text":
|
152
|
+
results = ddgs.text(
|
153
|
+
keywords=query,
|
154
|
+
region=region,
|
155
|
+
safesearch=safesearch,
|
156
|
+
timelimit=timelimit,
|
157
|
+
max_results=max_results,
|
158
|
+
)
|
159
|
+
elif search_type == "images":
|
160
|
+
results = ddgs.images(
|
161
|
+
keywords=query,
|
162
|
+
region=region,
|
163
|
+
safesearch=safesearch,
|
164
|
+
timelimit=timelimit,
|
165
|
+
max_results=max_results,
|
166
|
+
)
|
167
|
+
elif search_type == "videos":
|
168
|
+
results = ddgs.videos(
|
169
|
+
keywords=query,
|
170
|
+
region=region,
|
171
|
+
safesearch=safesearch,
|
172
|
+
timelimit=timelimit,
|
173
|
+
max_results=max_results,
|
174
|
+
)
|
175
|
+
elif search_type == "news":
|
176
|
+
results = ddgs.news(
|
177
|
+
keywords=query,
|
178
|
+
region=region,
|
179
|
+
safesearch=safesearch,
|
180
|
+
timelimit=timelimit,
|
181
|
+
max_results=max_results,
|
182
|
+
)
|
183
|
+
|
184
|
+
# Return pretty-printed JSON
|
185
|
+
import json
|
186
|
+
return json.dumps(results, indent=4, ensure_ascii=False)
|
187
|
+
|
188
|
+
except Exception as e:
|
189
|
+
raise RuntimeError(f"Search failed: {str(e)}")
|
190
|
+
|
191
|
+
|
192
|
+
def main():
|
193
|
+
"""Demonstrate DuckDuckGoSearchTool functionality."""
|
194
|
+
try:
|
195
|
+
tool = DuckDuckGoSearchTool()
|
196
|
+
|
197
|
+
# Test basic search functionality
|
198
|
+
print("Testing DuckDuckGoSearchTool with sample query...")
|
199
|
+
results = tool.execute(query="Python programming", max_results=3)
|
200
|
+
print(results)
|
201
|
+
|
202
|
+
# Test error handling
|
203
|
+
print("\nTesting error handling with invalid query...")
|
204
|
+
try:
|
205
|
+
tool.execute(query="")
|
206
|
+
except ValueError as e:
|
207
|
+
print(f"Caught expected ValueError: {e}")
|
208
|
+
|
209
|
+
except Exception as e:
|
210
|
+
print(f"Error in main: {e}")
|
211
|
+
|
212
|
+
|
213
|
+
if __name__ == "__main__":
|
214
|
+
main()
|
@@ -47,50 +47,53 @@ class MarkitdownTool(Tool):
|
|
47
47
|
Returns:
|
48
48
|
str: The Markdown content or a success message.
|
49
49
|
"""
|
50
|
-
# Handle tilde expansion for local paths
|
51
|
-
if file_path.startswith("~"):
|
52
|
-
file_path = os.path.expanduser(file_path)
|
53
|
-
|
54
|
-
# Handle URL paths
|
55
|
-
if file_path.startswith(("http://", "https://")):
|
56
|
-
try:
|
57
|
-
# Create a temporary file
|
58
|
-
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
|
59
|
-
temp_path = temp_file.name
|
60
|
-
# Download the file from URL
|
61
|
-
download_http_file(file_path, temp_path)
|
62
|
-
# Use the temporary file path for conversion
|
63
|
-
file_path = temp_path
|
64
|
-
is_temp_file = True
|
65
|
-
except Exception as e:
|
66
|
-
return f"Error downloading file from URL: {str(e)}"
|
67
|
-
else:
|
68
|
-
is_temp_file = False
|
69
|
-
|
70
50
|
try:
|
71
|
-
|
51
|
+
# Handle URL paths first
|
52
|
+
if file_path.startswith(("http://", "https://")):
|
53
|
+
try:
|
54
|
+
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
|
55
|
+
temp_path = temp_file.name
|
56
|
+
download_http_file(file_path, temp_path)
|
57
|
+
file_path = temp_path
|
58
|
+
is_temp_file = True
|
59
|
+
except Exception as e:
|
60
|
+
return f"Error downloading file from URL: {str(e)}"
|
61
|
+
else:
|
62
|
+
is_temp_file = False
|
63
|
+
# Handle local paths
|
64
|
+
if file_path.startswith("~"):
|
65
|
+
file_path = os.path.expanduser(file_path)
|
66
|
+
if not os.path.isabs(file_path):
|
67
|
+
file_path = os.path.abspath(file_path)
|
68
|
+
|
69
|
+
# Verify file exists
|
70
|
+
if not os.path.exists(file_path):
|
71
|
+
return f"Error: File not found at path: {file_path}"
|
72
72
|
|
73
|
+
from markitdown import MarkItDown
|
73
74
|
md = MarkItDown()
|
74
75
|
result = md.convert(file_path)
|
75
76
|
|
76
77
|
if output_file_path:
|
78
|
+
# Ensure output directory exists
|
79
|
+
output_dir = os.path.dirname(output_file_path)
|
80
|
+
if output_dir and not os.path.exists(output_dir):
|
81
|
+
os.makedirs(output_dir)
|
82
|
+
|
77
83
|
with open(output_file_path, "w", encoding="utf-8") as f:
|
78
84
|
f.write(result.text_content)
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
return output_message
|
85
|
+
return f"Markdown content successfully written to {output_file_path}"
|
86
|
+
|
87
|
+
# Handle content truncation
|
88
|
+
lines = result.text_content.splitlines()
|
89
|
+
if len(lines) > MAX_LINES:
|
90
|
+
truncated_content = "\n".join(lines[:MAX_LINES])
|
91
|
+
return f"Markdown content truncated to {MAX_LINES} lines:\n{truncated_content}"
|
92
|
+
return result.text_content
|
93
|
+
|
90
94
|
except Exception as e:
|
91
95
|
return f"Error converting file to Markdown: {str(e)}"
|
92
96
|
finally:
|
93
|
-
# Clean up temporary file if it was created
|
94
97
|
if is_temp_file and os.path.exists(file_path):
|
95
98
|
os.remove(file_path)
|
96
99
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/language_handlers/cpp_handler.py
RENAMED
File without changes
|
File without changes
|
{quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/language_handlers/java_handler.py
RENAMED
File without changes
|
{quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/language_handlers/javascript_handler.py
RENAMED
File without changes
|
{quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/language_handlers/python_handler.py
RENAMED
File without changes
|
{quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/language_handlers/rust_handler.py
RENAMED
File without changes
|
{quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/language_handlers/scala_handler.py
RENAMED
File without changes
|
{quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/tools/language_handlers/typescript_handler.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{quantalogic-0.2.14 → quantalogic-0.2.16}/quantalogic/utils/get_quantalogic_rules_content.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|