agent-patterns-py 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.
- agent_patterns_py-0.1.0/.env.example +6 -0
- agent_patterns_py-0.1.0/.gitignore +45 -0
- agent_patterns_py-0.1.0/LICENSE +21 -0
- agent_patterns_py-0.1.0/PKG-INFO +92 -0
- agent_patterns_py-0.1.0/README.md +76 -0
- agent_patterns_py-0.1.0/docs/agent-language.md +74 -0
- agent_patterns_py-0.1.0/docs/capabilities.md +79 -0
- agent_patterns_py-0.1.0/docs/dependency-injection.md +86 -0
- agent_patterns_py-0.1.0/docs/models.md +57 -0
- agent_patterns_py-0.1.0/docs/multi-agent.md +77 -0
- agent_patterns_py-0.1.0/docs/safety.md +85 -0
- agent_patterns_py-0.1.0/examples/specialist_personas.py +167 -0
- agent_patterns_py-0.1.0/examples/time_aware_capability.py +74 -0
- agent_patterns_py-0.1.0/pyproject.toml +42 -0
- agent_patterns_py-0.1.0/src/agent_patterns/__init__.py +98 -0
- agent_patterns_py-0.1.0/src/agent_patterns/capabilities/__init__.py +9 -0
- agent_patterns_py-0.1.0/src/agent_patterns/capabilities/base.py +109 -0
- agent_patterns_py-0.1.0/src/agent_patterns/capabilities/plan_first.py +62 -0
- agent_patterns_py-0.1.0/src/agent_patterns/capabilities/progress_tracking.py +64 -0
- agent_patterns_py-0.1.0/src/agent_patterns/core/__init__.py +17 -0
- agent_patterns_py-0.1.0/src/agent_patterns/core/action.py +62 -0
- agent_patterns_py-0.1.0/src/agent_patterns/core/agent.py +240 -0
- agent_patterns_py-0.1.0/src/agent_patterns/core/context.py +54 -0
- agent_patterns_py-0.1.0/src/agent_patterns/core/environment.py +99 -0
- agent_patterns_py-0.1.0/src/agent_patterns/core/goal.py +22 -0
- agent_patterns_py-0.1.0/src/agent_patterns/core/memory.py +34 -0
- agent_patterns_py-0.1.0/src/agent_patterns/language/__init__.py +10 -0
- agent_patterns_py-0.1.0/src/agent_patterns/language/base.py +81 -0
- agent_patterns_py-0.1.0/src/agent_patterns/language/function_calling.py +92 -0
- agent_patterns_py-0.1.0/src/agent_patterns/language/json_action.py +103 -0
- agent_patterns_py-0.1.0/src/agent_patterns/multi_agent/__init__.py +3 -0
- agent_patterns_py-0.1.0/src/agent_patterns/multi_agent/registry.py +32 -0
- agent_patterns_py-0.1.0/src/agent_patterns/safety/__init__.py +8 -0
- agent_patterns_py-0.1.0/src/agent_patterns/safety/reversible.py +111 -0
- agent_patterns_py-0.1.0/src/agent_patterns/safety/staged.py +95 -0
- agent_patterns_py-0.1.0/src/agent_patterns/tools/__init__.py +15 -0
- agent_patterns_py-0.1.0/src/agent_patterns/tools/agent_tools.py +72 -0
- agent_patterns_py-0.1.0/src/agent_patterns/tools/persona.py +100 -0
- agent_patterns_py-0.1.0/src/agent_patterns/tools/planning.py +116 -0
- agent_patterns_py-0.1.0/src/agent_patterns/tools/registry.py +230 -0
- agent_patterns_py-0.1.0/src/agent_patterns/utils/__init__.py +8 -0
- agent_patterns_py-0.1.0/src/agent_patterns/utils/llm.py +100 -0
- agent_patterns_py-0.1.0/tests/__init__.py +0 -0
- agent_patterns_py-0.1.0/uv.lock +2556 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
*.so
|
|
7
|
+
*.egg
|
|
8
|
+
*.egg-info/
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
.eggs/
|
|
12
|
+
|
|
13
|
+
# Virtual environments
|
|
14
|
+
.venv/
|
|
15
|
+
venv/
|
|
16
|
+
env/
|
|
17
|
+
|
|
18
|
+
# uv
|
|
19
|
+
.uv/
|
|
20
|
+
|
|
21
|
+
# Environment variables
|
|
22
|
+
.env
|
|
23
|
+
.env.*
|
|
24
|
+
!.env.example
|
|
25
|
+
|
|
26
|
+
# IDE
|
|
27
|
+
.vscode/
|
|
28
|
+
.idea/
|
|
29
|
+
*.swp
|
|
30
|
+
*.swo
|
|
31
|
+
|
|
32
|
+
# Testing & coverage
|
|
33
|
+
.pytest_cache/
|
|
34
|
+
.coverage
|
|
35
|
+
htmlcov/
|
|
36
|
+
.mypy_cache/
|
|
37
|
+
.ruff_cache/
|
|
38
|
+
|
|
39
|
+
# Jupyter
|
|
40
|
+
.ipynb_checkpoints/
|
|
41
|
+
*.ipynb_checkpoints
|
|
42
|
+
|
|
43
|
+
# OS
|
|
44
|
+
.DS_Store
|
|
45
|
+
Thumbs.db
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Moiz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-patterns-py
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Production-ready boilerplate for common agentic AI patterns in Python
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: litellm>=1.0
|
|
8
|
+
Requires-Dist: python-dotenv>=1.0
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: ipykernel>=6.0; extra == 'dev'
|
|
11
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
12
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
13
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
14
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# agent-patterns
|
|
18
|
+
|
|
19
|
+
A Python framework for building production-ready AI agents using the GAME pattern (Goals, Actions, Memory, Environment).
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
git clone https://github.com/your-username/agent-patterns.git
|
|
27
|
+
cd agent-patterns
|
|
28
|
+
uv sync
|
|
29
|
+
cp .env.example .env # add your API keys
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Quick start
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
import os
|
|
38
|
+
from agent_patterns import (
|
|
39
|
+
Agent, Goal,
|
|
40
|
+
PythonEnvironment,
|
|
41
|
+
AgentFunctionCallingActionLanguage,
|
|
42
|
+
PythonActionRegistry,
|
|
43
|
+
register_tool,
|
|
44
|
+
make_generate_response,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
@register_tool(tags=["files"])
|
|
48
|
+
def list_files() -> list:
|
|
49
|
+
"""List files in the current directory."""
|
|
50
|
+
return os.listdir(".")
|
|
51
|
+
|
|
52
|
+
@register_tool(tags=["files"])
|
|
53
|
+
def read_file(name: str) -> str:
|
|
54
|
+
"""Read a file and return its contents."""
|
|
55
|
+
with open(name) as f:
|
|
56
|
+
return f.read()
|
|
57
|
+
|
|
58
|
+
@register_tool(tags=["system"], terminal=True)
|
|
59
|
+
def terminate(message: str) -> str:
|
|
60
|
+
"""End the session with a final message."""
|
|
61
|
+
return message
|
|
62
|
+
|
|
63
|
+
agent = Agent(
|
|
64
|
+
goals=[
|
|
65
|
+
Goal(1, "Explore", "List and read files in the current directory."),
|
|
66
|
+
Goal(2, "Terminate", "Call terminate with a summary when done."),
|
|
67
|
+
],
|
|
68
|
+
agent_language=AgentFunctionCallingActionLanguage(),
|
|
69
|
+
action_registry=PythonActionRegistry(tags=["files", "system"]),
|
|
70
|
+
generate_response=make_generate_response("openai/gpt-4o"),
|
|
71
|
+
environment=PythonEnvironment(),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
memory = agent.run("What Python files are here and what do they do?")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Docs
|
|
80
|
+
|
|
81
|
+
- [Switching models](docs/models.md)
|
|
82
|
+
- [Agent language strategies](docs/agent-language.md)
|
|
83
|
+
- [Capabilities](docs/capabilities.md)
|
|
84
|
+
- [Dependency injection](docs/dependency-injection.md)
|
|
85
|
+
- [Multi-agent](docs/multi-agent.md)
|
|
86
|
+
- [Safety patterns](docs/safety.md)
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
MIT
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# agent-patterns
|
|
2
|
+
|
|
3
|
+
A Python framework for building production-ready AI agents using the GAME pattern (Goals, Actions, Memory, Environment).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
git clone https://github.com/your-username/agent-patterns.git
|
|
11
|
+
cd agent-patterns
|
|
12
|
+
uv sync
|
|
13
|
+
cp .env.example .env # add your API keys
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Quick start
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
import os
|
|
22
|
+
from agent_patterns import (
|
|
23
|
+
Agent, Goal,
|
|
24
|
+
PythonEnvironment,
|
|
25
|
+
AgentFunctionCallingActionLanguage,
|
|
26
|
+
PythonActionRegistry,
|
|
27
|
+
register_tool,
|
|
28
|
+
make_generate_response,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
@register_tool(tags=["files"])
|
|
32
|
+
def list_files() -> list:
|
|
33
|
+
"""List files in the current directory."""
|
|
34
|
+
return os.listdir(".")
|
|
35
|
+
|
|
36
|
+
@register_tool(tags=["files"])
|
|
37
|
+
def read_file(name: str) -> str:
|
|
38
|
+
"""Read a file and return its contents."""
|
|
39
|
+
with open(name) as f:
|
|
40
|
+
return f.read()
|
|
41
|
+
|
|
42
|
+
@register_tool(tags=["system"], terminal=True)
|
|
43
|
+
def terminate(message: str) -> str:
|
|
44
|
+
"""End the session with a final message."""
|
|
45
|
+
return message
|
|
46
|
+
|
|
47
|
+
agent = Agent(
|
|
48
|
+
goals=[
|
|
49
|
+
Goal(1, "Explore", "List and read files in the current directory."),
|
|
50
|
+
Goal(2, "Terminate", "Call terminate with a summary when done."),
|
|
51
|
+
],
|
|
52
|
+
agent_language=AgentFunctionCallingActionLanguage(),
|
|
53
|
+
action_registry=PythonActionRegistry(tags=["files", "system"]),
|
|
54
|
+
generate_response=make_generate_response("openai/gpt-4o"),
|
|
55
|
+
environment=PythonEnvironment(),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
memory = agent.run("What Python files are here and what do they do?")
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Docs
|
|
64
|
+
|
|
65
|
+
- [Switching models](docs/models.md)
|
|
66
|
+
- [Agent language strategies](docs/agent-language.md)
|
|
67
|
+
- [Capabilities](docs/capabilities.md)
|
|
68
|
+
- [Dependency injection](docs/dependency-injection.md)
|
|
69
|
+
- [Multi-agent](docs/multi-agent.md)
|
|
70
|
+
- [Safety patterns](docs/safety.md)
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
MIT
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Agent language strategies
|
|
2
|
+
|
|
3
|
+
`AgentLanguage` controls two things:
|
|
4
|
+
|
|
5
|
+
1. **How GAME components are formatted into a prompt** sent to the LLM
|
|
6
|
+
2. **How the LLM's response is parsed** back into an action
|
|
7
|
+
|
|
8
|
+
Swapping the language changes the prompting strategy without touching the agent loop.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Function calling (recommended)
|
|
13
|
+
|
|
14
|
+
Uses the LLM's native tool/function calling API. The LLM returns a structured tool call — no text parsing needed.
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from agent_patterns import AgentFunctionCallingActionLanguage
|
|
18
|
+
|
|
19
|
+
agent = Agent(
|
|
20
|
+
...
|
|
21
|
+
agent_language=AgentFunctionCallingActionLanguage(),
|
|
22
|
+
)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**When to use:** Almost always. More reliable, no prompt engineering needed to get structured output.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## JSON action blocks
|
|
30
|
+
|
|
31
|
+
The LLM outputs free-form reasoning text and embeds its action in a fenced ` ```action ``` ` block:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
I'll start by listing the files.
|
|
35
|
+
|
|
36
|
+
```action
|
|
37
|
+
{
|
|
38
|
+
"tool": "list_files",
|
|
39
|
+
"args": {}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from agent_patterns import AgentJsonActionLanguage
|
|
45
|
+
|
|
46
|
+
agent = Agent(
|
|
47
|
+
...
|
|
48
|
+
agent_language=AgentJsonActionLanguage(),
|
|
49
|
+
)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**When to use:**
|
|
53
|
+
- The model doesn't support native function calling
|
|
54
|
+
- You want the agent's reasoning visible in memory
|
|
55
|
+
- You need a model-agnostic fallback
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Custom language
|
|
60
|
+
|
|
61
|
+
Subclass `AgentLanguage` and implement two methods:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from agent_patterns import AgentLanguage, Prompt
|
|
65
|
+
|
|
66
|
+
class MyLanguage(AgentLanguage):
|
|
67
|
+
def construct_prompt(self, actions, environment, goals, memory) -> Prompt:
|
|
68
|
+
# build and return a Prompt however you like
|
|
69
|
+
...
|
|
70
|
+
|
|
71
|
+
def parse_response(self, response: str) -> dict:
|
|
72
|
+
# return {"tool": "...", "args": {...}}
|
|
73
|
+
...
|
|
74
|
+
```
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Capabilities
|
|
2
|
+
|
|
3
|
+
Capabilities extend the agent loop at specific lifecycle points without modifying the `Agent` class. Pass a list of them when creating an agent:
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from agent_patterns import Agent, PlanFirstCapability, ProgressTrackingCapability
|
|
7
|
+
|
|
8
|
+
agent = Agent(
|
|
9
|
+
...
|
|
10
|
+
capabilities=[
|
|
11
|
+
PlanFirstCapability(),
|
|
12
|
+
ProgressTrackingCapability(track_frequency=3),
|
|
13
|
+
],
|
|
14
|
+
)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Built-in capabilities
|
|
20
|
+
|
|
21
|
+
### PlanFirstCapability
|
|
22
|
+
|
|
23
|
+
Before the agent takes its first action, generates a detailed step-by-step execution plan and stores it in memory. Every subsequent prompt includes the plan, keeping the agent on track for multi-step tasks.
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from agent_patterns import PlanFirstCapability
|
|
27
|
+
|
|
28
|
+
PlanFirstCapability(
|
|
29
|
+
plan_memory_type="system", # memory type for the plan entry (default: "system")
|
|
30
|
+
)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
### ProgressTrackingCapability
|
|
36
|
+
|
|
37
|
+
At the end of every N iterations, generates a progress report (what's done, any blockers, recommended next steps) and adds it to memory. Useful for long-running tasks where the agent needs to self-correct.
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from agent_patterns import ProgressTrackingCapability
|
|
41
|
+
|
|
42
|
+
ProgressTrackingCapability(
|
|
43
|
+
memory_type="system", # memory type for report entries (default: "system")
|
|
44
|
+
track_frequency=1, # generate a report every N iterations (default: 1)
|
|
45
|
+
)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Writing your own
|
|
51
|
+
|
|
52
|
+
Subclass `Capability` and override only the hooks you need:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from agent_patterns import Capability
|
|
56
|
+
|
|
57
|
+
class LoggingCapability(Capability):
|
|
58
|
+
def __init__(self):
|
|
59
|
+
super().__init__(name="Logging", description="Logs every action taken.")
|
|
60
|
+
|
|
61
|
+
def process_action(self, agent, action_context, action):
|
|
62
|
+
print(f"[LOG] action={action['tool']} args={action.get('args')}")
|
|
63
|
+
return action # always return the (optionally modified) value
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Available hooks
|
|
67
|
+
|
|
68
|
+
| Method | Called when | Return |
|
|
69
|
+
|--------|------------|--------|
|
|
70
|
+
| `init` | Once, before the loop starts | — |
|
|
71
|
+
| `start_agent_loop` | Start of each iteration | `False` to stop the loop |
|
|
72
|
+
| `process_prompt` | Before the prompt is sent to the LLM | modified `Prompt` |
|
|
73
|
+
| `process_response` | After the LLM responds, before parsing | modified response `str` |
|
|
74
|
+
| `process_action` | After parsing, before execution | modified action `dict` |
|
|
75
|
+
| `process_result` | After execution | modified result |
|
|
76
|
+
| `process_new_memories` | Before memory entries are committed | modified `list[dict]` |
|
|
77
|
+
| `end_agent_loop` | End of each iteration | — |
|
|
78
|
+
| `should_terminate` | After memory update | `True` to stop the loop |
|
|
79
|
+
| `terminate` | Once, when the agent stops | — |
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Dependency injection
|
|
2
|
+
|
|
3
|
+
Tools often need access to things beyond their explicit arguments — a database connection, an auth token, the agent's memory. Rather than hardcoding these or passing them through the LLM, the framework injects them automatically based on parameter naming conventions.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## How it works
|
|
8
|
+
|
|
9
|
+
When `PythonEnvironment` executes a tool, it inspects the function's parameters and injects matching values before calling it. Three conventions are supported:
|
|
10
|
+
|
|
11
|
+
| Parameter name | What gets injected |
|
|
12
|
+
|---------------|-------------------|
|
|
13
|
+
| `action_context` | The live `ActionContext` for this run |
|
|
14
|
+
| `action_agent` | The `Agent` instance |
|
|
15
|
+
| `_<key>` | `action_context.properties[key]` |
|
|
16
|
+
|
|
17
|
+
These parameters are **hidden from the LLM** — they never appear in the tool's JSON schema.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## action_context
|
|
22
|
+
|
|
23
|
+
Use this when you need direct access to the context object:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from agent_patterns import register_tool, ActionContext
|
|
27
|
+
|
|
28
|
+
@register_tool(tags=["llm"])
|
|
29
|
+
def summarise(action_context: ActionContext, text: str) -> str:
|
|
30
|
+
"""Summarise the given text."""
|
|
31
|
+
generate = action_context.get("llm")
|
|
32
|
+
return generate(Prompt(messages=[
|
|
33
|
+
{"role": "user", "content": f"Summarise: {text}"}
|
|
34
|
+
]))
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## _prefixed parameters
|
|
40
|
+
|
|
41
|
+
For injecting specific values from the context, prefix the parameter name with `_`:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
@register_tool(tags=["db"])
|
|
45
|
+
def query(action_context: ActionContext, sql: str, _db_conn) -> list:
|
|
46
|
+
"""Run a SQL query."""
|
|
47
|
+
return _db_conn.execute(sql).fetchall()
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Then pass `db_conn` (without the underscore) when running the agent:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
agent.run(
|
|
54
|
+
"Find all users created this month.",
|
|
55
|
+
action_context_props={"db_conn": my_db_connection},
|
|
56
|
+
)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`_db_conn` maps to `action_context.properties["db_conn"]` automatically.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Common injected values
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
agent.run(
|
|
67
|
+
"Process the request.",
|
|
68
|
+
action_context_props={
|
|
69
|
+
"auth_token": "Bearer abc123", # → _auth_token
|
|
70
|
+
"db_conn": db, # → _db_conn
|
|
71
|
+
"user_config": config, # → _user_config
|
|
72
|
+
"agent_registry": registry, # → used by call_agent
|
|
73
|
+
},
|
|
74
|
+
)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Using base Environment
|
|
80
|
+
|
|
81
|
+
If you use the base `Environment` instead of `PythonEnvironment`, no injection happens — tools receive only the arguments the LLM explicitly provided.
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from agent_patterns import Environment # no DI
|
|
85
|
+
from agent_patterns import PythonEnvironment # with DI
|
|
86
|
+
```
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Switching models
|
|
2
|
+
|
|
3
|
+
The framework uses [LiteLLM](https://docs.litellm.ai) under the hood, so any model LiteLLM supports works out of the box.
|
|
4
|
+
|
|
5
|
+
Use `make_generate_response` to create a callable bound to a specific model and pass it to your agent:
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from agent_patterns import make_generate_response, Agent
|
|
9
|
+
|
|
10
|
+
agent = Agent(
|
|
11
|
+
...
|
|
12
|
+
generate_response=make_generate_response("openai/gpt-4o"),
|
|
13
|
+
)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Examples
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
# OpenAI
|
|
20
|
+
make_generate_response("openai/gpt-4o")
|
|
21
|
+
make_generate_response("openai/gpt-4o-mini")
|
|
22
|
+
|
|
23
|
+
# Anthropic
|
|
24
|
+
make_generate_response("anthropic/claude-opus-4-5")
|
|
25
|
+
make_generate_response("anthropic/claude-sonnet-4-5")
|
|
26
|
+
|
|
27
|
+
# Any other LiteLLM-supported provider
|
|
28
|
+
make_generate_response("groq/llama-3.1-70b-versatile")
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Different models per agent
|
|
32
|
+
|
|
33
|
+
Each agent gets its own `generate_response` callable, so you can mix models freely:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
fast_agent = Agent(
|
|
37
|
+
...
|
|
38
|
+
generate_response=make_generate_response("openai/gpt-4o-mini"),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
smart_agent = Agent(
|
|
42
|
+
...
|
|
43
|
+
generate_response=make_generate_response("anthropic/claude-opus-4-5"),
|
|
44
|
+
)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Using a custom LLM function
|
|
48
|
+
|
|
49
|
+
`generate_response` is just a callable with the signature `(Prompt) -> str`. You can pass anything that matches:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
def my_llm(prompt: Prompt) -> str:
|
|
53
|
+
# call any API you want
|
|
54
|
+
...
|
|
55
|
+
|
|
56
|
+
agent = Agent(..., generate_response=my_llm)
|
|
57
|
+
```
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Multi-agent
|
|
2
|
+
|
|
3
|
+
One agent can delegate tasks to other agents using the built-in `call_agent` tool and `AgentRegistry`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
### 1. Create your agents
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from agent_patterns import Agent, Goal, PythonEnvironment, AgentFunctionCallingActionLanguage, PythonActionRegistry, make_generate_response
|
|
13
|
+
|
|
14
|
+
summariser = Agent(
|
|
15
|
+
goals=[Goal(1, "Summarise", "Summarise the provided text concisely then terminate.")],
|
|
16
|
+
agent_language=AgentFunctionCallingActionLanguage(),
|
|
17
|
+
action_registry=PythonActionRegistry(tags=["summariser", "system"]),
|
|
18
|
+
generate_response=make_generate_response("openai/gpt-4o-mini"),
|
|
19
|
+
environment=PythonEnvironment(),
|
|
20
|
+
)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 2. Register them
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from agent_patterns import AgentRegistry
|
|
27
|
+
|
|
28
|
+
registry = AgentRegistry()
|
|
29
|
+
registry.register_agent("summariser", summariser.run)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 3. Give the coordinator the call_agent tool
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
# Import to register it in the global tool registry
|
|
36
|
+
import agent_patterns.tools.agent_tools # noqa: F401
|
|
37
|
+
|
|
38
|
+
coordinator = Agent(
|
|
39
|
+
goals=[Goal(1, "Coordinate", "Delegate tasks to specialist agents as needed.")],
|
|
40
|
+
agent_language=AgentFunctionCallingActionLanguage(),
|
|
41
|
+
action_registry=PythonActionRegistry(tags=["coordinator", "system"]),
|
|
42
|
+
generate_response=make_generate_response("openai/gpt-4o"),
|
|
43
|
+
environment=PythonEnvironment(),
|
|
44
|
+
)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 4. Pass the registry at runtime
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
memory = coordinator.run(
|
|
51
|
+
"Summarise this report: ...",
|
|
52
|
+
action_context_props={"agent_registry": registry},
|
|
53
|
+
)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## How call_agent works
|
|
59
|
+
|
|
60
|
+
When the coordinator calls `call_agent`:
|
|
61
|
+
|
|
62
|
+
- A **fresh memory** is created for the sub-agent (no memory leakage between agents)
|
|
63
|
+
- Context properties are forwarded, except `agent_registry` and `memory` (prevents infinite recursion)
|
|
64
|
+
- The sub-agent's final memory item is returned as the result
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Memory sharing patterns
|
|
69
|
+
|
|
70
|
+
Beyond basic message passing, you can control how much context a sub-agent receives by what you pass in `action_context_props`. Four patterns, in order of context shared:
|
|
71
|
+
|
|
72
|
+
| Pattern | How |
|
|
73
|
+
|---------|-----|
|
|
74
|
+
| Message passing | Default — fresh memory, task only |
|
|
75
|
+
| Memory handoff | Pass `memory=existing_memory` to `agent.run()` |
|
|
76
|
+
| Memory reflection | Copy sub-agent memories back to caller after the call |
|
|
77
|
+
| Selective sharing | Filter memories before passing them to the sub-agent |
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Safety patterns
|
|
2
|
+
|
|
3
|
+
Three patterns for agents that take real-world actions (calendar events, emails, API calls, etc.).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Reversible actions
|
|
8
|
+
|
|
9
|
+
Wrap any action so it can be undone:
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from agent_patterns import ReversibleAction
|
|
13
|
+
|
|
14
|
+
create_event = ReversibleAction(
|
|
15
|
+
execute_func=calendar.create_event,
|
|
16
|
+
reverse_func=lambda **record: calendar.delete_event(record["result"]["event_id"]),
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# Execute
|
|
20
|
+
result = create_event.run(title="Sync", attendees=["alice@example.com"])
|
|
21
|
+
|
|
22
|
+
# Undo if needed
|
|
23
|
+
create_event.undo()
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The `reverse_func` receives the full execution record as kwargs:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
{
|
|
30
|
+
"args": {...}, # original arguments
|
|
31
|
+
"result": {...}, # return value from execute_func
|
|
32
|
+
"timestamp": "...", # ISO timestamp
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Action transactions
|
|
39
|
+
|
|
40
|
+
Group multiple reversible actions into an atomic unit — all succeed or all roll back:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from agent_patterns import ActionTransaction, ReversibleAction
|
|
44
|
+
|
|
45
|
+
tx = ActionTransaction()
|
|
46
|
+
tx.add(create_event, title="Sync", attendees=["alice@example.com"])
|
|
47
|
+
tx.add(send_invite, event_id="abc123")
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
tx.execute() # runs both; rolls back automatically on failure
|
|
51
|
+
tx.commit() # marks as final
|
|
52
|
+
except Exception as e:
|
|
53
|
+
print(f"Transaction failed and was rolled back: {e}")
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Staged execution
|
|
59
|
+
|
|
60
|
+
Collect actions for review before executing them. Useful when a human or a more capable model should sign off before side-effects occur:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from agent_patterns import StagedActionEnvironment, ReversibleAction
|
|
64
|
+
|
|
65
|
+
env = StagedActionEnvironment()
|
|
66
|
+
|
|
67
|
+
# Register a reviewer — returns True to approve, False to reject
|
|
68
|
+
def my_reviewer(task_id, transaction):
|
|
69
|
+
print(f"Approving {len(transaction._pending)} actions for task {task_id}")
|
|
70
|
+
return True # or False to reject
|
|
71
|
+
|
|
72
|
+
env.set_reviewer(my_reviewer)
|
|
73
|
+
|
|
74
|
+
# Stage actions
|
|
75
|
+
tx = env.stage_actions(task_id="meeting-setup")
|
|
76
|
+
tx.add(create_event, title="Team sync")
|
|
77
|
+
tx.add(send_invite, attendees=["team@example.com"])
|
|
78
|
+
|
|
79
|
+
# Review, then execute
|
|
80
|
+
if env.review_transaction("meeting-setup"):
|
|
81
|
+
tx.execute()
|
|
82
|
+
tx.commit()
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The reviewer can be a human approval step, an LLM policy check, or any callable that returns a bool.
|