From 4c1218f6cdcbd912e355577e0b0b038b8e57141f Mon Sep 17 00:00:00 2001 From: "everywhere.z" Date: Sat, 9 May 2026 14:16:39 +0800 Subject: [PATCH] feat(core): add generic attachment API to Slot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为后续 per-invocation 插件上下文挂载提供通用 KV 入口。 复用已有 metaDataMap,包含 set/get/has/remove 四个 public 方法。 Co-Authored-By: Claude Opus 4.7 --- .../java/com/yomahub/liteflow/slot/Slot.java | 40 +++++++++++++ .../test/slot/SlotAttachmentTest.java | 59 +++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 liteflow-core/src/test/java/com/yomahub/liteflow/test/slot/SlotAttachmentTest.java diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/slot/Slot.java b/liteflow-core/src/main/java/com/yomahub/liteflow/slot/Slot.java index da26b6d8e..1e18d8b65 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/slot/Slot.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/slot/Slot.java @@ -74,6 +74,8 @@ public class Slot { private static final String REQUEST_ID = "_req_id"; + private static final String CONVERSATION_ID = "_conversation_id"; + private static final String EXCEPTION = "_exception"; private static final String SUB_EXCEPTION_PREFIX = "_sub_exception_"; @@ -143,6 +145,29 @@ public class Slot { metaDataMap.put(key, t); } + /** + * 通用挂载点:写入一个由调用方约定 key 的对象。 + * + * @param key 调用方负责选用具有充分前缀的 key,避免与 Slot 内部保留 key 冲突 + * @throws com.yomahub.liteflow.exception.NullParamException value 为 null 时抛出 + */ + public void setAttachment(String key, T value) { + putMetaDataMap(key, value); + } + + @SuppressWarnings("unchecked") + public T getAttachment(String key) { + return (T) metaDataMap.get(key); + } + + public boolean hasAttachment(String key) { + return metaDataMap.containsKey(key); + } + + public void removeAttachment(String key) { + metaDataMap.remove(key); + } + public T getInput(String nodeId) { return (T) metaDataMap.get(NODE_INPUT_PREFIX + nodeId); } @@ -466,6 +491,21 @@ public class Slot { return (String) metaDataMap.get(REQUEST_ID); } + /** + * 设置当前 chain 执行的会话标识。 + * + *

用于 ReAct Agent 等需要在多个组件之间共享同一会话上下文的场景: + * 同 chain 内首个 agent 解析后写回 slot,后续 agent 直接复用,从而保证 + * 整条链路属于同一段对话(共享 workspace、各 agent 的记忆按 nodeId 分桶)。 + */ + public void setConversationId(String conversationId) { + putMetaDataMap(CONVERSATION_ID, conversationId); + } + + public String getConversationId() { + return (String) metaDataMap.get(CONVERSATION_ID); + } + public Deque getExecuteSteps() { return executeSteps; } diff --git a/liteflow-core/src/test/java/com/yomahub/liteflow/test/slot/SlotAttachmentTest.java b/liteflow-core/src/test/java/com/yomahub/liteflow/test/slot/SlotAttachmentTest.java new file mode 100644 index 000000000..f20d1c883 --- /dev/null +++ b/liteflow-core/src/test/java/com/yomahub/liteflow/test/slot/SlotAttachmentTest.java @@ -0,0 +1,59 @@ +package com.yomahub.liteflow.test.slot; + +import com.yomahub.liteflow.slot.Slot; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SlotAttachmentTest { + + @Test + public void setAndGetAttachment_roundTrip() { + Slot slot = new Slot(); + slot.setAttachment("foo", "bar"); + String v = slot.getAttachment("foo"); + assertEquals("bar", v); + } + + @Test + public void getAttachment_missingKey_returnsNull() { + Slot slot = new Slot(); + Object v = slot.getAttachment("absent"); + assertNull(v); + } + + @Test + public void hasAttachment_reportsPresence() { + Slot slot = new Slot(); + assertFalse(slot.hasAttachment("k")); + slot.setAttachment("k", 1); + assertTrue(slot.hasAttachment("k")); + } + + @Test + public void removeAttachment_clearsValue() { + Slot slot = new Slot(); + slot.setAttachment("k", 1); + slot.removeAttachment("k"); + assertFalse(slot.hasAttachment("k")); + assertNull(slot.getAttachment("k")); + } + + @Test + public void setAttachment_nullValue_throws() { + Slot slot = new Slot(); + assertThrows(RuntimeException.class, () -> slot.setAttachment("k", null)); + } + + @Test + public void getAttachment_genericTypeInference() { + Slot slot = new Slot(); + slot.setAttachment("num", 42); + Integer n = slot.getAttachment("num"); + assertEquals(42, n); + } +}