import sys
import json
import time
import re
from pathlib import Path
import streamlit as st
# 将项目根目录添加到 python path
root_dir = Path(__file__).resolve().parent.parent
if str(root_dir) not in sys.path:
sys.path.insert(0, str(root_dir))
import importlib
import src.agent.engine
importlib.reload(src.agent.engine)
from src.agent.engine import AIScientistEngine
from src.llm.client import LLMClient
from src.skills.hypothesis_generator import HypothesisGeneratorSkill
from src.skills.hypothesis_critic import HypothesisCriticSkill
from src.skills.plan_writer import ResearchPlanWriterSkill
from src.config import Config
# 页面配置
st.set_page_config(
page_title="AI Scientist 发现引擎",
page_icon="🔬",
layout="wide",
initial_sidebar_state="expanded"
)
# 自定义 CSS 质感样式与极客暗黑终端
st.markdown("""
""", unsafe_allow_html=True)
# 默认 cs.AI 科学问题
DEFAULT_AI_PROBLEM = "如何突破大语言模型在多步复杂科学推理中的逻辑幻觉问题,并构建具备自一致性(Self-Consistency)校验能力的自进化多智能体科研发现闭环?"
DEFAULT_PROBLEMS = {
"cs.AI": "如何突破大语言模型在多步复杂科学推理中的逻辑幻觉问题,并构建具备自一致性(Self-Consistency)校验能力的自进化多智能体科研发现闭环?",
"astro-ph": "如何结合原初黑洞(PBH)假设与 JWST 观测到的红移 z>10 早期超大质量黑洞数据,构建自洽的早期宇宙黑洞吸积与自旋演化理论模型?",
"quant-ph": "如何通过容错量子纠错码(Surface Code)与拓扑量子比特的协同设计,降低百量子比特级超导量子芯片的逻辑门操作错误率至 10^-5 以下?",
"bio": "如何利用多模态蛋白质大语言模型与 AlphaFold3 构象采样,设计具备靶向变构抑制能力且高选择性的 KRAS G12D 突变体小分子抑制剂?"
}
# 辅助渲染:科学假说卡片与对抗性审查结果面板
def render_hypothesis_card(hypo: dict, title_prefix: str = "💡 科学假说):"):
if not hypo:
return
st.markdown(f"### {title_prefix}")
st.info(f"**📌 核心假说陈述 (Hypothesis Statement)**:\n\n{hypo.get('hypothesis_statement', 'N/A')}")
col_a, col_b = st.columns(2)
with col_a:
st.markdown(f"**💡 逻辑机制与理论推理 (Rationale)**:\n{hypo.get('rationale', 'N/A')}")
st.markdown(f"**✨ 理论新颖性评价 (Novelty)**:\n{hypo.get('novelty', hypo.get('novelty_score', 'N/A'))}")
with col_b:
falsification = hypo.get('falsifiable_conditions', hypo.get('falsification_conditions', 'N/A'))
st.markdown(f"**🔬 可证伪测量条件 (Falsification Conditions)**:\n`{falsification}`")
if hypo.get('evidence_chain'):
st.markdown(f"**📚 支撑文献链条 (Evidence Chain)**:\n{hypo.get('evidence_chain')}")
# 移除原本的正则拦截 (改为依赖大模型 Prompt 的准确指示)
def sanitize_review_comments(comments: str) -> str:
return comments
def render_markdown_with_mermaid(text: str):
import re
import streamlit.components.v1 as components
pattern = r"```mermaid\n(.*?)\n```"
parts = re.split(pattern, text, flags=re.DOTALL)
for i, part in enumerate(parts):
if i % 2 == 0:
if part.strip():
st.markdown(part)
else:
html_code = f"""
{part}
"""
components.html(html_code, height=500, scrolling=True)
def render_review_card(rev: dict, title_prefix: str = "🔍 Reviewer #2 对抗性同行审查打分结果"):
if not rev or not isinstance(rev, dict):
return
st.markdown(f"### {title_prefix}")
dec = rev.get("decision", "ACCEPT")
score = rev.get("total_score", 42)
if dec == "ACCEPT":
st.success(f"**同行评审裁决**: ✅ **ACCEPT (高分采纳)** | **综合审查得分**: `{score}/50` 分")
elif dec == "REVISE":
st.warning(f"**同行评审裁决**: ⚠️ **REVISE (需要修回)** | **综合审查得分**: `{score}/50` 分")
else:
st.error(f"**同行评审裁决**: ❌ **REJECT (驳回建议修改)** | **综合审查得分**: `{score}/50` 分")
scores = rev.get("scores", {})
if scores:
c1, c2, c3, c4, c5 = st.columns(5)
c1.metric("逻辑自洽性", f"{scores.get('logical_consistency', scores.get('自洽性', 8))}/10")
c2.metric("文献支撑度", f"{scores.get('literature_grounding', scores.get('文献支撑', 8))}/10")
c3.metric("可证伪性", f"{scores.get('falsifiability', scores.get('可证伪性', 8))}/10")
c4.metric("理论新颖性", f"{scores.get('novelty', scores.get('新颖性', 8))}/10")
c5.metric("实验可行性", f"{scores.get('feasibility', scores.get('数据一致性', 8))}/10")
comments = rev.get('detailed_comments', 'N/A')
comments = sanitize_review_comments(comments)
st.markdown(f"**细节意见批注**:\n\n{comments}")
# 状态机与控制台日志持久化初始化 (1_input, 2_hypo_generated, 3_reviewed, 4_report_done)
if "current_state" not in st.session_state:
st.session_state.current_state = "1_input"
if "current_hypo" not in st.session_state:
st.session_state.current_hypo = None
if "review_result" not in st.session_state:
st.session_state.review_result = None
if "final_report" not in st.session_state:
st.session_state.final_report = None
# 强持久化控制台日志与全自动轮数历史
if "gen_console_log" not in st.session_state:
st.session_state.gen_console_log = ""
if "review_console_log" not in st.session_state:
st.session_state.review_console_log = ""
if "auto_rounds_history" not in st.session_state:
st.session_state.auto_rounds_history = []
# 侧边栏
with st.sidebar:
st.image("https://img.icons8.com/color/96/artificial-intelligence.png", width=64)
st.markdown("### ⚙️ 控制面板")
domain_category = st.selectbox(
"选择科学领域分类",
["cs.AI (人工智能与科学发现)", "astro-ph (天文学/天体物理)", "quant-ph (量子物理)", "bio (生物医学)"],
index=0
)
category_code = domain_category.split()[0]
# 当切换分类时,自动联动更新当前待探索难题文本框内容
if st.session_state.get("prev_category") != category_code:
default_prob = DEFAULT_PROBLEMS.get(category_code, DEFAULT_AI_PROBLEM)
st.session_state["auto_problem"] = default_prob
st.session_state["co_pilot_problem"] = default_prob
st.session_state["prev_category"] = category_code
max_papers = st.slider(
"📚 检索文献篇数 (Max Papers)",
min_value=1,
max_value=20,
value=5,
step=1,
help="设置向 arXiv 真实检索文献的最大篇数,默认 5 篇"
)
max_rounds = st.slider(
"🔄 对抗审查最大轮数 (Max Rounds)",
min_value=1,
max_value=10,
value=3,
step=1,
help="设置 Reviewer #2 对抗审查与假说修回的最多个数轮次,默认 3 轮"
)
pass_threshold = st.slider(
"🎯 审查通过门槛分数 (Pass Threshold)",
min_value=20,
max_value=45,
value=35,
step=1,
help="设置 Reviewer #2 对抗审查的判定通过门槛得分,达到该分数即判定通过 (ACCEPT) 并自动导出报告,默认 35 分(满分 50 分)"
)
st.markdown("---")
st.markdown("### 🤖 底座大模型配置")
selected_model = st.selectbox(
"切换底座大模型 (Qwen 系列)",
["qwen3.7-max", "qwen3.7-plus", "qwen3.6-flash"],
index=0,
help="选择实时交互与推理打分所使用的阿里通义开源 Qwen 系列大模型"
)
st.info(f"**模型后端**: 阿里通义 Qwen MaaS\n\n**当前模型 ID**: `{selected_model}`")
st.markdown("---")
# 重新开始按钮
if st.button("🔄 重置/重新开始全流程", use_container_width=True):
st.session_state.current_state = "1_input"
st.session_state.current_hypo = None
st.session_state.review_result = None
st.session_state.final_report = None
st.session_state.gen_console_log = ""
st.session_state.review_console_log = ""
st.session_state.auto_console_log = ""
st.session_state.auto_dialogue_log = ""
st.session_state.auto_hypo = None
st.session_state.auto_review = None
st.session_state.auto_final_report = None
st.session_state.auto_rounds_history = []
st.rerun()
# 主内容区域
st.markdown('🔬 AI Scientist 发现引擎
', unsafe_allow_html=True)
st.markdown('基于国产开源大模型与闭环 Agent 的假说-审查交互迭代系统
', unsafe_allow_html=True)
# 模式选择 Tab (全自动一键流模式放在左边并默认选中)
tab1, tab2 = st.tabs(["⚡ 24/7 全自动一键流模式 (Autonomous Mode)", "🤝 人在回路协同模式 (Human-AI Co-Pilot Loop)"])
# ---------------------------------------------------------
# TAB 2: 人在回路协同 Loop 模式 (支持 Qwen 动态模型切换)
# ---------------------------------------------------------
with tab2:
llm_client = LLMClient(default_model=selected_model)
engine = AIScientistEngine(llm_client=llm_client)
hypo_skill = HypothesisGeneratorSkill(llm_client=llm_client)
critic_skill = HypothesisCriticSkill(llm_client=llm_client)
writer_skill = ResearchPlanWriterSkill(llm_client=llm_client)
# 1. 顶部初学者指南横幅 (Onboarding Guide)
st.markdown("""
💡 第一次使用指引 (新手 3 步法):
① 在下方输入框填写科学问题,点击 【💡 1. 触发/实时生成科学假说】
② 审阅生成结果(支持随时在线修改),点击 【🔍 2. 提交对抗性审查打分】
③ 查看审稿判定后,根据提示点击 【📝 3. 确认假说并生成完整报告】 下载最终论文!
""", unsafe_allow_html=True)
# 2. 顶部 Wizard 进度条
state = st.session_state.current_state
s1 = "w-step done" if state != "1_input" else "w-step active"
s2 = "w-step done" if state in ["3_reviewed", "4_report_done"] else ("w-step active" if state == "2_hypo_generated" else "w-step")
s3 = "w-step done" if state == "4_report_done" else ("w-step active" if state == "3_reviewed" else "w-step")
s4 = "w-step active" if state == "4_report_done" else "w-step"
st.markdown(f"""
1. 输入科学难题
➔
2. 实时生成假说
➔
3. 对抗性审查打分
➔
4. 导出论文报告
""", unsafe_allow_html=True)
# ---------------------------------------------------------
# 卡片 1:科学问题输入与生成
# ---------------------------------------------------------
st.markdown('📋 步骤 1:输入待探索的科学问题并生成假说
', unsafe_allow_html=True)
problem = st.text_area(
"请输入或修改您的科学难题:",
value=DEFAULT_AI_PROBLEM,
height=80,
key="co_pilot_problem"
)
col_gen, _ = st.columns([1, 2])
with col_gen:
gen_is_primary = (state == "1_input")
gen_btn = st.button(
"💡 1. 触发/实时生成科学假说" if state == "1_input" else "💡 重新触发/进化生成新假说",
type="primary" if gen_is_primary else "secondary",
use_container_width=True
)
# 处理点击生成假说与流式打字渲染
if gen_btn:
gen_status_text = st.empty()
gen_status_text.info("⚙️ [AI 假说生成器] 正在与阿里通义 Qwen 大模型建立 Channel,请稍候...")
with st.container():
console_placeholder = st.empty()
# 1. 点击第 0 秒:立刻渲染控制台并输出初始化日志
full_stream_text = "[System Log]: 引擎初始化成功,正在检索 arXiv 学术文献库...\n"
console_placeholder.markdown(f'⚡ [引擎状态]: 正在向 arXiv 检索学术文献...
{full_stream_text}
', unsafe_allow_html=True)
gen_status_text.info("⏳ 正在向 arXiv 检索真实文献并构建 1024 维 Dense Vector Embeddings...")
# 2. 执行文献检索 (使用侧边栏配置的 max_papers)
lit_res = engine.lit_miner.execute(problem, category=category_code, max_results=max_papers)
papers = lit_res["papers_found"]
st.session_state.papers = papers
full_stream_text += f"[System Log]: 检索成功!获取到 {len(papers)} 篇核心真实论文:\n"
for idx, paper in enumerate(papers, 1):
pub_date = paper.get('published', 'N/A')
title = paper.get('title', 'Untitled')
doi = paper.get('doi', 'N/A')
full_stream_text += f" [{idx}] [{pub_date}] \"{title}\" (DOI: {doi})\n"
full_stream_text += f"\n[System Log]: 稠密向量化编码完成!成功构建 {len(papers)} 篇文献的 1024 维 Dense Vector Embeddings 向量矩阵。\n[System Log]: 正在向阿里通义 {selected_model} 建立流式推理通道...\n\n[LLM Stream Output]: "
gen_status_text.info(f"🧠 正在与阿里通义 Qwen ({selected_model}) 进行深度多步逻辑推导...")
console_placeholder.markdown(f'🧠 [大模型 Channel]: 阿里通义 Qwen ({selected_model}) 正在深度推理假说中...
{full_stream_text}
', unsafe_allow_html=True)
human_guidance = st.session_state.get("human_guidance", "")
last_review_comments = st.session_state.review_result.get("detailed_comments", "") if st.session_state.review_result else ""
combined_feedback = f"{last_review_comments} {human_guidance}".strip()
if hasattr(hypo_skill, 'execute_stream'):
stream_gen = hypo_skill.execute_stream(problem, papers, critic_feedback=combined_feedback)
else:
res_list = hypo_skill.execute(problem, papers, critic_feedback=combined_feedback)
stream_gen = [json.dumps(res_list, ensure_ascii=False, indent=2)]
raw_accumulated = ""
for token_chunk in stream_gen:
raw_accumulated += token_chunk
for char in token_chunk:
full_stream_text += char
if len(full_stream_text) % 2 == 0:
console_placeholder.markdown(f'🧠 [大模型 Channel]: 阿里通义 Qwen ({selected_model}) 实时输出 Token 流...
{full_stream_text}
', unsafe_allow_html=True)
time.sleep(0.003)
full_stream_text += "\n\n[System Log]: 流式推导完成!正在解析 3 项结构化假说..."
console_placeholder.markdown(f'{full_stream_text}
', unsafe_allow_html=True)
# 保存日志到持久 session
st.session_state.gen_console_log = full_stream_text
try:
cleaned_res = raw_accumulated.strip()
if "```json" in raw_accumulated:
cleaned_res = raw_accumulated.split("```json")[1].split("```")[0].strip()
elif "```" in raw_accumulated:
cleaned_res = raw_accumulated.split("```")[1].split("```")[0].strip()
candidates = json.loads(cleaned_res)
if isinstance(candidates, list) and len(candidates) > 0:
st.session_state.current_hypo = candidates[0]
else:
st.session_state.current_hypo = hypo_skill.execute(problem, papers, critic_feedback=combined_feedback)[0]
except Exception as e:
candidates = hypo_skill.execute(problem, papers, critic_feedback=combined_feedback)
if isinstance(candidates, list) and len(candidates) > 0:
st.session_state.current_hypo = candidates[0]
gen_status_text.success("🎉 科学假说实时流式推导成功完成!")
st.session_state.current_state = "2_hypo_generated"
st.rerun()
# 常驻展示生成的控制台日志 (如果存在)
if st.session_state.gen_console_log:
st.markdown("##### 🖥️ 假说生成实时思维链控制台日志")
with st.container():
st.markdown(f'{st.session_state.gen_console_log}
', unsafe_allow_html=True)
# ---------------------------------------------------------
# 卡片 2:科学假说展现与导师干预编辑
# ---------------------------------------------------------
if st.session_state.current_hypo:
st.markdown("---")
st.markdown('💡 步骤 2:假说评估与人类导师先验介入
', unsafe_allow_html=True)
render_hypothesis_card(st.session_state.current_hypo, "💡 AI 实时推导产生的首选科学假说")
st.markdown("#### ✍️ 人类导师在线干预与先验指导面板")
st.caption("人类导师可直接在线修改上述假说的表达式或注入专家先验知识。点击下一步将带入您的修正。")
edited_statement = st.text_area(
"假说陈述 (Statement):",
value=st.session_state.current_hypo.get("hypothesis_statement", ""),
height=85,
key="edited_stmt"
)
edited_rationale = st.text_area(
"理论推理逻辑 (Rationale):",
value=st.session_state.current_hypo.get("rationale", ""),
height=85,
key="edited_rat"
)
edited_falsification = st.text_area(
"可证伪测量条件 (Falsifiable Conditions):",
value=st.session_state.current_hypo.get("falsifiable_conditions", st.session_state.current_hypo.get("falsification_conditions", "")),
height=85,
key="edited_fals"
)
st.session_state.current_hypo["hypothesis_statement"] = edited_statement
st.session_state.current_hypo["rationale"] = edited_rationale
st.session_state.current_hypo["falsifiable_conditions"] = edited_falsification
human_note = st.text_input(
"💬 补充人类导师指导意见(注入后重新生成将带入此引导):",
placeholder="例如:建议在推导中补充对偶博弈的极小极大熵约束...",
key="human_input"
)
if human_note:
st.session_state.human_guidance = human_note
# 提交审查按钮
rev_is_primary = (state == "2_hypo_generated")
col_rev, _ = st.columns([1, 2])
with col_rev:
review_btn = st.button(
"🔍 2. 提交对抗性审查打分 (Reviewer #2)",
type="primary" if rev_is_primary else "secondary",
use_container_width=True
)
if review_btn:
if hasattr(critic_skill, 'review_stream'):
try:
rev_stream = critic_skill.review_stream(st.session_state.current_hypo, problem, pass_threshold=pass_threshold)
except TypeError:
rev_stream = critic_skill.review_stream(st.session_state.current_hypo, problem)
else:
try:
rev_res = critic_skill.review(st.session_state.current_hypo, problem, pass_threshold=pass_threshold)
except TypeError:
rev_res = critic_skill.review(st.session_state.current_hypo, problem)
rev_stream = [json.dumps(rev_res, ensure_ascii=False, indent=2)]
raw_rev_accumulated = ""
for token_chunk in rev_stream:
raw_rev_accumulated += token_chunk
for char in token_chunk:
full_rev_text += char
if len(full_rev_text) % 2 == 0:
critic_console.markdown(f'{full_rev_text}▋
', unsafe_allow_html=True)
time.sleep(0.003)
full_rev_text += "\n\n[System Log]: Reviewer #2 审查完毕!正在解析 5 维结构化判定..."
critic_console.markdown(f'{full_rev_text}
', unsafe_allow_html=True)
# 持久化审查日志
st.session_state.review_console_log = full_rev_text
try:
cleaned_rev = raw_rev_accumulated.strip()
if "```json" in raw_rev_accumulated:
cleaned_rev = raw_rev_accumulated.split("```json")[1].split("```")[0].strip()
elif "```" in raw_rev_accumulated:
cleaned_rev = raw_rev_accumulated.split("```")[1].split("```")[0].strip()
parsed_rev = json.loads(cleaned_rev)
st.session_state.review_result = parsed_rev
except Exception as e:
st.session_state.review_result = critic_skill.review(st.session_state.current_hypo, problem)
st.session_state.current_state = "3_reviewed"
st.rerun()
# 常驻展示审查的控制台日志 (如果存在)
if st.session_state.review_console_log:
st.markdown("##### 🖥️ Reviewer #2 实时对抗审查控制台日志")
with st.container():
st.markdown(f'{st.session_state.review_console_log}
', unsafe_allow_html=True)
# ---------------------------------------------------------
# 卡片 3:审查判定结果与下一步引导
# ---------------------------------------------------------
if st.session_state.review_result:
rev = st.session_state.review_result
st.markdown("---")
st.markdown('📊 步骤 3:审查判定与决策指引
', unsafe_allow_html=True)
render_review_card(rev)
decision = rev.get("decision", "")
is_passed = (decision == "ACCEPT" or rev.get('total_score', 0) >= 40)
if is_passed:
st.success("🎉 **【下一步动作指引】审查已高分通过 (ACCEPT)!** 假说具备充分学术可行性。请点击下方高亮按钮 **【📝 3. 确认假说并生成完整报告】** 导出论文研究计划!")
else:
st.warning("⚠️ **【下一步动作指引】审稿人提出了修改意见 (REVISE/REJECT)!**\n\n您有两个选择:\n1. **推荐 (开启下一轮 Loop)**:参考上面审稿批注,在步骤 2 修改假说或补充导师意见,然后重新点击步骤 1 的 **【💡 重新触发/进化生成新假说】**。\n2. **直接生成**:点击下方 **【📝 3. 确认假说并生成完整报告】** 强行导出报告。")
col_build, _ = st.columns([1, 2])
with col_build:
build_report_btn = st.button(
"📝 3. 确认假说并生成完整报告",
type="primary" if is_passed else "secondary",
use_container_width=True
)
if build_report_btn:
with st.spinner("正在基于人机协同确立的假说编纂 10 字段标准研究计划..."):
papers = st.session_state.get("papers", [])
report_md = writer_skill.execute(
problem_statement=problem,
hypothesis=st.session_state.current_hypo,
evidence_graph_mermaid="graph TD\n A[人机协同确立科学假设] --> B[验证物理机制成立]",
literature_list=papers
)
st.session_state.final_report = report_md
st.session_state.current_state = "4_report_done"
st.rerun()
# ---------------------------------------------------------
# 卡片 4:最终论文报告展示与下载
# ---------------------------------------------------------
if st.session_state.final_report:
st.markdown("---")
report_text = st.session_state.final_report
first_line = report_text.strip().split('\n')[0].replace('#', '').strip()
clean_title = first_line.replace('《', '').replace('》', '').strip()
if not clean_title or len(clean_title) > 60:
clean_title = "科学假设与研究计划报告"
st.markdown(f'📄 步骤 4:导出的《{clean_title}》
', unsafe_allow_html=True)
render_markdown_with_mermaid(report_text)
file_name_clean = f"{clean_title.replace(' ', '_')}.md"
st.download_button(
f"⬇️ 下载《{clean_title}》Markdown 文档",
data=report_text,
file_name=file_name_clean,
mime="text/markdown",
use_container_width=True
)
# ---------------------------------------------------------
# TAB 1: 全自动一键流模式 (控制台流式输出 + 大模型对话实时显示 + 假说与审查卡片体现)
# ---------------------------------------------------------
with tab1:
st.markdown("#### ⚡ 24/7 全自动无人值守科研发现流")
st.info("💡 **全自动模式下,系统将自动依次联动【文献挖掘 ➔ 知识图谱 ➔ 假说生成 ➔ Reviewer #2 对抗审查 ➔ 报告编纂】全流阶段。下方将实时流式展示大模型对话与生成的假说/审查卡片。**")
auto_problem = st.text_area(
"输入待探索难题 (全自动模式):",
value=DEFAULT_AI_PROBLEM,
height=80,
key="auto_problem"
)
col_btn1, col_btn2 = st.columns([2, 1])
with col_btn1:
start_auto_btn = st.button("🚀 启动 24/7 全自动科研发现流程", type="primary", use_container_width=True)
with col_btn2:
stop_auto_btn = st.button("🛑 提前中止循环 (选当前最高分导出)", type="secondary", use_container_width=True)
if stop_auto_btn:
st.session_state.auto_stop_requested = True
st.warning("⚠️ 提前中止指令已发出!系统将在当前子阶段完成后,自动提取截至目前打分最高的假说导出研究计划。")
if start_auto_btn:
st.session_state.auto_stop_requested = False
st.markdown("##### 🖥️ 24/7 全自动科研发现引擎控制台 & 💬 大模型对话实时流视窗")
auto_status_text = st.empty()
auto_status_text.info("⚙️ [24/7 全自动科研发现引擎] 正在高效与阿里通义 Qwen 大模型交互流中...")
with st.container():
col_c1, col_c2 = st.columns(2)
with col_c1:
st.caption("📟 **底座引擎 Terminal 日志**")
auto_console = st.empty()
with col_c2:
st.caption("💬 **与大模型 (Qwen & Reviewer #2) 实时流式对话**")
auto_dialogue = st.empty()
progress_bar = st.progress(0)
st.markdown("---")
# 顺序卡片流挂载点
auto_cards_area = st.container()
auto_llm_client = LLMClient(default_model=selected_model)
auto_engine = AIScientistEngine(llm_client=auto_llm_client)
full_auto_text = f"[System Log]: 启动 24/7 全自动无人值守科研发现流程 (底座大模型: {selected_model}, 最大文献: {max_papers}篇, 最大审查轮数: {max_rounds}轮)...\n"
full_dialogue_html = f'🤖 [System Prompt]: 引擎启动,正向通义 Qwen ({selected_model}) 建立 Channel (最大审查: {max_rounds} 轮)...
'
auto_console.markdown(f'⚡ [24/7 自动发现引擎]: 通义 Qwen ({selected_model}) 实时 Channel 建立中...
{full_auto_text}
', unsafe_allow_html=True)
auto_dialogue.markdown(full_dialogue_html, unsafe_allow_html=True)
final_report = ""
auto_hypo = None
auto_review = None
current_dialogue_content = ""
rounds_history = []
rendered_hypo_rounds = set()
# 重置历史轮次
st.session_state.auto_rounds_history = []
try:
safe_problem = str(auto_problem or DEFAULT_AI_PROBLEM)
stop_check_func = lambda: st.session_state.get("auto_stop_requested", False)
for event in auto_engine.run_discovery_flow(
safe_problem,
category=category_code,
max_papers=max_papers,
max_rounds=max_rounds,
pass_threshold=pass_threshold,
stop_checker=stop_check_func
):
stage = event.get("stage", "")
status = event.get("status", "")
msg = event.get("msg", "")
role = event.get("role", "System")
# 动态更新顶栏转圈指示条
if msg:
auto_status_text.info(f"🔄 [{stage}] {msg} (大模型正在高速推理中...)")
if status == "streaming":
chunk = str(event.get("chunk", ""))
full_auto_text += chunk
current_dialogue_content += chunk
# 实时更新对话框
role_label = "💡 [AI Hypothesis Generator]" if role == "LLM-HypothesisGenerator" else "😈 [Reviewer #2 Critic]"
bubble_class = "chat-bubble-hypo" if role == "LLM-HypothesisGenerator" else "chat-bubble-rev"
# 限制对话框显示长度以保持平滑
disp_dialogue = current_dialogue_content[-600:]
dialogue_html = f'''
🤖 [System Status]: 正在实时接收大模型 Token 流...
{role_label}:
{disp_dialogue}
'''
auto_dialogue.markdown(dialogue_html, unsafe_allow_html=True)
elif stage == "1.文献检索" and status == "completed":
progress_bar.progress(25)
full_auto_text += f"\n[Stage 1 - 文献挖掘]: {msg}\n"
papers = event.get("papers", [])
if isinstance(papers, list) and papers:
full_auto_text += f" 成功检索到 {len(papers)} 篇核心真实论文列表:\n"
for idx, paper in enumerate(papers, 1):
if isinstance(paper, dict):
full_auto_text += f" [{idx}] [{paper.get('published', 'N/A')}] \"{paper.get('title')}\" (DOI: {paper.get('doi')})\n"
elif stage == "2.知识图谱" and status == "completed":
progress_bar.progress(50)
full_auto_text += f"\n[Stage 2 - 知识图谱]: {msg}\n"
elif stage == "3.假说生成" and status == "running":
progress_bar.progress(65)
current_dialogue_content = ""
full_auto_text += f"\n[Stage 3 - 假说生成]: {msg}\n[LLM Stream Output]: "
elif stage == "3.对抗审查" and status == "running":
progress_bar.progress(80)
c_round = event.get("round", 1)
auto_hypo = event.get("hypothesis")
# 按轮次顺序追加展示第 c_round 轮生成的假说卡片
if c_round not in rendered_hypo_rounds and auto_hypo and isinstance(auto_hypo, dict):
rendered_hypo_rounds.add(c_round)
with auto_cards_area:
st.markdown("---")
render_hypothesis_card(auto_hypo, f"💡 [第 {c_round} 轮] 全自动实时生成的科学假说:")
current_dialogue_content = ""
full_auto_text += f"\n\n[Stage 3 - 对抗性审查]: {msg}\n[LLM Stream Output]: "
elif stage == "3.审查完成":
c_round = event.get("round", 1)
auto_hypo = event.get("hypothesis")
auto_review = event.get("review")
full_auto_text += f"\n\n[System Log]: {msg}\n"
# 审查完毕,按轮次顺序在下方追加渲染第 c_round 轮对抗审查结果
with auto_cards_area:
if c_round not in rendered_hypo_rounds and isinstance(auto_hypo, dict):
rendered_hypo_rounds.add(c_round)
st.markdown("---")
render_hypothesis_card(auto_hypo, f"💡 [第 {c_round} 轮] 全自动实时生成的科学假说:")
if isinstance(auto_review, dict):
render_review_card(auto_review, f"🔍 [第 {c_round} 轮] Reviewer #2 对抗性同行审查打分结果:")
rounds_history.append({
"round": c_round,
"hypo": auto_hypo,
"review": auto_review
})
elif stage == "4.报告生成" and status == "running":
progress_bar.progress(90)
full_auto_text += f"\n[Stage 4 - 报告编纂]: {msg}\n"
elif stage == "4.报告生成" and status == "completed":
progress_bar.progress(100)
final_report = str(event.get("report_md", ""))
full_auto_text += f"\n[Stage 4 - 报告编纂]: {msg}\n"
# 平滑渲染更新控制台
if len(full_auto_text) % 3 == 0 or status != "streaming":
auto_console.markdown(f'⚡ [24/7 自动发现引擎]: 正在多步逻辑推理中...
{full_auto_text}
', unsafe_allow_html=True)
except Exception as auto_err:
full_auto_text += f"\n\n[System Error]: 全自动科研流捕获到异常: {auto_err}\n"
st.error(f"❌ 全自动流程捕获到异常: {auto_err}")
full_auto_text += "\n[System Log]: 全自动科研流执行完成。"
auto_console.markdown(f'{full_auto_text}
', unsafe_allow_html=True)
auto_status_text.success("🎉 24/7 全自动无人值守科研发现流程全部圆满完成!")
st.session_state.auto_console_log = full_auto_text
st.session_state.auto_final_report = final_report
st.session_state.auto_hypo = auto_hypo
st.session_state.auto_review = auto_review
st.session_state.auto_rounds_history = rounds_history
# 持久化展示 Tab 2 控制台与历史流 (仅在非点击实时运行状态下展示,避免与实时渲染区域内容重复)
if not start_auto_btn:
if st.session_state.get("auto_console_log"):
st.markdown("##### 🖥️ 全自动流程实时思维链控制台日志")
with st.container():
st.markdown(f'{st.session_state.auto_console_log}
', unsafe_allow_html=True)
# 强持久化按顺序展现全自动产生的各轮科学假说与 Reviewer #2 审查打分卡片
if st.session_state.get("auto_rounds_history"):
st.markdown("---")
st.markdown("## 📊 全自动发现流自进化轮次记录 (按顺序排列)")
for item in st.session_state.auto_rounds_history:
r_num = item.get("round", 1)
hypo_data = item.get("hypo")
rev_data = item.get("review")
if hypo_data and isinstance(hypo_data, dict):
render_hypothesis_card(hypo_data, f"💡 [第 {r_num} 轮] 全自动推导产生的科学假说:")
if rev_data and isinstance(rev_data, dict):
render_review_card(rev_data, f"🔍 [第 {r_num} 轮] Reviewer #2 对抗性同行审查打分结果:")
st.markdown("---")
elif st.session_state.get("auto_hypo"):
st.markdown("---")
render_hypothesis_card(st.session_state.auto_hypo, "💡 全自动推导产生的科学假说")
if st.session_state.get("auto_review"):
render_review_card(st.session_state.auto_review)
if st.session_state.get("auto_final_report"):
st.markdown("---")
report_text = st.session_state.auto_final_report
first_line = report_text.strip().split('\n')[0].replace('#', '').strip()
clean_title = first_line.replace('《', '').replace('》', '').strip()
if not clean_title or len(clean_title) > 60:
clean_title = "全自动科研发现研究计划报告"
st.markdown(f'### 📄 《{clean_title}》')
render_markdown_with_mermaid(report_text)
file_name_clean = f"Auto_{clean_title.replace(' ', '_')}.md"
st.download_button(
f"⬇️ 下载《{clean_title}》Markdown 文档",
data=report_text,
file_name=file_name_clean,
mime="text/markdown",
use_container_width=True
)