Skip to content

Commit 9fa8264

Browse files
Carreaumeeseeksmachine
authored andcommitted
Backport PR ipython#14720: Better Docs and configurability of LLM.
1 parent 1a47b9e commit 9fa8264

File tree

8 files changed

+176
-46
lines changed

8 files changed

+176
-46
lines changed

IPython/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def embed_kernel(module=None, local_ns=None, **kwargs):
8787
**kwargs : various, optional
8888
Further keyword args are relayed to the IPKernelApp constructor,
8989
such as `config`, a traitlets :class:`Config` object (see :ref:`configure_start_ipython`),
90-
allowing configuration of the kernel (see :ref:`kernel_options`). Will only have an effect
90+
allowing configuration of the kernel. Will only have an effect
9191
on the first embed_kernel call for a given process.
9292
"""
9393

IPython/terminal/interactiveshell.py

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,14 @@ def _displayhook_class_default(self):
426426
allow_none=True,
427427
).tag(config=True)
428428

429+
llm_constructor_kwargs = Dict(
430+
{},
431+
help="""
432+
Extra arguments to pass to `llm_provider_class` constructor.
433+
434+
This is used to – for example – set the `model_id`""",
435+
).tag(config=True)
436+
429437
llm_provider_class = DottedObjectName(
430438
None,
431439
allow_none=True,
@@ -471,7 +479,7 @@ def _set_autosuggestions(self, provider):
471479
# LLM stuff are all Provisional in 8.32
472480
if self.llm_provider_class:
473481
llm_provider_constructor = import_item(self.llm_provider_class)
474-
llm_provider = llm_provider_constructor()
482+
llm_provider = llm_provider_constructor(**self.llm_constructor_kwargs)
475483
else:
476484
llm_provider = None
477485
self.auto_suggest = NavigableAutoSuggestFromHistory()
@@ -508,23 +516,23 @@ def _autosuggestions_provider_changed(self, change):
508516
"create": Bool(False),
509517
},
510518
),
511-
help="""Add, disable or modifying shortcuts.
519+
help="""
520+
Add, disable or modifying shortcuts.
512521
513522
Each entry on the list should be a dictionary with ``command`` key
514523
identifying the target function executed by the shortcut and at least
515524
one of the following:
516525
517-
- ``match_keys``: list of keys used to match an existing shortcut,
518-
- ``match_filter``: shortcut filter used to match an existing shortcut,
519-
- ``new_keys``: list of keys to set,
520-
- ``new_filter``: a new shortcut filter to set
526+
- ``match_keys``: list of keys used to match an existing shortcut,
527+
- ``match_filter``: shortcut filter used to match an existing shortcut,
528+
- ``new_keys``: list of keys to set,
529+
- ``new_filter``: a new shortcut filter to set
521530
522531
The filters have to be composed of pre-defined verbs and joined by one
523532
of the following conjunctions: ``&`` (and), ``|`` (or), ``~`` (not).
524533
The pre-defined verbs are:
525534
526-
{}
527-
535+
{filters}
528536
529537
To disable a shortcut set ``new_keys`` to an empty list.
530538
To add a shortcut add key ``create`` with value ``True``.
@@ -539,8 +547,27 @@ def _autosuggestions_provider_changed(self, change):
539547
shortcuts) can be modified or disabled. The full list of shortcuts,
540548
command identifiers and filters is available under
541549
:ref:`terminal-shortcuts-list`.
550+
551+
Here is an example:
552+
553+
.. code::
554+
555+
c.TerminalInteractiveShell.shortcuts = [
556+
{{
557+
"new_keys": ["c-q"],
558+
"command": "prompt_toolkit:named_commands.capitalize_word",
559+
"create": True,
560+
}},
561+
{{
562+
"new_keys": ["c-j"],
563+
"command": "prompt_toolkit:named_commands.beginning_of_line",
564+
"create": True,
565+
}},
566+
]
567+
568+
542569
""".format(
543-
"\n ".join([f"- `{k}`" for k in KEYBINDING_FILTERS])
570+
filters="\n ".join([f" - ``{k}``" for k in KEYBINDING_FILTERS])
544571
),
545572
).tag(config=True)
546573

IPython/terminal/shortcuts/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
from prompt_toolkit.filters import Condition
2525

2626
from IPython.core.getipython import get_ipython
27-
from IPython.terminal.shortcuts import auto_match as match
28-
from IPython.terminal.shortcuts import auto_suggest
29-
from IPython.terminal.shortcuts.filters import filter_from_string
27+
from . import auto_match as match
28+
from . import auto_suggest
29+
from .filters import filter_from_string
3030
from IPython.utils.decorators import undoc
3131

3232
from prompt_toolkit.enums import DEFAULT_BUFFER
@@ -630,6 +630,7 @@ def win_paste(event):
630630
]
631631

632632
UNASSIGNED_ALLOWED_COMMANDS = [
633+
auto_suggest.llm_autosuggestion,
633634
nc.beginning_of_buffer,
634635
nc.end_of_buffer,
635636
nc.end_of_line,

IPython/terminal/shortcuts/auto_suggest.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,6 @@
2424

2525
from .filters import pass_through
2626

27-
try:
28-
import jupyter_ai_magics
29-
import jupyter_ai.completions.models as jai_models
30-
except ModuleNotFoundError:
31-
jai_models = None
32-
3327

3428
def _get_query(document: Document):
3529
return document.lines[document.cursor_position_row]
@@ -332,6 +326,11 @@ async def _trigger_llm(self, buffer) -> None:
332326
If there is a currently running llm task, it will cancel it.
333327
"""
334328
# we likely want to store the current cursor position, and cancel if the cursor has moved.
329+
try:
330+
import jupyter_ai_magics
331+
import jupyter_ai.completions.models as jai_models
332+
except ModuleNotFoundError:
333+
jai_models = None
335334
if not self._llm_provider:
336335
warnings.warn("No LLM provider found, cannot trigger LLM completions")
337336
return
@@ -381,6 +380,11 @@ async def _trigger_llm_core(self, buffer: Buffer):
381380
stream_inline_completions, I'm not sure it is the case for all
382381
providers.
383382
"""
383+
try:
384+
import jupyter_ai_magics
385+
import jupyter_ai.completions.models as jai_models
386+
except ModuleNotFoundError:
387+
jai_models = None
384388

385389
request = jai_models.InlineCompletionRequest(
386390
number=0,
@@ -411,9 +415,6 @@ async def _trigger_llm_core(self, buffer: Buffer):
411415
return
412416

413417

414-
_MIN_LINES = 5
415-
416-
417418
async def llm_autosuggestion(event: KeyPressEvent):
418419
"""
419420
Ask the AutoSuggester from history to delegate to ask an LLM for completion
@@ -424,6 +425,7 @@ async def llm_autosuggestion(event: KeyPressEvent):
424425
Provisional as of 8.32, may change without warnigns
425426
426427
"""
428+
_MIN_LINES = 5
427429
provider = get_ipython().auto_suggest
428430
if not isinstance(provider, NavigableAutoSuggestFromHistory):
429431
return

docs/autogen_config.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import inspect
44
from pathlib import Path
55
from IPython.terminal.ipapp import TerminalIPythonApp
6-
from ipykernel.kernelapp import IPKernelApp
76
from traitlets import Undefined
87
from collections import defaultdict
98

@@ -103,13 +102,9 @@ def write_doc(name, title, app, preamble=None):
103102
trait_aliases = reverse_aliases(app)
104103
filename = options / (name + ".rst")
105104
with open(filename, "w", encoding="utf-8") as f:
106-
f.write(".. _" + name + "_options:" + "\n\n")
107-
f.write(title + "\n")
108-
f.write(("=" * len(title)) + "\n")
109105
f.write("\n")
110106
if preamble is not None:
111107
f.write(preamble + '\n\n')
112-
#f.write(app.document_config_options())
113108

114109
for c in app._classes_inc_parents():
115110
f.write(class_config_rst_doc(c, trait_aliases))
@@ -121,7 +116,3 @@ def write_doc(name, title, app, preamble=None):
121116
Path(generated).write_text("", encoding="utf-8")
122117

123118
write_doc('terminal', 'Terminal IPython options', TerminalIPythonApp())
124-
write_doc('kernel', 'IPython kernel options', IPKernelApp(),
125-
preamble=("These options can be used in :file:`ipython_kernel_config.py`. "
126-
"The kernel also respects any options in `ipython_config.py`"),
127-
)

docs/source/config/details.rst

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,101 @@
1-
=======================
2-
Specific config details
3-
=======================
1+
==============================
2+
Specific configuration details
3+
==============================
4+
5+
.. _llm_suggestions:
6+
7+
LLM Suggestions
8+
===============
9+
10+
Starting with 9.0, IPython will be able to use LLM providers to suggest code in
11+
the terminal. This requires a recent version of prompt_toolkit in order to allow
12+
multiline suggestions. There are currently a number of limitations, and feedback
13+
on the API is welcome.
14+
15+
Unlike many of IPython features, this is not enabled by default and requires
16+
multiple configuration options to be set to properly work:
17+
18+
- Set a keybinding to trigger LLM suggestions. Due to terminal limitations
19+
across platforms and emulators, it is difficult to provide a default
20+
keybinding. Note that not all keybindings are availables, in particular all
21+
the `Ctrl-Enter`, `Alt-backslash` and `Ctrl-Shift-Enter` are not available
22+
without integration with your terminal emulator.
23+
24+
- Chose a LLM `provider`, usually from Jupyter-AI. This will be the interface
25+
between IPython itself, and the LLM – that may be local or in on a server.
26+
27+
- Configure said provider with models, API keys, etc – this will depend on the
28+
provider, and you will have to refer to Jupyter-AI documentation, and/or your
29+
LLM documenatation.
30+
31+
32+
While setting up IPython to use a real LLM, you can refer to
33+
``examples/auto_suggest_llm.py`` that both provide an example of how to set up
34+
IPython to use a Fake LLM provider, this can help ensure that the full setup is
35+
working before switching to a real LLM provider.
36+
37+
38+
Setup a keybinding
39+
------------------
40+
41+
You may want to refer on how to setup a keybinding in IPython, but in short you
42+
want to bind the ``IPython:auto_suggest.llm_autosuggestion`` command to a
43+
keybinding, and have it active only when the default buffer isi focused, and
44+
when using the NavigableSuggestions suggestter (this is the default suggestter,
45+
the one that is history and LLM aware). Thus the ``navigable_suggestions &
46+
default_buffer_focused`` filter should be used.
47+
48+
Usually ``Ctrl-Q`` on macos is an available shortcut, note that is does use
49+
``Ctrl``, and not ``Command``.
50+
51+
The following example will bind ``Ctrl-Q`` to the ``llm_autosuggestion``
52+
command, with the suggested filter::
53+
54+
c.TerminalInteractiveShell.shortcuts = [
55+
{
56+
"new_keys": ["c-q"],
57+
"command": "IPython:auto_suggest.llm_autosuggestion",
58+
"new_filter": "navigable_suggestions & default_buffer_focused",
59+
"create": True,
60+
},
61+
]
62+
63+
64+
Choose a LLM provider
65+
---------------------
66+
67+
Set the ``TerminalInteractiveShell.llm_provider_class`` trait to the fully
68+
qualified name of the Provider you like, when testing from inside the IPython
69+
source tree, you can use
70+
``"examples.auto_suggest_llm.ExampleCompletionProvider"`` This will always
71+
stream an extract of the Little Prince by Antoine de Saint-Exupéry, and will not
72+
require any API key or real LLM.
73+
74+
75+
In your configuration file adapt the following line to your needs:
76+
77+
.. code-block:: python
78+
79+
c.TerminalInteractiveShell.llm_provider_class = "examples.auto_suggest_llm.ExampleCompletionProvider"
80+
81+
Configure the provider
82+
----------------------
83+
84+
It the provider needs to be passed parameters at initialization, you can do so
85+
by setting the ``llm_provider_kwargs`` traitlet.
86+
87+
.. code-block:: python
88+
89+
c.TerminalInteractiveShell.llm_provider_kwargs = {"model": "skynet"}
90+
91+
This will depdend on the provider you chose, and you will have to refer to
92+
the provider documentation.
93+
94+
Extra configuration may be needed by setting environment variables, this will
95+
again depend on the provider you chose, and you will have to refer to the
96+
provider documentation.
97+
98+
499

5100
.. _custom_prompts:
6101

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
===============
2-
IPython options
3-
===============
1+
.. _terminal_options:
2+
3+
Terminal options
4+
================
45

56
Any of the options listed here can be set in config files, at the
67
command line, from inside IPython, or using a traitlets :class:`Config` object.
78
See :ref:`setting_config` for details.
89

9-
.. toctree::
10-
11-
terminal
12-
kernel
10+
.. include:: terminal.rst

examples/auto_suggest_llm.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,38 @@
11
"""
2-
This is an example of Fake LLM Completer for IPython
2+
This is an example of Fake LLM Completer for IPython, as
3+
well as example on how to configure IPython for LLMs.
4+
35
8.32 – this is provisional and may change.
46
57
To test this you can run the following command from the root of IPython
68
directory:
79
810
$ ipython --TerminalInteractiveShell.llm_provider_class=examples.auto_suggest_llm.ExampleCompletionProvider
911
10-
Or you can set the value in your config file
12+
Or you can set the value in your config file, which also allows you to set a
13+
keyboard shortcut::
14+
15+
c.TerminalInteractiveShell.llm_provider_class = "examples.auto_suggest_llm.ExampleCompletionProvider"
16+
c.TerminalInteractiveShell.shortcuts = [
17+
{
18+
"new_keys": ["c-q"],
19+
"command": "IPython:auto_suggest.llm_autosuggestion",
20+
"new_filter": "navigable_suggestions & default_buffer_focused",
21+
"create": True,
22+
},
23+
]
24+
1125
12-
c.TerminalInteractiveShell.llm_provider_class="fully.qualified.name.ToYourCompleter"
26+
You can use the following configuration option to::
1327
14-
And at runtime bing for example `ctrl-q` to triger autosugges:
28+
c.TerminalInteractiveShell.llm_constructor_kwargs = {"model_id": "mymodel"}
29+
30+
31+
For convenience and testing you can bind a shortcut at runtime::
1532
1633
In [1]: from examples.auto_suggest_llm import setup_shortcut
1734
...: setup_shortcut('c-q')
1835
19-
2036
"""
2137

2238
import asyncio

0 commit comments

Comments
 (0)