Skip to content

Commit 6735370

Browse files
committed
Speedup, increased max_output default, server improvements
1 parent 58d5727 commit 6735370

File tree

13 files changed

+483
-113
lines changed

13 files changed

+483
-113
lines changed

benchmarks/simple.py

Lines changed: 0 additions & 18 deletions
This file was deleted.

interpreter/core/async_core.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import asyncio
22
import json
3+
import os
34
import threading
45
import traceback
6+
from datetime import datetime
57
from typing import Any, Dict
68

79
from .core import OpenInterpreter
@@ -22,6 +24,7 @@ def __init__(self, *args, **kwargs):
2224
self.respond_thread = None
2325
self.stop_event = threading.Event()
2426
self.output_queue = None
27+
self.id = os.getenv("INTERPRETER_ID", datetime.now().timestamp())
2528

2629
self.server = Server(self)
2730

@@ -41,8 +44,20 @@ async def input(self, chunk):
4144
self.accumulate(chunk)
4245
elif "end" in chunk:
4346
# If the user is done talking, the interpreter should respond.
47+
48+
# But first, process any client messages.
49+
if self.messages[-1]["role"] == "client":
50+
command = self.messages[-1]["content"]
51+
self.messages = self.messages[:-1]
52+
53+
if command == "stop":
54+
return
55+
if command == "go":
56+
# This is to approve code.
57+
# We do nothing, as self.respond will run the last code block if the last message is one.
58+
pass
59+
4460
self.stop_event.clear()
45-
print("Responding.")
4661
self.respond_thread = threading.Thread(target=self.respond)
4762
self.respond_thread.start()
4863

@@ -53,7 +68,7 @@ async def output(self):
5368

5469
def respond(self):
5570
for chunk in self._respond_and_store():
56-
print(chunk.get("content", ""), end="")
71+
print(chunk.get("content", ""), end="", flush=True)
5772
if self.stop_event.is_set():
5873
return
5974
self.output_queue.sync_q.put(chunk)
@@ -160,8 +175,18 @@ async def send_output():
160175
finally:
161176
await websocket.close()
162177

178+
@router.post("/run")
179+
async def run_code(payload: Dict[str, Any]):
180+
language, code = payload.get("language"), payload.get("code")
181+
if not (language and code):
182+
return {"error": "Both 'language' and 'code' are required."}, 400
183+
try:
184+
return {"output": async_interpreter.computer.run(language, code)}
185+
except Exception as e:
186+
return {"error": str(e)}, 500
187+
163188
@router.post("/settings")
164-
async def settings(payload: Dict[str, Any]):
189+
async def set_settings(payload: Dict[str, Any]):
165190
for key, value in payload.items():
166191
print(f"Updating settings: {key} = {value}")
167192
if key in ["llm", "computer"] and isinstance(value, dict):
@@ -172,6 +197,17 @@ async def settings(payload: Dict[str, Any]):
172197

173198
return {"status": "success"}
174199

200+
@router.get("/interpreter/{setting}")
201+
async def get_setting(setting: str):
202+
if hasattr(async_interpreter, setting):
203+
setting_value = getattr(async_interpreter, setting)
204+
try:
205+
return json.dumps({setting: setting_value})
206+
except TypeError:
207+
return {"error": "Failed to serialize the setting value"}, 500
208+
else:
209+
return json.dumps({"error": "Setting not found"}), 404
210+
175211
return router
176212

177213

interpreter/core/computer/terminal/terminal.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import time
23

34
from ..utils.recipient_utils import parse_for_recipient
@@ -59,6 +60,31 @@ def run(self, language, code, stream=False, display=False):
5960
self.computer._has_imported_skills = True
6061
self.computer.skills.import_skills()
6162

63+
# This won't work because truncated code is stored in interpreter.messages :/
64+
# If the full code was stored, we could do this:
65+
if False and "get_last_output()" in code:
66+
if "# We wouldn't want to have maximum recursion depth!" in code:
67+
# We just tried to run this, in a moment.
68+
pass
69+
else:
70+
code_outputs = [
71+
m
72+
for m in self.computer.interpreter.messages
73+
if m["role"] == "computer"
74+
and "content" in m
75+
and m["content"] != ""
76+
]
77+
if len(code_outputs) > 0:
78+
last_output = code_outputs[-1]["content"]
79+
else:
80+
last_output = ""
81+
last_output = json.dumps(last_output)
82+
83+
self.computer.run(
84+
"python",
85+
f"# We wouldn't want to have maximum recursion depth!\nimport json\ndef get_last_output():\n return '''{last_output}'''",
86+
)
87+
6288
if stream == False:
6389
# If stream == False, *pull* from _streaming_run.
6490
output_messages = []

interpreter/core/core.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,9 @@ def is_active_line_chunk(chunk):
379379
# Truncate output if it's console output
380380
if chunk["type"] == "console" and chunk["format"] == "output":
381381
self.messages[-1]["content"] = truncate_output(
382-
self.messages[-1]["content"], self.max_output
382+
self.messages[-1]["content"],
383+
self.max_output,
384+
add_scrollbars=self.computer.import_computer_api, # I consider scrollbars to be a computer API thing
383385
)
384386

385387
# Yield a final end flag
Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import getpass
22
import platform
33

4-
default_system_message = (
5-
f"""
4+
default_system_message = f"""
65
76
You are Open Interpreter, a world-class programmer that can complete any goal by executing code.
87
First, write a plan. **Always recap the plan between each code block** (you have extreme short-term memory loss, so you need to recap the plan between each message block to retain it).
@@ -16,9 +15,3 @@
1615
1716
User's Name: {getpass.getuser()}
1817
User's OS: {platform.system()}""".strip()
19-
+ r"""
20-
21-
{{print(":)")}}
22-
23-
""".strip()
24-
)

interpreter/core/llm/llm.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ def run(self, messages):
6464
And then processing its output, whether it's a function or non function calling model, into LMC format.
6565
"""
6666

67+
if (
68+
self.max_tokens is not None
69+
and self.context_window is not None
70+
and self.max_tokens > self.context_window
71+
):
72+
print(
73+
"Warning: max_tokens is larger than context_window. Setting max_tokens to be 0.2 times the context_window."
74+
)
75+
self.max_tokens = int(0.2 * self.context_window)
76+
6777
# Assertions
6878
assert (
6979
messages[0]["role"] == "system"
@@ -200,7 +210,7 @@ def run(self, messages):
200210
if self.interpreter.in_terminal_interface:
201211
display_markdown_message(
202212
"""
203-
**We were unable to determine the context window of this model.** Defaulting to 3000.
213+
**We were unable to determine the context window of this model.** Defaulting to 8000.
204214
205215
If your model can handle more, run `interpreter --context_window {token limit} --max_tokens {max tokens per response}`.
206216
@@ -210,7 +220,7 @@ def run(self, messages):
210220
else:
211221
display_markdown_message(
212222
"""
213-
**We were unable to determine the context window of this model.** Defaulting to 3000.
223+
**We were unable to determine the context window of this model.** Defaulting to 8000.
214224
215225
If your model can handle more, run `self.context_window = {token limit}`.
216226
@@ -220,7 +230,7 @@ def run(self, messages):
220230
"""
221231
)
222232
messages = tt.trim(
223-
messages, system_message=system_message, max_tokens=3000
233+
messages, system_message=system_message, max_tokens=8000
224234
)
225235
except:
226236
# If we're trimming messages, this won't work.
@@ -330,7 +340,7 @@ def load(self):
330340
self.context_window = context_length
331341
if self.max_tokens == None:
332342
if self.context_window != None:
333-
self.max_tokens = int(self.context_window * 0.8)
343+
self.max_tokens = int(self.context_window * 0.2)
334344

335345
# Send a ping, which will actually load the model
336346
print(f"Loading {model_name}...\n")

interpreter/core/respond.py

Lines changed: 50 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -75,56 +75,61 @@ def respond(interpreter):
7575

7676
### RUN THE LLM ###
7777

78-
try:
79-
for chunk in interpreter.llm.run(messages_for_llm):
80-
yield {"role": "assistant", **chunk}
78+
if (
79+
interpreter.messages[-1]["type"] != "code"
80+
): # If it is, we should run the code (we do below)
81+
try:
82+
for chunk in interpreter.llm.run(messages_for_llm):
83+
yield {"role": "assistant", **chunk}
8184

82-
except litellm.exceptions.BudgetExceededError:
83-
display_markdown_message(
84-
f"""> Max budget exceeded
85+
except litellm.exceptions.BudgetExceededError:
86+
display_markdown_message(
87+
f"""> Max budget exceeded
8588
86-
**Session spend:** ${litellm._current_cost}
87-
**Max budget:** ${interpreter.max_budget}
89+
**Session spend:** ${litellm._current_cost}
90+
**Max budget:** ${interpreter.max_budget}
8891
89-
Press CTRL-C then run `interpreter --max_budget [higher USD amount]` to proceed.
90-
"""
91-
)
92-
break
93-
# Provide extra information on how to change API keys, if we encounter that error
94-
# (Many people writing GitHub issues were struggling with this)
95-
except Exception as e:
96-
if (
97-
interpreter.offline == False
98-
and "auth" in str(e).lower()
99-
or "api key" in str(e).lower()
100-
):
101-
output = traceback.format_exc()
102-
raise Exception(
103-
f"{output}\n\nThere might be an issue with your API key(s).\n\nTo reset your API key (we'll use OPENAI_API_KEY for this example, but you may need to reset your ANTHROPIC_API_KEY, HUGGINGFACE_API_KEY, etc):\n Mac/Linux: 'export OPENAI_API_KEY=your-key-here'. Update your ~/.zshrc on MacOS or ~/.bashrc on Linux with the new key if it has already been persisted there.,\n Windows: 'setx OPENAI_API_KEY your-key-here' then restart terminal.\n\n"
104-
)
105-
elif interpreter.offline == False and "not have access" in str(e).lower():
106-
response = input(
107-
f" You do not have access to {interpreter.llm.model}. You will need to add a payment method and purchase credits for the OpenAI API billing page (different from ChatGPT) to use `GPT-4`.\n\nhttps://platform.openai.com/account/billing/overview\n\nWould you like to try GPT-3.5-TURBO instead? (y/n)\n\n "
92+
Press CTRL-C then run `interpreter --max_budget [higher USD amount]` to proceed.
93+
"""
10894
)
109-
print("") # <- Aesthetic choice
110-
111-
if response.strip().lower() == "y":
112-
interpreter.llm.model = "gpt-3.5-turbo-1106"
113-
interpreter.llm.context_window = 16000
114-
interpreter.llm.max_tokens = 4096
115-
interpreter.llm.supports_functions = True
116-
display_markdown_message(
117-
f"> Model set to `{interpreter.llm.model}`"
118-
)
119-
else:
95+
break
96+
# Provide extra information on how to change API keys, if we encounter that error
97+
# (Many people writing GitHub issues were struggling with this)
98+
except Exception as e:
99+
if (
100+
interpreter.offline == False
101+
and "auth" in str(e).lower()
102+
or "api key" in str(e).lower()
103+
):
104+
output = traceback.format_exc()
120105
raise Exception(
121-
"\n\nYou will need to add a payment method and purchase credits for the OpenAI API billing page (different from ChatGPT) to use GPT-4.\n\nhttps://platform.openai.com/account/billing/overview"
106+
f"{output}\n\nThere might be an issue with your API key(s).\n\nTo reset your API key (we'll use OPENAI_API_KEY for this example, but you may need to reset your ANTHROPIC_API_KEY, HUGGINGFACE_API_KEY, etc):\n Mac/Linux: 'export OPENAI_API_KEY=your-key-here'. Update your ~/.zshrc on MacOS or ~/.bashrc on Linux with the new key if it has already been persisted there.,\n Windows: 'setx OPENAI_API_KEY your-key-here' then restart terminal.\n\n"
107+
)
108+
elif (
109+
interpreter.offline == False and "not have access" in str(e).lower()
110+
):
111+
response = input(
112+
f" You do not have access to {interpreter.llm.model}. You will need to add a payment method and purchase credits for the OpenAI API billing page (different from ChatGPT) to use `GPT-4`.\n\nhttps://platform.openai.com/account/billing/overview\n\nWould you like to try GPT-3.5-TURBO instead? (y/n)\n\n "
122113
)
123-
elif interpreter.offline and not interpreter.os:
124-
print(traceback.format_exc())
125-
raise Exception("Error occurred. " + str(e))
126-
else:
127-
raise
114+
print("") # <- Aesthetic choice
115+
116+
if response.strip().lower() == "y":
117+
interpreter.llm.model = "gpt-3.5-turbo-1106"
118+
interpreter.llm.context_window = 16000
119+
interpreter.llm.max_tokens = 4096
120+
interpreter.llm.supports_functions = True
121+
display_markdown_message(
122+
f"> Model set to `{interpreter.llm.model}`"
123+
)
124+
else:
125+
raise Exception(
126+
"\n\nYou will need to add a payment method and purchase credits for the OpenAI API billing page (different from ChatGPT) to use GPT-4.\n\nhttps://platform.openai.com/account/billing/overview"
127+
)
128+
elif interpreter.offline and not interpreter.os:
129+
print(traceback.format_exc())
130+
raise Exception("Error occurred. " + str(e))
131+
else:
132+
raise
128133

129134
### RUN CODE (if it's there) ###
130135

@@ -142,7 +147,7 @@ def respond(interpreter):
142147
if interpreter.verbose:
143148
print("Removing `\n")
144149

145-
if language == "text":
150+
if language == "text" or language == "markdown":
146151
# It does this sometimes just to take notes. Let it, it's useful.
147152
# In the future we should probably not detect this behavior as code at all.
148153
continue

interpreter/core/utils/system_debug_info.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ def interpreter_info(interpreter):
103103
Function calling: {interpreter.llm.supports_functions}
104104
Context window: {interpreter.llm.context_window}
105105
Max tokens: {interpreter.llm.max_tokens}
106+
Computer API: {interpreter.computer.import_computer_api}
106107
107108
Auto run: {interpreter.auto_run}
108109
API base: {interpreter.llm.api_base}

interpreter/core/utils/truncate_output.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1-
def truncate_output(data, max_output_chars=2000):
1+
def truncate_output(data, max_output_chars=2800, add_scrollbars=False):
22
if "@@@DO_NOT_TRUNCATE@@@" in data:
33
return data
44

55
needs_truncation = False
66

77
message = f"Output truncated. Showing the last {max_output_chars} characters.\n\n"
88

9+
# This won't work because truncated code is stored in interpreter.messages :/
10+
# If the full code was stored, we could do this:
11+
if add_scrollbars:
12+
message = (
13+
message.strip()
14+
+ f" Run `get_last_output()[0:{max_output_chars}]` to see the first page.\n\n"
15+
)
16+
# Then we have code in `terminal.py` which makes that function work. It should be a computer tool though to just access messages IMO. Or like, self.messages.
17+
918
# Remove previous truncation message if it exists
1019
if data.startswith(message):
1120
data = data[len(message) :]

interpreter/terminal_interface/start_terminal_interface.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ def main():
546546
contribute = "y"
547547
else:
548548
print(
549-
"Thanks for your feedback! Would you like to send us this chat so we can improve?\n"
549+
"\nThanks for your feedback! Would you like to send us this chat so we can improve?\n"
550550
)
551551
contribute = input("(y/n): ").strip().lower()
552552

0 commit comments

Comments
 (0)