> 本文作者为 360 奇舞团前端开发工程师
本篇文章我们将基于Ollama本地运行大语言模型(LLM),并结合ChormaDB、Langchain来建立一个小型的基于网页内容进行本地问答的RAG应用。
概念介绍先简单了解下这些术语:
LLM (A large language model) 是通过使用海量的文本数据集(书籍、网站等)训练出来的,具备通用语言理解和生成的能力。虽然它可以推理许多内容,但它们的知识仅限于特定时间点之前用于训练的数据。
LangChain 是一个用于开发由大型语言模型(LLM)驱动的应用程序的框架。提供了丰富的接口、组件、能力简化了构建LLM应用程序的过程。
Ollama 是一个免费的开源框架,可以让大模型很容易的运行在本地电脑上。
RAG(Retrieval Augmented Generation)是一种利用额外数据增强 LLM 知识的技术,它通过从外部数据库获取当前或相关上下文信息,并在请求大型语言模型(LLM)生成响应时呈现给它,从而解决了生成不正确或误导性信息的问题。
工作流程图解如下:
图片
图片
基于上述RAG步骤,接下来我们将使用代码完成它。
开始搭建1. 依据Ollama使用指南完成大模型的本地下载和的运行。
# LLMollama pull llama3# Embedding Modelollama pull nomic-embed-text2. 安装langchain、langchain-community、bs4
pip install langchain langchain-community bs4
3. 初始化langchain提供的Ollama对象
from langchain_community.llms import Ollamafrom langchain.callbacks.manager import CallbackManagerfrom langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler# 1. 初始化llm, 让其流式输出llm = Ollama(model='llama3', temperature=0.1, top_p=0.4, callback_manager=CallbackManager([StreamingStdOutCallbackHandler()]) )temperature控制文本生成的创造性,为0时响应是可预测,始终选择下一个最可能的单词,这对于事实和准确性非常重要的答案是非常有用的。为 1时生成文本会选择更多的单词,会产生更具创意但不可能预测的答案。
top_p 或 核心采样 决定了生成时要考虑多少可能的单词。高top_p值意味着模型会考虑更多可能的单词,甚至是可能性较低的单词,从而使生成的文本更加多样化。
较低的temperature和较高的top_p,可以产生具有创意的连贯文字。由于temperature较低,答案通常具有逻辑性和连贯性,但由于top_p较高,答案仍然具有丰富的词汇和观点。比较适合生成信息类文本,内容清晰且能吸引读者。
较高的temperature和较低的top_p,可能会把单词以难以预测的方式组合在一起。生成的文本创意高,会出现意想不到的结果,适合创作。
4. 获取RAG检索内容并分块
#`BeautifulSoup'解析网页内容:按照标签、类名、ID 等方式来定位和提取你需要的内容import bs4 #Load HTML pages using `urllib` and parse them with `BeautifulSoup'from langchain_community.document_loaders import WebBaseLoader#文本分割from langchain_text_splitters import RecursiveCharacterTextSplitterloader = WebBaseLoader( web_paths=('https://vuejs.org/guide/introduction.html#html',), bs_kwargs=dict( parse_only=bs4.SoupStrainer( class_=('content',), # id=('article-root',) ) ),)docs = loader.load()# chunk_overlap:分块的重叠部分text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)splits = text_splitter.split_documents(docs)
chunk_overlap:分块的重叠部分,重叠有助于降低将语句与与其相关的重要上下文分开的可能性。chunk_size:分块的大小,合理的分词设置会提高RAG的效果
内容基于本地的词嵌入模型 nomic-embed-text 嵌入向量数据库中# 向量嵌入 ::: conda install onnxruntime -c conda-forgefrom langchain_community.vectorstores import Chroma# 有许多嵌入模型from langchain_community.embeddings import OllamaEmbeddings# 基于ollama运行嵌入模型 nomic-embed-text :A high-performing open embedding model with a large token context window.vectorstore = Chroma.from_documents(documents=splits, embedding=OllamaEmbeddings(model='nomic-embed-text'))# 相似搜索# vectorstore.similarity_search('vue')此处的嵌入模型也可以使用其他的比如llama3、mistral,但是在本地运行太慢了,它们和nomic-embed-text 一样不支持中文的词嵌入。如果想试试建立一个中文的文档库,可以试试 herald/dmeta-embedding-zh词嵌入的模型,支持中文。
ollama pull herald/dmeta-embedding-zh:latest设置Prompt规范输出from langchain_core.prompts import PromptTemplateprompt = PromptTemplate( input_variables=['context', 'question'], template= '''You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. you don't know the answer, just say you don't know without any explanation Question: {question} Context: {context} Answer:''',)基于langchain实现检索问答
from langchain.chains import RetrievalQA# 向量数据库检索器retriever = vectorstore.as_retriever()qa_chain = RetrievalQA.from_chain_type( llm, retriever=retriever, chain_type_kwargs={'prompt': prompt})# what is Composition API?question = 'what is vue?'result = qa_chain.invoke({'query': question})# output# I think I know this one! Based on the context, # Vue is a JavaScript framework for building user interfaces # that builds on top of standard HTML, CSS, and JavaScript. # It provides a declarative way to use Vue primarily in # low-complexity scenarios or for building full applications with # Composition API + Single-File Components.
如果我问的问题与文档无关它的回答是怎样呢?
question = 'what is react?'result = qa_chain.invoke({'query': question})最终执行后输出了I don't know.。
构建用户界面Gradio是一个用于构建交互式机器学习界面的Python库。Gradio使用非常简单。你只需要定义一个有输入和输出的函数,然后Gradio将自动为你生成一个界面。用户可以在界面中输入数据,然后观察模型的输出结果。
整合上述代码,构建可交互的UI:
import gradio as grfrom langchain_community.llms import Ollamafrom langchain.callbacks.manager import CallbackManagerfrom langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandlerfrom langchain_community.document_loaders import WebBaseLoaderfrom langchain_text_splitters import RecursiveCharacterTextSplitterfrom langchain_community.vectorstores import Chromafrom langchain_community.embeddings import OllamaEmbeddingsfrom langchain.chains import RetrievalQAfrom langchain_core.prompts import PromptTemplatedef init_ollama_llm(model, temperature, top_p): return Ollama(model=model, temperature=temperature, top_p=top_p, callback_manager=CallbackManager([StreamingStdOutCallbackHandler()]) )def content_web(url): loader = WebBaseLoader( web_paths=(url,), ) docs = loader.load() # chunk_overlap:分块的重叠部分,重叠有助于降低将语句与与其相关的重要上下文分开的可能性, # 设置了chunk_overlap效果会更好 # 合理的分词会提高RAG的效果 text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) splits = text_splitter.split_documents(docs) return splitsdef chroma_retriever_store_content(splits): # 基于ollama运行嵌入模型 nomic-embed-text :A high-performing open embedding model with a large token context window. vectorstore = Chroma.from_documents(documents=splits, embedding=OllamaEmbeddings(model='nomic-embed-text')) return vectorstore.as_retriever()def rag_prompt(): return PromptTemplate( input_variables=['context', 'question'], template= '''You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. you don't know the answer, just say you don't know without any explanation Question: {question} Context: {context} Answer:''', )def ollama_rag_chroma_web_content(web_url, question,temperature,top_p): llm = init_ollama_llm('llama3', temperature, top_p) splits = content_web(web_url) retriever = chroma_retriever_store_content(splits) qa_chain = RetrievalQA.from_chain_type(llm, retriever=retriever, chain_type_kwargs={'prompt': rag_prompt()}) return qa_chain.invoke({'query': question})['result']demo = gr.Interface( fn=ollama_rag_chroma_web_content, inputs=[gr.Textbox(label='web_url',value='https://vuejs.org/guide/introduction.html',info='爬取内容的网页地址'), 'text', gr.Slider(0, 1,step=0.1), gr.Slider(0, 1,step=0.1)], outputs='text', title='Ollama+RAG Example', description='输入网页的URL,然后提问, 获取答案')demo.launch()
运行后会输出网页地址Running on local URL: http://127.0.0.1:7860, 打开后效果如下:
图片
参考https://github.com/ollama/ollama
https://python.langchain.com/
https://partee.io/2022/08/11/vector-embeddings/
https://jalammar.github.io/illustrated-word2vec/
- END -
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报。