Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom FastAPI routes no longer work #1819

Open
s0l4r opened this issue Jan 28, 2025 · 4 comments
Open

Custom FastAPI routes no longer work #1819

s0l4r opened this issue Jan 28, 2025 · 4 comments
Labels
backend Pertains to the Python backend. bug Something isn't working needs-triage

Comments

@s0l4r
Copy link

s0l4r commented Jan 28, 2025

Describe the bug
After upgrade from 2.0.2 to 2.0.603 I cannot get custom routes to work, for example https://github.com/Chainlit/cookbook/tree/main/custom-frontend/backend. I've been trying to create the custom endpoint /custom-auth, but the route is never reached.

I believe this may have been introduced in https://github.com/Chainlit/chainlit/pull/1723/files?diff=split&w=0

The custom route is now overwritten of routes by the chainlit catch-all route (e.g., /{full_path:path}).

INFO:     127.0.0.1:62200 - "GET /custom-auth HTTP/1.1" 200 OK
Intercepted path: custom-auth

I modified server.py to check if my custom route is activated, but it's caught by the catch all route:

@router.get("/{full_path:path}")
async def serve(full_path: str, request: Request):
    """Serve the UI files."""
    print(f"Intercepted path: {full_path}")
    html_template = get_html_template(request.scope["root_path"])
    response = HTMLResponse(content=html_template, status_code=200)

    return response

This results in that the default html template is being rendered when I try to access /custom-auth.

To Reproduce
Steps to reproduce the behavior:

Run the example at https://github.com/Chainlit/cookbook/tree/main/custom-frontend/backend with custom auth.

I tried starting the app using the new root-path parameter, but still doesn't work (also tried without --root-path set)

"args": [
       "run",
        "--root-path=/chainlit",
        "-h",
        "app.py"
]

Expected behavior
Custom routes should not be overwritten by Chainlit.

Desktop (please complete the following information):

  • OS: MacOS
  • Browser Chrome
  • Version 131.0.6778.265

Additional context

The error might be in how the sub application is mounted, and the changes done in #1723

mount_chainlit(app=app, target="cl-app.py", path="/chainlit"
@dosubot dosubot bot added backend Pertains to the Python backend. bug Something isn't working labels Jan 28, 2025
@willydouhard
Copy link
Collaborator

I updated the example, the signature of the _authenticate_user function changed and now asks for the http request object.

However, I notice that visiting the root of the app redirect to the Chainlit app, which I would not expect since it is mounted on /chainlit. Visiting it on /chainlit or any other endpoint actually also seems to work. Thoughts @dokterbob ?

@s0l4r
Copy link
Author

s0l4r commented Jan 29, 2025

I've managed to work around it, but it isn't pretty. This is what I did:

create new init_chainlit.py:

from fastapi import Request
from chainlit.server import app as chainlit_app # I have to use the main app, the custom app with routes is overwritten
from starlette.middleware.cors import CORSMiddleware
from chainlit.server import _authenticate_user
from chainlit.user import User

# Add CORS middleware
chainlit_app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:4200"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Add a custom route
@chainlit_app.get("/custom-auth")
async def custom_auth(request: Request):
    # Verify the user's identity with custom logic.
    user = User(identifier="Test User")
    return await _authenticate_user(request, user)

# Reorder routes: Move catch-all route to the end
catch_all_route = None
for route in chainlit_app.routes:
    if route.path == "/{full_path:path}":
        catch_all_route = route

if catch_all_route:
    chainlit_app.routes.remove(catch_all_route)
    chainlit_app.routes.append(catch_all_route)  # Add it back to the end

# Debugging: Print all registered routes
for route in chainlit_app.routes:
    print(f"Path: {route.path}, Name: {route.name}")

create new main.py:

import init_chainlit  # Ensure custom routes and middleware are applied, this is done to inject the custom routes in the main app
from chainlit.cli import run_chainlit

if __name__ == "__main__":
    target = "cl-app.py"
    run_chainlit(target)

Without reordering the request to /custom-auth is caught by Path: /{full_path:path}:

Path: /avatars/{avatar_id:str}, Name: get_avatar
Path: /, Name: status_check
Path: /{full_path:path}, Name: serve
Path: /custom-auth, Name: custom_auth

With reordering fix:

Path: /avatars/{avatar_id:str}, Name: get_avatar
Path: /, Name: status_check
Path: /custom-auth, Name: custom_auth < this works now
Path: /{full_path:path}, Name: serve

I know this isn't the correct way of doing it. The custom app in the example you have, mount_chainlit app is overridden in chainlit/cli/__init__.py in

def run_chainlit(target: str):
    from chainlit.server import app

So all the custom routes are added after the catch all route. That's why I had to inject it in the main app, and reorder the custom route.

To start the app I now run main.py which uses import init_chainlit, which runs the custom reordering.

If you have another recommendation on how to do it without my fix, I'll test it asap. Took me almost all day yesterday going through why the latest update didn't work with my custom route. But I'm happy to have some more insight how Chainlit works.

Thanks for a great product!

@willydouhard
Copy link
Collaborator

Thank for the insight, we'll fix this so you can remove the workaround!

@dokterbob
Copy link
Collaborator

I've managed to work around it, but it isn't pretty. This is what I did:

create new init_chainlit.py:

from fastapi import Request
from chainlit.server import app as chainlit_app # I have to use the main app, the custom app with routes is overwritten
from starlette.middleware.cors import CORSMiddleware
from chainlit.server import _authenticate_user
from chainlit.user import User

# Add CORS middleware
chainlit_app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:4200"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Add a custom route
@chainlit_app.get("/custom-auth")
async def custom_auth(request: Request):
    # Verify the user's identity with custom logic.
    user = User(identifier="Test User")
    return await _authenticate_user(request, user)

# Reorder routes: Move catch-all route to the end
catch_all_route = None
for route in chainlit_app.routes:
    if route.path == "/{full_path:path}":
        catch_all_route = route

if catch_all_route:
    chainlit_app.routes.remove(catch_all_route)
    chainlit_app.routes.append(catch_all_route)  # Add it back to the end

# Debugging: Print all registered routes
for route in chainlit_app.routes:
    print(f"Path: {route.path}, Name: {route.name}")

create new main.py:

import init_chainlit  # Ensure custom routes and middleware are applied, this is done to inject the custom routes in the main app
from chainlit.cli import run_chainlit

if __name__ == "__main__":
    target = "cl-app.py"
    run_chainlit(target)

Without reordering the request to /custom-auth is caught by Path: /{full_path:path}:

Path: /avatars/{avatar_id:str}, Name: get_avatar
Path: /, Name: status_check
Path: /{full_path:path}, Name: serve
Path: /custom-auth, Name: custom_auth

With reordering fix:

Path: /avatars/{avatar_id:str}, Name: get_avatar
Path: /, Name: status_check
Path: /custom-auth, Name: custom_auth < this works now
Path: /{full_path:path}, Name: serve

I know this isn't the correct way of doing it. The custom app in the example you have, mount_chainlit app is overridden in chainlit/cli/__init__.py in

def run_chainlit(target: str):
    from chainlit.server import app

So all the custom routes are added after the catch all route. That's why I had to inject it in the main app, and reorder the custom route.

To start the app I now run main.py which uses import init_chainlit, which runs the custom reordering.

If you have another recommendation on how to do it without my fix, I'll test it asap. Took me almost all day yesterday going through why the latest update didn't work with my custom route. But I'm happy to have some more insight how Chainlit works.

Thanks for a great product!

@s0l4r I think you got the strategy for adding custom routes wrong! Or, it changed, or something. ;)

The recommended strategy is now to mount chainlit into a regular FastAPI app. The FastAPI app will do as it should and it's possible to mix custom endpoints (mounted to the FastAPI app).

Unfortunately, the example isn't yet merged but here you go: https://github.com/Chainlit/cookbook/tree/d1c209e2dcdc7d304ae0364d0389688d6ad5a024/reverse_proxy

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend Pertains to the Python backend. bug Something isn't working needs-triage
Projects
None yet
Development

No branches or pull requests

3 participants