From 73c4c0db88965cd707b778cd838e1f31df7bf0c7 Mon Sep 17 00:00:00 2001 From: Annyfee <2287551746@qq.com> Date: Thu, 18 Dec 2025 17:49:39 +0800 Subject: [PATCH] feat(module12): update code and environment dependencies --- .env.example | 5 +- config.py | 3 + m12_agents_sdk_swarm/README.md | 118 +++++++++++++++++++++++++++++++ m12_agents_sdk_swarm/__init__.py | 0 m12_agents_sdk_swarm/agent.py | 105 +++++++++++++++++++++++++++ m12_agents_sdk_swarm/main.py | 107 ++++++++++++++++++++++++++++ m12_agents_sdk_swarm/tools.py | 18 +++++ requirements.txt | 23 +++--- 8 files changed, 367 insertions(+), 12 deletions(-) create mode 100644 m12_agents_sdk_swarm/README.md create mode 100644 m12_agents_sdk_swarm/__init__.py create mode 100644 m12_agents_sdk_swarm/agent.py create mode 100644 m12_agents_sdk_swarm/main.py create mode 100644 m12_agents_sdk_swarm/tools.py diff --git a/.env.example b/.env.example index c3cb4ab..3ebe300 100644 --- a/.env.example +++ b/.env.example @@ -5,4 +5,7 @@ OPENAI_API_KEY=your_openai_api_key LANGCHAIN_API_KEY=your_langsmith_api_key # 高德地图相关配置 (用于MCP服务调试) —— 未到MCP模块无需修改 -AMAP_MAPS_API_KEY=your_gaode_api_key \ No newline at end of file +AMAP_MAPS_API_KEY=your_gaode_api_key + +# ChatGPT密钥(用于Agents SDK对接Smith追踪) —— 可选,非必须 +CHATGPT_API_KEY=your_chatgpt_api_key \ No newline at end of file diff --git a/config.py b/config.py index c3a1e9a..a758e27 100644 --- a/config.py +++ b/config.py @@ -16,6 +16,7 @@ except ImportError: OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") LANGCHAIN_API_KEY = os.getenv("LANGCHAIN_API_KEY") AMAP_MAPS_API_KEY = os.getenv("AMAP_MAPS_API_KEY") +CHATGPT_API_KEY = os.getenv("CHATGPT_API_KEY") if not OPENAI_API_KEY: raise ValueError("× 请在.env中设置OPENAI_API_KEY") @@ -23,3 +24,5 @@ if not LANGCHAIN_API_KEY: raise ValueError("× 请在.env中设置LANGCHAIN_API_KEY") if not AMAP_MAPS_API_KEY: raise ValueError("× 请在.env中设置AMAP_MAPS_API_KEY") +if not CHATGPT_API_KEY: + raise ValueError("× 请在.env中设置CHATGPT_API_KEY") diff --git a/m12_agents_sdk_swarm/README.md b/m12_agents_sdk_swarm/README.md new file mode 100644 index 0000000..18bf666 --- /dev/null +++ b/m12_agents_sdk_swarm/README.md @@ -0,0 +1,118 @@ +# 🧩 模块说明:Agents SDK Swarm - 构建智能客服多智能体系统 + +> 📌 核心知识点:多智能体协作|智能体转接机制|工具调用|MCP Server 集成|流式响应输出 + +--- + +### 1. 模块概述 + +本模块基于 Agents SDK Swarm 框架实现了一个航空公司智能客服系统,展示了多智能体协作、智能转接和工具调用的核心功能。 + +- ✅ 掌握点: + - 使用 Agents SDK 创建多个专业智能体(Agent) + - 实现智能体之间的转接网络(Handoff Network) + - 集成外部工具和 MCP Server(如高德地图服务) + - 实现流式响应和实时交互 + - 配置动态提示词和上下文共享 + +--- + +### 2. `tools.py` (业务工具与上下文) + +定义了系统使用的业务工具和全局上下文变量。 + +- ✅ 掌握点: + - 使用 `@function_tool` 装饰器定义业务工具 + - 实现工具函数的逻辑 + - 配置全局上下文变量,实现智能体间信息共享 + +- 工具列表: + - `execute_refund()`:执行退款逻辑 + - `check_seat()`:查询航班座位余量 + - `context_variables`:全局上下文,包含用户信息和航班信息 + +--- + +### 3. `agent.py` (智能体定义与配置) + +定义了系统中的所有智能体及其交互规则。 + +- ✅ 掌握点: + - 创建专业智能体(退票专员、改签专员、前台分诊员) + - 配置智能体的指令和工具集 + - 集成高德地图 MCP Server + - 建立智能体之间的转接网络 + - 使用动态提示词根据上下文生成指令 + +- 核心功能: + - `refund_agent`:退票专员,处理退票请求 + - `change_agent`:改签专员,处理改签请求 + - `triage_agent`:前台分诊员,负责用户意图识别和智能转接 + - 建立了完整的智能体转接网络,支持灵活的任务流转 + +--- + +### 4. `main.py` (系统运行入口) + +实现了智能客服系统的运行逻辑和用户交互界面。 + +- ✅ 掌握点: + - 配置系统 tracing 选项 + - 连接 MCP Server + - 实现流式响应处理 + - 管理对话历史和智能体状态 + - 处理各类事件(文本流、智能体切换、工具调用等) + +- 技术特点: + - 支持异步事件处理 + - 实时展示智能体切换和工具调用过程 + - 默认关闭 ChatGPT 的 Tracing 功能(可配置启用) + +--- + +### 🔔 配置与运行说明 + +1. **环境配置**: + - 在项目根目录的 `.env` 文件中配置高德地图 API Key(必填): + ``` + AMAP_MAPS_API_KEY=your_amap_api_key + ``` + - ChatGPT API Key 为选配(用于启用 Tracing): + ``` + OPENAI_API_KEY=your_openai_api_key + ``` + +2. **运行方式**: + ```bash + python -m m12_agents_sdk_swarm.main + ``` + +3. **使用示例**: + ``` + ✈️ 客服系统启动... + ✅ MCP Server (amap) connected. + + User: 我想退票 + 🤖 [TriageAgent] 我帮您转接到退票专员 + 🔀 [系统]: TriageAgent → RefundAgent + 🔧 [工具]: execute_refund + ✅ [结果]: ✅️ 退款申请已提交,预计3个工作日内到账。 + 🤖 [RefundAgent] 退款申请已提交,预计3个工作日内到账。您还需要其他帮助吗? + ``` + +--- + +### 💡 扩展建议 + +- 尝试添加更多专业智能体,如值机专员、行李查询专员等 +- 集成更多外部服务,如航班查询 API、支付系统等 +- 优化智能体的转接逻辑,添加更复杂的意图识别规则 + +--- + +### 📋 注意事项 + +- 确保已正确安装所有依赖(可通过项目根目录的 `requirements.txt` 安装) +- 高德地图 API Key 是系统正常运行的必要条件 +- 如遇到连接问题,检查网络设置和 API Key 的有效性 +- 系统默认关闭 Tracing 功能,如需启用请在 `main.py` 中取消注释相关代码并配置 API Key \ No newline at end of file diff --git a/m12_agents_sdk_swarm/__init__.py b/m12_agents_sdk_swarm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/m12_agents_sdk_swarm/agent.py b/m12_agents_sdk_swarm/agent.py new file mode 100644 index 0000000..33b87f9 --- /dev/null +++ b/m12_agents_sdk_swarm/agent.py @@ -0,0 +1,105 @@ +from agents.mcp import MCPServerStdio, MCPServerStdioParams +from openai import AsyncOpenAI +from agents import Agent, OpenAIChatCompletionsModel +from config import OPENAI_API_KEY +from m12_agents_sdk_swarm.tools import execute_refund, check_seat +from config import AMAP_MAPS_API_KEY + +client = AsyncOpenAI(api_key=OPENAI_API_KEY, base_url="https://api.deepseek.com") +model = OpenAIChatCompletionsModel(model="deepseek-chat", openai_client=client) + +# 创建 Server,传入 params +amap_server = [ + MCPServerStdio( + name="amap", + params=MCPServerStdioParams( + command="npx", + args=["-y", "@amap/amap-maps-mcp-server"], + env={"AMAP_MAPS_API_KEY": AMAP_MAPS_API_KEY} + ) + ) +] + +# === 1. 创建所有Agent === + +# 退票专员 +refund_agent = Agent( + name="RefundAgent", + instructions=lambda context, __: f"""你是退票专员。 + +【客户信息】 +- 姓名: {context.context.get('user_name')} +- 航班号: {context.context.get('flight_no')} + +【核心职责】 +1. 当用户表达退票意图时,立即调用 execute_refund 工具完成退票 +2. 不要问用户是否确认,直接执行 +3. 执行完工具后,告知用户退款状态 + +【转接规则】 +- 如果用户改变主意想改签,转接到 ChangeAgent +- 如果用户询问其他问题,转接到 TriageAgent +- 退票完成后,询问用户是否还需要其他帮助 + +【重要】你已经被转接过来,说明用户想退票,直接执行工具即可,不要犹豫! +""", + tools=[execute_refund], + model=model +) + +# 改签专员 +change_agent = Agent( + name="ChangeAgent", + instructions=lambda context, __: f"""你是改签专员。 + +【客户信息】 +- 姓名: {context.context.get('user_name')} +- 航班号: {context.context.get('flight_no')} + +【核心职责】 +1. 当用户表达改签意图时,立即调用 check_seat 工具查询座位 +2. 不要问用户是否确认,直接执行 +3. 执行完工具后,告知用户结果 + +【转接规则】 +- 如果用户改变主意想退票,转接到 RefundAgent +- 如果用户询问其他问题,转接到 TriageAgent +- 改签完成后,询问用户是否还需要其他帮助 + +【重要】你已经被转接过来,说明用户想改签,直接执行工具即可! +""", + tools=[check_seat], + model=model, +) + +# 前台分诊员(动态提示词) +triage_agent = Agent( + name="TriageAgent", + instructions=lambda context, __: f"""你是航空公司前台客服。 + +【客户信息】 +- 姓名: {context.context.get('user_name')} +- 航班号: {context.context.get('flight_no')} + +【转接规则】 +1. 用户说"退票"、"退款"、"取消航班" → 转接到 RefundAgent +2. 用户说"改签"、"换航班"、"改时间" → 转接到 ChangeAgent +3. 用户想查询某个附近地方的酒店、景点信息时 → 调用 amap_server +4. 其他问题由你自己回答 + +【重要】 +- 转接时要明确告知用户:"我帮您转接到XX专员" +- 不要重复询问用户意图,识别后立即转接 +""", + mcp_servers=amap_server, + model=model, +) + +# === 2. 建立 Handoff 网络 === + +# 前台可转给两位专员 +triage_agent.handoffs = [refund_agent, change_agent] + +# 专员之间也可互相转接,并能回退到前台 +refund_agent.handoffs = [change_agent, triage_agent] +change_agent.handoffs = [refund_agent, triage_agent] diff --git a/m12_agents_sdk_swarm/main.py b/m12_agents_sdk_swarm/main.py new file mode 100644 index 0000000..2f8345b --- /dev/null +++ b/m12_agents_sdk_swarm/main.py @@ -0,0 +1,107 @@ +# 问题 +# 1. 命名不能中文 +# 2. chatgpt默认启用smith,要关闭它 +# 3. 如果不想关闭,需要配置一个api_key +# 4. 哪怕配置好,chatgpt与这边连接延迟较高,tracing追踪也不稳定,而且之前我们已经讲了一个langsmith,所以这里不用它其实更好。 + + +import asyncio +from openai.types.responses import ResponseTextDeltaEvent +from agents import Runner +from m12_agents_sdk_swarm.agent import triage_agent,amap_server +from m12_agents_sdk_swarm.tools import context_variables + +# 不启用Chatgpt官方提供的Tracing(无需配置api_key) +from agents import set_tracing_disabled +set_tracing_disabled(True) + +# 启用Chatgpt官方提供的Tracing(需要配置api_key) +# from config import CHATGPT_API_KEY +# from agents import set_tracing_export_api_key +# set_tracing_export_api_key(CHATGPT_API_KEY) + + +async def main(): + print('✈️ 客服系统启动...\n') + messages = [] # 对话历史 + cur_agent = triage_agent # 当前Agent + + try: + await amap_server[0].connect() + print("✅ MCP Server (amap) connected.") + except Exception as e: + print(f"⚠️ Failed to connect MCP Server: {e}") + + while True: + # 获取用户输入 + user_input = input("\nUser: ") + if user_input == "quit": + break + messages.append({"role": "user", "content": user_input}) + + # 启动流式响应 + result = Runner.run_streamed(cur_agent, input=messages, context=context_variables) + + # 状态变量 + current_agent_name = None # 当前Agent名称 + is_printing = False # 是否正在打印文本 + + # 处理事件流 + async for event in result.stream_events(): + + # 事件1: 文本流(逐Token输出) + if event.type == "raw_response_event": + if isinstance(event.data, ResponseTextDeltaEvent): + # 第一次打印时显示Agent标签 + if not is_printing: + agent_label = f"[{current_agent_name}]" if current_agent_name else "" + print(f"🤖 {agent_label} ", end="", flush=True) + is_printing = True + # 逐字输出 + print(event.data.delta, end="", flush=True) + + # 事件2: Agent切换 + elif event.type == "agent_updated_stream_event": + new_agent = event.new_agent.name + if current_agent_name is None: + # 第一次设置Agent名称 + current_agent_name = new_agent + else: + # Agent发生切换 + if is_printing: + print() # 先换行 + is_printing = False + print(f"🔀 [系统]: {current_agent_name} → {new_agent}") + current_agent_name = new_agent + + # 事件3: 工具调用 + elif event.type == "run_item_stream_event": + # 工具调用开始 + if event.name == "tool_called": + if is_printing: + print() # 先换行 + is_printing = False + tool_name = event.item.raw_item.name + tool_args = event.item.raw_item.arguments # 获取工具参数 调试需要时可加入 + # 区分转接工具和业务工具 + if tool_name.startswith("transfer_"): + print(f"📞 [转接]: {tool_name}") + else: + print(f"🔧 [工具]: {tool_name}") + + # 工具输出结果 + elif event.name == "tool_output": + print(f"✅ [结果]: {event.item.output}") + is_printing = False + + # 结束本轮,确保换行 + if is_printing: + print() + + # 更新状态 + messages = result.to_input_list() # 获取完整对话历史 + cur_agent = result.last_agent # 获取最后激活的Agent + + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/m12_agents_sdk_swarm/tools.py b/m12_agents_sdk_swarm/tools.py new file mode 100644 index 0000000..86571ab --- /dev/null +++ b/m12_agents_sdk_swarm/tools.py @@ -0,0 +1,18 @@ +from agents import function_tool + +# 全局上下文,所有 Agent 共享 +context_variables = { + "user_name":"张三(白金会员)", + "flight_no":"CA1234" +} + +# 业务工具 +@function_tool +def execute_refund(): + """执行退款逻辑""" + return "✅️ 退款申请已提交,预计3个工作日内到账。" + +@function_tool +def check_seat(): + """查询座位余量""" + return "✅ 明日航班尚有余票。" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index c913197..2b17c72 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,19 +1,20 @@ +agents==1.4.0 httpx==0.28.1 huggingface_hub==1.2.1 -langchain==1.1.3 -langchain_chroma==1.0.0 +langchain==1.2.0 +langchain_chroma==1.1.0 langchain_classic==1.0.0 langchain_community==0.4.1 -langchain_core==1.1.3 -langchain_huggingface==1.1.0 +langchain_core==1.2.2 +langchain_huggingface==1.2.0 langchain_mcp_adapters==0.2.1 -langchain_openai==1.1.1 -langchain_text_splitters==1.0.0 -langgraph==1.0.4 -langsmith==0.4.58 -mcp==1.23.3 -openai==2.9.0 +langchain_openai==1.1.5 +langchain_text_splitters==1.1.0 +langgraph==1.0.5 +langsmith==0.5.0 +mcp==1.24.0 +openai==2.13.0 pydantic==2.12.5 python-dotenv==1.2.1 Requests==2.32.5 -setuptools==80.9.0 \ No newline at end of file +setuptools==80.9.0