想讓AI翻譯既準確又地道?本文將教你如何利用 FastGPT 打造一個革命性的翻譯工作流。
它不僅支援文字翻譯,還能直接處理文件,更能透過自定義術語表確保專業術語的翻譯準確性,堪稱翻譯神器!
直接看效果:
再來看術語表:
這也太適合翻譯產品官網和官方文件了吧??
背景
吳恩達教授最近提出了一個創新的大語言模型(LLM)翻譯方案 —— translation-agent,這個方案的獨特之處在於引入了"自我反思"機制,具體工作流程如下:
- LLM 將原文從源語言翻譯為目標語言;
- LLM 對翻譯結果進行自我反思,提出改進建議;
- 根據這些建議最佳化翻譯結果。
這個 AI 翻譯流程是目前比較新的一種翻譯方式,利用 LLM 對自己的翻譯結果進行改進來獲得較好的 AI 翻譯效果。
專案中展示了可以利用對長文字進行分片,然後分別進行反思翻譯處理,以突破 LLM 對 tokens 數量的限制,真正實現長文字一鍵高效率高質量翻譯。
該專案還透過給大模型限定國家地區,已實現更精確的 AI 翻譯,如美式英語、英式英語之分;同時提出一些可能能帶來更好效果的最佳化,如對於一些 LLM 未曾訓練到的術語 (或有多種翻譯方式的術語) 建立術語表,進一步提升翻譯的精確度等等。
而這一切都能透過 FastGPT 工作流輕鬆實現,本文將手把手教你如何使用 FastGPT 復刻吳恩達老師的 translation-agent。
而且 FastGPT 的工作流還更進一步,既可以直接輸入文字進行翻譯,也可以上傳文件進行翻譯。
話不多說,拿起鍵盤,開始教學 ~
整個工作流大概分為三個板塊:
- 判斷是否上傳了文件
- 對文字進行切分,使切分出來的文字不超出 LLM tokens 數量限制
- 依次按順序對每個切分出來的文字進行翻譯
FastGPT 國內版地址:https://fastgpt.cn
FastGPT 海外版地址:https://tryfastgpt.ai
判斷是否上傳文件
首先第一步,在【系統配置】中新增兩個全域性變數用來表示源語言和目標語言,同時需要開啟檔案上傳功能。
接下來進入正式的工作流流程,先判斷一下源語言與目標語言是否相同,如果是一樣的,那就沒必要翻譯了!你讓我把中文翻譯成中文?開什麼玩笑,Token 不要錢的啦?
如果源語言和目標語言不同,就繼續往下走,判斷是否上傳了文件,如果上傳了文件,就將文件裡的內容解析出來。
OK,進入下一個流程。
切分長文字
接下來我們可以透過分片和迴圈,實現對長文字也即多文字塊的反思翻譯。
整體的邏輯是,首先對傳入文字的 tokens 數量做判斷,如果不超過設定的 tokens 限制,那麼直接呼叫單文字塊反思翻譯,如果超過設定的 tokens 限制,那麼切割為合理的大小,再分別進行對應的反思翻譯處理。
至於為什麼要切割分塊,有兩個原因:
- 大模型輸出上下文只有 4k,無法輸出超過 4k token 內容的文字。
- 輸入分塊可以減少太長的輸入導致的幻覺。
下面我們接著看工作流。
不管是否上傳了文件,最終都要把文字提取出來。如果直接輸入了文字,那麼直接將文字傳遞給下一個節點;如果上傳了文件,那麼解析完文件之後再將解析出來的文字傳遞給下一個節點。
下一個節點要乾的事情就是對文字進行切分。
這裡我們用到了 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! 🚀