为什么有时候结构化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
text
帮我写一个根据权限显示菜单的逻辑
AI输出
javascript
function getMenus(role) {
if (role === 'admin') {
return ['仪表盘', '用户管理', '内容管理', '设置']
} else {
return ['仪表盘']
}
}
问题分析
- 只区分了admin和非admin
- 没有考虑多角色场景
- 逻辑过于简单,无法满足真实需求
- 没有处理嵌套菜单等复杂情况
版本2:少样本提示(让AI学会模式)
我的Prompt
text
我需要一个根据用户权限动态显示菜单的函数。
请参考以下输入输出示例,生成类似的权限判断逻辑:
示例1:
输入:用户角色 = "admin"
输出:["仪表盘", "用户管理", "内容管理", "系统设置"]
示例2:
输入:用户角色 = "editor"
输出:["仪表盘", "内容管理", "文章审核"]
示例3:
输入:用户角色 = "visitor"
输出:["仪表盘", "公开文档"]
请根据以上模式,生成一个支持更多角色的权限菜单函数,并确保覆盖至少3种角色。
AI输出
javascript
/**
* 根据用户角色获取可见菜单
* @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
text
我需要一个复杂的动态权限菜单系统,支持嵌套菜单和细粒度权限控制。
请先列出你的思考步骤,然后生成代码。
需求:
1. 菜单可以有多级嵌套
2. 每个菜单项可以单独配置所需权限
3. 支持用户拥有多个角色
4. 需要根据用户的所有角色计算最终可见菜单
请按照以下步骤思考:
第一步:分析如何设计菜单数据结构
第二步:考虑多角色权限的合并策略(并集?交集?)
第三步:设计递归遍历菜单树的函数
第四步:处理边界情况(空权限、角色不存在等)
第五步:生成最终的TypeScript代码
AI输出
第一步思考:菜单数据结构设计
text
我需要设计一个支持嵌套的菜单结构,每个菜单项包含:
- name: 显示名称
- path: 路由路径
- icon: 图标
- children: 子菜单
- permissions: 所需权限数组(满足任一即可)
第二步思考:多角色权限合并策略
text
用户可能有多个角色,如 ['admin', 'editor']
策略:取所有角色权限的并集
原因:用户拥有多个角色时,应该拥有所有角色的权限总和
第三步思考:递归遍历算法
text
需要深度遍历菜单树
对每个菜单项检查权限
如果某个菜单项没有权限且没有可见的子菜单,则隐藏
第四步思考:边界情况处理
text
- 权限配置为空:表示公开菜单,所有人可见
- 角色不存在:视为无权限
- 空菜单树:返回空数组
最终代码输出
typescript
/**
* 动态权限菜单系统
* 支持多级嵌套、多角色、细粒度权限控制
*/
// ==================== 类型定义 ====================
/** 菜单项接口 */
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模板
text
我需要一个[复杂功能]。
第一步:请参考以下示例理解需求(少样本):
示例1:输入A -> 输出X
示例2:输入B -> 输出Y
第二步:请按照以下步骤思考(思维链):
1. 分析...
2. 设计...
3. 实现...
4. 处理边界...
第三步:生成完整的代码
如何写出高质量的思维链Prompt
技巧1:引导AI分步思考
text
请按以下步骤思考:
1. 先分析数据结构设计
2. 再考虑算法逻辑
3. 然后处理边界情况
4. 最后生成代码
技巧2:要求展示中间结果
text
在每个思考步骤后,输出当前步骤的结论或伪代码
技巧3:指定思考深度
text
请深入思考以下细节:
- 多角色权限冲突如何解决?
- 菜单树深度超过3层怎么办?
- 性能优化考虑?
技巧4:要求对比不同方案
text
请对比两种权限合并策略(并集vs交集)的优缺点,然后选择最优方案
结语
你在实际工作中遇到过哪些适合用思维链解决的复杂问题?有没有尝试过让AI解释它的推理过程?
对于文章中错误的地方或有任何疑问,欢迎在评论区留言讨论!