(保姆级教程)RAG教程,混合检索【使用haystack2.0】-----番外篇

因为上一篇写超字数了。

所以,请先阅读上一篇,本篇算彩蛋

只能加个番外篇进行补充了。

haystack是如何将管道结构做到如此优雅的?因为他们的base_class设计师将类继承,多模态,动态方法,静态方法,玩得炉火纯青。

说人话就是:代码整体结构设计者,是个资深的厉害家伙~ 他们的项目,是在动工之前,在设计层面就把所有可能性,扩展性,细节都考虑周全之后,由整体代码设计主管,或者某些大神,设计基础功能组件,每个功能基础组件都继承base组件,进行开发

haystack框架代码思路

haystack的管道思维的代码非常优雅。将大部分功能封装,然后做成功能组件。同时haystack也支持开发人员自定义功能处理组件,比如做一个文本翻译器,语言分类器什么的。为了实现这些自定义的管道组件,并加入进haystack框架,haystack提供了一个模板代码,只需要照着模板代码,即可开发兼容haystack管道的组件。

在仔细研究研了一下haystack的管道。我发现他们的代码架构师对于类继承封装用得炉火纯青。

类,对象

面向对象编程,是很常见的编程思路。你的老师会教过你“new”一个对象,虽然你也依旧是个光棍儿,但是在代码的世界,你已经有了多个“对象”。

以LOL为例,不懂编程的人眼里,是一个一个英雄小兵,草丛河道,野怪,大龙小龙。但是在优秀的开发者眼里,他们是一个一个对象。 程序框架设计师,会标定基础类,base类,这个类对所有继承自base类实例化的对象做了严格的限制,必须提供哪些功能,必须存在哪些变量,有的是必须实例化才可以使用,有的可以不用实例化对象直接在类里面引用。

因此,当我们是LOL的开发人员,对LOL里的英雄标定base类,首先是所有英雄必须存在的共同点。hero_base类包含英雄的共同的功能(不讨论动画什么的),移动,普攻(区分远程近战),一个被动,三个基础技能,一个大招(慧这种只需要增加翻页的功能)。为了实现英雄进入草丛会使敌方看不见,在实例化草丛的时候,标定范围大小,英雄所在的地图坐标,如果进入了草丛所在的地图坐标,则敌方视野消失。 那么当需要开发一个新的英雄,将基础英雄类继承过来,在技能这个function里面重写技能的功能。

haystack框架代码分析

上面的例子如果理解了。 那么回到haystack,标定基础类的一定是个很厉害的人,把所有可能出现的情况都考虑到,然后标定base类。根据开发过程随时修正base类。我们看到的每个功能组件都有一个base类。比如,document_store基础类,如果chroma继承这个基础类,就做成了chroma_document_store,faiss继承这个类,就变成了faiss_document_store,而base_document_store类,统计并约束了所有document_store的功能,如增删改查function等。

同时,由于一个类是可以传入对象的。因此Document对象约束了所有数据的json格式,包括必须包含的字段。将Document对象作为参数传入document_store类,在document_store类的内部执行Document对象的分析,调用writte写入等功能。

这种编程实在是优雅:

非常值得学习的代码设计思路!!!

一个代码结构设计师的工作,需要考虑很多可能性的出现,并标定base类,但是标定好的base类,可以让开发人员围绕base类进行开发,统一代码结构,格式,只需要修改功能部分。 因此超强的扩展性,以及工整程度!

非常值得学习的项目代码,极致的优雅,极致的封装复杂部分,把功能封装成一行命令也很值得学习。

于是,本厮尝试了一下封装huggingface。


首先我创建了一个base_LLM的类:

import torch
import requests
from abc import ABC, abstractmethod
from typing import Union

class BaseModel(ABC):
    def __init__(self, model_path: str = None, server_url: str = None, mode: str = "offline"):
        """
        初始化大模型类
        :param model_path: 离线模式下模型文件的路径
        :param server_url: 在线模式下服务的URL
        :param mode: 模式选择,"offline"或"online"
        """
        self.mode = mode
        if mode == "offline":
            if not model_path:
                raise ValueError("离线模式需要提供模型路径")
            self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
            self.model_path = model_path

            # 解包返回值并赋值
            model_and_tokenizer = self.load_model(model_path)
            self.model = model_and_tokenizer[0]
            self.tokenizer = model_and_tokenizer[1]
        elif mode == "online":
            if not server_url:
                raise ValueError("在线模式需要提供服务URL")
            self.server_url = server_url
        else:
            raise ValueError("模式应为 'offline' 或 'online'")

    @abstractmethod
    def load_model(self, model_path: str):
        """
        加载模型到显卡(需要子类实现)
        :param model_path: 模型路径
        """
        pass

    @abstractmethod
    def _generate_offline(self, user_input: str) -> str:
        """
        离线模式生成回复(需要子类实现)
        :param user_input: 用户输入
        :return: 回复内容
        """
        pass

    def _generate_online(self, user_input: str) -> dict:
        """
        在线模式发送请求获取回复
        :param user_input: 用户输入
        :return: 回复内容
        """
        print("在线模式运行中...")
        payload = {"input": user_input}
        response = requests.post(self.server_url, json=payload)
        if response.status_code == 200:
            return response.json()
        else:
            response.raise_for_status()

    def generate(self, user_input: str) -> Union[str, dict]:
        """
        生成模型回复
        :param user_input: 用户输入的内容
        :return: 模型的回复
        """
        if self.mode == "offline":
            if not hasattr(self, 'model'):
                raise RuntimeError("模型未加载,请检查模型路径或模式选择")
            return self._generate_offline(user_input)
        elif self.mode == "online":
            if not hasattr(self, 'server_url'):
                raise RuntimeError("服务URL未设置")
            return self._generate_online(user_input)

在这个类中,我标定模型是离线部署,还是部署成一个服务的在线部署,可以通过参数进行自定义。

如果是离线部署,则需要提供模型的路径,在线部署,需要传入请求的链接。

接下来,我要创建一个Qwen的LLM类,继承自这个基础类。

我需要这个Qwen离线部署,则继承后,覆盖写入load_model功能,加载模型,以huggingface进行加载。

再覆写generate功能,调用已加载的模型进行输出。

同时将其设置为私有属性,不暴露细节,并且只有实例化了这个Qwen的类,才可以使用这个方法。进行生成。

class QwenLLM(BaseModel):
    def load_model(self, model_path: str):
        """
        自定义加载 Qwen 模型和 tokenizer
        """
        print(f"加载 Qwen 模型: {model_path}")
        model = AutoModelForCausalLM.from_pretrained(
            model_path,
            torch_dtype="auto",
            device_map="auto"
        )
        tokenizer = AutoTokenizer.from_pretrained(model_path)
        print("模型加载完成!")
        return model, tokenizer

    def _generate_offline(self, user_input: str) -> str:
        """
        自定义 Qwen 的离线模式生成逻辑
        """
        print("离线模式运行中...")
        # 添加提示词模板
        messages = [
            {"role": "system", "content": "You are Qwen, created by Alibaba Cloud. You are a helpful assistant."},
            {"role": "user", "content": user_input},
        ]
        # 使用 tokenizer 的方法生成文本
        text = self.tokenizer.apply_chat_template(
            messages,
            tokenize=False,
            add_generation_prompt=True,
        )
        model_inputs = self.tokenizer([text], return_tensors="pt").to(self.model.device)

        # 模型生成回复
        generated_ids = self.model.generate(
            **model_inputs,
            max_new_tokens=512,
        )
        generated_ids = [
            output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
        ]

        response = self.tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
        return response
 

接下来再开一个脚本,测试一下加载刚刚的Qwen类,进行文本生成。

只需要下面的短短几行~~~~~

from Qwen import QwenLLM  # 从 qwen_model 模块中导入 QwenLLM

if __name__ == '__main__':
    # 设置模型路径
    model_path = r"F:\MC-PROJECT\CUDA_Preject\medical_assistant\LLM\models_cache\Qwen2.5-0.5B"

    # 实例化 QwenLLM 类
    llm = QwenLLM(model_path=model_path, mode="offline")

    # 测试输入
    user_input = "能解释一下大语言模型吗?"
    response = llm.generate(user_input)
    print(response)

上面的Qwen代码还可以优化, 还记得python的__init__吗,在实例化对象的时候__init__会立即执行,且执行一次。

因此,加载的模型的代码部分可以写入__init__,在该类被实例化成对象的时候,立刻执行加载命令,后面等待输入内容。

好啦,所谓的彩蛋,只有这么多,希望看完的你能觉得:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值