quantalogic 0.2.15__py3-none-any.whl → 0.2.17__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/__init__.py +3 -2
- quantalogic/agent.py +58 -37
- quantalogic/agent_config.py +18 -13
- quantalogic/coding_agent.py +9 -3
- quantalogic/{print_event.py → console_print_events.py} +1 -3
- quantalogic/console_print_token.py +16 -0
- quantalogic/docs_cli.py +50 -0
- quantalogic/generative_model.py +80 -77
- quantalogic/main.py +122 -29
- quantalogic/prompts.py +1 -0
- quantalogic/search_agent.py +15 -7
- quantalogic/server/agent_server.py +2 -2
- quantalogic/tools/llm_tool.py +52 -11
- quantalogic/tools/llm_vision_tool.py +23 -7
- quantalogic/xml_parser.py +109 -49
- {quantalogic-0.2.15.dist-info → quantalogic-0.2.17.dist-info}/METADATA +21 -148
- {quantalogic-0.2.15.dist-info → quantalogic-0.2.17.dist-info}/RECORD +20 -18
- quantalogic-0.2.17.dist-info/entry_points.txt +6 -0
- quantalogic-0.2.15.dist-info/entry_points.txt +0 -3
- {quantalogic-0.2.15.dist-info → quantalogic-0.2.17.dist-info}/LICENSE +0 -0
- {quantalogic-0.2.15.dist-info → quantalogic-0.2.17.dist-info}/WHEEL +0 -0
quantalogic/tools/llm_tool.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
"""LLM Tool for generating answers to questions using a language model."""
|
2
2
|
|
3
|
-
import
|
3
|
+
from typing import Callable
|
4
4
|
|
5
|
+
from loguru import logger
|
5
6
|
from pydantic import ConfigDict, Field
|
6
7
|
|
8
|
+
from quantalogic.console_print_token import console_print_token
|
7
9
|
from quantalogic.generative_model import GenerativeModel, Message
|
8
10
|
from quantalogic.tools.tool import Tool, ToolArgument
|
9
11
|
|
@@ -53,15 +55,42 @@ class LLMTool(Tool):
|
|
53
55
|
)
|
54
56
|
|
55
57
|
model_name: str = Field(..., description="The name of the language model to use")
|
56
|
-
generative_model: GenerativeModel | None = Field(default=None)
|
57
58
|
system_prompt: str | None = Field(default=None)
|
59
|
+
on_token: Callable | None = Field(default=None, exclude=True)
|
60
|
+
generative_model: GenerativeModel | None = Field(default=None, exclude=True)
|
61
|
+
|
62
|
+
def __init__(
|
63
|
+
self,
|
64
|
+
model_name: str,
|
65
|
+
system_prompt: str | None = None,
|
66
|
+
on_token: Callable | None = None,
|
67
|
+
name: str = "llm_tool",
|
68
|
+
generative_model: GenerativeModel | None = None,
|
69
|
+
):
|
70
|
+
# Use dict to pass validated data to parent constructor
|
71
|
+
super().__init__(
|
72
|
+
**{
|
73
|
+
"model_name": model_name,
|
74
|
+
"system_prompt": system_prompt,
|
75
|
+
"on_token": on_token,
|
76
|
+
"name": name,
|
77
|
+
"generative_model": generative_model,
|
78
|
+
}
|
79
|
+
)
|
80
|
+
|
81
|
+
# Initialize the generative model
|
82
|
+
self.model_post_init(None)
|
58
83
|
|
59
84
|
def model_post_init(self, __context):
|
60
85
|
"""Initialize the generative model after model initialization."""
|
61
86
|
if self.generative_model is None:
|
62
87
|
self.generative_model = GenerativeModel(model=self.model_name)
|
63
|
-
|
88
|
+
logger.debug(f"Initialized LLMTool with model: {self.model_name}")
|
64
89
|
|
90
|
+
# Only set up event listener if on_token is provided
|
91
|
+
if self.on_token is not None:
|
92
|
+
logger.debug(f"Setting up event listener for LLMTool with model: {self.model_name}")
|
93
|
+
self.generative_model.event_emitter.on("stream_chunk", self.on_token)
|
65
94
|
|
66
95
|
def execute(
|
67
96
|
self, system_prompt: str | None = None, prompt: str | None = None, temperature: str | None = None
|
@@ -85,7 +114,7 @@ class LLMTool(Tool):
|
|
85
114
|
if not (0.0 <= temp <= 1.0):
|
86
115
|
raise ValueError("Temperature must be between 0 and 1.")
|
87
116
|
except ValueError as ve:
|
88
|
-
|
117
|
+
logger.error(f"Invalid temperature value: {temperature}")
|
89
118
|
raise ValueError(f"Invalid temperature value: {temperature}") from ve
|
90
119
|
|
91
120
|
used_system_prompt = self.system_prompt if self.system_prompt else system_prompt
|
@@ -96,20 +125,29 @@ class LLMTool(Tool):
|
|
96
125
|
Message(role="user", content=prompt),
|
97
126
|
]
|
98
127
|
|
128
|
+
is_streaming = self.on_token is not None
|
129
|
+
|
99
130
|
# Set the model's temperature
|
100
131
|
if self.generative_model:
|
101
132
|
self.generative_model.temperature = temp
|
102
133
|
|
103
134
|
# Generate the response using the generative model
|
104
135
|
try:
|
105
|
-
|
106
|
-
messages_history=messages_history, prompt=
|
136
|
+
result = self.generative_model.generate_with_history(
|
137
|
+
messages_history=messages_history, prompt=prompt, streaming=is_streaming
|
107
138
|
)
|
108
|
-
|
109
|
-
|
139
|
+
|
140
|
+
if is_streaming:
|
141
|
+
response = ""
|
142
|
+
for chunk in result:
|
143
|
+
response += chunk
|
144
|
+
else:
|
145
|
+
response = result.response
|
146
|
+
|
147
|
+
logger.debug(f"Generated response: {response}")
|
110
148
|
return response
|
111
149
|
except Exception as e:
|
112
|
-
|
150
|
+
logger.error(f"Error generating response: {e}")
|
113
151
|
raise Exception(f"Error generating response: {e}") from e
|
114
152
|
else:
|
115
153
|
raise ValueError("Generative model not initialized")
|
@@ -123,6 +161,9 @@ if __name__ == "__main__":
|
|
123
161
|
temperature = "0.7"
|
124
162
|
answer = tool.execute(system_prompt=system_prompt, prompt=question, temperature=temperature)
|
125
163
|
print(answer)
|
126
|
-
pirate = LLMTool(
|
164
|
+
pirate = LLMTool(
|
165
|
+
model_name="openrouter/openai/gpt-4o-mini", system_prompt="You are a pirate.", on_token=console_print_token
|
166
|
+
)
|
127
167
|
pirate_answer = pirate.execute(system_prompt=system_prompt, prompt=question, temperature=temperature)
|
128
|
-
print(
|
168
|
+
print("\n")
|
169
|
+
print(f"Anwser: {pirate_answer}")
|
@@ -1,8 +1,8 @@
|
|
1
1
|
"""LLM Vision Tool for analyzing images using a language model."""
|
2
2
|
|
3
|
-
import logging
|
4
3
|
from typing import Optional
|
5
4
|
|
5
|
+
from loguru import logger
|
6
6
|
from pydantic import ConfigDict, Field
|
7
7
|
|
8
8
|
from quantalogic.generative_model import GenerativeModel, Message
|
@@ -65,7 +65,12 @@ class LLMVisionTool(Tool):
|
|
65
65
|
"""Initialize the generative model after model initialization."""
|
66
66
|
if self.generative_model is None:
|
67
67
|
self.generative_model = GenerativeModel(model=self.model_name)
|
68
|
-
|
68
|
+
logger.debug(f"Initialized LLMVisionTool with model: {self.model_name}")
|
69
|
+
|
70
|
+
# Only set up event listener if on_token is provided
|
71
|
+
if self.on_token is not None:
|
72
|
+
logger.debug(f"Setting up event listener for LLMVisionTool with model: {self.model_name}")
|
73
|
+
self.generative_model.event_emitter.on("stream_chunk", self.on_token)
|
69
74
|
|
70
75
|
def execute(self, system_prompt: str, prompt: str, image_url: str, temperature: str = "0.7") -> str:
|
71
76
|
"""Execute the tool to analyze an image and generate a response.
|
@@ -88,7 +93,7 @@ class LLMVisionTool(Tool):
|
|
88
93
|
if not (0.0 <= temp <= 1.0):
|
89
94
|
raise ValueError("Temperature must be between 0 and 1.")
|
90
95
|
except ValueError as ve:
|
91
|
-
|
96
|
+
logger.error(f"Invalid temperature value: {temperature}")
|
92
97
|
raise ValueError(f"Invalid temperature value: {temperature}") from ve
|
93
98
|
|
94
99
|
if not image_url.startswith(("http://", "https://")):
|
@@ -105,14 +110,25 @@ class LLMVisionTool(Tool):
|
|
105
110
|
self.generative_model.temperature = temp
|
106
111
|
|
107
112
|
try:
|
113
|
+
is_streaming = self.on_token is not None
|
108
114
|
response_stats = self.generative_model.generate_with_history(
|
109
|
-
messages_history=messages_history,
|
115
|
+
messages_history=messages_history,
|
116
|
+
prompt=prompt,
|
117
|
+
image_url=image_url,
|
118
|
+
streaming=is_streaming
|
110
119
|
)
|
111
|
-
|
112
|
-
|
120
|
+
|
121
|
+
if is_streaming:
|
122
|
+
response = ""
|
123
|
+
for chunk in response_stats:
|
124
|
+
response += chunk
|
125
|
+
else:
|
126
|
+
response = response_stats.response.strip()
|
127
|
+
|
128
|
+
logger.info(f"Generated response: {response}")
|
113
129
|
return response
|
114
130
|
except Exception as e:
|
115
|
-
|
131
|
+
logger.error(f"Error generating response: {e}")
|
116
132
|
raise Exception(f"Error generating response: {e}") from e
|
117
133
|
|
118
134
|
|
quantalogic/xml_parser.py
CHANGED
@@ -7,6 +7,7 @@ with support for handling malformed XML and CDATA sections.
|
|
7
7
|
import html
|
8
8
|
import re
|
9
9
|
from collections import defaultdict
|
10
|
+
from functools import lru_cache
|
10
11
|
from typing import Self
|
11
12
|
|
12
13
|
from loguru import logger
|
@@ -51,15 +52,38 @@ class ToleranceXMLParser:
|
|
51
52
|
edge cases such as incomplete tags and CDATA sections.
|
52
53
|
"""
|
53
54
|
|
55
|
+
# Default mappings for element name normalization
|
56
|
+
DEFAULT_NAME_MAP = {
|
57
|
+
"o": "output",
|
58
|
+
"i": "input",
|
59
|
+
"opt": "optional"
|
60
|
+
}
|
61
|
+
|
54
62
|
def __init__(self: Self) -> None:
|
55
63
|
"""Initialize the parser with regex patterns for matching XML-like elements."""
|
56
|
-
# Pattern for matching individual XML elements
|
57
|
-
|
58
|
-
|
64
|
+
# Pattern for matching individual XML elements with better whitespace handling
|
65
|
+
self.element_pattern = re.compile(
|
66
|
+
r"<\s*([^/>]+?)\s*>(.*?)(?:</\s*\1\s*>|<\s*\1\s*>)",
|
67
|
+
re.DOTALL
|
68
|
+
)
|
59
69
|
# Pattern for matching CDATA sections
|
60
70
|
self.cdata_pattern = re.compile(r"<!\[CDATA\[(.*?)]]>", re.DOTALL)
|
61
71
|
logger.debug("Initialized ToleranceXMLParser with regex patterns")
|
62
72
|
|
73
|
+
def _validate_input(self, text: str) -> None:
|
74
|
+
"""Validate input text before processing.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
text: Input text to validate.
|
78
|
+
|
79
|
+
Raises:
|
80
|
+
ValueError: If input text is invalid.
|
81
|
+
"""
|
82
|
+
if not text or not isinstance(text, str):
|
83
|
+
raise ValueError("Input text must be a non-empty string")
|
84
|
+
if len(text.strip()) == 0:
|
85
|
+
raise ValueError("Input text cannot be whitespace only")
|
86
|
+
|
63
87
|
def _extract_and_remove_cdata(self: Self, content: str, preserve_cdata: bool = False) -> tuple[str, list[str]]:
|
64
88
|
"""Extract CDATA sections from content.
|
65
89
|
|
@@ -96,6 +120,7 @@ class ToleranceXMLParser:
|
|
96
120
|
# Only unescape HTML entities, preserve everything else exactly as is
|
97
121
|
return html.unescape(content)
|
98
122
|
|
123
|
+
@lru_cache(maxsize=128)
|
99
124
|
def _map_element_name(self: Self, name: str) -> str:
|
100
125
|
"""Map element names to their canonical form.
|
101
126
|
|
@@ -105,9 +130,82 @@ class ToleranceXMLParser:
|
|
105
130
|
Returns:
|
106
131
|
Canonical element name.
|
107
132
|
"""
|
108
|
-
|
109
|
-
|
110
|
-
|
133
|
+
return self.DEFAULT_NAME_MAP.get(name.strip(), name.strip())
|
134
|
+
|
135
|
+
def _build_element_pattern(self, element_name: str) -> re.Pattern[str]:
|
136
|
+
"""Build regex pattern for finding specific XML elements.
|
137
|
+
|
138
|
+
Args:
|
139
|
+
element_name: Name of the element to match.
|
140
|
+
|
141
|
+
Returns:
|
142
|
+
Compiled regex pattern for matching the element.
|
143
|
+
"""
|
144
|
+
non_cdata = r"(?:(?!<!\[CDATA\[|]]>).)*?"
|
145
|
+
cdata_section = r"(?:<!\[CDATA\[.*?]]>)?"
|
146
|
+
content_pattern = f"({non_cdata}{cdata_section}{non_cdata})"
|
147
|
+
closing_pattern = "(?:</\1>|<\1>)"
|
148
|
+
|
149
|
+
return re.compile(
|
150
|
+
f"<{element_name}>{content_pattern}{closing_pattern}",
|
151
|
+
re.DOTALL
|
152
|
+
)
|
153
|
+
|
154
|
+
def _find_all_elements(self, text: str) -> list[tuple[str, str]]:
|
155
|
+
"""Find all XML elements in text.
|
156
|
+
|
157
|
+
Args:
|
158
|
+
text: Input text to search.
|
159
|
+
|
160
|
+
Returns:
|
161
|
+
List of tuples containing element names and their content.
|
162
|
+
"""
|
163
|
+
return [(match.group(1), match.group(2) or "")
|
164
|
+
for match in self.element_pattern.finditer(text)]
|
165
|
+
|
166
|
+
def _process_element_content(self, content: str, preserve_cdata: bool) -> str:
|
167
|
+
"""Process content of a single element.
|
168
|
+
|
169
|
+
Args:
|
170
|
+
content: Raw element content.
|
171
|
+
preserve_cdata: Whether to preserve CDATA sections.
|
172
|
+
|
173
|
+
Returns:
|
174
|
+
Processed content string.
|
175
|
+
"""
|
176
|
+
content, cdata_sections = self._extract_and_remove_cdata(content, preserve_cdata)
|
177
|
+
content = self._clean_content(content)
|
178
|
+
|
179
|
+
# If content is empty but we have CDATA sections and we're not preserving them
|
180
|
+
if not content.strip() and cdata_sections and not preserve_cdata:
|
181
|
+
return cdata_sections[0]
|
182
|
+
return content
|
183
|
+
|
184
|
+
def _process_elements(
|
185
|
+
self,
|
186
|
+
elements: list[tuple[str, str]],
|
187
|
+
preserve_cdata: bool
|
188
|
+
) -> dict[str, str]:
|
189
|
+
"""Process found elements and handle CDATA sections.
|
190
|
+
|
191
|
+
Args:
|
192
|
+
elements: List of element name and content tuples.
|
193
|
+
preserve_cdata: Whether to preserve CDATA sections.
|
194
|
+
|
195
|
+
Returns:
|
196
|
+
Dictionary mapping element names to their processed content.
|
197
|
+
"""
|
198
|
+
result: dict[str, str] = defaultdict(str)
|
199
|
+
for name, content in elements:
|
200
|
+
name = self._map_element_name(name)
|
201
|
+
result[name] = self._process_element_content(content, preserve_cdata)
|
202
|
+
|
203
|
+
# Handle nested elements
|
204
|
+
nested_elements = self._find_all_elements(content)
|
205
|
+
nested_results = self._process_elements(nested_elements, preserve_cdata)
|
206
|
+
result.update(nested_results)
|
207
|
+
|
208
|
+
return dict(result)
|
111
209
|
|
112
210
|
def _extract_element_content(self: Self, text: str, preserve_cdata: bool = False) -> dict[str, str]:
|
113
211
|
"""Extract content from nested XML elements.
|
@@ -119,35 +217,8 @@ class ToleranceXMLParser:
|
|
119
217
|
Returns:
|
120
218
|
Dictionary mapping element names to their content values.
|
121
219
|
"""
|
122
|
-
elements
|
123
|
-
|
124
|
-
# Process each match
|
125
|
-
for match in self.element_pattern.finditer(text):
|
126
|
-
name = match.group(1)
|
127
|
-
content = match.group(2) or ""
|
128
|
-
|
129
|
-
# Map element name to canonical form
|
130
|
-
name = self._map_element_name(name)
|
131
|
-
|
132
|
-
# Extract and handle CDATA sections
|
133
|
-
content, cdata_sections = self._extract_and_remove_cdata(content, preserve_cdata)
|
134
|
-
|
135
|
-
# Clean and normalize content
|
136
|
-
content = self._clean_content(content)
|
137
|
-
|
138
|
-
# If the content is empty but we have CDATA sections and we're
|
139
|
-
# not preserving them
|
140
|
-
if not content.strip() and cdata_sections and not preserve_cdata:
|
141
|
-
content = cdata_sections[0]
|
142
|
-
|
143
|
-
# Store the element content
|
144
|
-
elements[name] = content
|
145
|
-
|
146
|
-
# Extract nested elements from the content
|
147
|
-
nested_elements = self._extract_element_content(content, preserve_cdata)
|
148
|
-
elements.update(nested_elements)
|
149
|
-
|
150
|
-
return dict(elements) # Convert defaultdict to regular dict
|
220
|
+
elements = self._find_all_elements(text)
|
221
|
+
return self._process_elements(elements, preserve_cdata)
|
151
222
|
|
152
223
|
def extract_elements(
|
153
224
|
self: Self,
|
@@ -172,9 +243,7 @@ class ToleranceXMLParser:
|
|
172
243
|
ValueError: If the input text is invalid or contains malformed XML.
|
173
244
|
"""
|
174
245
|
try:
|
175
|
-
|
176
|
-
raise ValueError("Input text must be a non-empty string")
|
177
|
-
|
246
|
+
self._validate_input(text)
|
178
247
|
logger.debug(f"Extracting elements: {element_names or 'all'}")
|
179
248
|
|
180
249
|
# Extract all elements and their content
|
@@ -206,18 +275,9 @@ class ToleranceXMLParser:
|
|
206
275
|
ValueError: If the input text is invalid or contains malformed XML.
|
207
276
|
"""
|
208
277
|
try:
|
209
|
-
|
210
|
-
raise ValueError("Input text must be a non-empty string")
|
211
|
-
|
278
|
+
self._validate_input(text)
|
212
279
|
elements: list[XMLElement] = []
|
213
|
-
pattern =
|
214
|
-
f"<{element_name}>"
|
215
|
-
r"((?:(?!<!\[CDATA\[|]]>).)*?"
|
216
|
-
r"(?:<!\[CDATA\[.*?]]>)?"
|
217
|
-
r"(?:(?!<!\[CDATA\[|]]>).)*?)"
|
218
|
-
f"(?:</\1>|<\1>)",
|
219
|
-
re.DOTALL,
|
220
|
-
)
|
280
|
+
pattern = self._build_element_pattern(element_name)
|
221
281
|
|
222
282
|
for match in pattern.finditer(text):
|
223
283
|
content = match.group(1)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: quantalogic
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.17
|
4
4
|
Summary: QuantaLogic ReAct Agents
|
5
5
|
Author: Raphaël MANSUY
|
6
6
|
Author-email: raphael.mansuy@gmail.com
|
@@ -15,11 +15,20 @@ Requires-Dist: fastapi (>=0.115.6,<0.116.0)
|
|
15
15
|
Requires-Dist: google-auth (>=2.20.0,<3.0.0)
|
16
16
|
Requires-Dist: google-search-results (>=2.4.2,<3.0.0)
|
17
17
|
Requires-Dist: litellm (>=1.56.4,<2.0.0)
|
18
|
+
Requires-Dist: llmlingua (>=0.2.2,<0.3.0)
|
18
19
|
Requires-Dist: loguru (>=0.7.3,<0.8.0)
|
19
20
|
Requires-Dist: markitdown (>=0.0.1a3,<0.0.2)
|
21
|
+
Requires-Dist: mkdocs-git-revision-date-localized-plugin (>=1.2.0,<2.0.0)
|
22
|
+
Requires-Dist: mkdocs-macros-plugin (>=1.0.4,<2.0.0)
|
23
|
+
Requires-Dist: mkdocs-material[imaging] (>=9.5.49,<10.0.0)
|
24
|
+
Requires-Dist: mkdocs-mermaid2-plugin (>=1.1.1,<2.0.0)
|
25
|
+
Requires-Dist: mkdocs-minify-plugin (>=0.7.1,<0.8.0)
|
26
|
+
Requires-Dist: mkdocstrings (>=0.24.0,<0.25.0)
|
27
|
+
Requires-Dist: mkdocstrings-python (>=1.7.0,<2.0.0)
|
20
28
|
Requires-Dist: pathspec (>=0.12.1,<0.13.0)
|
21
29
|
Requires-Dist: prompt-toolkit (>=3.0.48,<4.0.0)
|
22
30
|
Requires-Dist: pydantic (>=2.10.4,<3.0.0)
|
31
|
+
Requires-Dist: pymdown-extensions (>=10.3.1,<11.0.0)
|
23
32
|
Requires-Dist: rich (>=13.9.4,<14.0.0)
|
24
33
|
Requires-Dist: serpapi (>=0.1.5,<0.2.0)
|
25
34
|
Requires-Dist: tenacity (>=9.0.0,<10.0.0)
|
@@ -43,7 +52,7 @@ Description-Content-Type: text/markdown
|
|
43
52
|
|
44
53
|
[](https://opensource.org/licenses/Apache-2.0)
|
45
54
|
[](https://www.python.org/downloads/)
|
46
|
-
[]()
|
55
|
+
[](https://quantalogic.github.io/quantalogic/)
|
47
56
|
|
48
57
|
QuantaLogic is a ReAct (Reasoning & Action) framework for building advanced AI agents.
|
49
58
|
|
@@ -51,6 +60,8 @@ It seamlessly integrates large language models (LLMs) with a robust tool system,
|
|
51
60
|
|
52
61
|
The `cli` version include coding capabilities comparable to Aider.
|
53
62
|
|
63
|
+
[📖 Documentation](https://quantalogic.github.io/quantalogic/)
|
64
|
+
|
54
65
|
|
55
66
|
## Why QuantaLogic?
|
56
67
|
|
@@ -80,6 +91,7 @@ We created [QuantaLogic](https://www.quantalogic.app) because we saw a significa
|
|
80
91
|
- [Development](#-development)
|
81
92
|
- [Contributing](#-contributing)
|
82
93
|
- [License](#-license)
|
94
|
+
- [Documentation Development](#-documentation-development)
|
83
95
|
|
84
96
|
## 📦 Installation
|
85
97
|
|
@@ -128,12 +140,13 @@ Usage: quantalogic [OPTIONS] COMMAND [ARGS]...
|
|
128
140
|
Options:
|
129
141
|
--version Show version information.
|
130
142
|
--model-name TEXT Specify the text model to use (litellm format,
|
131
|
-
e.g. "openrouter/deepseek-chat").
|
143
|
+
e.g. "openrouter/deepseek/deepseek-chat").
|
132
144
|
--vision-model-name TEXT Specify the vision model to use (litellm format,
|
133
|
-
e.g. "openrouter/
|
145
|
+
e.g. "openrouter/openai/gpt-4o-mini").
|
134
146
|
--log [info|debug|warning] Set logging level (info/debug/warning).
|
135
147
|
--verbose Enable verbose output.
|
136
|
-
--
|
148
|
+
--max-iterations INTEGER Maximum iterations for task solving (default: 30).
|
149
|
+
--mode [code|basic|interpreter|full|code-basic|search|search-full]
|
137
150
|
Agent mode (code/search/full).
|
138
151
|
--help Show this message and exit.
|
139
152
|
|
@@ -154,6 +167,7 @@ task Execute a task with the QuantaLogic AI Assistant
|
|
154
167
|
- interpreter: Interactive code execution agent
|
155
168
|
- full: Full-featured agent with all capabilities
|
156
169
|
- code-basic: Coding agent with basic reasoning
|
170
|
+
- search: Web search agent with Wikipedia, DuckDuckGo and SERPApi integration
|
157
171
|
|
158
172
|
#### Task Execution
|
159
173
|
|
@@ -249,7 +263,7 @@ from quantalogic.tools import PythonTool, ReadFileTool
|
|
249
263
|
|
250
264
|
# Create agent with specific tools
|
251
265
|
agent = Agent(
|
252
|
-
model_name="openrouter/deepseek-chat",
|
266
|
+
model_name="openrouter/deepseek/deepseek-chat",
|
253
267
|
tools=[
|
254
268
|
PythonTool(),
|
255
269
|
ReadFileTool()
|
@@ -842,148 +856,7 @@ print(results)
|
|
842
856
|
```
|
843
857
|
```
|
844
858
|
|
845
|
-
#### Creating Custom Tools
|
846
|
-
|
847
|
-
```python
|
848
|
-
from quantalogic.tools import Tool, ToolArgument
|
849
|
-
|
850
|
-
class DatabaseTool(Tool):
|
851
|
-
name: str = "database_tool"
|
852
|
-
description: str = "Execute database operations"
|
853
|
-
need_validation: bool = True
|
854
|
-
|
855
|
-
arguments: list[ToolArgument] = [
|
856
|
-
ToolArgument(
|
857
|
-
name="query",
|
858
|
-
arg_type="string",
|
859
|
-
description="SQL query to execute",
|
860
|
-
required=True
|
861
|
-
)
|
862
|
-
]
|
863
|
-
|
864
|
-
def execute(self, query: str) -> str:
|
865
|
-
# Tool implementation
|
866
|
-
return "Query results"
|
867
859
|
```
|
868
|
-
|
869
|
-
|
870
|
-
## 🌐 Web Interface
|
871
|
-
|
872
|
-
Features:
|
873
|
-
- Real-time event visualization
|
874
|
-
- Task submission and monitoring
|
875
|
-
- Interactive validation dialogs
|
876
|
-
- Model selection
|
877
|
-
- Event filtering and search
|
878
|
-
|
879
|
-
### API Endpoints
|
880
|
-
|
881
|
-
| Endpoint | Method | Description |
|
882
|
-
| ------------------ | ------ | --------------- |
|
883
|
-
| `/tasks` | POST | Submit tasks |
|
884
|
-
| `/tasks/{task_id}` | GET | Task status |
|
885
|
-
| `/events` | GET | SSE endpoint |
|
886
|
-
| `/validate` | POST | Task validation |
|
887
|
-
|
888
|
-
|
889
|
-
## 📖 Examples
|
890
|
-
|
891
|
-
### Python Tool Integration Example
|
892
|
-
|
893
|
-
```python
|
894
|
-
import os
|
895
|
-
|
896
|
-
from quantalogic import Agent, console_print_events
|
897
|
-
from quantalogic.tools import (
|
898
|
-
PythonTool,
|
899
|
-
)
|
900
|
-
|
901
|
-
# Verify API key is set - required for authentication with DeepSeek's API
|
902
|
-
# This check ensures the agent won't fail during runtime due to missing credentials
|
903
|
-
if not os.environ.get("DEEPSEEK_API_KEY"):
|
904
|
-
raise ValueError("DEEPSEEK_API_KEY environment variable is not set")
|
905
|
-
|
906
|
-
# Initialize agent with DeepSeek model and Python tool
|
907
|
-
agent = Agent(model_name="deepseek/deepseek-chat", tools=[PythonTool()])
|
908
|
-
|
909
|
-
# Configure comprehensive event monitoring system
|
910
|
-
# Tracks all agent activities including:
|
911
|
-
# - Code execution steps
|
912
|
-
# - Tool interactions
|
913
|
-
# - Error conditions
|
914
|
-
# Essential for debugging and performance optimization
|
915
|
-
agent.event_emitter.on(
|
916
|
-
"*",
|
917
|
-
console_print_events,
|
918
|
-
)
|
919
|
-
|
920
|
-
# Execute a precision mathematics task demonstrating:
|
921
|
-
# - High-precision calculations
|
922
|
-
# - PythonTool integration
|
923
|
-
# - Real-time monitoring capabilities
|
924
|
-
result = agent.solve_task("1. Calculate PI with 10000 decimal places.")
|
925
|
-
print(result)
|
926
|
-
```
|
927
|
-
|
928
|
-
### Agent with Event Monitoring
|
929
|
-
|
930
|
-
```python
|
931
|
-
import os
|
932
|
-
|
933
|
-
from quantalogic import Agent, console_print_events
|
934
|
-
from quantalogic.tools import (
|
935
|
-
LLMTool,
|
936
|
-
)
|
937
|
-
|
938
|
-
# Verify API key is set - required for authentication with DeepSeek's API
|
939
|
-
# This check ensures the agent won't fail during runtime due to missing credentials
|
940
|
-
if not os.environ.get("DEEPSEEK_API_KEY"):
|
941
|
-
raise ValueError("DEEPSEEK_API_KEY environment variable is not set")
|
942
|
-
|
943
|
-
# Initialize agent with DeepSeek model and LLM tool
|
944
|
-
# The LLM tool serves dual purpose:
|
945
|
-
# 1. As a reasoning engine for the agent's cognitive processes
|
946
|
-
# 2. As a latent space explorer, enabling the agent to:
|
947
|
-
# - Discover novel solution paths
|
948
|
-
# - Generate creative combinations of concepts
|
949
|
-
# - Explore alternative reasoning strategies
|
950
|
-
# Using the same model ensures consistent behavior across both roles
|
951
|
-
agent = Agent(model_name="deepseek/deepseek-chat", tools=[LLMTool(model_name="deepseek/deepseek-chat")])
|
952
|
-
|
953
|
-
# Set up event monitoring to track agent's lifecycle
|
954
|
-
# This helps in debugging and understanding the agent's behavior
|
955
|
-
agent.event_emitter.on(
|
956
|
-
[
|
957
|
-
"task_complete",
|
958
|
-
"task_think_start",
|
959
|
-
"task_think_end",
|
960
|
-
"tool_execution_start",
|
961
|
-
"tool_execution_end",
|
962
|
-
"error_max_iterations_reached",
|
963
|
-
"memory_full",
|
964
|
-
"memory_compacted",
|
965
|
-
"memory_summary",
|
966
|
-
],
|
967
|
-
console_print_events,
|
968
|
-
)
|
969
|
-
|
970
|
-
# Execute a multi-step task showcasing agent's capabilities
|
971
|
-
# Demonstrates:
|
972
|
-
# 1. Creative content generation
|
973
|
-
# 2. Language translation
|
974
|
-
# 3. Style adaptation
|
975
|
-
# 4. Multi-step reasoning and execution
|
976
|
-
result = agent.solve_task(
|
977
|
-
"1. Write a poem in English about a dog. "
|
978
|
-
"2. Translate the poem into French. "
|
979
|
-
"3. Choose 2 French authors"
|
980
|
-
"4. Rewrite the translated poem with the style of the chosen authors. "
|
981
|
-
)
|
982
|
-
print(result)
|
983
|
-
```
|
984
|
-
|
985
|
-
|
986
|
-
|
987
860
|
### Project Documentation
|
988
861
|
|
989
862
|
```python
|
@@ -991,7 +864,7 @@ from quantalogic import Agent
|
|
991
864
|
from quantalogic.tools import MarkitdownTool, ReadFileTool
|
992
865
|
|
993
866
|
agent = Agent(
|
994
|
-
model_name="openrouter/deepseek-chat",
|
867
|
+
model_name="openrouter/deepseek/deepseek-chat",
|
995
868
|
tools=[MarkitdownTool(), ReadFileTool()]
|
996
869
|
)
|
997
870
|
|