Skip to content

Commit

Permalink
feat: support issue comments (#257)
Browse files Browse the repository at this point in the history
- 支持 issue 多轮会话
  • Loading branch information
RaoHai authored Aug 24, 2024
2 parents 3f5859c + 4224502 commit 6d1a8ee
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 27 deletions.
33 changes: 33 additions & 0 deletions server/agent/prompts/issue_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
ISSUE_PROMPT = """
# Task
You have required to resolve an issue {issue_url} now:
## Issue Content:
{issue_content}
# Constraints:
First, carefully analyze the user’s requirements.
Then, search similar issues.
Make sure your suggestions are well-explained and align with the user’s needs.
"""

ISSUE_COMMENT_PROMPT = """
# Task
You have required to resolve an issue {issue_url} now:
## Issue Content:
{issue_content}
# Constraints:
- Summarize user needs based on the issue content and information.
- Avoid repeating answers. If you have previously given a similar response, please apologize.
"""

def generate_issue_prompt(issue_url: str, issue_content: str):
return ISSUE_PROMPT.format(issue_url=issue_url, issue_content=issue_content)

def generate_issue_comment_prompt(issue_url: str, issue_content: str):
return ISSUE_COMMENT_PROMPT.format(issue_url=issue_url, issue_content=issue_content)

21 changes: 14 additions & 7 deletions server/agent/qa_chat.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import AsyncIterator, Optional
from github import Auth
from agent.base import AgentBuilder
from agent.llm import get_llm
from core.dao.botDAO import BotDAO
Expand All @@ -9,8 +10,8 @@
from agent.tools import issue, sourcecode, knowledge, git_info


def get_tools(bot: Bot, token: Optional[str]):
issue_tools = issue.factory(access_token=token)
def get_tools(bot: Bot, token: Optional[Auth.Token]):
issue_tools = issue.factory(token=token)
return {
"search_knowledge": knowledge.factory(bot_id=bot.id),
"create_issue": issue_tools["create_issue"],
Expand All @@ -20,25 +21,31 @@ def get_tools(bot: Bot, token: Optional[str]):
"search_repo": git_info.search_repo,
}

def agent_stream_chat(input_data: ChatData, user_token: str) -> AsyncIterator[str]:
def agent_stream_chat(input_data: ChatData, token: Auth.Token) -> AsyncIterator[str]:
bot_dao = BotDAO()
bot = bot_dao.get_bot(input_data.bot_id)

agent = AgentBuilder(
chat_model=get_llm(bot.llm),
prompt=bot.prompt or generate_prompt_by_repo_name("ant-design"),
tools=get_tools(bot=bot, token=user_token),
tools=get_tools(bot=bot, token=token),
)
return agent.run_stream_chat(input_data)


def agent_chat(input_data: ChatData, user_token: Optional[str], llm: Optional[str] = "openai") -> AsyncIterator[str]:
def agent_chat(input_data: ChatData, token: Auth.Token) -> AsyncIterator[str]:
bot_dao = BotDAO()
bot = bot_dao.get_bot(input_data.bot_id)

prompt = bot.prompt or generate_prompt_by_repo_name("ant-design")
if input_data.prompt is not None:
prompt = f"{prompt}\n\n{input_data.prompt}"
print(f"agent_chat: prompt={prompt}")

agent = AgentBuilder(
chat_model=get_llm(bot.llm),
prompt=bot.prompt or generate_prompt_by_repo_name("ant-design"),
tools=get_tools(bot, token=user_token),
prompt=prompt,
tools=get_tools(bot, token=token),
)

return agent.run_chat(input_data)
13 changes: 8 additions & 5 deletions server/agent/tools/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

DEFAULT_REPO_NAME = "ant-design/ant-design"

def factory(access_token: Optional[str]):
def factory(token: Optional[Auth.Token]):
@tool
def create_issue(repo_name: str, title: str, body: str):
"""
Expand All @@ -19,10 +19,9 @@ def create_issue(repo_name: str, title: str, body: str):
:param title: The title of the issue to be created
:param body: The content of the issue to be created
"""
if access_token is None:
if token is None:
return need_github_login()
auth = Auth.Token(token=access_token)
g = Github(auth=auth)
g = Github(auth=token)
try:
# Get the repository object
repo = g.get_repo(repo_name)
Expand Down Expand Up @@ -93,7 +92,11 @@ def search_issues(
:param order: The order of the sorting, e.g: asc, desc
:param state: The state of the issue, e.g: open, closed, all
"""
g = Github()
if token is None:
g = Github()
else:
g = Github(auth=token)

try:
search_query = f"{keyword} in:title,body,comments repo:{repo_name}"
# Retrieve a list of open issues from the repository
Expand Down
8 changes: 6 additions & 2 deletions server/chat/router.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Annotated, Optional
from github import Auth
from fastapi import APIRouter, Depends
from fastapi.responses import StreamingResponse
from petercat_utils.data_class import ChatData
Expand Down Expand Up @@ -32,8 +33,10 @@ def run_qa_chat(
print(
f"run_qa_chat: input_data={input_data}, user_access_token={user_access_token}"
)

token = Auth.Token(user_access_token) if user_access_token is not None else None
result = qa_chat.agent_stream_chat(
input_data=input_data, user_token=user_access_token
input_data=input_data, token=token
)
return StreamingResponse(result, media_type="text/event-stream")

Expand All @@ -43,7 +46,8 @@ async def run_issue_helper(
input_data: ChatData,
user_access_token: Annotated[str | None, Depends(get_user_access_token)] = None,
):
result = await qa_chat.agent_chat(input_data, user_access_token)
token = Auth.Token(user_access_token) if user_access_token is not None else None
result = await qa_chat.agent_chat(input_data, token)
return result


Expand Down
76 changes: 65 additions & 11 deletions server/event_handler/issue.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
from typing import Any
from typing import Any, Tuple
from github import Github, Auth
from github.Issue import Issue
from github.Repository import Repository
from github import GithubException
from agent.prompts.issue_helper import generate_issue_comment_prompt, generate_issue_prompt

from core.dao.repositoryConfigDAO import RepositoryConfigDAO
from petercat_utils.data_class import ChatData, Message, TextContentBlock

from agent.qa_chat import agent_chat



class IssueEventHandler:
event: Any
auth: Auth.AppAuth
Expand All @@ -18,25 +22,75 @@ def __init__(self, payload: Any, auth: Auth.AppAuth, installation_id: int) -> No
self.auth: Auth.AppAuth = auth
self.g: Github = Github(auth=auth)

def get_issue(self) -> Tuple[Issue, Repository]:
repo_name = self.event["repository"]["full_name"]
issue_number = self.event["issue"]["number"]
repo = self.g.get_repo(repo_name)
# GET ISSUES
issue = repo.get_issue(number=issue_number)
return issue, repo

async def execute(self):
repository_config = RepositoryConfigDAO()
try:
print("actions:", self.event["action"])
if self.event["action"] == "opened":
repo_name = self.event["repository"]["full_name"]
issue_number = self.event["issue"]["number"]
repo = self.g.get_repo(repo_name)
issue = repo.get_issue(number=issue_number)
issue, repo = self.get_issue()

prompt = generate_issue_prompt(issue_url=issue.url, issue_content=issue.body)
issue_content = f"{issue.title}: {issue.body}"
text_block = TextContentBlock(type="text", text=issue_content)
issue_content = issue.body
message = Message(role="user", content=[text_block])
message = Message(role="user", content=[TextContentBlock(type="text", text=issue_content)])

repository_config = RepositoryConfigDAO()
repo_config = repository_config.get_by_repo_name(repo.full_name)

analysis_result = await agent_chat(
ChatData(
prompt=prompt,
messages=[message],
bot_id=repo_config.robot_id
), self.auth)

repo_config = repository_config.get_by_repo_name(repo_name)
analysis_result = await agent_chat(ChatData(messages=[message], bot_id=repo_config.robot_id), None)
issue.create_comment(analysis_result["output"])

return {"success": True}
except GithubException as e:
print(f"处理 GitHub 请求时出错:{e}")
return {"success": False, "error": str(e)}

class IssueCommentEventHandler(IssueEventHandler):
async def execute(self):
try:
print(f"actions={self.event['action']},sender={self.event['sender']}")
# 忽略机器人回复
if self.event['sender']['type'] == "Bot":
return {"success": True}
if self.event["action"] == "created":
issue, repo = self.get_issue()
issue_comments = issue.get_comments()

messages = [
Message(
role="assistant" if comment.user.type == "Bot" else "user",
content=[TextContentBlock(type="text", text=comment.body)],
) for comment in issue_comments
]

print(f"messages={messages}")
issue_content = f"{issue.title}: {issue.body}"
prompt = generate_issue_comment_prompt(issue_url=issue.url, issue_content=issue_content)

repository_config = RepositoryConfigDAO()
repo_config = repository_config.get_by_repo_name(repo.full_name)

analysis_result = await agent_chat(
ChatData(
prompt=prompt,
messages=messages,
bot_id=repo_config.robot_id
), self.auth)

issue.create_comment(analysis_result["output"])

except GithubException as e:
print(f"处理 GitHub 请求时出错:{e}")
return {"success": False, "error": str(e)}
5 changes: 3 additions & 2 deletions server/github_app/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@

from event_handler.pull_request import PullRequestEventHandler
from event_handler.discussion import DiscussionEventHandler
from event_handler.issue import IssueEventHandler
from event_handler.issue import IssueEventHandler, IssueCommentEventHandler

APP_ID = get_env_variable("X_GITHUB_APP_ID")


def get_handler(event: str, payload: dict, auth: Auth.AppAuth, installation_id: int) -> Union[PullRequestEventHandler, IssueEventHandler, DiscussionEventHandler, None]:
def get_handler(event: str, payload: dict, auth: Auth.AppAuth, installation_id: int) -> Union[PullRequestEventHandler, IssueCommentEventHandler, IssueEventHandler, DiscussionEventHandler, None]:
handlers = {
'pull_request': PullRequestEventHandler,
'issues': IssueEventHandler,
"issue_comment": IssueCommentEventHandler,
'discussion': DiscussionEventHandler
}
return handlers.get(event)(payload=payload, auth=auth, installation_id=installation_id) if event in handlers else None

0 comments on commit 6d1a8ee

Please sign in to comment.