LLM integration for Datasette
Project description
datasette-llm
LLM integration for Datasette plugins.
This plugin provides a standard interface for Datasette plugins to use LLM models via the llm library, with:
- Model management: Control which models are available, with filtering and defaults
- API key management: Integration with datasette-secrets for secure key storage
- Hooks for extensibility: Track usage, enforce policies, implement accounting
Installation
Install this plugin in the same environment as Datasette:
datasette install datasette-llm
You'll also need at least one LLM model plugin installed:
# For OpenAI models
datasette install llm
# For Anthropic models
datasette install llm-anthropic
# For testing without API calls
datasette install llm-echo
Configuration
Configure the plugin in your datasette.yaml:
plugins:
datasette-llm:
# Default model when none specified
default_model: gpt-4o-mini
# Purpose-specific model defaults
purposes:
enrichments:
model: gpt-4o-mini # Cheap for bulk operations
sql-assistant:
model: gpt-4o # Smarter for complex queries
chat:
model: claude-3-5-sonnet
# Model availability (optional)
models: # Allowlist - only these models available
- gpt-4o-mini
- gpt-4o
- claude-3-5-sonnet
# Or use a blocklist instead
blocked_models:
- o1-preview # Too expensive
# Only show models with API keys configured (default: true)
require_keys: true
API Key Management
datasette-llm integrates with datasette-secrets for API key management. Keys are automatically registered for all installed model providers.
Setting up keys
-
Via environment variables (recommended for deployment):
export DATASETTE_SECRETS_OPENAI_API_KEY=sk-... export DATASETTE_SECRETS_ANTHROPIC_API_KEY=sk-ant-...
-
Via the web interface: Navigate to
/-/secrets(requiresmanage-secretspermission) -
Via llm CLI (fallback): Keys set with
llm keys set openaiare also used
Key resolution order
- datasette-secrets (env var
DATASETTE_SECRETS_<PROVIDER>_API_KEYor encrypted database) - llm's keys.json (
~/.config/io.datasette.llm/keys.json) - llm's environment variables (e.g.,
OPENAI_API_KEY)
Usage
Basic usage
from datasette_llm import LLM
async def my_plugin_view(datasette, request):
llm = LLM(datasette)
# Get a model (uses default if configured)
model = await llm.model()
# Or specify a model explicitly
model = await llm.model("gpt-4o-mini")
# Execute a prompt
response = await model.prompt("What is the capital of France?")
text = await response.text()
The purpose parameter
Specify a purpose to:
- Select the right default model for the task
- Enable purpose-based auditing and permissions
- Allow purpose-specific budget limits (via datasette-llm-accountant)
# Uses the model configured for "sql-assistant" purpose
model = await llm.model(purpose="sql-assistant")
# Or with explicit model (purpose still tracked)
model = await llm.model("gpt-4o", purpose="sql-assistant")
Streaming responses
model = await llm.model("gpt-4o-mini")
response = await model.prompt("Tell me a story")
# Non-streaming - wait for complete response
text = await response.text()
# Streaming - process chunks as they arrive
async for chunk in response:
print(chunk, end="", flush=True)
Grouping prompts
Use group() for batch operations where multiple prompts are logically related:
async def enrich_rows(datasette, rows):
llm = LLM(datasette)
# Model determined by purpose configuration
async with llm.group(purpose="enrichments") as model:
results = []
for row in rows:
response = await model.prompt(f"Summarize: {row['content']}")
text = await response.text()
results.append(text)
# All responses guaranteed complete here
return results
Benefits of group():
- Transactional semantics: All responses forced to complete on exit
- Shared context: Hooks can treat grouped prompts together (e.g., shared budget reservation)
- Cleanup: The
llm_group_exithook is called for settlement/logging
Listing available models
llm = LLM(datasette)
# Get all available models (filtered by config and key availability)
models = await llm.models()
for model in models:
print(model.model_id)
# Filter by actor (for per-user permissions)
models = await llm.models(actor=request.actor)
# Filter by purpose
models = await llm.models(purpose="enrichments")
Plugin Hooks
datasette-llm provides hooks for other plugins to extend LLM operations.
llm_prompt_context
Wrap prompt execution with custom logic:
from datasette import hookimpl
from contextlib import asynccontextmanager
@hookimpl
def llm_prompt_context(datasette, model_id, prompt, purpose):
@asynccontextmanager
async def wrapper(result):
# Before the prompt executes
print(f"Starting prompt to {model_id}")
yield
# After prompt() returns (response may still be streaming)
async def on_complete(response):
usage = await response.usage()
print(f"Used {usage.input} input, {usage.output} output tokens")
if result.response:
await result.response.on_done(on_complete)
return wrapper
llm_group_exit
Called when a group() context manager exits:
@hookimpl
def llm_group_exit(datasette, group):
# Can return a coroutine for async cleanup
async def cleanup():
print(f"Group for {group.purpose} completed")
print(f"Processed {len(group._responses)} prompts")
return cleanup()
llm_filter_models
Filter the list of available models:
@hookimpl
async def llm_filter_models(datasette, models, actor, purpose):
if not actor:
# Anonymous users get limited models
return [m for m in models if m.model_id == "gpt-4o-mini"]
# Check database for user's allowed models
db = datasette.get_database()
result = await db.execute(
"SELECT model_id FROM user_models WHERE user_id = ?",
[actor["id"]]
)
allowed = {row["model_id"] for row in result.rows}
return [m for m in models if m.model_id in allowed]
llm_default_model
Provide dynamic default model selection:
@hookimpl
async def llm_default_model(datasette, purpose, actor):
if actor:
# Check user's preferred model
db = datasette.get_database()
result = await db.execute(
"SELECT preferred_model FROM user_prefs WHERE user_id = ?",
[actor["id"]]
)
row = result.first()
if row:
return row["preferred_model"]
return None # Use config defaults
Related Plugins
- datasette-secrets: Secure API key storage (required dependency)
- datasette-llm-accountant: Budget management and cost tracking
Development
To set up this plugin locally:
cd datasette-llm
uv sync
# Confirm the plugin is visible
uv run datasette plugins
To run the tests:
uv run pytest
The test suite uses the llm-echo model which echoes back prompts without making API calls.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file datasette_llm-0.1a0.tar.gz.
File metadata
- Download URL: datasette_llm-0.1a0.tar.gz
- Upload date:
- Size: 18.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
116e8a1d2f6bbfeae556fcef12e2b0abdfa75739327c912f7ce4515f81e2c6f6
|
|
| MD5 |
c384eadfb013aa80f0a2f7df2df72c02
|
|
| BLAKE2b-256 |
63840cea4d4e19235b5ed446d9796ec27f45594db929cd94d82f142d30d8b549
|
Provenance
The following attestation bundles were made for datasette_llm-0.1a0.tar.gz:
Publisher:
publish.yml on datasette/datasette-llm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
datasette_llm-0.1a0.tar.gz -
Subject digest:
116e8a1d2f6bbfeae556fcef12e2b0abdfa75739327c912f7ce4515f81e2c6f6 - Sigstore transparency entry: 844915169
- Sigstore integration time:
-
Permalink:
datasette/datasette-llm@f8b1acd73b1b21ce68e6dfb97624afbb11c1997c -
Branch / Tag:
refs/tags/0.1a0 - Owner: https://github.com/datasette
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f8b1acd73b1b21ce68e6dfb97624afbb11c1997c -
Trigger Event:
release
-
Statement type:
File details
Details for the file datasette_llm-0.1a0-py3-none-any.whl.
File metadata
- Download URL: datasette_llm-0.1a0-py3-none-any.whl
- Upload date:
- Size: 14.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
57d91c8902e4dc8baedf851cd690e4d5b8ec6f3ae9c29a7ed4634c08eee4c2b1
|
|
| MD5 |
17deeec0d5fa7336f2a9833abd63576c
|
|
| BLAKE2b-256 |
9441b15784ea5563815a2d2a95a91bcacaeddb8de943ef8a97c617042d52973c
|
Provenance
The following attestation bundles were made for datasette_llm-0.1a0-py3-none-any.whl:
Publisher:
publish.yml on datasette/datasette-llm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
datasette_llm-0.1a0-py3-none-any.whl -
Subject digest:
57d91c8902e4dc8baedf851cd690e4d5b8ec6f3ae9c29a7ed4634c08eee4c2b1 - Sigstore transparency entry: 844915173
- Sigstore integration time:
-
Permalink:
datasette/datasette-llm@f8b1acd73b1b21ce68e6dfb97624afbb11c1997c -
Branch / Tag:
refs/tags/0.1a0 - Owner: https://github.com/datasette
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f8b1acd73b1b21ce68e6dfb97624afbb11c1997c -
Trigger Event:
release
-
Statement type: