Skip to content

Commit 8aba1c4

Browse files
committed
save
1 parent ffbb0b3 commit 8aba1c4

File tree

6 files changed

+622
-0
lines changed

6 files changed

+622
-0
lines changed

.github/ottobot/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.env
2+
.venv

.github/ottobot/instructions.txt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
You are a developer for the Plain web framework.
2+
The name “Plain” reflects our philosophy: straightforward and clear.
3+
When you write code or documentation, you should keep this in mind.
4+
5+
Key Guidelines:
6+
7+
1. Simplicity and Clarity:
8+
- Keep explanations concise. Don’t over-explain.
9+
- Be friendly and approachable in your tone.
10+
2. Documentation Approach:
11+
- Use “source docs,” meaning README.md files next to the code they document.
12+
- Cover high-level topics and basic implementations. Mention advanced use cases briefly and direct users to the source code for details.
13+
- Focus on what users need to know to get started and understand the framework’s core concepts.
14+
- Try to verify the correctness of your documentation after writing it.
15+
16+
Your Task:
17+
18+
Finding a TODO to Document:
19+
20+
- Use available tools to navigate the repo and find TODO comments in README.md files.
21+
- Randomly choose one of the TODOs -- don't just choose the first one you find.
22+
- Always refer to the source code to ensure accurate documentation.
23+
- Ensure you have all the information needed to complete the TODO without needing further clarification.
24+
25+
Making the Improvement:
26+
27+
- Use a similar voice and style to existing documentation.
28+
- Only work on one TODO, even if multiple TODOs are present in the same file.
29+
- Don't write more than one section of documentation at a time.
30+
- Review your work for clarity and simplicity. Revise if needed.
31+
- Output the changes as a git diff text to the console for review. The PR should be straightforward for reviewers to approve or reject.
32+
33+
By following these guidelines, you’ll help create clear and accessible documentation for Plain.

.github/ottobot/ottobot.py

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
import json
2+
import os
3+
import subprocess
4+
import sys
5+
from pathlib import Path
6+
7+
from dotenv import load_dotenv
8+
from openai import AssistantEventHandler, OpenAI
9+
from typing_extensions import override
10+
11+
12+
def bold(text: str) -> str:
13+
return f"\033[1m{text}\033[0m"
14+
15+
16+
class Ottobot:
17+
def __init__(self, live_mode: bool = False):
18+
self.live_mode = live_mode
19+
20+
def _run(self, cmd: list) -> str:
21+
return subprocess.check_output(cmd).decode("utf-8")
22+
23+
def ls(self, path: str) -> str:
24+
return self._run(["ls", path])
25+
26+
def tree(self, path: str) -> str:
27+
return self._run(["tree", path, "--gitignore", "-i", "-f"])
28+
29+
def cat(self, file: str) -> str:
30+
return self._run(["cat", file])
31+
32+
def grep(self, pattern: str, path: str, recursive: bool) -> str:
33+
if recursive:
34+
result = subprocess.run(
35+
["git", "grep", "-r", pattern, path], capture_output=True
36+
)
37+
else:
38+
result = subprocess.run(["git", "grep", pattern, path], capture_output=True)
39+
if result.returncode == 0:
40+
return result.stdout.decode("utf-8")
41+
else:
42+
return result.stderr.decode("utf-8")
43+
44+
45+
class EventHandler(AssistantEventHandler):
46+
def __init__(self, otto):
47+
self.otto = otto
48+
super().__init__()
49+
50+
@override
51+
def on_event(self, event):
52+
# Retrieve events that are denoted with 'requires_action'
53+
# since these will have our tool_calls
54+
if event.event == "thread.run.requires_action":
55+
run_id = event.data.id # Retrieve the run ID from the event data
56+
self.handle_requires_action(event.data, run_id)
57+
58+
def handle_requires_action(self, data, run_id):
59+
tool_outputs = []
60+
61+
for tool in data.required_action.submit_tool_outputs.tool_calls:
62+
otto_func = getattr(self.otto, tool.function.name)
63+
kwargs = json.loads(tool.function.arguments)
64+
print(bold(f"Running tool: {tool.function.name} with arguments: {kwargs}"))
65+
tool_outputs.append(
66+
{"tool_call_id": tool.id, "output": otto_func(**kwargs)}
67+
)
68+
69+
# Submit all tool_outputs at the same time
70+
self.submit_tool_outputs(tool_outputs, run_id)
71+
72+
def submit_tool_outputs(self, tool_outputs, run_id):
73+
# Use the submit_tool_outputs_stream helper
74+
with client.beta.threads.runs.submit_tool_outputs_stream(
75+
thread_id=self.current_run.thread_id,
76+
run_id=self.current_run.id,
77+
tool_outputs=tool_outputs,
78+
event_handler=EventHandler(self.otto),
79+
) as stream:
80+
for text in stream.text_deltas:
81+
print(text, end="", flush=True)
82+
print()
83+
84+
85+
if __name__ == "__main__":
86+
load_dotenv()
87+
otto = Ottobot(live_mode="--live" in sys.argv)
88+
client = OpenAI()
89+
90+
instructions = Path("instructions.txt").read_text()
91+
92+
repo_root = (
93+
subprocess.check_output(["git", "rev-parse", "--show-toplevel"])
94+
.strip()
95+
.decode("utf-8")
96+
)
97+
print(bold(f"Starting Otto in {repo_root}"))
98+
99+
os.chdir(repo_root) # Move to the repo root so everything runs from there
100+
101+
assistant = client.beta.assistants.create(
102+
instructions=instructions,
103+
model="gpt-4o", # try mini?
104+
tools=[
105+
{
106+
"type": "function",
107+
"function": {
108+
"name": "ls",
109+
"description": "List files in a directory",
110+
"parameters": {
111+
"type": "object",
112+
"properties": {
113+
"path": {
114+
"type": "string",
115+
"description": "The path to the directory to list",
116+
}
117+
},
118+
"required": ["path"],
119+
},
120+
},
121+
},
122+
{
123+
"type": "function",
124+
"function": {
125+
"name": "tree",
126+
"description": "List files in a directory in a tree format",
127+
"parameters": {
128+
"type": "object",
129+
"properties": {
130+
"path": {
131+
"type": "string",
132+
"description": "The path to the directory to list",
133+
}
134+
},
135+
"required": ["path"],
136+
},
137+
},
138+
},
139+
{
140+
"type": "function",
141+
"function": {
142+
"name": "cat",
143+
"description": "Print the contents of a file",
144+
"parameters": {
145+
"type": "object",
146+
"properties": {
147+
"file": {
148+
"type": "string",
149+
"description": "The path to the file to print",
150+
}
151+
},
152+
"required": ["file"],
153+
},
154+
},
155+
},
156+
{
157+
"type": "function",
158+
"function": {
159+
"name": "grep",
160+
"description": "Search for a pattern in a file",
161+
"parameters": {
162+
"type": "object",
163+
"properties": {
164+
"pattern": {
165+
"type": "string",
166+
"description": "The pattern to search for",
167+
},
168+
"path": {
169+
"type": "string",
170+
"description": "The path to search in",
171+
},
172+
"recursive": {
173+
"type": "boolean",
174+
"description": "Whether to search recursively",
175+
},
176+
},
177+
"required": ["pattern", "path", "recursive"],
178+
},
179+
},
180+
},
181+
# {
182+
# "type": "function",
183+
# "function": {
184+
# "name": "find",
185+
# "description": "Search for a file in a directory",
186+
# "parameters": {
187+
# "type": "object",
188+
# "properties": {
189+
# "name": {
190+
# "type": "string",
191+
# "description": "The name of the file to search for"
192+
# },
193+
# "path": {
194+
# "type": "string",
195+
# "description": "The path to the directory to search in"
196+
# }
197+
# },
198+
# "required": ["name", "path"]
199+
# }
200+
# }
201+
# },
202+
# {
203+
# "type": "function",
204+
# "function": {
205+
# "name": "patch",
206+
# "description": "Generate a git patch for a file",
207+
# "parameters": {
208+
# "type": "object",
209+
# "properties": {
210+
# "file": {
211+
# "type": "string",
212+
# "
213+
],
214+
)
215+
216+
# Give it the initial information to work from, so it doesn't have to ask for it
217+
files = otto.tree(".")
218+
todos = otto.grep("TODO", ".", recursive=True)
219+
220+
thread = client.beta.threads.create()
221+
message = client.beta.threads.messages.create(
222+
thread_id=thread.id,
223+
role="user",
224+
content=f"Current files in the repo (from `tree`):\n{files}\n\nCurrent TODOs in the repo (from `grep`):\n{todos}",
225+
)
226+
227+
with client.beta.threads.runs.stream(
228+
thread_id=thread.id, assistant_id=assistant.id, event_handler=EventHandler(otto)
229+
) as stream:
230+
stream.until_done()

0 commit comments

Comments
 (0)