{ "cells": [ { "cell_type": "markdown", "id": "04815d1b-44ee-4bd3-878e-fa0c3bf9fa7f", "metadata": { "tags": [] }, "source": [ "# LangChain QA Panel App" ] }, { "cell_type": "code", "execution_count": 1, "id": "a181568b-9cde-4a55-a853-4d2a41dbfdad", "metadata": { "tags": [] }, "outputs": [], "source": [ "#!pip install langchain openai chromadb tiktoken pypdf panel" ] }, { "cell_type": "code", "execution_count": 2, "id": "9a464409-d064-4766-a9cb-5119f6c4b8f5", "metadata": { "tags": [] }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/Users/romainfouilland/code/envs/iamu2/lib/python3.9/site-packages/urllib3/__init__.py:34: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020\n", " warnings.warn(\n" ] } ], "source": [ "import os \n", "from langchain.chains import RetrievalQA\n", "from langchain.llms import OpenAI\n", "from langchain.document_loaders import TextLoader\n", "from langchain.document_loaders import PyPDFLoader\n", "from langchain.indexes import VectorstoreIndexCreator\n", "from langchain.text_splitter import CharacterTextSplitter\n", "from langchain.embeddings import OpenAIEmbeddings\n", "from langchain.vectorstores import Chroma\n", "import panel as pn\n", "import tempfile" ] }, { "cell_type": "code", "execution_count": 3, "id": "965a6847-1ae6-4a31-9bee-22aed67c4b48", "metadata": {}, "outputs": [], "source": [ "# Needed as panel serve was not finding my utils.py\n", "# => Copy paste its content here\n", "# LLM\n", "# Ollama for local tests\n", "from langchain.llms import Ollama\n", "from langchain.callbacks.manager import CallbackManager\n", "from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler\n", "from langchain.llms import Ollama\n", "# Ref.: https://mistral.ai/news/mixtral-of-experts/#instructed-models\n", "# Q5_K_M quantzation flavor for best quality/recommended tradeoff (memory is no problem here)\n", "# Ref.: https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.2-GGUF#provided-files\n", "MISTRAL = \"mistral:7b-instruct-v0.2-q5_K_M\"\n", "# Q4_K quantization flavor for best memory/quality/recommended tradeoff\n", "# Ref.: https://huggingface.co/TheBloke/Mixtral-8x7B-Instruct-v0.1-GGUF#provided-files\n", "# mixtral:8x7b-instruct-v0.1-q4_K_M was sadly still too big for my Mac\n", "MIXTRAL = \"mixtral:8x7b-instruct-v0.1-q3_K_L\"\n", "# Llama2 13B \n", "# Ref.: https://huggingface.co/TheBloke/Llama-2-13B-GGUF\n", "LLAMA2 = \"llama2:13b-chat-q5_K_M\"\n", "mistral = Ollama(\n", " model=MISTRAL,\n", " callback_manager=CallbackManager([StreamingStdOutCallbackHandler()]),\n", " # Ref.: https://api.python.langchain.com/en/latest/llms/langchain_community.llms.ollama.Ollama.html#langchain_community.llms.ollama.Ollama.format\n", " # format=\"json\"\n", ")\n", "mixtral = Ollama(\n", " model=MIXTRAL,\n", " callback_manager=CallbackManager([StreamingStdOutCallbackHandler()])\n", ")\n", "llama2 = Ollama(\n", " model=LLAMA2,\n", " callback_manager=CallbackManager([StreamingStdOutCallbackHandler()])\n", ")\n", "\n", "\n", "# LOAD\n", "from langchain_community.document_loaders import UnstructuredMarkdownLoader\n", "from langchain_community.document_loaders import TextLoader\n", "from langchain_community.document_loaders import PyPDFLoader\n", "\n", "FILES = {\n", " 'md': [\n", " # \"Présentation modes dégradés-20230120_112423-Enregistrement de la réunion.md\",\n", " \"YouTube - Mode secours telephonie.md\"\n", " ],\n", " 'pdf': [\n", " # \"SI-Samu_Fiche procédure_Mode dégradé_Perte de CRRA.pdf\",\n", " # \"[SI-Samu] Fiche mémo - Procédure Mode dégradé.pdf\",\n", " \"SI-Samu_Documentation_produit_SF4_J18HF2_20231219 - mode secours seul.pdf\",\n", " # \"SI-Samu_Documentation_produit_SF4_J18HF2_20231219.pdf\"\n", " ]\n", "}\n", "\n", "def load_data(files):\n", " data = {'md': [], 'pdf': []}\n", " for pdf in files['pdf']:\n", " data['pdf'].extend(PyPDFLoader('resources/' + pdf).load())\n", " for md in files['md']:\n", " data['md'].extend(TextLoader('resources/' + md).load())\n", " return data\n", "\n", "def to_full_data(data):\n", " return [\n", " *data['md'],\n", " *data['pdf']\n", " ]\n", "\n", "# SPLIT\n", "from langchain.text_splitter import RecursiveCharacterTextSplitter\n", "from langchain.text_splitter import MarkdownHeaderTextSplitter\n", "\n", "def split_MD_then_recursive(data):\n", " # - First use MarkDown title splitter on .MD and then RecursiveSplitter on all\n", " # MD splits\n", " markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=[\n", " (\"#\", \"Titre 1\"),\n", " (\"##\", \"Titre 2\"),\n", " (\"###\", \"Titre 3\"),\n", " ], strip_headers=False)\n", " md_header_splits = data['pdf'].copy()\n", " for md in data['md']:\n", " md_header_splits.extend(markdown_splitter.split_text(md.page_content))\n", "\n", " # Char-level splits\n", " text_splitter=RecursiveCharacterTextSplitter(\n", " chunk_size=500, \n", " chunk_overlap=50 # to improve results quality\n", " )\n", " # Split\n", " return text_splitter.split_documents(md_header_splits)\n", "\n", "# EMBED\n", "# Directly done in the different scripts\n", "\n", "# RETRIEVE\n", "from langchain.storage import InMemoryStore\n", "from langchain.retrievers import ParentDocumentRetriever, BM25Retriever, EnsembleRetriever\n", "from langchain.text_splitter import RecursiveCharacterTextSplitter\n", "from langchain.vectorstores import Chroma\n", "\n", "# Ensemble is based on weight fusion (Reciprocal Rank Fusion) | Ref.: https://safjan.com/implementing-rank-fusion-in-python/\n", "def get_parent_ensemble_retriever(embeddings, full_data, all_splits, k=4, parent_chunk_size=2000, child_chunk_size=400, collection_name=\"store\"):\n", " # - ParentDocumentRetriever: embed small chunks but retrieve with bigger context\n", " # This text splitter is used to create the parent documents\n", " parent_splitter = RecursiveCharacterTextSplitter(chunk_size=parent_chunk_size)\n", " # This text splitter is used to create the child documents\n", " # It should create documents smaller than the parent (don't make bigger than 512 as most embeddings trunk after that)\n", " child_splitter = RecursiveCharacterTextSplitter(chunk_size=child_chunk_size)\n", " # The vectorstore to use to index the child chunks\n", " parent_vectorstore = Chroma(\n", " collection_name=collection_name, \n", " embedding_function=embeddings\n", " )\n", " # The storage layer for the parent documents\n", " parent_store = InMemoryStore()\n", " parent_retriever = ParentDocumentRetriever(\n", " vectorstore=parent_vectorstore,\n", " docstore=parent_store,\n", " child_splitter=child_splitter,\n", " parent_splitter=parent_splitter,\n", " search_kwargs={\n", " \"k\": k,\n", " # \"score_threshold\": 0.5\n", " },\n", " # search_type=\"mmr\"\n", " )\n", " parent_retriever.add_documents(full_data)\n", "\n", " # - EnsembleRetriever\n", " # BM25 logic\n", " bm25_retriever = BM25Retriever.from_texts(\n", " list(map(lambda s: s.page_content, all_splits)), \n", " metadatas=list(map(lambda s: {\"retriever\": \"BM25 sparse similiarity\", **s.metadata}, all_splits))\n", " \n", " )\n", " bm25_retriever.k = k\n", "\n", " # Ensemble of BM25 + vectorstore on parent retriever\n", " return EnsembleRetriever(\n", " retrievers=[parent_retriever, bm25_retriever], weights=[0.5, 0.5]\n", " )\n", "\n", "# PROMPT\n", "# Add more context to query + update system prompt to make it speak French\n", "# Ref.: https://stackoverflow.com/questions/76554411/unable-to-pass-prompt-template-to-retrievalqa-in-langchain\n", "# Ref.: https://community.openai.com/t/how-to-prevent-chatgpt-from-answering-questions-that-are-outside-the-scope-of-the-provided-context-in-the-system-role-message/112027/7\n", "from langchain import PromptTemplate\n", "template = \"\"\"\n", "System: You are helping a user of \"bandeau téléphonique SI-SAMU\" (a CTI - Computer Telephony Integration - system) during system failure as he needs to use its local backup phone.\n", "Context information is below. Given the context information and not prior knowledge, answer the query.\n", "Language: Answer in French and using \"vous\".\n", "---\n", "Context: {context}\n", "---\n", "Question: {question}\n", "---\n", "Réponse :\n", "\"\"\"\n", "PROMPT = PromptTemplate(template=template, input_variables=['question', 'context'])\n", "\n", "# RESULTS\n", "class color:\n", " PURPLE = '\\033[95m'\n", " CYAN = '\\033[96m'\n", " DARKCYAN = '\\033[36m'\n", " BLUE = '\\033[94m'\n", " GREEN = '\\033[92m'\n", " YELLOW = '\\033[93m'\n", " RED = '\\033[91m'\n", " BOLD = '\\033[1m'\n", " UNDERLINE = '\\033[4m'\n", " END = '\\033[0m'\n", "\n", "def parse_answer(answer):\n", " print(f\">> {answer['query']}\")\n", " print(f\">> {answer['result']}\")\n", " print(\">> Sources :\")\n", " for doc in answer['source_documents']:\n", " page = ''\n", " if 'page' in doc.metadata:\n", " page = f\" (page {doc.metadata['page']})\"\n", " source = ''\n", " if 'source' in doc.metadata:\n", " source = doc.metadata['source']\n", " titles = ['Titre 1', 'Titre 2', 'Titre 3']\n", " for title in titles:\n", " if title in doc.metadata:\n", " source += f\" > {doc.metadata[title]}\"\n", " retriever = f\"B25\" if 'retriever' in doc.metadata else \"vectorstore\"\n", " print(f\">>> {color.BOLD}{source}{page} [{retriever}]{color.END}: {doc.page_content}\\n---\")\n", " print(\"--------\\n\\n\")" ] }, { "cell_type": "code", "execution_count": 4, "id": "28802097-6efb-4d57-a4d4-f11d0c005d4a", "metadata": {}, "outputs": [], "source": [ "try:\n", " # LOAD\n", " from utils import FILES, load_data, to_full_data\n", " \n", " # SPLIT\n", " from utils import split_MD_then_recursive\n", " \n", " # RETRIEVE\n", " from utils import get_parent_ensemble_retriever\n", " \n", " # PROMPT\n", " from utils import PROMPT\n", "except:\n", " print(\"Couldn't load utils\")" ] }, { "cell_type": "code", "execution_count": 5, "id": "b2d07ea5-9ff2-4c96-a8dc-92895d870b73", "metadata": { "tags": [] }, "outputs": [ { "data": { "application/javascript": [ "(function(root) {\n", " function now() {\n", " return new Date();\n", " }\n", "\n", " var force = true;\n", " var py_version = '3.3.3'.replace('rc', '-rc.').replace('.dev', '-dev.');\n", " var reloading = false;\n", " var Bokeh = root.Bokeh;\n", "\n", " if (typeof (root._bokeh_timeout) === \"undefined\" || force) {\n", " root._bokeh_timeout = Date.now() + 5000;\n", " root._bokeh_failed_load = false;\n", " }\n", "\n", " function run_callbacks() {\n", " try {\n", " root._bokeh_onload_callbacks.forEach(function(callback) {\n", " if (callback != null)\n", " callback();\n", " });\n", " } finally {\n", " delete root._bokeh_onload_callbacks;\n", " }\n", " console.debug(\"Bokeh: all callbacks have finished\");\n", " }\n", "\n", " function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n", " if (css_urls == null) css_urls = [];\n", " if (js_urls == null) js_urls = [];\n", " if (js_modules == null) js_modules = [];\n", " if (js_exports == null) js_exports = {};\n", "\n", " root._bokeh_onload_callbacks.push(callback);\n", "\n", " if (root._bokeh_is_loading > 0) {\n", " console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n", " return null;\n", " }\n", " if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n", " run_callbacks();\n", " return null;\n", " }\n", " if (!reloading) {\n", " console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n", " }\n", "\n", " function on_load() {\n", " root._bokeh_is_loading--;\n", " if (root._bokeh_is_loading === 0) {\n", " console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n", " run_callbacks()\n", " }\n", " }\n", " window._bokeh_on_load = on_load\n", "\n", " function on_error() {\n", " console.error(\"failed to load \" + url);\n", " }\n", "\n", " var skip = [];\n", " if (window.requirejs) {\n", " window.requirejs.config({'packages': {}, 'paths': {'Quill': 'https://cdn.quilljs.com/1.3.7/quill', 'jspanel': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/jspanel', 'jspanel-modal': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal', 'jspanel-tooltip': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip', 'jspanel-hint': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint', 'jspanel-layout': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout', 'jspanel-contextmenu': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu', 'jspanel-dock': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock', 'gridstack': 'https://cdn.jsdelivr.net/npm/gridstack@7.2.3/dist/gridstack-all', 'notyf': 'https://cdn.jsdelivr.net/npm/notyf@3/notyf.min'}, 'shim': {'jspanel': {'exports': 'jsPanel'}, 'gridstack': {'exports': 'GridStack'}}});\n", " require([\"Quill\"], function(Quill) {\n", "\twindow.Quill = Quill\n", "\ton_load()\n", " })\n", " require([\"jspanel\"], function(jsPanel) {\n", "\twindow.jsPanel = jsPanel\n", "\ton_load()\n", " })\n", " require([\"jspanel-modal\"], function() {\n", "\ton_load()\n", " })\n", " require([\"jspanel-tooltip\"], function() {\n", "\ton_load()\n", " })\n", " require([\"jspanel-hint\"], function() {\n", "\ton_load()\n", " })\n", " require([\"jspanel-layout\"], function() {\n", "\ton_load()\n", " })\n", " require([\"jspanel-contextmenu\"], function() {\n", "\ton_load()\n", " })\n", " require([\"jspanel-dock\"], function() {\n", "\ton_load()\n", " })\n", " require([\"gridstack\"], function(GridStack) {\n", "\twindow.GridStack = GridStack\n", "\ton_load()\n", " })\n", " require([\"notyf\"], function() {\n", "\ton_load()\n", " })\n", " root._bokeh_is_loading = css_urls.length + 10;\n", " } else {\n", " root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n", " }\n", "\n", " var existing_stylesheets = []\n", " var links = document.getElementsByTagName('link')\n", " for (var i = 0; i < links.length; i++) {\n", " var link = links[i]\n", " if (link.href != null) {\n", "\texisting_stylesheets.push(link.href)\n", " }\n", " }\n", " for (var i = 0; i < css_urls.length; i++) {\n", " var url = css_urls[i];\n", " if (existing_stylesheets.indexOf(url) !== -1) {\n", "\ton_load()\n", "\tcontinue;\n", " }\n", " const element = document.createElement(\"link\");\n", " element.onload = on_load;\n", " element.onerror = on_error;\n", " element.rel = \"stylesheet\";\n", " element.type = \"text/css\";\n", " element.href = url;\n", " console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n", " document.body.appendChild(element);\n", " } if (((window['Quill'] !== undefined) && (!(window['Quill'] instanceof HTMLElement))) || window.requirejs) {\n", " var urls = ['https://cdn.holoviz.org/panel/1.3.7/dist/bundled/quillinput/1.3.7/quill.js'];\n", " for (var i = 0; i < urls.length; i++) {\n", " skip.push(urls[i])\n", " }\n", " } if (((window['jsPanel'] !== undefined) && (!(window['jsPanel'] instanceof HTMLElement))) || window.requirejs) {\n", " var urls = ['https://cdn.holoviz.org/panel/1.3.7/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.3.7/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.3.7/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.3.7/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.3.7/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.3.7/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.3.7/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock.js'];\n", " for (var i = 0; i < urls.length; i++) {\n", " skip.push(urls[i])\n", " }\n", " } if (((window['GridStack'] !== undefined) && (!(window['GridStack'] instanceof HTMLElement))) || window.requirejs) {\n", " var urls = ['https://cdn.holoviz.org/panel/1.3.7/dist/bundled/gridstack/gridstack@7.2.3/dist/gridstack-all.js'];\n", " for (var i = 0; i < urls.length; i++) {\n", " skip.push(urls[i])\n", " }\n", " } if (((window['Notyf'] !== undefined) && (!(window['Notyf'] instanceof HTMLElement))) || window.requirejs) {\n", " var urls = ['https://cdn.holoviz.org/panel/1.3.7/dist/bundled/notificationarea/notyf@3/notyf.min.js'];\n", " for (var i = 0; i < urls.length; i++) {\n", " skip.push(urls[i])\n", " }\n", " } var existing_scripts = []\n", " var scripts = document.getElementsByTagName('script')\n", " for (var i = 0; i < scripts.length; i++) {\n", " var script = scripts[i]\n", " if (script.src != null) {\n", "\texisting_scripts.push(script.src)\n", " }\n", " }\n", " for (var i = 0; i < js_urls.length; i++) {\n", " var url = js_urls[i];\n", " if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n", "\tif (!window.requirejs) {\n", "\t on_load();\n", "\t}\n", "\tcontinue;\n", " }\n", " var element = document.createElement('script');\n", " element.onload = on_load;\n", " element.onerror = on_error;\n", " element.async = false;\n", " element.src = url;\n", " console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n", " document.head.appendChild(element);\n", " }\n", " for (var i = 0; i < js_modules.length; i++) {\n", " var url = js_modules[i];\n", " if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n", "\tif (!window.requirejs) {\n", "\t on_load();\n", "\t}\n", "\tcontinue;\n", " }\n", " var element = document.createElement('script');\n", " element.onload = on_load;\n", " element.onerror = on_error;\n", " element.async = false;\n", " element.src = url;\n", " element.type = \"module\";\n", " console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n", " document.head.appendChild(element);\n", " }\n", " for (const name in js_exports) {\n", " var url = js_exports[name];\n", " if (skip.indexOf(url) >= 0 || root[name] != null) {\n", "\tif (!window.requirejs) {\n", "\t on_load();\n", "\t}\n", "\tcontinue;\n", " }\n", " var element = document.createElement('script');\n", " element.onerror = on_error;\n", " element.async = false;\n", " element.type = \"module\";\n", " console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n", " element.textContent = `\n", " import ${name} from \"${url}\"\n", " window.${name} = ${name}\n", " window._bokeh_on_load()\n", " `\n", " document.head.appendChild(element);\n", " }\n", " if (!js_urls.length && !js_modules.length) {\n", " on_load()\n", " }\n", " };\n", "\n", " function inject_raw_css(css) {\n", " const element = document.createElement(\"style\");\n", " element.appendChild(document.createTextNode(css));\n", " document.body.appendChild(element);\n", " }\n", "\n", " var js_urls = [\"https://cdn.holoviz.org/panel/1.3.7/dist/bundled/quillinput/1.3.7/quill.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.3.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.3.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.3.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.3.3.min.js\", \"https://cdn.holoviz.org/panel/1.3.7/dist/panel.min.js\"];\n", " var js_modules = [];\n", " var js_exports = {};\n", " var css_urls = [\"https://cdn.holoviz.org/panel/1.3.7/dist/bundled/quillinput/1.3.7/quill.bubble.css\", \"https://cdn.holoviz.org/panel/1.3.7/dist/bundled/quillinput/1.3.7/quill.snow.css\"];\n", " var inline_js = [ function(Bokeh) {\n", " Bokeh.set_log_level(\"info\");\n", " },\n", "function(Bokeh) {} // ensure no trailing comma for IE\n", " ];\n", "\n", " function run_inline_js() {\n", " if ((root.Bokeh !== undefined) || (force === true)) {\n", " for (var i = 0; i < inline_js.length; i++) {\n", "\ttry {\n", " inline_js[i].call(root, root.Bokeh);\n", "\t} catch(e) {\n", "\t if (!reloading) {\n", "\t throw e;\n", "\t }\n", "\t}\n", " }\n", " // Cache old bokeh versions\n", " if (Bokeh != undefined && !reloading) {\n", "\tvar NewBokeh = root.Bokeh;\n", "\tif (Bokeh.versions === undefined) {\n", "\t Bokeh.versions = new Map();\n", "\t}\n", "\tif (NewBokeh.version !== Bokeh.version) {\n", "\t Bokeh.versions.set(NewBokeh.version, NewBokeh)\n", "\t}\n", "\troot.Bokeh = Bokeh;\n", " }} else if (Date.now() < root._bokeh_timeout) {\n", " setTimeout(run_inline_js, 100);\n", " } else if (!root._bokeh_failed_load) {\n", " console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n", " root._bokeh_failed_load = true;\n", " }\n", " root._bokeh_is_initializing = false\n", " }\n", "\n", " function load_or_wait() {\n", " // Implement a backoff loop that tries to ensure we do not load multiple\n", " // versions of Bokeh and its dependencies at the same time.\n", " // In recent versions we use the root._bokeh_is_initializing flag\n", " // to determine whether there is an ongoing attempt to initialize\n", " // bokeh, however for backward compatibility we also try to ensure\n", " // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n", " // before older versions are fully initialized.\n", " if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n", " root._bokeh_is_initializing = false;\n", " root._bokeh_onload_callbacks = undefined;\n", " console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n", " load_or_wait();\n", " } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n", " setTimeout(load_or_wait, 100);\n", " } else {\n", " root._bokeh_is_initializing = true\n", " root._bokeh_onload_callbacks = []\n", " var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n", " if (!reloading && !bokeh_loaded) {\n", "\troot.Bokeh = undefined;\n", " }\n", " load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n", "\tconsole.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n", "\trun_inline_js();\n", " });\n", " }\n", " }\n", " // Give older versions of the autoload script a head-start to ensure\n", " // they initialize before we start loading newer version.\n", " setTimeout(load_or_wait, 100)\n", "}(window));" ], "application/vnd.holoviews_load.v0+json": "(function(root) {\n function now() {\n return new Date();\n }\n\n var force = true;\n var py_version = '3.3.3'.replace('rc', '-rc.').replace('.dev', '-dev.');\n var reloading = false;\n var Bokeh = root.Bokeh;\n\n if (typeof (root._bokeh_timeout) === \"undefined\" || force) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n }\n if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n run_callbacks();\n return null;\n }\n if (!reloading) {\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error() {\n console.error(\"failed to load \" + url);\n }\n\n var skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {'Quill': 'https://cdn.quilljs.com/1.3.7/quill', 'jspanel': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/jspanel', 'jspanel-modal': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal', 'jspanel-tooltip': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip', 'jspanel-hint': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint', 'jspanel-layout': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout', 'jspanel-contextmenu': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu', 'jspanel-dock': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock', 'gridstack': 'https://cdn.jsdelivr.net/npm/gridstack@7.2.3/dist/gridstack-all', 'notyf': 'https://cdn.jsdelivr.net/npm/notyf@3/notyf.min'}, 'shim': {'jspanel': {'exports': 'jsPanel'}, 'gridstack': {'exports': 'GridStack'}}});\n require([\"Quill\"], function(Quill) {\n\twindow.Quill = Quill\n\ton_load()\n })\n require([\"jspanel\"], function(jsPanel) {\n\twindow.jsPanel = jsPanel\n\ton_load()\n })\n require([\"jspanel-modal\"], function() {\n\ton_load()\n })\n require([\"jspanel-tooltip\"], function() {\n\ton_load()\n })\n require([\"jspanel-hint\"], function() {\n\ton_load()\n })\n require([\"jspanel-layout\"], function() {\n\ton_load()\n })\n require([\"jspanel-contextmenu\"], function() {\n\ton_load()\n })\n require([\"jspanel-dock\"], function() {\n\ton_load()\n })\n require([\"gridstack\"], function(GridStack) {\n\twindow.GridStack = GridStack\n\ton_load()\n })\n require([\"notyf\"], function() {\n\ton_load()\n })\n root._bokeh_is_loading = css_urls.length + 10;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n var existing_stylesheets = []\n var links = document.getElementsByTagName('link')\n for (var i = 0; i < links.length; i++) {\n var link = links[i]\n if (link.href != null) {\n\texisting_stylesheets.push(link.href)\n }\n }\n for (var i = 0; i < css_urls.length; i++) {\n var url = css_urls[i];\n if (existing_stylesheets.indexOf(url) !== -1) {\n\ton_load()\n\tcontinue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } if (((window['Quill'] !== undefined) && (!(window['Quill'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.3.7/dist/bundled/quillinput/1.3.7/quill.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['jsPanel'] !== undefined) && (!(window['jsPanel'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.3.7/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.3.7/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.3.7/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.3.7/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.3.7/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.3.7/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.3.7/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['GridStack'] !== undefined) && (!(window['GridStack'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.3.7/dist/bundled/gridstack/gridstack@7.2.3/dist/gridstack-all.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['Notyf'] !== undefined) && (!(window['Notyf'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.3.7/dist/bundled/notificationarea/notyf@3/notyf.min.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } var existing_scripts = []\n var scripts = document.getElementsByTagName('script')\n for (var i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n\texisting_scripts.push(script.src)\n }\n }\n for (var i = 0; i < js_urls.length; i++) {\n var url = js_urls[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (var i = 0; i < js_modules.length; i++) {\n var url = js_modules[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n var url = js_exports[name];\n if (skip.indexOf(url) >= 0 || root[name] != null) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n var js_urls = [\"https://cdn.holoviz.org/panel/1.3.7/dist/bundled/quillinput/1.3.7/quill.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.3.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.3.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.3.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.3.3.min.js\", \"https://cdn.holoviz.org/panel/1.3.7/dist/panel.min.js\"];\n var js_modules = [];\n var js_exports = {};\n var css_urls = [\"https://cdn.holoviz.org/panel/1.3.7/dist/bundled/quillinput/1.3.7/quill.bubble.css\", \"https://cdn.holoviz.org/panel/1.3.7/dist/bundled/quillinput/1.3.7/quill.snow.css\"];\n var inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (var i = 0; i < inline_js.length; i++) {\n\ttry {\n inline_js[i].call(root, root.Bokeh);\n\t} catch(e) {\n\t if (!reloading) {\n\t throw e;\n\t }\n\t}\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n\tvar NewBokeh = root.Bokeh;\n\tif (Bokeh.versions === undefined) {\n\t Bokeh.versions = new Map();\n\t}\n\tif (NewBokeh.version !== Bokeh.version) {\n\t Bokeh.versions.set(NewBokeh.version, NewBokeh)\n\t}\n\troot.Bokeh = Bokeh;\n }} else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\n\troot.Bokeh = undefined;\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n\tconsole.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n\trun_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));" }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/javascript": [ "\n", "if ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n", " window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n", "}\n", "\n", "\n", " function JupyterCommManager() {\n", " }\n", "\n", " JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n", " if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n", " var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n", " comm_manager.register_target(comm_id, function(comm) {\n", " comm.on_msg(msg_handler);\n", " });\n", " } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n", " window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n", " comm.onMsg = msg_handler;\n", " });\n", " } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n", " google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n", " var messages = comm.messages[Symbol.asyncIterator]();\n", " function processIteratorResult(result) {\n", " var message = result.value;\n", " console.log(message)\n", " var content = {data: message.data, comm_id};\n", " var buffers = []\n", " for (var buffer of message.buffers || []) {\n", " buffers.push(new DataView(buffer))\n", " }\n", " var metadata = message.metadata || {};\n", " var msg = {content, buffers, metadata}\n", " msg_handler(msg);\n", " return messages.next().then(processIteratorResult);\n", " }\n", " return messages.next().then(processIteratorResult);\n", " })\n", " }\n", " }\n", "\n", " JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n", " if (comm_id in window.PyViz.comms) {\n", " return window.PyViz.comms[comm_id];\n", " } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n", " var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n", " var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n", " if (msg_handler) {\n", " comm.on_msg(msg_handler);\n", " }\n", " } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n", " var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n", " comm.open();\n", " if (msg_handler) {\n", " comm.onMsg = msg_handler;\n", " }\n", " } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n", " var comm_promise = google.colab.kernel.comms.open(comm_id)\n", " comm_promise.then((comm) => {\n", " window.PyViz.comms[comm_id] = comm;\n", " if (msg_handler) {\n", " var messages = comm.messages[Symbol.asyncIterator]();\n", " function processIteratorResult(result) {\n", " var message = result.value;\n", " var content = {data: message.data};\n", " var metadata = message.metadata || {comm_id};\n", " var msg = {content, metadata}\n", " msg_handler(msg);\n", " return messages.next().then(processIteratorResult);\n", " }\n", " return messages.next().then(processIteratorResult);\n", " }\n", " }) \n", " var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n", " return comm_promise.then((comm) => {\n", " comm.send(data, metadata, buffers, disposeOnDone);\n", " });\n", " };\n", " var comm = {\n", " send: sendClosure\n", " };\n", " }\n", " window.PyViz.comms[comm_id] = comm;\n", " return comm;\n", " }\n", " window.PyViz.comm_manager = new JupyterCommManager();\n", " \n", "\n", "\n", "var JS_MIME_TYPE = 'application/javascript';\n", "var HTML_MIME_TYPE = 'text/html';\n", "var EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\n", "var CLASS_NAME = 'output';\n", "\n", "/**\n", " * Render data to the DOM node\n", " */\n", "function render(props, node) {\n", " var div = document.createElement(\"div\");\n", " var script = document.createElement(\"script\");\n", " node.appendChild(div);\n", " node.appendChild(script);\n", "}\n", "\n", "/**\n", " * Handle when a new output is added\n", " */\n", "function handle_add_output(event, handle) {\n", " var output_area = handle.output_area;\n", " var output = handle.output;\n", " if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n", " return\n", " }\n", " var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n", " var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n", " if (id !== undefined) {\n", " var nchildren = toinsert.length;\n", " var html_node = toinsert[nchildren-1].children[0];\n", " html_node.innerHTML = output.data[HTML_MIME_TYPE];\n", " var scripts = [];\n", " var nodelist = html_node.querySelectorAll(\"script\");\n", " for (var i in nodelist) {\n", " if (nodelist.hasOwnProperty(i)) {\n", " scripts.push(nodelist[i])\n", " }\n", " }\n", "\n", " scripts.forEach( function (oldScript) {\n", " var newScript = document.createElement(\"script\");\n", " var attrs = [];\n", " var nodemap = oldScript.attributes;\n", " for (var j in nodemap) {\n", " if (nodemap.hasOwnProperty(j)) {\n", " attrs.push(nodemap[j])\n", " }\n", " }\n", " attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n", " newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n", " oldScript.parentNode.replaceChild(newScript, oldScript);\n", " });\n", " if (JS_MIME_TYPE in output.data) {\n", " toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n", " }\n", " output_area._hv_plot_id = id;\n", " if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n", " window.PyViz.plot_index[id] = Bokeh.index[id];\n", " } else {\n", " window.PyViz.plot_index[id] = null;\n", " }\n", " } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n", " var bk_div = document.createElement(\"div\");\n", " bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n", " var script_attrs = bk_div.children[0].attributes;\n", " for (var i = 0; i < script_attrs.length; i++) {\n", " toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n", " }\n", " // store reference to server id on output_area\n", " output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n", " }\n", "}\n", "\n", "/**\n", " * Handle when an output is cleared or removed\n", " */\n", "function handle_clear_output(event, handle) {\n", " var id = handle.cell.output_area._hv_plot_id;\n", " var server_id = handle.cell.output_area._bokeh_server_id;\n", " if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n", " var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n", " if (server_id !== null) {\n", " comm.send({event_type: 'server_delete', 'id': server_id});\n", " return;\n", " } else if (comm !== null) {\n", " comm.send({event_type: 'delete', 'id': id});\n", " }\n", " delete PyViz.plot_index[id];\n", " if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n", " var doc = window.Bokeh.index[id].model.document\n", " doc.clear();\n", " const i = window.Bokeh.documents.indexOf(doc);\n", " if (i > -1) {\n", " window.Bokeh.documents.splice(i, 1);\n", " }\n", " }\n", "}\n", "\n", "/**\n", " * Handle kernel restart event\n", " */\n", "function handle_kernel_cleanup(event, handle) {\n", " delete PyViz.comms[\"hv-extension-comm\"];\n", " window.PyViz.plot_index = {}\n", "}\n", "\n", "/**\n", " * Handle update_display_data messages\n", " */\n", "function handle_update_output(event, handle) {\n", " handle_clear_output(event, {cell: {output_area: handle.output_area}})\n", " handle_add_output(event, handle)\n", "}\n", "\n", "function register_renderer(events, OutputArea) {\n", " function append_mime(data, metadata, element) {\n", " // create a DOM node to render to\n", " var toinsert = this.create_output_subarea(\n", " metadata,\n", " CLASS_NAME,\n", " EXEC_MIME_TYPE\n", " );\n", " this.keyboard_manager.register_events(toinsert);\n", " // Render to node\n", " var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n", " render(props, toinsert[0]);\n", " element.append(toinsert);\n", " return toinsert\n", " }\n", "\n", " events.on('output_added.OutputArea', handle_add_output);\n", " events.on('output_updated.OutputArea', handle_update_output);\n", " events.on('clear_output.CodeCell', handle_clear_output);\n", " events.on('delete.Cell', handle_clear_output);\n", " events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n", "\n", " OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n", " safe: true,\n", " index: 0\n", " });\n", "}\n", "\n", "if (window.Jupyter !== undefined) {\n", " try {\n", " var events = require('base/js/events');\n", " var OutputArea = require('notebook/js/outputarea').OutputArea;\n", " if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n", " register_renderer(events, OutputArea);\n", " }\n", " } catch(err) {\n", " }\n", "}\n" ], "application/vnd.holoviews_load.v0+json": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n console.log(message)\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n comm.open();\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n }) \n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n" }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.holoviews_exec.v0+json": "", "text/html": [ "
\n", "
\n", "
\n", "" ] }, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": "ac7415e8-ffed-4cb8-bfe1-b4076fde7a73" } }, "output_type": "display_data" }, { "data": { "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pn.extension('texteditor', template=\"bootstrap\", sizing_mode='stretch_width')\n", "pn.state.template.param.update(\n", " main_max_width=\"960px\",\n", " header_background=\"#1D70B6\",\n", ")" ] }, { "cell_type": "code", "execution_count": 6, "id": "763db4d0-3436-41d3-8b0f-e66ce16468cd", "metadata": { "tags": [] }, "outputs": [], "source": [ "prompt = pn.widgets.TextEditor(\n", " value=\"\", \n", " placeholder=\"Veuillez saisir votre question ici...\\n\\nComment faire pour me connecter ?\\nComment savoir si j'ai des appels en attente ?\\nComment faire un transfert accompagné ?\\nMot de passe pour se connecter au téléphone ?\",\n", " height=140, \n", " toolbar=False\n", ")\n", "run_button = pn.widgets.Button(name=\"Envoyer\")\n", "\n", "select_k = pn.widgets.IntSlider(\n", " name=\"Nombre d'éléments pertinents à extraire\", start=1, end=5, step=1, value=2\n", ")\n", "select_model = pn.widgets.RadioButtonGroup(\n", " name='Modèle', \n", " options=['GPT 3.5', 'GPT 4'],\n", " value='GPT 4'\n", ")\n", "\n", "widgets = pn.Row(\n", " pn.Column(prompt, run_button, margin=5),\n", " pn.Card(\n", " \"Modèle\",\n", " pn.Column(select_model, select_k),\n", " title=\"Paramètres avancés\",\n", " collapsible=False,\n", " margin=12\n", " ), \n", " width=940\n", ")" ] }, { "cell_type": "code", "execution_count": 7, "id": "9b83cc06-3401-498f-8f84-8a98370f3121", "metadata": { "tags": [] }, "outputs": [], "source": [ "def qa(query, model, k):\n", " # LOAD\n", " # from utils import FILES, load_data, to_full_data\n", " data = load_data(FILES)\n", " full_data = to_full_data(data)\n", "\n", " # SPLIT\n", " # from utils import split_MD_then_recursive\n", " \n", " all_splits = split_MD_then_recursive(data)\n", "\n", " # EMBED\n", " # Find the relevant splits to then submit those to the model\n", " # -> Create embeddings and store them in a vector database\n", " # -> Use Ollama directly to instantiate an embedding model (ChromaDB here for a vector database)\n", " from langchain_openai import OpenAIEmbeddings\n", " # OpenAI ADA\n", " openai_embeddings = OpenAIEmbeddings()\n", "\n", " # RETRIEVE\n", " # from utils import get_parent_ensemble_retriever\n", "\n", " # PROMPT\n", " # from utils import PROMPT\n", "\n", " # LLM\n", " # Ref.: https://python.langchain.com/docs/integrations/platforms/openai\n", " from langchain_openai import ChatOpenAI, OpenAI\n", "\n", " gpt35_chat = ChatOpenAI(model_name=\"gpt-3.5-turbo-1106\", temperature=0)\n", " gpt4turbo_chat = ChatOpenAI(model_name=\"gpt-4-1106-preview\", temperature=0)\n", "\n", " # PARAMS\n", " if model == 'GPT 4':\n", " llm = gpt4turbo_chat\n", " # if \"_gpt4_\" in query:\n", " # llm = gpt4turbo_chat\n", " # query = query.replace(\"_gpt4_\", \"\")\n", " # else:\n", " # print(\"blocking GPT4!\")\n", " # return {\n", " # 'query': query,\n", " # 'result': \"GPT 4 désactivé, veuillez contacter l'administrateur\",\n", " # 'source_documents': []\n", " # }\n", " else: \n", " llm = gpt35_chat\n", " embeddings = openai_embeddings # openai_embeddings, inference_embeddings\n", " retriever = get_parent_ensemble_retriever(\n", " embeddings, \n", " full_data, \n", " all_splits,\n", " k, \n", " 800, \n", " 200, \n", " \"ada-store\"\n", " )\n", "\n", " # QA Chain\n", " from langchain.chains import RetrievalQA\n", " qachain = RetrievalQA.from_chain_type(\n", " llm=llm, \n", " retriever=retriever,\n", " return_source_documents=True,\n", " verbose=True,\n", " chain_type_kwargs={\"prompt\": PROMPT}\n", " )\n", " result = qachain({\"query\": query})\n", " print(result['result'])\n", " return result" ] }, { "cell_type": "code", "execution_count": 8, "id": "60e1b3d3-c0d2-4260-ae0c-26b03f1b8824", "metadata": {}, "outputs": [], "source": [ "convos = [] # store all panel objects in a list\n", "\n", "def qa_result(_): \n", " prompt_text = prompt.value\n", " if prompt_text:\n", " result = qa(query=prompt_text, model=select_model.value, k=select_k.value)\n", " convos.extend([\n", " pn.Row(\n", " pn.panel(\"\\U0001F60A\", width=10),\n", " prompt_text,\n", " width=900\n", " ),\n", " pn.Row(\n", " pn.panel(\"\\U0001F916\", width=10),\n", " pn.Column(\n", " result[\"result\"],\n", " pn.Accordion((\n", " f\"Éléments utilisés ({len(result['source_documents'])})\",\n", " pn.pane.Markdown('\\n\\n--------------------------------------------------------------------\\n\\n'.join(doc.page_content for doc in result[\"source_documents\"]))\n", " )) if len(result['source_documents']) > 0 else None\n", " )\n", " )\n", " ])\n", " return pn.Column(*convos, margin=15, width=875, min_height=400)\n" ] }, { "cell_type": "code", "execution_count": 9, "id": "c3a70857-0b98-4f62-a9c0-b62ca42b474c", "metadata": { "tags": [] }, "outputs": [], "source": [ "qa_interactive = pn.panel(\n", " pn.bind(qa_result, run_button),\n", " loading_indicator=True,\n", ")" ] }, { "cell_type": "code", "execution_count": 10, "id": "228e2b42-b1ed-43af-b923-031a70241ab0", "metadata": { "tags": [] }, "outputs": [], "source": [ "output = pn.WidgetBox(\"*Votre échange s'affichera ici :*\", qa_interactive, width=940, scroll=True)" ] }, { "cell_type": "code", "execution_count": 11, "id": "1b0ec253-2bcd-4f91-96d8-d8456e900a58", "metadata": { "tags": [] }, "outputs": [ { "data": {}, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.holoviews_exec.v0+json": "", "text/html": [ "
\n", "
\n", "
\n", "" ], "text/plain": [ "Column(sizing_mode='stretch_width')\n", " [0] Markdown(str, sizing_mode='stretch_width')\n", " [1] Row(width=940)\n", " [0] Column(margin=5, sizing_mode='stretch_width')\n", " [0] TextEditor(height=140, placeholder=\"Veuillez saisir v..., sizing_mode='stretch_width', toolbar=False)\n", " [1] Button(name='Envoyer', sizing_mode='stretch_width')\n", " [1] Card(collapsible=False, margin=12, sizing_mode='stretch_width', title='Paramètres avancés')\n", " [0] Markdown(str, sizing_mode='stretch_width')\n", " [1] Column(sizing_mode='stretch_width')\n", " [0] RadioButtonGroup(name='Modèle', options=['GPT 3.5', 'GPT 4'], sizing_mode='stretch_width', value='GPT 3.5')\n", " [1] IntSlider(end=5, name=\"Nombre d'éléments p..., sizing_mode='stretch_width', start=1, value=2)\n", " [2] WidgetBox(css_classes=['panel-widget-box', ...], scroll=True, width=940)\n", " [0] Markdown(str, sizing_mode='stretch_width')\n", " [1] ParamFunction(function, _pane=Column, defer_load=False, loading_indicator=True, sizing_mode='stretch_width')" ] }, "execution_count": 11, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": "57d53327-d9fe-47fd-9823-d1ed68ee7ade" } }, "output_type": "execute_result" } ], "source": [ "# layout\n", "pn.Column(\n", " pn.pane.Markdown(\"\"\"\n", " ## Support mode secours SI-SAMU\n", "\n", " Cet assistant a été entrainé sur \n", " - la documentation produit du Bandeau SI-SAMU (partie \"12. Fonctionnement en mode dégradé\")\n", " - la [vidéo](https://www.youtube.com/watch?v=EbupwqYeKdc) d'explication du mode secours de la téléphonie SI-SAMU\n", "\n", " Les réponses de cet assistant sont automatiques et non validées par les équipes du SI-SAMU. \n", " Veuillez également ne pas transmettre d'informations personnelles et/ou médicales à l'assistant.\n", " Vous pouvez l'utiliser pour vous guider et extraire les bouts de documentation adaptés mais un recours au support SI-SAMU est conseillé en cas de problème.\n", " \"\"\"),\n", " widgets,\n", " output\n", ").servable()" ] }, { "cell_type": "code", "execution_count": null, "id": "63534c18-fb1b-4427-abea-07105d5dc0c2", "metadata": {}, "outputs": [], "source": [] } ], "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.9.6" } }, "nbformat": 4, "nbformat_minor": 5 }