LLM 应用基础:Prompt、RAG 与 Tool Calling
介绍 Prompt、Message、Token、Context、Structured Output、Embedding、RAG 和 Tool Calling, 并通过一个 DeepSeek 工具调用示例说明模型与应用程序如何协作。
这篇文章从应用开发的角度介绍 LLM 的几个基础概念:模型如何接收输入、如何利用外部知识,以及如何通过 Tool Calling 使用外部能力。文中的代码主要用于展示完整的请求与消息流转。
1. 一张图理解LLM应用
2. Prompt 与 Message
Prompt
Prompt 是给模型的任务说明,通常包含:
角色 + 任务 + 上下文 + 约束 + 输出格式 + 示例
示例:
请把下面的文本总结成 3 个要点,不超过 100 字。
Message
在对话 API 中,Prompt 通常被拆分为一个或多个 Message。每条 Message 由 role 和 content 组成,多个 Message 共同构成模型当前看到的输入:
messages = [
{"role": "system", "content": "你是一个翻译助手。"},
{"role": "user", "content": "请翻译:今天天气很好。"},
]
常见角色:
| 角色 | 作用 |
|---|---|
system |
定义模型行为和规则 |
user |
用户输入 |
assistant |
模型历史回复 |
tool |
应用程序执行工具后返回的结果 |
多轮对话本质上是应用程序把历史 Message 再传给模型。对于无状态的 Chat Completions 接口,模型不会自动永久记住上下文;应用程序需要在后续请求中重新传入需要保留的消息。
3. Token 与 Context
Token
Token 是模型处理文本的基本单位,不等同于字符或单词。Token 数量会影响:
- 输入输出长度
- API 费用
- 推理速度
- 是否超过上下文限制
Context Window
Context Window 是模型一次请求能够处理的最大 Token 数量,通常同时受到输入内容和输出内容的限制。输入部分通常包括:
系统指令 + 历史消息 + 当前问题 + 工具定义 + 工具结果
模型本轮生成的输出会在下一轮作为历史消息重新传入。Context 不等于 Memory:
- Context:当前请求显式传入的内容
- Memory:应用程序保存并在后续请求中重新加载的内容
上下文过长时,可以截断历史、总结对话、减少工具描述或使用 RAG 按需检索。
4. Structured Output
Structured Output 让模型按照固定结构返回结果。JSON 是一种结果格式,JSON Schema 则是描述和约束该格式的规则;是否能由 API 严格保证结构,取决于具体模型和接口能力。
{
"city": "北京",
"temperature": 28,
"condition": "晴"
}
对应的 Schema:
{
"type": "object",
"properties": {
"city": {"type": "string"},
"temperature": {"type": "number"},
"condition": {"type": "string"}
},
"required": ["city", "temperature", "condition"],
"additionalProperties": false
}
适合信息抽取、分类、审核和下游程序解析。即使 API 提供结构化输出保证,结构合法也不代表内容一定正确,仍需在应用程序中进行类型、范围和业务语义校验。
5. Embedding 与 RAG
Embedding
Embedding 把文本转换成向量,用于表示语义关系:
文本 → [0.12, -0.38, 0.77, ...]
常见用途:语义搜索、相似度匹配、聚类、推荐和去重。
RAG
RAG(Retrieval-Augmented Generation)先检索相关资料,再把资料放进 Prompt 让模型回答:
文档 → 切分 → Embedding → 向量数据库
↑
问题 → Embedding → 相似度检索 ┘
↓
相关文档 + 问题 → LLM
Embedding 负责把文本转换为向量,检索系统再利用向量相似度召回相关内容;LLM 负责结合检索结果理解问题并生成答案。RAG 不等于重新训练模型,而是在请求时动态补充外部知识。
6. Tool Calling
根据 DeepSeek 官方文档:Tool Calls 的定义,Tool Calls 允许模型调用外部工具来增强自身能力。它的核心不是让模型直接执行函数,而是让模型返回一个工具调用请求,由应用程序负责执行函数,再将执行结果传回模型。
在 API 响应中,工具调用通常位于 assistant 消息的 tool_calls 字段中,结构类似:
{
"id": "call_123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"北京\"}"
}
}
注意:arguments 通常是 JSON 字符串,需要解析后再进行参数校验;工具调用请求也不等于工具已经执行。
应用程序执行工具,再把结果传回模型:
用户问题
↓
调用模型
↓
有 tool_calls?──否──→ 返回最终答案
│
是
↓
执行本地工具
↓
追加 tool message
↓
再次调用模型
核心原则:模型负责“决定调用什么”,应用程序负责“真正执行”。
工具定义
DeepSeek 官方文档通过请求中的 tools 参数声明可用函数,函数定义包含 type、function.name、function.description 和 function.parameters 等字段。下面的示例保留了 DeepSeek Tool Calls 官方文档 的 API 结构,仅将函数改成中文天气查询,并注明返回的是模拟数据。
TOOLS = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询城市天气;示例实现返回模拟数据",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"},
},
"required": ["city"],
"additionalProperties": False,
},
},
}
]
安全要求
- 只执行白名单工具
- 校验工具参数
- 限制调用次数和超时时间
- 高风险操作增加人工确认
- 不要直接
eval()模型生成的内容
工具定义中的 Schema 主要用于告诉模型如何构造参数,不能替代应用程序运行时的校验。对于真实的外部服务,还应考虑权限、超时、重试、幂等性和敏感信息泄露等问题。
7. tool_agent.py:不使用框架的原始实现
tool_agent.py 使用 Python 标准库 urllib,直接调用 DeepSeek 的 OpenAI-compatible Chat Completions 接口。
7.1 示例说明
代码通过环境变量 DEEPSEEK_API_KEY 读取 API 密钥,避免把密钥写入源码;通过 DEEPSEEK_MODEL 选择模型。示例关闭了 thinking 模式,以便专注展示 Tool Calling 的基本流程。
7.2 完整实现
下面是完整的 tool_agent.py,只依赖 Python 标准库。代码块直接引用同目录文件,便于在网页中阅读完整实现;其中天气函数返回模拟数据,接入真实天气服务时只需替换 get_weather 的实现。
<<< @/posts/Agent/tool_agent.py
7.3 代码结构
| 模块 | 作用 |
|---|---|
TOOLS |
向模型声明可用工具及参数 Schema |
call_model |
组装请求并调用 Chat Completions API |
TOOL_FUNCTIONS |
工具名到本地函数的白名单映射 |
run_tool_calling_loop |
循环处理模型请求、工具执行和结果回传 |
main |
检查配置并接收用户输入 |
代码中的 get_weather 只是一个模拟工具,真实项目中通常会替换为数据库查询、HTTP 请求或其他受控的业务函数。
7.4 消息变化
第一次请求:
[user]
模型请求工具后:
[user, assistant(tool_calls)]
工具执行后:
[user, assistant(tool_calls), tool(result)]
下一次请求:
把当前 messages 一起传给模型
根据 DeepSeek 多轮对话文档,Chat Completions 是无状态接口,因此每次请求都要传入当前需要的上下文。在同一个工具调用回合中,assistant 的 tool_calls 与对应的 tool 消息必须保持匹配;对于已经完成的历史回合,则可以在不破坏消息关系的前提下进行总结或截断。
8. 核心概念总结
| 概念 | 解决的问题 | 结果 |
|---|---|---|
| Prompt | 如何描述任务 | 提示文本 |
| Message | 如何传输对话 | role + content |
| Token / Context | 一次能处理多少内容 | Token 和上下文窗口 |
| Structured Output | 如何稳定解析结果 | JSON / Schema |
| Tool Calling | 如何调用外部能力 | 工具名 + 参数 |
| Embedding | 如何表达语义关系 | 向量 |
| RAG | 如何让模型利用外部知识 | 检索 + 生成 |