跳到主要内容

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)
AIAgentFeaturefeature 的最小契约:一个唯一 key + 造初始 configfeature/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
AgentLifecycleHandlersCollectorfeatureKey × 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 的契约极简——只有 keycreateInitialConfig(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 同时实现了 AIAgentGraphFeatureAIAgentFunctionalFeatureAIAgentPlannerFeature 三个接口(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) -> UnitinvokeRegisteredHandlersForEvent(type, ctx)
变换型(Transform)依次改写一个值,前一个的输出喂给后一个suspend (Context, T) -> TinvokeRegisteredHandlersForEvent(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:591interceptEnvironmentCreated)。
  • 工具元数据贡献 collectToolCallMetadata——工具执行前,各 feature 各自贡献一小段 Map(如追踪的 span id),合并后线程给 Tool.execute(AIAgentPipelineImpl.kt:405,注册点 provideToolCallMetadata:673)。合并规则:后装的 feature 覆盖先装的同名 key。

3.5 触发点究竟埋在哪

管线自己不产生事件,事件由引擎在三处埋点广播。这是"可观测性统一底座"的落点:

事件族埋点位置(path:line)广播的方法
节点执行agent/entity/AIAgentNode.kt:186,204,216onNodeExecutionStarting/Failed/Completed
LLM 调用/流式feature/ContextualPromptExecutor.kt:51,73,87,135,159,171,187onLLMCallStarting/Completed/FailedonLLMStreaming*
工具调用environment/ContextualAgentEnvironment.kt:69,101,112,160,189onToolCallStarting/CompletedcollectToolCallMetadataonToolValidationFailed

命名规律: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-opentelemetryOTel 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-snapshotcheckpoint 核心:存/取 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.mdrestoreDefault 的检查点恢复——引擎提供"回到某节点"的能力,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):llmBasedllmBasedWithCriticgoap

两种实现,思路完全不同:

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.ktAIAgentFeatureAIAgentGraphFeature.install
管线对外契约.../feature/pipeline/AIAgentPipeline.ktinterceptLLMCallStartingonLLMCallCompleted
管线实现(接线板).../feature/pipeline/AIAgentPipelineImpl.ktinvokeRegisteredHandlersForEventcreateConditionalHandlerprepareFeatures
图专属拦截点.../feature/pipeline/AIAgentGraphPipeline.ktinterceptNodeExecutionStartinginterceptSubgraphExecutionStarting
事件类型枚举.../feature/handler/AgentLifecycleEventType.ktAgentLifecycleEventType
handler 注册表.../feature/handler/AgentLifecycleHandlersCollector.ktaddHandlerForFeaturegetHandlersForEvent
feature 配置基类.../feature/config/FeatureConfig.kteventFiltersetEventFilter
事件序列化模型.../feature/model/events/*.ktFeatureEventllmCallEventstoolExecutionEvents
LLM 埋点装饰器.../feature/ContextualPromptExecutor.ktonLLMCallStarting(:51)
工具埋点装饰器.../environment/ContextualAgentEnvironment.ktonToolCallStartingcollectToolCallMetadata
节点埋点.../agent/entity/AIAgentNode.ktonNodeExecutionStarting(:186)
事件日志 featureagents/agents-features/agents-features-event-handler/.../EventHandler.ktEventHandler.Feature.installhandleEvents
追踪 featureagents-features-trace/.../tracing/feature/Tracing.ktTracing.Feature
OpenTelemetry featureagents-features-opentelemetry/.../feature/OpenTelemetry.ktOpenTelemetry.Feature.install;integration/langfuse/Langfuse.ktintegration/weave/Weave.kt
持久化/断点agents-features-snapshot/.../snapshot/feature/Persistence.ktPersistencecreateCheckpointAfterNoderollbackToLatestCheckpoint
聊天记忆agents-features-memory/.../chatMemory/feature/ChatMemory.ktChatMemory.FeatureinstallInternal
历史压缩策略agents-core/.../dsl/extension/HistoryCompressionStrategy.ktDefaultHistoryCompressionStrategies.ktFactRetrievalHistoryCompressionStrategy.ktHistoryCompressionStrategyFactRetrievalWholeHistory
RAG 向量存储rag/rag-vector/.../storage/VectorStorage.ktembedder/DocumentEmbedder.ktVectorStorageDocumentEmbedderTextDocumentEmbedder
规划器入口agents/agents-planner/.../planner/Planners.ktPlanners.llmBasedPlanners.goap
LLM 规划器.../planner/llm/SimpleLLMPlanner.ktSimpleLLMPlanner.buildPlanexecuteStep
GOAP 规划器.../planner/goap/GOAPPlanner.ktGOAPPlannerbuildPlanForGoal(A*)