Skip to content

Commit b30c77c

Browse files
authored
Backport PR ipython#14755 on branch 8.x (By default pass current input history to LLMs.) (ipython#14851)
Backport PR ipython#14755: By default pass current input history to LLMs.
2 parents da662c3 + 8605f31 commit b30c77c

File tree

3 files changed

+90
-11
lines changed

3 files changed

+90
-11
lines changed

IPython/terminal/interactiveshell.py

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,7 @@ def _displayhook_class_default(self):
425425
"Default is `'NavigableAutoSuggestFromHistory`'.",
426426
allow_none=True,
427427
).tag(config=True)
428+
_autosuggestions_provider: Any
428429

429430
llm_constructor_kwargs = Dict(
430431
{},
@@ -434,6 +435,31 @@ def _displayhook_class_default(self):
434435
This is used to – for example – set the `model_id`""",
435436
).tag(config=True)
436437

438+
llm_prefix_from_history = DottedObjectName(
439+
"input_history",
440+
help="""\
441+
Fully Qualifed name of a function that takes an IPython history manager and
442+
return a prefix to pass the llm provider in addition to the current buffer
443+
text.
444+
445+
You can use:
446+
447+
- no_prefix
448+
- input_history
449+
450+
As default value. `input_history` (default), will use all the input history
451+
of current IPython session
452+
453+
""",
454+
).tag(config=True)
455+
_llm_prefix_from_history: Any
456+
457+
@observe("llm_prefix_from_history")
458+
def _llm_prefix_from_history_changed(self, change):
459+
name = change.new
460+
self._llm_prefix_from_history = name
461+
self._set_autosuggestions()
462+
437463
llm_provider_class = DottedObjectName(
438464
None,
439465
allow_none=True,
@@ -448,6 +474,7 @@ class to use for the `NavigableAutoSuggestFromHistory` to request
448474
`stream_inline_completions`
449475
""",
450476
).tag(config=True)
477+
_llm_provider_class: Any = None
451478

452479
@observe("llm_provider_class")
453480
def _llm_provider_class_changed(self, change):
@@ -457,15 +484,12 @@ def _llm_provider_class_changed(self, change):
457484
"TerminalInteractiveShell.llm_provider_class is a provisional"
458485
" API as of IPython 8.32, and may change without warnings."
459486
)
460-
if isinstance(self.auto_suggest, NavigableAutoSuggestFromHistory):
461-
self.auto_suggest._llm_provider = provider_class()
462-
else:
463-
self.log.warn(
464-
"llm_provider_class only has effects when using"
465-
"`NavigableAutoSuggestFromHistory` as auto_suggest."
466-
)
487+
self._llm_provider_class = provider_class
488+
self._set_autosuggestions()
467489

468-
def _set_autosuggestions(self, provider):
490+
def _set_autosuggestions(self, provider=None):
491+
if provider is None:
492+
provider = self.autosuggestions_provider
469493
# disconnect old handler
470494
if self.auto_suggest and isinstance(
471495
self.auto_suggest, NavigableAutoSuggestFromHistory
@@ -477,14 +501,35 @@ def _set_autosuggestions(self, provider):
477501
self.auto_suggest = AutoSuggestFromHistory()
478502
elif provider == "NavigableAutoSuggestFromHistory":
479503
# LLM stuff are all Provisional in 8.32
480-
if self.llm_provider_class:
481-
llm_provider_constructor = import_item(self.llm_provider_class)
504+
if self._llm_provider_class:
505+
llm_provider_constructor = import_item(self._llm_provider_class)
482506
llm_provider = llm_provider_constructor(**self.llm_constructor_kwargs)
483507
else:
484508
llm_provider = None
485509
self.auto_suggest = NavigableAutoSuggestFromHistory()
486510
# Provisinal in 8.32
487511
self.auto_suggest._llm_provider = llm_provider
512+
513+
name = self.llm_prefix_from_history
514+
515+
if name == "no_prefix":
516+
print("set tofun1", self.llm_prefix_from_history)
517+
518+
def no_prefix(history_manager):
519+
return ""
520+
521+
fun = no_prefix
522+
523+
elif name == "input_history":
524+
525+
def input_history(history_manager):
526+
return "\n".join([s[2] for s in history_manager.get_range()]) + "\n"
527+
528+
fun = input_history
529+
530+
else:
531+
fun = import_item(name)
532+
self.auto_suggest._llm_prefixer = fun
488533
else:
489534
raise ValueError("No valid provider.")
490535
if self.pt_app:

IPython/terminal/shortcuts/auto_suggest.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory):
178178
# This is the instance of the LLM provider from jupyter-ai to which we forward the request
179179
# to generate inline completions.
180180
_llm_provider: Any | None
181+
_llm_prefixer: callable = lambda self, x: "wrong"
181182

182183
def __init__(self):
183184
super().__init__()
@@ -386,9 +387,13 @@ async def _trigger_llm_core(self, buffer: Buffer):
386387
except ModuleNotFoundError:
387388
jai_models = None
388389

390+
hm = buffer.history.shell.history_manager
391+
prefix = self._llm_prefixer(hm)
392+
print(prefix)
393+
389394
request = jai_models.InlineCompletionRequest(
390395
number=0,
391-
prefix=buffer.document.text,
396+
prefix=prefix + buffer.document.text,
392397
suffix="",
393398
mime="text/x-python",
394399
stream=True,

examples/auto_suggest_llm.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,35 @@
3333
In [1]: from examples.auto_suggest_llm import setup_shortcut
3434
...: setup_shortcut('c-q')
3535
36+
37+
Getting access to history content
38+
---------------------------------
39+
40+
This uses the same providers as Jupyter AI, In JupyterAI, providers may get
41+
access to the current notebook content to pass as to the LLM as context.
42+
43+
Here Jupyter AI documents how to get such context.
44+
45+
https://jupyter-ai.readthedocs.io/en/latest/developers/index.html
46+
47+
48+
When reusing these models you may want to pass them more context as well in
49+
IPython to do so you can set the
50+
`c.TerminalInteractiveShell.llm_prefix_from_history` to `"no_prefix"`,
51+
`"input_history"` or a fully qualified name of a function that will get
52+
imported, get passed a `HistoryManager`, and return a prefix to be added the LLM
53+
context.
54+
55+
56+
For more flexibility, subclass the provider, and access the hisotory of IPython
57+
via:
58+
59+
```
60+
ip = get_ipython()
61+
hm = ip.history_manager()
62+
hm.get_range(...) # will let you select how many input/output... etc.
63+
```
64+
3665
"""
3766

3867
import asyncio

0 commit comments

Comments
 (0)