项目文件夹

文件
2026-07-13 12:59:43 +08:00

1577 行
74 KiB
Plaintext

{
"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",
" <div style='padding: 15px; background: #e3f2fd; border-left: 4px solid #2196f3; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>🔍 Tool Invoked:</strong> hotel_booking(\"{destination}\")\n",
" </div>\n",
" \"\"\")\n",
" )\n",
"\n",
" # Simulate availability check\n",
" cities_with_rooms = [\"stockholm\", \"seattle\", \"tokyo\", \"london\", \"amsterdam\"]\n",
" has_rooms = destination.lower() in cities_with_rooms\n",
"\n",
" result = {\"has_availability\": has_rooms, \"destination\": destination}\n",
"\n",
" return json.dumps(result)\n",
"\n",
"\n",
"print(\"✅ hotel_booking tool created with @ai_function decorator\")"
]
},
{
"cell_type": "markdown",
"id": "65312a29",
"metadata": {},
"source": [
"## مرحله ۴: تعریف توابع شرطی برای مسیریابی\n",
"\n",
"ما به **چهار تابع شرطی** برای جریان کاری انسانی در حلقه نیاز داریم:\n",
"\n",
"**از جریان کاری شرطی:**\n",
"1. `has_availability_condition` - مسیریابی زمانی که هتل‌ها موجود هستند\n",
"2. `no_availability_condition` - مسیریابی زمانی که هتل‌ها موجود نیستند\n",
"\n",
"**جدید برای انسانی در حلقه:**\n",
"3. `user_wants_alternatives_condition` - مسیریابی زمانی که کاربر به گزینه‌های جایگزین \"بله\" می‌گوید\n",
"4. `user_declines_alternatives_condition` - مسیریابی زمانی که کاربر به گزینه‌های جایگزین \"نه\" می‌گوید\n"
]
},
{
"cell_type": "code",
"execution_count": 24,
"id": "0f4468d3",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"✅ Condition functions defined:\n",
" - has_availability_condition (routes when rooms exist)\n",
" - no_availability_condition (routes when no rooms)\n",
" - user_wants_alternatives_condition (routes when user says yes) 🆕\n",
" - user_declines_alternatives_condition (routes when user says no) 🆕\n"
]
}
],
"source": [
"# Existing condition functions from conditional workflow\n",
"def has_availability_condition(message: Any) -> bool:\n",
" \"\"\"Condition for routing when hotels ARE available.\"\"\"\n",
" if not isinstance(message, AgentExecutorResponse):\n",
" return True\n",
"\n",
" try:\n",
" result = BookingCheckResult.model_validate_json(message.agent_run_response.text)\n",
" display(\n",
" HTML(f\"\"\"\n",
" <div style='padding: 12px; background: #c8e6c9; border-left: 4px solid #4caf50; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>✅ Condition Check:</strong> has_availability = <strong>{result.has_availability}</strong> for {result.destination}\n",
" </div>\n",
" \"\"\")\n",
" )\n",
" return result.has_availability\n",
" except Exception as e:\n",
" display(HTML(f\"\"\"<div style='padding: 12px; background: #ffcdd2; border-left: 4px solid #f44336; border-radius: 4px; margin: 10px 0;'><strong>⚠️ Error:</strong> {str(e)}</div>\"\"\"))\n",
" return False\n",
"\n",
"\n",
"def no_availability_condition(message: Any) -> bool:\n",
" \"\"\"Condition for routing when hotels are NOT available.\"\"\"\n",
" if not isinstance(message, AgentExecutorResponse):\n",
" return False\n",
"\n",
" try:\n",
" result = BookingCheckResult.model_validate_json(message.agent_run_response.text)\n",
" display(\n",
" HTML(f\"\"\"\n",
" <div style='padding: 12px; background: #ffecb3; border-left: 4px solid #ff9800; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>❌ Condition Check:</strong> no_availability for {result.destination}\n",
" </div>\n",
" \"\"\")\n",
" )\n",
" return not result.has_availability\n",
" except Exception as e:\n",
" return False\n",
"\n",
"\n",
"# NEW: Condition functions for human-in-the-loop routing\n",
"def user_wants_alternatives_condition(message: Any) -> bool:\n",
" \"\"\"\n",
" Condition for routing when user WANTS to see alternatives.\n",
" \n",
" Checks the AgentExecutorRequest sent by decision_manager.\n",
" \"\"\"\n",
" # Check if it's an AgentExecutorRequest (what decision_manager sends)\n",
" if isinstance(message, AgentExecutorRequest):\n",
" # Check the message text to determine user's choice\n",
" if message.messages and len(message.messages) > 0:\n",
" msg_text = message.messages[0].text.lower()\n",
" wants_alternatives = \"wants to see alternative\" in msg_text or \"want to see alternative\" in msg_text\n",
" \n",
" display(\n",
" HTML(f\"\"\"\n",
" <div style='padding: 12px; background: #e1f5fe; border-left: 4px solid #0288d1; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>🔍 User Decision:</strong> User wants alternatives = <strong>{wants_alternatives}</strong>\n",
" </div>\n",
" \"\"\")\n",
" )\n",
" \n",
" return wants_alternatives\n",
" \n",
" return False\n",
"def user_declines_alternatives_condition(message: Any) -> bool:\n",
" \"\"\"\n",
" Condition for routing when user DECLINES alternatives.\n",
" \n",
" Checks the AgentExecutorRequest sent by decision_manager.\n",
" \"\"\"\n",
" # Check if it's an AgentExecutorRequest (what decision_manager sends)\n",
" if isinstance(message, AgentExecutorRequest):\n",
" # Check the message text to determine user's choice\n",
" if message.messages and len(message.messages) > 0:\n",
" msg_text = message.messages[0].text.lower()\n",
" declined = \"declined\" in msg_text or \"has declined\" in msg_text\n",
" \n",
" display(\n",
" HTML(f\"\"\"\n",
" <div style='padding: 12px; background: #fce4ec; border-left: 4px solid #c2185b; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>🚫 User Decision:</strong> User declined alternatives = <strong>{declined}</strong>\n",
" </div>\n",
" \"\"\")\n",
" )\n",
" \n",
" return declined\n",
" \n",
" return False\n",
"print(\"✅ Condition functions defined:\")\n",
"print(\" - has_availability_condition (routes when rooms exist)\")\n",
"print(\" - no_availability_condition (routes when no rooms)\")\n",
"print(\" - user_wants_alternatives_condition (routes when user says yes) 🆕\")\n",
"print(\" - user_declines_alternatives_condition (routes when user says no) 🆕\")"
]
},
{
"cell_type": "markdown",
"id": "00dd74b8",
"metadata": {},
"source": [
"## مرحله ۵: ایجاد اجراکننده مدیر تصمیم‌گیری\n",
"\n",
"این **هسته الگوی انسان در حلقه** است! `DecisionManager` یک `Executor` سفارشی است که:\n",
"\n",
"1. **بازخورد انسانی را دریافت می‌کند** از طریق اشیاء `RequestResponse`\n",
"2. **تصمیم کاربر را پردازش می‌کند** (بله/خیر)\n",
"3. **جریان کار را هدایت می‌کند** با ارسال پیام‌ها به عوامل مناسب\n",
"\n",
"ویژگی‌های کلیدی:\n",
"- از تزئین‌کننده `@handler` استفاده می‌کند تا روش‌ها را به عنوان مراحل جریان کار معرفی کند\n",
"- `RequestResponse[HumanFeedbackRequest, str]` را دریافت می‌کند که شامل درخواست اصلی و پاسخ کاربر است\n",
"- پیام‌های ساده \"بله\" یا \"خیر\" تولید می‌کند که توابع شرطی ما را فعال می‌کنند\n"
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "1281a719",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"✅ DecisionManager executor created with @handler method for human feedback\n"
]
}
],
"source": [
"class DecisionManager(Executor):\n",
" \"\"\"\n",
" Coordinates workflow routing based on human feedback.\n",
" \n",
" This executor receives RequestResponse objects from the RequestInfoExecutor\n",
" and makes routing decisions by sending simple messages that trigger\n",
" condition functions.\n",
" \"\"\"\n",
"\n",
" def __init__(self, id: str | None = None):\n",
" super().__init__(id=id or \"decision_manager\")\n",
"\n",
" @handler\n",
" async def on_human_feedback(\n",
" self,\n",
" feedback: RequestResponse[HumanFeedbackRequest, str],\n",
" ctx: WorkflowContext[AgentExecutorRequest],\n",
" ) -> None:\n",
" \"\"\"\n",
" Process human feedback and let the workflow route based on conditions.\n",
" \n",
" The RequestResponse contains:\n",
" - feedback.data: The user's string reply (e.g., \"yes\" or \"no\")\n",
" - feedback.original_request: The HumanFeedbackRequest with context\n",
" \n",
" This handler just displays feedback and passes the RequestResponse through.\n",
" The routing is done by condition functions on the edges.\n",
" \"\"\"\n",
" user_reply = (feedback.data or \"\").strip().lower()\n",
" destination = getattr(feedback.original_request, \"destination\", \"unknown\")\n",
"\n",
" display(\n",
" HTML(f\"\"\"\n",
" <div style='padding: 15px; background: #f3e5f5; border-left: 4px solid #9c27b0; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>🎯 Decision Manager:</strong> Processing user reply: <strong>\"{user_reply}\"</strong> for {destination}\n",
" </div>\n",
" \"\"\")\n",
" )\n",
"\n",
" if user_reply == \"yes\":\n",
" display(\n",
" HTML(\"\"\"\n",
" <div style='padding: 12px; background: #c8e6c9; border-left: 4px solid #4caf50; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>➡️ Routing:</strong> User wants alternatives → Will route to alternative_agent\n",
" </div>\n",
" \"\"\")\n",
" )\n",
" # Create and send a message for the alternative_agent\n",
" user_msg = ChatMessage(\n",
" Role.USER,\n",
" text=f\"The user wants to see alternative destinations near {destination}. Please suggest one.\",\n",
" )\n",
" await ctx.send_message(AgentExecutorRequest(messages=[user_msg], should_respond=True))\n",
" \n",
" elif user_reply == \"no\":\n",
" display(\n",
" HTML(\"\"\"\n",
" <div style='padding: 12px; background: #ffcdd2; border-left: 4px solid #f44336; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>🚫 Routing:</strong> User declined alternatives → Will route to cancellation_agent\n",
" </div>\n",
" \"\"\")\n",
" )\n",
" # Create and send a message for the cancellation_agent\n",
" user_msg = ChatMessage(\n",
" Role.USER,\n",
" text=\"The user has declined to see alternatives. Please acknowledge their decision.\",\n",
" )\n",
" await ctx.send_message(AgentExecutorRequest(messages=[user_msg], should_respond=True))\n",
" \n",
" else:\n",
" # Handle unexpected input - treat as decline\n",
" display(\n",
" HTML(f\"\"\"\n",
" <div style='padding: 12px; background: #fff3e0; border-left: 4px solid #ff9800; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>⚠️ Warning:</strong> Unexpected input \"{user_reply}\" - treating as decline\n",
" </div>\n",
" \"\"\")\n",
" )\n",
" user_msg = ChatMessage(\n",
" Role.USER,\n",
" text=\"The user has declined to see alternatives. Please acknowledge their decision.\",\n",
" )\n",
" await ctx.send_message(AgentExecutorRequest(messages=[user_msg], should_respond=True))\n",
"\n",
"\n",
"print(\"✅ DecisionManager executor created with @handler method for human feedback\")"
]
},
{
"cell_type": "markdown",
"id": "0e3cf777",
"metadata": {},
"source": [
"## مرحله ۶: ایجاد اجراکننده نمایش سفارشی\n",
"\n",
"همان اجراکننده نمایش از جریان کاری شرطی - نتایج نهایی را به عنوان خروجی جریان کاری ارائه می‌دهد.\n"
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "3b54aceb",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"✅ prepare_human_request executor created with @executor decorator\n",
"✅ display_result executor created with @executor decorator\n"
]
}
],
"source": [
"@executor(id=\"prepare_human_request\")\n",
"async def prepare_human_request(\n",
" response: AgentExecutorResponse, \n",
" ctx: WorkflowContext[HumanFeedbackRequest]\n",
") -> None:\n",
" \"\"\"\n",
" Transform agent response into HumanFeedbackRequest for RequestInfoExecutor.\n",
" \n",
" This executor bridges the type gap between:\n",
" - confirmation_agent outputs AgentExecutorResponse with ConfirmationQuestion JSON\n",
" - request_info_executor expects HumanFeedbackRequest (RequestInfoMessage dataclass)\n",
" \"\"\"\n",
" display(\n",
" HTML(\"\"\"\n",
" <div style='padding: 12px; background: #e1f5fe; border-left: 4px solid #0288d1; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>🔄 Transform:</strong> Converting ConfirmationQuestion to HumanFeedbackRequest\n",
" </div>\n",
" \"\"\")\n",
" )\n",
" \n",
" # Parse the agent's Pydantic output (ConfirmationQuestion)\n",
" confirmation = ConfirmationQuestion.model_validate_json(response.agent_run_response.text)\n",
" \n",
" # Convert to HumanFeedbackRequest dataclass for RequestInfoExecutor\n",
" feedback_request = HumanFeedbackRequest(\n",
" prompt=confirmation.question,\n",
" destination=confirmation.destination\n",
" )\n",
" \n",
" # Send the properly typed RequestInfoMessage to the RequestInfoExecutor\n",
" await ctx.send_message(feedback_request)\n",
"\n",
"\n",
"@executor(id=\"display_result\")\n",
"async def display_result(response: AgentExecutorResponse, ctx: WorkflowContext[Never, str]) -> None:\n",
" \"\"\"\n",
" Display the final result as workflow output.\n",
" \n",
" This executor receives the final agent response and yields it as the workflow output.\n",
" \"\"\"\n",
" display(\n",
" HTML(\"\"\"\n",
" <div style='padding: 15px; background: #f3e5f5; border-left: 4px solid #9c27b0; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>📤 Display Executor:</strong> Yielding workflow output\n",
" </div>\n",
" \"\"\")\n",
" )\n",
"\n",
" await ctx.yield_output(response.agent_run_response.text)\n",
"\n",
"\n",
"print(\"✅ prepare_human_request executor created with @executor decorator\")\n",
"print(\"✅ display_result executor created with @executor decorator\")"
]
},
{
"cell_type": "markdown",
"id": "a19ed583",
"metadata": {},
"source": [
"## مرحله ۷: بارگذاری متغیرهای محیطی\n",
"\n",
"پیکربندی کلاینت LLM (مدل‌های GitHub، Azure OpenAI یا OpenAI).\n"
]
},
{
"cell_type": "code",
"execution_count": 27,
"id": "d4a07f6e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"✅ Chat client configured with GitHub Models\n"
]
}
],
"source": [
"# Load environment variables\n",
"load_dotenv()\n",
"\n",
"# Check for GitHub Models or OpenAI\n",
"chat_client = OpenAIChatClient(\n",
" base_url=os.environ.get(\"GITHUB_ENDPOINT\"), \n",
" api_key=os.environ.get(\"GITHUB_TOKEN\"), \n",
" model_id=\"gpt-4o\"\n",
")\n",
"\n",
"print(\"✅ Chat client configured with GitHub Models\")"
]
},
{
"cell_type": "markdown",
"id": "7aa31e3d",
"metadata": {},
"source": [
"## مرحله ۸: ایجاد عوامل هوش مصنوعی و اجراکننده‌ها\n",
"\n",
"ما **شش جزء کاری** ایجاد می‌کنیم:\n",
"\n",
"**عوامل (در AgentExecutor پیچیده شده‌اند):**\n",
"1. **availability_agent** - بررسی موجودی هتل با استفاده از ابزار\n",
"2. **confirmation_agent** - 🆕 آماده‌سازی درخواست تأیید از انسان\n",
"3. **alternative_agent** - پیشنهاد شهرهای جایگزین (وقتی کاربر می‌گوید بله)\n",
"4. **booking_agent** - تشویق به رزرو (وقتی اتاق‌ها موجود هستند)\n",
"5. **cancellation_agent** - 🆕 مدیریت پیام لغو (وقتی کاربر می‌گوید نه)\n",
"\n",
"**اجراکننده‌های ویژه:**\n",
"6. **request_info_executor** - 🆕 `RequestInfoExecutor` که جریان کار را برای دریافت ورودی انسانی متوقف می‌کند\n",
"7. **decision_manager** - 🆕 اجراکننده سفارشی که بر اساس پاسخ انسانی مسیر را تعیین می‌کند (قبلاً در بالا تعریف شده است)\n"
]
},
{
"cell_type": "code",
"execution_count": 28,
"id": "216da316",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
" <div style='padding: 15px; background: #e3f2fd; border-left: 4px solid #2196f3; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>✅ Created Workflow Components:</strong>\n",
" <ul style='margin: 10px 0 0 0;'>\n",
" <li><strong>availability_agent</strong> - Checks availability with hotel_booking tool</li>\n",
" <li><strong>confirmation_agent</strong> 🆕 - Prepares human confirmation request</li>\n",
" <li><strong>alternative_agent</strong> - Suggests alternative cities</li>\n",
" <li><strong>booking_agent</strong> - Encourages booking</li>\n",
" <li><strong>cancellation_agent</strong> 🆕 - Handles user declining alternatives</li>\n",
" <li><strong>request_info_executor</strong> 🆕 - Pauses workflow for human input</li>\n",
" <li><strong>decision_manager</strong> 🆕 - Routes based on human response</li>\n",
" </ul>\n",
" </div>\n"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Agent 1: Check availability with tool (same as conditional workflow)\n",
"availability_agent = AgentExecutor(\n",
" chat_client.create_agent(\n",
" instructions=(\n",
" \"You are a hotel booking assistant that checks room availability. \"\n",
" \"Use the hotel_booking tool to check if rooms are available at the destination. \"\n",
" \"Return JSON with fields: destination (string), has_availability (bool), and message (string). \"\n",
" \"The message should summarize the availability status.\"\n",
" ),\n",
" tools=[hotel_booking],\n",
" response_format=BookingCheckResult,\n",
" ),\n",
" id=\"availability_agent\",\n",
")\n",
"\n",
"# Agent 2: NEW - Prepare human confirmation request\n",
"confirmation_agent = AgentExecutor(\n",
" chat_client.create_agent(\n",
" instructions=(\n",
" \"You are a helpful assistant. The user's requested destination has no available hotel rooms. \"\n",
" \"Create a polite message asking if they would like to see alternative destinations nearby. \"\n",
" \"Return a JSON with: destination (the unavailable city), and question (a friendly yes/no question). \"\n",
" \"Keep the question concise and friendly.\"\n",
" ),\n",
" response_format=ConfirmationQuestion, # Use Pydantic model for agent output\n",
" ),\n",
" id=\"confirmation_agent\",\n",
")\n",
"\n",
"# Agent 3: Suggest alternative (when user says yes)\n",
"alternative_agent = AgentExecutor(\n",
" chat_client.create_agent(\n",
" instructions=(\n",
" \"You are a helpful travel assistant. When a user cannot find hotels in their requested city, \"\n",
" \"suggest an alternative nearby city that has availability. \"\n",
" \"Return JSON with fields: alternative_destination (string) and reason (string). \"\n",
" \"Make your suggestion sound appealing and helpful.\"\n",
" ),\n",
" response_format=AlternativeResult,\n",
" ),\n",
" id=\"alternative_agent\",\n",
")\n",
"\n",
"# Agent 4: Suggest booking (when rooms available)\n",
"booking_agent = AgentExecutor(\n",
" chat_client.create_agent(\n",
" instructions=(\n",
" \"You are a booking assistant. The user has found available hotel rooms. \"\n",
" \"Encourage them to book by highlighting the destination's appeal. \"\n",
" \"Return JSON with fields: destination (string), action (string), and message (string). \"\n",
" \"The action should be 'book_now' and message should be encouraging.\"\n",
" ),\n",
" response_format=BookingConfirmation,\n",
" ),\n",
" id=\"booking_agent\",\n",
")\n",
"\n",
"# Agent 5: NEW - Handle cancellation when user declines alternatives\n",
"class CancellationMessage(BaseModel):\n",
" \"\"\"Message when user declines alternatives.\"\"\"\n",
" status: str\n",
" message: str\n",
"\n",
"cancellation_agent = AgentExecutor(\n",
" chat_client.create_agent(\n",
" instructions=(\n",
" \"You are a helpful assistant. The user has declined to see alternative hotel destinations. \"\n",
" \"Create a polite cancellation message. \"\n",
" \"Return JSON with: status (should be 'cancelled'), and message (a friendly acknowledgment). \"\n",
" \"Keep the message brief and understanding.\"\n",
" ),\n",
" response_format=CancellationMessage,\n",
" ),\n",
" id=\"cancellation_agent\",\n",
")\n",
"\n",
"# NEW: RequestInfoExecutor - pauses workflow to gather human input\n",
"request_info_executor = RequestInfoExecutor(id=\"request_info\")\n",
"\n",
"# NEW: DecisionManager instance - routes based on human feedback\n",
"decision_manager = DecisionManager(id=\"decision_manager\")\n",
"\n",
"display(\n",
" HTML(\"\"\"\n",
" <div style='padding: 15px; background: #e3f2fd; border-left: 4px solid #2196f3; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>✅ Created Workflow Components:</strong>\n",
" <ul style='margin: 10px 0 0 0;'>\n",
" <li><strong>availability_agent</strong> - Checks availability with hotel_booking tool</li>\n",
" <li><strong>confirmation_agent</strong> 🆕 - Prepares human confirmation request</li>\n",
" <li><strong>alternative_agent</strong> - Suggests alternative cities</li>\n",
" <li><strong>booking_agent</strong> - Encourages booking</li>\n",
" <li><strong>cancellation_agent</strong> 🆕 - Handles user declining alternatives</li>\n",
" <li><strong>request_info_executor</strong> 🆕 - Pauses workflow for human input</li>\n",
" <li><strong>decision_manager</strong> 🆕 - Routes based on human response</li>\n",
" </ul>\n",
" </div>\n",
"\"\"\")\n",
")"
]
},
{
"cell_type": "markdown",
"id": "8704fd1c",
"metadata": {},
"source": [
"## مرحله ۹: ساخت جریان کاری با حضور انسان در حلقه\n",
"\n",
"اکنون نمودار جریان کاری را با **مسیرهای شرطی** شامل مسیر حضور انسان در حلقه ایجاد می‌کنیم:\n",
"\n",
"**ساختار جریان کاری:**\n",
"```\n",
"availability_agent (START)\n",
" ↓\n",
" Evaluate conditions\n",
" ↙ ↘\n",
"[no_availability] [has_availability]\n",
" ↓ ↓\n",
"confirmation_agent booking_agent\n",
" ↓ ↓\n",
"prepare_human_request display_result\n",
" ↓\n",
"request_info_executor (PAUSE)\n",
" ↓\n",
"decision_manager\n",
" ↙ ↘\n",
"[yes] [no]\n",
" ↓ ↓\n",
"alternative cancellation\n",
" ↓ ↓\n",
"display_result\n",
"```\n",
"\n",
"**مسیرهای کلیدی:**\n",
"- `availability_agent → confirmation_agent` (وقتی اتاقی موجود نیست)\n",
"- `confirmation_agent → prepare_human_request` (تغییر نوع)\n",
"- `prepare_human_request → request_info_executor` (توقف برای انسان)\n",
"- `request_info_executor → decision_manager` (همیشه - ارائه RequestResponse)\n",
"- `decision_manager → alternative_agent` (وقتی کاربر می‌گوید \"بله\")\n",
"- `decision_manager → cancellation_agent` (وقتی کاربر می‌گوید \"نه\")\n",
"- `availability_agent → booking_agent` (وقتی اتاق‌ها موجود هستند)\n",
"- همه مسیرها در `display_result` پایان می‌یابند\n"
]
},
{
"cell_type": "code",
"execution_count": 29,
"id": "74b9102f",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
" <div style='padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 8px; margin: 10px 0;'>\n",
" <h3 style='margin: 0 0 15px 0;'>✅ Workflow Built Successfully!</h3>\n",
" <p style='margin: 0; line-height: 1.6;'>\n",
" <strong>Human-in-the-Loop Routing:</strong><br>\n",
" • If <strong>NO availability</strong> → confirmation_agent → prepare_human_request → request_info_executor → <strong>PAUSE FOR HUMAN</strong> → decision_manager<br>\n",
" &nbsp;&nbsp;• If user says <strong>YES</strong> → alternative_agent → display_result<br>\n",
" &nbsp;&nbsp;• If user says <strong>NO</strong> → cancellation_agent → display_result<br>\n",
" • If <strong>availability</strong> → booking_agent → display_result (no human input needed)\n",
" </p>\n",
" </div>\n"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Build the workflow with human-in-the-loop routing\n",
"workflow = (\n",
" WorkflowBuilder()\n",
" .set_start_executor(availability_agent)\n",
" \n",
" # NO AVAILABILITY PATH (with human-in-the-loop)\n",
" .add_edge(availability_agent, confirmation_agent, condition=no_availability_condition)\n",
" .add_edge(confirmation_agent, prepare_human_request) # Transform to HumanFeedbackRequest\n",
" .add_edge(prepare_human_request, request_info_executor) # Send to RequestInfoExecutor\n",
" .add_edge(request_info_executor, decision_manager) # Always goes to decision manager\n",
" \n",
" # Decision manager routes based on user response\n",
" .add_edge(decision_manager, alternative_agent, condition=user_wants_alternatives_condition)\n",
" .add_edge(decision_manager, cancellation_agent, condition=user_declines_alternatives_condition)\n",
" .add_edge(alternative_agent, display_result)\n",
" .add_edge(cancellation_agent, display_result)\n",
" \n",
" # HAS AVAILABILITY PATH (no human input needed)\n",
" .add_edge(availability_agent, booking_agent, condition=has_availability_condition)\n",
" .add_edge(booking_agent, display_result)\n",
" \n",
" .build()\n",
")\n",
"\n",
"display(\n",
" HTML(\"\"\"\n",
" <div style='padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 8px; margin: 10px 0;'>\n",
" <h3 style='margin: 0 0 15px 0;'>✅ Workflow Built Successfully!</h3>\n",
" <p style='margin: 0; line-height: 1.6;'>\n",
" <strong>Human-in-the-Loop Routing:</strong><br>\n",
" • If <strong>NO availability</strong> → confirmation_agent → prepare_human_request → request_info_executor → <strong>PAUSE FOR HUMAN</strong> → decision_manager<br>\n",
" &nbsp;&nbsp;• If user says <strong>YES</strong> → alternative_agent → display_result<br>\n",
" &nbsp;&nbsp;• If user says <strong>NO</strong> → cancellation_agent → display_result<br>\n",
" • If <strong>availability</strong> → booking_agent → display_result (no human input needed)\n",
" </p>\n",
" </div>\n",
"\"\"\")\n",
")"
]
},
{
"cell_type": "markdown",
"id": "82f28d21",
"metadata": {},
"source": [
"## مرحله ۱۰: اجرای تست مورد ۱ - شهر بدون موجودی (پاریس با تأیید انسانی)\n",
"\n",
"این تست چرخه کامل **انسان در حلقه** را نشان می‌دهد:\n",
"\n",
"1. درخواست هتل در پاریس\n",
"2. بررسی توسط availability_agent → بدون اتاق\n",
"3. confirmation_agent سوالی برای انسان ایجاد می‌کند\n",
"4. request_info_executor **جریان کار را متوقف می‌کند** و `RequestInfoEvent` را ارسال می‌کند\n",
"5. **برنامه رویداد را تشخیص داده و کاربر را در کنسول درخواست می‌کند**\n",
"6. کاربر \"بله\" یا \"خیر\" تایپ می‌کند\n",
"7. برنامه پاسخ را از طریق `send_responses_streaming()` ارسال می‌کند\n",
"8. decision_manager بر اساس پاسخ مسیر را تعیین می‌کند\n",
"9. نتیجه نهایی نمایش داده می‌شود\n",
"\n",
"**الگوی کلیدی:**\n",
"- استفاده از `workflow.run_stream()` برای اولین تکرار\n",
"- استفاده از `workflow.send_responses_streaming(pending_responses)` برای تکرارهای بعدی\n",
"- گوش دادن به `RequestInfoEvent` برای تشخیص زمانی که ورودی انسانی لازم است\n",
"- گوش دادن به `WorkflowOutputEvent` برای دریافت نتایج نهایی\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d89e37c8",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\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;'>🧪 TEST CASE 1: Paris (No Availability - Human-in-the-Loop)</h3>\n",
" <p style='margin: 0;'>Expected workflow path: availability_agent → confirmation_agent → request_info_executor → <strong>PAUSE</strong> → decision_manager → (depends on user input)</p>\n",
" </div>\n"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"🔄 Starting human-in-the-loop workflow...\n",
"============================================================\n",
"\n",
"🚀 Starting workflow with request: 'I want to book a hotel in Paris'\n"
]
},
{
"data": {
"text/html": [
"\n",
" <div style='padding: 15px; background: #e3f2fd; border-left: 4px solid #2196f3; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>🔍 Tool Invoked:</strong> hotel_booking(\"Paris\")\n",
" </div>\n",
" "
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"\n",
" <div style='padding: 12px; background: #ffecb3; border-left: 4px solid #ff9800; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>❌ Condition Check:</strong> no_availability for Paris\n",
" </div>\n",
" "
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"\n",
" <div style='padding: 12px; background: #c8e6c9; border-left: 4px solid #4caf50; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>✅ Condition Check:</strong> has_availability = <strong>False</strong> for Paris\n",
" </div>\n",
" "
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"\n",
" <div style='padding: 12px; background: #e1f5fe; border-left: 4px solid #0288d1; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>🔄 Transform:</strong> Converting ConfirmationQuestion to HumanFeedbackRequest\n",
" </div>\n",
" "
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"⏸️ WORKFLOW PAUSED - Human input requested!\n",
" Request ID: 032c8fce-b9d1-400e-ba8d-afd2248e2926\n",
" Destination: Paris\n",
"\n",
"============================================================\n",
"💬 QUESTION FOR YOU:\n",
" Unfortunately, there are no rooms available in Paris. Would you like to explore nearby alternative destinations?\n",
"============================================================\n",
"\n",
"📝 You answered: yes\n",
"\n",
"📤 Sending human responses: {'032c8fce-b9d1-400e-ba8d-afd2248e2926': 'yes'}\n",
"\n",
"🚀 Starting workflow with request: 'I want to book a hotel in Paris'\n",
"\n",
"📝 You answered: yes\n",
"\n",
"📤 Sending human responses: {'032c8fce-b9d1-400e-ba8d-afd2248e2926': 'yes'}\n",
"\n",
"🚀 Starting workflow with request: 'I want to book a hotel in Paris'\n"
]
},
{
"data": {
"text/html": [
"\n",
" <div style='padding: 15px; background: #e3f2fd; border-left: 4px solid #2196f3; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>🔍 Tool Invoked:</strong> hotel_booking(\"Paris\")\n",
" </div>\n",
" "
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"\n",
" <div style='padding: 12px; background: #ffecb3; border-left: 4px solid #ff9800; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>❌ Condition Check:</strong> no_availability for Paris\n",
" </div>\n",
" "
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"\n",
" <div style='padding: 12px; background: #c8e6c9; border-left: 4px solid #4caf50; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>✅ Condition Check:</strong> has_availability = <strong>False</strong> for Paris\n",
" </div>\n",
" "
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"\n",
" <div style='padding: 12px; background: #e1f5fe; border-left: 4px solid #0288d1; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>🔄 Transform:</strong> Converting ConfirmationQuestion to HumanFeedbackRequest\n",
" </div>\n",
" "
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"⏸️ WORKFLOW PAUSED - Human input requested!\n",
" Request ID: cf48dad0-ee5e-4f60-8806-341a7a292bd4\n",
" Destination: Paris\n",
"\n",
"============================================================\n",
"💬 QUESTION FOR YOU:\n",
" I'm sorry to inform you that there are no available hotel rooms in Paris. Would you like me to suggest nearby alternative destinations?\n",
"============================================================\n",
"\n",
"📝 You answered: \n",
"\n",
"📤 Sending human responses: {'cf48dad0-ee5e-4f60-8806-341a7a292bd4': ''}\n",
"\n",
"🚀 Starting workflow with request: 'I want to book a hotel in Paris'\n",
"\n",
"📝 You answered: \n",
"\n",
"📤 Sending human responses: {'cf48dad0-ee5e-4f60-8806-341a7a292bd4': ''}\n",
"\n",
"🚀 Starting workflow with request: 'I want to book a hotel in Paris'\n"
]
},
{
"data": {
"text/html": [
"\n",
" <div style='padding: 15px; background: #e3f2fd; border-left: 4px solid #2196f3; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>🔍 Tool Invoked:</strong> hotel_booking(\"Paris\")\n",
" </div>\n",
" "
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"display(\n",
" HTML(\"\"\"\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;'>🧪 TEST CASE 1: Paris (No Availability - Human-in-the-Loop)</h3>\n",
" <p style='margin: 0;'>Expected workflow path: availability_agent → confirmation_agent → request_info_executor → <strong>PAUSE</strong> → decision_manager → (depends on user input)</p>\n",
" </div>\n",
"\"\"\")\n",
")\n",
"\n",
"# Create request for Paris\n",
"request_paris = AgentExecutorRequest(\n",
" messages=[ChatMessage(Role.USER, text=\"I want to book a hotel in Paris\")], \n",
" should_respond=True\n",
")\n",
"\n",
"# Human-in-the-loop execution pattern\n",
"pending_responses: dict[str, str] | None = None\n",
"completed = False\n",
"workflow_output: str | None = None\n",
"\n",
"print(\"\\n🔄 Starting human-in-the-loop workflow...\")\n",
"print(\"=\" * 60)\n",
"\n",
"while not completed:\n",
" # First iteration uses run_stream with the request\n",
" # Subsequent iterations use send_responses_streaming with collected human responses\n",
" if pending_responses:\n",
" print(f\"\\n📤 Sending human responses: {pending_responses}\")\n",
" stream = workflow.send_responses_streaming(pending_responses)\n",
" pending_responses = None # Clear immediately after sending\n",
" else:\n",
" print(f\"\\n🚀 Starting workflow with request: 'I want to book a hotel in Paris'\")\n",
" stream = workflow.run_stream(request_paris)\n",
" \n",
" # Collect all events from this iteration\n",
" events = [event async for event in stream]\n",
" \n",
" # Process events\n",
" requests: list[tuple[str, str]] = [] # (request_id, prompt)\n",
" \n",
" for event in events:\n",
" # Check for human input requests\n",
" if isinstance(event, RequestInfoEvent) and isinstance(event.data, HumanFeedbackRequest):\n",
" print(f\"\\n⏸️ WORKFLOW PAUSED - Human input requested!\")\n",
" print(f\" Request ID: {event.request_id}\")\n",
" print(f\" Destination: {event.data.destination}\")\n",
" requests.append((event.request_id, event.data.prompt))\n",
" \n",
" # Check for workflow outputs\n",
" elif isinstance(event, WorkflowOutputEvent):\n",
" workflow_output = str(event.data)\n",
" completed = True\n",
" print(f\"\\n✅ Workflow completed with output!\")\n",
" \n",
" # If we have human requests, prompt the user\n",
" if requests and not completed:\n",
" responses: dict[str, str] = {}\n",
" for req_id, prompt in requests:\n",
" print(f\"\\n{'='*60}\")\n",
" print(f\"💬 QUESTION FOR YOU:\")\n",
" print(f\" {prompt}\")\n",
" print(f\"{'='*60}\")\n",
" \n",
" # Get user input (in notebook, this will pause execution)\n",
" answer = input(\"👉 Enter 'yes' or 'no': \").strip().lower()\n",
" \n",
" print(f\"\\n📝 You answered: {answer}\")\n",
" responses[req_id] = answer\n",
" \n",
" pending_responses = responses\n",
"\n",
"print(f\"\\n{'='*60}\")\n",
"print(f\"🏆 FINAL WORKFLOW OUTPUT:\")\n",
"print(f\"{'='*60}\")\n",
"\n",
"# Display final result\n",
"if workflow_output:\n",
" # Try to parse as JSON for pretty display\n",
" try:\n",
" result_data = json.loads(workflow_output)\n",
" if \"alternative_destination\" in result_data:\n",
" result_obj = AlternativeResult.model_validate_json(workflow_output)\n",
" display(\n",
" HTML(f\"\"\"\n",
" <div style='padding: 25px; background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%); border-radius: 12px; box-shadow: 0 4px 12px rgba(255,165,0,0.3); margin: 20px 0;'>\n",
" <h3 style='margin: 0 0 15px 0; color: #333;'>🏆 WORKFLOW RESULT</h3>\n",
" <div style='background: white; padding: 20px; border-radius: 8px;'>\n",
" <p style='margin: 0 0 10px 0; font-size: 16px;'><strong>Status:</strong> ❌ No rooms in Paris</p>\n",
" <p style='margin: 0 0 10px 0; font-size: 16px;'><strong>User Decision:</strong> ✅ Accepted alternatives</p>\n",
" <p style='margin: 0 0 10px 0; font-size: 16px;'><strong>Alternative Suggestion:</strong> 🏨 {result_obj.alternative_destination}</p>\n",
" <p style='margin: 0; font-size: 14px; color: #666;'><strong>Reason:</strong> {result_obj.reason}</p>\n",
" </div>\n",
" </div>\n",
" \"\"\")\n",
" )\n",
" else:\n",
" # User declined\n",
" display(\n",
" HTML(f\"\"\"\n",
" <div style='padding: 25px; background: linear-gradient(135deg, #f44336 0%, #e91e63 100%); color: white; border-radius: 12px; box-shadow: 0 4px 12px rgba(244,67,54,0.3); margin: 20px 0;'>\n",
" <h3 style='margin: 0 0 15px 0;'>🏆 WORKFLOW RESULT</h3>\n",
" <div style='background: white; color: #333; padding: 20px; border-radius: 8px;'>\n",
" <p style='margin: 0 0 10px 0; font-size: 16px;'><strong>Status:</strong> ❌ No rooms in Paris</p>\n",
" <p style='margin: 0 0 10px 0; font-size: 16px;'><strong>User Decision:</strong> 🚫 Declined alternatives</p>\n",
" <p style='margin: 0; font-size: 14px; color: #666;'><strong>Result:</strong> Booking request cancelled</p>\n",
" </div>\n",
" </div>\n",
" \"\"\")\n",
" )\n",
" except:\n",
" print(workflow_output)"
]
},
{
"cell_type": "markdown",
"id": "8409f8d1",
"metadata": {},
"source": [
"## مرحله 11: اجرای مورد آزمایشی 2 - شهر با موجودی (استکهلم - بدون نیاز به ورودی انسانی)\n",
"\n",
"این آزمایش مسیر **مستقیم** را زمانی که اتاق‌ها موجود هستند نشان می‌دهد:\n",
"\n",
"1. درخواست هتل در استکهلم\n",
"2. بررسی توسط availability_agent → اتاق‌ها موجود هستند ✅\n",
"3. booking_agent پیشنهاد رزرو می‌دهد\n",
"4. display_result تأییدیه را نشان می‌دهد\n",
"5. **نیازی به ورودی انسانی نیست!**\n",
"\n",
"این جریان کاملاً مسیر انسانی در حلقه را زمانی که اتاق‌ها موجود هستند دور می‌زند.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f9971239",
"metadata": {},
"outputs": [],
"source": [
"display(\n",
" HTML(\"\"\"\n",
" <div style='padding: 20px; background: #e8f5e9; border-left: 4px solid #4caf50; border-radius: 8px; margin: 20px 0;'>\n",
" <h3 style='margin: 0 0 10px 0; color: #1b5e20;'>🧪 TEST CASE 2: Stockholm (Has Availability - No Human Input)</h3>\n",
" <p style='margin: 0;'>Expected workflow path: availability_agent → booking_agent → display_result (direct, no pause)</p>\n",
" </div>\n",
"\"\"\")\n",
")\n",
"\n",
"# Create request for Stockholm\n",
"request_stockholm = AgentExecutorRequest(\n",
" messages=[ChatMessage(Role.USER, text=\"I want to book a hotel in Stockholm\")], \n",
" should_respond=True\n",
")\n",
"\n",
"# Run the workflow (should complete without human input)\n",
"events_stockholm = await workflow.run(request_stockholm)\n",
"outputs_stockholm = events_stockholm.get_outputs()\n",
"\n",
"# Display results\n",
"if outputs_stockholm:\n",
" result_stockholm = BookingConfirmation.model_validate_json(outputs_stockholm[0])\n",
"\n",
" display(\n",
" HTML(f\"\"\"\n",
" <div style='padding: 25px; background: linear-gradient(135deg, #4caf50 0%, #8bc34a 100%); color: white; border-radius: 12px; box-shadow: 0 4px 12px rgba(76,175,80,0.3); margin: 20px 0;'>\n",
" <h3 style='margin: 0 0 15px 0;'>🏆 WORKFLOW RESULT (Stockholm - No Human Input)</h3>\n",
" <div style='background: white; color: #333; padding: 20px; border-radius: 8px;'>\n",
" <p style='margin: 0 0 10px 0; font-size: 16px;'><strong>Status:</strong> ✅ Rooms Available!</p>\n",
" <p style='margin: 0 0 10px 0; font-size: 16px;'><strong>Destination:</strong> 🏨 {result_stockholm.destination}</p>\n",
" <p style='margin: 0 0 10px 0; font-size: 16px;'><strong>Action:</strong> {result_stockholm.action}</p>\n",
" <p style='margin: 0 0 10px 0; font-size: 14px; color: #666;'><strong>Message:</strong> {result_stockholm.message}</p>\n",
" <p style='margin: 10px 0 0 0; font-size: 12px; color: #999; font-style: italic;'>Note: No human input was requested because rooms were available!</p>\n",
" </div>\n",
" </div>\n",
" \"\"\")\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "b40fa995",
"metadata": {},
"source": [
"## نکات کلیدی و بهترین روش‌ها برای انسان در حلقه\n",
"\n",
"### ✅ آنچه آموختید:\n",
"\n",
"#### 1. **الگوی RequestInfoExecutor**\n",
"الگوی انسان در حلقه در Microsoft Agent Framework از سه جزء اصلی استفاده می‌کند:\n",
"- `RequestInfoExecutor` - جریان کار را متوقف کرده و رویدادها را منتشر می‌کند\n",
"- `RequestInfoMessage` - کلاس پایه برای بارهای درخواست تایپ شده (این را زیرکلاس کنید!)\n",
"- `RequestResponse` - پاسخ‌های انسانی را با درخواست‌های اصلی مرتبط می‌کند\n",
"\n",
"**درک حیاتی:**\n",
"- `RequestInfoExecutor` خودش ورودی جمع‌آوری نمی‌کند - فقط جریان کار را متوقف می‌کند\n",
"- کد برنامه شما باید به `RequestInfoEvent` گوش دهد و ورودی را جمع‌آوری کند\n",
"- شما باید `send_responses_streaming()` را با یک دیکشنری که `request_id` را به پاسخ کاربر نگاشت می‌کند، فراخوانی کنید\n",
"\n",
"#### 2. **الگوی اجرای جریان**\n",
"```python\n",
"# First iteration\n",
"stream = workflow.run_stream(initial_request)\n",
"\n",
"# Subsequent iterations (after human input)\n",
"stream = workflow.send_responses_streaming(pending_responses)\n",
"\n",
"# Always process events\n",
"events = [event async for event in stream]\n",
"```\n",
"\n",
"#### 3. **معماری مبتنی بر رویداد**\n",
"برای کنترل جریان کار به رویدادهای خاص گوش دهید:\n",
"- `RequestInfoEvent` - نیاز به ورودی انسانی (جریان کار متوقف شده)\n",
"- `WorkflowOutputEvent` - نتیجه نهایی در دسترس است (جریان کار کامل شده)\n",
"- `WorkflowStatusEvent` - تغییرات وضعیت (IN_PROGRESS، IDLE_WITH_PENDING_REQUESTS و غیره)\n",
"\n",
"#### 4. **اجراکننده‌های سفارشی با @handler**\n",
"`DecisionManager` نشان می‌دهد که چگونه اجراکننده‌هایی ایجاد کنید که:\n",
"- از دکوریتور `@handler` برای نمایش متدها به عنوان مراحل جریان کار استفاده می‌کنند\n",
"- پیام‌های تایپ شده دریافت می‌کنند (مانند `RequestResponse[HumanFeedbackRequest, str]`)\n",
"- جریان کار را با ارسال پیام‌ها به اجراکننده‌های دیگر هدایت می‌کنند\n",
"- از طریق `WorkflowContext` به زمینه دسترسی دارند\n",
"\n",
"#### 5. **مسیر شرطی با تصمیمات انسانی**\n",
"می‌توانید توابع شرطی ایجاد کنید که پاسخ‌های انسانی را ارزیابی کنند:\n",
"```python\n",
"def user_wants_alternatives_condition(message: Any) -> bool:\n",
" response_text = message.agent_run_response.text.lower()\n",
" return response_text == \"yes\"\n",
"```\n",
"\n",
"### 🎯 کاربردهای واقعی:\n",
"\n",
"1. **جریان‌های کاری تأیید**\n",
" - دریافت تأیید مدیر قبل از پردازش گزارش‌های هزینه\n",
" - نیاز به بررسی انسانی قبل از ارسال ایمیل‌های خودکار\n",
" - تأیید تراکنش‌های با ارزش بالا قبل از اجرا\n",
"\n",
"2. **نظارت بر محتوا**\n",
" - علامت‌گذاری محتوای مشکوک برای بررسی انسانی\n",
" - درخواست از ناظران برای تصمیم‌گیری نهایی در موارد مرزی\n",
" - ارجاع به انسان‌ها زمانی که اعتماد به نفس هوش مصنوعی پایین است\n",
"\n",
"3. **خدمات مشتری**\n",
" - اجازه دهید هوش مصنوعی به طور خودکار به سوالات معمولی پاسخ دهد\n",
" - مسائل پیچیده را به نمایندگان انسانی ارجاع دهید\n",
" - از مشتری بپرسید که آیا می‌خواهد با یک انسان صحبت کند\n",
"\n",
"4. **پردازش داده‌ها**\n",
" - درخواست از انسان‌ها برای حل ورودی‌های داده مبهم\n",
" - تأیید تفسیرهای هوش مصنوعی از اسناد نامشخص\n",
" - اجازه دهید کاربران بین تفسیرهای معتبر مختلف انتخاب کنند\n",
"\n",
"5. **سیستم‌های حساس به ایمنی**\n",
" - نیاز به تأیید انسانی قبل از اقدامات غیرقابل برگشت\n",
" - دریافت تأیید قبل از دسترسی به داده‌های حساس\n",
" - تأیید تصمیمات در صنایع تنظیم‌شده (بهداشت و درمان، مالی)\n",
"\n",
"6. **عامل‌های تعاملی**\n",
" - ساخت ربات‌های مکالمه‌ای که سوالات پیگیری می‌پرسند\n",
" - ایجاد راهنماهایی که کاربران را در فرآیندهای پیچیده هدایت می‌کنند\n",
" - طراحی عامل‌هایی که مرحله به مرحله با انسان‌ها همکاری می‌کنند\n",
"\n",
"### 🔄 مقایسه: با و بدون انسان در حلقه\n",
"\n",
"| ویژگی | جریان کار شرطی | جریان کار انسان در حلقه |\n",
"|-------|----------------|-------------------------|\n",
"| **اجرا** | یک `workflow.run()` | حلقه با `run_stream()` + `send_responses_streaming()` |\n",
"| **ورودی کاربر** | هیچ (کاملاً خودکار) | درخواست‌های تعاملی از طریق `input()` یا رابط کاربری |\n",
"| **اجزاء** | عامل‌ها + اجراکننده‌ها | + RequestInfoExecutor + DecisionManager |\n",
"| **رویدادها** | فقط AgentExecutorResponse | RequestInfoEvent، WorkflowOutputEvent و غیره |\n",
"| **توقف** | بدون توقف | جریان کار در RequestInfoExecutor متوقف می‌شود |\n",
"| **کنترل انسانی** | بدون کنترل انسانی | انسان‌ها تصمیمات کلیدی می‌گیرند |\n",
"| **مورد استفاده** | اتوماسیون | همکاری و نظارت |\n",
"\n",
"### 🚀 الگوهای پیشرفته:\n",
"\n",
"#### نقاط تصمیم‌گیری انسانی متعدد\n",
"می‌توانید چندین گره `RequestInfoExecutor` در یک جریان کار داشته باشید:\n",
"```python\n",
".add_edge(agent1, request_info_1) # First human decision\n",
".add_edge(decision_manager_1, agent2)\n",
".add_edge(agent2, request_info_2) # Second human decision\n",
".add_edge(decision_manager_2, final_agent)\n",
"```\n",
"\n",
"#### مدیریت زمان‌بندی\n",
"زمان‌بندی برای پاسخ‌های انسانی را پیاده‌سازی کنید:\n",
"```python\n",
"import asyncio\n",
"\n",
"try:\n",
" answer = await asyncio.wait_for(\n",
" asyncio.to_thread(input, \"Enter yes/no: \"),\n",
" timeout=60.0\n",
" )\n",
"except asyncio.TimeoutError:\n",
" answer = \"no\" # Default to safe option\n",
"```\n",
"\n",
"#### ادغام رابط کاربری غنی\n",
"به جای `input()`، با رابط وب، Slack، Teams و غیره ادغام کنید:\n",
"```python\n",
"if isinstance(event, RequestInfoEvent):\n",
" # Send notification to user's preferred channel\n",
" await slack_client.send_message(\n",
" user_id=current_user,\n",
" text=event.data.prompt,\n",
" request_id=event.request_id\n",
" )\n",
"```\n",
"\n",
"#### انسان در حلقه شرطی\n",
"فقط در شرایط خاص درخواست ورودی انسانی کنید:\n",
"```python\n",
"def needs_human_approval_condition(message: Any) -> bool:\n",
" # Only route to human if confidence is low or value is high\n",
" if result.confidence < 0.7 or result.value > 10000:\n",
" return True\n",
" return False\n",
"```\n",
"\n",
"### ⚠️ بهترین روش‌ها:\n",
"\n",
"1. **همیشه RequestInfoMessage را زیرکلاس کنید**\n",
" - ایمنی نوع و اعتبارسنجی را فراهم می‌کند\n",
" - زمینه غنی برای رندر رابط کاربری را فعال می‌کند\n",
" - هدف هر نوع درخواست را روشن می‌کند\n",
"\n",
"2. **از درخواست‌های توصیفی استفاده کنید**\n",
" - شامل زمینه‌ای درباره آنچه می‌پرسید باشید\n",
" - پیامدهای هر انتخاب را توضیح دهید\n",
" - سوالات را ساده و واضح نگه دارید\n",
"\n",
"3. **مدیریت ورودی غیرمنتظره**\n",
" - پاسخ‌های کاربر را اعتبارسنجی کنید\n",
" - برای ورودی نامعتبر مقادیر پیش‌فرض ارائه دهید\n",
" - پیام‌های خطای واضح بدهید\n",
"\n",
"4. **ردیابی شناسه‌های درخواست**\n",
" - از ارتباط بین request_id و پاسخ‌ها استفاده کنید\n",
" - سعی نکنید وضعیت را به صورت دستی مدیریت کنید\n",
"\n",
"5. **طراحی برای غیرمسدودکننده بودن**\n",
" - نخ‌ها را منتظر ورودی نگه ندارید\n",
" - از الگوهای غیرهمزمان در سراسر استفاده کنید\n",
" - از نمونه‌های جریان کار همزمان پشتیبانی کنید\n",
"\n",
"### 📚 مفاهیم مرتبط:\n",
"\n",
"- **Agent Middleware** - رهگیری تماس‌های عامل (دفترچه قبلی)\n",
"- **مدیریت وضعیت جریان کار** - حفظ وضعیت جریان کار بین اجراها\n",
"- **همکاری چندعاملی** - ترکیب انسان در حلقه با تیم‌های عامل\n",
"- **معماری‌های مبتنی بر رویداد** - ساخت سیستم‌های واکنشی با رویدادها\n",
"\n",
"---\n",
"\n",
"### 🎓 تبریک!\n",
"\n",
"شما جریان‌های کاری انسان در حلقه را با Microsoft Agent Framework به خوبی یاد گرفتید! اکنون می‌دانید که چگونه:\n",
"- ✅ جریان‌های کاری را برای جمع‌آوری ورودی انسانی متوقف کنید\n",
"- ✅ از RequestInfoExecutor و RequestInfoMessage استفاده کنید\n",
"- ✅ اجرای جریان را با رویدادها مدیریت کنید\n",
"- ✅ اجراکننده‌های سفارشی با @handler ایجاد کنید\n",
"- ✅ جریان‌های کاری را بر اساس تصمیمات انسانی هدایت کنید\n",
"- ✅ عامل‌های هوش مصنوعی تعاملی بسازید که با انسان‌ها همکاری کنند\n",
"\n",
"**این یک الگوی حیاتی برای ساخت سیستم‌های هوش مصنوعی قابل اعتماد و قابل کنترل است!** 🚀\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**سلب مسئولیت**: \nاین سند با استفاده از سرویس ترجمه هوش مصنوعی [Co-op Translator](https://github.com/Azure/co-op-translator) ترجمه شده است. در حالی که ما تلاش می‌کنیم دقت را حفظ کنیم، لطفاً توجه داشته باشید که ترجمه‌های خودکار ممکن است شامل خطاها یا نادرستی‌ها باشند. سند اصلی به زبان اصلی آن باید به عنوان منبع معتبر در نظر گرفته شود. برای اطلاعات حساس، ترجمه حرفه‌ای انسانی توصیه می‌شود. ما مسئولیتی در قبال سوء تفاهم‌ها یا تفسیرهای نادرست ناشی از استفاده از این ترجمه نداریم.\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv (3.12.11)",
"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.11"
},
"coopTranslator": {
"original_hash": "c912e4987c5915f5bcaaae55f3f95074",
"translation_date": "2025-10-23T09:46:57+00:00",
"source_file": "14-microsoft-agent-framework/code-samples/14-human-loop.ipynb",
"language_code": "fa"
}
},
"nbformat": 4,
"nbformat_minor": 5
}