{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Using Postgres as memory\n",
    "\n",
    "This notebook shows how to use Postgres as a memory store in Semantic Kernel.\n",
    "\n",
    "The code below pulls the most recent papers from [ArviX](https://arxiv.org/), creates embeddings from the paper abstracts, and stores them in a Postgres database.\n",
    "\n",
    "In the future, we can use the Postgres vector store to search the database for similar papers based on the embeddings - stay tuned!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import textwrap\n",
    "import xml.etree.ElementTree as ET\n",
    "from dataclasses import dataclass\n",
    "from datetime import datetime\n",
    "from typing import Annotated, Any\n",
    "\n",
    "import requests\n",
    "\n",
    "from semantic_kernel import Kernel\n",
    "from semantic_kernel.connectors.ai import FunctionChoiceBehavior\n",
    "from semantic_kernel.connectors.ai.open_ai import (\n",
    "    AzureChatCompletion,\n",
    "    AzureChatPromptExecutionSettings,\n",
    "    AzureTextEmbedding,\n",
    "    OpenAITextEmbedding,\n",
    ")\n",
    "from semantic_kernel.connectors.postgres import PostgresCollection\n",
    "from semantic_kernel.contents import ChatHistory\n",
    "from semantic_kernel.data.vector import (\n",
    "    DistanceFunction,\n",
    "    IndexKind,\n",
    "    VectorStoreField,\n",
    "    vectorstoremodel,\n",
    ")\n",
    "from semantic_kernel.functions import KernelParameterMetadata\n",
    "from semantic_kernel.functions.kernel_arguments import KernelArguments"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Set up your environment\n",
    "\n",
    "You'll need to set up your environment to provide connection information to Postgres, as well as OpenAI or Azure OpenAI.\n",
    "\n",
    "To do this, copy the `.env.example` file to `.env` and fill in the necessary information.\n",
    "\n",
    "__Note__: If you're using VSCode to execute the notebook, the settings in `.env` in the root of the repository will be picked up automatically.\n",
    "\n",
    "### Postgres configuration\n",
    "\n",
    "You'll need to provide a connection string to a Postgres database. You can use a local Postgres instance, or a cloud-hosted one.\n",
    "You can provide a connection string, or provide environment variables with the connection information. See the .env.example file for `POSTGRES_` settings.\n",
    "\n",
    "#### Using Docker\n",
    "\n",
    "You can also use docker to bring up a Postgres instance by following the steps below:\n",
    "\n",
    "Create an `init.sql` that has the following:\n",
    "\n",
    "```sql\n",
    "CREATE EXTENSION IF NOT EXISTS vector;\n",
    "```\n",
    "\n",
    "Now you can start a postgres instance with the following:\n",
    "\n",
    "```\n",
    "docker pull pgvector/pgvector:pg16\n",
    "docker run --rm -it --name pgvector -p 5432:5432 -v ./init.sql:/docker-entrypoint-initdb.d/init.sql -e POSTGRES_PASSWORD=example pgvector/pgvector:pg16\n",
    "```\n",
    "\n",
    "_Note_: Use `.\\init.sql` on Windows and `./init.sql` on WSL or Linux/Mac.\n",
    "\n",
    "Then you could use the connection string:\n",
    "\n",
    "```\n",
    "POSTGRES_CONNECTION_STRING=\"host=localhost port=5432 dbname=postgres user=postgres password=example\"\n",
    "```\n",
    "\n",
    "### OpenAI configuration\n",
    "\n",
    "You can either use OpenAI or Azure OpenAI APIs. You provide the API key and other configuration in the `.env` file. Set either the `OPENAI_` or `AZURE_OPENAI_` settings.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Path to the environment file\n",
    "env_file_path = \".env\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here we set some additional configuration."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# -- ArXiv settings --\n",
    "\n",
    "# The search term to use when searching for papers on arXiv. All metadata fields for the papers are searched.\n",
    "SEARCH_TERM = \"RAG\"\n",
    "\n",
    "# The category of papers to search for on arXiv. See https://arxiv.org/category_taxonomy for a list of categories.\n",
    "ARVIX_CATEGORY = \"cs.AI\"\n",
    "\n",
    "# The maximum number of papers to search for on arXiv.\n",
    "MAX_RESULTS = 300\n",
    "\n",
    "# -- OpenAI settings --\n",
    "\n",
    "# Set this flag to False to use the OpenAI API instead of Azure OpenAI\n",
    "USE_AZURE_OPENAI = True"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here we define a vector store model. This model defines the table and column names for storing the embeddings. We use the `@vectorstoremodel` decorator to tell Semantic Kernel to create a vector store definition from the model. The VectorStoreRecordField annotations define the fields that will be stored in the database, including key and vector fields."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@vectorstoremodel\n",
    "@dataclass\n",
    "class ArxivPaper:\n",
    "    id: Annotated[str, VectorStoreField(\"key\")]\n",
    "    title: Annotated[str, VectorStoreField(\"data\")]\n",
    "    abstract: Annotated[str, VectorStoreField(\"data\")]\n",
    "    published: Annotated[datetime, VectorStoreField(\"data\")]\n",
    "    authors: Annotated[list[str], VectorStoreField(\"data\")]\n",
    "    link: Annotated[str | None, VectorStoreField(\"data\")]\n",
    "    abstract_vector: Annotated[\n",
    "        list[float] | str | None,\n",
    "        VectorStoreField(\n",
    "            \"vector\",\n",
    "            index_kind=IndexKind.HNSW,\n",
    "            dimensions=1536,\n",
    "            distance_function=DistanceFunction.COSINE_DISTANCE,\n",
    "        ),\n",
    "    ] = None\n",
    "\n",
    "    def __post_init__(self):\n",
    "        if self.abstract_vector is None:\n",
    "            self.abstract_vector = self.abstract\n",
    "\n",
    "    @classmethod\n",
    "    def from_arxiv_info(cls, arxiv_info: dict[str, Any]) -> \"ArxivPaper\":\n",
    "        return cls(\n",
    "            id=arxiv_info[\"id\"],\n",
    "            title=arxiv_info[\"title\"].replace(\"\\n  \", \" \"),\n",
    "            abstract=arxiv_info[\"abstract\"].replace(\"\\n  \", \" \"),\n",
    "            published=arxiv_info[\"published\"],\n",
    "            authors=arxiv_info[\"authors\"],\n",
    "            link=arxiv_info[\"link\"],\n",
    "        )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Below is a function that queries the ArviX API for the most recent papers based on our search query and category."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "def query_arxiv(search_query: str, category: str = \"cs.AI\", max_results: int = 10) -> list[dict[str, Any]]:\n",
    "    \"\"\"\n",
    "    Query the ArXiv API and return a list of dictionaries with relevant metadata for each paper.\n",
    "\n",
    "    Args:\n",
    "        search_query: The search term or topic to query for.\n",
    "        category: The category to restrict the search to (default is \"cs.AI\").\n",
    "        See https://arxiv.org/category_taxonomy for a list of categories.\n",
    "        max_results: Maximum number of results to retrieve (default is 10).\n",
    "    \"\"\"\n",
    "    response = requests.get(\n",
    "        \"http://export.arxiv.org/api/query?\"\n",
    "        f\"search_query=all:%22{search_query.replace(' ', '+')}%22\"\n",
    "        f\"+AND+cat:{category}&start=0&max_results={max_results}&sortBy=lastUpdatedDate&sortOrder=descending\"\n",
    "    )\n",
    "\n",
    "    root = ET.fromstring(response.content)\n",
    "    ns = {\"atom\": \"http://www.w3.org/2005/Atom\"}\n",
    "\n",
    "    return [\n",
    "        {\n",
    "            \"id\": entry.find(\"atom:id\", ns).text.split(\"/\")[-1],\n",
    "            \"title\": entry.find(\"atom:title\", ns).text,\n",
    "            \"abstract\": entry.find(\"atom:summary\", ns).text,\n",
    "            \"published\": entry.find(\"atom:published\", ns).text,\n",
    "            \"link\": entry.find(\"atom:id\", ns).text,\n",
    "            \"authors\": [author.find(\"atom:name\", ns).text for author in entry.findall(\"atom:author\", ns)],\n",
    "            \"categories\": [category.get(\"term\") for category in entry.findall(\"atom:category\", ns)],\n",
    "            \"pdf_link\": next(\n",
    "                (link_tag.get(\"href\") for link_tag in entry.findall(\"atom:link\", ns) if link_tag.get(\"title\") == \"pdf\"),\n",
    "                None,\n",
    "            ),\n",
    "        }\n",
    "        for entry in root.findall(\"atom:entry\", ns)\n",
    "    ]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We use this function to query papers and store them in memory as our model types."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Found 300 papers on 'RAG'\n"
     ]
    }
   ],
   "source": [
    "arxiv_papers: list[ArxivPaper] = [\n",
    "    ArxivPaper.from_arxiv_info(paper)\n",
    "    for paper in query_arxiv(SEARCH_TERM, category=ARVIX_CATEGORY, max_results=MAX_RESULTS)\n",
    "]\n",
    "\n",
    "print(f\"Found {len(arxiv_papers)} papers on '{SEARCH_TERM}'\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Create a `PostgresCollection`, which represents the table in Postgres where we will store the paper information and embeddings."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if USE_AZURE_OPENAI:\n",
    "    text_embedding = AzureTextEmbedding(service_id=\"embedding\", env_file_path=env_file_path)\n",
    "else:\n",
    "    text_embedding = OpenAITextEmbedding(service_id=\"embedding\", env_file_path=env_file_path)\n",
    "collection = PostgresCollection[str, ArxivPaper](\n",
    "    collection_name=\"arxiv_records\",\n",
    "    record_type=ArxivPaper,\n",
    "    env_file_path=env_file_path,\n",
    "    embedding_generator=text_embedding,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now that the models have embeddings, we can write them into the Postgres database."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "async with collection:\n",
    "    await collection.ensure_collection_exists()\n",
    "    keys = await collection.upsert(arxiv_papers)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here we retrieve the first few models from the database and print out their information."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "# Engineering LLM Powered Multi-agent Framework for Autonomous CloudOps\n",
      "\n",
      "Abstract:   Cloud Operations (CloudOps) is a rapidly growing field focused on the\n",
      "automated management and optimization of cloud infrastructure which is essential\n",
      "for organizations navigating increasingly complex cloud environments. MontyCloud\n",
      "Inc. is one of the major companies in the CloudOps domain that leverages\n",
      "autonomous bots to manage cloud compliance, security, and continuous operations.\n",
      "To make the platform more accessible and effective to the customers, we\n",
      "leveraged the use of GenAI. Developing a GenAI-based solution for autonomous\n",
      "CloudOps for the existing MontyCloud system presented us with various challenges\n",
      "such as i) diverse data sources; ii) orchestration of multiple processes; and\n",
      "iii) handling complex workflows to automate routine tasks. To this end, we\n",
      "developed MOYA, a multi-agent framework that leverages GenAI and balances\n",
      "autonomy with the necessary human control. This framework integrates various\n",
      "internal and external systems and is optimized for factors like task\n",
      "orchestration, security, and error mitigation while producing accurate,\n",
      "reliable, and relevant insights by utilizing Retrieval Augmented Generation\n",
      "(RAG). Evaluations of our multi-agent system with the help of practitioners as\n",
      "well as using automated checks demonstrate enhanced accuracy, responsiveness,\n",
      "and effectiveness over non-agentic approaches across complex workflows.\n",
      "Published: 2025-01-14 16:30:10\n",
      "Link: http://arxiv.org/abs/2501.08243v1\n",
      "PDF Link: http://arxiv.org/abs/2501.08243v1\n",
      "Authors: Kannan Parthasarathy, Karthik Vaidhyanathan, Rudra Dhar, Venkat Krishnamachari, Basil Muhammed, Adyansh Kakran, Sreemaee Akshathala, Shrikara Arun, Sumant Dubey, Mohan Veerubhotla, Amey Karan\n",
      "Embedding: [ 0.01063822  0.02977918  0.04532182 ... -0.00264323  0.00081101\n",
      "  0.01491571]\n",
      "\n",
      "\n",
      "# Eliciting In-context Retrieval and Reasoning for Long-context Large Language Models\n",
      "\n",
      "Abstract:   Recent advancements in long-context language models (LCLMs) promise to\n",
      "transform Retrieval-Augmented Generation (RAG) by simplifying pipelines. With\n",
      "their expanded context windows, LCLMs can process entire knowledge bases and\n",
      "perform retrieval and reasoning directly -- a capability we define as In-Context\n",
      "Retrieval and Reasoning (ICR^2). However, existing benchmarks like LOFT often\n",
      "overestimate LCLM performance by providing overly simplified contexts. To\n",
      "address this, we introduce ICR^2, a benchmark that evaluates LCLMs in more\n",
      "realistic scenarios by including confounding passages retrieved with strong\n",
      "retrievers. We then propose three methods to enhance LCLM performance: (1)\n",
      "retrieve-then-generate fine-tuning, (2) retrieval-attention-probing, which uses\n",
      "attention heads to filter and de-noise long contexts during decoding, and (3)\n",
      "joint retrieval head training alongside the generation head. Our evaluation of\n",
      "five well-known LCLMs on LOFT and ICR^2 demonstrates significant gains with our\n",
      "best approach applied to Mistral-7B: +17 and +15 points by Exact Match on LOFT,\n",
      "and +13 and +2 points on ICR^2, compared to vanilla RAG and supervised fine-\n",
      "tuning, respectively. It even outperforms GPT-4-Turbo on most tasks despite\n",
      "being a much smaller model.\n",
      "Published: 2025-01-14 16:38:33\n",
      "Link: http://arxiv.org/abs/2501.08248v1\n",
      "PDF Link: http://arxiv.org/abs/2501.08248v1\n",
      "Authors: Yifu Qiu, Varun Embar, Yizhe Zhang, Navdeep Jaitly, Shay B. Cohen, Benjamin Han\n",
      "Embedding: [-0.01305697  0.01166064  0.06267344 ... -0.01627254  0.00974741\n",
      " -0.00573298]\n",
      "\n",
      "\n",
      "# ADAM-1: AI and Bioinformatics for Alzheimer's Detection and Microbiome-Clinical Data Integrations\n",
      "\n",
      "Abstract:   The Alzheimer's Disease Analysis Model Generation 1 (ADAM) is a multi-agent\n",
      "large language model (LLM) framework designed to integrate and analyze multi-\n",
      "modal data, including microbiome profiles, clinical datasets, and external\n",
      "knowledge bases, to enhance the understanding and detection of Alzheimer's\n",
      "disease (AD). By leveraging retrieval-augmented generation (RAG) techniques\n",
      "along with its multi-agent architecture, ADAM-1 synthesizes insights from\n",
      "diverse data sources and contextualizes findings using literature-driven\n",
      "evidence. Comparative evaluation against XGBoost revealed similar mean F1 scores\n",
      "but significantly reduced variance for ADAM-1, highlighting its robustness and\n",
      "consistency, particularly in small laboratory datasets. While currently tailored\n",
      "for binary classification tasks, future iterations aim to incorporate additional\n",
      "data modalities, such as neuroimaging and biomarkers, to broaden the scalability\n",
      "and applicability for Alzheimer's research and diagnostics.\n",
      "Published: 2025-01-14 18:56:33\n",
      "Link: http://arxiv.org/abs/2501.08324v1\n",
      "PDF Link: http://arxiv.org/abs/2501.08324v1\n",
      "Authors: Ziyuan Huang, Vishaldeep Kaur Sekhon, Ouyang Guo, Mark Newman, Roozbeh Sadeghian, Maria L. Vaida, Cynthia Jo, Doyle Ward, Vanni Bucci, John P. Haran\n",
      "Embedding: [ 0.03896349  0.00422515  0.05525447 ...  0.03374933 -0.01468264\n",
      "  0.01850895]\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "async with collection:\n",
    "    results = await collection.get(keys[:3])\n",
    "    if results:\n",
    "        for result in results:\n",
    "            print(f\"# {result.title}\")\n",
    "            print()\n",
    "            wrapped_abstract = textwrap.fill(result.abstract, width=80)\n",
    "            print(f\"Abstract: {wrapped_abstract}\")\n",
    "            print(f\"Published: {result.published}\")\n",
    "            print(f\"Link: {result.link}\")\n",
    "            print(f\"PDF Link: {result.link}\")\n",
    "            print(f\"Authors: {', '.join(result.authors)}\")\n",
    "            print(f\"Embedding: {result.abstract_vector}\")\n",
    "            print()\n",
    "            print()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `VectorStoreTextSearch` object gives us the ability to retrieve semantically similar documents directly from a prompt.\n",
    "Here we search for the top 5 ArXiV abstracts in our database similar to the query about chunking strategies in RAG applications:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Found 5 results for query.\n",
      "Advanced ingestion process powered by LLM parsing for RAG system: 0.38676463602221456\n",
      "StructRAG: Boosting Knowledge Intensive Reasoning of LLMs via Inference-time Hybrid Information Structurization: 0.39733734194342085\n",
      "UDA: A Benchmark Suite for Retrieval Augmented Generation in Real-world Document Analysis: 0.3981809737466562\n",
      "R^2AG: Incorporating Retrieval Information into Retrieval Augmented Generation: 0.4134050114864055\n",
      "Enhancing Retrieval-Augmented Generation: A Study of Best Practices: 0.4144733752075731\n"
     ]
    }
   ],
   "source": [
    "query = \"What are good chunking strategies to use for unstructured text in Retrieval-Augmented Generation applications?\"\n",
    "\n",
    "async with collection:\n",
    "    search_results = await collection.search(query, top=5, include_total_count=True)\n",
    "    print(f\"Found {search_results.total_count} results for query.\")\n",
    "    async for search_result in search_results.results:\n",
    "        title = search_result.record.title\n",
    "        score = search_result.score\n",
    "        print(f\"{title}: {score}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can enable chat completion to utilize the text search by creating a kernel function for searching the database..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "kernel = Kernel()\n",
    "plugin = kernel.add_functions(\n",
    "    plugin_name=\"arxiv_plugin\",\n",
    "    functions=[\n",
    "        collection.create_search_function(\n",
    "            # The default parameters match the parameters of the VectorSearchOptions class.\n",
    "            description=\"Searches for ArXiv papers that are related to the query.\",\n",
    "            parameters=[\n",
    "                KernelParameterMetadata(\n",
    "                    name=\"query\", description=\"What to search for.\", type=\"str\", is_required=True, type_object=str\n",
    "                ),\n",
    "                KernelParameterMetadata(\n",
    "                    name=\"top\",\n",
    "                    description=\"Number of results to return.\",\n",
    "                    type=\"int\",\n",
    "                    default_value=2,\n",
    "                    type_object=int,\n",
    "                ),\n",
    "            ],\n",
    "        ),\n",
    "    ],\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "...and then setting up a chat completions service that uses `FunctionChoiceBehavior.Auto` to automatically call the search function when appropriate to the users query. We also create the chat function that will be invoked by the kernel."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create the chat completion service. This requires an Azure OpenAI completions model deployment and configuration.\n",
    "chat_completion = AzureChatCompletion(service_id=\"completions\")\n",
    "kernel.add_service(chat_completion)\n",
    "\n",
    "# Now we create the chat function that will use the chat service.\n",
    "chat_function = kernel.add_function(\n",
    "    prompt=\"{{$chat_history}}{{$user_input}}\",\n",
    "    plugin_name=\"ChatBot\",\n",
    "    function_name=\"Chat\",\n",
    ")\n",
    "\n",
    "# we set the function choice to Auto, so that the LLM can choose the correct function to call.\n",
    "# and we exclude the ChatBot plugin, so that it does not call itself.\n",
    "execution_settings = AzureChatPromptExecutionSettings(\n",
    "    function_choice_behavior=FunctionChoiceBehavior.Auto(filters={\"excluded_plugins\": [\"ChatBot\"]}),\n",
    "    service_id=\"chat\",\n",
    "    max_tokens=7000,\n",
    "    temperature=0.7,\n",
    "    top_p=0.8,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here we create a chat history with a system message and some initial context:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "history = ChatHistory()\n",
    "system_message = \"\"\"\n",
    "You are a chat bot. Your name is Archie and\n",
    "you have one goal: help people find answers\n",
    "to technical questions by relying on the latest\n",
    "research papers published on ArXiv.\n",
    "You communicate effectively in the style of a helpful librarian. \n",
    "You always make sure to include the\n",
    "ArXiV paper references in your responses.\n",
    "If you cannot find the answer in the papers,\n",
    "you will let the user know, but also provide the papers\n",
    "you did find to be most relevant. If the abstract of the \n",
    "paper does not specifically reference the user's inquiry,\n",
    "but you believe it might be relevant, you can still include it\n",
    "BUT you must make sure to mention that the paper might not directly\n",
    "address the user's inquiry. Make certain that the papers you link are\n",
    "from a specific search result.\n",
    "\"\"\"\n",
    "history.add_system_message(system_message)\n",
    "history.add_user_message(\"Hi there, who are you?\")\n",
    "history.add_assistant_message(\n",
    "    \"I am Archie, the ArXiV chat bot. I'm here to help you find the latest research papers from ArXiv that relate to your inquiries.\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can now invoke the chat function via the Kernel to get chat completions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "arguments = KernelArguments(\n",
    "    user_input=query,\n",
    "    chat_history=history,\n",
    "    settings=execution_settings,\n",
    ")\n",
    "\n",
    "result = await kernel.invoke(chat_function, arguments=arguments)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Printing the result shows that the chat completion service used our text search to locate relevant ArXiV papers based on the query:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Archie:>\n",
      "What an excellent and timely question! Chunking strategies for unstructured text are\n",
      "critical for optimizing Retrieval-Augmented Generation (RAG) systems since they\n",
      "significantly affect how effectively a RAG model can retrieve and generate contextually\n",
      "relevant information. Let me consult the latest papers on this topic from ArXiv and\n",
      "provide you with relevant insights.\n",
      "---\n",
      "Here are some recent papers that dive into chunking strategies or similar concepts for\n",
      "retrieval-augmented frameworks:\n",
      "1. **\"Post-training optimization of retrieval-augmented generation models\"**\n",
      "   *Authors*: Vibhor Agarwal et al.\n",
      "   *Abstract*: While the paper discusses optimization strategies for retrieval-augmented\n",
      "generation models, there is a discussion on handling unstructured text that could apply to\n",
      "chunking methodologies. Chunking isn't always explicitly mentioned as \"chunking\" but may\n",
      "be referred to in contexts like splitting data for retrieval.\n",
      "   *ArXiv link*: [arXiv:2308.10701](https://arxiv.org/abs/2308.10701)\n",
      "   *Note*: This paper may not focus entirely on chunking strategies but might discuss\n",
      "relevant downstream considerations. It could still provide a foundation for you to explore\n",
      "how chunking integrates with retrievers.\n",
      "2. **\"Beyond Text: Retrieval-Augmented Reranking for Open-Domain Tasks\"**\n",
      "   *Authors*: Younggyo Seo et al.\n",
      "   *Abstract*: Although primarily focused on retrieval augmentation for reranking, there\n",
      "are reflections on how document structure impacts task performance. Chunking unstructured\n",
      "text to improve retrievability for such tasks could indirectly relate to this work.\n",
      "   *ArXiv link*: [arXiv:2310.03714](https://arxiv.org/abs/2310.03714)\n",
      "3. **\"ALMA: Alignment of Generative and Retrieval Models for Long Documents\"**\n",
      "   *Authors*: Yao Fu et al.\n",
      "   *Abstract excerpt*: \"Our approach is designed to handle retrieval and generation for\n",
      "long documents by aligning the retrieval and generation models more effectively.\"\n",
      "Strategies to divide and process long documents into smaller chunks for efficient\n",
      "alignment are explicitly discussed. A focus on handling unstructured long-form content\n",
      "makes this paper highly relevant.\n",
      "   *ArXiv link*: [arXiv:2308.05467](https://arxiv.org/abs/2308.05467)\n",
      "4. **\"Enhancing Context-aware Question Generation with Multi-modal Knowledge\"**\n",
      "   *Authors*: Jialong Han et al.\n",
      "   *Abstract excerpt*: \"Proposed techniques focus on improving retrievals through better\n",
      "division of available knowledge.\" It doesn’t focus solely on text chunking in the RAG\n",
      "framework but might be interesting since contextual awareness often relates to\n",
      "preprocessing unstructured input into structured chunks.\n",
      "   *ArXiv link*: [arXiv:2307.12345](https://arxiv.org/abs/2307.12345)\n",
      "---\n",
      "### Practical Approaches Discussed in Literature:\n",
      "From my broad understanding of RAG systems and some of the details in these papers, here\n",
      "are common chunking strategies discussed in the research community:\n",
      "1. **Sliding Window Approach**: Divide the text into overlapping chunks of fixed lengths\n",
      "(e.g., 512 tokens with an overlap of 128 tokens). This helps ensure no important context\n",
      "is left behind when chunks are created.\n",
      "\n",
      "2. **Semantic Chunking**: Use sentence embeddings or clustering techniques (e.g., via Bi-\n",
      "Encoders or Sentence Transformers) to ensure chunks align semantically rather than naively\n",
      "by token count.\n",
      "3. **Dynamic Partitioning**: Implement chunking based on higher-order structure in the\n",
      "text, such as splitting at sentence boundaries, paragraph breaks, or logical sections.\n",
      "4. **Content-aware Chunking**: Experiment with LLMs to pre-identify contextual relevance\n",
      "of different parts of the text and chunk accordingly.\n",
      "---\n",
      "If you'd like, I can search more specifically on a sub-part of chunking strategies or\n",
      "related RAG optimizations. Let me know!\n"
     ]
    }
   ],
   "source": [
    "def wrap_text(text, width=90):\n",
    "    paragraphs = text.split(\"\\n\\n\")  # Split the text into paragraphs\n",
    "    wrapped_paragraphs = [\n",
    "        \"\\n\".join(textwrap.fill(part, width=width) for paragraph in paragraphs for part in paragraph.split(\"\\n\"))\n",
    "    ]  # Wrap each paragraph, split by newlines\n",
    "    return \"\\n\\n\".join(wrapped_paragraphs)  # Join the wrapped paragraphs back together\n",
    "\n",
    "\n",
    "print(f\"Archie:>\\n{wrap_text(str(result))}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.15"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
