Feature 管线与运行时扩展
30 秒导读: Koog 的核心引擎(见 01-graph-engine.md)只管"图怎么跑"。所有横切能力——日志、追踪、断点续跑、长期记忆、RAG、规划——都不写进引擎,而是做成可插拔的 feature,通过一根叫
AIAgentPipeline的管线挂上去。这根管线的本质:feature 在安装 时注册拦截器,引擎在节点/LLM/工具执行的固定触发点广播事件,管线把事件分发给所有注册者。本章讲清这套拦截机制,再给出货架上现成 feature 的地图。
1. 这是什么(零基础也能懂)
一句话定义: Feature 是"装到 agent 上的一块可插拔能力",Pipeline 是"把这些能力和引擎的执行过程连起来的接线板"。
它解决什么问题。 假设你写好了一个 agent,现在产品要你:每次 LLM 调用都打日志、把整条执行链路上报到监控、崩了能从上次断点续跑、还要记住用户上轮说过的话。这些需求有个共同点——它们都不改变 agent 的业务逻辑,只是"在某些时刻插一脚"。如果把它们全塞进引擎主循环,引擎会变成一坨谁也不敢动的巨石。
Koog 的答案: 把"插一脚"这件事标准化。引擎在几个固定时刻(节点前后、LLM 前后、工具前后……)喊一嗓子;谁想听,就在安装时登记。
用起来什么样。 装一个 feature 就是在 agent 构造块里调 install。下面这段给 agent 加上"事件日志"能力:
// 示意,基于 EventHandler.kt:235 的真实 DSL
val agent = AIAgent(...) {
handleEvents { // 安装 EventHandler feature
onToolCallStarting { ctx -> // 工具被调用前打日志
println("调用工具 ${ctx.toolName},参数 ${ctx.toolArgs}")
}
onAgentCompleted { ctx -> // agent 跑完打结果
println("完成,结果:${ctx.result}")
}
}
}
一句话直觉。 把 Pipeline 想成公司的广播系统:引擎在关键路口按广播("节点要开始了""LLM 回来了"),各个 feature 是订阅了某几个频道的部门,听到自己关心的广播就干活。引擎不认识任何具体部门,只负责按时广播。
本节不涉及底层代码。记住一件事:引擎与能力彻底解耦,靠"事件广播 + 订阅"连接。
2. 顶层全景(它大概怎么转)
2.1 一张图看懂拦截模型
怎么读这张图:左边是 feature 的安装期(注册拦截器),右边是 agent 的运行期(引擎在触发点广播),中间的 AIAgentPipelineImpl 是把两者对上的接线板。
安装期(一次性) 运行期(每次执行)
┌───────────────────────┐ ┌──────────────────────────┐
│ feature.install( │ │ 引擎执行到触发点: │
│ config, pipeline) │ │ · 节点前/后/失败 │
│ │ │ · LLM 前/后/失败/流式 │
│ pipeline │ │ · 工具前/后/校验失败 │
│ .interceptLLMCall │ │ · 子图/策略/agent 生命周期 │
│ Starting(this){} │ └───────────┬──────────────┘
└──────────┬────────────┘ │ 调 onXxx(...)
│ 登记 handler ▼
▼ ┌──────────────────────────┐
┌───────────────────────────────────────────────────────────┐ │
│ AIAgentPipelineImpl(接线板) │ │
│ · registeredFeatures: key → (impl, config) │◀─┘
│ · handlersCollector: featureKey → eventType → [handler...] │
│ 分发:按 eventType 取出所有 handler,依 eventFilter 过滤后逐个调用 │
└───────────────────────────────────────────────────────────┘
2.2 部件一句话职责
| 部件 | 干什么 | 在哪(path:symbol) |
|---|---|---|
AIAgentFeature | feature 的最小契约:一个唯一 key + 造初始 config | feature/AIAgentFeature.kt:17 |
AIAgentGraphFeature / ...FunctionalFeature / ...PlannerFeature | 三种执行形态各自的 install(config, pipeline) | feature/AIAgentFeature.kt:40,54,68 |
AIAgentPipeline | 对外契约:一堆 interceptXxx 注册点 + 一堆 onXxx 触发点 | feature/pipeline/AIAgentPipeline.kt:62 |
AIAgentPipelineImpl | 真正的接线板:存 feature、收 handler、分发事件 | feature/pipeline/AIAgentPipelineImpl.kt:59 |
AIAgentGraphPipeline | 图专属子类,多出节点/子图的拦截点 | feature/pipeline/AIAgentGraphPipeline.kt:27 |
AgentLifecycleEventType | 事件类型的封闭枚举(节点/LLM/工具/规划…) | feature/handler/AgentLifecycleEventType.kt:9 |
AgentLifecycleHandlersCollector | 按 featureKey × eventType 归档 handler 的注册表 | feature/handler/AgentLifecycleHandlersCollector.kt:12 |
FeatureConfig | 每个 feature 的配置基类,带 eventFilter 和消息处理器列表 | feature/config/FeatureConfig.kt:13 |
2.3 主线走一遍(高层)
一次 LLM 调用是怎么被 feature "拦"到的:
业务节点想调 LLM
└─> 实际走的是 ContextualPromptExecutor(把真 executor 包了一层)
├─ 先喊 pipeline.onLLMCallStarting(...) ← 触发点
│ └─ 接线板取出所有订阅 LLMCallStarting 的 handler,逐个调用
│ (追踪 feature 在这里开一个 span;日志 feature 在这里打一行)
├─ 真正执行 executor.execute(prompt, model, tools)
└─ 喊 pipeline.onLLMCallCompleted(...) ← 触发点
└─ handler 收尾(结束 span、记 token)
真实接线在 feature/ContextualPromptExecutor.kt:51(onLLMCallStarting)与 :73(onLLMCallCompleted)——引擎侧只认 pipeline.onXxx,完全不知道谁在听。
3. 核心原理(逐个机制,由浅入深)
3.1 install:一个 feature 怎么装进管线
要解决的小问题: feature 是个独立对象,怎么让它"接上"引擎?
思路: 每个 feature 声明一个唯一 key(用于存取)和一个 install 方法。install 拿到 pipeline,在里面注册自己关心的拦截器,并把自己的实现对象存进管线。
AIAgentFeature 的契约极简——只有 key 和 createInitialConfig(feature/AIAgentFeature.kt:22,29)。真正"接线"的动作在按 形态分的子接口里,以图形态为例:
// feature/AIAgentFeature.kt:40 AIAgentGraphFeature
public fun install(config: TConfig, pipeline: AIAgentGraphPipeline): TFeatureImpl
看 EventHandler 的实现,install 做两件事:注册一批拦截器、返回 feature 实例:
// EventHandler.kt:72 EventHandler.Feature.install
override fun install(config: EventHandlerConfig, pipeline: AIAgentGraphPipeline): EventHandler {
val eventHandler = EventHandler()
registerCommonPipelineHandlers(config, pipeline) // 装 LLM/工具/策略等通用拦截
registerGraphPipelineHandlers(config, pipeline) // 装节点/子图这类图专属拦截
return eventHandler
}
而 registerCommonPipelineHandlers 里就是一串"把用户 config 里的回调转成拦截器"(EventHandler.kt:165):
pipeline.interceptLLMCallStarting(this) { ctx -> config.invokeOnLLMCallStarting(ctx) }
注意第一个参数 this——它是 feature 自己,用于把 handler 归到这个 feature 的 key 名下(卸载时按 key 精确摘除)。
关键点:一个 feature 可以适配多种执行形态。 EventHandler 同时实现了 AIAgentGraphFeature、AIAgentFunctionalFeature、AIAgentPlannerFeature 三个接口(EventHandler.kt:58),各写一份 install。图形态多注册节点/子图拦截,函数与规划形态没有节点概念就跳过。
3.2 拦截模型:注册 → 归档 → 分发 三步
这是全章的承重机制。拆成三步看。
第一步:注册。 interceptXxx(feature, handle) 把回调包成"条件 handler",按 feature.key × eventType 存进注册表:
// AIAgentPipelineImpl.kt:584 interceptLLMCallStarting
addHandlerForFeature(
featureKey = feature.key,
eventType = AgentLifecycleEventType.LLMCallStarting,
handler = createConditionalHandler(feature, handle) // 包一层条件判断
)
归档结构是两层 map(AgentLifecycleHandlersCollector.kt:43,23):
featureToHandlersMap: featureKey ──► FeatureEventHandlers
└─ handlersByEventType: eventType ──► [handler, handler, ...]
第二步:触发。 引擎跑到触发点调 onXxx,onXxx 只做一件事——包一个"事件上下文"对象,转手交给分发器:
// AIAgentPipelineImpl.kt:278 onLLMCallStarting
invokeRegisteredHandlersForEvent(
eventType = AgentLifecycleEventType.LLMCallStarting,
context = LLMCallStartingContext(eventId, executionInfo, context, runId, prompt, model, tools)
)
第三步:分发。 分发器按 eventType 从注册表取出所有 feature 的对应 handler,逐个调用(AIAgentPipelineImpl.kt:830)。这里有个细节:handler 是按 feature 安装顺序遍历的(map 是 mutableMapOf,保插入序),所以多个 feature 监听同一事件时,顺序可预期。
// AIAgentPipelineImpl.kt:836 invokeRegisteredHandlersForEvent
registeredHandlers.forEach { (featureKey, handlers) ->
handlers.forEach { handler -> handler.handle(context) } // 逐个执行
}
为什么这么设计。 三步拆开后,引擎侧(onXxx)、注册侧(interceptXxx)、存储侧(collector)互不知道对方细节。加一个新事件类型,只需在 AgentLifecycleEventType 加一个 object、在管线加一对 on/intercept,现有 feature 一行不改。
3.3 事件过滤:handler 跑不跑,config 说了算
要解决的小问题: 追踪 feature 装了一堆 handler,但用户只想追踪 LLM 事件、不想追踪节点事件,怎么办?
思路: 注册时不是裸存回调,而是存条件 handler——每次触发先问一句 feature 的 eventFilter 认不认这个事件,不认就短路返回:
// AIAgentPipelineImpl.kt:941 createConditionalHandler
internal fun <TContext> createConditionalHandler(feature, handle) = handler@{ eventContext ->
val featureConfig = registeredFeatures[feature.key]?.featureConfig
if (featureConfig != null && !featureConfig.isAccepted(eventContext)) {
return@handler // 被 eventFilter 拒绝,直接跳过
}
handle(eventContext)
}
eventFilter 默认放行一切,用户可在 config 上收窄(FeatureConfig.kt:28,55)。这让过滤发生在分发的最内层,每个 feature 独立决定,互不影响。
3.4 两种 handler:只读通知 vs 值变换
管线的事件分两类,对应两种 handler 签名。
| 类型 | 用途 | handler 形态 | 分发器 |
|---|---|---|---|
| 通知型(Context) | 只读观察,不改数据(打日志、开 span) | suspend (Context) -> Unit | invokeRegisteredHandlersForEvent(type, ctx) |
| 变换型(Transform) | 依次改写一个值,前一个的输出喂给后一个 | suspend (Context, T) -> T | invokeRegisteredHandlersForEvent(type, ctx, entity) |
变换型是"责任链",每个 handler 拿到上一环的结果、返回新结果(AIAgentPipelineImpl.kt:864):
var currentEntity = entity
registeredHandlers.forEach { (_, handlers) ->
handlers.forEach { handler ->
currentEntity = handler.handle(context, currentEntity) // 链式传递
}
}
return currentEntity
两个真实用途:
- 环境变换
onAgentEnvironmentTransforming——让 feature 在 agent 启动前改写AIAgentEnvironment(AIAgentPipelineImpl.kt:227,AIAgentPipeline.kt:591的interceptEnvironmentCreated)。 - 工具元数据贡献
collectToolCallMetadata——工具执行前,各 feature 各自贡献一小段Map(如追踪的 span id),合并后线程给Tool.execute(AIAgentPipelineImpl.kt:405,注册点provideToolCallMetadata在:673)。合并规则:后装的 feature 覆盖先装的同名 key。
3.5 触发点究竟埋在哪
管线自己不产生事件,事件由引擎在三处埋点广播。这是"可观测性统一底座"的落点:
| 事件族 | 埋点位置(path:line) | 广播的方法 |
|---|---|---|
| 节点执行 | agent/entity/AIAgentNode.kt:186,204,216 | onNodeExecutionStarting/Failed/Completed |
| LLM 调用/流式 | feature/ContextualPromptExecutor.kt:51,73,87,135,159,171,187 | onLLMCallStarting/Completed/Failed、onLLMStreaming* |
| 工具调用 | environment/ContextualAgentEnvironment.kt:69,101,112,160,189 | onToolCallStarting/Completed、collectToolCallMetadata、onToolValidationFailed |
命名规律:ContextualXxx 都是"把真组件包一层、在前后插广播"的装饰器。LLM 走 ContextualPromptExecutor(包 PromptExecutor),工具走 ContextualAgentEnvironment(包 AIAgentEnvironment,即 04-tools.md 讲的安全执行环境)。装饰器模式让埋点集中、引擎主循环干净。
一个巧妙细节:onLLMCallStarting 广播后,handler 可以改 context.llm.prompt;执行器发现 prompt 被改了就用新的(ContextualPromptExecutor.kt:61)。所以 LLM 前置拦截不只是"观察",还能改写发出去的提示词。
3.6 系统 feature:管线自带的隐藏乘客
prepareFeatures() 在准备阶段先装"系统 feature"(目前只有 Debugger),再初始化所有 feature 的消息处理器(AIAgentPipelineImpl.kt:141)。系统 feature 可通过环境变量 KOOG_FEATURES 或 VM 选项开启(AIAgentPipelineImpl.kt:739,readFeatureKeysFromSystemVariables),且用户已手动装过就不重复装(:778)。这让"调试器"这类工具能靠一个环境变量注入,无需改代码。
4. 扩展地图:货架上的成品 feature
所有下列 feature 都遵守 §3 的同一套拦截机制,区别只在"订阅哪些事件、干什么"。按类别一句话定位(不逐一钻实现)。
4.1 可观测性
| 模块 | 定位 | 入口(path:symbol) |
|---|---|---|
agents-features-event-handler | 最轻量:把生命周期事件转成用户回调,适合打日志/自定义钩子 | EventHandler.kt:54 |
agents-features-trace | 结构化追踪,可写日志/文件/远程,带消息格式化 | tracing/feature/Tracing.kt:102 |
agents-features-opentelemetry | OTel span/metric 全链路,内含 Langfuse、W&B Weave 导出适配器 | opentelemetry/feature/OpenTelemetry.kt:62;Langfuse integration/langfuse/Langfuse.kt;Weave integration/weave/Weave.kt |
OTel feature 就是把 §3.5 的每个触发点映射成一个 span:节点起止对应 startNodeExecuteSpan/endNodeExecuteSpan,LLM 对应 inference span,工具对应 execute-tool span(OpenTelemetry.kt:96 起的 interceptNodeExecutionStarting 等)。Langfuse/Weave 不是独立 feature,而是给同一套 span 换一个 SpanAdapter(导出目标),复用整条管线。
4.2 持久化与断点续跑
| 模块 | 定位 | 入口 |
|---|---|---|
agents-features-snapshot | checkpoint 核心:存/取 agent 状态,支持自动建点与回滚 | snapshot/feature/Persistence.kt:78 |
agents-features-persistence-jdbc | 把 checkpoint 落到关系库的 storage provider | 同族 persistence-jdbc/ |
Persistence 的接线正好用了两个拦截点(Persistence.kt:129,146):策略开始时尝试回滚到最近 checkpoint(rollbackToLatestCheckpoint),节点完成后若开了自动持久化就建点(createCheckpointAfterNode)。这正呼应 01-graph-engine.md 里 restoreDefault 的检查点恢复——引擎提供"回到某节点"的能力,persistence feature 提供"状态从哪来/存哪去"。
4.3 记忆与聊天历史
| 模块 | 定位 | 入口 |
|---|---|---|
agents-features-memory | 会话级聊天记忆:策略起把历史注入 prompt,策略完把新历史存回 | chatMemory/feature/ChatMemory.kt:40 |
agents-features-longterm-memory | 跨会话长期记忆:抽取→存储→检索增强(RAG 风格) | longtermmemory/feature/LongTermMemory.kt |
agents-features-chat-history-jdbc / -aws | 聊天历史的后端存储(JDBC / AWS) | 同族目录 |
ChatMemory 的接线极干净(ChatMemory.kt:92,106):interceptStrategyStarting 时从 provider 载入历史、经预处理器注入 prompt;interceptStrategyCompleted 时把最终对话存回 provider。业务逻辑一行不知情。
5. 图内建能力:历史压缩策略
这一块不是 feature,而是图 DSL 直接提供的节点能力,单列是因为它是长对话省 token 的关键。
要解决的问题: 对话越长,每次发给 LLM 的历史越贵。压缩策略在合适的节点把旧历史换成更短的等价表示。
HistoryCompressionStrategy 是抽象基类(dsl/extension/HistoryCompressionStrategy.kt:24),核心是一个 compress(llmSession, memoryMessages)。基类给了两个共用工具:compressPromptIntoTLDR(让 LLM 把历史总结成 TL;DR)和 composeMessageHistory(保留 system 消息+首条 user 消息+记忆消息,再拼总结)。
内建策略一览(DefaultHistoryCompressionStrategies.kt,工厂在基类 companion):
| 策略 | 做法 | 何时用 |
|---|---|---|
NoCompression | 不压 | 短对话 |
WholeHistory | 整段历史压成一条 TL;DR | 通用 |
WholeHistoryMultipleSystemMessages | 按 system 消息分块,各块分别 TL;DR | 多 system 段的复杂对话 |
FromLastNMessages(n) | 只留最近 n 条再压 | 只关心近期上下文 |
FromTimestamp(t) | 丢弃 t 之前的消息再压 | 按时间切 |
Chunked(size) | 按块分段各自 TL;DR | 超长历史 |
FactRetrieval(concepts) | 用 LLM 抽取指定概念的结构化事实,用一条事实消息替换全历史 | 需要保留特定事实而非流水叙述 |
FactRetrieval 最特别:它不是"总结",而是抽取——把历史换成 [CONTEXT RESTORATION] + 关于配置概念的事实(HistoryCompressionStrategy.kt:250,实现在 FactRetrievalHistoryCompressionStrategy.kt)。
6. 两个大高层扩展
6.1 RAG / 向量检索(rag/、embeddings/)
定位: 给 agent 一个"按语义找相关文档"的存储层,长期记忆和知识增强都建在它上面。
分层看:
DocumentEmbedder<Document> 把文档变成向量(rag/vector/embedder/DocumentEmbedder.kt:14)
└─ TextDocumentEmbedder 先读文本再交给 Embedder(:35)
Embedder 底层向量化(embeddings/,含 embeddings-llm 用 LLM 产向量)
VectorStorage<Document,Request> 面向用户的门面:写入 + 相似度检索 + 删除
= WriteStorage + LookupStorage + SearchStorage + DeletionStorage(rag/vector/storage/VectorStorage.kt:18)
VectorStorageBackend 底层向量库后端(内存 / 文件,rag/vector/backend/*)
DocumentEmbedder 的契约就一个 embed(document): Vector(DocumentEmbedder.kt:21);TextDocumentEmbedder 把"读文档→取文本→向量化→算相似度"串起来(:45)。VectorStorage 用组合多个能力接口的方式表达"能写、能查、能搜、能删"(VectorStorage.kt:18),具体实现有内存版和文件版。这是纯存储/检索基建,不挂管线;上层的 agents-features-longterm-memory(§4.3)才把它接到 agent 事件上。
6.2 规划器 Planner(agents-planner)
定位: 把"目标→计划→逐步执行→检查完成"做成可复用的规划循环。入口是 Planners 门面(planner/Planners.kt:34):llmBased、llmBasedWithCritic、goap。
两种实现,思路完全不同:
SimpleLLMPlanner(LLM 驱动) ——用 LLM 生成计划、逐步执行、可对计划做评估决定是否重规划(planner/llm/SimpleLLMPlanner.kt:24)。四个覆写方法对应规划循环:buildPlan(让 LLM 产出 SimplePlan,:33)、executeStep(用 LLM 执行当前步,:177)、isPlanCompleted(所有步完成?:202)、assessPlan(是否需要 replan,:168)。它只处理字符串状态,不做工具调用,是最轻的规划器。带 critic 的变体多一轮"评审再重规划"。
GOAPPlanner(目标导向,搜索驱动) ——Goal-Oriented Action Planning,把规划变成图搜索(planner/goap/GOAPPlanner.kt:27)。你定义一组带前置条件与效果的 action、一组 goal;规划器用 A* 搜出从当前状态到目标的最优 action 序列(buildPlanForGoal,:78)。核心循环:从 open set 取 f 值最小的状态,命中 goal 就回溯出路径,否则对满足前置条件的 action 扩展后继状态(:93)。buildPlan 对每个 goal 各搜一条、取代价最小的(:41)。计划表示成 action 名字列表,可序列化、可持久化。
一句话对比:SimpleLLMPlanner 让模型"想"出计划,GOAPPlanner 用算法"搜"出计划。 前者灵活但不可控,后者确定但需要你把领域建模成 action/goal。
7. 边界与局限(诚实)
- 拦截器执行不隔离。 分发器逐个调用 handler,一个 handler 抛异常会怎样、是否影响后续 handler,
invokeRegisteredHandlersForEvent(AIAgentPipelineImpl.kt:836)里没有 try/catch 包裹每个 handler——异常会向上冒泡到触发点。写 feature 时别在 handler 里裸抛。 - handler 顺序 = 安装顺序,但不可显式排序。 多 feature 监听同一事件时无优先级机制,只能靠安装先后。变换型链(如工具元数据合并)后装覆盖先装(
AIAgentPipelineImpl.kt:432的+合并)。 - 形态绑定。 节点/子图事件只存在于
AIAgentGraphPipeline;函数式与规划式管线没有节点概念,装图专属 feature 时那部分拦截不会生效(见EventHandler三份install的差异,EventHandler.kt:86,97)。 - 系统 feature 白名单固定。
KOOG_FEATURES只能开systemFeatures集合里已知的项(目前仅Debugger),不认识的 key 只告警不报错(AIAgentPipelineImpl.kt:770)。 - RAG/Planner 的具体后端有限。 向量后端目前是内存/文件级(
rag/vector/backend/),生产级向量库需自行接;本章不覆盖各后端实现深度。
8. 横向对比(同 shelf)
- 与 Koog 其它章的关系:本章的"触发点"由 01-graph-engine.md 的节点/子图执行产生;LLM 事件包裹的是 03-llm-layer.md 的 executor;工具事件包裹 04-tools.md 的安全环境;feature 的安装 DSL 属于 02-dsl-and-strategies.md 的构建层。
- 与兄弟 agent 框架:Koog 的"事件广播 + 订阅"底座在思路上接近其它框架的中间件/回调链(如 LangChain 的 callbacks、可观测的 OTel GenAI 语义约定)。Koog 的特点是把它做成强类型的封闭事件枚举 + 按 feature key 归档,便于精确装卸与形态分离。跨库对比见总库 doc。
9. 代码地图(导航索引)
| 主题 | 文件路径 | 关键符号 |
|---|---|---|
| feature 最小契约 | agents/agents-core/.../feature/AIAgentFeature.kt | AIAgentFeature、AIAgentGraphFeature.install |
| 管线对外契约 | .../feature/pipeline/AIAgentPipeline.kt | interceptLLMCallStarting、onLLMCallCompleted |
| 管线实现(接线板) | .../feature/pipeline/AIAgentPipelineImpl.kt | invokeRegisteredHandlersForEvent、createConditionalHandler、prepareFeatures |
| 图专属拦截点 | .../feature/pipeline/AIAgentGraphPipeline.kt | interceptNodeExecutionStarting、interceptSubgraphExecutionStarting |
| 事件类型枚举 | .../feature/handler/AgentLifecycleEventType.kt | AgentLifecycleEventType |
| handler 注册表 | .../feature/handler/AgentLifecycleHandlersCollector.kt | addHandlerForFeature、getHandlersForEvent |
| feature 配置基类 | .../feature/config/FeatureConfig.kt | eventFilter、setEventFilter |
| 事件序列化模型 | .../feature/model/events/*.kt | FeatureEvent、llmCallEvents、toolExecutionEvents |
| LLM 埋点装饰器 | .../feature/ContextualPromptExecutor.kt | onLLMCallStarting(:51) |
| 工具埋点装饰器 | .../environment/ContextualAgentEnvironment.kt | onToolCallStarting、collectToolCallMetadata |
| 节点埋点 | .../agent/entity/AIAgentNode.kt | onNodeExecutionStarting(:186) |
| 事件日志 feature | agents/agents-features/agents-features-event-handler/.../EventHandler.kt | EventHandler.Feature.install、handleEvents |
| 追踪 feature | agents-features-trace/.../tracing/feature/Tracing.kt | Tracing.Feature |
| OpenTelemetry feature | agents-features-opentelemetry/.../feature/OpenTelemetry.kt | OpenTelemetry.Feature.install;integration/langfuse/Langfuse.kt、integration/weave/Weave.kt |
| 持久化/断点 | agents-features-snapshot/.../snapshot/feature/Persistence.kt | Persistence、createCheckpointAfterNode、rollbackToLatestCheckpoint |
| 聊天记忆 | agents-features-memory/.../chatMemory/feature/ChatMemory.kt | ChatMemory.Feature、installInternal |
| 历史压缩策略 | agents-core/.../dsl/extension/HistoryCompressionStrategy.kt、DefaultHistoryCompressionStrategies.kt、FactRetrievalHistoryCompressionStrategy.kt | HistoryCompressionStrategy、FactRetrieval、WholeHistory |
| RAG 向量存储 | rag/rag-vector/.../storage/VectorStorage.kt、embedder/DocumentEmbedder.kt | VectorStorage、DocumentEmbedder、TextDocumentEmbedder |
| 规划器入口 | agents/agents-planner/.../planner/Planners.kt | Planners.llmBased、Planners.goap |
| LLM 规划器 | .../planner/llm/SimpleLLMPlanner.kt | SimpleLLMPlanner.buildPlan、executeStep |
| GOAP 规划器 | .../planner/goap/GOAPPlanner.kt | GOAPPlanner、buildPlanForGoal(A*) |