Просмотр исходного кода

优化百度版gpt模型的参数,防止内容截断

lh 3 недель назад
Родитель
Сommit
d9a44d6542
1 измененных файлов с 61 добавлено и 10 удалено
  1. 61 10
      app/Services/DeepSeek/DeepSeekService.php

+ 61 - 10
app/Services/DeepSeek/DeepSeekService.php

@@ -3733,7 +3733,8 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
      * - messages:原样透传(文本 + image_url 图片内容元素均支持);
      * - 采样/输出类参数走白名单透传(temperature / top_p / max_tokens / max_completion_tokens /
      *   frequency_penalty / presence_penalty / stop / response_format / n / seed / user / prompt_cache_key);
-     * - temperature:gpt-6.1 系仅支持默认值 1,调用方传入其它值时不下发(改用上游默认值,规避 400);
+     * - 采样参数能力兜底:BO-CHAT 推理模型(gpt-5.6 / gpt-6 / gpt-6.1 等)仅支持默认采样温度 1,
+     *   且不接受 frequency_penalty / presence_penalty;传入非 1 温度或这些参数会返回 400,这里统一剔除;
      * - reasoning_effort:按模型取支持档位(gpt-6.1 系为 low / medium / high / xhigh,不支持 none;
      *   其余 BO-CHAT 模型为 none / low / medium / high),非法值不下发;
      *   gpt-6.1 系未显式指定时默认 high(等价默认开启思考,规避该系不支持 none 的报错);
@@ -3823,11 +3824,13 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
             $payload['reasoning_effort'] = $effort;
         }
 
-        // gpt-6.1 系仅支持默认采样温度(1):调用方传入非 1 的温度时不下发,
-        // 交由上游使用默认值,规避 "temperature does not support X" 的 400
-        if ($effortNoNone && isset($payload['temperature']) && (float)$payload['temperature'] !== 1.0) {
+        // 采样参数能力兜底:BO-CHAT(gpt-5.6 / gpt-6 / gpt-6.1 等)推理模型仅支持默认采样温度(1),
+        // 且不接受 frequency_penalty / presence_penalty;下发这些参数会返回 400,这里统一剔除,
+        // 交由上游使用默认值(否则会直接报错,或导致异常/不完整输出)。
+        if (isset($payload['temperature']) && (float)$payload['temperature'] !== 1.0) {
             unset($payload['temperature']);
         }
+        unset($payload['frequency_penalty'], $payload['presence_penalty']);
 
         if ($stream && !isset($payload['stream_options'])) {
             $payload['stream_options'] = ['include_usage' => true];
@@ -4016,17 +4019,18 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
                     }
                 }
 
-                // 正文为空(且不是内容审核拦截):本次调用无有效产出,只记 token 不扣积分,再按配置重试
-                if ($fullContent === '' && $finishReason !== 'content_filter') {
+                // 正文为空,或被长度上限截断(finish_reason=length):本次调用产出不可用
+                // (且不是内容审核拦截),只记 token 不扣积分,再按配置重试
+                if (($fullContent === '' || $finishReason === 'length') && $finishReason !== 'content_filter') {
                     $this->recordAttemptTokens(
                         $usage,
                         $charge_context,
                         $model,
-                        '百度BO-CHAT第' . ($attempt + 1) . '次调用未返回正文,仅记录token不扣积分'
+                        '百度BO-CHAT第' . ($attempt + 1) . '次调用未返回可用正文,仅记录token不扣积分'
                     );
 
                     if ($attempt < $maxRetries) {
-                        dLog('deepseek')->warning('百度BO-CHAT返回空内容,准备重试', [
+                        dLog('deepseek')->warning('百度BO-CHAT返回空/截断内容,准备重试', [
                             'model'         => $model,
                             'retry'         => $attempt + 1,
                             'max_retries'   => $maxRetries,
@@ -4037,7 +4041,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
                         continue;
                     }
 
-                    dLog('deepseek')->error('百度BO-CHAT返回空内容,重试次数已用完', [
+                    dLog('deepseek')->error('百度BO-CHAT返回空/截断内容,重试次数已用完', [
                         'model'       => $model,
                         'attempt'     => $attempt + 1,
                         'max_retries' => $maxRetries,
@@ -14761,6 +14765,28 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
             $originSegmentCount = count($segments);
             $user_prompt .= "- 本集原有{$originSegmentCount}个片段,必须严格生成{$originSegmentCount}个片段(##片段1 到 ##片段{$originSegmentCount}),不得多不得少。\n";
         }
+
+        // 整集模式(episode_id>0):在原提示词后追加独立的“本集改动点”模块。
+        // 该模块自身格式独立,不影响上面【分段剧本】的既有格式要求;
+        // 生成后由解析阶段抽出写入 check_points 字段,不参与分段剧本解析。
+        if ($episode_id > 0) {
+            $user_prompt .= "\n\n【本集改动点模块-必须输出】\n";
+            $user_prompt .= "- 在完整输出【分段剧本】(从“###分段剧本”开始,到最后一个“##片段N”结束)之后,必须再输出一个独立模块,严格按下列格式(两条分隔行必须原样输出,不得增删或改写任何字符):\n";
+            $user_prompt .= "#####本集改动点#####\n";
+            $user_prompt .= "1.分段X镜头Y:<标题>\n";
+            $user_prompt .= "· 原描述:<原文或忠实概括>。\n";
+            $user_prompt .= "· 修改为:<新稿原文或忠实概括>。\n";
+            $user_prompt .= "· 改动说明:<实际调整及文本可确认的影响>。\n";
+            $user_prompt .= "#####本集改动点结束#####\n";
+            $user_prompt .= "- 规则:\n";
+            $user_prompt .= "  1.“分段X”对应“##片段X”,镜头沿用实际编号;修改、新增、合并、拆分按新稿定位,删除按原稿定位并标“删除”。编号不同须注明原稿位置;无编号用可核实位置,禁止编造。\n";
+            $user_prompt .= "  2.一项一主题;连续镜头可用Y-Z,非连续位置逐一列明。只列实质改动,不单列标点、空格或不改含义的措辞。\n";
+            $user_prompt .= "  3.保留核验所需关键信息;引号内须为对应原文,概括不加引号、不增信息。不强认原稿有错或猜测创作意图。\n";
+            $user_prompt .= "  4.新增的原描述写“原稿无此内容”;删除的修改为写“已删除”;合并、拆分列明两版对应位置和关系。\n";
+            $user_prompt .= "- 缺原稿时,模块内仅写“无法对比:未提供【原分段剧本】。”;无实质改动时仅写“无实质性改动。”,均保留分隔行。\n";
+            $user_prompt .= "- 输出前核验差异、位置、引用及对应关系,避免重复、遗漏或虚构。仅输出剧本正文与改动点模块,不输出分析、前言或总结。\n";
+            $user_prompt .= "- 除【分段剧本】与该【本集改动点】模块外,不得输出任何其它内容;两者缺一视为输出无效。\n";
+        }
         
         $system_prompt = "\n\n【输出格式铁律-最高优先级】\n";
         if ($isActIdMode) {
@@ -14778,6 +14804,9 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
         // $system_prompt .= "- 请严格参考【本集原始内容】或【原分段剧本内容】进行本次生成,注意新生成的分段剧本内容一定不要跟【原分段剧本内容】一致,要进行一定程度调整\n";
         $system_prompt .= "- 【重要】必须参考【本集原始内容】与【原分段剧本内容】中的剧情脉络、角色、场景、道具和分段结构,但严禁原样复制或几乎照抄原文;必须围绕【用户要求】对相关内容进行实质性修改,若生成结果与【原分段剧本内容】完全一致或未体现用户要求,视为生成失败\n";
         $system_prompt .= "- 请仅生成<分段剧本>部分,不要生成其他任何内容:\n";
+        if ($episode_id > 0) {
+            $system_prompt .= "- 【整集模式唯一例外】必须在【分段剧本】之后追加【本集改动点】模块(含固定的起止分隔行“#####本集改动点##### / #####本集改动点结束#####”,格式见用户消息的【本集改动点模块-必须输出】);除该模块外不得输出其它任何内容。\n";
+        }
         $system_prompt .= "- 第一优先级:片段时长上限(单个片段不超过15秒,超过必须切为新片段)\n";
         $system_prompt .= "- 每个分镜包含:分镜序号、出场角色、场景、出场道具、画面、运镜、配音台词、背景音效\n";
         $system_prompt .= "- 场景必须从【场景列表参考】中选择(如果提供了场景列表),场景名必须精确匹配【场景列表参考】中的名称(如需匹配“酒店会客室-景池筹备会版”,而不是“酒店会客室”,不得缺少“-”前后和“:”前面的任何内容,不能自创,不能自创){$scenes_ref}\n";
@@ -14882,6 +14911,22 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
             
             $generated_content = $fullContent;
             
+            // 整集模式:先抽出【本集改动点】模块写入 check_points,并从生成正文中剥离该模块,
+            // 避免其内容干扰后续分段剧本的解析
+            $check_points = '';
+            if ($episode_id > 0) {
+                if (preg_match('/#####\s*本集改动点\s*#####(.*?)(?:#####\s*本集改动点结束\s*#####|$)/su', $generated_content, $cpMatch)) {
+                    $check_points = trim($cpMatch[1]);
+                    $generated_content = trim(str_replace($cpMatch[0], '', $generated_content));
+                } else {
+                    dLog('deepseek')->warning('regenerateSegmentScript 未找到【本集改动点】模块', [
+                        'episode_id'     => $episode_id,
+                        'content_length' => mb_strlen($generated_content),
+                        'content_tail'   => mb_substr($generated_content, -300),
+                    ]);
+                }
+            }
+
             // 归一化"场景:"标签换行格式:场景:\n场景名\n => 场景:场景名\n,保证场景字段为一行
             $generated_content = preg_replace('/场景[::]\s*\n\s*([^\n]+)/u', '场景:$1', $generated_content);
             
@@ -15252,6 +15297,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
                         'title'           => getProp($episode, 'title', ''),
                         'episode_number'  => $target_episode_number,
                         'is_generated'    => getProp($episode, 'is_generated', 0),
+                        'check_points'    => $check_points,
                         'acts'            => $previewActs,
                     ];
                 }
@@ -15331,7 +15377,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
             }
             
             // 构造返回数据结构(参考AnimeService返回格式,但不包含products)
-            return [
+            $result = [
                 'anime_id' => $target_anime_id,
                 'episode_id' => $target_episode_id,
                 'title' => getProp($episode, 'title', ''),
@@ -15339,6 +15385,11 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
                 'is_generated' => getProp($episode, 'is_generated', 0),
                 'acts' => $saved_acts,
             ];
+            // 整集模式(episode_id>0):附带本集改动点 (从生成结果中抽出的独立模块)
+            if ($episode_id > 0) {
+                $result['check_points'] = $check_points;
+            }
+            return $result;
             
         } catch (\Exception $e) {
             dLog('deepseek')->error('重新生成分段剧本失败: ' . $e->getMessage());