Agent 的设计
xlyAi 是 ERP 之上的「理解与编排」层:听懂业务人员的话,替他们在 1700 多张表单里查数、看单、发起操作。它是一个智能体(无路由、无意图分类),且模型本身没有任何写入能力——改数据的永远是人的一次点击。
1.1 四条设计立场
① 单智能体,自己决定怎么做
所有对话进同一个 ReAct 循环,模型自行决定查数、澄清、载入流程还是出卡。不设意图门——实测无准确率收益,反而在分类环节丢失原话信息。
② 模型全只读,写入是人的动作
模型最多把写操作渲染成一张带按钮的卡片,然后停下;真正写入走用户点按钮后的确定性路径,不经过模型。幻觉在结构上碰不到数据。
③ 知识外置:流程是数据,不是代码
业务流程写成技能文本存在数据库里,模型按需载入照做;改流程不用改代码发版。领域结构(域、表单、字段)来自离线物化的知识图谱。
④ 薄层:业务权威永远在 ERP
读走 ERP 与网页相同的 API,写只投递到一张队列表由 ERP 执行。AI 层不复制业务规则,越薄越不会与 ERP 真实行为漂移。
1.2 Agent 解剖
权限不是模型要遵守的规则,而是注入在每个工具里、它无法越过的边界。
1.3 一轮对话的生命周期
失败即拒
谎称已保存→纠正
全程流式推送;每个事件按序落入账本,成为下一轮记忆。用户点了卡片按钮,则进入第二部分的确定性写路径。
1.4 能力面:七个工具,四种角色
| 角色 | 工具 | 做什么 / 为什么 |
|---|---|---|
| 载入流程 | useSkill | 载入某技能全文。提示词只放一行索引,全文按需加载;载入后钉在流程卡上跨轮生效。 |
| 感知查数 | findForms | 「某类单据」→ 具体哪张表单。模型不必背 1748 张表结构。 |
readFormData | 读某表单一页数据(可过滤、翻页)。答案里的每个数字都必须出自这里,输出截断分页。 | |
lookupRecord | 某条命名记录的完整字段。多条命中报候选让用户挑,不猜。 | |
| 对话交互 | askUser | 一个澄清问题 + 可点选项。缺关键信息才问、一次问全、问完即停。 |
| 请求核准 | collectForm | 新增:弹一张表单让用户一次填齐;已说的值预填,客户/产品从真实数据下拉选。 |
previewChange | 改/作废/审核等:出预览卡(当前值 + 变化高亮 + ERP 同名按钮)。人核对的就是将要写入的。 |
1.5 知识体系三层
域地图(常驻)
11 个业务域及上下游流转,每域一行常驻提示词——给模型一张「问题落在哪」的粗地图。
技能(按需载入)
每篇只写步骤与停止条件,不写错例枚举。内置五篇:查询、新建报价、新建单据、修改记录、单据状态操作。
知识图谱(工具背后)
从 ERP 元数据物化的表单目录与字段字典(中文名⇄技术列⇄外键)。模型不直接看它,但每次定位与解析都踩在它上面。
1.6 环境
ERP 登录态透传
+ 共享队列表
支撑设施:MySQL(与 ERP 共库,多租户)、Redis(缓存/暂存)、本地 27B 模型(OpenAI 兼容通道)。接口细节见第三部分,实现映射见附录。
核心机制
支撑上述设计的四条机制:写路径的层层闸门、事件账本记忆、以及针对小模型不可靠性的确定性护栏。
2.1 编排:技能文本代替路由与流水线
- 规则只有一条:全部自由文本进唯一 ReAct agent,流程知识由模型按需 useSkill 载入。
- 不做意图门:分类器不掌握字段定义,抽取必有损(「大16开」→「16开」);原话只应在字段定义存在的地方被解释一次。
- 跨轮续办靠流程卡:激活的技能全文钉在提示词尾部,流程终结才摘下——旧轮被压缩也不丢步骤。
2.2 写路径:三道闸
解析产物暂存服务端
→ 原子抢占防重复提交
其余进待办
所见即所写
保存时重读记录:被他人改过的值、变了的审核/作废状态一律拒绝,请重新预览。卡上显示的 FK 是实际解析绑定的记录名、数字是规范化后的值——人核对的就是入库的。
状态合法性硬检查
审核要求未审核、作废要求未作废等前置条件在预览与保存各查一次,ERP 执行时刻再查一次,三层防竞态。
新增走表单
用户填完点保存后由端点构建载荷:中文名→列、FK 名称→id、类型强转(解析不了的数字报错而非写 0)、补必填默认、生成主键与单号。报价单构建主表+从表+多数量三张表,价格留给 ERP 核价。
防重复防并发
previewId 一次性、原子抢占,并发点击只有一个能入队;多字段先全部过校验再单事务入队,绝不写一半。
2.3 记忆:事件账本 + 四段投影
会话没有可被并发覆盖的「聊天记录数组」:每句话、每次工具调用、每次按钮点击都是一条追加事件,落 MySQL(Redis 热缓存)。前端历史与 LLM 上下文是同一账本的两种投影,永不对不上。喂给模型的上下文按 token 预算分段组装:
发送前自检总量,超了继续收缩——绝不让推理端从头部静默截断(那样 system prompt 先死)。估算故意偏高,并与模型返回的实际 token 数持续对账。
2.4 针对小模型的确定性护栏
反编造
零工具调用却答出数字 → 注入纠正话术自动重试一次;复发则标注「未经核实」。
谎报纠正
声称「已保存/已完成」但本轮没出过卡 → 追加提示「没有卡片即没有任何写入」。
确定性解析
尺寸拆分(「50*30*5」→长/宽/高)、数字/日期强转、FK 消歧、单号生成全在代码层——模型负责理解意图,代码负责解释取值。
资源护栏
工具循环最多 8 步、超时 180 秒、所有工具输出分页/截断。
2.5 安全红线
| 不变量 | 一句话 |
|---|---|
| 身份 fail-closed | 身份只来自 ERP 服务端内省;token 无效即 401,绝不回落管理员身份。 |
| token 不进模型 | 用户 token 仅单次请求持有,绝不进提示词、账本、暂存。 |
| 表单级白名单 | 读/写/下拉共用同一权限边界;鉴权参数用服务端反查值,不信模型自报。 |
| 租户隔离 | 所有查询强制品牌谓词,拿不到租户宁可返回空。 |
| 会话隔离 | 会话 id 强制用户命名空间——伪造他人 id 只会落到自己名下。 |
| 注入防护 | URL id 白名单校验、SQL 全参数化、CORS 白名单。 |
| 读不变写 | 读接口只传分页/过滤参数,绝不透传 ERP 存在的写类参数。 |
AI 侧 ⇄ ERP 侧:协作与接口
分界线一句话:xlyAi 负责理解、定位、校验、请求核准;ERP 负责执行、审计、权属、回执。交接靠同步的薄 HTTP 接口 + 异步的共享队列表。
3.1 HTTP 接口(xlyAi → ERP,全部透传用户 token)
| 接口 | 用途 |
|---|---|
GET /ai/whoami | token → 服务端认定的身份与租户,所有端点的身份唯一来源。 |
POST /business/getBusinessDataByFormcustomId | 表单数据读取,与网页前端同一接口,天然带租户与行级权限。 |
GET /getUuid | 新增载荷的主键由 ERP 发号。 |
POST /business/checkBusinessData | dry-run 预检:复用 ERP 真实校验链、强制回滚,把「保存了才发现不合法」提前。 |
POST /ai/fieldOptions | FK 下拉候选,按 ERP 下拉配置取——带行级权限、级联过滤、联动回填;不可用时回落本地字典。 |
3.2 ai_op_queue:交接面与状态协议
用户点按钮后 xlyAi 写一行操作请求(类型 + 目标 + 载荷 + 描述),状态恒为 pending+100;领取、执行、幂等、审计、回写全在 ERP 侧。现行策略:报价自动执行,其余进「我的 AI 待办」。xlyAi 只把状态只读展示在流程卡上,绝不重复提交。
- xlyAi 只写 pending+100,其余状态只由 ERP 回写。
- 一次保存多字段 = 多行待办(单事务);ERP 执行时刻再验状态,冲突回 409。
3.3 身份与授权协作
凭证透传,不自造身份
xlyAi 不签发、不存储、不续期任何用户凭证:前端逐请求带 ERP token,xlyAi 原样转发。会话按稳定身份存,重登录换 token 后历史接得回。
授权互补
ERP 后端只强制租户 + 行级两层,表单级权限校验处于关闭状态(仅前端 UI 用)。xlyAi 用同一份权限数据在自己侧补回这层,作为纵深防御;根因整改待业务决策。
质量保障 · 残余风险 · 设计取舍
4.1 质量保障
基准评测
50 题冻结基线 + 多轮轨迹基准(消歧/接续/夹带/回指),与生产读同一份提示词和技能文件。当前:扩展集 12/12,全轨迹 84%。
单元测试
8 个测试类钉住不变量:fail-closed、会话归属、投影正确性、并发不丢事件、强转拒非法值等。
对抗性审计
多智能体并行审计 + 独立复核证否,最近一次全量复审 CRITICAL 0 / HIGH 3,HIGH 已全部修复回归。
可观测性
每次 LLM 调用记录耗时/token/错误,估算与实际 token 持续对账;可选导出自托管 Langfuse(数据不出网)。业务审计随执行权在 ERP 侧。
4.2 残余风险(交付说明)
| 级别 | 事项 | 一句话 |
|---|---|---|
| 安全 | 技能表是提示词注入面 | 谁能写 ai_skill 谁就能改 agent 行为,需收紧表写权限。 |
| 安全 | dry-run 失败放行 | 刻意取舍:预检挂了不挡用户,ERP 执行时仍会真校验。 |
| 安全 | token URL 通道 / 生产前置校验 | 壳层嵌入的 token 传递与 nginx 前置会话校验属环境决策项。 |
| 运维 | 账本永久保留 | append-only 永不删除,需定保留/归档策略。 |
| 协作 | 队列旧列待删 / 生产迁移待执行 | 等 ERP 去掉兼容读后删列;4 份迁移 SQL 待上生产。 |
| 协作 | 「处理中」卡死无恢复 | ERP 领取后异常退出会停在 101,需 ERP 侧超时补偿。 |
| 根因 | ERP 权限根因未整改 | xlyAi 的白名单是纵深防御,不替代后端重开表单级校验。 |
| 功能 | 无语义召回 | 表单定位全靠关键词匹配,模糊说法要换词重试。 |
| 功能 | 策展字段仅报价有 | 其他复杂主-从单据的新增走字典启发式,体验有限。 |
4.3 贯穿全局的取舍
- 不引编排框架:人在环用「出卡即停 + 确定性端点」实现,比中断/续跑机制更简单,也更安全——模型根本没有写工具。
- 可靠性放代码不放提示词:不写逐案纠错规则,靠确定性解析 + 廉价恢复 + 回归基准。
- 执行权交出去:AI 止步于入队,写入发生在 ERP 原生路径——代码量与攻击面同时缩小。
- 处处幂等:所有交接点都假设会被重复触发(一次性抢占、追加原子、幂等键、单号重生成)。
附录:设计概念 → 代码映射
| 设计概念 | 主要代码位置 |
|---|---|
| ReAct 循环 / 按请求组装 | agent/ReActAgent · config/AgentFactory · agent/AgentIdentity |
| 对话入口 / SSE / 护栏 | web/AgentChatController |
| 7 个工具 | tool/:UseSkillTool · KgQueryTool · ErpReadTool · FormCollectTool · PreviewChangeTool · InteractionTool |
| 提示词 / 技能 | service/SystemPromptService(prompts/system.txt)· service/SkillService(ai_skill / skills/*.md) |
| 表单/字段/FK 解析校验 | service/FormResolverService · service/FormRenderService(viw_ai_useful_forms / viw_kg_field_dict) |
| 预览/保存闭环 / 唯一写入口 | service/PreviewService · web/PreviewController · service/OpService(ai_op_queue) |
| 账本与记忆投影 | service/LedgerService(ai_chat_event)· agent/EventLogChatMemory · service/EventProjectionService |
| 身份与授权 / ERP 客户端 | service/AuthzService · service/ErpClient |
| 会话 / FK 选项 | service/ConversationService · web/ConversationController · web/FormController |
| 可观测性 | config/TracingChatModelListener |