Initial llm schemas list implementation, refs #781

This commit is contained in:
Simon Willison 2025-02-27 07:35:28 -08:00
parent a0845874ec
commit 99a1adcece
3 changed files with 138 additions and 0 deletions

View file

@ -77,6 +77,7 @@ Commands:
models Manage available models models Manage available models
openai Commands for working directly with the OpenAI API openai Commands for working directly with the OpenAI API
plugins List installed plugins plugins List installed plugins
schemas Manage stored schemas
similar Return top N similar IDs from a collection using cosine... similar Return top N similar IDs from a collection using cosine...
templates Manage stored prompt templates templates Manage stored prompt templates
uninstall Uninstall Python packages from the LLM environment uninstall Uninstall Python packages from the LLM environment
@ -417,6 +418,33 @@ Options:
--help Show this message and exit. --help Show this message and exit.
``` ```
(help-schemas)=
### llm schemas --help
```
Usage: llm schemas [OPTIONS] COMMAND [ARGS]...
Manage stored schemas
Options:
--help Show this message and exit.
Commands:
list* List stored schemas
```
(help-schemas-list)=
#### llm schemas list --help
```
Usage: llm schemas list [OPTIONS]
List stored schemas
Options:
-p, --path FILE Path to log database
-q, --query TEXT Search for schemas matching this string
--help Show this message and exit.
```
(help-aliases)= (help-aliases)=
### llm aliases --help ### llm aliases --help
``` ```

View file

@ -45,6 +45,7 @@ from .utils import (
make_schema_id, make_schema_id,
output_rows_as_json, output_rows_as_json,
resolve_schema_input, resolve_schema_input,
schema_summary,
) )
import base64 import base64
import httpx import httpx
@ -1373,6 +1374,73 @@ def templates_list():
click.echo(display_truncated(text)) click.echo(display_truncated(text))
@cli.group(
cls=DefaultGroup,
default="list",
default_if_no_args=True,
)
def schemas():
"Manage stored schemas"
@schemas.command(name="list")
@click.option(
"-p",
"--path",
type=click.Path(readable=True, exists=True, dir_okay=False),
help="Path to log database",
)
@click.option(
"queries",
"-q",
"--query",
multiple=True,
help="Search for schemas matching this string",
)
def schemas_list(path, queries):
"List stored schemas"
path = pathlib.Path(path or logs_db_path())
if not path.exists():
raise click.ClickException("No log database found at {}".format(path))
db = sqlite_utils.Database(path)
migrate(db)
params = []
where_sql = ""
if queries:
where_bits = ["schemas.content like ?" for _ in queries]
where_sql += " where {}".format(" and ".join(where_bits))
params.extend("%{}%".format(q) for q in queries)
sql = """
select
schemas.id,
schemas.content,
max(responses.datetime_utc) as recently_used,
count(*) as times_used
from schemas
join responses
on responses.schema_id = schemas.id
{} group by responses.schema_id
order by recently_used
""".format(
where_sql
)
rows = db.query(sql, params)
for row in rows:
click.echo("- id: {}".format(row["id"]))
click.echo(
" summary: |\n {}".format(schema_summary(json.loads(row["content"])))
)
click.echo(
" usage: |\n {} time{}, most recently {}".format(
row["times_used"],
"s" if row["times_used"] != 1 else "",
row["recently_used"],
)
)
@cli.group( @cli.group(
cls=DefaultGroup, cls=DefaultGroup,
default="list", default="list",

View file

@ -256,3 +256,45 @@ def resolve_schema_input(db, schema_input):
return json.loads(row["content"]) return json.loads(row["content"])
except (sqlite_utils.db.NotFoundError, ValueError): except (sqlite_utils.db.NotFoundError, ValueError):
raise click.BadParameter("Invalid schema") raise click.BadParameter("Invalid schema")
def schema_summary(schema: dict) -> str:
"""
Extract property names from a JSON schema and format them in a
concise way that highlights the array/object structure.
Args:
schema (dict): A JSON schema dictionary
Returns:
str: A human-friendly summary of the schema structure
"""
if not schema or not isinstance(schema, dict):
return ""
schema_type = schema.get("type", "")
if schema_type == "object":
props = schema.get("properties", {})
prop_summaries = []
for name, prop_schema in props.items():
prop_type = prop_schema.get("type", "")
if prop_type == "array":
items = prop_schema.get("items", {})
items_summary = schema_summary(items)
prop_summaries.append(f"{name}: [{items_summary}]")
elif prop_type == "object":
nested_summary = schema_summary(prop_schema)
prop_summaries.append(f"{name}: {nested_summary}")
else:
prop_summaries.append(name)
return "{" + ", ".join(prop_summaries) + "}"
elif schema_type == "array":
items = schema.get("items", {})
return schema_summary(items)
return ""