From 456fc42785eb3acac215e767b711a13971bd6a06 Mon Sep 17 00:00:00 2001 From: Annyfee <2287551746@qq.com> Date: Sun, 4 Jan 2026 17:08:01 +0800 Subject: [PATCH] feat(module13): update code and environment dependencies --- m13_streamlit/__init__.py | 0 m13_streamlit/s01_st_basics.py | 59 ++++++++ m13_streamlit/s02_st_layout_demo.py | 25 ++++ m13_streamlit/s03_agent_single_mvp.py | 75 ++++++++++ m13_streamlit/s04_agent_multi_session.py | 167 +++++++++++++++++++++++ 5 files changed, 326 insertions(+) create mode 100644 m13_streamlit/__init__.py create mode 100644 m13_streamlit/s01_st_basics.py create mode 100644 m13_streamlit/s02_st_layout_demo.py create mode 100644 m13_streamlit/s03_agent_single_mvp.py create mode 100644 m13_streamlit/s04_agent_multi_session.py diff --git a/m13_streamlit/__init__.py b/m13_streamlit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/m13_streamlit/s01_st_basics.py b/m13_streamlit/s01_st_basics.py new file mode 100644 index 0000000..a7cd726 --- /dev/null +++ b/m13_streamlit/s01_st_basics.py @@ -0,0 +1,59 @@ +import streamlit as st + +# === 输入框 === st.chat_input +prompt = st.chat_input("请输入问题") +if prompt: + # 处理用户输入 + st.write(f"你输入了:{prompt}") + + +# === 气泡 === st.chat_messages:自动生成对话气泡 +# 用户气泡 +with st.chat_message("user", avatar="👤"): + st.write("你是人吗") + +# 助手气泡 +with st.chat_message("assistant", avatar="🤖"): + st.write("似乎不太像是人") + + +# === 折叠状态栏 === st.status:收纳中间步骤 +with st.status("Agent 正在思考...",expanded=True): # expanded:默认是否打开折叠栏 + st.write("正在查询数据库...") + st.write("正在转接专员...") + # 最终更新状态 + st.success("处理完成") + + +# === 动态占位符 === st.empty():先占位,后续更新 +import time +response = st.empty() +for msg in ["处理中...", "完成!"]: + response.markdown(msg) + time.sleep(1) + + +# === 记忆中枢 === st.session_state:状态持久化 +# 初始化 +if "messages" not in st.session_state: + st.session_state.messages = [] + +# 读写 +st.session_state.messages.append({"role":"user","content":prompt}) + + +# === 侧边栏 === st.sidebar +with st.sidebar: + st.write('我是侧边栏') + st.json({'User':"张三"}) + +# === 状态同步器 === st.rerun():让UI与最新状态同步 +st.session_state.cur_agent = "Refund" +# 注意:在聊天机器人等交互场景中,通常这样写(安全): +# if some_condition: # 只有在用户提交新消息后 +# st.session_state.cur_agent = "RefundAgent" +# st.rerun() # 手动刷新,显示最新状态 + +# 如果像上面这样无条件调用 st.rerun(),会造成无限循环刷新, +# 所以这里注释掉,仅用于静态演示。 +# st.rerun() \ No newline at end of file diff --git a/m13_streamlit/s02_st_layout_demo.py b/m13_streamlit/s02_st_layout_demo.py new file mode 100644 index 0000000..3b8f261 --- /dev/null +++ b/m13_streamlit/s02_st_layout_demo.py @@ -0,0 +1,25 @@ +import streamlit as st + +st.set_page_config(page_title="智能客服驾驶舱",layout="wide") # 标签页的命名 +st.title("✈️ 智能航天客服 Swarm") # 标题 + +# 侧边栏 +with st.sidebar: + st.header("📦 驾驶舱监控") # 侧边栏标题 + st.info("当前坐席:前台 TriageAgent") # 侧边栏高亮信息 + st.subheader("用户画像") # 侧边栏副标题 + st.json({"name":"张三","vip":True}) + +# 画聊天历史(模拟) +with st.chat_message("user",avatar="👤"): # 用一个小表情代表发言人头像 + st.write("我要退票") + +with st.chat_message("assistant",avatar="🤖"): + st.write("好的,为您转接退票专员...") + +# 画输入框 +prompt = st.chat_input("请输入您的问题") +if prompt: + # 当用户输入后,页面会刷新,显示下面的内容 + with st.chat_message("user",avatar="👤"): + st.write(prompt) \ No newline at end of file diff --git a/m13_streamlit/s03_agent_single_mvp.py b/m13_streamlit/s03_agent_single_mvp.py new file mode 100644 index 0000000..5992289 --- /dev/null +++ b/m13_streamlit/s03_agent_single_mvp.py @@ -0,0 +1,75 @@ +import sys +import os + +# 添加项目根目录到 Python 路径 +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +import streamlit as st +from agents import Runner, set_tracing_disabled +from agents.agent import Agent +from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel +from openai import AsyncOpenAI +from config import OPENAI_API_KEY + +set_tracing_disabled(True) + +# 初始化模型 +client = AsyncOpenAI(api_key=OPENAI_API_KEY,base_url="https://api.deepseek.com") +model = OpenAIChatCompletionsModel(model="deepseek-chat",openai_client=client) + +# 定义一个通用Agent +smart_agent = Agent( + name="SmartAssistant", + instructions=""" + 你是航空公司智能客服助手,客户姓名:张三(白金会员),航班号:CA1234。 + + 你具备以下能力: + - 用户说“退票”“退款”“取消” → 回复:✅ 退款申请已提交,预计3个工作日内原路返回。 + - 用户说“改签”“换航班” → 回复:✅ 明日同航线航班尚有余座,已为您预留,可随时确认改签。 + - 其他任何问题 → 礼貌、专业地直接回答 + + 回复要简洁、自然、带表情符号,让用户感到温暖。 + """, + model=model +) + +# Streamlit UI组件 +st.set_page_config(page_title="智能客服驾驶舱",layout="wide") # 标签页命名 +st.title("✈️ 智能航天客服 Swarm") # 页面标题 + +# 侧边栏:驾驶舱监控(先固定不变动) +with st.sidebar: + st.header("🖥️ 驾驶舱监控") + st.success("当前坐席: 智能助理 SmartAssistant 🤖") # success用作高亮块显示 + st.subheader("用户画像") + st.json({"user_name": "张三(白金会员)", "flight_no": "CA1234"}) + +# 会话状态与历史消息 +if "messages" not in st.session_state: # 首次运行时,初始化空列表,用于存储聊天消息。 + st.session_state["messages"] = [] + +for msg in st.session_state["messages"]: # 重新渲染历史消息,确保聊天上下文在页面刷新后依然可见 + avatar = "👤" if msg['role'] == 'user' else "🤖" + with st.chat_message(msg['role'],avatar=avatar): + st.write(msg["content"]) + +# 用户输入与核心交互 +prompt = st.chat_input("请输入您的问题(试试:我要退票 / 想改签 / 你好)") +if prompt: + # 显示用户信息 + st.session_state.messages.append({"role":"user","content":prompt}) + with st.chat_message("user",avatar="👤"): + st.write(prompt) + + # 调用Agents SDK + with st.spinner("思考中..."): # 执行耗时操作时,显示旋转的加载动画 + result = Runner.run_sync( # 同步运行智能体 + smart_agent, # 参数:agent + st.session_state.messages # 参数:对话历史 + ) + + # 显示AI回复 + reply = result.final_output # 最终AI回复内容 + st.session_state.messages.append({"role":"assistant","content":reply}) + with st.chat_message("assistant",avatar="🤖"): + st.write(reply) \ No newline at end of file diff --git a/m13_streamlit/s04_agent_multi_session.py b/m13_streamlit/s04_agent_multi_session.py new file mode 100644 index 0000000..06367ad --- /dev/null +++ b/m13_streamlit/s04_agent_multi_session.py @@ -0,0 +1,167 @@ +import sys +import os +import asyncio +import nest_asyncio +import uuid +from agents import Runner, set_tracing_disabled, SQLiteSession +from openai.types.responses import ResponseTextDeltaEvent + +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +import streamlit as st + +from m12_agents_sdk_swarm.s01_tools import context_variables +from m12_agents_sdk_swarm.s02_agent import triage_agent, amap_server + +nest_asyncio.apply() +set_tracing_disabled(True) + +# 生成/获取动态 Session ID +if "session_id" not in st.session_state: + st.session_state.session_id = f"session_{uuid.uuid4().hex[:8]}" + + +async def init_mcp(): + try: + await amap_server[0].connect() + return "✅ 高德地图(按需连接)" + except Exception as e: + return f"⚠️ MCP 连接失败" + + +mcp_status = asyncio.run(init_mcp()) + +st.set_page_config(page_title="智能客服驾驶舱", layout="wide") +st.title("✈️ 智能航空客服 Multi-Agent 系统") +st.caption(f"🚀 实战三:基于 Agents SDK 的多智能体协作 (ID: {st.session_state.session_id})") # 小字体显示文本 + +# 初始化持久化 Session +if "session" not in st.session_state: + st.session_state.session = SQLiteSession( # 持久化内存,将AI对话历史搬到数据库中 + session_id=st.session_state.session_id, + db_path="./m13_streamlit/conversations.db" + ) + +if "display_messages" not in st.session_state: # 存储对话记录 + st.session_state.display_messages = [] + +if "tool_logs_history" not in st.session_state: # 存储工具调用记录 + st.session_state.tool_logs_history = [] + + +# 封装侧边栏渲染函数,以便在初次加载和转接发生时,都能向同一个占位符刷新内容 +def render_agent_status(placeholder, agent): + name = agent.name + if name == "TriageAgent": + placeholder.info("当前坐席: 前台 TriageAgent") + elif name == "RefundAgent": + placeholder.success("当前坐席: 退票专员 RefundAgent 🚨") + elif name == "ChangeAgent": + placeholder.warning("当前坐席: 改签专员 ChangeAgent 🔄") + + +with st.sidebar: + st.header("🖥️ 驾驶舱监控") + + # 确保有大脑(session_state)记住当前 Agent + if "current_agent" not in st.session_state: + st.session_state.current_agent = triage_agent + + # 当代码执行到对应位置时,自动更新上方侧边栏内容 + agent_status_placeholder = st.empty() + + # 初始渲染:基于当前state里的Agent + render_agent_status(agent_status_placeholder, st.session_state.current_agent) + + st.subheader("👤 用户画像") + st.json(context_variables) + + st.subheader("🔌 MCP 状态") + st.caption(mcp_status) + + st.subheader("📊 会话统计") + st.metric("消息数", len(st.session_state.display_messages)) + + if st.button("🗑️ 清空对话"): + asyncio.run(st.session_state.session.clear_session()) # 调用SQLiteSession对象的clear_session的方法,删除所有session_id等于当前ID的记录 + st.session_state.session_id = f"session_{uuid.uuid4().hex[:8]}" # 生成新的session_id + st.session_state.display_messages = [] # 重置UI缓存 + st.session_state.tool_logs_history = [] + st.session_state.current_agent = triage_agent + st.rerun() + +# 渲染历史消息 +for i, msg in enumerate(st.session_state.display_messages): + avatar = "👤" if msg["role"] == "user" else "🤖" + with st.chat_message(msg["role"], avatar=avatar): + st.write(msg["content"]) + if i < len(st.session_state.tool_logs_history) and st.session_state.tool_logs_history[i]: # 确保列表索引存在且实际工具调用有记录 + with st.expander("🔧 查看工具调用", expanded=False): # 默认折叠 + for log in st.session_state.tool_logs_history[i]: + st.caption(log) + +prompt = st.chat_input("请输入您的问题") + +if prompt: + st.session_state.display_messages.append({"role": "user", "content": prompt}) + st.session_state.tool_logs_history.append([]) # 提前占座,保证索引顺序 + + with st.chat_message("user", avatar="👤"): # 直接写入气泡 + st.write(prompt) + + with st.chat_message("assistant", avatar="🤖"): + message_placeholder = st.empty() # 先占位,AI思考后再填入 + + with st.status("Agent 正在思考...", expanded=True) as status: + async def process_stream(): + stream = Runner.run_streamed( + st.session_state.current_agent, + input=prompt, # 问题 + context=context_variables, # 全局上下文 + session=st.session_state.session # 读:从数据库提取聊天记录/写:回复结束,自动将新一轮对话存入数据库 + ) + + # 收集局部变量,再一起叠加到全局变量中 + reply = "" # 回复内容累加器 + tool_logs = [] # 工具日志收集 + + current_agent_name = st.session_state.current_agent.name + + async for event in stream.stream_events(): # 流式事件 + if event.type == "raw_response_event": + if isinstance(event.data, ResponseTextDeltaEvent): + delta = event.data.delta or "" # 提取新字符 + reply += delta + message_placeholder.write(reply) # 实时刷新UI + + # 当agent转换时 + elif event.type == "agent_updated_stream_event": + new_agent = event.new_agent + if current_agent_name != new_agent.name: + log_msg = f"🔀 转接: {current_agent_name} → {new_agent.name}" + status.write(log_msg) # 写入折叠栏 + tool_logs.append(log_msg) + + # 1. 更新“大脑”(状态持久化) + st.session_state.current_agent = new_agent + # 2. 实时更新“脸面”(让侧边栏占位符立刻变色/变字) + render_agent_status(agent_status_placeholder, new_agent) + + current_agent_name = new_agent.name + + elif event.type == "run_item_stream_event": + if event.name == "tool_called": + tool_name = event.item.raw_item.name # 提取被调用工具的名字 + log_msg = f"🔧 调用: {tool_name}" + status.write(log_msg) # 写入折叠栏 + tool_logs.append(log_msg) + + return reply, tool_logs # 返回完整回复的字符串与收集到的动作日志 + + + reply, tool_logs = asyncio.run(process_stream()) + status.update(label="✅ 处理完成", state="complete", expanded=False) # 标识处理成功 + + # 将(本轮)对话记录/工具调用,分别存入(总)对话记录/工具调用 + st.session_state.display_messages.append({"role": "assistant", "content": reply}) + st.session_state.tool_logs_history.append(tool_logs) \ No newline at end of file