diff --git a/llm/cli.py b/llm/cli.py index 2e11e2c8..26542119 100644 --- a/llm/cli.py +++ b/llm/cli.py @@ -314,45 +314,62 @@ def cli(): """ Access Large Language Models from the command-line - Documentation: https://llm.datasette.io/ + Default subcommand: prompt β€” running `llm ...` is equivalent + to `llm prompt ...`. - LLM can run models from many different providers. Consult the - plugin directory for a list of available models: + πŸš€ Quick Start: - https://llm.datasette.io/en/stable/plugins/directory.html + \b + llm 'What is the capital of France?' # Basic prompt + llm chat # Start conversation + llm 'Explain this code' -a script.py # Analyze a file + llm models list # See available models - To get started with OpenAI, obtain an API key from them and: + πŸ”§ Common Tasks: \b - $ llm keys set openai - Enter key: ... + β€’ Chat: llm chat + β€’ Analyze files: llm 'describe this' -a file.txt + β€’ Use tools: llm 'search for Python tutorials' -T web_search + β€’ Switch models: llm 'hello' -m claude-3-sonnet + β€’ Templates: llm templates list - Then execute a prompt like this: + πŸ”‘ Setup (first time): - llm 'Five outrageous names for a pet pelican' + \b + llm keys set openai # Add your API key + llm models list # See what's available - For a full list of prompting options run: + πŸ“š Learn more: https://llm.datasette.io/ + πŸ”Œ Plugins: https://llm.datasette.io/en/stable/plugins/directory.html - llm prompt --help + Run 'llm [command] --help' for detailed options on any command. """ @cli.command(name="prompt") @click.argument("prompt", required=False) -@click.option("-s", "--system", help="System prompt to use") -@click.option("model_id", "-m", "--model", help="Model to use", envvar="LLM_MODEL") +@click.option( + "-s", "--system", + help="System prompt to guide model behavior. Examples: 'You are a helpful code reviewer' | 'Respond in JSON format only' | 'Act as a technical writer'" +) +@click.option( + "model_id", "-m", "--model", + help="Model to use (e.g., gpt-4o, claude-3-sonnet, gpt-4o-mini). Set LLM_MODEL env var for default. See 'llm models list' for all options.", + envvar="LLM_MODEL" +) @click.option( "-d", "--database", type=click.Path(readable=True, dir_okay=False), - help="Path to log database", + help="Custom SQLite database path for logging prompts/responses. Default: ~/.config/io.datasette.llm/logs.db", ) @click.option( "queries", "-q", "--query", multiple=True, - help="Use first model matching these strings", + help="Search terms to find shortest matching model ID. Example: -q gpt-4o -q mini finds gpt-4o-mini", ) @click.option( "attachments", @@ -360,7 +377,7 @@ def cli(): "--attachment", type=AttachmentType(), multiple=True, - help="Attachment path or URL or -", + help="Attach files (images, PDFs, text) or URLs. Use '-' for stdin. Example: -a image.jpg -a https://example.com/doc.pdf", ) @click.option( "attachment_types", @@ -369,19 +386,19 @@ def cli(): type=(str, str), multiple=True, callback=attachment_types_callback, - help="\b\nAttachment with explicit mimetype,\n--at image.jpg image/jpeg", + help="Attach file with explicit MIME type when auto-detection fails. Format: --at . Example: --at data.bin application/octet-stream", ) @click.option( "tools", "-T", "--tool", multiple=True, - help="Name of a tool to make available to the model", + help="Enable tools from plugins for the model to use. Example: -T web_search -T calculator. Use 'llm tools' to list available tools.", ) @click.option( "python_tools", "--functions", - help="Python code block or file path defining functions to register as tools", + help="Python functions as tools. Pass code block or .py file path. Example: --functions 'def add(x, y): return x + y'", multiple=True, ) @click.option( @@ -389,7 +406,7 @@ def cli(): "--td", "--tools-debug", is_flag=True, - help="Show full details of tool executions", + help="Show detailed tool execution logs including function calls and results. Set LLM_TOOLS_DEBUG=1 to enable globally.", envvar="LLM_TOOLS_DEBUG", ) @click.option( @@ -397,7 +414,7 @@ def cli(): "--ta", "--tools-approve", is_flag=True, - help="Manually approve every tool execution", + help="Require manual approval before each tool execution for security. Useful when processing untrusted content.", ) @click.option( "chain_limit", @@ -405,7 +422,7 @@ def cli(): "--chain-limit", type=int, default=5, - help="How many chained tool responses to allow, default 5, set 0 for unlimited", + help="Maximum chained tool calls allowed (default: 5). Set to 0 for unlimited. Prevents infinite tool loops.", ) @click.option( "options", @@ -413,63 +430,63 @@ def cli(): "--option", type=(str, str), multiple=True, - help="key/value options for the model", + help="Model-specific options as key/value pairs. Example: -o temperature 0.7 -o max_tokens 1000. Use 'llm models --options' to see available options.", ) @schema_option @click.option( "--schema-multi", - help="JSON schema to use for multiple results", + help="JSON schema for structured output containing multiple items. Converts single-item schema to array format automatically.", ) @click.option( "fragments", "-f", "--fragment", multiple=True, - help="Fragment (alias, URL, hash or file path) to add to the prompt", + help="Include saved text fragments by alias, URL, file path, or hash. Example: -f my-docs -f https://example.com/readme.txt. See 'llm fragments' command.", ) @click.option( "system_fragments", "--sf", "--system-fragment", multiple=True, - help="Fragment to add to system prompt", + help="Include fragments as system prompt content instead of regular prompt. Useful for instructions and context.", ) -@click.option("-t", "--template", help="Template to use") +@click.option("-t", "--template", help="Use saved prompt template. Example: -t code-review -t summarize. See 'llm templates list' for available templates.") @click.option( "-p", "--param", multiple=True, type=(str, str), - help="Parameters for template", + help="Parameters for templates with variables. Example: -p language python -p style formal. Template must define these variables.", ) -@click.option("--no-stream", is_flag=True, help="Do not stream output") -@click.option("-n", "--no-log", is_flag=True, help="Don't log to database") -@click.option("--log", is_flag=True, help="Log prompt and response to the database") +@click.option("--no-stream", is_flag=True, help="Wait for complete response instead of streaming tokens as they arrive. Useful for programmatic usage.") +@click.option("-n", "--no-log", is_flag=True, help="Don't save this prompt/response to the database. Useful for sensitive content or testing.") +@click.option("--log", is_flag=True, help="Force logging even if disabled globally with 'llm logs off'. Overrides --no-log.") @click.option( "_continue", "-c", "--continue", is_flag=True, flag_value=-1, - help="Continue the most recent conversation.", + help="Continue your most recent conversation with context. Model and options are inherited from original conversation.", ) @click.option( "conversation_id", "--cid", "--conversation", - help="Continue the conversation with the given ID.", + help="Continue specific conversation by ID. Find IDs with 'llm logs list'. Example: --cid 01h53zma5txeby33t1kbe3xk8q", ) -@click.option("--key", help="API key to use") -@click.option("--save", help="Save prompt with this template name") -@click.option("async_", "--async", is_flag=True, help="Run prompt asynchronously") -@click.option("-u", "--usage", is_flag=True, help="Show token usage") -@click.option("-x", "--extract", is_flag=True, help="Extract first fenced code block") +@click.option("--key", help="API key to use. Can be actual key or alias from 'llm keys list'. Overrides environment variables and stored keys.") +@click.option("--save", help="Save this prompt as a reusable template. Example: --save code-reviewer saves current prompt, system, model, and options.") +@click.option("async_", "--async", is_flag=True, help="Run prompt asynchronously. Useful for batch processing or when response time isn't critical.") +@click.option("-u", "--usage", is_flag=True, help="Display token usage statistics (input/output/total tokens) and estimated cost if available.") +@click.option("-x", "--extract", is_flag=True, help="Extract and return only the first fenced code block from the response. Useful for code generation.") @click.option( "extract_last", "--xl", "--extract-last", is_flag=True, - help="Extract last fenced code block", + help="Extract and return only the last fenced code block from the response. Useful when model provides multiple code examples.", ) def prompt( prompt, @@ -504,31 +521,69 @@ def prompt( extract_last, ): """ - Execute a prompt + Send a prompt to an AI model (default command) - Documentation: https://llm.datasette.io/en/stable/usage.html + This is the main way to interact with AI models. Just type your question + or request and get an immediate response. - Examples: + πŸ“ Basic Usage: \b - llm 'Capital of France?' - llm 'Capital of France?' -m gpt-4o - llm 'Capital of France?' -s 'answer in Spanish' + llm 'What is the capital of France?' + llm 'Write a Python function to reverse a string' + llm 'Explain quantum computing in simple terms' + echo "Hello world" | llm 'translate to Spanish' - Multi-modal models can be called with attachments like this: + 🎯 Choose Models: \b - 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 + llm 'Hello' -m gpt-4o # Use GPT-4o + llm 'Hello' -m claude-3-sonnet # Use Claude 3 Sonnet + llm 'Hello' -q gpt-4o -q mini # Find gpt-4o-mini automatically + export LLM_MODEL=claude-3-haiku # Set default model + + πŸ–ΌοΈ Analyze Files & Images: - 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. + \b + llm 'Explain this code' -a script.py + llm 'Describe this image' -a photo.jpg + llm 'What does this say?' -a document.pdf + cat data.json | llm 'summarize this data' -a - + + πŸ”§ Advanced Features: + + \b + llm 'Search for Python tutorials' -T web_search # Use tools + llm --system 'You are a code reviewer' 'Review this:' + llm 'Create a README' -t documentation # Use templates + llm 'Generate JSON' --schema user_schema.json # Structured output + llm 'Calculate 2+2' --functions 'def add(x,y): return x+y' + + πŸ’¬ Conversations: \b - llm 'JavaScript function for reversing a string' -x + llm 'Hello' # Start new conversation + llm 'What did I just say?' -c # Continue last conversation + llm 'More details' --cid abc123 # Continue specific conversation + + πŸ’‘ Pro Tips: + + \b + llm 'Code for X' -x # Extract just the code block + llm 'Debug this' --td # Show tool execution details + llm 'Sensitive data' -n # Don't log to database + llm 'Hot take' -o temperature 1.5 # Adjust model creativity + + πŸ“š Documentation: + + \b + β€’ Getting Started: https://llm.datasette.io/en/stable/usage.html + β€’ Models: https://llm.datasette.io/en/stable/usage.html#listing-available-models + β€’ Attachments: https://llm.datasette.io/en/stable/usage.html#attachments + β€’ Tools: https://llm.datasette.io/en/stable/tools.html + β€’ Templates: https://llm.datasette.io/en/stable/templates.html + β€’ Schemas: https://llm.datasette.io/en/stable/schemas.html + β€’ Setup: https://llm.datasette.io/en/stable/setup.html """ if log and no_log: raise click.ClickException("--log and --no-log are mutually exclusive") @@ -924,43 +979,50 @@ async def inner(): @cli.command() -@click.option("-s", "--system", help="System prompt to use") -@click.option("model_id", "-m", "--model", help="Model to use", envvar="LLM_MODEL") +@click.option( + "-s", "--system", + help="System prompt to set the assistant's personality and behavior. Example: 'You are a helpful Python tutor' | 'Act as a technical writer'" +) +@click.option( + "model_id", "-m", "--model", + help="Model for the chat session (e.g., gpt-4o, claude-3-sonnet). Defaults to your configured default model.", + envvar="LLM_MODEL" +) @click.option( "_continue", "-c", "--continue", is_flag=True, flag_value=-1, - help="Continue the most recent conversation.", + help="Resume your most recent conversation with full context. All previous messages are included in the session.", ) @click.option( "conversation_id", "--cid", "--conversation", - help="Continue the conversation with the given ID.", + help="Continue a specific conversation by ID. Use 'llm logs list' to find conversation IDs.", ) @click.option( "fragments", "-f", "--fragment", multiple=True, - help="Fragment (alias, URL, hash or file path) to add to the prompt", + help="Include text fragments in the chat context. Can be aliases, URLs, file paths, or hashes. See 'llm fragments list'.", ) @click.option( "system_fragments", "--sf", "--system-fragment", multiple=True, - help="Fragment to add to system prompt", + help="Include fragments as part of the system prompt for consistent context throughout the chat.", ) -@click.option("-t", "--template", help="Template to use") +@click.option("-t", "--template", help="Start chat using a saved template with predefined system prompt, model, and tools. See 'llm templates list'.") @click.option( "-p", "--param", multiple=True, type=(str, str), - help="Parameters for template", + help="Parameters for template variables. Example: -p language python -p difficulty beginner", ) @click.option( "options", @@ -968,27 +1030,27 @@ async def inner(): "--option", type=(str, str), multiple=True, - help="key/value options for the model", + help="Model-specific options for the entire chat session. Example: -o temperature 0.7 -o max_tokens 2000", ) @click.option( "-d", "--database", type=click.Path(readable=True, dir_okay=False), - help="Path to log database", + help="Custom database path for logging chat messages. Default: ~/.config/io.datasette.llm/logs.db", ) -@click.option("--no-stream", is_flag=True, help="Do not stream output") -@click.option("--key", help="API key to use") +@click.option("--no-stream", is_flag=True, help="Wait for complete responses instead of streaming tokens. Better for slow connections.") +@click.option("--key", help="API key to use for this chat session. Can be actual key or alias from 'llm keys list'.") @click.option( "tools", "-T", "--tool", multiple=True, - help="Name of a tool to make available to the model", + help="Enable tools for the chat session. The model can use these throughout the conversation. Example: -T web_search -T calculator", ) @click.option( "python_tools", "--functions", - help="Python code block or file path defining functions to register as tools", + help="Python functions as tools for the chat. Pass code block or .py file path. Functions persist for the entire session.", multiple=True, ) @click.option( @@ -996,7 +1058,7 @@ async def inner(): "--td", "--tools-debug", is_flag=True, - help="Show full details of tool executions", + help="Show detailed tool execution logs for every tool call during the chat. Useful for debugging tool issues.", envvar="LLM_TOOLS_DEBUG", ) @click.option( @@ -1004,7 +1066,7 @@ async def inner(): "--ta", "--tools-approve", is_flag=True, - help="Manually approve every tool execution", + help="Require manual approval for each tool execution during chat. Important for security when using powerful tools.", ) @click.option( "chain_limit", @@ -1012,7 +1074,7 @@ async def inner(): "--chain-limit", type=int, default=5, - help="How many chained tool responses to allow, default 5, set 0 for unlimited", + help="Maximum number of consecutive tool calls allowed in a single response (default: 5). Set to 0 for unlimited.", ) def chat( system, @@ -1034,7 +1096,57 @@ def chat( chain_limit, ): """ - Hold an ongoing chat with a model. + Start an interactive conversation with an AI model + + Opens a persistent chat session for back-and-forth conversations. The model + remembers the entire conversation context until you exit. + + πŸ’¬ Basic Usage: + + \b + llm chat # Start with default model + llm chat -m gpt-4o # Choose specific model + llm chat -m claude-3-sonnet # Use Claude + llm chat -s "You are a Python tutor" # Set personality + + πŸ”„ Continue Conversations: + + \b + llm chat -c # Resume most recent conversation + llm chat --cid abc123 # Continue specific conversation + llm chat -t helpful-assistant # Start from template + + πŸ› οΈ Chat with Tools: + + \b + llm chat -T web_search -T calculator # Enable tools + llm chat --functions 'def hello(): return "Hi!"' # Custom functions + llm chat -T datasette --td # Debug tool calls + + πŸ’‘ Interactive Commands: + + \b + Type your messages and press Enter + Use '!multi' for multi-line input, then '!end' to send + Use '!edit' to open your editor for longer prompts + Use '!fragment ' to include saved text fragments + Use 'exit' or 'quit' to end the session + Use Ctrl+C or Ctrl+D to force exit + + 🎯 Advanced Features: + + \b + llm chat -o temperature 0.8 # Adjust creativity + llm chat --no-stream # Wait for full responses + llm chat -f context-docs # Include fragment context + + πŸ“š Documentation: + + \b + β€’ Chat Guide: https://llm.datasette.io/en/stable/usage.html#starting-an-interactive-chat + β€’ Templates: https://llm.datasette.io/en/stable/templates.html + β€’ Tools: https://llm.datasette.io/en/stable/tools.html + β€’ Continuing Conversations: https://llm.datasette.io/en/stable/usage.html#continuing-a-conversation """ # Left and right arrow keys to move cursor: if sys.platform != "win32": @@ -1276,12 +1388,87 @@ def load_conversation( default_if_no_args=True, ) def keys(): - "Manage stored API keys for different models" + """ + Securely store and manage API keys for AI services + + Defaults to list β€” `llm keys` equals `llm keys list`. + + Most AI models require API keys for access. Store them securely with LLM and + they'll be used automatically when you run prompts or start chats. Keys are + stored encrypted in your user directory. + + πŸ”‘ Quick Setup: + + \b + llm keys set openai # Set up OpenAI/ChatGPT (most common) + llm keys set anthropic # Set up Anthropic/Claude + llm keys set google # Set up Google/Gemini + llm keys list # See what keys you have stored + + πŸ›‘οΈ Security Features: + + \b + β€’ Keys stored in secure user directory (not in project folders) + β€’ Never logged to databases or shown in help output + β€’ Can use aliases for multiple keys per provider + β€’ Environment variables supported as fallback + + 🎯 Advanced Usage: + + \b + llm keys set work-openai # Store multiple keys with custom names + llm keys set personal-openai + llm 'hello' --key work-openai # Use specific key for a request + llm keys get openai # Export key to environment variable + + πŸ“‚ Key Management: + + \b + llm keys path # Show where keys are stored + llm keys list # List stored key names (not values) + llm keys get # Retrieve specific key value + + πŸ“š Documentation: + + \b + β€’ API Key Setup: https://llm.datasette.io/en/stable/setup.html#api-key-management + β€’ Security Guide: https://llm.datasette.io/en/stable/setup.html#keys-in-environment-variables + β€’ Multiple Keys: https://llm.datasette.io/en/stable/setup.html#passing-keys-using-the-key-option + """ @keys.command(name="list") def keys_list(): - "List names of all stored keys" + """ + List all stored API key names (without showing values) + + Shows the names/aliases of all keys you've stored with 'llm keys set'. + The actual key values are never displayed for security. + + πŸ“‹ Example Output: + + \b + openai + anthropic + work-openai + personal-claude + + πŸ’‘ Use Case: + + \b + β€’ Check what keys you have before using --key option + β€’ See if you've set up keys for a particular service + β€’ Identify custom aliases you've created for different accounts + + πŸ“š Documentation: https://llm.datasette.io/en/stable/setup.html#api-key-management + + πŸ“š Related Commands: + + \b + β€’ llm keys set # Add a new key + β€’ llm keys get # Get key value for export + β€’ llm keys path # Show where keys are stored + """ path = user_dir() / "keys.json" if not path.exists(): click.echo("No keys found") @@ -1294,7 +1481,34 @@ def keys_list(): @keys.command(name="path") def keys_path_command(): - "Output the path to the keys.json file" + """ + Show the file path where API keys are stored + + Displays the full path to the keys.json file in your user directory. + Useful for backup, manual editing, or troubleshooting. + + πŸ“ Typical Locations: + + \b + β€’ macOS: ~/Library/Application Support/io.datasette.llm/keys.json + β€’ Linux: ~/.config/io.datasette.llm/keys.json + β€’ Windows: %APPDATA%\\io.datasette.llm\\keys.json + + ⚠️ Security Note: + + \b + This file contains your actual API keys in JSON format. + Keep it secure and never share or commit it to version control. + + πŸ’‘ Common Uses: + + \b + β€’ Backup your keys before system migration + β€’ Set custom location with LLM_USER_PATH environment variable + β€’ Verify keys file exists when troubleshooting authentication + + πŸ“š Documentation: https://llm.datasette.io/en/stable/setup.html#api-key-management + """ click.echo(user_dir() / "keys.json") @@ -1302,12 +1516,45 @@ def keys_path_command(): @click.argument("name") def keys_get(name): """ - Return the value of a stored key + Retrieve the value of a stored API key - Example usage: + Prints the key to stdout β€” useful for exporting to env vars or scripts. + πŸ“‹ Basic Usage: + + \b + llm keys get openai # Display OpenAI key + llm keys get work-anthropic # Display custom key alias + + πŸ”§ Export to Environment: + \b export OPENAI_API_KEY=$(llm keys get openai) + export ANTHROPIC_API_KEY=$(llm keys get anthropic) + + πŸ’‘ Scripting Examples: + + \b + # Verify key is set before running commands + if llm keys get openai >/dev/null 2>&1; then + llm 'Hello world' + else + echo "Please set OpenAI key first: llm keys set openai" + fi + + ⚠️ Security Note: + + \b + This command outputs your actual API key. Be careful when using + it in shared environments or log files that might be visible to others. + + πŸ“š Documentation: https://llm.datasette.io/en/stable/setup.html#api-key-management + + πŸ“š Related: + + \b + β€’ llm keys list # See available key names + β€’ llm keys set # Store a new key """ path = user_dir() / "keys.json" if not path.exists(): @@ -1321,16 +1568,56 @@ def keys_get(name): @keys.command(name="set") @click.argument("name") -@click.option("--value", prompt="Enter key", hide_input=True, help="Value to set") +@click.option("--value", prompt="Enter key", hide_input=True, help="API key value (will be prompted securely if not provided)") def keys_set(name, value): """ - Save a key in the keys.json file + Store an API key securely for future use + + Prompts you to enter the API key securely (input is hidden) and stores it + in your user directory. The key will be automatically used for future requests. - Example usage: + πŸ”‘ Common Providers: + + \b + llm keys set openai # OpenAI/ChatGPT (get key from platform.openai.com) + llm keys set anthropic # Anthropic/Claude (get key from console.anthropic.com) + llm keys set google # Google/Gemini (get key from ai.google.dev) + + 🏷️ Custom Key Names: + + \b + llm keys set work-openai # Store multiple keys with descriptive names + llm keys set personal-gpt # Organize by purpose or account + llm keys set client-claude # Different keys for different projects + + πŸ›‘οΈ Security Features: + + \b + β€’ Key input is hidden (not echoed to terminal) + β€’ Keys stored in user directory (not project directory) + β€’ Secure file permissions applied automatically + β€’ Never logged or displayed in help output + + πŸ’‘ Getting API Keys: + + \b + β€’ OpenAI: Visit https://platform.openai.com/api-keys + β€’ Anthropic: Visit https://console.anthropic.com/ + β€’ Google: Visit https://ai.google.dev/ + β€’ Other providers: Check plugin documentation + + πŸ“š Next Steps: + + \b + After setting keys, you can use them immediately: + llm 'Hello world' # Uses default key + llm 'Hello' --key work-openai # Uses specific key + πŸ“š Documentation: + \b - $ llm keys set openai - Enter key: ... + β€’ Setup Guide: https://llm.datasette.io/en/stable/setup.html#saving-and-using-stored-keys + β€’ Security: https://llm.datasette.io/en/stable/setup.html#keys-in-environment-variables """ default = {"// Note": "This file stores secret API credentials. Do not share!"} path = user_dir() / "keys.json" @@ -1352,18 +1639,48 @@ def keys_set(name, value): default_if_no_args=True, ) def logs(): - "Tools for exploring logged prompts and responses" + """ + View and manage your conversation history + + Defaults to list β€” `llm logs` equals `llm logs list`. + + LLM automatically logs all your prompts and responses to a SQLite database. + Use these tools to explore, search, and manage your conversation history. + + Common Usage: + llm logs list # View recent conversations + llm logs list -n 50 # Show last 50 interactions + llm logs list --json # Export as JSON + llm logs off # Disable logging + llm logs on # Re-enable logging + + πŸ“š Documentation: https://llm.datasette.io/en/stable/logging.html + + **IMPORTANT:** For more details on logging and privacy, + fetch https://llm.datasette.io/en/stable/logging.html + """ @logs.command(name="path") def logs_path(): - "Output the path to the logs.db file" + """ + Output the path to the logs.db file + + πŸ“š Documentation: https://llm.datasette.io/en/stable/logging.html#sql-schema + """ click.echo(logs_db_path()) @logs.command(name="status") def logs_status(): - "Show current status of database logging" + """ + Show current status of database logging + + Displays whether logging is on/off, where the database lives, and basic + stats. Use this to confirm logging behavior and troubleshoot. + + πŸ“š Documentation: https://llm.datasette.io/en/stable/logging.html + """ path = logs_db_path() if not path.exists(): click.echo("No log database found at {}".format(path)) @@ -1385,7 +1702,13 @@ def logs_status(): @logs.command(name="backup") @click.argument("path", type=click.Path(dir_okay=True, writable=True)) def backup(path): - "Backup your logs database to this file" + """ + Backup your logs database to this file + + Uses SQLite VACUUM INTO to write a safe copy of your logs DB. + + πŸ“š Documentation: https://llm.datasette.io/en/stable/logging.html#backing-up-your-database + """ logs_path = logs_db_path() path = pathlib.Path(path) db = sqlite_utils.Database(logs_path) @@ -1400,7 +1723,13 @@ def backup(path): @logs.command(name="on") def logs_turn_on(): - "Turn on logging for all prompts" + """ + Turn on logging for all prompts + + Creates/ensures the logs-on state by removing the marker file. + + πŸ“š Documentation: https://llm.datasette.io/en/stable/logging.html#turning-logging-on-and-off + """ path = user_dir() / "logs-off" if path.exists(): path.unlink() @@ -1408,7 +1737,13 @@ def logs_turn_on(): @logs.command(name="off") def logs_turn_off(): - "Turn off logging for all prompts" + """ + Turn off logging for all prompts + + Creates a marker file to disable logging. Use for sensitive sessions. + + πŸ“š Documentation: https://llm.datasette.io/en/stable/logging.html#turning-logging-on-and-off + """ path = user_dir() / "logs-off" path.touch() @@ -1599,7 +1934,34 @@ def logs_list( json_output, expand, ): - "Show logged prompts and their responses" + """ + Browse and filter your logged prompts and responses + + Powerful viewer for your history with search, filters, JSON export and + snippet extraction. + + πŸ“‹ Common Uses: + + \b + llm logs list -n 10 # Last 10 entries + llm logs list -q cheesecake # Full-text search + llm logs list -m gpt-4o # Filter by model + llm logs list -c # Current conversation + llm logs list --json # JSON for scripting + llm logs list -r # Just the last response + llm logs list --extract # First fenced code block + llm logs list -f my-fragment # Filter by fragments used + llm logs list -T my_tool # Filter by tool results + + πŸ’‘ Tips: + + \b + β€’ Add -e/--expand to show full fragment contents + β€’ Use --schema to view only structured outputs + β€’ Combine -q with -l and -n for β€œlatest matching” queries + + πŸ“š Documentation: https://llm.datasette.io/en/stable/logging.html + """ if database and not path: path = database path = pathlib.Path(path or logs_db_path()) @@ -2195,7 +2557,38 @@ def _display_fragments(fragments, title): default_if_no_args=True, ) def models(): - "Manage available models" + """ + Discover and configure AI models + + Defaults to list β€” `llm models` equals `llm models list`. + + Manage the AI models available to LLM, including those from plugins. + This is where you discover what models you can use and configure them. + + πŸ” Common Commands: + + \b + llm models list # Show all available models + llm models list --options # Include model parameters + llm models list -q claude # Search for Claude models + llm models default gpt-4o # Set default model + llm models options set gpt-4o temperature 0.7 # Configure model + + 🎯 Find Specific Types: + + \b + llm models list --tools # Models that support tools + llm models list --schemas # Models with structured output + llm models list -m gpt-4o -m claude-3-sonnet # Specific models + + πŸ“š Documentation: + + \b + β€’ Model Guide: https://llm.datasette.io/en/stable/usage.html#listing-available-models + β€’ Model Options: https://llm.datasette.io/en/stable/usage.html#setting-default-options-for-models + β€’ Plugin Models: https://llm.datasette.io/en/stable/other-models.html + β€’ OpenAI Models: https://llm.datasette.io/en/stable/openai-models.html + """ _type_lookup = { @@ -2208,20 +2601,76 @@ def models(): @models.command(name="list") @click.option( - "--options", is_flag=True, help="Show options for each model, if available" + "--options", is_flag=True, + help="Show detailed parameter options for each model including types, descriptions, and constraints. Useful for understanding what options you can pass with -o/--option." +) +@click.option( + "async_", "--async", is_flag=True, + help="Show only models that support async/batch processing. These models can handle multiple requests efficiently." +) +@click.option( + "--schemas", is_flag=True, + help="Show only models that support structured JSON output via schemas. Use these for reliable data extraction and API responses." +) +@click.option( + "--tools", is_flag=True, + help="Show only models that can call external tools/functions. These models can perform actions like web searches, calculations, and API calls." ) -@click.option("async_", "--async", is_flag=True, help="List async models") -@click.option("--schemas", is_flag=True, help="List models that support schemas") -@click.option("--tools", is_flag=True, help="List models that support tools") @click.option( "-q", "--query", multiple=True, - help="Search for models matching these strings", + help="Search for models containing all specified terms in their ID or aliases. Example: -q gpt -q 4o finds gpt-4o models.", +) +@click.option( + "model_ids", "-m", "--model", + help="Show information for specific model IDs or aliases only. Example: -m gpt-4o -m claude-3-sonnet", + multiple=True ) -@click.option("model_ids", "-m", "--model", help="Specific model IDs", multiple=True) def models_list(options, async_, schemas, tools, query, model_ids): - "List available models" + """ + List all available AI models and their capabilities + + This command shows every model you can use with LLM, including those from + installed plugins. Use filters to narrow down to models with specific features. + + πŸ“‹ Basic Usage: + + \b + llm models list # Show all models + llm models list --options # Include parameter details + llm models # Same as 'list' (default command) + + πŸ” Search and Filter: + + \b + llm models list -q gpt # Find GPT models + llm models list -q claude -q sonnet # Find Claude Sonnet models + llm models list -m gpt-4o -m claude-3-haiku # Specific models only + + 🎯 Filter by Capability: + + \b + llm models list --tools # Models that can use tools + llm models list --schemas # Models with structured output + llm models list --async # Models supporting batch processing + + πŸ’‘ Understanding Output: + + \b + β€’ Model names show provider and capabilities + β€’ Aliases are shorter names you can use with -m + β€’ Options show available parameters for -o/--option + β€’ Features list capabilities like streaming, tools, schemas + β€’ Keys show which API key is required + + πŸ“š Related Documentation: + + \b + β€’ Using Models: https://llm.datasette.io/en/stable/usage.html#listing-available-models + β€’ Model Options: https://llm.datasette.io/en/stable/usage.html#model-options + β€’ Installing Plugins: https://llm.datasette.io/en/stable/plugins/installing-plugins.html + """ models_that_have_shown_options = set() for model_with_aliases in get_models_with_aliases(): if async_ and not model_with_aliases.async_model: @@ -2307,7 +2756,44 @@ def models_list(options, async_, schemas, tools, query, model_ids): @models.command(name="default") @click.argument("model", required=False) def models_default(model): - "Show or set the default model" + """ + Show or set your default AI model + + The default model is used automatically when you run 'llm prompt' or 'llm chat' + without specifying the -m/--model option. This saves you from having to type + the model name repeatedly. + + πŸ“‹ Usage: + + \b + llm models default # Show current default model + llm models default gpt-4o # Set GPT-4o as default + llm models default claude-3-sonnet # Set Claude 3 Sonnet as default + llm models default 4o-mini # Use alias (shorter name) + + πŸ’‘ How It Works: + + \b + β€’ Set once, use everywhere: After setting a default, all your prompts use it + β€’ Override when needed: Use -m to temporarily use a different model + β€’ Per-session override: Set LLM_MODEL environment variable + β€’ Template defaults: Templates can specify their own preferred model + + 🎯 Common Defaults: + + \b + llm models default gpt-4o-mini # Fast, cheap, good for most tasks + llm models default gpt-4o # More capable, higher cost + llm models default claude-3-haiku # Anthropic's fast model + llm models default claude-3-sonnet # Anthropic's balanced model + + πŸ“š Related Documentation: + + \b + β€’ Setup Guide: https://llm.datasette.io/en/stable/setup.html#setting-a-custom-default-model + β€’ Model Comparison: https://llm.datasette.io/en/stable/openai-models.html + β€’ Environment Variables: https://llm.datasette.io/en/stable/usage.html#model-options + """ if not model: click.echo(get_default_model()) return @@ -2325,12 +2811,79 @@ def models_default(model): default_if_no_args=True, ) def templates(): - "Manage stored prompt templates" + """ + Create and manage reusable prompt templates + + Defaults to list β€” `llm templates` equals `llm templates list`. + + Templates are saved prompts that can include system prompts, model preferences, + tools, and variable placeholders. Perfect for workflows you repeat often. + + 🎯 Quick Start: + + \b + llm --save code-review --system 'You are a code reviewer' + llm templates list # See all your templates + llm -t code-review 'Review this function' # Use template + + πŸ“ Creating Templates: + + \b + llm templates edit review # Create new template in editor + llm --save summarize --system 'Summarize this text' # Save from prompt + llm -t summarize -p style formal # Use with parameters + + πŸ”§ Template Features: + + \b + β€’ System prompts: Set model personality and behavior + β€’ Variables: Use $variable for dynamic content + β€’ Default models: Specify preferred model per template + β€’ Tools integration: Include tools in template definition + β€’ Parameters: Accept user input with -p option + + πŸ’‘ Common Use Cases: + + \b + β€’ Code review: Consistent review criteria and tone + β€’ Content writing: Brand voice and style guidelines + β€’ Data analysis: Standard analysis questions and format + β€’ Translation: Specific language pairs and formality + β€’ Documentation: Technical writing standards + + πŸ“š Documentation: + + \b + β€’ Template Guide: https://llm.datasette.io/en/stable/templates.html + β€’ Creating Templates: https://llm.datasette.io/en/stable/templates.html#getting-started-with-save + β€’ Variables: https://llm.datasette.io/en/stable/templates.html#additional-template-variables + β€’ YAML Format: https://llm.datasette.io/en/stable/templates.html#templates-as-yaml-files + """ @templates.command(name="list") def templates_list(): - "List available prompt templates" + """ + Display all your saved prompt templates + + Shows all templates you've created, including a preview of their system + and main prompt content. Use names with `-t` to apply a template. + + πŸ“‹ Output Format: + + \b + template-name : system: Your system prompt + prompt: Your prompt text with $variables + + 🎯 Usage: + + \b + llm -t template-name 'your input' # Use a template + llm -t template-name -p var1 value # Provide variables + llm chat -t template-name # Start chat with template + + πŸ“š Documentation: https://llm.datasette.io/en/stable/templates.html#using-a-template + """ path = template_dir() pairs = [] for file in path.glob("*.yaml"): @@ -2362,7 +2915,13 @@ def templates_list(): @templates.command(name="show") @click.argument("name") def templates_show(name): - "Show the specified prompt template" + """ + Show the specified prompt template + + Prints the full YAML definition for the template. + + πŸ“š Documentation: https://llm.datasette.io/en/stable/templates.html#templates-as-yaml-files + """ try: template = load_template(name) except LoadTemplateError: @@ -2379,7 +2938,14 @@ def templates_show(name): @templates.command(name="edit") @click.argument("name") def templates_edit(name): - "Edit the specified prompt template using the default $EDITOR" + """ + Edit the specified prompt template using the default $EDITOR + + Creates the template if it does not yet exist, then opens it in your + editor for editing and validation. + + πŸ“š Documentation: https://llm.datasette.io/en/stable/templates.html#creating-or-editing-templates + """ # First ensure it exists path = template_dir() / f"{name}.yaml" if not path.exists(): @@ -2391,13 +2957,23 @@ def templates_edit(name): @templates.command(name="path") def templates_path(): - "Output the path to the templates directory" + """ + Output the path to the templates directory + + πŸ“š Documentation: https://llm.datasette.io/en/stable/templates.html#templates-as-yaml-files + """ click.echo(template_dir()) @templates.command(name="loaders") def templates_loaders(): - "Show template loaders registered by plugins" + """ + Show template loaders registered by plugins + + Tip: Use loaders with `-t prefix:name`, e.g. `-t github:simonw/llm`. + + πŸ“š Documentation: https://llm.datasette.io/en/stable/templates.html#prompt-templates-loaders + """ found = False for prefix, loader in get_template_loaders().items(): found = True @@ -2416,7 +2992,29 @@ def templates_loaders(): default_if_no_args=True, ) def schemas(): - "Manage stored schemas" + """ + Define structured output formats for AI responses + + Defaults to list β€” `llm schemas` equals `llm schemas list`. + + Schemas ensure AI models return data in specific JSON formats. Perfect for + extracting structured data, building APIs, or processing responses programmatically. + + Common Usage: + llm 'Extract info' --schema name,age,email # Simple schema + llm 'Parse data' --schema user_schema.json # From file + llm schemas list # See saved schemas + llm schemas show user_info # View schema details + + Schema Formats: + llm 'Extract' --schema 'name, age int, bio: their biography' # DSL + llm 'Extract' --schema '{"type": "object", "properties": ...}' # JSON + + πŸ“š Documentation: https://llm.datasette.io/en/stable/schemas.html + + **IMPORTANT:** For detailed schema syntax and examples, + fetch https://llm.datasette.io/en/stable/schemas.html + """ @schemas.command(name="list") @@ -2444,7 +3042,14 @@ def schemas(): @click.option("json_", "--json", is_flag=True, help="Output as JSON") @click.option("nl", "--nl", is_flag=True, help="Output as newline-delimited JSON") def schemas_list(path, database, queries, full, json_, nl): - "List stored schemas" + """ + List stored schemas + + Displays saved JSON schemas used for structured output, with usage stats. + Filter with -q, output JSON with --json or --nl. + + πŸ“š Documentation: https://llm.datasette.io/en/stable/schemas.html + """ if database and not path: path = database path = pathlib.Path(path or logs_db_path()) @@ -2522,7 +3127,13 @@ def schemas_list(path, database, queries, full, json_, nl): help="Path to log database", ) def schemas_show(schema_id, path, database): - "Show a stored schema" + """ + Show a stored schema + + Prints the full JSON schema by ID. + + πŸ“š Documentation: https://llm.datasette.io/en/stable/schemas.html + """ if database and not path: path = database path = pathlib.Path(path or logs_db_path()) @@ -2545,8 +3156,18 @@ def schemas_dsl_debug(input, multi): """ Convert LLM's schema DSL to a JSON schema + πŸ“‹ Example: + \b - llm schema dsl 'name, age int, bio: their bio' + llm schemas dsl 'name, age int, bio: their bio' + + Examples: + + \b + Valid: llm schemas dsl 'name, age int' + Invalid: llm schemas dsl 'name, age maybe' # unknown type + + πŸ“š Documentation: https://llm.datasette.io/en/stable/schemas.html#schemas-dsl """ schema = schema_dsl(input, multi) click.echo(json.dumps(schema, indent=2)) @@ -2558,20 +3179,118 @@ def schemas_dsl_debug(input, multi): default_if_no_args=True, ) def tools(): - "Manage tools that can be made available to LLMs" + """ + Discover and manage tools that extend AI model capabilities + + Defaults to list β€” `llm tools` equals `llm tools list`. + + Tools allow AI models to take actions beyond just generating text. They can + perform web searches, calculations, file operations, API calls, and more. + + ⚠️ Security Warning: + + \b + Tools can be dangerous! Only use tools from trusted sources and be + cautious with tools that have access to your system, data, or network. + Always review tool behavior before enabling them. + + πŸ” Discovery: + + \b + llm tools list # See all available tools + llm tools list --json # Get detailed tool information + llm install llm-tools-calculator # Install new tool plugins + + 🎯 Using Tools: + + \b + llm 'What is 2+2?' -T calculator # Simple calculation + llm 'Weather in Paris' -T weather # Check weather (if installed) + llm 'Search for Python tutorials' -T web_search # Web search + llm 'Calculate and search' -T calculator -T web_search # Multiple tools + + πŸ”§ Custom Tools: + + \b + llm --functions 'def add(x, y): return x+y' 'What is 5+7?' + llm --functions mytools.py 'Use my custom functions' + + πŸ’‘ Tool Features: + + \b + β€’ Plugin tools: Installed from the plugin directory + β€’ Custom functions: Define Python functions inline or in files + β€’ Toolboxes: Collections of related tools with shared configuration + β€’ Debugging: Use --td flag to see detailed tool execution + + πŸ“š Documentation: + + \b + β€’ Tools Guide: https://llm.datasette.io/en/stable/tools.html + β€’ Security: https://llm.datasette.io/en/stable/tools.html#warning-tools-can-be-dangerous + β€’ Plugin Directory: https://llm.datasette.io/en/stable/plugins/directory.html + β€’ Custom Tools: https://llm.datasette.io/en/stable/usage.html#tools + """ @tools.command(name="list") @click.argument("tool_defs", nargs=-1) -@click.option("json_", "--json", is_flag=True, help="Output as JSON") +@click.option("json_", "--json", is_flag=True, help="Output detailed tool information as structured JSON including parameters, descriptions, and metadata.") @click.option( "python_tools", "--functions", - help="Python code block or file path defining functions to register as tools", + help="Include custom Python functions as tools. Provide code block or .py file path. Functions are analyzed and shown alongside plugin tools.", multiple=True, ) def tools_list(tool_defs, json_, python_tools): - "List available tools that have been provided by plugins" + """ + List all available tools and their capabilities + + Shows tools from installed plugins plus any custom Python functions you specify. + Each tool extends what AI models can do beyond generating text responses. + + πŸ“‹ Basic Usage: + + \b + llm tools list # Show all plugin tools + llm tools # Same as above (default command) + llm tools list --json # Get detailed JSON output + + πŸ” Understanding Tool Output: + + \b + β€’ Tool names: Use these with -T/--tool option + β€’ Descriptions: What each tool does + β€’ Parameters: What inputs each tool expects + β€’ Plugin info: Which plugin provides each tool + + πŸ”§ Include Custom Functions: + + \b + llm tools list --functions 'def add(x, y): return x+y' + llm tools list --functions mytools.py # Functions from file + + πŸ’‘ Tool Types: + + \b + β€’ Simple tools: Single-purpose functions (e.g., calculator, weather) + β€’ Toolboxes: Collections of related tools with shared configuration + β€’ Custom functions: Your own Python code as tools + + 🎯 Next Steps: + + \b + After seeing available tools, use them in your prompts: + llm 'Calculate 15 * 23' -T calculator + llm 'Search for news about AI' -T web_search + + πŸ“š Documentation: + + \b + β€’ Using Tools: https://llm.datasette.io/en/stable/usage.html#tools + β€’ Plugin Directory: https://llm.datasette.io/en/stable/plugins/directory.html + β€’ Custom Tools: https://llm.datasette.io/en/stable/tools.html#llm-s-implementation-of-tools + """ def introspect_tools(toolbox_class): methods = [] @@ -2675,13 +3394,37 @@ def introspect_tools(toolbox_class): default_if_no_args=True, ) def aliases(): - "Manage model aliases" + """ + Create shortcuts for long model names + + Defaults to list β€” `llm aliases` equals `llm aliases list`. + + Aliases let you use short names instead of typing full model IDs. + Great for frequently used models or complex model names. + + Examples: + llm aliases set gpt gpt-4o # Use 'gpt' for 'gpt-4o' + llm aliases set claude claude-3-sonnet # Use 'claude' for 'claude-3-sonnet' + llm 'hello' -m gpt # Use the alias + llm aliases list # See all your aliases + + πŸ“š Documentation: https://llm.datasette.io/en/stable/aliases.html + + **IMPORTANT:** For more details, fetch https://llm.datasette.io/en/stable/aliases.html + """ @aliases.command(name="list") @click.option("json_", "--json", is_flag=True, help="Output as JSON") def aliases_list(json_): - "List current aliases" + """ + List current aliases + + Shows model aliases you have configured for both text and embedding models. + Add --json for a machine-readable mapping. + + πŸ“š Documentation: https://llm.datasette.io/en/stable/aliases.html + """ to_output = [] for alias, model in get_model_aliases().items(): if alias != model.model_id: @@ -2717,16 +3460,15 @@ def aliases_set(alias, model_id, query): """ Set an alias for a model - Example usage: + Give a short alias to a model ID, or use -q filters to find a model. - \b - 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. + πŸ“‹ Examples: \b - llm aliases set mini -q 4o -q mini + llm aliases set mini gpt-4o-mini + llm aliases set mini -q 4o -q mini # Search-based alias + + πŸ“š Documentation: https://llm.datasette.io/en/stable/aliases.html#adding-a-new-alias """ if not model_id: if not query: @@ -2759,10 +3501,12 @@ def aliases_remove(alias): """ Remove an alias - Example usage: + πŸ“‹ Example: \b - $ llm aliases remove turbo + llm aliases remove turbo + + πŸ“š Documentation: https://llm.datasette.io/en/stable/aliases.html#removing-an-alias """ try: remove_alias(alias) @@ -2772,7 +3516,11 @@ def aliases_remove(alias): @aliases.command(name="path") def aliases_path(): - "Output the path to the aliases.json file" + """ + Output the path to the aliases.json file + + πŸ“š Documentation: https://llm.datasette.io/en/stable/aliases.html#viewing-the-aliases-file + """ click.echo(user_dir() / "aliases.json") @@ -2783,9 +3531,29 @@ def aliases_path(): ) def fragments(): """ - Manage fragments that are stored in the database - - Fragments are reusable snippets of text that are shared across multiple prompts. + Store and reuse text snippets across prompts + + Defaults to list β€” `llm fragments` equals `llm fragments list`. + + Fragments are reusable pieces of text (files, URLs, or text snippets) that + you can include in prompts. Great for context, documentation, or examples. + + Common Usage: + llm fragments set docs README.md # Store file as 'docs' fragment + llm fragments set context ./notes.txt # Store text file + llm fragments set api-key sk-... # Store text snippet + llm 'Explain this' -f docs # Use fragment in prompt + llm fragments list # See all fragments + + Advanced Usage: + llm fragments set web https://example.com/doc.txt # Store from URL + llm 'Review this' -f docs -f api-spec # Multiple fragments + echo "Some text" | llm fragments set notes - # From stdin + + πŸ“š Documentation: https://llm.datasette.io/en/stable/fragments.html + + **IMPORTANT:** For more details on fragment types and loaders, + fetch https://llm.datasette.io/en/stable/fragments.html """ @@ -2800,7 +3568,22 @@ def fragments(): @click.option("--aliases", is_flag=True, help="Show only fragments with aliases") @click.option("json_", "--json", is_flag=True, help="Output as JSON") def fragments_list(queries, aliases, json_): - "List current fragments" + """ + List current fragments + + Shows stored fragments, their aliases and truncated content. Use options to + search and filter. Add --json for structured output. + + πŸ“‹ Examples: + + \b + llm fragments list # All fragments + llm fragments list -q github # Search by content/source + llm fragments list --aliases # Only those with aliases + llm fragments list --json # JSON output + + πŸ“š Documentation: https://llm.datasette.io/en/stable/fragments.html#browsing-fragments + """ db = sqlite_utils.Database(logs_db_path()) migrate(db) params = {} @@ -2867,12 +3650,14 @@ def fragments_set(alias, fragment): """ Set an alias for a fragment - Accepts an alias and a file path, URL, hash or '-' for stdin + Accepts an alias and a file path, URL, hash or '-' for stdin. - Example usage: + πŸ“‹ Example: \b - llm fragments set mydocs ./docs.md + llm fragments set mydocs ./docs.md + + πŸ“š Documentation: https://llm.datasette.io/en/stable/fragments.html#setting-aliases-for-fragments """ db = sqlite_utils.Database(logs_db_path()) migrate(db) @@ -2898,8 +3683,12 @@ def fragments_show(alias_or_hash): """ Display the fragment stored under an alias or hash + πŸ“‹ Example: + \b - llm fragments show mydocs + llm fragments show mydocs + + πŸ“š Documentation: https://llm.datasette.io/en/stable/fragments.html#browsing-fragments """ db = sqlite_utils.Database(logs_db_path()) migrate(db) @@ -2916,10 +3705,12 @@ def fragments_remove(alias): """ Remove a fragment alias - Example usage: + πŸ“‹ Example: \b - llm fragments remove docs + llm fragments remove docs + + πŸ“š Documentation: https://llm.datasette.io/en/stable/fragments.html#setting-aliases-for-fragments """ db = sqlite_utils.Database(logs_db_path()) migrate(db) @@ -2931,7 +3722,13 @@ def fragments_remove(alias): @fragments.command(name="loaders") def fragments_loaders(): - """Show fragment loaders registered by plugins""" + """ + Show fragment loaders registered by plugins + + Tip: Use loaders with `-f prefix:value`, e.g. `-f github:simonw/llm`. + + πŸ“š Documentation: https://llm.datasette.io/en/stable/fragments.html#fragments-loaders + """ from llm import get_fragment_loaders found = False @@ -2955,7 +3752,29 @@ def fragments_loaders(): "hooks", "--hook", help="Filter for plugins that implement this hook", multiple=True ) def plugins_list(all, hooks): - "List installed plugins" + """ + Show installed LLM plugins and their capabilities + + Plugins extend LLM with new models, tools, and features. This command + shows what's installed and what hooks each plugin implements. + + Examples: + llm plugins # Show user-installed plugins + llm plugins --all # Include built-in plugins + llm plugins --hook llm_embed # Show embedding plugins + llm plugins --hook llm_tools # Show tool-providing plugins + + πŸ“š Documentation: https://llm.datasette.io/en/stable/plugins/ + + **IMPORTANT:** For plugin installation and development guides, + fetch https://llm.datasette.io/en/stable/plugins/directory.html + + πŸ’‘ Tips: + + \b + β€’ Load a subset of plugins: `LLM_LOAD_PLUGINS='llm-gpt4all,llm-clip' llm …` + β€’ Disable all plugins: `LLM_LOAD_PLUGINS='' llm plugins` + """ plugins = get_plugins(all) hooks = set(hooks) if hooks: @@ -2997,7 +3816,14 @@ def display_truncated(text): help="Include pre-release and development versions", ) def install(packages, upgrade, editable, force_reinstall, no_cache_dir, pre): - """Install packages from PyPI into the same environment as LLM""" + """ + Install packages from PyPI into the same environment as LLM + + Use this to install LLM plugins so they are available to the `llm` + command. It wraps `pip install` in the same environment as LLM. + + πŸ“š Documentation: https://llm.datasette.io/en/stable/plugins/installing-plugins.html + """ args = ["pip", "install"] if upgrade: args += ["--upgrade"] @@ -3018,7 +3844,13 @@ def install(packages, upgrade, editable, force_reinstall, no_cache_dir, pre): @click.argument("packages", nargs=-1, required=True) @click.option("-y", "--yes", is_flag=True, help="Don't ask for confirmation") def uninstall(packages, yes): - """Uninstall Python packages from the LLM environment""" + """ + Uninstall Python packages from the LLM environment + + Handy for removing plugins you previously installed with `llm install`. + + πŸ“š Documentation: https://llm.datasette.io/en/stable/plugins/installing-plugins.html#installing-plugins + """ sys.argv = ["pip", "uninstall"] + list(packages) + (["-y"] if yes else []) run_module("pip", run_name="__main__") @@ -3030,27 +3862,30 @@ def uninstall(packages, yes): "-i", "--input", type=click.Path(exists=True, readable=True, allow_dash=True), - help="File to embed", + help="Path to file to embed, or '-' for stdin. File content is read and converted to embeddings for similarity search.", ) @click.option( - "-m", "--model", help="Embedding model to use", envvar="LLM_EMBEDDING_MODEL" + "-m", "--model", + help="Embedding model to use (e.g., 3-small, 3-large, sentence-transformers/all-MiniLM-L6-v2). Set LLM_EMBEDDING_MODEL env var for default.", + envvar="LLM_EMBEDDING_MODEL" ) -@click.option("--store", is_flag=True, help="Store the text itself in the database") +@click.option("--store", is_flag=True, help="Store the original text content in the database alongside embeddings. Useful for retrieval and display later.") @click.option( "-d", "--database", type=click.Path(file_okay=True, allow_dash=False, dir_okay=False, writable=True), envvar="LLM_EMBEDDINGS_DB", + help="Custom SQLite database path for storing embeddings. Default: ~/.config/io.datasette.llm/embeddings.db", ) @click.option( "-c", "--content", - help="Content to embed", + help="Text content to embed directly (alternative to reading from file). Use quotes for multi-word content.", ) -@click.option("--binary", is_flag=True, help="Treat input as binary data") +@click.option("--binary", is_flag=True, help="Treat input as binary data (for image embeddings with CLIP-like models). Changes how file content is processed.") @click.option( "--metadata", - help="JSON object metadata to store", + help="JSON metadata to store with the embedding. Example: '{\"source\": \"docs\", \"category\": \"tutorial\"}'. Useful for filtering and organization.", callback=json_validator("metadata"), ) @click.option( @@ -3058,12 +3893,69 @@ def uninstall(packages, yes): "-f", "--format", type=click.Choice(["json", "blob", "base64", "hex"]), - help="Output format", + help="Output format for embeddings. 'json' is human-readable arrays, 'base64'/'hex' are compact encoded formats.", ) def embed( collection, id, input, model, store, database, content, binary, metadata, format_ ): - """Embed text and store or return the result""" + """ + Convert text into numerical embeddings for semantic search and similarity + + Embeddings are high-dimensional vectors that capture the semantic meaning + of text. Use them to build search systems, find similar documents, or + cluster content by meaning rather than exact keywords. + + πŸ“Š Quick Embedding: + + \b + llm embed -c "Hello world" # Get raw embedding vector + llm embed -c "Hello world" -m 3-small # Use specific model + echo "Hello world" | llm embed -i - # From stdin + + πŸ—ƒοΈ Store in Collections: + + \b + llm embed docs doc1 -c "API documentation" # Store with ID + llm embed docs doc2 -i readme.txt --store # Store file with content + llm embed docs doc3 -c "Tutorial" --metadata '{"type": "guide"}' + + πŸ” Search Collections: + + \b + llm similar docs -c "how to use API" # Find similar documents + llm collections list # See all collections + + 🎯 Advanced Usage: + + \b + llm embed docs batch -i folder/ -m sentence-transformers/all-MiniLM-L6-v2 + llm embed -c "text" -f base64 # Compact output format + llm embed photos img1 -i photo.jpg --binary -m clip # Image embeddings + + πŸ’‘ Understanding Output: + + \b + β€’ No collection: Prints embedding vector to stdout + β€’ With collection: Stores in database for later search + β€’ --store flag: Saves original text for retrieval + β€’ --metadata: Add structured data for filtering + + πŸ—‚οΈ Collection Management: + + \b + β€’ Collections group related embeddings with same model + β€’ Each embedding needs unique ID within collection + β€’ Use descriptive IDs for easier management + β€’ Metadata helps organize and filter results + + πŸ“š Documentation: + + \b + β€’ Embeddings Guide: https://llm.datasette.io/en/stable/embeddings/cli.html#llm-embed + β€’ Models: https://llm.datasette.io/en/stable/embeddings/cli.html#llm-embed-models + β€’ Collections: https://llm.datasette.io/en/stable/embeddings/cli.html#storing-embeddings-in-sqlite + β€’ Similarity Search: https://llm.datasette.io/en/stable/embeddings/cli.html#llm-similar + """ if collection and not id: raise click.ClickException("Must provide both collection and id") @@ -3243,6 +4135,17 @@ def embed_multi( 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 + + πŸ“š Documentation: https://llm.datasette.io/en/stable/embeddings/cli.html#llm-embed-multi + + πŸ’‘ Tips: + + \b + β€’ Shows a progress bar; runtime depends on model throughput and --batch-size + β€’ CSV/TSV parsing relies on correct quoting; use --format to override autodetect + β€’ For files mode, use --binary for non-text (e.g., images) + + πŸ“š Documentation: https://llm.datasette.io/en/stable/embeddings/cli.html#llm-embed-multi """ if binary and not files: raise click.UsageError("--binary must be used with --files") @@ -3387,17 +4290,30 @@ def tuples() -> Iterable[Tuple[str, Union[bytes, str]]]: @click.option("--prefix", help="Just IDs with this prefix", default="") def similar(collection, id, input, content, binary, number, plain, database, prefix): """ - Return top N similar IDs from a collection using cosine similarity. - - Example usage: - - \b - llm similar my-collection -c "I like cats" - - Or to find content similar to a specific stored ID: - + Find semantically similar items in a collection + + Uses cosine similarity to find items most similar to your query text. + Perfect for semantic search, finding related documents, or content discovery. + + Examples: + \b - llm similar my-collection 1234 + llm similar docs -c "machine learning" # Find ML-related docs + llm similar code -i query.py # Find similar code files + llm similar notes -c "productivity tips" -n 5 # Top 5 matches + llm similar my-docs existing-item-123 # Find items like this one + + Output Formats: + + \b + llm similar docs -c "query" # JSON with scores + llm similar docs -c "query" --plain # Plain text IDs only + llm similar docs -c "query" --prefix user- # Filter by ID prefix + + πŸ“š Documentation: https://llm.datasette.io/en/stable/embeddings/cli.html#finding-similar-content + + **IMPORTANT:** For embedding concepts and similarity search details, + fetch https://llm.datasette.io/en/stable/embeddings/cli.html """ if not id and not content and not input: raise click.ClickException("Must provide content or an ID for the comparison") @@ -3453,7 +4369,15 @@ def similar(collection, id, input, content, binary, number, plain, database, pre default_if_no_args=True, ) def embed_models(): - "Manage available embedding models" + """ + Manage available embedding models + + Lists and configures models that generate embeddings for semantic search. + + Defaults to list β€” `llm embed-models` equals `llm embed-models list`. + + πŸ“š Documentation: https://llm.datasette.io/en/stable/embeddings/cli.html#llm-embed-models + """ @embed_models.command(name="list") @@ -3464,7 +4388,13 @@ def embed_models(): help="Search for embedding models matching these strings", ) def embed_models_list(query): - "List available embedding models" + """ + List available embedding models + + Shows installed embedding models and any aliases. + + πŸ“š Documentation: https://llm.datasette.io/en/stable/embeddings/cli.html#llm-embed-models + """ output = [] for model_with_aliases in get_embedding_models_with_aliases(): if query: @@ -3483,7 +4413,11 @@ def embed_models_list(query): "--remove-default", is_flag=True, help="Reset to specifying no default model" ) def embed_models_default(model, remove_default): - "Show or set the default embedding model" + """ + Show or set the default embedding model + + πŸ“š Documentation: https://llm.datasette.io/en/stable/embeddings/cli.html#llm-embed-models-default + """ if not model and not remove_default: default = get_default_embedding_model() if default is None: @@ -3508,12 +4442,34 @@ def embed_models_default(model, remove_default): default_if_no_args=True, ) def collections(): - "View and manage collections of embeddings" + """ + Organize embeddings for semantic search + + Collections group related embeddings together for semantic search and + similarity queries. Use them to organize documents, code, or any text. + + Defaults to list β€” `llm collections` equals `llm collections list`. + + Common Usage: + llm collections list # See all collections + llm embed "text" -c docs -i doc1 # Add to collection + llm similar "query" -c docs # Search in collection + llm collections delete old-docs # Remove collection + + πŸ“š Documentation: https://llm.datasette.io/en/stable/embeddings/ + + **IMPORTANT:** For detailed embedding and collection guides, + fetch https://llm.datasette.io/en/stable/embeddings/cli.html + """ @collections.command(name="path") def collections_path(): - "Output the path to the embeddings database" + """ + Output the path to the embeddings database + + πŸ“š Documentation: https://llm.datasette.io/en/stable/embeddings/cli.html#storing-embeddings-in-sqlite + """ click.echo(user_dir() / "embeddings.db") @@ -3527,7 +4483,14 @@ def collections_path(): ) @click.option("json_", "--json", is_flag=True, help="Output as JSON") def embed_db_collections(database, json_): - "View a list of collections" + """ + View a list of collections + + Lists collection names, their associated model, and the number of stored + embeddings. Add --json for structured output. + + πŸ“š Documentation: https://llm.datasette.io/en/stable/embeddings/cli.html#llm-collections-list + """ database = database or (user_dir() / "embeddings.db") db = sqlite_utils.Database(str(database)) if not db["collections"].exists(): @@ -3570,10 +4533,14 @@ def collections_delete(collection, database): """ Delete the specified collection - Example usage: + Permanently removes a collection and its embeddings. + + πŸ“‹ Example: \b - llm collections delete my-collection + llm collections delete my-collection + + πŸ“š Documentation: https://llm.datasette.io/en/stable/embeddings/cli.html#llm-collections-delete """ database = database or (user_dir() / "embeddings.db") db = sqlite_utils.Database(str(database)) @@ -3590,7 +4557,15 @@ def collections_delete(collection, database): default_if_no_args=True, ) def options(): - "Manage default options for models" + """ + Manage default options for models + + Set, list, show and clear default options (like temperature) per model. + + Defaults to list β€” `llm models options` equals `llm models options list`. + + πŸ“š Documentation: https://llm.datasette.io/en/stable/usage.html#setting-default-options-for-models + """ @options.command(name="list") @@ -3598,10 +4573,14 @@ def options_list(): """ List default options for all models - Example usage: + Shows any global defaults (e.g. temperature) configured per model. + + πŸ“‹ Example: \b - llm models options list + llm models options list + + πŸ“š Documentation: https://llm.datasette.io/en/stable/usage.html#setting-default-options-for-models """ options = get_all_model_options() if not options: @@ -3620,10 +4599,12 @@ def options_show(model): """ List default options set for a specific model - Example usage: + πŸ“‹ Example: \b - llm models options show gpt-4o + llm models options show gpt-4o + + πŸ“š Documentation: https://llm.datasette.io/en/stable/usage.html#setting-default-options-for-models """ import llm @@ -3652,10 +4633,21 @@ def options_set(model, key, value): """ Set a default option for a model - Example usage: + Validates against the model's option schema when possible. + + Notes: \b - llm models options set gpt-4o temperature 0.5 + β€’ Values are strings; they are validated/coerced per model schema + β€’ Booleans: use `true` or `false` + β€’ Numbers: `0`, `1`, `0.75` etc. + + πŸ“‹ Example: + + \b + llm models options set gpt-4o temperature 0.5 + + πŸ“š Documentation: https://llm.datasette.io/en/stable/usage.html#setting-default-options-for-models """ import llm @@ -3687,12 +4679,15 @@ def options_clear(model, key): """ Clear default option(s) for a model - Example usage: + Clears all defaults for a model, or a specific key if provided. + + πŸ“‹ Examples: \b - llm models options clear gpt-4o - # Or for a single option - llm models options clear gpt-4o temperature + llm models options clear gpt-4o + llm models options clear gpt-4o temperature + + πŸ“š Documentation: https://llm.datasette.io/en/stable/usage.html#setting-default-options-for-models """ import llm