前言:本地大模型 + 全链路监控,才是生产级 AI 服务的标配
随着 Ollama 让本地大模型(如 Llama3、Qwen、Gemma)的部署成本大幅降低,越来越多开发者基于 Node+Express+LangChain 搭建私有化 AI 服务。但本地模型落地时容易遇到核心痛点:
- LangChain 1.0 版本 API 重构后,如何适配 Ollama 做标准化集成?
- 本地模型调用耗时、提示词输出 / 返回结果无法追踪,出问题只能盲调?
- 不知道本地模型占用多少 CPU / 内存,服务宕机无预警?
- LangSmith 如何监控本地 Ollama 模型的链执行全流程?
本文基于 LangChain 1.0 核心 API,结合 Ollama 本地大模型,从「标准化服务启动」和「全链路监控」两大维度,搭建可落地的私有化 AI 服务:既解决本地模型的集成适配问题,又通过 LangSmith 实现链执行追踪,配套日志、指标、健康检查体系,让本地大模型服务也能做到 “可观测、可追溯、可预警”。
一、前置准备:Ollama 环境与依赖安装
1.1 安装 Ollama 并拉取本地模型
1.2 项目依赖安装(适配 LangChain 1.0 + Ollama)
前两篇文章已做相关操作,可供参考: juejin.cn/post/757588…
1.3 安装LangSmith
官方文档:docs.langchain.com/langsmith/o…
二、核心步骤 1:LangChain 1.0 + Ollama 集成(标准化启动)
2.1 环境变量配置(.env)
所有配置通过环境变量隔离,适配不同环境(开发 / 生产):
# 服务基础配置
PORT=3000
NODE_ENV=development
# Ollama 配置
OLLAMA_BASE_URL=http://localhost:11434 #Ollama本地服务地址
OLLAMA_EMBED_MODEL=nomic-embed-text:latest #embedding模型
OLLAMA_CHAT_MODEL=deepseek-r1:8b #Thinking模型
# Chroma 向量数据库配置
CHROMA_URL=http://localhost:8000
CHROMA_COLLECTION_NAME=Devin-examples # 集合名称
CHROMA_PERSIST_DIR=./chroma-data # 存储地址
# LangSmith
LANGSMITH_API_KEY=xxx # LangSmith密钥
LANGSMITH_TRACING=true
2.2 LangChain + Ollama + LangSmith 初始化(核心配置)
import 'dotenv/config';
import type { AIMessageChunk, BaseMessage, MessageStructure } from '@langchain/core/messages';
import { HumanMessage, SystemMessage } from '@langchain/core/messages';
import { ChatOllama, type ChatOllamaInput } from '@langchain/ollama';
import { ragInstance } from './rag.js';
import { wrapSDK } from 'langsmith/wrappers';
/**
* LLM 配置接口
*/
interface LLMConfig extends Partial<ChatOllamaInput> {
/** 模型名称 */
model?: string;
/** 随机性参数,控制输出的随机性,范围 0-1,默认 0.7 */
temperature?: number;
/** Top-p 采样参数,控制多样性,范围 0-1 */
topP?: number;
/** 是否启用 RAG */
enableRAG?: boolean;
}
/**
* LLM 类
* 提供流式和非流式的对话功能
*/
class LLM extends ChatOllama {
private readonly enableRAG: boolean;
/**
* 构造函数
* @param config LLM 配置选项
*/
constructor(config: LLMConfig = {}) {
const options: Record<string, unknown> = {
model: config.model || process.env.OLLAMA_CHAT_MODEL,
temperature: config.temperature ?? 0.7,
think: config.think ?? true,
...config,
};
if (config.topP !== undefined) {
options.topP = config.topP;
}
super(options);
this.enableRAG = config.enableRAG ?? true;
}
/**
* 非流式对话
* @param message 用户消息
* @param systemPrompt 系统提示词(可选)
* @returns AI 完整回复文本
*/
async chat(message: string, systemPrompt?: string): Promise<AIMessageChunk<MessageStructure>> {
const messages: BaseMessage[] = await setMessage(message, this.enableRAG, systemPrompt);
const response = await this.invoke(messages);
return response;
}
/**
* 流式对话
* @param message 用户消息
* @param systemPrompt 系统提示词(可选)
* @returns 异步生成器,逐块返回内容
*/
async *chatStream(message: string, systemPrompt?: string): AsyncGenerator<AIMessageChunk<MessageStructure>, void, unknown> {
// 将message放入Chroma进行检索
const messages: BaseMessage[] = await setMessage(message, this.enableRAG, systemPrompt);
const stream = await this.stream(messages);
for await (const chunk of stream) {
yield chunk;
}
}
}
const setMessage = async (message: string, enableRAG: boolean, systemPrompt?: string): Promise<BaseMessage[]> => {
const messages: BaseMessage[] = [];
if (systemPrompt) {
messages.push(new SystemMessage(systemPrompt));
}
// 是否启用RAG检索 后续模块会进行RAG的讲解
if (enableRAG) {
const ragMessage = await ragInstance.retrieve(message);
messages.push(new SystemMessage(ragMessage));
} else {
messages.push(new HumanMessage(message));
}
return messages;
};
const llmInstance = (function () {
let instance: LLM;
return {
getInstance: function () {
if (!instance) {
// 添加监控 通过LangSmith进行监控和可视化
instance = wrapSDK(new LLM());
}
return instance;
},
};
})();
// 导出单例和类
export { LLM };
export default llmInstance;
2.3 Express 服务标准化启动
这部分就不进行详解,可自行查阅资料
这是我请求部分控制器的实现,可进行参考:
import type { Request, Response } from 'express';
import { randomUUID } from 'node:crypto';
import llmInstance from '../utils/langchain/llm.js';
import { sendSSEData } from '../utils/http/sseTools.js';
import { badRequestResponse } from '../utils/http/http.js';
const llm = llmInstance.getInstance();
const sendSSEData = (res: Response, data: object, event: string = 'message'): void => {
const arr = [`id: ${randomUUID()} \n`, `event: ${event}\n`, `data: ${JSON.stringify(data)}\n`];
res.write(arr.join('') + '\n');
};
export const getLLMChart = async (req: Request, res: Response): Promise<void> => {
try {
const { text } = req.body;
if (!text) {
badRequestResponse(res);
return;
}
// 发送连接成功消息
sendSSEData(res, { type: 'start' });
// 使用流式响应
const stream = llm.chatStream(text);
for await (const chunk of stream) {
// 按照 SSE 格式发送数据
const data = {
id: randomUUID(),
type: 'message',
message: chunk,
timestamp: new Date().toISOString(),
};
sendSSEData(res, data);
}
// 发送完成消息
sendSSEData(res, { type: 'end' });
res.end();
} catch (error) {
// SSE 错误处理
const errorData = {
status: 'error',
message: error instanceof Error ? error.message : 'Unknown error',
};
res.write(`data: ${JSON.stringify(errorData)}\n\n`);
res.end();
}
};
三.接口调用、LangSmith监控
从上图可以明确的看到LLM 应用调试时间、请求成功与否、数据格式等等