整合 Milvus 與 DSPy
什麼是 DSPy
DSPy 由 Stanford NLP Group 推出,是一個突破性的程式框架,專門用來優化語言模型內的提示和權重,尤其是在大型語言模型 (LLM) 整合於管道多個階段的情況下,更顯重要。DSPy 採用了以學習為基礎的方法,有別於依賴手動製作和調整的傳統提示工程技術。透過吸收問答範例,DSPy 可針對特定任務動態產生最佳化的提示。這種創新的方法能夠無縫地重新組合整個管道,省去持續手動調整提示的需要。DSPy 的 Pythonic 語法提供各種可組合與宣告式模組,簡化了 LLM 的指令。
使用 DSPy 的優點
- 程式設計方法:DSPy 將管道抽象為文字轉換圖形,而不只是提示 LLM,為 LM 管道開發提供了系統化的程式設計方法。其宣告式模組可進行結構化設計與最佳化,取代傳統提示範本的試誤法。
- 效能提升:DSPy 證實比現有方法有顯著的效能提升。透過案例研究,它的表現優於標準提示和專家建立的示範,即使編譯成較小的 LM 模型,也能展示其多功能性和有效性。
- 模組化抽象:DSPy 有效地抽象出 LM 管線開發的複雜層面,例如分解、微調與模型選擇。有了 DSPy,一個簡潔的程式可以無縫地轉換成各種模型的指令,例如 GPT-4、Llama2-13b 或 T5-base,從而簡化開發過程並提昇效能。
模組
建構 LLM 管道有許多元件。在此,我們將介紹一些關鍵元件,以提供對 DSPy 運作方式的高層次瞭解。
DSPy 模組
簽名:DSPy 中的簽章 (Signature) 是宣告性的規格,概述模組的輸入/輸出行為,在執行任務時引導語言模型。 模組 (Module):DSPy 模組是程式利用語言模型 (LM) 的基本元件。它們抽象出各種提示技術,例如連鎖思考或 ReAct,並可適應處理任何 DSPy Signature。這些模組具有可學習的參數,以及處理輸入和產生輸出的能力,可以結合成更大的程式,其靈感來自 PyTorch 中的 NN 模組,但專為 LM 應用程式量身打造。 優化器:DSPy 中的優化器可微調 DSPy 程式的參數,例如提示和 LLM 權重,以最大化指定的準確度等指標,進而提升程式效率。
為什麼在 DSPy 中使用 Milvus
DSPy 是一個強大的程式設計框架,可提升 RAG 應用程式。這類應用程式需要擷取有用的資訊來提升答案品質,而這需要向量資料庫。Milvus 是知名的開放原始碼向量資料庫,可提升效能與擴充性。有了 DSPy 中的retriever 模組 MilvusRM,整合 Milvus 變得無縫。現在,開發人員可以利用 Milvus 強大的向量搜尋功能,使用 DSPy 輕鬆定義和優化 RAG 程式。這項合作結合了 DSPy 的程式設計能力與 Milvus 的搜尋功能,讓 RAG 應用程式更有效率且更具擴充性。
範例
現在,讓我們以一個快速的範例來說明如何在 DSPy 中利用 Milvus 來優化 RAG 應用程式。
先決條件
在建立 RAG 應用程式之前,先安裝 DSPy 和 PyMilvus。
$ pip install "dspy-ai[milvus]"
$ pip install -U pymilvus
載入資料集
在本範例中,我們使用 HotPotQA 這個複雜問題-答案對的集合作為訓練資料集。我們可以透過 HotPotQA 類載入它們。
from dspy.datasets import HotPotQA
# Load the dataset.
dataset = HotPotQA(
train_seed=1, train_size=20, eval_seed=2023, dev_size=50, test_size=0
)
# Tell DSPy that the 'question' field is the input. Any other fields are labels and/or metadata.
trainset = [x.with_inputs("question") for x in dataset.train]
devset = [x.with_inputs("question") for x in dataset.dev]
將資料擷取至 Milvus 向量資料庫
將上下文資訊擷取至 Milvus 資料庫,以便進行向量檢索。這個集合應該有embedding
欄位和text
欄位。在這種情況下,我們使用 OpenAI 的text-embedding-3-small
模型作為預設的查詢嵌入函式。
import requests
import os
os.environ["OPENAI_API_KEY"] = "<YOUR_OPENAI_API_KEY>"
MILVUS_URI = "example.db"
MILVUS_TOKEN = ""
from pymilvus import MilvusClient, DataType, Collection
from dspy.retrieve.milvus_rm import openai_embedding_function
client = MilvusClient(uri=MILVUS_URI, token=MILVUS_TOKEN)
if "dspy_example" not in client.list_collections():
client.create_collection(
collection_name="dspy_example",
overwrite=True,
dimension=1536,
primary_field_name="id",
vector_field_name="embedding",
id_type="int",
metric_type="IP",
max_length=65535,
enable_dynamic=True,
)
text = requests.get(
"https://raw.githubusercontent.com/wxywb/dspy_dataset_sample/master/sample_data.txt"
).text
for idx, passage in enumerate(text.split("\n")):
if len(passage) == 0:
continue
client.insert(
collection_name="dspy_example",
data=[
{
"id": idx,
"embedding": openai_embedding_function(passage)[0],
"text": passage,
}
],
)
定義 MilvusRM。
現在,您需要定義 MilvusRM。
from dspy.retrieve.milvus_rm import MilvusRM
import dspy
retriever_model = MilvusRM(
collection_name="dspy_example",
uri=MILVUS_URI,
token=MILVUS_TOKEN, # ignore this if no token is required for Milvus connection
embedding_function=openai_embedding_function,
)
turbo = dspy.OpenAI(model="gpt-3.5-turbo")
dspy.settings.configure(lm=turbo)
建立簽章
現在我們已經載入資料,讓我們開始定義管道中子任務的簽章。我們可以定義我們簡單的輸入question
和輸出answer
,但因為我們正在建立一個 RAG 管道,所以我們會從 Milvus 擷取上下文資訊。因此,讓我們定義我們的簽章為context, question --> answer
。
class GenerateAnswer(dspy.Signature):
"""Answer questions with short factoid answers."""
context = dspy.InputField(desc="may contain relevant facts")
question = dspy.InputField()
answer = dspy.OutputField(desc="often between 1 and 5 words")
我們包含context
和answer
欄位的簡短說明,以定義更清楚的指引,說明模型將會接收什麼,應該產生什麼。
建立管道
現在,讓我們定義 RAG 管道。
class RAG(dspy.Module):
def __init__(self, rm):
super().__init__()
self.retrieve = rm
# This signature indicates the task imposed on the COT module.
self.generate_answer = dspy.ChainOfThought(GenerateAnswer)
def forward(self, question):
# Use milvus_rm to retrieve context for the question.
context = self.retrieve(question).passages
# COT module takes "context, query" and output "answer".
prediction = self.generate_answer(context=context, question=question)
return dspy.Prediction(
context=[item.long_text for item in context], answer=prediction.answer
)
執行管道並取得結果
現在,我們已經建立了 RAG 管道。讓我們試試並得到結果。
rag = RAG(retriever_model)
print(rag("who write At My Window").answer)
Townes Van Zandt
我們可以評估資料集的量化結果。
from dspy.evaluate.evaluate import Evaluate
from dspy.datasets import HotPotQA
evaluate_on_hotpotqa = Evaluate(
devset=devset, num_threads=1, display_progress=False, display_table=5
)
metric = dspy.evaluate.answer_exact_match
score = evaluate_on_hotpotqa(rag, metric=metric)
print("rag:", score)
優化管道
定義這個程式之後,下一步就是編譯。這個過程會更新每個模組內的參數,以提升效能。編譯過程取決於三個關鍵因素:
- 訓練集:我們會利用訓練資料集中的 20 個問答範例來進行示範。
- 驗證指標:我們會建立一個簡單的
validate_context_and_answer
公制。此標準可驗證預測答案的準確性,並確保擷取的上下文包含該答案。 - 特定優化器 (提詞器):DSPy 的編譯器結合了多個 teleprompter,旨在有效優化您的程式。
from dspy.teleprompt import BootstrapFewShot
# Validation logic: check that the predicted answer is correct.# Also check that the retrieved context does contain that answer.
def validate_context_and_answer(example, pred, trace=None):
answer_EM = dspy.evaluate.answer_exact_match(example, pred)
answer_PM = dspy.evaluate.answer_passage_match(example, pred)
return answer_EM and answer_PM
# Set up a basic teleprompter, which will compile our RAG program.
teleprompter = BootstrapFewShot(metric=validate_context_and_answer)
# Compile!
compiled_rag = teleprompter.compile(rag, trainset=trainset)
# Now compiled_rag is optimized and ready to answer your new question!
# Now, let’s evaluate the compiled RAG program.
score = evaluate_on_hotpotqa(compiled_rag, metric=metric)
print(score)
print("compile_rag:", score)
Ragas 分數從之前的 50.0 增加到 52.0,顯示答案品質有所提升。
總結
DSPy 標誌著語言模型互動的一大躍進,透過其可編程介面,有助於演算法和自動最佳化模型提示和權重。利用 DSPy 實作 RAG,可輕鬆適應不同的語言模型或資料集,大幅減少繁瑣的手動介入。