当前位置: 首页 > article >正文

探索 LLM:从基础原理到 RAG 实现的深度解析

一.LLM基础知识

1.1 大语言模型(Large Language Model,LLM)

  1. 他是模型,是 AI 能力的核心。
  2. 他是语言模型,其核心能力在于语言能力。
  3. 他是大语言模型,与传统模型相比,它最大的特点就是“大”。

1.2 AI应用

  1. AI应用,就是以 LLM 为核心的各种应用
  2. 从API的角度理解GPT模型,它最核心的参数就是输入一个或多个字符串,然后,大模型输出一个字符串
  3. 与传统的应用开发不同的是,这个 API 并非是传统应用开发中按照特定预期处理的结果。
  4. 使用传统的 API,我们需要关注的是接口文档,而想要发挥 LLM 的威力,我们需要对大模型有一定的了解

1.3 大模型原理

  1. 一次添加一个词
  2. 下一个词是什么
  3. token
  4. 温度(Temperature):表示随机性强弱的概念
    这个参数越小,表示确定性越强,越大,表示随机性越强,简单理解就是,温度越高越活跃
  5. Embedding :在大模型内部处理的是向量,,Embedding 是一种将高维数据(如文本、图像、视频等)转换为低维向量表示的技术。这种技术在自然语言处理(NLP)、计算机视觉等领域有着广泛的应用。Embedding 的核心思想是将离散数据映射到连续的向量空间,使得相似的数据点在向量空间中的距离较近,而不相似的数据点则距离较远。

1.4 promot工程

  1. 提示词 = 定义角色 + 背景信息 + 任务目标 + 输出要求
  2. 要求:大模型处理复杂任务场景的能力
  3. 原因:Agent背后的技术能让大模型推断下一步行为,利用大模型的推理能力,依赖于promot工程
  4. 起源:Natural Language Processing(NLP):如果给予 AI 适当的引导,它能更准确地理解我们的意图,响应我们的指令
  5. 零样本提示(Zero-Shot Prompting):适合简单的任务。比如,一些简单查询就可以使用零样本提示。我们需要做的就是调整提示词
  6. 少样本提示(Few-Shot Prompting):适合复杂的任务。比如,我们需要让 AI 回答一个问题,我们需要提供一些例子,让 AI 学习这些例子,然后再回答问题。
  7. 思维链提示(Chain-of-Thought Prompting):思维链提示给出的答案则是带有完整的思考过程,是一个“慢下来”的答案,于是得到了一个正确的答案
  8. ReAct 框架(Reasoning + Acting) :推理 + 行动=大模型为了完成一个大目标,需要不断地做一些任务。每个任务都会经历思考(Thought)、行动(Action)、观察(Observation)三个阶段。

二.LLM-Code

2.1 Open AI API

  • Text Generation:生成和处理文本
  • Embeddings:文本转向量
  • Speech to Text:语音转文本
  • Image Generation:生成图像
  • Vision:处理图像输入

2.2 SSE

  • SSE 是服务器发送事件(Server-Sent Event),它是一种服务器推送技术,客户端通过 HTTP 连接接收来自服务器的自动更新
  • 它描述了服务器如何在建立初始客户端连接后向客户端发起数据传输。
  1. 为啥不用WebSocket
  • SSE 的技术特点契合流式应答的需求:客户端与大模型的交互是一次性的,每产生一个 token,服务端就可以给客户端推送一次,当生成内容结束时,断掉连接,无需考虑客户端的存活情况
  • 如果采用 WebSocket 的话,服务端就需要维护连接,像 OpenAI 这样的服务体量,维护连接就会造成很大的服务器压力,而且,在生成内容场景下,也没有向服务端进一步发送内容,WebSocket 的双向通信在这里也是多余的
  • SSE 这项技术而言,它存在已经很长时间了,2004 年就有人提出,大模型才流行起来

2.3 核心的三个抽象

  • ChatModel:整个框架的核心,根据输入的内容生成输出
  • PromptTemplate: 负责处理输入,有效拆分开发者提示词和用户提示词
  • OutputParser:负责处理输出,许多输出解析器里包含了格式指令

2.4 编码实现

from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate 
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import RunnableWithMessageHistory 

# 设置 API 密钥
api_key = "你的密钥"
# 初始化 ChatOpenAI 实例时传递 API 密钥
chat_model = ChatOpenAI(model="gpt-4o-mini", api_key=api_key)
store = {}
def get_session_history(session_id: str) -> BaseChatMessageHistory:
    if session_id not in store:
        store[session_id] = InMemoryChatMessageHistory()
    return store[session_id]
prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "你现在扮演程序员的角色,可以直接生成代码",
        ),
        MessagesPlaceholder(variable_name="messages"),
    ]
)
with_message_history = RunnableWithMessageHistory(
    prompt | chat_model,
    get_session_history
)
config = {"configurable": {"session_id": "chatLLMCode"}}
while True:
    user_input = input("You:> ")
    if user_input.lower() == 'exit':
        break
    stream = with_message_history.stream(
        {"messages": [HumanMessage(content=user_input)]},
        config=config
    )
    for chunk in stream:
        print(chunk.content, end='', flush=True)
    print()

三.RAG

3.1 检索增强生成(Retrieval Augmented Generation,RAG)

  1. 检索增强生成(Retrieval Augmented Generation,RAG)是一种结合了检索和生成的技术,它可以在生成文本时,利用外部的知识库来增强生成的内容。
  2. 检索增强生成:在本地检索到相关的内容,把它增强到提示词里,然后再去做内容生成
    产生背景:

* 让LLM知道自己的行业知识,有两种方式

  1. 模型微调:使用业务信息对已经训练好的模型进行微调
  2. RAG:在上下文中带有业务信息,让大模型据此进行整合

3.2 Embeddings和VectorStore

  1. Embeddings:Embeddings 是一种将高维数据(如文本、图像、视频等)转换为低维向量表示的技术。这种技术在自然语言处理(NLP)、计算机视觉等领域有着广泛的应用。Embeddings 的核心思想是将离散数据映射到连续的向量空间,使得相似的数据点在向量空间中的距离较近,而不相似的数据点则距离较远。
  2. VectorStore:VectorStore 是一种用于存储和检索向量数据的技术。它可以将高维向量数据存储在一个向量空间中,以便快速查找相似的向量。VectorStore 的核心思想是将高维数据映射到低维向量空间,以便进行高效的相似度搜索。
    3.索引(Indexing):索引是一种用于快速查找数据的技术。它可以将数据存储在一个索引中,以便快速查找数据。索引的核心思想是将数据映射到一个索引空间中,以便进行高效的查找。
  3. 相似度搜索(Similarity Search):相似度搜索是一种用于查找与给定向量最相似的向量的技术。它可以将给定的向量与索引中的向量进行比较,以便找到最相似的向量。相似度搜索的核心思想是将给定的向量映射到索引空间中,以便进行高效的查找。

3.3 索引过程

  1. 首先,我们需要将文本数据转换为向量。这可以通过使用 Embeddings 技术来实现。
  2. 然后,我们需要将向量存储在一个向量空间中。这可以通过使用 VectorStore 技术来实现。
  3. 最后,索引把信息放到向量数据库中,而检索就是把信息提取出来,提取出来的信息与用户提示词合并起来,再到大模型去完成生成

RAG 是为了让大模型知道更多的东西。

3.4 RAG的实现

# 导入 operator 模块中的 itemgetter 函数,用于从字典中获取指定键的值
from operator import itemgetter
# 导入 typing 模块中的 List 类型,用于定义列表类型的变量
from typing import List
# 导入 tiktoken 库,用于计算字符串中的 token 数量
import tiktoken
# 从 langchain_core.messages 模块中导入各种消息类和消息修剪函数
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, ToolMessage, SystemMessage, trim_messages
# 从 langchain_core.chat_history 模块中导入聊天历史类
from langchain_core.chat_history import BaseChatMessageHistory, InMemoryChatMessageHistory
# 从 langchain_core.prompts 模块中导入提示模板类和消息占位符类
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
# 从 langchain_core.runnables 模块中导入可运行传递类
from langchain_core.runnables import RunnablePassthrough
# 从 langchain_core.runnables.history 模块中导入带有消息历史的可运行类
from langchain_core.runnables.history import RunnableWithMessageHistory
# 从 langchain_openai 模块中导入 OpenAIEmbeddings 类,用于生成文本的嵌入向量
from langchain_openai import OpenAIEmbeddings
# 从 langchain_openai.chat_models 模块中导入 ChatOpenAI 类,用于与 OpenAI 的聊天模型进行交互
from langchain_openai.chat_models import ChatOpenAI
# 从 langchain_chroma 模块中导入 Chroma 类,用于与 Chroma 向量数据库进行交互
from langchain_chroma import Chroma

# 创建一个 Chroma 向量数据库实例,指定集合名称为 "rag",嵌入函数为 OpenAIEmbeddings(),持久化目录为 "vectordb"
vectorstore = Chroma(
    collection_name="rag",
    embedding_function=OpenAIEmbeddings(),
    persist_directory="vectordb"
)

# 将向量数据库转换为检索器,使用相似度搜索类型
retriever = vectorstore.as_retriever(search_type="similarity")

# 定义一个函数,用于计算字符串中的 token 数量
def str_token_counter(text: str) -> int:
    # 获取 "o200k_base" 编码
    enc = tiktoken.get_encoding("o200k_base")
    # 返回编码后的字符串长度
    return len(enc.encode(text))

# 定义一个函数,用于计算消息列表中的 token 数量
def tiktoken_counter(messages: List[BaseMessage]) -> int:
    # 初始化 token 数量为 3
    num_tokens = 3
    # 每个消息的 token 数量为 3
    tokens_per_message = 3
    # 每个名称的 token 数量为 1
    tokens_per_name = 1
    # 遍历消息列表
    for msg in messages:
        # 如果消息是人类消息,设置角色为 "user"
        if isinstance(msg, HumanMessage):
            role = "user"
        # 如果消息是 AI 消息,设置角色为 "assistant"
        elif isinstance(msg, AIMessage):
            role = "assistant"
        # 如果消息是工具消息,设置角色为 "tool"
        elif isinstance(msg, ToolMessage):
            role = "tool"
        # 如果消息是系统消息,设置角色为 "system"
        elif isinstance(msg, SystemMessage):
            role = "system"
        # 如果消息类型不支持,抛出异常
        else:
            raise ValueError(f"Unsupported messages type {msg.__class__}")
        # 累加 token 数量
        num_tokens += (
                tokens_per_message
                + str_token_counter(role)
                + str_token_counter(msg.content)
        )
        # 如果消息有名称,累加名称的 token 数量
        if msg.name:
            num_tokens += tokens_per_name + str_token_counter(msg.name)
    # 返回 token 数量
    return num_tokens

# 创建一个消息修剪器,设置最大 token 数量为 4096,策略为 "last",token 计数器为 tiktoken_counter,包括系统消息
trimmer = trim_messages(
    max_tokens=4096,
    strategy="last",
    token_counter=tiktoken_counter,
    include_system=True,
)

# 创建一个空字典,用于存储会话历史
store = {}

# 定义一个函数,用于获取会话历史
def get_session_history(session_id: str) -> BaseChatMessageHistory:
    # 如果会话 ID 不存在,创建一个新的内存聊天历史实例
    if session_id not in store:
        store[session_id] = InMemoryChatMessageHistory()
    # 返回会话历史实例
    return store[session_id]

# 创建一个 ChatOpenAI 实例,用于与 OpenAI 的聊天模型进行交互
model = ChatOpenAI()

# 创建一个聊天提示模板,包含系统消息、历史消息占位符和人类消息
prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            """You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.
            Context: {context}""",
        ),
        MessagesPlaceholder(variable_name="history"),
        ("human", "{question}"),
    ]
)

# 定义一个函数,用于格式化文档列表
def format_docs(docs):
    # 返回格式化后的文档字符串
    return "\n\n".join(doc.page_content for doc in docs)

# 创建一个上下文获取器,通过检索器获取相关文档,并格式化文档
context = itemgetter("question") | retriever | format_docs
# 创建第一个步骤,将上下文赋值给 "context" 键
first_step = RunnablePassthrough.assign(context=context)
# 创建一个链,将第一个步骤、提示模板、消息修剪器和聊天模型连接起来
chain = first_step | prompt | trimmer | model

# 创建一个带有消息历史的可运行实例,将链、会话历史获取器、输入消息键和历史消息键连接起来
with_message_history = RunnableWithMessageHistory(
    chain,
    get_session_history=get_session_history,
    input_messages_key="question",
    history_messages_key="history",
)

# 设置配置,指定会话 ID 为 "dreamhead"
config = {"configurable": {"session_id": "dreamhead"}}

# 无限循环
while True:
    # 获取用户输入
    user_input = input("You:> ")
    # 如果用户输入为 "exit",退出循环
    if user_input.lower() == 'exit':
        break
    # 如果用户输入为空,继续循环
    if user_input.strip() == "":
        continue
    # 使用带有消息历史的可运行实例处理用户输入,并获取流式响应
    stream = with_message_history.stream({"question": user_input}, config=config)
    # 逐块打印流式响应内容
    for chunk in stream:
        print(chunk.content, end='', flush=True)
    # 打印换行符
    print()


http://www.kler.cn/a/515850.html

相关文章:

  • 解释 RESTful API,以及如何使用它构建 web 应用程序
  • 嵌入式知识点总结 操作系统 专题提升(一)-进程和线程
  • doris:腾讯云 COS导入数据
  • C语言操作符(上)
  • AIGC浪潮下,图文内容社区数据指标体系如何构建?
  • tinykv Project2ab 实现思路
  • Spring注解篇:@PostMapping详解
  • MATLAB中regexptranslate函数用法
  • 主站集中式和分布式的配电自动化系统区别在哪里?各适用于什么场所?一文详解
  • 【YOLOv11改进[Backbone]】使用LSKNet替换Backbone | 用于遥感目标检测的大型选择性核网络 | 2023
  • plus.runtime.install在android10无效
  • 【番外篇】排列组合实现算法1(Java版)
  • notepad++下载安装及使用笔记
  • Redis - 数据类型与编码方式
  • 埃氏算法C++实现: 快速输出质数( 素数 )
  • 免费开源的三维建模软件Blender
  • ThinkPHP 8请求处理-获取请求对象与请求上下文
  • 网络打印机的搜索与连接(一)
  • 设计模式的艺术-享元模式
  • 【Elasticsearch】HNSW
  • 鸿蒙模块概念和应用启动相关类(HAP、HAR、HSP、AbilityStage、UIAbility、WindowStage、window)
  • 无人机图传模块:深入理解其工作原理与实际效用
  • 【Spring Boot】Spring原理:Bean的作用域和生命周期
  • 使用傅里叶变换进行图像边缘检测
  • 华为小米vivo向上,苹果荣耀OPPO向下
  • Haskell语言的区块链