核心功能: - ExactCache: 请求级精确缓存,批量写入优化 (shutdown hook) - MCP Gateway: WordPress Abilities API 集成 (5 个能力) - AbstractService: cache_ttl 三级语义 (负数禁用/零自动/正数指定) API Gateway 模块设计 (Codex 三轮评审通过): - 完整设计方案 + 5 个 P0 解决方案 (API Key/Pipeline/SSE/限流/错误映射) - 11 阶段实施计划 (Phase 0-10),8 阶段中间件 Pipeline - 3 张 DB 表设计 (api_keys/api_key_usage/api_audit_log) 其他: - Provider 测试文档 (Qwen/Doubao 兼容性矩阵) - GEO 设置页 badge 文本修正 - 版本号升级到 3.10.1 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
315 lines
8.8 KiB
PHP
315 lines
8.8 KiB
PHP
<?php
|
||
/**
|
||
* Text Processing Service
|
||
*
|
||
* 处理翻译、摘要、内容审核
|
||
*
|
||
* @package WPMind
|
||
* @subpackage API\Services
|
||
* @since 3.7.0
|
||
*/
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace WPMind\API\Services;
|
||
|
||
use WP_Error;
|
||
|
||
/**
|
||
* Text Processing Service
|
||
*
|
||
* @since 3.7.0
|
||
*/
|
||
class TextProcessingService extends AbstractService {
|
||
|
||
private ChatService $chat_service;
|
||
private StructuredOutputService $structured_service;
|
||
|
||
public function __construct(ChatService $chat_service, StructuredOutputService $structured_service) {
|
||
$this->chat_service = $chat_service;
|
||
$this->structured_service = $structured_service;
|
||
}
|
||
|
||
/**
|
||
* 翻译文本
|
||
*
|
||
* @param string $text 要翻译的文本
|
||
* @param string $from 源语言
|
||
* @param string $to 目标语言
|
||
* @param array $options 选项
|
||
* @return string|WP_Error
|
||
*/
|
||
public function translate(string $text, string $from, string $to, array $options) {
|
||
$defaults = [
|
||
'context' => 'translation',
|
||
'format' => 'text',
|
||
'hint' => '',
|
||
'cache_ttl' => 86400,
|
||
];
|
||
$options = wp_parse_args($options, $defaults);
|
||
|
||
$context = $options['context'];
|
||
|
||
$args = compact('text', 'from', 'to', 'options');
|
||
$args = apply_filters('wpmind_translate_args', $args, $context);
|
||
|
||
$default_provider = get_option('wpmind_default_provider', 'openai');
|
||
$default_model = $this->get_current_model($default_provider);
|
||
|
||
$cache_key = $this->generate_cache_key('translate', $args, $default_provider, $default_model);
|
||
$cache_lookup = $this->get_cached_value($cache_key, (int) $options['cache_ttl']);
|
||
if ($cache_lookup['hit']) {
|
||
return $cache_lookup['value'];
|
||
}
|
||
|
||
$prompt = $this->build_translate_prompt($text, $from, $to, $options);
|
||
|
||
do_action('wpmind_before_request', 'translate', $args, $context);
|
||
|
||
$result = $this->chat_service->chat($prompt, [
|
||
'context' => $context,
|
||
'max_tokens' => max(500, strlen($text) * 2),
|
||
'temperature' => 0.3,
|
||
'cache_ttl' => 0,
|
||
]);
|
||
|
||
if (is_wp_error($result)) {
|
||
do_action('wpmind_error', $result, 'translate', $args);
|
||
return $result;
|
||
}
|
||
|
||
$translated = trim($result['content']);
|
||
|
||
if ($options['format'] === 'slug') {
|
||
$translated = sanitize_title_with_dashes($translated, '', 'save');
|
||
}
|
||
|
||
$translated = apply_filters('wpmind_translate_response', $translated, $text, $from, $to);
|
||
|
||
do_action('wpmind_after_request', 'translate', $translated, $args, $result['usage'] ?? []);
|
||
|
||
$this->set_cached_value($cache_key, $translated, (int) $options['cache_ttl'], [
|
||
'type' => 'translate',
|
||
'context' => $context,
|
||
'provider' => $default_provider,
|
||
'model' => $default_model,
|
||
]);
|
||
|
||
return $translated;
|
||
}
|
||
|
||
/**
|
||
* 文本摘要
|
||
*
|
||
* @since 2.7.0
|
||
* @param string $text 要摘要的文本
|
||
* @param array $options 选项
|
||
* @return string|WP_Error
|
||
*/
|
||
public function summarize(string $text, array $options = []) {
|
||
$defaults = [
|
||
'context' => 'summarize',
|
||
'max_length' => 200,
|
||
'style' => 'paragraph',
|
||
'language' => 'auto',
|
||
'cache_ttl' => 3600,
|
||
];
|
||
$options = wp_parse_args($options, $defaults);
|
||
|
||
$context = $options['context'];
|
||
|
||
$default_provider = get_option('wpmind_default_provider', 'openai');
|
||
$default_model = $this->get_current_model($default_provider);
|
||
|
||
$cache_key = $this->generate_cache_key('summarize', compact('text', 'options'), $default_provider, $default_model);
|
||
$cache_lookup = $this->get_cached_value($cache_key, (int) $options['cache_ttl']);
|
||
if ($cache_lookup['hit']) {
|
||
return $cache_lookup['value'];
|
||
}
|
||
|
||
$style_prompts = [
|
||
'paragraph' => '用一段简洁的文字总结以下内容',
|
||
'bullet' => '用要点列表总结以下内容的关键信息',
|
||
'title' => '为以下内容生成一个简洁的标题',
|
||
];
|
||
$style_prompt = $style_prompts[$options['style']] ?? $style_prompts['paragraph'];
|
||
|
||
$length_hint = $options['style'] === 'title'
|
||
? '(不超过 20 个字)'
|
||
: "(不超过 {$options['max_length']} 个字)";
|
||
|
||
$lang_hint = $options['language'] !== 'auto'
|
||
? ",用{$options['language']}输出"
|
||
: '';
|
||
|
||
$prompt = "{$style_prompt}{$length_hint}{$lang_hint}:\n\n{$text}";
|
||
|
||
do_action('wpmind_before_request', 'summarize', compact('text', 'options'), $context);
|
||
|
||
$result = $this->chat_service->chat($prompt, [
|
||
'context' => $context,
|
||
'max_tokens' => max(100, $options['max_length'] * 2),
|
||
'temperature' => 0.3,
|
||
'cache_ttl' => 0,
|
||
]);
|
||
|
||
if (is_wp_error($result)) {
|
||
do_action('wpmind_error', $result, 'summarize', compact('text', 'options'));
|
||
return $result;
|
||
}
|
||
|
||
$summary = trim($result['content']);
|
||
|
||
do_action('wpmind_after_request', 'summarize', $summary, compact('text', 'options'), $result['usage']);
|
||
|
||
$this->set_cached_value($cache_key, $summary, (int) $options['cache_ttl'], [
|
||
'type' => 'summarize',
|
||
'context' => $context,
|
||
'provider' => $default_provider,
|
||
'model' => $default_model,
|
||
]);
|
||
|
||
return $summary;
|
||
}
|
||
|
||
/**
|
||
* 内容审核
|
||
*
|
||
* @since 2.7.0
|
||
* @param string $content 要审核的内容
|
||
* @param array $options 选项
|
||
* @return array|WP_Error
|
||
*/
|
||
public function moderate(string $content, array $options = []) {
|
||
$defaults = [
|
||
'context' => 'moderation',
|
||
'categories' => ['spam', 'adult', 'violence', 'hate', 'illegal'],
|
||
'threshold' => 0.7,
|
||
'cache_ttl' => 300,
|
||
];
|
||
$options = wp_parse_args($options, $defaults);
|
||
|
||
$context = $options['context'];
|
||
|
||
$default_provider = get_option('wpmind_default_provider', 'openai');
|
||
$default_model = $this->get_current_model($default_provider);
|
||
|
||
$cache_key = $this->generate_cache_key('moderate', compact('content', 'options'), $default_provider, $default_model);
|
||
$cache_lookup = $this->get_cached_value($cache_key, (int) $options['cache_ttl']);
|
||
if ($cache_lookup['hit']) {
|
||
return $cache_lookup['value'];
|
||
}
|
||
|
||
$categories = implode('、', $options['categories']);
|
||
|
||
$schema = [
|
||
'type' => 'object',
|
||
'required' => ['safe', 'categories'],
|
||
'properties' => [
|
||
'safe' => ['type' => 'boolean'],
|
||
'categories' => [
|
||
'type' => 'object',
|
||
'properties' => array_combine(
|
||
$options['categories'],
|
||
array_fill(0, count($options['categories']), [
|
||
'type' => 'object',
|
||
'properties' => [
|
||
'flagged' => ['type' => 'boolean'],
|
||
'score' => ['type' => 'number'],
|
||
'reason' => ['type' => 'string'],
|
||
],
|
||
])
|
||
),
|
||
],
|
||
'summary' => ['type' => 'string'],
|
||
],
|
||
];
|
||
|
||
$prompt = "请审核以下内容是否包含不当信息。检查类别:{$categories}。\n\n内容:\n{$content}";
|
||
|
||
do_action('wpmind_before_request', 'moderate', compact('content', 'options'), $context);
|
||
|
||
$result = $this->structured_service->structured($prompt, $schema, [
|
||
'context' => $context,
|
||
'temperature' => 0.1,
|
||
'retries' => 2,
|
||
]);
|
||
|
||
if (is_wp_error($result)) {
|
||
do_action('wpmind_error', $result, 'moderate', compact('content', 'options'));
|
||
return $result;
|
||
}
|
||
|
||
$moderation = [
|
||
'safe' => $result['data']['safe'] ?? true,
|
||
'categories' => $result['data']['categories'] ?? [],
|
||
'summary' => $result['data']['summary'] ?? '',
|
||
'provider' => $result['provider'],
|
||
'model' => $result['model'],
|
||
'usage' => $result['usage'],
|
||
];
|
||
|
||
do_action('wpmind_after_request', 'moderate', $moderation, compact('content', 'options'), $result['usage']);
|
||
|
||
$this->set_cached_value($cache_key, $moderation, (int) $options['cache_ttl'], [
|
||
'type' => 'moderate',
|
||
'context' => $context,
|
||
'provider' => $default_provider,
|
||
'model' => $default_model,
|
||
]);
|
||
|
||
return $moderation;
|
||
}
|
||
|
||
/**
|
||
* 构建翻译 Prompt
|
||
*
|
||
* @param string $text 文本
|
||
* @param string $from 源语言
|
||
* @param string $to 目标语言
|
||
* @param array $options 选项
|
||
* @return string
|
||
*/
|
||
private function build_translate_prompt(string $text, string $from, string $to, array $options): string {
|
||
$lang_names = [
|
||
'zh' => '中文',
|
||
'en' => '英文',
|
||
'ja' => '日文',
|
||
'ko' => '韩文',
|
||
'fr' => '法文',
|
||
'de' => '德文',
|
||
'es' => '西班牙文',
|
||
'auto' => '自动检测',
|
||
];
|
||
|
||
$from_name = $lang_names[$from] ?? $from;
|
||
$to_name = $lang_names[$to] ?? $to;
|
||
|
||
if ($options['format'] === 'pinyin') {
|
||
$prompt = "将以下中文文本转换为拼音,要求:
|
||
1. 按词语分隔,不是按字分隔(如 '你好世界' 应为 'nihao-shijie' 而非 'ni-hao-shi-jie')
|
||
2. 词语之间用连字符 '-' 连接
|
||
3. 同一词语内的拼音不加分隔符
|
||
4. 全部小写,无声调
|
||
5. 保留英文和数字原样
|
||
6. 只返回拼音结果,不要其他解释
|
||
|
||
文本:{$text}";
|
||
return $prompt;
|
||
}
|
||
|
||
$prompt = "将以下{$from_name}文本翻译成{$to_name}";
|
||
|
||
if ($options['format'] === 'slug') {
|
||
$prompt .= ",输出结果应该适合作为 URL slug,使用小写英文和连字符";
|
||
}
|
||
|
||
if (!empty($options['hint'])) {
|
||
$prompt .= "。提示:{$options['hint']}";
|
||
}
|
||
|
||
$prompt .= "。只返回翻译结果,不要其他解释:\n\n{$text}";
|
||
|
||
return $prompt;
|
||
}
|
||
}
|