news 2026/3/21 3:40:58

Node.js后端集成Baichuan-M2-32B医疗问答API指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Node.js后端集成Baichuan-M2-32B医疗问答API指南

Node.js后端集成Baichuan-M2-32B医疗问答API指南

1. 为什么选择Baichuan-M2-32B构建医疗问答服务

在医疗健康领域,准确、可靠且响应迅速的AI辅助系统正变得越来越重要。Baichuan-M2-32B作为百川智能推出的医疗增强推理模型,专为真实世界的医疗推理任务设计,它基于Qwen2.5-32B基座,通过创新的大型验证器系统进行深度优化,在HealthBench评测中取得了60.1分的优异成绩,超越了众多开源及部分闭源模型。

与通用大模型不同,Baichuan-M2-32B在临床诊断思维、患者交互能力和医学常识方面表现出色。它能理解复杂的症状描述,识别潜在的疾病关联,并以专业但易懂的方式提供参考信息。更重要的是,它支持4-bit量化,在RTX4090单卡上即可高效部署,这对需要控制硬件成本的医疗应用开发团队来说是个关键优势。

Node.js作为现代Web后端开发的主流选择,凭借其非阻塞I/O特性和丰富的生态系统,非常适合构建高并发、低延迟的API服务。将Baichuan-M2-32B与Node.js结合,不仅能快速搭建起一个稳定可靠的医疗问答接口,还能轻松集成JWT鉴权、流式响应等现代Web开发必备功能,为后续构建患者管理平台、在线问诊系统或健康知识库打下坚实基础。

2. 环境准备与模型服务部署

2.1 Node.js安装及环境配置

在开始集成之前,确保你的开发环境已正确配置。Node.js版本建议使用18.x或20.x长期支持版本,这些版本对现代JavaScript特性和异步处理提供了最佳支持。

首先检查当前Node.js和npm版本:

node --version npm --version

如果需要安装或升级,推荐使用nvm(Node Version Manager)来管理多个Node.js版本:

# macOS/Linux安装nvm curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash # Windows用户可下载Node.js官方安装包,或使用nvm-windows

安装完成后,创建项目目录并初始化:

mkdir medical-ai-api cd medical-ai-api npm init -y

安装核心依赖:

npm install express axios cors helmet morgan jsonwebtoken bcryptjs dotenv npm install --save-dev nodemon

2.2 模型服务部署方案选择

Baichuan-M2-32B有多种部署方式,根据你的硬件条件和性能需求选择最适合的方案:

方案一:vLLM高性能推理服务(推荐)vLLM是目前最高效的LLM推理引擎之一,特别适合生产环境。它支持Baichuan-M2-32B的GPTQ-Int4量化版本,能在单张RTX4090上实现高吞吐量。

安装vLLM并启动服务:

pip install vllm --pre --extra-index-url https://wheels.vllm.ai/nightly # 启动API服务(使用GPTQ量化版本) vllm serve baichuan-inc/Baichuan-M2-32B-GPTQ-Int4 \ --host 0.0.0.0 \ --port 8000 \ --tensor-parallel-size 1 \ --max-model-len 32768 \ --enable-prefix-caching

方案二:Xinference轻量级部署Xinference提供了更友好的CLI界面,适合快速验证和中小规模部署:

pip install xinference # 启动Xinference服务 xinference-local --host 0.0.0.0 --port 9997 # 在另一个终端中注册并启动模型 xinference launch --model-engine vllm \ --model-name Baichuan-M2 \ --size-in-billions 32 \ --model-format gptq \ --quantization Int4

方案三:本地Transformers加载(开发调试用)对于开发和调试阶段,可以直接使用Transformers库加载模型,但请注意这需要大量GPU显存:

from transformers import AutoTokenizer, AutoModelForCausalLM import torch model = AutoModelForCausalLM.from_pretrained( "baichuan-inc/Baichuan-M2-32B", trust_remote_code=True, device_map="auto", torch_dtype=torch.float16 ) tokenizer = AutoTokenizer.from_pretrained("baichuan-inc/Baichuan-M2-32B")

无论选择哪种方案,最终目标都是获得一个可通过HTTP访问的OpenAI兼容API端点,如http://localhost:8000/v1/chat/completions

3. RESTful API接口设计与实现

3.1 核心路由与请求结构

我们采用Express框架构建RESTful API,遵循清晰的资源命名规范。医疗问答服务的核心是/api/v1/medical/chat端点,它接受标准的聊天消息格式。

创建主应用文件app.js

const express = require('express'); const cors = require('cors'); const helmet = require('helmet'); const morgan = require('morgan'); const { createMedicalChatRouter } = require('./routes/medicalChat'); const app = express(); const PORT = process.env.PORT || 3000; // 安全中间件 app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'"], styleSrc: ["'self'", "'unsafe-inline'"] } } })); // CORS配置 app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'], credentials: true })); // 日志记录 app.use(morgan('combined')); // 解析JSON请求体 app.use(express.json({ limit: '10mb' })); app.use(express.urlencoded({ extended: true, limit: '10mb' })); // 健康检查端点 app.get('/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString(), version: '1.0.0' }); }); // 医疗问答路由 app.use('/api/v1/medical', createMedicalChatRouter()); // 404处理 app.use('*', (req, res) => { res.status(404).json({ error: 'Route not found' }); }); // 全局错误处理 app.use((err, req, res, next) => { console.error('Unhandled error:', err); res.status(500).json({ error: 'Internal server error' }); }); app.listen(PORT, () => { console.log(`Medical AI API server running on port ${PORT}`); }); module.exports = app;

3.2 医疗问答路由实现

创建routes/medicalChat.js文件,实现核心的问答逻辑:

const express = require('express'); const axios = require('axios'); const { verifyToken } = require('../middleware/auth'); const { validateMedicalRequest } = require('../middleware/validation'); const createMedicalChatRouter = () => { const router = express.Router(); // POST /api/v1/medical/chat - 处理医疗问答请求 router.post('/chat', verifyToken, validateMedicalRequest, async (req, res) => { try { const { messages, stream = false, max_tokens = 2048, temperature = 0.3 } = req.body; // 构建vLLM兼容的请求体 const vllmRequestBody = { model: "baichuan-m2-32b", messages: messages.map(msg => ({ role: msg.role, content: msg.content })), stream, max_tokens, temperature, top_p: 0.95, presence_penalty: 0.1, frequency_penalty: 0.1 }; // 调用vLLM服务 const vllmResponse = await axios.post( 'http://localhost:8000/v1/chat/completions', vllmRequestBody, { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.VLLM_API_KEY || 'dummy-key'}` }, timeout: 300000 // 5分钟超时,医疗推理可能需要较长时间 } ); // 处理流式响应 if (stream) { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); vllmResponse.data.on('data', (chunk) => { try { const data = chunk.toString().trim(); if (data.startsWith('data:')) { const jsonStr = data.substring(5).trim(); if (jsonStr && jsonStr !== '[DONE]') { const parsed = JSON.parse(jsonStr); const content = parsed.choices?.[0]?.delta?.content || ''; if (content) { res.write(`data: ${JSON.stringify({ content })}\n\n`); } } } } catch (e) { console.error('Error parsing stream chunk:', e); } }); vllmResponse.data.on('end', () => { res.write('data: [DONE]\n\n'); res.end(); }); vllmResponse.data.on('error', (err) => { console.error('Stream error:', err); res.status(500).json({ error: 'Stream processing error' }); }); return; } // 处理普通响应 const { choices } = vllmResponse.data; const responseMessage = choices?.[0]?.message?.content || '抱歉,我无法生成回答。'; // 添加医疗免责声明 const finalResponse = `${responseMessage}\n\n **医疗免责声明**:本回答仅供参考,不能替代专业医疗诊断和治疗建议。如有紧急健康问题,请立即联系医生或前往医院就诊。`; res.json({ success: true, message: finalResponse, usage: vllmResponse.data.usage || {}, timestamp: new Date().toISOString() }); } catch (error) { console.error('Medical chat error:', error.response?.data || error.message); if (error.response?.status === 429) { return res.status(429).json({ error: '请求过于频繁,请稍后再试' }); } if (error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT') { return res.status(503).json({ error: '模型服务暂时不可用,请检查vLLM服务状态' }); } res.status(500).json({ error: '处理医疗问答请求时发生错误' }); } }); return router; }; module.exports = { createMedicalChatRouter };

3.3 请求验证与安全中间件

创建middleware/validation.js确保传入的医疗请求符合规范:

const { body, validationResult } = require('express-validator'); // 验证医疗问答请求 exports.validateMedicalRequest = [ body('messages') .isArray({ min: 1, max: 20 }) .withMessage('messages必须是包含1-20条消息的数组'), body('messages.*.role') .isIn(['user', 'assistant', 'system']) .withMessage('消息角色必须是user、assistant或system之一'), body('messages.*.content') .isString() .isLength({ min: 1, max: 4000 }) .withMessage('消息内容长度必须在1-4000字符之间'), body('messages.*.content') .custom((content) => { // 检查是否包含明显的医疗紧急情况关键词 const emergencyKeywords = [ '胸痛', '呼吸困难', '昏迷', '抽搐', '大出血', '严重过敏', '中风', '心梗', '休克', '窒息', '中毒', '自杀', '自残' ]; const hasEmergency = emergencyKeywords.some(keyword => content.includes(keyword) || content.toLowerCase().includes(keyword.toLowerCase()) ); if (hasEmergency) { throw new Error('检测到紧急医疗情况,请立即拨打急救电话或前往医院'); } return true; }), body('stream') .optional() .isBoolean() .withMessage('stream参数必须是布尔值'), body('max_tokens') .optional() .isInt({ min: 128, max: 8192 }) .withMessage('max_tokens必须在128-8192之间'), body('temperature') .optional() .isFloat({ min: 0.0, max: 1.0 }) .withMessage('temperature必须在0.0-1.0之间'), // 最终验证 (req, res, next) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ error: '请求参数验证失败', details: errors.array() }); } next(); } ];

4. JWT鉴权与用户认证系统

4.1 JWT令牌生成与验证

医疗数据敏感性要求严格的访问控制。我们使用JWT(JSON Web Token)实现无状态认证,确保只有授权用户才能访问医疗问答API。

创建middleware/auth.js

const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); const { promisify } = require('util'); // JWT密钥应从环境变量读取,生产环境务必使用强密钥 const JWT_SECRET = process.env.JWT_SECRET || 'medical-ai-secret-key-change-in-production'; const JWT_EXPIRE = process.env.JWT_EXPIRE || '30d'; // 生成JWT令牌 exports.generateToken = (userId, username, role = 'user') => { return jwt.sign( { userId, username, role }, JWT_SECRET, { expiresIn: JWT_EXPIRE } ); }; // JWT验证中间件 exports.verifyToken = async (req, res, next) => { try { let token; // 从Authorization头获取token if ( req.headers.authorization && req.headers.authorization.startsWith('Bearer') ) { token = req.headers.authorization.split(' ')[1]; } // 从cookie获取token(可选) if (!token && req.cookies && req.cookies.token) { token = req.cookies.token; } // 如果没有token,返回401 if (!token) { return res.status(401).json({ error: '访问被拒绝:缺少认证令牌' }); } // 验证token const decoded = await promisify(jwt.verify)(token, JWT_SECRET); // 将解码后的用户信息附加到请求对象 req.user = decoded; next(); } catch (error) { console.error('JWT验证错误:', error); if (error.name === 'TokenExpiredError') { return res.status(401).json({ error: '认证令牌已过期,请重新登录' }); } if (error.name === 'JsonWebTokenError') { return res.status(401).json({ error: '无效的认证令牌' }); } res.status(401).json({ error: '认证失败' }); } }; // 角色权限检查中间件 exports.requireRole = (...roles) => { return (req, res, next) => { if (!roles.includes(req.user.role)) { return res.status(403).json({ error: '权限不足:您没有访问此资源的权限' }); } next(); }; };

4.2 用户认证路由实现

创建routes/auth.js处理用户注册、登录和令牌刷新:

const express = require('express'); const bcrypt = require('bcryptjs'); const { generateToken, verifyToken } = require('../middleware/auth'); const { body, validationResult } = require('express-validator'); const createAuthRouter = () => { const router = express.Router(); // POST /api/v1/auth/register - 用户注册 router.post('/register', [ body('username').isLength({ min: 3, max: 30 }).withMessage('用户名长度必须在3-30个字符之间'), body('email').isEmail().normalizeEmail().withMessage('请输入有效的邮箱地址'), body('password').isLength({ min: 8 }).withMessage('密码至少需要8个字符'), body('confirmPassword').custom((value, { req }) => { if (value !== req.body.password) { throw new Error('两次输入的密码不一致'); } return true; }) ], async (req, res) => { try { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ error: '注册信息验证失败', details: errors.array() }); } const { username, email, password } = req.body; // 这里应该连接数据库检查用户是否已存在 // 为简化示例,我们模拟一个内存中的用户存储 const existingUser = mockUsers.find(u => u.email === email); if (existingUser) { return res.status(400).json({ error: '该邮箱已被注册' }); } // 密码哈希 const salt = await bcrypt.genSalt(12); const hashedPassword = await bcrypt.hash(password, salt); // 创建新用户(实际项目中应保存到数据库) const newUser = { id: Date.now().toString(), username, email, password: hashedPassword, role: 'user', createdAt: new Date().toISOString() }; mockUsers.push(newUser); // 生成JWT令牌 const token = generateToken(newUser.id, newUser.username, newUser.role); // 设置HTTP-only cookie(前端无需手动处理token) res.cookie('token', token, { httpOnly: true, secure: process.env.NODE_ENV === 'production', maxAge: 30 * 24 * 60 * 60 * 1000, // 30天 sameSite: 'strict' }); res.status(201).json({ success: true, message: '用户注册成功', user: { id: newUser.id, username: newUser.username, email: newUser.email, role: newUser.role } }); } catch (error) { console.error('注册错误:', error); res.status(500).json({ error: '注册过程中发生错误' }); } }); // POST /api/v1/auth/login - 用户登录 router.post('/login', [ body('email').isEmail().normalizeEmail().withMessage('请输入有效的邮箱地址'), body('password').exists().withMessage('密码不能为空') ], async (req, res) => { try { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ error: '登录信息验证失败', details: errors.array() }); } const { email, password } = req.body; // 查找用户(实际项目中应查询数据库) const user = mockUsers.find(u => u.email === email); if (!user) { return res.status(401).json({ error: '邮箱或密码错误' }); } // 验证密码 const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) { return res.status(401).json({ error: '邮箱或密码错误' }); } // 生成JWT令牌 const token = generateToken(user.id, user.username, user.role); // 设置HTTP-only cookie res.cookie('token', token, { httpOnly: true, secure: process.env.NODE_ENV === 'production', maxAge: 30 * 24 * 60 * 60 * 1000, sameSite: 'strict' }); res.json({ success: true, message: '登录成功', user: { id: user.id, username: user.username, email: user.email, role: user.role } }); } catch (error) { console.error('登录错误:', error); res.status(500).json({ error: '登录过程中发生错误' }); } }); // POST /api/v1/auth/logout - 用户登出 router.post('/logout', (req, res) => { // 清除cookie res.clearCookie('token'); res.json({ success: true, message: '已成功登出' }); }); // GET /api/v1/auth/me - 获取当前用户信息 router.get('/me', verifyToken, (req, res) => { res.json({ success: true, user: { id: req.user.userId, username: req.user.username, role: req.user.role, exp: req.user.exp } }); }); return router; }; // 模拟用户存储(实际项目中应使用数据库) const mockUsers = []; module.exports = { createAuthRouter };

5. 流式响应处理与用户体验优化

5.1 流式响应的完整实现

流式响应对于医疗问答场景至关重要,它能让用户在模型生成答案的过程中就看到部分内容,显著提升感知响应速度。我们已经在医疗问答路由中实现了基本的流式处理,现在需要完善前端友好的响应格式。

创建utils/streamHandler.js

/** * 流式响应处理器 * 将vLLM的SSE流转换为前端友好的格式 */ class StreamHandler { constructor(res) { this.res = res; this.isFirstChunk = true; } /** * 初始化流式响应头 */ init() { this.res.setHeader('Content-Type', 'text/event-stream'); this.res.setHeader('Cache-Control', 'no-cache'); this.res.setHeader('Connection', 'keep-alive'); this.res.setHeader('X-Accel-Buffering', 'no'); // Nginx兼容 } /** * 发送数据块 * @param {string} content - 要发送的内容 * @param {Object} metadata - 可选的元数据 */ send(content, metadata = {}) { try { const data = { content, timestamp: new Date().toISOString(), ...metadata }; // 第一个块添加特殊标识 if (this.isFirstChunk) { data.firstChunk = true; this.isFirstChunk = false; } this.res.write(`data: ${JSON.stringify(data)}\n\n`); } catch (error) { console.error('流式响应写入错误:', error); this.res.write('event: error\n'); this.res.write(`data: ${JSON.stringify({ error: error.message })}\n\n`); } } /** * 发送完成事件 */ complete() { this.res.write('event: complete\n'); this.res.write('data: {"status": "completed"}\n\n'); this.res.end(); } /** * 发送错误事件 * @param {string} errorMessage - 错误信息 */ error(errorMessage) { this.res.write('event: error\n'); this.res.write(`data: {"error": "${errorMessage}"}\n\n`); this.res.end(); } } module.exports = { StreamHandler };

更新医疗问答路由以使用新的流式处理器:

// 在medicalChat.js中添加对StreamHandler的引用 const { StreamHandler } = require('../utils/streamHandler'); // 在流式响应处理部分替换为: if (stream) { const streamHandler = new StreamHandler(res); streamHandler.init(); vllmResponse.data.on('data', (chunk) => { try { const data = chunk.toString().trim(); if (data.startsWith('data:')) { const jsonStr = data.substring(5).trim(); if (jsonStr && jsonStr !== '[DONE]') { const parsed = JSON.parse(jsonStr); const content = parsed.choices?.[0]?.delta?.content || ''; if (content) { streamHandler.send(content, { finish_reason: parsed.choices?.[0]?.finish_reason }); } } } } catch (e) { console.error('Error parsing stream chunk:', e); streamHandler.error('解析流式响应时发生错误'); } }); vllmResponse.data.on('end', () => { streamHandler.complete(); }); vllmResponse.data.on('error', (err) => { console.error('Stream error:', err); streamHandler.error('流式响应处理失败'); }); return; }

5.2 前端集成示例

为了展示如何在前端使用这个流式API,这里提供一个简单的HTML页面示例:

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>医疗AI助手</title> <style> .chat-container { max-width: 800px; margin: 0 auto; padding: 20px; } .message { margin-bottom: 15px; padding: 10px; border-radius: 8px; } .user-message { background-color: #e3f2fd; margin-left: 20%; } .ai-message { background-color: #f5f5f5; margin-right: 20%; } .typing-indicator { display: inline-block; margin-left: 10px; } .typing-indicator span { display: inline-block; width: 8px; height: 8px; background-color: #666; border-radius: 50%; margin-right: 4px; animation: typing 1.4s infinite; } .typing-indicator span:nth-child(2) { animation-delay: 0.2s; } .typing-indicator span:nth-child(3) { animation-delay: 0.4s; } @keyframes typing { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-5px); } } </style> </head> <body> <div class="chat-container"> <h1>🏥 医疗AI助手</h1> <div id="chat-messages"></div> <div> <input type="text" id="user-input" placeholder="描述您的症状或健康问题..." style="width: 70%; padding: 10px;"> <button onclick="sendMessage()">发送</button> </div> </div> <script> async function sendMessage() { const input = document.getElementById('user-input'); const messagesDiv = document.getElementById('chat-messages'); if (!input.value.trim()) return; // 显示用户消息 messagesDiv.innerHTML += `<div class="message user-message">${input.value}</div>`; input.value = ''; // 显示AI正在思考的指示器 messagesDiv.innerHTML += `<div class="message ai-message">AI正在思考中... <div class="typing-indicator"><span></span><span></span><span></span></div></div>`; messagesDiv.scrollTop = messagesDiv.scrollHeight; try { const response = await fetch('/api/v1/medical/chat', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer YOUR_JWT_TOKEN' // 实际项目中应从cookie或localStorage获取 }, body: JSON.stringify({ messages: [{ role: 'user', content: input.value }], stream: true }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let accumulatedContent = ''; while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { try { const data = JSON.parse(line.substring(6)); if (data.content) { accumulatedContent += data.content; // 更新AI消息显示 const aiMessage = messagesDiv.lastChild; aiMessage.innerHTML = `AI: ${accumulatedContent} <div class="typing-indicator"><span></span><span></span><span></span></div>`; messagesDiv.scrollTop = messagesDiv.scrollHeight; } } catch (e) { console.error('解析数据块错误:', e); } } } } // 移除打字指示器 const aiMessage = messagesDiv.lastChild; aiMessage.innerHTML = `AI: ${accumulatedContent}`; } catch (error) { console.error('发送消息错误:', error); const aiMessage = messagesDiv.lastChild; aiMessage.innerHTML = `AI: 对不起,处理您的请求时发生了错误。${error.message}`; } } // 支持回车发送 document.getElementById('user-input').addEventListener('keypress', (e) => { if (e.key === 'Enter') { sendMessage(); } }); </script> </body> </html>

6. 生产环境部署与监控

6.1 Docker容器化部署

为了确保开发、测试和生产环境的一致性,我们使用Docker进行容器化部署。

创建Dockerfile

FROM node:20-alpine # 设置工作目录 WORKDIR /app # 复制package.json和package-lock.json以利用Docker缓存 COPY package*.json ./ # 安装依赖 RUN npm ci --only=production # 复制应用代码 COPY . . # 创建非root用户提高安全性 RUN addgroup -g 1001 -f nodejs && adduser -S nextjs -u 1001 # 切换到非root用户 USER nextjs # 暴露端口 EXPOSE 3000 # 启动应用 CMD ["npm", "start"]

创建docker-compose.yml用于多服务编排:

version: '3.8' services: # 医疗AI API服务 medical-api: build: . restart: unless-stopped ports: - "3000:3000" environment: - NODE_ENV=production - PORT=3000 - JWT_SECRET=${JWT_SECRET} - JWT_EXPIRE=30d - ALLOWED_ORIGINS=http://localhost:3000,https://yourdomain.com - VLLM_API_KEY=${VLLM_API_KEY} depends_on: - vllm-service networks: - medical-network # vLLM模型服务 vllm-service: image: vllm/vllm-cpu:latest restart: unless-stopped ports: - "8000:8000" command: > vllm serve baichuan-inc/Baichuan-M2-32B-GPTQ-Int4 --host 0.0.0.0 --port 8000 --tensor-parallel-size 1 --max-model-len 32768 --enable-prefix-caching volumes: - ./models:/root/.cache/huggingface/hub environment: - HF_HOME=/root/.cache/huggingface/hub deploy: resources: limits: memory: 24g cpus: '4' networks: - medical-network # Redis缓存(可选,用于会话存储) redis: image: redis:7-alpine restart: unless-stopped ports: - "6379:6379" networks: - medical-network networks: medical-network: driver: bridge

6.2 日志与监控配置

创建utils/logger.js实现结构化日志记录:

const winston = require('winston'); const { format } = winston; // 创建日志格式 const logFormat = format.combine( format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), format.errors({ stack: true }), format.json() ); // 创建日志器 const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: logFormat, defaultMeta: { service: 'medical-ai-api' }, transports: [ // 控制台日志 new winston.transports.Console({ format: format.combine( format.colorize(), format.simple() ) }), // 文件日志(生产环境) ...(process.env.NODE_ENV === 'production' ? [ new winston.transports.File({ filename: 'logs/error.log', level: 'error' }), new winston.transports.File({ filename: 'logs/combined.log' }) ] : []) ] }); // 请求日志中间件 exports.requestLogger = (req, res, next) => { const start = Date.now(); res.on('finish', () => { const duration = Date.now() - start; const logData = { method: req.method, url: req.url, status: res.statusCode, duration: `${duration}ms`, ip: req.ip, userAgent: req.get('User-Agent'), referer: req.get('Referer') }; if (res.statusCode >= 400) { logger.error('Request failed', logData); } else { logger.info('Request completed', logData); } }); next(); }; module.exports = logger;

6.3 性能优化与最佳实践

在生产环境中,还需要考虑以下优化措施:

数据库连接池:如果应用需要访问数据库,使用连接池管理数据库连接:

const { Pool } = require('pg'); const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 20, // 最大连接数 min: 5, // 最小连接数 idleTimeoutMillis: 30000, // 空闲连接超时 connectionTimeoutMillis: 2000, // 连接超时 }); // 使用连接池执行查询 exports.query = (text, params) => pool.query(text, params);

缓存策略:对常见医疗问题的回答进行缓存,减少模型调用频率:

const NodeCache = require('node-cache'); const cache = new NodeCache({ stdTTL: 3600, checkperiod: 120 }); // 缓存1小时 // 在医疗问答路由中添加缓存逻辑 const cacheKey = `medical
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/20 1:46:50

如何用6大工具解决99%的跨平台文件传输难题?2025全场景技术指南

如何用6大工具解决99%的跨平台文件传输难题&#xff1f;2025全场景技术指南 【免费下载链接】Online-disk-direct-link-download-assistant 可以获取网盘文件真实下载地址。基于【网盘直链下载助手】修改&#xff08;改自6.1.4版本&#xff09; &#xff0c;自用&#xff0c;去…

作者头像 李华
网站建设 2026/3/19 11:06:13

Janus-Pro-7B实测分享:多模态AI的惊艳表现

Janus-Pro-7B实测分享&#xff1a;多模态AI的惊艳表现 1. 这不是“又能看又能画”的简单叠加&#xff0c;而是真正理解图像的多模态模型 很多人第一次听说Janus-Pro-7B&#xff0c;会下意识把它当成一个“图文混合版的ChatGPT”——能看图、能回答、还能生成图。但实际用下来…

作者头像 李华
网站建设 2026/3/20 17:29:11

YOLO12与Node.js集成:构建实时视频分析API

YOLO12与Node.js集成&#xff1a;构建实时视频分析API 1. 为什么需要将YOLO12封装为Node.js服务 在实际业务场景中&#xff0c;我们经常遇到这样的需求&#xff1a;工厂需要实时监控产线上的零部件是否缺失&#xff0c;零售门店想自动统计顾客进店人数和停留时长&#xff0c;…

作者头像 李华
网站建设 2026/3/20 6:58:00

StructBERT相似度模型实操手册:Gradio界面响应时间性能调优

StructBERT相似度模型实操手册&#xff1a;Gradio界面响应时间性能调优 1. 模型与工具介绍 StructBERT中文文本相似度模型是基于structbert-large-chinese预训练模型&#xff0c;通过多个高质量数据集训练而成的专业文本匹配工具。该模型在中文文本相似度计算任务中表现出色&…

作者头像 李华
网站建设 2026/3/20 17:27:54

ChatGLM-6B Java开发实战:SpringBoot微服务集成指南

ChatGLM-6B Java开发实战&#xff1a;SpringBoot微服务集成指南 1. 为什么选择Java与ChatGLM-6B的组合 在企业级AI应用开发中&#xff0c;很多团队已经构建了成熟的Java技术栈&#xff0c;特别是基于SpringBoot的微服务架构。当需要引入大语言模型能力时&#xff0c;直接用Py…

作者头像 李华
网站建设 2026/3/20 14:54:17

SeqGPT与Vue3前端集成:构建智能写作助手

SeqGPT与Vue3前端集成&#xff1a;构建智能写作助手 1. 为什么需要一个轻量级的智能写作助手 最近在帮几个内容团队做效率优化&#xff0c;发现一个很实际的问题&#xff1a;写文案、改稿子、整理会议纪要这些事&#xff0c;每天都要花掉大量时间。用传统方式&#xff0c;要么…

作者头像 李华