Skip to content

Commit 970db3a

Browse files
Update error handling
1 parent c3fa7c1 commit 970db3a

File tree

7 files changed

+116
-92
lines changed

7 files changed

+116
-92
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
.clj-kondo
44
.lsp
55
/prompts/stable-diffusion/*.png
6+
**/.DS_Store

graphs/prompts/pages/jsonrpc.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
probably represents something like a networking error or a configuration problem.
4242

4343
```json
44-
{"params": {"content": "error message"}}
44+
{"params": {"content": "error message", "exception": "..."}}
4545
```
4646
- ### Request Methods
4747
- #### prompt

prompts/.DS_Store

-6 KB
Binary file not shown.

prompts/curl/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ tools:
55

66
# prompt user
77

8-
Run the curl command to fetch gists for user slimslenderslacks from GitHub.
8+
Run the curl command, in silent mode, to fetch gists for user slimslenderslacks from GitHub.
9+

src/docker.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@
266266
(def extract-facts run-function)
267267

268268
(defn write-stdin [container-id content]
269-
(let [buf (ByteBuffer/allocate (* 4 (count content)))
269+
(let [buf (ByteBuffer/allocate (* 1024 20))
270270
address (UnixDomainSocketAddress/of "/var/run/docker.sock")
271271
client (SocketChannel/open address)]
272272

src/openai.clj

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@
2020
returns nil
2121
throws exception if response can't be initiated or if we get a non 200 status code"
2222
[request cb]
23-
(jsonrpc/notify :message {:content "\n## ROLE assistant\n"})
23+
(jsonrpc/notify :start {:level (or (:level request) 0) :role "assistant"})
2424
(let [b (merge
2525
{:model "gpt-4"}
26-
(dissoc request :url))
26+
(dissoc request :url :level))
2727
response
2828
(http/post
2929
(or (:url request) "https://api.openai.com/v1/chat/completions")
@@ -43,27 +43,31 @@
4343
(slurp (:body response))))
4444
(doseq [chunk (line-seq (io/reader (:body response)))]
4545
(cb chunk)))
46-
(throw (ex-info "Failed to call OpenAI API" {:body (if (string? (:body response))
47-
(:body response)
48-
(slurp (:body response)))})))))
46+
(let [s (if (string? (:body response))
47+
(:body response)
48+
(slurp (:body response)))]
49+
(jsonrpc/notify :message {:content s})
50+
(throw (ex-info "Failed to call OpenAI API" {:body s}))))))
4951

5052
(defn call-function
5153
" returns channel that will emit one message and then close"
52-
[function-handler function-name arguments tool-call-id]
54+
[level function-handler function-name arguments tool-call-id]
5355
(let [c (async/chan)]
5456
(try
5557
(function-handler
5658
function-name
5759
arguments
5860
{:resolve
5961
(fn [output]
60-
(jsonrpc/notify :message {:content (format "\n## ROLE tool (%s)\n%s\n" function-name output)})
62+
(jsonrpc/notify :start {:level level :role "tool" :content function-name})
63+
(jsonrpc/notify :message {:content (format "\n%s\n" output)})
6164
(async/go
6265
(async/>! c {:content output :role "tool" :tool_call_id tool-call-id})
6366
(async/close! c)))
6467
:fail
6568
(fn [output]
66-
(jsonrpc/notify :message {:content (format "\n## ROLE tool\n function call %s failed %s" function-name output)})
69+
(jsonrpc/notify :start {:level level :role "tool" :content function-name})
70+
(jsonrpc/notify :message {:content (format "function call failed %s" output)})
6771
(async/go
6872
(async/>! c {:content output :role "tool" :tool_call_id tool-call-id})
6973
(async/close! c)))})
@@ -76,10 +80,10 @@
7680

7781
(defn make-tool-calls
7882
" returns channel with all messages from completed executions of tools"
79-
[function-handler tool-calls]
83+
[level function-handler tool-calls]
8084
(->>
8185
(for [{{:keys [arguments name]} :function tool-call-id :id} tool-calls]
82-
(call-function function-handler name arguments tool-call-id))
86+
(call-function level function-handler name arguments tool-call-id))
8387
(async/merge)))
8488

8589
(defn function-merge [m {:keys [name arguments]}]
@@ -114,7 +118,7 @@
114118
"handle one response stream that we read from input channel c
115119
adds content or tool_calls while streaming and call any functions when done
116120
returns channel that will emit the an event with a ::response"
117-
[c]
121+
[level c]
118122
(let [response (atom {})]
119123
(async/go-loop
120124
[]
@@ -137,6 +141,7 @@
137141
(async/<!
138142
(->>
139143
(make-tool-calls
144+
level
140145
(:tool-handler e)
141146
(vals calls))
142147
(async/reduce conj messages)))})
@@ -160,9 +165,9 @@
160165
(defn chunk-handler
161166
"sets up a response handler loop for use with an OpenAI API call
162167
returns [channel openai-handler] - channel will emit the updated chat messages after dispatching any functions"
163-
[function-handler]
168+
[level function-handler]
164169
(let [c (async/chan 1)]
165-
[(response-loop c)
170+
[(response-loop level c)
166171
(fn [chunk]
167172
;; TODO this only supports when there's a single choice
168173
(let [{[{:keys [delta message finish_reason _role]}] :choices

src/prompts.clj

Lines changed: 93 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,10 @@
7474
:error
7575
{:content
7676
(logging/render
77-
"unable to run extractors \n```\n{{ container-definition }}\n```\n - {{ exception }}"
77+
"unable to run extractors \n```\n{{ container-definition }}\n```\n"
7878
{:dir host-dir
79-
:container-definition (str container-definition)
80-
:exception (str ex)})})
79+
:container-definition (str container-definition)})
80+
:exception (str ex)})
8181
m)))
8282

8383
(defn- metadata-file [prompts-file]
@@ -95,12 +95,7 @@
9595
(map (fn [m] (merge (registry/get-extractor m) m))))]
9696
(if (seq extractors)
9797
extractors
98-
[{:name "project-facts"
99-
:image "docker/lsp:latest"
100-
:entrypoint "/app/result/bin/docker-lsp"
101-
:command ["project-facts"
102-
"--vs-machine-id" "none"
103-
"--workspace" "/project"]}])))
98+
[])))
10499

105100
(def hub-images
106101
#{"curl" "qrencode" "toilet" "figlet" "gh" "typos" "fzf" "jq" "fmpeg" "pylint"})
@@ -109,50 +104,58 @@
109104
"get either :functions or :tools collection
110105
returns collection of openai compatiable tool definitions augmented with container info"
111106
[f]
112-
(->>
113-
(->
114-
(markdown/parse-metadata (metadata-file f))
115-
first
116-
(select-keys [:tools :functions])
117-
seq
118-
first ;; will take the first either tools or functions randomly
119-
second ;; returns the tools or functions array
120-
)
121-
(mapcat
122-
(fn [m]
123-
(if-let [tool (hub-images (:name m))]
124-
;; these come from our own public hub images
125-
[{:type "function"
126-
:function
127-
{:name (format "%s-manual" tool)
128-
:description (format "Run the man page for %s" tool)
129-
:container
130-
{:image (format "vonwig/%s:latest" tool)
131-
:command
132-
["{{raw|safe}}" "man"]}}}
133-
{:type "function"
134-
:function
135-
(merge
136-
{:description (format "Run a %s command." tool)
137-
:parameters
138-
{:type "object"
139-
:properties
140-
{:args
141-
{:type "string"
142-
:description (format "The arguments to pass to %s" tool)}}}
143-
:container
144-
{:image (format "vonwig/%s:latest" tool)
145-
:command ["{{raw|safe}}"]}}
146-
m)}]
147-
[{:type "function" :function (merge (registry/get-function m) (dissoc m :image))}])))))
107+
(try
108+
(->>
109+
(->
110+
(markdown/parse-metadata (metadata-file f))
111+
first
112+
(select-keys [:tools :functions])
113+
seq
114+
first ;; will take the first either tools or functions randomly
115+
second ;; returns the tools or functions array
116+
)
117+
(mapcat
118+
(fn [m]
119+
(if-let [tool (hub-images (:name m))]
120+
;; these come from our own public hub images
121+
[{:type "function"
122+
:function
123+
{:name (format "%s-manual" tool)
124+
:description (format "Run the man page for %s" tool)
125+
:container
126+
{:image (format "vonwig/%s:latest" tool)
127+
:command
128+
["{{raw|safe}}" "man"]}}}
129+
{:type "function"
130+
:function
131+
(merge
132+
{:description (format "Run a %s command." tool)
133+
:parameters
134+
{:type "object"
135+
:properties
136+
{:args
137+
{:type "string"
138+
:description (format "The arguments to pass to %s" tool)}}}
139+
:container
140+
{:image (format "vonwig/%s:latest" tool)
141+
:command ["{{raw|safe}}"]}}
142+
m)}]
143+
[{:type "function" :function (merge (registry/get-function m) (dissoc m :image))}]))))
144+
(catch Throwable _
145+
;; TODO warn about empty yaml front matter?
146+
[])))
148147

149148
(defn collect-metadata
150149
"collect metadata from yaml front-matter in README.md
151150
skip functions and extractors"
152151
[f]
153-
(dissoc
154-
(-> (markdown/parse-metadata (metadata-file f)) first)
155-
:extractors :functions))
152+
(try
153+
(dissoc
154+
(-> (markdown/parse-metadata (metadata-file f)) first)
155+
:extractors :functions)
156+
(catch Throwable _
157+
;; TODO warn about empty yaml front matter?
158+
{})))
156159

157160
(defn run-extractors
158161
"returns a map of extracted *math-context*
@@ -190,13 +193,20 @@
190193
m (merge (run-extractors opts) parameters)
191194
renderer (partial selma-render prompts (facts m user platform))
192195
prompts (if (fs/directory? prompts)
196+
;; directory based prompts
193197
(->> (fs/list-dir prompts)
194198
(filter (name-matches prompt-file-pattern))
195199
(sort-by fs/file-name)
196200
(map (fn [f] {:role (let [[_ role] (re-find prompt-file-pattern (fs/file-name f))] role)
197201
:content (slurp (fs/file f))}))
198202
(into []))
199-
(markdown-parser/parse-markdown (slurp prompts)))]
203+
;; file based prompts
204+
(try
205+
(let [p (slurp prompts)]
206+
(markdown-parser/parse-markdown p))
207+
(catch Throwable t
208+
(jsonrpc/notify :error {:content (format "failed to parse prompts from markdown %s" t)})
209+
[])))]
200210
(map renderer prompts)))
201211

202212
(defn interpolate [m template]
@@ -215,7 +225,7 @@
215225
function-name - the name of the function that the LLM has selected
216226
json-arg-string - the JSON arg string that the LLM has generated
217227
resolve fail - callbacks"
218-
[{:keys [functions user jwt timeout] :as opts} function-name json-arg-string {:keys [resolve fail]}]
228+
[{:keys [functions user jwt timeout level] :as opts :or {level 0}} function-name json-arg-string {:keys [resolve fail]}]
219229
(if-let [definition (->
220230
(->> (filter #(= function-name (-> % :function :name)) functions)
221231
first)
@@ -258,13 +268,15 @@
258268

259269
(= "prompt" (:type definition)) ;; asynchronous call to another agent - new conversation-loop
260270
(do
261-
(jsonrpc/notify :message {:content (format "## (%s) sub-prompt" (:ref definition))})
271+
(jsonrpc/notify :start {:level level
272+
:role "tool"
273+
:content (:ref definition)})
262274
(let [{:keys [messages _finish-reason]}
263275
(async/<!! (conversation-loop
264276
(assoc opts
277+
:level (inc (or (:level opts) 0))
265278
:prompts (git/prompt-file (:ref definition))
266279
:parameters arg-context)))]
267-
(jsonrpc/notify :message {:content (format "## (%s) end sub-prompt" (:ref definition))})
268280
(resolve (->> messages
269281
(filter #(= "assistant" (:role %)))
270282
(last)
@@ -275,37 +287,44 @@
275287
(fail (format "system failure %s" t)))))
276288
(fail "no function found")))
277289

290+
(defn- stop-looping [c s]
291+
(jsonrpc/notify :error {:content s})
292+
(async/>!! c {:messages [{:role "assistant" :content s}]
293+
:finish-reason "error"}))
294+
278295
(defn- run-prompts
279296
"call openai compatible chat completion endpoint and handle tool requests
280297
params
281298
prompts is the conversation history
282299
args for extracting functions, host-dir, user, platform
283300
returns channel that will contain the final set of messages and a finish-reason"
284-
[messages {:keys [prompts url model stream] :as opts}]
301+
[messages {:keys [prompts url model stream level] :as opts :or {level 0}}]
285302
(let [m (collect-metadata prompts)
286303
functions (collect-functions prompts)
287-
[c h] (openai/chunk-handler (partial
288-
function-handler
289-
(merge
290-
opts
291-
(select-keys m [:timeout])
292-
{:functions functions})))]
304+
[c h] (openai/chunk-handler
305+
level
306+
(partial
307+
function-handler
308+
(merge
309+
opts
310+
(select-keys m [:timeout])
311+
{:functions functions})))]
293312
(try
294-
(openai/openai
295-
(merge
296-
m
297-
{:messages messages}
298-
(when (seq functions) {:tools functions})
299-
(when url {:url url})
300-
(when model {:model model})
301-
(when (and stream (nil? (:stream m))) {:stream stream})) h)
313+
(if (seq messages)
314+
(openai/openai
315+
(merge
316+
m
317+
{:messages messages
318+
:level level}
319+
(when (seq functions) {:tools functions})
320+
(when url {:url url})
321+
(when model {:model model})
322+
(when (and stream (nil? (:stream m))) {:stream stream})) h)
323+
(stop-looping c "This is an empty set of prompts. Define prompts using h1 sections (eg `# prompt user`)" ))
302324
(catch ConnectException _
303-
;; when the conversation-loop can not connect to an openai compatible endpoint
304-
(async/>!! c {:messages [{:role "assistant" :content "I cannot connect to an openai compatible endpoint."}]
305-
:finish-reason "error"}))
325+
(stop-looping c "I cannot connect to an openai compatible endpoint."))
306326
(catch Throwable t
307-
(async/>!! c {:messages [{:role "assistant" :content (str t)}]
308-
:finish-reason "error"})))
327+
(stop-looping c (str t))))
309328
c))
310329

311330
(defn- conversation-loop
@@ -333,7 +352,7 @@
333352
(catch Throwable ex
334353
(let [c (async/promise-chan)]
335354
(jsonrpc/notify :error {:content
336-
(format "not a valid prompt configuration: %s" (with-out-str (pprint opts)))
355+
(format "failure for prompt configuration:\n %s" (with-out-str (pprint (dissoc opts :pat :jwt))))
337356
:exception (str ex)})
338357
(async/>! c {:messages [] :done "error"})
339358
c))))
@@ -410,9 +429,7 @@
410429
{:content
411430
(json/generate-string
412431
(if (map? x)
413-
(if (= "error" (:done x))
414-
(update x :messages last)
415-
(select-keys x [:done]))
432+
(select-keys x [:done])
416433
x))})))
417434

418435
(defn output-prompts [coll]

0 commit comments

Comments
 (0)