forked from agentstack-ai/AgentStack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathagents.py
More file actions
135 lines (107 loc) · 4.09 KB
/
agents.py
File metadata and controls
135 lines (107 loc) · 4.09 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
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, log
from agentstack.exceptions import ValidationError
from agentstack.providers import parse_provider_model
AGENTS_FILENAME: Path = Path("src/config/agents.yaml")
AGENTS_PROMPT_TPL: str = "You are {role}. {backstory}\nYour personal goal is: {goal}"
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.
Adheres to the format set by the framework.
"""
name: str
role: str = ""
goal: str = ""
backstory: str = ""
llm: str = ""
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}")
@property
def provider(self) -> str:
"""The LLM provider ie. 'openai' or 'openrouter'"""
return parse_provider_model(self.llm)[0]
@property
def model(self) -> str:
"""The model name ie. 'gpt-4o'"""
return parse_provider_model(self.llm)[1]
@property
def prompt(self) -> str:
"""Concatenate the prompts for role, goal, and backstory."""
return AGENTS_PROMPT_TPL.format(**{
'role': self.role,
'goal': self.goal,
'backstory': self.backstory,
})
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):
log.debug(f"Writing agent {self.name} to {AGENTS_FILENAME}")
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):
log.debug(f"Project does not have an {AGENTS_FILENAME} file.")
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()]
def get_agent(name: str) -> Optional[AgentConfig]:
"""Get an agent configuration by name."""
return AgentConfig(name)