外观
翻译 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: doneServer 端
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)
}
}交互流程图
进阶:取消请求
用户可能在翻译过程中点击"取消"按钮,此时需要中断 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 })) {
// ...
}