标签: 插件开发

  • WordPress AI Provider for DeepSeek 插件实现分析

    WordPress AI Provider for DeepSeek 插件实现分析

    前言

    WordPress 6.9 引入了 PHP AI Client SDK,为 WordPress 生态提供了统一的 AI 服务接入标准。本文分析我最近实现的 AI Provider for DeepSeek 插件,它是该 SDK 的 DeepSeek 适配实现,同时支持作为 Composer 包和 WordPress 插件两种使用方式。

    插件架构概览

    插件的目录结构遵循 PSR-4 规范:

    wp-ai-provider-for-deepseek/
    ├── plugin.php                          # WordPress 插件入口
    ├── readme.txt                        # WordPress 插件描述
    ├── assets/images/deepseek.svg        # 供应商 Logo
    └── src/
        ├── autoload.php                 # PSR-4 自动加载器
        ├── Provider/
        │   ├── DeepSeekProvider.php            # 主 Provider 类
        │   └── DeepSeekProviderAvailability.php # API Key 可用性检查器
        ├── Models/
        │   └── DeepSeekTextGenerationModel.php # 文本生成模型实现
        └── Metadata/
            └── DeepSeekModelMetadataDirectory.php # 模型元数据目录
    

    核心实现分析

    1. 插件入口与 Hook 注册(plugin.php)

    插件通过三个关键 Hook 完成初始化:

    // 注册 AI Provider(优先级 5,确保在 AI Client 之后加载)
    add_action('init', __NAMESPACE__ . '\\register_provider', 5);
    
    // 输出 Provider 数据到前端(确保 admin 页面可用)
    add_action('admin_enqueue_scripts', __NAMESPACE__ . '\\ensure_provider_data_output', 5);
    
    // 注册 Connector(WordPress 连接器系统)
    add_action('wp_connectors_init', __NAMESPACE__ . '\\register_connector', 10);
    
    // 自动审批 Connector 使用权限(优先级 20,确保 Approvals_Store 可用)
    add_action('init', __NAMESPACE__ . '\\auto_approve_connector', 20);
    

    设计亮点:

    • register_provider() 先检查 AiClient 类是否存在,避免依赖未安装时 fatal error
    • 使用 $registry->hasProvider() 防止重复注册
    • ensure_provider_data_output() 处理边缘情况:当 AI Client 的脚本未加载时,手动输出 window.aiProviderData

    2. Provider 主类(DeepSeekProvider.php)

    核心类继承自 AbstractApiProvider,实现四个工厂方法:

    class DeepSeekProvider extends AbstractApiProvider
    {
        // API 基础 URL
        protected static function baseUrl(): string
        {
            return 'https://api.deepseek.com';
        }
    
        // 创建模型实例(根据能力分发到具体模型类)
        protected static function createModel(...): ModelInterface
        {
            foreach ($capabilities as $capability) {
                if ($capability->isTextGeneration()) {
                    return new DeepSeekTextGenerationModel(...);
                }
            }
            throw new \RuntimeException('Unsupported model capabilities');
        }
    
        // 创建 Provider 元数据(支持版本化特性)
        protected static function createProviderMetadata(): ProviderMetadata
        {
            $args = [
                'deepseek',
                'DeepSeek',
                ProviderTypeEnum::cloud(),
                'https://platform.deepseek.com/api_keys',
                RequestAuthenticationMethod::apiKey()
            ];
    
            // 1.2.0+ 支持描述
            if (version_compare(AiClient::VERSION, '1.2.0', '>=')) {
                $args[] = __('Text generation with DeepSeek models.', 'ai-provider-for-deepseek');
            }
    
            // 1.3.0+ 支持 Logo
            if (version_compare(AiClient::VERSION, '1.3.0', '>=')) {
                $args[] = dirname(__DIR__, 2) . '/assets/images/deepseek.svg';
            }
    
            return new ProviderMetadata(...$args);
        }
    
        // 自定义可用性检查器(用真实 API 请求验证 Key)
        protected static function createProviderAvailability(): ProviderAvailabilityInterface
        {
            return new DeepSeekProviderAvailability(static::class);
        }
    
        // 模型元数据目录(动态从 API 发现可用模型)
        protected static function createModelMetadataDirectory(): ModelMetadataDirectoryInterface
        {
            return new DeepSeekModelMetadataDirectory();
        }
    }
    

    版本兼容处理是这段代码的精华:通过 version_compare 逐步添加新特性支持,确保插件在旧版 PHP AI Client 上也能正常运行。

    3. API Key 验证与缓存(DeepSeekProviderAvailability.php)

    DeepSeek 不支持标准的”列出模型”端点,因此插件采用真实请求验证法:发送一个 max_tokens=1 的最小化请求,根据 HTTP 状态码判断 Key 是否有效。

    public function isConfigured(): bool
    {
        // 1. 检查缓存(5 分钟过期)
        $cached = get_transient(self::CACHE_KEY);
        if ($cached !== false) {
            return (bool) $cached;
        }
    
        // 2. 获取 API Key(优先 WordPress 选项,fallback 到环境变量)
        $apiKey = $this->getApiKey();
        if (empty($apiKey)) {
            $this->cacheResult(false);
            return false;
        }
    
        // 3. 发送最小化请求
        $response = wp_remote_post($url, [
            'headers' => [
                'Content-Type'  => 'application/json',
                'Authorization' => 'Bearer ' . $apiKey,
            ],
            'body' => json_encode([
                'model'    => 'deepseek-v4-flash',
                'messages' => [['role' => 'user', 'content' => 'test']],
                'max_tokens' => 1,
            ]),
            'timeout' => 30,
        ]);
    
        // 4. 根据状态码判断(200/400 = Key 有效,401/403 = Key 无效)
        $responseCode = wp_remote_retrieve_response_code($response);
        $isConfigured = !in_array($responseCode, [401, 403], true);
    
        // 5. 缓存结果
        $this->cacheResult($isConfigured);
        return $isConfigured;
    }
    

    缓存策略有效减少了重复验证请求:使用 WordPress Transient API,5 分钟过期。注意错误时不缓存,允许下次重试。

    4. 模型元数据动态发现(DeepSeekModelMetadataDirectory.php)

    插件通过调用 DeepSeek 的 /models 端点动态获取可用模型列表,而非硬编码:

    protected function parseResponseToModelMetadataList(Response $response): array
    {
        $responseData = $response->getData();
        
        // 定义 DeepSeek 模型支持的能力和选项
        $textGenerationCapabilities = [
            CapabilityEnum::textGeneration(),
            CapabilityEnum::chatHistory(),
        ];
        
        $textGenerationOptions = [
            new SupportedOption(OptionEnum::systemInstruction()),
            new SupportedOption(OptionEnum::candidateCount()),
            new SupportedOption(OptionEnum::maxTokens()),
            new SupportedOption(OptionEnum::temperature()),
            new SupportedOption(OptionEnum::topP()),
            new SupportedOption(OptionEnum::customOptions()),
            new SupportedOption(OptionEnum::inputModalities()),  // 修复:添加必需的能力声明
            new SupportedOption(OptionEnum::outputModalities()),
        ];
    
        // 将 API 响应映射为 ModelMetadata 对象
        $models = array_map(function ($modelData) use ($textGenerationCapabilities, $textGenerationOptions) {
            return new ModelMetadata(
                $modelData['id'],    // model ID(如 deepseek-v4-flash)
                $modelData['id'],    // model name
                $textGenerationCapabilities,
                $textGenerationOptions
            );
        }, $responseData['data']);
    
        // 排序:deepseek-v4-flash 优先,其次是 deepseek-v4-pro,然后字母序
        usort($models, [$this, 'modelSortCallback']);
        
        return $models;
    }
    

    注意inputModalitiesoutputModalities 的添加——这是修复 ModelRequirements::fromPromptData()generate_text() 调用的关键,缺少这两个选项会导致运行时错误。

    5. 文本生成模型实现(DeepSeekTextGenerationModel.php)

    DeepSeek API 兼容 OpenAI 的 Chat Completions 格式,因此直接继承 AbstractOpenAiCompatibleTextGenerationModel

    class DeepSeekTextGenerationModel extends AbstractOpenAiCompatibleTextGenerationModel
    {
        protected function createRequest(
            HttpMethodEnum $method,
            string $path,
            array $headers = [],
            $data = null
        ): Request {
            // 覆盖父类:使用 DeepSeek Provider 的 URL 而非 OpenAI 的
            return new Request(
                $method,
                DeepSeekProvider::url($path),  // => https://api.deepseek.com/chat/completions
                $headers,
                $data,
                $this->getRequestOptions()
            );
        }
    }
    

    这个实现非常简洁,因为大部分工作(请求格式化、响应解析、流式输出等)都由父类 AbstractOpenAiCompatibleTextGenerationModel 完成。只需要覆盖 createRequest() 将请求路由到 DeepSeek 的 API 地址即可。

    Connector 审批系统

    WordPress 的 AI Client 引入了 Connector 审批机制:每个插件使用 Connector(如 DeepSeek)前必须获得审批。本插件在 init hook(优先级 20)自动审批自己:

    function auto_approve_connector(): void
    {
        if (!class_exists('\WordPress\AI\Connector_Approval\Approvals_Store')) {
            return;  // 旧版 AI Client 不支持审批系统
        }
    
        $store = new \WordPress\AI\Connector_Approval\Approvals_Store();
        $plugin_basename = plugin_basename(__FILE__);
        $connector_id = 'deepseek';
    
        if ($store->is_approved($plugin_basename, $connector_id)) {
            return;  // 已审批,跳过
        }
    
        $store->set_approval($plugin_basename, $connector_id, true);
    }
    

    这避免了用户手动在设置页面审批插件,提升了用户体验。

    使用方式

    作为 WordPress 插件

    // 1. 安装并激活插件(依赖 PHP AI Client 插件)
    // 2. 配置 API Key
    putenv('DEEPSEEK_API_KEY=your-api-key');
    
    // 3. 使用
    $result = AiClient::prompt('解释量子计算')
        ->usingProvider('deepseek')
        ->generateTextResult();
    
    echo $result->toText();
    

    作为 Composer 包

    composer require wordpress/ai-provider-for-deepseek
    
    <?php
    use WordPress\AiClient\AiClient;
    use WordPress\DeepSeekAiProvider\Provider\DeepSeekProvider;
    
    // 注册 Provider
    $registry = AiClient::defaultRegistry();
    $registry->registerProvider(DeepSeekProvider::class);
    
    // 设置 API Key
    putenv('DEEPSEEK_API_KEY=your-api-key');
    
    // 生成文本
    $result = AiClient::prompt('Explain quantum computing')
        ->usingProvider('deepseek')
        ->generateTextResult();
    
    echo $result->toText();
    

    技术亮点总结

    特性 实现方式 价值
    版本兼容 version_compare 渐进式添加特性 支持旧版 AI Client,无 breaking change
    API Key 验证 最小化真实请求 + Transient 缓存 准确验证 + 减少重复请求
    动态模型发现 调用 /models API + 元数据映射 自动支持新模型,无需更新代码
    Connector 审批 init hook 自动审批 零配置用户体验
    OpenAI 兼容 继承 AbstractOpenAiCompatible* 复用大量现有逻辑,代码量极小
    双模式运行 Composer 包 + WordPress 插件 最大灵活性,可在任何 PHP 项目中使用

    结论

    这个插件的实现展示了如何为一个新的 AI 服务商编写符合 WordPress PHP AI Client 标准的 Provider。核心要点:

    1. 继承正确的抽象类:文本生成继承 AbstractOpenAiCompatibleTextGenerationModel
    2. 实现四个工厂方法baseUrl()createModel()createProviderMetadata()createModelMetadataDirectory()
    3. 自定义可用性检查:当标准方法(列出模型)不可用时,用真实请求验证
    4. 版本兼容处理:使用 version_compare 渐进式添加新特性支持
    5. WordPress 集成:正确注册 Hook、Connector、审批

    完整代码已开源在 GitHub,欢迎试用和贡献。