auth0-ai-langchain 0.2.0__tar.gz → 1.0.0b1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of auth0-ai-langchain might be problematic. Click here for more details.
- {auth0_ai_langchain-0.2.0 → auth0_ai_langchain-1.0.0b1}/PKG-INFO +126 -25
- {auth0_ai_langchain-0.2.0 → auth0_ai_langchain-1.0.0b1}/README.md +119 -18
- {auth0_ai_langchain-0.2.0 → auth0_ai_langchain-1.0.0b1}/auth0_ai_langchain/FGARetriever.py +3 -3
- auth0_ai_langchain-1.0.0b1/auth0_ai_langchain/auth0_ai.py +112 -0
- auth0_ai_langchain-1.0.0b1/auth0_ai_langchain/ciba/__init__.py +3 -0
- auth0_ai_langchain-1.0.0b1/auth0_ai_langchain/ciba/ciba_authorizer.py +17 -0
- auth0_ai_langchain-1.0.0b1/auth0_ai_langchain/ciba/graph_resumer.py +154 -0
- auth0_ai_langchain-1.0.0b1/auth0_ai_langchain/federated_connections/__init__.py +7 -0
- auth0_ai_langchain-1.0.0b1/auth0_ai_langchain/federated_connections/federated_connection_authorizer.py +33 -0
- auth0_ai_langchain-1.0.0b1/auth0_ai_langchain/fga/__init__.py +4 -0
- auth0_ai_langchain-1.0.0b1/auth0_ai_langchain/utils/interrupt.py +30 -0
- auth0_ai_langchain-1.0.0b1/auth0_ai_langchain/utils/tool_wrapper.py +34 -0
- {auth0_ai_langchain-0.2.0 → auth0_ai_langchain-1.0.0b1}/pyproject.toml +7 -7
- auth0_ai_langchain-0.2.0/auth0_ai_langchain/auth0_ai.py +0 -43
- auth0_ai_langchain-0.2.0/auth0_ai_langchain/ciba/__init__.py +0 -0
- auth0_ai_langchain-0.2.0/auth0_ai_langchain/ciba/ciba_graph/ciba_graph.py +0 -109
- auth0_ai_langchain-0.2.0/auth0_ai_langchain/ciba/ciba_graph/initialize_ciba.py +0 -91
- auth0_ai_langchain-0.2.0/auth0_ai_langchain/ciba/ciba_graph/initialize_hitl.py +0 -50
- auth0_ai_langchain-0.2.0/auth0_ai_langchain/ciba/ciba_graph/types.py +0 -115
- auth0_ai_langchain-0.2.0/auth0_ai_langchain/ciba/ciba_graph/utils.py +0 -17
- auth0_ai_langchain-0.2.0/auth0_ai_langchain/ciba/ciba_poller_graph.py +0 -105
- auth0_ai_langchain-0.2.0/auth0_ai_langchain/ciba/types.py +0 -8
- auth0_ai_langchain-0.2.0/auth0_ai_langchain/federated_connections/__init__.py +0 -4
- auth0_ai_langchain-0.2.0/auth0_ai_langchain/federated_connections/federated_connection_authorizer.py +0 -52
- auth0_ai_langchain-0.2.0/auth0_ai_langchain/fga/fga_authorizer.py +0 -3
- auth0_ai_langchain-0.2.0/auth0_ai_langchain/utils/interrupt.py +0 -13
- {auth0_ai_langchain-0.2.0 → auth0_ai_langchain-1.0.0b1}/LICENSE +0 -0
- {auth0_ai_langchain-0.2.0 → auth0_ai_langchain-1.0.0b1}/auth0_ai_langchain/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: auth0-ai-langchain
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0b1
|
|
4
4
|
Summary: This package is an SDK for building secure AI-powered applications using Auth0, Okta FGA and LangChain.
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Author: Auth0
|
|
@@ -11,12 +11,12 @@ Classifier: Programming Language :: Python :: 3
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
-
Requires-Dist: auth0-ai (>=0.2.0
|
|
15
|
-
Requires-Dist: langchain (>=0.3.
|
|
16
|
-
Requires-Dist: langchain-core (>=0.3.
|
|
17
|
-
Requires-Dist: langgraph (>=0.3
|
|
18
|
-
Requires-Dist: langgraph-sdk (>=0.1.
|
|
19
|
-
Requires-Dist: openfga-sdk (>=0.9.
|
|
14
|
+
Requires-Dist: auth0-ai (>=1.0.0b1,<2.0.0)
|
|
15
|
+
Requires-Dist: langchain (>=0.3.25,<0.4.0)
|
|
16
|
+
Requires-Dist: langchain-core (>=0.3.59,<0.4.0)
|
|
17
|
+
Requires-Dist: langgraph (>=0.4.3,<0.5.0)
|
|
18
|
+
Requires-Dist: langgraph-sdk (>=0.1.66,<0.2.0)
|
|
19
|
+
Requires-Dist: openfga-sdk (>=0.9.4,<0.10.0)
|
|
20
20
|
Project-URL: Homepage, https://auth0.com
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
|
|
@@ -34,6 +34,52 @@ Description-Content-Type: text/markdown
|
|
|
34
34
|
pip install auth0-ai-langchain
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
## Async User Confirmation
|
|
38
|
+
|
|
39
|
+
`Auth0AI` uses CIBA (Client-Initiated Backchannel Authentication) to handle user confirmation asynchronously. This is useful when you need to confirm a user action before proceeding with a tool execution.
|
|
40
|
+
|
|
41
|
+
Full Example of [Async User Confirmation](https://github.com/auth0-lab/auth0-ai-python/tree/main/examples/async-user-confirmation/langchain-examples).
|
|
42
|
+
|
|
43
|
+
1. Define a tool with the proper authorizer specifying a function to resolve the user id:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from auth0_ai_langchain.auth0_ai import Auth0AI
|
|
47
|
+
from auth0_ai_langchain.ciba import get_ciba_credentials
|
|
48
|
+
from langchain_core.runnables import ensure_config
|
|
49
|
+
from langchain_core.tools import StructuredTool
|
|
50
|
+
|
|
51
|
+
# If not provided, Auth0 settings will be read from env variables: `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET`
|
|
52
|
+
auth0_ai = Auth0AI()
|
|
53
|
+
|
|
54
|
+
with_async_user_confirmation = auth0_ai.with_async_user_confirmation(
|
|
55
|
+
scope="stock:trade",
|
|
56
|
+
audience=os.getenv("AUDIENCE"),
|
|
57
|
+
binding_message=lambda ticker, qty: f"Authorize the purchase of {qty} {ticker}",
|
|
58
|
+
user_id=lambda *_, **__: ensure_config().get("configurable", {}).get("user_id"),
|
|
59
|
+
# Optional:
|
|
60
|
+
# store=InMemoryStore()
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def tool_function(ticker: str, qty: int) -> str:
|
|
64
|
+
credentials = get_ciba_credentials()
|
|
65
|
+
headers = {
|
|
66
|
+
"Authorization": f"{credentials["token_type"]} {credentials["access_token"]}",
|
|
67
|
+
# ...
|
|
68
|
+
}
|
|
69
|
+
# Call API
|
|
70
|
+
|
|
71
|
+
trade_tool = with_async_user_confirmation(
|
|
72
|
+
StructuredTool(
|
|
73
|
+
name="trade_tool",
|
|
74
|
+
description="Use this function to trade a stock",
|
|
75
|
+
func=trade_tool_function,
|
|
76
|
+
# ...
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
2. Handle interruptions properly. For example, if user is not enrolled to MFA, it will throw an interruption. See [Handling Interrupts](#handling-interrupts) section.
|
|
82
|
+
|
|
37
83
|
## Authorization for Tools
|
|
38
84
|
|
|
39
85
|
The `FGAAuthorizer` can leverage Okta FGA to authorize tools executions. The `FGAAuthorizer.create` function can be used to create an authorizer that checks permissions before executing the tool.
|
|
@@ -43,19 +89,12 @@ Full example of [Authorization for Tools](https://github.com/auth0-lab/auth0-ai-
|
|
|
43
89
|
1. Create an instance of FGA Authorizer:
|
|
44
90
|
|
|
45
91
|
```python
|
|
46
|
-
from auth0_ai_langchain.fga
|
|
92
|
+
from auth0_ai_langchain.fga import FGAAuthorizer
|
|
47
93
|
|
|
94
|
+
# If not provided, FGA settings will be read from env variables: `FGA_STORE_ID`, `FGA_CLIENT_ID`, `FGA_CLIENT_SECRET`, etc.
|
|
48
95
|
fga = FGAAuthorizer.create()
|
|
49
96
|
```
|
|
50
97
|
|
|
51
|
-
**Note**: Here, you can configure and specify your FGA credentials. By `default`, they are read from environment variables:
|
|
52
|
-
|
|
53
|
-
```sh
|
|
54
|
-
FGA_STORE_ID="<fga-store-id>"
|
|
55
|
-
FGA_CLIENT_ID="<fga-client-id>"
|
|
56
|
-
FGA_CLIENT_SECRET="<fga-client-secret>"
|
|
57
|
-
```
|
|
58
|
-
|
|
59
98
|
2. Define the FGA query (`build_query`) and, optionally, the `on_unauthorized` handler:
|
|
60
99
|
|
|
61
100
|
```python
|
|
@@ -73,10 +112,10 @@ async def build_fga_query(tool_input):
|
|
|
73
112
|
def on_unauthorized(tool_input):
|
|
74
113
|
return f"The user is not allowed to buy {tool_input["qty"]} shares of {tool_input["ticker"]}."
|
|
75
114
|
|
|
76
|
-
use_fga = fga(
|
|
115
|
+
use_fga = fga(
|
|
77
116
|
build_query=build_fga_query,
|
|
78
117
|
on_unauthorized=on_unauthorized,
|
|
79
|
-
)
|
|
118
|
+
)
|
|
80
119
|
```
|
|
81
120
|
|
|
82
121
|
**Note**: The parameters given to the `build_query` and `on_unauthorized` functions are the same as those provided to the tool function.
|
|
@@ -102,7 +141,7 @@ buy_tool = StructuredTool(
|
|
|
102
141
|
|
|
103
142
|
## Calling APIs On User's Behalf
|
|
104
143
|
|
|
105
|
-
The `Auth0AI.with_federated_connection` function exchanges user's refresh token taken from the runnable configuration (`config.configurable._credentials.refresh_token`) for a Federated Connection API token.
|
|
144
|
+
The `Auth0AI.with_federated_connection` function exchanges user's refresh token taken, by default, from the runnable configuration (`config.configurable._credentials.refresh_token`) for a Federated Connection API token.
|
|
106
145
|
|
|
107
146
|
Full Example of [Calling APIs On User's Behalf](https://github.com/auth0-lab/auth0-ai-python/tree/main/examples/calling-apis/langchain-examples).
|
|
108
147
|
|
|
@@ -110,19 +149,23 @@ Full Example of [Calling APIs On User's Behalf](https://github.com/auth0-lab/aut
|
|
|
110
149
|
|
|
111
150
|
```python
|
|
112
151
|
from auth0_ai_langchain.auth0_ai import Auth0AI
|
|
113
|
-
from auth0_ai_langchain.federated_connections import
|
|
152
|
+
from auth0_ai_langchain.federated_connections import get_credentials_for_connection
|
|
114
153
|
from langchain_core.tools import StructuredTool
|
|
115
154
|
|
|
155
|
+
# If not provided, Auth0 settings will be read from env variables: `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET`
|
|
116
156
|
auth0_ai = Auth0AI()
|
|
117
157
|
|
|
118
158
|
with_google_calendar_access = auth0_ai.with_federated_connection(
|
|
119
159
|
connection="google-oauth2",
|
|
120
|
-
scopes=["https://www.googleapis.com/auth/calendar.freebusy"]
|
|
160
|
+
scopes=["https://www.googleapis.com/auth/calendar.freebusy"],
|
|
161
|
+
# Optional:
|
|
162
|
+
# refresh_token=lambda *_, **__: ensure_config().get("configurable", {}).get("_credentials", {}).get("refresh_token"),
|
|
163
|
+
# store=InMemoryStore(),
|
|
121
164
|
)
|
|
122
165
|
|
|
123
166
|
def tool_function(date: datetime):
|
|
124
|
-
|
|
125
|
-
# Call Google API
|
|
167
|
+
credentials = get_credentials_for_connection()
|
|
168
|
+
# Call Google API using credentials["access_token"]
|
|
126
169
|
|
|
127
170
|
check_calendar_tool = with_google_calendar_access(
|
|
128
171
|
StructuredTool(
|
|
@@ -154,7 +197,7 @@ workflow = (
|
|
|
154
197
|
)
|
|
155
198
|
```
|
|
156
199
|
|
|
157
|
-
3. Handle interruptions properly.
|
|
200
|
+
3. Handle interruptions properly. For example, if the tool does not have access to user's Google Calendar, it will throw an interruption. See [Handling Interrupts](#handling-interrupts) section.
|
|
158
201
|
|
|
159
202
|
## RAG with FGA
|
|
160
203
|
|
|
@@ -184,7 +227,8 @@ vector_store = VectorStoreIndex.from_documents(documents)
|
|
|
184
227
|
# Create a retriever:
|
|
185
228
|
base_retriever = vector_store.as_retriever()
|
|
186
229
|
|
|
187
|
-
# Create the FGA retriever wrapper
|
|
230
|
+
# Create the FGA retriever wrapper.
|
|
231
|
+
# If not provided, FGA settings will be read from env variables: `FGA_STORE_ID`, `FGA_CLIENT_ID`, `FGA_CLIENT_SECRET`, etc.
|
|
188
232
|
retriever = FGARetriever(
|
|
189
233
|
base_retriever,
|
|
190
234
|
build_query=lambda node: ClientCheckRequest(
|
|
@@ -206,6 +250,63 @@ response = query_engine.query("What is the forecast for ZEKO?")
|
|
|
206
250
|
print(response)
|
|
207
251
|
```
|
|
208
252
|
|
|
253
|
+
## Handling Interrupts
|
|
254
|
+
|
|
255
|
+
`Auth0AI` uses interrupts extensively and will never block a graph. Whenever an authorizer requires user interaction, the graph throws a `GraphInterrupt` exception with data that allows the client to resume the flow.
|
|
256
|
+
|
|
257
|
+
It is important to disable error handling in your tools node as follows:
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
.add_node(
|
|
261
|
+
"tools",
|
|
262
|
+
ToolNode(
|
|
263
|
+
[
|
|
264
|
+
# your authorizer-wrapped tools
|
|
265
|
+
],
|
|
266
|
+
# Error handler should be disabled in order to trigger interruptions from within tools.
|
|
267
|
+
handle_tool_errors=False
|
|
268
|
+
)
|
|
269
|
+
)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
From the client side of the graph you get the interrupts:
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
from auth0_ai_langchain.utils.interrupt import get_auth0_interrupts
|
|
276
|
+
|
|
277
|
+
# Get the langgraph thread:
|
|
278
|
+
thread = await client.threads.get(thread_id)
|
|
279
|
+
|
|
280
|
+
# Filter the auth0 interrupts:
|
|
281
|
+
auth0_interrupts = get_auth0_interrupts(thread)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Then you can resume the thread by doing this:
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
await client.runs.wait(thread_id, assistant_id)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
For the specific case of **CIBA (Client-Initiated Backchannel Authorization)** you might attach a `GraphResumer` instance that watches for interrupted threads in the `"Authorization Pending"` state and attempts to resume them automatically, respecting Auth0's polling interval.
|
|
291
|
+
|
|
292
|
+
```python
|
|
293
|
+
import os
|
|
294
|
+
from auth0_ai_langchain.ciba import GraphResumer
|
|
295
|
+
from langgraph_sdk import get_client
|
|
296
|
+
|
|
297
|
+
resumer = GraphResumer(
|
|
298
|
+
lang_graph=get_client(url=os.getenv("LANGGRAPH_API_URL")),
|
|
299
|
+
# optionally, you can filter by a specific graph:
|
|
300
|
+
filters={"graph_id": "conditional-trade"},
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
resumer \
|
|
304
|
+
.on_resume(lambda thread: print(f"Attempting to resume thread {thread['thread_id']} from interruption {thread['interruption_id']}")) \
|
|
305
|
+
.on_error(lambda err: print(f"Error in GraphResumer: {str(err)}"))
|
|
306
|
+
|
|
307
|
+
resumer.start()
|
|
308
|
+
```
|
|
309
|
+
|
|
209
310
|
---
|
|
210
311
|
|
|
211
312
|
<p align="center">
|
|
@@ -12,6 +12,52 @@
|
|
|
12
12
|
pip install auth0-ai-langchain
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
## Async User Confirmation
|
|
16
|
+
|
|
17
|
+
`Auth0AI` uses CIBA (Client-Initiated Backchannel Authentication) to handle user confirmation asynchronously. This is useful when you need to confirm a user action before proceeding with a tool execution.
|
|
18
|
+
|
|
19
|
+
Full Example of [Async User Confirmation](https://github.com/auth0-lab/auth0-ai-python/tree/main/examples/async-user-confirmation/langchain-examples).
|
|
20
|
+
|
|
21
|
+
1. Define a tool with the proper authorizer specifying a function to resolve the user id:
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from auth0_ai_langchain.auth0_ai import Auth0AI
|
|
25
|
+
from auth0_ai_langchain.ciba import get_ciba_credentials
|
|
26
|
+
from langchain_core.runnables import ensure_config
|
|
27
|
+
from langchain_core.tools import StructuredTool
|
|
28
|
+
|
|
29
|
+
# If not provided, Auth0 settings will be read from env variables: `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET`
|
|
30
|
+
auth0_ai = Auth0AI()
|
|
31
|
+
|
|
32
|
+
with_async_user_confirmation = auth0_ai.with_async_user_confirmation(
|
|
33
|
+
scope="stock:trade",
|
|
34
|
+
audience=os.getenv("AUDIENCE"),
|
|
35
|
+
binding_message=lambda ticker, qty: f"Authorize the purchase of {qty} {ticker}",
|
|
36
|
+
user_id=lambda *_, **__: ensure_config().get("configurable", {}).get("user_id"),
|
|
37
|
+
# Optional:
|
|
38
|
+
# store=InMemoryStore()
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def tool_function(ticker: str, qty: int) -> str:
|
|
42
|
+
credentials = get_ciba_credentials()
|
|
43
|
+
headers = {
|
|
44
|
+
"Authorization": f"{credentials["token_type"]} {credentials["access_token"]}",
|
|
45
|
+
# ...
|
|
46
|
+
}
|
|
47
|
+
# Call API
|
|
48
|
+
|
|
49
|
+
trade_tool = with_async_user_confirmation(
|
|
50
|
+
StructuredTool(
|
|
51
|
+
name="trade_tool",
|
|
52
|
+
description="Use this function to trade a stock",
|
|
53
|
+
func=trade_tool_function,
|
|
54
|
+
# ...
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
2. Handle interruptions properly. For example, if user is not enrolled to MFA, it will throw an interruption. See [Handling Interrupts](#handling-interrupts) section.
|
|
60
|
+
|
|
15
61
|
## Authorization for Tools
|
|
16
62
|
|
|
17
63
|
The `FGAAuthorizer` can leverage Okta FGA to authorize tools executions. The `FGAAuthorizer.create` function can be used to create an authorizer that checks permissions before executing the tool.
|
|
@@ -21,19 +67,12 @@ Full example of [Authorization for Tools](https://github.com/auth0-lab/auth0-ai-
|
|
|
21
67
|
1. Create an instance of FGA Authorizer:
|
|
22
68
|
|
|
23
69
|
```python
|
|
24
|
-
from auth0_ai_langchain.fga
|
|
70
|
+
from auth0_ai_langchain.fga import FGAAuthorizer
|
|
25
71
|
|
|
72
|
+
# If not provided, FGA settings will be read from env variables: `FGA_STORE_ID`, `FGA_CLIENT_ID`, `FGA_CLIENT_SECRET`, etc.
|
|
26
73
|
fga = FGAAuthorizer.create()
|
|
27
74
|
```
|
|
28
75
|
|
|
29
|
-
**Note**: Here, you can configure and specify your FGA credentials. By `default`, they are read from environment variables:
|
|
30
|
-
|
|
31
|
-
```sh
|
|
32
|
-
FGA_STORE_ID="<fga-store-id>"
|
|
33
|
-
FGA_CLIENT_ID="<fga-client-id>"
|
|
34
|
-
FGA_CLIENT_SECRET="<fga-client-secret>"
|
|
35
|
-
```
|
|
36
|
-
|
|
37
76
|
2. Define the FGA query (`build_query`) and, optionally, the `on_unauthorized` handler:
|
|
38
77
|
|
|
39
78
|
```python
|
|
@@ -51,10 +90,10 @@ async def build_fga_query(tool_input):
|
|
|
51
90
|
def on_unauthorized(tool_input):
|
|
52
91
|
return f"The user is not allowed to buy {tool_input["qty"]} shares of {tool_input["ticker"]}."
|
|
53
92
|
|
|
54
|
-
use_fga = fga(
|
|
93
|
+
use_fga = fga(
|
|
55
94
|
build_query=build_fga_query,
|
|
56
95
|
on_unauthorized=on_unauthorized,
|
|
57
|
-
)
|
|
96
|
+
)
|
|
58
97
|
```
|
|
59
98
|
|
|
60
99
|
**Note**: The parameters given to the `build_query` and `on_unauthorized` functions are the same as those provided to the tool function.
|
|
@@ -80,7 +119,7 @@ buy_tool = StructuredTool(
|
|
|
80
119
|
|
|
81
120
|
## Calling APIs On User's Behalf
|
|
82
121
|
|
|
83
|
-
The `Auth0AI.with_federated_connection` function exchanges user's refresh token taken from the runnable configuration (`config.configurable._credentials.refresh_token`) for a Federated Connection API token.
|
|
122
|
+
The `Auth0AI.with_federated_connection` function exchanges user's refresh token taken, by default, from the runnable configuration (`config.configurable._credentials.refresh_token`) for a Federated Connection API token.
|
|
84
123
|
|
|
85
124
|
Full Example of [Calling APIs On User's Behalf](https://github.com/auth0-lab/auth0-ai-python/tree/main/examples/calling-apis/langchain-examples).
|
|
86
125
|
|
|
@@ -88,19 +127,23 @@ Full Example of [Calling APIs On User's Behalf](https://github.com/auth0-lab/aut
|
|
|
88
127
|
|
|
89
128
|
```python
|
|
90
129
|
from auth0_ai_langchain.auth0_ai import Auth0AI
|
|
91
|
-
from auth0_ai_langchain.federated_connections import
|
|
130
|
+
from auth0_ai_langchain.federated_connections import get_credentials_for_connection
|
|
92
131
|
from langchain_core.tools import StructuredTool
|
|
93
132
|
|
|
133
|
+
# If not provided, Auth0 settings will be read from env variables: `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET`
|
|
94
134
|
auth0_ai = Auth0AI()
|
|
95
135
|
|
|
96
136
|
with_google_calendar_access = auth0_ai.with_federated_connection(
|
|
97
137
|
connection="google-oauth2",
|
|
98
|
-
scopes=["https://www.googleapis.com/auth/calendar.freebusy"]
|
|
138
|
+
scopes=["https://www.googleapis.com/auth/calendar.freebusy"],
|
|
139
|
+
# Optional:
|
|
140
|
+
# refresh_token=lambda *_, **__: ensure_config().get("configurable", {}).get("_credentials", {}).get("refresh_token"),
|
|
141
|
+
# store=InMemoryStore(),
|
|
99
142
|
)
|
|
100
143
|
|
|
101
144
|
def tool_function(date: datetime):
|
|
102
|
-
|
|
103
|
-
# Call Google API
|
|
145
|
+
credentials = get_credentials_for_connection()
|
|
146
|
+
# Call Google API using credentials["access_token"]
|
|
104
147
|
|
|
105
148
|
check_calendar_tool = with_google_calendar_access(
|
|
106
149
|
StructuredTool(
|
|
@@ -132,7 +175,7 @@ workflow = (
|
|
|
132
175
|
)
|
|
133
176
|
```
|
|
134
177
|
|
|
135
|
-
3. Handle interruptions properly.
|
|
178
|
+
3. Handle interruptions properly. For example, if the tool does not have access to user's Google Calendar, it will throw an interruption. See [Handling Interrupts](#handling-interrupts) section.
|
|
136
179
|
|
|
137
180
|
## RAG with FGA
|
|
138
181
|
|
|
@@ -162,7 +205,8 @@ vector_store = VectorStoreIndex.from_documents(documents)
|
|
|
162
205
|
# Create a retriever:
|
|
163
206
|
base_retriever = vector_store.as_retriever()
|
|
164
207
|
|
|
165
|
-
# Create the FGA retriever wrapper
|
|
208
|
+
# Create the FGA retriever wrapper.
|
|
209
|
+
# If not provided, FGA settings will be read from env variables: `FGA_STORE_ID`, `FGA_CLIENT_ID`, `FGA_CLIENT_SECRET`, etc.
|
|
166
210
|
retriever = FGARetriever(
|
|
167
211
|
base_retriever,
|
|
168
212
|
build_query=lambda node: ClientCheckRequest(
|
|
@@ -184,6 +228,63 @@ response = query_engine.query("What is the forecast for ZEKO?")
|
|
|
184
228
|
print(response)
|
|
185
229
|
```
|
|
186
230
|
|
|
231
|
+
## Handling Interrupts
|
|
232
|
+
|
|
233
|
+
`Auth0AI` uses interrupts extensively and will never block a graph. Whenever an authorizer requires user interaction, the graph throws a `GraphInterrupt` exception with data that allows the client to resume the flow.
|
|
234
|
+
|
|
235
|
+
It is important to disable error handling in your tools node as follows:
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
.add_node(
|
|
239
|
+
"tools",
|
|
240
|
+
ToolNode(
|
|
241
|
+
[
|
|
242
|
+
# your authorizer-wrapped tools
|
|
243
|
+
],
|
|
244
|
+
# Error handler should be disabled in order to trigger interruptions from within tools.
|
|
245
|
+
handle_tool_errors=False
|
|
246
|
+
)
|
|
247
|
+
)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
From the client side of the graph you get the interrupts:
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
from auth0_ai_langchain.utils.interrupt import get_auth0_interrupts
|
|
254
|
+
|
|
255
|
+
# Get the langgraph thread:
|
|
256
|
+
thread = await client.threads.get(thread_id)
|
|
257
|
+
|
|
258
|
+
# Filter the auth0 interrupts:
|
|
259
|
+
auth0_interrupts = get_auth0_interrupts(thread)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Then you can resume the thread by doing this:
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
await client.runs.wait(thread_id, assistant_id)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
For the specific case of **CIBA (Client-Initiated Backchannel Authorization)** you might attach a `GraphResumer` instance that watches for interrupted threads in the `"Authorization Pending"` state and attempts to resume them automatically, respecting Auth0's polling interval.
|
|
269
|
+
|
|
270
|
+
```python
|
|
271
|
+
import os
|
|
272
|
+
from auth0_ai_langchain.ciba import GraphResumer
|
|
273
|
+
from langgraph_sdk import get_client
|
|
274
|
+
|
|
275
|
+
resumer = GraphResumer(
|
|
276
|
+
lang_graph=get_client(url=os.getenv("LANGGRAPH_API_URL")),
|
|
277
|
+
# optionally, you can filter by a specific graph:
|
|
278
|
+
filters={"graph_id": "conditional-trade"},
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
resumer \
|
|
282
|
+
.on_resume(lambda thread: print(f"Attempting to resume thread {thread['thread_id']} from interruption {thread['interruption_id']}")) \
|
|
283
|
+
.on_error(lambda err: print(f"Error in GraphResumer: {str(err)}"))
|
|
284
|
+
|
|
285
|
+
resumer.start()
|
|
286
|
+
```
|
|
287
|
+
|
|
187
288
|
---
|
|
188
289
|
|
|
189
290
|
<p align="center">
|
|
@@ -32,7 +32,7 @@ class FGARetriever(BaseRetriever):
|
|
|
32
32
|
Args:
|
|
33
33
|
retriever (BaseRetriever): The retriever used to fetch documents.
|
|
34
34
|
build_query (Callable[[Document], ClientBatchCheckItem]): Function to convert documents into FGA queries.
|
|
35
|
-
fga_configuration (
|
|
35
|
+
fga_configuration (ClientConfiguration, optional): Configuration for the OpenFGA client. If not provided, defaults to environment variables.
|
|
36
36
|
"""
|
|
37
37
|
super().__init__()
|
|
38
38
|
self._retriever = retriever
|
|
@@ -95,7 +95,7 @@ class FGARetriever(BaseRetriever):
|
|
|
95
95
|
|
|
96
96
|
Args:
|
|
97
97
|
query (str): The query for retrieving documents.
|
|
98
|
-
run_manager (
|
|
98
|
+
run_manager (object, optional): Optional manager for tracking runs.
|
|
99
99
|
|
|
100
100
|
Returns:
|
|
101
101
|
List[Document]: Filtered and relevant documents.
|
|
@@ -148,7 +148,7 @@ class FGARetriever(BaseRetriever):
|
|
|
148
148
|
|
|
149
149
|
Args:
|
|
150
150
|
query (str): The query for retrieving documents.
|
|
151
|
-
run_manager (
|
|
151
|
+
run_manager (object, optional): Optional manager for tracking runs.
|
|
152
152
|
|
|
153
153
|
Returns:
|
|
154
154
|
List[Document]: Filtered and relevant documents.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from typing import Callable, Optional
|
|
2
|
+
from langchain_core.tools import BaseTool
|
|
3
|
+
from auth0_ai.authorizers.ciba import CIBAAuthorizerParams
|
|
4
|
+
from auth0_ai.authorizers.federated_connection_authorizer import FederatedConnectionAuthorizerParams
|
|
5
|
+
from auth0_ai.authorizers.types import Auth0ClientParams
|
|
6
|
+
from auth0_ai_langchain.ciba.ciba_authorizer import CIBAAuthorizer
|
|
7
|
+
from auth0_ai_langchain.federated_connections.federated_connection_authorizer import FederatedConnectionAuthorizer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Auth0AI:
|
|
11
|
+
"""Provides decorators to secure LangChain tools using Auth0 authorization flows.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, auth0: Optional[Auth0ClientParams] = None):
|
|
15
|
+
"""Initializes the Auth0AI instance.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
auth0 (Optional[Auth0ClientParams]): Parameters for the Auth0 client.
|
|
19
|
+
If not provided, values will be automatically read from environment
|
|
20
|
+
variables: `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET`.
|
|
21
|
+
"""
|
|
22
|
+
self.auth0 = auth0
|
|
23
|
+
|
|
24
|
+
def with_async_user_confirmation(self, **params: CIBAAuthorizerParams) -> Callable[[BaseTool], BaseTool]:
|
|
25
|
+
"""Protects a tool with the CIBA (Client-Initiated Backchannel Authentication) flow.
|
|
26
|
+
|
|
27
|
+
Requires user confirmation via a second device (e.g., phone)
|
|
28
|
+
before allowing the tool to execute.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
**params: Parameters defined in `CIBAAuthorizerParams`.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Callable[[BaseTool], BaseTool]: A decorator to wrap a LangChain tool.
|
|
35
|
+
|
|
36
|
+
Example:
|
|
37
|
+
```python
|
|
38
|
+
import os
|
|
39
|
+
from auth0_ai_langchain.auth0_ai import Auth0AI
|
|
40
|
+
from auth0_ai_langchain.ciba import get_ciba_credentials
|
|
41
|
+
from langchain_core.runnables import ensure_config
|
|
42
|
+
from langchain_core.tools import StructuredTool
|
|
43
|
+
|
|
44
|
+
auth0_ai = Auth0AI()
|
|
45
|
+
|
|
46
|
+
with_async_user_confirmation = auth0_ai.with_async_user_confirmation(
|
|
47
|
+
scope="stock:trade",
|
|
48
|
+
audience=os.getenv("AUDIENCE"),
|
|
49
|
+
binding_message=lambda ticker, qty: f"Authorize the purchase of {qty} {ticker}",
|
|
50
|
+
user_id=lambda *_, **__: ensure_config().get("configurable", {}).get("user_id")
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def tool_function(ticker: str, qty: int) -> str:
|
|
54
|
+
credentials = get_ciba_credentials()
|
|
55
|
+
headers = {
|
|
56
|
+
"Authorization": f"{credentials['token_type']} {credentials['access_token']}",
|
|
57
|
+
# ...
|
|
58
|
+
}
|
|
59
|
+
# Call API
|
|
60
|
+
|
|
61
|
+
trade_tool = with_async_user_confirmation(
|
|
62
|
+
StructuredTool(
|
|
63
|
+
name="trade_tool",
|
|
64
|
+
description="Use this function to trade a stock",
|
|
65
|
+
func=tool_function,
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
```
|
|
69
|
+
"""
|
|
70
|
+
authorizer = CIBAAuthorizer(CIBAAuthorizerParams(**params), self.auth0)
|
|
71
|
+
return authorizer.authorizer()
|
|
72
|
+
|
|
73
|
+
def with_federated_connection(self, **params: FederatedConnectionAuthorizerParams) -> Callable[[BaseTool], BaseTool]:
|
|
74
|
+
"""Enables a tool to obtain an access token from a federated identity provider (e.g., Google, Azure AD).
|
|
75
|
+
|
|
76
|
+
The token can then be used within the tool to call third-party APIs on behalf of the user.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
**params: Parameters defined in `FederatedConnectionAuthorizerParams`.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Callable[[BaseTool], BaseTool]: A decorator to wrap a LangChain tool.
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
```python
|
|
86
|
+
from auth0_ai_langchain.auth0_ai import Auth0AI
|
|
87
|
+
from auth0_ai_langchain.federated_connections import get_credentials_for_connection
|
|
88
|
+
from langchain_core.tools import StructuredTool
|
|
89
|
+
from datetime import datetime
|
|
90
|
+
|
|
91
|
+
auth0_ai = Auth0AI()
|
|
92
|
+
|
|
93
|
+
with_google_calendar_access = auth0_ai.with_federated_connection(
|
|
94
|
+
connection="google-oauth2",
|
|
95
|
+
scopes=["https://www.googleapis.com/auth/calendar.freebusy"]
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def tool_function(date: datetime):
|
|
99
|
+
credentials = get_credentials_for_connection()
|
|
100
|
+
# Call Google API using credentials["access_token"]
|
|
101
|
+
|
|
102
|
+
check_calendar_tool = with_google_calendar_access(
|
|
103
|
+
StructuredTool(
|
|
104
|
+
name="check_user_calendar",
|
|
105
|
+
description="Use this function to check if the user is available on a certain date and time",
|
|
106
|
+
func=tool_function,
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
```
|
|
110
|
+
"""
|
|
111
|
+
authorizer = FederatedConnectionAuthorizer(FederatedConnectionAuthorizerParams(**params), self.auth0)
|
|
112
|
+
return authorizer.authorizer()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from typing import Union
|
|
3
|
+
from auth0_ai.authorizers.ciba import CIBAAuthorizerBase
|
|
4
|
+
from auth0_ai.interrupts.ciba_interrupts import AuthorizationPendingInterrupt, AuthorizationPollingInterrupt
|
|
5
|
+
from auth0_ai_langchain.utils.interrupt import to_graph_interrupt
|
|
6
|
+
from auth0_ai_langchain.utils.tool_wrapper import tool_wrapper
|
|
7
|
+
from langchain_core.tools import BaseTool
|
|
8
|
+
|
|
9
|
+
class CIBAAuthorizer(CIBAAuthorizerBase, ABC):
|
|
10
|
+
def _handle_authorization_interrupts(self, err: Union[AuthorizationPendingInterrupt, AuthorizationPollingInterrupt]) -> None:
|
|
11
|
+
raise to_graph_interrupt(err)
|
|
12
|
+
|
|
13
|
+
def authorizer(self):
|
|
14
|
+
def wrap_tool(tool: BaseTool) -> BaseTool:
|
|
15
|
+
return tool_wrapper(tool, self.protect)
|
|
16
|
+
|
|
17
|
+
return wrap_tool
|