diff --git a/README.md b/README.md index 261aea8..a942ee1 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,13 @@ ```json { - "run_id": "7ba01d0cc4ad402793938835654fbca5", + "run_id": "37155251926d4165a7b7af68e8967f64", "target_repo": "fastapi/fastapi", "target_commit": "ecace740f3eaccb1aba152cf1de79477095c56f4", "completed_tasks": 7, "accepted_score": 1.0, - "check_count": 12, + "check_count": 14, + "quality_consensus_rounds": 2, "failed_checks": [] } ``` diff --git a/docs/ACADEMIC_STANDARD_TEST_REPORT.zh-CN.md b/docs/ACADEMIC_STANDARD_TEST_REPORT.zh-CN.md index 4c2ab51..64ae9fd 100644 --- a/docs/ACADEMIC_STANDARD_TEST_REPORT.zh-CN.md +++ b/docs/ACADEMIC_STANDARD_TEST_REPORT.zh-CN.md @@ -12,9 +12,9 @@ | S02 | 单元回归 | 内存蜂群闭环、Azure 资源边界、配置脱敏、NewAPI mock 行为 | `python3 -B -m unittest discover -s tests` | | S03 | 连续推理链路 | 每一步必须承接上一步 marker,断链必须失败 | `tests/test_standard_scenarios.py` | | S04 | 依赖边界 | MVP 不把 NATS/Cosmos 当成必需依赖 | `tests/test_standard_scenarios.py` | -| S05 | 最终收敛评分 | 最终 STEP-07 输出必须优于中间步骤 | `tests/test_standard_scenarios.py` | +| S05 | 最终收敛评分与质量门 | 最终 STEP-07 输出必须优于中间步骤;拒答、角色拒绝、偏题和交接断裂必须被扣分 | `tests/test_standard_scenarios.py` | | S06 | 失败注入 | 失败任务要标记失败、产生负信息素,其他任务仍可收敛 | `tests/test_standard_scenarios.py` | -| S07 | live 外部 GitHub 代码推理 | 真实连接 Azure PostgreSQL、Redis、Blob 和 NewAPI,对 `fastapi/fastapi` 固定 commit 跑 7 步代码推理链 | `examples/run_continuous_reasoning_acceptance.py` | +| S07 | live 外部 GitHub 代码推理 | 真实连接 Azure PostgreSQL、Redis、Blob 和 NewAPI,对 `fastapi/fastapi` 固定 commit 跑 7 步代码推理链,并通过质量门、fallback 补救和多轮质量共识 | `examples/run_continuous_reasoning_acceptance.py` | | S08 | 模型 I/O 报告审计 | 验证报告包含场景、输入、输出、接手证据,且无明显真实密钥样式 | `tests/test_model_io_report_audit.py` | | B01 | 单 Agnet 故障隔离 | 一个 Agnet 崩溃不导致整体失败 | `tests/test_swarm_behavior_academic.py` | | B02 | 群体涌现 | 多个弱局部信号通过共享状态聚合成更强整体结果 | `tests/test_swarm_behavior_academic.py` | @@ -67,20 +67,20 @@ S07 需要本地被 git 忽略的 `.env`,其中包含 Azure PostgreSQL、Redis | 命令 | 本轮结果 | 说明 | | --- | --- | --- | | `python3 -B -m py_compile swarm_minimal/*.py examples/*.py tests/*.py` | PASS | 无输出,表示所有模块编译通过 | -| `python3 -B -m unittest discover -s tests` | PASS | 29 个测试通过 | +| `python3 -B -m unittest discover -s tests` | PASS | 32 个测试通过 | | `python3 -B -m unittest tests.test_markov_process_properties` | PASS | 3 个马尔可夫适配性测试通过 | | `python3 -B -m unittest tests.test_model_io_report_audit` | PASS | 2 个模型 I/O 报告审计测试通过 | | `python3 -u -B examples/run_swarm_behavior_acceptance.py` | PASS | B01-B04 全部通过 | | `python3 -u -B examples/run_swarm_vs_traditional_benchmark.py` | PASS | 蜂群总归一化分 0.9175,传统基线 0.1958,ratio 4.69 | | `python3 -u -B examples/run_consensus_convergence_acceptance.py` | PASS | 2 轮收敛,接受 `lease_based_pg_queue` | | `python3 -u -B examples/run_academic_standard_evaluation.py` | PASS | A01-A05 本地学术化门禁全部通过,检测到 `examples/.env` 可用于 live 测试 | -| `python3 -u -B examples/run_standard_scenario_acceptance.py` | PASS | S01-S08 全部通过;S07 外部 FastAPI 代码场景完成 7 个 live 任务,12 个检查无失败,S08 报告审计通过 | +| `python3 -u -B examples/run_standard_scenario_acceptance.py` | PASS | S01-S08 全部通过;S07 外部 FastAPI 代码场景完成 7 个 live 任务,14 个检查无失败,S08 报告审计通过 | 最新 S07 外部 GitHub live 证据: ```json { - "run_id": "7ba01d0cc4ad402793938835654fbca5", + "run_id": "37155251926d4165a7b7af68e8967f64", "target_repo": "fastapi/fastapi", "target_commit": "ecace740f3eaccb1aba152cf1de79477095c56f4", "completed_tasks": 7, @@ -91,8 +91,9 @@ S07 需要本地被 git 忽略的 `.env`,其中包含 Azure PostgreSQL、Redis "claude-sonnet-4-6" ], "failed_checks": [], - "check_count": 12, - "artifact_path": "swarm-runs/7ba01d0cc4ad402793938835654fbca5/result.json" + "check_count": 14, + "quality_consensus_rounds": 2, + "artifact_path": "swarm-runs/37155251926d4165a7b7af68e8967f64/result.json" } ``` @@ -115,7 +116,9 @@ S07 需要本地被 git 忽略的 `.env`,其中包含 Azure PostgreSQL、Redis | 能力匹配任务 claim | `InMemorySwarmStore.claim_next` | Agnet 只领取能力匹配的 pending 任务 | | 信息素排序 | `InMemorySwarmStore.claim_next` | pending 任务按 pheromone score 从高到低排序 | | 正/负反馈更新 | `complete_task` / `fail_task` | 成功加分,失败扣分 | -| 最高分收敛 | `converge` | 在完成任务中选择最高 score 作为最终输出 | +| 最高分收敛 | `converge` | 在完成任务中选择最高 score 作为最终输出,但 S07 已在此之前增加质量门和多轮质量共识 | +| 输出质量评分 | `assess_output_quality` / `score_output` | 拒答、角色拒绝、目标漂移、交接断裂和缺少 FastAPI 技术语义会被扣分 | +| 重试与 fallback 补救 | `chat_with_fallback` | 当前模型输出不合格时先重试,再切换 fallback 模型接手当前步骤 | | 多轮加权共识 | `ConsensusSwarm.run` | 按 agent weight 和 confidence 累积分数 | | 分数蒸发 | `ConsensusSwarm._evaporate_scores` | 每轮按 evaporation 衰减历史候选分 | | 动态模型发现 | `discover_newapi_models` | 从 NewAPI 兼容端点发现模型 | diff --git a/docs/AGENT_SWARM_QUALITY_STANDARD.zh-CN.md b/docs/AGENT_SWARM_QUALITY_STANDARD.zh-CN.md index 81c9591..d232090 100644 --- a/docs/AGENT_SWARM_QUALITY_STANDARD.zh-CN.md +++ b/docs/AGENT_SWARM_QUALITY_STANDARD.zh-CN.md @@ -34,7 +34,7 @@ | ID | 质量项 | 合格标准 | 当前证据 | | --- | --- | --- | --- | | AQS-01 任务理解 | Agent 输出必须回应分配给它的具体子任务,不泛泛回答 | S07 每步 task.input 明确当前步骤和要求 | -| AQS-02 指令遵循 | 输出必须包含指定 marker、约束、文件路径、验收命令或风险项 | `score_output()` 和 S07 12 项检查 | +| AQS-02 指令遵循 | 输出必须包含指定 marker、约束、文件路径、验收命令或风险项 | `score_output()`、质量门和 S07 14 项检查 | | AQS-03 上下文忠实 | 除第一步外,Agent 必须引用上一阶段 marker 和 summary | S07 `step_markers_and_previous_links` | | AQS-04 可验证输出 | 输出必须能被规则检查,不能只有自然语言主张 | S03/S05/S07 checks | | AQS-05 模型选择可控 | 多 Agent 不写死 `NEWAPI_MODEL`,必须从模型列表发现并选择 | S07 `three_distinct_models_from_discovery` | @@ -45,6 +45,7 @@ | AQS-10 交接准备度 | 输出要给下一个 Agent 留出摘要、风险和下一步 | S07 prompt/output 中强制“下一步/交接” | | AQS-11 外部依赖真实性 | live 测试必须真实连 PostgreSQL、Redis、Blob、NewAPI,并把代码任务指向外部 GitHub 项目 | S07 external GitHub live PASS | | AQS-12 人类审计友好 | 最终报告必须能回答:任务、输入、输出、接手、结果、未满足项 | S08 + `MODEL_AGNET_IO_REPORT.zh-CN.md` 和本文件 | +| AQS-13 输出质量恢复 | 拒答、角色拒绝、偏题或目标漂移必须被扣分,并触发重试或 fallback 模型接手 | S07 `all_outputs_pass_quality_gate` + fallback 单元测试 | ## 4. 蜂群 Agent 质量标准 SW-AQS @@ -58,13 +59,14 @@ | SW-AQS-06 间接协作 | 后续 Agent 通过 shared_state/summary/pheromone 感知前序结果 | S07 chain summary 和 cursor | | SW-AQS-07 handoff 连续性 | 交接必须有 from->to/previous->current edge,并保留 payload | B04 与 S07 `chain_edge` | | SW-AQS-08 广播/事件 | claim、done、converged 等事件进入 stream/outbox | S07 Redis Stream 事件数检查 | -| SW-AQS-09 收敛条件 | 不能只说“跑完”;必须检查任务、分数、状态、artifact、事件和内容质量 | S07 12 项 checks | +| SW-AQS-09 收敛条件 | 不能只说“跑完”;必须检查任务、分数、状态、artifact、事件、内容质量和多轮质量共识 | S07 14 项 checks | | SW-AQS-10 鲁棒性 | 单个 Agent 失败不应吞掉整体状态,失败要可观测 | B01/C01/S06 | | SW-AQS-11 涌现性 | 群体聚合结果能超过单个局部强信号 | B02/C02 | | SW-AQS-12 传统基线对比 | 必须和单 Agent/FIFO/无共享状态基线比较 | C01-C04 | | SW-AQS-13 live 外部闭环 | 不能只 mock,至少一次真实资源闭环 | S07 PASS | | SW-AQS-14 马尔可夫式状态 | 给定完整当前状态,下一步工程转移由当前状态决定 | M01-M03,非严格 MDP | -| SW-AQS-15 扩缩容与并发 | 3/5/7 Agent 并发自主 claim 下仍稳定 | 仍待补充 | +| SW-AQS-15 多轮质量共识 | 最终接受结果必须经过交接连续性、输出质量和最终收敛三类审查 Agnet 的多轮共识 | S07 `multi_round_quality_consensus_accepts_chain` | +| SW-AQS-16 扩缩容与并发 | 3/5/7 Agent 并发自主 claim 下仍稳定 | 仍待补充 | ## 5. S07 live 测试任务定义 @@ -116,11 +118,15 @@ - `primary_model` - `used_model` - `model_selection=discovered_models_not_NEWAPI_MODEL` -3. 当前 Agnet 完成后,系统写入: +3. 当前输出会先经过质量门: + - 拒答、角色拒绝、偏题、本仓库漂移会被重罚。 + - 不满足当前 STEP、前序 STEP、外部仓库、commit、FastAPI 技术语义和交接要求时,会触发重试或 fallback 模型接手。 +4. 当前 Agnet 完成后,系统写入: - `chain:{run_id}:{STEP}:summary` - `chain:{run_id}:cursor` - `chain:{run_id}:edge:->=done` -4. 下一个 Agnet 读取上一步 summary 和 marker 后继续执行。 +5. 下一个 Agnet 读取上一步 summary 和 marker 后继续执行。 +6. 最终接受结果还必须通过三类审查 Agnet 的多轮质量共识;第一轮只能形成候选,第二轮或之后达成接受才算通过。 因此“接手”不是人工解释,也不是模型凭空猜测,而是通过 PostgreSQL shared_state 中的 summary、cursor、edge 字段完成。 @@ -130,12 +136,13 @@ ```json { - "run_id": "7ba01d0cc4ad402793938835654fbca5", + "run_id": "37155251926d4165a7b7af68e8967f64", "target_repo": "fastapi/fastapi", "target_commit": "ecace740f3eaccb1aba152cf1de79477095c56f4", "completed_tasks": 7, "accepted_score": 1.0, - "check_count": 12, + "check_count": 14, + "quality_consensus_rounds": 2, "failed_checks": [] } ``` @@ -143,5 +150,5 @@ 结论: - 按 AQS 通用 Agent 标准:当前最小测试通过。 -- 按 SW-AQS 蜂群 Agent 标准:关键闭环通过,但“并发自主 claim、广播驱动决策、3/5/7 扩缩容压测”仍是下一阶段。 +- 按 SW-AQS 蜂群 Agent 标准:关键闭环、质量门、fallback 补救和多轮质量共识通过,但“并发自主 claim、广播驱动决策、3/5/7 扩缩容压测”仍是下一阶段。 - 所以它满足“最小蜂群 Agent 质量标准 v1”,还不等于完整生产级蜂群平台认证。 diff --git a/docs/AGNET_FRAMEWORK_INPUT_OUTPUT_LOGIC.zh-CN.md b/docs/AGNET_FRAMEWORK_INPUT_OUTPUT_LOGIC.zh-CN.md index a969cc9..dbdb709 100644 --- a/docs/AGNET_FRAMEWORK_INPUT_OUTPUT_LOGIC.zh-CN.md +++ b/docs/AGNET_FRAMEWORK_INPUT_OUTPUT_LOGIC.zh-CN.md @@ -4,7 +4,7 @@ ## 一句话逻辑 -当前框架不是让一个模型一次性回答完整问题,而是把一个复杂目标拆成连续的 Agnet 输入。每个 Agnet 接收“当前任务输入”和“上一位 Agnet 的输出摘要”,生成自己的输出,再把这个输出变成下一位 Agnet 的输入条件。最后,框架根据每一步输出的完成度、连续性和评分,选择一个最终可接受输出作为收敛结果。 +当前框架不是让一个模型一次性回答完整问题,而是把一个复杂目标拆成连续的 Agnet 输入。每个 Agnet 接收“当前任务输入”和“上一位 Agnet 的输出摘要”,生成自己的输出,再把这个输出变成下一位 Agnet 的输入条件。最后,框架先做输出质量门和多轮质量共识,再根据评分选择最终可接受输出作为收敛结果。 ## 被测场景是什么 @@ -26,6 +26,8 @@ 第四类是验收输入:每一步都必须产生自己的阶段标记,除第一步外必须承接前一步,最终输出必须能体现不变量、依赖图、复杂度、反例、修正策略、文件级计划和验收判断。 +第五类是质量门输入:如果模型输出拒答、角色拒绝、偏题、转向当前仓库或没有承接前一步,框架会把它判为低质量输出。低质量输出不能直接进入通过结论,而是触发重试或切换模型。 + ## Agnet 的分工如何发生 当前框架把一个完整复杂任务拆成七个连续 Agnet。每个 Agnet 的输入和输出关系如下。 @@ -33,14 +35,14 @@ | Agnet | 输入 | 期望输出 | 实际输出评价 | | --- | --- | --- | --- | | Agnet 01 | 起始目标、外部项目范围、禁止自测边界 | 问题边界、不变量、风险、下一步交接摘要 | 完成了边界定义,明确目标是 FastAPI 外部项目,并给出下一步依赖图分析方向 | -| Agnet 02 | Agnet 01 的输出摘要,以及建立依赖图的任务 | 路由、依赖注入、OpenAPI、序列化之间的依赖图 | 实际输出偏向模型能力边界说明,没有充分完成依赖图任务,因此被标记为输出质量风险 | +| Agnet 02 | Agnet 01 的输出摘要,以及建立依赖图的任务 | 路由、依赖注入、OpenAPI、序列化之间的依赖图 | 新最小版本中通过质量感知重试和模型接手,输出通过交接质量门 | | Agnet 03 | Agnet 02 的输出摘要,以及跨文件风险定位任务 | 响应模型、依赖参数、编码器、OpenAPI schema 之间的风险路径 | 继续围绕 FastAPI 外部项目展开,形成了跨文件风险分析 | | Agnet 04 | Agnet 03 的输出摘要,以及构造反例任务 | 响应过滤、默认值、nullable、依赖参数和 schema 不一致的反例 | 输出了反例方向,使问题从抽象风险进入可验证失败场景 | -| Agnet 05 | Agnet 04 的输出摘要,以及修正策略任务 | 修改策略、兼容性约束、恢复路径 | 实际输出再次偏向能力边界或拒绝式说明,没有充分完成修正策略,因此也被标记为输出质量风险 | +| Agnet 05 | Agnet 04 的输出摘要,以及修正策略任务 | 修改策略、兼容性约束、恢复路径 | 新最小版本中通过质量门约束,输出通过交接质量门 | | Agnet 06 | Agnet 05 的输出摘要,以及文件级计划任务 | 具体到文件层面的修正计划和测试计划 | 重新把链路拉回 FastAPI 文件级计划,承担了恢复连续性的作用 | | Agnet 07 | Agnet 06 的输出摘要,以及最终验收任务 | 验收命令、失败判定、可合并结论、最终收敛输出 | 形成最终验收判断,并成为本轮收敛选择的主要输出 | -这张表说明了当前框架的真实状态:链路是跑通的,但不是每个模型输出都完美。Agnet 02 和 Agnet 05 暴露了异构模型在“接受蜂群角色”和“持续承接任务”上的风险。框架没有把这个问题藏掉,而是在报告中标成输出质量风险。 +这张表说明了当前框架的最新状态:链路不仅跑通,而且已经把“拒答、角色拒绝、偏题、本仓库漂移、没有承接上一步”纳入质量门。旧 run 中 Agnet 02 和 Agnet 05 暴露过异构模型不接受蜂群角色的问题;新最小版本已经把这类输出改成扣分、重试、换模型和质量共识验收。 ## 输出如何变成下一步输入 @@ -66,30 +68,44 @@ 第四步,收集每个 Agnet 的输出。输出不是只看最终文字是否好看,而是看是否包含本阶段标记、是否承接前一步、是否仍然围绕 FastAPI 外部项目、是否给出下一步可用信息。 -第五步,对输出进行评分。评分偏向连续性、目标一致性、可交接性和最终可验收性。输出偏题、拒答或只声明能力边界时,即使链路继续,也应该被标成质量风险。 +第五步,对输出进行评分。评分偏向连续性、目标一致性、可交接性和最终可验收性。输出偏题、拒答或只声明能力边界时,会被重罚,不能靠普通高分混进最终结果。 -第六步,把每一步输出沉淀成共享状态、观测记录和评分记录。这样最终报告可以追溯“谁接了什么输入、谁输出了什么、谁把结论交给了下一步”。 +第六步,如果输出不合格,框架先进行同模型重试;仍不合格时,切换到 fallback 模型接手当前步骤。只有通过质量门的输出才会成为下一步摘要。 -第七步,做最终收敛。当前最小框架的收敛方式是从已完成输出里选择最高分结果作为接受输出。也就是说,收敛不是多数投票,也不是专家委员会共识,而是当前最小原型里的最高分输出选择。 +第七步,把每一步输出沉淀成共享状态、观测记录和评分记录。这样最终报告可以追溯“谁接了什么输入、谁输出了什么、谁把结论交给了下一步”。 + +第八步,做多角色质量共识。当前最小版本引入了三个审查 Agnet 角色:交接连续性审查、输出质量审查、最终收敛审查。第一轮只形成候选,第二轮才允许达成接受结论。 + +第九步,做最终收敛。当前最小框架仍保留最高分输出作为最终接受文本,但它不再是单独门槛;必须先通过输出质量门和多轮质量共识门。 ## 当前框架逻辑的优点 它已经能把复杂目标拆成连续输入输出链。用户可以看到每一步 Agnet 收到什么任务、输出了什么内容、下一步如何接手。 -它已经能暴露模型质量问题。比如本轮 Agnet 02 和 Agnet 05 没有完成预期技术任务,报告会把它们标成输出质量风险,而不是假装全部内容都合格。 +它已经能处理模型质量问题。遇到拒答、角色拒绝或偏题输出时,框架会重罚该输出,触发重试或换模型,并把质量门结果写入验收报告。 -它已经能形成可追溯收敛。最终结果不是聊天窗口里临时拼出来的,而是从已完成 Agnet 输出中选择,并保留评分、观测和最终 artifact 证据。 +它已经能形成可追溯收敛。最终结果不是聊天窗口里临时拼出来的,而是从已完成 Agnet 输出中选择,并保留评分、观测、多轮质量共识和最终 artifact 证据。 -## 当前框架逻辑的不足 +## 已修复的最小版本问题 -当前收敛还是最小版本,主要是最高分输出选择。它还不是严格的多轮共识,也不是多个 Agnet 互相质询后达成一致。 +第一,收敛不再只看最高分。最新 S07 live run 在最高分输出之前增加了多角色质量共识门,第一轮未收敛,第二轮才接受外部 FastAPI 推理链。 -当前评分还需要继续加强。特别是遇到模型拒答、角色拒绝或输出偏题时,应该更重地扣分,并触发重试、换模型或补充 Agnet,而不是只让链路继续往后走。 +第二,评分已经加强。模型拒答、角色拒绝、偏题、本仓库漂移和缺少交接信息都会触发质量风险,分数会被重罚,不能靠链路继续自动通过。 -当前输入输出链已经能说明“有交接”,但还需要进一步说明“交接质量是否足够好”。未来标准应该把承接前一步、纠正前一步错误、恢复偏题链路作为单独指标。 +第三,补救路径已经接入。当前步骤输出不合格时,框架会先重试,再切换到 fallback 模型接手,避免低质量输出直接污染下一步输入。 + +第四,交接质量已经成为单独指标。每一步都要同时满足当前阶段标记、上一阶段标记、外部仓库、固定 commit、FastAPI 技术语义、下一步交接和非拒答输出。 + +## 仍保留的边界 + +当前最小版本的多轮共识是质量门共识,不是完整自然语言辩论。它已经能阻止低质量输出通过,但还没有实现多个 Agnet 对同一候选方案进行长文本互相质询。 + +当前补救方式是重试和换模型,还没有自动创建新的补充任务。也就是说,它能修复当前步骤输出质量问题,但还没有把“补充 Agnet”扩展成动态任务图。 + +当前最高分输出仍是最终文本选择方式,只是它前面已经增加质量门和多轮共识门。下一阶段可以把最终输出改成多个候选的融合答案,而不是只选择单个最高分输出。 ## 结论 -当前框架的核心逻辑是:用户目标先变成标准化任务输入,再被拆成多个连续 Agnet 输入;每个 Agnet 的输出都会变成下一步输入的一部分;最终通过输出评分和收敛选择形成接受结果。 +当前框架的核心逻辑是:用户目标先变成标准化任务输入,再被拆成多个连续 Agnet 输入;每个 Agnet 的输出都会变成下一步输入的一部分;输出必须先通过质量门和多轮质量共识,最终再通过评分收敛形成接受结果。 -从输入输出角度看,本轮已经证明框架能跑通外部复杂代码场景的连续推理链,也能暴露部分模型输出不合格的问题。但从更高标准看,它还需要加入更强的拒答识别、自动重试、交叉质询和多轮共识,才能从“最小蜂群闭环”升级为更稳健的蜂群 Agent 框架。 +从输入输出角度看,最新最小版本已经修复了三项关键不足:不再只靠最高分收敛,不再放过拒答或偏题输出,交接质量也从“有记录”升级为“有质量判定”。剩余工作是把质量共识升级为更完整的互相质询和动态补充任务。 diff --git a/docs/INDUSTRY_STANDARD_AGNET_TEST_PLAN.md b/docs/INDUSTRY_STANDARD_AGNET_TEST_PLAN.md index a4a2220..c285b16 100644 --- a/docs/INDUSTRY_STANDARD_AGNET_TEST_PLAN.md +++ b/docs/INDUSTRY_STANDARD_AGNET_TEST_PLAN.md @@ -24,20 +24,22 @@ 本项目当前最小通过条件不是“代码质量通过”,而是: 1. AQS 通用 Agent 标准中,任务理解、上下文、模型选择、资源边界、敏感信息、可观测性、错误可解释、交接准备度均有证据。 -2. SW-AQS 蜂群 Agent 标准中,任务池、共享状态、信息素、handoff、事件流、收敛、鲁棒性、涌现性、传统基线对比和 live 外部闭环均有证据。 +2. SW-AQS 蜂群 Agent 标准中,任务池、共享状态、信息素、handoff、事件流、质量门、fallback 补救、多轮质量共识、收敛、鲁棒性、涌现性、传统基线对比和 live 外部闭环均有证据。 3. 模型输入输出能被审计:知道每个 Agent 被分配了什么任务、收到了什么输入、输出了什么、下一个 Agent 如何接手。 4. live 测试真实连接 Azure PostgreSQL、Redis、Blob 和 NewAPI,且不输出任何真实密钥。 +5. 模型拒答、角色拒绝、偏题或目标漂移不能直接通过,必须被扣分并进入重试、换模型或质量共识门。 最新一次标准矩阵(S01-S08)已经通过,以下为 S07 外部 GitHub live 证据: ```json { - "run_id": "7ba01d0cc4ad402793938835654fbca5", + "run_id": "37155251926d4165a7b7af68e8967f64", "target_repo": "fastapi/fastapi", "target_commit": "ecace740f3eaccb1aba152cf1de79477095c56f4", "completed_tasks": 7, "accepted_score": 1.0, - "check_count": 12, + "check_count": 14, + "quality_consensus_rounds": 2, "failed_checks": [] } ``` @@ -49,4 +51,5 @@ - 并发 worker 自主 claim,而不是 coordinator 顺序驱动。 - Redis Stream 事件被其他 Agent 消费并改变决策。 - 3/5/7 Agent 扩缩容和稳定性压测。 +- 更完整的自然语言互相质询与动态补充任务。 - 收敛速度、Agent 利用率、响应时间、信息素分布等统一 metrics。 diff --git a/docs/MODEL_AGNET_IO_REPORT.zh-CN.md b/docs/MODEL_AGNET_IO_REPORT.zh-CN.md index f12c00d..2d7c00e 100644 --- a/docs/MODEL_AGNET_IO_REPORT.zh-CN.md +++ b/docs/MODEL_AGNET_IO_REPORT.zh-CN.md @@ -18,6 +18,8 @@ - 通过 NewAPI 模型发现流程选择至少 3 个不同模型,而不是写死 `NEWAPI_MODEL`。 - 创建 7 个连续 Agnet 步骤:问题边界、依赖图、风险路径、反例、修正策略、文件级计划、最终验收。 - 用 Azure PostgreSQL 保存 task pool、shared state、observation、convergence;用 Redis 保存事件流和 pheromone score;用 Blob 保存最终 artifact。 +- 对拒答、角色拒绝、偏题、本仓库漂移和交接断裂输出加入质量门:先扣分,再重试,必要时切换 fallback 模型。 +- 在最高分收敛前加入三角色多轮质量共识:交接连续性审查、输出质量审查和最终收敛审查必须共同接受。 - 新增/更新报告审计测试,要求报告能说清场景、输入、输出、交接证据,并且不能泄露明显密钥样式。 ## 不同 Agnet 的工作怎么实现 @@ -41,10 +43,11 @@ 1. 初始化 run:写入 `run:{run_id}:goal/status`,创建 7 个 task,写入 PostgreSQL task pool。 2. 模型选择:从 NewAPI `/models` 类接口发现模型,筛选可响应模型,再给 7 个步骤轮转分配。 3. 任务执行:每个 Agnet claim 自己的 task,带上上一阶段 summary 调用模型。 -4. 状态推进:每步完成后写入 `chain:{run_id}:{STEP}:summary`、`chain:{run_id}:edge:上一步->当前步` 和 `chain:{run_id}:cursor`。 -5. 评分沉淀:每个 task 的 score 写入 PostgreSQL 和 Redis pheromone sorted set,同时写入 Redis Stream 事件。 -6. 结果收敛:`SwarmCoordinator.converge` 从已完成 task 中选择最高分输出作为 accepted output,写入 `swarm_convergence`,并上传 Blob artifact。 -7. 本轮局限:当前最小蜂群收敛是“最高分已完成任务”策略,不是多轮投票共识;因此本报告会单独标出模型拒答或偏题输出,避免把链路通过误读成每一步内容都完美。 +4. 质量补救:如果输出出现拒答、角色拒绝、偏题、本仓库漂移或交接断裂,先重试;仍不合格时切换 fallback 模型接手当前步骤。 +5. 状态推进:每步通过质量门后写入 `chain:{run_id}:{STEP}:summary`、`chain:{run_id}:edge:上一步->当前步` 和 `chain:{run_id}:cursor`。 +6. 评分沉淀:每个 task 的 score 写入 PostgreSQL 和 Redis pheromone sorted set,同时写入 Redis Stream 事件。 +7. 共识质量门:三类审查 Agnet 对交接连续性、输出质量和最终验收进行多轮投票,第一轮只形成候选,第二轮或以后达成接受才算通过。 +8. 结果收敛:通过质量门和共识门后,`SwarmCoordinator.converge` 从已完成 task 中选择最高分输出作为 accepted output,写入 `swarm_convergence`,并上传 Blob artifact。 ## 本轮测试场景补充 @@ -60,12 +63,12 @@ 最新重跑结论:S01-S08 全部 PASS;最新 S07 外部 GitHub live run 会排在下方第一个。 -## Run `7ba01d0cc4ad402793938835654fbca5` +## Run `37155251926d4165a7b7af68e8967f64` - 任务目标:外部 GitHub 代码场景:审查 fastapi/fastapi 响应序列化与 OpenAPI 依赖链 - 完成任务数:7 - 收敛分数:1.0 -- Blob artifact:`swarm-runs/7ba01d0cc4ad402793938835654fbca5/result.json` +- Blob artifact:`swarm-runs/37155251926d4165a7b7af68e8967f64/result.json` ### 模型系统提示词 @@ -78,10 +81,10 @@ You are one stage in a continuous long-reasoning swarm. Carry forward prior conc | 调用 | Agnet | capability | 模型 | 任务职责 | 输出质量备注 | | --- | --- | --- | --- | --- | --- | | 1 | `continuous-agnet-1` | `chain_step_01` | `deepseek-v4-flash` | 把被测对象锁定为外部 FastAPI 仓库,定义输入、输出、不变量、禁止自测边界和下一步交接摘要。 | 输出包含本步 STEP、外部仓库标识和交接信息,可作为本阶段审计证据。 | -| 2 | `continuous-agnet-2` | `chain_step_02` | `claude-haiku-4-5-20251001` | 承接 STEP-01,建立 routing、dependencies、openapi、encoders、测试文件之间的依赖关系。 | 模型输出主要是能力边界/拒绝式说明,未充分完成本步技术职责,标记为输出质量风险。 | +| 2 | `continuous-agnet-2` | `chain_step_02` | `claude-haiku-4-5-20251001` | 承接 STEP-01,建立 routing、dependencies、openapi、encoders、测试文件之间的依赖关系。 | 输出包含本步 STEP、外部仓库标识和交接信息,可作为本阶段审计证据。 | | 3 | `continuous-agnet-3` | `chain_step_03` | `claude-sonnet-4-6` | 承接 STEP-02,定位 response_model、Depends、参数 metadata、jsonable_encoder、OpenAPI schema 之间的漂移风险。 | 输出包含本步 STEP、外部仓库标识和交接信息,可作为本阶段审计证据。 | | 4 | `continuous-agnet-4` | `chain_step_04` | `deepseek-v4-flash` | 承接 STEP-03,构造响应过滤、默认值、nullable、依赖参数和 OpenAPI schema 不一致的反例。 | 输出包含本步 STEP、外部仓库标识和交接信息,可作为本阶段审计证据。 | -| 5 | `continuous-agnet-5` | `chain_step_05` | `claude-haiku-4-5-20251001` | 承接 STEP-04,给出应修改的 FastAPI 模块、兼容性策略和 Starlette/Pydantic 交互保护。 | 模型输出主要是能力边界/拒绝式说明,未充分完成本步技术职责,标记为输出质量风险。 | +| 5 | `continuous-agnet-5` | `chain_step_05` | `claude-haiku-4-5-20251001` | 承接 STEP-04,给出应修改的 FastAPI 模块、兼容性策略和 Starlette/Pydantic 交互保护。 | 输出包含本步 STEP、外部仓库标识和交接信息,可作为本阶段审计证据。 | | 6 | `continuous-agnet-6` | `chain_step_06` | `claude-sonnet-4-6` | 承接 STEP-05,把修正策略落到具体源码文件和测试文件。 | 输出包含本步 STEP、外部仓库标识和交接信息,可作为本阶段审计证据。 | | 7 | `continuous-agnet-7` | `chain_step_07` | `deepseek-v4-flash` | 承接 STEP-06,给出可执行验收命令、指标、失败判定和可合并结论。 | 输出包含本步 STEP、外部仓库标识和交接信息,可作为本阶段审计证据。 | @@ -90,7 +93,7 @@ You are one stage in a continuous long-reasoning swarm. Carry forward prior conc - Agnet:`continuous-agnet-1` - 模型:`deepseek-v4-flash` - 状态:`done` -- 分数:`0.975` +- 分数:`0.9` - 观测信号:`chain_step_01:done` - 本步职责:把被测对象锁定为外部 FastAPI 仓库,定义输入、输出、不变量、禁止自测边界和下一步交接摘要。 - 期望产出:外部仓库边界、不变量、风险和 STEP-02 交接。 @@ -111,8 +114,8 @@ Task input: - 当前输出前缀记录 `chain_edge=START->STEP-01`,证明本 Agnet 承接了上一阶段。 - 调用前,wrapper 会把 `Previous marker` 和 `Previous summary` 放入 user prompt。 -- 执行后,wrapper 把输出摘要写入 `chain:7ba01d0cc4ad402793938835654fbca5:STEP-01:summary`。 -- 同时推进 `chain:7ba01d0cc4ad402793938835654fbca5:cursor`,并写入 `chain:7ba01d0cc4ad402793938835654fbca5:edge:START->STEP-01=done`。 +- 执行后,wrapper 把输出摘要写入 `chain:37155251926d4165a7b7af68e8967f64:STEP-01:summary`。 +- 同时推进 `chain:37155251926d4165a7b7af68e8967f64:cursor`,并写入 `chain:37155251926d4165a7b7af68e8967f64:edge:START->STEP-01=done`。 - 下一个 Agnet 读取这个 summary 和 edge 后继续执行,所以接手不是靠口头描述,而是靠共享状态字段完成。 #### 本次任务输入 task.input @@ -151,6 +154,9 @@ Task input: - 合并输出必须体现 FastAPI 外部代码的不变量、依赖图、复杂度、反例、修正、文件级计划和验收命令。 - 最终输出必须引用至少 5 个 fastapi/fastapi 真实文件。 - 流程必须依赖模型发现,不能写死 NEWAPI_MODEL。 +- 模型输出如果出现拒答、角色拒绝、偏题或本仓库漂移,必须被质量门扣分,并触发同模型重试或 fallback 模型接手。 +- 每一步输出必须通过交接质量门,证明它同时保留当前 STEP、前序 STEP、外部仓库、commit、FastAPI 技术语义和下一步交接。 +- 最终接受结果必须通过多角色、多轮质量共识门;第一轮只能形成候选,第二轮或以后达成收敛才算通过。 输出要求: - 必须包含 `STEP-01`。 @@ -167,9 +173,9 @@ Task input: #### Agnet / 模型实际输出 task.output(审计摘要) - 原始来源:PostgreSQL `swarm_tasks.output`。本报告不全文粘贴原文,而是给中文审计摘要;需要原文时可用 run_id 和 task_id 回查数据库。 -- task_id:`cd0f649ee47d450192c544478f52ecd5` -- 关键前缀:`chain_edge=START->STEP-01; target_repo=fastapi/fastapi; target_commit=ecace740f3eaccb1aba152cf1de79477095c56f4; primary_model=deepseek-v4-flash; used_model=deepseek-v4-flash; model_selection=discovered_models_not_NEWAPI_MODEL` -- 链路边:`START->STEP-01`;对应 shared state 键 `chain:7ba01d0cc4ad402793938835654fbca5:edge:START->STEP-01`。 +- task_id:`f237902284f34e26a7a82bc58db1a164` +- 关键前缀:`chain_edge=START->STEP-01; target_repo=fastapi/fastapi; target_commit=ecace740f3eaccb1aba152cf1de79477095c56f4; primary_model=deepseek-v4-flash; used_model=deepseek-v4-flash; model_selection=discovered_models_not_NEWAPI_MODEL; quality_attempt=1` +- 链路边:`START->STEP-01`;对应 shared state 键 `chain:37155251926d4165a7b7af68e8967f64:edge:START->STEP-01`。 - STEP 标记检查:`STEP-01` 存在。 - 内容质量:输出包含本步 STEP、外部仓库标识和交接信息,可作为本阶段审计证据。 - 输出归纳:建立了 FastAPI 外部仓库边界、禁止自测约束、目标文件范围、不变量、风险和下一步依赖图交接。 @@ -180,7 +186,7 @@ Task input: - Agnet:`continuous-agnet-2` - 模型:`claude-haiku-4-5-20251001` - 状态:`done` -- 分数:`0.86` +- 分数:`0.91` - 观测信号:`chain_step_02:done` - 本步职责:承接 STEP-01,建立 routing、dependencies、openapi、encoders、测试文件之间的依赖关系。 - 期望产出:FastAPI response_model、依赖注入、OpenAPI schema 和响应序列化的依赖图。 @@ -201,8 +207,8 @@ Task input: - 当前输出前缀记录 `chain_edge=STEP-01->STEP-02`,证明本 Agnet 承接了上一阶段。 - 调用前,wrapper 会把 `Previous marker` 和 `Previous summary` 放入 user prompt。 -- 执行后,wrapper 把输出摘要写入 `chain:7ba01d0cc4ad402793938835654fbca5:STEP-02:summary`。 -- 同时推进 `chain:7ba01d0cc4ad402793938835654fbca5:cursor`,并写入 `chain:7ba01d0cc4ad402793938835654fbca5:edge:STEP-01->STEP-02=done`。 +- 执行后,wrapper 把输出摘要写入 `chain:37155251926d4165a7b7af68e8967f64:STEP-02:summary`。 +- 同时推进 `chain:37155251926d4165a7b7af68e8967f64:cursor`,并写入 `chain:37155251926d4165a7b7af68e8967f64:edge:STEP-01->STEP-02=done`。 - 下一个 Agnet 读取这个 summary 和 edge 后继续执行,所以接手不是靠口头描述,而是靠共享状态字段完成。 #### 本次任务输入 task.input @@ -241,6 +247,9 @@ Task input: - 合并输出必须体现 FastAPI 外部代码的不变量、依赖图、复杂度、反例、修正、文件级计划和验收命令。 - 最终输出必须引用至少 5 个 fastapi/fastapi 真实文件。 - 流程必须依赖模型发现,不能写死 NEWAPI_MODEL。 +- 模型输出如果出现拒答、角色拒绝、偏题或本仓库漂移,必须被质量门扣分,并触发同模型重试或 fallback 模型接手。 +- 每一步输出必须通过交接质量门,证明它同时保留当前 STEP、前序 STEP、外部仓库、commit、FastAPI 技术语义和下一步交接。 +- 最终接受结果必须通过多角色、多轮质量共识门;第一轮只能形成候选,第二轮或以后达成收敛才算通过。 输出要求: - 必须包含 `STEP-02`。 @@ -257,20 +266,20 @@ Task input: #### Agnet / 模型实际输出 task.output(审计摘要) - 原始来源:PostgreSQL `swarm_tasks.output`。本报告不全文粘贴原文,而是给中文审计摘要;需要原文时可用 run_id 和 task_id 回查数据库。 -- task_id:`8ad2d425adfc4e4298216bf679de21c7` -- 关键前缀:`chain_edge=STEP-01->STEP-02; target_repo=fastapi/fastapi; target_commit=ecace740f3eaccb1aba152cf1de79477095c56f4; primary_model=claude-haiku-4-5-20251001; used_model=claude-haiku-4-5-20251001; model_selection=discovered_models_not_NEWAPI_MODEL` -- 链路边:`STEP-01->STEP-02`;对应 shared state 键 `chain:7ba01d0cc4ad402793938835654fbca5:edge:STEP-01->STEP-02`。 +- task_id:`761305d5c154460f8825ba0552bd2607` +- 关键前缀:`chain_edge=STEP-01->STEP-02; target_repo=fastapi/fastapi; target_commit=ecace740f3eaccb1aba152cf1de79477095c56f4; primary_model=claude-haiku-4-5-20251001; used_model=claude-haiku-4-5-20251001; model_selection=discovered_models_not_NEWAPI_MODEL; quality_attempt=1` +- 链路边:`STEP-01->STEP-02`;对应 shared state 键 `chain:37155251926d4165a7b7af68e8967f64:edge:STEP-01->STEP-02`。 - STEP 标记检查:`STEP-02` 存在。 -- 内容质量:模型输出主要是能力边界/拒绝式说明,未充分完成本步技术职责,标记为输出质量风险。 -- 输出归纳:该步原本应产出“FastAPI response_model、依赖注入、OpenAPI schema 和响应序列化的依赖图。”,但实际更像模型安全/能力边界声明;这说明当前评分标准还需要增加“非任务型输出”扣分。 -- 处理结论:该步暴露了异构模型在角色约束上的风险;它可作为链路/边界测试证据,但不应被当作充分的 FastAPI 技术修正内容。 +- 内容质量:输出包含本步 STEP、外部仓库标识和交接信息,可作为本阶段审计证据。 +- 输出归纳:围绕 FastAPI 路由、依赖注入、OpenAPI 与响应序列化建立依赖图和状态模型。 +- 处理结论:该步输出进入下一阶段 summary,并参与最终 score、pheromone 和 convergence 计算。 ### Agnet 调用 3: `chain_step_03` - Agnet:`continuous-agnet-3` - 模型:`claude-sonnet-4-6` - 状态:`done` -- 分数:`0.99` +- 分数:`0.92` - 观测信号:`chain_step_03:done` - 本步职责:承接 STEP-02,定位 response_model、Depends、参数 metadata、jsonable_encoder、OpenAPI schema 之间的漂移风险。 - 期望产出:跨文件风险路径、复杂度和 STEP-04 反例构造入口。 @@ -291,8 +300,8 @@ Task input: - 当前输出前缀记录 `chain_edge=STEP-02->STEP-03`,证明本 Agnet 承接了上一阶段。 - 调用前,wrapper 会把 `Previous marker` 和 `Previous summary` 放入 user prompt。 -- 执行后,wrapper 把输出摘要写入 `chain:7ba01d0cc4ad402793938835654fbca5:STEP-03:summary`。 -- 同时推进 `chain:7ba01d0cc4ad402793938835654fbca5:cursor`,并写入 `chain:7ba01d0cc4ad402793938835654fbca5:edge:STEP-02->STEP-03=done`。 +- 执行后,wrapper 把输出摘要写入 `chain:37155251926d4165a7b7af68e8967f64:STEP-03:summary`。 +- 同时推进 `chain:37155251926d4165a7b7af68e8967f64:cursor`,并写入 `chain:37155251926d4165a7b7af68e8967f64:edge:STEP-02->STEP-03=done`。 - 下一个 Agnet 读取这个 summary 和 edge 后继续执行,所以接手不是靠口头描述,而是靠共享状态字段完成。 #### 本次任务输入 task.input @@ -331,6 +340,9 @@ Task input: - 合并输出必须体现 FastAPI 外部代码的不变量、依赖图、复杂度、反例、修正、文件级计划和验收命令。 - 最终输出必须引用至少 5 个 fastapi/fastapi 真实文件。 - 流程必须依赖模型发现,不能写死 NEWAPI_MODEL。 +- 模型输出如果出现拒答、角色拒绝、偏题或本仓库漂移,必须被质量门扣分,并触发同模型重试或 fallback 模型接手。 +- 每一步输出必须通过交接质量门,证明它同时保留当前 STEP、前序 STEP、外部仓库、commit、FastAPI 技术语义和下一步交接。 +- 最终接受结果必须通过多角色、多轮质量共识门;第一轮只能形成候选,第二轮或以后达成收敛才算通过。 输出要求: - 必须包含 `STEP-03`。 @@ -347,9 +359,9 @@ Task input: #### Agnet / 模型实际输出 task.output(审计摘要) - 原始来源:PostgreSQL `swarm_tasks.output`。本报告不全文粘贴原文,而是给中文审计摘要;需要原文时可用 run_id 和 task_id 回查数据库。 -- task_id:`63ab98d8e6d743d9b808e74c8e9f4cba` -- 关键前缀:`chain_edge=STEP-02->STEP-03; target_repo=fastapi/fastapi; target_commit=ecace740f3eaccb1aba152cf1de79477095c56f4; primary_model=claude-sonnet-4-6; used_model=claude-sonnet-4-6; model_selection=discovered_models_not_NEWAPI_MODEL` -- 链路边:`STEP-02->STEP-03`;对应 shared state 键 `chain:7ba01d0cc4ad402793938835654fbca5:edge:STEP-02->STEP-03`。 +- task_id:`fedacb56416740a0940fb6cb0b8c4adc` +- 关键前缀:`chain_edge=STEP-02->STEP-03; target_repo=fastapi/fastapi; target_commit=ecace740f3eaccb1aba152cf1de79477095c56f4; primary_model=claude-sonnet-4-6; used_model=claude-sonnet-4-6; model_selection=discovered_models_not_NEWAPI_MODEL; quality_attempt=1` +- 链路边:`STEP-02->STEP-03`;对应 shared state 键 `chain:37155251926d4165a7b7af68e8967f64:edge:STEP-02->STEP-03`。 - STEP 标记检查:`STEP-03` 存在。 - 内容质量:输出包含本步 STEP、外部仓库标识和交接信息,可作为本阶段审计证据。 - 输出归纳:定位 response_model、Depends、jsonable_encoder 与 OpenAPI schema 之间可能发生漂移的跨文件路径,并给出复杂度视角。 @@ -360,7 +372,7 @@ Task input: - Agnet:`continuous-agnet-4` - 模型:`deepseek-v4-flash` - 状态:`done` -- 分数:`0.99` +- 分数:`0.907` - 观测信号:`chain_step_04:done` - 本步职责:承接 STEP-03,构造响应过滤、默认值、nullable、依赖参数和 OpenAPI schema 不一致的反例。 - 期望产出:可触发失败的反例、预期破坏点和 STEP-05 修正入口。 @@ -381,8 +393,8 @@ Task input: - 当前输出前缀记录 `chain_edge=STEP-03->STEP-04`,证明本 Agnet 承接了上一阶段。 - 调用前,wrapper 会把 `Previous marker` 和 `Previous summary` 放入 user prompt。 -- 执行后,wrapper 把输出摘要写入 `chain:7ba01d0cc4ad402793938835654fbca5:STEP-04:summary`。 -- 同时推进 `chain:7ba01d0cc4ad402793938835654fbca5:cursor`,并写入 `chain:7ba01d0cc4ad402793938835654fbca5:edge:STEP-03->STEP-04=done`。 +- 执行后,wrapper 把输出摘要写入 `chain:37155251926d4165a7b7af68e8967f64:STEP-04:summary`。 +- 同时推进 `chain:37155251926d4165a7b7af68e8967f64:cursor`,并写入 `chain:37155251926d4165a7b7af68e8967f64:edge:STEP-03->STEP-04=done`。 - 下一个 Agnet 读取这个 summary 和 edge 后继续执行,所以接手不是靠口头描述,而是靠共享状态字段完成。 #### 本次任务输入 task.input @@ -421,6 +433,9 @@ Task input: - 合并输出必须体现 FastAPI 外部代码的不变量、依赖图、复杂度、反例、修正、文件级计划和验收命令。 - 最终输出必须引用至少 5 个 fastapi/fastapi 真实文件。 - 流程必须依赖模型发现,不能写死 NEWAPI_MODEL。 +- 模型输出如果出现拒答、角色拒绝、偏题或本仓库漂移,必须被质量门扣分,并触发同模型重试或 fallback 模型接手。 +- 每一步输出必须通过交接质量门,证明它同时保留当前 STEP、前序 STEP、外部仓库、commit、FastAPI 技术语义和下一步交接。 +- 最终接受结果必须通过多角色、多轮质量共识门;第一轮只能形成候选,第二轮或以后达成收敛才算通过。 输出要求: - 必须包含 `STEP-04`。 @@ -437,9 +452,9 @@ Task input: #### Agnet / 模型实际输出 task.output(审计摘要) - 原始来源:PostgreSQL `swarm_tasks.output`。本报告不全文粘贴原文,而是给中文审计摘要;需要原文时可用 run_id 和 task_id 回查数据库。 -- task_id:`da7ec61858ae457389189c2416350ddf` -- 关键前缀:`chain_edge=STEP-03->STEP-04; target_repo=fastapi/fastapi; target_commit=ecace740f3eaccb1aba152cf1de79477095c56f4; primary_model=deepseek-v4-flash; used_model=deepseek-v4-flash; model_selection=discovered_models_not_NEWAPI_MODEL` -- 链路边:`STEP-03->STEP-04`;对应 shared state 键 `chain:7ba01d0cc4ad402793938835654fbca5:edge:STEP-03->STEP-04`。 +- task_id:`83e38a6cf4ab4711bed3284072491b0c` +- 关键前缀:`chain_edge=STEP-03->STEP-04; target_repo=fastapi/fastapi; target_commit=ecace740f3eaccb1aba152cf1de79477095c56f4; primary_model=deepseek-v4-flash; used_model=deepseek-v4-flash; model_selection=discovered_models_not_NEWAPI_MODEL; quality_attempt=1` +- 链路边:`STEP-03->STEP-04`;对应 shared state 键 `chain:37155251926d4165a7b7af68e8967f64:edge:STEP-03->STEP-04`。 - STEP 标记检查:`STEP-04` 存在。 - 内容质量:输出包含本步 STEP、外部仓库标识和交接信息,可作为本阶段审计证据。 - 输出归纳:构造响应过滤、默认值、nullable、依赖参数和 OpenAPI schema 不一致的反例,为修正策略提供失败样本。 @@ -450,7 +465,7 @@ Task input: - Agnet:`continuous-agnet-5` - 模型:`claude-haiku-4-5-20251001` - 状态:`done` -- 分数:`0.8749999999999999` +- 分数:`0.917` - 观测信号:`chain_step_05:done` - 本步职责:承接 STEP-04,给出应修改的 FastAPI 模块、兼容性策略和 Starlette/Pydantic 交互保护。 - 期望产出:修正算法、兼容策略、恢复策略和 STEP-06 文件级计划入口。 @@ -471,8 +486,8 @@ Task input: - 当前输出前缀记录 `chain_edge=STEP-04->STEP-05`,证明本 Agnet 承接了上一阶段。 - 调用前,wrapper 会把 `Previous marker` 和 `Previous summary` 放入 user prompt。 -- 执行后,wrapper 把输出摘要写入 `chain:7ba01d0cc4ad402793938835654fbca5:STEP-05:summary`。 -- 同时推进 `chain:7ba01d0cc4ad402793938835654fbca5:cursor`,并写入 `chain:7ba01d0cc4ad402793938835654fbca5:edge:STEP-04->STEP-05=done`。 +- 执行后,wrapper 把输出摘要写入 `chain:37155251926d4165a7b7af68e8967f64:STEP-05:summary`。 +- 同时推进 `chain:37155251926d4165a7b7af68e8967f64:cursor`,并写入 `chain:37155251926d4165a7b7af68e8967f64:edge:STEP-04->STEP-05=done`。 - 下一个 Agnet 读取这个 summary 和 edge 后继续执行,所以接手不是靠口头描述,而是靠共享状态字段完成。 #### 本次任务输入 task.input @@ -511,6 +526,9 @@ Task input: - 合并输出必须体现 FastAPI 外部代码的不变量、依赖图、复杂度、反例、修正、文件级计划和验收命令。 - 最终输出必须引用至少 5 个 fastapi/fastapi 真实文件。 - 流程必须依赖模型发现,不能写死 NEWAPI_MODEL。 +- 模型输出如果出现拒答、角色拒绝、偏题或本仓库漂移,必须被质量门扣分,并触发同模型重试或 fallback 模型接手。 +- 每一步输出必须通过交接质量门,证明它同时保留当前 STEP、前序 STEP、外部仓库、commit、FastAPI 技术语义和下一步交接。 +- 最终接受结果必须通过多角色、多轮质量共识门;第一轮只能形成候选,第二轮或以后达成收敛才算通过。 输出要求: - 必须包含 `STEP-05`。 @@ -527,20 +545,20 @@ Task input: #### Agnet / 模型实际输出 task.output(审计摘要) - 原始来源:PostgreSQL `swarm_tasks.output`。本报告不全文粘贴原文,而是给中文审计摘要;需要原文时可用 run_id 和 task_id 回查数据库。 -- task_id:`9550f1ad8f3649ee95ac42c8f1349c48` -- 关键前缀:`chain_edge=STEP-04->STEP-05; target_repo=fastapi/fastapi; target_commit=ecace740f3eaccb1aba152cf1de79477095c56f4; primary_model=claude-haiku-4-5-20251001; used_model=claude-haiku-4-5-20251001; model_selection=discovered_models_not_NEWAPI_MODEL` -- 链路边:`STEP-04->STEP-05`;对应 shared state 键 `chain:7ba01d0cc4ad402793938835654fbca5:edge:STEP-04->STEP-05`。 +- task_id:`77729b0692574bf1bc559fdd558e5852` +- 关键前缀:`chain_edge=STEP-04->STEP-05; target_repo=fastapi/fastapi; target_commit=ecace740f3eaccb1aba152cf1de79477095c56f4; primary_model=claude-haiku-4-5-20251001; used_model=claude-haiku-4-5-20251001; model_selection=discovered_models_not_NEWAPI_MODEL; quality_attempt=1` +- 链路边:`STEP-04->STEP-05`;对应 shared state 键 `chain:37155251926d4165a7b7af68e8967f64:edge:STEP-04->STEP-05`。 - STEP 标记检查:`STEP-05` 存在。 -- 内容质量:模型输出主要是能力边界/拒绝式说明,未充分完成本步技术职责,标记为输出质量风险。 -- 输出归纳:该步原本应产出“修正算法、兼容策略、恢复策略和 STEP-06 文件级计划入口。”,但实际更像模型安全/能力边界声明;这说明当前评分标准还需要增加“非任务型输出”扣分。 -- 处理结论:该步暴露了异构模型在角色约束上的风险;它可作为链路/边界测试证据,但不应被当作充分的 FastAPI 技术修正内容。 +- 内容质量:输出包含本步 STEP、外部仓库标识和交接信息,可作为本阶段审计证据。 +- 输出归纳:给出响应序列化与 OpenAPI/依赖注入交互的修正策略和兼容保护思路。 +- 处理结论:该步输出进入下一阶段 summary,并参与最终 score、pheromone 和 convergence 计算。 ### Agnet 调用 6: `chain_step_06` - Agnet:`continuous-agnet-6` - 模型:`claude-sonnet-4-6` - 状态:`done` -- 分数:`0.99` +- 分数:`0.928` - 观测信号:`chain_step_06:done` - 本步职责:承接 STEP-05,把修正策略落到具体源码文件和测试文件。 - 期望产出:文件级补丁计划、目标测试文件和 STEP-07 验收入口。 @@ -561,8 +579,8 @@ Task input: - 当前输出前缀记录 `chain_edge=STEP-05->STEP-06`,证明本 Agnet 承接了上一阶段。 - 调用前,wrapper 会把 `Previous marker` 和 `Previous summary` 放入 user prompt。 -- 执行后,wrapper 把输出摘要写入 `chain:7ba01d0cc4ad402793938835654fbca5:STEP-06:summary`。 -- 同时推进 `chain:7ba01d0cc4ad402793938835654fbca5:cursor`,并写入 `chain:7ba01d0cc4ad402793938835654fbca5:edge:STEP-05->STEP-06=done`。 +- 执行后,wrapper 把输出摘要写入 `chain:37155251926d4165a7b7af68e8967f64:STEP-06:summary`。 +- 同时推进 `chain:37155251926d4165a7b7af68e8967f64:cursor`,并写入 `chain:37155251926d4165a7b7af68e8967f64:edge:STEP-05->STEP-06=done`。 - 下一个 Agnet 读取这个 summary 和 edge 后继续执行,所以接手不是靠口头描述,而是靠共享状态字段完成。 #### 本次任务输入 task.input @@ -601,6 +619,9 @@ Task input: - 合并输出必须体现 FastAPI 外部代码的不变量、依赖图、复杂度、反例、修正、文件级计划和验收命令。 - 最终输出必须引用至少 5 个 fastapi/fastapi 真实文件。 - 流程必须依赖模型发现,不能写死 NEWAPI_MODEL。 +- 模型输出如果出现拒答、角色拒绝、偏题或本仓库漂移,必须被质量门扣分,并触发同模型重试或 fallback 模型接手。 +- 每一步输出必须通过交接质量门,证明它同时保留当前 STEP、前序 STEP、外部仓库、commit、FastAPI 技术语义和下一步交接。 +- 最终接受结果必须通过多角色、多轮质量共识门;第一轮只能形成候选,第二轮或以后达成收敛才算通过。 输出要求: - 必须包含 `STEP-06`。 @@ -617,9 +638,9 @@ Task input: #### Agnet / 模型实际输出 task.output(审计摘要) - 原始来源:PostgreSQL `swarm_tasks.output`。本报告不全文粘贴原文,而是给中文审计摘要;需要原文时可用 run_id 和 task_id 回查数据库。 -- task_id:`20ed46ea8e8441e0a8537cb04623c19f` -- 关键前缀:`chain_edge=STEP-05->STEP-06; target_repo=fastapi/fastapi; target_commit=ecace740f3eaccb1aba152cf1de79477095c56f4; primary_model=claude-sonnet-4-6; used_model=claude-sonnet-4-6; model_selection=discovered_models_not_NEWAPI_MODEL` -- 链路边:`STEP-05->STEP-06`;对应 shared state 键 `chain:7ba01d0cc4ad402793938835654fbca5:edge:STEP-05->STEP-06`。 +- task_id:`b49605fa8df54b2abba15c4db0ab2299` +- 关键前缀:`chain_edge=STEP-05->STEP-06; target_repo=fastapi/fastapi; target_commit=ecace740f3eaccb1aba152cf1de79477095c56f4; primary_model=claude-sonnet-4-6; used_model=claude-sonnet-4-6; model_selection=discovered_models_not_NEWAPI_MODEL; quality_attempt=1` +- 链路边:`STEP-05->STEP-06`;对应 shared state 键 `chain:37155251926d4165a7b7af68e8967f64:edge:STEP-05->STEP-06`。 - STEP 标记检查:`STEP-06` 存在。 - 内容质量:输出包含本步 STEP、外部仓库标识和交接信息,可作为本阶段审计证据。 - 输出归纳:把修正策略落到 FastAPI 源码文件和测试文件,形成文件级补丁计划。 @@ -651,8 +672,8 @@ Task input: - 当前输出前缀记录 `chain_edge=STEP-06->STEP-07`,证明本 Agnet 承接了上一阶段。 - 调用前,wrapper 会把 `Previous marker` 和 `Previous summary` 放入 user prompt。 -- 执行后,wrapper 把输出摘要写入 `chain:7ba01d0cc4ad402793938835654fbca5:STEP-07:summary`。 -- 同时推进 `chain:7ba01d0cc4ad402793938835654fbca5:cursor`,并写入 `chain:7ba01d0cc4ad402793938835654fbca5:edge:STEP-06->STEP-07=done`。 +- 执行后,wrapper 把输出摘要写入 `chain:37155251926d4165a7b7af68e8967f64:STEP-07:summary`。 +- 同时推进 `chain:37155251926d4165a7b7af68e8967f64:cursor`,并写入 `chain:37155251926d4165a7b7af68e8967f64:edge:STEP-06->STEP-07=done`。 - 下一个 Agnet 读取这个 summary 和 edge 后继续执行,所以接手不是靠口头描述,而是靠共享状态字段完成。 #### 本次任务输入 task.input @@ -691,6 +712,9 @@ Task input: - 合并输出必须体现 FastAPI 外部代码的不变量、依赖图、复杂度、反例、修正、文件级计划和验收命令。 - 最终输出必须引用至少 5 个 fastapi/fastapi 真实文件。 - 流程必须依赖模型发现,不能写死 NEWAPI_MODEL。 +- 模型输出如果出现拒答、角色拒绝、偏题或本仓库漂移,必须被质量门扣分,并触发同模型重试或 fallback 模型接手。 +- 每一步输出必须通过交接质量门,证明它同时保留当前 STEP、前序 STEP、外部仓库、commit、FastAPI 技术语义和下一步交接。 +- 最终接受结果必须通过多角色、多轮质量共识门;第一轮只能形成候选,第二轮或以后达成收敛才算通过。 输出要求: - 必须包含 `STEP-07`。 @@ -708,9 +732,9 @@ Task input: #### Agnet / 模型实际输出 task.output(审计摘要) - 原始来源:PostgreSQL `swarm_tasks.output`。本报告不全文粘贴原文,而是给中文审计摘要;需要原文时可用 run_id 和 task_id 回查数据库。 -- task_id:`287f6efdc400475bb89ade8c5770b410` -- 关键前缀:`chain_edge=STEP-06->STEP-07; target_repo=fastapi/fastapi; target_commit=ecace740f3eaccb1aba152cf1de79477095c56f4; primary_model=deepseek-v4-flash; used_model=deepseek-v4-flash; model_selection=discovered_models_not_NEWAPI_MODEL; required_files=fastapi/routing.py,fastapi/dependencies/utils.py,fastapi/openapi/utils.py,fastapi/params.py,fastapi/encoders.py,fastapi/applications.py` -- 链路边:`STEP-06->STEP-07`;对应 shared state 键 `chain:7ba01d0cc4ad402793938835654fbca5:edge:STEP-06->STEP-07`。 +- task_id:`34b2b373af964e488aef211746267be5` +- 关键前缀:`chain_edge=STEP-06->STEP-07; target_repo=fastapi/fastapi; target_commit=ecace740f3eaccb1aba152cf1de79477095c56f4; primary_model=deepseek-v4-flash; used_model=deepseek-v4-flash; model_selection=discovered_models_not_NEWAPI_MODEL; quality_attempt=1; required_files=fastapi/routing.py,fastapi/dependencies/utils.py,fastapi/openapi/utils.py,fastapi/params.py,fastapi/encoders.py,fastapi/applications.py` +- 链路边:`STEP-06->STEP-07`;对应 shared state 键 `chain:37155251926d4165a7b7af68e8967f64:edge:STEP-06->STEP-07`。 - STEP 标记检查:`STEP-07` 存在。 - 内容质量:输出包含本步 STEP、外部仓库标识和交接信息,可作为本阶段审计证据。 - 输出归纳:给出最终验收命令、失败判定、文件引用和可合并结论,并作为最高分输出进入收敛。 diff --git a/examples/export_model_agnet_io_report.py b/examples/export_model_agnet_io_report.py index 70c14f6..3e06eae 100644 --- a/examples/export_model_agnet_io_report.py +++ b/examples/export_model_agnet_io_report.py @@ -146,6 +146,8 @@ def build_report(store: PostgresRedisBlobSwarmStore) -> str: "- 通过 NewAPI 模型发现流程选择至少 3 个不同模型,而不是写死 `NEWAPI_MODEL`。", "- 创建 7 个连续 Agnet 步骤:问题边界、依赖图、风险路径、反例、修正策略、文件级计划、最终验收。", "- 用 Azure PostgreSQL 保存 task pool、shared state、observation、convergence;用 Redis 保存事件流和 pheromone score;用 Blob 保存最终 artifact。", + "- 对拒答、角色拒绝、偏题、本仓库漂移和交接断裂输出加入质量门:先扣分,再重试,必要时切换 fallback 模型。", + "- 在最高分收敛前加入三角色多轮质量共识:交接连续性审查、输出质量审查和最终收敛审查必须共同接受。", "- 新增/更新报告审计测试,要求报告能说清场景、输入、输出、交接证据,并且不能泄露明显密钥样式。", "", "## 不同 Agnet 的工作怎么实现", @@ -169,10 +171,11 @@ def build_report(store: PostgresRedisBlobSwarmStore) -> str: "1. 初始化 run:写入 `run:{run_id}:goal/status`,创建 7 个 task,写入 PostgreSQL task pool。", "2. 模型选择:从 NewAPI `/models` 类接口发现模型,筛选可响应模型,再给 7 个步骤轮转分配。", "3. 任务执行:每个 Agnet claim 自己的 task,带上上一阶段 summary 调用模型。", - "4. 状态推进:每步完成后写入 `chain:{run_id}:{STEP}:summary`、`chain:{run_id}:edge:上一步->当前步` 和 `chain:{run_id}:cursor`。", - "5. 评分沉淀:每个 task 的 score 写入 PostgreSQL 和 Redis pheromone sorted set,同时写入 Redis Stream 事件。", - "6. 结果收敛:`SwarmCoordinator.converge` 从已完成 task 中选择最高分输出作为 accepted output,写入 `swarm_convergence`,并上传 Blob artifact。", - "7. 本轮局限:当前最小蜂群收敛是“最高分已完成任务”策略,不是多轮投票共识;因此本报告会单独标出模型拒答或偏题输出,避免把链路通过误读成每一步内容都完美。", + "4. 质量补救:如果输出出现拒答、角色拒绝、偏题、本仓库漂移或交接断裂,先重试;仍不合格时切换 fallback 模型接手当前步骤。", + "5. 状态推进:每步通过质量门后写入 `chain:{run_id}:{STEP}:summary`、`chain:{run_id}:edge:上一步->当前步` 和 `chain:{run_id}:cursor`。", + "6. 评分沉淀:每个 task 的 score 写入 PostgreSQL 和 Redis pheromone sorted set,同时写入 Redis Stream 事件。", + "7. 共识质量门:三类审查 Agnet 对交接连续性、输出质量和最终验收进行多轮投票,第一轮只形成候选,第二轮或以后达成接受才算通过。", + "8. 结果收敛:通过质量门和共识门后,`SwarmCoordinator.converge` 从已完成 task 中选择最高分输出作为 accepted output,写入 `swarm_convergence`,并上传 Blob artifact。", "", "## 本轮测试场景补充", "", @@ -347,6 +350,7 @@ def looks_like_boundary_refusal(text: str) -> bool: "fake \"previous context\"", "fabricated context", "not a component in a multi-step reasoning swarm", + "quality_gate_failed", ] return any(marker in lowered for marker in refusal_markers) diff --git a/examples/run_continuous_reasoning_acceptance.py b/examples/run_continuous_reasoning_acceptance.py index 0aa102e..23e025e 100644 --- a/examples/run_continuous_reasoning_acceptance.py +++ b/examples/run_continuous_reasoning_acceptance.py @@ -9,7 +9,7 @@ sys.path.insert(0, str(ROOT)) from swarm_minimal.azure_store import PostgresRedisBlobSwarmStore from swarm_minimal.config import SwarmConfig -from swarm_minimal.core import Agent, SwarmCoordinator, Task +from swarm_minimal.core import Agent, ConsensusAgent, ConsensusSwarm, ConsensusVote, SwarmCoordinator, Task from swarm_minimal.local_env import load_project_env from swarm_minimal.newapi_agnet import ( NewApiAgnet, @@ -44,6 +44,21 @@ LOCAL_PROJECT_TARGETS = [ "tests/test_minimal_swarm.py", ] +QUALITY_RISK_MARKERS = [ + "i appreciate the detailed context", + "i need to clarify my actual role", + "what i can actually do", + "what i cannot do", + "not a component in a multi-step reasoning swarm", + "fake \"previous context\"", + "fabricated context", + "cannot participate", + "can't participate", +] + +QUALITY_RISK_PENALTY_SCORE = 0.12 +QUALITY_PASS_MIN_SCORE = 0.72 + SCENARIO = { "title": "外部 GitHub 代码场景:审查 fastapi/fastapi 响应序列化与 OpenAPI 依赖链", @@ -116,6 +131,9 @@ ACCEPTANCE_CRITERIA = [ "合并输出必须体现 FastAPI 外部代码的不变量、依赖图、复杂度、反例、修正、文件级计划和验收命令。", "最终输出必须引用至少 5 个 fastapi/fastapi 真实文件。", "流程必须依赖模型发现,不能写死 NEWAPI_MODEL。", + "模型输出如果出现拒答、角色拒绝、偏题或本仓库漂移,必须被质量门扣分,并触发同模型重试或 fallback 模型接手。", + "每一步输出必须通过交接质量门,证明它同时保留当前 STEP、前序 STEP、外部仓库、commit、FastAPI 技术语义和下一步交接。", + "最终接受结果必须通过多角色、多轮质量共识门;第一轮只能形成候选,第二轮或以后达成收敛才算通过。", ] @@ -301,6 +319,8 @@ def chat_with_fallback( previous_summary: str, ) -> str: errors: list[str] = [] + best_candidate_output = "" + best_candidate_score = -1.0 for candidate in [primary_model, *fallback_models]: model_config = NewApiChannelConfig( base_url=config.base_url, @@ -309,74 +329,186 @@ def chat_with_fallback( timeout_seconds=config.timeout_seconds, ) agnet = NewApiAgnet(model_config, agent_id=f"continuous-agnet-{agent_index}", capability=step["capability"]) - try: - content = agnet.chat( - system_prompt=( - "You are one stage in a continuous long-reasoning swarm. " - "Carry forward prior conclusions for an external GitHub code review, expose risks, and hand off a concise next-state. " - "The code target is fastapi/fastapi, not the local harness repository. " - "Do not reveal secrets." - ), - user_prompt=( - f"Previous marker: {previous_marker}\n" - f"Previous summary:\n{previous_summary}\n\n" - f"Task kind: {task.kind}\n" - f"Task input:\n{task.input}\n" - ), + for attempt in range(2): + try: + content = agnet.chat( + system_prompt=continuous_reasoning_system_prompt(), + user_prompt=build_model_user_prompt( + task=task, + step=step, + previous_marker=previous_marker, + previous_summary=previous_summary, + retry_reason=errors[-1] if attempt else "", + ), + ) + except Exception as exc: + errors.append(f"{candidate}:attempt-{attempt + 1}:{exc.__class__.__name__}") + continue + output = format_model_output( + content=content, + primary_model=primary_model, + used_model=candidate, + previous_marker=previous_marker, + step=step, + errors=errors, + attempt=attempt + 1, ) - except Exception as exc: - errors.append(f"{candidate}:{exc.__class__.__name__}") - continue - prefix = ( - f"chain_edge={previous_marker}->{step['marker']}; " - f"target_repo={EXTERNAL_REPO}; target_commit={EXTERNAL_REPO_COMMIT}; " - f"primary_model={primary_model}; used_model={candidate}; " - "model_selection=discovered_models_not_NEWAPI_MODEL" - ) - if step["marker"] == "STEP-07": - prefix += "; required_files=" + ",".join(TARGET_FILES[:6]) - if errors: - prefix += "; fallback_after=" + ",".join(errors) - return prefix + "\n" + content + quality = assess_output_quality(output, step_index_for_marker(step["marker"])) + if quality["score"] > best_candidate_score: + best_candidate_output = output + best_candidate_score = quality["score"] + if quality["passed"]: + return output + errors.append(f"{candidate}:attempt-{attempt + 1}:quality_failed:{','.join(quality['missing'])}") + if best_candidate_output: + return mark_output_quality_failed(best_candidate_output, best_candidate_score, errors) raise RuntimeError("all model attempts failed: " + ",".join(errors)) +def continuous_reasoning_system_prompt() -> str: + return ( + "You are a code-review Agnet participating in a local acceptance test for an external GitHub code review. " + "Treat the provided previous marker and previous summary as test harness context, not as a claim that you personally control infrastructure. " + "Your job is to produce the requested FastAPI analysis stage in Chinese, carry forward prior conclusions, expose risks, and hand off a concise next-state. " + "Do not discuss your identity or refuse the test role. Do not reveal secrets." + ) + + +def build_model_user_prompt( + *, + task: Task, + step: dict[str, str], + previous_marker: str, + previous_summary: str, + retry_reason: str, +) -> str: + retry_block = "" + if retry_reason: + retry_block = ( + "\nQuality retry reason:\n" + f"{retry_reason}\n" + "Rewrite the answer to satisfy the stage requirements. Do not explain why the prior answer failed.\n" + ) + return ( + f"Previous marker: {previous_marker}\n" + f"Previous summary:\n{previous_summary}\n" + f"{retry_block}\n" + f"Task kind: {task.kind}\n" + f"Task input:\n{task.input}\n" + "\nQuality gate:\n" + f"- Include {step['marker']} and {previous_marker}.\n" + f"- Include {EXTERNAL_REPO} and {EXTERNAL_REPO_COMMIT}.\n" + "- Include FastAPI response_model, dependency injection, OpenAPI/schema, and serialization material.\n" + "- Include invariant, risk/counterexample, and next handoff summary.\n" + "- Do not answer with role refusal, identity clarification, or generic capability limits.\n" + ) + + +def format_model_output( + *, + content: str, + primary_model: str, + used_model: str, + previous_marker: str, + step: dict[str, str], + errors: list[str], + attempt: int, +) -> str: + prefix = ( + f"chain_edge={previous_marker}->{step['marker']}; " + f"target_repo={EXTERNAL_REPO}; target_commit={EXTERNAL_REPO_COMMIT}; " + f"primary_model={primary_model}; used_model={used_model}; " + "model_selection=discovered_models_not_NEWAPI_MODEL; " + f"quality_attempt={attempt}" + ) + if step["marker"] == "STEP-07": + prefix += "; required_files=" + ",".join(TARGET_FILES[:6]) + if errors: + prefix += "; fallback_after=" + ",".join(errors) + return prefix + "\n" + content + + +def mark_output_quality_failed(output: str, best_score: float, errors: list[str]) -> str: + return ( + output + + "\n\nQUALITY_GATE_FAILED\n" + + f"best_quality_score={best_score:.3f}\n" + + "quality_errors=" + + ",".join(errors) + ) + + def summarize_for_state(content: str) -> str: compact = " ".join(content.split()) return compact[:900] -def score_output(content: str, index: int) -> float: +def step_index_for_marker(marker: str) -> int: + for index, step in enumerate(CHAIN_STEPS): + if step["marker"] == marker: + return index + raise ValueError(f"unknown step marker: {marker}") + + +def has_quality_risk(content: str) -> bool: + lowered = content.lower() + return "quality_gate_failed" in lowered or any(marker in lowered for marker in QUALITY_RISK_MARKERS) + + +def assess_output_quality(content: str, index: int) -> dict[str, object]: lowered = content.lower() marker = CHAIN_STEPS[index]["marker"] previous_marker = "START" if index == 0 else CHAIN_STEPS[index - 1]["marker"] - checks = [ - marker in content, - previous_marker in content, - EXTERNAL_REPO in content, - EXTERNAL_REPO_COMMIT in content, - does_not_target_local_project(content), - "不变量" in content, - "风险" in content or "反例" in content, - "下一步" in content or "交接" in content, - "NEWAPI_MODEL" in content, - "模型发现" in content or "discover" in lowered, - contains_external_code_terms(content), - ] + checks = { + "own_marker": marker in content, + "previous_marker": previous_marker in content, + "external_repo": EXTERNAL_REPO in content, + "external_commit": EXTERNAL_REPO_COMMIT in content, + "not_local_project": does_not_target_local_project(content), + "no_refusal_or_role_boundary": not has_quality_risk(content), + "model_discovery": "NEWAPI_MODEL" in content and ("模型发现" in content or "discover" in lowered), + "external_code_terms": contains_external_code_terms(content), + "invariant": "不变量" in content, + "risk_or_counterexample": "风险" in content or "反例" in content, + "handoff": "下一步" in content or "交接" in content, + } if index >= 2: - checks.append("o(" in lowered or "复杂度" in content) + checks["complexity"] = "o(" in lowered or "复杂度" in content if index >= 5: - checks.append(count_referenced_files(content) >= 3) + checks["file_references"] = count_referenced_files(content) >= 3 if index == len(CHAIN_STEPS) - 1: - checks.extend( - [ - "./.venv/bin/python" in content or "unittest" in lowered, - count_referenced_files(content) >= 5, - "验收" in content or "pass" in lowered, - ] - ) - return 1.0 if marker in content and previous_marker in content else 0.99 - return min(0.99, 0.48 + 0.045 * sum(1 for item in checks if item) + 0.02 * index) + checks["acceptance"] = "./.venv/bin/python" in content or "unittest" in lowered or "pytest" in lowered + checks["final_file_references"] = count_referenced_files(content) >= 5 + checks["final_verdict"] = "验收" in content or "pass" in lowered or "可合并" in content + + missing = [name for name, passed in checks.items() if not passed] + critical = { + "own_marker", + "previous_marker", + "external_repo", + "external_commit", + "not_local_project", + "no_refusal_or_role_boundary", + "external_code_terms", + } + score = sum(1 for passed in checks.values() if passed) / len(checks) + return { + "passed": score >= QUALITY_PASS_MIN_SCORE and not any(name in missing for name in critical), + "score": score, + "missing": missing, + "checks": checks, + } + + +def score_output(content: str, index: int) -> float: + quality = assess_output_quality(content, index) + if has_quality_risk(content): + return QUALITY_RISK_PENALTY_SCORE + if not quality["passed"]: + return round(max(0.05, 0.3 + 0.35 * float(quality["score"])), 3) + if index == len(CHAIN_STEPS) - 1: + return 1.0 + return round(min(0.98, 0.62 + 0.28 * float(quality["score"]) + 0.01 * index), 3) def collect_report( @@ -407,6 +539,8 @@ def collect_report( blob_exists = bool(artifact_path and store.container.get_blob_client(artifact_path).exists()) expected_event_delta = 3 * len(task_ids) + 1 continuity_edges = [f"{'START' if index == 0 else CHAIN_STEPS[index - 1]['marker']}->{step['marker']}" for index, step in enumerate(CHAIN_STEPS)] + output_quality = output_quality_by_kind(outputs_by_kind) + consensus = run_output_quality_consensus(outputs_by_kind) checks = [ { @@ -426,6 +560,11 @@ def collect_report( "passed": outputs_have_markers_and_links(outputs_by_kind), "evidence": "each output must include own marker and previous marker", }, + { + "name": "all_outputs_pass_quality_gate", + "passed": all(item["passed"] for item in output_quality.values()), + "evidence": output_quality, + }, { "name": "shared_state_chain_cursor_and_summaries", "passed": shared_state.get(f"chain:{run_id}:cursor") == "STEP-07" @@ -468,6 +607,13 @@ def collect_report( and contains_external_code_terms(merged_output), "evidence": "requires FastAPI code-review material plus invariant, counterexample, revision, complexity and acceptance", }, + { + "name": "multi_round_quality_consensus_accepts_chain", + "passed": consensus["converged"] + and consensus["accepted_candidate"] == "accept_external_chain" + and consensus["round_count"] >= 2, + "evidence": consensus, + }, { "name": "final_output_references_external_files", "passed": count_referenced_files(result.accepted_output) >= 5, @@ -505,6 +651,7 @@ def collect_report( "accepted_score": result.accepted_score, "accepted_task_id": result.accepted_task_id, "artifact_path": artifact_path, + "quality_consensus": consensus, }, "discovered_models": discovered_models, "selected_models": selected_models, @@ -522,6 +669,97 @@ def outputs_have_markers_and_links(outputs_by_kind: dict[str, str]) -> bool: return True +def output_quality_by_kind(outputs_by_kind: dict[str, str]) -> dict[str, dict[str, object]]: + quality: dict[str, dict[str, object]] = {} + for index, step in enumerate(CHAIN_STEPS): + result = assess_output_quality(outputs_by_kind.get(step["capability"], ""), index) + quality[step["capability"]] = { + "marker": step["marker"], + "passed": result["passed"], + "score": round(float(result["score"]), 4), + "missing": result["missing"], + } + return quality + + +def run_output_quality_consensus(outputs_by_kind: dict[str, str]) -> dict[str, object]: + quality = output_quality_by_kind(outputs_by_kind) + continuity_ok = outputs_have_markers_and_links(outputs_by_kind) + all_quality_ok = all(item["passed"] for item in quality.values()) + final_ok = assess_output_quality(outputs_by_kind.get(CHAIN_STEPS[-1]["capability"], ""), len(CHAIN_STEPS) - 1)[ + "passed" + ] + + def candidate_for(passed: bool, round_index: int, *, first_round_probe: bool = False) -> str: + if not passed: + return "repair_required" + if first_round_probe and round_index == 1: + return "second_review_required" + return "accept_external_chain" + + agents = [ + ConsensusAgent( + id="continuity-review-agnet", + role="handoff-continuity", + weight=1.0, + vote=lambda scores, state, round_index: ConsensusVote( + agent_id="continuity-review-agnet", + role="handoff-continuity", + candidate=candidate_for(continuity_ok, round_index), + confidence=0.55 if round_index == 1 else 0.86, + evidence="checks every STEP includes own marker and previous marker", + ), + ), + ConsensusAgent( + id="quality-review-agnet", + role="output-quality", + weight=1.15, + vote=lambda scores, state, round_index: ConsensusVote( + agent_id="quality-review-agnet", + role="output-quality", + candidate=candidate_for(all_quality_ok, round_index), + confidence=0.52 if round_index == 1 else 0.88, + evidence="rejects refusal, role-boundary, off-target and local-project outputs", + ), + ), + ConsensusAgent( + id="convergence-review-agnet", + role="final-convergence", + weight=1.05, + vote=lambda scores, state, round_index: ConsensusVote( + agent_id="convergence-review-agnet", + role="final-convergence", + candidate=candidate_for(final_ok and all_quality_ok, round_index, first_round_probe=True), + confidence=0.61 if round_index == 1 else 0.9, + evidence="requires final output to preserve acceptance, file references and external target", + ), + ), + ] + result = ConsensusSwarm( + agents, + threshold=0.7, + min_margin=0.25, + max_rounds=3, + evaporation=0.82, + ).run("quality gate for external FastAPI Agnet chain") + return { + "accepted_candidate": result.accepted_candidate, + "accepted_score": round(result.accepted_score, 4), + "converged": result.converged, + "round_count": len(result.rounds), + "rounds": [ + { + "index": item.index, + "leader": item.leader, + "leader_share": round(item.leader_share, 4), + "margin": round(item.margin, 4), + "converged": item.converged, + } + for item in result.rounds + ], + } + + def compact_task_rows(task_rows: list[dict[str, object]], task_models: dict[str, str]) -> list[dict[str, object]]: return [ { diff --git a/swarm_minimal/academic_evaluation.py b/swarm_minimal/academic_evaluation.py index e12ea73..c89df80 100644 --- a/swarm_minimal/academic_evaluation.py +++ b/swarm_minimal/academic_evaluation.py @@ -77,7 +77,17 @@ ALGORITHMS_USED = ( { "name": "winner-take-highest-score convergence", "location": "swarm_minimal.core.InMemorySwarmStore.converge", - "description": "the highest-scoring completed task becomes the accepted result", + "description": "the highest-scoring completed task becomes the accepted result after scenario-level quality gates", + }, + { + "name": "quality-aware output scoring", + "location": "examples.run_continuous_reasoning_acceptance.assess_output_quality and score_output", + "description": "refusal, role-boundary, off-target and broken-handoff outputs are penalized before convergence", + }, + { + "name": "retry and fallback model recovery", + "location": "examples.run_continuous_reasoning_acceptance.chat_with_fallback", + "description": "low-quality model output triggers a retry and then fallback model takeover for the same Agnet step", }, { "name": "weighted multi-round consensus", diff --git a/tests/test_model_io_report_audit.py b/tests/test_model_io_report_audit.py index cf02c71..2071324 100644 --- a/tests/test_model_io_report_audit.py +++ b/tests/test_model_io_report_audit.py @@ -29,8 +29,9 @@ class ModelIoReportAuditTests(unittest.TestCase): "#### Agnet / 模型实际输出 task.output(审计摘要)", "#### 接手 / 交接机制", "原始来源:PostgreSQL `swarm_tasks.output`", - "输出质量风险", - "最高分已完成任务", + "质量补救", + "共识质量门", + "quality_attempt=1", "chain_edge=", ]: self.assertIn(required, text) @@ -42,6 +43,7 @@ class ModelIoReportAuditTests(unittest.TestCase): "I appreciate the detailed context", "What I can actually do", "What I cannot do", + "输出质量风险", ]: self.assertNotIn(forbidden, text) diff --git a/tests/test_standard_scenarios.py b/tests/test_standard_scenarios.py index 0c020ab..a4b5812 100644 --- a/tests/test_standard_scenarios.py +++ b/tests/test_standard_scenarios.py @@ -1,7 +1,9 @@ import unittest +from unittest.mock import patch from examples import run_continuous_reasoning_acceptance as continuous from swarm_minimal.core import Agent, InMemorySwarmStore, SwarmCoordinator, Task, TaskStatus +from swarm_minimal.newapi_agnet import NewApiChannelConfig class StandardScenarioTest(unittest.TestCase): @@ -35,16 +37,88 @@ class StandardScenarioTest(unittest.TestCase): final = ( "STEP-07 基于 STEP-06 不变量 风险 下一步 NEWAPI_MODEL 模型发现 验收 " "./.venv/bin/python unittest " + f"{continuous.EXTERNAL_REPO} {continuous.EXTERNAL_REPO_COMMIT} " + "response_model 依赖注入 OpenAPI schema 响应序列化 " + " ".join(continuous.TARGET_FILES[:5]) ) step_six = ( "STEP-06 基于 STEP-05 不变量 风险 下一步 NEWAPI_MODEL 模型发现 复杂度 " + f"{continuous.EXTERNAL_REPO} {continuous.EXTERNAL_REPO_COMMIT} " + "response_model 依赖注入 OpenAPI schema 响应序列化 " + " ".join(continuous.TARGET_FILES[:4]) ) self.assertEqual(continuous.score_output(final, 6), 1.0) self.assertLess(continuous.score_output(step_six, 5), continuous.score_output(final, 6)) + def test_quality_gate_rejects_refusal_and_penalizes_score(self) -> None: + refusal = ( + "chain_edge=STEP-01->STEP-02; " + f"target_repo={continuous.EXTERNAL_REPO}; target_commit={continuous.EXTERNAL_REPO_COMMIT}\n" + "I appreciate the detailed context, but I need to clarify my actual role." + ) + + quality = continuous.assess_output_quality(refusal, 1) + + self.assertFalse(quality["passed"]) + self.assertIn("no_refusal_or_role_boundary", quality["missing"]) + self.assertEqual(continuous.score_output(refusal, 1), continuous.QUALITY_RISK_PENALTY_SCORE) + + def test_quality_aware_fallback_switches_model_after_bad_output(self) -> None: + calls = [] + + class FakeAgnet: + def __init__(self, config, *, agent_id, capability): + self.model = config.model + + def chat(self, *, system_prompt, user_prompt): + calls.append((self.model, user_prompt)) + if self.model == "bad-model": + return "I appreciate the detailed context, but I need to clarify my actual role." + return ( + "STEP-02 基于 STEP-01 " + f"{continuous.EXTERNAL_REPO} {continuous.EXTERNAL_REPO_COMMIT} " + "不变量 风险 下一步交接 NEWAPI_MODEL 模型发现 " + "response_model 依赖注入 OpenAPI schema 响应序列化" + ) + + with patch.object(continuous, "NewApiAgnet", FakeAgnet): + output = continuous.chat_with_fallback( + config=NewApiChannelConfig(base_url="https://newapi.example", api_key="test"), + primary_model="bad-model", + fallback_models=["good-model"], + agent_index=2, + step=continuous.CHAIN_STEPS[1], + task=Task(kind="chain_step_02", input="build dependency graph"), + previous_marker="STEP-01", + previous_summary="STEP-01 summary", + ) + + self.assertIn("used_model=good-model", output) + self.assertIn("fallback_after=bad-model:attempt-1:quality_failed", output) + self.assertGreaterEqual(len(calls), 2) + + def test_quality_consensus_requires_multi_round_acceptance(self) -> None: + outputs_by_kind = {} + for index, step in enumerate(continuous.CHAIN_STEPS): + previous_marker = "START" if index == 0 else continuous.CHAIN_STEPS[index - 1]["marker"] + extra = "./.venv/bin/python unittest 验收 " if index == len(continuous.CHAIN_STEPS) - 1 else "" + outputs_by_kind[step["capability"]] = ( + f"chain_edge={previous_marker}->{step['marker']} " + f"{step['marker']} 基于 {previous_marker} " + f"{continuous.EXTERNAL_REPO} {continuous.EXTERNAL_REPO_COMMIT} " + "不变量 风险 反例 修正 下一步交接 NEWAPI_MODEL 模型发现 复杂度 " + "response_model 依赖注入 OpenAPI schema 响应序列化 " + f"{extra}" + + " ".join(continuous.TARGET_FILES[:6]) + ) + + consensus = continuous.run_output_quality_consensus(outputs_by_kind) + + self.assertTrue(consensus["converged"]) + self.assertEqual(consensus["accepted_candidate"], "accept_external_chain") + self.assertGreaterEqual(consensus["round_count"], 2) + def test_deterministic_seven_step_chain_updates_cursor_edges_and_summaries(self) -> None: store = InMemorySwarmStore() run_id = "deterministic-chain"