Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API endpoints for resolver functionality #5058

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
73e190e
Add API endpoints for resolver functionality
openhands-agent Nov 15, 2024
fba35a4
Revert changes to send_pull_request.py
openhands-agent Nov 15, 2024
486355b
Improve process_single_issue return type and error handling
openhands-agent Nov 15, 2024
cb92518
Merge branch 'main' into add-resolver-api-endpoints
neubig Nov 15, 2024
66b4e5d
Fix failing tests in test_listen.py
openhands-agent Nov 15, 2024
abde56f
Update
neubig Nov 15, 2024
cfd3911
Refactor resolver endpoints to use data models (#5073)
neubig Nov 15, 2024
95884c1
Lint
neubig Nov 16, 2024
87925dd
Merge branch 'main' into add-resolver-api-endpoints
neubig Nov 16, 2024
c2265e8
Fix pr #5058: Add API endpoints for resolver functionality
openhands-agent Nov 16, 2024
a4f5772
Fix pr #5058: Add API endpoints for resolver functionality
openhands-agent Nov 16, 2024
27592c5
fix: Fix send-pr endpoint to use correct file path and update tests t…
openhands-agent Nov 16, 2024
f037482
Merge branch 'main' into add-resolver-api-endpoints
neubig Nov 16, 2024
031e201
feat: Combine resolve_issue and send_pull_request API calls into a si…
openhands-agent Nov 16, 2024
845f1b2
style: Fix linting issues
openhands-agent Nov 16, 2024
dbf560d
refactor: Improve resolver API endpoints
openhands-agent Nov 16, 2024
1f53c93
refactor: Remove unused SendPullRequestDataModel
openhands-agent Nov 16, 2024
45a1486
style: Format imports in listen.py
openhands-agent Nov 16, 2024
2ce806e
fix: Convert ResolverOutput to dict in response
openhands-agent Nov 16, 2024
c088a08
style: Fix linting in test_listen.py
openhands-agent Nov 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 43 additions & 23 deletions openhands/resolver/send_pull_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import jinja2
import litellm
import requests
from pydantic import BaseModel

from openhands.core.config import LLMConfig
from openhands.core.logger import openhands_logger as logger
Expand Down Expand Up @@ -426,21 +427,36 @@ def update_existing_pull_request(
return pr_url


class ProcessIssueResult(BaseModel):
success: bool
url: str | None = None
error: str | None = None


def process_single_issue(
output_dir: str,
resolver_output: ResolverOutput,
github_token: str,
github_username: str,
github_username: str | None,
pr_type: str,
llm_config: LLMConfig,
fork_owner: str | None,
send_on_failure: bool,
) -> None:
) -> ProcessIssueResult:
if github_username is None:
return ProcessIssueResult(
success=False,
error='GITHUB_USERNAME environment variable not set',
)

if not resolver_output.success and not send_on_failure:
print(
f'Issue {resolver_output.issue.number} was not successfully resolved. Skipping PR creation.'
)
return
return ProcessIssueResult(
success=False,
error='Issue was not successfully resolved',
)

issue_type = resolver_output.issue_type

Expand All @@ -465,26 +481,30 @@ def process_single_issue(

make_commit(patched_repo_dir, resolver_output.issue, issue_type)

if issue_type == 'pr':
update_existing_pull_request(
github_issue=resolver_output.issue,
github_token=github_token,
github_username=github_username,
patch_dir=patched_repo_dir,
additional_message=resolver_output.success_explanation,
llm_config=llm_config,
)
else:
send_pull_request(
github_issue=resolver_output.issue,
github_token=github_token,
github_username=github_username,
patch_dir=patched_repo_dir,
pr_type=pr_type,
llm_config=llm_config,
fork_owner=fork_owner,
additional_message=resolver_output.success_explanation,
)
try:
if issue_type == 'pr':
url = update_existing_pull_request(
github_issue=resolver_output.issue,
github_token=github_token,
github_username=github_username,
patch_dir=patched_repo_dir,
additional_message=resolver_output.success_explanation,
llm_config=llm_config,
)
else:
url = send_pull_request(
github_issue=resolver_output.issue,
github_token=github_token,
github_username=github_username,
patch_dir=patched_repo_dir,
pr_type=pr_type,
llm_config=llm_config,
fork_owner=fork_owner,
additional_message=resolver_output.success_explanation,
)
return ProcessIssueResult(success=True, url=url)
except Exception as e:
return ProcessIssueResult(success=False, error=str(e))


def process_all_successful_issues(
Expand Down
124 changes: 124 additions & 0 deletions openhands/server/data_models/resolver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
from typing import Any, Literal

from pydantic import BaseModel, Field


class ResolveIssueDataModel(BaseModel):
owner: str = Field(..., description='Github owner of the repo')
repo: str = Field(..., description='Github repository name')
token: str = Field(..., description='Github token to access the repository')
username: str = Field(..., description='Github username to access the repository')
max_iterations: int = Field(50, description='Maximum number of iterations to run')
issue_type: Literal['issue', 'pr'] = Field(
..., description='Type of issue to resolve (issue or pr)'
)
issue_number: int = Field(..., description='Issue number to resolve')
comment_id: int | None = Field(
None, description='Optional ID of a specific comment to focus on'
)


class SendPullRequestDataModel(BaseModel):
issue_number: int = Field(..., description='Issue number to create PR for')
pr_type: Literal['branch', 'draft', 'ready'] = Field(
..., description='Type of PR to create (branch, draft, ready)'
)
fork_owner: str | None = Field(None, description='Optional owner to fork to')
send_on_failure: bool = Field(
False, description='Whether to send PR even if resolution failed'
)


async def resolve_github_issue_with_model(
data: ResolveIssueDataModel,
llm_config: Any,
runtime_container_image: str,
output_dir: str,
) -> dict[str, str]:
"""Resolve a GitHub issue using the provided data model.

Args:
data: The issue resolution request data
llm_config: The LLM configuration to use
runtime_container_image: The runtime container image to use
output_dir: The directory to store output files

Returns:
A dictionary containing the resolution results
"""
from openhands.resolver.resolve_issue import resolve_issue

try:
await resolve_issue(
owner=data.owner,
repo=data.repo,
token=data.token,
username=data.username,
max_iterations=data.max_iterations,
output_dir=output_dir,
llm_config=llm_config,
runtime_container_image=runtime_container_image,
prompt_template='', # Using default for now
issue_type=data.issue_type,
repo_instruction=None,
issue_number=data.issue_number,
comment_id=data.comment_id,
reset_logger=True,
)

# Read output.jsonl file
import os
output_file = os.path.join(output_dir, 'output.jsonl')
if os.path.exists(output_file):
with open(output_file, 'r') as f:
return {'status': 'success', 'output': f.read()}
else:
return {'status': 'error', 'message': 'No output file generated'}

except Exception as e:
return {'status': 'error', 'message': str(e)}


async def send_pull_request_with_model(
data: SendPullRequestDataModel,
llm_config: Any,
output_dir: str,
github_token: str,
github_username: str | None = None,
) -> dict[str, str | dict[str, Any]]:
"""Create a pull request using the provided data model.

Args:
data: The PR creation request data
llm_config: The LLM configuration to use
output_dir: The directory containing resolver output
github_token: The GitHub token to use
github_username: Optional GitHub username

Returns:
A dictionary containing the PR creation results
"""
from openhands.resolver.io_utils import load_single_resolver_output
from openhands.resolver.send_pull_request import process_single_issue

try:
resolver_output = load_single_resolver_output(output_dir, data.issue_number)
if not resolver_output:
raise ValueError(f'No resolver output found for issue {data.issue_number}')

result = process_single_issue(
output_dir=output_dir,
resolver_output=resolver_output,
github_token=github_token,
github_username=github_username,
pr_type=data.pr_type,
llm_config=llm_config,
fork_owner=data.fork_owner,
send_on_failure=data.send_on_failure,
)
if result.success:
return {'status': 'success', 'result': {'url': result.url}}
else:
return {'status': 'error', 'message': result.error or 'Unknown error'}
except Exception as e:
return {'status': 'error', 'message': str(e)}
Loading
Loading