wpmind/includes/SDK/SDKAdapter.php
LinuxJoy adb6c49301 style: phpcbf 自动格式化 — 全量 WPCS 3.x 规范对齐
135 文件 19,211 处自动修复:空格→Tab 缩进、括号间距、
函数声明空格、前置自增、尾逗号等纯格式化改动。
零逻辑变更,php -l + token 级对比验证通过。

新增 phpcs.xml.dist / phpstan.neon.dist 项目配置。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 00:48:46 +08:00

216 lines
5.8 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* WP AI Client SDK 适配器
*
* 封装 SDK 调用,提供与 PublicAPI 兼容的接口。
* SDK 使用异常而非 WP_Error响应是 GenerativeAiResult 对象而非数组。
* 本适配器负责两者之间的转换。
*
* @package WPMind\SDK
* @since 3.6.0
*/
declare(strict_types=1);
namespace WPMind\SDK;
use WP_Error;
/**
* SDK 适配器
*
* 将 WP AI Client SDK 的调用方式适配为 PublicAPI 兼容的数组格式。
*
* @since 3.6.0
*/
class SDKAdapter {
/**
* SDK 内置 Provider 映射
*
* @var array<string, string>
*/
private const BUILTIN_PROVIDERS = [
'openai' => 'WordPress\\AiClient\\Providers\\ProviderImplementations\\OpenAi\\OpenAiProvider',
'anthropic' => 'WordPress\\AiClient\\Providers\\ProviderImplementations\\Anthropic\\AnthropicProvider',
'google' => 'WordPress\\AiClient\\Providers\\ProviderImplementations\\Google\\GoogleProvider',
];
/**
* AI 对话
*
* @param array $args 请求参数messages, max_tokens, temperature, json_mode, tools, tool_choice
* @param string $provider 服务商标识
* @param string $model 模型标识
* @return array|WP_Error
*/
public function chat( array $args, string $provider, string $model ): array|WP_Error {
// 检查 SDK 可用性
if ( ! class_exists( 'WordPress\\AiClient\\AiClient' ) ) {
return new WP_Error( 'wpmind_sdk_unavailable', __( 'WP AI Client SDK 不可用', 'wpmind' ) );
}
// 解析 Provider 类名
$provider_class = $this->resolve_provider_class( $provider );
try {
$registry = \WordPress\AiClient\AiClient::defaultRegistry();
// 获取模型实例
$model_instance = null;
if ( $provider_class && $model !== 'auto' && $model !== 'default' ) {
try {
$model_instance = $registry->getProviderModel( $provider_class, $model );
} catch ( \Exception $e ) {
// 模型不存在,尝试不指定模型
}
}
// 构建 PromptBuilder
$builder = \WordPress\AiClient\AiClient::prompt( $args['messages'] );
if ( $model_instance ) {
$builder->usingModel( $model_instance );
} elseif ( $provider_class ) {
$builder->usingProvider( $provider_class );
}
$builder->usingTemperature( $args['temperature'] ?? 0.7 );
$builder->usingMaxTokens( $args['max_tokens'] ?? 2000 );
// 提取 System instruction
$system_msg = null;
foreach ( $args['messages'] as $msg ) {
if ( ( $msg['role'] ?? '' ) === 'system' ) {
$system_msg = $msg['content'] ?? '';
break;
}
}
if ( $system_msg ) {
$builder->usingSystemInstruction( $system_msg );
}
// JSON mode
if ( ! empty( $args['json_mode'] ) ) {
$builder->asJsonResponse();
}
// 执行请求
$result = $builder->generateTextResult();
// 提取 finish_reason
$finish_reason = '';
$candidates = $result->getCandidates();
if ( ! empty( $candidates ) ) {
$fr = $candidates[0]->getFinishReason();
if ( $fr !== null ) {
$finish_reason = is_object( $fr ) && property_exists( $fr, 'value' ) ? $fr->value : (string) $fr;
}
}
return [
'content' => $result->toText(),
'provider' => $provider,
'model' => $model,
'usage' => $this->extract_token_usage( $result ),
'finish_reason' => $finish_reason,
];
} catch ( \InvalidArgumentException $e ) {
return new WP_Error( 'wpmind_sdk_invalid_args', $e->getMessage() );
} catch ( \RuntimeException $e ) {
return $this->convert_exception_to_wp_error( $e );
} catch ( \Exception $e ) {
return $this->convert_exception_to_wp_error( $e );
}
}
/**
* 安全提取 token 用量
*
* @param object $result SDK 结果对象
* @return array
*/
private function extract_token_usage( object $result ): array {
try {
$usage = $result->getTokenUsage();
if ( $usage === null ) {
return [
'prompt_tokens' => 0,
'completion_tokens' => 0,
'total_tokens' => 0,
];
}
return [
'prompt_tokens' => $usage->getPromptTokens() ?? 0,
'completion_tokens' => $usage->getCompletionTokens() ?? 0,
'total_tokens' => $usage->getTotalTokens() ?? 0,
];
} catch ( \Throwable $e ) {
return [
'prompt_tokens' => 0,
'completion_tokens' => 0,
'total_tokens' => 0,
];
}
}
/**
* 解析 Provider 类名
*
* 先检查 WPMind 注册的 Provider再检查 SDK 内置 Provider。
*
* @param string $provider 服务商标识
* @return string|null Provider 完整类名,未找到返回 null
*/
private function resolve_provider_class( string $provider ): ?string {
// 先检查 WPMind 注册的 Provider
if ( class_exists( 'WPMind\\Providers\\ProviderRegistrar' ) ) {
$class = \WPMind\Providers\ProviderRegistrar::getProviderClass( $provider );
if ( $class ) {
return $class;
}
}
// 再检查 SDK 内置 Provider
return self::BUILTIN_PROVIDERS[ $provider ] ?? null;
}
/**
* 将异常转换为 WP_Error
*
* 尝试从异常消息中提取 HTTP 状态码。
*
* @param \Exception $e 异常
* @return WP_Error
*/
private function convert_exception_to_wp_error( \Exception $e ): WP_Error {
$message = $e->getMessage();
$status = 0;
// 尝试从异常消息中提取 HTTP 状态码
if ( preg_match( '/\b(4\d{2}|5\d{2})\b/', $message, $matches ) ) {
$status = (int) $matches[1];
}
$error_data = [];
if ( $status > 0 ) {
$error_data['status'] = $status;
}
// 仅在 debug 模式下记录完整异常信息
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( sprintf( '[WPMind SDK] Exception: %s', $message ) );
}
// 对外返回通用描述,不暴露内部细节
$user_message = $status > 0
? sprintf( __( 'SDK 请求失败 (HTTP %d)', 'wpmind' ), $status )
: __( 'SDK 请求失败', 'wpmind' );
return new WP_Error(
'wpmind_sdk_error',
$user_message,
$error_data
);
}
}