新增
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
/**
|
||||
* Dify Chat App blocking 客户端。
|
||||
*
|
||||
* 只接受服务端配置中的模型 profile,避免把上游地址和密钥暴露给前端。
|
||||
*/
|
||||
class DifyChatService
|
||||
{
|
||||
/**
|
||||
* @param array<string,mixed> $inputs
|
||||
* @return array{ok:bool,content?:string,message_id?:string,latency_ms?:int,error_code?:string,error?:string}
|
||||
*/
|
||||
public static function chat(string $profile, array $inputs, string $query, string $user): array
|
||||
{
|
||||
$config = config('prescription_ai') ?: [];
|
||||
if (empty($config['enable'])) {
|
||||
return self::error('CONFIG_DISABLED', '处方 AI 解释未启用');
|
||||
}
|
||||
|
||||
$modelConfig = $config['models'][$profile] ?? null;
|
||||
if (!is_array($modelConfig)) {
|
||||
return self::error('INVALID_PROFILE', '不支持的 AI 模型');
|
||||
}
|
||||
|
||||
$baseUrl = trim((string) ($config['base_url'] ?? ''));
|
||||
$apiKey = trim((string) ($modelConfig['api_key'] ?? ''));
|
||||
if ($baseUrl === '' || $apiKey === '') {
|
||||
return self::error('CONFIG_MISSING', '该模型尚未配置 Dify 地址或 App Key');
|
||||
}
|
||||
if (!function_exists('curl_init')) {
|
||||
return self::error('CURL_UNAVAILABLE', '服务器尚未启用 cURL 扩展');
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'inputs' => $inputs,
|
||||
'query' => $query,
|
||||
'response_mode' => 'blocking',
|
||||
'user' => $user,
|
||||
];
|
||||
$body = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
|
||||
if ($body === false) {
|
||||
return self::error('REQUEST_BUILD_FAILED', '处方数据编码失败');
|
||||
}
|
||||
|
||||
$timeout = max(10, min(120, (int) ($config['timeout'] ?? 90)));
|
||||
$ch = curl_init();
|
||||
if ($ch === false) {
|
||||
return self::error('CURL_INIT_FAILED', '无法初始化 AI 请求');
|
||||
}
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => self::buildEndpoint($baseUrl),
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $body,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CONNECTTIMEOUT => min(8, max(3, (int) ceil($timeout / 4))),
|
||||
CURLOPT_TIMEOUT => $timeout,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Content-Type: application/json',
|
||||
'Accept: application/json',
|
||||
'Authorization: Bearer ' . $apiKey,
|
||||
],
|
||||
]);
|
||||
|
||||
$startedAt = microtime(true);
|
||||
$responseBody = curl_exec($ch);
|
||||
$errno = curl_errno($ch);
|
||||
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
$latencyMs = (int) round((microtime(true) - $startedAt) * 1000);
|
||||
|
||||
if ($errno !== 0) {
|
||||
if ($errno === CURLE_OPERATION_TIMEDOUT) {
|
||||
return self::error('UPSTREAM_TIMEOUT', '模型响应超时,请稍后重试', $latencyMs);
|
||||
}
|
||||
return self::error('UPSTREAM_UNAVAILABLE', '暂时无法连接 AI 服务,请稍后重试', $latencyMs);
|
||||
}
|
||||
|
||||
$decoded = json_decode((string) $responseBody, true);
|
||||
if ($httpCode === 401 || $httpCode === 403) {
|
||||
return self::error('CONFIG_INVALID', '模型 App Key 无效或无权限', $latencyMs);
|
||||
}
|
||||
if ($httpCode === 429 || $httpCode >= 500) {
|
||||
return self::error('UPSTREAM_BUSY', '模型服务繁忙,请稍后重试', $latencyMs);
|
||||
}
|
||||
if ($httpCode >= 400) {
|
||||
return self::error('UPSTREAM_REJECTED', '模型未能处理本次请求', $latencyMs);
|
||||
}
|
||||
if (!is_array($decoded)) {
|
||||
return self::error('INVALID_RESPONSE', '模型返回格式异常,请重试', $latencyMs);
|
||||
}
|
||||
|
||||
$answer = trim((string) ($decoded['answer'] ?? ''));
|
||||
if ($answer === '') {
|
||||
return self::error('EMPTY_RESPONSE', '模型未返回报告内容,请重试', $latencyMs);
|
||||
}
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'content' => $answer,
|
||||
'message_id' => (string) ($decoded['message_id'] ?? ''),
|
||||
'latency_ms' => $latencyMs,
|
||||
];
|
||||
}
|
||||
|
||||
private static function buildEndpoint(string $baseUrl): string
|
||||
{
|
||||
$baseUrl = rtrim($baseUrl, '/');
|
||||
if (str_ends_with($baseUrl, '/chat-messages')) {
|
||||
return $baseUrl;
|
||||
}
|
||||
if (str_ends_with($baseUrl, '/v1')) {
|
||||
return $baseUrl . '/chat-messages';
|
||||
}
|
||||
return $baseUrl . '/v1/chat-messages';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{ok:false,error_code:string,error:string,latency_ms:int}
|
||||
*/
|
||||
private static function error(string $code, string $message, int $latencyMs = 0): array
|
||||
{
|
||||
return [
|
||||
'ok' => false,
|
||||
'error_code' => $code,
|
||||
'error' => $message,
|
||||
'latency_ms' => $latencyMs,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user