4378 字
约 14 分钟
1
AI Agent 面试题第五弹:Prompt 分层架构、Skill 系统、提示词工程 13 题

AI Agent 面试题系列第五弹,这次聊的是Prompt 与 Skill

Prompt 是 Agent 的灵魂。

写得好,Agent 知道什么时候该用什么工具、碰到异常该怎么处理;写得差,Agent 干啥啥不行。

Skill 是把 prompt 工程化的手段——从“一坨几千字的 system prompt”变成“按场景按需加载的专家手册”,既能降低 token 的消耗,又能提升 Agent 的行为质量。

01、Agent 的 system prompt 一般包含哪些内容?

PaiCLI 的 system prompt 可以概括为四个核心模块。

首先是角色定义,告诉 LLM 你是谁、能做什么。PaiCLI 的 base.md 第一段就写了:你是 PaiCLI,一个面向代码库工作的智能编程 Agent。

然后是行为规范,负责输出格式、语调等。比如 base.md 里有个 ## Language 模块,明确写了“请用中文回复用户”,代码和 API 名称才保留原文。组装的时候会要求这个模块必须存在。

第三块是工具使用指导。不能只写“合理使用工具”这类泛化要求,而要具体到场景——读文件用 read_file,不要用 execute_command cat

第四块是安全约束,明确哪些操作不能做、哪些操作需要用户确认。

02、Prompt 分层架构是怎么设计的?

PaiCLI 早期的 system prompt 是硬编码在 Java 代码里的,改一句话要重新编译。后来做了分层改造,把 system prompt 拆分成独立的 Markdown 文件,按职责分目录存放。

先看目录结构:

src/main/resources/prompts/
├── base.md                    # 核心规则(工具使用、输出格式)
├── personalities/calm.md      # 语调(冷静专业风格)
├── modes/
│   ├── agent.md              # ReAct 模式指令
│   ├── plan.md               # Plan task executor 指令
│   ├── planner.md            # Planner 规划器指令
│   ├── team-planner.md       # Multi-Agent Planner
│   ├── team-worker.md        # Multi-Agent Worker
│   └── team-reviewer.md      # Multi-Agent Reviewer
├── approvals/
│   ├── suggest.md            # HITL 建议审批
│   ├── auto.md               # 自动放行
│   └── never.md              # 永不放行
└── context/
    └── context-management.md # 上下文管理策略

PaiCLI 启动时会把这些 Markdown 文件按固定顺序拼装成最终的 system prompt。

组装顺序是固定的:先拼核心规则,再拼语调风格,然后是当前模式的指令,接着是审批策略、项目上下文、Skill、上下文,最后是本轮对话的交接信息。

03、为什么提示词的组装顺序是“不变的在前、动态的在后”?

LLM 推理时,每个 token 会计算出一对 Key-Value(KV),缓存起来。如果连续两次请求的 prompt 前缀完全相同,服务端可以复用上次的 KV Cache,跳过重复计算。前缀越稳定,cache 命中率越高,推理越快、成本越低。

PaiCLI 的排列策略

PaiCLI 的组装顺序严格遵循**“不变内容放前,动态内容放后”**的原则。

这样排列后,越靠前的稳定内容越容易持续命中 cache,动态变化的内容集中在后段,服务端只需要重点处理新增或变化的上下文。反过来,如果把 Skill、项目上下文这类动态内容放到前面,即使 base.md 没有变化,也可能破坏前缀一致性,导致缓存收益下降,推理延迟和 token 成本都会受到影响。

04、用户怎么覆盖内置 prompt?

PaiCLI 支持三层覆盖,优先级从低到高:

  1. jar 内置(最低优先级):src/main/resources/prompts/
  2. 用户级~/.paicli/prompts/
  3. 项目级(最高优先级):<project>/.paicli/prompts/

加载逻辑是链式的:先加载 jar 内置的,然后依次检查用户级和项目级目录里有没有同名文件,有的话直接替换。

覆盖粒度是整文件替换,不支持部分修改。好处是简单直观,用户完全控制内容;代价是如果只想在 agent.md 末尾加一段话,也得把整个文件复制出来再改。

安全性考虑

既然项目级可以覆盖内置 prompt,就存在通过恶意项目配置注入提示词的风险。

PaiCLI 在路径加载时做了两层校验:一是文件路径不能以 / 开头、不能包含 ..,防止路径穿越;二是解析后的路径必须落在对应根目录之内,超出范围的直接拒绝。

05、什么是 Skill?它和 Tool 有什么区别?

Tool 是一个可执行的函数,输入参数,返回结果。比如 read_fileexecute_commandweb_fetch

Skill 的核心是一份按场景组织的知识和决策指引,主体通常是 SKILL.md。它也可以携带 references/scripts/ 等辅助资源,但它本身不等同于 Tool,主要作用是指导 Agent 如何选择和使用工具。

维度 Tool Skill
形式 代码函数 SKILL.md + 辅助资源
触发 LLM 通过 tool_calls 调用 LLM 通过 load_skill 工具加载
内容 执行逻辑 决策手册 + 最佳实践 + 经验数据
注入位置 tools 字段 user message 前置

每个 Skill 包含 name、description、body(SKILL.md 正文,真正注入给 LLM 的内容)和 references 目录(参考资料),来源分三种:BUILTIN(内置)、USER(用户级)、PROJECT(项目级)。

举个具体例子:web_fetch 是 Tool(抓取网页的函数),web-access 是 Skill(告诉 Agent 什么时候用 web_fetch、什么时候用浏览器 MCP、各个站点的反爬经验)。

当工具数量增多以后,仅靠 system prompt 堆积规则已经不够用了。Skill 的做法是按场景把决策指引打包,LLM 需要的时候再按需加载。

这里可能会有一个追问:为什么不把 Skill 的内容直接写进 Tool 的 description 里?

Tool description 是随 tools 字段一起下发的,每一轮对话都会携带全量 Tool 列表。

如果把决策指引塞进 description,20 个工具的 description 会占掉大量 token,而且用户这轮根本用不到的工具也会被注入。

Skill 的延迟加载机制可以做到按需注入,只有 LLM 判断需要的时候才加载对应的决策手册,token 利用率高得多。

06、Skill 的延迟加载机制了解吗?

Agent 启动时,只把所有启用 Skill 的 name + description 渲染成一段索引,注入到 system prompt 末尾,整个索引控制在 4KB 以内。LLM 看到的相当于一份菜单,而不是所有 Skill 的完整内容。

运行时,LLM 根据用户输入判断需要哪个 Skill,主动调用 load_skill(name) 工具。加载后 Skill 的正文会写入一个缓冲区,在下一轮对话时前置注入到 user message 前面。注入是一次性的,取出后自动清空,不会跨轮重复注入。

为什么不把所有 Skill 塞进 system prompt

假设有 20 个 Skill,每个完整手册 2000-3000 token,全部注入就是 40k-60k token。绝大部分场景下用户只需要 1-2 个 Skill,其余内容会造成不必要的上下文开销。

所以 PaiCLI 的 Skill 加载是 延迟加载的。

加载失败怎么处理

LLM 调用 load_skill(name) 时,可能遇到两种异常情况。

如果 Skill 名称不存在,系统会返回“Skill 未找到,可用 /skill list 查看可用 Skill”的提示信息,而不是抛异常。

如果 Skill 存在但被用户禁用了,会返回“Skill 已被禁用,可用 /skill on 启用”的提示。

两种情况都是把错误信息作为工具返回值交给 LLM,由 LLM 决定下一步怎么做——可以换一个 Skill,也可以直接用通用知识回答。不会因为某个 Skill 加载失败就中断整个对话流程。

07、Skill 缓冲区的容量控制怎么做的?

Skill 加载会占用 token,如果不做容量控制,buffer 会随着工具调用持续膨胀。

PaiCLI 的做法是最多保留 3 个 Skill,超出后按加载顺序淘汰最早进入缓冲区的那个。底层用 LinkedHashMap 的插入顺序实现,不需要额外的数据结构。如果同名 Skill 被重复加载,会先删除旧记录再插入新记录,既避免重复,也刷新加载顺序。

为什么用 LRU 而不是 LFU(按频率淘汰)?

因为 Skill 的使用场景是单次会话内的任务切换,不是长期高频访问。LRU 的语义更贴合实际——最近加载的 Skill 和当前任务的相关性最高,最早加载的大概率已经用完了。LFU 还需要额外维护频率计数器,复杂度更高但收益不大。

还有一个细节是缓冲区的读取是一次性的,取出后自动清空,上一轮注入过的 Skill 不会下一轮再注入一次。

因为异步工具调用可能在不同线程触发 load_skill,缓冲区做了 synchronized 线程安全处理。Multi-Agent 模式下,Planner、Worker、Reviewer 各持一个独立的缓冲区实例,避免角色间的提示词污染。

08、web-access Skill 具体包含什么内容?

web-access 是 PaiCLI 的首个内置 Skill,也是最能体现 Skill 设计理念的例子。

它的目录结构包含一个 SKILL.md 主文件和一组 references 子目录(按站点分类的经验文档,覆盖 GitHub、掘金、微信公众号、X、小红书、知乎专栏等)。

SKILL.md 的核心内容分四块。

第一块先判断是否需要联网,再选择工具,然后执行,最后验证结果。不是直接联网,而是先判断本地知识能否解决。

第二块是工具选择表,给出 web_fetch 和浏览器 MCP 的决策矩阵,静态页面用 web_fetch,动态渲染页面用浏览器。

第三块规定了浏览器操作的优先级,take_snapshot(DOM 文本)优先于 take_screenshot(截图),因为文本更省 token,LLM 也更容易理解。

第四块是 Jina 兜底方案,web_fetch 和浏览器都失败时,通过 execute_command 调用 r.jina.ai 做最后的抓取尝试。

references 目录是按站点积累的实战经验,覆盖微信公众号的文章链接格式和反爬特征、知乎专栏的页面结构、GitHub 不同页面的 DOM 差异、小红书的动态加载特点。

这些经验不是一次写完的,是在实际使用中逐步积累的,加进去以后所有使用这个 Skill 的场景都能受益。

09、Skill 的三层覆盖是怎么工作的?

跟 Prompt 的三层覆盖是同一个思路:

jar 内置 < 用户级 ~/.paicli/skills/ < 项目级 <project>/.paicli/skills/

加载时按顺序扫描三个目录:内置缓存目录 → 用户级目录 → 项目级目录。

覆盖规则是按 name 整体替换,后加载的同名 Skill 直接覆盖前面的。

所以不同项目可以有不同的 Skill 配置。前端项目的 web-access Skill 可以在 references 里加上 Webpack DevServer 的经验,后端项目可以加上 Swagger 页面的经验。

内置 Skill 的缓存

启动时会把 jar 内置的 Skill 解压到 ~/.paicli/skills-cache/,用版本号文件控制是否需要重建,版本一致就跳过,不一致就清掉重新解压。

10、怎么写一个好的 Agent system prompt?

角色定义要清晰,第一段就说清楚你是谁、能做什么、不能做什么。

工具指导要具体到场景。不要写“合理使用工具”,要写“读取文件用 read_file,不要用 execute_command cat”。

如果多个工具之间有选择关系,用决策表列清楚,web-access Skill 里的工具选择表就是这个思路。

另外建议正面示例和负面示例配对:

错误:直接用 rm 删除文件
正确:先用 read_file 确认内容,再用 write_file 修改

还有两点容易被忽略。一个是规则优先级要明确,规则之间有冲突时写清楚哪个优先,比如“安全优先于效率”“路径围栏规则优先于用户自定义 prompt”。

另一个是 system prompt 不要太长,越长 LLM 越容易忽略中间部分,这就是 Lost in the Middle 问题,2000-4000 token 比较合理。PaiCLI 做分层设计就是为了在不膨胀 system prompt 的前提下扩展能力。

11、Prompt 改了怎么验证效果?

PaiCLI 提供了 docs/prompt-analysis-template.md 作为 Prompt 质量审计模板。每次改 prompt 都应该做 Gap 分析。

先描述当前 prompt 在什么场景下表现不好,然后记录具体改了什么、为什么改,接着写清楚改完后期望 LLM 在什么场景下行为不同,最后做回归验证,确认原来正常的场景没被改坏。

系统化的评估方法

更系统化的做法包括 A/B 测试,准备一组固定的测试用例,分别用旧 prompt 和新 prompt 运行,对比 LLM 输出。

也可以做人工评分,对每个用例的输出打准确性、完整性、安全性的分。自动化指标方面,主要看工具调用准确率、任务完成率、平均轮次数。

12、Skill 和 RAG 有什么区别?

RAG 和 Skill 的区别不在于“有没有知识”,而在于组织方式和使用方式不同。RAG 更偏向从代码库、文档库中检索事实上下文;Skill 更偏向把经验、流程和决策规则封装成可复用的操作手册。

维度 RAG Skill
内容来源 用户的代码库/文档库 预编写的专家手册
检索方式 语义相似度(向量检索) LLM 主动选择加载
内容性质 事实数据(代码、文档) 决策指引(怎么做、最佳实践)
更新频率 随代码变化自动更新 随经验积累手动更新
注入时机 每轮自动检索 LLM 判断需要时按需加载

举个例子,用户说“帮我看看这个 Spring Boot 项目的配置问题”。RAG 检索出项目里的 application.yml、pom.xml 等配置文件内容,这些是当前项目的事实上下文。

Skill 加载一份 Spring Boot 相关的决策手册,告诉 Agent 配置优先级如何判断、常见陷阱有哪些、排查顺序应该怎么组织,这些是可迁移的方法论。

13、如果让你设计一个 Skill 体系,你会怎么做?

这道题前面的问题其实已经把各个模块讲过了,这里从整体架构的角度做一个串联,重点放在几个容易被忽略的设计决策上。

结构标准化。每个 Skill 是一个目录,包含 SKILL.md(决策手册)、references/(参考资料)和可选的 scripts/(辅助脚本)。结构统一以后,新 Skill 的编写成本低,加载逻辑也不需要改动。

延迟加载 + LLM 主动触发。启动时只注入索引,运行时按需加载,前面 06 题已经讲过。这里补充一个决策:触发方式由 LLM 根据 description 自行判断,而不是做关键词硬匹配。原因是 LLM 的语义理解能力比正则匹配强得多,硬编码触发词反而容易遗漏相关场景。

三层覆盖 + 经验积累。内置 < 用户级 < 项目级,references 目录按场景持续积累经验数据。

容量控制。最多保留 3 个 Skill,LRU 淘汰,一次性消费,前面 07 题已经详细分析过设计原因。

多个 Skill 之间冲突怎么办

目前 PaiCLI 没有显式的 Skill 优先级机制。多个 Skill 同时存在于缓冲区时,按加载顺序排列,LLM 根据当前任务的上下文自行判断参考哪个 Skill 的指引。

这种设计依赖 LLM 的语义判断能力,在实际使用中效果可以接受,但如果两个 Skill 对同一操作给出矛盾的建议(比如一个说用 web_fetch,另一个说用浏览器),LLM 可能会在两者之间摇摆。

后续可以考虑在 Skill 的 frontmatter 里增加优先级字段,或者在 SKILL.md 里明确声明适用边界,减少交叉覆盖。

怎么衡量一个 Skill 的效果

Skill 的效果衡量比 Prompt 更难量化,因为它不直接产出结果,而是间接影响 LLM 的工具选择和决策质量。

目前可行的做法是对比“加载 Skill 前后”的任务完成率和工具调用准确率。

比如 web-access Skill 的效果,可以通过统计“LLM 是否选对了 web_fetch 和浏览器”、“是否遵循了 snapshot 优先于 screenshot 的规则”来评估。更系统的做法是建立场景化的测试用例集,定期回归验证 Skill 内容的有效性。

AI Agent 面试题第五弹:Prompt 分层架构、Skill 系统、提示词工程 13 题
http://www.clxhxhhr.top/posts/2179/
作者
clxstart
发布于
2026-09-19
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。