Prompt工程进阶:少样本与思维链

47 阅读10分钟

为什么有时候结构化Prompt还是不能达到满意的效果?

在上一篇文章中,我们学习了结构化Prompt的四大要素,能够生成专业级的Vue3组件。但在实际开发中,我们经常遇到更复杂的业务逻辑,比如:权限控制、状态机转换、复杂表单验证等。这时候,仅仅告诉AI“做什么”还不够,我们还需要教AI“怎么做”。

本篇文章将通过一个动态权限菜单的实战案例,带领我们掌握两个强大的Prompt技巧:少样本思维链

  • 少样本提示(Few-shot Prompting):给AI举例子,让它学会模仿
  • 思维链(Chain of Thought):让AI展示推理过程,解决复杂逻辑

少样本提示:给AI一个“模仿样本”

什么是少样本提示?

少样本提示的核心思想是:通过提供输入输出的示例,让AI理解任务的模式和规律。就像教小朋友做题一样,先让他们学习几个例题,再让他们做练习题。

适用场景

  • 需要特定输出格式的任务
  • 有明确模式匹配的逻辑
  • 希望AI模仿某种风格或规范

思维链:让AI“说出解题思路”

什么是思维链?

思维链的核心思想是:引导AI展示中间推理步骤,而不是直接给出答案。就像数学老师要求我们必须写出解题过程,而不是仅仅只是写出最终答案。

适用场景

  • 多步骤推理任务
  • 条件判断复杂的逻辑
  • 需要解释决策依据的场景
  • 数学计算
  • 算法设计

实战案例:动态权限菜单的Prompt进化史

需求描述

我们有这样一个需求:能根据用户权限,动态显示菜单,支持至少3种角色:

  • admin:看到所有菜单
  • editor:看到内容管理相关菜单
  • visitor:只能看到仪表盘和公开页面

版本1:直接提问(开放式)

我的Prompt

帮我写一个根据权限显示菜单的逻辑

AI输出

function getMenus(role) {
  if (role === 'admin') {
    return ['仪表盘', '用户管理', '内容管理', '设置']
  } else {
    return ['仪表盘']
  }
}

问题分析

  • 只区分了admin和非admin
  • 没有考虑多角色场景
  • 逻辑过于简单,无法满足真实需求
  • 没有处理嵌套菜单等复杂情况

版本2:少样本提示(让AI学会模式)

我的Prompt

我需要一个根据用户权限动态显示菜单的函数。
请参考以下输入输出示例,生成类似的权限判断逻辑:

示例1:
输入:用户角色 = "admin"
输出:["仪表盘", "用户管理", "内容管理", "系统设置"]

示例2:
输入:用户角色 = "editor"  
输出:["仪表盘", "内容管理", "文章审核"]

示例3:
输入:用户角色 = "visitor"
输出:["仪表盘", "公开文档"]

请根据以上模式,生成一个支持更多角色的权限菜单函数,并确保覆盖至少3种角色。

AI输出

/**
 * 根据用户角色获取可见菜单
 * @param {string} role - 用户角色
 * @returns {string[]} 菜单数组
 */
function getMenusByRole(role) {
  // 权限菜单映射表
  const roleMenus = {
    admin: ['仪表盘', '用户管理', '内容管理', '系统设置', '日志审计', '权限配置'],
    editor: ['仪表盘', '内容管理', '文章审核', '分类管理', '标签管理'],
    visitor: ['仪表盘', '公开文档', '帮助中心'],
    analyst: ['仪表盘', '数据报表', '用户分析', '流量统计'],
    maintainer: ['仪表盘', '系统监控', '性能优化', '错误日志']
  }
  
  // 返回对应角色的菜单,如果角色不存在则返回空数组
  return roleMenus[role] || []
}

// 测试
console.log('admin:', getMenusByRole('admin'))
console.log('editor:', getMenusByRole('editor'))
console.log('visitor:', getMenusByRole('visitor'))
console.log('analyst:', getMenusByRole('analyst'))

进步

  • 通过示例让AI理解了输入输出的映射关系
  • 自动扩展到了5种角色
  • 代码结构清晰,易于维护

不足

  • 菜单是硬编码的扁平数组,无法处理嵌套菜单
  • 没有考虑权限组合的场景(如用户有多个角色)
  • 缺乏对菜单项权限的细粒度控制

版本3:思维链(让AI展示推理过程)

我的Prompt

我需要一个复杂的动态权限菜单系统,支持嵌套菜单和细粒度权限控制。
请先列出你的思考步骤,然后生成代码。

需求:
1. 菜单可以有多级嵌套
2. 每个菜单项可以单独配置所需权限
3. 支持用户拥有多个角色
4. 需要根据用户的所有角色计算最终可见菜单

请按照以下步骤思考:
第一步:分析如何设计菜单数据结构
第二步:考虑多角色权限的合并策略(并集?交集?)
第三步:设计递归遍历菜单树的函数
第四步:处理边界情况(空权限、角色不存在等)
第五步:生成最终的TypeScript代码

AI输出

第一步思考:菜单数据结构设计
我需要设计一个支持嵌套的菜单结构,每个菜单项包含:
- name: 显示名称
- path: 路由路径
- icon: 图标
- children: 子菜单
- permissions: 所需权限数组(满足任一即可)
第二步思考:多角色权限合并策略
用户可能有多个角色,如 ['admin', 'editor']
策略:取所有角色权限的并集
原因:用户拥有多个角色时,应该拥有所有角色的权限总和
第三步思考:递归遍历算法
需要深度遍历菜单树
对每个菜单项检查权限
如果某个菜单项没有权限且没有可见的子菜单,则隐藏
第四步思考:边界情况处理
- 权限配置为空:表示公开菜单,所有人可见
- 角色不存在:视为无权限
- 空菜单树:返回空数组
最终代码输出
/**
 * 动态权限菜单系统
 * 支持多级嵌套、多角色、细粒度权限控制
 */

// ==================== 类型定义 ====================

/** 菜单项接口 */
interface MenuItem {
  /** 菜单名称 */
  name: string
  /** 路由路径 */
  path: string
  /** 图标名称(可选) */
  icon?: string
  /** 子菜单 */
  children?: MenuItem[]
  /** 所需权限列表(满足任一即可),为空表示公开 */
  permissions?: string[]
}

/** 用户角色接口 */
interface UserRole {
  /** 角色ID */
  id: string
  /** 角色名称 */
  name: string
  /** 角色拥有的权限列表 */
  permissions: string[]
}

/** 用户信息接口 */
interface User {
  /** 用户ID */
  id: string
  /** 用户名 */
  name: string
  /** 用户拥有的角色ID列表 */
  roleIds: string[]
}

// ==================== 权限计算函数 ====================

/**
 * 第一步:获取用户的所有权限(多角色并集)
 * @param user 用户信息
 * @param roles 所有角色的定义
 * @returns 用户拥有的所有权限数组(去重)
 */
function getUserPermissions(user: User, roles: Map<string, UserRole>): string[] {
  // 边界情况:用户没有角色
  if (!user.roleIds || user.roleIds.length === 0) {
    return []
  }
  
  // 收集用户所有角色的权限
  const permissionsSet = new Set<string>()
  
  user.roleIds.forEach(roleId => {
    const role = roles.get(roleId)
    if (role && role.permissions) {
      role.permissions.forEach(perm => permissionsSet.add(perm))
    }
  })
  
  return Array.from(permissionsSet)
}

/**
 * 第二步:检查菜单项对当前用户是否可见
 * @param menuItem 菜单项
 * @param userPermissions 用户权限列表
 * @returns 是否可见
 */
function isMenuItemVisible(menuItem: MenuItem, userPermissions: string[]): boolean {
  // 情况1:没有权限要求 -> 公开菜单,所有人都可见
  if (!menuItem.permissions || menuItem.permissions.length === 0) {
    return true
  }
  
  // 情况2:有权限要求 -> 检查用户是否拥有任一所需权限
  return menuItem.permissions.some(perm => userPermissions.includes(perm))
}

/**
 * 第三步:递归过滤菜单树
 * @param menus 原始菜单树
 * @param userPermissions 用户权限列表
 * @returns 过滤后的可见菜单树
 */
function filterMenusByPermissions(
  menus: MenuItem[],
  userPermissions: string[]
): MenuItem[] {
  // 边界情况:空菜单
  if (!menus || menus.length === 0) {
    return []
  }
  
  const result: MenuItem[] = []
  
  for (const menu of menus) {
    // 先检查当前菜单项是否可见
    const visible = isMenuItemVisible(menu, userPermissions)
    
    // 如果有子菜单,递归处理子菜单
    let filteredChildren: MenuItem[] | undefined
    if (menu.children && menu.children.length > 0) {
      filteredChildren = filterMenusByPermissions(menu.children, userPermissions)
    }
    
    // 判断当前菜单项是否应该保留:
    // 1. 菜单本身可见
    // 2. 或者有可见的子菜单(即使自身不可见,但子菜单可见时,应该保留作为父级导航)
    const hasVisibleChildren = filteredChildren && filteredChildren.length > 0
    
    if (visible || hasVisibleChildren) {
      // 创建新的菜单项(避免修改原始数据)
      const filteredMenu: MenuItem = {
        name: menu.name,
        path: menu.path,
        icon: menu.icon
      }
      
      // 如果有可见的子菜单,添加到结果中
      if (hasVisibleChildren) {
        filteredMenu.children = filteredChildren
      }
      
      result.push(filteredMenu)
    }
  }
  
  return result
}

/**
 * 第四步:主函数 - 获取用户的可见菜单
 * @param user 用户信息
 * @param allMenus 完整的菜单树
 * @param allRoles 所有角色的定义
 * @returns 该用户可见的菜单树
 */
function getUserMenus(
  user: User,
  allMenus: MenuItem[],
  allRoles: Map<string, UserRole>
): MenuItem[] {
  // 边界情况处理
  if (!user || !allMenus || !allRoles) {
    console.warn('参数不完整,返回空菜单')
    return []
  }
  
  // 第一步:获取用户的所有权限
  const userPermissions = getUserPermissions(user, allRoles)
  
  // 第二步:根据权限过滤菜单
  const visibleMenus = filterMenusByPermissions(allMenus, userPermissions)
  
  return visibleMenus
}

// ==================== 使用示例 ====================

// 定义所有角色
const roles = new Map<string, UserRole>([
  [
    'admin',
    {
      id: 'admin',
      name: '管理员',
      permissions: ['user:view', 'user:edit', 'content:view', 'content:edit', 'system:config']
    }
  ],
  [
    'editor',
    {
      id: 'editor',
      name: '编辑',
      permissions: ['content:view', 'content:edit', 'content:audit']
    }
  ],
  [
    'visitor',
    {
      id: 'visitor',
      name: '访客',
      permissions: ['public:view']
    }
  ]
])

// 定义完整的菜单树
const allMenus: MenuItem[] = [
  {
    name: '仪表盘',
    path: '/dashboard',
    icon: 'dashboard',
    permissions: [] // 公开菜单
  },
  {
    name: '用户管理',
    path: '/users',
    icon: 'user',
    permissions: ['user:view'],
    children: [
      {
        name: '用户列表',
        path: '/users/list',
        permissions: ['user:view']
      },
      {
        name: '新增用户',
        path: '/users/add',
        permissions: ['user:edit']
      },
      {
        name: '权限配置',
        path: '/users/permissions',
        permissions: ['system:config']
      }
    ]
  },
  {
    name: '内容管理',
    path: '/content',
    icon: 'document',
    permissions: ['content:view'],
    children: [
      {
        name: '文章列表',
        path: '/content/articles',
        permissions: ['content:view']
      },
      {
        name: '发布文章',
        path: '/content/publish',
        permissions: ['content:edit']
      },
      {
        name: '文章审核',
        path: '/content/audit',
        permissions: ['content:audit']
      }
    ]
  },
  {
    name: '系统设置',
    path: '/settings',
    icon: 'setting',
    permissions: ['system:config'],
    children: [
      {
        name: '基本设置',
        path: '/settings/basic',
        permissions: ['system:config']
      },
      {
        name: '安全设置',
        path: '/settings/security',
        permissions: ['system:config']
      }
    ]
  }
]

// 测试不同角色的用户
const testUsers: User[] = [
  {
    id: '1',
    name: '管理员',
    roleIds: ['admin']
  },
  {
    id: '2',
    name: '编辑',
    roleIds: ['editor']
  },
  {
    id: '3',
    name: '访客',
    roleIds: ['visitor']
  },
  {
    id: '4',
    name: '编辑兼访客',
    roleIds: ['editor', 'visitor'] // 多角色
  }
]

// 执行测试
testUsers.forEach(user => {
  console.log(`\n========== ${user.name} 的可见菜单 ==========`)
  const visibleMenus = getUserMenus(user, allMenus, roles)
  console.log(JSON.stringify(visibleMenus, null, 2))
})

少样本 vs 思维链:对比与选择

通过这个实战案例,我们可以清晰地看到两种提示技术的适用场景:

少样本提示适合的场景

场景类型示例为什么适合
格式转换JSON转XML,Markdown转HTML示例能清晰展示映射关系
模式识别根据几个例子总结规律AI能快速捕捉共同模式
风格模仿模仿特定代码风格示例提供了风格参考
简单分类将文本分为几类示例定义了分类标准

思维链适合的场景

场景类型示例为什么适合
复杂推理权限判断的多步骤逻辑需要逐步分析条件
算法设计递归遍历菜单树需要展示算法思路
数学计算复杂公式推导需要展示计算过程
决策解释为什么某个菜单项被隐藏需要说明判断依据
边界处理空权限、多角色等特殊情况需要全面考虑各种情况

组合使用的最佳实践

在实际应用中,少样本提示和思维链不是互斥的,而是可以组合使用的:

组合使用的Prompt模板

我需要一个[复杂功能]。

第一步:请参考以下示例理解需求(少样本):
示例1:输入A -> 输出X
示例2:输入B -> 输出Y

第二步:请按照以下步骤思考(思维链):
1. 分析...
2. 设计...
3. 实现...
4. 处理边界...

第三步:生成完整的代码

如何写出高质量的思维链Prompt

技巧1:引导AI分步思考

请按以下步骤思考:
1. 先分析数据结构设计
2. 再考虑算法逻辑
3. 然后处理边界情况
4. 最后生成代码

技巧2:要求展示中间结果

在每个思考步骤后,输出当前步骤的结论或伪代码

技巧3:指定思考深度

请深入思考以下细节:
- 多角色权限冲突如何解决?
- 菜单树深度超过3层怎么办?
- 性能优化考虑?

技巧4:要求对比不同方案

请对比两种权限合并策略(并集vs交集)的优缺点,然后选择最优方案

结语

你在实际工作中遇到过哪些适合用思维链解决的复杂问题?有没有尝试过让AI解释它的推理过程?

对于文章中错误的地方或有任何疑问,欢迎在评论区留言讨论!