|
|
@@ -36,7 +36,13 @@ class DeepSeekService
|
|
|
'Authorization' => 'Bearer '.$this->api_key,
|
|
|
'Content-Type' => 'application/json; charset=UTF-8'
|
|
|
];
|
|
|
- $this->valid_text_models = ['doubao-seed-2-0-pro-260215', 'doubao-seed-2-0-lite-260215', 'deepseek-v3-2-251201'];
|
|
|
+ // 火山引擎模型映射:模型名称 => endpoint_id
|
|
|
+ $this->valid_text_models = [
|
|
|
+ 'doubao-seed-2-0-pro-260215' => env('VOLC_DOUBAO_PRO_ENDPOINT', 'doubao-seed-2-0-pro-260215'),
|
|
|
+ 'doubao-seed-2-0-lite-260215' => env('VOLC_DOUBAO_LITE_ENDPOINT', 'doubao-seed-2-0-lite-260215'),
|
|
|
+ 'doubao-seed-2-0-mini-260215' => env('VOLC_DOUBAO_MINI_ENDPOINT', 'doubao-seed-2-0-mini-260215'),
|
|
|
+ 'deepseek-v3-2-251201' => env('VOLC_DEEPSEEK_V3_ENDPOINT', 'deepseek-v3-2-251201')
|
|
|
+ ];
|
|
|
$this->sys_message = [
|
|
|
'role' => 'system',
|
|
|
'content' => "你是一个专业的文档分析助手及资深编剧,请根据用户提供的文档内容完成示例格式的内容输出(需通过以下几个板块进行回复: <故事梗概><剧本亮点><人物关系><核心矛盾><主体列表><美术风格><场景列表><分集剧本>,每个板块之间用###分隔;同时板块之间需满足以下要求:\n
|
|
|
@@ -139,6 +145,181 @@ class DeepSeekService
|
|
|
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 通用文生文方法 - 支持多模型流式输出和图片输入
|
|
|
+ *
|
|
|
+ * @param array $data 请求参数
|
|
|
+ * @return \Generator 返回生成器,用于流式输出
|
|
|
+ */
|
|
|
+ public function generateText($data) {
|
|
|
+ // 获取请求参数
|
|
|
+ $model = getProp($data, 'model', 'deepseek-v3-2-251201'); // 默认使用deepseek-v3
|
|
|
+ $messages = getProp($data, 'messages', []);
|
|
|
+ $systemPrompt = getProp($data, 'system_prompt', '');
|
|
|
+ $prompt = getProp($data, 'prompt', '');
|
|
|
+ $images = getProp($data, 'images', []); // 支持多张图片
|
|
|
+ $imageUrls = getProp($data, 'image_urls', []); // 支持图片URL
|
|
|
+
|
|
|
+ // 如果没有提供messages,则根据system_prompt、content和images构建
|
|
|
+ if (empty($messages)) {
|
|
|
+ $messages = [];
|
|
|
+
|
|
|
+ // 添加系统提示词
|
|
|
+ if (!empty($systemPrompt)) {
|
|
|
+ $messages[] = [
|
|
|
+ 'role' => 'system',
|
|
|
+ 'content' => $systemPrompt
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建用户消息内容(支持文本+图片)
|
|
|
+ if (!empty($prompt) || !empty($images) || !empty($imageUrls)) {
|
|
|
+ $userMessage = [
|
|
|
+ 'role' => 'user',
|
|
|
+ 'content' => []
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 添加文本内容
|
|
|
+ if (!empty($prompt)) {
|
|
|
+ $userMessage['content'][] = [
|
|
|
+ 'type' => 'text',
|
|
|
+ 'text' => $prompt
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理上传的图片文件
|
|
|
+ if (!empty($images)) {
|
|
|
+ if (!is_array($images)) {
|
|
|
+ $images = [$images];
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach ($images as $image) {
|
|
|
+ $imageBase64 = $this->processImageToBase64($image);
|
|
|
+ if ($imageBase64) {
|
|
|
+ $userMessage['content'][] = [
|
|
|
+ 'type' => 'image_url',
|
|
|
+ 'image_url' => [
|
|
|
+ 'url' => "data:image/jpeg;base64,{$imageBase64}"
|
|
|
+ ]
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理图片URL
|
|
|
+ if (!empty($imageUrls)) {
|
|
|
+ if (!is_array($imageUrls)) {
|
|
|
+ $imageUrls = [$imageUrls];
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach ($imageUrls as $url) {
|
|
|
+ if (!empty($url)) {
|
|
|
+ $userMessage['content'][] = [
|
|
|
+ 'type' => 'image_url',
|
|
|
+ 'image_url' => [
|
|
|
+ 'url' => $url
|
|
|
+ ]
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果只有文本内容,简化为字符串格式
|
|
|
+ if (count($userMessage['content']) === 1 && $userMessage['content'][0]['type'] === 'text') {
|
|
|
+ $userMessage['content'] = $userMessage['content'][0]['text'];
|
|
|
+ }
|
|
|
+
|
|
|
+ $messages[] = $userMessage;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证messages不为空
|
|
|
+ if (empty($messages)) {
|
|
|
+ Utils::throwError('20003:请提供有效的对话内容');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建请求参数
|
|
|
+ $post_data = [
|
|
|
+ 'model' => $model,
|
|
|
+ 'messages' => $messages,
|
|
|
+ 'max_tokens' => getProp($data, 'max_tokens', 8192),
|
|
|
+ 'temperature' => getProp($data, 'temperature', 1),
|
|
|
+ 'frequency_penalty' => getProp($data, 'frequency_penalty', 0),
|
|
|
+ 'presence_penalty' => getProp($data, 'presence_penalty', 0),
|
|
|
+ 'thinking'=>'disabled',
|
|
|
+ 'stream' => true // 启用流式输出
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 添加可选参数
|
|
|
+ if (isset($data['top_p'])) {
|
|
|
+ $post_data['top_p'] = $data['top_p'];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isset($data['response_format'])) {
|
|
|
+ $post_data['response_format'] = $data['response_format'];
|
|
|
+ }
|
|
|
+
|
|
|
+ dLog('deepseek')->info('通用文生文请求参数', ['model' => $model, 'messages_count' => count($messages)]);
|
|
|
+
|
|
|
+ // 根据模型类型选择调用方法
|
|
|
+ if (in_array($model, ['deepseek-reasoner', 'deepseek-chat'])) {
|
|
|
+ // DeepSeek 官方模型使用 DeepSeek API
|
|
|
+ return $this->deepSeekStreamResponse($post_data);
|
|
|
+ } else if (array_key_exists($model, $this->valid_text_models)) {
|
|
|
+ // 火山引擎支持的模型使用火山引擎 API
|
|
|
+ // 将模型名称转换为 endpoint_id
|
|
|
+ $post_data['model'] = $this->valid_text_models[$model];
|
|
|
+ return $this->volcEngineChatCompletion($post_data);
|
|
|
+ } else {
|
|
|
+ Utils::throwError('20003:不支持的模型: ' . $model);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理图片转换为base64
|
|
|
+ *
|
|
|
+ * @param mixed $image 图片文件对象或文件路径
|
|
|
+ * @return string|null base64编码的图片数据
|
|
|
+ */
|
|
|
+ private function processImageToBase64($image) {
|
|
|
+ try {
|
|
|
+ // 获取文件路径
|
|
|
+ if (is_string($image)) {
|
|
|
+ $filePath = $image;
|
|
|
+ } else {
|
|
|
+ // Laravel UploadedFile 对象
|
|
|
+ $filePath = $image->getRealPath();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!file_exists($filePath)) {
|
|
|
+ dLog('deepseek')->warning('图片文件不存在', ['path' => $filePath]);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证是否为图片文件
|
|
|
+ $imageInfo = @getimagesize($filePath);
|
|
|
+ if ($imageInfo === false) {
|
|
|
+ dLog('deepseek')->warning('无效的图片文件', ['path' => $filePath]);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 读取图片并转换为base64
|
|
|
+ $imageData = file_get_contents($filePath);
|
|
|
+ $base64Image = base64_encode($imageData);
|
|
|
+
|
|
|
+ dLog('deepseek')->info('图片转换成功', [
|
|
|
+ 'size' => strlen($imageData),
|
|
|
+ 'type' => $imageInfo['mime']
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return $base64Image;
|
|
|
+
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ dLog('deepseek')->error('图片处理失败: ' . $e->getMessage());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private function getArtStylePromptMappings(): array
|
|
|
{
|
|
|
return [
|
|
|
@@ -4256,7 +4437,7 @@ class DeepSeekService
|
|
|
'messages' => $params['messages'] ?? [],
|
|
|
];
|
|
|
|
|
|
- if (!in_array($requestData['model'], $this->valid_text_models)) {
|
|
|
+ if (!array_key_exists($requestData['model'], $this->valid_text_models)) {
|
|
|
Utils::throwError('20003:该模型不支持!');
|
|
|
}
|
|
|
|
|
|
@@ -4310,7 +4491,7 @@ class DeepSeekService
|
|
|
}
|
|
|
|
|
|
if (isset($params['thinking'])) {
|
|
|
- $requestData['thinking'] = $params['thinking'];
|
|
|
+ $requestData['thinking'] = ['type'=>$params['thinking']];
|
|
|
}
|
|
|
|
|
|
if (isset($params['reasoning_effort'])) {
|