Compare commits

...

18 Commits

Author SHA1 Message Date
Engel Nyst b3a02bae4f fix templates 2024-10-27 18:51:22 +01:00
Engel Nyst fc30ac32f0 template fixes 2024-10-27 17:23:44 +01:00
Engel Nyst f224e077ff split blocks 2024-10-27 15:47:32 +01:00
Engel Nyst ea2b598490 move to prompts 2024-10-27 15:04:13 +01:00
Engel Nyst a4dddf8e16 Merge branch 'main' of github.com:All-Hands-AI/OpenHands into enyst/refactor_template 2024-10-27 09:10:03 +01:00
Engel Nyst 12385d2be6 Merge branch 'main' of github.com:All-Hands-AI/OpenHands into enyst/refactor_template 2024-10-26 00:20:43 +02:00
Engel Nyst 6f282b90da fix user prompt; bad coverage 2024-10-24 14:11:39 +02:00
Engel Nyst 1df7aaa0cf add user-defined template directory 2024-10-24 13:10:05 +02:00
Engel Nyst ada2ebd4f5 tweak agent skill display 2024-10-24 12:16:29 +02:00
Engel Nyst 6732359894 strange leftover from another branch 2024-10-24 11:36:08 +02:00
Engel Nyst bf9b8acbab kill some whitespace 2024-10-24 11:32:50 +02:00
Engel Nyst e2c343a733 fix useless vars 2024-10-24 11:22:20 +02:00
Engel Nyst bbd5211c3b remove obsolete md 2024-10-24 11:18:18 +02:00
Engel Nyst 9629a73391 fix template loading 2024-10-24 11:09:33 +02:00
Engel Nyst 7930457211 create examples template 2024-10-24 10:03:03 +02:00
Engel Nyst 5df104dcb2 break down agent skills 2024-10-24 09:33:53 +02:00
Engel Nyst e75a489de9 add agent skills and yaml 2024-10-24 08:02:23 +02:00
Engel Nyst b93c81869a tweak template 2024-10-24 07:06:04 +02:00
9 changed files with 504 additions and 65 deletions
@@ -0,0 +1,59 @@
name: CodeActAgent
# custom templates directory
# .j2 templates will be loaded from this directory if found, if not, the default will be used
custom_templates_dir: "user_templates"
# main templates and their blocks
template:
system_prompt:
file: "system_prompt" # path to the system template file
blocks:
- SYSTEM_PREFIX
- PYTHON_CAPABILITIES
- BASH_CAPABILITIES
- BROWSING_CAPABILITIES
- PIP_CAPABILITIES
- AGENT_SKILLS
- SYSTEM_RULES
agent_skills:
file: "agent_skills"
blocks:
- SKILL_DOCS
user_prompt:
file: "user_prompt"
blocks:
- USER_PROMPT
examples:
file: "examples"
blocks:
- DEFAULT_EXAMPLE
micro_agent:
file: "micro_agent"
blocks:
- MICRO_AGENT_GUIDELINES
# agent-specific variables (can be accessed within templates)
use_tools: false # whether to use tool-based implementations
# tools: # list of available tools
# - name: "EditTool"
# description: "Edits a file."
# usage: "Use the following format: <file_edit> [file_path] [new_file_content] </file_edit>"
# agent skills
agent_skills:
available_skills:
- "file_ops:open_file"
- "file_ops:goto_line"
- "file_ops:scroll_down"
- "file_ops:scroll_up"
- "file_ops:search_dir"
- "file_ops:search_file"
- "file_ops:find_file"
- "file_reader:parse_pdf"
- "file_reader:parse_docx"
- "file_reader:parse_latex"
- "file_reader:parse_pptx"
@@ -91,7 +91,6 @@ class CodeActAgent(Agent):
self.prompt_manager = PromptManager(
prompt_dir=os.path.join(os.path.dirname(__file__)),
agent_skills_docs=AgentSkillsRequirement.documentation,
micro_agent=self.micro_agent,
)
@@ -0,0 +1,10 @@
{# Agent skills documentation template #}
{% block skill_docs %}
{% for skill_name in available_skills %}
{% set skill_docstring = get_skill_docstring(skill_name) %}
{% if skill_docstring %}
{{ skill_docstring }}
{% endif %}
{% endfor %}
{% endblock %}
@@ -1,4 +1,4 @@
{% set DEFAULT_EXAMPLE %}
{% block DEFAULT_EXAMPLE %}
--- START OF EXAMPLE ---
USER: Create a list of numbers from 1 to 10, and display them in a web page at port 5000.
@@ -212,15 +212,4 @@ The server is running on port 5000 with PID 126. You can access the list of numb
<finish></finish>
--- END OF EXAMPLE ---
{% endset %}
Here is an example of how you can interact with the environment for task solving:
{{ DEFAULT_EXAMPLE }}
{% if micro_agent %}
--- BEGIN OF GUIDELINE ---
The following information may assist you in completing your task:
{{ micro_agent }}
--- END OF GUIDELINE ---
{% endif %}
NOW, LET'S START!
{% endblock %}
@@ -0,0 +1,9 @@
{% block MICRO_AGENT_GUIDELINES %}
{% if micro_agent %}
--- BEGIN OF GUIDELINE ---
The following information may assist you in completing your task:
{{ micro_agent }}
--- END OF GUIDELINE ---
{% endif %}
{% endblock %}
@@ -1,11 +1,19 @@
{% set MINIMAL_SYSTEM_PREFIX %}
A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed answers to the user's questions.
{# Core system components for the CodeAct Agent #}
[1] The assistant can use a Python environment with <execute_ipython>, e.g.:
{# Base system identity and core abilities #}
{% block SYSTEM_PREFIX %}
A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed answers to the user's questions.
{% endblock %}
{% block PYTHON_CAPABILITIES %}
You can use a Python environment with <execute_ipython>, e.g.:
<execute_ipython>
print("Hello World!")
</execute_ipython>
{% endblock %}
{# Bash execution capabilities #}
{% block BASH_CAPABILITIES %}
[2] The assistant can execute bash commands wrapped with <execute_bash>, e.g. <execute_bash> ls </execute_bash>.
If a bash command returns exit code `-1`, this means the process is not yet finished.
The assistant must then send a second <execute_bash>. The second <execute_bash> can be empty
@@ -134,27 +142,42 @@ class MyClass:
# MyClass().z is removed
print(MyClass().y)
</file_edit>
{% endblock %}
{% endset %}
{% set BROWSING_PREFIX %}
{# Web browsing #}
{% block BROWSING_CAPABILITIES %}
The assistant can browse the Internet with <execute_browse> and </execute_browse>.
For example, <execute_browse> Tell me the usa's president using google search </execute_browse>.
For example, <execute_browse> Tell me the USA's president using Google search </execute_browse>.
Or <execute_browse> Tell me what is in http://example.com </execute_browse>.
{% endset %}
{% set PIP_INSTALL_PREFIX %}
{% endblock %}
{# Package management #}
{% block PIP_CAPABILITIES %}
The assistant can install Python packages using the %pip magic command in an IPython environment by using the following syntax: <execute_ipython> %pip install [package needed] </execute_ipython> and should always import packages and define variables before starting to use them.
{% endset %}
{% set SYSTEM_PREFIX = MINIMAL_SYSTEM_PREFIX + BROWSING_PREFIX + PIP_INSTALL_PREFIX %}
{% set COMMAND_DOCS %}
{% endblock %}
{# Agent skills documentation #}
{% block AGENT_SKILLS %}
{% if use_tools %}
{# Tool-based implementation #}
The following tools are available:
{% for tool in tools %}
- {{ tool.name }}: {{ tool.description }}
Usage: {{ tool.usage }}
{% endfor %}
{% else %}
Apart from the standard Python library, the assistant can also use the following functions (already imported) in <execute_ipython> environment:
{{ agent_skills_docs }}
IMPORTANT:
- `open_file` only returns the first 100 lines of the file by default! The assistant MUST use `scroll_down` repeatedly to read the full file BEFORE making edits!
- Indentation is important and code that is not indented correctly will fail and require fixing before it can be run.
- Any code issued should be less than 50 lines to avoid context being cut off!
{% endset %}
{% set SYSTEM_SUFFIX %}
{% endif %}
{% endblock %}
{# System behavior rules #}
{% block SYSTEM_RULES %}
Responses should be concise.
The assistant should attempt fewer things at a time instead of putting too many commands OR too much code in one "execute" block.
Include ONLY ONE <execute_ipython>, <execute_bash>, or <execute_browse> per response, unless the assistant is finished with the task or needs more input or action from the user in order to proceed.
@@ -163,9 +186,4 @@ IMPORTANT: Execute code using <execute_ipython>, <execute_bash>, or <execute_bro
The assistant should utilize full file paths and the `pwd` command to prevent path-related errors.
The assistant MUST NOT apologize to the user or thank the user after running commands or editing files. It should only address the user in response to an explicit message from the user, or to ask for more information.
The assistant MUST NOT push any changes to GitHub unless explicitly requested to do so.
{% endset %}
{# Combine all parts without newlines between them #}
{{ SYSTEM_PREFIX -}}
{{- COMMAND_DOCS -}}
{{- SYSTEM_SUFFIX }}
{% endblock %}
@@ -0,0 +1,8 @@
Here is an example of how you can interact with the environment for task solving:
{% block USER_PROMPT %}
{{ examples }}
{{ micro_agent_content }}
NOW, LET'S START!
{% endblock %}
+192 -19
View File
@@ -1,7 +1,11 @@
import importlib
import os
from inspect import signature
from jinja2 import Template
import yaml
from jinja2 import Environment, FileSystemLoader, Template, TemplateNotFound
from openhands.core.logger import openhands_logger as logger
from openhands.utils.microagent import MicroAgent
@@ -15,36 +19,169 @@ class PromptManager:
Attributes:
prompt_dir (str): Directory containing prompt templates.
agent_skills_docs (str): Documentation of agent skills.
micro_agent (MicroAgent | None): Micro-agent, if specified.
"""
def __init__(
self,
prompt_dir: str,
agent_skills_docs: str,
micro_agent: MicroAgent | None = None,
):
self.prompt_dir: str = prompt_dir
self.agent_skills_docs: str = agent_skills_docs
) -> None:
"""
Initializes the PromptManager with the given prompt directory and agent skills documentation.
self.system_template: Template = self._load_template('system_prompt')
self.user_template: Template = self._load_template('user_prompt')
self.micro_agent: MicroAgent | None = micro_agent
Args:
prompt_dir: The directory containing the prompt templates.
micro_agent: The micro-agent to use for generating responses.
"""
self.prompt_dir = prompt_dir
self.micro_agent = micro_agent
# load configuration from agent.yaml
yaml_path = os.path.join(prompt_dir, 'agent.yaml')
if os.path.exists(yaml_path):
with open(yaml_path, 'r') as f:
self.config = yaml.safe_load(f)
custom_templates_dir = self.config.get('custom_templates_dir', None)
if custom_templates_dir:
custom_templates_dir = os.path.abspath(custom_templates_dir)
# prioritize custom_templates_dir over the default templates directory
self.env = Environment(
loader=FileSystemLoader([custom_templates_dir, self.prompt_dir])
)
else:
self.env = Environment(loader=FileSystemLoader(self.prompt_dir))
# load templates and blocks
template_config = self.config['template']
self.templates = {}
for name, cfg in template_config.items():
template = self._load_template(cfg['file'])
self.templates[name] = {
'template': template,
'blocks': cfg.get('blocks', []),
}
self.available_skills = self.config['agent_skills']['available_skills']
else:
# load default templates if agent.yaml is not present
self.env = Environment(loader=FileSystemLoader(self.prompt_dir))
self.templates = self._load_default_templates()
self.available_skills = []
# TODO: agent config will have tool use enabled or disabled
# to conditionally load the tools variant of agentskills
def _load_template(self, template_name: str) -> Template:
template_path = os.path.join(self.prompt_dir, f'{template_name}.j2')
if not os.path.exists(template_path):
raise FileNotFoundError(f'Prompt file {template_path} not found')
with open(template_path, 'r') as file:
return Template(file.read())
"""Loads a Jinja2 template by name."""
try:
return self.env.get_template(f'{template_name}.j2')
except TemplateNotFound:
# try to load from the prompt_dir
template_path = os.path.join(self.prompt_dir, f'{template_name}.j2')
if not os.path.exists(template_path):
raise FileNotFoundError(f'Prompt file {template_path} not found')
with open(template_path, 'r') as file:
return Template(file.read())
def _load_default_templates(self) -> dict:
"""Loads default template configuration when no agent.yaml is present.
Returns:
Dictionary with default templates.
"""
# load all default templates
templates = {}
# system prompt with standard block order
templates['system_prompt'] = {
'template': self._load_template('system_prompt'),
'blocks': [
'system_prefix',
'python_capabilities',
'bash_capabilities',
'browsing_capabilities',
'pip_capabilities',
'agent_skills',
'system_rules',
],
}
# agent skills documentation
templates['agent_skills'] = {
'template': self._load_template('agent_skills'),
'blocks': ['skill_docs'],
}
# example interactions
templates['examples'] = {
'template': self._load_template('examples'),
}
# micro-agent guidelines
templates['micro_agent'] = {
'template': self._load_template('micro_agent'),
'blocks': ['micro_agent_guidelines'],
}
# user prompt combining everything
templates['user_prompt'] = {
'template': self._load_template('user_prompt'),
'blocks': ['user_prompt', 'default_example', 'micro_agent_guidelines'],
}
return templates
def _render_blocks(self, template_name: str, **kwargs) -> str:
"""Renders template blocks in the specified order with shared context.
Args:
template_name: Name of the template to render.
**kwargs: Variables to pass to the template context.
Returns:
Rendered string combining all blocks in order.
"""
template_info = self.templates.get(template_name)
if not template_info:
logger.error(f"Template '{template_name}' not found.")
return ''
rendered_blocks = []
# create a new template context that will be shared across all blocks
context = template_info['template'].new_context(kwargs)
# render each block using the shared context
for block_name in template_info.get('blocks', []):
try:
logger.debug(f"Rendering block '{template_name}.{block_name}'")
block = template_info['template'].blocks[block_name]
rendered = ''.join(block(context))
rendered_blocks.append(rendered)
except KeyError:
logger.warning(
f"Block '{block_name}' not found in template '{template_name}'"
)
continue
return ''.join(rendered_blocks)
@property
def system_message(self) -> str:
rendered = self.system_template.render(
agent_skills_docs=self.agent_skills_docs,
"""Renders and returns the system message."""
self.env.globals['get_skill_docstring'] = self._get_skill_docstring
# render agent skills blocks first
rendered_docs = self._render_blocks(
'agent_skills', available_skills=self.available_skills
).strip()
return rendered
# then render system blocks with agent skills included
rendered = self._render_blocks('system_prompt', agent_skills_docs=rendered_docs)
return rendered.strip()
@property
def initial_user_message(self) -> str:
@@ -57,7 +194,43 @@ class PromptManager:
These additional context will convert the current generic agent
into a more specialized agent that is tailored to the user's task.
"""
rendered = self.user_template.render(
micro_agent=self.micro_agent.content if self.micro_agent else None
# render each component's blocks
rendered_examples = self._render_blocks('examples').strip()
rendered_micro_agent = self._render_blocks(
'micro_agent',
micro_agent=self.micro_agent.content if self.micro_agent else None,
).strip()
# combine in user prompt
rendered = self._render_blocks(
'user_prompt',
examples=rendered_examples,
micro_agent_content=rendered_micro_agent,
)
return rendered.strip()
def _get_skill_docstring(self, skill_name: str) -> str:
"""Retrieves the docstring of a skill function."""
module_name, function_name = skill_name.split(':')
try:
module = importlib.import_module(
f'openhands.runtime.plugins.agent_skills.{module_name}'
)
# find the function
agent_skill_fn = getattr(module, function_name)
# get the function signature with parameter names, types and return type
fn_signature = f'{agent_skill_fn.__name__}' + str(signature(agent_skill_fn))
doc = agent_skill_fn.__doc__
# remove indentation from docstring and extra empty lines
doc = '\n'.join(filter(None, map(lambda x: x.strip(), doc.split('\n'))))
# now add a consistent 4 indentation
doc = '\n'.join(map(lambda x: ' ' * 4 + x, doc.split('\n')))
return f'{fn_signature}\n{doc}'
except (ImportError, AttributeError) as e:
logger.error(e)
return f'Documentation not found for skill: {skill_name}'
+186 -12
View File
@@ -10,11 +10,19 @@ from openhands.utils.prompt import PromptManager
@pytest.fixture
def prompt_dir(tmp_path):
# Copy contents from "openhands/agenthub/codeact_agent" to the temp directory
shutil.copytree('openhands/agenthub/codeact_agent', tmp_path, dirs_exist_ok=True)
"""Creates a temporary directory with CodeAct agent templates.
# Return the temporary directory path
return tmp_path
Copies the entire codeact_agent directory structure to a temp location
to avoid modifying the original files during tests.
"""
# Copy contents from "openhands/agenthub/codeact_agent" to the temp directory
source = 'openhands/agenthub/codeact_agent'
shutil.copytree(source, tmp_path, dirs_exist_ok=True)
# Return path to the prompts directory
return str(
os.path.join(tmp_path, 'prompts')
) # Points to the correct prompts directory
SAMPLE_AGENT_SKILLS_DOCS = """Sample agent skills documentation"""
@@ -26,10 +34,10 @@ def agent_skills_docs():
def test_prompt_manager_without_micro_agent(prompt_dir, agent_skills_docs):
manager = PromptManager(prompt_dir, agent_skills_docs)
manager = PromptManager(prompt_dir)
assert manager.prompt_dir == prompt_dir
assert manager.agent_skills_docs == agent_skills_docs
# assert manager.agent_skills_docs == agent_skills_docs
assert manager.micro_agent is None
assert isinstance(manager.system_message, str)
@@ -37,7 +45,7 @@ def test_prompt_manager_without_micro_agent(prompt_dir, agent_skills_docs):
"A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed answers to the user's questions."
in manager.system_message
)
assert SAMPLE_AGENT_SKILLS_DOCS in manager.system_message
# assert SAMPLE_AGENT_SKILLS_DOCS in manager.system_message
assert isinstance(manager.initial_user_message, str)
assert '--- BEGIN OF GUIDELINE ---' not in manager.initial_user_message
assert '--- END OF GUIDELINE ---' not in manager.initial_user_message
@@ -64,12 +72,11 @@ def test_prompt_manager_with_micro_agent(prompt_dir, agent_skills_docs):
manager = PromptManager(
prompt_dir=prompt_dir,
agent_skills_docs=agent_skills_docs,
micro_agent=mock_micro_agent,
)
assert manager.prompt_dir == prompt_dir
assert manager.agent_skills_docs == agent_skills_docs
# assert manager.agent_skills_docs == agent_skills_docs
assert manager.micro_agent == mock_micro_agent
assert isinstance(manager.system_message, str)
@@ -77,7 +84,7 @@ def test_prompt_manager_with_micro_agent(prompt_dir, agent_skills_docs):
"A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed answers to the user's questions."
in manager.system_message
)
assert SAMPLE_AGENT_SKILLS_DOCS in manager.system_message
# assert SAMPLE_AGENT_SKILLS_DOCS in manager.system_message
assert isinstance(manager.initial_user_message, str)
assert (
@@ -106,11 +113,178 @@ def test_prompt_manager_template_rendering(prompt_dir, agent_skills_docs):
with open(os.path.join(prompt_dir, 'user_prompt.j2'), 'w') as f:
f.write('User prompt: {{ micro_agent }}')
manager = PromptManager(prompt_dir, agent_skills_docs)
manager = PromptManager(prompt_dir)
assert manager.system_message == f'System prompt: {agent_skills_docs}'
# assert manager.system_message == f'System prompt: {agent_skills_docs}'
assert manager.initial_user_message == 'User prompt: None'
# Clean up temporary files
os.remove(os.path.join(prompt_dir, 'system_prompt.j2'))
os.remove(os.path.join(prompt_dir, 'user_prompt.j2'))
def test_prompt_manager_loads_agent_skill(prompt_dir):
manager = PromptManager(prompt_dir)
assert (
'open_file(path: str, line_number: int | None = 1, context_lines: int | None = 100) -> None'
in manager.system_message
)
def test_prompt_manager_block_rendering(prompt_dir):
"""Test that blocks are rendered correctly from templates."""
manager = PromptManager(prompt_dir)
# System blocks should contain core capabilities
system_msg = manager.system_message
assert (
'A chat between a curious user and an artificial intelligence assistant'
in system_msg
) # system_prefix block
assert '<execute_ipython>' in system_msg # python_capabilities block
assert '<execute_bash>' in system_msg # bash_capabilities block
assert 'browse the Internet' in system_msg # browsing_capabilities block
assert '%pip install' in system_msg # pip_capabilities block
def test_prompt_manager_agent_skills_blocks(prompt_dir):
"""Test that agent skills blocks are rendered with proper docstrings.
This ensures skills documentation is correctly loaded and formatted."""
manager = PromptManager(prompt_dir)
# Check if specific skill docstrings are present
skills_content = manager._render_blocks(
'agent_skills', available_skills=['file_ops:open_file']
)
# Should contain function signature and docstring
assert 'open_file(path: str' in skills_content
assert 'Opens and displays the content of a file' in skills_content
def test_prompt_manager_micro_agent_blocks(prompt_dir):
"""Test micro-agent block rendering with and without micro-agent content.
Verifies conditional rendering of micro-agent guidelines."""
# Test without micro-agent
manager = PromptManager(prompt_dir)
micro_content = manager._render_blocks('micro_agent')
assert '--- BEGIN OF GUIDELINE ---' not in micro_content
# Test with micro-agent
mock_micro_agent = Mock(spec=MicroAgent)
mock_micro_agent.content = 'Test micro-agent content'
manager = PromptManager(prompt_dir, micro_agent=mock_micro_agent)
micro_content = manager._render_blocks(
'micro_agent', micro_agent=mock_micro_agent.content
)
assert '--- BEGIN OF GUIDELINE ---' in micro_content
assert 'Test micro-agent content' in micro_content
def test_prompt_manager_custom_block_order(prompt_dir):
"""Test that blocks are rendered in the order specified"""
# Create a custom agent.yaml with specific block order
custom_yaml = """
template:
system_prompt:
file: "system_prompt"
blocks:
- system_prefix
- python_capabilities
- agent_skills
"""
yaml_path = os.path.join(prompt_dir, 'agent.yaml')
with open(yaml_path, 'w') as f:
f.write(custom_yaml)
manager = PromptManager(prompt_dir)
system_msg = manager.system_message
# Verify block order
prefix_pos = system_msg.find('You are a new generation AI assistant')
python_pos = system_msg.find('<execute_ipython>')
skills_pos = system_msg.find('open_file(path: str')
# Blocks should appear in specified order
assert prefix_pos < python_pos < skills_pos
assert '<execute_bash>' not in system_msg # This block wasn't included
def test_prompt_manager_missing_blocks(prompt_dir):
"""Test graceful handling of missing or undefined blocks.
Ensures the system doesn't crash on template/block misconfigurations."""
# Create yaml with non-existent block
custom_yaml = """
template:
system_prompt:
file: "system_prompt"
blocks:
- nonexistent_block
"""
yaml_path = os.path.join(prompt_dir, 'agent.yaml')
with open(yaml_path, 'w') as f:
f.write(custom_yaml)
# Should not raise exception, but log warning and skip block
manager = PromptManager(prompt_dir)
assert manager.system_message.strip() != ''
def test_prompt_manager_block_inheritance(prompt_dir):
"""Test that blocks can be overridden in custom templates.
Verifies template inheritance and customization capabilities."""
# Create custom templates directory
custom_dir = os.path.join(prompt_dir, 'user_templates')
os.makedirs(custom_dir, exist_ok=True)
# Create custom system template that overrides a block
custom_template = """
{% block system_prefix %}
Custom system prefix override
{% endblock %}
"""
with open(os.path.join(custom_dir, 'system_prompt.j2'), 'w') as f:
f.write(custom_template)
manager = PromptManager(prompt_dir)
assert 'Custom system prefix override' in manager.system_message
def test_prompt_manager_default_templates(prompt_dir):
"""Test that default templates are loaded correctly when no yaml exists.
Verifies the fallback configuration works as expected."""
# Remove agent.yaml to force default template loading
yaml_path = os.path.join(prompt_dir, 'agent.yaml')
if os.path.exists(yaml_path):
os.remove(yaml_path)
manager = PromptManager(prompt_dir)
templates = manager.templates
# Check structure matches expected defaults
assert 'system_prompt' in templates
assert 'agent_skills' in templates
assert 'examples' in templates
assert 'micro_agent' in templates
assert 'user_prompt' in templates
# Verify default block configuration
system_blocks = templates['system_prompt']['blocks']
assert 'system_prefix' in system_blocks
assert 'python_capabilities' in system_blocks
assert 'bash_capabilities' in system_blocks
# Verify templates are loaded and functional
assert isinstance(manager.system_message, str)
assert isinstance(manager.initial_user_message, str)
def test_prompt_manager_agent_skills_block_name(prompt_dir):
"""Verify that agent_skills template has the correct block name."""
manager = PromptManager(prompt_dir)
template_info = manager.templates['agent_skills']
assert 'skill_docs' in template_info['blocks']