Koog — 架构与原理
30 秒导读: Koog 是 JetBrains 出的 Kotlin AI agent 框架。它把一个 agent 的行为画成一张有向图——每个节点做一件事(问 LLM、跑工具、压缩历史),节点之间的边决定"下一步去哪、带什么数据"。一个统一的图执行引擎从起点开始,一个节点一个节点地跑,直到走到终点。图用类型安全的 Kotlin DSL 声明,底层 LLM 走可切换的 PromptExecutor(一个执行器可挂多家 provider),观测/记忆/持久化这类横切能力则以可插拔 Feature 挂进拦截管线。
1. 这是什么(零基础也能懂)
一句话定义: 用 Kotlin 写 AI agent 的框架——你描述一张"该怎么一步步干活"的流程图,Koog 负责把它跑起来,并处理跟大模型和工具打交道的所有脏活。
解决谁的什么问题。 假设你要做一个"能自己调用工具、多轮对话、出错能重试"的 AI 助手。裸调 OpenAI SDK 你得自己写:什么时候让模型说话、什么时候执行它要求的工具、工具结果怎么塞回去、对话太长了怎么压缩、换个模型商怎么办。Koog 把这些沉淀成可复用的图节点 + 一个驱动引擎,你只拼装流程,不重复造轮子。
它能做什么(功能):
- 图式工作流:节点 + 边描述 agent 逻辑,引擎驱动执行。
- 工具调用:类型安全地定义工具,自动生成 JSON schema 给 LLM。
- 多 LLM:OpenAI / Anthropic / Google / DeepSeek / OpenRouter / Ollama / Bedrock,运行中可热切换。
- 横切能力:可观测(OpenTelemetry)、记忆、RAG、持久化/检查点、历史压缩,都以 Feature 插拔。
- 多平台:Kotlin Multiplatform,覆盖 JVM / JS / WasmJS / Android / iOS(依据:
README.md:31)。
用起来什么样。 最小可运行 agent(源自 README.md:63-77):
val agent = AIAgent(
promptExecutor = MultiLLMPromptExecutor(OpenAILLMClient(apiKey)), // 挂一个或多个 provider
systemPrompt = "You are a helpful assistant. Answer user questions concisely.",
llmModel = OpenAIModels.Chat.GPT4o
)
val result = agent.run("Hello! How can you help me?") // 一次 run 跑完整张策略图
一句话直觉。 把 agent 想成一台状态机流水线:节点是"工位",边是"传送带上的岔路口"——工位干完活,岔路口根据产出内容(是文本?是工具调用?)决定把料送去哪个下一工位,直到送进"出货口"(finish 节点)。
本节不涉及底层代码;完全没接触过 Koog 的人读完应能说清"它是干嘛的"。
2. 顶层全景(它大概怎么转)
2.1 五个层次
Koog 的代码按职责分层,从上到下是"怎么描述图 → 怎么跑图 → 跑图时调什么"。
| 层 | 干什么 | 代表模块 / 文件 |
|---|---|---|
| 构建层(DSL) | 用 strategy { } / node / edge 拼出图 | agents-core · dsl/builder/、dsl/extension/ |
| 图执行引擎 | 从 start 逐节点跑到 finish,匹配边、管迭代 | agents-core · agent/entity/(node/edge/subgraph/strategy) |
| 编排入口 | AIAgent / RunSession 建上下文、发生命周期事件 | agents-core · agent/(GraphAIAgent、RunSessionImpl) |
| LLM 层 | Prompt 模型 + PromptExecutor + 多 provider client | prompt/*、prompt-executor/* |
| 工具 & Feature | 工具定义/schema/执行;可插拔横切能力 | agents-tools、agents-features-* |
2.2 一张图看懂主控流
怎么读这张图: 从上往下是一次 agent.run() 的控制流;中间那个方框是全书的心脏——图执行主循环;循环里的节点按需向下调用 LLM 层或工具层。
agent.run(input)
│
▼
RunSession ── 建 AIAgentContext、发 onAgentStarting 事件
│ (AIAgentRunSessionImpl.run)
▼
Strategy(顶层图,本身就是一个 subgraph)
│ 从 nodeStart 出发
▼
┌────────────── 图执行引擎:while 主循环 ──────────────┐
│ 1. 执行当前节点 → 得到 output │
│ 2. 逐条试出边(edge) → 第一条"接得住"的即命中 │
│ 3. 沿边跳到下一节点,output 经边转换成新 input │
│ · 到 finish 且无出边 → 退出并返回 │
│ · 某节点没有可走的边 → 抛 "stuck in node" │
│ · 迭代数超 maxAgentIterations → 抛异常 │
└──────────┬───────────────────────────┬─────────────┘
│ 节点内部按需调用 │
▼ ▼
LLM 层 PromptExecutor 工具层 Environment / SafeTool
(按 provider 路由到 client) (执行工具,异常收敛成 Result)
│
▼ 横切:每个节点/LLM/工具事件都过
Feature 拦截管线(观测 / 记忆 / 持久化 …)
2.3 主线走一遍(以预制的 singleRunStrategy 为例)
这是 Koog 自带的"问一次、按需跑工具、再答"的最常见流程(依据:agent/AIAgentSimpleStrategies.kt:29-40):
nodeStart把用户输入原样传给nodeCallLLM。nodeCallLLM问 LLM。产出走两条互斥的边:- 产出是纯文本 →
onTextMessage边命中 → 直达nodeFinish,结束。 - 产出含工具调用 →
onToolCalls边命中 → 去nodeExecuteTool。
- 产出是纯文本 →
nodeExecuteTool执行工具,结果交给nodeSendToolResult。nodeSendToolResult把工具结果塞回 prompt 再问 LLM;产出同样按"文本 vs 工具调用"二选一,要么结束、要么再绕回第 3 步。
关键点: 循环不是写死的 for,而是"边不断把控制流绕回工具节点"自然形成的——图的形状即行为。
3. 阅读地图(建议顺序)
先读引擎(理解"图怎么跑"),再读构建层(理解"图怎么写"),之后按需下钻 LLM / 工具 / Feature。
-
第 1 章 · 图执行引擎:strategy / subgraph / node / edge 与主循环 ——全书心脏。
AIAgentSubgraphBase.executeWithInnerContext的 while 主循环、边匹配(resolveEdge)、迭代上限、"node = subgraph = strategy"的同构递归、检查点恢复。想懂 Koog 先读这章。 -
第 2 章 · 构建层:图 DSL、预制节点与预制策略 ——
strategy { }/node/subgraph/edge怎么用属性委托拼出图;onToolCalls/onTextMessage/onCondition/transformed这套边 DSL;nodeLLMRequest等几十个预制节点;parallel并行节点。 -
第 3 章 · LLM 层:Prompt 模型、Executor、多 Client 与会话 ——
Prompt/Message/LLModel数据模型;PromptExecutor抽象与MultiLLMPromptExecutor的 provider 路由 + fallback;读/写会话(requestLLM家族)如何把"改 prompt、发请求、回填历史"封装成一句话。 -
第 4 章 · 工具系统:定义、schema 生成、注册与安全执行 ——
Tool<TArgs,TResult>;从@Serializable+@LLMDescription自动生成ToolDescriptor(JSON schema);ToolRegistry注册与合并;SafeTool如何把工具异常收敛成Result.Success/Failure供边分流。 -
第 5 章 · Feature 管线与运行时扩展:拦截、观测、持久化、记忆、RAG、规划 ——
AIAgentFeature如何 install 进AIAgentPipeline,interceptNodeExecutionStarting/interceptLLMCallStarting/interceptToolCallStarting/interceptEnvironmentCreated等拦截点;agents-features-*家族(记忆、trace、persistence、tokenizer、a2a、opentelemetry…)一览。
4. 巧妙之处(读完能带走的精华)
① 节点是纯计算,边负责路由 + 数据变换,两者解耦。
节点只管 input → output(AIAgentNodeBase.execute,agent/entity/AIAgentNode.kt:120);"下一步去哪、料怎么变"全在边上。边的 forwardOutput 返回一个 Option:非空=这条边接得住并给出下一节点的输入,空=不匹配、试下一条(agent/entity/AIAgentEdge.kt:23-42、匹配逻辑 AIAgentNode.kt:87-100)。于是"分支/循环/条件"都是数据,不是写死的控制流。
② node = subgraph = strategy,同构递归。
AIAgentSubgraphBase 本身继承 AIAgentNodeBase(agent/entity/AIAgentSubgraph.kt:48-57),而 strategy 又继承 subgraph(AIAgentGraphStrategy.kt:43-54)。一张大图可以把整段子流程当成"一个节点"嵌进去,引擎一套主循环通吃三层,不用为"子图"另写引擎。
③ 统一迭代计数 + 显式卡死报错,防止 agent 失控。
主循环每转一圈 state.iterations++,超 maxAgentIterations 直接抛 AIAgentMaxNumberOfIterationsReachedException(AIAgentSubgraph.kt:288-299);某节点跑完却没有任何可走的边,不是静默停住,而是抛 AIAgentStuckInTheNodeException(AIAgentSubgraph.kt:315-322)。失控和拼错图都会响亮地失败。
④ LLM 可热切换,而且是"复制上下文"式的干净切换。
子图执行时用 context.llm.copy(model = …, tools = …, prompt = …) 换出一个新 LLM 上下文,跑完再还原(AIAgentSubgraph.kt:190-245);底层 MultiLLMPromptExecutor 按 model.provider 查表选 client,查不到就用 fallback 模型(prompt-executor/.../MultiLLMPromptExecutor.kt:148-160、clientFor:292)。换模型/换厂商不丢历史。
⑤ 工具执行被 SafeTool 收敛成结果类型,失败也是"值"。
工具异常不往上炸,而是变成 SafeTool.Result.Failure(environment/SafeTool.kt:181-193);配合边 DSL 的 onSuccessful / onFailure,"成功走这边、失败走那边"直接在图里画出来。容错是一等公民。
⑥ 工具 schema 零样板自动生成。
给参数类打 @Serializable + @LLMDescription,getToolDescriptor 就把 kotlinx.serialization 的结构反射成 JSON schema 交给 LLM(agents-tools/.../schema/SchemaGenerator.kt:66-96,描述提取 :36-40)。你写 Kotlin 数据类,LLM 看到的是标准 function-calling schema。
⑦ Feature 用"同一批事件"既做观测又能改运行时。
拦截点既有只读的 interceptLLMCallStarting / interceptToolCallStarting(feature/pipeline/AIAgentPipelineAPI.kt:344/386),也有能替换环境的 interceptEnvironmentCreated(:305)——测试 Feature 正是借它把真环境换成 mock 环境注入假工具结果。观测与改造走同一套管线。
5. 代码地图(导航索引)
按"想看什么 → 打开哪个文件 → 认哪个符号"排列。符号名比行号抗漂移,grep 符号即可定位。
5.1 图执行引擎(第 1 章)
| 主题 | 文件路径 | 符号 |
|---|---|---|
| 主循环(逐节点跑、匹配边、迭代上限) | agents/agents-core/.../agent/entity/AIAgentSubgraph.kt | AIAgentSubgraphBase.executeWithInnerContext |
| 子图执行 / 工具集切换 / 事件上报 | 同上 | AIAgentSubgraphBase.execute、selectTools |
| 工具选择策略(ALL/NONE/Tools/AutoSelect) | 同上 | ToolSelectionStrategy |
节点基类、边匹配、forwardTo | agents/agents-core/.../agent/entity/AIAgentNode.kt | AIAgentNodeBase、resolveEdge、StartNode、FinishNode |
| 边(转换/过滤,返回 Option) | agents/agents-core/.../agent/entity/AIAgentEdge.kt | AIAgentEdge.forwardOutput |
| strategy = 特殊 subgraph + 检查点恢复 | agents/agents-core/.../agent/entity/AIAgentGraphStrategy.kt | AIAgentGraphStrategyBase.execute、setExecutionPointAfterNode |
| 编排入口:建上下文、装 Feature | agents/agents-core/.../agent/GraphAIAgent.kt | GraphAIAgent、prepareContext |
| 一次 run 的生命周期 | agents/agents-core/.../agent/AIAgentRunSessionImpl.kt | AIAgentRunSessionImpl.run |
5.2 构建层 DSL 与预制件(第 2 章)
| 主题 | 文件路径 | 符号 |
|---|---|---|
strategy { } 入口 + 元数据构建 | agents/agents-core/.../dsl/builder/AIAgentGraphStrategyBuilder.kt | strategy、AIAgentGraphStrategyBuilder.build |
node / subgraph / parallel / then | agents/agents-core/.../dsl/builder/AIAgentSubgraphBuilder.kt | node、subgraph、parallel、AIAgentSubgraphBuilderBase.then |
| 边 DSL 原语 | agents/agents-core/.../dsl/builder/AIAgentEdgeBuilder.kt | AIAgentEdgeBuilderIntermediate.onCondition、transformed |
| 边条件糖(工具调用/文本/成功失败) | agents/agents-core/.../dsl/extension/AIAgentEdges.kt | onToolCalls、onTextMessage、onToolCall、onSuccessful、onFailure |
| 预制节点(问 LLM / 跑工具 / 压历史) | agents/agents-core/.../dsl/extension/AIAgentNodes.kt | nodeLLMRequest、nodeExecuteTools、nodeLLMSendToolResults、nodeLLMCompressHistory |
| 预制策略 | agents/agents-core/.../agent/AIAgentSimpleStrategies.kt | singleRunStrategy |
5.3 LLM 层(第 3 章)
| 主题 | 文件路径 | 符号 |
|---|---|---|
| Prompt 数据模型 | prompt/prompt-model/.../Prompt.kt | Prompt、withMessages、withUpdatedParams |
| 消息模型 | prompt/prompt-model/.../message/Message.kt | Message、MessagePart |
| 模型 + 能力位 | prompt/prompt-llm/.../llm/LLModel.kt | LLModel、supports |
| 执行器抽象 | prompt/prompt-executor/prompt-executor-model/.../model/PromptExecutor.kt | PromptExecutor |
| 多 provider 路由 + fallback | prompt/prompt-executor/prompt-executor-model/.../llms/MultiLLMPromptExecutor.kt | MultiLLMPromptExecutor.resolveModel、clientFor |
| provider client 接口 | prompt/prompt-executor/prompt-executor-clients/.../clients/LLMClientAPI.kt | LLMClientAPI.execute、executeStreaming、moderate |
| 写会话(改 prompt+发请求+回填) | agents/agents-core/.../agent/session/AIAgentLLMWriteSessionCommon.kt | requestLLM、requestLLMOnlyCallingTools、requestLLMStructured |
| 读会话(真正打执行器) | agents/agents-core/.../agent/session/AIAgentLLMReadSessionCommon.kt | requestLLM、executor.executeProcessed |
5.4 工具系统(第 4 章)
| 主题 | 文件路径 | 符号 |
|---|---|---|
| 工具基类 | agents/agents-tools/.../tools/Tool.kt | Tool.execute |
| 工具描述符(schema 载体) | agents/agents-tools/.../tools/ToolDescriptor.kt | ToolDescriptor |
| 从类型自动生成 schema | agents/agents-tools/.../tools/schema/SchemaGenerator.kt | getToolDescriptor、toToolParameter |
| 描述注解 | agents/agents-tools/.../tools/annotations/LLMDescription.kt | LLMDescription |
| 注册表(注册/查找/合并) | agents/agents-tools/.../tools/ToolRegistry.kt | ToolRegistry、plus |
| 安全执行 + 结果收敛 | agents/agents-core/.../environment/SafeTool.kt | SafeTool.execute、Result.Success、Result.Failure |
5.5 Feature 管线(第 5 章)
| 主题 | 文件路径 | 符号 |
|---|---|---|
| Feature 接口(key + install) | agents/agents-core/.../feature/AIAgentFeature.kt | AIAgentFeature、AIAgentGraphFeature |
| 节点/子图拦截点 | agents/agents-core/.../feature/pipeline/AIAgentGraphPipelineAPI.kt | interceptNodeExecutionStarting、interceptSubgraphExecutionStarting |
| LLM/工具/环境拦截点 | agents/agents-core/.../feature/pipeline/AIAgentPipelineAPI.kt | interceptLLMCallStarting、interceptToolCallStarting、interceptEnvironmentCreated |
| Feature 家族(模块清单) | settings.gradle.kts | agents:agents-features:*(memory / trace / persistence / tokenizer / opentelemetry / a2a …) |
全部引用 as-of
sourceCommit: dfa6efa24224c25687e795d9f9b683f9bd50ba80。路径中的.../省略了src/commonMain/kotlin/ai/koog/...中段,按符号名grep可精确定位。