"""Shared CLI help text and parser helpers.
Purpose:
Keep the command-line interface teachable without letting ``cli.py`` grow
into a pile of duplicated help strings and option builders.
"""
from __future__ import annotations
import argparse
from decimal import Decimal
from importlib.metadata import PackageNotFoundError, version as metadata_version
[docs]
MODEL_CAPABILITY_CHOICES = (
"chat",
"reasoning",
"coding",
"vision",
"function_calling",
"tool_calling",
"tool_choice",
"parallel_tool_calls",
"structured_output",
"cheap",
)
[docs]
MODEL_CATALOG_SORT_CHOICES = (
"recency",
"provider",
"model",
"cost",
"input_cost",
"output_cost",
"context",
"input_tokens",
"output_tokens",
)
[docs]
MODEL_COMPARE_SORT_CHOICES = (
"call_cost",
"cost",
"calls",
"calls_per_usd",
"provider",
"model",
"input_tokens",
"output_tokens",
"context",
)
[docs]
LCB_PRO_DIFFICULTY_CHOICES = ("easy", "medium", "hard")
[docs]
LCB_PRO_SORT_CHOICES = ("rating", "provider", "organization", "model", "status")
[docs]
ROOT_HELP = """\
Utilities for choosing, comparing, configuring, and inspecting LLMs.
Mental model:
Catalog/suite choose and compare models
ChatModelProfile serialize model configuration
LLM invoke a model and record observed usage/cost
"""
[docs]
ROOT_EXAMPLES = """\
Common tasks:
List cheap Mistral models:
ooai-llm models cheapest --providers mistral --limit 10
Compare coding-capable models for a 10k/2k call:
ooai-llm models coding --providers openai,anthropic,mistral --input-tokens 10000 --output-tokens 2000
Find tool + structured-output models:
ooai-llm models list --source litellm --tool-calling-only --structured-output-only --sort output_tokens
Explore the full raw LiteLLM registry:
ooai-llm models list --all-litellm --format table
ooai-llm tui --catalog-all
Build a reusable model suite:
ooai-llm models suite --suite comparison --providers openai,anthropic,mistral
Validate and resolve a serializable profile:
ooai-llm profiles validate --input profile.json
ooai-llm profiles resolve --input profile.json --format json
Print copy/paste CLI and Python recipes:
ooai-llm recipes --topic coding
ooai-llm recipes --topic rich
Launch the optional interactive explorer:
ooai-llm tui --theme paper
ooai-llm tui --providers mistral --views cheapest,catalog --limit 10
Use --format json or --format csv for automation. Use --no-rich for deterministic plain tables.
"""
[docs]
MODELS_EXAMPLES = """\
Examples:
ooai-llm models cheapest --providers mistral --limit 10
ooai-llm models coding --providers openai,anthropic,mistral --tool-calling-only
ooai-llm models list --source litellm --providers openai,mistral --sort cost --limit 0
ooai-llm models list --all-litellm --providers openrouter --limit 0
ooai-llm models compare --source litellm --providers mistral --input-tokens 10000 --output-tokens 2000
ooai-llm models suite --from-catalog --coding-only --sort cost --limit 5
"""
[docs]
MODEL_LIST_EXAMPLES = """\
Examples:
ooai-llm models list --source litellm --providers mistral --sort cost --limit 0
ooai-llm models list --all-litellm --limit 0
ooai-llm models list --all-litellm --providers fireworks_ai,openrouter --limit 0
ooai-llm models list --source litellm --tool-calling-only --structured-output-only --sort output_tokens
ooai-llm models list --source litellm --released-after 2026-01 --min-input-tokens 128000
"""
[docs]
MODEL_COMPARE_EXAMPLES = """\
Examples:
ooai-llm models compare --source litellm --providers mistral --input-tokens 10000 --output-tokens 2000
ooai-llm models compare --source litellm --providers openai,anthropic,mistral --baseline openai:gpt-5-mini
ooai-llm models compare --source litellm --coding-only --tool-calling-only --sort call_cost
"""
[docs]
MODEL_CHEAPEST_EXAMPLES = """\
Examples:
ooai-llm models cheapest --providers mistral --limit 10
ooai-llm models cheapest --providers openai,anthropic,google,deepseek,mistral --per-provider
ooai-llm models cheapest --structured-output-only --input-tokens 5000 --output-tokens 1000 --budget-usd 20
"""
[docs]
MODEL_CODING_EXAMPLES = """\
Examples:
ooai-llm models coding --providers mistral --limit 10
ooai-llm models coding --providers openai,anthropic,google,deepseek,mistral --per-provider
ooai-llm models coding --tool-calling-only --structured-output-only --min-output-tokens 8000
"""
[docs]
MODEL_SUITE_EXAMPLES = """\
Examples:
ooai-llm models suite --suite comparison --providers openai,anthropic,mistral
ooai-llm models suite --from-catalog --coding-only --sort cost --limit 5
ooai-llm models suite --suite comparison --parallel-tool-calls false --format json
"""
[docs]
PROFILES_EXAMPLES = """\
Examples:
ooai-llm profiles validate --input profile.json
ooai-llm profiles render --input profile.json
ooai-llm profiles resolve --input profile.json --format json
"""
[docs]
BENCHMARKS_EXAMPLES = """\
Examples:
ooai-llm benchmarks lcb-pro summary
ooai-llm benchmarks lcb-pro models --status active --limit 10
ooai-llm benchmarks lcb-pro difficulty --difficulty hard --provider openai
"""
[docs]
def package_version() -> str:
"""Return the installed package version for ``--version`` output."""
try:
return metadata_version("ooai-llm")
except PackageNotFoundError:
return "0.0.0"
[docs]
def add_provider_args(command: argparse.ArgumentParser, *, noun: str = "Provider") -> None:
"""Add repeatable and comma-separated provider options."""
command.add_argument(
"--provider",
action="append",
default=[],
help=f"{noun} to include. Can be repeated.",
)
command.add_argument(
"--providers",
action="append",
default=[],
help=f"Comma-separated {noun.lower()} list.",
)
[docs]
def add_catalog_filter_args(command: argparse.ArgumentParser, *, context_word: str = "show") -> None:
"""Add common model catalog filters used by list/compare/suite commands."""
command.add_argument(
"--include-non-chat",
action="store_true",
help="Include embeddings, image, audio, and other non-chat models.",
)
command.add_argument(
"--capability",
action="append",
choices=MODEL_CAPABILITY_CHOICES,
default=[],
help="Required capability filter. Can be repeated.",
)
command.add_argument("--reasoning-only", action="store_true", help=f"Only {context_word} reasoning-oriented models.")
command.add_argument("--coding-only", action="store_true", help=f"Only {context_word} coding-oriented models.")
command.add_argument("--vision-only", action="store_true", help=f"Only {context_word} vision-capable models.")
command.add_argument(
"--function-calling-only",
action="store_true",
help=f"Only {context_word} models marked as function/tool-call capable.",
)
command.add_argument(
"--tool-calling-only",
action="store_true",
help=f"Only {context_word} models marked as tool-call capable.",
)
command.add_argument(
"--tool-choice-only",
action="store_true",
help=f"Only {context_word} models marked as explicit tool-choice capable.",
)
command.add_argument(
"--parallel-tool-calls-only",
action="store_true",
help=f"Only {context_word} models marked as parallel-tool-call capable.",
)
command.add_argument(
"--structured-output-only",
action="store_true",
help=f"Only {context_word} models marked as native structured-output capable.",
)
command.add_argument(
"--min-context",
type=int,
default=None,
help="Only include models with at least this many input/context tokens.",
)
command.add_argument(
"--min-input-tokens",
dest="min_context",
type=int,
default=None,
help="Alias for --min-context.",
)
command.add_argument(
"--min-output-tokens",
type=int,
default=None,
help="Only include models with at least this many output tokens.",
)
command.add_argument(
"--max-input-cost-per-1m",
type=Decimal,
default=None,
help="Only include models at or below this input-token USD cost per 1M tokens.",
)
command.add_argument(
"--max-output-cost-per-1m",
type=Decimal,
default=None,
help="Only include models at or below this output-token USD cost per 1M tokens.",
)
command.add_argument("--released-after", default=None, help="Catalog lower release-date bound.")
command.add_argument("--released-before", default=None, help="Catalog upper release-date bound.")
[docs]
def add_compare_args(
command: argparse.ArgumentParser,
*,
default_sort: str = "call_cost",
default_limit: int = 20,
) -> None:
"""Add common cost-comparison options."""
command.add_argument(
"--source",
choices=("auto", "provider", "litellm"),
default="auto",
help="Catalog source. Defaults to auto.",
)
add_provider_args(command, noun="Provider")
command.add_argument(
"--format",
choices=("table", "json", "csv"),
default="table",
help="Output format. Defaults to table.",
)
command.add_argument(
"--style",
choices=("langchain", "litellm", "bare"),
default="langchain",
help="Model-string style for table and CSV output.",
)
command.add_argument(
"--limit",
type=int,
default=default_limit,
help="Maximum rows after ranking. Use 0 for no limit.",
)
command.add_argument(
"--input-tokens",
type=int,
default=10_000,
help="Representative input tokens per call. Defaults to 10000.",
)
command.add_argument(
"--output-tokens",
type=int,
default=2_000,
help="Representative output tokens per call. Defaults to 2000.",
)
command.add_argument(
"--budget-usd",
type=Decimal,
default=Decimal("1"),
help="Budget for calls-per-budget estimates. Defaults to 1.",
)
command.add_argument(
"--baseline",
default=None,
help="Optional model used to compute calls-per-baseline ratios.",
)
command.add_argument(
"--per-provider",
action="store_true",
help="Only keep the cheapest matching model for each provider.",
)
add_catalog_filter_args(command, context_word="compare")
command.add_argument(
"--sort",
choices=MODEL_COMPARE_SORT_CHOICES,
default=default_sort,
help=f"Sort comparison rows. Defaults to {default_sort}.",
)
command.add_argument("--strict", action="store_true", help="Fail if any selected provider cannot be listed.")
command.add_argument(
"--no-rich",
action="store_true",
help="Use the built-in plain table renderer even when Rich is installed.",
)