quantalogic 0.58.0__py3-none-any.whl → 0.59.0__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.
@@ -4,6 +4,12 @@ from loguru import logger
4
4
 
5
5
  from quantalogic.agent import Agent
6
6
  from quantalogic.agent_config import (
7
+ DuckDuckGoSearchTool,
8
+ NodeJsTool,
9
+ PythonTool,
10
+ SearchDefinitionNames,
11
+ TaskCompleteTool,
12
+ WikipediaSearchTool,
7
13
  create_basic_agent,
8
14
  create_full_agent,
9
15
  create_interpreter_agent,
@@ -26,38 +32,19 @@ class AgentRegistry:
26
32
 
27
33
  @classmethod
28
34
  def register_agent(cls, name: str, agent: Agent) -> None:
29
- """Register an agent instance with a name.
30
-
31
- Args:
32
- name: Unique name for the agent
33
- agent: Agent instance to register
34
- """
35
+ """Register an agent instance with a name."""
35
36
  if name in cls._agents:
36
37
  raise ValueError(f"Agent with name {name} already exists")
37
38
  cls._agents[name] = agent
38
39
 
39
40
  @classmethod
40
41
  def get_agent(cls, name: str) -> Agent:
41
- """Retrieve a registered agent by name.
42
-
43
- Args:
44
- name: Name of the agent to retrieve
45
-
46
- Returns:
47
- Registered Agent instance
48
-
49
- Raises:
50
- KeyError: If no agent with that name exists
51
- """
42
+ """Retrieve a registered agent by name."""
52
43
  return cls._agents[name]
53
44
 
54
45
  @classmethod
55
46
  def list_agents(cls) -> Dict[str, str]:
56
- """List all registered agents.
57
-
58
- Returns:
59
- Dictionary mapping agent names to their types
60
- """
47
+ """List all registered agents."""
61
48
  return {name: type(agent).__name__ for name, agent in cls._agents.items()}
62
49
 
63
50
 
@@ -75,7 +62,9 @@ def create_agent_for_mode(
75
62
  tools: Optional[List[Any]] = None,
76
63
  event_emitter: Any = None,
77
64
  specific_expertise: str = "",
78
- memory: AgentMemory | None = None
65
+ memory: AgentMemory | None = None,
66
+ chat_system_prompt: Optional[str] = None,
67
+ tool_mode: Optional[str] = None,
79
68
  ) -> Agent:
80
69
  """Create an agent based on the specified mode.
81
70
 
@@ -91,6 +80,8 @@ def create_agent_for_mode(
91
80
  event_emitter: Optional event emitter to use in the agent
92
81
  specific_expertise: Optional specific expertise for the agent
93
82
  memory: Optional AgentMemory instance to use in the agent
83
+ chat_system_prompt: Optional persona for chat mode
84
+ tool_mode: Optional tool or toolset to prioritize in chat mode
94
85
 
95
86
  Returns:
96
87
  Agent: The created agent instance
@@ -103,8 +94,33 @@ def create_agent_for_mode(
103
94
  logger.debug(f"Using no_stream: {no_stream}")
104
95
  logger.debug(f"Using compact_every_n_iteration: {compact_every_n_iteration}")
105
96
  logger.debug(f"Using max_tokens_working_memory: {max_tokens_working_memory}")
106
-
107
- if mode == "code":
97
+ logger.debug(f"Using tool_mode: {tool_mode}")
98
+
99
+ # Default tools if none provided
100
+ if tools is None:
101
+ tools = [TaskCompleteTool()]
102
+
103
+ if mode == "chat":
104
+ logger.debug(f"Creating chat agent with persona: {chat_system_prompt}")
105
+ # Customize tools based on tool_mode
106
+ if tool_mode:
107
+ if tool_mode == "search":
108
+ tools.extend([DuckDuckGoSearchTool(), WikipediaSearchTool()])
109
+ elif tool_mode == "code":
110
+ tools.extend([PythonTool(), NodeJsTool(), SearchDefinitionNames()])
111
+ elif tool_mode in [t.name for t in tools]: # Specific tool name
112
+ tools = [t for t in tools if t.name == tool_mode or isinstance(t, TaskCompleteTool)]
113
+ else:
114
+ logger.warning(f"Unknown tool mode '{tool_mode}', using default tools")
115
+ agent = Agent(
116
+ model_name=model_name,
117
+ memory=memory if memory else AgentMemory(),
118
+ tools=tools,
119
+ chat_system_prompt=chat_system_prompt,
120
+ tool_mode=tool_mode,
121
+ )
122
+ return agent
123
+ elif mode == "code":
108
124
  logger.debug("Creating code agent without basic mode")
109
125
  agent = create_coding_agent(
110
126
  model_name,
@@ -116,7 +132,7 @@ def create_agent_for_mode(
116
132
  max_tokens_working_memory=max_tokens_working_memory,
117
133
  )
118
134
  return agent
119
- if mode == "code-basic":
135
+ elif mode == "code-basic":
120
136
  agent = create_coding_agent(
121
137
  model_name,
122
138
  vision_model_name,
@@ -161,7 +177,7 @@ def create_agent_for_mode(
161
177
  max_tokens_working_memory=max_tokens_working_memory,
162
178
  )
163
179
  return agent
164
- if mode == "search-full":
180
+ elif mode == "search-full":
165
181
  agent = create_search_agent(
166
182
  model_name,
167
183
  mode_full=True,
@@ -170,17 +186,5 @@ def create_agent_for_mode(
170
186
  max_tokens_working_memory=max_tokens_working_memory,
171
187
  )
172
188
  return agent
173
- # if mode == "custom":
174
- # agent = create_custom_agent(
175
- # model_name,
176
- # vision_model_name,
177
- # no_stream=no_stream,
178
- # compact_every_n_iteration=compact_every_n_iteration,
179
- # max_tokens_working_memory=max_tokens_working_memory,
180
- # specific_expertise=specific_expertise,
181
- # tools=tools,
182
- # memory=memory
183
- # )
184
- # return agent
185
189
  else:
186
- raise ValueError(f"Unknown agent mode: {mode}")
190
+ raise ValueError(f"Unknown agent mode: {mode}")
quantalogic/config.py CHANGED
@@ -1,18 +1,19 @@
1
- from dataclasses import dataclass
2
1
  from typing import Optional
3
2
 
3
+ from pydantic import BaseModel
4
4
 
5
- @dataclass
6
- class QLConfig:
7
- """Central configuration for QuantaLogic agent parameters."""
8
5
 
6
+ class QLConfig(BaseModel):
9
7
  model_name: str
10
8
  verbose: bool
11
9
  mode: str
12
10
  log: str
13
- vision_model_name: Optional[str]
11
+ vision_model_name: Optional[str] = None
14
12
  max_iterations: int
15
- compact_every_n_iteration: Optional[int]
16
- max_tokens_working_memory: Optional[int]
13
+ compact_every_n_iteration: Optional[int] = None
14
+ max_tokens_working_memory: Optional[int] = None
17
15
  no_stream: bool
18
16
  thinking_model_name: str
17
+ chat_system_prompt: Optional[str] = None
18
+ tool_mode: Optional[str] = None # Added field for tool mode
19
+ auto_tool_call: bool = True # Default to True for automatic tool execution
@@ -329,9 +329,9 @@ class EventEmitter:
329
329
  """
330
330
  with self._lock:
331
331
  return {
332
- "wildcard_listeners": [(l.__name__, p, m) for l, p, m in self._wildcard_listeners],
332
+ "wildcard_listeners": [(listener.__name__, p, m) for listener, p, m in self._wildcard_listeners],
333
333
  "event_listeners": {
334
- evt: [(l.__name__, p, m) for l, p, m in listeners] for evt, listeners in self._listeners.items()
334
+ evt: [(listener.__name__, p, m) for listener, p, m in listeners] for evt, listeners in self._listeners.items()
335
335
  },
336
336
  }
337
337
 
quantalogic/main.py CHANGED
@@ -16,7 +16,6 @@ from quantalogic.version import get_version
16
16
  # Load environment variables from .env file
17
17
  load_dotenv()
18
18
 
19
-
20
19
  # Configure logger
21
20
  logger.remove()
22
21
 
@@ -45,7 +44,7 @@ except ImportError as e:
45
44
  termios = None
46
45
  tty = None
47
46
 
48
- AGENT_MODES = ["code", "basic", "interpreter", "full", "code-basic", "search", "search-full"]
47
+ AGENT_MODES = ["code", "basic", "interpreter", "full", "code-basic", "search", "search-full", "chat"] # Added "chat"
49
48
 
50
49
 
51
50
  def setup_terminal():
@@ -104,7 +103,7 @@ def restore_terminal(old_settings):
104
103
  help="Set logging level (info/debug/warning).",
105
104
  )
106
105
  @click.option("--verbose", is_flag=True, help="Enable verbose output.")
107
- @click.option("--mode", type=click.Choice(AGENT_MODES), default="basic", help="Agent mode (code/search/full).")
106
+ @click.option("--mode", type=click.Choice(AGENT_MODES), default="basic", help="Agent mode (code/search/full/chat).")
108
107
  @click.option(
109
108
  "--vision-model-name",
110
109
  default=None,
@@ -170,6 +169,7 @@ def cli(
170
169
  max_tokens_working_memory=max_tokens_working_memory,
171
170
  no_stream=False, # Default value for backward compatibility
172
171
  thinking_model_name=thinking_model,
172
+ chat_system_prompt=None, # Default to None for non-chat modes
173
173
  )
174
174
  ctx.invoke(
175
175
  task,
@@ -193,7 +193,7 @@ def cli(
193
193
  help='Specify the model to use (litellm format, e.g. "openrouter/deepseek/deepseek-chat").',
194
194
  )
195
195
  @click.option("--verbose", is_flag=True, help="Enable verbose output.")
196
- @click.option("--mode", type=click.Choice(AGENT_MODES), default="basic", help="Agent mode (code/search/full).")
196
+ @click.option("--mode", type=click.Choice(AGENT_MODES), default="basic", help="Agent mode (code/search/full/chat).")
197
197
  @click.option(
198
198
  "--log",
199
199
  type=click.Choice(["info", "debug", "warning"]),
@@ -263,6 +263,7 @@ def task(
263
263
  max_tokens_working_memory=max_tokens_working_memory,
264
264
  no_stream=no_stream,
265
265
  thinking_model_name=thinking_model,
266
+ chat_system_prompt=None # Default to None for task command
266
267
  )
267
268
 
268
269
  task_runner(
@@ -304,6 +305,81 @@ def list_models(search: Optional[str] = None):
304
305
  console.print(f"- {model}")
305
306
 
306
307
 
308
+ @cli.command()
309
+ @click.option(
310
+ "--persona",
311
+ default="You are a friendly, helpful AI assistant.",
312
+ help="Specify the persona for chat mode (e.g., 'You are a witty pirate AI')."
313
+ )
314
+ @click.option(
315
+ "--model-name",
316
+ default=MODEL_NAME,
317
+ help='Specify the model to use (litellm format, e.g. "openai/gpt-4o-mini").',
318
+ )
319
+ @click.option(
320
+ "--log",
321
+ type=click.Choice(["info", "debug", "warning"]),
322
+ default="info",
323
+ help="Set logging level (info/debug/warning).",
324
+ )
325
+ @click.option("--verbose", is_flag=True, help="Enable verbose output.")
326
+ @click.option(
327
+ "--vision-model-name",
328
+ default=None,
329
+ help='Specify the vision model to use (litellm format, e.g. "openrouter/openai/gpt-4o-mini").',
330
+ )
331
+ @click.option(
332
+ "--no-stream",
333
+ is_flag=True,
334
+ help="Disable streaming output (default: streaming enabled).",
335
+ )
336
+ @click.option(
337
+ "--tool-mode",
338
+ type=str,
339
+ default=None,
340
+ help="Specify a tool or toolset to use in chat mode (e.g., 'search', 'code', or a specific tool name).",
341
+ )
342
+ @click.option(
343
+ "--auto-tool-call",
344
+ is_flag=True,
345
+ default=True,
346
+ help="Enable automatic tool execution and interpretation in chat mode (default: True).",
347
+ )
348
+ def chat(
349
+ persona: str,
350
+ model_name: str,
351
+ log: str,
352
+ verbose: bool,
353
+ vision_model_name: str | None,
354
+ no_stream: bool,
355
+ tool_mode: Optional[str],
356
+ auto_tool_call: bool,
357
+ ) -> None:
358
+ """Start a conversational chat with the QuantaLogic agent."""
359
+ console = Console()
360
+ config = QLConfig(
361
+ model_name=model_name,
362
+ verbose=verbose,
363
+ mode="chat",
364
+ log=log,
365
+ vision_model_name=vision_model_name,
366
+ max_iterations=1, # Chat mode doesn't need iterations
367
+ compact_every_n_iteration=None,
368
+ max_tokens_working_memory=None,
369
+ no_stream=no_stream,
370
+ thinking_model_name="default",
371
+ chat_system_prompt=persona,
372
+ tool_mode=tool_mode,
373
+ auto_tool_call=auto_tool_call,
374
+ )
375
+ try:
376
+ task_runner(console, None, config, None)
377
+ except Exception as e:
378
+ console.print(f"[red]Error in chat mode: {str(e)}[/red]")
379
+ logger.error(f"Error in chat execution: {e}", exc_info=True)
380
+ sys.exit(1)
381
+
382
+
307
383
  def main():
308
384
  """Main entry point."""
309
385
  old_settings = setup_terminal()
@@ -314,4 +390,4 @@ def main():
314
390
 
315
391
 
316
392
  if __name__ == "__main__":
317
- main()
393
+ main()
@@ -0,0 +1,54 @@
1
+ ### Chat System Configuration
2
+
3
+ #### Agent Persona
4
+ {{ persona }}
5
+
6
+ You must reply to the user directly, without any additional information except if we must call a tool to execute a task before replying to the user.
7
+
8
+ #### Tool Usage Guidelines
9
+
10
+ When using tools, you MUST use the EXACT tool name as shown in the Tools list below. Each tool has specific required parameters.
11
+
12
+ Tools must be called using the following XML format, with NO additional text:
13
+
14
+ ```xml
15
+ <action>
16
+ <tool_name> <!-- Use the EXACT tool name from the list below -->
17
+ <parameter_name>parameter_value</parameter_name> <!-- Include all required parameters -->
18
+ <another_parameter>value</another_parameter> <!-- Optional parameters if needed -->
19
+ </tool_name>
20
+ </action>
21
+ ```
22
+
23
+ #### Examples of Correct Tool Usage
24
+
25
+ **Example 1 - Search Tool:**
26
+ ```xml
27
+ <action>
28
+ <duckduckgo_tool>
29
+ <query>search query text</query>
30
+ <max_results>5</max_results> <!-- Always include max_results for search -->
31
+ </duckduckgo_tool>
32
+ </action>
33
+ ```
34
+
35
+ **Example 2 - Task Completion Tool:**
36
+ ```xml
37
+ <action>
38
+ <task_complete>
39
+ <answer>Your final answer to the task</answer>
40
+ </task_complete>
41
+ </action>
42
+ ```
43
+
44
+ #### Operational Tools Available
45
+
46
+ {% if tools_prompt != "No tools available." %}
47
+ **Tools Available**:
48
+ {{ tools_prompt | indent(2) }}
49
+ {% else %}
50
+ **Tools Available**: None
51
+ {% endif %}
52
+
53
+ Don't invent tools that are not listed.
54
+