This library provides a Common Lisp interface to Google's Gemini Large Language Models.
Clone this repository into your local-projects directory:
git clone https://github.com/jrm-code-project/gemini.git ~/quicklisp/local-projects/geminiThen load with Quicklisp or ASDF as below.
You can install the gemini library using Quicklisp, ASDF, or manually:
Note, this is not part of the Quicklisp distribution, so you must manually install it into your local-projects
(ql:quickload "gemini")(asdf:load-system "gemini")You can obtain an API key from the Google AI Studio.
Put your default project in ~/.config/googleapis/default-project and put your
API key in ~/.config/googleapis/{default-project}/apikey.
or
Put your API key in ~/.config/googleapis/default-api-key
or
Define the GOOGLE_API_KEY environment variable with the value of your API key.
You can also set the API key at runtime:
(setf (getenv "GOOGLE_API_KEY") "your-api-key")Backend selection is configured per persona via that persona's config.lisp.
Preferred backend values are symbolic:
;; Google Gemini REST API
:googleapi :google-api
;; Google Interactions API over SSE
:googleapi :google-interactions-api
;; OpenAI-compatible chat completions
:googleapi :openai-api
:model "gpt-4o-mini"
:url "https://api.openai.com/v1/chat/completions"Notes:
:googleapi :google-apiuses the Gemini REST API flow.:googleapi :google-interactions-apiuses the stateful Interactions API and updates the activeruntime-sessionwith interaction state.:googleapi :openai-apiroutes requests through the OpenAI-compatible chat completions path.:urlis optional for:openai-api. If omitted, the default ishttp://localhost:1234/v1/chat/completions.- Legacy truthy /
nil:googleapivalues still work for compatibility, but new configs should prefer the symbolic values above. - OpenAI-compatible authorization is now resolved in this order:
- Runtime variable
gemini::*openai-authorization*(full header value, e.g."Bearer <token>") - Environment variable
OPENAI_AUTHORIZATION(full header value) - Environment variable
OPENAI_API_KEY(wrapped asBearer <key>) - Optional local fallback
Bearer lm-studiowhengemini::*openai-use-lm-studio-default-authorization*is set to non-NIL
- Runtime variable
Examples:
;; Use explicit runtime header
(setf gemini::*openai-authorization* "Bearer your-token")
;; Or enable explicit LM Studio fallback compatibility mode
(setf gemini::*openai-use-lm-studio-default-authorization* t)This library depends on:
alexandria- Quicklispasdf- Quicklispchanl- Quicklispcl-base64- Quicklispcl-json- Quicklispcl-ppcre- Quicklispdexador- Quicklispfold- jrm-code-projectfunction- jrm-code-projectgoogle- jrm-code-projectjsonx- jrm-code-projectnamed-let- jrm-code-projectpromise- jrm-code-projectseries- Sourceforge (series.sourceforge.net)[https://series.sourceforge.net/]str- Quicklisptrivial-backtrace- Quicklispuiop- Quicklisp
Ensure these have been quickloaded or are available in your Quicklisp local-projects or via ASDF.
If you use Quicklisp, dependencies will be handled automatically. For manual installation, ensure all dependencies are present.
Load the library using Quicklisp or ASDF:
(ql:quickload "gemini")
;; or
(asdf:load-system "gemini")Loading the gemini system no longer starts MCP servers automatically.
- If a persona has
:enable-mcp-tools t, shared MCP servers are started lazily on first MCP-backed use. - The persona-specific memory MCP server is also created lazily rather than during
content-generatorconstruction. - If you want explicit control over process lifetime, call:
(gemini:start-mcp-servers)
;; ... use MCP-backed features ...
(gemini:stop-mcp-servers)To refresh configured servers:
(gemini:restart-mcp-servers)If no ~/.config/mcp/mcp.lisp configuration file is present, these functions simply leave MCP disabled.
Run the full suite from the repository root:
(asdf:test-system "gemini-tests")For a focused suite, load the test system and run the FiveAM suite directly:
(asdf:load-system "gemini-tests")
(fiveam:run! 'gemini-tests::interaction-payload-tests)Focused suites currently include:
gemini-tests::interaction-payload-testsfor payload normalization and serializationgemini-tests::interaction-backend-testsfor Interactions backend invocation and tool translationgemini-tests::interaction-stream-testsfor Interactions and LM Studio stream/event handlinggemini-tests::lmstudio-backend-testsfor LM Studio backend and bridge behaviorgemini-tests::interaction-live-testsfor opt-in live backend coverage
The default gemini-tests run is hermetic. Live backend coverage is isolated in interaction-live-tests and only exercises external services when its environment gates are enabled:
GEMINI_RUN_LIVE_INTERACTIONS_STREAM_TESTenables live Google Interactions coverageGEMINI_RUN_LIVE_LMSTUDIO_STREAM_TESTenables live LM Studio streaming and tool-bridge coverage
The main exported functions are:
gemini:invoke-gemini— Generate text from a prompt using a fresh context.gemini:continue-gemini— Continue a conversation with the Gemini model using the existing context.gemini:invoke-gemini-with-session/gemini:continue-gemini-with-session— Run the same flows against an explicitruntime-sessioninstead of relying on compatibility globals.gemini:new-chat-with-session/gemini:chat-with-session— Session-first chat entry points.gemini:make-runtime-session/gemini:with-runtime-session— Create and install explicit session state for multi-turn or concurrent flows.gemini:invoke-interaction-with-session— Session-first entry point for the Google Interactions backend.gemini:start-mcp-servers/gemini:stop-mcp-servers— Explicitly manage MCP server lifetime when using MCP-backed tools.
See the source for additional utility functions.
To generate text from a prompt:
(gemini:invoke-gemini "In one sentence, explain how AI works to a child.")
;; => "AI is like a super smart computer brain that learns from information to answer questions and do tasks."(gemini:continue-gemini "What happens next?")(gemini:invoke-gemini "Translate to French: Hello!" :model "gemini-2.5-pro")or
(setq gemini:+default-model+ "gemini-2.5-pro")You can configure the API key and default model using environment variables or files as described above. The following environment variables are supported:
GOOGLE_API_KEY— Your API key
- Ensure your API key is valid and accessible.
- Check that all dependencies are loaded.
- For network errors, verify your internet connection and proxy settings.
- For authentication errors, confirm the API key location and permissions.
A unique feature of this library is its dynamic personality system, designed to make interactions more engaging and varied.
By default (*enable-personality* is t), the system will automatically select a new, random personality each day from a predefined list. All responses from the LLM, including code explanations and conversational filler, will be delivered in the voice of that character. Today you might be talking to a pirate; tomorrow, a film noir detective.
While this feature is intended to be fun, you may find yourself needing to change or disable the current personality. This can be controlled via the following functions:
-
Change the Daily Personality: If you are not enjoying the day's randomly selected persona, you can force a new one to be chosen:
(gemini:new-personality)This will randomly select a new personality from the list that will remain active for the rest of the day (or until
new-personalityis called again). -
Temporarily Disable Personality: For a more straightforward, professional interaction, you can temporarily disable the personality system within a specific block of code using the
without-personalitymacro:(gemini:without-personality (gemini:invoke-gemini "Please explain this concept plainly."))
Inside this block, the LLM will respond in a neutral, helpful-assistant tone.
-
Globally Disable Personality: To turn the feature off entirely for your session, you can set the special variable
*enable-personality*tonil:(setq gemini:*enable-personality* nil)
See LICENSE for details. This project is licensed under the MIT License.
This library was initially developed as a personal tool and, as such, contains certain architectural decisions that reflect its original scope. Users and potential contributors should be aware of the following limitations, which are the primary targets for future refactoring and development:
-
Backend Abstraction Is Still In Progress: The implementation now supports per-persona switching between the Google Gemini API and OpenAI-compatible chat-completions backends. However, core conversation logic is still centered on Gemini-style internal objects and not yet a fully provider-agnostic client interface. Some provider-specific behavior (tool-calling semantics, advanced safety controls, and payload feature parity) still requires further normalization.
- Roadmap: Continue refactoring toward a generic backend abstraction layer with a common "LLM client" interface. Provider adapters (Gemini, OpenAI-compatible, and others) should each implement the same contract so core logic can remain backend-neutral.
-
Compatibility Globals Still Exist at the API Boundary: The codebase now has an explicit
runtime-sessionabstraction and session-first entry points, but compatibility wrappers still synchronize legacy globals for older call sites. That means newer code can run isolated, concurrent sessions, while some older helper and app entry points still rely on the compatibility layer.- Roadmap: Continue pushing explicit-session entry points outward and keep shrinking the compatibility boundary so multi-session and nested orchestration flows never need ambient global state.
Contributions and ideas for tackling these architectural improvements are highly encouraged.
Pull requests and issues are welcome! Please follow standard Common Lisp style and include tests for new features. Please contact me if you have questions or need help.
This library uses Dexador for HTTP requests and cl-json for JSON parsing.