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:
parent
1d743df4e3
commit
c03469bf7a
@ -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 bug(open → 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\":\"一句话理由\"}]"
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user