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 +33 -0
- whatmodel/cli.py +197 -0
- whatmodel/dictionary.py +524 -0
- whatmodel/lookup.py +517 -0
- whatmodel/parser.py +468 -0
- whatmodel/py.typed +0 -0
- whatmodel/render.py +304 -0
- whatmodel/tokens.yaml +1101 -0
- whatmodel-0.0.1.dist-info/METADATA +270 -0
- whatmodel-0.0.1.dist-info/RECORD +13 -0
- whatmodel-0.0.1.dist-info/WHEEL +4 -0
- whatmodel-0.0.1.dist-info/entry_points.txt +2 -0
- whatmodel-0.0.1.dist-info/licenses/LICENSE +21 -0
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()
|