把一个需求连续问三次,AI 却像第一次听说一样重新开始,问题可能出在应用没有带上历史。DeepSeek 的聊天补全接口是无状态接口:每次请求需要由调用方提供本轮所需的对话上下文。网页聊天界面里看起来自动完成的工作,在自己的脚本中需要明确实现。

本文用“为社区阅览室设计借书查询小工具”练习两轮需求澄清。第一轮整理已知条件和缺口,第二轮加入负责人补充的规则,再让助手更新方案。最终得到的是带依据的需求草稿,不是已经上线的软件。示例材料为原创虚构,本文只核验请求组织方式,没有实际调用付费模型,也没有把参考结果冒充模型输出。

一、先确定这次要解决的具体问题

阅览室想做一个小工具,让志愿者按图书编号查询是否在架。负责人最初只提供三条信息:图书有编号;志愿者需要查询;目前不处理线上借阅。缺少的内容包括编号格式、数据来源、谁能修改状态,以及“在架”和“可借”是否表示同一件事。

第一轮不能要求 AI 直接写完系统,否则它可能顺手加入会员登录、预约、归还和支付。把任务限制为“列出已知需求、未知条件和待确认问题”。第二轮只补充已经由负责人确认的内容,不把模型自己的建议当成新的业务事实。

这类任务适合学习多轮上下文,因为读者很容易检查第二轮是否真的保留了“暂不处理线上借阅”的边界。需要连续对话,不代表可以让助手自行扩大项目范围。

两轮借书查询需求如何组成上下文的示意

二、准备账户、环境和文件

正式调用需要 DeepSeek 开放平台账户、可用 API 密钥及相应调用额度。网页聊天会员与 API 调用不是可以随意互换的概念,价格和账户条件以自己的平台页面为准。本文不填写固定单价,也不代读者购买额度。

本例沿用官方指南中的 OpenAI Python SDK 兼容调用方式。准备可用的 Python 环境,在单独的练习目录中安装 SDK:

python -m pip install openai

如果机器上有多个 Python,使用同一个解释器安装和运行,避免安装成功但脚本找不到模块。不要把项目文件命名为 openai.py,否则可能遮住真正的 SDK。公司设备是否允许安装依赖,应遵循已有管理规定。

密钥通过 DEEPSEEK_API_KEY 环境变量提供,不写进正文、仓库、截图或共享聊天记录。使用当前终端支持的环境变量设置方式,在本机输入自己的值;检查时只确认变量是否存在,不打印完整密钥。运行结束后按自己的安全要求处理会话环境。

三、理解 messages 的三个角色

system 放稳定的任务约束,user 放用户提供的需求和补充,assistant 放前一轮模型返回的回答。第二轮通常需要保持“约束、首次问题、首次回答、新问题”的顺序。只发送一句“按刚才的改一下”,接口不会凭空知道刚才是什么。

也不要把上一轮回答改成 system。模型提出的建议仍然是待审核建议,提升成更高优先级规则可能让后续回答错误地服从它。对话中出现的引用材料、客户文档或网页文字也不能自动升级为应用授权。

对话记录由自己的应用负责管理。不同客户或不同项目分别使用独立消息列表,不要把所有请求追加到一个全局数组。仅仅在文字里写“现在换一个客户”并不能代替真正的上下文隔离。

四、先构造第一轮需求

把第一轮任务保存为明确的文字,而非一句“帮我做个借书系统”:

社区阅览室需要一个给志愿者使用的图书查询小工具。
已知:图书有编号;需要查询是否在架;暂不处理线上借阅。
请按“已知需求、待确认问题、暂不实施事项”整理。
不得假设已有数据库、会员账户或支付功能。
没有提供的条件保留待确认,不要开始写代码。

预期验收是三类条目,而不是漂亮的长方案。合理的待确认问题应指向编号、数据来源和权限等缺口;若输出已经写了完整会员积分功能,就说明范围控制失败。即使接口返回成功,也不能把业务内容自动判为合格。

不要为了让模型看起来聪明而提前在第一轮塞入第二轮的全部答案。这个练习要检查的是历史与新增信息的关系,材料分层应该真实反映讨论的先后顺序。

五、两轮调用的最小示例

下面展示非流式调用结构。官方当前多轮示例使用 deepseek-flash 和 https://api.deepseek.com;正式使用时仍需核对账户实际可用模型。代码没有工具调用,也没有执行文件、部署网页或修改数据库的能力。

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["DEEPSEEK_API_KEY"],
    base_url="https://api.deepseek.com",
)
messages = [
    {"role": "system", "content": "只整理需求,不编造规则,不写代码。"},
    {"role": "user", "content": (
        "阅览室志愿者按图书编号查询是否在架;"
        "暂不处理线上借阅。列出已知、待确认和暂不实施事项。"
    )},
]
first = client.chat.completions.create(
    model="deepseek-flash", messages=messages, stream=False,
)
messages.append(first.choices[0].message)
messages.append({"role": "user", "content": (
    "补充确认:图书编号是六位数字,前导零要保留;"
    "资料来自管理员提供的CSV;志愿者只有查询权限。"
    "请更新需求,指出新增事实,继续保留未确认问题。"
)})
second = client.chat.completions.create(
    model="deepseek-flash", messages=messages, stream=False,
)
print(second.choices[0].message.content)

这是待读者在具备调用条件后运行的 API 示例,不是本文的付费实测。先理解两次请求的结构,确认额度和材料安全后再执行。不要把实际返回中尚未审核的内容直接写入生产配置。

如果密钥环境变量不存在,代码会在构造客户端前报错,这是提醒你先完成本机配置,不应改成把密钥硬编码进文件。接口认证失败、余额不足或网络错误,要按实际错误排查,不把失败请求伪装成一次成功对话。

六、第二轮应当保留和改变哪些东西

正确的更新草稿至少应保留“不处理线上借阅”,新增“六位数字编号、保留前导零、管理员提供 CSV、志愿者只查询”四项事实。它还应该继续询问 CSV 字段名、更新频率及数据异常如何处理,不能把缺少的字段名当作已经确定的接口。

参考整理可以写成:查询输入是编号文字,不直接转成整数;数据来源为管理员交付的 CSV;志愿者不可编辑状态;借阅、预约和支付不在本次范围。这里是人工设计的参考检查点,不是某个真实 API 返回的逐字内容。

前导零是一个具体的验收点。例如 001234 不能变成 1234;但保留字符串并不意味着可以接受任意六个字符。需要六位数字的业务校验应另行定义,不能只凭模型口头说“已处理”就认定实现完成。

第二轮新增事实与保留边界的验收示意

七、不调用模型也能先检查组织逻辑

用两份独立列表模拟两个项目,把第一份的历史追加完后,确认第二份仍为空或只有自己的初始材料。再检查第二轮发送前的角色顺序,应该包含前一轮 assistant 回答,而不是只有两条用户问题。这个检查不消耗 API,也不能证明生成质量。

还可以故意删除首轮需求,观察自己构造的消息中是否还保留范围约束。结构检查的意义是发现遗漏;不要把模拟回答写成“模型已成功记住所有内容”。真实模型回答是否遵守约束,需要读者运行后单独人工验收。

保存日志时保留项目编号、消息角色、请求时间和错误类型通常已经能帮助排查。是否保存完整正文应按材料敏感程度决定。不要为了调试方便,把客户文档和密钥一起打印进公开日志。

八、历史越来越长时怎么办

把所有历史永远追加并不合适。重复资料会增加输入用量,过长上下文也会影响使用边界。项目讨论进入新阶段时,可以整理一份经过人工确认的需求基线,明确哪些规则已确定、哪些还待确认,再把必要的新讨论接在后面。

基线摘要也可能遗漏条件,因此要保留原始记录用于回查。不能让模型自行摘要后删除唯一原稿;不能只保留“做一个查询系统”而丢掉“志愿者只读、编号保留零”的关键约束。缩短上下文与提示词缓存是不同问题,不要把减少历史等同于命中缓存。

验收一轮多轮功能应同时检查:上下文是否正确带入、项目是否隔离、业务事实是否保留、缺失条件是否继续标记、错误是否显式处理。只有请求结构正确与内容审核通过,才进入后续开发;本篇到需求草稿为止,不宣称已部署成品。

官方参考