erp-backend/app/Services/AIService.php
2026-04-01 17:07:04 +08:00

406 lines
12 KiB
PHP

<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
class AIService
{
/**
* 当前使用的服务
*/
protected $currentService;
/**
* 服务配置
*/
protected $config;
/**
* 构造函数
*/
public function __construct()
{
$this->config = config('ai');
$this->currentService = $this->config['default'];
}
/**
* 聊天对话
*/
public function chat(array $messages, array $options = []): array
{
$cacheKey = $this->getCacheKey('chat', $messages, $options);
// 检查缓存
if ($this->config['cache']['enabled']) {
$cached = Cache::get($cacheKey);
if ($cached) {
return $cached;
}
}
$result = $this->callService('chat', $messages, $options);
// 缓存结果
if ($this->config['cache']['enabled'] && $result['success']) {
Cache::put($cacheKey, $result, $this->config['cache']['ttl']);
}
return $result;
}
/**
* 调用AI服务
*/
protected function callService(string $action, array $data, array $options = []): array
{
$serviceConfig = $this->getServiceConfig();
// 记录请求日志
if ($this->config['logging']['enabled'] && $this->config['logging']['log_requests']) {
Log::info('AI服务请求', [
'service' => $this->currentService,
'action' => $action,
'data' => $this->filterSensitiveData($data),
]);
}
try {
$response = $this->makeRequest($serviceConfig, $data, $options);
// 记录响应日志
if ($this->config['logging']['enabled'] && $this->config['logging']['log_responses']) {
Log::info('AI服务响应', [
'service' => $this->currentService,
'action' => $action,
'response' => $this->filterSensitiveData($response),
]);
}
return $response;
} catch (\Exception $e) {
// 记录错误日志
if ($this->config['logging']['enabled'] && $this->config['logging']['log_errors']) {
Log::error('AI服务错误', [
'service' => $this->currentService,
'action' => $action,
'error' => $e->getMessage(),
]);
}
// 尝试降级服务
if ($this->config['fallback']['enabled']) {
return $this->fallbackService($action, $data, $options);
}
return [
'success' => false,
'error' => 'AI服务暂时不可用: ' . $e->getMessage(),
'service' => $this->currentService,
];
}
}
/**
* 发送HTTP请求
*/
protected function makeRequest(array $serviceConfig, array $data, array $options): array
{
$url = $serviceConfig['api_url'];
$timeout = $serviceConfig['timeout'] ?? 30;
$headers = $this->buildHeaders($serviceConfig);
$body = $this->buildRequestBody($serviceConfig, $data, $options);
$response = Http::withHeaders($headers)
->timeout($timeout)
->post($url, $body);
if ($response->successful()) {
return $this->parseResponse($serviceConfig, $response->json());
} else {
throw new \Exception('API请求失败: ' . $response->status() . ' - ' . $response->body());
}
}
/**
* 构建请求头
*/
protected function buildHeaders(array $serviceConfig): array
{
$headers = [
'Content-Type' => 'application/json',
];
switch ($this->currentService) {
case 'openai':
$headers['Authorization'] = 'Bearer ' . $serviceConfig['api_key'];
break;
case 'azure_openai':
$headers['api-key'] = $serviceConfig['api_key'];
break;
case 'anthropic':
$headers['x-api-key'] = $serviceConfig['api_key'];
$headers['anthropic-version'] = '2023-06-01';
break;
case 'aliyun_qwen':
$headers['Authorization'] = 'Bearer ' . $serviceConfig['api_key'];
break;
}
return $headers;
}
/**
* 构建请求体
*/
protected function buildRequestBody(array $serviceConfig, array $data, array $options): array
{
$body = [];
switch ($this->currentService) {
case 'openai':
case 'local':
$body = [
'model' => $serviceConfig['model'],
'messages' => $data['messages'] ?? [],
'max_tokens' => $options['max_tokens'] ?? $serviceConfig['max_tokens'],
'temperature' => $options['temperature'] ?? $serviceConfig['temperature'],
'stream' => $options['stream'] ?? false,
];
break;
case 'azure_openai':
$body = [
'messages' => $data['messages'] ?? [],
'max_tokens' => $options['max_tokens'] ?? $serviceConfig['max_tokens'],
'temperature' => $options['temperature'] ?? $serviceConfig['temperature'],
'stream' => $options['stream'] ?? false,
];
break;
case 'anthropic':
$body = [
'model' => $serviceConfig['model'],
'messages' => $data['messages'] ?? [],
'max_tokens' => $options['max_tokens'] ?? $serviceConfig['max_tokens'],
'temperature' => $options['temperature'] ?? $serviceConfig['temperature'],
'stream' => $options['stream'] ?? false,
];
break;
case 'aliyun_qwen':
$body = [
'model' => $serviceConfig['model'],
'messages' => $data['messages'] ?? [],
'max_tokens' => $options['max_tokens'] ?? $serviceConfig['max_tokens'],
'temperature' => $options['temperature'] ?? $serviceConfig['temperature'],
'stream' => $options['stream'] ?? false,
];
break;
}
return $body;
}
/**
* 解析响应
*/
protected function parseResponse(array $serviceConfig, array $response): array
{
switch ($this->currentService) {
case 'openai':
case 'azure_openai':
case 'local':
$content = $response['choices'][0]['message']['content'] ?? '';
$tokens = $response['usage']['total_tokens'] ?? 0;
break;
case 'anthropic':
$content = $response['content'][0]['text'] ?? '';
$tokens = $response['usage']['input_tokens'] + $response['usage']['output_tokens'] ?? 0;
break;
case 'aliyun_qwen':
$content = $response['choices'][0]['message']['content'] ?? '';
$tokens = $response['usage']['total_tokens'] ?? 0;
break;
default:
$content = '';
$tokens = 0;
}
return [
'success' => true,
'content' => $content,
'tokens' => $tokens,
'raw_response' => $response,
];
}
/**
* 降级服务
*/
protected function fallbackService(string $action, array $data, array $options): array
{
$services = $this->config['fallback']['service_order'];
$currentIndex = array_search($this->currentService, $services);
if ($currentIndex === false || $currentIndex >= count($services) - 1) {
return [
'success' => false,
'error' => '所有AI服务都不可用',
];
}
// 切换到下一个服务
$nextService = $services[$currentIndex + 1];
$this->currentService = $nextService;
Log::warning('AI服务降级', [
'from' => $services[$currentIndex],
'to' => $nextService,
'action' => $action,
]);
// 重试请求
return $this->callService($action, $data, $options);
}
/**
* 获取服务配置
*/
protected function getServiceConfig(): array
{
$serviceConfig = $this->config['services'][$this->currentService] ?? [];
if (empty($serviceConfig)) {
throw new \Exception("AI服务配置不存在: {$this->currentService}");
}
return $serviceConfig;
}
/**
* 过滤敏感数据
*/
protected function filterSensitiveData(array $data): array
{
if (!$this->config['security']['filter_sensitive_data']) {
return $data;
}
$filtered = $data;
$sensitiveKeywords = $this->config['security']['sensitive_keywords'];
array_walk_recursive($filtered, function (&$value, $key) use ($sensitiveKeywords) {
foreach ($sensitiveKeywords as $keyword) {
if (stripos($key, $keyword) !== false || stripos($value, $keyword) !== false) {
$value = '***FILTERED***';
break;
}
}
});
return $filtered;
}
/**
* 生成缓存键
*/
protected function getCacheKey(string $action, array $data, array $options): string
{
$prefix = $this->config['cache']['prefix'];
$hash = md5(serialize([$action, $data, $options, $this->currentService]));
return $prefix . $hash;
}
/**
* 获取当前服务
*/
public function getCurrentService(): string
{
return $this->currentService;
}
/**
* 设置服务
*/
public function setService(string $service): void
{
if (isset($this->config['services'][$service])) {
$this->currentService = $service;
} else {
throw new \Exception("不支持的AI服务: {$service}");
}
}
/**
* 获取可用服务列表
*/
public function getAvailableServices(): array
{
return array_keys($this->config['services']);
}
/**
* 测试服务连接
*/
public function testConnection(): array
{
try {
$testMessages = [
['role' => 'system', 'content' => '你是一个测试助手'],
['role' => 'user', 'content' => '请回复"OK"'],
];
$result = $this->chat($testMessages, [
'max_tokens' => 10,
'temperature' => 0.1,
]);
return [
'success' => $result['success'],
'service' => $this->currentService,
'response_time' => '正常',
'message' => $result['success'] ? '连接正常' : ($result['error'] ?? '连接失败'),
];
} catch (\Exception $e) {
return [
'success' => false,
'service' => $this->currentService,
'error' => $e->getMessage(),
'message' => '连接失败',
];
}
}
/**
* 获取服务状态
*/
public function getServiceStatus(): array
{
$services = [];
foreach ($this->config['services'] as $name => $config) {
$originalService = $this->currentService;
$this->currentService = $name;
try {
$testResult = $this->testConnection();
$services[$name] = [
'status' => $testResult['success'] ? 'online' : 'offline',
'message' => $testResult['message'],
];
} catch (\Exception $e) {
$services[$name] = [
'status' => 'error',
'message' => $e->getMessage(),
];
}
$this->currentService = $originalService;
}
return $services;
}
}