diff --git a/.env.example b/.env.example index 3642920..741c1f3 100644 --- a/.env.example +++ b/.env.example @@ -1,11 +1,18 @@ +OPENAI_API_TYPE="azure" +OPENAI_API_VERSION="2024-10-01-preview" # Environment variables obtained from Azure OpenAI +AZURE_OPENAI_CHAT_MODEL_NAME="gpt-35-turbo" AZURE_OPENAI_CHAT_DEPLOYMENT_NAME="" +AZURE_OPENAI_EMBEDDINGS_MODEL_NAME="text-embedding-ada-002" AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME="" AZURE_OPENAI_DEPLOYMENT_NAME="" -AZURE_OPENAI_ENDPOINT="" +AZURE_OPENAI_ENDPOINT="https://.openai.azure.com/" AZURE_OPENAI_API_KEY="" # Environment variable obtained from Azure Cosmos DB for MongoDB vCore -AZCOSMOS_CONNSTR="" +AZURE_COSMOS_CONNECTION_STRING="" +AZURE_COSMOS_USERNAME="" +AZURE_COSMOS_PASSWORD="" # Environment variables you set to be used by the code -AZCOSMOS_DATABASE_NAME="" -AZCOSMOS_CONTAINER_NAME="" +AZURE_COSMOS_DATABASE_NAME="" +AZURE_COSMOS_COLLECTION_NAME="" +AZURE_COSMOS_INDEX_NAME="VectorSearchIndex" diff --git a/.vscode/settings.json b/.vscode/settings.json index ee5ea91..5481fcf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,7 +15,6 @@ "**/__pycache__": true, "**/.ruff_cache": true, "**/.mypy_cache": true, - "**/.venv": true, }, "search.exclude": { "**/.venv": true, diff --git a/infra/main.bicep b/infra/main.bicep index 98d9d13..9080f72 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -11,12 +11,18 @@ param location string @description('Id of the user or app to assign application roles') param principalId string = '' +@description('SKU to use for App Service Plan') +param appServiceSku string + var mongoClusterName = '${uniqueString(resourceGroup.id)}-mvcore' var mongoAdminUser = 'admin${uniqueString(resourceGroup.id)}' @secure() @description('Mongo Server administrator password') param mongoAdminPassword string +@description('SKU to use for Cosmos DB for MongoDB vCore Plan') +param mongoServiceSku string + param openAIDeploymentName string = '${name}-openai' param chatGptDeploymentName string = 'chat-gpt' param chatGptDeploymentCapacity int = 30 @@ -109,7 +115,7 @@ module appServicePlan 'core/host/appserviceplan.bicep' = { location: location tags: tags sku: { - name: 'B1' + name: appServiceSku } reserved: true } @@ -126,19 +132,19 @@ module mongoCluster 'core/database/cosmos/mongo/cosmos-mongo-cluster.bicep' = { administratorLoginPassword: mongoAdminPassword storage: 32 nodeCount: 1 - sku: 'M25' + sku: mongoServiceSku allowAzureIPsFirewall: true } } module keyVaultSecrets './core/security/keyvault-secret.bicep' = { dependsOn: [ mongoCluster ] - name: 'keyvault-secret-mongo-connstr' + name: 'keyvault-secret-mongo-password' scope: resourceGroup params: { - name: 'mongoConnectionStr' + name: 'mongoAdminPassword' keyVaultName: keyVault.outputs.name - secretValue: replace(replace(mongoCluster.outputs.connectionStringKey, '', mongoAdminUser), '', mongoAdminPassword) + secretValue: mongoAdminPassword } } @@ -157,15 +163,20 @@ module web 'core/host/appservice.bicep' = { scmDoBuildDuringDeployment: true ftpsState: 'Disabled' managedIdentity: true + use32BitWorkerProcess: appServiceSku == 'F1' + alwaysOn: appServiceSku != 'F1' appSettings: { AZURE_OPENAI_DEPLOYMENT_NAME: openAIDeploymentName AZURE_OPENAI_ENDPOINT: openAi.outputs.endpoint AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: chatGptDeploymentName AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME: embeddingDeploymentName AZURE_OPENAI_API_KEY: '@Microsoft.KeyVault(VaultName=${keyVault.outputs.name};SecretName=cognitiveServiceKey)' - AZCOSMOS_CONNSTR: '@Microsoft.KeyVault(VaultName=${keyVault.outputs.name};SecretName=mongoConnectionStr)' - AZCOSMOS_DATABASE_NAME: 'sk_database' - AZCOSMOS_CONTAINER_NAME: 'sk_collection' + AZURE_COSMOS_PASSWORD: '@Microsoft.KeyVault(VaultName=${keyVault.outputs.name};SecretName=mongoAdminPassword)' + AZURE_COSMOS_CONNECTION_STRING: mongoCluster.outputs.connectionStringKey + AZURE_COSMOS_USERNAME: mongoAdminUser + AZURE_COSMOS_DATABASE_NAME: 'sk_database' + AZURE_COSMOS_COLLECTION_NAME: 'sk_collection' + AZURE_COSMOS_INDEX_NAME: 'sk_index' } } } diff --git a/infra/main.parameters.json b/infra/main.parameters.json index 79b8212..abeec06 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -13,6 +13,12 @@ }, "mongoAdminPassword": { "value": "$(secretOrRandomPassword ${AZURE_KEY_VAULT_NAME} mongoAdminPassword)" + }, + "appServiceSku": { + "value": "${AZURE_APP_SERVICE_SKU=B1}" + }, + "mongoServiceSku":{ + "value": "${AZURE_MONGO_SERVICE_SKU=M25}" } } } diff --git a/rag-azure-openai-cosmosdb-notebook.ipynb b/rag-azure-openai-cosmosdb-notebook.ipynb index 8324642..e4a225b 100644 --- a/rag-azure-openai-cosmosdb-notebook.ipynb +++ b/rag-azure-openai-cosmosdb-notebook.ipynb @@ -13,7 +13,9 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install semantic-kernel==0.9.6b1" + "%pip install semantic-kernel==1.18.0\n", + "%pip install openai==1.58.1\n", + "%pip install pymongo" ] }, { @@ -34,28 +36,10 @@ "cell_type": "code", "execution_count": 1, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Writing .env\n" - ] - } - ], + "outputs": [], "source": [ - "%%writefile .env\n", - "# Environment variables obtained from Azure OpenAI\n", - "AZURE_OPENAI_CHAT_DEPLOYMENT_NAME=\"\"\n", - "AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME=\"\"\n", - "AZURE_OPENAI_DEPLOYMENT_NAME=\"\"\n", - "AZURE_OPENAI_ENDPOINT=\"\"\n", - "AZURE_OPENAI_API_KEY=\"\"\n", - "# Environment variable obtained from Azure Cosmos DB for MongoDB vCore\n", - "AZCOSMOS_CONNSTR=\"\"\n", - "# Environment variables you set to be used by the code\n", - "AZCOSMOS_DATABASE_NAME=\"\"\n", - "AZCOSMOS_CONTAINER_NAME=\"\"" + "# Create .env file if it doesn't exist\n", + "%cp -n .env.example .env" ] }, { @@ -67,7 +51,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -76,17 +60,18 @@ "True" ] }, - "execution_count": 1, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# load the environment variables file\n", - "from dotenv import load_dotenv\n", "import os\n", "\n", - "load_dotenv()" + "from dotenv import load_dotenv\n", + "\n", + "load_dotenv(\".env\", override=True)" ] }, { @@ -104,18 +89,46 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "from urllib.parse import quote_plus\n", + "\n", + "\n", + "# Read and Store Environment variables\n", + "def get_mongo_connection_string():\n", + " mongo_connection_string = os.getenv(\"AZURE_COSMOS_CONNECTION_STRING\", \"\")\n", + " mongo_username = quote_plus(os.getenv(\"AZURE_COSMOS_USERNAME\"))\n", + " mongo_password = quote_plus(os.getenv(\"AZURE_COSMOS_PASSWORD\"))\n", + " return mongo_connection_string.replace(\"\", mongo_username).replace(\"\", mongo_password)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ + "from semantic_kernel.connectors.memory.azure_cosmosdb.utils import (\n", + " CosmosDBSimilarityType,\n", + " CosmosDBVectorSearchType,\n", + ")\n", + "\n", "# collection name will be used multiple times in the code so we store it in a variable\n", - "collection_name = os.environ.get(\"AZCOSMOS_CONTAINER_NAME\")\n", + "mongo_connection_string = get_mongo_connection_string()\n", + "database_name = os.getenv(\"AZURE_COSMOS_DATABASE_NAME\")\n", + "collection_name = os.getenv(\"AZURE_COSMOS_COLLECTION_NAME\")\n", "\n", "# Vector search index parameters\n", - "index_name = \"VectorSearchIndex\"\n", + "index_name = os.getenv(\"AZURE_COSMOS_INDEX_NAME\", \"VectorSearchIndex\")\n", "vector_dimensions = 1536 # text-embedding-ada-002 uses a 1536-dimensional embedding vector\n", - "num_lists = 1\n", - "similarity = \"COS\" # cosine distance" + "num_lists = 100\n", + "similarity = CosmosDBSimilarityType.COS\n", + "kind = CosmosDBVectorSearchType.VECTOR_HNSW\n", + "m = 16\n", + "ef_construction = 64\n", + "ef_search = 40" ] }, { @@ -138,13 +151,14 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "import json\n", - "from semantic_kernel.memory.semantic_text_memory import SemanticTextMemory\n", + "\n", "from semantic_kernel.memory.memory_store_base import MemoryStoreBase\n", + "from semantic_kernel.memory.semantic_text_memory import SemanticTextMemory\n", "\n", "\n", "async def upsert_data_to_memory_store(memory: SemanticTextMemory, store: MemoryStoreBase, data_file_path: str) -> None:\n", @@ -160,7 +174,7 @@ " Returns:\n", " None. The function performs an operation that modifies the memory stores in-place.\n", " \"\"\"\n", - " with open(file=data_file_path, mode=\"r\", encoding=\"utf-8\") as f:\n", + " with open(file=data_file_path, encoding=\"utf-8\") as f:\n", " data = json.load(f)\n", " n = 0\n", " for item in data:\n", @@ -207,13 +221,13 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "from semantic_kernel import Kernel\n", "\n", - "# Intialize the kernel\n", + "# Initialize the kernel\n", "kernel = Kernel()" ] }, @@ -228,7 +242,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -247,7 +261,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -260,9 +274,9 @@ ], "source": [ "# adding azure openai chat service\n", - "chat_model_deployment_name = os.environ.get(\"AZURE_OPENAI_CHAT_DEPLOYMENT_NAME\")\n", - "endpoint = os.environ.get(\"AZURE_OPENAI_ENDPOINT\")\n", - "api_key = os.environ.get(\"AZURE_OPENAI_API_KEY\")\n", + "chat_model_deployment_name = os.getenv(\"AZURE_OPENAI_CHAT_DEPLOYMENT_NAME\")\n", + "endpoint = os.getenv(\"AZURE_OPENAI_ENDPOINT\")\n", + "api_key = os.getenv(\"AZURE_OPENAI_API_KEY\")\n", "\n", "kernel.add_service(\n", " AzureChatCompletion(\n", @@ -284,7 +298,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -297,7 +311,7 @@ ], "source": [ "# adding azure openai text embedding service\n", - "embedding_model_deployment_name = os.environ.get(\"AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME\")\n", + "embedding_model_deployment_name = os.getenv(\"AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME\")\n", "\n", "kernel.add_service(\n", " AzureTextEmbedding(\n", @@ -330,14 +344,28 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Creating or updating Azure Cosmos DB Memory Store...\n", + "Creating or updating Azure Cosmos DB Memory Store...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/john0isaac/Developer/rag-semantic-kernel-mongodb-vcore/.venv/lib/python3.10/site-packages/semantic_kernel/connectors/memory/azure_cosmosdb/azure_cosmos_db_memory_store.py:102: UserWarning: You appear to be connected to a CosmosDB cluster. For more information regarding feature compatibility and support please visit https://www.mongodb.com/supportability/cosmosdb\n", + " mongodb_client = MongoClient(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "Finished updating Azure Cosmos DB Memory Store...\n" ] } @@ -351,14 +379,18 @@ "# create azure cosmos db for mongo db vcore api store and collection with vector ivf\n", "# currently, semantic kernel only supports the ivf vector kind\n", "store = await AzureCosmosDBMemoryStore.create(\n", - " cosmos_connstr=os.environ.get(\"AZCOSMOS_CONNSTR\"),\n", + " cosmos_connstr=mongo_connection_string,\n", " cosmos_api=\"mongo-vcore\",\n", - " database_name=os.environ.get(\"AZCOSMOS_DATABASE_NAME\"),\n", + " database_name=database_name,\n", " collection_name=collection_name,\n", " index_name=index_name,\n", " vector_dimensions=vector_dimensions,\n", " num_lists=num_lists,\n", " similarity=similarity,\n", + " kind=kind,\n", + " m=m,\n", + " ef_construction=ef_construction,\n", + " ef_search=ef_search,\n", ")\n", "print(\"Finished updating Azure Cosmos DB Memory Store...\")" ] @@ -372,7 +404,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -384,8 +416,8 @@ } ], "source": [ - "from semantic_kernel.memory.semantic_text_memory import SemanticTextMemory\n", "from semantic_kernel.core_plugins.text_memory_plugin import TextMemoryPlugin\n", + "from semantic_kernel.memory.semantic_text_memory import SemanticTextMemory\n", "\n", "memory = SemanticTextMemory(storage=store, embeddings_generator=kernel.get_service(\"text_embedding\"))\n", "kernel.add_plugin(TextMemoryPlugin(memory), \"TextMemoryPluginACDB\")\n", @@ -419,7 +451,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -456,7 +488,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -467,7 +499,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -475,7 +507,7 @@ "output_type": "stream", "text": [ "Result is: The Godfather: The aging patriarch of an organized crime dynasty transfers control of his clandestine empire to his reluctant son.\n", - "Relevance Score: 0.875003419815884\n", + "Relevance Score: 0.875689260702724\n", "Full Record: {\"text\": \"The Godfather: The aging patriarch of an organized crime dynasty transfers control of his clandestine empire to his reluctant son.\", \"description\": \"The Godfather\", \"additional_metadata\": null}\n" ] } @@ -495,7 +527,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -510,7 +542,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ @@ -523,7 +555,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -544,7 +576,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -555,13 +587,12 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "from semantic_kernel.functions import KernelArguments\n", "\n", - "\n", "completions_result = await kernel.invoke(\n", " chat_function, KernelArguments(query_term=query_term, db_record=result[0].additional_metadata)\n", ")" @@ -569,7 +600,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 20, "metadata": {}, "outputs": [ { @@ -593,7 +624,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -608,22 +639,24 @@ "Question:\n", "Do you know any crime dynasty movies?\n", "Response:\n", - "Yes, \"The Godfather\" is a classic crime dynasty movie.\n", + "Yes, \"The Godfather\" is a crime dynasty movie.\n", "\n", "Question:\n", "can you recommend me movies like the god father?\n", "Response:\n", - "Sure, if you enjoyed The Godfather, you might also like movies such as Goodfellas, The Departed, Scarface, and The Sopranos (TV series).\n", + "Sure! If you enjoyed \"The Godfather,\" you might also like these movies:\n", + "1. \"Goodfellas\" (1990) - A story about the rise and fall of a mob associate in the Italian-American crime syndicate.\n", + "2. \"Scarface\" (1983) - A tale of a Cuban immigrant who becomes a powerful drug lord in Miami.\n", + "3. \"The Departed\" (2006) - A gripping crime thriller about an undercover cop and a mole in the police force.\n", + "4. \"Casino\" (1995) - A film that explores the inner workings of a Las Vegas casino and the mob's influence on it.\n", + "5. \"Once Upon a Time in America\" (1984) - A saga spanning several decades, following a group of Jewish gangsters in New York City.\n", + "\n", + "These movies share similar themes of organized crime, family dynamics, and the complex world of the mafia. Enjoy!\n", "\n", "Question:\n", "thanks, bye!\n", "Response:\n", "You're welcome! Goodbye!\n", - "\n", - "Question:\n", - "exit\n", - "Response:\n", - "Goodbye!\n", "\n" ] } @@ -637,6 +670,8 @@ "\n", "while query_term != \"exit\":\n", " query_term = input(\"Enter a query: \")\n", + " if query_term == \"exit\":\n", + " break\n", " search_result = await memory.search(collection_name, query_term)\n", " completions_result = kernel.invoke_stream(\n", " chat_function, KernelArguments(query_term=query_term, db_record=search_result[0].additional_metadata)\n", @@ -665,7 +700,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 23, "metadata": {}, "outputs": [], "source": [ @@ -682,7 +717,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 24, "metadata": {}, "outputs": [], "source": [ @@ -699,13 +734,15 @@ ")\n", "\n", "chat_history_function = kernel.add_function(\n", - " function_name=\"ChatGPTFuncHist\", plugin_name=\"chatGPTPluginHist\", prompt_template_config=chat_prompt_hist_template_config\n", + " function_name=\"ChatGPTFuncHist\",\n", + " plugin_name=\"chatGPTPluginHist\",\n", + " prompt_template_config=chat_prompt_hist_template_config,\n", ")" ] }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 25, "metadata": {}, "outputs": [], "source": [ @@ -717,7 +754,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 26, "metadata": {}, "outputs": [ { @@ -732,35 +769,24 @@ "Question:\n", "Do you know any comedy movies?\n", "Response:\n", - "Yes, I can recommend some comedy movies for you. What type of comedy are you in the mood for? Romantic comedy, slapstick, satire, parody, or something else?\n", - "\n", - "Question:\n", - "paradoy\n", - "Response:\n", - "Great choice! Here are some parody movies that you might enjoy:\n", + "Yes, I can recommend some comedy movies for you. Here are a few popular ones:\n", "\n", - "1. Airplane! (1980)\n", - "2. The Naked Gun: From the Files of Police Squad! (1988)\n", - "3. This Is Spinal Tap (1984)\n", - "4. Austin Powers: International Man of Mystery (1997)\n", - "5. Spaceballs (1987)\n", - "6. Robin Hood: Men in Tights (1993)\n", - "7. Hot Shots! (1991)\n", - "8. Scary Movie (2000)\n", - "9. Shaun of the Dead (2004)\n", - "10. The Princess Bride (1987)\n", + "1. \"Anchorman: The Legend of Ron Burgundy\" - A hilarious comedy about a 1970s news anchor and his eccentric news team.\n", + "2. \"Superbad\" - A coming-of-age comedy about two high school friends who embark on a wild night of partying before graduation.\n", + "3. \"Bridesmaids\" - A comedy about the lead-up to a wedding, filled with hilarious mishaps and memorable characters.\n", + "4. \"The Hangover\" - A comedy about a group of friends who wake up after a wild bachelor party in Las Vegas, trying to piece together what happened.\n", + "5. \"Step Brothers\" - A comedy about two middle-aged men who become stepbrothers and wreak havoc on their parents' lives.\n", "\n", - "I hope you find one that you enjoy!\n", + "I hope you find these recommendations enjoyable! Let me know if you need more suggestions.\n", "\n", "Question:\n", "can you tell me more about the first movie?\n", "Response:\n", - "Sure! \"Airplane!\" is a classic comedy movie from 1980 that is a parody of disaster movies. The movie follows the story of a former fighter pilot named Ted Striker, who is traumatized by his experiences in the war and is now afraid to fly. However, when the passengers and crew of a commercial airliner fall ill from food poisoning, Ted must overcome his fears and land the plane safely. The movie is known for its absurd humor, visual gags, and puns, and features a star-studded cast including Leslie Nielsen, Robert Hays, and Julie Hagerty. It's definitely worth a watch if you're in the mood for a good laugh!\n", + "Certainly! \"Anchorman: The Legend of Ron Burgundy\" is a comedy film released in 2004. It is set in the 1970s and follows the story of Ron Burgundy, a charismatic and egotistical news anchor in San Diego. Ron is the top-rated newsman in the city, but his world is turned upside down when a talented and ambitious female reporter named Veronica Corningstone joins the news team.\n", "\n", - "Question:\n", - "exit\n", - "Response:\n", - "Goodbye! Don't hesitate to come back if you have any more questions or if you need any assistance.\n", + "The movie showcases Ron's hilarious attempts to maintain his dominance in the news industry while dealing with the challenges of working alongside Veronica. It features a talented ensemble cast including Will Ferrell as Ron Burgundy, Christina Applegate as Veronica Corningstone, and Steve Carell, Paul Rudd, and David Koechner as Ron's eccentric news team.\n", + "\n", + "\"Anchorman: The Legend of Ron Burgundy\" is known for its absurd humor, quotable lines, and memorable characters. It has become a cult classic and is beloved by fans of comedy films. If you enjoy witty and offbeat humor, this movie is definitely worth a watch!\n", "\n" ] } @@ -774,13 +800,16 @@ "\n", "while query_term != \"exit\":\n", " query_term = input(\"Enter a query: \")\n", + " if query_term == \"exit\":\n", + " break\n", " chat_history.add_user_message(query_term)\n", "\n", - " search_result = await memory.search(collection_name, query_term) # vector search\n", - " \n", + " search_result = await memory.search(collection_name, query_term) # vector search\n", + "\n", " completions_result = await kernel.invoke(\n", - " chat_history_function, KernelArguments(query_term=query_term, db_record=search_result[0].additional_metadata, history=chat_history)\n", - " ) # RAG\n", + " chat_history_function,\n", + " KernelArguments(query_term=query_term, db_record=search_result[0].additional_metadata, history=chat_history),\n", + " ) # RAG\n", " chat_history.add_assistant_message(str(completions_result))\n", "\n", " print(f\"Question:\\n{query_term}\\nResponse:\")\n", @@ -798,27 +827,26 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "You are a helpful chatbot who is good about giving movie recommendations.HeyHello! How can I assist you today?Do you know any comedy movies?Yes, I can recommend some comedy movies for you. What type of comedy are you in the mood for? Romantic comedy, slapstick, satire, parody, or something else?paradoyGreat choice! Here are some parody movies that you might enjoy:\n", + "You are a helpful chatbot who is good about giving movie recommendations.HeyHello! How can I assist you today?Do you know any comedy movies?Yes, I can recommend some comedy movies for you. Here are a few popular ones:\n", + "\n", + "1. \"Anchorman: The Legend of Ron Burgundy\" - A hilarious comedy about a 1970s news anchor and his eccentric news team.\n", + "2. \"Superbad\" - A coming-of-age comedy about two high school friends who embark on a wild night of partying before graduation.\n", + "3. \"Bridesmaids\" - A comedy about the lead-up to a wedding, filled with hilarious mishaps and memorable characters.\n", + "4. \"The Hangover\" - A comedy about a group of friends who wake up after a wild bachelor party in Las Vegas, trying to piece together what happened.\n", + "5. \"Step Brothers\" - A comedy about two middle-aged men who become stepbrothers and wreak havoc on their parents' lives.\n", + "\n", + "I hope you find these recommendations enjoyable! Let me know if you need more suggestions.can you tell me more about the first movie?Certainly! \"Anchorman: The Legend of Ron Burgundy\" is a comedy film released in 2004. It is set in the 1970s and follows the story of Ron Burgundy, a charismatic and egotistical news anchor in San Diego. Ron is the top-rated newsman in the city, but his world is turned upside down when a talented and ambitious female reporter named Veronica Corningstone joins the news team.\n", "\n", - "1. Airplane! (1980)\n", - "2. The Naked Gun: From the Files of Police Squad! (1988)\n", - "3. This Is Spinal Tap (1984)\n", - "4. Austin Powers: International Man of Mystery (1997)\n", - "5. Spaceballs (1987)\n", - "6. Robin Hood: Men in Tights (1993)\n", - "7. Hot Shots! (1991)\n", - "8. Scary Movie (2000)\n", - "9. Shaun of the Dead (2004)\n", - "10. The Princess Bride (1987)\n", + "The movie showcases Ron's hilarious attempts to maintain his dominance in the news industry while dealing with the challenges of working alongside Veronica. It features a talented ensemble cast including Will Ferrell as Ron Burgundy, Christina Applegate as Veronica Corningstone, and Steve Carell, Paul Rudd, and David Koechner as Ron's eccentric news team.\n", "\n", - "I hope you find one that you enjoy!can you tell me more about the first movie?Sure! \"Airplane!\" is a classic comedy movie from 1980 that is a parody of disaster movies. The movie follows the story of a former fighter pilot named Ted Striker, who is traumatized by his experiences in the war and is now afraid to fly. However, when the passengers and crew of a commercial airliner fall ill from food poisoning, Ted must overcome his fears and land the plane safely. The movie is known for its absurd humor, visual gags, and puns, and features a star-studded cast including Leslie Nielsen, Robert Hays, and Julie Hagerty. It's definitely worth a watch if you're in the mood for a good laugh!exitGoodbye! Don't hesitate to come back if you have any more questions or if you need any assistance.\n" + "\"Anchorman: The Legend of Ron Burgundy\" is known for its absurd humor, quotable lines, and memorable characters. It has become a cult classic and is beloved by fans of comedy films. If you enjoy witty and offbeat humor, this movie is definitely worth a watch!\n" ] } ], @@ -843,7 +871,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.2" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/src/pyproject.toml b/src/pyproject.toml index a33162a..73862f6 100644 --- a/src/pyproject.toml +++ b/src/pyproject.toml @@ -4,10 +4,12 @@ version = "1.0.0" description = "Demo Chat Application to demonstrate retrieval augmented generation using Azure Open AI, Azure Cosmos DB for MongoDB vCore, and semantic kernel." dependencies = [ "Quart", - "semantic-kernel==0.9.6b1", + "semantic-kernel==1.18.0", "python-dotenv", "Hypercorn", - ] + "pymongo", + "motor", +] [build-system] requires = ["flit_core<4"] diff --git a/src/quartapp/app.py b/src/quartapp/app.py index 8c5eedc..be59010 100644 --- a/src/quartapp/app.py +++ b/src/quartapp/app.py @@ -86,6 +86,6 @@ async def chat_handler() -> Any: # Or specify port manually: """ if __name__ == '__main__': - port = int(os.environ.get('PORT', 5000)) + port = int(os.getenv('PORT', 5000)) app.run(host='0.0.0.0', port=port) """ diff --git a/src/quartapp/rag.py b/src/quartapp/rag.py index 6b09a70..79c5c47 100644 --- a/src/quartapp/rag.py +++ b/src/quartapp/rag.py @@ -1,5 +1,6 @@ import logging import os +from urllib.parse import quote_plus from pymongo.errors import ServerSelectionTimeoutError from semantic_kernel import Kernel @@ -11,6 +12,10 @@ from semantic_kernel.connectors.memory.azure_cosmosdb import ( # type: ignore [import-untyped] AzureCosmosDBMemoryStore, ) +from semantic_kernel.connectors.memory.azure_cosmosdb.utils import ( + CosmosDBSimilarityType, + CosmosDBVectorSearchType, +) from semantic_kernel.core_plugins.text_memory_plugin import TextMemoryPlugin # type: ignore [import-untyped] from semantic_kernel.exceptions import FunctionExecutionException, KernelInvokeException, ServiceResponseException from semantic_kernel.functions import KernelArguments, KernelFunction, KernelFunctionMetadata @@ -31,13 +36,25 @@ # collection name will be used multiple times in the code so we store it in a variable -collection_name = os.environ.get("AZCOSMOS_CONTAINER_NAME") or "sk_collection" +database_name = os.getenv("AZURE_COSMOS_DATABASE_NAME", "semanticKernel") +collection_name = os.getenv("AZURE_COSMOS_COLLECTION_NAME", "textMemory") # Vector search index parameters -index_name = "VectorSearchIndex" +index_name = os.getenv("AZURE_COSMOS_INDEX_NAME", "VectorSearchIndex") vector_dimensions = 1536 # text-embedding-ada-002 uses a 1536-dimensional embedding vector -num_lists = 1 -similarity = "COS" # cosine distance +num_lists = 100 +similarity = CosmosDBSimilarityType.COS +kind = CosmosDBVectorSearchType.VECTOR_HNSW +m = 16 +ef_construction = 64 +ef_search = 40 + + +def get_mongo_connection_string() -> str: + mongo_connection_string = os.getenv("AZURE_COSMOS_CONNECTION_STRING", "") + mongo_username = quote_plus(os.getenv("AZURE_COSMOS_USERNAME", "admin")) + mongo_password = quote_plus(os.getenv("AZURE_COSMOS_PASSWORD", "password")) + return mongo_connection_string.replace("", mongo_username).replace("", mongo_password) async def prompt_with_rag_or_vector(query_term: str, option: str) -> str: @@ -59,9 +76,9 @@ async def prompt_with_rag_or_vector(query_term: str, option: str) -> str: def initialize_sk_chat_embedding() -> Kernel: kernel = Kernel() # adding azure openai chat service - chat_model_deployment_name = os.environ.get("AZURE_OPENAI_CHAT_DEPLOYMENT_NAME") or "chat-deployment" - endpoint = os.environ.get("AZURE_OPENAI_ENDPOINT") or "https://test-endpoint.openai.com/" - api_key = os.environ.get("AZURE_OPENAI_API_KEY") or "VerySecretApiKey" + chat_model_deployment_name = os.getenv("AZURE_OPENAI_CHAT_DEPLOYMENT_NAME", "chat-deployment") + endpoint = os.getenv("AZURE_OPENAI_ENDPOINT", "https://test-endpoint.openai.com/") + api_key = os.getenv("AZURE_OPENAI_API_KEY", "VerySecretApiKey") kernel.add_service( AzureChatCompletion( @@ -74,9 +91,7 @@ def initialize_sk_chat_embedding() -> Kernel: logging.info("Added Azure OpenAI Chat Service...") # adding azure openai text embedding service - embedding_model_deployment_name = ( - os.environ.get("AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME") or "embedding-deployment" - ) + embedding_model_deployment_name = os.getenv("AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME", "embedding-deployment") kernel.add_service( AzureTextEmbedding( @@ -100,14 +115,18 @@ async def initialize_sk_memory_store( try: logging.info("Creating or updating Azure Cosmos DB Memory Store...") store = await AzureCosmosDBMemoryStore.create( - cosmos_connstr=os.environ.get("AZCOSMOS_CONNSTR") or "connection-string", + cosmos_connstr=get_mongo_connection_string(), cosmos_api="mongo-vcore", - database_name=os.environ.get("AZCOSMOS_DATABASE_NAME") or "sk_database", + database_name=database_name, collection_name=collection_name, index_name=index_name, vector_dimensions=vector_dimensions, num_lists=num_lists, similarity=similarity, + kind=kind, + m=m, + ef_construction=ef_construction, + ef_search=ef_search, ) logging.info("Finished updating Azure Cosmos DB Memory Store...") @@ -139,7 +158,7 @@ async def grounded_response(kernel: Kernel) -> KernelFunction: User: {{$query_term}} Chatbot:""" - chat_model_deployment_name = os.environ.get("AZURE_OPENAI_CHAT_DEPLOYMENT_NAME") + chat_model_deployment_name = os.getenv("AZURE_OPENAI_CHAT_DEPLOYMENT_NAME", "chat-deployment") execution_settings = OpenAITextPromptExecutionSettings( service_id="chat_completion", diff --git a/src/requirements.in b/src/requirements.in index 0e0e4ca..484807e 100644 --- a/src/requirements.in +++ b/src/requirements.in @@ -1,4 +1,6 @@ -semantic-kernel==1.1.2 +semantic-kernel==1.18.0 +pymongo +motor python-dotenv Quart Hypercorn diff --git a/src/requirements.txt b/src/requirements.txt index 488d6de..90c06aa 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.10 # by the following command: # # pip-compile src/requirements.in @@ -18,11 +18,17 @@ anyio==4.3.0 # openai asgiref==3.8.1 # via openapi-core +async-timeout==4.0.3 + # via aiohttp attrs==23.2.0 # via # aiohttp # jsonschema # referencing +azure-core==1.32.0 + # via azure-identity +azure-identity==1.19.0 + # via semantic-kernel blinker==1.7.0 # via # flask @@ -32,6 +38,8 @@ certifi==2024.2.2 # httpcore # httpx # requests +cffi==1.17.1 + # via cryptography chardet==5.2.0 # via prance charset-normalizer==3.3.2 @@ -40,20 +48,36 @@ click==8.1.7 # via # flask # quart +cloudevents==1.11.0 + # via semantic-kernel +cryptography==44.0.0 + # via + # azure-identity + # msal + # pyjwt defusedxml==0.7.1 # via semantic-kernel +deprecated==1.2.15 + # via + # opentelemetry-api + # opentelemetry-semantic-conventions +deprecation==2.1.0 + # via cloudevents distro==1.9.0 # via openai -dnspython==2.6.1 +dnspython==2.7.0 # via pymongo +exceptiongroup==1.2.2 + # via + # anyio + # hypercorn + # taskgroup flask==3.0.3 # via quart frozenlist==1.4.1 # via # aiohttp # aiosignal -grpcio==1.62.1 - # via semantic-kernel h11==0.14.0 # via # httpcore @@ -79,6 +103,8 @@ idna==3.7 # httpx # requests # yarl +importlib-metadata==8.5.0 + # via opentelemetry-api isodate==0.6.1 # via openapi-core itsdangerous==2.1.2 @@ -90,6 +116,8 @@ jinja2==3.1.3 # flask # quart # semantic-kernel +jiter==0.8.2 + # via openai jsonschema==4.21.1 # via # openapi-core @@ -112,8 +140,14 @@ markupsafe==2.1.5 # werkzeug more-itertools==10.2.0 # via openapi-core -motor==3.4.0 - # via semantic-kernel +motor==3.6.0 + # via -r src/requirements.in +msal==1.31.1 + # via + # azure-identity + # msal-extensions +msal-extensions==1.2.0 + # via azure-identity multidict==6.0.5 # via # aiohttp @@ -121,10 +155,8 @@ multidict==6.0.5 nest-asyncio==1.6.0 # via semantic-kernel numpy==1.26.4 - # via - # scipy - # semantic-kernel -openai==1.17.0 + # via semantic-kernel +openai==1.58.1 # via semantic-kernel openapi-core==0.18.2 # via semantic-kernel @@ -134,20 +166,35 @@ openapi-schema-validator==0.6.2 # openapi-spec-validator openapi-spec-validator==0.7.1 # via openapi-core +opentelemetry-api==1.29.0 + # via + # opentelemetry-sdk + # opentelemetry-semantic-conventions + # semantic-kernel +opentelemetry-sdk==1.29.0 + # via semantic-kernel +opentelemetry-semantic-conventions==0.50b0 + # via opentelemetry-sdk packaging==24.0 - # via prance + # via + # deprecation + # prance parse==1.20.1 # via openapi-core pathable==0.4.3 # via # jsonschema-path # jsonschema-spec +portalocker==2.10.1 + # via msal-extensions prance==23.6.21.0 # via semantic-kernel priority==2.0.0 # via hypercorn pybars4==0.9.13 # via semantic-kernel +pycparser==2.22 + # via cffi pydantic==2.7.0 # via # openai @@ -157,10 +204,16 @@ pydantic-core==2.18.1 # via pydantic pydantic-settings==2.2.1 # via semantic-kernel +pyjwt[crypto]==2.10.1 + # via + # msal + # pyjwt pymeta3==0.5.1 # via pybars4 -pymongo==4.6.3 - # via motor +pymongo==4.9.2 + # via + # -r src/requirements.in + # motor python-dotenv==1.0.1 # via # -r src/requirements.in @@ -177,12 +230,12 @@ referencing==0.30.2 # jsonschema-path # jsonschema-spec # jsonschema-specifications -regex==2023.12.25 - # via semantic-kernel requests==2.31.0 # via + # azure-core # jsonschema-path # jsonschema-spec + # msal # prance rfc3339-validator==0.1.4 # via openapi-schema-validator @@ -194,12 +247,11 @@ ruamel-yaml==0.18.6 # via prance ruamel-yaml-clib==0.2.8 # via ruamel-yaml -scipy==1.13.0 - # via semantic-kernel -semantic-kernel==1.1.2 +semantic-kernel==1.18.0 # via -r src/requirements.in six==1.16.0 # via + # azure-core # isodate # prance # rfc3339-validator @@ -208,13 +260,24 @@ sniffio==1.3.1 # anyio # httpx # openai +taskgroup==0.2.1 + # via hypercorn +tomli==2.2.1 + # via hypercorn tqdm==4.66.2 # via openai -typing-extensions==4.11.0 +typing-extensions==4.12.2 # via + # anyio + # asgiref + # azure-core + # azure-identity + # hypercorn # openai + # opentelemetry-sdk # pydantic # pydantic-core + # taskgroup urllib3==2.2.1 # via requests werkzeug==3.0.2 @@ -222,7 +285,11 @@ werkzeug==3.0.2 # flask # openapi-core # quart +wrapt==1.17.0 + # via deprecated wsproto==1.2.0 # via hypercorn yarl==1.9.4 # via aiohttp +zipp==3.21.0 + # via importlib-metadata diff --git a/src/scripts/add_data.py b/src/scripts/add_data.py index af2bd3e..82ea7df 100644 --- a/src/scripts/add_data.py +++ b/src/scripts/add_data.py @@ -49,7 +49,7 @@ async def upsert_data_to_memory_store( description_field_name: str, ) -> None: # collection name will be used multiple times in the code so we store it in a variable - collection_name: str = os.environ.get("AZCOSMOS_CONTAINER_NAME") or "sk_collection" + collection_name: str = os.getenv("AZURE_COSMOS_COLLECTION_NAME", "sk_collection") with open(file=data_file_path, encoding="utf-8") as f: data = json.load(f)