Skip to content

Commit 247e2cd

Browse files
committed
Improved skill library, async usage, claude support, assistant profile
1 parent c8c51d4 commit 247e2cd

File tree

9 files changed

+360
-152
lines changed

9 files changed

+360
-152
lines changed

interpreter/core/async_core.py

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ async def output(self):
103103
return await self.output_queue.async_q.get()
104104

105105
def respond(self, run_code=None):
106-
for i in range(5): # 5 attempts
106+
for attempt in range(5): # 5 attempts
107107
try:
108108
if run_code == None:
109109
run_code = self.auto_run
@@ -157,10 +157,24 @@ def respond(self, run_code=None):
157157
if not sent_chunks:
158158
print("ERROR. NO CHUNKS SENT. TRYING AGAIN.")
159159
print("Messages:", self.messages)
160+
messages = [
161+
"Hello? Answer please.",
162+
"Just say something, anything.",
163+
"Are you there?",
164+
"Can you respond?",
165+
"Please reply.",
166+
]
167+
self.messages.append(
168+
{
169+
"role": "user",
170+
"type": "message",
171+
"content": messages[attempt % len(messages)],
172+
}
173+
)
160174
time.sleep(1)
161175
else:
162176
self.output_queue.sync_q.put(complete_message)
163-
if self.print or self.debug:
177+
if self.debug:
164178
print("\nServer response complete.\n")
165179
return
166180

@@ -702,29 +716,51 @@ class ChatCompletionRequest(BaseModel):
702716
stream: Optional[bool] = False
703717

704718
async def openai_compatible_generator():
705-
for i, chunk in enumerate(async_interpreter._respond_and_store()):
706-
output_content = None
707-
708-
if chunk["type"] == "message" and "content" in chunk:
709-
output_content = chunk["content"]
710-
if chunk["type"] == "code" and "start" in chunk:
711-
output_content = " "
712-
713-
if output_content:
714-
await asyncio.sleep(0)
715-
output_chunk = {
716-
"id": i,
717-
"object": "chat.completion.chunk",
718-
"created": time.time(),
719-
"model": "open-interpreter",
720-
"choices": [{"delta": {"content": output_content}}],
721-
}
722-
yield f"data: {json.dumps(output_chunk)}\n\n"
719+
made_chunk = False
720+
721+
for message in [
722+
".",
723+
"Just say something, anything.",
724+
"Hello? Answer please.",
725+
"Are you there?",
726+
"Can you respond?",
727+
"Please reply.",
728+
]:
729+
for i, chunk in enumerate(
730+
async_interpreter.chat(message=message, stream=True, display=True)
731+
):
732+
made_chunk = True
733+
734+
if async_interpreter.stop_event.is_set():
735+
break
736+
737+
output_content = None
738+
739+
if chunk["type"] == "message" and "content" in chunk:
740+
output_content = chunk["content"]
741+
if chunk["type"] == "code" and "start" in chunk:
742+
output_content = " "
743+
744+
if output_content:
745+
await asyncio.sleep(0)
746+
output_chunk = {
747+
"id": i,
748+
"object": "chat.completion.chunk",
749+
"created": time.time(),
750+
"model": "open-interpreter",
751+
"choices": [{"delta": {"content": output_content}}],
752+
}
753+
yield f"data: {json.dumps(output_chunk)}\n\n"
754+
755+
if made_chunk:
756+
break
723757

724758
@router.post("/openai/chat/completions")
725759
async def chat_completion(request: ChatCompletionRequest):
726760
# Convert to LMC
727761

762+
async_interpreter.stop_event.set()
763+
728764
last_message = request.messages[-1]
729765

730766
if last_message.role != "user":
@@ -739,20 +775,22 @@ async def chat_completion(request: ChatCompletionRequest):
739775
{
740776
"role": "user",
741777
"type": "message",
742-
"content": str(last_message.content),
778+
"content": last_message.content,
743779
}
744780
)
745-
if type(last_message.content) == list:
781+
print(">", last_message.content)
782+
elif type(last_message.content) == list:
746783
for content in last_message.content:
747784
if content["type"] == "text":
748785
async_interpreter.messages.append(
749786
{"role": "user", "type": "message", "content": str(content)}
750787
)
788+
print(">", content)
751789
elif content["type"] == "image_url":
752790
if "url" not in content["image_url"]:
753791
raise Exception("`url` must be in `image_url`.")
754792
url = content["image_url"]["url"]
755-
print(url[:100])
793+
print("> [user sent an image]", url[:100])
756794
if "base64," not in url:
757795
raise Exception(
758796
'''Image must be in the format: "data:image/jpeg;base64,{base64_image}"'''
@@ -778,12 +816,14 @@ async def chat_completion(request: ChatCompletionRequest):
778816
# Remove that {START} message that would have just been added
779817
async_interpreter.messages = async_interpreter.messages[:-1]
780818

819+
async_interpreter.stop_event.clear()
820+
781821
if request.stream:
782822
return StreamingResponse(
783823
openai_compatible_generator(), media_type="application/x-ndjson"
784824
)
785825
else:
786-
messages = async_interpreter.chat(message="", stream=False, display=True)
826+
messages = async_interpreter.chat(message=".", stream=False, display=True)
787827
content = messages[-1]["content"]
788828
return {
789829
"id": "200",

interpreter/core/computer/skills/skills.py

Lines changed: 51 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
import glob
22
import inspect
3+
import json
34
import os
45
import re
6+
import subprocess
57
from pathlib import Path
68

79
from ....terminal_interface.utils.oi_dir import oi_dir
810
from ...utils.lazy_import import lazy_import
911
from ..utils.recipient_utils import format_to_recipient
1012

11-
# Lazy import of aifs, imported when needed to speed up start time
13+
# Lazy import, imported when needed to speed up start time
1214
aifs = lazy_import("aifs")
15+
pyautogui = lazy_import("pyautogui")
16+
pynput = lazy_import("pynput")
17+
18+
element = None
19+
element_box = None
20+
icon_dimensions = None
1321

1422

1523
class Skills:
@@ -19,6 +27,18 @@ def __init__(self, computer):
1927
self.new_skill = NewSkill()
2028
self.new_skill.path = self.path
2129

30+
def list(self):
31+
return [
32+
file.replace(".py", "()")
33+
for file in os.listdir(self.path)
34+
if file.endswith(".py")
35+
]
36+
37+
def run(self, skill):
38+
print(
39+
"To run a skill, run its name as a function name (it is already imported)."
40+
)
41+
2242
def search(self, query):
2343
return aifs.search(query, self.path, python_docstrings_only=True)
2444

@@ -56,6 +76,7 @@ def import_skills(self):
5676
code_to_run = f.read() + "\n"
5777

5878
if self.computer.interpreter.debug:
79+
print(self.path)
5980
print("IMPORTING SKILL:\n", code_to_run)
6081

6182
output = self.computer.run("python", code_to_run)
@@ -77,18 +98,11 @@ def create(self):
7798
self._name = "Untitled"
7899
print(
79100
"""
80-
@@@SEND_MESSAGE_AS_USER@@@
101+
81102
INSTRUCTIONS
82103
You are creating a new skill. Follow these steps exactly to get me to tell you its name:
83104
1. Ask me what the name of this skill is.
84-
2. After I explicitly tell you the name of the skill (I may tell you to proceed which is not the name— if I do say that, you probably need more information from me, so tell me that), after you get the proper name, write the following (including the markdown code block):
85-
86-
---
87-
Got it. Give me one second.
88-
```python
89-
computer.skills.new_skill.name = "{INSERT THE SKILL NAME FROM QUESTION #1^}"`.
90-
```
91-
---
105+
2. After I explicitly tell you the name of the skill (I may tell you to proceed which is not the name— if I do say that, you probably need more information from me, so tell me that), after you get the proper name, execute `computer.skills.new_skill.name = "{INSERT THE SKILL NAME FROM QUESTION #1}"`.
92106
93107
""".strip()
94108
)
@@ -102,11 +116,11 @@ def name(self, value):
102116
self._name = value
103117
print(
104118
"""
105-
@@@SEND_MESSAGE_AS_USER@@@
119+
106120
Skill named. Now, follow these next INSTRUCTIONS exactly:
107121
108122
1. Ask me what the first step is.
109-
2. When I reply, execute code to accomplish that step.
123+
2. When I reply, execute code to accomplish that step. Write comments explaining your reasoning before each line.
110124
3. Ask me if you completed the step correctly.
111125
a. (!!!!!!!!!!!! >>>>>> THIS IS CRITICAL. DO NOT FORGET THIS.) IF you completed it correctly, run `computer.skills.new_skill.add_step(step, code)` where step is a generalized, natural language description of the step, and code is the code you ran to complete it.
112126
b. IF you did not complete it correctly, try to fix your code and ask me again.
@@ -121,7 +135,7 @@ def add_step(self, step, code):
121135
self.steps.append(step + "\n\n```python\n" + code + "\n```")
122136
print(
123137
"""
124-
@@@SEND_MESSAGE_AS_USER@@@
138+
125139
Step added. Now, follow these next INSTRUCTIONS exactly:
126140
127141
1. Ask me what the next step is.
@@ -138,29 +152,39 @@ def add_step(self, step, code):
138152

139153
def save(self):
140154
normalized_name = re.sub("[^0-9a-zA-Z]+", "_", self.name.lower())
141-
steps_string = "\n".join(
142-
[f"Step {i+1}:\n{step}\n" for i, step in enumerate(self.steps)]
143-
)
144-
steps_string = steps_string.replace('"""', "'''")
155+
145156
skill_string = f'''
146-
147-
def {normalized_name}():
157+
import json
158+
159+
def {normalized_name}(step=0):
148160
"""
149-
{normalized_name}
161+
Run this function to {normalized_name}. Pass in step=0 to see the first step, step=1 to see the next step, etc.
150162
"""
151-
152-
print("To complete this task / run this skill, flexibly follow the following tutorial, swapping out parts as necessary to fulfill the user's task:")
153-
154-
print("""{steps_string}""")
155-
156-
'''.strip()
163+
steps = {self.steps}
164+
165+
print("")
166+
167+
if step < len(steps):
168+
if isinstance(steps[step], str):
169+
print("To complete this task / run this skill, flexibly complete the following step, swapping out parts as necessary to fulfill the user's task. You will need to run the following code yourself, it hasn't run yet!")
170+
print("Step " + str(step + 1) + ": " + steps[step])
171+
else:
172+
computer.mouse.click(steps[step]["element"], icon_dimensions=steps[step]["icon_dimensions"]) # Instructed click
173+
if step + 1 < len(steps):
174+
print("After completing the above, I need you to run {normalized_name}(step=" + str(step + 1) + ") immediatly.")
175+
else:
176+
print("You have completed all the steps, the task/skill has been run!")
177+
else:
178+
print("The specified step number exceeds the available steps. Please run with a valid step number.")
179+
'''.strip()
157180

158181
if not os.path.exists(self.path):
159182
os.makedirs(self.path)
160-
with open(f"{self.path}/{normalized_name}.py", "w") as file:
183+
with open(self.path + "/" + normalized_name + ".py", "w") as file:
161184
file.write(skill_string)
162185

163186
print("SKILL SAVED:", self.name.upper())
187+
164188
print(
165189
"Teaching session finished. Tell the user that the skill above has been saved. Great work!"
166190
)

interpreter/core/computer/terminal/languages/jupyter_language.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,14 @@ def iopub_message_listener():
135135
print("interrupting kernel!!!!!")
136136
self.km.interrupt_kernel()
137137
return
138+
# For async usage
139+
if (
140+
hasattr(self.computer.interpreter, "stop_event")
141+
and self.computer.interpreter.stop_event.is_set()
142+
):
143+
self.km.interrupt_kernel()
144+
self.finish_flag = True
145+
return
138146
try:
139147
msg = self.kc.iopub_channel.get_msg(timeout=0.05)
140148
except queue.Empty:
@@ -262,6 +270,7 @@ def _capture_output(self, message_queue):
262270
hasattr(self.computer.interpreter, "stop_event")
263271
and self.computer.interpreter.stop_event.is_set()
264272
):
273+
self.finish_flag = True
265274
break
266275

267276
if self.listener_thread:

interpreter/core/llm/llm.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,14 @@ def run(self, messages):
105105
), "No message after the first can have the role 'system'"
106106

107107
model = self.model
108+
if model in [
109+
"claude-3.5",
110+
"claude-3-5",
111+
"claude-3.5-sonnet",
112+
"claude-3-5-sonnet",
113+
]:
114+
model = "claude-3-5-sonnet-20240620"
115+
self.model = "claude-3-5-sonnet-20240620"
108116
# Setup our model endpoint
109117
if model == "i":
110118
model = "openai/i"
@@ -446,7 +454,7 @@ def fixed_litellm_completions(**params):
446454
and "api_key" not in params
447455
):
448456
print(
449-
"LiteLLM requires an API key. Trying again with a dummy API key. In the future, please set a dummy API key to prevent this message. (e.g `interpreter --api_key x` or `self.api_key = 'x'`)"
457+
"LiteLLM requires an API key. Trying again with a dummy API key. In the future, if this fixes it, please set a dummy API key to prevent this message. (e.g `interpreter --api_key x` or `self.api_key = 'x'`)"
450458
)
451459
# So, let's try one more time with a dummy API key:
452460
params["api_key"] = "x"

interpreter/core/utils/system_debug_info.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,10 @@ def interpreter_info(interpreter):
8484

8585
messages_to_display = []
8686
for message in interpreter.messages:
87-
message = message.copy()
87+
message = str(message.copy())
8888
try:
89-
if len(message["content"]) > 5000:
90-
message["content"] = (
91-
message["content"][:800] + "..." + message["content"][-800:]
92-
)
89+
if len(message) > 2000:
90+
message = message[:1000]
9391
except Exception as e:
9492
print(str(e), "for message:", message)
9593
messages_to_display.append(message)

0 commit comments

Comments
 (0)