|
52 | 52 |
|
53 | 53 | (defn completion! [{:keys [model user-prompt context temperature api-key past-messages tools web-search] |
54 | 54 | :or {temperature 1.0}} |
55 | | - {:keys [on-message-received on-error on-tool-called on-reason]}] |
| 55 | + {:keys [on-message-received on-error on-prepare-tool-call on-tool-called on-reason]}] |
56 | 56 | (let [input (conj past-messages {:role "user" :content user-prompt}) |
57 | 57 | tools (cond-> tools |
58 | 58 | web-search (conj {:type "web_search_preview"})) |
|
63 | 63 | :temperature temperature |
64 | 64 | :tools tools |
65 | 65 | :stream true} |
66 | | - on-response-fn (fn handle-response [event data] |
67 | | - (llm-util/log-response logger-tag event data) |
68 | | - (case event |
69 | | - ;; text |
70 | | - "response.output_text.delta" (on-message-received {:type :text |
71 | | - :text (:delta data)}) |
72 | | - ;; tools |
73 | | - "response.output_item.done" (case (:type (:item data)) |
74 | | - "function_call" (let [function-name (-> data :item :name) |
75 | | - function-args (-> data :item :arguments) |
76 | | - response (on-tool-called {:id (-> data :item :call_id) |
77 | | - :name function-name |
78 | | - :arguments (json/parse-string function-args)})] |
79 | | - (base-completion-request! |
80 | | - {:body (assoc body :input (concat input |
81 | | - [{:type "function_call" |
82 | | - :call_id (-> data :item :call_id) |
83 | | - :name function-name |
84 | | - :arguments function-args}] |
85 | | - (mapv |
86 | | - (fn [{:keys [_type content]}] |
87 | | - ;; TODO handle different types |
88 | | - {:type "function_call_output" |
89 | | - :call_id (-> data :item :call_id) |
90 | | - :output content}) |
91 | | - (:contents response)))) |
92 | | - :api-key api-key |
93 | | - :on-error on-error |
94 | | - :on-response handle-response})) |
95 | | - "reasoning" (on-reason {:status :finished}) |
96 | | - nil) |
97 | | - ;; URL mentioned |
98 | | - "response.output_text.annotation.added" (case (-> data :annotation :type) |
99 | | - "url_citation" (on-message-received |
100 | | - {:type :url |
101 | | - :title (-> data :annotation :title) |
102 | | - :url (-> data :annotation :url)}) |
103 | | - nil) |
104 | | - ;; reasoning |
105 | | - "response.output_item.added" (case (-> data :item :type) |
106 | | - "reasoning" (on-reason {:status :started}) |
107 | | - nil) |
| 66 | + mcp-call-by-item-id* (atom {}) |
| 67 | + on-response-fn |
| 68 | + (fn handle-response [event data] |
| 69 | + (llm-util/log-response logger-tag event data) |
| 70 | + (case event |
| 71 | + ;; text |
| 72 | + "response.output_text.delta" |
| 73 | + (on-message-received {:type :text |
| 74 | + :text (:delta data)}) |
| 75 | + ;; tools |
| 76 | + "response.function_call_arguments.delta" (let [call (get @mcp-call-by-item-id* (:item_id data))] |
| 77 | + (on-prepare-tool-call {:id (:id call) |
| 78 | + :name (:name call) |
| 79 | + :argumentsText (:delta data)})) |
108 | 80 |
|
109 | | - ;; done |
110 | | - "response.completed" (when-not (= "function_call" (-> data :response :output last :type)) |
111 | | - (on-message-received {:type :finish |
112 | | - :finish-reason (-> data :response :status)})) |
113 | | - nil))] |
| 81 | + "response.output_item.done" |
| 82 | + (case (:type (:item data)) |
| 83 | + "function_call" (let [function-name (-> data :item :name) |
| 84 | + function-args (-> data :item :arguments) |
| 85 | + response (on-tool-called {:id (-> data :item :call_id) |
| 86 | + :name function-name |
| 87 | + :arguments (json/parse-string function-args)})] |
| 88 | + (base-completion-request! |
| 89 | + {:body (assoc body :input (concat input |
| 90 | + [{:type "function_call" |
| 91 | + :call_id (-> data :item :call_id) |
| 92 | + :name function-name |
| 93 | + :arguments function-args}] |
| 94 | + (mapv |
| 95 | + (fn [{:keys [_type content]}] |
| 96 | + ;; TODO handle different types |
| 97 | + {:type "function_call_output" |
| 98 | + :call_id (-> data :item :call_id) |
| 99 | + :output content}) |
| 100 | + (:contents response)))) |
| 101 | + :api-key api-key |
| 102 | + :on-error on-error |
| 103 | + :on-response handle-response}) |
| 104 | + (swap! mcp-call-by-item-id* dissoc (-> data :item :id))) |
| 105 | + "reasoning" (on-reason {:status :finished}) |
| 106 | + nil) |
| 107 | + |
| 108 | + ;; URL mentioned |
| 109 | + "response.output_text.annotation.added" |
| 110 | + (case (-> data :annotation :type) |
| 111 | + "url_citation" (on-message-received |
| 112 | + {:type :url |
| 113 | + :title (-> data :annotation :title) |
| 114 | + :url (-> data :annotation :url)}) |
| 115 | + nil) |
| 116 | + |
| 117 | + ;; reasoning / tools |
| 118 | + "response.output_item.added" |
| 119 | + (case (-> data :item :type) |
| 120 | + "reasoning" (on-reason {:status :started}) |
| 121 | + "function_call" (let [call-id (-> data :item :call_id) |
| 122 | + item-id (-> data :item :id) |
| 123 | + name (-> data :item :name)] |
| 124 | + (swap! mcp-call-by-item-id* assoc item-id {:name name :id call-id}) |
| 125 | + (on-prepare-tool-call {:id (-> data :item :call_id) |
| 126 | + :name (-> data :item :name) |
| 127 | + :argumentsText (-> data :item :arguments)})) |
| 128 | + nil) |
| 129 | + |
| 130 | + ;; done |
| 131 | + "response.completed" |
| 132 | + (when-not (= "function_call" (-> data :response :output last :type)) |
| 133 | + (on-message-received {:type :finish |
| 134 | + :finish-reason (-> data :response :status)})) |
| 135 | + nil))] |
114 | 136 | (base-completion-request! |
115 | 137 | {:body body |
116 | 138 | :api-key api-key |
|
0 commit comments