第 30 章 对话记录与结构化输出
文章目录
- [第 30 章 对话记录与结构化输出](#第 30 章 对话记录与结构化输出)
-
- [🌈 关于《AI 零基础 36 讲》](#🌈 关于《AI 零基础 36 讲》)
- 多轮对话需要自己携带历史
- 让计划成为可以计算的数据
- [JSON 模式之后,还要做本地校验](#JSON 模式之后,还要做本地校验)
- 格式失败后,只修复一次
- 五组输入的实际程序结果
- 接上真实模型
- 本次真实模型验证
给学习计划助手一句"我想入门 Python,每天只有20分钟",程序得到一张任务表。再说"改成10分钟,保留动手代码练习",程序需要把前一轮要求带进去,也要检查新任务合计确实是10分钟。
本章把这两个需求放进一个小项目。五组输入及固定回应为原创教学数据,用来验证程序。

🌈 关于《AI 零基础 36 讲》
🧠 零基础也能开始学 AI
《AI 零基础 36 讲》是一套面向初学者的 AI 入门教程。课程从认识和使用 AI 开始,逐步学习
Python、数据处理、机器学习与深度学习,再进入大模型应用开发,最后完成个人知识库助手的综合实践。
📖 本篇是正式讲解文章
通过具体例子、原创图示和操作结果讲清概念。涉及编程的章节提供可运行代码,方便你边读边试。
🎯 读完以后,动手检验
建议按专栏顺序学习,并完成对应的课后训练,检查自己能否独立运用本章知识。
博客专栏:《AI 零基础 36 讲》
多轮对话需要自己携带历史
第 29 章每次只发送系统要求和一个问题。本章在消息列表中加入以前的用户需求与助手回答,再追加最新用户需求。
对话保存在会话文件中,消息按 user、assistant 成对排列。重新启动程序后,可以读取已经保存的记录。这个项目没有把记忆写进模型参数,历史来自本地文件并随请求发送。

历史会逐渐增长。本章最多保留最近三对有效消息,并对一次请求设置2000字符预算。超出时从最早的完整消息对开始删除,同时保留系统要求和最新用户需求。如果这两部分本身已经太长,就要求缩短需求,不发送请求。
字符预算方便初学者先看懂裁剪过程。它不能直接代表模型 Token 上限,实际生产程序还需要使用对应模型的 Token 计数方法和上下文限制。
先观察裁剪后角色的顺序。
python
import json
from plan_core import build_messages
plan = {"goal": "Python", "daily_minutes": 20,
"tasks": [{"title": "练习变量", "minutes": 20}]}
history = []
for i in range(3):
history += [{"role": "user", "content": f"第{i+1}轮需求"},
{"role": "assistant", "content": json.dumps(plan)}]
messages = build_messages(history, "改为10分钟", 10, max_pairs=1)
print([item["role"] for item in messages])
print(messages[1]["content"], messages[-1]["content"])
输出如下。
text
['system', 'user', 'assistant', 'user']
第3轮需求 改为10分钟
最早两对没有发送。最新一对仍然包含用户和助手,避免只剩一半。系统要求中的当前分钟预算也会更新。
让计划成为可以计算的数据
如果回答只是一段自然语言,程序很难可靠地算出时间。本章要求返回一个 JSON 对象,字段固定为 goal、daily_minutes 和 tasks。
json
{
"goal": "入门 Python",
"daily_minutes": 20,
"tasks": [
{"title": "阅读变量示例", "minutes": 8},
{"title": "改写并运行两行代码", "minutes": 12}
]
}
这是第一组教学计划。goal 保存目标,daily_minutes 保存本轮预算,tasks 是任务列表,每个任务包含 title 和 minutes。接下来程序可以计算 8 加 12,也可以把任务显示成表格。
数据结构文件用 JSON Schema 描述类型、必需字段和列表范围。required 表示不能缺少的字段,additionalProperties=false 表示不能随意增加字段。
本章写了专用 Python 校验函数,逐项检查这个固定结构,没有实现一个通用 JSON Schema 引擎。空白目标、布尔值分钟和任务总时长另有业务检查。比如 Python 中 True 与整数有继承关系,分钟检查使用 type(value) is int,避免把 True 当作一分钟。
JSON 模式之后,还要做本地校验
客户端设置 response_format 为 json_object,系统要求包含 JSON 字样和一个形状示例。设置方法核对 DeepSeek JSON Output 文档。系统要求还说明输出应包含哪些字段,以及本轮分钟预算。
JSON 模式约束格式。程序仍需检查对象字段、字段类型、分钟总和及结束原因。一个可解析的对象可能缺少 tasks,也可能把 daily_minutes 写成字符串。

校验顺序从文本开始。先检查回答是否正常结束,再用 json.loads 转成对象,检查必需字段和类型,最后计算任务分钟之和。
python
from plan_core import parse_plan
text = '{"goal":"Python","daily_minutes":20,"tasks":[{"title":"练习变量","minutes":20}]}'
plan = parse_plan(text, 20)
print(plan["goal"])
print(sum(task["minutes"] for task in plan["tasks"]))
输出为 Python 和 20。当前预算显式传入校验函数,所以即使模型返回一份30分钟的合法 JSON,也不能通过这次20分钟的任务检查。
校验通过说明程序能处理这些字段,以及已检查的时间约束成立。它不能证明任务难度适合读者、学习目标一定能完成或计划内容没有事实错误。读者仍需查看具体安排。
格式失败后,只修复一次
收到"这是一份计划"时,JSON 解析失败;收到只有 goal 的对象时,必需字段检查失败。程序把具体校验原因放进修复消息,再请求一次完整 JSON。
下面用固定回应模拟第一份对象缺字段,第二份补齐。
python
from plan_core import empty_session, make_plan
from run_cases import TeachingReplay
client = TeachingReplay([
'{"goal":"Python"}',
'{"goal":"Python","daily_minutes":20,"tasks":[{"title":"练习变量","minutes":20}]}',
])
state, attempts = make_plan(empty_session(), "学Python", 20, client)
print(len(attempts), [item["valid"] for item in attempts])
print([item["role"] for item in state["history"]])
输出如下。
text
2 [False, True]
['user', 'assistant']
临时修复消息没有混进长期历史。会话只保存原始需求和已经通过校验的计划。两次回答仍然失败时,程序结束本轮,原会话保持不变。缺失密钥、认证失败、限流和网络错误沿用第 29 章处理,不在这里自动重试。
真实模型的格式修复可能增加一次调用与用量。默认最多一次修复,命令行 --repair 0 可以关闭。修复请求也受字符预算限制,错误文本最多截取400字符,避免把过长无效回答重新发回去。
五组输入的实际程序结果
执行本地样例入口。
powershell
py -3.12 examples/run_cases.py
它读取五组原创输入和计划,经过与真实入口相同的解析、字段及分钟校验流程,记录结果。
| 教学输入主题 | 当前预算 | 任务分钟 | 校验 |
|---|---|---|---|
| Python 入门 | 20 | 8、12 | 通过 |
| 英语词汇复习 | 30 | 10、15、5 | 通过 |
| 线性回归 | 25 | 10、15 | 通过 |
| 表格清洗 | 15 | 6、9 | 通过 |
| 注意力计算 | 40 | 15、20、5 | 通过 |
完整结果还保存了20分钟改为10分钟的两轮记录、会话保存重载一致性、无效 JSON 和缺字段修复。五组固定回应没有进行模型生成,也没有验证自然语言理解效果。
接上真实模型
真实入口复用第 29 章的本机密钥与集中配置。第一次请求明确提供分钟预算。
powershell
py -3.12 examples/plan_assistant.py "我想入门 Python,每天只有20分钟" --minutes 20
再次运行时,程序读取会话文件。修改分钟预算也要显式修改参数,避免只依赖自然语言推断。
powershell
py -3.12 examples/plan_assistant.py "改成10分钟,保留动手代码练习" --minutes 10
本轮全部尝试的回答与用量另存独立记录,长期会话保存最近的有效计划。若读取到损坏的会话文件,程序报告错误并保留原文件。
本次真实模型验证
2026 年 10 月 4 日,向 deepseek-flash 发送同一组五个需求,返回任务分钟分别如下。
| 输入主题 | 预算 | 真实响应中的任务分钟 | 本次 Token 总量 |
|---|---|---|---|
| Python 入门 | 20 | 10、10 | 213 |
| 英语词汇 | 30 | 10、12、8 | 225 |
| 线性回归 | 25 | 10、10、5 | 224 |
| 表格清洗 | 15 | 5、5、5 | 223 |
| 注意力计算 | 40 | 10、10、10、10 | 240 |
五份对象均通过字段、类型、当前预算和分钟求和校验。随后加载第一份真实计划,发送"改成10分钟,保留动手代码练习"。真实回应将任务改为4分钟基础语法和6分钟动手代码,校验通过,用量275个 Token。
这六次请求都一次通过,没有格式修复,合计1400个 Token。见五组真实结果与多轮记录。真实命令行入口另发送一个变量练习需求,返回一项20分钟任务,保存会话和独立用量记录,用量204个 Token。
本章真实验证合计七次请求、1604个 Token。输入为作者编制的教学需求,输出为当次平台实际生成。格式错误与缺字段仍通过本地固定失败案例验证,没有为了制造失败重复发送付费请求。
模型给出的任务分钟相加正确,计划是否适合自己的基础仍需阅读判断。先完成【零基础学AI】第 30 章课后练习与答案,下一章再让助手查找个人资料。
