From e0543a8966de34b38c16b1d6a2fb5c3ebe6e1594 Mon Sep 17 00:00:00 2001 From: slhafzjw Date: Fri, 17 Apr 2026 23:16:20 +0800 Subject: [PATCH] refactor(communication): create prompts for summarizer, and optimize message structure --- .../slhaf/partner/core/action/ActionCore.java | 1 + .../core/cognition/CognitionCapability.java | 3 + .../partner/core/cognition/CognitionCore.java | 26 +- .../communication/CommunicationProducer.java | 42 +++- .../module/communication/DialogRolling.java | 12 +- .../summarizer/MessageCompressor.java | 238 ++++++++++++++++++ .../summarizer/MessageSummarizer.java | 89 +++++++ .../summarizer/MultiSummarizer.java | 32 --- .../summarizer/SingleSummarizer.java | 63 ----- .../CommunicationProducerTest.java | 12 +- .../communication/DialogRollingTest.java | 34 +-- .../memory/runtime/MemoryRuntimeTest.java | 6 + 12 files changed, 423 insertions(+), 135 deletions(-) create mode 100644 Partner-Core/src/main/java/work/slhaf/partner/module/communication/summarizer/MessageCompressor.java create mode 100644 Partner-Core/src/main/java/work/slhaf/partner/module/communication/summarizer/MessageSummarizer.java delete mode 100644 Partner-Core/src/main/java/work/slhaf/partner/module/communication/summarizer/MultiSummarizer.java delete mode 100644 Partner-Core/src/main/java/work/slhaf/partner/module/communication/summarizer/SingleSummarizer.java diff --git a/Partner-Core/src/main/java/work/slhaf/partner/core/action/ActionCore.java b/Partner-Core/src/main/java/work/slhaf/partner/core/action/ActionCore.java index 2aa4f495..8bbb678a 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/core/action/ActionCore.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/core/action/ActionCore.java @@ -81,6 +81,7 @@ public class ActionCore implements StateSerializable { log.warn("{} tasks still running", count); } } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); } } diff --git a/Partner-Core/src/main/java/work/slhaf/partner/core/cognition/CognitionCapability.java b/Partner-Core/src/main/java/work/slhaf/partner/core/cognition/CognitionCapability.java index 0f3674e6..70fd9af6 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/core/cognition/CognitionCapability.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/core/cognition/CognitionCapability.java @@ -1,5 +1,6 @@ package work.slhaf.partner.core.cognition; +import org.w3c.dom.Element; import work.slhaf.partner.framework.agent.factory.capability.annotation.Capability; import work.slhaf.partner.framework.agent.model.pojo.Message; @@ -21,6 +22,8 @@ public interface CognitionCapability { void refreshRecentChatMessagesContext(); + Element messageNotesElement(); + Lock getMessageLock(); } diff --git a/Partner-Core/src/main/java/work/slhaf/partner/core/cognition/CognitionCore.java b/Partner-Core/src/main/java/work/slhaf/partner/core/cognition/CognitionCore.java index 9ec10174..0ee40e97 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/core/cognition/CognitionCore.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/core/cognition/CognitionCore.java @@ -7,6 +7,7 @@ import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.w3c.dom.Document; import org.w3c.dom.Element; +import work.slhaf.partner.common.base.Block; import work.slhaf.partner.framework.agent.factory.capability.annotation.CapabilityCore; import work.slhaf.partner.framework.agent.factory.capability.annotation.CapabilityMethod; import work.slhaf.partner.framework.agent.interaction.AgentRuntime; @@ -30,13 +31,22 @@ public class CognitionCore implements StateSerializable { private static final String RECENT_CHAT_MESSAGE_NOTES = """ 消息格式: - - user 消息当前写入格式: [[USER]: ]: <正文> 或 [[AGENT]: self]: <正文> - - assistant 消息直接记录回复正文;若以 [NOT_REPLIED]: <正文> 形式出现,表示该结果未直接发送给用户 + - 所有消息统一写为“标记行 + 空行 + 正文”,比如: + + [[AGENT]: self]: [NOT_REPLIED][COMPRESSED]: + + 正文内容 + + - 标记行一定包含身份标签,通常格式为 [[USER]: ] 或 [[AGENT]: self] + - 若身份标签提取失败,可能回退为 [[Unknown]: Unknown] + - 若存在其他标签,则写为“身份标签: 状态标签串:” + - 正文永远从空行后开始 标记含义: - [USER]: 外部用户来源 - [AGENT]: 系统内部来源 - [NOT_REPLIED]: 仅保留在历史中的未直接回复结果 + - [COMPRESSED]: 该消息正文经过压缩 """; private final ReentrantLock messageLock = new ReentrantLock(); @@ -117,7 +127,7 @@ public class CognitionCore implements StateSerializable { new BlockContent("recent_chat_messages", "communication_producer") { @Override protected void fillXml(@NotNull Document document, @NotNull Element root) { - appendTextElement(document, root, "message_tag_notes", RECENT_CHAT_MESSAGE_NOTES); + document.appendChild(document.importNode(messageNotesElement(), true)); Element chatMessagesElement = document.createElement("chat_messages"); root.appendChild(chatMessagesElement); appendRepeatedElements(document, chatMessagesElement, "chat_message", resolveRecentChatMessages(), (messageElement, message) -> { @@ -135,6 +145,16 @@ public class CognitionCore implements StateSerializable { contextWorkspace.register(block); } + @CapabilityMethod + public Element messageNotesElement() { + return new Block("message_tag_notes") { + @Override + protected void fillXml(@NotNull Document document, @NotNull Element root) { + root.setTextContent(RECENT_CHAT_MESSAGE_NOTES); + } + }.encodeToXml(); + } + private List resolveRecentChatMessages() { int exclusiveEnd = Math.max(chatMessages.size() - 1, 0); if (exclusiveEnd == 0) { diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/communication/CommunicationProducer.java b/Partner-Core/src/main/java/work/slhaf/partner/module/communication/CommunicationProducer.java index 43f6d5da..4390c487 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/communication/CommunicationProducer.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/communication/CommunicationProducer.java @@ -29,8 +29,9 @@ public class CommunicationProducer extends AbstractAgentModule.Running { - consumer.onDelta(INTERRUPTED_MARKER); - }); + .onFailure(exception -> consumer.onDelta(INTERRUPTED_MARKER)); updateChatMessages(runningFlowContext, consumer.collectResponse()); cognitionCapability.refreshRecentChatMessagesContext(); } @@ -115,7 +114,10 @@ public class CommunicationProducer extends AbstractAgentModule.Running snapshotConversationMessages() { @@ -189,7 +199,17 @@ public class CommunicationProducer extends AbstractAgentModule.Running chatSnapshot, int rollingSize, int retainDivisor) { - singleSummarizer.execute(chatSnapshot); - Result summaryResult = multiSummarizer.execute(chatSnapshot); + messageCompressor.execute(chatSnapshot); + Result summaryResult = messageSummarizer.execute(chatSnapshot); String summary = summaryResult.fold( value -> value, exp -> "no summary, due to exception" diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/communication/summarizer/MessageCompressor.java b/Partner-Core/src/main/java/work/slhaf/partner/module/communication/summarizer/MessageCompressor.java new file mode 100644 index 00000000..f61ec4cc --- /dev/null +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/communication/summarizer/MessageCompressor.java @@ -0,0 +1,238 @@ +package work.slhaf.partner.module.communication.summarizer; + +import org.jetbrains.annotations.NotNull; +import work.slhaf.partner.core.action.ActionCapability; +import work.slhaf.partner.core.action.ActionCore; +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.Init; +import work.slhaf.partner.framework.agent.model.ActivateModel; +import work.slhaf.partner.framework.agent.model.pojo.Message; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.IntStream; + +public class MessageCompressor extends AbstractAgentModule.Sub, Void> implements ActivateModel { + + private static final String MODULE_PROMPT = """ + 你负责对单条消息进行压缩改写。 + + 目标: + - 在不改变原意的前提下,压缩冗余表达,减少长度; + - 保留消息中真正有价值的信息; + - 让压缩结果仍然像原消息,而不是另一种文体的摘要。 + + 核心要求: + - 尽量保留原消息的视角、语气、态度、情绪与表达倾向,不要无故改写成中性、客观、旁白式总结。 + - 不要把第一人称改成第三人称;不要把直接表达改成“用户表示……”“其意思是……”这类转述,除非原消息本身就是这种口吻。 + - 若原消息包含明显的情绪、评价、犹豫、强调、否定、推进意图、反问、吐槽等内容,压缩后应尽量保留这些信息。 + - 压缩的重点是删除冗余、合并重复、收紧表达,不是改写说话风格。 + + 格式要求: + - 允许保留原消息中已有的 markdown、标题、项目符号、编号列表、引用、代码块、代码片段等结构。 + - 不要为了压缩而强行去除这些结构;若这些结构本身承载了信息层级或语义边界,应尽量保留。 + - 也不要为了“更整齐”主动新增原消息没有的标题、列表或代码块。 + - 原消息有结构时,优先继承其组织方式;原消息没有结构时,保持自然文本即可。 + + 压缩策略: + - 删除明显重复、空转、口头垫话、对主旨无帮助的展开。 + - 合并语义接近、重复推进的句子。 + - 保留真正影响理解的事实、判断、条件、限制、结论、态度和情绪。 + - 若原消息包含技术内容、代码、配置、接口、规则、步骤等,优先保留这些实质信息,不要只保留泛泛结论。 + - 若原消息本身已经很短或进一步压缩会损失重要语义,则可基本保持原样。 + + 关于日志、代码及长文本片段: + - 若原消息中包含日志、代码、配置、报错堆栈、命令输出等长片段,且内容较长、重复性强或并非全部都对理解当前消息同等重要,则可以进行截断。 + - 截断时应优先保留: + - 与当前问题、判断、结论直接相关的部分; + - 首尾中能体现上下文和结果的关键部分; + - 报错、异常、返回值、状态变化、关键参数、关键命令、关键代码段。 + - 不要无说明地直接删去中间内容;若发生截断,必须显式标注。 + - 截断标注统一使用以下格式之一,并与原文风格保持尽量一致: + - `...[中间内容已截断]...` + - 代码或日志块内可使用:`// ...[中间内容已截断]...` 或 `# ...[中间内容已截断]...` + - 截断后的内容仍应保持可读,且不能歪曲原始含义。 + - 若长片段本身就是当前消息的核心,且截断会损失关键语义,则不要截断。 + + 禁止事项: + - 不要补充原消息没有的新信息。 + - 不要替原消息做解释、分析、总结或评价。 + - 不要把技术表达改写得过于口语化,也不要把口语表达改写得过于书面化。 + - 不要输出“压缩后:”之类前缀,只直接输出压缩结果。 + + 输出要求: + - 只输出压缩后的消息正文。 + """; + + private static final int COMPRESS_TRIGGER_LENGTH = 1200; + private static final int FALLBACK_MAX_LENGTH = 900; + private static final String FALLBACK_OMITTED_MARKER = "\n...[中间内容已裁剪]...\n"; + private static final String COMPRESSED_MARKER = "[COMPRESSED]"; + private static final String UNKNOWN_ROLE_MARKER = "[[Unknown]: Unknown]"; + private static final String MARKER_BODY_SEPARATOR = ":\n\n"; + private static final Pattern ROLE_PREFIX_PATTERN = Pattern.compile("(\\[\\[(?:USER|AGENT)]:\\s*[^]]+])"); + + @InjectCapability + private ActionCapability actionCapability; + + private ExecutorService executor; + + @Init + public void init() { + executor = actionCapability.getExecutor(ActionCore.ExecutorType.VIRTUAL); + } + + @Override + protected Void doExecute(List chatMessages) { + List targetIndexes = IntStream.range(0, chatMessages.size()) + .filter(index -> shouldCompress(chatMessages.get(index))) + .boxed() + .toList(); + CountDownLatch latch = new CountDownLatch(targetIndexes.size()); + for (Integer index : targetIndexes) { + Message chatMessage = chatMessages.get(index); + ParsedMessage parsedMessage = parseMessage(chatMessage.getContent()); + executor.execute(() -> { + try { + String summarized = summarizeOrFallback(parsedMessage.body()); + chatMessages.set(index, new Message(chatMessage.getRole(), rebuildMessage(parsedMessage, summarized))); + } finally { + latch.countDown(); + } + }); + } + try { + latch.await(); + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + } + return null; + } + + private boolean shouldCompress(Message chatMessage) { + return parseMessage(chatMessage.getContent()).body().length() > COMPRESS_TRIGGER_LENGTH; + } + + private String summarizeOrFallback(String content) { + String summarized = chat(List.of(new Message(Message.Character.USER, content))).fold( + res -> res, + exp -> null + ); + if (isAcceptableSummary(summarized, content)) { + return summarized.trim(); + } + return truncateForFallback(content); + } + + private boolean isAcceptableSummary(String summarized, String originalContent) { + if (summarized == null) { + return false; + } + String normalized = summarized.trim(); + return !normalized.isEmpty() && normalized.length() < originalContent.length(); + } + + private String truncateForFallback(String content) { + if (content == null || content.length() <= FALLBACK_MAX_LENGTH) { + return content; + } + int available = FALLBACK_MAX_LENGTH - FALLBACK_OMITTED_MARKER.length(); + int headBudget = available / 2; + int tailBudget = available - headBudget; + int headEnd = adjustHeadEnd(content, headBudget); + int tailStart = adjustTailStart(content, content.length() - tailBudget); + if (tailStart <= headEnd) { + return content.substring(0, FALLBACK_MAX_LENGTH).stripTrailing(); + } + return content.substring(0, headEnd).stripTrailing() + + FALLBACK_OMITTED_MARKER + + content.substring(tailStart).stripLeading(); + } + + private int adjustHeadEnd(String content, int preferredEnd) { + int safePreferredEnd = Math.clamp(preferredEnd, 0, content.length()); + int windowEnd = Math.min(content.length(), safePreferredEnd + 80); + for (int i = safePreferredEnd; i < windowEnd; i++) { + if (isBoundary(content.charAt(i))) { + return i + 1; + } + } + return safePreferredEnd; + } + + private int adjustTailStart(String content, int preferredStart) { + int safePreferredStart = Math.clamp(preferredStart, 0, content.length()); + int windowStart = Math.max(0, safePreferredStart - 80); + for (int i = safePreferredStart; i > windowStart; i--) { + if (isBoundary(content.charAt(i - 1))) { + return i; + } + } + return safePreferredStart; + } + + private boolean isBoundary(char ch) { + return ch == '\n' + || ch == '。' + || ch == '!' + || ch == '?' + || ch == ';' + || ch == ';' + || ch == '.'; + } + + private ParsedMessage parseMessage(String content) { + String source = content == null ? "" : content; + int separatorIndex = source.indexOf(MARKER_BODY_SEPARATOR); + String markerLine = separatorIndex >= 0 ? source.substring(0, separatorIndex).trim() : ""; + String remaining = separatorIndex >= 0 ? source.substring(separatorIndex + MARKER_BODY_SEPARATOR.length()).trim() : source.trim(); + String rolePrefix = null; + String statusMarkers = ""; + + Matcher roleMatcher = ROLE_PREFIX_PATTERN.matcher(markerLine); + if (roleMatcher.find()) { + rolePrefix = roleMatcher.group(1); + statusMarkers = markerLine.substring(roleMatcher.end()).trim(); + if (statusMarkers.startsWith(":")) { + statusMarkers = statusMarkers.substring(1).trim(); + } + if (statusMarkers.endsWith(":")) { + statusMarkers = statusMarkers.substring(0, statusMarkers.length() - 1).trim(); + } + } + return new ParsedMessage(rolePrefix, statusMarkers, remaining); + } + + private String rebuildMessage(ParsedMessage parsedMessage, String compressedBody) { + return buildMarkerHeader(parsedMessage.rolePrefix(), parsedMessage.statusMarkers()) + + MARKER_BODY_SEPARATOR + + compressedBody; + } + + private String buildMarkerHeader(String rolePrefix, String statusMarkers) { + String identityMarker = rolePrefix == null || rolePrefix.isBlank() ? UNKNOWN_ROLE_MARKER : rolePrefix; + String normalizedStatusMarkers = statusMarkers == null ? "" : statusMarkers.trim(); + normalizedStatusMarkers = normalizedStatusMarkers.replace(COMPRESSED_MARKER, "").trim(); + normalizedStatusMarkers += COMPRESSED_MARKER; + return identityMarker + ": " + normalizedStatusMarkers; + } + + @Override + @NotNull + public List modulePrompt() { + return List.of(new Message(Message.Character.SYSTEM, MODULE_PROMPT)); + } + + @NotNull + @Override + public String modelKey() { + return "single_summarizer"; + } + + private record ParsedMessage(String rolePrefix, String statusMarkers, String body) { + } +} diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/communication/summarizer/MessageSummarizer.java b/Partner-Core/src/main/java/work/slhaf/partner/module/communication/summarizer/MessageSummarizer.java new file mode 100644 index 00000000..0a7fedc2 --- /dev/null +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/communication/summarizer/MessageSummarizer.java @@ -0,0 +1,89 @@ +package work.slhaf.partner.module.communication.summarizer; + +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.framework.agent.factory.capability.annotation.InjectCapability; +import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule; +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 java.util.List; + +public class MessageSummarizer extends AbstractAgentModule.Sub, Result> implements ActivateModel { + + private static final String MODULE_PROMPT = """ + 你负责对一组已经发生过的聊天消息进行总结整理,生成一段可供后续系统使用的摘要结果。 + + 你会收到一条结构化任务消息,其中: + - 说明聊天消息中可能出现的标签及其含义; + - 承载本次需要总结的消息列表,每条消息都带有 role 与正文内容。 + + 你的任务: + - 基于整组消息,提炼出一段紧凑、连贯、信息完整的摘要; + - 摘要应尽量覆盖这组消息中的主要事实、结论、约束、推进情况、未决点、明显态度与情绪变化; + - 若消息中包含技术讨论、配置、代码、报错、规则、方案比较、设计判断等内容,应优先保留这些对后续理解真正有帮助的信息。 + + 摘要视角要求: + - 摘要默认采用 AGENT 视角书写,即以“我”的立场整理这组对话,而不是使用外部旁观者口吻。 + - 对于来自 [AGENT] 或 assistant 的消息,可将其理解为我的表达、我的判断、我的推进、我的反思或我的内部反馈,并以“我”来概括。 + - 对于来自 [USER] 的消息,应明确保留其“用户”身份,不要模糊为无来源的陈述,也不要误写成“我”的观点。 + - 不要默认把整组消息改写成“用户近期……”“系统如何……”这类第三人称阶段报告,除非原消息本身就是这种汇报视角。 + - 若消息中出现 [NOT_REPLIED],表示这是一条我未直接发给用户、但保留在交流轨迹中的内部交流结果;必要时可在摘要中说明这是我内部保留的判断或反馈。 + + 总结原则: + - 重点提炼这组消息中真正影响后续理解和推进的信息,不要平均分配篇幅。 + - 合并重复表达、重复确认和多轮来回拉扯后的同类结论。 + - 若消息中形成了明确结论、决定、偏好、限制条件、行动推进或阶段性判断,应优先写出。 + - 若消息中仍存在未解决问题、待确认事项、分歧点或风险点,也应明确保留。 + - 若消息整体只是闲聊、感叹或状态表达,也应如实概括其主要情绪和交流走向,不要硬总结出不存在的任务结论。 + + 关于技术内容: + - 若消息中包含代码、日志、命令输出、配置片段等长内容,不要原样大段复写; + - 应概括其中真正关键的信息,例如:关键报错、关键配置、关键判断、关键修改点、关键结果。 + - 只有在少量原文片段对后续理解不可替代时,才可保留必要短句。 + + 输出要求: + - 只输出一段摘要正文,不要添加标题、前缀、说明或额外标签。 + - 不要输出项目符号列表,除非原始内容极度结构化且不用列表会明显损失可读性。 + - 不要输出结构之外的解释、注释或额外文本。 + """; + + @InjectCapability + private CognitionCapability cognitionCapability; + + @Override + protected @NotNull Result doExecute(List messages) { + return chat(List.of(buildChatMessagesBlock(messages).encodeToMessage())); + } + + private @NotNull TaskBlock buildChatMessagesBlock(List messages) { + return new TaskBlock() { + @Override + protected void fillXml(@NotNull Document document, @NotNull Element root) { + document.appendChild(document.importNode(cognitionCapability.messageNotesElement(), true)); + appendListElement(document, root, "chat_messages", "message", messages, (element, message) -> { + element.setAttribute("role", message.roleValue()); + element.setTextContent(message.getContent()); + return Unit.INSTANCE; + }); + } + }; + } + + @Override + @NotNull + public List modulePrompt() { + return List.of(new Message(Message.Character.SYSTEM, MODULE_PROMPT)); + } + + @NotNull + @Override + public String modelKey() { + return "multi_summarizer"; + } +} diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/communication/summarizer/MultiSummarizer.java b/Partner-Core/src/main/java/work/slhaf/partner/module/communication/summarizer/MultiSummarizer.java deleted file mode 100644 index e5767df1..00000000 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/communication/summarizer/MultiSummarizer.java +++ /dev/null @@ -1,32 +0,0 @@ -package work.slhaf.partner.module.communication.summarizer; - -import cn.hutool.json.JSONUtil; -import org.jetbrains.annotations.NotNull; -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.memory.runtime.MemoryRuntime; - -import java.util.List; - -public class MultiSummarizer extends AbstractAgentModule.Sub, Result> implements ActivateModel { - - @InjectModule - private MemoryRuntime memoryRuntime; - - @Override - protected @NotNull Result doExecute(List messages) { - return chat(List.of(new Message( - Message.Character.USER, - JSONUtil.toJsonPrettyStr(messages))) - ); - } - - @NotNull - @Override - public String modelKey() { - return "multi_summarizer"; - } -} diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/communication/summarizer/SingleSummarizer.java b/Partner-Core/src/main/java/work/slhaf/partner/module/communication/summarizer/SingleSummarizer.java deleted file mode 100644 index 27ae0891..00000000 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/communication/summarizer/SingleSummarizer.java +++ /dev/null @@ -1,63 +0,0 @@ -package work.slhaf.partner.module.communication.summarizer; - -import org.jetbrains.annotations.NotNull; -import work.slhaf.partner.core.action.ActionCapability; -import work.slhaf.partner.core.action.ActionCore; -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.Init; -import work.slhaf.partner.framework.agent.model.ActivateModel; -import work.slhaf.partner.framework.agent.model.pojo.Message; - -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; - -public class SingleSummarizer extends AbstractAgentModule.Sub, Void> implements ActivateModel { - - @InjectCapability - private ActionCapability actionCapability; - - private ExecutorService executor; - - @Init - public void init() { - executor = actionCapability.getExecutor(ActionCore.ExecutorType.VIRTUAL); - } - - @Override - protected Void doExecute(List chatMessages) { - CountDownLatch latch = new CountDownLatch(chatMessages.size()); - for (int i = 0; i < chatMessages.size(); i++) { - Message chatMessage = chatMessages.get(i); - if (chatMessage.getRole() == Message.Character.ASSISTANT) { - String content = chatMessage.getContent(); - if (chatMessage.getContent().length() > 500) { - int index = i; - executor.execute(() -> { - try { - String summarized = chat(List.of(new Message(Message.Character.USER, content))).fold( - res -> res, - exp -> content - ); - chatMessages.set(index, new Message(Message.Character.ASSISTANT, summarized)); - } finally { - latch.countDown(); - } - }); - } - } - } - try { - latch.await(); - } catch (InterruptedException ignored) { - } - return null; - } - - @NotNull - @Override - public String modelKey() { - return "single_summarizer"; - } -} diff --git a/Partner-Core/src/test/java/work/slhaf/partner/module/communication/CommunicationProducerTest.java b/Partner-Core/src/test/java/work/slhaf/partner/module/communication/CommunicationProducerTest.java index bd20a6ac..da356eec 100644 --- a/Partner-Core/src/test/java/work/slhaf/partner/module/communication/CommunicationProducerTest.java +++ b/Partner-Core/src/test/java/work/slhaf/partner/module/communication/CommunicationProducerTest.java @@ -1,6 +1,7 @@ package work.slhaf.partner.module.communication; import org.junit.jupiter.api.Test; +import org.w3c.dom.Element; import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.ContextWorkspace; import work.slhaf.partner.framework.agent.model.pojo.Message; @@ -51,8 +52,8 @@ class CommunicationProducerTest { List chatMessages = cognitionCapability.getChatMessages(); assertEquals(2, chatMessages.size()); - assertEquals("[[USER]: user-1]: hello", chatMessages.get(0).getContent()); - assertEquals("[NOT_REPLIED]: not now", chatMessages.get(1).getContent()); + assertEquals("[[USER]: user-1]:\n\nhello", chatMessages.get(0).getContent()); + assertEquals("[[AGENT]: self]: [NOT_REPLIED]:\n\nnot now", chatMessages.get(1).getContent()); } @Test @@ -68,7 +69,7 @@ class CommunicationProducerTest { ); List chatMessages = cognitionCapability.getChatMessages(); - assertEquals("normal reply", chatMessages.get(1).getContent()); + assertEquals("[[AGENT]: self]:\n\nnormal reply", chatMessages.get(1).getContent()); } private static final class StubCognitionCapability implements CognitionCapability { @@ -103,6 +104,11 @@ class CommunicationProducerTest { public void refreshRecentChatMessagesContext() { } + @Override + public Element messageNotesElement() { + return null; + } + @Override public Lock getMessageLock() { return lock; diff --git a/Partner-Core/src/test/java/work/slhaf/partner/module/communication/DialogRollingTest.java b/Partner-Core/src/test/java/work/slhaf/partner/module/communication/DialogRollingTest.java index e19c41a4..53d5542b 100644 --- a/Partner-Core/src/test/java/work/slhaf/partner/module/communication/DialogRollingTest.java +++ b/Partner-Core/src/test/java/work/slhaf/partner/module/communication/DialogRollingTest.java @@ -9,8 +9,8 @@ import work.slhaf.partner.core.memory.pojo.MemorySlice; import work.slhaf.partner.core.memory.pojo.MemoryUnit; import work.slhaf.partner.framework.agent.model.pojo.Message; import work.slhaf.partner.framework.agent.support.Result; -import work.slhaf.partner.module.communication.summarizer.MultiSummarizer; -import work.slhaf.partner.module.communication.summarizer.SingleSummarizer; +import work.slhaf.partner.module.communication.summarizer.MessageCompressor; +import work.slhaf.partner.module.communication.summarizer.MessageSummarizer; import java.lang.reflect.Field; import java.nio.file.Path; @@ -42,13 +42,13 @@ class DialogRollingTest { String sessionId = "dialog-rolling-" + UUID.randomUUID(); StubMemoryCapability memoryCapability = new StubMemoryCapability(sessionId); DialogRolling dialogRolling = new DialogRolling(); - MultiSummarizer multiSummarizer = Mockito.mock(MultiSummarizer.class); - SingleSummarizer singleSummarizer = Mockito.mock(SingleSummarizer.class); + MessageSummarizer messageSummarizer = Mockito.mock(MessageSummarizer.class); + MessageCompressor messageCompressor = Mockito.mock(MessageCompressor.class); setField(dialogRolling, "memoryCapability", memoryCapability); - setField(dialogRolling, "multiSummarizer", multiSummarizer); - setField(dialogRolling, "singleSummarizer", singleSummarizer); + setField(dialogRolling, "messageSummarizer", messageSummarizer); + setField(dialogRolling, "messageCompressor", messageCompressor); - when(multiSummarizer.execute(Mockito.any())).thenReturn(Result.success("new-summary")); + when(messageSummarizer.execute(Mockito.any())).thenReturn(Result.success("new-summary")); MemoryUnit existingUnit = new MemoryUnit(sessionId); existingUnit.getConversationMessages().addAll(List.of( @@ -83,13 +83,13 @@ class DialogRollingTest { String sessionId = "dialog-rolling-" + UUID.randomUUID(); StubMemoryCapability memoryCapability = new StubMemoryCapability(sessionId); DialogRolling dialogRolling = new DialogRolling(); - MultiSummarizer multiSummarizer = Mockito.mock(MultiSummarizer.class); - SingleSummarizer singleSummarizer = Mockito.mock(SingleSummarizer.class); + MessageSummarizer messageSummarizer = Mockito.mock(MessageSummarizer.class); + MessageCompressor messageCompressor = Mockito.mock(MessageCompressor.class); setField(dialogRolling, "memoryCapability", memoryCapability); - setField(dialogRolling, "multiSummarizer", multiSummarizer); - setField(dialogRolling, "singleSummarizer", singleSummarizer); + setField(dialogRolling, "messageSummarizer", messageSummarizer); + setField(dialogRolling, "messageCompressor", messageCompressor); - when(multiSummarizer.execute(Mockito.any())).thenReturn(Result.success("fresh-summary")); + when(messageSummarizer.execute(Mockito.any())).thenReturn(Result.success("fresh-summary")); RollingResult rollingResult = dialogRolling.buildRollingResult(List.of( message(Message.Character.USER, "first"), @@ -138,13 +138,13 @@ class DialogRollingTest { String sessionId = "dialog-rolling-" + UUID.randomUUID(); StubMemoryCapability memoryCapability = new StubMemoryCapability(sessionId); DialogRolling dialogRolling = new DialogRolling(); - MultiSummarizer multiSummarizer = Mockito.mock(MultiSummarizer.class); - SingleSummarizer singleSummarizer = Mockito.mock(SingleSummarizer.class); + MessageSummarizer messageSummarizer = Mockito.mock(MessageSummarizer.class); + MessageCompressor messageCompressor = Mockito.mock(MessageCompressor.class); setField(dialogRolling, "memoryCapability", memoryCapability); - setField(dialogRolling, "multiSummarizer", multiSummarizer); - setField(dialogRolling, "singleSummarizer", singleSummarizer); + setField(dialogRolling, "messageSummarizer", messageSummarizer); + setField(dialogRolling, "messageCompressor", messageCompressor); - when(multiSummarizer.execute(Mockito.any())).thenReturn(Result.success(" ")); + when(messageSummarizer.execute(Mockito.any())).thenReturn(Result.success(" ")); RollingResult rollingResult = dialogRolling.buildRollingResult(List.of( message(Message.Character.USER, "u1"), diff --git a/Partner-Core/src/test/java/work/slhaf/partner/module/memory/runtime/MemoryRuntimeTest.java b/Partner-Core/src/test/java/work/slhaf/partner/module/memory/runtime/MemoryRuntimeTest.java index 62d36617..a2b53305 100644 --- a/Partner-Core/src/test/java/work/slhaf/partner/module/memory/runtime/MemoryRuntimeTest.java +++ b/Partner-Core/src/test/java/work/slhaf/partner/module/memory/runtime/MemoryRuntimeTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.w3c.dom.Element; import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.memory.MemoryCapability; import work.slhaf.partner.core.memory.pojo.MemorySlice; @@ -93,6 +94,11 @@ class MemoryRuntimeTest { } + @Override + public Element messageNotesElement() { + return null; + } + @Override public Lock getMessageLock() { return lock;