LLM 学习笔记 2——提示词工程,RAG,Tool

Prompt Engineering

提示词工程,如果说 LLM 是硬件的话,提示词就是软件了,是人类发光发热的地方。通过精心设计提示词让 AI 生成更符合需要的输出。

Prompt 基本结构

Prompt 的重点是——清晰、具体、结构化

核心原则——CRISP:

  • Context:上下文,即对话的整个背景(可以说用户的背景也属于这个上下文)
  • Role:角色,模型总是扮演一个特定的角色
  • Instruction:指令,模型所扮演的角色要做什么
  • Step:步骤,按步骤去思考或输出(一说是 Structure,即整个系统提示词的结构
  • Parameters:参数,通过调参去配置它的风格,结构等

Few-shot / Zero-shot

通过提供给它示例来规范它的行为,但术语上它叫“少样本学习”。

Chain-of-Thought

要求 AI 去主动输出自己的思维链条,而非直接输出答案,根据之前学习的 Transformer,我们知道 AI 是一个 Token 一个 Token 去预测的,且之前输出的东西会影响它之后的输出,而让 AI 输出思维链就能让它能真正分步骤地处理问题,但有时候也会让 AI 无限兜圈子)。

Benchmark

写 Prompt 就像写程序,它并非是一蹴而就的,而是反复根据 AI 的反馈去写的,同样地,没有一个通用的 Prompt,Prompt 总是任务相关的。

Prompt 调优,重点是反馈——如何判断,是人判断还是机器判断……沿袭上面的 CRISP 框架,增加思维链,增加样本,就是某种调优了。

调优有何自动化的工具?Promptfoo 似乎是一个,但我没用过。

Prompt 在工程中的约束设计

  1. 配置和要求它使用 Json 输出并给定输出范式
  2. 在客户端处使用类型校验库如 Pydantic 检验
  3. 以工具调用而非 AI 直接响应的形式去收集 LLM 的结果

RAG(检索增强生成)

RAG 整体流程

RAG 有两种方式——直接拼接到上下文中,或者以 Tool 形式提供给 AI(Agentic RAG)。

对前者,直接将用户输入的整段文本去做 Embedding,然后去在知识库(向量数据库)中进行查询,返回相近的知识库的片段(Document)。

文档嵌入流程

加载,切分,Embedding,存储,查询。

加载——无论是 PDF 还是 EPUB 还是什么,需要加载成一个一致的格式,称为文档。文档需要进行切分,切分后的结果是我们用于查询的原子。它们会被进行 Embedding,然后连带着原始文本去存储进向量数据库中。查询时,我们根据查询时的向量(来自于用户输入的 Embedding)去匹配合适的向量并使用 top_k 等方法去筛选,然后将这些向量对应的文本去插入到用户提示词中供 LLM 使用。

这个检索的过程称为召回。

存在诸多向量数据库(FAISS / Milvus),但我没有使用过。

但对用户来说,一般不会提及这个“向量数据库”,提及“Embedding”,用户看到的只有“知识库”。

检索策略(top-k / rerank)

相似性或者最大边际相关性(MMR,maximum marginal relevance)——相似性直接匹配和输入相似的向量,MMR 则最小化结果之间的相似性

关于召回不足

关于召回不足,就是知识库里明显有这个内容却没有被召回,原因为:

  1. 知识库本身的问题——Embedding 太差或 Chunk 没切好,或文档本身存在歧义
  2. LLM 和客户端的问题——查到了,塞进去了,但 LLM 无视,或者查到的东西有歧义让 AI 迷糊(噪音)
  3. 用户的问题——用户的 Query 没写好

对于知识库本身的问题,解决方案有:

  1. Embedding 换更好的,注意测试一下用的模型是否是支持中文的
  2. Chunk 切分优化,比如 API 文档就适合按接口去切分
  3. 混合检索,既使用向量也使用关键词

关于 LLM 和客户端的问题:

  1. 使用 Reranking 重排,LLM 打分的方式重新明确“相关性”
  2. 在 Prompt 中约束 LLM 必须像论文一样引用上下文

关于用户的问题——在实际检索之前先让 AI 去扩展用户的查询或进一步明确用户的需求。

关于噪音和幻觉

在 Prompt 中约束 LLM 必须像论文一样引用上下文,上下文中没有就说自己不知道。置信度,重新检查……

知识图谱

RAG 是使用知识库的手段,我们常说的和用户直接看到的,只有知识库——他们只管把文档丢进知识库,然后查就对了。

其实 RAG 的英文是 retreival-augmented generation,augmented 就是乐理中的增,如增四度 Augmented 4th。

知识图谱,则是一种结构化(graph)的知识库,它的核心是三元组 实体 - 关系 - 实体,实体和实体之间就这样组成网状关系。

知识图谱的应用:

  1. 多跳推理——如 A 和 C 的关系,X 的依赖者
  2. 复杂关系查询——如社交网络
  3. 强一致性——每一个答案都要能追溯

图谱重在可验证

Agent / Tool Calling(重点)

Agent 基本概念

在现在,从用户的视角来看,Agent 就是 LLM 加上系统提示词加上可选的工具、Skill、记忆、知识库……它和客户端协作,去解决特定领域的问题。

ReAct

LLM 自己没有和外界交互的能力,LLM 只负责发起工具调用的请求,而客户端去负责工具的实际执行以及将执行结果反馈给 AI,AI 再去执行进一步操作。在没有 Tool Calling 的年代,需要使用 ReAct 模式,但现在大多数 LLM 都原生支持 Tool Calling 了(实际上它们也很大程度上是专门为此训练的),ReAct 已经不需要客户端去在系统提示词上去主动操心了。

但从更偏向理论的角度来说,Agent 最重要的是所谓的 Agent Loop——控制循环——思考、决策、调用、观察结果。这就是 ReAct,Reasoning and Action

Tool 调用流程

这里说的是 OpenAI 对话 API 工具调用流程。再次强调——LLM 只负责发送这个工具调用的“请求”,实际的执行者是客户端,客户端响应消息给 LLM。

  1. 客户端在请求中带上可用的工具
  2. 用户发送需要 AI 使用工具调用去解决的需求(一条 user 角色消息)
  3. AI 响应工具调用请求,其实际上是一个 assistant 角色的消息,content 为空,tool_calls 字段中存储工具调用请求
  4. 客户端根据 tool_calls 字段去执行 tool,执行结果以 tool 角色消息响应给 AI
  5. AI 去继续进行生成(可能直接响应结果,也可能是进行进一步的工具调用)

这里,user 的消息可以包含一个tool_choice字段,表示让模型是否自主决定调用工具或禁止调用工具,或者强制调用某工具。

一个有趣的地方是,工具调用也支持流式输出!但这需要客户端去支持,我没用过。

MCP

MCP 协议即一种标准的工具协议,客户端可以连接 MCP 去使用它所提供的工具,MCP 支持标准输入输出流格式交互(这时候就是客户端直接起 MCP 服务),或者 Streamable-HTTP 或 SSE 协议去连接远程的 MCP 服务。

MCP 协议包含三个角色——Tool,Resource 和 Prompt,Resource 其实就是只读的 Tool,而 Prompt 则是提供给 MCP 客户端(即用户)去使用的结构化的提示词,LLM 对它不可见。

Skill

Skill 其实就本质上来说是一种渐进式的提示词——LLM 客户端在系统提示词中塞入当前可用的 Skill 的名称,简单描述和SKILL.md,然后 LLM 自己去读取 SKILL 文件,去根据文件描述去进行进一步操作,如读取其他文件,执行技能同捆的程序脚本等。

SKILL 有独特的结构——必须放到 skills 的一个子目录下,入口文件叫SKILL.md,它要以一个 YAML Formatter 去开头去明确自己的名称,描述……

当然,我说的是 OpenClaw 语境下的 Skill。现在这玩意儿越来越花哨了。

SubAgent

分治,上下文隔离,控制能力边界。

主 Agent 经常会上下文爆炸,特别是在进行联网搜索等情况的时候,搜索结果会直接把上下文撑满,比如这种时候就特别适合去使用 SubAgent 去执行联网搜索然后自己去做一个简单的总结给主 Agent 避免让主 Agent 的上下文充斥无意义的内容。同时不同的任务串在一起会让上下文很快串味。

现在常见的 SubAgent 使用 Tool Calling 去实现,去包装成 tool 去将需求调用并等待它的结果。

但还有一种工程化的方式,类似 Dify 或者 LangGraph,一个 SubAgent 是一个节点,有明确的输入、输出 Schema,但这种我没用过也没有研究过。


本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 协议 ,转载请注明出处!