Client Library for the Google Gemini LLM APIs

While the Google Gemini APIs offer a compelling suite of advantages for developers seeking to integrate cutting-edge, multimodal AI capabilities into their applications. A primary benefit is the large one million token context size and very fast inference speeds. Gemini is very cost effective for natural language processing tasks such as text summarization, question answering, code generation, creative content creation, and conversational AI.

Note: March 22, 2026 addition: new section on Gemini Interaction APIs to blend local tools and Google’s built in tools for search, etc. New material is at the end of this chapter.

The source code for this Gemini library is in my GitHub repository https://github.com/mark-watson/gemini. As usual you want to git clone this repository in your local directory ~/quicklisp/local-projects/ so Quicklisp can find this library with (ql:quickload :gemini). We will list the code below and then look at example use.

package.lisp

We need the function post in the external library dexador:

1 ;;;; package.lisp
2 
3 (defpackage #:gemini
4   (:use #:cl)
5 
6   (:export #:generate #:count-tokens #:send-chat-message #:generate-streaming #:generate-with-search #:generate-with-search-and-citations
7            #:make-function-declaration #:generate-with-tools #:continue-with-function-responses))

gemini.asd

 1 ;;;; gemini.asd
 2 
 3 (asdf:defsystem #:gemini
 4   :description "Library for using the Google Gemini Interactions API"
 5   :author "Mark Watson"
 6   :license "Apache 2"
 7   :depends-on (#:uiop #:cl-json #:alexandria #:dexador)
 8   :components ((:file "package")
 9                (:file "gemini")
10            (:file "gemini_interactions_api")))

gemini.lisp

This code defines functions for generating content and counting tokens. Rather than spawning an external curl process, it executes native HTTP POST requests using the Dexador client via the helper function %post-json.

To generate text, the generate function calls the Google Interactions API at the https://generativelanguage.googleapis.com/v1beta/interactions endpoint. It sends a simple JSON payload containing the model identifier and prompt string, then decodes the response and extracts the generated text from the steps. The count-tokens function queries the standard models endpoint (countTokens action) using the same native HTTP post helper.

 1 (in-package #:gemini)
 2 
 3 (defvar *google-api-key* (uiop:getenv "GOOGLE_API_KEY"))
 4 (defvar
 5   *interactions-api-url*
 6   "https://generativelanguage.googleapis.com/v1beta/interactions")
 7 
 8 (defvar *model* "gemini-3-flash-preview") ;; model used in this file.
 9 
10 (defun %post-json (url headers payload-hash)
11   "Helper function to perform an HTTP POST request with a JSON payload using Dexador."
12   (let ((payload-json (cl-json:encode-json-to-string payload-hash)))
13     (dex:post url :headers headers :content payload-json)))
14 
15 ;;; ---- Interactions API helpers ----
16 
17 (defun %extract-text-from-steps (decoded-response)
18   "Extract the text from the last model_output step in an Interactions API response.
19    Response format: {\"steps\": [{\"type\": \"model_output\", \"content\": [{\"type\": \"text\", \"text\": \"...\"}]}]}"
20   (let* ((steps (cdr (assoc :STEPS decoded-response))))
21     (loop for step in (reverse steps)
22           when (string-equal (cdr (assoc :TYPE step)) "model_output")
23           return (let* ((content (cdr (assoc :CONTENT step)))
24                         (first-content (first content)))
25                    (cdr (assoc :TEXT first-content))))))
26 
27 (defun generate (prompt &optional (model-id *model*))
28   "Generates text from a given prompt using the Interactions API.
29    Uses *model* defined at the top of this file as default.
30    PROMPT: The text prompt to generate content from.
31    MODEL-ID: Optional. The ID of the model to use.
32    Returns the generated text as a string."
33   (let* ((payload (make-hash-table :test 'equal)))
34     (setf (gethash "model" payload) model-id
35           (gethash "input" payload) prompt)
36     (let* ((headers (list '("Content-Type" . "application/json")
37                           (cons "x-goog-api-key" *google-api-key*)
38                           '("Api-Revision" . "2026-05-20")))
39            (response-string (%post-json *interactions-api-url* headers payload))
40            (decoded-response (cl-json:decode-json-from-string response-string)))
41       (%extract-text-from-steps decoded-response))))
42   
43 ;; (gemini:generate "In one sentence, explain how AI works to a child.")
44 ;; (gemini:generate "Write a short, four-line poem about coding in Python.")
45 
46 (defun count-tokens (prompt &optional (model-id *model*))
47   "Counts the number of tokens for a given prompt and model.
48    Uses *model* defined at top of this file as default.
49    PROMPT: The text prompt to count tokens for.
50    MODEL-ID: Optional. The ID of the model to use.
51    Returns the total token count as an integer."
52   (let* ((api-url (concatenate 'string
53                                "https://generativelanguage.googleapis.com/v1beta/models/"
54                                model-id ":countTokens"))
55          (payload (make-hash-table :test 'equal)))
56     ;; Construct payload similar to generate function
57     (setf (gethash "contents" payload)
58           (list (let ((contents (make-hash-table :test 'equal)))
59                   (setf (gethash "parts" contents)
60                         (list (let ((part (make-hash-table :test 'equal)))
61                                 (setf (gethash "text" part) prompt)
62                                 part)))
63                   contents)))
64     (let* ((headers (list '("Content-Type" . "application/json")
65                           (cons "x-goog-api-key" *google-api-key*)))
66            (response-string (%post-json api-url headers payload))
67            (decoded-response (cl-json:decode-json-from-string response-string))
68            (total-tokens-pair (assoc :TOTAL-TOKENS decoded-response)))
69       (if total-tokens-pair
70           (cdr total-tokens-pair)
71           (error "Could not retrieve token count from API response: ~S"
72          decoded-response)))))
73 
74 ;; (gemini:count-tokens "In one sentence, explain how AI works to a child.")
75 
76 (defun run-tests ()
77   "Runs tests for generate and count-tokens functions."
78   (let* ((prompt "In one sentence, explain how AI works to a child.")
79          (generated-text (generate prompt))
80          (token-count (count-tokens prompt)))
81     (format t "Generated Text: ~A~%Token Count: ~A~%" generated-text token-count)))

Example Use

1 CL-USER 4 > (gemini:generate "In one sentence, explain how AI works to a child.")
2 "AI is like teaching a computer with lots and lots of examples, so it can learn to figure things out and act smart all by itself."
3 
4 CL-USER 5 > (gemini:count-tokens "How many tokens is this sentence?")
5 7

Google’s “Grounding with Google Search” is a powerful feature that connects the Gemini model to real-time web information, allowing it to answer queries about current events and reducing the likelihood of “hallucinations” by anchoring responses in verifiable external data. The following Common Lisp program utilizes this feature by defining a function, generate-with-search, which builds a JSON payload that specifically includes a tools configuration. By inserting an empty Google Search object into this configuration, the code explicitly instructs the API to perform a web search—such as looking up the winner of a recent tournament like Euro 2024—and synthesize those findings into the final response, which is then parsed and returned as a string. The following code snippet is near the bottom of the file gemini.lisp:

 1 (defun generate-with-search (prompt &optional (model-id *model*))
 2   "Generates text with Google Search grounding via the Interactions API."
 3   (let* ((payload (make-hash-table :test 'equal)))
 4     (setf (gethash "model" payload) model-id
 5           (gethash "input" payload) prompt
 6           (gethash "tools" payload)
 7           (list (let ((tool (make-hash-table :test 'equal)))
 8                   (setf (gethash "type" tool) "google_search")
 9                   tool)))
10     (let* ((headers (list '("Content-Type" . "application/json")
11                           (cons "x-goog-api-key" *google-api-key*)
12                           '("Api-Revision" . "2026-05-20")))
13            (response-string (%post-json *interactions-api-url* headers payload))
14            (decoded-response (cl-json:decode-json-from-string response-string)))
15       (%extract-text-from-steps decoded-response))))
16 
17 ;; (gemini:generate-with-search "Consultant Mark Watson has written Common Lisp, semantic web, Clojure, Java, and AI books. What musical instruments does he play?")

The core mechanism of this implementation relies on the payload hash table construction, specifically where the "tools" key is populated. Unlike a standard generation request, this payload includes a list containing a Google Search tool definition; the presence of this specific configuration acts as a switch, granting the model permission to query Google’s search index before formulating its answer. This is particularly critical for questions regarding current events, as the model’s static training data would otherwise be outdated.

Because we are calling the /v1beta/interactions API rather than standard generation endpoints, the payload accepts a direct "input" prompt string, and the response is structured as a series of unified "steps". The helper %extract-text-from-steps simplifies traversal by parsing the returned steps in reverse order and returning the text from the last model_output step.

Here is example output:

 1 $ sbcl
 2 This is SBCL 2.5.10, an implementation of ANSI Common Lisp.
 3 * (ql:quickload :gemini)
 4 To load "gemini":
 5   Load 1 ASDF system:
 6      gemini
 7 ; Loading "gemini"
 8 nil
 9 * (gemini:generate-with-search "What movies are playing in Flagstaff Arizona today at the Harkins Theater?")
10 "Today, **Thursday, December 18, 2025**, the following movies are playing at the **Harkins Flagstaff 16** theater. 
11 
12 Please note that many major releases, such as *Avatar: Fire and Ash*, are currently running early \"preview\" screenings ahead of their official opening tomorrow.
13 
14 ### **Movies & Showtimes**
15 
16 *   **Avatar: Fire and Ash** (PG-13)
17     *   **Ciné XL:** 2:00 PM, 6:15 PM, 10:30 PM
18     *   **3D HFR:** 2:30 PM, 5:00 PM, 6:45 PM, 9:15 PM
19     *   **Digital:** 3:00 PM, 4:00 PM, 7:15 PM, 8:15 PM, 9:30 PM
20 *   **Five Nights at Freddy's 2** (PG-13)
21     *   11:30 AM, 12:30 PM, 2:05 PM, 4:50 PM, 7:35 PM, 10:20 PM
22 *   **Zootopia 2** (PG)
23     *   1:45 PM, 5:10 PM, 7:25 PM, 8:10 PM
24 ...
25 "

Using Google’s “Grounding Search” With Citations

This example is also near the bottom of the file gemini.lisp and adds the retrieval and display of citations that consist of web sites searched. You can use the code in the web spidering chapter to fetch the text contents of these reference search results.

Here is the code:

 1 (defun generate-with-search-and-citations (prompt &optional (model-id *model*))
 2   "Generates text with Google Search grounding and returns citations via the Interactions API.
 3    Returns two values: the response text and a list of (title . url) citation pairs."
 4   (let* ((payload (make-hash-table :test 'equal)))
 5     (setf (gethash "model" payload) model-id
 6           (gethash "input" payload) prompt
 7           (gethash "tools" payload)
 8           (list (let ((tool (make-hash-table :test 'equal)))
 9                   (setf (gethash "type" tool) "google_search")
10                   tool)))
11     (let* ((headers (list '("Content-Type" . "application/json")
12                           (cons "x-goog-api-key" *google-api-key*)
13                           '("Api-Revision" . "2026-05-20")))
14            (response-string (%post-json *interactions-api-url* headers payload))
15            (decoded-response (cl-json:decode-json-from-string response-string))
16            (steps (cdr (assoc :STEPS decoded-response)))
17            ;; Extract text from last model_output step
18            (text (loop for step in (reverse steps)
19                        when (string-equal (cdr (assoc :TYPE step)) "model_output")
20                        return (let* ((content (cdr (assoc :CONTENT step)))
21                                      (first-content (first content)))
22                                  (cdr (assoc :TEXT first-content)))))
23            ;; Extract citations from url_citation annotations in model_output steps
24            (citations
25             (loop for step in steps
26                   when (string-equal (cdr (assoc :TYPE step)) "model_output")
27                   append (loop for content-item in (cdr (assoc :CONTENT step))
28                                append (loop for annotation in (cdr (assoc :ANNOTATIONS content-item))
29                                             when (string-equal (cdr (assoc :TYPE annotation)) "url_citation")
30                                             collect (cons (cdr (assoc :TITLE annotation))
31                                                           (cdr (assoc :URL annotation))))))))
32       ;; Return both text and citations
33       (values text citations))))

Here is the sample output (output shortened: redirect URIs shortened for brevity):

 1 * (multiple-value-bind (response sources)
 2     (gemini:generate-with-search-and-citations "Who won the Super Bowl in 2024?")
 3   (format t "Answer: ~a~%~%Sources:~%" response)
 4   (loop for (title . url) in sources
 5         do (format t "- ~a: ~a~%" title url)))
 6 
 7 Answer: The Kansas City Chiefs won Super Bowl LVIII in 2024, defeating the San Francisco 49ers 25-22 in overtime. The game was held in Las Vegas on February 11, 2024. This victory marked the Chiefs' third Super Bowl title in five years and made them the first back-to-back NFL champions in almost 20 years. Patrick Mahomes, the Chiefs' quarterback, was named Super Bowl MVP for the third time.
 8 
 9 Sources:
10 - olympics.com: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUjJ4MF1eTcc...
11 - kcur.org: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUOKyR9rZ...
12 - theguardian.com: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZItCsAVG...
13 - foxsports.com: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYrbCcX...
14 nil
15 * 

Mixing Local Tools with Google Platform Tools Using the Interactions APIs.

The following diagram shows the high-level architecture of the Google Gemini API client library developed in this chapter:

![Architecture diagram](images/gThe Google Interactions APIs provide a clean schema for managing multi-turn conversations and executing client-side tools. You can find more details in the official documentation here: https://ai.google.dev/gemini-api/docs/interactions.

The following Common Lisp implementation in the file gemini_interactions_api.lisp offers a robust framework for multi-turn conversations with tool orchestration. Unlike standard API generation endpoints where the developer has to manually accumulate and format the message history on the client side, the Interactions endpoint maintains the conversation state server-side.

When you call generate-with-tools on “Turn 1,” the API returns the model’s output alongside an interaction-id that references the server-side conversation state. If the model determines it needs to call a client-side function (tool), it returns a list of function calls. You execute those functions locally and pass the results to continue-with-function-responses on “Turn 2” alongside the same interaction-id. The server matches the results to the pending interaction context, allowing it to formulate the final answer.

  1 (in-package #:gemini)
  2 
  3 ;;; ====================================================================
  4 ;;; Gemini Interactions API - Multi-turn conversations with tool use
  5 ;;; ====================================================================
  6 ;;;
  7 ;;; Uses the v1beta Interactions API with the new steps schema.
  8 ;;; Supports multi-turn conversations combining Google Search (built-in)
  9 ;;; and custom function declarations (client-side tool use).
 10 ;;;
 11 ;;; Typical workflow:
 12 ;;;   1. Build function declarations with MAKE-FUNCTION-DECLARATION
 13 ;;;   2. Call GENERATE-WITH-TOOLS for Turn 1 -- returns TEXT, FUNCTION-CALLS, INTERACTION-ID
 14 ;;;   3. Invoke the functions yourself and collect results
 15 ;;;   4. Call CONTINUE-WITH-FUNCTION-RESPONSES for Turn 2 -- returns final TEXT
 16 ;;; ====================================================================
 17 
 18 
 19 ;;; ---- Internal utilities ----
 20 
 21 (defun %make-tools-list (function-declarations &optional google-search-p)
 22   "Builds the tools array for the Interactions API payload.
 23    FUNCTION-DECLARATIONS: list of hash-tables from MAKE-FUNCTION-DECLARATION, or NIL.
 24    GOOGLE-SEARCH-P: when T, includes the built-in Google Search tool."
 25   (let ((tools '()))
 26     (when function-declarations
 27       (loop for fn-decl in function-declarations
 28             do (let ((fn-tool (make-hash-table :test 'equal)))
 29                  (setf (gethash "type" fn-tool) "function"
 30                        (gethash "name" fn-tool) (gethash "name" fn-decl)
 31                        (gethash "description" fn-tool) (gethash "description" fn-decl)
 32                        (gethash "parameters" fn-tool) (gethash "parameters" fn-decl))
 33                  (push fn-tool tools))))
 34     (when google-search-p
 35       (let ((gs-tool (make-hash-table :test 'equal)))
 36         (setf (gethash "type" gs-tool) "google_search")
 37         (push gs-tool tools)))
 38     (nreverse tools)))
 39 
 40 (defun %extract-function-calls-from-steps (steps)
 41   "Returns a list of plists (:NAME :ID :ARGS) for every function_call step.
 42    ARGS is a cl-json decoded alist, e.g. ((:LOCATION . \"Barrow, AK\"))."
 43   (loop for step in steps
 44         when (string-equal (cdr (assoc :TYPE step)) "function_call")
 45         collect (list :name (cdr (assoc :NAME step))
 46                       :id   (cdr (assoc :ID   step))
 47                       :args (cdr (assoc :ARGUMENTS step)))))
 48 
 49 (defun %get-text-from-steps (steps)
 50   "Returns the text from the last model_output step, or NIL."
 51   (loop for step in (reverse steps)
 52         when (string-equal (cdr (assoc :TYPE step)) "model_output")
 53         return (let* ((content (cdr (assoc :CONTENT step)))
 54                       (first-content (first content)))
 55                  (cdr (assoc :TEXT first-content)))))
 56 
 57 
 58 ;;; ---- Public API ----
 59 
 60 (defun make-function-declaration (name description parameters &optional required-params)
 61   "Creates a functionDeclaration hash-table suitable for GENERATE-WITH-TOOLS.
 62 
 63    NAME: string -- the function name the model will invoke
 64    DESCRIPTION: string -- natural-language description of what the function does
 65    PARAMETERS: list of (param-name type description) triples, e.g.:
 66      '((\"location\" \"STRING\" \"The city and state, e.g. San Francisco, CA\"))
 67    REQUIRED-PARAMS: optional list of required parameter name strings, e.g.:
 68      '(\"location\")
 69 
 70    Example:
 71      (make-function-declaration
 72        \"getWeather\"
 73        \"Get the weather in a given location\"
 74        '((\"location\" \"STRING\" \"The city and state, e.g. San Francisco, CA\"))
 75        '(\"location\"))"
 76   (let ((decl-ht  (make-hash-table :test 'equal))
 77         (params-ht (make-hash-table :test 'equal))
 78         (props-ht  (make-hash-table :test 'equal)))
 79     (dolist (param parameters)
 80       (destructuring-bind (pname ptype pdesc) param
 81         (let ((prop-ht (make-hash-table :test 'equal)))
 82           (setf (gethash "type"        prop-ht) ptype
 83                 (gethash "description" prop-ht) pdesc)
 84           (setf (gethash pname props-ht) prop-ht))))
 85     (setf (gethash "type"       params-ht) "OBJECT"
 86           (gethash "properties" params-ht) props-ht)
 87     (when required-params
 88       (setf (gethash "required" params-ht) required-params))
 89     (setf (gethash "name"        decl-ht) name
 90           (gethash "description" decl-ht) description
 91           (gethash "parameters"  decl-ht) params-ht)
 92     decl-ht))
 93 
 94 (defun generate-with-tools (prompt function-declarations
 95                             &key (model-id *model*) google-search-p)
 96   "Turn 1: Send PROMPT to the model with optional tool support via the Interactions API.
 97 
 98    PROMPT: the user's text question.
 99    FUNCTION-DECLARATIONS: list of hash-tables from MAKE-FUNCTION-DECLARATION, or NIL.
100    :GOOGLE-SEARCH-P: when T, enables the built-in Google Search tool.
101    :MODEL-ID: model to use (defaults to *model*).
102 
103    Returns three values:
104      TEXT             - model's text reply, or NIL when it chose to call functions.
105      FUNCTION-CALLS   - list of plists (:NAME :ID :ARGS) for each function call made.
106                         ARGS is a cl-json alist, e.g. ((:LOCATION . \"Barrow, AK\")).
107      INTERACTION-ID   - the interaction ID for use in follow-up turns."
108   (let* ((payload (make-hash-table :test 'equal)))
109     (setf (gethash "model" payload) model-id
110           (gethash "input" payload) prompt
111           (gethash "tools" payload) (%make-tools-list function-declarations google-search-p))
112     (let* ((headers (list '("Content-Type" . "application/json")
113                           (cons "x-goog-api-key" *google-api-key*)
114                           '("Api-Revision" . "2026-05-20")))
115            (response-string  (%post-json *interactions-api-url* headers payload))
116            (decoded-response (cl-json:decode-json-from-string response-string))
117            (steps            (cdr (assoc :STEPS decoded-response)))
118            (interaction-id   (cdr (assoc :ID decoded-response)))
119            (text             (%get-text-from-steps steps))
120            (function-calls   (%extract-function-calls-from-steps steps)))
121       (values text function-calls interaction-id))))
122 
123 (defun continue-with-function-responses (interaction-id
124                                          function-responses function-declarations
125                                          &key (model-id *model*) google-search-p)
126   "Turn 2+: Continue an interaction by supplying function call results.
127 
128    INTERACTION-ID: the interaction ID from GENERATE-WITH-TOOLS.
129    FUNCTION-RESPONSES: list of plists with keys :NAME :ID :RESPONSE, one per function call, e.g.:
130      (list (list :name \"getWeather\" :id \"fc_123\" :response \"Very cold. 22F.\"))
131      The :ID must match the :ID from the corresponding FUNCTION-CALLS entry returned by Turn 1.
132    FUNCTION-DECLARATIONS: same list used in GENERATE-WITH-TOOLS.
133    :GOOGLE-SEARCH-P: whether to include the Google Search tool (match Turn 1 setting).
134    :MODEL-ID: model to use (defaults to *model*).
135 
136    Returns the model's final text response string."
137   (let* ((payload (make-hash-table :test 'equal))
138          (fn-results (mapcar (lambda (fr)
139                                (let ((result-ht (make-hash-table :test 'equal))
140                                      (text-block (make-hash-table :test 'equal)))
141                                  (setf (gethash "type" text-block) "text"
142                                        (gethash "text" text-block) (getf fr :response))
143                                  (setf (gethash "type" result-ht) "function_result"
144                                        (gethash "name" result-ht) (getf fr :name)
145                                        (gethash "call_id" result-ht) (getf fr :id)
146                                        (gethash "result" result-ht) (list text-block))
147                                  result-ht))
148                              function-responses)))
149     (setf (gethash "model" payload) model-id
150           (gethash "previous_interaction_id" payload) interaction-id
151           (gethash "input" payload) fn-results
152           (gethash "tools" payload) (%make-tools-list function-declarations google-search-p))
153     (let* ((headers (list '("Content-Type" . "application/json")
154                           (cons "x-goog-api-key" *google-api-key*)
155                           '("Api-Revision" . "2026-05-20")))
156            (response-string  (%post-json *interactions-api-url* headers payload))
157            (decoded-response (cl-json:decode-json-from-string response-string))
158            (steps            (cdr (assoc :STEPS decoded-response)))
159            (new-interaction-id (cdr (assoc :ID decoded-response)))
160            (text             (%get-text-from-steps steps))
161            (function-calls   (%extract-function-calls-from-steps steps)))
162       (values text function-calls new-interaction-id))))

This implementation is structured around two primary public functions: generate-with-tools and continue-with-function-responses. The first function initiates the dialogue, accepting a natural language prompt and a list of tool definitions created via make-function-declaration. If the model determines that it needs more information than it currently possesses, such as real-time weather data or a specific database query, it returns a list of function calls. The programmer is then responsible for executing these functions locally and passing the outputs to the second function, which targets the same interaction context using the interaction-id to provide the model with the context needed to conclude the interaction.

Under the hood, the helper %make-tools-list builds a tool declaration matching the expected structure of the Interactions API. The helper %extract-function-calls-from-steps extracts the model’s requested function calls and returns them as a structured Lisp property list.

Here is sample code for testing:

 1 ;;; ---- Usage example ----
 2 
 3 ;; 1. Define a custom function the model can request
 4 (defparameter *get-weather-fn*
 5   (gemini:make-function-declaration
 6    "getWeather"
 7    "Get the weather in a given location"
 8    '(("location" "STRING" "The city and state, e.g. San Francisco, CA"))
 9    '("location")))
10 
11 ;; 2. Turn 1 -- send the question with tools enabled
12 (multiple-value-bind (text function-calls interaction-id)
13     (gemini:generate-with-tools
14      "What is the northernmost city in the United States? What's the weather like there today?"
15      (list *get-weather-fn*)
16      :google-search-p t)
17   (format t "Text: ~A~%" text)
18   (format t "Function calls: ~A~%" function-calls)
19 
20   ;; 3. If the model requested function calls, handle them and continue
21   (when function-calls
22     (let* ((fc (first function-calls))
23            ;; In a real application you would call the actual weather API here.
24            ;; The :ARGS plist contains ((:LOCATION . "Barrow, AK")) or similar.
25            (weather-result "Very cold. 22 degrees Fahrenheit.")
26            (fn-responses
27             (list (list :name (getf fc :name)
28                         :id   (getf fc :id)
29                         :response weather-result))))
30 
31       ;; 4. Turn 2 -- provide the function results, get the final answer
32       (let ((final-answer
33              (gemini:continue-with-function-responses
34               interaction-id
35               fn-responses
36               (list *get-weather-fn*)
37               :google-search-p nil)))
38         (format t "~%Final answer: ~A~%" final-answer)))))

The output looks like:

1 Function calls: ((name getWeather id 04abw9d2 args
2                   ((location . Utqiagvik, AK))))
3 
4 Final answer: The northernmost city in the United States is **Utqiaġvik, Alaska** (formerly known as Barrow). 
5 
6 As of today, the weather there is very cold with a temperature of **22°F** (-5.5°C). 
7 
8 Located about 320 miles north of the Arctic Circle, Utqiaġvik is situated on the coast of the Arctic Ocean and experiences extreme conditions, including several weeks of total darkness in the winter and continuous daylight in the summer.
9 nil