跳到主要内容
查看文档索引

Agent 工具放前端还是后端?先看展示与存储是否需要一致

会影响前端状态、又需要让页面展示与后端存储保持一致的操作,应调用前端业务 action。通过 Store 复用状态变化和保存流程,决定 Agent 工具的前后端边界。

给 Agent 增加一个工具时,有一个问题很容易被跳过去:这个工具应该在前端执行,还是在后端执行?

我的判断是:会影响前端状态,并且需要让前后端状态在展示、存储两个维度保持一致的操作,应该调用前端已有的业务 action。

比如,Agent 修改图表的指标配置。用户眼前的图表要变化,设置面板要显示新配置,后端也要保存对应的结果。只完成其中一项,都不能算这次修改完整结束。

这时应该复用前端业务 action,让 Agent 进入与用户手动修改相同的流程:执行校验、请求保存、处理返回结果,再让 Store 和 UI 呈现对应状态。

如果只查询数据供 Agent 分析,或者执行一个不需要同时维护前端状态的后台任务,就可以使用后端工具。纯粹的图表选择、表单填写等界面操作,自然也由前端处理。

重点在于那些同时跨越展示与存储的操作:不能因为需要写数据库,就直接把整个工具放在后端。

先看这次操作要同时维护哪些状态

“保存成功”和“页面显示正确”,是同一次业务操作的两个要求。

以修改指标配置为例,如果 Agent 直接调用后端工具保存,数据库里的配置变了,但前端 Store 可能还保留旧值。图表仍按旧配置绘制,设置面板也没有更新。用户接着手动保存时,还可能把旧值重新写回去。

这个问题不是少了一句“修改成功”的提示。真正需要统一的是页面正在使用的状态,以及后端实际保存的状态。

所以,判断工具归属时,我会先列出它会影响什么:

操作应走的入口原因
修改并保存当前图表的指标配置前端业务 action图表、设置面板和持久配置需要沿同一流程更新
修改页面正在展示的业务对象并保存前端业务 action编辑状态、保存结果和页面展示需要保持一致
切换图表周期、填入表单参数前端业务 action前端状态需要按已有交互规则变化
查询数据供 Agent 分析后端工具不需要同步改变前端业务状态
执行独立的后台计算后端工具执行过程与结果存储应脱离页面存在

最后两类操作的结果也可能显示在页面上。但“结果会被展示”,不等于“这次操作需要一起维护前端正在使用的业务状态”。后台任务进度可以通过已有订阅展示,不必因此把计算搬到前端。

涉及存储,也可以是前端工具

前端工具的含义,是从前端业务入口发起并协调这次操作。它完全可以调用后端接口。

例如,一个保存配置的 action 可以负责校验输入、设置保存中状态、调用接口,再用后端确认的结果更新 Store。接口失败时,则按已有约定保留待编辑值、恢复已确认值或显示错误。

具体采用哪一种交互方式,由业务本身决定。Agent 应复用这个约定,而不是另外设计一个版本。

后端仍然负责权限、业务约束和持久化。前端 action 负责把这次业务结果与前端状态连起来。所谓展示与存储一致,也不是浏览器和数据库瞬间完成一笔共同事务,而是保存中、保存成功、保存失败都有明确的状态,页面不能把未保存的值冒充成已保存结果。

因此,不能只按“有没有请求接口”“有没有写数据库”来区分前后端工具。更有用的问题是:绕过前端业务 action 后,会不会只改了后端记录,却留下没有同步的前端状态?

如果会,就应该让工具复用这个前端入口。

决定做前端工具后,Store 怎样连接 UI?

这需要前端先具备一个清楚的结构:组件负责展示和接收输入,业务操作从组件里抽出来,通过 Store 提供调用入口。

Store 可以理解为页面共用的一份状态,以及一组操作这些状态的方法。状态告诉 UI“现在是什么样”,action 告诉业务“要做什么”。计算规则和数据访问仍可分别放在领域模块、Repository 中,不必全塞进 Store。

例如,设置面板和图表订阅同一份指标配置。用户修改并保存配置时,业务 action 协调校验、保存请求和状态更新。后端确认结果后,Store 更新对应配置,面板与图表根据同一份状态响应。

Agent 接入后,工具也调用这个 action:

用户点击 ──→ 组件事件 ─────────┐

                         Store action

Agent ──→ 前端工具适配层 ──────┘

                    校验/设置处理中状态

                    调用后端接口保存

                    按成功或失败结果更新 Store

                         UI 订阅并响应

这就是前端工具、状态操作和 UI 之间的绑定关系:工具调用 action,action 更新状态,UI 订阅状态。

工具不必知道按钮位置,也不必自己修改 DOM。组件同样不必知道操作来自鼠标、键盘还是 Agent。它只需要响应同一份状态。

暴露的是 action,不是任意状态写入

“把周期字段改成周线”和“执行切换周线”,未必是一回事。

一次正常的周期切换,可能还要触发加载、处理请求失败、更新关联控件。如果 Agent 直接修改 Store 的内部字段,就可能绕过这些流程。

因此,应该让工具调用 setTimeframesetFormValues 这样的明确操作,而不是开放任意 setState

以下是关系示意,并非项目完整代码:

// UI 与前端工具复用同一个操作入口。
const saveConfiguration = (changes) => chartPort.setIndicators(changes);

// 用户入口
onSave = (changes) => saveConfiguration(changes);

// Agent 入口:先验证目标与参数,再调用
invokeFrontendTool = async ({ changes }) => {
  const receipt = await saveConfiguration(changes);
  return { receipt, state: chartPort.inspect() };
};

这样绑定的价值,是复用完整的行为。以后调整校验、加载流程或错误处理,两个入口都可以使用同一处修改,不需要分别维护“手动操作版”和“Agent 操作版”。

当前这套实现怎样开放 Store 操作

当前系统的 frontend_store 工具采用显式注册的操作接口,并提供三个步骤:

操作作用
list发现当前页面已挂载的可操作目标
inspect读取必要状态、可用 action、参数结构和可用性
invoke校验后调用对应的 Store 操作,返回回执和当前状态

目前图表开放 setTimeframedrawclearDrawingssetIndicatorsresetIndicators,回测表单开放 setFormValues。导航和服务端任务提交、重试仍使用已有的各自工具,并非所有能力都塞进 frontend_store

Agent 先发现目标,再知道这个目标允许做什么,最后调用。这里没有自动导出 Store 的全部方法,也没有让模型任意读取内部字段。

目标还要对应真实的页面实例。用户切换标的或关闭页面后,先前发现的目标可能已经失效,旧调用应该失败,不能悄悄修改另一个实例。

为什么这能省掉一套状态通知机制?

如果一个需要同时维护展示与存储的操作绕过前端,直接由后端工具保存,后端执行完以后,通常还要通知前端:哪些业务对象变了,你该更新哪些状态了。

前端接到消息,再判断对应对象、刷新 Store、处理未保存的编辑和迟到的结果。这样就多出了一条“后端变更状态 → 通知前端 → 前端同步展示”的链路。

通过前端工具调用业务 action,这次操作从一开始就在已有的修改、保存与状态更新流程里。UI 订阅 Store,存储走正常后端接口,Agent 和用户手动操作共用这条路径,不需要再为 Agent 增加一套“后端改完,再通知页面修正状态”的机制。

这里省掉的是额外的同步逻辑。工具调用如何送达浏览器、执行结果如何返回 Agent,仍然需要通信。后台任务进度、跨设备变更和其他用户的操作,也仍需要各自的同步机制。

同时,正常响应不等于所有工作瞬间完成。周期已切换时,行情可能还在加载;保存请求发出后,也可能失败。工具回执应该反映真实阶段,UI 则继续显示已有的加载或错误状态。

设计一个新工具时,按这个顺序判断

先写清楚工具完成后应当发生什么,再决定它放在哪里:

  1. 会不会影响前端业务状态? 看具体的配置、编辑内容和当前展示对象,而不只是有没有一条消息出现在页面上。
  2. 展示与存储是否需要一起保持一致? 如果需要,就调用前端已有的业务 action,让它协调状态更新与后端保存。
  3. 有哪些纯后端职责? 独立查询、后台计算和服务端校验继续留在后端,前端工具通过已有接口使用它们。
  4. UI 是否订阅同一份 Store 状态? 让按钮和工具调用同一个 action,让相关组件从同一份状态取得展示数据。
  5. 失败时两侧会怎样? 确认保存失败、请求中断或目标变化时,不会把未确认的状态当作已保存结果。

工具的前后端边界,要围绕完整的业务操作来划分。凡是会影响前端状态、又需要统一展示与存储结果的操作,就让 Agent 调用前端业务 action。Store 把这次操作接到 UI,已有后端接口负责保存,两种入口因此遵守同一套状态变化规则。

完 / 继续实践