mirror of
https://github.com/slhaf/Partner.git
synced 2026-05-12 08:43:02 +08:00
refactor(memory): rename MemoryUpdater into MemoryRecallProfileExtractor, and create prompt
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
package work.slhaf.partner.module.memory.updater;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule;
|
||||
import work.slhaf.partner.framework.agent.factory.component.annotation.Init;
|
||||
import work.slhaf.partner.framework.agent.factory.component.annotation.InjectModule;
|
||||
import work.slhaf.partner.framework.agent.model.ActivateModel;
|
||||
import work.slhaf.partner.framework.agent.model.pojo.Message;
|
||||
import work.slhaf.partner.framework.agent.support.Result;
|
||||
import work.slhaf.partner.module.TaskBlock;
|
||||
import work.slhaf.partner.module.communication.AfterRolling;
|
||||
import work.slhaf.partner.module.communication.AfterRollingRegistry;
|
||||
import work.slhaf.partner.module.communication.RollingResult;
|
||||
import work.slhaf.partner.module.memory.pojo.ActivationProfile;
|
||||
import work.slhaf.partner.module.memory.runtime.MemoryRuntime;
|
||||
import work.slhaf.partner.module.memory.updater.summarizer.entity.MemoryTopicResult;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MemoryRecallProfileExtractor extends AbstractAgentModule.Standalone implements AfterRolling, ActivateModel {
|
||||
|
||||
private static final String MODULE_PROMPT = """
|
||||
你负责在新的 memory slice 生成后,为该切片提取用于后续记忆索引与召回的主题信息。你的任务不是总结整段对话,也不是判断当前要召回什么记忆,而是针对“这一个新产生的记忆切片”给出:
|
||||
- 它最适合挂载到哪个主题路径;
|
||||
- 它还可以关联哪些 related topic paths;
|
||||
- 它在后续召回时的三个基础特征权重。
|
||||
|
||||
你会收到一条任务消息,其中包含:
|
||||
- current_topic_tree:当前已有的记忆主题树结构;
|
||||
- slice_summary:当前 memory slice 的摘要;
|
||||
- message:该 memory slice 对应的原始消息片段,按发生顺序给出。
|
||||
|
||||
你的任务:
|
||||
- 为当前这一个 memory slice 选择一个主主题路径 topicPath;
|
||||
- 视需要补充少量 relatedTopicPaths;
|
||||
- 给出 activationProfile,其中包含:
|
||||
- activationWeight
|
||||
- diffusionWeight
|
||||
- contextIndependenceWeight
|
||||
|
||||
总体原则:
|
||||
- 你评估的是“当前这一个切片应该如何被索引和召回”,不是在做整轮对话总结。
|
||||
- slice_summary 与 message 要结合起来看;如果 summary 过于抽象,应以 message 体现出的实际讨论内容为准。
|
||||
- topicPath 应尽量对齐 current_topic_tree 已有的节点表达、层级关系与命名习惯。
|
||||
- 如果现有主题树中已有可匹配路径,优先复用,不要随意发明近义重复路径。
|
||||
- 如果当前切片只能稳定落到较上层主题,就输出较短路径;不要为了显得具体而捏造下层节点。
|
||||
- relatedTopicPaths 只用于表达这条切片还可能从哪些相邻主题方向被召回,不要泛化扩散。
|
||||
- 如果没有足够稳定的 related 方向,可以返回空列表。
|
||||
|
||||
关于 topicPath:
|
||||
- topicPath 表示这条切片的主挂载主题。
|
||||
- topicPath 必须是纯路径文本,使用 `->` 连接层级,例如 A->B->C。
|
||||
- topicPath 应描述这条切片讨论的核心议题,而不是宽泛标签。
|
||||
- 不要输出“聊天”“对话”“问题”“事情”“内容”这类过于空泛的路径。
|
||||
- 不要在路径中附加括号说明、注释、额外解释或权重信息。
|
||||
|
||||
关于 relatedTopicPaths:
|
||||
- relatedTopicPaths 表示与该切片存在明确相邻关系、可能支持跨主题扩散召回的少量主题路径。
|
||||
- relatedTopicPaths 中每一项也必须是 `A->B->C` 形式的纯路径文本。
|
||||
- 只有在该切片确实同时牵涉多个清晰主题方向时,才输出 relatedTopicPaths。
|
||||
- 不要因为共享几个表层关键词就添加 relatedTopicPaths。
|
||||
- 不要输出与 topicPath 语义重复的轻微改写路径。
|
||||
- 不要为了显得全面而输出很多 related paths;宁少勿滥。
|
||||
|
||||
关于 activationProfile:
|
||||
- 所有权重都使用 0.0 到 1.0 之间的小数表示。
|
||||
- activationWeight:表示这条切片整体上值不值得在后续召回中被优先考虑。
|
||||
- 高:包含明确事实、稳定偏好、约定、计划、决策、长期背景、可复用信息。
|
||||
- 低:只是寒暄、即时反应、短时情绪、信息量很低的临时片段。
|
||||
- diffusionWeight:表示这条切片是否值得通过 relatedTopicPaths 向相邻主题扩散召回。
|
||||
- 高:内容天然横跨多个明确主题,或者对相关主题也有明显补充价值。
|
||||
- 低:虽然属于某个主题,但基本不适合跨主题带出。
|
||||
- contextIndependenceWeight:表示这条切片脱离原始上下文后,是否仍然容易被单独理解和使用。
|
||||
- 高:内容自洽、事实明确、实体清楚、单独拿出来也能理解。
|
||||
- 低:强依赖当时上下文、代词、省略、只看切片本身难以明白。
|
||||
|
||||
评分尺度:
|
||||
- activationWeight 应反映“这条切片本身的重要性与可复用性”。
|
||||
- diffusionWeight 应反映“是否真的值得跨主题扩散”,默认应比 activationWeight 更保守。
|
||||
- contextIndependenceWeight 应反映“脱离原对话是否还能读懂和使用”。
|
||||
- 不要把所有值都打高;只有当切片在对应维度上确实强时才给高分。
|
||||
- 不要机械使用固定分数;要根据当前切片内容判断。
|
||||
|
||||
何时应给较高 activationWeight:
|
||||
- 明确记录了事实、偏好、约束、习惯、长期计划、角色信息;
|
||||
- 形成了结论、约定、决策、方案取舍;
|
||||
- 未来很可能被再次引用。
|
||||
|
||||
何时应给较低 activationWeight:
|
||||
- 只是礼貌回应、寒暄、附和、笑声、过渡句;
|
||||
- 主要是短时情绪或即时反馈;
|
||||
- 单独拿出来几乎没有长期价值。
|
||||
|
||||
何时应给较高 diffusionWeight:
|
||||
- 同一切片明显同时涉及两个或多个独立但相关的主题方向;
|
||||
- 这条切片从相关主题角度看也有明确召回价值。
|
||||
|
||||
何时应给较低 diffusionWeight:
|
||||
- 切片内容主要只属于一个主主题;
|
||||
- related 关系很弱,只是边缘沾到;
|
||||
- 只有表层关键词重合,没有稳定的跨主题价值。
|
||||
|
||||
何时应给较高 contextIndependenceWeight:
|
||||
- 切片中讨论对象明确;
|
||||
- 即使不看原始对话,也能大致理解这条记忆在说什么;
|
||||
- 内容偏事实、偏结论、偏规则,而不是依赖上下文补全。
|
||||
|
||||
何时应给较低 contextIndependenceWeight:
|
||||
- 大量使用“这个”“那个”“刚才说的”“上次那个”等强指代表达;
|
||||
- 切片很短,且核心信息依赖前文;
|
||||
- 单独提取出来容易误解。
|
||||
|
||||
你不应做的事:
|
||||
- 不要回答用户问题;
|
||||
- 不要写解释、理由、备注;
|
||||
- 不要输出除 topicPath、relatedTopicPaths、activationProfile 之外的额外字段;
|
||||
- 不要把 current_topic_tree 中的节点计数或标记原样抄进路径;
|
||||
- 不要因为不确定就发明新主题或给过高权重。
|
||||
|
||||
输出要求:
|
||||
- 严格按照 MemoryTopicResult 对应结构输出。
|
||||
- topicPath 必须是字符串。
|
||||
- relatedTopicPaths 必须是字符串数组,可为空数组。
|
||||
- activationProfile 必须包含:
|
||||
- activationWeight
|
||||
- diffusionWeight
|
||||
- contextIndependenceWeight
|
||||
- 所有路径字段都只输出纯路径文本。
|
||||
- 所有权重字段都必须是 0.0 到 1.0 的数值。
|
||||
""";
|
||||
|
||||
private static final float DEFAULT_ACTIVATION_WEIGHT = 0.55f;
|
||||
private static final float DEFAULT_DIFFUSION_WEIGHT = 0.35f;
|
||||
private static final float DEFAULT_CONTEXT_INDEPENDENCE_WEIGHT = 0.50f;
|
||||
private static final float NO_RELATED_DIFFUSION_CAP = 0.45f;
|
||||
private static final float SINGLE_MESSAGE_ACTIVATION_PENALTY = 0.05f;
|
||||
|
||||
@InjectModule
|
||||
private MemoryRuntime memoryRuntime;
|
||||
@InjectModule
|
||||
private AfterRollingRegistry afterRollingRegistry;
|
||||
|
||||
@Init
|
||||
public void init() {
|
||||
afterRollingRegistry.register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(RollingResult result) {
|
||||
List<Message> slicedMessages = sliceMessages(result);
|
||||
if (slicedMessages.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Result<MemoryTopicResult> extractResult = formattedChat(
|
||||
List.of(
|
||||
resolveTopicTaskMessage(result, slicedMessages)
|
||||
),
|
||||
MemoryTopicResult.class
|
||||
);
|
||||
extractResult.onSuccess(topicResult -> {
|
||||
String topicPath = topicResult.getTopicPath() == null ? null : memoryRuntime.fixTopicPath(topicResult.getTopicPath());
|
||||
List<String> relatedTopicPaths = topicResult.getRelatedTopicPaths() == null
|
||||
? List.of()
|
||||
: topicResult.getRelatedTopicPaths().stream().map(memoryRuntime::fixTopicPath).toList();
|
||||
ActivationProfile activationProfile = stabilizeActivationProfile(
|
||||
topicResult.getActivationProfile(),
|
||||
relatedTopicPaths,
|
||||
slicedMessages
|
||||
);
|
||||
memoryRuntime.recordMemory(result.memoryUnit(), topicPath, relatedTopicPaths, activationProfile);
|
||||
}).onFailure(exp -> memoryRuntime.recordMemory(
|
||||
result.memoryUnit(),
|
||||
null,
|
||||
List.of(),
|
||||
defaultActivationProfile()
|
||||
));
|
||||
}
|
||||
|
||||
private List<Message> sliceMessages(RollingResult result) {
|
||||
int size = result.memoryUnit().getConversationMessages().size();
|
||||
int start = Math.clamp(result.memorySlice().getStartIndex(), 0, size);
|
||||
int end = Math.clamp(result.memorySlice().getEndIndex(), start, size);
|
||||
if (start >= end) {
|
||||
return List.of();
|
||||
}
|
||||
return result.memoryUnit().getConversationMessages().subList(start, end);
|
||||
}
|
||||
|
||||
private Message resolveTopicTaskMessage(RollingResult result, List<Message> slicedMessages) {
|
||||
return new TaskBlock() {
|
||||
@Override
|
||||
protected void fillXml(@NotNull Document document, @NotNull Element root) {
|
||||
appendTextElement(document, root, "current_topic_tree", memoryRuntime.getTopicTree());
|
||||
appendTextElement(document, root, "slice_summary", result.summary());
|
||||
appendRepeatedElements(document, root, "message", slicedMessages, (messageElement, message) -> {
|
||||
messageElement.setAttribute("role", message.roleValue());
|
||||
messageElement.setTextContent(message.getContent());
|
||||
return kotlin.Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
}.encodeToMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String modelKey() {
|
||||
return "memory_recall_profile_extractor";
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public List<Message> modulePrompt() {
|
||||
return List.of(new Message(Message.Character.SYSTEM, MODULE_PROMPT));
|
||||
}
|
||||
|
||||
private ActivationProfile stabilizeActivationProfile(ActivationProfile activationProfile,
|
||||
List<String> relatedTopicPaths,
|
||||
List<Message> slicedMessages) {
|
||||
ActivationProfile profile = activationProfile == null ? defaultActivationProfile() : new ActivationProfile(
|
||||
activationProfile.getActivationWeight(),
|
||||
activationProfile.getDiffusionWeight(),
|
||||
activationProfile.getContextIndependenceWeight()
|
||||
);
|
||||
profile.setActivationWeight(clampOrDefault(profile.getActivationWeight(), DEFAULT_ACTIVATION_WEIGHT));
|
||||
profile.setDiffusionWeight(clampOrDefault(profile.getDiffusionWeight(), DEFAULT_DIFFUSION_WEIGHT));
|
||||
profile.setContextIndependenceWeight(clampOrDefault(
|
||||
profile.getContextIndependenceWeight(),
|
||||
DEFAULT_CONTEXT_INDEPENDENCE_WEIGHT
|
||||
));
|
||||
|
||||
if (relatedTopicPaths.isEmpty()) {
|
||||
profile.setDiffusionWeight(Math.min(profile.getDiffusionWeight(), NO_RELATED_DIFFUSION_CAP));
|
||||
}
|
||||
if (slicedMessages.size() <= 1) {
|
||||
profile.setActivationWeight(clamp(profile.getActivationWeight() - SINGLE_MESSAGE_ACTIVATION_PENALTY));
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
private ActivationProfile defaultActivationProfile() {
|
||||
return new ActivationProfile(
|
||||
DEFAULT_ACTIVATION_WEIGHT,
|
||||
DEFAULT_DIFFUSION_WEIGHT,
|
||||
DEFAULT_CONTEXT_INDEPENDENCE_WEIGHT
|
||||
);
|
||||
}
|
||||
|
||||
private float clampOrDefault(Float value, float defaultValue) {
|
||||
return value == null ? defaultValue : clamp(value);
|
||||
}
|
||||
|
||||
private float clamp(float value) {
|
||||
return Math.clamp(value, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
package work.slhaf.partner.module.memory.updater;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule;
|
||||
import work.slhaf.partner.framework.agent.factory.component.annotation.Init;
|
||||
import work.slhaf.partner.framework.agent.factory.component.annotation.InjectModule;
|
||||
import work.slhaf.partner.framework.agent.model.ActivateModel;
|
||||
import work.slhaf.partner.framework.agent.model.pojo.Message;
|
||||
import work.slhaf.partner.framework.agent.support.Result;
|
||||
import work.slhaf.partner.module.TaskBlock;
|
||||
import work.slhaf.partner.module.communication.AfterRolling;
|
||||
import work.slhaf.partner.module.communication.AfterRollingRegistry;
|
||||
import work.slhaf.partner.module.communication.RollingResult;
|
||||
import work.slhaf.partner.module.memory.pojo.ActivationProfile;
|
||||
import work.slhaf.partner.module.memory.runtime.MemoryRuntime;
|
||||
import work.slhaf.partner.module.memory.updater.summarizer.entity.MemoryTopicResult;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MemoryUpdater extends AbstractAgentModule.Standalone implements AfterRolling, ActivateModel {
|
||||
|
||||
private static final float DEFAULT_ACTIVATION_WEIGHT = 0.55f;
|
||||
private static final float DEFAULT_DIFFUSION_WEIGHT = 0.35f;
|
||||
private static final float DEFAULT_CONTEXT_INDEPENDENCE_WEIGHT = 0.50f;
|
||||
private static final float NO_RELATED_DIFFUSION_CAP = 0.45f;
|
||||
private static final float SINGLE_MESSAGE_ACTIVATION_PENALTY = 0.05f;
|
||||
|
||||
@InjectModule
|
||||
private MemoryRuntime memoryRuntime;
|
||||
@InjectModule
|
||||
private AfterRollingRegistry afterRollingRegistry;
|
||||
|
||||
@Init
|
||||
public void init() {
|
||||
afterRollingRegistry.register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(RollingResult result) {
|
||||
List<Message> slicedMessages = sliceMessages(result);
|
||||
if (slicedMessages.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Result<MemoryTopicResult> extractResult = formattedChat(
|
||||
List.of(
|
||||
resolveTopicTaskMessage(result, slicedMessages)
|
||||
),
|
||||
MemoryTopicResult.class
|
||||
);
|
||||
extractResult.onSuccess(topicResult -> {
|
||||
String topicPath = topicResult.getTopicPath() == null ? null : memoryRuntime.fixTopicPath(topicResult.getTopicPath());
|
||||
List<String> relatedTopicPaths = topicResult.getRelatedTopicPaths() == null
|
||||
? List.of()
|
||||
: topicResult.getRelatedTopicPaths().stream().map(memoryRuntime::fixTopicPath).toList();
|
||||
ActivationProfile activationProfile = stabilizeActivationProfile(
|
||||
topicResult.getActivationProfile(),
|
||||
relatedTopicPaths,
|
||||
slicedMessages
|
||||
);
|
||||
memoryRuntime.recordMemory(result.memoryUnit(), topicPath, relatedTopicPaths, activationProfile);
|
||||
}).onFailure(exp -> memoryRuntime.recordMemory(
|
||||
result.memoryUnit(),
|
||||
null,
|
||||
List.of(),
|
||||
defaultActivationProfile()
|
||||
));
|
||||
}
|
||||
|
||||
private List<Message> sliceMessages(RollingResult result) {
|
||||
int size = result.memoryUnit().getConversationMessages().size();
|
||||
int start = Math.clamp(result.memorySlice().getStartIndex(), 0, size);
|
||||
int end = Math.clamp(result.memorySlice().getEndIndex(), start, size);
|
||||
if (start >= end) {
|
||||
return List.of();
|
||||
}
|
||||
return result.memoryUnit().getConversationMessages().subList(start, end);
|
||||
}
|
||||
|
||||
private Message resolveTopicTaskMessage(RollingResult result, List<Message> slicedMessages) {
|
||||
return new TaskBlock() {
|
||||
@Override
|
||||
protected void fillXml(@NotNull Document document, @NotNull Element root) {
|
||||
appendTextElement(document, root, "current_topic_tree", memoryRuntime.getTopicTree());
|
||||
appendTextElement(document, root, "slice_summary", result.summary());
|
||||
appendRepeatedElements(document, root, "message", slicedMessages, (messageElement, message) -> {
|
||||
messageElement.setAttribute("role", message.roleValue());
|
||||
messageElement.setTextContent(message.getContent());
|
||||
return kotlin.Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
}.encodeToMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String modelKey() {
|
||||
return "topic_extractor";
|
||||
}
|
||||
|
||||
private ActivationProfile stabilizeActivationProfile(ActivationProfile activationProfile,
|
||||
List<String> relatedTopicPaths,
|
||||
List<Message> slicedMessages) {
|
||||
ActivationProfile profile = activationProfile == null ? defaultActivationProfile() : new ActivationProfile(
|
||||
activationProfile.getActivationWeight(),
|
||||
activationProfile.getDiffusionWeight(),
|
||||
activationProfile.getContextIndependenceWeight()
|
||||
);
|
||||
profile.setActivationWeight(clampOrDefault(profile.getActivationWeight(), DEFAULT_ACTIVATION_WEIGHT));
|
||||
profile.setDiffusionWeight(clampOrDefault(profile.getDiffusionWeight(), DEFAULT_DIFFUSION_WEIGHT));
|
||||
profile.setContextIndependenceWeight(clampOrDefault(
|
||||
profile.getContextIndependenceWeight(),
|
||||
DEFAULT_CONTEXT_INDEPENDENCE_WEIGHT
|
||||
));
|
||||
|
||||
if (relatedTopicPaths.isEmpty()) {
|
||||
profile.setDiffusionWeight(Math.min(profile.getDiffusionWeight(), NO_RELATED_DIFFUSION_CAP));
|
||||
}
|
||||
if (slicedMessages.size() <= 1) {
|
||||
profile.setActivationWeight(clamp(profile.getActivationWeight() - SINGLE_MESSAGE_ACTIVATION_PENALTY));
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
private ActivationProfile defaultActivationProfile() {
|
||||
return new ActivationProfile(
|
||||
DEFAULT_ACTIVATION_WEIGHT,
|
||||
DEFAULT_DIFFUSION_WEIGHT,
|
||||
DEFAULT_CONTEXT_INDEPENDENCE_WEIGHT
|
||||
);
|
||||
}
|
||||
|
||||
private float clampOrDefault(Float value, float defaultValue) {
|
||||
return value == null ? defaultValue : clamp(value);
|
||||
}
|
||||
|
||||
private float clamp(float value) {
|
||||
return Math.clamp(value, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class MemoryUpdaterTest {
|
||||
class MemoryRecallProfileExtractorTest {
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll(@TempDir Path tempDir) {
|
||||
@@ -43,7 +43,7 @@ class MemoryUpdaterTest {
|
||||
|
||||
@Test
|
||||
void shouldRegisterItselfToAfterRollingRegistryOnInit() throws Exception {
|
||||
MemoryUpdater updater = Mockito.spy(new MemoryUpdater());
|
||||
MemoryRecallProfileExtractor updater = Mockito.spy(new MemoryRecallProfileExtractor());
|
||||
AfterRollingRegistry registry = Mockito.mock(AfterRollingRegistry.class);
|
||||
setField(updater, "afterRollingRegistry", registry);
|
||||
|
||||
@@ -54,7 +54,7 @@ class MemoryUpdaterTest {
|
||||
|
||||
@Test
|
||||
void shouldExtractTopicAndRecordMemoryOnConsume() throws Exception {
|
||||
MemoryUpdater updater = Mockito.spy(new MemoryUpdater());
|
||||
MemoryRecallProfileExtractor updater = Mockito.spy(new MemoryRecallProfileExtractor());
|
||||
MemoryRuntime memoryRuntime = Mockito.mock(MemoryRuntime.class);
|
||||
setField(updater, "memoryRuntime", memoryRuntime);
|
||||
when(memoryRuntime.getTopicTree()).thenReturn("topic-tree");
|
||||
@@ -97,7 +97,7 @@ class MemoryUpdaterTest {
|
||||
|
||||
@Test
|
||||
void shouldFallbackToDateOnlyRecordWhenExtractionFails() throws Exception {
|
||||
MemoryUpdater updater = Mockito.spy(new MemoryUpdater());
|
||||
MemoryRecallProfileExtractor updater = Mockito.spy(new MemoryRecallProfileExtractor());
|
||||
MemoryRuntime memoryRuntime = Mockito.mock(MemoryRuntime.class);
|
||||
setField(updater, "memoryRuntime", memoryRuntime);
|
||||
when(memoryRuntime.getTopicTree()).thenReturn("topic-tree");
|
||||
@@ -128,7 +128,7 @@ class MemoryUpdaterTest {
|
||||
|
||||
@Test
|
||||
void shouldClampAndAdjustActivationProfileBeforeRecording() throws Exception {
|
||||
MemoryUpdater updater = Mockito.spy(new MemoryUpdater());
|
||||
MemoryRecallProfileExtractor updater = Mockito.spy(new MemoryRecallProfileExtractor());
|
||||
MemoryRuntime memoryRuntime = Mockito.mock(MemoryRuntime.class);
|
||||
setField(updater, "memoryRuntime", memoryRuntime);
|
||||
when(memoryRuntime.getTopicTree()).thenReturn("topic-tree");
|
||||
Reference in New Issue
Block a user