☰
openJiuwen agent-core-java 记忆提取机制解析:从对话消息到长期记忆单元
2026/10/13 13:22:02 网站建设 项目流程
  • 人工智能
  • AI Agent
  • Agent 框架
  • 工具调用
  • Agent 工作流
  • RAG
  • MCP Clients
  • 强化学习

【免费下载链接】agent-core-java

openJiuwen agent core Java是 openJiuwen Core的 Java版本,提供AI Agent开发、运行、调优与演进相关的全套SDK能力。

项目地址:https://gitcode.com/openJiuwen/agent-core-java
点击查看免费下载

com.openjiuwen.memory.process.extract是 openJiuwen agent-core-java 记忆引擎中的「提取」环节,负责把一轮对话消息转换成可落库的长期记忆结果,覆盖参数封装、模型分析、摘要生成与片段记忆提取。本文以该模块为核心,逐层拆解MemoryAnalyzer、LongTermMemoryExtractor、Generator三类核心组件的职责与调用链,并结合源码、提示词模板与测试用例给出可验证的实现细节,帮助读者理解并复用这套「消息 → 记忆单元」的流水线。

一、模块定位:extract 在记忆流程中的角色

记忆引擎通常包含「提取 → 管理 → 检索」等环节,extract包对应最前端的提取阶段。按照 extract.README.md 的说明,该包负责把对话消息转换为长期记忆相关结果,具体覆盖四个方面:

  • 参数封装:把提取流程所需的用户、消息与模型参数收敛为ExtractMemoryParams;
  • 模型分析:通过MemoryAnalyzer分析消息内容,输出关键信息标记、变量结果与摘要;
  • 摘要生成:按配置决定是否生成摘要记忆;
  • 片段记忆提取:通过LongTermMemoryExtractor抽取用户画像、语义记忆、情景记忆三类长期片段。

模块内共包含 6 个公开类型,核心类型总览如下(与原文档一致):

类型说明
ExtractMemoryParams封装提取流程所需的用户、消息与模型参数。
Generator协调分析器与提取器,生成变量、摘要和片段记忆单元。
LongTermMemoryExtractor使用模型和提示词抽取长期片段记忆。
MemoryAnalyzer分析消息内容,输出关键信息标记、变量结果与摘要。
MemoryAnalyzerResult保存分析阶段返回的结构化结果。
VariableResult表示单个变量的键值抽取结果。

二、数据对象层:参数封装与结构化输出

ExtractMemoryParams、MemoryAnalyzerResult、VariableResult三个类是面向流程编排的轻量数据对象,负责在两阶段调用(分析 → 生成)之间传递数据。

2.1 ExtractMemoryParams:提取流程的入参集合

ExtractMemoryParams封装记忆提取阶段使用的用户、消息历史和基础模型参数,字段定义如下(见 ExtractMemoryParams.java):

字段类型说明
userIdString当前用户标识。
scopeIdString当前记忆作用域标识。
messagesList<BaseMessage>本轮需要分析的消息列表。
historyMessagesList<BaseMessage>可选的历史消息列表。
baseChatModelModel基础聊天模型实例。

在源码中,Generator.genAllMemorySync会从kwargs中取出这五个参数组装该对象,再传给LongTermMemoryExtractor.extractLongTermMemory(...)使用;Generator同时支持无SearchManager与带SearchManager两种构造方式,后者用于处理「修改/删除记忆」指令时的语义检索。

2.2 MemoryAnalyzerResult:分析阶段的结构化输出

MemoryAnalyzerResult表示消息分析阶段的输出(见 MemoryAnalyzerResult.java):

字段类型默认值说明
hasKeyInformationbooleanfalse标记当前消息中是否包含可提取的关键信息。
variablesList<VariableResult>空列表提取出的变量结果列表。
summaryString空字符串生成的摘要文本。

Generator会读取其中的变量列表、摘要与hasKeyInformation标记,决定后续生成哪些记忆单元:变量非空则转VariableUnit,摘要启用则转SummaryUnit,hasKeyInformation = true时才继续做片段记忆提取。

2.3 VariableResult:单个变量的键值抽取结果

VariableResult表示单个记忆变量的抽取结果(见 VariableResult.java),包含variableKey(变量名)与variableValue(变量值)两个字段,默认均为空字符串。MemoryAnalyzer负责创建该对象列表,Generator会把非空变量值转换为VariableUnit(空值直接跳过,见 Generator.java 的processExtractedData逻辑)。

三、MemoryAnalyzer:消息分析与三路输出

MemoryAnalyzer负责分析对话内容,输出「是否含关键信息」「变量提取结果」「摘要文本」三路结果。它是一个final工具类,提供多个重载的静态analyze方法(见 MemoryAnalyzer.java):

public static CompletionStage<MemoryAnalyzerResult> analyze( List<BaseMessage> messages, List<BaseMessage> historyMessages, Model baseChatModel, AgentMemoryConfig memoryConfig, Integer summaryMaxToken) public static CompletionStage<MemoryAnalyzerResult> analyze( List<BaseMessage> messages, List<BaseMessage> historyMessages, Model baseChatModel, AgentMemoryConfig memoryConfig, Integer summaryMaxToken, MemoryScopeConfig scopeConfig, String forbiddenVariables, int retries)

3.1 分析流程

从源码看,一次完整分析包含以下步骤:

  1. 空消息校验:messages为空时记录No messages to analyze警告并返回null;
  2. 消息拼接:将historyMessages与messages分别按role: content格式拼接为history与conversation文本;
  3. 变量模板构建:遍历memoryConfig.getMemVariables()(List<Param>),生成variables_define_template(variable_key→ 变量名、variable_value→ 变量描述)与variables_output_template(值为空的输出骨架);
  4. 提示词变量注入:组装has_variable(布尔值转True/False)、forbidden_variables(空白时归一化为None)、max_message_token、user_profile_definition、semantic_memory_definition、episodic_memory_definition等变量;
  5. 提示词组装:PromptApplier.getInstance().apply("memory_analysis_prompt", promptVariables)渲染 memory_analysis_prompt.md;
  6. 模型调用与解析:输入固定为单条UserMessage,用JsonOutputParser把模型输出解析成Map,再映射为MemoryAnalyzerResult(fromMap读取has_key_information、variables、summary三个字段,见 MemoryAnalyzer.java);
  7. 重试兜底:默认最多重试 3 次(DEFAULT_RETRIES = 3),连续失败后返回空白的MemoryAnalyzerResult。

3.2 两个值得注意的行为细节

  • 摘要清空规则:若memoryConfig为空,或未启用长期记忆(enableLongTermMem),或未启用摘要记忆(enableSummaryMemory),返回结果中的summary会被强制清空——避免生成不会被使用的摘要,节省模型开销;
  • 敏感变量保护:forbidden_variables参数会注入提示词中的「禁止记忆变量定义」,与 memory_analysis_prompt.md 中的任务说明配合,约束模型不得提取明确列出的敏感变量。

3.3 期望的模型输出

分析阶段期望模型返回如下纯 JSON(保留```json代码块标记):

{ "has_key_information": true, "variables": [ {"variable_key": "nickname", "variable_value": "小王"} ], "summary": "用户昵称是小王" }

对应行为已被 MemoryAnalyzerTest.java 覆盖验证,包括:forbidden_variables注入到提示词、空白值归一化为None、以及变量列表的正确解析。

四、LongTermMemoryExtractor:长期片段记忆提取

LongTermMemoryExtractor使用提示词与模型调用,把消息提取为长期片段记忆的分类结果,同样是final工具类(见 LongTermMemoryExtractor.java):

public static CompletionStage<Map<String, Object>> extractLongTermMemory( ExtractMemoryParams extractMemoryParas, String timestamp, MemoryScopeConfig scopeConfig) public static CompletionStage<Map<String, Object>> extractLongTermMemory( ExtractMemoryParams extractMemoryParas, String timestamp, MemoryScopeConfig scopeConfig, int retries)

4.1 消息组装策略

与MemoryAnalyzer不同,提取器会区分两类消息文本:

  • 参考消息(reference_messages):历史消息 + 当前消息全部拼接,供模型做指代消解;
  • 目标消息(input_messages):仅收集当前消息中role == "user"的消息(appendReference中collectUserMessages开关控制,见 LongTermMemoryExtractor.java)。

消息行的格式为name: content,其中name优先取message.getName(),为空时回退到role。

4.2 时间上下文构造

buildTimeContext会把传入的timestamp(支持OffsetDateTime、LocalDateTime、LocalDate三种 ISO 格式)解析为日期,并推算「当前所在周」的周一到周日区间,输出形如2026年5月11日(周一)~2026年5月17日(周日)(即05.11~05.17)的中文周区间文本;解析失败时原样返回timestamp。该上下文会以current_week变量注入提示词,供模型做「下周/上周/上个月/X天前」等相对时间的推理基准。

4.3 提示词与输出结构

提示词通过PromptApplier加载 fragment_memory_prompt.md,注入conversation_time、input_messages、reference_messages、三类记忆定义与current_week。该模板定义了六步执行流程:

  1. 指代消解:识别第三人称代词与指示代词,回溯参考消息还原实体;
  2. 主体锁定:主语为「我/本人/我们」锁定为用户本人,其他主体单独处理,无主语则丢弃;
  3. 逻辑推理:知识推理 + 时间推理(下周/上周/上个月按「当前所在周」推算为绝对日期);
  4. 内容提取:逐句扫描、原子化拆分,排除高敏感信息与寒暄,按用户画像(user_profile)、情景记忆(episodic_memory)、语义记忆(semantic_memory)、变更记忆指令(instruct_memories)四类归档;
  5. 内容整理:隐私过滤、主语统一为「用户」、相对时间替换为绝对日期;
  6. 输出:返回纯 JSON。

期望的模型输出结构为:

{ "has_explict_instruct": true, "instruct_memories": [ { "mem_content": "标准的记忆内容陈述", "mem_type": "user_profile", "mem_instruct": "UPDATE", "old_mem": "被修改或删除的旧记忆" } ], "user_profile": [], "semantic_memory": [], "episodic_memory": [] }

模型输出经JsonOutputParser解析为Map后返回;默认最多重试 3 次,连续失败返回空映射(Map.of()),对应实现见 LongTermMemoryExtractor.java。

五、Generator:总协调器与记忆单元生成

Generator是整个 extract 模块的编排入口,负责协调消息分析、摘要生成与长期片段提取,输出按记忆类型分组的BaseMemoryUnit列表(见 Generator.java)。

5.1 入参与必填校验

genAllMemory(Map<String, Object> kwargs)返回CompletionStage<Map<String, List<BaseMemoryUnit>>>,在独立的 IO 线程池(memory-generator-io)中异步执行。kwargs支持以下键:

键类型说明
messagesList<BaseMessage>本轮消息(必填)
configAgentMemoryConfig记忆配置(必填)
user_idString用户标识(必填)
scope_idString作用域标识(必填)
base_chat_modelModel基础聊天模型(必填)
history_messagesList<BaseMessage>可选历史消息
forbidden_variablesString可选禁止记忆变量
message_mem_idString可选消息记忆 ID
timestampString可选对话时间
summary_max_tokenInteger可选摘要 token 上限
scope_configMemoryScopeConfig可选作用域配置(含三类记忆定义)
semantic_storeObject可选语义存储

方法要求messages、config、user_id、scope_id、base_chat_model同时存在;任一缺失都会记录MEMORY_PROCESS错误日志并返回空映射(见 Generator.java)。

5.2 生成流水线

genAllMemorySync的核心流程与文档描述一致:先分析,再按配置生成摘要与片段。

  1. 分析阶段:先调用MemoryAnalyzer.analyze(...)(传入summaryMaxToken、scopeConfig、forbiddenVariables),得到MemoryAnalyzerResult;
  2. 变量单元:processExtractedData把非空变量值转成VariableUnit并按类型归组;
  3. 摘要单元:若config.isEnableSummaryMemory()为 true,通过dataIdGenerator.generateNextId(userId)生成新memId,构造SummaryUnit(含摘要文本、messageMemId、timestamp);
  4. 片段记忆前置条件:若!analyzeResult.isHasKeyInformation()直接返回——消息不含关键信息时不触发片段提取;
  5. 片段记忆:categoriesToMemoryUnit调用LongTermMemoryExtractor.extractLongTermMemory(...),将返回的分类结果按CATEGORY_TO_CLASS映射为MemoryType(user_profile → USER_PROFILE、semantic_memory → SEMANTIC_MEMORY、episodic_memory → EPISODIC_MEMORY),每个片段生成独立的FragmentMemoryUnit(OperationType.ADD),再逐条按fragmentEnable开关过滤后加入结果。

其中fragmentEnable由三个独立开关决定(见 Generator.java):

Map<String, Boolean> fragmentEnable = Map.of( MemoryType.USER_PROFILE.getValue(), config.isEnableUserProfile(), MemoryType.SEMANTIC_MEMORY.getValue(), config.isEnableSemanticMemory(), MemoryType.EPISODIC_MEMORY.getValue(), config.isEnableEpisodicMemory() );

三种记忆类型在 MemoryType.java 中定义,FragmentMemoryUnit承载记忆内容、来源消息 ID、时间戳与操作类型(见 FragmentMemoryUnit.java)。

5.3 变更记忆指令(UPDATE / DELETE)

当模型输出的has_explict_instruct为 true 时,handleMemoryWithInstruct会处理instruct_memories中的 UPDATE/DELETE 指令:

  • 按mem_instruct拆分出待更新、待删除两条记忆列表;
  • processMemoryOperations以old_mem为查询构造SearchParams(topK=1,检索三种记忆类型),调用SearchManager做语义检索并按score降序取最优命中;
  • semanticValidation通过semantic_validation提示词让模型校验检索结果是否与old_mem语义一致(输出含CORRECT且不含WRONG才通过),校验通过后生成对应OperationType的FragmentMemoryUnit。

整个片段生成过程用try/catch兜底:捕获NullPointerException(属性异常)、IllegalArgumentException(取值异常)与其他RuntimeException,分别记录 debug/warning 日志后返回已生成的结果,不中断主流程(见 Generator.java)。

5.4 输入兼容性

从测试与实现可见,Generator对模型的片段输出保持较强兼容性:片段数组元素既可以是纯字符串,也可以是{"content": "..."}形式的对象(取content字段,缺失时回退为String.valueOf),该行为在 GeneratorTest.java 中有明确断言。

六、AgentMemoryConfig:控制提取行为的配置开关

MemoryAnalyzer与Generator的取舍都围绕AgentMemoryConfig展开,其完整字段与默认值见 AgentMemoryConfig.java:

JSON 字段Java 字段默认值作用
mem_variablesmemVariables空列表预定义的可提取记忆变量(List<Param>,含变量名与描述),驱动variables_define_template与variables_output_template。
enable_long_term_memenableLongTermMemtrue总开关,关闭后Generator直接跳过摘要与片段记忆生成。
enable_user_profileenableUserProfiletrue是否生成用户画像片段记忆。
enable_semantic_memoryenableSemanticMemorytrue是否生成语义记忆片段。
enable_episodic_memoryenableEpisodicMemorytrue是否生成情景记忆片段。
enable_summary_memoryenableSummaryMemorytrue是否生成摘要记忆。

此外还提供兼容方法isEnableFragmentMemory(),返回三个片段开关的「或」结果,供既有调用方判断是否需要片段提取能力。配置类同时支持无参构造与Builder链式构建,例如:

AgentMemoryConfig config = AgentMemoryConfig.builder() .memVariables(List.of(Param.string("nickname", "user nickname", false))) .enableSummaryMemory(true) .enableEpisodicMemory(false) .build();

注意:当enableLongTermMem或enableSummaryMemory关闭时,MemoryAnalyzer返回的summary会被清空;关闭enableUserProfile等片段开关后,即使模型输出了对应分类内容,Generator也会在归组时将其过滤掉。

七、提示词模板与 PromptApplier

MemoryAnalyzer与LongTermMemoryExtractor都通过 PromptApplier.java 组装提示词。PromptApplier从 classpath 的openjiuwen/core/memory/prompts目录按文件名前缀加载 Markdown 模板(如fragment_memory_prompt.md),内部用ConcurrentHashMap缓存PromptTemplate,支持apply(filePrefix, variables)模板渲染与clearCache()清理。

当前仓库 resources 下共有 4 个记忆提示词模板:

  • fragment_memory_prompt.md:长期片段记忆提取(六步执行流程 + 变更指令识别);
  • memory_analysis_prompt.md:消息分析与变量/摘要提取;
  • memory_update_check.md:记忆更新校验;
  • semantic_validation.md:UPDATE/DELETE 指令的语义一致性校验。

八、完整调用链与行为总结

将以上组件串联起来,一次记忆提取的完整调用链如下:

genAllMemory(kwargs) └─ MemoryAnalyzer.analyze(...) // 分析:关键信息标记 + 变量 + 摘要 └─ PromptApplier.apply("memory_analysis_prompt", ...) └─ Model.invoke(...) → JsonOutputParser ├─ processExtractedData(variables) // 变量 → VariableUnit(非空才转) ├─ (enableSummaryMemory) // 摘要 → SummaryUnit ├─ (hasKeyInformation) │ └─ LongTermMemoryExtractor.extractLongTermMemory(...) │ └─ PromptApplier.apply("fragment_memory_prompt", ...) │ └─ Model.invoke(...) → JsonOutputParser │ ├─ CATEGORY_TO_CLASS 分类映射 // → FragmentMemoryUnit (ADD) │ └─ instruct_memories 处理 // UPDATE/DELETE:SearchManager 检索 + semanticValidation └─ Map<MemoryType, List<BaseMemoryUnit>>

围绕该模块的三个关键行为(与原文档互相印证):

  1. 先分析后生成:Generator.genAllMemory(...)会先调用MemoryAnalyzer,再根据AgentMemoryConfig的开关决定是否生成摘要与片段记忆;
  2. 统一提示词与重试机制:LongTermMemoryExtractor与MemoryAnalyzer都通过PromptApplier组装提示词,并默认最多重试 3 次模型调用,连续失败时分别返回空映射或空白结果对象;
  3. 轻量数据对象:ExtractMemoryParams、MemoryAnalyzerResult、VariableResult均面向流程编排设计,仅承载字段数据,不包含业务逻辑。

对开发者而言,若要接入该提取流程,最直接的入口是Generator:构造DataIdManager(可选加SearchManager)后,在kwargs中补齐messages、config、user_id、scope_id、base_chat_model五个必填项即可异步获得按记忆类型分组的BaseMemoryUnit列表;若只想要「变量 + 摘要」能力,可直接调用MemoryAnalyzer.analyze(...);若只需要片段分类结果,则可直接调用LongTermMemoryExtractor.extractLongTermMemory(...)。各类型的字段语义、重试默认值与配置开关均可在本文引用的源码与测试中进一步核对。

  • 人工智能
  • AI Agent
  • Agent 框架
  • 工具调用
  • Agent 工作流
  • RAG
  • MCP Clients
  • 强化学习

【免费下载链接】agent-core-java

openJiuwen agent core Java是 openJiuwen Core的 Java版本,提供AI Agent开发、运行、调优与演进相关的全套SDK能力。

项目地址:https://gitcode.com/openJiuwen/agent-core-java
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询