deploy: auto commit server changes 2026-06-12 17:18:58
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
<?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) . '...';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user