从零实现AI助手跨会话记忆系统:三层记忆架构详解

发布时间:2026/9/8 4:02:40

从零实现AI助手跨会话记忆系统:三层记忆架构详解
大多数人做 AI 助手做到第三步就卡住了换一个新会话AI 什么都不记得。这不是模型不够聪明而是你没有给 AI 设计记忆系统。这次我们直接动手从零实现一个跨会话记忆系统采用三层记忆架构让 AI 在多个会话之间仍然能记住用户的名字、偏好和历史关键信息。这套方案重点解决三个问题第一新会话不再“失忆”第二记忆按重要程度分层不是把所有聊天记录都塞进上下文第三代码结构能直接接到你自己的 FastAPI 服务、Web 应用或本地模型上。文末会给出完整的功能测试流程、接口调用示例和常见问题排查清单。1. 三层记忆系统核心能力速览能力项说明项目类型AI 助手跨会话记忆系统从零手写实现记忆架构工作记忆短期 情景记忆长期 核心记忆用户画像技术栈Python、FastAPI、OpenAI 兼容接口、SentenceTransformer、NumPy大模型接入支持本地模型Ollama / vLLM / llama.cpp和云端 OpenAI 兼容 API启动方式命令行启动 FastAPI 服务是否支持 API支持提供/chat对话接口、/memory/{user_id}记忆查询接口是否支持批量任务接口天然支持批量调用注意并发下的记忆写入排锁记忆检索向量相似度检索默认使用BAAI/bge-small-zh-v1.5中文向量模型隐私安全支持按用户维度隔离记忆生产环境需增加鉴权和删除接口适合场景个人助手、客服机器人、知识库对话、角色扮演、工作流 Agent这套设计不依赖特定大模型LLM 部分只是走标准 OpenAI 兼容协议。你可以用云端模型调试逻辑也可以切换到本地模型做隐私部署。2. 为什么 AI 助手会“失忆”三层记忆设计与选型思路普通对话接口是“无状态”的。每一次请求模型只看到当前传入的消息列表请求结束之后上下文就丢掉了。你新建一个会话之前的对话内容就完全消失。要解决这个问题核心思路是把有价值的对话信息沉淀到外部存储中在后续请求时重新注入。近期一些开源记忆框架例如 RippleMem 这类项目也在强调同一个方向记忆系统不是把更多历史消息检索出来堆给模型而是让 agent 学会“回忆”。所谓回忆就是按重要程度提取、存储、检索和遗忘。基于这个思路我把记忆拆成三层。2.1 第一层工作记忆短期会话上下文工作记忆就是当前会话内的消息列表。它负责维持对话的连贯性直接拼到 prompt 里交给大模型。这层的特点是时效性强、信息密度低。如果全部会话历史都塞进去很快会超出模型上下文窗口。所以必须设置上限比如最近 20 条消息超过之后就丢弃最旧的消息或者用 LLM 对旧消息做摘要压缩。2.2 第二层情景记忆跨会话事件检索情景记忆负责记录有价值的历史信息例如用户在上一段对话里提到过“最近在准备考研”“喜欢刘慈欣的小说”“项目下周上线”。这些信息不会直接全部塞进 prompt而是先做向量化存储。用户发起新问题时把当前问题转成向量从已有记忆里检索出语义最相关的 top_k 条再拼入系统提示词。这层解决的是“跨会话回忆”问题用户在新会话中问起旧话题AI 能通过向量检索找到相关历史而不是靠模型自己去猜。2.3 第三层核心记忆用户画像与稳定事实核心记忆保存的是用户的基本事实和稳定偏好例如姓名、职业、所在地、兴趣爱好、说话风格偏好。这些信息每次对话都应该被加载作为系统提示词的一部分。和情景记忆不同核心记忆不依赖语义检索而是按用户维度直接读取。它的特点是稳定、长期、结构化适合用 JSON 文件或数据库表维护。场景上可以理解为“AI 对用户的长期认知”。三层记忆的配合关系如下核心记忆保证 AI 始终知道“用户是谁”情景记忆保证 AI 能回忆起“过去聊过什么”工作记忆保证当前这轮对话上下文完整。三层各司其职不会互相干扰。3. 环境准备与前置条件3.1 基础依赖本次演示使用 Python 3.10 以上版本。项目核心依赖如下fastapi uvicorn openai numpy sentence-transformers创建项目目录并安装依赖mkdir ai-memory-system cd ai-memory-system pip install fastapi uvicorn openai numpy sentence-transformers如果你本机没有可用的 GPU也没问题。SentenceTransformer 的向量化可以用 CPU 跑bge-small-zh-v1.5是轻量级模型CPU 推理速度可以接受。真正的大模型推理走的是外部接口不占用本地推理资源。3.2 LLM 接口说明本项目的LLMClient直接使用 OpenAI 兼容接口格式。我建议先准备一个可用的模型服务例如本地 Ollamaollama run qwen2.5:7bOllama 启动后默认暴露的接口是http://127.0.0.1:11434/v1可以通过 OpenAI SDK 直接调用。如果你使用的是其他模型服务只要它兼容/v1/chat/completions接口格式都可以通过修改base_url接入。3.3 向量检索方案为了便于理解原理这版实现没有引入独立的向量数据库而是用 SentenceTransformer 生成向量再用 NumPy 计算余弦相似度最后把向量和原始文本一起持久化到 JSON 文件。这种实现方式的好处是零额外依赖适合学习原理和做功能验证。如果未来记忆量达到几万条以上建议替换为 FAISS 或 ChromaDB检索性能会更好。这个点会在后续章节详细说明。4. 代码实现从零搭建三层记忆系统先给出完整项目结构后面按文件逐个实现ai-memory-system/ ├── main.py ├── requirements.txt ├── memory/ │ ├── __init__.py │ ├── agent.py │ ├── llm_client.py │ ├── working_memory.py │ ├── episodic_memory.py │ └── semantic_memory.py__init__.py保持空文件即可。下面直接写核心代码。4.1 LLM 客户端封装封装一个兼容 OpenAI 协议的大模型调用客户端。这样后续替换模型时只需要改配置参数不用改业务代码。# memory/llm_client.py from openai import OpenAI class LLMClient: def __init__( self, base_url: str http://127.0.0.1:11434/v1, api_key: str ollama, model: str qwen2.5:7b, ): self.model model self.client OpenAI(base_urlbase_url, api_keyapi_key) def chat(self, messages: list[dict], temperature: float 0.7) - str: resp self.client.chat.completions.create( modelself.model, messagesmessages, temperaturetemperature, ) return resp.choices[0].message.content如果你的模型服务不需要 api_key随便填一个占位字符串即可。OpenAI SDK 的base_url参数就是服务地址本地和云端用法一致。4.2 第一层实现工作记忆模块工作记忆负责保存当前会话的消息列表。这里用 JSON 文件持久化文件名为{session_id}.json。超过最大消息数时丢弃最旧的消息。# memory/working_memory.py import json import time from pathlib import Path class WorkingMemory: def __init__(self, storage_dir: str data/sessions, max_messages: int 20): self.storage_dir Path(storage_dir) self.storage_dir.mkdir(parentsTrue, exist_okTrue) self.max_messages max_messages def _file_path(self, session_id: str) - Path: return self.storage_dir / f{session_id}.json def get_messages(self, session_id: str) - list[dict]: file_path self._file_path(session_id) if not file_path.exists(): return [] data json.loads(file_path.read_text(encodingutf-8)) return data.get(messages, []) def add_message(self, session_id: str, role: str, content: str, user_id: str ): file_path self._file_path(session_id) if file_path.exists(): data json.loads(file_path.read_text(encodingutf-8)) else: data {session_id: session_id, user_id: user_id, messages: []} data[messages].append({ role: role, content: content, timestamp: time.time(), }) if len(data[messages]) self.max_messages: data[messages] data[messages][-self.max_messages:] file_path.write_text( json.dumps(data, ensure_asciiFalse, indent2), encodingutf-8, )生产环境建议把工作记忆放在 Redis 这类带过期策略的存储里会话关闭后自动释放。演示项目为了部署简单直接用文件存储。4.3 第二层实现情景记忆与向量检索情景记忆是跨会话记忆的核心模块。它负责保存“值得长期记住”的信息并提供向量检索能力。# memory/episodic_memory.py import json import time import uuid from pathlib import Path import numpy as np from sentence_transformers import SentenceTransformer class EpisodicMemory: def __init__( self, storage_dir: str data/episodic, model_name: str BAAI/bge-small-zh-v1.5, ): self.storage_dir Path(storage_dir) self.storage_dir.mkdir(parentsTrue, exist_okTrue) self.index_file self.storage_dir / index.json self.embedder SentenceTransformer(model_name) self.items self._load() def _load(self) - list[dict]: if self.index_file.exists(): return json.loads(self.index_file.read_text(encodingutf-8)) return [] def _save(self): self.index_file.write_text( json.dumps(self.items, ensure_asciiFalse, indent2), encodingutf-8, ) def add_memory( self, user_id: str, content: str, memory_type: str event, ): vector self.embedder.encode(content).tolist() item { id: str(uuid.uuid4()), user_id: user_id, content: content, memory_type: memory_type, vector: vector, created_at: time.time(), } self.items.append(item) self._save() def recall( self, query: str, user_id: str | None None, top_k: int 5, ) - list[dict]: query_vec self.embedder.encode(query) scored [] for item in self.items: if user_id and item[user_id] ! user_id: continue v np.array(item[vector]) cosine float( np.dot(query_vec, v) / (np.linalg.norm(query_vec) * np.linalg.norm(v) 1e-8) ) scored.append((cosine, item)) scored.sort(keylambda x: x[0], reverseTrue) return [item for _, item in scored[:top_k]] def remove_by_user(self, user_id: str): self.items [item for item in self.items if item[user_id] ! user_id] self._save()向量化模型首次运行时会自动从 Hugging Face 下载权重。你需要确保本机网络能正常访问模型仓库或者预先配置好镜像源。4.4 第三层实现核心画像记忆核心记忆按用户 ID 保存稳定的画像信息。数据结构分为facts和preferences两部分分别存储事实性信息和偏好性信息。# memory/semantic_memory.py import json from pathlib import Path class SemanticMemory: def __init__(self, storage_dir: str data/semantic): self.storage_dir Path(storage_dir) self.storage_dir.mkdir(parentsTrue, exist_okTrue) def _file_path(self, user_id: str) - Path: return self.storage_dir / f{user_id}.json def get_profile(self, user_id: str) - dict: file_path self._file_path(user_id) if not file_path.exists(): return {user_id: user_id, preferences: {}, facts: []} return json.loads(file_path.read_text(encodingutf-8)) def update_profile( self, user_id: str, new_facts: list[str] | None None, preferences: dict | None None, ): profile self.get_profile(user_id) if preferences: profile[preferences].update(preferences) if new_facts: existing set(profile[facts]) existing.update(new_facts) profile[facts] list(existing) file_path self._file_path(user_id) file_path.write_text( json.dumps(profile, ensure_asciiFalse, indent2), encodingutf-8, ) def remove_user(self, user_id: str): file_path self._file_path(user_id) if file_path.exists(): file_path.unlink()核心记忆是“每次对话都注入”的所以它的数据质量直接影响 AI 的用户认知。后文会讲如何用大模型自动抽取并更新画像。4.5 记忆抽取与更新策略跨会话记忆不是把对话原封不动存起来而是让 agent 学会提炼。每一轮对话结束后我会调用一次大模型让它判断这段对话中有没有值得长期记忆的信息然后输出结构化 JSON。# memory/agent.py 中的记忆抽取方法 import json import logging import re logger logging.getLogger(__name__) def extract_memories(self, user_id: str, user_message: str, assistant_reply: str): prompt f你是记忆提取器。请从用户消息中提取需要长期记忆的信息。 输出格式必须是 JSON不要输出其他内容 {{ preferences: [用户偏好1, 用户偏好2], facts: [用户事实1, 用户事实2] }} 如果没有值得记忆的信息输出{{preferences: [], facts: []}} 用户消息{user_message} 助手回复{assistant_reply} try: resp self.llm_client.chat( [ {role: system, content: 你是一个严格的记忆提取器只输出 JSON。}, {role: user, content: prompt}, ], temperature0, ) json_str resp.strip() if json_str.startswith(): json_str re.sub(r^.*$, , json_str, flagsre.MULTILINE).strip( \n ) data json.loads(json_str) preferences data.get(preferences, []) facts data.get(facts, []) if preferences: self.semantic_memory.update_profile( user_id, preferences{兴趣偏好: preferences}, ) if facts: self.semantic_memory.update_profile(user_id, new_factsfacts) for item in preferences facts: self.episodic_memory.add_memory( user_id, item, memory_typeextracted, ) except Exception as e: logger.warning(记忆抽取失败: %s, e)这段代码的核心是让大模型自己判断“什么值得记”。比如用户说“我叫小明”大模型会抽出事实用户姓名是小明更新到核心画像中。用户说“我喜欢看科幻小说”会抽出偏好并同时写入情景记忆方便后续相关性检索。这里是演示版生产环境可以每隔几轮做一次批量抽取减少大模型调用次数。4.6 记忆编排把三层串成统一 Agent现在把三层记忆组合起来实现完整的对话编排流程。流程顺序是读取核心画像检索情景记忆获取工作记忆历史组装系统提示词调用大模型最后保存消息并抽取新记忆。# memory/agent.py from .episodic_memory import EpisodicMemory from .llm_client import LLMClient from .semantic_memory import SemanticMemory from .working_memory import WorkingMemory class MemoryAgent: def __init__( self, llm_client: LLMClient, working_memory: WorkingMemory, episodic_memory: EpisodicMemory, semantic_memory: SemanticMemory, ): self.llm_client llm_client self.working_memory working_memory self.episodic_memory episodic_memory self.semantic_memory semantic_memory def _build_system_prompt( self, profile: dict, related_memories: list[dict], ) - str: prompt_parts [你是一个有长期记忆的 AI 助手请用自然、简洁的方式回答用户。] if profile.get(facts): prompt_parts.append(关于用户已经确认的事实) for fact in profile[facts]: prompt_parts.append(f- {fact}) if profile.get(preferences): prompt_parts.append(用户偏好) for key, values in profile[preferences].items(): if isinstance(values, list): prompt_parts.append(f- {key}: {, .join(values)}) else: prompt_parts.append(f- {key}: {values}) if related_memories: prompt_parts.append(以下是与你当前问题相关的历史记忆) for mem in related_memories: prompt_parts.append(f- [{mem[memory_type]}] {mem[content]}) prompt_parts.append(回答时不要说明你使用了记忆系统。) return \n.join(prompt_parts) def chat(self, user_id: str, session_id: str, user_message: str) - dict: profile self.semantic_memory.get_profile(user_id) related self.episodic_memory.recall( user_message, user_iduser_id, top_k5, ) history self.working_memory.get_messages(session_id) system_prompt self._build_system_prompt(profile, related) messages [ {role: system, content: system_prompt}, *history, {role: user, content: user_message}, ] reply self.llm_client.chat(messages) self.working_memory.add_message(session_id, user, user_message, user_id) self.working_memory.add_message(session_id, assistant, reply, user_id) self.extract_memories(user_id, user_message, reply) return { reply: reply, memories_used: [mem[content] for mem in related], }注意这里有一个细节history是当前会话历史消息user_message是当前这条新消息。组装 messages 时先放系统提示词再放历史消息最后放当前用户消息顺序不能颠倒。4.7 FastAPI 接口服务最后用 FastAPI 把整个 Agent 暴露成 HTTP 接口。# main.py from fastapi import FastAPI from pydantic import BaseModel from memory.agent import MemoryAgent from memory.episodic_memory import EpisodicMemory from memory.llm_client import LLMClient from memory.semantic_memory import SemanticMemory from memory.working_memory import WorkingMemory app FastAPI(titleAI Memory System) class ChatRequest(BaseModel): user_id: str session_id: str message: str llm_client LLMClient( base_urlhttp://127.0.0.1:11434/v1, api_keyollama, modelqwen2.5:7b, ) working_memory WorkingMemory(storage_dirdata/sessions, max_messages20) episodic_memory EpisodicMemory(storage_dirdata/episodic) semantic_memory SemanticMemory(storage_dirdata/semantic) agent MemoryAgent( llm_clientllm_client, working_memoryworking_memory, episodic_memoryepisodic_memory, semantic_memorysemantic_memory, ) app.post(/chat) def chat(req: ChatRequest): result agent.chat(req.user_id, req.session_id, req.message) return { session_id: req.session_id, reply: result[reply], memories_used: result[memories_used], profile: semantic_memory.get_profile(req.user_id), } app.get(/memory/{user_id}) def get_memory(user_id: str): profile semantic_memory.get_profile(user_id) memories [ item for item in episodic_memory.items if item[user_id] user_id ] safe_memories [ { content: item[content], memory_type: item[memory_type], created_at: item[created_at], } for item in memories ] return {profile: profile, memories: safe_memories}启动服务uvicorn main:app --host 127.0.0.1 --port 8000启动后控制台会输出 FastAPI 访问地址。第一次运行EpisodicMemory会加载向量模型如果模型还没下载到本地会先显示下载进度耐心等待即可。5. 跨会话记忆功能测试服务启动后用 Python 脚本模拟多个会话验证跨会话记忆是否真正生效。下面给出完整的测试流程。5.1 测试场景一跨会话记住用户基本信息先新建一个会话告诉 AI 自己的名字然后新建另一个会话询问 AI 是否记得自己。import requests API http://127.0.0.1:8000 # 第一个会话告诉 AI 基本信息 r1 requests.post( f{API}/chat, json{ user_id: u_001, session_id: session_1, message: 我叫小明是一名后端工程师最近在准备考研。, }, ) print(会话1, r1.json()[reply]) # 第二个会话新 session跨会话提问 r2 requests.post( f{API}/chat, json{ user_id: u_001, session_id: session_2, message: 你还记得我是谁吗, }, ) print(会话2, r2.json()[reply])预期现象第二次会话中模型能说出“你是小明一名后端工程师”。判断标准是模型回复中出现之前提到的姓名和职业信息。如果模型没有记住先调用/memory/u_001接口查看核心画像数据是否已经写入。5.2 测试场景二跨会话更新用户偏好继续模拟用户偏好变化。用户先表示喜欢科幻小说之后表示开始看推理小说最后请求推荐。# 会话 A表达兴趣 requests.post( f{API}/chat, json{ user_id: u_001, session_id: session_3, message: 我最近特别喜欢看科幻小说尤其是刘慈欣的作品。, }, ) # 会话 B表达新兴趣 requests.post( f{API}/chat, json{ user_id: u_001, session_id: session_4, message: 不过我最近也开始看推理小说了东野圭吾的看得比较多。, }, ) # 会话 C跨会话推荐 r3 requests.post( f{API}/chat, json{ user_id: u_001, session_id: session_5, message: 结合我的兴趣推荐几本我可能会喜欢的书。, }, ) print(会话5, r3.json()[reply]) print(使用到的记忆, r3.json()[memories_used])这里要注意推荐结果好不好除了看记忆系统还看底层大模型的理解能力。如果模型推荐结果里同时涵盖科幻和推理题材说明记忆抽取、存储、检索链条是通的。5.3 测试场景三长期记忆检索相关性直接调用情景记忆的recall方法验证向量检索是否返回正确内容。top_memories episodic_memory.recall( 用户喜欢什么类型的小说, user_idu_001, top_k3, ) for mem in top_memories: print(f[{mem[memory_type]}] {mem[content]})预期输出应包含“科幻小说”“刘慈欣”“推理小说”“东野圭吾”等相关记忆。如果检索结果不相关可以调整top_k或更换向量模型。5.4 测试场景四长对话上下文压缩模拟连续多轮对话验证只保留最近消息时对话不会中断。for i in range(30): response requests.post( f{API}/chat, json{ user_id: u_001, session_id: long_session, message: f这是第 {i 1} 轮测试消息, }, ) print(f第 {i 1} 轮完成)如果max_messages设置为 20那么存储文件里最多只有最近的 20 条消息。这样能保证请求体不会无限膨胀。这个测试主要验证系统在高频对话下是否稳定以及工作记忆淘汰机制是否生效。6. 接口 API 与扩展接入当前服务提供两个核心接口POST /chat和GET /memory/{user_id}。POST /chat请求体{ user_id: u_001, session_id: session_1, message: 你好 }响应体{ session_id: session_1, reply: 你好有什么可以帮你的, memories_used: [], profile: { user_id: u_001, preferences: {}, facts: [] } }GET /memory/{user_id}用于调试返回该用户当前的核心画像和情景记忆列表。开发阶段非常有用。curl 调用示例curl -X POST http://127.0.0.1:8000/chat \ -H Content-Type: application/json \ -d { user_id: u_001, session_id: curl_test, message: 我叫小明 }批量任务场景下你可以把一批消息按user_id和session_id分好循环调用/chat接口。注意目前的文件存储没有加锁高并发写入同一个用户时可能出现数据竞争生产环境需要把工作记忆和核心画像迁移到 Redis 或 PostgreSQL。7. 资源占用与性能观察性能观察主要看三个部分向量化耗时、大模型推理耗时、记忆文件读写耗时。SentenceTransformer默认在 CPU 上运行bge-small属于轻量级中文向量模型单条短文本向量化一般在几十毫秒到几百毫秒之间。如果你的服务器有多核 CPU可以设置torch.set_num_threads来优化。实际耗时以本机测试为准我这边不给出固定数值。大模型推理耗时取决于你使用的模型服务。本地 7B 量化模型和云端大模型差距明显但记忆系统本身引入的额外 token 并不多主要就是系统提示词里的画像和检索结果。控制检索条数top_k是控制 token 消耗最直接的方式。显存和内存占用方面本文的项目本体几乎没有显存需求向量模型内存占用很小。但如果你在本地跑 7B 或更大参数的模型显存占用会很高具体数值取决于量化等级、上下文长度和批处理大小。建议先用小模型验证记忆逻辑确认没有问题后再换大模型。日志规范建议每个/chat请求都打印请求耗时、memories_used数量和profile长度。这样能快速判断是记忆检索拖慢请求还是大模型推理拖慢请求。8. 常见问题与排查清单问题现象可能原因排查方式解决方案新会话仍然记不住用户核心画像未写入或未注入调用/memory/{user_id}查看画像检查记忆抽取流程是否报错看系统提示词是否包含画像检索到的记忆与问题无关embedding 模型语义能力不够top_k过大打印memories_used列表降低top_k或更换更强的向量模型请求超时大模型推理慢或向量化耗时过长查看接口日志中的耗时分布切换更快的模型服务减少历史消息数工作记忆被截断max_messages设置过小检查 session 文件中的消息数量适当调大max_messages或开启摘要压缩记忆抽取结果为空大模型未能按 JSON 格式输出查看日志中的抽取报错优化 extraction prompt增加 few-shot 示例并发写入文件出错文件存储没有加锁观察日志是否有写入冲突迁移到 Redis或为文件写入加线程锁向量模型下载失败网络无法访问模型仓库检查模型下载日志手动下载模型权重到本地缓存目录用户删除记忆后仍恢复旧记忆工作记忆、情景记忆、核心记忆删除不彻底检查三个 data 目录删除用户时清理三层存储并在生产环境增加 DELETE 接口排查这类记忆系统问题核心原则是先确认数据到底存没存进去再确认存进去的数据有没有被注入到 prompt最后才能怀疑模型能力。9. 最佳实践与合规使用建议第一每次对话都调用记忆抽取会增加大量大模型请求生产环境建议做一个节流策略例如每 5 轮做一次抽取或者在用户主动表达关键信息时才触发。第二记忆数据的去重和合并很重要。用户在不同时间说出相似偏好如果不做去重画像会越来越冗余。可以定期让大模型对画像做一次合并压缩。第三向量检索在数据量小时用 NumPy 扫描没问题但记忆量超过上万条后应切换到 FAISS 或向量数据库。隐私和数据合规方面必须重点强调记忆系统存储的是用户对话内容和个人信息上线前必须获得用户的明示授权并且提供用户自助查看和删除记忆的入口。涉及姓名、联系方式、身份证号、人脸、声音等敏感信息时建议在记忆抽取阶段就直接过滤或脱敏不要进入长期存储。测试阶段应使用虚构数据不要拿真实用户信息做实验。如果你的助手涉及换脸、声音克隆、数字人等能力还需要额外确认肖像权和声音授权避免侵权风险。接口服务默认绑定在127.0.0.1生产环境如果要暴露到局域网或公网必须加鉴权、限流和 HTTPS否则用户记忆数据会直接泄露。10. 结束后可以继续做什么这套三层记忆系统虽然实现简单但架构上是完整的。你可以继续扩展几个方向一是在系统提示词中加入记忆写入和更新的过程反思让模型自己判断哪些记忆已经失效需要替换二是增加时间衰减因子让太久远且不再被检索的记忆自动弱化三是做一个记忆可视化页面让用户直接看到 AI 记住了自己哪些信息。如果你打算接入微信、飞书或 Web 小程序只需要在回调逻辑里调用这个/chat接口把用户的 open_id 映射为user_id把单聊会话关联到固定的session_id跨会话记忆就能直接复用。建议先收藏本文的实现框架在本地跑通一次完整测试后再接入具体业务场景。

相关新闻

Ansys Maxwell参数设置详解:电流激励有效值/幅值与边界条件

Ansys Maxwell参数设置详解:电流激励有效值/幅值与边界条件

2026/9/8 3:52:40

如果你是因为“Maxwell参数”这个词点进来的,那大概率遇到过这种场面:Ansys Maxwell的GUI打开了,模型也画好了,结果在设置激励、材料、边界条件时,总有几个参数不知道填什么,或者填完以后算出来的结果完全对…

TMS320F28035 eCAN主从通信调试:回环模式为何不能替代总线互测

TMS320F28035 eCAN主从通信调试:回环模式为何不能替代总线互测

2026/9/8 3:52:40

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

期末博客作业写作指南:从选题到发布全流程

期末博客作业写作指南:从选题到发布全流程

2026/9/8 3:52:40

每到期末,社交媒体和课程群里就会被同一个问题刷屏:“期末博客作业到底怎么写?”“老师让我们交一篇博客,可我不知道写什么,也不知道写成什么样才能拿高分。” 这篇博文就是来帮你解决这件事的。我会从选题、结构、写…

Class 47柴油机车在保存铁路上的动态运行记录

Class 47柴油机车在保存铁路上的动态运行记录

2026/9/8 4:42:46

在 Churnet Valley Railway(简称 CVR)的某一个运营日,一列由 Class 47 柴油机车牵引的列车从 Froghall 方向驶出,穿过 Kingsley 附近的开阔地带,继续朝 Ipstones 方向前进。机车涂装上的 D1994 编号清晰可见&#xff0…

VMwareTools-8.8.0-471268.tar.gz在Ubuntu上的安装与排错指南

VMwareTools-8.8.0-471268.tar.gz在Ubuntu上的安装与排错指南

2026/9/8 4:42:46

简介:这是 VMware Tools 8.8.0-471268 的 Linux 安装包,面向虚拟化运维人员和需要手动装驱动的虚拟机用户,可解决虚拟机图形卡顿、磁盘与网络性能偏低、时间漂移及共享目录不便等问题。压缩包总计 2477 个文件,大小约 56.61MB&…

用Qwen3.8-Max搭建商品资料包体检助手:多文档交叉校验实战

用Qwen3.8-Max搭建商品资料包体检助手:多文档交叉校验实战

2026/9/8 4:42:46

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

Cocos2d-x iOS打包上架全流程:证书与描述文件配置避坑指南

Cocos2d-x iOS打包上架全流程:证书与描述文件配置避坑指南

2026/9/8 4:42:46

从 Cocos2d-x 一路把游戏搓到上架,最折磨人的往往不是 C 崩溃,也不是内存泄漏,而是你明明写好了游戏,却在 Xcode 里卡在证书和描述文件那一步,看着“No profiles for xxx were found”这种报错一脸懵。这篇文章我就把整…

Cocos Creator 2.0.10纸牌游戏合集技术拆解:从数据结构到交互优化

Cocos Creator 2.0.10纸牌游戏合集技术拆解:从数据结构到交互优化

2026/9/8 4:42:45

简介:一套基于Cocos Creator 2.0.10打造的纸牌游戏合集,面向希望系统学习休闲游戏开发的初中级开发者,也适合作为课程作业或小型项目参考原型。合集包含接龙、战争、多人纸牌等典型玩法示例,从牌桌、手牌、洗牌等界面元素&#xf…

opencode实战指南:AI编码代理从安装配置到高效工作流

opencode实战指南:AI编码代理从安装配置到高效工作流

2026/9/8 4:32:42

1. 开篇:为什么我弃用了一堆AI编码工具,最后留在opencode先说个发生在我自己身上的事。过去一年多,我几乎把所有主流的AI编码助手试了个遍:先用GitHub Copilot补全代码,后来觉得聊天式补全不够爽,转向Claud…

中国人民大学杨琳团队《Nature Communications》 | 全球潮汐湿地土壤有机碳时空格局与环境驱动:一项2009-2020年的全球评估

中国人民大学杨琳团队《Nature Communications》 | 全球潮汐湿地土壤有机碳时空格局与环境驱动:一项2009-2020年的全球评估

2026/9/7 20:21:46

本文首发于“生态学者”!从“湿地面积”到“土壤碳密度”:为什么需要重新认识潮汐湿地蓝碳变化?潮汐湿地位于陆地与海洋的交汇地带,包括红树林、盐沼和潮滩,是全球重要的蓝碳生态系统。其土壤能够长期储存大量有机碳&a…

adb抓包

adb抓包

2026/9/7 3:44:24

前言 本文介绍如何通过 tcpdump 在 Android 手机上抓取网络数据包,并在电脑端使用 Wireshark 进行分析。适用于需要排查 App 网络请求、分析接口调用或调试网络问题的开发与测试场景。1. 手机要有 root 权限2. 下载 tcpdump3. adb push C:\Users\zhangkuixun\Downlo…

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战

2026/9/7 8:03:37

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战 在云原生基础设施中,容器镜像体积直接决定了服务的部署速度与弹性扩容敏捷度。对于传统的 Go / Java 微服务,镜像体积通常被严格控制在 50MB 到 200MB 以内,拉取镜像只…

芯片良率波动可视化:动画拆解工艺因果,重建客户信任

芯片良率波动可视化:动画拆解工艺因果,重建客户信任

2026/9/8 0:02:30

芯片这个行业有个不太被人摆到台面上、但几乎每天都在发生的场景:客户拿着一条良率曲线截图问你,这批货的良率怎么掉了三个点,是不是工艺出问题了,产生的不良会不会流到他们产线上去。你解释了半天,客户似懂非懂&#…

PyTorch DataLoader参数冲突:sampler与shuffle互斥的根源与正确写法

PyTorch DataLoader参数冲突:sampler与shuffle互斥的根源与正确写法

2026/9/8 0:02:30

ValueError: sampler option is mutually exclusive with shuffle,这个报错我在 PyTorch 的 DataLoader 上至少见过几十次了,而且很有意思的是,它经常不是新手专属——很多写了好几年模型的老手,在从单机改成自定义采样器&#xf…

中国车企再破谣言,GAC吉利零跑获欧盟安全五星

中国车企再破谣言,GAC吉利零跑获欧盟安全五星

2026/9/8 0:02:30

有人可能在网上开着皮卡拍视频,声称中国电动车不仅性能不如美国大排量车型,安全性也堪忧。然而事实恰恰相反,GAC、吉利和零跑最新推出的电动车型在极为严苛的欧盟新车安全评鉴(Euro NCAP)测试中全部斩获满分。就在特斯…

远程协作的工作台整理

远程协作的工作台整理

2026/9/8 4:23:39

远程协作的工作台整理远程协作的核心不是再加一个工具,而是让交接信息足够完整。异步任务要写明目标、输入位置、完成标准和需要决策的人。 工作台的最小配置 将日程、待办、代码和沟通入口收拢到少数固定位置;通知按紧急程度分层。工作台不需要模仿办公…

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

2026/9/8 3:19:39

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

2026/9/8 4:00:23

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…