From 01cd0ddc3261b108460da2d47e5767a1b143cbaf Mon Sep 17 00:00:00 2001 From: "everywhere.z" Date: Sat, 20 Jun 2026 00:48:15 +0800 Subject: [PATCH] =?UTF-8?q?feat(agent):=20=E9=87=8D=E5=86=99=20process()?= =?UTF-8?q?=20=E6=8E=A5=E5=85=A5=20ReActAgent=20=E5=8D=95=E4=BE=8B+Runtime?= =?UTF-8?q?Context=20(RC3=20=E9=9D=9E=E6=B5=81=E5=BC=8F)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task 2.3:把 Task 0 的 process() stub 替换为真实非流式实现。 - ReActAgentComponent.process():agentConfig() → ReactAgentFactory.getOrCreate → 解析 cid/akey 写回 slot → RuntimeContext(userId=cid, sessionId=akey) → 挂 ReActAgentContext(含 rc) 到 slot → agent.call(List.of(new UserMessage(userPrompt())), rc).block() → handleReply(reply) → finally 摘除 ctx。新增 runtimeContext()/workspaceRoot()。 cfg 一致性:全程复用 agentConfig(),保证 stateStore 与 model 同一份配置。 - ReActAgentContext:新增 RuntimeContext 字段 + getter/setter。 - LiveTestSupport.resetAgentSessionManager():改反射已删的 AgentSessionManagerHolder 为直接调 ReactAgentFactory.resetForTesting()(修复运行期 CNPE)。 - ProcessIntegrationTest:Spring Boot + CannedReplyModel(确定性 mock,无真实 LLM/凭据/不 skip), THEN(agent, recordReply) 端到端跑通,断言 success/回复流转/调用次数/conversationId。 不做流式(留 Task 6.1);不接 middleware/permission/skill。 业务侧受保护方法签名不变。 Co-Authored-By: Claude Fable 5 --- .../agent/component/ReActAgentComponent.java | 89 +++++++++++++++--- .../agent/component/ReActAgentContext.java | 18 ++++ .../test/agent/support/LiveTestSupport.java | 28 +++--- .../test/agent/v2/CannedReplyModel.java | 51 ++++++++++ .../test/agent/v2/ProcessAgentCmp.java | 80 ++++++++++++++++ .../test/agent/v2/ProcessIntegrationTest.java | 92 +++++++++++++++++++ .../test/agent/v2/ProcessRecordReplyCmp.java | 22 +++++ .../feature/process/application.properties | 11 +++ .../resources/feature/process/flow.el.xml | 6 ++ 9 files changed, 371 insertions(+), 26 deletions(-) create mode 100644 liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/CannedReplyModel.java create mode 100644 liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/ProcessAgentCmp.java create mode 100644 liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/ProcessIntegrationTest.java create mode 100644 liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/ProcessRecordReplyCmp.java create mode 100644 liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/feature/process/application.properties create mode 100644 liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/feature/process/flow.el.xml diff --git a/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/component/ReActAgentComponent.java b/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/component/ReActAgentComponent.java index 8c533d7c2..e64dd0d9b 100644 --- a/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/component/ReActAgentComponent.java +++ b/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/component/ReActAgentComponent.java @@ -1,29 +1,32 @@ package com.yomahub.liteflow.agent.component; import com.yomahub.liteflow.agent.exception.AgentConfigException; -import com.yomahub.liteflow.agent.exception.AgentInvocationException; import com.yomahub.liteflow.agent.model.ModelSpec; import com.yomahub.liteflow.core.NodeComponent; import com.yomahub.liteflow.property.LiteflowConfigGetter; import com.yomahub.liteflow.property.agent.AgentConfig; import com.yomahub.liteflow.slot.Slot; import com.yomahub.liteflow.util.ConversationIdGenerator; +import io.agentscope.core.ReActAgent; +import io.agentscope.core.agent.RuntimeContext; import io.agentscope.core.hook.Hook; import io.agentscope.core.message.Msg; +import io.agentscope.core.message.UserMessage; import io.agentscope.core.model.Model; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import java.util.Map; /** * 封装 agentscope agent 的 LiteFlow 抽象组件。 * - *

状态:v2 迁移进行中(Task 0 green-commit 基底)。 - * 本类已删除所有依赖 agentscope 1.0 已删类型(session 管理 / skill / 工具 / - * 流式事件桥接)的实现,{@link #process()} 当前抛出 - * {@link AgentInvocationException}。后续 Task 2.x 会基于 v2 - * {@code HarnessAgent} 重建 {@code process()}。受保护方法签名保持不变, - * 子类实现与业务侧代码无需改动。 + *

状态:v2(RC3)非流式实现。 + * {@link #process()} 通过 {@link ReactAgentFactory#getOrCreate} 取按组件类缓存的无状态 + * {@code ReActAgent} 单例,按 {@code (conversationId, agentKey)} 构造 + * {@code RuntimeContext(userId, sessionId)},调 {@code agent.call(List<Msg>, RuntimeContext).block()} + * 拿到回复后交给 {@link #handleReply(Msg)} 写回 slot。流式({@code streamEvents})留给 Task 6.1。 * *

子类必须提供 {@link #model()}、{@link #systemPrompt()} 和 {@link #userPrompt()}。 * 可选覆写方法用于自定义工具、钩子和生命周期回调;当前均为空实现或读 @@ -34,8 +37,10 @@ import java.util.Map; * *

会话标识被拆为两层: *

* *

{@link #process()} 方法被声明为 {@code final},由框架统一保证。 @@ -209,14 +214,72 @@ public abstract class ReActAgentComponent extends NodeComponent { /* ===== 框架 final 执行体 ===== */ /** - * v2 迁移期 stub。当前抛出 {@link AgentInvocationException}, - * 由 Task 2.x 基于 v2 {@code HarnessAgent} 重建。 + * 端到端非流式执行(RC3)。 + * + *

流程: + *

    + *
  1. 取全局 {@link #agentConfig()}(与 {@code buildModel()} 读取的是同一份配置, + * 保证 stateStore 与 model 配置一致——Task 2.2 review 的 cfg 一致性要求)。
  2. + *
  3. 用 {@link ReactAgentFactory#getOrCreate} 取按组件类缓存的无状态 + * {@link ReActAgent} 单例。
  4. + *
  5. 解析 {@code conversationId} / {@code agentKey},写回 slot,并构造对应的 + * {@link RuntimeContext}({@code userId=conversationId, sessionId=agentKey})。
  6. + *
  7. 把 {@link ReActAgentContext}(含 runtimeContext)挂到 slot attachment 上, + * 使 {@link #ctx()} 在 {@code agent.call(...)} 触发的工具回调内可用。
  8. + *
  9. 调 {@code agent.call(List.of(new UserMessage(userPrompt())), rc).block()} + * 阻塞拿回复,交 {@link #handleReply(Msg)} 处理。
  10. + *
  11. {@code finally} 中摘除 ctx,避免跨 invocation 悬挂引用。
  12. + *
+ * + *

非流式:本方法始终用 {@code call(...)}。流式({@code streamEvents})桥接 + * 由 Task 6.1 单独实现,不在本方法范围内。 * *

签名保持 {@code final},与 1.0 一致。 */ @Override public final void process() { - throw new AgentInvocationException( - "ReActAgentComponent v2 migration in progress; process() is rebuilt in Task 2.3"); + AgentConfig cfg = agentConfig(); + ReActAgent agent = ReactAgentFactory.getOrCreate(this, cfg); + + Slot slot = getSlot(); + String cid = resolveConversationId(); + slot.setConversationId(cid); + String akey = agentKey(); + RuntimeContext rc = runtimeContext(cid, akey); + + ReActAgentContext ctx = new ReActAgentContext(slot, cid, akey, workspaceRoot(cfg, cid)); + ctx.setRuntimeContext(rc); + slot.setAttachment(ctxKey(), ctx); + try { + Msg reply = agent.call(List.of(new UserMessage(userPrompt())), rc).block(); + handleReply(reply); + } finally { + slot.removeAttachment(ctxKey()); + } + } + + /** + * 构造本次调用的 {@link RuntimeContext}。标识映射(spec §4.1): + * {@code userId = conversationId}、{@code sessionId = agentKey}。 + * + *

声明为 {@code protected} 便于子类在需要时覆写(例如注入额外 typed extras)。 + */ + protected RuntimeContext runtimeContext(String conversationId, String agentKey) { + return RuntimeContext.builder().userId(conversationId).sessionId(agentKey).build(); + } + + /** + * 返回本次调用使用的 workspace 根目录(仅用于 {@link ReActAgentContext#getWorkspaceDir()} + * 暴露给业务/未来工具;RC3 核心 {@code ReActAgent} 无 {@code .workspace()}, + * 此值传给 agent builder)。 + * + *

RC3-core 暂用 cfg 配置的单根目录;workspace 用户分桶由后续 GA/HarnessAgent 处理。 + */ + protected Path workspaceRoot(AgentConfig cfg, String conversationId) { + String root = cfg.getWorkspace().getRoot(); + if (root == null || root.isBlank()) { + return Paths.get(System.getProperty("java.io.tmpdir"), "liteflow-agent-workspace"); + } + return Paths.get(root); } } diff --git a/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/component/ReActAgentContext.java b/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/component/ReActAgentContext.java index 09ad8a509..535e7f5af 100644 --- a/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/component/ReActAgentContext.java +++ b/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/component/ReActAgentContext.java @@ -1,6 +1,7 @@ package com.yomahub.liteflow.agent.component; import com.yomahub.liteflow.slot.Slot; +import io.agentscope.core.agent.RuntimeContext; import io.agentscope.core.model.ChatUsage; import java.nio.file.Path; @@ -30,6 +31,7 @@ public class ReActAgentContext { private final String conversationId; private final String agentKey; private final Path workspaceDir; + private RuntimeContext runtimeContext; public ReActAgentContext(Slot slot, String conversationId, String agentKey, Path workspaceDir) { this.slot = Objects.requireNonNull(slot, "slot"); @@ -46,6 +48,22 @@ public class ReActAgentContext { public Path getWorkspaceDir() { return workspaceDir; } + /** + * 本次 {@code process()} 调用注入给底层 {@code ReActAgent.call(...)} 的 + * {@link RuntimeContext}。由 {@link ReActAgentComponent#process()} 在构造 ctx 后、 + * 调 {@code agent.call(...)} 前设置。 + * + *

标识映射:{@code runtimeContext.userId = conversationId}、 + * {@code runtimeContext.sessionId = agentKey}(spec §4.1)。 + * + * @return 本次调用使用的 {@link RuntimeContext};在 {@code process()} 生命周期外为 {@code null} + */ + public RuntimeContext getRuntimeContext() { return runtimeContext; } + + public void setRuntimeContext(RuntimeContext runtimeContext) { + this.runtimeContext = runtimeContext; + } + /** * 由框架注入:本次 {@code process()} 调用使用的 token 累加 hook。 * diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/support/LiveTestSupport.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/support/LiveTestSupport.java index 617af0d6b..a73afc5b5 100644 --- a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/support/LiveTestSupport.java +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/support/LiveTestSupport.java @@ -1,5 +1,6 @@ package com.yomahub.liteflow.test.agent.support; +import com.yomahub.liteflow.agent.component.ReactAgentFactory; import com.yomahub.liteflow.agent.model.ModelSpec; import com.yomahub.liteflow.agent.openai.OpenAICompatible; import com.yomahub.liteflow.property.LiteflowConfig; @@ -7,18 +8,17 @@ import com.yomahub.liteflow.property.agent.AgentConfig; import com.yomahub.liteflow.property.agent.PlatformCredential; import org.junit.jupiter.api.Assumptions; -import java.lang.reflect.Method; - /** * 整个模块唯一共享的「凭据/skip/重置」插管。 * - *

按用户约定:不同 package 之间只共享这一层(凭据解析、无 key 即 skip、SessionManager 重置); + *

按用户约定:不同 package 之间只共享这一层(凭据解析、无 key 即 skip、agent 运行时缓存重置); * 其余 agent 组件、辅助节点、探针、flow xml、application.properties 一律每个 package 各自冗余。 * *

提供: *