Parte 1 O que é LangChain: a biblioteca de funções/plug-ins do LLM

Parte 1 O que é LangChain: a biblioteca de funções/plug-ins do LLM 


1.1 A estrutura geral do langchain
Em termos leigos, o chamado langchain (endereço do site oficial, endereço do GitHub) encapsula muitas funções comumente usadas em IA em uma biblioteca e possui interfaces para chamar várias APIs de modelo comercial e modelos de código aberto e suporta os seguintes componentes

Amigos que estão em contato com ele pela primeira vez podem ficar tontos ao ver tantos componentes (são tantas coisas empacotadas, e sinto que ele quer empacotar todas as funções/ferramentas necessárias para o LLM). entendendo, podemos começar com o nível grande Toda a biblioteca langchain é dividida em três camadas principais: camada básica, camada de capacidade e camada de aplicativo

1.1.1 Camada básica: modelos, LLMs, índice
Modelos: Modelos
Vários tipos de modelos e integração de modelos, como as várias APIs/GPT-4 do OpenAI, etc. , fornecem uma interface unificada para vários modelos básicos,
como o preenchimento de uma pergunta e resposta via API
import
os.environ["OPENAI_API_KEY"] = 'your api key'
from langchain.llms import OpenAI
 
llm = OpenAI(model_name="text-davinci-003",max_tokens=1024)
llm("Como avaliar a inteligência artificial ")
Camada LLMS
Esta camada enfatiza principalmente o encapsulamento das capacidades da camada de modelos e as capacidades de saída orientadas a serviços. Ela inclui principalmente:
  várias plataformas de gerenciamento de modelo LLM: enfatizando a riqueza de tipos de modelo e a facilidade de uso.
  Capacidades de serviços integrados Produtos : enfatizando out-of-the-box Usar
  recursos diferenciados: como foco no gerenciamento de Promp (incluindo gerenciamento de prompt, otimização de prompt e serialização de prompt), modo de operação do modelo baseado em recursos compartilhados, etc.

Por exemplo, as APIs PaLM Text do Google ou
        model_token_mapping = {             "gpt-4": 8192,             "gpt-4-0314": 8192,             "gpt-4-0613": 8192,             "gpt-4 in the llms/openai. py arquivo -32k": 32768,             "gpt-4-32k-0314": 32768,             "gpt-4-32k-0613": 32768,             "gpt-3.5-turbo": 4096,             "gpt-3.5-turbo-0301 ": 4096,             "gpt-3.5-turbo-0613": 4096,             "gpt-3.5-turbo-16k": 16385,             "gpt-3.5-turbo-16k-0613": 16385,             "text-ada-001": 2049,             "ada": 2049,             "text-babbage-001": 2040,             "babbage": 2049,















            "text-curie-001": 2049,
            "curie": 2049,
            "davinci": 2049,
            "text-davinci-003": 4097,
            "text-davinci-002": 4097,
            "code-davinci-002": 8001,
            "code-davinci-001": 8001,
            "code-cushman-002": 2048,
            "code-cushman-001": 2048,
        }
Index:索引
对用户私域文本、图片、PDF等各类文档进行存储和检索(相当于结构化文档,以便让外部数据和模型交互),具体实现上有两个方案:
  Vector方案:即对文件先切分为Chunks,在按Chunks分别编码存储并检索,可参考此代码文件:https://github.com/hwchase17/langchain/blob/master/langchain/indexes/vectorstore.py
  KG方案:这部分利用LLM抽取文件中的三元组,将其存储为KG供后续检索,可参考此代码文件:https://github.com/hwchase17/langchain/blob/master/langchain/indexes/graph.py

为了索引,便不得不牵涉以下这些能力
Document Loaders,文档加载的标准接口
与各种格式的文档及数据源集成,比如 Email、Markdown、PDF (所以可以做类似ChatPDF这样的应用)、Youtube …
相近的还有
docstore,其中包含:
document_transformers

embeddings,涉及到各种embeddings算法,分别体现在各种代码文件中:
elasticsearch.py、google_palm.py、gpt4all.py、huggingface.py、huggingface_hub.py
llamacpp.py、minimax.py、modelscope_hub.py、mosaicml.py
openai.py
sentence_transformer.py、spacy_embeddings.py、tensorflow_hub.py、vertexai.py
1.1.2 能力层:Chains、Memory、Tools
如果基础层提供了最核心的能力,能力层则给这些能力安装上手、脚、脑,让其具有记忆和触发万物的能力,包括:Chains、Memory、Tool三部分

Chains:链接
简言之,相当于包括一系列对各种组件的调用,可能是一个 Prompt 模板,一个语言模型,一个输出解析器,一起工作处理用户的输入,生成响应,并处理输出

具体而言,则相当于按照不同的需求抽象并定制化不同的执行逻辑,Chain可以相互嵌套并串行执行,通过这一层,让LLM的能力链接到各行各业
比如与Elasticsearch数据库交互的:elasticsearch_database
比如基于知识图谱问答的:graph_qa
其中的代码文件:chains/graph_qa/base.py 便实现了一个基于知识图谱实现的问答系统,具体步骤为
首先,根据提取到的实体在知识图谱中查找相关的信息「这是通过 self.graph.get_entity_knowledge(entity) 实现的,它返回的是与实体相关的所有信息,形式为三元组」
然后,将所有的三元组组合起来,形成上下文
最后,将问题和上下文一起输入到qa_chain,得到最后的答案

比如能自动生成代码并执行的:llm_math等等
比如面向私域数据的:qa_with_sources,其中的这份代码文件 chains/qa_with_sources/vector_db.py 则是使用向量数据库的问题回答
比如面向SQL数据源的:sql_database,可以重点关注这份代码文件:chains/sql_database/base.py

比如面向模型对话的:chat_models,包括这些代码文件:__init__.py、anthropic.py、azure_openai.py、base.py、fake.py、google_palm.py、human.py、jinachat.py、openai.py、promptlayer_openai.py、vertexai.py

Além disso, há mais atrativos:
Constitution_ai: a lógica de enviesar o resultado final e lidar com questões de conformidade para garantir que o resultado final esteja em conformidade com o valor
llm_checker: a lógica que permite que o LLM detecte automaticamente se sua saída tem algum problema
Memória : Memória
Em suma, é usado para salvar o estado do contexto ao interagir com o modelo e lidar com a memória de longo prazo

Especificamente, esta camada tem principalmente dois pontos principais:
  memória e armazenamento estruturado da entrada e saída durante a execução de Chains e fornecer contexto para a próxima interação. Essa parte pode ser simplesmente armazenada no Redis e
  construída de acordo com o histórico da interação. o mapa de conhecimento fornece resultados precisos de acordo com as informações associadas. O arquivo de código correspondente é: memory/kg.py
Camada de ferramentas.
Na verdade, a camada de ferramentas Chains pode executar alguma lógica específica de acordo com LLM + Prompt, mas se você quiser usar A cadeia para implementar toda a lógica não é Na realidade, também pode ser realizada através da camada de Ferramentas. A camada de Ferramentas é entendida como uma habilidade mais razoável, como pesquisa, Wikipedia, previsão do tempo, serviço ChatGPT,
etc. camada: Agentes
Agentes:
Resumindo, há uma camada básica E a camada de habilidade, podemos construir uma variedade de serviços divertidos e valiosos, aqui está Agente

Especificamente, o Agent age como um agente para enviar uma solicitação ao LLM, então agir e verificar os resultados até que o trabalho seja concluído, incluindo agentes para tarefas que o LLM não pode lidar (como pesquisa ou cálculo, plug-ins como ChatGPT plus têm chamadas para função bing e calculadoras)
Por exemplo, um agente pode usar a Wikipédia para pesquisar a data de nascimento de Barack Obama e, em seguida, usar uma calculadora para calcular sua idade em 2023# pip install wikipedia from
langchain.agents
import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType
 
tools = load_tools(["wikipedia", "llm-math"], llm=llm)
agent = initialize_agent(tools, 
                         llm, 
                         agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, 
                         verbose=True)
 
 
agent.run( "Quando é o aniversário de Obama? a Quantos anos ele tem em 2023?")
Além disso, você pode prestar atenção a este arquivo de código sobre a Wikipedia: langchain/docstore/wikipedia.py ...
No final, a arquitetura técnica geral do langchain pode ser mostrada na figura abaixo (verifique a imagem grande em alta definição, além disso, há outro diagrama de arquitetura aqui)

1.2 Alguns exemplos de aplicação de langchain: pesquisa online + perguntas e respostas de documentos
Mas lendo a introdução teórica, você pode não ser capaz de entender para que serve langchain. Para sua conveniência, aqui estão alguns exemplos de aplicação de langchain

1.2.1 Pesquise no Google e retorne a resposta
Visto que o Serpapi é necessário para a implementação e o Serpapi fornece uma interface de API para pesquisa do Google

Então, primeiro registre um usuário no site oficial do Serpapi (https://serpapi.com/), e copie-o para gerar uma chave de API para nós e, em seguida, defina-a na variável de ambiente

import os
os.environ["OPENAI_API_KEY"] = 'sua chave de API'
os.environ["SERPAPI_API_KEY"] = 'sua chave de API'
Em seguida, comece a escrever o código

from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.llms import OpenAI
from langchain.agents import AgentType
 
# load OpenAI model
llm = OpenAI(temperature=0,max_tokens=2048) 
 
 # load serpapi tools
tools = load_tools([" serpapi"])
 
# Se você quiser recalcular após pesquisar, você pode escrever assim
# tools = load_tools(['serpapi', 'llm-math'], llm=llm)
 
# Se você quiser usar o print do python para fazer isso after search Para um cálculo simples, você pode escrever assim
# tools=load_tools(["serpapi","python_repl"])
 
# As ferramentas precisam ser inicializadas após o carregamento, o parâmetro detalhado é True e todos os detalhes da execução serão impressos
agente = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)
 
# run agent
agent.run("What's the date today? What great events have taken place today in history?")
1.2.2 用不到 50 行代码实现一个文档对话机器人
众所周知,由于ChatGPT训练的数据只更新到 2021 年,因此它不知道互联网最新的知识(除非它调用搜索功能bing),而利用 “LangChain + ChatGPT的API” 则可以用不到 50 行的代码然后实现一个和既存文档的对话机器人

假设所有 2022 年更新的内容都存在于 2022.txt 这个文档中,那么通过如下的代码,就可以让 ChatGPT 来支持回答 2022 年的问题

其中原理也很简单:

Vetorização de entrada/prompt do usuário,
segmentação de documento,
segmentação de documento
, vetorização de texto,
vetorização pode ser usada para calcular a semelhança entre vetores,
e o texto vetorizado é armazenado no banco de dados vetorial
para encontrar a resposta (resposta) nos dados vetoriais de acordo com a entrada/aviso do usuário O julgamento é baseado na semelhança entre o prompt/entrada e o vetor de parágrafo relevante no texto)
Finalmente, a resposta é retornada por LLM
#!/usr/bin/python
# -*- codificação: UTF- 8 -*-
 
import os # Importar o módulo os, Para operações relacionadas ao sistema operacional,
importar jieba como jb #Importar dicionário de sinônimos gago
de langchain.chains import ConversationalRetrievalChain #Importar a classe de langchain.chat_models import ChatOpenAI #Importar a classe de langchain
para criar Objetos ChatOpenAI
.document_loaders import DirectoryLoader # Importar classe para carregar arquivos
de langchain.embeddings import OpenAIEmbeddings # Importar classe para criar incorporações de vetores de palavras
from langchain.text_splitter import TokenTextSplitter       # 导入用于分割文档的类
from langchain.vectorstores import Chroma                   # 导入用于创建向量数据库的类
 
# 初始化函数,用于处理输入的文档
def init():  
    files = ['2022.txt']      # 需要处理的文件列表
    for file in files:        # 遍历每个文件
        with open(f"./data/{file}", 'r', encoding='utf-8') as f:   # 以读模式打开文件
            data = f.read()   # 读取文件内容
 
        cut_data = " ".join([w for w in list(jb.cut(data))])       # 对读取的文件内容进行分词处理
        cut_file = f"./data/cut/cut_{file}"      # 定义处理后的文件路径和名称
        with open(cut_file, 'w') as f:           # 以写模式打开文件
            f.write(cut_data)                    # 将处理后的内容写入文件
 
# 新建一个函数用于加载文档
def load_documents(directory):  
    # Cria um objeto DirectoryLoader para carregar todos os arquivos .txt na pasta especificada
    loader = DirectoryLoader(directory, glob='**/*.txt')  
    docs = loader.load() # carrega arquivos
    return docs # Retornar documentos carregados
 
# Criar uma nova função para dividir documentos
def split_documents(docs):  
    # Criar objetos TokenTextSplitter para dividir documentos
    text_splitter = TokenTextSplitter(chunk_size=1000, chunk_overlap=0)  
    docs_texts = text_splitter.split_documents(docs) # Segmentar o texto carregado
    return docs_texts # Retorna o texto segmentado
 
# Cria uma nova função para criar uma incorporação de palavras def
create_embeddings(api_key):  
    # Cria um objeto OpenAIEmbeddings para obter o vetor de palavras de OpenAI
    embeddings = OpenAIEmbeddings(openai_api_key=api_key)  
    return embeddings # retorna a incorporação de palavras criadas
 
# 新建一个函数用于创建向量数据库
def create_chroma(docs_texts, embeddings, persist_directory):  
    # 使用文档,embeddings和持久化目录创建Chroma对象
    vectordb = Chroma.from_documents(docs_texts, embeddings, persist_directory=persist_directory)  
    vectordb.persist()      # 持久化存储向量数据
    return vectordb         # 返回创建的向量数据库
 
# load函数,调用上面定义的具有各个职责的函数
def load():
    docs = load_documents('./data/cut')        # 调用load_documents函数加载文档
    docs_texts = split_documents(docs)         # 调用split_documents函数分割文档
    api_key = os.environ.get('OPENAI_API_KEY')   # 从环境变量中获取OpenAI的API密钥
    embeddings = create_embeddings(api_key)      # 调用create_embeddings函数创建词嵌入
 
    # 调用create_chroma函数创建向量数据库
    vectordb = create_chroma(docs_texts, embeddings, './data/cut/')  
 
    # Criar objeto ChatOpenAI para diálogo de bate-papo
    openai_ojb = ChatOpenAI(temperatura=0, model_name="gpt-3.5-turbo")  
 
    # Do modelo e vetor O recuperador cria the ConversationalRetrievalChain object
    chain = ConversationalRetrievalChain.from_llm(openai_ojb, vectordb.as_retriever())  
    return chain # Retorne o objeto
 
# Chame a função load para obter o ConversationalRetrievalChain object
chain = load()  
 
# Defina uma função para obter a resposta de acordo com a entrada question
def get_ans(question):  
    chat_history = [] # Inicializa o histórico do chat como uma lista vazia
    result = chain({ # chama o objeto da cadeia para obter o resultado do chat
        'chat_history': chat_history, # passa no histórico do chat
        'question' : question, # pass in the question
    } )
    return result['answer'] # retorna a resposta obtida
 
if __name__ == '__main__': # se este script for executado como o programa principal
    s = input('please input:') # obtém a entrada do usuário
    while s != 'exit ': # Se a entrada do usuário não for 'exit'
        ans = get_ans(s) # Chame a função get_ans para obter a resposta
        print(ans) # Imprima a resposta
        s = input('please input:') # Obtenha a entrada do usuário

//ser atualizado

A segunda parte é baseada em LangChain + ChatGLM-6B resposta a perguntas da base de conhecimento local
2.1 Etapas principais: Como implementar respostas a perguntas
da base de conhecimento local por meio de LangChain +LLM : langchain-ChatGLM (este é o endereço do GitHub e, claro, existem projetos semelhantes que suportam o Vicuna-13b, como LangChain-ChatGLM-Webui), o objetivo é construir uma base de conhecimento amigável para cenas chinesas e código aberto modelos e pode executar a solução de perguntas e respostas offline

Inspirado no projeto document.ai de GanymedeNil e no ChatGLM-6B Pull Request criado por Alex Zhangji, este projeto estabelece um aplicativo de resposta a perguntas de base de conhecimento local que pode ser implementado usando modelos de código aberto durante todo o processo. Agora suporta o acesso de modelos de linguagem grandes, como ChatGLM-6B, ClueAI/ChatYuan-large-v2.
Neste projeto, a seleção padrão de Incorporação é GanymedeNil/text2vec-large-chinese, e a seleção padrão de LLM é ChatGLM-6B , contando com os modelos acima, este projeto pode realizar toda a implantação privada off-line usando o modelo de código aberto
. /6 Vetorização de texto -> 8/9 Vetorização de perguntas -> 10 Corresponde aos k vetores de perguntas mais semelhantes no vetor do documento -> 11/ 13/12 O texto correspondente é adicionado como contexto e perguntas ao prompt -> 15/14 para enviar ao LLM para gerar uma resposta)

A primeira etapa: carregar arquivo - ler arquivo - divisor de texto (divisor de texto)
carregar arquivo: esta é a etapa de ler o arquivo da base de conhecimento armazenado localmente
ler arquivo: ler o conteúdo do arquivo carregado, geralmente convertê-lo em formato de texto
Divisor de texto (texto divisor): divide o texto de acordo com certas regras (como parágrafos, frases, palavras, etc.), o seguinte é apenas um exemplo de código (não o código-fonte do projeto langchain-ChatGLM)

    def _load_file(self, filename):
        # Determine o tipo de arquivo
        if filename.lower().endswith(".pdf"): # Se o arquivo estiver no formato PDF
            loader = UnstructuredFileLoader(filename) # Use o carregador UnstructuredFileLoader para carregar o Arquivo PDF
            text_splitor = CharacterTextSplitter() # Use CharacterTextSplitter para dividir o texto no arquivo
            docs = loader.load_and_split(text_splitor) # Carregue o arquivo e divida o texto
        else: # Se o arquivo não estiver no formato PDF
            loader = UnstructuredFileLoader(filename, mode="elements") # Use UnstructuredFileLoader O carregador carrega o arquivo no modo elemento
            text_splitor = CharacterTextSplitter() # Use CharacterTextSplitter para dividir o texto no arquivo
            docs = loader.load_and_split(text_splitor) # Carregar o arquivo e dividir o texto
        return docs    # 返回处理后的文件数据
第二阶段:文本向量化(embedding)-存储到向量数据库
文本向量化(embedding):这通常涉及到NLP的特征抽取,可以通过诸如TF-IDF、word2vec、BERT等方法将分割好的文本转化为数值向量

    # 初始化方法,接受一个可选的模型名称参数,默认值为 None
    def __init__(self, model_name=None) -> None:  
        if not model_name:  # 如果没有提供模型名称
            # 使用默认的嵌入模型
            # 创建一个 HuggingFaceEmbeddings 对象,模型名称为类的 model_name 属性
            self.embeddings = HuggingFaceEmbeddings(model_name=self.model_name)  
存储到向量数据库:文本向量化之后存储到数据库vectorstore (FAISS,下一节会详解FAISS)

def init_vector_store(self):
    persist_dir = os.path.join(VECTORE_PATH, ".vectordb") # O endereço do banco de dados vetorial persistente
    print("Endereço persistente do banco de dados vetorial: ", persist_dir) # Imprime o endereço persistente
 
    # Se endereço persistente existe
    se os.path.exists(persist_dir):  
        # Carregar do arquivo persistente local
        print("Carregar dados do vetor local...")
        # Use Chroma para carregar dados vetoriais persistentes
        vector_store = Chroma(persist_directory=persist_dir, embedding_function=self. embeddings)  
 
    # Se o endereço persistente não existir
    mais:      
        # Carregar
        documentos da base de conhecimento = self.load_knownlege()  
        # Use Chroma para criar armazenamento vetorial a partir de documentos
        vector_store = Chroma.from_documents(documents=documents, 
                                             embedding=self.embeddings,
                                             persist_directory=persist_dir)  
        vector_store.persist() # armazenamento de vetor persistente
    return vector_store # armazenamento de vetor de retorno
onde a implementação de load_knownlege é

def load_knownlege(self):
    documents = [] # Inicializa uma lista vazia para armazenar documentos
 
    # Percorre todos os arquivos no diretório DATASETS_DIR
    para root, _, arquivos em os.walk(DATASETS_DIR, topdown=False):
        for file in files:
            filename = os.path.join(root, file) # Obter o caminho completo do arquivo
            docs = self._load_file(filename) # Carregar documentos no arquivo
 
            # Atualizar dados de metadados
            new_docs = [] # Inicializar uma lista vazia para armazenar novos documentos
            for doc in docs:
                # Atualize os metadados do documento e substitua o valor do campo "source" por um caminho relativo que não inclua DATASETS_DIR
                doc.metadata = {"source": doc.metadata["source"]. replace(DATASETS_DIR, "")} 
                print("Inicialização do vetor do documento 2, aguarde...", doc.metadata) # imprime os metadados do documento que está sendo inicializado
                new_docs.append(doc)  # 将文档添加到新文档列表
 
            docments += new_docs      # 将新文档列表添加到总文档列表
 
    return docments      # 返回所有文档的列表
第三阶段:问句向量化
这是将用户的查询或问题转化为向量,应使用与文本向量化相同的方法,以便在相同的空间中进行比较

第四阶段:在文本向量中匹配出与问句向量最相似的top k个
这一步是信息检索的核心,通过计算余弦相似度、欧氏距离等方式,找出与问句向量最接近的文本向量

    def query(self, q):
        """在向量数据库中查找与问句向量相似的文本向量"""
        vector_store = self.init_vector_store()
        docs = vector_store.similarity_search_with_score(q, k=self.top_k)
        for doc in docs:
            dc, s = doc
            yield s, dc
第五阶段:匹配出的文本作为上下文和问题一起添加到prompt中
这是利用匹配出的文本来形成与问题相关的上下文,用于输入给语言模型

A sexta etapa: enviar para o LLM para gerar respostas
Por fim, envie esta pergunta e o contexto para o modelo de linguagem (como a série GPT), deixe-o gerar respostas
como consulta de conhecimento (fonte de código)

class KnownLedgeBaseQA:
    # Initialize
    def __init__(self) -> None:
        k2v = KnownLedge2Vector() # Crie um conversor de conhecimento para vetor
        self.vector_store = k2v.init_vector_store() # Inicialize o armazenamento de vetores
        self.llm = VicunaLLM() # Crie Um objeto VicunaLLM
    
    # Obtenha respostas semelhantes para a consulta
    def get_similar_answer(self, query):
        # Crie um modelo de prompt prompt
        = PromptTemplate(
            template=conv_qa_prompt_template, 
            input_variables=["context", "question"] # As variáveis ​​de entrada incluem "context"( context) e "question" (question)
        #
 
        use vector store para recuperar documentos
        retriever = self.vector_store.as_retriever(search_kwargs={"k": VECTOR_SEARCH_TOP_K}) 
        docs = retriever.get_relevant_documents(query=query) # Obtenha o texto relacionado à consulta
 
        context = [d.page_content for d in docs] # Extraia o conteúdo do texto
        result = prompt.format(context="\n".join (contexto), question=query) # Formate o template e preencha com o conteúdo e questões extraídas do texto
        return result # Retorna o resultado

Como você pode ver, este método de combinar langchain+LLM é especialmente adequado para alguns campos verticais ou empresas de grandes grupos para construir um sistema privado de perguntas e respostas por meio dos recursos de diálogo inteligente do LLM , e também é adequado para indivíduos conduzirem perguntas e respostas especificamente para alguns artigos em inglês. Por exemplo, um projeto de código aberto relativamente popular: ChatPDF, do ponto de vista do processamento de documentos, o processo de implementação é o seguinte (fonte):

2.2 Facebook AI Similarity Search (FAISS): Eficiente Vector Similarity Search


O nome completo de Faiss é Facebook AI Similarity Search (página de introdução oficial, endereço do GitHub). É uma ferramenta desenvolvida pela equipe de IA do FaceBook para problemas de recuperação de similaridade em grande escala. É escrita em C++ e possui uma interface python. Pode indexar 1 bilhão de ordens de magnitude. Obtenha desempenho de recuperação em nível de milissegundos

Simplificando, o trabalho de Faiss é encapsular nosso próprio conjunto de vetores candidatos em um banco de dados de índice, o que pode acelerar nosso processo de recuperação de vetores semelhantes TopK, e alguns dos índices também oferecem suporte à construção de GPU.

2.2.1 Faiss检索相似向量TopK的基本流程
Faiss检索相似向量TopK的工程基本都能分为三步:

得到向量库
import numpy as np
d = 64                                           # 向量维度
nb = 100000                                      # index向量库的数据量
nq = 10000                                       # 待检索query的数目
np.random.seed(1234)             
xb = np.random.random((nb, d)).astype('float32')
xb[:, 0] += np.arange(nb) / 1000.                # index向量库的向量
xq = np.random.random((nq, d)).astype('float32')
xq[:, 0] += np.arange(nq) / 1000.                # 待检索的query向量
用faiss 构建index,并将向量添加到index中
其中的构建索引选用暴力检索的方法FlatL2,L2代表构建的index采用的相似度度量方法为L2范数,即欧氏距离
import faiss          
index = faiss.IndexFlatL2(d)             
print(index.is_trained) # A saída é True, o que significa que esse tipo de índice não precisa ser treinado, apenas adicione o vetor a ele
index.add(xb) # Adicione o vetor da biblioteca de vetores ao índice
print (index.ntotal) # Exibe o número total de vetores contidos no índice, que é 100000.
Use o índice faiss para recuperar e recuperar a consulta semelhante de TopK
k = 4 # Valor K de topK
D, I = index.search( xq, k)# xq é o vetor a ser recuperado, retornado I é a lista de índices mais semelhante a TopK para cada consulta a ser recuperada e D é a distância correspondente print(I[:5])
print
(D[ -5:])
A impressão é:
>>> 
[[ 0 393 363 78 ] 
 [ 1 555 277 364] 
 [ 2 304 101 13] 
 [ 3 173 18 182] 
 [ 4 288 370 531]]  
[[ 0. 7.17517328 7.2076292 7.25116253] [ 0.  
 6.32356453 6.6 845808 6.79994535]  
 [ 0. 5.79640865 6.39173603 7.28151226]  
 [ 0. 7.27790546 7.52798653 7.66284657]  
 [ 0. 6.76380348 7.29512024 7.36881447]]

2.2.2 Múltiplas formas de construir índices no FAISS


O método de índice de construção e o método de passagem de parâmetro podem ser

dim, medida = 64, faiss.METRIC_L2
param = 'Flat'
index = faiss.index_factory(dim, param, measure)
dim é a dimensão do vetor,
o parâmetro mais importante é param, que é o parâmetro passado para o índice, representando o que precisa ser construído tipo de índice;
medida é um método de medição, atualmente suporta dois tipos, distância euclidiana e produto interno, ou seja, o produto interno. Portanto, para calcular a similaridade do cosseno, você só precisa normalizar os vecs e usar o produto interno para medir.
Neste artigo, o faiss agora suporta oficialmente oito métodos de medição, que são:

METRIC_INNER_PRODUCT (produto interno)
METRIC_L1 (distância de Manhattan)
METRIC_L2 (distância euclidiana)
METRIC_Linf (norma infinita) METRIC_Lp
(norma p)
METRIC_BrayCurtis (dissimilaridade BC)
METRIC_Canberra (distância de alcance/distância de Canberra)
METRIC_JensenShannon (divergência JS)
2.2.2.1 Plano: força bruta Vantagens de recuperação
: Este método é o mais preciso e o método com a maior taxa de recuperação entre todos os índices de Faiss; Desvantagens
: Velocidade lenta e grande uso de memória.
Uso: Há muito poucos conjuntos de candidatos a vetores, menos de 500.000, e a memória não é apertada.
Nota: Embora sejam todas pesquisas violentas, a velocidade de pesquisa violenta de faiss é muito mais rápida do que a pesquisa violenta escrita pelos próprios programadores comuns, portanto, não significa que seja inútil. Recomenda-se que os alunos que precisam de pesquisa violenta ainda usem faiss .
Método de construção:
dim, measure = 64, faiss.METRIC_L2
param = 'Flat'
index = faiss.index_factory(dim, param, measure)
index.is_train # saída é True
index.add(xb) # adiciona vetor ao índice
2.2.2.2 IVFx Flat: Inversão de recuperação de força bruta
Vantagens: IVF usa principalmente a ideia de inversão. A tecnologia de inversão no cenário de recuperação de documentos significa que um kw é seguido por muitos documentos contendo a palavra. Uma vez que o número de kw está longe É menor que doc, então reduzirá muito o tempo de recuperação. Como usar inversão em vetor? Você pode retirar o ID do vetor sob cada centro do cluster, pendurar um monte de vetores não centrais atrás de cada ID central, encontrar os poucos IDs centrais mais próximos sempre que consultar o vetor e procurar os não centros sob esses centros, respectivamente. . Melhore a eficiência da pesquisa reduzindo o intervalo de pesquisa.
Desvantagens: A velocidade não é muito rápida.
Uso: Comparado com Flat, aumentará muito a velocidade de recuperação. Recomenda-se que milhões de vetores possam ser usados.
Parâmetro: x em IVFx é o número de centros de agrupamento k-means
Método de construção:
dim, measure = 64, faiss.METRIC_L2 
param = 'IVF100,Flat' # Representa o centro de agrupamento k-means é 100,   
index = faiss. index_factory(dim , param, measure)
print(index.is_trained) # A saída é False neste momento, porque o índice invertido precisa treinar k-means,
index.train(xb) # Portanto, você precisa treinar o índice primeiro e depois adicionar o vetor
index.add (xb)                                     
2.2.2.3 PQx: Quantização do Produto
优点:利用乘积量化的方法,改进了普通检索,将一个向量的维度切成x段,每段分别进行检索,每段向量的检索结果取交集后得出最后的TopK。因此速度很快,而且占用内存较小,召回率也相对较高。
缺点:召回率相较于暴力检索,下降较多。
使用情况:内存及其稀缺,并且需要较快的检索速度,不那么在意召回率
参数:PQx中的x为将向量切分的段数,因此,x需要能被向量维度整除,且x越大,切分越细致,时间复杂度越高
构建方法:
dim, measure = 64, faiss.METRIC_L2 
param =  'PQ16' 
index = faiss.index_factory(dim, param, measure)
print(index.is_trained)                          # 此时输出为False,因为倒排索引需要训练k-means,
index.train(xb)                                  # 因此需要先训练index,再add向量
index.add(xb)          
2.2.2.4 IVFxPQy 倒排乘积量化
优点:工业界大量使用此方法,各项指标都均可以接受,利用乘积量化的方法,改进了IVF的k-means,将一个向量的维度切成x段,每段分别进行k-means再检索。
缺点:集百家之长,自然也集百家之短
使用情况:一般来说,各方面没啥特殊的极端要求的话,最推荐使用该方法!
Parâmetros: IVFx, PQy, onde xey são os mesmos que acima
Método de construção:
dim, measure = 64, faiss.METRIC_L2  
param = 'IVF100,PQ16'
index = faiss.index_factory(dim, param, measure) 
print(index. is_trained) # Neste momento, a saída é False, porque o índice invertido precisa treinar k-means, 
index.train(xb) # Portanto, você precisa treinar o índice primeiro e depois adicionar o vetor index.add(xb)       
2.2.2.5 LSH Local Sensitive Hash
Princípio: Hash É familiar para todos, mas o vetor também pode usar hash para acelerar a pesquisa. O hash de que estamos falando aqui se refere ao Locality Sensitive Hashing (LSH), que é diferente do tradicional hashing, que tenta evitar colisões e é localmente sensível.Ele depende de colisões para encontrar vizinhos. Se dois pontos no espaço de alta dimensão estiverem muito próximos, projete uma função de hash para realizar o cálculo de hash nesses dois pontos e, em seguida, divida-os em baldes, para que seus valores de balde de hash tenham uma alta probabilidade de serem iguais. a distância entre eles é grande, a probabilidade de que eles tenham o mesmo valor de balde de hash será muito pequena.
Vantagens: treinamento muito rápido, suporte para importação em lote, o índice ocupa uma pequena quantidade de memória e a recuperação é relativamente rápida
Desvantagens: a taxa de recuperação é muito baixa.
Uso: A biblioteca de vetores candidatos é muito grande, a recuperação off-line e os recursos de memória são relativamente escassos
Método de construção:
dim, measure = 64, faiss.METRIC_L2  
param = 'LSH'
index = faiss.index_factory(dim, param, measure) 
print(index.is_trained) # A saída é True neste momento
index.add(xb)    

  
2.2.2.6 HNSWx


Vantagens: Este método é um método aprimorado baseado na recuperação de gráficos. A velocidade de recuperação é extremamente rápida e os resultados da recuperação podem ser recuperados em segundos no nível de 1 bilhão. Além disso, a taxa de recuperação é quase comparável à de Flat e pode chegar a surpreendentes 97%. A complexidade de tempo de recuperação é loglogn, que quase pode ignorar a magnitude dos vetores candidatos. E suporta importação em lote, que é muito adequada para tarefas online e experiência em milissegundos.
Desvantagens: a construção de um índice é extremamente lenta e ocupa muita memória (a maior em Faiss, maior que o tamanho da memória ocupada pelo vetor original) Parâmetros:
x em HNSWx é o número máximo de nós conectados a cada ponto ao construir um gráfico, quanto maior for x, a composição Quanto mais complexa for a consulta, mais precisa será a consulta. Obviamente, o tempo para construir o índice será mais lento. x pode ser qualquer número inteiro de 4 a 64.
Uso: não se preocupe com a memória e tenha bastante tempo para construir o índice
Método de construção:
dim, measure = 64, faiss.METRIC_L2   
param = 'HNSW64' 
index = faiss.index_factory(dim, param, measure)  
print(index.is_trained ) # Neste momento, a saída é True 
index.add(xb)
2.3 Implantação do projeto: langchain + ChatGLM-6B para construir uma base de conhecimento local Perguntas e respostas
2.3.1 Processo de implantação 1: Suporta vários modos de uso
Entre eles, o modelo LLM pode ser selecionado de acordo com as necessidades reais do negócio, O ChatGLM-6B usado neste projeto, seu endereço GitHub é: https://github.com/THUDM/ChatGLM-6B
O ChatGLM-6B é um modelo de linguagem de diálogo bilíngue chinês-inglês de código aberto baseado na arquitetura General LanguageModel (GLM) com 6,2 bilhões de parâmetros. Combinado com a tecnologia de quantização de modelo, os usuários podem implantar localmente em placas gráficas de consumo (apenas 6 GB de memória de vídeo são necessários no nível de quantização INT4)

O ChatGLM-6B usa tecnologia semelhante ao ChatGPT, otimizado para perguntas e respostas em chinês e diálogo. Após cerca de 1T de identificadores de treinamento bilíngue chinês-inglês, complementado por supervisão e ajuste fino, auto-ajuda de feedback, aprendizado de reforço de feedback humano e outras tecnologias, o ChatGLM-6B com 6,2 bilhões de parâmetros foi capaz de gerar respostas bastante alinhadas com preferências humanas

Crie um ambiente python3.8.13 (o arquivo de modelo ainda está disponível)
conda create -n langchain python==3.8.13
Puxe o projeto
git clone https://github.com/imClumsyPanda/langchain-ChatGLM.git
Entre no diretório
cd langchain -ChatGLM
installation requirements.txt
conda activate langchain
pip install -r requirements.txt
A versão mais alta do langchain suportada pelo ambiente atual é 0.0.166, e 0.0.174 não pode ser instalada, então tente instalar 0.0.166 primeiro. Modifique a
configuração caminho do arquivo:
vi configs/model_config.py
defina o caminho de chatglm-6b para seu próprio
"chatglm-6b": { "name": "chatglm-6b", "pretrained_model_name": "/data/sim_chatgpt/chatglm-6b" , "local_model_path" : None, "provides": "ChatGLM" Modifique o arquivo de código a ser executado: webui.py vi webui.py Defina o compartilhamento na função de inicialização final como True e defina o inbrowser como True







执行webui.py文件
python webui.py
可能是网络问题,无法创建一个公用链接。可以进行云服务器和本地端口的映射,参考:https://www.cnblogs.com/monologuesmw/p/14465117.html
对应输出:

占用显存情况:大约15个G

2.3.2 部署过程二:支持多种社区上的在线体验
项目地址:https://github.com/thomas-yanxin/LangChain-ChatGLM-Webui
HUggingFace社区在线体验:https://huggingface.co/spaces/thomas-yanxin/LangChain-ChatLLM

另外也支持ModelScope魔搭社区、飞桨AIStudio社区等在线体验

Baixe o projeto
git clone https://github.com/thomas-yanxin/LangChain-ChatGLM-Webui.git
Entre no diretório
cd LangChain-ChatGLM-Webui
para instalar os pacotes necessários
pip install -r requirements.txt
pip install gradio== 3.10
modificação config.py
init_llm = "ChatGLM-6B"
 
llm_model_dict = {     "chatglm": {         "ChatGLM-6B": "/data/sim_chatgpt/chatglm-6b", modifique o arquivo app.py, defina o compartilhamento no lançamento função como True , inbrowser é definido como True para executar o arquivo webui.py python webui.py





 A memória de vídeo ocupa cerca de 13G

A terceira parte é uma análise aprofundada linha por linha: Interpretação do código-fonte do projeto langchain-ChatGLM
e, em seguida, revisão do diagrama de arquitetura do projeto langchain-ChatGLM (fonte)

Você descobrirá que o projeto é composto principalmente pelos seguintes módulos principais

chains: implementação de link de trabalho, como chains/local_doc_qa implementa perguntas e respostas com base em originais carregadosarquivos
configs: armazenamento de arquivo de configuraçãodocumentos locais textsplitter : classe de implementação de segmentação de texto vectorstores: usado para armazenar arquivos de biblioteca de vetores, ou seja, a ontologia da base de conhecimento local . Em seguida, para conveniência dos leitores, compreensão mais rápida






Basicamente, adicionei comentários em chinês a "cada linha de código no projeto a seguir"
e, para entender melhor, interpretei a ordem de cada pasta de código uma a uma de acordo com o processo do projeto (em vez de cada arquivo de código no GitHub em a imagem acima Ordem de apresentação da pasta)
Se você tiver alguma dúvida, pode deixar um comentário a qualquer momento

3.1 agent: custom_agent/bing_search
3.1.1 agent/custom_agent.py
from langchain.agents import Tool # Import tool module
from langchain.tools import BaseTool # Import basic tool class
from langchain import PromptTemplate, LLMChain # Import prompt template and language model chain
from agent.custom_search import DeepSearch # Importar módulo de pesquisa personalizada
 
# Importar agente básico de ação única, analisador de saída, agente de ação única de modelo de linguagem e executor de agente
de langchain.agents import BaseSingleActionAgent, AgentOutputParser, LLMSingleActionAgent, AgentExecutor    
da digitação import List, Tuple, Any, Union , Opcional, Tipo # Importar módulo de anotação de tipo
de langchain.schema import AgentAction, AgentFinish # Importar ação do agente e modo de conclusão do agente
de langchain.prompts import StringPromptTemplate # Importar modelo de prompt de string
from langchain.callbacks.manager import CallbackManagerForToolRun # Importar ferramenta para executar o gerenciador de callback
de langchain.base_language import BaseLanguageModel # Importar modelo de idioma básico
import re # Importar módulo de expressão regular
 
# Definir um template de agente string
agent_template = """
Você agora é um {role }. Aqui estão algumas informações conhecidas:
{related_content}
{background_infomation}
{question_guide}: {input}
{answer_format}
"""
 
# Defina uma classe de modelo de prompt personalizado, herdada da
classe de modelo de prompt de string CustomPromptTemplate(StringPromptTemplate):
    template: str # Prompt template string
    tools: List[Tool] # Tool list
 
    # Defina uma função de formatação para gerar o modelo de prompt final de acordo com os parâmetros fornecidos
    def format(self, **kwargs) -> str:
        intermediate_steps = kwargs.pop("intermediate_steps")
        # 判断是否有互联网查询信息
        if len(intermediate_steps) == 0:
            # 如果没有,则给出默认的背景信息,角色,问题指导和回答格式
            background_infomation = "\n"
            role = "傻瓜机器人"
            question_guide = "我现在有一个问题"
            answer_format = "如果你知道答案,请直接给出你的回答!如果你不知道答案,请你只回答\"DeepSearch('搜索词')\",并将'搜索词'替换为你认为需要搜索的关键词,除此之外不要回答其他任何内容。\n\n下面请回答我上面提出的问题!"
 
        else:
            # 否则,根据 intermediate_steps 中的 AgentAction 拼装 background_infomation
            background_infomation = "\n\n你还有这些已知信息作为参考:\n\n"
            action, observation = intermediate_steps[0]
            background_infomation += f"{observation}\n"
            role = "聪明的 AI 助手"
            question_guide = "请根据这些已知信息回答我的问题"
            answer_format = ""
 
        kwargs["background_infomation"] = background_infomation
        kwargs["role"] = role
        kwargs["question_guide"] = question_guide
        kwargs["answer_format"] = answer_format
        return self.template.format(**kwargs)  # 格式化模板并返回
 
# 定义一个自定义搜索工具类,继承自基础工具类
class CustomSearchTool(BaseTool):
    name: str = "DeepSearch"           # 工具名称
    description: str = ""              # 工具描述
 
    # 定义一个运行函数,接受一个查询字符串和一个可选的回调管理器作为参数,返回DeepSearch的搜索结果
    def _run(self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None):
        return DeepSearch.search(query = query)
 
    # 定义一个异步运行函数,但由于DeepSearch不支持异步,所以直接抛出一个未实现错误
    async def _arun(self, query: str):
        raise NotImplementedError("DeepSearch does not support async")
 
# 定义一个自定义代理类,继承自基础单动作代理
class CustomAgent(BaseSingleActionAgent):
    # 定义一个输入键的属性
    @property
    def input_keys(self):
        return ["input"]
 
    # 定义一个计划函数,接受一组中间步骤和其他参数,返回一个代理动作或者代理完成
    def plan(self, intermedate_steps: List[Tuple[AgentAction, str]],
            **kwargs: Any) -> Union[AgentAction, AgentFinish]:
        return AgentAction(tool="DeepSearch", tool_input=kwargs["input"], log="")
 
# 定义一个自定义输出解析器,继承自代理输出解析器
class CustomOutputParser(AgentOutputParser):
    # 定义一个解析函数,接受一个语言模型的输出字符串,返回一个代理动作或者代理完成
    def parse(self, llm_output: str) -> Union[AgentAction, AgentFinish]:
        # 使用正则表达式匹配输出字符串,group1是调用函数名字,group2是传入参数
        match = re.match(r'^[\s\w]*(DeepSearch)\(([^\)]+)\)', llm_output, re.DOTALL)
        print(match)
 
        # 如果语言模型没有返回 DeepSearch() 则认为直接结束指令
        if not match:
            return AgentFinish(
                return_values={"output": llm_output.strip()},
                log=llm_output,
            )
        # 否则的话都认为需要调用 Tool
        else:
            action = match.group(1).strip()
            action_input = match.group(2).strip()
            return AgentAction(tool=action, tool_input=action_input.strip(" ").strip('"'), log=llm_output)
 
 
# Define uma classe de agente profundo
class DeepAgent:
    tool_name : str = "DeepSearch" # Tool name
    agent_executor: any # Agent executor
    tools: List[Tool] # Tool list
    llm_chain: any # Language model chain
 
    # Defina uma função de consulta que aceite uma string de conteúdo relevante e uma string de consulta, retorne a execução resultado do atuador
    def query(self, related_content: str = "", query: str = ""):
        tool_name = O objetivo principal deste código é construir um agente de IA de pesquisa profunda. O agente de IA primeiro recebe uma pergunta Insira e, em seguida, gere um modelo de prompt com base na entrada e, em seguida, guie a IA para gerar uma resposta ou conduzir uma pesquisa mais profunda por meio do modelo. Agora, continuarei a adicionar comentários em chinês para o código
 
restante```python
        self.tool_name
        result = self.agent_executor.run(related_content=related_content, input=query ,tool_name=self.tool_name)
        return result # Retorna o resultado em execução do executor
 
    # Na função de inicialização, primeiro crie uma instância de ferramenta da ferramenta DeepSearch e adicione-a para a ferramenta Na lista
    def __init__(self, llm: BaseLanguageModel, **kwargs):
        tools = [
                    Tool.from_function(
                        func=DeepSearch.search,
                        name="DeepSearch",
                        descrição=""
                    )
                ]
        self.tools = ferramentas # salvar a lista de ferramentas
        tool_names = [tool.name for tool in tools] # Extrair o nome da ferramenta na lista de ferramentas
        output_parser = CustomOutputParser() # Criar uma instância de analisador de saída personalizada
        # Crie uma instância de modelo de prompt personalizado
        prompt = CustomPromptTemplate(template=agent_template,
                                      tools=tools,
                                      input_variables=["related_content","tool_name", "input", "intermediate_steps"])
        # Crie uma instância de cadeia de modelo de linguagem
        llm_chain = LLMChain ( llm=llm, prompt=prompt)
        self.llm_chain = llm_chain # Salvar instância de cadeia de modelo de linguagem
 
        # Criar uma instância de agente de ação única de modelo de linguagem
        agent = LLMSingleActionAgent(
            llm_chain=llm_chain,
            output_parser=output_parser,
            stop=["\nObservation:"] ,
            permitido_tools=tool_names
        )
 
        # Crie uma instância de executor de proxy
        agent_executor = AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, verbose=True)
        self.agent_executor = agent_executor # salva a instância do executor do agente

3.1.2 agent/bing_search.py
​​​​#coding=utf8
# Declare que o formato de codificação do arquivo é utf8
 
de langchain.utilities import BingSearchAPIWrapper
# Importe a classe BingSearchAPIWrapper, que é usada para interagir com a API de pesquisa do Bing
 
nas configurações. model_config import BING_SEARCH_URL, BING_SUBSCRIPTION_KEY
# importe o URL de pesquisa do Bing e a chave de assinatura do Bing no arquivo de configuração
 
def bing_search(text, result_len=3):
    # Defina uma função chamada bing_search, que aceita um parâmetro de texto e comprimento do resultado, o comprimento padrão do resultado é 3
 
    se não ( BING_SEARCH_URL e BING_SUBSCRIPTION_KEY):
        # Se o URL de pesquisa do Bing ou a chave de assinatura do Bing não estiver definido, retorne um documento com uma mensagem de erro
        return [{"snippet": "por favor, defina BING_SUBSCRIPTION_KEY e BING_SEARCH_URL no os ENV",
                 "title ": "ambiente não encontrado",
                 "link": "https://python.langchain.com/en/latest/modules/agents/tools/examples/bing_search.html"}]
 
    search = BingSearchAPIWrapper(bing_subscription_key=BING_SUBSCRIPTION_KEY,
                                  bing_search_url=BING_SEARCH_URL)
    # create BingSearchAPIWrapper class Instance , esta instância é usada para interagir com a API de pesquisa do Bing
 
    return search.results(text, result_len)
    # Retorna os resultados da pesquisa, o número de resultados é determinado pelo parâmetro result_len
 
if __name__ == "__main__":
    # Se este arquivo for executado diretamente , e Se não for importado como um módulo, execute o seguinte código
 
    r = bing_search('python')
    # use a API de pesquisa do Bing para pesquisar a palavra "python" e salve o resultado na variável r
 
    print(r)
    # imprimir os resultados da pesquisa

3.2 modelos: incluindo modelos e carregador de documentos
modelos do carregador: classe de interface e classe de implementação de llm, fornecendo suporte de saída de streaming para modelos de código aberto
carregador: classe de implementação do carregador de documentos


3.2.1 models/chatglm_llm.py
from abc import ABC # Importar classe base abstrata
de langchain.llms.base import LLM # Importar classe base do modelo de aprendizado de idiomas
da digitação import Opcional, Lista # Importar módulo de anotação
de tipo de models.loader import LoaderCheckPoint # Importe o ponto de carregamento
do modelo de models.base import (BaseAnswer, # Importe o modelo básico de resposta
                         AnswerResult) # Importe a classe do modelo de resultado da resposta
 
 
ChatGLM(BaseAnswer, LLM, ABC): # Defina a classe ChatGLM, herde a resposta básica, o modelo de aprendizado de idiomas e a classe base abstrata
    max_token: int = 10000 # O número máximo de tokens
    temperature: float = 0,01 # Parâmetro de temperatura, usado para controlar a aleatoriedade do texto gerado
    top_p = 0,9 # 0,9 tokens antes da classificação serão retidos
    checkPoint: LoaderCheckPoint = None # Checkpoint model
    # history = [] # Registro histórico
    history_len: int = 10 # Comprimento do registro histórico
 
    def __init__(self, checkPoint: LoaderCheckPoint = None): # Método de inicialização
        super().__init__() # Chama o método de inicialização da classe pai
        self.checkPoint = checkPoint # Modelo de ponto de verificação de atribuição
 
    @property
    def _llm_type(self) -> str: # Define a propriedade somente leitura _llm_type, retorna o tipo de modelo de aprendizado de idioma
        retorna "ChatGLM"
 
    @property
    def _check_point(self) -> LoaderCheckPoint: # Define a propriedade somente leitura _check_point, retorna o modelo de ponto de verificação
        return self.checkPoint
 
    @property
    def _history_len ( self) -> int: # Define o atributo somente leitura _history_len, retorna o comprimento do histórico
        return self.history_len
 
    def set_history_len(self, history_len: int = 10) -> None: # Define o comprimento do histórico
        self. histórico_len = histórico_len
 
    def _call(self, prompt: str, stop: Optional[List[str]] = None) -> str: # Defina o método _call para implementar a chamada específica do modelo print(f"__call:{prompt}") # Imprima a
        chamada
        Resposta de informações do prompt, _ = self.checkPoint.model.chat( # Chame o método de bate-papo do modelo para obter respostas e outras informações
            self.checkPoint.tokenizer, # O
            prompt do tokenizer usado, # Prompt information
            history=[] , # Registro de histórico
            max_length=self.max_token, # comprimento máximo
            temperature=self.temperature # parâmetro de temperatura
        )
        print(f"resposta:{resposta}") # imprimir informações da resposta
        print(f"++++++++++ +++ ++++++++++++++++++++++++") # Imprime a
        resposta de retorno do separador # Retorna a resposta
 
    def generatorAnswer(self, prompt: str,
                         history: List[List[str]] = [],
                         streaming: bool = False):  # 定义生成回答的方法,可以处理流式输入
 
        if streaming:  # 如果是流式输入
            history += [[]]  # 在历史记录中添加新的空列表
            for inum, (stream_resp, _) in enumerate(self.checkPoint.model.stream_chat(  # 对模型的stream_chat方法返回的结果进行枚举
                    self.checkPoint.tokenizer,  # 使用的分词器
                    prompt,  # 提示信息
                    history=history[-self.history_len:-1] if self.history_len > 1 else [],  # 使用的历史记录
                    max_length=self.max_token,  # 最大长度
                    temperature=self.temperature  # 温度参数
            )):
                # self.checkPoint.clear_torch_cache() # Limpa o cache
                history[-1] = [prompt, stream_resp] # Atualiza o último registro histórico
                answer_result = AnswerResult() # Cria um objeto de resultado de resposta
                answer_result.history = history # Atualiza o registro histórico do resultado da resposta
                answer_result.llm_output = {"answer": stream_resp} # Atualize a saída do resultado da resposta
                yield answer_result # Gere o resultado da resposta
        else: # Se não for uma
            resposta de entrada de fluxo, _ = self.checkPoint.model. chat( # Chame o método de bate-papo do modelo, obtenha respostas e outras informações self.checkPoint.tokenizer, #                 prompt
                do tokenizer usado , # prompt de informações                 history=history[-self.history_len:] if self.history_len > 0 else [], # histórico usado


                max_length=self.max_token, # comprimento máximo
                temperature=self.temperature # parâmetro de temperatura
            )
            self.checkPoint.clear_torch_cache() # limpar
            histórico de cache += [[prompt, resposta]] # atualizar histórico answer_result
            = AnswerResult() # criar resposta Result object
            answer_result.history = history # Atualizar o histórico dos resultados das respostas
            answer_result.llm_output = {"answer": response} # Atualizar a saída dos resultados das respostas
            yield answer_result # Gerar resultados das respostas

3.2.2
A função do arquivo models/shared.py é chamar o LLM remotamente

import sys      # 导入sys模块,通常用于与Python解释器进行交互
from typing import Any      # 从typing模块导入Any,用于表示任何类型
 
# 从models.loader.args模块导入parser,可能是解析命令行参数用
from models.loader.args import parser       
# 从models.loader模块导入LoaderCheckPoint,可能是模型加载点
from models.loader import LoaderCheckPoint  
 
# 从configs.model_config模块导入llm_model_dict和LLM_MODEL
from configs.model_config import (llm_model_dict, LLM_MODEL)  
# 从models.base模块导入BaseAnswer,即模型的基础类
from models.base import BaseAnswer  
 
# 定义一个名为loaderCheckPoint的变量,类型为LoaderCheckPoint,并初始化为None
loaderCheckPoint: LoaderCheckPoint = None  
 
 
def loaderLLM(llm_model: str = None, no_remote_model: bool = False, use_ptuning_v2: bool = False) -> Any:
    """
    初始化 llm_model_ins LLM
    :param llm_model: 模型名称
    :param no_remote_model: 是否使用远程模型,如果需要加载本地模型,则添加 `--no-remote-model
    :param use_ptuning_v2: 是否使用 p-tuning-v2 PrefixEncoder
    :return:
    """
    pre_model_name = loaderCheckPoint.model_name      # 获取loaderCheckPoint的模型名称
    llm_model_info = llm_model_dict[pre_model_name]   # 从模型字典中获取模型信息
 
    if no_remote_model:      # 如果不使用远程模型
        loaderCheckPoint.no_remote_model = no_remote_model  # 将loaderCheckPoint的no_remote_model设置为True
    if use_ptuning_v2:       # 如果使用p-tuning-v2
        loaderCheckPoint.use_ptuning_v2 = use_ptuning_v2    # 将loaderCheckPoint的use_ptuning_v2设置为True
 
    if llm_model:            # 如果指定了模型名称
        llm_model_info = llm_model_dict[llm_model]  # 从模型字典中获取指定的模型信息
 
    if loaderCheckPoint.no_remote_model:  # 如果不使用远程模型
        loaderCheckPoint.model_name = llm_model_info['name']  # 将loaderCheckPoint的模型名称设置为模型信息中的name
    else:  # 如果使用远程模型
        loaderCheckPoint.model_name = llm_model_info['pretrained_model_name']  # 将loaderCheckPoint的模型名称设置为模型信息中的pretrained_model_name
 
    loaderCheckPoint.model_path = llm_model_info["local_model_path"]  # 设置模型的本地路径
 
    if 'FastChatOpenAILLM' in llm_model_info["provides"]:  # 如果模型信息中的provides包含'FastChatOpenAILLM'
        loaderCheckPoint.unload_model()  # 卸载模型
    else:  # 如果不包含
        loaderCheckPoint.reload_model() # Recarregue o modelo
 
    provide_class = getattr(sys.modules['models'], llm_model_info['provides']) # Obtenha a classe do modelo modelInsLLM
    = provide_class(checkPoint=loaderCheckPoint) # Crie uma instância do modelo
    if 'FastChatOpenAILLM ' in llm_model_info["provides"]: # Se as informações fornecidas no modelo contiverem 'FastChatOpenAILLM'
        modelInsLLM.set_api_base_url(llm_model_info['api_base_url']) #Defina a URL base da API
        modelInsLLM.call_model_name(llm_model_info['name']) # Defina o nome do modelo
    return modelInsLLM # return model instance

 3.3 configs:配置文件存储model_config.py
import torch.cuda
import torch.backends
import os
import logging
import uuid
 
LOG_FORMAT = "%(levelname) -5s %(asctime)s" "-1d: %(message)s"
logger = logging.getLogger()
logger.setLevel(logging.INFO)
logging.basicConfig(format=LOG_FORMAT)
 
# 在以下字典中修改属性值,以指定本地embedding模型存储位置
# 如将 "text2vec": "GanymedeNil/text2vec-large-chinese" 修改为 "text2vec": "User/Downloads/text2vec-large-chinese"
# 此处请写绝对路径
embedding_model_dict = {
    "ernie-tiny": "nghuyong/ernie-3.0-nano-zh",
    "ernie-base": "nghuyong/ernie-3.0-base-zh",
    "text2vec-base": "shibing624/text2vec-base-chinese",
    "text2vec": "GanymedeNil/text2vec-large-chinese",
    "m3e-small": "moka-ai/m3e-small",
    "m3e-base": "moka-ai/m3e-base",
}
 
# Modelo de incorporação name
EMBEDDING_MODEL = "text2vec"
 
# Incorporando o dispositivo em execução
EMBEDDING_DEVICE = "cuda" if arch.cuda.is_available() else "mps" if arch.backends.mps.is_available() else "cpu"
 
 
# modelos LLM suportados
# llm_model_dict lida com alguns comportamentos predefinidos do carregador, como local de carregamento, nome do modelo, instância do processador modelo
# Modifique o valor do atributo no seguinte dicionário para especificar o local de armazenamento do modelo LLM local
# Por exemplo, altere o "local_model_path" de "chatglm-6b" de None para "User/Downloads/chatglm-6b"
# Por favor, escreva o caminho absoluto aqui
llm_model_dict = {     "chatglm-6b-int4-qe": {         "name" : "chatglm-6b-int4-qe",


        "pretrained_model_name": "THUDM/chatglm-6b-int4-qe",
        "local_model_path": Nenhum,
        "fornece": "ChatGLM"
    },
    "chatglm-6b-int4": {         "nome": "chatglm-6b-int4 ",         "pretrained_model_name": "THUDM/chatglm-6b-int4",         "local_model_path": Nenhum,         "fornece": "ChatGLM"     },     "chatglm-6b-int8": {         "name": "chatglm-6b-int8 ",         "pretrained_model_name": "THUDM/chatglm-6b-int8",         "local_model_path": Nenhum,         "fornece": "ChatGLM"     },     "chatglm-6b": {         "nome": "chatglm-6b",













        "pretrained_model_name": "THUDM/chatglm-6b",
        "local_model_path": Nenhum,
        "fornece": "ChatGLM"
    },
    "chatglm2-6b": {         "name": "chatglm2-6b",         "pretrained_model_name": "THUDM /chatglm2-6b",         "local_model_path": Nenhum,         "fornece": "ChatGLM"     },     "chatglm2-6b-int4": {         "name": "chatglm2-6b-int4",         "pretrained_model_name": "THUDM/chatglm2 -6b-int4",         "local_model_path": Nenhum,         "fornece":"ChatGLM"     },     "chatglm2-6b-int8": {         "nome": "chatglm2-6b-int8",













        "pretrained_model_name": "THUDM/chatglm2-6b-int8",
        "local_model_path": Nenhum,
        "fornece": "ChatGLM"
    },
    "chatyuan": {         "name": "chatyuan",         "pretrained_model_name": "ClueAI/ChatYuan -large-v2",         "local_model_path": Nenhum,         "fornece": Nenhum     },     "moss": {         "name": "musgo",         "pretrained_model_name": "fnlp/moss-moon-003-sft",         "local_model_path ": Nenhum,         "fornece": "MOSSLLM"     },     "vicuna-13b-hf": {         "name": "vicuna-13b-hf",         "pretrained_model_name": "vicuna-13b-hf",














        "local_model_path": None,
        "provides": "LLamaLLM"
    },
 
    # Consulte o seguinte formato para o modelo chamado por fastchat
    "fastchat-chatglm-6b": {         "name": "chatglm-6b", # "name " é alterado para "model_name" no serviço fastchat         "pretrained_model_name": "chatglm-6b",         "local_model_path": None,         "provides": "FastChatOpenAILLM", # Ao usar fastchat api, você precisa garantir que "provides" seja " FastChatOpenAILLM"         "api_base_url": "http://localhost:8000/v1" # "name" foi alterado para "api_base_url" no serviço fastchat }     ,     "fastchat-chatglm2-6b": {         "name": "chatglm2-6b", # "name" foi alterado para "model_name" no serviço fastchat         "pretrained_model_name": "chatglm2-6b",









        "local_model_path": None,
        "provides": "FastChatOpenAILLM", # Ao usar fastchat api, você precisa garantir que "provides" seja "FastChatOpenAILLM" "
        api_base_url": "http://localhost:8000/v1" # Alterar " name" para "api_base_url" no serviço fastchat
    },
 
    # Consulte o seguinte formato para o modelo chamado pelo fastchat
    "fastchat-vicuna-13b-hf": {         "name": "vicuna-13b-hf", # "name " foi alterado para o serviço fastchat "model_name" em         "pretrained_model_name": "vicuna-13b-hf",         "local_model_path": Nenhum,         "provides": "FastChatOpenAILLM",# Ao usar a API fastchat, certifique-se de que "provides" seja "FastChatOpenAILLM         " "api_base_url": "http://localhost:8000/v1" # Altere "name" para "api_base_url" no serviço fastchat }     , } # LLM nome







 

LLM_MODEL = "chatglm-6b"
# 量化加载8bit 模型
LOAD_IN_8BIT = False
# Load the model with bfloat16 precision. Requires NVIDIA Ampere GPU.
BF16 = False
# 本地lora存放的位置
LORA_DIR = "loras/"
 
# LLM lora path,默认为空,如果有请直接指定文件夹路径
LLM_LORA_PATH = ""
USE_LORA = True if LLM_LORA_PATH else False
 
# LLM streaming reponse
STREAMING = True
 
# Use p-tuning-v2 PrefixEncoder
USE_PTUNING_V2 = False
 
# LLM running device
LLM_DEVICE = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
 
# 知识库默认存储路径
KB_ROOT_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "knowledge_base")
 
# Modelo de prompt baseado em contexto, certifique-se de manter "{question}" e "{context}"
PROMPT_TEMPLATE = """Informações conhecidas:
{context} 
responderá à pergunta do usuário de forma concisa e profissional com base nas informações conhecidas acima. Se Incapaz de obter uma resposta, diga "esta pergunta não pode ser respondida com base em informações conhecidas" ou "informações relevantes insuficientes são fornecidas", não é permitido adicionar componentes fabricados à resposta, use chinês para a resposta. A pergunta é: {question}"""
 
# O número de bases de conhecimento em cache, se forem modelos ChatGLM2, ChatGLM2-int4, ChatGLM2-int8, se o efeito de recuperação não for bom, você pode ajustá-lo para '10'
CACHED_VS_NUM = 1
 
# Comprimento da frase de texto
SENTENCE_SIZE = 100
 
# Comprimento do contexto de segmento único após a correspondência
CHUNK_SIZE = 250 #
 
Comprimento do registro do histórico LLM de entrada
LLM_HISTORY_LEN = 3
 
# Número de itens de conteúdo correspondentes retornados durante a recuperação da base de conhecimento
VECTOR_SEARCH_TOP_K = 5
 
# Pontuação de relevância do conteúdo da recuperação de conhecimento, o intervalo de valores é cerca de 0-1100, se for 0, não terá efeito, após Quando a configuração do teste for menor que 500, o resultado correspondente será mais preciso
VECTOR_SEARCH_SCORE_THRESHOLD = 0
 
NLTK_DATA_PATH = os.path.join(os.path.dirname( os.path.dirname(__file__)), "nltk_data")
 
FLAG_USER_NAME = uuid.uuid4().hex
 
logger.info(f"""
loading model config
llm device: {LLM_DEVICE}
embedding device: {EMBEDDING_DEVICE}
dir: {os.path.dirname(os.path.dirname(__file__))}
flagging username: {FLAG_USER_NAME}
""")
 
# 是否开启跨域,默认为False,如果需要开启,请设置为True
# is open cross domain
OPEN_CROSS_DOMAIN = False
 
# Bing 搜索必备变量
# 使用 Bing 搜索需要使用 Bing Subscription Key,需要在azure port中申请试用bing search
# 具体申请方式请见
# https://learn.microsoft.com/en-us/bing/search-apis/bing-web-search/create-bing-search-service-resource
# 使用python创建bing api 搜索实例详见:
# https://learn.microsoft.com/en-us/bing/search-apis/bing-web-search/quickstarts/rest/python
BING_SEARCH_URL = "https://api.bing.microsoft.com/v7.0/search"
# Observe que não é a chave api do bing Webmaster Tools,
 
# Além disso, se estiver no servidor, relatar Falha ao estabelecer a new connection: [Errno 110] Connection timed out
# Como o servidor tem firewall, você precisa entrar em contato com o administrador para adicionar uma lista branca, se o servidor da empresa, nem pense nisso GG
BING_SUBSCRIPTION_KEY = ""
 
# Se deseja habilite o aprimoramento do título chinês e a configuração relacionada do aprimoramento do título
# Ao adicionar o julgamento do título, determine quais textos são títulos e marque-os nos metadados;
# Em seguida, combine o texto com o título do nível superior para realizar o aprimoramento das informações do texto.
ZH_TITLE_ENHANCE = Falso

3.4 loader: carregamento de documento e conversão de texto
3.4.1 loader/pdf_loader.py
# módulo de sugestão de tipo de importação para melhorar a legibilidade e robustez do código
ao digitar import List
 
# import UnstructuredFileLoader, que é um arquivo não estruturado A classe para carregar documentos
de langchain .document_loaders.unstructured import UnstructuredFileLoader
 
# Import PaddleOCR, que é uma ferramenta OCR de código aberto para reconhecer e ler texto de imagens
de paddleocr import PaddleOCR
 
# Import os módulo para processamento de arquivos e diretórios
import os
 
# import fitz módulo para processamento de arquivos PDF
import fitz
 
# importe o módulo nltk para processamento de dados de texto
import nltk
 
# importe NLTK_DATA_PATH no arquivo de configuração do modelo, este é o caminho dos dados nltk
de configs.model_config import NLTK_DATA_PATH
 
# defina o caminho de dados nltk, adicione o caminho na configuração do modelo ao caminho de dados do nltk
nltk.data.path = [NLTK_DATA_PATH] + nltk.data.path
 
# 定义一个类,UnstructuredPaddlePDFLoader,该类继承自UnstructuredFileLoader
class UnstructuredPaddlePDFLoader(UnstructuredFileLoader):
 
    # 定义一个内部方法_get_elements,返回一个列表
    def _get_elements(self) -> List:
 
        # 定义一个内部函数pdf_ocr_txt,用于从pdf中进行OCR并输出文本文件
        def pdf_ocr_txt(filepath, dir_path="tmp_files"):
            # 将dir_path与filepath的目录部分合并成一个新的路径
            full_dir_path = os.path.join(os.path.dirname(filepath), dir_path)
 
            # 如果full_dir_path对应的目录不存在,则创建这个目录
            if not os.path.exists(full_dir_path):
                os.makedirs(full_dir_path)
            
            # 创建一个PaddleOCR实例,设置一些参数
            ocr = PaddleOCR(use_angle_cls=True, lang="ch", use_gpu=False, show_log=False) #
 
            Abra o arquivo pdf
            doc = fitz.open(filepath)
 
            # Crie um caminho de arquivo txt
            txt_file_path = os.path.join(full_dir_path , f"{os.path.split(filepath)[-1]}.txt")
 
            # Crie um caminho de arquivo de imagem temporário
            img_name = os.path.join(full_dir_path, 'tmp.png')
 
            # Abra o arquivo txt_file_path correspondente e abra-o no modo de gravação
            com open(txt_file_path, 'w', encoding='utf-8') como fout:
                # percorra todas as páginas do pdf
                para i in range(doc.page_count):
                    # obtenha a
                    página atual page = doc [ i]
 
                    # Obtenha o conteúdo de texto da página atual e grave-o em um arquivo txt
                    text = page.get_text("")
                    fout.write(text)
                    fout.write("\n")
 
                    # Obtém todas as imagens na página atual
                    img_list = page.get_images()
 
                    # Percorre todas as imagens
                    para img em img_list:
                        # Coloque as imagens Converter para o objeto Pixmap
                        pix = fitz.Pixmap(doc, img[0])
 
                        # Se a imagem tiver informações de cores, converta-a para o formato RGB
                        if pix.n - pix.alpha >= 4:
                            pix = fitz. Pixmap(fitz .csRGB, pix)
                        
                        # salva a imagem
                        pix.save(img_name)
 
                        # executa o reconhecimento OCR na imagem
                        result = ocr.ocr(img_name)
 
                        # Extraia o texto do resultado do OCR e grave no arquivo txt
                        ocr_result = [i[1][0] for line in result for i in line]
                        fout.write("\n".join (ocr_result ))
            
            # Se o arquivo de imagem existir, apague-o
            if os.path.exists(img_name):
                os.remove(img_name)
            
            # Retorna o caminho do arquivo txt
            return txt_file_path
 
        # Chame a função definida acima para pegar o caminho do txt file
        txt_file_path = pdf_ocr_txt(self.file_path)
 
        # Importe a função partition_text, que é usada para dividir arquivos de texto em partes
        de unstructured.partition.text import partition_text
 
        # Divida arquivos txt em partes e retorne os resultados da parte
        return partition_text(filename=txt_file_path, **self.unstructured_kwargs)
 
# Execute a entrada
if __name__ == "__main__":
    # Importe o módulo sys para operar o ambiente operacional Python
    import sys
 
    # Adicione o diretório de nível superior do arquivo atual para Python No caminho de pesquisa
    sys.path.append(os.path.dirname(os.path.dirname(__file__)))
 
    # Defina o caminho de um arquivo pdf
    filepath = os.path.join(os.path.dirname(os .path .dirname(__file__)), "knowledge_base", "samples", "content", "test.pdf") #
 
    Crie uma instância de UnstructuredPaddlePDFLoader
    loader = UnstructuredPaddlePDFLoader(filepath, mode="elements")
 
    # Carrega documentos
    docs = loader.load()
 
    # percorre e imprime todos os documentos
    para doc in docs:
        print(doc)

// Ser atualizado..

3.5 textsplitter: segmentação de documentos
3.5.1 textsplitter/ali_text_splitter.py
o código ali_text_splitter.py é o seguinte

# 导入CharacterTextSplitter模块,用于文本切分
from langchain.text_splitter import CharacterTextSplitter  
import re                  # 导入正则表达式模块,用于文本匹配和替换
from typing import List    # 导入List类型,用于指定返回的数据类型
 
# 定义一个新的类AliTextSplitter,继承自CharacterTextSplitter
class AliTextSplitter(CharacterTextSplitter):  
    # 类的初始化函数,如果参数pdf为True,那么使用pdf文本切分规则,否则使用默认规则
    def __init__(self, pdf: bool = False, **kwargs):  
        # 调用父类的初始化函数,接收传入的其他参数
        super().__init__(**kwargs)  
        self.pdf = pdf          # 将pdf参数保存为类的成员变量
 
    # 定义文本切分方法,输入参数为一个字符串,返回值为字符串列表
    def split_text(self, text: str) -> List[str]:  
        if self.pdf:            # 如果pdf参数为True,那么对文本进行预处理
 
            # 替换掉连续的3个及以上的换行符为一个换行符
            text = re.sub(r"\n{3,}", r"\n", text)  
            # 将所有的空白字符(包括空格、制表符、换页符等)替换为一个空格
            text = re.sub('\s', " ", text)  
            # 将连续的两个换行符替换为一个空字符
            text = re.sub("\n\n", "", text)  
        
        # 导入pipeline模块,用于创建一个处理流程
        from modelscope.pipelines import pipeline  
 
        # 创建一个document-segmentation任务的处理流程
        # 用的模型为damo/nlp_bert_document-segmentation_chinese-base,计算设备为cpu
        p = pipeline(
            task="document-segmentation",
            model='damo/nlp_bert_document-segmentation_chinese-base',
            device="cpu")
        result = p(documents=text) # Processa o texto de entrada e retorna o resultado do processamento
        sent_list = [i for i in result["text"].split("\n\t") if i] # Processa o resultado de acordo com Quebras de linha e tabulações são divididas para obter uma lista de sentenças
        return sent_list # return lista de sentenças

Entre eles, há três pontos que merecem destaque

参数use_document_segmentation指定是否用语义切分文档
此处采取的文档语义分割模型为达摩院开源的:nlp_bert_document-segmentation_chinese-base  (这是其论文)
另,如果使用模型进行文档语义切分,那么需要安装:
modelscope[nlp]:pip install "modelscope[nlp]" -f https://modelscope.oss-cn-beijing.aliyuncs.com/releases/repo.html
且考虑到使用了三个模型,可能对于低配置gpu不太友好,因此这里将模型load进cpu计算,有需要的话可以替换device为自己的显卡id
3.6 knowledge_bas:index.faiss/index.pkl
knowledge_bas下面有两个文件,一个content 即用户上传的原始文件,vector_store则用于存储向量库⽂件,即本地知识库本体,因为content因人而异 谁上传啥就是啥 所以没啥好分析,而vector_store下面则有两个文件,一个index.faiss,一个index.pkl

3.7 chains:向量搜索/匹配
如之前所述,本节开头图中“FAISS索引、FAISS搜索”中的“FAISS”是Facebook AI推出的一种用于有效搜索大规模高维向量空间中相似度的库,在大规模数据集中快速找到与给定向量最相似的向量是很多AI应用的重要组成部分,例如在推荐系统、自然语言处理、图像检索等领域

3.7.1 chains/modules /vectorstores.py文件:根据查询向量query在向量数据库中查找与query相似的文本向量
主要是关于FAISS (Facebook AI Similarity Search)的使用,以及一个FAISS向量存储类(FAISSVS,FAISSVS类继承自FAISS类)的定义,包含以下主要方法:

max_marginal_relevance_search
给定查询语句,首先将查询语句转换为嵌入向量「embedding = self.embedding_function(query)」,然后调用 max_marginal_relevance_search_by_vector 函数进行MMR搜索
#  使用最大边际相关性返回被选中的文本
def max_marginal_relevance_search(
    self,
    query: str,            # 查询
    k: int = 4,            # 返回的文档数量,默认为 4
    fetch_k: int = 20,     # 用于传递给 MMR 算法的抓取文档数量
    **kwargs: Any,
) -> List[Tuple[Document, float]]:    
 
    # 查询向量化
    embedding = self.embedding_function(query)
    # 调用:max_marginal_relevance_search_by_vector
    docs = self.max_marginal_relevance_search_by_vector(embedding, k, fetch_k)
    return docs
  max_marginal_relevance_search_by_vector
Com um determinado vetor de incorporação, use o método MMR (Maximal Marginal Relevance) para retornar o texto relevante. O MMR
é um algoritmo para resolver a diversidade e a relevância dos resultados da consulta. Especificamente, ele não apenas exige que o texto retornado seja o mais semelhante possível ao consulta e espero que os conjuntos de texto retornados sejam tão diversos
quanto
possível max_marginal_relevance_search_by_vector(
    self, embedding: List[float], k: int = 4, fetch_k: int = 20, **kwargs: Any ) ->
List[Tuple[Document , float]]:
 
    # Use o índice para pesquisar e incorporar no texto Vector conteúdo semelhante, retorne a pontuação e
    as pontuações   , indices = self.index.search(np.array([embedding], dtype=np .float32), fetch_k)
    
    # Recuperar do texto pelo índice Construir o vetor de incorporação, -1 significa que não há texto suficiente para retornar
    embeddings = [self.index.reconstruct(int(i)) for i in indices[0] if i != -1] 
 
    # Use o algoritmo de correlação marginal máxima para selecionar o k texto mais relevante
    mmr_selected = maximal_marginal_relevance(
        np.array([embedding], dtype=np.float32), embeddings, k=k
    )  
 
    selected_indices = [indices[0][i] for i in mmr_selected] # Obtém o índice do texto selecionado
    selected_scores = [scores[0 ][i] for i in mmr_selected] # Obtenha a pontuação do texto selecionado
    docs = []
    for i, score in zip(selected_indices, selected_scores): # Para cada índice de texto selecionado e pontuação
        if i == -1 : # Se o índice é -1, não há texto suficiente para retornar
            continue
 
        _id = self.index_to_docstore_id[i] # Obtenha o id do texto através do índice
        doc = self.docstore.search(_id) # Pesquise o texto na biblioteca de documentos by id
        if not isinstance(doc, Document): # Se o texto pesquisado não for do tipo Document, raise ValueError
            (f"Could not find document for id {_id}, got {doc}")
        docs.append((doc, score)) # adiciona o texto e pontuação à lista de resultados
    return docs # retorna a lista de resultados

__from
用于从一组文本和对应的嵌入向量创建一个FAISSVS实例。该方法首先创建一个FAISS索引并添加嵌入向量,然后创建一个文本存储以存储与每个嵌入向量关联的文本
# 从给定的文本、嵌入向量、元数据等信息构建一个FAISS索引对象
def __from(
    cls,
    texts: List[str],                 # 文本列表,每个文本将被转化为一个文本对象
    embeddings: List[List[float]],    # 对应文本的嵌入向量列表
    embedding: Embeddings,            # 嵌入向量生成器,用于将查询语句转化为嵌入向量
    metadatas: Optional[List[dict]] = None,
    **kwargs: Any,
) -> FAISS:
 
    faiss = dependable_faiss_import()      # 导入FAISS库
    index = faiss.IndexFlatIP(len(embeddings[0]))      # 使用FAISS库创建一个新的索引,索引的维度等于嵌入文本向量的长度
    index.add(np.array(embeddings, dtype=np.float32))  # 将嵌入向量添加到FAISS索引中
 
    # quantizer = faiss.IndexFlatL2(len(embeddings[0]))
    # index = faiss.IndexIVFFlat(quantizer, len(embeddings[0]), 100)
    # index.train(np.array(embeddings, dtype=np.float32))
    # index.add(np.array(embeddings, dtype=np.float32))
 
    documents = []
    for i, text in enumerate(texts):      # 对于每一段文本
        # 获取对应的元数据,如果没有提供元数据则使用空字典
        metadata = metadatas[i] if metadatas else {}  
 
        # 创建一个文本对象并添加到文本列表中
        documents.append(Document(page_content=text, metadata=metadata))  
 
    # 为每个文本生成一个唯一的ID
    index_to_id = {i: str(uuid.uuid4()) for i in range(len(documents))}  
 
    # 创建一个文本库,用于存储文本对象和对应的ID
    docstore = InMemoryDocstore(
        {index_to_id[i]: doc for i, doc in enumerate(documents)}  
    )
 
    # 返回FAISS对象
    return cls(embedding.embed_query, index, docstore, index_to_id)  

O acima é o conteúdo principal deste código, usando FAISS e MMR, pode nos ajudar a encontrar o texto mais relevante para uma determinada consulta em uma grande quantidade de texto

3.7.2 chains/local_doc_qa.py arquivo de código: pesquisa vetorial
Importando pacotes e módulos
O início do código é uma série de instruções import que importam os pacotes e módulos Python necessários, incluindo carregadores de arquivos, divisores de texto, configurações de modelo e alguns Python módulos internos e outras bibliotecas de terceiros
Substituir o método hash da classe HuggingFaceEmbeddings
O código define uma função chamada _embeddings_hash e a atribui ao método __hash__ da classe HuggingFaceEmbeddings. O objetivo disso é fazer com que o objeto HuggingFaceEmbeddings possa ser hash, ou seja, pode ser usado como a chave do dicionário ou adicionado à coleção. Load vector
storage
define uma função chamada load_vector_store, que é usada para carregar um vetor de armazenamento localmente e retornar Object da classe FAISS. O decorador lru_cache é usado para armazenar em cache os resultados CACHED_VS_NUM usados ​​mais recentemente para melhorar a eficiência do código
.
. Ele pode ignorar o arquivo ou diretório especificado
Carregar arquivo:
A função load_file seleciona o carregador apropriado e o divisor de texto de acordo com a extensão do arquivo, carrega e divide o arquivo
Gerar lembrete:
A função generate_prompt é usada para gerar um lembrete com base em documentos e consultas relacionados . O modelo do lembrete é fornecido pelo parâmetro prompt_template
Criar uma lista de documentos
search_result2docs
# Criar uma lista vazia para armazenar documentos
def search_result2docs(search_results):
    docs = []
 
    # 对于搜索结果中的每一项
    for result in search_results:
        # 创建一个文档对象
        # 如果结果中包含"snippet"关键字,则其值作为页面内容,否则页面内容为空字符串
        # 如果结果中包含"link"关键字,则其值作为元数据中的源链接,否则源链接为空字符串
        # 如果结果中包含"title"关键字,则其值作为元数据中的文件名,否则文件名为空字符串
        doc = Document(page_content=result["snippet"] if "snippet" in result.keys() else "",
                       metadata={"source": result["link"] if "link" in result.keys() else "",
                                 "filename": result["title"] if "title" in result.keys() else ""})
 
        # 将创建的文档对象添加到列表中
        docs.append(doc)
    
    # 返回文档列表
    return docs

Em seguida, é definida uma classe chamada LocalDocQA, que é usada principalmente para tarefas de resposta a perguntas baseadas em documentos. A principal função da tarefa de resposta a perguntas baseadas em documentos é retornar uma resposta com base em um determinado conjunto de documentos (aqui chamado de base de conhecimento) e perguntas inseridas pelo usuário. Os principais métodos da classe LocalDocQA incluem:

init_cfg(): Este método inicializa algumas variáveis, incluindo atribuir llm_model (um modelo de linguagem usado para gerar respostas) a self.llm, atribuir um modelo de incorporação baseado em HuggingFace a self.embeddings e atribuir o parâmetro de entrada top_k a self.top_k
init_knowledge_vector_store (): Este método é responsável por inicializar o armazenamento do vetor de conhecimento. Ele primeiro verifica o caminho do arquivo de entrada e, para cada arquivo no caminho, carrega o conteúdo do arquivo em um objeto Document, depois converte esses documentos em vetores de incorporação e os armazena na biblioteca de vetores one_knowledge_add(): Este método é usado Adiciona
um novo documento de conhecimento para a base de conhecimento. Ele cria o título e o conteúdo de entrada como um objeto Document, então o converte em um vetor de incorporação e o adiciona à biblioteca de vetores
get_knowledge_based_answer(): Este método gera uma resposta com base na base de conhecimento fornecida e na pergunta inserida pelo usuário . Ele primeiro localiza o documento mais relevante na base de conhecimento com base na pergunta inserida pelo usuário, em seguida, gera um prompt contendo o documento relevante e a pergunta do usuário e passa o prompt para llm_model para gerar a resposta. Observe que esta função chama o método um que foi implementado acima
: similarity_search_with_score
get_knowledge_based_conent_test(): Este método é para teste, ele retornará os documentos mais relevantes e dicas de consulta para a consulta de entrada
    # query query content
    # vs_path caminho da base de conhecimento
    # chunk_conent Se a associação de contexto está habilitada
    # score_threshold limite de pontuação de correspondência de pesquisa
    # vector_search_top_k O número de itens de conteúdo da base de conhecimento de pesquisa, a pesquisa padrão é 5 resultados
    # chunk_sizes Corresponde ao comprimento do contexto de conexão de uma única parte do conteúdo
    def get_knowledge_based_conent_test(self, query, vs_path, chunk_conent,
                                        score_threshold=VECTOR_SEARCH_SCORE_THRESHOLD,
                                        vector_search_top_k=VECTOR_SE ARCH_TOP_K, chunk_size=CHUNK_SIZE):
get_search_result_based_answer( ): Este método é semelhante a get_knowledge_based_answer(), mas o resultado de bing_search é usado aqui como a base de conhecimento
def get_search_result_based_answer(self, query, chat_history=[], streaming: bool = STREAMING):
    # Execute uma pesquisa do Bing na consulta e obtenha o resultado da pesquisa Result results
    = bing_search(query)
 
    #Converta os resultados da pesquisa em formato de texto
    result_docs = search_result2docs(results)
 
    #Gere prompts para fazer perguntas
    prompt = generate_prompt(result_docs, query)
 
    # 通过 LLM(长语言模型)生成回答
    for answer_result in self.llm.generatorAnswer(prompt=prompt, history=chat_history,
                                                  streaming=streaming):
        # 获取回答的文本
        resp = answer_result.llm_output["answer"]
 
        # 获取聊天历史
        history = answer_result.history
 
        # 将聊天历史中的最后一项的提问替换为当前的查询
        history[-1][0] = query
 
        # 组装回答的结果
        response = {"query": query,
                    "result": resp,
                    "source_documents": result_docs}
 
        # 返回回答的结果和聊天历史
        yield response, history

Como você pode ver, a principal diferença entre esta função e a anterior é que esta função usa os resultados de pesquisa do mecanismo de pesquisa diretamente para gerar uma resposta, enquanto a função acima usa a pesquisa por similaridade de consulta para encontrar o texto mais relevante e, em seguida, com base em Esses textos geram respostas
e esse bing_search foi definido na Seção 3.1.2.
Em seguida, existem três métodos para excluir arquivos, atualizar arquivos e listar arquivos do armazenamento de vetores.
delete_file_from_vector_store
update_file_from_vector_store
list_file_from_vector_store
    # Excluir arquivos no armazenamento de vetores
    def delete_file_from_vector_store(self ,
                                      filepath: str ou List[str], # caminho do arquivo, que pode ser um único arquivo ou uma lista de vários arquivos
                                      vs_path): # vetor storage path
        vector_store = load_vector_store(vs_path, self.embeddings) # carregar de um determinado caminho Vector status de armazenamento
        = vector_store.delete_doc(filepath) # exclui o arquivo especificado
        return status # retorna o status de exclusão
 
    # atualiza o arquivo no armazenamento de vetores
    def update_file_from_vector_store(self,
                                      filepath: str ou List[str], # O caminho do arquivo que precisa ser atualizado, que pode ser um único arquivo ou uma lista de vários arquivos
                                      vs_path, # Caminho de armazenamento do vetor
                                      docs: List[Document],) : # O arquivo que precisa ser atualizado Conteúdo, os arquivos são fornecidos na forma de documento
        vector_store = load_vector_store(vs_path, self.embeddings) # carrega o armazenamento de vetores de determinado caminho
        status = vector_store.update_doc(filepath, docs) # atualiza
        o status de retorno do arquivo especificado # return update status
 
    # list vectors Files in storage
    def list_file_from_vector_store(self,
                                    vs_path, # vector storage path
                                    fullpath=False): # Se deve retornar o caminho completo, se for False, apenas o nome do arquivo é retornado
        vector_store = load_vector_store(vs_path, self.embeddings)  # 从给定路径加载向量存储
        docs = vector_store.list_docs()      # 列出所有文件
        if fullpath:  # 如果需要完整路径
            return docs  # 返回完整路径列表
        else:  # 如果只需要文件名
            return [os.path.split(doc)[-1] for doc in docs]  # 用 os.path.split 将路径和文件名分离,只返回文件名列表

__main__部分的代码是 LocalDocQA 类的实例化和使用示例

它首先初始化了一个 llm_model_ins 对象
然后创建了一个 LocalDocQA 的实例并调用其 init_cfg() 方法进行初始化
之后,它指定了一个查询和知识库的路径
然后调用 get_knowledge_based_answer() 或 get_search_result_based_answer() 方法获取基于该查询的答案,并打印出答案和来源文档的信息
3.7.3 chains/text_load.py
chain这个文件夹下 还有最后一个项目文件(langchain-ChatGLM/text_load.py at master · imClumsyPanda/langchain-ChatGLM · GitHub),如下所示

import os
import pinecone 
from tqdm import tqdm
from langchain.llms import OpenAI
from langchain.text_splitter import SpacyTextSplitter
from langchain.document_loaders import TextLoader
from langchain.document_loaders import DirectoryLoader
from langchain.indexes import VectorstoreInde xCreator
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain. vectorstores import Pinecone
 
#alguns arquivos de configuração
openai_key="sua chave" # registre openai.com e obtenha
pinecone_key="sua chave" # registre app.pinecone.io e obtenha
pinecone_index="sua biblioteca" #app.pinecone.io get
pinecone_environment="seu ambiente" # Após logar no pinecone, verifique o Ambiente na página de índices
pinecone_namespace="seu Namespace" #Se não existir, será criado automaticamente
 
#Ciência online que você conhece
os.environ['HTTP_PROXY'] = 'http://127.0.0.1:7890'
os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:7890'
 
#Inicializar pinha
pinha. init(
    api_key=pinecone_key,
    environment=pinecone_environment
)
index = pinecone.Index(pinecone_index)
 
#Initialize OpenAI's embeddings
embeddings = OpenAIEmbeddings(openai_api_key=openai_key)
 
#Initialize text_splitter
text_splitter = SpacyTextSplitter(pipeline='zh _core_web_sm',chunk_size=10 00,chunk_overlap= 200)
 
# Lê todos os arquivos no diretório com o sufixo txt
loader = DirectoryLoader('../docs', glob="**/*.txt", loader_cls=TextLoader) #
 
Lê arquivos de texto
documentos = loader.load( )
 
# Use text_splitter para dividir o documento
split_text = text_splitter.split_documents(documents)
try:
    for document in tqdm(split_text):
        # 获取向量并储存到pinecone
        Pinecone.from_documents([document], embeddings, index_name=pinecone_index)
except Exception as e:
    print(f"Error: {e}")
    quit()

3.8 vectorstores:MyFAISS.py
两个文件,一个__init__.py (就一行代码:from .MyFAISS import MyFAISS),另一个MyFAISS.py,如下代码所示

# Importe FAISS da biblioteca langchain.vectorstores
de langchain.vectorstores import FAISS
# Importe VectorStore            
da biblioteca langchain.vectorstores.base de langchain.vectorstores.base import VectorStore
# Importe dependable_faiss_import
de langchain.vectorstores.faiss import dep enable_faiss_import  
 
da digitação import Any, Callable , List, Dict # importar biblioteca de verificação de tipo
de langchain.docstore.base import Docstore # importar Docstore de langchain.docstore.base library
 
# importar documento de langchain.docstore.document library
de langchain.docstore.document import Document  
 
import numpy as np # Importe a biblioteca numpy para computação científica
import copy # Importe a biblioteca de cópias para replicação de dados
import os # Import os library para operações relacionadas ao sistema operacional
from configs.model_config import * # Importe todo o conteúdo da biblioteca configs.model_config
 
 
# Defina a classe MyFAISS, que herda das duas classes pai de FAISS e VectorStore
class MyFAISS(FAISS, VectorStore):

Em seguida, implemente as seguintes funções uma a uma

3.8.1 Defina a função de inicialização da classe: __init__
    # Defina a função de inicialização da classe
    def __init__(
            self,
            embedding_function: Callable,
            index: Any,
            docstore: Docstore,
            index_to_docstore_id: Dict[int, str],
            normalize_L2: bool = False,
    ):
        # Chame a função de inicialização da classe pai FAISS
        super().__init__(embedding_function=embedding_function,
                         index=index,
                         docstore=docstore,
                         index_to_docstore_id=index_to_docstore_id,
                         normalize_L2=normalize_L2)
        # Inicialize o limite de pontuação
        self.score_threshold=VECTOR_SEARCH_SCORE_THRESHOLD
        # inicializa o tamanho do bloco
        self.chunk_size = CHUNK_SIZE
        # inicializa o conteúdo do bloco
        self.chunk_conent = Falso

3.8.2 seperate_list: decompõe uma lista em várias sub-listas
    # Defina a função seperate_list para decompor uma lista em várias sub-listas, os elementos em cada sub-lista são contínuos na lista original
    def seperate_list(self, ls: List[ int] ) -> List[List[int]]:
        # TODO: Adicionar o julgamento de se eles pertencem ao mesmo documento
        lists = []
        ls1 = [ls[0]]
        for i in range(1, len(ls) ):
            if ls[i - 1] + 1 == ls[i]:
                ls1.append(ls[i])
            else:
                lists.append(ls1)
                ls1 = [ls[i]]
        lists.append(ls1)
        return listas
3.8.3 similarity_search_with_score_by_vector, De acordo com o vetor de entrada, encontre o texto k mais próximo
similarity_search_with_score_by_vector A função é usada para realizar a pesquisa de similaridade por vetor e retorna o texto mais semelhante e a pontuação correspondente ao vetor de incorporação fornecido

    # Defina a função similarity_search_with_score_by_vector para encontrar os k textos mais próximos de acordo com o vetor de entrada
    def similarity_search_with_score_by_vector(
            self, embedding: List[float], k: int = 4
    ) -> List[Document]:
        # Chame a função dependable_faiss_import para importar o faiss library
        faiss = dependable_faiss_import()
 
        # Converta a lista de entrada em um array numpy e defina o tipo de dados para float32
        vector = np.array([embedding], dtype=np.float32)
 
        # Se a normalização L2 for necessária, chame faiss. função normalize_L2 é normalizada
        se self._normalize_L2:
            faiss.normalize_L2(vetor)
 
        # Chame a função de pesquisa da biblioteca faiss para encontrar os k vetores mais próximos do vetor de entrada e retornar suas pontuações e
        pontuações de índice, índices = self.index .search (vetor, k)
 
        # Inicializa uma lista vazia para armazenar o texto encontrado
        docs = []
        # 初始化一个空集合,用于存储文本的id
        id_set = set()
 
        # 获取文本库中文本的数量
        store_len = len(self.index_to_docstore_id)
 
        # 初始化一个布尔变量,表示是否需要重新排列id列表
        rearrange_id_list = False
 
        # 遍历找到的索引和分数
        for j, i in enumerate(indices[0]):
            # 如果索引为-1,或者分数小于阈值,则跳过这个索引
            if i == -1 or 0 < self.score_threshold < scores[0][j]:
                # This happens when not enough docs are returned.
                continue
 
            # 如果索引存在于index_to_docstore_id字典中,则获取对应的文本id
            if i in self.index_to_docstore_id:
                _id = self.index_to_docstore_id[i]
 
            # Se o índice não existir no dicionário index_to_docstore_id, pule este índice
            senão:
                continue
            # Procure o texto correspondente ao id na biblioteca de texto
            doc = self.docstore.search(_id)
 
            # Se não houver necessidade de dividir o bloco content, ou do documento Se não houver campo context_expand nos metadados, ou o valor do campo context_expand for falso, execute o seguinte código
            if (não self.chunk_conent) ou ("context_expand" em doc.metadata e não doc.metadata ["context_expand"]):
                # match out Se o texto não precisar expandir o contexto, execute o seguinte código
                # Se o texto pesquisado não for do tipo Document, uma exceção será lançada
                se não for isinstance(doc, Document):
                    raise ValueError(f"Could not find document for id {_id}, got {doc}")
                # Adiciona um campo de pontuação aos metadados do texto, cujo valor é a pontuação encontrada
                doc.metadata["score"] = int(scores[0][j])
 
                # 将文本添加到docs列表中
                docs.append(doc)
                continue
 
            # 将文本id添加到id_set集合中
            id_set.add(i)
 
            # 获取文本的长度
            docs_len = len(doc.page_content)
 
            # 遍历范围在1到i和store_len - i之间的数字k
            for k in range(1, max(i, store_len - i)):
                # 初始化一个布尔变量,表示是否需要跳出循环
                break_flag = False
 
                # 如果文本的元数据中有context_expand_method字段,并且其值为"forward",则扩展范围设置为[i + k]
                if "context_expand_method" in doc.metadata and doc.metadata["context_expand_method"] == "forward":
                    expand_range = [i + k]
 
                # Se houver um campo context_expand_method nos metadados do texto e seu valor for "backward", o intervalo de expansão é definido como [i - k] elif "
                context_expand_method" em doc.metadata e doc .metadata[" context_expand_method"] == "backward":
                    expand_range = [i - k]
 
                # Se não houver campo context_expand_method nos metadados do texto, ou o valor do campo context_expand_method não for "forward" nem "backward" , o intervalo de expansão é definido como [i + k, i - k]
                senão:
                    expand_range = [i + k, i - k]
 
                # Atravesse o intervalo estendido
                para l em expand_range:
                    # Se l não estiver na coleção id_set e l está entre 0 e len(self.index_to_docstore_id), então execute o seguinte código
                    if l not in id_set and 0 <= l < len(self.index_to_docstore_id):
                        # Obtenha o id de texto correspondente a l
                        _id0 = self.index_to_docstore_id[l]
 
                        # Pesquise o texto correspondente ao id na biblioteca de texto
                        doc0 = self.docstore .search (_id0)
 
                        # Se o comprimento do texto mais o comprimento do novo documento for maior que o tamanho do bloco, ou a fonte do novo texto não for igual à fonte do texto atual, defina break_flag como true e break fora do loop
                        if docs_len + len(doc0.page_content) > self.chunk_size ou doc0.metadata["source"] != \
                                doc.metadata["source"]:
                            break_flag = True
                            break
 
                        # Se a origem do novo texto for igual à origem do texto atual, adicione o comprimento do novo texto ao comprimento do texto, adicione l à coleção id_set e defina rearrange_id_list como true
                        elif doc0.metadata["source"] == doc.metadata["source"]:
                            docs_len += len(doc0.page_content)
                            id_set.add(l)
                            rearrange_id_list = True
 
                # Se break_flag for true, saia do loop
                if break_flag:
                    break
 
        # Se você não precisa dividir o conteúdo do trecho, ou se não precisa reorganizar a lista de id, retorne a lista de documentos
        if (não self.chunk_conent) ou (não rearrange_id_list):
            return docs
 
        # If o comprimento da coleção id_set é 0 e o limite de pontuação é maior que 0, retorne uma lista vazia
        se len(id_set) == 0 e self.score_threshold > 0:
            return []
 
        # Classifique os elementos na coleção id_set e converta para uma lista
        id_list = sorted(list(id_set))
 
        # Chame a função seperate_list para decompor a id_list em várias sub-listas
        id_lists = self.seperate_list(id_list)
 
        # Atravesse cada sequência de id em id_lists
        para id_seq em id_lists:
            # Atravesse cada sequência de id em id_lists Um id
            para id em id_seq:
                # Se o id for igual ao primeiro elemento da sequência de id, procure o texto correspondente ao id na biblioteca de documentos e copie profundamente o texto
                if id == id_seq[0]:
                    _id = self.index_to_docstore_id[ id]
 
                    # doc = self.docstore.search(_id)
                    doc = copy.deepcopy(self.docstore.search(_id))
 
                # Se o id não for igual ao primeiro elemento da sequência de id, procure para o id correspondente do documento da biblioteca de texto, adicione o conteúdo do novo texto ao conteúdo do texto atual
                senão:
                    _id0 = self.index_to_docstore_id[id]
                    doc0 = self.docstore.search(_id0)
                    doc.page_content += " " + doc0.page_content
 
            # 如果搜索到的文本不是Document类型,则抛出异常
            if not isinstance(doc, Document):
                raise ValueError(f"Could not find document for id {_id}, got {doc}")
            # 计算文本的分数,分数等于id序列中的每一个id在分数列表中对应的分数的最小值
            doc_score = min([scores[0][id] for id in [indices[0].tolist().index(i) for i in id_seq if i in indices[0]]])
 
            # 在文本的元数据中添加score字段,其值为文档的分数
            doc.metadata["score"] = int(doc_score)
 
            # 将文本添加到docs列表中
            docs.append(doc)
        # 返回docs列表
        devolver documentos

3.8.4 método delete_doc: exclui o texto da fonte especificada na biblioteca de texto
    #Defina um método chamado delete_doc, que é usado para excluir o texto da fonte especificada na biblioteca de texto
    def delete_doc(self, source: str ou List[ str]):
        # Use a estrutura try-except para capturar possíveis exceções
        try:
            # If source is a string type
            if isinstance(source, str):
                # Descubra os ids de todos os textos cujo source é igual a source na biblioteca de textos ids
                = [k for k, v in self.docstore._dict.items() if v.metadata["source"] == source]
 
                # Obtém o caminho do armazenamento do vetor
                vs_path = os.path.join(os.path. split(os.path.split( source)[0])[0], "vector_store")
 
            # Se source for uma lista, digite
            else:
                # Descubra os ids de todos os textos na lista de source na biblioteca de texto
                ids = [k for k, v in self.docstore._dict.items() if v.metadata["source"] in source]
 
                # Obtém o caminho do armazenamento do vetor
                vs_path = os.path.join(os.path.split ( os.path.split(source[0])[0])[0], "vector_store")
 
            # Se o texto a ser excluído não for encontrado, retorna uma mensagem de falha
            if len(ids) == 0:
                return f "docs delete fail "
 
            # Se o texto a ser excluído for encontrado
            else:
                # Percorra todos os textos a serem excluídos id
                por id em ids:
                    # Obtém a posição do id no índice index
                    = list(self.index_to_docstore_id.keys( ))[list(self. index_to_docstore_id.values()).index(id)]
 
                    # remove o id do índice
                    self.index_to_docstore_id.pop(index)
 
                    # Exclua o texto correspondente ao id da biblioteca de texto
                    self.docstore._dict.pop(id)
 
                # TODO: Exclua o id correspondente de self.index, esta é uma tarefa inacabada
                # self . index.reset()
                # Salve o estado atual no local
                self.save_local(vs_path)
 
                # Retorna informações sobre exclusão bem-sucedida
                return f"docs delete success"
 
        # Captura exceções
        exceto Exception as e:
            # Imprime informações de exceção
            print(e)
            # Return delete Falha na informação
            return f"docs delete fail"

3.8.5 update_doc e lists_doc
   # Defina um método chamado update_doc, que é usado para atualizar documentos na biblioteca de documentos
    def update_doc(self, source, new_docs):
        # Use a estrutura try-except para capturar possíveis exceções
        try:
            # Exclua documentos antigos
            delete_len = self.delete_doc(source)
 
            #Adicionar novos documentos
            ls = self.add_documents(new_docs)
 
            #Retorna informações de sucesso de atualização
            return f"docs update success"
        #Catch exceção
        exceto exceção como e:
            #Imprime informações de exceção
            print(e)
 
            # Retorna update failed information
            return f"docs update fail"
 
    # Defina um método chamado list_docs, que é usado para listar as fontes de todos os documentos na biblioteca de documentos
    def list_docs(self):
        # 遍历文档库中的所有文档,取出每个文档的来源,转换为集合,再转换为列表,最后返回这个列表
        return list(set(v.metadata["source"] for v in self.docstore._dict.values()))

第四部分 LLM与知识图谱的结合
4.1 LLM为何要与知识图谱相结合
通过本文之前或本博客内之前的内容可知,由于大部分LLM都是基于过去互联网旧的预训练语料训练、推理而来,由此会引发两大问题

无法获取最新的知识,为解决这个问题,ChatGPT plus版一开始是通过引入bing搜索这个插件去获取最新知识(不过 现因商业问题而暂时下架),而ChatGPT的竞品Claude则通过更新其预训练的数据 比如2013年的数据
面对少部分专业性比较强的问题,没法更好的回答,有时推理时会犯一些事实性的知识错误
面对第二个问题,我们在上文已经展示了可以通过与langchain结合搭建本地知识库的办法解决,此外,还可以考虑让LLM与知识图谱结合

知识图谱能以三元组(即头实体、关系、尾实体)的形式存储结构化知识,因此知识图谱是一种结构化和决断性的知识表征形式,例子包括 Wikidata、YAGO 和 NELL。知识图谱对多种应用而言都至关重要,因为其能提供准确、明确的知识。此外众所周知,它们还具有很棒的符号推理能力,这能生成可解释的结果。知识图谱还能随着新知识的持续输入而积极演进。当然 知识图谱本身也有其不足,比如泛化能力不足

根据所存储信息的不同,现有的知识图谱可分为四大类:百科知识型知识图谱、常识型知识图谱、特定领域型知识图谱、多模态知识图谱

 而下图总结了 LLM 和知识图谱各自的优缺点

 而实际上,LLM与知识图谱可以互相促进、增强彼此

Se o LLM for aprimorado com um gráfico de conhecimento, o gráfico de conhecimento pode não apenas ser integrado aos estágios de pré-treinamento e raciocínio do LLM para fornecer conhecimento externo, mas também ser usado para analisar o LLM para fornecer interpretabilidade ao usar o LLM para aprimorar o
gráfico de conhecimento Por um lado, o LLM tem sido usado em uma variedade de aplicações relacionadas a gráficos de conhecimento, como incorporação de gráfico de conhecimento, conclusão de gráfico de conhecimento, construção de gráfico de conhecimento, geração de gráfico de conhecimento para texto e resposta a perguntas de gráfico de conhecimento. O LLM pode melhorar o desempenho do gráfico de conhecimento e ajudar em sua aplicação.
Resumindo, na pesquisa relacionada ao LLM e à colaboração do gráfico de conhecimento, os pesquisadores combinam as vantagens do LLM e do gráfico de conhecimento, de modo que suas habilidades na representação e raciocínio do conhecimento possam promover-se mutuamente .

4.2 Usando gráficos de conhecimento para aprimorar o pré-treinamento, o raciocínio e a interpretabilidade do LLM
Em junho deste ano, um artigo "Unificando grandes modelos de linguagem e gráficos de conhecimento: um roteiro" apontou que existem várias maneiras específicas de aprimorar o LLM com gráficos de conhecimento 

Uma é usar o gráfico de conhecimento para aprimorar o pré-treinamento do LLM, que visa injetar conhecimento no LLM durante o estágio de pré-treinamento. O segundo é usar o gráfico de conhecimento para aprimorar o raciocínio do LLM, o que permite que o LLM leve em consideração o conhecimento mais recente
ao gerando sentenças.
Interpretabilidade do LLM, para que possamos entender melhor o comportamento do LLM
A tabela a seguir resume os métodos típicos de aprimoramento do LLM com gráficos de conhecimento

4.2.1 Melhorando o pré-treinamento LLM com gráfico de conhecimento


Os LLMs existentes dependem principalmente da realização de treinamento auto-supervisionado em corpora de grande escala. Embora esses modelos sejam excelentes em tarefas posteriores, eles carecem de conhecimento prático relevante para o mundo real. Em termos de integração do gráfico de conhecimento no LLM, pesquisas anteriores podem ser divididas em três categorias:

Integre o mapa de conhecimento no alvo de treinamento
Conforme mostrado na figura abaixo, as informações do mapa de conhecimento são injetadas no alvo de treinamento do LLM por meio da perda de alinhamento de conhecimento de texto, onde h representa a representação oculta gerada pelo LLM


Integre o mapa de conhecimento na entrada do LLM
Conforme mostrado na figura abaixo, use a estrutura do gráfico para injetar informações do mapa de conhecimento na entrada do LLM


Integre o mapa de conhecimento no módulo de fusão adicional
Conforme mostrado na figura abaixo, o mapa de conhecimento é integrado ao LLM por meio do módulo de fusão adicional


4.2.2 Aprimorando o raciocínio LLM com gráfico de conhecimento


O método acima pode efetivamente fundir conhecimento com representação textual no LLM. No entanto, o conhecimento do mundo real muda, e uma limitação desses métodos é que eles não permitem atualizar o conhecimento integrado, a menos que o modelo seja retreinado. Portanto, durante o raciocínio, eles podem não conseguir generalizar bem para conhecimentos não vistos (por exemplo, os dados pré-treinamento do ChatGPT terminarão em setembro de 2021. Para resolver esse problema de atualização de conhecimento, ele usou Plug-in externo pesquisa bing para resolver)

Portanto, pesquisas consideráveis ​​são dedicadas a manter o espaço de conhecimento e o espaço de texto separados e injetar conhecimento no momento do raciocínio. Esses métodos se concentram principalmente em tarefas de QA de resposta a perguntas, porque a resposta a perguntas requer modelos para capturar a semântica textual e o conhecimento do mundo real de última geração, como

Fusão de Gráficos de Conhecimento Dinâmico para Raciocínio LLM
Especificamente, uma abordagem direta é alavancar uma arquitetura de torre gêmea, onde um módulo separado lida com a entrada de texto e o outro lida com a entrada do gráfico de conhecimento relacionado. No entanto, este método carece da interação entre texto e conhecimento,
portanto, KagNet propõe primeiro codificar o KG de entrada e depois aprimorar a representação do texto de entrada. Em contraste, o MHGRN usa a saída LLM final do texto de entrada para guiar a inferência

再比如,JointLK提出了一个框架,通过LM-to-KG和KG-to- lm双向注意机制,在文本输入中的任何tokens和任何KG实体之间进行细粒度交互(JointLK then proposes a framework with fine-grainedinteraction between any tokens in the textual inputs and anyKG entities through LM-to-KG and KG-to-LM bi-directionalattention mechanism)
如下图所示,在所有文本标记和KG实体上计算成对点积分数,分别计算双向注意分数(pairwise dot-product scores are calculated over all textual tokens and KGentities, the bi-directional attentive scores are computed sep-arately)


通过检索外部知识来增强 LLM 生成


 更多细节在我司的langchain实战课程上见

4.2.3 用知识图谱增强 LLM 可解释性
// 待更..

4.3 用LLM增强知识图谱
// 待更..

4.4 LLM与知识图谱的协同
// 待更..

4.5 Projeto de combate de LLM combinado com KG: extração de conhecimento KnowLM
KnowLM é um projeto de extração de conhecimento combinado com recursos de LLM. Baseado no llama 13b, ele usa seus próprios dados + dados públicos para pré-treinar o modelo e, em seguida, usa o corpus de instruções no pré-treinamento No ajuste fino de Lora, o efeito final que pode ser obtido é mostrado na figura abaixo (fonte da imagem).

A figura abaixo mostra todo o processo de treinamento e construção do conjunto de dados. Todo o processo de treinamento é dividido em duas etapas:

No estágio de pré-treinamento completo, o objetivo deste estágio é aprimorar a capacidade chinesa e a reserva de conhecimento do modelo.
Usando o estágio de ajuste fino de instrução do LoRA, este estágio permite que o modelo entenda as instruções humanas e produza o conteúdo apropriado


4.5.1 Construção do conjunto de dados pré-treinamento e processo de treinamento


A fim de melhorar a capacidade do modelo de entender chinês, mantendo a capacidade de codificação original e a capacidade de inglês, eles não expandiram o vocabulário, mas coletaram o corpus chinês, o corpus inglês e o corpus de código. O corpus chinês vem dos conjuntos de dados em inglês da Baidu
  Encyclopedia , Wudao e a Wikipedia chinesa
  são amostras do corpus original em inglês do LLaMA. A diferença é o Wikidata. O ponto de tempo mais recente do Wikidata em inglês no artigo original é agosto de 2022. Eles também rastrearam de setembro de 2022 a fevereiro de 2023, um total de seis meses de dados
  e conjunto de dados de código, porque a qualidade do código no conjunto de dados Pile não é alta, eles rastrearam os dados de código do Github e Leetcode, parte do qual foi usado para pré-treinamento, a outra parte é usada para instruções tuning
. Para o conjunto de dados rastreado acima, eles usaram um método heurístico para eliminar conteúdo prejudicial no conjunto de dados. Além disso, também eliminamos dados repetidos. Código de processamento de dados detalhado
e código de treinamento, completo O script de treinamento e as condições de treinamento detalhadas podem ser encontrado em ./pretrain

在训练之前,首先需要对数据进行分词。他们设置的单个样本的最大长度是1024,而大多数的文档的长度都远远大于这个长度,因此需要对这些文档进行划分。设计了一个贪心算法来对文档进行切分,贪心的目标是在保证每个样本都是完整的句子、分割的段数尽可能少的前提下,尽可能保证每个样本的长度尽可能长
此外,由于数据源的多样性,设计了一套完整的数据预处理工具,可以对各个数据源进行处理然后合并
最后,由于数据量很大,如果直接将数据加载到内存,会导致硬件压力过大,于是他们参考了DeepSpeed-Megatron,使用mmap的方法对数据进行处理和加载,即将索引读入内存,需要的时候根据索引去硬盘查找

最后在5500K条中文样本、1500K条英文样本、900K条代码样本进行预训练。他们使用了transformers的trainer搭配Deepspeed ZeRO3 (实测使用ZeRO2在多机多卡场景的速度较慢),在3个Node(每个Node上为8张32GB V100卡)进行多机多卡训练。下表是训练速度:
参数    值
micro batch size(单张卡的batch size大小)    20
gradient accumulation(梯度累积)    3
global batch size(一个step的、全局的batch size)    20*3*24=1440
一个step耗时    260s
4.5.2 指令微调数据集构建与指令微调训练过程
在目前千篇一律的模型中,除了要加入通用的能力(比如推理能力、代码能力等),还额外增加了信息抽取能力(包括NER、RE、EE)。需要注意的是,由于许多开源的数据集,比如alpaca数据集 CoT数据集 代码数据集都是英文的,因此为了获得对应的中文数据集,对这些英文数据集使用GPT4进行翻译

有两种情况:
1. 直接对问题和答案进行成中文
2. 将英文问题输入给模型,让模型输出中文回答

对通用的数据集使用第二种情况,对于其他数据集如CoT数据集 代码数据集使用第一种情况

Para o conjunto de dados de extração de informações (IE), a parte em inglês usa conjuntos de dados IE de software livre, como CoNLL ACE CASIS, para construir o conjunto de dados de instrução em inglês correspondente. A parte chinesa não apenas usa conjuntos de dados de código aberto como DuEE, PEOPLE DAILY, DuIE, etc., mas também usa seu próprio KG2Instruction para construir o conjunto de dados de instrução chinês correspondente
. conjunto de dados de extração de informações obtido por meio de supervisão remota no Wikidata, abrangendo uma ampla gama de campos para atender às necessidades reais de extração

Além disso, eles construíram manualmente um conjunto de dados geral em chinês e o traduziram para o inglês usando a segunda estratégia. Finalmente, a distribuição de nossos conjuntos de dados é a seguinte:
tipo de conjunto de dados
COT (chinês e inglês) 202.333
conjuntos de dados gerais (chinês e inglês) 105.216
conjuntos de dados de código (chinês e inglês) 44.688
conjuntos de dados de extração de instrução em inglês 537.429
dados de extração de instrução em chinês define 486.768
KG2Instruction e outros conjuntos de dados de ajuste fino de instruções


No momento, a maioria dos scripts de ajuste fino são baseados em alpaca-lora, então não vou entrar em detalhes aqui, e instruções detalhadas de ajuste fino de parâmetros de treinamento e scripts de treinamento podem ser encontradas em ./finetune/lora
Apêndice

 https://github.com/zjunlp/KnowLM
https://github.com/kaixindelele/ChatSensitiveWords
usa a biblioteca de palavras sensíveis LLM+ para identificar automaticamente se palavras sensíveis estão envolvidas
https://github.com/XLabCU/gpt3-relationship-extraction -to-kg
https://github.com/niris/KGExtract/blob/main/get_kg.py
https://github.com/semantic-systems/coypu-LlamaKGQA/blob/main/knowledge_graph_coypu.py
https:// github.com/xixi019/coypu-LlamaKGQA
https://github.com/jamesdouglaspearce/kg-llama-7b
https://github.com/zhuojianc/financial_chatglm_KG
Além disso, isto é: uma lista de artigos sobre LLM e gráficos de conhecimento

A quinta parte é a combinação de LLM e banco de dados: DB-GPT


https://github.com/csunny/DB-GPT

5.1 Arquitetura do DB-GPT: Usando a tecnologia LLM privatizada para definir a próxima geração de interação com o banco de dados


DB-GPT constrói um grande modelo de ambiente operacional baseado em FastChat e fornece vicunha como um grande modelo de linguagem baseado nele. Além disso, LangChain fornece recursos de perguntas e respostas de base de conhecimento de domínio privado e tem armazenamento e indexação de vetorização de dados unificados: fornece uma maneira unificada de armazenar e indexar vários tipos de dados, suporta o modo de plug-in e suporta nativamente o plug-in Auto-GPT , com as seguintes funções ou capacidades

Gere gráficos de análise com base em diálogos de linguagem natural, gere SQL para
se comunicar com informações de metadados do banco de dados, gere instruções SQL precisas
para se comunicar com dados e visualize diretamente os resultados da execução
Gerenciamento de base de conhecimento (atualmente suporta txt, pdf, md, html, doc, ppt, e url )
De acordo com o diálogo da base de conhecimento, como pdf, csv, txt, palavras, etc.
suporta uma variedade de modelos de linguagem grandes, atualmente suporta Vicuna (7b, 13b), ChatGLM-6b (int4, int8), guanaco (7b , 13b, 33b) , Gorilla(7b,13b), llama-2(7b,13b,70b), baichuan(7b,13b)
toda a arquitetura DB-GPT, conforme mostrado na figura abaixo (fonte)

5.2 Aplicação do DB-GPT 


Através do método de QLoRA (quantização de nível de 4 bits + LoRA), use 3090 para construir uma base de conhecimento pessoal baseada em 33B LLM em DB-GPT

// ser atualizado

Veja mais na aula: Combate real entre LLM e langchain/mapa de conhecimento/banco de dados em julho [resolução de problemas, praticidade é rei]

Referências e leitura recomendada
Site oficial do langchain: LangChain, https://python.langchain.com/, lista API: https://api.python.langchain.com/en/latest/api_reference.html
Site chinês do langchain (tradução não disponível ainda) Bom)
Panorama do LangChain
Um artigo para entender o langchain (ignore este título, porque apenas a leitura deste artigo não é suficiente)
Como construir um Chatbot inteligente em 10 minutos com o LangChain
Vários tutoriais sobre o FAISS: Introdução ao Faiss e registros de experiência do aplicativo,
QLoRA: Quantização de nível de 4 bits + método LoRA, use 3090 para construir uma base de conhecimento pessoal baseada em 33B LLM em DB-GPT construir
QA aprimorado com base em LangChain + LLM, use LangChain para construir um aplicativo de modelo de linguagem grande, o que é LangChain LangChain
Chinês Tutorial introdutório
Large Language Model Uma revisão da pesquisa colaborativa com gráficos de conhecimento: duas vantagens técnicas principais complementam
csunny/DB-GPT, https://db-gpt.readthedocs.io/en/latest/July
LLM e langchain/knowledge graphs/database combate real [resolução de problemas, prático é rei]
Pós-escrito
Este artigo passou por três estágios

Existem muitos componentes no langchain
. Se você quiser entender completamente, você precisa passo a passo, inclusive
eu. Quando comecei a olhar para esta biblioteca, fiquei muito tonto e não consegui começar. Depois de 10 dias, posso abrir diretamente um arquivo de cada vez.Olha... Resumindo
, tudo é um processo Interpretação
do código-fonte do projeto langchain-ChatGLM
Para ser sincero, fiquei bastante tonto no começo porque havia muitos arquivos de projeto diferentes. Felizmente, demorou uma semana para classificar
LLM e gráficos de conhecimento. Combinação de banco de dados
Criar, modificar, otimizar registros
7,5-7,9 dias, escrever uma parte todos os dias
7,10, melhorar a introdução da primeira parte sobre o que é langchain
7,11, de acordo com a atualização mais recente do projeto langchain-ChatGLM, classifique o conteúdo escrito
7.12 Termine a primeira seção 3.8 e ajuste a ordem de interpretação de cada pasta de acordo com o processo do projeto
. Demorou quase uma semana
e, finalmente, classificou o "código geral estrutura de langchain-ChatGLM". Dividido em três camadas principais: camada básica, camada de capacidade, camada de aplicação
7.17, comece a escrever a quarta parte, concentre-se na seção 4.2: use o gráfico de conhecimento para aprimorar o pré-treinamento LLM, raciocínio, interpretabilidade
7.26, continue a escrever a quarta parte, comece a atualizar a quinta parte: a combinação de LLM e banco de dados
 

Acho que você gosta

Origin blog.csdn.net/sinat_37574187/article/details/132269760
Recomendado
Clasificación