用 LangChain + DeepSeek + Qwen-MT-Image 构建图片翻译助手
1. 引言
在跨语言交流日益频繁的今天,图片中的文字翻译需求越来越常见。本文将带你从零构建一个图片翻译助手,它能够将上传图片中的文字从源语言翻译为目标语言,并保留原始排版。
为什么不用电商平台的通用 API? 传统电商平台提供的图片翻译 API 通常按调用次数收费,单次价格在 0.05~0.2 元/张 不等,且往往需要绑定企业认证、签署合同,个人开发者或小团队很难低成本接入。而本文方案采用 Qwen-MT-Image(约 0.0051 元/张),成本仅为电商 API 的 1/10~1/40,且无需企业资质,注册即可使用,真正实现「低成本、高可用」的图片翻译能力。
技术栈:
- Streamlit:快速搭建交互式前端
- LangChain:Agent 框架,编排工具调用
- DeepSeek Chat:作为 Agent 的 LLM 核心
- Qwen-MT-Image:阿里云通义千问图片翻译模型
- ImgBB:免费图床,用于获取图片公网 URL
最终效果:用户上传图片或输入图片 URL,选择源语言和目标语言,点击翻译即可获得翻译后的图片。
2. 环境准备与依赖安装
首先创建项目目录并安装依赖:
mkdir image-translator
cd image-translator
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
创建 requirements.txt:
streamlit>=1.28.0
langchain>=0.3.0
langchain-deepseek>=0.1.0
langchain-classic>=0.3.0
python-dotenv>=1.0.0
Pillow>=10.0.0
requests>=2.31.0
安装依赖:
pip install -r requirements.txt
3. 环境变量配置
在项目根目录创建 .env 文件,填入以下密钥:
DASHSCOPE_API_KEY=你的阿里云通义千问API密钥
DEEPSEEK_API_KEY=你的DeepSeek API密钥
DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
IMGBB_API_KEY=你的ImgBB API密钥
获取方式:
- DASHSCOPE_API_KEY:前往 阿里云百炼平台 创建 API Key
- DEEPSEEK_API_KEY:在 DeepSeek 官网 注册获取
- IMGBB_API_KEY:访问 ImgBB API 注册免费账号,在 Dashboard 中获取
4. 核心代码实现
4.1 导入依赖与加载环境变量
import os
import time
import json
import re
import requests
import streamlit as st
from dotenv import load_dotenv
from PIL import Image
from pydantic import BaseModel, Field
from typing import Optional
from langchain.tools import tool
from langchain_deepseek import ChatDeepSeek
from langchain_classic.agents import create_tool_calling_agent, AgentExecutor
from langchain_classic.prompts import ChatPromptTemplate, MessagesPlaceholder
import langchain
import langchain_core
# ---------- 打印版本(用于调试) ----------
print("langchain版本:", langchain.__version__)
print("langchain-core版本:", langchain_core.__version__)
# ---------- 加载环境变量 ----------
load_dotenv()
DASHSCOPE_API_KEY = os.getenv("DASHSCOPE_API_KEY")
DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY")
DEEPSEEK_BASE_URL = os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com/v1")
IMGBB_API_KEY = os.getenv("IMGBB_API_KEY")
4.2 配置 API 密钥
DASHSCOPE_API_KEY = os.getenv("DASHSCOPE_API_KEY")
DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY")
DEEPSEEK_BASE_URL = os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com/v1")
IMGBB_API_KEY = os.getenv("IMGBB_API_KEY")
4.3 实现 ImgBB 图床上传工具
当用户上传本地图片时,需要先将其上传到公网图床,才能传给 Qwen-MT-Image API:
def upload_to_imgbb(image_bytes, api_key: str) -> str:
"""
将图片二进制数据上传到 ImgBB,返回公网 URL
"""
if not api_key:
raise ValueError("未设置 IMGBB_API_KEY,请在 .env 文件中添加或前往 https://api.imgbb.com/ 注册获取")
url = "https://api.imgbb.com/1/upload"
payload = {"key": api_key}
files = {"image": image_bytes}
resp = requests.post(url, data=payload, files=files, timeout=30)
resp.raise_for_status()
data = resp.json()
if data.get("success"):
return data["data"]["url"]
else:
raise Exception(f"ImgBB 上传失败: {data}")
4.4 定义 LangChain Tool:图片翻译
这是核心工具函数,调用阿里云 Qwen-MT-Image 模型进行图片翻译:
# ---------- 1. 定义结构化输出模型 ----------
class TranslationResult(BaseModel):
status: str = Field(..., description="success 或 error")
image_url: Optional[str] = Field(None, description="翻译后的图片 URL(成功时必填)")
error_message: Optional[str] = Field(None, description="错误详情(失败时必填)")
# ---------- 2. 工具函数:上传图片到 ImgBB ----------
def upload_to_imgbb(image_bytes, api_key: str) -> str:
"""
将图片二进制数据上传到 ImgBB,返回公网 URL
"""
if not api_key:
raise ValueError("未设置 IMGBB_API_KEY,请在 .env 文件中添加")
url = "https://api.imgbb.com/1/upload"
payload = {"key": api_key}
files = {"image": image_bytes}
resp = requests.post(url, data=payload, files=files, timeout=30)
resp.raise_for_status()
data = resp.json()
if data.get("success"):
return data["data"]["url"]
else:
raise Exception(f"ImgBB 上传失败: {data}")
# ---------- 3. LangChain Tool(带重试和指数退避) ----------
@tool
def translate_image(image_url: str, source_lang: str = "zh", target_lang: str = "ru") -> str:
"""
将图片中的文字从源语言翻译为目标语言,保留原始排版。
返回 JSON 字符串,格式为 {"status": "success", "image_url": "..."}
或 {"status": "error", "error_message": "..."}
"""
if not image_url:
return json.dumps(TranslationResult(status="error", error_message="缺少 image_url 参数").dict())
create_url = "https://llm-2p1o6fdpikmzdwi8.cn-beijing.maas.aliyuncs.com/api/v1/services/aigc/image2image/image-synthesis"
headers = {
"Authorization": f"Bearer {DASHSCOPE_API_KEY}",
"Content-Type": "application/json",
"X-DashScope-Async": "enable"
}
payload = {
"model": "qwen-mt-image",
"input": {
"image_url": image_url,
"source_lang": source_lang,
"target_lang": target_lang
}
}
try:
# 步骤1:创建任务
response = requests.post(create_url, headers=headers, json=payload, timeout=30)
response.raise_for_status()
data = response.json()
task_id = data.get("output", {}).get("task_id")
if not task_id:
return json.dumps(TranslationResult(status="error", error_message=f"创建任务失败: {data}").dict())
# 步骤2:轮询查询任务状态(带指数退避重试)
query_url = f"https://llm-2p1o6fdpikmzdwi8.cn-beijing.maas.aliyuncs.com/api/v1/tasks/{task_id}"
max_attempts = 15 # 最多尝试15次
backoff = 2 # 初始等待秒数
for attempt in range(max_attempts):
time.sleep(backoff) # 首次等待2秒
qr = requests.get(query_url, headers={"Authorization": f"Bearer {DASHSCOPE_API_KEY}"})
if qr.status_code != 200:
try:
err = qr.json().get("message", qr.text)
except:
err = qr.text
return json.dumps(TranslationResult(status="error", error_message=f"查询任务失败: {err}").dict())
result = qr.json()
status = result.get("output", {}).get("task_status")
if status == "SUCCEEDED":
translated_url = result.get("output", {}).get("image_url")
if translated_url:
return json.dumps(TranslationResult(status="success", image_url=translated_url).dict())
else:
return json.dumps(
TranslationResult(status="error", error_message="任务完成但未返回图片 URL").dict())
elif status == "FAILED":
msg = result.get("output", {}).get("message", "无详细信息")
return json.dumps(TranslationResult(status="error", error_message=f"任务失败: {msg}").dict())
# 否则继续等待(PENDING 或 RUNNING)
# 指数退避:下次等待时间加倍,但不超过10秒
backoff = min(backoff * 1.5, 10)
# 超时未完成
return json.dumps(TranslationResult(status="error", error_message="翻译任务超时,请稍后重试").dict())
except requests.exceptions.RequestException as e:
return json.dumps(TranslationResult(status="error", error_message=f"API 调用失败: {str(e)}").dict())
except Exception as e:
return json.dumps(TranslationResult(status="error", error_message=f"发生未知错误: {str(e)}").dict())
4.5 创建 Agent
使用 LangChain 的 create_tool_calling_agent 构建 Agent,将翻译工具注册进去:
def create_agent():
llm = ChatDeepSeek(
model="deepseek-chat",
api_key=DEEPSEEK_API_KEY,
base_url=DEEPSEEK_BASE_URL,
temperature=0
)
tools = [translate_image]
prompt = ChatPromptTemplate.from_messages([
("system", """你是一个图片翻译助手。用户会提供图片 URL 和翻译需求。
请调用 translate_image 工具完成翻译。
支持的语言代码:
- 中文: zh, 英文: en, 日文: ja, 韩语: ko
- 西班牙语: es, 法语: fr, 葡萄牙语: pt, 阿拉伯语: ar
- 俄语: ru
注意:Qwen-MT-Image 只支持中文或英文与其他语种之间的互译,
不支持两个非中/英语种之间直接翻译。
"""),
("user", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad")
])
agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
handle_parsing_errors=True
)
return executor
4.6 Streamlit 前端界面
最后是用户交互界面,包含侧边栏设置、图片上传/URL输入、翻译按钮和结果展示:
# ---------- 5. 缓存包装器(基于输入参数缓存翻译结果) ----------
@st.cache_data(ttl=3600) # 缓存1小时
def translate_with_cache(image_url: str, source_lang: str, target_lang: str) -> str:
"""
调用 Agent 并返回原始输出字符串(JSON)。
缓存键为 (image_url, source_lang, target_lang)
"""
executor = create_agent()
result = executor.invoke({
"input": f"请将图片 {image_url} 中的文字从 {source_lang} 翻译为 {target_lang}"
})
return result.get("output", "")
# ---------- 6. Streamlit 前端 ----------
st.set_page_config(page_title="图片翻译助手", page_icon="🌎", layout="wide")
st.title("🌎 宽仔图片翻译")
st.markdown("上传图片或输入图片 URL,将图片中的文字翻译成目标语言")
# 侧边栏
with st.sidebar:
st.header("⚙️ 翻译设置")
source_lang = st.selectbox(
"源语言",
options=["zh", "en", "ja", "ko", "es", "fr", "pt", "ar"],
format_func=lambda x: {
"zh": "中文", "en": "英文", "ja": "日文", "ko": "韩语",
"es": "西班牙语", "fr": "法语", "pt": "葡萄牙语", "ar": "阿拉伯语"
}.get(x, x),
index=0
)
target_lang = st.selectbox(
"目标语言",
options=["ru", "en", "zh", "ja", "ko", "es", "fr", "pt", "ar"],
format_func=lambda x: {
"ru": "俄文", "zh": "中文", "en": "英文", "ja": "日文",
"ko": "韩语", "es": "西班牙语", "fr": "法语",
"pt": "葡萄牙语", "ar": "阿拉伯语"
}.get(x, x),
index=0
)
st.info("💡 提示:Qwen-MT-Image 支持中文或英文与其他语种互译")
# 主区域
input_method = st.radio("选择输入方式", ["上传图片", "图片 URL"], horizontal=True)
image_url = None
if input_method == "上传图片":
uploaded_file = st.file_uploader(
"点击上传图片",
type=["jpg", "jpeg", "png", "bmp", "tiff", "webp"],
help="支持 JPG、PNG、BMP、WEBP 等格式"
)
if uploaded_file is not None:
# 显示原图
image = Image.open(uploaded_file)
st.image(image, caption="原图", use_container_width=True)
# 检查 ImgBB API Key
if not IMGBB_API_KEY:
st.error("❌ 未设置 ImgBB API Key,请在 .env 文件中添加 IMGBB_API_KEY 或前往 https://api.imgbb.com/ 注册获取")
st.stop()
# 自动上传到 ImgBB
with st.spinner("正在上传图片到 ImgBB 图床..."):
try:
img_bytes = uploaded_file.getvalue()
image_url = upload_to_imgbb(img_bytes, IMGBB_API_KEY)
st.success("✅ 图片已上传,公网 URL 获取成功")
st.caption(f"URL: {image_url}")
except Exception as e:
st.error(f"❌ 上传失败: {str(e)}")
st.stop()
else: # 图片 URL 方式
image_url = st.text_input(
"输入图片公网 URL",
placeholder="https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/6962921671/p1006389.webp",
help="图片 URL 不能包含中文字符"
)
if image_url:
try:
st.image(image_url, caption="原图", use_container_width=True)
except Exception:
st.error("无法加载图片,请检查 URL 是否有效")
# 翻译按钮
if st.button("🚀 开始翻译", type="primary", disabled=not image_url):
if not DASHSCOPE_API_KEY:
st.error("❌ 请先设置 DASHSCOPE_API_KEY 环境变量")
st.stop()
if not DEEPSEEK_API_KEY:
st.error("❌ 请先设置 DEEPSEEK_API_KEY 环境变量")
st.stop()
with st.spinner("🔄 正在翻译图片,请稍候..."):
try:
# 调用带缓存的翻译函数
output = translate_with_cache(image_url, source_lang, target_lang)
# 解析 JSON
try:
data = json.loads(output)
if data.get("status") == "success":
translated_url = data.get("image_url")
if translated_url:
st.success("✅ 翻译完成!")
col1, col2 = st.columns(2)
with col1:
st.subheader("📷 原图")
st.image(image_url, use_container_width=True)
with col2:
st.subheader("🇷🇺 翻译后")
st.image(translated_url, use_container_width=True)
st.caption(f"翻译结果 URL:{translated_url}")
else:
st.error("❌ 翻译成功但未返回图片 URL,请检查 API 响应")
else:
error_msg = data.get("error_message", "未知错误")
st.error(f"❌ 翻译失败:{error_msg}")
except json.JSONDecodeError:
st.error(f"❌ Agent 返回了非 JSON 格式的内容,请检查 API 配置。原始输出:\n```\n{output}\n```")
with st.expander("查看完整输出"):
st.text(output)
except Exception as e:
st.error(f"❌ 发生错误:{str(e)}")
# 使用说明
with st.expander("📖 使用说明"):
st.markdown("""
### 支持的语言
- **源语言**:中文(zh)、英文(en)、日文(ja)、韩语(ko)、西班牙语(es)、法语(fr)、葡萄牙语(pt)、阿拉伯语(ar)
- **目标语言**:同上 + 俄文(ru)
### 上传方式
- **上传图片**:自动通过 ImgBB 图床获取公网 URL(需要 ImgBB API Key)
- **图片 URL**:直接输入任何公网可访问的图片链接
### 如何获取 ImgBB API Key?
1. 访问 https://api.imgbb.com/
2. 注册账号(免费)
3. 在 Dashboard 中找到 API Key
4. 将其添加到项目根目录的 `.env` 文件中:`IMGBB_API_KEY=你的key`
### 注意事项
1. Qwen-MT-Image 只支持**中文或英文**与其他语种之间的互译
2. 图片尺寸:15-8192 像素,宽高比 1:10 至 10:1
3. 图片大小不超过 100MB
4. 翻译结果会缓存 1 小时,相同图片重复翻译不会重复计费
### 费用说明
- **Qwen-MT-Image**:约 **0.0051 元/张**
- **ImgBB**:免费(有每日上传限制,个人使用足够)
- **DeepSeek Agent**:极少量文本 Token,成本极低
""")
}")
## 5. 运行应用
在项目根目录执行以下命令启动 Streamlit 应用:
```bash
python -m streamlit run app.py
浏览器会自动打开 http://localhost:8501,你将看到完整的图片翻译界面。
6. 使用说明
支持的语言
| 语言 | 代码 |
|---|---|
| 中文 | zh |
| 英文 | en |
| 日文 | ja |
| 韩语 | ko |
| 西班牙语 | es |
| 法语 | fr |
| 葡萄牙语 | pt |
| 阿拉伯语 | ar |
| 俄语 | ru |
注意:Qwen-MT-Image 只支持中文或英文与其他语种之间的互译,不支持两个非中/英语种之间直接翻译。
上传方式
- 上传图片:自动通过 ImgBB 图床获取公网 URL(需要 ImgBB API Key)
- 图片 URL:直接输入任何公网可访
费用说明
- Qwen-MT-Image:约 0.0051 元/张(仅为电商通用图片翻译 API 的 1/10~1/40)
- ImgBB:免费(有每日上传限制,个人使用足够)
- DeepSeek Agent:极少文本 Token,成本极低
- 电商 API 对比:主流电商平台图片翻译 API 单价约 0.05~0.2 元/张,且通常要求企业认证、预充值、签署服务协议,个人开发者门槛高。本文方案无需企业资质,注册即用,成本优势显著。0~1/40)
- ImgBB:免费(有每日上传限制,个人使用足够)
- DeepSeek Agent:极少文本 Token,成本极低
- 电商 API 对比:主流电商平台图片翻译 API 单价约 0.05~0.2 元/张,且通常要求企业认证、预充值、签署服务协议,个人开发者门槛高。本文方案无需企业资质,注册即用,成本优势显著。n,成本极低
7. 总结
本文实现了一个完整的图片翻译助手,核心流程如下:
- 用户上传图片或输入图片 URL
- 如果是上传图片,先通过 ImgBB 获取公网 URL
- LangChain Agent 调用 Qwen-MT-Image API 进行图片翻译
- 展示原图与翻译结果对比
你可以在此基础上扩展更多功能,例如:
- 支持批量翻译多张图片
- 添加翻译历史记录
- 集成更多图片翻译模型
- 优化 UI 交互体验
完整的源代码已在上文展示,效果如下:
更多推荐



所有评论(0)