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 CHANGED
@@ -11,10 +11,11 @@ warnings.filterwarnings(
11
11
 
12
12
 
13
13
  from .agent import Agent # noqa: E402
14
+ from .console_print_events import console_print_events # noqa: E402
15
+ from .console_print_token import console_print_token # noqa: E402
14
16
  from .event_emitter import EventEmitter # noqa: E402
15
17
  from .memory import AgentMemory, VariableMemory # noqa: E402
16
- from .print_event import console_print_events # noqa: E402
17
18
 
18
19
  """QuantaLogic package for AI-powered generative models."""
19
20
 
20
- __all__ = ["Agent", "EventEmitter", "AgentMemory", "VariableMemory", "console_print_events"]
21
+ __all__ = ["Agent", "EventEmitter", "AgentMemory", "VariableMemory", "console_print_events","console_print_token"]
quantalogic/agent.py CHANGED
@@ -8,7 +8,7 @@ from loguru import logger
8
8
  from pydantic import BaseModel, ConfigDict
9
9
 
10
10
  from quantalogic.event_emitter import EventEmitter
11
- from quantalogic.generative_model import GenerativeModel
11
+ from quantalogic.generative_model import GenerativeModel, ResponseStats, TokenUsage
12
12
  from quantalogic.memory import AgentMemory, Message, VariableMemory
13
13
  from quantalogic.prompts import system_prompt
14
14
  from quantalogic.tool_manager import ToolManager
@@ -85,6 +85,9 @@ class Agent(BaseModel):
85
85
  """Initialize the agent with model, memory, tools, and configurations."""
86
86
  try:
87
87
  logger.debug("Initializing agent...")
88
+ # Create event emitter first
89
+ event_emitter = EventEmitter()
90
+
88
91
  # Add TaskCompleteTool to the tools list if not already present
89
92
  if TaskCompleteTool() not in tools:
90
93
  tools.append(TaskCompleteTool())
@@ -108,7 +111,7 @@ class Agent(BaseModel):
108
111
 
109
112
  logger.debug("Base class init started ...")
110
113
  super().__init__(
111
- model=GenerativeModel(model=model_name),
114
+ model=GenerativeModel(model=model_name, event_emitter=event_emitter),
112
115
  memory=memory,
113
116
  variable_store=VariableMemory(),
114
117
  tools=tool_manager,
@@ -116,19 +119,21 @@ class Agent(BaseModel):
116
119
  ask_for_user_validation=ask_for_user_validation,
117
120
  task_to_solve=task_to_solve,
118
121
  specific_expertise=specific_expertise,
122
+ event_emitter=event_emitter,
119
123
  )
120
124
  logger.debug("Agent initialized successfully.")
121
125
  except Exception as e:
122
126
  logger.error(f"Failed to initialize agent: {str(e)}")
123
127
  raise
124
128
 
125
- def solve_task(self, task: str, max_iterations: int = 30) -> str:
129
+ def solve_task(self, task: str, max_iterations: int = 30, streaming: bool = False) -> str:
126
130
  """Solve the given task using the ReAct framework.
127
131
 
128
132
  Args:
129
133
  task (str): The task description.
130
134
  max_iterations (int, optional): Maximum number of iterations to attempt solving the task.
131
135
  Defaults to 30 to prevent infinite loops and ensure timely task completion.
136
+ streaming (bool, optional): Whether to use streaming mode for generating responses.
132
137
 
133
138
  Returns:
134
139
  str: The final response after task completion.
@@ -172,11 +177,34 @@ class Agent(BaseModel):
172
177
 
173
178
  self._compact_memory_if_needed(current_prompt)
174
179
 
175
- result = self.model.generate_with_history(messages_history=self.memory.memory, prompt=current_prompt)
180
+ if streaming:
181
+ # For streaming, collect the response chunks
182
+ content = ""
183
+ for chunk in self.model.generate_with_history(
184
+ messages_history=self.memory.memory, prompt=current_prompt, streaming=True
185
+ ):
186
+ content += chunk
187
+
188
+ # Create a response object similar to non-streaming mode
189
+ result = ResponseStats(
190
+ response=content,
191
+ usage=TokenUsage(
192
+ prompt_tokens=0, # We don't have token counts in streaming mode
193
+ completion_tokens=0,
194
+ total_tokens=0,
195
+ ),
196
+ model=self.model.model,
197
+ finish_reason="stop",
198
+ )
199
+ else:
200
+ result = self.model.generate_with_history(
201
+ messages_history=self.memory.memory, prompt=current_prompt, streaming=False
202
+ )
176
203
 
177
204
  content = result.response
178
- token_usage = result.usage
179
- self.total_tokens = token_usage.total_tokens
205
+ if not streaming: # Only update tokens for non-streaming mode
206
+ token_usage = result.usage
207
+ self.total_tokens = token_usage.total_tokens
180
208
 
181
209
  # Emit event: Task Think End
182
210
  self._emit_event(
@@ -187,7 +215,7 @@ class Agent(BaseModel):
187
215
  )
188
216
 
189
217
  # Process the assistant's response
190
- result = self._observe_response(result.response, iteration=self.current_iteration)
218
+ result = self._observe_response(content, iteration=self.current_iteration)
191
219
 
192
220
  current_prompt = result.next_prompt
193
221
 
@@ -292,9 +320,10 @@ class Agent(BaseModel):
292
320
  is_repeated_call = self._is_repeated_tool_call(tool_name, arguments_with_values)
293
321
 
294
322
  if is_repeated_call:
295
- return self._handle_repeated_tool_call(tool_name, arguments_with_values)
323
+ executed_tool, response = self._handle_repeated_tool_call(tool_name, arguments_with_values)
324
+ else:
325
+ executed_tool, response = self._execute_tool(tool_name, tool, arguments_with_values)
296
326
 
297
- executed_tool, response = self._execute_tool(tool_name, tool, arguments_with_values)
298
327
  if not executed_tool:
299
328
  return self._handle_tool_execution_failure(response)
300
329
 
@@ -406,7 +435,7 @@ class Agent(BaseModel):
406
435
 
407
436
  formatted_response = (
408
437
  "\n"
409
- f"--- Observations for iteration {iteration} ---\n"
438
+ f"--- Observations for iteration {iteration} / max {self.max_iterations} ---\n"
410
439
  "\n"
411
440
  f"\n --- Tool execution result stored in variable ${variable_name}$ --- \n"
412
441
  "\n"
@@ -427,34 +456,26 @@ class Agent(BaseModel):
427
456
 
428
457
  # Format the response message
429
458
  formatted_response = (
430
- "\n"
431
- f"--- Observations for iteration {iteration} ---\n"
432
- "\n"
433
- f"\n --- Tool execution result stored in variable ${variable_name}$ --- \n"
434
- "\n"
435
- f"<{variable_name}>\n{response_display}\n</{variable_name}>\n" + "\n"
436
- "\n"
437
- f"--- Tools --- \n"
438
- "\n"
439
- f"{self._get_tools_names_prompt()}"
440
- "\n"
441
- f"--- Variables --- \n"
442
- "\n"
443
- f"{self._get_variable_prompt()}"
444
- "\n"
445
- "You must analyze this answer and evaluate what to do next to solve the task.\n"
446
- "If the step failed, take a step back and rethink your approach.\n"
447
- "\n"
448
- "--- Task to solve summary ---\n"
449
- "\n"
450
- f"{self.task_to_solve_summary}"
451
- "\n"
459
+ f"\n--- Observations for iteration {iteration} / max {self.max_iterations} ---\n"
460
+ f"\n--- Tool execution result in ${variable_name}$ ---\n"
461
+ f"<{variable_name}>\n{response_display}\n</{variable_name}>\n\n"
462
+ f"--- Tools ---\n{self._get_tools_names_prompt()}\n"
463
+ f"--- Variables ---\n{self._get_variable_prompt()}\n"
464
+ "Analyze this response to determine the next steps. If the step failed, reconsider your approach.\n"
465
+ f"--- Task to solve summary ---\n{self.task_to_solve_summary}\n"
452
466
  "--- Format ---\n"
453
- "\n"
454
- "You MUST respond with exactly two XML blocks formatted in markdown:\n"
455
- "\n"
456
- " - One <thinking> block detailing your analysis,\n"
457
- " - One <tool_name> block specifying the chosen tool and its arguments, as outlined in the system prompt.\n"
467
+ "Respond only with two XML blocks in markdown as specified in system prompt.\n"
468
+ "No extra comments must be added.\n"
469
+ "```xml\n"
470
+ "<thinking>\n"
471
+ "...\n"
472
+ "</thinking>\n"
473
+ "```\n"
474
+ "```xml\n"
475
+ "< ...tool_name... >\n"
476
+ "...\n"
477
+ "</ ...tool_name... >\n"
478
+ "```"
458
479
  )
459
480
 
460
481
  return formatted_response
@@ -5,9 +5,11 @@
5
5
  # Local application imports
6
6
  from quantalogic.agent import Agent
7
7
  from quantalogic.coding_agent import create_coding_agent
8
+ from quantalogic.console_print_token import console_print_token
8
9
  from quantalogic.tools import (
9
10
  AgentTool,
10
11
  DownloadHttpFileTool,
12
+ DuckDuckGoSearchTool,
11
13
  EditWholeContentTool,
12
14
  ExecuteBashCommandTool,
13
15
  InputQuestionTool,
@@ -23,20 +25,20 @@ from quantalogic.tools import (
23
25
  RipgrepTool,
24
26
  SearchDefinitionNames,
25
27
  TaskCompleteTool,
26
- WriteFileTool,
27
- DuckDuckGoSearchTool,
28
28
  WikipediaSearchTool,
29
+ WriteFileTool,
29
30
  )
30
31
 
31
32
  MODEL_NAME = "deepseek/deepseek-chat"
32
33
 
33
34
 
34
- def create_agent(model_name: str, vision_model_name: str | None) -> Agent:
35
+ def create_agent(model_name: str, vision_model_name: str | None, no_stream: bool = False) -> Agent:
35
36
  """Create an agent with the specified model and tools.
36
37
 
37
38
  Args:
38
39
  model_name (str): Name of the model to use
39
40
  vision_model_name (str | None): Name of the vision model to use
41
+ no_stream (bool, optional): If True, the agent will not stream results.
40
42
 
41
43
  Returns:
42
44
  Agent: An agent with the specified model and tools
@@ -54,12 +56,12 @@ def create_agent(model_name: str, vision_model_name: str | None) -> Agent:
54
56
  RipgrepTool(),
55
57
  SearchDefinitionNames(),
56
58
  MarkitdownTool(),
57
- LLMTool(model_name=model_name),
59
+ LLMTool(model_name=model_name, on_token=console_print_token if not no_stream else None),
58
60
  DownloadHttpFileTool(),
59
61
  ]
60
62
 
61
63
  if vision_model_name:
62
- tools.append(LLMVisionTool(model_name=vision_model_name))
64
+ tools.append(LLMVisionTool(model_name=vision_model_name, on_token=console_print_token if not no_stream else None))
63
65
 
64
66
  return Agent(
65
67
  model_name=model_name,
@@ -67,12 +69,13 @@ def create_agent(model_name: str, vision_model_name: str | None) -> Agent:
67
69
  )
68
70
 
69
71
 
70
- def create_interpreter_agent(model_name: str, vision_model_name: str | None) -> Agent:
72
+ def create_interpreter_agent(model_name: str, vision_model_name: str | None, no_stream: bool = False) -> Agent:
71
73
  """Create an interpreter agent with the specified model and tools.
72
74
 
73
75
  Args:
74
76
  model_name (str): Name of the model to use
75
77
  vision_model_name (str | None): Name of the vision model to use
78
+ no_stream (bool, optional): If True, the agent will not stream results.
76
79
 
77
80
  Returns:
78
81
  Agent: An interpreter agent with the specified model and tools
@@ -92,18 +95,19 @@ def create_interpreter_agent(model_name: str, vision_model_name: str | None) ->
92
95
  NodeJsTool(),
93
96
  SearchDefinitionNames(),
94
97
  MarkitdownTool(),
95
- LLMTool(model_name=model_name),
98
+ LLMTool(model_name=model_name, on_token=console_print_token if not no_stream else None),
96
99
  DownloadHttpFileTool(),
97
100
  ]
98
101
  return Agent(model_name=model_name, tools=tools)
99
102
 
100
103
 
101
- def create_full_agent(model_name: str, vision_model_name: str | None) -> Agent:
104
+ def create_full_agent(model_name: str, vision_model_name: str | None, no_stream: bool = False) -> Agent:
102
105
  """Create an agent with the specified model and many tools.
103
106
 
104
107
  Args:
105
108
  model_name (str): Name of the model to use
106
109
  vision_model_name (str | None): Name of the vision model to use
110
+ no_stream (bool, optional): If True, the agent will not stream results.
107
111
 
108
112
  Returns:
109
113
  Agent: An agent with the specified model and tools
@@ -124,14 +128,14 @@ def create_full_agent(model_name: str, vision_model_name: str | None) -> Agent:
124
128
  NodeJsTool(),
125
129
  SearchDefinitionNames(),
126
130
  MarkitdownTool(),
127
- LLMTool(model_name=model_name),
131
+ LLMTool(model_name=model_name, on_token=console_print_token if not no_stream else None),
128
132
  DownloadHttpFileTool(),
129
133
  WikipediaSearchTool(),
130
134
  DuckDuckGoSearchTool(),
131
135
  ]
132
136
 
133
137
  if vision_model_name:
134
- tools.append(LLMVisionTool(model_name=vision_model_name))
138
+ tools.append(LLMVisionTool(model_name=vision_model_name,on_token=console_print_token if not no_stream else None))
135
139
 
136
140
  return Agent(
137
141
  model_name=model_name,
@@ -139,12 +143,13 @@ def create_full_agent(model_name: str, vision_model_name: str | None) -> Agent:
139
143
  )
140
144
 
141
145
 
142
- def create_orchestrator_agent(model_name: str, vision_model_name: str | None = None) -> Agent:
146
+ def create_orchestrator_agent(model_name: str, vision_model_name: str | None = None, no_stream: bool = False) -> Agent:
143
147
  """Create an agent with the specified model and tools.
144
148
 
145
149
  Args:
146
150
  model_name (str): Name of the model to use
147
151
  vision_model_name (str | None): Name of the vision model to use
152
+ no_stream (bool, optional): If True, the agent will not stream results.
148
153
 
149
154
  Returns:
150
155
  Agent: An agent with the specified model and tools
@@ -160,12 +165,12 @@ def create_orchestrator_agent(model_name: str, vision_model_name: str | None = N
160
165
  ReadFileBlockTool(),
161
166
  RipgrepTool(),
162
167
  SearchDefinitionNames(),
163
- LLMTool(model_name=MODEL_NAME),
168
+ LLMTool(model_name=model_name, on_token=console_print_token if not no_stream else None),
164
169
  AgentTool(agent=coding_agent_instance, agent_role="software expert", name="coder_agent_tool"),
165
170
  ]
166
171
 
167
172
  if vision_model_name:
168
- tools.append(LLMVisionTool(model_name=vision_model_name))
173
+ tools.append(LLMVisionTool(model_name=vision_model_name, on_token=console_print_token if not no_stream else None))
169
174
 
170
175
  return Agent(
171
176
  model_name=model_name,
@@ -1,5 +1,7 @@
1
1
  from quantalogic.agent import Agent
2
+ from quantalogic.console_print_token import console_print_token
2
3
  from quantalogic.tools import (
4
+ DuckDuckGoSearchTool,
3
5
  EditWholeContentTool,
4
6
  ExecuteBashCommandTool,
5
7
  InputQuestionTool,
@@ -18,13 +20,14 @@ from quantalogic.utils import get_coding_environment
18
20
  from quantalogic.utils.get_quantalogic_rules_content import get_quantalogic_rules_file_content
19
21
 
20
22
 
21
- def create_coding_agent(model_name: str, vision_model_name: str | None = None, basic: bool = False) -> Agent:
23
+ def create_coding_agent(model_name: str, vision_model_name: str | None = None, basic: bool = False,no_stream: bool = False) -> Agent:
22
24
  """Creates and configures a coding agent with a comprehensive set of tools.
23
25
 
24
26
  Args:
25
27
  model_name (str): Name of the language model to use for the agent's core capabilities
26
28
  vision_model_name (str | None): Name of the vision model to use for the agent's core capabilities
27
29
  basic (bool, optional): If True, the agent will be configured with a basic set of tools.
30
+ no_stream (bool, optional): If True, the agent will not stream results.
28
31
 
29
32
  Returns:
30
33
  Agent: A fully configured coding agent instance with:
@@ -59,17 +62,19 @@ def create_coding_agent(model_name: str, vision_model_name: str | None = None, b
59
62
  ReadFileTool(),
60
63
  ExecuteBashCommandTool(),
61
64
  InputQuestionTool(),
65
+ DuckDuckGoSearchTool(),
62
66
  ]
63
67
 
64
68
  if vision_model_name:
65
- tools.append(LLMVisionTool(model_name=vision_model_name))
69
+ tools.append(LLMVisionTool(model_name=vision_model_name, on_token=console_print_token if not no_stream else None))
66
70
 
67
71
  if not basic:
68
72
  tools.append(
69
73
  LLMTool(
70
74
  model_name=model_name,
71
75
  system_prompt="You are a software expert, your role is to answer coding questions.",
72
- name="coding_consultant", # Handles implementation-level coding questions
76
+ name="coding_consultant", # Handles implementation-level coding questions
77
+ on_token=console_print_token if not no_stream else None,
73
78
  )
74
79
  )
75
80
  tools.append(
@@ -77,6 +82,7 @@ def create_coding_agent(model_name: str, vision_model_name: str | None = None, b
77
82
  model_name=model_name,
78
83
  system_prompt="You are a software architect, your role is to answer software architecture questions.",
79
84
  name="software_architect", # Handles system design and architecture questions
85
+ on_token=console_print_token if not no_stream else None,
80
86
  )
81
87
  )
82
88
 
@@ -1,5 +1,3 @@
1
- """Print events with rich formatting."""
2
-
3
1
  from typing import Any
4
2
 
5
3
  from rich import box
@@ -65,4 +63,4 @@ def console_print_events(event: str, data: dict[str, Any] | None = None):
65
63
  expand=True,
66
64
  )
67
65
 
68
- console.print(panel)
66
+ console.print(panel)
@@ -0,0 +1,16 @@
1
+ """Print events with rich formatting."""
2
+
3
+ from typing import Any
4
+
5
+ from rich.console import Console
6
+
7
+
8
+ def console_print_token(event: str, data: Any | None = None):
9
+ """Print a token with rich formatting.
10
+
11
+ Args:
12
+ event (str): The event name (e.g., 'stream_chunk')
13
+ data (Any | None): The token data to print
14
+ """
15
+ console = Console()
16
+ console.print(data, end="")
@@ -0,0 +1,50 @@
1
+ import subprocess
2
+ import os
3
+ import sys
4
+
5
+ def get_config_path():
6
+ """Get the absolute path to the mkdocs configuration file."""
7
+ return os.path.join(os.path.dirname(os.path.dirname(__file__)), 'mkdocs', 'mkdocs.yml')
8
+
9
+ def serve_docs():
10
+ """Serve MkDocs documentation locally."""
11
+ config_path = get_config_path()
12
+ try:
13
+ subprocess.run(['mkdocs', 'serve', '--config-file', config_path], check=True)
14
+ except subprocess.CalledProcessError as e:
15
+ print(f"Error serving documentation: {e}")
16
+ sys.exit(1)
17
+
18
+ def build_docs():
19
+ """Build MkDocs documentation."""
20
+ config_path = get_config_path()
21
+ try:
22
+ subprocess.run(['mkdocs', 'build', '--config-file', config_path], check=True)
23
+ print("Documentation built successfully.")
24
+ except subprocess.CalledProcessError as e:
25
+ print(f"Error building documentation: {e}")
26
+ sys.exit(1)
27
+
28
+ def deploy_docs():
29
+ """Deploy MkDocs documentation to GitHub Pages."""
30
+ config_path = get_config_path()
31
+ try:
32
+ subprocess.run(['mkdocs', 'gh-deploy', '--config-file', config_path], check=True)
33
+ print("Documentation deployed successfully.")
34
+ except subprocess.CalledProcessError as e:
35
+ print(f"Error deploying documentation: {e}")
36
+ sys.exit(1)
37
+
38
+ # Ensure the script can be run directly for testing
39
+ if __name__ == '__main__':
40
+ command = sys.argv[1] if len(sys.argv) > 1 else None
41
+
42
+ if command == 'serve':
43
+ serve_docs()
44
+ elif command == 'build':
45
+ build_docs()
46
+ elif command == 'deploy':
47
+ deploy_docs()
48
+ else:
49
+ print("Usage: python docs_cli.py [serve|build|deploy]")
50
+ sys.exit(1)