-
Notifications
You must be signed in to change notification settings - Fork 218
Expand file tree
/
Copy pathschema.py
More file actions
151 lines (123 loc) · 4.8 KB
/
schema.py
File metadata and controls
151 lines (123 loc) · 4.8 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
"""Schema for Markdown-based agent definition files."""
from __future__ import annotations
import re
from pathlib import Path
from typing import TYPE_CHECKING, Any
import frontmatter
from pydantic import BaseModel, Field
if TYPE_CHECKING:
from openhands.sdk.agent.base import AgentBase
def _extract_examples(description: str) -> list[str]:
"""Extract <example> tags from description for agent triggering."""
pattern = r"<example>(.*?)</example>"
matches = re.findall(pattern, description, re.DOTALL | re.IGNORECASE)
return [m.strip() for m in matches if m.strip()]
class AgentDefinition(BaseModel):
"""Agent definition loaded from Markdown file.
Agents are specialized configurations that can be triggered based on
user input patterns. They define custom system prompts and tool access.
"""
name: str = Field(description="Agent name (from frontmatter or filename)")
description: str = Field(default="", description="Agent description")
model: str = Field(
default="inherit", description="Model to use ('inherit' uses parent model)"
)
color: str | None = Field(default=None, description="Display color for the agent")
tools: list[str] = Field(
default_factory=list, description="List of allowed tools for this agent"
)
system_prompt: str = Field(default="", description="System prompt content")
source: str | None = Field(
default=None, description="Source file path for this agent"
)
when_to_use_examples: list[str] = Field(
default_factory=list,
description="Examples of when to use this agent (for triggering)",
)
metadata: dict[str, Any] = Field(
default_factory=dict, description="Additional metadata from frontmatter"
)
@classmethod
def load(cls, agent_path: Path) -> AgentDefinition:
"""Load an agent definition from a Markdown file.
Agent Markdown files have YAML frontmatter with:
- name: Agent name
- description: Description with optional <example> tags for triggering
- tools (optional): List of allowed tools
- model (optional): Model profile to use (default: 'inherit')
- color (optional): Display color
The body of the Markdown is the system prompt.
Args:
agent_path: Path to the agent Markdown file.
Returns:
Loaded AgentDefinition instance.
"""
with open(agent_path) as f:
post = frontmatter.load(f)
fm = post.metadata
content = post.content.strip()
# Extract frontmatter fields with proper type handling
name = str(fm.get("name", agent_path.stem))
description = str(fm.get("description", ""))
model = str(fm.get("model", "inherit"))
color_raw = fm.get("color")
color: str | None = str(color_raw) if color_raw is not None else None
tools_raw = fm.get("tools", [])
# Ensure tools is a list of strings
tools: list[str]
if isinstance(tools_raw, str):
tools = [tools_raw]
elif isinstance(tools_raw, list):
tools = [str(t) for t in tools_raw]
else:
tools = []
# Extract whenToUse examples from description
when_to_use_examples = _extract_examples(description)
# Remove known fields from metadata to get extras
known_fields = {
"name",
"description",
"model",
"color",
"tools",
}
metadata = {k: v for k, v in fm.items() if k not in known_fields}
return cls(
name=name,
description=description,
model=model,
color=color,
tools=tools,
system_prompt=content,
source=str(agent_path),
when_to_use_examples=when_to_use_examples,
metadata=metadata,
)
@classmethod
def from_agent(
cls,
agent: AgentBase,
name: str,
description: str,
) -> AgentDefinition:
"""Build an AgentDefinition by introspecting a live Agent instance.
Args:
agent: The agent to extract configuration from.
name: Name for the agent definition.
description: Human-readable description of the agent.
Returns:
A fully populated AgentDefinition.
"""
tools = [t.name for t in agent.tools]
if agent.agent_context and agent.agent_context.system_message_suffix:
system_prompt = agent.agent_context.system_message_suffix
else:
system_prompt = ""
model = agent.llm.model if agent.llm.model else "inherit"
return cls(
name=name,
description=description,
tools=tools,
system_prompt=system_prompt,
model=model,
)