473 lines
20 KiB
Plaintext
473 lines
20 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "2b91961c",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Two Sequential Agents:\n",
|
||
"\n",
|
||
"1. **Front Desk Agent**: Makes initial attraction recommendations for the city\n",
|
||
"2. **Concierge Agent**: Reviews and rates the front desk recommendation based on popularity\n",
|
||
"\n",
|
||
"## Key Benefits of Sequential Orchestration:\n",
|
||
"\n",
|
||
"- **Iterative Refinement**: Second agent improves upon first agent's work\n",
|
||
"- **Specialization**: Each agent has a specific role in the process\n",
|
||
"- **Quality Control**: Built-in review and validation step\n",
|
||
"- **Clear Information Flow**: Structured handoff between agents\n",
|
||
"\n",
|
||
"## Prerequisites:\n",
|
||
"- Microsoft Agent Framework installed\n",
|
||
"- Microsoft Foundry project endpoint and model deployment configured (`AZURE_AI_PROJECT_ENDPOINT`, `AZURE_AI_MODEL_DEPLOYMENT_NAME`)\n",
|
||
"- Authenticated via Azure CLI (`az login`)\n",
|
||
"- Understanding of basic agent concepts\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "0981c0bb",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"import asyncio\n",
|
||
"import json\n",
|
||
"import os\n",
|
||
"from typing import Any, cast\n",
|
||
"\n",
|
||
"from agent_framework import Message, WorkflowBuilder\n",
|
||
"from agent_framework.foundry import FoundryChatClient\n",
|
||
"from azure.identity import AzureCliCredential\n",
|
||
"from dotenv import load_dotenv\n",
|
||
"from IPython.display import HTML, display\n",
|
||
"from pydantic import BaseModel\n",
|
||
"\n",
|
||
"print(\"All imports successful!\")\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "790b74bd",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Step 1: Define Pydantic Models for Structured Outputs\n",
|
||
"\n",
|
||
"These models define the schema that each agent will return. The front desk agent provides a recommendation, and the concierge agent provides a review and rating."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "3436dc8d",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"class AttractionRecommendation(BaseModel):\n",
|
||
" \"\"\"Attraction recommendation from the front desk agent.\"\"\"\n",
|
||
"\n",
|
||
" city: str\n",
|
||
" attraction_name: str\n",
|
||
" description: str\n",
|
||
" category: str # e.g., \"museum\", \"landmark\", \"park\", \"entertainment\"\n",
|
||
" recommended_duration: str # e.g., \"2-3 hours\", \"half day\"\n",
|
||
" why_recommended: str\n",
|
||
" best_time_to_visit: str\n",
|
||
"\n",
|
||
"\n",
|
||
"class AttractionReview(BaseModel):\n",
|
||
" \"\"\"Expert review and rating from the concierge agent.\"\"\"\n",
|
||
"\n",
|
||
" attraction_name: str\n",
|
||
" city: str\n",
|
||
" popularity_score: int # 1-10 scale\n",
|
||
" popularity_reasoning: str\n",
|
||
" visitor_rating: float # 1.0-5.0 scale\n",
|
||
" pros: list[str]\n",
|
||
" cons: list[str]\n",
|
||
" concierge_recommendation: str\n",
|
||
" alternative_suggestions: list[str]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "f4269b29",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Step 2: Load Environment Variables and Configure the Foundry Provider\n",
|
||
"\n",
|
||
"Use `FoundryChatClient` with keyless `AzureCliCredential` authentication, matching the pattern used in lessons 01–13.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "2152c7d3",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"# Load environment variables\n",
|
||
"load_dotenv()\n",
|
||
"\n",
|
||
"# Configure the Microsoft Foundry provider with keyless authentication\n",
|
||
"provider = FoundryChatClient(\n",
|
||
" project_endpoint=os.environ[\"AZURE_AI_PROJECT_ENDPOINT\"],\n",
|
||
" model=os.environ[\"AZURE_AI_MODEL_DEPLOYMENT_NAME\"],\n",
|
||
" credential=AzureCliCredential(),\n",
|
||
")\n",
|
||
"\n",
|
||
"print(\"Microsoft Foundry provider configured successfully!\")\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "e63c63dd",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Step 3: Create Two Sequential Agents\n",
|
||
"\n",
|
||
"Each agent has a specific role in the sequential workflow. The front desk agent makes recommendations, and the concierge agent reviews and rates them.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "3f862eee",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"# Agent 1: Front Desk Agent (Makes initial recommendations)\n",
|
||
"front_desk_agent = provider.as_agent(\n",
|
||
" name=\"front-desk-agent\",\n",
|
||
" instructions=(\n",
|
||
" \"You are a knowledgeable hotel front desk agent who specializes in local attractions. \"\n",
|
||
" \"When a guest asks about attractions in a city, provide a single, well-researched recommendation \"\n",
|
||
" \"for a popular tourist attraction. Focus on giving practical information including what makes \"\n",
|
||
" \"this attraction special, how long to spend there, and the best time to visit. \"\n",
|
||
" \"Be helpful and enthusiastic about your recommendation. \"\n",
|
||
" \"Return structured JSON matching the AttractionRecommendation schema.\"\n",
|
||
" ),\n",
|
||
")\n",
|
||
"\n",
|
||
"# Agent 2: Concierge Agent (Reviews and rates recommendations)\n",
|
||
"concierge_agent = provider.as_agent(\n",
|
||
" name=\"concierge-agent\",\n",
|
||
" instructions=(\n",
|
||
" \"You are an expert concierge with extensive knowledge of tourist attractions worldwide. \"\n",
|
||
" \"You will receive an attraction recommendation and must provide an expert review and rating. \"\n",
|
||
" \"Evaluate the recommendation based on the attraction's popularity, visitor satisfaction, \"\n",
|
||
" \"and overall quality. Provide a popularity score (1-10), visitor rating (1.0-5.0), \"\n",
|
||
" \"list pros and cons, and give your professional assessment. \"\n",
|
||
" \"Also suggest alternative attractions if appropriate. \"\n",
|
||
" \"Return structured JSON matching the AttractionReview schema.\"\n",
|
||
" ),\n",
|
||
")\n",
|
||
"\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "0afa99f5",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Step 4: Build the Sequential Workflow\n",
|
||
"\n",
|
||
"`WorkflowBuilder` creates a workflow where:\n",
|
||
"1. **Front Desk Agent** receives user input and makes a recommendation\n",
|
||
"2. **Concierge Agent** receives the front desk recommendation and provides expert review\n",
|
||
"3. **Output** contains both the original recommendation and the expert review"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "d76c5b11",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"# Build the sequential workflow with WorkflowBuilder\n",
|
||
"workflow = (\n",
|
||
" WorkflowBuilder(\n",
|
||
" start_executor=front_desk_agent,\n",
|
||
" output_executors=[front_desk_agent, concierge_agent],\n",
|
||
" )\n",
|
||
" .add_edge(front_desk_agent, concierge_agent)\n",
|
||
" .build()\n",
|
||
")\n",
|
||
"\n",
|
||
"display(HTML(\"\"\"\n",
|
||
"<div style='padding: 20px; background: linear-gradient(135deg, #ff7043 0%, #ff5722 100%); color: white; border-radius: 8px; margin: 10px 0;'>\n",
|
||
" <h3 style='margin: 0 0 15px 0;'>Sequential Workflow Built Successfully!</h3>\n",
|
||
" <p style='margin: 0; line-height: 1.6;'>\n",
|
||
" <strong>Flow:</strong><br>\n",
|
||
" • User Input → <strong>Front Desk Agent</strong> (recommendation)<br>\n",
|
||
" • Front Desk Output → <strong>Concierge Agent</strong> (review & rating)<br>\n",
|
||
" • Final Output → Combined recommendation + expert review\n",
|
||
" </p>\n",
|
||
"</div>\n",
|
||
"\"\"\"))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "27a52679",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"async def display_attraction_recommendation(city: str):\n",
|
||
" \"\"\"Run the sequential workflow and display formatted results.\"\"\"\n",
|
||
"\n",
|
||
" display(HTML(f\"\"\"\n",
|
||
" <div style='padding: 20px; background: #fff3e0; border-left: 4px solid #ff9800; border-radius: 8px; margin: 20px 0;'>\n",
|
||
" <h3 style='margin: 0 0 10px 0; color: #e65100;'>Processing Attraction Recommendation for {city}</h3>\n",
|
||
" <p style='margin: 0;'><strong>Status:</strong> Running sequential workflow...</p>\n",
|
||
" </div>\n",
|
||
" \"\"\"))\n",
|
||
"\n",
|
||
" # Run the workflow. With WorkflowBuilder(output_executors=[a1, a2]),\n",
|
||
" # outputs is a list of AgentResponse objects, one per output executor.\n",
|
||
" events = await workflow.run(f\"I want to visit an attraction in {city}\")\n",
|
||
" outputs = events.get_outputs()\n",
|
||
"\n",
|
||
" front_desk_response = outputs[0].text if len(outputs) > 0 else None\n",
|
||
" concierge_response = outputs[1].text if len(outputs) > 1 else None\n",
|
||
"\n",
|
||
" # Display results\n",
|
||
" display(HTML(f\"\"\"\n",
|
||
" <div style='padding: 25px; background: linear-gradient(135deg, #4caf50 0%, #8bc34a 100%); color: white; border-radius: 12px;\n",
|
||
" box-shadow: 0 4px 12px rgba(76,175,80,0.3); margin: 20px 0;'>\n",
|
||
" <h2 style='margin: 0 0 20px 0;'>Attraction Recommendation for {city}</h2>\n",
|
||
" <p style='margin: 0; font-size: 14px; opacity: 0.9;'>Generated by sequential agent workflow</p>\n",
|
||
" </div>\n",
|
||
" \"\"\"))\n",
|
||
"\n",
|
||
" # Process and display responses\n",
|
||
" if front_desk_response:\n",
|
||
" try:\n",
|
||
" recommendation_data = AttractionRecommendation.model_validate_json(front_desk_response)\n",
|
||
" display_front_desk_section(recommendation_data)\n",
|
||
" except Exception as e:\n",
|
||
" display(HTML(f\"\"\"\n",
|
||
" <div style='padding: 15px; background: #ffcdd2; border-left: 4px solid #f44336; border-radius: 4px; margin: 10px 0;'>\n",
|
||
" <strong>Error parsing front desk response:</strong> {str(e)}\n",
|
||
" <details><summary>Raw response</summary>{front_desk_response}</details>\n",
|
||
" </div>\n",
|
||
" \"\"\"))\n",
|
||
"\n",
|
||
" if concierge_response:\n",
|
||
" try:\n",
|
||
" review_data = AttractionReview.model_validate_json(concierge_response)\n",
|
||
" display_concierge_section(review_data)\n",
|
||
" except Exception as e:\n",
|
||
" display(HTML(f\"\"\"\n",
|
||
" <div style='padding: 15px; background: #ffcdd2; border-left: 4px solid #f44336; border-radius: 4px; margin: 10px 0;'>\n",
|
||
" <strong>Error parsing concierge response:</strong> {str(e)}\n",
|
||
" <details><summary>Raw response</summary>{concierge_response}</details>\n",
|
||
" </div>\n",
|
||
" \"\"\"))\n",
|
||
"\n",
|
||
"\n",
|
||
"def display_front_desk_section(data: AttractionRecommendation):\n",
|
||
" \"\"\"Display front desk recommendation in a formatted section.\"\"\"\n",
|
||
"\n",
|
||
" display(HTML(f\"\"\"\n",
|
||
" <div style='padding: 20px; background: #e3f2fd; border-radius: 8px; margin: 15px 0; border-left: 4px solid #2196f3;'>\n",
|
||
" <h3 style='margin: 0 0 15px 0; color: #1976d2;'>🏨 Front Desk Recommendation</h3>\n",
|
||
" <div style='margin-bottom: 15px;'>\n",
|
||
" <h4 style='margin: 0 0 8px 0; color: #333;'>{data.attraction_name}</h4>\n",
|
||
" <span style='background: #2196f3; color: white; padding: 4px 8px; border-radius: 12px; font-size: 12px;'>{data.category}</span>\n",
|
||
" </div>\n",
|
||
" <div style='margin-bottom: 15px;'>\n",
|
||
" <strong style='color: #333;'>Description:</strong> {data.description}\n",
|
||
" </div>\n",
|
||
" <div style='margin-bottom: 10px;'>\n",
|
||
" <strong style='color: #333;'>Why Recommended:</strong> {data.why_recommended}\n",
|
||
" </div>\n",
|
||
" <div style='margin-bottom: 10px;'>\n",
|
||
" <strong style='color: #333;'>Recommended Duration:</strong> {data.recommended_duration}\n",
|
||
" </div>\n",
|
||
" <div>\n",
|
||
" <strong style='color: #333;'>Best Time to Visit:</strong> {data.best_time_to_visit}\n",
|
||
" </div>\n",
|
||
" </div>\n",
|
||
" \"\"\"))\n",
|
||
"\n",
|
||
"\n",
|
||
"def display_concierge_section(data: AttractionReview):\n",
|
||
" \"\"\"Display concierge review in a formatted section.\"\"\"\n",
|
||
"\n",
|
||
" # Create star rating display\n",
|
||
" star_rating = \"⭐\" * int(data.visitor_rating) + \"☆\" * (5 - int(data.visitor_rating))\n",
|
||
"\n",
|
||
" # Create popularity bar\n",
|
||
" popularity_bar = \"🟩\" * data.popularity_score + \"⬜\" * (10 - data.popularity_score)\n",
|
||
"\n",
|
||
" pros_list = \"\".join([f\"<li style='color: #4caf50;'>✓ {pro}</li>\" for pro in data.pros])\n",
|
||
" cons_list = \"\".join([f\"<li style='color: #f44336;'>✗ {con}</li>\" for con in data.cons])\n",
|
||
" alternatives_list = \"\".join([f\"<li>{alt}</li>\" for alt in data.alternative_suggestions])\n",
|
||
"\n",
|
||
" display(HTML(f\"\"\"\n",
|
||
" <div style='padding: 20px; background: #fff3e0; border-radius: 8px; margin: 15px 0; border-left: 4px solid #ff9800;'>\n",
|
||
" <h3 style='margin: 0 0 15px 0; color: #f57c00;'>🎩 Concierge Expert Review</h3>\n",
|
||
"\n",
|
||
" <div style='display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px;'>\n",
|
||
" <div style='background: rgba(255,152,0,0.1); padding: 15px; border-radius: 8px;'>\n",
|
||
" <h4 style='margin: 0 0 8px 0; color: #333;'>Popularity Score</h4>\n",
|
||
" <div style='font-size: 24px; font-weight: bold; color: #f57c00;'>{data.popularity_score}/10</div>\n",
|
||
" <div style='font-size: 12px; margin-top: 5px;'>{popularity_bar}</div>\n",
|
||
" </div>\n",
|
||
" <div style='background: rgba(255,152,0,0.1); padding: 15px; border-radius: 8px;'>\n",
|
||
" <h4 style='margin: 0 0 8px 0; color: #333;'>Visitor Rating</h4>\n",
|
||
" <div style='font-size: 20px; font-weight: bold; color: #f57c00;'>{data.visitor_rating}/5.0</div>\n",
|
||
" <div style='font-size: 16px; margin-top: 5px;'>{star_rating}</div>\n",
|
||
" </div>\n",
|
||
" </div>\n",
|
||
"\n",
|
||
" <div style='margin-bottom: 15px;'>\n",
|
||
" <strong style='color: #333;'>Popularity Reasoning:</strong> {data.popularity_reasoning}\n",
|
||
" </div>\n",
|
||
"\n",
|
||
" <div style='display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 15px;'>\n",
|
||
" <div>\n",
|
||
" <h4 style='margin: 0 0 8px 0; color: #333;'>Pros:</h4>\n",
|
||
" <ul style='margin: 0; padding-left: 20px;'>{pros_list}</ul>\n",
|
||
" </div>\n",
|
||
" <div>\n",
|
||
" <h4 style='margin: 0 0 8px 0; color: #333;'>Cons:</h4>\n",
|
||
" <ul style='margin: 0; padding-left: 20px;'>{cons_list}</ul>\n",
|
||
" </div>\n",
|
||
" </div>\n",
|
||
" <div style='margin-bottom: 15px;'>\n",
|
||
" <strong style='color: #333;'>Concierge Recommendation:</strong> {data.concierge_recommendation}\n",
|
||
" </div>\n",
|
||
"\n",
|
||
" <div>\n",
|
||
" <h4 style='margin: 0 0 8px 0; color: #333;'>Alternative Suggestions:</h4>\n",
|
||
" <ul style='margin: 0; padding-left: 20px; color: #555;'>{alternatives_list}</ul>\n",
|
||
" </div>\n",
|
||
" </div>\n",
|
||
" \"\"\"))\n",
|
||
"\n",
|
||
"\n",
|
||
"# Test with Stockholm\n",
|
||
"await display_attraction_recommendation(\"Stockholm\")\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "1840d000",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Step 8: Workflow Analysis - Understanding Sequential Flow\n",
|
||
"\n",
|
||
"Let's examine how information flows between agents and analyze the conversation history."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "86e1bcc3",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"async def analyze_sequential_flow(city: str):\n",
|
||
" \"\"\"Analyze the sequential flow between agents.\"\"\"\n",
|
||
"\n",
|
||
" display(HTML(f\"\"\"\n",
|
||
" <div style='padding: 20px; background: #f3e5f5; border-left: 4px solid #9c27b0; border-radius: 8px; margin: 20px 0;'>\n",
|
||
" <h3 style='margin: 0 0 10px 0; color: #7b1fa2;'>Sequential Flow Analysis for {city}</h3>\n",
|
||
" <p style='margin: 0;'>Examining agent interactions and information handoff...</p>\n",
|
||
" </div>\n",
|
||
" \"\"\"))\n",
|
||
"\n",
|
||
" # Run the workflow\n",
|
||
" user_input = f\"I want to visit an attraction in {city}\"\n",
|
||
" events = await workflow.run(user_input)\n",
|
||
" outputs = events.get_outputs()\n",
|
||
"\n",
|
||
" # Reconstruct the conversation flow as a list of (author, text) steps.\n",
|
||
" # outputs is a list of AgentResponse objects, ordered to match output_executors.\n",
|
||
" steps = [(\"user\", user_input)]\n",
|
||
" if len(outputs) > 0:\n",
|
||
" steps.append((\"front-desk-agent\", outputs[0].text))\n",
|
||
" if len(outputs) > 1:\n",
|
||
" steps.append((\"concierge-agent\", outputs[1].text))\n",
|
||
"\n",
|
||
" display(HTML(f\"\"\"\n",
|
||
" <div style='padding: 25px; background: #f3e5f5; border-radius: 12px; margin: 20px 0;'>\n",
|
||
" <h2 style='margin: 0 0 20px 0; color: #7b1fa2;'>Conversation Flow Analysis</h2>\n",
|
||
" </div>\n",
|
||
" \"\"\"))\n",
|
||
"\n",
|
||
" # Display each step in the sequence\n",
|
||
" for i, (author, text) in enumerate(steps, 1):\n",
|
||
" role_color = {\n",
|
||
" \"user\": \"#2196f3\",\n",
|
||
" \"front-desk-agent\": \"#4caf50\",\n",
|
||
" \"concierge-agent\": \"#ff9800\"\n",
|
||
" }.get(author, \"#666666\")\n",
|
||
"\n",
|
||
" role_name = {\n",
|
||
" \"user\": \"👤 User\",\n",
|
||
" \"front-desk-agent\": \"🏨 Front Desk Agent\",\n",
|
||
" \"concierge-agent\": \"🎩 Concierge Agent\"\n",
|
||
" }.get(author, \"Unknown\")\n",
|
||
"\n",
|
||
" # Truncate long messages for flow analysis\n",
|
||
" content_preview = text[:200] + \"...\" if len(text) > 200 else text\n",
|
||
" display(HTML(f\"\"\"\n",
|
||
" <div style='padding: 15px; background: white; border-left: 4px solid {role_color}; border-radius: 4px; margin: 10px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1);'>\n",
|
||
" <div style='display: flex; align-items: center; margin-bottom: 10px;'>\n",
|
||
" <span style='font-weight: bold; color: {role_color}; margin-right: 10px;'>Step {i}:</span>\n",
|
||
" <span style='font-weight: bold; color: {role_color};'>{role_name}</span>\n",
|
||
" </div>\n",
|
||
" <div style='color: #555; font-size: 14px; line-height: 1.4;'>\n",
|
||
" {content_preview}\n",
|
||
" </div>\n",
|
||
" </div>\n",
|
||
" \"\"\"))\n",
|
||
"\n",
|
||
" # Analyze the flow\n",
|
||
" display(HTML(f\"\"\"\n",
|
||
" <div style='padding: 20px; background: linear-gradient(135deg, #9c27b0 0%, #673ab7 100%); color: white; border-radius: 8px; margin: 20px 0;'>\n",
|
||
" <h3 style='margin: 0 0 15px 0;'>Flow Analysis Summary</h3>\n",
|
||
" <ul style='margin: 0; padding-left: 20px; line-height: 1.6;'>\n",
|
||
" <li><strong>Total Steps:</strong> {len(steps)}</li>\n",
|
||
" <li><strong>Agents Involved:</strong> 2 (Front Desk + Concierge)</li>\n",
|
||
" <li><strong>Flow Pattern:</strong> Linear sequential (User → Agent 1 → Agent 2)</li>\n",
|
||
" <li><strong>Information Handoff:</strong> Front desk recommendation becomes concierge input</li>\n",
|
||
" <li><strong>Output Quality:</strong> Enhanced through expert review and rating</li>\n",
|
||
" </ul>\n",
|
||
" </div>\n",
|
||
" \"\"\"))\n",
|
||
"\n",
|
||
"\n",
|
||
"# Analyze the flow for Barcelona\n",
|
||
"await analyze_sequential_flow(\"Barcelona\")\n"
|
||
]
|
||
}
|
||
],
|
||
"metadata": {
|
||
"kernelspec": {
|
||
"display_name": ".venv (3.12.12)",
|
||
"language": "python",
|
||
"name": "python3"
|
||
},
|
||
"language_info": {
|
||
"codemirror_mode": {
|
||
"name": "ipython",
|
||
"version": 3
|
||
},
|
||
"file_extension": ".py",
|
||
"mimetype": "text/x-python",
|
||
"name": "python",
|
||
"nbconvert_exporter": "python",
|
||
"pygments_lexer": "ipython3",
|
||
"version": "3.12.12"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 5
|
||
}
|