whatmodel 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
whatmodel/__init__.py ADDED
@@ -0,0 +1,33 @@
1
+ """whatmodel: explain LLM model names in plain English.
2
+
3
+ Two layers:
4
+
5
+ * :func:`parse` reads the name itself, offline.
6
+ * :func:`explain` does the same and then looks up the facts a name cannot show.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from importlib.metadata import PackageNotFoundError
12
+ from importlib.metadata import version as _distribution_version
13
+
14
+ from whatmodel.dictionary import Reading
15
+ from whatmodel.lookup import Fact, ModelInfo, explain
16
+ from whatmodel.parser import Implied, ParsedName, Token, parse
17
+
18
+ try:
19
+ __version__ = _distribution_version("whatmodel")
20
+ except PackageNotFoundError: # running from a source tree that was never installed
21
+ __version__ = "0+unknown"
22
+
23
+ __all__ = [
24
+ "Fact",
25
+ "Implied",
26
+ "ModelInfo",
27
+ "ParsedName",
28
+ "Reading",
29
+ "Token",
30
+ "__version__",
31
+ "explain",
32
+ "parse",
33
+ ]
whatmodel/cli.py ADDED
@@ -0,0 +1,197 @@
1
+ """Command line interface.
2
+
3
+ whatmodel <name> [<name>...] explain one or more names
4
+ whatmodel list explain every model installed in local Ollama
5
+ whatmodel tokens print the token dictionary
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ from typing import Annotated, Any
12
+
13
+ import typer
14
+ from rich.console import Console
15
+ from typer.core import TyperGroup
16
+
17
+ from whatmodel import __version__
18
+ from whatmodel.dictionary import get_dictionary
19
+ from whatmodel.lookup import (
20
+ DEFAULT_OLLAMA_URL,
21
+ ModelInfo,
22
+ OllamaUnavailableError,
23
+ explain,
24
+ list_installed,
25
+ )
26
+ from whatmodel.render import print_info, render_tokens
27
+
28
+ _GROUP_OPTIONS = frozenset({"--help", "-h", "--version"})
29
+
30
+
31
+ class _ExplainByDefault(TyperGroup):
32
+ """Treat ``whatmodel <name>`` as ``whatmodel explain <name>``.
33
+
34
+ A model that happens to be called "list" or "tokens" can still be explained
35
+ with ``whatmodel explain list``.
36
+ """
37
+
38
+ def parse_args(self, ctx: typer.Context, args: list[str]) -> list[str]:
39
+ if args and args[0] not in self.commands and args[0] not in _GROUP_OPTIONS:
40
+ args = ["explain", *args]
41
+ return super().parse_args(ctx, args)
42
+
43
+
44
+ app = typer.Typer(
45
+ cls=_ExplainByDefault,
46
+ add_completion=False,
47
+ no_args_is_help=True,
48
+ context_settings={"help_option_names": ["-h", "--help"]},
49
+ help="Explain LLM model names in plain English.",
50
+ )
51
+
52
+ JsonOption = Annotated[bool, typer.Option("--json", help="Print JSON instead of text.")]
53
+ OfflineOption = Annotated[
54
+ bool, typer.Option("--offline", help="Read the name only; make no network requests.")
55
+ ]
56
+ OllamaUrlOption = Annotated[
57
+ str, typer.Option("--ollama-url", help="Base URL of the Ollama API.", envvar="OLLAMA_HOST")
58
+ ]
59
+ VerboseOption = Annotated[
60
+ bool, typer.Option("--verbose", "-v", help="Show a one-sentence explanation under each row.")
61
+ ]
62
+
63
+
64
+ def _ollama_url(value: str) -> str:
65
+ """Accept the bare ``host:port`` form that OLLAMA_HOST usually holds."""
66
+ return value if "://" in value else f"http://{value}"
67
+
68
+
69
+ def _print_json(data: Any) -> None:
70
+ typer.echo(json.dumps(data, indent=2))
71
+
72
+
73
+ def _version(value: bool) -> None:
74
+ if value:
75
+ typer.echo(f"whatmodel {__version__}")
76
+ raise typer.Exit
77
+
78
+
79
+ @app.callback()
80
+ def _main(
81
+ version: Annotated[
82
+ bool,
83
+ typer.Option(
84
+ "--version", callback=_version, is_eager=True, help="Print the version and exit."
85
+ ),
86
+ ] = False,
87
+ ) -> None:
88
+ """Explain LLM model names in plain English."""
89
+
90
+
91
+ def _explain_all(
92
+ names: list[str], *, offline: bool, ollama_url: str
93
+ ) -> tuple[list[ModelInfo], list[str]]:
94
+ infos: list[ModelInfo] = []
95
+ errors: list[str] = []
96
+ for name in names:
97
+ try:
98
+ infos.append(explain(name, offline=offline, ollama_url=_ollama_url(ollama_url)))
99
+ except ValueError as exc:
100
+ errors.append(f"{name!r}: {exc}")
101
+ return infos, errors
102
+
103
+
104
+ def _report(
105
+ infos: list[ModelInfo], errors: list[str], *, as_json: bool, as_list: bool, verbose: bool
106
+ ) -> None:
107
+ if as_json:
108
+ data = [info.to_dict() for info in infos]
109
+ _print_json(data if as_list or len(data) != 1 else data[0])
110
+ else:
111
+ console = Console()
112
+ for info in infos:
113
+ print_info(console, info, verbose=verbose)
114
+ if infos:
115
+ console.print()
116
+ for error in errors:
117
+ typer.echo(f"whatmodel: {error}", err=True)
118
+ if errors:
119
+ raise typer.Exit(code=1)
120
+
121
+
122
+ @app.command("explain")
123
+ def explain_command(
124
+ names: Annotated[list[str], typer.Argument(help="Model names to explain.")],
125
+ as_json: JsonOption = False,
126
+ offline: OfflineOption = False,
127
+ ollama_url: OllamaUrlOption = DEFAULT_OLLAMA_URL,
128
+ verbose: VerboseOption = False,
129
+ ) -> None:
130
+ """Explain one or more model names. This is what `whatmodel <name>` runs."""
131
+ infos, errors = _explain_all(names, offline=offline, ollama_url=ollama_url)
132
+ _report(infos, errors, as_json=as_json, as_list=len(names) > 1, verbose=verbose)
133
+
134
+
135
+ @app.command("list")
136
+ def list_command(
137
+ as_json: JsonOption = False,
138
+ offline: OfflineOption = False,
139
+ ollama_url: OllamaUrlOption = DEFAULT_OLLAMA_URL,
140
+ verbose: VerboseOption = False,
141
+ ) -> None:
142
+ """Explain every model installed in local Ollama.
143
+
144
+ Ollama has to be running to list what is installed. With --offline the
145
+ names are still listed, but nothing more is looked up about them.
146
+ """
147
+ try:
148
+ names = list_installed(_ollama_url(ollama_url))
149
+ except OllamaUnavailableError as exc:
150
+ typer.echo(f"whatmodel: {exc}. Is Ollama running?", err=True)
151
+ raise typer.Exit(code=1) from exc
152
+ if not names and not as_json:
153
+ typer.echo("No models are installed in Ollama.")
154
+ return
155
+ infos, errors = _explain_all(names, offline=offline, ollama_url=ollama_url)
156
+ _report(infos, errors, as_json=as_json, as_list=True, verbose=verbose)
157
+
158
+
159
+ @app.command("tokens")
160
+ def tokens_command(as_json: JsonOption = False) -> None:
161
+ """Print the token dictionary: every token whatmodel knows and what it means."""
162
+ dictionary = get_dictionary()
163
+ if not as_json:
164
+ Console().print(render_tokens(dictionary))
165
+ return
166
+ _print_json(
167
+ {
168
+ "categories": [
169
+ {
170
+ "id": category.id,
171
+ "heading": category.heading,
172
+ "description": category.description,
173
+ }
174
+ for category in dictionary.categories.values()
175
+ ],
176
+ "tokens": [
177
+ {
178
+ "pattern": list(entry.patterns),
179
+ "regex": entry.is_regex,
180
+ "category": entry.category,
181
+ "label": entry.label,
182
+ "explanation": entry.explanation,
183
+ "examples": list(entry.examples),
184
+ }
185
+ for entry in dictionary.entries
186
+ ],
187
+ }
188
+ )
189
+
190
+
191
+ def main() -> None:
192
+ """Entry point for the ``whatmodel`` command."""
193
+ app()
194
+
195
+
196
+ if __name__ == "__main__":
197
+ main()