Agent提示词如何实时更新
上一篇结尾留了个尾巴:管理员在控制台按下保存,新人设要经过哪些缓存和重建,才真正到达模型。这一篇把这条路走完。
顺着走会发现,路上的东西多半不在代码里。一个智能体长什么样、它在哪个档位下用哪几段提示词、改了之后什么时候算数,全在库里和控制台上。所以本篇顺带把一件事说清楚:Ragent2.0在Agent档下,哪些东西写在代码里,哪些不写,两边如何协作。
从初始化器的一次运行开始
1. 十二条槽位,九条挂着默认
比特严选那套数据集装进Ragent的最后一步,是跑bit-selection目录下的AgentProfileInitMain。它不直连数据库,走的是控制台那几个管理接口:先建一个叫“比特严选Agent”的智能体,再往它身上写三段提示词,最后把它设为激活。系统自带的那个“默认助手”一个字没动。
跑完打开控制台,点进这个新智能体。左边一列十二条槽位,分三组摆着:WorkFlow专属、Agent专属、通用。九条右侧挂着一个默认小标,鼠标挪上去是“未自定义,沿用默认助手”。每条前面还有个圆点,亮的表示当前档位下会被读,灰的表示不会。填了内容的那三条里,Agent人设是亮的,历史对话摘要和闲聊应答是灰的,点开右侧写着“当前不生效 · Agent模式下由主Agent直接应答”。

看到这页的人多半会问:九条挂默认的,模型跑起来时这九段提示词从哪来?两条灰着的,当前档位下压根不会被读,初始化器为什么还要往里写?亮着的那条人设,管理员改一个字保存,模型什么时候开始按新的说?
这几个问题就是本篇的几块。回答之前先退一步,看提示词在这类系统里到底算什么东西。
2. 提示词是内容,不是参数
传统Spring应用里,配置是旋钮:连接串、线程池大小、超时。行为写在代码里,配置只给行为调几个数。
LLM应用不一样。决定它怎么表现的最大一块是提示词,而提示词是内容不是参数。人设里改一句话,模型的口吻、选工具的倾向、什么该答什么该拒,可能整个变样;反过来代码一行不动,换一份人设它就是另一个助手。业界这几年做的提示词管理(Prompt Management)、Agent编排平台,解决的都是这件事:把提示词从代码里搬出去,给它编辑界面、版本和生效机制。
于是系统天然分成两个面:
- 运行面是请求进来之后走的那条路,Controller、编排、循环、工具、存储。
- 配置面是这条路上所有能被外部改写的内容:人设、工具描述、工具清单、技能手册。两个面不是平行的,运行面上有一站要去配置面取东西,取完才能往下走。
在Ragent里这一站就是ReActAgentProvider.getAgent(),本章前面拆这个工厂的时候讲过:每次请求先解析人设、再解析工具目录,两样都没变就复用缓存的Agent实例,变了就重建。本篇不再进它内部,只接它的前后。

配置面上那四块,本篇只拆第一块,也是最大的一块。工具挂载归讲工具发现与调用那部分,技能手册归技能那一章,模型档位是yaml里的事,本篇不碰。
3. 运行面的地图
Agent档对外暴露三个端点。
主入口是 /agent/v1/chat。用户发一句话,请求从这里进入,服务端通过SSE持续把结果返回前端。
另外两个端点并不是一开始就设计好的,而是被Agent的两个特性逼出来的。
/agent/v1/chat/confirm 是被写操作逼出来的。Agent不只是查数据,还会真的调用工具修改业务状态,比如申请售后、取消订单。这类操作不能直接执行,必须先停下来等用户确认。此时原来的/chat请求已经进入等待状态,用户后续点击“确认”属于一次新的请求,因此需要单独的端点把这个动作接进来,再让原来的执行继续往下走。
/agent/v1/stop 则是被长任务逼出来的。一次Agent请求可能在ReActAgent里连续循环很多轮,用户随时可能想中途停止。这个停止动作不能依赖正在执行的那条SSE链路,只能通过一个带外端点发出中断信号,让当前任务安全结束。
这也是Agent档和Workflow档一个很直观的区别:Workflow是一问一答,请求结束就结束;Agent会在一次请求里持续思考、调用工具、再思考,因此存在一个明确的正在运行状态。有这个状态,才需要确认和停止这两类控制入口。
三个端点后面统一进入编排层。这一层负责管理一次请求从开始到结束的生命周期,包括保证同一会话的运行互斥、限制单用户并发会话数、维护SSE通道、消息落库,以及处理异常和停止后的收尾。
但编排层不负责模型怎么思考。真正的推理、工具调用和多轮循环,是再往里的 ReActAgent 负责;反过来,ReActAgent也不关心HTTP、SSE或Controller,它只处理消息输入,并产出消息和执行结果。
所以在真正进入循环之前,编排层还要完成一个关键动作:调用 getAgent(),拿到这次请求要使用的、已经装配好的Agent实例,然后把请求交给它执行。
至于并发闸门怎么加、异常怎么通过SSE告诉前端、用户停止后怎么安全收尾,本章后面会分别展开。
一个智能体在库里长什么样
1. 两张表
t_agent_profile存智能体本身,一行一个:
CREATE TABLE t_agent_profile (
id VARCHAR(20) NOT NULL PRIMARY KEY,
name VARCHAR(64) NOT NULL,
description VARCHAR(512),
avatar VARCHAR(32),
builtin SMALLINT NOT NULL DEFAULT 0,
active SMALLINT NOT NULL DEFAULT 0,
-- 省略审计与逻辑删除字段
CONSTRAINT uk_agent_name UNIQUE (name)
);
t_agent_prompt存它填过的提示词,一行一个槽位,agent_id + slot_key唯一:
CREATE TABLE t_agent_prompt (
id VARCHAR(20) NOT NULL PRIMARY KEY,
agent_id VARCHAR(20) NOT NULL,
slot_key VARCHAR(64) NOT NULL,
content TEXT,
-- 省略审计与逻辑删除字段
CONSTRAINT uk_agent_slot UNIQUE (agent_id, slot_key)
);
这里有三个字段需要特别留意。
builtin表示内置智能体。内置智能体不允许编辑,也不允许删除,同时它还是提示词回落链的最后一层:其他智能体某个槽位没有配置时,最终会回落到内置智能体对应的槽位。
active表示当前全局生效的智能体。业务上任意时刻只允许一条记录为1,所以切换智能体本质上就是切换这条激活记录。
而t_agent_prompt.content 允许为空。这里的空值、空串或者纯空白内容,都统一视为这个槽位没有配置,不会阻断后续的提示词回落。
这几个字段看起来都很简单,但实际上分别决定了后面的提示词回落链和智能体激活机制。后面讲到这两部分时,再把具体规则拆开。
2. 槽位元数据只有一个权威源
slot_key的合法取值统一定义在枚举AgentPromptSlot里。目前共十二个槽位,覆盖Agent和Workflow两种档位下所有面向用户的提示词。
每个槽位除了显示名,还携带自己的生效范围、必需占位符和编辑提示。这些元数据全部跟着枚举走,不落表,也不单独放配置文件:
public enum AgentPromptSlot {
AGENT_MAIN("Agent人设", Group.AGENT,
Set.of(OrchestrationMode.AGENT),
"WorkFlow 模式不经过 ReAct 架构",
Set.of()),
CONVERSATION_SUMMARY("历史对话摘要", Group.WORKFLOW,
Set.of(OrchestrationMode.WORKFLOW),
"Agent 模式的长会话改由「Agent 上下文压缩」承接",
Set.of("{summary_max_chars}")),
KB_ANSWER("知识库应答", Group.COMMON,
Set.of(OrchestrationMode.WORKFLOW, OrchestrationMode.AGENT),
null,
Set.of());
// 省略其余九个槽位
private final String displayName;
private final Group group;
private final Set<OrchestrationMode> effectiveModes;
private final String inactiveReason;
private final Set<String> requiredPlaceholders;
private final String editorHint;
}
2.1按功能命名,不按架构命名
槽位叫KB_ANSWER,不叫WORKFLOW_KB_ANSWER。
因为生效范围会变,功能语义相对稳定。这个槽位早期只给Workflow用,后来Agent档的检索工具也开始读取它。如果把架构写进名字,范围一变,标识符就过时了,还会牵扯表里的slot_key数据迁移。
所以这里的约定是:标识符描述它做什么,不描述它现在在哪个档位使用。