Koa.js 与大语言模型集成实践
引言
随着大语言模型(LLM)的快速发展,将 AI 能力集成到 Web 应用已成为趋势。本文将介绍如何在 Koa.js 框架中集成大语言模型,构建智能化的 Web 服务。
架构设计
Koa.js 与 LLM 集成的整体架构:
| 组件 | 职责 | 技术选型 |
|---|---|---|
| API 网关 | 请求路由、认证、限流 | Koa + koa-router |
| 提示词管理 | 模板管理、上下文管理 | 自研/ LangChain |
| 模型适配层 | 多模型切换、成本优化 | OpenAI/Anthropic/本地模型 |
| 缓存层 | 响应缓存、会话管理 | Redis |
核心代码实现
LLM 服务封装:
// LLM 服务基类
class LLMService {
constructor(config) {
this.config = config;
this.client = null;
this.cache = new Map();
}
// 发送聊天请求
async chat(messages, options = {}) {
const cacheKey = this.getCacheKey(messages, options);
// 检查缓存
if (options.useCache !== false) {
const cached = await this.getFromCache(cacheKey);
if (cached) {
return cached;
}
}
// 调用模型
const response = await this.sendRequest(messages, options);
// 缓存响应
if (options.useCache !== false) {
await this.setCache(cacheKey, response, options.cacheTTL);
}
return response;
}
// 发送请求(子类实现)
async sendRequest(messages, options) {
throw new Error('must be implemented by subclass');
}
// 获取缓存键
getCacheKey(messages, options) {
const content = JSON.stringify({ messages, options });
return crypto.createHash('md5').update(content).digest('hex');
}
// 获取缓存
async getFromCache(key) {
const cached = this.cache.get(key);
if (cached && cached.expire > Date.now()) {
return cached.data;
}
this.cache.delete(key);
return null;
}
// 设置缓存
async setCache(key, data, ttl = 3600000) {
this.cache.set(key, {
data,
expire: Date.now() + ttl
});
}
}
// OpenAI 服务实现
class OpenAIService extends LLMService {
constructor(config) {
super(config);
this.client = new OpenAI({
apiKey: config.apiKey,
baseURL: config.baseURL // 支持代理/自定义端点
});
}
async sendRequest(messages, options = {}) {
const params = {
model: options.model || this.config.defaultModel,
messages: messages.map(m => ({
role: m.role,
content: m.content
})),
temperature: options.temperature || 0.7,
max_tokens: options.maxTokens || 2048,
top_p: options.topP || 1,
frequency_penalty: options.frequencyPenalty || 0,
presence_penalty: options.presencePenalty || 0
};
// 流式响应
if (options.stream) {
return this.streamChat(params);
}
const response = await this.client.chat.completions.create(params);
return this.parseResponse(response);
}
// 流式响应处理
async *streamChat(params) {
const stream = await this.client.chat.completions.create({
...params,
stream: true
});
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content;
if (content) {
yield content;
}
}
}
parseResponse(response) {
return {
content: response.choices[0].message.content,
role: response.choices[0].message.role,
usage: {
prompt: response.usage.prompt_tokens,
completion: response.usage.completion_tokens,
total: response.usage.total_tokens
},
model: response.model,
id: response.id
};
}
}
// 多模型路由
class ModelRouter {
constructor(config) {
this.services = new Map();
this.routingRules = config.routingRules || [];
this.defaultService = config.defaultService;
this.initServices(config.providers);
}
initServices(providers) {
for (const [name, provider] of Object.entries(providers)) {
switch (provider.type) {
case 'openai':
this.services.set(name, new OpenAIService(provider.config));
break;
case 'anthropic':
this.services.set(name, new AnthropicService(provider.config));
break;
case 'local':
this.services.set(name, new LocalLLMService(provider.config));
break;
}
}
}
// 选择合适的服务
selectService(context) {
for (const rule of this.routingRules) {
if (this.matchRule(rule, context)) {
return this.services.get(rule.service);
}
}
return this.services.get(this.defaultService);
}
matchRule(rule, context) {
if (rule.condition === 'always') return true;
if (rule.condition === 'model') {
return rule.models.includes(context.model);
}
if (rule.condition === 'prompt_tokens') {
return context.promptTokens > rule.threshold;
}
return false;
}
// 统一聊天接口
async chat(messages, options = {}) {
const service = this.selectService({
model: options.model,
promptTokens: this.countTokens(messages)
});
return await service.chat(messages, options);
}
countTokens(messages) {
// 简单估算
return messages.reduce((sum, m) =>
sum + Math.ceil(m.content.length / 4), 0);
}
}
提示词工程
提示词模板管理:
// 提示词管理器
class PromptManager {
constructor() {
this.templates = new Map();
this.defaultTemplate = null;
}
// 注册提示词模板
register(name, template, description = '') {
this.templates.set(name, {
name,
template,
description,
variables: this.extractVariables(template),
examples: []
});
}
// 提取变量
extractVariables(template) {
const matches = template.match(/\{\{(\w+)\}\}/g);
if (!matches) return [];
return [...new Set(matches.map(m => m.replace(/\{\{|\}\}/g, '')))];
}
// 渲染模板
render(name, context) {
const template = this.templates.get(name);
if (!template) {
throw new Error(`Template ${name} not found`);
}
// 检查必需变量
const missing = template.variables.filter(v => !(v in context));
if (missing.length > 0) {
throw new Error(`Missing variables: ${missing.join(', ')}`);
}
let prompt = template.template;
// 替换变量
for (const [key, value] of Object.entries(context)) {
prompt = prompt.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value);
}
return prompt;
}
// 构建对话消息
buildMessages(name, context, history = []) {
const systemPrompt = this.render(name, context);
const messages = [
{ role: 'system', content: systemPrompt }
];
// 添加历史对话
messages.push(...history.map(h => ({
role: h.role,
content: h.content
})));
// 添加当前用户输入
messages.push({
role: 'user',
content: context.userInput || context.input || ''
});
return messages;
}
// 添加示例
addExample(name, input, output) {
const template = this.templates.get(name);
if (template) {
template.examples.push({ input, output });
}
}
// 生成带示例的提示词
renderWithExamples(name, context) {
const template = this.templates.get(name);
let prompt = this.render(name, context);
if (template.examples.length > 0) {
prompt += '\n\n请参考以下示例:\n\n';
for (const example of template.examples) {
prompt += `用户输入:${example.input}\n`;
prompt += `助手回复:${example.output}\n\n`;
}
}
return prompt;
}
}
// 对话上下文管理
class ConversationContext {
constructor(config) {
this.maxHistory = config.maxHistory || 10;
this.maxTokens = config.maxTokens || 4000;
this.conversations = new Map();
}
// 创建会话
createSession(sessionId, systemPrompt = '') {
this.conversations.set(sessionId, {
systemPrompt,
messages: [],
metadata: {
createdAt: Date.now(),
updatedAt: Date.now()
}
});
return sessionId;
}
// 添加消息
addMessage(sessionId, role, content) {
const conversation = this.conversations.get(sessionId);
if (!conversation) {
throw new Error(`Session ${sessionId} not found`);
}
conversation.messages.push({
role,
content,
timestamp: Date.now()
});
// 修剪过长的历史
this.trimHistory(sessionId);
conversation.metadata.updatedAt = Date.now();
}
// 修剪历史
trimHistory(sessionId) {
const conversation = this.conversations.get(sessionId);
// 限制消息数量
while (conversation.messages.length > this.maxHistory) {
conversation.messages.shift();
}
// 限制 token 数量
while (this.countTokens(conversation.messages) > this.maxTokens) {
conversation.messages.shift();
}
}
// 计算 token 数
countTokens(messages) {
return messages.reduce((sum, m) =>
sum + Math.ceil(m.content.length / 4), 0);
}
// 获取对话历史
getMessages(sessionId, includeSystem = true) {
const conversation = this.conversations.get(sessionId);
if (!conversation) return [];
const messages = [];
if (includeSystem && conversation.systemPrompt) {
messages.push({
role: 'system',
content: conversation.systemPrompt
});
}
return messages.concat(conversation.messages);
}
// 会话摘要
async summarize(sessionId, llmService) {
const messages = this.getMessages(sessionId, false);
const summaryPrompt = `请简洁总结以下对话的要点:\n\n${
messages.map(m => `${m.role}: ${m.content}`).join('\n')
}`;
const result = await llmService.chat([
{ role: 'user', content: summaryPrompt }
]);
return result.content;
}
}
最佳实践建议
- 缓存策略:合理设置缓存策略,减少重复请求降低成本
- 流式响应:长文本响应使用流式输出,提升用户体验
- 错误处理:做好降级处理和重试机制,提升服务稳定性
- 成本控制:根据请求复杂度选择合适的模型
- 安全防护:过滤用户输入,防止提示词注入攻击
总结
Koa.js 与大语言模型集成为应用带来智能化的同时,也需要关注:
- 架构设计:合理设计模型路由和提示词管理
- 成本优化:通过缓存和模型选择控制使用成本
- 用户体验:流式响应和进度提示提升体验
- 安全防护:做好输入过滤和安全验证
通过本文介绍的技术方案,可以在 Koa.js 中快速构建 AI 驱动的应用服务。