Files
autogen/frontend/streamlit_app_v3.py
2026-03-12 13:27:03 +08:00

653 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""
Streamlit 前端 v3 - Agent 对话流可视化版本
核心特性:
1. 清晰展示每个 Agent 当前在做什么(任务标签)
2. Agent 之间的对话流(类似聊天室)
3. 实时活动指示器(高亮当前发言的 Agent
4. 对话历史完整记录
"""
import streamlit as st
import os
import sys
from pathlib import Path
from datetime import datetime
import time
from typing import Dict, Any, List
import json
# 添加项目根目录到路径
sys.path.insert(0, str(Path(__file__).parent.parent))
try:
from autogen import AssistantAgent, UserProxyAgent, GroupChat, GroupChatManager
AUTOGEN_AVAILABLE = True
except ImportError:
AUTOGEN_AVAILABLE = False
from config.llm_config import (
get_llm_config,
PM_PROMPT,
QA_PROMPT,
DEV_PROMPT,
ORCH_PROMPT
)
# 页面配置
st.set_page_config(
page_title="AutoGen SDLC - Agent 对话流",
page_icon="💬",
layout="wide",
initial_sidebar_state="expanded"
)
# 自定义 CSS 样式 - 突出对话流
st.markdown("""
<style>
/* 主对话区域 */
.chat-flow-container {
background: #f0f2f6;
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
}
/* Agent 对话气泡 */
.agent-chat-bubble {
padding: 15px 20px;
margin: 10px 0;
border-radius: 15px;
max-width: 85%;
position: relative;
animation: bubble-in 0.3s ease;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
@keyframes bubble-in {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 不同 Agent 的气泡样式 */
.pm-bubble {
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
border: 2px solid #2196f3;
margin-left: 0;
}
.qa-bubble {
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
border: 2px solid #4caf50;
margin-left: 0;
}
.dev-bubble {
background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%);
border: 2px solid #ff9800;
margin-left: 0;
}
.orchestrator-bubble {
background: linear-gradient(135deg, #f3e5f5 0%, #e1bee7 100%);
border: 2px solid #9c27b0;
margin-left: 0;
}
.user-bubble {
background: linear-gradient(135deg, #efebe9 0%, #d7ccc8 100%);
border: 2px solid #795548;
margin-left: auto;
}
/* Agent 头像和名称 */
.agent-chat-header {
display: flex;
align-items: center;
margin-bottom: 8px;
font-weight: bold;
}
.agent-chat-avatar {
font-size: 1.5rem;
margin-right: 8px;
}
.agent-chat-name {
font-size: 1rem;
color: #333;
}
.agent-chat-time {
margin-left: auto;
font-size: 0.75rem;
color: #666;
}
/* 任务标签 */
.task-badge {
display: inline-block;
padding: 3px 10px;
border-radius: 12px;
font-size: 0.75rem;
font-weight: bold;
margin-left: 8px;
color: white;
}
.pm-task { background: #2196f3; }
.qa-task { background: #4caf50; }
.dev-task { background: #ff9800; }
.orchestrator-task { background: #9c27b0; }
.user-task { background: #795548; }
/* Agent 状态卡片 */
.agent-status-card {
padding: 15px;
border-radius: 10px;
text-align: center;
border: 3px solid #e0e0e0;
transition: all 0.3s ease;
background: white;
}
.agent-status-card.active {
border-color: #4caf50;
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
animation: agent-pulse 1.5s infinite;
}
@keyframes agent-pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.02); }
}
.agent-status-avatar {
font-size: 2.5rem;
margin-bottom: 5px;
}
.agent-status-name {
font-weight: bold;
font-size: 0.9rem;
margin-bottom: 5px;
}
.agent-status-task {
font-size: 0.75rem;
color: #666;
min-height: 20px;
}
.agent-status-indicator {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 5px;
}
.status-active {
background: #4caf50;
animation: dot-pulse 1s infinite;
}
.status-waiting {
background: #ffc107;
}
.status-inactive {
background: #e0e0e0;
}
@keyframes dot-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
/* 对话流指示器 */
.chat-flow-indicator {
display: flex;
align-items: center;
margin: 10px 0;
padding: 10px;
background: white;
border-radius: 8px;
}
.flow-arrow {
font-size: 1.5rem;
color: #2196f3;
margin: 0 10px;
}
/* 当前活动提示 */
.active-agent-banner {
background: linear-gradient(135deg, #4caf50, #8bc34a);
color: white;
padding: 10px 20px;
border-radius: 8px;
text-align: center;
font-weight: bold;
margin-bottom: 20px;
animation: banner-pulse 2s infinite;
}
@keyframes banner-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.8; }
}
</style>
""", unsafe_allow_html=True)
# Agent 配置
AGENT_CONFIG = {
"PM_Agent": {
"name": "产品经理",
"avatar": "📋",
"color": "#2196f3",
"bubble_class": "pm-bubble",
"task_class": "pm-task",
"default_task": "待命"
},
"QA_Agent": {
"name": "测试工程师",
"avatar": "",
"color": "#4caf50",
"bubble_class": "qa-bubble",
"task_class": "qa-task",
"default_task": "待命"
},
"Dev_Agent": {
"name": "开发工程师",
"avatar": "💻",
"color": "#ff9800",
"bubble_class": "dev-bubble",
"task_class": "dev-task",
"default_task": "待命"
},
"Orchestrator": {
"name": "协调器",
"avatar": "🎯",
"color": "#9c27b0",
"bubble_class": "orchestrator-bubble",
"task_class": "orchestrator-task",
"default_task": "待命"
},
"User_Proxy": {
"name": "用户代理",
"avatar": "👤",
"color": "#795548",
"bubble_class": "user-bubble",
"task_class": "user-task",
"default_task": "待命"
}
}
def init_session_state():
"""初始化 session state"""
if "messages" not in st.session_state:
st.session_state.messages = []
if "is_running" not in st.session_state:
st.session_state.is_running = False
if "current_agent" not in st.session_state:
st.session_state.current_agent = None
if "agent_tasks" not in st.session_state:
st.session_state.agent_tasks = {
"PM_Agent": "待命",
"QA_Agent": "待命",
"Dev_Agent": "待命",
"Orchestrator": "待命",
"User_Proxy": "待命"
}
if "agent_stats" not in st.session_state:
st.session_state.agent_stats = {agent: 0 for agent in AGENT_CONFIG}
def display_agent_status_row():
"""显示 Agent 状态行 - 突出当前活动的 Agent"""
st.subheader("🎭 Agent 实时状态")
cols = st.columns(5)
for i, (agent_key, config) in enumerate(AGENT_CONFIG.items()):
with cols[i]:
is_active = st.session_state.current_agent == agent_key
current_task = st.session_state.agent_tasks.get(agent_key, "待命")
msg_count = st.session_state.agent_stats.get(agent_key, 0)
status_class = "active" if is_active else ""
indicator_class = "status-active" if is_active else "status-inactive"
st.markdown(f"""
<div class="agent-status-card {status_class}">
<div class="agent-status-avatar">{config['avatar']}</div>
<div class="agent-status-name">
<span class="agent-status-indicator {indicator_class}"></span>
{config['name']}
</div>
<div class="agent-status-task">
📝 {current_task}
</div>
<div style="font-size:0.7rem;color:#999;margin-top:5px;">
💬 {msg_count} 条消息
</div>
</div>
""", unsafe_allow_html=True)
def display_active_agent_banner():
"""显示当前活动 Agent 的横幅"""
if st.session_state.current_agent:
config = AGENT_CONFIG.get(st.session_state.current_agent, {})
st.markdown(f"""
<div class="active-agent-banner">
{config.get('avatar', '🤖')} 当前发言:{config.get('name', 'Unknown')} - {st.session_state.agent_tasks.get(st.session_state.current_agent, '工作中...')}
</div>
""", unsafe_allow_html=True)
def display_chat_flow():
"""显示 Agent 对话流 - 核心功能"""
st.subheader("💬 Agent 对话流")
if not st.session_state.messages:
st.info("👈 暂无对话,请启动工作流")
return
# 使用容器显示对话流
chat_container = st.container()
for idx, msg in enumerate(st.session_state.messages):
agent_key = msg.get("agent_key", "Unknown")
config = AGENT_CONFIG.get(agent_key, {"name": "未知", "avatar": "🤖", "bubble_class": ""})
task_class = AGENT_CONFIG.get(agent_key, {}).get("task_class", "")
timestamp = msg.get("timestamp", "")
if timestamp:
time_str = datetime.fromisoformat(timestamp).strftime("%H:%M:%S")
else:
time_str = datetime.now().strftime("%H:%M:%S")
content = msg.get("content", "")
task = msg.get("task", "工作中")
# 显示对话气泡
st.markdown(f"""
<div class="agent-chat-bubble {config['bubble_class']}">
<div class="agent-chat-header">
<span class="agent-chat-avatar">{config['avatar']}</span>
<span class="agent-chat-name">{config['name']}</span>
<span class="task-badge {task_class}">{task}</span>
<span class="agent-chat-time">{time_str}</span>
</div>
<div style="color:#333;line-height:1.6;">{content}</div>
</div>
""", unsafe_allow_html=True)
# 显示对话流指示器(下一条消息的箭头)
if idx < len(st.session_state.messages) - 1:
next_msg = st.session_state.messages[idx + 1]
next_agent = next_msg.get("agent_key", "")
next_config = AGENT_CONFIG.get(next_agent, {})
if next_agent != agent_key:
st.markdown(f"""
<div class="chat-flow-indicator">
<div style="flex:1;"></div>
<div class="flow-arrow">⬇️</div>
<div style="flex:1;text-align:center;color:#666;font-size:0.8rem;">
传递给 {next_config.get('name', 'Unknown')}
</div>
</div>
""", unsafe_allow_html=True)
def create_agents(api_key: str, base_url: str, model: str):
"""创建所有 Agent"""
llm_config = get_llm_config(model=model, api_key=api_key, base_url=base_url)
pm_agent = AssistantAgent(
name="PM_Agent",
system_message=PM_PROMPT,
llm_config=llm_config,
description="资深软件产品经理",
human_input_mode="NEVER"
)
qa_agent = AssistantAgent(
name="QA_Agent",
system_message=QA_PROMPT,
llm_config=llm_config,
description="资深测试工程师",
human_input_mode="NEVER"
)
dev_agent = AssistantAgent(
name="Dev_Agent",
system_message=DEV_PROMPT,
llm_config=llm_config,
description="资深软件工程师",
human_input_mode="NEVER"
)
orchestrator = AssistantAgent(
name="Orchestrator",
system_message=ORCH_PROMPT,
llm_config=llm_config,
description="多智能体协调器",
human_input_mode="NEVER"
)
user_proxy = UserProxyAgent(
name="User_Proxy",
human_input_mode="NEVER",
max_consecutive_auto_reply=0,
code_execution_config={
"work_dir": "workspace",
"use_docker": False,
},
is_termination_msg=lambda x: x.get("content", "").rstrip().endswith("TERMINATE")
)
return pm_agent, qa_agent, dev_agent, orchestrator, user_proxy, llm_config
def add_message(agent_key: str, content: str, task: str = "工作中"):
"""添加消息到对话流"""
msg = {
"agent_key": agent_key,
"content": content,
"timestamp": datetime.now().isoformat(),
"task": task
}
st.session_state.messages.append(msg)
# 更新统计
st.session_state.agent_stats[agent_key] = st.session_state.agent_stats.get(agent_key, 0) + 1
# 更新当前 Agent
st.session_state.current_agent = agent_key
# 更新任务
st.session_state.agent_tasks[agent_key] = task
def main():
"""主应用"""
init_session_state()
# 标题
st.title("💬 AutoGen SDLC - Agent 对话流可视化")
st.markdown("**清晰展示每个 Agent 在做什么 · 实时追踪 Agent 之间的对话交互**")
# 侧边栏配置
with st.sidebar:
st.title("⚙️ 控制中心")
api_key = st.text_input("API Key", type="password", value=os.getenv("DASHSCOPE_API_KEY", ""))
base_url = st.text_input("Base URL", value="https://dashscope.aliyuncs.com/compatible-mode/v1")
model = st.selectbox("模型选择", ["qwen3.5-flash", "qwen-max", "qwen-plus", "qwen-turbo"], index=0)
max_round = st.slider("最大对话轮数", 5, 50, 20)
st.divider()
col1, col2 = st.columns(2)
with col1:
start_btn = st.button("▶️ 启动对话流", type="primary", use_container_width=True)
with col2:
stop_btn = st.button("⏸️ 暂停", use_container_width=True)
st.divider()
if st.button("🗑️ 清空对话", use_container_width=True):
st.session_state.messages = []
st.session_state.current_agent = None
st.session_state.agent_tasks = {k: "待命" for k in AGENT_CONFIG}
st.session_state.agent_stats = {k: 0 for k in AGENT_CONFIG}
st.rerun()
if st.button("📥 导出对话", use_container_width=True):
if st.session_state.messages:
json_str = json.dumps(st.session_state.messages, ensure_ascii=False, indent=2)
st.download_button(
label="下载 JSON",
data=json_str,
file_name=f"agent_chat_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
mime="application/json"
)
# 主界面
display_agent_status_row()
st.divider()
display_active_agent_banner()
display_chat_flow()
# 用户输入
st.divider()
user_input = st.chat_input("💡 输入需求,或点击启动按钮开始...")
if user_input:
add_message("User_Proxy", user_input, "提出需求")
st.rerun()
# 启动工作流
if start_btn:
if not api_key:
st.error("请先设置 API Key")
st.stop()
if not AUTOGEN_AVAILABLE:
st.error("请先安装 AutoGen: pip install pyautogen")
st.stop()
# 获取需求
user_msgs = [m for m in st.session_state.messages if m.get("agent_key") == "User_Proxy"]
if not user_msgs:
add_message("User_Proxy", "请开发一个电池健康状态预测 API", "提出需求")
latest_requirement = user_msgs[-1]["content"] if user_msgs else "请开发一个电池健康状态预测 API"
st.session_state.is_running = True
# 进度提示
progress_placeholder = st.empty()
progress_placeholder.info("🚀 启动 SDLC 工作流Agent 开始协作...")
try:
# 创建 Agent
pm_agent, qa_agent, dev_agent, orchestrator, user_proxy, llm_config = create_agents(
api_key=api_key,
base_url=base_url,
model=model
)
# 添加 Orchestrator 启动消息
add_message("Orchestrator", f"🚀 启动 SDLC 工作流!用户需求:{latest_requirement[:100]}...", "启动流程")
# 创建 GroupChat
groupchat = GroupChat(
agents=[pm_agent, qa_agent, dev_agent, orchestrator, user_proxy],
messages=[],
max_round=max_round,
speaker_selection_method="round_robin"
)
manager = GroupChatManager(groupchat=groupchat, llm_config=llm_config)
# 初始消息
initial_message = f"""
请启动完整的 SDLC 流程:
【用户需求】
{latest_requirement}
【工作流程】
1. PM_Agent → 生成 SRS 文档
2. QA_Agent → 生成测试用例
3. Dev_Agent → 编写代码
4. User_Proxy → 执行测试
5. Orchestrator → 汇总报告
开始协作!每个步骤完成后请明确说明。
"""
# 执行对话
with st.spinner("💬 Agent 们正在协作中,请稍候..."):
chat_result = user_proxy.initiate_chat(
manager,
message=initial_message,
max_turns=max_round
)
# 记录所有对话
for msg in groupchat.messages:
agent_name = msg.get("name", "Unknown")
content = msg.get("content", "")
# 推断任务
task_map = {
"PM_Agent": "需求分析",
"QA_Agent": "测试设计",
"Dev_Agent": "代码实现",
"Orchestrator": "流程协调",
"User_Proxy": "测试执行"
}
task = task_map.get(agent_name, "工作中")
add_message(agent_name, content, task)
# 完成
add_message("Orchestrator", "✅ SDLC 流程完成!所有任务已完成。", "总结完成")
progress_placeholder.success("✅ SDLC 工作流完成!查看上方的对话流了解详情。")
st.session_state.is_running = False
st.rerun()
except Exception as e:
st.session_state.is_running = False
st.session_state.current_agent = None
progress_placeholder.error(f"❌ 错误:{str(e)}")
st.error("请检查 API Key 和网络连接")
if stop_btn:
st.session_state.is_running = False
st.session_state.current_agent = None
st.info("⏸️ 工作流已暂停")
if __name__ == "__main__":
main()