This commit is contained in:
2026-03-12 17:58:15 +08:00
parent cd67c6dac2
commit 7931ce5070
7 changed files with 598 additions and 139 deletions

View File

@@ -4,13 +4,15 @@ Streamlit 实时 Agent 协作平台
功能:
1. 实时展示每个 Agent 的状态和动作
2. 自动保存生成的文件到 workspace/
3. 简单稳定的代码结构
3. Agent 自主决定对话对象(动态发言顺序)
4. 实时更新对话流和 Agent 状态
"""
import streamlit as st
import os
from pathlib import Path
from datetime import datetime
import time
import re
try:
from autogen import AssistantAgent, UserProxyAgent, GroupChat, GroupChatManager
@@ -23,17 +25,18 @@ import sys
sys.path.insert(0, str(Path(__file__).parent.parent))
from config.llm_config import get_llm_config, PM_PROMPT, QA_PROMPT, DEV_PROMPT, ORCH_PROMPT
from utils.callback_handler import get_callback_handler
# 页面配置
st.set_page_config(page_title="多 Agent 协作平台", page_icon="🤖", layout="wide")
# Agent 配置
AGENTS = {
"PM_Agent": {"name": "产品经理", "avatar": "📋", "color": "blue"},
"QA_Agent": {"name": "测试工程师", "avatar": "", "color": "green"},
"Dev_Agent": {"name": "开发工程师", "avatar": "💻", "color": "orange"},
"Orchestrator": {"name": "协调器", "avatar": "🎯", "color": "purple"},
"User_Proxy": {"name": "用户代理", "avatar": "👤", "color": "gray"}
"PM_Agent": {"name": "产品经理", "avatar": "📋", "color": "blue", "desc": "需求分析与 SRS 生成"},
"QA_Agent": {"name": "测试工程师", "avatar": "", "color": "green", "desc": "测试用例设计"},
"Dev_Agent": {"name": "开发工程师", "avatar": "💻", "color": "orange", "desc": "代码实现"},
"Orchestrator": {"name": "协调器", "avatar": "🎯", "color": "purple", "desc": "流程协调与验证"},
"User_Proxy": {"name": "用户代理", "avatar": "👤", "color": "gray", "desc": "测试执行"}
}
def init_state():
@@ -46,6 +49,14 @@ def init_state():
st.session_state.current_agent = None
if "agent_counts" not in st.session_state:
st.session_state.agent_counts = {k: 0 for k in AGENTS}
if "agent_status" not in st.session_state:
st.session_state.agent_status = {k: "⚪ 等待中" for k in AGENTS}
if "current_task" not in st.session_state:
st.session_state.current_task = {k: "" for k in AGENTS}
if "saved_files" not in st.session_state:
st.session_state.saved_files = []
if "conversation_history" not in st.session_state:
st.session_state.conversation_history = []
def add_message(agent, content, task=""):
"""添加消息"""
@@ -58,14 +69,29 @@ def add_message(agent, content, task=""):
st.session_state.messages.append(msg)
st.session_state.agent_counts[agent] = st.session_state.agent_counts.get(agent, 0) + 1
st.session_state.current_agent = agent
# 更新 Agent 状态
for ag in AGENTS:
if ag == agent:
st.session_state.agent_status[ag] = "🟢 发言中"
st.session_state.current_task[ag] = task
else:
st.session_state.agent_status[ag] = "⚪ 等待中"
# 添加到对话历史(用于展示完整的对话流)
st.session_state.conversation_history.append(msg)
def show_agent_status():
"""显示 Agent 状态"""
st.subheader("🎯 Agent 实时状态")
cols = st.columns(len(AGENTS))
for i, (agent_key, info) in enumerate(AGENTS.items()):
with cols[i]:
is_active = st.session_state.current_agent == agent_key
count = st.session_state.agent_counts.get(agent_key, 0)
status = st.session_state.agent_status.get(agent_key, "⚪ 等待中")
current_task = st.session_state.current_task.get(agent_key, "")
border_color = info["color"]
bg_color = "#e8f5e9" if is_active else "white"
@@ -77,15 +103,18 @@ def show_agent_status():
border: 3px {"solid" if is_active else "dashed"} {border_color};
background: {bg_color};
text-align: center;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
'>
<div style='font-size: 2.5rem;'>{info["avatar"]}</div>
<div style='font-weight: bold; margin: 5px 0;'>{info["name"]}</div>
<div style='font-size: 0.8rem; color: #666;'>
{"🟢 发言中" if is_active else "⚪ 等待中"}
<div style='font-weight: bold; margin: 5px 0; font-size: 1.1rem;'>{info["name"]}</div>
<div style='font-size: 0.75rem; color: #666; margin-bottom: 5px;'>{info["desc"]}</div>
<div style='font-size: 0.85rem; color: #2e7d32; margin: 5px 0;'>
{status}
</div>
<div style='font-size: 0.8rem; color: #999; margin-top: 5px;'>
<div style='font-size: 0.75rem; color: #999; margin-top: 5px;'>
💬 {count} 条消息
</div>
{f"<div style='font-size: 0.7rem; color: #1976d2; margin-top: 5px; font-style: italic;'>📋 {current_task}</div>" if current_task else ""}
</div>
""", unsafe_allow_html=True)
@@ -93,17 +122,41 @@ def show_chat():
"""显示对话流"""
st.subheader("💬 Agent 对话流")
if not st.session_state.messages:
if not st.session_state.conversation_history:
st.info("👈 暂无对话,请在下方输入需求并启动")
return
for msg in st.session_state.messages:
agent = msg["agent"]
info = AGENTS.get(agent, {"name": "未知", "avatar": "🤖", "color": "gray"})
with st.chat_message(agent.lower(), avatar=info["avatar"]):
st.markdown(f"**{info['name']}** *{msg['time']}* - {msg['task']}")
st.markdown(msg["content"][:800] + ("..." if len(msg["content"]) > 800 else ""))
# 使用容器展示对话,支持滚动
chat_container = st.container()
with chat_container:
for i, msg in enumerate(st.session_state.conversation_history):
agent = msg["agent"]
info = AGENTS.get(agent, {"name": "未知", "avatar": "🤖", "color": "gray"})
# 创建对话气泡
with st.chat_message(agent.lower(), avatar=info["avatar"]):
# 显示 Agent 名称、时间和任务
task_info = f"- {msg['task']}" if msg['task'] else ""
st.markdown(f"**{info['name']}** *{msg['time']}* {task_info}")
# 处理长内容,提供折叠选项
content = msg["content"]
if len(content) > 1000:
# 短预览 + 展开详情
preview = content[:800] + "..."
st.markdown(preview)
with st.expander("查看完整内容"):
st.markdown(content)
else:
st.markdown(content)
# 如果是代码内容,提供语法高亮
if "```" in content:
code_blocks = re.findall(r'```(\w+)?\n(.*?)```', content, re.DOTALL)
for lang, code in code_blocks:
language = lang if lang else "python"
st.code(code, language=language)
def extract_code(content):
"""从 Markdown 代码块中提取纯代码"""
@@ -123,6 +176,40 @@ def extract_code(content):
# 没有标记,返回原内容
return content
def is_code_complete(code):
"""检查代码是否完整"""
if not code or not code.strip():
return False
# 检查是否有未闭合的括号
if code.count('(') != code.count(')'):
print(f"⚠️ 括号不匹配:( {code.count('(')} vs ) {code.count(')')}")
return False
if code.count('[') != code.count(']'):
print(f"⚠️ 方括号不匹配:[ {code.count('[')} vs ] {code.count(']')}")
return False
if code.count('{') != code.count('}'):
print(f"⚠️ 花括号不匹配:{{ {code.count('{')} vs }} {code.count('}')}")
return False
# 检查是否以完整的方式结束(不是突然截断)
lines = code.strip().split('\n')
if lines:
last_line = lines[-1].rstrip()
# 如果最后一行以这些符号结尾,说明可能被截断了
if last_line.endswith(':') or last_line.endswith('\\') or last_line.endswith('(') or last_line.endswith('['):
print(f"⚠️ 代码可能截断:最后一行是 '{last_line}'")
return False
# 尝试简单的语法检查
try:
compile(code, '<string>', 'exec')
except SyntaxError as e:
print(f"⚠️ 语法错误:{e}")
return False
return True
def save_files():
"""保存生成的文件到 workspace/"""
workspace = Path("workspace")
@@ -130,37 +217,70 @@ def save_files():
files = []
# 遍历消息,提取并保存文件
# 1. 保存 PM Agent 的 SRS 文档
for msg in st.session_state.messages:
agent = msg["agent"]
content = msg["content"]
# PM Agent 生成 SRS
if agent == "PM_Agent" and ("需求" in content or "SRS" in content):
if msg["agent"] == "PM_Agent" and ("需求" in msg["content"] or "SRS" in msg["content"]):
file = workspace / "SRS.md"
with open(file, "w", encoding="utf-8") as f:
f.write(f"# 软件需求规格说明书\n\n生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
f.write(content)
f.write(msg["content"])
files.append(str(file))
# QA Agent 生成测试
if agent == "QA_Agent" and ("test" in content.lower() or "测试" in content or "def test_" in content):
break # 只保存第一个
# 2. 保存 QA Agent 的测试代码
for msg in st.session_state.messages:
if msg["agent"] == "QA_Agent" and ("test" in msg["content"].lower() or "测试" in msg["content"] or "def test_" in msg["content"]):
file = workspace / "test_sample.py"
# 提取纯代码
code = extract_code(content)
code = extract_code(msg["content"])
with open(file, "w", encoding="utf-8") as f:
f.write(f"# 测试用例\n# 生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
f.write(code)
files.append(str(file))
break # 只保存第一个
# 3. 保存 Dev Agent 的代码 - 收集所有消息合并处理
dev_messages = [msg for msg in st.session_state.messages if msg["agent"] == "Dev_Agent"]
if dev_messages:
# 合并所有 Dev Agent 的消息内容
all_dev_content = "\n\n".join([msg["content"] for msg in dev_messages])
# Dev Agent 生成代码
if agent == "Dev_Agent" and ("def " in content or "class " in content):
# 尝试提取带文件名的代码
import re
code_blocks = re.findall(r'```python\s*\n#(?:\s*File:|\s*filename:)?\s*([^\n]+)\n(.*?)```', all_dev_content, re.DOTALL)
if code_blocks:
# 保存多个文件
for filename, code_content in code_blocks:
filename = filename.strip()
file_path = workspace / filename
code = extract_code(f"```python\n{code_content}```")
# 检查完整性
print(f"\n🔍 检查文件 {filename}...")
if not is_code_complete(code):
print(f"⚠️ 警告:{filename} 代码可能不完整,但仍会保存")
else:
print(f"{filename} 代码完整")
with open(file_path, "w", encoding="utf-8") as f:
f.write(f"# 生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n{code}")
files.append(str(file_path))
else:
# 没有文件名标记,合并所有代码保存为 src_sample.py
all_code = "\n\n".join([extract_code(msg["content"]) for msg in dev_messages])
file = workspace / "src_sample.py"
# 提取纯代码
code = extract_code(content)
# 检查完整性
print(f"\n🔍 检查文件 src_sample.py...")
if not is_code_complete(all_code):
print(f"⚠️ 警告src_sample.py 代码可能不完整,但仍会保存")
else:
print(f"✅ src_sample.py 代码完整")
with open(file, "w", encoding="utf-8") as f:
f.write(f"# 源代码\n# 生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
f.write(code)
f.write(all_code)
files.append(str(file))
return files