最初启动这个项目时,我有一个雄心勃勃的愿景:创建一个完全在客户端运行的人工智能系统,并保证用户数据的私密性。我的目标很简单:
将文本改写为更容易发音的短语 实时分析流畅度 给出讲话节奏的建议 提供互动角色扮演对话 确保零数据离开用户设备 我设想了一个系统,用户无需向服务器发送敏感的语音或文本数据即可获得人工智能协助。这听起来很完美。
现实却大相径庭。经过数周的调试,我解决了浏览器兼容性、内存崩溃以及内部库错误等问题,最终意识到梦想尚未实现。最终,我不得不删除所有客户端 AI 代码,转而采用纯服务器端方案。
第一阶段:下载灾难 我首先使用 transformer.js 进行了简单的设置:
import { pipeline } from '@xenova/transformers';
// Simple documentation example const generator = await pipeline('text-generation', 'Xenova/distilgpt2'); const response = await generator( "Rewrite this to be easier to say: creepy crawly crabs can be creative" ); 它看起来很简单。库文档承诺可以轻松实现基于浏览器的 AI,无需服务器。
但我的浏览器立即报错:
transformerService.js:29 Failed to initialize transformer model: SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON 这里发生了什么?
Transformer.js 尝试从 Hugging Face 的 CDN 下载模型文件。它没有收到 JSON 模型文件,而是收到了 HTML 404 页面。该库随后尝试将 HTML 解析为 JSON,从而导致语法错误。
我尝试了多种模型,认为其中一些可能有效:
const modelsToTry = [ { name: 'TinyLlama', task: 'text-generation', model: 'Xenova/TinyLlama-1.1B-Chat-v1.0' }, { name: 'GPT-2', task: 'text-generation', model: 'gpt2' }, { name: 'DistilGPT-2', task: 'text-generation', model: 'Xenova/distilgpt2' }, { name: 'FLAN-T5-small', task: 'text2text-generation', model: 'Xenova/flan-t5-small' } ]; 我甚至尝试手动配置 CDN:
import { pipeline, env } from '@xenova/transformers';
env.allowLocalModels = false; env.allowRemoteModels = true; env.remoteURL = 'cdn.huggingface.co/'; 什么都没用。很多宣传说兼容浏览器的模型根本就不存在,或者下载链接失效了。到现在为止,我已经花了两天时间才找到一个可以下载的模型。
第二阶段:记忆屠杀 终于,我成功下载了一些模型。我满怀希望,但下一个问题却接踵而至:内存崩溃。
const output = await generator("creepy crawly crabs can be creative"); 控制台错误:
transformerService.js:89 An error occurred during model execution: "RangeError: offset is out of bounds" 即使输入六个字也会发生这种情况。
我尝试了所有我能想到的参数调整:
// Conservative attempt const output = await generator(prompt, { max_new_tokens: 100, temperature: 0.7, do_sample: true, });
// More conservative const output = await generator(prompt, { max_length: 150, temperature: 0.1, do_sample: false, });
// Minimal const output = await generator(prompt, { max_length: 80, temperature: 0.1, do_sample: false, });
// Default call const output = await generator(prompt); 任何组合都行不通。浏览器内存限制导致内部张量分配错误。即使是“微型”模型也太大,无法在客户端执行。这个问题对我来说是无法解决的。
第三阶段:代币化恐怖 对于加载时没有崩溃的模型,我遇到了另一个奇怪的问题:标记器错误。
transformerService.js:132 Model execution failed: text.split is not a function TypeError: text.split is not a function 我仔细检查了输入内容三次:
async generateText(prompt, options = {}) { const stringPrompt = String(prompt || '').trim(); const truncatedPrompt = stringPrompt.length > 200 ? stringPrompt.substring(0, 200) + "..." : stringPrompt; const finalPrompt = String(truncatedPrompt);
const output = await generator(finalPrompt); } 控制台确认我的输入始终是一个字符串:
String prompt type: string "Process sentence: creepy crawly crabs can be creative" Final prompt type: string "Process sentence: creepy crawly crabs can be creative" 然而,transformer.js 还是崩溃了。内部 bug 导致该库将非字符串传递给了它自己的 tokenizer,无论我提供什么。
甚至最简单的测试也失败了:
try { const testOutput = await generator({ question: "What is this?", context: "This is a test." }); } catch (testError) { console.error('Even minimal test failed:', testError); } 此时,我意识到该库本身对于客户端使用而言存在根本性缺陷。
第四阶段:绝望的转变 我尝试切换到更简单的模型,例如分类或嵌入:
const modelsToTry = [ { name: 'Sentiment', task: 'sentiment-analysis', model: 'Xenova/distilbert-base-uncased-finetuned-sst-2-english' }, { name: 'Embeddings', task: 'feature-extraction', model: 'Xenova/all-MiniLM-L6-v2' }, ]; 它们成功加载,这给了我希望。也许我可以在情绪分析的基础上构建我的逻辑。
但是,当我执行它们时,同样的内存错误出现了:
transformerService.js:130 An error occurred during model execution: "RangeError: offset is out of bounds" 甚至连将情绪转化为言语治疗指导的后备方法也是不可能的:
convertSentimentToSpeech(sentimentOutput, originalPrompt) { const phrase = this.extractPhrase(originalPrompt);
if (originalPrompt.includes('Process sentence')) {
return For "${phrase}": Practice slow, controlled breathing. Break the phrase into smaller chunks.;
}
if (originalPrompt.includes('Rewrite for smoother speech')) {
const words = phrase.split(' ');
const alternatives = [
1. ${words.slice(0, Math.ceil(words.length / 2)).join(' ')},
2. ${phrase.toLowerCase()},
3. ${phrase.replace(/\b(and|but)\b/g, ',')}
];
return alternatives.join('\n');
}
return Guidance for "${phrase}": Use strategies and breathing techniques.;
}
无论我尝试什么,模型都会在返回任何输出之前崩溃。
第五阶段:突破点 这时,我做出了一个艰难的决定:彻底删除 transformer.js。
原本包含回退、初始化和错误处理的 350 多行复杂的客户端代码,现在变成了一个简单、可靠的服务器端 API 调用:
class TransformerService { constructor() {}
async rephrase(prompt, language = "en-US") { const response = await fetch('http://localhost:8000/rephrase', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt, language }), }); const data = await response.json(); return data.response; }
async fluencyCheck(inputText, language = "en-US") { const response = await fetch('http://localhost:8000/fluency-check', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ input: inputText, language }), }); const data = await response.json(); return data.response; } } 前端逻辑也大大简化了:
const getSuggestionsForPromptBox = useCallback(async () => { try { if (!prompt.trim()) return; setLoadingPromptBox(true);
const [suggestionsResponse, pacingResponse] = await Promise.all([
transformerService.rephrase(prompt, selectedLanguage),
transformerService.pacingAdvice(prompt, selectedLanguage)
]);
const lines = suggestionsResponse.split('\n')
.filter(line => line.trim())
.map(line => line.trim());
setPromptSuggestions(lines);
setPromptPacingAdvice(pacingResponse);
} catch (error) { console.error('Error in getSuggestionsForPromptBox:', error); setPromptSuggestions([]); setPromptPacingAdvice(''); } finally { setLoadingPromptBox(false); } }, [prompt, selectedLanguage]); 不再有混合回退。不再有崩溃。不再有内部库错误。
经验教训 浏览器 AI 尚未准备好投入生产 内存限制甚至会导致小型模型崩溃 内部库错误无法通过用户代码修复 模型可用性不可靠 许多“浏览器兼容”模型并不存在 下载链接经常断开 文档经常具有误导性 调试几乎不可能 错误发生在精简库代码的深处 堆栈跟踪很少指向可操作的修复 服务器端人工智能运行可靠 高性能和一致的响应 没有浏览器内存限制 更易于维护和调试 服务器端替代方案 切换到仅服务器的 AI 实现使一切变得更加简单和可靠。使用 FastAPI 和 Python AI 库:
from fastapi import FastAPI, Request import ollama
app = FastAPI()
@app.post("/rephrase") async def rephrase_input(request: Request): data = await request.json() prompt = data.get("prompt")
system_prompt = (
f"Rewrite this sentence to be easier to say: \"{prompt}\""
)
response = ollama.generate(model="llama3", prompt=system_prompt)
return {"response": response["response"]}
实施仅需十分钟。每次都完美无缺。
结论 经过数周与客户端 AI 的斗争后:
文本生成模型:彻底失败 分类模型:加载但在执行过程中崩溃 服务器端AI:可靠运行 有时,最好的工程决策是知道何时停止与技术对抗。在我的项目中,放弃客户端 AI 并仅使用服务器端 AI 带来了以下好处:
简单 可靠性 可维护性 一致、高质量的人工智能响应 完全私密的客户端人工智能的梦想仍然只是一个梦想。但将人工智能迁移到服务器后,每次都能发挥作用的实用人工智能是可以实现的。查看更多www.mxwd.cc