mirror of
https://github.com/kuaifan/dootask.git
synced 2026-01-26 20:48:12 +00:00
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:
parent
165ad03024
commit
d4d7a0d69f
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
/** ******************************************************************************************** */
|
/** ******************************************************************************************** */
|
||||||
/** ******************************************************************************************** */
|
/** ******************************************************************************************** */
|
||||||
/** ******************************************************************************************** */
|
/** ******************************************************************************************** */
|
||||||
|
|||||||
@ -136,11 +136,13 @@ class AiTaskSuggestion
|
|||||||
$searchText = $task->name . ' ' . ($task->content ?? '');
|
$searchText = $task->name . ' ' . ($task->content ?? '');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$embedding = AI::getEmbedding($searchText);
|
$result = AI::getEmbedding($searchText);
|
||||||
if (empty($embedding)) {
|
if (Base::isError($result) || empty($result['data'])) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$embedding = $result['data'];
|
||||||
|
|
||||||
// 搜索相似任务(排除自己和子任务)
|
// 搜索相似任务(排除自己和子任务)
|
||||||
$similarTasks = self::searchSimilarByEmbedding(
|
$similarTasks = self::searchSimilarByEmbedding(
|
||||||
$embedding,
|
$embedding,
|
||||||
@ -270,7 +272,12 @@ PROMPT;
|
|||||||
['user', $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) {
|
} catch (\Exception $e) {
|
||||||
\Log::error('AiTaskSuggestion::callAi error: ' . $e->getMessage());
|
\Log::error('AiTaskSuggestion::callAi error: ' . $e->getMessage());
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user