auth0-ai-langchain 0.1.0__py3-none-any.whl

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.

@@ -0,0 +1,158 @@
1
+ import os
2
+
3
+ from typing import Callable, Optional
4
+ from langchain_core.retrievers import BaseRetriever
5
+ from langchain_core.documents import Document
6
+ from openfga_sdk.client.client import ClientBatchCheckRequest
7
+ from pydantic import PrivateAttr
8
+ from openfga_sdk import ClientConfiguration, OpenFgaClient
9
+ from openfga_sdk.client.models import ClientBatchCheckItem
10
+ from openfga_sdk.sync import OpenFgaClient as OpenFgaClientSync
11
+ from openfga_sdk.credentials import CredentialConfiguration, Credentials
12
+
13
+
14
+ class FGARetriever(BaseRetriever):
15
+ """
16
+ FGARetriever integrates with OpenFGA to filter documents based on fine-grained authorization (FGA).
17
+ """
18
+
19
+ _retriever: BaseRetriever = PrivateAttr()
20
+ _fga_configuration: ClientConfiguration = PrivateAttr()
21
+ _query_builder: Callable[[Document], ClientBatchCheckItem] = PrivateAttr()
22
+
23
+ def __init__(
24
+ self,
25
+ retriever: BaseRetriever,
26
+ build_query: Callable[[Document], ClientBatchCheckItem],
27
+ fga_configuration: Optional[ClientConfiguration] = None,
28
+ ):
29
+ """
30
+ Initialize the FGARetriever with the specified retriever, query builder, and configuration.
31
+
32
+ Args:
33
+ retriever (BaseRetriever): The retriever used to fetch documents.
34
+ build_query (Callable[[Document], ClientBatchCheckItem]): Function to convert documents into FGA queries.
35
+ fga_configuration (Optional[ClientConfiguration]): Configuration for the OpenFGA client. If not provided, defaults to environment variables.
36
+ """
37
+ super().__init__()
38
+ self._retriever = retriever
39
+ self._fga_configuration = fga_configuration or ClientConfiguration(
40
+ api_url=os.getenv("FGA_API_URL") or "https://api.us1.fga.dev",
41
+ store_id=os.getenv("FGA_STORE_ID"),
42
+ credentials=Credentials(
43
+ method="client_credentials",
44
+ configuration=CredentialConfiguration(
45
+ api_issuer=os.getenv("FGA_API_TOKEN_ISSUER") or "auth.fga.dev",
46
+ api_audience=os.getenv("FGA_API_AUDIENCE")
47
+ or "https://api.us1.fga.dev/",
48
+ client_id=os.getenv("FGA_CLIENT_ID"),
49
+ client_secret=os.getenv("FGA_CLIENT_SECRET"),
50
+ ),
51
+ ),
52
+ )
53
+ self._query_builder = build_query
54
+
55
+ async def _async_filter_FGA(self, docs: list[Document]) -> list[Document]:
56
+ """
57
+ Asynchronously filter documents using OpenFGA.
58
+
59
+ Args:
60
+ docs (List[Document]): List of documents to filter.
61
+
62
+ Returns:
63
+ List[Document]: Filtered list of documents authorized by FGA.
64
+ """
65
+ async with OpenFgaClient(self._fga_configuration) as fga_client:
66
+ all_checks = [self._query_builder(doc) for doc in docs]
67
+ unique_checks = list(
68
+ {
69
+ (check.relation, check.object, check.user): check
70
+ for check in all_checks
71
+ }.values()
72
+ )
73
+
74
+ doc_to_obj = {doc: check.object for check, doc in zip(all_checks, docs)}
75
+
76
+ fga_response = await fga_client.batch_check(
77
+ ClientBatchCheckRequest(checks=unique_checks)
78
+ )
79
+ await fga_client.close()
80
+
81
+ permissions_map = {
82
+ result.request.object: result.allowed for result in fga_response.result
83
+ }
84
+
85
+ return [
86
+ doc
87
+ for doc in docs
88
+ if doc_to_obj[doc] in permissions_map
89
+ and permissions_map[doc_to_obj[doc]]
90
+ ]
91
+
92
+ async def _aget_relevant_documents(self, query, *, run_manager) -> list[Document]:
93
+ """
94
+ Asynchronously retrieve relevant documents from the base retrieve and filter them using FGA.
95
+
96
+ Args:
97
+ query (str): The query for retrieving documents.
98
+ run_manager (Optional[object]): Optional manager for tracking runs.
99
+
100
+ Returns:
101
+ List[Document]: Filtered and relevant documents.
102
+ """
103
+ docs = await self._retriever._aget_relevant_documents(
104
+ query, run_manager=run_manager
105
+ )
106
+ docs = await self._async_filter_FGA(docs)
107
+ return docs
108
+
109
+ def _filter_FGA(self, docs: list[Document]) -> list[Document]:
110
+ """
111
+ Synchronously filter documents using OpenFGA.
112
+
113
+ Args:
114
+ docs (List[Document]): List of documents to filter.
115
+
116
+ Returns:
117
+ List[Document]: Filtered list of documents authorized by FGA.
118
+ """
119
+ with OpenFgaClientSync(self._fga_configuration) as fga_client:
120
+ all_checks = [self._query_builder(doc) for doc in docs]
121
+ unique_checks = list(
122
+ {
123
+ (check.relation, check.object, check.user): check
124
+ for check in all_checks
125
+ }.values()
126
+ )
127
+
128
+ doc_to_obj = {doc.id: check.object for check, doc in zip(all_checks, docs)}
129
+
130
+ fga_response = fga_client.batch_check(
131
+ ClientBatchCheckRequest(checks=unique_checks)
132
+ )
133
+
134
+ permissions_map = {
135
+ result.request.object: result.allowed for result in fga_response.result
136
+ }
137
+
138
+ return [
139
+ doc
140
+ for doc in docs
141
+ if doc_to_obj[doc.id] in permissions_map
142
+ and permissions_map[doc_to_obj[doc.id]]
143
+ ]
144
+
145
+ def _get_relevant_documents(self, query, *, run_manager) -> list[Document]:
146
+ """
147
+ Retrieve relevant documents and filter them using FGA.
148
+
149
+ Args:
150
+ query (str): The query for retrieving documents.
151
+ run_manager (Optional[object]): Optional manager for tracking runs.
152
+
153
+ Returns:
154
+ List[Document]: Filtered and relevant documents.
155
+ """
156
+ docs = self._retriever._get_relevant_documents(query, run_manager=run_manager)
157
+ docs = self._filter_FGA(docs)
158
+ return docs
@@ -0,0 +1,3 @@
1
+ from .FGARetriever import FGARetriever
2
+
3
+ __all__ = ["FGARetriever"]
@@ -0,0 +1,43 @@
1
+ from typing import Callable, Optional
2
+ from langchain_core.runnables.config import RunnableConfig
3
+ from langchain_core.tools import BaseTool
4
+ from auth0_ai.credentials import Credential
5
+ from auth0_ai.authorizers.types import AuthorizerParams
6
+ from auth0_ai.authorizers.federated_connection_authorizer import FederatedConnectionAuthorizerParams
7
+ from .federated_connections.federated_connection_authorizer import FederatedConnectionAuthorizer
8
+ from .ciba.ciba_graph.ciba_graph import CIBAGraph
9
+ from .ciba.ciba_graph.types import CIBAGraphOptions
10
+
11
+ def get_access_token(config: RunnableConfig) -> Credential:
12
+ """
13
+ Fetch the access token obtained during the CIBA flow.
14
+
15
+ Attributes:
16
+ config(RunnableConfig): LangGraph runnable configuration instance.
17
+ """
18
+ return config.get("configurable", {}).get("_credentials", {}).get("access_token")
19
+
20
+ class Auth0AI():
21
+ def __init__(self, config: Optional[AuthorizerParams] = None):
22
+ self._graph: Optional[CIBAGraph] = None
23
+ self.config = config
24
+
25
+ def with_async_user_confirmation(self, **options: CIBAGraphOptions) -> CIBAGraph:
26
+ """
27
+ Initializes and registers a state graph for conditional trade operations using CIBA.
28
+
29
+ Attributes:
30
+ options (Optional[CIBAGraphOptions]): The base CIBA options.
31
+ """
32
+ self._graph = CIBAGraph(CIBAGraphOptions(**options), self.config)
33
+ return self._graph
34
+
35
+ def with_federated_connection(self, **options: FederatedConnectionAuthorizerParams) -> Callable[[BaseTool], BaseTool]:
36
+ """
37
+ Protects a tool execution with the Federated Connection authorizer.
38
+
39
+ Attributes:
40
+ options (FederatedConnectionAuthorizerParams): The Federated Connections authorizer options.
41
+ """
42
+ authorizer = FederatedConnectionAuthorizer(FederatedConnectionAuthorizerParams(**options), self.config)
43
+ return authorizer.authorizer()
File without changes
@@ -0,0 +1,109 @@
1
+ from typing import Awaitable, Hashable, List, Optional, Callable, Any, Union
2
+ from langchain_core.tools import StructuredTool
3
+ from langchain_core.tools.base import BaseTool
4
+ from langgraph.graph import StateGraph, END, START
5
+ from langchain_core.runnables import Runnable
6
+ from auth0_ai.authorizers.types import AuthorizerParams
7
+ from ..types import Auth0Nodes
8
+ from .initialize_ciba import initialize_ciba
9
+ from .initialize_hitl import initialize_hitl
10
+ from .types import CIBAGraphOptions, CIBAOptions, ProtectedTool, BaseState
11
+
12
+ class CIBAGraph():
13
+ def __init__(
14
+ self,
15
+ options: Optional[CIBAGraphOptions] = None,
16
+ authorizer_params: Optional[AuthorizerParams] = None,
17
+ ):
18
+ self.options = options
19
+ self.authorizer_params = authorizer_params
20
+ self.tools: List[ProtectedTool] = []
21
+ self.graph: Optional[StateGraph] = None
22
+
23
+ def get_tools(self) -> List[ProtectedTool]:
24
+ return self.tools
25
+
26
+ def get_graph(self) -> Optional[StateGraph]:
27
+ return self.graph
28
+
29
+ def get_options(self) -> Optional[CIBAGraphOptions]:
30
+ return self.options
31
+
32
+ def get_authorizer_params(self) -> Optional[AuthorizerParams]:
33
+ return self.authorizer_params
34
+
35
+ def register_nodes(
36
+ self,
37
+ graph: StateGraph,
38
+ ) -> StateGraph:
39
+ self.graph = graph
40
+
41
+ # Add CIBA HITL and CIBA nodes
42
+ self.graph.add_node(Auth0Nodes.AUTH0_CIBA_HITL.value, initialize_hitl(self))
43
+ self.graph.add_node(Auth0Nodes.AUTH0_CIBA.value, initialize_ciba(self))
44
+ self.graph.add_conditional_edges(
45
+ Auth0Nodes.AUTH0_CIBA.value,
46
+ lambda state: END if getattr(state, "auth0", {}).get("error") else Auth0Nodes.AUTH0_CIBA_HITL.value,
47
+ )
48
+
49
+ return graph
50
+
51
+ def protect_tool(
52
+ self,
53
+ tool: Union[BaseTool, Callable],
54
+ options: CIBAOptions,
55
+ ) -> StructuredTool:
56
+ """
57
+ Authorize Options to start CIBA flow.
58
+
59
+ Attributes:
60
+ tool (Union[BaseTool, Callable]): The tool to be protected.
61
+ options (CIBAOptions): The CIBA options.
62
+ """
63
+
64
+ # Merge default options with tool-specific options
65
+ merged_options = {**self.options, **options.__dict__} if isinstance(self.options, dict) else {**vars(self.options), **vars(options)}
66
+
67
+ if merged_options["on_approve_go_to"] is None:
68
+ raise ValueError(f"[{tool.name}] on_approve_go_to is required")
69
+
70
+ if merged_options["on_reject_go_to"] is None:
71
+ raise ValueError(f"[{tool.name}] on_reject_go_to is required")
72
+
73
+ self.tools.append(ProtectedTool(tool_name=tool.name, options=merged_options))
74
+
75
+ return tool
76
+
77
+ def with_auth(self, path: Union[
78
+ Callable[..., Union[Hashable, list[Hashable]]],
79
+ Callable[..., Awaitable[Union[Hashable, list[Hashable]]]],
80
+ Runnable[Any, Union[Hashable, list[Hashable]]],
81
+ ]):
82
+ """
83
+ A wrapper for the callable that determines the next node or nodes using a protected tool.
84
+
85
+ Attributes:
86
+ path (Union[Callable[..., Union[Hashable, list[Hashable]]], Callable[..., Awaitable[Union[Hashable, list[Hashable]]]], Runnable[Any, Union[Hashable, list[Hashable]]]])): The callable that determines the next node or nodes using a protected tool.
87
+ """
88
+ def wrapper(*args):
89
+ if not callable(path):
90
+ return START
91
+
92
+ state: BaseState = args[0]
93
+ messages = state.get("messages")
94
+ last_message = messages[-1] if messages else None
95
+
96
+ # Call default path if there are no tool calls
97
+ if not last_message or not hasattr(last_message, "tool_calls") or not last_message.tool_calls:
98
+ return path(*args)
99
+
100
+ tool_name = last_message.tool_calls[0]["name"]
101
+ tool = next((t for t in self.tools if t.tool_name == tool_name), None)
102
+
103
+ if tool:
104
+ return Auth0Nodes.AUTH0_CIBA.value
105
+
106
+ # Call default path if tool is not protected
107
+ return path(*args)
108
+
109
+ return wrapper
@@ -0,0 +1,91 @@
1
+ import os
2
+ from langgraph.types import Command
3
+ from langgraph_sdk import get_client
4
+ from langchain_core.runnables.config import RunnableConfig
5
+ from auth0_ai.authorizers.ciba_authorizer import CIBAAuthorizer
6
+ from ..types import Auth0Graphs, Auth0Nodes
7
+ from .types import ICIBAGraph, BaseState
8
+ from .utils import get_tool_definition
9
+
10
+ def initialize_ciba(ciba_graph: ICIBAGraph):
11
+ async def handler(state: BaseState, config: RunnableConfig):
12
+ try:
13
+ ciba_params = ciba_graph.get_options()
14
+ tools = ciba_graph.get_tools()
15
+ tool_definition = get_tool_definition(state, tools)
16
+
17
+ if not tool_definition:
18
+ return Command(resume=True)
19
+
20
+ graph = ciba_graph.get_graph()
21
+ metadata, tool = tool_definition["metadata"], tool_definition["tool"]
22
+ ciba_options = metadata.options
23
+
24
+ langgraph = get_client(url=os.getenv("LANGGRAPH_API_URL", "http://localhost:54367"))
25
+
26
+ # Check if CIBA Poller Graph exists
27
+ search_result = await langgraph.assistants.search(graph_id=Auth0Graphs.CIBA_POLLER.value)
28
+ if not search_result:
29
+ raise ValueError(
30
+ f"[{Auth0Nodes.AUTH0_CIBA}] \"{Auth0Graphs.CIBA_POLLER}\" does not exist. Make sure to register the graph in your \"langgraph.json\"."
31
+ )
32
+
33
+ if ciba_options["on_approve_go_to"] not in graph.nodes:
34
+ raise ValueError(f"[{Auth0Nodes.AUTH0_CIBA}] \"{ciba_options["on_approve_go_to"]}\" is not a valid node.")
35
+
36
+ if ciba_options["on_reject_go_to"] not in graph.nodes:
37
+ raise ValueError(f"[{Auth0Nodes.AUTH0_CIBA}] \"{ciba_options["on_reject_go_to"]}\" is not a valid node.")
38
+
39
+ scheduler = ciba_params.config["scheduler"]
40
+ on_resume_invoke = ciba_params.config["on_resume_invoke"]
41
+ audience = ciba_params.audience
42
+
43
+ if not scheduler:
44
+ raise ValueError(f"[{Auth0Nodes.AUTH0_CIBA}] \"scheduler\" must be a \"function\" or a \"string\".")
45
+
46
+ if not on_resume_invoke:
47
+ raise ValueError(f"[{Auth0Nodes.AUTH0_CIBA}] \"on_resume_invoke\" must be defined.")
48
+
49
+ user_id = config.get("configurable", {}).get("user_id")
50
+ thread_id = config.get("metadata", {}).get("thread_id")
51
+
52
+ ciba_response = await CIBAAuthorizer.start(
53
+ {
54
+ "user_id": user_id,
55
+ "scope": ciba_options["scope"] or "openid",
56
+ "audience": audience,
57
+ "binding_message": ciba_options["binding_message"],
58
+ },
59
+ ciba_graph.get_authorizer_params(),
60
+ tool["args"],
61
+ )
62
+
63
+ scheduler_params = {
64
+ "tool_id": tool["id"],
65
+ "user_id": user_id,
66
+ "ciba_graph_id": Auth0Graphs.CIBA_POLLER.value,
67
+ "thread_id": thread_id,
68
+ "ciba_response": ciba_response,
69
+ "on_resume_invoke": on_resume_invoke,
70
+ }
71
+
72
+ if callable(scheduler):
73
+ # Use Custom Scheduler
74
+ await scheduler(scheduler_params)
75
+ elif isinstance(scheduler, str):
76
+ # Use Langgraph SDK to schedule the task
77
+ await langgraph.crons.create_for_thread(
78
+ thread_id,
79
+ scheduler_params["ciba_graph_id"],
80
+ schedule="*/1 * * * *", # Default to every minute
81
+ input=scheduler_params,
82
+ )
83
+
84
+ print("CIBA Task Scheduled")
85
+ except Exception as e:
86
+ print(e)
87
+ state["auth0"] = {"error": str(e)}
88
+
89
+ return state
90
+
91
+ return handler
@@ -0,0 +1,50 @@
1
+ from typing import Awaitable, Callable
2
+ from langchain_core.messages import ToolMessage, AIMessage, ToolCall
3
+ from langgraph.types import interrupt, Command
4
+ from .types import ICIBAGraph, BaseState
5
+ from .utils import get_tool_definition
6
+ from auth0_ai.authorizers.ciba_authorizer import CibaAuthorizerCheckResponse
7
+
8
+ def initialize_hitl(ciba_graph: ICIBAGraph) -> Callable[[BaseState], Awaitable[Command]]:
9
+ async def handler(state: BaseState) -> Command:
10
+ tools = ciba_graph.get_tools()
11
+ tool_definition = get_tool_definition(state, tools)
12
+
13
+ # if no tool calls, resume
14
+ if not tool_definition:
15
+ return Command(resume=True)
16
+
17
+ # wait for user approval
18
+ human_review = interrupt("A push notification has been sent to your device.")
19
+
20
+ metadata, tool, message = tool_definition["metadata"], tool_definition["tool"], tool_definition["message"]
21
+
22
+ if human_review["status"] == CibaAuthorizerCheckResponse.APPROVED.value:
23
+ updated_message = AIMessage(
24
+ id=message.id,
25
+ content="The user has approved the transaction",
26
+ tool_calls=[
27
+ ToolCall(
28
+ name=tool["name"],
29
+ args=tool["args"],
30
+ id=tool["id"],
31
+ )
32
+ ],
33
+ )
34
+
35
+ return Command(
36
+ goto=metadata.options["on_approve_go_to"],
37
+ update={"messages": [updated_message]},
38
+ )
39
+ else:
40
+ tool_message = ToolMessage(
41
+ name=tool["name"],
42
+ content="The user has rejected the transaction.",
43
+ tool_call_id=tool["id"],
44
+ )
45
+ return Command(
46
+ goto=metadata.options["on_reject_go_to"],
47
+ update={"messages": [tool_message]},
48
+ )
49
+
50
+ return handler
@@ -0,0 +1,115 @@
1
+ from typing import Optional, List, Callable, Union, Awaitable, TypedDict
2
+ from abc import ABC, abstractmethod
3
+ from langgraph.graph import StateGraph
4
+ from langchain_core.messages import AIMessage, ToolMessage
5
+ from auth0_ai.authorizers.types import AuthorizerParams
6
+ from auth0_ai.authorizers.ciba_authorizer import AuthorizeResponse
7
+
8
+ class Auth0State(TypedDict):
9
+ error: str
10
+
11
+ class BaseState(TypedDict):
12
+ task_id: str
13
+ messages: List[Union[AIMessage, ToolMessage]]
14
+ auth0: Optional[Auth0State] = None
15
+
16
+ class SchedulerParams:
17
+ def __init__(
18
+ self,
19
+ user_id: str,
20
+ thread_id: str,
21
+ ciba_graph_id: str,
22
+ ciba_response: AuthorizeResponse,
23
+ tool_id: Optional[str] = None,
24
+ on_resume_invoke: str = "",
25
+ ):
26
+ self.user_id = user_id
27
+ self.thread_id = thread_id
28
+ self.tool_id = tool_id
29
+ self.on_resume_invoke = on_resume_invoke
30
+ self.ciba_graph_id = ciba_graph_id
31
+ self.ciba_response = ciba_response
32
+
33
+ class CIBAOptions():
34
+ """
35
+ The CIBA options.
36
+
37
+ Attributes:
38
+ binding_message (Union[str, Callable[..., Awaitable[str]]]): A human-readable string to display to the user, or a function that resolves it.
39
+ scope (Optional[str]): Space-separated list of OIDC and custom API scopes.
40
+ on_approve_go_to (Optional[str]): A node name to redirect the flow after user approval.
41
+ on_reject_go_to (Optional[str]): A node name to redirect the flow after user rejection.
42
+ audience (Optional[str]): Unique identifier of the audience for an issued token.
43
+ request_expiry (Optional[int]): To configure a custom expiry time in seconds for CIBA request, pass a number between 1 and 300.
44
+ """
45
+ def __init__(
46
+ self,
47
+ binding_message: Union[str, Callable[..., Awaitable[str]]],
48
+ scope: Optional[str] = None,
49
+ on_approve_go_to: Optional[str] = None,
50
+ on_reject_go_to: Optional[str] = None,
51
+ audience: Optional[str] = None,
52
+ request_expiry: Optional[int] = None,
53
+ ):
54
+ self.binding_message = binding_message
55
+ self.scope = scope
56
+ self.on_approve_go_to = on_approve_go_to
57
+ self.on_reject_go_to = on_reject_go_to
58
+ self.audience = audience
59
+ self.request_expiry = request_expiry
60
+
61
+ class ProtectedTool():
62
+ def __init__(self, tool_name: str, options: CIBAOptions):
63
+ self.tool_name = tool_name
64
+ self.options = options
65
+
66
+ class CIBAGraphOptionsConfig:
67
+ def __init__(self, on_resume_invoke: str, scheduler: Union[str, Callable[[SchedulerParams], Awaitable[None]]]):
68
+ self.on_resume_invoke = on_resume_invoke
69
+ self.scheduler = scheduler
70
+
71
+ class CIBAGraphOptions():
72
+ """
73
+ The base CIBA options.
74
+
75
+ Attributes:
76
+ config (CIBAGraphOptionsConfig): Configuration options.
77
+ scope (Optional[str]): Space-separated list of OIDC and custom API scopes.
78
+ on_approve_go_to (Optional[str]): A node name to redirect the flow after user approval.
79
+ on_reject_go_to (Optional[str]): A node name to redirect the flow after user rejection.
80
+ audience (Optional[str]): Unique identifier of the audience for an issued token.
81
+ request_expiry (Optional[int]): To configure a custom expiry time in seconds for CIBA request, pass a number between 1 and 300.
82
+ """
83
+ def __init__(
84
+ self,
85
+ config: CIBAGraphOptionsConfig,
86
+ scope: Optional[str] = None,
87
+ on_approve_go_to: Optional[str] = None,
88
+ on_reject_go_to: Optional[str] = None,
89
+ audience: Optional[str] = None,
90
+ request_expiry: Optional[int] = None,
91
+
92
+ ):
93
+ self.config = config
94
+ self.scope = scope
95
+ self.on_approve_go_to = on_approve_go_to
96
+ self.on_reject_go_to = on_reject_go_to
97
+ self.audience = audience
98
+ self.request_expiry = request_expiry
99
+
100
+ class ICIBAGraph(ABC):
101
+ @abstractmethod
102
+ def get_tools(self) -> List[ProtectedTool]:
103
+ pass
104
+
105
+ @abstractmethod
106
+ def get_graph(self) -> StateGraph:
107
+ pass
108
+
109
+ @abstractmethod
110
+ def get_authorizer_params(self) -> Optional[AuthorizerParams]:
111
+ pass
112
+
113
+ @abstractmethod
114
+ def get_options(self) -> Optional[CIBAGraphOptions]:
115
+ pass
@@ -0,0 +1,17 @@
1
+ from typing import Optional, List
2
+ from .types import ProtectedTool, BaseState
3
+
4
+ def get_tool_definition(state: BaseState, tools: List[ProtectedTool]) -> Optional[dict]:
5
+ message = state["messages"][-1]
6
+
7
+ if not hasattr(message, "tool_calls") or not message.tool_calls:
8
+ return None
9
+
10
+ tool_calls = message.tool_calls
11
+ tool = tool_calls[-1]
12
+ metadata = next((t for t in tools if t.tool_name == tool["name"]), None)
13
+
14
+ if not metadata:
15
+ return None
16
+
17
+ return {"metadata": metadata, "tool": tool, "message": message}
@@ -0,0 +1,94 @@
1
+ import os
2
+ from langgraph.graph import StateGraph, END, START
3
+ from typing import Awaitable, Callable, Union, Optional, TypedDict
4
+ from langgraph_sdk import get_client
5
+ from langgraph_sdk.schema import Command
6
+ from auth0_ai.authorizers.ciba_authorizer import CIBAAuthorizer, CibaAuthorizerCheckResponse, AuthorizeResponse
7
+ from auth0_ai.credentials import Credentials
8
+ from auth0_ai.token_response import TokenResponse
9
+ from langchain_auth0_ai.ciba.types import Auth0Graphs
10
+
11
+ class State(TypedDict):
12
+ ciba_response: AuthorizeResponse
13
+ on_resume_invoke: str
14
+ thread_id: str
15
+ user_id: str
16
+
17
+ # Internal
18
+ task_id: str
19
+ tool_id: str
20
+ status: CibaAuthorizerCheckResponse
21
+ token_response: Optional[TokenResponse]
22
+
23
+ def ciba_poller_graph(on_stop_scheduler: Union[str, Callable[[State], Awaitable[None]]]):
24
+ """
25
+ A LangGraph graph to monitor the status of a CIBA transaction.
26
+
27
+ Attributes:
28
+ on_stop_scheduler (Union[str, Callable[[State], Awaitable[None]]]): A graph name to redirect the flow, or a function to execute when the CIBA transaction expires.
29
+ """
30
+ async def check_status(state: State):
31
+ try:
32
+ res = await CIBAAuthorizer.check(state["ciba_response"]["auth_req_id"])
33
+ state["token_response"] = res.get("token")
34
+ state["status"] = res.get("status")
35
+ except Exception as e:
36
+ print(f"Error in check_status: {e}")
37
+ return state
38
+
39
+ async def stop_scheduler(state: State):
40
+ try:
41
+ if isinstance(on_stop_scheduler, str):
42
+ langgraph = get_client(url=os.getenv("LANGGRAPH_API_URL", "http://localhost:54367"))
43
+ await langgraph.crons.create_for_thread(state.thread_id, Auth0Graphs.CIBA_POLLER.value)
44
+ elif callable(on_stop_scheduler):
45
+ await on_stop_scheduler(state)
46
+ except Exception as e:
47
+ print(f"Error in stop_scheduler: {e}")
48
+ return state
49
+
50
+ async def resume_agent(state: State):
51
+ langgraph = get_client(url=os.getenv("LANGGRAPH_API_URL", "http://localhost:54367"))
52
+ _credentials: Credentials = None
53
+
54
+ try:
55
+ if state["status"] == CibaAuthorizerCheckResponse.APPROVED:
56
+ _credentials = {
57
+ "access_token": {
58
+ "type": state["token_response"].get("token_type", "Bearer"),
59
+ "value": state["token_response"].get("access_token"),
60
+ }
61
+ }
62
+
63
+ await langgraph.runs.wait(
64
+ state["thread_id"],
65
+ state["on_resume_invoke"],
66
+ config={
67
+ "configurable": {"_credentials": _credentials} # this is only for this run / thread_id
68
+ },
69
+ command=Command(resume={"status": state["status"]})
70
+ )
71
+ except Exception as e:
72
+ print(f"Error in resume_agent: {e}")
73
+
74
+ return state
75
+
76
+ async def should_continue(state: State):
77
+ status = state.get("status")
78
+ if status == CibaAuthorizerCheckResponse.PENDING:
79
+ return END
80
+ elif status == CibaAuthorizerCheckResponse.EXPIRED:
81
+ return "stop_scheduler"
82
+ elif status in [CibaAuthorizerCheckResponse.APPROVED, CibaAuthorizerCheckResponse.REJECTED]:
83
+ return "resume_agent"
84
+ return END
85
+
86
+ state_graph = StateGraph(State)
87
+ state_graph.add_node("check_status", check_status)
88
+ state_graph.add_node("stop_scheduler", stop_scheduler)
89
+ state_graph.add_node("resume_agent", resume_agent)
90
+ state_graph.add_edge(START, "check_status")
91
+ state_graph.add_edge("resume_agent", "stop_scheduler")
92
+ state_graph.add_conditional_edges("check_status", should_continue)
93
+
94
+ return state_graph
@@ -0,0 +1,8 @@
1
+ from enum import Enum
2
+
3
+ class Auth0Nodes(Enum):
4
+ AUTH0_CIBA_HITL = "AUTH0_CIBA_HITL"
5
+ AUTH0_CIBA = "AUTH0_CIBA"
6
+
7
+ class Auth0Graphs(Enum):
8
+ CIBA_POLLER = "AUTH0_CIBA_POLLER"
@@ -0,0 +1,3 @@
1
+ from auth0_ai.interrupts.federated_connection_interrupt import FederatedConnectionError as FederatedConnectionError
2
+ from auth0_ai.authorizers.federated_connection_authorizer import get_access_token_for_connection as get_access_token_for_connection
3
+ from .federated_connection_authorizer import FederatedConnectionAuthorizer as FederatedConnectionAuthorizer
@@ -0,0 +1,52 @@
1
+ import copy
2
+ from abc import ABC
3
+ from auth0_ai.authorizers.federated_connection_authorizer import FederatedConnectionAuthorizerBase, FederatedConnectionAuthorizerParams
4
+ from auth0_ai.authorizers.types import AuthorizerParams
5
+ from auth0_ai.interrupts.federated_connection_interrupt import FederatedConnectionInterrupt
6
+ from langchain_core.tools import BaseTool, tool
7
+ from langchain_core.runnables import ensure_config
8
+ from ..utils.interrupt import to_graph_interrupt
9
+
10
+ async def get_refresh_token(*_args, **_kwargs) -> str | None:
11
+ return ensure_config().get("configurable", {}).get("_credentials", {}).get("refresh_token")
12
+
13
+ class FederatedConnectionAuthorizer(FederatedConnectionAuthorizerBase, ABC):
14
+ def __init__(
15
+ self,
16
+ options: FederatedConnectionAuthorizerParams,
17
+ config: AuthorizerParams = None,
18
+ ):
19
+ if options.refresh_token.value is None:
20
+ options = copy.copy(options)
21
+ options.refresh_token.value = get_refresh_token
22
+
23
+ super().__init__(options, config)
24
+
25
+ def _handle_authorization_interrupts(self, err: FederatedConnectionInterrupt) -> None:
26
+ raise to_graph_interrupt(err)
27
+
28
+ def authorizer(self):
29
+ def wrapped_tool(t: BaseTool) -> BaseTool:
30
+ async def execute_fn(*_args, **kwargs):
31
+ return await t.ainvoke(input=kwargs)
32
+
33
+ tool_fn = self.protect(
34
+ lambda *_args, **_kwargs: {
35
+ "thread_id": ensure_config().get("configurable", {}).get("thread_id"),
36
+ "checkpoint_ns": ensure_config().get("configurable", {}).get("checkpoint_ns"),
37
+ "run_id": ensure_config().get("configurable", {}).get("run_id"),
38
+ "tool_call_id": ensure_config().get("configurable", {}).get("tool_call_id"), # TODO: review this
39
+ },
40
+ execute_fn
41
+ )
42
+ tool_fn.__name__ = t.name
43
+
44
+ return tool(
45
+ tool_fn,
46
+ description=t.description,
47
+ return_direct=t.return_direct,
48
+ args_schema=t.args_schema,
49
+ response_format=t.response_format,
50
+ )
51
+
52
+ return wrapped_tool
@@ -0,0 +1,3 @@
1
+ from auth0_ai.authorizers.fga_authorizer import FGAAuthorizer, FGAAuthorizerOptions
2
+
3
+ __all__ = ["FGAAuthorizer", "FGAAuthorizerOptions"]
@@ -0,0 +1,13 @@
1
+ from auth0_ai.interrupts.auth0_interrupt import Auth0Interrupt
2
+ from langgraph.errors import GraphInterrupt
3
+
4
+
5
+ def to_graph_interrupt(interrupt: Auth0Interrupt) -> GraphInterrupt:
6
+ return GraphInterrupt([
7
+ {
8
+ "value": interrupt,
9
+ "when": "during",
10
+ "resumable": True,
11
+ "ns": [f"auth0AI:{interrupt.__class__.__name__}:{interrupt.code}"]
12
+ }
13
+ ])
@@ -0,0 +1,176 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
@@ -0,0 +1,227 @@
1
+ Metadata-Version: 2.3
2
+ Name: auth0-ai-langchain
3
+ Version: 0.1.0
4
+ Summary: This package is an SDK for building secure AI-powered applications using Auth0, Okta FGA and LangChain.
5
+ License: Apache-2.0
6
+ Author: Auth0
7
+ Author-email: support@auth0.com
8
+ Requires-Python: >=3.11,<4.0
9
+ Classifier: License :: OSI Approved :: Apache Software License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Requires-Dist: auth0-ai (>=0.1.0,<0.2.0)
15
+ Requires-Dist: langchain (>=0.3.20,<0.4.0)
16
+ Requires-Dist: langchain-core (>=0.3.43,<0.4.0)
17
+ Requires-Dist: langgraph (>=0.3.25,<0.4.0)
18
+ Requires-Dist: langgraph-sdk (>=0.1.55,<0.2.0)
19
+ Requires-Dist: openfga-sdk (>=0.9.0,<0.10.0)
20
+ Project-URL: Homepage, https://auth0.com
21
+ Description-Content-Type: text/markdown
22
+
23
+ # Auth0 AI for LangChain
24
+
25
+ `auth0-ai-langchain` is an SDK for building secure AI-powered applications using [Auth0](https://www.auth0.ai/), [Okta FGA](https://docs.fga.dev/) and [LangChain](https://python.langchain.com/docs/tutorials/).
26
+
27
+ ![Release](https://img.shields.io/pypi/v/auth0-ai-langchain) ![Downloads](https://img.shields.io/pypi/dw/auth0-ai-langchain) [![License](https://img.shields.io/:license-APACHE%202.0-blue.svg?style=flat)](https://opensource.org/license/apache-2-0)
28
+
29
+ ## Installation
30
+
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.
32
+
33
+ ```bash
34
+ pip install auth0-ai-langchain
35
+ ```
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](../../examples/async-user-confirmation/langchain-examples/).
42
+
43
+ ## Authorization for Tools
44
+
45
+ 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.
46
+
47
+ Full example of [Authorization for Tools](../../examples/authorization-for-tools/langchain-examples/).
48
+
49
+ 1. Create an instance of FGA Authorizer:
50
+
51
+ ```python
52
+ from langchain_auth0_ai.fga.fga_authorizer import FGAAuthorizer, FGAAuthorizerOptions
53
+
54
+ fga = FGAAuthorizer.create()
55
+ ```
56
+
57
+ **Note**: Here, you can configure and specify your FGA credentials. By `default`, they are read from environment variables:
58
+
59
+ ```sh
60
+ FGA_STORE_ID="<fga-store-id>"
61
+ FGA_CLIENT_ID="<fga-client-id>"
62
+ FGA_CLIENT_SECRET="<fga-client-secret>"
63
+ ```
64
+
65
+ 2. Define the FGA query (`build_query`) and, optionally, the `on_unauthorized` handler:
66
+
67
+ ```python
68
+ from langchain_core.runnables import ensure_config
69
+
70
+ async def build_fga_query(tool_input):
71
+ user_id = ensure_config().get("configurable",{}).get("user_id")
72
+ return {
73
+ "user": f"user:{user_id}",
74
+ "object": f"asset:{tool_input["ticker"]}",
75
+ "relation": "can_buy",
76
+ "context": {"current_time": datetime.now(timezone.utc).isoformat()}
77
+ }
78
+
79
+ def on_unauthorized(tool_input):
80
+ return f"The user is not allowed to buy {tool_input["qty"]} shares of {tool_input["ticker"]}."
81
+
82
+ use_fga = fga(FGAAuthorizerOptions(
83
+ build_query=build_fga_query,
84
+ on_unauthorized=on_unauthorized,
85
+ ))
86
+ ```
87
+
88
+ **Note**: The parameters given to the `build_query` and `on_unauthorized` functions are the same as those provided to the tool function.
89
+
90
+ 3. Wrap the tool:
91
+
92
+ ```python
93
+ from langchain_core.tools import StructuredTool
94
+
95
+ async def buy_tool_function(ticker: str, qty: int) -> str:
96
+ # TODO: implement buy operation
97
+ return f"Purchased {qty} shares of {ticker}"
98
+
99
+ func=use_fga(buy_tool_function)
100
+
101
+ buy_tool = StructuredTool(
102
+ func=func,
103
+ coroutine=func,
104
+ name="buy",
105
+ description="Use this function to buy stocks",
106
+ )
107
+ ```
108
+
109
+ ## Calling APIs On User's Behalf
110
+
111
+ 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.
112
+
113
+ Full Example of [Calling APIs On User's Behalf](../../examples/calling-apis/langchain-examples/).
114
+
115
+ 1. Define a tool with the proper authorizer:
116
+
117
+ ```python
118
+ from langchain_auth0_ai.auth0_ai import Auth0AI
119
+ from langchain_auth0_ai.federated_connections import get_access_token_for_connection
120
+ from langchain_core.tools import StructuredTool
121
+
122
+ auth0_ai = Auth0AI()
123
+
124
+ with_google_calendar_access = auth0_ai.with_federated_connection(
125
+ connection="google-oauth2",
126
+ scopes=["https://www.googleapis.com/auth/calendar.freebusy"]
127
+ )
128
+
129
+ def tool_function(date: datetime):
130
+ access_token = get_access_token_for_connection()
131
+ # Call Google API
132
+
133
+ check_calendar_tool = with_google_calendar_access(
134
+ StructuredTool(
135
+ name="check_user_calendar",
136
+ description="Use this function to check if the user is available on a certain date and time",
137
+ func=tool_function,
138
+ # ...
139
+ )
140
+ )
141
+ ```
142
+
143
+ 2. Add a node to your graph for your tools:
144
+
145
+ ```python
146
+ workflow = (
147
+ StateGraph(State)
148
+ .add_node(
149
+ "tools",
150
+ ToolNode(
151
+ [
152
+ check_calendar_tool,
153
+ # ...
154
+ ],
155
+ # The error handler should be disabled to allow interruptions to be triggered from within tools.
156
+ handle_tool_errors=False
157
+ )
158
+ )
159
+ # ...
160
+ )
161
+ ```
162
+
163
+ 3. Handle interruptions properly. If the tool does not have access to user's Google Calendar, it will throw an interruption.
164
+
165
+ ## RAG with FGA
166
+
167
+ The `FGARetriever` can be used to filter documents based on access control checks defined in Okta FGA. This retriever performs batch checks on retrieved documents, returning only the ones that pass the specified access criteria.
168
+
169
+ Full Example of [RAG Application](../../examples/authorization-for-rag/langchain-examples/).
170
+
171
+ Create a retriever instance using the `FGARetriever` class.
172
+
173
+ ```python
174
+ from langchain.vectorstores import VectorStoreIndex
175
+ from langchain.schema import Document
176
+ from langchain_auth0_ai import FGARetriever
177
+ from openfga_sdk.client.models import ClientCheckRequest
178
+ from openfga_sdk import ClientConfiguration
179
+ from openfga_sdk.credentials import CredentialConfiguration, Credentials
180
+
181
+ # Define some docs:
182
+ documents = [
183
+ Document(page_content="This is a public doc", metadata={"doc_id": "public-doc"}),
184
+ Document(page_content="This is a private doc", metadata={"doc_id": "private-doc"}),
185
+ ]
186
+
187
+ # Create a vector store:
188
+ vector_store = VectorStoreIndex.from_documents(documents)
189
+
190
+ # Create a retriever:
191
+ base_retriever = vector_store.as_retriever()
192
+
193
+ # Create the FGA retriever wrapper:
194
+ retriever = FGARetriever(
195
+ base_retriever,
196
+ build_query=lambda node: ClientCheckRequest(
197
+ user=f'user:{user}',
198
+ object=f'doc:{node.metadata["doc_id"]}',
199
+ relation="viewer",
200
+ )
201
+ )
202
+
203
+ # Create a query engine:
204
+ query_engine = RetrieverQueryEngine.from_args(
205
+ retriever=retriever,
206
+ llm=OpenAI()
207
+ )
208
+
209
+ # Query:
210
+ response = query_engine.query("What is the forecast for ZEKO?")
211
+
212
+ print(response)
213
+ ```
214
+
215
+ ---
216
+
217
+ <p align="center">
218
+ <picture>
219
+ <source media="(prefers-color-scheme: light)" srcset="https://cdn.auth0.com/website/sdks/logos/auth0_light_mode.png" width="150">
220
+ <source media="(prefers-color-scheme: dark)" srcset="https://cdn.auth0.com/website/sdks/logos/auth0_dark_mode.png" width="150">
221
+ <img alt="Auth0 Logo" src="https://cdn.auth0.com/website/sdks/logos/auth0_light_mode.png" width="150">
222
+ </picture>
223
+ </p>
224
+ <p align="center">Auth0 is an easy to implement, adaptable authentication and authorization platform. To learn more checkout <a href="https://auth0.com/why-auth0">Why Auth0?</a></p>
225
+ <p align="center">
226
+ This project is licensed under the Apache 2.0 license. See the <a href="/LICENSE"> LICENSE</a> file for more info.</p>
227
+
@@ -0,0 +1,19 @@
1
+ auth0_ai_langchain/FGARetriever.py,sha256=6nQXRkbDLHZt9zYZJsS5iQljrogQVLW0aVwDIf6Mpac,6002
2
+ auth0_ai_langchain/__init__.py,sha256=I331Kz-q97ZU7TfXaOR5UBbJamGEJ15twbf2HP1iCHs,67
3
+ auth0_ai_langchain/auth0_ai.py,sha256=8NUV_80SxR8qQt_3RQGf0Oga178kChuROHuhz7rfOyU,1919
4
+ auth0_ai_langchain/ciba/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ auth0_ai_langchain/ciba/ciba_graph/ciba_graph.py,sha256=Wi7qXSMzvcfqdO8WsUJejmQzOVM469TFJCkH7eRlaR8,4115
6
+ auth0_ai_langchain/ciba/ciba_graph/initialize_ciba.py,sha256=a41KedBzxfLqG2AhvkFnemcEqWwQWVh1r-Oro-qJX-M,3752
7
+ auth0_ai_langchain/ciba/ciba_graph/initialize_hitl.py,sha256=CR3jMolZYYOBHx1AXb6yERBaZThMg677qGGo_vRy6I8,1901
8
+ auth0_ai_langchain/ciba/ciba_graph/types.py,sha256=NZS99vPOgJRc2O7mO5MWj6nAa4RSG1R5oSvzYhkz0RA,4234
9
+ auth0_ai_langchain/ciba/ciba_graph/utils.py,sha256=ZPAh0Gs7Hj59_xngg8M7yx1v52dTn2pNpMNRpFKCSII,560
10
+ auth0_ai_langchain/ciba/ciba_poller_graph.py,sha256=aKTOsnlZ-Uy6jAvEh2JBOYP0UGgZiDJ5DzDv7fXMRvM,3792
11
+ auth0_ai_langchain/ciba/types.py,sha256=gybqYEprklZwcMBgaWFooBsJ1GcNUK8ZWRvAX5PZWdE,177
12
+ auth0_ai_langchain/federated_connections/__init__.py,sha256=OJCWTnxYuaWjn8FyFGjCAF7m5Y4Eigkzx7a59atFFFg,356
13
+ auth0_ai_langchain/federated_connections/federated_connection_authorizer.py,sha256=ZLF4p7fPTrODOeHWIShfBTsReSy27u73rIp0L_Umhjg,2218
14
+ auth0_ai_langchain/fga/fga_authorizer.py,sha256=uDaGDSXaxQd1X-2w2zTvnfizMB-DtQ-1G6SIaDNBrho,137
15
+ auth0_ai_langchain/utils/interrupt.py,sha256=JoYJkigDEAPRHZtjo6gw6k3439E4i1O7F4_0ExkL_RE,405
16
+ auth0_ai_langchain-0.1.0.dist-info/LICENSE,sha256=Lu_2YH0oK8b_VVisAhNQ2WIdtwY8pSU2PLbll-y6Cj8,9792
17
+ auth0_ai_langchain-0.1.0.dist-info/METADATA,sha256=6HQKAj7PdTgh4u45LyBcoLv-iYFEOOefBLoJ3R8x53w,7926
18
+ auth0_ai_langchain-0.1.0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
19
+ auth0_ai_langchain-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.1.2
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any