突破LLM的token限制:多塊上下文保留的實用系統(tǒng)(含code)
大語言模型(LLMs)取得了令人矚目的進(jìn)展,已廣泛應(yīng)用于文本生成、翻譯、問答等諸多場景。然而,LLMs存在的一些局限性,如有限的上下文窗口(令牌限制)和缺乏長期記憶,限制了其在處理復(fù)雜任務(wù)時的表現(xiàn)。本文將深入探討一種實用的解決方案,旨在克服這些限制,提升LLMs的性能。
一、大語言模型概述
LLMs是基于Transformer架構(gòu)構(gòu)建的深度學(xué)習(xí)模型,通過在海量文本數(shù)據(jù)集上進(jìn)行訓(xùn)練來學(xué)習(xí)語言模式和知識。Transformer模型的核心機(jī)制是自注意力機(jī)制,它能夠讓模型在無需人工監(jiān)督的情況下,自動學(xué)習(xí)輸入文本中各部分之間的相關(guān)性。在處理文本時,LLMs會將文本拆分為詞元(token),這些詞元可以是子詞或字符,隨后將其輸入到Transformer模型中進(jìn)行處理,最終輸出每個詞元的嵌入表示,用于各種自然語言處理任務(wù)。
在實際應(yīng)用中,GPT-4以其廣泛的通用性、出色的指令遵循能力和強(qiáng)大的代碼生成能力而聞名;Claude注重安全性和對話應(yīng)用場景;Gemini旨在實現(xiàn)推理和多模態(tài)應(yīng)用;LLaMA作為開源權(quán)重的大語言模型,在研究和微調(diào)方面應(yīng)用廣泛。這些模型雖然功能強(qiáng)大,但仍面臨一些挑戰(zhàn)。
二、大語言模型的局限性
(一)長期記憶和個性化問題
大多數(shù)LLMs在默認(rèn)情況下是無狀態(tài)的,它們不會自動記住過去的對話內(nèi)容。除非專門為其設(shè)計記憶機(jī)制,否則每個輸入提示(prompt)都會被獨立處理。這意味著在連續(xù)對話場景中,模型無法利用之前的交互信息,導(dǎo)致對話缺乏連貫性和上下文感知能力。例如,在多輪問答中,用戶詢問了一系列相關(guān)問題,但模型無法結(jié)合之前的回答進(jìn)行更準(zhǔn)確、更連貫的回應(yīng)。
(二)知識局限性
基于靜態(tài)數(shù)據(jù)集訓(xùn)練的LLMs,無法獲取當(dāng)前事件或?qū)崟r數(shù)據(jù)。如果沒有與網(wǎng)絡(luò)搜索工具或API集成,模型的知識將局限于訓(xùn)練數(shù)據(jù)的時間范圍,對于新出現(xiàn)的信息無法知曉。比如,當(dāng)詢問關(guān)于最新科技成果或時事新聞時,模型可能會給出過時的答案。
(三)上下文窗口限制(提示大小約束)
每個LLM都有一個固定的上下文窗口,即一次能夠處理的最大詞元數(shù)量。雖然一些先進(jìn)的模型能夠支持多達(dá)100萬個詞元,但在實際應(yīng)用中,這個限制仍然會對處理大型代碼庫或長篇文檔造成阻礙。此外,HTTP請求的大小限制也會進(jìn)一步約束發(fā)送給模型的有效負(fù)載,導(dǎo)致無法一次性將全部輸入內(nèi)容發(fā)送給模型進(jìn)行處理。
三、解決令牌限制和長期記憶問題的方法
(一)核心思路
為了解決缺乏長期記憶和有限提示窗口這兩個關(guān)鍵問題,一種有效的方法是將提示詞元進(jìn)行結(jié)構(gòu)化處理,分為用戶輸入(用戶給出的任務(wù))和上下文窗口(相關(guān)的支持?jǐn)?shù)據(jù)片段)。具體而言,就是將分配給用戶輸入的總詞元劃分為分塊提示詞元和上下文詞元。由于模型的詞元限制,需要將大型輸入(如完整的代碼庫或長篇文檔)拆分成較小的塊,然后按順序處理這些塊,并將每個塊的響應(yīng)摘要作為下一個塊的上下文。通過這種方式,既可以模擬長期記憶,又能夠突破提示窗口的限制。
(二)具體實現(xiàn)步驟
- 拆分用戶提示:首先,將用戶的輸入文本按單詞進(jìn)行拆分。然后,依次遍歷每個單詞,將其添加到當(dāng)前塊中。當(dāng)當(dāng)前塊的大小達(dá)到為單個提示設(shè)定的詞元限制(由開發(fā)者自行定義)時,將該塊確定為可執(zhí)行的塊,并將其添加到塊列表中。接著,清空當(dāng)前塊,繼續(xù)添加下一個單詞,重復(fù)上述過程,直到整個提示被拆分成合適大小的塊。例如,使用Python代碼實現(xiàn)如下:
def split_text_into_chunks(self, text, max_chunk_tokens=2000):
words = text.split()
chunks = []
current_chunk = []
for word in words:
current_chunk.append(word)
current_text = " ".join(current_chunk)
if self.num_tokens(current_text) > max_chunk_tokens:
current_chunk.pop()
chunks.append(" ".join(current_chunk))
current_chunk = [word]
if current_chunk:
chunks.append(" ".join(current_chunk))
return chunks
- 處理大型提示:在拆分用戶提示后,初始化一個空的overall_summarized_context,用于保存所有先前響應(yīng)的單一摘要。對于每個分塊,將當(dāng)前的overall_summarized_context附加到該分塊上,并發(fā)送給LLM以生成響應(yīng)。然后,使用LLM對響應(yīng)進(jìn)行摘要(如果需要),并更新overall_summarized_context,使其包含最新響應(yīng)的上下文。重復(fù)這個過程,直到處理完所有分塊。最后,將所有的響應(yīng)合并成一個最終的統(tǒng)一響應(yīng),并返回給用戶。以下是Python代碼示例:
def process_large_prompt(self, user_prompt):
chunks = self.split_text_into_chunks(user_prompt)
running_context = []
overall_summarized_context = ""
results = []
for i, chunk in enumerate(chunks):
prompt = f"""
You are reading a large document split into chunks.
Here is chunk {i+1}/{len(chunks)}:
{chunk}
Previous context:
{overall_summarized_context}
Please analyze this chunk in light of the previous context.
"""
messages = [
{"role": "system", "content": "You are a smart assistant that reads the provided software code, and provide the detailed explanation of what business purpose the code is providing and how different components are connected, and interacting with each other."},
{"role": "user", "content": prompt}
]
response = self.chat(messages, temperature=0.3, max_tokens=1000)
results.append(response.strip())
context_messages = [
{"role": "system", "content": "You are a smart assistant that summarizes the given content below within 1000 words so that it can be used as a context for remaining analysis."},
{"role": "user", "content": response.strip()}
]
overall_summarized_context = self.chat(context_messages, temperature=0.3, max_tokens=1000)
running_context.append(overall_summarized_context)
return {
"final_summary": overall_summarized_context,
"individual_context_summaries": running_context,
"individual_results": results,
}
- 完整代碼可在GitHub上獲?。篽ttps://github.com/akshit04/PromptSlicerLlmWrapper。用戶可以按照README.md文件中的設(shè)置說明進(jìn)行操作,通過配置GitHub倉庫URL和OpenRouter API密鑰,將任何GitHub倉庫作為OpenRouter LLM的提示源。
四、關(guān)鍵考慮因素
(一)示例提示的GitHub項目
在實際測試中,使用了https://github.com/akshit04/StackOverflow 這個GitHub項目作為示例提示。該項目是一個基本的StackOverflow風(fēng)格的項目,允許用戶注冊、提問、提交答案以及對現(xiàn)有答案進(jìn)行點贊或點踩。為了確保測試的公正性,故意從README文件中排除了項目描述,這樣最終的摘要就能真正反映模型從代碼塊和運行上下文中構(gòu)建的理解,而不是簡單地從項目描述中進(jìn)行總結(jié)。
(二)單個用戶提示塊的最大詞元限制
在本示例中,為單個用戶提示塊設(shè)置的最大詞元限制為2000。需要注意的是,這個限制僅適用于用戶提示,不包括上下文詞元,發(fā)送給LLM的總提示詞元數(shù)為用戶提示詞元數(shù)加上上下文詞元數(shù)。這個2000的詞元限制可以在openrouter_wrapper.py文件中進(jìn)行配置。由于本示例只是一個概念驗證(POC),故意設(shè)置了較低的詞元限制,相較于大多數(shù)LLMs通常支持的數(shù)量要低很多。這樣做的目的是在小規(guī)模項目上更便于手動分析結(jié)果的質(zhì)量。
五、結(jié)果分析
通過對代碼的試運行,得到了相應(yīng)的結(jié)果,并將其中一個示例響應(yīng)直接包含在倉庫中(response1.md)。該文件主要分為三個部分:
(一)單個結(jié)果
這部分包含了發(fā)送給LLM的每個請求的原始輸出,包括對單個提示塊的部分響應(yīng),以及在每個步驟中使用的整體總結(jié)上下文。通過這些原始輸出,可以清晰地看到模型對每個分塊的具體處理結(jié)果。
(二)單個上下文摘要
在每次LLM響應(yīng)后,都會生成一個摘要以捕獲關(guān)鍵點。這些摘要隨著時間的推移不斷積累上下文信息,因為每個響應(yīng)都是在訪問運行上下文的情況下生成的,它們有效地將從第一個提示中獲得的知識傳遞到當(dāng)前提示。這種上下文的積累使得模型能夠更好地理解整個輸入的連貫性和相關(guān)性。
(三)最終摘要
這是通過組合單個上下文摘要而創(chuàng)建的綜合摘要,等同于上一部分中提到的最后一個上下文摘要,代表了模型對所有分塊處理后的最終理解。以測試的項目代碼為例,最終摘要準(zhǔn)確地總結(jié)出代碼是一個使用Ruby on Rails構(gòu)建的Web應(yīng)用程序結(jié)構(gòu),涵蓋了用戶管理、問答發(fā)布和用戶關(guān)系等功能模塊,以及各個模塊的具體實現(xiàn)細(xì)節(jié)和它們之間的關(guān)聯(lián)。盡管是通過分塊處理的方式逐步構(gòu)建的,但最終摘要有效地捕捉了整個項目的上下文信息。
六、局限性與未來展望
(一)當(dāng)前局限性
雖然這種多塊上下文保留的方法在一定程度上克服了LLMs的令牌限制和長期記憶問題,但也存在一些局限性。由于將初始用戶輸入拆分成多個塊,并對每個塊和運行上下文多次調(diào)用LLM,多個請求會導(dǎo)致總處理時間增加。此外,當(dāng)前代碼假設(shè)輸入提示是來自GitHub的代碼庫,如果要支持其他輸入類型,如語音、圖像或純文本,需要對代碼進(jìn)行修改。不過,這只是實現(xiàn)層面的局限性,而非方法本身的問題。
(二)未來展望
在未來的研究和開發(fā)中,可以進(jìn)一步優(yōu)化算法,減少請求次數(shù)或提高每次請求的效率,以降低處理時間。例如,可以探索更智能的分塊策略,根據(jù)文本的語義結(jié)構(gòu)進(jìn)行分塊,而不僅僅是基于詞元數(shù)量;或者開發(fā)更高效的上下文摘要算法,減少不必要的信息傳遞。同時,針對不同輸入類型的支持?jǐn)U展也將是一個重要的研究方向,通過開發(fā)通用的預(yù)處理模塊,將各種類型的輸入轉(zhuǎn)換為適合LLMs處理的格式,從而擴(kuò)大該方法的應(yīng)用范圍。此外,結(jié)合其他技術(shù),如知識圖譜、外部知識庫等,與多塊上下文保留方法相結(jié)合,有望進(jìn)一步提升LLMs的性能和智能水平,使其能夠更好地應(yīng)對復(fù)雜多樣的自然語言處理任務(wù)。