文章目录
- 前言
- 1. 为啥大模型转头就忘?根子在这
- 1.1 你以为的聊天,本质是发请求
- 1.2 HTTP天生就“记不住”
- 2. 无状态不是缺陷,是故意设计的
- 2.1 真要“有状态”,服务器先崩了
- 2.2 无状态的真正爽点:随便扩容,挂了也不怕
- 3. 想让它“记住”你?全靠自己带档案
- 3.1 chatHistory就是你的随身病历本
- 3.2 带档案也有麻烦:越带越沉,烧钱
- 4. 光堆聊天记录不够,还有四层进阶玩法
- 4.1 第一层:Prompt Engineering
- 4.2 第二层:Context Engineering
- 4.3 第三层:Loop Engineering
- 4.4 第四层:Harness
- 5. 写个小demo:亲手验证“无状态”
- 5.1 项目结构和依赖
- 5.2 核心代码逻辑
- 5.3 跑起来是什么效果
- 5.4 几个容易踩的小坑
- 6. 最后唠两句实在的
P.S. 无意间发现了一个巨牛的人工智能教程,非常通俗易懂,对AI感兴趣的朋友强烈推荐去看看, 传送门https://blog.csdn.net/HHX_01
前言
不知道你们有没有过这种经历。
跟AI聊得好好的。
刚报完自己网名。
隔三句话再问它我叫啥。
它当场给你表演一个“您哪位?”。
你气得想拍桌子。
心说这AI怕不是只有七秒记忆,属金鱼的?
1. 为啥大模型转头就忘?根子在这
1.1 你以为的聊天,本质是发请求
真不是它记性差。
人家是正规军服务,不是跟你唠嗑的网友。
背后的道理说穿了俩词:HTTP,无状态。
你平时写代码调SDK。
写个client.chat.completions.create,看起来挺智能。
剥了SDK那层包装纸。
本质就是发了个HTTP POST请求。
跟你逛电商点“立即下单”,底层逻辑是一个路数。
1.2 HTTP天生就“记不住”
HTTP这协议,天生就没长“记事儿”的基因。
每一次请求,都是独立的、崭新的、六亲不认的。
服务器处理完就忘,干净利落,绝不拖泥带水。
就像奶茶店点单。
你今天去买了杯全糖珍珠奶茶。
明天再去同一个窗口。
服务员照样问你“您好喝点什么?”。
人家没义务记你昨天喝了啥。
每天那么多客人,真记不过来。
2. 无状态不是缺陷,是故意设计的
2.1 真要“有状态”,服务器先崩了
有人说,那服务器存一下聊天记录不行吗?
行,当然行,就是代价太大。
几百万用户同时在线聊天。
每个人的对话都塞服务器内存里。
那得堆多少服务器才够?成本直接上天。
更要命的是,万一这台服务器挂了。
所有人的对话上下文当场蒸发,集体失忆。
到时候客服电话能被打爆,运维直接连夜跑路。
高并发场景下,有状态就是累赘,谁用谁知道。
2.2 无状态的真正爽点:随便扩容,挂了也不怕
无状态的核心优势,不是“所有请求都一样”。
是你的请求,扔给集群里任何一台服务器,都能正常处理。
不用绑定某台机器,不用在机器之间同步状态。
扩容就加机器,坏了就切流量。
就像政务大厅的窗口。
你去哪个窗口都能办事。
不用非得找上次给你办的那个柜员。
人多了就多开几个窗口,柜员请假了就换个人顶班。
丝滑得很,根本不会卡壳。
3. 想让它“记住”你?全靠自己带档案
3.1 chatHistory就是你的随身病历本
那想让模型记得上下文怎么办?
简单,你自己带着。
服务器不存,你就每次都把聊天记录打包一起发过去。
就像去医院看病,自己带着病历本。
医生不用存你的病史,翻开本子啥都知道。
我们代码里那个chatHistory数组,干的就是这个活儿。
不是模型记住了你。
是你把上一轮的话原封不动又念了一遍。
3.2 带档案也有麻烦:越带越沉,烧钱
但这事儿不是没有代价。
聊天越久,history数组越长,token烧得越快。
相当于你每次去看病。
都把从小到大所有病历全带上。
医生翻得累,你打印也费钱。
所以就有了LRU淘汰、容量限制这些操作。
太久远的病历就别带了,只留最近、最相关的。
在“记得住”和“花得起”之间,找个平衡点。
4. 光堆聊天记录不够,还有四层进阶玩法
想让模型真的靠谱干活,光靠堆聊天记录太低级。
行业里基本是按这四层往上叠buff。
4.1 第一层:Prompt Engineering
说白了就是好好说话,把需求写清楚。
但这玩意儿本质是抽卡。
同一个prompt,这次答得惊艳,下次可能直接跑偏。
全看概率,稳定性约等于开盲盒。
4.2 第二层:Context Engineering
模型不知道的知识,你主动喂给它。
RAG捡外部资料,MCP接数据源,skill封装固定能力。
相当于给它配了个外置硬盘,要用啥插啥。
4.3 第三层:Loop Engineering
让它自己循环干活,想一步、做一步、看一步。
比如经典的ReAct框架,推理、执行、观察来回转。
不是一锤子买卖,是多轮闭环干活。
4.4 第四层:Harness
给它套上护栏和缰绳。
定目标、卡边界、做验收、控资源。
把实验室里的玩具,变成能上线的生产系统。
这四层不是替代关系,是一层叠一层。
越往上,可控性越强,靠谱程度越高。
5. 写个小demo:亲手验证“无状态”
说再多不如写两行代码直观。
整个极简demo,看看它到底是怎么“记住”你的。
5.1 项目结构和依赖
项目很简单,就仨文件。
依赖装个dotenv读配置,装个openai SDK调接口。
用DeepSeek做演示,接口兼容OpenAI,拿来就能用。
{"name":"demo","version":"1.0.0","type":"commonjs","dependencies":{"dotenv":"^17.4.2","openai":"^7.4.0"}}5.2 核心代码逻辑
核心就一个思路:自己维护聊天历史,每次全量发过去。
importOpenAIfrom'openai';import{config}from'dotenv';config();constclient=newOpenAI({apiKey:process.env.DEEPSEEK_API_KEY,baseURL:process.env.DEEPSEEK_BASE_URL,})// 全局维护对话历史constchatHistory=[{role:'system',content:'你是一个严谨的助手'}];asyncfunctiontestStateless(){// 第一轮:告诉模型名字chatHistory.push({role:'user',content:'请记住我叫字节戴'})constresponse=awaitclient.chat.completions.create({model:'deepseek-v4-flash',messages:chatHistory});chatHistory.push({role:'assistant',content:response.choices[0].message.content})// 第二轮:提问验证chatHistory.push({role:'user',content:'请问我的名字是什么?'})constresponse2=awaitclient.chat.completions.create({model:'deepseek-v4-flash',messages:chatHistory});chatHistory.push({role:'assistant',content:response2.choices[0].message.content})console.log('第二轮回复:',response2.choices[0].message.content);}testStateless().catch(console.error)5.3 跑起来是什么效果
执行流程特别直白。
先初始化数组,放一条system消息定人设。
第一轮把“我叫字节戴”塞进去,整段发给模型。
模型回复了,也塞回数组存好。
第二轮问名字,再把整个数组原封不动发过去。
注意,第二次发的时候,数组里已经有四条消息了。
模型能答出“字节戴”,全靠上下文里写着。
跟它服务器记没记住,半毛钱关系都没有。
5.4 几个容易踩的小坑
第一,模型的回复一定要存回数组。
只存用户提问不存模型回复,上下文就是碎的。
下一轮它照样不知道你在说啥。
第二,接口调用返回的是Promise。
老老实实写async/await,外层用catch接住错误。
别直接console.log异步函数。
打出来全是pending,纯自欺欺人。
6. 最后唠两句实在的
说白了,无状态就是大模型服务的底层规矩。
你平时用的那些AI编辑器、AI助手,看起来啥都记得。
不是模型记性好,是人家客户端把上下文玩明白了。
检索、裁剪、拼装、管理token,全是工程硬活。
地基是无状态的。
上面所有的“智能感”,都是一行行代码堆出来的。
至于长对话怎么裁剪、LRU怎么实现才不破坏语义。
那就是后面要踩的坑了,咱们慢慢踩慢慢唠。
P.S. 无意间发现了一个巨牛的人工智能教程,非常通俗易懂,对AI感兴趣的朋友强烈推荐去看看,传送门https://blog.csdn.net/HHX_01