最近团队内部要接大模型能力处理一批文档摘要和对话问答,数据又绕不开内网,我最后选了 Ollama 做本地推理,用 LangChain 做逻辑编排,再封装一层统一的 HTTP API 给业务方调用。整套方案跑下来最大的体会是:Ollama 解决“模型跑得起来”的问题,LangChain 解决“逻辑组织得起来”的问题,而真正在工程上花时间的是 API 封装那部分——参数传递、超时控制、上下文管理、并发处理、流式输出,每一个细节都能让你在线上炸一次。这篇文章把我实际写的代码和踩过的坑完整整理一遍,适合正在做本地大模型服务化的朋友参考,尤其是准备把 Ollama、LangChain 组合起来对外提供接口的开发同学。
1. 项目背景与整体设计思路:为什么是 Ollama 加 LangChain
1.1 需求场景与选型考量
这次项目的诉求很直接:业务方想在自己的系统里接入大模型能力,但核心数据不能出内网。公有云大模型 API 再方便,数据链路这一关就过不了。所以第一步就锁定在私有化部署这套路线上。
模型运行层选型的时候,我对比过 vLLM、llama.cpp、Ollama 三套方案。vLLM 吞吐量确实高,但部署复杂度也高,对显存调度和依赖环境要求比较多,为了一个内部小规模场景去搭一套完整的高性能推理服务,有点杀鸡用牛刀。llama.cpp 灵活,但上层接口和模型管理都要自己动手搓。Ollama 是这三者里最“傻瓜”的,安装完一条命令就能拉模型起服务,自带模型管理、并发调度和 OpenAI 兼容端点,对团队后续维护非常友好。配合 NVIDIA 显卡和 CUDA 环境,qwen2.5、llama3 这类主流开源模型都能直接跑。
编排层选 LangChain 而不是直接裸调 Ollama,核心原因有两个。第一,LangChain 把 ChatMessage、Prompt 模板、输出解析器、RAG 检索这些通用组件都抽象好了,业务方不可能只用一个“烤肉式”的问答接口,很快就会提出“要能参照某些文档回答”“要能按固定格式输出”这类需求,有编排层会从容很多。第二,LangChain 的 Runnable pipeline 天然支持流式、异步和链式组合,后面想加 Agent、加工具调用,改造成本很低。
1.2 三层架构的整体设计
整个项目我拆成了三层,各司其职:
- 接入层:FastAPI 对外提供 RESTful API,负责参数校验、鉴权、路由分发和错误处理。
- 编排服务层:LangChain 封装对话链路、Prompt 模板、历史消息管理、RAG 组合逻辑。
- 模型基础服务层:Ollama 负责模型加载与推理,通过 HTTP 接口暴露能力。
对外统一走 FastAPI,业务方只关心一个接口地址和一套请求格式。内部具体是走 LangChain 链路,还是直接调用 Ollama 原始接口,对调用方完全透明。这样的好处是后面换底层模型、调整推理参数,只要保证接口协议不变,业务方代码一行都不用动。
目录结构我是这么组织的:
code复制llm-api/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI 入口
│ ├── api/
│ │ ├── __init__.py
│ │ └── routes.py # 路由层
│ ├── core/
│ │ ├── __init__.py
│ │ ├── config.py # 全局配置
│ │ ├── ollama_client.py # Ollama 原始客户端封装
│ │ ├── langchain_service.py # LangChain 服务封装
│ │ └── service_pool.py # 服务实例缓存池
│ └── schemas/
│ ├── __init__.py
│ └── requests.py # Pydantic 请求/响应模型
├── .env.example
├── requirements.txt
└── README.md
这个结构看起来比单纯一个脚本复杂,但真上了生产环境就发现很值。配置、路由、业务逻辑分开,测试和排错都清晰;服务实例用缓存池管理,避免每个请求都创建新的 LangChain 实例导致的连接开销和资源浪费。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与目录搭建:从 Ollama 安装到 LangChain 依赖
2.1 Ollama 安装与模型拉取
Ollama 的安装在 Linux、macOS、Windows 上都有对应的安装包。我们线上是 Linux 服务器,直接官方脚本装完再用 ollama serve 把服务跑起来,默认监听 11434 端口。装完之后第一件事是确认版本,不同版本的参数行为和并发策略有差异:
bash复制ollama --version
ollama serve
模型拉取我建议按实际需求来,不要一上来就追求大参数。内部业务场景,qwen2.5:7b 这个档位在 8G 显存以上的环境已经能给出不错的效果,中文表现也稳。拉取命令很简单:
bash复制ollama pull qwen2.5:7b
拉完之后用 ollama list 确认本地模型列表,有多个模型的时候,后续 API 请求里可以通过 model 字段动态指定,这也是封装层要暴露的参数之一。
2.2 Python 依赖安装
Python 侧我用的 3.10 版本。核心依赖整理在 requirements.txt 里:
code复制langchain-core>=0.3.0
langchain-ollama>=0.2.0
ollama>=0.4.0
fastapi>=0.115.0
uvicorn[standard]>=0.32.0
pydantic>=2.8.0
pydantic-settings>=2.6.0
这里有个容易踩的坑:LangChain 生态更新很快,不同版本之间 API 差异不小。网上很多教程还在用 from langchain.llms import Ollama,那是老版本写法,新版本已经迁移到 langchain-ollama 这个独立包里,类名也变成了 ChatOllama。如果你照着老教程写,会直接报 ImportError。所以装依赖的时候一定注意版本,尽量统一用我上面列的新系列。
2.3 配置文件与环境变量
配置我单独拆了一个 config.py,用 pydantic-settings 从环境变量和 .env 文件中读取,做到一处修改、全局生效:
python复制# app/core/config.py
from functools import lru_cache
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
ollama_base_url: str = "http://localhost:11434"
ollama_model: str = "qwen2.5:7b"
default_temperature: float = 0.7
default_num_ctx: int = 8192
request_timeout: int = 600
max_history_messages: int = 20
api_token: str = ""
class Config:
env_file = ".env"
@lru_cache
def get_settings() -> Settings:
return Settings()
max_history_messages 这个参数是我后来加的。如果不控制历史消息轮数,对话时间一长,消息列表无限膨胀,迟早把上下文窗口撑爆。后面讲上下文管理的时候还会细说。
.env.example 文件长这样:
code复制OLLAMA_BAS
