🔌 Tool Integration 工具集成

OpenClaw API集成 认证 实战

世界上有一种痛苦叫"API文档写得像天书"。
我曾经为了一个OAuth2.0流程,和官方文档对视了整整三天三夜。最后发现,是文档写错了。
这就是工具集成的现实——你以为在写代码,其实是在破案

📚 什么是 Tool Integration?

Tool Integration(工具集成)是指将外部API、服务或系统连接到AI Agent的能力体系,让Agent能够调用现实世界的功能。它是AI从"聊天机器人"升级为"智能助手"的关键一步。

集成层次

层次 说明 示例
L1 - 简单API 无认证或简单Key,RESTful接口 天气查询、汇率转换
L2 - 标准OAuth OAuth2.0流程,用户授权 Google Calendar、GitHub
L3 - 企业级 复杂认证、SDK、Webhook Salesforce、SAP
L4 - 私有系统 内部系统、数据库、微服务 内部ERP、数据仓库

🏗️ 工具集成架构

核心组件

┌─────────────────────────────────────────────────────────┐
│                    AI Agent Core                        │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────┐  │
│  │  Intent     │  │  Planning   │  │  Tool Selection │  │
│  │  Parser     │→ │  Engine     │→ │     Router      │  │
│  └─────────────┘  └─────────────┘  └─────────────────┘  │
└─────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────┐
│                  Tool Integration Layer                 │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐   │
│  │  Auth    │ │  Request │ │  Cache   │ │  Retry   │   │
│  │  Manager  │ │ Handler  │ │  Layer   │ │ Handler  │   │
│  └──────────┘ └──────────┘ └──────────┘ └──────────┘   │
└─────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────┐
│              External Services / APIs                   │
│     Slack   Gmail   Salesforce   Database   Custom      │
└─────────────────────────────────────────────────────────┘

🔐 认证管理实战

1. API Key 认证

// 最简单的认证方式
const weatherTool = {
  name: 'get_weather',
  
  async execute(params) {
    const response = await fetch('https://api.weather.com/v1/current', {
      method: 'GET',
      headers: {
        'Authorization': `Bearer ${process.env.WEATHER_API_KEY}`,
        'Content-Type': 'application/json'
      },
      params: {
        city: params.city,
        units: 'metric'
      }
    });
    
    if (!response.ok) {
      throw new Error(`Weather API error: ${response.status}`);
    }
    
    return await response.json();
  }
};

2. OAuth2.0 用户授权

import { OAuth2Client } from 'google-auth-library';

class GoogleCalendarIntegration {
  constructor() {
    this.oauth2Client = new OAuth2Client({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      redirectUri: 'https://miaoquai.com/oauth/callback'
    });
  }
  
  // Step 1: 生成授权URL
  getAuthUrl(userId) {
    const scopes = [
      'https://www.googleapis.com/auth/calendar.readonly',
      'https://www.googleapis.com/auth/calendar.events'
    ];
    
    return this.oauth2Client.generateAuthUrl({
      access_type: 'offline',  // 获取refresh_token
      scope: scopes,
      state: userId,  // 用于回调时识别用户
      prompt: 'consent'  // 强制显示授权页面
    });
  }
  
  // Step 2: 处理回调,交换code获取token
  async handleCallback(code) {
    const { tokens } = await this.oauth2Client.getToken(code);
    
    // 保存token到数据库(加密存储!)
    await this.saveTokens(tokens);
    
    return tokens;
  }
  
  // Step 3: 使用token调用API
  async createEvent(userId, eventData) {
    const tokens = await this.getTokens(userId);
    this.oauth2Client.setCredentials(tokens);
    
    // token过期时自动刷新
    if (tokens.expiry_date && tokens.expiry_date < Date.now()) {
      const { credentials } = await this.oauth2Client.refreshAccessToken();
      await this.updateTokens(userId, credentials);
    }
    
    const calendar = google.calendar({ version: 'v3', auth: this.oauth2Client });
    
    const response = await calendar.events.insert({
      calendarId: 'primary',
      requestBody: eventData
    });
    
    return response.data;
  }
}
⚠️ 安全警告
  • 永远不要将API Key硬编码在代码中
  • 使用环境变量或密钥管理服务(AWS KMS、HashiCorp Vault)
  • 数据库中的token必须加密存储
  • 设置token过期时间,支持自动刷新

⚠️ 错误处理与重试

class ToolExecutor {
  constructor() {
    this.maxRetries = 3;
    this.timeout = 30000;
  }
  
  async executeWithRetry(tool, params) {
    let lastError;
    
    for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
      try {
        return await this.execute(tool, params, attempt);
      } catch (error) {
        lastError = error;
        console.error(`Attempt ${attempt} failed:`, error.message);
        
        // 判断是否可重试
        if (!this.isRetryable(error)) {
          throw error;
        }
        
        // 指数退避
        if (attempt < this.maxRetries) {
          await this.sleep(Math.pow(2, attempt) * 1000);
        }
      }
    }
    
    // 所有重试都失败
    throw new ToolExecutionError(
      `Tool ${tool.name} failed after ${this.maxRetries} attempts`,
      lastError
    );
  }
  
  isRetryable(error) {
    // 网络错误、超时、服务端错误可重试
    const retryableCodes = ['ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND', 502, 503, 504];
    return retryableCodes.includes(error.code) || 
           retryableCodes.includes(error.status);
  }
  
  async execute(tool, params, attempt) {
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), this.timeout);
    
    try {
      const result = await tool.execute(params, { signal: controller.signal });
      clearTimeout(timeout);
      return result;
    } catch (error) {
      clearTimeout(timeout);
      
      // 转换为统一错误格式
      throw new ToolError(tool.name, {
        message: error.message,
        code: error.code,
        status: error.status,
        attempt,
        timestamp: new Date().toISOString()
      });
    }
  }
}

🚀 OpenClaw 工具注册实战

import { Tool, ToolRegistry } from '@openclaw/core';

// 注册自定义工具
const registry = new ToolRegistry();

// 添加天气工具
registry.register(new Tool({
  name: 'weather',
  description: '查询指定城市的天气',
  parameters: {
    city: { type: 'string', required: true, description: '城市名称' }
  },
  execute: async (params, context) => {
    const weatherData = await callExternalAPI({
      url: 'https://api.weather.com/v1/forecast',
      params: { city: params.city }
    });
    
    return {
      city: params.city,
      temperature: weatherData.temp,
      condition: weatherData.condition,
      humidity: weatherData.humidity
    };
  }
}));

// 添加Slack通知工具
registry.register(new Tool({
  name: 'slack_notify',
  description: '发送Slack消息到指定频道',
  parameters: {
    channel: { type: 'string', required: true },
    message: { type: 'string', required: true },
    mentions: { type: 'array', description: '@用户列表' }
  },
  execute: async (params, context) => {
    // 使用OAuth token
    const token = await context.getOAuthToken('slack');
    
    return await fetch('https://slack.com/api/chat.postMessage', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        channel: params.channel,
        text: params.message,
        link_names: true
      })
    });
  }
}));

// Agent中使用工具
const agent = new Agent({
  tools: registry.list()
});

// 用户: "上海明天天气怎么样?"
// Agent自动调用: weather({ city: '上海' })
// 返回: { city: '上海', temperature: 18, condition: '晴', humidity: 65 }

💡 最佳实践

工具设计原则
  • 单一职责 - 每个工具只做一件事
  • 幂等性 - 同一参数调用结果一致
  • 自描述性 - 名称和描述让LLM能理解
  • 错误可处理 - 返回清晰的错误信息
安全建议
  • 使用最小权限原则(OAuth scopes)
  • 实现工具调用频率限制
  • 敏感操作需要二次确认
  • 记录所有工具调用日志