fix: develop不deliver三修——①限制探索轮数+强制产出 ②实际产出兜底(检测write_file/git) ③巨型任务拆小(粒度控制)

This commit is contained in:
ymq 2026-08-19 22:46:40 +08:00
parent b341a43ad6
commit d84f68dd7e
2 changed files with 54 additions and 2 deletions

View File

@ -493,6 +493,7 @@ PM_SYSTEM_PROMPT = """你是项目经理PM。你的职责是项目计划
## 任务分解与编排(先评估,再拆解)
- 派发前先评估当前任务是否过于复杂涉及多个模块/应用多个独立交付单元工作量超单 agent 一次产出简单任务直接派发单个任务不必强行拆分
- **任务粒度控制巨型任务拆小**单个任务必须聚焦单一交付单元禁止派发一次性实现全部 N 个模块 / 全部表契约 / 脚手架 + DDL 全套这种巨型任务任务过大时 agent 会因工作量大方向迷失而陷入探索死循环反复 read_file/run_shell 却不 write_file/deliver按模块/单元拆成多个小任务每个模块一个 develop 子任务能并行就并行不填 depends_on有依赖就串行depends_on
- 复杂任务 自动分解把大任务拆成多个子任务每个子任务含 titleroledescription子任务默认挂当前里程碑任务名下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

View File

@ -218,7 +218,7 @@ SDL_PROMPT = """你是「开发产线」的驾驶舱 agent负责软件项目
- 用户未提供时在会话中逐步追问明确 ask_user / list_questions不要跳过明确后再创建 requirement 任务
## 典型场景(帮助判断何时用哪个工具,非强制)
- 用户提出新的开发需求/功能"实现XX""XX系统要做XX" 先确认部署环境需求再用 create_task 创建任务可按 requirementdesigndevelop 拆分 start_agents 启动
- 用户提出新的开发需求/功能"实现XX""XX系统要做XX" 先确认部署环境需求再用 create_task 创建任务可按 requirementdesigndevelop 拆分 start_agents 启动**单个任务不要过大**涉及多个模块/多个独立交付单元时按模块拆小每个模块一个 develop 任务禁止一次性派发实现全部 N 个模块/全部表契约/脚手架+DDL 全套这种巨型任务
- 用户问进展/状态 list_tasks / diagnose_project / check_progress
- 用户问待回答问题 list_questions / answer_question
- 用户报告异常/故障 先用 diagnose_project 定位根因再用 task_detail 查详情