本文是 AI Agent 入门系列的第 2 篇。学完第 1 篇的基础调用后,我们来深入理解 LLM 的工作机制。
掌握这些知识,下一课就能实现 Tool Use(函数调用),这是 Agent 最核心的能力。


本课目标

  • 理解 LLM 对话补全的本质(消息结构、多轮对话)
  • 掌握关键参数的含义与调优(temperature、max_tokens)
  • 学会结构化输出 —— 让 LLM 返回 JSON

本课产出

  • 新建文件lesson02_structured_output.py,粘贴下文完整代码
  • 运行python lesson02_structured_output.py
  • 效果:让 LLM 稳定输出 JSON 格式数据,掌握 temperature 和 max_tokens 的影响

一、Chat Completion 的本质

消息结构

LLM 的输入是一个消息列表,每条消息有 role 和 content:

messages = [
    {"role": "system",   "content": "你是 AI 助手"},  # 系统指令
    {"role": "user",     "content": "你好"},          # 用户输入
    {"role": "assistant","content": "你好!"},         # LLM 回复
    {"role": "user",     "content": "什么是 Agent?"}, # 继续发问
]
role 含义 说明
system 系统指令 设定 LLM 身份和行为规则
user 用户消息 用户的输入
assistant LLM 回复 模型生成的回复,多轮时保留历史

关键理解:LLM 本质上是"下一个词预测器"。给定前面的所有文本,它预测最可能的下一个词。对话补全只是包装成了聊天的形式。

多轮对话

每次调用 API 时,你需要把完整的对话历史传回去:

# 第一轮
messages = [{"role": "user", "content": "1+1 等于几?"}]
response = llm(messages)

# 第二轮 —— 带上第一轮的历史
messages = [
    {"role": "user", "content": "1+1 等于几?"},
    {"role": "assistant", "content": "等于2"},
    {"role": "user", "content": "再加上3呢?"},
]
response = llm(messages)  # LLM 才能理解上下文

这就是 LLM 没有记忆的体现——每次调用独立,"记忆"靠你把历史传回去。

二、关键参数详解

temperature —— 创造力的旋钮

temperature 行为 适用场景
0 ~ 0.2 确定性输出,相同输入几乎总是相同输出 代码生成、分类
0.3 ~ 0.7 平衡创造力和准确性 日常对话
0.8 ~ 1.5 高随机性 创意写作、头脑风暴

核心:temperature 控制概率分布的采样策略。越低越倾向选概率最高的词,越高低概率词也有机会被选中。

max_tokens —— 输出长度上限

  • LLM 生成到 max_tokens 个 token 后停止
  • 一个汉字约 1~2 个 token
  • 设置太小会导致回答被截断

其他参数

参数 作用 说明
top_p 核采样 和 temperature 二选一
stream 流式输出 逐 token 返回,体验更流畅
stop 停止序列 遇到指定字符串时停止生成

三、结构化输出 —— JSON mode

这是 Agent 开发中最重要的技能之一。

Agent 需要 LLM 返回机器可解析的数据,而非自然语言:

  • Tool Use 的参数(下节课核心)
  • 信息抽取的结果
  • 分类/判定的结果

方式一:在 prompt 中要求

response = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": "你只输出 JSON 格式。"},
        {"role": "user", "content": "从"张三,25岁,北京"中提取信息,返回 {"name": "", "age": 0}"}
    ]
)

方式二:使用 response_format(推荐)

response = client.chat.completions.create(
    model=MODEL,
    messages=[...],
    response_format={"type": "json_object"},  # 原生 JSON mode
)

不是所有模型都支持 response_format。不支持时,方式一的 prompt 方式同样有效。

四、完整代码

💡 环境准备:本课使用与第 1 课相同的环境。如尚未配置,请先完成:

  1. pip install openai python-dotenv
  2. 进入 code/ 目录,复制 .env.example.env,填入你的 API Key(详见第 1 课)

新建 lesson02_structured_output.py,粘贴以下代码:

"""
第 2 课:理解 LLM —— 参数调优与结构化输出
"""
import json, os
from dotenv import load_dotenv
load_dotenv()

from openai import OpenAI
client = OpenAI(
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url="https://api.deepseek.com",
)
MODEL = "deepseek-chat"

# 切换 OpenAI 或通义千问请参考第 1 课

if not client.api_key:
    print("❌ 未检测到 API Key!")
    exit(1)

# === 示例 1:多轮对话 ===
print("=" * 50, "多轮对话", "=" * 50, sep="\n")
messages = [
    {"role": "system", "content": "你是 AI 助手,回答要简洁。"},
    {"role": "user", "content": "什么是 AI Agent?"},
]
resp = client.chat.completions.create(model=MODEL, messages=messages)
print("第一轮回答:", resp.choices[0].message.content[:100], "...")

messages.append({"role": "assistant", "content": resp.choices[0].message.content})
messages.append({"role": "user", "content": "它和普通 LLM 有什么区别?"})
resp = client.chat.completions.create(model=MODEL, messages=messages)
print("第二轮回答:", resp.choices[0].message.content[:100], "...")

# === 示例 2:temperature 对比 ===
print("\n", "=" * 50, "temperature 对比", "=" * 50, sep="\n")
for temp in [0.0, 0.7, 1.2]:
    print(f"\n--- temperature = {temp} ---")
    resp = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "user", "content": "给出一个 Python 斐波那契函数。"}],
        temperature=temp,
    )
    print(resp.choices[0].message.content[:120] + "...")

# === 示例 3:JSON 结构化输出(prompt 方式)===
print("\n", "=" * 50, "JSON 结构化输出", "=" * 50, sep="\n")
resp = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": "你只输出 JSON 格式。"},
        {"role": "user", "content": "从'王小明今年28岁,住在深圳'中提取信息。"
         "格式: {\"name\": \"...\", \"age\": 0, \"city\": \"...\"}"}
    ],
)
try:
    data = json.loads(resp.choices[0].message.content)
    print("解析成功:", json.dumps(data, indent=2, ensure_ascii=False))
except json.JSONDecodeError:
    print("解析失败,原始输出:", resp.choices[0].message.content)

# === 示例 4:response_format JSON mode ===
print("\n", "=" * 50, "response_format JSON mode", "=" * 50, sep="\n")
print("(部分模型不支持此功能,不支持时会自动降级)\n")
try:
    resp = client.chat.completions.create(
        model=MODEL,
        messages=[
            {"role": "system", "content": "你是一个 JSON 信息提取助手。"},
            {"role": "user", "content": "提取:李四,30岁,上海,设计师。"}
        ],
        response_format={"type": "json_object"},
    )
    data = json.loads(resp.choices[0].message.content)
    print("解析成功:", json.dumps(data, indent=2, ensure_ascii=False))
except Exception as e:
    print(f"response_format 方式失败(此模型可能不支持): {e}")

# === 示例 5:max_tokens 截断 ===
print("\n", "=" * 50, "max_tokens 截断效果", "=" * 50, sep="\n")
for limit in [20, 100, 500]:
    print(f"\n--- max_tokens = {limit} ---")
    resp = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "user", "content": "用 200 字介绍 AI Agent。"}],
        max_tokens=limit,
    )
    print(f"实际输出 {len(resp.choices[0].message.content)} 字",
          "→ 被截断" if resp.choices[0].finish_reason == "length" else "→ 完整")

运行结果示例

==================================================
多轮对话
==================================================
第一轮回答:AI Agent 是一个能自主感知环境、做出决策并采取行动...
第二轮回答:主要区别在于:LLM 只生成文本回复,而 Agent...

==================================================
temperature 对比
==================================================

--- temperature = 0.0 ---
def fibonacci(n): ...

--- temperature = 1.2 ---
Oh, 斐波那契!这玩意有趣,我想想...

==================================================
JSON 结构化输出
==================================================
解析成功:{
  "name": "王小明",
  "age": 28,
  "city": "深圳"
}

常见报错

问题 原因 解决
json.JSONDecodeError LLM 返回的不是有效 JSON 优化 prompt,或使用 response_format
response_format 方式失败 模型不支持原生 JSON mode 切回 prompt 方式
finish_reason: length 输出被 max_tokens 截断 增大 max_tokens 值

动手练习

  1. 写一个 prompt 让 LLM 稳定输出包含姓名、年龄、城市的 JSON
  2. 把 temperature 设为 0,连续调用 5 次,观察输出是否完全一致
  3. 把 max_tokens 设为 5,观察被截断的效果

思考题

  1. temperature = 0 时输出是"完全确定"的吗?为什么?
  2. 在 Agent 场景中,结构化输出为什么比自然语言输出更适合?
  3. 如果你的 Agent 需要从一段文本中提取关键信息,你会怎么设计 prompt?

📦 完整代码

本课程所有代码已托管在 GitCode:

git clone git@gitcode.com:gcw_A202cbBm/ai-agent.git
cd ai-agent/code

也可直接访问:https://gitcode.com/gcw_A202cbBm/ai-agent


下一篇预告:AI Agent 入门(三):Tool Use 入门 —— Function Calling 原理与实战

你将学会给 LLM 绑定工具(计算器、天气查询),让它从"只能说话"变成"能做事"。这是 Agent 最核心的一课。


如果您觉得有用,欢迎 点赞、转发、评论、关注

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐