Skip to content

Commit 53e4e72

Browse files
Create simple conversation state manipulators
* need to create operators for composing different langraph nodes
1 parent 36b8bc4 commit 53e4e72

File tree

7 files changed

+143
-108
lines changed

7 files changed

+143
-108
lines changed

prompts/sql/query-check.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ tools:
1414
container:
1515
image: vonwig/sqlite:latest
1616
command:
17-
- "{{database}}"
17+
- "./Chinook.db"
1818
- "{{sql}}"
1919
tool_choice: required
2020
---

src/docker/main.clj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
jsonrpc
1515
[logging :refer [warn]]
1616
prompts
17+
state
1718
user-loop)
1819
(:gen-class))
1920

@@ -176,7 +177,7 @@
176177
(user-loop/start-jsonrpc-loop
177178
(user-loop/create-step
178179
(fn [state]
179-
(let [m (graph/construct-initial-state-from-prompts
180+
(let [m (state/construct-initial-state-from-prompts
180181
(assoc state :opts
181182
(-> (with-options opts (rest args))
182183
(assoc :thread-id thread-id))))]

src/graph.clj

Lines changed: 13 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
(:require
33
[babashka.fs :as fs]
44
[clojure.core.async :as async]
5-
[clojure.pprint :refer [pprint]]
65
[clojure.core.match :refer [match]]
76
git
87
jsonrpc
@@ -72,7 +71,6 @@
7271
[state]
7372
(run-llm (:messages state) (dissoc (:metadata state) :agent) (:functions state) (:opts state)))
7473

75-
;; TODO does the LangGraph Tool Node always search for the a tool_call
7674
(defn tool
7775
"execute the tool_calls from the last AI message in the conversation"
7876
[state]
@@ -97,63 +95,8 @@
9795
[_]
9896
(async/go {}))
9997

100-
(defn construct-initial-state-from-prompts [{{:keys [prompts] :as opts} :opts :as state}]
101-
(try
102-
(-> state
103-
(merge
104-
{:metadata (prompts/collect-metadata prompts)
105-
:functions (prompts/collect-functions prompts)
106-
:opts (merge opts {:level (or (:level opts) 0)})})
107-
(update
108-
:messages
109-
(fnil concat [])
110-
(when (not (seq (:messages state)))
111-
(let [new-prompts (prompts/get-prompts opts)]
112-
(jsonrpc/notify :prompts {:messages new-prompts})
113-
new-prompts))))
114-
(catch Throwable ex
115-
(jsonrpc/notify :error {:content
116-
(format "failure for prompt configuration:\n %s" (with-out-str (pprint (dissoc opts :pat :jwt))))
117-
:exception (str ex)}))))
118-
119-
; tool_calls are maps with an id and a function with arguments an name
120-
; look up the full tool definition using the name
121-
122-
(defn add-prompt-ref [state]
123-
(let [definition (state/get-function-definition state)
124-
arg-context (let [raw-args (-> state :messages last :tool_calls first :function :arguments)]
125-
(tools/arg-context raw-args))]
126-
(-> state
127-
(dissoc :messages)
128-
(update-in [:opts :level] (fnil inc 0))
129-
(update-in [:opts :prompts] (constantly (git/prompt-file (-> definition :function :ref))))
130-
(update-in [:opts :parameters] (constantly arg-context)))))
131-
132-
(comment
133-
;; TODO move this into the thingy
134-
(add-prompt-ref {:messages [{:tool_calls [{:function {:name "sql_db_list_tables"
135-
:arguments "{\"arg\": 1}"}}]}]
136-
:functions [{:function {:name "sql_db_list_tables"
137-
:description "List all tables in the database"
138-
:parameters {:type "object"
139-
:properties
140-
{:database {:type "string" :description "the database to query"}}}
141-
:ref "github:docker/labs-ai-tools-for-devs?path=prompts/curl/README.md"}}]}))
142-
14398
(declare stream chat-with-tools)
14499

145-
(defn add-last-message-as-tool-call
146-
[state sub-graph-state]
147-
{:messages [(-> sub-graph-state
148-
:messages
149-
last
150-
(state/add-tool-call-id (-> state :messages last :tool_calls first :id)))]})
151-
152-
(defn append-new-messages
153-
[state sub-graph-state]
154-
{:messages (->> (:messages sub-graph-state)
155-
(filter (complement (fn [m] (some #(= m %) (:messages state))))))})
156-
157100
(defn sub-graph-node
158101
"create a sub-graph node that initializes a conversation from the current one,
159102
creates a new agent graph from the current state and returns the messages to be added
@@ -165,8 +108,10 @@
165108
(async/<!
166109
(stream
167110
((or construct-graph chat-with-tools) state)
168-
((or init-state (comp construct-initial-state-from-prompts add-prompt-ref)) state)))]
169-
((or next-state add-last-message-as-tool-call) state sub-graph-state)))))
111+
(->
112+
((or init-state (comp state/construct-initial-state-from-prompts state/add-prompt-ref)) state)
113+
(update-in [:opts :level] (fnil inc 0)))))]
114+
((or next-state state/add-last-message-as-tool-call) state sub-graph-state)))))
170115

171116
; =====================================================
172117
; edge functions takes state and returns next node
@@ -280,7 +225,7 @@
280225
["tools-query"]]
281226
[["end" end]]])
282227

283-
(defn one-tool-call [_]
228+
(defn generate-one-tool-call [_]
284229
(-> {}
285230
(add-node "start" start)
286231
(add-node "completion" completion)
@@ -302,22 +247,12 @@
302247
["completion"]]
303248
[["end" end]]])
304249

305-
(comment
306-
(alter-var-root #'jsonrpc/notify (fn [_] (partial jsonrpc/-println {:debug true})))
307-
(let [x {:prompts (fs/file "/Users/slim/docker/labs-ai-tools-for-devs/prompts/curl/README.md")
308-
:platform "darwin"
309-
:user "jimclark106"
310-
:thread-id "thread"
311-
:host-dir "/Users/slim"
312-
:stream true}]
313-
(state/summarize (async/<!! (stream (chat-with-tools x) x)))))
314-
315-
(comment
316-
(def x {:stream true,
317-
:host-dir "/Users/slim/docker/labs-make-runbook",
318-
:prompts "/Users/slim/docker/labs-ai-tools-for-devs/prompts/hub/default.md"
319-
:platform "darwin", :user "jimclark106",
320-
:thread-id "3e61ffe7-840e-4177-b84a-f6f7db58b24d"})
321-
(state/summarize
322-
(async/<!! (stream (chat-with-tools x) x))))
250+
; requires finish-reason to be set to tool_calls
251+
(def start-with-tool
252+
[[["start" start]
253+
[:edge tool-or-end]]
254+
[["sub-graph" (sub-graph-node {})]
255+
["end" end]]
256+
[["tool" (tool-node {})]
257+
["end"]]])
323258

src/graphs/sql.clj

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
[babashka.fs :as fs]
44
[clojure.core.async :as async]
55
[clojure.string :as string]
6-
[graph]))
6+
[graph]
7+
state))
78

89
(def first-tool-call
910
{:messages [{:role "assistant"
@@ -36,10 +37,6 @@
3637
{:image "vonwig/sqlite:latest"
3738
:command ["{{database}}" ".schema {{table}}"]}}}]})
3839

39-
(defn list-tables-inject-tool [_]
40-
(async/go
41-
first-tool-call))
42-
4340
(defn failed-tool-call-message [tool-call-name]
4441
;; this is awful - binds the should-continue edge to the format of this string
4542
(format
@@ -56,7 +53,7 @@
5653
(dissoc :functions)
5754
(update-in [:opts :level] (fnil inc 0))
5855
(update-in [:opts :prompts] (constantly (fs/file "prompts/sql/query-gen.md")))
59-
(graph/construct-initial-state-from-prompts)
56+
(state/construct-initial-state-from-prompts)
6057
(update-in [:messages] concat (:messages state)))
6158
{:keys [messages _finish-reason]} (async/<! (graph/run-llm
6259
(:messages x)
@@ -81,15 +78,22 @@
8178
(let [last-message (last messages)]
8279
(cond
8380
(contains? last-message :tool_calls) "end"
81+
;; prevent inifinite loops of errors
8482
(string/starts-with? (:content last-message) "Error:") "query-gen"
83+
;; how many times should we try to correct because correct-query will always end up back here
8584
:else "correct-query")))
8685

86+
(defn seed-list-tables-conversation [state]
87+
(-> state
88+
(assoc :finish-reason "tool_calls")
89+
(update-in [:functions] (constantly (:tools first-tool-call)))
90+
(update-in [:messages] concat (:messages first-tool-call))))
91+
8792
(defn seed-get-schema-conversation [state]
8893
; inherit full conversation
8994
; no prompts
9095
; add the schema tool
9196
(-> state
92-
(update-in [:opts :level] (fnil inc 0))
9397
(update-in [:functions] (fnil concat []) (:tools model-get-schema))))
9498

9599
(defn seed-correct-query-conversation
@@ -98,25 +102,29 @@
98102
; add the last message to the conversation
99103
(-> state
100104
(dissoc :messages)
101-
(update-in [:opts :level] (fnil inc 0))
102105
(update-in [:opts :prompts] (constantly (fs/file "prompts/sql/query-check.md")))
103-
(graph/construct-initial-state-from-prompts)
106+
(state/construct-initial-state-from-prompts)
104107
(update-in [:messages] concat [(last (:messages state))])))
105108

109+
;; query-gen has a prompt
110+
;; seed-correct-query-conversation has a prompt
111+
;; prompts/sql/query-gen.md has a hard-coded db file
106112
(defn graph [_]
107113
(graph/construct-graph
108-
[[["start" graph/start]
109-
["list-tables-inject-tool" list-tables-inject-tool]
110-
["list-tables-tool" (graph/tool-node {})]
111-
["model-get-schema" (graph/sub-graph-node
112-
{:init-state seed-get-schema-conversation
113-
:next-state graph/append-new-messages})]
114-
["query-gen" query-gen]
115-
[:edge should-continue]]
116-
[["correct-query" (graph/sub-graph-node
117-
{:init-state seed-correct-query-conversation
118-
:construct-graph graph/one-tool-call
119-
:next-state graph/append-new-messages})]
114+
[[["start" graph/start]
115+
["list-tables-tool" (graph/sub-graph-node
116+
{:init-state seed-list-tables-conversation
117+
:construct-graph (fn [_] (graph/construct-graph graph/start-with-tool))
118+
:next-state state/take-last-two-messages})]
119+
["model-get-schema" (graph/sub-graph-node
120+
{:init-state seed-get-schema-conversation
121+
:next-state state/append-new-messages})]
122+
["query-gen" query-gen]
123+
[:edge should-continue]]
124+
[["correct-query" (graph/sub-graph-node
125+
{:init-state seed-correct-query-conversation
126+
:construct-graph graph/generate-one-tool-call
127+
:next-state state/append-new-messages})]
120128
["query-gen"]]
121-
[["end" graph/end]]]))
129+
[["end" graph/end]]]))
122130

src/state.clj

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
(ns state)
1+
(ns state
2+
(:require
3+
git
4+
jsonrpc
5+
prompts
6+
tools
7+
[clojure.pprint :refer [pprint]]))
28

39
(set! *warn-on-reflection* true)
410

@@ -40,3 +46,51 @@
4046
(def prompt-tool? (comp prompt? get-function-definition))
4147

4248
(defn add-tool-call-id [m id] (assoc m :role "tool" :tool_call_id id))
49+
50+
(defn construct-initial-state-from-prompts [{{:keys [prompts] :as opts} :opts :as state}]
51+
(try
52+
(-> state
53+
(merge
54+
{:metadata (prompts/collect-metadata prompts)
55+
:functions (prompts/collect-functions prompts)})
56+
(update
57+
:messages
58+
(fnil concat [])
59+
(when (not (seq (:messages state)))
60+
(let [new-prompts (prompts/get-prompts opts)]
61+
(jsonrpc/notify :prompts {:messages new-prompts})
62+
new-prompts))))
63+
(catch Throwable ex
64+
(jsonrpc/notify :error {:content
65+
(format "failure for prompt configuration:\n %s" (with-out-str (pprint (dissoc opts :pat :jwt))))
66+
:exception (str ex)}))))
67+
68+
(defn add-prompt-ref
69+
[state]
70+
(let [definition (state/get-function-definition state)
71+
arg-context (let [raw-args (-> state :messages last :tool_calls first :function :arguments)]
72+
(tools/arg-context raw-args))]
73+
(-> state
74+
(dissoc :messages)
75+
(update-in [:opts :prompts] (constantly (git/prompt-file (-> definition :function :ref))))
76+
(update-in [:opts :parameters] (constantly arg-context)))))
77+
78+
79+
(defn add-last-message-as-tool-call
80+
[state sub-graph-state]
81+
{:messages [(-> sub-graph-state
82+
:messages
83+
last
84+
(state/add-tool-call-id (-> state :messages last :tool_calls first :id)))]})
85+
86+
(defn append-new-messages
87+
[state sub-graph-state]
88+
{:messages (->> (:messages sub-graph-state)
89+
(filter (complement (fn [m] (some #(= m %) (:messages state))))))})
90+
91+
(defn take-last-two-messages
92+
[_ sub-graph-state]
93+
{:messages (->> (:messages sub-graph-state)
94+
(take-last 2))})
95+
96+

src/tools.clj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232

3333
(defn arg-context [json-arg-string]
3434
(merge
35-
;; TODO raw is a bad name when merging
3635
{:raw (if json-arg-string
3736
json-arg-string
3837
"{}")}

test/graph_t.clj

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,46 @@
11
(ns graph_t
2-
(:require [clojure.test :as t]
3-
[graph]
4-
[graphs.sql]))
2+
(:require
3+
[babashka.fs :as fs]
4+
[clojure.core.async :as async]
5+
[clojure.test :as t]
6+
[graph]
7+
[graphs.sql]
8+
jsonrpc
9+
state))
510

611
(t/deftest
712
(graph/construct-graph graph/chat-with-tools-representation)
8-
(graph/construct-graph graphs.sql/graph-data))
13+
(graph/construct-graph graphs.sql/graph))
14+
15+
(comment
16+
;; requires network to test
17+
(state/add-prompt-ref
18+
{:messages [{:tool_calls [{:function {:name "sql_db_list_tables"
19+
:arguments "{\"arg\": 1}"}}]}]
20+
:functions [{:function {:name "sql_db_list_tables"
21+
:description "List all tables in the database"
22+
:parameters {:type "object"
23+
:properties
24+
{:database {:type "string" :description "the database to query"}}}
25+
:ref "github:docker/labs-ai-tools-for-devs?path=prompts/curl/README.md"}}]}))
26+
27+
(comment
28+
(alter-var-root #'jsonrpc/notify (fn [_] (partial jsonrpc/-println {:debug true})))
29+
(let [x {:prompts (fs/file "/Users/slim/docker/labs-ai-tools-for-devs/prompts/curl/README.md")
30+
:platform "darwin"
31+
:user "jimclark106"
32+
:thread-id "thread"
33+
:host-dir "/Users/slim"
34+
:stream true}]
35+
(state/summarize (async/<!! (graph/stream (graph/chat-with-tools x) x)))))
36+
37+
(comment
38+
(def x {:stream true,
39+
:host-dir "/Users/slim/docker/labs-make-runbook",
40+
:prompts "/Users/slim/docker/labs-ai-tools-for-devs/prompts/hub/default.md"
41+
:platform "darwin", :user "jimclark106",
42+
:thread-id "3e61ffe7-840e-4177-b84a-f6f7db58b24d"})
43+
(state/summarize
44+
(async/<!! (graph/stream (graph/chat-with-tools x) x))))
45+
46+

0 commit comments

Comments
 (0)