LangChain實戰

1.概述

最近,在研究LangChain時,發現一些比較有意思的點,今天筆者將給大家分享關於LangChain的一些內容。

2.內容

2.1 什麼是LangChain?

LangChain是一項旨在賦能開發人員利用語言模型構建端到端應用程序的強大框架。它的設計理念在於簡化和加速利用大型語言模型(LLM)和對話模型構建應用程序的過程。這個框架提供了一套全面的工具、組件和接口,旨在簡化基於大型語言模型和對話模型的應用程序開發過程。

LangChain本質上可被視爲類似於開源GPT的插件。它不僅提供了豐富的大型語言模型工具,還支持在開源模型的基礎上快速增強模型的功能。通過LangChain,開發人員可以更輕鬆地管理與語言模型的交互,無縫地連接多個組件,並集成額外的資源,如API和數據庫等,以加強應用的能力和靈活性。

LangChain的優勢在於爲開發人員提供了一個便捷的框架,使其能夠更高效地構建應用程序,利用最先進的語言模型和對話系統,同時能夠靈活地定製和整合其他必要的資源,爲應用程序的開發和部署提供了更大的靈活性和便利性。

2.2 LangChain框架組成?

 1.Model

任何語言模型應用的核心要素是…… 模型。LangChain爲您提供了與任何語言模型進行接口的基本構件。

  • 提示:模板化、動態選擇和管理模型輸入
  • 語言模型:通過通用接口調用語言模型
  • 輸出解析器:從模型輸出中提取信息

此外,LangChain還提供了額外的關鍵功能,如:

  • 模型適配器:通過適配器將不同類型的語言模型無縫整合到應用程序中。
  • 模型優化器:優化和調整模型以提高性能和效率。
  • 自定義組件:允許開發人員根據特定需求創建和整合自定義組件,以擴展和改進模型的功能。

 2.Retrieval

 許多大型語言模型(LLM)應用需要用戶特定的數據,這些數據並不屬於模型的訓練集。實現這一點的主要方法是通過檢索增強生成(RAG)。在這個過程中,會檢索外部數據,然後在生成步驟中傳遞給LLM。

LangChain爲RAG應用提供了從簡單到複雜的所有構建模塊。文檔的此部分涵蓋與檢索步驟相關的所有內容,例如數據的獲取。儘管聽起來簡單,但實際上可能相當複雜。這包括幾個關鍵模塊。

文檔加載器

從許多不同來源加載文檔。LangChain提供超過100種不同的文檔加載器,並與其他主要提供者(如AirByte和Unstructured)進行集成。我們提供與各種位置(私人S3存儲桶、公共網站)的各種文檔類型(HTML、PDF、代碼)的加載集成。

文檔轉換器

檢索的關鍵部分是僅獲取文檔的相關部分。這涉及幾個轉換步驟,以最佳準備文檔進行檢索。其中一個主要步驟是將大型文檔分割(或分塊)爲較小的塊。LangChain提供了幾種不同的算法來執行此操作,同時針對特定文檔類型(代碼、Markdown等)進行了優化邏輯。

文本嵌入模型

檢索的另一個關鍵部分是爲文檔創建嵌入。嵌入捕獲文本的語義含義,使您能夠快速高效地查找其他類似的文本。LangChain與超過25種不同的嵌入提供者和方法進行集成,涵蓋了從開源到專有API的各種選擇,使您可以選擇最適合您需求的方式。LangChain提供了標準接口,使您能夠輕鬆切換模型。

向量存儲

隨着嵌入技術的興起,出現了對數據庫的需求,以支持這些嵌入的有效存儲和搜索。LangChain與超過50種不同的向量存儲進行集成,涵蓋了從開源本地存儲到雲託管專有存儲的各種選擇,使您可以選擇最適合您需求的方式。LangChain提供標準接口,使您能夠輕鬆切換向量存儲。

檢索器

一旦數據存儲在數據庫中,您仍需要檢索它。LangChain支持許多不同的檢索算法,這是我們增加最多價值的領域之一。我們支持易於入門的基本方法,即簡單的語義搜索。然而,我們還在此基礎上添加了一系列算法以提高性能。這些包括:

  • 父文檔檢索器:這允許您爲每個父文檔創建多個嵌入,使您能夠查找較小的塊,但返回更大的上下文。
  • 自查詢檢索器:用戶的問題通常包含對不僅僅是語義的某些邏輯的引用,而是可以最好表示爲元數據過濾器的邏輯。自查詢允許您從查詢中提取出語義部分以及查詢中存在的其他元數據過濾器。
  • 組合檢索器:有時您可能希望從多個不同來源或使用多種不同算法檢索文檔。組合檢索器可以輕鬆實現此操作。

3.Chains

單獨使用LLM適用於簡單的應用程序,但更復雜的應用程序需要將LLM鏈接起來,要麼彼此鏈接,要麼與其他組件鏈接。

LangChain提供了兩個高級框架來“鏈接”組件。傳統方法是使用Chain接口。更新的方法是使用LangChain表達語言(LCEL)。在構建新應用程序時,我們建議使用LCEL進行鏈式組合。但是,我們繼續支持許多有用的內置Chain,因此我們在這裏對兩個框架進行了文檔化。正如我們將在下面提到的,Chain本身也可以用於LCEL,因此兩者並不是互斥的。

新內容補充:
此外,LangChain還提供了其他關於鏈式組合的重要內容:

  • 模塊兼容性:LangChain旨在確保不同模塊之間的兼容性,使得可以無縫地將不同類型的LLMs和其他組件相互鏈接,以構建更加複雜和高效的應用程序。
  • 動態調整:框架允許動態調整不同模塊和LLMs之間的連接方式,從而使得應用程序的功能和性能得到靈活的調整和優化。
  • 可擴展性:LangChain提供了靈活的擴展機制,允許開發人員根據具體需求定製新的鏈接策略和模塊組合,以適應不同場景下的需求。

 LCEL最顯著的部分是它提供了直觀且易讀的組合語法。但更重要的是,它還提供了一流的支持。爲一個簡單且常見的例子,我們可以看到如何將提示(prompt)、模型和輸出解析器結合起來:

from langchain.chat_models import ChatAnthropic
from langchain.prompts import ChatPromptTemplate
from langchain.schema import StrOutputParser

model = ChatAnthropic()
prompt = ChatPromptTemplate.from_messages([
    ("system", "You're a very knowledgeable historian who provides accurate and eloquent answers to historical questions."),
    ("human", "{question}"),
])
runnable = prompt | model | StrOutputParser()

Chain 是“鏈式”應用程序的傳統接口。我們通常很泛化地定義 Chain 爲對組件的一系列調用,其中可能包括其他鏈。基本接口很簡單:

class Chain(BaseModel, ABC):
    """Base interface that all chains should implement."""

    memory: BaseMemory
    callbacks: Callbacks

    def __call__(
        self,
        inputs: Any,
        return_only_outputs: bool = False,
        callbacks: Callbacks = None,
    ) -> Dict[str, Any]:
        ...

4.Memory

在與模型交互時,用於存儲上下文狀態。模型本身不具備上下文記憶,因此在與模型交互時,需要傳遞聊天內容的上下文。

大多數大型語言模型應用具有會話式界面。對話的一個基本組成部分是能夠引用先前在對話中提到的信息。至少,會話系統應能直接訪問一定範圍內的先前消息。更復雜的系統需要具有一個持續更新的世界模型,使其能夠保持關於實體及其關係的信息。

我們稱存儲關於先前互動的信息的能力爲“記憶”。LangChain爲向系統添加記憶提供了許多實用工具。這些實用工具可以單獨使用,也可以無縫地整合到鏈中。

一個記憶系統需要支持兩種基本操作:讀取和寫入。要記住,每個鏈定義了一些核心執行邏輯,期望得到某些輸入。其中一些輸入直接來自用戶,但有些輸入可能來自記憶。在給定運行中,一個鏈將兩次與其記憶系統交互。

在接收到初始用戶輸入之後,但在執行核心邏輯之前,鏈將從其記憶系統中讀取並增補用戶輸入。
在執行核心邏輯後,但在返回答案之前,鏈將把當前運行的輸入和輸出寫入記憶,以便在將來的運行中引用。

 讓我們看一看如何在鏈中使用 ConversationBufferMemory。ConversationBufferMemory 是一種極其簡單的內存形式,它只是在緩衝區中保留了一系列聊天消息,並將其傳遞到提示模板中。

from langchain.memory import ConversationBufferMemory

memory = ConversationBufferMemory()
memory.chat_memory.add_user_message("hi!")
memory.chat_memory.add_ai_message("what's up?")

在鏈中使用記憶時,有一些關鍵概念需要理解。請注意,這裏我們介紹了適用於大多數記憶類型的一般概念。每種單獨的記憶類型可能都有其自己的參數和概念,需要理解。

通常情況下,鏈接收或返回多個輸入/輸出鍵。在這些情況下,我們如何知道要保存到聊天消息歷史記錄的鍵是哪些?這通常可以通過記憶類型的 input_key 和 output_key 參數來控制。這些參數默認爲 None - 如果只有一個輸入/輸出鍵,那麼會自動使用該鍵。但是,如果有多個輸入/輸出鍵,則必須明確指定要使用哪一個的名稱。最後,讓我們來看看如何在鏈中使用這個功能。我們將使用一個 LLMChain,並展示如何同時使用 LLM 和 ChatModel。

使用一個LLM:

from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
from langchain.memory import ConversationBufferMemory


llm = OpenAI(temperature=0)
# Notice that "chat_history" is present in the prompt template
template = """You are a nice chatbot having a conversation with a human.

Previous conversation:
{chat_history}

New human question: {question}
Response:"""
prompt = PromptTemplate.from_template(template)
# Notice that we need to align the `memory_key`
memory = ConversationBufferMemory(memory_key="chat_history")
conversation = LLMChain(
    llm=llm,
    prompt=prompt,
    verbose=True,
    memory=memory
)

使用一個ChatModel:

from langchain.chat_models import ChatOpenAI
from langchain.prompts import (
    ChatPromptTemplate,
    MessagesPlaceholder,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
)
from langchain.chains import LLMChain
from langchain.memory import ConversationBufferMemory


llm = ChatOpenAI()
prompt = ChatPromptTemplate(
    messages=[
        SystemMessagePromptTemplate.from_template(
            "You are a nice chatbot having a conversation with a human."
        ),
        # The `variable_name` here is what must align with memory
        MessagesPlaceholder(variable_name="chat_history"),
        HumanMessagePromptTemplate.from_template("{question}")
    ]
)
# Notice that we `return_messages=True` to fit into the MessagesPlaceholder
# Notice that `"chat_history"` aligns with the MessagesPlaceholder name.
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
conversation = LLMChain(
    llm=llm,
    prompt=prompt,
    verbose=True,
    memory=memory
)

5.Agents

Agents的核心理念是利用LLM選擇要採取的一系列行動。在鏈式結構中,行動序列是硬編碼的(在代碼中)。而在Agents中,語言模型被用作推理引擎,決定要採取哪些行動以及順序。

一些重要的術語(和模式)需要了解:

  • AgentAction:這是一個數據類,代表Agents應該執行的操作。它具有一個tool屬性(應該調用的工具的名稱)和一個tool_input屬性(該工具的輸入)。
  • AgentFinish:這是一個數據類,表示Agent已經完成並應該返回給用戶。它有一個return_values參數,這是要返回的字典。通常它只有一個鍵 - output - 是一個字符串,因此通常只返回這個鍵。
  • intermediate_steps:這些表示傳遞的先前Agent操作和對應的輸出。這些很重要,以便在將來的迭代中傳遞,以便Agent知道它已經完成了哪些工作。它的類型被定義爲List[Tuple[AgentAction, Any]]。請注意,observation目前以Any類型留下以保持最大的靈活性。在實踐中,這通常是一個字符串。

6.Callbacks

LangChain提供了回調系統,允許您連接到LLM應用程序的各個階段。這對於日誌記錄、監控、流處理和其他任務非常有用。

您可以通過使用API中始終可用的callbacks參數來訂閱這些事件。該參數是處理程序對象的列表,這些處理程序對象應該詳細實現下面描述的一個或多個方法。

CallbackHandlers 是實現 CallbackHandler 接口的對象,該接口爲可以訂閱的每個事件都有一個方法。當事件觸發時,CallbackManager 將在每個處理程序上調用適當的方法。

class BaseCallbackHandler:
    """Base callback handler that can be used to handle callbacks from langchain."""

    def on_llm_start(
        self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any
    ) -> Any:
        """Run when LLM starts running."""

    def on_chat_model_start(
        self, serialized: Dict[str, Any], messages: List[List[BaseMessage]], **kwargs: Any
    ) -> Any:
        """Run when Chat Model starts running."""

    def on_llm_new_token(self, token: str, **kwargs: Any) -> Any:
        """Run on new LLM token. Only available when streaming is enabled."""

    def on_llm_end(self, response: LLMResult, **kwargs: Any) -> Any:
        """Run when LLM ends running."""

    def on_llm_error(
        self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any
    ) -> Any:
        """Run when LLM errors."""

    def on_chain_start(
        self, serialized: Dict[str, Any], inputs: Dict[str, Any], **kwargs: Any
    ) -> Any:
        """Run when chain starts running."""

    def on_chain_end(self, outputs: Dict[str, Any], **kwargs: Any) -> Any:
        """Run when chain ends running."""

    def on_chain_error(
        self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any
    ) -> Any:
        """Run when chain errors."""

    def on_tool_start(
        self, serialized: Dict[str, Any], input_str: str, **kwargs: Any
    ) -> Any:
        """Run when tool starts running."""

    def on_tool_end(self, output: str, **kwargs: Any) -> Any:
        """Run when tool ends running."""

    def on_tool_error(
        self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any
    ) -> Any:
        """Run when tool errors."""

    def on_text(self, text: str, **kwargs: Any) -> Any:
        """Run on arbitrary text."""

    def on_agent_action(self, action: AgentAction, **kwargs: Any) -> Any:
        """Run on agent action."""

    def on_agent_finish(self, finish: AgentFinish, **kwargs: Any) -> Any:
        """Run on agent end."""

LangChain提供了一些內置處理程序,供開發者快速開始使用。這些處理程序位於 langchain/callbacks 模塊中。最基本的處理程序是 StdOutCallbackHandler,它簡單地將所有事件記錄到標準輸出(stdout)。

注意:當對象的 verbose 標誌設置爲 true 時,即使未顯式傳入,StdOutCallbackHandler 也會被調用。

from langchain.callbacks import StdOutCallbackHandler
from langchain.chains import LLMChain
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate

handler = StdOutCallbackHandler()
llm = OpenAI()
prompt = PromptTemplate.from_template("1 + {number} = ")

# Constructor callback: First, let's explicitly set the StdOutCallbackHandler when initializing our chain
chain = LLMChain(llm=llm, prompt=prompt, callbacks=[handler])
chain.run(number=2)

# Use verbose flag: Then, let's use the `verbose` flag to achieve the same result
chain = LLMChain(llm=llm, prompt=prompt, verbose=True)
chain.run(number=2)

# Request callbacks: Finally, let's use the request `callbacks` to achieve the same result
chain = LLMChain(llm=llm, prompt=prompt)
chain.run(number=2, callbacks=[handler])

結果如下:

    > Entering new LLMChain chain...
    Prompt after formatting:
    1 + 2 =

    > Finished chain.


    > Entering new LLMChain chain...
    Prompt after formatting:
    1 + 2 =

    > Finished chain.


    > Entering new LLMChain chain...
    Prompt after formatting:
    1 + 2 =

    > Finished chain.


    '\n\n3'

3.快速使用

3.1 環境設置

使用LangChain通常需要與一個或多個模型提供商、數據存儲、API等進行集成。在本示例中,我們將使用OpenAI的模型API。

首先,我們需要安裝他們的Python包:

pip install langchain
pip install openai

在初始化OpenAI LLM類時直接通過名爲 openai_api_key 的參數傳遞密鑰:

from langchain.llms import OpenAI

llm = OpenAI(openai_api_key="...")

3.2 LLM

在LangChain中有兩種類型的語言模型,分別被稱爲:

  • LLMs:這是一個以字符串作爲輸入並返回字符串的語言模型。
  • ChatModels:這是一個以消息列表作爲輸入並返回消息的語言模型。

LLMs的輸入/輸出簡單易懂 - 字符串。但ChatModels呢?那裏的輸入是一個ChatMessages列表,輸出是一個單獨的ChatMessage。ChatMessage有兩個必需的組成部分:

  • content:這是消息的內容。
  • role:這是產生ChatMessage的實體的角色。

LangChain提供了幾個對象來輕鬆區分不同的角色:

  • HumanMessage:來自人類/用戶的ChatMessage。
  • AIMessage:來自AI/助手的ChatMessage。
  • SystemMessage:來自系統的ChatMessage。
  • FunctionMessage:來自函數調用的ChatMessage。

如果這些角色都不合適,還有一個ChatMessage類,可以手動指定角色。

LangChain提供了標準接口,但瞭解這種差異有助於構建特定語言模型的提示。LangChain提供的標準接口有兩種方法:

  • predict:接受一個字符串,返回一個字符串。
  • predict_messages:接受一個消息列表,返回一個消息。
from langchain.llms import OpenAI
from langchain.chat_models import ChatOpenAI

llm = OpenAI()
chat_model = ChatOpenAI()

llm.predict("hi!")
>>> "Hi"

chat_model.predict("hi!")
>>> "Hi"

OpenAI 和 ChatOpenAI 對象基本上只是配置對象。開發者可以使用諸如 temperature 等參數來初始化它們,並在各處傳遞它們。

接下來,讓我們使用 predict 方法來處理一個字符串輸入。

text = "What would be a good company name for a company that makes colorful socks?"

llm.predict(text)
# >> Feetful of Fun

chat_model.predict(text)
# >> Socks O'Color

最後,讓我們使用 predict_messages 方法來處理一個消息列表。

from langchain.schema import HumanMessage

text = "What would be a good company name for a company that makes colorful socks?"
messages = [HumanMessage(content=text)]

llm.predict_messages(messages)
# >> Feetful of Fun

chat_model.predict_messages(messages)
# >> Socks O'Color

3.3 Prompt 模版

PromptTemplates也可以用於生成消息列表。在這種情況下,提示不僅包含有關內容的信息,還包含每條消息的信息(其角色、在列表中的位置等)。在這裏,最常見的情況是ChatPromptTemplate是ChatMessageTemplate列表。每個ChatMessageTemplate包含了如何格式化ChatMessage的指令 - 其角色以及內容。

from langchain.prompts.chat import ChatPromptTemplate

template = "You are a helpful assistant that translates {input_language} to {output_language}."
human_template = "{text}"

chat_prompt = ChatPromptTemplate.from_messages([
    ("system", template),
    ("human", human_template),
])

chat_prompt.format_messages(input_language="English", output_language="French", text="I love programming.")

3.3 Output parsers

OutputParsers 將LLM的原始輸出轉換爲可在下游使用的格式。其中主要的OutputParsers類型包括:

  • 將LLM的文本轉換爲結構化信息(例如 JSON)
  • 將 ChatMessage 轉換爲純字符串
  • 將調用返回的除消息外的額外信息(例如 OpenAI 函數調用)轉換爲字符串。
from langchain.schema import BaseOutputParser

class CommaSeparatedListOutputParser(BaseOutputParser):
    """Parse the output of an LLM call to a comma-separated list."""


    def parse(self, text: str):
        """Parse the output of an LLM call."""
        return text.strip().split(", ")

CommaSeparatedListOutputParser().parse("hi, bye")
# >> ['hi', 'bye']

3.4 PromptTemplate + LLM + OutputParser

 我們現在可以將所有這些組合成一個鏈條。該鏈條將接收輸入變量,將其傳遞到提示模板以創建提示,將提示傳遞給語言模型,然後通過(可選的)輸出解析器傳遞輸出。這是打包模塊化邏輯的便捷方式。

from langchain.chat_models import ChatOpenAI
from langchain.prompts.chat import ChatPromptTemplate
from langchain.schema import BaseOutputParser

class CommaSeparatedListOutputParser(BaseOutputParser):
    """Parse the output of an LLM call to a comma-separated list."""


    def parse(self, text: str):
        """Parse the output of an LLM call."""
        return text.strip().split(", ")

template = """You are a helpful assistant who generates comma separated lists.
A user will pass in a category, and you should generate 5 objects in that category in a comma separated list.
ONLY return a comma separated list, and nothing more."""
human_template = "{text}"

chat_prompt = ChatPromptTemplate.from_messages([
    ("system", template),
    ("human", human_template),
])
chain = chat_prompt | ChatOpenAI() | CommaSeparatedListOutputParser()
chain.invoke({"text": "colors"})
# >> ['red', 'blue', 'green', 'yellow', 'orange']

請注意,我們使用 | 符號將這些組件連接在一起。這種 | 符號稱爲 LangChain 表達語言。

4.結束語

這篇博客就和大家分享到這裏,如果大家在研究學習的過程當中有什麼問題,可以加羣進行討論或發送郵件給我,我會盡我所能爲您解答,與君共勉!

另外,博主出書了《Kafka並不難學》和《Hadoop大數據挖掘從入門到進階實戰》,喜歡的朋友或同學, 可以在公告欄那裏點擊購買鏈接購買博主的書進行學習,在此感謝大家的支持。關注下面公衆號,根據提示,可免費獲取書籍的教學視頻。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章