fix: 阻塞求助不得当缺陷驳回——bug_flow 前置确定性分流转 need_info

回归修复(我引入):人事项目7 实测 deploy_test 两次 report_bug 上报「env/test.json
待确认无法部署」,被 pm_bug_confirm_run 在 9/13 秒内以「属测试环境问题,非代码缺陷」
自动 reject,PM 收不到求助 → 回退 requirement 重做 → requirement 同样拿不到环境信息
→ 再编占位 → QC 再驳,死循环。

根因:prompt 判据写了「reject:测试环境问题导致的假失败」,LLM 严格照做。但
report_bug 在这里承担的是「向上冒泡求助」,不是「报告代码缺陷」。

修复分两层:
1) 代码层确定性分流(主):_is_blocking_help() 按特征词判定阻塞求助,
   命中者不进 LLM,直接 _escalate_blocking_bugs() 转 need_info 冒泡给人,
   bug 保持 open 等信息补齐;幂等(已有 pending need_info 则跳过)。
2) prompt 兜底(次):删除「测试环境问题→reject」判据,加铁律说明外部输入
   缺口不是可 reject 的非缺陷。

实测验证:2 个真实被误驳文本命中分流,3 个真缺陷文本不命中。
This commit is contained in:
ymq 2026-08-25 21:27:09 +08:00
parent 1d743df4e3
commit c03469bf7a

View File

@ -253,24 +253,104 @@ def _parse_bug_decisions(raw):
return []
_BLOCKING_HELP_PAT = re.compile(
r"待明确|待确认|待补齐|未提供|未配置|缺少环境|环境信息|无法\s*ssh|无法连接|连接不上|"
r"凭据|credentials|placeholder|\bTBD\b|需(要)?(人|PM|运维)(补|确认|提供)|"
r"占位|前置条件不具备|权限不足无法",
re.IGNORECASE)
def _is_blocking_help(bug):
"""判定 bug 是否为「阻塞求助」而非「代码缺陷」。
阻塞求助 = 缺少外部输入环境信息/凭据/配置/权限导致无法执行只有人能解决
这类必须转 need_info 冒泡给人绝不能当非缺陷reject否则求助通道被掐死
2026-08-25 人事项目7 实测deploy_test 两次 report_bug 上报env/test.json 待确认无法部署
PM 确认流程在 9 秒内以属测试环境问题非代码缺陷自动驳回PM 收不到求助
转而回退 requirement 重做 requirement 同样拿不到环境信息 死循环
判定用确定性规则特征词不交给 LLM是求助还是缺陷有明确特征属于机制该管的部分
"""
text = f"{bug.get('title','')} {bug.get('description','') or ''}"
return bool(_BLOCKING_HELP_PAT.search(text))
async def _escalate_blocking_bugs(sor, project_id, help_bugs):
"""把「阻塞求助」类 bug 转成 need_info 冒泡给人bug 保持 open 等人补信息。
幂等同一 bug 已有 pending need_info 冒泡则跳过避免 poller 20 秒刷一条
"""
from .communication import raise_problem
escalated = 0
for b in help_bugs:
bid = b.get('id', '')
if not bid:
continue
# 幂等:该 bug 已冒泡且未答结 → 跳过
dup = await sor.sqlExe(
"SELECT id FROM pipeline_agent_questions "
"WHERE tenant_id=${pid}$ AND status='pending' AND problem_type='need_info' "
"AND question LIKE ${kw}$ LIMIT 1",
{"pid": project_id, "kw": f"%{bid}%"})
await sor.sqlExe("COMMIT", {})
if dup:
continue
question = (
f"【阻塞求助需人工补充信息】Bug {bid}\n"
f"标题:{b.get('title','')}\n"
f"详情:{(b.get('description','') or '').strip()[:600]}\n\n"
f"该问题属于外部输入缺口(环境信息/凭据/配置/权限未提供),"
f"agent 无法自行解决,也不是代码缺陷。请补充所需信息后回答本问题,"
f"流程将自动继续Bug 保持 open 直至信息补齐。"
)
try:
await raise_problem(
"need_info", question, from_role="agent.pm",
from_agentid="bug-flow", tenant_id=project_id,
task_id=b.get('task_id') or '',
first_handler_role="agent.main_agent",
suspend_task=bool(b.get('task_id')))
escalated += 1
logger.info(f"bug_flow 阻塞求助已冒泡给人 bug={bid} project={project_id}")
except Exception as e:
logger.warning(f"bug_flow 冒泡失败 bug={bid}: {e}")
return escalated
async def pm_bug_confirm_run(sor, project_id):
"""PM 独立确认 open bugopen → confirmed/rejected
独立于任务审核循环bug poller 扫到 open bug 时触发本流程PM open bug 清单
逐个判断真缺陷(confirm) / 误报重复(reject)不依赖任务链任何环节
只处理进行中迭代 open bug
前置确定性分流命中阻塞求助特征的 bug 不进 LLM 判断直接转 need_info 冒泡给人
bug 保持 open 等人补信息 reject confirm
"""
open_rows = await sor.sqlExe(
"SELECT b.id, b.title, b.description, b.severity, b.reporter_type, b.iteration_id "
"SELECT b.id, b.title, b.description, b.severity, b.reporter_type, b.iteration_id, b.task_id "
"FROM sd_bugs b JOIN sd_iterations i ON b.iteration_id=i.id "
"WHERE b.status='open' AND i.status='in_progress' AND i.project_id=${pid}$ "
"ORDER BY b.created_at ASC LIMIT 50",
{"pid": project_id})
await sor.sqlExe("COMMIT", {})
if not open_rows:
return {"confirmed": 0, "rejected": 0, "skipped": 0}
return {"confirmed": 0, "rejected": 0, "skipped": 0, "escalated": 0}
all_bugs = [_rec_to_dict(r) for r in open_rows]
# ── 前置分流:阻塞求助 → need_info 冒泡给人,不进 LLM ──
help_bugs = [b for b in all_bugs if _is_blocking_help(b)]
bugs = [b for b in all_bugs if not _is_blocking_help(b)]
escalated = 0
if help_bugs:
escalated = await _escalate_blocking_bugs(sor, project_id, help_bugs)
if not bugs:
return {"confirmed": 0, "rejected": 0, "skipped": 0, "escalated": escalated}
bugs = [_rec_to_dict(r) for r in open_rows]
model, org_id = await _resolve_model_org(sor, project_id)
bug_list = "\n".join(
@ -283,7 +363,10 @@ async def pm_bug_confirm_run(sor, project_id):
"逐个判断每个 Bug真缺陷 → confirm确认进入修复流程误报/重复/非缺陷 → reject驳回\n"
"判断标准:\n"
"- confirm功能错误、代码缺陷、接口返回错误、字段/金额/权限/RBAC 校验错、表结构错、部署失败导致的真实代码问题。\n"
"- reject测试环境问题导致的假失败、重复上报同一问题、非缺陷如文案/样式已符合预期)。\n"
"- reject重复上报同一问题、非缺陷如文案/样式已符合预期)、经核实预期行为本就如此。\n"
"🔴 铁律:**缺少外部输入(环境信息/凭据/配置/权限未提供)导致无法执行,绝不是可 reject 的「非缺陷」**——"
"那是需要人补信息的阻塞求助,必须保留待处理,不得驳回。此类已由系统前置分流,你收到的列表里不应再有;"
"若仍见到,一律判 confirm 而非 reject。\n"
"必须对每个 Bug 都给出 decision不要遗漏。\n"
"只输出一个 JSON 数组,格式:[{\"bug_id\":\"...\",\"decision\":\"confirm|reject\",\"comment\":\"一句话理由\"}]"
)