wpmind/includes/API/Services/TextProcessingService.php
LinuxJoy b8bfb55dad feat: Phase 4.0 — ExactCache + MCP Gateway + API Gateway 设计方案
核心功能:
- 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>
2026-02-08 19:06:30 +08:00

315 lines
8.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
/**
* 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;
}
}