必须用持久化存储存对话历史,因内存map在进程重启或pod重建后会清零,导致会话丢失和goroutine泄漏oom风险;裁剪上下文须按token数而非消息条数,需缓存每条消息tokencount并从最老user/assistant对开始删,跳过system和tool_calls消息;stop和switch_model指令依赖websocket双向能力实现,sse无法支持。

必须用持久化存储存对话历史,不能只靠内存 map;裁剪上下文必须按 token 数而非消息条数;控制指令(如 stop、switch_model)得靠 WebSocket 双向能力实现,SSE 做不了。
为什么不能把 []ChatMessage 直接存在内存里
进程重启或 Pod 重建后,所有会话历史瞬间清零。用户刚说“帮我查上个月的报销记录”,刷新页面就变成“我不记得你问过什么”。更隐蔽的问题是:goroutine 泄漏——每个 WebSocket 连接起一个 goroutine 管理读写,若连接异常断开但没清理 session 映射,map[string][]ChatMessage 会持续膨胀,OOM 风险陡增。
- 本地开发测试可用
github.com/dgraph-io/badger/v4:嵌入式、带 TTL、无网络开销,Open(Options{Dir: "./data", ValueDir: "./data"})一行初始化 - K8s 多副本部署必须用
github.com/go-redis/redis/v9:用SetEX(ctx, session_id, jsonBytes, 24*time.Hour)显式设过期,否则KEYS *扫描会随时间变慢 - 别在
Upgrader.CheckOrigin或握手阶段查 DB/Redis——耗时操作会卡住连接建立,前端频繁触发onerror
按 token 裁剪 []ChatMessage 的实操要点
硬删前 3 条消息可能把 system 指令或刚返回的 tool_calls JSON 一起删掉,模型直接报 400: context_length_exceeded 或胡言乱语。token 数才是模型真正“看见”的长度,len() 或字节数都不准。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 缓存每条消息的
TokenCount字段,避免每次写入都重算——ollama/api.CountTokens(本地)或openai.CountTokens(v1.40+)返回真实值 - 裁剪逻辑必须从最老的
user/assistant对开始删,跳过system和含tool_calls的消息 - 注意
Role字符本身占 token:"user"是 2 个 token,"assistant"是 3 个;Qwen2 和 Llama3 对 tool JSON 序列化的开销差 30%+ - 调用
TrimToMaxTokens(32768, "qwen2")后立刻写回存储——别等 AI 响应结束再存,断连时最后一条消息就丢了
如何用 WebSocket 实现 stop 和 switch_model
SSE 是单向的,只能服务器推,做不到这个。WebSocket 的双向特性刚好补上缺口:客户端随时发 {"action":"stop"} 或 {"action":"switch_model","model":"qwen2:14b"},后端收到就中断当前 http.Client 请求或更新 session 绑定的模型名。
- 前端
WebSocket.send(JSON.stringify({action: "stop"})),后端用conn.ReadJSON(&msg)解析 - 服务端需为每个会话维护
context.WithCancel,收到stop就调cancel()中断http.Client.Do - 模型切换要原子更新:先写新模型名到存储,再 reload 当前请求上下文,避免并发请求混用不同模型
- 用
sync.Mutex保护 session 写操作——前端可能快速连发多条消息,不加锁会导致append顺序错乱
token 计算和裁剪逻辑最容易被当成“辅助功能”草率处理,但它实际决定了模型能否稳定输出。别信文档写的“大概 1000 tokens”,一定要用对应模型的真实 tokenizer 算;也别图省事只删最老一条消息——那可能只是用户一句“你好”,而真正撑爆上下文的是后面三条带 JSON 的 tool call。










