Skip to content

Commit b4721dd

Browse files
committed
Fix openai skip streaming response corner cases.
1 parent 4cd662a commit b4721dd

File tree

2 files changed

+40
-24
lines changed

2 files changed

+40
-24
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Fix openai skip streaming response corner cases.
6+
57
## 0.20.0
68

79
- Support custom commands via md files in `~/.config/eca/commands/` or `.eca/commands/`.

src/eca/llm_providers/openai.clj

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,9 @@
8383
{:effort "medium"
8484
:summary "detailed"})
8585
:stream true
86+
;; :verbosity "medium"
8687
:max_output_tokens max-output-tokens}
87-
mcp-call-by-item-id* (atom {})
88+
tool-call-by-item-id* (atom {})
8889
on-response-fn
8990
(fn handle-response [event data]
9091
(case event
@@ -93,27 +94,13 @@
9394
(on-message-received {:type :text
9495
:text (:delta data)})
9596
;; tools
96-
"response.function_call_arguments.delta" (let [call (get @mcp-call-by-item-id* (:item_id data))]
97+
"response.function_call_arguments.delta" (let [call (get @tool-call-by-item-id* (:item_id data))]
9798
(on-prepare-tool-call {:id (:id call)
9899
:name (:name call)
99100
:arguments-text (:delta data)}))
100101

101102
"response.output_item.done"
102103
(case (:type (:item data))
103-
"function_call" (let [function-name (-> data :item :name)
104-
function-args (-> data :item :arguments)
105-
{:keys [new-messages]} (on-tool-called {:id (-> data :item :call_id)
106-
:name function-name
107-
:arguments (json/parse-string function-args)})
108-
input (normalize-messages new-messages)]
109-
(base-completion-request!
110-
{:rid (llm-util/gen-rid)
111-
:body (assoc body :input input)
112-
:api-url api-url
113-
:api-key api-key
114-
:on-error on-error
115-
:on-response handle-response})
116-
(swap! mcp-call-by-item-id* dissoc (-> data :item :id)))
117104
"reasoning" (on-reason {:status :finished
118105
:id (-> data :item :id)
119106
:external-id (-> data :item :id)})
@@ -140,22 +127,49 @@
140127
:id (-> data :item :id)})
141128
"function_call" (let [call-id (-> data :item :call_id)
142129
item-id (-> data :item :id)
143-
name (-> data :item :name)]
144-
(swap! mcp-call-by-item-id* assoc item-id {:name name :id call-id})
145-
(on-prepare-tool-call {:id (-> data :item :call_id)
146-
:name (-> data :item :name)
147-
:arguments-text (-> data :item :arguments)}))
130+
function-name (-> data :item :name)
131+
function-args (-> data :item :arguments)]
132+
(swap! tool-call-by-item-id* assoc item-id {:name function-name :id call-id})
133+
(on-prepare-tool-call {:id call-id
134+
:name function-name
135+
:arguments-text function-args}))
148136
nil)
149137

150138
;; done
151139
"response.completed"
152-
(do
140+
(let [last-output (-> data :response :output last)]
153141
(on-usage-updated {:input-tokens (-> data :response :usage :input_tokens)
154142
:output-tokens (-> data :response :usage :output_tokens)})
155-
(when-not (= "function_call" (-> data :response :output last :type))
156-
(on-message-received {:type :finish
143+
(case (:type last-output)
144+
"function_call"
145+
(let [function-name (:name last-output)
146+
function-args (:arguments last-output)
147+
call-id (:call_id last-output)
148+
item-id (:id last-output)]
149+
;; Fallback case when the tool call was not prepared before when
150+
;; some models/apis respond only with response.completed (skipping streaming).
151+
(when-not (get @tool-call-by-item-id* item-id)
152+
(swap! tool-call-by-item-id* assoc item-id {:name function-name :id call-id})
153+
(on-prepare-tool-call {:id call-id
154+
:name function-name
155+
:arguments-text function-args}))
156+
(let [{:keys [new-messages]} (on-tool-called {:id call-id
157+
:name function-name
158+
:arguments (json/parse-string function-args)})
159+
input (normalize-messages new-messages)]
160+
(base-completion-request!
161+
{:rid (llm-util/gen-rid)
162+
:body (assoc body :input input)
163+
:api-url api-url
164+
:api-key api-key
165+
:on-error on-error
166+
:on-response handle-response})
167+
(swap! tool-call-by-item-id* dissoc item-id)))
157168

169+
;; else
170+
(on-message-received {:type :finish
158171
:finish-reason (-> data :response :status)})))
172+
159173
nil))]
160174
(base-completion-request!
161175
{:rid (llm-util/gen-rid)

0 commit comments

Comments
 (0)