FastAPI猜字谜实战(4):让AI主持人开口

这是系列的第四篇。上一篇,我把每局游戏的"空壳"存下来了,但里面 messages 还是空的,AI 还不会说话。这一篇我接上大模型 DeepSeek,让主持人"柯灵"真正能出题、判题,还能记住我们聊到哪了。

一、先想清楚:大模型到底吃什么?

我一开始以为,调大模型就是把用户那句话发过去就行。后来发现根本不是。

大模型是无状态的 ,它不记得上一句,每次都得你把该知道的背景全塞给它。所以调用它时,我发的是一个列表,叫 messages,里面每条都是一个带 role 和 content 的字典:

  • system:我给它的总设定。

  • user:玩家说的话。

  • assistant:AI 之前回过的话。

它靠每次把历史全带上,才制造出一种"它在记住"的错觉。

二、给 AI 立人设:SYSTEM_PROMPT

柯灵该是什么脾气、该怎么出题判题,我写在一段系统提示词里,做成一个全局变量:

SYSTEM_PROMPT = """你是一位汉字猜字谜游戏的主持人,名叫柯灵。

  • 每次给一个字谜,难度先询问用户,不重复出题。

  • 你必须牢牢记住当前这道题的谜底,用户猜错就判错,绝不因为用户说得自信就改口。

  • 用户猜错时先给一点提示,不要直接公布谜底。

  • 语气亲切活泼,回复尽量简短。"""

这段我放在函数外面,因为它恒定不变。判题那条是我特意强调的,不然 AI 特别容易被玩家忽悠,玩家一自信它就直接改口了。

三、装库、配密钥

DeepSeek 兼容 openai 的调用格式,所以我直接 pip install openai,然后建一个客户端:

from openai import OpenAI

import os

client = OpenAI(

api_key=os.environ.get("DEEPSEEK_API_KEY"),

base_url="https://api.deepseek.com",

)

这个 api_key 我我没选择写在代码里,而是从环境变量读。因为密钥一旦硬编码,我传代码或者截图时就泄露了。我把密钥配在系统环境变量 DEEPSEEK_API_KEY 里,代码只负责去取。base_url 告诉它请求发到 DeepSeek 的服务器,而不是 OpenAI 官方。

四、组装 messages:背景 + 历史 + 这一次

到了对话接口里,最核心的动作就是把三样东西按顺序拼成 messages:

@app.post("/api/chat", response_model=ApiResponse, summary="与AI柯灵猜字谜", tags="AI 对话")

def chat(request: ChatRequest) -> ApiResponse:

session_file = get_session_file_name(request.session_id)

with open(session_file, "r", encoding="utf-8") as f:

session_data = json.load(f)

messages = {"role": "system", "content": SYSTEM_PROMPT}

messages.extend(session_data"messages")

messages.append({"role": "user", "content": request.message})

第一步,先根据会话 id 把那局的历史 JSON 读出来。然后我拼消息:第一条永远是系统人设,中间把历史消息 extend 进来,最后把玩家这一次说的话作为 user 加上。

顺序不能乱,人设必须在最前面压着。

(注:那个 get_session_file_name 是个小工具,把会话 id 变成路径,好几个接口都要用,我就抽出来单独写了。)

五、调用并解析回复

拼好消息就发给大模型:

response = client.chat.completions.create(

model="deepseek-chat",

messages=messages,

stream=False,

)

ai_reply = response.choices0.message.content

stream=False 是我先不做打字机效果,一次性把整段回复拿回来。拿到之后,真正的文字藏在 response.choices[0].message.content 里,我把它取出来,就是柯灵这轮的回复。

六、让它记住进度:一存一删是关键

光回一句不够,得让这轮的问答也存进历史,下轮才接得上。这里有个我琢磨了一阵才懂的细节:

messages.append({"role": "assistant", "content": ai_reply})

messages.pop(0)

session_data"messages" = messages

with open(session_file, "w", encoding="utf-8") as f:

json.dump(session_data, f, ensure_ascii=False, indent=2)

return ApiResponse(code=200, message="请求成功", data=ai_reply)

我先把 AI 这句回复以 assistant 角色追加进 messages,这样历史就完整了。但紧接着,我 pop(0) 把第一条系统提示词删掉,再写回文件。

为什么要删?因为系统人设是我每次调用时现加的,根本不需要存进文件。要是把它也存下来,文件会越来越臃肿,而且下轮我又加一遍,就重复了。

所以,落盘的历史里只有 user 和 assistant 交替,干净,又能续得上。

我实测连猜了两轮,JSON 里确实累积了四条消息,且没有 system 那条,进度也接得上。到这一步,游戏算是有灵魂了。

相关推荐
打工仔折腾 AI1 小时前
AI Agent 工程化实战(二):5 分钟跑通 Deep Agent 并逐层拆解底层链路
java·数据库·人工智能·后端·python·langchain·ai agent 实战
云和数据.ChenGuang1 小时前
Python map() 函数详解:从语法、原理到实战用法
开发语言·人工智能·python·langchain·1024程序员节
秋名RG1 小时前
(二)存储、CPU 与指令执行——程序为什么快,又为什么慢
java·开发语言
Highcharts.js1 小时前
Highcharts甘特图开发带导航功能的项目管理图示例
开发语言·前端·javascript·甘特图·highcharts·项目管理图
kyle~1 小时前
C++ 原子操作、内存模型与无锁并发编程
linux·开发语言·c++·操作系统
BeiGuTing_code1 小时前
Java泛型与Object
java·开发语言
叶子野格1 小时前
《JAVA学习:前置知识》
学习
2401_872418781 小时前
互联网公开信息检索完全指南:Google、Yandex、Wayback Machine、DNS、WHOIS、Shodan 等 OSINT 工具大全
开发语言·php
HEJOO91 小时前
Java 类加载过程详解:从加载到初始化的完整链路
java·开发语言·firefox