diff --git a/pipeline_bidding/bid_common.py b/pipeline_bidding/bid_common.py index 7a21ead..824bda1 100644 --- a/pipeline_bidding/bid_common.py +++ b/pipeline_bidding/bid_common.py @@ -47,6 +47,7 @@ BLOCKING_HT_TYPES = (HT_TENDER_FILE, HT_HUMAN_DOCS) # 任务未终结状态(判断「是否已有在办任务」用) OPEN_TASK_STATES = ("submitted", "running", "review", "qc_review") +# failed 任务单独处理:角色 agent 执行失败(如机构未配 LLM)→ 对账器不重复造任务,抛人工介入 # ── 门限默认值(可用 appbase params 表覆盖)── DEFAULT_PARAMS = { diff --git a/pipeline_bidding/bid_flow.py b/pipeline_bidding/bid_flow.py index 672949b..b39ca33 100644 --- a/pipeline_bidding/bid_flow.py +++ b/pipeline_bidding/bid_flow.py @@ -80,7 +80,31 @@ async def reconcile_project(sor, project_id, project_name=""): acts = [] pname = project_name or project_id - # ── 0. 招标文件到位则自动关闭上传任务,再判阻塞门禁 ── + # ── 0a. 失败任务优先处理:角色 agent 执行失败(如机构未配置 LLM)→ + # 抛人工介入,且不再造新任务(防每轮对账重复造)。 + n_failed = await _count( + sor, "SELECT COUNT(*) AS c FROM pipeline_tasks WHERE tenant_id=${p}$ " + "AND pipeline_id='role_task' AND state='failed'", {"p": project_id}) + if n_failed > 0: + ht = await find_human_task(sor, project_id, "general", status="pending") + if not ht: + tasks = await sor.sqlExe( + "SELECT role, title FROM pipeline_tasks WHERE tenant_id=${p}$ " + "AND pipeline_id='role_task' AND state='failed' LIMIT 5", {"p": project_id}) + await sor.sqlExe("COMMIT", {}) + tlist = "\n".join("- %s: %s" % (getattr(r, "role", ""), getattr(r, "title", "")) + for r in (tasks or [])) + await create_human_task( + sor, project_id, "general", + "投标任务执行失败,需人工处理(%d 个)" % n_failed, + ("以下角色任务执行失败,对账器已暂停自动派发:\n" + tlist + + "\n\n常见原因:机构未配置 LLM 模型(项目缺省模型/角色模型未设置)、" + "模型调用超时、任务内容异常。\n请处理后用 reset_task_retry 恢复,或取消任务。"), + assignee_role="owner.superuser") + await sor.sqlExe("COMMIT", {}) + return ["blocked: %d 个任务执行失败,已抛人工介入,暂停派发" % n_failed] + + # ── 0b. 招标文件到位则自动关闭上传任务,再判阻塞门禁 ── await _auto_close_file_upload_task(sor, project_id) if await has_pending_blocking_human_task(sor, project_id): return ["blocked: 有待办人类任务(等招标文件/等人类资料/等审批),本轮不派发"]