|
|
@@ -243,8 +243,6 @@ class DeepSeekService
|
|
|
$post_data['response_format'] = $data['response_format'];
|
|
|
}
|
|
|
|
|
|
- dLog('deepseek')->info('通用文生文请求参数', ['model' => $model, 'thinking' => $thinkingMode, 'messages_count' => count($messages)]);
|
|
|
-
|
|
|
// 根据模型类型选择调用方法
|
|
|
if (in_array($model, ['deepseek-reasoner', 'deepseek-chat', 'deepseek-v4-flash', 'deepseek-v4-pro'])) {
|
|
|
// DeepSeek 官方模型使用 DeepSeek API
|
|
|
@@ -261,6 +259,261 @@ class DeepSeekService
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
+ * 通用文生文方法(非流式版本)- 支持多模型、图片输入、JSON输出和模板提示词
|
|
|
+ *
|
|
|
+ * @param array $data 请求参数
|
|
|
+ * @return array 返回结果数组
|
|
|
+ */
|
|
|
+ public function newGenerateText($data) {
|
|
|
+ // 获取请求参数
|
|
|
+ $model = getProp($data, 'model', 'deepseek-v4-pro'); // 默认使用deepseek-v4
|
|
|
+ $messages = getProp($data, 'messages', []);
|
|
|
+ $systemPrompt = getProp($data, 'system_prompt', '');
|
|
|
+ $prompt = getProp($data, 'prompt', '');
|
|
|
+ $images = getProp($data, 'images', []); // 支持多张图片
|
|
|
+ $imageUrls = getProp($data, 'image_urls', []); // 支持图片URL
|
|
|
+ $responseFormat = getProp($data, 'response_format', 'text'); // 新增: 响应格式,默认text,可选json
|
|
|
+ $templateId = getProp($data, 'template_id', 0); // 新增: 模板ID
|
|
|
+
|
|
|
+ // 新增: 如果提供了template_id,从数据库获取template_prompt
|
|
|
+ if ($templateId > 0) {
|
|
|
+ $template = DB::table('mp_prompt_templates')
|
|
|
+ ->where('id', $templateId)
|
|
|
+ ->where('is_deleted', 0)
|
|
|
+ ->first();
|
|
|
+
|
|
|
+ if (!$template) {
|
|
|
+ Utils::throwError('20003:模板不存在或已删除');
|
|
|
+ }
|
|
|
+
|
|
|
+ $templatePrompt = $template->template_prompt ?? '';
|
|
|
+
|
|
|
+ // 将模板提示词和用户输入的prompt组合
|
|
|
+ // 模板为准,用户输入作为补充要求
|
|
|
+ if (!empty($templatePrompt)) {
|
|
|
+ if (!empty($prompt)) {
|
|
|
+ $prompt = $templatePrompt . "\n\n用户要求:\n" . $prompt;
|
|
|
+ } else {
|
|
|
+ $prompt = $templatePrompt;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果没有提供messages,则根据system_prompt、prompt和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:请提供有效的对话内容');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 模型兼容性处理:deepseek-chat 和 deepseek-reasoner 映射到 deepseek-v4-flash
|
|
|
+ $thinkingMode = getProp($data, 'thinking', 'disabled'); // 默认非思考模式
|
|
|
+ $reasoningEffort = getProp($data, 'reasoning_effort', 'high'); // 默认思考强度
|
|
|
+
|
|
|
+ if ($model === 'deepseek-chat') {
|
|
|
+ // deepseek-chat 映射到 deepseek-v4-flash,非思考模式
|
|
|
+ $model = 'deepseek-v4-flash';
|
|
|
+ $thinkingMode = 'disabled';
|
|
|
+ } elseif ($model === 'deepseek-reasoner') {
|
|
|
+ // deepseek-reasoner 映射到 deepseek-v4-flash,思考模式
|
|
|
+ $model = 'deepseek-v4-flash';
|
|
|
+ $thinkingMode = 'enabled';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建请求参数(非流式)
|
|
|
+ $post_data = [
|
|
|
+ 'model' => $model,
|
|
|
+ 'messages' => $messages,
|
|
|
+ 'temperature' => getProp($data, 'temperature', 1),
|
|
|
+ 'frequency_penalty' => getProp($data, 'frequency_penalty', 0),
|
|
|
+ 'presence_penalty' => getProp($data, 'presence_penalty', 0),
|
|
|
+ 'thinking' => ['type' => $thinkingMode],
|
|
|
+ 'stream' => false // 非流式输出
|
|
|
+ ];
|
|
|
+ if ($thinkingMode == 'enabled') $post_data['reasoning_effort'] = $reasoningEffort;
|
|
|
+
|
|
|
+ // 添加可选参数
|
|
|
+ if (isset($data['top_p'])) {
|
|
|
+ $post_data['top_p'] = $data['top_p'];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 新增: 根据response_format设置响应格式
|
|
|
+ if ($responseFormat === 'json') {
|
|
|
+ // 检查提示词中是否包含"json"关键词,如果没有则自动添加
|
|
|
+ $hasJsonKeyword = false;
|
|
|
+ foreach ($post_data['messages'] as $message) {
|
|
|
+ if (isset($message['content'])) {
|
|
|
+ $content = is_array($message['content']) ? json_encode($message['content']) : $message['content'];
|
|
|
+ if (stripos($content, 'json') !== false) {
|
|
|
+ $hasJsonKeyword = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果提示词中没有"json"关键词,在最后一条用户消息中添加JSON格式说明
|
|
|
+ // 这对所有模型都适用,确保模型理解需要返回JSON格式
|
|
|
+ if (!$hasJsonKeyword) {
|
|
|
+ $lastMessageIndex = count($post_data['messages']) - 1;
|
|
|
+ if ($lastMessageIndex >= 0 && $post_data['messages'][$lastMessageIndex]['role'] === 'user') {
|
|
|
+ $lastContent = $post_data['messages'][$lastMessageIndex]['content'];
|
|
|
+
|
|
|
+ // 如果content是字符串,直接追加
|
|
|
+ if (is_string($lastContent)) {
|
|
|
+ $post_data['messages'][$lastMessageIndex]['content'] .= "\n\n请以JSON格式返回结果。";
|
|
|
+ }
|
|
|
+ // 如果content是数组(包含文本和图片),在文本部分追加
|
|
|
+ else if (is_array($lastContent)) {
|
|
|
+ foreach ($post_data['messages'][$lastMessageIndex]['content'] as &$contentPart) {
|
|
|
+ if ($contentPart['type'] === 'text') {
|
|
|
+ $contentPart['text'] .= "\n\n请以JSON格式返回结果。";
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ dLog('deepseek')->info('自动添加JSON格式提示词', ['model' => $model]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置response_format参数(所有支持的模型)
|
|
|
+ if (in_array($model, ['deepseek-reasoner', 'deepseek-chat', 'deepseek-v4-flash', 'deepseek-v4-pro'])) {
|
|
|
+ // DeepSeek模型
|
|
|
+ $post_data['response_format'] = ['type' => 'json_object'];
|
|
|
+ } else if ($model === 'gpt-5.4') {
|
|
|
+ // GPT-5.4模型
|
|
|
+ $post_data['response_format'] = ['type' => 'json_object'];
|
|
|
+ } else if (in_array($model, $this->valid_text_models)) {
|
|
|
+ // 火山引擎模型
|
|
|
+ $post_data['response_format'] = ['type' => 'json_object'];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据模型类型选择调用方法(复用现有的chatOnly方法)
|
|
|
+ if (in_array($model, ['deepseek-reasoner', 'deepseek-chat', 'deepseek-v4-flash', 'deepseek-v4-pro'])) {
|
|
|
+ // DeepSeek 官方模型使用现有的 chatOnly 方法
|
|
|
+ $result = $this->chatOnly($post_data);
|
|
|
+
|
|
|
+ // 记录实际使用的模型
|
|
|
+ dLog('deepseek')->info('newGenerateText使用DeepSeek模型', ['requested_model' => $model, 'actual_model' => $model]);
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'content' => $result['fullContent'],
|
|
|
+ 'reasoning_content' => $result['fullReasoningContent'],
|
|
|
+ 'usage' => $result['usage'],
|
|
|
+ 'model' => $model,
|
|
|
+ 'finish_reason' => 'stop'
|
|
|
+ ];
|
|
|
+ } else if ($model === 'gpt-5.4') {
|
|
|
+ // GPT-5.4 模型使用现有的 gpt54ChatOnly 方法
|
|
|
+ $result = $this->gpt54ChatOnly($post_data);
|
|
|
+
|
|
|
+ dLog('deepseek')->info('newGenerateText使用GPT-5.4模型', ['requested_model' => $model]);
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'content' => $result['fullContent'],
|
|
|
+ 'reasoning_content' => '',
|
|
|
+ 'usage' => $result['usage'],
|
|
|
+ 'model' => $model,
|
|
|
+ 'finish_reason' => 'stop'
|
|
|
+ ];
|
|
|
+ } else if (in_array($model, $this->valid_text_models)) {
|
|
|
+ // 火山引擎支持的模型使用火山引擎 API(非流式)
|
|
|
+ $post_data['stream'] = false;
|
|
|
+ $result = $this->volcEngineChatCompletion($post_data);
|
|
|
+
|
|
|
+ dLog('deepseek')->info('newGenerateText使用火山引擎模型', [
|
|
|
+ 'requested_model' => $model,
|
|
|
+ 'result_keys' => array_keys($result),
|
|
|
+ 'has_fullContent' => isset($result['fullContent']),
|
|
|
+ 'has_content' => isset($result['content'])
|
|
|
+ ]);
|
|
|
+
|
|
|
+ // 火山引擎返回的数据结构使用 fullContent 和 fullReasoningContent
|
|
|
+ return [
|
|
|
+ 'content' => $result['fullContent'] ?? $result['content'] ?? '',
|
|
|
+ 'reasoning_content' => $result['fullReasoningContent'] ?? $result['reasoning_content'] ?? '',
|
|
|
+ 'usage' => $result['usage'] ?? [],
|
|
|
+ 'model' => $model,
|
|
|
+ 'finish_reason' => $result['finish_reason'] ?? 'stop'
|
|
|
+ ];
|
|
|
+ } else {
|
|
|
+ Utils::throwError('20003:不支持的模型: ' . $model);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
* 处理图片转换为base64
|
|
|
*
|
|
|
* @param mixed $image 图片文件对象或文件路径
|