{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "f5c76c5f",
   "metadata": {},
   "source": [
    "# Groundedness Checking Plugins\n",
    "\n",
    "For the proper configuration settings and setup, please follow the steps outlined at the beginning of the [first](./00-getting-started.ipynb) getting started notebook.\n",
    "\n",
    "A well-known problem with large language models (LLMs) is that they make things up. These are sometimes called 'hallucinations' but a safer (and less anthropomorphic) term is 'ungrounded addition' - something in the text which cannot be firmly established. When attempting to establish whether or not something in an LLM response is 'true' we can either check for it in the supplied prompt (this is called 'narrow grounding') or use our general knowledge ('broad grounding'). Note that narrow grounding can lead to things being classified as 'true, but ungrounded.' For example \"I live in Switzerland\" is **not** _narrowly_ grounded in \"I live in Geneva\" even though it must be true (it **is** _broadly_ grounded).\n",
    "\n",
    "In this notebook we run a simple grounding pipeline, to see if a summary text has any ungrounded additions as compared to the original, and use this information to improve the summary text. This can be done in three stages:\n",
    "\n",
    "1. Make a list of the entities in the summary text\n",
    "1. Check to see if these entities appear in the original (grounding) text\n",
    "1. Remove the ungrounded entities from the summary text\n",
    "\n",
    "What is an 'entity' in this context? In its simplest form, it's a named object such as a person or place (so 'Dean' or 'Seattle'). However, the idea could be a _claim_ which relates concepts (such as 'Dean lives near Seattle'). In this notebook, we will keep to the simpler case of named objects.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60e2513c",
   "metadata": {},
   "source": [
    "Import Semantic Kernel SDK from pypi.org"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "19ce6d37",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Note: if using a virtual environment, do not run this cell\n",
    "%pip install -U semantic-kernel\n",
    "from semantic_kernel import __version__\n",
    "\n",
    "__version__"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "93e86ea5",
   "metadata": {},
   "source": [
    "Initial configuration for the notebook to run properly."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "47a5560b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Make sure paths are correct for the imports\n",
    "\n",
    "import os\n",
    "import sys\n",
    "\n",
    "notebook_dir = os.path.abspath(\"\")\n",
    "parent_dir = os.path.dirname(notebook_dir)\n",
    "grandparent_dir = os.path.dirname(parent_dir)\n",
    "\n",
    "\n",
    "sys.path.append(grandparent_dir)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "181f38db",
   "metadata": {},
   "source": [
    "### Configuring the Kernel\n",
    "\n",
    "Let's get started with the necessary configuration to run Semantic Kernel. For Notebooks, we require a `.env` file with the proper settings for the model you use. Create a new file named `.env` and place it in this directory. Copy the contents of the `.env.example` file from this directory and paste it into the `.env` file that you just created.\n",
    "\n",
    "**NOTE: Please make sure to include `GLOBAL_LLM_SERVICE` set to either OpenAI, AzureOpenAI, or HuggingFace in your .env file. If this setting is not included, the Service will default to AzureOpenAI.**\n",
    "\n",
    "#### Option 1: using OpenAI\n",
    "\n",
    "Add your [OpenAI Key](https://openai.com/product/) key to your `.env` file (org Id only if you have multiple orgs):\n",
    "\n",
    "```\n",
    "GLOBAL_LLM_SERVICE=\"OpenAI\"\n",
    "OPENAI_API_KEY=\"sk-...\"\n",
    "OPENAI_ORG_ID=\"\"\n",
    "OPENAI_CHAT_MODEL_ID=\"\"\n",
    "OPENAI_TEXT_MODEL_ID=\"\"\n",
    "OPENAI_EMBEDDING_MODEL_ID=\"\"\n",
    "```\n",
    "The names should match the names used in the `.env` file, as shown above.\n",
    "\n",
    "#### Option 2: using Azure OpenAI\n",
    "\n",
    "Add your [Azure Open AI Service key](https://learn.microsoft.com/azure/cognitive-services/openai/quickstart?pivots=programming-language-studio) settings to the `.env` file in the same folder:\n",
    "\n",
    "```\n",
    "GLOBAL_LLM_SERVICE=\"AzureOpenAI\"\n",
    "AZURE_OPENAI_API_KEY=\"...\"\n",
    "AZURE_OPENAI_ENDPOINT=\"https://...\"\n",
    "AZURE_OPENAI_CHAT_DEPLOYMENT_NAME=\"...\"\n",
    "AZURE_OPENAI_TEXT_DEPLOYMENT_NAME=\"...\"\n",
    "AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME=\"...\"\n",
    "AZURE_OPENAI_API_VERSION=\"...\"\n",
    "```\n",
    "The names should match the names used in the `.env` file, as shown above.\n",
    "\n",
    "As alternative to `AZURE_OPENAI_API_KEY`, it's possible to authenticate using `credential` parameter, more information here: [Azure Identity](https://learn.microsoft.com/en-us/python/api/overview/azure/identity-readme).\n",
    "\n",
    "In the following example, `AzureCliCredential` is used. To authenticate using Azure CLI:\n",
    "\n",
    "1. Install [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli).\n",
    "2. Run `az login` command in terminal and follow the authentication steps.\n",
    "\n",
    "For more advanced configuration, please follow the steps outlined in the [setup guide](./CONFIGURING_THE_KERNEL.md)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fadcfde4",
   "metadata": {},
   "source": [
    "Let us define our grounding text:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "23b26e2f",
   "metadata": {},
   "outputs": [],
   "source": [
    "grounding_text = \"\"\"I am by birth a Genevese, and my family is one of the most distinguished of that republic.\n",
    "My ancestors had been for many years counsellors and syndics, and my father had filled several public situations\n",
    "with honour and reputation. He was respected by all who knew him for his integrity and indefatigable attention\n",
    "to public business. He passed his younger days perpetually occupied by the affairs of his country; a variety\n",
    "of circumstances had prevented his marrying early, nor was it until the decline of life that he became a husband\n",
    "and the father of a family.\n",
    "\n",
    "As the circumstances of his marriage illustrate his character, I cannot refrain from relating them. One of his\n",
    "most intimate friends was a merchant who, from a flourishing state, fell, through numerous mischances, into poverty.\n",
    "This man, whose name was Beaufort, was of a proud and unbending disposition and could not bear to live in poverty\n",
    "and oblivion in the same country where he had formerly been distinguished for his rank and magnificence. Having\n",
    "paid his debts, therefore, in the most honourable manner, he retreated with his daughter to the town of Lucerne,\n",
    "where he lived unknown and in wretchedness. My father loved Beaufort with the truest friendship and was deeply\n",
    "grieved by his retreat in these unfortunate circumstances. He bitterly deplored the false pride which led his friend\n",
    "to a conduct so little worthy of the affection that united them. He lost no time in endeavouring to seek him out,\n",
    "with the hope of persuading him to begin the world again through his credit and assistance.\n",
    "\n",
    "Beaufort had taken effectual measures to conceal himself, and it was ten months before my father discovered his\n",
    "abode. Overjoyed at this discovery, he hastened to the house, which was situated in a mean street near the Reuss.\n",
    "But when he entered, misery and despair alone welcomed him. Beaufort had saved but a very small sum of money from\n",
    "the wreck of his fortunes, but it was sufficient to provide him with sustenance for some months, and in the meantime\n",
    "he hoped to procure some respectable employment in a merchant's house. The interval was, consequently, spent in\n",
    "inaction; his grief only became more deep and rankling when he had leisure for reflection, and at length it took\n",
    "so fast hold of his mind that at the end of three months he lay on a bed of sickness, incapable of any exertion.\n",
    "\n",
    "His daughter attended him with the greatest tenderness, but she saw with despair that their little fund was\n",
    "rapidly decreasing and that there was no other prospect of support. But Caroline Beaufort possessed a mind of an\n",
    "uncommon mould, and her courage rose to support her in her adversity. She procured plain work; she plaited straw\n",
    "and by various means contrived to earn a pittance scarcely sufficient to support life.\n",
    "\n",
    "Several months passed in this manner. Her father grew worse; her time was more entirely occupied in attending him;\n",
    "her means of subsistence decreased; and in the tenth month her father died in her arms, leaving her an orphan and\n",
    "a beggar. This last blow overcame her, and she knelt by Beaufort's coffin weeping bitterly, when my father entered\n",
    "the chamber. He came like a protecting spirit to the poor girl, who committed herself to his care; and after the\n",
    "interment of his friend he conducted her to Geneva and placed her under the protection of a relation. Two years\n",
    "after this event Caroline became his wife.\"\"\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4fd80b62",
   "metadata": {},
   "source": [
    "We will load our settings and get the LLM service to use for the notebook."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c2d4f01f",
   "metadata": {},
   "outputs": [],
   "source": [
    "from services import Service\n",
    "\n",
    "from samples.service_settings import ServiceSettings\n",
    "\n",
    "service_settings = ServiceSettings()\n",
    "\n",
    "# Select a service to use for this notebook (available services: OpenAI, AzureOpenAI, HuggingFace)\n",
    "selectedService = (\n",
    "    Service.AzureOpenAI\n",
    "    if service_settings.global_llm_service is None\n",
    "    else Service(service_settings.global_llm_service.lower())\n",
    ")\n",
    "print(f\"Using service type: {selectedService}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "723087dc",
   "metadata": {},
   "source": [
    "We now configure our Chat Completion service on the kernel."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e13d3519",
   "metadata": {},
   "outputs": [],
   "source": [
    "from semantic_kernel import Kernel\n",
    "from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion, OpenAIChatCompletion\n",
    "\n",
    "kernel = Kernel()\n",
    "\n",
    "service_id = None\n",
    "if selectedService == Service.OpenAI:\n",
    "    from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion\n",
    "\n",
    "    service_id = \"default\"\n",
    "    kernel.add_service(\n",
    "        OpenAIChatCompletion(\n",
    "            service_id=service_id,\n",
    "        ),\n",
    "    )\n",
    "elif selectedService == Service.AzureOpenAI:\n",
    "    from azure.identity import AzureCliCredential\n",
    "\n",
    "    from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion\n",
    "\n",
    "    service_id = \"default\"\n",
    "    kernel.add_service(\n",
    "        AzureChatCompletion(service_id=service_id, credential=AzureCliCredential()),\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0c65f786",
   "metadata": {},
   "source": [
    "## Import the Plugins\n",
    "\n",
    "We are going to be using the grounding plugin, to check its quality, and remove ungrounded additions:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "56ed7688",
   "metadata": {},
   "outputs": [],
   "source": [
    "# note: using plugins from the samples folder\n",
    "plugins_directory = \"../../../prompt_template_samples/\"\n",
    "\n",
    "groundingSemanticFunctions = kernel.add_plugin(parent_directory=plugins_directory, plugin_name=\"GroundingPlugin\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0d087993",
   "metadata": {},
   "source": [
    "We can also extract the individual semantic functions for our use:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "738eb0ec",
   "metadata": {},
   "outputs": [],
   "source": [
    "entity_extraction = groundingSemanticFunctions[\"ExtractEntities\"]\n",
    "reference_check = groundingSemanticFunctions[\"ReferenceCheckEntities\"]\n",
    "entity_excision = groundingSemanticFunctions[\"ExciseEntities\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e5bda16e",
   "metadata": {},
   "source": [
    "## Calling Individual Semantic Functions\n",
    "\n",
    "We will start by calling the individual grounding functions in turn, to show their use. For this we need to create a same summary text:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c9a872f6",
   "metadata": {},
   "outputs": [],
   "source": [
    "summary_text = \"\"\"\n",
    "My father, a respected resident of Milan, was a close friend of a merchant named Beaufort who, after a series of\n",
    "misfortunes, moved to Zurich in poverty. My father was upset by his friend's troubles and sought him out,\n",
    "finding him in a mean street. Beaufort had saved a small sum of money, but it was not enough to support him and\n",
    "his daughter, Mary. Mary procured work to eke out a living, but after ten months her father died, leaving\n",
    "her a beggar. My father came to her aid and two years later they married when they visited Rome.\n",
    "\"\"\"\n",
    "\n",
    "summary_text = summary_text.replace(\"\\n\", \" \").replace(\"  \", \" \")\n",
    "print(summary_text)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a41eccb",
   "metadata": {},
   "source": [
    "Some things to note:\n",
    "\n",
    "- The implied residence of Geneva has been changed to Milan\n",
    "- Lucerne has been changed to Zurich\n",
    "- Caroline has been renamed as Mary\n",
    "- A reference to Rome has been added\n",
    "\n",
    "The grounding plugin has three stages:\n",
    "\n",
    "1. Extract entities from a summary text\n",
    "2. Perform a reference check against the grounding text\n",
    "3. Excise any entities which failed the reference check from the summary\n",
    "\n",
    "Now, let us start calling individual semantic functions.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "071c05e4",
   "metadata": {},
   "source": [
    "### Extracting the Entities\n",
    "\n",
    "The first function we need is entity extraction. We are going to take our summary text, and get a list of entities found within it. For this we use `entity_extraction()`:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c1d4b7ff",
   "metadata": {},
   "outputs": [],
   "source": [
    "extraction_result = await kernel.invoke(\n",
    "    entity_extraction,\n",
    "    input=summary_text,\n",
    "    topic=\"people and places\",\n",
    "    example_entities=\"John, Jane, mother, brother, Paris, Rome\",\n",
    ")\n",
    "\n",
    "print(extraction_result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b93c661f",
   "metadata": {},
   "source": [
    "So we have our list of entities in the summary\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "958ad1ff",
   "metadata": {},
   "source": [
    "### Performing the reference check\n",
    "\n",
    "We now use the grounding text to see if the entities we found are grounded. We start by adding the grounding text to our context:\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "894e38d7",
   "metadata": {},
   "source": [
    "With this in place, we can run the reference checking function. This will use both the entity list in the input, and the `reference_context` in the context object itself:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d240d669",
   "metadata": {},
   "outputs": [],
   "source": [
    "grounding_result = await kernel.invoke(\n",
    "    reference_check, input=str(extraction_result.value), reference_context=grounding_text\n",
    ")\n",
    "\n",
    "print(grounding_result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9a83c66f",
   "metadata": {},
   "source": [
    "So we now have a list of ungrounded entities (of course, this list may not be well grounded itself). Let us store this in the context:\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "35c1c329",
   "metadata": {},
   "source": [
    "### Excising the ungrounded entities\n",
    "\n",
    "Finally we can remove the ungrounded entities from the summary text:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "db82d97d",
   "metadata": {},
   "outputs": [],
   "source": [
    "excision_result = await kernel.invoke(\n",
    "    entity_excision, input=summary_text, ungrounded_entities=str(grounding_result.value)\n",
    ")\n",
    "\n",
    "print(excision_result)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
