Skip to content

Commit fd5dc57

Browse files
committed
wip
1 parent 9372a62 commit fd5dc57

File tree

10 files changed

+123
-68
lines changed

10 files changed

+123
-68
lines changed

Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ FROM arm64v8/python:3.9.17-alpine
22
WORKDIR /app
33

44
RUN apk add build-base linux-headers clang rust cargo
5-
COPY . .
5+
COPY requirements.txt requirements_docker.txt ./
66
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt
77
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements_docker.txt
88

9+
COPY . .
10+
911
RUN adduser -D gpt
1012
USER gpt
1113
RUN mkdir -p $HOME/.config/gpt-cli

gpt.py

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
choose_config_file,
3030
read_yaml_config,
3131
)
32+
from gptcli.interpreter import CodeInterpreterListener
3233
from gptcli.llama import init_llama_models
3334
from gptcli.logging import LoggingChatListener
3435
from gptcli.cost import PriceChatListener
@@ -216,27 +217,6 @@ def run_non_interactive(args, assistant):
216217
simple_response(assistant, "\n".join(args.prompt), stream=not args.no_stream)
217218

218219

219-
def evaluate_python(source: str) -> str:
220-
if not os.environ.get("GPTCLI_ALLOW_CODE_EXECUTION"):
221-
return "unable to evaluate"
222-
223-
source = "%colors NoColor\n" + source
224-
225-
logger.info("Evaluating python code: %s", source)
226-
227-
try:
228-
result = subprocess.check_output(
229-
["ipython", "-c", source], stderr=subprocess.STDOUT
230-
).decode("utf-8")
231-
except subprocess.CalledProcessError as e:
232-
result = e.output.decode("utf-8")
233-
except Exception as e:
234-
return traceback.format_exc()
235-
236-
logger.info("Result: %s", result)
237-
return result
238-
239-
240220
class CLIChatSession(ChatSession):
241221
def __init__(self, assistant: Assistant, markdown: bool, show_price: bool):
242222
listeners = [
@@ -247,13 +227,13 @@ def __init__(self, assistant: Assistant, markdown: bool, show_price: bool):
247227
if show_price:
248228
listeners.append(PriceChatListener(assistant))
249229

230+
if os.environ.get("GPTCLI_ALLOW_CODE_EXECUTION") == "1":
231+
listeners.append(CodeInterpreterListener("python_eval"))
232+
250233
listener = CompositeChatListener(listeners)
251234
super().__init__(
252235
assistant,
253236
listener,
254-
functions={
255-
"python_eval": evaluate_python,
256-
},
257237
)
258238

259239

gptcli/assistant.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import platform
55
from typing import Any, Dict, Iterator, Optional, TypedDict, List
66

7-
from gptcli.completion import CompletionProvider, ModelOverrides, Message
7+
from gptcli.completion import Completion, CompletionProvider, ModelOverrides, Message
88
from gptcli.google import GoogleCompletionProvider
99
from gptcli.llama import LLaMACompletionProvider
1010
from gptcli.openai import OpenAICompletionProvider
@@ -16,12 +16,14 @@ class AssistantConfig(TypedDict, total=False):
1616
model: str
1717
temperature: float
1818
top_p: float
19+
enable_code_execution: bool
1920

2021

2122
CONFIG_DEFAULTS = {
2223
"model": "gpt-3.5-turbo",
2324
"temperature": 0.7,
2425
"top_p": 1.0,
26+
"enable_code_execution": False,
2527
}
2628

2729
DEFAULT_ASSISTANTS: Dict[str, AssistantConfig] = {
@@ -89,7 +91,7 @@ def init_messages(self) -> List[Message]:
8991
return self.config.get("messages", [])[:]
9092

9193
def supported_overrides(self) -> List[str]:
92-
return ["model", "temperature", "top_p"]
94+
return ["model", "temperature", "top_p", "enable_code_execution"]
9395

9496
def _param(self, param: str, override_params: ModelOverrides) -> Any:
9597
# If the param is in the override_params, use that value
@@ -101,9 +103,15 @@ def _param(self, param: str, override_params: ModelOverrides) -> Any:
101103

102104
def complete_chat(
103105
self, messages, override_params: ModelOverrides = {}, stream: bool = True
104-
) -> Iterator[str]:
106+
) -> Iterator[Completion]:
105107
model = self._param("model", override_params)
106108
completion_provider = get_completion_provider(model)
109+
110+
enable_code_execution = (
111+
bool(self._param("enable_code_execution", override_params))
112+
and os.environ.get("GPTCLI_ALLOW_CODE_EXECUTION") == "1"
113+
)
114+
107115
return completion_provider.complete(
108116
messages,
109117
{
@@ -112,6 +120,7 @@ def complete_chat(
112120
"top_p": float(self._param("top_p", override_params)),
113121
},
114122
stream,
123+
enable_code_execution,
115124
)
116125

117126

gptcli/cli.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import base64
12
import logging
23
import re
34
import json
5+
from imgcat import imgcat
46
from prompt_toolkit import PromptSession
57
from prompt_toolkit.history import FileHistory
68
from openai import OpenAIError, InvalidRequestError
@@ -76,27 +78,20 @@ def _format_function_call(self, function_call: FunctionCall) -> str:
7678
def print(self, message_delta: Message):
7779
self.current_message = merge_dicts(self.current_message, message_delta)
7880

79-
style = "green" if self.current_message.get("role") == "assistant" else "yellow"
80-
8181
if self.markdown:
8282
assert self.live
83-
text = ""
84-
if self.current_message.get("role") == "function":
85-
result = self.current_message.get("content") or ""
86-
text = f"```\n# Function result:\n{result}\n```"
87-
else:
88-
text = self.current_message.get("content", "")
83+
text = self.current_message.get("content", "")
8984

9085
function_call = self.current_message.get("function_call")
9186
if function_call:
9287
text += self._format_function_call(function_call)
9388

94-
content = Markdown(text, style=style)
89+
content = Markdown(text, style="green")
9590
self.live.update(content)
9691
self.live.refresh()
9792
else:
9893
self.console.print(
99-
Text(message_delta.get("content", ""), style=style), end=""
94+
Text(message_delta.get("content", ""), style="green"), end=""
10095
)
10196

10297
def __exit__(self, *args):
@@ -119,6 +114,22 @@ def __enter__(self):
119114
def on_message_delta(self, message_delta: Message):
120115
self.printer.print(message_delta)
121116

117+
def on_function_result(self, result: dict):
118+
self.console.print(Text("Function result:", style="yellow"))
119+
if "image/png" in result:
120+
image_base64 = result["image/png"]
121+
image_bytes = base64.b64decode(image_base64)
122+
imgcat(image_bytes)
123+
if "text/plain" in result:
124+
text = result["text/plain"]
125+
if self.markdown:
126+
content = Markdown(
127+
f"```\n{text}\n```",
128+
)
129+
else:
130+
content = Text(text, style="yellow")
131+
self.console.print(content)
132+
122133
def __exit__(self, *args):
123134
self.printer.__exit__(*args)
124135

gptcli/completion.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class FunctionCall(TypedDict, total=False):
1010

1111
class Message(TypedDict, total=False):
1212
role: Required[str]
13-
content: str
13+
content: Optional[str]
1414
name: str
1515
function_call: FunctionCall
1616

@@ -32,6 +32,7 @@ class ModelOverrides(TypedDict, total=False):
3232
model: str
3333
temperature: float
3434
top_p: float
35+
enable_code_execution: bool
3536

3637

3738
class CompletionDelta(TypedDict):
@@ -47,6 +48,10 @@ class Completion(TypedDict):
4748
class CompletionProvider:
4849
@abstractmethod
4950
def complete(
50-
self, messages: List[Message], args: dict, stream: bool = False
51+
self,
52+
messages: List[Message],
53+
args: dict,
54+
stream: bool = False,
55+
enable_code_execution: bool = False,
5156
) -> Iterator[Completion]:
5257
pass

gptcli/composite.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from gptcli.completion import FunctionCall, Message, ModelOverrides
1+
from gptcli.completion import Message, ModelOverrides
22
from gptcli.session import ChatListener, ResponseStreamer
33

44

5-
from typing import List
5+
from typing import List, Optional
66

77

88
class CompositeResponseStreamer(ResponseStreamer):
@@ -18,6 +18,10 @@ def on_message_delta(self, message_delta: Message):
1818
for streamer in self.streamers:
1919
streamer.on_message_delta(message_delta)
2020

21+
def on_function_result(self, result: dict):
22+
for streamer in self.streamers:
23+
streamer.on_function_result(result)
24+
2125
def __exit__(self, *args):
2226
for streamer in self.streamers:
2327
streamer.__exit__(*args)
@@ -57,3 +61,9 @@ def on_chat_response(
5761
):
5862
for listener in self.listeners:
5963
listener.on_chat_response(messages, response, overrides)
64+
65+
def on_function_call(self, function_name: str, **kwargs) -> Optional[str]:
66+
for listener in self.listeners:
67+
result = listener.on_function_call(function_name, **kwargs)
68+
if result is not None:
69+
return result

gptcli/openai.py

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,56 @@
55

66
from gptcli.completion import Completion, CompletionProvider, Message
77

8+
FUNCTIONS_SCHEMA = [
9+
{
10+
"name": "python_eval",
11+
"description": "Evaluate an arbitrary Python snippet",
12+
"parameters": {
13+
"type": "object",
14+
"properties": {
15+
"source": {
16+
"type": "string",
17+
"description": "The Python code to evaluate",
18+
},
19+
},
20+
"required": ["source"],
21+
},
22+
},
23+
{
24+
"name": "pip_install",
25+
"description": "Install a Python package. The kernel will be restarted automatically after the package is installed.",
26+
"parameters": {
27+
"type": "object",
28+
"properties": {
29+
"package": {
30+
"type": "string",
31+
"description": "The package to install",
32+
},
33+
},
34+
"required": ["package"],
35+
},
36+
},
37+
]
38+
839

940
class OpenAICompletionProvider(CompletionProvider):
1041
def complete(
11-
self, messages: List[Message], args: dict, stream: bool = False
42+
self,
43+
messages: List[Message],
44+
args: dict,
45+
stream: bool = False,
46+
enable_code_execution: bool = False,
1247
) -> Iterator[Completion]:
1348
kwargs = {}
1449
if "temperature" in args:
1550
kwargs["temperature"] = args["temperature"]
1651
if "top_p" in args:
1752
kwargs["top_p"] = args["top_p"]
1853

19-
functions = [
20-
{
21-
"name": "python_eval",
22-
"description": "Evaluate an arbitrary Python snippet",
23-
"parameters": {
24-
"type": "object",
25-
"properties": {
26-
"source": {
27-
"type": "string",
28-
"description": "The Python code to evaluate",
29-
},
30-
},
31-
"required": ["source"],
32-
},
33-
},
34-
]
54+
if enable_code_execution:
55+
functions = FUNCTIONS_SCHEMA
56+
else:
57+
functions = []
3558

3659
response_iter = cast(
3760
Any,

0 commit comments

Comments
 (0)