ovos-agentic-loop 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 (42) hide show
  1. ovos_agentic_loop-0.1.0/PKG-INFO +175 -0
  2. ovos_agentic_loop-0.1.0/README.md +157 -0
  3. ovos_agentic_loop-0.1.0/ovos_agentic_loop/__init__.py +16 -0
  4. ovos_agentic_loop-0.1.0/ovos_agentic_loop/base.py +143 -0
  5. ovos_agentic_loop-0.1.0/ovos_agentic_loop/chain_of_thought.py +182 -0
  6. ovos_agentic_loop-0.1.0/ovos_agentic_loop/context/__init__.py +13 -0
  7. ovos_agentic_loop-0.1.0/ovos_agentic_loop/context/agents_md.py +274 -0
  8. ovos_agentic_loop-0.1.0/ovos_agentic_loop/critic.py +321 -0
  9. ovos_agentic_loop-0.1.0/ovos_agentic_loop/factory.py +93 -0
  10. ovos_agentic_loop-0.1.0/ovos_agentic_loop/plan_execute.py +379 -0
  11. ovos_agentic_loop-0.1.0/ovos_agentic_loop/react.py +341 -0
  12. ovos_agentic_loop-0.1.0/ovos_agentic_loop/reflexion.py +295 -0
  13. ovos_agentic_loop-0.1.0/ovos_agentic_loop/self_ask.py +371 -0
  14. ovos_agentic_loop-0.1.0/ovos_agentic_loop/skills/__init__.py +13 -0
  15. ovos_agentic_loop-0.1.0/ovos_agentic_loop/skills/loader.py +253 -0
  16. ovos_agentic_loop-0.1.0/ovos_agentic_loop/skills/toolbox.py +158 -0
  17. ovos_agentic_loop-0.1.0/ovos_agentic_loop/tools/__init__.py +20 -0
  18. ovos_agentic_loop-0.1.0/ovos_agentic_loop/tools/clock.py +89 -0
  19. ovos_agentic_loop-0.1.0/ovos_agentic_loop/tools/filesystem.py +323 -0
  20. ovos_agentic_loop-0.1.0/ovos_agentic_loop/tools/math.py +745 -0
  21. ovos_agentic_loop-0.1.0/ovos_agentic_loop/tools/shell.py +159 -0
  22. ovos_agentic_loop-0.1.0/ovos_agentic_loop/tools/web.py +128 -0
  23. ovos_agentic_loop-0.1.0/ovos_agentic_loop/tree_of_thoughts.py +363 -0
  24. ovos_agentic_loop-0.1.0/ovos_agentic_loop/version.py +20 -0
  25. ovos_agentic_loop-0.1.0/ovos_agentic_loop.egg-info/PKG-INFO +175 -0
  26. ovos_agentic_loop-0.1.0/ovos_agentic_loop.egg-info/SOURCES.txt +40 -0
  27. ovos_agentic_loop-0.1.0/ovos_agentic_loop.egg-info/dependency_links.txt +1 -0
  28. ovos_agentic_loop-0.1.0/ovos_agentic_loop.egg-info/entry_points.txt +19 -0
  29. ovos_agentic_loop-0.1.0/ovos_agentic_loop.egg-info/requires.txt +9 -0
  30. ovos_agentic_loop-0.1.0/ovos_agentic_loop.egg-info/top_level.txt +1 -0
  31. ovos_agentic_loop-0.1.0/pyproject.toml +51 -0
  32. ovos_agentic_loop-0.1.0/setup.cfg +4 -0
  33. ovos_agentic_loop-0.1.0/test/test_agents_md.py +241 -0
  34. ovos_agentic_loop-0.1.0/test/test_base.py +109 -0
  35. ovos_agentic_loop-0.1.0/test/test_coverage.py +471 -0
  36. ovos_agentic_loop-0.1.0/test/test_loader.py +230 -0
  37. ovos_agentic_loop-0.1.0/test/test_math_toolbox.py +276 -0
  38. ovos_agentic_loop-0.1.0/test/test_new_loops.py +353 -0
  39. ovos_agentic_loop-0.1.0/test/test_new_loops2.py +344 -0
  40. ovos_agentic_loop-0.1.0/test/test_react.py +304 -0
  41. ovos_agentic_loop-0.1.0/test/test_toolbox.py +217 -0
  42. ovos_agentic_loop-0.1.0/test/test_tools.py +303 -0
@@ -0,0 +1,175 @@
1
+ Metadata-Version: 2.4
2
+ Name: ovos-agentic-loop
3
+ Version: 0.1.0
4
+ Summary: AgenticLoopEngine base and ReAct implementation for OVOS, with SKILL.md and AGENTS.md runtime consumption
5
+ Author-email: OpenVoiceOS <openvoiceos@gmail.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/OpenVoiceOS/ovos-agentic-loop
8
+ Project-URL: Repository, https://github.com/OpenVoiceOS/ovos-agentic-loop
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: ovos-plugin-manager<3.0.0,>=2.3.0a1
12
+ Requires-Dist: pydantic>=2.0.0
13
+ Provides-Extra: test
14
+ Requires-Dist: pytest; extra == "test"
15
+ Requires-Dist: pytest-cov; extra == "test"
16
+ Provides-Extra: web
17
+ Requires-Dist: duckduckgo-search>=6.0; extra == "web"
18
+
19
+ # ovos-agentic-loop
20
+
21
+ [![PyPI](https://img.shields.io/pypi/v/ovos-agentic-loop)](https://pypi.org/project/ovos-agentic-loop/)
22
+ [![License](https://img.shields.io/badge/license-Apache%202.0-blue)](LICENSE)
23
+ [![Python](https://img.shields.io/badge/python-%3E%3D3.10-blue)](https://www.python.org/)
24
+
25
+ Agent-loop `ChatEngine` plugins for [OVOS](https://openvoiceos.org). Implements seven agentic reasoning patterns (ReAct, Plan-and-Execute, Reflexion, Self-Ask, Chain-of-Thought, CRITIC, Tree-of-Thoughts), five built-in toolboxes, SKILL.md integration, and AGENTS.md context management — all as standard OPM plugins.
26
+
27
+ ---
28
+
29
+ ## Features
30
+
31
+ - **7 loop architectures** — from simple Chain-of-Thought to full Reflexion and Tree-of-Thoughts beam search
32
+ - **5 built-in toolboxes** — filesystem, shell, web search, clock, math (calculator + unit conversion + statistics)
33
+ - **SKILL.md toolbox** — turns any installed SKILL.md file into an agent tool
34
+ - **AGENTS.md context manager** — loads structured system prompts from AGENTS.md files at runtime
35
+ - **No LLM bundled** — wire any OPM `ChatEngine` as the inner brain
36
+ - **No persona bundled** — compose your own persona using the provided primitives
37
+
38
+ ---
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ pip install ovos-agentic-loop
44
+
45
+ # Optional: web search support
46
+ pip install 'ovos-agentic-loop[web]'
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Quick Start
52
+
53
+ ### Minimal ReAct agent with a calculator
54
+
55
+ ```python
56
+ from ovos_agentic_loop.react import ReActLoopEngine
57
+ from ovos_agentic_loop.tools.math import MathToolBox
58
+ from ovos_plugin_manager.templates.agents import AgentMessage, MessageRole
59
+
60
+ # Wire a local LLM as the brain (any OPM ChatEngine works)
61
+ engine = ReActLoopEngine({
62
+ "brain": "ovos-chat-openai-plugin",
63
+ "ovos-chat-openai-plugin": {
64
+ "api_url": "http://localhost:11434/v1/chat/completions",
65
+ },
66
+ "max_iterations": 8,
67
+ })
68
+ engine.load_toolboxes([MathToolBox()])
69
+
70
+ response = engine.continue_chat([
71
+ AgentMessage(role=MessageRole.USER, content="What is 17 * 23 + sqrt(144)?")
72
+ ])
73
+ print(response.content)
74
+ ```
75
+
76
+ ### Persona JSON (all-in-one config)
77
+
78
+ ```json
79
+ {
80
+ "name": "MyAgent",
81
+ "solvers": ["ovos-react-loop"],
82
+ "ovos-react-loop": {
83
+ "brain": "ovos-chat-openai-plugin",
84
+ "ovos-chat-openai-plugin": {
85
+ "api_url": "http://localhost:11434/v1/chat/completions"
86
+ },
87
+ "toolboxes": ["ovos-math-tools", "ovos-web-search-tools", "ovos-clock-tools"],
88
+ "max_iterations": 10
89
+ }
90
+ }
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Loop Architectures
96
+
97
+ | Entry point | Class | Best for |
98
+ |---|---|---|
99
+ | `ovos-react-loop` | `ReActLoopEngine` | General tool-using Q&A |
100
+ | `ovos-plan-execute-loop` | `PlanAndExecuteEngine` | Multi-step tasks requiring a plan |
101
+ | `ovos-reflexion-loop` | `ReflexionEngine` | Tasks requiring self-critique and retry |
102
+ | `ovos-self-ask-loop` | `SelfAskEngine` | Compositional questions needing sub-questions |
103
+ | `ovos-chain-of-thought-loop` | `ChainOfThoughtEngine` | Reasoning without tools (math, logic) |
104
+ | `ovos-critic-loop` | `CRITICEngine` | Factual tasks requiring claim verification |
105
+ | `ovos-tree-of-thoughts-loop` | `TreeOfThoughtsEngine` | Exploration-heavy problems (beam search) |
106
+
107
+ ---
108
+
109
+ ## Built-in Toolboxes
110
+
111
+ | Entry point | Class | Tools |
112
+ |---|---|---|
113
+ | `ovos-math-tools` | `MathToolBox` | `evaluate_expression`, `unit_convert`, `statistics_summary`, `solve_equation` |
114
+ | `ovos-filesystem-tools` | `FileSystemToolBox` | `read_file`, `write_file`, `list_directory`, `search_in_files`, `find_files` |
115
+ | `ovos-shell-tools` | `ShellToolBox` | `run_command` (disabled by default; set `allow_shell: true` to enable) |
116
+ | `ovos-web-search-tools` | `WebSearchToolBox` | `web_search` (requires `ovos-agentic-loop[web]`) |
117
+ | `ovos-clock-tools` | `ClockToolBox` | `get_current_datetime` |
118
+ | `ovos-skill-md-toolbox` | `SkillMDToolBox` | One tool per installed SKILL.md |
119
+
120
+ ---
121
+
122
+ ## SKILL.md Integration
123
+
124
+ Any package that ships a `SKILL.md` file is automatically discovered and exposed as an agent tool:
125
+
126
+ ```markdown
127
+ ---
128
+ name: my-skill
129
+ description: Does something useful.
130
+ ---
131
+ You are a helpful assistant specialised in...
132
+ ```
133
+
134
+ The tool name is the slugified `name` field; the SKILL.md body becomes the system prompt for a sub-LLM call.
135
+
136
+ ---
137
+
138
+ ## AGENTS.md Context Management
139
+
140
+ `AgentsMDContextManager` assembles system prompts from `AGENTS.md` files at runtime — the same files Claude Code reads at dev-time:
141
+
142
+ ```python
143
+ from ovos_agentic_loop.context.agents_md import AgentsMDContextManager
144
+
145
+ ctx = AgentsMDContextManager({
146
+ "agents_md_sources": ["auto"], # auto-discover from installed packages
147
+ "include_sections": ["Rules", "Style"], # filter to specific headings
148
+ })
149
+ messages = ctx.build_conversation_context(utterance, session_id="s1")
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Security Notes
155
+
156
+ - **`ShellToolBox`** — `allow_shell` defaults to `False`. Only enable with fully-trusted LLMs; the command string is passed directly to `/bin/sh`.
157
+ - **`FileSystemToolBox`** — set `root_path` to restrict file access to a subtree. Without it, the agent can read any world-readable file.
158
+ - **`MathToolBox`** — uses `ast.parse` with an allowlist; `eval()` is never called.
159
+
160
+ ---
161
+
162
+ ## Documentation
163
+
164
+ - [docs/loop-architectures.md](docs/loop-architectures.md) — all 7 loop engines with algorithm details
165
+ - [docs/react-loop.md](docs/react-loop.md) — ReAct prompt format and parsing
166
+ - [docs/toolboxes.md](docs/toolboxes.md) — all toolbox schemas and config keys
167
+ - [docs/skill-md.md](docs/skill-md.md) — SKILL.md authoring and packaging guide
168
+ - [docs/agents-md.md](docs/agents-md.md) — AGENTS.md context manager reference
169
+ - [docs/opm-integration.md](docs/opm-integration.md) — OPM entry points and persona wiring
170
+
171
+ ---
172
+
173
+ ## License
174
+
175
+ Apache 2.0 — see [LICENSE](LICENSE).
@@ -0,0 +1,157 @@
1
+ # ovos-agentic-loop
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/ovos-agentic-loop)](https://pypi.org/project/ovos-agentic-loop/)
4
+ [![License](https://img.shields.io/badge/license-Apache%202.0-blue)](LICENSE)
5
+ [![Python](https://img.shields.io/badge/python-%3E%3D3.10-blue)](https://www.python.org/)
6
+
7
+ Agent-loop `ChatEngine` plugins for [OVOS](https://openvoiceos.org). Implements seven agentic reasoning patterns (ReAct, Plan-and-Execute, Reflexion, Self-Ask, Chain-of-Thought, CRITIC, Tree-of-Thoughts), five built-in toolboxes, SKILL.md integration, and AGENTS.md context management — all as standard OPM plugins.
8
+
9
+ ---
10
+
11
+ ## Features
12
+
13
+ - **7 loop architectures** — from simple Chain-of-Thought to full Reflexion and Tree-of-Thoughts beam search
14
+ - **5 built-in toolboxes** — filesystem, shell, web search, clock, math (calculator + unit conversion + statistics)
15
+ - **SKILL.md toolbox** — turns any installed SKILL.md file into an agent tool
16
+ - **AGENTS.md context manager** — loads structured system prompts from AGENTS.md files at runtime
17
+ - **No LLM bundled** — wire any OPM `ChatEngine` as the inner brain
18
+ - **No persona bundled** — compose your own persona using the provided primitives
19
+
20
+ ---
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pip install ovos-agentic-loop
26
+
27
+ # Optional: web search support
28
+ pip install 'ovos-agentic-loop[web]'
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Quick Start
34
+
35
+ ### Minimal ReAct agent with a calculator
36
+
37
+ ```python
38
+ from ovos_agentic_loop.react import ReActLoopEngine
39
+ from ovos_agentic_loop.tools.math import MathToolBox
40
+ from ovos_plugin_manager.templates.agents import AgentMessage, MessageRole
41
+
42
+ # Wire a local LLM as the brain (any OPM ChatEngine works)
43
+ engine = ReActLoopEngine({
44
+ "brain": "ovos-chat-openai-plugin",
45
+ "ovos-chat-openai-plugin": {
46
+ "api_url": "http://localhost:11434/v1/chat/completions",
47
+ },
48
+ "max_iterations": 8,
49
+ })
50
+ engine.load_toolboxes([MathToolBox()])
51
+
52
+ response = engine.continue_chat([
53
+ AgentMessage(role=MessageRole.USER, content="What is 17 * 23 + sqrt(144)?")
54
+ ])
55
+ print(response.content)
56
+ ```
57
+
58
+ ### Persona JSON (all-in-one config)
59
+
60
+ ```json
61
+ {
62
+ "name": "MyAgent",
63
+ "solvers": ["ovos-react-loop"],
64
+ "ovos-react-loop": {
65
+ "brain": "ovos-chat-openai-plugin",
66
+ "ovos-chat-openai-plugin": {
67
+ "api_url": "http://localhost:11434/v1/chat/completions"
68
+ },
69
+ "toolboxes": ["ovos-math-tools", "ovos-web-search-tools", "ovos-clock-tools"],
70
+ "max_iterations": 10
71
+ }
72
+ }
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Loop Architectures
78
+
79
+ | Entry point | Class | Best for |
80
+ |---|---|---|
81
+ | `ovos-react-loop` | `ReActLoopEngine` | General tool-using Q&A |
82
+ | `ovos-plan-execute-loop` | `PlanAndExecuteEngine` | Multi-step tasks requiring a plan |
83
+ | `ovos-reflexion-loop` | `ReflexionEngine` | Tasks requiring self-critique and retry |
84
+ | `ovos-self-ask-loop` | `SelfAskEngine` | Compositional questions needing sub-questions |
85
+ | `ovos-chain-of-thought-loop` | `ChainOfThoughtEngine` | Reasoning without tools (math, logic) |
86
+ | `ovos-critic-loop` | `CRITICEngine` | Factual tasks requiring claim verification |
87
+ | `ovos-tree-of-thoughts-loop` | `TreeOfThoughtsEngine` | Exploration-heavy problems (beam search) |
88
+
89
+ ---
90
+
91
+ ## Built-in Toolboxes
92
+
93
+ | Entry point | Class | Tools |
94
+ |---|---|---|
95
+ | `ovos-math-tools` | `MathToolBox` | `evaluate_expression`, `unit_convert`, `statistics_summary`, `solve_equation` |
96
+ | `ovos-filesystem-tools` | `FileSystemToolBox` | `read_file`, `write_file`, `list_directory`, `search_in_files`, `find_files` |
97
+ | `ovos-shell-tools` | `ShellToolBox` | `run_command` (disabled by default; set `allow_shell: true` to enable) |
98
+ | `ovos-web-search-tools` | `WebSearchToolBox` | `web_search` (requires `ovos-agentic-loop[web]`) |
99
+ | `ovos-clock-tools` | `ClockToolBox` | `get_current_datetime` |
100
+ | `ovos-skill-md-toolbox` | `SkillMDToolBox` | One tool per installed SKILL.md |
101
+
102
+ ---
103
+
104
+ ## SKILL.md Integration
105
+
106
+ Any package that ships a `SKILL.md` file is automatically discovered and exposed as an agent tool:
107
+
108
+ ```markdown
109
+ ---
110
+ name: my-skill
111
+ description: Does something useful.
112
+ ---
113
+ You are a helpful assistant specialised in...
114
+ ```
115
+
116
+ The tool name is the slugified `name` field; the SKILL.md body becomes the system prompt for a sub-LLM call.
117
+
118
+ ---
119
+
120
+ ## AGENTS.md Context Management
121
+
122
+ `AgentsMDContextManager` assembles system prompts from `AGENTS.md` files at runtime — the same files Claude Code reads at dev-time:
123
+
124
+ ```python
125
+ from ovos_agentic_loop.context.agents_md import AgentsMDContextManager
126
+
127
+ ctx = AgentsMDContextManager({
128
+ "agents_md_sources": ["auto"], # auto-discover from installed packages
129
+ "include_sections": ["Rules", "Style"], # filter to specific headings
130
+ })
131
+ messages = ctx.build_conversation_context(utterance, session_id="s1")
132
+ ```
133
+
134
+ ---
135
+
136
+ ## Security Notes
137
+
138
+ - **`ShellToolBox`** — `allow_shell` defaults to `False`. Only enable with fully-trusted LLMs; the command string is passed directly to `/bin/sh`.
139
+ - **`FileSystemToolBox`** — set `root_path` to restrict file access to a subtree. Without it, the agent can read any world-readable file.
140
+ - **`MathToolBox`** — uses `ast.parse` with an allowlist; `eval()` is never called.
141
+
142
+ ---
143
+
144
+ ## Documentation
145
+
146
+ - [docs/loop-architectures.md](docs/loop-architectures.md) — all 7 loop engines with algorithm details
147
+ - [docs/react-loop.md](docs/react-loop.md) — ReAct prompt format and parsing
148
+ - [docs/toolboxes.md](docs/toolboxes.md) — all toolbox schemas and config keys
149
+ - [docs/skill-md.md](docs/skill-md.md) — SKILL.md authoring and packaging guide
150
+ - [docs/agents-md.md](docs/agents-md.md) — AGENTS.md context manager reference
151
+ - [docs/opm-integration.md](docs/opm-integration.md) — OPM entry points and persona wiring
152
+
153
+ ---
154
+
155
+ ## License
156
+
157
+ Apache 2.0 — see [LICENSE](LICENSE).
@@ -0,0 +1,16 @@
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+
13
+ """ovos-agentic-loop — AgenticLoopEngine base and ReAct implementation."""
14
+ from ovos_agentic_loop.version import __version__
15
+
16
+ __all__ = ["__version__"]
@@ -0,0 +1,143 @@
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+
13
+ """AgenticLoopEngine — base class for agent-loop ChatEngine plugins."""
14
+ import abc
15
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
16
+
17
+ from ovos_plugin_manager.templates.agents import AgentMessage, ChatEngine
18
+ from ovos_utils.log import LOG
19
+
20
+ if TYPE_CHECKING:
21
+ from ovos_plugin_manager.templates.agent_tools import ToolBox
22
+
23
+
24
+ class AgenticLoopEngine(ChatEngine):
25
+ """
26
+ A ``ChatEngine`` subclass for plugins that implement an internal agent loop
27
+ (e.g. ReAct, tool-call/observe cycles, background worker agents).
28
+
29
+ From the perspective of a ``PersonaService`` or any caller, an
30
+ ``AgenticLoopEngine`` is identical to a ``ChatEngine`` — it receives a list
31
+ of ``AgentMessage`` objects and returns one. All loop mechanics, tool
32
+ dispatch, retries, and background tasks are implementation details hidden
33
+ inside the plugin.
34
+
35
+ The ``toolboxes`` attribute gives persona configs a standard place to inject
36
+ ``ToolBox`` instances. Plugins are free to discover and load additional
37
+ toolboxes internally via ``_load_toolboxes_from_config``.
38
+
39
+ Entry point group: ``opm.agents.chat``
40
+ """
41
+
42
+ def __init__(self, config: Optional[Dict[str, Any]] = None) -> None:
43
+ """
44
+ Initialise the engine and optionally load toolboxes from config.
45
+
46
+ Args:
47
+ config: Plugin-specific configuration dictionary. May contain a
48
+ ``"toolboxes"`` key listing toolbox plugin IDs to load.
49
+ """
50
+ super().__init__(config=config)
51
+ self.toolboxes: "List[ToolBox]" = []
52
+ self._load_toolboxes_from_config()
53
+
54
+ def load_toolboxes(self, toolboxes: List[Any]) -> None:
55
+ """
56
+ Register a list of ``ToolBox`` instances with this engine.
57
+
58
+ Replaces any previously registered toolboxes. If the engine already
59
+ has a ``brain`` wired in, it is propagated to toolboxes that expose a
60
+ ``set_brain`` method (e.g. ``SkillMDToolBox``).
61
+
62
+ Args:
63
+ toolboxes: Instantiated ``ToolBox`` objects to make available to
64
+ the agent loop.
65
+ """
66
+ self.toolboxes = list(toolboxes)
67
+ brain = getattr(self, "_brain", None)
68
+ if brain is not None:
69
+ self._inject_brain_into_toolboxes(brain)
70
+
71
+ def _inject_brain_into_toolboxes(self, brain: Any) -> None:
72
+ """
73
+ Propagate a brain ``ChatEngine`` to toolboxes that require one.
74
+
75
+ Calls ``toolbox.set_brain(brain)`` on every registered toolbox that
76
+ exposes a ``set_brain`` method (duck-type check). This ensures that
77
+ toolboxes like ``SkillMDToolBox`` — which use an inner LLM for
78
+ tool-call routing — receive the brain automatically when it is set on
79
+ the engine, regardless of load order.
80
+
81
+ Args:
82
+ brain: The ``ChatEngine`` instance to propagate.
83
+ """
84
+ for tb in self.toolboxes:
85
+ if callable(getattr(tb, "set_brain", None)):
86
+ try:
87
+ tb.set_brain(brain)
88
+ except Exception as exc: # noqa: BLE001
89
+ LOG.warning(
90
+ f"AgenticLoopEngine: failed to inject brain into "
91
+ f"{type(tb).__name__}: {exc}"
92
+ )
93
+
94
+ def _load_toolboxes_from_config(self) -> None:
95
+ """
96
+ Discover and instantiate toolboxes declared in ``config["toolboxes"]``.
97
+
98
+ Reads a list of toolbox plugin IDs from the plugin config, calls OPM to
99
+ find matching ``ToolBox`` plugins, and populates ``self.toolboxes``.
100
+ Brain injection is deferred — toolboxes are wired after the engine's
101
+ ``_brain`` attribute is set (see ``_inject_brain_into_toolboxes``).
102
+ Does nothing if the config key is absent or OPM is unavailable.
103
+ """
104
+ toolbox_ids: List[str] = self.config.get("toolboxes", [])
105
+ if not toolbox_ids:
106
+ return
107
+ try:
108
+ from ovos_plugin_manager.agent_tools import load_toolbox_plugin
109
+ except ImportError:
110
+ LOG.debug("AgenticLoopEngine: ovos_plugin_manager.agent_tools not available; "
111
+ "skipping toolbox auto-load")
112
+ return
113
+
114
+ for tid in toolbox_ids:
115
+ try:
116
+ plugin = load_toolbox_plugin(tid, config=self.config.get(tid, {}))
117
+ if plugin is not None:
118
+ self.toolboxes.append(plugin)
119
+ except Exception as exc: # noqa: BLE001
120
+ LOG.warning(f"AgenticLoopEngine: failed to load toolbox '{tid}': {exc}")
121
+
122
+ @abc.abstractmethod
123
+ def continue_chat(self, messages: List[AgentMessage],
124
+ session_id: str = "default",
125
+ lang: Optional[str] = None,
126
+ units: Optional[str] = None) -> AgentMessage:
127
+ """
128
+ Run the agent loop and return the final response.
129
+
130
+ The implementation is responsible for all internal steps: tool
131
+ selection, execution, observation, and iteration. The caller always
132
+ receives a single ``AgentMessage`` with ``MessageRole.ASSISTANT``.
133
+
134
+ Args:
135
+ messages: Full conversation history including the latest user turn.
136
+ session_id: Conversation session identifier.
137
+ lang: BCP-47 language code.
138
+ units: Preferred measurement system (``"metric"`` / ``"imperial"``).
139
+
140
+ Returns:
141
+ The assistant's final response after the loop has completed.
142
+ """
143
+ raise NotImplementedError()
@@ -0,0 +1,182 @@
1
+ # Copyright 2025, OpenVoiceOS
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """ChainOfThoughtEngine — structured step-by-step reasoning without tool calls.
15
+
16
+ Based on "Chain-of-Thought Prompting Elicits Reasoning in Large Language Models"
17
+ (Wei et al., 2022 — https://arxiv.org/abs/2201.11903) and the follow-up
18
+ zero-shot variant "Large Language Models are Zero-Shot Reasoners" (Kojima et al.,
19
+ 2022 — https://arxiv.org/abs/2205.11916).
20
+
21
+ Algorithm
22
+ ---------
23
+ A single LLM call is made with a system prompt that instructs the model to
24
+ reason step by step before giving its final answer. The final answer is
25
+ extracted from a ``FINAL ANSWER:`` marker; if absent the full response is
26
+ returned.
27
+
28
+ This is the **simplest possible agent loop** — one LLM call, no tools, no
29
+ iteration. It is the recommended baseline for reasoning-heavy tasks that do
30
+ not require external information, and it is the inner building block used by
31
+ all more complex engines.
32
+
33
+ Key differences from ReAct
34
+ --------------------------
35
+ - No tools, no observation loop.
36
+ - Single LLM call — fastest and cheapest.
37
+ - Produces human-readable reasoning traces naturally.
38
+ - Best for arithmetic, logic, common-sense reasoning, multi-step instructions.
39
+ """
40
+ from typing import Any, Dict, List, Optional
41
+
42
+ from ovos_plugin_manager.templates.agents import AgentMessage, ChatEngine, MessageRole
43
+
44
+ from ovos_agentic_loop.base import AgenticLoopEngine
45
+
46
+ _COT_SYSTEM_PROMPT = """\
47
+ Think through the problem step by step before giving your final answer.
48
+
49
+ Format your response as:
50
+ Step 1: <reasoning>
51
+ Step 2: <reasoning>
52
+ ...
53
+ FINAL ANSWER: <concise answer>
54
+
55
+ Rules:
56
+ - Work through every relevant step explicitly.
57
+ - Do not skip steps.
58
+ - Put ONLY the final answer after "FINAL ANSWER:" — no extra reasoning.
59
+ """
60
+
61
+ _FINAL_ANSWER_MARKER = "FINAL ANSWER:"
62
+
63
+
64
+ def _extract_final_answer(text: str) -> Optional[str]:
65
+ """
66
+ Extract the text following ``FINAL ANSWER:`` in LLM output.
67
+
68
+ Args:
69
+ text: Raw LLM output.
70
+
71
+ Returns:
72
+ The answer string, or ``None`` if the marker is absent.
73
+ """
74
+ idx = text.upper().find(_FINAL_ANSWER_MARKER.upper())
75
+ if idx == -1:
76
+ return None
77
+ return text[idx + len(_FINAL_ANSWER_MARKER):].strip()
78
+
79
+
80
+ class ChainOfThoughtEngine(AgenticLoopEngine):
81
+ """
82
+ ``AgenticLoopEngine`` implementing zero-shot Chain-of-Thought prompting.
83
+
84
+ Adds a "think step by step" system prompt to every request and extracts
85
+ the ``FINAL ANSWER:`` from the structured response. No tools, no loop —
86
+ a single LLM call per ``continue_chat`` invocation.
87
+
88
+ This is the recommended baseline for reasoning tasks (arithmetic, logic,
89
+ multi-step instructions) that do not require external information.
90
+ Registered toolboxes are ignored.
91
+
92
+ Config keys:
93
+
94
+ - ``brain`` (str): ChatEngine plugin ID used as the inner LLM.
95
+ - ``system_prompt`` (str): Optional extra system context prepended before
96
+ the CoT instruction.
97
+
98
+ Entry point group: ``opm.agents.chat``
99
+ """
100
+
101
+ def __init__(self, config: Optional[Dict[str, Any]] = None) -> None:
102
+ """
103
+ Initialise the Chain-of-Thought engine.
104
+
105
+ Args:
106
+ config: Plugin configuration dict.
107
+ """
108
+ super().__init__(config=config)
109
+ self._brain: Optional[ChatEngine] = None
110
+
111
+ @property
112
+ def brain(self) -> Optional[ChatEngine]:
113
+ """The inner ChatEngine used for the single LLM call."""
114
+ if self._brain is None:
115
+ self._brain = self._load_brain()
116
+ return self._brain
117
+
118
+ def set_brain(self, brain: ChatEngine) -> None:
119
+ """
120
+ Inject a ChatEngine instance as the inner LLM.
121
+
122
+ Args:
123
+ brain: Instantiated ``ChatEngine``.
124
+ """
125
+ self._brain = brain
126
+ self._inject_brain_into_toolboxes(brain)
127
+
128
+ def _load_brain(self) -> Optional[ChatEngine]:
129
+ """
130
+ Load the brain ChatEngine from config using OPM.
131
+
132
+ Returns:
133
+ Instantiated ``ChatEngine``, or ``None`` if loading fails.
134
+ """
135
+ brain_id: str = self.config.get("brain", "")
136
+ if not brain_id:
137
+ return None
138
+ try:
139
+ from ovos_plugin_manager.agents import load_chat_plugin
140
+ return load_chat_plugin(brain_id, config=self.config.get(brain_id, {}))
141
+ except Exception: # noqa: BLE001
142
+ return None
143
+
144
+ def continue_chat(self, messages: List[AgentMessage],
145
+ session_id: str = "default",
146
+ lang: Optional[str] = None,
147
+ units: Optional[str] = None) -> AgentMessage:
148
+ """
149
+ Run a single CoT-prompted LLM call and return the final answer.
150
+
151
+ Prepends the CoT system instruction (and any ``system_prompt`` config
152
+ value) to the message list, calls the brain once, and extracts the
153
+ ``FINAL ANSWER:`` text. If the marker is absent the full response is
154
+ returned as-is.
155
+
156
+ Args:
157
+ messages: Conversation history including the latest user turn.
158
+ session_id: Session identifier forwarded to the brain.
159
+ lang: BCP-47 language code forwarded to the brain.
160
+ units: Measurement system forwarded to the brain.
161
+
162
+ Returns:
163
+ ``AgentMessage`` with ``MessageRole.ASSISTANT`` containing the
164
+ extracted final answer or the full CoT response.
165
+ """
166
+ if self.brain is None:
167
+ return AgentMessage(role=MessageRole.ASSISTANT,
168
+ content="Error: no brain configured.")
169
+
170
+ extra_prompt = self.config.get("system_prompt", "")
171
+ system_content = (extra_prompt + "\n\n" if extra_prompt else "") + _COT_SYSTEM_PROMPT
172
+
173
+ loop_messages = [
174
+ AgentMessage(role=MessageRole.SYSTEM, content=system_content),
175
+ *messages,
176
+ ]
177
+ response = self.brain.continue_chat(
178
+ loop_messages, session_id=session_id, lang=lang, units=units
179
+ )
180
+ final = _extract_final_answer(response.content)
181
+ content = final if final is not None else response.content
182
+ return AgentMessage(role=MessageRole.ASSISTANT, content=content)
@@ -0,0 +1,13 @@
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+
13
+ """AGENTS.md context manager integration."""