pipeline-service/pipeline_service/capability_tools.py
ymq 981238b401 feat: capability 工具分发支持全路径模块 + 通用工具聚合层
- exec_capability_tool:module 支持裸名(=pipeline_service.*)或全路径,
  产线自有能力包(如 pipeline_bidding.*)可注册角色工具而宿主零接触
- shared_ability:聚合 SDLC 能力包里的通用任务/交付件/问答/项目工具,
  供新产线能力包复用,不重复实现、不反向依赖具体产线
2026-08-26 22:54:00 +08:00

309 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""能力工具注入capability → 工具列表 从产线状态机规范 frontmatter 读取(可配置)。
三层:
1. 产线状态机规范common/feature 等frontmatter 声明 `capability: feature_capability` + `tools: [propose_feature, ...]`
—— 这是「配置」:哪个 capability 含哪些工具,改 frontmatter 即可增删,不用改代码。
2. 角色技能roles/{role}/rolefrontmatter 声明 `capability: feature_capability`
—— 角色声明「我需要这个能力」,系统据此注入该能力下的全部工具。
3. 本模块:工具 schema分发模块/描述/参数)硬编码在此(代码不可避免),
但 capability→工具列表 由第 1 层 frontmatter 决定(可配置)。
系统上下文project_id/iteration_id/who/agent_id由分发函数自动注入不暴露给 LLM。
"""
import inspect
import importlib
import logging
logger = logging.getLogger("pipeline")
# 工具 schema分发模块 + 描述 + 参数)——代码层,不可避免。
# 每个工具名对应某个 capability 模块里的同名函数。参数只暴露 LLM 可见的业务参数,
# project_id/iteration_id/who/agent_id 由 exec 自动注入。
TOOL_SCHEMAS = {
# ── feature_capability ──
"propose_feature": {
"module": "feature_capability",
"description": "提出功能点(拆解需求提功能),落库 sd_featuresstatus=proposed。每个业务域的功能都要提",
"params": {"feature_name": "功能名称", "description": "功能描述(输入/处理/输出/验收标准)",
"priority": "优先级 P0/P1/P2默认 P2", "feature_type": "功能类型(默认 new_feature"},
"required": ["feature_name"],
},
"list_features": {
"module": "feature_capability",
"description": "列出功能清单sd_features可按状态/迭代过滤",
"params": {"status": "状态(可选 proposed/approved/...)", "iteration_id": "迭代ID(可选)"},
"required": [],
},
"approve_feature": {
"module": "feature_capability",
"description": "审批功能proposed → approved",
"params": {"feature_id": "功能ID", "comment": "审批意见(可选)"},
"required": ["feature_id"],
},
"reject_feature": {
"module": "feature_capability",
"description": "驳回功能proposed/approved → rejected",
"params": {"feature_id": "功能ID", "comment": "驳回原因"},
"required": ["feature_id"],
},
"start_feature": {
"module": "feature_capability",
"description": "开始开发功能approved → in_progress",
"params": {"feature_id": "功能ID"},
"required": ["feature_id"],
},
"deliver_feature": {
"module": "feature_capability",
"description": "交付功能in_progress → delivered",
"params": {"feature_id": "功能ID"},
"required": ["feature_id"],
},
"verify_feature": {
"module": "feature_capability",
"description": "验收功能delivered → verified",
"params": {"feature_id": "功能ID"},
"required": ["feature_id"],
},
"reopen_feature": {
"module": "feature_capability",
"description": "重新打开功能rejected/verified → proposed回炉",
"params": {"feature_id": "功能ID", "comment": "重开原因(可选)"},
"required": ["feature_id"],
},
# ── test_plan_capability ──
"create_test_plan": {
"module": "test_plan_capability",
"description": "创建测试计划,落库 sd_test_plansstatus=draft",
"params": {"plan_name": "计划名称", "plan_type": "类型(默认 functional)", "scope": "测试范围",
"environment": "测试环境", "entry_criteria": "准入标准", "exit_criteria": "准出标准"},
"required": ["plan_name"],
},
"list_plans": {
"module": "test_plan_capability",
"description": "列出测试计划sd_test_plans",
"params": {"status": "状态(可选)"},
"required": [],
},
# ── test_case_capability ──
"create_case": {
"module": "test_case_capability",
"description": "创建测试用例,落库 sd_test_casesstatus=pending",
"params": {"plan_id": "测试计划ID", "case_name": "用例名称", "case_type": "类型(默认 functional)",
"priority": "优先级(默认 P2)", "precondition": "前置条件", "steps": "执行步骤",
"expected_result": "预期结果"},
"required": ["plan_id", "case_name"],
},
"pass_case": {
"module": "test_case_capability",
"description": "用例通过pending/fail → pass",
"params": {"case_id": "用例ID", "plan_id": "计划ID", "actual_result": "实际结果"},
"required": ["case_id", "plan_id"],
},
"fail_case": {
"module": "test_case_capability",
"description": "用例失败pending/pass → fail失败后应 report_bug 建 Bug",
"params": {"case_id": "用例ID", "plan_id": "计划ID", "actual_result": "实际结果(与预期不符处)"},
"required": ["case_id", "plan_id"],
},
"skip_case": {
"module": "test_case_capability",
"description": "用例跳过pending → skipped注明原因",
"params": {"case_id": "用例ID", "plan_id": "计划ID", "actual_result": "跳过原因"},
"required": ["case_id", "plan_id"],
},
"block_case": {
"module": "test_case_capability",
"description": "用例阻塞pending → blocked环境/依赖无法执行)",
"params": {"case_id": "用例ID", "plan_id": "计划ID", "actual_result": "阻塞原因"},
"required": ["case_id", "plan_id"],
},
"list_cases": {
"module": "test_case_capability",
"description": "列出测试用例sd_test_cases",
"params": {"plan_id": "计划ID", "status": "状态(可选)"},
"required": ["plan_id"],
},
# ── bug_capability ──
"report_bug": {
"module": "bug_capability",
"description": "上报 Bug落库 sd_bugsstatus=open用例 fail 后应调用)",
"params": {"title": "Bug 标题", "description": "Bug 描述", "severity": "严重度 critical/major/minor(默认 major)",
"priority": "优先级(默认 P1)", "case_id": "关联用例ID(可选)", "step_name": "发现步骤(可选)"},
"required": ["title"],
},
"list_bugs": {
"module": "bug_capability",
"description": "列出 Bugsd_bugs",
"params": {"status": "状态(可选)", "severity": "严重度(可选)"},
"required": [],
},
"classify_task": {
"module": "bug_capability",
"description": "判断任务来源new_dev(新开发)/bug_fix(修复Bug)/rework(质检重做)。develop 接任务后先调用,只有 bug_fix 才走 start_fix→fix_bug 状态机rework/new_dev 不调 bug 工具",
"params": {"task_id": "任务ID(可选,默认当前任务)"},
"required": [],
},
"start_fix": {
"module": "bug_capability",
"description": "开始修复 Bugopen/confirmed → fixing记录处理人。回退重做/修复 Bug 任务里修完代码必须调用open 状态也可直接修复PM review_rollback 回退时不走 confirm_bug",
"params": {"bug_id": "Bug ID", "assignee_id": "处理人(可选)"},
"required": ["bug_id"],
},
"fix_bug": {
"module": "bug_capability",
"description": "修复完成 Bugfixing → fixed附修复说明+提交)。修完代码必须调用,把对应 bug 标记 fixed否则 PM 查不到 fixed bug 无法派重新部署、任务链断",
"params": {"bug_id": "Bug ID", "fix_description": "修复说明", "fix_commit": "修复提交(可选)"},
"required": ["bug_id"],
},
"verify_bug": {
"module": "bug_capability",
"description": "验证 Bug 修复fixed → verified记录验证人",
"params": {"bug_id": "Bug ID", "verified_by": "验证人(可选)"},
"required": ["bug_id"],
},
"close_bug": {
"module": "bug_capability",
"description": "关闭 Bugverified → closed",
"params": {"bug_id": "Bug ID"},
"required": ["bug_id"],
},
"confirm_bug": {
"module": "bug_capability",
"description": "确认 Bugopen → confirmed确认缺陷有效准备派发修复",
"params": {"bug_id": "Bug ID"},
"required": ["bug_id"],
},
"reject_bug": {
"module": "bug_capability",
"description": "驳回 Bugopen/confirmed → rejected附驳回原因如误报/重复/非缺陷)",
"params": {"bug_id": "Bug ID", "comment": "驳回原因"},
"required": ["bug_id"],
},
"reopen_bug": {
"module": "bug_capability",
"description": "重开 Bugclosed/rejected → open复测未通过或驳回后重新处理",
"params": {"bug_id": "Bug ID", "comment": "重开原因(可选)"},
"required": ["bug_id"],
},
# ── deploy_capability ──
"configure_env": {
"module": "deploy_capability",
"description": "配置部署环境,落库 sd_deploy_envsstatus=configured",
"params": {"env_type": "环境类型 test/prod", "host": "目标机主机", "user": "SSH 用户",
"deploy_path": "部署路径", "port": "SSH 端口(默认 22)", "ssh_key_path": "SSH 密钥路径(可选)",
"sudo_enabled": "是否 sudo(可选)", "python_path": "Python 路径(可选)", "db_host": "DB 主机(可选)",
"db_port": "DB 端口(可选)", "db_name": "DB 名(可选)", "db_user": "DB 用户(可选)",
"db_password": "DB 密码(可选)"},
"required": ["env_type", "host", "user", "deploy_path"],
},
"verify_env": {
"module": "deploy_capability",
"description": "验证部署环境可用configured → verified",
"params": {"env_id": "环境ID"},
"required": ["env_id"],
},
"fail_env": {
"module": "deploy_capability",
"description": "标记部署环境不可用(部署前检查失败时调用)",
"params": {"env_id": "环境ID", "reason": "不可用原因"},
"required": ["env_id"],
},
"list_envs": {
"module": "deploy_capability",
"description": "列出部署环境sd_deploy_envs",
"params": {"env_type": "类型(可选 test/prod)", "status": "状态(可选)"},
"required": [],
},
}
def _capability_to_tools_map(merged_skills):
"""从技能 frontmatter 的 capability + tools 声明,建立 capability → 工具名列表。
「可配置」的核心产线状态机规范common/feature 等frontmatter 写
`capability: feature_capability` + `tools: [propose_feature, ...]`
这里读取该声明。改 frontmatter 即可增删 capability 下的工具,不用改代码。
"""
m = {}
for skill in merged_skills.values():
cap = getattr(skill, "capability", "") or ""
tools = getattr(skill, "tools", []) or []
if not cap:
continue
m.setdefault(cap, [])
for t in tools:
if t not in m[cap]:
m[cap].append(t)
return m
def resolve_capability_tools(capabilities, merged_skills):
"""根据角色声明的 capability 集合 + 技能 frontmatter 映射,返回工具定义列表。
capabilities: 角色技能 frontmatter 声明的 capability 集合
merged_skills: 角色可见技能get_merged 结果)
返回 [{name, description, params, required}, ...](去重)
"""
cap_map = _capability_to_tools_map(merged_skills)
tools = []
seen = set()
for cap in (capabilities or []):
for tool_name in cap_map.get(cap, []):
schema = TOOL_SCHEMAS.get(tool_name)
if not schema or tool_name in seen:
continue
seen.add(tool_name)
tools.append({
"name": tool_name,
"description": schema["description"],
"params": schema["params"],
"required": schema["required"],
})
return tools
async def exec_capability_tool(tool_name, params, ctx):
"""分发能力工具。ctx: {project_id, iteration_id, who, agent_id}。"""
schema = TOOL_SCHEMAS.get(tool_name)
if not schema:
return f"FAIL: 未注册的能力工具 {tool_name}"
p = dict(params or {})
p.setdefault("project_id", ctx.get("project_id", ""))
p.setdefault("iteration_id", ctx.get("iteration_id", ""))
p.setdefault("who", ctx.get("who", ""))
p.setdefault("agent_id", ctx.get("agent_id", ""))
p.setdefault("task_id", ctx.get("task_id", ""))
try:
# module 支持两种写法:裸名(如 'feature_capability'pipeline-service 包内)
# 或全路径(如 'pipeline_bidding.bid_tender_capability',产线自有能力包)。
# 产线能力模块放自己仓库:宿主不装该产线就零接触,不污染引擎包。
modname = schema['module']
if '.' not in modname:
modname = "pipeline_service." + modname
mod = importlib.import_module(modname)
fn = getattr(mod, tool_name)
sig = inspect.signature(fn)
kwargs = {k: v for k, v in p.items() if k in sig.parameters}
result = await fn(**kwargs)
if isinstance(result, tuple) and len(result) == 2:
ok, msg = result
return f"{'OK' if ok else 'FAIL'}: {msg}"
if isinstance(result, list):
if not result:
return "(空)"
lines = []
for item in result[:50]:
if hasattr(item, "keys"):
lines.append(str({k: getattr(item, k, "") for k in item.keys()}))
else:
lines.append(str(item))
return "\n".join(lines)
return str(result)
except Exception as e:
logger.exception(f"exec_capability_tool {tool_name} failed")
return f"ERROR: {str(e)[:300]}"