feat(core): add generic attachment API to Slot

为后续 per-invocation 插件上下文挂载提供通用 KV 入口。
复用已有 metaDataMap,包含 set/get/has/remove 四个 public 方法。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
everywhere.z 2026-05-09 14:16:39 +08:00
parent c31e279560
commit 4c1218f6cd
2 changed files with 99 additions and 0 deletions

View File

@ -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 <T> void setAttachment(String key, T value) {
putMetaDataMap(key, value);
}
@SuppressWarnings("unchecked")
public <T> 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> 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 执行的会话标识
*
* <p>用于 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<CmpStep> getExecuteSteps() {
return executeSteps;
}

View File

@ -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);
}
}