-
Notifications
You must be signed in to change notification settings - Fork 217
Expand file tree
/
Copy pathagents.py
More file actions
110 lines (88 loc) · 3.33 KB
/
agents.py
File metadata and controls
110 lines (88 loc) · 3.33 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
from typing import Optional
import os
from pathlib import Path
import pydantic
from ruamel.yaml import YAML, YAMLError
from ruamel.yaml.scalarstring import FoldedScalarString
from agentstack import conf
from agentstack.exceptions import ValidationError
AGENTS_FILENAME: Path = Path("src/config/agents.yaml")
yaml = YAML()
yaml.preserve_quotes = True # Preserve quotes in existing data
class AgentConfig(pydantic.BaseModel):
"""
Interface for interacting with an agent configuration.
Multiple agents are stored in a single YAML file, so we always look up the
requested agent by `name`.
Use it as a context manager to make and save edits:
```python
with AgentConfig('agent_name') as config:
config.llm = "openai/gpt-4o"
Config Schema
-------------
name: str
The name of the agent; used for lookup.
role: str
The role of the agent.
goal: str
The goal of the agent.
backstory: str
The backstory of the agent.
llm: str
The model this agent should use.
Always follows the format `org/repo`, regardless of the framework.
# TODO we need to reformat this on-read depending on the framework
retries: int
The number of times the agent should retry a task.
"""
name: str
role: str = ""
goal: str = ""
backstory: str = ""
llm: str = ""
retries: int = 0
def __init__(self, name: str):
filename = conf.PATH / AGENTS_FILENAME
if not os.path.exists(filename):
os.makedirs(filename.parent, exist_ok=True)
filename.touch()
try:
with open(filename, 'r') as f:
data = yaml.load(f) or {}
data = data.get(name, {}) or {}
super().__init__(**{**{'name': name}, **data})
except YAMLError as e:
# TODO format MarkedYAMLError lines/messages
raise ValidationError(f"Error parsing agents file: {filename}\n{e}")
except pydantic.ValidationError as e:
error_str = "Error validating agent config:\n"
for error in e.errors():
error_str += f"{' '.join([str(loc) for loc in error['loc']])}: {error['msg']}\n"
raise ValidationError(f"Error loading agent {name} from {filename}.\n{error_str}")
def model_dump(self, *args, **kwargs) -> dict:
dump = super().model_dump(*args, **kwargs)
dump.pop('name') # name is the key, so keep it out of the data
# format these as FoldedScalarStrings
for key in ('role', 'goal', 'backstory'):
dump[key] = FoldedScalarString(dump.get(key) or "")
return {self.name: dump}
def write(self):
filename = conf.PATH / AGENTS_FILENAME
with open(filename, 'r') as f:
data = yaml.load(f) or {}
data.update(self.model_dump())
with open(filename, 'w') as f:
yaml.dump(data, f)
def __enter__(self) -> 'AgentConfig':
return self
def __exit__(self, *args):
self.write()
def get_all_agent_names() -> list[str]:
filename = conf.PATH / AGENTS_FILENAME
if not os.path.exists(filename):
return []
with open(filename, 'r') as f:
data = yaml.load(f) or {}
return list(data.keys())
def get_all_agents() -> list[AgentConfig]:
return [AgentConfig(name) for name in get_all_agent_names()]