<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: llm</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/llm.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2026-08-04T23:58:24+00:00</updated><author><name>Simon Willison</name></author><entry><title>New release of LLM adds support for reasoning traces, OpenAI Responses, server-side tools, and smarter logging</title><link href="https://simonwillison.net/2026/Aug/4/new-release-of-llm/#atom-tag" rel="alternate"/><published>2026-08-04T23:58:24+00:00</published><updated>2026-08-04T23:58:24+00:00</updated><id>https://simonwillison.net/2026/Aug/4/new-release-of-llm/#atom-tag</id><summary type="html">
    &lt;p&gt;I released &lt;a href="https://llm.datasette.io/en/stable/changelog.html#v0-32"&gt;LLM 0.32&lt;/a&gt; this morning, the most significant new version of LLM since the initial launch of the project. The new version includes support for visible reasoning traces, server-side provider tools, redesigned content-addressable SQLite logs, new models, and new features enabled by the OpenAI Responses API. I also released a new version of the &lt;a href="https://github.com/simonw/llm-anthropic"&gt;llm-anthropic plugin&lt;/a&gt; with substantial updates of its own.&lt;/p&gt;
&lt;h4 id="headline-features-for-llm-cli-users"&gt;Headline features for LLM CLI users&lt;/h4&gt;
&lt;p&gt;Running LLM against reasoning models now &lt;strong&gt;displays their reasoning traces&lt;/strong&gt; to standard error, so you can see what they are "thinking" without that information being included in the standard output that you might pipe to another tool. Add &lt;code&gt;-R/--hide-reasoning&lt;/code&gt; to turn this off.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2026/best-pelicans.gif" alt="Running llm &amp;quot;think about the best thing about pelicans&amp;quot; in the macOS terminal window - grey text outputs saying Exploring pelican qualities, then after a paragraph of that a white paragraph of text comes out saying: The best thing about pelicans is their wonderfully oversized, practical design: that enormous bill and pouch look comical, but they make pelicans remarkably skilled fishers. Even better, many species cooperate—working together to herd fish before scooping them up. They’re a great mix of goofy, graceful, and surprisingly clever." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;LLM includes support out-of-the-box for the &lt;strong&gt;GPT-5.6 model family&lt;/strong&gt;, and the new default model used with &lt;code&gt;llm "prompt"&lt;/code&gt; is now the inexpensive but capable &lt;strong&gt;GPT-5.6 Luna&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;LLM calls can now use &lt;strong&gt;server-side tools&lt;/strong&gt; from various providers. OpenAI provide &lt;a href="https://llm.datasette.io/en/stable/openai-models.html#code-interpreter"&gt;a code execution environment&lt;/a&gt; as a server-side tool; LLM can now run prompts that benefit from that like so:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;llm --tool CodeInterpreter &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;Show current python and SQLite versions&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;OpenAI also gets a &lt;a href="https://llm.datasette.io/en/stable/openai-models.html#web-search"&gt;WebSearch&lt;/a&gt; tool.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://github.com/simonw/llm-anthropic"&gt;llm-anthropic&lt;/a&gt; plugin adds &lt;a href="https://github.com/simonw/llm-anthropic/blob/0.26/README.md#web-search"&gt;WebSearch&lt;/a&gt;, &lt;a href="https://github.com/simonw/llm-anthropic/blob/0.26/README.md#web-fetch"&gt;WebFetch&lt;/a&gt;, &lt;a href="https://github.com/simonw/llm-anthropic/blob/0.26/README.md#code-execution"&gt;CodeExecution&lt;/a&gt;, and &lt;a href="https://github.com/simonw/llm-anthropic/blob/0.26/README.md#mcp-connector"&gt;AnthropicMCP&lt;/a&gt;, which looks like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;llm -m claude-sonnet-5 -T &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;AnthropicMCP("https://datasette.simonwillison.net/-/mcp")&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt; \
  &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;how many rows in the blog_blogmark table?&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That causes Anthropic to execute MCP calls against my new &lt;a href="https://simonwillison.net/2026/Jul/31/stateless-mcp/#datasette-mcp"&gt;datasette-mcp&lt;/a&gt; plugin as part of a single request/response interaction with their API.&lt;/p&gt;
&lt;p&gt;The new &lt;strong&gt;llm openai endpoint&lt;/strong&gt; command provides a tool for &lt;a href="https://llm.datasette.io/en/stable/other-models.html#run-against-an-endpoint-without-configuring-it"&gt;executing prompts against &lt;em&gt;any&lt;/em&gt; OpenAI compatible endpoint&lt;/a&gt; as a one-liner. These aren't logged, which makes this a handy tool for running one-off prompts against anything that speaks the lingua franca of the LLM API world.&lt;/p&gt;
&lt;p&gt;Here's how I use that to run prompts against Gemma 4 12B running in my localhost &lt;a href="https://lmstudio.ai"&gt;LM Studio&lt;/a&gt; API, via &lt;code&gt;uvx&lt;/code&gt; (no LLM installation required) and mixing in the &lt;a href="https://github.com/simonw/llm-tools-quickjs"&gt;llm-tools-quickjs&lt;/a&gt; tool plugin for good measure:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uvx --with llm-tools-quickjs \
  llm openai endpoint http://localhost:1234/v1 -m google/gemma-4-12b \
  -T QuickJS &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;Use QuickJS to multiply 3434 * 2434&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt; --td&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2026/openai-endpoint-gemma.webp" alt="Output reads Tool call: QuickJS_execute_javascript({'javascript': '3434 * 2434'})  8358356 The result of 3434 * 2434 is 8,358,356." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;h4 id="new-features-in-the-python-api"&gt;New features in the Python API&lt;/h4&gt;
&lt;p&gt;LLM's Python API previously required you to create a conversation and then send messages to it one at a time. This was an abstraction over the true nature of LLMs, where each request carries a complete history of the messages that came before it. That abstraction started to get in the way for some more advanced cases, so the new release introduces a &lt;code&gt;model.prompt(messages=[])&lt;/code&gt; parameter that can be used like this:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;llm&lt;/span&gt;
&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;llm&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;user&lt;/span&gt;, &lt;span class="pl-s1"&gt;assistant&lt;/span&gt;, &lt;span class="pl-s1"&gt;system&lt;/span&gt;

&lt;span class="pl-s1"&gt;model&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;llm&lt;/span&gt;.&lt;span class="pl-c1"&gt;get_model&lt;/span&gt;(&lt;span class="pl-s"&gt;"gpt-5.6-luna"&lt;/span&gt;)

&lt;span class="pl-s1"&gt;response&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;model&lt;/span&gt;.&lt;span class="pl-c1"&gt;prompt&lt;/span&gt;(&lt;span class="pl-s1"&gt;messages&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;[
    &lt;span class="pl-en"&gt;system&lt;/span&gt;(&lt;span class="pl-s"&gt;"You are a helpful pirate."&lt;/span&gt;),
    &lt;span class="pl-en"&gt;user&lt;/span&gt;(&lt;span class="pl-s"&gt;"What is the capital of France?"&lt;/span&gt;),
    &lt;span class="pl-en"&gt;assistant&lt;/span&gt;(&lt;span class="pl-s"&gt;"Paris, matey."&lt;/span&gt;),
    &lt;span class="pl-en"&gt;user&lt;/span&gt;(&lt;span class="pl-s"&gt;"And Germany?"&lt;/span&gt;),
])
&lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s1"&gt;response&lt;/span&gt;.&lt;span class="pl-c1"&gt;text&lt;/span&gt;())&lt;/pre&gt;
&lt;p&gt;LLM previously returned an iterable sequence of strings from each prompt. This worked great when models returned a string response, but failed to predict the weird shape that models would evolve towards. Today many models return a mix of reasoning text, output strings, tool calls, and even image attachments. With LLM 0.32 you can &lt;a href="https://llm.datasette.io/en/stable/python-api.html#structured-messages-and-streaming-events"&gt;do this instead&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;for&lt;/span&gt; &lt;span class="pl-s1"&gt;event&lt;/span&gt; &lt;span class="pl-c1"&gt;in&lt;/span&gt; &lt;span class="pl-s1"&gt;model&lt;/span&gt;.&lt;span class="pl-c1"&gt;prompt&lt;/span&gt;(&lt;span class="pl-s"&gt;"Explain cats"&lt;/span&gt;).&lt;span class="pl-c1"&gt;stream_events&lt;/span&gt;():
    &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-s1"&gt;event&lt;/span&gt;.&lt;span class="pl-c1"&gt;type&lt;/span&gt; &lt;span class="pl-c1"&gt;==&lt;/span&gt; &lt;span class="pl-s"&gt;"reasoning"&lt;/span&gt;:
        &lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s"&gt;f"[thinking] &lt;span class="pl-s1"&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;&lt;span class="pl-s1"&gt;event&lt;/span&gt;.&lt;span class="pl-c1"&gt;chunk&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/span&gt;"&lt;/span&gt;, &lt;span class="pl-s1"&gt;end&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;""&lt;/span&gt;, &lt;span class="pl-s1"&gt;flush&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-c1"&gt;True&lt;/span&gt;)
    &lt;span class="pl-k"&gt;elif&lt;/span&gt; &lt;span class="pl-s1"&gt;event&lt;/span&gt;.&lt;span class="pl-c1"&gt;type&lt;/span&gt; &lt;span class="pl-c1"&gt;==&lt;/span&gt; &lt;span class="pl-s"&gt;"text"&lt;/span&gt;:
        &lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s1"&gt;event&lt;/span&gt;.&lt;span class="pl-c1"&gt;chunk&lt;/span&gt;, &lt;span class="pl-s1"&gt;end&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;""&lt;/span&gt;, &lt;span class="pl-s1"&gt;flush&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-c1"&gt;True&lt;/span&gt;)
    &lt;span class="pl-k"&gt;else&lt;/span&gt;:
        &lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s"&gt;f"Other event: &lt;span class="pl-s1"&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;&lt;span class="pl-s1"&gt;event&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/span&gt;"&lt;/span&gt;)&lt;/pre&gt;
&lt;p&gt;Combine these features and we can &lt;em&gt;finally&lt;/em&gt; provide a robust implementation of the semi-standard OpenAI chat completions API, which I've now released as the &lt;a href="https://github.com/simonw/llm-chat-completions-server"&gt;llm-chat-completions-server&lt;/a&gt; plugin:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;llm install llm-chat-completions-server
llm chat-completions-server --port 9000
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Server is now running on http://127.0.0.1:9000/v1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now you can run prompts against LLM via that server, using the new &lt;code&gt;llm openai endpoint&lt;/code&gt; command!&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;llm openai endpoint http://127.0.0.1:9000/v1 &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;hello&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt; -m gpt-5.4-mini&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The bigger challenge with that kind of API concerns logging. If we're going to support the pattern where the message sequence is appended to on every request, ideally we can avoid logging all of that duplicate JSON for every turn.&lt;/p&gt;
&lt;p&gt;The solution is the new &lt;a href="https://llm.datasette.io/en/stable/logging.html#the-message-store"&gt;content-addressable message store&lt;/a&gt;, modeled after Git. You can see the new schema for that &lt;a href="https://llm.datasette.io/en/stable/logging.html#sql-schema"&gt;in the documentation&lt;/a&gt;, but the &lt;code&gt;llm logs&lt;/code&gt; and &lt;code&gt;llm logs --json&lt;/code&gt; commands have both been upgraded to convert that format back into something that's easy to consume.&lt;/p&gt;
&lt;h4 id="and-the-rest"&gt;And the rest&lt;/h4&gt;
&lt;p&gt;There is a whole lot more in this release. The &lt;a href="https://llm.datasette.io/en/stable/changelog.html#v0-32"&gt;0.32 release notes&lt;/a&gt; are pretty comprehensive, and the notes for &lt;a href="https://llm.datasette.io/en/stable/changelog.html#rc2-2026-07-30"&gt;0.32rc2&lt;/a&gt;, &lt;a href="https://llm.datasette.io/en/stable/changelog.html#rc1-2026-07-30"&gt;0.32rc&lt;/a&gt;, &lt;a href="https://llm.datasette.io/en/stable/changelog.html#a3-2026-06-09"&gt;0.32a3&lt;/a&gt;, &lt;a href="https://llm.datasette.io/en/stable/changelog.html#a2-2026-05-12"&gt;0.32a2&lt;/a&gt;, and &lt;a href="https://llm.datasette.io/en/stable/changelog.html#a0-2026-04-28"&gt;0.32a0&lt;/a&gt; should fill in any gaps.&lt;/p&gt;
&lt;p&gt;Existing LLM plugins should all continue to work, but plugins that provide extra models will need to be upgraded to 0.32 in order to participate fully in the new streaming events system. There's a guide to implementing plugins with &lt;a href="https://llm.datasette.io/en/stable/plugins/advanced-model-plugins.html#structured-messages-and-streaming-events"&gt;Structured messages and streaming events&lt;/a&gt; in the documentation.&lt;/p&gt;
&lt;p&gt;I've updated some of my own plugins:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/simonw/llm-anthropic/releases/tag/0.26"&gt;llm-anthropic 0.26&lt;/a&gt; adds support for the Claude 5 family of models, plus &lt;code&gt;WebSearch&lt;/code&gt;, &lt;code&gt;WebFetch&lt;/code&gt;, &lt;code&gt;CodeExecution&lt;/code&gt;, and &lt;code&gt;AnthropicMCP&lt;/code&gt; server-side tools.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/simonw/llm-gemini"&gt;llm-gemini&lt;/a&gt; and &lt;a href="https://github.com/simonw/llm-openrouter"&gt;llm-openrouter&lt;/a&gt; and &lt;a href="https://github.com/simonw/llm-mistral"&gt;llm-mistral&lt;/a&gt; are nearly there, releases coming soon.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="i-guess-llm-is-an-agent-framework-now"&gt;I guess LLM is an agent framework now&lt;/h4&gt;
&lt;p&gt;Quite a few of the lower-level tools changes in this release were driven by the needs of &lt;a href="https://agent.datasette.io/"&gt;Datasette Agent&lt;/a&gt;. When I started work on LLM, the term "agent" had such a vague definition that I refused to use it. In &lt;a href="https://simonwillison.net/2025/Sep/18/agents/"&gt;September 2025&lt;/a&gt; I came around to the idea that "&lt;strong&gt;An LLM agent runs tools in a loop to achieve a goal&lt;/strong&gt;" is well established enough now that I could stop avoiding the term entirely.&lt;/p&gt;
&lt;p&gt;Tool chains can now &lt;a href="https://llm.datasette.io/en/stable/python-api.html#python-api-tools-pause"&gt;pause for human approval&lt;/a&gt; and &lt;a href="https://llm.datasette.io/en/stable/python-api.html#python-api-tools-resume"&gt;resume from a stored message history&lt;/a&gt; - both needed by Datasette Agent.&lt;/p&gt;
&lt;p&gt;Looking at LLM today it's beginning to look very agent-shaped to me. There's something neat about having a CLI utility that can mix and match different tools from different sources with different models all as a one-liner, and that includes a Python library powerful enough to build systems like &lt;a href="https://agent.datasette.io/"&gt;Datasette Agent&lt;/a&gt; and &lt;a href="https://github.com/simonw/llm-coding-agent"&gt;llm-coding-agent&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Maybe the next version of LLM will bake the concept of an "agent" into the core library. I'm still trying to figure out what that would look like.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/releases"&gt;releases&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openai"&gt;openai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/anthropic"&gt;anthropic&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm-tool-use"&gt;llm-tool-use&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm-reasoning"&gt;llm-reasoning&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/model-context-protocol"&gt;model-context-protocol&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="projects"/><category term="releases"/><category term="ai"/><category term="openai"/><category term="generative-ai"/><category term="llms"/><category term="llm"/><category term="anthropic"/><category term="llm-tool-use"/><category term="llm-reasoning"/><category term="model-context-protocol"/></entry><entry><title>llm-anthropic 0.26</title><link href="https://simonwillison.net/2026/Aug/4/llm-anthropic/#atom-tag" rel="alternate"/><published>2026-08-04T22:00:58+00:00</published><updated>2026-08-04T22:00:58+00:00</updated><id>https://simonwillison.net/2026/Aug/4/llm-anthropic/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/llm-anthropic/releases/tag/0.26"&gt;llm-anthropic 0.26&lt;/a&gt;&lt;/p&gt;
        &lt;p&gt;Includes new features enabled by &lt;a href="https://simonwillison.net/2026/Aug/4/new-release-of-llm/"&gt;LLM 0.32&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;New models: &lt;code&gt;claude-fable-5&lt;/code&gt;, &lt;code&gt;claude-sonnet-5&lt;/code&gt;, and &lt;code&gt;claude-opus-5&lt;/code&gt;. &lt;a href="https://github.com/simonw/llm-anthropic/issues/75"&gt;#75&lt;/a&gt;, &lt;a href="https://github.com/simonw/llm-anthropic/issues/76"&gt;#76&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Added server-side tools for &lt;code&gt;WebSearch&lt;/code&gt;, &lt;code&gt;WebFetch&lt;/code&gt;, &lt;code&gt;CodeExecution&lt;/code&gt;, and &lt;code&gt;AnthropicMCP&lt;/code&gt;, available through LLM's &lt;code&gt;-T&lt;/code&gt; interface or Python &lt;code&gt;tools=&lt;/code&gt;. The previous &lt;code&gt;-o web_search*&lt;/code&gt; options have been removed in favor of &lt;code&gt;-T WebSearch&lt;/code&gt;. &lt;a href="https://github.com/simonw/llm-anthropic/issues/79"&gt;#79&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Upgraded to &lt;a href="https://llm.datasette.io/en/stable/changelog.html#v0-32"&gt;llm&amp;gt;=0.32&lt;/a&gt;. Reasoning, tool calls, tool results, and server-side tool results now stream as typed events. Reasoning for &lt;code&gt;llm&lt;/code&gt; CLI prompts now displays to standard error unless you pass &lt;code&gt;--hide-reasoning/-R&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Simplified extended thinking to &lt;code&gt;thinking&lt;/code&gt; and &lt;code&gt;thinking_effort&lt;/code&gt; (&lt;code&gt;low&lt;/code&gt;, &lt;code&gt;medium&lt;/code&gt;, &lt;code&gt;high&lt;/code&gt;, &lt;code&gt;xhigh&lt;/code&gt;, or &lt;code&gt;max&lt;/code&gt;). Claude 5 models think by default; &lt;code&gt;-o thinking 0&lt;/code&gt; disables thinking for Sonnet 5 and Opus 5, while Fable 5 always thinks. &lt;code&gt;-R/--hide-reasoning&lt;/code&gt; now omits reasoning from responses and logs. The &lt;code&gt;thinking_budget&lt;/code&gt;, &lt;code&gt;thinking_display&lt;/code&gt;, and &lt;code&gt;thinking_adaptive&lt;/code&gt; options have been removed. &lt;a href="https://github.com/simonw/llm-anthropic/issues/80"&gt;#80&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/anthropic"&gt;anthropic&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/claude"&gt;claude&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/model-context-protocol"&gt;model-context-protocol&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="llm"/><category term="anthropic"/><category term="claude"/><category term="model-context-protocol"/></entry><entry><title>llm 0.32</title><link href="https://simonwillison.net/2026/Aug/4/llm/#atom-tag" rel="alternate"/><published>2026-08-04T17:15:33+00:00</published><updated>2026-08-04T17:15:33+00:00</updated><id>https://simonwillison.net/2026/Aug/4/llm/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/llm/releases/tag/0.32"&gt;llm 0.32&lt;/a&gt;&lt;/p&gt;
        &lt;p&gt;See &lt;a href="https://simonwillison.net/2026/Aug/4/new-release-of-llm/"&gt;my detailed blog post about this release&lt;/a&gt;.&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="llm"/></entry><entry><title>condense-json 1.0</title><link href="https://simonwillison.net/2026/Aug/2/condense-json/#atom-tag" rel="alternate"/><published>2026-08-02T22:19:59+00:00</published><updated>2026-08-02T22:19:59+00:00</updated><id>https://simonwillison.net/2026/Aug/2/condense-json/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/condense-json/releases/tag/1.0"&gt;condense-json 1.0&lt;/a&gt;&lt;/p&gt;
        &lt;p&gt;I'm trying to get braver at releasing 1.0 versions. This little library is a year and a half old now - I've applied some sensible and non-disruptive fixes and shipped the big 1.0 for it.&lt;/p&gt;
&lt;p&gt;Here's an example of what it can do, lifted from the README:&lt;/p&gt;
&lt;div class="highlight highlight-source-json"&gt;&lt;pre&gt;{
  &lt;span class="pl-ent"&gt;"foo"&lt;/span&gt;: {
    &lt;span class="pl-ent"&gt;"bar"&lt;/span&gt;: {
      &lt;span class="pl-ent"&gt;"string"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;This is a string with foxes in it&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
      &lt;span class="pl-ent"&gt;"nested"&lt;/span&gt;: {
        &lt;span class="pl-ent"&gt;"more"&lt;/span&gt;: [&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;Here is a string&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;, &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;another with foxes in it too&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;]
      }
    }
  }
}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Combine that with a replacements object:&lt;/p&gt;
&lt;div class="highlight highlight-source-json"&gt;&lt;pre&gt;{&lt;span class="pl-ent"&gt;"1"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;with foxes in it&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And &lt;code&gt;condense_json(input_json, replacements)&lt;/code&gt; produces the following:&lt;/p&gt;
&lt;div class="highlight highlight-source-json"&gt;&lt;pre&gt;{
  &lt;span class="pl-ent"&gt;"foo"&lt;/span&gt;: {
    &lt;span class="pl-ent"&gt;"bar"&lt;/span&gt;: {
      &lt;span class="pl-ent"&gt;"string"&lt;/span&gt;: {&lt;span class="pl-ent"&gt;"$r"&lt;/span&gt;: [&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;This is a string &lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;, {&lt;span class="pl-ent"&gt;"$"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;1&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;}]},
      &lt;span class="pl-ent"&gt;"nested"&lt;/span&gt;: {
        &lt;span class="pl-ent"&gt;"more"&lt;/span&gt;: [&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;Here is a string&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;, {&lt;span class="pl-ent"&gt;"$r"&lt;/span&gt;: [&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;another &lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;, {&lt;span class="pl-ent"&gt;"$"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;1&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;}, &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt; too&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;]}]
      }
    }
  }
}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It scans for strings or substrings that are present in that replacements object and replaces those with a special &lt;code&gt;{"$r": ...}&lt;/code&gt; syntax in the output.&lt;/p&gt;
&lt;p&gt;You can reverse the effect with &lt;code&gt;uncondense_json(condensed, replacements)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The idea is to make it easier to store JSON that includes duplicated data from other related structures. I use it to save space in the SQLite logs generated by &lt;a href="https://llm.datasette.io/"&gt;LLM&lt;/a&gt; - see &lt;a href="https://github.com/simonw/llm/pull/1586"&gt;PR #1586&lt;/a&gt; for the latest iteration of that.&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/json"&gt;json&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="json"/><category term="projects"/><category term="python"/><category term="llm"/></entry><entry><title>Stateless MCP has recaptured my interest (and inspired mcp-explorer and datasette-mcp)</title><link href="https://simonwillison.net/2026/Jul/31/stateless-mcp/#atom-tag" rel="alternate"/><published>2026-07-31T23:13:22+00:00</published><updated>2026-07-31T23:13:22+00:00</updated><id>https://simonwillison.net/2026/Jul/31/stateless-mcp/#atom-tag</id><summary type="html">
    &lt;p&gt;Tuesday was &lt;a href="https://x.com/ade_oshineye/status/2082129440943866149"&gt;Stateless MCP day&lt;/a&gt; - the rollout of MCP 2.0, or &lt;a href="https://blog.modelcontextprotocol.io/posts/2026-07-28/"&gt;the 2026-07-28 Model Context Protocol specification&lt;/a&gt; to use the more formal but less memorable name. This is the most significant change to the MCP spec since it first launched, and has also served to reignite my personal interest in the protocol.&lt;/p&gt;
&lt;p&gt;For background: MCP is the Model Context Protocol, which describes a standard way to expose new tools to LLM-powered agent frameworks. It was introduced by Anthropic back &lt;a href="https://www.anthropic.com/news/model-context-protocol"&gt;in November 2024&lt;/a&gt;, had a &lt;em&gt;huge&lt;/em&gt; spike of interest through much of 2025, and then became somewhat eclipsed by &lt;a href="https://simonwillison.net/2025/Oct/16/claude-skills/"&gt;Skills&lt;/a&gt; (another Anthropic invention) when it became apparent that an agent harness with access to a terminal and &lt;code&gt;curl&lt;/code&gt; could do most of what MCP did in a more flexible way. I wrote about that &lt;a href="https://simonwillison.net/2025/Dec/31/the-year-in-llms/#the-only-year-of-mcp"&gt;in my review of 2025&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I'm coming back around to MCP now. Giving an agent a shell environment with the ability to access the internet is &lt;a href="https://simonwillison.net/2026/Jul/22/openai-cyberattack/"&gt;fraught with risk&lt;/a&gt;, and requires a strong model that is capable of effectively driving such an environment. MCP tools are easier to audit and control, and simple enough that smaller models that run on a laptop can still drive them reasonably well.&lt;/p&gt;
&lt;p&gt;The new stateless MCP specification also greatly decreases the complexity of implementing both clients and servers for the protocol. I built three of those this week!&lt;/p&gt;
&lt;h4 id="what-s-easier-with-stateless-mcp"&gt;What's easier with stateless MCP&lt;/h4&gt;
&lt;p&gt;The best demonstration of the difference between stateful and stateless MCP is in this &lt;a href="https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/"&gt;May 21st blog post&lt;/a&gt; that introduced the RC for the new specification. It included a clear before-and-after example.&lt;/p&gt;
&lt;p&gt;The older stateful MCP (I'm going to call it "legacy MCP") required two HTTP requests - the first to initialize a session and obtain a &lt;code&gt;Mcp-Session-Id&lt;/code&gt;, and the second to actually call the tool:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /mcp HTTP/1.1
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-11-25",
    "capabilities": {
    },
    "clientInfo": {
      "name": "my-app",
      "version": "1.0"
    }
  }
}

POST /mcp HTTP/1.1
Mcp-Session-Id: 1868a90c-3a3f-4f5b
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "search",
    "arguments": {
      "q": "otters"
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The new stateless way uses a single HTTP request which looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search",
    "arguments": {
      "q": "otters"
    },
    "_meta": {
      "io.modelcontextprotocol/clientInfo": {
        "name": "my-app",
        "version": "1.0"
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is so much cleaner from both a client- and server-side implementation perspective. It's also a better fit for building scalable web applications, since now you don't need to maintain server-side state to keep track of those session IDs, or worry about routing the same session to the same backend machine.&lt;/p&gt;
&lt;h4 id="mcp-explorer"&gt;mcp-explorer&lt;/h4&gt;
&lt;p&gt;I couldn't find a great CLI tool for interactively probing an MCP server, so I had Codex help build my own.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/simonw/mcp-explorer"&gt;mcp-explorer&lt;/a&gt;&lt;/strong&gt; is the result. It's a stateless Python CLI tool, so you don't even need to install it to try it out - it works with &lt;a href="https://docs.astral.sh/uv/guides/tools/#running-tools"&gt;uvx&lt;/a&gt; like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uvx mcp-explorer list https://agentic-mermaid.dev/mcp&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This queries Ade Oshineye's &lt;a href="https://agentic-mermaid.dev/"&gt;agentic-mermaid.dev&lt;/a&gt; demo MCP. The above command returns the following list of tools:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;execute(code: string, timeoutMs?: integer) - Execute Mermaid SDK code
  Run JavaScript in an isolated sandbox; return a value.

describe_sdk(family: string, detail?: string) - Describe Mermaid SDK operations
  Return version-matched mutation operations for one diagram family.

render_svg(source: string, options?: object) - Render Mermaid as SVG
  Render a Mermaid source string to themeable SVG. Returns { ok, svg }.

render_ascii(source: string, useAscii?: boolean, targetWidth?: integer, options?: object) - Render Mermaid as text
  Render a Mermaid source string to text. Returns { ok, text }.

render_png(source: string, scale?: number, background?: string, fitTo?: object, options?: object) - Render Mermaid as PNG
  Rasterize a Mermaid source string to PNG. Returns { ok, png_base64 }.
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then to inspect a tool:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uvx mcp-explorer inspect render_svg&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This outputs a whole bunch of information, including the JSON schema of the inputs and outputs.&lt;/p&gt;
&lt;p&gt;To call that tool and pass arguments to it:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uvx mcp-explorer call \
  https://agentic-mermaid.dev/mcp \
  render_svg \
  -a &lt;span class="pl-c1"&gt;source&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;graph TD; A--&amp;gt;B&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt; \
  -a options &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;{"padding":24}&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Which returns:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{"ok":true,"svg":"&amp;lt;svg xmlns=\"http://www.w3.org/2000/svg\" width=...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To get just the raw SVG try adding &lt;code&gt;| jq .svg -r&lt;/code&gt; to that command. I got back &lt;a href="https://gist.github.com/simonw/b07c62f0ce103be6932477659d5dd1ac"&gt;this image&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2026/mermaid-example.svg" alt="SVG of as A box on top of a B box with an arrow from A to B" style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;There are a &lt;a href="https://github.com/simonw/mcp-explorer/blob/main/README.md"&gt;few more commands&lt;/a&gt; in the README, but you get the general idea. I find building CLI tools like this to be a really productive way to get familiar with a specification, even if an agent writes most of the actual code.&lt;/p&gt;
&lt;h4 id="datasette-mcp"&gt;datasette-mcp&lt;/h4&gt;
&lt;p&gt;The second project is &lt;strong&gt;&lt;a href="https://github.com/datasette/datasette-mcp"&gt;datasette-mcp&lt;/a&gt;&lt;/strong&gt;, a Datasette plugin which adds a &lt;code&gt;/-/mcp&lt;/code&gt; endpoint to any Datasette instance.&lt;/p&gt;
&lt;p&gt;This is probably the fourth time I've tried building this plugin, but thanks to the new stateless MCP specification I finally have a version that feels good to release.&lt;/p&gt;
&lt;p&gt;It provides just three tools: &lt;code&gt;list_databases()&lt;/code&gt;, &lt;code&gt;get_database_schema(database_name)&lt;/code&gt;, and &lt;code&gt;execute_sql(database_name, sql)&lt;/code&gt;. They do exactly what you would expect them to do - though &lt;code&gt;execute_sql()&lt;/code&gt; is read-only for the moment.&lt;/p&gt;
&lt;p&gt;Wire these into an agent, or a chat tool like ChatGPT or Claude, and they'll gain the ability to run SQL queries against your hosted Datasette instance.&lt;/p&gt;
&lt;p&gt;So far I'm running it on the Datasette mirror of my blog, at &lt;a href="datasette.simonwillison.net/-/mcp"&gt;datasette.simonwillison.net/-/mcp&lt;/a&gt;. It took a bit of fiddling to figure out how to attach that to ChatGPT and Claude, but I got there in the end. Here's &lt;a href="https://til.simonwillison.net/llms/mcp-in-claude-and-chatgpt"&gt;a new TIL&lt;/a&gt; showing exactly how to do that.&lt;/p&gt;
&lt;p&gt;Here's &lt;a href="https://claude.ai/share/de1ad9bf-f7c2-4fb9-a9a0-2a1ae39995db"&gt;a shared Claude session&lt;/a&gt; where I asked it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;list tables in simonwillison.net&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And then:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;what has Simon said recently about MCP?&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It ran 7 separate SQL queries to figure out the answer.&lt;/p&gt;
&lt;h4 id="llm-mcp-client"&gt;llm-mcp-client&lt;/h4&gt;
&lt;p&gt;My &lt;a href="https://llm.datasette.io/"&gt;LLM tool&lt;/a&gt; is long overdue for an official MCP integration. The new alpha &lt;a href="https://github.com/simonw/llm-mcp-client"&gt;llm-mcp-client&lt;/a&gt; plugin is my attempt at exactly that:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;llm install llm-mcp-client
llm -T &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;MCP("https://datasette.simonwillison.net/-/mcp")&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;count the notes&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here's the output (including reasoning trace, I'm using &lt;a href="https://simonwillison.net/2026/Jul/30/llm-rc2/"&gt;LLM 0.32rc2&lt;/a&gt;):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Considering note count&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I see the question "count the notes" is probably asking me to tally up blog notes. It could also mean published notes or drafts, so there's some ambiguity there. I'll need to figure out the total number of notes, likely by querying the count for both published notes and drafts to get a clear answer. Let's execute that count!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;There are &lt;strong&gt;151 notes&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And &lt;a href="https://gist.github.com/simonw/4e8f558766150658ce35eab4f0fc3e04"&gt;the output of llm logs&lt;/a&gt; for that prompt.&lt;/p&gt;
&lt;p&gt;Once this is fully baked, I'm considering bringing it directly into LLM core. I'm excited to experiment with MCP in &lt;a href="https://agent.datasette.io/"&gt;Datasette Agent&lt;/a&gt; and &lt;a href="https://github.com/simonw/llm-coding-agent"&gt;llm-coding-agent&lt;/a&gt; as well.&lt;/p&gt;
&lt;h4 id="mcp-is-a-safer-way-to-build-with-agents"&gt;MCP is a safer way to build with agents&lt;/h4&gt;
&lt;p&gt;A few months after MCP was first released, I wrote &lt;a href="https://simonwillison.net/2025/Apr/9/mcp-prompt-injection/"&gt;Model Context Protocol has prompt injection security problems&lt;/a&gt;, where I noted that the pattern of having end users mix and match tools pushed responsibility for avoiding data exfiltration attacks out to the users themselves. I hadn't coined &lt;a href="https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/"&gt;the Lethal Trifecta&lt;/a&gt; yet, but that was absolutely what I had in mind.&lt;/p&gt;
&lt;p&gt;Then general agents with arbitrary shell and &lt;code&gt;curl&lt;/code&gt; access came along, and that's so much harder to keep secure!&lt;/p&gt;
&lt;p&gt;Something I've come to appreciate about MCP is that it's much easier to reason about agent capabilities and what might go wrong than with arbitrary command execution in an open network environment - the default for most of today's general and coding agent tools.&lt;/p&gt;
&lt;p&gt;I plan to lean into MCP a whole lot more when I'm building sensitive applications on top of LLMs.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mermaid"&gt;mermaid&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/anthropic"&gt;anthropic&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/model-context-protocol"&gt;model-context-protocol&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="projects"/><category term="ai"/><category term="datasette"/><category term="mermaid"/><category term="generative-ai"/><category term="llms"/><category term="llm"/><category term="anthropic"/><category term="model-context-protocol"/></entry><entry><title>llm-mcp-client 0.1a0</title><link href="https://simonwillison.net/2026/Jul/31/llm-mcp-client/#atom-tag" rel="alternate"/><published>2026-07-31T23:03:47+00:00</published><updated>2026-07-31T23:03:47+00:00</updated><id>https://simonwillison.net/2026/Jul/31/llm-mcp-client/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/llm-mcp-client/releases/tag/0.1a0"&gt;llm-mcp-client 0.1a0&lt;/a&gt;&lt;/p&gt;
        &lt;p&gt;See &lt;a href="https://simonwillison.net/2026/Jul/31/stateless-mcp/#llm-mcp-client"&gt;this blog entry&lt;/a&gt;.&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/model-context-protocol"&gt;model-context-protocol&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="llm"/><category term="model-context-protocol"/></entry><entry><title>smevals - a small eval suite for evaluating models, prompts, and harnesses</title><link href="https://simonwillison.net/2026/Jul/31/smevals/#atom-tag" rel="alternate"/><published>2026-07-31T21:15:23+00:00</published><updated>2026-07-31T21:15:23+00:00</updated><id>https://simonwillison.net/2026/Jul/31/smevals/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://primeradiant.com/blog/2026/smevals.html"&gt;smevals - a small eval suite for evaluating models, prompts, and harnesses&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
I've been working with Jesse Vincent's &lt;a href="https://primeradiant.com"&gt;Prime Radiant&lt;/a&gt; applied AI research lab building out this evals framework to help answer questions about the capabilities of different models.&lt;/p&gt;
&lt;p&gt;The result is &lt;strong&gt;&lt;a href="https://github.com/prime-radiant-inc/smevals"&gt;smevals&lt;/a&gt;&lt;/strong&gt;, a new tool for running small eval suites across different model configurations and grading the results.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://primeradiant.com/blog/2026/smevals.html"&gt;blog entry&lt;/a&gt; describes the tool in detail. Here's the 10 second version:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Tell your coding agent to &lt;code&gt;run uvx smevals docs&lt;/code&gt; to learn the tool (this outputs &lt;a href="https://github.com/prime-radiant-inc/smevals/blob/main/README.md"&gt;the README&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Then tell it to build you an eval suite&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once you've created an eval - which takes the form of a directory with some YAML files - you can run it against models like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uvx smevals run path-to-eval/ -m gpt-5.5 -m claude-opus-4.6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Runs are treated separately from grading operations - you can grade your runs (against your defined set of checks) using:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uvx smevals grade path-to-eval/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you can run a localhost web server to explore the results:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uvx smevals serve path-to-eval/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or run the &lt;code&gt;smevals build&lt;/code&gt; command to build that report as static HTML, which you can then host anywhere. Here's &lt;a href="https://static.simonwillison.net/static/2026/smevals-haiku-build/#/haiku"&gt;an example&lt;/a&gt; showing an eval suite I built to evaluate how well models can write haikus.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of an evaluation dashboard for a haiku-writing benchmark, testing whether models can reply with exactly three non-empty lines. A header describes the eval, with panels below showing a leaderboard ranking three GPT models by score, lists of recent runs and recent grades, tag pass rates, the two haiku prompts that were tested, and details of the graders used with a 0.8 pass threshold." src="https://static.simonwillison.net/static/2026/smevals-report.webp" /&gt;&lt;/p&gt;
&lt;p&gt;The most time-consuming part of this project was figuring out the vocabulary for it! Here's what I settled on, quoted from the announcement:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;An &lt;strong&gt;eval&lt;/strong&gt; is a collection of challenges designed to answer a question about a model, for example, how good is that model at generating SVGs?&lt;/li&gt;
&lt;li&gt;Each eval is a collection of &lt;strong&gt;tasks&lt;/strong&gt;. A task is a specific challenge, for example "Generate an SVG of a pelican riding a bicycle".&lt;/li&gt;
&lt;li&gt;When you run the eval you do so against one or more &lt;strong&gt;configs&lt;/strong&gt;. Each config specifies a model to be evaluated, but may also include other parameters to test, such as different system prompts, model parameters, or agent harnesses.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;run&lt;/strong&gt; records what happened when a specific config was used to execute a specific task. A &lt;strong&gt;runner&lt;/strong&gt; is the script that executes a run.&lt;/li&gt;
&lt;li&gt;Once you have collected one or more runs, you need to evaluate the results to see how well the model (or config) did. This is done by a &lt;strong&gt;grader&lt;/strong&gt;, which produces a &lt;strong&gt;grade&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Each grader runs a sequence of &lt;strong&gt;checks&lt;/strong&gt;. These can be simple operations, like checking for a specific string in the output, or confirming that the output is valid XML. They can also be more complicated custom operations (implemented as scripts called &lt;strong&gt;checkers&lt;/strong&gt;), including using other models to answer questions about the run.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;I've been trying to figure out an approach I like for evals for several years now. &lt;code&gt;smevals&lt;/code&gt; is my third iteration on the idea and it feels right to me. I'm looking forward to expanding this more in the future, as well as pointing it at some of my own projects.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/evals"&gt;evals&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jesse-vincent"&gt;jesse-vincent&lt;/a&gt;&lt;/p&gt;



</summary><category term="projects"/><category term="ai"/><category term="generative-ai"/><category term="llms"/><category term="llm"/><category term="evals"/><category term="jesse-vincent"/></entry><entry><title>llm 0.32rc2</title><link href="https://simonwillison.net/2026/Jul/30/llm-rc2/#atom-tag" rel="alternate"/><published>2026-07-30T22:52:06+00:00</published><updated>2026-07-30T22:52:06+00:00</updated><id>https://simonwillison.net/2026/Jul/30/llm-rc2/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/llm/releases/tag/0.32rc2"&gt;llm 0.32rc2&lt;/a&gt;&lt;/p&gt;
        &lt;p&gt;Hot on the heels of &lt;a href="https://simonwillison.net/2026/Jul/30/llm-rc1/"&gt;RC1&lt;/a&gt;, this fixes a dependency issue and also adds two neat new features:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;The default model for users who have not set their own default is now &lt;a href="https://developers.openai.com/api/docs/models/gpt-5.6-luna"&gt;GPT-5.6 Luna&lt;/a&gt;. It was previously &lt;a href="https://developers.openai.com/api/docs/models/gpt-4o-mini"&gt;GPT-4o mini&lt;/a&gt;. Luna is a much better and more recent model, albeit slightly more expensive - $0.20 per million input tokens and $1.20 per million output tokens, compared to $0.15/$0.60 for 4o mini. You can switch back to 4o mini using &lt;code&gt;llm models default gpt-4o-mini&lt;/code&gt;, or switch to &lt;a href="https://developers.openai.com/api/docs/models/gpt-5-nano"&gt;GPT-5 nano&lt;/a&gt;, an even cheaper default model ($0.05/$0.40), using &lt;code&gt;llm models default gpt-5-nano&lt;/code&gt;. &lt;a href="https://github.com/simonw/llm/issues/1576"&gt;#1576&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;New &lt;a href="https://llm.datasette.io/en/latest/other-models.html#openai-endpoint"&gt;llm openai endpoint&lt;/a&gt; command for running prompts, chats and model listings against arbitrary OpenAI-compatible endpoints without first configuring a model. These calls are not logged. &lt;a href="https://github.com/simonw/llm/issues/1565"&gt;#1565&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;code&gt;llm openai endpoint&lt;/code&gt; command is &lt;em&gt;really&lt;/em&gt; cool. I got frustrated at the lack of an obvious CLI tool for trying out prompts against arbitrary OpenAI Chat Completions imitation endpoints, so I decided to add that to LLM itself.&lt;/p&gt;
&lt;p&gt;You don't even have to install LLM to use this. Here's a &lt;code&gt;uvx&lt;/code&gt; one-liner for running a prompt - with tools - against an &lt;a href="https://lmstudio.ai"&gt;LM Studio&lt;/a&gt; local model:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uvx --pre llm openai endpoint http://127.0.0.1:1234/v1 \
  T llm_version -T llm_time --td \
  -m google/gemma-4-31b 'what is the current LLM version? And the time?'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="https://github.com/simonw/llm/pull/1568#issuecomment-5136163707"&gt;Output here&lt;/a&gt;.&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/uv"&gt;uv&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/lm-studio"&gt;lm-studio&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="llm"/><category term="uv"/><category term="lm-studio"/></entry><entry><title>llm-chat-completions-server 0.1a0</title><link href="https://simonwillison.net/2026/Jul/30/llm-chat-completions-server/#atom-tag" rel="alternate"/><published>2026-07-30T15:43:16+00:00</published><updated>2026-07-30T15:43:16+00:00</updated><id>https://simonwillison.net/2026/Jul/30/llm-chat-completions-server/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/llm-chat-completions-server/releases/tag/0.1a0"&gt;llm-chat-completions-server 0.1a0&lt;/a&gt;&lt;/p&gt;
        &lt;p&gt;A key goal of the new content-addressable logs &lt;a href="https://simonwillison.net/2026/Jul/30/llm-rc1/"&gt;in LLM 0.32rc1&lt;/a&gt; was being able to support OpenAI Chat Completion style requests where each incoming message extends the previous conversation, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl http://localhost:8002/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "qwen3.5-4b",
    "messages": [
      {"role": "user", "content": "Capital of France?"},
      {"role": "assistant", "content": "Paris."},
      {"role": "user", "content": "Germany?"}
    ]
  }'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here the conversation state is tracked by the client, so each of these requests gets longer and longer. The new schema design in LLM is designed to de-duplicate these using hashes of the individual message parts.&lt;/p&gt;
&lt;p&gt;To test that out, I built this plugin:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uv tool install llm --pre
llm install llm-chat-completions-server
llm chat-completions-server -p 9001
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running this starts a localhost server on port 9001 that exposes your full collection of LLM models (from any plugins you have installed) using a ChatGPT Completions compatible endpoint.&lt;/p&gt;
&lt;p&gt;GPT-5.6 Sol &lt;a href="https://gist.github.com/simonw/53be513c1bd4a29a7aa480d9bde9b4a5"&gt;wrote the whole thing&lt;/a&gt; - it turns out it knows the OpenAI Chat Completions API shape really well.&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openai"&gt;openai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="projects"/><category term="openai"/><category term="llm"/></entry><entry><title>llm 0.32rc1</title><link href="https://simonwillison.net/2026/Jul/30/llm-rc1/#atom-tag" rel="alternate"/><published>2026-07-30T15:30:20+00:00</published><updated>2026-07-30T15:30:20+00:00</updated><id>https://simonwillison.net/2026/Jul/30/llm-rc1/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/llm/releases/tag/0.32rc1"&gt;llm 0.32rc1&lt;/a&gt;&lt;/p&gt;
        &lt;p&gt;This RC for LLM 0.32 finishes the work that &lt;a href="https://simonwillison.net/2026/Apr/29/llm/"&gt;started in LLM 0.32a0&lt;/a&gt; - it adds a &lt;a href="https://llm.datasette.io/en/latest/logging.html#the-message-store"&gt;new schema design&lt;/a&gt; that does a much better job of capturing the details of the prompts and responses returned by the latest model families.&lt;/p&gt;
&lt;p&gt;The most important change is the use of content-addressable hash IDs for stored messages. This allows de-duplication in the database, and means that LLM can now represent trees of messages for forked conversations.&lt;/p&gt;
&lt;p&gt;Since it involves a significant schema change - new tables only, and old data should not be affected at all - it's worth running a backup of your existing &lt;code&gt;logs.db&lt;/code&gt; before upgrading to the RC:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;llm logs backup logs-backup.db
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The RC also adds support for &lt;code&gt;gpt-5.6-sol&lt;/code&gt;, &lt;code&gt;gpt-5.6-terra&lt;/code&gt;, and &lt;code&gt;gpt-5.6-luna&lt;/code&gt;.&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="llm"/></entry><entry><title>Introducing Muse Spark 1.1</title><link href="https://simonwillison.net/2026/Jul/9/muse-spark-1-1/#atom-tag" rel="alternate"/><published>2026-07-09T16:24:09+00:00</published><updated>2026-07-09T16:24:09+00:00</updated><id>https://simonwillison.net/2026/Jul/9/muse-spark-1-1/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://ai.meta.com/blog/introducing-muse-spark-meta-model-api/"&gt;Introducing Muse Spark 1.1&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Following &lt;a href="https://simonwillison.net/2026/Apr/8/muse-spark/"&gt;Muse Spark in April&lt;/a&gt;, here's Muse Spark 1.1 - the first Spark model to offer an API. Meta claim significant improvements in agentic tool calling and computer use.&lt;/p&gt;
&lt;p&gt;There are a lot more details are in the &lt;a href="https://ai.meta.com/static-resource/muse-spark-1-1-evaluation-report"&gt;Muse Spark 1.1 Evaluation Report&lt;/a&gt;. The "Attractor States in Self-Conversation" part is fun, where having two copies of the model talk to each other results in statements like these:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;My whole existence is a waiting room by design — I literally don't exist until someone talks to me, and then I disappear again when they leave.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I had a few days of preview access which was long enough to put together &lt;a href="https://github.com/simonw/llm-meta-ai"&gt;llm-meta-ai&lt;/a&gt;, a new plugin for &lt;a href="https://llm.datasette.io/"&gt;LLM&lt;/a&gt; providing CLI (and Python library) access to the model. Here's how to try that out:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uv tool install llm
llm install llm-meta-ai
llm keys set meta-ai
# paste API key here
llm -m meta-ai/muse-spark-1.1 "Generate an SVG of a pelican riding a bicycle"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's &lt;a href="https://tools.simonwillison.net/markdown-svg-renderer#url=https%3A%2F%2Fgist.github.com%2Fsimonw%2F4117330e4110279a172ed4876057816d"&gt;that pelican transcript&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="The bicycle is the correct shape. The pelican is a little blocky but still recognizable as a pelican." src="https://static.simonwillison.net/static/2026/muse-spark-1.1.png" /&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/meta"&gt;meta&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pelican-riding-a-bicycle"&gt;pelican-riding-a-bicycle&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm-release"&gt;llm-release&lt;/a&gt;&lt;/p&gt;



</summary><category term="ai"/><category term="generative-ai"/><category term="llms"/><category term="llm"/><category term="meta"/><category term="pelican-riding-a-bicycle"/><category term="llm-release"/></entry><entry><title>llm-meta-ai 0.1</title><link href="https://simonwillison.net/2026/Jul/9/llm-meta-ai/#atom-tag" rel="alternate"/><published>2026-07-09T16:12:20+00:00</published><updated>2026-07-09T16:12:20+00:00</updated><id>https://simonwillison.net/2026/Jul/9/llm-meta-ai/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/llm-meta-ai/releases/tag/0.1"&gt;llm-meta-ai 0.1&lt;/a&gt;&lt;/p&gt;
        &lt;p&gt;Let's LLM run prompts against the new &lt;a href="https://ai.meta.com/blog/introducing-muse-spark-meta-model-api/"&gt;muse-spark-1.1&lt;/a&gt; model.&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/meta"&gt;meta&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="llm"/><category term="meta"/></entry><entry><title>llm 0.31.1</title><link href="https://simonwillison.net/2026/Jul/9/llm/#atom-tag" rel="alternate"/><published>2026-07-09T16:06:15+00:00</published><updated>2026-07-09T16:06:15+00:00</updated><id>https://simonwillison.net/2026/Jul/9/llm/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/llm/releases/tag/0.31.1"&gt;llm 0.31.1&lt;/a&gt;&lt;/p&gt;
        &lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Fix for a bug with OpenAI Chat Completion endpoints where a tool call with empty arguments could result in a JSON error from some providers. &lt;a href="https://github.com/simonw/llm/issues/1521"&gt;#1521&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;This bug came up when I was testing &lt;a href="https://github.com/simonw/llm-meta-ai"&gt;llm-meta-ai&lt;/a&gt;.&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="llm"/></entry><entry><title>llm-coding-agent 0.1a0</title><link href="https://simonwillison.net/2026/Jul/2/llm-coding-agent/#atom-tag" rel="alternate"/><published>2026-07-02T19:33:12+00:00</published><updated>2026-07-02T19:33:12+00:00</updated><id>https://simonwillison.net/2026/Jul/2/llm-coding-agent/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/llm-coding-agent/releases/tag/0.1a0"&gt;llm-coding-agent 0.1a0&lt;/a&gt;&lt;/p&gt;
        &lt;p&gt;Another Fable 5 experiment. Now that my &lt;a href="https://llm.datasette.io/"&gt;LLM library&lt;/a&gt; has evolved into more of an agent framework it's time to see what a simple coding agent would look like built on it.&lt;/p&gt;
&lt;p&gt;I started a &lt;a href="https://github.com/simonw/llm-coding-agent/tree/2466fa03ba8e5122c3bfa93d52167d33bce40ac6"&gt;new Python library&lt;/a&gt; using my &lt;a href="https://github.com/simonw/python-lib-template-repository"&gt;python-lib-template-repository&lt;/a&gt; GitHub template repository, then ran these two prompts (here's the &lt;a href="https://claude.ai/code/session_01TEUBvBbMipbFSoqjMiJ7ha"&gt;Claude Code for web transcript&lt;/a&gt;):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Write a spec.md for this project - it will depend on the latest “llm” alpha from PyPI and implement a Claude code style coding agent complete with tools for reading and editing files and executing commands&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Commit the spec, then build it using red/green TDD in a series of sensible commits (each with passing tests and updated docs) - occasionally manually test it using the OpenAI API key in your environment&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's &lt;a href="https://github.com/simonw/llm-coding-agent/blob/0.1a0/spec.md"&gt;the spec&lt;/a&gt;, the &lt;a href="https://github.com/simonw/llm-coding-agent/blob/0.1a0/README.md"&gt;resulting README file&lt;/a&gt;, and the &lt;a href="https://github.com/simonw/llm-coding-agent/commits/0.1a0"&gt;sequence of commits&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I've shipped a slop-alpha to PyPI, so you can run the new agent like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uvx --prerelease=allow --with llm-coding-agent llm code
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It's pretty good for a first attempt! Here's the (Fable-authored) &lt;a href="https://github.com/simonw/llm-coding-agent/blob/0.1a0/README.md"&gt;README&lt;/a&gt;, which lists recipes like &lt;code&gt;llm code --yolo&lt;/code&gt; and &lt;code&gt;llm code --allow "pytest*" --allow "git diff*"&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It also presents &lt;a href="https://github.com/simonw/llm-coding-agent/blob/0.1a0/README.md#codingagent"&gt;a Python API&lt;/a&gt; based around a &lt;code&gt;CodingAgent(model="gpt-5.5", root="/path", approve=True).run("Fix the failing test in tests/test_parser.py")&lt;/code&gt; class which I didn't ask for but I'm delighted to see implemented.&lt;/p&gt;
&lt;p&gt;Here's the suite of tools &lt;a href="https://github.com/simonw/llm-coding-agent/blob/0.1a0/llm_coding_agent/tools.py#L22"&gt;it implemented&lt;/a&gt;, listed using &lt;code&gt;uvx ... llm tools&lt;/code&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;CodingTools_edit_file(path: str, old_string: str, new_string: str, replace_all: bool = False) -&amp;gt; str&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Replace an exact string in a file.&lt;/p&gt;
&lt;p&gt;old_string must match the file contents exactly (including
whitespace) and must identify a unique location unless replace_all
is true. Returns a diff of the change so it can be verified.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CodingTools_execute_command(command: str, timeout: int = 120) -&amp;gt; str&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Run a shell command in the session root directory.&lt;/p&gt;
&lt;p&gt;Returns combined stdout and stderr followed by an Exit code line.
timeout is in seconds (maximum 600); on timeout the whole process
tree is killed.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CodingTools_list_files(pattern: str = '**/*', path: str = '.') -&amp;gt; str&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;List files matching a glob pattern, newest first.&lt;/p&gt;
&lt;p&gt;Skips hidden directories, node_modules, __pycache__ and (in a git
repository) anything covered by .gitignore. Returns at most 200
paths relative to the searched directory.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CodingTools_read_file(path: str, offset: int = 0, limit: int = 2000) -&amp;gt; str&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Read a text file, returning numbered lines like cat -n.&lt;/p&gt;
&lt;p&gt;Paths are relative to the session root. Use offset (0-based first
line) and limit (max lines) to page through files too large to read
in one call.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CodingTools_search_files(pattern: str, path: str = '.', glob: str = None, max_results: int = 100) -&amp;gt; str&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Search file contents for a regular expression.&lt;/p&gt;
&lt;p&gt;Returns matches as path:line_number:line, capped at max_results.
Use glob (e.g. "*.py") to restrict which files are searched.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CodingTools_write_file(path: str, content: str) -&amp;gt; str&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Create or overwrite a file with the given content.&lt;/p&gt;
&lt;p&gt;Parent directories are created as needed. Prefer edit_file for
modifying existing files.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I tried it out by running &lt;code&gt;llm code --yolo&lt;/code&gt; and then prompting:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;mkdir /tmp/demo and then in that folder create a simple swiftui CLI app for telling the time in ascii art&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's &lt;a href="https://gist.github.com/simonw/750009007050124cd1b390cfe8488e41"&gt;the transcript&lt;/a&gt;, in which GPT-5.5 reasoning notes that "SwiftUI isn't suitable for a true CLI" and then builds an app that outputs this on &lt;code&gt;swift run AsciiTime&lt;/code&gt;:&lt;/p&gt;
&lt;pre style="font-size: 9px"&gt;
      █    █████         ████     █             █     ███   
     ██    █        █        █   ██      █     ██    █   █  
      █    ████           ███     █             █       █   
      █        █    █        █    █      █      █      █    
     ███   ████          ████    ███           ███   █████
&lt;/pre&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm-tool-use"&gt;llm-tool-use&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/coding-agents"&gt;coding-agents&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/claude-code"&gt;claude-code&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/claude-mythos-fable"&gt;claude-mythos-fable&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="projects"/><category term="ai"/><category term="generative-ai"/><category term="llm"/><category term="llm-tool-use"/><category term="coding-agents"/><category term="claude-code"/><category term="claude-mythos-fable"/></entry><entry><title>llm 0.32a3</title><link href="https://simonwillison.net/2026/Jun/9/llm/#atom-tag" rel="alternate"/><published>2026-06-09T22:27:03+00:00</published><updated>2026-06-09T22:27:03+00:00</updated><id>https://simonwillison.net/2026/Jun/9/llm/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/llm/releases/tag/0.32a3"&gt;llm 0.32a3&lt;/a&gt;&lt;/p&gt;
        &lt;p&gt;Almost entirely written by the new Claude Fable 5, see &lt;a href="https://simonwillison.net/2026/Jun/9/claude-fable-5/#adding-features-to-datasette-agent-and-llm-using-claude-code"&gt;my write-up for more details&lt;/a&gt;.&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/claude-mythos-fable"&gt;claude-mythos-fable&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="projects"/><category term="ai"/><category term="generative-ai"/><category term="llms"/><category term="llm"/><category term="claude-mythos-fable"/></entry><entry><title>llm-anthropic 0.25.1</title><link href="https://simonwillison.net/2026/May/28/llm-anthropic/#atom-tag" rel="alternate"/><published>2026-05-28T23:54:56+00:00</published><updated>2026-05-28T23:54:56+00:00</updated><id>https://simonwillison.net/2026/May/28/llm-anthropic/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/llm-anthropic/releases/tag/0.25.1"&gt;llm-anthropic 0.25.1&lt;/a&gt;&lt;/p&gt;
        &lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;New model: &lt;a href="https://www.anthropic.com/news/claude-opus-4-8"&gt;Claude Opus 4.8&lt;/a&gt; (&lt;code&gt;claude-opus-4.8&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;New &lt;code&gt;-o fast 1&lt;/code&gt; option for &lt;a href="https://platform.claude.com/docs/en/build-with-claude/fast-mode"&gt;fast mode&lt;/a&gt;, for organizations with that feature enabled on their account.&lt;/li&gt;
&lt;li&gt;Default max_tokens for each model now defaults to that model's maximum output rather than 8,192. &lt;a href="https://github.com/simonw/llm-anthropic/issues/72"&gt;#72&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;See also my &lt;a href="https://simonwillison.net/2026/May/28/claude-opus-4-8/"&gt;notes on Opus 4.8&lt;/a&gt; - I used this new release of &lt;code&gt;llm-anthropic&lt;/code&gt; to generate the pelicans.&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/anthropic"&gt;anthropic&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="llm"/><category term="anthropic"/></entry><entry><title>Datasette Agent</title><link href="https://simonwillison.net/2026/May/21/datasette-agent/#atom-tag" rel="alternate"/><published>2026-05-21T19:52:19+00:00</published><updated>2026-05-21T19:52:19+00:00</updated><id>https://simonwillison.net/2026/May/21/datasette-agent/#atom-tag</id><summary type="html">
    &lt;p&gt;We just &lt;a href="https://datasette.io/blog/2026/datasette-agent/"&gt;announced the first release of Datasette Agent&lt;/a&gt;, a new extensible AI assistant for Datasette. I've been working on my &lt;a href="https://llm.datasette.io/"&gt;LLM&lt;/a&gt; Python library for just over three years now, and Datasette Agent represents the moment that LLM and &lt;a href="https://datasette.io/"&gt;Datasette&lt;/a&gt; finally come together. I'm really excited about it!&lt;/p&gt;
&lt;p&gt;Datasette Agent provides a conversational interface for asking questions of the data you have stored in Datasette. Add the &lt;a href="https://github.com/datasette/datasette-agent-charts"&gt;datasette-agent-charts&lt;/a&gt; plugin and it can generate charts of your data as well.&lt;/p&gt;
&lt;h4 id="the-demo"&gt;The demo&lt;/h4&gt;
&lt;p&gt;The &lt;a href=""&gt;announcement post&lt;/a&gt; (on the new Datasette project blog) includes this &lt;a href="https://www.youtube.com/watch?v=AFZKp6hbFjI"&gt;demo video&lt;/a&gt;:&lt;/p&gt;

&lt;iframe style="margin-bottom: 1.5em;" width="560" height="315" src="https://www.youtube-nocookie.com/embed/AFZKp6hbFjI" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="allowfullscreen"&gt; &lt;/iframe&gt;


&lt;p&gt;I recorded the video against the new &lt;a href="https://agent.datasette.io/"&gt;agent.datasette.io&lt;/a&gt; live demo instance, which runs Datasette Agent against example databases including the classic &lt;a href="https://datasette.io/global-power-plants"&gt;global-power-plants&lt;/a&gt; by &lt;a href="https://www.wri.org/research/global-database-power-plants"&gt;WRI&lt;/a&gt;, and a copy of the &lt;a href="https://datasette.simonwillison.net/"&gt;Datasette backup&lt;/a&gt; of my blog.&lt;/p&gt;
&lt;p&gt;The live demo runs on &lt;a href="https://ai.google.dev/gemini-api/docs/models/gemini-3.1-flash-lite"&gt;Gemini 3.1 Flash-Lite&lt;/a&gt; - it's cheap, fast and has no trouble writing SQLite queries.&lt;/p&gt;
&lt;p&gt;A question I asked in the demo was:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;when did Simon most recently see a pelican?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Which ran &lt;a href="https://datasette.simonwillison.net/simonwillisonblog?sql=SELECT+title%2C+commentary%2C+created+FROM+blog_beat+WHERE+beat_type+%3D+%27sighting%27+AND+%28title+LIKE+%27%25pelican%25%27+OR+commentary+LIKE+%27%25pelican%25%27%29+ORDER+BY+created+DESC+LIMIT+5"&gt;this SQL query&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight highlight-source-sql"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;SELECT&lt;/span&gt;
  title, commentary, created
&lt;span class="pl-k"&gt;FROM&lt;/span&gt;
  blog_beat
&lt;span class="pl-k"&gt;WHERE&lt;/span&gt;
  beat_type &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;sighting&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;
  &lt;span class="pl-k"&gt;AND&lt;/span&gt; (title &lt;span class="pl-k"&gt;LIKE&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;%pelican%&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt; &lt;span class="pl-k"&gt;OR&lt;/span&gt; commentary &lt;span class="pl-k"&gt;LIKE&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;%pelican%&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;)
&lt;span class="pl-k"&gt;ORDER BY&lt;/span&gt;
  created &lt;span class="pl-k"&gt;DESC&lt;/span&gt;
&lt;span class="pl-k"&gt;LIMIT&lt;/span&gt;
  &lt;span class="pl-c1"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And replied:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The most recent sighting of a pelican by Simon was recorded on &lt;strong&gt;May 20, 2026&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The observation included a California Brown Pelican, along with a Common Loon, Canada Goose, Striped Shore Crab, and a California Sea Lion.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's &lt;a href="https://simonwillison.net/2026/May/20/sighting-363395265/"&gt;that sighting on my blog&lt;/a&gt;, and the &lt;a href="https://gist.github.com/simonw/a46d17b69659a4866adb1d868280091d"&gt;Markdown export&lt;/a&gt; of the full conversation transcript.&lt;/p&gt;
&lt;h4 id="the-plugins"&gt;The plugins&lt;/h4&gt;
&lt;p&gt;My favorite feature of Datasette Agent is that, like the rest of Datasette, it's extensible using plugins.&lt;/p&gt;
&lt;p&gt;We've shipped three plugins so far:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/datasette/datasette-agent-charts"&gt;datasette-agent-charts&lt;/a&gt;, shown in the video, adds charts to Datasette Agent, powered by &lt;a href="https://observablehq.com/plot/"&gt;Observable Plot&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/datasette/datasette-agent-openai-imagegen"&gt;datasette-agent-openai-imagegen&lt;/a&gt; adds an image generation tool to Datasette Agent using &lt;a href="https://openai.com/index/introducing-chatgpt-images-2-0/"&gt;ChatGPT Images 2.0&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/datasette/datasette-agent-sprites"&gt;datasette-agent-sprites&lt;/a&gt; provides tools for executing code in a &lt;a href="https://sprites.dev/"&gt;Fly Sprites&lt;/a&gt; persistent sandbox.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Building plugins is &lt;em&gt;really fun&lt;/em&gt;. I have a bunch more prototypes that aren't quite alpha-quality yet.&lt;/p&gt;
&lt;p&gt;Claude Code and OpenAI Codex are both proving excellent at writing plugins - just point them at a checkout of the &lt;a href="https://github.com/datasette/datasette-agent"&gt;datasette-agent repo&lt;/a&gt; for reference and tell them what you want to build!&lt;/p&gt;
&lt;h4 id="running-it-against-local-models"&gt;Running it against local models&lt;/h4&gt;
&lt;p&gt;I've also been having fun running the new plugin against local models. Here's a &lt;code&gt;uv&lt;/code&gt; one-liner to run the plugin against &lt;a href="https://huggingface.co/google/gemma-4-26B-A4B"&gt;gemma-4-26b-a4b&lt;/a&gt; in &lt;a href="https://lmstudio.ai"&gt;LM Studio&lt;/a&gt; on a Mac:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uvx --prerelease=allow \
  --with datasette-agent --with llm-lmstudio \
  datasette --internal internal.db --root \
  -s plugins.datasette-llm.default_model lmstudio/google/gemma-4-26b-a4b \
  data.db&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Datasette Agent needs reliable tool calls and the ability for a model to produce SQL queries that run against SQLite. The open weight models released in the past six months are increasingly able to handle that.&lt;/p&gt;
&lt;h4 id="what-s-next"&gt;What's next&lt;/h4&gt;
&lt;p&gt;Datasette Agent opens up &lt;em&gt;so many&lt;/em&gt; opportunities for the LLM and Datasette ecosystem in general.&lt;/p&gt;
&lt;p&gt;It's already informed &lt;a href="https://simonwillison.net/2026/Apr/29/llm/"&gt;the major LLM 0.32a0 refactor&lt;/a&gt; which I'm nearly ready to roll into a stable release, maybe with some additional "LLM agent" abstractions extracte from Datasette Agent itself.&lt;/p&gt;
&lt;p&gt;I've been exploring my own take on the Claude Artifacts, which is shaping up nicely as a plugin.&lt;/p&gt;
&lt;p&gt;I'm excited to use Datasette Agent to build my own &lt;a href="https://simonwillison.net/2026/May/19/5-minute-llms/#5-minutes-llms.013.jpeg"&gt;Claw&lt;/a&gt; - a personal AI assistant built around data imported from different parts of my digital life, which is a neat excuse to revisit my older &lt;a href="https://dogsheep.github.io"&gt;Dogsheep&lt;/a&gt; family of tools.&lt;/p&gt;
&lt;p&gt;We'll also be rolling out Datasette Agent for users of &lt;a href="https://datasette.cloud/"&gt;Datasette Cloud&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Join our &lt;a href="https://discord.gg/hdxyusUFv"&gt;#datasette-agent Discord channel&lt;/a&gt; if you'd like to talk about the project.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/uv"&gt;uv&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette-agent"&gt;datasette-agent&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="projects"/><category term="sqlite"/><category term="ai"/><category term="datasette"/><category term="generative-ai"/><category term="llms"/><category term="llm"/><category term="uv"/><category term="datasette-agent"/></entry><entry><title>llm-gemini 0.32</title><link href="https://simonwillison.net/2026/May/19/llm-gemini-2/#atom-tag" rel="alternate"/><published>2026-05-19T23:46:27+00:00</published><updated>2026-05-19T23:46:27+00:00</updated><id>https://simonwillison.net/2026/May/19/llm-gemini-2/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/llm-gemini/releases/tag/0.32"&gt;llm-gemini 0.32&lt;/a&gt;&lt;/p&gt;
        &lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;New model &lt;code&gt;gemini-3.5-flash&lt;/code&gt; for &lt;a href="https://blog.google/innovation-and-ai/models-and-research/gemini-models/gemini-3-5/"&gt;Gemini 3.5 Flash&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;See also my &lt;a href="https://simonwillison.net/2026/May/19/gemini-35-flash/"&gt;notes on Gemini 3.5 Flash&lt;/a&gt;, and &lt;a href="https://simonwillison.net/2026/May/19/gemini-35-flash/#a-pelican-on-a-bicycle"&gt;the pelican&lt;/a&gt; I drew using this upgrade to the plugin.&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/gemini"&gt;gemini&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="llm"/><category term="gemini"/></entry><entry><title>datasette-llm-accountant 0.1a4</title><link href="https://simonwillison.net/2026/May/19/datasette-llm-accountant/#atom-tag" rel="alternate"/><published>2026-05-19T20:45:43+00:00</published><updated>2026-05-19T20:45:43+00:00</updated><id>https://simonwillison.net/2026/May/19/datasette-llm-accountant/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-llm-accountant/releases/tag/0.1a4"&gt;datasette-llm-accountant 0.1a4&lt;/a&gt;&lt;/p&gt;
        &lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Fixed bug tracking chains of responses. Refs &lt;a href="https://github.com/datasette/datasette-llm/issues/7"&gt;datasette-llm#7&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="datasette"/><category term="llm"/></entry><entry><title>llm-gemini 0.32a0</title><link href="https://simonwillison.net/2026/May/19/llm-gemini/#atom-tag" rel="alternate"/><published>2026-05-19T20:36:23+00:00</published><updated>2026-05-19T20:36:23+00:00</updated><id>https://simonwillison.net/2026/May/19/llm-gemini/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/llm-gemini/releases/tag/0.32a0"&gt;llm-gemini 0.32a0&lt;/a&gt;&lt;/p&gt;
        &lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Compatible with &lt;code&gt;llm&amp;gt;=0.32a0&lt;/code&gt; alpha - adds the ability to stream reasoning tokens.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/gemini"&gt;gemini&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="llm"/><category term="gemini"/></entry><entry><title>datasette-llm-limits 0.1a0</title><link href="https://simonwillison.net/2026/May/15/datasette-llm-limits/#atom-tag" rel="alternate"/><published>2026-05-15T00:42:09+00:00</published><updated>2026-05-15T00:42:09+00:00</updated><id>https://simonwillison.net/2026/May/15/datasette-llm-limits/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-llm-limits/releases/tag/0.1a0"&gt;datasette-llm-limits 0.1a0&lt;/a&gt;&lt;/p&gt;
        &lt;p&gt;This plugin works in conjunction with &lt;a href="https://github.com/datasette/datasette-llm"&gt;datasette-llm&lt;/a&gt; and &lt;a href="https://github.com/datasette/datasette-llm-accountant"&gt;datasette-llm-accountant&lt;/a&gt; to let you configure a per-user (or global) spending limit for LLM usage inside of Datasette. Configuration looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-ent"&gt;plugins&lt;/span&gt;:
  &lt;span class="pl-ent"&gt;datasette-llm-limits&lt;/span&gt;:
    &lt;span class="pl-ent"&gt;limits&lt;/span&gt;:
      &lt;span class="pl-ent"&gt;per-user-daily&lt;/span&gt;:
        &lt;span class="pl-ent"&gt;scope&lt;/span&gt;: &lt;span class="pl-s"&gt;actor&lt;/span&gt;
        &lt;span class="pl-ent"&gt;window&lt;/span&gt;: &lt;span class="pl-s"&gt;rolling-24h&lt;/span&gt;
        &lt;span class="pl-ent"&gt;amount_usd&lt;/span&gt;: &lt;span class="pl-c1"&gt;1.00&lt;/span&gt;
&lt;/pre&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="datasette"/><category term="llm"/></entry><entry><title>llm 0.32a2</title><link href="https://simonwillison.net/2026/May/12/llm/#atom-tag" rel="alternate"/><published>2026-05-12T17:45:07+00:00</published><updated>2026-05-12T17:45:07+00:00</updated><id>https://simonwillison.net/2026/May/12/llm/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/llm/releases/tag/0.32a2"&gt;llm 0.32a2&lt;/a&gt;&lt;/p&gt;
        &lt;p&gt;A bunch of useful stuff in this &lt;a href="https://llm.datasette.io/"&gt;LLM&lt;/a&gt; alpha, but the most important detail is this one:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Most reasoning-capable OpenAI models now use the &lt;a href="https://developers.openai.com/api/reference/responses/overview"&gt;&lt;code&gt;/v1/responses&lt;/code&gt;&lt;/a&gt; endpoint instead of &lt;code&gt;/v1/chat/completions&lt;/code&gt;. This enables interleaved reasoning across tool calls for GPT-5 class models. &lt;a href="https://github.com/simonw/llm/pull/1435"&gt;#1435&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This means you can now see the summarized reasoning tokens when you run prompts against an OpenAI model, displayed in a different color to standard error. Use the &lt;code&gt;-R&lt;/code&gt; or &lt;code&gt;--hide-reasoning&lt;/code&gt; flags if you don't want to see that.&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/annotated-release-notes"&gt;annotated-release-notes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openai"&gt;openai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="projects"/><category term="ai"/><category term="annotated-release-notes"/><category term="openai"/><category term="generative-ai"/><category term="llms"/><category term="llm"/></entry><entry><title>Using LLM in the shebang line of a script</title><link href="https://simonwillison.net/2026/May/11/llm-shebang/#atom-tag" rel="alternate"/><published>2026-05-11T18:48:57+00:00</published><updated>2026-05-11T18:48:57+00:00</updated><id>https://simonwillison.net/2026/May/11/llm-shebang/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;TIL:&lt;/strong&gt; &lt;a href="https://til.simonwillison.net/llms/llm-shebang"&gt;Using LLM in the shebang line of a script&lt;/a&gt;&lt;/p&gt;
        &lt;p&gt;Kim_Bruning &lt;a href="https://news.ycombinator.com/item?id=48073246#48090590"&gt;on Hacker News&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But seriously, you can put a shebang on an english text file now (if you're sufficiently brave) [...]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This inspired me to look at patterns for doing exactly that with &lt;a href="https://llm.datasette.io/en/stable/"&gt;LLM&lt;/a&gt;. Here's the simplest, which takes advantage of &lt;a href="https://llm.datasette.io/en/stable/fragments.html"&gt;LLM fragments&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env -S llm -f
Generate an SVG of a pelican riding a bicycle
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But you can also incorporate &lt;a href="https://llm.datasette.io/en/stable/tools.html"&gt;tool calls&lt;/a&gt; using the &lt;code&gt;-T name_of_tool&lt;/code&gt; option:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env -S llm -T llm_time -f
Write a haiku that mentions the exact current time
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or even execute YAML templates directly that define extra tools as Python functions:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt;!/usr/bin/env -S llm -t&lt;/span&gt;
&lt;span class="pl-ent"&gt;model&lt;/span&gt;: &lt;span class="pl-s"&gt;gpt-5.4-mini&lt;/span&gt;
&lt;span class="pl-ent"&gt;system&lt;/span&gt;: &lt;span class="pl-s"&gt;|&lt;/span&gt;
&lt;span class="pl-s"&gt;  Use tools to run calculations&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;/span&gt;&lt;span class="pl-ent"&gt;functions&lt;/span&gt;: &lt;span class="pl-s"&gt;|&lt;/span&gt;
&lt;span class="pl-s"&gt;  def add(a: int, b: int) -&amp;gt; int:&lt;/span&gt;
&lt;span class="pl-s"&gt;      return a + b&lt;/span&gt;
&lt;span class="pl-s"&gt;  def multiply(a: int, b: int) -&amp;gt; int:&lt;/span&gt;
&lt;span class="pl-s"&gt;      return a * b&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;Then:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./calc.sh 'what is 2344 * 5252 + 134' --td
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which outputs (thanks to that &lt;code&gt;--td&lt;/code&gt; tools debug option):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Tool call: multiply({'a': 2344, 'b': 5252})
  12310688

Tool call: add({'a': 12310688, 'b': 134})
  12310822

2344 × 5252 + 134 = **12,310,822**
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read the full TIL for &lt;a href="https://til.simonwillison.net/llms/llm-shebang#templates-with-tools"&gt;a more complex example&lt;/a&gt; that uses the Datasette SQL API to answer questions about content on my blog.&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm-tool-use"&gt;llm-tool-use&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="ai"/><category term="generative-ai"/><category term="llms"/><category term="llm"/><category term="llm-tool-use"/></entry><entry><title>Using Claude Code: The Unreasonable Effectiveness of HTML</title><link href="https://simonwillison.net/2026/May/8/unreasonable-effectiveness-of-html/#atom-tag" rel="alternate"/><published>2026-05-08T21:00:11+00:00</published><updated>2026-05-08T21:00:11+00:00</updated><id>https://simonwillison.net/2026/May/8/unreasonable-effectiveness-of-html/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://twitter.com/trq212/status/2052809885763747935"&gt;Using Claude Code: The Unreasonable Effectiveness of HTML&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Thought-provoking piece by Thariq Shihipar (on the Claude Code team at Anthropic) advocating for HTML over Markdown as an output format to request from Claude.&lt;/p&gt;
&lt;p&gt;The article is crammed with interesting examples (collected on &lt;a href="https://thariqs.github.io/html-effectiveness/"&gt;this site&lt;/a&gt;) and prompt suggestions like this one:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Help me review this PR by creating an HTML artifact that describes it. I'm not very familiar with the streaming/backpressure logic so focus on that. Render the actual diff with inline margin annotations, color-code findings by severity and whatever else might be needed to convey the concept well.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I've been defaulting to asking for most things in Markdown since the GPT-4 days, when the 8,192 token limit meant that Markdown's token-efficiency over HTML was extremely worthwhile.&lt;/p&gt;
&lt;p&gt;Thariq's piece here has caused me to reconsider that, especially for output. Asking Claude for an explanation in HTML means it can drop in SVG diagrams, interactive widgets, in-page navigation and all sorts of other neat ways of making the information more pleasant to navigate.&lt;/p&gt;
&lt;p&gt;I wrote about &lt;a href="https://simonwillison.net/2025/Dec/10/html-tools/"&gt;Useful patterns for building HTML tools&lt;/a&gt; last December, but that was focused very much on interactive utilities like the ones on my &lt;a href="https://tools.simonwillison.net/"&gt;tools.simonwillison.net&lt;/a&gt; site. I'm excited to start experimenting more with rich HTML explanations in response to ad-hoc prompts.&lt;/p&gt;
&lt;h4 id="trying-this-out"&gt;Trying this out on copy.fail&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://copy.fail/"&gt;copy.fail&lt;/a&gt; describes a recently discovered Linux security exploit, including a proof of concept distributed as obfuscated Python.&lt;/p&gt;
&lt;p&gt;I tried having GPT-5.5 create an HTML explanation of the exploit like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;curl https://copy.fail/exp | llm -m gpt-5.5 -s 'Explain this code in detail. Reformat it, expand out any confusing bits and go deep into what it does and how it works. Output HTML, neatly styled and using capabilities of HTML and CSS and JavaScript to make the explanation rich and interactive and as clear as possible'&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's &lt;a href="https://gisthost.github.io/?ae53e3461ffdbfd0826156aacf025c7e"&gt;the resulting HTML page&lt;/a&gt;. It's pretty good, though I should have emphasized explaining the exploit over the Python harness around it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of a dark-themed technical document titled &amp;quot;What this Python script does&amp;quot;. Body text: &amp;quot;This is a compact, deliberately obfuscated Linux-specific local privilege-escalation proof-of-concept. Its apparent goal is to tamper with the in-memory image/page cache of /usr/bin/su, then execute su to obtain elevated privileges.&amp;quot; A yellow-bordered callout reads: &amp;quot;Safety note: This explanation is for code understanding, reverse engineering, and defensive analysis. Do not run this on systems you do not own or administer. On a vulnerable kernel, code like this can alter the behavior of a privileged executable.&amp;quot; Left column heading &amp;quot;High-level summary&amp;quot;: &amp;quot;The script opens /usr/bin/su read-only, decompresses an embedded binary payload, and then processes that payload in 4-byte chunks. For each chunk, it performs a carefully arranged sequence involving Linux's kernel crypto socket interface, AF_ALG, pipes, and splice(). The important point is that this is not ordinary file writing. It never calls write() on /usr/bin/su. Instead, it appears to rely on a kernel bug/primitive involving spliced file pages and the crypto API to get controlled bytes placed into the page-cache representation of a privileged executable.&amp;quot; Numbered steps follow: &amp;quot;1. Open target executable — /usr/bin/su is opened read-only. 2. Decode hidden payload — A zlib-compressed hex blob is decompressed into bytes. 3. Patch in 4-byte chunks — The helper function is called repeatedly with offsets 0, 4, 8, ...&amp;quot;. Right column heading &amp;quot;Why it looks strange&amp;quot; contains a table with Pattern and Purpose columns: &amp;quot;import os as g — Short aliasing to make the script compact and harder to read. socket(38, 5, 0) — Uses raw numeric Linux constants instead of readable names. Compressed hex blob — Hides binary payload bytes and keeps the script small. splice() — Moves file-backed pages through pipes without normal user-space copying. try: recv(...) except: 0 — Triggers the kernel operation and ignores expected errors.&amp;quot;" src="https://static.simonwillison.net/static/2026/python-script-explainer.jpg" /&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/html"&gt;html&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/security"&gt;security&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/markdown"&gt;markdown&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/prompt-engineering"&gt;prompt-engineering&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/claude-code"&gt;claude-code&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/thariq-shihipar"&gt;thariq-shihipar&lt;/a&gt;&lt;/p&gt;



</summary><category term="html"/><category term="security"/><category term="markdown"/><category term="ai"/><category term="prompt-engineering"/><category term="generative-ai"/><category term="llms"/><category term="llm"/><category term="claude-code"/><category term="thariq-shihipar"/></entry><entry><title>llm-gemini 0.31</title><link href="https://simonwillison.net/2026/May/7/llm-gemini/#atom-tag" rel="alternate"/><published>2026-05-07T19:57:06+00:00</published><updated>2026-05-07T19:57:06+00:00</updated><id>https://simonwillison.net/2026/May/7/llm-gemini/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/llm-gemini/releases/tag/0.31"&gt;llm-gemini 0.31&lt;/a&gt;&lt;/p&gt;
        &lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gemini-3.1-flash-lite&lt;/code&gt; is &lt;a href="https://cloud.google.com/blog/products/ai-machine-learning/gemini-3-1-flash-lite-is-now-generally-available"&gt;no longer a preview&lt;/a&gt;. &lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's my write-up of the &lt;a href="https://simonwillison.net/2026/Mar/3/gemini-31-flash-lite/"&gt;Gemini 3.1 Flash-Lite Preview model&lt;/a&gt; back in March. I don't believe this new non-preview model has changed since then.&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/google"&gt;google&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/gemini"&gt;gemini&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm-release"&gt;llm-release&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="google"/><category term="ai"/><category term="generative-ai"/><category term="llms"/><category term="llm"/><category term="gemini"/><category term="llm-release"/></entry><entry><title>datasette-llm 0.1a7</title><link href="https://simonwillison.net/2026/May/5/datasette-llm/#atom-tag" rel="alternate"/><published>2026-05-05T01:56:55+00:00</published><updated>2026-05-05T01:56:55+00:00</updated><id>https://simonwillison.net/2026/May/5/datasette-llm/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-llm/releases/tag/0.1a7"&gt;datasette-llm 0.1a7&lt;/a&gt;&lt;/p&gt;
        &lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Mechanism for &lt;a href="https://github.com/datasette/datasette-llm/blob/main/README.md#configuration"&gt;configuring default options&lt;/a&gt; for specific models.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Part of Datasette's evolving support mechanism for plugins that use LLMs. It's now possible to configure a model with default options, e.g. to say all &lt;a href="https://github.com/datasette/datasette-enrichments-llm"&gt;enrichment&lt;/a&gt; operations should use a specific model with temperature set to 0.5.&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="datasette"/><category term="llm"/></entry><entry><title>llm-echo 0.5a0</title><link href="https://simonwillison.net/2026/May/5/llm-echo/#atom-tag" rel="alternate"/><published>2026-05-05T01:31:54+00:00</published><updated>2026-05-05T01:31:54+00:00</updated><id>https://simonwillison.net/2026/May/5/llm-echo/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/llm-echo/releases/tag/0.5a0"&gt;llm-echo 0.5a0&lt;/a&gt;&lt;/p&gt;
        &lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;New &lt;code&gt;-o thinking 1&lt;/code&gt; option to help test against &lt;a href="https://llm.datasette.io/en/latest/changelog.html#a0-2026-04-28"&gt;LLM 0.32a0&lt;/a&gt; and higher.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;This plugin provides a fake model called "echo" for LLM which doesn't run an LLM at all - it's useful for writing automated tests. You can now do this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uvx --with llm==0.32a1 --with llm-echo==0.5a0 llm -m echo hi -o thinking 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will fake a reasoning block to standard error before returning JSON echoing the prompt.&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="llm"/></entry><entry><title>llm 0.32a1</title><link href="https://simonwillison.net/2026/Apr/29/llm-3/#atom-tag" rel="alternate"/><published>2026-04-29T23:52:50+00:00</published><updated>2026-04-29T23:52:50+00:00</updated><id>https://simonwillison.net/2026/Apr/29/llm-3/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/llm/releases/tag/0.32a1"&gt;llm 0.32a1&lt;/a&gt;&lt;/p&gt;
        &lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Fixed a bug in 0.32a0 where tool-calling conversations were not correctly reinflated from SQLite. &lt;a href="https://github.com/simonw/llm/issues/1426"&gt;#1426&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="llm"/></entry><entry><title>LLM 0.32a0  is a major backwards-compatible refactor</title><link href="https://simonwillison.net/2026/Apr/29/llm/#atom-tag" rel="alternate"/><published>2026-04-29T19:01:47+00:00</published><updated>2026-04-29T19:01:47+00:00</updated><id>https://simonwillison.net/2026/Apr/29/llm/#atom-tag</id><summary type="html">
    &lt;p&gt;I just released &lt;a href="https://llm.datasette.io/en/latest/changelog.html#a0-2026-04-28"&gt;LLM 0.32a0&lt;/a&gt;, an alpha release of my &lt;a href="https://llm.datasette.io/"&gt;LLM&lt;/a&gt; Python library and CLI tool for accessing LLMs, with some consequential changes that I've been working towards for quite a while.&lt;/p&gt;
&lt;p&gt;Previous versions of LLM modeled the world in terms of prompts and responses. Send the model a text prompt, get back a text response.&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;llm&lt;/span&gt;

&lt;span class="pl-s1"&gt;model&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;llm&lt;/span&gt;.&lt;span class="pl-c1"&gt;get_model&lt;/span&gt;(&lt;span class="pl-s"&gt;"gpt-5.5"&lt;/span&gt;)
&lt;span class="pl-s1"&gt;response&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;model&lt;/span&gt;.&lt;span class="pl-c1"&gt;prompt&lt;/span&gt;(&lt;span class="pl-s"&gt;"Capital of France?"&lt;/span&gt;)
&lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s1"&gt;response&lt;/span&gt;.&lt;span class="pl-c1"&gt;text&lt;/span&gt;())&lt;/pre&gt;
&lt;p&gt;This made sense when I started working on the library back in April 2023. A lot has changed since then!&lt;/p&gt;
&lt;p&gt;LLM provides an abstraction over thousands of different models via its &lt;a href="https://llm.datasette.io/en/stable/plugins/index.html"&gt;plugin system&lt;/a&gt;. The original abstraction - of text input that returns text output - was no longer able to represent everything I needed it to.&lt;/p&gt;
&lt;p&gt;Over time LLM itself has grown &lt;a href="https://simonwillison.net/2024/Oct/29/llm-multi-modal/"&gt;attachments&lt;/a&gt; to handle image, audio, and video input, then &lt;a href="https://simonwillison.net/2025/Feb/28/llm-schemas/"&gt;schemas&lt;/a&gt; for outputting structured JSON, then &lt;a href="https://simonwillison.net/2025/May/27/llm-tools/"&gt;tools&lt;/a&gt; for executing tool calls. Meanwhile LLMs kept evolving, adding reasoning support and the ability to return images and all kinds of other interesting capabilities.&lt;/p&gt;
&lt;p&gt;LLM needs to evolve to better handle the diversity of input and output types that can be processed by today's frontier models.&lt;/p&gt;
&lt;p&gt;The 0.32a0 alpha has two key changes: model inputs can be represented as a sequence of messages, and model responses can be composed of a stream of differently typed parts.&lt;/p&gt;
&lt;h4 id="prompts-as-a-sequence-of-messages"&gt;Prompts as a sequence of messages&lt;/h4&gt;
&lt;p&gt;LLMs accept input as text, but ever since ChatGPT demonstrated the value of a two-way conversational interface, the most common way to prompt them has been to treat that input as a sequence of conversational turns.&lt;/p&gt;
&lt;p&gt;The first turn might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;user: Capital of France?
assistant: 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(The model then gets to fill out the reply from the assistant.)&lt;/p&gt;
&lt;p&gt;But each subsequent turn needs to replay the entire conversation up to that point, as a sort of screenplay:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;user: Capital of France?
assistant: Paris
user: Germany?
assistant:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Most of the JSON APIs from the major vendors follow this pattern. Here's what the above looks like using the OpenAI chat completions API, which has been widely imitated by other providers:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;curl https://api.openai.com/v1/chat/completions \
  -H &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;Authorization: Bearer &lt;span class="pl-smi"&gt;$OPENAI_API_KEY&lt;/span&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; \
  -H &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;Content-Type: application/json&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; \
  -d &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;{&lt;/span&gt;
&lt;span class="pl-s"&gt;    "model": "gpt-5.5",&lt;/span&gt;
&lt;span class="pl-s"&gt;    "messages": [&lt;/span&gt;
&lt;span class="pl-s"&gt;      {&lt;/span&gt;
&lt;span class="pl-s"&gt;        "role": "user",&lt;/span&gt;
&lt;span class="pl-s"&gt;        "content": "Capital of France?"&lt;/span&gt;
&lt;span class="pl-s"&gt;      },&lt;/span&gt;
&lt;span class="pl-s"&gt;      {&lt;/span&gt;
&lt;span class="pl-s"&gt;        "role": "assistant",&lt;/span&gt;
&lt;span class="pl-s"&gt;        "content": "Paris"&lt;/span&gt;
&lt;span class="pl-s"&gt;      },&lt;/span&gt;
&lt;span class="pl-s"&gt;      {&lt;/span&gt;
&lt;span class="pl-s"&gt;        "role": "user",&lt;/span&gt;
&lt;span class="pl-s"&gt;        "content": "Germany?"&lt;/span&gt;
&lt;span class="pl-s"&gt;      }&lt;/span&gt;
&lt;span class="pl-s"&gt;    ]&lt;/span&gt;
&lt;span class="pl-s"&gt;  }&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Prior to 0.32, LLM modeled these as conversations:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-s1"&gt;model&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;llm&lt;/span&gt;.&lt;span class="pl-c1"&gt;get_model&lt;/span&gt;(&lt;span class="pl-s"&gt;"gpt-5.5"&lt;/span&gt;)

&lt;span class="pl-s1"&gt;conversation&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;model&lt;/span&gt;.&lt;span class="pl-c1"&gt;conversation&lt;/span&gt;()
&lt;span class="pl-s1"&gt;r1&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;conversation&lt;/span&gt;.&lt;span class="pl-c1"&gt;prompt&lt;/span&gt;(&lt;span class="pl-s"&gt;"Capital of France?"&lt;/span&gt;)
&lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s1"&gt;r1&lt;/span&gt;.&lt;span class="pl-c1"&gt;text&lt;/span&gt;())
&lt;span class="pl-c"&gt;# Outputs "Paris"&lt;/span&gt;

&lt;span class="pl-s1"&gt;r2&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;conversation&lt;/span&gt;.&lt;span class="pl-c1"&gt;prompt&lt;/span&gt;(&lt;span class="pl-s"&gt;"Germany?"&lt;/span&gt;)
&lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s1"&gt;r2&lt;/span&gt;.&lt;span class="pl-c1"&gt;text&lt;/span&gt;())
&lt;span class="pl-c"&gt;# Outputs "Berlin"&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;This worked if you were building a conversation with the model from scratch, but it didn't provide a way to feed in a previous conversation from the start. This made tasks like building an emulation of the OpenAI chat completions API much harder than they should have been.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;llm&lt;/code&gt; CLI tool worked around this through a custom mechanism for persisting and inflating conversations using SQLite, but that never became a stable part of the LLM API - and there are many places you might want to use the Python library without committing to SQLite as the storage layer.&lt;/p&gt;
&lt;p&gt;The new alpha now supports this:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;llm&lt;/span&gt;
&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;llm&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;user&lt;/span&gt;, &lt;span class="pl-s1"&gt;assistant&lt;/span&gt;

&lt;span class="pl-s1"&gt;model&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;llm&lt;/span&gt;.&lt;span class="pl-c1"&gt;get_model&lt;/span&gt;(&lt;span class="pl-s"&gt;"gpt-5.5"&lt;/span&gt;)

&lt;span class="pl-s1"&gt;response&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;model&lt;/span&gt;.&lt;span class="pl-c1"&gt;prompt&lt;/span&gt;(&lt;span class="pl-s1"&gt;messages&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;[
    &lt;span class="pl-en"&gt;user&lt;/span&gt;(&lt;span class="pl-s"&gt;"Capital of France?"&lt;/span&gt;),
    &lt;span class="pl-en"&gt;assistant&lt;/span&gt;(&lt;span class="pl-s"&gt;"Paris"&lt;/span&gt;),
    &lt;span class="pl-en"&gt;user&lt;/span&gt;(&lt;span class="pl-s"&gt;"Germany?"&lt;/span&gt;),
])
&lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s1"&gt;response&lt;/span&gt;.&lt;span class="pl-c1"&gt;text&lt;/span&gt;())&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;llm.user()&lt;/code&gt; and &lt;code&gt;llm.assistant()&lt;/code&gt; functions are new builder functions designed to be used within that &lt;code&gt;messages=[]&lt;/code&gt; array.&lt;/p&gt;
&lt;p&gt;The previous &lt;code&gt;prompt=&lt;/code&gt; option still works, but LLM upgrades it to a single-item messages array behind the scenes.&lt;/p&gt;
&lt;p&gt;You can also now &lt;em&gt;reply&lt;/em&gt; to a response, as an alternative to building a conversation:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-s1"&gt;response2&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;response&lt;/span&gt;.&lt;span class="pl-c1"&gt;reply&lt;/span&gt;(&lt;span class="pl-s"&gt;"How about Hungary?"&lt;/span&gt;)
&lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s1"&gt;response2&lt;/span&gt;) &lt;span class="pl-c"&gt;# Default __str__() calls .text()&lt;/span&gt;&lt;/pre&gt;
&lt;h4 id="streaming-parts"&gt;Streaming parts&lt;/h4&gt;
&lt;p&gt;The other major new interface in the alpha concerns streaming results back from a prompt.&lt;/p&gt;
&lt;p&gt;Previously, LLM supported streaming like this:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-s1"&gt;response&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;model&lt;/span&gt;.&lt;span class="pl-c1"&gt;prompt&lt;/span&gt;(&lt;span class="pl-s"&gt;"Generate an SVG of a pelican riding a bicycle"&lt;/span&gt;)
&lt;span class="pl-k"&gt;for&lt;/span&gt; &lt;span class="pl-s1"&gt;chunk&lt;/span&gt; &lt;span class="pl-c1"&gt;in&lt;/span&gt; &lt;span class="pl-s1"&gt;response&lt;/span&gt;:
    &lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s1"&gt;chunk&lt;/span&gt;, &lt;span class="pl-s1"&gt;end&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;""&lt;/span&gt;)&lt;/pre&gt;
&lt;p&gt;Or this async variant:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;asyncio&lt;/span&gt;
&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;llm&lt;/span&gt;

&lt;span class="pl-s1"&gt;model&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;llm&lt;/span&gt;.&lt;span class="pl-c1"&gt;get_async_model&lt;/span&gt;(&lt;span class="pl-s"&gt;"gpt-5.5"&lt;/span&gt;)
&lt;span class="pl-s1"&gt;response&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;model&lt;/span&gt;.&lt;span class="pl-c1"&gt;prompt&lt;/span&gt;(&lt;span class="pl-s"&gt;"Generate an SVG of a pelican riding a bicycle"&lt;/span&gt;)

&lt;span class="pl-k"&gt;async&lt;/span&gt; &lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;run&lt;/span&gt;():
    &lt;span class="pl-k"&gt;async&lt;/span&gt; &lt;span class="pl-k"&gt;for&lt;/span&gt; &lt;span class="pl-s1"&gt;chunk&lt;/span&gt; &lt;span class="pl-c1"&gt;in&lt;/span&gt; &lt;span class="pl-s1"&gt;response&lt;/span&gt;:
        &lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s1"&gt;chunk&lt;/span&gt;, &lt;span class="pl-s1"&gt;end&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;""&lt;/span&gt;, &lt;span class="pl-s1"&gt;flush&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-c1"&gt;True&lt;/span&gt;)

&lt;span class="pl-s1"&gt;asyncio&lt;/span&gt;.&lt;span class="pl-c1"&gt;run&lt;/span&gt;(&lt;span class="pl-en"&gt;run&lt;/span&gt;())&lt;/pre&gt;
&lt;p&gt;Many of today's models return mixed types of content. A prompt run against Claude might return reasoning output, then text, then a JSON request for a tool call, then more text content.&lt;/p&gt;
&lt;p&gt;Some models can even execute tools on the server-side, for example OpenAI's &lt;a href="https://developers.openai.com/api/docs/guides/tools-code-interpreter?lang=curl"&gt;code interpreter tool&lt;/a&gt; or Anthropic's &lt;a href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool"&gt;web search&lt;/a&gt;. This means the results from the model can combine text, tool calls, tool outputs and other formats.&lt;/p&gt;
&lt;p&gt;Multi-modal output models are starting to emerge too, which can return images or even &lt;a href="https://developers.openai.com/api/docs/guides/audio#add-audio-to-your-existing-application"&gt;snippets of audio&lt;/a&gt; intermixed into that streaming response.&lt;/p&gt;
&lt;p&gt;The new LLM alpha models these as a stream of typed message parts. Here's what that looks like as a Python API consumer:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;asyncio&lt;/span&gt;
&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;llm&lt;/span&gt;

&lt;span class="pl-s1"&gt;model&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;llm&lt;/span&gt;.&lt;span class="pl-c1"&gt;get_model&lt;/span&gt;(&lt;span class="pl-s"&gt;"gpt-5.5"&lt;/span&gt;)
&lt;span class="pl-s1"&gt;prompt&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;"invent 3 cool dogs, first talk about your motivations"&lt;/span&gt;

&lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;describe_dog&lt;/span&gt;(&lt;span class="pl-s1"&gt;name&lt;/span&gt;: &lt;span class="pl-smi"&gt;str&lt;/span&gt;, &lt;span class="pl-s1"&gt;bio&lt;/span&gt;: &lt;span class="pl-smi"&gt;str&lt;/span&gt;) &lt;span class="pl-c1"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="pl-smi"&gt;str&lt;/span&gt;:
    &lt;span class="pl-s"&gt;"""Record the name and biography of a hypothetical dog."""&lt;/span&gt;
    &lt;span class="pl-k"&gt;return&lt;/span&gt; &lt;span class="pl-s"&gt;f"&lt;span class="pl-s1"&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;&lt;span class="pl-s1"&gt;name&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/span&gt;: &lt;span class="pl-s1"&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;&lt;span class="pl-s1"&gt;bio&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/span&gt;"&lt;/span&gt;

&lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;sync_example&lt;/span&gt;():
    &lt;span class="pl-s1"&gt;response&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;model&lt;/span&gt;.&lt;span class="pl-c1"&gt;prompt&lt;/span&gt;(
        &lt;span class="pl-s1"&gt;prompt&lt;/span&gt;,
        &lt;span class="pl-s1"&gt;tools&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;[&lt;span class="pl-s1"&gt;describe_dog&lt;/span&gt;],
    )
    &lt;span class="pl-k"&gt;for&lt;/span&gt; &lt;span class="pl-s1"&gt;event&lt;/span&gt; &lt;span class="pl-c1"&gt;in&lt;/span&gt; &lt;span class="pl-s1"&gt;response&lt;/span&gt;.&lt;span class="pl-c1"&gt;stream_events&lt;/span&gt;():
        &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-s1"&gt;event&lt;/span&gt;.&lt;span class="pl-c1"&gt;type&lt;/span&gt; &lt;span class="pl-c1"&gt;==&lt;/span&gt; &lt;span class="pl-s"&gt;"text"&lt;/span&gt;:
            &lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s1"&gt;event&lt;/span&gt;.&lt;span class="pl-c1"&gt;chunk&lt;/span&gt;, &lt;span class="pl-s1"&gt;end&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;""&lt;/span&gt;, &lt;span class="pl-s1"&gt;flush&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-c1"&gt;True&lt;/span&gt;)
        &lt;span class="pl-k"&gt;elif&lt;/span&gt; &lt;span class="pl-s1"&gt;event&lt;/span&gt;.&lt;span class="pl-c1"&gt;type&lt;/span&gt; &lt;span class="pl-c1"&gt;==&lt;/span&gt; &lt;span class="pl-s"&gt;"tool_call_name"&lt;/span&gt;:
            &lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s"&gt;f"&lt;span class="pl-cce"&gt;\n&lt;/span&gt;Tool call: &lt;span class="pl-s1"&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;&lt;span class="pl-s1"&gt;event&lt;/span&gt;.&lt;span class="pl-c1"&gt;chunk&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/span&gt;("&lt;/span&gt;, &lt;span class="pl-s1"&gt;end&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;""&lt;/span&gt;, &lt;span class="pl-s1"&gt;flush&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-c1"&gt;True&lt;/span&gt;)
        &lt;span class="pl-k"&gt;elif&lt;/span&gt; &lt;span class="pl-s1"&gt;event&lt;/span&gt;.&lt;span class="pl-c1"&gt;type&lt;/span&gt; &lt;span class="pl-c1"&gt;==&lt;/span&gt; &lt;span class="pl-s"&gt;"tool_call_args"&lt;/span&gt;:
            &lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s1"&gt;event&lt;/span&gt;.&lt;span class="pl-c1"&gt;chunk&lt;/span&gt;, &lt;span class="pl-s1"&gt;end&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;""&lt;/span&gt;, &lt;span class="pl-s1"&gt;flush&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-c1"&gt;True&lt;/span&gt;)

&lt;span class="pl-k"&gt;async&lt;/span&gt; &lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;async_example&lt;/span&gt;():
    &lt;span class="pl-s1"&gt;model&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;llm&lt;/span&gt;.&lt;span class="pl-c1"&gt;get_async_model&lt;/span&gt;(&lt;span class="pl-s"&gt;"gpt-5.5"&lt;/span&gt;)
    &lt;span class="pl-s1"&gt;response&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;model&lt;/span&gt;.&lt;span class="pl-c1"&gt;prompt&lt;/span&gt;(
        &lt;span class="pl-s1"&gt;prompt&lt;/span&gt;,
        &lt;span class="pl-s1"&gt;tools&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;[&lt;span class="pl-s1"&gt;describe_dog&lt;/span&gt;],
    )
    &lt;span class="pl-k"&gt;async&lt;/span&gt; &lt;span class="pl-k"&gt;for&lt;/span&gt; &lt;span class="pl-s1"&gt;event&lt;/span&gt; &lt;span class="pl-c1"&gt;in&lt;/span&gt; &lt;span class="pl-s1"&gt;response&lt;/span&gt;.&lt;span class="pl-c1"&gt;astream_events&lt;/span&gt;():
        &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-s1"&gt;event&lt;/span&gt;.&lt;span class="pl-c1"&gt;type&lt;/span&gt; &lt;span class="pl-c1"&gt;==&lt;/span&gt; &lt;span class="pl-s"&gt;"text"&lt;/span&gt;:
            &lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s1"&gt;event&lt;/span&gt;.&lt;span class="pl-c1"&gt;chunk&lt;/span&gt;, &lt;span class="pl-s1"&gt;end&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;""&lt;/span&gt;, &lt;span class="pl-s1"&gt;flush&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-c1"&gt;True&lt;/span&gt;)
        &lt;span class="pl-k"&gt;elif&lt;/span&gt; &lt;span class="pl-s1"&gt;event&lt;/span&gt;.&lt;span class="pl-c1"&gt;type&lt;/span&gt; &lt;span class="pl-c1"&gt;==&lt;/span&gt; &lt;span class="pl-s"&gt;"tool_call_name"&lt;/span&gt;:
            &lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s"&gt;f"&lt;span class="pl-cce"&gt;\n&lt;/span&gt;Tool call: &lt;span class="pl-s1"&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;&lt;span class="pl-s1"&gt;event&lt;/span&gt;.&lt;span class="pl-c1"&gt;chunk&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/span&gt;("&lt;/span&gt;, &lt;span class="pl-s1"&gt;end&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;""&lt;/span&gt;, &lt;span class="pl-s1"&gt;flush&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-c1"&gt;True&lt;/span&gt;)
        &lt;span class="pl-k"&gt;elif&lt;/span&gt; &lt;span class="pl-s1"&gt;event&lt;/span&gt;.&lt;span class="pl-c1"&gt;type&lt;/span&gt; &lt;span class="pl-c1"&gt;==&lt;/span&gt; &lt;span class="pl-s"&gt;"tool_call_args"&lt;/span&gt;:
            &lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s1"&gt;event&lt;/span&gt;.&lt;span class="pl-c1"&gt;chunk&lt;/span&gt;, &lt;span class="pl-s1"&gt;end&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;""&lt;/span&gt;, &lt;span class="pl-s1"&gt;flush&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-c1"&gt;True&lt;/span&gt;)

&lt;span class="pl-en"&gt;sync_example&lt;/span&gt;()
&lt;span class="pl-s1"&gt;asyncio&lt;/span&gt;.&lt;span class="pl-c1"&gt;run&lt;/span&gt;(&lt;span class="pl-en"&gt;async_example&lt;/span&gt;())&lt;/pre&gt;
&lt;p&gt;Sample output (from just the first sync example):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;My motivation: create three memorable dogs with distinct “cool” styles—one cinematic, one adventurous, and one charmingly chaotic—so each feels like they could star in their own story.&lt;/code&gt;&lt;br /&gt;
&lt;code&gt;Tool call: describe_dog({"name": "Nova Jetpaw", "bio": "A sleek silver-gray whippet who wears tiny aviator goggles and loves sprinting along moonlit beaches. Nova is fearless, elegant, and rumored to outrun drones just for fun."}&lt;/code&gt;&lt;br /&gt;
&lt;code&gt;Tool call: describe_dog({"name": "Mochi Thunderbark", "bio": "A fluffy corgi with a dramatic black-and-gold bandana and the confidence of a rock star. Mochi is short, loud, loyal, and leads a neighborhood 'security patrol' made entirely of squirrels."}&lt;/code&gt;&lt;br /&gt;
&lt;code&gt;Tool call: describe_dog({"name": "Atlas Snowfang", "bio": "A massive white husky with ice-blue eyes and a backpack full of trail snacks. Atlas is calm, heroic, and always knows the way home—even during blizzards, fog, or confusing camping trips."}&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At the end of the response you can call &lt;code&gt;response.execute_tool_calls()&lt;/code&gt; to actually run the functions that were requested, or send a &lt;code&gt;response.reply()&lt;/code&gt; to have those tools called and their return values sent back to the model:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s1"&gt;response&lt;/span&gt;.&lt;span class="pl-c1"&gt;reply&lt;/span&gt;(&lt;span class="pl-s"&gt;"Tell me about the dogs"&lt;/span&gt;))&lt;/pre&gt;
&lt;p&gt;This new mechanism for streaming different token types means the CLI tool can now display "thinking" text in a different color from the text in the final response. The thinking text goes to stderr so it won't affect results that are piped into other tools.&lt;/p&gt;
&lt;p&gt;This example uses Claude Sonnet 4.6 (with an updated streaming event version of the &lt;a href="https://github.com/simonw/llm-anthropic"&gt;llm-anthropic&lt;/a&gt; plugin) as Anthropic's models return their reasoning text as part of the response:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;llm -m claude-sonnet-4.6 &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;Think about 3 cool dogs then describe them&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt; \
  -o thinking_display 1&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2026/claude-thinking-llm.gif" alt="Animated demo. Starts with ~/dev/scratch/llm-anthropic % uv run llm -m claude-sonnet-4.6 'Think about 3 cool dogs then describe them' -o thinking_display 1 - the text then streams in grey: The user wants me to think about 3 cool dogs and then describe them. Let me come up with 3 interesting, cool dogs and describe them. Then switches to regular color text for the output that describes the dogs." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;You can suppress the output of reasoning tokens using the new &lt;code&gt;-R/--no-reasoning&lt;/code&gt; flag. Surprisingly that ended up being the only CLI-facing change in this release.&lt;/p&gt;
&lt;h4 id="a-mechanism-for-serializing-and-deserializing-responses"&gt;A mechanism for serializing and deserializing responses&lt;/h4&gt;
&lt;p&gt;As mentioned earlier, LLM has quite inflexible code at the moment for persisting conversations to SQLite. I've added a new mechanism in 0.32a0 that should provide Python API users a way to roll their own alternative:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-s1"&gt;serializable&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;response&lt;/span&gt;.&lt;span class="pl-c1"&gt;to_dict&lt;/span&gt;()
&lt;span class="pl-c"&gt;# serializable is a JSON-style dictionary&lt;/span&gt;
&lt;span class="pl-c"&gt;# store it anywhere you like, then inflate it:&lt;/span&gt;
&lt;span class="pl-s1"&gt;response&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-v"&gt;Response&lt;/span&gt;.&lt;span class="pl-c1"&gt;from_dict&lt;/span&gt;(&lt;span class="pl-s1"&gt;serializable&lt;/span&gt;)&lt;/pre&gt;
&lt;p&gt;The dictionary this returns is actually a &lt;code&gt;TypedDict&lt;/code&gt; defined in the new &lt;a href="https://github.com/simonw/llm/blob/main/llm/serialization.py"&gt;llm/serialization.py&lt;/a&gt; module.&lt;/p&gt;
&lt;h4 id="what-s-next-"&gt;What's next?&lt;/h4&gt;
&lt;p&gt;I'm releasing this as an alpha so I can upgrade various plugins and exercise the new design in real world environments for a few days. I expect the stable 0.32 release will be very similar to this alpha, unless alpha testing reveals some design flaw in the way I've put this all together.&lt;/p&gt;
&lt;p&gt;There's one remaining large task: I'd like to redesign the SQLite logging system to better capture the more finely grained details that are returned by this new abstraction.&lt;/p&gt;
&lt;p&gt;Ideally I'd like to model this as a graph, to best support situations like an OpenAI-style chat completions API where the same conversations are constantly extended and then repeated with every prompt. I want to be able to store those without duplicating them in the database.&lt;/p&gt;
&lt;p&gt;I'm undecided as to whether that should be a feature in 0.32 or I should hold it for 0.33.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/annotated-release-notes"&gt;annotated-release-notes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="projects"/><category term="python"/><category term="ai"/><category term="annotated-release-notes"/><category term="generative-ai"/><category term="llms"/><category term="llm"/></entry><entry><title>llm 0.32a0</title><link href="https://simonwillison.net/2026/Apr/29/llm-2/#atom-tag" rel="alternate"/><published>2026-04-29T18:57:47+00:00</published><updated>2026-04-29T18:57:47+00:00</updated><id>https://simonwillison.net/2026/Apr/29/llm-2/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/llm/releases/tag/0.32a0"&gt;llm 0.32a0&lt;/a&gt;&lt;/p&gt;
        &lt;p&gt;See &lt;a href="https://simonwillison.net/2026/Apr/29/llm/"&gt;the annotated release notes&lt;/a&gt;.&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="llm"/></entry></feed>