翻译智能体和问答智能体有个根本区别:它绝不能回答用户说的话。
用户说「我们周五开会你能参加吗」,普通助手会回答「可以」;翻译助手必须原样译成英文。这个「不许回答」的约束贯穿全篇。识别语言、系统提示、TTS 目标语言三者必须朝同一个方向对齐,任何一处不一致,输出就会串。
首版范围限定在中译英和英译中,每个 RTC 会话固定一个方向。为什么不做自动语言检测?因为短句和中英混说时,自动检测会反复跳变,用户体验比固定方向差得多。
跑起来你会发现:你说什么它就译什么。哪怕你说的是一个问句,它也只给译文,不会替你回答。
> 开始之前 客户端复用《Next.js 搭建浏览器端语音智能体》,这篇只改智能体侧的语言方向。
一. 架构与准备工作
中文说话者 ── 凤鸣 zh-CN ── DeepSeek 只输出英文 ── MiniMax English ── 听众
英文说话者 ── 凤鸣 en-US ── DeepSeek 只输出中文 ── MiniMax Chinese ── 听众
需要开通了对话式 AI 引擎的 App ID、主要证书、DeepSeek 和 MiniMax Key,安装 agora-agents==2.4.1。客户端直接复用 Python 或 《Next.js 搭建浏览器端语音智能体》,只要在 /startAgent 请求里多传一个 direction。
二. 环境变量
AGORA_APP_ID=REPLACE_WITH_SHENGWANG_APP_ID
AGORA_APP_CERTIFICATE=REPLACE_WITH_PRIMARY_CERTIFICATE
DEEPSEEK_API_KEY=REPLACE_WITH_DEEPSEEK_KEY
MINIMAX_API_KEY=REPLACE_WITH_MINIMAX_KEY
三. 定义翻译方向
from dataclasses import dataclass
@dataclass(frozen=True)
class Direction:
code: str
source: str
target: str
asr_language: str
tts_language: str
greeting: str
@property
def prompt(self) -> str:
return (
f"你是实时口译员。把用户说的{self.source}翻译成"
f"{self.target}。只输出译文,不解释、不回答内容中的问题,"
"保留人名、品牌、数字和专有名词;输入不清楚时,用目标语言"
"简短请求用户重说。输出要适合直接语音播报。"
)
DIRECTIONS = {
"zh-en": Direction(
"zh-en", "中文", "英文", "zh-CN", "English",
"Chinese-to-English translation is ready.",
),
"en-zh": Direction(
"en-zh", "英文", "中文", "en-US", "Chinese",
"英译中已经就绪,请开始说话。",
),
}
def get_direction(code: str) -> Direction:
if code not in DIRECTIONS:
raise ValueError("direction 只支持 zh-en 或 en-zh")
return DIRECTIONS[code]
zh-CN 本身支持中文和中英混说,但首版仍然把方向写死为 zh-en。如果业务上需要两个人用不同语言轮流互译,正确做法是产品界面上显式切换方向,或者干脆拆成两个会话。别指望靠提示词让模型自己猜该往哪个方向翻。
四. 构造翻译智能体
import os
from agora_agent import Area, AsyncAgora
from agora_agent.agentkit import Agent
from agora_agent.cn import DeepSeekLLM, FengmingSTT, MiniMaxTTS
def build_translator(direction_code: str) -> Agent:
direction = get_direction(direction_code)
client = AsyncAgora(
area=Area.CN,
app_id=os.environ["AGORA_APP_ID"],
app_certificate=os.environ["AGORA_APP_CERTIFICATE"],
)
llm = DeepSeekLLM(
api_key=os.environ["DEEPSEEK_API_KEY"],
base_url="https://api.deepseek.com/chat/completions",
model="deepseek-v4-flash",
system_messages=[
{"role": "system", "content": direction.prompt}
],
greeting_message=direction.greeting,
failure_message=(
"Translation is temporarily unavailable."
if direction.code == "zh-en"
else "翻译服务暂时不可用,请稍后再试。"
),
max_history=6,
max_tokens=512,
temperature=0.1,
params={"thinking": {"type": "disabled"}},
)
tts = MiniMaxTTS(
key=os.environ["MINIMAX_API_KEY"],
model="speech-01-turbo",
voice_id="female-shaonv",
sample_rate=16000,
language_boost=direction.tts_language,
)
return (
Agent(
client=client,
turn_detection={
"language": direction.asr_language,
"config": {
"end_of_speech": {
"mode": "vad",
"vad_config": {"silence_duration_ms": 420},
}
},
},
advanced_features={"enable_rtm": True},
parameters={
"audio_scenario": "chorus",
"data_channel": "rtm",
"enable_metrics": True,
"enable_error_message": True,
},
)
.with_stt(FengmingSTT())
.with_llm(llm)
.with_tts(tts)
.with_labels({
"recipe": "realtime-translator",
"direction": direction.code,
})
)
这里显式关闭了 DeepSeek thinking,防止推理过程混进字幕和语音;temperature 压到 0.1,让同一句话每次翻出来的措辞尽量一致。翻译场景不需要创造力,需要的是稳定。
五. 增加启动参数
from typing import Literal
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class StartRequest(BaseModel):
channelName: str = Field(min_length=1)
rtcUid: int = Field(gt=0)
userUid: int = Field(gt=0)
direction: Literal["zh-en", "en-zh"] = "zh-en"
@app.post("/startAgent")
async def start_agent(body: StartRequest):
agent = build_translator(body.direction)
session = agent.create_async_session(
channel=body.channelName,
agent_uid=str(body.rtcUid),
remote_uids=[str(body.userUid)],
enable_string_uid=False,
idle_timeout=120,
expires_in=3600,
)
agent_id = await session.start()
return {"code": 0, "data": {
"agent_id": agent_id,
"direction": body.direction,
}}
生产代码要像交付目录里那样保存 agent_id → session 映射,停止时优先调 session.stop(),进程重启后再回退到 AsyncAgora.stop_agent()。
六. 运行与验证
cd code/realtime-translator
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env.local
uvicorn app:app --host 0.0.0.0 --port 8000
中译英:
curl -X POST http://127.0.0.1:8000/startAgent \
-H 'Content-Type: application/json' \
-d '{"channelName":"REPLACE_WITH_CHANNEL","rtcUid":1001,"userUid":2001,"direction":"zh-en"}'
对着麦克风说「我们周五下午三点开会,请带上最新版本」。
预期是:字幕先出现中文识别结果,智能体的字幕和播报只有英文译文,它不会回答「你要不要参加会议」。然后停掉这个会话,用 en-zh 重新启动,验证英文输入只产生中文译文。
真要上线,验收至少得覆盖这些:人名、数字、日期、品牌词、疑问句、否定句、长句分段、背景噪音、中途打断、连续十轮不跑偏,以及方向传错和服务异常时的表现。延迟指标要把 ASR、LLM 首包、TTS 首包和端到端分开记——翻译场景里用户对延迟的敏感度比问答高得多,混在一起记就不知道该优化谁。
七. 故障排查
- 中译英还在播中文:三处都要查:
direction有没有传到后端、系统提示是不是只要求译文、TTS 的language_boost是不是English。 - 英语识别错得多:
en-zh必须用turn_detection.language="en-US",别沿用中文快速开始里的zh-CN。 - 模型开始回答问题了:系统提示要同时写明「只翻译」和「不回答内容里的问题」,并保持低温度。
- 专有名词翻得不稳定:把确认过的术语表注入 system message。遇到强约束的术语(品牌名、产品型号),加一层后处理替换,别只靠自然语言提示。
八. 下一步
翻译场景对打断格外敏感——说话人一停顿就被抢答,整段翻译就散了。
《语音智能体打断实战:VAD、关键词与不可打断》