2-notebooks/4-frameworks/1-rag-sk-agents-aisearch.ipynb (470 lines of code) (raw):

{ "cells": [ { "cell_type": "markdown", "id": "intro-md", "metadata": {}, "source": [ "# 🏋️ Azure AI Search + Semantic Kernel + AI Agents: Fitness-Fun Workshop 🤸\n", "\n", "Welcome to this self-guided workshop where you'll:\n", "\n", "1. **Create** an Azure AI Search index containing some sample fitness equipment data\n", "2. **Upload** and verify your documents\n", "3. **Create** a Semantic Kernel Agent (powered by Azure AI Agent Service) that uses the Azure AI Search tool\n", "4. **Run** an asynchronous conversation to query your index (with a fun fitness twist)\n", "\n", "> **Note:** This demo uses Semantic Kernel’s abstractions over Azure AI Agents. Make sure you run:\n", ">\n", "> ```bash\n", "> pip install semantic-kernel[azure]\n", "> ```\n", "\n", "Also ensure you’ve set your environment variables for:\n", "\n", "- `PROJECT_CONNECTION_STRING`\n", "- `MODEL_DEPLOYMENT_NAME`\n", "\n", "Let’s get started!" ] }, { "cell_type": "markdown", "id": "prereq-md", "metadata": {}, "source": [ "## Prerequisites\n", "\n", "Before running the cells below, please verify:\n", "\n", "1. You have installed the required dependency:\n", " ```bash\n", " pip install semantic-kernel[azure]\n", " ```\n", "2. Your environment is configured with the necessary variables (`PROJECT_CONNECTION_STRING` and `MODEL_DEPLOYMENT_NAME`)." ] }, { "cell_type": "code", "execution_count": 7, "id": "install-deps", "metadata": {}, "outputs": [], "source": [ "# Uncomment and run the cell below if you have not installed the dependency yet\n", "# !pip install semantic-kernel[azure]" ] }, { "cell_type": "markdown", "id": "index-intro-md", "metadata": {}, "source": [ "## 1. Create & Populate Azure AI Search Index\n", "\n", "In this section we will:\n", "\n", "1. **Create** an Azure AI Search index called `myfitnessindex` with a schema suited for fitness items\n", "2. **Upload** sample documents containing fitness equipment data\n", "3. **Verify** that the documents are searchable\n", "\n", "Make sure your environment has the appropriate search credentials (typically obtained via your AI Foundry project)." ] }, { "cell_type": "code", "execution_count": null, "id": "init-search-clients", "metadata": {}, "outputs": [], "source": [ "import os\n", "import sys\n", "from pathlib import Path\n", "from dotenv import load_dotenv\n", "from azure.core.credentials import AzureKeyCredential\n", "from azure.search.documents.indexes import SearchIndexClient\n", "from azure.search.documents.indexes.models import SearchIndex, SimpleField, SearchFieldDataType, SearchableField\n", "from azure.search.documents import SearchClient\n", "from azure.identity import DefaultAzureCredential\n", "from azure.ai.projects import AIProjectClient\n", "from azure.ai.projects.models import ConnectionType\n", "\n", "# Load environment variables\n", "notebook_path = Path().absolute()\n", "env_path = notebook_path.parent.parent / '.env' # Adjust path as needed\n", "load_dotenv(env_path)\n", "\n", "connection_string = os.environ.get(\"PROJECT_CONNECTION_STRING\")\n", "if not connection_string:\n", " raise ValueError(\"🚨 PROJECT_CONNECTION_STRING not set in .env.\")\n", "\n", "# Initialize the AI Project client to access project resources\n", "try:\n", " project_client = AIProjectClient.from_connection_string(\n", " credential=DefaultAzureCredential(),\n", " conn_str=connection_string\n", " )\n", " print(\"✅ Initialized AIProjectClient\")\n", "except Exception as e:\n", " print(f\"❌ Error initializing AIProjectClient: {e}\")\n", "\n", "# Get the Azure AI Search connection details from the project (including endpoint and API key)\n", "search_conn = project_client.connections.get_default(\n", " connection_type=ConnectionType.AZURE_AI_SEARCH,\n", " include_credentials=True\n", ")\n", "\n", "if not search_conn:\n", " raise RuntimeError(\"❌ No default Azure AI Search connection found in your project.\")\n", "\n", "# Define the index name for our fitness data\n", "index_name = \"myfitnessindex\"\n", "\n", "try:\n", " credential = AzureKeyCredential(search_conn.key)\n", " index_client = SearchIndexClient(endpoint=search_conn.endpoint_url, credential=credential)\n", " print(\"✅ Created SearchIndexClient\")\n", " \n", " search_client = SearchClient(\n", " endpoint=search_conn.endpoint_url,\n", " index_name=index_name,\n", " credential=credential\n", " )\n", " print(\"✅ Created SearchClient for document operations\")\n", "except Exception as e:\n", " print(f\"❌ Error creating search clients: {e}\")" ] }, { "cell_type": "markdown", "id": "schema-md", "metadata": {}, "source": [ "### Define the Index Schema\n", "\n", "We will create an index with the following fields:\n", "\n", "- `FitnessItemID`: Unique key\n", "- `Name`: Searchable text field (also filterable)\n", "- `Category`: Searchable, filterable, and facetable (e.g. Strength, Cardio, Flexibility)\n", "- `Price`: Numeric field (filterable, sortable, and facetable)\n", "- `Description`: Full-text searchable field" ] }, { "cell_type": "code", "execution_count": null, "id": "create-index", "metadata": {}, "outputs": [], "source": [ "def create_fitness_index():\n", " fields = [\n", " SimpleField(name=\"FitnessItemID\", type=SearchFieldDataType.String, key=True),\n", " SearchableField(name=\"Name\", type=SearchFieldDataType.String, filterable=True),\n", " SearchableField(name=\"Category\", type=SearchFieldDataType.String, filterable=True, facetable=True),\n", " SimpleField(name=\"Price\", type=SearchFieldDataType.Double, filterable=True, sortable=True, facetable=True),\n", " SearchableField(name=\"Description\", type=SearchFieldDataType.String)\n", " ]\n", "\n", " index = SearchIndex(name=index_name, fields=fields)\n", "\n", " # Delete the index if it already exists (for a fresh start)\n", " if index_name in [x.name for x in index_client.list_indexes()]:\n", " index_client.delete_index(index_name)\n", " print(f\"🗑️ Deleted existing index: {index_name}\")\n", "\n", " created = index_client.create_index(index)\n", " print(f\"🎉 Created index: {created.name}\")\n", "\n", "# Create the index\n", "create_fitness_index()" ] }, { "cell_type": "markdown", "id": "upload-docs-md", "metadata": {}, "source": [ "### Upload Sample Documents\n", "\n", "Now we’ll add some sample fitness items to `myfitnessindex`." ] }, { "cell_type": "code", "execution_count": null, "id": "upload-docs", "metadata": {}, "outputs": [], "source": [ "def upload_fitness_docs():\n", " search_client = SearchClient(\n", " endpoint=search_conn.endpoint_url,\n", " index_name=index_name,\n", " credential=AzureKeyCredential(search_conn.key)\n", " )\n", "\n", " sample_docs = [\n", " {\n", " \"FitnessItemID\": \"1\",\n", " \"Name\": \"Adjustable Dumbbell\",\n", " \"Category\": \"Strength\",\n", " \"Price\": 59.99,\n", " \"Description\": \"A compact, adjustable weight for targeted muscle workouts.\"\n", " },\n", " {\n", " \"FitnessItemID\": \"2\",\n", " \"Name\": \"Yoga Mat\",\n", " \"Category\": \"Flexibility\",\n", " \"Price\": 25.0,\n", " \"Description\": \"Non-slip mat designed for yoga, Pilates, and other exercises.\"\n", " },\n", " {\n", " \"FitnessItemID\": \"3\",\n", " \"Name\": \"Treadmill\",\n", " \"Category\": \"Cardio\",\n", " \"Price\": 499.0,\n", " \"Description\": \"A sturdy treadmill with adjustable speed and incline settings.\"\n", " },\n", " {\n", " \"FitnessItemID\": \"4\",\n", " \"Name\": \"Resistance Bands\",\n", " \"Category\": \"Strength\",\n", " \"Price\": 15.0,\n", " \"Description\": \"Set of colorful bands for light to moderate resistance workouts.\"\n", " }\n", " ]\n", "\n", " result = search_client.upload_documents(documents=sample_docs)\n", " print(f\"🚀 Upload result: {result}\")\n", "\n", "upload_fitness_docs()\n", "print(\"✅ Documents uploaded to search index\")" ] }, { "cell_type": "markdown", "id": "verify-md", "metadata": {}, "source": [ "### Verify the Documents\n", "\n", "Let’s perform a basic search query (e.g. for items in the **Strength** category) to ensure everything is working." ] }, { "cell_type": "code", "execution_count": null, "id": "verify-search", "metadata": {}, "outputs": [], "source": [ "results = search_client.search(search_text=\"Strength\", filter=None, top=10)\n", "\n", "print(\"🔍 Search results for 'Strength':\")\n", "print(\"-\" * 50)\n", "found_items = False\n", "for doc in results:\n", " found_items = True\n", " print(f\"Name: {doc['Name']}\")\n", " print(f\"Category: {doc['Category']}\")\n", " print(f\"Price: ${doc['Price']:.2f}\")\n", " print(f\"Description: {doc['Description']}\")\n", " print(\"-\" * 50)\n", "\n", "if not found_items:\n", " print(\"No matching items found.\")" ] }, { "cell_type": "markdown", "id": "sk-agent-md", "metadata": {}, "source": [ "## 2. Create Semantic Kernel Agent with Azure AI Search Tool\n", "\n", "In this section we’ll use Semantic Kernel’s Azure AI Agent abstractions to build a fitness shopping assistant. This agent will:\n", "\n", "- Use your Azure OpenAI model (specified by `MODEL_DEPLOYMENT_NAME`)\n", "- Attach an **Azure AI Search tool** (pointing to `myfitnessindex`)\n", "- Engage in an asynchronous conversation that queries the index based on user input\n", "\n", "The code below uses asynchronous Python (with `asyncio`) and Semantic Kernel classes from the `semantic_kernel` package." ] }, { "cell_type": "code", "execution_count": null, "id": "sk-agent-code", "metadata": {}, "outputs": [], "source": [ "import asyncio\n", "import logging\n", "\n", "from azure.ai.projects.aio import AIProjectClient\n", "from azure.ai.projects.models import AzureAISearchTool, ConnectionType\n", "from azure.identity.aio import DefaultAzureCredential\n", "\n", "from semantic_kernel.agents.azure_ai import AzureAIAgent, AzureAIAgentSettings\n", "from semantic_kernel.contents.chat_message_content import ChatMessageContent\n", "from semantic_kernel.contents.utils.author_role import AuthorRole\n", "\n", "logging.basicConfig(level=logging.WARNING)\n", "\n", "# For this demo, we will use the same index name as before\n", "AZURE_AI_SEARCH_INDEX_NAME = \"myfitnessindex\"\n", "\n", "# Get required environment variables\n", "model_deployment_name = os.environ.get(\"MODEL_DEPLOYMENT_NAME\")\n", "project_connection_string = os.environ.get(\"PROJECT_CONNECTION_STRING\")\n", "\n", "if not model_deployment_name:\n", " raise ValueError(\"🚨 MODEL_DEPLOYMENT_NAME not set in .env\")\n", "if not project_connection_string:\n", " raise ValueError(\"🚨 PROJECT_CONNECTION_STRING not set in .env\")\n", "\n", "# Create agent settings with required parameters\n", "ai_agent_settings = AzureAIAgentSettings.create(\n", " model_deployment_name=model_deployment_name,\n", " project_connection_string=project_connection_string\n", ")\n", "\n", "async with (\n", " DefaultAzureCredential() as creds,\n", " AIProjectClient.from_connection_string(\n", " credential=creds,\n", " conn_str=ai_agent_settings.project_connection_string.get_secret_value()\n", " ) as client,\n", "):\n", " # List available connections and find one of type Azure AI Search\n", " conn_list = await client.connections.list()\n", " ai_search_conn_id = \"\"\n", " for conn in conn_list:\n", " if conn.connection_type == ConnectionType.AZURE_AI_SEARCH:\n", " ai_search_conn_id = conn.id\n", " break\n", "\n", " if not ai_search_conn_id:\n", " print(\"❌ No Azure AI Search connection found.\")\n", " raise ValueError(\"❌ No Azure AI Search connection found.\")\n", "\n", " # Create the Azure AI Search tool pointing to our fitness index\n", " ai_search_tool = AzureAISearchTool(\n", " index_connection_id=ai_search_conn_id, \n", " index_name=AZURE_AI_SEARCH_INDEX_NAME\n", " )\n", "\n", " # Create the agent definition with instructions for a fitness shopping assistant\n", " agent_definition = await client.agents.create_agent(\n", " model=ai_agent_settings.model_deployment_name,\n", " instructions=\"\"\"\n", " You are a Fitness Shopping Assistant. You help users find fitness equipment based on their queries.\n", " Always include a disclaimer that you are not providing medical advice.\n", " \"\"\",\n", " tools=ai_search_tool.definitions,\n", " tool_resources=ai_search_tool.resources,\n", " headers={\"x-ms-enable-preview\": \"true\"},\n", " )\n", "\n", " # Create the Semantic Kernel Azure AI Agent\n", " agent = AzureAIAgent(\n", " client=client,\n", " definition=agent_definition,\n", " )\n", "\n", " # Create a new conversation thread\n", " thread = await client.agents.create_thread()\n", " print(f\"📝 Created thread with ID: {thread.id}\")\n", "\n", " # Define some example fitness queries\n", " user_queries = [\n", " \"Which items are best for strength training?\",\n", " \"I need something for cardio under $300. Any suggestions?\"\n", " ]\n", "\n", " try:\n", " for query in user_queries:\n", " # Add the user message\n", " await agent.add_chat_message(\n", " thread_id=thread.id,\n", " message=ChatMessageContent(role=AuthorRole.USER, content=query),\n", " )\n", " print(f\"\\n# User: {query}\\n\")\n", "\n", " # Invoke the agent and stream its response\n", " async for content in agent.invoke(thread_id=thread.id):\n", " if content.role != AuthorRole.TOOL:\n", " print(f\"# Agent: {content.content}\\n\")\n", " finally:\n", " # Clean up the conversation thread and agent\n", " await client.agents.delete_thread(thread.id)\n", " await client.agents.delete_agent(agent.id)\n", " print(\"🗑️ Cleaned up agent and thread\")" ] }, { "cell_type": "markdown", "id": "cleanup-md", "metadata": {}, "source": [ "## 3. Cleanup\n", "\n", "For this demo we already clean up the agent and thread inside the async function. In case you want to remove the search index as well (for a fresh start), run the cell below." ] }, { "cell_type": "code", "execution_count": null, "id": "cleanup-index", "metadata": {}, "outputs": [], "source": [ "try:\n", " index_client.delete_index(index_name)\n", " print(f\"🗑️ Deleted index {index_name}\")\n", "except Exception as e:\n", " print(f\"Error deleting index: {e}\")" ] }, { "cell_type": "markdown", "id": "congrats-md", "metadata": {}, "source": [ "# 🎉 Congrats!\n", "\n", "You've successfully:\n", "\n", "1. Created an Azure AI Search index and populated it with fitness data\n", "2. Verified the data via a basic search query\n", "3. Built and run a Semantic Kernel Agent that leverages Azure AI Search to answer natural language queries\n", "\n", "Feel free to explore further enhancements (e.g. integrating more tools or advanced evaluation) and enjoy your journey with Azure AI Foundry and Semantic Kernel!" ] } ], "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.11" } }, "nbformat": 4, "nbformat_minor": 5 }