我们计划采用Llama-index来做RAG。Langchain也是个不错的选择。我们之后会加入Langchain的版本,不过目前只做了Llama-index的教程。干活之前我们先准备好必备模型。一个llm模型,和一个embedding模型。 想要借助Llama-index构建llm和embedding模型,我们大体上有四种思路。 第一个思路:使用Llama-index为各个厂家单独构建的服务,比如Llama-index为智谱和零一万物构建了专门的包,我们可以直接安装使用。 第二个思路:如果Llama-index没有为某个厂家构建的服务,我们可以借助Llama-index为openai构建的库。只要我们国内的模型是openai兼容型的,我们就可以稍微修改一下源码直接使用。
我们计划采用Llama-index来做RAG。Langchain也是个不错的选择。我们之后会加入Langchain的版本,不过目前只做了Llama-index的教程。干活之前我们先准备好必备模型。一个llm模型,和一个embedding模型。
想要借助Llama-index构建llm和embedding模型,我们大体上有四种思路。
这种方法对智谱来说最友好,但是对于国内的大模型,Llama-index只为智谱和零一万物构建了专用包,其他厂家的大模型,我们就没办法这样使用了。顺便提一句,零一万物现在对学生的优惠有36元,可以去官网免费试用。
%pip install llama-index-core %pip install llama-index-embeddings-zhipuai %pip install llama-index-llms-zhipuai %pip install llama-index-readers-file %pip install llama-index-vector-stores-faiss %pip install llamaindex-py-client
跟第一节课的开头一样,咱们现在先把四样前菜准备一下吧:
import os from dotenv import load_dotenv # 加载环境变量 load_dotenv() # 从环境变量中读取api_key api_key = os.getenv('ZHIPU_API_KEY') base_url = "https://open.bigmodel.cn/api/paas/v4/" chat_model = "glm-4-flash" emb_model = "embedding-2"
from llama_index.llms.zhipuai import ZhipuAI llm = ZhipuAI( api_key = api_key, model = chat_model, )
# 测试对话模型 response = llm.complete("你是谁?") print(response)
# 测试对话模型流式输出 response = llm.stream_complete("你是谁?") for chunk in response: print('\n') print(chunk, end="",flush=True)
# 配置嵌入模型 from llama_index.embeddings.zhipuai import ZhipuAIEmbedding embedding = ZhipuAIEmbedding( api_key = api_key, model = emb_model, )
# 测试嵌入模型 emb = embedding.get_text_embedding("你好呀呀") len(emb), type(emb)
输出 (1024, list)
说明配置成功。
直接安装最新版本
如果是在jupyter notebook里运行,需要先用魔法命令安装这些库。
%pip install llama-index-core %pip install llama-index-embeddings-openai %pip install llama-index-llms-openai %pip install llama-index-readers-file %pip install llama-index-vector-stores-faiss %pip install llamaindex-py-client
我们想要借助llama_index的OpenAI接口使用其他厂家的模型,通过翻阅源码,发现llama index 把OpenAI和OpenAIEmbedding的模型名称写死在代码里了,它会检查每个模型的输入上下文大小,如果模型没有在他的列表中,就会报错。所以我们可以重写一下llama_index的OpenAI类,通过新建一个NewOpenAI类,并继承OpenAI类,我们直接把输入上下文大小写死,不让它检查了,它就不报错了。
重写OpenAI类:
from llama_index.llms.openai import OpenAI from llama_index.core.base.llms.types import LLMMetadata,MessageRole class NewOpenAI(OpenAI): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @property def metadata(self) -> LLMMetadata: # 创建一个新的LLMMetadata实例,只修改context_window return LLMMetadata( context_window=8192, num_output=self.max_tokens or -1, is_chat_model=True, is_function_calling_model=True, model_name=self.model, system_role=MessageRole.USER, )
重写完后,我们用NewOpenAI这个类来配置llm。
llm = NewOpenAI( temperature=0.95, api_key = api_key, model = chat_model, api_base = base_url # 注意这里单词不一样 )
测试对话模型
response = llm.complete("你是谁?") print(response)
我是一个名为 ChatGLM 的人工智能助手,是基于清华大学 KEG 实验室和智谱 AI 公司于 2024 年共同训练的语言模型开发的。我的任务是针对用户的问题和要求提供适当的答复和支持。
# 测试对话模型流式输出 response = llm.stream_complete("你是谁?") for chunk in response: print('\n') print(chunk, end="",flush=True)
除了重写类,我们也可以直接去源代码文件去修改源代码。
找到这个文件:Lib\site-packages\llama_index\llms\openai\utils.py
在大约第30行,有一个GPT4_MODELS: Dict[str, int]
在这里面列举了很多模型的名称,把智谱的glm-4-flash加进入,变成这样:
"gpt-4": 8192,
"gpt-4-32k": 32768,
改成:
"gpt-4": 8192,
"glm-4-flash": 8192,
"gpt-4-32k": 32768,
改源码:
Lib\site-packages\llama_index\embeddings\openai\base.py
一共改四个地方
class OpenAIEmbeddingModelType(str, Enum):
最下面增加
EMBED_2 = "embedding-2"
class OpenAIEmbeddingModeModel(str, Enum):
最下面增加
EMBED_2 = "embedding-2"
_QUERY_MODE_MODEL_DICT = {
最下面增加
(OAEM.TEXT_SEARCH_MODE, "embedding-2"): OAEMM.EMBED_2,
_TEXT_MODE_MODEL_DICT = {
最下面增加
(OAEM.TEXT_SEARCH_MODE, "embedding-2"): OAEMM.EMBED_2,
改了上面这四个地方,再调用OpenAIEmbedding这个类,就可以正常使用了
如果怕麻烦,也可以用教程里附的base.py 直接替换。
如果不小心导入过llama_index,改完源码不要忘了重启jupyter内核。
from llama_index.llms.openai import OpenAI llm = OpenAI( temperature=0.95, api_key = api_key, model = chat_model, api_base = base_url # 注意这里单词不一样 ) # 测试对话模型 response = llm.complete("你是谁?") print(response) # 测试对话模型流失输出 response = llm.stream_complete("你是谁?") for chunk in response: print('\n') print(chunk, end="",flush=True)
# 配置Embedding模型 from llama_index.embeddings.openai import OpenAIEmbedding embedding = OpenAIEmbedding( api_key = api_key, model = emb_model, api_base = base_url # 注意这里单词不一样 ) emb = embedding.get_text_embedding("你好呀呀") len(emb), type(emb)
输出 (1024, list)
说明配置成功。
自定义可以利用openai-like的包,来封装任何openai类似的大模型
这个思路的缺点很明显,只有对话模型,没有嵌入模型。
虽然不推荐这个方式,但是如果实在想要尝试,先安装这个库:
%pip install llama-index-llms-openai-like
对话模型可以直接使用
from llama_index.llms.openai_like import OpenAILike llm = OpenAILike( model = chat_model, api_base = base_url, api_key = api_key, is_chat_model=True )
# 导入必要的库和模块 from openai import OpenAI from pydantic import Field # 导入Field,用于Pydantic模型中定义字段的元数据 from typing import Optional, List, Mapping, Any, Generator import os from llama_index.core import SimpleDirectoryReader, SummaryIndex from llama_index.core.callbacks import CallbackManager from llama_index.core.llms import ( CustomLLM, CompletionResponse, CompletionResponseGen, LLMMetadata, ) from llama_index.core.llms.callbacks import llm_completion_callback from llama_index.core import Settings # 定义OurLLM类,继承自CustomLLM基类 class OurLLM(CustomLLM): api_key: str = Field(default=api_key) base_url: str = Field(default=base_url) model_name: str = Field(default=chat_model) client: OpenAI = Field(default=None, exclude=True) # 显式声明 client 字段 def __init__(self, api_key: str, base_url: str, model_name: str = chat_model, **data: Any): super().__init__(**data) self.api_key = api_key self.base_url = base_url self.model_name = model_name self.client = OpenAI(api_key=self.api_key, base_url=self.base_url) # 使用传入的api_key和base_url初始化 client 实例 @property def metadata(self) -> LLMMetadata: """Get LLM metadata.""" return LLMMetadata( model_name=self.model_name, ) @llm_completion_callback() def complete(self, prompt: str, **kwargs: Any) -> CompletionResponse: response = self.client.chat.completions.create(model=self.model_name, messages=[{"role": "user", "content": prompt}]) if hasattr(response, 'choices') and len(response.choices) > 0: response_text = response.choices[0].message.content return CompletionResponse(text=response_text) else: raise Exception(f"Unexpected response format: {response}") @llm_completion_callback() def stream_complete( self, prompt: str, **kwargs: Any ) -> Generator[CompletionResponse, None, None]: response = self.client.chat.completions.create( model=self.model_name, messages=[{"role": "user", "content": prompt}], stream=True ) try: for chunk in response: chunk_message = chunk.choices[0].delta if not chunk_message.content: continue content = chunk_message.content yield CompletionResponse(text=content, delta=content) except Exception as e: raise Exception(f"Unexpected response format: {e}")
# 测试对话模型 llm = OurLLM(api_key=api_key, base_url=base_url, model_name=chat_model) response = llm.complete("你是谁?") print(response) # 测试对话模型流失输出 response = llm.stream_complete("你是谁?") for chunk in response: print('\n') print(chunk, end="",flush=True)
我是一个名为 ChatGLM 的人工智能助手,是基于清华大学 KEG 实验室和智谱 AI 公司于 2024 年共同训练的语言模型开发的。我的任务是针对用户的问题和要求提供适当的答复和支持。
from openai import OpenAI from typing import Any, List from llama_index.core.embeddings import BaseEmbedding from pydantic import Field class OurEmbeddings(BaseEmbedding): api_key: str = Field(default=api_key) base_url: str = Field(default=base_url) model_name: str = Field(default=emb_model) client: OpenAI = Field(default=None, exclude=True) # 显式声明 client 字段 def __init__( self, api_key: str = api_key, base_url: str = base_url, model_name: str = emb_model, **kwargs: Any, ) -> None: super().__init__(**kwargs) self.api_key = api_key self.base_url = base_url self.model_name = model_name self.client = OpenAI(api_key=self.api_key, base_url=self.base_url) def invoke_embedding(self, query: str) -> List[float]: response = self.client.embeddings.create(model=self.model_name, input=[query]) # 检查响应是否成功 if response.data and len(response.data) > 0: return response.data[0].embedding else: raise ValueError("Failed to get embedding from ZhipuAI API") def _get_query_embedding(self, query: str) -> List[float]: return self.invoke_embedding(query) def _get_text_embedding(self, text: str) -> List[float]: return self.invoke_embedding(text) def _get_text_embeddings(self, texts: List[str]) -> List[List[float]]: return [self._get_text_embedding(text) for text in texts] async def _aget_query_embedding(self, query: str) -> List[float]: return self._get_query_embedding(query) async def _aget_text_embedding(self, text: str) -> List[float]: return self._get_text_embedding(text) async def _aget_text_embeddings(self, texts: List[str]) -> List[List[float]]: return self._get_text_embeddings(texts)
来测试一下自定义的嵌入模型
embedding = OurEmbeddings(api_key=api_key, base_url=base_url, model_name=emb_model) emb = embedding.get_text_embedding("你好呀呀") len(emb), type(emb)
(1024, list)
Ollama里的qwen2系列非常棒,有很多尺寸规模的模型,最小的模型只有 0.5b,然后还有1.5b、7b、32b等等,可以适应各种设备。
访问 https://ollama.com。 下载Windows版本。直接安装。 安装完成后,打开命令行窗口,输入 ollama,如果出现 Usage: Available Commands: 之类的信息,说明安装成功。
我们用qwen2:7b这个模型就行,整个还不到4G。 运行 ollama run qwen2:7b
如果出现了success,就说明安装成功。 然后会出现一个>>>符号,这就是对话窗口。可以直接输入问题。
想要退出交互页面,直接输入 /bye 就行。斜杠是需要的。否则不是退出交互页面,而是对大模型说话,它会继续跟你聊。
在浏览器中输入 127.0.0.1:11434,如果出现 Ollama is running
说明端口运行正常。
安装完ollama后,我们还需要进行配置一下,主要是两个方面。这个配置只有在Windows电脑上试过。Linux和Mac抱歉还没有试过,需要的朋友可能要自己探索一下了。
第一:这时候模型是放在内存中的。我们希望把模型放在硬盘中。所以,我们可以在硬盘中建一个文件夹,比如: D:\programs\ollama\models
然后新建系统环境变量。 变量名: OLLAMA_MODELS
变量值: D:\programs\ollama\models
第二:这时候的大模型只能通过127.0.0.1:11434来访问。我们希望在局域网中的任何电脑都可以访问。这也是通过新建环境变量来解决。
变量名: OLLAMA_HOST 变量值: 0.0.0.0:11434
这样就完成了配置。是不是非常简单方便?
如果是在jupyter notebook里运行,需要先用魔法命令安装这些库。
%pip install llama-index-core %pip install llama-index-embeddings-ollama %pip install llama-index-llms-ollama %pip install llama-index-readers-file %pip install llama-index-vector-stores-faiss %pip install llamaindex-py-client
# 我们先用requets库来测试一下大模型 import json import requests # 192.168.0.123就是部署了大模型的电脑的IP, # 请根据实际情况进行替换 BASE_URL = "http://192.168.0.123:11434/api/chat"
直接输出看看:
payload = { "model": "qwen2:7b", "messages": [ { "role": "user", "content": "请写一篇1000字左右的文章,论述法学专业的就业前景。" } ] } response = requests.post(BASE_URL, json=payload) print(response.text)
然后改成流式输出:
payload = { "model": "qwen2:7b", "messages": [ { "role": "user", "content": "请写一篇1000字左右的文章,论述法学专业的就业前景。" } ], "stream": True } response = requests.post(BASE_URL, json=payload, stream=True) # 在这里设置stream=True告诉requests不要立即下载响应内容 # 检查响应状态码 if response.status_code == 200: # 使用iter_content()迭代响应体 for chunk in response.iter_content(chunk_size=1024): # 你可以设置chunk_size为你想要的大小 if chunk: # 在这里处理chunk(例如,打印、写入文件等) rtn = json.loads(chunk.decode('utf-8')) # 假设响应是文本,并且使用UTF-8编码 print(rtn["message"]["content"], end="") else: print(f"Error: {response.status_code}") # 不要忘记关闭响应 response.close()
如果上面输出正常,说明Ollama的配置成功。我们再来配置一下Llama-index。
# 配置chat模型 from llama_index.llms.ollama import Ollama llm = Ollama(base_url="http://192.168.0.123:11434", model="qwen2:7b")
# 测试chat模型 response = llm.complete("你是谁?") print(response) # 我是阿里云开发的一款超大规模语言模型,我叫通义千问。
# 配置嵌入模型 from llama_index.embeddings.ollama import OllamaEmbedding embedding = OllamaEmbedding(base_url="http://192.168.0.123:11434", model_name="qwen2:7b")
# 测试嵌入模型 emb = embedding.get_text_embedding("你好呀呀") len(emb), type(emb) # (3584, list)
但是有个问题,就是Ollama这个嵌入模型用来做向量检索,效果不好。一般来说向量的相似度得分是在0到1之间的小数。但是Ollama这个嵌入模型计算出来的得分要么非常大,要么非常小。根据经验,还是智谱的这个向量模型效果最好。