-
Notifications
You must be signed in to change notification settings - Fork 215
Expand file tree
/
Copy pathagent_protocol.py
More file actions
231 lines (192 loc) · 8.6 KB
/
agent_protocol.py
File metadata and controls
231 lines (192 loc) · 8.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
"""
Agent Protocol framework implementation for AgentStack.
This module implements the agent-protocol specification (https://github.com/langchain-ai/agent-protocol)
as a framework within AgentStack.
"""
from typing import Optional, Any, List
from pathlib import Path
import ast
import re
from agentstack.exceptions import ValidationError, ThreadError, RunError, StoreError
from agentstack.tools import ToolConfig
from agentstack.tasks import TaskConfig
from agentstack.agents import AgentConfig
from agentstack import conf, frameworks
ENTRYPOINT: Path = Path('src/agent.py')
class AgentProtocolFile:
"""
Handles agent-protocol specific file operations.
This class manages the interaction with agent-protocol files, including
validation, reading, and writing operations.
"""
def __init__(self, path: Path):
self.path = path
if not self.path.exists():
raise ValidationError(f"Agent protocol file not found at {self.path}")
self._content = self.path.read_text()
def validate(self) -> None:
"""Validate the agent protocol file structure."""
try:
tree = ast.parse(self._content)
except SyntaxError as e:
raise ValidationError(f"Invalid Python syntax in {self.path}: {e}")
# Check for FastAPI app and required components
has_app = False
has_agent_protocol = False
has_tools = False
has_task_handler = False
has_step_handler = False
for node in ast.walk(tree):
if isinstance(node, ast.Assign):
for target in node.targets:
if isinstance(target, ast.Name):
if target.id == 'app':
has_app = True
elif target.id == 'agent_protocol':
has_agent_protocol = True
elif target.id == 'tools':
has_tools = True
elif isinstance(node, ast.FunctionDef):
for decorator in node.decorator_list:
# Handle all possible decorator patterns
decorator_str = ''
if isinstance(decorator, ast.Name):
decorator_str = decorator.id
elif isinstance(decorator, ast.Attribute):
# Build full decorator path (e.g., agent_protocol.on_task)
parts = []
current = decorator
while isinstance(current, ast.Attribute):
parts.append(current.attr)
current = current.value
if isinstance(current, ast.Name):
parts.append(current.id)
decorator_str = '.'.join(reversed(parts))
elif isinstance(decorator, ast.Call):
# Handle decorator calls (e.g., @decorator())
if isinstance(decorator.func, ast.Attribute):
parts = []
current = decorator.func
while isinstance(current, ast.Attribute):
parts.append(current.attr)
current = current.value
if isinstance(current, ast.Name):
parts.append(current.id)
decorator_str = '.'.join(reversed(parts))
elif isinstance(decorator.func, ast.Name):
decorator_str = decorator.func.id
# Check for task and step handlers
if 'on_task' in decorator_str:
has_task_handler = True
elif 'on_step' in decorator_str:
has_step_handler = True
if not has_app:
raise ValidationError(f"FastAPI app not found in {self.path}")
if not has_agent_protocol:
raise ValidationError(f"AgentProtocol instance not found in {self.path}")
if not has_tools:
raise ValidationError(f"Tools list not found in {self.path}")
if not has_task_handler:
raise ValidationError(f"@agent_protocol.on_task handler not found in {self.path}")
if not has_step_handler:
raise ValidationError(f"@agent_protocol.on_step handler not found in {self.path}")
def _update_tools_list(self, tool: ToolConfig, add: bool = True) -> None:
"""Update the tools list in the file."""
if add:
if tool.tools_bundled:
tool_str = f"*tools.{tool.name}"
else:
tool_str = f"tools.{tool.name}"
# If tools list exists, modify it
if re.search(r"tools\s*=\s*\[", self._content):
if tool_str not in self._content:
# Replace existing tools list with updated one
self._content = re.sub(
r"tools\s*=\s*\[(.*?)\]",
lambda m: f"tools=[{tool_str}]"
if not m.group(1).strip()
else f"tools=[{', '.join(t.strip() for t in m.group(1).split(',') if t.strip())}, {tool_str}]",
self._content,
flags=re.DOTALL,
)
else:
# Create new tools list
self._content = re.sub(
r"(import tools\n+)", f"\\1tools=[{tool_str}] # Initial tool setup\n\n", self._content
)
else:
# Remove tool from list
if tool.tools_bundled:
tool_str = f"*tools.{tool.name}"
else:
tool_str = f"tools.{tool.name}"
# First, get the current tools list content
match = re.search(r"tools\s*=\s*\[(.*?)\]", self._content, re.DOTALL)
if match:
tools = [t.strip() for t in match.group(1).split(',') if t.strip()]
# Remove the tool if it exists
tools = [t for t in tools if t != tool_str]
# Update the tools list with proper format
self._content = re.sub(
r"tools\s*=\s*\[.*?\]",
f"tools=[{', '.join(tools)}]" if tools else "tools=[]",
self._content,
flags=re.DOTALL,
)
# Write updated content back to file
self.path.write_text(self._content)
def validate_project() -> None:
"""Validate the agent protocol project structure."""
entrypoint_path = frameworks.get_entrypoint_path('agent_protocol')
if not entrypoint_path.exists():
raise ValidationError("Agent Protocol entrypoint file not found")
file = AgentProtocolFile(entrypoint_path)
file.validate()
# Additional project-level validation can be added here
def get_task_names() -> List[str]:
"""
Get a list of task names from the agent protocol implementation.
Currently returns an empty list as tasks are created dynamically.
"""
return []
def add_task(task: TaskConfig) -> None:
"""
Add a task to the agent protocol implementation.
Tasks in agent-protocol are created dynamically through the API.
"""
pass
def get_agent_names() -> List[str]:
"""
Get a list of available agent names.
Currently returns a single agent as agent-protocol typically uses one agent.
"""
return ["agent_protocol_agent"]
def add_agent(agent: AgentConfig) -> None:
"""
Register a new agent in the agent protocol implementation.
Updates the agent configuration in the agent.py file.
"""
pass
def add_tool(tool: ToolConfig, agent_name: str) -> None:
"""
Add a tool to the specified agent in the agent protocol implementation.
Args:
tool: Tool configuration to add
agent_name: Name of the agent to add the tool to
"""
entrypoint_path = frameworks.get_entrypoint_path('agent_protocol')
if not entrypoint_path.exists():
raise ValidationError("Agent Protocol entrypoint file not found")
# Validate project before adding tool
validate_project()
agent_file = AgentProtocolFile(entrypoint_path)
agent_file._update_tools_list(tool, add=True)
def remove_tool(tool: ToolConfig, agent_name: str) -> None:
"""
Remove a tool from the specified agent in the agent protocol implementation.
Args:
tool: Tool configuration to remove
agent_name: Name of the agent to remove the tool from
"""
agent_file = AgentProtocolFile(conf.PATH / ENTRYPOINT)
agent_file._update_tools_list(tool, add=False)