agent-patterns-py 0.1.0__py3-none-any.whl
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/__init__.py +98 -0
- agent_patterns/capabilities/__init__.py +9 -0
- agent_patterns/capabilities/base.py +109 -0
- agent_patterns/capabilities/plan_first.py +62 -0
- agent_patterns/capabilities/progress_tracking.py +64 -0
- agent_patterns/core/__init__.py +17 -0
- agent_patterns/core/action.py +62 -0
- agent_patterns/core/agent.py +240 -0
- agent_patterns/core/context.py +54 -0
- agent_patterns/core/environment.py +99 -0
- agent_patterns/core/goal.py +22 -0
- agent_patterns/core/memory.py +34 -0
- agent_patterns/language/__init__.py +10 -0
- agent_patterns/language/base.py +81 -0
- agent_patterns/language/function_calling.py +92 -0
- agent_patterns/language/json_action.py +103 -0
- agent_patterns/multi_agent/__init__.py +3 -0
- agent_patterns/multi_agent/registry.py +32 -0
- agent_patterns/safety/__init__.py +8 -0
- agent_patterns/safety/reversible.py +111 -0
- agent_patterns/safety/staged.py +95 -0
- agent_patterns/tools/__init__.py +15 -0
- agent_patterns/tools/agent_tools.py +72 -0
- agent_patterns/tools/persona.py +100 -0
- agent_patterns/tools/planning.py +116 -0
- agent_patterns/tools/registry.py +230 -0
- agent_patterns/utils/__init__.py +8 -0
- agent_patterns/utils/llm.py +100 -0
- agent_patterns_py-0.1.0.dist-info/METADATA +92 -0
- agent_patterns_py-0.1.0.dist-info/RECORD +32 -0
- agent_patterns_py-0.1.0.dist-info/WHEEL +4 -0
- agent_patterns_py-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""
|
|
2
|
+
agent-patterns
|
|
3
|
+
==============
|
|
4
|
+
|
|
5
|
+
Production-ready boilerplate for common agentic AI patterns in Python.
|
|
6
|
+
|
|
7
|
+
Quick start::
|
|
8
|
+
|
|
9
|
+
from agent_patterns import (
|
|
10
|
+
Agent, Goal, Memory,
|
|
11
|
+
PythonEnvironment,
|
|
12
|
+
AgentFunctionCallingActionLanguage,
|
|
13
|
+
PythonActionRegistry,
|
|
14
|
+
register_tool,
|
|
15
|
+
make_generate_response,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
@register_tool(tags=["my_tools"], terminal=True)
|
|
19
|
+
def terminate(message: str) -> str:
|
|
20
|
+
\"\"\"End the session and return a final message.\"\"\"
|
|
21
|
+
return message
|
|
22
|
+
|
|
23
|
+
agent = Agent(
|
|
24
|
+
goals=[Goal(1, "Task", "Complete the assigned task then terminate.")],
|
|
25
|
+
agent_language=AgentFunctionCallingActionLanguage(),
|
|
26
|
+
action_registry=PythonActionRegistry(tags=["my_tools"]),
|
|
27
|
+
generate_response=make_generate_response("openai/gpt-4o"),
|
|
28
|
+
environment=PythonEnvironment(),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
memory = agent.run("Hello!")
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
# Core GAME components
|
|
35
|
+
from agent_patterns.core.goal import Goal
|
|
36
|
+
from agent_patterns.core.memory import Memory
|
|
37
|
+
from agent_patterns.core.action import Action, ActionRegistry
|
|
38
|
+
from agent_patterns.core.context import ActionContext
|
|
39
|
+
from agent_patterns.core.environment import Environment, PythonEnvironment
|
|
40
|
+
from agent_patterns.core.agent import Agent
|
|
41
|
+
|
|
42
|
+
# Language strategies
|
|
43
|
+
from agent_patterns.language.base import AgentLanguage, Prompt
|
|
44
|
+
from agent_patterns.language.function_calling import AgentFunctionCallingActionLanguage
|
|
45
|
+
from agent_patterns.language.json_action import AgentJsonActionLanguage
|
|
46
|
+
|
|
47
|
+
# Tool system
|
|
48
|
+
from agent_patterns.tools.registry import register_tool, get_tool_metadata, PythonActionRegistry
|
|
49
|
+
|
|
50
|
+
# Utilities
|
|
51
|
+
from agent_patterns.utils.llm import make_generate_response, generate_response, prompt_llm
|
|
52
|
+
|
|
53
|
+
# Capabilities
|
|
54
|
+
from agent_patterns.capabilities.base import Capability
|
|
55
|
+
from agent_patterns.capabilities.plan_first import PlanFirstCapability
|
|
56
|
+
from agent_patterns.capabilities.progress_tracking import ProgressTrackingCapability
|
|
57
|
+
|
|
58
|
+
# Multi-agent
|
|
59
|
+
from agent_patterns.multi_agent.registry import AgentRegistry
|
|
60
|
+
|
|
61
|
+
# Safety
|
|
62
|
+
from agent_patterns.safety.reversible import ReversibleAction, ActionTransaction
|
|
63
|
+
from agent_patterns.safety.staged import StagedActionEnvironment
|
|
64
|
+
|
|
65
|
+
__all__ = [
|
|
66
|
+
# Core
|
|
67
|
+
"Goal",
|
|
68
|
+
"Memory",
|
|
69
|
+
"Action",
|
|
70
|
+
"ActionRegistry",
|
|
71
|
+
"ActionContext",
|
|
72
|
+
"Environment",
|
|
73
|
+
"PythonEnvironment",
|
|
74
|
+
"Agent",
|
|
75
|
+
# Language
|
|
76
|
+
"AgentLanguage",
|
|
77
|
+
"Prompt",
|
|
78
|
+
"AgentFunctionCallingActionLanguage",
|
|
79
|
+
"AgentJsonActionLanguage",
|
|
80
|
+
# Tools
|
|
81
|
+
"register_tool",
|
|
82
|
+
"get_tool_metadata",
|
|
83
|
+
"PythonActionRegistry",
|
|
84
|
+
# Utils
|
|
85
|
+
"make_generate_response",
|
|
86
|
+
"generate_response",
|
|
87
|
+
"prompt_llm",
|
|
88
|
+
# Capabilities
|
|
89
|
+
"Capability",
|
|
90
|
+
"PlanFirstCapability",
|
|
91
|
+
"ProgressTrackingCapability",
|
|
92
|
+
# Multi-agent
|
|
93
|
+
"AgentRegistry",
|
|
94
|
+
# Safety
|
|
95
|
+
"ReversibleAction",
|
|
96
|
+
"ActionTransaction",
|
|
97
|
+
"StagedActionEnvironment",
|
|
98
|
+
]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from agent_patterns.capabilities.base import Capability
|
|
2
|
+
from agent_patterns.capabilities.plan_first import PlanFirstCapability
|
|
3
|
+
from agent_patterns.capabilities.progress_tracking import ProgressTrackingCapability
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"Capability",
|
|
7
|
+
"PlanFirstCapability",
|
|
8
|
+
"ProgressTrackingCapability",
|
|
9
|
+
]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from agent_patterns.core.action import Action
|
|
8
|
+
from agent_patterns.core.context import ActionContext
|
|
9
|
+
from agent_patterns.core.memory import Memory
|
|
10
|
+
from agent_patterns.language.base import Prompt
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Capability(ABC):
|
|
14
|
+
"""
|
|
15
|
+
Base class for agent loop extensions.
|
|
16
|
+
|
|
17
|
+
Capabilities plug into eight lifecycle hooks in the agent loop,
|
|
18
|
+
allowing rich extensions (time awareness, planning, logging, metrics,
|
|
19
|
+
etc.) without modifying :class:`~agent_patterns.core.agent.Agent`.
|
|
20
|
+
|
|
21
|
+
All methods have no-op defaults — override only what you need.
|
|
22
|
+
|
|
23
|
+
Lifecycle (per ``agent.run()`` call)::
|
|
24
|
+
|
|
25
|
+
init()
|
|
26
|
+
┌─ loop ─────────────────────────────────────────────────────┐
|
|
27
|
+
│ start_agent_loop() ← return False to stop │
|
|
28
|
+
│ process_prompt() │
|
|
29
|
+
│ [LLM call] │
|
|
30
|
+
│ process_response() │
|
|
31
|
+
│ process_action() │
|
|
32
|
+
│ [Environment.execute_action()] │
|
|
33
|
+
│ process_result() │
|
|
34
|
+
│ process_new_memories() │
|
|
35
|
+
│ end_agent_loop() │
|
|
36
|
+
│ should_terminate() ← return True to stop │
|
|
37
|
+
└─────────────────────────────────────────────────────────────┘
|
|
38
|
+
terminate()
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, name: str, description: str) -> None:
|
|
42
|
+
self.name = name
|
|
43
|
+
self.description = description
|
|
44
|
+
|
|
45
|
+
def init(self, agent: Any, action_context: "ActionContext") -> None:
|
|
46
|
+
"""Called once before the loop starts. Set up state here."""
|
|
47
|
+
|
|
48
|
+
def start_agent_loop(
|
|
49
|
+
self, agent: Any, action_context: "ActionContext"
|
|
50
|
+
) -> Optional[bool]:
|
|
51
|
+
"""
|
|
52
|
+
Called at the start of each iteration.
|
|
53
|
+
Return ``False`` to stop the loop immediately.
|
|
54
|
+
"""
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
def process_prompt(
|
|
58
|
+
self, agent: Any, action_context: "ActionContext", prompt: "Prompt"
|
|
59
|
+
) -> "Prompt":
|
|
60
|
+
"""Modify the prompt before it is sent to the LLM."""
|
|
61
|
+
return prompt
|
|
62
|
+
|
|
63
|
+
def process_response(
|
|
64
|
+
self, agent: Any, action_context: "ActionContext", response: str
|
|
65
|
+
) -> str:
|
|
66
|
+
"""Modify the raw LLM response before it is parsed."""
|
|
67
|
+
return response
|
|
68
|
+
|
|
69
|
+
def process_action(
|
|
70
|
+
self, agent: Any, action_context: "ActionContext", action: Dict
|
|
71
|
+
) -> Dict:
|
|
72
|
+
"""Modify the parsed action invocation before execution."""
|
|
73
|
+
return action
|
|
74
|
+
|
|
75
|
+
def process_result(
|
|
76
|
+
self,
|
|
77
|
+
agent: Any,
|
|
78
|
+
action_context: "ActionContext",
|
|
79
|
+
response: str,
|
|
80
|
+
action_def: "Action",
|
|
81
|
+
action: Dict,
|
|
82
|
+
result: Any,
|
|
83
|
+
) -> Any:
|
|
84
|
+
"""Modify the action result before it is stored in memory."""
|
|
85
|
+
return result
|
|
86
|
+
|
|
87
|
+
def process_new_memories(
|
|
88
|
+
self,
|
|
89
|
+
agent: Any,
|
|
90
|
+
action_context: "ActionContext",
|
|
91
|
+
memory: "Memory",
|
|
92
|
+
response: str,
|
|
93
|
+
result: Any,
|
|
94
|
+
memories: List[Dict],
|
|
95
|
+
) -> List[Dict]:
|
|
96
|
+
"""Add, remove, or modify memory entries before they are committed."""
|
|
97
|
+
return memories
|
|
98
|
+
|
|
99
|
+
def end_agent_loop(self, agent: Any, action_context: "ActionContext") -> None:
|
|
100
|
+
"""Called at the end of each iteration (after memory update)."""
|
|
101
|
+
|
|
102
|
+
def should_terminate(
|
|
103
|
+
self, agent: Any, action_context: "ActionContext", response: str
|
|
104
|
+
) -> bool:
|
|
105
|
+
"""Return ``True`` to stop the loop after the current iteration."""
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
def terminate(self, agent: Any, action_context: "ActionContext") -> None:
|
|
109
|
+
"""Called once when the agent stops (cleanup, flushing, etc.)."""
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from agent_patterns.capabilities.base import Capability
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from agent_patterns.core.context import ActionContext
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PlanFirstCapability(Capability):
|
|
12
|
+
"""
|
|
13
|
+
Forces the agent to create a detailed execution plan before acting.
|
|
14
|
+
|
|
15
|
+
On the first iteration, invokes
|
|
16
|
+
:func:`~agent_patterns.tools.planning.create_plan` and stores the
|
|
17
|
+
result in memory so every subsequent prompt includes the plan.
|
|
18
|
+
|
|
19
|
+
This improves reliability for multi-step tasks by anchoring the agent
|
|
20
|
+
to a structured strategy rather than improvising step-by-step.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
plan_memory_type: Memory type tag for the plan entry.
|
|
24
|
+
``"system"`` keeps it out of the visible
|
|
25
|
+
conversation while still influencing the prompt.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, plan_memory_type: str = "system") -> None:
|
|
29
|
+
super().__init__(
|
|
30
|
+
name="Plan First",
|
|
31
|
+
description="Generates an execution plan before the agent takes its first action.",
|
|
32
|
+
)
|
|
33
|
+
self.plan_memory_type = plan_memory_type
|
|
34
|
+
self._planned = False
|
|
35
|
+
|
|
36
|
+
def init(self, agent: Any, action_context: "ActionContext") -> None:
|
|
37
|
+
if self._planned:
|
|
38
|
+
return
|
|
39
|
+
self._planned = True
|
|
40
|
+
|
|
41
|
+
# Import here to avoid circular imports at module load time
|
|
42
|
+
from agent_patterns.tools.planning import create_plan
|
|
43
|
+
|
|
44
|
+
memory = action_context.get_memory()
|
|
45
|
+
action_registry = action_context.get_action_registry()
|
|
46
|
+
|
|
47
|
+
if memory is None or action_registry is None:
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
plan = create_plan(
|
|
51
|
+
action_context=action_context,
|
|
52
|
+
_memory=memory,
|
|
53
|
+
_action_registry=action_registry,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
memory.add_memory({
|
|
57
|
+
"type": self.plan_memory_type,
|
|
58
|
+
"content": (
|
|
59
|
+
"You must follow these instructions carefully to complete the task:\n\n"
|
|
60
|
+
+ plan
|
|
61
|
+
),
|
|
62
|
+
})
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from agent_patterns.capabilities.base import Capability
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from agent_patterns.core.context import ActionContext
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ProgressTrackingCapability(Capability):
|
|
12
|
+
"""
|
|
13
|
+
Adds a progress report to memory at the end of every N iterations.
|
|
14
|
+
|
|
15
|
+
After each tracked iteration, invokes
|
|
16
|
+
:func:`~agent_patterns.tools.planning.track_progress` and stores the
|
|
17
|
+
result so the agent can reflect on what it has accomplished and adjust
|
|
18
|
+
its strategy accordingly.
|
|
19
|
+
|
|
20
|
+
This is especially useful for long-running tasks where the agent needs
|
|
21
|
+
to self-correct rather than continue executing a stale plan.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
memory_type: Type tag for progress report entries.
|
|
25
|
+
track_frequency: Generate a report every N iterations (default 1).
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
memory_type: str = "system",
|
|
31
|
+
track_frequency: int = 1,
|
|
32
|
+
) -> None:
|
|
33
|
+
super().__init__(
|
|
34
|
+
name="Progress Tracking",
|
|
35
|
+
description="Adds an LLM-generated progress report to memory after each iteration.",
|
|
36
|
+
)
|
|
37
|
+
self.memory_type = memory_type
|
|
38
|
+
self.track_frequency = track_frequency
|
|
39
|
+
self._iteration = 0
|
|
40
|
+
|
|
41
|
+
def end_agent_loop(self, agent: Any, action_context: "ActionContext") -> None:
|
|
42
|
+
self._iteration += 1
|
|
43
|
+
|
|
44
|
+
if self._iteration % self.track_frequency != 0:
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
from agent_patterns.tools.planning import track_progress
|
|
48
|
+
|
|
49
|
+
memory = action_context.get_memory()
|
|
50
|
+
action_registry = action_context.get_action_registry()
|
|
51
|
+
|
|
52
|
+
if memory is None or action_registry is None:
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
report = track_progress(
|
|
56
|
+
action_context=action_context,
|
|
57
|
+
_memory=memory,
|
|
58
|
+
_action_registry=action_registry,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
memory.add_memory({
|
|
62
|
+
"type": self.memory_type,
|
|
63
|
+
"content": f"Progress Report (iteration {self._iteration}):\n\n{report}",
|
|
64
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from agent_patterns.core.goal import Goal
|
|
2
|
+
from agent_patterns.core.memory import Memory
|
|
3
|
+
from agent_patterns.core.action import Action, ActionRegistry
|
|
4
|
+
from agent_patterns.core.context import ActionContext
|
|
5
|
+
from agent_patterns.core.environment import Environment, PythonEnvironment
|
|
6
|
+
from agent_patterns.core.agent import Agent
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Goal",
|
|
10
|
+
"Memory",
|
|
11
|
+
"Action",
|
|
12
|
+
"ActionRegistry",
|
|
13
|
+
"ActionContext",
|
|
14
|
+
"Environment",
|
|
15
|
+
"PythonEnvironment",
|
|
16
|
+
"Agent",
|
|
17
|
+
]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Action:
|
|
7
|
+
"""
|
|
8
|
+
Represents a tool the agent can invoke.
|
|
9
|
+
|
|
10
|
+
``Action`` is the *interface* — it describes what the tool does and
|
|
11
|
+
what parameters it accepts. The ``Environment`` provides the
|
|
12
|
+
*implementation* that actually runs the underlying function.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
name: Unique identifier matched against the LLM's tool call.
|
|
16
|
+
function: The Python callable to execute.
|
|
17
|
+
description: Human/LLM-readable description (used in the prompt).
|
|
18
|
+
parameters: JSON Schema object describing the tool's arguments.
|
|
19
|
+
terminal: When ``True``, the agent loop stops after this action.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
name: str,
|
|
25
|
+
function: Callable,
|
|
26
|
+
description: str,
|
|
27
|
+
parameters: Dict,
|
|
28
|
+
terminal: bool = False,
|
|
29
|
+
) -> None:
|
|
30
|
+
self.name = name
|
|
31
|
+
self.function = function
|
|
32
|
+
self.description = description
|
|
33
|
+
self.terminal = terminal
|
|
34
|
+
self.parameters = parameters
|
|
35
|
+
|
|
36
|
+
def execute(self, **kwargs: Any) -> Any:
|
|
37
|
+
"""Call the underlying function with *kwargs*."""
|
|
38
|
+
return self.function(**kwargs)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ActionRegistry:
|
|
42
|
+
"""
|
|
43
|
+
Maps action names to :class:`Action` objects.
|
|
44
|
+
|
|
45
|
+
Use :class:`~agent_patterns.tools.registry.PythonActionRegistry` for
|
|
46
|
+
automatic population from the ``@register_tool`` decorator registry.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self) -> None:
|
|
50
|
+
self.actions: Dict[str, Action] = {}
|
|
51
|
+
|
|
52
|
+
def register(self, action: Action) -> None:
|
|
53
|
+
"""Add *action* to the registry (overwrites if name already exists)."""
|
|
54
|
+
self.actions[action.name] = action
|
|
55
|
+
|
|
56
|
+
def get_action(self, name: str) -> Optional[Action]:
|
|
57
|
+
"""Return the ``Action`` for *name*, or ``None`` if not found."""
|
|
58
|
+
return self.actions.get(name)
|
|
59
|
+
|
|
60
|
+
def get_actions(self) -> List[Action]:
|
|
61
|
+
"""Return all registered actions."""
|
|
62
|
+
return list(self.actions.values())
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import time
|
|
6
|
+
from typing import TYPE_CHECKING, Callable, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
from agent_patterns.core.action import ActionRegistry
|
|
9
|
+
from agent_patterns.core.context import ActionContext
|
|
10
|
+
from agent_patterns.core.environment import Environment
|
|
11
|
+
from agent_patterns.core.goal import Goal
|
|
12
|
+
from agent_patterns.core.memory import Memory
|
|
13
|
+
from agent_patterns.language.base import AgentLanguage, Prompt
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from agent_patterns.capabilities.base import Capability
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Agent:
|
|
22
|
+
"""
|
|
23
|
+
Core GAME loop: Goals · Actions · Memory · Environment.
|
|
24
|
+
|
|
25
|
+
The loop runs until one of:
|
|
26
|
+
|
|
27
|
+
- A ``terminal`` action is selected.
|
|
28
|
+
- ``max_iterations`` is reached.
|
|
29
|
+
- ``max_duration_seconds`` elapses.
|
|
30
|
+
- A :class:`~agent_patterns.capabilities.base.Capability` signals stop.
|
|
31
|
+
|
|
32
|
+
**Capabilities** plug into eight lifecycle hooks without modifying this
|
|
33
|
+
class. Pass a list of them to ``capabilities``.
|
|
34
|
+
|
|
35
|
+
Example::
|
|
36
|
+
|
|
37
|
+
agent = Agent(
|
|
38
|
+
goals=[Goal(1, "task", "Do X")],
|
|
39
|
+
agent_language=AgentFunctionCallingActionLanguage(),
|
|
40
|
+
action_registry=PythonActionRegistry(tags=["my_tools"]),
|
|
41
|
+
generate_response=make_generate_response("openai/gpt-4o"),
|
|
42
|
+
environment=PythonEnvironment(),
|
|
43
|
+
)
|
|
44
|
+
memory = agent.run("Please do X for me.")
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
goals: List[Goal],
|
|
50
|
+
agent_language: AgentLanguage,
|
|
51
|
+
action_registry: ActionRegistry,
|
|
52
|
+
generate_response: Callable[[Prompt], str],
|
|
53
|
+
environment: Environment,
|
|
54
|
+
capabilities: Optional[List["Capability"]] = None,
|
|
55
|
+
max_iterations: int = 50,
|
|
56
|
+
max_duration_seconds: int = 180,
|
|
57
|
+
verbose: bool = True,
|
|
58
|
+
) -> None:
|
|
59
|
+
self.goals = goals
|
|
60
|
+
self.generate_response = generate_response
|
|
61
|
+
self.agent_language = agent_language
|
|
62
|
+
self.actions = action_registry
|
|
63
|
+
self.environment = environment
|
|
64
|
+
self.capabilities: List["Capability"] = capabilities or []
|
|
65
|
+
self.max_iterations = max_iterations
|
|
66
|
+
self.max_duration_seconds = max_duration_seconds
|
|
67
|
+
self.verbose = verbose
|
|
68
|
+
|
|
69
|
+
# ------------------------------------------------------------------
|
|
70
|
+
# Prompt construction
|
|
71
|
+
# ------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
def construct_prompt(
|
|
74
|
+
self, goals: List[Goal], memory: Memory, actions: ActionRegistry
|
|
75
|
+
) -> Prompt:
|
|
76
|
+
return self.agent_language.construct_prompt(
|
|
77
|
+
actions=actions.get_actions(),
|
|
78
|
+
environment=self.environment,
|
|
79
|
+
goals=goals,
|
|
80
|
+
memory=memory,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# ------------------------------------------------------------------
|
|
84
|
+
# Response helpers
|
|
85
|
+
# ------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
def get_action(self, response: str):
|
|
88
|
+
"""Parse *response* and look up the corresponding Action."""
|
|
89
|
+
invocation = self.agent_language.parse_response(response)
|
|
90
|
+
action = self.actions.get_action(invocation["tool"])
|
|
91
|
+
return action, invocation
|
|
92
|
+
|
|
93
|
+
def should_terminate(self, response: str) -> bool:
|
|
94
|
+
"""Return ``True`` if the parsed action is terminal."""
|
|
95
|
+
try:
|
|
96
|
+
action_def, _ = self.get_action(response)
|
|
97
|
+
return action_def is not None and action_def.terminal
|
|
98
|
+
except Exception:
|
|
99
|
+
return False
|
|
100
|
+
|
|
101
|
+
# ------------------------------------------------------------------
|
|
102
|
+
# Memory helpers
|
|
103
|
+
# ------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
def set_current_task(self, memory: Memory, task: str) -> None:
|
|
106
|
+
memory.add_memory({"type": "user", "content": task})
|
|
107
|
+
|
|
108
|
+
# ------------------------------------------------------------------
|
|
109
|
+
# LLM call
|
|
110
|
+
# ------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
def prompt_llm_for_action(self, prompt: Prompt) -> str:
|
|
113
|
+
return self.generate_response(prompt)
|
|
114
|
+
|
|
115
|
+
# ------------------------------------------------------------------
|
|
116
|
+
# Main loop
|
|
117
|
+
# ------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
def run(
|
|
120
|
+
self,
|
|
121
|
+
user_input: str,
|
|
122
|
+
memory: Optional[Memory] = None,
|
|
123
|
+
action_context_props: Optional[Dict] = None,
|
|
124
|
+
max_iterations: Optional[int] = None,
|
|
125
|
+
) -> Memory:
|
|
126
|
+
"""
|
|
127
|
+
Execute the GAME loop and return the final :class:`Memory`.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
user_input: The task to give the agent.
|
|
131
|
+
memory: Existing memory to continue from (e.g. for
|
|
132
|
+
multi-agent hand-offs). A fresh one is
|
|
133
|
+
created when ``None``.
|
|
134
|
+
action_context_props: Extra properties injected into
|
|
135
|
+
:class:`ActionContext` and available to
|
|
136
|
+
tools via ``_<key>`` parameters.
|
|
137
|
+
max_iterations: Override the instance-level default.
|
|
138
|
+
"""
|
|
139
|
+
memory = memory or Memory()
|
|
140
|
+
action_context_props = action_context_props or {}
|
|
141
|
+
iterations = max_iterations if max_iterations is not None else self.max_iterations
|
|
142
|
+
|
|
143
|
+
action_context = ActionContext({
|
|
144
|
+
"memory": memory,
|
|
145
|
+
"llm": self.generate_response,
|
|
146
|
+
"action_registry": self.actions,
|
|
147
|
+
**action_context_props,
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
# Initialise capabilities (runs once)
|
|
151
|
+
for cap in self.capabilities:
|
|
152
|
+
cap.init(self, action_context)
|
|
153
|
+
|
|
154
|
+
self.set_current_task(memory, user_input)
|
|
155
|
+
start_time = time.time()
|
|
156
|
+
|
|
157
|
+
for _ in range(iterations):
|
|
158
|
+
# Hard wall on execution time
|
|
159
|
+
if time.time() - start_time > self.max_duration_seconds:
|
|
160
|
+
logger.warning("Agent stopped: max_duration_seconds (%s) reached.", self.max_duration_seconds)
|
|
161
|
+
break
|
|
162
|
+
|
|
163
|
+
# start_agent_loop — any capability can halt by returning False
|
|
164
|
+
if any(cap.start_agent_loop(self, action_context) is False for cap in self.capabilities):
|
|
165
|
+
break
|
|
166
|
+
|
|
167
|
+
# Build and augment prompt
|
|
168
|
+
prompt = self.construct_prompt(self.goals, memory, self.actions)
|
|
169
|
+
for cap in self.capabilities:
|
|
170
|
+
prompt = cap.process_prompt(self, action_context, prompt)
|
|
171
|
+
|
|
172
|
+
if self.verbose:
|
|
173
|
+
print("Agent thinking…")
|
|
174
|
+
|
|
175
|
+
# Call the LLM
|
|
176
|
+
response = self.prompt_llm_for_action(prompt)
|
|
177
|
+
|
|
178
|
+
if self.verbose:
|
|
179
|
+
print(f"Agent Decision: {response}")
|
|
180
|
+
|
|
181
|
+
# Capability post-processing on the raw response
|
|
182
|
+
for cap in self.capabilities:
|
|
183
|
+
response = cap.process_response(self, action_context, response)
|
|
184
|
+
|
|
185
|
+
# Parse → look up action
|
|
186
|
+
try:
|
|
187
|
+
action_def, invocation = self.get_action(response)
|
|
188
|
+
except Exception as exc:
|
|
189
|
+
logger.warning("Failed to parse agent response: %s", exc)
|
|
190
|
+
break
|
|
191
|
+
|
|
192
|
+
if action_def is None:
|
|
193
|
+
logger.warning("Unknown action in response, stopping: %s", response)
|
|
194
|
+
break
|
|
195
|
+
|
|
196
|
+
# Capability hook on the parsed invocation
|
|
197
|
+
for cap in self.capabilities:
|
|
198
|
+
invocation = cap.process_action(self, action_context, invocation)
|
|
199
|
+
|
|
200
|
+
# Execute in environment
|
|
201
|
+
result = self.environment.execute_action(
|
|
202
|
+
self, action_context, action_def, invocation.get("args", {})
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
if self.verbose:
|
|
206
|
+
print(f"Action Result: {result}")
|
|
207
|
+
|
|
208
|
+
# Capability hook on the result
|
|
209
|
+
for cap in self.capabilities:
|
|
210
|
+
result = cap.process_result(
|
|
211
|
+
self, action_context, response, action_def, invocation, result
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Build memory entries and let capabilities modify them
|
|
215
|
+
new_memories: List[Dict] = [
|
|
216
|
+
{"type": "assistant", "content": response},
|
|
217
|
+
{"type": "environment", "content": json.dumps(result)},
|
|
218
|
+
]
|
|
219
|
+
for cap in self.capabilities:
|
|
220
|
+
new_memories = cap.process_new_memories(
|
|
221
|
+
self, action_context, memory, response, result, new_memories
|
|
222
|
+
)
|
|
223
|
+
for m in new_memories:
|
|
224
|
+
memory.add_memory(m)
|
|
225
|
+
|
|
226
|
+
# End-of-loop hook
|
|
227
|
+
for cap in self.capabilities:
|
|
228
|
+
cap.end_agent_loop(self, action_context)
|
|
229
|
+
|
|
230
|
+
# Termination checks
|
|
231
|
+
if self.should_terminate(response):
|
|
232
|
+
break
|
|
233
|
+
if any(cap.should_terminate(self, action_context, response) for cap in self.capabilities):
|
|
234
|
+
break
|
|
235
|
+
|
|
236
|
+
# Shutdown hook
|
|
237
|
+
for cap in self.capabilities:
|
|
238
|
+
cap.terminate(self, action_context)
|
|
239
|
+
|
|
240
|
+
return memory
|