refactor(runtime): support collect context by source and interrupt same-source running flow by module order

This commit is contained in:
2026-04-15 14:32:52 +08:00
parent 247057e100
commit dc147000ba
12 changed files with 537 additions and 122 deletions

View File

@@ -69,7 +69,7 @@ public class ActionPlanner extends AbstractAgentModule.Running<PartnerRunningFlo
@Override
protected void doExecute(@NotNull PartnerRunningFlowContext context) {
String input = context.getInput();
String input = context.encodeInputsXml();
Result<ExtractorResult> result = actionExtractor.execute(input)
.onFailure(exp -> {
ExceptionReporterHandler.INSTANCE.report(exp, ContextExceptionReporter.REPORTER_NAME);

View File

@@ -38,7 +38,7 @@ public class CommunicationProducer extends AbstractAgentModule.Running<PartnerRu
你接下来收到的消息固定分为三个区段:
1. system message 是 Head, 用于说明整个输入结构与输出要求。
2. <context> 区段只承载 type=CONTEXT 的上下文块, 其中每个子块都带有独立来源, 仅作为理解当前状态与辅助决策的依据。
3. Conversation 区段是对话轨迹; 最新的一条 user message 会使用 <input> 结构, 其中 <content> 是本轮用户原始输入, 其他子标签是输入元信息与 type=SUPPLY 的补充块, 补充块会按 blockName 分区。
3. Conversation 区段是对话轨迹; 最新的一条 user message 会使用 <input> 结构, 其中 <inputs> 承载本轮按时间顺序排列的输入序列, 每个 <input> 节点会带有相对首条输入的时间间隔属性, 其他子标签是输入元信息与 type=SUPPLY 的补充块, 补充块会按 blockName 分区。
你必须综合 Context 与 Conversation 回答最新输入, 不要把 XML 标签当作需要原样复述给用户的内容。
直接输出最终回应内容即可, 不需要额外包装为 JSON。
""";
@@ -129,7 +129,7 @@ public class CommunicationProducer extends AbstractAgentModule.Running<PartnerRu
Element root = document.createElement("input");
document.appendChild(root);
appendTextElement(document, root, "content", runningFlowContext.getInput());
runningFlowContext.appendInputsXml(document, root);
appendTextElement(document, root, "source", runningFlowContext.getSource());
for (Map.Entry<String, String> entry : runningFlowContext.getAdditionalUserInfo().entrySet()) {
appendTextElement(document, root, sanitizeTagName(entry.getKey()), entry.getValue());
@@ -159,7 +159,7 @@ public class CommunicationProducer extends AbstractAgentModule.Running<PartnerRu
}
private String formatConversationUserMessage(PartnerRunningFlowContext runningFlowContext) {
return "[" + runningFlowContext.getSource() + "]" + ": " + runningFlowContext.getInput();
return "[" + runningFlowContext.getSource() + "]" + ": " + runningFlowContext.formatInputsForHistory();
}
private Document newDocument() throws Exception {

View File

@@ -6,14 +6,13 @@ import lombok.EqualsAndHashCode;
import org.jetbrains.annotations.NotNull;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.action.ActionCore;
import work.slhaf.partner.core.cognition.BlockContent;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock;
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability;
import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule;
import work.slhaf.partner.framework.agent.factory.component.annotation.InjectModule;
import work.slhaf.partner.framework.agent.interaction.flow.RunningFlowContext;
import work.slhaf.partner.framework.agent.model.pojo.Message;
import work.slhaf.partner.module.memory.runtime.MemoryRuntime;
import work.slhaf.partner.module.memory.runtime.exception.MemoryLookupException;
@@ -26,12 +25,7 @@ import work.slhaf.partner.module.memory.selector.extractor.entity.ExtractorResul
import work.slhaf.partner.runtime.PartnerRunningFlowContext;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@EqualsAndHashCode(callSuper = true)
@Data
@@ -42,8 +36,6 @@ public class MemorySelector extends AbstractAgentModule.Running<PartnerRunningFl
@InjectCapability
private CognitionCapability cognitionCapability;
@InjectCapability
private ActionCapability actionCapability;
@InjectModule
private MemoryRuntime memoryRuntime;
@@ -52,72 +44,21 @@ public class MemorySelector extends AbstractAgentModule.Running<PartnerRunningFl
@InjectModule
private MemorySelectExtractor memorySelectExtractor;
private AtomicBoolean memoryCalling = new AtomicBoolean(false);
private Map<LocalDateTime, String> collectedInputs = new HashMap<>();
private Lock inputsLock = new ReentrantLock();
@Override
protected void doExecute(@NotNull PartnerRunningFlowContext runningFlowContext) {
inputsLock.lock();
try {
collectedInputs.put(ZonedDateTime.now().toLocalDateTime(), runningFlowContext.getInput());
} finally {
inputsLock.unlock();
}
List<RunningFlowContext.InputEntry> snapshotInputs = List.copyOf(runningFlowContext.getInputs());
ExtractorInput input = new ExtractorInput(
snapshotInputs,
memoryRuntime.getTopicTree(),
runningFlowContext.getFirstInputDateTime().toLocalDate()
);
tryStartMemoryRecallWorker();
}
private void tryStartMemoryRecallWorker() {
if (!memoryCalling.compareAndSet(false, true)) {
ExtractorResult extractorResult = memorySelectExtractor.execute(input);
if (extractorResult.getMatches().isEmpty()) {
return;
}
actionCapability.getExecutor(ActionCore.ExecutorType.VIRTUAL).execute(() -> {
try {
drainMemoryRecall();
} finally {
memoryCalling.set(false);
// 防止竞态worker 退出前后,刚好来了新输入,但没有线程负责再拉起 worker
if (!collectedInputs.isEmpty()) {
tryStartMemoryRecallWorker();
}
}
});
}
private void drainMemoryRecall() {
while (true) {
Map<LocalDateTime, String> snapshotInputs;
inputsLock.lock();
try {
if (collectedInputs.isEmpty()) {
return;
}
snapshotInputs = new HashMap<>(collectedInputs);
collectedInputs.clear();
} finally {
inputsLock.unlock();
}
ExtractorInput input = new ExtractorInput(
snapshotInputs,
memoryRuntime.getTopicTree(),
snapshotInputs.keySet()
.stream()
.max(LocalDateTime::compareTo)
.orElseThrow()
.toLocalDate()
);
ExtractorResult extractorResult = memorySelectExtractor.execute(input);
if (!extractorResult.getMatches().isEmpty()) {
List<ActivatedMemorySlice> activatedSlices = selectAndEvaluateMemory(snapshotInputs, extractorResult);
updateMemoryContext(activatedSlices);
}
}
List<ActivatedMemorySlice> activatedSlices = selectAndEvaluateMemory(snapshotInputs, extractorResult);
updateMemoryContext(activatedSlices);
}
private void updateMemoryContext(List<ActivatedMemorySlice> activatedSlices) {
@@ -205,7 +146,7 @@ public class MemorySelector extends AbstractAgentModule.Running<PartnerRunningFl
};
}
private List<ActivatedMemorySlice> selectAndEvaluateMemory(Map<LocalDateTime, String> snapshotInputs, ExtractorResult extractorResult) {
private List<ActivatedMemorySlice> selectAndEvaluateMemory(List<RunningFlowContext.InputEntry> snapshotInputs, ExtractorResult extractorResult) {
LinkedHashMap<String, ActivatedMemorySlice> candidates = new LinkedHashMap<>();
setMemoryCandidates(candidates, extractorResult.getMatches());
EvaluatorInput evaluatorInput = EvaluatorInput.builder()

View File

@@ -21,7 +21,6 @@ import work.slhaf.partner.module.memory.selector.evaluator.entity.EvaluatorBatch
import work.slhaf.partner.module.memory.selector.evaluator.entity.EvaluatorBatchResult;
import work.slhaf.partner.module.memory.selector.evaluator.entity.EvaluatorInput;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -89,9 +88,12 @@ public class SliceSelectEvaluator extends AbstractAgentModule.Sub<EvaluatorInput
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());
appendChildElement(document, root, "new_inputs", (inputsElement) -> {
appendListElement(document, inputsElement, "inputs", "input", batchInput.getInputs(), (inputElement, entry) -> {
inputElement.setAttribute("interval-to-first", String.valueOf(entry.getOffsetMillis()));
inputElement.setTextContent(entry.getContent());
return Unit.INSTANCE;
});
return Unit.INSTANCE;
});
appendChildElement(document, root, "memory_slice", (element) -> {

View File

@@ -2,14 +2,14 @@ package work.slhaf.partner.module.memory.selector.evaluator.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import work.slhaf.partner.framework.agent.interaction.flow.RunningFlowContext;
import work.slhaf.partner.module.memory.selector.ActivatedMemorySlice;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.List;
@Data
@AllArgsConstructor
public class EvaluatorBatchInput {
private Map<LocalDateTime, String> inputs;
private List<RunningFlowContext.InputEntry> inputs;
private ActivatedMemorySlice activatedMemorySlice;
}

View File

@@ -2,15 +2,14 @@ package work.slhaf.partner.module.memory.selector.evaluator.entity;
import lombok.Builder;
import lombok.Data;
import work.slhaf.partner.framework.agent.interaction.flow.RunningFlowContext;
import work.slhaf.partner.module.memory.selector.ActivatedMemorySlice;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@Data
@Builder
public class EvaluatorInput {
private Map<LocalDateTime, String> inputs;
private List<RunningFlowContext.InputEntry> inputs;
private List<ActivatedMemorySlice> memorySlices;
}

View File

@@ -57,9 +57,12 @@ public class MemorySelectExtractor extends AbstractAgentModule.Sub<ExtractorInpu
return new TaskBlock() {
@Override
protected void fillXml(@NotNull Document document, @NotNull Element root) {
appendListElement(document, root, "new_inputs", "input", input.getInputs().entrySet(), (inputElement, input) -> {
inputElement.setAttribute("datetime", input.getKey().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
inputElement.setTextContent(input.getValue());
appendChildElement(document, root, "new_inputs", (inputsElement) -> {
appendListElement(document, inputsElement, "inputs", "input", input.getInputs(), (inputElement, entry) -> {
inputElement.setAttribute("interval-to-first", String.valueOf(entry.getOffsetMillis()));
inputElement.setTextContent(entry.getContent());
return Unit.INSTANCE;
});
return Unit.INSTANCE;
});
appendTextElement(document, root, "current_date", input.getDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));

View File

@@ -2,15 +2,15 @@ package work.slhaf.partner.module.memory.selector.extractor.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import work.slhaf.partner.framework.agent.interaction.flow.RunningFlowContext;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.List;
@Data
@AllArgsConstructor
public class ExtractorInput {
private Map<LocalDateTime, String> inputs;
private List<RunningFlowContext.InputEntry> inputs;
private String topic_tree;
private LocalDate date;
}

View File

@@ -4,8 +4,12 @@ import work.slhaf.partner.framework.agent.interaction.flow.RunningFlowContext
class PartnerRunningFlowContext private constructor(
override val source: String,
override val input: String,
) : RunningFlowContext() {
inputs: List<InputEntry>,
firstInputEpochMillis: Long,
additionalUserInfo: Map<String, String> = emptyMap(),
skippedModules: Set<String> = emptySet(),
target: String = source
) : RunningFlowContext(inputs, firstInputEpochMillis, additionalUserInfo, skippedModules, target) {
companion object {
@@ -27,15 +31,39 @@ class PartnerRunningFlowContext private constructor(
}
@JvmStatic
fun fromUser(userId: String, input: String) = PartnerRunningFlowContext(
SourceTag.buildUserSource(userId),
input
)
fun fromUser(userId: String, input: String, receivedAtMillis: Long = System.currentTimeMillis()) =
PartnerRunningFlowContext(
SourceTag.buildUserSource(userId),
listOf(InputEntry(0L, input)),
receivedAtMillis
)
@JvmStatic
fun fromSelf(input: String) = PartnerRunningFlowContext(SourceTag.buildAgentSource(), input).apply {
putUserInfo(InfoKeys.PLATFORM, SOURCE_SELF_PLATFORM)
putUserInfo(InfoKeys.NICKNAME, SOURCE_SELF_NICKNAME)
}
fun fromSelf(input: String, receivedAtMillis: Long = System.currentTimeMillis()) =
PartnerRunningFlowContext(
SourceTag.buildAgentSource(),
listOf(InputEntry(0L, input)),
receivedAtMillis
).apply {
putUserInfo(InfoKeys.PLATFORM, SOURCE_SELF_PLATFORM)
putUserInfo(InfoKeys.NICKNAME, SOURCE_SELF_NICKNAME)
}
}
}
override fun copyWith(
inputs: List<InputEntry>,
firstInputEpochMillis: Long,
additionalUserInfo: Map<String, String>,
skippedModules: Set<String>,
target: String
): RunningFlowContext {
return PartnerRunningFlowContext(
source = source,
inputs = inputs,
firstInputEpochMillis = firstInputEpochMillis,
additionalUserInfo = additionalUserInfo,
skippedModules = skippedModules,
target = target
)
}
}