branch: externals/llm
commit ba3241e5d2198d3b888841804ec10e89ba282cb3
Author: Andrew Hyatt <[email protected]>
Commit: GitHub <[email protected]>

    Add Gemini 3, add thought signatures and fix function responses (#218)
---
 llm-models.el         |  6 ++++++
 llm-provider-utils.el |  2 +-
 llm-vertex.el         | 59 ++++++++++++++++++++++++++++++++++-----------------
 3 files changed, 47 insertions(+), 20 deletions(-)

diff --git a/llm-models.el b/llm-models.el
index 476324ea98..198a251261 100644
--- a/llm-models.el
+++ b/llm-models.el
@@ -224,6 +224,12 @@ REGEX is a regular expression that can be used to identify 
the model, uniquely (
                                pdf-input caching reasoning)
     :context-length 1048576
     :regex "gemini-2\\.5-flash$")
+   (make-llm-model
+    :name "Gemini 3 Pro" :symbol 'gemini-3-pro
+    :capabilities '(generation tool-use image-input audio-input video-input 
json-response
+                               pdf-input caching reasoning)
+    :context-length 1048576
+    :regex "gemini-3-pro")
    (make-llm-model
     :name "Gemini 2.0 Pro" :symbol 'gemini-2.0-pro
     :capabilities '(generation tool-use image-input audio-input video-input)
diff --git a/llm-provider-utils.el b/llm-provider-utils.el
index 7371e87a80..262f398a23 100644
--- a/llm-provider-utils.el
+++ b/llm-provider-utils.el
@@ -710,7 +710,7 @@ This returns a JSON object (a list that can be converted to 
JSON)."
 (defun llm-provider-utils-append-to-prompt (prompt output &optional 
tool-results role)
   "Append OUTPUT to PROMPT as an assistant interaction.
 
-OUTPUT can be a string or a structure in the case of function calls.
+OUTPUT can be a string or a structure in the case of tool uses.
 
 TOOL-RESULTS is a list of results from the LLM output, if any.
 
diff --git a/llm-vertex.el b/llm-vertex.el
index b587e9ebf7..ea92f10a43 100644
--- a/llm-vertex.el
+++ b/llm-vertex.el
@@ -90,6 +90,11 @@ the key must be regenerated every hour."
   (chat-model llm-vertex-default-chat-model)
   key-gentime)
 
+(cl-defstruct (llm-gemini-tool-use (:include llm-provider-utils-tool-use))
+  "A struct representing tool use from Gemini models, which needs more
+information than standard tool use."
+  thought-signature)
+
 ;; API reference: 
https://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/send-chat-prompts-gemini#gemini-chat-samples-drest
 
 (cl-defmethod llm-provider-request-prelude ((provider llm-vertex))
@@ -176,13 +181,19 @@ the key must be regenerated every hour."
       (llm-provider-extract-tool-uses provider (aref response 0))
     ;; In some error cases, the response does not have any candidates.
     (when (assoc-default 'candidates response)
-      (mapcar (lambda (call)
-                (make-llm-provider-utils-tool-use
-                 :name (assoc-default 'name call)
-                 :args (assoc-default 'args call)))
+      (mapcar (lambda (call-and-sig)
+                (make-llm-gemini-tool-use
+                 :name (assoc-default 'name (car call-and-sig))
+                 :args (assoc-default 'args (car call-and-sig))
+                 :thought-signature (or (cdr call-and-sig)
+                                        ;; Specific magic string for when 
there is no signature, should
+                                        ;; only happen when migrating the 
prompt from other models.
+                                        ;; See 
https://ai.google.dev/gemini-api/docs/gemini-3
+                                        
"context_engineering_is_the_way_to_go")))
               (mapcan (lambda (maybe-call)
                         (when-let ((fc (assoc-default 'functionCall 
maybe-call)))
-                          (list fc)))
+                          (list (cons fc
+                                      (assoc-default 'thoughtSignature 
maybe-call)))))
                       (assoc-default
                        'parts (assoc-default
                                'content
@@ -193,7 +204,7 @@ the key must be regenerated every hour."
   `(:role ,(pcase (llm-chat-prompt-interaction-role interaction)
              ('user "user")
              ('assistant "model")
-             ('tool-results "function"))
+             ('tool-results "user"))
           :parts
           ,(cond
             ((eq 'tool-results (llm-chat-prompt-interaction-role interaction))
@@ -202,16 +213,16 @@ the key must be regenerated every hour."
                         `(:functionResponse
                           (:name ,(llm-chat-prompt-tool-result-tool-name fc)
                                  :response
-                                 (:name 
,(llm-chat-prompt-tool-result-tool-name fc)
-                                        :content 
,(llm-chat-prompt-tool-result-result fc)))))
+                                 (:output ,(llm-chat-prompt-tool-result-result 
fc)))))
                       (llm-chat-prompt-interaction-tool-results interaction))))
             ((and (consp (llm-chat-prompt-interaction-content interaction))
                   (llm-provider-utils-tool-use-p (car 
(llm-chat-prompt-interaction-content interaction))))
              (vconcat
               (mapcar (lambda (tool-use)
                         `(:functionCall
-                          (:name ,(llm-provider-utils-tool-use-name tool-use)
-                                 :args ,(llm-provider-utils-tool-use-args 
tool-use))))
+                          (:name ,(llm-gemini-tool-use-name tool-use)
+                                 :args ,(llm-gemini-tool-use-args tool-use))
+                          :thoughtSignature 
,(llm-gemini-tool-use-thought-signature tool-use)))
                       (llm-chat-prompt-interaction-content interaction))))
             ((llm-multipart-p (llm-chat-prompt-interaction-content 
interaction))
              (vconcat (mapcar (lambda (part)
@@ -309,15 +320,25 @@ which is necessary to properly set some paremeters."
         (setq thinking-plist '(:includeThoughts t))
         (when-let ((budget (llm-chat-prompt-reasoning prompt))
                    (max-budget (if (eq model 'gemini-2.5-pro) 32768 24576)))
-          (if (and (eq model 'gemini-2.5-pro) (eq budget 'none))
-              (display-warning 'llm :warning "Cannot turn off reasoning in 
Gemini 2.5 Pro, ignoring reasoning setting")
-            (setq thinking-plist (plist-put thinking-plist
-                                            :thinkingBudget
-                                            (pcase budget
-                                              ('none 0)
-                                              ('light 1024)
-                                              ('medium (/ max-budget 2))
-                                              ('maximum max-budget)))))))
+          (if (and (or (eq model 'gemini-2.5-pro)
+                       (eq model 'gemini-3-pro))
+                   (eq budget 'none))
+              (display-warning 'llm :warning "Cannot turn off reasoning in 
selected model, ignoring reasoning setting")
+            (if (eq model 'gemini-3-pro)
+                (setq thinking-plist (plist-put thinking-plist
+                                                :thinking_level
+                                                (pcase budget
+                                                  ('light "low")
+                                                  ;; Medium is currently not 
supported, coming soon.
+                                                  ('medium "high")
+                                                  ('maximum "high"))))
+              (setq thinking-plist (plist-put thinking-plist
+                                              :thinkingBudget
+                                              (pcase budget
+                                                ('none 0)
+                                                ('light 1024)
+                                                ('medium (/ max-budget 2))
+                                                ('maximum max-budget))))))))
       (when thinking-plist
         (setq params-plist (plist-put params-plist :thinkingConfig 
thinking-plist))))
     (when params-plist

Reply via email to