Reputation: 41
My backend is running langchain routes with certain runnables. I want to be able to return an http exception like "404 Not found" to the frontend, but when I raise it with with raise HTTPException(status_code=404, detail="Item not found")
a CancelledException is created and the frontend recieves "internal server error". How can I pull this off?
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "/my-code-path/.venv/lib/python3.10/site-packages/sse_starlette/sse.py", line 269, in __call__
await wrap(partial(self.listen_for_disconnect, receive))
File "/my-code-path/.venv/lib/python3.10/site-packages/sse_starlette/sse.py", line 258, in wrap
await func()
File "/my-code-path/.venv/lib/python3.10/site-packages/sse_starlette/sse.py", line 215, in listen_for_disconnect
message = await receive()
File "/my-code-path/.venv/lib/python3.10/site-packages/uvicorn/protocols/http/httptools_impl.py", line 596, in receive
await self.message_event.wait()
File "/usr/lib/python3.10/asyncio/locks.py", line 214, in wait
await fut
asyncio.exceptions.CancelledError: Cancelled by cancel scope 7fba499a8490
During handling of the above exception, another exception occurred:
+ Exception Group Traceback (most recent call last):
| File "/my-code-path/.venv/lib/python3.10/site-packages/uvicorn/protocols/http/httptools_impl.py", line 435, in run_asgi
| result = await app( # type: ignore[func-returns-value]
| File "/my-code-path/.venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 78, in __call__
| return await self.app(scope, receive, send)
| File "/my-code-path/.venv/lib/python3.10/site-packages/fastapi/applications.py", line 276, in __call__
| await super().__call__(scope, receive, send)
| File "/my-code-path/.venv/lib/python3.10/site-packages/starlette/applications.py", line 122, in __call__
| await self.middleware_stack(scope, receive, send)
| File "/my-code-path/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 184, in __call__
| raise exc
| File "/my-code-path/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 162, in __call__
| await self.app(scope, receive, _send)
| File "/my-code-path/.venv/lib/python3.10/site-packages/starlette/middleware/cors.py", line 91, in __call__
| await self.simple_response(scope, receive, send, request_headers=headers)
| File "/my-code-path/.venv/lib/python3.10/site-packages/starlette/middleware/cors.py", line 146, in simple_response
| await self.app(scope, receive, send)
| File "/my-code-path/.venv/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 79, in __call__
| raise exc
| File "/my-code-path/.venv/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 68, in __call__
| await self.app(scope, receive, sender)
| File "/my-code-path/.venv/lib/python3.10/site-packages/fastapi/middleware/asyncexitstack.py", line 21, in __call__
| raise e
| File "/my-code-path/.venv/lib/python3.10/site-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__
| await self.app(scope, receive, send)
| File "/my-code-path/.venv/lib/python3.10/site-packages/starlette/routing.py", line 718, in __call__
| await route.handle(scope, receive, send)
| File "/my-code-path/.venv/lib/python3.10/site-packages/starlette/routing.py", line 276, in handle
| await self.app(scope, receive, send)
| File "/my-code-path/.venv/lib/python3.10/site-packages/starlette/routing.py", line 69, in app
| await response(scope, receive, send)
| File "/my-code-path/.venv/lib/python3.10/site-packages/sse_starlette/sse.py", line 255, in __call__
| async with anyio.create_task_group() as task_group:
| File "/my-code-path/.venv/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 680, in __aexit__
| raise BaseExceptionGroup(
| exceptiongroup.ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/my-code-path/.venv/lib/python3.10/site-packages/sse_starlette/sse.py", line 258, in wrap
| await func()
| File "/my-code-path/.venv/lib/python3.10/site-packages/sse_starlette/sse.py", line 245, in stream_response
| async for data in self.body_iterator:
| File "/my-code-path/.venv/lib/python3.10/site-packages/langserve/api_handler.py", line 1121, in _stream
| async for chunk in self._runnable.astream(
| File "/my-code-path/.venv/lib/python3.10/site-packages/langchain_core/runnables/base.py", line 4745, in astream
| async for item in self.bound.astream(
| File "/my-code-path/.venv/lib/python3.10/site-packages/langchain_core/runnables/base.py", line 2898, in astream
| async for chunk in self.atransform(input_aiter(), config, **kwargs):
| File "/my-code-path/.venv/lib/python3.10/site-packages/langchain_core/runnables/base.py", line 2881, in atransform
| async for chunk in self._atransform_stream_with_config(
| File "/my-code-path/.venv/lib/python3.10/site-packages/langchain_core/runnables/base.py", line 1972, in _atransform_stream_with_config
| chunk = cast(Output, await py_anext(iterator))
| File "/my-code-path/.venv/lib/python3.10/site-packages/langchain_core/runnables/base.py", line 2851, in _atransform
| async for output in final_pipeline:
| File "/my-code-path/.venv/lib/python3.10/site-packages/langchain_core/runnables/base.py", line 4178, in atransform
| async for output in self._atransform_stream_with_config(
| File "/my-code-path/.venv/lib/python3.10/site-packages/langchain_core/runnables/base.py", line 1972, in _atransform_stream_with_config
| chunk = cast(Output, await py_anext(iterator))
| File "/my-code-path/.venv/lib/python3.10/site-packages/langchain_core/runnables/base.py", line 4147, in _atransform
| output = await acall_func_with_variable_args(
| File "/my-code-path/.venv/lib/python3.10/site-packages/langchain_core/runnables/base.py", line 4122, in f
| return await run_in_executor(config, func, *args, **kwargs)
| File "/my-code-path/.venv/lib/python3.10/site-packages/langchain_core/runnables/config.py", line 547, in run_in_executor
| return await asyncio.get_running_loop().run_in_executor(
| File "/usr/lib/python3.10/concurrent/futures/thread.py", line 58, in run
| result = self.fn(*self.args, **self.kwargs)
| File "/my-code-path/.venv/lib/python3.10/site-packages/langchain_core/runnables/base.py", line 4116, in func
| return call_func_with_variable_args(
| File "/my-code-path/.venv/lib/python3.10/site-packages/langchain_core/runnables/config.py", line 380, in call_func_with_variable_args
| return func(input, **kwargs) # type: ignore[call-arg]
| File "/my-code-path/src/chains/forms/refine_chain.py", line 65, in get_refined_runnable_map
| raise HTTPException(status_code=404, detail="Item not found")
| fastapi.exceptions.HTTPException
+------------------------------------
I tried searching for a way to somehow catch that exception but the runnable is directly inserted into the "add_routes" function so I can't figure out how to do it, since any exception inside the runnable raises the "CancelledException".
refine_chain = RefineChain(custom_app, tool_name)
add_routes(
app,
refine_chain.get_runnable().with_config({"tags": [custom_app]}),
enabled_endpoints=["stream"],
path=f"/{custom_app}/{tool_name}/refine",
config_keys=["configurable"],
dependencies=[Depends(get_current_active_username)],
)
Upvotes: 1
Views: 353
Reputation: 1
The current implementation of LangServe has a hardcoded error handling for the stream method:
https://github.com/langchain-ai/langserve/blob/v0.3.0/langserve/api_handler.py#L1210-L1219
There is an open issue in the LangServe repository to address this limitation: https://github.com/langchain-ai/langserve/issues/481
So, the client will always receive the same "Internal Server Error" message.
Upvotes: 0
Reputation: 414
I usually do manage the exceptions through the exception_handler
-s, so it can scale more when the project grows, here is how I would implement handling the asyncio.exceptions.CancelledError
that your code is raising:
@app.exception_handler(asyncio.exceptions.CancelledError)
async def http_exception_handler(req: Request, exc: RequestValidationError) -> Response:
error_message = 'Bad Request'
return JSONResponse(
status_code=400,
content={"message": error_message},
)
Note: app
is the app = FastAPI(...)
You can customise the JSONResponse
however you like it, put 404
and whatever message you want.
Upvotes: 0