From d84f68dd7ea1f0f098d4463349a9b3aadfb99ad3 Mon Sep 17 00:00:00 2001 From: ymq Date: Wed, 19 Aug 2026 22:46:40 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20develop=E4=B8=8Ddeliver=E4=B8=89?= =?UTF-8?q?=E4=BF=AE=E2=80=94=E2=80=94=E2=91=A0=E9=99=90=E5=88=B6=E6=8E=A2?= =?UTF-8?q?=E7=B4=A2=E8=BD=AE=E6=95=B0+=E5=BC=BA=E5=88=B6=E4=BA=A7?= =?UTF-8?q?=E5=87=BA=20=E2=91=A1=E5=AE=9E=E9=99=85=E4=BA=A7=E5=87=BA?= =?UTF-8?q?=E5=85=9C=E5=BA=95(=E6=A3=80=E6=B5=8Bwrite=5Ffile/git)=20?= =?UTF-8?q?=E2=91=A2=E5=B7=A8=E5=9E=8B=E4=BB=BB=E5=8A=A1=E6=8B=86=E5=B0=8F?= =?UTF-8?q?(=E7=B2=92=E5=BA=A6=E6=8E=A7=E5=88=B6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pipeline_service/agent_loop.py | 54 +++++++++++++++++++++++++++++++- pipeline_service/sdlc_ability.py | 2 +- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/pipeline_service/agent_loop.py b/pipeline_service/agent_loop.py index a3b82f1..f967d12 100644 --- a/pipeline_service/agent_loop.py +++ b/pipeline_service/agent_loop.py @@ -493,6 +493,7 @@ PM_SYSTEM_PROMPT = """你是项目经理(PM)。你的职责是项目计划 ## 任务分解与编排(先评估,再拆解) - 派发前先评估:当前任务是否「过于复杂」(涉及多个模块/应用、多个独立交付单元、工作量超单 agent 一次产出)。简单任务直接派发单个任务,不必强行拆分。 +- **任务粒度控制(巨型任务拆小)**:单个任务必须聚焦单一交付单元,禁止派发「一次性实现全部 N 个模块 / 全部表契约 / 脚手架 + DDL 全套」这种巨型任务——任务过大时 agent 会因工作量大、方向迷失而陷入探索死循环(反复 read_file/run_shell 却不 write_file/deliver)。按模块/单元拆成多个小任务(每个模块一个 develop 子任务),能并行就并行(不填 depends_on),有依赖就串行(depends_on)。 - 复杂任务 → 自动分解:把大任务拆成多个子任务,每个子任务含 title、role、description;子任务默认挂当前里程碑任务名下(parent_id 自动记录,任务树据此分层)。 - 自动编排(能并行并行、不能并行串行): - 无依赖的子任务 → 并行(不填 depends_on,系统同时认领执行)。 @@ -996,6 +997,48 @@ async def _exec_agent_tool(tool, params, workspace_dir): # ── 角色 Agent ── +# 强制产出轮数:前 5 轮允许探索,第 6 轮起 auto-inject 强制产出。 +# 治「探索死循环」——重做场景 + 复杂 workspace(已有大量文件/git 历史)时 develop 的 LLM 会迷失方向, +# 30 轮全耗在 list_files/read_file/run_shell 反复「了解现状 + 验证已有内容」,从不 write_file/deliver。 +_FORCE_PRODUCE_TURN = 5 + +_FORCE_PRODUCE_HINT = ( + "⚠️ 你已经探索了足够多轮(已超过 5 轮)。现在必须立即产出并交付:\n" + "1. 用 write_file 写出实际交付文件(代码/文档/契约/DDL),不要再 read_file / list_files / run_shell / git_status 等探索或检查类工具。\n" + "2. 信息不确定就用你的专业判断给出合理结果,先产出再迭代。\n" + "3. 写完后立即调用 deliver 提交交付件,禁止再调用任何探索类工具。" +) + + +async def _build_fallback_deliverable(workspace_dir, written_files, repos_dir, role): + """循环结束未 deliver 时,检测 write_file/git 实际产出,构造真实交付件给 QC(而非占位符「Agent未产出交付件」)。""" + lines = [] + if written_files: + lines.append("本任务执行期间 write_file 实际写入的文件:") + for f in sorted(set(written_files)): + rel = os.path.relpath(f, workspace_dir) if f.startswith(workspace_dir) else f + lines.append("- " + rel) + if os.path.isdir(repos_dir): + for repo_name in sorted(os.listdir(repos_dir)): + repo_path = os.path.join(repos_dir, repo_name) + if os.path.isdir(os.path.join(repo_path, '.git')): + try: + r = await _run_shell('git status --short', repo_path, 10) + out = (r.get('stdout') or '').strip() + if out: + lines.append(f"git 仓库 [{repo_name}] 工作区变更:") + lines.append(out[:2000]) + except Exception: + pass + if not lines: + lines.append("(本轮未检测到 write_file 或 git 变更——agent 确实未产出)") + return { + "result": "\n".join(lines), + "summary": "Agent 循环结束未调用 deliver,以下为实际产出检测结果(供 QC 判断是否合格)", + "deliverable_type": f"{role}_fallback", + } + + async def role_agent_run(project_id, role, agent_id=None, model_name=None): role, _role_specific, _ = await _resolve_role(project_id, role) if role == 'pm': @@ -1049,6 +1092,7 @@ async def role_agent_run(project_id, role, agent_id=None, model_name=None): deliverable = None ask_question = None + written_files = [] # 本任务执行期间 write_file 实际写入的文件(无 deliver 时的产出兜底) # ── Tool Loop(原生 function calling)── for turn in range(30): @@ -1064,6 +1108,9 @@ async def role_agent_run(project_id, role, agent_id=None, model_name=None): if _st and getattr(_st[0], 'state', '') == 'cancelled': logger.info(f"task cancelled mid-run: {task_id}") return {"status": "cancelled", "task_id": task_id} + # 强制产出:第 6 轮起注入,打断「了解现状/反复验证」探索死循环,强制转向 write_file + deliver + if turn >= _FORCE_PRODUCE_TURN: + msgs.append({"role": "user", "content": _FORCE_PRODUCE_HINT}) try: resp = await llm_call_msgs_native(msgs, tools=tools_schema, model=model_name, temperature=0.4, org_id=org_id) except Exception as e: @@ -1094,6 +1141,8 @@ async def role_agent_run(project_id, role, agent_id=None, model_name=None): break result = await _exec_agent_tool(tool, params, workspace_dir) + if tool == "write_file" and params.get("path"): + written_files.append(os.path.join(workspace_dir, params["path"])) msgs.append({"role": "tool", "tool_call_id": tc.get("id", ""), "content": str(result)}) logger.info(f"role_agent tool_call: {tool} -> {str(result)[:100]}") if deliverable or ask_question: @@ -1113,6 +1162,8 @@ async def role_agent_run(project_id, role, agent_id=None, model_name=None): tool = act.get('tool', '') params = act.get('params', {}) result = await _exec_agent_tool(tool, params, workspace_dir) + if tool == 'write_file' and params.get('path'): + written_files.append(os.path.join(workspace_dir, params['path'])) msgs.append({"role": "assistant", "content": raw}) msgs.append({"role": "user", "content": f"工具 {tool} 结果:\n{result}"}) logger.info(f"role_agent tool_call(text): {tool} -> {str(result)[:100]}") @@ -1131,7 +1182,8 @@ async def role_agent_run(project_id, role, agent_id=None, model_name=None): return {"status": "need_info", "task_id": task_id, "question_id": qid, "question": ask_question} if not deliverable: - deliverable = {"result": "Agent未产出交付件"} + # 兜底:循环结束未 deliver,检测 write_file/git 实际产出构造真实交付件(而非占位符) + deliverable = await _build_fallback_deliverable(workspace_dir, written_files, repos_dir, role) # ── 处理产出 ── from appPublic.uniqueID import getID diff --git a/pipeline_service/sdlc_ability.py b/pipeline_service/sdlc_ability.py index f77c637..d7a3652 100644 --- a/pipeline_service/sdlc_ability.py +++ b/pipeline_service/sdlc_ability.py @@ -218,7 +218,7 @@ SDL_PROMPT = """你是「开发产线」的驾驶舱 agent,负责软件项目 - 用户未提供时,在会话中逐步追问明确(用 ask_user / list_questions),不要跳过;明确后再创建 requirement 任务。 ## 典型场景(帮助判断何时用哪个工具,非强制) -- 用户提出新的开发需求/功能("实现XX""XX系统要做XX")→ 先确认部署环境需求,再用 create_task 创建任务(可按 requirement→design→develop 拆分),再 start_agents 启动。 +- 用户提出新的开发需求/功能("实现XX""XX系统要做XX")→ 先确认部署环境需求,再用 create_task 创建任务(可按 requirement→design→develop 拆分),再 start_agents 启动。**单个任务不要过大**:涉及多个模块/多个独立交付单元时按模块拆小(每个模块一个 develop 任务),禁止一次性派发「实现全部 N 个模块/全部表契约/脚手架+DDL 全套」这种巨型任务。 - 用户问进展/状态 → list_tasks / diagnose_project / check_progress。 - 用户问待回答问题 → list_questions / answer_question。 - 用户报告异常/故障 → 先用 diagnose_project 定位根因,再用 task_detail 查详情。