[Refactor]: Update resolver instructions (#8601)

Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
This commit is contained in:
Rohit Malhotra 2025-05-21 19:26:48 -04:00 committed by GitHub
parent ac87ff8d27
commit 097f757c65
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 261 additions and 201 deletions

View File

@ -1,5 +1,4 @@
import { ConfirmationButtons } from "#/components/shared/buttons/confirmation-buttons";
import { I18nKey } from "#/i18n/declaration";
import { OpenHandsAction } from "#/types/core/actions";
import {
isUserMessage,
@ -16,7 +15,6 @@ import { ChatMessage } from "./chat-message";
import { ErrorMessage } from "./error-message";
import { getObservationResult } from "./event-content-helpers/get-observation-result";
import { getEventContent } from "./event-content-helpers/get-event-content";
import { ExpandableMessage } from "./expandable-message";
import { GenericEventMessage } from "./generic-event-message";
const hasThoughtProperty = (
@ -26,7 +24,6 @@ const hasThoughtProperty = (
interface EventMessageProps {
event: OpenHandsAction | OpenHandsObservation;
hasObservationPair: boolean;
isFirstMessageWithResolverTrigger: boolean;
isAwaitingUserConfirmation: boolean;
isLastMessage: boolean;
}
@ -34,32 +31,12 @@ interface EventMessageProps {
export function EventMessage({
event,
hasObservationPair,
isFirstMessageWithResolverTrigger,
isAwaitingUserConfirmation,
isLastMessage,
}: EventMessageProps) {
const shouldShowConfirmationButtons =
isLastMessage && event.source === "agent" && isAwaitingUserConfirmation;
const isFirstUserMessageWithResolverTrigger =
isFirstMessageWithResolverTrigger && isUserMessage(event);
// Special case: First user message with resolver trigger
if (isFirstUserMessageWithResolverTrigger) {
return (
<div>
<ExpandableMessage
type="action"
message={event.args.content}
id={I18nKey.CHAT$RESOLVER_INSTRUCTIONS}
/>
{event.args.image_urls && event.args.image_urls.length > 0 && (
<ImageCarousel size="small" images={event.args.image_urls} />
)}
</div>
);
}
if (isErrorObservation(event)) {
return (
<ErrorMessage

View File

@ -1,6 +1,4 @@
import React from "react";
import { useUserConversation } from "#/hooks/query/use-user-conversation";
import { useConversation } from "#/context/conversation-context";
import { OpenHandsAction } from "#/types/core/actions";
import { OpenHandsObservation } from "#/types/core/observations";
import { isOpenHandsAction, isOpenHandsObservation } from "#/types/core/guards";
@ -38,14 +36,9 @@ interface MessagesProps {
export const Messages: React.FC<MessagesProps> = React.memo(
({ messages, isAwaitingUserConfirmation }) => {
const { getOptimisticUserMessage } = useOptimisticUserMessage();
const { conversationId } = useConversation();
const { data: conversation } = useUserConversation(conversationId || null);
const optimisticUserMessage = getOptimisticUserMessage();
// Check if conversation metadata has trigger=resolver
const isResolverTrigger = conversation?.trigger === "resolver";
const actionHasObservationPair = React.useCallback(
(event: OpenHandsAction | OpenHandsObservation): boolean => {
if (isOpenHandsAction(event)) {
@ -66,7 +59,6 @@ export const Messages: React.FC<MessagesProps> = React.memo(
key={index}
event={message}
hasObservationPair={actionHasObservationPair(message)}
isFirstMessageWithResolverTrigger={index === 0 && isResolverTrigger}
isAwaitingUserConfirmation={isAwaitingUserConfirmation}
isLastMessage={messages.length - 1 === index}
/>

View File

@ -61,7 +61,6 @@ export enum I18nKey {
HOME$RESOLVE_MERGE_CONFLICTS = "HOME$RESOLVE_MERGE_CONFLICTS",
HOME$RESOLVE_UNRESOLVED_COMMENTS = "HOME$RESOLVE_UNRESOLVED_COMMENTS",
HOME$LAUNCH = "HOME$LAUNCH",
CHAT$RESOLVER_INSTRUCTIONS = "CHAT$RESOLVER_INSTRUCTIONS",
SETTINGS$ADVANCED = "SETTINGS$ADVANCED",
SETTINGS$BASE_URL = "SETTINGS$BASE_URL",
SETTINGS$AGENT = "SETTINGS$AGENT",

View File

@ -975,22 +975,6 @@
"de": "Starten",
"uk": "Запуск"
},
"CHAT$RESOLVER_INSTRUCTIONS": {
"en": "Resolver Instructions",
"ja": "リゾルバの指示",
"zh-CN": "解析器指令",
"zh-TW": "解析器指令",
"ko-KR": "리졸버 지침",
"no": "Resolver-instruksjoner",
"it": "Istruzioni del resolver",
"pt": "Instruções do resolvedor",
"es": "Instrucciones del resolvedor",
"ar": "تعليمات المحلل",
"fr": "Instructions du résolveur",
"tr": "Çözümleyici Talimatları",
"de": "Resolver-Anweisungen",
"uk": "Інструкції для вирішувача"
},
"SETTINGS$ADVANCED": {
"en": "Advanced",
"ja": "詳細設定",

View File

@ -0,0 +1,18 @@
You are requested to fix issue number #{{ issue_number }} in a repository.
A comment on the issue has been addressed to you.
# Steps to Handle the Comment
1. Address the comment. Use the GitHub API to read issue title, body, and comments if you need more context
2. For all changes to actual application code (e.g. in Python or Javascript), add an appropriate test to the testing directory to make sure that the issue has been fixed
3. Run the tests, and if they pass you are done!
4. You do NOT need to write new tests if there are only changes to documentation or configuration files.
When you're done, make sure to
1. Re-read the issue title, body, and comments and make sure that you have successfully implemented all requirements.
2. Use the `GITHUB_TOKEN` environment variable and GitHub API to open a new PR
3. Name the branch using `openhands/` as a prefix (e.g `openhands/update-readme`)
4. The PR description should mention that it "fixes" or "closes" the issue number
5. Make sure to leave the following sentence at the end of the PR description: `@{{ username }} can click here to [continue refining the PR]({{ conversation_url }})`

View File

@ -1,22 +1 @@
You are requested to fix issue number #{{ issue_number }} in a repository.
A comment on the issue has been addressed to you.
# Comment
{{ issue_comment }}
# Steps to Handle the Comment
1. Address the comment. Use the GitHub API to read issue title, body, and comments if you need more context
2. For all changes to actual application code (e.g. in Python or Javascript), add an appropriate test to the testing directory to make sure that the issue has been fixed
3. Run the tests, and if they pass you are done!
4. You do NOT need to write new tests if there are only changes to documentation or configuration files.
When you're done, make sure to
1. Re-read the issue title, body, and comments and make sure that you have successfully implemented all requirements.
2. Use the `GITHUB_TOKEN` environment variable and GitHub API to open a new PR
3. Name the branch using `openhands/` as a prefix (e.g `openhands/update-readme`)
4. The PR description should mention that it "fixes" or "closes" the issue number
5. Make sure to leave the following sentence at the end of the PR description: `@{{ username }} can click here to [continue refining the PR]({{ conversation_url }})`
{{ issue_comment }}

View File

@ -0,0 +1,12 @@
Your tasking is to fix an issue in your repository. Do the following
1. Read the issue body and comments using the Github API
2. For all changes to actual application code (e.g. in Python or Javascript), add an appropriate test to the testing directory to make sure that the issue has been fixed
3. Run the tests, and if they pass you are done!
4. You do NOT need to write new tests if there are only changes to documentation or configuration files.
When you're done, make sure to
1. Use the `GITHUB_TOKEN` environment variable and GitHub API to open a new PR
2. The PR description should mention that it "fixes" or "closes" the issue number
3. Make sure to leave the following sentence at the end of the PR description: `@{{ username }} can click here to [continue refining the PR]({{ conversation_url }})`

View File

@ -1,12 +1 @@
Please fix a issue number #{{ issue_number }} in your repository. Do the following
1. Read the issue body and comments using the Github API
2. For all changes to actual application code (e.g. in Python or Javascript), add an appropriate test to the testing directory to make sure that the issue has been fixed
3. Run the tests, and if they pass you are done!
4. You do NOT need to write new tests if there are only changes to documentation or configuration files.
When you're done, make sure to
1. Use the `GITHUB_TOKEN` environment variable and GitHub API to open a new PR
2. The PR description should mention that it "fixes" or "closes" the issue number
3. Make sure to leave the following sentence at the end of the PR description: `@{{ username }} can click here to [continue refining the PR]({{ conversation_url }})`
Please fix issue number #{{ issue_number }} in your repository.

View File

@ -0,0 +1,21 @@
You are checked out to branch {{ branch_name }}, which has an open PR #{{ pr_number }}.
A comment on the PR has been addressed to you. Do NOT respond to this comment via the GitHub API.
{% if file_location %} The comment is in the file `{{ file_location }}` on line #{{ line_number }}{% endif %}.
# Steps to Handle the Comment
## Understand the PR Context
Use the GitHub API to:
1. Retrieve the diff against main to understand the changes
2. Fetch the PR body and the linked issue for context
## Process the Comment
If it's a question:
1. Answer the question asked
2. DO NOT leave any comments on the PR
If it requests a code update:
1. Modify the code accordingly in the current branch
2. Push the changes to update the PR
3. DO NOT leave any comments on the PR

View File

@ -1,25 +1 @@
You are checked out to branch {{ branch_name }}, which has an open PR #{{ pr_number }}.
A comment on the PR (below) has been addressed to you. Do NOT respond to this comment via the GitHub API.
{% if file_location %} The comment is in the file `{{ file_location }}` on line #{{ line_number }}{% endif %}.
# Comment
{{ pr_comment }}
# Steps to Handle the Comment
## Understand the PR Context
Use the GitHub API to:
1. Retrieve the diff against main to understand the changes
2. Fetch the PR body and the linked issue for context
## Process the Comment
If it's a question:
1. Answer the question asked
2. DO NOT leave any comments on the PR
If it requests a code update:
1. Modify the code accordingly in the current branch
2. Push the changes to update the PR
3. DO NOT leave any comments on the PR
{{ pr_comment }}

View File

@ -115,11 +115,13 @@ class ServiceContextPR(ServiceContext):
def get_instruction(
self,
issue: Issue,
prompt_template: str,
user_instructions_prompt_template: str,
conversation_instructions_prompt_template: str,
repo_instruction: str | None = None,
) -> tuple[str, list[str]]:
) -> tuple[str, str, list[str]]:
"""Generate instruction for the agent."""
template = jinja2.Template(prompt_template)
user_instruction_template = jinja2.Template(user_instructions_prompt_template)
conversation_instructions_template = jinja2.Template(conversation_instructions_prompt_template)
images = []
issues_str = None
@ -153,15 +155,19 @@ class ServiceContextPR(ServiceContext):
thread_context = '\n---\n'.join(issue.thread_comments)
images.extend(extract_image_urls(thread_context))
instruction = template.render(
issues=issues_str,
user_instruction = user_instruction_template.render(
review_comments=review_comments_str,
review_threads=review_thread_str,
files=review_thread_file_str,
thread_context=thread_context,
repo_instruction=repo_instruction,
thread_context=thread_context
)
return instruction, images
conversation_instructions = conversation_instructions_template.render(
issues=issues_str,
repo_instruction=repo_instruction
)
return user_instruction, conversation_instructions, images
def _check_feedback_with_llm(self, prompt: str) -> tuple[bool, str]:
"""Helper function to check feedback with LLM and parse response."""
@ -330,9 +336,10 @@ class ServiceContextIssue(ServiceContext):
def get_instruction(
self,
issue: Issue,
prompt_template: str,
user_instructions_prompt_template: str,
conversation_instructions_prompt_template: str,
repo_instruction: str | None = None,
) -> tuple[str, list[str]]:
) -> tuple[str, str, list[str]]:
"""Generate instruction for the agent."""
# Format thread comments if they exist
thread_context = ''
@ -345,15 +352,18 @@ class ServiceContextIssue(ServiceContext):
images.extend(extract_image_urls(issue.body))
images.extend(extract_image_urls(thread_context))
template = jinja2.Template(prompt_template)
return (
template.render(
body=issue.title + '\n\n' + issue.body + thread_context,
repo_instruction=repo_instruction,
),
images,
user_instructions_template = jinja2.Template(user_instructions_prompt_template)
user_instructions = user_instructions_template.render(
body=issue.title + '\n\n' + issue.body + thread_context
) # Issue body and comments
conversation_instructions_template = jinja2.Template(conversation_instructions_prompt_template)
conversation_instructions = conversation_instructions_template.render(
repo_instruction=repo_instruction,
)
return user_instructions, conversation_instructions, images
def guess_success(
self, issue: Issue, history: list[Event], git_patch: str | None = None
) -> tuple[bool, None | list[bool], str]:

View File

@ -0,0 +1,7 @@
IMPORTANT: You should ONLY interact with the environment provided to you AND NEVER ASK FOR HUMAN HELP.
You SHOULD INCLUDE PROPER INDENTATION in your edit commands.{% if repo_instruction %}
Some basic information about this repository:
{{ repo_instruction }}{% endif %}
When you think you have fixed the issue through code changes, please finish the interaction.

View File

@ -0,0 +1,16 @@
The current code is an attempt at fixing one or more issues. The code is not satisfactory and follow up feedback have been provided to address this.
The feedback may be addressed to specific code files. In this case the file locations will be provided.
Please update the code based on the feedback for the repository in /workspace.
An environment has been set up for you to start working. You may assume all necessary tools are installed.
# Issues addressed
{{ issues }}
IMPORTANT: You should ONLY interact with the environment provided to you AND NEVER ASK FOR HUMAN HELP.
You SHOULD INCLUDE PROPER INDENTATION in your edit commands.{% if repo_instruction %}
Some basic information about this repository:
{{ repo_instruction }}{% endif %}
When you think you have fixed the issue through code changes, please finish the interaction.

View File

@ -1,10 +1,4 @@
The current code is an attempt at fixing one or more issues. The code is not satisfactory and follow up feedback have been provided to address this.
The feedback may be addressed to specific code files. In this case the file locations will be provided.
Please update the code based on the feedback for the repository in /workspace.
An environment has been set up for you to start working. You may assume all necessary tools are installed.
# Issues addressed
{{ issues }}
Please fix the code based on the following feedback
# Review comments
{{ review_comments }}
@ -17,11 +11,3 @@ An environment has been set up for you to start working. You may assume all nece
# PR Thread Comments
{{ thread_context }}
IMPORTANT: You should ONLY interact with the environment provided to you AND NEVER ASK FOR HUMAN HELP.
You SHOULD INCLUDE PROPER INDENTATION in your edit commands.{% if repo_instruction %}
Some basic information about this repository:
{{ repo_instruction }}{% endif %}
When you think you have fixed the issue through code changes, please finish the interaction.

View File

@ -0,0 +1,11 @@
IMPORTANT: You should ONLY interact with the environment provided to you AND NEVER ASK FOR HUMAN HELP.
You SHOULD INCLUDE PROPER INDENTATION in your edit commands.{% if repo_instruction %}
Some basic information about this repository:
{{ repo_instruction }}{% endif %}
For all changes to actual application code (e.g. in Python or Javascript), add an appropriate test to the testing directory to make sure that the issue has been fixed.
Run the tests, and if they pass you are done!
You do NOT need to write new tests if there are only changes to documentation or configuration files.
When you think you have fixed the issue through code changes, please call the finish action to end the interaction.

View File

@ -2,16 +2,4 @@ Please fix the following issue for the repository in /workspace.
An environment has been set up for you to start working. You may assume all necessary tools are installed.
# Problem Statement
{{ body }}
IMPORTANT: You should ONLY interact with the environment provided to you AND NEVER ASK FOR HUMAN HELP.
You SHOULD INCLUDE PROPER INDENTATION in your edit commands.{% if repo_instruction %}
Some basic information about this repository:
{{ repo_instruction }}{% endif %}
For all changes to actual application code (e.g. in Python or Javascript), add an appropriate test to the testing directory to make sure that the issue has been fixed.
Run the tests, and if they pass you are done!
You do NOT need to write new tests if there are only changes to documentation or configuration files.
When you think you have fixed the issue through code changes, please call the finish action to end the interaction.
{{ body }}

View File

@ -2,12 +2,4 @@ Please fix the following issue for the repository in /workspace.
An environment has been set up for you to start working. You may assume all necessary tools are installed.
# Problem Statement
{{ body }}
IMPORTANT: You should ONLY interact with the environment provided to you AND NEVER ASK FOR HUMAN HELP.
You SHOULD INCLUDE PROPER INDENTATION in your edit commands.{% if repo_instruction %}
Some basic information about this repository:
{{ repo_instruction }}{% endif %}
When you think you have fixed the issue through code changes, please finish the interaction.
{{ body }}

View File

@ -143,7 +143,12 @@ class IssueResolver:
os.path.dirname(__file__), 'prompts/resolve/basic-followup.jinja'
)
with open(prompt_file, 'r') as f:
prompt_template = f.read()
user_instructions_prompt_template = f.read()
with open(
prompt_file.replace('.jinja', '-conversation-instructions.jinja')
) as f:
conversation_instructions_prompt_template = f.read()
base_domain = args.base_domain
if base_domain is None:
@ -157,7 +162,10 @@ class IssueResolver:
self.max_iterations = args.max_iterations
self.output_dir = args.output_dir
self.llm_config = llm_config
self.prompt_template = prompt_template
self.user_instructions_prompt_template = user_instructions_prompt_template
self.conversation_instructions_prompt_template = (
conversation_instructions_prompt_template
)
self.issue_type = issue_type
self.repo_instruction = repo_instruction
self.issue_number = args.issue_number
@ -394,8 +402,13 @@ class IssueResolver:
self.initialize_runtime(runtime)
instruction, images_urls = issue_handler.get_instruction(
issue, self.prompt_template, self.repo_instruction
instruction, conversation_instructions, images_urls = (
issue_handler.get_instruction(
issue,
self.user_instructions_prompt_template,
self.conversation_instructions_prompt_template,
self.repo_instruction,
)
)
# Here's how you can run the agent (similar to the `main` function) and get the final task state
action = MessageAction(content=instruction, image_urls=images_urls)
@ -405,6 +418,7 @@ class IssueResolver:
initial_user_action=action,
runtime=runtime,
fake_user_response_fn=codeact_user_response,
conversation_instructions=conversation_instructions,
)
if state is None:
raise RuntimeError('Failed to run the agent.')

View File

@ -91,10 +91,16 @@ def mock_os():
@pytest.fixture
def mock_prompt_template():
def mock_user_instructions_template():
return 'Issue: {{ body }}\n\nPlease fix this issue.'
@pytest.fixture
def mock_conversation_instructions_template():
return 'Instructions: {{ repo_instruction }}'
@pytest.fixture
def mock_followup_prompt_template():
return 'Issue context: {{ issues }}\n\nReview comments: {{ review_comments }}\n\nReview threads: {{ review_threads }}\n\nFiles: {{ files }}\n\nThread comments: {{ thread_context }}\n\nPlease fix this issue.'
@ -422,7 +428,7 @@ async def test_process_issue(
default_mock_args,
mock_github_token,
mock_output_dir,
mock_prompt_template,
mock_user_instructions_template,
test_case,
):
"""Test the process_issue method with different scenarios."""
@ -443,7 +449,7 @@ async def test_process_issue(
# Create a resolver instance with mocked token identification
resolver = IssueResolver(default_mock_args)
resolver.prompt_template = mock_prompt_template
resolver.user_instructions_prompt_template = mock_user_instructions_template
# Mock the handler with LLM config
llm_config = LLMConfig(model='test', api_key='test')
@ -453,7 +459,11 @@ async def test_process_issue(
test_case.get('comment_success', None),
test_case['expected_explanation'],
)
handler_instance.get_instruction.return_value = ('Test instruction', [])
handler_instance.get_instruction.return_value = (
'Test instruction',
'Test conversation instructions',
[],
)
handler_instance.issue_type = 'pr' if test_case.get('is_pr', False) else 'issue'
handler_instance.llm = LLM(llm_config)
@ -522,7 +532,7 @@ async def test_process_issue(
handler_instance.guess_success.assert_not_called()
def test_get_instruction(mock_prompt_template, mock_followup_prompt_template):
def test_get_instruction(mock_user_instructions_template, mock_conversation_instructions_template, mock_followup_prompt_template):
issue = Issue(
owner='test_owner',
repo='test_repo',
@ -534,14 +544,15 @@ def test_get_instruction(mock_prompt_template, mock_followup_prompt_template):
issue_handler = ServiceContextIssue(
GithubIssueHandler('owner', 'repo', 'token'), mock_llm_config
)
instruction, images_urls = issue_handler.get_instruction(
issue, mock_prompt_template, None
instruction, conversation_instructions, images_urls = issue_handler.get_instruction(
issue, mock_user_instructions_template, mock_conversation_instructions_template, None
)
expected_instruction = 'Issue: Test Issue\n\nThis is a test issue refer to image ![First Image](https://sampleimage.com/image1.png)\n\nPlease fix this issue.'
assert images_urls == ['https://sampleimage.com/image1.png']
assert issue_handler.issue_type == 'issue'
assert instruction == expected_instruction
assert conversation_instructions is not None
issue = Issue(
owner='test_owner',
@ -564,14 +575,18 @@ def test_get_instruction(mock_prompt_template, mock_followup_prompt_template):
pr_handler = ServiceContextPR(
GithubPRHandler('owner', 'repo', 'token'), mock_llm_config
)
instruction, images_urls = pr_handler.get_instruction(
issue, mock_followup_prompt_template, None
instruction, conversation_instructions, images_urls = pr_handler.get_instruction(
issue, mock_followup_prompt_template, mock_conversation_instructions_template, None
)
expected_instruction = "Issue context: [\n \"Issue 1 fix the type\"\n]\n\nReview comments: None\n\nReview threads: [\n \"There is still a typo 'pthon' instead of 'python'\"\n]\n\nFiles: []\n\nThread comments: I've left review comments, please address them\n---\nThis is a valid concern.\n\nPlease fix this issue."
assert images_urls == []
assert pr_handler.issue_type == 'pr'
assert instruction == expected_instruction
# Compare content ignoring exact formatting
assert "There is still a typo 'pthon' instead of 'python'" in instruction
assert "I've left review comments, please address them" in instruction
assert 'This is a valid concern' in instruction
assert conversation_instructions is not None
def test_file_instruction():
@ -585,26 +600,35 @@ def test_file_instruction():
# load prompt from openhands/resolver/prompts/resolve/basic.jinja
with open('openhands/resolver/prompts/resolve/basic.jinja', 'r') as f:
prompt = f.read()
with open('openhands/resolver/prompts/resolve/basic-conversation-instructions.jinja', 'r') as f:
conversation_instructions_template = f.read()
# Test without thread comments
mock_llm_config = LLMConfig(model='test_model', api_key='test_api_key')
issue_handler = ServiceContextIssue(
GithubIssueHandler('owner', 'repo', 'token'), mock_llm_config
)
instruction, images_urls = issue_handler.get_instruction(issue, prompt, None)
instruction, conversation_instructions, images_urls = issue_handler.get_instruction(
issue, prompt,conversation_instructions_template, None
)
expected_instruction = """Please fix the following issue for the repository in /workspace.
An environment has been set up for you to start working. You may assume all necessary tools are installed.
# Problem Statement
Test Issue
This is a test issue ![image](https://sampleimage.com/sample.png)
This is a test issue ![image](https://sampleimage.com/sample.png)"""
IMPORTANT: You should ONLY interact with the environment provided to you AND NEVER ASK FOR HUMAN HELP.
expected_conversation_instructions = """IMPORTANT: You should ONLY interact with the environment provided to you AND NEVER ASK FOR HUMAN HELP.
You SHOULD INCLUDE PROPER INDENTATION in your edit commands.
When you think you have fixed the issue through code changes, please finish the interaction."""
assert instruction == expected_instruction
assert conversation_instructions == expected_conversation_instructions
assert images_urls == ['https://sampleimage.com/sample.png']
@ -619,6 +643,10 @@ def test_file_instruction_with_repo_instruction():
# load prompt from openhands/resolver/prompts/resolve/basic.jinja
with open('openhands/resolver/prompts/resolve/basic.jinja', 'r') as f:
prompt = f.read()
with open('openhands/resolver/prompts/resolve/basic-conversation-instructions.jinja', 'r') as f:
conversation_instructions_prompt = f.read()
# load repo instruction from openhands/resolver/prompts/repo_instructions/all-hands-ai___openhands-resolver.txt
with open(
'openhands/resolver/prompts/repo_instructions/all-hands-ai___openhands-resolver.txt',
@ -630,18 +658,20 @@ def test_file_instruction_with_repo_instruction():
issue_handler = ServiceContextIssue(
GithubIssueHandler('owner', 'repo', 'token'), mock_llm_config
)
instruction, image_urls = issue_handler.get_instruction(
issue, prompt, repo_instruction
instruction, conversation_instructions, image_urls = issue_handler.get_instruction(
issue, prompt, conversation_instructions_prompt, repo_instruction
)
expected_instruction = """Please fix the following issue for the repository in /workspace.
An environment has been set up for you to start working. You may assume all necessary tools are installed.
# Problem Statement
Test Issue
This is a test issue
This is a test issue"""
IMPORTANT: You should ONLY interact with the environment provided to you AND NEVER ASK FOR HUMAN HELP.
expected_conversation_instructions = """IMPORTANT: You should ONLY interact with the environment provided to you AND NEVER ASK FOR HUMAN HELP.
You SHOULD INCLUDE PROPER INDENTATION in your edit commands.
Some basic information about this repository:
@ -652,7 +682,11 @@ This is a Python repo for openhands-resolver, a library that attempts to resolve
When you think you have fixed the issue through code changes, please finish the interaction."""
assert instruction == expected_instruction
assert conversation_instructions == expected_conversation_instructions
assert conversation_instructions is not None
assert issue_handler.issue_type == 'issue'
assert image_urls == []
@ -751,11 +785,16 @@ def test_instruction_with_thread_comments():
with open('openhands/resolver/prompts/resolve/basic.jinja', 'r') as f:
prompt = f.read()
with open('openhands/resolver/prompts/resolve/basic-conversation-instructions.jinja', 'r') as f:
conversation_instructions_template = f.read()
llm_config = LLMConfig(model='test', api_key='test')
issue_handler = ServiceContextIssue(
GithubIssueHandler('owner', 'repo', 'token'), llm_config
)
instruction, images_urls = issue_handler.get_instruction(issue, prompt, None)
instruction, _, images_urls = issue_handler.get_instruction(
issue, prompt, conversation_instructions_template, None
)
# Verify that thread comments are included in the instruction
assert 'First comment' in instruction

View File

@ -92,10 +92,15 @@ def mock_os():
@pytest.fixture
def mock_prompt_template():
def mock_user_instructions_template():
return 'Issue: {{ body }}\n\nPlease fix this issue.'
@pytest.fixture
def mock_conversation_instructions_template():
return 'Instructions: {{ repo_instruction }}'
@pytest.fixture
def mock_followup_prompt_template():
return 'Issue context: {{ issues }}\n\nReview comments: {{ review_comments }}\n\nReview threads: {{ review_threads }}\n\nFiles: {{ files }}\n\nThread comments: {{ thread_context }}\n\nPlease fix this issue.'
@ -459,7 +464,7 @@ async def test_process_issue(
default_mock_args,
mock_gitlab_token,
mock_output_dir,
mock_prompt_template,
mock_user_instructions_template,
test_case,
):
"""Test the process_issue method with different scenarios."""
@ -479,7 +484,7 @@ async def test_process_issue(
# Create a resolver instance with mocked token identification
resolver = IssueResolver(default_mock_args)
resolver.prompt_template = mock_prompt_template
resolver.user_instructions_prompt_template = mock_user_instructions_template
# Mock the handler with LLM config
llm_config = LLMConfig(model='test', api_key='test')
@ -489,7 +494,11 @@ async def test_process_issue(
test_case.get('comment_success', None),
test_case['expected_explanation'],
)
handler_instance.get_instruction.return_value = ('Test instruction', [])
handler_instance.get_instruction.return_value = (
'Test instruction',
'Test conversation instructions',
[],
)
handler_instance.issue_type = 'pr' if test_case.get('is_pr', False) else 'issue'
handler_instance.llm = LLM(llm_config)
@ -542,7 +551,11 @@ async def test_process_issue(
handler_instance.guess_success.assert_not_called()
def test_get_instruction(mock_prompt_template, mock_followup_prompt_template):
def test_get_instruction(
mock_user_instructions_template,
mock_conversation_instructions_template,
mock_followup_prompt_template,
):
issue = Issue(
owner='test_owner',
repo='test_repo',
@ -554,14 +567,18 @@ def test_get_instruction(mock_prompt_template, mock_followup_prompt_template):
issue_handler = ServiceContextIssue(
GitlabIssueHandler('owner', 'repo', 'token'), mock_llm_config
)
instruction, images_urls = issue_handler.get_instruction(
issue, mock_prompt_template, None
instruction, conversation_instructions, images_urls = issue_handler.get_instruction(
issue,
mock_user_instructions_template,
mock_conversation_instructions_template,
None,
)
expected_instruction = 'Issue: Test Issue\n\nThis is a test issue refer to image ![First Image](https://sampleimage.com/image1.png)\n\nPlease fix this issue.'
assert images_urls == ['https://sampleimage.com/image1.png']
assert issue_handler.issue_type == 'issue'
assert instruction == expected_instruction
assert conversation_instructions is not None
issue = Issue(
owner='test_owner',
@ -584,14 +601,21 @@ def test_get_instruction(mock_prompt_template, mock_followup_prompt_template):
pr_handler = ServiceContextPR(
GitlabPRHandler('owner', 'repo', 'token'), mock_llm_config
)
instruction, images_urls = pr_handler.get_instruction(
issue, mock_followup_prompt_template, None
instruction, conversation_instructions, images_urls = pr_handler.get_instruction(
issue,
mock_followup_prompt_template,
mock_conversation_instructions_template,
None,
)
expected_instruction = "Issue context: [\n \"Issue 1 fix the type\"\n]\n\nReview comments: None\n\nReview threads: [\n \"There is still a typo 'pthon' instead of 'python'\"\n]\n\nFiles: []\n\nThread comments: I've left review comments, please address them\n---\nThis is a valid concern.\n\nPlease fix this issue."
assert images_urls == []
assert pr_handler.issue_type == 'pr'
assert instruction == expected_instruction
# Compare content ignoring exact formatting
assert "There is still a typo 'pthon' instead of 'python'" in instruction
assert "I've left review comments, please address them" in instruction
assert 'This is a valid concern' in instruction
assert conversation_instructions is not None
def test_file_instruction():
@ -605,26 +629,35 @@ def test_file_instruction():
# load prompt from openhands/resolver/prompts/resolve/basic.jinja
with open('openhands/resolver/prompts/resolve/basic.jinja', 'r') as f:
prompt = f.read()
with open(
'openhands/resolver/prompts/resolve/basic-conversation-instructions.jinja', 'r'
) as f:
conversation_instructions_template = f.read()
# Test without thread comments
mock_llm_config = LLMConfig(model='test_model', api_key='test_api_key')
issue_handler = ServiceContextIssue(
GitlabIssueHandler('owner', 'repo', 'token'), mock_llm_config
)
instruction, images_urls = issue_handler.get_instruction(issue, prompt, None)
instruction, conversation_instructions, images_urls = issue_handler.get_instruction(
issue, prompt, conversation_instructions_template, None
)
expected_instruction = """Please fix the following issue for the repository in /workspace.
An environment has been set up for you to start working. You may assume all necessary tools are installed.
# Problem Statement
Test Issue
This is a test issue ![image](https://sampleimage.com/sample.png)
This is a test issue ![image](https://sampleimage.com/sample.png)"""
IMPORTANT: You should ONLY interact with the environment provided to you AND NEVER ASK FOR HUMAN HELP.
expected_conversation_instructions = """IMPORTANT: You should ONLY interact with the environment provided to you AND NEVER ASK FOR HUMAN HELP.
You SHOULD INCLUDE PROPER INDENTATION in your edit commands.
When you think you have fixed the issue through code changes, please finish the interaction."""
assert instruction == expected_instruction
assert conversation_instructions == expected_conversation_instructions
assert images_urls == ['https://sampleimage.com/sample.png']
@ -639,6 +672,12 @@ def test_file_instruction_with_repo_instruction():
# load prompt from openhands/resolver/prompts/resolve/basic.jinja
with open('openhands/resolver/prompts/resolve/basic.jinja', 'r') as f:
prompt = f.read()
with open(
'openhands/resolver/prompts/resolve/basic-conversation-instructions.jinja', 'r'
) as f:
conversation_instructions_prompt = f.read()
# load repo instruction from openhands/resolver/prompts/repo_instructions/all-hands-ai___openhands-resolver.txt
with open(
'openhands/resolver/prompts/repo_instructions/all-hands-ai___openhands-resolver.txt',
@ -650,18 +689,19 @@ def test_file_instruction_with_repo_instruction():
issue_handler = ServiceContextIssue(
GitlabIssueHandler('owner', 'repo', 'token'), mock_llm_config
)
instruction, image_urls = issue_handler.get_instruction(
issue, prompt, repo_instruction
instruction, conversation_instructions, image_urls = issue_handler.get_instruction(
issue, prompt, conversation_instructions_prompt, repo_instruction
)
expected_instruction = """Please fix the following issue for the repository in /workspace.
An environment has been set up for you to start working. You may assume all necessary tools are installed.
# Problem Statement
Test Issue
This is a test issue
This is a test issue"""
IMPORTANT: You should ONLY interact with the environment provided to you AND NEVER ASK FOR HUMAN HELP.
expected_conversation_instructions = """IMPORTANT: You should ONLY interact with the environment provided to you AND NEVER ASK FOR HUMAN HELP.
You SHOULD INCLUDE PROPER INDENTATION in your edit commands.
Some basic information about this repository:
@ -672,7 +712,10 @@ This is a Python repo for openhands-resolver, a library that attempts to resolve
When you think you have fixed the issue through code changes, please finish the interaction."""
assert instruction == expected_instruction
assert conversation_instructions == expected_conversation_instructions
assert conversation_instructions is not None
assert issue_handler.issue_type == 'issue'
assert image_urls == []
@ -771,11 +814,18 @@ def test_instruction_with_thread_comments():
with open('openhands/resolver/prompts/resolve/basic.jinja', 'r') as f:
prompt = f.read()
with open(
'openhands/resolver/prompts/resolve/basic-conversation-instructions.jinja', 'r'
) as f:
conversation_instructions_template = f.read()
llm_config = LLMConfig(model='test', api_key='test')
issue_handler = ServiceContextIssue(
GitlabIssueHandler('owner', 'repo', 'token'), llm_config
)
instruction, images_urls = issue_handler.get_instruction(issue, prompt, None)
instruction, conversation_instructions, images_urls = issue_handler.get_instruction(
issue, prompt, conversation_instructions_template, None
)
# Verify that thread comments are included in the instruction
assert 'First comment' in instruction