跳到主要内容

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 clientprompt/*prompt-executor/*
工具 & Feature工具定义/schema/执行;可插拔横切能力agents-toolsagents-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):

  1. nodeStart 把用户输入原样传给 nodeCallLLM
  2. nodeCallLLM 问 LLM。产出走两条互斥的边:
    • 产出是纯文本onTextMessage 边命中 → 直达 nodeFinish,结束。
    • 产出含工具调用onToolCalls 边命中 → 去 nodeExecuteTool
  3. nodeExecuteTool 执行工具,结果交给 nodeSendToolResult
  4. nodeSendToolResult 把工具结果塞回 prompt 再问 LLM;产出同样按"文本 vs 工具调用"二选一,要么结束、要么再绕回第 3 步。

关键点: 循环不是写死的 for,而是"边不断把控制流绕回工具节点"自然形成的——图的形状即行为。


3. 阅读地图(建议顺序)

先读引擎(理解"图怎么跑"),再读构建层(理解"图怎么写"),之后按需下钻 LLM / 工具 / Feature。


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);底层 MultiLLMPromptExecutormodel.provider 查表选 client,查不到就用 fallback 模型(prompt-executor/.../MultiLLMPromptExecutor.kt:148-160clientFor: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.ktAIAgentSubgraphBase.executeWithInnerContext
子图执行 / 工具集切换 / 事件上报同上AIAgentSubgraphBase.executeselectTools
工具选择策略(ALL/NONE/Tools/AutoSelect)同上ToolSelectionStrategy
节点基类、边匹配、forwardToagents/agents-core/.../agent/entity/AIAgentNode.ktAIAgentNodeBaseresolveEdgeStartNodeFinishNode
边(转换/过滤,返回 Option)agents/agents-core/.../agent/entity/AIAgentEdge.ktAIAgentEdge.forwardOutput
strategy = 特殊 subgraph + 检查点恢复agents/agents-core/.../agent/entity/AIAgentGraphStrategy.ktAIAgentGraphStrategyBase.executesetExecutionPointAfterNode
编排入口:建上下文、装 Featureagents/agents-core/.../agent/GraphAIAgent.ktGraphAIAgentprepareContext
一次 run 的生命周期agents/agents-core/.../agent/AIAgentRunSessionImpl.ktAIAgentRunSessionImpl.run

5.2 构建层 DSL 与预制件(第 2 章)

主题文件路径符号
strategy { } 入口 + 元数据构建agents/agents-core/.../dsl/builder/AIAgentGraphStrategyBuilder.ktstrategyAIAgentGraphStrategyBuilder.build
node / subgraph / parallel / thenagents/agents-core/.../dsl/builder/AIAgentSubgraphBuilder.ktnodesubgraphparallelAIAgentSubgraphBuilderBase.then
边 DSL 原语agents/agents-core/.../dsl/builder/AIAgentEdgeBuilder.ktAIAgentEdgeBuilderIntermediate.onConditiontransformed
边条件糖(工具调用/文本/成功失败)agents/agents-core/.../dsl/extension/AIAgentEdges.ktonToolCallsonTextMessageonToolCallonSuccessfulonFailure
预制节点(问 LLM / 跑工具 / 压历史)agents/agents-core/.../dsl/extension/AIAgentNodes.ktnodeLLMRequestnodeExecuteToolsnodeLLMSendToolResultsnodeLLMCompressHistory
预制策略agents/agents-core/.../agent/AIAgentSimpleStrategies.ktsingleRunStrategy

5.3 LLM 层(第 3 章)

主题文件路径符号
Prompt 数据模型prompt/prompt-model/.../Prompt.ktPromptwithMessageswithUpdatedParams
消息模型prompt/prompt-model/.../message/Message.ktMessageMessagePart
模型 + 能力位prompt/prompt-llm/.../llm/LLModel.ktLLModelsupports
执行器抽象prompt/prompt-executor/prompt-executor-model/.../model/PromptExecutor.ktPromptExecutor
多 provider 路由 + fallbackprompt/prompt-executor/prompt-executor-model/.../llms/MultiLLMPromptExecutor.ktMultiLLMPromptExecutor.resolveModelclientFor
provider client 接口prompt/prompt-executor/prompt-executor-clients/.../clients/LLMClientAPI.ktLLMClientAPI.executeexecuteStreamingmoderate
写会话(改 prompt+发请求+回填)agents/agents-core/.../agent/session/AIAgentLLMWriteSessionCommon.ktrequestLLMrequestLLMOnlyCallingToolsrequestLLMStructured
读会话(真正打执行器)agents/agents-core/.../agent/session/AIAgentLLMReadSessionCommon.ktrequestLLMexecutor.executeProcessed

5.4 工具系统(第 4 章)

主题文件路径符号
工具基类agents/agents-tools/.../tools/Tool.ktTool.execute
工具描述符(schema 载体)agents/agents-tools/.../tools/ToolDescriptor.ktToolDescriptor
从类型自动生成 schemaagents/agents-tools/.../tools/schema/SchemaGenerator.ktgetToolDescriptortoToolParameter
描述注解agents/agents-tools/.../tools/annotations/LLMDescription.ktLLMDescription
注册表(注册/查找/合并)agents/agents-tools/.../tools/ToolRegistry.ktToolRegistryplus
安全执行 + 结果收敛agents/agents-core/.../environment/SafeTool.ktSafeTool.executeResult.SuccessResult.Failure

5.5 Feature 管线(第 5 章)

主题文件路径符号
Feature 接口(key + install)agents/agents-core/.../feature/AIAgentFeature.ktAIAgentFeatureAIAgentGraphFeature
节点/子图拦截点agents/agents-core/.../feature/pipeline/AIAgentGraphPipelineAPI.ktinterceptNodeExecutionStartinginterceptSubgraphExecutionStarting
LLM/工具/环境拦截点agents/agents-core/.../feature/pipeline/AIAgentPipelineAPI.ktinterceptLLMCallStartinginterceptToolCallStartinginterceptEnvironmentCreated
Feature 家族(模块清单)settings.gradle.ktsagents:agents-features:*(memory / trace / persistence / tokenizer / opentelemetry / a2a …)

全部引用 as-of sourceCommit: dfa6efa24224c25687e795d9f9b683f9bd50ba80。路径中的 .../ 省略了 src/commonMain/kotlin/ai/koog/... 中段,按符号名 grep 可精确定位。