"""任务启动前准备度检查(交付报告 P1-1,Hermes 融合增强)。 运行前一次性检查:模型 Provider、生产 Agent、知识库索引、可用知识片段、 QC/Composer Agent、点数余额;UI 在启动前显示阻断原因,避免开跑后中途失败。 """ from __future__ import annotations from sqlmodel import Session, select from .models import Agent, Campaign, KnowledgeBase, Segment from .billing import point_state from .agents.default_composer import resolve_composer_agent def _provider_ready(session: Session, agent: Agent) -> tuple[bool, str]: """Agent 的模型链路是否可用(显式配置或全局默认)。""" try: from .agents.coordinator import _provider_model _provider_model(session, agent) return True, "" except Exception as exc: return False, f"Agent「{agent.name}」模型不可用:{str(exc)[:140]}" def readiness_report(session: Session, campaign: Campaign) -> dict: """返回 {ready, blockers[], warnings[]},供 API 与任务页直接展示。""" blockers: list[str] = [] warnings: list[str] = [] segments = session.exec(select(Segment).where(Segment.campaign_id == campaign.id)).all() if not segments: blockers.append("任务还没有内容段——拆解失败或导图为空。请重新创建任务并粘贴导图文本(或用图片 OCR)") return {"ready": False, "blockers": blockers, "warnings": warnings} # 1. 每段必须有可用的生产 Agent + 模型链路 agent_ids = {seg.agent_id for seg in segments if seg.agent_id} if not agent_ids: blockers.append("内容段没有挂接任何生产 Agent") for agent_id in sorted(agent_ids): agent = session.get(Agent, agent_id) if not agent: blockers.append(f"内容段引用的 Agent #{agent_id} 不存在") continue if agent.status != "active": blockers.append(f"Agent「{agent.name}」已停用,请先在 Agent 管理中启用") continue ok, reason = _provider_ready(session, agent) if not ok: blockers.append(reason) # 2. 生产 Agent 的知识库:未索引的警告;完全没挂接的阻断 for agent_id in sorted(agent_ids): agent = session.get(Agent, agent_id) if not agent or agent.status != "active" or agent.role != "production": continue kb_ids = list(agent.kb_ids or []) if not kb_ids: blockers.append(f"生产 Agent「{agent.name}」未挂接知识库,无法保证引用可溯源") continue for kb_id in kb_ids: kb = session.get(KnowledgeBase, kb_id) if not kb: blockers.append(f"Agent「{agent.name}」挂接的知识库 #{kb_id} 已被删除") elif kb.index_status not in ("ready", "empty"): warnings.append(f"知识库「{kb.name}」索引状态为 {kb.index_status},检索可能为空") # 3. QC 必须存在且启用;组合 Agent 是系统内置阶段,不属于用户资源。 qc_rows = session.exec(select(Agent).where(Agent.role == "qc")).all() if not qc_rows: blockers.append("缺少质检 Agent,流水线无法完成质检") elif not any(r.status == "active" for r in qc_rows): blockers.append("质检 Agent 全部处于停用状态") composer = resolve_composer_agent(session) composer_ok, composer_reason = _provider_ready(session, composer) if not composer_ok: blockers.append(f"系统组合 Agent 模型不可用:{composer_reason.split(':', 1)[-1]}") # 4. 点数余额:至少要能撑过第一轮模型调用 owner_id = int(campaign.owner_user_id or 0) org_id = int(campaign.organization_id or 1) if owner_id: points = point_state(session, user_id=owner_id, organization_id=org_id) per_token = int(points.get("points_per_token") or 10) balance = int(points.get("points") or 0) token_eq = int(points.get("token_equivalent") or 0) if balance < per_token: blockers.append( f"点数余额不足({balance} 点数,至少需要 {per_token} 点数)," "请联系管理员在用量与账单中增加点数") elif token_eq < 2000: warnings.append( f"点数偏低(约 {token_eq} Token),长任务可能中途暂停") return {"ready": not blockers, "blockers": blockers, "warnings": warnings} __all__ = ["readiness_report"]