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.
@@ -1,9 +1,11 @@
1
1
  """LLM Tool for generating answers to questions using a language model."""
2
2
 
3
- import logging
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
- logging.debug(f"Initialized LLMTool with model: {self.model_name}")
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
- logging.error(f"Invalid temperature value: {temperature}")
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
- response_stats = self.generative_model.generate_with_history(
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
- response = response_stats.response.strip()
109
- logging.info(f"Generated response: {response}")
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
- logging.error(f"Error generating response: {e}")
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(model_name="openrouter/openai/gpt-4o-mini", system_prompt="You are a pirate.")
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(pirate_answer)
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
- logging.debug(f"Initialized LLMVisionTool with model: {self.model_name}")
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
- logging.error(f"Invalid temperature value: {temperature}")
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, prompt=prompt, image_url=image_url
115
+ messages_history=messages_history,
116
+ prompt=prompt,
117
+ image_url=image_url,
118
+ streaming=is_streaming
110
119
  )
111
- response = response_stats.response.strip()
112
- logging.info(f"Generated response: {response}")
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
- logging.error(f"Error generating response: {e}")
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, including malformed tags
57
- # Modified to be more lenient with content and preserve exact formatting
58
- self.element_pattern = re.compile(r"<([^/>]+?)>(.*?)(?:</\1>|<\1>)", re.DOTALL)
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
- # Map common element name variations
109
- name_map = {"o": "output", "i": "input", "opt": "optional"}
110
- return name_map.get(name.strip(), name.strip())
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: dict[str, str] = defaultdict(str)
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
- if not text or not isinstance(text, str):
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
- if not text or not isinstance(text, str):
210
- raise ValueError("Input text must be a non-empty string")
211
-
278
+ self._validate_input(text)
212
279
  elements: list[XMLElement] = []
213
- pattern = re.compile(
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.15
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
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
45
54
  [![Python](https://img.shields.io/badge/Python-3.12+-blue.svg)](https://www.python.org/downloads/)
46
- [![Documentation](https://img.shields.io/badge/docs-latest-brightgreen.svg)]()
55
+ [![Documentation](https://img.shields.io/badge/docs-latest-brightgreen.svg)](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/A/gpt-4o-mini").
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
- --mode [code|basic|interpreter|full|code-basic|search]
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