Skip to content

Commit f78b44d

Browse files
committed
CodelldBot
1 parent 003d20f commit f78b44d

File tree

2 files changed

+185
-0
lines changed

2 files changed

+185
-0
lines changed

.github/workflows/analyze_issue.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import os
2+
import json
3+
import io
4+
import time
5+
from openai import OpenAI
6+
from openai.types.beta.assistant_stream_event import ThreadRunRequiresAction, ThreadMessageCompleted
7+
from octokit import Octokit
8+
9+
class IssueAnalyzer:
10+
def __init__(self):
11+
self.octokit = Octokit()
12+
self.openai = OpenAI()
13+
self.repo_full_name = os.getenv('GITHUB_REPOSITORY')
14+
15+
def handle_event(self):
16+
17+
with open(os.getenv('GITHUB_EVENT_PATH'), 'rb') as f:
18+
event = json.load(f)
19+
20+
match os.getenv('GITHUB_EVENT_NAME'):
21+
case 'issues':
22+
issue = event['issue']
23+
case 'workflow_dispatch':
24+
issue_number = int(event['inputs']['issue'])
25+
owner, repo = self.repo_full_name.split('/')
26+
response = self.octokit.issues.get(owner=owner, repo=repo, issue_number=issue_number)
27+
issue = response.json
28+
29+
assistant = self.openai.beta.assistants.retrieve(os.getenv('ASSISTANT_ID'))
30+
31+
issue_file = self.openai.files.create(
32+
file=('BUG_REPORT.md', self.make_issue_content(issue, show_labels=False)),
33+
purpose='assistants'
34+
)
35+
36+
thread = self.openai.beta.threads.create(
37+
metadata={
38+
'issue': f'{issue["number"]}: {issue["title"]}',
39+
'run_id': os.getenv('GITHUB_RUN_ID'),
40+
'model': f'{assistant.model} t={assistant.temperature} top_p={assistant.top_p}',
41+
},
42+
messages=[{
43+
'role': 'user',
44+
'content': 'We have a new issue report (attached as BUG_REPORT.md)',
45+
'attachments': [{
46+
'file_id': issue_file.id,
47+
'tools': [{'type': 'file_search'}]
48+
}]
49+
}
50+
]
51+
)
52+
print('Thread:', thread.id)
53+
54+
thread_vstore_id = thread.tool_resources.file_search.vector_store_ids[0]
55+
self.wait_vector_store(thread_vstore_id)
56+
57+
stream = self.openai.beta.threads.runs.create(
58+
assistant_id=assistant.id,
59+
thread_id=thread.id,
60+
stream=True
61+
)
62+
63+
streams = [stream]
64+
while streams:
65+
stream = streams.pop(0)
66+
for event in stream:
67+
match event:
68+
case ThreadMessageCompleted():
69+
for c in event.data.content:
70+
print('Assistant:', c.text.value)
71+
case ThreadRunRequiresAction():
72+
tool_outputs = []
73+
for tool in event.data.required_action.submit_tool_outputs.tool_calls:
74+
args = json.loads(tool.function.arguments)
75+
print(f'Tool call:', tool.function.name, args)
76+
match tool.function.name:
77+
case 'search_github':
78+
query = f'repo:{self.repo_full_name} {args["query"]}'
79+
output = self.search_github(query, thread_vstore_id, exclude=[issue['number']])
80+
tool_outputs.append({'tool_call_id': tool.id, 'output': output})
81+
case 'add_issue_labels':
82+
tool_outputs.append({'tool_call_id': tool.id, 'output': 'Ok'})
83+
case 'set_issue_title':
84+
tool_outputs.append({'tool_call_id': tool.id, 'output': 'Ok'})
85+
case 'add_issue_comment':
86+
tool_outputs.append({'tool_call_id': tool.id, 'output': 'Ok'})
87+
88+
new_stream = self.openai.beta.threads.runs.submit_tool_outputs(
89+
thread_id=thread.id,
90+
run_id=event.data.id,
91+
tool_outputs=tool_outputs,
92+
stream=True)
93+
streams.append(new_stream)
94+
95+
def search_github(self, query: str, vstore_id: str, exclude:list=[], max_results=5) -> str:
96+
response = self.octokit.search.issues(q=query)
97+
if response.json.get('status'):
98+
return f'Search failed: {response.json["message"]}'
99+
100+
result_lines = []
101+
for issue in response.json['items']:
102+
issue_number = issue['number']
103+
if issue_number in exclude:
104+
continue
105+
issue_file = self.openai.files.create(
106+
file=(f'ISSUE_{issue_number}.md', self.make_issue_content(issue, fetch_comments=True)),
107+
purpose='assistants'
108+
)
109+
self.openai.beta.vector_stores.files.create(
110+
vector_store_id=vstore_id,
111+
file_id=issue_file.id,
112+
)
113+
result_lines.append(f'Issue number: {issue_number}, file name: {issue_file.filename}')
114+
if len(result_lines) >= max_results:
115+
break
116+
117+
self.wait_vector_store(vstore_id)
118+
119+
result_lines.insert(0, f'Found {len(result_lines)} issues and attached as files to this thread:')
120+
return '\n'.join(result_lines)
121+
122+
def make_issue_content(self, issue, fetch_comments=False, show_labels=True) -> bytes:
123+
f = io.StringIO()
124+
f.write(f'### Title: {issue["title"]}\n')
125+
f.write(f'### Author: {issue["user"]["login"]}\n')
126+
f.write(f'### State: {issue["state"]}\n')
127+
if show_labels:
128+
f.write(f'### Labels: {",".join(label["name"] for label in issue["labels"])}\n')
129+
f.write(f'\n{issue["body"]}\n')
130+
131+
if fetch_comments:
132+
owner, repo = self.repo_full_name.split('/')
133+
comments = self.octokit.issues.list_issue_comments(
134+
owner=owner, repo=repo, issue_number=issue['number'])
135+
for comment in comments.json:
136+
f.write(f'### Comment by {comment["user"]["login"]}\n')
137+
f.write(f'\n{comment["body"]}\n')
138+
139+
return f.getvalue().encode('utf-8')
140+
141+
def wait_vector_store(self, vstore_id):
142+
vstore = self.openai.beta.vector_stores.retrieve(vstore_id)
143+
while vstore.status == 'in_progress':
144+
print('Waiting for vector store.')
145+
time.sleep(1)
146+
vstore = self.openai.beta.vector_stores.retrieve(vstore_id)
147+
148+
149+
if __name__ == '__main__':
150+
IssueAnalyzer().handle_event()

.github/workflows/analyze_issue.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: analyze_issue
2+
3+
on:
4+
issues:
5+
types: [opened]
6+
workflow_dispatch:
7+
inputs:
8+
issue: null
9+
10+
jobs:
11+
analyze_issue:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Check out repo
15+
uses: actions/checkout@v3
16+
17+
- name: Set up Python
18+
uses: actions/setup-python@v4
19+
with:
20+
python-version: "3.11"
21+
cache: 'pip'
22+
cache-dependency-path: .github/workflows/analyze_issue.yml
23+
24+
- name: Install dependencies
25+
run: pip install openai octokitpy
26+
27+
- name: Analysis
28+
env:
29+
GITHUB_TOKEN: ${{ github.token }}
30+
GITHUB_REPOSITORY: ${{ github.repository }}
31+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
32+
ASSISTANT_ID: ${{ vars.ASSISTANT_ID }}
33+
PYTHONUNBUFFERED: 1
34+
run: |
35+
python .github/workflows/analyze_issue.py

0 commit comments

Comments
 (0)