Agent 框架这一年我换了四个,从 LangChain 到 CrewAI 再到 AutoGen,最后落在了 Google ADK。不是说其他的不好,而是 ADK 在 Skill 这层抽象做得最干净——你写出来的 Agent 不是一坨胶水代码,而是真正可以拆、可以测、可以复用的模块。
问题在于,大多数人停留在"跑通 Hello World"阶段。写出来的 Agent 能用但脆弱,Skill 之间耦合严重,稍微改个需求就得重构。这篇我把在生产项目里踩过的坑和总结出来的 5 种 Skill 设计模式一次讲清楚。
模式一:Sequential Chain(顺序链)
最直觉的模式——A 做完给 B,B 做完给 C,严格串行。数据抽取流水线、意图识别 → 参数提取 → API 调用,都是这个套路。
from google.adk import Agent, Skill, SequentialRunner
class ExtractSkill(Skill):
"""从用户输入中提取结构化数据"""
def execute(self, context):
raw_text = context.get("user_input")
entities = self.llm.extract_entities(raw_text)
context.set("entities", entities)
return context
class ValidateSkill(Skill):
"""校验抽取结果的完整性"""
def execute(self, context):
entities = context.get("entities")
missing = [f for f in ["name", "date", "amount"] if f not in entities]
if missing:
context.set("validation_error", f"缺少字段: {missing}")
context.set("is_valid", False)
else:
context.set("is_valid", True)
return context
class PersistSkill(Skill):
"""校验通过才写库,否则跳过"""
def execute(self, context):
if not context.get("is_valid"):
return context
entities = context.get("entities")
db.insert("records", entities)
context.set("result", "写入成功")
return context
agent = Agent(
skills=[ExtractSkill(), ValidateSkill(), PersistSkill()],
runner=SequentialRunner()
)
上线第一周就翻车了两次。第一次是 ValidateSkill 抛异常,PersistSkill 收到空数据直接炸了——每个 Skill 必须做防御性检查,别指望上游永远正常。第二次更隐蔽:多次调用后 context 里残留了上一次请求的数据,导致新请求用了旧字段。解决方案很简单但容易忘——每次新请求创建新的 context 实例。
还有一个不起眼但救命的习惯:给每个 Skill 加 context.log() 记录中间态。出问题的时候你才知道是哪一步开始偏的。
适用度:简单线性流程 ★★★★★ / 复杂分支逻辑 ★★☆☆☆
模式二:Parallel Fan-Out(并行扇出)
一个请求同时触发多个不相干的 Skill,最后把结果合到一起。典型场景:多源数据查询、多模型投票、多维度评估同时跑。
from google.adk import Agent, Skill, ParallelRunner, AggregatorSkill
class SearchDBSkill(Skill):
def execute(self, context):
result = db.query(context.get("query"))
return {"source": "db", "data": result}
class SearchAPISkill(Skill):
def execute(self, context):
result = api.search(context.get("query"))
return {"source": "api", "data": result}
class SearchCacheSkill(Skill):
def execute(self, context):
result = cache.get(context.get("query"))
return {"source": "cache", "data": result}
class MergeResultsSkill(AggregatorSkill):
def aggregate(self, results):
valid = [r for r in results if r["data"] is not None]
priority = {"cache": 0, "db": 1, "api": 2}
valid.sort(key=lambda x: priority.get(x["source"], 99))
return valid[0] if valid else {"error": "所有数据源均无结果"}
agent = Agent(
skills=[SearchDBSkill(), SearchAPISkill(), SearchCacheSkill()],
aggregator=MergeResultsSkill(),
runner=ParallelRunner(timeout_seconds=10)
)
实测数据说明一切:
| 方案 | 3 个数据源总耗时 | 备注 |
|---|---|---|
| 顺序调用 | 1.2s + 0.8s + 0.1s = 2.1s | 逐个等 |
| 并行扇出 | max(1.2, 0.8, 0.1) = 1.2s | 取最慢那个 |
| 并行 + 超时兜底 | 1.0s | 超时的直接丢弃 |
从 2.1s 干到 1.0s,用户体感完全不同。但有个前提——你的 Skill 之间真的没有依赖关系。一旦有数据依赖,老老实实用 Sequential。
模式三:Router Pattern(路由模式)
一个决策 Skill 根据输入动态选择走哪条路。你可以理解为 Agent 内部的 switch-case,但决策逻辑可以是 LLM 驱动,也可以是硬编码规则。
from google.adk import Agent, Skill, RouterSkill
class IntentRouter(RouterSkill):
ROUTES = {
"query": "QuerySkill",
"create": "CreateSkill",
"analyze": "AnalyzeSkill",
}
def route(self, context):
user_input = context.get("user_input")
# LLM 分类——准但贵
intent = self.llm.classify(
user_input,
categories=list(self.ROUTES.keys())
)
return self.ROUTES.get(intent, "FallbackSkill")
关于 LLM 路由和规则路由怎么选,我踩了不少坑后的结论:
| 维度 | LLM 路由 | 规则路由 |
|---|---|---|
| 准确率 | 95%+(模糊意图也能分) | 85%(模糊场景拉胯) |
| 延迟 | 200-800ms | <5ms |
| Token 成本 | 约 500 token/次 | 零 |
| 迭代成本 | 改 prompt 就行 | 正则/关键词要人肉维护 |
我的做法是混合模式:规则先走一遍,命中就直接路由;miss 了再 fallback 到 LLM。线上数据看,80% 的请求被规则吃掉了,LLM 只处理那 20% 的模糊 case,成本直接降了 4 倍。
模式四:Supervisor Pattern(监督者模式)
这是构建复杂 Agent 的核心模式。一个管理 Skill 拿着全局状态,动态调度其他 Skill,能循环、能回退、能重试。
from google.adk import Agent, Skill, SupervisorSkill
class TaskSupervisor(SupervisorSkill):
MAX_RETRIES = 3
def supervise(self, context):
plan = self.llm.plan(context.get("task_description"))
context.set("plan", plan)
for step in plan["steps"]:
skill_name = step["skill"]
retries = 0
while retries < self.MAX_RETRIES:
result = self.dispatch(skill_name, context)
quality = self.llm.evaluate(
task=step["description"],
result=result,
criteria=step.get("success_criteria", "完成且无错误")
)
if quality["passed"]:
context.set(f"step_{step['id']}_result", result)
break
else:
retries += 1
context.set(f"step_{step['id']}_feedback", quality["feedback"])
if retries >= self.MAX_RETRIES:
return {"status": "failed", "step": step["id"]}
return {"status": "completed", "results": context.get_all_results()}
两个关键设计决策需要提前想清楚:
静态计划 vs 动态计划。 静态就是开始时 LLM 一次性吐完所有步骤,按序执行。简单但死板。动态是每步做完让 LLM 重新规划下一步,灵活但 token 消耗实测高 3-5 倍。我的建议是先上静态,等真的遇到需要中途调整的场景再换动态——大部分任务其实不需要。
什么时候该放弃。 不只看重试次数,还要看成本:
if total_tokens_used > budget_limit:
return fallback_result()
if elapsed_time > timeout:
return partial_result()
生产环境里,一个 Supervisor 跑飞了烧掉你一整天的 token 预算,这种事我见过不止一次。
模式五:Specialist Ensemble(专家集成)
多个 Skill 扮演不同专家角色,各自独立分析同一问题,最后由主持人综合意见。代码审查场景最典型:
class SecurityExpert(Skill):
SYSTEM_PROMPT = "你是安全工程师,专注发现代码安全漏洞。"
def execute(self, context):
code = context.get("code_to_review")
analysis = self.llm.analyze(
system=self.SYSTEM_PROMPT,
prompt=f"审查以下代码的安全性:\n{code}"
)
return {"expert": "security", "findings": analysis}
class PerformanceExpert(Skill):
SYSTEM_PROMPT = "你是性能优化专家,专注发现瓶颈和优化机会。"
def execute(self, context):
code = context.get("code_to_review")
analysis = self.llm.analyze(
system=self.SYSTEM_PROMPT,
prompt=f"分析以下代码的性能:\n{code}"
)
return {"expert": "performance", "findings": analysis}
class ReviewModerator(AggregatorSkill):
def aggregate(self, expert_results):
combined = "\n".join([
f"【{r['expert']}】{r['findings']}"
for r in expert_results
])
return self.llm.synthesize(
prompt=f"综合以下专家意见,给出最终审查报告:\n{combined}"
)
我在一个内部项目上跑了对比测试:
| 方案 | 发现问题数 | 误报率 | Token 消耗 |
|---|---|---|---|
| 单 LLM 直接审查 | 12 | 25% | 3,200 |
| 3 专家集成 | 23 | 15% | 9,800 |
| 3 专家 + 交叉验证 | 21 | 8% | 14,500 |
问题发现翻了近一倍,误报率反而降了。代价是 Token 消耗涨了 3-5 倍。这就引出了最实际的问题——成本怎么控。
生产环境成本控制
当你把 Router + Supervisor + Specialist 组合起来用,Token 成本是会爆炸的。三个实战技巧:
1. 分层模型策略
# 路由层——便宜快就行
router.set_model("gemini-2.5-flash") # ~$0.15/1M tokens
# 执行层——能力够用即可
worker.set_model("claude-sonnet-4-6") # ~$3/1M tokens
# 关键决策——上最强的
supervisor.set_model("claude-opus-4-6") # ~$15/1M tokens
多模型混用的项目,最头疼的是管理多套 API Key 和不同的 SDK 接口。我目前是通过 ofox 做统一入口,一个 endpoint 切模型:
client = openai.OpenAI(
base_url="https://api.ofox.ai/v1", # 统一 endpoint,切模型只改 model 参数
api_key="sk-xxx"
)
2. Context 窗口别传满
class SmartContextManager:
def get_for_skill(self, skill_name, full_context):
required_keys = SKILL_CONTEXT_MAP[skill_name]
return {k: full_context[k] for k in required_keys if k in full_context}
每个 Skill 只拿它需要的 context 片段,别把整个 context 一股脑塞进去。实测能省 40-60% 的 token。
3. 缓存中间结果
@skill_cache(ttl=3600, key_fn=lambda ctx: hash(ctx.get("query")))
def execute(self, context):
pass # 缓存命中就不跑了
选型速查表
| 模式 | 复杂度 | Token 成本 | 最佳场景 | 延迟特征 |
|---|---|---|---|---|
| Sequential | 低 | 低 | 线性流水线 | 各步累加 |
| Parallel | 中 | 中 | 多源查询/多维评估 | 取最慢步 |
| Router | 低 | 低-中 | 多类型任务分发 | 路由+1步 |
| Supervisor | 高 | 高 | 复杂多步任务 | 不可预测 |
| Specialist | 中 | 高 | 需多角度专家分析 | 可并行优化 |
常见问题
Q: ADK 和 LangChain 的 Agent 有什么区别?
ADK 更紧密地集成 Google Cloud 生态,原生对接 Vertex AI。LangChain 更通用但工程化程度不如 ADK。如果基础设施在 GCP 上,ADK 是更顺手的选择。
Q: 这些模式可以嵌套吗?
生产系统几乎都是嵌套的。典型组合是 Router → Supervisor → Parallel。但嵌套层数建议不超过 3 层,再深调试成本会指数增长。
Q: 一个 Router 最多管多少个下游 Skill?
ADK 没有硬限制,但路由准确率会随选项变多而下降。经验值是单个 Router 不超过 10 个下游,超了就用分层 Router。
更多 AI 开发实战 → ofox.ai/blog
更新日期:2026-03-26