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

本文为FastAPI猜字谜系列终篇,聚焦用户体验细节优化:实现服务启动自动打开浏览器页面;前端+后端双层清洗AI返回的Markdown乱码符号;改进会话加载逻辑支持续玩上局;为历史会话添加可编辑名称字段并补全RESTful接口;通过降低temperature参数抑制AI输出外语乱码词。所有优化均基于Python、FastAPI、Uvicorn及前端JavaScript实现。

这是系列的最后一篇。功能都齐了,页面也能玩了,但攒了几个膈应人的小毛病没治:每次得手动开浏览器、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(sessions[0].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 点通,再去做前端把接口接上玩,最后回头收拾那些体验毛病。 要是反过来先搭花架子,我多半早就绕晕在前后端那条线里了。

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值