← All examples
// Use-case Recipe

Translation + Backtranslation Quality Check (Cross-Model)

Translate with one model family, translate back with another, then flag possible semantic drift in a structured report.

Useful forLocalization teams checking whether important meaning survived translation

Enterprise support
01 The scenario

A fluent translation can still lose a condition, number, or nuance. Asking the same model to grade its own work also gives the check too little independence.

Source text and a target language

The example uses credentials for two different provider families so translation and backtranslation are separated.

02 How it works

A team, with a clear handoff.

Each specialist sees the evidence it needs. Independent work runs together; dependent work waits for the right inputs.

  1. Translate the source

    The first model creates the target-language version.

    Translator

    Translates the English source into the requested language.

  2. Translate it back independently

    A different provider family sees the translated text, not the original answer rationale.

    Backtranslator

    Produces a fresh English version from the translation.

  3. Compare meaning

    A third specialist compares the original with the backtranslation.

    Quality reviewer

    Flags semantic drift and returns schema-validated findings.

03 The result

A structured translation quality check

The result identifies where meaning may have shifted; it does not merely score surface fluency.

  1. 01Target-language translation
  2. 02Independent backtranslation
  3. 03Structured semantic-drift findings
  • Different roles keep generation and checking distinct.
  • Cross-provider review adds useful independence.
  • Structured findings are easier to route for human review.
Scope and limits
Backtranslation is a signal, not proof of translation quality. High-stakes or culturally sensitive content still requires a qualified human reviewer.
04 For builders

Run it, then inspect the implementation.

The business view above is editorial. The commands, prerequisites, APIs, and source below stay synchronized with the real repository.

Implementation facts AgentAgentPoolToolRegistryToolExecutorregisterBuiltInTools
Use-case Recipe 328 lines

Run it

From a clone of the repo — this exact file:

terminal
npx tsx packages/core/examples/cookbook/translation-backtranslation.ts
Prerequisites
  • ANTHROPIC_API_KEY must be set
  • and at least one of OPENAI_API_KEY / GEMINI_API_KEY must be set
Open the complete, synchronized source · 328 lines

The complete example, synchronized from the pinned Framework commit.

cookbook/translation-backtranslation.ts
/**
* Translation + Backtranslation Quality Check (Cross-Model)
*
* Demonstrates:
* - Agent A: translate EN -> target language with Claude
* - Agent B: back-translate -> EN with a different provider family
* - Agent C: compare original vs. backtranslation and flag semantic drift
* - Structured output with Zod schemas
*
* Run:
* npx tsx packages/core/examples/cookbook/translation-backtranslation.ts
*
* Prerequisites:
* ANTHROPIC_API_KEY must be set
* and at least one of OPENAI_API_KEY / GEMINI_API_KEY must be set
*/
 
import { z } from 'zod'
import {
Agent,
AgentPool,
ToolRegistry,
ToolExecutor,
registerBuiltInTools,
} from '../../src/index.js'
import type { AgentConfig } from '../../src/types.js'
 
// ---------------------------------------------------------------------------
// Inline sample text (3-5 technical paragraphs, per issue requirement)
// ---------------------------------------------------------------------------
 
const SAMPLE_TEXT = `
Modern CI/CD pipelines rely on deterministic builds and reproducible environments.
A deployment may fail even when the application code is correct if the runtime,
dependency graph, or container image differs from what engineers tested locally.
Observability should combine logs, metrics, and traces rather than treating them
as separate debugging tools. Metrics show that something is wrong, logs provide
local detail, and traces explain how a request moved across services.
Schema validation is especially important in LLM systems. A response may sound
reasonable to a human reader but still break automation if the JSON structure,
field names, or enum values do not match the downstream contract.
Cross-model verification can reduce self-confirmation bias. When one model
produces a translation and a different provider family performs the
backtranslation, semantic drift becomes easier to detect.
`.trim()
 
// ---------------------------------------------------------------------------
// Zod schemas
// ---------------------------------------------------------------------------
 
const ParagraphInput = z.object({
index: z.number().int().positive(),
original: z.string(),
})
type ParagraphInput = z.infer<typeof ParagraphInput>
 
const TranslationBatch = z.object({
target_language: z.string(),
items: z.array(
z.object({
index: z.number().int().positive(),
translation: z.string(),
}),
),
})
type TranslationBatch = z.infer<typeof TranslationBatch>
 
const BacktranslationBatch = z.object({
items: z.array(
z.object({
index: z.number().int().positive(),
backtranslation: z.string(),
}),
),
})
type BacktranslationBatch = z.infer<typeof BacktranslationBatch>
 
const DriftRow = z.object({
original: z.string(),
translation: z.string(),
backtranslation: z.string(),
drift_severity: z.enum(['none', 'minor', 'major']),
notes: z.string(),
})
type DriftRow = z.infer<typeof DriftRow>
 
const DriftTable = z.array(DriftRow)
type DriftTable = z.infer<typeof DriftTable>
 
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
 
function buildAgent(config: AgentConfig): Agent {
const registry = new ToolRegistry()
registerBuiltInTools(registry)
const executor = new ToolExecutor(registry)
return new Agent(config, registry, executor)
}
 
function splitParagraphs(text: string): ParagraphInput[] {
return text
.split(/\n\s*\n/)
.map((p, i) => ({
index: i + 1,
original: p.trim(),
}))
.filter((p) => p.original.length > 0)
}
 
// ---------------------------------------------------------------------------
// Provider selection
// ---------------------------------------------------------------------------
 
const hasAnthropic = Boolean(process.env.ANTHROPIC_API_KEY)
const hasOpenAI = Boolean(process.env.OPENAI_API_KEY)
const hasGemini = Boolean(process.env.GEMINI_API_KEY)
 
if (!hasAnthropic || (!hasGemini && !hasOpenAI)) {
console.log(
'[skip] This example needs ANTHROPIC_API_KEY plus GEMINI_API_KEY or OPENAI_API_KEY.',
)
process.exit(0)
}
 
// Prefer native Gemini when GEMINI_API_KEY is available.
// Fall back to OpenAI otherwise.
const backProvider: 'gemini' | 'openai' = hasGemini ? 'gemini' : 'openai'
 
const backModel =
backProvider === 'gemini'
? 'gemini-2.5-pro'
: (process.env.OPENAI_MODEL || 'gpt-5.4')
// ---------------------------------------------------------------------------
// Agent configs
// ---------------------------------------------------------------------------
 
// Agent A ---------------------------------------------------------------
// 用 Claude 做 “英文 -> 目标语言” 翻译
const translatorConfig: AgentConfig = {
name: 'translator',
provider: 'anthropic',
model: 'claude-sonnet-4-6',
systemPrompt: `You are Agent A, a technical translator.
Translate English paragraphs into Simplified Chinese.
Preserve meaning, terminology, paragraph boundaries, and index numbers.
Do not merge paragraphs.
Return JSON only, matching the schema exactly.`,
maxTurns: 1,
temperature: 0,
outputSchema: TranslationBatch,
}
 
// Agent B ---------------------------------------------------------------
// 用不同 provider 家族做 “目标语言 -> 英文” 回译
const backtranslatorConfig: AgentConfig = {
name: 'backtranslator',
provider: backProvider,
model: backModel,
baseURL: backProvider === 'openai' ? process.env.OPENAI_BASE_URL : undefined,
systemPrompt: `You are Agent B, a back-translation specialist.
Back-translate the provided Simplified Chinese paragraphs into English.
Preserve meaning as literally as possible.
Do not merge paragraphs.
Keep the same index numbers.
Return JSON only, matching the schema exactly.`,
maxTurns: 1,
temperature: 0,
outputSchema: BacktranslationBatch,
}
// Agent C ---------------------------------------------------------------
// 比较原文和回译文,判断语义漂移
const reviewerConfig: AgentConfig = {
name: 'reviewer',
provider: 'anthropic',
model: 'claude-sonnet-4-6',
systemPrompt: `You are Agent C, a semantic drift reviewer.
You will receive:
- the original English paragraph
- the translated paragraph
- the backtranslated English paragraph
For each paragraph, judge drift_severity using only:
- none: meaning preserved
- minor: slight wording drift, but no important meaning change
- major: material semantic change, omission, contradiction, or mistranslation
Return JSON only.
The final output must be an array where each item contains:
original, translation, backtranslation, drift_severity, notes.`,
maxTurns: 1,
temperature: 0,
outputSchema: DriftTable,
}
 
// ---------------------------------------------------------------------------
// Build agents
// ---------------------------------------------------------------------------
 
const translator = buildAgent(translatorConfig)
const backtranslator = buildAgent(backtranslatorConfig)
const reviewer = buildAgent(reviewerConfig)
 
const pool = new AgentPool(1)
pool.add(translator)
pool.add(backtranslator)
pool.add(reviewer)
 
// ---------------------------------------------------------------------------
// Run pipeline
// ---------------------------------------------------------------------------
 
const paragraphs = splitParagraphs(SAMPLE_TEXT)
 
console.log('Translation + Backtranslation Quality Check')
console.log('='.repeat(60))
console.log(`Paragraphs: ${paragraphs.length}`)
console.log(`Translator provider: anthropic (claude-sonnet-4-6)`)
console.log(`Backtranslator provider: ${backProvider} (${backModel})`)
console.log()
 
// Step 1: Agent A translates
console.log('[1/3] Agent A translating EN -> zh-CN...\n')
 
const translationPrompt = `Target language: Simplified Chinese
Translate the following paragraphs.
Return exactly one translated item per paragraph.
Input:
${JSON.stringify(paragraphs, null, 2)}`
const translationResult = await pool.run('translator', translationPrompt)
 
if (!translationResult.success || !translationResult.structured) {
console.error('Agent A failed:', translationResult.output)
process.exit(1)
}
 
const translated = translationResult.structured as TranslationBatch
 
// Step 2: Agent B back-translates
console.log('[2/3] Agent B back-translating zh-CN -> EN...\n')
 
const backtranslationPrompt = `Back-translate the following paragraphs into English.
Keep the same indexes.
Input:
${JSON.stringify(translated.items, null, 2)}`
const backtranslationResult = await pool.run('backtranslator', backtranslationPrompt)
 
if (!backtranslationResult.success || !backtranslationResult.structured) {
console.error('Agent B failed:', backtranslationResult.output)
process.exit(1)
}
 
const backtranslated = backtranslationResult.structured as BacktranslationBatch
 
// Step 3: Agent C reviews semantic drift
console.log('[3/3] Agent C reviewing semantic drift...\n')
 
const mergedInput = paragraphs.map((p) => ({
index: p.index,
original: p.original,
translation: translated.items.find((x) => x.index === p.index)?.translation ?? '',
backtranslation:
backtranslated.items.find((x) => x.index === p.index)?.backtranslation ?? '',
}))
 
const reviewPrompt = `Compare the original English against the backtranslated English.
Important:
- Evaluate semantic drift paragraph by paragraph
- Do not judge style differences as major unless meaning changed
- Return only the final JSON array
Input:
${JSON.stringify(mergedInput, null, 2)}`
const reviewResult = await pool.run('reviewer', reviewPrompt)
 
if (!reviewResult.success || !reviewResult.structured) {
console.error('Agent C failed:', reviewResult.output)
process.exit(1)
}
 
const driftTable = reviewResult.structured as DriftTable
 
// ---------------------------------------------------------------------------
// Final output
// ---------------------------------------------------------------------------
 
console.log('='.repeat(60))
console.log('FINAL DRIFT TABLE')
console.log('='.repeat(60))
console.log(JSON.stringify(driftTable, null, 2))
console.log()
 
console.log('Token Usage Summary')
console.log('-'.repeat(60))
console.log(
`Agent A (translator) — input: ${translationResult.tokenUsage.input_tokens}, output: ${translationResult.tokenUsage.output_tokens}`,
)
console.log(
`Agent B (backtranslator) — input: ${backtranslationResult.tokenUsage.input_tokens}, output: ${backtranslationResult.tokenUsage.output_tokens}`,
)
console.log(
`Agent C (reviewer) — input: ${reviewResult.tokenUsage.input_tokens}, output: ${reviewResult.tokenUsage.output_tokens}`,
)
 
const totalInput =
translationResult.tokenUsage.input_tokens +
backtranslationResult.tokenUsage.input_tokens +
reviewResult.tokenUsage.input_tokens
 
const totalOutput =
translationResult.tokenUsage.output_tokens +
backtranslationResult.tokenUsage.output_tokens +
reviewResult.tokenUsage.output_tokens
 
console.log('-'.repeat(60))
console.log(`TOTAL — input: ${totalInput}, output: ${totalOutput}`)
console.log('\nDone.')
View & edit on GitHub
// Enterprise

Taking this to production?

open-multi-agent is MIT-licensed and free to run yourself. When you need it delivered, integrated, or supported on a deadline, 元定义科技 (YuanASI) offers commercial delivery and support.

Enterprise support