refactor(memory): refactor memory extract/evaluating logic and messages building in memory modules

This commit is contained in:
2026-03-28 21:52:02 +08:00
parent baa6870ccf
commit 09f90d8ad5
6 changed files with 162 additions and 103 deletions

View File

@@ -1,92 +1,148 @@
package work.slhaf.partner.module.memory.selector.evaluator; package work.slhaf.partner.module.memory.selector.evaluator;
import cn.hutool.json.JSONUtil; import kotlin.Unit;
import com.alibaba.fastjson2.JSONObject;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.jetbrains.annotations.NotNull;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import work.slhaf.partner.api.agent.factory.capability.annotation.InjectCapability;
import work.slhaf.partner.api.agent.factory.component.abstracts.AbstractAgentModule; import work.slhaf.partner.api.agent.factory.component.abstracts.AbstractAgentModule;
import work.slhaf.partner.api.agent.factory.component.abstracts.ActivateModel; import work.slhaf.partner.api.agent.factory.component.abstracts.ActivateModel;
import work.slhaf.partner.api.agent.factory.component.annotation.Init; import work.slhaf.partner.api.agent.factory.component.annotation.Init;
import work.slhaf.partner.api.chat.pojo.Message; import work.slhaf.partner.api.chat.pojo.Message;
import work.slhaf.partner.common.thread.InteractionThreadPoolExecutor; import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.action.ActionCore;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock;
import work.slhaf.partner.core.memory.pojo.ActivatedMemorySlice; import work.slhaf.partner.core.memory.pojo.ActivatedMemorySlice;
import work.slhaf.partner.module.TaskBlock;
import work.slhaf.partner.module.memory.selector.evaluator.entity.EvaluatorBatchInput; import work.slhaf.partner.module.memory.selector.evaluator.entity.EvaluatorBatchInput;
import work.slhaf.partner.module.memory.selector.evaluator.entity.EvaluatorBatchResult;
import work.slhaf.partner.module.memory.selector.evaluator.entity.EvaluatorInput; import work.slhaf.partner.module.memory.selector.evaluator.entity.EvaluatorInput;
import work.slhaf.partner.module.memory.selector.evaluator.entity.EvaluatorResult;
import work.slhaf.partner.module.memory.selector.evaluator.entity.SliceSummary;
import java.util.*; import java.time.format.DateTimeFormatter;
import java.util.concurrent.Callable; import java.util.ArrayList;
import java.util.concurrent.ConcurrentLinkedDeque; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Data @Data
public class SliceSelectEvaluator extends AbstractAgentModule.Sub<EvaluatorInput, List<ActivatedMemorySlice>> implements ActivateModel { public class SliceSelectEvaluator extends AbstractAgentModule.Sub<EvaluatorInput, List<ActivatedMemorySlice>> implements ActivateModel {
private InteractionThreadPoolExecutor executor;
@InjectCapability
private ActionCapability actionCapability;
@InjectCapability
private CognitionCapability cognitionCapability;
private ExecutorService executor;
@Init @Init
public void init() { public void init() {
executor = InteractionThreadPoolExecutor.getInstance(); executor = actionCapability.getExecutor(ActionCore.ExecutorType.VIRTUAL);
} }
@Override @Override
public List<ActivatedMemorySlice> execute(EvaluatorInput evaluatorInput) { public List<ActivatedMemorySlice> execute(EvaluatorInput evaluatorInput) {
log.debug("[SliceSelectEvaluator] 切片评估模块开始..."); log.debug("切片评估模块开始...");
List<ActivatedMemorySlice> memorySlices = evaluatorInput.getMemorySlices(); List<ActivatedMemorySlice> preparedSlices = evaluatorInput.getMemorySlices();
List<Callable<Void>> tasks = new ArrayList<>(); List<ActivatedMemorySlice> result = new ArrayList<>();
Queue<ActivatedMemorySlice> queue = new ConcurrentLinkedDeque<>(); CountDownLatch latch = new CountDownLatch(preparedSlices.size());
AtomicInteger count = new AtomicInteger(0);
if (memorySlices == null || memorySlices.isEmpty()) { Message contextMessage = cognitionCapability.contextWorkspace().resolve(List.of(
return List.of(); ContextBlock.VisibleDomain.COGNITION,
} ContextBlock.VisibleDomain.MEMORY
tasks.add(() -> { )).encodeToMessage();
int thisCount = count.incrementAndGet();
log.debug("[SliceSelectEvaluator] 评估[{}]开始", thisCount); for (ActivatedMemorySlice slice : preparedSlices) {
List<SliceSummary> sliceSummaryList = new ArrayList<>(); executor.execute(() -> {
Map<Long, ActivatedMemorySlice> map = new HashMap<>();
try { try {
setSliceSummaryList(memorySlices, sliceSummaryList, map); EvaluatorBatchInput batchInput = new EvaluatorBatchInput(evaluatorInput.getInputs(), slice);
EvaluatorBatchInput batchInput = EvaluatorBatchInput.builder() List<Message> messages = List.of(
.text(evaluatorInput.getInput()) contextMessage,
.memory_slices(sliceSummaryList) resolveTaskMessage(batchInput)
.history(evaluatorInput.getMessages())
.build();
log.debug("[SliceSelectEvaluator] 评估[{}]输入: {}", thisCount, JSONObject.toJSONString(batchInput));
EvaluatorResult evaluatorResult = formattedChat(
List.of(new Message(Message.Character.USER, JSONUtil.toJsonStr(batchInput))),
EvaluatorResult.class
); );
log.debug("[SliceSelectEvaluator] 评估[{}]结果: {}", thisCount, JSONObject.toJSONString(evaluatorResult)); EvaluatorBatchResult batchResult = formattedChat(messages, EvaluatorBatchResult.class);
for (Long result : evaluatorResult.getResults()) { if (batchResult.isPassed()) {
ActivatedMemorySlice slice = map.get(result); synchronized (result) {
if (slice != null) { result.add(slice);
queue.offer(slice);
} }
} }
} catch (Exception e) { } catch (Exception ignore) {
log.error("[SliceSelectEvaluator] 评估[{}]出现错误: {}", thisCount, e.getLocalizedMessage()); } finally {
latch.countDown();
} }
return null;
}); });
executor.invokeAll(tasks, 30, TimeUnit.SECONDS);
log.debug("[SliceSelectEvaluator] 评估模块结束, 输出队列: {}", queue);
return new ArrayList<>(queue);
} }
private void setSliceSummaryList(List<ActivatedMemorySlice> memorySlices, List<SliceSummary> sliceSummaryList, try {
Map<Long, ActivatedMemorySlice> map) { latch.await();
for (ActivatedMemorySlice memorySlice : memorySlices) { } catch (InterruptedException ignored) {
SliceSummary sliceSummary = new SliceSummary();
sliceSummary.setId(memorySlice.getTimestamp());
sliceSummary.setSummary(memorySlice.getSummary());
sliceSummary.setDate(memorySlice.getDate());
sliceSummaryList.add(sliceSummary);
map.put(memorySlice.getTimestamp(), memorySlice);
} }
log.debug("切片评估模块结束...");
return result;
} }
private Message resolveTaskMessage(EvaluatorBatchInput batchInput) {
return new TaskBlock() {
@Override
protected void fillXml(@NotNull Document document, @NotNull Element root) {
appendListElement(document, root, "new_inputs", "input", batchInput.getInputs().entrySet(), (inputElement, input) -> {
inputElement.setAttribute("datetime", input.getKey().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
inputElement.setTextContent(input.getValue());
return Unit.INSTANCE;
});
appendChildElement(document, root, "memory_slice", (element) -> {
ActivatedMemorySlice slice = batchInput.getActivatedMemorySlice();
appendTextElement(document, element, "slice_summary", slice.getSummary());
appendChildElement(document, element, "source_messages", (messagesElement) -> {
List<Message> messages = slice.getMessages();
int size = messages.size();
if (size > 10) {
// 展示前两条
appendMessageElement(document, messagesElement, messages.subList(0, 2));
// 中间省略说明
int middleStart = Math.max(2, (size - 4) / 2);
int middleEnd = Math.min(size - 2, middleStart + 4);
int omittedBeforeMiddle = middleStart - 2;
appendTextElement(document, messagesElement, "omitted_messages", "省略了 " + omittedBeforeMiddle + " 条消息");
// 中间四条
appendMessageElement(document, messagesElement, messages.subList(middleStart, middleEnd));
// 中间到结尾前的省略说明
int omittedAfterMiddle = (size - 2) - middleEnd;
if (omittedAfterMiddle > 0) {
appendTextElement(document, messagesElement, "omitted_messages", "省略了 " + omittedAfterMiddle + " 条消息");
}
// 展示末尾两条
appendMessageElement(document, messagesElement, messages.subList(size - 2, size));
} else {
appendMessageElement(document, messagesElement, messages);
}
return Unit.INSTANCE;
});
return Unit.INSTANCE;
});
}
private void appendMessageElement(Document document, Element parent, List<Message> messages) {
appendRepeatedElements(document, parent, "message", messages, (messageElement, message) -> {
messageElement.setAttribute("role", message.getRole().name().toLowerCase(Locale.ROOT));
messageElement.setTextContent(message.getContent());
return Unit.INSTANCE;
});
}
}.encodeToMessage();
}
@Override
@NotNull
public String modelKey() { public String modelKey() {
return "slice_evaluator"; return "slice_evaluator";
} }

View File

@@ -1,15 +1,15 @@
package work.slhaf.partner.module.memory.selector.evaluator.entity; package work.slhaf.partner.module.memory.selector.evaluator.entity;
import lombok.Builder; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import work.slhaf.partner.api.chat.pojo.Message; import work.slhaf.partner.core.memory.pojo.ActivatedMemorySlice;
import java.util.List; import java.time.LocalDateTime;
import java.util.Map;
@Data @Data
@Builder @AllArgsConstructor
public class EvaluatorBatchInput { public class EvaluatorBatchInput {
private String text; private Map<LocalDateTime, String> inputs;
private List<Message> history; private ActivatedMemorySlice activatedMemorySlice;
private List<SliceSummary> memory_slices;
} }

View File

@@ -2,9 +2,7 @@ package work.slhaf.partner.module.memory.selector.evaluator.entity;
import lombok.Data; import lombok.Data;
import java.util.List;
@Data @Data
public class EvaluatorResult { public class EvaluatorBatchResult {
private List<Long> results; private boolean passed;
} }

View File

@@ -2,15 +2,15 @@ package work.slhaf.partner.module.memory.selector.evaluator.entity;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import work.slhaf.partner.api.chat.pojo.Message;
import work.slhaf.partner.core.memory.pojo.ActivatedMemorySlice; import work.slhaf.partner.core.memory.pojo.ActivatedMemorySlice;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Map;
@Data @Data
@Builder @Builder
public class EvaluatorInput { public class EvaluatorInput {
private String input; private Map<LocalDateTime, String> inputs;
private List<Message> messages;
private List<ActivatedMemorySlice> memorySlices; private List<ActivatedMemorySlice> memorySlices;
} }

View File

@@ -1,54 +1,47 @@
package work.slhaf.partner.module.memory.selector.extractor; package work.slhaf.partner.module.memory.selector.extractor;
import cn.hutool.json.JSONUtil;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.jetbrains.annotations.NotNull;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import work.slhaf.partner.api.agent.factory.capability.annotation.InjectCapability; import work.slhaf.partner.api.agent.factory.capability.annotation.InjectCapability;
import work.slhaf.partner.api.agent.factory.component.abstracts.AbstractAgentModule; import work.slhaf.partner.api.agent.factory.component.abstracts.AbstractAgentModule;
import work.slhaf.partner.api.agent.factory.component.abstracts.ActivateModel; import work.slhaf.partner.api.agent.factory.component.abstracts.ActivateModel;
import work.slhaf.partner.api.agent.factory.component.annotation.InjectModule; import work.slhaf.partner.api.agent.factory.component.annotation.InjectModule;
import work.slhaf.partner.api.chat.pojo.Message; import work.slhaf.partner.api.chat.pojo.Message;
import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.memory.MemoryCapability; import work.slhaf.partner.core.cognition.ContextBlock;
import work.slhaf.partner.core.memory.pojo.ActivatedMemorySlice; import work.slhaf.partner.module.TaskBlock;
import work.slhaf.partner.module.memory.runtime.MemoryRuntime; import work.slhaf.partner.module.memory.runtime.MemoryRuntime;
import work.slhaf.partner.module.memory.selector.extractor.entity.ExtractorInput; import work.slhaf.partner.module.memory.selector.extractor.entity.ExtractorInput;
import work.slhaf.partner.module.memory.selector.extractor.entity.ExtractorMatchData; import work.slhaf.partner.module.memory.selector.extractor.entity.ExtractorMatchData;
import work.slhaf.partner.module.memory.selector.extractor.entity.ExtractorResult; import work.slhaf.partner.module.memory.selector.extractor.entity.ExtractorResult;
import work.slhaf.partner.runtime.interaction.data.context.PartnerRunningFlowContext;
import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
import static work.slhaf.partner.common.util.ExtractUtil.fixTopicPath; import static work.slhaf.partner.common.util.ExtractUtil.fixTopicPath;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Data @Data
public class MemorySelectExtractor extends AbstractAgentModule.Sub<PartnerRunningFlowContext, ExtractorResult> public class MemorySelectExtractor extends AbstractAgentModule.Sub<ExtractorInput, ExtractorResult> implements ActivateModel {
implements ActivateModel {
@InjectCapability
private MemoryCapability memoryCapability;
@InjectCapability @InjectCapability
private CognitionCapability cognitionCapability; private CognitionCapability cognitionCapability;
@InjectModule @InjectModule
private MemoryRuntime memoryRuntime; private MemoryRuntime memoryRuntime;
@Override @Override
public ExtractorResult execute(PartnerRunningFlowContext context) { public ExtractorResult execute(ExtractorInput input) {
log.debug("[MemorySelectExtractor] 主题提取模块开始..."); log.debug("[MemorySelectExtractor] 主题提取模块开始...");
List<Message> chatMessages = cognitionCapability.snapshotChatMessages();
ExtractorResult extractorResult; ExtractorResult extractorResult;
try { try {
List<ActivatedMemorySlice> activatedMemorySlices = memoryCapability.getActivatedSlices(); List<Message> messages = List.of(
ExtractorInput extractorInput = ExtractorInput.builder() resolveContextMessage(),
.text(context.getInput()) resolveTaskMessage(input)
.date(context.getInfo().getDateTime().toLocalDate()) );
.history(chatMessages)
.topic_tree(memoryRuntime.getTopicTree())
.activatedMemorySlices(activatedMemorySlices)
.build();
log.debug("[MemorySelectExtractor] 主题提取输入: {}", JSONUtil.toJsonStr(extractorInput));
extractorResult = formattedChat( extractorResult = formattedChat(
List.of(new Message(Message.Character.USER, JSONUtil.toJsonPrettyStr(extractorInput))), messages,
ExtractorResult.class ExtractorResult.class
); );
log.debug("[MemorySelectExtractor] 主题提取结果: {}", extractorResult); log.debug("[MemorySelectExtractor] 主题提取结果: {}", extractorResult);
@@ -61,6 +54,23 @@ public class MemorySelectExtractor extends AbstractAgentModule.Sub<PartnerRunnin
return fix(extractorResult); return fix(extractorResult);
} }
private Message resolveTaskMessage(ExtractorInput input) {
return new TaskBlock() {
@Override
protected void fillXml(@NotNull Document document, @NotNull Element root) {
appendTextElement(document, root, "latest_input", input.getInput());
appendTextElement(document, root, "current_date", input.getDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
appendTextElement(document, root, "memory_topic_tree", input.getTopic_tree());
}
}.encodeToMessage();
}
private Message resolveContextMessage() {
return cognitionCapability.contextWorkspace().resolve(List.of(
ContextBlock.VisibleDomain.COGNITION, ContextBlock.VisibleDomain.MEMORY
)).encodeToMessage();
}
private ExtractorResult fix(ExtractorResult extractorResult) { private ExtractorResult fix(ExtractorResult extractorResult) {
extractorResult.getMatches().forEach(m -> { extractorResult.getMatches().forEach(m -> {
if (m.getType().equals(ExtractorMatchData.Constant.DATE)) { if (m.getType().equals(ExtractorMatchData.Constant.DATE)) {

View File

@@ -1,19 +1,14 @@
package work.slhaf.partner.module.memory.selector.extractor.entity; package work.slhaf.partner.module.memory.selector.extractor.entity;
import lombok.Builder; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import work.slhaf.partner.api.chat.pojo.Message;
import work.slhaf.partner.core.memory.pojo.ActivatedMemorySlice;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List;
@Data @Data
@Builder @AllArgsConstructor
public class ExtractorInput { public class ExtractorInput {
private String text; private String input;
private String topic_tree; private String topic_tree;
private LocalDate date; private LocalDate date;
private List<Message> history;
private List<ActivatedMemorySlice> activatedMemorySlices;
} }