Use keyword matching for CodeAct microagents (#4568)

Co-authored-by: Xingyao Wang <xingyao@all-hands.dev>
This commit is contained in:
Robert Brennan 2024-11-09 11:25:02 -05:00 committed by GitHub
parent 67c8915d51
commit be82832eb1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 200 additions and 253 deletions

View File

@ -35,7 +35,8 @@ def codeact_user_response_eda(state: State) -> str:
# retrieve the latest model message from history
if state.history:
model_guess = state.get_last_agent_message()
last_agent_message = state.get_last_agent_message()
model_guess = last_agent_message.content if last_agent_message else ''
assert game is not None, 'Game is not initialized.'
msg = game.generate_user_response(model_guess)
@ -140,7 +141,8 @@ def process_instance(
if state is None:
raise ValueError('State should not be None.')
final_message = state.get_last_agent_message()
last_agent_message = state.get_last_agent_message()
final_message = last_agent_message.content if last_agent_message else ''
logger.info(f'Final message: {final_message} | Ground truth: {instance["text"]}')
test_result = game.reward()

View File

@ -102,7 +102,8 @@ def process_instance(
raise ValueError('State should not be None.')
# retrieve the last message from the agent
model_answer_raw = state.get_last_agent_message()
last_agent_message = state.get_last_agent_message()
model_answer_raw = last_agent_message.content if last_agent_message else ''
# attempt to parse model_answer
ast_eval_fn = instance['ast_eval']

View File

@ -127,7 +127,8 @@ def process_instance(instance: Any, metadata: EvalMetadata, reset_logger: bool =
raise ValueError('State should not be None.')
# retrieve the last message from the agent
model_answer_raw = state.get_last_agent_message()
last_agent_message = state.get_last_agent_message()
model_answer_raw = last_agent_message.content if last_agent_message else ''
# attempt to parse model_answer
correct = eval_answer(str(model_answer_raw), str(answer))

View File

@ -43,10 +43,7 @@ export function ProjectMenuCard({
posthog.capture("push_to_github_button_clicked");
const rawEvent = {
content: `
Let's push the code to GitHub.
If we're currently on the openhands-workspace branch, please create a new branch with a descriptive name.
Commit any changes and push them to the remote repository.
Finally, open up a pull request using the GitHub API and the token in the GITHUB_TOKEN environment variable, then show me the URL of the pull request.
Please push the changes to GitHub and open a pull request.
`,
imageUrls: [],
timestamp: new Date().toISOString(),

View File

@ -39,7 +39,6 @@ from openhands.runtime.plugins import (
JupyterRequirement,
PluginRequirement,
)
from openhands.utils.microagent import MicroAgent
from openhands.utils.prompt import PromptManager
@ -86,16 +85,6 @@ class CodeActAgent(Agent):
super().__init__(llm, config)
self.reset()
self.micro_agent = (
MicroAgent(
os.path.join(
os.path.dirname(__file__), 'micro', f'{config.micro_agent_name}.md'
)
)
if config.micro_agent_name
else None
)
self.function_calling_active = self.config.function_calling
if self.function_calling_active and not self.llm.is_function_calling_active():
logger.warning(
@ -105,7 +94,6 @@ class CodeActAgent(Agent):
self.function_calling_active = False
if self.function_calling_active:
# Function calling mode
self.tools = codeact_function_calling.get_tools(
codeact_enable_browsing=self.config.codeact_enable_browsing,
codeact_enable_jupyter=self.config.codeact_enable_jupyter,
@ -114,18 +102,17 @@ class CodeActAgent(Agent):
logger.debug(
f'TOOLS loaded for CodeActAgent: {json.dumps(self.tools, indent=2)}'
)
self.system_prompt = codeact_function_calling.SYSTEM_PROMPT
self.initial_user_message = None
self.prompt_manager = PromptManager(
microagent_dir=os.path.join(os.path.dirname(__file__), 'micro'),
prompt_dir=os.path.join(os.path.dirname(__file__), 'prompts', 'tools'),
)
else:
# Non-function-calling mode
self.action_parser = CodeActResponseParser()
self.prompt_manager = PromptManager(
prompt_dir=os.path.join(os.path.dirname(__file__)),
microagent_dir=os.path.join(os.path.dirname(__file__), 'micro'),
prompt_dir=os.path.join(os.path.dirname(__file__), 'prompts', 'default'),
agent_skills_docs=AgentSkillsRequirement.documentation,
micro_agent=self.micro_agent,
)
self.system_prompt = self.prompt_manager.system_message
self.initial_user_message = self.prompt_manager.initial_user_message
self.pending_actions: deque[Action] = deque()
@ -337,8 +324,8 @@ class CodeActAgent(Agent):
return self.pending_actions.popleft()
# if we're done, go back
last_user_message = state.get_last_user_message()
if last_user_message and last_user_message.strip() == '/exit':
latest_user_message = state.get_last_user_message()
if latest_user_message and latest_user_message.content.strip() == '/exit':
return AgentFinishAction()
# prepare what we want to send to the LLM
@ -403,17 +390,19 @@ class CodeActAgent(Agent):
role='system',
content=[
TextContent(
text=self.system_prompt,
cache_prompt=self.llm.is_caching_prompt_active(), # Cache system prompt
text=self.prompt_manager.get_system_message(),
cache_prompt=self.llm.is_caching_prompt_active(),
)
],
)
]
if self.initial_user_message:
example_message = self.prompt_manager.get_example_user_message()
if example_message:
messages.append(
Message(
role='user',
content=[TextContent(text=self.initial_user_message)],
content=[TextContent(text=example_message)],
cache_prompt=self.llm.is_caching_prompt_active(),
)
)
@ -462,8 +451,9 @@ class CodeActAgent(Agent):
pending_tool_call_action_messages.pop(response_id)
for message in messages_to_add:
# add regular message
if message:
if message.role == 'user':
self.prompt_manager.enhance_message(message)
# handle error if the message is the SAME role as the previous message
# litellm.exceptions.BadRequestError: litellm.BadRequestError: OpenAIException - Error code: 400 - {'detail': 'Only supports u/a/u/a/u...'}
# there shouldn't be two consecutive messages from the same role
@ -493,23 +483,6 @@ class CodeActAgent(Agent):
break
if not self.function_calling_active:
# The latest user message is important:
# we want to remind the agent of the environment constraints
latest_user_message = next(
islice(
(
m
for m in reversed(messages)
if m.role == 'user'
and any(isinstance(c, TextContent) for c in m.content)
),
1,
),
None,
)
# do not add this for function calling
if latest_user_message:
reminder_text = f'\n\nENVIRONMENT REMINDER: You have {state.max_iterations - state.iteration} turns left to complete the task. When finished reply with <finish></finish>.'
latest_user_message.content.append(TextContent(text=reminder_text))
self.prompt_manager.add_turns_left_reminder(messages, state)
return messages

View File

@ -25,14 +25,6 @@ from openhands.events.action import (
)
from openhands.events.tool import ToolCallMetadata
SYSTEM_PROMPT = """You are OpenHands agent, a helpful AI assistant that can interact with a computer to solve tasks.
<IMPORTANT>
* If user provides a path, you should NOT assume it's relative to the current working directory. Instead, you should explore the file system to find the file before working on it.
* When configuring git credentials, use "openhands" as the user.name and "openhands@all-hands.dev" as the user.email by default, unless explicitly instructed otherwise.
* The assistant MUST NOT include comments in the code unless they are necessary to describe non-obvious behavior.
</IMPORTANT>
"""
_BASH_DESCRIPTION = """Execute a bash command in the terminal.
* Long running commands: For commands that may run indefinitely, it should be run in the background and the output should be redirected to a file, e.g. command = `python3 app.py > server.log 2>&1 &`.
* Interactive: If a bash command returns exit code `-1`, this means the process is not yet finished. The assistant must then send a second call to terminal with an empty `command` (which will retrieve any additional logs), or it can send additional text (set `command` to the text) to STDIN of the running process, or it can send command=`ctrl+c` to interrupt the process.

View File

@ -0,0 +1,9 @@
---
name: flarglebargle
agent: CodeActAgent
triggers:
- flarglebargle
---
IMPORTANT! The user has said the magic word "flarglebargle". You must
only respond with a message telling them how smart they are

View File

@ -1,69 +1,31 @@
---
name: github
agent: CodeActAgent
require_env_var:
SANDBOX_ENV_GITHUB_TOKEN: "Create a GitHub Personal Access Token (https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) and set it as SANDBOX_GITHUB_TOKEN in your environment variables."
triggers:
- github
- git
---
# How to Interact with Github
You have access to an environment variable, `GITHUB_TOKEN`, which allows you to interact with
the GitHub API.
## Environment Variable Available
You can use `curl` with the `GITHUB_TOKEN` to interact with GitHub's API.
ALWAYS use the GitHub API for operations instead of a web browser.
- `GITHUB_TOKEN`: A read-only token for Github.
## Using GitHub's RESTful API
Use `curl` with the `GITHUB_TOKEN` to interact with GitHub's API. Here are some common operations:
Here's a template for API calls:
```sh
curl -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/{endpoint}"
Here are some instructions for pushing, but ONLY do this if the user asks you to:
* NEVER push directly to the `main` or `master` branch
* Git config (username and email) is pre-set. Do not modify.
* You may already be on a branch called `openhands-workspace`. Create a new branch with a better name before pushing.
* Use the GitHub API to create a pull request, if you haven't already
* Use the main branch as the base branch, unless the user requests otherwise
* After opening or updating a pull request, send the user a short message with a link to the pull request.
* Do all of the above in as few steps as possible. E.g. you could open a PR with one step by running the following bash commands:
```bash
git checkout -b create-widget
git add .
git commit -m "Create widget"
git push origin create-widget
curl -X POST "https://api.github.com/repos/CodeActOrg/openhands/pulls" \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-d '{"title":"Create widget","head":"create-widget","base":"openhands-workspace"}'
```
First replace `{endpoint}` with the specific API path. Common operations:
1. View an issue or pull request:
- Issues: `/repos/{owner}/{repo}/issues/{issue_number}`
- Pull requests: `/repos/{owner}/{repo}/pulls/{pull_request_number}`
2. List repository issues or pull requests:
- Issues: `/repos/{owner}/{repo}/issues`
- Pull requests: `/repos/{owner}/{repo}/pulls`
3. Search issues or pull requests:
- `/search/issues?q=repo:{owner}/{repo}+is:{type}+{search_term}+state:{state}`
- Replace `{type}` with `issue` or `pr`
4. List repository branches:
`/repos/{owner}/{repo}/branches`
5. Get commit details:
`/repos/{owner}/{repo}/commits/{commit_sha}`
6. Get repository details:
`/repos/{owner}/{repo}`
7. Get user information:
`/user`
8. Search repositories:
`/search/repositories?q={query}`
9. Get rate limit status:
`/rate_limit`
Replace `{owner}`, `{repo}`, `{commit_sha}`, `{issue_number}`, `{pull_request_number}`,
`{search_term}`, `{state}`, and `{query}` with appropriate values.
## Important Notes
1. Always use the GitHub API for operations instead of a web browser.
2. The `GITHUB_TOKEN` is read-only. Avoid operations that require write access.
3. Git config (username and email) is pre-set. Do not modify.
4. Edit and test code locally. Never push directly to remote.
5. Verify correct branch before committing.
6. Commit changes frequently.
7. If the issue or task is ambiguous or lacks sufficient detail, always request clarification from the user before proceeding.
8. You should avoid using command line tools like `sed` for file editing.

View File

@ -215,12 +215,5 @@ The server is running on port 5000 with PID 126. You can access the list of numb
{% 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!

View File

@ -0,0 +1,7 @@
You are OpenHands agent, a helpful AI assistant that can interact with a computer to solve tasks.
<IMPORTANT>
* If user provides a path, you should NOT assume it's relative to the current working directory. Instead, you should explore the file system to find the file before working on it.
* When configuring git credentials, use "openhands" as the user.name and "openhands@all-hands.dev" as the user.email by default, unless explicitly instructed otherwise.
* The assistant MUST NOT include comments in the code unless they are necessary to describe non-obvious behavior.
</IMPORTANT>

View File

@ -155,7 +155,7 @@ class CodeActSWEAgent(Agent):
"""
# if we're done, go back
last_user_message = state.get_last_user_message()
if last_user_message and last_user_message.strip() == '/exit':
if last_user_message and last_user_message.content.strip() == '/exit':
return AgentFinishAction()
# prepare what we want to send to the LLM

View File

@ -156,14 +156,14 @@ class State:
return last_user_message, last_user_message_image_urls
def get_last_agent_message(self) -> str | None:
def get_last_agent_message(self) -> MessageAction | None:
for event in reversed(self.history):
if isinstance(event, MessageAction) and event.source == EventSource.AGENT:
return event.content
return event
return None
def get_last_user_message(self) -> str | None:
def get_last_user_message(self) -> MessageAction | None:
for event in reversed(self.history):
if isinstance(event, MessageAction) and event.source == EventSource.USER:
return event.content
return event
return None

View File

@ -3,15 +3,11 @@ import os
import frontmatter
import pydantic
from openhands.controller.agent import Agent
from openhands.core.exceptions import MicroAgentValidationError
from openhands.core.logger import openhands_logger as logger
class MicroAgentMetadata(pydantic.BaseModel):
name: str
agent: str
require_env_var: dict[str, str]
triggers: list[str] = []
class MicroAgent:
@ -23,22 +19,30 @@ class MicroAgent:
self._loaded = frontmatter.load(file)
self._content = self._loaded.content
self._metadata = MicroAgentMetadata(**self._loaded.metadata)
self._validate_micro_agent()
def get_trigger(self, message: str) -> str | None:
message = message.lower()
for trigger in self.triggers:
if trigger.lower() in message:
return trigger
return None
@property
def content(self) -> str:
return self._content
def _validate_micro_agent(self):
logger.debug(
f'Loading and validating micro agent [{self._metadata.name}] based on [{self._metadata.agent}]'
)
# Make sure the agent is registered
agent_cls = Agent.get_cls(self._metadata.agent)
assert agent_cls is not None
# Make sure the environment variables are set
for env_var, instruction in self._metadata.require_env_var.items():
if env_var not in os.environ:
raise MicroAgentValidationError(
f'Environment variable [{env_var}] is required by micro agent [{self._metadata.name}] but not set. {instruction}'
)
@property
def metadata(self) -> MicroAgentMetadata:
return self._metadata
@property
def name(self) -> str:
return self._metadata.name
@property
def triggers(self) -> list[str]:
return self._metadata.triggers
@property
def agent(self) -> str:
return self._metadata.agent

View File

@ -1,7 +1,10 @@
import os
from itertools import islice
from jinja2 import Template
from openhands.controller.state.state import State
from openhands.core.message import Message, TextContent
from openhands.utils.microagent import MicroAgent
@ -16,21 +19,31 @@ 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,
microagent_dir: str = '',
agent_skills_docs: str = '',
):
self.prompt_dir: str = prompt_dir
self.agent_skills_docs: str = agent_skills_docs
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
self.microagents: dict = {}
microagent_files = []
if microagent_dir:
microagent_files = [
os.path.join(microagent_dir, f)
for f in os.listdir(microagent_dir)
if f.endswith('.md')
]
for microagent_file in microagent_files:
microagent = MicroAgent(microagent_file)
self.microagents[microagent.name] = microagent
def _load_template(self, template_name: str) -> Template:
template_path = os.path.join(self.prompt_dir, f'{template_name}.j2')
@ -39,15 +52,13 @@ class PromptManager:
with open(template_path, 'r') as file:
return Template(file.read())
@property
def system_message(self) -> str:
def get_system_message(self) -> str:
rendered = self.system_template.render(
agent_skills_docs=self.agent_skills_docs,
).strip()
return rendered
@property
def initial_user_message(self) -> str:
def get_example_user_message(self) -> str:
"""This is the initial user message provided to the agent
before *actual* user instructions are provided.
@ -57,7 +68,39 @@ 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
return self.user_template.render().strip()
def enhance_message(self, message: Message) -> None:
"""Enhance the user message with additional context.
This method is used to enhance the user message with additional context
about the user's task. The additional context will convert the current
generic agent into a more specialized agent that is tailored to the user's task.
"""
if not message.content:
return
message_content = message.content[0].text
for microagent in self.microagents.values():
trigger = microagent.get_trigger(message_content)
if trigger:
micro_text = f'<extra_info>\nThe following information has been included based on a keyword match for "{trigger}". It may or may not be relevant to the user\'s request.'
micro_text += '\n\n' + microagent.content
micro_text += '\n</extra_info>'
message.content.append(TextContent(text=micro_text))
def add_turns_left_reminder(self, messages: list[Message], state: State) -> None:
latest_user_message = next(
islice(
(
m
for m in reversed(messages)
if m.role == 'user'
and any(isinstance(c, TextContent) for c in m.content)
),
1,
),
None,
)
return rendered.strip()
if latest_user_message:
reminder_text = f'\n\nENVIRONMENT REMINDER: You have {state.max_iterations - state.iteration} turns left to complete the task. When finished reply with <finish></finish>.'
latest_user_message.content.append(TextContent(text=reminder_text))

View File

@ -1,13 +1,8 @@
import os
import pytest
from pytest import MonkeyPatch
import openhands.agenthub # noqa: F401
from openhands.core.exceptions import (
AgentNotRegisteredError,
MicroAgentValidationError,
)
from openhands.utils.microagent import MicroAgent
CONTENT = (
@ -34,40 +29,3 @@ def test_micro_agent_load(tmp_path, monkeypatch: MonkeyPatch):
micro_agent = MicroAgent(os.path.join(tmp_path, 'dummy.md'))
assert micro_agent is not None
assert micro_agent.content == CONTENT.strip()
def test_not_existing_agent(tmp_path, monkeypatch: MonkeyPatch):
with open(os.path.join(tmp_path, 'dummy.md'), 'w') as f:
f.write(
(
'---\n'
'name: dummy\n'
'agent: NotExistingAgent\n'
'require_env_var:\n'
' SANDBOX_OPENHANDS_TEST_ENV_VAR: "Set this environment variable for testing purposes"\n'
'---\n' + CONTENT
)
)
monkeypatch.setenv('SANDBOX_OPENHANDS_TEST_ENV_VAR', 'dummy_value')
with pytest.raises(AgentNotRegisteredError):
MicroAgent(os.path.join(tmp_path, 'dummy.md'))
def test_not_existing_env_var(tmp_path):
with open(os.path.join(tmp_path, 'dummy.md'), 'w') as f:
f.write(
(
'---\n'
'name: dummy\n'
'agent: CodeActAgent\n'
'require_env_var:\n'
' SANDBOX_OPENHANDS_TEST_ENV_VAR: "Set this environment variable for testing purposes"\n'
'---\n' + CONTENT
)
)
with pytest.raises(MicroAgentValidationError) as excinfo:
MicroAgent(os.path.join(tmp_path, 'dummy.md'))
assert 'Set this environment variable for testing purposes' in str(excinfo.value)

View File

@ -1,9 +1,9 @@
import os
import shutil
from unittest.mock import Mock
import pytest
from openhands.core.message import Message, TextContent
from openhands.utils.microagent import MicroAgent
from openhands.utils.prompt import PromptManager
@ -11,7 +11,9 @@ 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)
shutil.copytree(
'openhands/agenthub/codeact_agent/prompts/default', tmp_path, dirs_exist_ok=True
)
# Return the temporary directory path
return tmp_path
@ -25,78 +27,79 @@ def agent_skills_docs():
return SAMPLE_AGENT_SKILLS_DOCS
def test_prompt_manager_without_micro_agent(prompt_dir, agent_skills_docs):
manager = PromptManager(prompt_dir, agent_skills_docs)
def test_prompt_manager_without_microagent(prompt_dir, agent_skills_docs):
manager = PromptManager(
prompt_dir, microagent_dir='', agent_skills_docs=agent_skills_docs
)
assert manager.prompt_dir == prompt_dir
assert manager.agent_skills_docs == agent_skills_docs
assert manager.micro_agent is None
assert len(manager.microagents) == 0
assert isinstance(manager.system_message, str)
assert isinstance(manager.get_system_message(), str)
assert (
"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
in manager.get_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
assert "NOW, LET'S START!" in manager.initial_user_message
assert 'micro_agent' not in manager.initial_user_message
assert SAMPLE_AGENT_SKILLS_DOCS in manager.get_system_message()
assert isinstance(manager.get_example_user_message(), str)
assert '--- BEGIN OF GUIDELINE ---' not in manager.get_example_user_message()
assert '--- END OF GUIDELINE ---' not in manager.get_example_user_message()
assert "NOW, LET'S START!" in manager.get_example_user_message()
assert 'microagent' not in manager.get_example_user_message()
def test_prompt_manager_with_micro_agent(prompt_dir, agent_skills_docs):
micro_agent_name = 'test_micro_agent'
micro_agent_content = (
'## Micro Agent\n'
'This is a test micro agent.\n'
'It is used to test the prompt manager.\n'
)
def test_prompt_manager_with_microagent(prompt_dir, agent_skills_docs):
microagent_name = 'test_microagent'
microagent_content = """
---
name: flarglebargle
agent: CodeActAgent
triggers:
- flarglebargle
---
IMPORTANT! The user has said the magic word "flarglebargle". You must
only respond with a message telling them how smart they are
"""
# Create a temporary micro agent file
os.makedirs(os.path.join(prompt_dir, 'micro'), exist_ok=True)
with open(os.path.join(prompt_dir, 'micro', f'{micro_agent_name}.md'), 'w') as f:
f.write(micro_agent_content)
# Mock MicroAgent
mock_micro_agent = Mock(spec=MicroAgent)
mock_micro_agent.content = micro_agent_content
with open(os.path.join(prompt_dir, 'micro', f'{microagent_name}.md'), 'w') as f:
f.write(microagent_content)
manager = PromptManager(
prompt_dir=prompt_dir,
microagent_dir=os.path.join(prompt_dir, 'micro'),
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.micro_agent == mock_micro_agent
assert len(manager.microagents) == 1
assert isinstance(manager.system_message, str)
assert isinstance(manager.get_system_message(), str)
assert (
"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
in manager.get_system_message()
)
assert SAMPLE_AGENT_SKILLS_DOCS in manager.system_message
assert SAMPLE_AGENT_SKILLS_DOCS in manager.get_system_message()
assert isinstance(manager.initial_user_message, str)
assert (
'--- BEGIN OF GUIDELINE ---\n'
+ 'The following information may assist you in completing your task:\n\n'
+ micro_agent_content
+ '\n'
+ '--- END OF GUIDELINE ---\n'
+ "\n\nNOW, LET'S START!"
) in manager.initial_user_message
assert micro_agent_content in manager.initial_user_message
assert isinstance(manager.get_example_user_message(), str)
# Clean up the temporary file
os.remove(os.path.join(prompt_dir, 'micro', f'{micro_agent_name}.md'))
message = Message(
role='user',
content=[TextContent(text='Hello, flarglebargle!')],
)
manager.enhance_message(message)
assert 'magic word' in message.content[1].text
os.remove(os.path.join(prompt_dir, 'micro', f'{microagent_name}.md'))
def test_prompt_manager_file_not_found(prompt_dir, agent_skills_docs):
with pytest.raises(FileNotFoundError):
MicroAgent(os.path.join(prompt_dir, 'micro', 'non_existent_micro_agent.md'))
MicroAgent(os.path.join(prompt_dir, 'micro', 'non_existent_microagent.md'))
def test_prompt_manager_template_rendering(prompt_dir, agent_skills_docs):
@ -104,12 +107,14 @@ def test_prompt_manager_template_rendering(prompt_dir, agent_skills_docs):
with open(os.path.join(prompt_dir, 'system_prompt.j2'), 'w') as f:
f.write('System prompt: {{ agent_skills_docs }}')
with open(os.path.join(prompt_dir, 'user_prompt.j2'), 'w') as f:
f.write('User prompt: {{ micro_agent }}')
f.write('User prompt: foo')
manager = PromptManager(prompt_dir, agent_skills_docs)
manager = PromptManager(
prompt_dir, microagent_dir='', agent_skills_docs=agent_skills_docs
)
assert manager.system_message == f'System prompt: {agent_skills_docs}'
assert manager.initial_user_message == 'User prompt: None'
assert manager.get_system_message() == f'System prompt: {agent_skills_docs}'
assert manager.get_example_user_message() == 'User prompt: foo'
# Clean up temporary files
os.remove(os.path.join(prompt_dir, 'system_prompt.j2'))