简介:这是一份面向需要搭建多语言客服系统的开发者或站长提供的Telegram AI全自动翻译客服机器人源码包,附带视频搭建教程。机器人基于DeepSeek语言识别能力实现双向消息翻译,既能把各国客户消息自动翻译为客服预设语言,也能将客服回复转化为符合客户母语口语习惯的表达,适合外贸、跨境电商、海外社群等业务场景使用。压缩包共929个文件、约28.94MB,主要包含Node.js/TypeScript项目源码(js、ts、json、mjs、cjs等)、环境与依赖配置(env、yml、lock、npmignore等)、工程说明与文档(md、txt、markdown等)以及视频教程文件(mp4),结构完整,方便二次开发与部署调试。目前已有92人学习下载。通过源码阅读和随包视频,使用者可以快速掌握机器人配置流程、DeepSeek接口调用方式、多语言消息处理逻辑及常见排错要点。
1. Telegram AI全自动翻译客服机器人:它到底解决什么,值不值得自己搭
Telegram AI全自动翻译客服机器人,是一个跑在 Bot API 上的常驻服务:用户发来的消息自动翻译成客服的默认语言,客服的回复再翻回用户的语言,同时保留人工接管的通道。跨境店铺、出海工具和海外社群运营者最需要它——凌晨一个日语用户提问,没有客服值班,机器人先把问题翻译成中文并自动回复,你早上进后台再决定要不要转人工。市面上这类源码包的视频教程通常只讲流程,真正让机器人不翻车的是 token 权限、翻译后端选型和会话状态管理这些细节。下面按可复现的顺序拆开,新手能跟步骤走,熟手重点看参数和边界。
2. 机器人骨架:用BotFather拿token,把翻译客服的最小闭环跑起来
2.1 从零拿token,先分清机器人的权限边界
Telegram 机器人没有传统账号密码,一切身份都靠一串形如123456:ABC-DEF1234的 token。先找官方机器人 BotFather,给它发/newbot,按提示填显示名称和 username。username 必须以bot结尾,且只能用小写字母、数字和下划线,例如my_shop_translate_bot。创建成功后 BotFather 会一次性展示 token,只显示这一次,之后要用/mybots进管理页才能重新查看。
拿到 token 后建议直接写进环境变量,不要硬编码进源码。token 一旦泄露,别人就能控制你的机器人删消息、拉黑用户,后果是客服通道直接被劫持。我一般会在.env里存一份,程序启动时用os.getenv("BOT_TOKEN")读取,这样源码包即使传到服务器上也不带密钥。视频教程里常忽略这一步,等机器人被别人接管了才后悔。
2.2 最小闭环:python-telegram-bot 的 handler 与消息流转
翻译客服机器人最常见的 Python 后端是python-telegram-bot(下文简称 PTB),它把 Telegram 的 getUpdates 轮询和事件分发封装成了 handler 机制。v20 之后是 asyncio 风格,代码结构和旧版的@dispatcher写法差别很大。先搭一个能响应的最小骨架:
import os from telegram import Update from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes TOKEN = os.getenv("BOT_TOKEN") async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text("我是客服翻译机器人。直接发消息,我会自动翻译。") async def handle_text(update: Update, context: ContextTypes.DEFAULT_TYPE): text = update.message.text if not text: return # 这里先占位,下一章接入真正的翻译函数 translated = await translate_text(text, target="zh") await update.message.reply_text(f"你: {text}\n\n翻译: {translated}") def main(): app = Application.builder().token(TOKEN).build() app.add_handler(CommandHandler("start", start)) app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text)) app.run_polling(allowed_updates=Update.ALL_TYPES) if __name__ == "__main__": main()这段代码的逻辑是:PTB 内置一个轮询循环,每收到一条消息,按 handler 注册顺序匹配。CommandHandler("start", start)只接管/start命令;MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text)接管所有文本消息但排除命令,避免用户发/help这类命令时也被当作待翻译内容。run_polling是开发模式最省事的方式,不需要公网地址,服务器能访问 Bot API 就能跑。
参数说明:allowed_updates=Update.ALL_TYPES表示接收消息、回执、群聊变更等所有更新类型。如果你的机器人只处理私聊文本,可以收紧为allowed_updates=["message"],能减少无效轮询请求。注意handle_text内先判空,Telegram 的消息可能只有附件没有文本,直接取.text会拿回None,不判空后续翻译接口会报参数错误。
2.3 把上一句聊天的上下文留住:context.user_data 与短期会话记忆
翻译客服和普通翻译软件最大的区别是会话连续。用户问“你们发货到东京吗”,你翻译成中文,客服答“可以,运费 20 美元”,再翻回去。第二轮的“可以”如果单独翻译,大概率被译成“OK”而不是“可以发货”,这就是缺少上下文的典型翻车现场。
PTB 为每个用户维护了一个独立的context.user_data字典,天然按用户隔离数据,不需要自己建全局 dict 再操心并发覆盖。用它存一个最近 10 轮的对话记录:
from collections import deque async def handle_text(update: Update, context: ContextTypes.DEFAULT_TYPE): text = update.message.text if not text: return history = context.user_data.setdefault("history", deque(maxlen=10)) history.append({"role": "user", "content": text}) translated = await translate_text(text, target="zh", history=list(history)) history.append({"role": "assistant", "content": translated}) await update.message.reply_text(f"翻译: {translated}")deque(maxlen=10)是这里的关键:队列满 10 条后,新消息进来自动丢弃最老的一条,内存占用恒定,不需要自己写裁剪逻辑。setdefault保证第一次进会话时有默认值,第二次就直接复用。把history转成list再传给翻译函数,是因为 deque 的切片操作不如 list 直观,翻译引擎也只需读不修改。
这里要提醒一点:context.user_data是存在进程内存里的,机器人重启就清空。如果你需要“用户昨天问过什么”这种跨天记忆,就得落库,常见做法是 SQLite 或 Redis,按 user_id 存聊天记录。用 PTB 的context.user_data做短期记忆足够,跨天记忆是另一个量级的需求,后面第 6 章会提到怎么权衡。
3. 翻译后端怎么选:腾讯翻译免费额度与LLM上下文翻译,一个接口切换
3.1 平价方案:腾讯翻译与百度翻译的免费额度和 API 差异
翻译服务是整个机器人的成本大头,选型决定你一个月要烧多少钱。腾讯翻译(腾讯云机器翻译 TMT)和百度翻译是两家最常用的云端翻译 API,都有免费额度,具体数值以各自控制台为准,小卖家和个人项目基本够用。两者的差异不在翻译质量,而在接入方式:腾讯翻译用腾讯云的 SecretId/SecretKey 签名,百度翻译用 appid + key + 签名。
腾讯云开通机器翻译后,在访问管理页面创建子账号拿 SecretId 和 SecretKey,然后安装官方 SDK:
pip install tencentcloud-sdk-python调用代码很简洁:
import asyncio from tencentcloud.common import credential from tencentcloud.tmt.v20180321 import tmt_client, models class TencentTranslator: def __init__(self, secret_id: str, secret_key: str): self.cred = credential.Credential(secret_id, secret_key) # TMT 是机器翻译产品,地域固定填 ap-guangzhou self.client = tmt_client.TmtClient(self.cred, "ap-guangzhou") def translate_sync(self, text: str, target: str = "zh") -> str: req = models.TextTranslateRequest() req.SourceText = text req.Source = "auto" req.Target = target req.ProjectId = 0 resp = self.client.TextTranslate(req) return resp.TargetText async def translate(self, text: str, target: str = "zh") -> str: # 同步 SDK 的调用会阻塞事件循环,用 to_thread 丢到线程池 return await asyncio.to_thread(self.translate_sync, text, target)逻辑说明:TextTranslateRequest里Source="auto"表示让服务端自动识别源语言,Target是目标语言代码,中文填zh,日文填ja。ProjectId是腾讯云的区分维度,个人项目填 0 即可。TmtClient的TextTranslate是同步方法,直接在 asyncio 的 handler 里调用会卡住整个机器人,用asyncio.to_thread把它丢到线程池是必须的一步,这段代码如果照抄视频里的同步写法,你会看到机器人处理一条消息时其他用户全部排队。
百度翻译的接入方式类似,但签名逻辑要自己写 MD5,SDK 不如腾讯云省心。我的建议是:新项目优先腾讯翻译,开源生态里找python的腾讯翻译封装更容易和 PTB 集成。
3.2 用 LLM 做翻译:上下文感知的利与成本控制的弊
云端翻译 API 最大的局限是单句翻译,不感知对话历史。用户说“这个多少钱”翻译没问题,但客服回复“那个可以,这个不行”,单句翻译基本会失真。这时候把翻译换成大模型(LLM),把最近的对话历史一起喂进去,翻译质量会明显上一个台阶,这也是“多 AI 协作”最常见的落地形态。
import os from openai import AsyncOpenAI TRANSLATE_MODEL = os.getenv("TRANSLATE_MODEL", "gpt-4o-mini") LLM_CLIENT = AsyncOpenAI(api_key=os.getenv("LLM_API_KEY"), base_url=os.getenv("LLM_BASE_URL")) async def llm_translate(text: str, history: list, target: str = "中文") -> str: system_prompt = ( "你是跨境客服翻译引擎。只输出翻译结果,不解释、不添加意见。" "保持礼貌语气,品牌名、订单号、地址保留原文。" ) messages = [{"role": "system", "content": system_prompt}] for item in history[-6:]: role = "user" if item.get("role") == "user" else "assistant" messages.append({"role": role, "content": item["content"]}) messages.append({"role": "user", "content": f"把这段话翻译成{target},只给译文:\n{text}"}) resp = await LLM_CLIENT.chat.completions.create( model=TRANSLATE_MODEL, messages=messages, temperature=0.1, # 翻译任务要低随机性,别让它自由发挥 ) return resp.choices[0].message.content这里的 prompt 设计有两个细节:一是系统提示里明确“只输出翻译结果”,否则模型偶尔会回一句“好的,以下是翻译:”,这些杂讯会直接发给用户;二是temperature=0.1,翻译不是创作,随机性越低越可靠。历史对话只取最近 6 条,LLM 的上下文窗口虽大,但每轮翻译都塞全部历史,token 成本会线性上涨,不值。
成本控制上,我的做法是先让一个轻量语言检测判断源语言,如果用户发的内容已经是客服语言,根本不用调大模型;再把同一用户 1 分钟内的多条短消息合并成一条再翻译,减少调用次数。具体模型价格以各厂商官网为准,但记住一条:翻译场景用 mini 档模型足够,旗舰模型的钱花在客服意图识别上更值。
3.3 多后端封装:一个 translate 函数,线上换引擎不动业务
代码里最忌讳写死翻译引擎。今天用腾讯翻译免费额度,明天额度烧完了要切 LLM,如果业务代码里到处是TencentTranslator,改起来想死。我一般封装一个统一接口:
class Translator: def __init__(self, backend: str = "tencent"): self.backend = backend if backend == "tencent": self._tencent = TencentTranslator( os.getenv("TENCENT_SECRET_ID", ""), os.getenv("TENCENT_SECRET_KEY", ""), ) elif backend == "llm": self._llm_ready = True async def translate(self, text: str, target: str = "zh", history=None) -> str: if self.backend == "tencent": return await self._tencent.translate(text, target) if self.backend == "llm": return await llm_translate(text, history or [], target) raise ValueError(f"unknown backend: {self.backend}") translator = Translator(backend=os.getenv("TRANSLATE_BACKEND", "tencent"))切引擎就改一个环境变量,业务层完全无感。这个封装顺手解决了另一个问题:测试时可以切到tencent跑冒烟,确认逻辑没问题再切llm,不用重启改代码。后面第 5 章讲排查时你会看到,这个开关在线上出问题时是后悔药,能让你 30 秒切回便宜引擎止血。
4. 自动接待与人工接管:用会话状态机把客服流程闭合
4.1 三种状态与触发条件:自动、转人工、静默
翻译客服机器人如果只有“自动翻译+回复”这一个动作,那它和翻译器没区别,撑不起客服二字。真实客服流程里必须有人工介入的通道:机器人聊不明白的时候要转给真人,真人忙的时候要进静默状态只记录不打扰用户。这需要一个极简的会话状态机。
| 状态 | 谁在回复 | 触发条件 | 退出条件 |
|---|---|---|---|
| auto | 机器人自动翻译回复 | 新会话默认态 | 用户发 /human 或机器人识别到高优问题 |
| human | 真人客服通过机器人回传 | 用户请求转人工 / 关键词命中 | 客服标记处理完成 |
| silent | 无人回复,只记录 | 客服下班时段 / 机器人故障降级 | 到达营业时间或管理员手动恢复 |
状态机存在context.user_data["state"]里,每个用户独立。设计原则是:默认永远落在 auto,转人工必须显式触发,不能因为机器人误判让真人半夜被叫起来。触发转人工的常见做法有两个,一是用户主动发/human命令,二是用关键词命中,例如用户连续发“人工”“human”“有人吗”,次数超过阈值就自动升级。
4.2 转人工的核心实现:消息转发、回程路由与映射表
转人工的机制,是把用户的消息转发到一个只有客服在的 Telegram 群,客服在群里回复时引用原消息,机器人再把客服的回复发回给用户。这里最关键的是回程路由:机器人怎么知道群里客服回复的是哪个用户?
HUMAN_GROUP_ID = -1001234567890 # 客服群 chat_id,负号开头表示群组 forward_map = {} # key: 客服群里的消息 message_id,value: 对应用户 user_id async def forward_to_human(update: Update, context: ContextTypes.DEFAULT_TYPE): uid = update.effective_user.id forwarded = await update.message.forward(chat_id=HUMAN_GROUP_ID) forward_map[forwarded.message_id] = uid await update.message.reply_text("已转人工客服,请留意新消息。") async def on_group_reply(update: Update, context: ContextTypes.DEFAULT_TYPE): reply = update.message.reply_to_message if not reply or reply.message_id not in forward_map: return uid = forward_map[reply.message_id] customer_text = update.message.text # 客服发中文,机器人翻译成用户的语言再回传 translated = await translator.translate(customer_text, target=user_lang(uid), history=None) await context.bot.send_message(chat_id=uid, text=f"客服回复: {translated}")逻辑说明:用户消息被forward到客服群后,forwarded.message_id是这条消息在群里的新 ID,把它和用户 ID 存进映射表。客服在群里点“回复”那条消息,on_group_reply通过reply_to_message.message_id反查用户,完成回程路由。这个方案的好处是客服端不需要额外的面板,直接在群里操作即可,培训成本几乎为零。
参数说明:forward_map是内存字典,单实例部署没问题,多实例部署会各存各的,客服在群里回复可能查不到映射。生产环境把forward_map换成 Redis,用EXPIRE设置 30 分钟过期,避免内存无限增长。另外forward转发消息不携带原文的文本内容属性,客服在群里看到的是一条带原发送者名的引用消息,这是 Telegram 的预期行为。
4.3 上下文压缩:把最近N轮对话喂给翻译引擎
转人工之后,客服在群里回复“可以明天发货”,如果机器人只翻译这一句,用户可能看不懂“可以”指代什么。所以人工通道的翻译同样要带上下文,只是这里的上下文来自用户之前的对话记录,而不是客服群里的群聊。
我在实现时把上下文压缩做成一个独立函数,核心是只保留“用户问过什么”和“机器人/客服答过什么”,丢弃所有系统字段:
def build_context(history: deque, max_len: int = 6) -> list: ctx = [] for item in list(history)[-max_len:]: if item.get("role") in ("user", "assistant"): ctx.append({ "role": item["role"], "content": item["content"], }) return ctx这段代码在llm_translate里被复用:无论自动回复还是转人工回传,都拿同一份会话历史。压缩策略两句话:超过 6 轮只取最近 6 轮,长消息每轮只截前 200 字符。截断的做法是content[:200],虽然粗暴但省 token 效果明显。如果哪天真要精准压缩,再上摘要模型把历史概括成三句话——但客服场景里用户更在意的是“这次的问题解决没有”,而不是“你记得我上周问过什么”。
5. 避坑与排查:API申请失败、验证码收不到、消息乱序的现场处置
5.1 现象:BotFather 提示 error,token 申请失败
有段时间网上讨论 API 申请失败,常见报错是 BotFather 回复Sorry, too many attempts或者“username is already taken”。前者是因为你在短时间内反复创建机器人,Telegram 官方对创建频率有限流;后者是 username 撞车。
原因有两个:一是频繁试名字,二是没遵守命名规则。解决方式:username 只允许小写字母、数字、下划线且强制以bot结尾,先想好一个全球唯一的名字再提交;触发限流后不要继续点,等 15 到 30 分钟再试。如果你在多个设备同时操作同一个 BotFather,也会互相踢下线导致请求失败,保持同一会话操作。
5.2 现象:账号验证码收不到,注册卡在原地
这是 Telegram 账号注册的经典问题,报错通常表现为请求验证码后系统提示“wait a few minutes”,然后短信迟迟不来。原因分两类:一是号码被官方限流,常见于虚拟号段和频繁重复请求;二是本机时间不准,导致验证码加密校验对不上。
处理办法:使用真实手机号,确保号码带正确国家码,中国大陆号码写+86;不要重复点“重新发送”,每点一次限流时间会重置拉长;等 24 小时再试通常能恢复。如果你的运营账号已经登录过官方 App,机器人 API 的 token 不受注册验证的影响,两者独立,不需要为机器人单独注册号码。只是要注意,一个手机号能创建的 Bot 数量有限,别把客服机器人和个人号混在同一号码下。
5.3 现象:启动报 409,webhook 和轮询打架
run_polling启动时如果报409: Conflict: terminated by other getUpdates request,说明同一 token 有另一个实例在跑,或者之前设过 webhook 没清除。PTB 的轮询和 webhook 不能并存,这是 Telegram Bot API 层面的限制:同一时刻只允许一个 getUpdates 消费者。
先清 webhook 再启动:
curl "https://api.telegram.org/bot<你的TOKEN>/setWebhook?url="再查有没有残留进程:
ps aux | grep main.py kill <pid>清完等 3 秒再启动。如果你同时跑多个.py文件,检查是不是都用了同一个环境变量里的 token。上线部署时如果走 webhook 模式,要保证服务器有公网地址和合法证书,个人小项目我还是推荐 polling,省掉 HTTPS 证书和反向层的一大堆麻烦。
5.4 现象:群里的消息机器人看不见
机器人加入客服群后,只响应命令,不响应群里的普通文本。这不是代码 bug,是 Telegram 的机器人隐私模式默认开启。群主把机器人拉进群后,非命令消息默认不推送给机器人。
到 BotFather 里/mybots,选你的机器人,进 Bot Settings,再进 Group Privacy,把它从 Enabled 改成 Disabled。改完立即生效,不用重启服务。这里要区分两个概念:隐私模式管的是“群消息能否看到”,而群里 @机器人 的命令永远能看到。所以如果你只打算让用户在群里@机器人翻译,保持默认即可;如果你想机器人监控群里所有消息做自动回复,必须关隐私模式。
5.5 现象:翻译结果串台、消息顺序错乱
最危险的坑:两个用户同时发消息,A 用户收到的翻译文本里混着 B 用户的内容。根因是 PTB v20 的 handler 是并发执行的,多个任务共享同一个Translator实例,如果你的翻译函数内部用了模块级变量存中间状态,就会交叉污染。另外一个常见场景是同一个用户连发两条消息,异步处理导致“后发先至”,回复顺序颠倒。
解决分两层。第一层,翻译函数必须是纯函数,不读写任何模块级可变状态,我们的Translator类里只有配置没有会话缓存,天然安全。第二层,同一个用户的请求要串行处理,给每个用户一把锁:
import asyncio async def handle_text(update: Update, context: ContextTypes.DEFAULT_TYPE): lock = context.user_data.setdefault("lock", asyncio.Lock()) async with lock: text = update.message.text if not text: return translated = await translator.translate(text, target=user_lang(update.effective_user.id)) await update.message.reply_text(f"翻译: {translated}")context.user_data每个用户独享,所以每个用户有自己的锁,互不阻塞;同一用户的连发消息会被锁串行化,顺序就保住了。这里有个取舍:锁的粒度是用户,不是全局,否则性能会退化到所有用户排队。这个方案在单进程 polling 模式下完全够用,如果你上了多 worker 的 webhook 部署,锁要迁移到 Redis 的分布式锁,逻辑不变,载体换掉。
6. 部署上线与进阶:常驻守护、自动语言检测、多引擎协作
6.1 用 systemd 把机器人变成常驻服务
开发时终端里跑python3 main.py没问题,一关终端机器人就死。生产环境我用 systemd 管它,开机自启、崩溃自动拉起、日志统一进 journald:
[Unit] Description=Telegram AI Translate Bot After=network-online.target Wants=network-online.target [Service] User=www WorkingDirectory=/opt/tg_translate_bot ExecStart=/usr/bin/python3 main.py Restart=always RestartSec=5 Environment=PYTHONUNBUFFERED=1 EnvironmentFile=/opt/tg_translate_bot/.env [Install] WantedBy=multi-user.targetRestart=always是救命参数,进程异常退出 5 秒后自动拉起,半夜挂了你也不用起来。EnvironmentFile指向.env,密钥和令牌都放里面,不进代码仓库。日志用journalctl -u tgbot -f实时看,排错时能看到 PTB 的完整堆栈。
6.2 自动检测源语言:langdetect 与 fasttext 的取舍
翻译前先判断用户发的到底是不是外语,避免把中文翻译成中文的浪费。轻量级方案是用langdetect:
from langdetect import detect def need_translate(text: str, local_lang: str = "zh") -> bool: try: return detect(text) != local_lang except Exception: return True # 检测失败时保守起见,还是翻译langdetect对长文本识别准确率还行,短文本经常翻车,比如单字“好”可能被识别成英文。要求高的场景换fasttext-langdetect,模型体积大几十兆,但短文本准确率明显更高。我的取舍是:客服场景默认langdetect,因为翻译 API 本身有auto识别能力,多一次本地检测只是省成本,不是保正确。检测失败时返回True走翻译,宁多花一次调用也不漏翻,这个是血泪教训——漏翻一条客户投诉比多花几分钱严重得多。
6.3 多 AI 协作与冒烟压测:上线前该做的验证
最后一个进阶是把意图识别和翻译拆成两个模型协作:小模型判断用户消息是不是投诉、是否涉及退单这类高优问题,是才升级转人工,翻译仍然走便宜的后端。这就是“多 AI 协作”在客服机器人里最常见的架构——谁便宜谁干活,谁聪明谁决策,分工不重叠。
上线前用一个脚本模拟用户压力测试,看事件循环有没有卡死、翻译并发会不会报错:
import asyncio from telegram import Bot async def smoke(bot: Bot, chat_id: int, rounds: int = 100): for i in range(rounds): await bot.send_message(chat_id=chat_id, text=f"压力测试第 {i} 条:请问这个商品能发日本吗") await asyncio.sleep(0.1) # 控制发送速率,接近真人手速 async def main(): bot = Bot(token=os.getenv("BOT_TOKEN")) await smoke(bot, chat_id=123456789) asyncio.run(main())压测时观察两个指标:一是机器人是否每条都回了,二是回复顺序是否颠倒。顺序颠倒说明锁没生效,直接用第 5 章的方案修。压测结束后删掉测试聊天记录,别让脏数据留在客服群里。我自己的习惯是留一套SMOKE_CHAT_ID环境变量指向一个只有机器人和我的私有测试群,每次改完代码先发一轮冒烟,再切生产环境变量。这套机器人从骨架到上线要不了多少代码,真正的成本都在这些边界参数和异常分支里,希望这篇能帮你绕开我踩过的坑。
本文还有配套的精品资源,点击获取