Showing preview only (1,046K chars total). Download the full file or copy to clipboard to get everything.
Repository: simonw/llm
Branch: main
Commit: c7cf7e506ebe
Files: 96
Total size: 1006.4 KB
Directory structure:
gitextract_ij31elfv/
├── .github/
│ ├── FUNDING.yml
│ ├── dependabot.yml
│ └── workflows/
│ ├── cog.yml
│ ├── publish.yml
│ ├── stable-docs.yml
│ └── test.yml
├── .gitignore
├── .readthedocs.yaml
├── AGENTS.md
├── Justfile
├── LICENSE
├── MANIFEST.in
├── README.md
├── docs/
│ ├── .gitignore
│ ├── Makefile
│ ├── _templates/
│ │ └── base.html
│ ├── aliases.md
│ ├── changelog.md
│ ├── conf.py
│ ├── contributing.md
│ ├── embeddings/
│ │ ├── cli.md
│ │ ├── index.md
│ │ ├── python-api.md
│ │ ├── storage.md
│ │ └── writing-plugins.md
│ ├── fragments.md
│ ├── help.md
│ ├── index.md
│ ├── logging.md
│ ├── openai-models.md
│ ├── other-models.md
│ ├── plugins/
│ │ ├── advanced-model-plugins.md
│ │ ├── directory.md
│ │ ├── index.md
│ │ ├── installing-plugins.md
│ │ ├── llm-markov/
│ │ │ ├── llm_markov.py
│ │ │ └── pyproject.toml
│ │ ├── plugin-hooks.md
│ │ ├── plugin-utilities.md
│ │ └── tutorial-model-plugin.md
│ ├── python-api.md
│ ├── related-tools.md
│ ├── requirements.txt
│ ├── schemas.md
│ ├── setup.md
│ ├── templates.md
│ ├── tools.md
│ └── usage.md
├── llm/
│ ├── __init__.py
│ ├── __main__.py
│ ├── cli.py
│ ├── default_plugins/
│ │ ├── __init__.py
│ │ ├── default_tools.py
│ │ └── openai_models.py
│ ├── embeddings.py
│ ├── embeddings_migrations.py
│ ├── errors.py
│ ├── hookspecs.py
│ ├── migrations.py
│ ├── models.py
│ ├── plugins.py
│ ├── py.typed
│ ├── templates.py
│ ├── tools.py
│ └── utils.py
├── mypy.ini
├── pyproject.toml
├── pytest.ini
├── ruff.toml
└── tests/
├── cassettes/
│ ├── test_tools/
│ │ ├── test_tool_use_basic.yaml
│ │ └── test_tool_use_chain_of_two_calls.yaml
│ └── test_tools_streaming/
│ ├── test_tools_streaming_variant_a.yaml
│ ├── test_tools_streaming_variant_b.yaml
│ └── test_tools_streaming_variant_c.yaml
├── conftest.py
├── test-llm-load-plugins.sh
├── test_aliases.py
├── test_async.py
├── test_attachments.py
├── test_chat.py
├── test_chat_templates.py
├── test_cli_openai_models.py
├── test_cli_options.py
├── test_embed.py
├── test_embed_cli.py
├── test_encode_decode.py
├── test_fragments_cli.py
├── test_keys.py
├── test_llm.py
├── test_llm_logs.py
├── test_migrate.py
├── test_plugins.py
├── test_templates.py
├── test_tools.py
├── test_tools_streaming.py
└── test_utils.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
github: [simonw]
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: pip
directory: "/"
schedule:
interval: daily
groups:
python-packages:
patterns:
- "*"
================================================
FILE: .github/workflows/cog.yml
================================================
name: Run Cog
on:
pull_request:
types: [opened, synchronize]
permissions:
contents: write
pull-requests: write
jobs:
run-cog:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install . --group dev
- name: Run cog
run: |
cog -r -p "import sys, os; sys._called_from_test=True; os.environ['LLM_USER_PATH'] = '/tmp'" docs/**/*.md docs/*.md README.md
- name: Check for changes
id: check-changes
run: |
if [ -n "$(git diff)" ]; then
echo "changes=true" >> $GITHUB_OUTPUT
else
echo "changes=false" >> $GITHUB_OUTPUT
fi
- name: Commit and push if changed
if: steps.check-changes.outputs.changes == 'true'
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add -A
git commit -m "Ran cog"
git push
================================================
FILE: .github/workflows/publish.yml
================================================
name: Publish Python Package
on:
release:
types: [created]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: setup.py
- name: Install dependencies
run: |
pip install . --group dev
- name: Run tests
run: |
pytest
deploy:
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
needs: [test]
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.13'
cache: pip
cache-dependency-path: setup.py
- name: Install dependencies
run: |
pip install setuptools wheel build
- name: Build
run: |
python -m build
- name: Publish
uses: pypa/gh-action-pypi-publish@release/v1
================================================
FILE: .github/workflows/stable-docs.yml
================================================
name: Update Stable Docs
on:
release:
types: [published]
push:
branches:
- main
permissions:
contents: write
jobs:
update_stable_docs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0 # We need all commits to find docs/ changes
- name: Set up Git user
run: |
git config user.name "Automated"
git config user.email "actions@users.noreply.github.com"
- name: Create stable branch if it does not yet exist
run: |
if ! git ls-remote --heads origin stable | grep stable; then
git checkout -b stable
# If there are any releases, copy docs/ in from most recent
LATEST_RELEASE=$(git tag | sort -Vr | head -n1)
if [ -n "$LATEST_RELEASE" ]; then
rm -rf docs/
git checkout $LATEST_RELEASE -- docs/
fi
git commit -m "Populate docs/ from $LATEST_RELEASE" || echo "No changes"
git push -u origin stable
fi
- name: Handle Release
if: github.event_name == 'release' && !github.event.release.prerelease
run: |
git fetch --all
git checkout stable
git reset --hard ${GITHUB_REF#refs/tags/}
git push origin stable --force
- name: Handle Commit to Main
if: contains(github.event.head_commit.message, '!stable-docs')
run: |
git fetch origin
git checkout -b stable origin/stable
# Get the list of modified files in docs/ from the current commit
FILES=$(git diff-tree --no-commit-id --name-only -r ${{ github.sha }} -- docs/)
# Check if the list of files is non-empty
if [[ -n "$FILES" ]]; then
# Checkout those files to the stable branch to over-write with their contents
for FILE in $FILES; do
git checkout ${{ github.sha }} -- $FILE
done
git add docs/
git commit -m "Doc changes from ${{ github.sha }}"
git push origin stable
else
echo "No changes to docs/ in this commit."
exit 0
fi
================================================
FILE: .github/workflows/test.yml
================================================
name: Test
on: [push, pull_request]
permissions:
contents: read
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: setup.py
- name: Install dependencies
run: |
pip install . --group dev
- name: Run tests
run: |
python -m pytest -vv
- name: Check if cog needs to be run
if: matrix.os != 'windows-latest'
run: |
cog --check \
-p "import sys, os; sys._called_from_test=True; os.environ['LLM_USER_PATH'] = '/tmp'" \
docs/**/*.md docs/*.md
- name: Run Black
if: matrix.os != 'windows-latest'
run: |
black --check .
- name: Run mypy
if: matrix.os != 'windows-latest'
run: |
mypy llm
- name: Run ruff
if: matrix.os != 'windows-latest'
run: |
ruff check .
- name: Check it builds
run: |
python -m build
- name: Run test-llm-load-plugins.sh
if: matrix.os != 'windows-latest'
run: |
llm install llm-cluster llm-mistral
./tests/test-llm-load-plugins.sh
- name: Upload artifact of builds
if: matrix.python-version == '3.13' && matrix.os == 'ubuntu-latest'
uses: actions/upload-artifact@v4
with:
name: dist-${{ matrix.os }}-${{ matrix.python-version }}
path: dist/*
================================================
FILE: .gitignore
================================================
.venv
__pycache__/
*.py[cod]
*$py.class
venv
.eggs
.pytest_cache
*.egg-info
.DS_Store
.idea/
.vscode/
uv.lock
================================================
FILE: .readthedocs.yaml
================================================
version: 2
build:
os: ubuntu-22.04
tools:
python: "3.11"
sphinx:
configuration: docs/conf.py
formats:
- pdf
- epub
python:
install:
- requirements: docs/requirements.txt
- method: pip
path: .
================================================
FILE: AGENTS.md
================================================
# AGENTS.md
This project uses a Python environment for development and tests.
## Setting up a development environment
1. Install the project with its test dependencies:
```bash
pip install -e '.[test]'
```
2. Run the tests:
```bash
pytest
```
## Building the documentation
Run the following commands if you want to build the docs locally:
```bash
cd docs
pip install -r requirements.txt
make html
```
================================================
FILE: Justfile
================================================
# Run tests and linters
@default: test lint
# Run pytest with supplied options
@test *options:
uv run pytest {{options}}
# Run linters
@lint:
echo "Linters..."
echo " Black"
uv run black . --check
echo " cog"
uv run cog --check \
-p "import sys, os; sys._called_from_test=True; os.environ['LLM_USER_PATH'] = '/tmp'" \
README.md docs/*.md
echo " mypy"
uv run mypy llm
echo " ruff"
uv run ruff check .
# Run mypy
@mypy:
uv run mypy llm
# Rebuild docs with cog
@cog:
uv run cog -r -p "import sys, os; sys._called_from_test=True; os.environ['LLM_USER_PATH'] = '/tmp'" docs/**/*.md docs/*.md README.md
# Serve live docs on localhost:8000
@docs: cog
rm -rf docs/_build
cd docs && uv run make livehtml
# Apply Black
@black:
uv run black .
# Run automatic fixes
@fix: cog
uv run ruff check . --fix
uv run black .
# Push commit if tests pass
@push: test lint
git push
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: MANIFEST.in
================================================
global-exclude tests/*
================================================
FILE: README.md
================================================
<!-- [[[cog
# README.md is generated from docs/index.md using sphinx_markdown_builder
import tempfile
import subprocess
from pathlib import Path
readme_markdown = ''
with tempfile.TemporaryDirectory() as tmpdir:
tmp_path = Path(tmpdir)
# Run: sphinx-build -M markdown ./docs ./tmpdir
subprocess.run([
"sphinx-build",
"-M", "markdown",
"./docs",
str(tmp_path)
], check=True)
index_file = tmp_path / "markdown" / "index.md"
readme_markdown = index_file.read_text(encoding="utf-8")
cog.out(readme_markdown)
]]] -->
# LLM
[](https://github.com/simonw/llm)
[](https://pypi.org/project/llm/)
[](https://llm.datasette.io/en/stable/changelog.html)
[](https://github.com/simonw/llm/actions?query=workflow%3ATest)
[](https://github.com/simonw/llm/blob/main/LICENSE)
[](https://datasette.io/discord-llm)
[](https://formulae.brew.sh/formula/llm)
A CLI tool and Python library for interacting with **OpenAI**, **Anthropic’s Claude**, **Google’s Gemini**, **Meta’s Llama** and dozens of other Large Language Models, both via remote APIs and with models that can be installed and run on your own machine.
Watch **[Language models on the command-line](https://www.youtube.com/watch?v=QUXQNi6jQ30)** on YouTube for a demo or [read the accompanying detailed notes](https://simonwillison.net/2024/Jun/17/cli-language-models/).
With LLM you can:
- [Run prompts from the command-line](https://llm.datasette.io/en/stable/usage.html#usage-executing-prompts)
- [Store prompts and responses in SQLite](https://llm.datasette.io/en/stable/logging.html#logging)
- [Generate and store embeddings](https://llm.datasette.io/en/stable/embeddings/index.html#embeddings)
- [Extract structured content from text and images](https://llm.datasette.io/en/stable/schemas.html#schemas)
- [Grant models the ability to execute tools](https://llm.datasette.io/en/stable/tools.html#tools)
- … and much, much more
## Quick start
First, install LLM using `pip` or Homebrew or `pipx` or `uv`:
```bash
pip install llm
```
Or with Homebrew (see [warning note](https://llm.datasette.io/en/stable/setup.html#homebrew-warning)):
```bash
brew install llm
```
Or with [pipx](https://pypa.github.io/pipx/):
```bash
pipx install llm
```
Or with [uv](https://docs.astral.sh/uv/guides/tools/)
```bash
uv tool install llm
```
If you have an [OpenAI API key](https://platform.openai.com/api-keys) key you can run this:
```bash
# Paste your OpenAI API key into this
llm keys set openai
# Run a prompt (with the default gpt-4o-mini model)
llm "Ten fun names for a pet pelican"
# Extract text from an image
llm "extract text" -a scanned-document.jpg
# Use a system prompt against a file
cat myfile.py | llm -s "Explain this code"
```
Run prompts against [Gemini](https://aistudio.google.com/apikey) or [Anthropic](https://console.anthropic.com/) with their respective plugins:
```bash
llm install llm-gemini
llm keys set gemini
# Paste Gemini API key here
llm -m gemini-2.0-flash 'Tell me fun facts about Mountain View'
llm install llm-anthropic
llm keys set anthropic
# Paste Anthropic API key here
llm -m claude-4-opus 'Impress me with wild facts about turnips'
```
You can also [install a plugin](https://llm.datasette.io/en/stable/plugins/installing-plugins.html#installing-plugins) to access models that can run on your local device. If you use [Ollama](https://ollama.com/):
```bash
# Install the plugin
llm install llm-ollama
# Download and run a prompt against the Orca Mini 7B model
ollama pull llama3.2:latest
llm -m llama3.2:latest 'What is the capital of France?'
```
To start [an interactive chat](https://llm.datasette.io/en/stable/usage.html#usage-chat) with a model, use `llm chat`:
```bash
llm chat -m gpt-4.1
```
```default
Chatting with gpt-4.1
Type 'exit' or 'quit' to exit
Type '!multi' to enter multiple lines, then '!end' to finish
Type '!edit' to open your default editor and modify the prompt.
Type '!fragment <my_fragment> [<another_fragment> ...]' to insert one or more fragments
> Tell me a joke about a pelican
Why don't pelicans like to tip waiters?
Because they always have a big bill!
```
More background on this project:
- [llm, ttok and strip-tags—CLI tools for working with ChatGPT and other LLMs](https://simonwillison.net/2023/May/18/cli-tools-for-llms/)
- [The LLM CLI tool now supports self-hosted language models via plugins](https://simonwillison.net/2023/Jul/12/llm/)
- [LLM now provides tools for working with embeddings](https://simonwillison.net/2023/Sep/4/llm-embeddings/)
- [Build an image search engine with llm-clip, chat with models with llm chat](https://simonwillison.net/2023/Sep/12/llm-clip-and-chat/)
- [You can now run prompts against images, audio and video in your terminal using LLM](https://simonwillison.net/2024/Oct/29/llm-multi-modal/)
- [Structured data extraction from unstructured content using LLM schemas](https://simonwillison.net/2025/Feb/28/llm-schemas/)
- [Long context support in LLM 0.24 using fragments and template plugins](https://simonwillison.net/2025/Apr/7/long-context-llm/)
See also [the llm tag](https://simonwillison.net/tags/llm/) on my blog.
## Contents
* [Setup](https://llm.datasette.io/en/stable/setup.html)
* [Installation](https://llm.datasette.io/en/stable/setup.html#installation)
* [Upgrading to the latest version](https://llm.datasette.io/en/stable/setup.html#upgrading-to-the-latest-version)
* [Using uvx](https://llm.datasette.io/en/stable/setup.html#using-uvx)
* [A note about Homebrew and PyTorch](https://llm.datasette.io/en/stable/setup.html#a-note-about-homebrew-and-pytorch)
* [Installing plugins](https://llm.datasette.io/en/stable/setup.html#installing-plugins)
* [API key management](https://llm.datasette.io/en/stable/setup.html#api-key-management)
* [Saving and using stored keys](https://llm.datasette.io/en/stable/setup.html#saving-and-using-stored-keys)
* [Passing keys using the –key option](https://llm.datasette.io/en/stable/setup.html#passing-keys-using-the-key-option)
* [Keys in environment variables](https://llm.datasette.io/en/stable/setup.html#keys-in-environment-variables)
* [Configuration](https://llm.datasette.io/en/stable/setup.html#configuration)
* [Setting a custom default model](https://llm.datasette.io/en/stable/setup.html#setting-a-custom-default-model)
* [Setting a custom directory location](https://llm.datasette.io/en/stable/setup.html#setting-a-custom-directory-location)
* [Turning SQLite logging on and off](https://llm.datasette.io/en/stable/setup.html#turning-sqlite-logging-on-and-off)
* [Usage](https://llm.datasette.io/en/stable/usage.html)
* [Executing a prompt](https://llm.datasette.io/en/stable/usage.html#executing-a-prompt)
* [Model options](https://llm.datasette.io/en/stable/usage.html#model-options)
* [Attachments](https://llm.datasette.io/en/stable/usage.html#attachments)
* [System prompts](https://llm.datasette.io/en/stable/usage.html#system-prompts)
* [Tools](https://llm.datasette.io/en/stable/usage.html#tools)
* [Extracting fenced code blocks](https://llm.datasette.io/en/stable/usage.html#extracting-fenced-code-blocks)
* [Schemas](https://llm.datasette.io/en/stable/usage.html#schemas)
* [Fragments](https://llm.datasette.io/en/stable/usage.html#fragments)
* [Continuing a conversation](https://llm.datasette.io/en/stable/usage.html#continuing-a-conversation)
* [Tips for using LLM with Bash or Zsh](https://llm.datasette.io/en/stable/usage.html#tips-for-using-llm-with-bash-or-zsh)
* [Completion prompts](https://llm.datasette.io/en/stable/usage.html#completion-prompts)
* [Starting an interactive chat](https://llm.datasette.io/en/stable/usage.html#starting-an-interactive-chat)
* [Listing available models](https://llm.datasette.io/en/stable/usage.html#listing-available-models)
* [Setting default options for models](https://llm.datasette.io/en/stable/usage.html#setting-default-options-for-models)
* [OpenAI models](https://llm.datasette.io/en/stable/openai-models.html)
* [Configuration](https://llm.datasette.io/en/stable/openai-models.html#configuration)
* [OpenAI language models](https://llm.datasette.io/en/stable/openai-models.html#openai-language-models)
* [Model features](https://llm.datasette.io/en/stable/openai-models.html#model-features)
* [OpenAI embedding models](https://llm.datasette.io/en/stable/openai-models.html#openai-embedding-models)
* [OpenAI completion models](https://llm.datasette.io/en/stable/openai-models.html#openai-completion-models)
* [Adding more OpenAI models](https://llm.datasette.io/en/stable/openai-models.html#adding-more-openai-models)
* [Other models](https://llm.datasette.io/en/stable/other-models.html)
* [Installing and using a local model](https://llm.datasette.io/en/stable/other-models.html#installing-and-using-a-local-model)
* [OpenAI-compatible models](https://llm.datasette.io/en/stable/other-models.html#openai-compatible-models)
* [Extra HTTP headers](https://llm.datasette.io/en/stable/other-models.html#extra-http-headers)
* [Tools](https://llm.datasette.io/en/stable/tools.html)
* [How tools work](https://llm.datasette.io/en/stable/tools.html#how-tools-work)
* [Trying out tools](https://llm.datasette.io/en/stable/tools.html#trying-out-tools)
* [LLM’s implementation of tools](https://llm.datasette.io/en/stable/tools.html#llm-s-implementation-of-tools)
* [Default tools](https://llm.datasette.io/en/stable/tools.html#default-tools)
* [Tips for implementing tools](https://llm.datasette.io/en/stable/tools.html#tips-for-implementing-tools)
* [Schemas](https://llm.datasette.io/en/stable/schemas.html)
* [Schemas tutorial](https://llm.datasette.io/en/stable/schemas.html#schemas-tutorial)
* [Getting started with dogs](https://llm.datasette.io/en/stable/schemas.html#getting-started-with-dogs)
* [Extracting people from a news articles](https://llm.datasette.io/en/stable/schemas.html#extracting-people-from-a-news-articles)
* [Using JSON schemas](https://llm.datasette.io/en/stable/schemas.html#using-json-schemas)
* [Ways to specify a schema](https://llm.datasette.io/en/stable/schemas.html#ways-to-specify-a-schema)
* [Concise LLM schema syntax](https://llm.datasette.io/en/stable/schemas.html#concise-llm-schema-syntax)
* [Saving reusable schemas in templates](https://llm.datasette.io/en/stable/schemas.html#saving-reusable-schemas-in-templates)
* [Browsing logged JSON objects created using schemas](https://llm.datasette.io/en/stable/schemas.html#browsing-logged-json-objects-created-using-schemas)
* [Templates](https://llm.datasette.io/en/stable/templates.html)
* [Getting started with <code>–save</code>](https://llm.datasette.io/en/stable/templates.html#getting-started-with-save)
* [Using a template](https://llm.datasette.io/en/stable/templates.html#using-a-template)
* [Listing available templates](https://llm.datasette.io/en/stable/templates.html#listing-available-templates)
* [Templates as YAML files](https://llm.datasette.io/en/stable/templates.html#templates-as-yaml-files)
* [System prompts](https://llm.datasette.io/en/stable/templates.html#system-prompts)
* [Fragments](https://llm.datasette.io/en/stable/templates.html#fragments)
* [Options](https://llm.datasette.io/en/stable/templates.html#options)
* [Tools](https://llm.datasette.io/en/stable/templates.html#tools)
* [Schemas](https://llm.datasette.io/en/stable/templates.html#schemas)
* [Additional template variables](https://llm.datasette.io/en/stable/templates.html#additional-template-variables)
* [Specifying default parameters](https://llm.datasette.io/en/stable/templates.html#specifying-default-parameters)
* [Configuring code extraction](https://llm.datasette.io/en/stable/templates.html#configuring-code-extraction)
* [Setting a default model for a template](https://llm.datasette.io/en/stable/templates.html#setting-a-default-model-for-a-template)
* [Template loaders from plugins](https://llm.datasette.io/en/stable/templates.html#template-loaders-from-plugins)
* [Fragments](https://llm.datasette.io/en/stable/fragments.html)
* [Using fragments in a prompt](https://llm.datasette.io/en/stable/fragments.html#using-fragments-in-a-prompt)
* [Using fragments in chat](https://llm.datasette.io/en/stable/fragments.html#using-fragments-in-chat)
* [Browsing fragments](https://llm.datasette.io/en/stable/fragments.html#browsing-fragments)
* [Setting aliases for fragments](https://llm.datasette.io/en/stable/fragments.html#setting-aliases-for-fragments)
* [Viewing fragments in your logs](https://llm.datasette.io/en/stable/fragments.html#viewing-fragments-in-your-logs)
* [Using fragments from plugins](https://llm.datasette.io/en/stable/fragments.html#using-fragments-from-plugins)
* [Listing available fragment prefixes](https://llm.datasette.io/en/stable/fragments.html#listing-available-fragment-prefixes)
* [Model aliases](https://llm.datasette.io/en/stable/aliases.html)
* [Listing aliases](https://llm.datasette.io/en/stable/aliases.html#listing-aliases)
* [Adding a new alias](https://llm.datasette.io/en/stable/aliases.html#adding-a-new-alias)
* [Removing an alias](https://llm.datasette.io/en/stable/aliases.html#removing-an-alias)
* [Viewing the aliases file](https://llm.datasette.io/en/stable/aliases.html#viewing-the-aliases-file)
* [Embeddings](https://llm.datasette.io/en/stable/embeddings/index.html)
* [Embedding with the CLI](https://llm.datasette.io/en/stable/embeddings/cli.html)
* [llm embed](https://llm.datasette.io/en/stable/embeddings/cli.html#llm-embed)
* [llm embed-multi](https://llm.datasette.io/en/stable/embeddings/cli.html#llm-embed-multi)
* [llm similar](https://llm.datasette.io/en/stable/embeddings/cli.html#llm-similar)
* [llm embed-models](https://llm.datasette.io/en/stable/embeddings/cli.html#llm-embed-models)
* [llm collections list](https://llm.datasette.io/en/stable/embeddings/cli.html#llm-collections-list)
* [llm collections delete](https://llm.datasette.io/en/stable/embeddings/cli.html#llm-collections-delete)
* [Using embeddings from Python](https://llm.datasette.io/en/stable/embeddings/python-api.html)
* [Working with collections](https://llm.datasette.io/en/stable/embeddings/python-api.html#working-with-collections)
* [Retrieving similar items](https://llm.datasette.io/en/stable/embeddings/python-api.html#retrieving-similar-items)
* [SQL schema](https://llm.datasette.io/en/stable/embeddings/python-api.html#sql-schema)
* [Writing plugins to add new embedding models](https://llm.datasette.io/en/stable/embeddings/writing-plugins.html)
* [Embedding binary content](https://llm.datasette.io/en/stable/embeddings/writing-plugins.html#embedding-binary-content)
* [Embedding storage format](https://llm.datasette.io/en/stable/embeddings/storage.html)
* [Plugins](https://llm.datasette.io/en/stable/plugins/index.html)
* [Installing plugins](https://llm.datasette.io/en/stable/plugins/installing-plugins.html)
* [Listing installed plugins](https://llm.datasette.io/en/stable/plugins/installing-plugins.html#listing-installed-plugins)
* [Running with a subset of plugins](https://llm.datasette.io/en/stable/plugins/installing-plugins.html#running-with-a-subset-of-plugins)
* [Plugin directory](https://llm.datasette.io/en/stable/plugins/directory.html)
* [Local models](https://llm.datasette.io/en/stable/plugins/directory.html#local-models)
* [Remote APIs](https://llm.datasette.io/en/stable/plugins/directory.html#remote-apis)
* [Tools](https://llm.datasette.io/en/stable/plugins/directory.html#tools)
* [Fragments and template loaders](https://llm.datasette.io/en/stable/plugins/directory.html#fragments-and-template-loaders)
* [Embedding models](https://llm.datasette.io/en/stable/plugins/directory.html#embedding-models)
* [Extra commands](https://llm.datasette.io/en/stable/plugins/directory.html#extra-commands)
* [Just for fun](https://llm.datasette.io/en/stable/plugins/directory.html#just-for-fun)
* [Plugin hooks](https://llm.datasette.io/en/stable/plugins/plugin-hooks.html)
* [register_commands(cli)](https://llm.datasette.io/en/stable/plugins/plugin-hooks.html#register-commands-cli)
* [register_models(register)](https://llm.datasette.io/en/stable/plugins/plugin-hooks.html#register-models-register)
* [register_embedding_models(register)](https://llm.datasette.io/en/stable/plugins/plugin-hooks.html#register-embedding-models-register)
* [register_tools(register)](https://llm.datasette.io/en/stable/plugins/plugin-hooks.html#register-tools-register)
* [register_template_loaders(register)](https://llm.datasette.io/en/stable/plugins/plugin-hooks.html#register-template-loaders-register)
* [register_fragment_loaders(register)](https://llm.datasette.io/en/stable/plugins/plugin-hooks.html#register-fragment-loaders-register)
* [Developing a model plugin](https://llm.datasette.io/en/stable/plugins/tutorial-model-plugin.html)
* [The initial structure of the plugin](https://llm.datasette.io/en/stable/plugins/tutorial-model-plugin.html#the-initial-structure-of-the-plugin)
* [Installing your plugin to try it out](https://llm.datasette.io/en/stable/plugins/tutorial-model-plugin.html#installing-your-plugin-to-try-it-out)
* [Building the Markov chain](https://llm.datasette.io/en/stable/plugins/tutorial-model-plugin.html#building-the-markov-chain)
* [Executing the Markov chain](https://llm.datasette.io/en/stable/plugins/tutorial-model-plugin.html#executing-the-markov-chain)
* [Adding that to the plugin](https://llm.datasette.io/en/stable/plugins/tutorial-model-plugin.html#adding-that-to-the-plugin)
* [Understanding execute()](https://llm.datasette.io/en/stable/plugins/tutorial-model-plugin.html#understanding-execute)
* [Prompts and responses are logged to the database](https://llm.datasette.io/en/stable/plugins/tutorial-model-plugin.html#prompts-and-responses-are-logged-to-the-database)
* [Adding options](https://llm.datasette.io/en/stable/plugins/tutorial-model-plugin.html#adding-options)
* [Distributing your plugin](https://llm.datasette.io/en/stable/plugins/tutorial-model-plugin.html#distributing-your-plugin)
* [GitHub repositories](https://llm.datasette.io/en/stable/plugins/tutorial-model-plugin.html#github-repositories)
* [Publishing plugins to PyPI](https://llm.datasette.io/en/stable/plugins/tutorial-model-plugin.html#publishing-plugins-to-pypi)
* [Adding metadata](https://llm.datasette.io/en/stable/plugins/tutorial-model-plugin.html#adding-metadata)
* [What to do if it breaks](https://llm.datasette.io/en/stable/plugins/tutorial-model-plugin.html#what-to-do-if-it-breaks)
* [Advanced model plugins](https://llm.datasette.io/en/stable/plugins/advanced-model-plugins.html)
* [Tip: lazily load expensive dependencies](https://llm.datasette.io/en/stable/plugins/advanced-model-plugins.html#tip-lazily-load-expensive-dependencies)
* [Models that accept API keys](https://llm.datasette.io/en/stable/plugins/advanced-model-plugins.html#models-that-accept-api-keys)
* [Async models](https://llm.datasette.io/en/stable/plugins/advanced-model-plugins.html#async-models)
* [Supporting schemas](https://llm.datasette.io/en/stable/plugins/advanced-model-plugins.html#supporting-schemas)
* [Supporting tools](https://llm.datasette.io/en/stable/plugins/advanced-model-plugins.html#supporting-tools)
* [Attachments for multi-modal models](https://llm.datasette.io/en/stable/plugins/advanced-model-plugins.html#attachments-for-multi-modal-models)
* [Tracking token usage](https://llm.datasette.io/en/stable/plugins/advanced-model-plugins.html#tracking-token-usage)
* [Tracking resolved model names](https://llm.datasette.io/en/stable/plugins/advanced-model-plugins.html#tracking-resolved-model-names)
* [LLM_RAISE_ERRORS](https://llm.datasette.io/en/stable/plugins/advanced-model-plugins.html#llm-raise-errors)
* [Utility functions for plugins](https://llm.datasette.io/en/stable/plugins/plugin-utilities.html)
* [llm.get_key()](https://llm.datasette.io/en/stable/plugins/plugin-utilities.html#llm-get-key)
* [llm.user_dir()](https://llm.datasette.io/en/stable/plugins/plugin-utilities.html#llm-user-dir)
* [llm.ModelError](https://llm.datasette.io/en/stable/plugins/plugin-utilities.html#llm-modelerror)
* [Response.fake()](https://llm.datasette.io/en/stable/plugins/plugin-utilities.html#response-fake)
* [Python API](https://llm.datasette.io/en/stable/python-api.html)
* [Basic prompt execution](https://llm.datasette.io/en/stable/python-api.html#basic-prompt-execution)
* [System prompts](https://llm.datasette.io/en/stable/python-api.html#system-prompts)
* [Attachments](https://llm.datasette.io/en/stable/python-api.html#attachments)
* [Tools](https://llm.datasette.io/en/stable/python-api.html#tools)
* [Schemas](https://llm.datasette.io/en/stable/python-api.html#schemas)
* [Fragments](https://llm.datasette.io/en/stable/python-api.html#fragments)
* [Model options](https://llm.datasette.io/en/stable/python-api.html#model-options)
* [Passing an API key](https://llm.datasette.io/en/stable/python-api.html#passing-an-api-key)
* [Models from plugins](https://llm.datasette.io/en/stable/python-api.html#models-from-plugins)
* [Accessing the underlying JSON](https://llm.datasette.io/en/stable/python-api.html#accessing-the-underlying-json)
* [Token usage](https://llm.datasette.io/en/stable/python-api.html#token-usage)
* [Streaming responses](https://llm.datasette.io/en/stable/python-api.html#streaming-responses)
* [Async models](https://llm.datasette.io/en/stable/python-api.html#async-models)
* [Tool functions can be sync or async](https://llm.datasette.io/en/stable/python-api.html#tool-functions-can-be-sync-or-async)
* [Tool use for async models](https://llm.datasette.io/en/stable/python-api.html#tool-use-for-async-models)
* [Conversations](https://llm.datasette.io/en/stable/python-api.html#conversations)
* [Conversations using tools](https://llm.datasette.io/en/stable/python-api.html#conversations-using-tools)
* [Listing models](https://llm.datasette.io/en/stable/python-api.html#listing-models)
* [Running code when a response has completed](https://llm.datasette.io/en/stable/python-api.html#running-code-when-a-response-has-completed)
* [Other functions](https://llm.datasette.io/en/stable/python-api.html#other-functions)
* [set_alias(alias, model_id)](https://llm.datasette.io/en/stable/python-api.html#set-alias-alias-model-id)
* [remove_alias(alias)](https://llm.datasette.io/en/stable/python-api.html#remove-alias-alias)
* [set_default_model(alias)](https://llm.datasette.io/en/stable/python-api.html#set-default-model-alias)
* [get_default_model()](https://llm.datasette.io/en/stable/python-api.html#get-default-model)
* [set_default_embedding_model(alias) and get_default_embedding_model()](https://llm.datasette.io/en/stable/python-api.html#set-default-embedding-model-alias-and-get-default-embedding-model)
* [Logging to SQLite](https://llm.datasette.io/en/stable/logging.html)
* [Viewing the logs](https://llm.datasette.io/en/stable/logging.html#viewing-the-logs)
* [-s/–short mode](https://llm.datasette.io/en/stable/logging.html#s-short-mode)
* [Logs for a conversation](https://llm.datasette.io/en/stable/logging.html#logs-for-a-conversation)
* [Searching the logs](https://llm.datasette.io/en/stable/logging.html#searching-the-logs)
* [Filtering past a specific ID](https://llm.datasette.io/en/stable/logging.html#filtering-past-a-specific-id)
* [Filtering by model](https://llm.datasette.io/en/stable/logging.html#filtering-by-model)
* [Filtering by prompts that used specific fragments](https://llm.datasette.io/en/stable/logging.html#filtering-by-prompts-that-used-specific-fragments)
* [Filtering by prompts that used specific tools](https://llm.datasette.io/en/stable/logging.html#filtering-by-prompts-that-used-specific-tools)
* [Browsing data collected using schemas](https://llm.datasette.io/en/stable/logging.html#browsing-data-collected-using-schemas)
* [Browsing logs using Datasette](https://llm.datasette.io/en/stable/logging.html#browsing-logs-using-datasette)
* [Backing up your database](https://llm.datasette.io/en/stable/logging.html#backing-up-your-database)
* [SQL schema](https://llm.datasette.io/en/stable/logging.html#sql-schema)
* [Related tools](https://llm.datasette.io/en/stable/related-tools.html)
* [strip-tags](https://llm.datasette.io/en/stable/related-tools.html#strip-tags)
* [ttok](https://llm.datasette.io/en/stable/related-tools.html#ttok)
* [Symbex](https://llm.datasette.io/en/stable/related-tools.html#symbex)
* [CLI reference](https://llm.datasette.io/en/stable/help.html)
* [llm –help](https://llm.datasette.io/en/stable/help.html#llm-help)
* [llm prompt –help](https://llm.datasette.io/en/stable/help.html#llm-prompt-help)
* [llm chat –help](https://llm.datasette.io/en/stable/help.html#llm-chat-help)
* [llm keys –help](https://llm.datasette.io/en/stable/help.html#llm-keys-help)
* [llm logs –help](https://llm.datasette.io/en/stable/help.html#llm-logs-help)
* [llm models –help](https://llm.datasette.io/en/stable/help.html#llm-models-help)
* [llm templates –help](https://llm.datasette.io/en/stable/help.html#llm-templates-help)
* [llm schemas –help](https://llm.datasette.io/en/stable/help.html#llm-schemas-help)
* [llm tools –help](https://llm.datasette.io/en/stable/help.html#llm-tools-help)
* [llm aliases –help](https://llm.datasette.io/en/stable/help.html#llm-aliases-help)
* [llm fragments –help](https://llm.datasette.io/en/stable/help.html#llm-fragments-help)
* [llm plugins –help](https://llm.datasette.io/en/stable/help.html#llm-plugins-help)
* [llm install –help](https://llm.datasette.io/en/stable/help.html#llm-install-help)
* [llm uninstall –help](https://llm.datasette.io/en/stable/help.html#llm-uninstall-help)
* [llm embed –help](https://llm.datasette.io/en/stable/help.html#llm-embed-help)
* [llm embed-multi –help](https://llm.datasette.io/en/stable/help.html#llm-embed-multi-help)
* [llm similar –help](https://llm.datasette.io/en/stable/help.html#llm-similar-help)
* [llm embed-models –help](https://llm.datasette.io/en/stable/help.html#llm-embed-models-help)
* [llm collections –help](https://llm.datasette.io/en/stable/help.html#llm-collections-help)
* [llm openai –help](https://llm.datasette.io/en/stable/help.html#llm-openai-help)
* [Contributing](https://llm.datasette.io/en/stable/contributing.html)
* [Updating recorded HTTP API interactions and associated snapshots](https://llm.datasette.io/en/stable/contributing.html#updating-recorded-http-api-interactions-and-associated-snapshots)
* [Debugging tricks](https://llm.datasette.io/en/stable/contributing.html#debugging-tricks)
* [Documentation](https://llm.datasette.io/en/stable/contributing.html#documentation)
* [Release process](https://llm.datasette.io/en/stable/contributing.html#release-process)
* [Changelog](https://llm.datasette.io/en/stable/changelog.html)
<!-- [[[end]]] -->
================================================
FILE: docs/.gitignore
================================================
_build
================================================
FILE: docs/Makefile
================================================
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = sqlite-utils
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
livehtml:
sphinx-autobuild -b html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(0)
================================================
FILE: docs/_templates/base.html
================================================
{%- extends "!base.html" %}
{%- block htmltitle -%}
{% if not docstitle %}
<title>{{ title|striptags|e }}</title>
{% elif pagename == master_doc %}
<title>LLM: A CLI utility and Python library for interacting with Large Language Models</title>
{% else %}
<title>{{ title|striptags|e }} - {{ docstitle|striptags|e }}</title>
{% endif %}
{%- endblock -%}
{% block site_meta %}
{{ super() }}
<script defer data-domain="llm.datasette.io" src="https://plausible.io/js/plausible.js"></script>
{% endblock %}
================================================
FILE: docs/aliases.md
================================================
(aliases)=
# Model aliases
LLM supports model aliases, which allow you to refer to a model by a short name instead of its full ID.
## Listing aliases
To list current aliases, run this:
```bash
llm aliases
```
Example output:
<!-- [[[cog
from click.testing import CliRunner
from llm.cli import cli
result = CliRunner().invoke(cli, ["aliases", "list"])
cog.out("```\n{}```".format(result.output))
]]] -->
```
4o : gpt-4o
chatgpt-4o : chatgpt-4o-latest
4o-mini : gpt-4o-mini
4.1 : gpt-4.1
4.1-mini : gpt-4.1-mini
4.1-nano : gpt-4.1-nano
3.5 : gpt-3.5-turbo
chatgpt : gpt-3.5-turbo
chatgpt-16k : gpt-3.5-turbo-16k
3.5-16k : gpt-3.5-turbo-16k
4 : gpt-4
gpt4 : gpt-4
4-32k : gpt-4-32k
gpt-4-turbo-preview : gpt-4-turbo
4-turbo : gpt-4-turbo
4t : gpt-4-turbo
gpt-4.5 : gpt-4.5-preview
3.5-instruct : gpt-3.5-turbo-instruct
chatgpt-instruct : gpt-3.5-turbo-instruct
ada : text-embedding-ada-002 (embedding)
ada-002 : text-embedding-ada-002 (embedding)
3-small : text-embedding-3-small (embedding)
3-large : text-embedding-3-large (embedding)
3-small-512 : text-embedding-3-small-512 (embedding)
3-large-256 : text-embedding-3-large-256 (embedding)
3-large-1024 : text-embedding-3-large-1024 (embedding)
```
<!-- [[[end]]] -->
Add `--json` to get that list back as JSON:
```bash
llm aliases list --json
```
Example output:
```json
{
"3.5": "gpt-3.5-turbo",
"chatgpt": "gpt-3.5-turbo",
"4": "gpt-4",
"gpt4": "gpt-4",
"ada": "ada-002"
}
```
## Adding a new alias
The `llm aliases set <alias> <model-id>` command can be used to add a new alias:
```bash
llm aliases set mini gpt-4o-mini
```
You can also pass one or more `-q search` options to set an alias on the first model matching those search terms:
```bash
llm aliases set mini -q 4o -q mini
```
Now you can run the `gpt-4o-mini` model using the `mini` alias like this:
```bash
llm -m mini 'An epic Greek-style saga about a cheesecake that builds a SQL database from scratch'
```
Aliases can be set for both regular models and {ref}`embedding models <embeddings>` using the same command. To set an alias of `oai` for the OpenAI `ada-002` embedding model use this:
```bash
llm aliases set oai ada-002
```
Now you can embed a string using that model like so:
```bash
llm embed -c 'hello world' -m oai
```
Output:
```
[-0.014945968054234982, 0.0014304015785455704, ...]
```
## Removing an alias
The `llm aliases remove <alias>` command will remove the specified alias:
```bash
llm aliases remove mini
```
## Viewing the aliases file
Aliases are stored in an `aliases.json` file in the LLM configuration directory.
To see the path to that file, run this:
```bash
llm aliases path
```
To view the content of that file, run this:
```bash
cat "$(llm aliases path)"
```
================================================
FILE: docs/changelog.md
================================================
# Changelog
(v0_29)=
## 0.29 (2025-03-17)
- The `-t/--template` option now works correctly with the `-x/--extract` and `--xl/--extract-last` flags.
- `llm logs` now shows any additional model options in the Markdown output. [#1322](https://github.com/simonw/llm/issues/1322)
- New OpenAI models: `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.4-nano`. [#1376](https://github.com/simonw/llm/issues/1376)
(v0_28)=
## 0.28 (2025-12-12)
- New OpenAI models: `gpt-5.1`, `gpt-5.1-chat-latest`, `gpt-5.2` and `gpt-5.2-chat-latest`. [#1300](https://github.com/simonw/llm/issues/1300), [#1317](https://github.com/simonw/llm/issues/1317)
- LLM now requires Python 3.10 or higher. Python 3.14 is now covered by the tests.
- When fetching URLs as fragments using `llm -f URL`, the request now includes a custom user-agent header: `llm/VERSION (https://llm.datasette.io/)`. [#1309](https://github.com/simonw/llm/issues/1309)
- Fixed a bug where fragments were not correctly registered with their source when using `llm chat`. Thanks, [Giuseppe Rota](https://github.com/grota). [#1316](https://github.com/simonw/llm/pull/1316)
- Fixed some file descriptor leak warnings. Thanks, [Eric Bloch](https://github.com/eedeebee). [#1313](https://github.com/simonw/llm/issues/1313)
- Fixed a deprecation warning for `asyncio.iscoroutinefunction`.
- Type annotations for the OpenAI Chat, AsyncChat and Completion `execute()` methods. Thanks, [Arjan Mossel](https://github.com/ar-jan). [#1315](https://github.com/simonw/llm/pull/1315)
- The project now uses `uv` and dependency groups for development. See the updated {ref}`contributing documentation <contributing>`. [#1318](https://github.com/simonw/llm/issues/1318)
(v0_27_1)=
## 0.27.1 (2025-08-11)
- `llm chat -t template` now correctly loads any tools that are included in that template. [#1239](https://github.com/simonw/llm/issues/1239)
- Fixed a bug where `llm -m gpt5 -o reasoning_effort minimal --save gm` saved a template containing invalid YAML. [#1237](https://github.com/simonw/llm/issues/1237)
- Fixed a bug where running `llm chat -t template` could cause prompts to be duplicated. [#1240](https://github.com/simonw/llm/issues/1240)
- Less confusing error message if a requested toolbox class is unavailable. [#1238](https://github.com/simonw/llm/issues/1238)
(v0_27)=
## 0.27 (2025-08-11)
This release adds support for the new **GPT-5 family** of models from OpenAI. It also enhances tool calling in a number of ways, including allowing {ref}`templates <prompt-templates>` to bundle pre-configured tools.
### New features
- New models: `gpt-5`, `gpt-5-mini` and `gpt-5-nano`. [#1229](https://github.com/simonw/llm/issues/1229)
- LLM {ref}`templates <prompt-templates>` can now include a list of tools. These can be named tools from plugins or arbitrary Python function blocks, see {ref}`Tools in templates <prompt-templates-tools>`. [#1009](https://github.com/simonw/llm/issues/1009)
- Tools {ref}`can now return attachments <python-api-tools-attachments>`, for models that support features such as image input. [#1014](https://github.com/simonw/llm/issues/1014)
- New methods on the `Toolbox` class: `.add_tool()`, `.prepare()` and `.prepare_async()`, described in {ref}`Dynamic toolboxes <python-api-tools-dynamic>`. [#1111](https://github.com/simonw/llm/issues/1111)
- New `model.conversation(before_call=x, after_call=y)` attributes for registering callback functions to run before and after tool calls. See {ref}`tool debugging hooks <python-api-tools-debug-hooks>` for details. [#1088](https://github.com/simonw/llm/issues/1088)
- Some model providers can serve different models from the same configured URL - [llm-llama-server](https://github.com/simonw/llm-llama-server) for example. Plugins for these providers can now record the resolved model ID of the model that was used to the LLM logs using the `response.set_resolved_model(model_id)` method. [#1117](https://github.com/simonw/llm/issues/1117)
- Raising `llm.CancelToolCall` now only cancels the current tool call, passing an error back to the model and allowing it to continue. [#1148](https://github.com/simonw/llm/issues/1148)
- New `-l/--latest` option for `llm logs -q searchterm` for searching logs ordered by date (most recent first) instead of the default relevance search. [#1177](https://github.com/simonw/llm/issues/1177)
### Bug fixes and documentation
- Fix for various bugs with different formats of streaming function responses for OpenAI-compatible models. Thanks, [James Sanford](https://github.com/jamessanford). [#1218](https://github.com/simonw/llm/pull/1218)
- The `register_embedding_models` hook is [now documented](https://llm.datasette.io/en/stable/plugins/plugin-hooks.html#register-embedding-models-register). [#1049](https://github.com/simonw/llm/issues/1049)
- Show visible stack trace for `llm templates show invalid-template-name`. [#1053](https://github.com/simonw/llm/issues/1053)
- Handle invalid tool names more gracefully in `llm chat`. [#1104](https://github.com/simonw/llm/issues/1104)
- Add a {ref}`Tool plugins <plugin-directory-tools>` section to the plugin directory. [#1110](https://github.com/simonw/llm/issues/1110)
- Error on `register(Klass)` if the passed class is not a subclass of `Toolbox`. [#1114](https://github.com/simonw/llm/issues/1114)
- Add `-h` for `--help` for all `llm` CLI commands. [#1134](https://github.com/simonw/llm/issues/1134)
- Add missing `dataclasses` to advanced model plugins docs. [#1137](https://github.com/simonw/llm/issues/1137)
- Fixed a bug where `llm logs -T llm_version "version" --async` incorrectly recorded just one single log entry when it should have recorded two. [#1150](https://github.com/simonw/llm/issues/1150)
- All extra OpenAI model keys in `extra-openai-models.yaml` are {ref}`now documented <openai-compatible-models>`. [#1228](https://github.com/simonw/llm/issues/1228)
(v0_26)=
## 0.26 (2025-05-27)
**Tool support** is finally here! This release adds support exposing {ref}`tools <tools>` to LLMs, previously described in the release notes for {ref}`0.26a0 <v0_26_a0>` and {ref}`0.26a1 <v0_26_a1>`.
Read **[Large Language Models can run tools in your terminal with LLM 0.26](https://simonwillison.net/2025/May/27/llm-tools/)** for a detailed overview of the new features.
Also in this release:
- Two new {ref}`default tools <tools-default>`: `llm_version()` and `llm_time()`. [#1096](https://github.com/simonw/llm/issues/1096), [#1103](https://github.com/simonw/llm/issues/1103)
- Documentation on {ref}`how to add tool supports to a model plugin <advanced-model-plugins-tools>`. [#1000](https://github.com/simonw/llm/issues/1000)
- Added a {ref}`prominent warning <tools-warning>` about the risk of prompt injection when using tools. [#1097](https://github.com/simonw/llm/issues/1097)
- Switched to using monotonic ULIDs for the response IDs in the logs, fixing some intermittent test failures. [#1099](https://github.com/simonw/llm/issues/1099)
- New `tool_instances` table records details of Toolbox instances created while executing a prompt. [#1089](https://github.com/simonw/llm/issues/1089)
- `llm.get_key()` is now a {ref}`documented utility function <plugin-utilities-get-key>`. [#1094](https://github.com/simonw/llm/issues/1094)
(v0_26_a1)=
## 0.26a1 (2025-05-25)
Hopefully the last alpha before a stable release that includes tool support.
### Features
* **Plugin-provided tools can now be grouped into "Toolboxes".**
* Toolboxes (`llm.Toolbox` classes) allow plugins to expose multiple related tools that share state or configuration (e.g., a `Memory` tool or `Filesystem` tool). ([#1059](https://github.com/simonw/llm/issues/1059), [#1086](https://github.com/simonw/llm/issues/1086))
* **Tool support for `llm chat`.**
* The `llm chat` command now accepts `--tool` and `--functions` arguments, allowing interactive chat sessions to use tools. ([#1004](https://github.com/simonw/llm/issues/1004), [#1062](https://github.com/simonw/llm/issues/1062))
* **Tools can now execute asynchronously.**
* Models that implement `AsyncModel` can now run tools, including tool functions defined as `async def`. ([#1063](https://github.com/simonw/llm/issues/1063))
* **`llm chat` now supports adding fragments during a session.**
* Use the new `!fragment <id>` command while chatting to insert content from a fragment. Initial fragments can also be passed to `llm chat` using `-f` or `--sf`. Thanks, [Dan Turkel](https://github.com/daturkel). ([#1044](https://github.com/simonw/llm/issues/1044), [#1048](https://github.com/simonw/llm/issues/1048))
* **Filter `llm logs` by tools.**
* New `--tool <name>` option to filter logs to show only responses that involved a specific tool (e.g., `--tool simple_eval`).
* The `--tools` flag shows all responses that used any tool. ([#1013](https://github.com/simonw/llm/issues/1013), [#1072](https://github.com/simonw/llm/issues/1072))
* **`llm schemas list` can output JSON.**
* Added `--json` and `--nl` (newline-delimited JSON) options to `llm schemas list` for programmatic access to saved schema definitions. ([#1070](https://github.com/simonw/llm/issues/1070))
* **Filter `llm similar` results by ID prefix.**
* The new `--prefix` option for `llm similar` allows searching for similar items only within IDs that start with a specified string (e.g., `llm similar my-collection --prefix 'docs/'`). Thanks, [Dan Turkel](https://github.com/daturkel). ([#1052](https://github.com/simonw/llm/issues/1052))
* **Control chained tool execution limit.**
* New `--chain-limit <N>` (or `--cl`) option for `llm prompt` and `llm chat` to specify the maximum number of consecutive tool calls allowed for a single prompt. Defaults to 5; set to 0 for unlimited. ([#1025](https://github.com/simonw/llm/issues/1025))
* **`llm plugins --hook <NAME>` option.**
* Filter the list of installed plugins to only show those that implement a specific plugin hook. ([#1047](https://github.com/simonw/llm/issues/1047))
* `llm tools list` now shows toolboxes and their methods. ([#1013](https://github.com/simonw/llm/issues/1013))
* `llm prompt` and `llm chat` now automatically re-enable plugin-provided tools when continuing a conversation (`-c` or `--cid`). ([#1020](https://github.com/simonw/llm/issues/1020))
* The `--tools-debug` option now pretty-prints JSON tool results for improved readability. ([#1083](https://github.com/simonw/llm/issues/1083))
* New `LLM_TOOLS_DEBUG` environment variable to permanently enable `--tools-debug`. ([#1045](https://github.com/simonw/llm/issues/1045))
* `llm chat` sessions now correctly respect default model options configured with `llm models set-options`. Thanks, [André Arko](https://github.com/indirect). ([#985](https://github.com/simonw/llm/issues/985))
* New `--pre` option for `llm install` to allow installing pre-release packages. ([#1060](https://github.com/simonw/llm/issues/1060))
* OpenAI models (`gpt-4o`, `gpt-4o-mini`) now explicitly declare support for tools and vision. ([#1037](https://github.com/simonw/llm/issues/1037))
* The `supports_tools` parameter is now supported in `extra-openai-models.yaml`. Thanks, [Mahesh Hegde ](https://github.com/mahesh-hegde). ([#1068](https://github.com/simonw/llm/issues/1068))
### Bug fixes
* Fixed a bug where the `name` parameter in `register(function, name="name")` was ignored for tool plugins. ([#1032](https://github.com/simonw/llm/issues/1032))
* Ensure `pathlib.Path` objects are cast to `str` before passing to `click.edit` in `llm templates edit`. Thanks, [Abizer Lokhandwala](https://github.com/abizer). ([#1031](https://github.com/simonw/llm/issues/1031))
(v0_26_a0)=
## 0.26a0 (2025-05-13)
This is the first alpha to introduce {ref}`support for tools<tools>`! Models with tool capability (which includes the default OpenAI model family) can now be granted access to execute Python functions as part of responding to a prompt.
Tools are supported by {ref}`the command-line interface <usage-tools>`:
```bash
llm --functions '
def multiply(x: int, y: int) -> int:
"""Multiply two numbers."""
return x * y
' 'what is 34234 * 213345'
```
And in {ref}`the Python API <python-api-tools>`, using a new `model.chain()` method for executing multiple prompts in a sequence:
```python
import llm
def multiply(x: int, y: int) -> int:
"""Multiply two numbers."""
return x * y
model = llm.get_model("gpt-4.1-mini")
response = model.chain(
"What is 34234 * 213345?",
tools=[multiply]
)
print(response.text())
```
New tools can also be defined using the {ref}`register_tools() plugin hook <plugin-hooks-register-tools>`. They can then be called by name from the command-line like this:
```bash
llm -T multiply 'What is 34234 * 213345?'
```
Tool support is currently under **active development**. Consult [this milestone](https://github.com/simonw/llm/milestone/12) for the latest status.
(v0_25)=
## 0.25 (2025-05-04)
- New plugin feature: {ref}`plugin-hooks-register-fragment-loaders` plugins can now return a mixture of fragments and attachments. The [llm-video-frames](https://github.com/simonw/llm-video-frames) plugin is the first to take advantage of this mechanism. [#972](https://github.com/simonw/llm/issues/972)
- New OpenAI models: `gpt-4.1`, `gpt-4.1-mini`, `gpt-41-nano`, `o3`, `o4-mini`. [#945](https://github.com/simonw/llm/issues/945), [#965](https://github.com/simonw/llm/issues/965), [#976](https://github.com/simonw/llm/issues/976).
- New environment variables: `LLM_MODEL` and `LLM_EMBEDDING_MODEL` for setting the model to use without needing to specify `-m model_id` every time. [#932](https://github.com/simonw/llm/issues/932)
- New command: `llm fragments loaders`, to list all currently available fragment loader prefixes provided by plugins. [#941](https://github.com/simonw/llm/issues/941)
- `llm fragments` command now shows fragments ordered by the date they were first used. [#973](https://github.com/simonw/llm/issues/973)
- `llm chat` now includes a `!edit` command for editing a prompt using your default terminal text editor. Thanks, [Benedikt Willi](https://github.com/Hopiu). [#969](https://github.com/simonw/llm/pull/969)
- Allow `-t` and `--system` to be used at the same time. [#916](https://github.com/simonw/llm/issues/916)
- Fixed a bug where accessing a model via its alias would fail to respect any default options set for that model. [#968](https://github.com/simonw/llm/issues/968)
- Improved documentation for {ref}`extra-openai-models.yaml <openai-compatible-models>`. Thanks, [Rahim Nathwani](https://github.com/rahimnathwani) and [Dan Guido](https://github.com/dguido). [#950](https://github.com/simonw/llm/pull/950), [#957](https://github.com/simonw/llm/pull/957)
- `llm -c/--continue` now works correctly with the `-d/--database` option. `llm chat` now accepts that `-d/--database` option. Thanks, [Sukhbinder Singh](https://github.com/sukhbinder). [#933](https://github.com/simonw/llm/issues/933)
(v0_25a0)=
## 0.25a0 (2025-04-10)
- `llm models --options` now shows keys and environment variables for models that use API keys. Thanks, [Steve Morin](https://github.com/smorin). [#903](https://github.com/simonw/llm/issues/903)
- Added `py.typed` marker file so LLM can now be used as a dependency in projects that use `mypy` without a warning. [#887](https://github.com/simonw/llm/issues/887)
- `$` characters can now be used in templates by escaping them as `$$`. Thanks, [@guspix](https://github.com/guspix). [#904](https://github.com/simonw/llm/issues/904)
- LLM now uses `pyproject.toml` instead of `setup.py`. [#908](https://github.com/simonw/llm/issues/908)
(v0_24_2)=
## 0.24.2 (2025-04-08)
- Fixed a bug on Windows with the new `llm -t path/to/file.yaml` feature. [#901](https://github.com/simonw/llm/issues/901)
(v0_24_1)=
## 0.24.1 (2025-04-08)
- Templates can now be specified as a path to a file on disk, using `llm -t path/to/file.yaml`. This makes them consistent with how `-f` fragments are loaded. [#897](https://github.com/simonw/llm/issues/897)
- `llm logs backup /tmp/backup.db` command for {ref}`backing up your <logging-backup>` `logs.db` database. [#879](https://github.com/simonw/llm/issues/879)
(v0_24)=
## 0.24 (2025-04-07)
Support for **fragments** to help assemble prompts for long context models. Improved support for **templates** to support attachments and fragments. New plugin hooks for providing custom loaders for both templates and fragments. See [Long context support in LLM 0.24 using fragments and template plugins](https://simonwillison.net/2025/Apr/7/long-context-llm/) for more on this release.
The new [llm-docs](https://github.com/simonw/llm-docs) plugin demonstrates these new features. Install it like this:
```bash
llm install llm-docs
```
Now you can ask questions of the LLM documentation like this:
```bash
llm -f docs: 'How do I save a new template?'
```
The `docs:` prefix is registered by the plugin. The plugin fetches the LLM documentation for your installed version (from the [docs-for-llms](https://github.com/simonw/docs-for-llms) repository) and uses that as a prompt fragment to help answer your question.
Two more new plugins are [llm-templates-github](https://github.com/simonw/llm-templates-github) and [llm-templates-fabric](https://github.com/simonw/llm-templates-fabric).
`llm-templates-github` lets you share and use templates on GitHub. You can run my [Pelican riding a bicycle](https://simonwillison.net/tags/pelican-riding-a-bicycle/) benchmark against a model like this:
```bash
llm install llm-templates-github
llm -t gh:simonw/pelican-svg -m o3-mini
```
This executes [this pelican-svg.yaml](https://github.com/simonw/llm-templates/blob/main/pelican-svg.yaml) template stored in my [simonw/llm-templates](https://github.com/simonw/llm-templates) repository, using a new repository naming convention.
To share your own templates, create a repository on GitHub under your user account called `llm-templates` and start saving `.yaml` files to it.
[llm-templates-fabric](https://github.com/simonw/llm-templates-fabric) provides a similar mechanism for loading templates from Daniel Miessler's [fabric collection](https://github.com/danielmiessler/fabric):
```bash
llm install llm-templates-fabric
curl https://simonwillison.net/2025/Apr/6/only-miffy/ | \
llm -t f:extract_main_idea
```
Major new features:
- New {ref}`fragments feature <fragments>`. Fragments can be used to assemble long prompts from multiple existing pieces - URLs, file paths or previously used fragments. These will be stored de-duplicated in the database avoiding wasting space storing multiple long context pieces. Example usage: `llm -f https://llm.datasette.io/robots.txt 'explain this file'`. [#617](https://github.com/simonw/llm/issues/617)
- The `llm logs` file now accepts `-f` fragment references too, and will show just logged prompts that used those fragments.
- {ref}`register_template_loaders() plugin hook <plugin-hooks-register-template-loaders>` allowing plugins to register new `prefix:value` custom template loaders. [#809](https://github.com/simonw/llm/issues/809)
- {ref}`register_fragment_loaders() plugin hook <plugin-hooks-register-fragment-loaders>` allowing plugins to register new `prefix:value` custom fragment loaders. [#886](https://github.com/simonw/llm/issues/886)
- {ref}`llm fragments <fragments-browsing>` family of commands for browsing fragments that have been previously logged to the database.
- The new [llm-openai plugin](https://github.com/simonw/llm-openai-plugin) provides support for **o1-pro** (which is not supported by the OpenAI mechanism used by LLM core). Future OpenAI features will migrate to this plugin instead of LLM core itself.
Improvements to templates:
- `llm -t $URL` option can now take a URL to a YAML template. [#856](https://github.com/simonw/llm/issues/856)
- Templates can now store default model options. [#845](https://github.com/simonw/llm/issues/845)
- Executing a template that does not use the `$input` variable no longer blocks LLM waiting for input, so prompt templates can now be used to try different models using `llm -t pelican-svg -m model_id`. [#835](https://github.com/simonw/llm/issues/835)
- `llm templates` command no longer crashes if one of the listed template files contains invalid YAML. [#880](https://github.com/simonw/llm/issues/880)
- Attachments can now be stored in templates. [#826](https://github.com/simonw/llm/issues/826)
Other changes:
- New {ref}`llm models options <usage-executing-default-options>` family of commands for setting default options for particular models. [#829](https://github.com/simonw/llm/issues/829)
- `llm logs list`, `llm schemas list` and `llm schemas show` all now take a `-d/--database` option with an optional path to a SQLite database. They used to take `-p/--path` but that was inconsistent with other commands. `-p/--path` still works but is excluded from `--help` and will be removed in a future LLM release. [#857](https://github.com/simonw/llm/issues/857)
- `llm logs -e/--expand` option for expanding fragments. [#881](https://github.com/simonw/llm/issues/881)
- `llm prompt -d path-to-sqlite.db` option can now be used to write logs to a custom SQLite database. [#858](https://github.com/simonw/llm/issues/858)
- `llm similar -p/--plain` option providing more human-readable output than the default JSON. [#853](https://github.com/simonw/llm/issues/853)
- `llm logs -s/--short` now truncates to include the end of the prompt too. Thanks, [Sukhbinder Singh](https://github.com/sukhbinder). [#759](https://github.com/simonw/llm/issues/759)
- Set the `LLM_RAISE_ERRORS=1` environment variable to raise errors during prompts rather than suppressing them, which means you can run `python -i -m llm 'prompt'` and then drop into a debugger on errors with `import pdb; pdb.pm()`. [#817](https://github.com/simonw/llm/issues/817)
- Improved [--help output](https://llm.datasette.io/en/stable/help.html#llm-embed-multi-help) for `llm embed-multi`. [#824](https://github.com/simonw/llm/issues/824)
- `llm models -m X` option which can be passed multiple times with model IDs to see the details of just those models. [#825](https://github.com/simonw/llm/issues/825)
- OpenAI models now accept PDF attachments. [#834](https://github.com/simonw/llm/issues/834)
- `llm prompt -q gpt -q 4o` option - pass `-q searchterm` one or more times to execute a prompt against the first model that matches all of those strings - useful for if you can't remember the full model ID. [#841](https://github.com/simonw/llm/issues/841)
- {ref}`OpenAI compatible models <openai-compatible-models>` configured using `extra-openai-models.yaml` now support `supports_schema: true`, `vision: true` and `audio: true` options. Thanks [@adaitche](https://github.com/adaitche) and [@giuli007](https://github.com/giuli007). [#819](https://github.com/simonw/llm/pull/819), [#843](https://github.com/simonw/llm/pull/843)
(v0_24a1)=
## 0.24a1 (2025-04-06)
- New Fragments feature. [#617](https://github.com/simonw/llm/issues/617)
- `register_fragment_loaders()` plugin hook. [#809](https://github.com/simonw/llm/issues/886)
(v0_24a0)=
## 0.24a0 (2025-02-28)
- Alpha release with experimental `register_template_loaders()` plugin hook. [#809](https://github.com/simonw/llm/issues/809)
(v0_23)=
## 0.23 (2025-02-28)
Support for **schemas**, for getting supported models to output JSON that matches a specified JSON schema. See also [Structured data extraction from unstructured content using LLM schemas](https://simonwillison.net/2025/Feb/28/llm-schemas/) for background on this feature. [#776](https://github.com/simonw/llm/issues/776)
- New `llm prompt --schema '{JSON schema goes here}` option for specifying a schema that should be used for the output from the model. The {ref}`schemas documentation <schemas>` has more details and a tutorial.
- Schemas can also be defined using a {ref}`concise schema specification <schemas-dsl>`, for example `llm prompt --schema 'name, bio, age int'`. [#790](https://github.com/simonw/llm/issues/790)
- Schemas can also be specified by passing a filename and through {ref}`several other methods <schemas-specify>`. [#780](https://github.com/simonw/llm/issues/780)
- New {ref}`llm schemas family of commands <help-schemas>`: `llm schemas list`, `llm schemas show`, and `llm schemas dsl` for debugging the new concise schema language. [#781](https://github.com/simonw/llm/issues/781)
- Schemas can now be saved to templates using `llm --schema X --save template-name` or through modifying the {ref}`template YAML <prompt-templates-yaml>`. [#778](https://github.com/simonw/llm/issues/778)
- The {ref}`llm logs <logging>` command now has new options for extracting data collected using schemas: `--data`, `--data-key`, `--data-array`, `--data-ids`. [#782](https://github.com/simonw/llm/issues/782)
- New `llm logs --id-gt X` and `--id-gte X` options. [#801](https://github.com/simonw/llm/issues/801)
- New `llm models --schemas` option for listing models that support schemas. [#797](https://github.com/simonw/llm/issues/797)
- `model.prompt(..., schema={...})` parameter for specifying a schema from Python. This accepts either a dictionary JSON schema definition or a Pydantic `BaseModel` subclass, see {ref}`schemas in the Python API docs <python-api-schemas>`.
- The default OpenAI plugin now enables schemas across all supported models. Run `llm models --schemas` for a list of these.
- The [llm-anthropic](https://github.com/simonw/llm-anthropic) and [llm-gemini](https://github.com/simonw/llm-gemini) plugins have been upgraded to add schema support for those models. Here's documentation on how to {ref}`add schema support to a model plugin <advanced-model-plugins-schemas>`.
Other smaller changes:
- [GPT-4.5 preview](https://openai.com/index/introducing-gpt-4-5/) is now a supported model: `llm -m gpt-4.5 'a joke about a pelican and a wolf'` [#795](https://github.com/simonw/llm/issues/795)
- The prompt string is now optional when calling `model.prompt()` from the Python API, so `model.prompt(attachments=llm.Attachment(url=url)))` now works. [#784](https://github.com/simonw/llm/issues/784)
- `extra-openai-models.yaml` now supports a `reasoning: true` option. Thanks, [Kasper Primdal Lauritzen](https://github.com/KPLauritzen). [#766](https://github.com/simonw/llm/pull/766)
- LLM now depends on Pydantic v2 or higher. Pydantic v1 is no longer supported. [#520](https://github.com/simonw/llm/issues/520)
(v0_22)=
## 0.22 (2025-02-16)
See also [LLM 0.22, the annotated release notes](https://simonwillison.net/2025/Feb/17/llm/).
- Plugins that provide models that use API keys can now subclass the new `llm.KeyModel` and `llm.AsyncKeyModel` classes. This results in the API key being passed as a new `key` parameter to their `.execute()` methods, and means that Python users can pass a key as the `model.prompt(..., key=)` - see {ref}`Passing an API key <python-api-models-api-keys>`. Plugin developers should consult the new documentation on writing {ref}`Models that accept API keys <advanced-model-plugins-api-keys>`. [#744](https://github.com/simonw/llm/issues/744)
- New OpenAI model: `chatgpt-4o-latest`. This model ID accesses the current model being used to power ChatGPT, which can change without warning. [#752](https://github.com/simonw/llm/issues/752)
- New `llm logs -s/--short` flag, which returns a greatly shortened version of the matching log entries in YAML format with a truncated prompt and without including the response. [#737](https://github.com/simonw/llm/issues/737)
- Both `llm models` and `llm embed-models` now take multiple `-q` search fragments. You can now search for all models matching "gemini" and "exp" using `llm models -q gemini -q exp`. [#748](https://github.com/simonw/llm/issues/748)
- New `llm embed-multi --prepend X` option for prepending a string to each value before it is embedded - useful for models such as [nomic-embed-text-v2-moe](https://huggingface.co/nomic-ai/nomic-embed-text-v2-moe) that require passages to start with a string like `"search_document: "`. [#745](https://github.com/simonw/llm/issues/745)
- The `response.json()` and `response.usage()` methods are {ref}`now documented <python-api-underlying-json>`.
- Fixed a bug where conversations that were loaded from the database could not be continued using `asyncio` prompts. [#742](https://github.com/simonw/llm/issues/742)
- New plugin for macOS users: [llm-mlx](https://github.com/simonw/llm-mlx), which provides [extremely high performance access](https://simonwillison.net/2025/Feb/15/llm-mlx/) to a wide range of local models using Apple's MLX framework.
- The `llm-claude-3` plugin has been renamed to [llm-anthropic](https://github.com/simonw/llm-anthropic).
(v0_21)=
## 0.21 (2025-01-31)
- New model: `o3-mini`. [#728](https://github.com/simonw/llm/issues/728)
- The `o3-mini` and `o1` models now support a `reasoning_effort` option which can be set to `low`, `medium` or `high`.
- `llm prompt` and `llm logs` now have a `--xl/--extract-last` option for extracting the last fenced code block in the response - a complement to the existing `--x/--extract` option. [#717](https://github.com/simonw/llm/issues/717)
(v0_20)=
## 0.20 (2025-01-22)
- New model, `o1`. This model does not yet support streaming. [#676](https://github.com/simonw/llm/issues/676)
- `o1-preview` and `o1-mini` models now support streaming.
- New models, `gpt-4o-audio-preview` and `gpt-4o-mini-audio-preview`. [#677](https://github.com/simonw/llm/issues/677)
- `llm prompt -x/--extract` option, which returns just the content of the first fenced code block in the response. Try `llm prompt -x 'Python function to reverse a string'`. [#681](https://github.com/simonw/llm/issues/681)
- Creating a template using `llm ... --save x` now supports the `-x/--extract` option, which is saved to the template. YAML templates can set this option using `extract: true`.
- New `llm logs -x/--extract` option extracts the first fenced code block from matching logged responses.
- New `llm models -q 'search'` option returning models that case-insensitively match the search query. [#700](https://github.com/simonw/llm/issues/700)
- Installation documentation now also includes `uv`. Thanks, [Ariel Marcus](https://github.com/ajmarcus). [#690](https://github.com/simonw/llm/pull/690) and [#702](https://github.com/simonw/llm/issues/702)
- `llm models` command now shows the current default model at the bottom of the listing. Thanks, [Amjith Ramanujam](https://github.com/amjith). [#688](https://github.com/simonw/llm/pull/688)
- {ref}`Plugin directory <plugin-directory>` now includes `llm-venice`, `llm-bedrock`, `llm-deepseek` and `llm-cmd-comp`.
- Fixed bug where some dependency version combinations could cause a `Client.__init__() got an unexpected keyword argument 'proxies'` error. [#709](https://github.com/simonw/llm/issues/709)
- OpenAI embedding models are now available using their full names of `text-embedding-ada-002`, `text-embedding-3-small` and `text-embedding-3-large` - the previous names are still supported as aliases. Thanks, [web-sst](https://github.com/web-sst). [#654](https://github.com/simonw/llm/pull/654)
(v0_19_1)=
## 0.19.1 (2024-12-05)
- FIxed bug where `llm.get_models()` and `llm.get_async_models()` returned the same model multiple times. [#667](https://github.com/simonw/llm/issues/667)
(v0_19)=
## 0.19 (2024-12-01)
- Tokens used by a response are now logged to new `input_tokens` and `output_tokens` integer columns and a `token_details` JSON string column, for the default OpenAI models and models from other plugins that {ref}`implement this feature <advanced-model-plugins-usage>`. [#610](https://github.com/simonw/llm/issues/610)
- `llm prompt` now takes a `-u/--usage` flag to display token usage at the end of the response.
- `llm logs -u/--usage` shows token usage information for logged responses.
- `llm prompt ... --async` responses are now logged to the database. [#641](https://github.com/simonw/llm/issues/641)
- `llm.get_models()` and `llm.get_async_models()` functions, {ref}`documented here <python-api-listing-models>`. [#640](https://github.com/simonw/llm/issues/640)
- `response.usage()` and async response `await response.usage()` methods, returning a `Usage(input=2, output=1, details=None)` dataclass. [#644](https://github.com/simonw/llm/issues/644)
- `response.on_done(callback)` and `await response.on_done(callback)` methods for specifying a callback to be executed when a response has completed, {ref}`documented here <python-api-response-on-done>`. [#653](https://github.com/simonw/llm/issues/653)
- Fix for bug running `llm chat` on Windows 11. Thanks, [Sukhbinder Singh](https://github.com/sukhbinder). [#495](https://github.com/simonw/llm/issues/495)
(v0_19a2)=
## 0.19a2 (2024-11-20)
- `llm.get_models()` and `llm.get_async_models()` functions, {ref}`documented here <python-api-listing-models>`. [#640](https://github.com/simonw/llm/issues/640)
(v0_19a1)=
## 0.19a1 (2024-11-19)
- `response.usage()` and async response `await response.usage()` methods, returning a `Usage(input=2, output=1, details=None)` dataclass. [#644](https://github.com/simonw/llm/issues/644)
(v0_19a0)=
## 0.19a0 (2024-11-19)
- Tokens used by a response are now logged to new `input_tokens` and `output_tokens` integer columns and a `token_details` JSON string column, for the default OpenAI models and models from other plugins that {ref}`implement this feature <advanced-model-plugins-usage>`. [#610](https://github.com/simonw/llm/issues/610)
- `llm prompt` now takes a `-u/--usage` flag to display token usage at the end of the response.
- `llm logs -u/--usage` shows token usage information for logged responses.
- `llm prompt ... --async` responses are now logged to the database. [#641](https://github.com/simonw/llm/issues/641)
(v0_18)=
## 0.18 (2024-11-17)
- Initial support for async models. Plugins can now provide an `AsyncModel` subclass that can be accessed in the Python API using the new `llm.get_async_model(model_id)` method. See {ref}`async models in the Python API docs<python-api-async>` and {ref}`implementing async models in plugins <advanced-model-plugins-async>`. [#507](https://github.com/simonw/llm/issues/507)
- OpenAI models all now include async models, so function calls such as `llm.get_async_model("gpt-4o-mini")` will return an async model.
- `gpt-4o-audio-preview` model can be used to send audio attachments to the GPT-4o audio model. [#608](https://github.com/simonw/llm/issues/608)
- Attachments can now be sent without requiring a prompt. [#611](https://github.com/simonw/llm/issues/611)
- `llm models --options` now includes information on whether a model supports attachments. [#612](https://github.com/simonw/llm/issues/612)
- `llm models --async` shows available async models.
- Custom OpenAI-compatible models can now be marked as `can_stream: false` in the YAML if they do not support streaming. Thanks, [Chris Mungall](https://github.com/cmungall). [#600](https://github.com/simonw/llm/pull/600)
- Fixed bug where OpenAI usage data was incorrectly serialized to JSON. [#614](https://github.com/simonw/llm/issues/614)
- Standardized on `audio/wav` MIME type for audio attachments rather than `audio/wave`. [#603](https://github.com/simonw/llm/issues/603)
(v0_18a1)=
## 0.18a1 (2024-11-14)
- Fixed bug where conversations did not work for async OpenAI models. [#632](https://github.com/simonw/llm/issues/632)
- `__repr__` methods for `Response` and `AsyncResponse`.
(v0_18a0)=
## 0.18a0 (2024-11-13)
Alpha support for **async models**. [#507](https://github.com/simonw/llm/issues/507)
Multiple [smaller changes](https://github.com/simonw/llm/compare/0.17.1...0.18a0).
(v0_17)=
## 0.17 (2024-10-29)
Support for **attachments**, allowing multi-modal models to accept images, audio, video and other formats. [#578](https://github.com/simonw/llm/issues/578)
The default OpenAI `gpt-4o` and `gpt-4o-mini` models can both now be prompted with JPEG, GIF, PNG and WEBP images.
Attachments {ref}`in the CLI <usage-attachments>` can be URLs:
```bash
llm -m gpt-4o "describe this image" \
-a https://static.simonwillison.net/static/2024/pelicans.jpg
```
Or file paths:
```bash
llm -m gpt-4o-mini "extract text" -a image1.jpg -a image2.jpg
```
Or binary data, which may need to use `--attachment-type` to specify the MIME type:
```bash
cat image | llm -m gpt-4o-mini "extract text" --attachment-type - image/jpeg
```
Attachments are also available {ref}`in the Python API <python-api-attachments>`:
```python
model = llm.get_model("gpt-4o-mini")
response = model.prompt(
"Describe these images",
attachments=[
llm.Attachment(path="pelican.jpg"),
llm.Attachment(url="https://static.simonwillison.net/static/2024/pelicans.jpg"),
]
)
```
Plugins that provide alternative models can support attachments, see {ref}`advanced-model-plugins-attachments` for details.
The latest **[llm-claude-3](https://github.com/simonw/llm-claude-3)** plugin now supports attachments for Anthropic's Claude 3 and 3.5 models. The **[llm-gemini](https://github.com/simonw/llm-gemini)** plugin supports attachments for Google's Gemini 1.5 models.
Also in this release: OpenAI models now record their `"usage"` data in the database even when the response was streamed. These records can be viewed using `llm logs --json`. [#591](https://github.com/simonw/llm/issues/591)
(v0_17a0)=
## 0.17a0 (2024-10-28)
Alpha support for **attachments**. [#578](https://github.com/simonw/llm/issues/578)
(v0_16)=
## 0.16 (2024-09-12)
- OpenAI models now use the internal `self.get_key()` mechanism, which means they can be used from Python code in a way that will pick up keys that have been configured using `llm keys set` or the `OPENAI_API_KEY` environment variable. [#552](https://github.com/simonw/llm/issues/552). This code now works correctly:
```python
import llm
print(llm.get_model("gpt-4o-mini").prompt("hi"))
```
- New documented API methods: `llm.get_default_model()`, `llm.set_default_model(alias)`, `llm.get_default_embedding_model(alias)`, `llm.set_default_embedding_model()`. [#553](https://github.com/simonw/llm/issues/553)
- Support for OpenAI's new [o1 family](https://openai.com/o1/) of preview models, `llm -m o1-preview "prompt"` and `llm -m o1-mini "prompt"`. These models are currently only available to [tier 5](https://platform.openai.com/docs/guides/rate-limits/usage-tiers?context=tier-five) OpenAI API users, though this may change in the future. [#570](https://github.com/simonw/llm/issues/570)
(v0_15)=
## 0.15 (2024-07-18)
- Support for OpenAI's [new GPT-4o mini](https://openai.com/index/gpt-4o-mini-advancing-cost-efficient-intelligence/) model: `llm -m gpt-4o-mini 'rave about pelicans in French'` [#536](https://github.com/simonw/llm/issues/536)
- `gpt-4o-mini` is now the default model if you do not {ref}`specify your own default <setup-default-model>`, replacing GPT-3.5 Turbo. GPT-4o mini is both cheaper and better than GPT-3.5 Turbo.
- Fixed a bug where `llm logs -q 'flourish' -m haiku` could not combine both the `-q` search query and the `-m` model specifier. [#515](https://github.com/simonw/llm/issues/515)
(v0_14)=
## 0.14 (2024-05-13)
- Support for OpenAI's [new GPT-4o](https://openai.com/index/hello-gpt-4o/) model: `llm -m gpt-4o 'say hi in Spanish'` [#490](https://github.com/simonw/llm/issues/490)
- The `gpt-4-turbo` alias is now a model ID, which indicates the latest version of OpenAI's GPT-4 Turbo text and image model. Your existing `logs.db` database may contain records under the previous model ID of `gpt-4-turbo-preview`. [#493](https://github.com/simonw/llm/issues/493)
- New `llm logs -r/--response` option for outputting just the last captured response, without wrapping it in Markdown and accompanying it with the prompt. [#431](https://github.com/simonw/llm/issues/431)
- Nine new {ref}`plugins <plugin-directory>` since version 0.13:
- **[llm-claude-3](https://github.com/simonw/llm-claude-3)** supporting Anthropic's [Claude 3 family](https://www.anthropic.com/news/claude-3-family) of models.
- **[llm-command-r](https://github.com/simonw/llm-command-r)** supporting Cohere's Command R and [Command R Plus](https://txt.cohere.com/command-r-plus-microsoft-azure/) API models.
- **[llm-reka](https://github.com/simonw/llm-reka)** supports the [Reka](https://www.reka.ai/) family of models via their API.
- **[llm-perplexity](https://github.com/hex/llm-perplexity)** by Alexandru Geana supporting the [Perplexity Labs](https://docs.perplexity.ai/) API models, including `llama-3-sonar-large-32k-online` which can search for things online and `llama-3-70b-instruct`.
- **[llm-groq](https://github.com/angerman/llm-groq)** by Moritz Angermann providing access to fast models hosted by [Groq](https://console.groq.com/docs/models).
- **[llm-fireworks](https://github.com/simonw/llm-fireworks)** supporting models hosted by [Fireworks AI](https://fireworks.ai/).
- **[llm-together](https://github.com/wearedevx/llm-together)** adds support for the [Together AI](https://www.together.ai/) extensive family of hosted openly licensed models.
- **[llm-embed-onnx](https://github.com/simonw/llm-embed-onnx)** provides seven embedding models that can be executed using the ONNX model framework.
- **[llm-cmd](https://github.com/simonw/llm-cmd)** accepts a prompt for a shell command, runs that prompt and populates the result in your shell so you can review it, edit it and then hit `<enter>` to execute or `ctrl+c` to cancel, see [this post for details](https://simonwillison.net/2024/Mar/26/llm-cmd/).
(v0_13_1)=
## 0.13.1 (2024-01-26)
- Fix for `No module named 'readline'` error on Windows. [#407](https://github.com/simonw/llm/issues/407)
(v0_13)=
## 0.13 (2024-01-26)
See also [LLM 0.13: The annotated release notes](https://simonwillison.net/2024/Jan/26/llm/).
- Added support for new OpenAI embedding models: `3-small` and `3-large` and three variants of those with different dimension sizes,
`3-small-512`, `3-large-256` and `3-large-1024`. See {ref}`OpenAI embedding models <openai-models-embedding>` for details. [#394](https://github.com/simonw/llm/issues/394)
- The default `gpt-4-turbo` model alias now points to `gpt-4-turbo-preview`, which uses the most recent OpenAI GPT-4 turbo model (currently `gpt-4-0125-preview`). [#396](https://github.com/simonw/llm/issues/396)
- New OpenAI model aliases `gpt-4-1106-preview` and `gpt-4-0125-preview`.
- OpenAI models now support a `-o json_object 1` option which will cause their output to be returned as a valid JSON object. [#373](https://github.com/simonw/llm/issues/373)
- New {ref}`plugins <plugin-directory>` since the last release include [llm-mistral](https://github.com/simonw/llm-mistral), [llm-gemini](https://github.com/simonw/llm-gemini), [llm-ollama](https://github.com/taketwo/llm-ollama) and [llm-bedrock-meta](https://github.com/flabat/llm-bedrock-meta).
- The `keys.json` file for storing API keys is now created with `600` file permissions. [#351](https://github.com/simonw/llm/issues/351)
- Documented {ref}`a pattern <homebrew-warning>` for installing plugins that depend on PyTorch using the Homebrew version of LLM, despite Homebrew using Python 3.12 when PyTorch have not yet released a stable package for that Python version. [#397](https://github.com/simonw/llm/issues/397)
- Underlying OpenAI Python library has been upgraded to `>1.0`. It is possible this could cause compatibility issues with LLM plugins that also depend on that library. [#325](https://github.com/simonw/llm/issues/325)
- Arrow keys now work inside the `llm chat` command. [#376](https://github.com/simonw/llm/issues/376)
- `LLM_OPENAI_SHOW_RESPONSES=1` environment variable now outputs much more detailed information about the HTTP request and response made to OpenAI (and OpenAI-compatible) APIs. [#404](https://github.com/simonw/llm/issues/404)
- Dropped support for Python 3.7.
(v0_12)=
## 0.12 (2023-11-06)
- Support for the [new GPT-4 Turbo model](https://openai.com/blog/new-models-and-developer-products-announced-at-devday) from OpenAI. Try it using `llm chat -m gpt-4-turbo` or `llm chat -m 4t`. [#323](https://github.com/simonw/llm/issues/323)
- New `-o seed 1` option for OpenAI models which sets a seed that can attempt to evaluate the prompt deterministically. [#324](https://github.com/simonw/llm/issues/324)
(v0_11_2)=
## 0.11.2 (2023-11-06)
- Pin to version of OpenAI Python library prior to 1.0 to avoid breaking. [#327](https://github.com/simonw/llm/issues/327)
(v0_11_1)=
## 0.11.1 (2023-10-31)
- Fixed a bug where `llm embed -c "text"` did not correctly pick up the configured {ref}`default embedding model <embeddings-cli-embed-models-default>`. [#317](https://github.com/simonw/llm/issues/317)
- New plugins: [llm-python](https://github.com/simonw/llm-python), [llm-bedrock-anthropic](https://github.com/sblakey/llm-bedrock-anthropic) and [llm-embed-jina](https://github.com/simonw/llm-embed-jina) (described in [Execute Jina embeddings with a CLI using llm-embed-jina](https://simonwillison.net/2023/Oct/26/llm-embed-jina/)).
- [llm-gpt4all](https://github.com/simonw/llm-gpt4all) now uses the new GGUF model format. [simonw/llm-gpt4all#16](https://github.com/simonw/llm-gpt4all/issues/16)
(v0_11)=
## 0.11 (2023-09-18)
LLM now supports the new OpenAI `gpt-3.5-turbo-instruct` model, and OpenAI completion (as opposed to chat completion) models in general. [#284](https://github.com/simonw/llm/issues/284)
```bash
llm -m gpt-3.5-turbo-instruct 'Reasons to tame a wild beaver:'
```
OpenAI completion models like this support a `-o logprobs 3` option, which accepts a number between 1 and 5 and will include the log probabilities (for each produced token, what were the top 3 options considered by the model) in the logged response.
```bash
llm -m gpt-3.5-turbo-instruct 'Say hello succinctly' -o logprobs 3
```
You can then view the `logprobs` that were recorded in the SQLite logs database like this:
```bash
sqlite-utils "$(llm logs path)" \
'select * from responses order by id desc limit 1' | \
jq '.[0].response_json' -r | jq
```
Truncated output looks like this:
```
[
{
"text": "Hi",
"top_logprobs": [
{
"Hi": -0.13706253,
"Hello": -2.3714375,
"Hey": -3.3714373
}
]
},
{
"text": " there",
"top_logprobs": [
{
" there": -0.96057636,
"!\"": -0.5855763,
".\"": -3.2574513
}
]
}
]
```
Also in this release:
- The `llm.user_dir()` function, used by plugins, now ensures the directory exists before returning it. [#275](https://github.com/simonw/llm/issues/275)
- New `LLM_OPENAI_SHOW_RESPONSES=1` environment variable for displaying the full HTTP response returned by OpenAI compatible APIs. [#286](https://github.com/simonw/llm/issues/286)
- The `llm embed-multi` command now has a `--batch-size X` option for setting the batch size to use when processing embeddings - useful if you have limited memory available. [#273](https://github.com/simonw/llm/issues/273)
- The `collection.embed_multi()` method also now accepts an optional `batch_size=int` argument.
- Fixed two bugs with `llm embed-multi --files` relating to handling of directories. Thanks, [ealvar3z](https://github.com/ealvar3z). [#274](https://github.com/simonw/llm/issues/274), [#280](https://github.com/simonw/llm/issues/280)
(v0_10)=
## 0.10 (2023-09-12)
The two major features in this release are the **{ref}`llm chat command <usage-chat>`** and support for **embedding binary data**.
See [Build an image search engine with llm-clip, chat with models with llm chat](https://simonwillison.net/2023/Sep/12/llm-clip-and-chat/) for more background on these features.
### llm chat
The new `llm chat` command starts an ongoing chat conversation with a model in your terminal. It works with all models supported by LLM and its {ref}`plugins <plugins>`, including locally installed models such as Llama 2. [#231](https://github.com/simonw/llm/issues/231)
This offers a big performance boost for local models, since they don't need to be freshly loaded into memory for each prompt.
Here's an example chat with Llama 2 13B, provided by the [llm-mlc](https://github.com/simonw/llm-mlc) plugin.
```bash
llm chat -m mlc-chat-Llama-2-13b-chat-hf-q4f16_1
```
This starts a chat session:
```
Type 'exit' or 'quit' to exit
Type '!multi' to enter multiple lines, then '!end' to finish
Type '!edit' to open your default editor and modify the prompt.
> Who are you?
Hello! I'm just an AI, here to assist you with any questions you may have.
My name is LLaMA, and I'm a large language model trained to provide helpful
and informative responses to a wide range of questions and topics. I'm here
to help you find the information you need, while ensuring a safe and
respectful conversation. Please feel free to ask me anything, and I'll do my
best to provide a helpful and accurate response.
> Tell me a joke about otters
Sure, here's a joke about otters:
Why did the otter go to the party?
Because he heard it was a "whale" of a time!
(Get it? Whale, like a big sea mammal, but also a "wild" or "fun" time.
Otters are known for their playful and social nature, so it's a lighthearted
and silly joke.)
I hope that brought a smile to your face! Do you have any other questions or
topics you'd like to discuss?
> exit
```
Chat sessions are {ref}`logged to SQLite <logging>` - use `llm logs` to view them. They can accept system prompts, templates and model options - consult {ref}`the chat documentation <usage-chat>` for details.
### Binary embedding support
LLM's {ref}`embeddings feature <embeddings>` has been expanded to provide support for embedding binary data, in addition to text. [#254](https://github.com/simonw/llm/pull/254)
This enables models like [CLIP](https://openai.com/research/clip), supported by the new **[llm-clip](https://github.com/simonw/llm-clip)** plugin.
CLIP is a multi-modal embedding model which can embed images and text into the same vector space. This means you can use it to create an embedding index of photos, and then search for the embedding vector for "a happy dog" and get back images that are semantically closest to that string.
To create embeddings for every JPEG in a directory stored in a `photos` collection, run:
```bash
llm install llm-clip
llm embed-multi photos --files photos/ '*.jpg' --binary -m clip
```
Now you can search for photos of raccoons using:
```
llm similar photos -c 'raccoon'
```
This spits out a list of images, ranked by how similar they are to the string "raccoon":
```
{"id": "IMG_4801.jpeg", "score": 0.28125139257127457, "content": null, "metadata": null}
{"id": "IMG_4656.jpeg", "score": 0.26626441704164294, "content": null, "metadata": null}
{"id": "IMG_2944.jpeg", "score": 0.2647445926996852, "content": null, "metadata": null}
...
```
### Also in this release
- The {ref}`LLM_LOAD_PLUGINS environment variable <llm-load-plugins>` can be used to control which plugins are loaded when `llm` starts running. [#256](https://github.com/simonw/llm/issues/256)
- The `llm plugins --all` option includes builtin plugins in the list of plugins. [#259](https://github.com/simonw/llm/issues/259)
- The `llm embed-db` family of commands has been renamed to `llm collections`. [#229](https://github.com/simonw/llm/issues/229)
- `llm embed-multi --files` now has an `--encoding` option and defaults to falling back to `latin-1` if a file cannot be processed as `utf-8`. [#225](https://github.com/simonw/llm/issues/225)
(v0_10_a1)=
## 0.10a1 (2023-09-11)
- Support for embedding binary data. [#254](https://github.com/simonw/llm/pull/254)
- `llm chat` now works for models with API keys. [#247](https://github.com/simonw/llm/issues/247)
- `llm chat -o` for passing options to a model. [#244](https://github.com/simonw/llm/issues/244)
- `llm chat --no-stream` option. [#248](https://github.com/simonw/llm/issues/248)
- `LLM_LOAD_PLUGINS` environment variable. [#256](https://github.com/simonw/llm/issues/256)
- `llm plugins --all` option for including builtin plugins. [#259](https://github.com/simonw/llm/issues/259)
- `llm embed-db` has been renamed to `llm collections`. [#229](https://github.com/simonw/llm/issues/229)
- Fixed bug where `llm embed -c` option was treated as a filepath, not a string. Thanks, [mhalle](https://github.com/mhalle). [#263](https://github.com/simonw/llm/pull/263)
(v0_10_a0)=
## 0.10a0 (2023-09-04)
- New {ref}`llm chat <usage-chat>` command for starting an interactive terminal chat with a model. [#231](https://github.com/simonw/llm/issues/231)
- `llm embed-multi --files` now has an `--encoding` option and defaults to falling back to `latin-1` if a file cannot be processed as `utf-8`. [#225](https://github.com/simonw/llm/issues/225)
(v0_9)=
## 0.9 (2023-09-03)
The big new feature in this release is support for **embeddings**. See [LLM now provides tools for working with embeddings](https://simonwillison.net/2023/Sep/4/llm-embeddings/) for additional details.
{ref}`Embedding models <embeddings>` take a piece of text - a word, sentence, paragraph or even a whole article, and convert that into an array of floating point numbers. [#185](https://github.com/simonw/llm/issues/185)
This embedding vector can be thought of as representing a position in many-dimensional-space, where the distance between two vectors represents how semantically similar they are to each other within the content of a language model.
Embeddings can be used to find **related documents**, and also to implement **semantic search** - where a user can search for a phrase and get back results that are semantically similar to that phrase even if they do not share any exact keywords.
LLM now provides both CLI and Python APIs for working with embeddings. Embedding models are defined by plugins, so you can install additional models using the {ref}`plugins mechanism <installing-plugins>`.
The first two embedding models supported by LLM are:
- OpenAI's [ada-002](https://platform.openai.com/docs/guides/embeddings) embedding model, available via an inexpensive API if you set an OpenAI key using `llm keys set openai`.
- The [sentence-transformers](https://www.sbert.net/) family of models, available via the new [llm-sentence-transformers](https://github.com/simonw/llm-sentence-transformers) plugin.
See {ref}`embeddings-cli` for detailed instructions on working with embeddings using LLM.
The new commands for working with embeddings are:
- **{ref}`llm embed <embeddings-cli-embed>`** - calculate embeddings for content and return them to the console or store them in a SQLite database.
- **{ref}`llm embed-multi <embeddings-cli-embed-multi>`** - run bulk embeddings for multiple strings, using input from a CSV, TSV or JSON file, data from a SQLite database or data found by scanning the filesystem. [#215](https://github.com/simonw/llm/issues/215)
- **{ref}`llm similar <embeddings-cli-similar>`** - run similarity searches against your stored embeddings - starting with a search phrase or finding content related to a previously stored vector. [#190](https://github.com/simonw/llm/issues/190)
- **{ref}`llm embed-models <embeddings-cli-embed-models>`** - list available embedding models.
- `llm embed-db` - commands for inspecting and working with the default embeddings SQLite database.
There's also a new {ref}`llm.Collection <embeddings-python-collections>` class for creating and searching collections of embedding from Python code, and a {ref}`llm.get_embedding_model() <embeddings-python-api>` interface for embedding strings directly. [#191](https://github.com/simonw/llm/issues/191)
(v0_8_1)=
## 0.8.1 (2023-08-31)
- Fixed bug where first prompt would show an error if the `io.datasette.llm` directory had not yet been created. [#193](https://github.com/simonw/llm/issues/193)
- Updated documentation to recommend a different `llm-gpt4all` model since the one we were using is no longer available. [#195](https://github.com/simonw/llm/issues/195)
(v0_8)=
## 0.8 (2023-08-20)
- The output format for `llm logs` has changed. Previously it was JSON - it's now a much more readable Markdown format suitable for pasting into other documents. [#160](https://github.com/simonw/llm/issues/160)
- The new `llm logs --json` option can be used to get the old JSON format.
- Pass `llm logs --conversation ID` or `--cid ID` to see the full logs for a specific conversation.
- You can now combine piped input and a prompt in a single command: `cat script.py | llm 'explain this code'`. This works even for models that do not support {ref}`system prompts <usage-system-prompts>`. [#153](https://github.com/simonw/llm/issues/153)
- Additional {ref}`openai-compatible-models` can now be configured with custom HTTP headers. This enables platforms such as [openrouter.ai](https://openrouter.ai/) to be used with LLM, which can provide Claude access even without an Anthropic API key.
- Keys set in `keys.json` are now used in preference to environment variables. [#158](https://github.com/simonw/llm/issues/158)
- The documentation now includes a {ref}`plugin directory <plugin-directory>` listing all available plugins for LLM. [#173](https://github.com/simonw/llm/issues/173)
- New {ref}`related tools <related-tools>` section in the documentation describing `ttok`, `strip-tags` and `symbex`. [#111](https://github.com/simonw/llm/issues/111)
- The `llm models`, `llm aliases` and `llm templates` commands now default to running the same command as `llm models list` and `llm aliases list` and `llm templates list`. [#167](https://github.com/simonw/llm/issues/167)
- New `llm keys` (aka `llm keys list`) command for listing the names of all configured keys. [#174](https://github.com/simonw/llm/issues/174)
- Two new Python API functions, `llm.set_alias(alias, model_id)` and `llm.remove_alias(alias)` can be used to configure aliases from within Python code. [#154](https://github.com/simonw/llm/pull/154)
- LLM is now compatible with both Pydantic 1 and Pydantic 2. This means you can install `llm` as a Python dependency in a project that depends on Pydantic 1 without running into dependency conflicts. Thanks, [Chris Mungall](https://github.com/cmungall). [#147](https://github.com/simonw/llm/pull/147)
- `llm.get_model(model_id)` is now documented as raising `llm.UnknownModelError` if the requested model does not exist. [#155](https://github.com/simonw/llm/issues/155)
(v0_7_1)=
## 0.7.1 (2023-08-19)
- Fixed a bug where some users would see an `AlterError: No such column: log.id` error when attempting to use this tool, after upgrading to the latest [sqlite-utils 3.35 release](https://sqlite-utils.datasette.io/en/stable/changelog.html#v3-35). [#162](https://github.com/simonw/llm/issues/162)
(v0_7)=
## 0.7 (2023-08-12)
The new {ref}`aliases` commands can be used to configure additional aliases for models, for example:
```bash
llm aliases set turbo gpt-3.5-turbo-16k
```
Now you can run the 16,000 token `gpt-3.5-turbo-16k` model like this:
```bash
llm -m turbo 'An epic Greek-style saga about a cheesecake that builds a SQL database from scratch'
```
Use `llm aliases list` to see a list of aliases and `llm aliases remove turbo` to remove one again. [#151](https://github.com/simonw/llm/issues/151)
### Notable new plugins
- **[llm-mlc](https://github.com/simonw/llm-mlc)** can run local models released by the [MLC project](https://mlc.ai/mlc-llm/), including models that can take advantage of the GPU on Apple Silicon M1/M2 devices.
- **[llm-llama-cpp](https://github.com/simonw/llm-llama-cpp)** uses [llama.cpp](https://github.com/ggerganov/llama.cpp) to run models published in the GGML format. See [Run Llama 2 on your own Mac using LLM and Homebrew](https://simonwillison.net/2023/Aug/1/llama-2-mac/) for more details.
### Also in this release
- OpenAI models now have min and max validation on their floating point options. Thanks, Pavel Král. [#115](https://github.com/simonw/llm/issues/115)
- Fix for bug where `llm templates list` raised an error if a template had an empty prompt. Thanks, Sherwin Daganato. [#132](https://github.com/simonw/llm/pull/132)
- Fixed bug in `llm install --editable` option which prevented installation of `.[test]`. [#136](https://github.com/simonw/llm/issues/136)
- `llm install --no-cache-dir` and `--force-reinstall` options. [#146](https://github.com/simonw/llm/issues/146)
(v0_6_1)=
## 0.6.1 (2023-07-24)
- LLM can now be installed directly from Homebrew core: `brew install llm`. [#124](https://github.com/simonw/llm/issues/124)
- Python API documentation now covers {ref}`python-api-system-prompts`.
- Fixed incorrect example in the {ref}`prompt-templates` documentation. Thanks, Jorge Cabello. [#125](https://github.com/simonw/llm/pull/125)
(v0_6)=
## 0.6 (2023-07-18)
- Models hosted on [Replicate](https://replicate.com/) can now be accessed using the [llm-replicate](https://github.com/simonw/llm-replicate) plugin, including the new Llama 2 model from Meta AI. More details here: [Accessing Llama 2 from the command-line with the llm-replicate plugin](https://simonwillison.net/2023/Jul/18/accessing-llama-2/).
- Model providers that expose an API that is compatible with the OpenAPI API format, including self-hosted model servers such as [LocalAI](https://github.com/go-skynet/LocalAI), can now be accessed using {ref}`additional configuration <openai-compatible-models>` for the default OpenAI plugin. [#106](https://github.com/simonw/llm/issues/106)
- OpenAI models that are not yet supported by LLM can also {ref}`be configured <openai-extra-models>` using the new `extra-openai-models.yaml` configuration file. [#107](https://github.com/simonw/llm/issues/107)
- The {ref}`llm logs command <logging-view>` now accepts a `-m model_id` option to filter logs to a specific model. Aliases can be used here in addition to model IDs. [#108](https://github.com/simonw/llm/issues/108)
- Logs now have a SQLite full-text search index against their prompts and responses, and the `llm logs -q SEARCH` option can be used to return logs that match a search term. [#109](https://github.com/simonw/llm/issues/109)
(v0_5)=
## 0.5 (2023-07-12)
LLM now supports **additional language models**, thanks to a new {ref}`plugins mechanism <installing-plugins>` for installing additional models.
Plugins are available for 19 models in addition to the default OpenAI ones:
- [llm-gpt4all](https://github.com/simonw/llm-gpt4all) adds support for 17 models that can download and run on your own device, including Vicuna, Falcon and wizardLM.
- [llm-mpt30b](https://github.com/simonw/llm-mpt30b) adds support for the MPT-30B model, a 19GB download.
- [llm-palm](https://github.com/simonw/llm-palm) adds support for Google's PaLM 2 via the Google API.
A comprehensive tutorial, {ref}`writing a plugin to support a new model <tutorial-model-plugin>` describes how to add new models by building plugins in detail.
### New features
- {ref}`python-api` documentation for using LLM models, including models from plugins, directly from Python. [#75](https://github.com/simonw/llm/issues/75)
- Messages are now logged to the database by default - no need to run the `llm init-db` command any more, which has been removed. Instead, you can toggle this behavior off using `llm logs off` or turn it on again using `llm logs on`. The `llm logs status` command shows the current status of the log database. If logging is turned off, passing `--log` to the `llm prompt` command will cause that prompt to be logged anyway. [#98](https://github.com/simonw/llm/issues/98)
- New database schema for logged messages, with `conversations` and `responses` tables. If you have previously used the old `logs` table it will continue to exist but will no longer be written to. [#91](https://github.com/simonw/llm/issues/91)
- New `-o/--option name value` syntax for setting options for models, such as temperature. Available options differ for different models. [#63](https://github.com/simonw/llm/issues/63)
- `llm models list --options` command for viewing all available model options. [#82](https://github.com/simonw/llm/issues/82)
- `llm "prompt" --save template` option for saving a prompt directly to a template. [#55](https://github.com/simonw/llm/issues/55)
- Prompt templates can now specify {ref}`default values <prompt-default-parameters>` for parameters. Thanks, Chris Mungall. [#57](https://github.com/simonw/llm/pull/57)
- `llm openai models` command to list all available OpenAI models from their API. [#70](https://github.com/simonw/llm/issues/70)
- `llm models default MODEL_ID` to set a different model as the default to be used when `llm` is run without the `-m/--model` option. [#31](https://github.com/simonw/llm/issues/31)
### Smaller improvements
- `llm -s` is now a shortcut for `llm --system`. [#69](https://github.com/simonw/llm/issues/69)
- `llm -m 4-32k` alias for `gpt-4-32k`.
- `llm install -e directory` command for installing a plugin from a local directory.
- The `LLM_USER_PATH` environment variable now controls the location of the directory in which LLM stores its data. This replaces the old `LLM_KEYS_PATH` and `LLM_LOG_PATH` and `LLM_TEMPLATES_PATH` variables. [#76](https://github.com/simonw/llm/issues/76)
- Documentation covering {ref}`plugin-utilities`.
- Documentation site now uses Plausible for analytics. [#79](https://github.com/simonw/llm/issues/79)
(v0_4_1)=
## 0.4.1 (2023-06-17)
- LLM can now be installed using Homebrew: `brew install simonw/llm/llm`. [#50](https://github.com/simonw/llm/issues/50)
- `llm` is now styled LLM in the documentation. [#45](https://github.com/simonw/llm/issues/45)
- Examples in documentation now include a copy button. [#43](https://github.com/simonw/llm/issues/43)
- `llm templates` command no longer has its display disrupted by newlines. [#42](https://github.com/simonw/llm/issues/42)
- `llm templates` command now includes system prompt, if set. [#44](https://github.com/simonw/llm/issues/44)
(v0_4)=
## 0.4 (2023-06-17)
This release includes some backwards-incompatible changes:
- The `-4` option for GPT-4 is now `-m 4`.
- The `--code` option has been removed.
- The `-s` option has been removed as streaming is now the default. Use `--no-stream` to opt out of streaming.
### Prompt templates
{ref}`prompt-templates` is a new feature that allows prompts to be saved as templates and re-used with different variables.
Templates can be created using the `llm templates edit` command:
```bash
llm templates edit summarize
```
Templates are YAML - the following template defines summarization using a system prompt:
```yaml
system: Summarize this text
```
The template can then be executed like this:
```bash
cat myfile.txt | llm -t summarize
```
Templates can include both system prompts, regular prompts and indicate the model they should use. They can reference variables such as `$input` for content piped to the tool, or other variables that are passed using the new `-p/--param` option.
This example adds a `voice` parameter:
```yaml
system: Summarize this text in the voice of $voice
```
Then to run it (via [strip-tags](https://github.com/simonw/strip-tags) to remove HTML tags from the input):
```bash
curl -s 'https://til.simonwillison.net/macos/imovie-slides-and-audio' | \
strip-tags -m | llm -t summarize -p voice GlaDOS
```
Example output:
> My previous test subject seemed to have learned something new about iMovie. They exported keynote slides as individual images [...] Quite impressive for a human.
The {ref}`prompt-templates` documentation provides more detailed examples.
### Continue previous chat
You can now use `llm` to continue a previous conversation with the OpenAI chat models (`gpt-3.5-turbo` and `gpt-4`). This will include your previous prompts and responses in the prompt sent to the API, allowing the model to continue within the same context.
Use the new `-c/--continue` option to continue from the previous message thread:
```bash
llm "Pretend to be a witty gerbil, say hi briefly"
```
> Greetings, dear human! I am a clever gerbil, ready to entertain you with my quick wit and endless energy.
```bash
llm "What do you think of snacks?" -c
```
> Oh, how I adore snacks, dear human! Crunchy carrot sticks, sweet apple slices, and chewy yogurt drops are some of my favorite treats. I could nibble on them all day long!
The `-c` option will continue from the most recent logged message.
To continue a different chat, pass an integer ID to the `--chat` option. This should be the ID of a previously logged message. You can find these IDs using the `llm logs` command.
Thanks [Amjith Ramanujam](https://github.com/amjith) for contributing to this feature. [#6](https://github.com/simonw/llm/issues/6)
### New mechanism for storing API keys
API keys for language models such as those by OpenAI can now be saved using the new `llm keys` family of commands.
To set the default key to be used for the OpenAI APIs, run this:
```bash
llm keys set openai
```
Then paste in your API key.
Keys can also be passed using the new `--key` command line option - this can be a full key or the alias of a key that has been previously stored.
See {ref}`api-keys` for more. [#13](https://github.com/simonw/llm/issues/13)
### New location for the logs.db database
The `logs.db` database that stores a history of executed prompts no longer lives at `~/.llm/log.db` - it can now be found in a location that better fits the host operating system, which can be seen using:
```bash
llm logs path
```
On macOS this is `~/Library/Application Support/io.datasette.llm/logs.db`.
To open that database using Datasette, run this:
```bash
datasette "$(llm logs path)"
```
You can upgrade your existing installation by copying your database to the new location like this:
```bash
cp ~/.llm/log.db "$(llm logs path)"
rm -rf ~/.llm # To tidy up the now obsolete directory
```
The database schema has changed, and will be updated automatically the first time you run the command.
That schema is [included in the documentation](https://llm.datasette.io/en/stable/logging.html#sql-schema). [#35](https://github.com/simonw/llm/issues/35)
### Other changes
- New `llm logs --truncate` option (shortcut `-t`) which truncates the displayed prompts to make the log output easier to read. [#16](https://github.com/simonw/llm/issues/16)
- Documentation now spans multiple pages and lives at <https://llm.datasette.io/> [#21](https://github.com/simonw/llm/issues/21)
- Default `llm chatgpt` command has been renamed to `llm prompt`. [#17](https://github.com/simonw/llm/issues/17)
- Removed `--code` option in favour of new prompt templates mechanism. [#24](https://github.com/simonw/llm/issues/24)
- Responses are now streamed by default, if the model supports streaming. The `-s/--stream` option has been removed. A new `--no-stream` option can be used to opt-out of streaming. [#25](https://github.com/simonw/llm/issues/25)
- The `-4/--gpt4` option has been removed in favour of `-m 4` or `-m gpt4`, using a new mechanism that allows models to have additional short names.
- The new `gpt-3.5-turbo-16k` model with a 16,000 token context length can now also be accessed using `-m chatgpt-16k` or `-m 3.5-16k`. Thanks, Benjamin Kirkbride. [#37](https://github.com/simonw/llm/issues/37)
- Improved display of error messages from OpenAI. [#15](https://github.com/simonw/llm/issues/15)
(v0_3)=
## 0.3 (2023-05-17)
- `llm logs` command for browsing logs of previously executed completions. [#3](https://github.com/simonw/llm/issues/3)
- `llm "Python code to output factorial 10" --code` option which sets a system prompt designed to encourage code to be output without any additional explanatory text. [#5](https://github.com/simonw/llm/issues/5)
- Tool can now accept a prompt piped directly to standard input. [#11](https://github.com/simonw/llm/issues/11)
(v0_2)=
## 0.2 (2023-04-01)
- If a SQLite database exists in `~/.llm/log.db` all prompts and responses are logged to that file. The `llm init-db` command can be used to create this file. [#2](https://github.com/simonw/llm/issues/2)
(v0_1)=
## 0.1 (2023-04-01)
- Initial prototype release. [#1](https://github.com/simonw/llm/issues/1)
================================================
FILE: docs/conf.py
================================================
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from subprocess import PIPE, Popen
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"myst_parser",
"sphinx_copybutton",
"sphinx_markdown_builder",
"sphinx.ext.autodoc",
]
myst_enable_extensions = ["colon_fence"]
markdown_http_base = "https://llm.datasette.io/en/stable"
markdown_uri_doc_suffix = ".html"
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = ".rst"
# The master toctree document.
master_doc = "index"
# General information about the project.
project = "LLM"
copyright = "2025, Simon Willison"
author = "Simon Willison"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
pipe = Popen("git describe --tags --always", stdout=PIPE, shell=True)
git_version = pipe.stdout.read().decode("utf8")
if git_version:
version = git_version.rsplit("-", 1)[0]
release = git_version
else:
version = ""
release = ""
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = "en"
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = "furo"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
html_theme_options = {}
html_title = "LLM"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = []
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = "llm-doc"
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(
master_doc,
"llm.tex",
"LLM documentation",
"Simon Willison",
"manual",
)
]
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(
master_doc,
"llm",
"LLM documentation",
[author],
1,
)
]
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(
master_doc,
"llm",
"LLM documentation",
author,
"llm",
" Access large language models from the command-line ",
"Miscellaneous",
)
]
================================================
FILE: docs/contributing.md
================================================
(contributing)=
# Contributing
To contribute to this tool, first checkout the code. Then run the tests with `uv run`:
```bash
cd llm
uv run pytest
```
You can run your development copy of `llm` using `uv run` as well:
```bash
uv run llm --help
```
## Updating recorded HTTP API interactions and associated snapshots
This project uses [pytest-recording](https://github.com/kiwicom/pytest-recording) to record OpenAI API responses for some of the tests, and [syrupy](https://github.com/syrupy-project/syrupy) to capture snapshots of their results.
If you add a new test that calls the API you can capture the API response and snapshot like this:
```bash
PYTEST_OPENAI_API_KEY="$(llm keys get openai)" uv run pytest --record-mode once --snapshot-update
```
Then review the new snapshots in `tests/__snapshots__/` to make sure they look correct.
## Debugging tricks
The default OpenAI plugin has a debugging mechanism for showing the exact requests and responses that were sent to the OpenAI API.
Set the `LLM_OPENAI_SHOW_RESPONSES` environment variable like this:
```bash
LLM_OPENAI_SHOW_RESPONSES=1 uv run llm -m chatgpt 'three word slogan for an an otter-run bakery'
```
This will output details of the API requests and responses to the console.
Use `--no-stream` to see a more readable version of the body that avoids streaming the response:
```bash
LLM_OPENAI_SHOW_RESPONSES=1 uv run llm -m chatgpt --no-stream \
'three word slogan for an an otter-run bakery'
```
## Documentation
Documentation for this project uses [MyST](https://myst-parser.readthedocs.io/) - it is written in Markdown and rendered using Sphinx.
To build the documentation locally, run the following:
```bash
just docs
```
This will start a live preview server, using [sphinx-autobuild](https://pypi.org/project/sphinx-autobuild/).
The CLI `--help` examples in the documentation are managed using [Cog](https://github.com/nedbat/cog). Update those files like this:
```bash
just cog
```
You'll need [Just](https://github.com/casey/just) installed to run these commands.
## Release process
To release a new version:
1. Update `docs/changelog.md` with the new changes.
2. Update the version number in `pyproject.toml`
3. Run `just cog` to update `docs/fragments.md` with the new version number.
4. [Create a GitHub release](https://github.com/simonw/llm/releases/new) for the new version.
5. Wait for the package to push to PyPI and then...
6. Run the [regenerate.yaml](https://github.com/simonw/homebrew-llm/actions/workflows/regenerate.yaml) workflow to update the Homebrew tap to the latest version.
================================================
FILE: docs/embeddings/cli.md
================================================
(embeddings-cli)=
# Embedding with the CLI
LLM provides command-line utilities for calculating and storing embeddings for pieces of content.
(embeddings-cli-embed)=
## llm embed
The `llm embed` command can be used to calculate embedding vectors for a string of content. These can be returned directly to the terminal, stored in a SQLite database, or both.
### Returning embeddings to the terminal
The simplest way to use this command is to pass content to it using the `-c/--content` option, like this:
```bash
llm embed -c 'This is some content' -m 3-small
```
`-m 3-small` specifies the OpenAI `text-embedding-3-small` model. You will need to have set an OpenAI API key using `llm keys set openai` for this to work.
You can install plugins to access other models. The [llm-sentence-transformers](https://github.com/simonw/llm-sentence-transformers) plugin can be used to run models on your own laptop, such as the [MiniLM-L6](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2) model:
```bash
llm install llm-sentence-transformers
llm embed -c 'This is some content' -m sentence-transformers/all-MiniLM-L6-v2
```
The `llm embed` command returns a JSON array of floating point numbers directly to the terminal:
```json
[0.123, 0.456, 0.789...]
```
You can omit the `-m/--model` option if you set a {ref}`default embedding model <embeddings-cli-embed-models-default>`.
You can also set the `LLM_EMBEDDING_MODEL` environment variable to set a default model for all `llm embed` commands in the current shell session:
```bash
export LLM_EMBEDDING_MODEL=3-small
llm embed -c 'This is some content'
```
LLM also offers a binary storage format for embeddings, described in {ref}`embeddings storage format <embeddings-storage>`.
You can output embeddings using that format as raw bytes using `--format blob`, or in hexadecimal using `--format hex`, or in Base64 using `--format base64`:
```bash
llm embed -c 'This is some content' -m 3-small --format base64
```
This outputs:
```
8NGzPFtdgTqHcZw7aUT6u+++WrwwpZo8XbSxv...
```
Some models such as [llm-clip](https://github.com/simonw/llm-clip) can run against binary data. You can pass in binary data using the `-i` and `--binary` options:
```bash
llm embed --binary -m clip -i image.jpg
```
Or from standard input like this:
```bash
cat image.jpg | llm embed --binary -m clip -i -
```
(embeddings-collections)=
### Storing embeddings in SQLite
Embeddings are much more useful if you store them somewhere, so you can calculate similarity scores between different embeddings later on.
LLM includes the concept of a **collection** of embeddings. A collection groups together a set of stored embeddings created using the same model, each with a unique ID within that collection.
Embeddings also store a hash of the content that was embedded. This hash is later used to avoid calculating duplicate embeddings for the same content.
First, we'll set a default model so we don't have to keep repeating it:
```bash
llm embed-models default 3-small
```
The `llm embed` command can store results directly in a named collection like this:
```bash
llm embed quotations philkarlton-1 -c \
'There are only two hard things in Computer Science: cache invalidation and naming things'
```
This stores the given text in the `quotations` collection under the key `philkarlton-1`.
You can also pipe content to standard input, like this:
```bash
cat one.txt | llm embed files one
```
This will store the embedding for the contents of `one.txt` in the `files` collection under the key `one`.
A collection will be created the first time you mention it.
Collections have a fixed embedding model, which is the model that was used for the first embedding stored in that collection.
In the above example this would have been the default embedding model at the time that the command was run.
The following example stores the embedding for the string "my happy hound" in a collection called `phrases` under the key `hound` and using the model `3-small`:
```bash
llm embed phrases hound -m 3-small -c 'my happy hound'
```
By default, the SQLite database used to store embeddings is the `embeddings.db` in the user content directory managed by LLM.
You can see the path to this directory by running `llm collections path`.
You can store embeddings in a different SQLite database by passing a path to it using the `-d/--database` option to `llm embed`. If this file does not exist yet the command will create it:
```bash
llm embed phrases hound -d my-embeddings.db -c 'my happy hound'
```
This creates a database file called `my-embeddings.db` in the current directory.
(embeddings-collections-content-metadata)=
#### Storing content and metadata
By default, only the entry ID and the embedding vector are stored in the database table.
You can store a copy of the original text in the `content` column by passing the `--store` option:
```bash
llm embed phrases hound -c 'my happy hound' --store
```
You can also store a JSON object containing arbitrary metadata in the `metadata` column by passing the `--metadata` option. This example uses both `--store` and `--metadata` options:
```bash
llm embed phrases hound \
-m 3-small \
-c 'my happy hound' \
--metadata '{"name": "Hound"}' \
--store
```
Data stored in this way will be returned by calls to `llm similar`, for example:
```bash
llm similar phrases -c 'hound'
```
```
{"id": "hound", "score": 0.8484683588631485, "content": "my happy hound", "metadata": {"name": "Hound"}}
```
(embeddings-cli-embed-multi)=
## llm embed-multi
The `llm embed` command embeds a single string at a time.
`llm embed-multi` can be used to embed multiple strings at once, taking advantage of any efficiencies that the embedding model may provide when processing multiple strings.
This command can be called in one of three ways:
1. With a CSV, TSV, JSON or newline-delimited JSON file
2. With a SQLite database and a SQL query
3. With one or more paths to directories, each accompanied by a glob pattern
All three mechanisms support these options:
- `-m model_id` to specify the embedding model to use
- `-d database.db` to specify a different database file to store the embeddings in
- `--store` to store the original content in the embeddings table in addition to the embedding vector
- `--prefix` to prepend a prefix to the stored ID of each item
- `--prepend` to prepend a string to the content before embedding
- `--batch-size SIZE` to process embeddings in batches of the specified size
The `--prepend` option is useful for embedding models that require you to prepend a special token to the content before embedding it. [nomic-embed-text-v2-moe](https://huggingface.co/nomic-ai/nomic-embed-text-v2-moe) for example requires documents to be prepended `'search_document: '` and search queries to be prepended `'search_query: '`.
(embeddings-cli-embed-multi-csv-etc)=
### Embedding data from a CSV, TSV or JSON file
You can embed data from a CSV, TSV or JSON file by passing that file to the command as the second option, after the collection name.
Your file must contain at least two columns. The first one is expected to contain the ID of the item, and any subsequent columns will be treated as containing content to be embedded.
An example CSV file might look like this:
```
id,content
one,This is the first item
two,This is the second item
```
TSV would use tabs instead of commas.
JSON files can be structured like this:
```json
[
{"id": "one", "content": "This is the first item"},
{"id": "two", "content": "This is the second item"}
]
```
Or as newline-delimited JSON like this:
```json
{"id": "one", "content": "This is the first item"}
{"id": "two", "content": "This is the second item"}
```
In each of these cases the file can be passed to `llm embed-multi` like this:
```bash
llm embed-multi items mydata.csv
```
The first argument is the name of the collection, the second is the filename.
You can also pipe content to standard input of the tool using `-`:
```bash
cat mydata.json | llm embed-multi items -
```
LLM will attempt to detect the format of your data automatically. If this doesn't work you can specify the format using the `--format` option. This is required if you are piping newline-delimited JSON to standard input.
```bash
cat mydata.json | llm embed-multi items - --format nl
```
Other supported `--format` options are `csv`, `tsv` and `json`.
This example embeds the data from a JSON file in a collection called `items` in database called `docs.db` using the `3-small` model and stores the original content in the `embeddings` table as well, adding a prefix of `my-items/` to each ID:
```bash
llm embed-multi items mydata.json \
-d docs.db \
-m 3-small \
--prefix my-items/ \
--store
```
(embeddings-cli-embed-multi-sqlite)=
### Embedding data from a SQLite database
You can embed data from a SQLite database using `--sql`, optionally combined with `--attach` to attach an additional database.
If you are storing embeddings in the same database as the source data, you can do this:
```bash
llm embed-multi docs \
-d docs.db \
--sql 'select id, title, content from documents' \
-m 3-small
```
The `docs.db` database here contains a `documents` table, and we want to embed the `title` and `content` columns from that table and store the results back in the same database.
To load content from a database other than the one you are using to store embeddings, attach it with the `--attach` option and use `alias.table` in your SQLite query:
```bash
llm embed-multi docs \
-d embeddings.db \
--attach other other.db \
--sql 'select id, title, content from other.documents' \
-m 3-small
```
(embeddings-cli-embed-multi-directories)=
### Embedding data from files in directories
LLM can embed the content of every text file in a specified directory, using the file's path and name as the ID.
Consider a directory structure like this:
```
docs/aliases.md
docs/contributing.md
docs/embeddings/binary.md
docs/embeddings/cli.md
docs/embeddings/index.md
docs/index.md
docs/logging.md
docs/plugins/directory.md
docs/plugins/index.md
```
To embed all of those documents, you can run the following:
```bash
llm embed-multi documentation \
-m 3-small \
--files docs '**/*.md' \
-d documentation.db \
--store
```
Here `--files docs '**/*.md'` specifies that the `docs` directory should be scanned for files matching the `**/*.md` glob pattern - which will match Markdown files in any nested directory.
The result of the above command is a `embeddings` table with the following IDs:
```
aliases.md
contributing.md
embeddings/binary.md
embeddings/cli.md
embeddings/index.md
index.md
logging.md
plugins/directory.md
plugins/index.md
```
Each corresponding to embedded content for the file in question.
The `--prefix` option can be used to add a prefix to each ID:
```bash
llm embed-multi documentation \
-m 3-small \
--files docs '**/*.md' \
-d documentation.db \
--store \
--prefix llm-docs/
```
This will result in the following IDs instead:
```
llm-docs/aliases.md
llm-docs/contributing.md
llm-docs/embeddings/binary.md
llm-docs/embeddings/cli.md
llm-docs/embeddings/index.md
llm-docs/index.md
llm-docs/logging.md
llm-docs/plugins/directory.md
llm-docs/plugins/index.md
```
Files are assumed to be `utf-8`, but LLM will fall back to `latin-1` if it encounters an encoding error. You can specify a different set of encodings using the `--encoding` option.
This example will try `utf-16` first and then `mac_roman` before falling back to `latin-1`:
```
llm embed-multi documentation \
-m 3-small \
--files docs '**/*.md' \
-d documentation.db \
--encoding utf-16 \
--encoding mac_roman \
--encoding latin-1
```
If a file cannot be read it will be logged to standard error but the script will keep on running.
If you are embedding binary content such as images for use with CLIP, add the `--binary` option:
```
llm embed-multi photos \
-m clip \
--files photos/ '*.jpeg' --binary
```
(embeddings-cli-similar)=
## llm similar
The `llm similar` command searches a collection of embeddings for the items that are most similar to a given or item ID, based on [cosine similarity](https://en.wikipedia.org/wiki/Cosine_similarity).
This currently uses a slow brute-force approach which does not scale well to large collections. See [issue 216](https://github.com/simonw/llm/issues/216) for plans to add a more scalable approach via vector indexes provided by plugins.
To search the `quotations` collection for items that are semantically similar to `'computer science'`:
```bash
llm similar quotations -c 'computer science'
```
This embeds the provided string and returns a newline-delimited list of JSON objects like this:
```json
{"id": "philkarlton-1", "score": 0.8323904531677017, "content": null, "metadata": null}
```
Use `-p/--plain` to get back results in plain text instead of JSON:
```bash
llm similar quotations -c 'computer science' -p
```
Example output:
```
philkarlton-1 (0.8323904531677017)
```
You can compare against text stored in a file using `-i filename`:
```bash
llm similar quotations -i one.txt
```
Or feed text to standard input using `-i -`:
```bash
echo 'computer science' | llm similar quotations -i -
```
When using a model like CLIP, you can find images similar to an input image using `-i filename` with `--binary`:
```bash
llm similar photos -i image.jpg --binary
```
You can filter results to only show IDs that begin with a specific prefix using --prefix:
```bash
llm similar quotations --prefix 'movies/' -c 'star wars'
```
(embeddings-cli-embed-models)=
## llm embed-models
To list all available embedding models, including those provided by plugins, run this command:
```bash
llm embed-models
```
The output should look something like this:
```
OpenAIEmbeddingModel: text-embedding-ada-002 (aliases: ada, ada-002)
OpenAIEmbeddingModel: text-embedding-3-small (aliases: 3-small)
OpenAIEmbeddingModel: text-embedding-3-large (aliases: 3-large)
...
```
Add `-q` one or more times to search for models matching those terms:
```bash
llm embed-models -q 3-small
```
(embeddings-cli-embed-models-default)=
### llm embed-models default
This command can be used to get and set the default embedding model.
This will return the name of the current default model:
```bash
llm embed-models default
```
You can set a different default like this:
```bash
llm embed-models default 3-small
```
This will set the default model to OpenAI's `3-small` model.
Any of the supported aliases for a model can be passed to this command.
You can unset the default model using `--remove-default`:
```bash
llm embed-models default --remove-default
```
When no default model is set, the `llm embed` and `llm embed-multi` commands will require that a model is specified using `-m/--model`.
## llm collections list
To list all of the collections in the embeddings database, run this command:
```bash
llm collections list
```
Add `--json` for JSON output:
```bash
llm collections list --json
```
Add `-d/--database` to specify a different database file:
```bash
llm collections list -d my-embeddings.db
```
## llm collections delete
To delete a collection from the database, run this:
```bash
llm collections delete collection-name
```
Pass `-d` to specify a different database file:
```bash
llm collections delete collection-name -d my-embeddings.db
```
================================================
FILE: docs/embeddings/index.md
================================================
(embeddings)=
# Embeddings
Embedding models allow you to take a piece of text - a word, sentence, paragraph or even a whole article, and convert that into an array of floating point numbers.
This floating point array is called an "embedding vector", and works as a numerical representation of the semantic meaning of the content in a many-multi-dimensional space.
By calculating the distance between embedding vectors, we can identify which content is semantically "nearest" to other content.
This can be used to build features like related article lookups. It can also be used to build semantic search, where a user can search for a phrase and get back results that are semantically similar to that phrase even if they do not share any exact keywords.
Some embedding models like [CLIP](https://github.com/simonw/llm-clip) can even work against binary files such as images. These can be used to search for images that are similar to other images, or to search for images that are semantically similar to a piece of text.
LLM supports multiple embedding models through {ref}`plugins <plugins>`. Once installed, an embedding model can be used on the command-line or via the Python API to calculate and store embeddings for content, and then to perform similarity searches against those embeddings.
See [LLM now provides tools for working with embeddings](https://simonwillison.net/2023/Sep/4/llm-embeddings/) for an extended explanation of embeddings, why they are useful and what you can do with them.
```{toctree}
---
maxdepth: 3
---
cli
python-api
writing-plugins
storage
```
================================================
FILE: docs/embeddings/python-api.md
================================================
(embeddings-python-api)=
# Using embeddings from Python
You can load an embedding model using its model ID or alias like this:
```python
import llm
embedding_model = llm.get_embedding_model("3-small")
```
To embed a string, returning a Python list of floating point numbers, use the `.embed()` method:
```python
vector = embedding_model.embed("my happy hound")
```
If the embedding model can handle binary input, you can call `.embed()` with a byte string instead. You can check the `supports_binary` property to see if this is supported:
```python
if embedding_model.supports_binary:
vector = embedding_model.embed(open("my-image.jpg", "rb").read())
```
The `embedding_model.supports_text` property indicates if the model supports text input.
Many embeddings models are more efficient when you embed multiple strings or binary strings at once. To embed multiple strings at once, use the `.embed_multi()` method:
```python
vectors = list(embedding_model.embed_multi(["my happy hound", "my dissatisfied cat"]))
```
This returns a generator that yields one embedding vector per string.
Embeddings are calculated in batches. By default all items will be processed in a single batch, unless the underlying embedding model has defined its own preferred batch size. You can pass a custom batch size using `batch_size=N`, for example:
```python
vectors = list(embedding_model.embed_multi(lines_from_file, batch_size=20))
```
(embeddings-python-collections)=
## Working with collections
The `llm.Collection` class can be used to work with **collections** of embeddings from Python code.
A collection is a named group of embedding vectors, each stored along with their IDs in a SQLite database table.
To work with embeddings in this way you will need an instance of a [sqlite-utils Database](https://sqlite-utils.datasette.io/en/stable/python-api.html#connecting-to-or-creating-a-database) object. You can then pass that to the `llm.Collection` constructor along with the unique string name of the collection and the ID of the embedding model you will be using with that collection:
```python
import sqlite_utils
import llm
# This collection will use an in-memory database that will be
# discarded when the Python process exits
collection = llm.Collection("entries", model_id="3-small")
# Or you can persist the database to disk like this:
db = sqlite_utils.Database("my-embeddings.db")
collection = llm.Collection("entries", db, model_id="3-small")
# You can pass a model directly using model= instead of model_id=
embedding_model = llm.get_embedding_model("3-small")
collection = llm.Collection("entries", db, model=embedding_model)
```
If the collection already exists in the database you can omit the `model` or `model_id` argument - the model ID will be read from the `collections` table.
To embed a single string and store it in the collection, use the `embed()` method:
```python
collection.embed("hound", "my happy hound")
```
This stores the embedding for the string "my happy hound" in the `entries` collection under the key `hound`.
Add `store=True` to store the text content itself in the database table along with the embedding vector.
To attach additional metadata to an item, pass a JSON-compatible dictionary as the `metadata=` argument:
```python
collection.embed("hound", "my happy hound", metadata={"name": "Hound"}, store=True)
```
This additional metadata will be stored as JSON in the `metadata` column of the embeddings database table.
(embeddings-python-bulk)=
### Storing embeddings in bulk
The `collection.embed_multi()` method can be used to store embeddings for multiple items at once. This can be more efficient for some embedding models.
```python
collection.embed_multi(
[
("hound", "my happy hound"),
("cat", "my dissatisfied cat"),
],
# Add this to store the strings in the content column:
store=True,
)
```
To include metadata to be stored with each item, call `embed_multi_with_metadata()`:
```python
collection.embed_multi_with_metadata(
[
("hound", "my happy hound", {"name": "Hound"}),
("cat", "my dissatisfied cat", {"name": "Cat"}),
],
# This can also take the store=True argument:
store=True,
)
```
The `batch_size=` argument defaults to 100, and will be used unless the embedding model itself defines a lower batch size. You can adjust this if you are having trouble with memory while embedding large collections:
```python
collection.embed_multi(
(
(i, line)
for i, line in enumerate(lines_in_file)
),
batch_size=10
)
```
(embeddings-python-collection-class)=
### Collection class reference
A collection instance has the following properties and methods:
- `id` - the integer ID of the collection in the database
- `name` - the string name of the collection (unique in the database)
- `model_id` - the string ID of the embedding model used for this collection
- `model()` - returns the `EmbeddingModel` instance, based on that `model_id`
- `count()` - returns the integer number of items in the collection
- `embed(id: str, text: str, metadata: dict=None, store: bool=False)` - embeds the given string and stores it in the collection under the given ID. Can optionally include metadata (stored as JSON) and store the text content itself in the database table.
- `embed_multi(entries: Iterable, store: bool=False, batch_size: int=100)` - see above
- `embed_multi_with_metadata(entries: Iterable, store: bool=False, batch_size: int=100)` - see above
- `similar(query: str, number: int=10)` - returns a list of entries that are most similar to the embedding of the given query string
- `similar_by_id(id: str, number: int=10)` - returns a list of entries that are most similar to the embedding of the item with the given ID
- `similar_by_vector(vector: List[float], number: int=10, skip_id: str=None)` - returns a list of entries that are most similar to the given embedding vector, optionally skipping the entry with the given ID
- `delete()` - deletes the collection and its embeddings from the database
There is also a `Collection.exists(db, name)` class method which returns a boolean value and can be used to determine if a collection exists or not in a database:
```python
if Collection.exists(db, "entries"):
print("The entries collection exists")
```
(embeddings-python-similar)=
## Retrieving similar items
Once you have populated a collection of embeddings you can retrieve the entries that are most similar to a given string using the `similar()` method.
This method uses a brute force approach, calculating distance scores against every document. This is fine for small collections, but will not scale to large collections. See [issue 216](https://github.com/simonw/llm/issues/216) for plans to add a more scalable approach via vector indexes provided by plugins.
```python
for entry in collection.similar("hound"):
print(entry.id, entry.score)
```
The string will first by embedded using the model for the collection.
The `entry` object returned is an object with the following properties:
- `id` - the string ID of the item
- `score` - the floating point similarity score between the item and the query string
- `content` - the string text content of the item, if it was stored - or `None`
- `metadata` - the dictionary (from JSON) metadata for the item, if it was stored - or `None`
This defaults to returning the 10 most similar items. You can change this by passing a different `number=` argument:
```python
for entry in collection.similar("hound", number=5):
print(entry.id, entry.score)
```
The `similar_by_id()` method takes the ID of another item in the collection and returns the most similar items to that one, based on the embedding that has already been stored for it:
```python
for entry in collection.similar_by_id("cat"):
print(entry.id, entry.score)
```
The item itself is excluded from the results.
(embeddings-sql-schema)=
## SQL schema
Here's the SQL schema used by the embeddings database:
<!-- [[[cog
import cog
from llm.embeddings_migrations import embeddings_migrations
import sqlite_utils
import re
db = sqlite_utils.Database(memory=True)
embeddings_migrations.apply(db)
cog.out("```sql\n")
for table in ("collections", "embeddings"):
schema = db[table].schema
cog.out(format(schema))
cog.out("\n")
cog.out("```\n")
]]] -->
```sql
CREATE TABLE [collections] (
[id] INTEGER PRIMARY KEY,
[name] TEXT,
[model] TEXT
)
CREATE TABLE "embeddings" (
[collection_id] INTEGER REFERENCES [collections]([id]),
[id] TEXT,
[embedding] BLOB,
[content] TEXT,
[content_blob] BLOB,
[content_hash] BLOB,
[metadata] TEXT,
[updated] INTEGER,
PRIMARY KEY ([collection_id], [id])
)
```
<!-- [[[end]]] -->
================================================
FILE: docs/embeddings/storage.md
================================================
(embeddings-storage)=
# Embedding storage format
The default output format of the `llm embed` command is a JSON array of floating point numbers.
LLM stores embeddings in space-efficient format: a little-endian binary sequences of 32-bit floating point numbers, each represented using 4 bytes.
These are stored in a `BLOB` column in a SQLite database.
The following Python functions can be used to convert between this format and an array of floating point numbers:
```python
import struct
def encode(values):
return struct.pack("<" + "f" * len(values), *values)
def decode(binary):
return struct.unpack("<" + "f" * (len(binary) // 4), binary)
```
These functions are available as `llm.encode()` and `llm.decode()`.
If you are using [NumPy](https://numpy.org/) you can decode one of these binary values like this:
```python
import numpy as np
numpy_array = np.frombuffer(value, "<f4")
```
The `<f4` format string here ensures NumPy will treat the data as a little-endian sequence of 32-bit floats.
================================================
FILE: docs/embeddings/writing-plugins.md
================================================
(embeddings-writing-plugins)=
# Writing plugins to add new embedding models
Read the {ref}`plugin tutorial <tutorial-model-plugin>` for details on how to develop and package a plugin.
This page shows an example plugin that implements and registers a new embedding model.
There are two components to an embedding model plugin:
1. An implementation of the `register_embedding_models()` hook, which takes a `register` callback function and calls it to register the new model with the LLM plugin system.
2. A class that extends the `llm.EmbeddingModel` abstract base class.
The only required method on this class is `embed_batch(texts)`, which takes an iterable of strings and returns an iterator over lists of floating point numbers.
The following example uses the [sentence-transformers](https://github.com/UKPLab/sentence-transformers) package to provide access to the [MiniLM-L6](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2) embedding model.
```python
import llm
from sentence_transformers import SentenceTransformer
@llm.hookimpl
def register_embedding_models(register):
model_id = "sentence-transformers/all-MiniLM-L6-v2"
register(SentenceTransformerModel(model_id, model_id), aliases=("all-MiniLM-L6-v2",))
class SentenceTransformerModel(llm.EmbeddingModel):
def __init__(self, model_id, model_name):
self.model_id = model_id
self.model_name = model_name
self._model = None
def embed_batch(self, texts):
if self._model is None:
self._model = SentenceTransformer(self.model_name)
results = self._model.encode(texts)
return (list(map(float, result)) for result in results)
```
Once installed, the model provided by this plugin can be used with the {ref}`llm embed <embeddings-cli-embed>` command like this:
```bash
cat file.txt | llm embed -m sentence-transformers/all-MiniLM-L6-v2
```
Or via its registered alias like this:
```bash
cat file.txt | llm embed -m all-MiniLM-L6-v2
```
[llm-sentence-transformers](https://github.com/simonw/llm-sentence-transformers) is a complete example of a plugin that provides an embedding model.
[Execute Jina embeddings with a CLI using llm-embed-jina](https://simonwillison.net/2023/Oct/26/llm-embed-jina/#how-i-built-the-plugin) talks through a similar process to add support for the [Jina embeddings models](https://jina.ai/news/jina-ai-launches-worlds-first-open-source-8k-text-embedding-rivaling-openai/).
## Embedding binary content
If your model can embed binary content, use the `supports_binary` property to indicate that:
```python
class ClipEmbeddingModel(llm.EmbeddingModel):
model_id = "clip"
supports_binary = True
supports_text= True
```
`supports_text` defaults to `True` and so is not necessary here. You can set it to `False` if your model only supports binary data.
If your model accepts binary, your `.embed_batch()` model may be called with a list of Python bytestrings. These may be mixed with regular strings if the model accepts both types of input.
[llm-clip](https://github.com/simonw/llm-clip) is an example of a model that can embed both binary and text content.
================================================
FILE: docs/fragments.md
================================================
(fragments)=
# Fragments
LLM prompts can optionally be composed out of **fragments** - reusable pieces of text that are logged just once to the database and can then be attached to multiple prompts.
These are particularly useful when you are working with long context models, which support feeding large amounts of text in as part of your prompt.
Fragments primarily exist to save space in the database, but may be used to support other features such as vendor prompt caching as well.
Fragments can be specified using several different mechanisms:
- URLs to text files online
- Paths to text files on disk
- Aliases that have been attached to a specific fragment
- Hash IDs of stored fragments, where the ID is the SHA256 hash of the fragment content
- Fragments that are provided by custom plugins - these look like `plugin-name:argument`
(fragments-usage)=
## Using fragments in a prompt
Use the `-f/--fragment` option to specify one or more fragments to be used as part of your prompt:
```bash
llm -f https://llm.datasette.io/robots.txt "Explain this robots.txt file in detail"
```
Here we are specifying a fragment using a URL. The contents of that URL will be included in the prompt that is sent to the model, prepended prior to the prompt text.
<!--[[[cog
from importlib.metadata import version
llm_version = version("llm")
cog.out(f'The URL will be fetched with the user-agent `llm/{llm_version} (https://llm.datasette.io/)`.')
]]]-->
The URL will be fetched with the user-agent `llm/0.28 (https://llm.datasette.io/)`.
<!--[[[end]]]-->
The `-f` option can be used multiple times to combine together multiple fragments.
Fragments can also be files on disk, for example:
```bash
llm -f setup.py 'extract the metadata'
```
Use `-` to specify a fragment that is read from standard input:
```bash
llm -f - 'extract the metadata' < setup.py
```
This will read the contents of `setup.py` from standard input and use it as a fragment.
Fragments can also be used as part of your system prompt. Use `--sf value` or `--system-fragment value` instead of `-f`.
## Using fragments in chat
The `chat` command also supports the `-f` and `--sf` arguments to start a chat with fragments.
```bash
llm chat -f my_doc.txt
Chatting with gpt-4
Type 'exit' or 'quit' to exit
Type '!multi' to enter multiple lines, then '!end' to finish
Type '!edit' to open your default editor and modify the prompt.
Type '!fragment <my_fragment> [<another_fragment> ...]' to insert one or more fragments
> Explain this document to me
```
Fragments can also be added *during* a chat conversation using the `!fragment <my_fragment>` command.
```bash
Chatting with gpt-4
Type 'exit' or 'quit' to exit
Type '!multi' to enter multiple lines, then '!end' to finish
Type '!edit' to open your default editor and modify the prompt.
Type '!fragment <my_fragment> [<another_fragment> ...]' to insert one or more fragments
> !fragment https://llm.datasette.io/en/stable/fragments.html
```
This can be combined with `!multi`:
```bash
> !multi
Explain the difference between fragments and templates to me
!fragment https://llm.datasette.io/en/stable/fragments.html https://llm.datasette.io/en/stable/templates.html
!end
```
Any `!fragment` lines found in a prompt created with `!edit` will not be parsed.
(fragments-browsing)=
## Browsing fragments
You can view a truncated version of the fragments you have previously stored in your database with the `llm fragments` command:
```bash
llm fragments
```
The output from that command looks like this:
```yaml
- hash: 0d6e368f9bc21f8db78c01e192ecf925841a957d8b991f5bf9f6239aa4d81815
aliases: []
datetime_utc: '2025-04-06 07:36:53'
source: https://raw.githubusercontent.com/simonw/llm-docs/refs/heads/main/llm/0.22.txt
content: |-
<documents>
<document index="1">
<source>docs/aliases.md</source>
<document_content>
(aliases)=
#...
- hash: 16b686067375182573e2aa16b5bfc1e64d48350232535d06444537e51f1fd60c
aliases: []
datetime_utc: '2025-04-06 23:03:47'
source: simonw/files-to-prompt/pyproject.toml
content: |-
[project]
name = "files-to-prompt"
version = "0.6"
description = "Concatenate a directory full of...
```
Those long `hash` values are IDs that can be used to reference a fragment in the future:
```bash
llm -f 16b686067375182573e2aa16b5bfc1e64d48350232535d06444537e51f1fd60c 'Extract metadata'
```
Use `-q searchterm` one or more times to search for fragments that match a specific set of search terms.
To view the full content of a fragment use `llm fragments show`:
```bash
llm fragments show 0d6e368f9bc21f8db78c01e192ecf925841a957d8b991f5bf9f6239aa4d81815
```
(fragments-aliases)=
## Setting aliases for fragments
You can assign aliases to fragments that you use often using the `llm fragments set` command:
```bash
llm fragments set mydocs ./docs.md
```
To remove an alias, use `llm fragments remove`:
```bash
llm fragments remove mydocs
```
You can then use that alias in place of the fragment hash ID:
```bash
llm -f mydocs 'How do I access metadata?'
```
Use `llm fragments --aliases` to see a full list of fragments that have been assigned aliases:
```bash
llm fragments --aliases
```
(fragments-logs)=
## Viewing fragments in your logs
The `llm logs` command lists the fragments that were used for a prompt. By default these are listed as fragment hash IDs, but you can use the `--expand` option to show the full content of each fragment.
This command will show the expanded fragments for your most recent conversation:
```bash
llm logs -c --expand
```
You can filter for logs that used a specific fragment using the `-f/--fragment` option:
```bash
llm logs -c -f 0d6e368f9bc21f8db78c01e192ecf925841a957d8b991f5bf9f6239aa4d81815
```
This accepts URLs, file paths, aliases, and hash IDs.
Multiple `-f` options will return responses that used **all** of the specified fragments.
Fragments are returned by `llm logs --json` as well. By default these are truncated but you can add the `-e/--expand` option to show the full content of each fragment.
```bash
llm logs -c --json --expand
```
(fragments-plugins)=
## Using fragments from plugins
LLM plugins can provide custom fragment loaders which do useful things.
One example is the [llm-fragments-github plugin](https://github.com/simonw/llm-fragments-github). This can convert the files from a public GitHub repository into a list of fragments, allowing you to ask questions about the full repository.
Here's how to try that out:
```bash
llm install llm-fragments-github
llm -f github:simonw/s3-credentials 'Suggest new features for this tool'
```
This plugin turns a single call to `-f github:simonw/s3-credentials` into multiple fragments, one for every text file in the [simonw/s3-credentials](https://github.com/simonw/s3-credentials) GitHub repository.
Running `llm logs -c` will show that this prompt incorporated 26 fragments, one for each file.
Running `llm logs -c --usage --expand` (shortcut: `llm logs -cue`) includes token usage information and turns each fragment ID into a full copy of that file. [Here's the output of that command](https://gist.github.com/simonw/c9bbbc5f6560b01f4b7882ac0194fb25).
Fragment plugins can return {ref}`attachments <usage-attachments>` (such as images) as well.
See the {ref}`register_fragment_loaders() plugin hook <plugin-hooks-register-fragment-loaders>` documentation for details on writing your own custom fragment plugin.
(fragments-loaders)=
## Listing available fragment prefixes
The `llm fragments loaders` command shows all prefixes that have been installed by plugins, along with their documentation:
```bash
llm install llm-fragments-github
llm fragments loaders
```
Example output:
```
github:
Load files from a GitHub repository as fragments
Argument is a GitHub repository URL or username/repository
issue:
Fetch GitHub issue and comments as Markdown
Argument is either "owner/repo/NUMBER"
or "https://github.com/owner/repo/issues/NUMBER"
```
================================================
FILE: docs/help.md
================================================
# CLI reference
This page lists the `--help` output for all of the `llm` commands.
<!-- [[[cog
from click.testing import CliRunner
from llm.cli import cli
def all_help(cli):
"Return all help for Click command and its subcommands"
# First find all commands and subcommands
# List will be [["command"], ["command", "subcommand"], ...]
commands = []
def find_commands(command, path=None):
path = path or []
commands.append(path + [command.name])
if hasattr(command, 'commands'):
for subcommand in command.commands.values():
find_commands(subcommand, path + [command.name])
find_commands(cli)
# Remove first item of each list (it is 'cli')
commands = [command[1:] for command in commands]
# Now generate help for each one, with appropriate heading level
output = []
for command in commands:
heading_level = len(command) + 2
result = CliRunner().invoke(cli, command + ["--help"])
hyphenated = "-".join(command)
if hyphenated:
hyphenated = "-" + hyphenated
output.append(f"\n(help{hyphenated})=")
output.append("#" * heading_level + " llm " + " ".join(command) + " --help")
output.append("```")
output.append(result.output.replace("Usage: cli", "Usage: llm").strip())
output.append("```")
return "\n".join(output)
cog.out(all_help(cli))
]]] -->
(help)=
## llm --help
```
Usage: llm [OPTIONS] COMMAND [ARGS]...
Access Large Language Models from the command-line
Documentation: https://llm.datasette.io/
LLM can run models from many different providers. Consult the plugin directory
for a list of available models:
https://llm.datasette.io/en/stable/plugins/directory.html
To get started with OpenAI, obtain an API key from them and:
$ llm keys set openai
Enter key: ...
Then execute a prompt like this:
llm 'Five outrageous names for a pet pelican'
For a full list of prompting options run:
llm prompt --help
Options:
--version Show the version and exit.
-h, --help Show this message and exit.
Commands:
prompt* Execute a prompt
aliases Manage model aliases
chat Hold an ongoing chat with a model.
collections View and manage collections of embeddings
embed Embed text and store or return the result
embed-models Manage available embedding models
embed-multi Store embeddings for multiple strings at once in the...
fragments Manage fragments that are stored in the database
install Install packages from PyPI into the same environment as LLM
keys Manage stored API keys for different models
logs Tools for exploring logged prompts and responses
models Manage available models
openai Commands for working directly with the OpenAI API
plugins List installed plugins
schemas Manage stored schemas
similar Return top N similar IDs from a collection using cosine...
templates Manage stored prompt templates
tools Manage tools that can be made available to LLMs
uninstall Uninstall Python packages from the LLM environment
```
(help-prompt)=
### llm prompt --help
```
Usage: llm prompt [OPTIONS] [PROMPT]
Execute a prompt
Documentation: https://llm.datasette.io/en/stable/usage.html
Examples:
llm 'Capital of France?'
llm 'Capital of France?' -m gpt-4o
llm 'Capital of France?' -s 'answer in Spanish'
Multi-modal models can be called with attachments like this:
llm 'Extract text from this image' -a image.jpg
llm 'Describe' -a https://static.simonwillison.net/static/2024/pelicans.jpg
cat image | llm 'describe image' -a -
# With an explicit mimetype:
cat image | llm 'describe image' --at - image/jpeg
The -x/--extract option returns just the content of the first ``` fenced code
block, if one is present. If none are present it returns the full response.
llm 'JavaScript function for reversing a string' -x
Options:
-s, --system TEXT System prompt to use
-m, --model TEXT Model to use
-d, --database FILE Path to log database
-q, --query TEXT Use first model matching these strings
-a, --attachment ATTACHMENT Attachment path or URL or -
--at, --attachment-type <TEXT TEXT>...
Attachment with explicit mimetype,
--at image.jpg image/jpeg
-T, --tool TEXT Name of a tool to make available to the model
--functions TEXT Python code block or file path defining
functions to register as tools
--td, --tools-debug Show full details of tool executions
--ta, --tools-approve Manually approve every tool execution
--cl, --chain-limit INTEGER How many chained tool responses to allow,
default 5, set 0 for unlimited
-o, --option <TEXT TEXT>... key/value options for the model
--schema TEXT JSON schema, filepath or ID
--schema-multi TEXT JSON schema to use for multiple results
-f, --fragment TEXT Fragment (alias, URL, hash or file path) to
add to the prompt
--sf, --system-fragment TEXT Fragment to add to system prompt
-t, --template TEXT Template to use
-p, --param <TEXT TEXT>... Parameters for template
--no-stream Do not stream output
-n, --no-log Don't log to database
--log Log prompt and response to the database
-c, --continue Continue the most recent conversation.
--cid, --conversation TEXT Continue the conversation with the given ID.
--key TEXT API key to use
--save TEXT Save prompt with this template name
--async Run prompt asynchronously
-u, --usage Show token usage
-x, --extract Extract first fenced code block
--xl, --extract-last Extract last fenced code block
-h, --help Show this message and exit.
```
(help-chat)=
### llm chat --help
```
Usage: llm chat [OPTIONS]
Hold an ongoing chat with a model.
Options:
-s, --system TEXT System prompt to use
-m, --model TEXT Model to use
-c, --continue Continue the most recent conversation.
--cid, --conversation TEXT Continue the conversation with the given ID.
-f, --fragment TEXT Fragment (alias, URL, hash or file path) to add
to the prompt
--sf, --system-fragment TEXT Fragment to add to system prompt
-t, --template TEXT Template to use
-p, --param <TEXT TEXT>... Parameters for template
-o, --option <TEXT TEXT>... key/value options for the model
-d, --database FILE Path to log database
--no-stream Do not stream output
--key TEXT API key to use
-T, --tool TEXT Name of a tool to make available to the model
--functions TEXT Python code block or file path defining
functions to register as tools
--td, --tools-debug Show full details of tool executions
--ta, --tools-approve Manually approve every tool execution
--cl, --chain-limit INTEGER How many chained tool responses to allow,
default 5, set 0 for unlimited
-h, --help Show this message and exit.
```
(help-keys)=
### llm keys --help
```
Usage: llm keys [OPTIONS] COMMAND [ARGS]...
Manage stored API keys for different models
Options:
-h, --help Show this message and exit.
Commands:
list* List names of all stored keys
get Return the value of a stored key
path Output the path to the keys.json file
set Save a key in the keys.json file
```
(help-keys-list)=
#### llm keys list --help
```
Usage: llm keys list [OPTIONS]
List names of all stored keys
Options:
-h, --help Show this message and exit.
```
(help-keys-path)=
#### llm keys path --help
```
Usage: llm keys path [OPTIONS]
Output the path to the keys.json file
Options:
-h, --help Show this message and exit.
```
(help-keys-get)=
#### llm keys get --help
```
Usage: llm keys get [OPTIONS] NAME
Return the value of a stored key
Example usage:
export OPENAI_API_KEY=$(llm keys get openai)
Options:
-h, --help Show this message and exit.
```
(help-keys-set)=
#### llm keys set --help
```
Usage: llm keys set [OPTIONS] NAME
Save a key in the keys.json file
Example usage:
$ llm keys set openai
Enter key: ...
Options:
--value TEXT Value to set
-h, --help Show this message and exit.
```
(help-logs)=
### llm logs --help
```
Usage: llm logs [OPTIONS] COMMAND [ARGS]...
Tools for exploring logged prompts and responses
Options:
-h, --help Show this message and exit.
Commands:
list* Show logged prompts and their responses
backup Backup your logs database to this file
off Turn off logging for all prompts
on Turn on logging for all prompts
path Output the path to the logs.db file
status Show current status of database logging
```
(help-logs-path)=
#### llm logs path --help
```
Usage: llm logs path [OPTIONS]
Output the path to the logs.db file
Options:
-h, --help Show this message and exit.
```
(help-logs-status)=
#### llm logs status --help
```
Usage: llm logs status [OPTIONS]
Show current status of database logging
Options:
-h, --help Show this message and exit.
```
(help-logs-backup)=
#### llm logs backup --help
```
Usage: llm logs backup [OPTIONS] PATH
Backup your logs database to this file
Options:
-h, --help Show this message and exit.
```
(help-logs-on)=
#### llm logs on --help
```
Usage: llm logs on [OPTIONS]
Turn on logging for all prompts
Options:
-h, --help Show this message and exit.
```
(help-logs-off)=
#### llm logs off --help
```
Usage: llm logs off [OPTIONS]
Turn off logging for all prompts
Options:
-h, --help Show this message and exit.
```
(help-logs-list)=
#### llm logs list --help
```
Usage: llm logs list [OPTIONS]
Show logged prompts and their responses
Options:
-n, --count INTEGER Number of entries to show - defaults to 3, use 0
for all
-d, --database FILE Path to log database
-m, --model TEXT Filter by model or model alias
-q, --query TEXT Search for logs matching this string
-f, --fragment TEXT Filter for prompts using these fragments
-T, --tool TEXT Filter for prompts with results from these tools
--tools Filter for prompts with results from any tools
--schema TEXT JSON schema, filepath or ID
--schema-multi TEXT JSON schema used for multiple results
-l, --latest Return latest results matching search query
--data Output newline-delimited JSON data for schema
--data-array Output JSON array of data for schema
--data-key TEXT Return JSON objects from array in this key
--data-ids Attach corresponding IDs to JSON objects
-t, --truncate Truncate long strings in output
-s, --short Shorter YAML output with truncated prompts
-u, --usage Include token usage
-r, --response Just output the last response
-x, --extract Extract first fenced code block
--xl, --extract-last Extract last fenced code block
-c, --current Show logs from the current conversation
--cid, --conversation TEXT Show logs for this conversation ID
--id-gt TEXT Return responses with ID > this
--id-gte TEXT Return responses with ID >= this
--json Output logs as JSON
-e, --expand Expand fragments to show their content
-h, --help Show this message and exit.
```
(help-models)=
### llm models --help
```
Usage: llm models [OPTIONS] COMMAND [ARGS]...
Manage available models
Options:
-h, --help Show this message and exit.
Commands:
list* List available models
default Show or set the default model
options Manage default options for models
```
(help-models-list)=
#### llm models list --help
```
Usage: llm models list [OPTIONS]
List available models
Options:
--options Show options for each model, if available
--async List async models
--schemas List models that support schemas
--tools List models that support tools
-q, --query TEXT Search for models matching these strings
-m, --model TEXT Specific model IDs
-h, --help Show this message and exit.
```
(help-models-default)=
#### llm models default --help
```
Usage: llm models default [OPTIONS] [MODEL]
Show or set the default model
Options:
-h, --help Show this message and exit.
```
(help-models-options)=
#### llm models options --help
```
Usage: llm models options [OPTIONS] COMMAND [ARGS]...
Manage default options for models
Options:
-h, --help Show this message and exit.
Commands:
list* List default options for all models
clear Clear default option(s) for a model
set Set a default option for a model
show List default options set for a specific model
```
(help-models-options-list)=
##### llm models options list --help
```
Usage: llm models options list [OPTIONS]
List default options for all models
Example usage:
llm models options list
Options:
-h, --help Show this message and exit.
```
(help-models-options-show)=
##### llm models options show --help
```
Usage: llm models options show [OPTIONS] MODEL
List default options set for a specific model
Example usage:
llm models options show gpt-4o
Options:
-h, --help Show this message and exit.
```
(help-models-options-set)=
##### llm models options set --help
```
Usage: llm models options set [OPTIONS] MODEL KEY VALUE
Set a default option for a model
Example usage:
llm models options set gpt-4o temperature 0.5
Options:
-h, --help Show this message and exit.
```
(help-models-options-clear)=
##### llm models options clear --help
```
Usage: llm models options clear [OPTIONS] MODEL [KEY]
Clear default option(s) for a model
Example usage:
llm models options clear gpt-4o
# Or for a single option
llm models options clear gpt-4o temperature
Options:
-h, --help Show this message and exit.
```
(help-templates)=
### llm templates --help
```
Usage: llm templates [OPTIONS] COMMAND [ARGS]...
Manage stored prompt templates
Options:
-h, --help Show this message and exit.
Commands:
list* List available prompt templates
edit Edit the specified prompt template using the default $EDITOR
loaders Show template loaders registered by plugins
path Output the path to the templates directory
show Show the specified prompt template
```
(help-templates-list)=
#### llm templates list --help
```
Usage: llm templates list [OPTIONS]
List available prompt templates
Options:
-h, --help Show this message and exit.
```
(help-templates-show)=
#### llm templates show --help
```
Usage: llm templates show [OPTIONS] NAME
Show the specified prompt template
Options:
-h, --help Show this message and exit.
```
(help-templates-edit)=
#### llm templates edit --help
```
Usage: llm templates edit [OPTIONS] NAME
Edit the specified prompt template using the default $EDITOR
Options:
-h, --help Show this message and exit.
```
(help-templates-path)=
#### llm templates path --help
```
Usage: llm templates path [OPTIONS]
Output the path to the templates directory
Options:
-h, --help Show this message and exit.
```
(help-templates-loaders)=
#### llm templates loaders --help
```
Usage: llm templates loaders [OPTIONS]
Show template loaders registered by plugins
Options:
-h, --help Show this message and exit.
```
(help-schemas)=
### llm schemas --help
```
Usage: llm schemas [OPTIONS] COMMAND [ARGS]...
Manage stored schemas
Options:
-h, --help Show this message and exit.
Commands:
list* List stored schemas
dsl Convert LLM's schema DSL to a JSON schema
show Show a stored schema
```
(help-schemas-list)=
#### llm schemas list --help
```
Usage: llm schemas list [OPTIONS]
List stored schemas
Options:
-d, --database FILE Path to log database
-q, --query TEXT Search for schemas matching this string
--full Output full schema contents
--json Output as JSON
--nl Output as newline-delimited JSON
-h, --help Show this message and exit.
```
(help-schemas-show)=
#### llm schemas show --help
```
Usage: llm schemas show [OPTIONS] SCHEMA_ID
Show a stored schema
Options:
-d, --database FILE Path to log database
-h, --help Show this message and exit.
```
(help-schemas-dsl)=
#### llm schemas dsl --help
```
Usage: llm schemas dsl [OPTIONS] INPUT
Convert LLM's schema DSL to a JSON schema
llm schema dsl 'name, age int, bio: their bio'
Options:
--multi Wrap in an array
-h, --help Show this message and exit.
```
(help-tools)=
### llm tools --help
```
Usage: llm tools [OPTIONS] COMMAND [ARGS]...
Manage tools that can be made available to LLMs
Options:
-h, --help Show this message and exit.
Commands:
list* List available tools that have been provided by plugins
```
(help-tools-list)=
#### llm tools list --help
```
Usage: llm tools list [OPTIONS] [TOOL_DEFS]...
List available tools that have been provided by plugins
Options:
--json Output as JSON
--functions TEXT Python code block or file path defining functions to
register as tools
-h, --help Show this message and exit.
```
(help-aliases)=
### llm aliases --help
```
Usage: llm aliases [OPTIONS] COMMAND [ARGS]...
Manage model aliases
Options:
-h, --help Show this message and exit.
Commands:
list* List current aliases
path Output the path to the aliases.json file
remove Remove an alias
set Set an alias for a model
```
(help-aliases-list)=
#### llm aliases list --help
```
Usage: llm aliases list [OPTIONS]
List current aliases
Options:
--json Output as JSON
-h, --help Show this message and exit.
```
(help-aliases-set)=
#### llm aliases set --help
```
Usage: llm aliases set [OPTIONS] ALIAS [MODEL_ID]
Set an alias for a model
Example usage:
llm aliases set mini gpt-4o-mini
Alternatively you can omit the model ID and specify one or more -q options.
The first model matching all of those query strings will be used.
llm aliases set mini -q 4o -q mini
Options:
-q, --query TEXT Set alias for model matching these strings
-h, --help Show this message and exit.
```
(help-aliases-remove)=
#### llm aliases remove --help
```
Usage: llm aliases remove [OPTIONS] ALIAS
Remove an alias
Example usage:
$ llm aliases remove turbo
Options:
-h, --help Show this message and exit.
```
(help-aliases-path)=
#### llm aliases path --help
```
Usage: llm aliases path [OPTIONS]
Output the path to the aliases.json file
Options:
-h, --help Show this message and exit.
```
(help-fragments)=
### llm fragments --help
```
Usage: llm fragments [OPTIONS] COMMAND [ARGS]...
Manage fragments that are stored in the database
Fragments are reusable snippets of text that are shared across multiple
prompts.
Options:
-h, --help Show this message and exit.
Commands:
list* List current fragments
loaders Show fragment loaders registered by plugins
remove Remove a fragment alias
set Set an alias for a fragment
show Display the fragment stored under an alias or hash
```
(help-fragments-list)=
#### llm fragments list --help
```
Usage: llm fragments list [OPTIONS]
List current fragments
Options:
-q, --query TEXT Search for fragments matching these strings
--aliases Show only fragments with aliases
--json Output as JSON
-h, --help Show this message and exit.
```
(help-fragments-set)=
#### llm fragments set --help
```
Usage: llm fragments set [OPTIONS] ALIAS FRAGMENT
Set an alias for a fragment
Accepts an alias and a file path, URL, hash or '-' for stdin
Example usage:
llm fragments set mydocs ./docs.md
Options:
-h, --help Show this message and exit.
```
(help-fragments-show)=
#### llm fragments show --help
```
Usage: llm fragments show [OPTIONS] ALIAS_OR_HASH
Display the fragment stored under an alias or hash
llm fragments show mydocs
Options:
-h, --help Show this message and exit.
```
(help-fragments-remove)=
#### llm fragments remove --help
```
Usage: llm fragments remove [OPTIONS] ALIAS
Remove a fragment alias
Example usage:
llm fragments remove docs
Options:
-h, --help Show this message and exit.
```
(help-fragments-loaders)=
#### llm fragments loaders --help
```
Usage: llm fragments loaders [OPTIONS]
Show fragment loaders registered by plugins
Options:
-h, --help Show this message and exit.
```
(help-plugins)=
### llm plugins --help
```
Usage: llm plugins [OPTIONS]
List installed plugins
Options:
--all Include built-in default plugins
--hook TEXT Filter for plugins that implement this hook
-h, --help Show this message and exit.
```
(help-install)=
### llm install --help
```
Usage: llm install [OPTIONS] [PACKAGES]...
Install packages from PyPI into the same environment as LLM
Options:
-U, --upgrade Upgrade packages to latest version
-e, --editable TEXT Install a project in editable mode from this path
--force-reinstall Reinstall all packages even if they are already up-to-
date
--no-cache-dir Disable the cache
--pre Include pre-release and development versions
-h, --help Show this message and exit.
```
(help-uninstall)=
### llm uninstall --help
```
Usage: llm uninstall [OPTIONS] PACKAGES...
Uninstall Python packages from the LLM environment
Options:
-y, --yes Don't ask for confirmation
-h, --help Show this message and exit.
```
(help-embed)=
### llm embed --help
```
Usage: llm embed [OPTIONS] [COLLECTION] [ID]
Embed text and store or return the result
Options:
-i, --input PATH File to embed
-m, --model TEXT Embedding model to use
--store Store the text itself in the database
-d, --database FILE
-c, --content TEXT Content to embed
--binary Treat input as binary data
--metadata TEXT JSON object metadata to store
-f, --format [json|blob|base64|hex]
Output format
-h, --help Show this message and exit.
```
(help-embed-multi)=
### llm embed-multi --help
```
Usage: llm embed-multi [OPTIONS] COLLECTION [INPUT_PATH]
Store embeddings for multiple strings at once in the specified collection.
Input data can come from one of three sources:
1. A CSV, TSV, JSON or JSONL file:
- CSV/TSV: First column is ID, remaining columns concatenated as content
- JSON: Array of objects with "id" field and content fields
- JSONL: Newline-delimited JSON objects
Examples:
llm embed-multi docs input.csv
cat data.json | llm embed-multi docs -
llm embed-multi docs input.json --format json
2. A SQL query against a SQLite database:
- First column returned is used as ID
- Other columns concatenated to form content
Examples:
llm embed-multi docs --sql "SELECT id, title, body FROM posts"
llm embed-multi docs --attach blog blog.db --sql "SELECT id, content FROM blog.posts"
3. Files in directories matching glob patterns:
- Each file becomes one embedding
- Relative file paths become IDs
Examples:
llm embed-multi docs --files docs '**/*.md'
llm embed-multi images --files photos '*.jpg' --binary
llm embed-multi texts --files texts '*.txt' --encoding utf-8 --encoding latin-1
Options:
--format [json|csv|tsv|nl] Format of input file - defaults to auto-detect
--files <DIRECTORY TEXT>... Embed files in this directory - specify directory
and glob pattern
--encoding TEXT Encodings to try when reading --files
--binary Treat --files as binary data
--sql TEXT Read input using this SQL query
--attach <TEXT FILE>... Additional databases to attach - specify alias
and file path
--batch-size INTEGER Batch size to use when running embeddings
--prefix TEXT Prefix to add to the IDs
-m, --model TEXT Embedding model to use
--prepend TEXT Prepend this string to all content before
embedding
--store Store the text itself in the database
-d, --database FILE
-h, --help Show this message and exit.
```
(help-similar)=
### llm similar --help
```
Usage: llm similar [OPTIONS] COLLECTION [ID]
Return top N similar IDs from a collection using cosine similarity.
Example usage:
llm similar my-collection -c "I like cats"
Or to find content similar to a specific stored ID:
llm similar my-collection 1234
Options:
-i, --input PATH File to embed for comparison
-c, --content TEXT Content to embed for comparison
--binary Treat input as binary data
-n, --number INTEGER Number of results to return
-p, --plain Output in plain text format
-d, --database FILE
--prefix TEXT Just IDs with this prefix
-h, --help Show this message and exit.
```
(help-embed-models)=
### llm embed-models --help
```
Usage: llm embed-models [OPTIONS] COMMAND [ARGS]...
Manage available embedding models
Options:
-h, --help Show this message and exit.
Commands:
list* List available embedding models
default Show or set the default embedding model
```
(help-embed-models-list)=
#### llm embed-models list --help
```
Usage: llm embed-models list [OPTIONS]
List available embedding models
Options:
-q, --query TEXT Search for embedding models matching these strings
-h, --help Show this message and exit.
```
(help-embed-models-default)=
#### llm embed-models default --help
```
Usage: llm embed-models default [OPTIONS] [MODEL]
Show or set the default embedding model
Options:
--remove-default Reset to specifying no default model
-h, --help Show this message and exit.
```
(he
gitextract_ij31elfv/
├── .github/
│ ├── FUNDING.yml
│ ├── dependabot.yml
│ └── workflows/
│ ├── cog.yml
│ ├── publish.yml
│ ├── stable-docs.yml
│ └── test.yml
├── .gitignore
├── .readthedocs.yaml
├── AGENTS.md
├── Justfile
├── LICENSE
├── MANIFEST.in
├── README.md
├── docs/
│ ├── .gitignore
│ ├── Makefile
│ ├── _templates/
│ │ └── base.html
│ ├── aliases.md
│ ├── changelog.md
│ ├── conf.py
│ ├── contributing.md
│ ├── embeddings/
│ │ ├── cli.md
│ │ ├── index.md
│ │ ├── python-api.md
│ │ ├── storage.md
│ │ └── writing-plugins.md
│ ├── fragments.md
│ ├── help.md
│ ├── index.md
│ ├── logging.md
│ ├── openai-models.md
│ ├── other-models.md
│ ├── plugins/
│ │ ├── advanced-model-plugins.md
│ │ ├── directory.md
│ │ ├── index.md
│ │ ├── installing-plugins.md
│ │ ├── llm-markov/
│ │ │ ├── llm_markov.py
│ │ │ └── pyproject.toml
│ │ ├── plugin-hooks.md
│ │ ├── plugin-utilities.md
│ │ └── tutorial-model-plugin.md
│ ├── python-api.md
│ ├── related-tools.md
│ ├── requirements.txt
│ ├── schemas.md
│ ├── setup.md
│ ├── templates.md
│ ├── tools.md
│ └── usage.md
├── llm/
│ ├── __init__.py
│ ├── __main__.py
│ ├── cli.py
│ ├── default_plugins/
│ │ ├── __init__.py
│ │ ├── default_tools.py
│ │ └── openai_models.py
│ ├── embeddings.py
│ ├── embeddings_migrations.py
│ ├── errors.py
│ ├── hookspecs.py
│ ├── migrations.py
│ ├── models.py
│ ├── plugins.py
│ ├── py.typed
│ ├── templates.py
│ ├── tools.py
│ └── utils.py
├── mypy.ini
├── pyproject.toml
├── pytest.ini
├── ruff.toml
└── tests/
├── cassettes/
│ ├── test_tools/
│ │ ├── test_tool_use_basic.yaml
│ │ └── test_tool_use_chain_of_two_calls.yaml
│ └── test_tools_streaming/
│ ├── test_tools_streaming_variant_a.yaml
│ ├── test_tools_streaming_variant_b.yaml
│ └── test_tools_streaming_variant_c.yaml
├── conftest.py
├── test-llm-load-plugins.sh
├── test_aliases.py
├── test_async.py
├── test_attachments.py
├── test_chat.py
├── test_chat_templates.py
├── test_cli_openai_models.py
├── test_cli_options.py
├── test_embed.py
├── test_embed_cli.py
├── test_encode_decode.py
├── test_fragments_cli.py
├── test_keys.py
├── test_llm.py
├── test_llm_logs.py
├── test_migrate.py
├── test_plugins.py
├── test_templates.py
├── test_tools.py
├── test_tools_streaming.py
└── test_utils.py
SYMBOL INDEX (652 symbols across 36 files)
FILE: docs/plugins/llm-markov/llm_markov.py
function register_models (line 9) | def register_models(register):
function build_markov_table (line 13) | def build_markov_table(text):
function generate (line 24) | def generate(transitions, length, start_word=None):
class Markov (line 33) | class Markov(llm.Model):
class Options (line 37) | class Options(llm.Options):
method validate_length (line 46) | def validate_length(cls, length):
method validate_delay (line 54) | def validate_delay(cls, delay):
method execute (line 61) | def execute(self, prompt, stream, response, conversation):
FILE: llm/__init__.py
function get_plugins (line 73) | def get_plugins(all=False):
function get_models_with_aliases (line 93) | def get_models_with_aliases() -> List["ModelWithAliases"]:
function _get_loaders (line 116) | def _get_loaders(hook_method) -> Dict[str, Callable]:
function get_template_loaders (line 132) | def get_template_loaders() -> Dict[str, Callable[[str], Template]]:
function get_fragment_loaders (line 137) | def get_fragment_loaders() -> Dict[
function get_tools (line 145) | def get_tools() -> Dict[str, Union[Tool, Type[Toolbox]]]:
function get_embedding_models_with_aliases (line 218) | def get_embedding_models_with_aliases() -> List["EmbeddingModelWithAlias...
function get_embedding_models (line 241) | def get_embedding_models():
function get_embedding_model (line 252) | def get_embedding_model(name):
function get_embedding_model_aliases (line 260) | def get_embedding_model_aliases() -> Dict[str, EmbeddingModel]:
function get_async_model_aliases (line 269) | def get_async_model_aliases() -> Dict[str, AsyncModel]:
function get_model_aliases (line 281) | def get_model_aliases() -> Dict[str, Model]:
class UnknownModelError (line 291) | class UnknownModelError(KeyError):
function get_models (line 295) | def get_models() -> List[Model]:
function get_async_models (line 301) | def get_async_models() -> List[AsyncModel]:
function get_async_model (line 307) | def get_async_model(name: Optional[str] = None) -> AsyncModel:
function get_model (line 326) | def get_model(name: Optional[str] = None, _skip_async: bool = False) -> ...
function get_key (line 347) | def get_key(
function load_keys (line 387) | def load_keys():
function user_dir (line 395) | def user_dir():
function set_alias (line 405) | def set_alias(alias, model_id_or_alias):
function remove_alias (line 434) | def remove_alias(alias):
function encode (line 451) | def encode(values):
function decode (line 455) | def decode(binary):
function cosine_similarity (line 459) | def cosine_similarity(a, b):
function get_default_model (line 466) | def get_default_model(filename="default_model.txt", default=DEFAULT_MODEL):
function set_default_model (line 474) | def set_default_model(model, filename="default_model.txt"):
function get_default_embedding_model (line 482) | def get_default_embedding_model():
function set_default_embedding_model (line 486) | def set_default_embedding_model(model):
FILE: llm/cli.py
class FragmentNotFound (line 88) | class FragmentNotFound(Exception):
function validate_fragment_alias (line 92) | def validate_fragment_alias(ctx, param, value):
function resolve_fragments (line 98) | def resolve_fragments(
function process_fragments_in_chat (line 173) | def process_fragments_in_chat(
class AttachmentError (line 206) | class AttachmentError(Exception):
function resolve_attachment (line 212) | def resolve_attachment(value):
class AttachmentType (line 254) | class AttachmentType(click.ParamType):
method convert (line 257) | def convert(self, value, param, ctx):
function resolve_attachment_with_type (line 264) | def resolve_attachment_with_type(value: str, mimetype: str) -> Attachment:
function attachment_types_callback (line 280) | def attachment_types_callback(ctx, param, values) -> List[Attachment]:
function json_validator (line 287) | def json_validator(object_name):
function schema_option (line 302) | def schema_option(fn):
function cli (line 318) | def cli():
function prompt (line 479) | def prompt(
function chat (line 1022) | def chat(
function load_conversation (line 1244) | def load_conversation(
function keys (line 1281) | def keys():
function keys_list (line 1286) | def keys_list():
function keys_path_command (line 1299) | def keys_path_command():
function keys_get (line 1306) | def keys_get(name):
function keys_set (line 1328) | def keys_set(name, value):
function logs (line 1357) | def logs():
function logs_path (line 1362) | def logs_path():
function logs_status (line 1368) | def logs_status():
function backup (line 1390) | def backup(path):
function logs_turn_on (line 1405) | def logs_turn_on():
function logs_turn_off (line 1413) | def logs_turn_off():
function logs_list (line 1576) | def logs_list(
function models (line 2205) | def models():
function models_list (line 2231) | def models_list(options, async_, schemas, tools, query, model_ids):
function models_default (line 2317) | def models_default(model):
function templates (line 2335) | def templates():
function templates_list (line 2340) | def templates_list():
function templates_show (line 2372) | def templates_show(name):
function templates_edit (line 2389) | def templates_edit(name):
function templates_path (line 2401) | def templates_path():
function templates_loaders (line 2407) | def templates_loaders():
function schemas (line 2426) | def schemas():
function schemas_list (line 2454) | def schemas_list(path, database, queries, full, json_, nl):
function schemas_show (line 2530) | def schemas_show(schema_id, path, database):
function schemas_dsl_debug (line 2550) | def schemas_dsl_debug(input, multi):
function tools (line 2566) | def tools():
function tools_list (line 2579) | def tools_list(tool_defs, json_, python_tools):
function aliases (line 2683) | def aliases():
function aliases_list (line 2689) | def aliases_list(json_):
function aliases_set (line 2722) | def aliases_set(alias, model_id, query):
function aliases_remove (line 2764) | def aliases_remove(alias):
function aliases_path (line 2780) | def aliases_path():
function fragments (line 2790) | def fragments():
function fragments_list (line 2808) | def fragments_list(queries, aliases, json_):
function fragments_set (line 2868) | def fragments_set(alias, fragment):
function fragments_show (line 2899) | def fragments_show(alias_or_hash):
function fragments_remove (line 2917) | def fragments_remove(alias):
function fragments_loaders (line 2935) | def fragments_loaders():
function plugins_list (line 2959) | def plugins_list(all, hooks):
function display_truncated (line 2968) | def display_truncated(text):
function install (line 3001) | def install(packages, upgrade, editable, force_reinstall, no_cache_dir, ...
function uninstall (line 3022) | def uninstall(packages, yes):
function embed (line 3065) | def embed(
function embed_multi (line 3195) | def embed_multi(
function similar (line 3390) | def similar(collection, id, input, content, binary, number, plain, datab...
function embed_models (line 3457) | def embed_models():
function embed_models_list (line 3468) | def embed_models_list(query):
function embed_models_default (line 3487) | def embed_models_default(model, remove_default):
function collections (line 3512) | def collections():
function collections_path (line 3517) | def collections_path():
function embed_db_collections (line 3531) | def embed_db_collections(database, json_):
function collections_delete (line 3569) | def collections_delete(collection, database):
function options (line 3592) | def options():
function options_list (line 3597) | def options_list():
function options_show (line 3619) | def options_show(model):
function options_set (line 3651) | def options_set(model, key, value):
function options_clear (line 3686) | def options_clear(model, key):
function template_dir (line 3724) | def template_dir():
function logs_db_path (line 3730) | def logs_db_path():
function get_history (line 3734) | def get_history(chat_id):
function render_errors (line 3753) | def render_errors(errors):
function _human_readable_size (line 3766) | def _human_readable_size(size_bytes):
function logs_on (line 3780) | def logs_on():
function get_all_model_options (line 3784) | def get_all_model_options() -> dict:
function get_model_options (line 3800) | def get_model_options(model_id: str) -> dict:
function set_model_option (line 3822) | def set_model_option(model_id: str, key: str, value: Any) -> None:
function clear_model_option (line 3851) | def clear_model_option(model_id: str, key: str) -> None:
class LoadTemplateError (line 3879) | class LoadTemplateError(ValueError):
function _parse_yaml_template (line 3883) | def _parse_yaml_template(name, content):
function load_template (line 3899) | def load_template(name: str) -> Template:
function _tools_from_code (line 3937) | def _tools_from_code(code_or_path: str) -> List[Tool]:
function _debug_tool_call (line 3959) | def _debug_tool_call(_, tool_call, tool_result):
function _approve_tool_call (line 3999) | def _approve_tool_call(_, tool_call):
function _gather_tools (line 4012) | def _gather_tools(
function _get_conversation_tools (line 4044) | def _get_conversation_tools(conversation, tools):
FILE: llm/default_plugins/default_tools.py
function register_tools (line 6) | def register_tools(register):
FILE: llm/default_plugins/openai_models.py
function register_models (line 34) | def register_models(register):
function register_embedding_models (line 321) | def register_embedding_models(register):
class OpenAIEmbeddingModel (line 358) | class OpenAIEmbeddingModel(EmbeddingModel):
method __init__ (line 363) | def __init__(self, model_id, openai_model_id, dimensions=None):
method embed_batch (line 368) | def embed_batch(self, items: Iterable[Union[str, bytes]]) -> Iterator[...
function register_commands (line 381) | def register_commands(cli):
class SharedOptions (line 423) | class SharedOptions(llm.Options):
method validate_logit_bias (line 486) | def validate_logit_bias(cls, logit_bias):
class ReasoningEffortEnum (line 511) | class ReasoningEffortEnum(str, Enum):
class OptionsForReasoning (line 520) | class OptionsForReasoning(SharedOptions):
function _attachment (line 535) | def _attachment(attachment):
class _Shared (line 564) | class _Shared:
method __init__ (line 565) | def __init__(
method __str__ (line 621) | def __str__(self) -> str:
method build_messages (line 624) | def build_messages(self, prompt, conversation):
method set_usage (line 701) | def set_usage(self, response, usage):
method get_client (line 711) | def get_client(self, key, *, async_=False):
method build_kwargs (line 736) | def build_kwargs(self, prompt, stream):
class Chat (line 765) | class Chat(_Shared, KeyModel):
class Options (line 770) | class Options(SharedOptions):
method execute (line 776) | def execute(
class AsyncChat (line 855) | class AsyncChat(_Shared, AsyncKeyModel):
class Options (line 860) | class Options(SharedOptions):
method execute (line 866) | async def execute(
class Completion (line 947) | class Completion(Chat):
class Options (line 948) | class Options(SharedOptions):
method __init__ (line 955) | def __init__(self, *args, default_max_tokens=None, **kwargs):
method __str__ (line 959) | def __str__(self) -> str:
method execute (line 962) | def execute(
function not_nulls (line 1013) | def not_nulls(data) -> dict:
function combine_chunks (line 1017) | def combine_chunks(chunks: List) -> dict:
function redact_data (line 1065) | def redact_data(input_dict):
FILE: llm/embeddings.py
class Entry (line 14) | class Entry:
class Collection (line 21) | class Collection:
class DoesNotExist (line 22) | class DoesNotExist(Exception):
method __init__ (line 25) | def __init__(
method model (line 88) | def model(self) -> EmbeddingModel:
method count (line 97) | def count(self) -> int:
method embed (line 115) | def embed(
method embed_multi (line 153) | def embed_multi(
method embed_multi_with_metadata (line 173) | def embed_multi_with_metadata(
method similar_by_vector (line 238) | def similar_by_vector(
method similar_by_id (line 297) | def similar_by_id(
method similar (line 326) | def similar(
method exists (line 344) | def exists(cls, db: Database, name: str) -> bool:
method delete (line 354) | def delete(self):
method content_hash (line 363) | def content_hash(input: Union[str, bytes]) -> bytes:
FILE: llm/embeddings_migrations.py
function m001_create_tables (line 9) | def m001_create_tables(db):
function m002_foreign_key (line 25) | def m002_foreign_key(db):
function m003_add_updated (line 30) | def m003_add_updated(db):
function m004_store_content_hash (line 41) | def m004_store_content_hash(db):
function m005_add_content_blob (line 85) | def m005_add_content_blob(db):
FILE: llm/errors.py
class ModelError (line 1) | class ModelError(Exception):
class NeedsKeyException (line 5) | class NeedsKeyException(ModelError):
FILE: llm/hookspecs.py
function register_commands (line 9) | def register_commands(cli):
function register_models (line 14) | def register_models(register):
function register_embedding_models (line 19) | def register_embedding_models(register):
function register_template_loaders (line 24) | def register_template_loaders(register):
function register_fragment_loaders (line 29) | def register_fragment_loaders(register):
function register_tools (line 34) | def register_tools(register):
FILE: llm/migrations.py
function migrate (line 8) | def migrate(db):
function ensure_migrations_table (line 24) | def ensure_migrations_table(db):
function m001_initial (line 36) | def m001_initial(db):
function m002_id_primary_key (line 57) | def m002_id_primary_key(db):
function m003_chat_id_foreign_key (line 62) | def m003_chat_id_foreign_key(db):
function m004_column_order (line 68) | def m004_column_order(db):
function m004_drop_provider (line 83) | def m004_drop_provider(db):
function m005_debug (line 88) | def m005_debug(db):
function m006_new_logs_table (line 94) | def m006_new_logs_table(db):
function m007_finish_logs_table (line 131) | def m007_finish_logs_table(db):
function m008_reply_to_id_foreign_key (line 142) | def m008_reply_to_id_foreign_key(db):
function m008_fix_column_order_in_logs (line 147) | def m008_fix_column_order_in_logs(db):
function m009_delete_logs_table_if_empty (line 168) | def m009_delete_logs_table_if_empty(db):
function m010_create_new_log_tables (line 176) | def m010_create_new_log_tables(db):
function m011_fts_for_responses (line 205) | def m011_fts_for_responses(db):
function m012_attachments_tables (line 210) | def m012_attachments_tables(db):
function m013_usage (line 236) | def m013_usage(db):
function m014_schemas (line 243) | def m014_schemas(db):
function m015_fragments_tables (line 261) | def m015_fragments_tables(db):
function m016_fragments_table_pks (line 308) | def m016_fragments_table_pks(db):
function m017_tools_tables (line 316) | def m017_tools_tables(db):
function m017_tools_plugin (line 374) | def m017_tools_plugin(db):
function m018_tool_instances (line 379) | def m018_tool_instances(db):
function m019_resolved_model (line 396) | def m019_resolved_model(db):
function m020_tool_results_attachments (line 403) | def m020_tool_results_attachments(db):
function m021_tool_results_exception (line 419) | def m021_tool_results_exception(db):
FILE: llm/models.py
class Usage (line 48) | class Usage:
class Attachment (line 55) | class Attachment:
method id (line 62) | def id(self):
method resolve_type (line 75) | def resolve_type(self):
method content_bytes (line 89) | def content_bytes(self):
method base64_content (line 100) | def base64_content(self):
method __repr__ (line 103) | def __repr__(self):
method from_row (line 116) | def from_row(cls, row):
class Tool (line 127) | class Tool:
method __post_init__ (line 134) | def __post_init__(self):
method hash (line 138) | def hash(self):
method function (line 150) | def function(cls, function, name=None, description=None):
function _get_arguments_input_schema (line 171) | def _get_arguments_input_schema(function, name):
class Toolbox (line 190) | class Toolbox:
method __init_subclass__ (line 206) | def __init_subclass__(cls, **kwargs):
method method_tools (line 232) | def method_tools(cls) -> List[Tool]:
method tools (line 246) | def tools(self) -> Iterable[Tool]:
method add_tool (line 259) | def add_tool(
method prepare (line 276) | def prepare(self):
method prepare_async (line 283) | async def prepare_async(self):
class ToolCall (line 291) | class ToolCall:
class ToolResult (line 298) | class ToolResult:
class ToolOutput (line 308) | class ToolOutput:
class CancelToolCall (line 322) | class CancelToolCall(Exception):
class Prompt (line 327) | class Prompt:
method __init__ (line 340) | def __init__(
method prompt (line 370) | def prompt(self):
method system (line 374) | def system(self):
function _wrap_tools (line 383) | def _wrap_tools(tools: List[ToolDef]) -> List[Tool]:
class _BaseConversation (line 398) | class _BaseConversation:
method from_row (line 408) | def from_row(cls, row: Any) -> "_BaseConversation":
class Conversation (line 413) | class Conversation(_BaseConversation):
method prompt (line 417) | def prompt(
method chain (line 451) | def chain(
method from_row (line 493) | def from_row(cls, row):
method __repr__ (line 502) | def __repr__(self):
class AsyncConversation (line 509) | class AsyncConversation(_BaseConversation):
method chain (line 513) | def chain(
method prompt (line 554) | def prompt(
method to_sync_conversation (line 588) | def to_sync_conversation(self):
method from_row (line 599) | def from_row(cls, row):
method __repr__ (line 608) | def __repr__(self):
class _BaseResponse (line 634) | class _BaseResponse:
method __init__ (line 645) | def __init__(
method add_tool_call (line 679) | def add_tool_call(self, tool_call: ToolCall):
method set_usage (line 682) | def set_usage(
method set_resolved_model (line 693) | def set_resolved_model(self, model_id: str):
method from_row (line 697) | def from_row(cls, db, row, _async=False):
method token_usage (line 806) | def token_usage(self) -> str:
method log_to_db (line 811) | def log_to_db(self, db):
class Response (line 1003) | class Response(_BaseResponse):
method on_done (line 1007) | def on_done(self, callback):
method _on_done (line 1013) | def _on_done(self):
method __str__ (line 1017) | def __str__(self) -> str:
method _force (line 1020) | def _force(self):
method text (line 1024) | def text(self) -> str:
method text_or_raise (line 1028) | def text_or_raise(self) -> str:
method execute_tool_calls (line 1031) | def execute_tool_calls(
method tool_calls (line 1129) | def tool_calls(self) -> List[ToolCall]:
method tool_calls_or_raise (line 1133) | def tool_calls_or_raise(self) -> List[ToolCall]:
method json (line 1136) | def json(self) -> Optional[Dict[str, Any]]:
method duration_ms (line 1140) | def duration_ms(self) -> int:
method datetime_utc (line 1144) | def datetime_utc(self) -> str:
method usage (line 1148) | def usage(self) -> Usage:
method __iter__ (line 1156) | def __iter__(self) -> Iterator[str]:
method __repr__ (line 1193) | def __repr__(self):
class AsyncResponse (line 1200) | class AsyncResponse(_BaseResponse):
method from_row (line 1205) | def from_row(cls, db, row, _async=False):
method on_done (line 1208) | async def on_done(self, callback):
method _on_done (line 1220) | async def _on_done(self):
method execute_tool_calls (line 1229) | async def execute_tool_calls(
method __aiter__ (line 1385) | def __aiter__(self):
method __anext__ (line 1392) | async def __anext__(self) -> str:
method _force (line 1432) | async def _force(self):
method text_or_raise (line 1440) | def text_or_raise(self) -> str:
method text (line 1445) | async def text(self) -> str:
method tool_calls (line 1449) | async def tool_calls(self) -> List[ToolCall]:
method tool_calls_or_raise (line 1453) | def tool_calls_or_raise(self) -> List[ToolCall]:
method json (line 1458) | async def json(self) -> Optional[Dict[str, Any]]:
method duration_ms (line 1462) | async def duration_ms(self) -> int:
method datetime_utc (line 1466) | async def datetime_utc(self) -> str:
method usage (line 1470) | async def usage(self) -> Usage:
method __await__ (line 1478) | def __await__(self):
method to_sync_response (line 1481) | async def to_sync_response(self) -> Response:
method fake (line 1522) | def fake(
method __repr__ (line 1545) | def __repr__(self):
class _BaseChainResponse (line 1552) | class _BaseChainResponse:
method __init__ (line 1558) | def __init__(
method log_to_db (line 1579) | def log_to_db(self, db):
class ChainResponse (line 1590) | class ChainResponse(_BaseChainResponse):
method responses (line 1595) | def responses(self) -> Iterator[Response]:
method __iter__ (line 1638) | def __iter__(self) -> Iterator[str]:
method text (line 1642) | def text(self) -> str:
class AsyncChainResponse (line 1646) | class AsyncChainResponse(_BaseChainResponse):
method responses (line 1651) | async def responses(self) -> AsyncIterator[AsyncResponse]:
method __aiter__ (line 1696) | async def __aiter__(self) -> AsyncIterator[str]:
method text (line 1701) | async def text(self) -> str:
class Options (line 1708) | class Options(BaseModel):
class _get_key_mixin (line 1715) | class _get_key_mixin:
method get_key (line 1720) | def get_key(self, explicit_key: Optional[str] = None) -> Optional[str]:
class _BaseModel (line 1749) | class _BaseModel(ABC, _get_key_mixin):
class Options (line 1757) | class Options(_Options):
method _validate_attachments (line 1760) | def _validate_attachments(
method __str__ (line 1773) | def __str__(self) -> str:
method __repr__ (line 1780) | def __repr__(self) -> str:
class _Model (line 1784) | class _Model(_BaseModel):
method conversation (line 1785) | def conversation(
method prompt (line 1800) | def prompt(
method chain (line 1834) | def chain(
class Model (line 1868) | class Model(_Model):
method execute (line 1870) | def execute(
class KeyModel (line 1880) | class KeyModel(_Model):
method execute (line 1882) | def execute(
class _AsyncModel (line 1893) | class _AsyncModel(_BaseModel):
method conversation (line 1894) | def conversation(
method prompt (line 1909) | def prompt(
method chain (line 1943) | def chain(
class AsyncModel (line 1977) | class AsyncModel(_AsyncModel):
method execute (line 1979) | async def execute(
class AsyncKeyModel (line 1991) | class AsyncKeyModel(_AsyncModel):
method execute (line 1993) | async def execute(
class EmbeddingModel (line 2006) | class EmbeddingModel(ABC, _get_key_mixin):
method _check (line 2015) | def _check(self, item: Union[str, bytes]):
method embed (line 2025) | def embed(self, item: Union[str, bytes]) -> List[float]:
method embed_multi (line 2030) | def embed_multi(
method embed_batch (line 2054) | def embed_batch(self, items: Iterable[Union[str, bytes]]) -> Iterator[...
method __str__ (line 2060) | def __str__(self) -> str:
method __repr__ (line 2063) | def __repr__(self) -> str:
class ModelWithAliases (line 2068) | class ModelWithAliases:
method matches (line 2073) | def matches(self, query: str) -> bool:
class EmbeddingModelWithAliases (line 2085) | class EmbeddingModelWithAliases:
method matches (line 2089) | def matches(self, query: str) -> bool:
function _conversation_name (line 2097) | def _conversation_name(text):
function _ensure_dict_schema (line 2105) | def _ensure_dict_schema(schema):
function _remove_titles_recursively (line 2114) | def _remove_titles_recursively(obj):
function _get_instance (line 2129) | def _get_instance(implementation):
FILE: llm/plugins.py
function load_plugins (line 21) | def load_plugins():
FILE: llm/templates.py
class AttachmentType (line 6) | class AttachmentType(BaseModel):
class Template (line 11) | class Template(BaseModel):
class MissingVariables (line 30) | class MissingVariables(Exception):
method __init__ (line 33) | def __init__(self, **data):
method evaluate (line 39) | def evaluate(
method vars (line 58) | def vars(self) -> set:
method interpolate (line 67) | def interpolate(cls, text: Optional[str], params: Dict[str, Any]) -> O...
method extract_vars (line 81) | def extract_vars(string_template: string.Template) -> List[str]:
FILE: llm/tools.py
function llm_version (line 6) | def llm_version() -> str:
function llm_time (line 11) | def llm_time() -> dict:
FILE: llm/utils.py
class Fragment (line 24) | class Fragment(str):
method __new__ (line 25) | def __new__(cls, content, *args, **kwargs):
method __init__ (line 29) | def __init__(self, content, source=""):
method id (line 33) | def id(self):
function mimetype_from_string (line 37) | def mimetype_from_string(content) -> Optional[str]:
function mimetype_from_path (line 45) | def mimetype_from_path(path) -> Optional[str]:
function dicts_to_table_string (line 53) | def dicts_to_table_string(
function remove_dict_none_values (line 77) | def remove_dict_none_values(d):
class _LogResponse (line 97) | class _LogResponse(httpx.Response):
method iter_bytes (line 98) | def iter_bytes(self, *args, **kwargs):
class _LogTransport (line 104) | class _LogTransport(httpx.BaseTransport):
method __init__ (line 105) | def __init__(self, transport: httpx.BaseTransport):
method handle_request (line 108) | def handle_request(self, request: httpx.Request) -> httpx.Response:
function _no_accept_encoding (line 118) | def _no_accept_encoding(request: httpx.Request):
function _log_response (line 122) | def _log_response(response: httpx.Response):
function logging_client (line 149) | def logging_client() -> httpx.Client:
function simplify_usage_dict (line 156) | def simplify_usage_dict(d):
function token_usage_string (line 171) | def token_usage_string(input_tokens, output_tokens, token_details) -> str:
function extract_fenced_code_block (line 182) | def extract_fenced_code_block(text: str, last: bool = False) -> Optional...
function make_schema_id (line 220) | def make_schema_id(schema: dict) -> Tuple[str, str]:
function output_rows_as_json (line 226) | def output_rows_as_json(rows, nl=False, compact=False, json_cols=()):
function resolve_schema_input (line 274) | def resolve_schema_input(db, schema_input, load_template):
function schema_summary (line 312) | def schema_summary(schema: dict) -> str:
function schema_dsl (line 354) | def schema_dsl(schema_dsl: str, multi: bool = False) -> Dict[str, Any]:
function multi_schema (line 422) | def multi_schema(schema: dict) -> dict:
function find_unused_key (line 431) | def find_unused_key(item: dict, key: str) -> str:
function truncate_string (line 438) | def truncate_string(
function ensure_fragment (line 478) | def ensure_fragment(db, content):
function ensure_tool (line 495) | def ensure_tool(db, tool):
function maybe_fenced_code (line 517) | def maybe_fenced_code(content: str) -> str:
function has_plugin_prefix (line 549) | def has_plugin_prefix(value: str) -> bool:
function _parse_kwargs (line 554) | def _parse_kwargs(arg_str: str) -> Dict[str, Any]:
function instantiate_from_spec (line 608) | def instantiate_from_spec(class_map: Dict[str, Type], spec: str):
function monotonic_ulid (line 688) | def monotonic_ulid() -> ULID:
function _fresh (line 731) | def _fresh(ms: int) -> bytes:
FILE: tests/conftest.py
function pytest_configure (line 12) | def pytest_configure(config):
function user_path (line 19) | def user_path(tmpdir):
function logs_db (line 26) | def logs_db(user_path):
function user_path_with_embeddings (line 31) | def user_path_with_embeddings(user_path):
function templates_path (line 40) | def templates_path(user_path):
function env_setup (line 47) | def env_setup(monkeypatch, user_path):
class MockModel (line 51) | class MockModel(llm.Model):
class Options (line 57) | class Options(llm.Options):
method __init__ (line 62) | def __init__(self):
method enqueue (line 67) | def enqueue(self, messages):
method execute (line 71) | def execute(self, prompt, stream, response, conversation):
class MockKeyModel (line 90) | class MockKeyModel(llm.KeyModel):
method execute (line 94) | def execute(self, prompt, stream, response, conversation, key):
class MockAsyncKeyModel (line 98) | class MockAsyncKeyModel(llm.AsyncKeyModel):
method execute (line 102) | async def execute(self, prompt, stream, response, conversation, key):
class AsyncMockModel (line 106) | class AsyncMockModel(llm.AsyncModel):
method __init__ (line 110) | def __init__(self):
method enqueue (line 115) | def enqueue(self, messages):
method execute (line 119) | async def execute(self, prompt, stream, response, conversation):
class EmbedDemo (line 138) | class EmbedDemo(llm.EmbeddingModel):
method __init__ (line 143) | def __init__(self):
method embed_batch (line 146) | def embed_batch(self, texts):
class EmbedBinaryOnly (line 159) | class EmbedBinaryOnly(EmbedDemo):
class EmbedTextOnly (line 165) | class EmbedTextOnly(EmbedDemo):
function embed_demo (line 172) | def embed_demo():
function mock_model (line 177) | def mock_model():
function async_mock_model (line 182) | def async_mock_model():
function mock_key_model (line 187) | def mock_key_model():
function mock_async_key_model (line 192) | def mock_async_key_model():
function register_embed_demo_model (line 197) | def register_embed_demo_model(embed_demo, mock_model, async_mock_model):
function register_echo_model (line 219) | def register_echo_model():
function mocked_openai_chat (line 235) | def mocked_openai_chat(httpx_mock):
function mocked_openai_chat_returning_fenced_code (line 250) | def mocked_openai_chat_returning_fenced_code(httpx_mock):
function stream_events (line 270) | def stream_events():
function mocked_openai_chat_stream (line 294) | def mocked_openai_chat_stream(httpx_mock):
function mocked_openai_completion (line 304) | def mocked_openai_completion(httpx_mock):
function stream_completion_events (line 328) | def stream_completion_events():
function mocked_openai_completion_logprobs_stream (line 400) | def mocked_openai_completion_logprobs_stream(httpx_mock):
function mocked_openai_completion_logprobs (line 411) | def mocked_openai_completion_logprobs(httpx_mock):
function mocked_localai (line 445) | def mocked_localai(httpx_mock):
function collection (line 470) | def collection():
function vcr_config (line 478) | def vcr_config():
function extract_braces (line 482) | def extract_braces(s):
FILE: tests/test_aliases.py
function test_set_alias (line 10) | def test_set_alias(model_id_or_alias):
function test_remove_alias (line 17) | def test_remove_alias():
function test_cli_aliases_list (line 28) | def test_cli_aliases_list(args):
function test_cli_aliases_list_json (line 53) | def test_cli_aliases_list_json(args):
function test_cli_aliases_set (line 82) | def test_cli_aliases_set(user_path, args, expected, expected_error):
function test_cli_aliases_path (line 96) | def test_cli_aliases_path(user_path):
function test_cli_aliases_remove (line 103) | def test_cli_aliases_remove(user_path):
function test_cli_aliases_remove_invalid (line 111) | def test_cli_aliases_remove_invalid(user_path):
function test_cli_aliases_are_registered (line 120) | def test_cli_aliases_are_registered(user_path, args):
FILE: tests/test_async.py
function test_async_model (line 6) | async def test_async_model(async_mock_model):
function test_async_model_conversation (line 25) | async def test_async_model_conversation(async_mock_model):
function test_async_on_done (line 38) | async def test_async_on_done(async_mock_model):
function test_async_conversation (line 54) | async def test_async_conversation(async_mock_model):
FILE: tests/test_attachments.py
function test_prompt_attachment (line 29) | def test_prompt_attachment(mock_model, logs_db, attachment_type, attachm...
function _count_open_fds (line 64) | def _count_open_fds():
function test_attachment_no_file_descriptor_leak (line 79) | def test_attachment_no_file_descriptor_leak(tmp_path):
FILE: tests/test_chat.py
function test_chat_basic (line 12) | def test_chat_basic(mock_model, logs_db):
function test_chat_system (line 134) | def test_chat_system(mock_model, logs_db):
function test_chat_options (line 178) | def test_chat_options(mock_model, logs_db, user_path):
function test_chat_multi (line 269) | def test_chat_multi(mock_model, logs_db, input, expected):
function test_llm_chat_creates_log_database (line 283) | def test_llm_chat_creates_log_database(tmpdir, monkeypatch, custom_datab...
function test_chat_tools (line 309) | def test_chat_tools(logs_db):
function test_chat_fragments (line 374) | def test_chat_fragments(tmpdir):
FILE: tests/test_chat_templates.py
function test_chat_template_system_only_no_duplicate_prompt (line 8) | def test_chat_template_system_only_no_duplicate_prompt(
function test_chat_system_fragments_only_first_turn (line 34) | def test_chat_system_fragments_only_first_turn(tmpdir, mock_model, logs_...
function test_chat_template_loads_tools_into_logs (line 66) | def test_chat_template_loads_tools_into_logs(logs_db, templates_path):
FILE: tests/test_cli_openai_models.py
function mocked_models (line 8) | def mocked_models(httpx_mock):
function test_openai_models (line 33) | def test_openai_models(mocked_models):
function test_openai_options_min_max (line 44) | def test_openai_options_min_max():
function test_only_gpt4_audio_preview_allows_mp3_or_wav (line 64) | def test_only_gpt4_audio_preview_allows_mp3_or_wav(httpx_mock, model, fi...
function test_gpt4o_mini_sync_and_async (line 151) | def test_gpt4o_mini_sync_and_async(monkeypatch, tmpdir, httpx_mock, asyn...
FILE: tests/test_cli_options.py
function test_set_model_default_options (line 27) | def test_set_model_default_options(user_path, args, expected_options, ex...
function test_model_options_list_and_show (line 42) | def test_model_options_list_and_show(user_path):
function test_model_options_clear (line 61) | def test_model_options_clear(user_path):
function test_prompt_uses_model_options (line 84) | def test_prompt_uses_model_options(user_path):
FILE: tests/test_embed.py
function test_demo_plugin (line 9) | def test_demo_plugin():
function test_embed_huge_list (line 21) | def test_embed_huge_list(batch_size, expected_batches):
function test_embed_store (line 37) | def test_embed_store(collection):
function test_embed_metadata (line 46) | def test_embed_metadata(collection):
function test_collection (line 58) | def test_collection(collection):
function test_similar (line 88) | def test_similar(collection):
function test_similar_prefixed (line 96) | def test_similar_prefixed(collection):
function test_similar_by_id (line 103) | def test_similar_by_id(collection):
function test_embed_multi (line 118) | def test_embed_multi(with_metadata, batch_size, expected_batches):
function test_collection_delete (line 149) | def test_collection_delete(collection):
function test_binary_only_and_text_only_embedding_models (line 158) | def test_binary_only_and_text_only_embedding_models():
FILE: tests/test_embed_cli.py
function test_embed_output_format (line 44) | def test_embed_output_format(tmpdir, format_, expected, scenario):
function test_embed_errors (line 66) | def test_embed_errors(args, expected_error):
function test_embed_store (line 83) | def test_embed_store(user_path, metadata, metadata_error):
function test_embed_store_binary (line 151) | def test_embed_store_binary(user_path):
function test_collection_delete_errors (line 177) | def test_collection_delete_errors(user_path):
function test_similar_errors (line 200) | def test_similar_errors(args, expected_error, user_path_with_embeddings):
function test_similar_by_id_cli (line 207) | def test_similar_by_id_cli(user_path_with_embeddings):
function test_similar_by_id_cli_output_plain (line 220) | def test_similar_by_id_cli_output_plain(user_path_with_embeddings, option):
function test_similar_by_content_cli (line 232) | def test_similar_by_content_cli(tmpdir, user_path_with_embeddings, scena...
function test_similar_by_content_prefixed (line 286) | def test_similar_by_content_prefixed(
function test_embed_multi_file_input (line 317) | def test_embed_multi_file_input(tmpdir, use_stdin, prefix, prepend, file...
function test_embed_multi_files_binary_store (line 348) | def test_embed_multi_files_binary_store(tmpdir):
function test_embed_multi_sql (line 380) | def test_embed_multi_sql(tmpdir, use_other_db, prefix, prepend):
function test_embed_multi_batch_size (line 433) | def test_embed_multi_batch_size(embed_demo, tmpdir):
function multi_files (line 468) | def multi_files(tmpdir):
function test_embed_multi_files (line 489) | def test_embed_multi_files(multi_files, scenario, prepend):
function test_embed_multi_files_errors (line 562) | def test_embed_multi_files_errors(multi_files, args, expected_error):
function test_embed_multi_files_encoding (line 583) | def test_embed_multi_files_encoding(multi_files, extra_args, expected_er...
function test_default_embedding_model (line 618) | def test_default_embedding_model():
function test_llm_embed_models_query (line 648) | def test_llm_embed_models_query(user_path, args, expected_model_id):
function test_default_embed_model_errors (line 657) | def test_default_embed_model_errors(user_path, default_is_set, command):
function test_duplicate_content_embedded_only_once (line 689) | def test_duplicate_content_embedded_only_once(embed_demo):
FILE: tests/test_encode_decode.py
function test_roundtrip (line 13) | def test_roundtrip(array):
FILE: tests/test_fragments_cli.py
function test_fragments_set_show_remove (line 11) | def test_fragments_set_show_remove(user_path):
function test_fragments_list (line 70) | def test_fragments_list(user_path):
function test_fragment_url_user_agent (line 130) | def test_fragment_url_user_agent(mocked_openai_chat, user_path):
FILE: tests/test_keys.py
function test_keys_in_user_path (line 11) | def test_keys_in_user_path(monkeypatch, env, user_path):
function test_keys_set (line 25) | def test_keys_set(monkeypatch, tmpdir):
function test_keys_get (line 44) | def test_keys_get(monkeypatch, tmpdir):
function test_keys_list (line 56) | def test_keys_list(monkeypatch, tmpdir, args):
function test_uses_correct_key (line 70) | def test_uses_correct_key(mocked_openai_chat, monkeypatch, tmpdir):
FILE: tests/test_llm.py
function test_version (line 14) | def test_version():
function test_llm_prompt_creates_log_database (line 23) | def test_llm_prompt_creates_log_database(
function test_llm_default_prompt (line 59) | def test_llm_default_prompt(
function test_llm_prompt_continue (line 157) | def test_llm_prompt_continue(httpx_mock, user_path, async_):
function test_extract_fenced_code (line 217) | def test_extract_fenced_code(
function test_openai_chat_stream (line 233) | def test_openai_chat_stream(mocked_openai_chat_stream, user_path):
function test_openai_completion (line 240) | def test_openai_completion(mocked_openai_completion, user_path):
function test_openai_completion_system_prompt_error (line 284) | def test_openai_completion_system_prompt_error():
function test_openai_completion_logprobs_stream (line 305) | def test_openai_completion_logprobs_stream(
function test_openai_completion_logprobs_nostream (line 343) | def test_openai_completion_logprobs_nostream(
function test_openai_localai_configuration (line 404) | def test_openai_localai_configuration(mocked_localai, user_path):
function test_prompt_select_model_with_queries (line 443) | def test_prompt_select_model_with_queries(mock_model, user_path, args, e...
function test_llm_models_options (line 493) | def test_llm_models_options(user_path):
function test_llm_models_async (line 507) | def test_llm_models_async(user_path):
function test_llm_models_filter (line 532) | def test_llm_models_filter(user_path, args, expected_model_ids, unexpect...
function test_llm_user_dir (line 544) | def test_llm_user_dir(tmpdir, monkeypatch):
function test_model_defaults (line 553) | def test_model_defaults(tmpdir, monkeypatch):
function test_get_models (line 566) | def test_get_models():
function test_get_async_models (line 578) | def test_get_async_models():
function test_mock_model (line 589) | def test_mock_model(mock_model):
class Dog (line 603) | class Dog(BaseModel):
function test_schema (line 621) | def test_schema(mock_model, use_pydantic):
function test_model_environment_variable (line 631) | def test_model_environment_variable(monkeypatch):
function test_schema_via_cli (line 650) | def test_schema_via_cli(mock_model, tmpdir, monkeypatch, use_filename):
function test_schema_using_dsl (line 717) | def test_schema_using_dsl(mock_model, tmpdir, monkeypatch, args, expected):
function test_schema_async (line 735) | async def test_schema_async(async_mock_model, use_pydantic):
function test_mock_key_model (line 744) | def test_mock_key_model(mock_key_model):
function test_mock_async_key_model (line 750) | async def test_mock_async_key_model(mock_async_key_model):
function test_sync_on_done (line 756) | def test_sync_on_done(mock_model):
function test_schemas_dsl (line 771) | def test_schemas_dsl():
function test_llm_prompt_continue_with_database (line 807) | def test_llm_prompt_continue_with_database(
function test_default_exports (line 861) | def test_default_exports():
FILE: tests/test_llm_logs.py
function log_path (line 23) | def log_path(user_path):
function schema_log_path (line 46) | def schema_log_path(user_path):
function test_logs_text (line 92) | def test_logs_text(log_path, usage):
function test_logs_text_with_options (line 138) | def test_logs_text_with_options(user_path):
function test_logs_json (line 175) | def test_logs_json(n, log_path):
function test_logs_response_only (line 196) | def test_logs_response_only(args, log_path):
function test_logs_extract_first_code (line 217) | def test_logs_extract_first_code(args, log_path):
function test_logs_extract_last_code (line 236) | def test_logs_extract_last_code(args, log_path):
function test_logs_short (line 246) | def test_logs_short(log_path, arg, usage):
function test_logs_path (line 285) | def test_logs_path(monkeypatch, env, user_path):
function test_logs_filtered (line 300) | def test_logs_filtered(user_path, model, path_option):
function test_logs_search (line 344) | def test_logs_search(user_path, query, extra_args, expected):
function test_logs_schema (line 407) | def test_logs_schema(schema_log_path, args, expected):
function test_logs_schema_data_ids (line 418) | def test_logs_schema_data_ids(schema_log_path):
function test_schemas_list_yaml (line 492) | def test_schemas_list_yaml(schema_log_path, args, expected):
function test_schemas_list_json (line 499) | def test_schemas_list_json(schema_log_path, is_nl):
function fragments_fixture (line 520) | def fragments_fixture(user_path):
function test_logs_fragments (line 664) | def test_logs_fragments(fragments_fixture, fragment_refs, expected):
function test_logs_fragments_markdown (line 713) | def test_logs_fragments_markdown(fragments_fixture):
function test_expand_fragment_json (line 896) | def test_expand_fragment_json(fragments_fixture, arg):
function test_expand_fragment_markdown (line 916) | def test_expand_fragment_markdown(fragments_fixture):
function test_logs_tools (line 935) | def test_logs_tools(logs_db):
function test_logs_backup (line 970) | def test_logs_backup(logs_db):
function test_logs_resolved_model (line 987) | def test_logs_resolved_model(logs_db, mock_model, async_mock_model, asyn...
FILE: tests/test_migrate.py
function test_migrate_blank (line 27) | def test_migrate_blank():
function test_migrate_from_original_schema (line 55) | def test_migrate_from_original_schema(has_record):
function test_migrations_with_legacy_alter_table (line 94) | def test_migrations_with_legacy_alter_table():
function test_migrations_for_embeddings (line 101) | def test_migrations_for_embeddings():
function test_backfill_content_hash (line 119) | def test_backfill_content_hash():
FILE: tests/test_plugins.py
function test_register_commands (line 13) | def test_register_commands():
function test_register_template_loaders (line 48) | def test_register_template_loaders():
function test_register_fragment_loaders (line 98) | def test_register_fragment_loaders(logs_db, httpx_mock):
function test_register_tools (line 200) | def test_register_tools(tmpdir, logs_db):
class Memory (line 508) | class Memory(llm.Toolbox):
method _get_memory (line 511) | def _get_memory(self):
method set (line 516) | def set(self, key: str, value: str):
method get (line 520) | def get(self, key: str):
method append (line 524) | def append(self, key: str, value: str):
method keys (line 529) | def keys(self):
class Filesystem (line 534) | class Filesystem(llm.Toolbox):
method __init__ (line 535) | def __init__(self, path: str):
method list_files (line 538) | async def list_files(self):
class ToolboxPlugin (line 543) | class ToolboxPlugin:
method register_tools (line 547) | def register_tools(self, register):
function test_register_toolbox (line 552) | def test_register_toolbox(tmpdir, logs_db):
function test_register_toolbox_fails_on_bad_class (line 853) | def test_register_toolbox_fails_on_bad_class():
function test_toolbox_logging_async (line 874) | def test_toolbox_logging_async(logs_db, tmpdir):
function test_plugins_command (line 971) | def test_plugins_command():
FILE: tests/test_templates.py
function test_template_evaluate (line 36) | def test_template_evaluate(
function test_templates_list_no_templates_found (line 50) | def test_templates_list_no_templates_found():
function test_templates_list (line 58) | def test_templates_list(templates_path, args):
function test_templates_prompt_save (line 154) | def test_templates_prompt_save(templates_path, args, expected, expected_...
function test_templates_error_on_missing_schema (line 178) | def test_templates_error_on_missing_schema(templates_path):
function test_execute_prompt_with_a_template (line 304) | def test_execute_prompt_with_a_template(
function test_execute_prompt_from_template_url (line 376) | def test_execute_prompt_from_template_url(httpx_mock, template, expected):
function test_execute_prompt_from_template_path (line 396) | def test_execute_prompt_from_template_path():
function test_template_respects_cli_extract_flag (line 416) | def test_template_respects_cli_extract_flag(
class Greeting (line 437) | class Greeting(Toolbox):
method __init__ (line 438) | def __init__(self, greeting: str):
method greet (line 441) | def greet(self, name: str) -> str:
class GreetingsPlugin (line 446) | class GreetingsPlugin:
method register_tools (line 450) | def register_tools(self, register):
function test_tools_in_templates (line 464) | def test_tools_in_templates(
FILE: tests/test_tools.py
function test_tool_use_basic (line 18) | def test_tool_use_basic(vcr):
function test_tool_use_chain_of_two_calls (line 73) | def test_tool_use_chain_of_two_calls(vcr):
function test_tool_use_async_tool_function (line 104) | def test_tool_use_async_tool_function():
function test_async_tools_run_tools_in_parallel (line 133) | async def test_async_tools_run_tools_in_parallel():
function test_async_toolbox (line 180) | async def test_async_toolbox():
function test_toolbox_add_tool (line 206) | def test_toolbox_add_tool():
function test_toolbox_add_tool_with_pass_self (line 235) | def test_toolbox_add_tool_with_pass_self():
function test_conversation_with_tools (line 260) | def test_conversation_with_tools(vcr):
function test_default_tool_llm_version (line 290) | def test_default_tool_llm_version():
function test_cli_tools_with_options (line 306) | def test_cli_tools_with_options():
function test_functions_tool_locals (line 327) | def test_functions_tool_locals():
function test_default_tool_llm_time (line 345) | def test_default_tool_llm_time():
function test_incorrect_tool_usage (line 372) | def test_incorrect_tool_usage():
function test_tool_returning_attachment (line 386) | def test_tool_returning_attachment():
function test_async_tool_returning_attachment (line 410) | async def test_async_tool_returning_attachment():
function test_tool_conversation_settings (line 433) | def test_tool_conversation_settings():
function test_tool_conversation_settings_async (line 455) | async def test_tool_conversation_settings_async():
function test_tool_errors (line 482) | def test_tool_errors(async_):
function test_chain_sync_cancel_only_first_of_two (line 522) | def test_chain_sync_cancel_only_first_of_two():
function test_chain_async_cancel_only_first_of_two (line 562) | async def test_chain_async_cancel_only_first_of_two():
FILE: tests/test_tools_streaming.py
function test_tools_streaming_variant_a (line 11) | def test_tools_streaming_variant_a():
function test_tools_streaming_variant_b (line 21) | def test_tools_streaming_variant_b():
function test_tools_streaming_variant_c (line 31) | def test_tools_streaming_variant_c():
FILE: tests/test_utils.py
function test_simplify_usage_dict (line 51) | def test_simplify_usage_dict(input_data, expected_output):
function test_extract_fenced_code_block (line 112) | def test_extract_fenced_code_block(input, last, expected):
function test_schema_dsl (line 223) | def test_schema_dsl(schema, expected):
function test_schema_dsl_multi (line 228) | def test_schema_dsl_multi():
function test_truncate_string (line 293) | def test_truncate_string(text, max_length, normalize_whitespace, keep_en...
function test_test_truncate_string_keep_end (line 318) | def test_test_truncate_string_keep_end(
function test_maybe_fenced_code (line 361) | def test_maybe_fenced_code(content: str, expected_fenced: bool):
function test_backtick_count_adjustment (line 386) | def test_backtick_count_adjustment(content: str, backtick_count: int):
class Files (line 400) | class Files:
method __init__ (line 401) | def __init__(self, dir="."):
class ValueFlag (line 405) | class ValueFlag:
method __init__ (line 406) | def __init__(self, value=None, flag=False):
function test_instantiate_valid (line 428) | def test_instantiate_valid(spec, expected_cls, expected_attrs):
function test_instantiate_invalid (line 447) | def test_instantiate_invalid(spec):
function test_get_key (line 452) | def test_get_key(user_path, monkeypatch):
function test_monotonic_ulids (line 466) | def test_monotonic_ulids():
function test_toolbox_config_capture (line 471) | def test_toolbox_config_capture():
Condensed preview — 96 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,090K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 17,
"preview": "github: [simonw]\n"
},
{
"path": ".github/dependabot.yml",
"chars": 155,
"preview": "version: 2\nupdates:\n- package-ecosystem: pip\n directory: \"/\"\n schedule:\n interval: daily\n groups:\n python-packa"
},
{
"path": ".github/workflows/cog.yml",
"chars": 1226,
"preview": "name: Run Cog\n\non:\n pull_request:\n types: [opened, synchronize]\n\npermissions:\n contents: write\n pull-requests: wri"
},
{
"path": ".github/workflows/publish.yml",
"chars": 1161,
"preview": "name: Publish Python Package\n\non:\n release:\n types: [created]\n\npermissions:\n contents: read\n\njobs:\n test:\n runs"
},
{
"path": ".github/workflows/stable-docs.yml",
"chars": 2141,
"preview": "name: Update Stable Docs\n\non:\n release:\n types: [published]\n push:\n branches:\n - main\n\npermissions:\n content"
},
{
"path": ".github/workflows/test.yml",
"chars": 1691,
"preview": "name: Test\n\non: [push, pull_request]\n\npermissions:\n contents: read\n\njobs:\n test:\n runs-on: ${{ matrix.os }}\n str"
},
{
"path": ".gitignore",
"chars": 109,
"preview": ".venv\n__pycache__/\n*.py[cod]\n*$py.class\nvenv\n.eggs\n.pytest_cache\n*.egg-info\n.DS_Store\n.idea/\n.vscode/\nuv.lock"
},
{
"path": ".readthedocs.yaml",
"chars": 226,
"preview": "version: 2\n\nbuild:\n os: ubuntu-22.04\n tools:\n python: \"3.11\"\n\nsphinx:\n configuration: docs/conf.py\n\nformats:\n - "
},
{
"path": "AGENTS.md",
"chars": 425,
"preview": "# AGENTS.md\n\nThis project uses a Python environment for development and tests.\n\n## Setting up a development environment\n"
},
{
"path": "Justfile",
"chars": 915,
"preview": "# Run tests and linters\n@default: test lint\n\n# Run pytest with supplied options\n@test *options:\n uv run pytest {{option"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "MANIFEST.in",
"chars": 23,
"preview": "global-exclude tests/*\n"
},
{
"path": "README.md",
"chars": 27854,
"preview": "<!-- [[[cog\n# README.md is generated from docs/index.md using sphinx_markdown_builder\nimport tempfile\nimport subprocess\n"
},
{
"path": "docs/.gitignore",
"chars": 7,
"preview": "_build\n"
},
{
"path": "docs/Makefile",
"chars": 695,
"preview": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS =\nSPHI"
},
{
"path": "docs/_templates/base.html",
"chars": 510,
"preview": "{%- extends \"!base.html\" %}\n\n{%- block htmltitle -%}\n{% if not docstitle %}\n <title>{{ title|striptags|e }}</title>\n{% "
},
{
"path": "docs/aliases.md",
"chars": 3039,
"preview": "(aliases)=\n# Model aliases\n\nLLM supports model aliases, which allow you to refer to a model by a short name instead of i"
},
{
"path": "docs/changelog.md",
"chars": 72358,
"preview": "# Changelog\n\n(v0_29)=\n## 0.29 (2025-03-17)\n\n- The `-t/--template` option now works correctly with the `-x/--extract` and"
},
{
"path": "docs/conf.py",
"chars": 5160,
"preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nfrom subprocess import PIPE, Popen\n\n# This file is execfile()d with the "
},
{
"path": "docs/contributing.md",
"chars": 2589,
"preview": "(contributing)=\n# Contributing\n\nTo contribute to this tool, first checkout the code. Then run the tests with `uv run`:\n`"
},
{
"path": "docs/embeddings/cli.md",
"chars": 15384,
"preview": "(embeddings-cli)=\n# Embedding with the CLI\n\nLLM provides command-line utilities for calculating and storing embeddings f"
},
{
"path": "docs/embeddings/index.md",
"chars": 1585,
"preview": "(embeddings)=\n# Embeddings\n\nEmbedding models allow you to take a piece of text - a word, sentence, paragraph or even a w"
},
{
"path": "docs/embeddings/python-api.md",
"chars": 8750,
"preview": "(embeddings-python-api)=\n# Using embeddings from Python\n\nYou can load an embedding model using its model ID or alias lik"
},
{
"path": "docs/embeddings/storage.md",
"chars": 1015,
"preview": "(embeddings-storage)=\n# Embedding storage format\n\nThe default output format of the `llm embed` command is a JSON array o"
},
{
"path": "docs/embeddings/writing-plugins.md",
"chars": 3158,
"preview": "(embeddings-writing-plugins)=\n# Writing plugins to add new embedding models\n\nRead the {ref}`plugin tutorial <tutorial-mo"
},
{
"path": "docs/fragments.md",
"chars": 7984,
"preview": "(fragments)=\n# Fragments\n\nLLM prompts can optionally be composed out of **fragments** - reusable pieces of text that are"
},
{
"path": "docs/help.md",
"chars": 28778,
"preview": "# CLI reference\n\nThis page lists the `--help` output for all of the `llm` commands.\n\n<!-- [[[cog\nfrom click.testing impo"
},
{
"path": "docs/index.md",
"chars": 4988,
"preview": "# LLM\n\n[](https://github.com/simonw/llm)\n[=\n# Logging to SQLite\n\n`llm` defaults to logging all prompts and responses to a SQLite database.\n\nYou can find "
},
{
"path": "docs/openai-models.md",
"chars": 7639,
"preview": "(openai-models)=\n\n# OpenAI models\n\nLLM ships with a default plugin for talking to OpenAI's API. OpenAI offer both langua"
},
{
"path": "docs/other-models.md",
"chars": 3269,
"preview": "(other-models)=\n# Other models\n\nLLM supports OpenAI models by default. You can install {ref}`plugins <plugins>` to add s"
},
{
"path": "docs/plugins/advanced-model-plugins.md",
"chars": 14581,
"preview": "(advanced-model-plugins)=\n# Advanced model plugins\n\nThe {ref}`model plugin tutorial <tutorial-model-plugin>` covers the "
},
{
"path": "docs/plugins/directory.md",
"chars": 11557,
"preview": "(plugin-directory)=\n# Plugin directory\n\nThe following plugins are available for LLM. Here's {ref}`how to install them <i"
},
{
"path": "docs/plugins/index.md",
"chars": 549,
"preview": "(plugins)=\n# Plugins\n\nLLM plugins can enhance LLM by making alternative Large Language Models available, either via API "
},
{
"path": "docs/plugins/installing-plugins.md",
"chars": 2214,
"preview": "(installing-plugins)=\n# Installing plugins\n\nPlugins must be installed in the same virtual environment as LLM itself.\n\nYo"
},
{
"path": "docs/plugins/llm-markov/llm_markov.py",
"chars": 1984,
"preview": "import llm\nimport random\nimport time\nfrom typing import Optional\nfrom pydantic import field_validator, Field\n\n\n@llm.hook"
},
{
"path": "docs/plugins/llm-markov/pyproject.toml",
"chars": 95,
"preview": "[project]\nname = \"llm-markov\"\nversion = \"0.1\"\n\n[project.entry-points.llm]\nmarkov = \"llm_markov\""
},
{
"path": "docs/plugins/plugin-hooks.md",
"chars": 10257,
"preview": "(plugin-hooks)=\n# Plugin hooks\n\nPlugins use **plugin hooks** to customize LLM's behavior. These hooks are powered by the"
},
{
"path": "docs/plugins/plugin-utilities.md",
"chars": 3617,
"preview": "(plugin-utilities)=\n# Utility functions for plugins\n\nLLM provides some utility functions that may be useful to plugins.\n"
},
{
"path": "docs/plugins/tutorial-model-plugin.md",
"chars": 21751,
"preview": "(tutorial-model-plugin)=\n\n# Developing a model plugin\n\nThis tutorial will walk you through developing a new plugin for L"
},
{
"path": "docs/python-api.md",
"chars": 26314,
"preview": "(python-api)=\n# Python API\n\nLLM provides a Python API for executing prompts, in addition to the command-line interface.\n"
},
{
"path": "docs/related-tools.md",
"chars": 2181,
"preview": "(related-tools)=\n# Related tools\n\nThe following tools are designed to be used with LLM:\n\n(related-tools-strip-tags)=\n## "
},
{
"path": "docs/requirements.txt",
"chars": 115,
"preview": "sphinx==7.2.6\nfuro==2023.9.10\nsphinx-autobuild\nsphinx-copybutton\nsphinx-markdown-builder==0.6.8\nmyst-parser\ncogapp\n"
},
{
"path": "docs/schemas.md",
"chars": 25927,
"preview": "(schemas)=\n\n# Schemas\n\nLarge Language Models are very good at producing structured output as JSON or other formats. LLM'"
},
{
"path": "docs/setup.md",
"chars": 6075,
"preview": "# Setup\n\n## Installation\n\nInstall this tool using `pip`:\n```bash\npip install llm\n```\nOr using [pipx](https://pypa.github"
},
{
"path": "docs/templates.md",
"chars": 12612,
"preview": "(prompt-templates)=\n# Templates\n\nA **template** can combine a prompt, system prompt, model, default model options, schem"
},
{
"path": "docs/tools.md",
"chars": 5134,
"preview": "(tools)=\n\n# Tools\n\nMany Large Language Models have been trained to execute tools as part of responding to a prompt. LLM "
},
{
"path": "docs/usage.md",
"chars": 41991,
"preview": "(usage)=\n# Usage\n\nThe command to run a prompt is `llm prompt 'your prompt'`. This is the default command, so you can use"
},
{
"path": "llm/__init__.py",
"chars": 14819,
"preview": "from .hookspecs import hookimpl\nfrom .errors import (\n ModelError,\n NeedsKeyException,\n)\nfrom .models import (\n "
},
{
"path": "llm/__main__.py",
"chars": 59,
"preview": "from .cli import cli\n\nif __name__ == \"__main__\":\n cli()\n"
},
{
"path": "llm/cli.py",
"chars": 126846,
"preview": "import asyncio\nimport click\nfrom click_default_group import DefaultGroup\nfrom dataclasses import asdict\nfrom importlib.m"
},
{
"path": "llm/default_plugins/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "llm/default_plugins/default_tools.py",
"chars": 150,
"preview": "import llm\nfrom llm.tools import llm_time, llm_version\n\n\n@llm.hookimpl\ndef register_tools(register):\n register(llm_ve"
},
{
"path": "llm/default_plugins/openai_models.py",
"chars": 37530,
"preview": "from llm import (\n AsyncConversation,\n AsyncKeyModel,\n AsyncResponse,\n Conversation,\n EmbeddingModel,\n "
},
{
"path": "llm/embeddings.py",
"chars": 12696,
"preview": "from .models import EmbeddingModel\nfrom .embeddings_migrations import embeddings_migrations\nfrom dataclasses import data"
},
{
"path": "llm/embeddings_migrations.py",
"chars": 2446,
"preview": "from sqlite_migrate import Migrations\nimport hashlib\nimport time\n\nembeddings_migrations = Migrations(\"llm.embeddings\")\n\n"
},
{
"path": "llm/errors.py",
"chars": 196,
"preview": "class ModelError(Exception):\n \"Models can raise this error, which will be displayed to the user\"\n\n\nclass NeedsKeyExce"
},
{
"path": "llm/hookspecs.py",
"chars": 823,
"preview": "from pluggy import HookimplMarker\nfrom pluggy import HookspecMarker\n\nhookspec = HookspecMarker(\"llm\")\nhookimpl = Hookimp"
},
{
"path": "llm/migrations.py",
"chars": 10378,
"preview": "import datetime\nfrom typing import Callable, List\n\nMIGRATIONS: List[Callable] = []\nmigration = MIGRATIONS.append\n\n\ndef m"
},
{
"path": "llm/models.py",
"chars": 72092,
"preview": "import asyncio\nimport base64\nfrom condense_json import condense_json\nfrom dataclasses import dataclass, field\nimport dat"
},
{
"path": "llm/plugins.py",
"chars": 1618,
"preview": "import importlib\nfrom importlib import metadata\nimport os\nimport pluggy\nimport sys\nfrom . import hookspecs\n\nDEFAULT_PLUG"
},
{
"path": "llm/py.typed",
"chars": 0,
"preview": ""
},
{
"path": "llm/templates.py",
"chars": 2917,
"preview": "from pydantic import BaseModel, ConfigDict\nimport string\nfrom typing import Optional, Any, Dict, List, Tuple\n\n\nclass Att"
},
{
"path": "llm/tools.py",
"chars": 1111,
"preview": "from datetime import datetime, timezone\nfrom importlib.metadata import version\nimport time\n\n\ndef llm_version() -> str:\n "
},
{
"path": "llm/utils.py",
"chars": 24293,
"preview": "import click\nimport hashlib\nimport httpx\nimport itertools\nimport json\nimport pathlib\nimport puremagic\nimport re\nimport s"
},
{
"path": "mypy.ini",
"chars": 169,
"preview": "[mypy]\n\n[mypy-pluggy.*]\nignore_missing_imports = True\n\n[mypy-click_default_group.*]\nignore_missing_imports = True\n\n[mypy"
},
{
"path": "pyproject.toml",
"chars": 2228,
"preview": "[project]\nname = \"llm\"\nversion = \"0.29\"\ndescription = \"CLI utility and Python library for interacting with Large Languag"
},
{
"path": "pytest.ini",
"chars": 54,
"preview": "[pytest]\nasyncio_default_fixture_loop_scope = function"
},
{
"path": "ruff.toml",
"chars": 18,
"preview": "line-length = 160\n"
},
{
"path": "tests/cassettes/test_tools/test_tool_use_basic.yaml",
"chars": 19888,
"preview": "interactions:\n- request:\n body: '{\"messages\":[{\"role\":\"user\",\"content\":\"What is 1231 * 2331?\"}],\"model\":\"gpt-4o-mini\""
},
{
"path": "tests/cassettes/test_tools/test_tool_use_chain_of_two_calls.yaml",
"chars": 12496,
"preview": "interactions:\n- request:\n body: '{\"messages\":[{\"role\":\"user\",\"content\":\"Can the country of Crumpet have\n dragons"
},
{
"path": "tests/cassettes/test_tools_streaming/test_tools_streaming_variant_a.yaml",
"chars": 10420,
"preview": "interactions:\n- request:\n body: '{\"messages\":[{\"role\":\"user\",\"content\":\"What is the current llm version?\"}],\"model\":\""
},
{
"path": "tests/cassettes/test_tools_streaming/test_tools_streaming_variant_b.yaml",
"chars": 9996,
"preview": "interactions:\n- request:\n body: '{\"messages\":[{\"role\":\"user\",\"content\":\"What is the current llm version?\"}],\"model\":\""
},
{
"path": "tests/cassettes/test_tools_streaming/test_tools_streaming_variant_c.yaml",
"chars": 9800,
"preview": "interactions:\n- request:\n body: '{\"messages\":[{\"role\":\"user\",\"content\":\"What is the current llm version?\"}],\"model\":\""
},
{
"path": "tests/conftest.py",
"chars": 13380,
"preview": "import pytest\nimport sqlite_utils\nimport json\nimport llm\nimport llm_echo\nfrom llm.plugins import pm\nfrom pydantic import"
},
{
"path": "tests/test-llm-load-plugins.sh",
"chars": 929,
"preview": "#!/bin/bash\n# This should only run in environments where both\n# llm-cluster and llm-mistral are installed\n\nPLUGINS=$(llm"
},
{
"path": "tests/test_aliases.py",
"chars": 4559,
"preview": "from click.testing import CliRunner\nfrom llm.cli import cli\nimport llm\nimport json\nimport pytest\nimport re\n\n\n@pytest.mar"
},
{
"path": "tests/test_async.py",
"chars": 1858,
"preview": "import llm\nimport pytest\n\n\n@pytest.mark.asyncio\nasync def test_async_model(async_mock_model):\n gathered = []\n asyn"
},
{
"path": "tests/test_attachments.py",
"chars": 3275,
"preview": "from click.testing import CliRunner\nimport os\nimport sys\nfrom unittest.mock import ANY\nimport llm\nfrom llm import cli\nim"
},
{
"path": "tests/test_chat.py",
"chars": 12526,
"preview": "from click.testing import CliRunner\nfrom unittest.mock import ANY\nimport json\nimport llm.cli\nimport pytest\nimport sqlite"
},
{
"path": "tests/test_chat_templates.py",
"chars": 3307,
"preview": "from click.testing import CliRunner\nimport sys\nimport llm.cli\nimport pytest\n\n\n@pytest.mark.xfail(sys.platform == \"win32\""
},
{
"path": "tests/test_cli_openai_models.py",
"chars": 7075,
"preview": "from click.testing import CliRunner\nfrom llm.cli import cli\nimport pytest\nimport sqlite_utils\n\n\n@pytest.fixture\ndef mock"
},
{
"path": "tests/test_cli_options.py",
"chars": 4250,
"preview": "from click.testing import CliRunner\nfrom llm.cli import cli\nimport pytest\nimport json\n\n\n@pytest.mark.parametrize(\n \"a"
},
{
"path": "tests/test_embed.py",
"chars": 6060,
"preview": "import json\nimport llm\nfrom llm.embeddings import Entry\nimport pytest\nimport sqlite_utils\nfrom unittest.mock import ANY\n"
},
{
"path": "tests/test_embed_cli.py",
"chars": 24606,
"preview": "from click.testing import CliRunner\nfrom llm.cli import cli\nfrom llm import Collection\nimport json\nimport pathlib\nimport"
},
{
"path": "tests/test_encode_decode.py",
"chars": 409,
"preview": "import llm\nimport pytest\nimport numpy as np\n\n\n@pytest.mark.parametrize(\n \"array\",\n (\n (0.0, 1.0, 1.5),\n "
},
{
"path": "tests/test_fragments_cli.py",
"chars": 5114,
"preview": "from click.testing import CliRunner\nfrom importlib.metadata import version\nfrom llm.cli import cli\nfrom unittest import "
},
{
"path": "tests/test_keys.py",
"chars": 4032,
"preview": "from click.testing import CliRunner\nimport json\nfrom llm.cli import cli\nimport pathlib\nimport pytest\nimport sys\n\n\n@pytes"
},
{
"path": "tests/test_llm.py",
"chars": 28143,
"preview": "from click.testing import CliRunner\nimport llm\nfrom llm.cli import cli\nfrom llm.models import Usage\nimport json\nimport o"
},
{
"path": "tests/test_llm_logs.py",
"chars": 30462,
"preview": "from click.testing import CliRunner\nfrom llm.cli import cli\nfrom llm.migrations import migrate\nfrom llm.utils import mon"
},
{
"path": "tests/test_migrate.py",
"chars": 5243,
"preview": "import llm\nfrom llm.migrations import migrate\nfrom llm.embeddings_migrations import embeddings_migrations\nimport pytest\n"
},
{
"path": "tests/test_plugins.py",
"chars": 35552,
"preview": "from click.testing import CliRunner\nimport click\nimport importlib\nimport json\nimport llm\nfrom llm.tools import llm_versi"
},
{
"path": "tests/test_templates.py",
"chars": 17845,
"preview": "from click.testing import CliRunner\nfrom importlib.metadata import version\nimport json\nfrom llm import Template, Toolbox"
},
{
"path": "tests/test_tools.py",
"chars": 16715,
"preview": "import asyncio\nfrom click.testing import CliRunner\nfrom importlib.metadata import version\nimport json\nimport llm\nfrom ll"
},
{
"path": "tests/test_tools_streaming.py",
"chars": 1356,
"preview": "import llm\nfrom llm.tools import llm_version\nimport os\nimport pytest\n\nAPI_KEY = os.environ.get(\"PYTEST_OPENAI_API_KEY\", "
},
{
"path": "tests/test_utils.py",
"chars": 17933,
"preview": "import json\nimport pytest\nfrom llm.utils import (\n extract_fenced_code_block,\n instantiate_from_spec,\n maybe_fe"
}
]
About this extraction
This page contains the full source code of the simonw/llm GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 96 files (1006.4 KB), approximately 255.5k tokens, and a symbol index with 652 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.