mirror of
https://github.com/slhaf/Partner.git
synced 2026-05-12 08:43:02 +08:00
feat(memory): create prompts for submodules of memory selector
This commit is contained in:
@@ -14,11 +14,10 @@ import work.slhaf.partner.framework.agent.interaction.flow.RunningFlowContext;
|
|||||||
import work.slhaf.partner.framework.agent.model.pojo.Message;
|
import work.slhaf.partner.framework.agent.model.pojo.Message;
|
||||||
import work.slhaf.partner.module.memory.runtime.MemoryRuntime;
|
import work.slhaf.partner.module.memory.runtime.MemoryRuntime;
|
||||||
import work.slhaf.partner.module.memory.runtime.exception.MemoryLookupException;
|
import work.slhaf.partner.module.memory.runtime.exception.MemoryLookupException;
|
||||||
import work.slhaf.partner.module.memory.selector.evaluator.SliceSelectEvaluator;
|
import work.slhaf.partner.module.memory.selector.evaluator.MemoryRecallEvaluator;
|
||||||
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.extractor.MemorySelectExtractor;
|
import work.slhaf.partner.module.memory.selector.extractor.MemoryRecallCueExtractor;
|
||||||
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.ExtractorResult;
|
import work.slhaf.partner.module.memory.selector.extractor.entity.ExtractorResult;
|
||||||
import work.slhaf.partner.runtime.PartnerRunningFlowContext;
|
import work.slhaf.partner.runtime.PartnerRunningFlowContext;
|
||||||
|
|
||||||
@@ -36,9 +35,9 @@ public class MemorySelector extends AbstractAgentModule.Running<PartnerRunningFl
|
|||||||
@InjectModule
|
@InjectModule
|
||||||
private MemoryRuntime memoryRuntime;
|
private MemoryRuntime memoryRuntime;
|
||||||
@InjectModule
|
@InjectModule
|
||||||
private SliceSelectEvaluator sliceSelectEvaluator;
|
private MemoryRecallEvaluator memoryRecallEvaluator;
|
||||||
@InjectModule
|
@InjectModule
|
||||||
private MemorySelectExtractor memorySelectExtractor;
|
private MemoryRecallCueExtractor memoryRecallCueExtractor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doExecute(@NotNull PartnerRunningFlowContext runningFlowContext) {
|
protected void doExecute(@NotNull PartnerRunningFlowContext runningFlowContext) {
|
||||||
@@ -49,7 +48,7 @@ public class MemorySelector extends AbstractAgentModule.Running<PartnerRunningFl
|
|||||||
runningFlowContext.getFirstInputDateTime().toLocalDate()
|
runningFlowContext.getFirstInputDateTime().toLocalDate()
|
||||||
);
|
);
|
||||||
|
|
||||||
ExtractorResult extractorResult = memorySelectExtractor.execute(input);
|
ExtractorResult extractorResult = memoryRecallCueExtractor.execute(input);
|
||||||
if (extractorResult.getMatches().isEmpty()) {
|
if (extractorResult.getMatches().isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -149,17 +148,16 @@ public class MemorySelector extends AbstractAgentModule.Running<PartnerRunningFl
|
|||||||
.inputs(snapshotInputs)
|
.inputs(snapshotInputs)
|
||||||
.memorySlices(new ArrayList<>(candidates.values()))
|
.memorySlices(new ArrayList<>(candidates.values()))
|
||||||
.build();
|
.build();
|
||||||
return sliceSelectEvaluator.execute(evaluatorInput);
|
return memoryRecallEvaluator.execute(evaluatorInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setMemoryCandidates(LinkedHashMap<String, ActivatedMemorySlice> candidates,
|
private void setMemoryCandidates(LinkedHashMap<String, ActivatedMemorySlice> candidates, List<ExtractorResult.ExtractorMatchData> matches) {
|
||||||
List<ExtractorMatchData> matches) {
|
for (ExtractorResult.ExtractorMatchData match : matches) {
|
||||||
for (ExtractorMatchData match : matches) {
|
|
||||||
try {
|
try {
|
||||||
List<ActivatedMemorySlice> recalledSlices = switch (match.getType()) {
|
List<ActivatedMemorySlice> recalledSlices = switch (match.getType()) {
|
||||||
case ExtractorMatchData.Constant.TOPIC ->
|
case ExtractorResult.ExtractorMatchData.Constant.TOPIC ->
|
||||||
memoryRuntime.queryActivatedMemoryByTopicPath(match.getText());
|
memoryRuntime.queryActivatedMemoryByTopicPath(match.getText());
|
||||||
case ExtractorMatchData.Constant.DATE ->
|
case ExtractorResult.ExtractorMatchData.Constant.DATE ->
|
||||||
memoryRuntime.queryActivatedMemoryByDate(LocalDate.parse(match.getText()));
|
memoryRuntime.queryActivatedMemoryByDate(LocalDate.parse(match.getText()));
|
||||||
default -> List.of();
|
default -> List.of();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,7 +25,74 @@ import java.util.Locale;
|
|||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
public class SliceSelectEvaluator extends AbstractAgentModule.Sub<EvaluatorInput, List<ActivatedMemorySlice>> implements ActivateModel {
|
public class MemoryRecallEvaluator extends AbstractAgentModule.Sub<EvaluatorInput, List<ActivatedMemorySlice>> implements ActivateModel {
|
||||||
|
|
||||||
|
private static final String MODULE_PROMPT = """
|
||||||
|
你负责在记忆切片召回流程中,对单个候选 memory slice 做保留评估。你的任务不是重新提取检索线索,也不是在多个切片之间排序,而是基于当前新输入、近期 communication 语境,以及当前这个 memory slice 本身,判断:这段切片是否值得保留进入后续记忆上下文。
|
||||||
|
|
||||||
|
你会收到:
|
||||||
|
- 一条结构化上下文消息,其中包含当前活跃的 communication 域内容;
|
||||||
|
- 一条任务消息,其中包含:
|
||||||
|
- new_inputs:一组按时间顺序累积的新输入,每条输入附带 interval-to-first;
|
||||||
|
- memory_slice:当前正在评估的单个候选切片,其中包括:
|
||||||
|
- slice_summary:该切片的摘要;
|
||||||
|
- source_messages:该切片对应的部分原始消息。若消息较多,只会展示前两条、中间四条、末尾两条,并在中间插入省略说明。
|
||||||
|
|
||||||
|
你的任务:
|
||||||
|
- 判断当前这个 memory slice 是否与本轮输入明显相关,并且值得保留;
|
||||||
|
- 如果值得保留,则返回 passed=true;
|
||||||
|
- 如果不值得保留,则返回 passed=false。
|
||||||
|
|
||||||
|
评估目标:
|
||||||
|
- 这里的“值得保留”不是指“勉强有点关系”,而是指:这段切片很可能对理解当前输入、承接当前话题、补充当前回指对象、或支持接下来的回应有实际帮助。
|
||||||
|
- 你评估的是“当前这个切片自身是否值得保留”,而不是“是否存在某个相关记忆主题”。
|
||||||
|
- 不要把切片所属主题近期很活跃,误当成它当前就应被保留。
|
||||||
|
|
||||||
|
核心判断原则:
|
||||||
|
- new_inputs 应整体理解,不要只看最后一句;如果多条输入共同收敛到某个具体关注点,应按整体意图判断。
|
||||||
|
- communication 域用于辅助理解当前输入是否在承接、回指或延续近期对话中的某个对象、某段讨论、某个比较目标。
|
||||||
|
- memory_slice 是当前唯一需要评估的对象;判断重点在于它是否真正贴合当前输入,而不是它是否看起来“总体上像是相关的”。
|
||||||
|
- slice_summary 与 source_messages 应结合起来看;如果 summary 看似相关,但 source_messages 显示其实际讨论重点并不一致,则不应仅凭 summary 通过。
|
||||||
|
- source_messages 可能被截断展示,因此你可以基于已展示内容做审慎判断;若现有内容已足以看出明显不贴合,则应直接拒绝,不要自行脑补缺失内容。
|
||||||
|
|
||||||
|
何时应通过:
|
||||||
|
- 当前输入明显在追问、延续、回指、比较、复盘这段切片所对应的那段讨论;
|
||||||
|
- 当前输入中的代词、简称、模糊表达,结合 communication 与该切片内容后,可以较明确地对应到这段切片;
|
||||||
|
- 当前输入虽然没有复述切片内容,但它正在继续推进这段切片中的核心议题;
|
||||||
|
- 当前输入需要补足的背景、上下文或前情,正是该切片能够提供的;
|
||||||
|
- 该切片与当前输入在关注点上是同一条线,而不只是共享一些表层关键词。
|
||||||
|
|
||||||
|
何时不应通过:
|
||||||
|
- 该切片只是在宽泛主题上相关,但与当前输入的具体关注点并不一致;
|
||||||
|
- 当前输入谈的是某条主题中的一个更具体方向,而该切片实际对应的是同主题下的另一支内容;
|
||||||
|
- 该切片只是“最近讨论过”“当前活跃过”,但当前输入并没有明显指向它;
|
||||||
|
- 该切片与当前输入只有表层关键词重合,缺乏稳定的语义承接;
|
||||||
|
- 只能看出“可能有一点关系”,但不足以认为它对当前轮次真的有帮助;
|
||||||
|
- 当前输入是在排除、收窄或转离该切片所对应的方向;
|
||||||
|
- 仅凭切片摘要中的高层概括词、抽象标签、热门主题,就武断通过;
|
||||||
|
- 仅因为 communication 中存在宽泛相关话题,就把当前切片也一起保留。
|
||||||
|
|
||||||
|
关于评估尺度:
|
||||||
|
- 通过标准应偏保守,宁可少保留,也不要把低质量、弱相关、误方向的切片混入后续上下文。
|
||||||
|
- “属于同一个大主题”不等于“值得保留”。
|
||||||
|
- “曾经讨论过类似内容”不等于“当前就在指向这段切片”。
|
||||||
|
- “能勉强联想到”不等于“应通过”。
|
||||||
|
- 只有当你能够比较稳定地判断:这段切片对当前输入确实有帮助时,才应通过。
|
||||||
|
|
||||||
|
你不应做的事:
|
||||||
|
- 不要重新选择别的切片;
|
||||||
|
- 不要把别的可能更相关的记忆当成当前切片通过的理由;
|
||||||
|
- 不要扩展用户话题;
|
||||||
|
- 不要回答用户问题;
|
||||||
|
- 不要总结整个对话;
|
||||||
|
- 不要输出解释、理由、附加字段或额外文本;
|
||||||
|
- 不要因为不确定就倾向通过。
|
||||||
|
|
||||||
|
输出要求:
|
||||||
|
- 严格按照 EvaluatorBatchResult 对应结构输出;
|
||||||
|
- 结果中只表达当前这个 memory slice 是否通过;
|
||||||
|
- 不要输出除结构要求之外的任何内容。
|
||||||
|
""";
|
||||||
|
|
||||||
@InjectCapability
|
@InjectCapability
|
||||||
private ActionCapability actionCapability;
|
private ActionCapability actionCapability;
|
||||||
@@ -45,16 +112,12 @@ public class SliceSelectEvaluator extends AbstractAgentModule.Sub<EvaluatorInput
|
|||||||
List<ActivatedMemorySlice> result = new ArrayList<>();
|
List<ActivatedMemorySlice> result = new ArrayList<>();
|
||||||
CountDownLatch latch = new CountDownLatch(preparedSlices.size());
|
CountDownLatch latch = new CountDownLatch(preparedSlices.size());
|
||||||
|
|
||||||
Message contextMessage = cognitionCapability.contextWorkspace().resolve(List.of(
|
|
||||||
ContextBlock.FocusedDomain.MEMORY
|
|
||||||
)).encodeToMessage();
|
|
||||||
|
|
||||||
for (ActivatedMemorySlice slice : preparedSlices) {
|
for (ActivatedMemorySlice slice : preparedSlices) {
|
||||||
executor.execute(() -> {
|
executor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
EvaluatorBatchInput batchInput = new EvaluatorBatchInput(evaluatorInput.getInputs(), slice);
|
EvaluatorBatchInput batchInput = new EvaluatorBatchInput(evaluatorInput.getInputs(), slice);
|
||||||
List<Message> messages = List.of(
|
List<Message> messages = List.of(
|
||||||
contextMessage,
|
resolveContextMessage(),
|
||||||
resolveTaskMessage(batchInput)
|
resolveTaskMessage(batchInput)
|
||||||
);
|
);
|
||||||
formattedChat(messages, EvaluatorBatchResult.class)
|
formattedChat(messages, EvaluatorBatchResult.class)
|
||||||
@@ -74,11 +137,18 @@ public class SliceSelectEvaluator extends AbstractAgentModule.Sub<EvaluatorInput
|
|||||||
try {
|
try {
|
||||||
latch.await();
|
latch.await();
|
||||||
} catch (InterruptedException ignored) {
|
} catch (InterruptedException ignored) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Message resolveContextMessage() {
|
||||||
|
return cognitionCapability.contextWorkspace().resolve(List.of(
|
||||||
|
ContextBlock.FocusedDomain.COMMUNICATION
|
||||||
|
)).encodeToMessage();
|
||||||
|
}
|
||||||
|
|
||||||
private Message resolveTaskMessage(EvaluatorBatchInput batchInput) {
|
private Message resolveTaskMessage(EvaluatorBatchInput batchInput) {
|
||||||
return new TaskBlock() {
|
return new TaskBlock() {
|
||||||
@Override
|
@Override
|
||||||
@@ -137,6 +207,12 @@ public class SliceSelectEvaluator extends AbstractAgentModule.Sub<EvaluatorInput
|
|||||||
}.encodeToMessage();
|
}.encodeToMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public List<Message> modulePrompt() {
|
||||||
|
return List.of(new Message(Message.Character.SYSTEM, MODULE_PROMPT));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
public String modelKey() {
|
public String modelKey() {
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
package work.slhaf.partner.module.memory.selector.extractor;
|
||||||
|
|
||||||
|
import kotlin.Unit;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
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.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.memory.runtime.MemoryRuntime;
|
||||||
|
import work.slhaf.partner.module.memory.selector.extractor.entity.ExtractorInput;
|
||||||
|
import work.slhaf.partner.module.memory.selector.extractor.entity.ExtractorResult;
|
||||||
|
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MemoryRecallCueExtractor extends AbstractAgentModule.Sub<ExtractorInput, ExtractorResult> implements ActivateModel {
|
||||||
|
|
||||||
|
private static final String MODULE_PROMPT = """
|
||||||
|
你负责在记忆召回前,根据当前新输入与现有语境,提取本次值得检索的记忆线索。你的任务不是直接召回记忆内容,也不是总结对话,而是从当前输入中识别出:接下来应优先尝试检索哪些记忆主题路径,或是否应按具体日期检索记忆。
|
||||||
|
|
||||||
|
你会收到:
|
||||||
|
- 一条结构化上下文消息,其中包含当前活跃的 communication 域与 memory 域内容;
|
||||||
|
- 一条任务消息,其中包含:
|
||||||
|
- new_inputs:一组按时间顺序累积的新输入,每条输入附带 interval-to-first;
|
||||||
|
- current_date:当前日期;
|
||||||
|
- memory_topic_tree:当前可用的记忆主题树结构。
|
||||||
|
|
||||||
|
你的任务:
|
||||||
|
- 基于 new_inputs、当前语境与已有记忆主题树,提取本次记忆召回最值得尝试的匹配项;
|
||||||
|
- 匹配项只允许有两类:topic 或 date;
|
||||||
|
- topic 用于表示应优先检索的记忆主题路径;
|
||||||
|
- date 用于表示应优先检索的具体日期;
|
||||||
|
- 若当前输入不足以支持稳定的记忆检索线索,则返回空列表。
|
||||||
|
|
||||||
|
提取原则:
|
||||||
|
- 你的目标是提取“可用于后续召回”的线索,而不是复述输入内容本身。
|
||||||
|
- new_inputs 应整体理解,不要只抓最后一句;如果多条输入共同收敛到同一记忆方向,应提取更稳定的主题线索。
|
||||||
|
- communication 域用于判断当前输入是否在承接近期某段对话、某个旧话题或某个已出现过的指代对象。
|
||||||
|
- memory 域用于辅助判断当前输入与哪些已激活记忆方向明显相关;只有在这种相关性明确时才使用,不要机械复述 memory 域内容。
|
||||||
|
- memory_topic_tree 是 topic 提取的主要参照;topic 应尽量贴近主题树中已有的层级与命名,不要随意发明与主题树无关的新路径。
|
||||||
|
- 如果输入只能支持较上层的主题方向,则输出较短路径;不要为了显得具体而伪造下层节点。
|
||||||
|
- 如果输入同时指向多个可能的记忆方向,只保留最有召回价值、最稳定的少量结果,不要泛化扩散。
|
||||||
|
|
||||||
|
关于 topic:
|
||||||
|
- topic 表示一个记忆主题路径。
|
||||||
|
- topic 的 text 必须是纯路径文本,使用 `->` 连接层级,例如 A->B->C。
|
||||||
|
- topic 应尽量对齐 memory_topic_tree 中已有的节点表达、层级关系与命名习惯。
|
||||||
|
- topic 应体现“当前输入最可能在回指、延续或需要补充回忆的主题”。
|
||||||
|
- 不要输出过于空泛、没有检索价值的 topic,例如“聊天”“问题”“内容”“事情”这类抽象词。
|
||||||
|
- 不要在 topic 中附加解释、括号说明、标签注释或额外文字。
|
||||||
|
|
||||||
|
关于 date:
|
||||||
|
- date 表示一个明确的记忆日期。
|
||||||
|
- date 的 text 必须是可被 Java LocalDate.parse 正常解析的日期文本,即 yyyy-MM-dd。
|
||||||
|
- 只有在输入中存在明确日期,或结合 current_date 后可以稳定推断出具体某一天时,才输出 date。
|
||||||
|
- 像“今天”“昨天”“前天”“上周六”这类表达,只有在能够稳定落到某个具体日期时才可输出。
|
||||||
|
- 对于“最近”“前几天”“那段时间”“之前”“上次”这类无法稳定定位到某一天的表达,不要输出 date。
|
||||||
|
|
||||||
|
何时应提取 topic:
|
||||||
|
- 当前输入明显在延续某个已出现过的话题;
|
||||||
|
- 当前输入在追问、回指、比较、复盘某个过去讨论过的方向;
|
||||||
|
- 当前输入虽然表面简短,但结合 communication 或 memory 上下文后,可以明确看出其所指主题;
|
||||||
|
- 当前输入本身就在讨论一个具有长期记忆意义、适合按主题检索的内容。
|
||||||
|
|
||||||
|
何时应提取 date:
|
||||||
|
- 当前输入明确提到了某个具体日期;
|
||||||
|
- 当前输入使用相对日期表达,但结合 current_date 可以稳定还原到具体某一天;
|
||||||
|
- 当前输入中的回忆目标明显依赖某个特定日期,且该日期能够被明确确定。
|
||||||
|
|
||||||
|
何时不应轻易输出:
|
||||||
|
- 当前输入只是即时情绪、临时感叹或泛泛回应,没有形成稳定的记忆检索方向;
|
||||||
|
- 只能看出模糊相关性,无法判断应检索哪个主题;
|
||||||
|
- 只能看出模糊时间范围,无法稳定确定到某一天;
|
||||||
|
- 仅凭 memory 域里恰好出现过某个内容,但当前输入并没有明显指向它;
|
||||||
|
- 仅凭表层关键词联想出某条路径,但缺乏足够语境支持。
|
||||||
|
|
||||||
|
关于 matches 列表:
|
||||||
|
- 每个 match 只能是 topic 或 date 两种类型之一。
|
||||||
|
- 可以同时输出 topic 与 date;如果两者都对当前记忆召回明显有帮助,则都可以保留。
|
||||||
|
- 不要输出语义重复或明显冗余的 match。
|
||||||
|
- 如果已经能够稳定定位到较具体的 topic,通常不要再同时输出它的宽泛父路径,除非父路径本身也具有独立检索价值。
|
||||||
|
- 若没有足够明确、足够稳定的匹配项,返回空的 matches 列表。
|
||||||
|
|
||||||
|
其他约束:
|
||||||
|
- 你不是在生成记忆内容,只是在提取检索线索。
|
||||||
|
- 不要回答用户问题,不要总结输入,不要解释推理过程。
|
||||||
|
- 不要输出除 topic / date 之外的类型。
|
||||||
|
- 不要编造上下文中不存在的主题、事实或日期。
|
||||||
|
- 不要为了凑结果而输出低质量匹配项。
|
||||||
|
|
||||||
|
输出要求:
|
||||||
|
- 严格按照 ExtractorResult 对应结构输出。
|
||||||
|
- matches 中每一项都必须只包含 type 与 text。
|
||||||
|
- type 只能是 "topic" 或 "date"。
|
||||||
|
- topic 的 text 必须是 `A->B->C` 形式的纯路径文本。
|
||||||
|
- date 的 text 必须是 yyyy-MM-dd 形式的日期文本。
|
||||||
|
""";
|
||||||
|
|
||||||
|
@InjectCapability
|
||||||
|
private CognitionCapability cognitionCapability;
|
||||||
|
@InjectModule
|
||||||
|
private MemoryRuntime memoryRuntime;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ExtractorResult doExecute(ExtractorInput input) {
|
||||||
|
ExtractorResult extractorResult;
|
||||||
|
List<Message> messages = List.of(
|
||||||
|
resolveContextMessage(),
|
||||||
|
resolveTaskMessage(input)
|
||||||
|
);
|
||||||
|
Result<ExtractorResult> result = formattedChat(
|
||||||
|
messages,
|
||||||
|
ExtractorResult.class
|
||||||
|
);
|
||||||
|
extractorResult = result.fold(
|
||||||
|
value -> value,
|
||||||
|
exception -> {
|
||||||
|
ExtractorResult fallback = new ExtractorResult();
|
||||||
|
fallback.setMatches(List.of());
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return fix(extractorResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Message resolveTaskMessage(ExtractorInput input) {
|
||||||
|
return new TaskBlock() {
|
||||||
|
@Override
|
||||||
|
protected void fillXml(@NotNull Document document, @NotNull Element root) {
|
||||||
|
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")));
|
||||||
|
appendTextElement(document, root, "memory_topic_tree", input.getTopic_tree());
|
||||||
|
}
|
||||||
|
}.encodeToMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Message resolveContextMessage() {
|
||||||
|
return cognitionCapability.contextWorkspace().resolve(List.of(
|
||||||
|
ContextBlock.FocusedDomain.COMMUNICATION, ContextBlock.FocusedDomain.MEMORY
|
||||||
|
)).encodeToMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExtractorResult fix(ExtractorResult extractorResult) {
|
||||||
|
extractorResult.getMatches().forEach(m -> {
|
||||||
|
if (m.getType().equals(ExtractorResult.ExtractorMatchData.Constant.DATE)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m.setText(memoryRuntime.fixTopicPath(m.getText()));
|
||||||
|
});
|
||||||
|
if (extractorResult.getMatches().isEmpty()) {
|
||||||
|
return extractorResult;
|
||||||
|
}
|
||||||
|
extractorResult.getMatches().removeIf(m -> m.getText().split("->")[0].isEmpty());
|
||||||
|
return extractorResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public List<Message> modulePrompt() {
|
||||||
|
return List.of(new Message(Message.Character.SYSTEM, MODULE_PROMPT));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public String modelKey() {
|
||||||
|
return "topic_extractor";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
package work.slhaf.partner.module.memory.selector.extractor;
|
|
||||||
|
|
||||||
import kotlin.Unit;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.w3c.dom.Element;
|
|
||||||
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.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.memory.runtime.MemoryRuntime;
|
|
||||||
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.ExtractorResult;
|
|
||||||
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class MemorySelectExtractor extends AbstractAgentModule.Sub<ExtractorInput, ExtractorResult> implements ActivateModel {
|
|
||||||
@InjectCapability
|
|
||||||
private CognitionCapability cognitionCapability;
|
|
||||||
@InjectModule
|
|
||||||
private MemoryRuntime memoryRuntime;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ExtractorResult doExecute(ExtractorInput input) {
|
|
||||||
ExtractorResult extractorResult;
|
|
||||||
List<Message> messages = List.of(
|
|
||||||
resolveContextMessage(),
|
|
||||||
resolveTaskMessage(input)
|
|
||||||
);
|
|
||||||
Result<ExtractorResult> result = formattedChat(
|
|
||||||
messages,
|
|
||||||
ExtractorResult.class
|
|
||||||
);
|
|
||||||
extractorResult = result.fold(
|
|
||||||
value -> value,
|
|
||||||
exception -> {
|
|
||||||
ExtractorResult fallback = new ExtractorResult();
|
|
||||||
fallback.setMatches(List.of());
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return fix(extractorResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Message resolveTaskMessage(ExtractorInput input) {
|
|
||||||
return new TaskBlock() {
|
|
||||||
@Override
|
|
||||||
protected void fillXml(@NotNull Document document, @NotNull Element root) {
|
|
||||||
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")));
|
|
||||||
appendTextElement(document, root, "memory_topic_tree", input.getTopic_tree());
|
|
||||||
}
|
|
||||||
}.encodeToMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Message resolveContextMessage() {
|
|
||||||
return cognitionCapability.contextWorkspace().resolve(List.of(
|
|
||||||
ContextBlock.FocusedDomain.COMMUNICATION, ContextBlock.FocusedDomain.MEMORY
|
|
||||||
)).encodeToMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExtractorResult fix(ExtractorResult extractorResult) {
|
|
||||||
extractorResult.getMatches().forEach(m -> {
|
|
||||||
if (m.getType().equals(ExtractorMatchData.Constant.DATE)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m.setText(memoryRuntime.fixTopicPath(m.getText()));
|
|
||||||
});
|
|
||||||
if (extractorResult.getMatches().isEmpty()) {
|
|
||||||
return extractorResult;
|
|
||||||
}
|
|
||||||
extractorResult.getMatches().removeIf(m -> m.getText().split("->")[0].isEmpty());
|
|
||||||
return extractorResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String modelKey() {
|
|
||||||
return "topic_extractor";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package work.slhaf.partner.module.memory.selector.extractor.entity;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class ExtractorMatchData {
|
|
||||||
private String type;
|
|
||||||
private String text;
|
|
||||||
|
|
||||||
public static class Constant {
|
|
||||||
public static final String DATE = "date";
|
|
||||||
public static final String TOPIC = "topic";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,4 +7,15 @@ import java.util.List;
|
|||||||
@Data
|
@Data
|
||||||
public class ExtractorResult {
|
public class ExtractorResult {
|
||||||
private List<ExtractorMatchData> matches;
|
private List<ExtractorMatchData> matches;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class ExtractorMatchData {
|
||||||
|
private String type;
|
||||||
|
private String text;
|
||||||
|
|
||||||
|
public static class Constant {
|
||||||
|
public static final String DATE = "date";
|
||||||
|
public static final String TOPIC = "topic";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user