免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

用SQL管理智能体会话记忆:从表结构到生产落地

用SQL管理智能体会话记忆:从表结构到生产落地 在实际的 Agent 项目中最容易被低估的问题不是模型能力而是会话记忆。模型本身不保留上一轮之外的信息聊天界面关闭后对话记录通常就留在日志里了。一个团队跑了几十个 Agent 会话讨论过方案、定过接口、否决过选项但下一个新会话开始时新 Agent 只能重新开始。用 SQL 管理所有智能体会话是比较直接的解决方案建立标准表结构把每次讨论、每个决策和关键上下文持久化下来让新 Agent 通过搜索旧 Agent 的讨论和决策恢复上下文。这篇文章会围绕一张会话表、一张消息表、一张决策表的最小设计讲清楚 Schema 怎么建、数据怎么写进去、新会话怎么把历史讨论检索出来以及落到生产环境时还有哪些坑。这里的 “ctx” 可以理解成两层含义一是需要保存的上下文 Context二是 Agent 开发中常见的运行时上下文对象ctx。后面代码里会直接用ctx变量保存当前会话 ID并在工具函数里读写 SQLite。先说明一下这套设计不是某个框架的专用方案而是可以复用到 LangChain、AutoGen、自己写的 Agent 循环等场景的通用存储层。1. 智能体会话为什么需要数据库而不是临时文件或内存1.1 会话丢失与上下文断裂的真正原因很多 Agent 项目在本地调试时一切正常一旦部署到服务器用户刷新页面或隔一段时间再回来就会遇到“会话丢失”。最常见的原因是程序把对话内容放在内存对象或临时变量里进程重启后这些数据就没了。另一个常见原因是前端和服务端各自维护一份会话前端展示的是浏览器内存里的记录服务端只保存了当前这一轮的输入输出没有形成完整的持久化链路。从本质上看大模型本身是无状态的。所谓多轮对话是程序把历史消息重新拼进 Prompt 再发给模型。一旦这个“拼接历史”的数据源丢失模型就无法感知过去讨论过什么。对于一个执行复杂任务的 Agent 来说这会引发一系列实际问题上一轮已经确认的技术选型新会话又重新讨论一遍。之前否决过的方案被再次提出浪费时间。同一个问题问多个 Agent得到相互矛盾的结论。因为拿不到旧决策上下文新 Agent 给出和团队既定方向不一致的答案。用 SQL 管理会话意图就是把“历史”变成一个可查询、可过滤、可统计的数据资产。会话记录不再只是聊天记录而是团队或系统内的共识库。1.2 SQL 相比 JSON 文件、内存和向量库的取舍先看几个常见的替代方案方案优势局限适合场景内存变量读写快实现简单进程重启丢失无法跨实例本地演示、单轮调试JSON 文件可持久化人类可读并发写入容易损坏查询能力弱个人脚本、低频单用户向量数据库支持语义检索适合模糊 Recall部署复杂结构化过滤弱成本高需要“按意思找”的大规模记忆SQL 数据库持久化稳定支持事务、索引、聚合、权限需要设计表结构语义检索能力不如向量库结构化会话管理、决策查询、团队协作SQL 的核心优势是“可查询”。旧会话里有没有讨论过“超时时间”哪次决策被否决过某个 Agent 过去一周解决了多少任务这些用 SQL 一条语句就能统计出来。而向量库擅长语义相近的内容召回却不擅长精确的条件过滤和事务控制。实际项目里SQL 和向量库不是非此即彼。可以先在 SQL 里保存完整会话原文和决策记录再在消息表上增加向量列由搜索层先做 SQL 过滤再做向量相似度排序。这样既能保证基础查询准确又具备语义召回能力。注意不要一上来就引入向量库。大多数团队的 Agent 会话量在百万条以内SQL 的全文检索和结构化查询足够应付。先把 SQL 方案跑通再按瓶颈扩展。2. 会话库的表结构设计一张会话表不够2.1 核心表Agents、Sessions、Messages、Decisions只建一张“会话表”其实是常见误区。如果把每条消息、每个决策都塞进同一张大表字段会非常混乱查询时也没法区分“这是用户输入”“这是模型回复”“这是已确认决策”。合理的做法是把实体分开。下面是一套兼容 SQLite 和 PostgreSQL 的建表 SQL核心思路是agents保存 Agent 的身份信息便于多 Agent 隔离。sessions保存一次会话的元数据包括任务目标、状态、父会话。messages保存对话中的每一条消息。decisions保存从对话中提炼出的决策。CREATE TABLE IF NOT EXISTS agents ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, description TEXT, created_at TEXT NOT NULL DEFAULT (datetime(now)) ); CREATE TABLE IF NOT EXISTS sessions ( id INTEGER PRIMARY KEY AUTOINCREMENT, agent_id INTEGER NOT NULL REFERENCES agents(id), title TEXT, parent_session_id INTEGER REFERENCES sessions(id), task_goal TEXT, status TEXT NOT NULL DEFAULT active, created_at TEXT NOT NULL DEFAULT (datetime(now)), updated_at TEXT NOT NULL DEFAULT (datetime(now)) ); CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id INTEGER NOT NULL REFERENCES sessions(id), role TEXT NOT NULL CHECK (role IN (user, assistant, tool, system)), content TEXT NOT NULL, metadata TEXT, created_at TEXT NOT NULL DEFAULT (datetime(now)) ); CREATE TABLE IF NOT EXISTS decisions ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id INTEGER NOT NULL REFERENCES sessions(id), message_id INTEGER REFERENCES messages(id), decision_type TEXT NOT NULL, summary TEXT NOT NULL, status TEXT NOT NULL DEFAULT accepted, rationale TEXT, decision_metadata TEXT, created_at TEXT NOT NULL DEFAULT (datetime(now)) ); CREATE INDEX idx_messages_session_created ON messages(session_id, created_at); CREATE INDEX idx_sessions_agent_created ON sessions(agent_id, created_at); CREATE INDEX idx_sessions_parent ON sessions(parent_session_id); CREATE INDEX idx_decisions_session_status ON decisions(session_id, status); CREATE INDEX idx_decisions_type ON decisions(decision_type);关键字段解释sessions.parent_session_id表示“本次会话是哪个旧会话派生出来的”。新 Agent 在旧会话基础上继续工作时写下这个关联后续可以追溯讨论链路。messages.role用CHECK限制为四种值避免脏数据。messages.metadata存 JSON 字符串用来保存 token 数、模型名、工具调用 ID 等非结构化信息。decisions.message_id将决策关联到产生它的那一条消息方便回查原始讨论。decisions.status用accepted、rejected、superseded区分决策状态新会话搜索时可以直接排除已淘汰选项。2.2 会话元数据与上下文快照除了消息正文会话还需要保存一些“任务级”信息。比如本次会话的目标是什么。评审过哪些候选方案。最终确认的约束条件是什么。用到了哪些工具和文件。这些内容不适合塞进messages因为消息可能很多每次搜索都要扫全表。更合理的做法是用sessions.task_goal保存目标再扩展一张session_context表保存标签和快照。CREATE TABLE IF NOT EXISTS session_context ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id INTEGER NOT NULL REFERENCES sessions(id), context_key TEXT NOT NULL, context_value TEXT, created_at TEXT NOT NULL DEFAULT (datetime(now)) ); CREATE INDEX idx_session_context_session_key ON session_context(session_id, context_key);使用方式示例context_keyselected_stack,context_valuePython 3.12 FastAPI PostgreSQLcontext_keyconstraint,context_value接口响应时间必须低于 500mscontext_keyrelated_doc,context_valuedocs/architecture.md这样新会话搜索时既能搜messages.content这种正文内容又能通过session_context精确定位“某个会话已经确定过什么关键决策”。2.3 为什么必须单独建一张决策表决策是从讨论中提炼出来的不等同于某条普通消息。它的价值在于“可以按状态查询、按类型统计、按时间追踪”。举个例子旧会话里出现过“用 Redis 做缓存”的提议后来因为运维成本高被否决了。如果把这段对话只存在messages表里新会话搜索时可能搜到这段讨论但不容易判断它最终是否被采纳。单独建decisions表后可以直接用一条查询把所有statusrejected的结论找出来新 Agent 看到后就能避免重复提出已经被否决的方案。决策表也承担了“审计”功能。当团队需要回溯“为什么当初选了方案 A 而不是方案 B”时rationale字段保存原因message_id指向原始讨论session_id标记时间场景整条链路是完整的。3. 用一个最小 Agent 框架把会话写入 SQL3.1 选定存储封装SQLite 起步PostgreSQL 上线本地开发建议先用 SQLite。它不需要安装服务单文件保存适合快速验证表结构和查询语句。生产环境如果有多人、多实例同时写入建议迁移到 PostgreSQL它支持更好的并发控制、JSONB 和全文检索。Python 环境只需要内置模块import sqlite3 import json from datetime import datetime DB_PATH agent_ctx.db def get_conn(): conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row conn.execute(PRAGMA foreign_keys ON) return conn def init_db(): with get_conn() as conn: conn.executescript(DDL_SQL)PRAGMA foreign_keys ON要执行否则外键约束不会生效。这在 SQLite 里是最容易踩的坑。3.2 持久化消息和决策的核心函数写操作要封装成独立函数不要让业务代码直接拼 SQL。下面的函数负责创建会话、保存消息、保存决策def create_session(agent_name, title, task_goal, parent_session_idNone): with get_conn() as conn: conn.execute( INSERT OR IGNORE INTO agents(name, description) VALUES (?, ?), (agent_name, ), ) agent conn.execute( SELECT id FROM agents WHERE name ?, (agent_name,) ).fetchone() now datetime.utcnow().isoformat() cur conn.execute( INSERT INTO sessions(agent_id, title, task_goal, parent_session_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?) , (agent[id], title, task_goal, parent_session_id, now, now), ) return cur.lastrowid def save_message(session_id, role, content, metadataNone): with get_conn() as conn: now datetime.utcnow().isoformat() cur conn.execute( INSERT INTO messages(session_id, role, content, metadata, created_at) VALUES (?, ?, ?, ?, ?) , (session_id, role, content, json.dumps(metadata, ensure_asciiFalse), now), ) return cur.lastrowid def save_decision(session_id, message_id, decision_type, summary, statusaccepted, rationaleNone, metadataNone): with get_conn() as conn: now datetime.utcnow().isoformat() cur conn.execute( INSERT INTO decisions(session_id, message_id, decision_type, summary, status, rationale, decision_metadata, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?) , (session_id, message_id, decision_type, summary, status, rationale, json.dumps(metadata, ensure_asciiFalse), now), ) return cur.lastrowid这里使用了with get_conn() as conn可以保证事务提交和异常回滚。metadata字段用 JSON 字符串保存方便以后在 PostgreSQL 里改成 JSONB。3.3 通过 ctx 对象在工具调用中读写会话很多 Agent 框架会在工具函数中传入一个上下文对象习惯命名为ctx。围绕这个对象可以封装出更符合业务语义的方法class AgentContext: def __init__(self, session_id): self.session_id session_id def log_user_message(self, content, metadataNone): return save_message(self.session_id, user, content, metadata) def log_assistant_message(self, content, metadataNone): return save_message(self.session_id, assistant, content, metadata) def add_decision(self, decision_type, summary, statusaccepted, rationaleNone, metadataNone): msg_id save_message( self.session_id, assistant, f决策记录{summary}, {kind: decision_summary}, ) return save_decision( self.session_id, msg_id, decision_type, summary, status, rationale, metadata, ) def remember(self, question, limit5): results search_agent_memory(self.session_id, question, limitlimit) return build_context_snippets(results)在 Agent 主循环里只需要维护一个ctx对象ctx AgentContext(session_id) ctx.log_user_message(帮我评估订单模块是否要引入消息队列) # Agent 执行过程中调用工具、产出结论后 ctx.log_assistant_message(结论在日订单量低于 10 万的阶段不需要引入消息队列) ctx.add_decision( decision_typearchitecture, summary订单模块暂不引入消息队列, statusaccepted, rationale当前峰值 QPS 较低消息队列会增加运维复杂度, )这样每次 Agent 运行都会把关键信息写入 SQL。后续新会话只要拿到session_id就能以这个会话为入口查询整条历史链。注意消息写入应该放在 Agent 主循环中而不是在模型流式输出结束后才一次性写入。因为工具调用过程中也可能产生重要中间结果这些中间结果往往比最终回复更有检索价值。4. 新 Agent 如何搜索旧 Agent 的讨论和决策4.1 明确“搜索”的输入和输出新 Agent 要搜索旧会话不是直接执行一个简单 SQL 就结束。先把搜索流程拆成四个环节输入用户问题、当前 Agent 名称、时间范围、决策状态。召回从messages、decisions、session_context中查出候选记录。过滤去掉与当前任务无关的会话去掉已过时或被否决的决策。注入把结果压缩成适合放进 Prompt 的文本片段。输出应该是“一段可被新 Agent 引用的历史摘要”而不是把数据库里几万条消息全返回。4.2 基于 SQL 的关键词搜索最朴素也最容易理解的搜索方式是用LIKEdef search_agent_memory(keyword, agent_nameNone, limit10): params [f%{keyword}%] sql SELECT s.id AS session_id, s.title, m.role, m.content, m.created_at FROM messages m JOIN sessions s ON s.id m.session_id JOIN agents a ON a.id s.agent_id WHERE m.content LIKE ? if agent_name: sql AND a.name ? params.append(agent_name) sql ORDER BY m.created_at DESC LIMIT ? params.append(limit) with get_conn() as conn: rows conn.execute(sql, params).fetchall() return [dict(row) for row in rows]LIKE的优点是简单缺点是中文分词能力弱搜“订单超时”和“超时时间”是两条独立记录。模糊查询无法使用普通 B-tree 索引数据量大时性能会快速下降。用户输入包含%、_等符号时会被当作通配符需要转义。如果要走全文检索PostgreSQL 可以用tsvector和GIN索引ALTER TABLE messages ADD COLUMN content_tsv tsvector GENERATED ALWAYS AS (to_tsvector(simple, content)) STORED; CREATE INDEX messages_content_tsv_idx ON messages USING GIN(content_tsv); SELECT session_id, role, content, created_at FROM messages WHERE content_tsv websearch_to_tsquery(订单 AND NOT 消息队列) ORDER BY created_at DESC;SQLite 也可以用 FTS5 虚拟表但建表语法和触发器会多一点。实际项目里可以先从LIKE做起等搜索延迟超过可接受范围再切换全文检索。4.3 决策记录查询和过滤决策搜索比消息搜索更关键。新 Agent 需要知道的是旧讨论到底有没有得出最终结论。常见的查询场景需求SQL 思路查某个主题最终被采纳的方案按decision_type和statusaccepted过滤查历史上被否决的方案按statusrejected过滤查最近一周的架构决策按created_at ?过滤查某次会话里做出的所有技术决策按session_id ?过滤def search_decisions(keyword, statusNone, agent_nameNone, limit10): params [f%{keyword}%, f%{keyword}%] sql SELECT d.id, d.decision_type, d.summary, d.status, d.rationale, s.title AS session_title, a.name AS agent_name, d.created_at FROM decisions d JOIN sessions s ON s.id d.session_id JOIN agents a ON a.id s.agent_id WHERE (d.summary LIKE ? OR d.rationale LIKE ?) if status: sql AND d.status ? params.append(status) if agent_name: sql AND a.name ? params.append(agent_name) sql ORDER BY d.created_at DESC LIMIT ? params.append(limit) with get_conn() as conn: rows conn.execute(sql, params).fetchall() return [dict(row) for row in rows]这个函数的价值在于新会话可以直接问“这个方案以前被否过吗”得到的回答不是一段模棱两可的对话而是明确的决策状态。4.4 把检索结果格式化成上下文并注入提示词搜出来的结果不能原样塞进 Prompt否则会浪费大量 token还容易把模型引导到无关话题。推荐压缩成这样的格式def build_context_snippets(rows, max_chars_per_row200): lines [] for row in rows: content row[content] if len(content) max_chars_per_row: content content[:max_chars_per_row] ... lines.append( f[{row[created_at]}] f会话:{row.get(session_title, )} f角色:{row.get(role, )}\n{content} ) return \n\n.join(lines)Prompt 模板可以这样写你是一个新的 Agent下面是检索到的历史会话片段。 请先判断这些历史内容是否适用于当前任务。如果适用直接引用结论 如果不适用说明为什么丢弃。禁止编造历史记录。 历史讨论 {snippets} 历史决策 {decision_snippets} 当前任务 {task}搜索时还要加一个“时间衰减”策略。旧结论可能已经失效不能盲目沿用。可以在格式化结果时附带决策状态和日期让模型自己判断历史决策 [2025-02-10] [已否决]订单模块暂不引入消息队列。 否决原因当前峰值 QPS 较低。这样新 Agent 搜索旧 Agent 的讨论和决策后既能获得经验又不会把过期信息当成铁律。5. 本地运行和验证效果5.1 写入测试数据为了验证整套流程先手工插入几条数据。这里用 SQL 直接模拟一个“旧会话”INSERT INTO agents(name, description) VALUES (planning-agent, 负责架构规划); INSERT INTO sessions(agent_id, title, task_goal, created_at, updated_at) VALUES (1, 订单模块架构评估, 判断是否引入消息队列, datetime(now), datetime(now)); INSERT INTO messages(session_id, role, content, created_at) VALUES (1, user, 订单模块峰值 QPS 只有 200要不要引入消息队列, datetime(now)); INSERT INTO messages(session_id, role, content, created_at) VALUES (1, assistant, 建议暂时不引入消息队列优先优化数据库索引和接口缓存。, datetime(now)); INSERT INTO decisions(session_id, message_id, decision_type, summary, status, rationale, created_at) VALUES (1, 2, architecture, 订单模块暂不引入消息队列, accepted, 峰值 QPS 较低引入消息队列会增加运维复杂度, datetime(now));这里用datetime(now)让时间符合 SQLite 语法。如果迁移到 PostgreSQL要改成NOW()或应用层传入 ISO 时间。5.2 模拟新 Agent 搜索旧会话写一个 Python 脚本模拟新会话ctx AgentContext(session_id1) question 我们需要处理订单高峰吗查一下以前有没有讨论过消息队列 messages search_agent_memory(question, agent_nameplanning-agent, limit5) decisions search_decisions(消息队列, statusaccepted, limit5) print(命中消息, len(messages)) print(命中决策, len(decisions)) for d in decisions: print([决策], d[status], d[summary], |, d[rationale])预期输出应该包含命中消息 2 命中决策 1 [决策] accepted 订单模块暂不引入消息队列 | 峰值 QPS 较低引入消息队列会增加运维复杂度验证标准不是“程序不报错”而是三个问题新 Agent 是否能通过搜索命中和当前问题相关的旧讨论。旧会话中已经确认的“决策”是否以明确状态出现在结果里。提示词注入后模型是否能把旧结论和当前任务关联起来。5.3 验证搜索准确率的简单方法每次搜索后把命中结果人工看一遍统计有多少条是真正有帮助的。这个过程中可以记录三个指标相关率返回结果中真正和任务相关的比例。决策命中率需要引用的历史决策是否能被搜到。召回稳定性同样的关键词在多次执行时是否得到一致结果避免因为排序不稳定导致模型回答漂移。6. 从学习环境到生产环境还需要解决的问题6.1 并发与会话一致性问题本地脚本只有一个进程写 SQLite问题不明显。生产环境会有多个 Agent 实例同时写同一个会话SQLite 的写锁会成为瓶颈。需要做的改造数据库切换到 PostgreSQL。会话更新时增加updated_at并在应用层判断是否更新了过旧的数据。如果多个 Agent 会并发修改同一份决策给decisions表增加version字段更新时带上版本号。ALTER TABLE decisions ADD COLUMN version INTEGER NOT NULL DEFAULT 1; UPDATE decisions SET summary ?, status ?, version version 1 WHERE id ? AND version ?;如果UPDATE影响行数为 0说明数据已经被其他会话改过需要重新读取再做决策。6.2 权限与多租户隔离多团队共用一套会话库时不能让任意 Agent 搜到所有会话。常见做法是加namespace或team_id字段agents表增加team_id。所有查询都带上team_id条件。跨团队共享时通过session_context显式标记公开范围。业务层还要控制“谁能看到哪个会话的决策”。内部实现时可以在decisions表增加visibility字段值为private、team、public。搜索函数在拼 SQL 时必须注入权限条件比如sql AND a.team_id ? params.append(team_id)这一步不能省略否则就会出现“新 Agent 能搜索到其他团队旧 Agent 讨论”的权限泄露问题。6.3 慢 SQL 与数据量增长会话数据增长很快一个活跃 Agent 一天可能产生几万条消息。慢查询主要出现在LIKE %keyword%扫描整张消息表。时间范围没有索引。多次 JOIN 时缺少关联索引。排查路径按顺序执行用EXPLAIN QUERY PLAN看 SQLite 的执行计划。用EXPLAIN ANALYZE看 PostgreSQL 的实际执行耗时。确认索引是否被选中而不是建了索引但查询没使用。对超过半年的旧消息做冷热分离只保留摘要在热表中。冷热分离的策略数据范围存储位置用途最近 30 天消息主表新 Agent 搜索30 到 180 天消息归档表审计追溯超过 180 天汇总摘要表统计和复盘7. 常见问题和排查路径7.1 会话数据写不进去问题现象常见原因检查方式处理建议插入会话时报外键错误SQLite 未开启PRAGMA foreign_keys检查连接初始化语句每次连接都执行PRAGMA foreign_keys ON消息保存成功决策表没有记录业务代码只调用了save_message没有调用add_decision检查 Agent 主循环是否在最终结论处调用了决策写入在状态机节点上显式调用决策保存写入频繁导致资源占用高每条消息都新建数据库连接查看数据库连接数用连接池复用连接避免高频建连7.2 搜索旧会话总是搜不到排查顺序很重要先确认旧会话是否真的写入了数据库。再确认搜索的agent_name、session_id、status条件是否匹配。然后检查关键词是否因为大小写或分词差异导致匹配失败。最后确认是不是索引和数据量导致超时。常见问题问题现象可能原因解决方案搜“消息队列”命中不了“MQ”关键词表达差异建立同义词表搜索时扩展关键词搜出的结果没有最近 3 天内容时间过滤条件写错检查created_at类型和时间时区决策搜索只返回accepted搜索不到已否决方案查询条件里写死了status确认业务是否需要statusrejected的记录用LIKE %关键词%查询特别慢没有全文索引切换全文检索或增加限定条件减少扫描量7.3 上下文注入后还是回答不出来问题可能不出在搜索而在“注入方式”。历史片段太长模型注意力被无关内容干扰。历史决策没有带状态和日期模型无法判断是否已过期。提示词没有告诉模型“何时引用历史、何时忽略历史”。建议每次只注入 3 到 5 条高相关结果每条内容控制在 200 字以内。决策记录前方明确标注状态和时间。7.4 SQL 注入与输入校验用户提问和 AI Agent 生成的关键词都可能包含特殊字符。直接拼接 SQL 会有注入风险也会破坏查询语义。正确做法是所有外部输入都使用参数化查询。特殊字符用转义或参数传入不要直接替换进 SQL 字符串。如果允许 LLM 生成 SQL 查询必须限制只能查白名单表并设置只读账号。# 不推荐直接把用户输入拼到 SQL 中 sql fSELECT * FROM messages WHERE content LIKE %{user_input}% # 推荐使用参数化查询 sql SELECT * FROM messages WHERE content LIKE ? params [f%{user_input}%]这个原则无论使用 SQLite、PostgreSQL 还是 SQL Server 都适用。8. 最佳实践和下一步扩展8.1 可执行的最佳实践清单把前面提到的经验整理成一份检查清单方便在项目落地时逐项确认[ ] 表结构变更统一放在迁移脚本里禁止在业务代码里执行ALTER TABLE。[ ] 每个 Agent 运行时上下文对象必须有明确的session_id。[ ] 所有写操作放进事务避免消息写了一半、决策没写进去。[ ] 所有查询都使用参数化 SQL不直接拼接用户输入。[ ] 搜索函数默认加上team_id或agent_name条件缩小扫描范围。[ ] Prompt 中明确告知模型历史记录的引用方式和过期判断规则。[ ] 生产环境的数据库账号使用最小权限Agent 只拥有查询和写入所需表的权限。[ ] 定期用EXPLAIN检查慢查询对超大表做冷热归档。[ ] 登录日志和操作审计只在关键节点记录避免日志表无限增长。8.2 扩展方向向量检索、摘要层和记忆分级SQL 会话库跑通后可以自然扩展到三个方向。方向一是向量检索。给messages表增加embedding列在写入时调用本地或远程 Embedding 模型生成向量搜索时先做 SQL 结构化过滤再按向量相似度排序。这能解决“表达不一致但语义相同”的搜索问题。实现时要注意向量列的存储成本和索引更新时间。方向二是摘要层。旧会话语料很多时一次性注入所有历史摘要会超过模型上下文窗口。可以在每天结束后用 Agent 把所有新增决策生成一份“当日纪要”保存到session_context中。新会话只需要先搜索纪要再按session_id回溯详细讨论。方向三是记忆分级。把会话记忆分成短期、中期、长期层级数据使用方式短期记忆当前轮对话直接放入 Prompt中期记忆当前会话的历史消息按时间截断后注入长期记忆跨会话决策、偏好、约束通过 SQL 搜索后选择注入用 SQL 管理所有智能体会话本质上是在给 Agent 建立一套“可查询的长期记忆”。会话表保存过程决策表保存结论上下文表保存关键信息三者配合才能让新 Agent 接续旧任务时不再从零开始。这套方案不依赖特定 Agent 框架也不要求先引入向量数据库只要把建表、写入、搜索、注入这四条链路做好就能在日常项目中看到明显效果。
返回列表