这篇文章从应用开发的角度介绍 LLM 的几个基础概念:模型如何接收输入、如何利用外部知识,以及如何通过 Tool Calling 使用外部能力。文中的代码主要用于展示完整的请求与消息流转。

1. 一张图理解LLM应用

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 如何让模型利用外部知识 检索 + 生成