新闻详情

新闻详情

首页 / 资讯中心 / 详情

AI代理随机推理与确定性提交:PostgreSQL密封依赖合同实战

发布时间:2026/9/26 20:09:58来源:尧图网络
AI代理随机推理与确定性提交:PostgreSQL密封依赖合同实战
1. 从一次线上事故说起为什么随机推理需要“密封合同”去年冬天我们团队上线了一个基于 AI 代理的自动工单处理系统。代理会根据用户提交的自然语言描述自主决定调用哪些工具、生成哪些字段、写入哪些表。上线第一周跑得挺顺第二周开始出问题同一个工单代理两次提交的结果不一致一次把优先级判成“高”一次判成“中”更麻烦的是它偶尔会在事务提交到一半时“反悔”重新调用模型导致数据库里出现半截数据。排查了两天根因很清楚大模型的推理过程本质上是随机的temperature 大于 0 时尤其明显而数据库事务提交要求的是确定性——要么全成功要么全回滚中间不能有“我再想想”。这两者天然冲突。后来我们引入了一套“密封依赖合同”的机制把随机推理和确定性提交隔离开问题才算彻底解决。这篇博文就把这套思路完整拆开讲。核心关键词是AI 代理、密封依赖合同、随机推理、确定性提交、PostgreSQL。它解决的是当 AI 代理要往数据库写数据时如何保证“推理可以随机但提交必须确定”。适合正在做 AI 代理落地、自动化工作流、或者任何“模型输出要落库”场景的开发者参考。哪怕你只是用 PostgreSQL 存 AI 生成的内容这套思路也能帮你少踩很多坑。2. 问题本质拆解随机推理与确定性提交的天然矛盾2.1 随机推理到底“随机”在哪里很多人以为大模型的随机性只体现在“措辞不同”其实远不止。在一个 AI 代理的执行链路里随机性至少出现在四个层面采样随机temperature、top_p 等参数决定了 token 采样不是取最大值而是按概率分布抽。同一个 prompt两次调用可能得到不同的工具调用序列。工具选择随机代理在“调用 A 工具还是 B 工具”上做决策时本质是一次分类采样边界情况下会摇摆。参数填充随机即使工具选对了填进去的参数比如日期格式、金额单位、枚举值也可能有细微差异。重试随机当代理发现结果不理想时可能自主重试而重试次数和重试后的结果都不确定。这四层随机叠加起来意味着代理的一次“思考”不是一个纯函数。你给它同样的输入它可能给你不同的输出。这在对话场景里是优点显得灵活但在数据写入场景里是灾难。2.2 确定性提交对数据库意味着什么PostgreSQL 的事务模型是 ACID 里的“A”和“C”和“I”和“D”共同保证的。一个事务从 BEGIN 到 COMMIT中间的所有操作要么全部生效要么全部不生效。这里有个关键点常被忽略事务内部的语句必须是确定的。举个具体例子。假设代理生成了一条 INSERTINSERT INTO tickets (id, priority, assignee, created_at) VALUES (gen_random_uuid(), high, team-a, now());这条语句本身是确定的——给定同样的输入PostgreSQL 执行结果一致。但如果代理在事务中间又去调用了一次模型根据模型返回决定要不要再插一条那这个事务就“不确定”了。因为模型可能返回“要”也可能返回“不要”而 PostgreSQL 无法预知这个分支。更隐蔽的问题是幂等性。如果代理因为网络抖动重试了一次提交而它没有携带幂等键数据库里就会出现两条重复工单。这不是 PostgreSQL 的错是提交语义没设计好。2.3 为什么不能简单“把 temperature 调成 0”这是最常见的误区。把 temperature 设为 0确实能让采样变成贪心但很多推理框架在 temperature0 时仍有浮点误差导致的非确定性代理的多轮决策里只要有一轮用了非零温度整体就不确定更根本的是即使推理完全确定网络重试、并发、时钟等因素仍会让提交不确定。所以“调 temperature”只是治标。真正要解决的是把随机推理的产物固化成一份确定的、可校验的、可重放的“合同”再拿这份合同去提交。这就是“密封依赖合同”的核心思想。3. 密封依赖合同的设计思路与核心机制3.1 什么是“密封依赖合同”我用一个生活类比来解释。你去餐厅点菜服务员AI 代理可能今天推荐红烧肉、明天推荐糖醋排骨这是“随机推理”。但一旦你确认下单厨房拿到的是一张写死的、签了字的点菜单密封合同上面明确写了菜名、数量、备注。厨房数据库只认这张单子不再管服务员当时怎么想的。映射到技术层面密封依赖合同是一份结构化文档包含代理决策的完整快照调用了哪些工具、传了什么参数、得到了什么中间结果最终要执行的数据库操作具体的 SQL 语句或 ORM 操作序列依赖声明这次提交依赖哪些前置条件比如“必须存在用户 X”“库存必须大于 0”幂等键一个全局唯一的标识用于去重签名/哈希对合同内容做哈希提交时校验防止中途被篡改。这份合同一旦生成就“密封”了——后续的提交过程不再调用模型只执行合同里写死的内容。3.2 为什么用 PostgreSQL 来承载合同热词里 PostgreSQL 出现频率极高这不是偶然。承载密封依赖合同PostgreSQL 有几个天然优势JSONB 类型合同本身是半结构化的JSONB 既能存又能查还能建 GIN 索引。事务的严格性PostgreSQL 的事务隔离级别尤其是 SERIALIZABLE能保证合同执行的原子性。约束与触发器可以用 CHECK 约束、外键、触发器来校验合同里的依赖声明。advisory lock可以用咨询锁来串行化同一幂等键的提交避免并发重复。LISTEN/NOTIFY合同状态变更可以实时通知下游。相比之下MySQL 在 JSON 支持和事务严格性上稍弱虽然 8.0 后改善很多而 PostgreSQL 的“严谨”气质和“确定性提交”这个需求高度契合。3.3 整体架构三段式隔离我把整套机制拆成三段每段职责单一推理段随机AI 代理自由发挥调用模型、工具产出候选操作。这一段允许不确定允许重试允许失败。密封段固化把推理段的产物整理成合同计算哈希写入agent_contracts表状态为sealed。这一段是幂等的——同样的推理结果生成同样的合同。提交段确定读取sealed状态的合同在事务里执行执行成功则状态改为committed失败则failed并记录原因。这一段绝不调用模型。三段之间通过数据库表解耦任何一段崩溃都不影响其他段。这就是“让随机推理安全进入确定性提交”的工程实现。4. PostgreSQL 侧的落地实现表结构与关键代码4.1 合同表的设计先看核心表结构。我用 PostgreSQL 15 实测下面的 DDL 可以直接跑CREATE TABLE agent_contracts ( id BIGSERIAL PRIMARY KEY, idempotency_key UUID NOT NULL UNIQUE, agent_id TEXT NOT NULL, contract_body JSONB NOT NULL, contract_hash TEXT NOT NULL, status TEXT NOT NULL DEFAULT sealed CHECK (status IN (sealed, committed, failed)), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), committed_at TIMESTAMPTZ, error_detail TEXT ); CREATE INDEX idx_contracts_status ON agent_contracts (status); CREATE INDEX idx_contracts_agent ON agent_contracts (agent_id); CREATE INDEX idx_contracts_body ON agent_contracts USING GIN (contract_body);几个设计要点值得说明idempotency_key 唯一约束这是防重复提交的第一道防线。代理生成合同时必须带一个 UUID重复插入会直接报唯一冲突天然幂等。contract_body 用 JSONB合同内容灵活不同代理的合同结构可能不同JSONB 不用改表结构。contract_hash对 body 做 SHA-256提交前校验防止合同在存储过程中被意外修改。status 用 CHECK 约束状态机只有三个合法值避免脏状态。4.2 合同体的结构约定合同体我建议用固定 schema方便校验。一个典型合同长这样{ version: 1.0, agent_id: ticket-triager, reasoning_trace_id: trace-abc-123, operations: [ { type: insert, table: tickets, values: { priority: high, assignee: team-a, summary: 用户反馈登录失败 } }, { type: update, table: users, set: { last_ticket_at: 2025-01-01T10:00:00Z }, where: { id: 42 } } ], dependencies: [ { kind: exists, table: users, where: { id: 42 } }, { kind: unique, table: tickets, columns: [summary, created_date] } ] }operations是确定性的操作序列dependencies是前置条件。提交段会先校验 dependencies全部满足才执行 operations。4.3 密封合同的生成函数密封段的核心是一个 PL/pgSQL 函数负责把推理产物固化成合同CREATE OR REPLACE FUNCTION seal_contract( p_idempotency_key UUID, p_agent_id TEXT, p_body JSONB ) RETURNS BIGINT AS $$ DECLARE v_hash TEXT; v_id BIGINT; BEGIN v_hash : encode(digest(p_body::text, sha256), hex); INSERT INTO agent_contracts (idempotency_key, agent_id, contract_body, contract_hash) VALUES (p_idempotency_key, p_agent_id, p_body, v_hash) ON CONFLICT (idempotency_key) DO NOTHING RETURNING id INTO v_id; IF v_id IS NULL THEN SELECT id INTO v_id FROM agent_contracts WHERE idempotency_key p_idempotency_key; END IF; RETURN v_id; END; $$ LANGUAGE plpgsql;注意ON CONFLICT DO NOTHING加回查的写法——这是幂等插入的标准套路。第一次插入返回新 id重复插入返回已有 id调用方无感知。4.4 提交段的执行逻辑提交段是整个机制里最需要小心的部分。我的做法是用一个函数包住“校验依赖 执行操作 更新状态”CREATE OR REPLACE FUNCTION commit_contract(p_contract_id BIGINT) RETURNS TEXT AS $$ DECLARE v_contract RECORD; v_op JSONB; v_dep JSONB; BEGIN SELECT * INTO v_contract FROM agent_contracts WHERE id p_contract_id AND status sealed FOR UPDATE; IF NOT FOUND THEN RETURN skipped; END IF; -- 校验依赖 FOR v_dep IN SELECT * FROM jsonb_array_elements(v_contract.contract_body-dependencies) LOOP IF NOT check_dependency(v_dep) THEN UPDATE agent_contracts SET status failed, error_detail dependency unmet: || v_dep::text WHERE id p_contract_id; RETURN failed; END IF; END LOOP; -- 执行操作 FOR v_op IN SELECT * FROM jsonb_array_elements(v_contract.contract_body-operations) LOOP PERFORM execute_operation(v_op); END LOOP; UPDATE agent_contracts SET status committed, committed_at now() WHERE id p_contract_id; RETURN committed; EXCEPTION WHEN OTHERS THEN UPDATE agent_contracts SET status failed, error_detail SQLERRM WHERE id p_contract_id; RAISE; END; $$ LANGUAGE plpgsql;FOR UPDATE锁住合同行防止并发提交同一合同。check_dependency和execute_operation是两个辅助函数分别校验依赖和把 JSON 操作翻译成 SQL。这里的关键是整个函数不调用任何模型纯确定性执行。5. 实操全流程从代理推理到合同提交5.1 环境准备与 PostgreSQL 安装要点先把环境搭起来。热词里“postgresql安装教程”“linux安装postgresql”“postgresql下载哪个版本”都是高频问题我按实际经验给建议。版本选择上生产环境建议 PostgreSQL 15 或 16。15 引入了 MERGE 语句和更好的 JSONB 性能16 在并行查询和逻辑复制上有提升。14 及以下不是不能用但 JSONB 的 GIN 索引性能差一截。Windows 用户直接去官网下 EDB 的安装包Mac 用户用 Homebrew 最省事brew install postgresql16 brew services start postgresql16Linux 上以 Ubuntu 22.04 为例sudo apt install -y postgresql-16 postgresql-contrib-16 sudo systemctl enable --now postgresqlpostgresql-contrib一定要装digest函数做 SHA-256就在这个包里。很多人装完发现digest找不到就是漏了 contrib。装完后建库建用户sudo -u postgres psql CREATE DATABASE agent_demo; CREATE USER agent_user WITH PASSWORD strong_password; GRANT ALL PRIVILEGES ON DATABASE agent_demo TO agent_user; \c agent_demo CREATE EXTENSION IF NOT EXISTS pgcrypto;pgcrypto扩展提供digest和gen_random_uuid是密封合同的依赖。5.2 代理侧生成合同的完整代码代理侧我用 Python 写因为它和主流推理框架集成最顺。核心逻辑是推理完成后把结果整理成合同体调用seal_contract。import json import uuid import hashlib import psycopg2 from psycopg2.extras import Json def build_contract(agent_id, reasoning_result): operations [] for action in reasoning_result[actions]: operations.append({ type: action[type], table: action[table], values: action.get(values), set: action.get(set), where: action.get(where), }) dependencies reasoning_result.get(dependencies, []) return { version: 1.0, agent_id: agent_id, reasoning_trace_id: reasoning_result[trace_id], operations: operations, dependencies: dependencies, } def seal(conn, agent_id, contract_body): idem_key str(uuid.uuid4()) body_text json.dumps(contract_body, sort_keysTrue, ensure_asciiFalse) body_hash hashlib.sha256(body_text.encode()).hexdigest() with conn.cursor() as cur: cur.execute( INSERT INTO agent_contracts (idempotency_key, agent_id, contract_body, contract_hash) VALUES (%s, %s, %s, %s) ON CONFLICT (idempotency_key) DO NOTHING RETURNING id , (idem_key, agent_id, Json(contract_body), body_hash)) row cur.fetchone() if row is None: cur.execute( SELECT id FROM agent_contracts WHERE idempotency_key %s, (idem_key,) ) row cur.fetchone() conn.commit() return row[0]这里有个细节json.dumps用了sort_keysTrue。为什么因为哈希要对内容敏感如果字典顺序变了哈希就变了会导致同一份合同算出不同哈希。排序后保证确定性。5.3 提交段的调用与状态流转提交段可以是一个独立的 worker轮询sealed状态的合同def commit_worker(conn, batch_size10): with conn.cursor() as cur: cur.execute( SELECT id FROM agent_contracts WHERE status sealed ORDER BY created_at LIMIT %s FOR UPDATE SKIP LOCKED , (batch_size,)) ids [r[0] for r in cur.fetchall()] for cid in ids: with conn.cursor() as cur: cur.execute(SELECT commit_contract(%s), (cid,)) result cur.fetchone()[0] conn.commit() print(fcontract {cid}: {result})FOR UPDATE SKIP LOCKED是多 worker 并发的关键——它让每个 worker 拿到不同的合同不会互相阻塞。这个技巧在 PostgreSQL 做任务队列时非常常用比用外部队列简单得多。5.4 依赖校验函数的实现check_dependency负责把依赖声明翻译成 SQL 校验CREATE OR REPLACE FUNCTION check_dependency(p_dep JSONB) RETURNS BOOLEAN AS $$ DECLARE v_kind TEXT : p_dep-kind; v_table TEXT : p_dep-table; v_where JSONB : p_dep-where; v_count INT; v_sql TEXT; BEGIN IF v_kind exists THEN v_sql : format(SELECT count(*) FROM %I WHERE %s, v_table, jsonb_to_where(v_where)); EXECUTE v_sql INTO v_count; RETURN v_count 0; ELSIF v_kind unique THEN v_sql : format(SELECT count(*) FROM %I WHERE %s, v_table, jsonb_to_where(v_where)); EXECUTE v_sql INTO v_count; RETURN v_count 0; END IF; RETURN FALSE; END; $$ LANGUAGE plpgsql;jsonb_to_where是个辅助函数把{id: 42}翻译成id 42。这里要特别注意 SQL 注入——表名用%I转义值用参数化。我见过有人直接字符串拼接结果代理生成的合同里带了恶意内容直接把表删了。代理的输出永远不可信必须当外部输入处理。6. 常见问题与排查技巧实录6.1 合同重复提交怎么办这是最高频的问题。现象是数据库里出现两条一样的业务数据。排查思路先查agent_contracts表看idempotency_key是否重复。如果重复说明代理侧生成 key 的逻辑有问题——可能每次重试都生成新 key。如果 key 不重复但业务数据重复说明合同体里的 operations 本身不幂等。比如INSERT没有唯一约束保护。检查提交段是否用了FOR UPDATE。没用的话两个 worker 可能同时读到同一合同。解决套路代理侧的重试必须复用同一个idempotency_key。我通常把 key 和推理请求绑定请求 ID 就是 key重试时不变。6.2 依赖校验通过但提交失败这种“校验时还在提交时没了”的情况是并发导致的。比如依赖声明“用户 42 必须存在”校验时存在执行 INSERT 时用户被删了外键约束报错。解决办法有两个一是把依赖校验和执行放在同一个事务里用SERIALIZABLE隔离级别二是用SELECT ... FOR SHARE锁住依赖行。我一般用第一种简单可靠代价是并发度略低。6.3 JSONB 查询慢的优化合同表数据量大了之后contract_body的查询会变慢。几个优化点给常用查询路径建表达式索引比如CREATE INDEX ON agent_contracts ((contract_body-agent_id))如果只查状态和 ID别SELECT *只取需要的列定期归档committed状态的旧合同到历史表主表只留近期数据。我实测过100 万条合同、每条 body 约 2KB 的情况下加表达式索引后按 agent_id 查询从 800ms 降到 15ms。6.4 常见问题速查表问题现象可能原因排查方法解决套路业务数据重复幂等键未复用查 idempotency_key 是否重复重试复用同一 key提交时依赖失效并发删除查事务隔离级别用 SERIALIZABLE 或行锁合同哈希不匹配序列化顺序不一致对比 body 文本json.dumps 加 sort_keys提交段卡住行锁等待查 pg_locks用 SKIP LOCKEDJSONB 查询慢缺索引EXPLAIN ANALYZE建表达式索引digest 函数找不到缺 pgcrypto\dx 查看扩展CREATE EXTENSION pgcrypto6.5 几个我踩过的坑第一个坑合同体里存了时间戳。代理生成合同时写了now()结果合同重放时时间变了哈希对不上。后来改成生成时就把时间戳固化成字符串提交时直接用。第二个坑用 Navicat 之类的工具手动改合同状态。有次为了调试手动把failed改成sealed结果合同体里的依赖已经失效提交时又失败。教训是状态流转必须走函数不能手动改。第三个坑代理生成的 SQL 里带了分号。有个代理在 values 里塞了; DROP TABLE tickets; --幸好我用了参数化没出事。但这也提醒我代理输出必须严格校验最好用白名单限制操作类型。7. 性能与扩展让这套机制扛住真实流量7.1 批量提交与流水线单条合同提交的开销主要在事务开启和提交上。如果代理每秒生成几百份合同逐条提交会成为瓶颈。我的做法是批量一次取 50 条合同在同一个事务里执行最后统一提交。但要注意批量提交时如果某一条失败整批回滚需要把失败的单独拎出来重试。def batch_commit(conn, ids): with conn.cursor() as cur: for cid in ids: try: cur.execute(SELECT commit_contract(%s), (cid,)) except Exception as e: conn.rollback() mark_failed(conn, cid, str(e)) conn.commit() continue conn.commit()7.2 分区表应对海量合同合同表增长很快一天几十万条很常见。用 PostgreSQL 的原生分区按时间切CREATE TABLE agent_contracts ( ... ) PARTITION BY RANGE (created_at); CREATE TABLE agent_contracts_2025_01 PARTITION OF agent_contracts FOR VALUES FROM (2025-01-01) TO (2025-02-01);分区后查询近期数据只扫对应分区归档旧数据直接DETACH PARTITION比 DELETE 快几个数量级。7.3 与增量同步工具的配合热词里出现了“postgresql增量同步软件”这在实际场景里确实有用。合同表可以作为 CDC变更数据捕获的源把committed状态的合同同步到下游分析库。逻辑复制logical replication是首选配置简单CREATE PUBLICATION contract_pub FOR TABLE agent_contracts WHERE (status committed);下游订阅后只同步已提交的合同天然过滤掉中间状态。这比应用层双写可靠得多。7.4 监控指标上线后必须盯几个指标sealed状态的合同积压量超过阈值说明提交段跟不上合同从sealed到committed的 P99 延迟failed合同的比例和错误分布幂等键冲突次数反映代理重试频率。这些指标用一条 SQL 就能查SELECT status, count(*), percentile_cont(0.99) WITHIN GROUP (ORDER BY EXTRACT(EPOCH FROM (committed_at - created_at))) AS p99_seconds FROM agent_contracts WHERE created_at now() - interval 1 hour GROUP BY status;8. 一些延伸思考与个人体会这套“密封依赖合同”的机制本质上是在随机系统和确定系统之间加了一层缓冲层。它不只适用于 AI 代理任何“上游不确定、下游要求确定”的场景都能用。比如人工审批流、第三方回调、消息队列的消费端思路是相通的。我个人在实际操作中的体会是不要试图让模型变确定而要让不确定的产物变得可管理。调 temperature 是徒劳的因为随机性只是问题的一部分。真正管用的是把推理和提交解耦用一份不可变的合同把两者隔开。合同一旦生成就当成外部输入对待严格校验、幂等处理、事务执行。最后再分享一个小技巧合同体的版本号字段version一定要留。我吃过亏——后来想给合同加字段老合同没有新字段解析时直接报错。有了版本号就能按版本走不同的解析逻辑平滑升级。这个字段现在是我所有合同类结构的标配哪怕一开始用不上。另外如果你用的是本地模型热词里“ai代理助手加本地模型”很火推理延迟比云端高密封段的吞吐可能成为瓶颈。这时候可以考虑把密封段做成异步——代理先把推理结果丢进队列密封 worker 慢慢消费。反正密封是幂等的慢一点不影响正确性。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

383个技能怎么选?按工作流快速定位NVIDIA Agent Skills目录的实用清单 2026/9/26 21:04:35

383个技能怎么选?按工作流快速定位NVIDIA Agent Skills目录的实用清单

383个技能怎么选?按工作流快速定位NVIDIA Agent Skills目录的实用清单 【免费下载链接】skills Agent Skills for NVIDIA products — install into Claude Code, Codex, and other coding agents to run Physical AI, robotics, simulation, CUDA, and RAG workflo…

阅读更多 →
避免“指标变成目标”:效能度量如何跳出KPI游戏? 2026/9/26 21:04:28

避免“指标变成目标”:效能度量如何跳出KPI游戏?

做研发效能和测试这块时间长了,你会发现一个特别拧巴的现象:效能度量、测试指标、KPI这三个词放在一起,嘴上说的是“提升质量”,落地却经常变成“数字游戏”。我在一线待了这么多年,见过太多团队把测试指标做成了一堆毫…

阅读更多 →
Tesseract 3.02.02 C++ Win32集成:从编译到避坑全解析 2026/9/26 21:04:28

Tesseract 3.02.02 C++ Win32集成:从编译到避坑全解析

简介:面向 Windows 平台 OCR 开发者的 Tesseract 3.02.02 SDK 整合包,适合需要在 C/C 工程中集成 Tesseract 识别能力或进行旧版接口调试的工程师。压缩包共 36 个文件,大小约 27.1MB;其中 24 个 h 头文件用于声明 OCR API 与数据…

阅读更多 →
NIST CSF在物联网落地:五大功能拆解与实战避坑指南 2026/9/26 21:04:08

NIST CSF在物联网落地:五大功能拆解与实战避坑指南

作为长期在物联网一线摸爬滚打的安全从业者,我越来越强烈地感受到一件事:把NIST网络安全框架(CSF)引入IoT/IIoT场景,不是赶时髦,而是被现实逼出来的。很多团队面对"智能设备被挖矿""摄像头被…

阅读更多 →
E7Helper技术解析:第七史诗图像识别+规则引擎自动化工作流 2026/9/26 21:04:08

E7Helper技术解析:第七史诗图像识别+规则引擎自动化工作流

1. 项目概述:这不是一个“挂机外挂”,而是一套可验证、可调试、可审计的游戏辅助工作流E7Helper这个名字在第七史诗玩家圈里已经不算新鲜,但很多人对它的理解还停留在“点开就自动刷图”的模糊印象里。我从2022年游戏公测初期就开始跟踪这类工…

阅读更多 →
Twitter自动化营销:热门霸屏与精准获客实操指南 2026/9/26 21:04:08

Twitter自动化营销:热门霸屏与精准获客实操指南

做外贸独立站和跨境电商的朋友,应该都有过这种体验:明明产品图拍得不错,账号也坚持发了几个月,结果询盘寥寥,反而是那些会蹭话题、在热门话题出现后第一时间发声的同行,一条推文就能带来几十个点击和一堆私…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉