auth0-ai-langchain 0.1.2__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.1.2 → auth0_ai_langchain-1.0.0b1}/PKG-INFO +127 -27
- {auth0_ai_langchain-0.1.2 → auth0_ai_langchain-1.0.0b1}/README.md +120 -20
- {auth0_ai_langchain-0.1.2 → 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.1.2 → auth0_ai_langchain-1.0.0b1}/pyproject.toml +8 -9
- auth0_ai_langchain-0.1.2/auth0_ai_langchain/auth0_ai.py +0 -43
- auth0_ai_langchain-0.1.2/auth0_ai_langchain/ciba/__init__.py +0 -0
- auth0_ai_langchain-0.1.2/auth0_ai_langchain/ciba/ciba_graph/ciba_graph.py +0 -109
- auth0_ai_langchain-0.1.2/auth0_ai_langchain/ciba/ciba_graph/initialize_ciba.py +0 -91
- auth0_ai_langchain-0.1.2/auth0_ai_langchain/ciba/ciba_graph/initialize_hitl.py +0 -50
- auth0_ai_langchain-0.1.2/auth0_ai_langchain/ciba/ciba_graph/types.py +0 -115
- auth0_ai_langchain-0.1.2/auth0_ai_langchain/ciba/ciba_graph/utils.py +0 -17
- auth0_ai_langchain-0.1.2/auth0_ai_langchain/ciba/ciba_poller_graph.py +0 -105
- auth0_ai_langchain-0.1.2/auth0_ai_langchain/ciba/types.py +0 -8
- auth0_ai_langchain-0.1.2/auth0_ai_langchain/federated_connections/__init__.py +0 -3
- auth0_ai_langchain-0.1.2/auth0_ai_langchain/federated_connections/federated_connection_authorizer.py +0 -52
- auth0_ai_langchain-0.1.2/auth0_ai_langchain/fga/fga_authorizer.py +0 -3
- auth0_ai_langchain-0.1.2/auth0_ai_langchain/utils/interrupt.py +0 -13
- {auth0_ai_langchain-0.1.2 → auth0_ai_langchain-1.0.0b1}/LICENSE +0 -0
- {auth0_ai_langchain-0.1.2 → 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 (>=
|
|
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
|
|
|
@@ -28,13 +28,58 @@ Description-Content-Type: text/markdown
|
|
|
28
28
|
|
|
29
29
|
## Installation
|
|
30
30
|
|
|
31
|
-
>
|
|
32
|
-
> `auth0-ai-langchain` is currently under development and it is not intended to be used in production, and therefore has no official support.
|
|
31
|
+
> ⚠️ **WARNING**: `auth0-ai-langchain` is currently under development and it is not intended to be used in production, and therefore has no official support.
|
|
33
32
|
|
|
34
33
|
```bash
|
|
35
34
|
pip install auth0-ai-langchain
|
|
36
35
|
```
|
|
37
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
|
+
|
|
38
83
|
## Authorization for Tools
|
|
39
84
|
|
|
40
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.
|
|
@@ -44,19 +89,12 @@ Full example of [Authorization for Tools](https://github.com/auth0-lab/auth0-ai-
|
|
|
44
89
|
1. Create an instance of FGA Authorizer:
|
|
45
90
|
|
|
46
91
|
```python
|
|
47
|
-
from auth0_ai_langchain.fga
|
|
92
|
+
from auth0_ai_langchain.fga import FGAAuthorizer
|
|
48
93
|
|
|
94
|
+
# If not provided, FGA settings will be read from env variables: `FGA_STORE_ID`, `FGA_CLIENT_ID`, `FGA_CLIENT_SECRET`, etc.
|
|
49
95
|
fga = FGAAuthorizer.create()
|
|
50
96
|
```
|
|
51
97
|
|
|
52
|
-
**Note**: Here, you can configure and specify your FGA credentials. By `default`, they are read from environment variables:
|
|
53
|
-
|
|
54
|
-
```sh
|
|
55
|
-
FGA_STORE_ID="<fga-store-id>"
|
|
56
|
-
FGA_CLIENT_ID="<fga-client-id>"
|
|
57
|
-
FGA_CLIENT_SECRET="<fga-client-secret>"
|
|
58
|
-
```
|
|
59
|
-
|
|
60
98
|
2. Define the FGA query (`build_query`) and, optionally, the `on_unauthorized` handler:
|
|
61
99
|
|
|
62
100
|
```python
|
|
@@ -74,10 +112,10 @@ async def build_fga_query(tool_input):
|
|
|
74
112
|
def on_unauthorized(tool_input):
|
|
75
113
|
return f"The user is not allowed to buy {tool_input["qty"]} shares of {tool_input["ticker"]}."
|
|
76
114
|
|
|
77
|
-
use_fga = fga(
|
|
115
|
+
use_fga = fga(
|
|
78
116
|
build_query=build_fga_query,
|
|
79
117
|
on_unauthorized=on_unauthorized,
|
|
80
|
-
)
|
|
118
|
+
)
|
|
81
119
|
```
|
|
82
120
|
|
|
83
121
|
**Note**: The parameters given to the `build_query` and `on_unauthorized` functions are the same as those provided to the tool function.
|
|
@@ -103,7 +141,7 @@ buy_tool = StructuredTool(
|
|
|
103
141
|
|
|
104
142
|
## Calling APIs On User's Behalf
|
|
105
143
|
|
|
106
|
-
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.
|
|
107
145
|
|
|
108
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).
|
|
109
147
|
|
|
@@ -111,19 +149,23 @@ Full Example of [Calling APIs On User's Behalf](https://github.com/auth0-lab/aut
|
|
|
111
149
|
|
|
112
150
|
```python
|
|
113
151
|
from auth0_ai_langchain.auth0_ai import Auth0AI
|
|
114
|
-
from auth0_ai_langchain.federated_connections import
|
|
152
|
+
from auth0_ai_langchain.federated_connections import get_credentials_for_connection
|
|
115
153
|
from langchain_core.tools import StructuredTool
|
|
116
154
|
|
|
155
|
+
# If not provided, Auth0 settings will be read from env variables: `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET`
|
|
117
156
|
auth0_ai = Auth0AI()
|
|
118
157
|
|
|
119
158
|
with_google_calendar_access = auth0_ai.with_federated_connection(
|
|
120
159
|
connection="google-oauth2",
|
|
121
|
-
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(),
|
|
122
164
|
)
|
|
123
165
|
|
|
124
166
|
def tool_function(date: datetime):
|
|
125
|
-
|
|
126
|
-
# Call Google API
|
|
167
|
+
credentials = get_credentials_for_connection()
|
|
168
|
+
# Call Google API using credentials["access_token"]
|
|
127
169
|
|
|
128
170
|
check_calendar_tool = with_google_calendar_access(
|
|
129
171
|
StructuredTool(
|
|
@@ -155,7 +197,7 @@ workflow = (
|
|
|
155
197
|
)
|
|
156
198
|
```
|
|
157
199
|
|
|
158
|
-
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.
|
|
159
201
|
|
|
160
202
|
## RAG with FGA
|
|
161
203
|
|
|
@@ -185,7 +227,8 @@ vector_store = VectorStoreIndex.from_documents(documents)
|
|
|
185
227
|
# Create a retriever:
|
|
186
228
|
base_retriever = vector_store.as_retriever()
|
|
187
229
|
|
|
188
|
-
# 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.
|
|
189
232
|
retriever = FGARetriever(
|
|
190
233
|
base_retriever,
|
|
191
234
|
build_query=lambda node: ClientCheckRequest(
|
|
@@ -207,6 +250,63 @@ response = query_engine.query("What is the forecast for ZEKO?")
|
|
|
207
250
|
print(response)
|
|
208
251
|
```
|
|
209
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
|
+
|
|
210
310
|
---
|
|
211
311
|
|
|
212
312
|
<p align="center">
|
|
@@ -6,13 +6,58 @@
|
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
|
-
>
|
|
10
|
-
> `auth0-ai-langchain` is currently under development and it is not intended to be used in production, and therefore has no official support.
|
|
9
|
+
> ⚠️ **WARNING**: `auth0-ai-langchain` is currently under development and it is not intended to be used in production, and therefore has no official support.
|
|
11
10
|
|
|
12
11
|
```bash
|
|
13
12
|
pip install auth0-ai-langchain
|
|
14
13
|
```
|
|
15
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
|
+
|
|
16
61
|
## Authorization for Tools
|
|
17
62
|
|
|
18
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.
|
|
@@ -22,19 +67,12 @@ Full example of [Authorization for Tools](https://github.com/auth0-lab/auth0-ai-
|
|
|
22
67
|
1. Create an instance of FGA Authorizer:
|
|
23
68
|
|
|
24
69
|
```python
|
|
25
|
-
from auth0_ai_langchain.fga
|
|
70
|
+
from auth0_ai_langchain.fga import FGAAuthorizer
|
|
26
71
|
|
|
72
|
+
# If not provided, FGA settings will be read from env variables: `FGA_STORE_ID`, `FGA_CLIENT_ID`, `FGA_CLIENT_SECRET`, etc.
|
|
27
73
|
fga = FGAAuthorizer.create()
|
|
28
74
|
```
|
|
29
75
|
|
|
30
|
-
**Note**: Here, you can configure and specify your FGA credentials. By `default`, they are read from environment variables:
|
|
31
|
-
|
|
32
|
-
```sh
|
|
33
|
-
FGA_STORE_ID="<fga-store-id>"
|
|
34
|
-
FGA_CLIENT_ID="<fga-client-id>"
|
|
35
|
-
FGA_CLIENT_SECRET="<fga-client-secret>"
|
|
36
|
-
```
|
|
37
|
-
|
|
38
76
|
2. Define the FGA query (`build_query`) and, optionally, the `on_unauthorized` handler:
|
|
39
77
|
|
|
40
78
|
```python
|
|
@@ -52,10 +90,10 @@ async def build_fga_query(tool_input):
|
|
|
52
90
|
def on_unauthorized(tool_input):
|
|
53
91
|
return f"The user is not allowed to buy {tool_input["qty"]} shares of {tool_input["ticker"]}."
|
|
54
92
|
|
|
55
|
-
use_fga = fga(
|
|
93
|
+
use_fga = fga(
|
|
56
94
|
build_query=build_fga_query,
|
|
57
95
|
on_unauthorized=on_unauthorized,
|
|
58
|
-
)
|
|
96
|
+
)
|
|
59
97
|
```
|
|
60
98
|
|
|
61
99
|
**Note**: The parameters given to the `build_query` and `on_unauthorized` functions are the same as those provided to the tool function.
|
|
@@ -81,7 +119,7 @@ buy_tool = StructuredTool(
|
|
|
81
119
|
|
|
82
120
|
## Calling APIs On User's Behalf
|
|
83
121
|
|
|
84
|
-
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.
|
|
85
123
|
|
|
86
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).
|
|
87
125
|
|
|
@@ -89,19 +127,23 @@ Full Example of [Calling APIs On User's Behalf](https://github.com/auth0-lab/aut
|
|
|
89
127
|
|
|
90
128
|
```python
|
|
91
129
|
from auth0_ai_langchain.auth0_ai import Auth0AI
|
|
92
|
-
from auth0_ai_langchain.federated_connections import
|
|
130
|
+
from auth0_ai_langchain.federated_connections import get_credentials_for_connection
|
|
93
131
|
from langchain_core.tools import StructuredTool
|
|
94
132
|
|
|
133
|
+
# If not provided, Auth0 settings will be read from env variables: `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET`
|
|
95
134
|
auth0_ai = Auth0AI()
|
|
96
135
|
|
|
97
136
|
with_google_calendar_access = auth0_ai.with_federated_connection(
|
|
98
137
|
connection="google-oauth2",
|
|
99
|
-
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(),
|
|
100
142
|
)
|
|
101
143
|
|
|
102
144
|
def tool_function(date: datetime):
|
|
103
|
-
|
|
104
|
-
# Call Google API
|
|
145
|
+
credentials = get_credentials_for_connection()
|
|
146
|
+
# Call Google API using credentials["access_token"]
|
|
105
147
|
|
|
106
148
|
check_calendar_tool = with_google_calendar_access(
|
|
107
149
|
StructuredTool(
|
|
@@ -133,7 +175,7 @@ workflow = (
|
|
|
133
175
|
)
|
|
134
176
|
```
|
|
135
177
|
|
|
136
|
-
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.
|
|
137
179
|
|
|
138
180
|
## RAG with FGA
|
|
139
181
|
|
|
@@ -163,7 +205,8 @@ vector_store = VectorStoreIndex.from_documents(documents)
|
|
|
163
205
|
# Create a retriever:
|
|
164
206
|
base_retriever = vector_store.as_retriever()
|
|
165
207
|
|
|
166
|
-
# 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.
|
|
167
210
|
retriever = FGARetriever(
|
|
168
211
|
base_retriever,
|
|
169
212
|
build_query=lambda node: ClientCheckRequest(
|
|
@@ -185,6 +228,63 @@ response = query_engine.query("What is the forecast for ZEKO?")
|
|
|
185
228
|
print(response)
|
|
186
229
|
```
|
|
187
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
|
+
|
|
188
288
|
---
|
|
189
289
|
|
|
190
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
|