quantalogic 0.80__py3-none-any.whl → 0.93__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.
Files changed (55) hide show
  1. quantalogic/flow/__init__.py +16 -34
  2. quantalogic/main.py +11 -6
  3. quantalogic/tools/tool.py +8 -922
  4. quantalogic-0.93.dist-info/METADATA +475 -0
  5. {quantalogic-0.80.dist-info → quantalogic-0.93.dist-info}/RECORD +8 -54
  6. quantalogic/codeact/TODO.md +0 -14
  7. quantalogic/codeact/__init__.py +0 -0
  8. quantalogic/codeact/agent.py +0 -478
  9. quantalogic/codeact/cli.py +0 -50
  10. quantalogic/codeact/cli_commands/__init__.py +0 -0
  11. quantalogic/codeact/cli_commands/create_toolbox.py +0 -45
  12. quantalogic/codeact/cli_commands/install_toolbox.py +0 -20
  13. quantalogic/codeact/cli_commands/list_executor.py +0 -15
  14. quantalogic/codeact/cli_commands/list_reasoners.py +0 -15
  15. quantalogic/codeact/cli_commands/list_toolboxes.py +0 -47
  16. quantalogic/codeact/cli_commands/task.py +0 -215
  17. quantalogic/codeact/cli_commands/tool_info.py +0 -24
  18. quantalogic/codeact/cli_commands/uninstall_toolbox.py +0 -43
  19. quantalogic/codeact/config.yaml +0 -21
  20. quantalogic/codeact/constants.py +0 -9
  21. quantalogic/codeact/events.py +0 -85
  22. quantalogic/codeact/examples/README.md +0 -342
  23. quantalogic/codeact/examples/agent_sample.yaml +0 -29
  24. quantalogic/codeact/executor.py +0 -186
  25. quantalogic/codeact/history_manager.py +0 -94
  26. quantalogic/codeact/llm_util.py +0 -57
  27. quantalogic/codeact/plugin_manager.py +0 -92
  28. quantalogic/codeact/prompts/error_format.j2 +0 -11
  29. quantalogic/codeact/prompts/generate_action.j2 +0 -77
  30. quantalogic/codeact/prompts/generate_program.j2 +0 -52
  31. quantalogic/codeact/prompts/response_format.j2 +0 -11
  32. quantalogic/codeact/react_agent.py +0 -318
  33. quantalogic/codeact/reasoner.py +0 -185
  34. quantalogic/codeact/templates/toolbox/README.md.j2 +0 -10
  35. quantalogic/codeact/templates/toolbox/pyproject.toml.j2 +0 -16
  36. quantalogic/codeact/templates/toolbox/tools.py.j2 +0 -6
  37. quantalogic/codeact/templates.py +0 -7
  38. quantalogic/codeact/tools_manager.py +0 -258
  39. quantalogic/codeact/utils.py +0 -62
  40. quantalogic/codeact/xml_utils.py +0 -126
  41. quantalogic/flow/flow.py +0 -1070
  42. quantalogic/flow/flow_extractor.py +0 -783
  43. quantalogic/flow/flow_generator.py +0 -322
  44. quantalogic/flow/flow_manager.py +0 -676
  45. quantalogic/flow/flow_manager_schema.py +0 -287
  46. quantalogic/flow/flow_mermaid.py +0 -365
  47. quantalogic/flow/flow_validator.py +0 -479
  48. quantalogic/flow/flow_yaml.linkedin.md +0 -31
  49. quantalogic/flow/flow_yaml.md +0 -767
  50. quantalogic/flow/templates/prompt_check_inventory.j2 +0 -1
  51. quantalogic/flow/templates/system_check_inventory.j2 +0 -1
  52. quantalogic-0.80.dist-info/METADATA +0 -900
  53. {quantalogic-0.80.dist-info → quantalogic-0.93.dist-info}/LICENSE +0 -0
  54. {quantalogic-0.80.dist-info → quantalogic-0.93.dist-info}/WHEEL +0 -0
  55. {quantalogic-0.80.dist-info → quantalogic-0.93.dist-info}/entry_points.txt +0 -0
@@ -1,478 +0,0 @@
1
- """High-level interface for the Quantalogic Agent with modular configuration."""
2
-
3
- import asyncio
4
- import os
5
- from dataclasses import dataclass, field
6
- from pathlib import Path
7
- from typing import Any, Callable, Dict, List, Optional, Tuple, Union
8
-
9
- import yaml
10
- from jinja2 import Environment
11
- from loguru import logger
12
- from lxml import etree
13
-
14
- from quantalogic.tools import Tool
15
-
16
- from .constants import MAX_HISTORY_TOKENS, MAX_TOKENS
17
- from .executor import BaseExecutor, Executor
18
- from .llm_util import litellm_completion
19
- from .plugin_manager import PluginManager
20
- from .react_agent import ReActAgent
21
- from .reasoner import BaseReasoner, Reasoner
22
- from .templates import jinja_env as default_jinja_env
23
- from .tools_manager import RetrieveStepTool, get_default_tools
24
- from .utils import process_tools
25
-
26
-
27
- @dataclass
28
- class AgentConfig:
29
- """Comprehensive configuration for the Agent, loadable from a YAML file or direct arguments."""
30
- model: str = "gemini/gemini-2.0-flash"
31
- max_iterations: int = 5
32
- tools: Optional[List[Union[Tool, Callable]]] = None
33
- max_history_tokens: int = MAX_HISTORY_TOKENS
34
- toolbox_directory: str = "toolboxes"
35
- enabled_toolboxes: Optional[List[str]] = None
36
- reasoner_name: str = "default"
37
- executor_name: str = "default"
38
- personality: Optional[str] = None
39
- backstory: Optional[str] = None
40
- sop: Optional[str] = None
41
- jinja_env: Optional[Environment] = None
42
- name: Optional[str] = None
43
- tools_config: Optional[List[Dict[str, Any]]] = None
44
- reasoner: Optional[Dict[str, Any]] = field(default_factory=lambda: {"name": "default"})
45
- executor: Optional[Dict[str, Any]] = field(default_factory=lambda: {"name": "default"})
46
- profile: Optional[str] = None
47
- customizations: Optional[Dict[str, Any]] = None
48
- agent_tool_model: str = "gemini/gemini-2.0-flash" # Configurable model for AgentTool
49
- agent_tool_timeout: int = 30 # Configurable timeout for AgentTool
50
-
51
- def __init__(
52
- self,
53
- model: str = "gemini/gemini-2.0-flash",
54
- max_iterations: int = 5,
55
- tools: Optional[List[Union[Tool, Callable]]] = None,
56
- max_history_tokens: int = MAX_HISTORY_TOKENS,
57
- toolbox_directory: str = "toolboxes",
58
- enabled_toolboxes: Optional[List[str]] = None,
59
- reasoner_name: str = "default",
60
- executor_name: str = "default",
61
- personality: Optional[str] = None,
62
- backstory: Optional[str] = None,
63
- sop: Optional[str] = None,
64
- jinja_env: Optional[Environment] = None,
65
- config_file: Optional[str] = None,
66
- name: Optional[str] = None,
67
- tools_config: Optional[List[Dict[str, Any]]] = None,
68
- reasoner: Optional[Dict[str, Any]] = None,
69
- executor: Optional[Dict[str, Any]] = None,
70
- profile: Optional[str] = None,
71
- customizations: Optional[Dict[str, Any]] = None,
72
- agent_tool_model: str = "gemini/gemini-2.0-flash",
73
- agent_tool_timeout: int = 30
74
- ) -> None:
75
- """Initialize configuration from arguments or a YAML file."""
76
- if config_file:
77
- try:
78
- with open(Path(__file__).parent / config_file) as f:
79
- config: Dict = yaml.safe_load(f) or {}
80
- self._load_from_config(config, model, max_iterations, max_history_tokens, toolbox_directory,
81
- tools, enabled_toolboxes, reasoner_name, executor_name, personality,
82
- backstory, sop, jinja_env, name, tools_config, reasoner, executor,
83
- profile, customizations, agent_tool_model, agent_tool_timeout)
84
- except FileNotFoundError as e:
85
- logger.warning(f"Config file {config_file} not found: {e}. Using defaults.")
86
- self._set_defaults(model, max_iterations, max_history_tokens, toolbox_directory,
87
- tools, enabled_toolboxes, reasoner_name, executor_name, personality,
88
- backstory, sop, jinja_env, name, tools_config, reasoner, executor,
89
- profile, customizations, agent_tool_model, agent_tool_timeout)
90
- except yaml.YAMLError as e:
91
- logger.error(f"Error parsing YAML config {config_file}: {e}. Falling back to defaults.")
92
- self._set_defaults(model, max_iterations, max_history_tokens, toolbox_directory,
93
- tools, enabled_toolboxes, reasoner_name, executor_name, personality,
94
- backstory, sop, jinja_env, name, tools_config, reasoner, executor,
95
- profile, customizations, agent_tool_model, agent_tool_timeout)
96
- else:
97
- self._set_defaults(model, max_iterations, max_history_tokens, toolbox_directory,
98
- tools, enabled_toolboxes, reasoner_name, executor_name, personality,
99
- backstory, sop, jinja_env, name, tools_config, reasoner, executor,
100
- profile, customizations, agent_tool_model, agent_tool_timeout)
101
- self.__post_init__()
102
-
103
- def _load_from_config(self, config: Dict, *args) -> None:
104
- """Load configuration from a dictionary, overriding with explicit arguments if provided."""
105
- model, max_iterations, max_history_tokens, toolbox_directory, tools, enabled_toolboxes, \
106
- reasoner_name, executor_name, personality, backstory, sop, jinja_env, name, tools_config, \
107
- reasoner, executor, profile, customizations, agent_tool_model, agent_tool_timeout = args
108
-
109
- self.model = config.get("model", model)
110
- self.max_iterations = config.get("max_iterations", max_iterations)
111
- self.max_history_tokens = config.get("max_history_tokens", max_history_tokens)
112
- self.toolbox_directory = config.get("toolbox_directory", toolbox_directory)
113
- self.tools = tools if tools is not None else config.get("tools")
114
- self.enabled_toolboxes = config.get("enabled_toolboxes", enabled_toolboxes)
115
- self.reasoner = config.get("reasoner", {"name": config.get("reasoner_name", reasoner_name)})
116
- self.executor = config.get("executor", {"name": config.get("executor_name", executor_name)})
117
- self.personality = config.get("personality", personality)
118
- self.backstory = config.get("backstory", backstory)
119
- self.sop = config.get("sop", sop)
120
- self.jinja_env = jinja_env or default_jinja_env
121
- self.name = config.get("name", name)
122
- self.tools_config = config.get("tools_config", tools_config)
123
- self.profile = config.get("profile", profile)
124
- self.customizations = config.get("customizations", customizations)
125
- self.agent_tool_model = config.get("agent_tool_model", agent_tool_model)
126
- self.agent_tool_timeout = config.get("agent_tool_timeout", agent_tool_timeout)
127
-
128
- def _set_defaults(self, model, max_iterations, max_history_tokens, toolbox_directory,
129
- tools, enabled_toolboxes, reasoner_name, executor_name, personality,
130
- backstory, sop, jinja_env, name, tools_config, reasoner, executor,
131
- profile, customizations, agent_tool_model, agent_tool_timeout) -> None:
132
- """Set default values for all configuration fields."""
133
- self.model = model
134
- self.max_iterations = max_iterations
135
- self.max_history_tokens = max_history_tokens
136
- self.toolbox_directory = toolbox_directory
137
- self.tools = tools
138
- self.enabled_toolboxes = enabled_toolboxes
139
- self.reasoner = reasoner if reasoner is not None else {"name": reasoner_name}
140
- self.executor = executor if executor is not None else {"name": executor_name}
141
- self.personality = personality
142
- self.backstory = backstory
143
- self.sop = sop
144
- self.jinja_env = jinja_env or default_jinja_env
145
- self.name = name
146
- self.tools_config = tools_config
147
- self.profile = profile
148
- self.customizations = customizations
149
- self.agent_tool_model = agent_tool_model
150
- self.agent_tool_timeout = agent_tool_timeout
151
-
152
- def __post_init__(self) -> None:
153
- """Apply profile defaults and customizations after initialization."""
154
- profiles = {
155
- "math_expert": {
156
- "personality": {"traits": ["precise", "logical"]},
157
- "tools_config": [{"name": "math_tools", "enabled": True}],
158
- "sop": "Focus on accuracy and clarity in mathematical solutions."
159
- },
160
- "creative_writer": {
161
- "personality": {"traits": ["creative", "expressive"]},
162
- "tools_config": [{"name": "text_tools", "enabled": True}],
163
- "sop": "Generate engaging and imaginative content."
164
- }
165
- }
166
- if self.profile and self.profile in profiles:
167
- base_config = profiles[self.profile]
168
- for key, value in base_config.items():
169
- if not getattr(self, key) or (key == "personality" and isinstance(getattr(self, key), str)):
170
- setattr(self, key, value)
171
- if self.customizations:
172
- for key, value in self.customizations.items():
173
- if hasattr(self, key):
174
- current = getattr(self, key)
175
- if isinstance(current, dict):
176
- current.update(value)
177
- elif current is None or (key in ["personality", "backstory"] and isinstance(current, str)):
178
- setattr(self, key, value)
179
-
180
- class Agent:
181
- """High-level interface for the Quantalogic Agent with unified configuration."""
182
- def __init__(
183
- self,
184
- config: Union[AgentConfig, str, None] = None
185
- ) -> None:
186
- """Initialize the agent with a configuration."""
187
- try:
188
- if isinstance(config, str):
189
- config = AgentConfig(config_file=config)
190
- elif config is None:
191
- config = AgentConfig()
192
- elif not isinstance(config, AgentConfig):
193
- raise ValueError("Config must be an AgentConfig instance or a string path to a config file.")
194
- except Exception as e:
195
- logger.error(f"Failed to initialize config: {e}. Using default configuration.")
196
- config = AgentConfig()
197
-
198
- self.config = config
199
- self.plugin_manager = PluginManager()
200
- try:
201
- self.plugin_manager.load_plugins()
202
- except Exception as e:
203
- logger.error(f"Failed to load plugins: {e}")
204
- self.model: str = config.model
205
- self.default_tools: List[Tool] = self._get_tools()
206
- self.max_iterations: int = config.max_iterations
207
- self.personality = config.personality
208
- self.backstory = config.backstory
209
- self.sop: Optional[str] = config.sop
210
- self.name: Optional[str] = config.name
211
- self.max_history_tokens: int = config.max_history_tokens
212
- self.jinja_env: Environment = config.jinja_env
213
- self._observers: List[Tuple[Callable, List[str]]] = []
214
- self.last_solve_context_vars: Dict = {}
215
- self.default_reasoner_name: str = config.reasoner.get("name", config.reasoner_name)
216
- self.default_executor_name: str = config.executor.get("name", config.executor_name)
217
-
218
- def _get_tools(self) -> List[Tool]:
219
- """Load tools, applying tools_config if provided."""
220
- try:
221
- base_tools = (
222
- process_tools(self.config.tools)
223
- if self.config.tools is not None
224
- else get_default_tools(self.model, enabled_toolboxes=self.config.enabled_toolboxes)
225
- )
226
- if not self.config.tools_config:
227
- return base_tools
228
-
229
- self._resolve_secrets(self.config.tools_config)
230
- filtered_tools = []
231
- processed_names = set()
232
- for tool_conf in self.config.tools_config:
233
- tool_name = tool_conf.get("name")
234
- if tool_conf.get("enabled", True):
235
- tool = next((t for t in base_tools if t.name == tool_name or t.toolbox_name == tool_name), None)
236
- if tool and tool.name not in processed_names:
237
- for key, value in tool_conf.items():
238
- if key not in ["name", "enabled"]:
239
- setattr(tool, key, value)
240
- filtered_tools.append(tool)
241
- processed_names.add(tool.name)
242
- for tool in base_tools:
243
- if tool.name not in processed_names:
244
- filtered_tools.append(tool)
245
- logger.info(f"Loaded {len(filtered_tools)} tools successfully.")
246
- return filtered_tools
247
- except Exception as e:
248
- logger.error(f"Error loading tools: {e}. Returning empty toolset.")
249
- return []
250
-
251
- def _resolve_secrets(self, config_dict: List[Dict[str, Any]]) -> None:
252
- """Resolve environment variable placeholders in tools_config."""
253
- try:
254
- for item in config_dict:
255
- for key, value in item.items():
256
- if isinstance(value, str) and "{{ env." in value:
257
- env_var = value.split("{{ env.")[1].split("}}")[0]
258
- item[key] = os.getenv(env_var, value)
259
- elif isinstance(value, dict):
260
- self._resolve_secrets([value])
261
- except Exception as e:
262
- logger.error(f"Error resolving secrets in tools_config: {e}")
263
-
264
- def _build_system_prompt(self) -> str:
265
- """Build a system prompt based on name, personality, backstory, and SOP."""
266
- try:
267
- prompt = f"I am {self.name}, an AI assistant." if self.name else "You are an AI assistant."
268
- if self.personality:
269
- if isinstance(self.personality, str):
270
- prompt += f" I have a {self.personality} personality."
271
- elif isinstance(self.personality, dict):
272
- traits = self.personality.get("traits", [])
273
- if traits:
274
- prompt += f" I have the following personality traits: {', '.join(traits)}."
275
- tone = self.personality.get("tone")
276
- if tone:
277
- prompt += f" My tone is {tone}."
278
- humor = self.personality.get("humor_level")
279
- if humor:
280
- prompt += f" My humor level is {humor}."
281
- if self.backstory:
282
- if isinstance(self.backstory, str):
283
- prompt += f" My backstory is: {self.backstory}"
284
- elif isinstance(self.backstory, dict):
285
- origin = self.backstory.get("origin")
286
- if origin:
287
- prompt += f" I was created by {origin}."
288
- purpose = self.backstory.get("purpose")
289
- if purpose:
290
- prompt += f" My purpose is {purpose}."
291
- experience = self.backstory.get("experience")
292
- if experience:
293
- prompt += f" My experience includes: {experience}"
294
- if self.sop:
295
- prompt += f" Follow this standard operating procedure: {self.sop}"
296
- return prompt
297
- except Exception as e:
298
- logger.error(f"Error building system prompt: {e}. Using default.")
299
- return "You are an AI assistant."
300
-
301
- async def chat(
302
- self,
303
- message: str,
304
- use_tools: bool = False,
305
- tools: Optional[List[Union[Tool, Callable]]] = None,
306
- timeout: int = 30,
307
- max_tokens: int = MAX_TOKENS,
308
- temperature: float = 0.7,
309
- streaming: bool = False,
310
- reasoner_name: Optional[str] = None,
311
- executor_name: Optional[str] = None
312
- ) -> str:
313
- """Single-step interaction with optional custom tools and streaming."""
314
- system_prompt: str = self._build_system_prompt()
315
- try:
316
- if use_tools:
317
- chat_tools: List[Tool] = process_tools(tools) if tools is not None else self.default_tools
318
- reasoner_name = reasoner_name or self.default_reasoner_name
319
- executor_name = executor_name or self.default_executor_name
320
- reasoner_cls = self.plugin_manager.reasoners.get(reasoner_name, Reasoner)
321
- executor_cls = self.plugin_manager.executors.get(executor_name, Executor)
322
- reasoner_config = self.config.reasoner.get("config", {})
323
- executor_config = self.config.executor.get("config", {})
324
- chat_agent = ReActAgent(
325
- model=self.model,
326
- tools=chat_tools,
327
- max_iterations=1,
328
- max_history_tokens=self.max_history_tokens,
329
- reasoner=reasoner_cls(self.model, chat_tools, **reasoner_config),
330
- executor=executor_cls(chat_tools, self._notify_observers, **executor_config)
331
- )
332
- chat_agent.executor.register_tool(RetrieveStepTool(chat_agent.history_manager.store))
333
- for observer, event_types in self._observers:
334
- chat_agent.add_observer(observer, event_types)
335
- history: List[Dict] = await chat_agent.solve(message, system_prompt=system_prompt, streaming=streaming)
336
- return self._extract_response(history)
337
- else:
338
- response: str = await litellm_completion(
339
- model=self.model,
340
- messages=[
341
- {"role": "system", "content": system_prompt},
342
- {"role": "user", "content": message}
343
- ],
344
- max_tokens=max_tokens,
345
- temperature=temperature,
346
- stream=streaming,
347
- notify_event=self._notify_observers if streaming else None
348
- )
349
- return response.strip()
350
- except Exception as e:
351
- logger.error(f"Chat failed: {e}")
352
- return f"Error: Unable to process chat request due to {str(e)}"
353
-
354
- def sync_chat(self, message: str, timeout: int = 30) -> str:
355
- """Synchronous wrapper for chat."""
356
- try:
357
- return asyncio.run(self.chat(message, timeout=timeout))
358
- except Exception as e:
359
- logger.error(f"Synchronous chat failed: {e}")
360
- return f"Error: {str(e)}"
361
-
362
- async def solve(
363
- self,
364
- task: str,
365
- success_criteria: Optional[str] = None,
366
- max_iterations: Optional[int] = None,
367
- tools: Optional[List[Union[Tool, Callable]]] = None,
368
- timeout: int = 300,
369
- streaming: bool = False,
370
- reasoner_name: Optional[str] = None,
371
- executor_name: Optional[str] = None
372
- ) -> List[Dict]:
373
- """Multi-step task solving with optional custom tools, max_iterations, and streaming."""
374
- system_prompt: str = self._build_system_prompt()
375
- try:
376
- solve_tools: List[Tool] = process_tools(tools) if tools is not None else self.default_tools
377
- reasoner_name = reasoner_name or self.default_reasoner_name
378
- executor_name = executor_name or self.default_executor_name
379
- reasoner_cls = self.plugin_manager.reasoners.get(reasoner_name, Reasoner)
380
- executor_cls = self.plugin_manager.executors.get(executor_name, Executor)
381
- reasoner_config = self.config.reasoner.get("config", {})
382
- executor_config = self.config.executor.get("config", {})
383
- solve_agent = ReActAgent(
384
- model=self.model,
385
- tools=solve_tools,
386
- max_iterations=max_iterations if max_iterations is not None else self.max_iterations,
387
- max_history_tokens=self.max_history_tokens,
388
- reasoner=reasoner_cls(self.model, solve_tools, **reasoner_config),
389
- executor=executor_cls(solve_tools, self._notify_observers, **executor_config)
390
- )
391
- solve_agent.executor.register_tool(RetrieveStepTool(solve_agent.history_manager.store))
392
- for observer, event_types in self._observers:
393
- solve_agent.add_observer(observer, event_types)
394
-
395
- history: List[Dict] = await solve_agent.solve(
396
- task,
397
- success_criteria,
398
- system_prompt=system_prompt,
399
- max_iterations=max_iterations,
400
- streaming=streaming
401
- )
402
- self.last_solve_context_vars = solve_agent.context_vars.copy()
403
- return history
404
- except Exception as e:
405
- logger.error(f"Solve failed: {e}")
406
- return [{"error": f"Failed to solve task: {str(e)}"}]
407
-
408
- def sync_solve(self, task: str, success_criteria: Optional[str] = None, timeout: int = 300) -> List[Dict]:
409
- """Synchronous wrapper for solve."""
410
- try:
411
- return asyncio.run(self.solve(task, success_criteria, timeout=timeout))
412
- except Exception as e:
413
- logger.error(f"Synchronous solve failed: {e}")
414
- return [{"error": f"Failed to solve task synchronously: {str(e)}"}]
415
-
416
- def add_observer(self, observer: Callable, event_types: List[str]) -> 'Agent':
417
- """Add an observer to be applied to agents created in chat and solve."""
418
- self._observers.append((observer, event_types))
419
- return self
420
-
421
- def register_tool(self, tool: Tool) -> None:
422
- """Register a new tool dynamically at runtime."""
423
- try:
424
- if tool.name in [t.name for t in self.default_tools]:
425
- raise ValueError(f"Tool '{tool.name}' is already registered")
426
- self.default_tools.append(tool)
427
- self.plugin_manager.tools.register(tool)
428
- except Exception as e:
429
- logger.error(f"Failed to register tool {tool.name}: {e}")
430
-
431
- def register_reasoner(self, reasoner: BaseReasoner, name: str) -> None:
432
- """Register a new reasoner dynamically at runtime."""
433
- try:
434
- self.plugin_manager.reasoners[name] = reasoner.__class__
435
- except Exception as e:
436
- logger.error(f"Failed to register reasoner {name}: {e}")
437
-
438
- def register_executor(self, executor: BaseExecutor, name: str) -> None:
439
- """Register a new executor dynamically at runtime."""
440
- try:
441
- self.plugin_manager.executors[name] = executor.__class__
442
- except Exception as e:
443
- logger.error(f"Failed to register executor {name}: {e}")
444
-
445
- def list_tools(self) -> List[str]:
446
- """Return a list of available tool names."""
447
- return [tool.name for tool in self.default_tools]
448
-
449
- def get_context_vars(self) -> Dict:
450
- """Return the context variables from the last solve call."""
451
- return self.last_solve_context_vars
452
-
453
- def _extract_response(self, history: List[Dict]) -> str:
454
- """Extract a clean response from the history."""
455
- if not history:
456
- return "No response generated."
457
- last_result: str = history[-1].get("result", "")
458
- try:
459
- root = etree.fromstring(last_result)
460
- if root.findtext("Status") == "Success":
461
- value: str = root.findtext("Value") or ""
462
- final_answer: Optional[str] = root.findtext("FinalAnswer")
463
- return final_answer.strip() if final_answer else value.strip()
464
- else:
465
- return f"Error: {root.findtext('Value') or 'Unknown error'}"
466
- except etree.XMLSyntaxError as e:
467
- logger.error(f"Failed to parse response XML: {e}")
468
- return last_result
469
-
470
- async def _notify_observers(self, event: object) -> None:
471
- """Notify all subscribed observers of an event."""
472
- try:
473
- await asyncio.gather(
474
- *(observer(event) for observer, types in self._observers if event.event_type in types),
475
- return_exceptions=True
476
- )
477
- except Exception as e:
478
- logger.error(f"Error notifying observers: {e}")
@@ -1,50 +0,0 @@
1
- """Command-line interface entry point for Quantalogic Agent."""
2
-
3
- import importlib
4
- from pathlib import Path
5
-
6
- import typer
7
- from loguru import logger
8
- from rich.console import Console
9
-
10
- from quantalogic.codeact.plugin_manager import PluginManager
11
-
12
- app = typer.Typer(no_args_is_help=True)
13
- console = Console()
14
-
15
- # Initialize PluginManager at module level to avoid duplicate loading
16
- plugin_manager = PluginManager()
17
- plugin_manager.load_plugins() # This is now synchronous
18
-
19
- # Dynamically load CLI commands from cli_commands directory
20
- cli_commands_dir = Path(__file__).parent / "cli_commands"
21
- for file in cli_commands_dir.glob("*.py"):
22
- if file.stem != "__init__":
23
- module_name = f"quantalogic.codeact.cli_commands.{file.stem}"
24
- try:
25
- module = importlib.import_module(module_name)
26
- command_name = file.stem.replace("_", "-")
27
- # Handle Typer app instances (e.g., list_toolboxes.py)
28
- if hasattr(module, "app") and isinstance(module.app, typer.Typer):
29
- if len(module.app.registered_commands) == 1:
30
- # If there's exactly one command, register it directly
31
- command = module.app.registered_commands[0]
32
- app.command(name=command_name)(command.callback)
33
- logger.debug(f"Loaded direct command: {command_name}")
34
- else:
35
- # Otherwise, treat it as a subcommand group
36
- app.add_typer(module.app, name=command_name)
37
- logger.debug(f"Loaded command group: {command_name}")
38
- # Handle direct function commands (e.g., task.py)
39
- elif hasattr(module, file.stem) and callable(getattr(module, file.stem)):
40
- app.command(name=command_name)(getattr(module, file.stem))
41
- logger.debug(f"Loaded direct command: {command_name}")
42
- except ImportError as e:
43
- logger.error(f"Failed to load command module {module_name}: {e}")
44
-
45
- # Load plugin CLI commands dynamically using the module-level plugin_manager
46
- for cmd_name, cmd_func in plugin_manager.cli_commands.items():
47
- app.command(name=cmd_name)(cmd_func)
48
-
49
- if __name__ == "__main__":
50
- app()
File without changes
@@ -1,45 +0,0 @@
1
- from pathlib import Path
2
-
3
- import typer
4
- from jinja2 import Environment, FileSystemLoader
5
- from loguru import logger
6
- from rich.console import Console
7
-
8
- app = typer.Typer()
9
-
10
- console = Console()
11
-
12
- @app.command()
13
- def create_toolbox(
14
- name: str = typer.Argument(..., help="Name of the new toolbox (e.g., 'my-toolbox')")
15
- ) -> None:
16
- """Create a starter toolbox project with the given name using Jinja2 templates."""
17
- toolbox_dir = Path(name)
18
- if toolbox_dir.exists():
19
- logger.error(f"Directory '{name}' already exists.")
20
- console.print(f"[red]Error: Directory '{name}' already exists.[/red]")
21
- raise typer.Exit(code=1)
22
-
23
- # Set up Jinja2 environment
24
- template_dir = Path(__file__).parent.parent / "templates" / "toolbox"
25
- env = Environment(loader=FileSystemLoader(template_dir), trim_blocks=True, lstrip_blocks=True)
26
-
27
- # Create directory structure
28
- toolbox_dir.mkdir()
29
- package_name = name.replace("-", "_")
30
- package_dir = toolbox_dir / package_name
31
- package_dir.mkdir()
32
- (package_dir / "__init__.py").touch()
33
-
34
- # Render and write template files
35
- context = {"name": name, "package_name": package_name}
36
- for template_name in ["pyproject.toml.j2", "tools.py.j2", "README.md.j2"]:
37
- template = env.get_template(template_name)
38
- content = template.render(**context)
39
- output_file = toolbox_dir / template_name.replace(".j2", "")
40
- if template_name == "tools.py.j2":
41
- output_file = package_dir / "tools.py"
42
- output_file.write_text(content.strip())
43
-
44
- logger.info(f"Created starter toolbox project: {name}")
45
- console.print(f"[green]Created starter toolbox project: {name}[/green]")
@@ -1,20 +0,0 @@
1
- import subprocess
2
-
3
- import typer
4
- from rich.console import Console
5
-
6
- app = typer.Typer()
7
-
8
- console = Console()
9
-
10
- @app.command()
11
- def install_toolbox(
12
- toolbox_name: str = typer.Argument(..., help="Name of the toolbox to install (PyPI package or local wheel file)")
13
- ) -> None:
14
- """Install a toolbox using uv pip install."""
15
- try:
16
- subprocess.run(["uv", "pip", "install", toolbox_name], check=True)
17
- console.print(f"[green]Toolbox '{toolbox_name}' installed successfully[/green]")
18
- except subprocess.CalledProcessError as e:
19
- console.print(f"[red]Failed to install toolbox: {e}[/red]")
20
- raise typer.Exit(code=1)
@@ -1,15 +0,0 @@
1
- import typer
2
- from rich.console import Console
3
-
4
- from quantalogic.codeact.cli import plugin_manager # Import shared plugin_manager from cli.py
5
-
6
- app = typer.Typer()
7
-
8
- console = Console()
9
-
10
- @app.command()
11
- def list_executors() -> None:
12
- """List all available executors."""
13
- console.print("[bold cyan]Available Executors:[/bold cyan]")
14
- for name in plugin_manager.executors.keys():
15
- console.print(f"- {name}")
@@ -1,15 +0,0 @@
1
- import typer
2
- from rich.console import Console
3
-
4
- from quantalogic.codeact.cli import plugin_manager # Import shared plugin_manager from cli.py
5
-
6
- app = typer.Typer()
7
-
8
- console = Console()
9
-
10
- @app.command()
11
- def list_reasoners() -> None:
12
- """List all available reasoners."""
13
- console.print("[bold cyan]Available Reasoners:[/bold cyan]")
14
- for name in plugin_manager.reasoners.keys():
15
- console.print(f"- {name}")