☰
AI Agent存储实战:用PostgreSQL解决记忆与状态管理难题
2026/10/15 8:44:28 网站建设 项目流程

最近半年我一直在折腾AI Agent,说实话,大多数半途而废的Agent项目,都不是死在模型不够聪明,而是死在"什么都记不住"。模型本身无状态,每次调用都是全新的开始,你要让它完成多步任务、记住用户偏好、维持长时间对话,就必须外挂一套存储。试过Redis、试过文件、也试过纯向量库,最后真正扛住线上压力的,还是PostgreSQL关系型数据库。这篇文章就是我在这套组合上踩坑、填坑、最终稳定运行的完整复盘,适合正在给Agent选存储方案的开发者,也适合想把现有PostgreSQL业务系统接进Agent的团队参考。

为什么要把这两个东西放一起聊?因为AI Agent要落地,不能只靠一个"会说话的大模型",它得有自己的记忆、状态和工作台,而PostgreSQL恰好能把关系数据、JSON文档、向量检索全塞进同一套体系里。接下来的内容会从架构思路、协作模式、实操步骤、踩坑记录到进阶玩法一条线捋下来,尽量把"为什么这么选""这里怎么配""炸了怎么修"都讲清楚。

1. AI Agent为什么非要配一个关系型数据库

1.1 先聊聊Agent的"失忆症"

所有Agent框架碰到第一个坎,都是记忆问题。你让Agent帮用户订机票,它先问出发地,再问目的地,好不容易聊到日期,结果用户切了个窗口,回来它全忘了。这不是模型笨,而是大模型天生就是"一次性天才"——每次请求都从零开始,上下文窗口关掉就什么都没了。

这时候就需要外部存储。常见方案有几种:Redis缓存响应快,但它是内存数据库,重启丢数据,多实例共享也麻烦;文件存储最简单,可一旦数据量上来,查询和过滤全靠遍历;向量数据库擅长语义检索,但你要做事务、统计、关联查询,它就非常别扭。Agent真正需要的是一块既能存结构化业务数据,又能顺手存非结构化内容,还要支持复杂查询和并发控制的地方。关系型数据库就是干这个的,而PostgreSQL在关系型里又最贴合这类场景。

用个生活化的类比:大模型像一个每次醒来都失忆的天才,Agent框架是给他配的秘书,秘书手里那本档案柜就是数据库。天才负责思考,秘书负责翻档案、记笔记、确认状态。没有档案柜,秘书只能靠脑子记,事情一多照样乱套。

1.2 为什么偏偏是PostgreSQL

选PostgreSQL而不是MySQL或者专用向量库,我在实际对比后有几个明确理由。第一是JSONB类型,Agent产生的很多数据都是半结构的,比如用户画像、工具调用参数、临时状态,用JSONB存进去既不用提前拆表,又能用GIN索引做内部字段查询,灵活性很够。第二是MVCC并发控制,生产环境里多个Agent实例同时在读写,PostgreSQL的事务隔离能扛住,不会出现锁来锁去把服务拖死的情况。第三是pgvector扩展,它让PostgreSQL同时具备向量检索能力,一张表里既能存用户ID、订单金额,又能存文本向量,省掉单独部署一套向量数据库的运维成本。

另外还有全文检索、窗口函数、行级安全这些硬能力。比如行级安全,我可以在同一张表里给不同Agent租户做数据隔离,不用为每个租户单独建表,这在多租户Agent场景里非常实用。我整理了一张对比表,方便大家按场景选型:

需求维度PostgreSQLMySQL专用向量数据库
事务与关联查询强中弱
JSON/半结构化存储JSONB+索引JSON较弱通常不支持
向量检索pgvector,单表混合存储需外挂方案最强
并发一致性MVCC成熟接近,但复杂查询弱偏OLAP,不擅长事务
运维成本单实例覆盖多需求常需配ES/Redis多一套系统

一句话结论:如果你的Agent业务里既有结构化的用户、订单、任务数据,又有对话记录、文本向量这类非结构化内容,PostgreSQL可以用一套系统把两层都兜住。省一套数据库,就省一堆监控、备份、权限和故障处理的活。

2. Agent与PostgreSQL的三种主流协作模式

2.1 模式一:记忆与状态落库

这是最基础也最常用的一种架构,把Agent的对话历史、用户画像、任务状态直接落到PostgreSQL表里。每次对话开始时,Agent先从库里把最近N轮消息捞出来,拼进上下文;每轮对话结束后,把新消息异步写回。这样一个简单的"记忆读写循环",就能治住前面说的失忆症。

表结构设计上不用搞太复杂,我一般是三张表起步:

CREATE TABLE conversations ( id BIGSERIAL PRIMARY KEY, user_id VARCHAR(64) NOT NULL, agent_name VARCHAR(64) NOT NULL, created_at TIMESTAMPTZ DEFAULT now(), updated_at TIMESTAMPTZ DEFAULT now() ); CREATE TABLE messages ( id BIGSERIAL PRIMARY KEY, conversation_id BIGINT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE, role VARCHAR(16) NOT NULL, -- user / assistant / system / tool content TEXT NOT NULL, token_count INT DEFAULT 0, created_at TIMESTAMPTZ DEFAULT now() ); CREATE TABLE agent_state ( agent_id VARCHAR(64) PRIMARY KEY, state JSONB NOT NULL DEFAULT '{}', updated_at TIMESTAMPTZ DEFAULT now() );

conversations和messages负责对话记忆,agent_state负责Agent当前执行到哪一步了。注意agent_state我用JSONB,因为Agent的状态字段常变,今天是"等待用户确认出发地",明天可能就多一个"航班查询结果缓存",用关系模式硬拆表会频繁改表结构,JSONB就灵活很多。写入时用异步批量插入,不要每条消息都同步刷库,否则高并发下数据库会先扛不住。

2.2 模式二:Function Calling让Agent自己查库

比记忆落库更进一步的是把PostgreSQL暴露成Agent的工具。现在主流Agent框架基本都支持Function Calling,模型会自己判断"用户问的问题需要查数据库",然后生成一个结构化的工具调用请求。我们只需要把工具函数注册进去,让模型决定何时执行。

比如我给Agent注册了这样一个工具:

from typing import Any def query_postgres(sql: str) -> list[dict[str, Any]]: """执行只读SQL查询,返回结果列表。只允许SELECT语句。""" if not sql.strip().upper().startswith("SELECT"): raise ValueError("只允许执行SELECT查询") with conn_pool.connection() as conn: cursor = conn.execute(sql + " LIMIT 50") # 强制限制返回行数 rows = cursor.fetchall() columns = [desc[0] for desc in cursor.description] return [dict(zip(columns, row)) for row in rows]

这里有三个关键保护点:第一,校验SQL必须以SELECT开头,堵住DELETE、DROP这类危险操作;第二,执行时强制追加LIMIT 50,防止模型生成SELECT * FROM users这种一捞全表的语句;第三,用只读数据库账号连接。这三个点组合起来,即使模型生成有问题的SQL,也不会造成实质破坏。

实际跑下来,模型对大表直接做聚合统计比人写SQL还勤快,比如"帮我统计这个月每个代理商的订单量",它真的会自动生成GROUP BY语句。这不光是省了写SQL的时间,更重要的是让非技术用户也能用自然语言跟库对话。

2.3 模式三:MCP与Skill的标准化接入

如果你不想自己封装工具函数,或者希望用社区现成的能力,那就看MCP(Model Context Protocol,模型上下文协议)。这是目前AI Agent生态里很热的标准化方案,核心思想是把"让Agent能干什么"封装成标准接口,一个MCP Server写好了,所有支持MCP的Agent框架都能直接挂载。

PostgreSQL相关的MCP Server社区已经有不少,常用的能力包括执行只读SQL、获取表结构、列出数据库列表、甚至写入操作。部署方式差不多就是跑一个本地服务,然后在Agent配置文件里声明mcpServers:

{ "mcpServers": { "postgres": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-postgres"], "env": { "DATABASE_URI": "postgresql://agent_ro:password@localhost:5432/business_db" } } } }

配置完,Agent就能像调用原生工具一样去查PostgreSQL。我个人的体会是,MCP的标准化思路是对的,但生产环境里我仍然会在MCP Server外面再套一层自己的校验逻辑,比如统一审计、统一行数限制,因为通用MCP Server的权限策略通常比较宽松。Skill则是比MCP更偏业务侧的概念,相当于预置好的一组"会用数据库的Agent技能包",比如"经营分析Agent"内置了常用的GMV、转化率SQL模板,直接调用就能出报表。

这三套模式并不互斥。我的建议是:小项目从模式一做起来,先把记忆问题解决;团队有开发能力就上模式二,可控性最高;如果是想集成多个Agent框架或者快速验证,直接用模式三,省写代码但要把安全策略补上。

3. 从零搭建:给Agent配上PostgreSQL的完整实操

3.1 版本选择与安装:别在第一步踩坑

安装PostgreSQL看似简单,其实版本选择影响后面所有事。我的建议是选16或17的稳定版,不要追老版本,也不要追刚发布的大版本——Agent生态里的扩展(比如pgvector、pg_ai)对新版本支持有个滞后期,等半年再上更稳。生产环境用发行版自带包管理装就好。

Ubuntu/Debian系:

sudo apt update sudo apt install postgresql-16 postgresql-contrib-16

RHEL/CentOS系离线环境,下载对应的rpm包然后本地安装:

sudo rpm -ivh postgresql16-server-16.4-1PGDG.rhel9.x86_64.rpm

如果是Ubuntu离线环境,可以用deb包加apt离线安装;自己编译源码是比较折腾的选择,除非你有内核级定制需求,否则不建议在生产环境搞。装完先确认版本和编码:

sudo -u postgres psql -c "SELECT version();" sudo -u postgres psql -c "SHOW server_encoding;"

注意:server_encoding必须UTF8,如果初始化时用了非UTF8编码,之后存中文或者emoji会非常痛苦,重装更痛苦。初始化最好直接指定:initdb -E UTF8 --locale=en_US.UTF-8。

Windows和macOS用户就简单了,官方installer一路下一步,勾选Stack Builder时把pgAdmin一起装上,方便日常看表。装完第一件事我建议是关掉自动启动,或者至少设置强密码,PostgreSQL默认的peer/trust认证在开发环境无所谓,暴露到外网就非常危险。

3.2 建库建表:用户、权限与表结构设计

装完数据库,先别急着一把梭建表,权限模型的优先级更高。我从第一天就给Agent业务分了三个账号:一个超级管理员仅供DBA用、一个读写账号给Agent编排层、一个只读账号专门给查询工具和数据报表用。这样即使Agent乱来,也只波及读写库的业务表,不会把结构搞坏。

创建用户和授权:

-- 只读账号:供Agent查询工具使用 CREATE ROLE agent_ro LOGIN PASSWORD 'secure_pass_ro'; GRANT CONNECT ON DATABASE agent_db TO agent_ro; GRANT USAGE ON SCHEMA public TO agent_ro; GRANT SELECT ON ALL TABLES IN SCHEMA public TO agent_ro; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO agent_ro; -- 读写账号:供Agent写记忆使用 CREATE ROLE agent_rw LOGIN PASSWORD 'secure_pass_rw'; GRANT CONNECT ON DATABASE agent_db TO agent_rw; GRANT USAGE ON SCHEMA public TO agent_rw; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO agent_rw;

表结构这块,除了前面说的记忆表,我会再加一张带向量字段的"知识片段"表,方便Agent做语义检索。建表语句里直接加上vector字段:

CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE knowledge_chunks ( id BIGSERIAL PRIMARY KEY, agent_name VARCHAR(64) NOT NULL, content TEXT NOT NULL, embedding vector(1536), -- 按你用的模型维度调整 metadata JSONB DEFAULT '{}', created_at TIMESTAMPTZ DEFAULT now() ); CREATE INDEX ON knowledge_chunks USING hnsw (embedding vector_cosine_ops);

HNsw索引是让向量检索不掉链子的关键,不加索引,数据量过万以后查询速度会直线下滑。维度1536对应OpenAI的text-embedding-3-small,如果换模型,维度要跟着改。

连接配置上,Agent服务落在生产环境时优先走内网,连接串写成:

DATABASE_URL = "postgresql://agent_rw:secure_pass_rw@10.0.0.5:5432/agent_db?ssl=require&connect_timeout=5"

connect_timeout=5非常重要,代理服务如果遇到数据库打嗝,没有超时上限,请求会无限挂起,直接把Agent的所有任务线程拖死。

3.3 Agent侧实现:连接、读写与检索代码

我这里以Python为例,Python生态接PostgreSQL最经典的方案是psycopg2加连接池。直接说结论,不要每轮对话新建连接,一定要用连接池:

from psycopg2 import pool conn_pool = pool.ThreadedConnectionPool( minconn=2, maxconn=20, dsn="postgresql://agent_rw:secure_pass_rw@10.0.0.5:5432/agent_db" ) def save_message(conversation_id: int, role: str, content: str) -> None: with conn_pool.getconn() as conn: try: with conn.cursor() as cur: cur.execute( "INSERT INTO messages (conversation_id, role, content) VALUES (%s, %s, %s)", (conversation_id, role, content) ) conn.commit() finally: conn_pool.putconn(conn)

连接池参数minconn和maxconn的配比,通常按Agent并发请求数除以单请求最大并发查询数来估。比如你有50路Agent并发,每路一次最多同时执行2条SQL,那maxconn=100比较合理。开太大了会打爆数据库,太小了高峰期排队严重。

检索逻辑上,我习惯先走向量检索,再用过滤条件缩小范围,最后用关键词兜底。比如用户问"上次我们说的关于会员体系的方案是什么",我先把这个问题embedding化,然后用SQL做向量检索:

SELECT id, content, 1 - (embedding <=> %s::vector) AS similarity FROM knowledge_chunks WHERE agent_name = %s ORDER BY similarity DESC LIMIT 5;

这里用的<=>是余弦距离操作符,值越小越相似,所以取1减它变成相似度。LIMIT 5是硬限制,取太多碎片进上下文反而干扰模型判断。

4. 实操中躲过和踩过的那些坑

4.1 连接池被打满,服务直接雪崩

这个坑我印象最深。一开始图省事,Agent进程每次请求都新建一个数据库连接,不关闭。开发环境只有几个请求,没感觉;一上生产,20个Agent实例同时跑,每个实例攒了几十条连接,PostgreSQL默认max_connections=100瞬间被打穿,业务方反馈"数据库连不上了"。查了下日志,满屏都是too many connections。

解法分两层。第一层是应用层,强制用连接池并限制maxconn;第二层是数据库层,如果并发确实高,在前端加一层PgBouncer做事务级连接池。PgBouncer能把几千个客户端连接复用成几百个后端连接,非常管用。配置里最关键的是pool_mode=transaction,这样每次事务结束连接就归还,不会长期占用。

4.2 向量检索越来越慢

大概三万条知识片段之后,我发现Agent回答变慢了,定位到知识检索SQL扫描时间飙到几秒,本来能秒答的变卡顿。原因就是建表时图省事没建索引,后面虽然补建了HNSW索引,但老表数据索引构建需要时间,期间查询更慢。后来学乖了,新表一律先建索引再灌数据:

CREATE INDEX ON knowledge_chunks USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64);

HNSW的m和ef_construction是召回质量和索引构建成本的平衡参数,一般先按默认值跑,等召回率不行再调。另外查询时的probes参数也要设,不然默认值在大基数的embedding集合上效果不理想。

4.3 Agent乱写SQL,差点把表删了

在一次测试里,我放开权限让Agent自己生成SQL去执行,结果模型直接生成了一条TRUNCATE TABLE messages。好在当时用了只读账号,被数据库权限拦下来,不然整张记忆表就没了。这事让我后怕,把安全边界补了三层:

  • 数据库层:所有Agent查询工具账号一律只读,写操作走专门的业务代码,不走模型生成的SQL。
  • 应用层:Function Calling工具内部校验SQL,非SELECT开头直接拒绝,并对关键表名做黑名单过滤。
  • 流程层:对于高危操作(批量更新、删除),必须经过人工确认,Agent只负责生成建议,不能直接执行。

注意:大模型生成的SQL永远不可信,它不是不能犯错,而是犯错方式非常出乎意料。一定要在安全设计上假设它一定会生成危险语句,然后从权限根本上去堵。

4.4 Token预算失控,上下文被塞爆

Agent查库最开心的就是拿到一堆数据全往上下文里塞。曾有一次让它统计用户分布,它生成了SELECT * FROM users,虽然LIMIT 50兜底了,但每行几十个字段,合起来几千个token就进去了,占光了整个上下文窗口,模型反而答非所问。后面我在工具描述里明确要求"查询时只选择需要的列,并优先使用聚合函数",同时在工具函数里加了返回前压缩的逻辑,超过一定行数就先让模型总结中间结果。

另外提醒一句,AI Agent中的token不是数据库token,是模型对文本的计费单位。给模型发多长的文本,就消耗多少token,超了窗口会直接报错或者截断,不是无限塞的。

5. 进阶玩法:让Agent真正"管"起数据库

5.1 自动生成SQL日报

Agent查一次库只是开始,更有价值的做法是让Agent定时自动生产SQL报表。我是用一个定时任务框架,每天早上八点让Agent查一下昨天的订单、活跃用户、转化漏斗,自动生成一份摘要发到群里。

核心思路是:不让人手动写报表SQL,而是让Agent基于表结构描述自己生成。先把表结构发给它,然后设定一个指令模板:

请基于 orders 和 users 表,统计昨天的新增用户数、订单数、GMV,并与前天做环比,输出简明摘要。

Agent先生成SQL,用只读账号执行,拿结果写summary,整个过程完全自动化。跑了一个月,准确率大约在九成左右,偶尔有SQL语法报错,但重试一次基本能过。

5.2 触发器加NOTIFY:让Agent主动干活

另一个我很喜欢的玩法是用PostgreSQL的触发器加异步通知,实现数据变更驱动Agent行动。简单说,当表里有数据变化时,PostgreSQL主动发出通知,Agent收到后立即执行任务,不用轮询。

比如我在订单表上建了一个触发器,每当新订单落库,就通过pg_notify发出消息。Agent端监听该channel,一收到消息就去处理订单,比如调用确认接口、更新库存、发起通知。代码大致是:

CREATE OR REPLACE FUNCTION notify_new_order() RETURNS TRIGGER AS $$ BEGIN PERFORM pg_notify('order_channel', row_to_json(NEW)::text); RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trg_notify_new_order AFTER INSERT ON orders FOR EACH ROW EXECUTE FUNCTION notify_new_order();

Agent端用psycopg2的listen机制监听channel,数据一变就触发后续动作。这种模式比定时轮询实时性高,数据库压力也小。我做了一个弱库存提醒:商品库存低于某个阈值时,触发器发通知,Agent收到后自动生成补货下单建议,推给运营确认。

5.3 Rust生态下的高性能Agent组合

如果对性能有更高要求,比如要支撑大量并发机器人类Agent,我推荐把控制流逻辑用Rust写,状态照旧放PostgreSQL。Rust生态里接PostgreSQL最顺手的是sqlx,它编译期就能检查SQL正确性,运行时还有连接池和异步支持,写起来很踏实。

use sqlx::postgres::{PgPool, PgPoolOptions}; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let pool = PgPoolOptions::new() .max_connections(10) .connect("postgresql://agent_rw:pass@localhost:5432/agent_db") .await?; let status = sqlx::query_scalar::<_, String>( "SELECT content FROM messages WHERE conversation_id = $1 ORDER BY id DESC LIMIT 1", ) .bind(1001) .fetch_one(&pool) .await?; println!("last message: {}", status); Ok(()) }

Rust控制流加PostgreSQL存状态,再加一个模型推理服务,是性价比很高的组合:比纯Python省CPU,部署时只需要一个二进制文件,内存占用也小。学习路线其实很明确:先过Rust基础语法,然后直接啃sqlx和tokio,再套一层Agent框架把记忆和工具串起来。多说一句,如果你的Agent要做Web管理后台,后端用Django接PostgreSQL是经典搭配,Agent本身纯Python写也好、混合也行,数据库这一层不变。

最后说几句实在话

这套方案跑到现在,我最深的体会是:刚开始容易把所有东西都往PostgreSQL里塞,后来才慢慢分清楚什么数据适合关系表、什么适合JSONB、什么该走向量索引,分清楚之后系统的复杂度反而下来了。权限要从第一天就管好,偷懒一时爽,出事火葬场;连接池配好并发就稳了一大半;给Agent用的表尽量多预留几个JSONB字段,因为Agent的需求变化太快了,频繁改表非常折腾。

如果你正准备给自己的Agent配上PostgreSQL,我的建议就是别贪大,先做一个最小场景:让Agent只能读,不能写,跑通记忆回填和查询,再慢慢开放写权限和自动化动作。这样每走一步都在可控范围内,出问题能快速定位。等这一套顺畅了,你会发现Agent不再是聊完就忘的玩具,而是一个真正能沉淀业务价值的工作伙伴。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询