Skip to content

翻译 Agent

本教程将带你从零开始构建一个翻译 Agent,支持多语言翻译和流式输出。

你将学到

  • 定义 Agent 配置和 Skill Handler
  • 接入 LLM 实现流式翻译
  • 双向交互(Server 提问,Client 回答)
  • 取消信号处理

最终效果

输入: { originalText: "你好世界", targetLang: "English" }
输出: Hello world(逐字流式输出)

第一步:创建项目

bash
mkdir translate-agent && cd translate-agent
bun init -y
bun add @multi-agent/a2a zod @langchain/openai

第二步:定义输入参数

创建 server.ts,定义翻译接口的参数结构:

typescript
import { z } from 'zod'

const translateSchema = z.object({
  originalText: z.string().describe('要翻译的文本'),
  targetLang: z.string().describe('目标语言'),
})

type TranslateInput = z.infer<typeof translateSchema>
typescript
// server.ts
import { z } from 'zod'
const translateSchema = z.object({ 
  originalText: z.string().describe('要翻译的文本'), 
  targetLang: z.string().describe('目标语言'), 
}) 
type TranslateInput = z.infer<typeof translateSchema> 

第三步:创建 LLM 实例

typescript
import { ChatOpenAI } from '@langchain/openai'

const llm = new ChatOpenAI({
  model: 'gpt-4o-mini',
  temperature: 0.3,
})
typescript
// server.ts
import { z } from 'zod'
import { ChatOpenAI } from '@langchain/openai'

const translateSchema = z.object({
  originalText: z.string().describe('要翻译的文本'),
  targetLang: z.string().describe('目标语言'),
})

type TranslateInput = z.infer<typeof translateSchema>

const llm = new ChatOpenAI({ 
  model: 'gpt-4o-mini', 
  temperature: 0.3, 
}) 

第四步:实现翻译 Handler

typescript
import { type Context } from '@multi-agent/a2a'

async function translateHandler(params: TranslateInput, ctx: Context) {
  const { originalText, targetLang } = params

  // 1. 参数校验
  if (!originalText) {
    return ctx.stream.send({ type: 'error', text: '请告诉我要翻译什么内容' })
  }
  if (!targetLang) {
    return ctx.stream.send({ type: 'error', text: '请告诉我要翻译成哪种语言' })
  }

  // 2. 发送进度
  ctx.stream.send({
    type: 'progress',
    text: `正在翻译为 ${targetLang}...`,
  })

  // 3. 构造提示词
  const prompt = `翻译以下文本为 ${targetLang},只输出翻译结果:\n\n${originalText}`

  // 4. 流式调用 LLM(传递 ctx.signal 支持取消)
  let translated = ''
  for await (const chunk of await llm.stream(prompt, { signal: ctx.signal })) {
    const content = chunk.content as string
    translated += content
    ctx.stream.send({ type: 'progress', text: content })
  }

  // 5. 发送完成消息
  ctx.stream.send({
    type: 'done',
    text: '翻译完成',
    data: { original: originalText, translated, targetLang },  // data 可选
  })
}
typescript
// server.ts
import { z } from 'zod'
import { ChatOpenAI } from '@langchain/openai'
import { type Context } from '@multi-agent/a2a'

const translateSchema = z.object({
  originalText: z.string().describe('要翻译的文本'),
  targetLang: z.string().describe('目标语言'),
})

type TranslateInput = z.infer<typeof translateSchema>

const llm = new ChatOpenAI({
  model: 'gpt-4o-mini',
  temperature: 0.3,
})

async function translateHandler(params: TranslateInput, ctx: Context) { 
  const { originalText, targetLang } = params
  // 1. 参数校验
  if (!originalText) { 
    return ctx.stream.send({ type: 'error', text: '请告诉我要翻译什么内容' }) 
  } 
  if (!targetLang) { 
    return ctx.stream.send({ type: 'error', text: '请告诉我要翻译成哪种语言' }) 
  } 
  // 2. 发送进度
  ctx.stream.send({ 
    type: 'progress', 
    text: `正在翻译为 ${targetLang}...`, 
  }) 
  // 3. 构造提示词
  const prompt = `翻译以下文本为 ${targetLang},只输出翻译结果:\n\n${originalText}`
  // 4. 流式调用 LLM(传递 ctx.signal 支持取消)
  let translated = ''
  for await (const chunk of await llm.stream(prompt, { signal: ctx.signal })) { 
    const content = chunk.content as string
    translated += content
    ctx.stream.send({ type: 'progress', text: content }) 
  } 
  // 5. 发送完成消息
  ctx.stream.send({ 
    type: 'done', 
    text: '翻译完成', 
    data: { original: originalText, translated, targetLang }, 
  }) 
} 

ctx.signal

ctx.signal 传递给 LLM,用户取消时立即中断调用,避免不必要的 token 消耗。

第五步:配置 Agent Server

typescript
import { createAgentServer, type AgentConfig } from '@multi-agent/a2a'

const config: AgentConfig = {
  agentId: 'translate-agent',
  name: 'Translate Agent',
  version: '1.0.0',
  description: '多语言翻译 Agent',
  address: 'a2a://0.0.0.0:50061',
  skills: [
    {
      name: 'translate',
      description: '翻译文本到目标语言',
      handler: translateHandler,
      inputSchema: z.toJSONSchema(translateSchema),
    },
  ],
  defaultSkill: 'translate',
}

const server = createAgentServer(config)
await server.start()
console.log('Translate Agent 已启动: localhost:50061')
typescript
// server.ts
import { z } from 'zod'
import { ChatOpenAI } from '@langchain/openai'
import { createAgentServer, type AgentConfig, type Context } from '@multi-agent/a2a'

const translateSchema = z.object({
  originalText: z.string().describe('要翻译的文本'),
  targetLang: z.string().describe('目标语言'),
})

type TranslateInput = z.infer<typeof translateSchema>

const llm = new ChatOpenAI({
  model: 'gpt-4o-mini',
  temperature: 0.3,
})

async function translateHandler(params: TranslateInput, ctx: Context) {
  const { originalText, targetLang } = params

  ctx.stream.send({
    type: 'progress',
    text: `正在翻译为 ${targetLang}...`,
  })

  const prompt = `翻译以下文本为 ${targetLang},只输出翻译结果:\n\n${originalText}`

  let translated = ''
  for await (const chunk of await llm.stream(prompt, { signal: ctx.signal })) {
    const content = chunk.content as string
    translated += content
    ctx.stream.send({ type: 'progress', text: content })
  }

  ctx.stream.send({
    type: 'done',
    text: '翻译完成',
    data: { original: originalText, translated, targetLang },
  })
}

const config: AgentConfig = { 
  agentId: 'translate-agent', 
  name: 'Translate Agent', 
  version: '1.0.0', 
  description: '多语言翻译 Agent', 
  address: 'a2a://0.0.0.0:50061', 
  skills: [ 
    { 
      name: 'translate', 
      description: '翻译文本到目标语言', 
      handler: translateHandler, 
      inputSchema: z.toJSONSchema(translateSchema), 
    }, 
  ], 
  defaultSkill: 'translate', 
} 
const server = createAgentServer(config) 
await server.start() 
console.log('Translate Agent 已启动: localhost:50061') 

第六步:创建测试客户端

创建 client.ts

typescript
// client.ts
import { createAgentClient } from '@multi-agent/a2a'

// 1. 创建客户端
const client = createAgentClient({
  agentId: 'translate-agent',
  address: 'a2a://localhost:50061',
})

// 2. 调用技能
const stream = await client.call('translate', {
  originalText: '人工智能正在改变我们的工作和生活方式。',
  targetLang: 'English',
})

// 3. 处理响应
process.stdout.write('译文: ')
for await (const msg of stream) {
  if (msg.type === 'progress') {
    process.stdout.write(msg.text)
  }
  if (msg.type === 'done') {
    console.log('\n翻译完成')
  }
}

// 4. 关闭连接
await client.close()

第七步:运行

bash
# 终端 1
bun run server.ts

# 终端 2
bun run client.ts

输出:

译文: Artificial intelligence is transforming how we work and live.
翻译完成

进阶:双向流

Server 可以向 Client 提问,Client 回答后继续处理。

stream.send vs ctx.stream.send

两者是同一个双向流的两端

  • Client 端client.call() 返回 stream,通过 stream.send() 向 Server 发送消息
  • Server 端:Handler 接收 ctx 参数,通过 ctx.stream.send() 向 Client 发送消息

双方都通过 for await (const msg of stream) 接收对方发来的消息。

场景示例

用户只提供了要翻译的文本,Server 主动询问目标语言:

Client: call({ originalText: "人工智能正在改变世界" })
Server: question - "为了更好地完成翻译,请告诉我您想翻译成哪种语言?"
Client: answer - "帮我翻译成英文吧"
Server: progress - "Artificial intelligence is changing the world"
Server: done

Server 端

typescript
async function translateHandler(params: { originalText: string }, ctx: Context) {
  ctx.stream.send({ type: 'question', text: '请告诉我您想翻译成哪种语言?' })

  for await (const msg of ctx.stream) {
    if (msg.type === 'answer') {
      const result = await translate(params.originalText, msg.text, ctx)  // LLM 提取目标语言并翻译
      ctx.stream.send({ type: 'done', data: result })
      break
    }
  }
}

Client 端

typescript
const stream = await client.call('translate', { originalText: '人工智能正在改变世界' })

for await (const msg of stream) {
  if (msg.type === 'question') {
    const answer = await readline.question(`Agent: ${msg.text}\nYou: `)
    stream.send({ type: 'answer', text: answer })
  }
  if (msg.type === 'done') {
    console.log('翻译完成:', msg.data.translated)
  }
}

交互流程图

翻译 Agent 交互流程


进阶:取消请求

用户可能在翻译过程中点击"取消"按钮,此时需要中断 LLM 调用。

Client 端:触发取消

方式一:直接调用 stream.cancel()

typescript
const stream = await client.call('translate', { originalText: 'Hello', targetLang: 'Chinese' })

// 用户点击取消按钮时
cancelButton.onclick = () => stream.cancel()

方式二:使用 AbortController

typescript
const controller = new AbortController()

cancelButton.onclick = () => controller.abort()

const stream = await client.call('translate',
  { originalText: 'Hello', targetLang: 'Chinese' },
  { signal: controller.signal }
)

Server 端:响应取消

Handler 中的 ctx.signal 会在客户端取消时触发,传递给 LLM 即可自动中断:

typescript
// ctx.signal 传递给 LLM,取消时自动中断
for await (const chunk of await llm.stream(prompt, { signal: ctx.signal })) {
  // ...
}

MIT Licensed