{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!pip install -qU langsmith langchain-core langchain-community langchain-openai langchain-qdrant langchain_experimental pymupdf ragas" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "import getpass\n", "from uuid import uuid4\n", "\n", "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", "os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangChain API Key:\")\n", "\n", "os.environ[\"LANGCHAIN_PROJECT\"] = \"AIM-SDG-MidTerm - AI Safety\"\n", "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n", "\n", "os.environ[\"QDRANT_API_KEY\"] = getpass.getpass(\"Enter Your Qdrant API Key: \")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Synthetic data generation using Ragas framework" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will generate set of synthetic data for evaluating different opetions\n", "1. Evaluating Embedding model\n", "2. Evaluating Chunking Strategies" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from langchain_experimental.text_splitter import SemanticChunker\n", "\n", "from enum import Enum\n", "from typing import List\n", "from langchain_community.document_loaders import PyMuPDFLoader\n", "from langchain_core.documents import Document\n", "import asyncio\n", "\n", "class PDFLoaderWrapper():\n", " class LoaderType(str, Enum):\n", " PYMUPDF = \"pymupdf\"\n", "\n", " def __init__(self, file_path: str | List[str] , loader_type: LoaderType = LoaderType.PYMUPDF):\n", " self.file_path = file_path if isinstance(file_path, list) else [file_path]\n", " self.loader_type = loader_type\n", "\n", " async def aload(self) -> List[Document]:\n", " all_docs = []\n", " for file_path in self.file_path:\n", " if self.loader_type == self.LoaderType.PYMUPDF:\n", " try:\n", " loader = PyMuPDFLoader(file_path)\n", " docs = await loader.aload()\n", " all_docs.extend(docs)\n", " except Exception as e:\n", " print(f\"Error loading file {file_path}: {e}\")\n", " continue\n", " return all_docs\n", "\n", "BOR_FILE_PATH = \"https://www.whitehouse.gov/wp-content/uploads/2022/10/Blueprint-for-an-AI-Bill-of-Rights.pdf\"\n", "NIST_FILE_PATH = \"https://nvlpubs.nist.gov/nistpubs/ai/NIST.AI.600-1.pdf\"\n", "documents = [\n", " BOR_FILE_PATH,\n", " NIST_FILE_PATH\n", "]\n", "\n", "pdf_loader = PDFLoaderWrapper(\n", " documents, PDFLoaderWrapper.LoaderType.PYMUPDF\n", ")\n", "documents = await pdf_loader.aload()\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from ragas.testset.generator import TestsetGenerator\n", "from ragas.testset.evolutions import simple, reasoning, multi_context\n", "from langchain_openai import ChatOpenAI, OpenAIEmbeddings\n", "from ragas.testset.docstore import Document, DocumentStore,InMemoryDocumentStore\n", "from langchain_experimental.text_splitter import SemanticChunker,RecursiveCharacterTextSplitter\n", "from langchain_huggingface import HuggingFaceEmbeddings, HuggingFacePipeline\n", "from ragas.testset.extractor import KeyphraseExtractor\n", "\n", "print (\"Packages import complete\")\n", "print (\"Getting the Embedding model from Huggingface\")\n", "# Using best performing embedding model from hugging face to generate quality dataset.\n", "# Need GPU\n", "model_name = \"Snowflake/snowflake-arctic-embed-l\"\n", "embedding_model = HuggingFaceEmbeddings(model_name=model_name)\n", "print (\"Embedding model loaded\")\n", "\n", "print (\"Splitting the documents into semantic chunks\")\n", "text_splitter = RecursiveCharacterTextSplitter(\n", " chunk_size = 1024,\n", " chunk_overlap = 100,\n", " length_function = len,\n", ")\n", "chunked_docs = text_splitter.split_documents(documents)\n", "\n", "print (\"Creating the document store for ragas and loading LLM models\")\n", "generator_llm = ChatOpenAI(model=\"gpt-4o\")\n", "critic_llm = ChatOpenAI(model=\"gpt-4o\")\n", "\n", "keyphrase_extractor = KeyphraseExtractor(llm=generator_llm)\n", "docstore = InMemoryDocumentStore(splitter=text_splitter,extractor=keyphrase_extractor, embeddings=embedding_model)\n", "\n", "print (\"Creating the testset generator\")\n", "generator = TestsetGenerator.from_langchain( # Default uses TokenTextSplitter\n", " generator_llm=generator_llm,\n", " critic_llm=critic_llm,\n", " embeddings=embedding_model,\n", " docstore=docstore # Document store uses SemenaticChunker\n", ")\n", "\n", "distributions = {\n", " simple: 0.5,\n", " multi_context: 0.3,\n", " reasoning: 0.2\n", "}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_size = 50\n", "\n", "testset = generator.generate_with_langchain_docs(\n", " documents, \n", " test_size, \n", " distributions, \n", " with_debugging_logs=True\n", ") # Default RunConfig(max_retries=15, max_wait=90)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "testset_df = testset.to_pandas()\n", "testset_df" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "testset_df.to_csv('task5-ai-safety-sdg.csv', index=False)\n", "test_questions = testset_df[\"question\"].values.tolist()\n", "test_groundtruths = testset_df[\"ground_truth\"].values.tolist()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create Rag chain to generate answers for above questions in the dataset" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> Note that we are usig Qdrant cloud where the pdf document is processed and saved for us to consume. For the RAG pipeline we use the same embedding model originally used to populate the Qdrant vectorstore." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from langchain_qdrant import QdrantVectorStore\n", "from langchain_core.documents import Document\n", "from qdrant_client import QdrantClient\n", "from qdrant_client.http.models import Distance, VectorParams\n", "\n", "dimension = 1024\n", "collection_name = \"ai-safety-sr-arctic-embed-l-recursive\"\n", "qdrant_server = \"https://500cb0e8-ea08-4662-b4f2-3eca11e635da.europe-west3-0.gcp.cloud.qdrant.io:6333\"\n", "qdrant_client = QdrantClient(url=qdrant_server,api_key=os.environ[\"QDRANT_API_KEY\"])\n", "qdrant_client.create_collection(\n", " collection_name=collection_name,\n", " vectors_config=VectorParams(size=dimension, distance=Distance.COSINE),\n", ")\n", "\n", "vector_store = QdrantVectorStore(\n", " client=qdrant_client,\n", " collection_name=collection_name,\n", " embedding=embedding_model,\n", ")\n", "\n", "retriever = vector_store.as_retriever()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from langchain.prompts import ChatPromptTemplate\n", "\n", "RAG_PROMPT = \"\"\"\\\n", "Given a provided context and question, you must answer the question based only on context.\n", "\n", "If you cannot answer the question based on the context - you must say \"I don't know\".\n", "\n", "Context: {context}\n", "Question: {question}\n", "\"\"\"\n", "\n", "rag_prompt = ChatPromptTemplate.from_template(RAG_PROMPT)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from langchain_openai import ChatOpenAI\n", "\n", "# Using the same model used in the app.\n", "chat_model_name = \"gpt-4o\"\n", "llm = ChatOpenAI(model=chat_model_name)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from operator import itemgetter\n", "from langchain_core.runnables import RunnablePassthrough, RunnableParallel\n", "from langchain.schema import StrOutputParser\n", "\n", "rag_chain_generate_anwsers = (\n", " {\"context\": itemgetter(\"question\") | retriever, \"question\": itemgetter(\"question\")}\n", " | rag_prompt | llm | StrOutputParser()\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rag_chain_generate_anwsers.invoke({\"question\" : \"What steps can organizations take to minimize bias in AI models?\"})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Create Rag Chain with config " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We are going to replicate the exact implementation used in the hosted RAG app but with different configuration to evaluate and compare." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Utility function to create a rag chain with config\n", "from langchain_experimental.text_splitter import SemanticChunker\n", "from enum import Enum\n", "from typing import List\n", "from langchain_community.document_loaders import PyMuPDFLoader\n", "from langchain_core.documents import Document\n", "import asyncio\n", "from langchain_qdrant import QdrantVectorStore\n", "from langchain_core.documents import Document\n", "from qdrant_client import QdrantClient\n", "from qdrant_client.http.models import Distance, VectorParams\n", "from langchain.retrievers.contextual_compression import ContextualCompressionRetriever\n", "from langchain.retrievers.document_compressors import LLMChainExtractor\n", "from langchain_openai import ChatOpenAI\n", "from langchain.prompts import ChatPromptTemplate\n", "from operator import itemgetter\n", "from langchain_core.runnables import RunnablePassthrough, RunnableParallel\n", "from langchain.schema import StrOutputParser\n", "from langchain.chains.combine_documents import create_stuff_documents_chain\n", "from langchain.prompts import MessagesPlaceholder\n", "from langchain.prompts import ChatPromptTemplate\n", "from langchain.chains.history_aware_retriever import create_history_aware_retriever\n", "from langchain.chains.retrieval import create_retrieval_chain\n", "from langchain_core.runnables.history import RunnableWithMessageHistory\n", "from langchain_core.chat_history import BaseChatMessageHistory\n", "from langchain_community.chat_message_histories import ChatMessageHistory\n", "\n", "BOR_FILE_PATH = \"https://www.whitehouse.gov/wp-content/uploads/2022/10/Blueprint-for-an-AI-Bill-of-Rights.pdf\"\n", "NIST_FILE_PATH = \"https://nvlpubs.nist.gov/nistpubs/ai/NIST.AI.600-1.pdf\"\n", "documents_to_preload = [\n", " BOR_FILE_PATH,\n", " NIST_FILE_PATH\n", "]\n", "store = {}\n", "\n", "class PDFLoaderWrapper():\n", " class LoaderType(str, Enum):\n", " PYMUPDF = \"pymupdf\"\n", "\n", " def __init__(self, file_path: str | List[str] , loader_type: LoaderType = LoaderType.PYMUPDF):\n", " self.file_path = file_path if isinstance(file_path, list) else [file_path]\n", " self.loader_type = loader_type\n", "\n", " async def aload(self) -> List[Document]:\n", " all_docs = []\n", " for file_path in self.file_path:\n", " if self.loader_type == self.LoaderType.PYMUPDF:\n", " try:\n", " loader = PyMuPDFLoader(file_path)\n", " docs = await loader.aload()\n", " all_docs.extend(docs)\n", " except Exception as e:\n", " print(f\"Error loading file {file_path}: {e}\")\n", " continue\n", " return all_docs\n", "\n", "async def get_contextual_compressed_retriever(retriver):\n", "\n", " base_retriever = retriver\n", " compressor_llm = ChatOpenAI(temperature=0, model_name=\"gpt-4o\", max_tokens=1500)\n", " compressor = LLMChainExtractor.from_llm(compressor_llm)\n", "\n", " #Combine the retriever with the compressor\n", " compression_retriever = ContextualCompressionRetriever(\n", " base_compressor=compressor,\n", " base_retriever=base_retriever\n", " )\n", " return compression_retriever\n", "\n", "def create_history_aware_retriever_self(chat_model, retriever):\n", " contextualize_q_system_prompt = (\n", " \"Given a chat history and the latest user question which might reference context in the chat history, \"\n", " \"formulate a standalone question which can be understood without the chat history. Do NOT answer the question, \"\n", " \"just reformulate it if needed and otherwise return it as is.\"\n", " )\n", " contextualize_q_prompt = ChatPromptTemplate.from_messages(\n", " [\n", " (\"system\", contextualize_q_system_prompt),\n", " MessagesPlaceholder(\"chat_history\"),\n", " (\"human\", \"{input}\"),\n", " ]\n", " )\n", " return create_history_aware_retriever(chat_model, retriever, contextualize_q_prompt)\n", "\n", "def create_qa_chain(chat_model):\n", " qa_system_prompt = (\n", " \"You are an helpful assistant named 'Shield' and your task is to answer any questions related to AI Safety for the given context.\"\n", " \"Use the following pieces of retrieved context to answer the question.\"\n", " # \"If any questions asked outside AI Safety context, just say that you are a specialist in AI Safety and can't answer that.\"\n", " # f\"When introducing you, just say that you are an AI assistant powered by embedding model {embedding_model_name} and chat model {chat_model_name} and your knowledge is limited to 'Blueprint for an AI Bill of Rights' and 'NIST AI Standards' documents.\"\n", " \"If you don't know the answer, just say that you don't know.\\n\\n\"\n", " \"{context}\"\n", " )\n", " qa_prompt = ChatPromptTemplate.from_messages(\n", " [\n", " (\"system\", qa_system_prompt),\n", " MessagesPlaceholder(\"chat_history\"),\n", " (\"human\", \"{input}\"),\n", " ]\n", " )\n", " return create_stuff_documents_chain(chat_model, qa_prompt)\n", "\n", "def create_conversational_rag_chain(chat_model, retriever):\n", " history_aware_retriever = create_history_aware_retriever_self(chat_model, retriever)\n", " question_answer_chain = create_qa_chain(chat_model)\n", " return create_retrieval_chain(history_aware_retriever, question_answer_chain)\n", "\n", "def get_session_history(session_id: str) -> BaseChatMessageHistory:\n", " if session_id not in store:\n", " store[session_id] = ChatMessageHistory()\n", " return store[session_id]\n", "\n", "\n", "pdf_loader = PDFLoaderWrapper(\n", " documents_to_preload, PDFLoaderWrapper.LoaderType.PYMUPDF\n", ")\n", "documents = await pdf_loader.aload()\n", "\n", "async def create_history_aware_rag_chain_with_params(huggingface_embedding,text_splitter,collection_name,compress:bool=False,conversational:bool =False):\n", "\n", " chunked_docs = text_splitter.split_documents(documents)\n", " dimension = 1024\n", " qdrant_server = os.environ[\"QDRANT_URL\"]\n", " qdrant_client = QdrantClient(url=qdrant_server,api_key=os.environ[\"QDRANT_API_KEY\"])\n", "\n", " # Below fails if collection already exists so make sure to delete the collection first\n", " qdrant_client.create_collection(\n", " collection_name=collection_name,\n", " vectors_config=VectorParams(size=dimension, distance=Distance.COSINE),\n", " )\n", "\n", " vector_store = QdrantVectorStore(\n", " client=qdrant_client,\n", " collection_name=collection_name,\n", " embedding=huggingface_embedding,\n", " )\n", " vector_store.add_documents(chunked_docs)\n", "\n", " retriever = vector_store.as_retriever(search_type=\"similarity_score_threshold\",\n", " search_kwargs={'k':10,'score_threshold': 0.8})\n", " \n", " # Using the same model used in the app.\n", " chat_model_name = \"gpt-4o\"\n", " llm = ChatOpenAI(model=chat_model_name,temperature=0) \n", "\n", " if compress:\n", " contextual_compressed_retriever = await get_contextual_compressed_retriever(retriever)\n", " \n", " if conversational:\n", " history_ai_safety_rag_chain = create_conversational_rag_chain(\n", " llm, \n", " contextual_compressed_retriever if contextual_compressed_retriever else retriever)\n", "\n", " conversational_rag_chain = RunnableWithMessageHistory(\n", " history_ai_safety_rag_chain,\n", " get_session_history,\n", " input_messages_key=\"input\",\n", " history_messages_key=\"chat_history\",\n", " output_messages_key=\"answer\",\n", " )\n", " else:\n", " RAG_PROMPT = \"\"\"\\\n", " Given a provided context and question, you must answer the question based only on context.\n", "\n", " If you cannot answer the question based on the context - you must say \"I don't know\".\n", "\n", " Context: {context}\n", " Question: {question}\n", " \"\"\"\n", "\n", " rag_prompt = ChatPromptTemplate.from_template(RAG_PROMPT)\n", " ai_safety_rag_chain = (\n", " {\"context\": itemgetter(\"question\") | retriever, \"question\": itemgetter(\"input\")}\n", " | rag_prompt | llm | StrOutputParser()\n", " )\n", "\n", " ret = contextual_compressed_retriever if contextual_compressed_retriever else retriever\n", " chain = conversational_rag_chain if conversational_rag_chain else ai_safety_rag_chain\n", " return chain, ret\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# RAGAS Evaluation for Embedding Model" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from langchain.text_splitter import RecursiveCharacterTextSplitter\n", "\n", "# Load the embedding models\n", "base_embedding_model = HuggingFaceEmbeddings(model_name=\"Snowflake/snowflake-arctic-embed-l\")\n", "fine_tuned_embedding_model = HuggingFaceEmbeddings(model_name=\"\")\n", "\n", "# Common splitter to keep variables minimum\n", "recursive_text_splitter = RecursiveCharacterTextSplitter(\n", " chunk_size = 1024,\n", " chunk_overlap = 100,\n", " length_function = len,\n", ")\n", "recursive_chunked_docs = recursive_text_splitter.split_documents(documents)\n", "\n", "# Create two rag chaings with different embeddings\n", "rag_chain_base, retriever_base = await create_history_aware_rag_chain_with_params(\n", " base_embedding_model,\n", " recursive_text_splitter,\n", " \"aichatty-snowflake-arctic-embed-l-recursive-base\"\n", ")\n", "\n", "rag_chain_fine_tuned, retriever_fine_tuned = await create_history_aware_rag_chain_with_params(\n", " fine_tuned_embedding_model,\n", " recursive_text_splitter,\n", " \"aichatty-snowflake-arctic-embed-l-recursive-ft\"\n", ")\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# First Base model chain\n", "\n", "from datasets import Dataset\n", "import time\n", "import uuid\n", "from ragas import evaluate\n", "from ragas.metrics import (\n", " faithfulness,\n", " answer_relevancy,\n", " answer_correctness,\n", " context_recall,\n", " context_precision,\n", ")\n", "\n", "answers = []\n", "contexts = []\n", "\n", "for question in test_questions:\n", " store = {}\n", " session_id = str(uuid.uuid4())\n", "\n", " response = rag_chain_base.invoke({\"input\" : question}, config={\"configurable\": {\"session_id\": session_id}})\n", " answers.append(response[\"answer\"])\n", " contexts.append([context.page_content for context in response[\"context\"]])\n", "\n", "base_chain_response_dataset = Dataset.from_dict({\n", " \"question\" : test_questions,\n", " \"answer\" : answers,\n", " \"contexts\" : contexts,\n", " \"ground_truth\" : test_groundtruths\n", "})\n", "\n", "metrics = [\n", " faithfulness,\n", " answer_relevancy,\n", " context_recall,\n", " context_precision,\n", " answer_correctness,\n", "]\n", "base_chain_eval_results = evaluate(base_chain_response_dataset, metrics)\n", "\n", "base_chain_eval_results_df = base_chain_eval_results.to_pandas()\n", "base_chain_eval_results_df" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# For Fine Tuned model chain\n", "\n", "answers = []\n", "contexts = []\n", "\n", "for question in test_questions:\n", " store = {}\n", " session_id = str(uuid.uuid4())\n", "\n", " response = rag_chain_fine_tuned.invoke({\"input\" : question}, config={\"configurable\": {\"session_id\": session_id}})\n", " answers.append(response[\"answer\"])\n", " contexts.append([context.page_content for context in response[\"context\"]])\n", "\n", "ft_chain_response_dataset = Dataset.from_dict({\n", " \"question\" : test_questions,\n", " \"answer\" : answers,\n", " \"contexts\" : contexts,\n", " \"ground_truth\" : test_groundtruths\n", "})\n", "\n", "ft_chain_eval_results = evaluate(ft_chain_response_dataset, metrics)\n", "\n", "ft_chain_eval_results_df = ft_chain_eval_results.to_pandas()\n", "ft_chain_eval_results_df" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "\n", "# Load your dataframes here\n", "\n", "# Merge on the 'question' column or another common identifier\n", "merged_df = pd.merge(base_chain_eval_results_df, ft_chain_eval_results_df, on='question', suffixes=('_base', '_finetuned'))\n", "\n", "# Now, let's calculate the improvement in relevant metrics, e.g., answer_correctness\n", "merged_df['improvement_answer_correctness'] = merged_df['answer_correctness_finetuned'] - merged_df['answer_correctness_base']\n", "\n", "# Plotting the comparison of correctness between the two models\n", "plt.figure(figsize=(10, 6))\n", "plt.plot(merged_df['question'], merged_df['answer_correctness_base'], label='Base Model', marker='o')\n", "plt.plot(merged_df['question'], merged_df['answer_correctness_finetuned'], label='Fine-tuned Model', marker='x')\n", "plt.xlabel('Questions')\n", "plt.ylabel('Answer Correctness')\n", "plt.title('Comparison of Base Model and Fine-tuned Model on Answer Correctness')\n", "plt.xticks(rotation=90)\n", "plt.legend()\n", "plt.tight_layout()\n", "plt.show()\n", "\n", "# Plotting the improvement\n", "plt.figure(figsize=(10, 6))\n", "plt.bar(merged_df['question'], merged_df['improvement_answer_correctness'], color='green')\n", "plt.xlabel('Questions')\n", "plt.ylabel('Improvement in Answer Correctness')\n", "plt.title('Improvement in Answer Correctness after Fine-tuning')\n", "plt.xticks(rotation=90)\n", "plt.tight_layout()\n", "plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# RAGAS Evaluation for chunking strategy" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from langchain.text_splitter import RecursiveCharacterTextSplitter\n", "\n", "# Using fine_tuned_embedding_model from before\n", "\n", "# First Splitter RecursiveCharacterTextSplitter\n", "recursive_text_splitter = RecursiveCharacterTextSplitter(\n", " chunk_size = 1024,\n", " chunk_overlap = 100,\n", " length_function = len,\n", ")\n", "recursive_chunked_docs = recursive_text_splitter.split_documents(documents)\n", "\n", "# Create two rag chaings with different embeddings\n", "conversational_rag_chain_base, contextual_compressed_retriever_base, retriever_base = await create_history_aware_rag_chain_with_params(\n", " base_embedding_model,recursive_text_splitter,\"aichatty-snowflake-arctic-embed-l-recursive-base\"\n", ")\n", "\n", "conversational_rag_chain_fine_tuned, contextual_compressed_retriever_fine_tuned, retriever_fine_tuned = await create_history_aware_rag_chain_with_params(\n", " fine_tuned_embedding_model,recursive_text_splitter,\"aichatty-snowflake-arctic-embed-l-recursive-ft\"\n", ")\n" ] } ], "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.11.9" } }, "nbformat": 4, "nbformat_minor": 2 }