Skip to content

Commit 6f38c6d

Browse files
Document --jsonrpc
* add shared /thread volume
1 parent d096771 commit 6f38c6d

File tree

9 files changed

+265
-13
lines changed

9 files changed

+265
-13
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,25 @@ Prompts are fetched from a GitHub repository. The mandatory parts of the ref ar
9393
but optional `path` and `ref` can be added to pull prompts from branches, and to specify a subdirectory
9494
where the prompt files are located in the repo.
9595

96+
## Output json-rpc notifications
97+
98+
Add the flag `--jsonrpc` to the list of arguments to switch the stdout stream to be a series of `jsonrpc` notifications.
99+
This is useful if you are running the tool and streaming responses on to a canvas.
100+
101+
Try running the with the `--jsonrpc` to see a full example but the stdout stream will look something like this.
102+
103+
```
104+
{"jsonrpc":"2.0","method":"message","params":{"content":" consistently"}}Content-Length: 65
105+
106+
{"jsonrpc":"2.0","method":"message","params":{"content":" high"}}Content-Length: 61
107+
108+
{"jsonrpc":"2.0","method":"message","params":{"content":"."}}Content-Length: 52
109+
110+
{"jsonrpc":"2.0","method":"functions","params":null}Content-Length: 57
111+
112+
{"jsonrpc":"2.0","method":"functions-done","params":null}Content-Length: 1703
113+
```
114+
96115
### Prompt file layout
97116

98117
Each prompt directory should contain a README.md describing the prompts and their purpose. Each prompt file

functions/hub/curl/Dockerfile

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
# syntax = docker/dockerfile:1.4
3+
FROM nixos/nix:2.21.1@sha256:3f6c77ee4d2c82e472e64e6cd7087241dc391421a0b42c22e6849c586d5398d9 AS builder
4+
5+
WORKDIR /tmp/build
6+
RUN mkdir /tmp/nix-store-closure
7+
8+
# ignore SC2046 because the output of nix-store -qR will never have spaces - this is safe here
9+
# hadolint ignore=SC2046
10+
RUN --mount=type=cache,target=/nix,from=nixos/nix:2.21.1,source=/nix \
11+
--mount=type=cache,target=/root/.cache \
12+
--mount=type=bind,target=/tmp/build \
13+
<<EOF
14+
nix \
15+
--extra-experimental-features "nix-command flakes" \
16+
--option filter-syscalls false \
17+
--extra-trusted-substituters "https://cache.iog.io" \
18+
--extra-trusted-public-keys "hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ=" \
19+
--show-trace \
20+
--log-format raw \
21+
build . --out-link /tmp/output/result
22+
cp -R $(nix-store -qR /tmp/output/result) /tmp/nix-store-closure
23+
EOF
24+
25+
FROM scratch
26+
27+
WORKDIR /app
28+
29+
COPY --from=builder /tmp/nix-store-closure /nix/store
30+
COPY --from=builder /tmp/output/ /app/
31+
32+
ENTRYPOINT ["/app/result/bin/entrypoint"]
33+
CMD ["--help"]

functions/hub/curl/flake.lock

Lines changed: 61 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

functions/hub/curl/flake.nix

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
description = "fasttext";
3+
4+
inputs = {
5+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
6+
flake-utils.url = "github:numtide/flake-utils";
7+
};
8+
9+
outputs = { self, nixpkgs, flake-utils, ...}@inputs:
10+
11+
flake-utils.lib.eachDefaultSystem
12+
(system:
13+
let
14+
pkgs = import nixpkgs {
15+
inherit system;
16+
};
17+
18+
in rec
19+
{
20+
packages = rec {
21+
22+
# this derivation just contains the init.clj script
23+
scripts = pkgs.stdenv.mkDerivation {
24+
name = "scripts";
25+
src = ./.;
26+
installPhase = ''
27+
cp init.clj $out
28+
'';
29+
};
30+
31+
# the script must have gh in the PATH
32+
default = pkgs.writeShellScriptBin "entrypoint" ''
33+
export PATH=${pkgs.lib.makeBinPath [pkgs.curl]}
34+
export SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
35+
${pkgs.babashka}/bin/bb ${scripts} "$@"
36+
'';
37+
};
38+
});
39+
}

functions/hub/curl/init.clj

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
(ns init
2+
(:require
3+
[babashka.process]
4+
[cheshire.core]))
5+
6+
(try
7+
(let [[json-string & args] *command-line-args*
8+
{llm-args :args} (cheshire.core/parse-string json-string true)]
9+
(println
10+
(-> (apply babashka.process/process
11+
{:out :string}
12+
"curl" args)
13+
(deref)
14+
(babashka.process/check)
15+
:out)))
16+
(catch Throwable t
17+
(binding [*out* *err*]
18+
(println (str "Error: " (.getMessage t)))
19+
(System/exit 1))))

functions/hub/curl/runbook.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Background
2+
3+
The `curl` function has one parameter.
4+
5+
* `args`: the args to send to the image
6+
7+
## Usage
8+
9+
This function should be given a rw bind mount for the root of a project.
10+
11+
```sh
12+
docker run --rm \
13+
--mount type=bind,source=$PWD,target=/project \
14+
--entrypoint /app/result/bin/entrypoint \
15+
--workdir /project \
16+
vonwig/curl:latest '{}'
17+
```
18+
19+
## Build
20+
21+
```sh
22+
docker build -t vonwig/fasttext:latest .
23+
```
24+
25+
```sh
26+
# docker:command=build
27+
28+
docker buildx build \
29+
--builder hydrobuild \
30+
--platform linux/amd64,linux/arm64 \
31+
--tag vonwig/fasttext:latest \
32+
--file Dockerfile \
33+
--push .
34+
docker pull vonwig/fasttext:latest
35+
```

runbook.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,23 @@ docker run --rm \
4242
bb -m clean-local-images
4343
```
4444

45+
## https://github.com/docker/labs-eslint-violations.git
46+
47+
```sh
48+
docker run --rm \
49+
-it \
50+
-v /var/run/docker.sock:/var/run/docker.sock \
51+
--mount type=bind,source=$PWD,target=/app/local \
52+
--workdir /app \
53+
--mount type=volume,source=docker-prompts,target=/prompts \
54+
--mount type=bind,source=$HOME/.openai-api-key,target=/root/.openai-api-key \
55+
vonwig/prompts:local \
56+
run \
57+
/Users/slim/repo/labs-eslint-violations \
58+
jimclark106 \
59+
"$(uname -o)" \
60+
local/prompts/eslint \
61+
--pat "$(cat ~/.secrets/dockerhub-pat-ai-tools-for-devs.txt)" \
62+
--jsonrpc
63+
64+
```

src/docker.clj

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
;; entrypoint is an array of strings
6868
;; env is a map
6969
;; Env is an array of name=value strings
70-
(defn create-container [{:keys [image entrypoint command host-dir env]}]
70+
(defn create-container [{:keys [image entrypoint command host-dir env thread-volume]}]
7171
(let [payload (json/generate-string
7272
(merge
7373
{:Image image
@@ -76,8 +76,10 @@
7676
(map (fn [[k v]] (format "%s=%s" (name k) v)))
7777
(into []))})
7878
(when host-dir {:HostConfig
79-
{:Binds [(format "%s:/project:rw" host-dir)
80-
"docker-lsp:/docker"]}
79+
{:Binds
80+
(concat [(format "%s:/project:rw" host-dir)
81+
"docker-lsp:/docker-lsp"]
82+
(when thread-volume (format "%s:/thread" thread-volume)))}
8183
:WorkingDir "/project"})
8284
(when entrypoint {:Entrypoint entrypoint})
8385
(when command {:Cmd command})))]
@@ -89,6 +91,18 @@
8991
:headers {"Content-Type" "application/json"
9092
"Content-Length" (count payload)}})))
9193

94+
(defn create-volume [{:keys [thread-volume-name]}]
95+
(curl/post "http://localhost/volumes/create"
96+
{:raw-args ["--unix-socket" "/var/run/docker.sock"]
97+
:throw false
98+
:body (json/generate-string {:Name thread-volume-name})
99+
:headers {"Content-Type" "application/json"}}))
100+
101+
(defn remove-volume [{:keys [thread-volume-name]}]
102+
(curl/delete (format "http://localhost/volumes/%s" thread-volume-name)
103+
{:raw-args ["--unix-socket" "/var/run/docker.sock"]
104+
:throw false}))
105+
92106
(defn inspect-container [{:keys [Id]}]
93107
(curl/get
94108
(format "http://localhost/containers/%s/json" Id)
@@ -133,6 +147,8 @@
133147
(throw (ex-info (format "%s -- (%d != %s)" s (:status response) code) response)))))
134148

135149
(def create (comp ->json (status? 201 "create-container") create-container))
150+
(def thread-volume (comp (status? 201 "create-volume") create-volume))
151+
(def delete-thread-volume (comp (status? 204 "remove-volume") remove-volume))
136152
(def inspect (comp ->json (status? 200 "inspect container") inspect-container))
137153
(def start (comp (status? 204 "start-container") start-container))
138154
(def wait (comp (status? 200 "wait-container") wait-container))
@@ -157,19 +173,20 @@
157173
(spec/def ::container-definition (spec/keys :opt-un [::host-dir ::entrypoint ::command ::user ::pat]
158174
:req-un [::image]))
159175

160-
;; TODO verify that m is a container-definition
161176
(defn run-function [m]
162177
(when (and (:user m) (and (not (:offline m)) (or (:pat m) (creds/credential-helper->jwt))))
163178
(pull (assoc m :creds {:username (:user m)
164179
:password (or (:pat m) (creds/credential-helper->jwt))
165180
:serveraddress "https://index.docker.io/v1/"})))
181+
(when (:thread-volume m) (thread-volume m))
166182
(let [x (create m)]
167183
(start x)
168184
(wait x)
169185
;; body is raw PTY output
170186
(let [s (:body (attach x))
171187
info (inspect x)]
172188
(delete x)
189+
(when (and (:thread-volume m) (not (true? (:save-thread-volume m)))) (delete-thread-volume m))
173190
{:pty-output s
174191
:exit-code (-> info :State :ExitCode)
175192
:info info})))

src/prompts.clj

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@
159159
function-name - the name of the function that the LLM has selected
160160
json-arg-string - the JSON arg string that the LLM has generated
161161
resolve fail - callbacks"
162-
[{:keys [functions user pat] :as opts} function-name json-arg-string {:keys [resolve fail]}]
162+
[{:keys [functions user pat thread-volume save-thread-volume] :as opts} function-name json-arg-string {:keys [resolve fail]}]
163163
(if-let [definition (->
164164
(->> (filter #(= function-name (-> % :function :name)) functions)
165165
first)
@@ -175,7 +175,9 @@
175175
(if json-arg-string [json-arg-string] ["{}"])
176176
(when-let [c (-> definition :container :command)] c))}
177177
(when user {:user user})
178-
(when pat {:pat pat}))
178+
(when pat {:pat pat})
179+
(when thread-volume {:thread-volume thread-volume})
180+
(when (true? save-thread-volume) {:save-thread-volume true}))
179181
{:keys [pty-output exit-code]} (docker/run-function function-call)]
180182
(if (= 0 exit-code)
181183
(resolve pty-output)
@@ -206,7 +208,7 @@
206208
args for extracting functions, host-dir, user, platform
207209
returns channel that will contain the final set of messages and a finish-reason"
208210
[prompts & args]
209-
(let [[host-dir user platform prompts-dir & {:keys [url pat]}] args
211+
(let [[host-dir user platform prompts-dir & {:keys [url pat save-thread-volume thread-id]}] args
210212
prompt-dir (get-dir prompts-dir)
211213
m (collect-metadata prompt-dir)
212214
functions (collect-functions prompt-dir)
@@ -217,7 +219,11 @@
217219
:host-dir host-dir
218220
:user user
219221
:platform platform}
220-
(when pat {:pat pat}))))]
222+
(when pat {:pat pat})
223+
(when save-thread-volume {:save-thread-volume true})
224+
(if thread-id
225+
{:thread-volume thread-id}
226+
{:thread-volume (str (random-uuid))}))))]
221227
(try
222228
(openai/openai
223229
(merge
@@ -298,21 +304,23 @@
298304
[nil "--host-dir DIR" "Project directory"]
299305
[nil "--prompts DIR_OR_GITHUB_REF" "prompts"]
300306
[nil "--offline" "do not try to pull new images"]
301-
[nil "--pretty-print-prompts" "pretty print prompts"]])
307+
[nil "--pretty-print-prompts" "pretty print prompts"]
308+
[nil "--save-thread-volume" "save the thread volume for debugging"]
309+
[nil "--thread-id THREAD_ID" "use this thread-id for the next conversation"]])
302310

303311
(defn- add-arg [options args k]
304312
(if-let [v (k options)] (concat args [k v]) args))
305313

306-
(def output-handler (fn [x] (println (json/generate-string x))))
314+
(def output-handler (fn [x] (jsonrpc/notify {:message {:content (json/generate-string x)}})))
307315
(defn output-prompts [coll]
308-
(println "## Prompts:\n")
316+
(jsonrpc/notify {:message {:content "## Prompts:\n"}})
309317
(->> coll
310318
(mapcat (fn [{:keys [role content]}]
311319
[(format "## %s\n" role)
312320
content]))
313321
(interpose "\n")
314322
(apply str)
315-
(println)))
323+
((fn [s] (jsonrpc/notify {:message {:content s}})))))
316324

317325
(defn -main [& args]
318326
(try
@@ -328,7 +336,8 @@
328336
(apply -run-command (concat
329337
arguments
330338
(reduce (partial add-arg options) [] [:url :pat :host-dir :prompts])
331-
(when (:offline options) [:offline true])))))
339+
(when (:offline options) [:offline true])
340+
(when (:save-thread-volume options) [:save-thread-volume true])))))
332341
(catch Throwable t
333342
(warn "Error: {{ exception }}" {:exception t})
334343
(System/exit 1))))

0 commit comments

Comments
 (0)