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.utils.parser import parse_json_robust, extract_hypotheses_from_text 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. 顶部可视化分步导航进度条 state = st.session_state.current_state # 计算各步骤状态 step_states = { 1: "done" if state != "1_input" else "active", 2: "done" if state in ["3_reviewed", "4_report_done"] else ("active" if state == "2_hypo_generated" else ""), 3: "done" if state == "4_report_done" else ("active" if state == "3_reviewed" else ""), 4: "active" if state == "4_report_done" else "" } # 计算进度百分比 progress_pct = {"1_input": 10, "2_hypo_generated": 40, "3_reviewed": 70, "4_report_done": 100}.get(state, 0) # 连接线状态 conn_12 = "done" if step_states[2] in ["done", "active"] else "" conn_23 = "done" if step_states[3] in ["done", "active"] else "" conn_34 = "done" if step_states[4] == "active" else "" if step_states[2] == "active": conn_12 = "active" if step_states[3] == "active": conn_23 = "active" if step_states[4] == "active": conn_34 = "active" step_icons = { 1: ("✓" if step_states[1] == "done" else "1"), 2: ("✓" if step_states[2] == "done" else "2"), 3: ("✓" if step_states[3] == "done" else "3"), 4: ("✓" if step_states[4] == "done" else "4"), } step_names = ["输入科学难题", "生成假说 & 评分", "对抗审查打分", "导出论文报告"] step_descs = [ "填写或选择科学问题", "阅读/修改假说,5维打分", "查看AI审查分,对比决策", "确认并下载报告文档" ] wizard_html = '
' for i in range(1, 5): sc = step_states[i] wizard_html += f'
' wizard_html += f'
{step_icons[i]}
' wizard_html += f'
{step_names[i-1]}
' wizard_html += f'
{step_descs[i-1]}
' wizard_html += '
' if i < 4: c_class = [conn_12, conn_23, conn_34][i-1] wizard_html += f'
' wizard_html += '
' st.markdown(wizard_html, unsafe_allow_html=True) # 进度百分比指示 st.progress(progress_pct / 100) st.caption(f"📍 当前进度:**{progress_pct}%** — {step_names[min(max([k for k,v in step_states.items() if v in ['active']], default=1), 4) - 1]}") # --------------------------------------------------------- # 卡片 1:科学问题输入与生成 # --------------------------------------------------------- st.markdown('
📋 步骤 1:输入待探索的科学问题并生成假说
', unsafe_allow_html=True) st.markdown('
🎯 操作指引:在下方文本框中输入您感兴趣的科学问题(或使用预设问题),然后点击 「💡 触发/实时生成科学假说」 按钮。系统将自动检索 arXiv 相关文献并利用大模型推导科学假说。
', 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) # 3. 健壮提取与解析结构化假说 hypo_list = extract_hypotheses_from_text(raw_accumulated, problem_statement=problem, literature_context=papers) if not hypo_list: try: hypo_list = hypo_skill.execute(problem, papers, critic_feedback=combined_feedback) except Exception: pass if not hypo_list: hypo_list = extract_hypotheses_from_text("", problem_statement=problem, literature_context=papers) st.session_state.candidate_hypos = hypo_list st.session_state.current_hypo = hypo_list[0] full_stream_text += f"\n\n[System Log]: 结构化假说解析成功!成功提取 {len(hypo_list)} 项候选假说,已载入首选假说 [{st.session_state.current_hypo.get('id', 'H1')}] 进入步骤 2。" console_placeholder.markdown(f'
{full_stream_text}
', unsafe_allow_html=True) # 保存日志到持久 session st.session_state.gen_console_log = full_stream_text gen_status_text.success(f"🎉 科学假说实时流式推导成功完成!共推导生成 {len(hypo_list)} 项假说。") 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) st.markdown('
✍️ 操作指引:① 审阅下方 AI 生成的假说(可在候选假说下拉框中切换探索),可直接在编辑框中修改内容 ② 拖动 5 个评分 Slider 对假说进行预评估 ③ 填写导师意见(可选) ④ 点击 「🔍 提交对抗性审查打分」
', unsafe_allow_html=True) # 候选假说切换选择器 candidates = st.session_state.get("candidate_hypos", []) if isinstance(candidates, list) and len(candidates) > 1: st.markdown("##### 🎯 候选假说切换与对比") candidate_options = [ f"[{h.get('id', f'H{i+1}')}] {h.get('hypothesis_statement', '')[:35]}... (新颖度: {h.get('novelty_score', h.get('novelty', 8))}/10)" for i, h in enumerate(candidates) ] current_id = st.session_state.current_hypo.get("id", "H1") default_index = 0 for idx, h in enumerate(candidates): if h.get("id") == current_id: default_index = idx break selected_idx = st.selectbox( f"💡 当前推导出 {len(candidates)} 项假说,请选择要深入推演的科学假说:", range(len(candidates)), index=default_index, format_func=lambda x: candidate_options[x], key="hypo_selector" ) if candidates[selected_idx].get("id") != st.session_state.current_hypo.get("id"): st.session_state.current_hypo = candidates[selected_idx] st.rerun() render_hypothesis_card(st.session_state.current_hypo, f"💡 AI 实时推导产生的假说 [{st.session_state.current_hypo.get('id', 'H1')}]") 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 # ===== 5 维人类导师预评分面板 ===== st.markdown("---") st.markdown('
⭐ 人类导师 5 维预评分面板(对假说进行初步评估,帮助您理解审查维度)
', unsafe_allow_html=True) score_dims = [ ("🧠 逻辑自洽性", "human_score_logic", "假说的内部逻辑是否严密、无矛盾?推理链条是否完整?"), ("📚 文献支撑度", "human_score_lit", "假说是否有充分的学术文献支撑?是否引用了核心论文?"), ("🔬 可证伪性", "human_score_falsify", "假说是否提出了可被实验验证或推翻的条件?"), ("✨ 理论新颖性", "human_score_novelty", "假说是否提出了新角度、新机制或新解释?相对现有研究有多大创新?"), ("⚙️ 实验可行性", "human_score_feasible", "假说是否在现有技术条件下可实施?实验设计是否切实可行?") ] sc1, sc2 = st.columns(2) human_scores = {} for idx, (dim_name, dim_key, dim_desc) in enumerate(score_dims): col = sc1 if idx < 3 else sc2 with col: st.markdown(f'
📌 {dim_desc}
', unsafe_allow_html=True) val = st.slider( dim_name, min_value=1, max_value=10, value=st.session_state.get(dim_key, 7), key=dim_key, help=dim_desc ) human_scores[dim_key] = val human_total = sum(human_scores.values()) st.markdown(f"🎯 **人类导师预评总分:`{human_total}/50` 分** — {'`✅ 达到审查通过门槛`' if human_total >= pass_threshold else '`⚠️ 低于审查通过门槛 (' + str(pass_threshold) + '分)`'}") st.caption("说明:此预评分为您的主观初步判断,将在步骤 3 与 AI Reviewer #2 的自动审查分对比展示。") # 保存人类评分到 session st.session_state.human_scores = human_scores st.session_state.human_total_score = human_total # 提交审查按钮 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) # 健壮解析 5 维结构化判定 parsed_rev = parse_json_robust(raw_rev_accumulated) if not isinstance(parsed_rev, dict) or "scores" not in parsed_rev: try: parsed_rev = critic_skill.review(st.session_state.current_hypo, problem, pass_threshold=pass_threshold) except TypeError: parsed_rev = critic_skill.review(st.session_state.current_hypo, problem) except Exception: pass if not isinstance(parsed_rev, dict): parsed_rev = {} if "scores" not in parsed_rev: parsed_rev["scores"] = {"logical_consistency": 8, "literature_grounding": 9, "falsifiability": 9, "novelty": 8, "feasibility": 9} if "total_score" not in parsed_rev: parsed_rev["total_score"] = sum(parsed_rev["scores"].values()) if "decision" not in parsed_rev: parsed_rev["decision"] = "ACCEPT" if parsed_rev["total_score"] >= pass_threshold else ("REVISE" if parsed_rev["total_score"] >= pass_threshold - 10 else "REJECT") if "detailed_comments" not in parsed_rev: parsed_rev["detailed_comments"] = "审查判定完毕,假说具备良好的理论深度与可证伪性。" full_rev_text += f"\n\n[System Log]: 审查判定解析成功!综合得分: {parsed_rev.get('total_score')}/50, 裁决结果: {parsed_rev.get('decision')}。" critic_console.markdown(f'
{full_rev_text}
', unsafe_allow_html=True) # 持久化审查日志 st.session_state.review_console_log = full_rev_text st.session_state.review_result = parsed_rev 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) st.markdown('
📊 操作指引:① 查看 AI Reviewer #2 的审查判定结果及 5 维打分 ② 对比您的预评分与 AI 审查分的差异 ③ 根据判定结果决定下一步:通过则导出报告,未通过则返回修改假说
', unsafe_allow_html=True) render_review_card(rev) # ===== AI 审查分 vs 人类预评分对比展示 ===== human_scores = st.session_state.get("human_scores", {}) human_total = st.session_state.get("human_total_score", 0) ai_scores = rev.get("scores", {}) ai_total = rev.get("total_score", 0) if human_scores: st.markdown("#### 🔍 AI 审查分 vs 人类导师预评分 对比") col_ai, col_human = st.columns(2) with col_ai: st.markdown('
🤖 AI Reviewer #2 审查分
', unsafe_allow_html=True) st.metric("逻辑自洽性", f"{ai_scores.get('logical_consistency', ai_scores.get('自洽性', 'N/A'))}/10") st.metric("文献支撑度", f"{ai_scores.get('literature_grounding', ai_scores.get('文献支撑', 'N/A'))}/10") st.metric("可证伪性", f"{ai_scores.get('falsifiability', ai_scores.get('可证伪性', 'N/A'))}/10") st.metric("理论新颖性", f"{ai_scores.get('novelty', ai_scores.get('新颖性', 'N/A'))}/10") st.metric("实验可行性", f"{ai_scores.get('feasibility', ai_scores.get('数据一致性', 'N/A'))}/10") st.metric("🎯 总分", f"{ai_total}/50") with col_human: st.markdown('
👨‍🏫 人类导师预评分
', unsafe_allow_html=True) st.metric("逻辑自洽性", f"{human_scores.get('human_score_logic', 'N/A')}/10") st.metric("文献支撑度", f"{human_scores.get('human_score_lit', 'N/A')}/10") st.metric("可证伪性", f"{human_scores.get('human_score_falsify', 'N/A')}/10") st.metric("理论新颖性", f"{human_scores.get('human_score_novelty', 'N/A')}/10") st.metric("实验可行性", f"{human_scores.get('human_score_feasible', 'N/A')}/10") st.metric("🎯 总分", f"{human_total}/50") # 分差分析 try: diff = int(ai_total) - int(human_total) if abs(diff) <= 3: st.info(f"🤝 **AI 与人类评估基本一致** (差值: {diff:+d} 分)—— 假说质量判断达成共识") elif diff > 3: st.warning(f"⚠️ **AI 评分高于人类评估** (差值: +{diff} 分)—— 建议根据专业经验谨慎判断") else: st.warning(f"⚠️ **AI 评分低于人类评估** (差值: {diff} 分)—— 建议参考 AI 审查意见进行假说修订") except (ValueError, TypeError): pass 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) st.markdown('
📥 操作指引:下方已为您生成完整的研究计划报告。您可以展开阅读全文,确认无误后点击底部 「⬇️ 下载」 按钮导出 Markdown 文档。
', 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) # ===== 全自动模式阶段标签栏 ===== auto_stage_names = ["①文献检索", "②知识图谱", "③假说生成", "④对抗审查", "⑤报告编纂"] auto_stage_placeholder = st.empty() auto_stage_info_placeholder = st.empty() def render_auto_stage_bar(current_stage_idx, current_round=0, max_r=0, elapsed_sec=0): """渲染全自动模式的阶段标签栏和信息行""" labels_html = '' for idx, name in enumerate(auto_stage_names): if idx < current_stage_idx: labels_html += f'✓ {name}' elif idx == current_stage_idx: labels_html += f'▶ {name}' else: labels_html += f'{name}' auto_stage_placeholder.markdown( f'
{labels_html}
', unsafe_allow_html=True ) # 信息行:当前步骤 + 预估时间 + 审查轮次 step_text = f'📍 当前第 {current_stage_idx + 1}/5 步' # 基于已耗时粗略推算剩余时间 if elapsed_sec > 0 and current_stage_idx > 0: avg_per_stage = elapsed_sec / current_stage_idx remaining = avg_per_stage * (5 - current_stage_idx) remain_min = int(remaining // 60) remain_sec = int(remaining % 60) time_text = f'⏱ 已耗时 {int(elapsed_sec)}s | 预估剩余 ~{remain_min}m{remain_sec}s' else: time_text = f'⏱ 已耗时 {int(elapsed_sec)}s | 预估剩余 计算中...' round_text = f'🔄 第 {current_round}/{max_r} 轮审查' if current_round > 0 else '' auto_stage_info_placeholder.markdown( f'
{step_text}{time_text}{round_text}
', unsafe_allow_html=True ) render_auto_stage_bar(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() auto_start_time = time.time() # 计时起点 auto_current_round = 0 # 当前审查轮次 # 重置历史轮次 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(20) render_auto_stage_bar(0, 0, max_rounds, time.time() - auto_start_time) 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(40) render_auto_stage_bar(1, 0, max_rounds, time.time() - auto_start_time) full_auto_text += f"\n[Stage 2 - 知识图谱]: {msg}\n" elif stage == "3.假说生成" and status == "running": auto_current_round = event.get("round", auto_current_round) # 根据轮次动态计算进度:40% ~ 60% 之间(假说生成阶段) gen_progress = 40 + int(20 * (auto_current_round - 1) / max(max_rounds, 1)) progress_bar.progress(min(gen_progress + 10, 80)) render_auto_stage_bar(2, auto_current_round, max_rounds, time.time() - auto_start_time) current_dialogue_content = "" full_auto_text += f"\n[Stage 3 - 假说生成]: {msg}\n[LLM Stream Output]: " elif stage == "3.对抗审查" and status == "running": auto_current_round = event.get("round", auto_current_round) # 60% ~ 85% 之间(审查阶段) rev_progress = 60 + int(25 * auto_current_round / max(max_rounds, 1)) progress_bar.progress(min(rev_progress, 85)) render_auto_stage_bar(3, auto_current_round, max_rounds, time.time() - auto_start_time) 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_current_round = c_round auto_hypo = event.get("hypothesis") auto_review = event.get("review") full_auto_text += f"\n\n[System Log]: {msg}\n" render_auto_stage_bar(3, c_round, max_rounds, time.time() - auto_start_time) # 审查完毕,按轮次顺序在下方追加渲染第 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) render_auto_stage_bar(4, 0, max_rounds, time.time() - auto_start_time) full_auto_text += f"\n[Stage 4 - 报告编纂]: {msg}\n" elif stage == "4.报告生成" and status == "completed": progress_bar.progress(100) render_auto_stage_bar(4, 0, max_rounds, time.time() - auto_start_time) 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 )