{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 人机交互循环\n\n在前一节[团队](./teams.ipynb)中,我们已经了解了如何创建、观察和控制一个智能体团队。\n本节将重点介绍如何从您的应用程序与团队进行交互,并向团队提供人工反馈。\n\n从应用程序与团队交互主要有两种方式:\n\n1. 在团队运行期间——执行{py:meth}`~autogen_agentchat.teams.BaseGroupChat.run`或{py:meth}`~autogen_agentchat.teams.BaseGroupChat.run_stream`时,通过{py:class}`~autogen_agentchat.agents.UserProxyAgent`提供反馈。\n2. 运行结束后,通过输入到下一次调用{py:meth}`~autogen_agentchat.teams.BaseGroupChat.run`或{py:meth}`~autogen_agentchat.teams.BaseGroupChat.run_stream`来提供反馈。\n\n我们将在本节中介绍这两种方法。\n\n要直接查看与Web和UI框架集成的代码示例,请参阅以下链接:\n- [AgentChat + FastAPI](https://github.com/microsoft/autogen/tree/main/python/samples/agentchat_fastapi)\n- [AgentChat + ChainLit](https://github.com/microsoft/autogen/tree/main/python/samples/agentchat_chainlit)\n- [AgentChat + Streamlit](https://github.com/microsoft/autogen/tree/main/python/samples/agentchat_streamlit)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 在运行期间提供反馈\n\n{py:class}`~autogen_agentchat.agents.UserProxyAgent`是一个特殊的内置智能体,\n它作为用户的代理向团队提供反馈。\n\n要使用{py:class}`~autogen_agentchat.agents.UserProxyAgent`,您可以创建其实例\n并在运行团队之前将其包含在团队中。\n团队会决定何时调用{py:class}`~autogen_agentchat.agents.UserProxyAgent`\n来向用户请求反馈。\n\n例如在{py:class}`~autogen_agentchat.teams.RoundRobinGroupChat`团队中,\n{py:class}`~autogen_agentchat.agents.UserProxyAgent`按照传递给团队的顺序被调用,\n而在{py:class}`~autogen_agentchat.teams.SelectorGroupChat`团队中,\n选择器提示或选择器函数决定何时调用{py:class}`~autogen_agentchat.agents.UserProxyAgent`。\n\n下图展示了如何在团队运行期间使用{py:class}`~autogen_agentchat.agents.UserProxyAgent`\n从用户获取反馈:\n\n![human-in-the-loop-user-proxy](./human-in-the-loop-user-proxy.svg)\n\n粗箭头表示团队运行期间的控制流:\n当团队调用{py:class}`~autogen_agentchat.agents.UserProxyAgent`时,\n它将控制权转移给应用程序/用户,并等待反馈;\n一旦提供反馈,控制权就转移回团队,团队继续执行。\n\n```{note}\n当在运行期间调用{py:class}`~autogen_agentchat.agents.UserProxyAgent`时,\n它会阻塞团队的执行,直到用户提供反馈或出错。\n这会阻碍团队的进展,并使团队处于无法保存或恢复的不稳定状态。\n```\n\n由于这种方法的阻塞特性,建议仅将其用于需要用户立即反馈的简短交互,\n例如通过点击按钮请求批准或拒绝,或者需要立即关注否则会导致任务失败的警报。\n\n以下是在{py:class}`~autogen_agentchat.teams.RoundRobinGroupChat`中\n使用{py:class}`~autogen_agentchat.agents.UserProxyAgent`进行诗歌生成任务的示例:\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "---------- user ----------\n", "Write a 4-line poem about the ocean.\n", "---------- assistant ----------\n", "In endless blue where whispers play, \n", "The ocean's waves dance night and day. \n", "A world of depths, both calm and wild, \n", "Nature's heart, forever beguiled. \n", "TERMINATE\n", "---------- user_proxy ----------\n", "APPROVE\n" ] }, { "data": { "text/plain": [ "TaskResult(messages=[TextMessage(source='user', models_usage=None, metadata={}, content='Write a 4-line poem about the ocean.', type='TextMessage'), TextMessage(source='assistant', models_usage=RequestUsage(prompt_tokens=46, completion_tokens=43), metadata={}, content=\"In endless blue where whispers play, \\nThe ocean's waves dance night and day. \\nA world of depths, both calm and wild, \\nNature's heart, forever beguiled. \\nTERMINATE\", type='TextMessage'), UserInputRequestedEvent(source='user_proxy', models_usage=None, metadata={}, request_id='2622a0aa-b776-4e54-9e8f-4ecbdf14b78d', content='', type='UserInputRequestedEvent'), TextMessage(source='user_proxy', models_usage=None, metadata={}, content='APPROVE', type='TextMessage')], stop_reason=\"Text 'APPROVE' mentioned\")" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from autogen_agentchat.agents import AssistantAgent, UserProxyAgent\n", "from autogen_agentchat.conditions import TextMentionTermination\n", "from autogen_agentchat.teams import RoundRobinGroupChat\n", "from autogen_agentchat.ui import Console\n", "from autogen_ext.models.openai import OpenAIChatCompletionClient\n", "\n", "# 创建智能体\n", "model_client = OpenAIChatCompletionClient(model=\"gpt-4o-mini\")\n", "assistant = AssistantAgent(\"assistant\", model_client=model_client)\n", "user_proxy = UserProxyAgent(\"user_proxy\", input_func=input) # 使用input()从控制台获取用户输入\n", "\n", "# 创建终止条件,当用户说\"APPROVE\"时结束对话\n", "termination = TextMentionTermination(\"APPROVE\")\n", "\n", "# 创建团队。\n", "team = RoundRobinGroupChat([assistant, user_proxy], termination_condition=termination)\n", "\n", "# 运行对话并将结果流式输出到控制台。\n", "stream = team.run_stream(task=\"Write a 4-line poem about the ocean.\")\n", "# 在脚本中运行时使用 asyncio.run(...)。\n", "await Console(stream)\n", "await model_client.close()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "从控制台输出可以看到,团队通过 `user_proxy` 向用户征求反馈\n来批准生成的诗作。\n\n您可以为 {py:class}`~autogen_agentchat.agents.UserProxyAgent` 提供自定义输入函数\n来定制反馈流程。\n例如,当团队作为网络服务运行时,可以使用自定义输入函数\n等待来自 WebSocket 连接的消息。\n以下代码片段展示了使用 [FastAPI](https://fastapi.tiangolo.com/) 框架时\n自定义输入函数的示例:\n\n```python\n@app.websocket(\"/ws/chat\")\nasync def chat(websocket: WebSocket):\n await websocket.accept()\n\n async def _user_input(prompt: str, cancellation_token: CancellationToken | None) -> str:\n data = await websocket.receive_json() # 从websocket等待用户消息\n message = TextMessage.model_validate(data) # 假设用户消息是TextMessage类型\n return message.content\n \n # 创建带有自定义输入函数的用户代理\n # 使用该用户代理运行团队\n # ...\n```\n\n完整示例请参考 [AgentChat FastAPI 示例](https://github.com/microsoft/autogen/blob/main/python/samples/agentchat_fastapi)。\n\n关于 {py:class}`~autogen_agentchat.agents.UserProxyAgent` 与 [ChainLit](https://github.com/Chainlit/chainlit) 的集成,\n请参阅 [AgentChat ChainLit 示例](https://github.com/microsoft/autogen/blob/main/python/samples/agentchat_chainlit)。\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 为下次运行提供反馈\n\n通常,应用程序或用户会与代理团队进行交互式循环:\n团队运行直至终止,\n应用程序或用户提供反馈,然后团队带着反馈再次运行。\n\n这种方法在持久会话中非常有用,\n可以实现团队与应用程序/用户之间的异步通信:\n当团队完成一次运行时,应用程序保存团队状态,\n将其放入持久存储,并在收到反馈时恢复团队运行。\n\n```{note}\n关于如何保存和加载团队状态,请参考[状态管理](./state.ipynb)。\n本节将重点介绍反馈机制。\n```\n\n下图展示了这种方法的控制流程:\n\n![human-in-the-loop-termination](./human-in-the-loop-termination.svg)\n\n有两种实现方式:\n\n- 设置最大轮次限制,使团队在指定轮次后总是停止\n- 使用终止条件如 {py:class}`~autogen_agentchat.conditions.TextMentionTermination` 和 {py:class}`~autogen_agentchat.conditions.HandoffTermination`,让团队根据内部状态决定何时停止并交回控制权\n\n您可以同时使用这两种方法来实现所需的行为。\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 使用最大轮次\n\n此方法允许通过设置最大轮次来暂停团队以等待用户输入。例如,您可以通过将 `max_turns` 设为 1 来配置团队在第一个代理响应后停止。这在需要持续用户交互的场景中特别有用,比如聊天机器人。\n\n要实现这一点,请在 {py:meth}`~autogen_agentchat.teams.RoundRobinGroupChat` 构造函数中设置 `max_turns` 参数。\n\n```python\nteam = RoundRobinGroupChat([...], max_turns=1)\n```\n\n当团队停止时,轮次计数将被重置。当您恢复团队时,它将从 0 重新开始。不过,团队的内部状态将被保留,例如 {py:class}`~autogen_agentchat.teams.RoundRobinGroupChat` 会从列表中的下一个代理继续,并保持相同的对话历史。\n\n```{note}\n`max_turn` 是团队类特有的功能,目前仅支持\n{py:class}`~autogen_agentchat.teams.RoundRobinGroupChat`、{py:class}`~autogen_agentchat.teams.SelectorGroupChat` 和 {py:class}`~autogen_agentchat.teams.Swarm`。\n当与终止条件一起使用时,团队会在任一条件满足时停止。\n```\n\n以下是一个在 {py:class}`~autogen_agentchat.teams.RoundRobinGroupChat` 中使用 `max_turns` 进行诗歌生成任务的示例,最大轮次设为 1:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "---------- user ----------\n", "Write a 4-line poem about the ocean.\n", "---------- assistant ----------\n", "Endless waves in a dance with the shore, \n", "Whispers of secrets in tales from the roar, \n", "Beneath the vast sky, where horizons blend, \n", "The ocean’s embrace is a timeless friend. \n", "TERMINATE\n", "[Prompt tokens: 46, Completion tokens: 48]\n", "---------- Summary ----------\n", "Number of messages: 2\n", "Finish reason: Maximum number of turns 1 reached.\n", "Total prompt tokens: 46\n", "Total completion tokens: 48\n", "Duration: 1.63 seconds\n", "---------- user ----------\n", "Can you make it about a person and its relationship with the ocean\n", "---------- assistant ----------\n", "She walks along the tide, where dreams intertwine, \n", "With every crashing wave, her heart feels aligned, \n", "In the ocean's embrace, her worries dissolve, \n", "A symphony of solace, where her spirit evolves. \n", "TERMINATE\n", "[Prompt tokens: 117, Completion tokens: 49]\n", "---------- Summary ----------\n", "Number of messages: 2\n", "Finish reason: Maximum number of turns 1 reached.\n", "Total prompt tokens: 117\n", "Total completion tokens: 49\n", "Duration: 1.21 seconds\n" ] } ], "source": [ "from autogen_agentchat.agents import AssistantAgent\n", "from autogen_agentchat.teams import RoundRobinGroupChat\n", "from autogen_agentchat.ui import Console\n", "from autogen_ext.models.openai import OpenAIChatCompletionClient\n", "\n", "# 创建代理\n", "model_client = OpenAIChatCompletionClient(model=\"gpt-4o-mini\")\n", "assistant = AssistantAgent(\"assistant\", model_client=model_client)\n", "\n", "# 创建团队并设置最大轮次为 1\n", "team = RoundRobinGroupChat([assistant], max_turns=1)\n", "\n", "task = \"Write a 4-line poem about the ocean.\"\n", "while True:\n", " # 运行对话并输出到控制台\n", " stream = team.run_stream(task=task)\n", " # 在脚本中使用 asyncio.run(...)\n", " await Console(stream)\n", " # 获取用户响应。\n", " task = input(\"Enter your feedback (type 'exit' to leave): \")\n", " if task.lower().strip() == \"exit\":\n", " break\n", "await model_client.close()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "你可以看到团队在一个代理响应后立即停止了。\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 使用终止条件\n\n在前面的章节中我们已经看到了几个终止条件的例子。\n本节我们将重点介绍 {py:class}`~autogen_agentchat.conditions.HandoffTermination`,\n当代理发送 {py:class}`~autogen_agentchat.messages.HandoffMessage` 消息时,该条件会停止团队运行。\n\n让我们创建一个包含单个 {py:class}`~autogen_agentchat.agents.AssistantAgent` 代理的团队,\n并设置交接功能,然后运行一个需要用户额外输入的任务,\n因为该代理没有相关工具来继续处理任务。\n\n```{note}\n与 {py:class}`~autogen_agentchat.agents.AssistantAgent` 一起使用的模型必须支持工具调用\n才能使用交接功能。\n```\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "---------- user ----------\n", "What is the weather in New York?\n", "---------- lazy_assistant ----------\n", "[FunctionCall(id='call_EAcMgrLGHdLw0e7iJGoMgxuu', arguments='{}', name='transfer_to_user')]\n", "[Prompt tokens: 69, Completion tokens: 12]\n", "---------- lazy_assistant ----------\n", "[FunctionExecutionResult(content='Transfer to user.', call_id='call_EAcMgrLGHdLw0e7iJGoMgxuu')]\n", "---------- lazy_assistant ----------\n", "Transfer to user.\n", "---------- Summary ----------\n", "Number of messages: 4\n", "Finish reason: Handoff to user from lazy_assistant detected.\n", "Total prompt tokens: 69\n", "Total completion tokens: 12\n", "Duration: 0.69 seconds\n" ] }, { "data": { "text/plain": [ "TaskResult(messages=[TextMessage(source='user', models_usage=None, content='What is the weather in New York?', type='TextMessage'), ToolCallRequestEvent(source='lazy_assistant', models_usage=RequestUsage(prompt_tokens=69, completion_tokens=12), content=[FunctionCall(id='call_EAcMgrLGHdLw0e7iJGoMgxuu', arguments='{}', name='transfer_to_user')], type='ToolCallRequestEvent'), ToolCallExecutionEvent(source='lazy_assistant', models_usage=None, content=[FunctionExecutionResult(content='Transfer to user.', call_id='call_EAcMgrLGHdLw0e7iJGoMgxuu')], type='ToolCallExecutionEvent'), HandoffMessage(source='lazy_assistant', models_usage=None, target='user', content='Transfer to user.', context=[], type='HandoffMessage')], stop_reason='Handoff to user from lazy_assistant detected.')" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from autogen_agentchat.agents import AssistantAgent\n", "from autogen_agentchat.base import Handoff\n", "from autogen_agentchat.conditions import HandoffTermination, TextMentionTermination\n", "from autogen_agentchat.teams import RoundRobinGroupChat\n", "from autogen_agentchat.ui import Console\n", "from autogen_ext.models.openai import OpenAIChatCompletionClient\n", "\n", "# 创建 OpenAI 模型客户端。\n", "model_client = OpenAIChatCompletionClient(\n", " model=\"gpt-4o\",\n", " # api_key=\"sk-...\", # 如果设置了 OPENAI_API_KEY 环境变量则为可选\n", ")\n", "\n", "# 创建一个懒惰的助手代理,总是将任务转交给用户。\n", "lazy_agent = AssistantAgent(\n", " \"lazy_assistant\",\n", " model_client=model_client,\n", " handoffs=[Handoff(target=\"user\", message=\"Transfer to user.\")],\n", " system_message=\"If you cannot complete the task, transfer to user. Otherwise, when finished, respond with 'TERMINATE'.\",\n", ")\n", "\n", "# 定义一个终止条件,用于检查转交消息。\n", "handoff_termination = HandoffTermination(target=\"user\")\n", "# 定义一个终止条件,用于检查特定文本提及。\n", "text_termination = TextMentionTermination(\"TERMINATE\")\n", "\n", "# 创建一个包含懒惰助手和两个终止条件的单代理团队。\n", "lazy_agent_team = RoundRobinGroupChat([lazy_agent], termination_condition=handoff_termination | text_termination)\n", "\n", "# 运行团队并将结果流式输出到控制台。\n", "task = \"What is the weather in New York?\"\n", "await Console(lazy_agent_team.run_stream(task=task), output_stats=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "你可以看到团队因检测到交接消息而停止运行。\n让我们通过提供代理所需的信息来继续团队工作。\n" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "---------- user ----------\n", "The weather in New York is sunny.\n", "---------- lazy_assistant ----------\n", "Great! Enjoy the sunny weather in New York! Is there anything else you'd like to know?\n", "---------- lazy_assistant ----------\n", "TERMINATE\n" ] }, { "data": { "text/plain": [ "TaskResult(messages=[TextMessage(source='user', models_usage=None, content='The weather in New York is sunny.', type='TextMessage'), TextMessage(source='lazy_assistant', models_usage=RequestUsage(prompt_tokens=110, completion_tokens=21), content=\"Great! Enjoy the sunny weather in New York! Is there anything else you'd like to know?\", type='TextMessage'), TextMessage(source='lazy_assistant', models_usage=RequestUsage(prompt_tokens=137, completion_tokens=5), content='TERMINATE', type='TextMessage')], stop_reason=\"Text 'TERMINATE' mentioned\")" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "await Console(lazy_agent_team.run_stream(task=\"The weather in New York is sunny.\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "你可以看到在用户提供信息后团队继续运行了。\n\n```{note}\n如果你使用的是针对用户的{py:class}`~autogen_agentchat.teams.Swarm`团队\n配合{py:class}`~autogen_agentchat.conditions.HandoffTermination`条件,\n要恢复团队运行,你需要将`task`设置为一个{py:class}`~autogen_agentchat.messages.HandoffMessage`\n并将`target`指向你想要运行的下一个代理。\n更多详情请参阅[Swarm](../swarm.ipynb)。\n```\n" ] } ], "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.9" } }, "nbformat": 4, "nbformat_minor": 2 }