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.
Files changed (91) hide show
  1. nucleusiq-0.1.0/PKG-INFO +57 -0
  2. nucleusiq-0.1.0/README.md +23 -0
  3. nucleusiq-0.1.0/core/__init__.py +24 -0
  4. nucleusiq-0.1.0/core/agents/__init__.py +22 -0
  5. nucleusiq-0.1.0/core/agents/agent.py +453 -0
  6. nucleusiq-0.1.0/core/agents/builder/__init__.py +9 -0
  7. nucleusiq-0.1.0/core/agents/builder/base_agent.py +251 -0
  8. nucleusiq-0.1.0/core/agents/chat_models.py +119 -0
  9. nucleusiq-0.1.0/core/agents/components/__init__.py +18 -0
  10. nucleusiq-0.1.0/core/agents/components/critic.py +788 -0
  11. nucleusiq-0.1.0/core/agents/components/decomposer.py +318 -0
  12. nucleusiq-0.1.0/core/agents/components/executor.py +135 -0
  13. nucleusiq-0.1.0/core/agents/components/progress.py +80 -0
  14. nucleusiq-0.1.0/core/agents/components/refiner.py +528 -0
  15. nucleusiq-0.1.0/core/agents/components/validation.py +245 -0
  16. nucleusiq-0.1.0/core/agents/config/__init__.py +10 -0
  17. nucleusiq-0.1.0/core/agents/config/agent_config.py +145 -0
  18. nucleusiq-0.1.0/core/agents/execution_context.py +60 -0
  19. nucleusiq-0.1.0/core/agents/messaging/__init__.py +5 -0
  20. nucleusiq-0.1.0/core/agents/messaging/message_builder.py +157 -0
  21. nucleusiq-0.1.0/core/agents/modes/__init__.py +21 -0
  22. nucleusiq-0.1.0/core/agents/modes/autonomous_mode.py +270 -0
  23. nucleusiq-0.1.0/core/agents/modes/base_mode.py +276 -0
  24. nucleusiq-0.1.0/core/agents/modes/direct_mode.py +73 -0
  25. nucleusiq-0.1.0/core/agents/modes/standard_mode.py +255 -0
  26. nucleusiq-0.1.0/core/agents/plan.py +197 -0
  27. nucleusiq-0.1.0/core/agents/planning/__init__.py +19 -0
  28. nucleusiq-0.1.0/core/agents/planning/plan_creator.py +249 -0
  29. nucleusiq-0.1.0/core/agents/planning/plan_executor.py +409 -0
  30. nucleusiq-0.1.0/core/agents/planning/plan_parser.py +151 -0
  31. nucleusiq-0.1.0/core/agents/planning/planner.py +147 -0
  32. nucleusiq-0.1.0/core/agents/planning/prompt_strategy.py +151 -0
  33. nucleusiq-0.1.0/core/agents/planning/schema.py +60 -0
  34. nucleusiq-0.1.0/core/agents/react_agent.py +341 -0
  35. nucleusiq-0.1.0/core/agents/structured_output/__init__.py +77 -0
  36. nucleusiq-0.1.0/core/agents/structured_output/config.py +179 -0
  37. nucleusiq-0.1.0/core/agents/structured_output/errors.py +146 -0
  38. nucleusiq-0.1.0/core/agents/structured_output/handler.py +106 -0
  39. nucleusiq-0.1.0/core/agents/structured_output/parser.py +480 -0
  40. nucleusiq-0.1.0/core/agents/structured_output/resolver.py +156 -0
  41. nucleusiq-0.1.0/core/agents/structured_output/types.py +112 -0
  42. nucleusiq-0.1.0/core/agents/task.py +60 -0
  43. nucleusiq-0.1.0/core/llms/__init__.py +7 -0
  44. nucleusiq-0.1.0/core/llms/base.py +76 -0
  45. nucleusiq-0.1.0/core/llms/base_llm.py +87 -0
  46. nucleusiq-0.1.0/core/llms/llm_params.py +123 -0
  47. nucleusiq-0.1.0/core/llms/mock_llm.py +157 -0
  48. nucleusiq-0.1.0/core/memory/__init__.py +24 -0
  49. nucleusiq-0.1.0/core/memory/base.py +159 -0
  50. nucleusiq-0.1.0/core/memory/factory.py +99 -0
  51. nucleusiq-0.1.0/core/memory/full_history.py +42 -0
  52. nucleusiq-0.1.0/core/memory/sliding_window.py +54 -0
  53. nucleusiq-0.1.0/core/memory/summary.py +140 -0
  54. nucleusiq-0.1.0/core/memory/summary_window.py +161 -0
  55. nucleusiq-0.1.0/core/memory/token_budget.py +86 -0
  56. nucleusiq-0.1.0/core/plugins/__init__.py +89 -0
  57. nucleusiq-0.1.0/core/plugins/base.py +330 -0
  58. nucleusiq-0.1.0/core/plugins/builtin/__init__.py +31 -0
  59. nucleusiq-0.1.0/core/plugins/builtin/context_window.py +185 -0
  60. nucleusiq-0.1.0/core/plugins/builtin/human_approval.py +277 -0
  61. nucleusiq-0.1.0/core/plugins/builtin/model_call_limit.py +33 -0
  62. nucleusiq-0.1.0/core/plugins/builtin/model_fallback.py +76 -0
  63. nucleusiq-0.1.0/core/plugins/builtin/pii_guard.py +253 -0
  64. nucleusiq-0.1.0/core/plugins/builtin/result_validator.py +60 -0
  65. nucleusiq-0.1.0/core/plugins/builtin/tool_call_limit.py +35 -0
  66. nucleusiq-0.1.0/core/plugins/builtin/tool_guard.py +96 -0
  67. nucleusiq-0.1.0/core/plugins/builtin/tool_retry.py +61 -0
  68. nucleusiq-0.1.0/core/plugins/decorators.py +259 -0
  69. nucleusiq-0.1.0/core/plugins/errors.py +31 -0
  70. nucleusiq-0.1.0/core/plugins/manager.py +179 -0
  71. nucleusiq-0.1.0/core/prompts/__init__.py +9 -0
  72. nucleusiq-0.1.0/core/prompts/auto_chain_of_thought.py +191 -0
  73. nucleusiq-0.1.0/core/prompts/base.py +195 -0
  74. nucleusiq-0.1.0/core/prompts/chain_of_thought.py +121 -0
  75. nucleusiq-0.1.0/core/prompts/factory.py +85 -0
  76. nucleusiq-0.1.0/core/prompts/few_shot.py +195 -0
  77. nucleusiq-0.1.0/core/prompts/meta_prompt.py +321 -0
  78. nucleusiq-0.1.0/core/prompts/prompt_composer.py +137 -0
  79. nucleusiq-0.1.0/core/prompts/retrieval_augmented_generation.py +98 -0
  80. nucleusiq-0.1.0/core/prompts/zero_shot.py +92 -0
  81. nucleusiq-0.1.0/core/tools/__init__.py +5 -0
  82. nucleusiq-0.1.0/core/tools/base_tool.py +282 -0
  83. nucleusiq-0.1.0/core/utilities/__init__.py +9 -0
  84. nucleusiq-0.1.0/core/utilities/clustering.py +49 -0
  85. nucleusiq-0.1.0/nucleusiq.egg-info/PKG-INFO +57 -0
  86. nucleusiq-0.1.0/nucleusiq.egg-info/SOURCES.txt +89 -0
  87. nucleusiq-0.1.0/nucleusiq.egg-info/dependency_links.txt +1 -0
  88. nucleusiq-0.1.0/nucleusiq.egg-info/requires.txt +13 -0
  89. nucleusiq-0.1.0/nucleusiq.egg-info/top_level.txt +1 -0
  90. nucleusiq-0.1.0/pyproject.toml +146 -0
  91. nucleusiq-0.1.0/setup.cfg +4 -0
@@ -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
@@ -0,0 +1,9 @@
1
+ """
2
+ Agent builder framework for NucleusIQ.
3
+
4
+ This package provides base classes and builders for creating custom agents.
5
+ """
6
+
7
+ from nucleusiq.agents.builder.base_agent import BaseAgent
8
+
9
+ __all__ = ["BaseAgent"]