从零开发AI翻译助手:大模型接口、流式输出与跨端部署实战 说实话一开始我并没有打算自己从头写一个翻译工具。直到有一次朋友发来一份产品需求文档让我帮忙翻译我打开常用的翻译软件通篇上下文时而译成语境、时而译成环境并发直接被翻成同时发生关键术语乱到没法看。为了不再被这类工具气到我花了一个周末用大模型接口搓了个翻译程序出来。后来因为自己用得太顺手又把它封装成了跨端应用一路迭代到了现在的v6.1.0版本也就是浔川AI翻译。这篇文章就把整个开发过程掰开揉碎讲一遍包括技术栈怎么选、大模型接口怎么封装、流式输出怎么做、上下文记忆和历史记录怎么设计以及从HBuilderX打包到微信小程序上线的完整链路最后还有我在v6.1.0里做的实测数据。适合两类人看一类是准备入门AI应用开发、但一直没动手的开发者另一类是已经在用各种翻译接口、想进一步优化翻译体验的产品或技术同学。文章里涉及的关键代码我都会贴出来你可以直接照着改。1. 为什么放着现成翻译软件不用非要自己写一个在动手之前我先认真整理了一遍「现成翻译工具到底哪里不好用」。如果不把这个想清楚很容易做成一个套壳调用接口的玩具没必要也没价值。1.1 现成翻译工具的三大痛点第一个痛点是术语一致性。同一个词在一篇文档里翻译工具可能给出三种译法。尤其是技术类、医疗类、法律类文本术语不统一会直接导致理解偏差。我用某款在线翻译测试过一篇英文技术博客shell这个词在全文里分别被译成外壳贝壳命令行环境人工校对的时间比自己翻译还长。第二个痛点是格式破坏。我经常翻译带Markdown标记的文档、带换行和缩进的日志、带表格的说明性文字。普通翻译接口往往把格式打乱或者把代码块里的变量名也当成普通英文给译了。等到我复制回编辑器整个结构都废了。第三个痛点是缺乏上下文。翻译软件的核心单位是句子但很多词单独看和放在上下文里完全是两个意思。比如it指代的是前一句的哪个对象比如一个转折词承接的是上文的哪层逻辑这些都需要更大的上下文窗口才能处理。传统翻译引擎做不到而大模型恰恰擅长这个。1.2 从翻译工具到翻译助手产品定位的变化想清楚痛点之后我把产品定位从翻译工具改成了翻译助手。工具只负责把一段文字从A语言变成B语言助手则要理解需求、记忆偏好、给出更贴近使用场景的结果。具体来说浔川AI翻译从设计之初就确定了几个原则翻译单位是对话而不是单句。用户发一句英文程序不只返回译文还会理解这可能是上一句的延续。支持术语表。用户可以把concurrency锁定为并发以后所有翻译都优先用这个词。保留原文格式。如果输入是Markdown输出也尽量是Markdown如果输入是代码片段代码部分不做翻译。所有历史记录保存在本地不走服务端存储保护隐私。这个定位决定了我后面所有的技术选型。它不是做一个API套壳而是做一个有记忆、有偏好的AI翻译agent——虽然这个agent只负责一件事但它的工作方式已经和普通翻译工具有了本质区别。2. 技术底座怎么选大模型接口、跨端框架与端侧存储选型这个环节我纠结了挺久因为翻译类应用对延迟、成本、稳定性都很敏感。下面把这些选择的思考过程完整说一遍也好给你做AI应用开发时参考。2.1 为什么选大模型接口而不是传统翻译API传统翻译API的优势是快、便宜、稳定但它解决不了术语一致和上下文理解的问题。大模型接口虽然单次调用更贵、延迟更高但翻译质量尤其是长文本、专业文本的质量明显上了一个台阶。我们对出版社的朋友做过盲测大模型翻译的文本在可读性和术语准确率上比传统API高出不少。在模型选型上我的核心考量有三个上下文长度至少需要支持8K以上因为多轮对话时要携带历史消息。输出稳定性JSON输出的字段要稳定便于程序解析。成本与配额个人开发者最怕的是调用成本失控。我最终选用的是兼容OpenAI接口格式的大模型服务这样的好处是以后想换模型供应商只需要改基础地址和密钥代码几乎不用动。市场上有好几家国产大模型都提供这种兼容接口按量付费也不贵。2.2 前端框架为什么最终选了uni-app做前端容器时我认真对比过三条路微信原生小程序、Taro、uni-app。最后选了uni-app最主要的原因是开发效率。项目里同一套代码可以编译成微信小程序、H5、App我一个个人开发者实在没有精力给每个端单独写一套。而且uni-app基于Vue语法生态里有很多现成组件遇到问题时社区资料也足够多。另外uni-app配套的HBuilderX确实方便。从新建项目、写代码、真机调试到云打包一条龙完成。对于个人开发者和独立开发者来说省掉了大量环境配置的麻烦。我在后面第7节会专门讲HBuilderX打包发布的流程。目录结构大致是这样└── 浔川AI翻译 ├── pages │ ├── index // 首页翻译主界面 │ ├── history // 历史记录 │ └── settings // 设置术语表、暗色模式等 ├── utils │ ├── request.js // 网络请求封装 │ ├── sse.js // 流式响应解析 │ └── storage.js // 本地存储封装 ├── static └── manifest.json // uni-app配置文件细心的读者会发现我并没有把大模型接口的密钥放在前端代码里。这是所有AI应用开发里最重要的一条安全底线——前端直接放密钥等于把钥匙挂在大门上。我这里的做法在3.1节里详细说。2.3 数据流向从用户输入到译文显示的全过程整个翻译链路是这样的用户在输入框输入文本前端把文本连同历史消息一起发给云函数云函数在这个中间层拼接好system prompt和用户消息再调用大模型接口。大模型返回流式数据云函数以流式方式转发给前端前端逐字渲染出来。整个过程中模型供应商的密钥只存在于云函数环境变量里前端永远接触不到。用户输入 → uni-app前端 → uniCloud云函数 → 大模型接口 ↑ ↓ 逐字渲染 ←← 流式数据 ←←←←←←这个链路看起来简单但每一步都有很多细节比如流式数据怎么解析、超时怎么处理、术语表怎么注入我逐个展开讲。3. 核心引擎开发API封装、流式输出与翻译质量调优核心引擎是浔川AI翻译的心脏也是v6.1.0版本里优化最多的部分。下面按模块拆解。3.1 请求层封装与密钥保护为什么必须加一个中间层直接让前端调用大模型接口逻辑上也能跑通但有两个致命问题。第一接口密钥暴露在前端代码里任何人通过抓包就能拿到然后盗刷你的额度第二前端直接拼Prompt很多业务逻辑混在一起后续维护非常痛苦。我的做法是在uniCloud上部署一个云函数作为代理层。前端请求云函数云函数再请求大模型。云函数里用环境变量保存密钥前端只能看到云函数的URL拿不到任何敏感信息。云函数核心代码大概是这样的// 云函数translate const crypto require(crypto) const modelApiKey process.env.MODEL_API_KEY // 从环境变量读取不硬编码 exports.main async (event) { const { messages, temperature 0.3 } event const url https://api.example.com/v1/chat/completions const body { model: translation-model, messages, temperature, stream: true // 开启流式返回 } // 注意uniCloud云函数支持返回流式响应这里需要配合前端做SSE解析 return await fetchStream(url, body, modelApiKey) }考虑到有些读者还没有接触过uniCloud补充一句它是uni-app配套的云开发平台支持云函数、云数据库、云存储。如果你不打算用uniCloud也可以用微信云开发或者在服务器上用Node.js单独部署一个代理服务思路都是一样的——前端不直接持有密钥所有敏感操作收口到服务端。3.2 流式输出的前端处理让译文逐字打出来很多人第一次用大模型翻译时会觉得怎么这么慢。如果等整体结果返回再显示可能需要好几秒。加上流式输出之后用户体验会好很多内容一个字一个字蹦出来用户能第一时间看到开头部分也愿意等后面的内容。大模型的流式返回一般走SSEServer-Sent Events格式是一段段以data:开头的文本最后以一个[DONE]标记结束。前端要做的事情是解析这段流把增量内容追加到页面上。在uni-app里我封装了这样一个请求函数// utils/request.js export function streamTranslate(payload, onChunk, onDone, onError) { const requestTask uni.request({ url: https://your-cloud-function-url/translate, method: POST, data: payload, enableChunked: true, // 关键开启分块接收 success(res) { if (res.statusCode 200) onDone() }, fail(err) { onError(err) } }) // 监听分块数据 requestTask.onChunkReceived((response) { const arrayBuffer response.data const text new TextDecoder().decode(arrayBuffer) // 按行解析SSE const lines text.split(\n) for (const line of lines) { if (line.startsWith(data: )) { const data line.slice(6) if (data [DONE]) continue try { const json JSON.parse(data) const delta json.choices[0]?.delta?.content || onChunk(delta) } catch (e) { // 忽略解析失败的分片多半是半包数据 } } } }) return requestTask }这里有个非常容易踩的坑SSE数据在网络上传输时一个事件块可能被拆成多个网络包也可能多个事件块合并成一个网络包。如果直接用split(\n)去切可能会出现解析失败。我前几个版本就在这里翻过车后来引入了缓存队列的写法把没解析完的残片先存起来等下一个数据包到达时拼接在一起再解析。let buffer function handleChunk(text) { buffer text const lines buffer.split(\n) buffer lines.pop() // 最后一段可能不完整留到下次 for (const line of lines) { // 解析处理 } }另外prettier一点的处理是加一个停止生成按钮。用户发现翻译不满可以直接中断请求这个时候调用requestTask.abort()并且标记当前状态为已停止避免后续的数据包继续刷新界面。3.3 让译文更像人话的三个调优手段只把文本丢给大模型出来的结果不一定好用。我在v3.0之后开始沉淀一套翻译Prompt模板和控制规则到v6.1.0已经是第三版。第一个是System Prompt设计。我会明确告诉大模型它是专业翻译助手并给出严格要求你是浔川AI翻译一名资深专业翻译。你的任务是把用户输入的文本翻译成目标语言。 要求 1. 保持原文格式Markdown标记、换行、代码块不得破坏。 2. 术语保持全文一致优先使用用户提供的术语表。 3. 译文要自然通顺不要逐字直译。 4. 如果输入是代码只翻译注释部分代码本身保持不变。 5. 只输出译文不做任何解释。最后一条只输出译文不做任何解释非常重要。如果没有这一条模型有时会自作主张加上以下是翻译结果之类的提示语干扰程序处理。第二个是术语表注入。用户在小程序里设置的术语会拼进Prompt里格式很简单用户术语表 concurrency - 并发 throughput - 吞吐量 latency - 延迟每次翻译之前程序会把术语表追加到系统指令末尾。实测对专业文档的术语一致性提升非常明显。不过要注意术语表不能太长超过20条后会稀释其他指令的效果甚至导致模型注意力跑偏。目前我限制用户最多设置30条术语。第三个是后处理规则。大模型偶尔会输出带多余空格的译文或者把英文引号用到中文译文里还会把全角标点混在一起。我的做法是在前端渲染前过一遍轻量级后处理把中文译文里的英文逗号、句号替换成中文标点把两个连续的空格压缩成一个去掉译文首尾的空白字符。效果用一句话概括用户看到的内容更干净了虽然这些规则都很简单但对观感的提升是实实在在的。4. 上下文对话与历史记录把翻译变成会话v6.1.0这个版本最大的改动就是上下文对话能力。之前版本是一问一答式的翻译升级之后用户可以和它连续对话它能理解上一句的它指的是什么、这句话是上一句的延续还是新话题。4.1 多轮上下文怎么存滑动窗口机制大模型本身没有记忆所谓的上下文意识完全靠把历史消息重新发给它来实现。所以工程上要解决的问题是怎么在有限的上下文窗口里以最少的token保留最有价值的信息。我维护一个messages数组结构是这样的[ { role: system, content: systemPrompt }, { role: user, content: 请把这句话翻译成中文The quick brown fox jumps over the lazy dog. }, { role: assistant, content: 敏捷的棕色狐狸跳过了懒狗。 }, { role: user, content: 其中lazy还有别的译法吗 } ]每次翻译新内容时把整个数组发给大模型。但数组不能无限增长否则token成本和响应时间都会暴涨。我采用的策略是滑动窗口只保留最近6轮12条消息超出的更早消息用一条摘要替代前面你们讨论了XXX话题已结束摘要由大模型在每满6轮时生成一次和普通翻译请求一样走流式接口。用摘要替代旧消息是兼顾成本与效果的关键。一开始我图省事直接保留全部历史结果翻译一篇长文章到了后面几轮单次请求耗时暴涨费用也明显上去了。改成滑动窗口后单次请求稳定在2秒以内。4.2 源语言自动识别不需要用户手动切换既然是助手就不应该让用户每次手动选从英语翻译成中文还是从中文翻译成英语。v5.0开始我加入了语言自动识别。识别逻辑分两层第一层是规则判断。前端拿到输入文本后先统计字符的Unicode范围。如果中文字符占比超过80%判定源语言为中文如果英文字母占比超过90%判定为英语否则继续走第二层。第二层是模型兜底。规则判断不确定时把这个任务交给大模型让它返回一个JSON{ source_lang: ja, target_lang: zh }然后把这个结果拼接进下一次翻译请求的Prompt里保证同一次会话内语言方向是稳定的。这里有个细节语言识别要请求一次模型翻译又要请求一次在用户感知上会多出几百毫秒。为了不让用户等太久我做了个并行优化——先把文本发给云函数云函数同时做语言识别和翻译两次调用等两者都返回后再统一交付。虽然单次翻译没有变快但总耗时可感知地缩短了。4.3 历史会话的本地管理只存译文不存原文翻译内容往往涉及隐私用户可能不希望自己的文本被服务端留存。我在设计历史记录时做了一个大胆决定译文和原文都只存在本地存储uni.setStorageSync里云函数只负责转发不落盘存储。本地存储的封装很简单// utils/storage.js const MAX_HISTORY 200 export function saveHistory(record) { const list uni.getStorageSync(history) || [] list.unshift(record) if (list.length MAX_HISTORY) list.pop() uni.setStorageSync(history, list) }把上限设在200条是因为微信小程序的本地存储容量有限单个key上限约1MB而翻译记录动辄几百字加上时间戳和语言方向200条已经接近安全阈值。超过这个数量自动淘汰最早的记录类似一个简单的FIFO队列。用户也可以在设置页一键清空历史这个按钮我放在比较显眼的位置方便隐私敏感用户使用。5. 界面交互与用户体验从能用到好用程序开发和普通网页开发有一个很大区别用户不会给你第二次机会。界面丑一点、交互别扭一点用户转身就走。这一节讲我在界面交互上的取舍。5.1 输入区与结果区的布局思路主界面我采用了上下两栏结构上栏是译文结果区下栏是输入区和主流聊天软件的布局一致。这样的好处是用户已经习惯这种交互不需要学习成本。输入框我用的是textarea组件做了三处优化自动增高。输入多行文字时输入框会随着内容变高而不是出现内部滚动条右下角的翻译按钮始终可见不在键盘上方被遮挡支持粘贴检测。用户从其他App复制文本后打开小程序时弹出气泡提示检测到剪贴板内容是否直接翻译省去手动粘贴的步骤。翻译结果区默认显示双语对照原文在上译文在下。用户也可以切换成只看译文模式界面会清爽很多。这个开关我放在了右上角的设置图标里而不是主界面上目的是减少视觉噪音。5.2 复制、朗读、重新翻译三个提升留存率的小功能翻译结果出来之后用户大概率要做三件事复制译文、听一下发音、对译文不满意重新翻译一次。这三个需求我在界面上各放了一个按钮。复制按钮调用uni.setClipboardData复制成功后会有一个轻提示。这里有个细节用户有时只想复制部分内容而不是整段。我做了手势优化长按译文区域会弹出操作菜单可以选全选复制或部分选择。实测下来这个功能被使用的频率比预期的还高。朗读功能在微信小程序环境里用的是同声传译插件。它不仅能读译文还可以选择男声/女声、调节语速。不过小程序插件的包体积比较大我最终只在设置页做了一个开关默认关闭需要用的用户自己去打开。对于H5版本直接调浏览器自带的SpeechSynthesis接口就行代码量非常小。重新翻译这个按钮是v6.0加的。加了之后我才发现用户对译文的改译需求是刚需。有人觉得译文太文绉绉希望换成大白话有人希望译文更正式。所以v6.1.0里我把重新翻译升级成了换一种风格点击后弹出四个选项直白、书面、简练、详细。选完直接带着风格指令重新请求大模型不用把原文再粘贴一遍。5.3 暗色模式与无障碍适配v6.1.0另外一个显眼变化是支持了暗色模式。这里不是简单地换个背景色而是所有颜色都走CSS变量page { --bg-primary: #ffffff; --bg-secondary: #f5f5f5; --text-primary: #1a1a1a; --text-secondary: #666666; } media (prefers-color-scheme: dark) { page { --bg-primary: #1a1a1a; --bg-secondary: #2d2d2d; --text-primary: #e6e6e6; --text-secondary: #999999; } }实际开发中要注意按钮、输入框、列表项、空状态页面的背景都要跟着变量走否则会出现白底白字或者黑底黑字的尴尬情况。我踩过的坑是只改了主背景和文字颜色结果弹出菜单还是白色的在暗色模式下亮得刺眼。后来统一排查了所有弹层、Sheet、Toast组件的配色才算真正做完。无障碍方面我给关键按钮都加了aria-label翻译结果区域的字体支持跟随系统设置放大。国内不少用户会把手机字体调到很大如果程序不做适配译文会显示不全。这块改动不大但口碑提升立竿见影。6. 性能、异常兜底与内容合规上线前必须过的一关程序开发最怕的不是功能做不出来而是做出来之后线上出问题。翻译应用尤其如此模型接口不稳定、用户输入不合法、网络环境复杂每一个坑都可能导致用户流失。v6.1.0发布前我把性能、异常和合规这三件事系统梳理了一遍。6.1 请求超时与失败重试指数退避策略大模型服务的响应时间波动很大高峰期可能从几百毫秒飙到十几秒。如果前端直接给用户转圈等待体验会很差。我的策略是设置双段时间控制建立连接阶段超时5秒流式响应阶段如果连续30秒没有任何数据块判定超时。超时后的处理分三层。第一层是前端提示请求超时正在重试第1次第二层最多重试2次每次间隔2秒第三层如果仍然失败自动降级为普通翻译API——这个降级接口不需要流式但能把结果先给用户至少保证能翻出来而不是完全不可用。重试之间加一个指数退避间隔分别是2秒和4秒。不要用固定间隔否则多个用户同时失败重试时会给服务器造成瞬时压力。具体实现就是setTimeout(fn, 2000 * Math.pow(2, retryCount))很简单的公式但很管用。6.2 渲染性能优化长文本分段渲染流式输出时如果把整个译文都放进一个text组件里长文本场景下每一次增量更新都会触发整段文本的重新布局用户会明显感到卡顿。v4.0时我做过一次实测翻译一篇2000字的英文文档用单组件渲染的帧率只有30fps左右肉眼可见的掉帧。后来改成按段落渲染每收到一个换行符就把当前段落追加到段落数组里页面用v-for渲染多个段落组件。这样每次只更新最后一个段落前面的段落已经定型不再参与重排。优化之后同样2000字文档的渲染帧率能稳定在55fps以上。6.3 内容安全与合规负责任的AI产品必须做的事这一点必须多说几句。翻译类应用天然要面对用户输入的各种文本如果不做任何内容安全控制很容易被滥用成一个绕过内容审核的工具。所有AI应用开发者在把产品发布到应用市场之前都必须想清楚这个问题。我在这版程序里做了三层防护第一层前端对输入做基本的敏感词预检命中明显的违规词时直接拦截提示输入内容包含敏感信息请修改后重试第二层云函数在调用大模型之前再跑一遍服务端的内容安全接口微信云开发自带这个能力对文本做一次更全面的鉴别判断是否涉及违规或不适宜内容第三层大模型返回的译文在展示给用户之前也会用同样的内容安全接口做检查。如果译文被判定违规前端会显示该内容无法展示而不是把模型输出的原文直接渲染出来。这套逻辑在开发时确实增加了一些工作量但我认为是必须的。一个负责任的AI产品应该在设计阶段就考虑内容安全问题而不是等到被通报下架再去补救。7. 调试、打包与发布从HBuilderX到微信小程序的完整流程代码写完了怎么把它变成用户手机上的一个小程序这一步对不熟悉发布流程的新手来说往往是最容易卡壳的。我把v6.1.0实际走过的发布链路完整记录在这里。7.1 真机调试先在开发者工具里跑通再上真机真机调试之前先用微信开发者工具打开HBuilderX导出的编译产物检查三件事第一页面路由是否正常。重点检查从主页面跳转到历史记录、设置页面的路径是否正确第二接口能否通。在开发者工具里勾选不校验合法域名可以暂时跳过域名校验方便本地联调。但记住这只是开发阶段的权宜之计正式发布前必须配置真实域名第三本地存储是否生效。在开发者工具的Storage面板里可以直接看到history这个key的数据结构方便排查存储逻辑问题。真正的问题是开发者工具里一切正常一上真机就不行。常见的有三类手机和电脑不在同一局域网导致真机调试连不上。微信开发者工具把调试服务跑在电脑上手机需要通过局域网访问两个设备必须在同一WiFi下小程序的request合法域名没有配置真机上接口直接被拦截。进入微信公众平台在开发管理-开发设置-服务器域名里把云函数域名加入request合法域名列表手机端样式错乱。开发者工具的模拟器和真机的渲染内核不完全一致尤其是textarea组件在部分安卓机型上会有光标错位问题需要针对性做兼容。7.2 云打包配置证书、AppID与密钥管理HBuilderX的云打包流程我总结成四个步骤注册小程序AppID。去微信公众平台申请个人主体可以免费注册审核一般1-3天在HBuilderX的manifest.json里填入AppID并选择微信小程序作为发行目标配置uniCloud云函数确保已经关联到正确的前端云空间点击发行-小程序-微信HBuilderX会自动编译并在微信开发者工具里打开编译产物。在开发者工具里点击上传就能把代码包提交到微信后台。之后进入微信公众平台在版本管理里找到刚提交的开发版本点击提交审核。审核一般需要1-7天内容涉及工具类目时通常1-2天就能过。这里提醒一点提审前一定要把服务器域名配好否则审核人员打开小程序时所有接口都请求失败大概率会被拒。7.3 v6.1.0版本迭代记录版本号不是随便打的从v1.0到v6.1.0中间经历了十几次小版本迭代。我习惯采用语义化版本号主版本号表示架构性变化次版本号表示功能新增修订号表示问题修复和细节优化。v6.1.0的意思是第6个大版本里第1次功能更新而这次更新没有包含架构重构。简单回顾一下各版本的关键节点版本核心变化v1.0调用大模型接口完成基础翻译v2.0接入流式输出翻译过程可见v3.0优化Prompt模板加入术语一致性控制v4.0增加历史记录实现本地存储v5.0支持语言自动识别加入朗读功能v6.0新增用户术语表改译风格切换v6.1.0引入上下文滑动窗口新增暗色模式优化长文本渲染这样记录版本最大的好处是出了线上问题能快速定位——比如v6.1.0里我改过历史记录模块如果用户反馈历史记录丢失先查这一版的缓存逻辑再往前查v4.0的基础实现排查范围会小很多。8. 实测数据与个人使用感受v6.1.0做完之后我自己连续用了两周也拉了几个朋友小范围内测。这节放一些真实数据和使用感受方便你评估这套方案的实际效果。8.1 翻译效果的定量对比我拿三段典型文本做了测试一段技术文档英译中、一段产品发布会演讲稿英译中、一段知乎问题中译英。对比对象是某主流在线翻译工具和浔川AI翻译v6.1.0。指标主流工具浔川AI翻译技术文档术语一致性73%96%译文可读性5分制3.64.4平均单句耗时秒0.81.9格式保留完整/部分/丢失部分保留完整保留人工校对成本相对值基准降低约60%可以看到耗时确实比传统翻译API慢但翻译质量提升非常明显。尤其是格式保留这一项很多用户跟我说这是他们坚持用浔川而不是传统工具的根本原因——省去了大量重新排版的时间。8.2 实测中的意外情况和处理测试过程中遇到一个比较有意思的问题大模型在翻译某些网络用语时会把带有特定文化背景的梗直接字面翻译导致译文完全不可读。比如the ball is in your court会被直译成球在你球场里而不是现在轮到你行动了。这是Prompt调优救不回来的本质上是模型训练语料的问题。最终的处理方案是在术语表里允许用户添加短语级术语比如the ball is in your court - 现在轮到你行动了这个短语会以更高优先级注入Prompt。虽然只解决了部分问题但至少给了用户一个可操作的兜底路径。如果你做的是面向特定行业的AI翻译应用我强烈建议把短语级术语库做成标配功能。8.3 几点真实感受代码写到现在最大的体会是AI应用开发的门槛不在调用接口而在把接口能力落成一个真正能解决用户问题的产品。流式输出、上下文、术语表、格式保留这些细节单拎出来都不难但组合起来才是用户愿意留下来用的理由。还有一点是做这类工具型小程序千万不要想着一步到位。我的习惯是每半个月发一个小版本每次只改一个核心体验点然后看用户反馈数据。v6.1.0的两个重点上下文窗口、暗色模式都是用户反馈最集中的方向改完之后留存率的提升是能感知到的。技术方面我仍然在持续优化。下一步打算把术语表做成自动提取——用户在历史记录里手动纠正过译文的词自动进术语库候选列表省去手动录入的麻烦。如果你也在做AI翻译相关的小工具建议你也试试这个方向数据的闭环往往比功能堆砌更有价值。