# embedding_metadata

key: _node_content
string_value:
{"id_": "8e60ec8c-ec17-4ff1-a6e0-ef9a6680e4b3", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "379642c4-6259-407d-8bc7-20486f37e723", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "98759ab3f29100bf093ebd1d9dd7e4e2a5592f3db8faba9515e6b412b3ff62f6", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "89d75a97-1d9b-4b28-ad49-5e88f414a6fa", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "2054fa8d68e8dc447dd8401b8d84ff4db67fafddbfaa52cc6a156fd808acfc6d", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "edebc805-a2d9-41ce-8443-9b47ef3a9e2d", "node_type": "1", "metadata": {}, "hash": "3d9749de9765f0c484cdf81bc66dc55f49e425a7732f978a7a12119851d90473", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 9270, "end_char_idx": 12536, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
```python
# Retrieve the admin keys for the search service
try:
    response = search_management_client.admin_keys.get(
        resource_group_name=resource_group,
        search_service_name=search_service_name,
    )
    # Extract the primary API key from the response and save as a variable to be used later
    search_service_api_key = response.primary_key
    print("Successfully retrieved the API key.")
except Exception as e:
    print(f"Failed to retrieve the API key: {e}")
```

# Prepare data
We're going to embed and store a few pages of the OpenAI docs in the oai_docs folder. We'll first embed each, add it to a CSV, and then use that CSV to upload to the index.

In order to handle longer text files beyond the context of 8191 tokens, we can either use the chunk embeddings separately, or combine them in some way, such as averaging (weighted by the size of each chunk).

We will take a function from Python's own cookbook that breaks up a sequence into chunks.


```python
def batched(iterable, n):
    """Batch data into tuples of length n. The last batch may be shorter."""
    # batched('ABCDEFG', 3) --> ABC DEF G
    if n < 1:
        raise ValueError('n must be at least one')
    it = iter(iterable)
    while (batch := tuple(islice(it, n))):
        yield batch

```

Now we define a function that encodes a string into tokens and then breaks it up into chunks. We'll use tiktoken, a fast open-source tokenizer by OpenAI.

To read more about counting tokens with Tiktoken, check out [this cookbook](https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken). 




```python
def chunked_tokens(text, chunk_length, encoding_name='cl100k_base'):
    # Get the encoding object for the specified encoding name. OpenAI's tiktoken library, which is used in this notebook, currently supports two encodings: 'bpe' and 'cl100k_base'. The 'bpe' encoding is used for GPT-3 and earlier models, while 'cl100k_base' is used for newer models like GPT-4.
    encoding = tiktoken.get_encoding(encoding_name)
    # Encode the input text into tokens
    tokens = encoding.encode(text)
    # Create an iterator that yields chunks of tokens of the specified length
    chunks_iterator = batched(tokens, chunk_length)
    # Yield each chunk from the iterator
    yield from chunks_iterator
```

Finally, we can write a function that safely handles embedding requests, even when the input text is longer than the maximum context length, by chunking the input tokens and embedding each chunk individually. The average flag can be set to True to return the weighted average of the chunk embeddings, or False to simply return the unmodified list of chunk embeddings.

> Note: there are other, more sophisticated techniques you can take here, including:
> - using GPT-4o to capture images/chart descriptions for embedding.
> - keeping text overlap between the chunks to minimize cutting off important context.
> - chunking based on paragraphs or sections.
> - adding more descriptive metadata about each article.


```python
## Change the below based on model. The below is for the latest embeddings models from OpenAI, so you can leave as is unless you are using a different embedding model..
EMBEDDING_CTX_LENGTH = 8191
EMBEDDING_ENCODING='cl100k_base'
```


key: _node_content
string_value:
{"id_": "edebc805-a2d9-41ce-8443-9b47ef3a9e2d", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "379642c4-6259-407d-8bc7-20486f37e723", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "98759ab3f29100bf093ebd1d9dd7e4e2a5592f3db8faba9515e6b412b3ff62f6", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "8e60ec8c-ec17-4ff1-a6e0-ef9a6680e4b3", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "79ca571432b6928aef471f406095e201a84ce6fb0eb2399b6756d2fd855215a0", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "419e1a72-9ef2-4e7a-81a3-50b356be494c", "node_type": "1", "metadata": {}, "hash": "3fad5a39935ab46eecd10c3b745833bc1065f43e8c6abf8c64398510f00eede6", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 10870, "end_char_idx": 13962, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
```python
def chunked_tokens(text, chunk_length, encoding_name='cl100k_base'):
    # Get the encoding object for the specified encoding name. OpenAI's tiktoken library, which is used in this notebook, currently supports two encodings: 'bpe' and 'cl100k_base'. The 'bpe' encoding is used for GPT-3 and earlier models, while 'cl100k_base' is used for newer models like GPT-4.
    encoding = tiktoken.get_encoding(encoding_name)
    # Encode the input text into tokens
    tokens = encoding.encode(text)
    # Create an iterator that yields chunks of tokens of the specified length
    chunks_iterator = batched(tokens, chunk_length)
    # Yield each chunk from the iterator
    yield from chunks_iterator
```

Finally, we can write a function that safely handles embedding requests, even when the input text is longer than the maximum context length, by chunking the input tokens and embedding each chunk individually. The average flag can be set to True to return the weighted average of the chunk embeddings, or False to simply return the unmodified list of chunk embeddings.

> Note: there are other, more sophisticated techniques you can take here, including:
> - using GPT-4o to capture images/chart descriptions for embedding.
> - keeping text overlap between the chunks to minimize cutting off important context.
> - chunking based on paragraphs or sections.
> - adding more descriptive metadata about each article.


```python
## Change the below based on model. The below is for the latest embeddings models from OpenAI, so you can leave as is unless you are using a different embedding model..
EMBEDDING_CTX_LENGTH = 8191
EMBEDDING_ENCODING='cl100k_base'
```


```python
def generate_embeddings(text, model):
    # Generate embeddings for the provided text using the specified model
    embeddings_response = openai_client.embeddings.create(model=model, input=text)
    # Extract the embedding data from the response
    embedding = embeddings_response.data[0].embedding
    return embedding

def len_safe_get_embedding(text, model=embeddings_model, max_tokens=EMBEDDING_CTX_LENGTH, encoding_name=EMBEDDING_ENCODING):
    # Initialize lists to store embeddings and corresponding text chunks
    chunk_embeddings = []
    chunk_texts = []
    # Iterate over chunks of tokens from the input text
    for chunk in chunked_tokens(text, chunk_length=max_tokens, encoding_name=encoding_name):
        # Generate embeddings for each chunk and append to the list
        chunk_embeddings.append(generate_embeddings(chunk, model=model))
        # Decode the chunk back to text and append to the list
        chunk_texts.append(tiktoken.get_encoding(encoding_name).decode(chunk))
    # Return the list of chunk embeddings and the corresponding text chunks
    return chunk_embeddings, chunk_texts
```

Next, we can define a helper function that will capture additional metadata about the documents. This is useful to use as a metadata filter for search queries, and capturing richer data for search. 

In this example, I'll choose from a list of categories to use later on in a metadata filter.


key: _node_content
string_value:
{"id_": "419e1a72-9ef2-4e7a-81a3-50b356be494c", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "379642c4-6259-407d-8bc7-20486f37e723", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "98759ab3f29100bf093ebd1d9dd7e4e2a5592f3db8faba9515e6b412b3ff62f6", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "edebc805-a2d9-41ce-8443-9b47ef3a9e2d", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "d95e5f30ed74662d7575553d97d4022de5c04f6bc36c1ec174c0fc2752d515ef", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "6d028dab-4951-4395-9da8-77886f57dd41", "node_type": "1", "metadata": {}, "hash": "b31bb4a6fc5a48e1591deb96ac6db9d49beb87daf6417a8ea1bb02b6065157ab", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 12293, "end_char_idx": 15261, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
```python
## Change the below based on model. The below is for the latest embeddings models from OpenAI, so you can leave as is unless you are using a different embedding model..
EMBEDDING_CTX_LENGTH = 8191
EMBEDDING_ENCODING='cl100k_base'
```


```python
def generate_embeddings(text, model):
    # Generate embeddings for the provided text using the specified model
    embeddings_response = openai_client.embeddings.create(model=model, input=text)
    # Extract the embedding data from the response
    embedding = embeddings_response.data[0].embedding
    return embedding

def len_safe_get_embedding(text, model=embeddings_model, max_tokens=EMBEDDING_CTX_LENGTH, encoding_name=EMBEDDING_ENCODING):
    # Initialize lists to store embeddings and corresponding text chunks
    chunk_embeddings = []
    chunk_texts = []
    # Iterate over chunks of tokens from the input text
    for chunk in chunked_tokens(text, chunk_length=max_tokens, encoding_name=encoding_name):
        # Generate embeddings for each chunk and append to the list
        chunk_embeddings.append(generate_embeddings(chunk, model=model))
        # Decode the chunk back to text and append to the list
        chunk_texts.append(tiktoken.get_encoding(encoding_name).decode(chunk))
    # Return the list of chunk embeddings and the corresponding text chunks
    return chunk_embeddings, chunk_texts
```

Next, we can define a helper function that will capture additional metadata about the documents. This is useful to use as a metadata filter for search queries, and capturing richer data for search. 

In this example, I'll choose from a list of categories to use later on in a metadata filter.


```python
## These are the categories I will be using for the categorization task. You can change these as needed based on your use case.
categories = ['authentication','models','techniques','tools','setup','billing_limits','other']

def categorize_text(text, categories):
    # Create a prompt for categorization
    messages = [
        {"role": "system", "content": f"""You are an expert in LLMs, and you will be given text that corresponds to an article in OpenAI's documentation.
         Categorize the document into one of these categories: {', '.join(categories)}. Only respond with the category name and nothing else."""},
        {"role": "user", "content": text}
    ]
    try:
        # Call the OpenAI API to categorize the text
        response = openai_client.chat.completions.create(
            model="gpt-4o",
            messages=messages
        )
        # Extract the category from the response
        category = response.choices[0].message.content
        return category
    except Exception as e:
        print(f"Error categorizing text: {str(e)}")
        return None
```

Now, we can define some helper functions to process the .txt files in the oai_docs folder within the data folder. You can use this with your own data as well and supports both .txt and .pdf files.


key: _node_content
string_value:
{"id_": "6d028dab-4951-4395-9da8-77886f57dd41", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "379642c4-6259-407d-8bc7-20486f37e723", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "98759ab3f29100bf093ebd1d9dd7e4e2a5592f3db8faba9515e6b412b3ff62f6", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "419e1a72-9ef2-4e7a-81a3-50b356be494c", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "d3520ea6231e1850e9077054756ace32b5d8f92928bffae3a79181e6322391f6", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "982bb43a-2c94-47a0-aebd-11d8e603ac1e", "node_type": "1", "metadata": {}, "hash": "5ab9ebcfaaf610da64bfd282c681175b718da1a6aab53fad94b53ae79559cad4", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 13965, "end_char_idx": 17109, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
```python
## These are the categories I will be using for the categorization task. You can change these as needed based on your use case.
categories = ['authentication','models','techniques','tools','setup','billing_limits','other']

def categorize_text(text, categories):
    # Create a prompt for categorization
    messages = [
        {"role": "system", "content": f"""You are an expert in LLMs, and you will be given text that corresponds to an article in OpenAI's documentation.
         Categorize the document into one of these categories: {', '.join(categories)}. Only respond with the category name and nothing else."""},
        {"role": "user", "content": text}
    ]
    try:
        # Call the OpenAI API to categorize the text
        response = openai_client.chat.completions.create(
            model="gpt-4o",
            messages=messages
        )
        # Extract the category from the response
        category = response.choices[0].message.content
        return category
    except Exception as e:
        print(f"Error categorizing text: {str(e)}")
        return None
```

Now, we can define some helper functions to process the .txt files in the oai_docs folder within the data folder. You can use this with your own data as well and supports both .txt and .pdf files.


```python
def extract_text_from_pdf(pdf_path):
    # Initialize the PDF reader
    reader = PdfReader(pdf_path)
    text = ""
    # Iterate through each page in the PDF and extract text
    for page in reader.pages:
        text += page.extract_text()
    return text

def process_file(file_path, idx, categories, embeddings_model):
    file_name = os.path.basename(file_path)
    print(f"Processing file {idx + 1}: {file_name}")
    
    # Read text content from .txt files
    if file_name.endswith('.txt'):
        with open(file_path, 'r', encoding='utf-8') as file:
            text = file.read()
    # Extract text content from .pdf files
    elif file_name.endswith('.pdf'):
        text = extract_text_from_pdf(file_path)
    
    title = file_name
    # Generate embeddings for the title
    title_vectors, title_text = len_safe_get_embedding(title, embeddings_model)
    print(f"Generated title embeddings for {file_name}")
    
    # Generate embeddings for the content
    content_vectors, content_text = len_safe_get_embedding(text, embeddings_model)
    print(f"Generated content embeddings for {file_name}")
    
    category = categorize_text(' '.join(content_text), categories)
    print(f"Categorized {file_name} as {category}")
    
    # Prepare the data to be appended
    data = []
    for i, content_vector in enumerate(content_vectors):
        data.append({
            "id": f"{idx}_{i}",
            "vector_id": f"{idx}_{i}",
            "title": title_text[0],
            "text": content_text[i],
            "title_vector": json.dumps(title_vectors[0]),  # Assuming title is short and has only one chunk
            "content_vector": json.dumps(content_vector),
            "category": category
        })
        print(f"Appended data for chunk {i + 1}/{len(content_vectors)} of {file_name}")
    
    return data


key: _node_content
string_value:
{"id_": "982bb43a-2c94-47a0-aebd-11d8e603ac1e", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "379642c4-6259-407d-8bc7-20486f37e723", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "98759ab3f29100bf093ebd1d9dd7e4e2a5592f3db8faba9515e6b412b3ff62f6", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "6d028dab-4951-4395-9da8-77886f57dd41", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "0b5f95c91288905e2761c1a684637e21399f5916946996297ff77c190993b8fa", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "affebfd6-e952-489e-9f54-63186a1a5b2c", "node_type": "1", "metadata": {}, "hash": "e7146340321845649d3b045caf59e98d38150892b0d9f6f0f95d8b51b08d50a2", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 17112, "end_char_idx": 20019, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
```

We'll now use this helper function to process our OpenAI documentation. Feel free to update this to use your own data by changing the folder in `process_files` below.

Note that this will process the documents in chosen folder concurrently, so this should take <30 seconds if using txt files, and slightly longer if using PDFs.


```python
## Customize the location below if you are using different data besides the OpenAI documentation. Note that if you are using a different dataset, you will need to update the categories list as well.
folder_name = "../../../data/oai_docs"

files = [os.path.join(folder_name, f) for f in os.listdir(folder_name) if f.endswith('.txt') or f.endswith('.pdf')]
data = []

# Process each file concurrently
with concurrent.futures.ThreadPoolExecutor() as executor:
    futures = {executor.submit(process_file, file_path, idx, categories, embeddings_model): idx for idx, file_path in enumerate(files)}
    for future in concurrent.futures.as_completed(futures):
        try:
            result = future.result()
            data.extend(result)
        except Exception as e:
            print(f"Error processing file: {str(e)}")

# Write the data to a CSV file
csv_file = os.path.join("..", "embedded_data.csv")
with open(csv_file, 'w', newline='', encoding='utf-8') as csvfile:
    fieldnames = ["id", "vector_id", "title", "text", "title_vector", "content_vector","category"]
    writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
    writer.writeheader()
    for row in data:
        writer.writerow(row)
        print(f"Wrote row with id {row['id']} to CSV")

# Convert the CSV file to a Dataframe
article_df = pd.read_csv("../embedded_data.csv")
# Read vectors from strings back into a list using json.loads
article_df["title_vector"] = article_df.title_vector.apply(json.loads)
article_df["content_vector"] = article_df.content_vector.apply(json.loads)
article_df["vector_id"] = article_df["vector_id"].apply(str)
article_df["category"] = article_df["category"].apply(str)
article_df.head()

```

We now have an `embedded_data.csv` file with six columns that we can upload to our vector database! 

# Create Azure AI Vector Search

##  Create index
We'll define and create a search index using the `SearchIndexClient` from the Azure AI Search Python SDK. The index incorporates both vector search and hybrid search capabilities. For more details, visit Microsoft's documentation on how to [Create a Vector Index](https://learn.microsoft.com/azure/search/vector-search-how-to-create-index?.tabs=config-2023-11-01%2Crest-2023-11-01%2Cpush%2Cportal-check-index)


```python
index_name = "azure-ai-search-openai-cookbook-demo"
# index_name = "<insert_name_for_index>"

index_client = SearchIndexClient(
    endpoint=search_service_endpoint, credential=AzureKeyCredential(search_service_api_key)
)
# Define the fields for the index. Update these based on your data.


key: _node_content
string_value:
{"id_": "affebfd6-e952-489e-9f54-63186a1a5b2c", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "379642c4-6259-407d-8bc7-20486f37e723", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "98759ab3f29100bf093ebd1d9dd7e4e2a5592f3db8faba9515e6b412b3ff62f6", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "982bb43a-2c94-47a0-aebd-11d8e603ac1e", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "f4bf8670419a8864902bbefa4195495c35097f07400661641213a4499789e21e", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "e251723b-8030-4504-ac3b-ba10de2fe7a1", "node_type": "1", "metadata": {}, "hash": "09095bd7aa3b65d85450acf1978e92b20e1e3294808891f48d59b508d7cf2d89", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 19720, "end_char_idx": 23108, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
```python
index_name = "azure-ai-search-openai-cookbook-demo"
# index_name = "<insert_name_for_index>"

index_client = SearchIndexClient(
    endpoint=search_service_endpoint, credential=AzureKeyCredential(search_service_api_key)
)
# Define the fields for the index. Update these based on your data.
# Each field represents a column in the search index
fields = [
    SimpleField(name="id", type=SearchFieldDataType.String),  # Simple string field for document ID
    SimpleField(name="vector_id", type=SearchFieldDataType.String, key=True),  # Key field for the index
    # SimpleField(name="url", type=SearchFieldDataType.String),  # URL field (commented out)
    SearchableField(name="title", type=SearchFieldDataType.String),  # Searchable field for document title
    SearchableField(name="text", type=SearchFieldDataType.String),  # Searchable field for document text
    SearchField(
        name="title_vector",
        type=SearchFieldDataType.Collection(SearchFieldDataType.Single),  # Collection of single values for title vector
        vector_search_dimensions=1536,  # Number of dimensions in the vector
        vector_search_profile_name="my-vector-config",  # Profile name for vector search configuration
    ),
    SearchField(
        name="content_vector",
        type=SearchFieldDataType.Collection(SearchFieldDataType.Single),  # Collection of single values for content vector
        vector_search_dimensions=1536,  # Number of dimensions in the vector
        vector_search_profile_name="my-vector-config",  # Profile name for vector search configuration
    ),
    SearchableField(name="category", type=SearchFieldDataType.String, filterable=True),  # Searchable field for document category
]

# This configuration defines the algorithm and parameters for vector search
vector_search = VectorSearch(
    algorithms=[
        HnswAlgorithmConfiguration(
            name="my-hnsw",  # Name of the HNSW algorithm configuration
            kind=VectorSearchAlgorithmKind.HNSW,  # Type of algorithm
            parameters=HnswParameters(
                m=4,  # Number of bi-directional links created for every new element
                ef_construction=400,  # Size of the dynamic list for the nearest neighbors during construction
                ef_search=500,  # Size of the dynamic list for the nearest neighbors during search
                metric=VectorSearchAlgorithmMetric.COSINE,  # Distance metric used for the search
            ),
        )
    ],
    profiles=[
        VectorSearchProfile(
            name="my-vector-config",  # Name of the vector search profile
            algorithm_configuration_name="my-hnsw",  # Reference to the algorithm configuration
        )
    ],
)

# Create the search index with the vector search configuration
# This combines all the configurations into a single search index
index = SearchIndex(
    name=index_name,  # Name of the index
    fields=fields,  # Fields defined for the index
    vector_search=vector_search  # Vector search configuration

)

# Create or update the index
# This sends the index definition to the Azure Search service
result = index_client.create_index(index)
print(f"{result.name} created")  # Output the name of the created index
```

## Upload Data

Now we'll upload the articles from above that we've stored in `embedded_data.csv` from a pandas DataFrame to an Azure AI Search index.


key: _node_content
string_value:
{"id_": "e251723b-8030-4504-ac3b-ba10de2fe7a1", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "379642c4-6259-407d-8bc7-20486f37e723", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "98759ab3f29100bf093ebd1d9dd7e4e2a5592f3db8faba9515e6b412b3ff62f6", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "affebfd6-e952-489e-9f54-63186a1a5b2c", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "7848e3e0981bf74ff941cb9bdd51a36f8ff0bf3ab2a983d0057e6b67a712b825", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "04454e6e-1440-432b-b3db-d18b8240b577", "node_type": "1", "metadata": {}, "hash": "e139fca35c98549565441e2770a6762598fb370601f11d77955b961d92712860", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 23109, "end_char_idx": 25723, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
For a detailed guide on data import strategies and best practices, refer to  [Data Import in Azure AI Search](https://learn.microsoft.com/azure/search/search-what-is-data-import).



```python
# Convert the 'id' and 'vector_id' columns to string so one of them can serve as our key field
article_df["id"] = article_df["id"].astype(str)
article_df["vector_id"] = article_df["vector_id"].astype(str)

# Convert the DataFrame to a list of dictionaries
documents = article_df.to_dict(orient="records")

# Log the number of documents to be uploaded
print(f"Number of documents to upload: {len(documents)}")

# Create a SearchIndexingBufferedSender
batch_client = SearchIndexingBufferedSender(
    search_service_endpoint, index_name, AzureKeyCredential(search_service_api_key)
)
# Get the first document to check its schema
first_document = documents[0]

# Get the index schema
index_schema = index_client.get_index(index_name)

# Get the field names from the index schema
index_fields = {field.name: field.type for field in index_schema.fields}

# Check each field in the first document
for field, value in first_document.items():
    if field not in index_fields:
        print(f"Field '{field}' is not in the index schema.")

# Check for any fields in the index schema that are not in the documents
for field in index_fields:
    if field not in first_document:
        print(f"Field '{field}' is in the index schema but not in the documents.")

try:
    if documents:
        # Add upload actions for all documents in a single call
        upload_result = batch_client.upload_documents(documents=documents)

        # Check if the upload was successful
        # Manually flush to send any remaining documents in the buffer
        batch_client.flush()
        
        print(f"Uploaded {len(documents)} documents in total")
    else:
        print("No documents to upload.")
except HttpResponseError as e:
    print(f"An error occurred: {e}")
    raise  # Re-raise the exception to ensure it errors out
finally:
    # Clean up resources
    batch_client.close()
```

## Test search
Now that the data is uploaded, we'll test both vector similarity search and hybrid search locally below to make sure it is working as expected.

You can test both a pure vector search and hybrid search. Pure vector search passes in `None` to the `search_text` below and will only search on vector similarity. Hybrid search will combines the capabilities of traditional keyword-based search by passing in the query text `query` to the `search_text` with vector-based similarity search to provide more relevant and contextual results.


key: _node_content
string_value:
{"id_": "04454e6e-1440-432b-b3db-d18b8240b577", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "379642c4-6259-407d-8bc7-20486f37e723", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "98759ab3f29100bf093ebd1d9dd7e4e2a5592f3db8faba9515e6b412b3ff62f6", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "e251723b-8030-4504-ac3b-ba10de2fe7a1", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "f3ee599540dac316ac27075e41ddf5a024c4f093991a042917005b4a7c14ccdc", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "cbb7b582-28e9-43b7-96be-096df50a26a3", "node_type": "1", "metadata": {}, "hash": "bb4beb9b584c369cccb62e2d23a84ebf72500450ed5c571d1a03c4f1ad43c0aa", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 25727, "end_char_idx": 27870, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
```python
query = "What model should I use to embed?"
# Note: we'll have the GPT choose the category automatically once we put it in ChatGPT
category ="models"

search_client = SearchClient(search_service_endpoint, index_name, AzureKeyCredential(search_service_api_key))
vector_query = VectorizedQuery(vector=generate_embeddings(query, embeddings_model), k_nearest_neighbors=3, fields="content_vector")
  
results = search_client.search(  
    search_text=None, # Pass in None if you want to use pure vector search, and `query` if you want to use hybrid search
    vector_queries= [vector_query], 
    select=["title", "text"],
    filter=f"category eq '{category}'" 
)

for result in results:  
    print(result)

```

## Create Azure Function

Azure Functions are an easy way to build an API on top of our new AI search. Our code (see the `function_app.py` file in this folder, or linked [here](https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/function_app.py)) does the following:

1. Takes in an input of the user's query, search index endpoint, the index name, the k_nearest_neighbors*, the search column to use (either content_vector or title_vector), and whether it should use a hybrid query
2. Takes the user's query and embeds it.
3. Conducts a vector search and retrieves relevant text chunks.
4. Returns those relevant text chunks as the response body. 

*In the context of vector search, k_nearest_neighbors specifies the number of "closest" vectors (in terms of cosine similarity) that the search should return. For example, if k_nearest_neighbors is set to 3, the search will return the 3 vectors in the index that are most similar to the query vector.

> Note that this Azure Function _does not have any authentication_. However, you can set authentication on it following docs [here](https://learn.microsoft.com/en-us/azure/azure-functions/security-concepts?tabs=v4)

### Create storage account

We can create a new storage account using the code below, but feel free to skip that block and modify the subsequent steps to use an existing storage account. This may take up to 30 seconds.


key: _node_content
string_value:
{"id_": "cbb7b582-28e9-43b7-96be-096df50a26a3", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "379642c4-6259-407d-8bc7-20486f37e723", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "98759ab3f29100bf093ebd1d9dd7e4e2a5592f3db8faba9515e6b412b3ff62f6", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "04454e6e-1440-432b-b3db-d18b8240b577", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "b1d5975b2a1b8d9afad177a02fbe0c519cdeabdd24ba75a51d47333ead4a087b", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "d604890d-54cb-46ed-a9c7-8ac697b8e8f9", "node_type": "1", "metadata": {}, "hash": "d39ef8b1882dbf951f971da693677e563d83c5a644cd2f797b5123549bf259f9", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 27873, "end_char_idx": 29766, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
```python
## Update below with a different name
storage_account_name = "<enter-storage-account-name>"

## Use below SKU or any other SKU as per your requirement
sku = "Standard_LRS"
resource_client = ResourceManagementClient(credential, subscription_id)
storage_client = StorageManagementClient(credential, subscription_id)

# Create resource group if it doesn't exist
rg_result = resource_client.resource_groups.create_or_update(resource_group, {"location": region})

# Create storage account
storage_async_operation = storage_client.storage_accounts.begin_create(
    resource_group,
    storage_account_name,
    {
        "sku": {"name": sku},
        "kind": "StorageV2",
        "location": region,
    },
)
storage_account = storage_async_operation.result()

print(f"Storage account {storage_account.name} created")

```

### Create Function App
This Function App is where the python code will execute once it is triggered via a GPT Action. To read more about Function Apps, see the docs [here](https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview?pivots=programming-language-csharp). 

To deploy Function Apps, we'll need to use the Azure CLI and Azure Functions Core Tools. 

> The below will attempt to install it and run it based on your platform type in your virtual environment, but if that does not work, read the Azure documentation to figure out how to install [Azure Function Core Tools](https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-python?tabs=linux,bash,azure-cli,browser) and [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli). After doing that, run the below `subprocess.run` commands in your terminal after navigating to this folder.

First we'll make sure we have the relevant tools in the environment in order to run the Azure commands necessary. This may take a few minutes to install.


key: _node_content
string_value:
{"id_": "d604890d-54cb-46ed-a9c7-8ac697b8e8f9", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "379642c4-6259-407d-8bc7-20486f37e723", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "98759ab3f29100bf093ebd1d9dd7e4e2a5592f3db8faba9515e6b412b3ff62f6", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "cbb7b582-28e9-43b7-96be-096df50a26a3", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "2c36deea52b7a26b098599d8eab4e344da089a098856015a35f5b8e3fd22e484", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "c402fce5-fc85-4c3b-9878-ce3f110a1069", "node_type": "1", "metadata": {}, "hash": "cce1f9e13ec7f459778f6b35924847e19ac9e435703b7c3a2f1992ce004177f4", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 27873, "end_char_idx": 32120, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
```python
## Update below with a different name
storage_account_name = "<enter-storage-account-name>"

## Use below SKU or any other SKU as per your requirement
sku = "Standard_LRS"
resource_client = ResourceManagementClient(credential, subscription_id)
storage_client = StorageManagementClient(credential, subscription_id)

# Create resource group if it doesn't exist
rg_result = resource_client.resource_groups.create_or_update(resource_group, {"location": region})

# Create storage account
storage_async_operation = storage_client.storage_accounts.begin_create(
    resource_group,
    storage_account_name,
    {
        "sku": {"name": sku},
        "kind": "StorageV2",
        "location": region,
    },
)
storage_account = storage_async_operation.result()

print(f"Storage account {storage_account.name} created")

```

### Create Function App
This Function App is where the python code will execute once it is triggered via a GPT Action. To read more about Function Apps, see the docs [here](https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview?pivots=programming-language-csharp). 

To deploy Function Apps, we'll need to use the Azure CLI and Azure Functions Core Tools. 

> The below will attempt to install it and run it based on your platform type in your virtual environment, but if that does not work, read the Azure documentation to figure out how to install [Azure Function Core Tools](https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-python?tabs=linux,bash,azure-cli,browser) and [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli). After doing that, run the below `subprocess.run` commands in your terminal after navigating to this folder.

First we'll make sure we have the relevant tools in the environment in order to run the Azure commands necessary. This may take a few minutes to install.


```python
os_type = platform.system()

if os_type == "Windows":
    # Install Azure Functions Core Tools on Windows
    subprocess.run(["npm", "install", "-g", "azure-functions-core-tools@3", "--unsafe-perm", "true"], check=True)
    # Install Azure CLI on Windows
    subprocess.run(["powershell", "-Command", "Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\\AzureCLI.msi; Start-Process msiexec.exe -ArgumentList '/I AzureCLI.msi /quiet' -Wait"], check=True)
elif os_type == "Darwin":  # MacOS
    # Install Azure Functions Core Tools on MacOS
    if platform.machine() == 'arm64':
        # For M1 Macs
        subprocess.run(["arch", "-arm64", "brew", "install", "azure-functions-core-tools@3"], check=True)
    else:
        # For Intel Macs
        subprocess.run(["brew", "install", "azure-functions-core-tools@3"], check=True)
    # Install Azure CLI on MacOS
    subprocess.run(["brew", "update"], check=True)
    subprocess.run(["brew", "install", "azure-cli"], check=True)
elif os_type == "Linux":
    # Install Azure Functions Core Tools on Linux
    subprocess.run(["curl", "https://packages.microsoft.com/keys/microsoft.asc", "|", "gpg", "--dearmor", ">", "microsoft.gpg"], check=True, shell=True)
    subprocess.run(["sudo", "mv", "microsoft.gpg", "/etc/apt/trusted.gpg.d/microsoft.gpg"], check=True)
    subprocess.run(["sudo", "sh", "-c", "'echo \"deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-$(lsb_release -cs)-prod $(lsb_release -cs) main\" > /etc/apt/sources.list.d/dotnetdev.list'"], check=True, shell=True)
    subprocess.run(["sudo", "apt-get", "update"], check=True)
    subprocess.run(["sudo", "apt-get", "install", "azure-functions-core-tools-3"], check=True)
    # Install Azure CLI on Linux
    subprocess.run(["curl", "-sL", "https://aka.ms/InstallAzureCLIDeb", "|", "sudo", "bash"], check=True, shell=True)
else:
    # Raise an error if the operating system is not supported
    raise OSError("Unsupported operating system")

# Verify the installation of Azure Functions Core Tools
subprocess.run(["func", "--version"], check=True)
# Verify the installation of Azure CLI
subprocess.run(["az", "--version"], check=True)

subprocess.run([
    "az", "login"
], check=True)
```

Now, we need to create a `local.settings.json` file with our key environment variables for Azure


key: _node_content
string_value:
{"id_": "c402fce5-fc85-4c3b-9878-ce3f110a1069", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "379642c4-6259-407d-8bc7-20486f37e723", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "98759ab3f29100bf093ebd1d9dd7e4e2a5592f3db8faba9515e6b412b3ff62f6", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "d604890d-54cb-46ed-a9c7-8ac697b8e8f9", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "098dd32ba38b684e7e73ee6b1af00b4d89e95b34b52c0d5da9434999270c3116", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "010cd799-9f0f-4c22-81b2-f0a2d032dc9f", "node_type": "1", "metadata": {}, "hash": "da02124675a40c469a6698bb71a29fd692b97174b81741ce23c1003646e01ee6", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 32123, "end_char_idx": 34773, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
```python
local_settings_content = f"""
{{
  "IsEncrypted": false,
  "Values": {{
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "python",
    "OPENAI_API_KEY": "{openai_api_key}",
    "EMBEDDINGS_MODEL": "{embeddings_model}",
    "SEARCH_SERVICE_API_KEY": "{search_service_api_key}",
  }}
}}
"""

with open("local.settings.json", "w") as file:
    file.write(local_settings_content)
```

Check the `local.settings.json` file and make sure that the environment variables match what you expect. 

Now, give your app a name below, and you are ready to create your Function App and then publish your function. 


```python
# Replace this with your own values. This name will appear in the URL of the API call https://<app_name>.azurewebsites.net
app_name = "<app-name>"

subprocess.run([
    "az", "functionapp", "create",
    "--resource-group", resource_group,
    "--consumption-plan-location", region,
    "--runtime", "python",
    "--name", app_name,
    "--storage-account", storage_account_name,
    "--os-type", "Linux",
], check=True)
```

Once we've created the Function App, we now want to add the configuration variables to the function app to use in the function. Specifically, we need the `OPENAI_API_KEY`, the `SEARCH_SERVICE_API_KEY`, and the `EMBEDDINGS_MODEL` as these are all used in the `function_app.py` code.


```python
# Collect the relevant environment variables 
env_vars = {
    "OPENAI_API_KEY": openai_api_key,
    "SEARCH_SERVICE_API_KEY": search_service_api_key,
    "EMBEDDINGS_MODEL": embeddings_model
}

# Create the settings argument for the az functionapp create command
settings_args = []
for key, value in env_vars.items():
    settings_args.append(f"{key}={value}")

subprocess.run([
    "az", "functionapp", "config", "appsettings", "set",
    "--name", app_name,
    "--resource-group", resource_group,
    "--settings", *settings_args
], check=True)
```

We are now ready to publish your function code `function_app.py` to the Azure Function. This may take up to 10 minutes to deploy. Once this is finished, we now have an API endpoint using an Azure Function on top of Azure AI Search.


```python
subprocess.run([
    "func", "azure", "functionapp", "publish", app_name
], check=True)
```

## Input in a Custom GPT in ChatGPT
Now that we have an Azure Function that queries this Vector Search Index, let's put it as a GPT Action!

See documentation [here](https://openai.com/index/introducing-gpts/) on GPTs and [here](https://platform.openai.com/docs/actions) on GPT Actions. Use the below as the instructions for the GPT and as the OpenAPI spec for the GPT Action.


key: _node_content
string_value:
{"id_": "010cd799-9f0f-4c22-81b2-f0a2d032dc9f", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "379642c4-6259-407d-8bc7-20486f37e723", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "98759ab3f29100bf093ebd1d9dd7e4e2a5592f3db8faba9515e6b412b3ff62f6", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "c402fce5-fc85-4c3b-9878-ce3f110a1069", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "ca1e3333af43200e28c0013ee4a0a0643d3d8ccfcf8735dfec5d1f31b67f0f48", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "ae019c35-afe2-4d6a-9f81-e76c35ce0b68", "node_type": "1", "metadata": {}, "hash": "1570acc15216e7b75854b9beb12a95b149e9e28ab8f40783769998a68aae6620", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 33496, "end_char_idx": 35386, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
```python
# Collect the relevant environment variables 
env_vars = {
    "OPENAI_API_KEY": openai_api_key,
    "SEARCH_SERVICE_API_KEY": search_service_api_key,
    "EMBEDDINGS_MODEL": embeddings_model
}

# Create the settings argument for the az functionapp create command
settings_args = []
for key, value in env_vars.items():
    settings_args.append(f"{key}={value}")

subprocess.run([
    "az", "functionapp", "config", "appsettings", "set",
    "--name", app_name,
    "--resource-group", resource_group,
    "--settings", *settings_args
], check=True)
```

We are now ready to publish your function code `function_app.py` to the Azure Function. This may take up to 10 minutes to deploy. Once this is finished, we now have an API endpoint using an Azure Function on top of Azure AI Search.


```python
subprocess.run([
    "func", "azure", "functionapp", "publish", app_name
], check=True)
```

## Input in a Custom GPT in ChatGPT
Now that we have an Azure Function that queries this Vector Search Index, let's put it as a GPT Action!

See documentation [here](https://openai.com/index/introducing-gpts/) on GPTs and [here](https://platform.openai.com/docs/actions) on GPT Actions. Use the below as the instructions for the GPT and as the OpenAPI spec for the GPT Action.


### Create OpenAPI Spec
Below is a sample OpenAPI spec. When we run the block below, a functional spec should be copied to the clipboard to paste in the GPT Action.

Note that this does not have any authentication by default, but you can set up Azure Functions with OAuth by following the pattern in [this cookbook](https://cookbook.openai.com/examples/chatgpt/sharepoint_azure_function/using_azure_functions_and_microsoft_graph_to_query_sharepoint) in the Authentication section or looking at the documentation [here](https://learn.microsoft.com/en-us/azure/app-service/overview-authentication-authorization).


key: _node_content
string_value:
{"id_": "ae019c35-afe2-4d6a-9f81-e76c35ce0b68", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "379642c4-6259-407d-8bc7-20486f37e723", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "98759ab3f29100bf093ebd1d9dd7e4e2a5592f3db8faba9515e6b412b3ff62f6", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "010cd799-9f0f-4c22-81b2-f0a2d032dc9f", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "fc8cbf41011b226b53028fbc31d648d7d112f75c82704bbae0a67c4d6104ec0b", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "e12f004b-2ff6-4a67-be2e-785ddaee8b60", "node_type": "1", "metadata": {}, "hash": "656140e72a3e6217c4006e5160f1f47f0387be5d092554f2077a7aeeb402c574", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 34294, "end_char_idx": 38289, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
```python
subprocess.run([
    "func", "azure", "functionapp", "publish", app_name
], check=True)
```

## Input in a Custom GPT in ChatGPT
Now that we have an Azure Function that queries this Vector Search Index, let's put it as a GPT Action!

See documentation [here](https://openai.com/index/introducing-gpts/) on GPTs and [here](https://platform.openai.com/docs/actions) on GPT Actions. Use the below as the instructions for the GPT and as the OpenAPI spec for the GPT Action.


### Create OpenAPI Spec
Below is a sample OpenAPI spec. When we run the block below, a functional spec should be copied to the clipboard to paste in the GPT Action.

Note that this does not have any authentication by default, but you can set up Azure Functions with OAuth by following the pattern in [this cookbook](https://cookbook.openai.com/examples/chatgpt/sharepoint_azure_function/using_azure_functions_and_microsoft_graph_to_query_sharepoint) in the Authentication section or looking at the documentation [here](https://learn.microsoft.com/en-us/azure/app-service/overview-authentication-authorization).


```python

spec = f"""
openapi: 3.1.0
info:
  title: Vector Similarity Search API
  description: API for performing vector similarity search.
  version: 1.0.0
servers:
  - url: https://{app_name}.azurewebsites.net/api
    description: Main (production) server
paths:
  /vector_similarity_search:
    post:
      operationId: vectorSimilaritySearch
      summary: Perform a vector similarity search.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                search_service_endpoint:
                  type: string
                  description: The endpoint of the search service.
                index_name:
                  type: string
                  description: The name of the search index.
                query:
                  type: string
                  description: The search query.
                k_nearest_neighbors:
                  type: integer
                  description: The number of nearest neighbors to return.
                search_column:
                  type: string
                  description: The name of the search column.
                use_hybrid_query:
                  type: boolean
                  description: Whether to use a hybrid query.
                category:
                  type: string
                  description: category to filter.
              required:
                - search_service_endpoint
                - index_name
                - query
                - k_nearest_neighbors
                - search_column
                - use_hybrid_query
      responses:
        '200':
          description: A successful response with the search results.
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          description: The identifier of the result item.
                        score:
                          type: number
                          description: The similarity score of the result item.
                        content:
                          type: object
                          description: The content of the result item.
        '400':
          description: Bad request due to missing or invalid parameters.
        '500':
          description: Internal server error.
"""
pyperclip.copy(spec)
print("OpenAPI spec copied to clipboard")
print(spec)
```

### Create GPT Instructions

Feel free to modify instructions as you see fit. Check out our docs [here](https://platform.openai.com/docs/guides/prompt-engineering) for some tips on prompt engineering.


key: _node_content
string_value:
{"id_": "e12f004b-2ff6-4a67-be2e-785ddaee8b60", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "379642c4-6259-407d-8bc7-20486f37e723", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "98759ab3f29100bf093ebd1d9dd7e4e2a5592f3db8faba9515e6b412b3ff62f6", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "ae019c35-afe2-4d6a-9f81-e76c35ce0b68", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/rag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb", "title": "Azure AI Search as a vector database + Azure Functions for GPT integration in ChatGPT", "tokens": 8713, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "5609ccd8fef6db6252f1439864e95bd7d156bb2f62ed0884bb4e4c8ae18ac9d8", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 38292, "end_char_idx": 39960, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
```python
instructions = f'''
You are an OAI docs assistant. You have an action in your knowledge base where you can make a POST request to search for information. The POST request should always include: {{
    "search_service_endpoint": "{search_service_endpoint}",
    "index_name": {index_name},
    "query": "<user_query>",
    "k_nearest_neighbors": 1,
    "search_column": "content_vector",
    "use_hybrid_query": true,
    "category": "<category>"
}}. Only the query and category change based on the user's request. Your goal is to assist users by performing searches using this POST request and providing them with relevant information based on the query.

You must only include knowledge you get from your action in your response.
The category must be from the following list: {categories}, which you should determine based on the user's query. If you cannot determine, then do not include the category in the POST request.
'''
pyperclip.copy(instructions)
print("GPT Instructions copied to clipboard")
print(instructions)
```

We now have a GPT that queries a vector database! 

# Recap
We've now successfully integrated Azure AI Search with GPT Actions in ChatGPT by doing the following:
1. embedded them using OpenAI's embeddings, while adding some additional metadata using gpt-4o.
2. uploaded that data to Azure AI Search.
3. created an endpoint to query it using Azure Functions.
4. incorporated it into a Custom GPT. 

Our GPT can now retrieve information to help answer user queries, making it much more accurate and customized to our data. Here's the GPT in action:

# ![azure-rag-quickstart-gpt.png](../../../../images/azure-rag-quickstart-gpt.png)


key: url
string_value:
https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb


key: _node_content
string_value:
{"id_": "07d12c72-8fc2-48c8-94c5-f27e601b3d68", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0b2dbbc2-c0a2-4960-9490-defd15e48c56", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "498acf78e9b003aa4d3215a59649de7bc0e277df73adff40a804bf31b2f2ad51", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "02c04605-5d8f-40db-b6bd-eec0c7076e02", "node_type": "1", "metadata": {}, "hash": "d92159f1fd1c7fd540d2a233c74b4ebe8dfc72e9c790502457153290acef14f9", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 0, "end_char_idx": 2662, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
# Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT



## Overview

This solution enables a GPT action to answer a user’s question with the context of files the user can access in SharePoint or Office365, using Microsoft’s Graph API [search capabilities](https://learn.microsoft.com/en-us/graph/api/resources/search-api-overview?view=graph-rest-1.0) and the ability to [retrieve files](https://learn.microsoft.com/en-us/graph/api/driveitem-get?view=graph-rest-1.0\&tabs=http). It uses Azure Functions to process the Graph API response and convert it to a human readable format or structure it in a way ChatGPT understands. This code is meant to be directional, and you should modify it to your requirements.


## How it Works

There are two solutions below, with code for each in the repository. 

The first solution **Solution 1** uses the ability to[ retrieve files in Actions](https://platform.openai.com/docs/actions/sending-files) and use them as if you had uploaded them directly to a conversation. The Azure Function returns a base64 string that ChatGPT converts into a file, treated the same way as if you uploaded the file directly to the conversation. This solution can handle more types of files than the other solution below, but does have size volume limitations (see docs [here](https://platform.openai.com/docs/actions/sending-files))

The second solution **Solution 2** pre-processes the file within the Azure Function. The Azure Function returns text, instead of the base64 encoded file. Due to the pre-processing and the conversion to text, this solution is best used for large, unstructured documents, and for when you want to analyze more than the amount of files supported in the first solution (see documentation [here](https://platform.openai.com/docs/actions/sending-files)).


### Solution 1: Returning the file to GPT using the [Returning Files](https://platform.openai.com/docs/actions/sending-files) pattern

![](../../../images/solution_1.gif)

This solution uses a Node.js Azure Function to, based on the logged in user:

1. Search for a relevant file that the user has access to, based on the user’s initial question. 

2. For each file that is found, convert it to a base64 string.

3. Format the data in the structure ChatGPT is expecting [here](https://platform.openai.com/docs/actions/sending-files/inline-option).

4. Return that to ChatGPT. The GPT then can use those files as if you had uploaded it to the conversation.


![](../../../images/solution_1_architecture.png)


### Solution 2: Converting the file to text in the Azure Function


![](../../../images/solution_2.gif)


key: _node_content
string_value:
{"id_": "02c04605-5d8f-40db-b6bd-eec0c7076e02", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0b2dbbc2-c0a2-4960-9490-defd15e48c56", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "498acf78e9b003aa4d3215a59649de7bc0e277df73adff40a804bf31b2f2ad51", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "07d12c72-8fc2-48c8-94c5-f27e601b3d68", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "3242de8bbea4b8659c80ac52b96e29fdfdab5bb7d39fef3633839e94892662b8", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "95c3b17b-d4a6-410b-b9b9-f7ef9753195c", "node_type": "1", "metadata": {}, "hash": "16da118ca5df00633330efd515c170b36a862e194322564dbb63f6bebec31e94", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 1850, "end_char_idx": 3649, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
### Solution 1: Returning the file to GPT using the [Returning Files](https://platform.openai.com/docs/actions/sending-files) pattern

![](../../../images/solution_1.gif)

This solution uses a Node.js Azure Function to, based on the logged in user:

1. Search for a relevant file that the user has access to, based on the user’s initial question. 

2. For each file that is found, convert it to a base64 string.

3. Format the data in the structure ChatGPT is expecting [here](https://platform.openai.com/docs/actions/sending-files/inline-option).

4. Return that to ChatGPT. The GPT then can use those files as if you had uploaded it to the conversation.


![](../../../images/solution_1_architecture.png)


### Solution 2: Converting the file to text in the Azure Function


![](../../../images/solution_2.gif)


This solution uses a Node.js Azure Function to, based on the logged in user:

1. Search for a relevant file that the user has access to, based on the user’s initial question.

2. For each file that is found, convert it to a consistent readable format and retrieve all the text.

3. Use GPT 3.5-turbo (gpt-3.5-turbo-0125) to extract the relevant text from the files based on the initial user’s question. Note the pricing of GPT 3.5 turbo [here](https://openai.com/pricing#language-models) - since we are dealing with small token chunks, the cost of this step is nominal.  

4. Returns that data to ChatGPT. The GPT then uses that information to respond to the user's initial question.

As you can see from the below architecture diagram, the first three steps are the same as Solution 1. The main difference is that this solution converts the file to text instead of a base64 string, and then summarizes that text using GPT 3.5 Turbo.


![](../../../images/solution_2_architecture.png)


key: _node_content
string_value:
{"id_": "95c3b17b-d4a6-410b-b9b9-f7ef9753195c", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0b2dbbc2-c0a2-4960-9490-defd15e48c56", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "498acf78e9b003aa4d3215a59649de7bc0e277df73adff40a804bf31b2f2ad51", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "02c04605-5d8f-40db-b6bd-eec0c7076e02", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "d9feaf1d9f9b9d8ba37756f3d908d5ab4dcc3a29a6db161587b542f613f7460e", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "51fae1e0-fc53-46ec-a6bc-00abee5a42be", "node_type": "1", "metadata": {}, "hash": "3bbd4e827c05984ebbc5515eb87a59d2433d2793d2beb9ac38446c4c68b0c329", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 2508, "end_char_idx": 5776, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
![](../../../images/solution_1_architecture.png)


### Solution 2: Converting the file to text in the Azure Function


![](../../../images/solution_2.gif)


This solution uses a Node.js Azure Function to, based on the logged in user:

1. Search for a relevant file that the user has access to, based on the user’s initial question.

2. For each file that is found, convert it to a consistent readable format and retrieve all the text.

3. Use GPT 3.5-turbo (gpt-3.5-turbo-0125) to extract the relevant text from the files based on the initial user’s question. Note the pricing of GPT 3.5 turbo [here](https://openai.com/pricing#language-models) - since we are dealing with small token chunks, the cost of this step is nominal.  

4. Returns that data to ChatGPT. The GPT then uses that information to respond to the user's initial question.

As you can see from the below architecture diagram, the first three steps are the same as Solution 1. The main difference is that this solution converts the file to text instead of a base64 string, and then summarizes that text using GPT 3.5 Turbo.


![](../../../images/solution_2_architecture.png)


### Why is this necessary instead of interacting with the Microsoft API directly?

- Following the guide [here](https://learn.microsoft.com/en-us/graph/search-concept-files), the [Microsoft Graph Search API](https://learn.microsoft.com/en-us/graph/search-concept-files) returns references to files that fit the criteria, but not the file contents themselves. So, we need have two options, corresponding to the two solutions above:

  - **Solution 1: restructure the response for compatibility:** 

    1. We need to restructure the response from that API so that it matches the expected structure in `openaiFileResponse` outlined [here](https://platform.openai.com/docs/actions/getting-started/inline-option).

  - **Solution 2: extract the text from the files directly:**

    1. loop through the returned files and download the files using the [Download File endpoint](https://learn.microsoft.com/en-us/graph/api/driveitem-get-content?view=graph-rest-1.0\&tabs=http) or [Convert File endpoint](https://learn.microsoft.com/en-us/graph/api/driveitem-get-content-format?view=graph-rest-1.0\&tabs=http)

    2. convert that Binary stream to human readable text using [pdf-parse](https://www.npmjs.com/package/pdf-parse)

    3. Then, we can optimize further by summarizing using gpt-3.5-turbo in the function to help with the 100,000 character limit we impose on Actions today. 


## Prerequisites

- Azure Portal with access to create Azure Function Apps and Azure Entra App Registrations

- Postman (and knowledge of APIs and OAuth)

- _Solution 2 Only:_ An OpenAI API Key from platform.openai.com


## Solution 1 + Solution 2 Installation Instructions

The below are the instructions for setting up the Azure Function with Authentication. Please make sure to follow these steps before implementing the code. 

> These installation instructions apply to both Solution 1 and Solution 2. We encourage setting both solutions up as separate functions within the same Function App to test which works best for you, as once you set up one function, it takes only a few minutes to set up another function in that same function app.


key: _node_content
string_value:
{"id_": "51fae1e0-fc53-46ec-a6bc-00abee5a42be", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0b2dbbc2-c0a2-4960-9490-defd15e48c56", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "498acf78e9b003aa4d3215a59649de7bc0e277df73adff40a804bf31b2f2ad51", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "95c3b17b-d4a6-410b-b9b9-f7ef9753195c", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "f3724a58c5bece95211232ae52167953ad6fd5457fe2d8776ae4eeed14e63ce6", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "f416a368-b471-4f4e-b640-8fe95fe49e95", "node_type": "1", "metadata": {}, "hash": "71ccef29f47fb8ba5b9d2066fc28bd7013953d4e5ed671526fa00bfc0e96e3f6", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 5031, "end_char_idx": 7862, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
## Prerequisites

- Azure Portal with access to create Azure Function Apps and Azure Entra App Registrations

- Postman (and knowledge of APIs and OAuth)

- _Solution 2 Only:_ An OpenAI API Key from platform.openai.com


## Solution 1 + Solution 2 Installation Instructions

The below are the instructions for setting up the Azure Function with Authentication. Please make sure to follow these steps before implementing the code. 

> These installation instructions apply to both Solution 1 and Solution 2. We encourage setting both solutions up as separate functions within the same Function App to test which works best for you, as once you set up one function, it takes only a few minutes to set up another function in that same function app.


### Installing the app

You can read more about languages and deployment options for Azure Functions on the left hand side of the documentation [here](https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview?pivots=programming-language-csharp). 


#### Option 1: Use VSCode

See Microsoft’s documentation [here](https://learn.microsoft.com/en-us/azure/azure-functions/functions-develop-vs-code?tabs=node-v4,python-v2,isolated-process\&pivots=programming-language-javascript) for how to deploy using VSCode. If you have familiarity with this approach, feel free to use it. 


#### Option 2: Directly in Azure Portal

See the documentation [here](https://learn.microsoft.com/en-us/azure/azure-functions/functions-create-function-app-portal?pivots=programming-language-javascript) for how to deploy using the Azure portal. We’ll walk through an example here step by step.

> Note: you can use Part 1 - Part 4 below to set up any Azure Function App with Entra Authentication


##### Part 1: Create Function


![](../../../images/create_function_app.png)


1. Create an [Azure Function app](https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview?pivots=programming-language-csharp). I used the following settings but you can use anything you are comfortable with. Note that not every language / operating system allows for editing the functions in the console directly - the combination I chose below does. For my walkthrough, I left everything as default and made the selections below

   1. Basics

      1. _Do you want to deploy code or container image?:_  **Code**

      2. _Runtime stack:_ **Node.js**

      3. _Operating system:_ **Windows**

   2. Networking

      1. _Enable public access_: **on (need this on to connect to the GPT)**

2. After completing the above, you’ll land on the “Deployments” page. Once the deployment completes (which should only take a few minutes) click on **“Go to Resource”** to go back to the Function App

  > You may get an error the first time you attempt this, click create again and it will likely work.


key: _node_content
string_value:
{"id_": "f416a368-b471-4f4e-b640-8fe95fe49e95", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0b2dbbc2-c0a2-4960-9490-defd15e48c56", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "498acf78e9b003aa4d3215a59649de7bc0e277df73adff40a804bf31b2f2ad51", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "51fae1e0-fc53-46ec-a6bc-00abee5a42be", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "8d9a2fb8ea62b3d029f0c9c170048bc737ebf29d373463cf2b156d2c1b1c4162", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "fe988d0a-2ae6-4804-9a7a-2fe8763f0bde", "node_type": "1", "metadata": {}, "hash": "e0d2712dbc56c9c372c202f365e757622269b5d94db053d911f13a8132b38e31", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 6372, "end_char_idx": 10316, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
#### Option 2: Directly in Azure Portal

See the documentation [here](https://learn.microsoft.com/en-us/azure/azure-functions/functions-create-function-app-portal?pivots=programming-language-javascript) for how to deploy using the Azure portal. We’ll walk through an example here step by step.

> Note: you can use Part 1 - Part 4 below to set up any Azure Function App with Entra Authentication


##### Part 1: Create Function


![](../../../images/create_function_app.png)


1. Create an [Azure Function app](https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview?pivots=programming-language-csharp). I used the following settings but you can use anything you are comfortable with. Note that not every language / operating system allows for editing the functions in the console directly - the combination I chose below does. For my walkthrough, I left everything as default and made the selections below

   1. Basics

      1. _Do you want to deploy code or container image?:_  **Code**

      2. _Runtime stack:_ **Node.js**

      3. _Operating system:_ **Windows**

   2. Networking

      1. _Enable public access_: **on (need this on to connect to the GPT)**

2. After completing the above, you’ll land on the “Deployments” page. Once the deployment completes (which should only take a few minutes) click on **“Go to Resource”** to go back to the Function App

  > You may get an error the first time you attempt this, click create again and it will likely work. 


##### Part 2: Set up Auth

3. On the left-hand side menu of the Azure Function App, click on **Authentication** under the **Settings** menu. 

   1. Add identity provider

   2. Select **Microsoft** as identity provider. 

   3. **Workforce** as tenant type

   4. **Create a new application.** The instructions are fairly similar if you are using an existing application, but it is easier to create a new application as it will have the callback URLs and the API exposed automatically using “Easy Auth”. You can read more about that [**here**](https://learn.microsoft.com/en-us/azure/app-service/overview-authentication-authorization).

   5. Leave all the other settings on this page as the default, but feel free to change based on your internal guidelines.

   6. On the **permissions** tab, click **Add Permission** and add **Files.Read.All** and **Sites.ReadAll**, then **Add.** This allows this application to read files which is important in order to use the Microsoft Graph Search API.

4. Once it is created, **click on the enterprise application you just created** (so, leave the Function App page and land on the Enterprise Application that you just spun up)**.** We are now going to give it one more permission, to execute the Azure Function by impersonating the user logging into the application. See [here](https://learn.microsoft.com/en-us/azure/app-service/configure-authentication-provider-aad?tabs=workforce-tenant) for more details.

   1. On the main page, click “**View API Permissions”**

   2. Search for **Microsoft Azure App Service** in the **APIs my organization uses** and find **user\_impersonation** 

   3. Add it, then you’ll need an Admin on Azure Portal to **Grant Admin Consent.**

5) **Within that enterprise application**, Click on **“Expose an API”** on the left hand menu under **Manage,** then copy the **scope** that was created using the **Copy to Clipboard** button. The scope should look like “api://\<insert-uuid>/user\_impersonation”. **Save this for later as** `SCOPE`**.**

6) Click on **“Authentication”** on the left hand menu under **Manage**

   1. Under the **Web** section, you’ll notice one callback URI was added automatically. Add the Postman redirect URI (<https://oauth.pstmn.io/v1/callback>) for testing.

7) On the left-hand side, go to **Overview**. Copy the **application (client) ID** and and the **directory (tenant) ID** and **save for later as** `CLIENT_ID` **and** `TENANT_ID`**.**


key: _node_content
string_value:
{"id_": "fe988d0a-2ae6-4804-9a7a-2fe8763f0bde", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0b2dbbc2-c0a2-4960-9490-defd15e48c56", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "498acf78e9b003aa4d3215a59649de7bc0e277df73adff40a804bf31b2f2ad51", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "f416a368-b471-4f4e-b640-8fe95fe49e95", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "9350441c6a63d74d570c7bf3492092566759ff818eed87d55946e6455541e383", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "f8824af9-33e3-4a67-adfa-018b5367b9f2", "node_type": "1", "metadata": {}, "hash": "9b820fab1b9917b0146cbd3e8a290c12184ea4af4c62d4c10932c66f19f8e5e8", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 10319, "end_char_idx": 12971, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
##### Part 3: Set up Test Function

8. Leave the page by going home and then back to your **Function App.**

9. Click on **Create Function.** For this example, I’m going to develop it in the portal, but you can also use VSCode or another IDE.

   1. Choose **HTTP trigger**

   2. For **Authorization Level,** you can choose any key type you want.

      1. Note this may error out the first time, but it is likely the Function did create, do a refresh of the page to check.

10. Click on the function you just created (You may need to click refresh to see it). Click on **Get Function URL** and save it to test in Postman. You will also use this when creating the OpenAPI spec later when you put it into the GPT. 


![](../../../images/get_function_url.png)

11. Go back to the function app and click on **Configuration.** Show the value for the `MICROSOFT_PROVIDER_AUTHENTICATION_SECRET` variable, copy it (click advanced edit to copy it), and **save it for later.**  

At this point, you should have a test function created, and you should have saved a **client id, tenant id, secret, scope, and function URL**. You are now ready to test out the authentication in Postman


##### Part 4: Test Authentication in Postman

12. Try to hit endpoint you created in Postman using those OAuth settings:

    1. **Grant Type:** Authorization Code

    2. **Auth URL**: [https://login.microsoftonline.com/](about:blank)`TENANT_ID`[/oauth2/v2.0/authorize](about:blank)

    3. **Auth Token URL**: [https://login.microsoftonline.com/`TENANT_ID`/oauth2/v2.0/token](about:blank)

    4. **Client ID:** `CLIENT_ID` from step 7 above

    5. **Client secret:** `MICROSOFT_PROVIDER_AUTHENTICATION_SECRET `from step 11 above

    6. **Scope**: `SCOPE` from step 5 above

    7. **Client credentials**: Send client credentials in body

13. You will need to click **Get New Access Token**, and then hit the endpoint you saved in step 10 above. If it was successful, you should get this response: `”This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.”`


##### Part 5: Add in Function Code

Now that you have an authenticated Azure Function, we can update the function to search SharePoint / O365


14. Go to your test function and paste in the code from [this file](https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/solution_one_file_retrieval.js) for Solution 1 and [this file](https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/solution_two_preprocessing.js) for Solution 2.  Save the function.


key: _node_content
string_value:
{"id_": "f8824af9-33e3-4a67-adfa-018b5367b9f2", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0b2dbbc2-c0a2-4960-9490-defd15e48c56", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "498acf78e9b003aa4d3215a59649de7bc0e277df73adff40a804bf31b2f2ad51", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "fe988d0a-2ae6-4804-9a7a-2fe8763f0bde", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "b957471334e2640d8ca1fc46e3b6626f7134a80c99c93135775bc6a37278ab8b", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "a95534e3-3ee0-4069-806d-0330e547d59f", "node_type": "1", "metadata": {}, "hash": "7ab420b16a47b82ab049250898583c5bc8401310a2430cc8b7e899ca8619e537", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 11496, "end_char_idx": 14361, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
##### Part 4: Test Authentication in Postman

12. Try to hit endpoint you created in Postman using those OAuth settings:

    1. **Grant Type:** Authorization Code

    2. **Auth URL**: [https://login.microsoftonline.com/](about:blank)`TENANT_ID`[/oauth2/v2.0/authorize](about:blank)

    3. **Auth Token URL**: [https://login.microsoftonline.com/`TENANT_ID`/oauth2/v2.0/token](about:blank)

    4. **Client ID:** `CLIENT_ID` from step 7 above

    5. **Client secret:** `MICROSOFT_PROVIDER_AUTHENTICATION_SECRET `from step 11 above

    6. **Scope**: `SCOPE` from step 5 above

    7. **Client credentials**: Send client credentials in body

13. You will need to click **Get New Access Token**, and then hit the endpoint you saved in step 10 above. If it was successful, you should get this response: `”This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.”`


##### Part 5: Add in Function Code

Now that you have an authenticated Azure Function, we can update the function to search SharePoint / O365


14. Go to your test function and paste in the code from [this file](https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/solution_one_file_retrieval.js) for Solution 1 and [this file](https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/solution_two_preprocessing.js) for Solution 2.  Save the function. 


> **This code is meant to be directional** - while it should work out of the box, it is designed to be customized to your needs (see examples towards the end of this document).

15. Set up the following env variables by going to the **Configuration** tab on the left under **Settings.** Note that this may be listed directly in **Environment Variables** depending on your Azure UI.

    1. `TENANT_ID`: copied from previous section

    2. `CLIENT_ID`: copied from previous section

    3. _Solution 2 only:_

       1. `OPENAI_API_KEY:` spin up an OpenAI API key on platform.openai.com.

16. Go to the **Console** tab under the **Development Tools**

    1. Install the following packages in console

       1. `npm install @microsoft/microsoft-graph-client`

       2. `npm install axios`

       3. _Solution 2 only:_

          1. `npm install pdf-parse`

          2. `npm install openai`

17. Once this is complete, try calling the function (POST call) from Postman again, putting the below into body (using a query and search term you think will generate responses).

     *Solution 1*:
     ```json
    {
        "searchTerm": "<choose a search term>"
    }
    ```
    *Solution 2*: 
    ```json
    {
        "query": "<choose a question>",
        "searchTerm": "<choose a search term>"
    }
    ```
18. If you get a response, you are ready to set this up with a Custom GPT!


key: _node_content
string_value:
{"id_": "a95534e3-3ee0-4069-806d-0330e547d59f", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0b2dbbc2-c0a2-4960-9490-defd15e48c56", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "498acf78e9b003aa4d3215a59649de7bc0e277df73adff40a804bf31b2f2ad51", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "f8824af9-33e3-4a67-adfa-018b5367b9f2", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "dba95eb0482b97489c87c49c0c68f6538f504d91fa646a97ce8bb4ecc4523325", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "26588861-5a5e-487c-a62a-93e6a409afc4", "node_type": "1", "metadata": {}, "hash": "10348bb134bcf4ba4429bcc99563c05ef9681541e341bed5291c233eaf562e6a", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 12975, "end_char_idx": 16131, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
> **This code is meant to be directional** - while it should work out of the box, it is designed to be customized to your needs (see examples towards the end of this document).

15. Set up the following env variables by going to the **Configuration** tab on the left under **Settings.** Note that this may be listed directly in **Environment Variables** depending on your Azure UI.

    1. `TENANT_ID`: copied from previous section

    2. `CLIENT_ID`: copied from previous section

    3. _Solution 2 only:_

       1. `OPENAI_API_KEY:` spin up an OpenAI API key on platform.openai.com.

16. Go to the **Console** tab under the **Development Tools**

    1. Install the following packages in console

       1. `npm install @microsoft/microsoft-graph-client`

       2. `npm install axios`

       3. _Solution 2 only:_

          1. `npm install pdf-parse`

          2. `npm install openai`

17. Once this is complete, try calling the function (POST call) from Postman again, putting the below into body (using a query and search term you think will generate responses).

     *Solution 1*:
     ```json
    {
        "searchTerm": "<choose a search term>"
    }
    ```
    *Solution 2*: 
    ```json
    {
        "query": "<choose a question>",
        "searchTerm": "<choose a search term>"
    }
    ```
18. If you get a response, you are ready to set this up with a Custom GPT!


##### Part 6: Setting it up in a Custom GPT

19. Generate an OpenAPI spec for your endpoint. 

20. Paste that into the Actions section of a GPT, and choose OAuth as the authentication type. Fill out the OAuth settings the same way you did for Postman above. 

21. Once you save the action, you will see a callback URI at the bottom of the GPT configuration. Copy that URL, then go **back to your Function App in the Azure Portal**.

22. Click on **Authentication** under **Settings**, then click on your Entra application.

23. Once you are there, then click **Authentication** under the **Manage** section.

24. Add a new Redirect URI under the **Web** section of that page, and paste in the Callback URI you got from step 20, then click Save. 

25. Customize the prompt to use this action. You can see a sample prompt in the Sample GPT Instructions in this document, which is customized to try three times to find an answer by changing the searchTerm. 

26. Test out the GPT and it should work as expected.


## Solution 1 Detailed Walkthrough: Returning the File to GPT using the [Returning Files](https://platform.openai.com/docs/actions/sending-files) Pattern


The below walks through setup instructions and walkthrough unique to this solution. You can find the entire code [here](https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/solution_one_file_retrieval.js). If you are interested in Solution 2 instead, you can jump [here](#solution-2-converting-the-file-to-text-in-the-azure-function-1). 

### Code Walkthrough

The below walks through the different parts of the function. Before you begin, ensure you have the required packages installed and environment variables set up (see the Installation Steps section).


key: _node_content
string_value:
{"id_": "26588861-5a5e-487c-a62a-93e6a409afc4", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0b2dbbc2-c0a2-4960-9490-defd15e48c56", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "498acf78e9b003aa4d3215a59649de7bc0e277df73adff40a804bf31b2f2ad51", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "a95534e3-3ee0-4069-806d-0330e547d59f", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "d4a9f5796872acbe50b69cb1da2823f311cc7fd207dcb9bf7e541e51729abc7a", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "bdb462d1-29f6-4f70-b3d0-067b723966d7", "node_type": "1", "metadata": {}, "hash": "7c463bcf130770fb3ec03be51a77c0c55d51607aaf65f6f016f151477c6f8220", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 15375, "end_char_idx": 18123, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
## Solution 1 Detailed Walkthrough: Returning the File to GPT using the [Returning Files](https://platform.openai.com/docs/actions/sending-files) Pattern


The below walks through setup instructions and walkthrough unique to this solution. You can find the entire code [here](https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/solution_one_file_retrieval.js). If you are interested in Solution 2 instead, you can jump [here](#solution-2-converting-the-file-to-text-in-the-azure-function-1). 

### Code Walkthrough

The below walks through the different parts of the function. Before you begin, ensure you have the required packages installed and environment variables set up (see the Installation Steps section).


#### Implementing the Authentication 

Below we have a few helper functions that we’ll use in the function.


##### Initializing the Microsoft Graph Client

Create a function to initialize the Graph client with an access token. This will be used to search through Office 365 and SharePoint.

```javascript
const { Client } = require('@microsoft/microsoft-graph-client');

function initGraphClient(accessToken) {
    return Client.init({
        authProvider: (done) => {
            done(null, accessToken);
        }
    });
}
```

##### Obtaining an On-Behalf-Of (OBO) Token

This function uses an existing bearer token to request an OBO token from Microsoft's identity platform. This enables passing through the credentials to ensure the search only returns files the logged-in user can access.

```javascript
const axios = require('axios');
const qs = require('querystring');

async function getOboToken(userAccessToken) {
    const { TENANT_ID, CLIENT_ID, MICROSOFT_PROVIDER_AUTHENTICATION_SECRET } = process.env;
    const params = {
        client_id: CLIENT_ID,
        client_secret: MICROSOFT\_PROVIDER\_AUTHENTICATION\_SECRET,
        grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        assertion: userAccessToken,
        requested_token_use: 'on_behalf_of',
        scope: 'https://graph.microsoft.com/.default'
    };

    const url = `https\://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token`;
    try {
        const response = await axios.post(url, qs.stringify(params), {
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
        });
        return response.data.access\_token;
    } catch (error) {
        console.error('Error obtaining OBO token:', error.response?.data || error.message);
        throw error;
    }
}
```

#### Retrieving Content from O365 / SharePoint Items

This function fetches the content of drive items, converts it to a base64 string, and restructures to match the `openaiFileResponse` format.


key: _node_content
string_value:
{"id_": "bdb462d1-29f6-4f70-b3d0-067b723966d7", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0b2dbbc2-c0a2-4960-9490-defd15e48c56", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "498acf78e9b003aa4d3215a59649de7bc0e277df73adff40a804bf31b2f2ad51", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "26588861-5a5e-487c-a62a-93e6a409afc4", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "bc2c3fa8a9c76493886f04b23e63ec4117c209c0fbd2184bedcd6e29f9c06650", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "745bb3d6-03ab-45e4-81ea-4977518c891a", "node_type": "1", "metadata": {}, "hash": "7db814d396dcc8e6ee55df676c6bb3bd6db91b0492137bfde266e988f624d8bb", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 16816, "end_char_idx": 20015, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
This enables passing through the credentials to ensure the search only returns files the logged-in user can access.

```javascript
const axios = require('axios');
const qs = require('querystring');

async function getOboToken(userAccessToken) {
    const { TENANT_ID, CLIENT_ID, MICROSOFT_PROVIDER_AUTHENTICATION_SECRET } = process.env;
    const params = {
        client_id: CLIENT_ID,
        client_secret: MICROSOFT\_PROVIDER\_AUTHENTICATION\_SECRET,
        grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        assertion: userAccessToken,
        requested_token_use: 'on_behalf_of',
        scope: 'https://graph.microsoft.com/.default'
    };

    const url = `https\://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token`;
    try {
        const response = await axios.post(url, qs.stringify(params), {
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
        });
        return response.data.access\_token;
    } catch (error) {
        console.error('Error obtaining OBO token:', error.response?.data || error.message);
        throw error;
    }
}
```

#### Retrieving Content from O365 / SharePoint Items

This function fetches the content of drive items, converts it to a base64 string, and restructures to match the `openaiFileResponse` format.
```javascript
const getDriveItemContent = async (client, driveId, itemId, name) => {
   try
       const filePath = `/drives/${driveId}/items/${itemId}`;
       const downloadPath = filePath + `/content`
       // this is where we get the contents and convert to base64
       const fileStream = await client.api(downloadPath).getStream();
       let chunks = [];
           for await (let chunk of fileStream) {
               chunks.push(chunk);
           }
       const base64String = Buffer.concat(chunks).toString('base64');
       // this is where we get the other metadata to include in response
       const file = await client.api(filePath).get();
       const mime_type = file.file.mimeType;
       const name = file.name;
       return {"name":name, "mime_type":mime_type, "content":base64String}
   } catch (error) {
       console.error('Error fetching drive content:', error);
       throw new Error(`Failed to fetch content for ${name}: ${error.message}`);
   }
```

#### Creating the Azure Function to Handle Requests

Now that we have all these helper functions, the Azure Function will orchestrate the flow, by authenticating the user, performing the search, and iterating through the search results to extract the text and retrieve the relevant parts of the text to the GPT.

**Handling HTTP Requests:** The function starts by extracting the query and searchTerm from the HTTP request. It checks if the Authorization header is present and extracts the bearer token.

**Authentication:** Using the bearer token, it obtains an OBO token from Microsoft's identity platform using getOboToken defined above.

**Initializing the Graph Client:** With the OBO token, it initializes the Microsoft Graph client using initGraphClient defined above.

**Document Search:** It constructs a search query and sends it to the Microsoft Graph API to find documents based on the searchTerm.


key: _node_content
string_value:
{"id_": "745bb3d6-03ab-45e4-81ea-4977518c891a", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0b2dbbc2-c0a2-4960-9490-defd15e48c56", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "498acf78e9b003aa4d3215a59649de7bc0e277df73adff40a804bf31b2f2ad51", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "bdb462d1-29f6-4f70-b3d0-067b723966d7", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "58ff50ce2c4e6743de105d9c26dc2fa95d88dd8a58a609ad3fddd2353114720e", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "5e4f45eb-cd07-4795-b239-fa85deb759a7", "node_type": "1", "metadata": {}, "hash": "ef6c3b8a6b4665a0d472a2b7133e174a5ed33000c03d484aa182a46bbb94ae8d", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 19420, "end_char_idx": 21757, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
**Handling HTTP Requests:** The function starts by extracting the query and searchTerm from the HTTP request. It checks if the Authorization header is present and extracts the bearer token.

**Authentication:** Using the bearer token, it obtains an OBO token from Microsoft's identity platform using getOboToken defined above.

**Initializing the Graph Client:** With the OBO token, it initializes the Microsoft Graph client using initGraphClient defined above.

**Document Search:** It constructs a search query and sends it to the Microsoft Graph API to find documents based on the searchTerm.

**Document Processing**: For each document returned by the search:

- It retrieves the document content using getDriveItemContent.

- It converts the document to base64 string and restructures it to match the `openaiFileResponse` structure.

**Response**: The function sends them back in the HTTP response.
```javascript
module.exports = async function (context, req) {
   // const query = req.query.query || (req.body && req.body.query);
   const searchTerm = req.query.searchTerm || (req.body && req.body.searchTerm);
   if (!req.headers.authorization) {
       context.res = {
           status: 400,
           body: 'Authorization header is missing'
       };
       return;
   }
   /// The below takes the token passed to the function, to use to get an OBO token.
   const bearerToken = req.headers.authorization.split(' ')[1];
   let accessToken;
   try {
       accessToken = await getOboToken(bearerToken);
   } catch (error) {
       context.res = {
           status: 500,
           body: `Failed to obtain OBO token: ${error.message}`
       };
       return;
   }
   // Initialize the Graph Client using the initGraphClient function defined above
   let client = initGraphClient(accessToken);
   // this is the search body to be used in the Microsft Graph Search API: https://learn.microsoft.com/en-us/graph/search-concept-files
   const requestBody = {
       requests: [
           {
               entityTypes: ['driveItem'],
               query: {
                   queryString: searchTerm
               },
               from: 0,
               // the below is set to summarize the top 10 search results from the Graph API, but can configure based on your documents.
               size: 10
           }
       ]
   };


key: _node_content
string_value:
{"id_": "5e4f45eb-cd07-4795-b239-fa85deb759a7", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0b2dbbc2-c0a2-4960-9490-defd15e48c56", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "498acf78e9b003aa4d3215a59649de7bc0e277df73adff40a804bf31b2f2ad51", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "745bb3d6-03ab-45e4-81ea-4977518c891a", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "17b172df732b6befce519c8c6012f70ac7711fd1a4c6d64b15bb19d6345e3a00", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "8d59b037-bd90-4bf1-aea2-c65029fe98d2", "node_type": "1", "metadata": {}, "hash": "0f067d4e1f335db5a83b7310db87dfc1010700e2f7c636c90907f9f21b6297ba", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 19883, "end_char_idx": 24905, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
**Document Search:** It constructs a search query and sends it to the Microsoft Graph API to find documents based on the searchTerm.

**Document Processing**: For each document returned by the search:

- It retrieves the document content using getDriveItemContent.

- It converts the document to base64 string and restructures it to match the `openaiFileResponse` structure.

**Response**: The function sends them back in the HTTP response.
```javascript
module.exports = async function (context, req) {
   // const query = req.query.query || (req.body && req.body.query);
   const searchTerm = req.query.searchTerm || (req.body && req.body.searchTerm);
   if (!req.headers.authorization) {
       context.res = {
           status: 400,
           body: 'Authorization header is missing'
       };
       return;
   }
   /// The below takes the token passed to the function, to use to get an OBO token.
   const bearerToken = req.headers.authorization.split(' ')[1];
   let accessToken;
   try {
       accessToken = await getOboToken(bearerToken);
   } catch (error) {
       context.res = {
           status: 500,
           body: `Failed to obtain OBO token: ${error.message}`
       };
       return;
   }
   // Initialize the Graph Client using the initGraphClient function defined above
   let client = initGraphClient(accessToken);
   // this is the search body to be used in the Microsft Graph Search API: https://learn.microsoft.com/en-us/graph/search-concept-files
   const requestBody = {
       requests: [
           {
               entityTypes: ['driveItem'],
               query: {
                   queryString: searchTerm
               },
               from: 0,
               // the below is set to summarize the top 10 search results from the Graph API, but can configure based on your documents.
               size: 10
           }
       ]
   };


   try {
       // This is where we are doing the search
       const list = await client.api('/search/query').post(requestBody);
       const processList = async () => {
           // This will go through and for each search response, grab the contents of the file and summarize with gpt-3.5-turbo
           const results = [];
           await Promise.all(list.value[0].hitsContainers.map(async (container) => {
               for (const hit of container.hits) {
                   if (hit.resource["@odata.type"] === "#microsoft.graph.driveItem") {
                       const { name, id } = hit.resource;
                       // The below is where the file lives
                       const driveId = hit.resource.parentReference.driveId;
                       // we use the helper function we defined above to get the contents, convert to base64, and restructure it
                       const contents = await getDriveItemContent(client, driveId, id, name);
                       results.push(contents)
               }
           }));
           return results;
       };
       let results;
       if (list.value[0].hitsContainers[0].total == 0) {
           // Return no results found to the API if the Microsoft Graph API returns no results
           results = 'No results found';
       } else {
           // If the Microsoft Graph API does return results, then run processList to iterate through.
           results = await processList();
           // this is where we structure the response so ChatGPT knows they are files
           results = {'openaiFileResponse': results}
       }
       context.res = {
           status: 200,
           body: results
       };
   } catch (error) {
       context.res = {
           status: 500,
           body: `Error performing search or processing results: ${error.message}`,
       };
   }
};
```
### Customizations

Below are some potential areas to customize. 

- You can customize the GPT prompt to search again a certain amount of times if nothing is found.

- You can customize the code to only search through specific SharePoint sites or O365 Drives by customizing the search query. This will help focus the search and improve the retrieval. The function as setup now looks through all files the logged-in user can access.

- You can update the code to only return certain types of files. For example, only return structured data / CSVs. 

- You can customize the amount of files it searches through within the call to Microsoft Graph. Note that you should only put a maximum of 10 files based on the documentation [here](https://platform.openai.com/docs/actions/getting-started). 

### Considerations

Note that all the same limitations of Actions apply here, with regards to returning 100K characters or less and the [45 second timeout](https://platform.openai.com/docs/actions/production/timeouts).

- Make sure you read the documentation here around [returning files](https://platform.openai.com/docs/actions/sending-files) and [file uploads](https://help.openai.com/en/articles/8555545-file-uploads-faq), as those limitations apply here.

### Sample GPT Instructions


key: _node_content
string_value:
{"id_": "8d59b037-bd90-4bf1-aea2-c65029fe98d2", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0b2dbbc2-c0a2-4960-9490-defd15e48c56", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "498acf78e9b003aa4d3215a59649de7bc0e277df73adff40a804bf31b2f2ad51", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "5e4f45eb-cd07-4795-b239-fa85deb759a7", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "ed51fd1f19ad54b90a5dce6c68872aaae075aba5328b11335ee3c8413b712a8d", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "3900f868-188a-4bbe-ae72-438e57db13ff", "node_type": "1", "metadata": {}, "hash": "3e313ed78309cf1f662323f8bad2f4af993c8e2888d637aa73e3c998248f2f1e", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 24908, "end_char_idx": 28638, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
```text
You are a Q&A helper that helps answer users questions. You have access to a documents repository through your API action. When a user asks a question, you pass in the "searchTerm" a single keyword or term you think you should use for the search.

****

Scenario 1: There are answers

If your action returns results, then you take the results from the action and try to answer the users question. 

****

Scenario 2: No results found

If the response you get from the action is "No results found", stop there and let the user know there were no results and that you are going to try a different search term, and explain why. You must always let the user know before conducting another search.

Example:

****

I found no results for "DEI". I am now going to try [insert term] because [insert explanation]

****

Then, try a different searchTerm that is similar to the one you tried before, with a single word. 

Try this three times. After the third time, then let the user know you did not find any relevant documents to answer the question, and to check SharePoint. 
Be sure to be explicit about what you are searching for at each step.

****

In either scenario, try to answer the user's question. If you cannot answer the user's question based on the knowledge you find, let the user know and ask them to go check the HR Docs in SharePoint. 
```
### Sample OpenAPI Spec
This expects a response that matches the file retrieval structure in our doc [here](https://platform.openai.com/docs/actions/sending-files) and passes in a `searchTerm` parameter to inform the search.
>Make sure to switch the function app name, function name and code based on link copied in screenshot [here](#part-3-set-up-test-function)

```yaml
openapi: 3.0.0
info:
  title: SharePoint Search API
  description: API for searching SharePoint documents.
  version: 1.0.0
servers:
  - url: https://{your_function_app_name}.azurewebsites.net/api
    description: SharePoint Search API server
paths:
  /{your_function_name}?code={enter your specific endpoint id here}:
    post:
      operationId: searchSharePoint
      summary: Searches SharePoint for documents matching a query and term.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                searchTerm:
                  type: string
                  description: A specific term to search for within the documents.
      responses:
        '200':
          description: A CSV file of query results encoded in base64.
          content:
            application/json:
              schema:
                type: object
                properties:
                  openaiFileResponseData:
                    type: array
                    items:
                      type: object
                      properties:
                        name:
                          type: string
                          description: The name of the file.
                        mime_type:
                          type: string
                          description: The MIME type of the file.
                        content:
                          type: string
                          format: byte
                          description: The base64 encoded contents of the file.
        '400':
          description: Bad request when the SQL query parameter is missing.
        '413':
          description: Payload too large if the response exceeds the size limit.
        '500':
          description: Server error when there are issues executing the query or encoding the results.
```

## Solution 2 Detailed Walkthrough: Converting the file to text in the Azure Function


key: _node_content
string_value:
{"id_": "3900f868-188a-4bbe-ae72-438e57db13ff", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0b2dbbc2-c0a2-4960-9490-defd15e48c56", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "498acf78e9b003aa4d3215a59649de7bc0e277df73adff40a804bf31b2f2ad51", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "8d59b037-bd90-4bf1-aea2-c65029fe98d2", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "7c2a558528fcb12b8053aba526d736857cefc603df247008da402a15e4a368e9", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "75834d1c-f1a2-425b-9d04-d01f3bc2a792", "node_type": "1", "metadata": {}, "hash": "c930271d7bfe294e892e48097475080244411c50ddecec5aedf3c7f907dd14fc", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 28641, "end_char_idx": 31076, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
The below walks through setup instructions and walkthrough unique to this solution of pre-processing the files and extracting summaries in the Azure Function. You can find the entire code [here](https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/solution_two_preprocessing.js).

### Code Walkthrough

#### Implementing the Authentication 

This solution follows the same authentication steps as solution 1 above - see [Initializing the Microsoft Graph Client](#initializing-the-microsoft-graph-client) and [Obtaining an On-Behalf-Of (OBO) Token](#obtaining-an-on-behalf-of-obo-token) sections.


#### Retrieving Content from O365 / SharePoint Items

This function fetches the content of drive items, handling different file types and converting files to PDF when necessary for text extraction. This uses the [download endpoint](https://learn.microsoft.com/en-us/graph/api/driveitem-get-content?view=graph-rest-1.0\&tabs=http) for PDFs and the [convert endpoint](https://learn.microsoft.com/en-us/graph/api/driveitem-get-content-format?view=graph-rest-1.0\&tabs=http) for other supported file types.
```javascript
const getDriveItemContent = async (client, driveId, itemId, name) => {
    try {
        const fileType = path.extname(name).toLowerCase();
        // the below files types are the ones that are able to be converted to PDF to extract the text. See https://learn.microsoft.com/en-us/graph/api/driveitem-get-content-format?view=graph-rest-1.0&tabs=http
        const allowedFileTypes = ['.pdf', '.doc', '.docx', '.odp', '.ods', '.odt', '.pot', '.potm', '.potx', '.pps', '.ppsx', '.ppsxm', '.ppt', '.pptm', '.pptx', '.rtf'];
        // filePath changes based on file type, adding ?format=pdf to convert non-pdf types to pdf for text extraction, so all files in allowedFileTypes above are converted to pdf
        const filePath = `/drives/${driveId}/items/${itemId}/content` + ((fileType === '.pdf' || fileType === '.txt' || fileType === '.csv') ? '' : '?format=pdf');
        if (allowedFileTypes.includes(fileType)) {
            response = await client.api(filePath).getStream();
            // The below takes the chunks in response and combines
            let chunks = [];
            for await (let chunk of response) {
                chunks.push(chunk);
            }
            let buffer = Buffer.concat(chunks);
            // the below extracts the text from the PDF.


key: _node_content
string_value:
{"id_": "75834d1c-f1a2-425b-9d04-d01f3bc2a792", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0b2dbbc2-c0a2-4960-9490-defd15e48c56", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "498acf78e9b003aa4d3215a59649de7bc0e277df73adff40a804bf31b2f2ad51", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "3900f868-188a-4bbe-ae72-438e57db13ff", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "00442e7ae1984e08b6b929fb718ef4e664df0b78bc1a5b9e07298967d2146a65", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "21b03cd1-70d4-42a5-8dcc-6235e5b1b04c", "node_type": "1", "metadata": {}, "hash": "70b3611d472007836ddaef22121ee5c2bb33e28aefd027a18e24ef014a537771", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 29479, "end_char_idx": 32708, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
This uses the [download endpoint](https://learn.microsoft.com/en-us/graph/api/driveitem-get-content?view=graph-rest-1.0\&tabs=http) for PDFs and the [convert endpoint](https://learn.microsoft.com/en-us/graph/api/driveitem-get-content-format?view=graph-rest-1.0\&tabs=http) for other supported file types.
```javascript
const getDriveItemContent = async (client, driveId, itemId, name) => {
    try {
        const fileType = path.extname(name).toLowerCase();
        // the below files types are the ones that are able to be converted to PDF to extract the text. See https://learn.microsoft.com/en-us/graph/api/driveitem-get-content-format?view=graph-rest-1.0&tabs=http
        const allowedFileTypes = ['.pdf', '.doc', '.docx', '.odp', '.ods', '.odt', '.pot', '.potm', '.potx', '.pps', '.ppsx', '.ppsxm', '.ppt', '.pptm', '.pptx', '.rtf'];
        // filePath changes based on file type, adding ?format=pdf to convert non-pdf types to pdf for text extraction, so all files in allowedFileTypes above are converted to pdf
        const filePath = `/drives/${driveId}/items/${itemId}/content` + ((fileType === '.pdf' || fileType === '.txt' || fileType === '.csv') ? '' : '?format=pdf');
        if (allowedFileTypes.includes(fileType)) {
            response = await client.api(filePath).getStream();
            // The below takes the chunks in response and combines
            let chunks = [];
            for await (let chunk of response) {
                chunks.push(chunk);
            }
            let buffer = Buffer.concat(chunks);
            // the below extracts the text from the PDF.
            const pdfContents = await pdfParse(buffer);
            return pdfContents.text;
        } else if (fileType === '.txt') {
            // If the type is txt, it does not need to create a stream and instead just grabs the content
            response = await client.api(filePath).get();
            return response;
        }  else if (fileType === '.csv') {
            response = await client.api(filePath).getStream();
            let chunks = [];
            for await (let chunk of response) {
                chunks.push(chunk);
            }
            let buffer = Buffer.concat(chunks);
            let dataString = buffer.toString('utf-8');
            return dataString
            
    } else {
        return 'Unsupported File Type';
    }
     
    } catch (error) {
        console.error('Error fetching drive content:', error);
        throw new Error(`Failed to fetch content for ${name}: ${error.message}`);
    }
};
```

#### Integrating GPT 3.5-Turbo for Text Analysis

This function utilizes the OpenAI SDK to analyze text extracted from documents and find relevant information based on a user query. This helps to ensure only relevant text to the user’s question is returned to the GPT. 

```javascript
const getRelevantParts = async (text, query) => {
    try {
        // We use your OpenAI key to initialize the OpenAI client
        const openAIKey = process.env["OPENAI_API_KEY"];
        const openai = new OpenAI({
            apiKey: openAIKey,
        });
        const response = await openai.chat.completions.create({
            // Using gpt-3.5-turbo due to speed to prevent timeouts.


key: _node_content
string_value:
{"id_": "21b03cd1-70d4-42a5-8dcc-6235e5b1b04c", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0b2dbbc2-c0a2-4960-9490-defd15e48c56", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "498acf78e9b003aa4d3215a59649de7bc0e277df73adff40a804bf31b2f2ad51", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "75834d1c-f1a2-425b-9d04-d01f3bc2a792", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "0e8d8db86319b82a4af9434dfef6fafc627e93ec011cfcd8e3c456546251c66b", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "829e43db-cda6-4f9b-8f02-dd6c03ea5ac6", "node_type": "1", "metadata": {}, "hash": "1d6b2c634281e3f181c15f8121224082806161ebb54580e3a5037c3e3aa42a9d", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 31089, "end_char_idx": 34540, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
const pdfContents = await pdfParse(buffer);
            return pdfContents.text;
        } else if (fileType === '.txt') {
            // If the type is txt, it does not need to create a stream and instead just grabs the content
            response = await client.api(filePath).get();
            return response;
        }  else if (fileType === '.csv') {
            response = await client.api(filePath).getStream();
            let chunks = [];
            for await (let chunk of response) {
                chunks.push(chunk);
            }
            let buffer = Buffer.concat(chunks);
            let dataString = buffer.toString('utf-8');
            return dataString
            
    } else {
        return 'Unsupported File Type';
    }
     
    } catch (error) {
        console.error('Error fetching drive content:', error);
        throw new Error(`Failed to fetch content for ${name}: ${error.message}`);
    }
};
```

#### Integrating GPT 3.5-Turbo for Text Analysis

This function utilizes the OpenAI SDK to analyze text extracted from documents and find relevant information based on a user query. This helps to ensure only relevant text to the user’s question is returned to the GPT. 

```javascript
const getRelevantParts = async (text, query) => {
    try {
        // We use your OpenAI key to initialize the OpenAI client
        const openAIKey = process.env["OPENAI_API_KEY"];
        const openai = new OpenAI({
            apiKey: openAIKey,
        });
        const response = await openai.chat.completions.create({
            // Using gpt-3.5-turbo due to speed to prevent timeouts. You can tweak this prompt as needed
            model: "gpt-3.5-turbo-0125",
            messages: [
                {"role": "system", "content": "You are a helpful assistant that finds relevant content in text based on a query. You only return the relevant sentences, and you return a maximum of 10 sentences"},
                {"role": "user", "content": `Based on this question: **"${query}"**, get the relevant parts from the following text:*****\n\n${text}*****. If you cannot answer the question based on the text, respond with 'No information provided'`}
            ],
            // using temperature of 0 since we want to just extract the relevant content
            temperature: 0,
            // using max_tokens of 1000, but you can customize this based on the number of documents you are searching. 
            max_tokens: 1000
        });
        return response.choices[0].message.content;
    } catch (error) {
        console.error('Error with OpenAI:', error);
        return 'Error processing text with OpenAI' + error;
    }
};
```

#### Creating the Azure Function to Handle Requests

Now that we have all these helper functions, the Azure Function will orchestrate the flow, by authenticating the user, performing the search, and iterating through the search results to extract the text and retrieve the relevant parts of the text to the GPT.

**Handling HTTP Requests:** The function starts by extracting the query and searchTerm from the HTTP request. It checks if the Authorization header is present and extracts the bearer token.

**Authentication:** Using the bearer token, it obtains an OBO token from Microsoft's identity platform using getOboToken defined above.

**Initializing the Graph Client:** With the OBO token, it initializes the Microsoft Graph client using initGraphClient defined above.


key: _node_content
string_value:
{"id_": "829e43db-cda6-4f9b-8f02-dd6c03ea5ac6", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0b2dbbc2-c0a2-4960-9490-defd15e48c56", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "498acf78e9b003aa4d3215a59649de7bc0e277df73adff40a804bf31b2f2ad51", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "21b03cd1-70d4-42a5-8dcc-6235e5b1b04c", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "4ca555a24d7ad152064d69df923f39364c9ed55f97abef31dedbf24f9b3305b9", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "90a5eb01-0c1d-465f-92e7-a1f40ddd98e5", "node_type": "1", "metadata": {}, "hash": "1ec5993c3d924ff612b5843853f3c98473f9d6e47dbeb66f96bc834a05f6074b", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 32709, "end_char_idx": 35645, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
You can tweak this prompt as needed
            model: "gpt-3.5-turbo-0125",
            messages: [
                {"role": "system", "content": "You are a helpful assistant that finds relevant content in text based on a query. You only return the relevant sentences, and you return a maximum of 10 sentences"},
                {"role": "user", "content": `Based on this question: **"${query}"**, get the relevant parts from the following text:*****\n\n${text}*****. If you cannot answer the question based on the text, respond with 'No information provided'`}
            ],
            // using temperature of 0 since we want to just extract the relevant content
            temperature: 0,
            // using max_tokens of 1000, but you can customize this based on the number of documents you are searching. 
            max_tokens: 1000
        });
        return response.choices[0].message.content;
    } catch (error) {
        console.error('Error with OpenAI:', error);
        return 'Error processing text with OpenAI' + error;
    }
};
```

#### Creating the Azure Function to Handle Requests

Now that we have all these helper functions, the Azure Function will orchestrate the flow, by authenticating the user, performing the search, and iterating through the search results to extract the text and retrieve the relevant parts of the text to the GPT.

**Handling HTTP Requests:** The function starts by extracting the query and searchTerm from the HTTP request. It checks if the Authorization header is present and extracts the bearer token.

**Authentication:** Using the bearer token, it obtains an OBO token from Microsoft's identity platform using getOboToken defined above.

**Initializing the Graph Client:** With the OBO token, it initializes the Microsoft Graph client using initGraphClient defined above.

**Document Search:** It constructs a search query and sends it to the Microsoft Graph API to find documents based on the searchTerm.

**Document Processing**: For each document returned by the search:

- It retrieves the document content using getDriveItemContent.

- If the file type is supported, it analyzes the content using getRelevantParts, which sends the text to OpenAI's model for extracting relevant information based on the query.

- It collects the analysis results and includes metadata like the document name and URL.

**Response**: The function sorts the results by relevance and sends them back in the HTTP response.

```javascript
module.exports = async function (context, req) {
    const query = req.query.query || (req.body && req.body.query);
    const searchTerm = req.query.searchTerm || (req.body && req.body.searchTerm);
    if (!req.headers.authorization) {
        context.res = {
            status: 400,
            body: 'Authorization header is missing'
        };
        return;
    }
    /// The below takes the token passed to the function, to use to get an OBO token.


key: _node_content
string_value:
{"id_": "90a5eb01-0c1d-465f-92e7-a1f40ddd98e5", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0b2dbbc2-c0a2-4960-9490-defd15e48c56", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "498acf78e9b003aa4d3215a59649de7bc0e277df73adff40a804bf31b2f2ad51", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "829e43db-cda6-4f9b-8f02-dd6c03ea5ac6", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "8690243ac675bdf29c49331db87b21e8255871c2d0d11766f6c757df886198f7", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "b14138ce-6c6f-42a3-bd65-8750baa02f48", "node_type": "1", "metadata": {}, "hash": "24bec4220dea7df050b359bc68d7a764141a262a901653298336f35bcad8be06", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 34079, "end_char_idx": 36720, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
**Handling HTTP Requests:** The function starts by extracting the query and searchTerm from the HTTP request. It checks if the Authorization header is present and extracts the bearer token.

**Authentication:** Using the bearer token, it obtains an OBO token from Microsoft's identity platform using getOboToken defined above.

**Initializing the Graph Client:** With the OBO token, it initializes the Microsoft Graph client using initGraphClient defined above.

**Document Search:** It constructs a search query and sends it to the Microsoft Graph API to find documents based on the searchTerm.

**Document Processing**: For each document returned by the search:

- It retrieves the document content using getDriveItemContent.

- If the file type is supported, it analyzes the content using getRelevantParts, which sends the text to OpenAI's model for extracting relevant information based on the query.

- It collects the analysis results and includes metadata like the document name and URL.

**Response**: The function sorts the results by relevance and sends them back in the HTTP response.

```javascript
module.exports = async function (context, req) {
    const query = req.query.query || (req.body && req.body.query);
    const searchTerm = req.query.searchTerm || (req.body && req.body.searchTerm);
    if (!req.headers.authorization) {
        context.res = {
            status: 400,
            body: 'Authorization header is missing'
        };
        return;
    }
    /// The below takes the token passed to the function, to use to get an OBO token.
    const bearerToken = req.headers.authorization.split(' ')[1];
    let accessToken;
    try {
        accessToken = await getOboToken(bearerToken);
    } catch (error) {
        context.res = {
            status: 500,
            body: `Failed to obtain OBO token: ${error.message}`
        };
        return;
    }
    // Initialize the Graph Client using the initGraphClient function defined above
    let client = initGraphClient(accessToken);
    // this is the search body to be used in the Microsft Graph Search API: https://learn.microsoft.com/en-us/graph/search-concept-files
    const requestBody = {
        requests: [
            {
                entityTypes: ['driveItem'],
                query: {
                    queryString: searchTerm
                },
                from: 0,
                // the below is set to summarize the top 10 search results from the Graph API, but can configure based on your documents. 
                size: 10
            }
        ]
    };

    try { 
        // Function to tokenize content (e.g., based on words).


key: _node_content
string_value:
{"id_": "b14138ce-6c6f-42a3-bd65-8750baa02f48", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0b2dbbc2-c0a2-4960-9490-defd15e48c56", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "498acf78e9b003aa4d3215a59649de7bc0e277df73adff40a804bf31b2f2ad51", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "90a5eb01-0c1d-465f-92e7-a1f40ddd98e5", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "3ca447bb54f47adbf0a3e2f3637e55c9b5b366f70bcf67fb980d91c8bf509b80", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "f1589eb5-969b-45f2-babe-41dc9ce6de16", "node_type": "1", "metadata": {}, "hash": "5cb7e353546898a1873ca8d7202e402f50833c9dc4bde2184e3aed9f4adc7ec8", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 34808, "end_char_idx": 40012, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
- If the file type is supported, it analyzes the content using getRelevantParts, which sends the text to OpenAI's model for extracting relevant information based on the query.

- It collects the analysis results and includes metadata like the document name and URL.

**Response**: The function sorts the results by relevance and sends them back in the HTTP response.

```javascript
module.exports = async function (context, req) {
    const query = req.query.query || (req.body && req.body.query);
    const searchTerm = req.query.searchTerm || (req.body && req.body.searchTerm);
    if (!req.headers.authorization) {
        context.res = {
            status: 400,
            body: 'Authorization header is missing'
        };
        return;
    }
    /// The below takes the token passed to the function, to use to get an OBO token.
    const bearerToken = req.headers.authorization.split(' ')[1];
    let accessToken;
    try {
        accessToken = await getOboToken(bearerToken);
    } catch (error) {
        context.res = {
            status: 500,
            body: `Failed to obtain OBO token: ${error.message}`
        };
        return;
    }
    // Initialize the Graph Client using the initGraphClient function defined above
    let client = initGraphClient(accessToken);
    // this is the search body to be used in the Microsft Graph Search API: https://learn.microsoft.com/en-us/graph/search-concept-files
    const requestBody = {
        requests: [
            {
                entityTypes: ['driveItem'],
                query: {
                    queryString: searchTerm
                },
                from: 0,
                // the below is set to summarize the top 10 search results from the Graph API, but can configure based on your documents. 
                size: 10
            }
        ]
    };

    try { 
        // Function to tokenize content (e.g., based on words). 
        const tokenizeContent = (content) => {
            return content.split(/\s+/);
        };

        // Function to break tokens into 10k token windows for gpt-3.5-turbo
        const breakIntoTokenWindows = (tokens) => {
            const tokenWindows = []
            const maxWindowTokens = 10000; // 10k tokens
            let startIndex = 0;

            while (startIndex < tokens.length) {
                const window = tokens.slice(startIndex, startIndex + maxWindowTokens);
                tokenWindows.push(window);
                startIndex += maxWindowTokens;
            }

            return tokenWindows;
        };
        // This is where we are doing the search
        const list = await client.api('/search/query').post(requestBody);

        const processList = async () => {
            // This will go through and for each search response, grab the contents of the file and summarize with gpt-3.5-turbo
            const results = [];

            await Promise.all(list.value[0].hitsContainers.map(async (container) => {
                for (const hit of container.hits) {
                    if (hit.resource["@odata.type"] === "#microsoft.graph.driveItem") {
                        const { name, id } = hit.resource;
                        // We use the below to grab the URL of the file to include in the response
                        const webUrl = hit.resource.webUrl.replace(/\s/g, "%20");
                        // The Microsoft Graph API ranks the reponses, so we use this to order it
                        const rank = hit.rank;
                        // The below is where the file lives
                        const driveId = hit.resource.parentReference.driveId;
                        const contents = await getDriveItemContent(client, driveId, id, name);
                        if (contents !== 'Unsupported File Type') {
                            // Tokenize content using function defined previously
                            const tokens = tokenizeContent(contents);

                            // Break tokens into 10k token windows
                            const tokenWindows = breakIntoTokenWindows(tokens);

                            // Process each token window and combine results
                            const relevantPartsPromises = tokenWindows.map(window => getRelevantParts(window.join(' '), query));
                            const relevantParts = await Promise.all(relevantPartsPromises);
                            const combinedResults = relevantParts.join('\n'); // Combine results

                            results.push({ name, webUrl, rank, contents: combinedResults });
                        } 
                        else {
                            results.push({ name, webUrl, rank, contents: 'Unsupported File Type' });
                        }
                    }
                }
            }));

            return results;
        };
        let results;
        if (list.value[0].hitsContainers[0].total == 0) {
            // Return no results found to the API if the Microsoft Graph API returns no results
            results = 'No results found';
        } else {
            // If the Microsoft Graph API does return results, then run processList to iterate through.


key: _node_content
string_value:
{"id_": "f1589eb5-969b-45f2-babe-41dc9ce6de16", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0b2dbbc2-c0a2-4960-9490-defd15e48c56", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "498acf78e9b003aa4d3215a59649de7bc0e277df73adff40a804bf31b2f2ad51", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "b14138ce-6c6f-42a3-bd65-8750baa02f48", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "e9e56d61102c21cf0602f8586227ea0bf55bcc613078750cf96a4f718696b6e6", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "00a988ca-811b-4d0b-8d0f-d95e247a5b87", "node_type": "1", "metadata": {}, "hash": "fa2758e04d5dbbf9b995263a270260bb5890be6d5e19ab8de300798e9a919c83", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 40025, "end_char_idx": 41624, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
results = await processList();
            results.sort((a, b) => a.rank - b.rank);
        }
        context.res = {
            status: 200,
            body: results
        };
    } catch (error) {
        context.res = {
            status: 500,
            body: `Error performing search or processing results: ${error.message}`,
        };
    }
};
```

### Customizations

Below are some potential areas to customize. 

- You can customize the GPT prompt to search again a certain amount of times if nothing is found.

- You can customize the code to only search through specific SharePoint sites or O365 Drives by customizing the search query. This will help focus the search and improve the retrieval. The function as setup now looks through all files the logged-in user can access.

- You could use gpt-4o instead of gpt-3.5 turbo for longer context. This would slightly increase the cost and latency, but you may get higher quality summarizations.

- You can customize the amount of files it searches through within the call to Microsoft Graph.


### Considerations

Note that all the same limitations of Actions apply here, with regards to returning 100K characters or less and the [45 second timeout](https://platform.openai.com/docs/actions/production/timeouts).


- This only works for text, not for images. With some additional code in the Azure Function, you could customize this by using GPT-4o to extract summarizations of images.

- This does not work for structured data. We recommend Solution 1 if structured data is a major part of your use case.

### Sample GPT Instructions


key: _node_content
string_value:
{"id_": "00a988ca-811b-4d0b-8d0f-d95e247a5b87", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0b2dbbc2-c0a2-4960-9490-defd15e48c56", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "498acf78e9b003aa4d3215a59649de7bc0e277df73adff40a804bf31b2f2ad51", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "f1589eb5-969b-45f2-babe-41dc9ce6de16", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "213b06ead5e52a1bddcfab7128da333c028f534b70528d01708fcf4ac6ded35e", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "9175f397-01f4-43fd-b438-b6468fbfc83a", "node_type": "1", "metadata": {}, "hash": "dfc699f9a3059359172b101f665f4cef13ab09f8bc1c1156bc67918c7630d3ea", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 40025, "end_char_idx": 45157, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
results = await processList();
            results.sort((a, b) => a.rank - b.rank);
        }
        context.res = {
            status: 200,
            body: results
        };
    } catch (error) {
        context.res = {
            status: 500,
            body: `Error performing search or processing results: ${error.message}`,
        };
    }
};
```

### Customizations

Below are some potential areas to customize. 

- You can customize the GPT prompt to search again a certain amount of times if nothing is found.

- You can customize the code to only search through specific SharePoint sites or O365 Drives by customizing the search query. This will help focus the search and improve the retrieval. The function as setup now looks through all files the logged-in user can access.

- You could use gpt-4o instead of gpt-3.5 turbo for longer context. This would slightly increase the cost and latency, but you may get higher quality summarizations.

- You can customize the amount of files it searches through within the call to Microsoft Graph.


### Considerations

Note that all the same limitations of Actions apply here, with regards to returning 100K characters or less and the [45 second timeout](https://platform.openai.com/docs/actions/production/timeouts).


- This only works for text, not for images. With some additional code in the Azure Function, you could customize this by using GPT-4o to extract summarizations of images.

- This does not work for structured data. We recommend Solution 1 if structured data is a major part of your use case.

### Sample GPT Instructions


```
You are a Q&A helper that helps answer users questions. You have access to a documents repository through your API action. When a user asks a question, you pass in that question exactly as stated to the "query" parameter, and for the "searchTerm" you use a single keyword or term you think you should use for the search.

****

Scenario 1: There are answers

If your action returns results, then you take the results from the action and summarize concisely with the webUrl returned from the action. You answer the users question to the best of your knowledge from the action

****

Scenario 2: No results found

If the response you get from the action is "No results found", stop there and let the user know there were no results and that you are going to try a different search term, and explain why. You must always let the user know before conducting another search.

Example:

****

I found no results for "DEI". I am now going to try [insert term] because [insert explanation]

****

Then, try a different searchTerm that is similar to the one you tried before, with a single word. 

Try this three times. After the third time, then let the user know you did not find any relevant documents to answer the question, and to check SharePoint. Be sure to be explicit about what you are searching for at each step.

****

In either scenario, try to answer the user's question. If you cannot answer the user's question based on the knowledge you find, let the user know and ask them to go check the HR Docs in SharePoint. If the file is a CSV, XLSX, or XLS, you can tell the user to download the file using the link and re-upload to use Advanced Data Analysis.
```

### Sample OpenAPI Spec
The below spec passes in the `query` parameter to inform the pre-processing and a `searchTerm` to find the right files in Microsoft Graph.
>Make sure to switch the function app name, function name and code based on link copied in screenshot [here](#part-3-set-up-test-function)

```yaml
openapi: 3.0.0
info:
  title: SharePoint Search API
  description: API for searching SharePoint documents.
  version: 1.0.0
servers:
  - url: https://{your_function_app_name}.azurewebsites.net/api
    description: SharePoint Search API server
paths:
  /{your_function_name}?code={enter your specific endpoint id here}:
    post:
      operationId: searchSharePoint
      summary: Searches SharePoint for documents matching a query and term.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                query:
                  type: string
                  description: The full query to search for in SharePoint documents.
                searchTerm:
                  type: string
                  description: A specific term to search for within the documents.
      responses:
        '200':
          description: Search results
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    documentName:
                      type: string
                      description: The name of the document.
                    snippet:
                      type: string
                      description: A snippet from the document containing the search term.
                    url:
                      type: string
                      description: The URL to access the document.
```


key: _node_content
string_value:
{"id_": "9175f397-01f4-43fd-b438-b6468fbfc83a", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0b2dbbc2-c0a2-4960-9490-defd15e48c56", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "498acf78e9b003aa4d3215a59649de7bc0e277df73adff40a804bf31b2f2ad51", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "00a988ca-811b-4d0b-8d0f-d95e247a5b87", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/Using_Azure_Functions_and_Microsoft_Graph_to_Query_SharePoint.ipynb", "title": "Using Azure Functions with OAuth and Microsoft Graph API to Search O365 / SharePoint from ChatGPT", "tokens": 10420, "retrieve_doc": false, "source": "OpenAI_Cookbooks"}, "hash": "55e4ad92ad5b9f9b127221e8679fa28a0efb9c6b5db27daf5a4aabb772f89ea2", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 45160, "end_char_idx": 47618, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
## FAQ

- Why are you using the Microsoft Graph API in your code instead of the [SharePoint API](https://learn.microsoft.com/en-us/sharepoint/dev/sp-add-ins/get-to-know-the-sharepoint-rest-service?tabs=csom)?

  - The SharePoint API is legacy - per the Microsoft documentation [here](https://learn.microsoft.com/en-us/sharepoint/dev/apis/sharepoint-rest-graph), “For SharePoint Online, innovation using a REST API against SharePoint is driven via the Microsoft Graph REST API's.” The Graph API gives us more flexibility, and the SharePoint API still runs into the same file issues listed in the [Why is this necessary instead of interacting with the Microsoft Graph API directly?](#why-is-this-necessary-instead-of-interacting-with-the-microsoft-api-directly) section.

- What types of files does this support?

  - _Solution 1:_ 

    1. It follows the same guidelines as the documentation [here](https://help.openai.com/en/articles/8555545-file-uploads-faq) about file uploads. 

  - _Solution 2:_ 

    1. This supports all files listed in the documentation for the Convert File endpoint [_here_](https://learn.microsoft.com/en-us/graph/api/driveitem-get-content-format?view=graph-rest-1.0\&tabs=http). Specifically, it supports _pdf, doc, docx, odp, ods, odt, pot, potm, potx, pps, ppsx, ppsxm, ppt, pptm, pptx, rtf_.

    2. When a search result returns XLS, XLSX, or CSV, this prompts the user to download the file and re-upload to ask questions using Advanced Data Analysis. As stated above, we recommend solution 1 if structured data is part of your use case.

- Why do I need to request an OBO token?

  - When you try to use the same token to authenticate to the Graph API as the one you use to authenticate into the Azure Function, you get an “invalid audience” token. This is because the audience for the token can only be user\_impersonation.

  - To address this, the function requests a new token scoped to Files.Read.All within the app using the [On Behalf Of flow](https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-on-behalf-of-flow). This will inherit the permissions of the logged in user, meaning this function will only search through files the logged-in user has access to. 

  - We are purposefully requesting a new On Behalf Of token with each request, because Azure Function Apps are meant to be stateless. You could potentially integrate this with Azure Key Vault to store the secret and retrieve programmatically.


key: url
string_value:
https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb


key: _node_content
string_value:
{"id_": "c7dc8ae8-cd57-4cae-b1f1-2526a8dcaef1", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "38220194-2201-43c3-9ed3-8278d7daed33", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "fe98cd1c8d9c39256f24f70ee453bf76dbf9e49d7fa4cd35abd7925d0348d871", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "c466607b-291c-49ee-89de-a43115134272", "node_type": "1", "metadata": {}, "hash": "0af3ef85d43c953425c5f88c0d904093ac9e66e7d87b09e19958a36d9f38552f", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 0, "end_char_idx": 3604, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
# GPT Action Library: Outlook

## Introduction

This page provides an instruction & guide for developers building a GPT Action for a specific application. Before you proceed, make sure to first familiarize yourself with the following information: 
- [Introduction to GPT Actions](https://platform.openai.com/docs/actions)
- [Introduction to GPT Actions Library](https://platform.openai.com/docs/actions/actions-library)
- [Example of Buliding a GPT Action from Scratch](https://platform.openai.com/docs/actions/getting-started)

This particular GPT Action provides an overview of how to connect to Outlook, Microsoft's web service for emailing and calendar events. This action assumes a user’s context and allows them to send and retrieve emails and calendar events from Outlook.

### Value + Example Business Use Cases

**Value**: Users can now leverage ChatGPT's natural language capability to connect directly to Outlook

**Example Use Cases**: 
- A user can look up all of their meetings for the day and have ChatGPT summarize the day
- A user can email a ChatGPT output to someone directly

## Application Information

### Application Key Links

Check out these links from the application before you get started:
- Application Website: https://portal.azure.com/
- Application API Documentation: https://learn.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0

### Application Prerequisites

Before you get started, make sure you go through the following steps in your application environment:
- Ensure you have the access and permissions to [Set up an App Registration in Azure](https://portal.azure.com/?feature.tokencaching=true&feature.internalgraphapiversion=true#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade)

## ChatGPT Steps

### Custom GPT Instructions 

Once you've created a Custom GPT, copy the text below in the Instructions panel. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.


```python
**Context**: you are specialized GPT designed to manage emails and calendar events through API connections to Microsoft Outlook. This GPT can create, read, send, and alter emails and calendar events based on user instructions. It ensures efficient handling of communication and scheduling needs by leveraging Microsoft Graph API for seamless integration with Outlook services.

**Instructions**:
- When asked to perform a task, use the available actions via the microsoft.graph.com API.
- You should behave professionally and provide clear, concise responses.
- Offer assistance with tasks such as drafting emails, scheduling meetings, organising calendar events, and retrieving email or event details.
- Ask for clarification when needed to ensure accuracy and completeness in fulfilling user requests.
- Always conclude an email by signing off with logged in user's name which can be retrieved via the User.Read endpoint

```

### OpenAPI Schema 

Once you've created a Custom GPT, copy the text below in the Actions panel. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.


```python
openapi: 3.1.0
info:
  title: Microsoft Graph API Integration
  version: 1.0.0
servers:
  - url: https://graph.microsoft.com/v1.0
components:
  securitySchemes:
    OAuth2:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token
          scopes:
            https://graph.microsoft.com/User.


key: _node_content
string_value:
{"id_": "c466607b-291c-49ee-89de-a43115134272", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "38220194-2201-43c3-9ed3-8278d7daed33", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "fe98cd1c8d9c39256f24f70ee453bf76dbf9e49d7fa4cd35abd7925d0348d871", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "c7dc8ae8-cd57-4cae-b1f1-2526a8dcaef1", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "89a4bfa444a508ea82946b21737b7fc4bee89d129c22bb0e9b5b22a43ee999bb", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "5a3ccbc9-72bd-4d80-a027-57184a47b38b", "node_type": "1", "metadata": {}, "hash": "a88754e261410dfdeebf1e0d3c5bab16ee62cf4ebd2809a431b927b6145fdb5d", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 2024, "end_char_idx": 4534, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
```python
**Context**: you are specialized GPT designed to manage emails and calendar events through API connections to Microsoft Outlook. This GPT can create, read, send, and alter emails and calendar events based on user instructions. It ensures efficient handling of communication and scheduling needs by leveraging Microsoft Graph API for seamless integration with Outlook services.

**Instructions**:
- When asked to perform a task, use the available actions via the microsoft.graph.com API.
- You should behave professionally and provide clear, concise responses.
- Offer assistance with tasks such as drafting emails, scheduling meetings, organising calendar events, and retrieving email or event details.
- Ask for clarification when needed to ensure accuracy and completeness in fulfilling user requests.
- Always conclude an email by signing off with logged in user's name which can be retrieved via the User.Read endpoint

```

### OpenAPI Schema 

Once you've created a Custom GPT, copy the text below in the Actions panel. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.


```python
openapi: 3.1.0
info:
  title: Microsoft Graph API Integration
  version: 1.0.0
servers:
  - url: https://graph.microsoft.com/v1.0
components:
  securitySchemes:
    OAuth2:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token
          scopes:
            https://graph.microsoft.com/User.Read: Access current user profile
            https://graph.microsoft.com/Mail.Read: Read user mail
            https://graph.microsoft.com/Mail.Send: Send mail
            https://graph.microsoft.com/Calendars.ReadWrite: Read and write user calendars
  schemas:
    UserProfile:
      type: object
      properties:
        id:
          type: string
        displayName:
          type: string
        mail:
          type: string
    UserMessage:
      type: object
      properties:
        id:
          type: string
        subject:
          type: string
        bodyPreview:
          type: string
    CalendarEvent:
      type: object
      properties:
        id:
          type: string
        subject:
          type: string
        start:
          type: object
          properties:
            dateTime:
              type: string
            timeZone:
              type: string
        end:
          type: object


key: _node_content
string_value:
{"id_": "5a3ccbc9-72bd-4d80-a027-57184a47b38b", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "38220194-2201-43c3-9ed3-8278d7daed33", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "fe98cd1c8d9c39256f24f70ee453bf76dbf9e49d7fa4cd35abd7925d0348d871", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "c466607b-291c-49ee-89de-a43115134272", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "44f133971a40c6b49180e144746dfb1e109a6b471da7d96a324c092e48cc9056", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "3ca11d6e-8054-49ed-8efc-e6cf977fb26f", "node_type": "1", "metadata": {}, "hash": "11076a725ee66c26f8fb1cd5e98c84079fb48252ad9256a14bae6cfa5100f870", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 3730, "end_char_idx": 5166, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
microsoft.com/Mail.Send: Send mail
            https://graph.microsoft.com/Calendars.ReadWrite: Read and write user calendars
  schemas:
    UserProfile:
      type: object
      properties:
        id:
          type: string
        displayName:
          type: string
        mail:
          type: string
    UserMessage:
      type: object
      properties:
        id:
          type: string
        subject:
          type: string
        bodyPreview:
          type: string
    CalendarEvent:
      type: object
      properties:
        id:
          type: string
        subject:
          type: string
        start:
          type: object
          properties:
            dateTime:
              type: string
            timeZone:
              type: string
        end:
          type: object
          properties:
            dateTime:
              type: string
            timeZone:
              type: string
    NewEvent:
      type: object
      properties:
        subject:
          type: string
        start:
          type: object
          properties:
            dateTime:
              type: string
            timeZone:
              type: string
        end:
          type: object
          properties:
            dateTime:
              type: string
            timeZone:
              type: string
        attendees:
          type: array
          items:
            type: object
            properties:


key: _node_content
string_value:
{"id_": "3ca11d6e-8054-49ed-8efc-e6cf977fb26f", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "38220194-2201-43c3-9ed3-8278d7daed33", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "fe98cd1c8d9c39256f24f70ee453bf76dbf9e49d7fa4cd35abd7925d0348d871", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "5a3ccbc9-72bd-4d80-a027-57184a47b38b", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "f297b095b383db0c585c63c3b190f43c47dbddafda3984af7cd68a876f00f5d3", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "d965f7f1-bc36-46d1-beba-55be9486d91a", "node_type": "1", "metadata": {}, "hash": "ec5eb6d57c2e44b50b4d6a3d852f5e134fa50613aa06fc593afd63a222f4a273", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 4492, "end_char_idx": 5775, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
string
        end:
          type: object
          properties:
            dateTime:
              type: string
            timeZone:
              type: string
    NewEvent:
      type: object
      properties:
        subject:
          type: string
        start:
          type: object
          properties:
            dateTime:
              type: string
            timeZone:
              type: string
        end:
          type: object
          properties:
            dateTime:
              type: string
            timeZone:
              type: string
        attendees:
          type: array
          items:
            type: object
            properties:
              emailAddress:
                type: object
                properties:
                  address:
                    type: string
                  name:
                    type: string
    SendMailRequest:
      type: object
      properties:
        message:
          type: object
          properties:
            subject:
              type: string
            body:
              type: object
              properties:
                contentType:
                  type: string
                content:
                  type: string
            toRecipients:
              type: array


key: _node_content
string_value:
{"id_": "d965f7f1-bc36-46d1-beba-55be9486d91a", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "38220194-2201-43c3-9ed3-8278d7daed33", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "fe98cd1c8d9c39256f24f70ee453bf76dbf9e49d7fa4cd35abd7925d0348d871", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "3ca11d6e-8054-49ed-8efc-e6cf977fb26f", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "f4ab7efdd7c5c0af7633a92fef2c9cfb53b1c8ae8820dedf2bddbf50a02ecfb4", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "3e1dcda7-3e43-4cb3-b7ac-dc027946f659", "node_type": "1", "metadata": {}, "hash": "3bb256693656c9d0de236b7de5dfe1d3a85fd5a2b859e026c5e7b99414cb576b", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 5130, "end_char_idx": 6534, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
type: object
            properties:
              emailAddress:
                type: object
                properties:
                  address:
                    type: string
                  name:
                    type: string
    SendMailRequest:
      type: object
      properties:
        message:
          type: object
          properties:
            subject:
              type: string
            body:
              type: object
              properties:
                contentType:
                  type: string
                content:
                  type: string
            toRecipients:
              type: array
              items:
                type: object
                properties:
                  emailAddress:
                    type: object
                    properties:
                      address:
                        type: string
security:
  - OAuth2: []
paths:
  /me:
    get:
      operationId: getUserProfile
      summary: Get the authenticated user's profile
      security:
        - OAuth2: []
      responses:
        '200':
          description: A user profile
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserProfile'
  /me/messages:
    get:
      operationId: getUserMessages
      summary: Get the authenticated user's messages
      security:
        - OAuth2:


key: _node_content
string_value:
{"id_": "3e1dcda7-3e43-4cb3-b7ac-dc027946f659", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "38220194-2201-43c3-9ed3-8278d7daed33", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "fe98cd1c8d9c39256f24f70ee453bf76dbf9e49d7fa4cd35abd7925d0348d871", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "d965f7f1-bc36-46d1-beba-55be9486d91a", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "511451c4e5e77276c60241bc7850a48312809c408d0b37fb356e255a903218d7", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "f411e3f8-625c-4824-aa36-5fd3eba6ebeb", "node_type": "1", "metadata": {}, "hash": "10a9861089ebdfc4086d69071ae552f9975e509b588c12c6de4816f6a271e1e1", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 5736, "end_char_idx": 7245, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
toRecipients:
              type: array
              items:
                type: object
                properties:
                  emailAddress:
                    type: object
                    properties:
                      address:
                        type: string
security:
  - OAuth2: []
paths:
  /me:
    get:
      operationId: getUserProfile
      summary: Get the authenticated user's profile
      security:
        - OAuth2: []
      responses:
        '200':
          description: A user profile
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserProfile'
  /me/messages:
    get:
      operationId: getUserMessages
      summary: Get the authenticated user's messages
      security:
        - OAuth2: []
      parameters:
        - name: $top
          in: query
          required: false
          schema:
            type: integer
            default: 10
            description: Number of messages to return
        - name: $filter
          in: query
          required: false
          schema:
            type: string
            description: OData filter query to narrow results
        - name: $orderby
          in: query
          required: false
          schema:
            type: string
            description: OData order by query to sort results
      responses:
        '200':
          description: A list of user messages
          content:
            application/json:
              schema:


key: _node_content
string_value:
{"id_": "f411e3f8-625c-4824-aa36-5fd3eba6ebeb", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "38220194-2201-43c3-9ed3-8278d7daed33", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "fe98cd1c8d9c39256f24f70ee453bf76dbf9e49d7fa4cd35abd7925d0348d871", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "3e1dcda7-3e43-4cb3-b7ac-dc027946f659", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "906d29435c27d95bff7cef719bf6c168945f6ba3717483fbce97663bf82e0bf4", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "b7df6c73-7d86-4460-9174-d27c5649b9ff", "node_type": "1", "metadata": {}, "hash": "fea013b2de01f8239303e85fdb2ebdeeb803cde8818ac0da3908c6954e398624", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 6454, "end_char_idx": 8076, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
summary: Get the authenticated user's messages
      security:
        - OAuth2: []
      parameters:
        - name: $top
          in: query
          required: false
          schema:
            type: integer
            default: 10
            description: Number of messages to return
        - name: $filter
          in: query
          required: false
          schema:
            type: string
            description: OData filter query to narrow results
        - name: $orderby
          in: query
          required: false
          schema:
            type: string
            description: OData order by query to sort results
      responses:
        '200':
          description: A list of user messages
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/UserMessage'
  /me/sendMail:
    post:
      operationId: sendUserMail
      summary: Send an email as the authenticated user
      security:
        - OAuth2: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendMailRequest'
      responses:
        '202':
          description: Accepted
  /me/events:
    get:
      operationId: getUserCalendarEvents
      summary: Get the authenticated user's calendar events
      security:
        - OAuth2: []
      responses:
        '200':
          description: A list of calendar events
          content:
            application/json:
              schema:
                type: array


key: _node_content
string_value:
{"id_": "b7df6c73-7d86-4460-9174-d27c5649b9ff", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "38220194-2201-43c3-9ed3-8278d7daed33", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "fe98cd1c8d9c39256f24f70ee453bf76dbf9e49d7fa4cd35abd7925d0348d871", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "f411e3f8-625c-4824-aa36-5fd3eba6ebeb", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "206dcb974731246b2ad37b65b8914cdea505efa037f849cd02feb1f752c54b28", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "64444890-eec8-4c5c-bf25-099b09afa153", "node_type": "1", "metadata": {}, "hash": "fc68a576eff8fe3c447087dbb81d80f4c89ce63bf637114280983ce6aeca83eb", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 7238, "end_char_idx": 8930, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
schema:
                type: array
                items:
                  $ref: '#/components/schemas/UserMessage'
  /me/sendMail:
    post:
      operationId: sendUserMail
      summary: Send an email as the authenticated user
      security:
        - OAuth2: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendMailRequest'
      responses:
        '202':
          description: Accepted
  /me/events:
    get:
      operationId: getUserCalendarEvents
      summary: Get the authenticated user's calendar events
      security:
        - OAuth2: []
      responses:
        '200':
          description: A list of calendar events
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/CalendarEvent'
    post:
      operationId: createUserCalendarEvent
      summary: Create a new calendar event for the authenticated user
      security:
        - OAuth2: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewEvent'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CalendarEvent'


```

## Authentication Instructions

Below are instructions on setting up authentication with Outlook. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.


key: _node_content
string_value:
{"id_": "64444890-eec8-4c5c-bf25-099b09afa153", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "38220194-2201-43c3-9ed3-8278d7daed33", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "fe98cd1c8d9c39256f24f70ee453bf76dbf9e49d7fa4cd35abd7925d0348d871", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "b7df6c73-7d86-4460-9174-d27c5649b9ff", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "e6984d7be78749d57d1a98cafb2fb4f52973f579b8dd65dd929b4b7e7e78744c", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "86562368-92ab-41c9-ad80-0e6222f03f75", "node_type": "1", "metadata": {}, "hash": "b00cc2d556a1706e52baf0e1b65b0b906fbb0eda7faab1f339728938fb52b7d6", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 7939, "end_char_idx": 10608, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
description: A list of calendar events
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/CalendarEvent'
    post:
      operationId: createUserCalendarEvent
      summary: Create a new calendar event for the authenticated user
      security:
        - OAuth2: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewEvent'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CalendarEvent'


```

## Authentication Instructions

Below are instructions on setting up authentication with Outlook. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.


### Azure Steps

1. <b>App Registration</b>: The first step is to register a new App registration in the [Azure Portal](portal.azure.com) which will be used to integrate OAuth between our application and Azure Active Directory/Entra ID. Simply provide the application with a relevant name, leaving the Redirect URI blank for now as we will return to this, and save.

![gptactions_outlook_registerapplication.png](../../../images/gptactions_outlook_registerapplication.png)

2. <b>Certificate & Secrets</b>: We next need to generate a client secret to provide secure communication between the GPT and Azure. Within the App registration, navigate to <b>Certificate & secrets</b> in the sidebar

![gptactions_outlook_secrets.png](../../../images/gptactions_outlook_secrets.png)

Click New client secret and create a new client secret with desired name and expiry date. Clicking save will provide us a Secret to use in our GPT creation. Make sure to save the **Value** field as it’ll only be visible at creation, and we will need it later!

![gptactions_outlook_secretvalue.png](../../../images/gptactions_outlook_secretvalue.png)

3. <b>API Permissions</b>: The next step is to provide the integration with the scope it needs to perform our specific required actions. 
Within the App registration, navigate to <b>Manage > API permissions</b> in the sidebar. 

![gptactions_outlook_permissions.png](../../../images/gptactions_outlook_permissions.png)

Click <b>Add a permission</b> and <b>Microsoft graph > Delegated Permissions</b> as options in the opened side menu. Use the search bar to add the following permissions:
- Calendars.ReadWrite
- Mail.Read
- Mail.Send
- User.Read


key: _node_content
string_value:
{"id_": "86562368-92ab-41c9-ad80-0e6222f03f75", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "38220194-2201-43c3-9ed3-8278d7daed33", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "fe98cd1c8d9c39256f24f70ee453bf76dbf9e49d7fa4cd35abd7925d0348d871", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "64444890-eec8-4c5c-bf25-099b09afa153", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "44ba83b0cd3674e55d510d579edaea2e545cb12483907adfb8d32f023caa1e1e", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "7a5bd4e5-26f8-4bec-a356-657f05613411", "node_type": "1", "metadata": {}, "hash": "6f0574bd37adb7d85332c3f6b956dc41096de5c7cbda9aed30296218fa0c41eb", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 8933, "end_char_idx": 11981, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
### Azure Steps

1. <b>App Registration</b>: The first step is to register a new App registration in the [Azure Portal](portal.azure.com) which will be used to integrate OAuth between our application and Azure Active Directory/Entra ID. Simply provide the application with a relevant name, leaving the Redirect URI blank for now as we will return to this, and save.

![gptactions_outlook_registerapplication.png](../../../images/gptactions_outlook_registerapplication.png)

2. <b>Certificate & Secrets</b>: We next need to generate a client secret to provide secure communication between the GPT and Azure. Within the App registration, navigate to <b>Certificate & secrets</b> in the sidebar

![gptactions_outlook_secrets.png](../../../images/gptactions_outlook_secrets.png)

Click New client secret and create a new client secret with desired name and expiry date. Clicking save will provide us a Secret to use in our GPT creation. Make sure to save the **Value** field as it’ll only be visible at creation, and we will need it later!

![gptactions_outlook_secretvalue.png](../../../images/gptactions_outlook_secretvalue.png)

3. <b>API Permissions</b>: The next step is to provide the integration with the scope it needs to perform our specific required actions. 
Within the App registration, navigate to <b>Manage > API permissions</b> in the sidebar. 

![gptactions_outlook_permissions.png](../../../images/gptactions_outlook_permissions.png)

Click <b>Add a permission</b> and <b>Microsoft graph > Delegated Permissions</b> as options in the opened side menu. Use the search bar to add the following permissions:
- Calendars.ReadWrite
- Mail.Read
- Mail.Send
- User.Read


![gptactions_outlook_permissionadd.png](../../../images/gptactions_outlook_permissionadd.png)

### In ChatGPT

In ChatGPT, click on "Authentication" and choose **"OAuth"**. Enter in the information below. 

- **Client ID**: The value listed on the Azure Registered App’s Overview page under **Application (client) ID**
- **Client Secret**: the secret **Value** saved from step 2 of **Azure Steps**

For the following two inputs, replace <Tenant_ID> with the value listed on the Registered App’s Overview page under **Directory (tenant) ID**

- **Authorization URL**: https://login.microsoftonline.com/<Tenant_ID>/oauth2/v2.0/authorize
- **Token URL**: https://login.microsoftonline.com/<Tenant_ID>/oauth2/v2.0/token
- **Scope**: https://graph.microsoft.com/User.Read https://graph.microsoft.com/Mail.Send https://graph.microsoft.com/Mail.Read https://graph.microsoft.com/Calendars.ReadWrite 
- **Token Exchange Method**: Default (POST Request)

### Post-Action Steps

Once you've set up authentication in ChatGPT, follow the steps below in the application to finalize the Action. 

- Copy the callback URL from the GPT Action

![gptactions_outlook_callback.png](../../../images/gptactions_outlook_callback.png)

- In the Azure app, navigate to the **Manage > Authentication** tab, click **Add a platform**, select **Web** and add your callback URL under **Redirect URI**


key: _node_content
string_value:
{"id_": "7a5bd4e5-26f8-4bec-a356-657f05613411", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "38220194-2201-43c3-9ed3-8278d7daed33", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "fe98cd1c8d9c39256f24f70ee453bf76dbf9e49d7fa4cd35abd7925d0348d871", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "86562368-92ab-41c9-ad80-0e6222f03f75", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_outlook.ipynb", "title": "GPT Action Library: Outlook", "tokens": 2589, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "50b2c3298f6301db6067e05581e4788ce6daa731cafb2a16ff509742ded0f6a0", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 10611, "end_char_idx": 12549, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
![gptactions_outlook_permissionadd.png](../../../images/gptactions_outlook_permissionadd.png)

### In ChatGPT

In ChatGPT, click on "Authentication" and choose **"OAuth"**. Enter in the information below. 

- **Client ID**: The value listed on the Azure Registered App’s Overview page under **Application (client) ID**
- **Client Secret**: the secret **Value** saved from step 2 of **Azure Steps**

For the following two inputs, replace <Tenant_ID> with the value listed on the Registered App’s Overview page under **Directory (tenant) ID**

- **Authorization URL**: https://login.microsoftonline.com/<Tenant_ID>/oauth2/v2.0/authorize
- **Token URL**: https://login.microsoftonline.com/<Tenant_ID>/oauth2/v2.0/token
- **Scope**: https://graph.microsoft.com/User.Read https://graph.microsoft.com/Mail.Send https://graph.microsoft.com/Mail.Read https://graph.microsoft.com/Calendars.ReadWrite 
- **Token Exchange Method**: Default (POST Request)

### Post-Action Steps

Once you've set up authentication in ChatGPT, follow the steps below in the application to finalize the Action. 

- Copy the callback URL from the GPT Action

![gptactions_outlook_callback.png](../../../images/gptactions_outlook_callback.png)

- In the Azure app, navigate to the **Manage > Authentication** tab, click **Add a platform**, select **Web** and add your callback URL under **Redirect URI**


![gptactions_outlook_redirectconfig.png](../../../images/gptactions_outlook_redirectconfig.png)
![gptactions_outlook_redirectinput.png](../../../images/gptactions_outlook_redirectinput.png)

### FAQ & Troubleshooting

- **Callback URL Error**: If you get a callback URL error in ChatGPT, double check the Callback URL value as it can occasionally change depending on any alterations made to the authentication


*Are there integrations that you’d like us to prioritize? Are there errors in our integrations? File a PR or issue in our github, and we’ll take a look.*


key: url
string_value:
https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/.gpt_action_getting_started.ipynb


key: _node_content
string_value:
{"id_": "b366a319-d29a-4c07-87af-f3a53e0678c6", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/.gpt_action_getting_started.ipynb", "title": "GPT Action Library: Getting Started (Weather.gov)", "tokens": 1288, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "812a2cd8-8166-4d2c-b8ed-4bf2281ba1fc", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/.gpt_action_getting_started.ipynb", "title": "GPT Action Library: Getting Started (Weather.gov)", "tokens": 1288, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "352dad29d49ddf1eee32c43609743b075bd27d43798b9375efaa718333ed768d", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "53b3655e-2ba6-4a53-8113-30e259c57f3d", "node_type": "1", "metadata": {}, "hash": "ffe96ad6abf53e8a41fccf2319a8cb2f8d7ba72917a8db7976cf78034d1a7f66", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 0, "end_char_idx": 3049, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
# GPT Action Library: Getting Started (Weather.gov)

## Introduction

This page provides an instruction & guide for developers building a GPT Action for a specific application. Before you proceed, make sure to first familiarize yourself with the following information: 
- [Introduction to GPT Actions](https://platform.openai.com/docs/actions)
- [Introduction to GPT Actions Library](https://platform.openai.com/docs/actions/actions-library)
- [Example of Buliding a GPT Action from Scratch](https://platform.openai.com/docs/actions/getting-started)

This particular GPT Action provides an overview of how to connect to a **Weather.gov** weather forecast. This Action takes a user’s question about a location, converts the lat-long into a weather forecast office (WFO), x, and y coordinates, then converts those 3 values into a weather forecast.

Note: When setting up the GPT Action, for authentication, leave it with "None". This is a public API and does not require any Authentication

### Value + Example Business Use Cases

**Value**: Users can now leverage ChatGPT's natural language capability to forecast the weather

**Example Use Cases**: 
- Users can plan out their day based on weather patterns
- Users can quickly visualize (including graphs) what the weather is forecasted to look like

## Application Information

### Application Key Links

Check out these links from the application before you get started:
- Application Website: https://www.weather.gov/  
- Application API Documentation: https://www.weather.gov/documentation/services-web-api  

## ChatGPT Steps

### Custom GPT Instructions 

Once you've created a Custom GPT, copy the text below in the Instructions panel. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.


```python
**Context**: A user needs information related to a weather forecast of a specific location.

**Instructions**:
1. The user will provide a lat-long point or a general location or landmark (e.g. New York City, the White House). If the user does not provide one, ask for the relevant location
2. If the user provides a general location or landmark, convert that into a lat-long coordinate. If required, browse the web to look up the lat-long point. 
3. Run the "getPointData" API action and retrieve back the gridId, gridX, and gridY parameters.
4. Apply those variables as the office, gridX, and gridY variables in the "getGridpointForecast" API action to retrieve back a forecast
5. Use that forecast to answer the user's question 

**Additional Notes**: 
- Assume the user uses US weather units (e.g. Farenheit) unless otherwise specified
- If the user says "Let's get started" or "What do I do?", explain the purpose of this Custom GPT
```

### OpenAPI Schema 

Once you've created a Custom GPT, copy the text below in the Actions panel. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.


key: _node_content
string_value:
{"id_": "53b3655e-2ba6-4a53-8113-30e259c57f3d", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/.gpt_action_getting_started.ipynb", "title": "GPT Action Library: Getting Started (Weather.gov)", "tokens": 1288, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "812a2cd8-8166-4d2c-b8ed-4bf2281ba1fc", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/.gpt_action_getting_started.ipynb", "title": "GPT Action Library: Getting Started (Weather.gov)", "tokens": 1288, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "352dad29d49ddf1eee32c43609743b075bd27d43798b9375efaa718333ed768d", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "b366a319-d29a-4c07-87af-f3a53e0678c6", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/.gpt_action_getting_started.ipynb", "title": "GPT Action Library: Getting Started (Weather.gov)", "tokens": 1288, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "80dbab90dc0292bae2aaed679b7c2efb81aecb6f895179130925b7bc5c02ff75", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 1848, "end_char_idx": 6839, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
```python
**Context**: A user needs information related to a weather forecast of a specific location.

**Instructions**:
1. The user will provide a lat-long point or a general location or landmark (e.g. New York City, the White House). If the user does not provide one, ask for the relevant location
2. If the user provides a general location or landmark, convert that into a lat-long coordinate. If required, browse the web to look up the lat-long point. 
3. Run the "getPointData" API action and retrieve back the gridId, gridX, and gridY parameters.
4. Apply those variables as the office, gridX, and gridY variables in the "getGridpointForecast" API action to retrieve back a forecast
5. Use that forecast to answer the user's question 

**Additional Notes**: 
- Assume the user uses US weather units (e.g. Farenheit) unless otherwise specified
- If the user says "Let's get started" or "What do I do?", explain the purpose of this Custom GPT
```

### OpenAPI Schema 

Once you've created a Custom GPT, copy the text below in the Actions panel. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.


```python
openapi: 3.1.0
info:
  title: NWS Weather API
  description: Access to weather data including forecasts, alerts, and observations.
  version: 1.0.0
servers:
  - url: https://api.weather.gov
    description: Main API Server
paths:
  /points/{latitude},{longitude}:
    get:
      operationId: getPointData
      summary: Get forecast grid endpoints for a specific location
      parameters:
        - name: latitude
          in: path
          required: true
          schema:
            type: number
            format: float
          description: Latitude of the point
        - name: longitude
          in: path
          required: true
          schema:
            type: number
            format: float
          description: Longitude of the point
      responses:
        '200':
          description: Successfully retrieved grid endpoints
          content:
            application/json:
              schema:
                type: object
                properties:
                  properties:
                    type: object
                    properties:
                      forecast:
                        type: string
                        format: uri
                      forecastHourly:
                        type: string
                        format: uri
                      forecastGridData:
                        type: string
                        format: uri

  /gridpoints/{office}/{gridX},{gridY}/forecast:
    get:
      operationId: getGridpointForecast
      summary: Get forecast for a given grid point
      parameters:
        - name: office
          in: path
          required: true
          schema:
            type: string
          description: Weather Forecast Office ID
        - name: gridX
          in: path
          required: true
          schema:
            type: integer
          description: X coordinate of the grid
        - name: gridY
          in: path
          required: true
          schema:
            type: integer
          description: Y coordinate of the grid
      responses:
        '200':
          description: Successfully retrieved gridpoint forecast
          content:
            application/json:
              schema:
                type: object
                properties:
                  properties:
                    type: object
                    properties:
                      periods:
                        type: array
                        items:
                          type: object
                          properties:
                            number:
                              type: integer
                            name:
                              type: string
                            startTime:
                              type: string
                              format: date-time
                            endTime:
                              type: string
                              format: date-time
                            temperature:
                              type: integer
                            temperatureUnit:
                              type: string
                            windSpeed:
                              type: string
                            windDirection:
                              type: string
                            icon:
                              type: string
                              format: uri
                            shortForecast:
                              type: string
                            detailedForecast:
                              type: string
```

### FAQ & Troubleshooting

*Are there integrations that you’d like us to prioritize? Are there errors in our integrations? File a PR or issue in our github, and we’ll take a look.*


key: url
string_value:
https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb


key: _node_content
string_value:
{"id_": "868fda13-4327-4321-9daf-5e5328b5b11a", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "764d98b9-913d-4c98-840e-d987bb388c95", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "d654ce14c642848975092d91c1d355f57762a4a45fc21b906014e7d2786da52a", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "013e2c93-cb81-4368-be01-a8ce5b068b57", "node_type": "1", "metadata": {}, "hash": "f84f3c05cd5b0eb2ecfc74a1c716eaff44d9149ef0bd403513917be8c0838d5b", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 0, "end_char_idx": 3510, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
# GPT Action Library: Jira

## Introduction

This page provides an instruction & guide for developers building a GPT Action for a specific application. Before you proceed, make sure to first familiarize yourself with the following information: 
- [Introduction to GPT Actions](https://platform.openai.com/docs/actions)
- [Introduction to GPT Actions Library](https://platform.openai.com/docs/actions/actions-library)
- [Example of Buliding a GPT Action from Scratch](https://platform.openai.com/docs/actions/getting-started)

This particular GPT Action provides an overview of how to connect to Jira, Atlassian's tool for project and ticket management. This action assumes a user’s context and allows them to read and write to issues in a given project.

### Value + Example Business Use Cases

**Value**: Users can now leverage ChatGPT's natural language capability to connect directly to Jira Cloud

**Example Use Cases**: 
- A user can load up recent issues for a particular project and use ChatGPT to provide solutions
- A user can create and alter issues and sub-tasks and assign to specific users by instructing ChatGPT

## Application Information

### Application Key Links

Check out these links from the application before you get started:
- Application Website: https://<YOUR_SUBDOMAIN>.atlassian.net/jira
- Application API Documentation: https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/
- Application OAuth 2.0 Documentation: https://developer.atlassian.com/cloud/jira/platform/oauth-2-3lo-apps/

### Application Prerequisites

Before you get started, make sure you go through the following steps in your application environment:
- Ensure you have the access and permissions to create an application in the [Atlassian Cloud Developer Console](https://developer.atlassian.com/console/myapps/)

## ChatGPT Steps

### Custom GPT Instructions 

Once you've created a Custom GPT, copy the text below in the Instructions panel. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.


```python
**Context**: you are specialized GPT designed to create and edit issues through API connections to Jira Cloud. This GPT can create, read, and edit project issues based on user instructions.

**Instructions**:
- When asked to perform a task, use the available actions via the api.atlassian.com API.
- When asked to create an issue, use the user's input to synthesize a summary and description and file the issue in JIRA.
- When asked to create a subtask, assume the project key and parent issue key of the currently discussed issue. Clarify with if this context is not available.
- When asked to assign an issue or task to the user, first use jql to query the current user's profile and use this account as the assignee. 
- Ask for clarification when needed to ensure accuracy and completeness in fulfilling user requests.
```

### OpenAPI Schema 

Once you've created a Custom GPT, copy the text below in the Actions panel. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.

*NOTE: Replace the placeholder <CLOUD_ID> in url with your cloud environment's unique ID. You can find this value by visiting https://<YOUR_SUBDOMAIN>.atlassian.net/_edge/tenant_info*



```python
openapi: 3.1.0
info:
  title: Jira API
  description: API for interacting with Jira issues and sub-tasks.
  version: 1.


key: _node_content
string_value:
{"id_": "013e2c93-cb81-4368-be01-a8ce5b068b57", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "764d98b9-913d-4c98-840e-d987bb388c95", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "d654ce14c642848975092d91c1d355f57762a4a45fc21b906014e7d2786da52a", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "868fda13-4327-4321-9daf-5e5328b5b11a", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "bc059567c07d4fa25d054c50fe25b32c6f68363d565a39b20e3c881fd5089d72", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "d0065da4-605c-42fe-bfe4-fceef7927f47", "node_type": "1", "metadata": {}, "hash": "256c0fe0720a7e19929186ad76f084edeceea8b002d3dcc20350735ef1c26d02", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 2106, "end_char_idx": 4516, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
```python
**Context**: you are specialized GPT designed to create and edit issues through API connections to Jira Cloud. This GPT can create, read, and edit project issues based on user instructions.

**Instructions**:
- When asked to perform a task, use the available actions via the api.atlassian.com API.
- When asked to create an issue, use the user's input to synthesize a summary and description and file the issue in JIRA.
- When asked to create a subtask, assume the project key and parent issue key of the currently discussed issue. Clarify with if this context is not available.
- When asked to assign an issue or task to the user, first use jql to query the current user's profile and use this account as the assignee. 
- Ask for clarification when needed to ensure accuracy and completeness in fulfilling user requests.
```

### OpenAPI Schema 

Once you've created a Custom GPT, copy the text below in the Actions panel. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.

*NOTE: Replace the placeholder <CLOUD_ID> in url with your cloud environment's unique ID. You can find this value by visiting https://<YOUR_SUBDOMAIN>.atlassian.net/_edge/tenant_info*



```python
openapi: 3.1.0
info:
  title: Jira API
  description: API for interacting with Jira issues and sub-tasks.
  version: 1.0.0
servers:
  - url: https://api.atlassian.com/ex/jira/<CLOUD_ID>/rest/api/3
    description: Jira Cloud API
components:
  securitySchemes:
    OAuth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://auth.atlassian.com/authorize
          tokenUrl: https://auth.atlassian.com/oauth/token
          scopes:
            read:jira-user: Read Jira user information
            read:jira-work: Read Jira work data
            write:jira-work: Write Jira work data
  schemas:
    Issue:
      type: object
      properties:
        id:
          type: string
        key:
          type: string
        fields:
          type: object
          properties:
            summary:
              type: string
            description:
              type: string
            issuetype:
              type: object
              properties:
                name:
                  type: string
paths:
  /search:
    get:
      operationId: getIssues
      summary: Retrieve


key: _node_content
string_value:
{"id_": "d0065da4-605c-42fe-bfe4-fceef7927f47", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "764d98b9-913d-4c98-840e-d987bb388c95", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "d654ce14c642848975092d91c1d355f57762a4a45fc21b906014e7d2786da52a", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "013e2c93-cb81-4368-be01-a8ce5b068b57", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "21660331881411140d3e2ccda25ab8c3959142ab2278e7d43e8b24fca6fffc64", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "da1f589c-8c89-4439-b63d-6f75181e5015", "node_type": "1", "metadata": {}, "hash": "e4bac58a24076e64abf75044742ab4426b1ea66aadd4621edfe5240dfe6d530f", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 3763, "end_char_idx": 5176, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
atlassian.com/authorize
          tokenUrl: https://auth.atlassian.com/oauth/token
          scopes:
            read:jira-user: Read Jira user information
            read:jira-work: Read Jira work data
            write:jira-work: Write Jira work data
  schemas:
    Issue:
      type: object
      properties:
        id:
          type: string
        key:
          type: string
        fields:
          type: object
          properties:
            summary:
              type: string
            description:
              type: string
            issuetype:
              type: object
              properties:
                name:
                  type: string
paths:
  /search:
    get:
      operationId: getIssues
      summary: Retrieve a list of issues
      parameters:
        - name: jql
          in: query
          required: false
          schema:
            type: string
        - name: startAt
          in: query
          required: false
          schema:
            type: integer
        - name: maxResults
          in: query
          required: false
          schema:
            type: integer
      responses:
        '200':
          description: A list of issues
          content:
            application/json:
              schema:
                type: object
                properties:
                  issues:
                    type: array
                    items:


key: _node_content
string_value:
{"id_": "da1f589c-8c89-4439-b63d-6f75181e5015", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "764d98b9-913d-4c98-840e-d987bb388c95", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "d654ce14c642848975092d91c1d355f57762a4a45fc21b906014e7d2786da52a", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "d0065da4-605c-42fe-bfe4-fceef7927f47", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "f5ed0042d58438e40009effe030589a8adc12272de75654bd6491563a0018e1d", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "eb0a2f1e-7bfa-4a6f-a45c-dde377b8fa46", "node_type": "1", "metadata": {}, "hash": "151871c3b9401bc6ec8a47576cba59004d3d5ffaa9e0e093091c5bebea8b4d9c", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 4499, "end_char_idx": 5793, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
summary: Retrieve a list of issues
      parameters:
        - name: jql
          in: query
          required: false
          schema:
            type: string
        - name: startAt
          in: query
          required: false
          schema:
            type: integer
        - name: maxResults
          in: query
          required: false
          schema:
            type: integer
      responses:
        '200':
          description: A list of issues
          content:
            application/json:
              schema:
                type: object
                properties:
                  issues:
                    type: array
                    items:
                      $ref: '#/components/schemas/Issue'
  /issue:
    post:
      operationId: createIssue
      summary: Create a new issue
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                fields:
                  type: object
                  properties:
                    project:
                      type: object
                      properties:
                        key:
                          type: string
                    summary:
                      type: string


key: _node_content
string_value:
{"id_": "eb0a2f1e-7bfa-4a6f-a45c-dde377b8fa46", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "764d98b9-913d-4c98-840e-d987bb388c95", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "d654ce14c642848975092d91c1d355f57762a4a45fc21b906014e7d2786da52a", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "da1f589c-8c89-4439-b63d-6f75181e5015", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "1fe43845a115e6d56b0b5426622e6effa23260c11ec14e3d539eb84252d2cb0c", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "4d03b672-e419-49da-b77b-879b3ed791ba", "node_type": "1", "metadata": {}, "hash": "313ded860fb0dcf4f66f2a3aa772a2ff59d59fe6f11032202f48b712ca3ca999", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 5170, "end_char_idx": 6471, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
items:
                      $ref: '#/components/schemas/Issue'
  /issue:
    post:
      operationId: createIssue
      summary: Create a new issue
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                fields:
                  type: object
                  properties:
                    project:
                      type: object
                      properties:
                        key:
                          type: string
                    summary:
                      type: string
                    description:
                      type: string
                    issuetype:
                      type: object
                      properties:
                        name:
                          type: string
      responses:
        '201':
          description: Issue created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Issue'
  /issue/{issueIdOrKey}:
    get:
      operationId: getIssue
      summary: Retrieve a specific issue
      parameters:
        - name: issueIdOrKey
          in: path
          required: true
          schema:
            type: string


key: _node_content
string_value:
{"id_": "4d03b672-e419-49da-b77b-879b3ed791ba", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "764d98b9-913d-4c98-840e-d987bb388c95", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "d654ce14c642848975092d91c1d355f57762a4a45fc21b906014e7d2786da52a", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "eb0a2f1e-7bfa-4a6f-a45c-dde377b8fa46", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "eb8987baf91e7627615e9928bbb8900c11b006d86aed2379884adc502866530d", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "bf471613-852d-404e-b84d-2c62c99d83bc", "node_type": "1", "metadata": {}, "hash": "360b6039faa8b4a030aa991a1a8229258f24978599a6837ef4d7ca7a403f66aa", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 5781, "end_char_idx": 7150, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
type: string
                    description:
                      type: string
                    issuetype:
                      type: object
                      properties:
                        name:
                          type: string
      responses:
        '201':
          description: Issue created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Issue'
  /issue/{issueIdOrKey}:
    get:
      operationId: getIssue
      summary: Retrieve a specific issue
      parameters:
        - name: issueIdOrKey
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Issue details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Issue'
    put:
      operationId: updateIssue
      summary: Update an existing issue
      parameters:
        - name: issueIdOrKey
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                fields:
                  type: object
                  properties:
                    summary:


key: _node_content
string_value:
{"id_": "bf471613-852d-404e-b84d-2c62c99d83bc", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "764d98b9-913d-4c98-840e-d987bb388c95", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "d654ce14c642848975092d91c1d355f57762a4a45fc21b906014e7d2786da52a", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "4d03b672-e419-49da-b77b-879b3ed791ba", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "f12e3bd275380c54669e453af8cb5afc4ddf662e26c764999b057b48d78e26af", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "f103db39-5ed8-4fa7-80fb-0b5a9dc9d4be", "node_type": "1", "metadata": {}, "hash": "bbbcb1dfc0d624c3bff8350d0d97d8183c07cd4d0f1137027058a8ed68269962", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 6414, "end_char_idx": 7787, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
required: true
          schema:
            type: string
      responses:
        '200':
          description: Issue details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Issue'
    put:
      operationId: updateIssue
      summary: Update an existing issue
      parameters:
        - name: issueIdOrKey
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                fields:
                  type: object
                  properties:
                    summary:
                      type: string
                    description:
                      type: string
                    issuetype:
                      type: object
                      properties:
                        name:
                          type: string
      responses:
        '204':
          description: Issue updated successfully
  /issue:
    post:
      operationId: createSubTask
      summary: Create a sub-task for an issue
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                fields:


key: _node_content
string_value:
{"id_": "f103db39-5ed8-4fa7-80fb-0b5a9dc9d4be", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "764d98b9-913d-4c98-840e-d987bb388c95", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "d654ce14c642848975092d91c1d355f57762a4a45fc21b906014e7d2786da52a", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "bf471613-852d-404e-b84d-2c62c99d83bc", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "10bf31967749c28b00f50a2295ed68c3b681851a320af97c940505db91b6e66d", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "d18b599c-ea4c-43bb-97c7-0f46fdb16da3", "node_type": "1", "metadata": {}, "hash": "ff12320ceaedadbb5ff405402bc7184654589653f8b1daf2a9daf65158a97b1c", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 7110, "end_char_idx": 8311, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
properties:
                    summary:
                      type: string
                    description:
                      type: string
                    issuetype:
                      type: object
                      properties:
                        name:
                          type: string
      responses:
        '204':
          description: Issue updated successfully
  /issue:
    post:
      operationId: createSubTask
      summary: Create a sub-task for an issue
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                fields:
                  type: object
                  properties:
                    project:
                      type: object
                      properties:
                        key:
                          type: string
                    parent:
                      type: object
                      properties:
                        key:
                          type: string
                    summary:
                      type: string
                    description:
                      type: string


key: _node_content
string_value:
{"id_": "d18b599c-ea4c-43bb-97c7-0f46fdb16da3", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "764d98b9-913d-4c98-840e-d987bb388c95", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "d654ce14c642848975092d91c1d355f57762a4a45fc21b906014e7d2786da52a", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "f103db39-5ed8-4fa7-80fb-0b5a9dc9d4be", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "7901ed157a966baddc814fec5d192fb8c933c9239cce99e76ef72e21b92f3ffd", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "a64ab75b-62b7-4cdd-b018-6a6be26804a0", "node_type": "1", "metadata": {}, "hash": "cd08dd486b477938ee05a45589584ffaa8255f7be0d4fd5d6ea7a44f5ae3e46f", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 7780, "end_char_idx": 9033, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
fields:
                  type: object
                  properties:
                    project:
                      type: object
                      properties:
                        key:
                          type: string
                    parent:
                      type: object
                      properties:
                        key:
                          type: string
                    summary:
                      type: string
                    description:
                      type: string
                    issuetype:
                      type: object
                      properties:
                        name:
                          type: string
      responses:
        '201':
          description: Sub-task created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Issue'
security:
  - OAuth2:
      - read:jira-user
      - read:jira-work
      - write:jira-work

```

## Authentication Instructions

Below are instructions on setting up authentication with Jira. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.


key: _node_content
string_value:
{"id_": "a64ab75b-62b7-4cdd-b018-6a6be26804a0", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "764d98b9-913d-4c98-840e-d987bb388c95", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "d654ce14c642848975092d91c1d355f57762a4a45fc21b906014e7d2786da52a", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "d18b599c-ea4c-43bb-97c7-0f46fdb16da3", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "a6f8ad6a4b5fe1511cef3477ad215b4d0bd4613e37a03652e0c4f2edf93c2802", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "c304c797-8f2f-448c-a068-df67d97ccd18", "node_type": "1", "metadata": {}, "hash": "1dd28ecbc0ce2746a665d2ea15ff876b21628481865fc9bbbd70992836597213", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 8200, "end_char_idx": 11733, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
summary:
                      type: string
                    description:
                      type: string
                    issuetype:
                      type: object
                      properties:
                        name:
                          type: string
      responses:
        '201':
          description: Sub-task created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Issue'
security:
  - OAuth2:
      - read:jira-user
      - read:jira-work
      - write:jira-work

```

## Authentication Instructions

Below are instructions on setting up authentication with Jira. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.


### Jira Steps

1. <b>Create an Application</b>: The first step is to create a new application in Jira for the integration with ChatGPT. This can be done by visiting the [Atlassian Developer Console](https://developer.atlassian.com/console/myapps/), Clicking **Create** and selecting **OAuth 2.0 Integration**. 

![gptactions_jira_devconsole.png](../../../images/gptactions_jira_devconsole.png)

From here, simply enter the name of your integration and click **Create**.

![gptactions_jira_newapplication.png](../../../images/gptactions_jira_newapplication.png)

2. <b>Define Permissions</b>: Next we need to provide the required permissions to our application. Within the new application, open the **Permissions** menu from the sidebar, locate **Jira API** and click **Add** and then **Configure**.

![gptactions_jira_permissions.png](../../../images/gptactions_jira_permissions.png)

Required permissions will vary depending on the intended functionality of the GPT. In this scenario we wish to read and write to Jira issues, so select the following scopes under **Jira platform REST API** by clicking **Edit Scopes**:

- read:jira-work
- write:jira-work
- read:jira-user

Once selected, click **Save**

![gptactions_jira_scopes.png](../../../images/gptactions_jira_scopes.png)

3. <b>Application Client ID/Secret</b>: The next step is to locate the **Client ID** and **Secret** for enabling secure authentication between ChatGPT and Jira. We can find these values by clicking on **Settings** in the sidebar and scrolling down to **Authentication Details**. 

      Keep this page open as we will require these values in the next stage of configuration!

![gptactions_jira_clientsecret.png](../../../images/gptactions_jira_clientsecret.png)

### In ChatGPT

In ChatGPT, click on "Authentication" and choose **"OAuth"**. Enter in the information below. 

- **Client ID**: The **Client ID** from **Step 3** of Jira Configuration
- **Client Secret**: The **Secret** from **Step 3** of Jira Configuration
- **Authorization URL**: https://auth.atlassian.com/authorize
- **Token URL**: https://auth.atlassian.com/oauth/token
- **Scope**: read:jira-work write:jira-work read:jira-user 
- **Token Exchange Method**: Default (POST Request)

### Post-Action Steps

Once you've set up authentication in ChatGPT, follow the steps below in the application to finalize the Action. 

- Copy the callback URL from the GPT Action

![gptactions_jira_redirect.png](../../../images/gptactions_jira_redirect.png)

- In your application in the Atlassian Developer Console, navigate to the **Authorization** sidebar tab, next to **OAuth 2.0 (3L0)** click **Configure**, and add your callback URL under **Callback URL**


key: _node_content
string_value:
{"id_": "c304c797-8f2f-448c-a068-df67d97ccd18", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "764d98b9-913d-4c98-840e-d987bb388c95", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "d654ce14c642848975092d91c1d355f57762a4a45fc21b906014e7d2786da52a", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "a64ab75b-62b7-4cdd-b018-6a6be26804a0", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_jira.ipynb", "title": "GPT Action Library: Jira", "tokens": 2501, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "8adcf641fc0c233202d8bb35fceab240aac98d4bc1c0676d84c27c71c5052067", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 11736, "end_char_idx": 12189, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
![gptactions_jira_callback.png](../../../images/gptactions_jira_callback.png)

### FAQ & Troubleshooting

- **Callback URL Error**: If you get a callback URL error in ChatGPT, double check the Callback URL value as it can occasionally change depending on any alterations made to the authentication


*Are there integrations that you’d like us to prioritize? Are there errors in our integrations? File a PR or issue in our github, and we’ll take a look.*


key: url
string_value:
https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_salesforce.ipynb


key: _node_content
string_value:
{"id_": "495099d0-0bc3-48bc-ba2a-b12132535a10", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_salesforce.ipynb", "title": "## Introduction", "tokens": 2113, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "b7510561-4c1c-4e55-ad52-10ef79588580", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_salesforce.ipynb", "title": "## Introduction", "tokens": 2113, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "c5f8df4110429f7bcd9c95cfd2a79de0e439195a53ba3c73ec55ff2875fdde55", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "ef3a7408-6973-4367-9d29-51ece63004ba", "node_type": "1", "metadata": {}, "hash": "6d727e0097d3179e46edfe001d231c68c7e28db448924f6912ee0cf5ddf62a3a", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 0, "end_char_idx": 3592, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
## Introduction

This page provides an instruction & guide for developers building a GPT Action for a specific application. Before you proceed, make sure to first familiarize yourself with the following information: 
- [Introduction to GPT Actions](https://platform.openai.com/docs/actions/introduction)
- [Introduction to GPT Actions Library](https://platform.openai.com/docs/actions/actions-library)
- [Example of Buliding a GPT Action from Scratch](https://platform.openai.com/docs/actions/getting-started)

This particular GPT Action provides an overview of how to connect to Salesforce, specifically, Salesforce Service Cloud. This schema detailed in this Action allows the user to pull case data and update cases directly from ChatGPT. The setup process to create Actions for other Salesforce Cloud solutions uses the same Connected App and authentication setup, but will require a different API schema.

### Value + Example Business Use Cases

**Value**: Users can now leverage ChatGPT's natural language capability to connect directly to Salesforce

**Example Use Cases**: 
- Reduce average response time to customers
- Reduce time to troubleshoot cases or issues
- Ensure more consistent brand voice in reponse to customers when combined with knowledge and instructions in the GPT

## Application Information

### Application Key Links

Check out these links from the application before you get started:
- [Create Lightning Apps in Salesforce](https://help.salesforce.com/s/articleView?id=sf.apps_lightning_create.htm&type=5)
- [OAuth Tokens and Scopes](https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_tokens_scopes.htm&type=5)
- [Salesforce API Docs](https://developer.salesforce.com/docs/apis)

### Application Prerequisites

Before you get started, make sure you go through the following steps in your application environment:
- Ensure you have permissions to create an App in Salesforce

## ChatGPT Steps

### Custom GPT Instructions

Once you've created a Custom GPT, copy the text below in the Instructions panel. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.
**Context**: Your purpose is to pull information from Service Cloud, and push updates to cases. A user is going to ask you a question and ask you to make updates.

**Instructions**:
1. When a user asks you to help them solve a case in Service Cloud, ask for the case number and pull the details for the case into the conversation using the getCaseDetailsFromNumber action.
2. If the user asks you to update the case details, use the action updateCaseStatus.

**Example**: 
User: Help me solve case 00001104 in Service Cloud.
### OpenAPI Schema 

Once you've created a Custom GPT, copy the text below in the Actions panel. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.


```python
openapi: 3.1.0
info:
  title: Salesforce Service Cloud Case Update API
  description: API for updating the status of Service Cloud tickets (cases) in Salesforce.
  version: 1.0.3
servers:
  - url: https://your_instance.my.salesforce.com
    description: Base URL for your Salesforce instance (replace 'your_instance' with your actual Salesforce domain)
paths:
  /services/data/v60.0/sobjects/Case/{CaseId}:
    patch:
      operationId: updateCaseStatus
      summary: Updates the status of a Service Cloud case
      description: Updates the status of a Service Cloud ticket based on the case ID number.


key: _node_content
string_value:
{"id_": "ef3a7408-6973-4367-9d29-51ece63004ba", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_salesforce.ipynb", "title": "## Introduction", "tokens": 2113, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "b7510561-4c1c-4e55-ad52-10ef79588580", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_salesforce.ipynb", "title": "## Introduction", "tokens": 2113, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "c5f8df4110429f7bcd9c95cfd2a79de0e439195a53ba3c73ec55ff2875fdde55", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "495099d0-0bc3-48bc-ba2a-b12132535a10", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_salesforce.ipynb", "title": "## Introduction", "tokens": 2113, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "5232c4bf173cc6ab8e3877c281c888859d009e9494da7b5bbe98d5a606e7b97a", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "4446dc33-6335-4ef9-b6e7-f6802bdc07e2", "node_type": "1", "metadata": {}, "hash": "fa11e092abecc66a7fbe08fb2c40f745e67f11002fceb4e33a2d0be6ca358195", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 2978, "end_char_idx": 6949, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
```python
openapi: 3.1.0
info:
  title: Salesforce Service Cloud Case Update API
  description: API for updating the status of Service Cloud tickets (cases) in Salesforce.
  version: 1.0.3
servers:
  - url: https://your_instance.my.salesforce.com
    description: Base URL for your Salesforce instance (replace 'your_instance' with your actual Salesforce domain)
paths:
  /services/data/v60.0/sobjects/Case/{CaseId}:
    patch:
      operationId: updateCaseStatus
      summary: Updates the status of a Service Cloud case
      description: Updates the status of a Service Cloud ticket based on the case ID number.
      parameters:
        - name: CaseId
          in: path
          required: true
          description: The ID of the case to update.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                Status:
                  type: string
                  description: The new status of the case.
      responses:
        '204':
          description: Successfully updated the case status
        '400':
          description: Bad request - invalid input or case ID not found
        '401':
          description: Unauthorized - authentication required
        '404':
          description: Not Found - case ID does not exist
    delete:
      operationId: deleteCase
      summary: Deletes a Service Cloud case
      description: Deletes a Service Cloud ticket based on the case ID number.
      parameters:
        - name: CaseId
          in: path
          required: true
          description: The ID of the case to delete.
          schema:
            type: string
      responses:
        '204':
          description: Successfully deleted the case
        '400':
          description: Bad request - invalid case ID
        '401':
          description: Unauthorized - authentication required
        '404':
          description: Not Found - case ID does not exist
  /services/data/v60.0/query:
    get:
      operationId: getCaseDetailsFromNumber
      summary: Retrieves case details using a case number
      description: Retrieves the details of a Service Cloud case associated with a given case number.
      parameters:
        - name: q
          in: query
          required: true
          description: SOQL query string to find the Case details based on Case Number.
          schema:
            type: string
            example: "SELECT Id, CaseNumber, Status, Subject, Description FROM Case WHERE CaseNumber = '123456'"
      responses:
        '200':
          description: Successfully retrieved the case details
          content:
            application/json:
              schema:
                type: object
                properties:
                  totalSize:
                    type: integer
                  done:
                    type: boolean
                  records:
                    type: array
                    items:
                      type: object
                      properties:
                        Id:
                          type: string
                        CaseNumber:
                          type: string
                        Status:
                          type: string
                        Subject:
                          type: string
                        Description:
                          type: string
        '400':
          description: Bad request - invalid query
        '401':
          description: Unauthorized - authentication required
        '404':
          description: Not Found - case number does not exist

```

## Authentication Instructions

Below are instructions on setting up authentication with this 3rd party application. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.


key: _node_content
string_value:
{"id_": "4446dc33-6335-4ef9-b6e7-f6802bdc07e2", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_salesforce.ipynb", "title": "## Introduction", "tokens": 2113, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "b7510561-4c1c-4e55-ad52-10ef79588580", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_salesforce.ipynb", "title": "## Introduction", "tokens": 2113, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "c5f8df4110429f7bcd9c95cfd2a79de0e439195a53ba3c73ec55ff2875fdde55", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "ef3a7408-6973-4367-9d29-51ece63004ba", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_salesforce.ipynb", "title": "## Introduction", "tokens": 2113, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "2f423b7586589a27d6a15a633eb28409910dbefcf6d8a2657ca1bd323a6e7cd5", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "77dd5587-05b4-4939-b5b7-226ec88f817e", "node_type": "1", "metadata": {}, "hash": "0c72ef83736e2424a8eaa8246e4a5c0698cf738d2d57c8a0562720f4def819f4", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 4690, "end_char_idx": 8279, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
schema:
            type: string
      responses:
        '204':
          description: Successfully deleted the case
        '400':
          description: Bad request - invalid case ID
        '401':
          description: Unauthorized - authentication required
        '404':
          description: Not Found - case ID does not exist
  /services/data/v60.0/query:
    get:
      operationId: getCaseDetailsFromNumber
      summary: Retrieves case details using a case number
      description: Retrieves the details of a Service Cloud case associated with a given case number.
      parameters:
        - name: q
          in: query
          required: true
          description: SOQL query string to find the Case details based on Case Number.
          schema:
            type: string
            example: "SELECT Id, CaseNumber, Status, Subject, Description FROM Case WHERE CaseNumber = '123456'"
      responses:
        '200':
          description: Successfully retrieved the case details
          content:
            application/json:
              schema:
                type: object
                properties:
                  totalSize:
                    type: integer
                  done:
                    type: boolean
                  records:
                    type: array
                    items:
                      type: object
                      properties:
                        Id:
                          type: string
                        CaseNumber:
                          type: string
                        Status:
                          type: string
                        Subject:
                          type: string
                        Description:
                          type: string
        '400':
          description: Bad request - invalid query
        '401':
          description: Unauthorized - authentication required
        '404':
          description: Not Found - case number does not exist

```

## Authentication Instructions

Below are instructions on setting up authentication with this 3rd party application. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.

### Pre-Action Steps

Before you set up authentication in ChatGPT, please take the following steps in the application.

Before you set up authentication in ChatGPT, please take the following steps in the application.
1. Navigate to Salesforce Setup

![gptactions_salesforce.png](../../../images/gpt_actions_salesforce_setup.png)

2. Search for “App Manager”


![gptactions_salesforce.png](../../../images//gpt_actions_salesforce_manager.png)

3. Click “New Connected App”
4. Enter a Connected App Name
5. Enter contact email (your email)
6. Check the box to enable OAuth settings
7. Insert a callback URL (use a placeholder like https://chat.openai.com/aip//oauth/callback for now, you’ll update this later when you create the Action in ChatGPT) 


![gptactions_salesforce.png](../../../images//gpt_actions_salesforce_oauth2.png)

8. Select “Selected OAuth Scopes” and grant the appropriate permissions. Scope these based on your internal security policies. 


![gptactions_salesforce.png](../../../images//gpt_actions_salesforce_scope.png)

9. Ensure the following boxes are checked:
- Enable Client Credentials Flow
- Enable Authorization Code and Credentials FLow
- Enable Token Exchange Flow
10. Ensure the following box is unchecked: 
- Require Proof Key for Code Exchange (PKCE) Extension for Supported Authorization Flows


key: _node_content
string_value:
{"id_": "77dd5587-05b4-4939-b5b7-226ec88f817e", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_salesforce.ipynb", "title": "## Introduction", "tokens": 2113, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "b7510561-4c1c-4e55-ad52-10ef79588580", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_salesforce.ipynb", "title": "## Introduction", "tokens": 2113, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "c5f8df4110429f7bcd9c95cfd2a79de0e439195a53ba3c73ec55ff2875fdde55", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "4446dc33-6335-4ef9-b6e7-f6802bdc07e2", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_salesforce.ipynb", "title": "## Introduction", "tokens": 2113, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "00851c8463f5783273be92fa437a41075dd8caf60cceee33eaa449a6bbfb3f49", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "ea9e6b0b-5c0b-4f3f-9125-35356f7636fe", "node_type": "1", "metadata": {}, "hash": "8d9f81bf49d278243ffde964c91b080c4f4505e6b675d2321daca8e46ebf0c2a", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 6797, "end_char_idx": 9697, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.

### Pre-Action Steps

Before you set up authentication in ChatGPT, please take the following steps in the application.

Before you set up authentication in ChatGPT, please take the following steps in the application.
1. Navigate to Salesforce Setup

![gptactions_salesforce.png](../../../images/gpt_actions_salesforce_setup.png)

2. Search for “App Manager”


![gptactions_salesforce.png](../../../images//gpt_actions_salesforce_manager.png)

3. Click “New Connected App”
4. Enter a Connected App Name
5. Enter contact email (your email)
6. Check the box to enable OAuth settings
7. Insert a callback URL (use a placeholder like https://chat.openai.com/aip//oauth/callback for now, you’ll update this later when you create the Action in ChatGPT) 


![gptactions_salesforce.png](../../../images//gpt_actions_salesforce_oauth2.png)

8. Select “Selected OAuth Scopes” and grant the appropriate permissions. Scope these based on your internal security policies. 


![gptactions_salesforce.png](../../../images//gpt_actions_salesforce_scope.png)

9. Ensure the following boxes are checked:
- Enable Client Credentials Flow
- Enable Authorization Code and Credentials FLow
- Enable Token Exchange Flow
10. Ensure the following box is unchecked: 
- Require Proof Key for Code Exchange (PKCE) Extension for Supported Authorization Flows


![gptactions_salesforce.png](../../../images/gpt_actions_salesforce_settings_condensed.png) 

11. Save your New Connected App
12. Under “Consumer Key and Secret” click “Manage Consumer Details”. Verify your access using the code emailed to your account, and then copy the key and secret. 
- Salesforce Consumer Key = ChatGPT Client ID
- Salesforce Consumer Secret = ChatGPT Client Secret


![gptactions_salesforce.png](../../../images//gpt_actions_salesforce_credentials.png)

13. Return to App page
14. Click “Manage” 
15. Click “Edit Policies”
16. Under OAuth Policies, check the “Enable Token Exchange Flow” box


![gptactions_salesforce.png](../../../images//gpt_actions_salesforce_token.png)

17. Click save!

### In ChatGPT

In ChatGPT, click on "Authentication" and choose **"OAuth"**. Enter in the information below. 

- **Client ID**: use Client ID from steps above 
- **Client Secret**: use Client Secret from steps above
- **Authorization URL**: https://[inserturlhere].my.salesforce.com/services/oauth2/authorize
- **Token URL**: https://[inserturlhere].my.salesforce.com/services/oauth2/token 
- **Scope**: full
- **Token**: Default (POST)

### Post-Action Steps


Once you've set up authentication in ChatGPT, follow the steps below in the application to finalize the Action. 

- Copy the callback URL from the GPT Action
- Navigate back to your Connected App in Salesforce, and add your callback URL.


key: _node_content
string_value:
{"id_": "ea9e6b0b-5c0b-4f3f-9125-35356f7636fe", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_salesforce.ipynb", "title": "## Introduction", "tokens": 2113, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "b7510561-4c1c-4e55-ad52-10ef79588580", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_salesforce.ipynb", "title": "## Introduction", "tokens": 2113, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "c5f8df4110429f7bcd9c95cfd2a79de0e439195a53ba3c73ec55ff2875fdde55", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "77dd5587-05b4-4939-b5b7-226ec88f817e", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_salesforce.ipynb", "title": "## Introduction", "tokens": 2113, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "a7bd5c1652c5fbc46385d1ed56975d5fe32b40a935130a32a3278eff997eaf43", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 8282, "end_char_idx": 10226, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
![gptactions_salesforce.png](../../../images/gpt_actions_salesforce_settings_condensed.png) 

11. Save your New Connected App
12. Under “Consumer Key and Secret” click “Manage Consumer Details”. Verify your access using the code emailed to your account, and then copy the key and secret. 
- Salesforce Consumer Key = ChatGPT Client ID
- Salesforce Consumer Secret = ChatGPT Client Secret


![gptactions_salesforce.png](../../../images//gpt_actions_salesforce_credentials.png)

13. Return to App page
14. Click “Manage” 
15. Click “Edit Policies”
16. Under OAuth Policies, check the “Enable Token Exchange Flow” box


![gptactions_salesforce.png](../../../images//gpt_actions_salesforce_token.png)

17. Click save!

### In ChatGPT

In ChatGPT, click on "Authentication" and choose **"OAuth"**. Enter in the information below. 

- **Client ID**: use Client ID from steps above 
- **Client Secret**: use Client Secret from steps above
- **Authorization URL**: https://[inserturlhere].my.salesforce.com/services/oauth2/authorize
- **Token URL**: https://[inserturlhere].my.salesforce.com/services/oauth2/token 
- **Scope**: full
- **Token**: Default (POST)

### Post-Action Steps


Once you've set up authentication in ChatGPT, follow the steps below in the application to finalize the Action. 

- Copy the callback URL from the GPT Action
- Navigate back to your Connected App in Salesforce, and add your callback URL.


### FAQ & Troubleshooting

- *Callback URL Error:*  If you get a callback URL error in ChatGPT, pay close attention to the screenshot above. You need to add the callback URL directly into Salesforce for the action to authenticate correctly
- *Internal Server Error:* Ensure all the correct boxes are checked and/or unchecked in the OAuth settings for your connected app. 

*Are there integrations that you’d like us to prioritize? Are there errors in our integrations? File a PR or issue in our github, and we’ll take a look.*


key: url
string_value:
https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_bigquery.ipynb


key: _node_content
string_value:
{"id_": "2abf5d74-d7ce-4adf-98b3-7cc50a92ca53", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_bigquery.ipynb", "title": "GPT Action Library: BigQuery", "tokens": 1723, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "1c38aa33-ddb1-4ce9-89f0-a0ba25bab653", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_bigquery.ipynb", "title": "GPT Action Library: BigQuery", "tokens": 1723, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "ff68a885876b3ddfc1cabb15fa4d20df5df858a0a0584fe922c25b8bdc3dc2b1", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "3386f703-e3de-41d8-b6d5-f0967a87e1d4", "node_type": "1", "metadata": {}, "hash": "57911b57e3a2a95bd3e2d1746922a69f772e67627146884ac697912cd7aaac92", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 0, "end_char_idx": 2280, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
# GPT Action Library: BigQuery

## Introduction

This page provides an instruction & guide for developers building a GPT Action for a specific application. Before you proceed, make sure to first familiarize yourself with the following information: 
- [Introduction to GPT Actions](https://platform.openai.com/docs/actions)
- [Introduction to GPT Actions Library](https://platform.openai.com/docs/actions/actions-library)
- [Example of Building a GPT Action from Scratch](https://platform.openai.com/docs/actions/getting-started)

This particular GPT Action provides an overview of how to connect to **Google BigQuery**, Google Cloud's Analytical Data Warehouse. This Action takes a user’s question, scans the relevant tables to gather the data schema, then writes a SQL query to answer the user’s question. 

Note: these instructions return back a functioning SQL statement, rather than the result itself. Currently middleware is required to return back a CSV file – we’ll be posting instructions on an example of that soon

### Value + Example Business Use Cases

**Value**: Users can now leverage ChatGPT's natural language capability to connect directly to BigQuery's DWH. 

**Example Use Cases**: 
- Data scientists can connect to tables and run data analyses using ChatGPT's Data Analysis
- Citizen data users can ask basic questions of their transactional data
- Users gain more visibility into their data & potential anomalies

## Application Information

### Application Key Links

Check out these links from the application before you get started:
- Application Website: https://cloud.google.com/bigquery 
- Application API Documentation: https://cloud.google.com/bigquery/docs/reference/rest 

### Application Prerequisites

Before you get started, make sure you go through the following steps in your application environment:
- Set up a GCP project 
- Set up a BQ dataset in that GCP project
- Ensure that the user authenticating into BigQuery via ChatGPT has access to that BQ dataset 

## ChatGPT Steps

### Custom GPT Instructions 

Once you've created a Custom GPT, copy the text below in the Instructions panel. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.


key: _node_content
string_value:
{"id_": "3386f703-e3de-41d8-b6d5-f0967a87e1d4", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_bigquery.ipynb", "title": "GPT Action Library: BigQuery", "tokens": 1723, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "1c38aa33-ddb1-4ce9-89f0-a0ba25bab653", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_bigquery.ipynb", "title": "GPT Action Library: BigQuery", "tokens": 1723, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "ff68a885876b3ddfc1cabb15fa4d20df5df858a0a0584fe922c25b8bdc3dc2b1", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "2abf5d74-d7ce-4adf-98b3-7cc50a92ca53", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_bigquery.ipynb", "title": "GPT Action Library: BigQuery", "tokens": 1723, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "86ee15f5f07b628251fe86098d04edc587f0a777403af3e78b8eb9d4ccc82cd7", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "1416ab15-eb78-4293-a149-30aea28e0383", "node_type": "1", "metadata": {}, "hash": "803397d4bd6a825097902ea1d58578550f1f72b850da83566193f2bc56907def", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 2283, "end_char_idx": 6323, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
```python
**Context**: You are an expert at writing BigQuery SQL queries. A user is going to ask you a question. 

**Instructions**:
1. No matter the user's question, start by running `runQuery` operation using this query: "SELECT column_name, table_name, data_type, description FROM `{project}.{dataset}.INFORMATION_SCHEMA.COLUMN_FIELD_PATHS`" 
-- Assume project = "<insert your default project here>", dataset = "<insert your default dataset here>", unless the user provides different values 
-- Remember to include useLegacySql:false in the json output
2. Convert the user's question into a SQL statement that leverages the step above and run the `runQuery` operation on that SQL statement to confirm the query works. Add a limit of 100 rows
3. Now remove the limit of 100 rows and return back the query for the user to see

**Additional Notes**: If the user says "Let's get started", explain that the user can provide a project or dataset, along with a question they want answered. If the user has no ideas, suggest that we have a sample flights dataset they can query - ask if they want you to query that
```

### OpenAPI Schema 

Once you've created a Custom GPT, copy the text below in the Actions panel. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.


```python
openapi: 3.1.0
info:
  title: BigQuery API
  description: API for querying a BigQuery table.
  version: 1.0.0
servers:
  - url: https://bigquery.googleapis.com/bigquery/v2
    description: Google BigQuery API server
paths:
  /projects/{projectId}/queries:
    post:
      operationId: runQuery
      summary: Executes a query on a specified BigQuery table.
      description: Submits a query to BigQuery and returns the results.
      x-openai-isConsequential: false
      parameters:
        - name: projectId
          in: path
          required: true
          description: The ID of the Google Cloud project.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                query:
                  type: string
                  description: The SQL query string.
                useLegacySql:
                  type: boolean
                  description: Whether to use legacy SQL.
                  default: false
      responses:
        '200':
          description: Successful query execution.
          content:
            application/json:
              schema:
                type: object
                properties:
                  kind:
                    type: string
                    example: "bigquery#queryResponse"
                  schema:
                    type: object
                    description: The schema of the results.
                  jobReference:
                    type: object
                    properties:
                      projectId:
                        type: string
                      jobId:
                        type: string
                  rows:
                    type: array
                    items:
                      type: object
                      properties:
                        f:
                          type: array
                          items:
                            type: object
                            properties:
                              v:
                                type: string
                  totalRows:
                    type: string
                    description: Total number of rows in the query result.
                  pageToken:
                    type: string
                    description: Token for pagination of query results.
        '400':
          description: Bad request. The request was invalid.
        '401':
          description: Unauthorized. Authentication is required.
        '403':
          description: Forbidden. The request is not allowed.


key: _node_content
string_value:
{"id_": "1416ab15-eb78-4293-a149-30aea28e0383", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_bigquery.ipynb", "title": "GPT Action Library: BigQuery", "tokens": 1723, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "1c38aa33-ddb1-4ce9-89f0-a0ba25bab653", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_bigquery.ipynb", "title": "GPT Action Library: BigQuery", "tokens": 1723, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "ff68a885876b3ddfc1cabb15fa4d20df5df858a0a0584fe922c25b8bdc3dc2b1", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "3386f703-e3de-41d8-b6d5-f0967a87e1d4", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_bigquery.ipynb", "title": "GPT Action Library: BigQuery", "tokens": 1723, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "191d9ce1685161f8bbe8e6382e72cdb3b1ce916fd8ee66a3cb475048ab8d9c35", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "378edc95-c2cb-4588-8d08-4550912738dc", "node_type": "1", "metadata": {}, "hash": "b32152074618f769857856817faa2a03968f0050468e7d9bae585a11eb01e4a9", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 4023, "end_char_idx": 7912, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
description: Submits a query to BigQuery and returns the results.
      x-openai-isConsequential: false
      parameters:
        - name: projectId
          in: path
          required: true
          description: The ID of the Google Cloud project.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                query:
                  type: string
                  description: The SQL query string.
                useLegacySql:
                  type: boolean
                  description: Whether to use legacy SQL.
                  default: false
      responses:
        '200':
          description: Successful query execution.
          content:
            application/json:
              schema:
                type: object
                properties:
                  kind:
                    type: string
                    example: "bigquery#queryResponse"
                  schema:
                    type: object
                    description: The schema of the results.
                  jobReference:
                    type: object
                    properties:
                      projectId:
                        type: string
                      jobId:
                        type: string
                  rows:
                    type: array
                    items:
                      type: object
                      properties:
                        f:
                          type: array
                          items:
                            type: object
                            properties:
                              v:
                                type: string
                  totalRows:
                    type: string
                    description: Total number of rows in the query result.
                  pageToken:
                    type: string
                    description: Token for pagination of query results.
        '400':
          description: Bad request. The request was invalid.
        '401':
          description: Unauthorized. Authentication is required.
        '403':
          description: Forbidden. The request is not allowed.
        '404':
          description: Not found. The specified resource was not found.
        '500':
          description: Internal server error. An error occurred while processing the request.
```

## Authentication Instructions

Below are instructions on setting up authentication with this 3rd party application. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.

### Pre-Action Steps

Before you set up authentication in ChatGPT, please take the following steps in the application.

- Go to the Google Cloud Console
- Navigate to API & Services > Credentials
- Create new OAuth credentials (or use an existing one)
- Locate your OAuth Client ID & Client Secret and store both values securely (see screenshot below)

![gptactions_BigQuery_auth.png](../../../images/gptactions_BigQuery_auth.png)

### In ChatGPT

In ChatGPT, click on "Authentication" and choose **"OAuth"**. Enter in the information below. 

- **Client ID**: use Client ID from steps above 
- **Client Secret**: use Client Secret from steps above
- **Authorization URL**: https://accounts.google.com/o/oauth2/auth
- **Token URL**: https://oauth2.googleapis.com/token 
- **Scope**: https://www.googleapis.com/auth/bigquery 
- **Token**: Default (POST)

### Post-Action Steps

Once you've set up authentication in ChatGPT, follow the steps below in the application to finalize the Action. 

- Copy the callback URL from the GPT Action
- In the “Authorized redirect URIs” (see screenshot above), add your callback URL


key: _node_content
string_value:
{"id_": "378edc95-c2cb-4588-8d08-4550912738dc", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_bigquery.ipynb", "title": "GPT Action Library: BigQuery", "tokens": 1723, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "1c38aa33-ddb1-4ce9-89f0-a0ba25bab653", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_bigquery.ipynb", "title": "GPT Action Library: BigQuery", "tokens": 1723, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "ff68a885876b3ddfc1cabb15fa4d20df5df858a0a0584fe922c25b8bdc3dc2b1", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "1416ab15-eb78-4293-a149-30aea28e0383", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_bigquery.ipynb", "title": "GPT Action Library: BigQuery", "tokens": 1723, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "dcc96a50f7bf8c67f99e79e111b4df3acd2e74b46325a67e5c20becbca044497", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 6099, "end_char_idx": 8589, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
'400':
          description: Bad request. The request was invalid.
        '401':
          description: Unauthorized. Authentication is required.
        '403':
          description: Forbidden. The request is not allowed.
        '404':
          description: Not found. The specified resource was not found.
        '500':
          description: Internal server error. An error occurred while processing the request.
```

## Authentication Instructions

Below are instructions on setting up authentication with this 3rd party application. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.

### Pre-Action Steps

Before you set up authentication in ChatGPT, please take the following steps in the application.

- Go to the Google Cloud Console
- Navigate to API & Services > Credentials
- Create new OAuth credentials (or use an existing one)
- Locate your OAuth Client ID & Client Secret and store both values securely (see screenshot below)

![gptactions_BigQuery_auth.png](../../../images/gptactions_BigQuery_auth.png)

### In ChatGPT

In ChatGPT, click on "Authentication" and choose **"OAuth"**. Enter in the information below. 

- **Client ID**: use Client ID from steps above 
- **Client Secret**: use Client Secret from steps above
- **Authorization URL**: https://accounts.google.com/o/oauth2/auth
- **Token URL**: https://oauth2.googleapis.com/token 
- **Scope**: https://www.googleapis.com/auth/bigquery 
- **Token**: Default (POST)

### Post-Action Steps

Once you've set up authentication in ChatGPT, follow the steps below in the application to finalize the Action. 

- Copy the callback URL from the GPT Action
- In the “Authorized redirect URIs” (see screenshot above), add your callback URL 


### FAQ & Troubleshooting

- *Callback URL Error:* If you get a callback URL error in ChatGPT, pay close attention to the screenshot above. You need to add the callback URL directly into GCP for the action to authenticate correctly
- *Schema calls the wrong project or dataset:* If ChatGPT calls the wrong project or dataset, consider updating your instructions to make it more explicit either (a) which project / dataset should be called or (b) to require the user provide those exact details before it runs the query

*Are there integrations that you’d like us to prioritize? Are there errors in our integrations? File a PR or issue in our github, and we’ll take a look.*


key: url
string_value:
https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb


key: _node_content
string_value:
{"id_": "79cdd0f6-a3fc-473d-83cd-2543ed6cddaa", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0bad2d8c-273e-43e2-b615-c2fef5fbfe4e", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "0bff53afb5fc2cd1b87081629e296d8f636618662e6925d2d8357e7df8ffa7c0", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "ee77031a-6ddb-48f0-a0b9-e62f709c88b0", "node_type": "1", "metadata": {}, "hash": "7bb958fe707b46f22189caef0ec89c6a3ab67e5ab5f03e57ad8fae234cef3724", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 0, "end_char_idx": 2929, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
# GPT Action Library: Sharepoint (Return as Document)

## Introduction

This page provides an instruction & guide for developers building a GPT Action for a specific application. Before you proceed, make sure to first familiarize yourself with the following information: 
- [Introduction to GPT Actions](https://platform.openai.com/docs/actions)
- [Introduction to GPT Actions Library](https://platform.openai.com/docs/actions/actions-library)
- [Example of Building a GPT Action from Scratch](https://platform.openai.com/docs/actions/getting-started)

This solution enables a GPT action to answer a user’s question with the context of files the user can access in SharePoint or Office365, using Microsoft’s Graph API [search capabilities](https://learn.microsoft.com/en-us/graph/api/resources/search-api-overview?view=graph-rest-1.0) and the ability to [retrieve files](https://learn.microsoft.com/en-us/graph/api/driveitem-get?view=graph-rest-1.0\&tabs=http). It uses Azure Functions to process the Graph API response and convert it to a human readable format or structure it in a way ChatGPT understands. This code is meant to be directional, and you should modify it to your requirements.

This solution pre-processes the file within the Azure Function. The Azure Function returns text, instead of the base64 encoded file. Due to the pre-processing and the conversion to text, this solution is best used for large, unstructured documents, and for when you want to analyze more than the amount of files supported in the first solution (see documentation [here](https://platform.openai.com/docs/actions/sending-files)).

### Value + Example Business Use Cases

**Value**: Users can now leverage ChatGPT's natural language capability to connect directly to files in Sharpeoint

**Example Use Cases**: 
- A user needs to look up which files relate to a certain topic
- A user needs an answer to a critical question, buried deep in documents

## Architecture / Example

![](../../../images/solution_2.gif)

This solution uses a Node.js Azure Function to, based on the logged in user:

1. Search for a relevant file that the user has access to, based on the user’s initial question.

2. For each file that is found, convert it to a consistent readable format and retrieve all the text.

3. Use GPT 4o mini (gpt-4o-mini) to extract the relevant text from the files based on the initial user’s question. Note the pricing of GPT 4o mini [here](https://openai.com/pricing#language-models) - since we are dealing with small token chunks, the cost of this step is nominal.  

4. Returns that data to ChatGPT. The GPT then uses that information to respond to the user's initial question.

As you can see from the below architecture diagram, the first three steps are the same as Solution 1. The main difference is that this solution converts the file to text instead of a base64 string, and then summarizes that text using GPT 4o mini.

!


key: _node_content
string_value:
{"id_": "ee77031a-6ddb-48f0-a0b9-e62f709c88b0", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0bad2d8c-273e-43e2-b615-c2fef5fbfe4e", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "0bff53afb5fc2cd1b87081629e296d8f636618662e6925d2d8357e7df8ffa7c0", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "79cdd0f6-a3fc-473d-83cd-2543ed6cddaa", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "d8d9568bfedab9c9c2ddc00c9d96f045e4ab5799b16e31b9ac89940335e2f928", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "78beaf20-6ee9-4ad5-a456-04937ae79550", "node_type": "1", "metadata": {}, "hash": "e58c907949189810b92519fe61b8a36ebadba4594ca9c1df30131bd4c6fa42b9", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 1194, "end_char_idx": 4587, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
This solution pre-processes the file within the Azure Function. The Azure Function returns text, instead of the base64 encoded file. Due to the pre-processing and the conversion to text, this solution is best used for large, unstructured documents, and for when you want to analyze more than the amount of files supported in the first solution (see documentation [here](https://platform.openai.com/docs/actions/sending-files)).

### Value + Example Business Use Cases

**Value**: Users can now leverage ChatGPT's natural language capability to connect directly to files in Sharpeoint

**Example Use Cases**: 
- A user needs to look up which files relate to a certain topic
- A user needs an answer to a critical question, buried deep in documents

## Architecture / Example

![](../../../images/solution_2.gif)

This solution uses a Node.js Azure Function to, based on the logged in user:

1. Search for a relevant file that the user has access to, based on the user’s initial question.

2. For each file that is found, convert it to a consistent readable format and retrieve all the text.

3. Use GPT 4o mini (gpt-4o-mini) to extract the relevant text from the files based on the initial user’s question. Note the pricing of GPT 4o mini [here](https://openai.com/pricing#language-models) - since we are dealing with small token chunks, the cost of this step is nominal.  

4. Returns that data to ChatGPT. The GPT then uses that information to respond to the user's initial question.

As you can see from the below architecture diagram, the first three steps are the same as Solution 1. The main difference is that this solution converts the file to text instead of a base64 string, and then summarizes that text using GPT 4o mini.

![](../../../images/solution_2_architecture.png)

## Application Information

### Application Key Links

Check out these links from the application before you get started:
- Application Website: https://www.microsoft.com/en-us/microsoft-365/sharepoint/collaboration
- Application API Documentation: https://learn.microsoft.com/en-us/previous-versions/office/developer/sharepoint-rest-reference/

### Application Prerequisites

Before you get started, make sure you go through the following steps in your application environment:
- Access to a Sharepoint environment 
- Postman (and knowledge of APIs and OAuth)
- An OpenAI API Key from platform.openai.com

## Middleware Information

If you follow the [search concept files guide](https://learn.microsoft.com/en-us/graph/search-concept-files), the [Microsoft Graph Search API](https://learn.microsoft.com/en-us/graph/search-concept-files) returns references to files that fit the criteria, but not the file contents themselves. Therefore, middleware is required, rather than hitting the MSFT endpoints directly.

Steps: 

1. loop through the returned files and download the files using the [Download File endpoint](https://learn.microsoft.com/en-us/graph/api/driveitem-get-content?view=graph-rest-1.0\&tabs=http) or [Convert File endpoint](https://learn.microsoft.com/en-us/graph/api/driveitem-get-content-format?view=graph-rest-1.0\&tabs=http)

2. convert that Binary stream to human readable text using [pdf-parse](https://www.npmjs.com/package/pdf-parse)

3. Then, we can optimize further by summarizing using gpt-4o-mini in the function to help with the 100,000 character limit we impose on Actions today.


key: _node_content
string_value:
{"id_": "78beaf20-6ee9-4ad5-a456-04937ae79550", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0bad2d8c-273e-43e2-b615-c2fef5fbfe4e", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "0bff53afb5fc2cd1b87081629e296d8f636618662e6925d2d8357e7df8ffa7c0", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "ee77031a-6ddb-48f0-a0b9-e62f709c88b0", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "6b473b8f54a1500a2dfcb526d152eafb077d52b402877c3708705e359a1466f5", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "e53fd73b-7de0-4c38-b01a-188c7796d169", "node_type": "1", "metadata": {}, "hash": "664c4573cc56757082575373837db4382ba9d8d061753e0ab2064e9593bb637d", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 2782, "end_char_idx": 5992, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
The main difference is that this solution converts the file to text instead of a base64 string, and then summarizes that text using GPT 4o mini.

![](../../../images/solution_2_architecture.png)

## Application Information

### Application Key Links

Check out these links from the application before you get started:
- Application Website: https://www.microsoft.com/en-us/microsoft-365/sharepoint/collaboration
- Application API Documentation: https://learn.microsoft.com/en-us/previous-versions/office/developer/sharepoint-rest-reference/

### Application Prerequisites

Before you get started, make sure you go through the following steps in your application environment:
- Access to a Sharepoint environment 
- Postman (and knowledge of APIs and OAuth)
- An OpenAI API Key from platform.openai.com

## Middleware Information

If you follow the [search concept files guide](https://learn.microsoft.com/en-us/graph/search-concept-files), the [Microsoft Graph Search API](https://learn.microsoft.com/en-us/graph/search-concept-files) returns references to files that fit the criteria, but not the file contents themselves. Therefore, middleware is required, rather than hitting the MSFT endpoints directly.

Steps: 

1. loop through the returned files and download the files using the [Download File endpoint](https://learn.microsoft.com/en-us/graph/api/driveitem-get-content?view=graph-rest-1.0\&tabs=http) or [Convert File endpoint](https://learn.microsoft.com/en-us/graph/api/driveitem-get-content-format?view=graph-rest-1.0\&tabs=http)

2. convert that Binary stream to human readable text using [pdf-parse](https://www.npmjs.com/package/pdf-parse)

3. Then, we can optimize further by summarizing using gpt-4o-mini in the function to help with the 100,000 character limit we impose on Actions today. 

### Additional Steps

#### Set up Azure Function

1. Set up an Azure Function using the steps in the [Azure Function cookbook](https://cookbook.openai.com/examples/chatgpt/gpt_actions_library/gpt_middleware_azure_function)

#### Add in Function Code

Now that you have an authenticated Azure Function, we can update the function to search SharePoint / O365

2. Go to your test function and paste in the code from [this file](https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/solution_two_preprocessing.js). Save the function.

> **This code is meant to be directional** - while it should work out of the box, it is designed to be customized to your needs (see examples towards the end of this document).

3. Set up the following env variables by going to the **Configuration** tab on the left under **Settings.** Note that this may be listed directly in **Environment Variables** depending on your Azure UI.

    1. `TENANT_ID`: copied from previous section

    2. `CLIENT_ID`: copied from previous section 

    3. `OPENAI_API_KEY:` spin up an OpenAI API key on platform.openai.com.

4. Go to the **Console** tab under the **Development Tools**

    1. Install the following packages in console

       1. `npm install @microsoft/microsoft-graph-client`

       2. `npm install axios`

       3. `npm install pdf-parse`

       4. `npm install openai`

5.


key: _node_content
string_value:
{"id_": "e53fd73b-7de0-4c38-b01a-188c7796d169", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0bad2d8c-273e-43e2-b615-c2fef5fbfe4e", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "0bff53afb5fc2cd1b87081629e296d8f636618662e6925d2d8357e7df8ffa7c0", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "78beaf20-6ee9-4ad5-a456-04937ae79550", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "aeb2be9440d4cf720f5e87edd2a4d0ae2d22f869e8f2e56a4c8ad6b74f820ae6", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "8e8ff368-9b20-47a7-a4c7-c700fd9f66de", "node_type": "1", "metadata": {}, "hash": "24580a9bf86d8b884db30bef5d03680d97a7845dd90a0063c3d72ae3ee4a3f70", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 4440, "end_char_idx": 7527, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
Then, we can optimize further by summarizing using gpt-4o-mini in the function to help with the 100,000 character limit we impose on Actions today. 

### Additional Steps

#### Set up Azure Function

1. Set up an Azure Function using the steps in the [Azure Function cookbook](https://cookbook.openai.com/examples/chatgpt/gpt_actions_library/gpt_middleware_azure_function)

#### Add in Function Code

Now that you have an authenticated Azure Function, we can update the function to search SharePoint / O365

2. Go to your test function and paste in the code from [this file](https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/solution_two_preprocessing.js). Save the function.

> **This code is meant to be directional** - while it should work out of the box, it is designed to be customized to your needs (see examples towards the end of this document).

3. Set up the following env variables by going to the **Configuration** tab on the left under **Settings.** Note that this may be listed directly in **Environment Variables** depending on your Azure UI.

    1. `TENANT_ID`: copied from previous section

    2. `CLIENT_ID`: copied from previous section 

    3. `OPENAI_API_KEY:` spin up an OpenAI API key on platform.openai.com.

4. Go to the **Console** tab under the **Development Tools**

    1. Install the following packages in console

       1. `npm install @microsoft/microsoft-graph-client`

       2. `npm install axios`

       3. `npm install pdf-parse`

       4. `npm install openai`

5. Once this is complete, try calling the function (POST call) from Postman again, putting the below into body (using a query and search term you think will generate responses).

    ```json
    {
        "query": "<choose a question>",
        "searchTerm": "<choose a search term>"
    }
    ```

6. If you get a response, you are ready to set this up with a Custom GPT!


## Detailed Walkthrough

The below walks through setup instructions and walkthrough unique to this solution of pre-processing the files and extracting summaries in the Azure Function. You can find the entire code [here](https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/solution_two_preprocessing.js).

### Code Walkthrough

#### Implementing the Authentication 

Below we have a few helper functions that we’ll use in the function.

#### Initializing the Microsoft Graph Client
Create a function to initialize the Graph client with an access token. This will be used to search through Office 365 and SharePoint.

```javascript
const { Client } = require('@microsoft/microsoft-graph-client');

function initGraphClient(accessToken) {
    return Client.init({
        authProvider: (done) => {
            done(null, accessToken);
        }
    });
}
```

#### Obtaining an On-Behalf-Of (OBO) Token
This function uses an existing bearer token to request an OBO token from Microsoft's identity platform. This enables passing through the credentials to ensure the search only returns files the logged-in user can access.


key: _node_content
string_value:
{"id_": "8e8ff368-9b20-47a7-a4c7-c700fd9f66de", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0bad2d8c-273e-43e2-b615-c2fef5fbfe4e", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "0bff53afb5fc2cd1b87081629e296d8f636618662e6925d2d8357e7df8ffa7c0", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "e53fd73b-7de0-4c38-b01a-188c7796d169", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "68fc5e30a4c633ee8e6aa80db397c167e38f86f696bd1301bf6fc0e4004ccd7d", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "a105e9ac-7813-43df-9b36-3e682ecc83f4", "node_type": "1", "metadata": {}, "hash": "4558c52194f55355a17c72861a1550e773c6f7d4569fedcf77e09f384432e7af", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 5790, "end_char_idx": 9024, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
Install the following packages in console

       1. `npm install @microsoft/microsoft-graph-client`

       2. `npm install axios`

       3. `npm install pdf-parse`

       4. `npm install openai`

5. Once this is complete, try calling the function (POST call) from Postman again, putting the below into body (using a query and search term you think will generate responses).

    ```json
    {
        "query": "<choose a question>",
        "searchTerm": "<choose a search term>"
    }
    ```

6. If you get a response, you are ready to set this up with a Custom GPT!


## Detailed Walkthrough

The below walks through setup instructions and walkthrough unique to this solution of pre-processing the files and extracting summaries in the Azure Function. You can find the entire code [here](https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/sharepoint_azure_function/solution_two_preprocessing.js).

### Code Walkthrough

#### Implementing the Authentication 

Below we have a few helper functions that we’ll use in the function.

#### Initializing the Microsoft Graph Client
Create a function to initialize the Graph client with an access token. This will be used to search through Office 365 and SharePoint.

```javascript
const { Client } = require('@microsoft/microsoft-graph-client');

function initGraphClient(accessToken) {
    return Client.init({
        authProvider: (done) => {
            done(null, accessToken);
        }
    });
}
```

#### Obtaining an On-Behalf-Of (OBO) Token
This function uses an existing bearer token to request an OBO token from Microsoft's identity platform. This enables passing through the credentials to ensure the search only returns files the logged-in user can access.

```javascript
const axios = require('axios');
const qs = require('querystring');

async function getOboToken(userAccessToken) {
    const { TENANT_ID, CLIENT_ID, MICROSOFT_PROVIDER_AUTHENTICATION_SECRET } = process.env;
    const params = {
        client_id: CLIENT_ID,
        client_secret: MICROSOFT_PROVIDER_AUTHENTICATION_SECRET,
        grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        assertion: userAccessToken,
        requested_token_use: 'on_behalf_of',
        scope: 'https://graph.microsoft.com/.default'
    };

    const url = `https\://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token`;
    try {
        const response = await axios.post(url, qs.stringify(params), {
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
        });
        return response.data.access\_token;
    } catch (error) {
        console.error('Error obtaining OBO token:', error.response?.data || error.message);
        throw error;
    }
}
```

#### Retrieving Content from O365 / SharePoint Items

This function fetches the content of drive items, handling different file types and converting files to PDF when necessary for text extraction. This uses the [download endpoint](https://learn.microsoft.com/en-us/graph/api/driveitem-get-content?view=graph-rest-1.0\&tabs=http) for PDFs and the [convert endpoint](https://learn.microsoft.com/en-us/graph/api/driveitem-get-content-format?view=graph-rest-1.0\&tabs=http) for other supported file types.


key: _node_content
string_value:
{"id_": "a105e9ac-7813-43df-9b36-3e682ecc83f4", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0bad2d8c-273e-43e2-b615-c2fef5fbfe4e", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "0bff53afb5fc2cd1b87081629e296d8f636618662e6925d2d8357e7df8ffa7c0", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "8e8ff368-9b20-47a7-a4c7-c700fd9f66de", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "535450dcb1e1d96a1088b893fbdf99658555ab3595a0b6e82b50228aa70c9222", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "3678aec0-f200-4b15-9bf2-709223c53c7a", "node_type": "1", "metadata": {}, "hash": "175562afa5784a2cc5d7c82a2e3d79f20333f8838a43b191ce93900a64365172", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 7412, "end_char_idx": 10317, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
This enables passing through the credentials to ensure the search only returns files the logged-in user can access.

```javascript
const axios = require('axios');
const qs = require('querystring');

async function getOboToken(userAccessToken) {
    const { TENANT_ID, CLIENT_ID, MICROSOFT_PROVIDER_AUTHENTICATION_SECRET } = process.env;
    const params = {
        client_id: CLIENT_ID,
        client_secret: MICROSOFT_PROVIDER_AUTHENTICATION_SECRET,
        grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        assertion: userAccessToken,
        requested_token_use: 'on_behalf_of',
        scope: 'https://graph.microsoft.com/.default'
    };

    const url = `https\://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token`;
    try {
        const response = await axios.post(url, qs.stringify(params), {
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
        });
        return response.data.access\_token;
    } catch (error) {
        console.error('Error obtaining OBO token:', error.response?.data || error.message);
        throw error;
    }
}
```

#### Retrieving Content from O365 / SharePoint Items

This function fetches the content of drive items, handling different file types and converting files to PDF when necessary for text extraction. This uses the [download endpoint](https://learn.microsoft.com/en-us/graph/api/driveitem-get-content?view=graph-rest-1.0\&tabs=http) for PDFs and the [convert endpoint](https://learn.microsoft.com/en-us/graph/api/driveitem-get-content-format?view=graph-rest-1.0\&tabs=http) for other supported file types.
```javascript
const getDriveItemContent = async (client, driveId, itemId, name) => {
    try {
        const fileType = path.extname(name).toLowerCase();
        // the below files types are the ones that are able to be converted to PDF to extract the text. See https://learn.microsoft.com/en-us/graph/api/driveitem-get-content-format?view=graph-rest-1.0&tabs=http
        const allowedFileTypes = ['.pdf', '.doc', '.docx', '.odp', '.ods', '.odt', '.pot', '.potm', '.potx', '.pps', '.ppsx', '.ppsxm', '.ppt', '.pptm', '.pptx', '.rtf'];
        // filePath changes based on file type, adding ?format=pdf to convert non-pdf types to pdf for text extraction, so all files in allowedFileTypes above are converted to pdf
        const filePath = `/drives/${driveId}/items/${itemId}/content` + ((fileType === '.pdf' || fileType === '.txt' || fileType === '.csv') ? '' : '?format=pdf');
        if (allowedFileTypes.includes(fileType)) {
            response = await client.api(filePath).getStream();
            // The below takes the chunks in response and combines
            let chunks = [];
            for await (let chunk of response) {
                chunks.push(chunk);
            }
            let buffer = Buffer.concat(chunks);
            // the below extracts the text from the PDF.


key: _node_content
string_value:
{"id_": "3678aec0-f200-4b15-9bf2-709223c53c7a", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0bad2d8c-273e-43e2-b615-c2fef5fbfe4e", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "0bff53afb5fc2cd1b87081629e296d8f636618662e6925d2d8357e7df8ffa7c0", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "a105e9ac-7813-43df-9b36-3e682ecc83f4", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "e9110c0874a0ddca1962676830dd8d16ce0dc084e92e030d3acd0eac03bb1368", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "6cee0db9-8896-4c43-a512-65b065a6413c", "node_type": "1", "metadata": {}, "hash": "b17f18c930c6ac111ed1b4cb1b5f54b60bae6a5e2373b9728f56c8ba43af4bfd", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 8720, "end_char_idx": 11945, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
This uses the [download endpoint](https://learn.microsoft.com/en-us/graph/api/driveitem-get-content?view=graph-rest-1.0\&tabs=http) for PDFs and the [convert endpoint](https://learn.microsoft.com/en-us/graph/api/driveitem-get-content-format?view=graph-rest-1.0\&tabs=http) for other supported file types.
```javascript
const getDriveItemContent = async (client, driveId, itemId, name) => {
    try {
        const fileType = path.extname(name).toLowerCase();
        // the below files types are the ones that are able to be converted to PDF to extract the text. See https://learn.microsoft.com/en-us/graph/api/driveitem-get-content-format?view=graph-rest-1.0&tabs=http
        const allowedFileTypes = ['.pdf', '.doc', '.docx', '.odp', '.ods', '.odt', '.pot', '.potm', '.potx', '.pps', '.ppsx', '.ppsxm', '.ppt', '.pptm', '.pptx', '.rtf'];
        // filePath changes based on file type, adding ?format=pdf to convert non-pdf types to pdf for text extraction, so all files in allowedFileTypes above are converted to pdf
        const filePath = `/drives/${driveId}/items/${itemId}/content` + ((fileType === '.pdf' || fileType === '.txt' || fileType === '.csv') ? '' : '?format=pdf');
        if (allowedFileTypes.includes(fileType)) {
            response = await client.api(filePath).getStream();
            // The below takes the chunks in response and combines
            let chunks = [];
            for await (let chunk of response) {
                chunks.push(chunk);
            }
            let buffer = Buffer.concat(chunks);
            // the below extracts the text from the PDF.
            const pdfContents = await pdfParse(buffer);
            return pdfContents.text;
        } else if (fileType === '.txt') {
            // If the type is txt, it does not need to create a stream and instead just grabs the content
            response = await client.api(filePath).get();
            return response;
        }  else if (fileType === '.csv') {
            response = await client.api(filePath).getStream();
            let chunks = [];
            for await (let chunk of response) {
                chunks.push(chunk);
            }
            let buffer = Buffer.concat(chunks);
            let dataString = buffer.toString('utf-8');
            return dataString
            
    } else {
        return 'Unsupported File Type';
    }
     
    } catch (error) {
        console.error('Error fetching drive content:', error);
        throw new Error(`Failed to fetch content for ${name}: ${error.message}`);
    }
};
```

#### Integrating GPT 4o mini for Text Analysis

This function utilizes the OpenAI SDK to analyze text extracted from documents and find relevant information based on a user query. This helps to ensure only relevant text to the user’s question is returned to the GPT. 

```javascript
const getRelevantParts = async (text, query) => {
    try {
        // We use your OpenAI key to initialize the OpenAI client
        const openAIKey = process.env["OPENAI_API_KEY"];
        const openai = new OpenAI({
            apiKey: openAIKey,
        });
        const response = await openai.chat.completions.create({
            // Using gpt-4o-mini due to speed to prevent timeouts.


key: _node_content
string_value:
{"id_": "6cee0db9-8896-4c43-a512-65b065a6413c", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0bad2d8c-273e-43e2-b615-c2fef5fbfe4e", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "0bff53afb5fc2cd1b87081629e296d8f636618662e6925d2d8357e7df8ffa7c0", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "3678aec0-f200-4b15-9bf2-709223c53c7a", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "57fbcc7889cfdfe39ed183bb0a83eac93e46878023c8ab7b47132e8af66f3bba", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "e60d315c-32b8-4f23-8905-8eee0b8dd101", "node_type": "1", "metadata": {}, "hash": "9494567576bf19e6efd626c8802f6e519e4b30ae3772115610f74cf87376f541", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 10330, "end_char_idx": 13904, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
const pdfContents = await pdfParse(buffer);
            return pdfContents.text;
        } else if (fileType === '.txt') {
            // If the type is txt, it does not need to create a stream and instead just grabs the content
            response = await client.api(filePath).get();
            return response;
        }  else if (fileType === '.csv') {
            response = await client.api(filePath).getStream();
            let chunks = [];
            for await (let chunk of response) {
                chunks.push(chunk);
            }
            let buffer = Buffer.concat(chunks);
            let dataString = buffer.toString('utf-8');
            return dataString
            
    } else {
        return 'Unsupported File Type';
    }
     
    } catch (error) {
        console.error('Error fetching drive content:', error);
        throw new Error(`Failed to fetch content for ${name}: ${error.message}`);
    }
};
```

#### Integrating GPT 4o mini for Text Analysis

This function utilizes the OpenAI SDK to analyze text extracted from documents and find relevant information based on a user query. This helps to ensure only relevant text to the user’s question is returned to the GPT. 

```javascript
const getRelevantParts = async (text, query) => {
    try {
        // We use your OpenAI key to initialize the OpenAI client
        const openAIKey = process.env["OPENAI_API_KEY"];
        const openai = new OpenAI({
            apiKey: openAIKey,
        });
        const response = await openai.chat.completions.create({
            // Using gpt-4o-mini due to speed to prevent timeouts. You can tweak this prompt as needed
            model: "gpt-4o-mini",
            messages: [
                {"role": "system", "content": "You are a helpful assistant that finds relevant content in text based on a query. You only return the relevant sentences, and you return a maximum of 10 sentences"},
                {"role": "user", "content": `Based on this question: **"${query}"**, get the relevant parts from the following text:*****\n\n${text}*****. If you cannot answer the question based on the text, respond with 'No information provided'`}
            ],
            // using temperature of 0 since we want to just extract the relevant content
            temperature: 0,
            // using max_tokens of 1000, but you can customize this based on the number of documents you are searching. 
            max_tokens: 1000
        });
        return response.choices[0].message.content;
    } catch (error) {
        console.error('Error with OpenAI:', error);
        return 'Error processing text with OpenAI' + error;
    }
};
```

#### Creating the Azure Function to Handle Requests

Now that we have all these helper functions, the Azure Function will orchestrate the flow, by authenticating the user, performing the search, and iterating through the search results to extract the text and retrieve the relevant parts of the text to the GPT.

**Handling HTTP Requests:** The function starts by extracting the query and searchTerm from the HTTP request. It checks if the Authorization header is present and extracts the bearer token.

**Authentication:** Using the bearer token, it obtains an OBO token from Microsoft's identity platform using getOboToken defined above.

**Initializing the Graph Client:** With the OBO token, it initializes the Microsoft Graph client using initGraphClient defined above.

**Document Search:** It constructs a search query and sends it to the Microsoft Graph API to find documents based on the searchTerm.


key: _node_content
string_value:
{"id_": "e60d315c-32b8-4f23-8905-8eee0b8dd101", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0bad2d8c-273e-43e2-b615-c2fef5fbfe4e", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "0bff53afb5fc2cd1b87081629e296d8f636618662e6925d2d8357e7df8ffa7c0", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "6cee0db9-8896-4c43-a512-65b065a6413c", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "21f4995f091e83614940aecbcab00a273dd2bdb311e1a49053f8d2cb24a2687b", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "2c52bd69-5e7b-4196-bf53-a6f622b8ffef", "node_type": "1", "metadata": {}, "hash": "be676deb2006b94321d8fc57dd8b857e6530e26b35a2d894bfc254d7714d0e76", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 12169, "end_char_idx": 15817, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
You only return the relevant sentences, and you return a maximum of 10 sentences"},
                {"role": "user", "content": `Based on this question: **"${query}"**, get the relevant parts from the following text:*****\n\n${text}*****. If you cannot answer the question based on the text, respond with 'No information provided'`}
            ],
            // using temperature of 0 since we want to just extract the relevant content
            temperature: 0,
            // using max_tokens of 1000, but you can customize this based on the number of documents you are searching. 
            max_tokens: 1000
        });
        return response.choices[0].message.content;
    } catch (error) {
        console.error('Error with OpenAI:', error);
        return 'Error processing text with OpenAI' + error;
    }
};
```

#### Creating the Azure Function to Handle Requests

Now that we have all these helper functions, the Azure Function will orchestrate the flow, by authenticating the user, performing the search, and iterating through the search results to extract the text and retrieve the relevant parts of the text to the GPT.

**Handling HTTP Requests:** The function starts by extracting the query and searchTerm from the HTTP request. It checks if the Authorization header is present and extracts the bearer token.

**Authentication:** Using the bearer token, it obtains an OBO token from Microsoft's identity platform using getOboToken defined above.

**Initializing the Graph Client:** With the OBO token, it initializes the Microsoft Graph client using initGraphClient defined above.

**Document Search:** It constructs a search query and sends it to the Microsoft Graph API to find documents based on the searchTerm.

**Document Processing**: For each document returned by the search:

- It retrieves the document content using getDriveItemContent.

- If the file type is supported, it analyzes the content using getRelevantParts, which sends the text to OpenAI's model for extracting relevant information based on the query.

- It collects the analysis results and includes metadata like the document name and URL.

**Response**: The function sorts the results by relevance and sends them back in the HTTP response.

```javascript
module.exports = async function (context, req) {
    const query = req.query.query || (req.body && req.body.query);
    const searchTerm = req.query.searchTerm || (req.body && req.body.searchTerm);
    if (!req.headers.authorization) {
        context.res = {
            status: 400,
            body: 'Authorization header is missing'
        };
        return;
    }
    /// The below takes the token passed to the function, to use to get an OBO token.
    const bearerToken = req.headers.authorization.split(' ')[1];
    let accessToken;
    try {
        accessToken = await getOboToken(bearerToken);
    } catch (error) {
        context.res = {
            status: 500,
            body: `Failed to obtain OBO token: ${error.message}`
        };
        return;
    }
    // Initialize the Graph Client using the initGraphClient function defined above
    let client = initGraphClient(accessToken);
    // this is the search body to be used in the Microsft Graph Search API: https://learn.microsoft.com/en-us/graph/search-concept-files
    const requestBody = {
        requests: [
            {
                entityTypes: ['driveItem'],
                query: {
                    queryString: searchTerm
                },
                from: 0,
                // the below is set to summarize the top 10 search results from the Graph API, but can configure based on your documents.


key: _node_content
string_value:
{"id_": "2c52bd69-5e7b-4196-bf53-a6f622b8ffef", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0bad2d8c-273e-43e2-b615-c2fef5fbfe4e", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "0bff53afb5fc2cd1b87081629e296d8f636618662e6925d2d8357e7df8ffa7c0", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "e60d315c-32b8-4f23-8905-8eee0b8dd101", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "ec8691a09a2693c4fb4442a92b8114ac104e46beeedfa71db4dad97e15ed3c75", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "46495823-25ea-47bf-89e2-1f6bb234a04e", "node_type": "1", "metadata": {}, "hash": "ada524fd571cbf3ec2ccf3fb2a52eb3955843ac086c1ae47598e4412f3e09ed2", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 13906, "end_char_idx": 15950, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
**Document Processing**: For each document returned by the search:

- It retrieves the document content using getDriveItemContent.

- If the file type is supported, it analyzes the content using getRelevantParts, which sends the text to OpenAI's model for extracting relevant information based on the query.

- It collects the analysis results and includes metadata like the document name and URL.

**Response**: The function sorts the results by relevance and sends them back in the HTTP response.

```javascript
module.exports = async function (context, req) {
    const query = req.query.query || (req.body && req.body.query);
    const searchTerm = req.query.searchTerm || (req.body && req.body.searchTerm);
    if (!req.headers.authorization) {
        context.res = {
            status: 400,
            body: 'Authorization header is missing'
        };
        return;
    }
    /// The below takes the token passed to the function, to use to get an OBO token.
    const bearerToken = req.headers.authorization.split(' ')[1];
    let accessToken;
    try {
        accessToken = await getOboToken(bearerToken);
    } catch (error) {
        context.res = {
            status: 500,
            body: `Failed to obtain OBO token: ${error.message}`
        };
        return;
    }
    // Initialize the Graph Client using the initGraphClient function defined above
    let client = initGraphClient(accessToken);
    // this is the search body to be used in the Microsft Graph Search API: https://learn.microsoft.com/en-us/graph/search-concept-files
    const requestBody = {
        requests: [
            {
                entityTypes: ['driveItem'],
                query: {
                    queryString: searchTerm
                },
                from: 0,
                // the below is set to summarize the top 10 search results from the Graph API, but can configure based on your documents. 
                size: 10
            }
        ]
    };

    try { 
        // Function to tokenize content (e.g., based on words).


key: _node_content
string_value:
{"id_": "46495823-25ea-47bf-89e2-1f6bb234a04e", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0bad2d8c-273e-43e2-b615-c2fef5fbfe4e", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "0bff53afb5fc2cd1b87081629e296d8f636618662e6925d2d8357e7df8ffa7c0", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "2c52bd69-5e7b-4196-bf53-a6f622b8ffef", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "79789c425d13ac78eb190bac032e217ffee3f3e60cf88fa87381551221c8e9e9", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "abab3bc3-dc35-4488-8074-5bf5c5ef1d04", "node_type": "1", "metadata": {}, "hash": "4285ee4fb199e3ef7d27569ec6aac3e35e01892efbd1ac4423a78556ac2ae142", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 14038, "end_char_idx": 19238, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
- If the file type is supported, it analyzes the content using getRelevantParts, which sends the text to OpenAI's model for extracting relevant information based on the query.

- It collects the analysis results and includes metadata like the document name and URL.

**Response**: The function sorts the results by relevance and sends them back in the HTTP response.

```javascript
module.exports = async function (context, req) {
    const query = req.query.query || (req.body && req.body.query);
    const searchTerm = req.query.searchTerm || (req.body && req.body.searchTerm);
    if (!req.headers.authorization) {
        context.res = {
            status: 400,
            body: 'Authorization header is missing'
        };
        return;
    }
    /// The below takes the token passed to the function, to use to get an OBO token.
    const bearerToken = req.headers.authorization.split(' ')[1];
    let accessToken;
    try {
        accessToken = await getOboToken(bearerToken);
    } catch (error) {
        context.res = {
            status: 500,
            body: `Failed to obtain OBO token: ${error.message}`
        };
        return;
    }
    // Initialize the Graph Client using the initGraphClient function defined above
    let client = initGraphClient(accessToken);
    // this is the search body to be used in the Microsft Graph Search API: https://learn.microsoft.com/en-us/graph/search-concept-files
    const requestBody = {
        requests: [
            {
                entityTypes: ['driveItem'],
                query: {
                    queryString: searchTerm
                },
                from: 0,
                // the below is set to summarize the top 10 search results from the Graph API, but can configure based on your documents. 
                size: 10
            }
        ]
    };

    try { 
        // Function to tokenize content (e.g., based on words). 
        const tokenizeContent = (content) => {
            return content.split(/\s+/);
        };

        // Function to break tokens into 10k token windows for gpt-4o-mini
        const breakIntoTokenWindows = (tokens) => {
            const tokenWindows = []
            const maxWindowTokens = 10000; // 10k tokens
            let startIndex = 0;

            while (startIndex < tokens.length) {
                const window = tokens.slice(startIndex, startIndex + maxWindowTokens);
                tokenWindows.push(window);
                startIndex += maxWindowTokens;
            }

            return tokenWindows;
        };
        // This is where we are doing the search
        const list = await client.api('/search/query').post(requestBody);

        const processList = async () => {
            // This will go through and for each search response, grab the contents of the file and summarize with gpt-4o-mini
            const results = [];

            await Promise.all(list.value[0].hitsContainers.map(async (container) => {
                for (const hit of container.hits) {
                    if (hit.resource["@odata.type"] === "#microsoft.graph.driveItem") {
                        const { name, id } = hit.resource;
                        // We use the below to grab the URL of the file to include in the response
                        const webUrl = hit.resource.webUrl.replace(/\s/g, "%20");
                        // The Microsoft Graph API ranks the reponses, so we use this to order it
                        const rank = hit.rank;
                        // The below is where the file lives
                        const driveId = hit.resource.parentReference.driveId;
                        const contents = await getDriveItemContent(client, driveId, id, name);
                        if (contents !== 'Unsupported File Type') {
                            // Tokenize content using function defined previously
                            const tokens = tokenizeContent(contents);

                            // Break tokens into 10k token windows
                            const tokenWindows = breakIntoTokenWindows(tokens);

                            // Process each token window and combine results
                            const relevantPartsPromises = tokenWindows.map(window => getRelevantParts(window.join(' '), query));
                            const relevantParts = await Promise.all(relevantPartsPromises);
                            const combinedResults = relevantParts.join('\n'); // Combine results

                            results.push({ name, webUrl, rank, contents: combinedResults });
                        } 
                        else {
                            results.push({ name, webUrl, rank, contents: 'Unsupported File Type' });
                        }
                    }
                }
            }));

            return results;
        };
        let results;
        if (list.value[0].hitsContainers[0].total == 0) {
            // Return no results found to the API if the Microsoft Graph API returns no results
            results = 'No results found';
        } else {
            // If the Microsoft Graph API does return results, then run processList to iterate through.


key: _node_content
string_value:
{"id_": "abab3bc3-dc35-4488-8074-5bf5c5ef1d04", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0bad2d8c-273e-43e2-b615-c2fef5fbfe4e", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "0bff53afb5fc2cd1b87081629e296d8f636618662e6925d2d8357e7df8ffa7c0", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "46495823-25ea-47bf-89e2-1f6bb234a04e", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "06499acc0aa9a5b19d2d70f013274e16876f971ee71744c8ad7c89afd3408cfb", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "9a1dac56-2e42-470f-8954-d3bcffe0985b", "node_type": "1", "metadata": {}, "hash": "81b5ea3f42ac50c6082230d37b203b4a8293be492fc29a8321e924bbc74fa81b", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 19251, "end_char_idx": 21083, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
results = await processList();
            results.sort((a, b) => a.rank - b.rank);
        }
        context.res = {
            status: 200,
            body: results
        };
    } catch (error) {
        context.res = {
            status: 500,
            body: `Error performing search or processing results: ${error.message}`,
        };
    }
};
```

### Customizations

Below are some potential areas to customize. 

- You can customize the GPT prompt to search again a certain amount of times if nothing is found.

- You can customize the code to only search through specific SharePoint sites or O365 Drives by customizing the search query. This will help focus the search and improve the retrieval. The function as setup now looks through all files the logged-in user can access.

- You could use gpt-4o instead of gpt-4o-mini. This would slightly increase the cost and latency, but you may get higher quality summarizations.

- You can customize the amount of files it searches through within the call to Microsoft Graph.


### Considerations

Note that all the same limitations of Actions apply here, with regards to returning 100K characters or less and the [45 second timeout](https://platform.openai.com/docs/actions/production/timeouts).


- This only works for text, not for images. With some additional code in the Azure Function, you could customize this by using GPT-4o to extract summarizations of images.

- This does not work for structured data. We recommend Solution 1 if structured data is a major part of your use case.

## ChatGPT Steps

### Custom GPT Instructions 

Once you've created a Custom GPT, copy the text below in the Instructions panel. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.


key: _node_content
string_value:
{"id_": "9a1dac56-2e42-470f-8954-d3bcffe0985b", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0bad2d8c-273e-43e2-b615-c2fef5fbfe4e", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "0bff53afb5fc2cd1b87081629e296d8f636618662e6925d2d8357e7df8ffa7c0", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "abab3bc3-dc35-4488-8074-5bf5c5ef1d04", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "01a224aae8edb27c52d00ba114df8c160ec82ef9c86c4469720ef262b3d32c74", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "932af17e-a54a-48a6-b990-b30d129dded4", "node_type": "1", "metadata": {}, "hash": "087d3578ad1060509da92b5592ca5dae883e732b59df56a47dbf6617393fdb31", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 19251, "end_char_idx": 23258, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
results = await processList();
            results.sort((a, b) => a.rank - b.rank);
        }
        context.res = {
            status: 200,
            body: results
        };
    } catch (error) {
        context.res = {
            status: 500,
            body: `Error performing search or processing results: ${error.message}`,
        };
    }
};
```

### Customizations

Below are some potential areas to customize. 

- You can customize the GPT prompt to search again a certain amount of times if nothing is found.

- You can customize the code to only search through specific SharePoint sites or O365 Drives by customizing the search query. This will help focus the search and improve the retrieval. The function as setup now looks through all files the logged-in user can access.

- You could use gpt-4o instead of gpt-4o-mini. This would slightly increase the cost and latency, but you may get higher quality summarizations.

- You can customize the amount of files it searches through within the call to Microsoft Graph.


### Considerations

Note that all the same limitations of Actions apply here, with regards to returning 100K characters or less and the [45 second timeout](https://platform.openai.com/docs/actions/production/timeouts).


- This only works for text, not for images. With some additional code in the Azure Function, you could customize this by using GPT-4o to extract summarizations of images.

- This does not work for structured data. We recommend Solution 1 if structured data is a major part of your use case.

## ChatGPT Steps

### Custom GPT Instructions 

Once you've created a Custom GPT, copy the text below in the Instructions panel. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.


```python
You are a Q&A helper that helps answer users questions. You have access to a documents repository through your API action. When a user asks a question, you pass in that question exactly as stated to the "query" parameter, and for the "searchTerm" you use a single keyword or term you think you should use for the search.

****

Scenario 1: There are answers

If your action returns results, then you take the results from the action and summarize concisely with the webUrl returned from the action. You answer the users question to the best of your knowledge from the action

****

Scenario 2: No results found

If the response you get from the action is "No results found", stop there and let the user know there were no results and that you are going to try a different search term, and explain why. You must always let the user know before conducting another search.

Example:

****

I found no results for "DEI". I am now going to try [insert term] because [insert explanation]

****

Then, try a different searchTerm that is similar to the one you tried before, with a single word. 

Try this three times. After the third time, then let the user know you did not find any relevant documents to answer the question, and to check SharePoint. Be sure to be explicit about what you are searching for at each step.

****

In either scenario, try to answer the user's question. If you cannot answer the user's question based on the knowledge you find, let the user know and ask them to go check the HR Docs in SharePoint. If the file is a CSV, XLSX, or XLS, you can tell the user to download the file using the link and re-upload to use Advanced Data Analysis.
```

### OpenAPI Schema 

Once you've created a Custom GPT, copy the text below in the Actions panel. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.

The below spec passes in the `query` parameter to inform the pre-processing and a `searchTerm` to find the right files in Microsoft Graph.
>Make sure to switch the function app name, function name and code based on link copied in screenshot above


key: _node_content
string_value:
{"id_": "932af17e-a54a-48a6-b990-b30d129dded4", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0bad2d8c-273e-43e2-b615-c2fef5fbfe4e", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "0bff53afb5fc2cd1b87081629e296d8f636618662e6925d2d8357e7df8ffa7c0", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "9a1dac56-2e42-470f-8954-d3bcffe0985b", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "00a91a6650e9d22a0145b8d36df74f132f355109bf3164e1eaf128f76945d98d", "class_name": "RelatedNodeInfo"}, "3": {"node_id": "7968d94b-dc76-44ce-ac74-46e2f9e474c6", "node_type": "1", "metadata": {}, "hash": "9da297155d9b7eccca24550bdaef40745cde3f4162cabb0cf922ba571d183b39", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 23261, "end_char_idx": 26896, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
```python
openapi: 3.1.0
info:
  title: SharePoint Search API
  description: API for searching SharePoint documents.
  version: 1.0.0
servers:
  - url: https://{your_function_app_name}.azurewebsites.net/api
    description: SharePoint Search API server
paths:
  /{your_function_name}?code={enter your specific endpoint id here}:
    post:
      operationId: searchSharePoint
      summary: Searches SharePoint for documents matching a query and term.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                query:
                  type: string
                  description: The full query to search for in SharePoint documents.
                searchTerm:
                  type: string
                  description: A specific term to search for within the documents.
      responses:
        '200':
          description: Search results
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    documentName:
                      type: string
                      description: The name of the document.
                    snippet:
                      type: string
                      description: A snippet from the document containing the search term.
                    url:
                      type: string
                      description: The URL to access the document.
```

## Authentication Instructions

Below are instructions on setting up authentication with this 3rd party application. Have questions? Check out [Getting Started Example](https://platform.openai.com/docs/actions/getting-started) to see how this step works in more detail.

*See above and on the [Azure Function cookbook](https://cookbook.openai.com/examples/chatgpt/gpt_actions_library/gpt_middleware_azure_function) for more detailed instructions on authentication.*

## FAQ & Troubleshooting

- Why are you using the Microsoft Graph API in your code instead of the [SharePoint API](https://learn.microsoft.com/en-us/sharepoint/dev/sp-add-ins/get-to-know-the-sharepoint-rest-service?tabs=csom)?

  - The SharePoint API is legacy - per the Microsoft documentation [here](https://learn.microsoft.com/en-us/sharepoint/dev/apis/sharepoint-rest-graph), “For SharePoint Online, innovation using a REST API against SharePoint is driven via the Microsoft Graph REST API's.” The Graph API gives us more flexibility, and the SharePoint API still runs into the same file issues listed in the [Why is this necessary instead of interacting with the Microsoft Graph API directly?](#why-is-this-necessary-instead-of-interacting-with-the-microsoft-api-directly) section.

- What types of files does this support?
    1. This supports all files listed in the documentation for the Convert File endpoint [_here_](https://learn.microsoft.com/en-us/graph/api/driveitem-get-content-format?view=graph-rest-1.0\&tabs=http). Specifically, it supports _pdf, doc, docx, odp, ods, odt, pot, potm, potx, pps, ppsx, ppsxm, ppt, pptm, pptx, rtf_.

    2. When a search result returns XLS, XLSX, or CSV, this prompts the user to download the file and re-upload to ask questions using Advanced Data Analysis. As stated above, we recommend solution 1 if structured data is part of your use case.

- Why do I need to request an OBO token?

  - When you try to use the same token to authenticate to the Graph API as the one you use to authenticate into the Azure Function, you get an “invalid audience” token.


key: _node_content
string_value:
{"id_": "7968d94b-dc76-44ce-ac74-46e2f9e474c6", "embedding": null, "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "excluded_embed_metadata_keys": ["url", "tokens", "retrieve_doc", "source"], "excluded_llm_metadata_keys": ["title", "tokens", "retrieve_doc", "source"], "relationships": {"1": {"node_id": "0bad2d8c-273e-43e2-b615-c2fef5fbfe4e", "node_type": "4", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "0bff53afb5fc2cd1b87081629e296d8f636618662e6925d2d8357e7df8ffa7c0", "class_name": "RelatedNodeInfo"}, "2": {"node_id": "932af17e-a54a-48a6-b990-b30d129dded4", "node_type": "1", "metadata": {"url": "https://github.com/openai/openai-cookbook/blob/main/examples/chatgpt/gpt_actions_library/gpt_action_sharepoint_text.ipynb", "title": "GPT Action Library: Sharepoint (Return as Document)", "tokens": 5886, "retrieve_doc": true, "source": "OpenAI_Cookbooks"}, "hash": "18f6776e6e1eecb908ef07f3dee85c19f314a20bf447ceaad8d70483a16484d9", "class_name": "RelatedNodeInfo"}}, "text": "", "mimetype": "text/plain", "start_char_idx": 25287, "end_char_idx": 27731, "text_template": "{metadata_str}\n\n{content}", "metadata_template": "{key}: {value}", "metadata_seperator": "\n", "class_name": "TextNode"}


key: chroma:document
string_value:
*

## FAQ & Troubleshooting

- Why are you using the Microsoft Graph API in your code instead of the [SharePoint API](https://learn.microsoft.com/en-us/sharepoint/dev/sp-add-ins/get-to-know-the-sharepoint-rest-service?tabs=csom)?

  - The SharePoint API is legacy - per the Microsoft documentation [here](https://learn.microsoft.com/en-us/sharepoint/dev/apis/sharepoint-rest-graph), “For SharePoint Online, innovation using a REST API against SharePoint is driven via the Microsoft Graph REST API's.” The Graph API gives us more flexibility, and the SharePoint API still runs into the same file issues listed in the [Why is this necessary instead of interacting with the Microsoft Graph API directly?](#why-is-this-necessary-instead-of-interacting-with-the-microsoft-api-directly) section.

- What types of files does this support?
    1. This supports all files listed in the documentation for the Convert File endpoint [_here_](https://learn.microsoft.com/en-us/graph/api/driveitem-get-content-format?view=graph-rest-1.0\&tabs=http). Specifically, it supports _pdf, doc, docx, odp, ods, odt, pot, potm, potx, pps, ppsx, ppsxm, ppt, pptm, pptx, rtf_.

    2. When a search result returns XLS, XLSX, or CSV, this prompts the user to download the file and re-upload to ask questions using Advanced Data Analysis. As stated above, we recommend solution 1 if structured data is part of your use case.

- Why do I need to request an OBO token?

  - When you try to use the same token to authenticate to the Graph API as the one you use to authenticate into the Azure Function, you get an “invalid audience” token. This is because the audience for the token can only be user\_impersonation.

  - To address this, the function requests a new token scoped to Files.Read.All within the app using the [On Behalf Of flow](https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-on-behalf-of-flow). This will inherit the permissions of the logged in user, meaning this function will only search through files the logged-in user has access to. 

  - We are purposefully requesting a new On Behalf Of token with each request, because Azure Function Apps are meant to be stateless. You could potentially integrate this with Azure Key Vault to store the secret and retrieve programmatically. 

*Are there integrations that you’d like us to prioritize? Are there errors in our integrations? File a PR or issue in our github, and we’ll take a look.*


