Files
sbnews/server/app/common/service/ai/AiAssistantWebNewsRetriever.php
T

149 lines
5.5 KiB
PHP
Raw 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
namespace app\common\service\ai;
use app\common\model\ai\AiConfig;
class AiAssistantWebNewsRetriever
{
public static function retrieve(string $message, array $plan, array $historyMessages = []): array
{
if (!self::shouldRetrieve($message, $plan)) {
return [];
}
$client = new DeepSeekClient();
$result = $client->chatJson(self::systemPrompt(), self::userPrompt($message, $plan, $historyMessages), self::modelOptions());
if (empty($result['success']) || !is_array($result['parsed'] ?? null)) {
return [];
}
return self::normalizeItems($result['parsed'], $message);
}
private static function shouldRetrieve(string $message, array $plan): bool
{
if ((int) AiConfig::getVal('assistant_enable_web_news_search', '1') !== 1) {
return false;
}
$intent = (string) ($plan['intent'] ?? '');
$preferredDomain = (string) ($plan['preferred_domain'] ?? '');
$domains = (array) ($plan['domains'] ?? []);
if (in_array($intent, ['crypto_quote', 'match_schedule', 'match_result', 'lottery_analysis'], true)) {
return false;
}
if ($preferredDomain === 'article' || in_array('article', $domains, true) || in_array('post', $domains, true)) {
return true;
}
return (bool) preg_match('/资讯|新闻|消息|报道|热点|刚刚|最新|头条/u', $message);
}
private static function systemPrompt(): string
{
$today = date('Y-m-d');
return '你是世博头条 AI 助手的联网资讯检索器。'
. '请调用 web search 工具搜索与用户问题最相关、尽量最新的体育资讯或新闻。'
. '今天日期是 ' . $today . '。'
. '只输出 JSON{"items":[{"title":"","source":"","url":"","summary":"","published_at":""}]}。'
. '要求:最多返回 5 条;title 和 url 必填;summary 只写 1-2 句;没有可靠结果时返回 {"items":[]}。';
}
private static function userPrompt(string $message, array $plan, array $historyMessages): string
{
$history = [];
foreach (array_slice($historyMessages, -4) as $row) {
$content = trim((string) ($row['content'] ?? ''));
if ($content === '') {
continue;
}
$history[] = [
'role' => (string) ($row['role'] ?? 'user'),
'content' => mb_substr($content, 0, 200),
];
}
return "用户问题:\n"
. $message
. "\n\n查询规划:\n"
. json_encode([
'intent' => $plan['intent'] ?? '',
'rewritten_query' => $plan['rewritten_query'] ?? '',
'alternate_queries' => $plan['alternate_queries'] ?? [],
'entities' => $plan['entities'] ?? [],
'date_range' => $plan['date_range'] ?? [],
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
. "\n\n最近对话:\n"
. json_encode($history, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
. "\n\n请检索最新外网资讯并输出 JSON。";
}
private static function modelOptions(): array
{
return DeepSeekClient::defaultModelOptions([
'max_tokens' => 900,
'temperature' => 0.2,
'retry_times' => 1,
'retry_sleep_ms' => 600,
'tools' => [
['type' => 'web_search'],
],
'tool_choice' => 'required',
'include' => ['web_search_call.action.sources'],
]);
}
private static function normalizeItems(array $parsed, string $message): array
{
$items = [];
foreach ((array) ($parsed['items'] ?? []) as $row) {
$title = trim((string) ($row['title'] ?? ''));
$url = trim((string) ($row['url'] ?? ''));
if ($title === '' || $url === '' || !preg_match('#^https?://#i', $url)) {
continue;
}
$sourceName = trim((string) ($row['source'] ?? ''));
$publishedAt = trim((string) ($row['published_at'] ?? ''));
$summary = trim((string) ($row['summary'] ?? ''));
$contextSummary = trim(implode(' ', array_filter([
$sourceName !== '' ? '来源:' . $sourceName : '',
$publishedAt !== '' ? '时间:' . $publishedAt : '',
$summary,
])));
$items[] = [
'context' => [
'domain' => 'web_news',
'title' => $title,
'summary' => $contextSummary !== '' ? $contextSummary : $message,
'url' => $url,
'source' => $sourceName,
'published_at' => $publishedAt,
],
'source' => [
'type' => 'web_news',
'title' => $title,
'summary' => self::clipText($contextSummary !== '' ? $contextSummary : $summary, 140),
'path' => '/pages/webview/webview?url=' . urlencode($url),
'source_id' => 0,
],
];
}
return array_slice($items, 0, 5);
}
private static function clipText(string $text, int $length): string
{
$text = trim(preg_replace('/\s+/u', ' ', $text) ?: '');
if (mb_strlen($text) <= $length) {
return $text;
}
return mb_substr($text, 0, $length) . '...';
}
}