Skip to content

Commit 910a403

Browse files
rahul-tripRahul Tripathi
andauthored
add sharepoint retriever app. (#399)
* add sharepoint retriever app. Signed-off-by: Rahul Tripathi <rauhl.psit.ec@gmail.com> * add requiremens.txt Signed-off-by: Rahul Tripathi <rauhl.psit.ec@gmail.com> * add sample for non sdk synchrounos fetching of authorised identities. Signed-off-by: Rahul Tripathi <rauhl.psit.ec@gmail.com> * rename samples Signed-off-by: Rahul Tripathi <rauhl.psit.ec@gmail.com> * Update pebblo_identity_api_rag.py --------- Signed-off-by: Rahul Tripathi <rauhl.psit.ec@gmail.com> Co-authored-by: Rahul Tripathi <rauhl.psit.ec@gmail.com>
1 parent 5f3a80c commit 910a403

5 files changed

Lines changed: 450 additions & 0 deletions

File tree

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
from typing import Optional
2+
import os
3+
import requests
4+
5+
from dotenv import load_dotenv
6+
load_dotenv()
7+
8+
class SharepointADHelper:
9+
def __init__(
10+
self,
11+
client_id: Optional[str] = None,
12+
client_secret: Optional[str] = None,
13+
tenant_id: Optional[str] = None
14+
):
15+
self.client_id = client_id or os.environ.get("O365_CLIENT_ID")
16+
self.client_secret = client_secret or os.environ.get("O365_CLIENT_SECRET")
17+
self.tenant_id = tenant_id or os.environ.get("O365_TENANT_ID")
18+
if not all([self.client_id, self.client_secret, self.tenant_id]):
19+
raise EnvironmentError(
20+
"At-least one of O365_CLIENT_ID, O365_CLIENT_SECRET or O365_TENANT_ID not provided"
21+
)
22+
self.access_token = self.get_access_token()
23+
if not self.access_token:
24+
raise EnvironmentError("o365 client id/secret or tenant id is invalid."
25+
"Please check the environment variables.")
26+
self.headers = { 'Authorization': 'Bearer' + self.access_token }
27+
28+
def get_authorized_identities(self, user_id: str):
29+
"""
30+
Retrieves the authorized identities for a given user.
31+
32+
Args:
33+
user_id (str): The ID of the user.
34+
35+
Returns:
36+
list: A list of authorized identities, including associated group emails and the user ID.
37+
"""
38+
user = self._get_users(user_id)
39+
user_index_id = user.get("id")
40+
if not user_index_id:
41+
print(f"Could not find the user `{user_id}` information in Microsoft Graph API. Not authorized.")
42+
return [user_id]
43+
associated_groups = self._get_associated_groups(user_index_id)
44+
associated_groups_emails = [
45+
group.get("mail") for group in associated_groups["value"] if group.get("mail")
46+
]
47+
return associated_groups_emails + [user_id]
48+
49+
def _get_associated_groups(self, user_index_id: str):
50+
"""
51+
Retrieves the associated groups for a given user.
52+
53+
Args:
54+
user_index_id (str): The index ID of the user.
55+
56+
Returns:
57+
dict: A dictionary containing the associated groups information.
58+
59+
Raises:
60+
Exception: If there is an error while making the API request.
61+
"""
62+
url = f"https://graph.microsoft.com/v1.0/users/{user_index_id}/memberOf"
63+
try:
64+
response = requests.get(url=url, headers=self.headers, timeout=10)
65+
response.raise_for_status()
66+
except requests.exceptions.HTTPError:
67+
print("Error while retrieving associated groups from Microsoft Graph API")
68+
return {}
69+
else:
70+
return response.json()
71+
72+
def _get_users(self, user_id: str):
73+
"""
74+
Retrieves information about a specific user from the Microsoft Graph API.
75+
76+
Args:
77+
user_id (str): The ID of the user to retrieve information for.
78+
79+
Returns:
80+
dict: A dictionary containing the user's information.
81+
82+
Raises:
83+
Exception: If there is an error while making the API request.
84+
"""
85+
url = f"https://graph.microsoft.com/v1.0/users/{user_id}"
86+
try:
87+
response = requests.get(url=url, headers=self.headers, timeout=10)
88+
response.raise_for_status()
89+
except requests.exceptions.HTTPError:
90+
print("Error while retrieving user information from Microsoft Graph API")
91+
return {}
92+
else:
93+
return response.json()
94+
95+
96+
def get_access_token(self):
97+
"""
98+
Retrieves an access token from Microsoft Graph API using client credentials.
99+
Returns:
100+
str: The access token.
101+
Raises:
102+
requests.exceptions.HTTPError: If the request to retrieve the access token fails.
103+
"""
104+
# ToDo: This access token should be cached and refreshed when it expires
105+
# It should also be stored in home directory or in a secure location
106+
url = f"https://login.microsoftonline.com/{self.tenant_id}/oauth2/v2.0/token"
107+
headers = {"Content-Type": "application/x-www-form-urlencoded"}
108+
data = {
109+
"grant_type": "client_credentials",
110+
"client_id": self.client_id,
111+
"client_secret": self.client_secret,
112+
"scope": "https://graph.microsoft.com/.default"
113+
}
114+
try:
115+
response = requests.post(url, headers=headers, data=data, timeout=10)
116+
response.raise_for_status() # Raise exception if the request failed
117+
except requests.exceptions.HTTPError:
118+
print("Error while retrieving access token from Microsoft Graph API")
119+
return ""
120+
else:
121+
return response.json()["access_token"]
122+
123+
if __name__ == "__main__":
124+
pass
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from dotenv import load_dotenv
2+
load_dotenv()
3+
4+
import asyncio
5+
import os
6+
from typing import Optional
7+
8+
from msgraph import GraphServiceClient
9+
from azure.identity import ClientSecretCredential
10+
from kiota_abstractions.api_error import APIError
11+
12+
async def get_authorized_identities(
13+
user_id: str,
14+
client_id: Optional[str] = None,
15+
client_secret: Optional[str] = None,
16+
tenant_id: Optional[str] = None
17+
):
18+
client_id = client_id or os.environ.get("O365_CLIENT_ID")
19+
client_secret = client_secret or os.environ.get("O365_CLIENT_SECRET")
20+
tenant_id = tenant_id or os.environ.get("O365_TENANT_ID")
21+
if not all([client_id, client_secret, tenant_id]):
22+
raise EnvironmentError(
23+
"atleast one of {O365_CLIENT_ID, O365_CLIENT_SECRET or O365_TENANT_ID not provided"
24+
)
25+
credentials = ClientSecretCredential(
26+
tenant_id,
27+
client_id,
28+
client_secret,
29+
)
30+
graph_client = GraphServiceClient(credentials)
31+
32+
# user = graph_client.users.by_user_id(user_id)
33+
try:
34+
groups = await graph_client.users.by_user_id(user_id).member_of.get()
35+
except APIError:
36+
print(f"ms_graph API error: invalid user: {user_id}")
37+
return [user_id]
38+
auth_iden = [
39+
group.__dict__.get("mail")
40+
for group in groups.value
41+
if group.__dict__.get("mail")
42+
] + [user_id]
43+
return auth_iden
44+
45+
if __name__ == "__main__":
46+
print(asyncio.run(get_authorized_identities("arpit@daxaai.onmicrosoft.com")))
47+
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Fill-in OPENAI_API_KEY in .env file in this directory before proceeding
2+
from dotenv import load_dotenv
3+
load_dotenv()
4+
5+
import asyncio
6+
import os
7+
from msgraph_api_auth import SharepointADHelper
8+
from langchain_community.chains import PebbloRetrievalQA
9+
from langchain_community.chains.pebblo_retrieval.models import (
10+
AuthContext,
11+
ChainInput,
12+
)
13+
from langchain_community.document_loaders import UnstructuredFileIOLoader
14+
from langchain_community.document_loaders.pebblo import PebbloSafeLoader
15+
from langchain_community.vectorstores.qdrant import Qdrant
16+
from langchain_community.document_loaders import SharePointLoader
17+
from langchain_openai.embeddings import OpenAIEmbeddings
18+
from langchain_openai.llms import OpenAI
19+
20+
21+
22+
23+
class PebbloIdentityRAG:
24+
def __init__(self, drive_id: str, folder_path: str, collection_name: str):
25+
self.loader_app_name = "pebblo-identity-loader"
26+
self.retrieval_app_name = "pebblo-identity-retriever"
27+
self.collection_name = collection_name
28+
self.drive_id = drive_id
29+
self.folder_path = folder_path
30+
31+
# Load documents
32+
print("\nLoading RAG documents ...")
33+
self.loader = PebbloSafeLoader(
34+
SharePointLoader(
35+
document_library_id=self.drive_id,
36+
folder_path=self.folder_path or "/",
37+
auth_with_token=True,
38+
load_auth=True,
39+
recursive=True,
40+
load_extended_metadata=True,
41+
),
42+
name=self.loader_app_name, # App name (Mandatory)
43+
owner="Joe Smith", # Owner (Optional)
44+
description="Identity enabled SafeLoader and SafeRetrival app using Pebblo", # Description (Optional)
45+
)
46+
self.documents = self.loader.load()
47+
print(self.documents[-1].metadata.get("authorized_identities"))
48+
print(f"Loaded {len(self.documents)} documents ...\n")
49+
50+
# Load documents into VectorDB
51+
52+
print("Hydrating Vector DB ...")
53+
self.vectordb = self.embeddings()
54+
print("Finished hydrating Vector DB ...\n")
55+
56+
# Prepare LLM
57+
self.llm = OpenAI()
58+
print("Initializing PebbloRetrievalQA ...")
59+
self.retrieval_chain = self.init_retrieval_chain()
60+
61+
def init_retrieval_chain(self):
62+
"""
63+
Initialize PebbloRetrievalQA chain
64+
"""
65+
return PebbloRetrievalQA.from_chain_type(
66+
llm=self.llm,
67+
app_name=self.retrieval_app_name,
68+
owner="Joe Smith",
69+
description="Identity enabled filtering using PebbloSafeLoader, and PebbloRetrievalQA",
70+
chain_type="stuff",
71+
retriever=self.vectordb.as_retriever(),
72+
verbose=True,
73+
)
74+
75+
def embeddings(self):
76+
embeddings = OpenAIEmbeddings()
77+
vectordb = Qdrant.from_documents(
78+
self.documents,
79+
embeddings,
80+
location=":memory:",
81+
collection_name=self.collection_name,
82+
)
83+
return vectordb
84+
85+
def ask(self, question: str, user_email: str, auth_identifiers: list):
86+
auth_context = {
87+
"user_id": user_email,
88+
"user_auth": auth_identifiers,
89+
}
90+
auth_context = AuthContext(**auth_context)
91+
chain_input = ChainInput(query=question, auth_context=auth_context)
92+
93+
return self.retrieval_chain.invoke(chain_input.dict())
94+
95+
96+
if __name__ == "__main__":
97+
input_collection_name = "identity-enabled-rag"
98+
99+
print("Please enter ingestion user details for loading data...")
100+
app_client_id = input("App client id : ") or os.environ.get("O365_CLIENT_ID")
101+
app_client_secret = input("App client secret : ") or os.environ.get("O365_CLIENT_SECRET")
102+
tenant_id = input("Tenant id : ") or os.environ.get("O365_TENANT_ID")
103+
104+
drive_id = input("Drive id : ") or "b!TVvGZhXfGUuSKMdCgOucz08XRKxsDuVCojWCjzBMN-as9t-EstljQKBl332OMJnI"
105+
106+
rag_app = PebbloIdentityRAG(
107+
drive_id = drive_id, folder_path = "/document", collection_name=input_collection_name
108+
)
109+
110+
while True:
111+
print("Please enter end user details below")
112+
end_user_email_address = input("User email address : ") or "arpit@daxaai.onmicrosoft.com"
113+
prompt = input("Please provide the prompt : ") or "tell me about sample.pdf."
114+
print(f"User: {end_user_email_address}.\nQuery:{prompt}\n")
115+
authorized_identities = SharepointADHelper(
116+
client_id = app_client_id,
117+
client_secret = app_client_secret,
118+
tenant_id = tenant_id,
119+
).get_authorized_identities(end_user_email_address)
120+
response = rag_app.ask(prompt, end_user_email_address, authorized_identities)
121+
print(f"Response:\n{response}")
122+
try:
123+
continue_or_exist = int(input("\n\nType 1 to continue and 0 to exit : "))
124+
except ValueError:
125+
print("Please provide valid input")
126+
continue
127+
128+
if not continue_or_exist:
129+
exit(0)
130+
131+
print("\n\n")
132+

0 commit comments

Comments
 (0)