一、基本概念
1.1、Prompt
大模型的所有輸入,即,我們每一次訪問大模型的輸入為一個 Prompt, 而大模型給我們的返回結(jié)果則被稱為 Completion。
1.2、Temperature
LLM 生成是具有隨機性的,在模型的頂層通過選取不同預(yù)測概率的預(yù)測結(jié)果來生成最后的結(jié)果,而Temperature 參數(shù)就是用來控制 LLM 生成結(jié)果的隨機性與創(chuàng)造性。
Temperature 一般取值在 0~1 之間,當(dāng)取值較低接近0時,預(yù)測的隨機性小,更為保守,嚴謹,穩(wěn)定。當(dāng)取值較高接近1時,預(yù)測的隨機性會較高,預(yù)測結(jié)果更創(chuàng)意,多樣化。
不同問題的應(yīng)用場景,設(shè)置不同的Temperature。例如:
- 個人知識庫項目,一般將 Temperature 設(shè)置為0,從而保證知識庫內(nèi)容的穩(wěn)定使用,規(guī)避錯誤內(nèi)容;
- 產(chǎn)品智能客服、科研論文寫作等場景中,同樣更需要穩(wěn)定性而不是創(chuàng)造性;
- 個性化 AI、創(chuàng)意營銷文案生成等場景中,更需要創(chuàng)意性,從而更傾向于將 Temperature 設(shè)置為較高的值。
1.3、System Prompt
使用 ChatGPT API 時,可以設(shè)置兩種 Prompt:一種是 System Prompt,該種 Prompt 內(nèi)容會在整個會話過程中持久地影響模型的回復(fù),且相比于普通 Prompt 具有更高的重要性;另一種是 User Prompt,這更偏向于普通的 Prompt,即需要模型做出回復(fù)的輸入。
System Prompt 一般在一個會話中僅有一個。
二、調(diào)用ChatGPT
調(diào)用 ChatGPT API 的2種方法:直接調(diào)用 OpenAI 的原生接口,或是基于 LangChain 調(diào)用 ChatGPT API。
2.1、準備OpenAI API Key
1、登錄openai官網(wǎng),登錄賬號
2、選擇API,然后點擊右上角的頭像,選擇View API keys
3、點擊Create new secret key按鈕創(chuàng)建OpenAI API key,將創(chuàng)建好的OpenAI API key復(fù)制以此形式OPENAI_API_KEY="sk-..."保存到.env文件中,并將.env文件保存在項目根目錄下。
讀取.env文件的代碼
import os
import openai
from dotenv import load_dotenv, find_dotenv
# 讀取本地/項目的環(huán)境變量。
# find_dotenv()尋找并定位.env文件的路徑
# load_dotenv()讀取該.env文件,并將其中的環(huán)境變量加載到當(dāng)前的運行環(huán)境中
# 如果你設(shè)置的是全局的環(huán)境變量,這行代碼則沒有任何作用。
_ = load_dotenv(find_dotenv())
# 如果你需要通過代理端口訪問,你需要如下配置
os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:7890'
os.environ["HTTP_PROXY"] = 'http://127.0.0.1:7890'
# 獲取環(huán)境變量 OPENAI_API_KEY
openai.api_key = os.environ['OPENAI_API_KEY']
2.2、調(diào)用OpenAI原生接口
接口文檔地址:https://platform.openai.com/docs/api-reference/chat
調(diào)用 ChatGPT 需要使用 ChatCompletion API,ChatCompletion API 調(diào)用方法如下:
import openai
# 導(dǎo)入所需庫
# 注意,此處我們假設(shè)你已根據(jù)上文配置了 OpenAI API Key,如沒有將訪問失敗
completion = openai.ChatCompletion.create(
# 創(chuàng)建一個 ChatCompletion
# 調(diào)用模型:ChatGPT-3.5
model="gpt-3.5-turbo",
# message 是你的 prompt
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"}
]
)
調(diào)用該 API 會返回一個 ChatCompletion 對象,其中包括了回答文本、創(chuàng)建時間、ID等屬性。我們一般需要的是回答文本,也就是回答對象中的 content 信息。
<OpenAIObject chat.completion id=chatcmpl-80QUFny7lXqOcfu5CZMRYhgXqUCv0 at 0x7f1fbc0bd770> JSON: {
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "Hello! How can I assist you today?",
"role": "assistant"
}
}
],
"created": 1695112507,
"id": "chatcmpl-80QUFny7lXqOcfu5CZMRYhgXqUCv0",
"model": "gpt-3.5-turbo-0613",
"object": "chat.completion",
"usage": {
"completion_tokens": 9,
"prompt_tokens": 19,
"total_tokens": 28
}
}
print(completion["choices"][0]["message"]["content"])
Hello! How can I assist you today?
API 常會用到的幾個參數(shù):
· model,即調(diào)用的模型,一般取值包括“gpt-3.5-turbo”(ChatGPT-3.5)、“gpt-3.5-16k-0613”(ChatGPT-3.5 16K 版本)、“gpt-4”(ChatGPT-4)。注意,不同模型的成本是不一樣的。
· message,即我們的 prompt。ChatCompletion 的 message 需要傳入一個列表,列表中包括多個不同角色的 prompt。我們可以選擇的角色一般包括 system:即前文中提到的 system prompt;user:用戶輸入的 prompt;assitance:助手,一般是模型歷史回復(fù),作為給模型參考的示例。
· temperature,溫度。即前文中提到的 Temperature 系數(shù)。
· max_tokens,最大 token 數(shù),即模型輸出的最大 token 數(shù)。OpenAI 計算 token 數(shù)是合并計算 Prompt 和 Completion 的總 token 數(shù),要求總 token 數(shù)不能超過模型上限(如默認模型 token 上限為 4096)。因此,如果輸入的 prompt 較長,需要設(shè)置較小的 max_token 值,否則會報錯超出限制長度。
把對OpenAI API的調(diào)用封裝成一個函數(shù),直接傳入 Prompt 并獲得模型的輸出:
# 一個封裝 OpenAI 接口的函數(shù),參數(shù)為 Prompt,返回對應(yīng)結(jié)果
def get_completion(prompt, model="gpt-3.5-turbo", temperature = 0):
'''
prompt: 對應(yīng)的提示詞
model: 調(diào)用的模型,默認為 gpt-3.5-turbo(ChatGPT),有內(nèi)測資格的用戶可以選擇 gpt-4
'''
messages = [{"role": "user", "content": prompt}]
response = openai.ChatCompletion.create(
model=model,
messages=messages,
temperature=temperature, # 模型輸出的溫度系數(shù),控制輸出的隨機程度
)
# 調(diào)用 OpenAI 的 ChatCompletion 接口
return response.choices[0].message["content"]
上述函數(shù)中,封裝了 messages 的細節(jié),僅使用 user prompt 來實現(xiàn)調(diào)用。在簡單場景中,該函數(shù)完全足夠使用。
2.3、基于 LangChain 調(diào)用 ChatGPT
LangChain 提供了對于多種大模型的封裝,基于 LangChain 的接口可以便捷地調(diào)用 ChatGPT 并將其集合在以 LangChain 為基礎(chǔ)框架搭建的個人應(yīng)用中。
官網(wǎng)文檔:https://api.python.langchain.com/en/latest/api_reference.html#module-langchain.chat_models
1、從langchain.chat_models導(dǎo)入OpenAI的對話模型ChatOpenAI。
from langchain.chat_models import ChatOpenAI
2、實例化一個 ChatOpenAI 類,可以在實例化時傳入超參數(shù)來控制回答,例如 temperature 參數(shù)。
# 這里我們將參數(shù)temperature設(shè)置為0.0,從而減少生成答案的隨機性。
# 如果你想要每次得到不一樣的有新意的答案,可以嘗試調(diào)整該參數(shù)。
chat = ChatOpenAI(temperature=0.0)
chat
ChatOpenAI(cache=None, verbose=False, callbacks=None, callback_manager=None, tags=None, metadata=None, client=<class 'openai.api_resources.chat_completion.ChatCompletion'>, model_name='gpt-3.5-turbo', temperature=0.0, model_kwargs={}, openai_api_key='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', openai_api_base='', openai_organization='', openai_proxy='', request_timeout=None, max_retries=6, streaming=False, n=1, max_tokens=None, tiktoken_model_name=None)
常用的超參數(shù)設(shè)置包括:
· model_name:所要使用的模型,默認為 ‘gpt-3.5-turbo’,參數(shù)設(shè)置與 OpenAI 原生接口參數(shù)設(shè)置一致。
· temperature:溫度系數(shù),取值同原生接口。
· openai_api_key:OpenAI API key,如果不使用環(huán)境變量設(shè)置 API Key,也可以在實例化時設(shè)置。
· openai_proxy:設(shè)置代理,如果不使用環(huán)境變量設(shè)置代理,也可以在實例化時設(shè)置。
· streaming:是否使用流式傳輸,即逐字輸出模型回答,默認為 False,此處不贅述。
· max_tokens:模型輸出的最大 token 數(shù),意義及取值同上。
3、 構(gòu)造個性化 Template。
Template,即模板,是 LangChain 設(shè)置好的一種 Prompt 格式,開發(fā)者可以直接調(diào)用 Template 向里面填充個性化任務(wù)。
from langchain.prompts import ChatPromptTemplate
# 這里我們要求模型對給定文本進行中文翻譯
template_string = """Translate the text \
that is delimited by triple backticks \
into a Chinses. \
text: ```{text}```
"""
# 接著將 Template 實例化
chat_template = ChatPromptTemplate.from_template(template_string)
4、將 template 轉(zhuǎn)化為message 格式
# 我們首先設(shè)置變量值
text = "Today is a nice day."
# 接著調(diào)用 format_messages 將 template 轉(zhuǎn)化為 message 格式
message = chat_template.format_messages(text=text)
print(message)
[HumanMessage(content='Translate the text that is delimited by triple backticks into a Chinses. text: ```Today is a nice day.```\n', additional_kwargs={}, example=False)]
5、使用實例化的類型直接傳入設(shè)定好的 prompt
response = chat(message)
response
AIMessage(content='今天是個好天氣。', additional_kwargs={}, example=False)
返回值的 content 屬性即為模型的返回文本。
三、langchain 核心組件詳解
3.1、模型輸入/輸出
LangChain 中模型輸入/輸出模塊是與各種大語言模型進行交互的基本組件,是大語言模型應(yīng)用的核心元素。模型 I/O 允許管理 prompt(提示),通過通用接口調(diào)用語言模型以及從模型輸出中提取信息。該模塊的基本流程:
3.2、數(shù)據(jù)連接
大語言模型的知識來源于其訓(xùn)練數(shù)據(jù)集,并沒有用戶的信息(比如用戶的個人數(shù)據(jù),公司的自有數(shù)據(jù)),也沒有最新發(fā)生時事的信息(在大模型數(shù)據(jù)訓(xùn)練后發(fā)表的文章或者新聞)。因此大模型能給出的答案比較受限。如果能夠讓大模型在訓(xùn)練數(shù)據(jù)集的基礎(chǔ)上,利用我們自有數(shù)據(jù)中的信息來回答我們的問題,那便能夠得到更有用的答案。
LangChain 數(shù)據(jù)連接(Data connection)模塊支持自定義數(shù)據(jù),支持加載、轉(zhuǎn)換、存儲和查詢數(shù)據(jù),模塊具體內(nèi)容包括:Document loaders、Document transformers、Text embedding models、Vector stores 以及 Retrievers。數(shù)據(jù)連接模塊部分的基本框架如下。
3.3、鏈(Chain)
獨立使用大型語言模型能夠應(yīng)對一些簡單任務(wù),但對于更加復(fù)雜的需求,可能需要將多個大型語言模型進行鏈式組合,或與其他組件進行鏈式調(diào)用。鏈允許將多個組件組合在一起,創(chuàng)建一個連貫的應(yīng)用程序。
大語言模型鏈(LLMChain)的使用:
import warnings
warnings.filterwarnings('ignore')
from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.chains import LLMChain
# 這里我們將參數(shù)temperature設(shè)置為0.0,從而減少生成答案的隨機性。
# 如果你想要每次得到不一樣的有新意的答案,可以嘗試調(diào)整該參數(shù)。
llm = ChatOpenAI(temperature=0.0)
#初始化提示模版
prompt = ChatPromptTemplate.from_template("描述制造{product}的一個公司的最佳名稱是什么?")
#將大語言模型(LLM)和提示(Prompt)組合成鏈
chain = LLMChain(llm=llm, prompt=prompt)
#運行大語言模型鏈
product = "大號床單套裝"
chain.run(product)
輸出:
'"豪華床紡"'
除了LLMChain,LangChain 中鏈還包含 RouterChain、SimpleSequentialChain、SequentialChain、TransformChain 等。
RouterChain 可以根據(jù)輸入數(shù)據(jù)的某些屬性/特征值,選擇調(diào)用不同的子鏈(Subchain)。
SimpleSequentialChain 是最簡單的序列鏈形式,其中每個步驟具有單一的輸入/輸出,上一個步驟的輸出是下一個步驟的輸入。
SequentialChain 是簡單順序鏈的更復(fù)雜形式,允許多個輸入/輸出。
TransformChain 可以引入自定義轉(zhuǎn)換函數(shù),對輸入進行處理后進行輸出。
使用 SimpleSequentialChain 的代碼示例:
from langchain.chains import SimpleSequentialChain
llm = ChatOpenAI(temperature=0.9)
#創(chuàng)建兩個子鏈
# 提示模板 1 :這個提示將接受產(chǎn)品并返回最佳名稱來描述該公司
first_prompt = ChatPromptTemplate.from_template(
"描述制造{product}的一個公司的最好的名稱是什么"
)
chain_one = LLMChain(llm=llm, prompt=first_prompt)
# 提示模板 2 :接受公司名稱,然后輸出該公司的長為20個單詞的描述
second_prompt = ChatPromptTemplate.from_template(
"寫一個20字的描述對于下面這個\
公司:{company_name}的"
)
chain_two = LLMChain(llm=llm, prompt=second_prompt)
#構(gòu)建簡單順序鏈
#現(xiàn)在我們可以組合兩個LLMChain,以便我們可以在一個步驟中創(chuàng)建公司名稱和描述
overall_simple_chain = SimpleSequentialChain(chains=[chain_one, chain_two], verbose=True)
#運行簡單順序鏈
product = "大號床單套裝"
overall_simple_chain.run(product)
輸出:
> Entering new SimpleSequentialChain chain...
優(yōu)床制造公司
優(yōu)床制造公司是一家專注于生產(chǎn)高品質(zhì)床具的公司。
> Finished chain.
'優(yōu)床制造公司是一家專注于生產(chǎn)高品質(zhì)床具的公司。'
3.4、記憶(Meomory)
在 LangChain 中,記憶(Memory)指的是大語言模型(LLM)的短期記憶。
為什么是短期記憶?那是因為LLM訓(xùn)練好之后 (獲得了一些長期記憶),它的參數(shù)便不會因為用戶的輸入而發(fā)生改變。當(dāng)用戶與訓(xùn)練好的LLM進行對話時,LLM 會暫時記住用戶的輸入和它已經(jīng)生成的輸出,以便預(yù)測之后的輸出,而模型輸出完畢后,它便會“遺忘”之前用戶的輸入和它的輸出。因此,之前的這些信息只能稱作為 LLM 的短期記憶。
3.5、 代理(Agents)
大型語言模型(LLMs)非常強大,但它們?nèi)狈Α白畋俊钡挠嬎銠C程序可以輕松處理的特定能力。例如,無法準確回答簡單的計算問題,還有當(dāng)詢問最近發(fā)生的事件時,其回答也可能過時或錯誤,因為無法主動獲取最新信息。這是由于當(dāng)前語言模型僅依賴預(yù)訓(xùn)練數(shù)據(jù),與外界“斷開”。要克服這一缺陷, LangChain 框架提出了 “代理”( Agent ) 的解決方案。
代理作為語言模型的外部模塊,可提供計算、邏輯、檢索等功能的支持,使語言模型獲得異常強大的推理和獲取信息的超能力。
3.6、回調(diào)(Callback)
LangChain的回調(diào)系統(tǒng),允許連接到LLM應(yīng)用程序的各個階段。這對于日志記錄、監(jiān)視、流式處理和其他任務(wù)非常有用。
Callback 模塊扮演著記錄整個流程運行情況的角色,充當(dāng)類似于日志的功能。在每個關(guān)鍵節(jié)點,它記錄了相應(yīng)的信息,以便跟蹤整個應(yīng)用的運行情況。
Callback 模塊的具體實現(xiàn)包括兩個主要功能,對應(yīng)CallbackHandler 和 CallbackManager 的基類功能:
- CallbackHandler 用于記錄每個應(yīng)用場景(如 Agent、LLchain 或 Tool )的日志,它是單個日志處理器,主要記錄單個場景的完整日志信息。
- CallbackManager則封裝和管理所有的 CallbackHandler ,包括單個場景的處理器,也包括整個運行時鏈路的處理器。"
四、基于 LangChain 自定義 LLM
LangChain 為基于 LLM 開發(fā)自定義應(yīng)用提供了高效的開發(fā)框架,便于開發(fā)者迅速地激發(fā) LLM 的強大能力,搭建 LLM 應(yīng)用。LangChain 也同樣支持多種大模型,內(nèi)置了 OpenAI、LLAMA 等大模型的調(diào)用接口。但是,LangChain 并沒有內(nèi)置所有大模型,它通過允許用戶自定義 LLM 類型,來提供強大的可擴展性。
要實現(xiàn)自定義 LLM,需要定義一個自定義類繼承自 LangChain 的 LLM 基類,然后定義兩個函數(shù):① _call 方法,其接受一個字符串,并返回一個字符串,即模型的核心調(diào)用;② _identifying_params 方法,用于打印 LLM 信息。
4.1、導(dǎo)入所需的第三方庫
import json
import time
from typing import Any, List, Mapping, Optional, Dict, Union, Tuple
import requests
from langchain.callbacks.manager import CallbackManagerForLLMRun
from langchain.llms.base import LLM
from langchain.utils import get_from_dict_or_env
from pydantic import Field, model_validator
4.2、定義一個 get_access_token 方法來獲取 access_token
def get_access_token(api_key : str, secret_key : str):
"""
使用 API Key,Secret Key 獲取access_token,替換下列示例中的應(yīng)用API Key、應(yīng)用Secret Key
"""
# 指定網(wǎng)址
url = f"https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id={api_key}&client_secret={secret_key}"
# 設(shè)置 POST 訪問
payload = json.dumps("")
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
# 通過 POST 訪問獲取賬戶對應(yīng)的 access_token
response = requests.request("POST", url, headers=headers, data=payload)
return response.json().get("access_token")
4.3、定義一個繼承自 LLM 類的自定義 LLM 類:
# 繼承自 langchain.llms.base.LLM
class Wenxin_LLM(LLM):
# 原生接口地址
url = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/eb-instant"
# 默認選用 ERNIE-Bot-turbo 模型,即目前一般所說的百度文心大模型
model_name: str = Field(default="ERNIE-Bot-turbo", alias="model")
# 訪問時延上限
request_timeout: Optional[Union[float, Tuple[float, float]]] = None
# 溫度系數(shù)
temperature: float = 0.1
# API_Key
api_key: str = None
# Secret_Key
secret_key : str = None
# access_token
access_token: str = None
# 必備的可選參數(shù)
model_kwargs: Dict[str, Any] = Field(default_factory=dict)
4.4、實現(xiàn)一個初始化方法 init_access_token,當(dāng)模型的 access_token 為空時調(diào)用
def init_access_token(self):
if self.api_key != None and self.secret_key != None:
# 兩個 Key 均非空才可以獲取 access_token
try:
self.access_token = get_access_token(self.api_key, self.secret_key)
except Exception as e:
print(e)
print("獲取 access_token 失敗,請檢查 Key")
else:
print("API_Key 或 Secret_Key 為空,請檢查 Key")
4.5、實現(xiàn)核心的方法——調(diào)用模型 API
def _call(self, prompt : str, stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any):
# 除 prompt 參數(shù)外,其他參數(shù)并沒有被用到,但當(dāng)我們通過 LangChain 調(diào)用時會傳入這些參數(shù),因此必須設(shè)置
# 如果 access_token 為空,初始化 access_token
if self.access_token == None:
self.init_access_token()
# API 調(diào)用 url
url = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/eb-instant?access_token={}".format(self.access_token)
# 配置 POST 參數(shù)
payload = json.dumps({
"messages": [
{
"role": "user",# user prompt
"content": "{}".format(prompt)# 輸入的 prompt
}
],
'temperature' : self.temperature
})
headers = {
'Content-Type': 'application/json'
}
# 發(fā)起請求
response = requests.request("POST", url, headers=headers, data=payload, timeout=self.request_timeout)
if response.status_code == 200:
# 返回的是一個 Json 字符串
js = json.loads(response.text)
return js["result"]
else:
return "請求失敗"
4.6、定義模型的描述方法
# 首先定義一個返回默認參數(shù)的方法
@property
def _default_params(self) -> Dict[str, Any]:
"""獲取調(diào)用Ennie API的默認參數(shù)。"""
normal_params = {
"temperature": self.temperature,
"request_timeout": self.request_timeout,
}
return {**normal_params}
@property
def _identifying_params(self) -> Mapping[str, Any]:
"""Get the identifying parameters."""
return {**{"model_name": self.model_name}, **self._default_params}
五、將大模型 API 封裝成本地 API
我們可以使用 FastAPI,對不同的大模型 API 再進行一層封裝,將其映射到本地接口上,從而通過統(tǒng)一的方式來調(diào)用本地接口實現(xiàn)不同大模型的調(diào)用。通過這樣的手段,可以極大程度減少對于模型調(diào)用的工作量和復(fù)雜度。
以訊飛星火大模型 API 為例。
5.1、創(chuàng)建 api 對象
安裝 fastapi 第三方庫
! pip install fastapi
導(dǎo)入需要依賴包
from fastapi import FastAPI
from pydantic import BaseModel
import os
app = FastAPI() # 創(chuàng)建 api 對象
5.2、定義數(shù)據(jù)模型接收數(shù)據(jù)
本地 API 一般通過 POST 方式進行訪問,即參數(shù)會附加在 POST 請求中,我們需要定義一個數(shù)據(jù)模型來接收 POST 請求中的數(shù)據(jù):
# 定義一個數(shù)據(jù)模型,用于接收POST請求中的數(shù)據(jù)
class Item(BaseModel):
prompt : str # 用戶 prompt
temperature : float # 溫度系數(shù)
max_tokens : int # token 上限
if_list : bool = False # 是否多輪對話
數(shù)據(jù)模型中常用參數(shù):
· prompt:即用戶輸入的 Prompt。我們默認為單輪對話調(diào)用,因此 prompt 默認為一句輸入;如果將 if_list 設(shè)置為 True,那么就是多輪對話調(diào)用,prompt 應(yīng)為一個已構(gòu)造好(即有標(biāo)準 role、content 格式)的列表字符串
· temperature:溫度系數(shù)
· max_tokens:回答的最大 token 上限
· if_list:是否多輪對話,默認為 False
5.3、創(chuàng)建 POST 請求的 API 端點
@app.post("/spark/")
async def get_spark_response(item: Item):
# 實現(xiàn)星火大模型調(diào)用的 API 端點
response = get_spark(item)
return response
定義一個函數(shù)來實現(xiàn)對星火 API 的調(diào)用
import SparkApiSelf
# 首先定義一個構(gòu)造參數(shù)函數(shù)
def getText(role, content, text = []):
# role 是指定角色,content 是 prompt 內(nèi)容
jsoncon = {}
jsoncon["role"] = role
jsoncon["content"] = content
text.append(jsoncon)
return text
def get_spark(item):
# 配置 spark 秘鑰
#以下密鑰信息從控制臺獲取
appid = "9f922c84" #填寫控制臺中獲取的 APPID 信息
api_secret = "YjU0ODk4MWQ4NTgyNDU5MzNiNWQzZmZm" #填寫控制臺中獲取的 APISecret 信息
api_key ="5d4e6e41f6453936ccc34dd524904324" #填寫控制臺中獲取的 APIKey 信息
domain = "generalv2" # v2.0版本
Spark_url = "ws://spark-api.xf-yun.com/v2.1/chat" # v2.0環(huán)境的地址
# 構(gòu)造請求參數(shù)
if item.if_list:
prompt = item.prompt
else:
prompt = getText("user", item.prompt)
response = SparkApiSelf.main(appid,api_key,api_secret,Spark_url,domain,prompt, item.temperature, item.max_tokens)
return response
注意,由于星火給出的示例 SparkApi 中將 temperature、max_tokens 都進行了封裝,我們需要對示例代碼進行改寫,暴露出這兩個參數(shù)接口,我們實現(xiàn)了一個新的文件 SparkApiSelf,對其中的改動如下:
首先,我們對參數(shù)類中新增了 temperature、max_tokens 兩個屬性:
class Ws_Param(object):
# 初始化
def __init__(self, APPID, APIKey, APISecret, Spark_url):
self.APPID = APPID
self.APIKey = APIKey
self.APISecret = APISecret
self.host = urlparse(Spark_url).netloc
self.path = urlparse(Spark_url).path
self.Spark_url = Spark_url
# 自定義
self.temperature = 0
self.max_tokens = 2048
然后在生成請求參數(shù)的函數(shù)中,增加這兩個參數(shù)并在構(gòu)造請求數(shù)據(jù)時加入?yún)?shù):
def gen_params(appid, domain,question, temperature, max_tokens):
"""
通過appid和用戶的提問來生成請參數(shù)
"""
data = {
"header": {
"app_id": appid,
"uid": "1234"
},
"parameter": {
"chat": {
"domain": domain,
"random_threshold": 0.5,
"max_tokens": max_tokens,
"temperature" : temperature,
"auditing": "default"
}
},
"payload": {
"message": {
"text": question
}
}
}
return data
在 run 函數(shù)中調(diào)用生成參數(shù)時加入這兩個參數(shù):
def run(ws, *args):
data = json.dumps(gen_params(appid=ws.appid, domain= ws.domain,question=ws.question, temperature = ws.temperature, max_tokens = ws.max_tokens))
ws.send(data)
由于 WebSocket 是直接打印到終端,但我們需要將最后的結(jié)果返回給用戶,我們需要修改 main 函數(shù),使用一個隊列來裝填星火流式輸出產(chǎn)生的結(jié)果,并最終集成返回給用戶:
def main(appid, api_key, api_secret, Spark_url,domain, question, temperature, max_tokens):
# print("星火:")
output_queue = queue.Queue()
def on_message(ws, message):
data = json.loads(message)
code = data['header']['code']
if code != 0:
print(f'請求錯誤: {code}, {data}')
ws.close()
else:
choices = data["payload"]["choices"]
status = choices["status"]
content = choices["text"][0]["content"]
# print(content, end='')
# 將輸出值放入隊列
output_queue.put(content)
if status == 2:
ws.close()
wsParam = Ws_Param(appid, api_key, api_secret, Spark_url)
websocket.enableTrace(False)
wsUrl = wsParam.create_url()
ws = websocket.WebSocketApp(wsUrl, on_message=on_message, on_error=on_error, on_close=on_close, on_open=on_open)
ws.appid = appid
ws.question = question
ws.domain = domain
ws.temperature = temperature
ws.max_tokens = max_tokens
ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
return ''.join([output_queue.get() for _ in range(output_queue.qsize())])
API 封裝完成。
六、基于 LangChain 自定義 Embeddings
LangChain 為基于 LLM 開發(fā)自定義應(yīng)用提供了高效的開發(fā)框架,便于開發(fā)者迅速地激發(fā) LLM 的強大能力,搭建 LLM 應(yīng)用。LangChain 也同樣支持多種大模型的 Embeddings,內(nèi)置了 OpenAI、LLAMA 等大模型 Embeddings 的調(diào)用接口。但是,LangChain 并沒有內(nèi)置所有大模型,它通過允許用戶自定義 Embeddings 類型,來提供強大的可擴展性。
要實現(xiàn)自定義 Embeddings,需要定義一個自定義類繼承自 LangChain 的 Embeddings 基類,然后定義三個函數(shù):① _embed 方法,其接受一個字符串,并返回一個存放 Embeddings 的 List[float],即模型的核心調(diào)用;② embed_query 方法,用于對單個字符串(query)進行 embedding。③ embed_documents 方法,用于對字符串列表(documents)進行 embedding。
6.1、導(dǎo)入所需的第三方庫
from __future__ import annotations
import logging
from typing import Any, Dict, List, Optional
from langchain.embeddings.base import Embeddings
from langchain.pydantic_v1 import BaseModel, root_validator
from langchain.utils import get_from_dict_or_env
6.2、定義一個繼承自 Embeddings 類的自定義 Embeddings 類
class ZhipuAIEmbeddings(BaseModel, Embeddings):
"""`Zhipuai Embeddings` embedding models."""
zhipuai_api_key: Optional[str] = None
"""Zhipuai application apikey"""
在 Python 中,root_validator 是 Pydantic 模塊中一個用于自定義數(shù)據(jù)校驗的裝飾器函數(shù)。root_validator 用于在校驗整個數(shù)據(jù)模型之前對整個數(shù)據(jù)模型進行自定義校驗,以確保所有的數(shù)據(jù)都符合所期望的數(shù)據(jù)結(jié)構(gòu)。
root_validator 接收一個函數(shù)作為參數(shù),該函數(shù)包含需要校驗的邏輯。函數(shù)應(yīng)該返回一個字典,其中包含經(jīng)過校驗的數(shù)據(jù)。如果校驗失敗,則拋出一個 ValueError 異常。
裝飾器 root_validator 確保導(dǎo)入了相關(guān)的包和并配置了相關(guān)的 API_Key 這里取巧,在確保導(dǎo)入 zhipuai model 后直接將zhipuai.model_api綁定到 cliet 上,減少和其他 Embeddings 類的差異。
values["client"] = zhipuai.model_api
@root_validator()
def validate_environment(cls, values: Dict) -> Dict:
"""
驗證環(huán)境變量或配置文件中的zhipuai_api_key是否可用。
Args:
values (Dict): 包含配置信息的字典,必須包含 zhipuai_api_key 的字段
Returns:
values (Dict): 包含配置信息的字典。如果環(huán)境變量或配置文件中未提供 zhipuai_api_key,則將返回原始值;否則將返回包含 zhipuai_api_key 的值。
Raises:
ValueError: zhipuai package not found, please install it with `pip install
zhipuai`
"""
values["zhipuai_api_key"] = get_from_dict_or_env(
values,
"zhipuai_api_key",
"ZHIPUAI_API_KEY",
)
try:
import zhipuai
zhipuai.api_key = values["zhipuai_api_key"]
values["client"] = zhipuai.model_api
except ImportError:
raise ValueError(
"Zhipuai package not found, please install it with "
"`pip install zhipuai`"
)
return values
6.3、重寫 _embed 方法,調(diào)用遠程 API 并解析 embedding 結(jié)果
def _embed(self, texts: str) -> List[float]:
"""
生成輸入文本的 embedding。
Args:
texts (str): 要生成 embedding 的文本。
Return:
embeddings (List[float]): 輸入文本的 embedding,一個浮點數(shù)值列表。
"""
try:
resp = self.client.invoke(
model="text_embedding",
prompt=texts
)
except Exception as e:
raise ValueError(f"Error raised by inference endpoint: {e}")
if resp["code"] != 200:
raise ValueError(
"Error raised by inference API HTTP code: %s, %s"
% (resp["code"], resp["msg"])
)
embeddings = resp["data"]["embedding"]
return embeddings
6.4、重寫 embed_documents 方法
因為這里 _embed 已經(jīng)定義好了,可以直接傳入文本并返回結(jié)果即可。
def embed_documents(self, texts: List[str]) -> List[List[float]]:
"""
生成輸入文本列表的 embedding。
Args:
texts (List[str]): 要生成 embedding 的文本列表.
Returns:
List[List[float]]: 輸入列表中每個文檔的 embedding 列表。每個 embedding 都表示為一個浮點值列表。
"""
return [self._embed(text) for text in texts]
embed_query 是對單個文本計算 embedding 的方法,因為我們已經(jīng)定義好對文檔列表計算 embedding 的方法embed_documents 了,這里可以直接將單個文本組裝成 list 的形式傳給 embed_documents。
def embed_query(self, text: str) -> List[float]:
"""
生成輸入文本的 embedding。
Args:
text (str): 要生成 embedding 的文本。
Return:
List [float]: 輸入文本的 embedding,一個浮點數(shù)值列表。
"""
resp = self.embed_documents([text])
return resp[0]
什么要先定義embed_documents再用 embed_query 調(diào)用呢,不返過來呢,其實也是可以的,embed_query 單獨請求也是可以的。
對于 embed_documents 可以加入一些內(nèi)容處理后再請求 embedding,如果文檔特別長,可以考慮對文檔分段,防止超過最大 token 限制。