diff --git a/packages/jupyter-ai/jupyter_ai/completions/handlers/base.py b/packages/jupyter-ai/jupyter_ai/completions/handlers/base.py index 5d2e16e1c..cf07550bc 100644 --- a/packages/jupyter-ai/jupyter_ai/completions/handlers/base.py +++ b/packages/jupyter-ai/jupyter_ai/completions/handlers/base.py @@ -129,9 +129,10 @@ async def handle_exc(self, e: Exception, request: InlineCompletionRequest): `handle_stream_request()`. This base class provides a default implementation, which may be overridden by subclasses. """ + title = e.args[0] if e.args else "Exception" error = CompletionError( type=e.__class__.__name__, - title=e.args[0] if e.args else "Exception", + title=title, traceback=traceback.format_exc(), ) self.reply( diff --git a/packages/jupyter-ai/jupyter_ai/tests/completions/test_handlers.py b/packages/jupyter-ai/jupyter_ai/tests/completions/test_handlers.py index c043fd8a5..b3d00159e 100644 --- a/packages/jupyter-ai/jupyter_ai/tests/completions/test_handlers.py +++ b/packages/jupyter-ai/jupyter_ai/tests/completions/test_handlers.py @@ -21,15 +21,22 @@ class MockProvider(BaseProvider, FakeListLLM): name = "My Provider" model_id_key = "model" models = ["model"] + raise_exc: bool = False def __init__(self, **kwargs): if "responses" not in kwargs: kwargs["responses"] = ["Test response"] super().__init__(**kwargs) + async def _acall(self, *args, **kwargs): + if self.raise_exc: + raise Exception("Test exception") + else: + return super()._call(*args, **kwargs) + class MockCompletionHandler(DefaultInlineCompletionHandler): - def __init__(self, lm_provider=None, lm_provider_params=None): + def __init__(self, lm_provider=None, lm_provider_params=None, raise_exc=False): self.request = HTTPServerRequest() self.application = Application() self.messages = [] @@ -50,10 +57,6 @@ def reply( ) -> None: self.messages.append(message) - async def handle_exc(self, e: Exception, _request: InlineCompletionRequest): - # raise all exceptions during testing rather - raise e - @fixture def inline_handler() -> MockCompletionHandler: @@ -191,3 +194,21 @@ async def test_handle_stream_request(): assert third.type == "stream" assert third.response.insertText == "test" assert third.done is True + + +async def test_handle_request_with_error(inline_handler): + inline_handler = MockCompletionHandler( + lm_provider=MockProvider, + lm_provider_params={ + "model_id": "model", + "responses": ["test"], + "raise_exc": True, + }, + ) + dummy_request = InlineCompletionRequest( + number=1, prefix="", suffix="", mime="", stream=True + ) + await inline_handler.on_message(json.dumps(dict(dummy_request))) + await inline_handler.tasks[0] + error = inline_handler.messages[-1].model_dump().get("error", None) + assert error is not None diff --git a/packages/jupyter-ai/src/completions/provider.ts b/packages/jupyter-ai/src/completions/provider.ts index 80c199f5a..f4d959cb7 100644 --- a/packages/jupyter-ai/src/completions/provider.ts +++ b/packages/jupyter-ai/src/completions/provider.ts @@ -122,15 +122,19 @@ export class JaiInlineProvider label: 'Show Traceback', callback: () => { showErrorMessage('Inline completion failed on the server side', { - message: error.traceback + message: `${error.title}\n${error.traceback}` }); } } ] }); - throw new Error( - `Inline completion failed: ${error.type}\n${error.traceback}` - ); + const items = [ + { + error: { message: error.title }, + insertText: '' + } + ]; + return { items }; } return result.list; } diff --git a/packages/jupyter-ai/src/completions/types.ts b/packages/jupyter-ai/src/completions/types.ts index 13dad9c8e..f491e5079 100644 --- a/packages/jupyter-ai/src/completions/types.ts +++ b/packages/jupyter-ai/src/completions/types.ts @@ -35,6 +35,7 @@ export namespace AiCompleterService { export type CompletionError = { type: string; traceback: string; + title: string; }; export type InlineCompletionReply = {