configService = $configService; } /** * 获取所有配置的 AI 提供商 */ public function getProviders(): array { $providers = $this->configService->get('log_analysis.ai_providers', []); // 如果数据库没有配置,使用 .env 默认配置 if (empty($providers)) { $envConfig = config('services.ai'); if (!empty($envConfig['api_key'])) { $providers = [ 'default' => [ 'name' => '默认 (环境变量)', 'endpoint' => $envConfig['endpoint'], 'api_key' => $envConfig['api_key'], 'model' => $envConfig['model'], 'temperature' => $envConfig['temperature'], 'timeout' => $envConfig['timeout'], 'max_tokens' => $envConfig['max_tokens'], 'enabled' => true, ], ]; } } return $providers; } /** * 获取当前激活的 AI 提供商 */ public function getActiveProvider(): ?array { if ($this->currentProvider !== null) { return $this->currentProvider; } $activeKey = $this->configService->get('log_analysis.active_ai_provider'); $providers = $this->getProviders(); if ($activeKey && isset($providers[$activeKey]) && ($providers[$activeKey]['enabled'] ?? true)) { $this->currentProvider = $providers[$activeKey]; $this->currentProvider['key'] = $activeKey; return $this->currentProvider; } // 返回第一个启用的提供商 foreach ($providers as $key => $provider) { if ($provider['enabled'] ?? true) { $this->currentProvider = $provider; $this->currentProvider['key'] = $key; return $this->currentProvider; } } return null; } /** * 设置当前使用的提供商 */ public function setActiveProvider(string $providerKey): void { $providers = $this->getProviders(); if (!isset($providers[$providerKey])) { throw new RuntimeException("AI 提供商不存在: {$providerKey}"); } $this->configService->set('log_analysis.active_ai_provider', $providerKey); $this->currentProvider = $providers[$providerKey]; $this->currentProvider['key'] = $providerKey; } /** * 检查是否已配置 */ public function isConfigured(): bool { return $this->getActiveProvider() !== null; } /** * 发送聊天请求 (OpenAI 兼容接口) * * @param array $messages 消息数组 * @param string|null $systemPrompt 系统提示词 * @return string AI 响应内容 */ public function chat(array $messages, ?string $systemPrompt = null): string { $provider = $this->getActiveProvider(); if (!$provider) { throw new RuntimeException('未配置 AI 服务,请在设置页面配置 AI 提供商'); } // 速率限制 $this->waitForRateLimit($provider['key'] ?? 'default'); $allMessages = []; if ($systemPrompt) { $allMessages[] = ['role' => 'system', 'content' => $systemPrompt]; } $allMessages = array_merge($allMessages, $messages); $endpoint = rtrim($provider['endpoint'], '/') . '/chat/completions'; $response = Http::timeout($provider['timeout'] ?? 120) ->withHeaders([ 'Authorization' => 'Bearer ' . $provider['api_key'], 'Content-Type' => 'application/json', ]) ->post($endpoint, [ 'model' => $provider['model'], 'messages' => $allMessages, 'temperature' => $provider['temperature'] ?? 0.3, 'max_tokens' => $provider['max_tokens'] ?? 4096, ]); if (!$response->successful()) { $error = $response->json('error.message') ?? $response->body(); throw new RuntimeException("AI 请求失败: {$error}"); } return $response->json('choices.0.message.content', ''); } /** * 分析日志并返回结构化结果 * * @param string $logsContent 日志内容 * @param string|null $codeContext 相关代码上下文 * @return array 分析结果 */ public function analyzeLogs(string $logsContent, ?string $codeContext = null): array { $systemPrompt = $this->buildAnalysisSystemPrompt(); $userContent = $this->buildAnalysisUserPrompt($logsContent, $codeContext); $response = $this->chat([ ['role' => 'user', 'content' => $userContent], ], $systemPrompt); return $this->parseAnalysisResponse($response); } /** * 测试连接 */ public function testConnection(): array { $provider = $this->getActiveProvider(); if (!$provider) { return [ 'success' => false, 'message' => '未配置 AI 服务', ]; } try { $response = $this->chat([ ['role' => 'user', 'content' => 'Hello, please respond with "OK" only.'], ]); return [ 'success' => true, 'message' => '连接成功', 'provider' => $provider['name'] ?? $provider['key'], 'model' => $provider['model'], ]; } catch (\Exception $e) { return [ 'success' => false, 'message' => $e->getMessage(), ]; } } private function buildAnalysisSystemPrompt(): string { return <<<'PROMPT' 你是一个专业的日志分析专家。你的任务是分析提供的日志内容,识别异常和问题,并给出分类和建议。 请按照以下 JSON 格式返回分析结果: { "core_anomalies": [ { "type": "error|warning|performance|security", "classification": "database|network|application|configuration|resource|other", "count": 数量, "sample": "示例日志内容", "possible_cause": "可能的原因", "suggestion": "建议的解决方案" } ], "summary": "整体分析摘要", "impact": "high|medium|low", "trends": "趋势分析(可选)" } 分类说明: - type: error(错误), warning(警告), performance(性能问题), security(安全问题) - classification: database(数据库), network(网络), application(应用逻辑), configuration(配置), resource(资源), other(其他) - impact: high(高影响), medium(中等影响), low(低影响) 请确保返回有效的 JSON 格式。 PROMPT; } private function buildAnalysisUserPrompt(string $logsContent, ?string $codeContext): string { $prompt = "请分析以下日志内容:\n\n```\n{$logsContent}\n```"; if ($codeContext) { $prompt .= "\n\n相关代码上下文:\n\n```\n{$codeContext}\n```"; } return $prompt; } private function parseAnalysisResponse(string $response): array { // 尝试提取 JSON if (preg_match('/\{[\s\S]*}/', $response, $matches)) { $json = $matches[0]; $decoded = json_decode($json, true); if (json_last_error() === JSON_ERROR_NONE) { return $decoded; } } // 如果无法解析 JSON,返回原始响应 return [ 'core_anomalies' => [], 'summary' => $response, 'impact' => 'unknown', 'raw_response' => $response, ]; } private function waitForRateLimit(string $providerKey): void { $key = "ai_rate_limit:{$providerKey}"; // 每分钟最多 60 次请求 $maxAttempts = 60; $decaySeconds = 60; if (RateLimiter::tooManyAttempts($key, $maxAttempts)) { $seconds = RateLimiter::availableIn($key); sleep($seconds); } RateLimiter::hit($key, $decaySeconds); } }