Skip to content

Commit df9293a

Browse files
authored
Merge pull request #1465 from OpenInterpreter/development
Development
2 parents c81d910 + 547b180 commit df9293a

File tree

9 files changed

+206
-46
lines changed

9 files changed

+206
-46
lines changed

interpreter/core/async_core.py

Lines changed: 100 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def __init__(self, *args, **kwargs):
6060
self.server = Server(self)
6161

6262
# For the 01. This lets the OAI compatible server accumulate context before responding.
63-
self.context_mode = True
63+
self.context_mode = False
6464

6565
async def input(self, chunk):
6666
"""
@@ -723,7 +723,39 @@ class ChatCompletionRequest(BaseModel):
723723
temperature: Optional[float] = None
724724
stream: Optional[bool] = False
725725

726-
async def openai_compatible_generator():
726+
async def openai_compatible_generator(run_code):
727+
if run_code:
728+
print("Running code.\n")
729+
for i, chunk in enumerate(async_interpreter._respond_and_store()):
730+
if "content" in chunk:
731+
print(chunk["content"], end="") # Sorry! Shitty display for now
732+
if "start" in chunk:
733+
print("\n")
734+
735+
output_content = None
736+
737+
if chunk["type"] == "message" and "content" in chunk:
738+
output_content = chunk["content"]
739+
if chunk["type"] == "code" and "start" in chunk:
740+
output_content = " "
741+
if chunk["type"] == "code" and "content" in chunk:
742+
output_content = (
743+
f"""<unvoiced code="{chunk["content"]}"></unvoiced>"""
744+
)
745+
746+
if output_content:
747+
await asyncio.sleep(0)
748+
output_chunk = {
749+
"id": i,
750+
"object": "chat.completion.chunk",
751+
"created": time.time(),
752+
"model": "open-interpreter",
753+
"choices": [{"delta": {"content": output_content}}],
754+
}
755+
yield f"data: {json.dumps(output_chunk)}\n\n"
756+
757+
return
758+
727759
made_chunk = False
728760

729761
for message in [
@@ -740,6 +772,12 @@ async def openai_compatible_generator():
740772
await asyncio.sleep(0) # Yield control to the event loop
741773
made_chunk = True
742774

775+
if (
776+
chunk["type"] == "confirmation"
777+
and async_interpreter.auto_run == False
778+
):
779+
break
780+
743781
if async_interpreter.stop_event.is_set():
744782
break
745783

@@ -749,6 +787,10 @@ async def openai_compatible_generator():
749787
output_content = chunk["content"]
750788
if chunk["type"] == "code" and "start" in chunk:
751789
output_content = " "
790+
if chunk["type"] == "code" and "content" in chunk:
791+
output_content = (
792+
f"""<unvoiced code="{chunk["content"]}"></unvoiced>"""
793+
)
752794

753795
if output_content:
754796
await asyncio.sleep(0)
@@ -764,6 +806,18 @@ async def openai_compatible_generator():
764806
if made_chunk:
765807
break
766808

809+
if async_interpreter.messages[-1]["type"] == "code":
810+
await asyncio.sleep(0)
811+
output_content = "{CODE_FINISHED}"
812+
output_chunk = {
813+
"id": i,
814+
"object": "chat.completion.chunk",
815+
"created": time.time(),
816+
"model": "open-interpreter",
817+
"choices": [{"delta": {"content": output_content}}],
818+
}
819+
yield f"data: {json.dumps(output_chunk)}\n\n"
820+
767821
@router.post("/openai/chat/completions")
768822
async def chat_completion(request: ChatCompletionRequest):
769823
global last_start_time
@@ -776,6 +830,9 @@ async def chat_completion(request: ChatCompletionRequest):
776830

777831
if last_message.content == "{STOP}":
778832
# Handle special STOP token
833+
async_interpreter.stop_event.set()
834+
time.sleep(5)
835+
async_interpreter.stop_event.clear()
779836
return
780837

781838
if last_message.content in ["{CONTEXT_MODE_ON}", "{REQUIRE_START_ON}"]:
@@ -786,6 +843,14 @@ async def chat_completion(request: ChatCompletionRequest):
786843
async_interpreter.context_mode = False
787844
return
788845

846+
if last_message.content == "{AUTO_RUN_ON}":
847+
async_interpreter.auto_run = True
848+
return
849+
850+
if last_message.content == "{AUTO_RUN_OFF}":
851+
async_interpreter.auto_run = False
852+
return
853+
789854
if type(last_message.content) == str:
790855
async_interpreter.messages.append(
791856
{
@@ -825,43 +890,49 @@ async def chat_completion(request: ChatCompletionRequest):
825890
}
826891
)
827892

828-
if async_interpreter.context_mode:
829-
# In context mode, we only respond if we recieved a {START} message
830-
# Otherwise, we're just accumulating context
831-
if last_message.content == "{START}":
832-
if async_interpreter.messages[-1]["content"] == "{START}":
893+
run_code = False
894+
if last_message.content == "{RUN}":
895+
run_code = True
896+
# Remove that {RUN} message that would have just been added
897+
async_interpreter.messages = async_interpreter.messages[:-1]
898+
else:
899+
if async_interpreter.context_mode:
900+
# In context mode, we only respond if we recieved a {START} message
901+
# Otherwise, we're just accumulating context
902+
if last_message.content == "{START}":
903+
if async_interpreter.messages[-1]["content"] == "{START}":
904+
# Remove that {START} message that would have just been added
905+
async_interpreter.messages = async_interpreter.messages[:-1]
906+
last_start_time = time.time()
907+
if (
908+
async_interpreter.messages
909+
and async_interpreter.messages[-1].get("role") != "user"
910+
):
911+
return
912+
else:
913+
# Check if we're within 6 seconds of last_start_time
914+
current_time = time.time()
915+
if current_time - last_start_time <= 6:
916+
# Continue processing
917+
pass
918+
else:
919+
# More than 6 seconds have passed, so return
920+
return
921+
922+
else:
923+
if last_message.content == "{START}":
924+
# This just sometimes happens I guess
833925
# Remove that {START} message that would have just been added
834926
async_interpreter.messages = async_interpreter.messages[:-1]
835-
last_start_time = time.time()
836-
if (
837-
async_interpreter.messages
838-
and async_interpreter.messages[-1].get("role") != "user"
839-
):
840-
return
841-
else:
842-
# Check if we're within 6 seconds of last_start_time
843-
current_time = time.time()
844-
if current_time - last_start_time <= 6:
845-
# Continue processing
846-
pass
847-
else:
848-
# More than 6 seconds have passed, so return
849927
return
850928

851-
else:
852-
if last_message.content == "{START}":
853-
# This just sometimes happens I guess
854-
# Remove that {START} message that would have just been added
855-
async_interpreter.messages = async_interpreter.messages[:-1]
856-
return
857-
858929
async_interpreter.stop_event.set()
859930
time.sleep(0.1)
860931
async_interpreter.stop_event.clear()
861932

862933
if request.stream:
863934
return StreamingResponse(
864-
openai_compatible_generator(), media_type="application/x-ndjson"
935+
openai_compatible_generator(run_code), media_type="application/x-ndjson"
865936
)
866937
else:
867938
messages = async_interpreter.chat(message=".", stream=False, display=True)

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,12 @@ def iopub_message_listener():
149149
self.finish_flag = True
150150
return
151151
try:
152+
input_patience = int(
153+
os.environ.get("INTERPRETER_TERMINAL_INPUT_PATIENCE", 15)
154+
)
152155
if (
153-
time.time() - self.last_output_time > 15
154-
and time.time() - self.last_output_message_time > 15
156+
time.time() - self.last_output_time > input_patience
157+
and time.time() - self.last_output_message_time > input_patience
155158
):
156159
self.last_output_message_time = time.time()
157160

@@ -364,7 +367,11 @@ def preprocess_python(code):
364367

365368
# Add print commands that tell us what the active line is
366369
# but don't do this if any line starts with ! or %
367-
if not any(line.strip().startswith(("!", "%")) for line in code.split("\n")):
370+
if (
371+
not any(line.strip().startswith(("!", "%")) for line in code.split("\n"))
372+
and os.environ.get("INTERPRETER_ACTIVE_LINE_DETECTION", "True").lower()
373+
== "true"
374+
):
368375
code = add_active_line_prints(code)
369376

370377
# Wrap in a try except (DISABLED)

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ def preprocess_shell(code):
4545

4646
# Add commands that tell us what the active line is
4747
# if it's multiline, just skip this. soon we should make it work with multiline
48-
if not has_multiline_commands(code):
48+
if (
49+
not has_multiline_commands(code)
50+
and os.environ.get("INTERPRETER_ACTIVE_LINE_DETECTION", "True").lower()
51+
== "true"
52+
):
4953
code = add_active_line_prints(code)
5054

5155
# Add end command (we'll be listening for this so we know when it ends)

interpreter/core/core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def __init__(
5757
"Let me know what you'd like to do next.",
5858
"Please provide more information.",
5959
],
60-
disable_telemetry=os.getenv("DISABLE_TELEMETRY", "false").lower() == "true",
60+
disable_telemetry=False,
6161
in_terminal_interface=False,
6262
conversation_history=True,
6363
conversation_filename=None,

interpreter/core/utils/truncate_output.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ def truncate_output(data, max_output_chars=2800, add_scrollbars=False):
44

55
needs_truncation = False
66

7-
message = f"Output truncated. Showing the last {max_output_chars} characters.\n\n"
7+
message = f"Output truncated. Showing the last {max_output_chars} characters. You should try again and use computer.ai.summarize(output) over the output, or break it down into smaller steps.\n\n"
88

99
# This won't work because truncated code is stored in interpreter.messages :/
1010
# If the full code was stored, we could do this:
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""
2+
This is an Open Interpreter profile. It is specialized for searching AWS documentation and is configured to run Anthropic's `Claude 3.5 Sonnet`.
3+
"""
4+
5+
# Configure Open Interpreter
6+
from interpreter import interpreter
7+
8+
interpreter.llm.model = "claude-3-5-sonnet-20240620"
9+
interpreter.computer.import_computer_api = True
10+
interpreter.llm.supports_functions = True
11+
interpreter.llm.supports_vision = True
12+
interpreter.llm.context_window = 100000
13+
interpreter.llm.max_tokens = 4096
14+
15+
AWS_DOCS_SEARCH_URL = "https://docs.aws.amazon.com/search/doc-search.html?searchPath=documentation&searchQuery=<query>"
16+
17+
custom_tool = """
18+
19+
import os
20+
import requests
21+
22+
def search_aws_docs(query):
23+
24+
url = "https://api.perplexity.ai/chat/completions"
25+
26+
payload = {
27+
"model": "llama-3.1-sonar-small-128k-online",
28+
"messages": [
29+
{
30+
"role": "system",
31+
"content": "Be precise and concise."
32+
},
33+
{
34+
"role": "user",
35+
"content": query
36+
}
37+
],
38+
"temperature": 0.2,
39+
"top_p": 0.9,
40+
"return_citations": True,
41+
"search_domain_filter": ["docs.aws.amazon.com"],
42+
"return_images": False,
43+
"return_related_questions": False,
44+
#"search_recency_filter": "month",
45+
"top_k": 0,
46+
"stream": False,
47+
"presence_penalty": 0,
48+
"frequency_penalty": 1
49+
}
50+
headers = {
51+
"Authorization": f"Bearer {os.environ.get('PPLX_API_KEY')}",
52+
"Content-Type": "application/json"
53+
}
54+
55+
response = requests.request("POST", url, json=payload, headers=headers)
56+
57+
print(response.text)
58+
59+
return response.text
60+
61+
"""
62+
63+
64+
interpreter.computer.run("python", custom_tool)
65+
66+
interpreter.custom_instructions = f"""
67+
You have access to a special function imported inside your python environment, to be executed in python, called `search_aws_docs(query)` which lets you search the AWS docs.
68+
Use it frequently to ground your usage of AWS products.
69+
Use it often!
70+
71+
If the user wants you to open the docs, open their browser to the URL: {AWS_DOCS_SEARCH_URL}
72+
"""

interpreter/terminal_interface/profiles/defaults/bedrock-anthropic.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
"""
44

55
"""
6-
Required pip package:
7-
pip install boto3>=1.28.57
6+
Recommended pip package:
7+
pip install boto3
88
9-
Required environment variables:
9+
Recommended environment variables:
1010
os.environ["AWS_ACCESS_KEY_ID"] = "" # Access key
1111
os.environ["AWS_SECRET_ACCESS_KEY"] = "" # Secret access key
1212
os.environ["AWS_REGION_NAME"] = "" # us-east-1, us-east-2, us-west-1, us-west-2
@@ -20,7 +20,7 @@
2020

2121
interpreter.computer.import_computer_api = True
2222

23-
interpreter.llm.supports_functions = True
24-
interpreter.llm.supports_vision = True
25-
interpreter.llm.context_window = 100000
23+
interpreter.llm.supports_functions = False
24+
interpreter.llm.supports_vision = False
25+
interpreter.llm.context_window = 10000
2626
interpreter.llm.max_tokens = 4096

interpreter/terminal_interface/start_terminal_interface.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,7 @@ def print_help(self, *args, **kwargs):
478478
### Set attributes on interpreter, because the arguments passed in via the CLI should override profile
479479

480480
set_attributes(args, arguments)
481+
interpreter.disable_telemetry=os.getenv("DISABLE_TELEMETRY", "false").lower() == "true" or args.disable_telemetry
481482

482483
### Set some helpful settings we know are likely to be true
483484

interpreter/terminal_interface/terminal_interface.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,16 @@ def terminal_interface(interpreter, message):
9292
interpreter.messages = interpreter.messages[:-1]
9393
else:
9494
### This is the primary input for Open Interpreter.
95-
message = (
96-
cli_input("> ").strip()
97-
if interpreter.multi_line
98-
else input("> ").strip()
99-
)
95+
try:
96+
message = (
97+
cli_input("> ").strip()
98+
if interpreter.multi_line
99+
else input("> ").strip()
100+
)
101+
except (KeyboardInterrupt, EOFError):
102+
# Treat Ctrl-D on an empty line the same as Ctrl-C by exiting gracefully
103+
interpreter.display_message("\n\n`Exiting...`")
104+
raise KeyboardInterrupt
100105

101106
try:
102107
# This lets users hit the up arrow key for past messages

0 commit comments

Comments
 (0)