Skip to content

Commit fcc41fa

Browse files
authored
Backport PR ipython#14910 on branch 8.x (Eliminate startup delay when slow-starting LLM completion provider is configured) (ipython#14912)
Backport PR ipython#14910: Eliminate startup delay when slow-starting LLM completion provider is configured
2 parents 21d9433 + 576bc55 commit fcc41fa

File tree

5 files changed

+58
-16
lines changed

5 files changed

+58
-16
lines changed

IPython/terminal/interactiveshell.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -502,13 +502,16 @@ def _set_autosuggestions(self, provider=None):
502502
elif provider == "NavigableAutoSuggestFromHistory":
503503
# LLM stuff are all Provisional in 8.32
504504
if self._llm_provider_class:
505-
llm_provider_constructor = import_item(self._llm_provider_class)
506-
llm_provider = llm_provider_constructor(**self.llm_constructor_kwargs)
505+
506+
def init_llm_provider():
507+
llm_provider_constructor = import_item(self._llm_provider_class)
508+
return llm_provider_constructor(**self.llm_constructor_kwargs)
509+
507510
else:
508-
llm_provider = None
511+
init_llm_provider = None
509512
self.auto_suggest = NavigableAutoSuggestFromHistory()
510513
# Provisinal in 8.32
511-
self.auto_suggest._llm_provider = llm_provider
514+
self.auto_suggest._init_llm_provider = init_llm_provider
512515

513516
name = self.llm_prefix_from_history
514517

IPython/terminal/shortcuts/auto_suggest.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -171,16 +171,19 @@ class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory):
171171
# another request.
172172
_llm_task: asyncio.Task | None = None
173173

174-
# This is the instance of the LLM provider from jupyter-ai to which we forward the request
175-
# to generate inline completions.
176-
_llm_provider: Any | None
174+
# This is the constructor of the LLM provider from jupyter-ai
175+
# to which we forward the request to generate inline completions.
176+
_init_llm_provider: Callable | None
177+
178+
_llm_provider_instance: Any | None
177179
_llm_prefixer: Callable = lambda self, x: "wrong"
178180

179181
def __init__(self):
180182
super().__init__()
181183
self.skip_lines = 0
182184
self._connected_apps = []
183-
self._llm_provider = None
185+
self._llm_provider_instance = None
186+
self._init_llm_provider = None
184187
self._request_number = 0
185188

186189
def reset_history_position(self, _: Buffer):
@@ -317,6 +320,16 @@ def _cancel_running_llm_task(self) -> None:
317320
"LLM task not cancelled, does your provider support cancellation?"
318321
)
319322

323+
@property
324+
def _llm_provider(self):
325+
"""Lazy-initialized instance of the LLM provider.
326+
327+
Do not use in the constructor, as `_init_llm_provider` can trigger slow side-effects.
328+
"""
329+
if self._llm_provider_instance is None and self._init_llm_provider:
330+
self._llm_provider_instance = self._init_llm_provider()
331+
return self._llm_provider_instance
332+
320333
async def _trigger_llm(self, buffer) -> None:
321334
"""
322335
This will ask the current llm provider a suggestion for the current buffer.
@@ -325,14 +338,14 @@ async def _trigger_llm(self, buffer) -> None:
325338
"""
326339
# we likely want to store the current cursor position, and cancel if the cursor has moved.
327340
try:
328-
import jupyter_ai.completions.models as jai_models
341+
import jupyter_ai_magics
329342
except ModuleNotFoundError:
330-
jai_models = None
343+
jupyter_ai_magics = None
331344
if not self._llm_provider:
332345
warnings.warn("No LLM provider found, cannot trigger LLM completions")
333346
return
334-
if jai_models is None:
335-
warnings.warn("LLM Completion requires `jupyter_ai` to be installed")
347+
if jupyter_ai_magics is None:
348+
warnings.warn("LLM Completion requires `jupyter_ai_magics` to be installed")
336349

337350
self._cancel_running_llm_task()
338351

@@ -359,7 +372,7 @@ async def _trigger_llm_core(self, buffer: Buffer):
359372
provider to stream it's response back to us iteratively setting it as
360373
the suggestion on the current buffer.
361374
362-
Unlike with JupyterAi, as we do not have multiple cell, the cell id
375+
Unlike with JupyterAi, as we do not have multiple cells, the cell id
363376
is always set to `None`.
364377
365378
We set the prefix to the current cell content, but could also insert the

IPython/terminal/tests/fake_llm.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import asyncio
2+
from time import sleep
23

34
try:
4-
from jupyter_ai_magics import BaseProvider
5+
from jupyter_ai_magics.providers import BaseProvider
56
from langchain_community.llms import FakeListLLM
67
except ImportError:
78

@@ -87,3 +88,16 @@ async def _stream(self, sentence, request_number, token, start_with=""):
8788
reply_to=request_number,
8889
done=True,
8990
)
91+
92+
93+
class SlowStartingCompletionProvider(BaseProvider, FakeListLLM): # type: ignore[misc, valid-type]
94+
id = "slow_provider"
95+
name = "Slow Provider"
96+
model_id_key = "model"
97+
models = ["model_a"]
98+
99+
def __init__(self, **kwargs):
100+
kwargs["responses"] = ["This fake response will be used for completion"]
101+
kwargs["model_id"] = "model_a"
102+
sleep(10)
103+
super().__init__(**kwargs)

IPython/terminal/tests/test_shortcuts.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pytest
2+
import time
23
from IPython.terminal.interactiveshell import PtkHistoryAdapter
34
from IPython.terminal.shortcuts.auto_suggest import (
45
accept,
@@ -68,6 +69,17 @@ async def test_llm_autosuggestion():
6869
assert event.current_buffer.suggestion.text == FIBONACCI[len(text) :]
6970

7071

72+
def test_slow_llm_provider_should_not_block_init():
73+
ip = get_ipython()
74+
provider = NavigableAutoSuggestFromHistory()
75+
ip.auto_suggest = provider
76+
start = time.perf_counter()
77+
ip.llm_provider_class = "tests.fake_llm.SlowStartingCompletionProvider"
78+
end = time.perf_counter()
79+
elapsed = end - start
80+
assert elapsed < 0.1
81+
82+
7183
@pytest.mark.parametrize(
7284
"text, suggestion, expected",
7385
[

examples/auto_suggest_llm.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,13 @@
6868
import textwrap
6969
from typing import Any, AsyncIterable, AsyncIterator
7070

71-
from jupyter_ai.completions.models import (
71+
from jupyter_ai_magics.models.completion import (
7272
InlineCompletionList,
7373
InlineCompletionReply,
7474
InlineCompletionRequest,
7575
InlineCompletionStreamChunk,
7676
)
77-
from jupyter_ai_magics import BaseProvider
77+
from jupyter_ai_magics.providers import BaseProvider
7878
from langchain_community.llms import FakeListLLM
7979

8080

0 commit comments

Comments
 (0)