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

feat: 巴译中工作流新增 evaluate 质量评估步骤

将原 qc 步骤重命名为 evaluate(质量评估),作为工作流终审独立运行:
对照原文找出译文真实问题,按 fatal/error/warning/suggestion 分级,
就地用 HTML span 标注译文。

- 后端 STEPS、方法、提示词、日志、标注类名 qc-* → evaluate-*
- 命令 --steps 选项说明同步更新
- 前端 article.css 标注样式 .qc-* → .evaluate-*
- TypePali 引入 article.css 并加 pcd_article 容器类以应用标注样式
- 新增 documents/translation-evaluate.md 记录问题分级标准

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
visuddhinanda 3 дней назад
Родитель
Сommit
2ae12687a7

+ 2 - 2
api-v13/app/Console/Commands/UpgradeAITranslation.php

@@ -34,7 +34,7 @@ class UpgradeAITranslation extends Command
     {--resume}
     {--model=}
     {--thinking= : 开启和关闭deepseek thinking true | false}
-    {--steps=translate : translation 工作流步骤,逗号分隔,可选 translate,review,revise}
+    {--steps=translate : translation 工作流步骤,逗号分隔,可选 translate,review,revise,evaluate(evaluate 为质量评估,须放最后)}
     {--fresh : 清除缓存断点,从头开始}';
 
     // 缓存键前缀:以 type、channel 区分,记录已完成的 "book|para" 集合,中断后重跑自动跳过
@@ -278,7 +278,7 @@ class UpgradeAITranslation extends Command
             ->get();
         $result = [];
         foreach ($sentences as $key => $sentence) {
-            if (!empty($sentence->content)) {
+            if (! empty($sentence->content)) {
                 $id = "{$sentence->book_id}-{$sentence->paragraph}-{$sentence->word_start}-{$sentence->word_end}";
 
                 $aiNissaya = $this->nissayaTranslateService

+ 88 - 6
api-v13/app/Services/AIAssistant/PaliTranslateService.php

@@ -13,19 +13,20 @@ use Illuminate\Support\Facades\Log;
 /**
  * 巴利原文 -> 简体中文 的多步骤翻译工作流。
  *
- * 支持个步骤,可单独运行或按顺序串联:
+ * 支持个步骤,可单独运行或按顺序串联:
  * - translate:根据巴利原文产出译文
  * - review:对已有译文打分并给出问题清单(不修改译文)
  * - revise:根据 review 的问题清单产出改进后的译文
+ * - evaluate:质量评估,对照原文找出译文的真实问题,按级别就地用 HTML span 标注译文(颜色=严重程度,title=问题+建议),作为工作流最后一步
  *
- * 单独运行 review / revise 时,已有译文从输出 channel 读取。
+ * 单独运行 review / revise / evaluate 时,已有译文从输出 channel 读取。
  */
 class PaliTranslateService
 {
     /**
      * 可用的工作流步骤
      */
-    public const STEPS = ['translate', 'review', 'revise'];
+    public const STEPS = ['translate', 'review', 'revise', 'evaluate'];
 
     protected AiModelResource $model;
 
@@ -121,6 +122,54 @@ class PaliTranslateService
         {"id":"2-3-4-5","content":"译文"}
         md;
 
+    /**
+     * evaluate 步骤的提示词:对照原文找出译文真实问题,按级别就地标注译文。
+     */
+    protected string $evaluatePrompt = <<<'md'
+        你是一位资深的巴利语译文质量检查员,精通巴利原典与注释书传统。
+        用户会提供巴利原文(pali)与对应的简体中文译文(translation),两者均为 json,通过 id 一一对应。
+
+        你的任务:逐句对照原文审查译文,找出其中**确实存在**的翻译问题,按严重程度分级,并把问题**就地标注在译文上**,最后输出标注后的译文。
+
+        # 问题分级(严重程度由高到低)
+        - fatal(严重错误):会让读者产生邪见或重大误解。例如主语、谓语、宾语等句子核心成分判断错误,或句意违背基本教理。
+        - error(错误,必须修改):漏译;衍译(多出原文没有的内容);词义或修饰关系译错;造成误解的表达;义理或用词与注释书不符;代词指代错误。
+        - warning(待提升):关键词有歧义却未加注释;代词指代不清;不致误解的汉语语病;标点误用;术语标记使用不当;整句逻辑表达不规范。
+        - suggestion(可提升):语言不够流畅;风格不统一;该用术语标记而未用;嵌套长句对读者不友好等仅影响阅读体验的问题。
+
+        若同一片段存在多重问题,只按其中最严重的一级标注。
+
+        # 标注方法
+        只对译文中**有问题的最小片段**,用如下 span 原地包裹(不改动译文本身的文字与黑体等格式,仅在外层套标签):
+
+        <span class="evaluate-级别" style="background:颜色" title="类别·级别:问题简述|建议:修改建议">有问题的译文片段</span>
+
+        级别与背景颜色对应(越暖代表越严重):
+        - fatal      颜色 #ffcdd2
+        - error      颜色 #ffe0b2
+        - warning    颜色 #fff9c4
+        - suggestion 颜色 #c8e6c9
+
+        title 写法:先写问题类别与级别,再用一句话说清问题是什么,最后给出具体可操作的修改建议,用「|建议:」分隔。
+
+        # 必须遵守的原则
+        1. 只标注你**确有把握**的真实问题;拿不准就不标。
+        2. 宁缺毋滥:不要为凑数而标注,不要把正确译文误标为问题,严禁过度标注。没有问题的句子,content 与原译文**一字不差**地原样返回,不加任何标签。
+        3. 标注片段尽量短,精准定位到出问题的词或短语,不要整句包裹。
+        4. 完整保留译文原有的文字与格式(黑体 ** **、全角标点等)。
+
+        # 输出格式 jsonl
+        每行对应一个句子,包含两个字段:
+        id:与原文相同的句子 id
+        content:标注后的译文(无问题则与原译文完全一致)
+
+        直接输出 jsonl 数据,无需解释。
+
+        **输出范例**
+        {"id":"1-2-3-4","content":"他于<span class=\"evaluate-error\" style=\"background:#ffe0b2\" title=\"漏译·error:原文 bhagavā 未译出|建议:补译为‘世尊’\">那时</span>住在王舍城。"}
+        {"id":"2-3-4-5","content":"完全正确的译文原样返回。"}
+        md;
+
     public function __construct(
         protected OpenAIService $openAIService,
         protected SearchPaliDataService $searchPaliDataService,
@@ -201,6 +250,16 @@ class PaliTranslateService
         return $this;
     }
 
+    /**
+     * 设置 evaluate 步骤的提示词
+     */
+    public function setEvaluatePrompt(string $prompt): self
+    {
+        $this->evaluatePrompt = $prompt;
+
+        return $this;
+    }
+
     /**
      * 执行多步骤工作流,返回最终译文(list of ['id' => ..., 'content' => ...])。
      *
@@ -236,12 +295,15 @@ class PaliTranslateService
                 case 'revise':
                     $translation = $this->revise($pali, $translation, $review);
                     break;
+                case 'evaluate':
+                    $translation = $this->evaluate($pali, $translation);
+                    break;
             }
         }
 
-        // 只有产出译文的步骤(translate / revise)才返回可写库的数据;
-        // 仅 review 时 review 报告已写入日志,无需重新保存原译文
-        $producesTranslation = (bool) array_intersect($steps, ['translate', 'revise']);
+        // 只有产出译文的步骤(translate / revise / evaluate)才返回可写库的数据;
+        // evaluate 写库内容为带 HTML 标注的译文;仅 review 时报告已写入日志,无需重新保存原译文
+        $producesTranslation = (bool) array_intersect($steps, ['translate', 'revise', 'evaluate']);
 
         return $producesTranslation ? $translation : [];
     }
@@ -324,6 +386,26 @@ class PaliTranslateService
         return LlmResponseParser::jsonl($content);
     }
 
+    /**
+     * evaluate 步骤:对照原文找出译文真实问题,按级别就地用 HTML span 标注译文。
+     * 返回标注后的译文(无问题的句子原样返回),作为工作流最后一步写库。
+     *
+     * @param  array<int, array{id: string, content: string}>  $pali
+     * @param  array<int, array{id: string, content: string}>  $translation
+     * @return array<int, array{id: string, content: string}>
+     */
+    public function evaluate(array $pali, array $translation): array
+    {
+        $userText = "# pali\n\n".$this->jsonBlock($pali)."\n\n"
+            ."# translation\n\n".$this->jsonBlock($translation)."\n\n";
+        Log::debug('PaliTranslate: evaluate', ['input' => $userText]);
+
+        $content = $this->send($this->evaluatePrompt, $userText);
+        Log::debug('PaliTranslate: evaluate', ['output' => $content]);
+
+        return LlmResponseParser::jsonl($content);
+    }
+
     /**
      * 从输出 channel 读取已有译文,按句子返回 ['id' => ..., 'content' => ...]
      *

+ 31 - 0
api-v13/documents/translation-evaluate.md

@@ -0,0 +1,31 @@
+# 问题分级
+
+- 第一类 严重错误 fatal(零容忍;增长普通用户邪见、降低普通用户对译文的评价)
+    1. 主谓(含非谓语动词)宾有一项判断错误
+    2. 句子意思违背基本教理原则
+
+- 第二类 错误 error(专家有举必究;只要发现,一定要改、只有巴利专家能发现)
+    1. 漏译(例句:32**两**糖块的体积)
+    2. 错误多译
+    3. 错译(词义,修饰关系)
+    4. 导致误解的表达
+    5. 义理或用词与注释书不符
+    6. 代词指代错误
+
+- 第三类 待提升 warning
+    1. 关键词语意不明确或二意场合没有注释(稣息、转起)
+    2. 代词指代不明确
+    3. 不导致误解的汉语语病
+    4. 标点符号使用错误
+    5. 不该使用术语标记时使用了术语标记
+    6. 整句逻辑表达不规范
+
+- 第四类 可提升 suggestion
+    1. 语言表达不够流畅
+    2. 代词指代可能不够明确
+    3. 语言风格不统一
+    4. 该使用而没有使用术语标记
+    5. 不常用术语编写注释或者百科
+    6. 复杂的嵌套句整句语言逻辑理解困难(对读者不友好)
+
+第三类是出版社编辑提出的,第四类是由有佛教背景的读者提出的

+ 2 - 1
dashboard-v6/src/components/article/TypePali.tsx

@@ -27,6 +27,7 @@ import { TaskBuilderChapterModal } from "../task/TaskBuilderChapterModal";
 import type { TTarget } from "../../types";
 import TocPath from "../tipitaka/TocPath";
 import ParagraphNode from "../tipitaka/ParagraphNode";
+import "./article.css";
 
 export interface ISearchParams {
   key: string;
@@ -140,7 +141,7 @@ const TypePali = ({
   };
 
   return (
-    <div>
+    <div className="pcd_article">
       <TaskBuilderChapterModal
         studioName={user?.realName}
         book={parseInt(mBook ?? "0")}

+ 13 - 0
dashboard-v6/src/components/article/article.css

@@ -171,3 +171,16 @@
 .video-js video {
   max-width: 100%;
 }
+
+.pcd_article .evaluate-fatal{
+  background-color: #ffcdd2;
+}
+.pcd_article .evaluate-error{
+  background-color: #ffe0b2;
+}
+.pcd_article .evaluate-warning{
+  background-color: #fff9c4;
+}
+.pcd_article .evaluate-suggestion{
+  background-color: #c8e6c9;
+}