FastAPI猜字谜实战(8):收官

这是系列的最后一篇。功能都齐了,页面也能玩了,但攒了几个膈应人的小毛病没治:每次得手动开浏览器、AI 回复带乱码符号、一进页面就顶掉我上局的进度、历史全是时间戳分不清。这一篇挨个收拾,项目就算正式落地了。

一、不想每次手动敲地址,让它自己弹出来

最烦的是每次玩都得先跑 main.py,再切到浏览器输入 localhost:8000。能不能一启动就自动把页面打开?

能。Python 自带 webbrowser,我配合定时器延后一下就成:

import threading

import webbrowser

if name == "main":

延迟 1.5 秒后再打开浏览器

threading.Timer(1.5, lambda: webbrowser.open("http://localhost:8000/")).start()

uvicorn.run(app, host="0.0.0.0", port=8000, access_log=False)

关键是那个延迟。 服务是 uvicorn.run 才起来的,我要是紧接着就 webbrowser.open,浏览器抢在服务监听之前打开,会直接显示连不上。所以我用 threading.Timer 把开浏览器这件事推后 1.5 秒,等后厨把门支起来了再叫客人。这样我直接点运行,终端服务和浏览器页面就能一起就位。

二、AI 回复带一堆井号星号,看着太乱

大模型爱用 Markdown 排版,动不动给我冒出 # 标题、**加粗** 这些符号,可我的气泡是纯文本,这些符号就原样杵在那,脏兮兮的。

我上了两道防线。第一道从源头治,在系统提示词里明令禁止:

- 只用纯文本回复,绝对不要使用任何 Markdown 标记(如 # 井号标题、* 星号加粗、反引号、- 列表符号等)。

但 prompt 不是百分百听话,偶尔还是漏。所以第二道在前端兜底,渲染 AI 回复前先用正则把这些符号洗掉:

function cleanMarkdown(text) {

if (!text) return text;

return text

.replace(/```\\s\\S*?```/g, '') // 去代码块

.replace(/`(\^\`*)`/g, '$1') // 去行内代码

.replace(/\*\*(\^\**)\*\*/g, '$1') // 去加粗

.replace(/(^|\n) \\t*#{1,6} \\t*/g, '$1') // 去标题

.replace(/(^|\n) \\t*\*- \\t+/g, '$1') // 去列表符

.trim();

}

我在 addBubble 里,凡是 AI 的消息就先过一遍 cleanMarkdown 再显示(我的消息不用洗)。源头约束加末端兜底,双保险,页面就干净了。

三、一进页面就顶掉上局进度,改成"接着玩"

第七篇我写的是页面一加载就 startNewGame 新开一局。可当我想续玩上一局时,一刷新全没了,特别气人。

我改了加载逻辑:进来先问后端要历史列表,有历史就自动载入最近那一局,实在没有才新开。

async function initOnLoad() {

const resp = await fetch('/api/sessions');

const data = await resp.json();

const sessions = (data.code === 200 && data.data) ? data.data : \[\];

if (sessions.length) {

await resumeSession(sessions0.id); // 直接续上最近的一局

} else {

await startNewGame(); // 没历史才新开

}

}

这里能直接拿 sessions[0] 当最近一局,靠的是第五篇列表接口已经按时间倒序排好了,头一个就是最新的。resumeSession 会请求那局的详情、把历史消息一条条铺成气泡,我接着往下猜就行。想开新局我留了"新游戏"按钮,主动点它才是新开。

四、历史全是时间戳分不清,给它加个名字

历史一多全是 2026-10-23_15-30-12 这种,根本记不住哪个是哪局。第三篇我存了个空 name 字段,就是留着这时候用的。

后端我补了个 PUT 接口专门改名。改名前我先判断这局在不在,不在就返回 404 提示,而不是直接抛异常:

@app.put("/api/sessions/{session_id}", response_model=ApiResponse, summary="重命名会话", tags="会话管理")

def rename_session(session_id: str, request: RenameRequest) -> ApiResponse:

session_file = get_session_file_name(session_id)

if not session_file.exists():

return ApiResponse(code=404, message="会话不存在,无法重命名", data=None)

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

session_data = json.load(f)

session_data"name" = request.name

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=None)

我只改 name 这一个字段,读出来改完再原样写回,messages 一点不动。前端历史列表每项挂个重命名按钮,弹个框让玩家输新名字,请求成功后刷新列表就显示新名了。

这一来,RESTful 那四个动作 GET 查、POST 增、PUT 改、DELETE 删,我总算在这项目里凑齐了。

五、AI 偶尔蹦几个外语乱码词,调下温度

有个小毛病我注意了一阵:AI 偶尔会莫名其妙蹦出 first、obg 这种外语碎词,很出戏。这是模型采样太跳脱导致的,跟提示词关系不大。

我调了调用大模型时的 temperature,从偏高的 1.5 降到了 1.2:

response = client.chat.completions.create(

model="deepseek-chat",

messages=messages,

stream=False,

temperature=1.2,

)

这个值管的是回复的随机程度,越高越放飞、越低越稳。猜字谜这种要它老老实实出题判题的活,我不需要它太有创意。降一点之后中文输出明显稳了,乱码词基本不蹦了。

六、折腾完回头看这八篇

从第一篇那六行 Hello World,到这最后一篇的细碎打磨,一个能在浏览器里点着玩的 AI 猜字谜应用,就这么一步步立起来了。

我最大的体会是,这活真没我想象中的高深。前后端怎么分工、数据怎么存成 JSON、大模型怎么接、页面怎么点着玩------拆开看每一步都挺朴素,就是一个接一个地把痛点填上。

我这一路的顺序也一直没乱:先把后端接口写出来、拿 /docs 点通,再去做前端把接口接上玩,最后回头收拾那些体验毛病。 要是反过来先搭花架子,我多半早就绕晕在前后端那条线里了。

相关推荐
爱敲代码的TOM1 小时前
10.27 易购软件 Java开发线下一面
java·开发语言
学逆向的1 小时前
C++ 别名命名空间
开发语言·c++·网络安全
沉下心来学鲁班1 小时前
具身智能实战:让机器鸭爬梯子——双策略交接与接触力控制的深度解读
开发语言·人工智能·python·机器人
TickDB1 小时前
美股历史数据下载:用 Codex 取数,Python 导出 CSV
python·websocket·mcp·行情数据 api
云南新人和数采集团1 小时前
开源项目吐槽大会:一篇技术文章的大纲设计
python·scrapy·开源
fengyangaiGEO1 小时前
外贸B2B GEO优化:为何是平台成功的关键?
大数据·人工智能·python
David猪大卫1 小时前
【C++修炼】STL容器—stack与queue
c语言·开发语言·数据结构·c++·经验分享·笔记·学习