为什么 AI 需要学会拆解任务?
一个让AI 崩溃的真实场景
用户说了一句看似简单的话:
text
"找出本月修改次数最多的文件,备份到桌面"
人类的直觉处理
- 我知道"本月"是什么意思(时间范围)。
- 我知道"修改次数最多"意味着要统计。
- 我知道"备份"是复制操作。
- 我凭经验知道执行顺序。
AI 面临的挑战
- 没有"文件系统"的直觉。
- 不知道"本月"在代码中怎么表示。
- 不知道"修改次数"从哪获取。
- 不知道"最多"需要比较。
核心矛盾
自然语言 vs 计算机指令
| 维度 | 自然语言(用户) | 计算机指令(AI需要产出) |
|---|---|---|
| 表达方式 | 声明式(说什么) | 命令式(怎么做) |
| 结构 | 模糊、隐含 | 精确、显式 |
| 依赖 | 隐含依赖 | 明确依赖 |
| 粒度 | 粗粒度 | 原子操作 |
解决方案:任务拆解
让 AI 像项目经理一样,把一句话的需求分解成多个可执行的任务列表。例如:用户的原始需求是:"找出本月修改次数最多的文件,备份到桌面":
- 获取当前目录所有文件列表。
- 获取每个文件的修改历史。
- 筛选本月内的修改记录。
- 统计每个文件的修改次数。
- 比较找出修改次数最多的文件。
- 复制该文件到桌面。
- 返回操作结果。
什么是复杂任务拆解?
拆解的关键特征
- 原子性:每个步骤不可再分,可直接执行。
- 有序性:步骤之间有明确的先后顺序。
- 可验证性:每个步骤的输出可被验证。
- 可组合性:子任务可以复用。
拆解的三个层次
层次1: 任务理解
- 意图识别:用户想做什么?
- 约束提取:有时间限制吗?范围是什么?
- 隐含需求:用户没说但需要的(如错误处理、反馈)
层次2: 任务分解
- 将目标拆分为 3-7 个逻辑子任务。
- 标注子任务间的依赖关系。
- 识别可并行执行的任务。
层次3: 原子操作
- 将每个子任务映射到具体工具。
- 确定工具参数和调用顺序。
- 设计输入输出格式。
为什么拆解如此重要?
| 维度 | 无拆解 | 有拆解 |
|---|---|---|
| 成功率 | 低(一步到位容易出错) | 高(每步可验证) |
| 可调试性 | 无法定位问题 | 可定位到具体步骤 |
| 可复用性 | 每个任务单独处理 | 子任务可复用 |
| 可解释性 | 黑盒 | 每步可见 |
| 错误恢复 | 全部重来 | 从失败步骤恢复 |
任务拆解的5种策略模式
策略一:顺序拆解
适用于步骤有明确的前后依赖关系的场景。
顺序拆解示例
text
查询 -─▶ 统计 ──▶ 比较 ──▶ 操作
每个步骤的输出是下一步的输入!
代码示例
javascript
async function sequentialPlan() {
const files = await listFiles() // Step 1
const stats = await countModifications(files) // Step 2
const max = await findMax(stats) // Step 3
const result = await backup(max) // Step 4
return result
}
策略二:并行拆解
适用于存在独立、无依赖关系的子任务的场景。
并行拆解示例
graph TD
Start([任务拆解]) --> Query1[查北京天气]
Start --> Query2[查上海天气]
Start --> Query3[查武汉天气]
Query1 --> Merge[合并结果]
Query2 --> Merge
Query3 --> Merge
Merge --> End([结束])
代码示例
javascript
async function parallelPlan() {
const [beijing, shanghai, shenzhen] = await Promise.all([
getWeather("北京"),
getWeather("上海"),
getWeather("武汉")
])
return { beijing, shanghai, shenzhen }
}
策略三:条件拆解
适用于有决策点的任务的场景。
条件拆解示例
graph TD
Start([获取天气]) --> Decision{判断天气}
Decision -->|下雨| Rain[提醒带伞
建议穿雨鞋] Decision -->|晴天| Sunny[推荐公园
注意防晒] Decision -->|下雪| Snow[提醒保暖
注意路滑] Rain --> End([结束]) Sunny --> End Snow --> End
建议穿雨鞋] Decision -->|晴天| Sunny[推荐公园
注意防晒] Decision -->|下雪| Snow[提醒保暖
注意路滑] Rain --> End([结束]) Sunny --> End Snow --> End
代码示例
javascript
async function conditionalPlan() {
const weather = await getWeather("北京")
if (weather.condition === "rain") {
return "下雨了,记得带伞,建议穿雨鞋"
} else if (weather.condition === "sunny") {
const park = await recommendPark("北京")
return `天气好,推荐去${park},注意防晒`
} else if (weather.condition === "snowy") {
return `下雪了,记得多穿点衣服,注意保暖,防止路滑`
}
}
策略四:循环拆解
适用于需要批量处理相同操作的场景。
循环拆解示例
graph TD
Start([获取文件列表]) --> Decision{还有文件
未处理?} Decision -->|是| Process[处理文件] Decision -->|否| End([结束]) Process --> Decision
未处理?} Decision -->|是| Process[处理文件] Decision -->|否| End([结束]) Process --> Decision
代码示例
javascript
async function loopPlan(files) {
const results = []
for (const file of files) {
const result = await processFile(file)
results.push(result)
}
return results
}
策略五:聚合拆解
适用于需要汇总多个结果的场景。
聚合拆解示例
graph TD
File1[文件1统计] --> Merge[比较聚合]
File2[文件2统计] --> Merge
File3[文件3统计] --> Merge
Merge --> Output[输出结果]
代码示例
javascript
async function aggregatePlan() {
// 并行执行多个统计
const stats = await Promise.all(
files.map(file => countModifications(file))
)
// 聚合比较
const max = stats.reduce((max, current) =>
current.count > max.count ? current : max
)
return max
}
让 AI 学会拆解的 Prompt 技术
技术一:显式要求拆解
text
在回答问题前,请先将任务拆解为具体步骤:
1. 列出所有需要的子任务
2. 说明每个子任务的作用
3. 标注子任务间的依赖关系
4. 确认所有子任务都能执行
任务: {task}
技术二:提供拆解模板
text
请使用以下模板拆解任务:
### 任务分析
- 目标: [一句话总结]
- 约束: [时间、范围、条件]
- 隐含需求: [用户没明说但需要的]
### 子任务列表
| ID | 子任务 | 依赖 | 工具 | 输出 |
|----|--------|------|------|------|
| 1 | ... | 无 | ... | ... |
| 2 | ... | 1 | ... | ... |
| 3 | ... | 1,2 | ... | ... |
### 异常处理
- 如果步骤1失败,则...
- 如果步骤2无结果,则...
技术三:Few-shot 示例
text
## 示例1:文件备份任务
用户输入:找出本月修改次数最多的文件,备份到桌面
拆解结果:
1. [查询] 获取当前目录所有文件列表
2. [筛选] 获取每个文件的修改历史,筛选本月记录
3. [统计] 统计每个文件的修改次数
4. [比较] 找出修改次数最多的文件
5. [处理并列] 如果有多个并列,询问用户选择
6. [执行] 复制文件到桌面
7. [反馈] 返回备份结果
## 示例2:代码质量分析任务
用户输入:分析项目代码,找出圈复杂度超过10的函数
拆解结果:
1. [扫描] 扫描项目所有代码文件
2. [解析] 解析每个文件的AST
3. [提取] 提取所有函数定义
4. [计算] 计算每个函数的圈复杂度
5. [筛选] 筛选复杂度>10的函数
6. [生成] 生成报告(文件、函数名、复杂度值)
## 当前任务
{task}
技术四:思维链引导
text
请按以下思路思考任务拆解:
1. **目标分析**:用户想要达到什么目的?
2. **信息需求**:完成这个目的需要哪些信息?
3. **信息获取**:如何获取这些信息?(需要哪些工具)
4. **信息处理**:获取信息后如何处理?(计算、比较、筛选)
5. **结果呈现**:最后如何呈现结果?
6. **异常处理**:什么情况会出错?如何处理?
任务: {task}
实战:任务拆解 Agent 实现
完整的拆解 Agent 代码
typescript
import axios from 'axios'
import dotenv from 'dotenv'
dotenv.config()
// ==================== 类型定义 ====================
interface SubTask {
id: string
description: string
tool?: string
dependsOn: string[]
fallback?: string
output?: string
}
interface TaskPlan {
originalTask: string
goal: string
constraints: string[]
implicitNeeds: string[]
steps: SubTask[]
exceptions: Array<{
condition: string
action: string
}>
}
interface TaskUnderstanding {
goal: string
constraints: string[]
implicitNeeds: string[]
complexity: 'simple' | 'moderate' | 'complex'
estimatedSteps: number
}
interface LLMResponse {
choices: Array<{
message: {
content: string
}
}>
}
// ==================== 任务拆解Agent ====================
class TaskDecompositionAgent {
private llm: any
private apiKey: string
private apiUrl: string
constructor() {
this.apiKey = process.env.DEEPSEEK_API_KEY || ''
this.apiUrl = process.env.DEEPSEEK_API_URL || 'https://api.deepseek.com/v1/chat/completions'
if (!this.apiKey) {
throw new Error('请设置 DEEPSEEK_API_KEY 环境变量')
}
}
/**
* 核心方法:将用户输入拆解为可执行计划
*/
async decompose(userInput: string): Promise<TaskPlan> {
try {
// 步骤1:任务理解
const understanding = await this.understandTask(userInput)
// 步骤2:任务分解
const plan = await this.decomposeIntoSteps(understanding, userInput)
// 步骤3:验证计划
const validatedPlan = await this.validatePlan(plan)
// 步骤4:优化计划(并行化、缓存等)
const optimizedPlan = await this.optimizePlan(validatedPlan)
return optimizedPlan
} catch (error) {
console.error('任务拆解失败:', error)
// 返回降级计划
return this.createFallbackPlan(userInput)
}
}
private async understandTask(userInput: string): Promise<TaskUnderstanding> {
const prompt = `
你是一个任务分析专家。请分析用户的任务,提取关键信息。
用户输入: ${userInput}
请输出JSON格式,不要包含其他文字:
{
"goal": "一句话总结任务目标",
"constraints": ["时间约束", "范围约束", "条件约束"],
"implicitNeeds": ["用户没说但需要的", "如错误处理、反馈等"],
"complexity": "simple | moderate | complex",
"estimatedSteps": 预计需要多少步骤
}
`
const response = await this.callLLM(prompt)
try {
// 清理响应,提取JSON部分
const jsonMatch = response.match(/\{[\s\S]*\}/)
if (!jsonMatch) {
throw new Error('无法从LLM响应中提取JSON')
}
return JSON.parse(jsonMatch[0]) as TaskUnderstanding
} catch (error) {
console.error('解析任务理解失败:', error)
// 返回默认理解
return {
goal: userInput,
constraints: [],
implicitNeeds: ['错误处理', '结果反馈'],
complexity: 'moderate',
estimatedSteps: 3
}
}
}
private async decomposeIntoSteps(
understanding: TaskUnderstanding,
originalTask: string
): Promise<TaskPlan> {
const prompt = `
基于以下任务分析,将其拆解为可执行的子任务列表。
任务分析:
- 目标: ${understanding.goal}
- 约束: ${understanding.constraints.join(', ')}
- 隐含需求: ${understanding.implicitNeeds.join(', ')}
## 拆解要求
1. 每个子任务应该是原子操作(不可再分)
2. 使用数字ID(1, 2, 3...)标识子任务
3. 明确标注子任务间的依赖关系
4. 为关键子任务设计异常处理
5. 考虑可并行执行的子任务
## 输出格式(JSON)
{
"steps": [
{
"id": "1",
"description": "子任务描述",
"tool": "推荐使用的工具名",
"dependsOn": [],
"fallback": "失败时的备选方案",
"output": "预期输出"
}
],
"exceptions": [
{
"condition": "异常条件",
"action": "处理动作"
}
]
}
请直接输出JSON,不要添加其他文字:
`
const response = await this.callLLM(prompt)
try {
const jsonMatch = response.match(/\{[\s\S]*\}/)
if (!jsonMatch) {
throw new Error('无法从LLM响应中提取JSON')
}
const parsed = JSON.parse(jsonMatch[0])
return {
originalTask: originalTask,
goal: understanding.goal,
constraints: understanding.constraints,
implicitNeeds: understanding.implicitNeeds,
steps: parsed.steps || [],
exceptions: parsed.exceptions || []
}
} catch (error) {
console.error('解析任务分解失败:', error)
// 返回默认计划
return this.createFallbackPlan(originalTask)
}
}
private async validatePlan(plan: TaskPlan): Promise<TaskPlan> {
// 检查完整性
if (!plan.steps || plan.steps.length === 0) {
throw new Error('计划为空')
}
// 检查步骤ID唯一性
const ids = plan.steps.map(s => s.id)
if (new Set(ids).size !== ids.length) {
throw new Error('步骤ID不唯一')
}
// 检查依赖关系
const allIds = new Set(plan.steps.map(s => s.id))
for (const step of plan.steps) {
if (step.dependsOn) {
for (const dep of step.dependsOn) {
if (!allIds.has(dep)) {
throw new Error(`步骤 ${step.id} 依赖不存在的步骤 ${dep}`)
}
}
}
}
// 检查循环依赖
this.checkCyclicDependency(plan.steps)
// 检查原子性(只警告,不抛出错误)
for (const step of plan.steps) {
if (this.isTooCoarse(step.description)) {
console.warn(`⚠️ 步骤 ${step.id} 可能过粗: ${step.description}`)
}
}
// 为没有fallback的步骤添加默认fallback
for (const step of plan.steps) {
if (!step.fallback) {
step.fallback = `重试步骤 ${step.id} 或跳过并记录错误`
}
}
return plan
}
private checkCyclicDependency(steps: SubTask[]): void {
const graph = new Map<string, string[]>()
for (const step of steps) {
graph.set(step.id, step.dependsOn || [])
}
const visited = new Set<string>()
const recursionStack = new Set<string>()
const hasCycle = (node: string): boolean => {
visited.add(node)
recursionStack.add(node)
const deps = graph.get(node) || []
for (const dep of deps) {
if (!visited.has(dep)) {
if (hasCycle(dep)) return true
} else if (recursionStack.has(dep)) {
return true
}
}
recursionStack.delete(node)
return false
}
for (const step of steps) {
if (!visited.has(step.id)) {
if (hasCycle(step.id)) {
throw new Error(`检测到循环依赖,涉及步骤: ${step.id}`)
}
}
}
}
private isTooCoarse(description: string): boolean {
// 启发式判断:如果描述中包含"处理"、"分析"等模糊词汇,可能过粗
const coarseWords = ['处理', '分析', '操作', '管理', '进行', '执行']
return coarseWords.some(word => description.includes(word))
}
private async optimizePlan(plan: TaskPlan): Promise<TaskPlan> {
// 识别可并行的步骤
const parallelGroups = this.identifyParallelGroups(plan.steps)
// 为并行组添加元数据(不修改tool字段,避免破坏原有逻辑)
const optimizedSteps = plan.steps.map(step => ({
...step,
parallelWith: parallelGroups.find(group => group.includes(step.id))?.filter(id => id !== step.id) || []
}))
// 重新排序步骤:将可并行的步骤放在相邻位置
const reorderedSteps = this.reorderStepsForParallel(optimizedSteps, parallelGroups)
return {
...plan,
steps: reorderedSteps
}
}
private identifyParallelGroups(steps: SubTask[]): string[][] {
const groups: string[][] = []
const processed = new Set<string>()
let remainingSteps = [...steps]
while (remainingSteps.length > 0) {
// 找出所有依赖都已满足的步骤
const parallelGroup: string[] = []
for (const step of remainingSteps) {
const hasUnmetDeps = step.dependsOn.some(dep => !processed.has(dep))
if (!hasUnmetDeps) {
parallelGroup.push(step.id)
}
}
if (parallelGroup.length === 0) {
// 如果没有可执行的步骤,说明有循环依赖或依赖错误
console.warn('无法识别并行组,可能存在依赖问题')
break
}
groups.push(parallelGroup)
for (const id of parallelGroup) {
processed.add(id)
}
// 移除已处理的步骤
remainingSteps = remainingSteps.filter(step => !processed.has(step.id))
}
return groups
}
private reorderStepsForParallel(steps: SubTask[], parallelGroups: string[][]): SubTask[] {
const reordered: SubTask[] = []
const stepMap = new Map(steps.map(s => [s.id, s]))
for (const group of parallelGroups) {
// 按原顺序添加同组的步骤
for (const id of group) {
const step = stepMap.get(id)
if (step) {
reordered.push(step)
}
}
}
return reordered
}
private createFallbackPlan(userInput: string): TaskPlan {
return {
originalTask: userInput,
goal: userInput,
constraints: [],
implicitNeeds: ['错误处理'],
steps: [
{
id: '1',
description: `执行任务: ${userInput}`,
dependsOn: [],
fallback: '任务失败,请手动处理',
output: '执行结果'
}
],
exceptions: [
{
condition: '任何步骤失败',
action: '记录错误并终止执行'
}
]
}
}
private async callLLM(prompt: string): Promise<string> {
if (!this.apiKey) {
throw new Error('请设置 DEEPSEEK_API_KEY 环境变量')
}
try {
// 修复:添加必要的headers
const response = await axios.post<LLMResponse>(
this.apiUrl,
{
model: 'deepseek-chat',
messages: [{ role: 'user', content: prompt }],
temperature: 0.3, // 低温度,保证拆解稳定
max_tokens: 2000
},
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`
},
timeout: 30000 // 30秒超时
}
)
const content = response.data.choices[0]?.message?.content
if (!content) {
throw new Error('LLM返回空响应')
}
return content
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response) {
// 服务器返回了错误状态码
throw new Error(`API调用失败 [${error.response.status}]: ${error.response.data?.error?.message || error.message}`)
} else if (error.request) {
// 请求已发送但没有收到响应
throw new Error(`API调用失败: 无响应 (${error.message})`)
} else {
// 请求配置出错
throw new Error(`API调用失败: ${error.message}`)
}
}
throw error
}
}
/**
* 获取任务的执行顺序(拓扑排序)
*/
getExecutionOrder(plan: TaskPlan): string[][] {
return this.identifyParallelGroups(plan.steps)
}
/**
* 获取任务的统计信息
*/
getStats(plan: TaskPlan): {
totalSteps: number
parallelGroups: number
maxParallelism: number
hasFallbacks: number
} {
const parallelGroups = this.identifyParallelGroups(plan.steps)
const maxParallelism = Math.max(...parallelGroups.map(g => g.length), 0)
const hasFallbacks = plan.steps.filter(s => s.fallback).length
return {
totalSteps: plan.steps.length,
parallelGroups: parallelGroups.length,
maxParallelism,
hasFallbacks
}
}
}
// ==================== 使用示例 ====================
async function main() {
// 检查环境变量
if (!process.env.DEEPSEEK_API_KEY) {
console.error('❌ 错误: 请设置 DEEPSEEK_API_KEY 环境变量')
console.error('示例: export DEEPSEEK_API_KEY="your-api-key"')
process.exit(1)
}
console.log('🚀 启动任务拆解Agent...\n')
const agent = new TaskDecompositionAgent()
const tasks = [
"找出本月修改次数最多的文件,备份到桌面",
"分析项目代码,找出圈复杂度超过10的函数",
"将下载文件夹中超过30天未使用的文件移动到归档文件夹"
]
for (const task of tasks) {
console.log('\n' + '='.repeat(60))
console.log(`📝 原始任务: ${task}`)
console.log('='.repeat(60))
try {
const plan = await agent.decompose(task)
console.log('\n🎯 目标:', plan.goal)
console.log('\n📋 执行计划:')
plan.steps.forEach(step => {
const deps = step.dependsOn.length > 0
? ` (依赖: ${step.dependsOn.join(', ')})`
: ''
console.log(` ${step.id}. ${step.description}${deps}`)
if (step.tool) {
console.log(` 🔧 工具: ${step.tool}`)
}
if (step.fallback) {
console.log(` 🔄 备选: ${step.fallback}`)
}
})
if (plan.exceptions.length > 0) {
console.log('\n⚠️ 异常处理:')
plan.exceptions.forEach(ex => {
console.log(` 如果 ${ex.condition},则 ${ex.action}`)
})
}
// 显示执行顺序
const executionOrder = agent.getExecutionOrder(plan)
console.log('\n⚡ 执行顺序(可并行组):')
executionOrder.forEach((group, idx) => {
console.log(` 组${idx + 1}: [${group.join(', ')}] ${group.length > 1 ? '(可并行)' : '(串行)'}`)
})
// 显示统计信息
const stats = agent.getStats(plan)
console.log('\n📊 统计信息:')
console.log(` 总步骤数: ${stats.totalSteps}`)
console.log(` 并行组数: ${stats.parallelGroups}`)
console.log(` 最大并行度: ${stats.maxParallelism}`)
console.log(` 有备选方案: ${stats.hasFallbacks}`)
} catch (error) {
console.error(`❌ 处理任务失败:`, error)
}
}
}
main().catch(console.error)
运行输出示例
text
============================================================
📝 原始任务: 找出本月修改次数最多的文件,备份到桌面
============================================================
🎯 目标: 找出本月修改最多的文件并备份
📋 执行计划:
1. 获取当前目录所有文件列表
2. 获取每个文件的修改历史记录
3. 筛选本月内的修改记录
4. 统计每个文件的本月修改次数
5. 比较找出修改次数最多的文件 (依赖: 4)
6. 处理并列情况,询问用户选择 (依赖: 5)
7. 复制文件到桌面 (依赖: 6)
8. 返回备份结果 (依赖: 7)
备选: 如果备份失败,提示用户手动检查
⚠️ 异常处理:
如果 步骤3 无结果,则 告知用户本月无修改记录
如果 步骤6 用户取消,则 终止操作并告知
拆解质量评估
质量评估清单
| 维度 | 检查项 | 通过标准 |
|---|---|---|
| 完整性 | 是否覆盖所有用户需求? | 无遗漏 |
| 粒度 | 每个步骤是否原子操作? | 不可再分 |
| 可执行性 | 每个步骤是否有对应工具? | 工具存在 |
| 依赖正确 | 步骤顺序是否合理? | 数据流正确 |
| 异常处理 | 是否考虑失败场景? | 有备选方案 |
| 可验证性 | 是否有中间结果检查点? | 关键步骤可验证 |
常见问题与优化
| 问题 | 表现 | 优化方法 |
|---|---|---|
| 拆解过粗 | 步骤如"处理文件" | 细化:打开→读取→修改→保存 |
| 遗漏步骤 | 忘记备份后的验证 | 增加验证步骤 |
| 依赖错误 | 步骤顺序不对 | 检查数据流向,绘制依赖图 |
| 无异常处理 | 文件不存在直接崩溃 | 增加条件判断和fallback |
| 粒度不一致 | 有的步骤过粗,有的过细 | 统一粒度标准 |
最佳实践清单
- 先理解后拆解:明确目标、约束、隐含需求。
- 原子化:每个步骤应该是可执行的最小单元。
- 标注依赖:明确步骤间的数据依赖。
- 设计异常:为关键步骤准备fallback。
- 保留中间结果:便于调试和恢复。
- 可验证:关键步骤后设置检查点。
- 迭代优化:根据执行结果持续优化拆解。
结语
你在使用 AI 处理复杂任务时,遇到过哪些"拆解失败"的案例?欢迎在评论区分享,一起分析优化!
对于文章中错误的地方或有任何疑问,欢迎在评论区留言讨论!