nucleusiq 0.1.0__tar.gz
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.
- nucleusiq-0.1.0/PKG-INFO +57 -0
- nucleusiq-0.1.0/README.md +23 -0
- nucleusiq-0.1.0/core/__init__.py +24 -0
- nucleusiq-0.1.0/core/agents/__init__.py +22 -0
- nucleusiq-0.1.0/core/agents/agent.py +453 -0
- nucleusiq-0.1.0/core/agents/builder/__init__.py +9 -0
- nucleusiq-0.1.0/core/agents/builder/base_agent.py +251 -0
- nucleusiq-0.1.0/core/agents/chat_models.py +119 -0
- nucleusiq-0.1.0/core/agents/components/__init__.py +18 -0
- nucleusiq-0.1.0/core/agents/components/critic.py +788 -0
- nucleusiq-0.1.0/core/agents/components/decomposer.py +318 -0
- nucleusiq-0.1.0/core/agents/components/executor.py +135 -0
- nucleusiq-0.1.0/core/agents/components/progress.py +80 -0
- nucleusiq-0.1.0/core/agents/components/refiner.py +528 -0
- nucleusiq-0.1.0/core/agents/components/validation.py +245 -0
- nucleusiq-0.1.0/core/agents/config/__init__.py +10 -0
- nucleusiq-0.1.0/core/agents/config/agent_config.py +145 -0
- nucleusiq-0.1.0/core/agents/execution_context.py +60 -0
- nucleusiq-0.1.0/core/agents/messaging/__init__.py +5 -0
- nucleusiq-0.1.0/core/agents/messaging/message_builder.py +157 -0
- nucleusiq-0.1.0/core/agents/modes/__init__.py +21 -0
- nucleusiq-0.1.0/core/agents/modes/autonomous_mode.py +270 -0
- nucleusiq-0.1.0/core/agents/modes/base_mode.py +276 -0
- nucleusiq-0.1.0/core/agents/modes/direct_mode.py +73 -0
- nucleusiq-0.1.0/core/agents/modes/standard_mode.py +255 -0
- nucleusiq-0.1.0/core/agents/plan.py +197 -0
- nucleusiq-0.1.0/core/agents/planning/__init__.py +19 -0
- nucleusiq-0.1.0/core/agents/planning/plan_creator.py +249 -0
- nucleusiq-0.1.0/core/agents/planning/plan_executor.py +409 -0
- nucleusiq-0.1.0/core/agents/planning/plan_parser.py +151 -0
- nucleusiq-0.1.0/core/agents/planning/planner.py +147 -0
- nucleusiq-0.1.0/core/agents/planning/prompt_strategy.py +151 -0
- nucleusiq-0.1.0/core/agents/planning/schema.py +60 -0
- nucleusiq-0.1.0/core/agents/react_agent.py +341 -0
- nucleusiq-0.1.0/core/agents/structured_output/__init__.py +77 -0
- nucleusiq-0.1.0/core/agents/structured_output/config.py +179 -0
- nucleusiq-0.1.0/core/agents/structured_output/errors.py +146 -0
- nucleusiq-0.1.0/core/agents/structured_output/handler.py +106 -0
- nucleusiq-0.1.0/core/agents/structured_output/parser.py +480 -0
- nucleusiq-0.1.0/core/agents/structured_output/resolver.py +156 -0
- nucleusiq-0.1.0/core/agents/structured_output/types.py +112 -0
- nucleusiq-0.1.0/core/agents/task.py +60 -0
- nucleusiq-0.1.0/core/llms/__init__.py +7 -0
- nucleusiq-0.1.0/core/llms/base.py +76 -0
- nucleusiq-0.1.0/core/llms/base_llm.py +87 -0
- nucleusiq-0.1.0/core/llms/llm_params.py +123 -0
- nucleusiq-0.1.0/core/llms/mock_llm.py +157 -0
- nucleusiq-0.1.0/core/memory/__init__.py +24 -0
- nucleusiq-0.1.0/core/memory/base.py +159 -0
- nucleusiq-0.1.0/core/memory/factory.py +99 -0
- nucleusiq-0.1.0/core/memory/full_history.py +42 -0
- nucleusiq-0.1.0/core/memory/sliding_window.py +54 -0
- nucleusiq-0.1.0/core/memory/summary.py +140 -0
- nucleusiq-0.1.0/core/memory/summary_window.py +161 -0
- nucleusiq-0.1.0/core/memory/token_budget.py +86 -0
- nucleusiq-0.1.0/core/plugins/__init__.py +89 -0
- nucleusiq-0.1.0/core/plugins/base.py +330 -0
- nucleusiq-0.1.0/core/plugins/builtin/__init__.py +31 -0
- nucleusiq-0.1.0/core/plugins/builtin/context_window.py +185 -0
- nucleusiq-0.1.0/core/plugins/builtin/human_approval.py +277 -0
- nucleusiq-0.1.0/core/plugins/builtin/model_call_limit.py +33 -0
- nucleusiq-0.1.0/core/plugins/builtin/model_fallback.py +76 -0
- nucleusiq-0.1.0/core/plugins/builtin/pii_guard.py +253 -0
- nucleusiq-0.1.0/core/plugins/builtin/result_validator.py +60 -0
- nucleusiq-0.1.0/core/plugins/builtin/tool_call_limit.py +35 -0
- nucleusiq-0.1.0/core/plugins/builtin/tool_guard.py +96 -0
- nucleusiq-0.1.0/core/plugins/builtin/tool_retry.py +61 -0
- nucleusiq-0.1.0/core/plugins/decorators.py +259 -0
- nucleusiq-0.1.0/core/plugins/errors.py +31 -0
- nucleusiq-0.1.0/core/plugins/manager.py +179 -0
- nucleusiq-0.1.0/core/prompts/__init__.py +9 -0
- nucleusiq-0.1.0/core/prompts/auto_chain_of_thought.py +191 -0
- nucleusiq-0.1.0/core/prompts/base.py +195 -0
- nucleusiq-0.1.0/core/prompts/chain_of_thought.py +121 -0
- nucleusiq-0.1.0/core/prompts/factory.py +85 -0
- nucleusiq-0.1.0/core/prompts/few_shot.py +195 -0
- nucleusiq-0.1.0/core/prompts/meta_prompt.py +321 -0
- nucleusiq-0.1.0/core/prompts/prompt_composer.py +137 -0
- nucleusiq-0.1.0/core/prompts/retrieval_augmented_generation.py +98 -0
- nucleusiq-0.1.0/core/prompts/zero_shot.py +92 -0
- nucleusiq-0.1.0/core/tools/__init__.py +5 -0
- nucleusiq-0.1.0/core/tools/base_tool.py +282 -0
- nucleusiq-0.1.0/core/utilities/__init__.py +9 -0
- nucleusiq-0.1.0/core/utilities/clustering.py +49 -0
- nucleusiq-0.1.0/nucleusiq.egg-info/PKG-INFO +57 -0
- nucleusiq-0.1.0/nucleusiq.egg-info/SOURCES.txt +89 -0
- nucleusiq-0.1.0/nucleusiq.egg-info/dependency_links.txt +1 -0
- nucleusiq-0.1.0/nucleusiq.egg-info/requires.txt +13 -0
- nucleusiq-0.1.0/nucleusiq.egg-info/top_level.txt +1 -0
- nucleusiq-0.1.0/pyproject.toml +146 -0
- nucleusiq-0.1.0/setup.cfg +4 -0
nucleusiq-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nucleusiq
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: NucleusIQ – open-source framework for building autonomous AI agents with advanced prompt engineering, flexible execution modes, and modular architecture.
|
|
5
|
+
Author-email: Nucleusbox <info@nucleusbox.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/nucleusbox/NucleusIQ
|
|
8
|
+
Project-URL: Documentation, https://github.com/nucleusbox/NucleusIQ#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/nucleusbox/NucleusIQ
|
|
10
|
+
Project-URL: Issues, https://github.com/nucleusbox/NucleusIQ/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/nucleusbox/NucleusIQ/releases
|
|
12
|
+
Keywords: AI,agents,orchestration,framework,prompt-engineering,LLM
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Python: <4.0,>=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
Requires-Dist: pydantic<3.0,>=2.0
|
|
26
|
+
Requires-Dist: PyYAML<7.0,>=5.4
|
|
27
|
+
Requires-Dist: python-dotenv<2.0,>=0.19.0
|
|
28
|
+
Requires-Dist: requests<3.0,>=2.25.1
|
|
29
|
+
Requires-Dist: typing_extensions>=4.0; python_version < "3.11"
|
|
30
|
+
Provides-Extra: openai
|
|
31
|
+
Requires-Dist: nucleusiq-openai; extra == "openai"
|
|
32
|
+
Provides-Extra: clustering
|
|
33
|
+
Requires-Dist: scikit-learn>=1.0; extra == "clustering"
|
|
34
|
+
|
|
35
|
+
# NucleusIQ
|
|
36
|
+
|
|
37
|
+
**Core package** for the NucleusIQ AI agent framework.
|
|
38
|
+
|
|
39
|
+
Includes agents, prompts, tools, and utilities.
|
|
40
|
+
|
|
41
|
+
See the main [README](https://github.com/nucleusbox/NucleusIQ) for full documentation.
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install nucleusiq
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from nucleusiq.agents import Agent
|
|
53
|
+
from nucleusiq.llms import MockLLM
|
|
54
|
+
|
|
55
|
+
agent = Agent(name="test", role="assistant", objective="help", llm=MockLLM())
|
|
56
|
+
result = await agent.execute({"id": "1", "objective": "Hello!"})
|
|
57
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# NucleusIQ
|
|
2
|
+
|
|
3
|
+
**Core package** for the NucleusIQ AI agent framework.
|
|
4
|
+
|
|
5
|
+
Includes agents, prompts, tools, and utilities.
|
|
6
|
+
|
|
7
|
+
See the main [README](https://github.com/nucleusbox/NucleusIQ) for full documentation.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install nucleusiq
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from nucleusiq.agents import Agent
|
|
19
|
+
from nucleusiq.llms import MockLLM
|
|
20
|
+
|
|
21
|
+
agent = Agent(name="test", role="assistant", objective="help", llm=MockLLM())
|
|
22
|
+
result = await agent.execute({"id": "1", "objective": "Hello!"})
|
|
23
|
+
```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NucleusIQ - An open-source framework for building and managing autonomous AI agents.
|
|
3
|
+
|
|
4
|
+
NucleusIQ offers diverse strategies and architectures to create intelligent chatbots,
|
|
5
|
+
financial tools, and multi-agent systems. With NucleusIQ, developers have the core
|
|
6
|
+
components and flexibility needed to develop advanced AI applications effortlessly.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__version__ = "0.1.0"
|
|
10
|
+
|
|
11
|
+
# Load environment variables from project root .env (if present).
|
|
12
|
+
# This makes OPENAI_API_KEY etc. available no matter which submodule is imported.
|
|
13
|
+
try:
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from dotenv import load_dotenv
|
|
17
|
+
|
|
18
|
+
_repo_root = Path(__file__).resolve().parents[2]
|
|
19
|
+
_env_path = _repo_root / ".env"
|
|
20
|
+
if _env_path.exists():
|
|
21
|
+
load_dotenv(_env_path, override=False)
|
|
22
|
+
except Exception:
|
|
23
|
+
# Never fail import due to dotenv loading.
|
|
24
|
+
pass
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Agent framework for NucleusIQ."""
|
|
2
|
+
|
|
3
|
+
from nucleusiq.agents.agent import Agent
|
|
4
|
+
from nucleusiq.agents.builder.base_agent import BaseAgent
|
|
5
|
+
from nucleusiq.agents.chat_models import ChatMessage, LLMCallKwargs, ToolCallRequest
|
|
6
|
+
from nucleusiq.agents.modes.base_mode import BaseExecutionMode
|
|
7
|
+
from nucleusiq.agents.plan import Plan, PlanStep
|
|
8
|
+
from nucleusiq.agents.react_agent import ReActAgent
|
|
9
|
+
from nucleusiq.agents.task import Task
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"Agent",
|
|
13
|
+
"BaseAgent",
|
|
14
|
+
"BaseExecutionMode",
|
|
15
|
+
"ChatMessage",
|
|
16
|
+
"LLMCallKwargs",
|
|
17
|
+
"Plan",
|
|
18
|
+
"PlanStep",
|
|
19
|
+
"ReActAgent",
|
|
20
|
+
"Task",
|
|
21
|
+
"ToolCallRequest",
|
|
22
|
+
]
|
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
# src/nucleusiq/agents/agent.py
|
|
2
|
+
"""
|
|
3
|
+
Agent — Thin orchestrator for NucleusIQ agents.
|
|
4
|
+
|
|
5
|
+
Routes execution to mode strategies (Direct, Standard, Autonomous)
|
|
6
|
+
via a pluggable registry. All heavy logic lives in:
|
|
7
|
+
|
|
8
|
+
- ``modes/`` — execution strategies
|
|
9
|
+
- ``planning/`` — plan creation & execution
|
|
10
|
+
- ``messaging/`` — LLM message construction
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import inspect
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from typing import Any, ClassVar, Dict, List, Type
|
|
16
|
+
|
|
17
|
+
from nucleusiq.agents.builder.base_agent import BaseAgent
|
|
18
|
+
from nucleusiq.agents.components.executor import Executor
|
|
19
|
+
from nucleusiq.agents.config.agent_config import AgentMetrics, AgentState
|
|
20
|
+
from nucleusiq.agents.modes.autonomous_mode import AutonomousMode
|
|
21
|
+
|
|
22
|
+
# Mode imports
|
|
23
|
+
from nucleusiq.agents.modes.base_mode import BaseExecutionMode
|
|
24
|
+
from nucleusiq.agents.modes.direct_mode import DirectMode
|
|
25
|
+
from nucleusiq.agents.modes.standard_mode import StandardMode
|
|
26
|
+
from nucleusiq.agents.plan import Plan, PlanStep
|
|
27
|
+
from nucleusiq.agents.structured_output.handler import StructuredOutputHandler
|
|
28
|
+
from nucleusiq.agents.task import Task
|
|
29
|
+
from nucleusiq.llms.llm_params import LLMParams
|
|
30
|
+
from nucleusiq.plugins.base import AgentContext, BasePlugin
|
|
31
|
+
from nucleusiq.plugins.errors import PluginHalt
|
|
32
|
+
from nucleusiq.plugins.manager import PluginManager
|
|
33
|
+
from pydantic import Field, PrivateAttr
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Agent(BaseAgent):
|
|
37
|
+
"""
|
|
38
|
+
Concrete implementation of an agent in the NucleusIQ framework.
|
|
39
|
+
|
|
40
|
+
This is a thin orchestrator that delegates execution to mode strategies
|
|
41
|
+
(DirectMode, StandardMode, AutonomousMode) via a pluggable registry.
|
|
42
|
+
|
|
43
|
+
Execution Modes (Gearbox Strategy):
|
|
44
|
+
- "direct": Fast, simple, no tools (Gear 1)
|
|
45
|
+
- "standard": Tool-enabled, linear execution (Gear 2) - default
|
|
46
|
+
- "autonomous": Full reasoning loop with planning and self-correction (Gear 3)
|
|
47
|
+
|
|
48
|
+
Prompt Precedence:
|
|
49
|
+
- If ``prompt`` is provided, it takes precedence over ``role``/``objective``
|
|
50
|
+
for LLM message construction during execution.
|
|
51
|
+
- If ``prompt`` is None, ``role`` and ``objective`` are used to construct
|
|
52
|
+
the system message: "You are a {role}. Your objective is to {objective}."
|
|
53
|
+
- ``role`` and ``objective`` are always used for planning context, even
|
|
54
|
+
when prompt exists.
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
# With prompt (prompt takes precedence)
|
|
58
|
+
agent = Agent(
|
|
59
|
+
name="CalculatorBot",
|
|
60
|
+
role="Calculator", # Used for planning context only
|
|
61
|
+
objective="Perform calculations", # Used for planning context only
|
|
62
|
+
prompt=PromptFactory.create_prompt().configure(
|
|
63
|
+
system="You are a helpful calculator assistant.",
|
|
64
|
+
user="Answer questions accurately."
|
|
65
|
+
),
|
|
66
|
+
llm=llm,
|
|
67
|
+
config=AgentConfig(execution_mode="standard")
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Without prompt (role/objective used)
|
|
71
|
+
agent = Agent(
|
|
72
|
+
name="CalculatorBot",
|
|
73
|
+
role="Calculator", # Used to build system message
|
|
74
|
+
objective="Perform calculations", # Used to build system message
|
|
75
|
+
prompt=None,
|
|
76
|
+
llm=llm,
|
|
77
|
+
config=AgentConfig(execution_mode="direct")
|
|
78
|
+
)
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
# ------------------------------------------------------------------ #
|
|
82
|
+
# Mode registry (Open/Closed Principle) #
|
|
83
|
+
# ------------------------------------------------------------------ #
|
|
84
|
+
|
|
85
|
+
_mode_registry: ClassVar[Dict[str, Type[BaseExecutionMode]]] = {
|
|
86
|
+
"direct": DirectMode,
|
|
87
|
+
"standard": StandardMode,
|
|
88
|
+
"autonomous": AutonomousMode,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def register_mode(cls, name: str, mode_class: Type[BaseExecutionMode]) -> None:
|
|
93
|
+
"""
|
|
94
|
+
Register a new execution mode without modifying Agent.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
name: Mode name (used in AgentConfig.execution_mode)
|
|
98
|
+
mode_class: Class implementing BaseExecutionMode
|
|
99
|
+
"""
|
|
100
|
+
cls._mode_registry[name] = mode_class
|
|
101
|
+
|
|
102
|
+
# ------------------------------------------------------------------ #
|
|
103
|
+
# Plugin system #
|
|
104
|
+
# ------------------------------------------------------------------ #
|
|
105
|
+
|
|
106
|
+
plugins: List[BasePlugin] = Field(
|
|
107
|
+
default_factory=list,
|
|
108
|
+
description="List of plugins to hook into the agent execution pipeline",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# ------------------------------------------------------------------ #
|
|
112
|
+
# Private attributes (initialised in initialize()) #
|
|
113
|
+
# ------------------------------------------------------------------ #
|
|
114
|
+
|
|
115
|
+
_executor: Executor | None = PrivateAttr(default=None)
|
|
116
|
+
_plugin_manager: PluginManager = PrivateAttr(default=None)
|
|
117
|
+
_structured_output: StructuredOutputHandler = PrivateAttr(
|
|
118
|
+
default_factory=StructuredOutputHandler
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# ------------------------------------------------------------------ #
|
|
122
|
+
# LIFECYCLE #
|
|
123
|
+
# ------------------------------------------------------------------ #
|
|
124
|
+
|
|
125
|
+
async def initialize(self) -> None:
|
|
126
|
+
"""Initialize agent components and resources."""
|
|
127
|
+
self._logger.info(f"Initializing agent: {self.name}")
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
# Initialize plugin manager
|
|
131
|
+
self._plugin_manager = PluginManager(self.plugins)
|
|
132
|
+
if self.plugins:
|
|
133
|
+
self._logger.debug(
|
|
134
|
+
"Plugin manager initialized with %d plugins",
|
|
135
|
+
len(self.plugins),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Initialize Executor component (always needed for tool execution)
|
|
139
|
+
if self.llm:
|
|
140
|
+
self._executor = Executor(self.llm, self.tools)
|
|
141
|
+
self._logger.debug("Executor component initialized")
|
|
142
|
+
else:
|
|
143
|
+
self._executor = None
|
|
144
|
+
self._logger.debug("Executor not initialized (no LLM)")
|
|
145
|
+
|
|
146
|
+
# Initialize memory if provided
|
|
147
|
+
if self.memory:
|
|
148
|
+
await self.memory.ainitialize()
|
|
149
|
+
self._logger.debug("Memory system initialized")
|
|
150
|
+
|
|
151
|
+
# Initialize prompt if provided
|
|
152
|
+
if self.prompt:
|
|
153
|
+
prompt_text = self.prompt.format_prompt()
|
|
154
|
+
self._logger.debug(f"Prompt system initialized \n {prompt_text}")
|
|
155
|
+
|
|
156
|
+
# Initialize tools
|
|
157
|
+
for tool in self.tools:
|
|
158
|
+
await tool.initialize()
|
|
159
|
+
if self.tools:
|
|
160
|
+
self._logger.debug("Initialised %d tools", len(self.tools))
|
|
161
|
+
|
|
162
|
+
# Initialization succeeded
|
|
163
|
+
self.state = AgentState.INITIALIZING
|
|
164
|
+
self._logger.info("Agent initialization completed successfully")
|
|
165
|
+
|
|
166
|
+
except Exception as e:
|
|
167
|
+
self.state = AgentState.ERROR
|
|
168
|
+
self._logger.error(f"Agent initialization failed: {str(e)}")
|
|
169
|
+
raise
|
|
170
|
+
|
|
171
|
+
# ------------------------------------------------------------------ #
|
|
172
|
+
# PLANNING (very simple by default) #
|
|
173
|
+
# ------------------------------------------------------------------ #
|
|
174
|
+
|
|
175
|
+
async def plan(self, task: Task | Dict[str, Any]) -> Plan:
|
|
176
|
+
"""
|
|
177
|
+
Create an execution plan for the given task.
|
|
178
|
+
|
|
179
|
+
By default, returns a simple one-step plan that executes the task
|
|
180
|
+
directly. Override this method or use LLM-based planning
|
|
181
|
+
(``_create_llm_plan``) for more sophisticated multi-step planning.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
task: Task instance or dictionary with 'id' and 'objective' keys
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Plan instance with steps
|
|
188
|
+
"""
|
|
189
|
+
# Convert dict to Task if needed (backward compatibility)
|
|
190
|
+
if isinstance(task, dict):
|
|
191
|
+
task = Task.from_dict(task)
|
|
192
|
+
|
|
193
|
+
# Create default one-step plan
|
|
194
|
+
step = PlanStep(step=1, action="execute", task=task)
|
|
195
|
+
return Plan(steps=[step], task=task)
|
|
196
|
+
|
|
197
|
+
# ------------------------------------------------------------------ #
|
|
198
|
+
# EXECUTION — thin dispatcher via mode registry #
|
|
199
|
+
# ------------------------------------------------------------------ #
|
|
200
|
+
|
|
201
|
+
def _resolve_llm_params(
|
|
202
|
+
self,
|
|
203
|
+
per_execute: LLMParams | None = None,
|
|
204
|
+
) -> Dict[str, Any]:
|
|
205
|
+
"""
|
|
206
|
+
Merge LLM parameter overrides and return a kwargs dict.
|
|
207
|
+
|
|
208
|
+
Merge chain (highest priority wins):
|
|
209
|
+
LLM defaults (in __init__) < AgentConfig.llm_params < per-execute llm_params
|
|
210
|
+
|
|
211
|
+
Only non-None values are included in the result.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
per_execute: Optional per-task LLM parameter overrides.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Dict of merged LLM call kwargs (may be empty).
|
|
218
|
+
"""
|
|
219
|
+
config_params = getattr(self.config, "llm_params", None)
|
|
220
|
+
if config_params is None and per_execute is None:
|
|
221
|
+
return {}
|
|
222
|
+
if config_params is not None and per_execute is not None:
|
|
223
|
+
return config_params.merge(per_execute).to_call_kwargs()
|
|
224
|
+
if config_params is not None:
|
|
225
|
+
return config_params.to_call_kwargs()
|
|
226
|
+
return per_execute.to_call_kwargs()
|
|
227
|
+
|
|
228
|
+
async def execute(
|
|
229
|
+
self,
|
|
230
|
+
task: Task | Dict[str, Any],
|
|
231
|
+
llm_params: LLMParams | None = None,
|
|
232
|
+
) -> Any:
|
|
233
|
+
"""
|
|
234
|
+
Execute a task using the agent's capabilities.
|
|
235
|
+
|
|
236
|
+
Execution Flow (Gearbox Strategy):
|
|
237
|
+
- Direct mode: Fast, simple, no tools
|
|
238
|
+
- Standard mode: Tool-enabled, linear execution (default)
|
|
239
|
+
- Autonomous mode: Full reasoning loop with planning and self-correction
|
|
240
|
+
|
|
241
|
+
The execution uses:
|
|
242
|
+
- Task: User's request (what to do) - from task.objective
|
|
243
|
+
- Prompt: Agent's instructions (how to behave) - from self.prompt
|
|
244
|
+
- Plan: Task decomposition (how to break down) - optional, from plan()
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
task: Task instance or dictionary with 'id' and 'objective' keys
|
|
248
|
+
llm_params: Optional type-safe per-task LLM parameter overrides.
|
|
249
|
+
Accepts :class:`LLMParams` or any provider subclass
|
|
250
|
+
(e.g. ``OpenAILLMParams``). These override both the LLM-level
|
|
251
|
+
defaults and the ``AgentConfig.llm_params`` for this single
|
|
252
|
+
execution only.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
Execution result (final answer or tool result)
|
|
256
|
+
"""
|
|
257
|
+
# Convert dict to Task if needed (backward compatibility)
|
|
258
|
+
if isinstance(task, dict):
|
|
259
|
+
task = Task.from_dict(task)
|
|
260
|
+
|
|
261
|
+
# Resolve merged LLM params for this execution
|
|
262
|
+
self._current_llm_overrides = self._resolve_llm_params(per_execute=llm_params)
|
|
263
|
+
|
|
264
|
+
self._logger.debug("Starting execution for task %s", task.id)
|
|
265
|
+
self._current_task = task.to_dict() # Store as dict for compat
|
|
266
|
+
|
|
267
|
+
# Ensure plugin manager exists (even if initialize() was not called)
|
|
268
|
+
if self._plugin_manager is None:
|
|
269
|
+
self._plugin_manager = PluginManager(self.plugins)
|
|
270
|
+
self._plugin_manager.reset_counters()
|
|
271
|
+
|
|
272
|
+
# --- BEFORE_AGENT hook ---
|
|
273
|
+
agent_ctx = AgentContext(
|
|
274
|
+
agent_name=self.name,
|
|
275
|
+
task=task,
|
|
276
|
+
state=self.state,
|
|
277
|
+
config=self.config,
|
|
278
|
+
memory=self.memory,
|
|
279
|
+
)
|
|
280
|
+
try:
|
|
281
|
+
agent_ctx = await self._plugin_manager.run_before_agent(agent_ctx)
|
|
282
|
+
except PluginHalt as halt:
|
|
283
|
+
return halt.result
|
|
284
|
+
|
|
285
|
+
# Store user input in memory before execution
|
|
286
|
+
if self.memory:
|
|
287
|
+
user_input = (
|
|
288
|
+
task.objective
|
|
289
|
+
if hasattr(task, "objective")
|
|
290
|
+
else task.to_dict().get("objective", "")
|
|
291
|
+
)
|
|
292
|
+
if user_input:
|
|
293
|
+
await self.memory.aadd_message("user", user_input)
|
|
294
|
+
|
|
295
|
+
# Route to appropriate execution mode (Gearbox Strategy)
|
|
296
|
+
|
|
297
|
+
execution_mode = self.config.execution_mode
|
|
298
|
+
|
|
299
|
+
# Get mode value (handle both enum and string for backward compat)
|
|
300
|
+
mode_value = (
|
|
301
|
+
execution_mode.value
|
|
302
|
+
if hasattr(execution_mode, "value")
|
|
303
|
+
else str(execution_mode)
|
|
304
|
+
)
|
|
305
|
+
self._logger.info(
|
|
306
|
+
"Agent '%s' executing in %s mode",
|
|
307
|
+
self.name,
|
|
308
|
+
mode_value.upper(),
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# Look up mode from registry
|
|
312
|
+
mode_class = self._mode_registry.get(mode_value)
|
|
313
|
+
if not mode_class:
|
|
314
|
+
raise ValueError(f"Unknown execution mode: {execution_mode}")
|
|
315
|
+
|
|
316
|
+
try:
|
|
317
|
+
try:
|
|
318
|
+
result = await mode_class().run(self, task)
|
|
319
|
+
except PluginHalt as halt:
|
|
320
|
+
result = halt.result
|
|
321
|
+
|
|
322
|
+
# --- AFTER_AGENT hook ---
|
|
323
|
+
result = await self._plugin_manager.run_after_agent(agent_ctx, result)
|
|
324
|
+
return result
|
|
325
|
+
finally:
|
|
326
|
+
# Clean up per-execute overrides
|
|
327
|
+
self._current_llm_overrides = {}
|
|
328
|
+
|
|
329
|
+
# ------------------------------------------------------------------ #
|
|
330
|
+
# STRUCTURED OUTPUT HELPERS (cross-cutting, used by all modes) #
|
|
331
|
+
# ------------------------------------------------------------------ #
|
|
332
|
+
|
|
333
|
+
def _resolve_response_format(self):
|
|
334
|
+
"""Resolve response_format to an OutputSchema (or None).
|
|
335
|
+
|
|
336
|
+
Delegates to ``StructuredOutputHandler``.
|
|
337
|
+
"""
|
|
338
|
+
return self._structured_output.resolve_response_format(
|
|
339
|
+
self.response_format, self.llm
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
def _get_structured_output_kwargs(self, output_config: Any) -> Dict[str, Any]:
|
|
343
|
+
"""Build LLM call kwargs for structured output.
|
|
344
|
+
|
|
345
|
+
Delegates to ``StructuredOutputHandler``.
|
|
346
|
+
"""
|
|
347
|
+
return self._structured_output.get_call_kwargs(
|
|
348
|
+
output_config, self.response_format, self.llm
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
def _wrap_structured_output_result(self, response, output_config) -> Any:
|
|
352
|
+
"""Wrap LLM response with structured-output metadata.
|
|
353
|
+
|
|
354
|
+
Delegates to ``StructuredOutputHandler``.
|
|
355
|
+
"""
|
|
356
|
+
return self._structured_output.wrap_result(response, output_config)
|
|
357
|
+
|
|
358
|
+
# ------------------------------------------------------------------ #
|
|
359
|
+
# UTILITY METHODS (stay on Agent) #
|
|
360
|
+
# ------------------------------------------------------------------ #
|
|
361
|
+
|
|
362
|
+
async def _process_result(self, result: Any) -> Any:
|
|
363
|
+
"""Process and store execution results."""
|
|
364
|
+
try:
|
|
365
|
+
if self.memory:
|
|
366
|
+
summary = str(result)[:500] if result else ""
|
|
367
|
+
await self.memory.aadd_message("assistant", summary)
|
|
368
|
+
|
|
369
|
+
# Process through prompt if available and method exists
|
|
370
|
+
if self.prompt:
|
|
371
|
+
process_result = getattr(self.prompt, "process_result", None)
|
|
372
|
+
if process_result and callable(process_result):
|
|
373
|
+
if inspect.iscoroutinefunction(process_result):
|
|
374
|
+
result = await process_result(result)
|
|
375
|
+
else:
|
|
376
|
+
result = process_result(result)
|
|
377
|
+
|
|
378
|
+
return result
|
|
379
|
+
|
|
380
|
+
except Exception as e:
|
|
381
|
+
self._logger.error(f"Result processing failed: {str(e)}")
|
|
382
|
+
raise
|
|
383
|
+
|
|
384
|
+
def _validate_task(self, task: Dict[str, Any]) -> bool:
|
|
385
|
+
"""Validate task format and requirements."""
|
|
386
|
+
required_fields = ["id", "objective"]
|
|
387
|
+
return all(field in task for field in required_fields)
|
|
388
|
+
|
|
389
|
+
async def _execute_tool(self, tool_name: str, params: Dict[str, Any]) -> Any:
|
|
390
|
+
"""Execute a specific tool with parameters."""
|
|
391
|
+
tool = next((t for t in self.tools if t.name == tool_name), None)
|
|
392
|
+
if not tool:
|
|
393
|
+
raise ValueError(f"Tool not found: {tool_name}")
|
|
394
|
+
|
|
395
|
+
self.state = AgentState.WAITING_FOR_TOOLS
|
|
396
|
+
try:
|
|
397
|
+
return await tool.execute(**params)
|
|
398
|
+
finally:
|
|
399
|
+
self.state = AgentState.EXECUTING
|
|
400
|
+
|
|
401
|
+
async def _handle_error(self, error: Exception, context: Dict[str, Any]) -> None:
|
|
402
|
+
"""Handle execution errors with appropriate logging and recovery."""
|
|
403
|
+
self._logger.error(f"Error during execution: {str(error)}")
|
|
404
|
+
|
|
405
|
+
if self.memory:
|
|
406
|
+
await self.memory.aadd_message(
|
|
407
|
+
"system",
|
|
408
|
+
f"Error: {error}",
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
self.metrics.error_count += 1
|
|
412
|
+
self.state = AgentState.ERROR
|
|
413
|
+
|
|
414
|
+
async def save_state(self) -> Dict[str, Any]:
|
|
415
|
+
"""Save agent's current state."""
|
|
416
|
+
state = {
|
|
417
|
+
"id": self.id,
|
|
418
|
+
"name": self.name,
|
|
419
|
+
"state": self.state,
|
|
420
|
+
"metrics": self.metrics.model_dump(),
|
|
421
|
+
"current_task": self._current_task,
|
|
422
|
+
"timestamp": datetime.now().isoformat(),
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if self.memory:
|
|
426
|
+
state["memory"] = await self.memory.aexport_state()
|
|
427
|
+
|
|
428
|
+
return state
|
|
429
|
+
|
|
430
|
+
async def load_state(self, state: Dict[str, Any]) -> None:
|
|
431
|
+
"""Load agent's saved state."""
|
|
432
|
+
self.state = state["state"]
|
|
433
|
+
self.metrics = AgentMetrics(**state["metrics"])
|
|
434
|
+
self._current_task = state["current_task"]
|
|
435
|
+
|
|
436
|
+
if self.memory and "memory" in state:
|
|
437
|
+
await self.memory.aimport_state(state["memory"])
|
|
438
|
+
|
|
439
|
+
self._logger.info(f"Loaded agent state from {state['timestamp']}")
|
|
440
|
+
|
|
441
|
+
async def delegate_task(
|
|
442
|
+
self, task: Dict[str, Any], target_agent: "BaseAgent"
|
|
443
|
+
) -> Any:
|
|
444
|
+
"""Delegate a task to another agent."""
|
|
445
|
+
self._logger.info(
|
|
446
|
+
f"Delegating task to agent to perfoming the task: {target_agent.name}"
|
|
447
|
+
)
|
|
448
|
+
self.state = AgentState.WAITING_FOR_HUMAN
|
|
449
|
+
|
|
450
|
+
try:
|
|
451
|
+
return await target_agent.execute(task)
|
|
452
|
+
finally:
|
|
453
|
+
self.state = AgentState.EXECUTING
|