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.
- ovos_agentic_loop-0.1.0/PKG-INFO +175 -0
- ovos_agentic_loop-0.1.0/README.md +157 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop/__init__.py +16 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop/base.py +143 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop/chain_of_thought.py +182 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop/context/__init__.py +13 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop/context/agents_md.py +274 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop/critic.py +321 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop/factory.py +93 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop/plan_execute.py +379 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop/react.py +341 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop/reflexion.py +295 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop/self_ask.py +371 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop/skills/__init__.py +13 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop/skills/loader.py +253 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop/skills/toolbox.py +158 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop/tools/__init__.py +20 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop/tools/clock.py +89 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop/tools/filesystem.py +323 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop/tools/math.py +745 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop/tools/shell.py +159 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop/tools/web.py +128 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop/tree_of_thoughts.py +363 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop/version.py +20 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop.egg-info/PKG-INFO +175 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop.egg-info/SOURCES.txt +40 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop.egg-info/dependency_links.txt +1 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop.egg-info/entry_points.txt +19 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop.egg-info/requires.txt +9 -0
- ovos_agentic_loop-0.1.0/ovos_agentic_loop.egg-info/top_level.txt +1 -0
- ovos_agentic_loop-0.1.0/pyproject.toml +51 -0
- ovos_agentic_loop-0.1.0/setup.cfg +4 -0
- ovos_agentic_loop-0.1.0/test/test_agents_md.py +241 -0
- ovos_agentic_loop-0.1.0/test/test_base.py +109 -0
- ovos_agentic_loop-0.1.0/test/test_coverage.py +471 -0
- ovos_agentic_loop-0.1.0/test/test_loader.py +230 -0
- ovos_agentic_loop-0.1.0/test/test_math_toolbox.py +276 -0
- ovos_agentic_loop-0.1.0/test/test_new_loops.py +353 -0
- ovos_agentic_loop-0.1.0/test/test_new_loops2.py +344 -0
- ovos_agentic_loop-0.1.0/test/test_react.py +304 -0
- ovos_agentic_loop-0.1.0/test/test_toolbox.py +217 -0
- 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
|
+
[](https://pypi.org/project/ovos-agentic-loop/)
|
|
22
|
+
[](LICENSE)
|
|
23
|
+
[](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
|
+
[](https://pypi.org/project/ovos-agentic-loop/)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](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."""
|