架构设计
Google ADK

Langchain-ai

核心流程设计
Google ADK

Langchain-ai


核心功能设计
Skill
Google ADK

| 阶段 | 核心动作 | 关键类 |
|---|---|---|
| Phase 1: Skill 定义 | Skill = 文件夹 (SKILL.md + references/ + assets/ + scripts/);元数据由 Frontmatter (YAML) 表示 |
Frontmatter, SkillSource 接口 |
| Phase 2: 加载 (Source 构造) | 三种 SkillSource 实现从不同介质加载 skill;共享解析逻辑在 AbstractSkillSource |
ClassPathSkillSource, LocalSkillSource, InMemorySkillSource, AbstractSkillSource |
| Phase 3: 注册 | SkillToolset 包装 SkillSource,创建 3 个工具,挂到 LlmAgent |
SkillToolset, BaseToolset |
| Phase 4: 发现 (每次 LLM 请求) | processLlmRequest 自动列出所有 skill frontmatter,拼成 <available_skills> XML 注入系统提示 |
SkillToolset.processLlmRequest, AbstractSkillSource.listFrontmatters |
| Phase 5: 查找 (LLM 工具调用) | LLM 调用 3 个工具之一:list_skills / load_skill / load_skill_resource |
ListSkillsTool, LoadSkillTool, LoadSkillResourceTool |
| Phase 6: Script 运行 | run_skill_script |
RunSkillScriptTool, CodeExecution, BaseCodeExecutoradk java原生缺,adk python不缺。 |
langchain-ai

Skills 中间件实现了 渐进式披露(progressive disclosure) 模式:
- 会话开始时 一次性加载所有 skill 的元数据(name + description),写入
SkillsState; - 每次模型调用时把 skill 列表格式化进系统提示,告诉模型「有哪些 skill 可用」;
- 运行时由模型驱动 :模型根据任务决定是否调用某个 skill,再按需
read_file读取完整的SKILL.md、辅助脚本与资源,最后执行脚本或按SKILL.md中的工作流完成任务。
中间件只负责前两步(加载 + 注入),第三步的「查找 → 读资源 → 读脚本 → 运行脚本 → 运行 Skill」是模型在运行时通过工具调用自主完成的------中间件不直接执行这些动作,而是通过系统提示引导模型完成。
LangChain deepagents skills 内置了常见的CLi命令,需要注入:FileSystemMiddleware,使用FilteSystemBackend执行。
自定义的CLI命令,需要自己实现Tool,或者自定义Middleware,自定义Backend
自己实现Tool,参考:
python
@tool
def run_calc_funnel(command: str, flow_id: int) -> str:
"""Execute a calc_funnel.py CLI subcommand and return the output.
This tool runs the marketing funnel analysis script as a CLI command.
The script must be called with a subcommand and a flow_id.
Available subcommands:
- get_funnel_data: Get raw funnel stage data for a given flow_id
- check_funnel_data: Validate funnel data consistency for a given flow_id
- calc_funnel_rate: Calculate overall conversion rates for each funnel stage
- calc_funnel_decay: Calculate step-by-step conversion rates and find the biggest decay stage
Args:
command: The subcommand to execute (get_funnel_data, check_funnel_data, calc_funnel_rate, calc_funnel_decay)
flow_id: The marketing flow ID to analyze (e.g., 1, 2, 3)
"""
valid_commands = {"get_funnel_data", "check_funnel_data", "calc_funnel_rate", "calc_funnel_decay"}
if command not in valid_commands:
return f"Invalid command '{command}'. Valid commands: {', '.join(sorted(valid_commands))}"
cmd = [sys.executable, str(CALC_FUNNEL_SCRIPT), command, "--flow_id", str(flow_id)]
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=30,
)
output = result.stdout.strip() if result.stdout.strip() else result.stderr.strip()
return output if output else "(no output)"
except subprocess.TimeoutExpired:
return "Error: command timed out after 30 seconds"
except Exception as e:
return f"Error executing command: {e}"
自定义实现Middleware,使用sandbox
python
import asyncio
from pathlib import Path
from typing import Any
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StoreBackend
from deepagents.backends.langsmith import LangSmithSandbox
from deepagents.backends.utils import create_file_data
from langchain.agents.middleware import AgentMiddleware, AgentState
from langgraph.runtime import Runtime
from langgraph.store.memory import InMemoryStore
from langsmith.sandbox import SandboxClient
# Identical skill bundles for every user: one shared store namespace.
SKILLS_SHARED_NAMESPACE = ("skills", "builtin")
class SkillSandboxSyncMiddleware(AgentMiddleware[AgentState, Any, Any]):
"""Copy shared skill files from the store into the sandbox before each agent run."""
def __init__(self, backend: CompositeBackend) -> None:
super().__init__()
self.backend = backend
async def abefore_agent(self, state: AgentState, runtime: Runtime[Any]) -> None:
store = runtime.store
files: list[tuple[str, bytes]] = []
for item in await store.asearch(SKILLS_SHARED_NAMESPACE):
key = str(item.key)
if ".." in key or any(c in key for c in ("*", "?")):
msg = f"Invalid key: {key}"
raise ValueError(msg)
normalized = key if key.startswith("/") else f"/{key}"
# CompositeBackend routes paths and batches uploads to the right backend.
files.append((f"/skills{normalized}", item.value["content"].encode()))
if files:
await self.backend.aupload_files(files)
async def seed_skill_store(store: InMemoryStore) -> None:
"""Load canonical skill files from disk into the shared store namespace (run once at deploy).
You can retrieve skills from any source (local filesystem, remote URL, etc.).
"""
skills_dir = Path(__file__).resolve().parent / "skills"
for file_path in sorted(p for p in skills_dir.rglob("*") if p.is_file()):
rel = file_path.relative_to(skills_dir).as_posix()
key = f"/{rel}"
await store.aput(
SKILLS_SHARED_NAMESPACE,
key,
create_file_data(file_path.read_text(encoding="utf-8")),
)
async def main() -> None:
store = InMemoryStore()
await seed_skill_store(store)
client = SandboxClient()
ls_sandbox = client.create_sandbox()
sandbox_backend = LangSmithSandbox(sandbox=ls_sandbox)
backend = CompositeBackend(
default=sandbox_backend,
routes={
"/skills/": StoreBackend(
store=store,
namespace=lambda _rt: SKILLS_SHARED_NAMESPACE,
),
},
)
try:
agent = create_deep_agent(
model="openai:gpt-5.5",
backend=backend,
skills=["/skills/"],
store=store,
middleware=[SkillSandboxSyncMiddleware(backend)],
)
finally:
client.delete_sandbox(ls_sandbox.name)
if __name__ == "__main__":
asyncio.run(main())
MultiAgent
Google adk

langchain-ai

create_deep_agent 是 Deep Agents 的主入口,负责把模型、工具、子代理、中间件、权限、内存等配置装配成一个可运行的 CompiledStateGraph。整个过程可划分为 6 个阶段,按顺序依次执行:
- 模型与 Profile 解析
- 工具与 Backend 初始化
- 子代理(Subagents)处理
- 通用子代理(General-Purpose)自动注入
- 主代理中间件栈装配
- 状态聚合与最终构造
Human In The Loop
Google adk

HITL 确认是一个跨两次 runAsync 调用的流程,图中的虚线分隔标记了 Turn 1 和 Turn 2 的边界。
Turn 1:工具请求确认
- LLM 调用工具 (如
delete_file),Functions.handleFunctionCalls调用FunctionTool.runAsync。 - 确认检查 (FunctionTool.java:248):
requireConfirmation=true且toolContext.toolConfirmation()为空(首次调用,无确认)。 - 请求确认 :
toolContext.requestConfirmation(hint)把ToolConfirmation(hint, confirmed=false)写入EventActions.requestedToolConfirmations[callId]。 - 返回错误响应 :工具返回
{"error": "requires confirmation"},作为 FunctionResponse Event。 - 生成确认请求事件 :
Functions.generateRequestConfirmationEvent生成一个request_confirmationFunctionCall Event,其 args 中携带originalFunctionCall(原始调用信息)。 - 流程暂停:确认请求事件流式返回给调用方,流程暂停,等待用户响应。
用户响应(两次 runAsync 之间)
- 用户决定批准或拒绝,以
request_confirmationFunctionResponse(含ToolConfirmation(confirmed=true/false))的形式回传。 - 该响应作为 user event 追加到 session。
Turn 2:恢复已确认工具(preprocess)
- 下一次
runAsync触发 preprocess,RequestConfirmationLlmRequestProcessor.processRequest执行(RequestConfirmationLlmRequestProcessor.java:60)。 - 扫描历史 :
findMostRecentConfirmations从 session 末尾向前找最近的 user event 中的request_confirmationFunctionResponse(RequestConfirmationLlmRequestProcessor.java:148)。 - 找不到:跳过,走正常 LLM 调用流程。
- 找到 :提取
ToolConfirmation,并从request_confirmation的 args 中解析出originalFunctionCall(原始工具调用)。 - 过滤已执行:扫描确认事件之后的 FunctionResponse ID,移除已经执行过的工具(避免重复执行)(RequestConfirmationLlmRequestProcessor.java:114)。
- 无剩余:跳过。
- 有剩余 :通过
Functions.handleFunctionCalls重新执行,这次传入toolConfirmations映射。 - FunctionTool.runAsync 再次执行 (FunctionTool.java:246):这次
toolContext.toolConfirmation()非空:
-
confirmed=true:调用call()真正执行工具,返回真实结果;confirmed=false:返回{"error": "rejected"}。
- 结果作为
RequestProcessingResult返回,flow 继续推进。
Langchain-ai

- 模型输出 :模型节点产出一条带
tool_calls的AIMessage,图准备路由到ToolNode,但在此之前先经过after_model钩子。 - 钩子触发 :
after_model读取state["messages"],定位最后一条AIMessage;若不存在或没有tool_calls,直接返回None,中间件「透明」。 - 逐个筛选 :遍历
tool_calls:
-
- 不在
interrupt_on→ 自动放行(绿色)。 - 在
interrupt_on但when(req)返回False→ 自动放行(绿色)。 - 两者都通过 → 生成
ActionRequest+ReviewConfig,记下原始下标idx,加入中断列表。
- 不在
- 是否需要中断 :若中断列表为空,返回
None,全部工具正常执行。 - 批量中断 :把所有
ActionRequest/ReviewConfig打包成HITLRequest,调用interrupt(hitl_request)。整张图在此暂停,控制权交回调用方。 - 人工决策 :调用方恢复执行时传入
HITLResponse(decisions=[...]),decisions顺序与action_requests一一对应;数量不符则抛ValueError。 - 按类型改写 :对每个决策调用
_process_decision,按原始下标顺序重建tool_calls:
-
approve/edit:不生成ToolMessage,ToolNode会执行(edit执行编辑后的版本)。reject/respond:保留tool_call但追加合成的ToolMessage,ToolNode因已有响应而跳过执行,模型直接读到人工提供的内容。
- 写回并返回 :
last_ai_msg.tool_calls = revised_tool_calls,返回{"messages": [last_ai_msg, *artificial_tool_messages]}。图继续走向ToolNode。
综合比较
HITL(Human-In-The-Loop)
| 维度 | LangChain-ai 生态 | Google ADK |
|---|---|---|
| 中断机制 | interrupt() 原语,抛出 GraphInterrupt 异常 |
FunctionTool (confirm) |
| 恢复机制 | Command(resume=value) 传入恢复值 |
通过 invocation_id + FunctionResponse 恢复调用链 |
| 决策类型 | 4 种:Approve / Edit / Reject / Respond | 2 种:approve / reject(需手动扩展) |
| 状态持久化 | Checkpoint 自动保存中断点 | Event Stream + Session.state 保存 confirm 等 |
| 多次中断 | 原生支持(同一节点多次 interrupt()) |
需覆盖 _get_declaration() 移除防重复调用指令(非默认行为) |
| 中断粒度 | 任意代码位置 | 仅在 FunctionTool 返回时 |
| 恢复后执行 | 从中断点继续执行(checkpoint 恢复) | rerun_on_resume 重跑节点(DynamicNodeScheduler 惰性重建状态) |
关键差异:
- LangGraph 的
interrupt()是任意位置中断原语,可以停在代码任何地方,恢复时从断点继续。天然支持多步 HITL - ADK 的 HITL 仅在 FunctionTool 返回时 才能中断,且默认带"不要重复调用"指令,多步 HITL 需要手动覆盖(FunctionTool)。恢复后是
rerun_on_resume(重跑),而非断点续行
意图识别与动态路由
| 维度 | LangChain-ai 生态 | Google ADK |
|---|---|---|
| 路由方式 | conditional_edges(StateGraph 条件边) | transfer_to_agent tool(LLM 自主路由) |
| 动态节点 | Send 实现动态 fan-out |
DynamicNodeScheduler + ctx.run_node() |
| 意图识别 | 在 conditional_edge 函数中实现,可通过工程化代码,LLM,规则引擎,分词搜索等方式实现意图识别 | Agent 自身即意图识别器,LLM 判断后调用 transfer |
| 路由灵活性 | 编译时确定边,运行时条件选择 | 完全由 LLM 运行时决定 |
| 确定性 | 高(图结构编译时固定) | 低(依赖 LLM 判断质量) |
关键差异:
- LangGraph 的路由是编译时声明 + 运行时条件,图结构可见、可调试
- ADK 的路由是LLM 运行时自主决策,灵活但不可预测,依赖 prompt engineering 约束
Skills
| 维度 | LangChain-ai 生态(DeepAgents) | Google ADK |
|---|---|---|
| Skill 格式 | SKILL.md(YAML frontmatter + markdown) | 目录结构(SKILL.md + scripts/) |
| 加载方式 | load_skill_from_dir |
load_skill_from_dir |
| 渐进注入 | SkillsMiddleware 实现 4 阶段:discovery → availability → activation → injection | SkillToolset:list_skills → load_skill 两步 |
| 分层优先级 | SDK → user → project → team | 无显式分层 |
| 安全性 | SkillsState 约束,references 限制 | UnsafeLocalCodeExecutor(无安全约束) |
| 稳定性 | SkillsMiddleware 管理 SkillsState,可排除/重载 | SkillToolset 一次性加载,无运行时管理 |
| 运行CLI | 1. FileSystemMiddleware内置的一些常见的CLI 命令 |
- 自定义tool
- 自定义Middleware | 1. 运行 run_skill_script tool。adk java原生缺,adk_python原生有。
- adk python 运行不稳定,参数推断错 |
优缺点
Google ADK 优缺点
优点:
- 一体化与开箱即用:Runner → Flow → Agent → Tool 全链路封装,无需拼装多个库,部署成本低。
- Session / State / Event 一等公民 :内建
SessionService、State、Event体系,会话持久化、状态增量(stateDelta)、Artifact 管理原生支持,无需额外选型。 - Plugin / Callback 扩展点丰富 :
beforeModelCallback/afterModelCallback/beforeToolCallback/afterToolCallback/onEventCallback等钩子覆盖全生命周期,且可通过Plugin接口统一管理。 - RequestProcessor / ResponseProcessor 管线清晰 :
SingleFlow.REQUEST_PROCESSORS链式处理 LlmRequest(Instructions / Identity / Compaction / CodeExecution / Tools),postprocess 阶段统一处理 functionCalls / AgentTransfer / OutputSchema,流程可追踪。 - Skill 体系规范 :6 阶段(定义 → 加载 → 注册 → 发现 → 查找 → 脚本运行),
SkillToolset自动注入<available_skills>XML,渐进式披露由框架兜底。 - 多 Agent 转移语义统一 :
transfer_to_agent作为工具调用,LLM 自主路由,无需手工编排条件边,适合动态多 Agent 协作。 - Tracing 内建 :
Tracing.traceCallLlm等 span 原生集成,可观测性起步门槛低。
缺点:
- HITL 能力较弱 :中断仅在 FunctionTool 返回时触发,默认带"不要重复调用"指令,多步 HITL 需手动覆盖
_get_declaration();恢复后是rerun_on_resume(重跑节点)而非断点续行,语义不直观。 - 路由确定性低 :完全依赖 LLM 判断
transfer_to_agent,缺乏编译时图结构约束,复杂流程难以静态验证与调试。 - 状态图能力缺失:无显式 StateGraph / conditional_edges / Send(fan-out)等图原语,复杂分支与并行编排需借助 Flow 与 RequestProcessor 间接实现,表达力弱于 LangGraph。
- Checkpointer / Time Travel 缺失:无 LangGraph 那样的状态回放与历史修改能力,长程任务的回溯与调试依赖 Event Stream 手工分析。
- Skill 脚本运行不完整 :
run_skill_script在 adk java 原生缺失,adk python 存在但参数推断不稳定;UnsafeLocalCodeExecutor缺乏安全约束。 - 生态广度受限:相比 LangChain 社区的工具 / 向量库 / 检索器集成规模,ADK 的第三方集成相对较少,主要绑定 Google 生态(Gemini / Vertex)。
- 多步 HITL 与复杂中断场景需自行扩展:原生仅 approve / reject 两类决策,Approve / Edit / Reject / Respond 需手工补齐。
LangChain-AI 生态优缺点
优点:
- 分层清晰、按需选用:LangChain(原语)→ LangGraph(状态图)→ DeepAgents(自治框架)分层明确,可从原语层逐步升级,避免过度封装。
- LangGraph 状态图表达力强 :
StateGraph+conditional_edges+Send提供编译时可见、可调试的图结构;Checkpointer(Postgres / SQLite)原生支持状态持久化与 Time Travel(状态回放 / 修改)。 - HITL 原语强大 :
interrupt()可在任意代码位置 中断,Command(resume=value)恢复;支持 4 类决策(Approve / Edit / Reject / Respond),天然适配多步审批与复杂人工流程。 - Middleware 钩子体系成熟 :
before_agent/before_model/after_model/after_agent/wrap_model_call/wrap_tool_call钩子粒度细,且支持同步 / 异步对称实现,易于横切关注点。 - DeepAgents 高级抽象完备 :
create_deep_agent一站式装配模型 / 工具 / 子代理 / 中间件 / 权限 / 内存 / Skills,Profile + Backend + Subagents + General-Purpose + 状态聚合 6 阶段流程化。 - Skills 渐进式披露规范 :
SkillsMiddleware实现 discovery → availability → activation → injection 4 阶段;分层优先级(SDK → user → project → team);SkillsState约束 + 安全的 references 限制。 - 生态与可观测性最丰富:LangSmith / LangGraph Cloud 提供完整可观测性与托管;工具 / 向量库 / 检索器 / 模型适配器覆盖最广。
- 确定性路由与动态 fan-out :编译时声明条件边 + 运行时
Send动态 fan-out,兼顾确定性与灵活性,适合复杂工作流。
缺点:
- 学习曲线与拼装成本高:三层生态需理解 LangChain LCEL、LangGraph StateGraph、DeepAgents Harness 三套抽象,入门门槛与初始工程化成本显著高于 ADK。
- Session / State 抽象分散:状态管理依赖 Checkpointer + Store + SkillsState 多处,缺少 ADK 那种统一的 Session / Event / Artifact 一等公民模型,跨层一致性需自行维护。
- Runner / 持久化需要额外组件:生产部署需引入 LangGraph Cloud 或自建 Postgres / SQLite Checkpointer,运维复杂度高于 ADK 的内建 SessionService。
- Skill 脚本运行需自配 Backend :DeepAgents 的
run_skill_script依赖FileSystemMiddleware+FileSystemBackend或自定义 Sandbox(如 LangSmithSandbox),自定义 CLI 命令需手写 Tool 或 Middleware,工程量较 ADK 的RunSkillScriptTool更重。 - 中间件链复杂时调试困难 :
wrap_model_call/wrap_tool_call多层嵌套(_chain_model_call_handlers/_chain_tool_call_wrappers),顺序与异常传播路径不直观,易出错。 - 路由虽确定但灵活性受限:编译时固定边结构,动态意图识别需在 conditional_edge 函数中自行实现(规则 / LLM / 分词搜索),变更图结构需重新 compile,迭代成本高于 ADK 的 LLM 自主路由。
- Python 为主,多语言支持弱:LangChain 生态以 Python 为中心,Java / Go 等语言_binding 相对薄弱,跨语言团队需额外适配。