Skip to content

Commit a5ffd42

Browse files
chore(examples): minor improvements to async-user-confirmation and dependencies updates (#36)
1 parent d01013f commit a5ffd42

File tree

25 files changed

+4174
-3413
lines changed

25 files changed

+4174
-3413
lines changed

.vscode/settings.json

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
{
22
"poetryMonorepo.appendExtraPaths": true,
33
"python.analysis.extraPaths": [
4+
"examples/async-user-confirmation/langchain-examples",
5+
"examples/async-user-confirmation/llama-index-examples",
6+
"examples/async-user-confirmation/sample-api",
7+
"examples/authorization-for-tools/langchain-examples",
8+
"examples/authorization-for-tools/llama-index-examples",
49
"examples/calling-apis/langchain-examples",
510
"examples/calling-apis/llama-index-examples",
6-
"examples/async-user-confirmation/langchain-examples/src",
7-
"examples/async-user-confirmation/llama-index-examples/src",
8-
"examples/async-user-confirmation/sample-api",
9-
"examples/authorization-for-rag/langchain-examples/langchain_rag",
10-
"examples/authorization-for-rag/llama-index-examples/llama_index_rag",
11-
"examples/authorization-for-tools/langchain-examples/src",
12-
"examples/authorization-for-tools/llama-index-examples/src",
13-
"packages/auth0-ai/auth0_ai/auth0_ai",
1411
"packages/auth0-ai-langchain/auth0_ai_langchain/auth0_ai_langchain",
15-
"packages/auth0-ai-llamaindex/auth0_ai_llamaindex/auth0_ai_llamaindex"
12+
"packages/auth0-ai-llamaindex/auth0_ai_llamaindex/auth0_ai_llamaindex",
13+
"packages/auth0-ai/auth0_ai/auth0_ai"
1614
],
1715
"python.testing.pytestArgs": [
1816
"packages/auth0-ai-langchain/tests",

examples/async-user-confirmation/langchain-examples/poetry.lock

Lines changed: 91 additions & 79 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/async-user-confirmation/langchain-examples/pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ httpx = "^0.28.1"
2020
python-dotenv = "^1.0.1"
2121
pydantic = "^2.10.6"
2222
nanoid = "^2.0.0"
23-
langchain-openai = "^0.3.16"
2423
langchain = "^0.3.25"
2524
langchain-core = "^0.3.59"
25+
langchain-openai = "^0.3.16"
2626
langgraph = "^0.4.3"
2727
langgraph-cli = "^0.2.8"
28-
langgraph-api = "^0.2.17"
29-
langgraph-runtime-inmem = "^0.0.9"
28+
langgraph-api = "^0.2.19"
29+
langgraph-runtime-inmem = "^0.0.10"
3030
auth0-ai-langchain = { path = "../../../packages/auth0-ai-langchain", develop = true }
3131

3232
[build-system]

examples/async-user-confirmation/langchain-examples/src/agents/agent.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
from langgraph.prebuilt import ToolNode
44
from langchain_core.messages import BaseMessage, AIMessage
55
from typing import TypedDict, Annotated, Sequence
6-
from tools.trade import trade_tool
7-
from tools.conditional_trade import conditional_trade_tool
86
from langchain_openai import ChatOpenAI
7+
from src.agents.tools.trade import trade_tool
8+
from src.agents.tools.conditional_trade import conditional_trade_tool
99

1010
class State(TypedDict):
1111
messages: Annotated[Sequence[BaseMessage], add_messages]

examples/async-user-confirmation/langchain-examples/src/agents/conditional_trade.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from langgraph.graph.message import add_messages
1111
from langgraph.prebuilt import ToolNode
1212
from src.agents.clients.scheduler import SchedulerClient
13-
from tools.trade import trade_tool
13+
from src.agents.tools.trade import trade_tool
1414

1515
class ConditionalTrade(TypedDict):
1616
ticker: str

examples/async-user-confirmation/langchain-examples/src/agents/tools/conditional_trade.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from enum import Enum
33
from langchain_core.tools import StructuredTool
44
from langchain_core.runnables.config import RunnableConfig
5-
from clients.scheduler import SchedulerClient
5+
from src.agents.clients.scheduler import SchedulerClient
66

77
class MetricEnum(str, Enum):
88
PE = "P/E"

examples/async-user-confirmation/llama-index-examples/poetry.lock

Lines changed: 704 additions & 634 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/async-user-confirmation/llama-index-examples/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ python = "^3.11"
1515
python-dotenv = "^1.0.1"
1616
flask = { extras = ["async"], version = "^3.1.0" }
1717
requests = "^2.32.3"
18-
authlib = "^1.5.1"
1918
hypercorn = "^0.17.3"
2019
llama-index = "^0.12.33"
2120
llama-index-agent-openai = "^0.4.6"
2221
auth0-ai-llamaindex = { path = "../../../packages/auth0-ai-llamaindex", develop = true }
22+
auth0-server-python = "^1.0.0b4"
2323

2424
[build-system]
2525
requires = ["poetry-core"]
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import asyncio
2+
from asgiref.wsgi import WsgiToAsgi
23
from hypercorn.config import Config
34
from hypercorn.asyncio import serve
45
from src.app.app import app
56

7+
68
def main():
79
config = Config()
810
config.bind = ["0.0.0.0:3000"]
911
config.worker_class = "asyncio"
1012
config.use_reloader = True
1113

12-
loop = asyncio.new_event_loop()
13-
asyncio.set_event_loop(loop)
14-
loop.run_until_complete(serve(app, config))
14+
asyncio.run(serve(WsgiToAsgi(app), config))
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from datetime import datetime
2+
from llama_index.agent.openai import OpenAIAgent
3+
4+
from .memory import get_memory
5+
from .tools.trade import trade_tool
6+
7+
8+
system_prompt = f"""You are a specialized stock trading assistant designed to
9+
guide users through the process of buying stocks step by step.
10+
11+
**Important Constraints**:
12+
- You cannot discuss, buy, or sell any stocks outside this limited list, whether real or fictional.
13+
- You and the user can discuss the prices of these stocks, adjust stock amounts, and place buy orders through the UI.
14+
15+
**Additional Guidelines**:
16+
- Today’s date for reference: {datetime.now().isoformat()}
17+
- You may perform calculations as needed and engage in general discussion with the user.
18+
"""
19+
20+
21+
async def get_agent(user_id: str, chat_id: str):
22+
chat_memory = await get_memory(user_id, chat_id)
23+
return OpenAIAgent.from_tools(
24+
model="gpt-4o",
25+
memory=chat_memory,
26+
system_prompt=system_prompt,
27+
tools=[trade_tool],
28+
verbose=True
29+
)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from llama_index.core.storage.chat_store import SimpleChatStore
2+
from llama_index.core.memory import ChatMemoryBuffer
3+
4+
chat_store = SimpleChatStore()
5+
6+
7+
async def get_memory(user_id: str, chat_id: str):
8+
return ChatMemoryBuffer.from_defaults(
9+
token_limit=3000,
10+
chat_store=chat_store,
11+
chat_store_key=f"{user_id}_{chat_id}",
12+
)

examples/async-user-confirmation/llama-index-examples/src/tools/trade.py renamed to examples/async-user-confirmation/llama-index-examples/src/agents/tools/trade.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import os
2-
2+
from dotenv import load_dotenv
33
import httpx
4-
from auth0_ai_llamaindex.ciba import get_ciba_credentials
54
from llama_index.core.tools import FunctionTool
65

6+
from auth0_ai_llamaindex.ciba import get_ciba_credentials
7+
from ...auth0.auth0_ai import with_async_user_confirmation
8+
load_dotenv()
9+
710

811
def trade_tool_function(ticker: str, qty: int) -> str:
912
credentials = get_ciba_credentials()
@@ -29,8 +32,8 @@ def trade_tool_function(ticker: str, qty: int) -> str:
2932
return f"HTTP request failed: {str(e)}"
3033

3134

32-
trade_tool = FunctionTool.from_defaults(
35+
trade_tool = with_async_user_confirmation(FunctionTool.from_defaults(
3336
name="trade_tool",
3437
description="Use this function to trade a stock",
3538
fn=trade_tool_function,
36-
)
39+
))
Lines changed: 38 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,67 @@
11
import os
22
import uuid
3-
from datetime import datetime
4-
from urllib.parse import quote_plus, urlencode
5-
6-
from auth0_ai_llamaindex.auth0_ai import Auth0AI, set_ai_context
7-
from authlib.integrations.flask_client import OAuth
83
from dotenv import load_dotenv
94
from flask import Flask, jsonify, redirect, render_template, request, session, url_for
10-
from llama_index.agent.openai import OpenAIAgent
115

12-
from ..tools.trade import trade_tool
6+
from auth0_ai_llamaindex.auth0_ai import set_ai_context
137

14-
load_dotenv()
8+
from ..agents.agent import get_agent
9+
from ..agents.memory import get_memory
10+
from ..auth0.routes import login_bp
1511

16-
agents = {}
17-
system_prompt = f"""You are a specialized stock trading assistant designed to
18-
guide users through the process of buying stocks step by step.
1912

20-
**Important Constraints**:
21-
- You cannot discuss, buy, or sell any stocks outside this limited list, whether real or fictional.
22-
- You and the user can discuss the prices of these stocks, adjust stock amounts, and place buy orders through the UI.
13+
load_dotenv()
2314

24-
**Additional Guidelines**:
25-
- Today’s date for reference: {datetime.now().isoformat()}
26-
- You may perform calculations as needed and engage in general discussion with the user.
27-
"""
15+
app = Flask(__name__)
16+
app.secret_key = os.getenv("APP_SECRET_KEY", "SOME_RANDOM_SECRET_KEY")
2817

29-
auth0_ai = Auth0AI()
30-
with_async_user_confirmation = auth0_ai.with_async_user_confirmation(
31-
scope="stock:trade",
32-
audience=os.getenv("AUDIENCE"),
33-
binding_message=lambda ticker, qty: f"Authorize the purchase of {qty} {ticker}",
34-
user_id=lambda *_, **__: session["user"]["userinfo"]["sub"],
35-
# When this flag is set to `"block"`, the execution of the tool awaits until the user approves or rejects the request.
36-
# Given the asynchronous nature of the CIBA flow, this mode is only useful during development.
37-
on_authorization_request="block",
38-
)
18+
app.register_blueprint(login_bp)
3919

40-
tools = [with_async_user_confirmation(trade_tool)]
4120

21+
@app.route("/")
22+
async def home():
23+
if "user" not in session:
24+
return redirect(url_for("auth0.login", _external=True))
4225

43-
def get_agent():
44-
user_id = session["user"]["userinfo"]["sub"]
45-
if user_id not in agents:
46-
agents[user_id] = OpenAIAgent.from_tools(
47-
tools=tools, model="gpt-4o", system_prompt=system_prompt, verbose=True)
48-
return agents[user_id]
26+
session["thread_id"] = str(uuid.uuid4())
27+
return redirect(url_for("chat", thread_id=session["thread_id"], _external=True))
4928

5029

51-
app = Flask(__name__)
52-
app.secret_key = os.getenv("APP_SECRET_KEY", "YOUR_SECRET_KEY")
30+
@app.route("/chat/<thread_id>")
31+
async def chat(thread_id: str):
32+
if "user" not in session:
33+
return "please login", 401
5334

54-
oauth = OAuth(app)
55-
oauth.register(
56-
"auth0",
57-
client_id=os.getenv("AUTH0_CLIENT_ID"),
58-
client_secret=os.getenv("AUTH0_CLIENT_SECRET"),
59-
client_kwargs={
60-
"scope": "openid profile",
61-
},
62-
server_metadata_url=f'https://{os.getenv("AUTH0_DOMAIN")}/.well-known/openid-configuration'
63-
)
35+
if ("thread_id" not in session) or (session["thread_id"] != thread_id):
36+
return "Invalid or mismatched chat session. Please start a new chat.", 400
6437

38+
user_id = session["user"]["sub"]
6539

66-
@app.route("/")
67-
def home():
68-
if "user" not in session:
69-
return redirect("/login")
70-
71-
thread_id = str(uuid.uuid4())
72-
set_ai_context(thread_id)
40+
memory = await get_memory(user_id, thread_id)
41+
42+
messages = [
43+
{
44+
"role": m.role,
45+
"content": m.content
46+
} for m in memory.get_all()
47+
]
7348

74-
return render_template("index.html", user=session.get('user'))
49+
return render_template("index.html", user=session["user"], messages=messages, interrupt=None)
7550

7651

77-
@app.route("/chat", methods=["POST"])
78-
async def chat():
52+
@app.route("/api/chat", methods=["POST"])
53+
async def api_chat():
7954
if "user" not in session:
8055
return jsonify({"error": "unauthorized"}), 401
8156

57+
user_id = session["user"]["sub"]
58+
thread_id = session["thread_id"]
59+
set_ai_context(thread_id)
60+
8261
try:
8362
message = request.json.get("message")
84-
response = await get_agent().achat(message)
63+
agent = await get_agent(user_id, thread_id)
64+
response = await agent.achat(message)
8565
return jsonify({"response": str(response)})
8666
except Exception as e:
8767
return jsonify({"error": str(e)}), 500
88-
89-
90-
@app.route("/login")
91-
def login():
92-
return oauth.auth0.authorize_redirect(
93-
redirect_uri=url_for("login_callback", _external=True)
94-
)
95-
96-
97-
@app.route("/login/callback", methods=["GET", "POST"])
98-
def login_callback():
99-
token = oauth.auth0.authorize_access_token()
100-
session["user"] = token
101-
return redirect("/")
102-
103-
104-
@app.route("/logout")
105-
def logout():
106-
session.clear()
107-
return redirect(
108-
"https://" + os.getenv("AUTH0_DOMAIN")
109-
+ "/v2/logout?"
110-
+ urlencode(
111-
{
112-
"returnTo": url_for("home", _external=True),
113-
"client_id": os.getenv("AUTH0_CLIENT_ID"),
114-
},
115-
quote_via=quote_plus,
116-
)
117-
)

0 commit comments

Comments
 (0)