Skip to content

Commit a5cc926

Browse files
Inject use messages into ongoing conversations
1 parent abc2b6e commit a5cc926

File tree

9 files changed

+120
-54
lines changed

9 files changed

+120
-54
lines changed

Dockerfile

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,30 +16,24 @@ RUN --mount=type=cache,target=/nix,from=nixos/nix:2.21.1,source=/nix \
1616
--extra-trusted-substituters "https://cache.iog.io" \
1717
--extra-trusted-public-keys "hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ=" \
1818
--show-trace \
19-
--log-format raw \
19+
--log-format bar-with-logs \
2020
build . --out-link /tmp/output/result
2121
cp -R $(nix-store -qR /tmp/output/result) /tmp/nix-store-closure
2222
EOF
2323

2424
FROM scratch
2525

26+
# my convention is that images have only two top-level folders
27+
# /nix/store has all of the software
28+
# /app/result has symbolic links for any entrypoints needed
2629
WORKDIR /app
27-
2830
COPY --from=builder /tmp/nix-store-closure /nix/store
2931
COPY --from=builder /tmp/output/ /app/
3032

31-
COPY ./extractors/registry.edn ./extractors/registry.edn
32-
COPY ./functions/registry.edn ./functions/registry.edn
33-
COPY prompts/docker docker
34-
COPY prompts/lazy_docker lazy_docker
35-
36-
# curl needs the /tmp directory to already exist
33+
# programs like curl needs the /tmp directory to already exist
34+
# what is the right way to manage things like this?
3735
COPY <<EOF /tmp/.blank
3836
empty
3937
EOF
4038

41-
COPY <<EOF /root/.blank
42-
empty
43-
EOF
44-
4539
ENTRYPOINT ["/app/result/bin/entrypoint"]

deps.edn

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{:paths ["src" "dev"]
2-
:deps {markdown-clj/markdown-clj {:mvn/version "1.12.1"}
2+
:deps {org.clojure/clojure {:mvn/version "1.11.4"}
3+
markdown-clj/markdown-clj {:mvn/version "1.12.1"}
34
pogonos/pogonos {:mvn/version "0.2.1"}
45
dev.weavejester/medley {:mvn/version "1.8.0"}
56
io.replikativ/hasch {:mvn/version "0.3.94"}

flake.nix

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,29 +36,17 @@
3636
in
3737
{
3838
packages = rec {
39+
40+
# build uber jar
3941
clj = pkgs.clj-nix.mkCljBin {
4042
name = "agent-graph";
4143
projectSrc = ./.;
4244
main-ns = "docker.main";
4345
buildCommand = "clj -T:build uber";
4446
jdkRunner = pkgs.jdk17_headless;
4547
};
46-
deps-cache = pkgs.clj-nix.mk-deps-cache {
47-
lockfile = ./deps-lock.json;
48-
};
49-
graal = pkgs.clj-nix.mkGraalBin {
50-
# lazy lookup of a derivation that will exist
51-
cljDrv = self.packages."${system}".clj;
52-
graalvmXmx = "-J-Xmx8g";
53-
graalvm = pkgs.graalvm-ce;
54-
extraNativeImageBuildArgs = [
55-
"--native-image-info"
56-
"--initialize-at-build-time"
57-
"--enable-http"
58-
"--enable-https"
59-
];
60-
};
6148

49+
# create a minimal java runtime for this uberjar
6250
custom-jdk = pkgs.clj-nix.customJdk {
6351
cljDrv = clj;
6452
jdkBase = pkgs.jdk17_headless;
@@ -67,15 +55,30 @@
6755
extraJdkModules = ["java.security.jgss" "java.security.sasl" "jdk.crypto.ec"];
6856
};
6957

58+
# create some resources that will need to be copied into the final image
59+
registries = pkgs.stdenv.mkDerivation {
60+
name = "registries";
61+
src = ./.;
62+
installPhase = ''
63+
mkdir -p $out/extractors
64+
mkdir -p $out/functions
65+
cp ./extractors/registry.edn $out/extractors
66+
cp ./functions/registry.edn $out/functions
67+
'';
68+
};
69+
70+
# our application makes calls to the curl binary
71+
# therefore, wrap the custom-jdk in a script with curl in the PATH
7072
entrypoint = pkgs.writeShellScriptBin "entrypoint" ''
7173
export PATH=${pkgs.lib.makeBinPath [pkgs.curl]}
7274
export SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
7375
${custom-jdk}/bin/agent-graph "$@"
7476
'';
7577

78+
# the final entrypoint
7679
default = pkgs.buildEnv {
7780
name = "agent-graph-env";
78-
paths = [ entrypoint ];
81+
paths = [ entrypoint registries ];
7982
};
8083
};
8184

graphs/prompts/journals/2024_09_03.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
-v /run/host-services/backend.sock:/host-services/docker-desktop-backend.sock \
99
-e "DOCKER_DESKTOP_SOCKET_PATH=/host-services/docker-desktop-backend.sock" \
1010
--mount type=volume,source=docker-prompts,target=/prompts \
11+
-e "OPENAI_API_KEY_LOCATION=/root" \
1112
--mount type=bind,source=$HOME/.openai-api-key,target=/root/.openai-api-key \
1213
vonwig/prompts:latest \
1314
run \
@@ -29,6 +30,7 @@
2930
-v /run/host-services/backend.sock:/host-services/docker-desktop-backend.sock \
3031
-e "DOCKER_DESKTOP_SOCKET_PATH=/host-services/docker-desktop-backend.sock" \
3132
--mount type=volume,source=docker-prompts,target=/prompts \
33+
-e "OPENAI_API_KEY_LOCATION=/root" \
3234
--mount type=bind,source=$HOME/.openai-api-key,target=/root/.openai-api-key \
3335
--mount type=bind,source=$PROMPTS_DIR,target=/app/workdir \
3436
--workdir /app/workdir \

src/graph.clj

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
; be merged into the conversation state
5353
; =====================================================
5454

55-
(defn start
55+
(defn start
5656
"create starting messages, metadata, and functions to bootstrap the thread"
5757
[{:keys [prompts] :as opts} _]
5858
(let [c (async/promise-chan)]
@@ -70,20 +70,20 @@
7070
(async/put! c {:messages [] :done "error"})))
7171
c))
7272

73-
(defn end
73+
(defn end
7474
"merge the :done signal"
7575
[state]
7676
(let [c (async/promise-chan)]
7777
;; this is a normal ending and we try to add a :done key to the state for this
7878
(async/put! c (assoc state :done (:finish-reason state)))
7979
c))
8080

81-
(defn completion
81+
(defn completion
8282
"get the next llm completion"
8383
[state]
8484
(run-llm (:messages state) (:metadata state) (:functions state) (:opts state)))
8585

86-
(defn tool
86+
(defn tool
8787
"make docker container tool calls"
8888
[state]
8989
(let [calls (-> (:messages state) last :tool_calls)]
@@ -102,7 +102,7 @@
102102

103103
; tool_calls are maps with an id and a function with arguments an name
104104
; look up the full tool definition using the name
105-
(defn sub-graph
105+
(defn sub-graph
106106
"answer a tool call by processing a sub-graph"
107107
[state]
108108
(async/go
@@ -126,7 +126,7 @@
126126
; edge functions takes state and returns next node
127127
; =====================================================
128128

129-
(defn tool-or-end
129+
(defn tool-or-end
130130
"after a completion, check whether you need to make a tool call"
131131
[state]
132132
(let [finish-reason (-> state :finish-reason)]
@@ -148,7 +148,7 @@
148148
(defn add-conditional-edges [graph s1 f & [m]]
149149
(assoc-in graph [:edges s1] ((or m identity) f)))
150150

151-
(defn state-reducer
151+
(defn state-reducer
152152
"reduce the state with the change from running a node"
153153
[state change]
154154
(-> state
@@ -157,20 +157,21 @@
157157

158158
(defn stream
159159
"start streaming a conversation"
160-
[graph]
161-
(async/go-loop
162-
[state {}
163-
node "start"]
164-
(jsonrpc/notify :message {:debug (format "\n-> entering %s\n\n" node)})
165-
;; TODO handling bad graphs with missing nodes
166-
(let [enter-node (get-in graph [:nodes node])
167-
new-state (state-reducer state (async/<! (enter-node state)))]
168-
(if (= "end" node)
169-
new-state
170-
;; TODO check for :done keys and possibly bail
171-
;; transition to the next state
172-
;; TODO handling missing edges
173-
(recur new-state ((get-in graph [:edges node]) new-state))))))
160+
([graph] (stream graph {}))
161+
([graph m]
162+
(async/go-loop
163+
[state m
164+
node "start"]
165+
(jsonrpc/notify :message {:debug (format "\n-> entering %s\n\n" node)})
166+
;; TODO handling bad graphs with missing nodes
167+
(let [enter-node (get-in graph [:nodes node])
168+
new-state (state-reducer state (async/<! (enter-node state)))]
169+
(if (= "end" node)
170+
new-state
171+
;; TODO check for :done keys and possibly bail
172+
;; transition to the next state
173+
;; TODO handling missing edges
174+
(recur new-state ((get-in graph [:edges node]) new-state)))))))
174175

175176
; ============================================================
176177
; this is the graph we tend to use in our experiments thus far

src/jsonrpc.clj

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
:else (recur (parse-header line headers))))))
9191
messages))
9292

93-
(defn ^:private write-message [^OutputStream output msg]
93+
(defn write-message [^OutputStream output msg]
9494
(let [content (json/generate-string msg)
9595
content-bytes (.getBytes content "utf-8")]
9696
(locking write-lock
@@ -106,6 +106,12 @@
106106
:method method
107107
:params params})
108108

109+
(defn request [method params get-id]
110+
{:jsonrpc "2.0"
111+
:method method
112+
:id (get-id)
113+
:params params})
114+
109115
;; message({:debug ""}) - debug messages are often serialized edn but still meant to be streamed
110116
;; message({:content ""}) - meant to be streamed
111117
;; prompts({:messages [{:role "", :content ""}]})

src/prompts.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@
9393
[])))
9494

9595
(def hub-images
96-
#{"curl" "qrencode" "toilet" "figlet" "gh" "typos" "fzf" "jq" "fmpeg" "pylint" "imagemagick"})
96+
#{"curl" "qrencode" "toilet" "figlet" "gh" "typos" "fzf" "jq" "fmpeg" "pylint" "imagemagick" "graphviz"})
9797

9898
(defn collect-functions
9999
"get either :functions or :tools collection

src/registry.clj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
(set! *warn-on-reflection* true)
88

99
(defn- functions-dir []
10-
(dir/get-dir "./functions" "/app/functions"))
10+
(dir/get-dir "./functions" "/app/result/functions" "/app/functions"))
1111

1212
(defn- extractors-dir []
13-
(dir/get-dir "./extractors" "/app/extractors"))
13+
(dir/get-dir "./extractors" "/app/result/extractors" "/app/extractors"))
1414

1515
(defn- get-registry [f]
1616
(when-let [d (f)]

src/user_loop.clj

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
(ns user-loop
2+
(:require
3+
[clojure.core.async :as async]
4+
graph
5+
jsonrpc)
6+
(:import
7+
[java.io
8+
BufferedOutputStream
9+
OutputStream
10+
PipedInputStream
11+
PipedOutputStream]))
12+
13+
(declare graph)
14+
15+
(def do-stream (partial graph/stream graph))
16+
17+
(defn start-jsonrpc-loop [f in m]
18+
(let [c (jsonrpc/input-stream->input-chan in {})]
19+
(async/go-loop
20+
[state m]
21+
(let [message (async/<! c)
22+
s (-> message :params :content)]
23+
(println "message content: " s)
24+
(if (some (partial = s) ["exit" "quit" "q"])
25+
state
26+
(recur (async/<! (f state s))))))))
27+
28+
(def counter (atom 0))
29+
(defn get-id [] (swap! counter inc))
30+
31+
(def ^{:private true} start-test-loop
32+
(partial start-jsonrpc-loop (fn [state s]
33+
(async/go
34+
(update state :messages (fnil conj []) s)))))
35+
36+
(defn -create-pipe []
37+
;; Create a PipedInputStream and PipedOutputStream
38+
(let [piped-out (PipedOutputStream.)
39+
piped-in (PipedInputStream. piped-out)
40+
buffered-out (BufferedOutputStream. piped-out)]
41+
[[(fn [s] (jsonrpc/write-message buffered-out s))
42+
(fn [] (.close ^OutputStream buffered-out))]
43+
piped-in]))
44+
45+
(comment
46+
(let [[[w c] in] (-create-pipe)]
47+
(async/go (println "ending: " (async/<! (start-test-loop in {}))))
48+
(w (jsonrpc/request "prompt" {:content "hello"} get-id))
49+
(w (jsonrpc/request "prompt" {:content "hello1"} get-id))
50+
(w (jsonrpc/request "prompt" {:content "exit"} get-id))
51+
(c)))
52+
53+
(comment
54+
;; an input stream is something from which we can read bytes
55+
;; in a jvm, we can create Strings from bytes
56+
;; a byte is 8 bits in java and big-endian by default
57+
;; 8 bits can be stored using two hex digits (0-9, a-f) 00-ff (0-255) 1111 8+4+2+1=15
58+
)
59+

0 commit comments

Comments
 (0)