|
158 | 158 | "reason" (if (:delta-reasoning? content) |
159 | 159 | ;; DeepSeek-style: reasoning_content must be passed back to API |
160 | 160 | {:role "assistant" |
| 161 | + :content "" |
161 | 162 | :reasoning_content (:text content)} |
162 | 163 | ;; Fallback: wrap in thinking tags for models that use text-based reasoning |
163 | 164 | {:role "assistant" |
|
169 | 170 | :content (extract-content content supports-image?)} |
170 | 171 | nil)) |
171 | 172 |
|
172 | | -(defn ^:private merge-reasoning-with-response |
173 | | - "Merge reasoning_content from prev with tool_calls/content from msg. |
174 | | - Required for DeepSeek which needs reasoning_content on same message as the response." |
| 173 | +(defn ^:private merge-assistant-messages |
| 174 | + "Merge two assistant messages into one. |
| 175 | + Concatenates contents and tool_calls, and preserves reasoning_content." |
175 | 176 | [prev msg] |
176 | | - (cond-> {:role "assistant" |
177 | | - :reasoning_content (:reasoning_content prev)} |
178 | | - ;; content: from msg (DeepSeek's final answer) |
179 | | - (:content msg) |
180 | | - (assoc :content (:content msg)) |
181 | | - ;; tool_calls: concatenate (prev may have some from earlier merge, msg adds more) |
182 | | - (or (:tool_calls prev) (:tool_calls msg)) |
183 | | - (assoc :tool_calls (into (or (:tool_calls prev) []) (:tool_calls msg))))) |
| 177 | + (let [as-seq (fn [c] (if (sequential? c) c (extract-content c false))) |
| 178 | + prev-content (:content prev) |
| 179 | + msg-content (:content msg) |
| 180 | + combined-content (vec (concat (as-seq prev-content) (as-seq msg-content)))] |
| 181 | + (cond-> {:role "assistant" |
| 182 | + :content combined-content} |
| 183 | + (or (:reasoning_content prev) (:reasoning_content msg)) |
| 184 | + (assoc :reasoning_content (str (:reasoning_content prev) (:reasoning_content msg))) |
| 185 | + |
| 186 | + (or (:tool_calls prev) (:tool_calls msg)) |
| 187 | + (assoc :tool_calls (vec (concat (:tool_calls prev) |
| 188 | + (:tool_calls msg))))))) |
184 | 189 |
|
185 | 190 | (defn ^:private merge-adjacent-assistants |
186 | | - "Merge adjacent assistant messages only when prev has reasoning_content. |
187 | | - This is required for DeepSeek which needs reasoning_content on same message as tool_calls/content." |
| 191 | + "Merge all adjacent assistant messages. |
| 192 | + This is required by many OpenAI-compatible APIs (including DeepSeek) |
| 193 | + which do not allow multiple consecutive assistant messages." |
188 | 194 | [messages] |
189 | 195 | (reduce |
190 | 196 | (fn [acc msg] |
191 | 197 | (let [prev (peek acc)] |
192 | 198 | (if (and (= "assistant" (:role prev)) |
193 | | - (= "assistant" (:role msg)) |
194 | | - (:reasoning_content prev)) |
195 | | - (conj (pop acc) (merge-reasoning-with-response prev msg)) |
| 199 | + (= "assistant" (:role msg))) |
| 200 | + (conj (pop acc) (merge-assistant-messages prev msg)) |
196 | 201 | (conj acc msg)))) |
197 | 202 | [] |
198 | 203 | messages)) |
199 | 204 |
|
200 | 205 | (defn ^:private valid-message? |
201 | 206 | "Check if a message should be included in the final output." |
202 | | - [{:keys [role content tool_calls reasoning_content] :as msg}] |
203 | | - (and msg |
204 | | - (or (= role "tool") ; Never remove tool messages |
| 207 | + [{:keys [role content tool_calls reasoning_content] :as _msg}] |
| 208 | + (and (or (= role "tool") ; Never remove tool messages |
205 | 209 | (seq tool_calls) ; Keep messages with tool calls |
206 | 210 | (seq reasoning_content) ; Keep messages with reasoning_content (DeepSeek) |
207 | 211 | (and (string? content) |
|
0 commit comments