feat(ai): add AI::invoke() method for task suggestions

- Add generic invoke() static method to AI module for custom chat completion
- Fix AiTaskSuggestion::callAi() to properly handle AI::invoke() response
- Fix findSimilarTasks() to properly handle AI::getEmbedding() response

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kuaifan 2026-01-21 02:02:04 +00:00
parent 165ad03024
commit d4d7a0d69f
2 changed files with 105 additions and 3 deletions

View File

@ -276,6 +276,101 @@ class AI
]);
}
/**
* 通用 AI 调用接口
* 适用于自定义对话场景
*
* @param array $messages 消息数组,格式:[['role', 'content'], ...]
* role: system | user | assistant
* @param int $timeout 超时时间(秒)
* @param bool $noCache 是否禁用缓存
* @return array 返回结果,成功时 data 包含 content 字段
*/
public static function invoke(array $messages, int $timeout = 60, bool $noCache = true): array
{
if (!Apps::isInstalled('ai')) {
return Base::retError('应用「AI Assistant」未安装');
}
if (empty($messages)) {
return Base::retError('消息内容不能为空');
}
$provider = self::resolveTextProvider();
if (!$provider) {
return Base::retError("请先配置 AI 助手");
}
// 转换消息格式
$formattedMessages = [];
foreach ($messages as $msg) {
if (!is_array($msg) || count($msg) < 2) {
continue;
}
$role = trim((string)($msg[0] ?? ''));
$content = trim((string)($msg[1] ?? ''));
if ($role === '' || $content === '') {
continue;
}
// 标准化 role
$role = match ($role) {
'system' => 'system',
'assistant' => 'assistant',
default => 'user',
};
$formattedMessages[] = [
'role' => $role,
'content' => $content,
];
}
if (empty($formattedMessages)) {
return Base::retError('消息内容格式错误');
}
// 构建缓存 key
$cacheKey = "AIInvoke::" . md5(json_encode($formattedMessages));
if ($noCache) {
Cache::forget($cacheKey);
}
$result = Cache::remember($cacheKey, Carbon::now()->addHours(1), function () use ($formattedMessages, $provider, $timeout) {
$payload = [
"model" => $provider['model'],
"messages" => $formattedMessages,
];
$reasoningEffort = self::getReasoningEffort($provider);
if ($reasoningEffort !== null) {
$payload['reasoning_effort'] = $reasoningEffort;
}
$post = json_encode($payload);
$ai = new self($post);
$ai->setProvider($provider);
$ai->setTimeout($timeout);
$res = $ai->request();
if (Base::isError($res)) {
return Base::retError("AI 调用失败", $res);
}
$content = $res['data'];
if (empty($content)) {
return Base::retError("AI 返回内容为空");
}
return Base::retSuccess("success", [
'content' => $content,
]);
});
if (Base::isError($result)) {
Cache::forget($cacheKey);
}
return $result;
}
/** ******************************************************************************************** */
/** ******************************************************************************************** */
/** ******************************************************************************************** */

View File

@ -136,11 +136,13 @@ class AiTaskSuggestion
$searchText = $task->name . ' ' . ($task->content ?? '');
try {
$embedding = AI::getEmbedding($searchText);
if (empty($embedding)) {
$result = AI::getEmbedding($searchText);
if (Base::isError($result) || empty($result['data'])) {
return null;
}
$embedding = $result['data'];
// 搜索相似任务(排除自己和子任务)
$similarTasks = self::searchSimilarByEmbedding(
$embedding,
@ -270,7 +272,12 @@ PROMPT;
['user', $prompt],
]);
return $result['content'] ?? null;
if (Base::isError($result)) {
\Log::error('AiTaskSuggestion::callAi error: ' . ($result['msg'] ?? 'Unknown error'));
return null;
}
return $result['data']['content'] ?? null;
} catch (\Exception $e) {
\Log::error('AiTaskSuggestion::callAi error: ' . $e->getMessage());
return null;