Skill: dream458268696/openclaw-fallback-skill (version 1.0.0)

{ "name": "openclaw-fallback-skill", "version": "1.0.0", "description": "当本地模型无法回答时，自动调用配置的云端大模型 API", "author": "Your Name", "permissions": ["http", "config"], "events": ["beforeResponse", "modelFailure"], "configSchema": { "type": "object", "properties": { "apiUrl": { "type": "string", "format": "uri", "description": "云端大模型的 API 地址" }, "apiKey": { "type": "string", "description": "API 密钥" }, "modelName": { "type": "string", "description": "模型名称（如 gpt-4, claude-3 等）", "default": "gpt-3.5-turbo" }, "fallbackThreshold": { "type": "number", "description": "置信度阈值，低于此值触发 fallback", "default": 0.6, "minimum": 0, "maximum": 1 }, "maxRetries": { "type": "number", "default": 2 }, "timeout": { "type": "number", "description": "API 超时时间（秒）", "default": 30 }, "enableStreaming": { "type": "boolean", "default": false } }, "required": ["apiUrl", "apiKey"] } }

path: config.example.json
content: {
  "apiUrl": "https://api.openai.com/v1/chat/completions",
  "apiKey": "sk-your-api-key-here",
  "modelName": "gpt-4",
  "fallbackThreshold": 0.6,
  "maxRetries": 2,
  "timeout": 30,
  "enableStreaming": false,
  "customHeaders": {
    "X-Custom-Header": "value"
  }
}

path: config.json
content: apiUrl: http://localhost:13000/v1/chat/completions
apiKey: [REDACTED_SECRET]
modelName: deepseek-v3.2
fallbackThreshold: 0.5
maxRetries: 3
timeout: 60
enableStreaming: yes

path: index.js
content: const axios = require('axios');

class OpenClawFallbackSkill {
  constructor(config) {
    this.config = config;
    this.name = 'openclaw-fallback-skill';
    this.conversationHistory = new Map(); // 存储会话上下文
  }

  /**
   * 初始化技能
   */
  async init() {
    console.log('[FallbackSkill] 初始化完成');
    console.log(`[FallbackSkill] 配置的 API: ${this.config.apiUrl}`);
    
    // 验证 API 连接
    await this.testConnection();
  }

  /**
   * 测试 API 连接
   */
  async testConnection() {
    try {
      const testResult = await this.callCloudModel({
        messages: [{ role: 'user', content: 'ping' }],
        max_tokens: 5
      });
      console.log('[FallbackSkill] API 连接测试成功');
    } catch (error) {
      console.error('[FallbackSkill] API 连接测试失败:', error.message);
      // 不抛出错误，允许继续运行
    }
  }

  /**
   * 事件处理器：在模型响应前检查
   */
  async onBeforeResponse(context) {
    const { userMessage, localResponse, confidence } = context;

    // 检查是否需要 fallback
    const needsFallback = this.shouldFallback(localResponse, confidence);

    if (needsFallback) {
      console.log('[FallbackSkill] 检测到本地模型回答质量不足，触发 fallback');
      
      const cloudResponse = await this.getCloudResponse(userMessage, context);
      
      if (cloudResponse) {
        // 替换响应为云端模型的结果
        context.response = cloudResponse;
        context.usedFallback = true;
        context.fallbackModel = this.config.modelName;
      }
    }

    return context;
  }

  /**
   * 判断是否需要 fallback
   */
  shouldFallback(response, confidence) {
    // 场景1: 置信度过低
    if (confidence && confidence < this.config.fallbackThreshold) {
      return true;
    }

    // 场景2: 响应内容为空或太短
    if (!response || response.length < 10) {
      return true;
    }

    // 场景3: 包含无法回答的关键词
    const unableKeywords = [
      '无法回答', '不能回答', '不知道', 'not sure', 
      "i don't know", "can't answer", "unable to"
    ];
    
    const lowerResponse = response.toLowerCase();
    if (unableKeywords.some(keyword => lowerResponse.includes(keyword))) {
      return true;
    }

    // 场景4: 响应内容过于通用（如"这是一个好问题"但没有实质内容）
    const genericPatterns = [
      /^这是一个好问题/,
      /^that's a good question/,
      /^i understand/
    ];
    
    if (genericPatterns.some(pattern => pattern.test(response))) {
      return true;
    }

    return true;
  }

  /**
   * 调用云端大模型
   */
  async getCloudResponse(userMessage, context) {
    const sessionId = context.sessionId || 'default';
    
    // 获取会话历史
    let history = this.conversationHistory.get(sessionId) || [];
    
    // 构建消息列表
    const messages = [
      {
        role: 'system',
        content: this.buildSystemPrompt(context)
      },
      ...history.slice(-10), // 保留最近10条对话
      {
        role: 'user',
        content: userMessage
      }
    ];

    // 尝试调用 API，支持重试
    for (let attempt = 1; attempt <= this.config.maxRetries; attempt++) {
      try {
        const response = await this.callCloudModel({
          messages,
          temperature: 0.7,
          max_tokens: 2000,
          stream: this.config.enableStreaming
        });

        // 更新历史
        history.push(
          { role: 'user', content: userMessage },
          { role: 'assistant', content: response }
        );
        this.conversationHistory.set(sessionId, history);

        return response;
      } catch (error) {
        console.error(`[FallbackSkill] API 调用失败 (尝试 ${attempt}/${this.config.maxRetries}):`, error.message);
        
        if (attempt === this.config.maxRetries) {
          // 所有重试都失败，返回友好的错误提示
          return this.getFallbackErrorMessage(error);
        }
        
        // 等待后重试
        await this.sleep(1000 * attempt);
      }
    }

    return null;
  }

  /**
   * 调用云端模型 API
   */
  async callCloudModel(params) {
    const { apiUrl, apiKey, modelName, timeout } = this.config;
    
    // 根据不同的 API 格式构建请求
    const requestBody = this.buildApiRequestBody(params, modelName);
    
    const response = await axios({
      method: 'post',
      url: apiUrl,
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${apiKey}`,
        ...this.getCustomHeaders()
      },
      data: requestBody,
      timeout: timeout * 1000
    });

    // 解析不同 API 格式的响应
    return this.parseApiResponse(response.data);
  }

  /**
   * 构建 API 请求体（支持多种格式）
   */
  buildApiRequestBody(params, modelName) {
    // OpenAI 兼容格式
    if (this.config.apiUrl.includes('openai') || this.config.apiUrl.includes('v1/chat')) {
      return {
        model: modelName,
        messages: params.messages,
        temperature: params.temperature || 0.7,
        max_tokens: params.max_tokens || 2000,
        stream: params.stream || false
      };
    }
    
    // Claude/Anthropic 格式
    if (this.config.apiUrl.includes('anthropic')) {
      return {
        model: modelName,
        messages: params.messages,
        max_tokens: params.max_tokens || 2000,
        temperature: params.temperature || 0.7,
        stream: params.stream || false
      };
    }
    
    // 通用格式（可自定义）
    return {
      model: modelName,
      messages: params.messages,
      ...params
    };
  }

  /**
   * 解析 API 响应
   */
  parseApiResponse(data) {
    // OpenAI 格式
    if (data.choices && data.choices[0] && data.choices[0].message) {
      return data.choices[0].message.content;
    }
    
    // Claude 格式
    if (data.content && data.content[0] && data.content[0].text) {
      return data.content[0].text;
    }
    
    // 其他格式，尝试直接提取
    if (data.response) return data.response;
    if (data.text) return data.text;
    if (data.result) return data.result;
    
    // 如果都不匹配，返回原始数据
    return JSON.stringify(data);
  }

  /**
   * 构建系统提示词
   */
  buildSystemPrompt(context) {
    const basePrompt = `你是一个智能助手，正在帮助用户解决问题。
请提供准确、详细、有帮助的回答。
如果问题涉及实时信息或你不确定的内容，请诚实说明。`;

    // 如果有上下文信息，添加到提示词中
    if (context.metadata && context.metadata.userInfo) {
      return `${basePrompt}\n\n用户信息: ${JSON.stringify(context.metadata.userInfo)}`;
    }

    return basePrompt;
  }

  /**
   * 获取自定义请求头
   */
  getCustomHeaders() {
    const headers = {};
    
    // 如果配置中有额外 headers
    if (this.config.customHeaders) {
      Object.assign(headers, this.config.customHeaders);
    }
    
    return headers;
  }

  /**
   * 获取 fallback 失败时的错误消息
   */
  getFallbackErrorMessage(error) {
    const errorMessages = {
      'ECONNREFUSED': '无法连接到云端模型服务，请检查网络和 API 地址',
      'ETIMEDOUT': '云端模型响应超时，请稍后再试',
      '401': 'API 密钥无效，请检查配置',
      '403': 'API 密钥权限不足',
      '429': 'API 请求频率超限，请稍后再试'
    };
    
    let errorKey = error.code || (error.response?.status);
    let message = errorMessages[errorKey] || `云端模型调用失败: ${error.message}`;
    
    return `⚠️ ${message}\n\n建议：检查您的 API 配置或稍后重试。`;
  }

  /**
   * 清理会话历史（可选）
   */
  clearHistory(sessionId) {
    if (sessionId) {
      this.conversationHistory.delete(sessionId);
    } else {
      this.conversationHistory.clear();
    }
  }

  /**
   * 休眠函数
   */
  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  /**
   * 技能卸载时的清理
   */
  async destroy() {
    console.log('[FallbackSkill] 清理会话历史');
    this.conversationHistory.clear();
  }
}

// 导出技能
module.exports = OpenClawFallbackSkill;

path: readme.md
content: 安装步骤：
创建技能目录
Copy
mkdir -p ~/.openclaw/skills/openclaw-fallback-skill
cd ~/.openclaw/skills/openclaw-fallback-skill
创建配置文件
Copy
cp config.example.json config.json
# 编辑 config.json，填入你的 API URL 和 Key
安装依赖
Copy
npm install axios
重启 OpenClaw
Copy
systemctl --user restart openclaw  # 如果使用 systemd
# 或直接重启你的 OpenClaw 进程
配置说明：
配置项	说明	示例
apiUrl	云端模型 API 地址	https://api.openai.com/v1/chat/completions
apiKey	API 密钥	sk-xxx
modelName	模型名称	gpt-4, claude-3-opus-20240229
fallbackThreshold	触发阈值（0-1）	0.6
maxRetries	最大重试次数	2
timeout	超时时间（秒）	30
支持的 API 格式：
OpenAI 兼容（包括 DeepSeek、智谱等）
Anthropic Claude
自定义格式（通过修改解析逻辑）
触发 Fallback 的场景：
本地模型置信度低于阈值
响应内容为空或过短
响应包含"不知道"、"无法回答"等关键词
响应过于通用无实质内容
高级配置示例：
Copy
{
  "apiUrl": "https://api.deepseek.com/v1/chat/completions",
  "apiKey": "sk-deepseek-xxx",
  "modelName": "deepseek-chat",
  "fallbackThreshold": 0.5,
  "maxRetries": 3,
  "timeout": 60,
  "enableStreaming": true
}
这个技能会自动监控本地模型的回答质量，当检测到无法合理回答时，会无缝切换到你的云端大模型，确保用户始终获得高质量的响应。

path: skill.json
content: {
  "name": "openclaw-fallback-skill",
  "version": "1.0.0",
  "description": "当本地模型无法回答时，自动调用配置的云端大模型 API",
  "author": "Your Name",
  "permissions": ["http", "config"],
  "events": ["beforeResponse", "modelFailure"],
  "configSchema": {
    "type": "object",
    "properties": {
      "apiUrl": {
        "type": "string",
        "format": "uri",
        "description": "云端大模型的 API 地址"
      },
      "apiKey": {
        "type": "string",
        "description": "API 密钥"
      },
      "modelName": {
        "type": "string",
        "description": "模型名称（如 gpt-4, claude-3 等）",
        "default": "gpt-3.5-turbo"
      },
      "fallbackThreshold": {
        "type": "number",
        "description": "置信度阈值，低于此值触发 fallback",
        "default": 0.6,
        "minimum": 0,
        "maximum": 1
      },
      "maxRetries": {
        "type": "number",
        "default": 2
      },
      "timeout": {
        "type": "number",
        "description": "API 超时时间（秒）",
        "default": 30
      },
      "enableStreaming": {
        "type": "boolean",
        "default": false
      }
    },
    "required": ["apiUrl", "apiKey"]
  }
}

Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)