mirror of https://gitee.com/dromara/liteFlow
feat(agent): 重写 process() 接入 ReActAgent 单例+RuntimeContext (RC3 非流式)
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 <noreply@anthropic.com>
This commit is contained in:
parent
c73b261658
commit
01cd0ddc32
|
|
@ -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 抽象组件。
|
||||
*
|
||||
* <p><b>状态:v2 迁移进行中(Task 0 green-commit 基底)。</b>
|
||||
* 本类已删除所有依赖 agentscope 1.0 已删类型(session 管理 / skill / 工具 /
|
||||
* 流式事件桥接)的实现,{@link #process()} 当前抛出
|
||||
* {@link AgentInvocationException}。后续 Task 2.x 会基于 v2
|
||||
* {@code HarnessAgent} 重建 {@code process()}。受保护方法签名保持不变,
|
||||
* 子类实现与业务侧代码无需改动。
|
||||
* <p><b>状态:v2(RC3)非流式实现。</b>
|
||||
* {@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。
|
||||
*
|
||||
* <p>子类必须提供 {@link #model()}、{@link #systemPrompt()} 和 {@link #userPrompt()}。
|
||||
* 可选覆写方法用于自定义工具、钩子和生命周期回调;当前均为空实现或读
|
||||
|
|
@ -34,8 +37,10 @@ import java.util.Map;
|
|||
*
|
||||
* <p>会话标识被拆为两层:
|
||||
* <ul>
|
||||
* <li>{@code conversationId}:业务/对话维度,由 {@link #resolveConversationId()} 解析。</li>
|
||||
* <li>{@code agentKey}:组件维度,默认 {@code nodeId},隔离同对话内不同 agent。</li>
|
||||
* <li>{@code conversationId}:业务/对话维度,由 {@link #resolveConversationId()} 解析,
|
||||
* 映射为 {@code RuntimeContext.userId}。</li>
|
||||
* <li>{@code agentKey}:组件维度,默认 {@code nodeId},隔离同对话内不同 agent,
|
||||
* 映射为 {@code RuntimeContext.sessionId}。</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>{@link #process()} 方法被声明为 {@code final},由框架统一保证。
|
||||
|
|
@ -209,14 +214,72 @@ public abstract class ReActAgentComponent extends NodeComponent {
|
|||
/* ===== 框架 final 执行体 ===== */
|
||||
|
||||
/**
|
||||
* <b>v2 迁移期 stub。</b>当前抛出 {@link AgentInvocationException},
|
||||
* 由 Task 2.x 基于 v2 {@code HarnessAgent} 重建。
|
||||
* 端到端非流式执行(RC3)。
|
||||
*
|
||||
* <p>流程:
|
||||
* <ol>
|
||||
* <li>取全局 {@link #agentConfig()}(与 {@code buildModel()} 读取的是同一份配置,
|
||||
* 保证 stateStore 与 model 配置一致——Task 2.2 review 的 cfg 一致性要求)。</li>
|
||||
* <li>用 {@link ReactAgentFactory#getOrCreate} 取按组件类缓存的无状态
|
||||
* {@link ReActAgent} 单例。</li>
|
||||
* <li>解析 {@code conversationId} / {@code agentKey},写回 slot,并构造对应的
|
||||
* {@link RuntimeContext}({@code userId=conversationId, sessionId=agentKey})。</li>
|
||||
* <li>把 {@link ReActAgentContext}(含 runtimeContext)挂到 slot attachment 上,
|
||||
* 使 {@link #ctx()} 在 {@code agent.call(...)} 触发的工具回调内可用。</li>
|
||||
* <li>调 {@code agent.call(List.of(new UserMessage(userPrompt())), rc).block()}
|
||||
* 阻塞拿回复,交 {@link #handleReply(Msg)} 处理。</li>
|
||||
* <li>{@code finally} 中摘除 ctx,避免跨 invocation 悬挂引用。</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p><b>非流式</b>:本方法始终用 {@code call(...)}。流式({@code streamEvents})桥接
|
||||
* 由 Task 6.1 单独实现,不在本方法范围内。
|
||||
*
|
||||
* <p>签名保持 {@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}。
|
||||
*
|
||||
* <p>声明为 {@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()},
|
||||
* 此值<b>不</b>传给 agent builder)。
|
||||
*
|
||||
* <p>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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(...)} 前设置。
|
||||
*
|
||||
* <p>标识映射:{@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。
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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/重置」插管。
|
||||
*
|
||||
* <p>按用户约定:不同 package 之间只共享这一层(凭据解析、无 key 即 skip、SessionManager 重置);
|
||||
* <p>按用户约定:不同 package 之间只共享这一层(凭据解析、无 key 即 skip、agent 运行时缓存重置);
|
||||
* 其余 agent 组件、辅助节点、探针、flow xml、application.properties 一律每个 package 各自冗余。
|
||||
*
|
||||
* <p>提供:
|
||||
* <ul>
|
||||
* <li>{@link #resetAgentSessionManager()}:反射重置 ReActAgentComponent 内部单例 SessionManager,
|
||||
* 避免跨测试类的全局静态状态互相污染;</li>
|
||||
* <li>{@link #resetAgentSessionManager()}:重置 {@link ReactAgentFactory} 的进程内
|
||||
* {@code ReActAgent} 单例缓存(1.0 反射 {@code AgentSessionManagerHolder} 已随 Task 0
|
||||
* 删除——会运行期 CNPE),避免跨测试类的全局静态状态互相污染;</li>
|
||||
* <li>{@link #compatibleCustomModel()}:功能测试统一用的 OpenAI 兼容自定义模型描述符;</li>
|
||||
* <li>各平台的「装凭据或 skip」方法:把真实 apikey/baseUrl 从环境变量装入 AgentConfig,
|
||||
* 缺失即 {@code Assumptions.assumeTrue} 跳过当前测试。</li>
|
||||
|
|
@ -36,15 +36,17 @@ public final class LiveTestSupport {
|
|||
}
|
||||
|
||||
/**
|
||||
* 强行重置 ReActAgentComponent 内部缓存的单例 SessionManager。
|
||||
* 否则跨 test class 的 JVM 静态状态复用时,前一个测试遗留的 Session 会污染当前断言。
|
||||
* 强行重置 {@link ReactAgentFactory} 内部缓存的 {@code ReActAgent} 单例表。
|
||||
* 否则跨 test class 的 JVM 静态状态复用时,前一个测试遗留的 agent(绑定旧 model/stateStore)
|
||||
* 会污染当前断言。
|
||||
*
|
||||
* <p>方法名保留 {@code resetAgentSessionManager} 以兼容现有调用点(如
|
||||
* {@code BaseAgentLiveTest.resetAgentRuntime})——RC3 会话状态已不在独立 SessionManager,
|
||||
* 而由 {@code AgentStateStore}(按 {@code (userId, sessionId)} 寻址)承担,重置单例缓存即可
|
||||
* 让下一个测试用全新的 cfg 重新构建 agent。
|
||||
*/
|
||||
public static void resetAgentSessionManager() throws Exception {
|
||||
Class<?> holder = Class.forName(
|
||||
"com.yomahub.liteflow.agent.component.ReActAgentComponent$AgentSessionManagerHolder");
|
||||
Method reset = holder.getDeclaredMethod("resetForTesting");
|
||||
reset.setAccessible(true);
|
||||
reset.invoke(null);
|
||||
public static void resetAgentSessionManager() {
|
||||
ReactAgentFactory.resetForTesting();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
package com.yomahub.liteflow.test.agent.v2;
|
||||
|
||||
import io.agentscope.core.message.ContentBlock;
|
||||
import io.agentscope.core.message.Msg;
|
||||
import io.agentscope.core.message.TextBlock;
|
||||
import io.agentscope.core.model.ChatResponse;
|
||||
import io.agentscope.core.model.GenerateOptions;
|
||||
import io.agentscope.core.model.ToolSchema;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 确定性的「罐头回复」{@link io.agentscope.core.model.Model} 实现,专供 v2
|
||||
* {@code process()} 集成测试在<b>无网络、无真实 LLM</b> 的前提下端到端跑通。
|
||||
*
|
||||
* <p>{@link #stream(List, List, GenerateOptions)} 无论输入如何,固定 emit 一个
|
||||
* {@link ChatResponse}(单 {@link TextBlock} + {@code finishReason="stop"})。
|
||||
* ReActAgent 在第一轮拿到带 stop 的文本回复即收敛,{@code call(...).block()} 返回该回复,
|
||||
* 使 {@code ReActAgentComponent.process()} 能完整走通「factory 取 agent → call → handleReply」
|
||||
* 链路而无需任何凭据。
|
||||
*
|
||||
* <p>实现 {@link io.agentscope.core.model.Model} 全部抽象方法({@code stream} + {@code getModelName});
|
||||
* {@code supportsNativeStructuredOutput} 走 default。无状态、线程安全。
|
||||
*/
|
||||
final class CannedReplyModel implements io.agentscope.core.model.Model {
|
||||
|
||||
static final String CANNED_REPLY = "[canned-reply] hello from mock model";
|
||||
|
||||
private final String modelName;
|
||||
|
||||
CannedReplyModel(String modelName) {
|
||||
this.modelName = modelName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(
|
||||
List<Msg> messages, List<ToolSchema> tools, GenerateOptions options) {
|
||||
ChatResponse resp = ChatResponse.builder()
|
||||
.id("canned-" + System.nanoTime())
|
||||
.content(List.<ContentBlock>of(TextBlock.builder().text(CANNED_REPLY).build()))
|
||||
.finishReason("stop")
|
||||
.build();
|
||||
return Flux.just(resp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModelName() {
|
||||
return modelName;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package com.yomahub.liteflow.test.agent.v2;
|
||||
|
||||
import com.yomahub.liteflow.agent.component.ReActAgentComponent;
|
||||
import com.yomahub.liteflow.agent.model.ModelSpec;
|
||||
import com.yomahub.liteflow.property.agent.AgentConfig;
|
||||
import io.agentscope.core.model.Model;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* {@code ProcessIntegrationTest} 用的最小 ReActAgentComponent 子类:
|
||||
* <ul>
|
||||
* <li>{@link #buildModel()} escape hatch 返回 {@link CannedReplyModel},绕开真实 LLM;
|
||||
* ({@code model()} 仍需提供,但 buildModel() 覆写后不再走 {@code ModelSpec.resolve}。)</li>
|
||||
* <li>关闭 shell / workspace 工具,最小化 toolkit;</li>
|
||||
* <li>记录 userPrompt/handleReply 调用次数,供断言。</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Component("processAgent")
|
||||
public class ProcessAgentCmp extends ReActAgentComponent {
|
||||
|
||||
public static final AtomicInteger USER_PROMPT_COUNT = new AtomicInteger();
|
||||
public static final AtomicInteger HANDLE_REPLY_COUNT = new AtomicInteger();
|
||||
|
||||
public static void reset() {
|
||||
USER_PROMPT_COUNT.set(0);
|
||||
HANDLE_REPLY_COUNT.set(0);
|
||||
}
|
||||
|
||||
/** 仅满足抽象方法签名;buildModel() 被覆写后这里不会被调用。 */
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
protected ModelSpec model() {
|
||||
return new ModelSpec() {
|
||||
@Override
|
||||
public Model resolve(AgentConfig c) {
|
||||
return new CannedReplyModel("canned");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Model buildModel() {
|
||||
return new CannedReplyModel("canned");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String systemPrompt() {
|
||||
return "test system prompt";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String userPrompt() {
|
||||
USER_PROMPT_COUNT.incrementAndGet();
|
||||
Object reqData = getSlot().getChainReqData(getSlot().getChainId());
|
||||
return reqData == null ? "hi" : reqData.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean enableShellTool() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean enableWorkspaceFileTools() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean enableReActLogging() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleReply(io.agentscope.core.message.Msg reply) {
|
||||
HANDLE_REPLY_COUNT.incrementAndGet();
|
||||
super.handleReply(reply);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package com.yomahub.liteflow.test.agent.v2;
|
||||
|
||||
import com.yomahub.liteflow.agent.component.ReActAgentComponent;
|
||||
import com.yomahub.liteflow.flow.LiteflowResponse;
|
||||
import com.yomahub.liteflow.property.LiteflowConfig;
|
||||
import com.yomahub.liteflow.test.agent.support.LiveTestSupport;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* Task 2.3 端到端集成测试:验证重写后的 {@link ReActAgentComponent#process()}
|
||||
* 在 Spring Boot 上下文 + 最简 EL chain 下能完整跑通「factory 取无状态 ReActAgent 单例 →
|
||||
* 按 (conversationId, agentKey) 构造 RuntimeContext → agent.call(UserMessage, rc).block() →
|
||||
* handleReply 写回 slot.responseData」链路。
|
||||
*
|
||||
* <p><b>无真实 LLM</b>:{@link ProcessAgentCmp} 覆写 {@code buildModel()} 返回
|
||||
* {@link CannedReplyModel}(确定性罐头回复),整个测试不需要任何 apikey/baseUrl,
|
||||
* 不会因缺失凭据被 {@code Assumptions.assumeTrue} 跳过。
|
||||
*
|
||||
* <p>断言:
|
||||
* <ul>
|
||||
* <li>{@code response.isSuccess()} 为 true(process() 不再抛 stub 异常);</li>
|
||||
* <li>下游 {@code processRecordReply} 节点拿到非空回复(即 CANNED_REPLY),证明 responseData
|
||||
* 流转正常;</li>
|
||||
* <li>{@code userPrompt()} / {@code handleReply(...)} 各被调用一次;</li>
|
||||
* <li>{@code response.getConversationId()} 非空(process() 写回 slot 后透出)。</li>
|
||||
* </ul>
|
||||
*/
|
||||
@TestPropertySource("classpath:/feature/process/application.properties")
|
||||
@SpringBootTest(classes = ProcessIntegrationTest.class)
|
||||
@EnableAutoConfiguration
|
||||
@ComponentScan("com.yomahub.liteflow.test.agent.v2")
|
||||
public class ProcessIntegrationTest {
|
||||
|
||||
@Resource
|
||||
private com.yomahub.liteflow.core.FlowExecutor flowExecutor;
|
||||
|
||||
@Resource
|
||||
private LiteflowConfig liteflowConfig;
|
||||
|
||||
@BeforeEach
|
||||
public void resetRuntime() {
|
||||
// 清空 ReactAgentFactory 进程内单例缓存,保证本类用本测试的 cfg 重新构建 agent。
|
||||
LiveTestSupport.resetAgentSessionManager();
|
||||
ProcessAgentCmp.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessRunsEndToEndWithMockModel() {
|
||||
String prompt = "ping";
|
||||
LiteflowResponse response = flowExecutor.execute2Resp("processAgentChain", prompt);
|
||||
|
||||
Assertions.assertTrue(response.isSuccess(),
|
||||
"chain failed: " + (response.getCause() == null
|
||||
? "<no cause>"
|
||||
: toString(response.getCause())));
|
||||
|
||||
// 回复通过默认 handleReply 写入 responseData,并由 processRecordReply 复制到 output。
|
||||
Object reply = response.getSlot().getOutput(ProcessRecordReplyCmp.NODE_ID);
|
||||
Assertions.assertNotNull(reply, "agent reply must reach processRecordReply node");
|
||||
Assertions.assertEquals(CannedReplyModel.CANNED_REPLY, reply.toString(),
|
||||
"downstream node must observe the canned reply");
|
||||
|
||||
// userPrompt / handleReply 各调用一次(证明 process() 真正调用了 agent 并处理回复)。
|
||||
Assertions.assertEquals(1, ProcessAgentCmp.USER_PROMPT_COUNT.get());
|
||||
Assertions.assertEquals(1, ProcessAgentCmp.HANDLE_REPLY_COUNT.get());
|
||||
|
||||
// conversationId 由 process() 写回 slot 后经 LiteflowResponse 透出。
|
||||
Assertions.assertNotNull(response.getConversationId(),
|
||||
"conversationId must be propagated to LiteflowResponse");
|
||||
}
|
||||
|
||||
private static String toString(Throwable t) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Throwable cur = t;
|
||||
while (cur != null) {
|
||||
sb.append(cur.getClass().getSimpleName()).append(": ").append(cur.getMessage());
|
||||
cur = cur.getCause();
|
||||
if (cur != null) {
|
||||
sb.append(" || caused by: ");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.yomahub.liteflow.test.agent.v2;
|
||||
|
||||
import com.yomahub.liteflow.core.NodeComponent;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 把 Agent 写入 slot 的 responseData 复制到本节点 output,集成测试侧通过
|
||||
* {@code slot.getOutput(NODE_ID)} 拿到回复,验证回复确实流转到下游普通节点。
|
||||
*/
|
||||
@Component("processRecordReply")
|
||||
public class ProcessRecordReplyCmp extends NodeComponent {
|
||||
|
||||
public static final String NODE_ID = "processRecordReply";
|
||||
|
||||
@Override
|
||||
public void process() {
|
||||
Object reply = getSlot().getResponseData();
|
||||
if (reply != null) {
|
||||
getSlot().setOutput(NODE_ID, reply);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
liteflow.rule-source=feature/process/flow.el.xml
|
||||
liteflow.print-banner=false
|
||||
|
||||
# 最小 agent 配置:workspace 指向 tmp(CannedReplyModel 不实际写盘);memory=NONE;
|
||||
# shell=disabled;skills 关闭。本测试不接触真实 LLM。
|
||||
liteflow.agent.workspace.root=target/wk/v2_process_test
|
||||
liteflow.agent.workspace.auto-create=true
|
||||
liteflow.agent.shell.mode=disabled
|
||||
liteflow.agent.defaults.max-iterations=3
|
||||
liteflow.agent.logging.react-enabled=false
|
||||
liteflow.agent.skills.enabled=false
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<flow>
|
||||
<chain name="processAgentChain">
|
||||
THEN(processAgent, processRecordReply);
|
||||
</chain>
|
||||
</flow>
|
||||
Loading…
Reference in New Issue