-
Notifications
You must be signed in to change notification settings - Fork 46
Add version for Llamaindex with FastApi and Auth0 #10
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
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,114 @@ | ||
# Assistant0: An AI Personal Assistant Secured with Auth0 - LlamaIndex Python Version | ||
# Assistant0: An AI Personal Assistant Secured with Auth0 - Llamaindex Python/FastAPI Version | ||
|
||
Assistant0 an AI personal assistant that consolidates your digital life by dynamically accessing multiple tools to help you stay organized and efficient. | ||
|
||
## About the template | ||
|
||
This template scaffolds an Auth0 + LlamaIndex.js + React JS starter app. It mainly uses the following libraries: | ||
|
||
- [LlamaIndex's Python framework](https://docs.llamaindex.ai/en/stable/#introduction) | ||
- The [Auth0 AI SDK](https://github.com/auth0-lab/auth0-ai-python) and [Auth0 FastAPI SDK](https://github.com/auth0/auth0-fastapi) to secure the application and call third-party APIs. | ||
- [Auth0 FGA](https://auth0.com/fine-grained-authorization) to define fine-grained access control policies for your tools and RAG pipelines. | ||
|
||
## 🚀 Getting Started | ||
|
||
First, clone this repo and download it locally. | ||
|
||
```bash | ||
git clone https://github.com/auth0-samples/auth0-assistant0.git | ||
cd auth0-assistant0/py-llamaindex | ||
``` | ||
|
||
The project is divided into two parts: | ||
|
||
- `backend/` contains the backend code for the Web app and API written in Python using FastAPI. | ||
- `frontend/` contains the frontend code for the Web app written in React as a Vite SPA. | ||
|
||
### Setup the backend | ||
|
||
```bash | ||
cd backend | ||
``` | ||
|
||
Next, you'll need to set up environment variables in your repo's `.env` file. Copy the `.env.example` file to `.env`. | ||
|
||
To start with the basic examples, you'll just need to add your OpenAI API key and Auth0 credentials. | ||
|
||
- To start with the examples, you'll just need to add your OpenAI API key and Auth0 credentials for the Web app. | ||
- You can setup a new Auth0 tenant with an Auth0 Web App and Token Vault following the Prerequisites instructions [here](https://auth0.com/ai/docs/call-others-apis-on-users-behalf). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we describe some place how you are configuring this app with Auth? E.g. "Create an API..", "Create a SPA application"... etc. I'm having trouble following this at the moment, and it's quite important as far as what auth flow we want to use. If going for an Auth0 API/resource server for the Fast API + embedded llamaindex agent, and a SPA application for the client, we'll like want to make use of the new access token / token vault flow. (more like this example w/ a SPA: auth0/docs-v2#39) If going for a FastAPI (app) (Regular Web App for the Auth0 client) like the existing example here, and an embedded llamaindex agent, we'll likely be fine w/ the existing refresh token / token vault exchange flow. If going for a FastAPI (app) (Regular Web App for the Auth0 client) like the existing example here, and an external llamaindex agent/external LangGraph server, we'll likely want access token / token vault exchange flow (and the AT provided to Langgraph server). |
||
- An Auth0 FGA account, you can create one [here](https://dashboard.fga.dev). Add the FGA store ID, client ID, client secret, and API URL to the `.env` file. | ||
|
||
Next, install the required packages using your preferred package manager, e.g. uv: | ||
|
||
```bash | ||
uv sync | ||
``` | ||
|
||
Now you're ready to start the database: | ||
|
||
```bash | ||
# start the postgres database | ||
docker compose up -d | ||
``` | ||
|
||
Initialize FGA store: | ||
|
||
```bash | ||
source .venv/bin/activate | ||
python -m app.core.fga_init | ||
``` | ||
|
||
Now you're ready to run the development server: | ||
|
||
```bash | ||
source .venv/bin/activate | ||
uv run uvicorn app.main:app --reload | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. isn't is |
||
# fastapi dev app/main.py | ||
``` | ||
|
||
### Start the frontend server | ||
|
||
Rename `.env.example` file to `.env` in the `frontend` directory. | ||
|
||
Finally, you can start the frontend server in another terminal: | ||
|
||
```bash | ||
cd frontend | ||
cp .env.example .env # Copy the `.env.example` file to `.env`. | ||
npm install | ||
npm run dev | ||
``` | ||
|
||
## Auth configuration | ||
|
||
There are two supported setups: | ||
|
||
### 1) SPA (frontend) + FastAPI (backend) + embedded LlamaIndex agent | ||
- In Auth0 Dashboard: | ||
- Create a **SPA Application** for the frontend. | ||
- Create an **API (Resource Server)** for the FastAPI backend. | ||
- (If using Federated Connections like Google Calendar) enable **Token Vault** and grant your backend the right audience/scopes. | ||
- The frontend obtains an **access token** for the API and calls the FastAPI endpoints. | ||
- FastAPI uses **Auth0 FastAPI SDK** to validate the token and manage the session. Tools read access tokens from the session and use Token Vault for federated access. | ||
|
||
### 2) Regular Web App (FastAPI handles browser auth) + embedded LlamaIndex agent | ||
- In Auth0 Dashboard: | ||
- Create a **Regular Web App** for FastAPI. | ||
- (Optional) Create an API if you also expose protected endpoints to SPAs. | ||
- FastAPI handles cookie-based session and federated connections via Token Vault. | ||
- Tools do **not** receive tokens as arguments; they read them from the session or use the federated-connection wrapper. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. super helpful 👍 👍 Seems this will work ok and RT -> AT / token exchange flow is desired if Regular Web App is used, and agent is embedded as we are here. I'm good w/ this if others are 👍 |
||
|
||
This will start a React vite server on port 5173. | ||
|
||
#TODO IMAGE | ||
|
||
Agent configuration lives in `backend/app/agents/assistant0.ts`. From here, you can change the prompt and model, or add other tools and logic. | ||
|
||
## License | ||
|
||
This project is open-sourced under the MIT License - see the [LICENSE](LICENSE) file for details. | ||
|
||
## Author | ||
|
||
This project is built by [Adam W.](https://github.com/AdamWozniewski). | ||
|
||
Coming soon |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
APP_BASE_URL='http://localhost:8000' | ||
API_PREFIX=/api | ||
|
||
AUTH0_SECRET='use [openssl rand -hex 32] to generate a 32 bytes value' | ||
AUTH0_DOMAIN='' | ||
AUTH0_CLIENT_ID='' | ||
AUTH0_CLIENT_SECRET='' | ||
|
||
OPENAI_API_KEY='' | ||
PROVIDER=openai | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this used? |
||
|
||
# Database | ||
DATABASE_URL="postgresql+psycopg://postgres:postgres@localhost:5432/ai_documents_db" | ||
|
||
# Auth0 FGA | ||
FGA_STORE_ID=<your-fga-store-id> | ||
FGA_CLIENT_ID=<your-fga-store-client-id> | ||
FGA_CLIENT_SECRET=<your-fga-store-client-secret> | ||
FGA_API_URL=https://api.xxx.fga.dev | ||
FGA_API_AUDIENCE=https://api.xxx.fga.dev/ | ||
|
||
# Shop API URL (Optional) | ||
# SHOP_API_URL="http://localhost:3001/api/shop" | ||
# SHOP_API_AUDIENCE="https://api.shop-online-demo.com" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
__pycache__ | ||
app.egg-info | ||
*.pyc | ||
.mypy_cache | ||
.coverage | ||
htmlcov | ||
.cache | ||
.venv | ||
.env | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
3.13 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# Setup the backend | ||
|
||
```bash | ||
cd backend | ||
``` | ||
|
||
You'll need to set up environment variables in your repo's `.env` file. Copy the `.env.example` file to `.env`. | ||
|
||
To start with the basic examples, you'll just need to add your OpenAI API key and Auth0 credentials. | ||
|
||
- To start with the examples, you'll just need to add your OpenAI API key and Auth0 credentials for the Web app. | ||
- You can setup a new Auth0 tenant with an Auth0 Web App and Token Vault following the Prerequisites instructions [here](https://auth0.com/ai/docs/call-others-apis-on-users-behalf). | ||
- An Auth0 FGA account, you can create one [here](https://dashboard.fga.dev). Add the FGA store ID, client ID, client secret, and API URL to the `.env` file. | ||
|
||
Next, install the required packages using your preferred package manager, e.g. uv: | ||
|
||
```bash | ||
uv sync | ||
``` | ||
|
||
Now you're ready to start and migrate the database: | ||
|
||
```bash | ||
# start the postgres database | ||
docker compose up -d | ||
``` | ||
|
||
Initialize FGA store: | ||
|
||
```bash | ||
source .venv/bin/activate | ||
python -m app.core.fga_init | ||
``` | ||
|
||
Now you're ready to run the development server: | ||
|
||
```bash | ||
source .venv/bin/activate | ||
uv run uvicorn app.main:app --reload | ||
# fastapi dev app/main.py | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
from __future__ import annotations | ||
from llama_index.core.agent import ReActAgentWorker | ||
from llama_index.llms.openai import OpenAI | ||
|
||
from app.agents.tools.user_info_li import get_user_info_li | ||
from app.agents.tools.google_calendar_li import list_upcoming_events_li | ||
from app.agents.tools.shop_online_li import shop_online_li | ||
from app.agents.tools.context_docs_li import get_context_docs_li | ||
|
||
llm = OpenAI(model="gpt-4.1-mini", temperature=0.2) | ||
|
||
tools = [ | ||
get_user_info_li, | ||
list_upcoming_events_li, | ||
shop_online_li, | ||
get_context_docs_li, | ||
] | ||
|
||
agent = ReActAgentWorker.from_tools( | ||
tools=tools, | ||
llm=llm, | ||
verbose=True, | ||
system_prompt=( | ||
"You are a personal assistant named Assistant0. " | ||
"Use tools when helpful; prefer get_context_docs for knowledge base queries. " | ||
"Render email-like bodies as markdown (no code fences)." | ||
), | ||
).as_agent() | ||
Comment on lines
+19
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how are you finding the interrupt support w/ this ReActAgent worker, and compatibility w/ langgraph's It seems a simpler use case may also be like this one. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fwiw, i am finding a lot of gaps w/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
from __future__ import annotations | ||
from typing import List | ||
|
||
from llama_index.core.tools import FunctionTool | ||
from pydantic import BaseModel | ||
|
||
from app.core.rag_li import retrieve_nodes | ||
from app.core.fga import authorization_manager | ||
|
||
|
||
class GetContextDocsSchema(BaseModel): | ||
question: str | ||
|
||
async def get_context_docs_li_fn(question: str, user_email: str) -> str: | ||
nodes = retrieve_nodes(question, top_k=12) | ||
|
||
allowed: List[str] = [] | ||
for n in nodes: | ||
doc_id = n.metadata.get("document_id") | ||
if not doc_id: | ||
continue | ||
|
||
can_view = await authorization_manager.check_relation( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we align with other samples and use the FGARetriver https://github.com/auth0-samples/auth0-assistant0/blob/main/py-langchain/backend/app/agents/tools/context_docs.py#L32 |
||
user=user_email, doc_id=doc_id, relation="can_view" | ||
) | ||
if can_view: | ||
allowed.append(n.get_content(metadata_mode="none")) | ||
|
||
if not allowed: | ||
return "I couldn't find any documents you are allowed to view." | ||
return "\n\n".join(allowed) | ||
|
||
|
||
get_context_docs_li = FunctionTool.from_defaults( | ||
name="get_context_docs", | ||
description="Retrieve documents from the knowledge base (LlamaIndex + FGA).", | ||
fn=get_context_docs_li_fn, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
from __future__ import annotations | ||
import datetime, json | ||
from google.oauth2.credentials import Credentials | ||
from googleapiclient.discovery import build | ||
from llama_index.core.tools import FunctionTool | ||
from auth0_ai_llamaindex.federated_connections import ( | ||
with_federated_connection, | ||
get_access_token_for_connection, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Believe this library is currently using "refresh_token" exchange. We should enhance this library in the core to support subject_token_type This will likely be useful in the case that LangGraph server is hosted separately. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ^^ we have a separate ticket for this effort this sprint. If we keep the langgraph agent embedded, the RT flow here now is fine. If the langgraph server / agent is separate server, my current assumption is we should use AT flow and add that support to the core. |
||
) | ||
|
||
async def _list_events() -> str: | ||
google_access_token = get_access_token_for_connection() | ||
if not google_access_token: | ||
return "Authorization required to access the Federated Connection API" | ||
|
||
service = build("calendar", "v3", credentials=Credentials(google_access_token)) | ||
events = (service.events() | ||
.list( | ||
calendarId="primary", | ||
timeMin=datetime.datetime.utcnow().isoformat() + "Z", | ||
timeMax=(datetime.datetime.utcnow() + datetime.timedelta(days=7)).isoformat() + "Z", | ||
maxResults=5, | ||
singleEvents=True, | ||
orderBy="startTime", | ||
) | ||
.execute() | ||
.get("items", []) | ||
) | ||
|
||
return json.dumps([ | ||
{"summary": e.get("summary", "(no title)"), | ||
"start": e["start"].get("dateTime", e["start"].get("date"))} | ||
for e in events | ||
]) | ||
|
||
list_upcoming_events_li = with_federated_connection( | ||
FunctionTool.from_defaults( | ||
name="list_upcoming_events", | ||
description="List upcoming events from the user's Google Calendar.", | ||
fn=_list_events, | ||
), | ||
connection="google-oauth2", | ||
scopes=["https://www.googleapis.com/auth/calendar.events"], | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from __future__ import annotations | ||
from typing import Dict, Any | ||
from llama_index.core.tools import FunctionTool | ||
|
||
async def _shop_online(product: str, quantity: int) -> Dict[str, Any]: | ||
return {"ok": True, "message": f"Would buy {quantity} x {product} (demo stub)"} | ||
|
||
shop_online_li = FunctionTool.from_defaults( | ||
name="shop_online", | ||
description="Demo purchase tool (stub).", | ||
fn=_shop_online, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
from __future__ import annotations | ||
import httpx | ||
from llama_index.core.tools import FunctionTool | ||
from app.core.config import settings | ||
from app.core.auth import auth_client | ||
|
||
async def _get_user_info() -> str: | ||
if not sess: | ||
return "There is no user logged in." | ||
|
||
access_token = sess.get("token_sets", [{}])[0].get("access_token") | ||
if not access_token: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not get access token from user session |
||
return "There is no user logged in." | ||
|
||
async with httpx.AsyncClient() as client: | ||
r = await client.get( | ||
f"https://{settings.AUTH0_DOMAIN}/userinfo", | ||
headers={"Authorization": f"Bearer {access_token}"}, | ||
) | ||
return f"User information: {r.json()}" if r.status_code == 200 else "I couldn't verify your identity" | ||
|
||
get_user_info_li = FunctionTool.from_defaults( | ||
name="get_user_info", | ||
description="Get information about the current logged in user.", | ||
fn=_get_user_info, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
from fastapi import APIRouter | ||
from app.api.routes.agent_li import agent_router | ||
from app.api.routes.documents import documents_router | ||
from app.core.auth import auth_router | ||
|
||
api_router = APIRouter() | ||
|
||
api_router.include_router(agent_router) | ||
api_router.include_router(auth_router, tags=["auth"]) | ||
api_router.include_router(documents_router) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from __future__ import annotations | ||
from fastapi import APIRouter, Depends, Request | ||
from fastapi.responses import StreamingResponse, JSONResponse | ||
from app.core.auth import auth_client | ||
from app.agents.assistant0_li import agent | ||
|
||
agent_router = APIRouter(prefix="/agent", tags=["agent"]) | ||
|
||
@agent_router.post("/chat") | ||
async def chat(request: Request, auth_session=Depends(auth_client.require_session)): | ||
try: | ||
body = await request.json() | ||
query: str = body.get("input") or body.get("message") or "" | ||
stream = agent.stream_chat(query) | ||
|
||
async def gen(): | ||
async for ev in stream: | ||
if token := ev.get("delta"): | ||
yield token | ||
return StreamingResponse(gen(), media_type="text/plain") | ||
|
||
except Exception as e: | ||
return JSONResponse(status_code=500, content={"error": str(e)}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we also highlight that we are using a lot of Langgraph libraries / sdks w/ this example now? Or is that still t.b.d.?
If we go w/ Langgraph, just wanting to call it out better in README. Also agree w/ @deepu105 's feedback, that if we already have a langgraph sample, capturing some of the additional things in the existing example:
https://github.com/auth0-samples/auth0-assistant0/tree/main/py-langchain