{ "cells": [ { "cell_type": "markdown", "id": "d5acee06", "metadata": {}, "source": [ "# گردش کار انسان در حلقه با چارچوب عامل مایکروسافت\n", "\n", "## 🎯 اهداف یادگیری\n", "\n", "در این دفترچه، یاد خواهید گرفت که چگونه گردش کارهای **انسان در حلقه** را با استفاده از `RequestInfoExecutor` در چارچوب عامل مایکروسافت پیادهسازی کنید. این الگوی قدرتمند به شما امکان میدهد تا گردش کارهای هوش مصنوعی را متوقف کنید تا ورودی انسانی جمعآوری شود، و این کار عوامل شما را تعاملی کرده و کنترل تصمیمات حیاتی را به انسانها میسپارد.\n", "\n", "## 🔄 انسان در حلقه چیست؟\n", "\n", "**انسان در حلقه (HITL)** یک الگوی طراحی است که در آن عوامل هوش مصنوعی اجرای خود را متوقف میکنند تا قبل از ادامه، ورودی انسانی درخواست کنند. این امر برای موارد زیر ضروری است:\n", "\n", "- ✅ **تصمیمات حیاتی** - دریافت تأیید انسانی قبل از انجام اقدامات مهم\n", "- ✅ **وضعیتهای مبهم** - اجازه دهید انسانها زمانی که هوش مصنوعی مطمئن نیست، توضیح دهند\n", "- ✅ **ترجیحات کاربر** - از کاربران بخواهید بین گزینههای مختلف انتخاب کنند\n", "- ✅ **رعایت قوانین و ایمنی** - اطمینان از نظارت انسانی برای عملیاتهای تحت نظارت\n", "- ✅ **تجربههای تعاملی** - ساخت عوامل مکالمهای که به ورودی کاربر پاسخ میدهند\n", "\n", "## 🏗️ نحوه کار در چارچوب عامل مایکروسافت\n", "\n", "این چارچوب سه مؤلفه کلیدی برای HITL ارائه میدهد:\n", "\n", "1. **`RequestInfoExecutor`** - یک اجراکننده ویژه که گردش کار را متوقف کرده و یک `RequestInfoEvent` منتشر میکند\n", "2. **`RequestInfoMessage`** - کلاس پایه برای پیامهای درخواست تایپشده که به انسانها ارسال میشود\n", "3. **`RequestResponse`** - پاسخهای انسانی را با درخواستهای اصلی با استفاده از `request_id` مرتبط میکند\n", "\n", "**الگوی گردش کار:**\n", "```\n", "Agent detects need for input\n", " ↓\n", "Sends message to RequestInfoExecutor\n", " ↓\n", "Workflow pauses & emits RequestInfoEvent\n", " ↓\n", "Application collects human input (console, UI, etc.)\n", " ↓\n", "Application sends RequestResponse via send_responses_streaming()\n", " ↓\n", "Workflow resumes with human input\n", "```\n", "\n", "## 🏨 مثال ما: رزرو هتل با تأیید کاربر\n", "\n", "ما بر اساس گردش کار شرطی، تأیید انسانی را **قبل از** پیشنهاد مقصدهای جایگزین اضافه خواهیم کرد:\n", "\n", "1. کاربر یک مقصد درخواست میکند (مثلاً \"پاریس\")\n", "2. `availability_agent` بررسی میکند که آیا اتاقها موجود هستند\n", "3. **اگر اتاقی موجود نباشد** → `confirmation_agent` میپرسد \"آیا مایلید گزینههای جایگزین را ببینید؟\"\n", "4. گردش کار با استفاده از `RequestInfoExecutor` **متوقف میشود**\n", "5. **انسان پاسخ میدهد** \"بله\" یا \"خیر\" از طریق ورودی کنسول\n", "6. `decision_manager` بر اساس پاسخ مسیر را تعیین میکند:\n", " - **بله** → نمایش مقصدهای جایگزین\n", " - **خیر** → لغو درخواست رزرو\n", "7. نمایش نتیجه نهایی\n", "\n", "این نشان میدهد که چگونه میتوان کنترل پیشنهادات عامل را به کاربران سپرد!\n", "\n", "---\n", "\n", "بیایید شروع کنیم! 🚀\n" ] }, { "cell_type": "markdown", "id": "f0012efd", "metadata": {}, "source": [ "## مرحله ۱: وارد کردن کتابخانههای مورد نیاز\n", "\n", "ما اجزای استاندارد چارچوب Agent را به همراه **کلاسهای خاص مرتبط با انسان در حلقه** وارد میکنیم:\n", "- `RequestInfoExecutor` - اجرایی که جریان کار را برای دریافت ورودی انسانی متوقف میکند\n", "- `RequestInfoEvent` - رویدادی که هنگام درخواست ورودی انسانی صادر میشود\n", "- `RequestInfoMessage` - کلاس پایه برای بارهای درخواست تایپشده\n", "- `RequestResponse` - ارتباطدهنده پاسخهای انسانی با درخواستها\n", "- `WorkflowOutputEvent` - رویدادی برای شناسایی خروجیهای جریان کار\n" ] }, { "cell_type": "code", "execution_count": 21, "id": "2bb201f4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "✅ All imports successful!\n", "🔄 Human-in-the-loop components loaded: RequestInfoExecutor, RequestInfoEvent, RequestResponse\n" ] } ], "source": [ "import asyncio\n", "import json\n", "import os\n", "from dataclasses import dataclass\n", "from typing import Annotated, Any, Never\n", "\n", "from agent_framework import (\n", " AgentExecutor,\n", " AgentExecutorRequest,\n", " AgentExecutorResponse,\n", " ChatMessage,\n", " Executor,\n", " RequestInfoEvent, # NEW: Event when human input is requested\n", " RequestInfoExecutor, # NEW: Executor that gathers human input\n", " RequestInfoMessage, # NEW: Base class for request payloads\n", " RequestResponse, # NEW: Correlates response with request\n", " Role,\n", " WorkflowBuilder,\n", " WorkflowContext,\n", " WorkflowOutputEvent, # NEW: Event for workflow outputs\n", " WorkflowRunState, # NEW: Enum of workflow run states\n", " WorkflowStatusEvent, # NEW: Event for run state changes\n", " ai_function,\n", " executor,\n", " handler, # NEW: Decorator for executor methods\n", ")\n", "\n", "# 🤖 GitHub Models or OpenAI client integration\n", "from agent_framework.openai import OpenAIChatClient\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", "print(\"🔄 Human-in-the-loop components loaded: RequestInfoExecutor, RequestInfoEvent, RequestResponse\")" ] }, { "cell_type": "markdown", "id": "e95b62b7", "metadata": {}, "source": [ "## مرحله ۲: تعریف مدلهای Pydantic برای خروجیهای ساختاریافته\n", "\n", "این مدلها **طرح**ی را تعریف میکنند که عوامل بازگشت خواهند داد. ما تمام مدلها را از جریان کاری شرطی حفظ میکنیم و اضافه میکنیم:\n", "\n", "**جدید برای انسان در حلقه:**\n", "- `HumanFeedbackRequest` - زیرکلاسی از `RequestInfoMessage` که بار درخواست ارسال شده به انسانها را تعریف میکند\n", " - شامل `prompt` (سؤالی که باید پرسیده شود) و `destination` (زمینهای درباره شهر غیرقابل دسترس)\n" ] }, { "cell_type": "code", "execution_count": 22, "id": "b423a7b8", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "✅ Pydantic models defined:\n", " - BookingCheckResult (availability check)\n", " - AlternativeResult (alternative suggestion)\n", " - BookingConfirmation (booking confirmation)\n", " - ConfirmationQuestion (agent response format) 🆕\n", " - HumanFeedbackRequest (RequestInfoMessage for HITL) 🆕\n" ] } ], "source": [ "# Existing models from conditional workflow\n", "class BookingCheckResult(BaseModel):\n", " \"\"\"Result from checking hotel availability at a destination.\"\"\"\n", " destination: str\n", " has_availability: bool\n", " message: str\n", "\n", "\n", "class AlternativeResult(BaseModel):\n", " \"\"\"Suggested alternative destination when no rooms available.\"\"\"\n", " alternative_destination: str\n", " reason: str\n", "\n", "\n", "class BookingConfirmation(BaseModel):\n", " \"\"\"Booking suggestion when rooms are available.\"\"\"\n", " destination: str\n", " action: str\n", " message: str\n", "\n", "\n", "# NEW: Pydantic model for agent's response format\n", "class ConfirmationQuestion(BaseModel):\n", " \"\"\"\n", " Pydantic model used by confirmation_agent's response_format.\n", " This is what the agent will output as JSON.\n", " \"\"\"\n", " question: str # The question to ask the user\n", " destination: str # The unavailable destination for context\n", "\n", "\n", "# NEW: Dataclass for RequestInfoExecutor\n", "@dataclass\n", "class HumanFeedbackRequest(RequestInfoMessage):\n", " \"\"\"\n", " Request sent to RequestInfoExecutor asking if user wants alternatives.\n", " \n", " MUST be a dataclass subclassing RequestInfoMessage for type compatibility.\n", " This is what gets sent to the RequestInfoExecutor.\n", " \"\"\"\n", " prompt: str = \"\" # The question to ask the user\n", " destination: str = \"\" # The unavailable destination for context\n", "\n", "\n", "print(\"✅ Pydantic models defined:\")\n", "print(\" - BookingCheckResult (availability check)\")\n", "print(\" - AlternativeResult (alternative suggestion)\")\n", "print(\" - BookingConfirmation (booking confirmation)\")\n", "print(\" - ConfirmationQuestion (agent response format) 🆕\")\n", "print(\" - HumanFeedbackRequest (RequestInfoMessage for HITL) 🆕\")" ] }, { "cell_type": "markdown", "id": "128574c9", "metadata": {}, "source": [ "## مرحله ۳: ایجاد ابزار رزرو هتل\n", "\n", "همان ابزار از جریان کاری شرطی - بررسی میکند که آیا اتاقها در مقصد موجود هستند.\n" ] }, { "cell_type": "code", "execution_count": 23, "id": "743314fa", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "✅ hotel_booking tool created with @ai_function decorator\n" ] } ], "source": [ "@ai_function(description=\"Check hotel room availability for a destination city\")\n", "def hotel_booking(destination: Annotated[str, \"The destination city to check for hotel rooms\"]) -> str:\n", " \"\"\"\n", " Simulates checking hotel room availability.\n", " \n", " Returns JSON string with availability status.\n", " \"\"\"\n", " display(\n", " HTML(f\"\"\"\n", "
\n",
" Human-in-the-Loop Routing:
\n",
" • If NO availability → confirmation_agent → prepare_human_request → request_info_executor → PAUSE FOR HUMAN → decision_manager
\n",
" • If user says YES → alternative_agent → display_result
\n",
" • If user says NO → cancellation_agent → display_result
\n",
" • If availability → booking_agent → display_result (no human input needed)\n",
"
\n",
" Human-in-the-Loop Routing:
\n",
" • If NO availability → confirmation_agent → prepare_human_request → request_info_executor → PAUSE FOR HUMAN → decision_manager
\n",
" • If user says YES → alternative_agent → display_result
\n",
" • If user says NO → cancellation_agent → display_result
\n",
" • If availability → booking_agent → display_result (no human input needed)\n",
"
Expected workflow path: availability_agent → confirmation_agent → request_info_executor → PAUSE → decision_manager → (depends on user input)
\n", "Expected workflow path: availability_agent → confirmation_agent → request_info_executor → PAUSE → decision_manager → (depends on user input)
\n", "Status: ❌ No rooms in Paris
\n", "User Decision: ✅ Accepted alternatives
\n", "Alternative Suggestion: 🏨 {result_obj.alternative_destination}
\n", "Reason: {result_obj.reason}
\n", "Status: ❌ No rooms in Paris
\n", "User Decision: 🚫 Declined alternatives
\n", "Result: Booking request cancelled
\n", "Expected workflow path: availability_agent → booking_agent → display_result (direct, no pause)
\n", "Status: ✅ Rooms Available!
\n", "Destination: 🏨 {result_stockholm.destination}
\n", "Action: {result_stockholm.action}
\n", "Message: {result_stockholm.message}
\n", "Note: No human input was requested because rooms were available!
\n", "