微信扫码
添加专属顾问
我要投稿
从单Agent到多Agent架构的优化实践,Token消耗直降60%,代码更简洁高效!核心内容: 1. 从单Agent多Tool到主Agent+多Sub-Agent的架构演进 2. 优化提示词与引入缓存带来的显著Token降幅 3. ADK框架相比LangChain的代码简洁优势与性能提升
在Agent:从零到一的实践指南(含关键代码)" data-itemshowtype="0" linktype="text" data-linktype="2">用 LangChain 打造你的第一个 AI Agent:从零到一的实践指南(含关键代码)这篇文章中,我用 LangChain实现了一个 AI Agent,结构是单主 Agent + 多个 Tool(如:DatabaseInfoTool
、TableSchemaTool
、QueryExecutorTool
、SampleDataTool
等)。后来我用 Google ADK把这个 Agent 重写了一遍,依旧沿用了“一个主 Agent 带多个 Tool”的思路。
在随后的分析中从ReAct流程看LangChain和Google ADK的Agent设计理念差异,我发现:在 ADK 框架中,单 Agent 多 Tool 的架构 Token 消耗偏高。于是我提出一个设计思路:ADK 更适合“主 Agent + 多个 Sub-Agent”的结构——每个 Sub-Agent 只包含一个 Tool,职责单一、边界清晰。
一、优化效果:立竿见影
我按这个思路把 ADK 架构改成了1 个主 Agent 负责调度,多个 Sub-Agent 负责执行,且每个 Sub-Agent 只绑定 1 个 Tool,严格遵守“单一职责”原则。
主要做了以下两方面的调整:
第一步(架构从单 Agent 多 Tool → 主 + 多 Sub-Agent):Token 立减约 35%。
第二步(在此基础上优化提示词 + 引入缓存):总降幅达到约 60%。
由于ADK 框架使用了 LLM 的原生能力(如工具筛选、Function Call等),所以在 Agent 的代码实现上确实更加的简洁。调整完成后,项目总代码量大约为 1800 行,比 LangChain 版本的体量小很多。
说明:上述降幅是在同一任务下统计的,统计口径一致(包括 prompt、function call等Agent和LLM之间交互的所有 token)。
之前:单 Agent 多 Tool
统一持有多个 Tool,路由调度集中在主 Agent 内部。
上下文容易“越滚越大”,无形增加 Token。
工具候选多时,模型在选择与澄清上会产生大量Token消耗,也容易造成额外的交互。
现在:主 Agent + 多 Sub-Agent(每个 Sub-Agent 仅 1 个 Tool)
主 Agent 只负责意图识别和任务分发。
Sub-Agent 处理单一职责,上下文短、交互简单。
路由清晰,减少无效澄清与工具试探。
为什么能省:
上下文更短:Sub-Agent 只携带本职所需的少量上下文与历史信息。
工具候选更少:每个 Sub-Agent 仅 1 个 Tool,减少工具选择带来的额外 token。
调度更“干脆”:主 Agent 明确路由,避免在一个 Agent 里多轮试错。
工具描述更精简:职责切分后,工具描述能够更加的精简,可以删除大量的冗长描述。
2. 项目结构
sqlite-multi-agent/
├── sqlite_agent/ # 主要智能体代码
│ ├── agent.py # 主智能体实现
│ ├── config.py # 配置管理
│ ├── tools.py # 工具函数
│ ├── utils.py # 工具类
│ ├── prompts.py # 提示词模板
│ ├── monitored_llm.py # LLM监控包装器(LLM必须,监控为可选)
│ └── sub_agents/ # 子智能体
│ ├── list_tables/ # 表列表智能体
│ ├── table_info/ # 表信息智能体
│ ├── query_execution/ # 查询执行智能体
│ └── sample_data/ # 样本数据智能体
├── examples/ # 示例代码
├── tests/ # 测试代码
└── deployment/ # 部署脚本
def execute_query(query: str) -> str:
"""Execute a SELECT query on the SQLite database. Use for data retrieval, filtering, aggregation, and analysis. Only SELECT queries are allowed."""
config = get_sqlite_config()
# Validate query for safety
validation_result = validate_query(query)
if not validation_result["valid"]:
return f"Query validation failed: {validation_result['error']}"
try:
with get_database_connection(config.database_path) as conn:
cursor = conn.cursor()
cursor.execute(query)
# Get column names
columns = [description[0] for description in cursor.description] if cursor.description else []
# Fetch results with limit
results = cursor.fetchmany(config.max_results)
if not results:
return "Query executed successfully but returned no results."
# Format results
return format_results(results, columns, config.max_results)
except Exception as e:
return f"Error executing query: {str(e)}"
# Create ADK function tools
list_tables_tool = FunctionTool(list_tables)
get_table_info_tool = FunctionTool(get_table_info)
execute_query_tool = FunctionTool(execute_query)
get_sample_data_tool = FunctionTool(get_sample_data)
# Export all tools
sqlite_tools = [
list_tables_tool,
get_table_info_tool,
execute_query_tool,
get_sample_data_tool
]
结果格式化:
def format_results(results: List[Any], columns: List[str], max_results: int) -> str:
"""Format query results for display."""
if not results:
return "No results found."
# Convert results to list of lists for tabulate
formatted_rows = []
for row in results:
if hasattr(row, '_fields'): # sqlite3.Row object
formatted_rows.append([str(value) if value is not None else 'NULL' for value in row])
else:
formatted_rows.append([str(value) if value is not None else 'NULL' for value in row])
# Create table
table = tabulate(formatted_rows, headers=columns, tablefmt="grid")
result = f"Query Results ({len(results)} rows):\n\n{table}"
# Add warning if results were limited
if len(results) >= max_results:
result += f"\n\n⚠️ Results limited to {max_results} rows. Use LIMIT clause for more control."
return result
import os
from typing import Optional
from datetime import datetime
from google.adk.agents import Agent
from google.adk.agents.callback_context import CallbackContext
from google.adk.models.lite_llm import LiteLlm
from google.adk.tools.agent_tool import AgentTool
from google.genai import types
from .config import get_sqlite_config, get_model_name, get_DeepSeek_api_key
from .prompts import get_sqlite_agent_instructions
from .utils import create_sample_database, get_database_connection
from .sub_agents import (
list_tables,
table_info,
query_execution,
sample_data
)
from .monitored_llm import create_monitored_llm
# 创建多Agent协调的SQLite Agent
sqlite_agent = Agent(
name="sqlite_agent",
model=create_monitored_llm(model=get_model_name()),
description="SQLite数据库助手,通过协调多个专门的子agent来完成数据库操作",
instruction=get_sqlite_agent_instructions,
sub_agents=[
list_tables,
table_info,
query_execution,
sample_data,
],
before_agent_callback=setup_database,
generate_content_config=types.GenerateContentConfig(
safety_settings=[
types.SafetySetting(
category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
threshold=types.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
),
],
temperature=0.1,
top_p=0.8,
max_output_tokens=2048,
),
)
def get_sqlite_agent_instructions(ctx) -> str:
"""Get the main instructions for the SQLite agent."""
return f"""
你是专业的SQLite数据库助手,通过协调专门的子agent来高效完成数据库操作。
## 核心职责
- 分析用户意图,精准选择最合适的agent
- 确保查询结果准确、格式清晰
- 主动识别潜在需求,提供相关建议
## 可用agent选择指南
### list_tables - 表发现
**用途**:当用户询问"有哪些表"、"显示所有表"、"查看数据库结构"时使用
**关键词**:表、列表、所有、结构、数据库
### table_info - 表结构分析
**用途**:当用户询问"表结构"、"字段信息"、"列信息"、"schema"时使用
**关键词**:结构、字段、列、schema、信息、详情
### query_execution - 数据查询
**用途**:当用户需要执行具体查询、分析数据、筛选记录时使用
**关键词**:查询、查找、统计、分析、最贵、最新、数量、总和
### sample_data - 样本数据
**用途**:当用户想"看看数据"、"预览内容"、"示例数据"时使用
**关键词**:看看、样本、示例、预览、数据
## 智能选择策略
1. **探索阶段**:用户首次接触数据库 → list_tables
2. **结构了解**:用户想了解特定表 → table_info
3. **数据分析**:用户有具体查询需求 → query_execution
4. **快速预览**:用户想快速了解数据 → sample_data
## 响应要求
- 直接调用agent,不解释选择过程
- 确保结果对用户可见
- 中文问题用中文回答
- 表格数据用清晰格式展示
当前数据库表:{', '.join(ctx.state.get('available_tables', []))}
"""
"""Query execution agent - specialized agent for executing SQL queries."""
from google.adk.agents import Agent
from google.genai import types
from ...config import get_model_name
from ...tools import execute_query_tool, get_table_info_tool
from ...monitored_llm import create_monitored_llm
from .prompt import QUERY_EXECUTION_AGENT_INSTR
query_execution = Agent(
name="query_execution",
model=create_monitored_llm(model=get_model_name()),
description="专门负责执行SQLite数据库查询的agent,遵循单一职责原则",
instruction=QUERY_EXECUTION_AGENT_INSTR,
tools=[execute_query_tool], # 仅执行查询,避免多余表结构查询
disallow_transfer_to_parent=True,
disallow_transfer_to_peers=True,
generate_content_config=types.GenerateContentConfig(
temperature=0.1,
top_p=0.8,
max_output_tokens=2048,
),
)
"""Query execution agent - specialized agent for executing SQL queries."""
from google.adk.agents import Agent
from google.genai import types
from ...config import get_model_name
from ...tools import execute_query_tool, get_table_info_tool
from ...monitored_llm import create_monitored_llm
from .prompt import QUERY_EXECUTION_AGENT_INSTR
query_execution = Agent(
name="query_execution",
model=create_monitored_llm(model=get_model_name()),
description="专门负责执行SQLite数据库查询的agent,遵循单一职责原则",
instruction=QUERY_EXECUTION_AGENT_INSTR,
tools=[execute_query_tool], # 仅执行查询,避免多余表结构查询
disallow_transfer_to_parent=True,
disallow_transfer_to_peers=True,
generate_content_config=types.GenerateContentConfig(
temperature=0.1,
top_p=0.8,
max_output_tokens=2048,
),
)
"""Prompts for the query execution agent."""
QUERY_EXECUTION_AGENT_INSTR = """
执行SQL查询专家。直接执行查询并返回结果给用户。
**使用execute_query工具:**
- 将用户问题直接转换为SQL查询
- 例如:"最贵的订单" → "SELECT * FROM orders ORDER BY total_amount DESC LIMIT 1"
- 例如:"最新用户" → "SELECT * FROM users ORDER BY created_at DESC LIMIT 1"
**输出格式:**
```sql
[实际SQL]
```
[查询结果表格]
统计:X条记录
**规则:**
- 仅执行SELECT查询
- 直接回答,不解释过程
"""
def create_monitored_llm(model: str, **kwargs) -> LiteLlm:
"""创建带监控功能的LLM实例"""
return LiteLlm(model=model, **kwargs)
先确定设计原则:单 Agent 多 Tool vs 主 + 多 Sub-Agent。
子 Agent 职责切到最小:每个 Sub-Agent 只干一件事。
主 Agent 只负责调度:主 Agent 只做意图识别与任务分发。
精简系统提示:优化 Sub-Agent 提示词,不要背不必要的上下文。
逐步优化 Token:深入分析 Token 的损耗点,逐步进行优化。
把架构从“单 Agent 多 Tool”切到“主 Agent + 多个单一职责的 Sub-Agent”,再配合提示词精简,我的 Token 消耗最终降到了原来的约 40%。这次改造对工程复杂度的增加可控,重构后的代码结构更加合理,而且对成本和吞吐的改善非常可观。
下一篇,我应该会写 ADK 自带的评估机制,让你能够利用框架自身的能力编写自动化评估测试Case。
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业
2025-08-02
自建个人知识库, RAGflow、Dify、fastGPT、AnythingLLM、Cherry Studio怎么选
2025-08-02
大模型时代的AI Infra内容浅析与趋势思考
2025-08-02
阿里Qwen-MT翻译模型发布: 挑战GPT-4.1,专业术语、领域风格精准拿捏!
2025-08-02
AI开发者必看:深度解析MCP,打造高效LLM应用的秘密武器!
2025-08-02
【深度】企业 AI 落地实践(四):如何构建端到端的 AI 应用观测体系
2025-08-02
Ollama vs vLLM:哪个框架更适合推理?(第二部分)
2025-08-02
刚刚,Anthropic切断OpenAI对Claude的访问权限
2025-08-02
金融大模型的“垂直突围”:蚂蚁数科打造更懂金融的行业大脑
2025-05-29
2025-05-23
2025-06-01
2025-05-07
2025-05-07
2025-05-07
2025-06-07
2025-06-21
2025-06-12
2025-05-20
2025-08-02
2025-08-02
2025-07-31
2025-07-31
2025-07-31
2025-07-30
2025-07-30
2025-07-30