技术深度:buddyMe 框架任务拆解的 "盲拆" 问题与技能感知优化方案
在 AI 智能体开发中,任务拆解的质量直接决定了复杂任务的执行效率和最终效果。一个优秀的规划器不仅要能把大任务拆成可执行的小步骤,更重要的是要知道自己有什么工具可用,并据此做出最优的拆解决策。
然而在 buddyMe 框架的早期实现中,我们发现了一个普遍存在于绝大多数开源 Agent 项目中的设计缺陷:规划器在进行任务拆解时,完全不知道系统有哪些专业技能可用。
问题本质:"盲拆" 导致的效率与质量双重损失
一般的 Skill 模块只负责子任务的执行,不参与项目的拆解与规划。这种设计在简单场景下工作良好,但在处理复杂专业任务时会暴露出严重问题:
规划器由于上下文限制,只能根据大模型自身的通用能力进行任务拆解,完全不会考虑系统中已经存在的专业技能。这导致拆解出的子任务往往不够专业,甚至会产生大量不必要的重复工作。
典型案例:当用户要求 "设计一个响应式 HTML 登录页面" 时,规划器可能会将其拆分为:
- 编写 HTML 结构
- 添加基础 CSS 样式
- 调整移动端适配
- 优化按钮样式
- 修复布局问题
- 再次调整颜色搭配
而实际上,buddyMe 框架中已经有一个专门的web_page_generator技能,可以一次性完成从需求到完整页面的所有工作。规划器的 "盲拆" 不仅浪费了 token 资源,还导致执行过程中反复进行样式修正,大幅延长了任务完成时间。
根因分析:buddyMe 当前的任务拆解与技能匹配流程
让我们深入代码层面,看看这个问题是如何产生的。
1. 任务拆解阶段(todo_manager.py)
当前plan_task函数的实现非常简洁,但也存在明显的局限性:
try:
messages = [
{"role": "system", "content": "你是一个务实的任务分解助手。"},
{"role": "user", "content": plan_prompt}
]
response = await client.chat(messages=messages)
# 提取所有 type 为 "text" 的内容块并拼接
texts = [b["text"] for b in response["content"] if b["type"] == "text"]
plan_text = "".join(texts).strip()
except Exception:
# 调用失败时降级为只包含原任务
return [user_input]
# 解析:按行分割,过滤空行,去除可能的编号前缀(如 "1. "、"- " 等)
steps = []
for line in plan_text.splitlines():
line = line.strip()
if not line:
continue
# 去除常见编号前缀
if line[0].isdigit() and len(line) > 2 and line[1] in ".、) ":
parts = line.split(maxsplit=1)
if len(parts) > 1:
line = parts[1]
elif line[0] in "-*•▪▫":
line = line[1:].strip()
steps.append(line)
return steps if steps else [user_input]
核心问题:
- plan_task只接收enriched_input(用户输入 + 对话上下文)和 LLM 客户端两个参数
- System Prompt 是硬编码的 "你是一个务实的任务分解助手"
- 规划器完全不知道系统中有哪些 Skill 可用
2. 技能匹配阶段(agent.py:888-912)
技能匹配发生在每个子任务执行之前,而不是规划阶段:
matched_instructions = self._skill_loader.get_matched_instructions(item["text"], max_skills=1)
get_matched_instructions内部调用match_skills()函数,使用关键词重叠评分算法(技能名称权重 3,描述权重 1)来匹配子任务描述。匹配成功后,才会将 Level 2 完整指令体注入到执行上下文中。
流程总结
当前流程:先盲目拆任务 → 再逐个子任务匹配技能
这种 "先拆后配" 的模式会导致两个严重后果:
- 规划器可能把一个 Skill 就能覆盖的工作拆成多个子任务
- 规划器完全不知道某个 Skill 存在,导致子任务需要从零开始实现
解决方案:让规划器 "看见" 所有可用技能
要解决这个问题,我们需要让规划阶段就感知到系统的技能能力。幸运的是,这个改动非常小,只需要修改两个文件。
方案核心思想
在任务分解之前,将所有可用技能的 Level 1 元数据注入到规划器的 Prompt 中。这样规划器在拆解任务时,就可以:
- 优先使用已有技能来完成子任务
- 避免重复造轮子
- 生成更专业、更高效的任务计划
具体实现步骤
1. 修改 todo_manager.py:增加 skill_metadata 参数
在plan_task函数中增加skill_metadata参数,并将其注入到规划 Prompt 中:
async def plan_task(user_input: str, client, skill_metadata: str = "") -> list:
"""
将用户输入的复杂任务分解为可执行的子任务列表
新增:支持传入技能元数据,让规划器了解可用技能
"""
plan_prompt = f"""
将以下任务分解为清晰、可执行的步骤:
{user_input}
可用技能列表:
{skill_metadata}
分解规则:
1. 如果某个步骤可以使用上述可用技能完成,请用 [SKILL:skill-name] 标注该步骤
2. 每个步骤应该是一个独立的、可完成的小任务
3. 步骤数量控制在3-10个之间
4. 保持语言简洁明了
"""
try:
messages = [
{"role": "system", "content": "你是一个务实的任务分解助手,擅长利用已有技能高效完成任务。"},
{"role": "user", "content": plan_prompt}
]
response = await client.chat(messages=messages)
# 提取所有 type 为 "text" 的内容块并拼接
texts = [b["text"] for b in response["content"] if b["type"] == "text"]
plan_text = "".join(texts).strip()
except Exception:
# 调用失败时降级为只包含原任务
return [user_input]
# 解析:按行分割,过滤空行,去除可能的编号前缀(如 "1. "、"- " 等)
steps = []
for line in plan_text.splitlines():
line = line.strip()
if not line:
continue
# 去除常见编号前缀
if line[0].isdigit() and len(line) > 2 and line[1] in ".、) ":
parts = line.split(maxsplit=1)
if len(parts) > 1:
line = parts[1]
elif line[0] in "-*•▪▫":
line = line[1:].strip()
steps.append(line)
return steps if steps else [user_input]
关键改进:
- 新增skill_metadata参数,接收所有可用技能的元数据
- 在规划 Prompt 中明确列出可用技能
- 要求规划器对可以使用技能的步骤进行[SKILL:skill-name]标注
- 更新 System Prompt,强调 "擅长利用已有技能高效完成任务"
2. 修改 agent.py:调用时传入技能元数据
在 Agent 调用plan_task函数时,传入从 SkillLoader 获取的技能元数据:
plans = await todo_manager.plan_task(
enriched_input,
client=self._client,
skill_metadata=self._skill_loader.get_metadata_prompt()
)
配套实现:SkillLoader 的 get_metadata_prompt 方法
为了让这个方案完整工作,我们还需要在SkillLoader类中实现get_metadata_prompt方法,用于生成格式化的技能元数据:
def get_metadata_prompt(self) -> str:
"""生成用于规划器的技能元数据Prompt"""
if not self.skills:
return "暂无可用技能"
metadata = []
for skill_name, skill in self.skills.items():
# 只返回Level 1元数据(名称和简短描述),避免token浪费
metadata.append(f"- {skill_name}: {skill.description}")
return "\n".join(metadata)
优化效果与后续扩展
立即见效的优化
- 任务拆解更专业:规划器会优先使用已有技能,避免重复工作
- 执行效率提升:一个技能就能完成的工作不会被拆成多个子任务
- token 成本降低:减少了不必要的 LLM 调用和上下文传递
- 错误率下降:使用经过验证的专业技能,比大模型从零开始实现更可靠
后续扩展方向
- 技能依赖管理:让规划器了解技能之间的依赖关系,生成更合理的执行顺序
- 技能能力评估:在元数据中加入技能的能力范围和限制,帮助规划器做出更准确的判断
- 动态技能加载:根据任务类型动态加载相关技能,减少规划 Prompt 的长度
- 技能使用统计:记录每个技能的使用频率和成功率,用于后续优化
总结
这个看似微小的改动,实际上解决了 Agent 开发中一个非常普遍的架构问题。绝大多数开源 Agent 项目都采用了 "先拆后配" 的模式,导致规划器无法充分利用系统的能力。
通过让规划器 "看见" 所有可用技能,我们不仅提升了 buddyMe 框架的执行效率和任务质量,也为后续的技能生态建设打下了坚实的基础。这也再次印证了工程开发中的一个真理:好的架构不是一蹴而就的,而是在解决实际问题的过程中不断演进的。