Skip to content

Commit a316064

Browse files
committed
make LLM support optional
* LLM support can be installed with new extras "llm" or "all" * conditionally import "llm" and "llm.cli" * show alternative message when user attempts to use \llm without dependencies available. Unlike ssh extras, there is no need to exit on failure. * cache the list of possible CLI commands for performance, avoiding a regression * update quickstart to recommend installing with the "all" extra * update changelog
1 parent fb984a3 commit a316064

File tree

4 files changed

+61
-11
lines changed

4 files changed

+61
-11
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ If you already know how to install Python packages, then you can install it via
2020
You might need sudo on Linux.
2121

2222
```bash
23-
pip install -U mycli
23+
pip install -U 'mycli[all]'
2424
```
2525

2626
or

changelog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
Upcoming (TBD)
22
==============
33

4+
Features
5+
--------
6+
* Make LLM dependencies an optional extra.
7+
8+
49
Internal
510
--------
611
* Add mypy to Pull Request template.

mycli/packages/special/llm.py

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import contextlib
2+
import functools
23
import io
34
import logging
45
import os
@@ -10,15 +11,24 @@
1011
from typing import Optional, Tuple
1112

1213
import click
13-
import llm
14-
from llm.cli import cli
14+
15+
try:
16+
import llm
17+
18+
LLM_IMPORTED = True
19+
except ImportError:
20+
LLM_IMPORTED = False
21+
try:
22+
from llm.cli import cli
23+
24+
LLM_CLI_IMPORTED = True
25+
except ImportError:
26+
LLM_CLI_IMPORTED = False
1527

1628
from mycli.packages.special.main import Verbosity, parse_special_command
1729

1830
log = logging.getLogger(__name__)
1931

20-
LLM_CLI_COMMANDS = list(cli.commands.keys())
21-
MODELS = {x.model_id: None for x in llm.get_models()}
2232
LLM_TEMPLATE_NAME = "mycli-llm-template"
2333

2434

@@ -67,7 +77,7 @@ def build_command_tree(cmd):
6777
if isinstance(cmd, click.Group):
6878
for name, subcmd in cmd.commands.items():
6979
if cmd.name == "models" and name == "default":
70-
tree[name] = MODELS
80+
tree[name] = {x.model_id: None for x in llm.get_models()}
7181
else:
7282
tree[name] = build_command_tree(subcmd)
7383
else:
@@ -76,7 +86,7 @@ def build_command_tree(cmd):
7686

7787

7888
# Generate the command tree for autocompletion
79-
COMMAND_TREE = build_command_tree(cli) if cli else {}
89+
COMMAND_TREE = build_command_tree(cli) if LLM_CLI_IMPORTED is True else {}
8090

8191

8292
def get_completions(tokens, tree=COMMAND_TREE):
@@ -120,7 +130,25 @@ def __init__(self, results=None):
120130
# Plugins directory
121131
# https://llm.datasette.io/en/stable/plugins/directory.html
122132
"""
133+
134+
NEED_DEPENDENCIES = """
135+
To enable LLM features you need to install mycli with LLM support:
136+
137+
pip install 'mycli[llm]'
138+
139+
or
140+
141+
pip install 'mycli[all]'
142+
143+
or install LLM libraries separately
144+
145+
pip install llm
146+
147+
This is required to use the \\llm command.
148+
"""
149+
123150
_SQL_CODE_FENCE = r"```sql\n(.*?)\n```"
151+
124152
PROMPT = """
125153
You are a helpful assistant who is a MySQL expert. You are embedded in a mysql
126154
cli tool called mycli.
@@ -159,8 +187,16 @@ def ensure_mycli_template(replace=False):
159187
return
160188

161189

190+
@functools.cache
191+
def cli_commands() -> list[str]:
192+
return list(cli.commands.keys())
193+
194+
162195
def handle_llm(text, cur) -> Tuple[str, Optional[str], float]:
163196
_, verbosity, arg = parse_special_command(text)
197+
if not LLM_IMPORTED:
198+
output = [(None, None, None, NEED_DEPENDENCIES)]
199+
raise FinishIteration(output)
164200
if not arg.strip():
165201
output = [(None, None, None, USAGE)]
166202
raise FinishIteration(output)
@@ -176,7 +212,7 @@ def handle_llm(text, cur) -> Tuple[str, Optional[str], float]:
176212
capture_output = False
177213
use_context = False
178214
restart = True
179-
elif parts and parts[0] in LLM_CLI_COMMANDS:
215+
elif parts and parts[0] in cli_commands():
180216
capture_output = False
181217
use_context = False
182218
elif parts and parts[0] == "--help":

pyproject.toml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ dependencies = [
2121
"pyperclip >= 1.8.1",
2222
"pycryptodomex",
2323
"pyfzf >= 0.3.1",
24-
"llm>=0.19.0",
25-
"setuptools", # Required by llm commands to install models
26-
"pip",
2724
]
2825

2926
[build-system]
@@ -35,6 +32,15 @@ build-backend = "setuptools.build_meta"
3532

3633
[project.optional-dependencies]
3734
ssh = ["paramiko", "sshtunnel"]
35+
llm = [
36+
"llm>=0.19.0",
37+
"setuptools", # Required by llm commands to install models
38+
"pip",
39+
]
40+
all = [
41+
"mycli[ssh]",
42+
"mycli[llm]",
43+
]
3844
dev = [
3945
"behave>=1.2.6",
4046
"coverage>=7.2.7",
@@ -46,6 +52,9 @@ dev = [
4652
"pdbpp>=0.10.3",
4753
"paramiko",
4854
"sshtunnel",
55+
"llm>=0.19.0",
56+
"setuptools", # Required by llm commands to install models
57+
"pip",
4958
]
5059

5160
[project.scripts]

0 commit comments

Comments
 (0)