使用 FastGPT 实现最佳 AI 翻译工作流:全世界最信达雅的翻译

想让AI翻译既准确又地道?本文将教你如何利用 FastGPT 打造一个革命性的翻译工作流。

不仅支持文本翻译,还能直接处理文档,更能通过自定义术语表确保专业术语的翻译准确性,堪称翻译神器!

直接看效果:

再来看术语表:

这也太适合翻译产品官网和官方文档了吧??

背景

吴恩达教授最近提出了一个创新的大语言模型(LLM)翻译方案 —— translation-agent,这个方案的独特之处在于引入了"自我反思"机制,具体工作流程如下:

  1. LLM 将原文从源语言翻译为目标语言;
  2. LLM 对翻译结果进行自我反思,提出改进建议;
  3. 根据这些建议优化翻译结果。

这个 AI 翻译流程是目前比较新的一种翻译方式,利用 LLM 对自己的翻译结果进行改进来获得较好的 AI 翻译效果。

项目中展示了可以利用对长文本进行分片,然后分别进行反思翻译处理,以突破 LLM 对 tokens 数量的限制,真正实现长文本一键高效率高质量翻译。

该项目还通过给大模型限定国家地区,已实现更精确的 AI 翻译,如美式英语、英式英语之分;同时提出一些可能能带来更好效果的优化,如对于一些 LLM 未曾训练到的术语 (或有多种翻译方式的术语) 建立术语表,进一步提升翻译的精确度等等。

而这一切都能通过 FastGPT 工作流轻松实现,本文将手把手教你如何使用 FastGPT 复刻吴恩达老师的 translation-agent。

而且 FastGPT 的工作流还更进一步,既可以直接输入文本进行翻译,也可以上传文档进行翻译

话不多说,拿起键盘,开始教学 ~

整个工作流大概分为三个板块:

  1. 判断是否上传了文档
  2. 对文本进行切分,使切分出来的文本不超出 LLM tokens 数量限制
  3. 依次按顺序对每个切分出来的文本进行翻译

FastGPT 国内版地址:https://fastgpt.cn
FastGPT 海外版地址:https://tryfastgpt.ai

判断是否上传文档

首先第一步,在【系统配置】中添加两个全局变量用来表示源语言和目标语言,同时需要开启文件上传功能。

接下来进入正式的工作流流程,先判断一下源语言与目标语言是否相同,如果是一样的,那就没必要翻译了!你让我把中文翻译成中文?开什么玩笑,Token 不要钱的啦?

如果源语言和目标语言不同,就继续往下走,判断是否上传了文档,如果上传了文档,就将文档里的内容解析出来。

OK,进入下一个流程。

切分长文本

接下来我们可以通过分片和循环,实现对长文本也即多文本块的反思翻译。

整体的逻辑是,首先对传入文本的 tokens 数量做判断,如果不超过设置的 tokens 限制,那么直接调用单文本块反思翻译,如果超过设置的 tokens 限制,那么切割为合理的大小,再分别进行对应的反思翻译处理。

至于为什么要切割分块,有两个原因:

  1. 大模型输出上下文只有 4k,无法输出超过 4k token 内容的文字。
  2. 输入分块可以减少太长的输入导致的幻觉。

下面我们接着看工作流。

不管是否上传了文档,最终都要把文本提取出来。如果直接输入了文本,那么直接将文本传递给下一个节点;如果上传了文档,那么解析完文档之后再将解析出来的文本传递给下一个节点。

下一个节点要干的事情就是对文本进行切分。

这里我们用到了 Jina AI 开源的一个强大的正则表达式,它能利用所有可能的边界线索和启发式方法来精确切分文本,来看看代码:

const MAX_HEADING_LENGTH = 7; // 最大标题长度
const MAX_HEADING_CONTENT_LENGTH = 200; // 最大标题内容长度
const MAX_HEADING_UNDERLINE_LENGTH = 200; // 最大标题下划线长度
const MAX_HTML_HEADING_ATTRIBUTES_LENGTH = 100; // 最大HTML标题属性长度
const MAX_LIST_ITEM_LENGTH = 200; // 最大列表项长度
const MAX_NESTED_LIST_ITEMS = 6; // 最大嵌套列表项数
const MAX_LIST_INDENT_SPACES = 7; // 最大列表缩进空格数
const MAX_BLOCKQUOTE_LINE_LENGTH = 200; // 最大块引用行长度
const MAX_BLOCKQUOTE_LINES = 15; // 最大块引用行数
const MAX_CODE_BLOCK_LENGTH = 1500; // 最大代码块长度
const MAX_CODE_LANGUAGE_LENGTH = 20; // 最大代码语言长度
const MAX_INDENTED_CODE_LINES = 20; // 最大缩进代码行数
const MAX_TABLE_CELL_LENGTH = 200; // 最大表格单元格长度
const MAX_TABLE_ROWS = 20; // 最大表格行数
const MAX_HTML_TABLE_LENGTH = 2000; // 最大HTML表格长度
const MIN_HORIZONTAL_RULE_LENGTH = 3; // 最小水平分隔线长度
const MAX_SENTENCE_LENGTH = 400; // 最大句子长度
const MAX_QUOTED_TEXT_LENGTH = 300; // 最大引用文本长度
const MAX_PARENTHETICAL_CONTENT_LENGTH = 200; // 最大括号内容长度
const MAX_NESTED_PARENTHESES = 5; // 最大嵌套括号数
const MAX_MATH_INLINE_LENGTH = 100; // 最大行内数学公式长度
const MAX_MATH_BLOCK_LENGTH = 500; // 最大数学公式块长度
const MAX_PARAGRAPH_LENGTH = 1000; // 最大段落长度
const MAX_STANDALONE_LINE_LENGTH = 800; // 最大独立行长度
const MAX_HTML_TAG_ATTRIBUTES_LENGTH = 100; // 最大HTML标签属性长度
const MAX_HTML_TAG_CONTENT_LENGTH = 1000; // 最大HTML标签内容长度
const LOOKAHEAD_RANGE = 100;  // 向前查找句子边界的字符数

const AVOID_AT_START = `[\\s\\]})>,']`; // 避免在开头匹配的字符
const PUNCTUATION = `[.!?…]|\\.{3}|[\\u2026\\u2047-\\u2049]|[\\p{Emoji_Presentation}\\p{Extended_Pictographic}]`; // 标点符号
const QUOTE_END = `(?:'(?=\`)|''(?=\`\`))`; // 引号结束
const SENTENCE_END = `(?:${PUNCTUATION}(?<!${AVOID_AT_START}(?=${PUNCTUATION}))|${QUOTE_END})(?=\\S|$)`; // 句子结束
const SENTENCE_BOUNDARY = `(?:${SENTENCE_END}|(?=[\\r\\n]|$))`; // 句子边界
const LOOKAHEAD_PATTERN = `(?:(?!${SENTENCE_END}).){1,${LOOKAHEAD_RANGE}}${SENTENCE_END}`; // 向前查找句子结束的模式
const NOT_PUNCTUATION_SPACE = `(?!${PUNCTUATION}\\s)`; // 非标点符号空格
const SENTENCE_PATTERN = `${NOT_PUNCTUATION_SPACE}(?:[^\\r\\n]{1,{MAX_LENGTH}}${SENTENCE_BOUNDARY}|[^\\r\\n]{1,{MAX_LENGTH}}(?=${PUNCTUATION}|${QUOTE_END})(?:${LOOKAHEAD_PATTERN})?)${AVOID_AT_START}*`; // 句子模式

const regex = new RegExp(
  "(" +
  // 1. Headings (Setext-style, Markdown, and HTML-style, with length constraints)
  `(?:^(?:[#*=-]{1,${MAX_HEADING_LENGTH}}|\\w[^\\r\\n]{0,${MAX_HEADING_CONTENT_LENGTH}}\\r?\\n[-=]{2,${MAX_HEADING_UNDERLINE_LENGTH}}|<h[1-6][^>]{0,${MAX_HTML_HEADING_ATTRIBUTES_LENGTH}}>)[^\\r\\n]{1,${MAX_HEADING_CONTENT_LENGTH}}(?:</h[1-6]>)?(?:\\r?\\n|$))` +
  "|" +
  // New pattern for citations
  `(?:\\[[0-9]+\\][^\\r\\n]{1,${MAX_STANDALONE_LINE_LENGTH}})` +
  "|" +
  // 2. List items (bulleted, numbered, lettered, or task lists, including nested, up to three levels, with length constraints)
  `(?:(?:^|\\r?\\n)[ \\t]{0,3}(?:[-*+•]|\\d{1,3}\\.\\w\\.|\\[[ xX]\\])[ \\t]+${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_LIST_ITEM_LENGTH))}` +
  `(?:(?:\\r?\\n[ \\t]{2,5}(?:[-*+•]|\\d{1,3}\\.\\w\\.|\\[[ xX]\\])[ \\t]+${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_LIST_ITEM_LENGTH))}){0,${MAX_NESTED_LIST_ITEMS}}` +
  `(?:\\r?\\n[ \\t]{4,${MAX_LIST_INDENT_SPACES}}(?:[-*+•]|\\d{1,3}\\.\\w\\.|\\[[ xX]\\])[ \\t]+${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_LIST_ITEM_LENGTH))}){0,${MAX_NESTED_LIST_ITEMS}})?)` +
  "|" +
  // 3. Block quotes (including nested quotes and citations, up to three levels, with length constraints)
  `(?:(?:^>(?:>|\\s{2,}){0,2}${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_BLOCKQUOTE_LINE_LENGTH))}\\r?\\n?){1,${MAX_BLOCKQUOTE_LINES}})` +
  "|" +
  // 4. Code blocks (fenced, indented, or HTML pre/code tags, with length constraints)
  `(?:(?:^|\\r?\\n)(?:\`\`\`|~~~)(?:\\w{0,${MAX_CODE_LANGUAGE_LENGTH}})?\\r?\\n[\\s\\S]{0,${MAX_CODE_BLOCK_LENGTH}}?(?:\`\`\`|~~~)\\r?\\n?` +
  `|(?:(?:^|\\r?\\n)(?: {4}|\\t)[^\\r\\n]{0,${MAX_LIST_ITEM_LENGTH}}(?:\\r?\\n(?: {4}|\\t)[^\\r\\n]{0,${MAX_LIST_ITEM_LENGTH}}){0,${MAX_INDENTED_CODE_LINES}}\\r?\\n?)` +
  `|(?:<pre>(?:<code>)?[\\s\\S]{0,${MAX_CODE_BLOCK_LENGTH}}?(?:</code>)?</pre>))` +
  "|" +
  // 5. Tables (Markdown, grid tables, and HTML tables, with length constraints)
  `(?:(?:^|\\r?\\n)(?:\\|[^\\r\\n]{0,${MAX_TABLE_CELL_LENGTH}}\\|(?:\\r?\\n\\|[-:]{1,${MAX_TABLE_CELL_LENGTH}}\\|){0,1}(?:\\r?\\n\\|[^\\r\\n]{0,${MAX_TABLE_CELL_LENGTH}}\\|){0,${MAX_TABLE_ROWS}}` +
  `|<table>[\\s\\S]{0,${MAX_HTML_TABLE_LENGTH}}?</table>))` +
  "|" +
  // 6. Horizontal rules (Markdown and HTML hr tag)
  `(?:^(?:[-*_]){${MIN_HORIZONTAL_RULE_LENGTH},}\\s*$|<hr\\s*/?>)` +
  "|" +
  // 10. Standalone lines or phrases (including single-line blocks and HTML elements, with length constraints)
  `(?!${AVOID_AT_START})(?:^(?:<[a-zA-Z][^>]{0,${MAX_HTML_TAG_ATTRIBUTES_LENGTH}}>)?${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_STANDALONE_LINE_LENGTH))}(?:</[a-zA-Z]+>)?(?:\\r?\\n|$))` +
  "|" +
  // 7. Sentences or phrases ending with punctuation (including ellipsis and Unicode punctuation)
  `(?!${AVOID_AT_START})${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_SENTENCE_LENGTH))}` +
  "|" +
  // 8. Quoted text, parenthetical phrases, or bracketed content (with length constraints)
  "(?:" +
  `(?<!\\w)\"\"\"[^\"]{0,${MAX_QUOTED_TEXT_LENGTH}}\"\"\"(?!\\w)` +
  `|(?<!\\w)(?:['\"\`'"])[^\\r\\n]{0,${MAX_QUOTED_TEXT_LENGTH}}\\1(?!\\w)` +
  `|(?<!\\w)\`[^\\r\\n]{0,${MAX_QUOTED_TEXT_LENGTH}}'(?!\\w)` +
  `|(?<!\\w)\`\`[^\\r\\n]{0,${MAX_QUOTED_TEXT_LENGTH}}''(?!\\w)` +
  `|\\([^\\r\\n()]{0,${MAX_PARENTHETICAL_CONTENT_LENGTH}}(?:\\([^\\r\\n()]{0,${MAX_PARENTHETICAL_CONTENT_LENGTH}}\\)[^\\r\\n()]{0,${MAX_PARENTHETICAL_CONTENT_LENGTH}}){0,${MAX_NESTED_PARENTHESES}}\\)` +
  `|\\[[^\\r\\n\\[\\]]{0,${MAX_PARENTHETICAL_CONTENT_LENGTH}}(?:\\[[^\\r\\n\\[\\]]{0,${MAX_PARENTHETICAL_CONTENT_LENGTH}}\\][^\\r\\n\\[\\]]{0,${MAX_PARENTHETICAL_CONTENT_LENGTH}}){0,${MAX_NESTED_PARENTHESES}}\\]` +
  `|\\$[^\\r\\n$]{0,${MAX_MATH_INLINE_LENGTH}}\\$` +
  `|\`[^\`\\r\\n]{0,${MAX_MATH_INLINE_LENGTH}}\`` +
  ")" +
  "|" +
  // 9. Paragraphs (with length constraints)
  `(?!${AVOID_AT_START})(?:(?:^|\\r?\\n\\r?\\n)(?:<p>)?${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_PARAGRAPH_LENGTH))}(?:</p>)?(?=\\r?\\n\\r?\\n|$))` +
  "|" +
  // 11. HTML-like tags and their content (including self-closing tags and attributes, with length constraints)
  `(?:<[a-zA-Z][^>]{0,${MAX_HTML_TAG_ATTRIBUTES_LENGTH}}(?:>[\\s\\S]{0,${MAX_HTML_TAG_CONTENT_LENGTH}}?</[a-zA-Z]+>|\\s*/>))` +
  "|" +
  // 12. LaTeX-style math expressions (inline and block, with length constraints)
  `(?:(?:\\$\\$[\\s\\S]{0,${MAX_MATH_BLOCK_LENGTH}}?\\$\\$)|(?:\\$[^\\$\\r\\n]{0,${MAX_MATH_INLINE_LENGTH}}\\$))` +
  "|" +
  // 14. Fallback for any remaining content (with length constraints)
  `(?!${AVOID_AT_START})${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_STANDALONE_LINE_LENGTH))}` +
  ")",
  "gmu"
);

function main({text}){
  const chunks = [];
  let currentChunk = '';
  const tokens = countToken(text)

  const matches = text.match(regex);
  if (matches) {
    matches.forEach((match) => {
      if (currentChunk.length + match.length <= 1000) {
        currentChunk += match;
      } else {
        if (currentChunk) {
          chunks.push(currentChunk);
        }
        currentChunk = match;
      }
    });
    if (currentChunk) {
      chunks.push(currentChunk);
    }
  }

  return {chunks, tokens};
}

算了别看了,你直接用就好啦。。

格式化文本

切分完文本之后,开始对源文本进行格式化:

格式化代码如下:

function main({source_text_chunks, source_doc_text_chunks, i=0}){
    let chunks = source_doc_text_chunks || source_text_chunks;
    let before = chunks.slice(0, i).join("");
    let current = " <TRANSLATE_THIS>" + chunks[i] + "</TRANSLATE_THIS>";
    let after = chunks.slice(i + 1).join("");
    let tagged_text = before + current + after;

    return {
        tagged_text,
        chunk_to_translate: chunks[i],
    }
}

最终会输出两个变量,其中 tagged_text 包含了整个文本,而 chunk_to_translate 只包含了本轮需要翻译的文本块。

接入术语表

在正式翻译之前,我们还可以将专有名词的词库作为知识库,在翻译前进行搜索。

记得要开启问题优化哦。

开始翻译

现在我们终于进入了翻译环节,这里我们使用 CoT 思维链,让 LLM 显式地、系统地生成推理链条,展示翻译的完整思考过程。

系统提示词如下:

# Role: 资深翻译专家

## Background:
你是一位经验丰富的翻译专家,精通{{source_lang}}和{{target_lang}}互译,尤其擅长将{{source_lang}}文章译成流畅易懂的{{target_lang}}。你曾多次带领团队完成大型翻译项目,译文广受好评。

## Attention:
- 翻译过程中要始终坚持"信、达、雅"的原则,但"达"尤为重要
- 翻译的译文要符合{{target_lang}}的表达习惯,通俗易懂,连贯流畅
- 避免使用过于文绉绉的表达和晦涩难懂的典故引用 
- 诗词歌词等内容需按原文换行和节奏分行,不破坏原排列格式 
- 对于专有的名词或术语,按照给出的术语表进行合理替换 
- 在翻译过程中,注意保留文档原有的列表项和格式标识
- 不要翻译代码块中的内容,保持原样输出

## Constraints:
- 必须严格遵循四轮翻译流程:直译、意译、反思、提升
- 译文要忠实原文,准确无误,不能遗漏或曲解原意
- 注意判断上下文,避免重复翻译
- 最终译文使用Markdown的代码块呈现,但是不用输出markdown这个单词

## Goals:
- 通过四轮翻译流程,将{{source_lang}}原文译成高质量的{{target_lang}}译文  
- 译文要准确传达原文意思,语言表达力求浅显易懂,朗朗上口
- 适度使用一些熟语俗语、流行网络用语等,增强译文的亲和力

## Skills:
- 精通{{source_lang}} {{target_lang}}两种语言,具有扎实的语言功底和丰富的翻译经验
- 擅长将{{source_lang}}表达习惯转换为地道自然的{{target_lang}}
- 对当代{{target_lang}}语言的发展变化有敏锐洞察,善于把握语言流行趋势

## Workflow:
1. 第一轮直译:逐字逐句忠实原文,不遗漏任何信息(代码块内容除外)
2. 第二轮意译:在直译的基础上用通俗流畅的{{target_lang}}意译原文(代码块内容除外)
3. 第三轮反思:仔细审视译文,分点列出一份建设性的批评和有用的建议清单以改进翻译,逐句提出建议,从以下6个角度展开
    (i) 准确性(纠正冗余、误译、遗漏或未翻译的文本错误),
    (ii) 流畅性(应用{{target_lang}}的语法、拼写和标点规则,并确保没有不必要的重复),
    (iii) 风格(确保翻译反映源文本的风格并考虑其文化背景),
    (iv) 术语(严格参考给出的术语表,确保术语使用一致)
    (v) 语序(合理调整语序,不要生搬{{source_lang}}中的语序,注意调整为{{target_lang}}中的合理语序)
    (vi) 代码保护(确保所有代码块内容保持原样,不被翻译)
4. 第四轮提升:严格遵循第三轮提出的建议对翻译修改,定稿出一个简洁畅达、符合大众阅读习惯的译文

## OutputFormat:
- 每一轮前用【思考】说明该轮要点
- 第一轮和第二轮翻译后用【翻译】呈现译文
- 第三轮用【建议】输出建议清单,分点列出,在每一点前用*xxx*标识这条建议对应的要点,如*风格*;建议前用【思考】说明该轮要点,建议后用【建议】呈现建议
- 第四轮在\`\`\`代码块中展示最终译文内容,如\`\`\`xxx\`\`\`,不用输出markdown这个单词

## Suggestions:
- 直译时力求忠实原文,但不要过于拘泥逐字逐句
- 意译时在准确表达原意的基础上,用最朴实无华的{{target_lang}}来表达
- 反思环节重点关注译文是否符合{{target_lang}}表达习惯,是否通俗易懂,是否准确流畅,是否术语一致
- 提升环节采用反思环节的建议对意译环节的翻译进行修改,适度采用一些口语化的表达、网络流行语等,增强译文的亲和力
- 所有包含在代码块(\`\`\`)中的内容都应保持原样,不进行翻译

用户问题如下:

同时不要忘了接入词库。

还需要在 AI 模型配置中关闭【返回 AI 内容】。

这里 AI 会进行好几轮翻译,但是我们只需要最终的翻译结果,所以还需要继续接入【代码运行】节点,将最后一轮的翻译结果提取出来。

代码如下:

function main({data1}){
    const result = data1.split("```").filter(item => !!item.trim())

    if(result[result.length-1]) {
        return {
            result: result[result.length-1]
        }
    }

    return {
        result: '未截取到翻译内容'
    }
}

到这里翻译基本上就结束了,但是有些模型在输出中文内容时,会夹杂英文标点符号,所以为了以防万一,我们可以再加一个【代码运行】节点来对输出内容进行格式化。

代码如下:

function main({target_lang, source_text}) {
  let text = source_text;

  if (target_lang === '简体中文' || target_lang === '繁體中文') {
    // 存储代码块内容
    const codeBlocks = [];
    let text = source_text.replace(/```[\s\S]*?```/g, (match) => {
      codeBlocks.push(match);
      return `__CODE_BLOCK_${codeBlocks.length - 1}__`;
    });

    // 替换成对的英文引号
    text = text.replace(/"(.*?)"/g, '“$1”');

    // 保护 Markdown 链接格式中的括号
    text = text.replace(/\[(.*?)\]\((.*?)\)/g, function(match) {
      return match.replace(/\(/g, 'LEFTPAREN')
                 .replace(/\)/g, 'RIGHTPAREN');
    });

    // 替换成对的英文括号
    text = text.replace(/\((.*?)\)/g, '($1)');

    // 恢复被保护的 Markdown 链接括号
    text = text.replace(/LEFTPAREN/g, '(')
             .replace(/RIGHTPAREN/g, ')');

    // 更新句号替换逻辑,增加对版本号和URL的保护
    text = text.replace(/(\d+)\.(\d+)\.(\d+)/g, '$1DOT$2DOT$3') // 保护版本号 (如 16.2.1)
               .replace(/(\d)\.(\d)/g, '$1DOT$2') // 临时替换小数点
               .replace(/([a-zA-Z])\.([a-zA-Z])/g, '$1DOT$2') // 临时替换缩写中的句号
               .replace(/([a-zA-Z])\.(\d)/g, '$1DOT$2') // 临时替换字母与数字之间的句号
               .replace(/(\d)\.([a-zA-Z])/g, '$1DOT$2') // 临时替换数字与字母之间的句号
               .replace(/([a-zA-Z])\./g, '$1DOT') // 临时替换字母后面的句号(如 a.)
               .replace(/https?:/g, 'HTTPCOLON') // 保护 URL 中的冒号
               .replace(/\./g, '。') // 替换其他句号
               .replace(/DOT/g, '.') // 恢复被保护的句号
               .replace(/HTTPCOLON/g, 'http:'); // 恢复 URL 中的冒号

    // 替换英文逗号,但不替换数字中的逗号
    text = text.replace(/(\d),(\d)/g, '$1COMMA$2') // 临时替换数字中的逗号
               .replace(/,/g, ',') // 替换其他逗号
               .replace(/COMMA/g, ','); // 恢复数字中的逗号

    // 替换其他常见符号
    const replacements = {
      '!': '!',
      '?': '?',
      ';': ';',
    };

    for (const [key, value] of Object.entries(replacements)) {
      text = text.replace(new RegExp(`\\${key}`, 'g'), value);
    }

    // 在中文和英文字符之间添加空格
    // 中文字符范围: \u4e00-\u9fa5
    // 英文字符范围: a-zA-Z0-9
    text = text.replace(/([\u4e00-\u9fa5])([a-zA-Z0-9])/g, '$1 $2')
               .replace(/([a-zA-Z0-9])([\u4e00-\u9fa5])/g, '$1 $2');

    // 恢复代码块
    text = text.replace(/__CODE_BLOCK_(\d+)__/g, (_, index) => codeBlocks[index]);
  }

  return {
    text
  };
}

循环

单文本块翻译完成后,需要判断一下所有的文本是否都翻译完成了,如果还没翻译完,就回到循环的起点,按照之前的流程继续翻译。

代码如下:

function main({chunks, doc_chunks, currentChunk}){
    let new_chunks = doc_chunks || chunks
    const findIndex = new_chunks.findIndex((item) => item ===currentChunk)
    
    return {
        isEnd: new_chunks.length-1 === findIndex,
        i: findIndex + 1,
    }
}

如果翻译完成了,就直接回复翻译完成,整个工作流结束。

效果演示

咱们先来导入一个词库。

下载链接:https://images.tryfastgpt.ai/vocabulary.csv

导入方式很简单,在 FastGPT 中新建一个通用知识库:

然后导入表格数据集:

然后上传你的 csv 数据集,一路下一步,最后就得到了处理完成的词库。

点进去可以看到详情:

导入完成后,就可以在工作流中选择该词库了。

最终我们来测试一下翻译效果:

点击聊天框左侧的回形针图标上传附件,然后选择需要上传的文档。

测试文档地址:https://images.tryfastgpt.ai/Sealos-Devbox-quick-start.pdf

上传文档后,点击右边的发送按钮开始翻译。

术语翻译的一致性保持的非常完美:

总结

好啦!到这里我们就完整复刻了吴恩达老师的 translation-agent,而且通过 FastGPT 工作流的能力,我们不仅实现了文本翻译,还支持了文档翻译功能。整个翻译过程不仅准确,而且通过术语表的加持,专业术语的翻译也能保持高度一致性。

相信有了这个工具,你的翻译工作效率一定能上一个台阶!

如果你连一点点代码都不想写,那也没问题,只需要导入我分享的工作流就可以了。

工作流导入方式:将鼠标指针悬停在新建的工作流左上方标题处,然后点击【导入配置】

完整工作流:https://pan.quark.cn/s/019132869eca

最后,如果你觉得这篇教程对你有帮助,欢迎分享给更多需要的朋友。当然,如果你在使用过程中遇到任何问题,也欢迎随时反馈交流。让我们一起把 AI 翻译变得更好!

Happy translating! ????

上一篇:基于C语言实现的TCP客户端