大语言模型RAG-将本地大模型封装为langchain的chat model(三)

大语言模型RAG-将本地大模型封装为langchain的chat model(三)

往期文章:
大语言模型RAG-技术概览 (一)
大语言模型RAG-langchain models (二)

上一期langchain还在0.1时代,这期使用的langchain v0.2已经与之前不兼容了。
本期介绍如何将开源模型封装为langchainchat模型 这是后面构建RAG应用的基础。

基于langchain_core.language_models.BaseChatModel创建类。这个类就是本地llm的langchain封装,需要定义以下方法或属性。

方法/属性作用是否必要
_generate根据prompt生成聊天结果必要
_llm_type (属性)模型的唯一标识,用于生成日志必要
_identifying_params(属性)用于返回模型的一些参数,用于日志和调试可选
_stream用于流式输出可选
_agenerate本机异步的_generate可选
_astream异步的_stream可选

示例如下,只实现比较常用的功能:

from typing import Any, AsyncIterator, Dict, Iterator, List, Optional
from threading import Thread
from langchain_core.callbacks import (
    AsyncCallbackManagerForLLMRun,
    CallbackManagerForLLMRun,
)
from langchain_core.language_models import BaseChatModel, SimpleChatModel
from langchain_core.messages import AIMessageChunk, BaseMessage, HumanMessage
from langchain_core.outputs import (
ChatGeneration, 
ChatGenerationChunk, 
ChatResult
)
from langchain_core.messages import (
    AIMessage,
    BaseMessage,
    HumanMessage,
)
from typing import AsyncIterator, Literal, Iterator, Optional, List, Dict, Any
from transformers import AutoTokenizer, AutoModelForCausalLM
from pydantic import BaseModel, Field, validator
import torch

class CustomChatModel(BaseChatModel):
    """将本地模型封装为langchain的chat模型"""
    
    model_name: str  # 模型的本地地址
    device: str = "cpu"  # 计算设备
    n: int = 512  # 最大生成长度
    temperature: float = 1.0  # 温度
    
    model: Optional[AutoModelForCausalLM] = None
    tokenizer: Optional[AutoTokenizer] = None
    
    def __init__(self, model_name: str, device: str, temperature: float = 1.0, **kwargs):
    """
    我把模型加载放在初始化里面了。当然有更灵活的处理方式,这部分你可以随心所欲,只要在
    模型获得输入前加载好模型就OK。
    
    Params:
	    model_name (str): 本地模型的路径
	    device (str): 计算设备
	    temperature (float): 模型温度
	"""
        super().__init__(model_name=model_name, device=device, **kwargs)
        self.model_name = model_name
        self.device = device
        self.temperature = temperature
        
		# 加载本地模型。
        try:
            self.tokenizer = AutoTokenizer.from_pretrained(self.model_name, trust_remote_code=True, use_fast=False)
            self.model = AutoModelForCausalLM.from_pretrained(self.model_name, trust_remote_code=True).half().to(self.device)
        except Exception as e:
            raise RuntimeError(f"模型或分词器加载失败: {e}")
            
	def _generate(self, messages: List[BaseMessage], stop: Optional[List[str]] = ['.', '。', '?', '?', '!', '!'], 
                  run_manager: Optional[CallbackManagerForLLMRun] = None, **kwargs: Any) -> ChatResult:
        """
        实现模型的文本生成逻辑
		这是langchain必须的类方法。

		Params:
			messages (BaseMessage): 输入模型的消息列表
			stop (List[str]): 
				这是一个字符列表,langchain会根据这个列表中的字符找到
				完整句子的结束位置。
			run_manager (CallbackManagerForLLMRun): 
				用于管理和追踪执行的过程,通常用于实现回调和异步处理。
				虽然这个功能很重要,但本文暂时不做示例(我没玩明白)
		
		Return:ChatResult类的输出,完全符合langchain风格。
		"""
        
        last_message = messages[-1].content  # 获取最后一条消息
        inputs = self.tokenizer.encode(last_message, return_tensors="pt").to(self.device)  # 将消息内容编码为张量
        outputs = self.model.generate(inputs, max_length=self.n + len(inputs[0]), temperature=self.temperature)  # 调用模型生成文本张量
        tokens = self.tokenizer.decode(outputs[0], skip_special_tokens=True)  # 将张量解码为文本

        # 截取合适的长度(找到完整句子的结束位置)
        end_positions = [tokens.find(c) for c in stop if tokens.find(c) != -1]
        if end_positions:
            end_pos = max(end_positions) + 1  # 包括结束符
            if end_pos < self.n:
                tokens = tokens[:end_pos]
            else:
                tokens = tokens[:self.n]
        else:
            tokens = tokens[:self.n]

        message = AIMessage(content=tokens)  # 将生成的文本封装为消息
        generation = ChatGeneration(message=message)  # 封装为ChatGeneration
        

        return ChatResult(generations=[generation])

	def _stream(self, messages: List[BaseMessage], stop: Optional[List[str]] = None, 
            run_manager: Optional[CallbackManagerForLLMRun] = None, **kwargs: Any) -> Iterator[ChatGenerationChunk]:
        """
        流式生成文本。这个方法不是必须的,但实际应用中流式输出能提高用户
        体验,从这个角度来说它是必须的。下面仅给出了最基本的流式输出的逻
        辑,实际工程中你可以自行添加你需要的逻辑。
        
		Params:
			messages (BaseMessage): 输入模型的消息列表
			stop (List[str]): 
				这是一个字符列表,langchain会根据这个列表中的字符找到
				完整句子的结束位置。
			run_manager (CallbackManagerForLLMRun): 
				用于管理和追踪执行的过程,通常用于实现回调和异步处理。

		Return:ChatGenerationChunk类的输出,完全符合langchain风格。
			
        """

        last_message = messages[-1].content
        inputs = self.tokenizer(last_message, return_tensors="pt").to(self.device)
        streamer = TextIteratorStreamer(tokenizer=self.tokenizer)  # transformers提供的标准接口,专为流式输出而生
        inputs.update({"streamer": streamer, "max_new_tokens": 512})  # 这是model.generate的参数,可以自由发挥
        thread = Thread(target=self.model.generate, kwargs=inputs)  # TextIteratorStreamer需要配合线程使用
        thread.start()
        for new_token in streamer:
        # 用迭代器的返回形式 。
            yield ChatGenerationChunk(message=AIMessage(content=new_token))

	@property
    def _llm_type(self) -> str:
        """返回自定义模型的标记"""
        return "Custom"
    
    @property
    def _identifying_params(self) -> Dict[str, Any]:
        """返回自定义的debug信息"""
        return {"model_path": self.model_name, "device": self.device}

本文使用chatglm3-6b模型为例,演示最基础使用方法。

import CustomChatModel
import os
from langchain_core.messages import HumanMessage

# 模型实例化
model = LoadChatLLM(model_name="THUDM/chatglm-6b", 
                    device=“cuda”, 
                    temperature=float(1.0))

def stream_generate_text(text):
        """
        流式输出的方式打印到终端。
        原理就是每生成一个新token,就清空屏幕,然后把原先生成过的所有tokens
        都打印一次。
        """
        generated_text = ''
        count = 0
        message = [HumanMessage(content=text)]
        for new_text in model._stream(message):
            generated_text += new_text.text
            count += 1
            if count % 8 == 0:  # 避免刷新太频繁,每8个tokens刷新一次
                os.system("clear")
                print(generated_text)
            os.system("clear")
            print(generated_text)

    while True:
        ipt = input("请输入:")
        stream_generate_text(ipt)

实际效果:
我问它:如何安装Python?
在这里插入图片描述

它的输出:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/690646.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

1-8 C语言分支循环语句

C语言的语句分为 5 类 1&#xff1a;表达式语句2&#xff1a;函数调用语句3&#xff1a;控制语句4&#xff1a;复合语句5&#xff1a;空语句 控制语句&#xff1a;用于控制程序的执行流程&#xff0c;以实现程序的各种结构方式&#xff0c;它们由特定的语句定义符组成&#x…

启动信息全掌握,Android 15 重磅 API:ApplicationStartInfo

前言 App 进程启动的时候&#xff0c;开发者很难获悉到本次启动的详细信息&#xff0c;比如&#xff1a; 是冷启动的、暖启动的、还是热启动的&#xff1f;是被 Broadcast 拉起来的、Activity 拉起来的、还是 ContentProvider 拉起来的&#xff1f; 针对这些 pain-points&am…

高中数学:数列-基础概念

一、什么是数列&#xff1f; 一般地&#xff0c;我们把按照确定的顺序排列的一列数称为数列&#xff0c;数列中的每一个数叫做这个数列的项&#xff0c;数列的第一项称为首项。 项数有限个的数列叫做有穷数列&#xff0c;项数无限个的数列叫做无穷数列。 二、一般形式 数列和…

24考研408大变化,25考研高分上岸规划+应对策略

巧了&#xff0c;我有现成的经验&#xff1a; 数学和专业课的成绩都不高不低&#xff0c;刚好够用&#xff0c;其实408想上岸&#xff0c;不仅仅要学好408&#xff0c;还要学好考研数学&#xff0c;这是我的肺腑之言&#xff0c;我复试的时候&#xff0c;我知道的那些没有进复试…

搭建 Langchain-Chatchat 详细过程

前言 本文参考官网和其他多方教程&#xff0c;将搭建 Langchain-Chatchat 的详细步骤进行了整理&#xff0c;供大家参考。 我的硬件 4090 显卡win10 专业版本 搭建环境使用 chatglm2-6b 模型 1. 创建虚拟环境 chatchat &#xff0c;python 3.9 以上 conda create -n chat…

光伏电站绘制软件的基本方法

随着可再生能源的快速发展&#xff0c;光伏电站的建设日益受到重视。为了提高光伏电站设计的效率和准确性&#xff0c;光伏电站绘制软件的应用变得至关重要。本文将介绍光伏电站绘制软件的基本方法&#xff0c;包括绘制屋顶、屋脊、障碍物和参照物&#xff0c;铺设光伏板&#…

二叉树的实现(初阶数据结构)

1.二叉树的概念及结构 1.1 概念 一棵二叉树是结点的一个有限集合&#xff0c;该集合&#xff1a; 1.或者为空 2.由一个根结点加上两棵别称为左子树和右子树的二叉树组成 从上图可以看出&#xff1a; 1.二叉树不存在度大于2的结点 2.二叉树的子树有左右之分&#xff0c;次序不能…

2024年全国大学生数据统计与分析竞赛B题论文和代码:电信银行卡诈骗检测数据分析和机器学习模型构建

2024年全国大学生数据统计与分析竞赛B题论文和代码已完成&#xff0c;代码为B题全部问题的代码&#xff0c;论文包括摘要、问题重述、问题分析、模型假设、符号说明、模型的建立和求解&#xff08;问题1模型的建立和求解、问题2模型的建立和求解、问题3模型的建立和求解&#x…

SpringBoot Elasticsearch06-以黑马商场为例-黑马程序员学习笔记

黑马商城作为一个电商项目&#xff0c;商品的搜索肯定是访问频率最高的页面之一。目前搜索功能是基于数据库的模糊搜索来实现的&#xff0c;存在很多问题。 首先&#xff0c;查询效率较低。 由于数据库模糊查询不走索引&#xff0c;在数据量较大的时候&#xff0c;查询性能很…

统计信号处理基础 习题解答10-8

题目 一个随机变量具有PDF 。希望在没有任何可用数据的情况下估计的一个现实。为此提出了使最小的MMSE估计量&#xff0c;其中期望仅是对求的。证明MMSE估计量为。将你的结果应用到例10.1&#xff0c;当把数据考虑进去时&#xff0c;证明最小贝叶斯MSE是减少的。 解答 在贝叶…

2024年如何通过完善的工程化,从0到1手把手建立个人 react 组件库

本文聚焦于快速创建并部署个人的组件库&#xff0c;方便平时开发中将通用的组件抽出&#xff0c;也可用于简历上展示个人的组件成果~ 组件库体验地址&#xff1a;components-library 关于以上内容&#xff0c;你是否好奇如何实现的&#xff0c;对于大多数项目&#xff0c;诸如…

计算机网络基础-VRRP原理与配置

目录 一、了解VRRP 1、VRRP的基本概述 2、VRRP的作用 二、VRRP的基本原理 1、VRRP的基本结构图 2、设备类型&#xff08;Master&#xff0c;Backup&#xff09; 3、VRRP抢占功能 3.1&#xff1a;抢占模式 3.2、非抢占模式 4、VRRP设备的优先级 5、VRRP工作原理 三…

素颜个人引导页源码

源码介绍 素颜个人引导页源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 效果预览 源码下载 素颜个人引导页源码

Hadoop3:MapReduce源码解读之Map阶段的数据输入过程整体概览(0)

一、MapReduce中数据流向 二、MapTask并行度 1、原理概览 数据块&#xff1a;Block是HDFS物理上把数据分成一块一块。数据块是HDFS存储数据单位。 数据切片&#xff1a;数据切片只是在逻辑上对输入进行分片&#xff0c;并不会在磁盘上将其切分成片进行存储。数据切片是MapRed…

视频去水印电脑版,视频去水印软件

视频去水印怎么去&#xff0c;一直是视频编辑者们的热门话题。那么&#xff0c;如何去除频水印呢&#xff1f;接下来&#xff0c;我们将为您详细介绍视频去水印方法。 第一种方法&#xff1a; 首先通过浏览器打开 “ 51视频处理官网” 的网站。打开网站后&#xff0c;我们上传…

Linux--标准IO库

一、标准IO简介 所谓标准 I/O 库则是标准 C 库中用于文件 I/O 操作&#xff08;譬如读文件、写文件等&#xff09;相关的一系列库函数的集合&#xff0c;通常标准 I/O 库函数相关的函数定义都在头文件 <stdio.h> 中&#xff0c;所以我们需要在程序源码中包含 <s…

Windows 11中删除分区的几种方法,总有一种适合你

序言 想从Windows 11 PC中删除一个分区,以便将空间重新分配给现有分区或创建一个新分区吗?我们将为你介绍删除Windows 11分区的多种方法。 删除Windows上的分区时会发生什么 删除分区时,Windows会擦除该分区的内容,并将该分区从电脑上的任何位置删除。你将丢失保存在该分…

【启程Golang之旅】协程和管道操作

欢迎来到Golang的世界&#xff01;在当今快节奏的软件开发领域&#xff0c;选择一种高效、简洁的编程语言至关重要。而在这方面&#xff0c;Golang&#xff08;又称Go&#xff09;无疑是一个备受瞩目的选择。在本文中&#xff0c;带领您探索Golang的世界&#xff0c;一步步地了…

美国演员工会SAG-AFTRA 要求人工智能在广告中使用演员声音需征得同意并付费

SAG-AFTRA 的新豁免允许在人工智能生成的广告中使用演员的声音&#xff0c;但需要同意、补偿和安全措施 美国演员工会&#xff08;SAG-AFTRA&#xff09;推出了一项新的豁免&#xff0c;以保护会员免受未经授权的人工智能在广告中使用其声音的影响。动态人工智能音频广告豁免定…

C语言----字符串、字符数组

一、定义 C语言中的字符串是以字符数组的形态存在的 在C语言中&#xff0c;没有字符串类型&#xff0c;字符串实际上是使用空字符\0结尾的一维字符数组。因此&#xff0c;\0是用于标记字符串的结束。 二 、如何创建字符串&#xff1f; 1.通过字符数组来创建字符串&#xff0…