<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: pydantic</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/pydantic.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2026-02-06T22:31:31+00:00</updated><author><name>Simon Willison</name></author><entry><title>Running Pydantic's Monty Rust sandboxed Python subset in WebAssembly</title><link href="https://simonwillison.net/2026/Feb/6/pydantic-monty/#atom-tag" rel="alternate"/><published>2026-02-06T22:31:31+00:00</published><updated>2026-02-06T22:31:31+00:00</updated><id>https://simonwillison.net/2026/Feb/6/pydantic-monty/#atom-tag</id><summary type="html">
    &lt;p&gt;There's a jargon-filled headline for you! Everyone's &lt;a href="https://simonwillison.net/2026/Jan/8/llm-predictions-for-2026/#1-year-we-re-finally-going-to-solve-sandboxing"&gt;building sandboxes&lt;/a&gt; for running untrusted code right now, and Pydantic's latest attempt, &lt;a href="https://github.com/pydantic/monty"&gt;Monty&lt;/a&gt;, provides a custom Python-like language (a subset of Python) in Rust and makes it available as both a Rust library and a Python package. I got it working in WebAssembly, providing a sandbox-in-a-sandbox.&lt;/p&gt;
&lt;p&gt;Here's &lt;a href="https://github.com/pydantic/monty"&gt;how they describe Monty&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Monty avoids the cost, latency, complexity and general faff of using full container based sandbox for running LLM generated code.&lt;/p&gt;
&lt;p&gt;Instead, it let's you safely run Python code written by an LLM embedded in your agent, with startup times measured in single digit microseconds not hundreds of milliseconds.&lt;/p&gt;
&lt;p&gt;What Monty &lt;strong&gt;can&lt;/strong&gt; do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Run a reasonable subset of Python code - enough for your agent to express what it wants to do&lt;/li&gt;
&lt;li&gt;Completely block access to the host environment: filesystem, env variables and network access are all implemented via external function calls the developer can control&lt;/li&gt;
&lt;li&gt;Call functions on the host - only functions you give it access to [...]&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;A quick way to try it out is via &lt;a href="https://github.com/astral-sh/uv"&gt;uv&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uv run --with pydantic-monty python -m asyncio
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then paste this into the Python interactive prompt - the &lt;code&gt;-m asyncio&lt;/code&gt; enables top-level await:&lt;/p&gt;
&lt;pre&gt;&lt;span&gt;import&lt;/span&gt; &lt;span&gt;pydantic_monty&lt;/span&gt;
&lt;span&gt;code&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;pydantic_monty&lt;/span&gt;.&lt;span&gt;Monty&lt;/span&gt;(&lt;span&gt;'print("hello " + str(4 * 5))'&lt;/span&gt;)
&lt;span&gt;await&lt;/span&gt; &lt;span&gt;pydantic_monty&lt;/span&gt;.&lt;span&gt;run_monty_async&lt;/span&gt;(&lt;span&gt;code&lt;/span&gt;)&lt;/pre&gt;
&lt;p&gt;Monty supports a &lt;em&gt;very&lt;/em&gt; small subset of Python - it doesn't even support class declarations yet!&lt;/p&gt;
&lt;p&gt;But, given its target use-case, that's not actually a problem.&lt;/p&gt;
&lt;p&gt;The neat thing about providing tools like this for LLMs is that they're really good at iterating against error messages. A coding agent can run some Python code, get an error message telling it that classes aren't supported and then try again with a different approach.&lt;/p&gt;
&lt;p&gt;I wanted to try this in a browser, so I fired up &lt;a href="https://simonwillison.net/2025/Nov/6/async-code-research/"&gt;a code research task&lt;/a&gt; in Claude Code for web and kicked it off with the following:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Clone &lt;a href="https://github.com/pydantic/monty"&gt;https://github.com/pydantic/monty&lt;/a&gt; to /tmp and figure out how to compile it into a python WebAssembly wheel that can then be loaded in Pyodide. The wheel file itself should be checked into the repo along with build scripts and passing pytest playwright test scripts that load Pyodide from a CDN and the wheel from a “python -m http.server” localhost and demonstrate it working&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then a little later:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I want an additional WASM file that works independently of Pyodide, which is also usable in a web browser - build that too along with playwright tests that show it working. Also build two HTML files - one called demo.html and one called pyodide-demo.html - these should work similar to &lt;a href="https://tools.simonwillison.net/micropython"&gt;https://tools.simonwillison.net/micropython&lt;/a&gt; (download that code with curl to inspect it) - one should load the WASM build, the other should load Pyodide and have it use the WASM wheel. These will be served by GitHub Pages so they can load the WASM and wheel from a relative path since the .html files will be served from the same folder as the wheel and WASM file&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's &lt;a href="https://gisthost.github.io/?22d88e6367d7e002c4fb383c213c2df2/page-001.html"&gt;the transcript&lt;/a&gt;, and the &lt;a href="https://github.com/simonw/research/tree/main/monty-wasm-pyodide"&gt;final research report&lt;/a&gt; it produced.&lt;/p&gt;
&lt;p&gt;I now have the Monty Rust code compiled to WebAssembly in two different shapes - as a &lt;code&gt;.wasm&lt;/code&gt; bundle you can load and call from JavaScript, and as a &lt;code&gt;monty-wasm-pyodide/pydantic_monty-0.0.3-cp313-cp313-emscripten_4_0_9_wasm32.whl&lt;/code&gt; wheel file which can be loaded into &lt;a href="https://pyodide.org/"&gt;Pyodide&lt;/a&gt; and then called from Python in Pyodide in WebAssembly in a browser.&lt;/p&gt;
&lt;p&gt;Here are those two demos, hosted on GitHub Pages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://simonw.github.io/research/monty-wasm-pyodide/demo.html"&gt;Monty WASM demo&lt;/a&gt; - a UI over JavaScript that loads the Rust WASM module directly.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://simonw.github.io/research/monty-wasm-pyodide/pyodide-demo.html"&gt;Monty Pyodide demo&lt;/a&gt; - this one provides an identical interface but here the code is &lt;a href="https://github.com/simonw/research/blob/3add1ffec70b530711fa237d91f546da5bcf1f1c/monty-wasm-pyodide/pyodide-demo.html#L257-L280"&gt;loading Pyodide and then installing the Monty WASM wheel&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2026/monty-pyodide.jpg" alt="Screenshot of a web app titled &amp;quot;Monty via Pyodide&amp;quot; with description &amp;quot;Run Monty (a sandboxed Python interpreter by Pydantic) inside Pyodide (CPython compiled to WebAssembly). This loads the pydantic-monty wheel and uses its full Python API. Code is saved in the URL for sharing.&amp;quot; A green banner reads &amp;quot;Code executed successfully!&amp;quot; Below are example buttons labeled &amp;quot;Basic&amp;quot;, &amp;quot;Inputs&amp;quot;, &amp;quot;Reuse&amp;quot;, &amp;quot;Error Handling&amp;quot;, &amp;quot;Fibonacci&amp;quot;, and &amp;quot;Classes&amp;quot;. A code editor labeled &amp;quot;Python Code (runs inside Monty sandbox via Pyodide):&amp;quot; contains: &amp;quot;import pydantic_monty\n\n# Create interpreter with input variables\nm = pydantic_monty.Monty('x + y', inputs=['x', 'y'])\n\n# Run with different inputs\nresult1 = m.run(inputs={&amp;quot;x&amp;quot;: 10, &amp;quot;y&amp;quot;: 20})\nprint(f&amp;quot;10 + 20 = {result1}&amp;quot;)\n\nresult2 = m.run(inputs={&amp;quot;x&amp;quot;: 100, &amp;quot;y&amp;quot;: 200})&amp;quot; with &amp;quot;Run Code&amp;quot; and &amp;quot;Clear&amp;quot; buttons. The Output section shows &amp;quot;10 + 20 = 30&amp;quot; and &amp;quot;100 + 200 = 300&amp;quot; with a &amp;quot;Copy&amp;quot; button. Footer reads &amp;quot;Executed in 4.0ms&amp;quot;." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;As a connoisseur of sandboxes - the more options the better! - this new entry from Pydantic ticks a lot of my boxes. It's small, fast, widely available (thanks to Rust and WebAssembly) and provides strict limits on memory usage, CPU time and access to disk and network.&lt;/p&gt;
&lt;p&gt;It was also a great excuse to spin up another demo showing how easy it is these days to turn compiled code like C or Rust into WebAssembly that runs in both a browser and a Pyodide environment.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/javascript"&gt;javascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sandboxing"&gt;sandboxing&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/rust"&gt;rust&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/webassembly"&gt;webassembly&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pyodide"&gt;pyodide&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/ai-assisted-programming"&gt;ai-assisted-programming&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pydantic"&gt;pydantic&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;/p&gt;
    

</summary><category term="javascript"/><category term="python"/><category term="sandboxing"/><category term="ai"/><category term="rust"/><category term="webassembly"/><category term="pyodide"/><category term="generative-ai"/><category term="llms"/><category term="ai-assisted-programming"/><category term="pydantic"/><category term="coding-agents"/><category term="claude-code"/></entry><entry><title>mistralai/mistral-vibe</title><link href="https://simonwillison.net/2025/Dec/9/mistral-vibe/#atom-tag" rel="alternate"/><published>2025-12-09T20:19:21+00:00</published><updated>2025-12-09T20:19:21+00:00</updated><id>https://simonwillison.net/2025/Dec/9/mistral-vibe/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/mistralai/mistral-vibe"&gt;mistralai/mistral-vibe&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Here's the Apache 2.0 licensed source code for Mistral's new "Vibe" CLI coding agent, &lt;a href="https://mistral.ai/news/devstral-2-vibe-cli"&gt;released today&lt;/a&gt; alongside Devstral 2.&lt;/p&gt;
&lt;p&gt;It's a neat implementation of the now standard terminal coding agent pattern, built in Python on top of Pydantic and Rich/Textual (here are &lt;a href="https://github.com/mistralai/mistral-vibe/blob/v1.0.4/pyproject.toml#L29-L46"&gt;the dependencies&lt;/a&gt;.) &lt;a href="https://github.com/google-gemini/gemini-cli"&gt;Gemini CLI&lt;/a&gt; is TypeScript, Claude Code is closed source (TypeScript, now &lt;a href="https://simonwillison.net/2025/Dec/2/anthropic-acquires-bun/"&gt;on top of Bun&lt;/a&gt;), OpenAI's &lt;a href="https://github.com/openai/codex"&gt;Codex CLI&lt;/a&gt; is Rust. &lt;a href="https://github.com/OpenHands/OpenHands"&gt;OpenHands&lt;/a&gt; is the other major Python coding agent I know of, but I'm likely missing some others. (UPDATE: &lt;a href="https://github.com/MoonshotAI/kimi-cli"&gt;Kimi CLI&lt;/a&gt; is another open source Apache 2 Python one.)&lt;/p&gt;
&lt;p&gt;The Vibe source code is pleasant to read and the crucial prompts are neatly extracted out into Markdown files. Some key places to look:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/mistralai/mistral-vibe/blob/v1.0.4/vibe/core/prompts/cli.md"&gt;core/prompts/cli.md&lt;/a&gt; is the main system prompt ("You are operating as and within Mistral Vibe, a CLI coding-agent built by Mistral AI...")&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mistralai/mistral-vibe/blob/v1.0.4/vibe/core/prompts/compact.md"&gt;core/prompts/compact.md&lt;/a&gt; is the prompt used to generate compacted summaries of conversations ("Create a comprehensive summary of our entire conversation that will serve as complete context for continuing this work...")&lt;/li&gt;
&lt;li&gt;Each of the core tools has its own prompt file:&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/mistralai/mistral-vibe/blob/v1.0.4/vibe/core/tools/builtins/prompts/bash.md"&gt;.../prompts/bash.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mistralai/mistral-vibe/blob/v1.0.4/vibe/core/tools/builtins/prompts/grep.md"&gt;.../prompts/grep.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mistralai/mistral-vibe/blob/v1.0.4/vibe/core/tools/builtins/prompts/read_file.md"&gt;.../prompts/read_file.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mistralai/mistral-vibe/blob/v1.0.4/vibe/core/tools/builtins/prompts/write_file.md"&gt;.../prompts/write_file.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mistralai/mistral-vibe/blob/v1.0.4/vibe/core/tools/builtins/prompts/search_replace.md"&gt;.../prompts/search_replace.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mistralai/mistral-vibe/blob/v1.0.4/vibe/core/tools/builtins/prompts/todo.md"&gt;.../prompts/todo.md&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Python implementations of those tools &lt;a href="https://github.com/mistralai/mistral-vibe/tree/v1.0.4/vibe/core/tools/builtins"&gt;can be found here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I tried it out and had it build me a Space Invaders game using three.js with the following prompt:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;make me a space invaders game as HTML with three.js loaded from a CDN&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="Animated screenshot demo of Mistral Vibe running in a terminal. The text reads: I've created a Space Invaders game using HTML and Three. js loaded from a CDN. The game is now available in the file space_invaders.html in your current directory. Here's how to play: 1. Open the space_invaders.html file in a web browser 2. Use the left and right arrow keys to move your player (green rectangle) 3. Press the spacebar to shoot at the invaders (red rectangles) 4. Try to get the highest score before the invaders reach you or hit you with their bullets The game features: © Player movement with arrow keys © Shooting mechanics with spacebar © Enemy invaders that move back and forth © Collision detection « Score tracking * Game over screen © Increasing difficulty Writing file (64s esc to interrupt) »» auto-approve on (shift-tab to toggle) - 7% of 100k tokens" src="https://static.simonwillison.net/static/2025/vibe.gif" /&gt;&lt;/p&gt;
&lt;p&gt;Here's &lt;a href="https://github.com/simonw/space-invaders-by-llms/blob/main/mistral-vibe-devstral-2/index.html"&gt;the source code&lt;/a&gt;  and &lt;a href="https://space-invaders.simonwillison.net/mistral-vibe-devstral-2/"&gt;the live game&lt;/a&gt; (hosted in my new &lt;a href="https://github.com/simonw/space-invaders-by-llms"&gt;space-invaders-by-llms&lt;/a&gt; repo). It did OK.


    &lt;p&gt;Tags: &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/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/textual"&gt;textual&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai-assisted-programming"&gt;ai-assisted-programming&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mistral"&gt;mistral&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pydantic"&gt;pydantic&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/vibe-coding"&gt;vibe-coding&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/system-prompts"&gt;system-prompts&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/space-invaders"&gt;space-invaders&lt;/a&gt;&lt;/p&gt;



</summary><category term="python"/><category term="ai"/><category term="prompt-engineering"/><category term="generative-ai"/><category term="llms"/><category term="textual"/><category term="ai-assisted-programming"/><category term="mistral"/><category term="pydantic"/><category term="vibe-coding"/><category term="coding-agents"/><category term="system-prompts"/><category term="space-invaders"/></entry><entry><title>Reflections on OpenAI</title><link href="https://simonwillison.net/2025/Jul/15/reflections-on-openai/#atom-tag" rel="alternate"/><published>2025-07-15T18:02:41+00:00</published><updated>2025-07-15T18:02:41+00:00</updated><id>https://simonwillison.net/2025/Jul/15/reflections-on-openai/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://calv.info/openai-reflections"&gt;Reflections on OpenAI&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Calvin French-Owen spent just over a year working at OpenAI, during which time the organization grew from 1,000 to 3,000 people and Calvin found himself in "the top 30% by tenure".&lt;/p&gt;
&lt;p&gt;His reflections on leaving are &lt;em&gt;fascinating&lt;/em&gt; - absolutely crammed with detail about OpenAI's internal culture that I haven't seen described anywhere else before.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I think of OpenAI as an organization that started like Los Alamos. It was a group of scientists and tinkerers investigating the cutting edge of science. That group happened to accidentally spawn the most viral consumer app in history. And then grew to have ambitions to sell to governments and enterprises.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There's a lot in here, and it's worth spending time with the whole thing. A few points that stood out to me below.&lt;/p&gt;
&lt;p&gt;Firstly, OpenAI are a Python shop who lean a whole lot on &lt;a href="https://docs.pydantic.dev/latest/"&gt;Pydantic&lt;/a&gt; and &lt;a href="https://fastapi.tiangolo.com/"&gt;FastAPI&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;OpenAI uses a &lt;strong&gt;giant monorepo&lt;/strong&gt; which is ~mostly Python (though there is a growing set of Rust services and a handful of Golang services sprinkled in for things like network proxies). This creates a lot of strange-looking code because there are so many ways you can write Python. You will encounter both libraries designed for scale from 10y Google veterans as well as throwaway Jupyter notebooks newly-minted PhDs. Pretty much everything operates around FastAPI to create APIs and Pydantic for validation. But there aren't style guides enforced writ-large.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;ChatGPT's success has influenced everything that they build, even at a technical level:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Chat runs really deep&lt;/strong&gt;. Since ChatGPT took off, a &lt;em&gt;lot&lt;/em&gt; of the codebase is structured around the idea of chat messages and conversations. These primitives are so baked at this point, you should probably ignore them at your own peril.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's a rare peek at how improvements to large models get discovered and incorporated into training runs:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How large models are trained (at a high-level).&lt;/strong&gt; There's a spectrum from "experimentation" to "engineering". Most ideas start out as small-scale experiments. If the results look promising, they then get incorporated into a bigger run. Experimentation is as much about tweaking the core algorithms as it is tweaking the data mix and carefully studying the results. On the large end, doing a big run almost looks like giant distributed systems engineering. There will be weird edge cases and things you didn't expect.&lt;/p&gt;
&lt;/blockquote&gt;

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=44573195"&gt;Hacker News&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &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/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/chatgpt"&gt;chatgpt&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pydantic"&gt;pydantic&lt;/a&gt;&lt;/p&gt;



</summary><category term="python"/><category term="ai"/><category term="openai"/><category term="generative-ai"/><category term="chatgpt"/><category term="llms"/><category term="pydantic"/></entry><entry><title>MCP Run Python</title><link href="https://simonwillison.net/2025/Apr/18/mcp-run-python/#atom-tag" rel="alternate"/><published>2025-04-18T04:51:20+00:00</published><updated>2025-04-18T04:51:20+00:00</updated><id>https://simonwillison.net/2025/Apr/18/mcp-run-python/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/pydantic/pydantic-ai/tree/main/mcp-run-python"&gt;MCP Run Python&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Pydantic AI's MCP server for running LLM-generated Python code in a sandbox. They ended up using a trick I explored &lt;a href="https://til.simonwillison.net/deno/pyodide-sandbox"&gt;two years ago&lt;/a&gt;: using a &lt;a href="https://deno.com/"&gt;Deno&lt;/a&gt; process to run &lt;a href="https://pyodide.org/"&gt;Pyodide&lt;/a&gt; in a WebAssembly sandbox.&lt;/p&gt;
&lt;p&gt;Here's a bit of a wild trick: since Deno loads code on-demand from &lt;a href="https://jsr.io/"&gt;JSR&lt;/a&gt;, and &lt;a href="https://docs.astral.sh/uv/guides/scripts/"&gt;uv run&lt;/a&gt; can install Python dependencies on demand via the &lt;code&gt;--with&lt;/code&gt; option... here's a one-liner you can paste into a macOS shell (provided you have Deno and &lt;code&gt;uv&lt;/code&gt; installed already) which will run the example from &lt;a href="https://github.com/pydantic/pydantic-ai/blob/v0.1.2/mcp-run-python/README.md"&gt;their README&lt;/a&gt; - calculating the number of days between two dates in the most complex way imaginable:&lt;/p&gt;
&lt;pre&gt;ANTHROPIC_API_KEY=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;sk-ant-...&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; \
uv run --with pydantic-ai python -c &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;import asyncio&lt;/span&gt;
&lt;span class="pl-s"&gt;from pydantic_ai import Agent&lt;/span&gt;
&lt;span class="pl-s"&gt;from pydantic_ai.mcp import MCPServerStdio&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;server = MCPServerStdio(&lt;/span&gt;
&lt;span class="pl-s"&gt;    "deno",&lt;/span&gt;
&lt;span class="pl-s"&gt;    args=[&lt;/span&gt;
&lt;span class="pl-s"&gt;        "run",&lt;/span&gt;
&lt;span class="pl-s"&gt;        "-N",&lt;/span&gt;
&lt;span class="pl-s"&gt;        "-R=node_modules",&lt;/span&gt;
&lt;span class="pl-s"&gt;        "-W=node_modules",&lt;/span&gt;
&lt;span class="pl-s"&gt;        "--node-modules-dir=auto",&lt;/span&gt;
&lt;span class="pl-s"&gt;        "jsr:@pydantic/mcp-run-python",&lt;/span&gt;
&lt;span class="pl-s"&gt;        "stdio",&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;agent = Agent("claude-3-5-haiku-latest", mcp_servers=[server])&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;async def main():&lt;/span&gt;
&lt;span class="pl-s"&gt;    async with agent.run_mcp_servers():&lt;/span&gt;
&lt;span class="pl-s"&gt;        result = await agent.run("How many days between 2000-01-01 and 2025-03-18?")&lt;/span&gt;
&lt;span class="pl-s"&gt;    print(result.output)&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;asyncio.run(main())&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;I ran that just now and got:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The number of days between January 1st, 2000 and March 18th, 2025 is 9,208 days.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I thoroughly enjoy how tools like &lt;code&gt;uv&lt;/code&gt; and Deno enable throwing together shell one-liner demos like this one.&lt;/p&gt;
&lt;p&gt;Here's &lt;a href="https://gist.github.com/simonw/54fc42ef9a7fb8f777162bbbfbba4f23"&gt;an extended version&lt;/a&gt; of this example which adds pretty-printed logging of the messages exchanged with the LLM to illustrate exactly what happened. The most important piece is this tool call where Claude 3.5 Haiku asks for Python code to be executed my the MCP server:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-en"&gt;ToolCallPart&lt;/span&gt;(
    &lt;span class="pl-s1"&gt;tool_name&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;'run_python_code'&lt;/span&gt;,
    &lt;span class="pl-s1"&gt;args&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;{
        &lt;span class="pl-s"&gt;'python_code'&lt;/span&gt;: (
            &lt;span class="pl-s"&gt;'from datetime import date&lt;span class="pl-cce"&gt;\n&lt;/span&gt;'&lt;/span&gt;
            &lt;span class="pl-s"&gt;'&lt;span class="pl-cce"&gt;\n&lt;/span&gt;'&lt;/span&gt;
            &lt;span class="pl-s"&gt;'date1 = date(2000, 1, 1)&lt;span class="pl-cce"&gt;\n&lt;/span&gt;'&lt;/span&gt;
            &lt;span class="pl-s"&gt;'date2 = date(2025, 3, 18)&lt;span class="pl-cce"&gt;\n&lt;/span&gt;'&lt;/span&gt;
            &lt;span class="pl-s"&gt;'&lt;span class="pl-cce"&gt;\n&lt;/span&gt;'&lt;/span&gt;
            &lt;span class="pl-s"&gt;'days_between = (date2 - date1).days&lt;span class="pl-cce"&gt;\n&lt;/span&gt;'&lt;/span&gt;
            &lt;span class="pl-s"&gt;'print(f"Number of days between {date1} and {date2}: {days_between}")'&lt;/span&gt;
        ),
    },
    &lt;span class="pl-s1"&gt;tool_call_id&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;'toolu_01TXXnQ5mC4ry42DrM1jPaza'&lt;/span&gt;,
    &lt;span class="pl-s1"&gt;part_kind&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;'tool-call'&lt;/span&gt;,
)&lt;/pre&gt;

&lt;p&gt;I also managed to run it against &lt;a href="https://ollama.com/library/mistral-small3.1"&gt;Mistral Small 3.1&lt;/a&gt; (15GB) running locally using &lt;a href="https://ollama.com/"&gt;Ollama&lt;/a&gt; (I had to add "Use your python tool" to the prompt to get it to work):&lt;/p&gt;
&lt;pre&gt;ollama pull mistral-small3.1:24b

uv run --with devtools --with pydantic-ai python -c &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;import asyncio&lt;/span&gt;
&lt;span class="pl-s"&gt;from devtools import pprint&lt;/span&gt;
&lt;span class="pl-s"&gt;from pydantic_ai import Agent, capture_run_messages&lt;/span&gt;
&lt;span class="pl-s"&gt;from pydantic_ai.models.openai import OpenAIModel&lt;/span&gt;
&lt;span class="pl-s"&gt;from pydantic_ai.providers.openai import OpenAIProvider&lt;/span&gt;
&lt;span class="pl-s"&gt;from pydantic_ai.mcp import MCPServerStdio&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;server = MCPServerStdio(&lt;/span&gt;
&lt;span class="pl-s"&gt;    "deno",&lt;/span&gt;
&lt;span class="pl-s"&gt;    args=[&lt;/span&gt;
&lt;span class="pl-s"&gt;        "run",&lt;/span&gt;
&lt;span class="pl-s"&gt;        "-N",&lt;/span&gt;
&lt;span class="pl-s"&gt;        "-R=node_modules",&lt;/span&gt;
&lt;span class="pl-s"&gt;        "-W=node_modules",&lt;/span&gt;
&lt;span class="pl-s"&gt;        "--node-modules-dir=auto",&lt;/span&gt;
&lt;span class="pl-s"&gt;        "jsr:@pydantic/mcp-run-python",&lt;/span&gt;
&lt;span class="pl-s"&gt;        "stdio",&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&gt;
&lt;span class="pl-s"&gt;agent = Agent( &lt;/span&gt;
&lt;span class="pl-s"&gt;    OpenAIModel(                          &lt;/span&gt;
&lt;span class="pl-s"&gt;        model_name="mistral-small3.1:latest",&lt;/span&gt;
&lt;span class="pl-s"&gt;        provider=OpenAIProvider(base_url="http://localhost:11434/v1"),                &lt;/span&gt;
&lt;span class="pl-s"&gt;    ),            &lt;/span&gt;
&lt;span class="pl-s"&gt;    mcp_servers=[server],&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;async def main():&lt;/span&gt;
&lt;span class="pl-s"&gt;    with capture_run_messages() as messages:&lt;/span&gt;
&lt;span class="pl-s"&gt;        async with agent.run_mcp_servers():&lt;/span&gt;
&lt;span class="pl-s"&gt;            result = await agent.run("How many days between 2000-01-01 and 2025-03-18? Use your python tool.")&lt;/span&gt;
&lt;span class="pl-s"&gt;    pprint(messages)&lt;/span&gt;
&lt;span class="pl-s"&gt;    print(result.output)&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;asyncio.run(main())&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;Here's &lt;a href="https://gist.github.com/simonw/e444a81440bda2f37b0fef205780074a"&gt;the full output&lt;/a&gt; including the debug logs.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=43691230"&gt;Hacker News&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sandboxing"&gt;sandboxing&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/deno"&gt;deno&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/local-llms"&gt;local-llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/claude"&gt;claude&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mistral"&gt;mistral&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/uv"&gt;uv&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ollama"&gt;ollama&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pydantic"&gt;pydantic&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="python"/><category term="sandboxing"/><category term="ai"/><category term="deno"/><category term="generative-ai"/><category term="local-llms"/><category term="llms"/><category term="claude"/><category term="mistral"/><category term="llm-tool-use"/><category term="uv"/><category term="ollama"/><category term="pydantic"/><category term="model-context-protocol"/></entry><entry><title>llm-docsmith</title><link href="https://simonwillison.net/2025/Apr/10/llm-docsmith/#atom-tag" rel="alternate"/><published>2025-04-10T18:09:18+00:00</published><updated>2025-04-10T18:09:18+00:00</updated><id>https://simonwillison.net/2025/Apr/10/llm-docsmith/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://mathpn.com/posts/llm-docsmith/"&gt;llm-docsmith&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Matheus Pedroni released this neat plugin for LLM for adding docstrings to existing Python code. You can run it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;llm install llm-docsmith
llm docsmith ./scripts/main.py -o
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;-o&lt;/code&gt; option previews the changes that will be made - without &lt;code&gt;-o&lt;/code&gt; it edits the files directly.&lt;/p&gt;
&lt;p&gt;It also accepts a &lt;code&gt;-m claude-3.7-sonnet&lt;/code&gt; parameter for using an alternative model from the default (GPT-4o mini).&lt;/p&gt;
&lt;p&gt;The implementation uses the Python &lt;a href="https://pypi.org/project/libcst/"&gt;libcst&lt;/a&gt; "Concrete Syntax Tree" package to manipulate the code, which means there's no chance of it making edits to anything other than the docstrings.&lt;/p&gt;
&lt;p&gt;Here's &lt;a href="https://github.com/mathpn/llm-docsmith/blob/v0.1/docsmith.py#L10-L30"&gt;the full system prompt&lt;/a&gt; it uses.&lt;/p&gt;
&lt;p&gt;One neat trick is at the end of the system prompt it says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;You will receive a JSON template. Fill the slots marked with &amp;lt;SLOT&amp;gt; with the appropriate description. Return as JSON.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That template is actually provided JSON generated using these Pydantic classes:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;class&lt;/span&gt; &lt;span class="pl-v"&gt;Argument&lt;/span&gt;(&lt;span class="pl-v"&gt;BaseModel&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;description&lt;/span&gt;: &lt;span class="pl-smi"&gt;str&lt;/span&gt;
    &lt;span class="pl-s1"&gt;annotation&lt;/span&gt;: &lt;span class="pl-s1"&gt;str&lt;/span&gt; &lt;span class="pl-c1"&gt;|&lt;/span&gt; &lt;span class="pl-c1"&gt;None&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-c1"&gt;None&lt;/span&gt;
    &lt;span class="pl-s1"&gt;default&lt;/span&gt;: &lt;span class="pl-s1"&gt;str&lt;/span&gt; &lt;span class="pl-c1"&gt;|&lt;/span&gt; &lt;span class="pl-c1"&gt;None&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-c1"&gt;None&lt;/span&gt;

&lt;span class="pl-k"&gt;class&lt;/span&gt; &lt;span class="pl-v"&gt;Return&lt;/span&gt;(&lt;span class="pl-v"&gt;BaseModel&lt;/span&gt;):
    &lt;span class="pl-s1"&gt;description&lt;/span&gt;: &lt;span class="pl-smi"&gt;str&lt;/span&gt;
    &lt;span class="pl-s1"&gt;annotation&lt;/span&gt;: &lt;span class="pl-s1"&gt;str&lt;/span&gt; &lt;span class="pl-c1"&gt;|&lt;/span&gt; &lt;span class="pl-c1"&gt;None&lt;/span&gt;

&lt;span class="pl-k"&gt;class&lt;/span&gt; &lt;span class="pl-v"&gt;Docstring&lt;/span&gt;(&lt;span class="pl-v"&gt;BaseModel&lt;/span&gt;):
    &lt;span class="pl-s1"&gt;node_type&lt;/span&gt;: &lt;span class="pl-v"&gt;Literal&lt;/span&gt;[&lt;span class="pl-s"&gt;"class"&lt;/span&gt;, &lt;span class="pl-s"&gt;"function"&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;docstring&lt;/span&gt;: &lt;span class="pl-smi"&gt;str&lt;/span&gt;
    &lt;span class="pl-s1"&gt;args&lt;/span&gt;: &lt;span class="pl-s1"&gt;list&lt;/span&gt;[&lt;span class="pl-smi"&gt;Argument&lt;/span&gt;] &lt;span class="pl-c1"&gt;|&lt;/span&gt; &lt;span class="pl-c1"&gt;None&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-c1"&gt;None&lt;/span&gt;
    &lt;span class="pl-s1"&gt;ret&lt;/span&gt;: &lt;span class="pl-v"&gt;Return&lt;/span&gt; &lt;span class="pl-c1"&gt;|&lt;/span&gt; &lt;span class="pl-c1"&gt;None&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-c1"&gt;None&lt;/span&gt;

&lt;span class="pl-k"&gt;class&lt;/span&gt; &lt;span class="pl-v"&gt;Documentation&lt;/span&gt;(&lt;span class="pl-v"&gt;BaseModel&lt;/span&gt;):
    &lt;span class="pl-s1"&gt;entries&lt;/span&gt;: &lt;span class="pl-s1"&gt;list&lt;/span&gt;[&lt;span class="pl-smi"&gt;Docstring&lt;/span&gt;]&lt;/pre&gt;

&lt;p&gt;The code adds &lt;code&gt;&amp;lt;SLOT&amp;gt;&lt;/code&gt; notes to that in various places, so the template included in the prompt ends up looking like this:&lt;/p&gt;
&lt;pre&gt;{
  &lt;span class="pl-ent"&gt;"entries"&lt;/span&gt;: [
    {
      &lt;span class="pl-ent"&gt;"node_type"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;function&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
      &lt;span class="pl-ent"&gt;"name"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;create_docstring_node&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
      &lt;span class="pl-ent"&gt;"docstring"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&amp;lt;SLOT&amp;gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
      &lt;span class="pl-ent"&gt;"args"&lt;/span&gt;: [
        {
          &lt;span class="pl-ent"&gt;"name"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;docstring_text&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
          &lt;span class="pl-ent"&gt;"description"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&amp;lt;SLOT&amp;gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
          &lt;span class="pl-ent"&gt;"annotation"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;str&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
          &lt;span class="pl-ent"&gt;"default"&lt;/span&gt;: &lt;span class="pl-c1"&gt;null&lt;/span&gt;
        },
        {
          &lt;span class="pl-ent"&gt;"name"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;indent&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
          &lt;span class="pl-ent"&gt;"description"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&amp;lt;SLOT&amp;gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
          &lt;span class="pl-ent"&gt;"annotation"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;str&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
          &lt;span class="pl-ent"&gt;"default"&lt;/span&gt;: &lt;span class="pl-c1"&gt;null&lt;/span&gt;
        }
      ],
      &lt;span class="pl-ent"&gt;"ret"&lt;/span&gt;: {
        &lt;span class="pl-ent"&gt;"description"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&amp;lt;SLOT&amp;gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
        &lt;span class="pl-ent"&gt;"annotation"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;cst.BaseStatement&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
      }
    }
  ]
}&lt;/pre&gt;

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/pnmath/status/1909386592944292018"&gt;@pnmath&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/plugins"&gt;plugins&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/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/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pydantic"&gt;pydantic&lt;/a&gt;&lt;/p&gt;



</summary><category term="plugins"/><category term="python"/><category term="ai"/><category term="prompt-engineering"/><category term="generative-ai"/><category term="llm"/><category term="pydantic"/></entry><entry><title>Pydantic Evals</title><link href="https://simonwillison.net/2025/Apr/1/pydantic-evals/#atom-tag" rel="alternate"/><published>2025-04-01T04:43:56+00:00</published><updated>2025-04-01T04:43:56+00:00</updated><id>https://simonwillison.net/2025/Apr/1/pydantic-evals/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://ai.pydantic.dev/evals/"&gt;Pydantic Evals&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Brand new package from David Montague and the Pydantic AI team which directly tackles what I consider to be the single hardest problem in AI engineering: building evals to determine if your LLM-based system is working correctly and getting better over time.&lt;/p&gt;
&lt;p&gt;The feature is described as "in beta" and comes with this very realistic warning:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Unlike unit tests, evals are an emerging art/science; anyone who claims to know for sure exactly how your evals should be defined can safely be ignored.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This code example from their documentation illustrates the relationship between the two key nouns - Cases and Datasets:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;pydantic_evals&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-v"&gt;Case&lt;/span&gt;, &lt;span class="pl-v"&gt;Dataset&lt;/span&gt;

&lt;span class="pl-s1"&gt;case1&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;Case&lt;/span&gt;(
    &lt;span class="pl-s1"&gt;name&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;"simple_case"&lt;/span&gt;,
    &lt;span class="pl-s1"&gt;inputs&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;"What is the capital of France?"&lt;/span&gt;,
    &lt;span class="pl-s1"&gt;expected_output&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;"Paris"&lt;/span&gt;,
    &lt;span class="pl-s1"&gt;metadata&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;{&lt;span class="pl-s"&gt;"difficulty"&lt;/span&gt;: &lt;span class="pl-s"&gt;"easy"&lt;/span&gt;},
)

&lt;span class="pl-s1"&gt;dataset&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;Dataset&lt;/span&gt;(&lt;span class="pl-s1"&gt;cases&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;[&lt;span class="pl-s1"&gt;case1&lt;/span&gt;])&lt;/pre&gt;

&lt;p&gt;The library also supports custom evaluators, including LLM-as-a-judge:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-en"&gt;Case&lt;/span&gt;(
    &lt;span class="pl-s1"&gt;name&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;"vegetarian_recipe"&lt;/span&gt;,
    &lt;span class="pl-s1"&gt;inputs&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-en"&gt;CustomerOrder&lt;/span&gt;(
        &lt;span class="pl-s1"&gt;dish_name&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;"Spaghetti Bolognese"&lt;/span&gt;, &lt;span class="pl-s1"&gt;dietary_restriction&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;"vegetarian"&lt;/span&gt;
    ),
    &lt;span class="pl-s1"&gt;expected_output&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-c1"&gt;None&lt;/span&gt;,
    &lt;span class="pl-s1"&gt;metadata&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;{&lt;span class="pl-s"&gt;"focus"&lt;/span&gt;: &lt;span class="pl-s"&gt;"vegetarian"&lt;/span&gt;},
    &lt;span class="pl-s1"&gt;evaluators&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;(
        &lt;span class="pl-en"&gt;LLMJudge&lt;/span&gt;(
            &lt;span class="pl-s1"&gt;rubric&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;"Recipe should not contain meat or animal products"&lt;/span&gt;,
        ),
    ),
)&lt;/pre&gt;

&lt;p&gt;Cases and datasets can also be serialized to YAML.&lt;/p&gt;
&lt;p&gt;My first impressions are that this looks like a solid implementation of a sensible design. I'm looking forward to trying it out against a real project.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/samuel_colvin/status/1906841604377211375"&gt;@samuel_colvin&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &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/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/evals"&gt;evals&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pydantic"&gt;pydantic&lt;/a&gt;&lt;/p&gt;



</summary><category term="python"/><category term="ai"/><category term="generative-ai"/><category term="llms"/><category term="evals"/><category term="pydantic"/></entry><entry><title>googleapis/python-genai</title><link href="https://simonwillison.net/2024/Dec/12/python-genai/#atom-tag" rel="alternate"/><published>2024-12-12T16:21:46+00:00</published><updated>2024-12-12T16:21:46+00:00</updated><id>https://simonwillison.net/2024/Dec/12/python-genai/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/googleapis/python-genai"&gt;googleapis/python-genai&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Google released this brand new Python library for accessing their generative AI models yesterday, offering an alternative to their existing &lt;a href="https://github.com/google-gemini/generative-ai-python"&gt;generative-ai-python&lt;/a&gt; library.&lt;/p&gt;
&lt;p&gt;The API design looks very solid to me, and it includes both sync and async implementations. Here's an async streaming response:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async for response in client.aio.models.generate_content_stream(
    model='gemini-2.0-flash-exp',
    contents='Tell me a story in 300 words.'
):
    print(response.text)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It also includes Pydantic-based output schema support and some nice syntactic sugar for defining tools using Python functions.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/async"&gt;async&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/google"&gt;google&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/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/gemini"&gt;gemini&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/pydantic"&gt;pydantic&lt;/a&gt;&lt;/p&gt;



</summary><category term="async"/><category term="google"/><category term="python"/><category term="ai"/><category term="generative-ai"/><category term="llms"/><category term="gemini"/><category term="llm-tool-use"/><category term="pydantic"/></entry><entry><title>PydanticAI</title><link href="https://simonwillison.net/2024/Dec/2/pydanticai/#atom-tag" rel="alternate"/><published>2024-12-02T21:08:50+00:00</published><updated>2024-12-02T21:08:50+00:00</updated><id>https://simonwillison.net/2024/Dec/2/pydanticai/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://ai.pydantic.dev/"&gt;PydanticAI&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
New project from Pydantic, which they describe as an "Agent Framework / shim to use Pydantic with LLMs".&lt;/p&gt;
&lt;p&gt;I asked &lt;a href="https://twitter.com/simonw/status/1863567881553977819"&gt;which agent definition they are using&lt;/a&gt; and it's the "system prompt with bundled tools" one. To their credit, they explain that &lt;a href="https://ai.pydantic.dev/agents/"&gt;in their documentation&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The &lt;a href="https://ai.pydantic.dev/api/agent/"&gt;Agent&lt;/a&gt; has full API documentation, but conceptually you can think of an agent as a container for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;a href="https://ai.pydantic.dev/agents/#system-prompts"&gt;system prompt&lt;/a&gt; — a set of instructions for the LLM written by the developer&lt;/li&gt;
&lt;li&gt;One or more &lt;a href="https://ai.pydantic.dev/agents/#function-tools"&gt;retrieval tool&lt;/a&gt; — functions that the LLM may call to get information while generating a response&lt;/li&gt;
&lt;li&gt;An optional structured &lt;a href="https://ai.pydantic.dev/results/"&gt;result type&lt;/a&gt; — the structured datatype the LLM must return at the end of a run&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Given how many other existing tools already lean on Pydantic to help define JSON schemas for talking to LLMs this is an interesting complementary direction for Pydantic to take.&lt;/p&gt;
&lt;p&gt;There's some overlap here with my own &lt;a href="https://llm.datasette.io/"&gt;LLM&lt;/a&gt; project, which I still hope to add a function calling / tools abstraction to in the future.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/pydantic/status/1863538947059544218"&gt;@pydantic&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/python"&gt;python&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;a href="https://simonwillison.net/tags/ai-agents"&gt;ai-agents&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pydantic"&gt;pydantic&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/agent-definitions"&gt;agent-definitions&lt;/a&gt;&lt;/p&gt;



</summary><category term="python"/><category term="generative-ai"/><category term="llms"/><category term="llm"/><category term="llm-tool-use"/><category term="ai-agents"/><category term="pydantic"/><category term="agent-definitions"/></entry><entry><title>Themes from DjangoCon US 2024</title><link href="https://simonwillison.net/2024/Sep/27/themes-from-djangocon-us-2024/#atom-tag" rel="alternate"/><published>2024-09-27T23:36:02+00:00</published><updated>2024-09-27T23:36:02+00:00</updated><id>https://simonwillison.net/2024/Sep/27/themes-from-djangocon-us-2024/#atom-tag</id><summary type="html">
    &lt;p&gt;I just arrived home from a trip to Durham, North Carolina for DjangoCon US 2024. I’ve already written &lt;a href="https://simonwillison.net/2024/Sep/25/djp-a-plugin-system-for-django/"&gt;about my talk where I announced a new plugin system for Django&lt;/a&gt;; here are my notes on some of the other themes that resonated with me during the conference.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Sep/27/themes-from-djangocon-us-2014/#growing-the-django-software-foundation-dsf-"&gt;Growing the Django Software Foundation (DSF)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Sep/27/themes-from-djangocon-us-2014/#could-we-fund-a-django-lts-accessibility-audit-"&gt;Could we fund a Django LTS accessibility audit?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Sep/27/themes-from-djangocon-us-2014/#django-fellows-continue-to-provide-outstanding-value"&gt;Django fellows continue to provide outstanding value&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Sep/27/themes-from-djangocon-us-2014/#django-needs-feature-champions"&gt;Django needs feature champions&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Sep/27/themes-from-djangocon-us-2014/#htmx-fits-django-really-well"&gt;htmx fits Django really well&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Sep/27/themes-from-djangocon-us-2014/#django-ninja-has-positive-buzz"&gt;Django Ninja has positive buzz&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Sep/27/themes-from-djangocon-us-2014/#valkey-as-a-last-minute-sponsor"&gt;Valkey as a last-minute sponsor&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Sep/27/themes-from-djangocon-us-2014/#durham-has-a-world-class-collection-of-tubas"&gt;Durham has a world-class collection of tubas&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id="growing-the-django-software-foundation-dsf-"&gt;Growing the Django Software Foundation (DSF)&lt;/h4&gt;
&lt;p&gt;Jacob Kaplan-Moss gave &lt;a href="https://2024.djangocon.us/talks/if-we-had-1000000-what-could-the-dsf-do-with-4x-its-budget/"&gt;my favorite talk&lt;/a&gt; of the conference, asking what the Django Software Foundation could do if it quadrupled its annual income from $250,000 to $1 million dollars, and then mapping out a convincing path to get there.&lt;/p&gt;
&lt;p&gt;I really liked this diagram Jacob provided summarizing the foundation’s current income and expenditures. It’s pretty cool that $90,000 of annual income comes from individual donors, over a third of the total since corporate donors provide $160,000.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/dsf-diagram.jpg" alt="Financial breakdown diagram with the following numbers:  PLATINUM &amp;amp; GOLD: $125,000 CORPORATE DONORS: $160,000 BUDGET: $255,000 SILVER &amp;amp; BELOW: $35,000 INDIVIDUAL DONORS: $90,000  Spending:  WAGES (FELLOWS): $200,000 GRANTS: $35,000 OTHER: $5,000 FEES/HOSTING: $10,000 SURPLUS: $10,000​​​​​​​​​​​​​​​​" style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;Top priority would be hiring an Executive Director for the foundation, which is currently lead entirely by an elected, volunteer board. I’ve seen how useful a professional ED is from my own experiences &lt;a href="https://simonwillison.net/2024/Sep/18/board-of-the-python-software-foundation/"&gt;on the Python Software Foundation board&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Having someone working full time on the foundation outside of our current fellows - who have more than enough on their plates already - would enable the foundation to both take on more ambitious goals and also raise more money with which to tackle them.&lt;/p&gt;
&lt;p&gt;A line that Jacob used repeatedly in his talk about funding the foundation was this: if you or your organization &lt;em&gt;wouldn’t&lt;/em&gt; want to sponsor Django, he’d love to know why that is - understanding those blockers right now is almost as valuable as receiving actual cash. You can reach out to him at &lt;code&gt;jacob at djangoproject.com&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id="could-we-fund-a-django-lts-accessibility-audit-"&gt;Could we fund a Django LTS accessibility audit?&lt;/h4&gt;
&lt;p&gt;Django fellows and the &lt;a href="https://github.com/django/deps/blob/main/final/0011-accessibility-team.rst"&gt;Django Accessibility Team&lt;/a&gt; have been focusing significant effort on the accessibility of the Django admin. I found this very inspiring, and in combination with the talk of more funding for the foundation it put an idea in my head: what if every Django LTS release (once every two years) was backed by a full, professional accessibility audit, run by an agency staffed with developers who use screen readers?&lt;/p&gt;
&lt;p&gt;Imagine how much impact it would have if the default Django admin interface had excellent, documented accessibility out of the box. It could improve things for hundreds of thousands of users, and set an excellent precedent for projects (and foundations) in the wider open source community.&lt;/p&gt;
&lt;p&gt;This also feels to me like something that should be inherently attractive to sponsors. A lot of agencies use Django for government work, where accessibility is a requirement with teeth. Would one of those agencies like to be the “accessibility sponsor” for a major Django release?&lt;/p&gt;
&lt;h4 id="django-fellows-continue-to-provide-outstanding-value"&gt;Django fellows continue to provide outstanding value&lt;/h4&gt;
&lt;p&gt;The &lt;a href="https://www.djangoproject.com/fundraising/#fellowship-program"&gt;DSF’s fellowship program&lt;/a&gt; remains one of the most impactful initiatives I’ve seen anywhere for ensuring the ongoing sustainability of a community-driven open source project.&lt;/p&gt;
&lt;p&gt;Both of the current fellows, Natalia Bidart and Sarah Boyce, were in attendance and gave talks. It was great getting to meet them in person.&lt;/p&gt;
&lt;p&gt;If you’re not familiar with the program, the fellows are contractors who are paid by the DSF to keep the Django project ticking over - handling many of the somewhat less glamorous tasks of responsible open source maintenance such as ticket triage, release management, security fixes and code review.&lt;/p&gt;
&lt;p&gt;The fellows program is in its tenth year, and is a key reason that Django continues to release new versions &lt;a href="https://www.djangoproject.com/download/#supported-versions"&gt;on a regular schedule&lt;/a&gt; despite having no single corporate parent with paid developers.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/django-roadmap.png" alt="Software release timeline: 4.2 LTS (April 2023), 5.0 (August 2024), 5.1 (2025), 5.2 LTS (2026), 6.0 (2027), 6.1 (2027), 6.2 LTS (2028), 7.0 (2029). LTS versions have extended support periods." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;Unsurprisingly there is always more work than fellow capacity, hence Jacob’s desire to further expand the existing program.&lt;/p&gt;
&lt;p&gt;The fellows program launched with a policy that fellows should not work on new feature development. I believe this was partly related to interpretation of IRS nonprofit guidelines which have since been reconsidered, and there is a growing consensus now that this policy should be dropped.&lt;/p&gt;
&lt;h4 id="django-needs-feature-champions"&gt;Django needs feature champions&lt;/h4&gt;
&lt;p&gt;Django has a well deserved reputation for stability, reliability and a dependable release process. It has less of a reputation for constantly turning out ground-breaking new features.&lt;/p&gt;
&lt;p&gt;Long-time Django contributors who I talked to all had a similar position on this: the challenge here is that big new features need dedicated champions to both lead design and development on them and to push them through to completion.&lt;/p&gt;
&lt;p&gt;The pool of community members who are both willing and able to take on these larger projects is currently too small.&lt;/p&gt;
&lt;p&gt;There are a number of ways we could address this - most notably through investing financial resources in sponsoring feature development. This has worked well for Django in the past - Django’s migrations work was funded by &lt;a href="https://www.kickstarter.com/projects/andrewgodwin/schema-migrations-for-django"&gt;a Kickstarter campaign&lt;/a&gt; back in 2013.&lt;/p&gt;
&lt;p&gt;The Django Software Foundation will shortly be announcing details of elections for both the DSF board and the Django Steering Council. These are extremely influential positions for people who want to help solve some of these larger problems.&lt;/p&gt;
&lt;h4 id="htmx-fits-django-really-well"&gt;htmx fits Django really well&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://htmx.org/"&gt;htmx&lt;/a&gt; is an incredibly good fit for the uncodified Django community philosophy of building for the web. It came up in multiple talks. It feels like it may be a solution that the Django community has been seeking for years, as a very compelling alternative to writing everything in SPA JavaScript and using Django purely as a backend via something like Django REST Framework.&lt;/p&gt;
&lt;p&gt;I've been slightly resistant to embracing htmx myself purely because it's such a critical dependency and in the past I wasn't convinced of its staying power. It's now mature, stable and widely-enough used that I'm ready to consider it for my own long-term projects.&lt;/p&gt;
&lt;h4 id="django-ninja-has-positive-buzz"&gt;Django Ninja has positive buzz&lt;/h4&gt;
&lt;p&gt;I haven’t paid much attention to &lt;a href="https://django-ninja.dev/"&gt;Django Ninja&lt;/a&gt; but it had a lot of very positive buzz at the conference as well, as a tool for quickly building full-featured, performative API endpoints (thanks to Rust-backed &lt;a href="https://docs.pydantic.dev/"&gt;Pydantic&lt;/a&gt; for serialization) with &lt;a href="https://django-ninja.dev/#interactive-api-docs"&gt;interactive API docs&lt;/a&gt; powered by OpenAPI.&lt;/p&gt;
&lt;p&gt;I respect Django REST Framework a lot, but my personal programming style leans away from Class Based Views, which it uses quite a bit. Django Ninja looks like it might fit my function-view biases better.&lt;/p&gt;
&lt;p&gt;I wrote about Richard Terry’s excellent &lt;a href="https://github.com/radiac/nanodjango"&gt;nanodjango&lt;/a&gt; single-file Django application tool &lt;a href="https://simonwillison.net/2024/Sep/24/nanodjango/"&gt;the other day&lt;/a&gt; - Django Ninja comes baked into that project as well.&lt;/p&gt;
&lt;h4 id="valkey-as-a-last-minute-sponsor"&gt;Valkey as a last-minute sponsor&lt;/h4&gt;
&lt;p&gt;The three platinum sponsors for DjangoCon this year were &lt;a href="https://www.revsys.com/"&gt;REVSYS&lt;/a&gt;, &lt;a href="https://www.caktusgroup.com/"&gt;Caktus Group&lt;/a&gt; and &lt;a href="https://valkey.io/"&gt;Valkey&lt;/a&gt;. Valkey were a late and somewhat surprising addition to the sponsorship lineup.&lt;/p&gt;
&lt;p&gt;Valkey is the &lt;a href="https://www.linuxfoundation.org/press/linux-foundation-launches-open-source-valkey-community"&gt;Linux Foundation backed&lt;/a&gt; fork of Redis, created in response to Redis &lt;a href="https://redis.io/blog/redis-adopts-dual-source-available-licensing/"&gt;ditching their Open Source license&lt;/a&gt; (which I took quite personally, having contributed my own free effort to promoting and improving Redis in the past).&lt;/p&gt;
&lt;p&gt;Aside from expressing thanks to them, I usually don’t pay sponsors that much attention. For some reason this one hit differently - the fact that Valkey were ready to step in as a major sponsor despite being only a few months old has caused me to take that project a whole lot more seriously than I did before. I’ll certainly consider them next time I come across a Redis-shaped problem.&lt;/p&gt;
&lt;h4 id="durham-has-a-world-class-collection-of-tubas"&gt;Durham has a world-class collection of tubas&lt;/h4&gt;
&lt;p&gt;My favorite category of &lt;a href="https://www.niche-museums.com/"&gt;Niche Museum&lt;/a&gt; is one that's available by appointment only where the person who collected everything is available to show you around.&lt;/p&gt;
&lt;p&gt;I always check &lt;a href="https://www.atlasobscura.com/"&gt;Atlas Obscura&lt;/a&gt; any time I visit a new city, and this time I was delighted to learn about The Vincent and Ethel Simonetti Historic Tuba Collection!&lt;/p&gt;
&lt;p&gt;I promoted it in the DjangoCon US #outings Slack channel and got together a group of five conference attendees for a visit on Thursday, shortly before my flight.&lt;/p&gt;
&lt;p&gt;It was &lt;em&gt;peak&lt;/em&gt; Niche Museum. I’ve posted photos and notes over &lt;a href="https://www.niche-museums.com/112"&gt;on my Niche Museums&lt;/a&gt; website, the first new article there in quite a while.&lt;/p&gt;

&lt;p&gt;&lt;img alt="More than a dozen varied and beautiful tubas, each with a neat attached label." src="https://static.simonwillison.net/static/2024/tuba-collection-card.jpeg" /&gt;&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/accessibility"&gt;accessibility&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/conferences"&gt;conferences&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/djangocon"&gt;djangocon&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jacob-kaplan-moss"&gt;jacob-kaplan-moss&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/redis"&gt;redis&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/dsf"&gt;dsf&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pydantic"&gt;pydantic&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/htmx"&gt;htmx&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="accessibility"/><category term="conferences"/><category term="django"/><category term="djangocon"/><category term="jacob-kaplan-moss"/><category term="python"/><category term="redis"/><category term="dsf"/><category term="pydantic"/><category term="htmx"/></entry><entry><title>Jiter</title><link href="https://simonwillison.net/2024/Sep/22/jiter/#atom-tag" rel="alternate"/><published>2024-09-22T20:03:07+00:00</published><updated>2024-09-22T20:03:07+00:00</updated><id>https://simonwillison.net/2024/Sep/22/jiter/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/pydantic/jiter/tree/main/crates/jiter-python"&gt;Jiter&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
One of the challenges in dealing with LLM streaming APIs is the need to parse partial JSON - until the stream has ended you won't have a complete valid JSON object, but you may want to display components of that JSON as they become available.&lt;/p&gt;
&lt;p&gt;I've solved this previously using the &lt;a href="https://pypi.org/project/ijson/"&gt;ijson&lt;/a&gt; streaming JSON library, see &lt;a href="https://til.simonwillison.net/json/ijson-stream"&gt;my previous TIL&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Today I found out about Jiter, a new option from the team behind Pydantic. It's written in Rust and extracted from &lt;a href="https://github.com/pydantic/pydantic-core"&gt;pydantic-core&lt;/a&gt;, so the Python wrapper for it can be installed using:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install jiter
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can feed it an incomplete JSON bytes object and use &lt;code&gt;partial_mode="on"&lt;/code&gt; to parse the valid subset:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;jiter&lt;/span&gt;
&lt;span class="pl-s1"&gt;partial_json&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;b'{"name": "John", "age": 30, "city": "New Yor'&lt;/span&gt;
&lt;span class="pl-s1"&gt;jiter&lt;/span&gt;.&lt;span class="pl-en"&gt;from_json&lt;/span&gt;(&lt;span class="pl-s1"&gt;partial_json&lt;/span&gt;, &lt;span class="pl-s1"&gt;partial_mode&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;"on"&lt;/span&gt;)
&lt;span class="pl-c"&gt;# {'name': 'John', 'age': 30}&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;Or use &lt;code&gt;partial_mode="trailing-strings"&lt;/code&gt; to include incomplete string fields too:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-s1"&gt;jiter&lt;/span&gt;.&lt;span class="pl-en"&gt;from_json&lt;/span&gt;(&lt;span class="pl-s1"&gt;partial_json&lt;/span&gt;, &lt;span class="pl-s1"&gt;partial_mode&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;"trailing-strings"&lt;/span&gt;)
&lt;span class="pl-c"&gt;# {'name': 'John', 'age': 30, 'city': 'New Yor'}&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;The &lt;a href="https://github.com/pydantic/jiter/blob/ae5fc7d8548c90ad8762dfdf2ea6461776c2feb6/crates/jiter-python/README.md"&gt;current README&lt;/a&gt; was a little thin, so I submiitted &lt;a href="https://github.com/pydantic/jiter/pull/143"&gt;a PR&lt;/a&gt; with some extra examples. I &lt;a href="https://gist.github.com/simonw/264d487db1a18f8585c2ca0c68e50d1e"&gt;got some help&lt;/a&gt; from &lt;code&gt;files-to-prompt&lt;/code&gt; and Claude 3.5 Sonnet):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;cd crates/jiter-python/ &amp;amp;&amp;amp; files-to-prompt -c README.md tests | llm -m claude-3.5-sonnet --system 'write a new README with comprehensive documentation'&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=41615404#41618393"&gt;jackmpcollins on Hacker News&lt;/a&gt;&lt;/small&gt;&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/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/rust"&gt;rust&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai-assisted-programming"&gt;ai-assisted-programming&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pydantic"&gt;pydantic&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/files-to-prompt"&gt;files-to-prompt&lt;/a&gt;&lt;/p&gt;



</summary><category term="json"/><category term="python"/><category term="rust"/><category term="ai-assisted-programming"/><category term="pydantic"/><category term="files-to-prompt"/></entry><entry><title>Python Developers Survey 2023 Results</title><link href="https://simonwillison.net/2024/Sep/3/python-developers-survey-2023/#atom-tag" rel="alternate"/><published>2024-09-03T02:47:45+00:00</published><updated>2024-09-03T02:47:45+00:00</updated><id>https://simonwillison.net/2024/Sep/3/python-developers-survey-2023/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://lp.jetbrains.com/python-developers-survey-2023/"&gt;Python Developers Survey 2023 Results&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
The seventh annual Python survey is out. Here are the things that caught my eye or that I found surprising:&lt;/p&gt;
&lt;p&gt;25% of survey respondents had been programming in Python for less than a year, and 33% had less than a year of professional experience.&lt;/p&gt;
&lt;p&gt;37% of Python developers reported contributing to open-source projects last year - a new question for the survey. This is delightfully high!&lt;/p&gt;
&lt;p&gt;6% of users are still using Python 2. The survey notes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Almost half of Python 2 holdouts are under 21 years old and a third are students. Perhaps courses are still using Python 2?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In web frameworks, Flask and Django neck and neck at 33% each, but &lt;a href="https://fastapi.tiangolo.com/"&gt;FastAPI&lt;/a&gt; is a close third at 29%! &lt;a href="https://www.starlette.io/"&gt;Starlette&lt;/a&gt; is at 6%, but that's an under-count because it's the basis for FastAPI.&lt;/p&gt;
&lt;p&gt;The most popular library in "other framework and libraries" was BeautifulSoup with 31%, then Pillow 28%, then &lt;a href="https://github.com/opencv/opencv-python"&gt;OpenCV-Python&lt;/a&gt; at 22% (wow!) and Pydantic at 22%. Tkinter had 17%. These numbers are all a surprise to me.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.pytest.org/en/stable/"&gt;pytest&lt;/a&gt; scores 52% for unit testing, &lt;code&gt;unittest&lt;/code&gt; from the standard library just 25%. I'm glad to see &lt;code&gt;pytest&lt;/code&gt; so widely used, it's my favourite testing tool across any programming language.&lt;/p&gt;
&lt;p&gt;The top cloud providers are AWS, then Google Cloud Platform, then Azure... but &lt;a href="https://www.pythonanywhere.com/"&gt;PythonAnywhere&lt;/a&gt; (11%) took fourth place just ahead of DigitalOcean (10%). And &lt;a href="https://www.alibabacloud.com/"&gt;Alibaba Cloud&lt;/a&gt; is a new entrant in sixth place (after Heroku) with 4%. Heroku's ending of its free plan dropped them from 14% in 2021 to 7% now.&lt;/p&gt;
&lt;p&gt;Linux and Windows equal at 55%, macOS is at 29%. This was one of many multiple-choice questions that could add up to more than 100%.&lt;/p&gt;
&lt;p&gt;In databases, SQLite usage was trending down - 38% in 2021 to 34% for 2023, but still in second place behind PostgreSQL, stable at 43%.&lt;/p&gt;
&lt;p&gt;The survey incorporates quotes from different Python experts responding to the numbers, it's worth &lt;a href="https://lp.jetbrains.com/python-developers-survey-2023/"&gt;reading through the whole thing&lt;/a&gt;.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://pyfound.blogspot.com/2024/08/python-developers-survey-2023-results.html"&gt;PSF news&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/open-source"&gt;open-source&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/postgresql"&gt;postgresql&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/surveys"&gt;surveys&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pytest"&gt;pytest&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/psf"&gt;psf&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pydantic"&gt;pydantic&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/starlette"&gt;starlette&lt;/a&gt;&lt;/p&gt;



</summary><category term="open-source"/><category term="postgresql"/><category term="python"/><category term="sqlite"/><category term="surveys"/><category term="pytest"/><category term="psf"/><category term="pydantic"/><category term="starlette"/></entry><entry><title>OpenAI: Introducing Structured Outputs in the API</title><link href="https://simonwillison.net/2024/Aug/6/openai-structured-outputs/#atom-tag" rel="alternate"/><published>2024-08-06T18:32:25+00:00</published><updated>2024-08-06T18:32:25+00:00</updated><id>https://simonwillison.net/2024/Aug/6/openai-structured-outputs/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://openai.com/index/introducing-structured-outputs-in-the-api/"&gt;OpenAI: Introducing Structured Outputs in the API&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
OpenAI have offered structured outputs for a while now: you could specify &lt;code&gt;"response_format": {"type": "json_object"}}&lt;/code&gt; to request a valid JSON object, or you could use the &lt;a href="https://platform.openai.com/docs/guides/function-calling"&gt;function calling&lt;/a&gt; mechanism to request responses that match a specific schema.&lt;/p&gt;
&lt;p&gt;Neither of these modes were guaranteed to return valid JSON! In my experience they usually did, but there was always a chance that something could go wrong and the returned code could not match the schema, or even not be valid JSON at all.&lt;/p&gt;
&lt;p&gt;Outside of OpenAI techniques like &lt;a href="https://github.com/1rgs/jsonformer"&gt;jsonformer&lt;/a&gt; and &lt;a href="https://til.simonwillison.net/llms/llama-cpp-python-grammars"&gt;llama.cpp grammars&lt;/a&gt; could provide those guarantees against open weights models, by interacting directly with the next-token logic to ensure that only tokens that matched the required schema were selected.&lt;/p&gt;
&lt;p&gt;OpenAI credit that work in this announcement, so they're presumably using the same trick. They've provided two new ways to guarantee valid outputs. The first a new &lt;code&gt;"strict": true&lt;/code&gt; option for function definitions. The second is a new feature: a &lt;code&gt;"type": "json_schema"&lt;/code&gt; option for the &lt;code&gt;"response_format"&lt;/code&gt; field which lets you then pass a JSON schema (and another &lt;code&gt;"strict": true&lt;/code&gt; flag) to specify your required output.&lt;/p&gt;
&lt;p&gt;I've been using the existing &lt;code&gt;"tools"&lt;/code&gt; mechanism for exactly this already in my &lt;a href="https://github.com/datasette/datasette-extract"&gt;datasette-extract&lt;/a&gt; plugin - defining a function that I have no intention of executing just to get structured data out of the API in the shape that I want.&lt;/p&gt;
&lt;p&gt;Why isn't &lt;code&gt;"strict": true&lt;/code&gt; by default? Here's OpenAI's &lt;a href="https://news.ycombinator.com/item?id=41173223#41174306"&gt;Ted Sanders&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We didn't cover this in the announcement post, but there are a few reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The first request with each JSON schema will be slow, as we need to preprocess the JSON schema into a context-free grammar. If you don't want that latency hit (e.g., you're prototyping, or have a use case that uses variable one-off schemas), then you might prefer "strict": false&lt;/li&gt;
&lt;li&gt;You might have a schema that isn't covered by our subset of JSON schema. (To keep performance fast, we don't support some more complex/long-tail features.)&lt;/li&gt;
&lt;li&gt;In JSON mode and Structured Outputs, failures are rarer but more catastrophic. If the model gets too confused, it can get stuck in loops where it just prints technically valid output forever without ever closing the object. In these cases, you can end up waiting a minute for the request to hit the max_token limit, and you also have to pay for all those useless tokens. So if you have a really tricky schema, and you'd rather get frequent failures back quickly instead of infrequent failures back slowly, you might also want &lt;code&gt;"strict": false&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But in 99% of cases, you'll want &lt;code&gt;"strict": true&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;More &lt;a href="https://news.ycombinator.com/item?id=41173223#41174213"&gt;from Ted&lt;/a&gt; on how the new mode differs from function calling:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Under the hood, it's quite similar to function calling. A few differences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Structured Outputs is a bit more straightforward. e.g., you don't have to pretend you're writing a function where the second arg could be a two-page report to the user, and then pretend the "function" was called successfully by returning &lt;code&gt;{"success": true}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Having two interfaces lets us teach the model different default behaviors and styles, depending on which you use&lt;/li&gt;
&lt;li&gt;Another difference is that our current implementation of function calling can return both a text reply plus a function call (e.g., "Let me look up that flight for you"), whereas Structured Outputs will only return the JSON&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;The official &lt;code&gt;openai-python&lt;/code&gt; library also &lt;a href="https://github.com/openai/openai-python/commit/bf1ca86cf392eb0ffed1e146937c5d73d8a568f0"&gt;added structured output support&lt;/a&gt; this morning, based on Pydantic and looking very similar to the &lt;a href="https://python.useinstructor.com/"&gt;Instructor library&lt;/a&gt; (also credited as providing inspiration in their announcement).&lt;/p&gt;
&lt;p&gt;There are some key limitations on the new structured output mode, &lt;a href="https://platform.openai.com/docs/guides/structured-outputs/supported-schemas"&gt;described in the documentation&lt;/a&gt;. Only a subset of JSON schema is supported, and most notably the &lt;code&gt;"additionalProperties": false&lt;/code&gt; property must be set on all objects and all object keys must be listed in &lt;code&gt;"required"&lt;/code&gt; - no optional keys are allowed.&lt;/p&gt;
&lt;p&gt;Another interesting new feature: if the model denies a request on safety grounds a new &lt;a href="https://platform.openai.com/docs/guides/structured-outputs/refusals"&gt;refusal message&lt;/a&gt; will be returned:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  "message": {
    "role": "assistant",
    "refusal": "I'm sorry, I cannot assist with that request."
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, tucked away at the bottom of this announcement is a significant new model release with a major price cut:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;By switching to the new &lt;code&gt;gpt-4o-2024-08-06&lt;/code&gt;, developers save 50% on inputs ($2.50/1M input tokens) and 33% on outputs ($10.00/1M output tokens) compared to &lt;code&gt;gpt-4o-2024-05-13&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This new model &lt;a href="https://platform.openai.com/docs/models/gpt-4o"&gt;also supports&lt;/a&gt; 16,384 output tokens, up from 4,096.&lt;/p&gt;
&lt;p&gt;The price change is particularly notable because &lt;a href="https://simonwillison.net/2024/Jul/18/gpt-4o-mini/"&gt;GPT-4o-mini&lt;/a&gt;, the much cheaper alternative to GPT-4o, prices image inputs at the &lt;em&gt;same price&lt;/em&gt; as GPT-4o. This new model cuts that by half (&lt;a href="https://news.ycombinator.com/item?id=41173223#41174929"&gt;confirmed here&lt;/a&gt;), making &lt;code&gt;gpt-4o-2024-08-06&lt;/code&gt; the new cheapest model from OpenAI for handling image inputs.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/json"&gt;json&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/structured-extraction"&gt;structured-extraction&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pydantic"&gt;pydantic&lt;/a&gt;&lt;/p&gt;



</summary><category term="json"/><category term="ai"/><category term="openai"/><category term="generative-ai"/><category term="llms"/><category term="structured-extraction"/><category term="pydantic"/></entry><entry><title>Quoting James Bennett</title><link href="https://simonwillison.net/2023/Apr/7/james-bennett/#atom-tag" rel="alternate"/><published>2023-04-07T02:19:12+00:00</published><updated>2023-04-07T02:19:12+00:00</updated><id>https://simonwillison.net/2023/Apr/7/james-bennett/#atom-tag</id><summary type="html">
    &lt;blockquote cite="https://lobste.rs/s/2beggz/different_uses_python_type_hints#c_bbbae5"&gt;&lt;p&gt;Several libraries let you declare objects with type-hinted members and automatically derive validation rules and serialization/deserialization from the type hints – Pydantic is the most popular, but alternatives like msgspec are out there too. There’s also a whole new generation of web frameworks like FastAPI and Starlite which use type hints at runtime to do not just input validation and serialization/deserialization but also things like dependency injection.&lt;/p&gt;
&lt;p&gt;Personally, I’ve seen more significant gains in productivity from those runtime usages of Python’s type hints than from any static ahead-of-time type checking, which mostly is only useful to me as documentation.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p class="cite"&gt;&amp;mdash; &lt;a href="https://lobste.rs/s/2beggz/different_uses_python_type_hints#c_bbbae5"&gt;James Bennett&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/james-bennett"&gt;james-bennett&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pydantic"&gt;pydantic&lt;/a&gt;&lt;/p&gt;



</summary><category term="james-bennett"/><category term="python"/><category term="pydantic"/></entry><entry><title>The different uses of Python type hints</title><link href="https://simonwillison.net/2023/Apr/7/the-different-uses-of-python-type-hints/#atom-tag" rel="alternate"/><published>2023-04-07T02:17:04+00:00</published><updated>2023-04-07T02:17:04+00:00</updated><id>https://simonwillison.net/2023/Apr/7/the-different-uses-of-python-type-hints/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://lukeplant.me.uk/blog/posts/the-different-uses-of-python-type-hints/"&gt;The different uses of Python type hints&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Luke Plant describes five different categories for how Python optional types are being used today: IDE assistants, type checking, runtime behavior changes via introspection (e.g. Pydantic), code documentation, compiler instructions (ala mypyc)—and a bonus sixth, dependency injection.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://lobste.rs/s/2beggz/different_uses_python_type_hints"&gt;lobste.rs&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/luke-plant"&gt;luke-plant&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pydantic"&gt;pydantic&lt;/a&gt;&lt;/p&gt;



</summary><category term="luke-plant"/><category term="python"/><category term="pydantic"/></entry><entry><title>SQLModel</title><link href="https://simonwillison.net/2021/Aug/24/sqlmodel/#atom-tag" rel="alternate"/><published>2021-08-24T23:16:42+00:00</published><updated>2021-08-24T23:16:42+00:00</updated><id>https://simonwillison.net/2021/Aug/24/sqlmodel/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/tiangolo/sqlmodel"&gt;SQLModel&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
A new project by FastAPI creator Sebastián Ramírez: SQLModel builds on top of both SQLAlchemy and Sebastián’s Pydantic validation library to provide a new ORM that’s designed around Python 3’s optional typing. The real brilliance here is that a SQLModel subclass is simultaneously a valid SQLAlchemy ORM model AND a valid Pydantic validation model, saving on duplicate code by allowing the same class to be used both for form/API validation and for interacting with the database.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/orm"&gt;orm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sql"&gt;sql&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlalchemy"&gt;sqlalchemy&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pydantic"&gt;pydantic&lt;/a&gt;&lt;/p&gt;



</summary><category term="orm"/><category term="python"/><category term="sql"/><category term="sqlalchemy"/><category term="pydantic"/></entry></feed>