Skip to content

Commit b27befd

Browse files
committed
Edit code blocks with e after it asks for confirmation
1 parent 96632d2 commit b27befd

File tree

9 files changed

+650
-542
lines changed

9 files changed

+650
-542
lines changed

Dockerfile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,20 @@ FROM python:3.11.8
88
# Set environment variables
99
# ENV OPENAI_API_KEY ...
1010

11+
ENV HOST 0.0.0.0
12+
# ^ Sets the server host to 0.0.0.0, Required for the server to be accessible outside the container
13+
1114
# Copy required files into container
12-
RUN mkdir -p interpreter
15+
RUN mkdir -p interpreter scripts
1316
COPY interpreter/ interpreter/
17+
COPY scripts/ scripts/
1418
COPY poetry.lock pyproject.toml README.md ./
1519

1620
# Expose port 8000
1721
EXPOSE 8000
1822

1923
# Install server dependencies
20-
RUN pip install -e ".[server]"
24+
RUN pip install ".[server]"
2125

2226
# Start the server
2327
ENTRYPOINT ["interpreter", "--server"]

benchmarks/run.py

Whitespace-only changes.

docs/server/usage.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ Example:
107107
import requests
108108

109109
settings = {
110-
"model": "gpt-4",
110+
"llm": {"model": "gpt-4"},
111111
"custom_instructions": "You only write Python code.",
112112
"auto_run": True,
113113
}
@@ -120,9 +120,9 @@ To get current settings, send a GET request to `http://localhost:8000/settings/{
120120

121121
Example:
122122
```python
123-
response = requests.get("http://localhost:8000/settings/model")
123+
response = requests.get("http://localhost:8000/settings/custom_instructions")
124124
print(response.json())
125-
# Output: {"model": "gpt-4"}
125+
# Output: {"custom_instructions": "You only write react."}
126126
```
127127

128128
## Advanced Usage: Accessing the FastAPI App Directly

interpreter/core/async_core.py

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -75,34 +75,42 @@ async def output(self):
7575
return await self.output_queue.async_q.get()
7676

7777
def respond(self, run_code=None):
78-
if run_code == None:
79-
run_code = self.auto_run
78+
try:
79+
if run_code == None:
80+
run_code = self.auto_run
8081

81-
for chunk in self._respond_and_store():
82-
if chunk["type"] == "confirmation":
83-
if run_code:
84-
continue # We don't need to send out confirmation chunks on the server. I don't even like them.
85-
else:
86-
break
82+
for chunk in self._respond_and_store():
83+
if chunk["type"] == "confirmation":
84+
if run_code:
85+
continue # We don't need to send out confirmation chunks on the server. I don't even like them.
86+
else:
87+
break
8788

88-
if self.stop_event.is_set():
89-
return
89+
if self.stop_event.is_set():
90+
return
9091

91-
if self.print:
92-
if "start" in chunk:
93-
print("\n")
94-
if chunk["type"] in ["code", "console"] and "format" in chunk:
92+
if self.print:
9593
if "start" in chunk:
96-
print("\n------------\n\n```" + chunk["format"], flush=True)
97-
if "end" in chunk:
98-
print("\n```\n\n------------\n\n", flush=True)
99-
print(chunk.get("content", ""), end="", flush=True)
100-
101-
self.output_queue.sync_q.put(chunk)
102-
103-
self.output_queue.sync_q.put(
104-
{"role": "server", "type": "status", "content": "complete"}
105-
)
94+
print("\n")
95+
if chunk["type"] in ["code", "console"] and "format" in chunk:
96+
if "start" in chunk:
97+
print("\n------------\n\n```" + chunk["format"], flush=True)
98+
if "end" in chunk:
99+
print("\n```\n\n------------\n\n", flush=True)
100+
print(chunk.get("content", ""), end="", flush=True)
101+
102+
self.output_queue.sync_q.put(chunk)
103+
104+
self.output_queue.sync_q.put(
105+
{"role": "server", "type": "status", "content": "complete"}
106+
)
107+
except Exception as e:
108+
error_message = {
109+
"role": "server",
110+
"type": "error",
111+
"content": traceback.format_exc() + "\n" + str(e),
112+
}
113+
self.output_queue.sync_q.put(error_message)
106114

107115
def accumulate(self, chunk):
108116
"""
@@ -202,13 +210,29 @@ async def send_output():
202210
finally:
203211
await websocket.close()
204212

213+
# TODO
214+
@router.post("/")
215+
async def post_input(payload: Dict[str, Any]):
216+
# This doesn't work, but something like this should exist
217+
query = payload.get("query")
218+
if not query:
219+
return {"error": "Query is required."}, 400
220+
try:
221+
async_interpreter.input.put(query)
222+
return {"status": "success"}
223+
except Exception as e:
224+
return {"error": str(e)}, 500
225+
205226
@router.post("/run")
206227
async def run_code(payload: Dict[str, Any]):
207228
language, code = payload.get("language"), payload.get("code")
208229
if not (language and code):
209230
return {"error": "Both 'language' and 'code' are required."}, 400
210231
try:
211-
return {"output": async_interpreter.computer.run(language, code)}
232+
print(f"Running {language}:", code)
233+
output = async_interpreter.computer.run(language, code)
234+
print("Output:", output)
235+
return {"output": output}
212236
except Exception as e:
213237
return {"error": str(e)}, 500
214238

@@ -217,10 +241,20 @@ async def set_settings(payload: Dict[str, Any]):
217241
for key, value in payload.items():
218242
print(f"Updating settings: {key} = {value}")
219243
if key in ["llm", "computer"] and isinstance(value, dict):
220-
for sub_key, sub_value in value.items():
221-
setattr(getattr(async_interpreter, key), sub_key, sub_value)
222-
else:
244+
if hasattr(async_interpreter, key):
245+
for sub_key, sub_value in value.items():
246+
if hasattr(getattr(async_interpreter, key), sub_key):
247+
setattr(getattr(async_interpreter, key), sub_key, sub_value)
248+
else:
249+
return {
250+
"error": f"Sub-setting {sub_key} not found in {key}"
251+
}, 404
252+
else:
253+
return {"error": f"Setting {key} not found"}, 404
254+
elif hasattr(async_interpreter, key):
223255
setattr(async_interpreter, key, value)
256+
else:
257+
return {"error": f"Setting {key} not found"}, 404
224258

225259
return {"status": "success"}
226260

@@ -238,8 +272,14 @@ async def get_setting(setting: str):
238272
return router
239273

240274

275+
host = os.getenv(
276+
"HOST", "127.0.0.1"
277+
) # IP address for localhost, used for local testing
278+
port = int(os.getenv("PORT", 8000)) # Default port is 8000
279+
280+
241281
class Server:
242-
def __init__(self, async_interpreter, host="0.0.0.0", port=8000):
282+
def __init__(self, async_interpreter, host=host, port=port):
243283
self.app = FastAPI()
244284
router = create_router(async_interpreter)
245285
self.app.include_router(router)

interpreter/core/llm/llm.py

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -378,25 +378,29 @@ def fixed_litellm_completions(**params):
378378
litellm.drop_params = True
379379

380380
# Run completion
381+
attempts = 4
381382
first_error = None
382-
try:
383-
yield from litellm.completion(**params)
384-
except Exception as e:
385-
# Store the first error
386-
first_error = e
387-
# LiteLLM can fail if there's no API key,
388-
# even though some models (like local ones) don't require it.
389-
390-
if "api key" in str(first_error).lower() and "api_key" not in params:
391-
print(
392-
"LiteLLM requires an API key. Please set a dummy API key to prevent this message. (e.g `interpreter --api_key x` or `self.api_key = 'x'`)"
393-
)
394-
395-
# So, let's try one more time with a dummy API key:
396-
params["api_key"] = "x"
397383

384+
for attempt in range(attempts):
398385
try:
399386
yield from litellm.completion(**params)
400-
except:
401-
# If the second attempt also fails, raise the first error
402-
raise first_error
387+
return # If the completion is successful, exit the function
388+
except Exception as e:
389+
if attempt == 0:
390+
# Store the first error
391+
first_error = e
392+
if (
393+
isinstance(e, litellm.exceptions.AuthenticationError)
394+
and "api_key" not in params
395+
):
396+
print(
397+
"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'`)"
398+
)
399+
# So, let's try one more time with a dummy API key:
400+
params["api_key"] = "x"
401+
if attempt == 1:
402+
# Try turning up the temperature?
403+
params["temperature"] = params.get("temperature", 0.0) + 0.1
404+
405+
if first_error is not None:
406+
raise first_error # If all attempts fail, raise the first error

interpreter/core/respond.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,9 @@ def respond(interpreter):
219219
# We need to tell python what we (the generator) should do if they exit
220220
break
221221

222+
# They may have edited the code! Grab it again
223+
code = interpreter.messages[-1]["content"]
224+
222225
# don't let it import computer — we handle that!
223226
if interpreter.computer.import_computer_api and language == "python":
224227
code = code.replace("import computer\n", "pass\n")

interpreter/terminal_interface/terminal_interface.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import random
1414
import re
1515
import subprocess
16+
import tempfile
1617
import time
1718

1819
from ..core.utils.scan_code import scan_code
@@ -286,6 +287,31 @@ def terminal_interface(interpreter, message):
286287
active_block.margin_top = False # <- Aesthetic choice
287288
active_block.language = language
288289
active_block.code = code
290+
elif response.strip().lower() == "e":
291+
# Edit
292+
293+
# Create a temporary file
294+
with tempfile.NamedTemporaryFile(
295+
suffix=".tmp", delete=False
296+
) as tf:
297+
tf.write(code.encode())
298+
tf.flush()
299+
300+
# Open the temporary file with the default editor
301+
subprocess.call([os.environ.get("EDITOR", "vim"), tf.name])
302+
303+
# Read the modified code
304+
with open(tf.name, "r") as tf:
305+
code = tf.read()
306+
307+
interpreter.messages[-1]["content"] = code # Give it code
308+
309+
# Delete the temporary file
310+
os.unlink(tf.name)
311+
active_block = CodeBlock()
312+
active_block.margin_top = False # <- Aesthetic choice
313+
active_block.language = language
314+
active_block.code = code
289315
else:
290316
# User declined to run code.
291317
interpreter.messages.append(

0 commit comments

Comments
 (0)