Agent 工具调用最佳实践:从 OpenClaw 技能系统说起
写在前面:2026 年,AI Agent 工具调用成为企业 AI 落地的核心技术。这篇文章基于 OpenClaw 技能系统的实战经验,详解工具调用的完整设计思路和最佳实践。
一、背景:为什么需要工具调用?
1.1 LLM 的局限性
LLM 能做的:
- ✅ 文本生成
- ✅ 知识问答
- ✅ 代码编写
- ✅ 逻辑推理
LLM 不能做的:
- ❌ 访问实时数据(天气、股票)
- ❌ 执行系统命令(部署、重启)
- ❌ 调用外部 API(数据库、消息队列)
- ❌ 操作用户数据(文件、配置)
1.2 工具调用的价值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 用户:部署 OpenClaw 到 K8s
❌ 纯 LLM: "部署 OpenClaw 到 K8s 需要以下步骤: 1. 创建 PVC 2. 创建 Secret ..." (只能提供建议,无法执行)
✅ Agent + 工具: "正在部署 OpenClaw 到 K8s... ✓ 创建 PVC: openclaw-data-pvc ✓ 创建 Secret: openclaw-secrets ✓ 部署 Pod: openclaw-gateway 部署成功!Pod Running (1/1)" (实际执行并返回结果)
|
1.3 OpenClaw 技能系统
技能数量:15+
调用次数:日均 1000+
成功率:98%+
核心技能:
k8s-deploy - K8s 部署
feishu-doc - 飞书文档操作
web-search - 网络搜索
minio-manager - MinIO 文件管理
tts - 语音合成
二、工具定义规范
2.1 工具描述文件(SKILL.md)
示例:skills/k8s-deploy/SKILL.md
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| # k8s-deploy 技能
**用途**: 部署应用到 Kubernetes 集群
**触发条件**: - 用户提到"部署"、"kubectl"、"K8s" - 包含应用名称和部署目标
**参数**: - `app_name`: 应用名称(必填) - `namespace`: 命名空间(选填,默认 default) - `replicas`: 副本数(选填,默认 1) - `image`: 容器镜像(选填)
**执行步骤**: 1. 验证参数 2. 生成 YAML 配置 3. 执行 kubectl apply 4. 验证部署状态 5. 返回结果
**错误处理**: - 参数缺失 → 提示用户补充 - 部署失败 → 返回错误信息 - 超时 → 重试 3 次后报错
**示例**: 用户:"部署 myapp 到 production" → app_name=myapp, namespace=production
|
2.2 工具函数定义
Python 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| from typing import Optional, Dict, Any from pydantic import BaseModel, Field
class K8sDeployInput(BaseModel): """K8s 部署输入参数""" app_name: str = Field(..., description="应用名称") namespace: str = Field(default="default", description="命名空间") replicas: int = Field(default=1, description="副本数") image: Optional[str] = Field(None, description="容器镜像")
def k8s_deploy(params: K8sDeployInput) -> Dict[str, Any]: """部署应用到 K8s""" if not params.app_name: return {"success": False, "error": "应用名称不能为空"} yaml_content = generate_deployment_yaml(params) try: result = exec_kubectl_apply(yaml_content, params.namespace) except Exception as e: return {"success": False, "error": str(e)} status = wait_for_deployment(params.app_name, params.namespace) return { "success": status == "Running", "message": f"部署成功:{params.app_name}", "status": status, "details": { "namespace": params.namespace, "replicas": params.replicas } }
|
2.3 工具注册机制
OpenClaw 技能注册:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
class SkillRegistry: def __init__(self): self.skills = {} def register(self, name: str, func: callable, schema: dict): """注册技能""" self.skills[name] = { "func": func, "schema": schema, "enabled": True } def get(self, name: str) -> dict: """获取技能""" return self.skills.get(name) def list(self) -> list: """列出所有技能""" return list(self.skills.keys())
registry = SkillRegistry()
registry.register( name="k8s-deploy", func=k8s_deploy, schema={ "type": "object", "properties": { "app_name": {"type": "string"}, "namespace": {"type": "string"}, "replicas": {"type": "integer"}, "image": {"type": "string"} }, "required": ["app_name"] } )
|
三、工具调用流程
3.1 完整流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 用户请求 ↓ 意图识别(LLM) ↓ 选择工具(Skill Router) ↓ 提取参数(Parameter Extractor) ↓ 参数验证(Validator) ↓ 执行工具(Executor) ↓ 结果处理(Result Handler) ↓ 返回用户
|
3.2 意图识别
LLM Prompt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 你是一个 AI Agent 助手。请分析用户意图,选择合适的工具。
可用工具: 1. k8s-deploy - 部署应用到 K8s 2. feishu-doc - 操作飞书文档 3. web-search - 网络搜索 4. minio-manager - MinIO 文件管理
用户请求:部署 OpenClaw 到 K8s
请回答: 1. 是否需要调用工具?(是/否) 2. 选择哪个工具? 3. 提取参数(JSON 格式)
示例回答: { "need_tool": true, "tool_name": "k8s-deploy", "parameters": { "app_name": "OpenClaw", "namespace": "default" } }
|
3.3 参数提取
Few-shot 示例:
1 2 3 4 5 6 7 8
| 用户:部署 myapp 到 production,3 个副本 → {"app_name": "myapp", "namespace": "production", "replicas": 3}
用户:查下北京天气 → {"city": "北京", "tool": "get_weather"}
用户:把这篇文章保存到飞书 → {"doc_title": "文章标题", "content": "文章内容", "tool": "feishu-doc"}
|
3.4 参数验证
Pydantic 验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| from pydantic import ValidationError
def validate_params(tool_name: str, params: dict) -> tuple: """验证参数""" schema = get_tool_schema(tool_name) try: if tool_name == "k8s-deploy": validated = K8sDeployInput(**params) elif tool_name == "feishu-doc": validated = FeishuDocInput(**params) else: validated = params return True, validated, None except ValidationError as e: return False, None, str(e)
success, validated, error = validate_params("k8s-deploy", params)
if not success: return f"参数错误:{error}"
|
四、错误处理机制
4.1 错误分类
| 错误类型 |
说明 |
处理策略 |
| 参数错误 |
参数缺失/类型错误 |
提示用户补充 |
| 工具执行失败 |
API 调用失败 |
重试 3 次 |
| 超时 |
执行时间过长 |
异步执行 + 轮询 |
| 权限不足 |
缺少访问权限 |
提示用户授权 |
| 资源不足 |
系统资源限制 |
降级或排队 |
4.2 重试机制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import time from functools import wraps
def retry(max_attempts=3, delay=1, backoff=2): """重试装饰器""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): current_delay = delay for attempt in range(max_attempts): try: return func(*args, **kwargs) except Exception as e: if attempt == max_attempts - 1: raise log(f"第{attempt+1}次失败:{e}") time.sleep(current_delay) current_delay *= backoff return None return wrapper return decorator
@retry(max_attempts=3, delay=2, backoff=2) def k8s_deploy(params): pass
|
4.3 降级策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| def execute_with_fallback(tool_name: str, params: dict): """带降级的工具执行""" try: return execute_primary(tool_name, params) except PrimaryError as e: log(f"主方案失败:{e}") try: return execute_fallback_1(tool_name, params) except Exception as e: log(f"备用方案 1 失败:{e}") return { "success": False, "message": "暂时无法完成该操作,请稍后重试", "suggestion": "您可以尝试手动执行:kubectl apply -f ..." }
|
4.4 错误信息友好化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| def format_error_message(error: Exception, tool_name: str) -> str: """格式化错误信息""" error_messages = { "ConnectionError": "网络连接失败,请检查网络", "TimeoutError": "操作超时,请稍后重试", "PermissionError": "权限不足,请联系管理员授权", "ResourceExhausted": "资源不足,请释放部分资源", } error_type = type(error).__name__ if error_type in error_messages: return error_messages[error_type] return f"{tool_name} 执行失败:{str(error)}"
|
五、OpenClaw 实战案例
5.1 案例 1:K8s 部署
用户请求:部署 OpenClaw 到 K8s
执行过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| 步骤 1:意图识别 - 检测到关键词:"部署"、"K8s" - 选择工具:k8s-deploy
步骤 2:参数提取 - app_name: "OpenClaw" - namespace: "default"(默认值) - replicas: 1(默认值)
步骤 3:参数验证 - ✓ app_name 存在 - ✓ namespace 有效 - ✓ replicas 在合理范围
步骤 4:执行部署 ✓ kubectl apply -f pvc.yaml ✓ kubectl create secret generic openclaw-secrets ✓ kubectl apply -f deployment.yaml ✓ kubectl apply -f service.yaml
步骤 5:验证状态 ✓ kubectl get pods → Running (1/1) ✓ kubectl get svc → ClusterIP 分配 ✓ curl health check → 200 OK
步骤 6:返回结果 { "success": true, "message": "OpenClaw 部署成功", "details": { "pod": "openclaw-gateway-56b6855c5-qjnmd", "status": "Running", "service": "192.169.218.43:80" } }
|
5.2 案例 2:飞书文档操作
用户请求:把这篇文章保存到飞书
执行过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 步骤 1:意图识别 - 检测到关键词:"保存"、"飞书" - 选择工具:feishu-doc
步骤 2:参数提取 - doc_title: "AI Agent 工具调用最佳实践" - content: "文章内容..." - folder_token: (从上下文获取)
步骤 3:执行保存 ✓ feishu_doc(action="create", title=doc_title, content=content) → doc_token: "dscnABC123..."
步骤 4:返回结果 { "success": true, "message": "文档已保存到飞书", "doc_url": "https://feishu.cn/docx/dscnABC123..." }
|
5.3 案例 3:多工具协作
用户请求:搜索 RAG 技术文章,保存到飞书,然后生成语音
执行过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 步骤 1:分解任务 - 任务 1:搜索 RAG 技术文章 → web-search - 任务 2:保存到飞书 → feishu-doc - 任务 3:生成语音 → tts
步骤 2:顺序执行 ✓ web-search(query="RAG 技术") → 搜索结果:10 篇文章
✓ feishu-doc(action="create", content=搜索结果) → doc_token: "dscnABC123..."
✓ tts(text=文章摘要) → audio_url: "https://.../audio.mp3"
步骤 3:返回结果 { "success": true, "message": "已完成所有操作", "results": { "search_count": 10, "doc_url": "https://feishu.cn/docx/dscnABC123...", "audio_url": "https://.../audio.mp3" } }
|
六、性能优化技巧
6.1 工具缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| from functools import lru_cache import hashlib
def cache_tool_result(func): """工具结果缓存装饰器""" cache = {} @wraps(func) def wrapper(*args, **kwargs): key_data = f"{func.__name__}:{args}:{kwargs}" key = hashlib.md5(key_data.encode()).hexdigest() if key in cache: log(f"使用缓存:{key}") return cache[key] result = func(*args, **kwargs) cache[key] = result return result return wrapper
@cache_tool_result def web_search(query: str) -> list: pass
|
6.2 并发执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import asyncio from concurrent.futures import ThreadPoolExecutor
async def execute_parallel(tools: list) -> list: """并发执行多个工具""" loop = asyncio.get_event_loop() executor = ThreadPoolExecutor(max_workers=10) tasks = [ loop.run_in_executor(executor, tool_func, params) for tool_func, params in tools ] results = await asyncio.gather(*tasks, return_exceptions=True) return results
tools = [ (web_search, {"query": "RAG 技术"}), (web_search, {"query": "AI Agent"}), (web_search, {"query": "Function Calling"}), ]
results = await execute_parallel(tools)
|
6.3 超时控制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import signal from contextlib import contextmanager
class TimeoutError(Exception): pass
@contextmanager def timeout(seconds: int): """超时控制""" def handler(signum, frame): raise TimeoutError(f"执行超时({seconds}秒)") signal.signal(signal.SIGALRM, handler) signal.alarm(seconds) try: yield finally: signal.alarm(0)
with timeout(30): result = k8s_deploy(params)
|
七、最佳实践清单
7.1 工具设计
7.2 安全控制
7.3 性能优化
7.4 监控告警
八、常见问题
8.1 工具选择不准确
问题:LLM 选择了错误的工具
解决:
- 优化工具描述(更清晰)
- 提供更多 Few-shot 示例
- 添加工具选择置信度阈值
8.2 参数提取失败
问题:LLM 提取的参数不完整
解决:
- 使用结构化输出(JSON Schema)
- 添加参数验证和提示
- 支持多轮对话补充参数
8.3 工具执行慢
问题:某些工具执行时间长
解决:
- 异步执行 + 轮询结果
- 添加进度反馈
- 优化底层实现
九、总结
9.1 核心要点
- 工具定义 - 清晰的 SKILL.md 描述
- 参数验证 - 使用 Pydantic 等验证库
- 错误处理 - 重试 + 降级 + 友好提示
- 性能优化 - 缓存 + 并发 + 超时控制
9.2 行动建议
开发者:
- 从简单工具开始(1-2 个)
- 完善错误处理
- 添加监控和日志
企业:
十、相关链接
作者:John
创建时间:2026-04-02
文档版本:v1.0