最近我在做一个有趣的实验:如何用不到500行JavaScript代码,通过Hugging Face Inference API来协调多个小型语言模型(SLM)。这个项目的灵感来源于Jay Alammar的神经网络模拟器教程,但我在其中加入了一个独特的交互层——当你调整滑块试图降低误差值时,不同的AI模型会根据你的尝试历史生成实时评论。
这个系统的核心在于它的动态模型选择机制。我使用了包括Phi3、Llama、Mistral等多个参数量在数十亿级别的小型模型。每次请求到来时,系统会根据各模型的历史表现智能选择最合适的模型。如果某个模型开始频繁出错,它在下次被选中的概率就会自动降低。这种设计不仅实现了LLM的高可用性,还能通过调整temperature参数让单个模型在不同请求中表现出不同的创造性。
整个项目构建在以下技术栈上:
选择这个技术组合主要基于几个考虑:
项目的主要文件结构如下:
code复制├── Dockerfile # 容器构建配置
├── docker-compose.yml # 本地开发环境配置
├── server.js # 核心业务逻辑
└── README.md # 项目说明
其中server.js是整个系统的大脑,包含了所有业务逻辑和API端点。我采用Express框架构建RESTful API,主要实现了以下几个关键端点:
/error:核心业务端点,接收用户当前的误差值和尝试历史,返回AI生成的评论/models:调试用端点,展示各模型的使用统计信息/test:服务健康检查端点系统启动时,会初始化一个包含所有可用语言模型的配置对象。这是我的模型配置示例:
javascript复制const MODELS = {
phi3: {
name: "microsoft/phi-3-mini",
prompt: (error) => generatePhi3Prompt(error)
},
llama: {
name: "meta-llama/Llama-2-7b-chat",
prompt: (error) => generateLlamaPrompt(error)
},
mistral: {
name: "mistralai/Mistral-7B-v0.1",
prompt: (error) => generateMistralPrompt(error)
}
};
每个模型配置包含两个关键属性:
name:Hugging Face上的唯一模型标识符,格式为"组织/模型名"prompt:模型特定的提示词生成函数这种设计允许每个模型使用最适合它的提示词格式,而不必强制统一。在实际调用时,只需调用model.prompt(error)即可获得格式化好的提示词。
为了智能调度模型,系统会为每个模型维护一组统计信息:
javascript复制{
total: 0, // 总调用次数
errors: 0, // 失败次数
errop: 0, // 错误百分比 (errors/total)
pok: 1 // 成功率 (1 - errop)
}
这些统计数据会在每次模型调用后更新,成为模型选择算法的重要依据。初始化时,系统会为每个模型生成这些统计字段,并创建一个便于遍历的模型列表。
这是系统的主业务端点,接收两个参数:
error:用户当前尝试的误差值tentativas:用户的历史尝试记录(逗号分隔的数字)端点处理流程如下:
参数验证:
提示词生成:
javascript复制const prompt = selectedModel.prompt(error, tentativas);
模型调用:
javascript复制const response = await getModelAnswer(prompt);
响应处理:
为了提升模型响应的相关性,我设计了动态提示词生成策略。根据用户当前的误差值,系统会生成不同风格的提示词:
这种分段策略有几个优势:
每个模型都有自己特定的提示词模板函数,例如Phi3的提示词生成器:
javascript复制function generatePhi3Prompt(error) {
if (error > 2000) {
return `你现在的误差值是${error},这真是太糟糕了!请用幽默的方式调侃这个结果,不超过20个单词,以|fim|结尾。`;
} else if (error > 450) {
return `误差值${error}还有改进空间。请给出具体建议,不超过20个单词,以|fim|结尾。`;
} else {
return `做得不错!误差值${error}已经很接近目标了。请给予鼓励,不超过20个单词,以|fim|结尾。`;
}
}
系统采用基于表现的动态权重算法来选择模型。核心逻辑在updateProbs函数中实现:
pok = 1 - (errors/total)totalPok = sum(pok for all models)probability = pok / totalPok举例说明:
系统会根据模型的实际表现动态调整其权重:
javascript复制// 如果响应时间超过2.5秒,轻微增加错误计数
if (responseTime > 2500) {
currentModel.stats.errors += 0.2;
}
// 如果响应时间低于900ms,轻微减少错误计数
if (responseTime < 900) {
currentModel.stats.errors = Math.max(0, currentModel.stats.errors - 0.1);
}
这种设计使得系统能够:
getModelAnswer函数负责实际调用Hugging Face Inference API:
javascript复制async function getModelAnswer(prompt, modelName) {
const url = `https://api-inference.huggingface.co/models/${modelName}`;
const data = {
inputs: prompt,
parameters: {
max_new_tokens: 70,
temperature: 0.5
}
};
const response = await fetch(url, {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.HF_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify(data)
});
return response;
}
关键参数说明:
max_new_tokens: 70:限制生成内容长度temperature: 0.5:平衡生成结果的创造性和稳定性当主选模型调用失败时,系统会自动尝试下一个候选模型:
javascript复制let retryCount = 0;
while (retryCount < ModelList.length) {
const model = getNextModel();
try {
const response = await callModel(model, prompt);
if (response.ok) {
return processResponse(response);
}
} catch (error) {
model.stats.errors++;
}
retryCount++;
}
throw new Error("All models failed");
这种设计确保了单个模型故障不会影响整体服务可用性。
项目使用Docker容器化,便于在Hugging Face Spaces上部署。Dockerfile配置如下:
dockerfile复制FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8080
CMD ["node", "server.js"]
关键优化点:
docker-compose.yml简化了本地测试:
yaml复制version: '3'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- HF_TOKEN=${HF_TOKEN}
通过环境变量注入Hugging Face访问令牌,确保开发与生产环境一致性。
在实际开发中,我总结了以下几点重要经验:
提示词工程至关重要:
模型调度策略优化:
性能监控与调试:
对于想要扩展这个项目的开发者,我建议考虑:
这个项目展示了如何用简洁的代码实现强大的LLM编排功能。通过智能调度多个小型模型,我们既获得了高可用性,又保持了响应质量,同时避免了依赖单一大型模型的高成本。