news 2026/9/20 13:24:15

Phi-4 接入 LangChain 实战:通过自定义 LLM 类封装本地模型完成统一调用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Phi-4 接入 LangChain 实战:通过自定义 LLM 类封装本地模型完成统一调用

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 模型(魔搭社区)

使用魔搭社区中的modelscopesnapshot_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。从调用链看,这保证generateattention_mask构造与停止符判定一致,避免因 pad token 缺失或不一致导致的生成异常。
  • self.model.generation_config = GenerationConfig.from_pretrained(mode_name_or_path):显式从模型目录加载生成配置(temperature、top_p 等),让模型按官方默认生成策略推理。

把模型加载放在构造函数中,意味着后续每次_call都不再需要重新加载权重,大幅降低多轮调用的时延。

4.2 _call 函数:LangChain 与模型之间的桥梁

_callLLM类的核心函数,LangChain 会调用该函数来调用 LLM。其内部流程分为四步:

  1. 构造消息列表:将用户提示词包装为[{"role": "user", "content": prompt }],与 Chat 模型的对话格式保持一致。
  2. 应用聊天模板apply_chat_template(messages, tokenize=False, add_generation_prompt=True)把消息列表按 Phi-4 的对话模板拼装成完整输入串,并追加生成提示符(generation prompt),这是让模型正确理解对话结构的关键。
  3. 生成 tokenself.model.generate(model_inputs.input_ids, attention_mask=..., max_new_tokens=512)控制最多新生成 512 个 token,可通过修改max_new_tokens调节回答长度上限。
  4. 裁剪与解码:将生成结果中与输入部分重叠的 token 裁掉,仅保留新生成的部分,再通过batch_decode(..., skip_special_tokens=True)解码为纯文本返回,skip_special_tokens=True用于剔除<|endoftext|>等特殊 token。

4.3 _llm_type 属性

_llm_typeLLM基类要求子类实现的只读属性,用于标识当前 LLM 的类型。这里返回字符串"Phi_4",在日志、序列化与调试时用于区分不同的模型实现。

五、源码级剖析:LangChain 对 LLM 的抽象与调用机制

了解 LangChain 内部如何驱动这个自定义类,有助于排查问题。从langchain.llms.base.LLM基类看:

  • _call是抽象方法:子类必须实现它。基类通过generate_prompt/__call__等公开入口,最终路由到_call完成真正的文本生成。
  • _llm_type是抽象属性:子类必须提供,用于标识模型类型。
  • 回调机制run_manager: CallbackManagerForLLMRun参数允许在生成过程中触发on_llm_starton_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.pyrun_gradio.py,用自定义 LLM 结合向量库实现 RAG 问答。
  • 串联 FastAPI 服务:如需把模型封装为 HTTP 服务供其他系统调用,可参考同系列的 01-Phi-4 FastApi 部署调用,其中tokenizer.pad_token_id = tokenizer.eos_token_id = 100265bfloat16device_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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 13:21:41

豆包 LeetCode 96. 不同的二叉搜索树 Rust实现

LeetCode 96. 不同的二叉搜索树 Rust 实现 函数签名&#xff1a; pub fn num_trees(n: i32) -> i32 解法1&#xff1a;动态规划 O(n) rust pub struct Solution; impl Solution { pub fn num_trees(n: i32) -> i32 { let n n as usize; let mut dp vec![0; n 1]; dp[0…

作者头像 李华
网站建设 2026/9/20 13:21:40

Ruffle Flash 模拟器实战指南:3 条路让老游戏与老动画再次跑起来

Ruffle Flash 模拟器实战指南&#xff1a;3 条路让老游戏与老动画再次跑起来 【免费下载链接】ruffle A Flash Player emulator written in Rust 项目地址: https://gitcode.com/GitHub_Trending/ru/ruffle 打开五年前收藏的页面&#xff0c;动画该出现的位置只剩灰白方…

作者头像 李华
网站建设 2026/9/20 13:19:13

Excel Power Query自动获取股票历史数据实战指南

1. 为什么我最终放弃了手动更新股票数据做股票复盘这件事&#xff0c;我坚持了快六年。前三年一直用最笨的办法&#xff1a;每天收盘后打开行情软件&#xff0c;把自选股的收盘价、成交量、涨跌幅一个个敲进Excel表格里。十几只股票还好&#xff0c;后来自选池扩到五六十只&…

作者头像 李华
网站建设 2026/9/20 13:19:01

通达信L2资金流向函数实战:从原理到公式编写与参数调优

1. 先搞清楚L2资金流向到底在算什么很多人一看到“L2资金流向”就觉得是个黑箱&#xff0c;券商软件里红红绿绿的柱子&#xff0c;到底怎么来的&#xff1f;其实拆开看并不复杂。通达信的L2数据本质上是逐笔成交的委托队列快照&#xff0c;它比普通Level-1行情多了两样东西&…

作者头像 李华
网站建设 2026/9/20 13:18:14

把 OpenClaw 的 Base URL 改到 TaoToken 后,中间件怎么统计请求耗时

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华