Thursday, September 4, 2025

Gemini

I chose to experiment with the Gemini LLM. I figured that Google is likely to remain competitive in AI for a while. I didn't want to be chasing the latest and greatest LLM every few weeks.

Requirements

You will need these prerequisites

You will need a Google Cloud account and a project with the Gemini API enabled. You will need to create an API key.

Put your Gemini API key in ~/.config/googleapis/{project}/Gemini/apikey

Create a directory ~/Gemini/ to hold Gemini related files. In this directory, create subdirectory ~/Gemini/transcripts/ to hold conversation logs.

Usage

Basic Usage

+default-model+
The default Gemini model to use. You can set this to any model supported by your Google Cloud project. It starts as "gemini-2.5-flash".

invoke-gemini prompt
Invoke Gemini with prompt and return the response as a string. An empty context will be used. Use this to start new conversations.

continue-gemini prompt
Invoke Gemini with prompt and return the response as a string. The accumulated context will be used. Use this to continue your conversation.

Conversations will be logged in ~/Gemini/transcripts/ in files named NNNNNNNNNN-NN.txt. The first number being the universal time that the conversation started, the second number being the number of turns in the conversation.

Advanced Usage

Results generation can be controlled via these special variables:

  • *CANDIDATE-COUNT*
  • *ENABLE-ADVANCED-CIVIC-ANSWERS*
  • *FREQUENCY-PENALTY*
  • *INCLUDE-THOUGHTS*
  • *LANGUAGE-CODE*
  • *LOGPROBS*
  • *MAX-OUTPUT-TOKENS*
  • *MEDIA-RESOLUTION*
  • *MULTI-SPEAKER-VOICE-CONFIG*
  • *PREBUILT-VOICE-CONFIG*
  • *PRESENCE-PENALTY*
  • *RESPONSE-JSON-SCHEMA*
  • *RESPONSE-LOGPROBS*
  • *RESPONSE-MIME-TYPE*
  • *RESPONSE-MODALITIES*
  • *RESPONSE-SCHEMA*
  • *SAFETY-SETTINGS*
  • *SEED*
  • *SPEECH-CONFIG*
  • *STOP-SEQUENCES*
  • *SYSTEM-INSTRUCTION*
  • *TEMPERATURE*
  • *THINKING-BUDGET*
  • *THINKING-CONFIG*
  • *TOOL-CONFIG*
  • *TOOLS*
  • *TOP-K*
  • *TOP-P*
  • *VOICE-CONFIG*
  • *VOICE-NAME*

See the Gemini API documentation for details on what these do. If you leave them alone, you will get the default values. I suggest binding *include-thoughts* to T in order to have thoughts printed during processing and *temperature* to a value between 0 (very deterministic) and 2 (very random) if you want something other than the default value of 1. Don't fool around with the other values unless you know what you are doing.

Prompts

The default prompt is just a string. This is so that you can use the LLM as a string to string mapping. However, you can supply a fully structured content object with multiple parts if you wish. For example, you could supply

(content
    (part "Describe this function.")
    (part "```lisp
(defun foo (x) (+ x 3))
```"))

Tools

  • *enable-bash* — enable the shell command tool.
  • *enable-eval* — enable the lisp interpreter tool.
  • *enable-interaction* — allow the LLM to query the user for input.
  • *enable-lisp-introspection — allow the LLM to query if symbols are bound or fbound and to query ASDF and Quicklisp modules and load them.
  • *enable-personality* — give the LLM a pseudorandom personality.
  • *enable-web-functions — allow the LLM to perform HTTP GET and POST commands.
  • *enable-web-search — allow the LLM to perform web searches.

Personality

Unless you disable personality (default is enabled), the LLM will be instructed to respond in a randomly selected literary or celebrity style. This is just for amusement. Some personalities are quite obnoxious. Personalities change every day.

without-personality &body body
disable personality while executing body.

new-personality
Select a new personality at random. Use this if you find the current personality too obnoxious.

personalities.txt personalities are selected from this file. Each line contains a personality description. Blank lines and comments are ignored.

MCP

Currently only stdio MCP servers are supported.

~/.config/mcp/mcp.lisp contains the configurations for any MCP servers you wish to supply to the model. Here is an example file:

((:mcp-servers

  ("memory"
   (:command "npx")
   (:args "-y" "@modelcontextprotocol/server-memory")
   (:env ("MEMORY_FILE_PATH" . "/home/jrm/memory.json"))
   (:system-instruction "You have access to a persistent memory that stores entities and relationships between them.  You can add, update, delete, and query entities and relationships as needed."))

  ("sequential-thinking"
   (:command "npx")
   (:args "-y" "@modelcontextprotocol/server-sequential-thinking")
   (:system-instruction "You can use this tool to break down complex tasks into smaller, manageable steps.  You can create, view, and complete steps as needed."))

  ("mcp-server-time"
   (:command "uvx")
   (:args "mcp-server-time")
   (:system-instruction "You have access to the current date and time along with time zone information."))
  ))

Wednesday, September 3, 2025

Google API

To experiment with LLMs I chose to use Gemini. The cost is modest and I expect Google to regularly improve the model so that it remains competitive. I wrote a Gemini client in Common Lisp. Some people have already forked the repo, but I've refactored things recently so I thought I'd give a quick rundown of the code.

google repository

https://github.com/jrm-code-project/google

When I add APIs to Google, I intend to put them in this repository. It is pretty empty so far, but here is where they will go. Contributions welcome.

Basic Requirements

There are some basic libraries you can load with Zach Beane's Quicklisp. alexandria for some selected Common Lisp extensions, cl-json for JSON parsing, dexador for HTTP requests, and str for string manipulation. I also use series (available from SourceForge). My own fold, function, named-let, and jsonx libraries are also used (available from GitHub).

fold gives you fold-left for list reduction. function gives you compose. jsonx modifies cl-json to remove the ambiguity betweeen JSON objects and nested arrays. named-let gives you the named let syntax from Scheme for local iteration and recursion.

Google API

The google repository contains the beginnings of a Common Lisp client to Google services. It currently only supports an interface to Blogger that can return your blog content as a series of posts and an interface to Google's Custom Search Engine API. You create a Google services account (there is a free tier, and a paid tier if you want more). You create a project (you'll want at least a default project), and within that project you enable the Google services that you want that project to have access to.

API Keys and Config Files

For each service you can get an API key. In your XDG config directory (usually "~/.config/") you create a googleapis directory and subdirectories for each project you have created. The ~/.config/googleapis/default-project file contains the name of your default project. In the examples below, it contains the string my-project.

Within each project directory you create subdirectories for each service you want to use. In each service directory you create a file to hold the API key and possibly other files to hold IDs, options, and other config information. For example, for Blogger you create ~/.config/googleapis/my-project/Blogger/apikey to hold your Blogger API key and ~/.config/googleapis/my-project/Blogger/blog-id to hold your blog ID.

Here is a sample directory structure:

  ~/.config/googleapis/
    ├── my-project/
    │   ├── Blogger/
    │   │   ├── apikey
    │   │   └── blog-id
    │   ├── CustomSearchEngine/
    │   │   ├── apikey
    │   │   ├── hyperspec-id
    │   │   └── id
    │   └── Gemini/
    │       └── apikey
    └── default-project

When you create the API keys for the various services, it is a good idea to restrict the API key for only that service. That way, if the API key is compromised, the damage is limited to a single service.

Blogger

The blogger API will eventually have more features, but it currently only allows you to get the posts from a blog as a series.

scan-blogger-posts blog-id
Returns a series of posts from the blog with the given ID. Each post comes back as a JSON object (hash-table). The :content field contains the HTML content of the post.

Custom Search Engine

The Custom Search Engine API allows you to search a set of web pages. You create a search engine on Google and pass the ID custom-search. It is assumed that the default CustomSearchEngine id is a default, vanilla search, and that the hyperspec id searches the Common Lisp Hyperspec.

In each search, be sure to replace the spaces in the query with plus signs (+) before searching.

custom-search query &key custom-search-engine-id
Returns search results for query. Each search result comes back as a JSON object (hash-table).

web-search query
Search the web and return search results for query. Each search result comes back as a JSON object (hash-table).

hyperspec-search query
Search the Common Lisp Hyperspec and return search results for query. Each search result comes back as a JSON object (hash-table).

Monday, September 1, 2025

Lisp Still Matters

Lisp is not the most popular computer language, yet it continues to attract a small but devoted following. Sure, you can find nutters for any language, but a lot of Lisp programmers are high power expert programmers. Lisp was developed by the world's top researchers and was a language of choice at places like MIT, Stanford, Carnegie-Mellon, Xerox PARC and Bell Labs. It may be a niche, but your companions in the niche are some of the best programmers in the world. What is it about Lisp that attracts the top talent?

Lisp is a powerful language because it can be easily extended to express new ideas that were not foreseen when the language was originally designed. The original designers were well aware that their new computer language couldn't take into account ideas that would be developed in the future, so they made it easy to extend the language beyond its original design. Lisp was based on Church's lambda calculus, which is a logical formalism designed to allow you to reason about logical formalism itself. When you extend the language, you can use the base language to reason about the extension.

The fundamental building block of lambda calculus is the function. In his Conversions of the Lambda Calculi, Church shows how to bootstrap the rest of mathematics from functions. With Lisp, McCarthy showed the isomorphism between s-expressions and lambda expressions, and developed a universal function within Lisp. Thus if you extend Lisp, you can use Lisp to implement and interpret your extension from within Lisp. You don't have to exit the language to extend it.

Lisp syntax, such as it is, is based upon s-expressions, which are tree structures. Lisp allows you to invent your own nodes in the tree and assign your own semantics to them. You can express the new semantics via function calls, which Church showed to be universal. You don't need to hack the parser or the compiler to extend the language, a simple defmacro will extend the syntax and a handful of defuns will assign semantics to the new syntax. If you are so inclined, you can use compiler macros to generate efficient code for your new constructs.

With Lisp, I have the option of expressing the solution to my problems in Lisp, or the option to extend Lisp to express the solution more naturally. I can tailor my problem to be solved in Lisp, or I can tailor Lisp to solve my problem. Or work from both ends to meet in the middle.

It works. Lately I've been using a forty year old dialect of a sixty year old language to interact with cutting edge LLMs. I've not only built an API to the LLM, but I've extended Lisp to include features implemented by the LLM. This was certainly not forseen by McCarthy et al when Lisp was first designed. I decided to use Lisp to explore the integration because I knew that Lisp was amenable to extension. I could have used another language, but I could not have whipped up a prototype or proof of concept by myself over a couple of weekends.

In conclusion, Lisp's enduring appeal to expert programmers lies in its powerful, foundational design. By allowing a programmer to extend the language from within, Lisp provides a unique environment for tackling novel problems and developing new abstractions. The principles of lambda calculus and the flexibility of s-expressions empower developers to not only solve problems but also to build the perfect tool for the job. This capacity to adapt and evolve, as demonstrated by its modern use with LLMs, ensures that Lisp remains a relevant and potent language for those who wish to go beyond the confines of a predefined system.