Phi-4 接入 LangChain 实战:通过自定义 LLM 类封装本地模型完成统一调用
【免费下载链接】self-llm《开源大模型食用指南》针对中国宝宝量身打造的基于Linux环境快速微调(全参数/Lora)、部署国内外开源大模型(LLM)/多模态大模型(MLLM)教程项目地址: https://gitcode.com/datawhalechina/self-llm
本文是《开源大模型食用指南》中 Phi-4 系列的 LangChain 接入教程,讲解如何将本地部署的 Phi-4 模型封装成 LangChain 自定义 LLM 类,从而以统一接口驱动模型推理。读完本文,你将掌握从环境准备、魔搭模型下载、自定义 LLM 类编写到调用与报错排查的完整流程,并理解 LangChain 底层对 LLM 的抽象机制。
一、整体思路:为什么需要自定义 LLM 类
LangChain 的核心价值之一,是提供一套与具体模型解耦的统一接口。无论底层是 OpenAI API、Hugging Face 模型还是本地权重,上层应用只需面向同一个LLM抽象调用即可。要让本地部署的 Phi-4 也能享受这一便利,就需要基于本地模型自定义一个 LLM 类:从langchain.llms.base.LLM继承子类,重写构造函数与_call函数。构造函数在实例化时一次性加载模型与分词器,避免每次调用都重新加载;_call是 LangChain 调用模型的核心入口,负责把提示词交给模型生成并返回文本结果。
完成封装后,Phi-4 就能以与任何 LangChain 大模型完全一致的方式被调用,上层代码无需关心底层是哪个模型、走什么推理管线。这也为后续接入向量库、构建知识库助手、串联 Agent 等能力打下基础。
二、环境准备
本文基础环境如下:
---------------- ubuntu 22.04 python 3.12 cuda 12.1 pytorch 2.3.0 ----------------本文默认学习者已安装好以上 Pytorch(cuda) 环境,如未安装请自行安装。
pip 换源加速下载并安装依赖包:
# 升级pip python -m pip install --upgrade pip # 更换 pypi 源加速库的安装 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple pip install transformers==4.44.2 pip install huggingface-hub==0.25.0 pip install accelerate==0.34.2 pip install modelscope==1.18.0 pip install langchain==0.3.0这里各依赖的版本均与本文示例代码验证过:transformers负责加载模型与分词器,accelerate提供device_map="auto"的设备自动分配能力,modelscope用于从魔搭社区下载模型权重,langchain==0.3.0提供LLM基类与调用框架。若部分组件版本不一致,最可能的影响是接口签名变化,建议优先复现本文固定版本组合。
三、下载 Phi-4 模型(魔搭社区)
使用魔搭社区中的modelscope的snapshot_download函数下载模型。第一个参数为模型名称(可在魔搭社区搜索该模型获取,如下图所框),参数cache_dir为模型的下载路径,参数revision一般默认为master。
在/root/autodl-tmp新建model_download.py文件并输入以下内容,保存后运行python model_download.py执行下载。模型大小约 28 GB,下载大概需要 10 到 20 分钟:
import torch from modelscope import snapshot_download, AutoModel, AutoTokenizer import os model_dir = snapshot_download('LLM-Research/phi-4', cache_dir='/root/autodl-tmp', revision='master')注意:记得修改
cache_dir为你的模型下载路径。
在魔搭社区中搜索并进入phi-4模型页,确认模型名称为LLM-Research/phi-4,分支为master,即可获得与上方代码一致的下载参数:
四、编写自定义 LLM 类(LLM.py)
在当前路径新建一个LLM.py文件,输入以下内容,保存后即可作为封装模块被后续代码引入:
from langchain.llms.base import LLM #基础类,用于实现自定义的语言模型 from typing import Any, List, Optional from langchain.callbacks.manager import CallbackManagerForLLMRun #回调管理器,用于处理在模型运行期间的事件 from transformers import AutoTokenizer, AutoModelForCausalLM, GenerationConfig, LlamaTokenizerFast #Hugging Face 提供的库,用于加载预训练的 NLP 模型 import torch class Phi_4_LLM(LLM): # 基于本地 Phi_4 自定义 LLM 类 tokenizer: AutoTokenizer = None #tokenizer:用于将输入文本转换为模型可以理解的 token model: AutoModelForCausalLM = None #model:预训练的语言模型 def __init__(self, mode_name_or_path :str): #__init__ 方法初始化模型和分词器 super().__init__() print("正在从本地加载模型...") self.tokenizer = AutoTokenizer.from_pretrained(mode_name_or_path, use_fast=False) #使用 AutoTokenizer.from_pretrained 加载分词器 self.tokenizer.pad_token_id = self.tokenizer.eos_token_id = 100265 self.model = AutoModelForCausalLM.from_pretrained(mode_name_or_path, torch_dtype=torch.bfloat16, device_map="auto") #使用 AutoModelForCausalLM.from_pretrained 加载预训练的因果语言模型,并设置数据类型为 bfloat16,使用自动设备分配策略。 self.model.generation_config = GenerationConfig.from_pretrained(mode_name_or_path) #设置生成配置 print("完成本地模型的加载") def _call(self, prompt : str, stop: Optional[List[str]] = None, run_manager: Optional[CallbackManagerForLLMRun] = None, **kwargs: Any): #_call 方法用于生成文本响应 messages = [{"role": "user", "content": prompt }] #构造消息列表,包含用户的角色和提示内容 input_ids = self.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) #使用 apply_chat_template 方法应用聊天模板,并获取输入 ID model_inputs = self.tokenizer([input_ids], return_tensors="pt").to(self.model.device) #将输入 ID 转换为 PyTorch 张量,并移动到 GPU 上 generated_ids = self.model.generate(model_inputs.input_ids, attention_mask=model_inputs['attention_mask'], max_new_tokens=512) #使用 generate 方法生成新的 token generated_ids = [ output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids) ] #处理生成的 token,移除输入部分,只保留新生成的部分 response = self.tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0] return response #将生成的 token 解码为文本响应,并返回 @property def _llm_type(self) -> str: return "Phi_4"4.1 构造函数:一次加载,处处复用
构造函数在对象实例化时完成两件事:
- 加载分词器:
AutoTokenizer.from_pretrained(mode_name_or_path, use_fast=False)加载 Phi-4 的分词器。use_fast=False使用经典实现,避免 fast tokenizer 与模型特殊 token 之间的兼容问题。 - 加载模型:
AutoModelForCausalLM.from_pretrained(mode_name_or_path, torch_dtype=torch.bfloat16, device_map="auto")以bfloat16半精度加载因果语言模型,并通过device_map="auto"(依赖accelerate)自动将权重分配到可用设备(GPU/CPU),无需手动指定cuda:0。
两个细节值得注意:
self.tokenizer.pad_token_id = self.tokenizer.eos_token_id = 100265:将 padding token 与结束符统一设置为100265。从调用链看,这保证generate时attention_mask构造与停止符判定一致,避免因 pad token 缺失或不一致导致的生成异常。self.model.generation_config = GenerationConfig.from_pretrained(mode_name_or_path):显式从模型目录加载生成配置(temperature、top_p 等),让模型按官方默认生成策略推理。
把模型加载放在构造函数中,意味着后续每次_call都不再需要重新加载权重,大幅降低多轮调用的时延。
4.2 _call 函数:LangChain 与模型之间的桥梁
_call是LLM类的核心函数,LangChain 会调用该函数来调用 LLM。其内部流程分为四步:
- 构造消息列表:将用户提示词包装为
[{"role": "user", "content": prompt }],与 Chat 模型的对话格式保持一致。 - 应用聊天模板:
apply_chat_template(messages, tokenize=False, add_generation_prompt=True)把消息列表按 Phi-4 的对话模板拼装成完整输入串,并追加生成提示符(generation prompt),这是让模型正确理解对话结构的关键。 - 生成 token:
self.model.generate(model_inputs.input_ids, attention_mask=..., max_new_tokens=512)控制最多新生成 512 个 token,可通过修改max_new_tokens调节回答长度上限。 - 裁剪与解码:将生成结果中与输入部分重叠的 token 裁掉,仅保留新生成的部分,再通过
batch_decode(..., skip_special_tokens=True)解码为纯文本返回,skip_special_tokens=True用于剔除<|endoftext|>等特殊 token。
4.3 _llm_type 属性
_llm_type是LLM基类要求子类实现的只读属性,用于标识当前 LLM 的类型。这里返回字符串"Phi_4",在日志、序列化与调试时用于区分不同的模型实现。
五、源码级剖析:LangChain 对 LLM 的抽象与调用机制
了解 LangChain 内部如何驱动这个自定义类,有助于排查问题。从langchain.llms.base.LLM基类看:
_call是抽象方法:子类必须实现它。基类通过generate_prompt/__call__等公开入口,最终路由到_call完成真正的文本生成。_llm_type是抽象属性:子类必须提供,用于标识模型类型。- 回调机制:
run_manager: CallbackManagerForLLMRun参数允许在生成过程中触发on_llm_start、on_llm_new_token等事件回调。本文示例中未显式使用,但保留该参数即可保持与 LangChain 事件体系的兼容。 __call__的演进:在新版 LangChain 中,BaseLLM.__call__已被标记为弃用(LangChainDeprecationWarning),官方推荐改用invoke方法。示例运行输出中出现的弃用警告即源于此,不影响功能,但新项目建议直接用llm.invoke("你是谁")或llm.invoke({"prompt": ...})风格调用。
在整体项目中,我们将上述代码封装为LLM.py,后续直接从该文件中引入自定义的 LLM 类即可。
六、调用自定义 LLM
封装完成后,就可以像使用任何其他 LangChain 大模型功能一样使用它了:
from LLM import Phi_4_LLM llm = Phi_4_LLM(mode_name_or_path = "/root/autodl-tmp/LLM-Research/phi-4") print(llm("你是谁"))注意:记得修改模型路径为你的路径。
在 Jupyter Notebook 中执行上述代码,先看到模型加载进度条与"完成本地模型的加载"提示,随后模型返回回答(输出中可能附带LangChainDeprecationWarning弃用警告,属正常现象):
七、常见报错与排查
7.1 ImportError: cannot import name 'Phi_4_LLM' from 'LLM'
在调用阶段最容易遇到的报错如下:
报错原因:LLM.py文件中定义的类名与导入语句不一致。例如一开始类名写成了Phi_4,而from LLM import Phi_4_LLM这行代码是从LLM模块中导入Phi_4_LLM类,两者必须保持一致。将LLM.py中的类名统一改为Phi_4_LLM后即可调用成功:
该报错同时提示了一个排查思路:ImportError出现时,先核对模块文件名(LLM.py)与类名(Phi_4_LLM)是否与导入语句完全一致,再检查两个文件是否位于同一路径下。
7.2 其他易踩坑点
- 模型路径错误:
mode_name_or_path必须指向包含config.json、分词器文件与权重文件的完整模型目录,否则from_pretrained会报文件缺失错误。 - 显存不足:28 GB 权重的 Phi-4 在 bfloat16 下仍需约 14~16 GB 显存,若
device_map="auto"无法全部放入 GPU,可接受其将部分层分配到 CPU,但推理会变慢;显存紧张的场景建议参考仓库中 04-Phi-4-Lora 微调 使用低秩适配缩减占用。 - 生成结果含特殊符号:若未使用
skip_special_tokens=True,解码结果会包含<|endoftext|>等 token,注意保留该参数。
八、举一反三:仓库中的延伸实践
本文的自定义 LLM 封装思路,在《开源大模型食用指南》中具有通用性,同一套模式被应用于多个模型的 LangChain 接入教程中(例如 Qwen2.5 Langchain 接入、GLM-4 langchain 接入、InternLM3 Langchain 接入),区别仅在于各模型的聊天模板、特殊 token 与加载参数,核心的"继承LLM+ 重写_call"结构完全一致。
进一步地,封装好的自定义 LLM 可直接用于更复杂的应用:
- 构建知识库助手:参考 Atom-7B-Chat 接入 langchain 搭建知识库助手 中的 LLM.py、
creat_db.py与run_gradio.py,用自定义 LLM 结合向量库实现 RAG 问答。 - 串联 FastAPI 服务:如需把模型封装为 HTTP 服务供其他系统调用,可参考同系列的 01-Phi-4 FastApi 部署调用,其中
tokenizer.pad_token_id = tokenizer.eos_token_id = 100265、bfloat16与device_map="auto"等加载逻辑与本文完全一致。 - 交互式 Web Demo:参考 03-Phi-4 WebDemo部署 将封装好的模型接入 Gradio 前端。
相关文档在仓库 support_model.md 的 phi4 小节中均有索引,可以按需查阅同系列的全部教程(部署、WebDemo、LoRA 微调、GRPO 微调等),形成从"模型接入"到"应用落地"的完整链路。
【免费下载链接】self-llm《开源大模型食用指南》针对中国宝宝量身打造的基于Linux环境快速微调(全参数/Lora)、部署国内外开源大模型(LLM)/多模态大模型(MLLM)教程项目地址: https://gitcode.com/datawhalechina/self-llm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考