|
6 | 6 | [clojure.string :as string] |
7 | 7 | [hato.client :as http] |
8 | 8 | [hato.middleware :as hm] |
| 9 | + [simulflow.async :as async] |
9 | 10 | [simulflow.utils.core :as u]) |
10 | 11 | (:import |
11 | 12 | (java.io InputStream))) |
|
53 | 54 | {:as :stream}))) |
54 | 55 | buffer-size (calc-buffer-size params) |
55 | 56 | events (a/chan (a/buffer buffer-size) (map parse-event))] |
56 | | - (a/thread |
57 | | - (try |
58 | | - (loop [byte-coll []] |
59 | | - (let [byte-arr (byte-array (max 1 (.available event-stream))) |
60 | | - bytes-read (.read event-stream byte-arr)] |
61 | | - |
62 | | - (if (neg? bytes-read) |
63 | | - |
64 | | - ;; Input stream closed, exiting read-loop |
65 | | - nil |
66 | | - |
67 | | - (let [next-byte-coll (concat byte-coll (seq byte-arr)) |
68 | | - data (slurp (byte-array next-byte-coll))] |
69 | | - (if-let [es (not-empty (re-seq event-mask data))] |
70 | | - (if (every? true? (map #(a/>!! events %) es)) |
71 | | - (recur (drop (apply + (map #(count (.getBytes ^String %)) es)) |
72 | | - next-byte-coll)) |
73 | | - |
74 | | - ;; Output stream closed, exiting read-loop |
75 | | - nil) |
76 | | - |
77 | | - (recur next-byte-coll)))))) |
78 | | - (finally |
79 | | - (when close? |
80 | | - (a/close! events)) |
81 | | - (.close event-stream)))) |
| 57 | + ((async/vfuturize |
| 58 | + (fn [] |
| 59 | + (try |
| 60 | + (loop [byte-coll []] |
| 61 | + (let [byte-arr (byte-array (max 1 (.available event-stream))) |
| 62 | + bytes-read (.read event-stream byte-arr)] |
| 63 | + |
| 64 | + (if (neg? bytes-read) |
| 65 | + |
| 66 | + ;; Input stream closed, exiting read-loop |
| 67 | + nil |
| 68 | + |
| 69 | + (let [next-byte-coll (concat byte-coll (seq byte-arr)) |
| 70 | + data (slurp (byte-array next-byte-coll))] |
| 71 | + (if-let [es (not-empty (re-seq event-mask data))] |
| 72 | + (if (every? true? (map #(a/>!! events %) es)) |
| 73 | + (recur (drop (apply + (map #(count (.getBytes ^String %)) es)) |
| 74 | + next-byte-coll)) |
| 75 | + |
| 76 | + ;; Output stream closed, exiting read-loop |
| 77 | + nil) |
| 78 | + |
| 79 | + (recur next-byte-coll)))))) |
| 80 | + (finally |
| 81 | + (when close? |
| 82 | + (a/close! events)) |
| 83 | + (.close event-stream)))))) |
| 84 | + |
82 | 85 | events)) |
83 | 86 |
|
84 | 87 | (defn sse-request |
|
135 | 138 | hm/wrap-nested-params |
136 | 139 | hm/wrap-method]) |
137 | 140 |
|
| 141 | +(def openai-completions-url "https://api.openai.com/v1/chat/completions") |
| 142 | + |
| 143 | +(defn stream-chat-completion |
| 144 | + [{:keys [api-key messages tools model response-format completions-url] |
| 145 | + :or {model "gpt-4o-mini" |
| 146 | + completions-url openai-completions-url}}] |
| 147 | + (:body (sse-request {:request {:url completions-url |
| 148 | + :headers {"Authorization" (str "Bearer " api-key) |
| 149 | + "Content-Type" "application/json"} |
| 150 | + |
| 151 | + :method :post |
| 152 | + :body (u/json-str (cond-> {:messages messages |
| 153 | + :stream true |
| 154 | + :response_format response-format |
| 155 | + :model model} |
| 156 | + (pos? (count tools)) (assoc :tools tools)))} |
| 157 | + :params {:stream/close? true}}))) |
| 158 | + |
| 159 | +(defn normal-chat-completion |
| 160 | + [{:keys [api-key messages tools model response-format stream completions-url] |
| 161 | + :or {model "gpt-4o-mini" |
| 162 | + stream false}}] |
| 163 | + (http/request {:url openai-completions-url |
| 164 | + :headers {"Authorization" (str "Bearer " api-key) |
| 165 | + "Content-Type" "application/json"} |
| 166 | + |
| 167 | + :throw-on-error? false |
| 168 | + :method :post |
| 169 | + :body (u/json-str (cond-> {:messages messages |
| 170 | + :stream stream |
| 171 | + :response_format response-format |
| 172 | + :model model} |
| 173 | + (pos? (count tools)) (assoc :tools tools)))})) |
| 174 | + |
138 | 175 | (comment |
139 | 176 | (require '[simulflow.secrets :refer [secret]]) |
140 | 177 |
|
|
0 commit comments