openJiuwen agent-core-java 结构化日志之 ContextEvent:上下文消息写入、读取与清理事件的记录与序列化
人工智能AI AgentAgent 框架工具调用Agent 工作流RAGMCP Clients强化学习【免费下载链接】agent-core-javaopenJiuwen agent core Java是 openJiuwen Core的 Java版本提供AI Agent开发、运行、调优与演进相关的全套SDK能力。项目地址https://gitcode.com/openJiuwen/agent-core-java点击查看免费下载导读ContextEvent是 openJiuwen agent-core-java 结构化日志子系统com.openjiuwen.core.common.logging.events中专用于「上下文Context」的事件载荷类负责记录上下文消息写入、读取和清理过程中的消息属性与容量信息。本文以 ContextEvent.md 为骨架结合仓库源码讲解其字段设计、序列化机制、事件工厂注册、脱敏策略与实际使用场景帮助你掌握在 Agent 运行链路中如何产出、识别和消费上下文相关日志事件。一、ContextEvent 是什么上下文生命周期的事件快照public class ContextEvent extends BaseLogEventContextEvent位于core/src/main/java/com/openjiuwen/core/common/logging/events/ContextEvent.java继承自所有结构化日志事件的公共基类BaseLogEvent。它的定位非常聚焦记录上下文消息写入add message、读取retrieve和清理clear过程中的消息属性与容量信息。在 Agent 的运行过程中上下文窗口Context Window是承载对话历史、工具结果等消息的核心载体。每当模型上下文发生以下三类操作时都会对应产生上下文事件向上下文追加/写入消息context_add_message从上下文读取消息context_retrieve清空上下文context_clear。ContextEvent正是这些操作的结构化日志表示它同时记录了「这条消息是什么」消息类型、正文、角色和「当前上下文装了多少」条目数与最大容量为排障、容量监控与成本分析提供了直接的数据来源。二、新增字段消息属性与容量信息的五个序列化键与通用基类相比ContextEvent只新增了 5 个字段全部在消息操作发生时用于刻画上下文的状态字段类型序列化键说明messageTypeStringmessage_type上下文消息类型。messageContentStringmessage_content上下文消息正文。messageRoleStringmessage_role上下文消息角色。contextSizeIntegercontext_size当前上下文条目数。maxContextSizeIntegermax_context_size允许的最大上下文容量。其中context_size与max_context_size构成了一个「水位指示器」通过两者的差值可以快速判断当前上下文距离容量上限还有多少余量这与com.openjiuwen.core上下文引擎中上下文窗口的容量管理直接相关。从源码结构看这两个字段对应当前上下文条目数与允许的最大上下文容量便于在日志中直接观测上下文的占用情况。字段序列化实现新增字段通过覆写父类提供的受保护扩展点addFieldsToMap(MapString, Object map)写入输出映射Override protected void addFieldsToMap(MapString, Object map) { putIfNotNull(map, message_type, messageType); putIfNotNull(map, message_content, messageContent); putIfNotNull(map, message_role, messageRole); putIfNotNull(map, context_size, contextSize); putIfNotNull(map, max_context_size, maxContextSize); }这里使用了父类BaseLogEvent提供的静态工具putIfNotNull(...)只有当字段值非 null 时才写入映射因此未被赋值的新增字段不会出现在最终序列化输出中避免了日志中出现大量无意义的null键。三、继承的通用元数据事件被谁产出、属于哪条链路ContextEvent的大部分信息来自父类 BaseLogEvent.java。所有新增字段之外序列化输出还包含以下通用元数据均为 snake_case 键名字段类型默认值说明eventIdStringUUID 随机生成唯一事件 ID。eventTypeLogEventType-事件类型。logLevelLogLevelINFO日志等级。timestampInstant创建时刻事件创建时间。moduleTypeModuleTypeSYSTEM模块类别ContextEvent 构造后固定为CONTEXT。moduleId/moduleNameString-模块实例 ID 与名称。sessionId/conversationIdString-会话 ID 与会话内对话 ID。traceId/correlationId/parentEventIdString-链路追踪 ID、关联事件 ID、父事件 ID。statusEventStatusSUCCESS事件状态success/failure/pending/timeout/cancelled。errorCode/errorMessageString-错误码与错误消息。message/stacktrace/exceptionDetailString-主消息、栈追踪与异常细节。metadataMapString, Object空 LinkedHashMap扩展字段非空时才输出。这些元数据让一个上下文事件可以完整地回答三类问题谁产生的moduleType/moduleId/moduleName、在哪个会话和链路中sessionId/traceId/correlationId/parentEventId、结果如何status/errorCode/errorMessage。StructuredLogEventTest中针对eventId、logLevel、status、moduleType、timestamp与metadata的默认值均有断言覆盖。四、构造与序列化机制默认构造函数模块归属的自动绑定public ContextEvent() { super(); setModuleType(ModuleType.CONTEXT); }默认构造函数调用super()后会把moduleType强制设为ModuleType.CONTEXT对应序列化值context使得由注册表工厂创建的任何ContextEvent天然归属于 context 模块无需调用方手动设置模块类型。事件类型的标记方式BaseLogEvent.toMap()在输出event_type键时优先取eventType.getValue()若eventType为 null 则回退到eventTypeKey。因此一个ContextEvent的event_type取值可能是context_add_message向上下文追加消息context_retrieve从上下文读取消息context_clear清空上下文。Lombok 样板代码生成与整个事件体系一致ContextEvent使用 Lombok 的Data、SuperBuilder与EqualsAndHashCode(callSuper true)生成 getter/setter、builder 构造方式和包含父类字段的相等性逻辑。这意味着你可以用两种方式创建事件// 方式一直接 new再逐个 set ContextEvent event new ContextEvent(); event.setMessageType(text); event.setMessageContent(用户输入的问题); event.setMessageRole(user); event.setContextSize(42); event.setMaxContextSize(128); // 方式二通过 EventClassRegistry 工厂创建并赋值 BaseLogEvent event2 EventClassRegistry.createEvent( LogEventType.CONTEXT_ADD_MESSAGE, Map.of(messageType, text, messageRole, user, contextSize, 42, maxContextSize, 128) );完整序列化输出示例综合父类与子类字段一个典型的ContextEvent经toMap()序列化后的扁平化 JSON 输出形如{ event_id: 3f0a2b7e-...-uuid, event_type: context_add_message, log_level: INFO, timestamp: 2026-10-12T04:58:10.123Z, module_type: context, session_id: session_456, trace_id: trace_789, status: success, message_type: text, message_content: 用户输入的问题, message_role: user, context_size: 42, max_context_size: 128 }注意module_type的值是枚举ModuleType.CONTEXT的 value 字符串context而log_level、status同样会被转换为各自的 value 字符串确保输出是 JSON 兼容的扁平结构这一点在StructuredLogEventTest#testEventSerialization中有明确验证。五、事件工厂注册三种事件类型映射到 ContextEventEventClassRegistry见 EventClassRegistry.java在静态初始化时把以下三个枚举值映射到ContextEvent::new// Context events for (LogEventType t : List.of( LogEventType.CONTEXT_ADD_MESSAGE, LogEventType.CONTEXT_CLEAR, LogEventType.CONTEXT_RETRIEVE)) { m.put(t, ContextEvent::new); }对应的枚举值定义在 LogEventType.javaCONTEXT_ADD_MESSAGE(context_add_message), CONTEXT_CLEAR(context_clear), CONTEXT_RETRIEVE(context_retrieve), CONTEXT_SAVE(context_save),值得注意的细节CONTEXT_SAVE 的映射缺口从源码可以观察到LogEventType枚举中虽然额外定义了CONTEXT_SAVE(context_save)但EventClassRegistry的静态映射并未将其注册到ContextEvent。这意味着若调用方以CONTEXT_SAVE创建事件工厂会按getFactory的回退逻辑自定义注册表 → 静态映射 →BaseLogEvent返回BaseLogEvent实例而不是ContextEvent。在扩展上下文保存类事件时需要注意这一映射差异。反射式字段填充EventClassRegistry.createEvent(LogEventType, MapString, Object)会通过反射的 setter 查找机制填充字段populateFields会把传入属性键中的下划线风格如message_type归一化为驼峰messageType再拼装setXxx方法名进行调用未知属性键会被忽略并输出一条 warning 日志。因此下面两种写法等价createEvent(LogEventType.CONTEXT_ADD_MESSAGE, Map.of(message_type, text)); createEvent(LogEventType.CONTEXT_ADD_MESSAGE, Map.of(messageType, text));六、默认脱敏message_content 一律替换为 REDACTEDContextEvent的message_content字段默认会被 EventSanitizer.java 脱敏。其默认敏感字段清单DEFAULT_SENSITIVE_FIELDS明确包含message_contentpublic static final ListString DEFAULT_SENSITIVE_FIELDS List.of( messages, response_content, input_content, query, arguments, result, message_content, tool_calls, input_data, output_data, retrieved_memories);脱敏逻辑sanitizeEventForLogging(BaseLogEvent event)对event.toMap()后的输出逐字段检查只要敏感清单中的键存在且非 null就把该键的值整体替换为占位符REDACTEDfor (String fieldName : fields) { if (eventDict.containsKey(fieldName) eventDict.get(fieldName) ! null) { eventDict.put(fieldName, REDACTED); } }由此默认情况下上下文的消息正文不会以明文落入日志避免用户输入或模型输出等敏感内容泄漏而message_type、message_role、context_size、max_context_size等结构信息仍完整保留兼顾了可观测性与隐私安全。若业务需要自定义脱敏范围可调用带ListString sensitiveFields参数的重载版本传入自定义清单该行为在StructuredLogEventTest#testSanitizeEventCustomFields中有覆盖。七、实际使用场景从上下文引擎到日志消费上下文引擎中的事件产出在 openJiuwen 的上下文引擎中多个处理器会在压缩compression和卸载offload流程里产出事件。以 CurrentRoundCompressor.java 为例多轮压缩完成后会构造ContextEvent并连同被修改的消息索引、压缩摘要与压缩用量一并放入处理结果ContextEvent event new ContextEvent( processorType(), result.modifiedIndices(), result.compactSummary(), currentCompressionUsage() ); context.setMessages(result.messages(), true); return CompletableFuture.completedFuture( new SessionModelContext.ProcessResult(event, List.of(), null));类似地DialogueCompressor、FullCompactProcessor、MicroCompactProcessor、RoundLevelCompressor以及MessageOffloader、MessageSummaryOffloader、ToolResultBudgetProcessor等处理器也都会在操作完成后产出上下文事件。需要说明的是这些处理器产出的ContextEvent是com.openjiuwen.core.context.processor.ContextEvent见 processor/ContextEvent.java它承载的是「哪些消息被修改、压缩摘要、压缩用量」这类处理器内部结果而本文所述的com.openjiuwen.core.common.logging.events.ContextEvent属于结构化日志子系统负责把上下文操作以日志事件的形式输出。两者分属不同层次、职责互补前者是上下文引擎的处理结果载体后者是可观测性数据的日志化表达。Runner 回调侧的事件键在 Runner 回调层面ContextEvents.java 定义了与上下文生命周期相关的回调事件键常量public static final String CONTEXT_UPDATED Events.getEvent(context_updated); public static final String CONTEXT_OFFLOADED Events.getEvent(context_offloaded); public static final String CONTEXT_RETRIEVED Events.getEvent(context_retrieved); public static final String CONTEXT_CLEARED Events.getEvent(context_cleared); public static final String CONTEXT_COMPRESSION_STATE Events.getEvent(context.compression_state);这些键与LogEventType中的context_retrieve、context_clear等序列化值相呼应构成从「上下文操作发生」到「结构化日志落盘」的完整链路处理器/回调产生上下文变更信号ContextEvent将其封装为带消息属性与容量信息的结构化事件最终经toMap()EventSanitizer输出为可采集、可检索的日志。八、测试覆盖结构化事件体系的验证依据ContextEvent及其所属事件体系的正确性由 StructuredLogEventTest.javaJUnit 5从 Python 版test_structured_log.py移植而来保障覆盖点包括EventClassRegistry的静态映射与动态自定义注册、注销及优先级自定义 → 静态 →BaseLogEvent与内建枚举值冲突的字符串注册会被拒绝IllegalArgumentExceptionBaseLogEvent各字段默认值UUID eventId、INFO等级、SUCCESS状态、SYSTEM模块、空 metadatatoMap()输出的 JSON 兼容扁平结构event_type/log_level使用 value 字符串嵌套 map 可递归序列化EventSanitizer默认与自定义敏感字段的脱敏行为事件关联parentEventId、correlationId。需要验证上下文事件行为时可在core模块下运行该测试类mvn -pl core test -DtestStructuredLogEventTest九、小结ContextEvent是 openJiuwen agent-core-java 结构化日志体系中针对上下文操作的精简载荷5 个新增字段覆盖消息的类型、正文、角色与上下文容量水位父类元数据补齐会话、链路与结果信息EventClassRegistry统一工厂化创建EventSanitizer默认对消息正文脱敏。理解它的字段与序列化键你就能在日志采集、链路追踪和上下文容量监控中准确识别context_add_message、context_retrieve、context_clear三类事件并利用context_size与max_context_size的差值提前发现上下文溢出风险。相关资源关联文档ContextEvent.md、事件体系总览 events.README.md源码ContextEvent.java、BaseLogEvent.java、EventClassRegistry.java、EventSanitizer.java、LogEventType.java、ModuleType.java测试StructuredLogEventTest.java上下文引擎与回调processor/ContextEvent.java、CurrentRoundCompressor.java、ContextEvents.java赞分享人工智能AI AgentAgent 框架工具调用Agent 工作流RAGMCP Clients强化学习【免费下载链接】agent-core-javaopenJiuwen agent core Java是 openJiuwen Core的 Java版本提供AI Agent开发、运行、调优与演进相关的全套SDK能力。项目地址https://gitcode.com/openJiuwen/agent-core-java点击查看免费下载相关推荐type-challenges 中等题 ReplaceAll用模板字面量类型实现字符串全局替换type challenges 中等题 ReplaceAll用模板字面量类型实现字符串全局替换 导读 本文围绕 type challenges 题库中的第 1人工智能AI AgentAgent 框架工具调用Agent 工作流RAGMCP Clients强化学习openJiuwen Agent Core Java 结构化日志事件详解RetrievalEvent 检索事件的设计与使用openJiuwen Agent Core Java 结构化日志事件详解RetrievalEvent 检索事件的设计与使用 本文以 openJiuwen Ag人工智能AI AgentAgent 框架工具调用Agent 工作流RAGMCP Clients强化学习openJiuwen agent-core-java 结构化日志指南RunnerEvent 事件模型解析与实战openJiuwen agent core java 结构化日志指南RunnerEvent 事件模型解析与实战 RunnerEvent 是 openJiuwe人工智能AI AgentAgent 框架工具调用Agent 工作流RAGMCP Clients强化学习上一篇i18next-browser-languageDetector 常见问题解决方案下一篇OpenTracing-Python实战如何在Python微服务中实现分布式追踪创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考