返回文章列表

LangChain文字分割任務分解與提示鏈應用

本文探討如何使用 LangChain 進行文字分割、任務分解及提示鏈應用,包含 TextSplitter 與 LLMChain 的基本用法、多步驟提示鏈的建構、LCEL 的使用,以及如何動態豐富場景、結構化輸出和分工合作以增強 LLM 生成能力。

Web 開發 機器學習

LangChain 提供了便捷的工具,像是 TextSplitterLLMChain,方便開發者進行文字分割和任務分解,提升大語言模型的處理效率。TextSplitter 能將長文字分割成適合 LLM 處理的區塊,而 LLMChain 則能將 LLM 與其他功能模組串聯,實作更複雜的應用。在處理長文字或需要多步驟邏輯的任務時,這些工具能有效簡化開發流程。更進一步,可以利用提示鏈(Prompt Chaining)的概念,將多個提示串聯起來,引導 LLM 完成更複雜的任務,例如角色生成、情節生成、場景生成等。每個提示都專注於一個子任務,讓 LLM 更有效率地生成所需內容。

結合 LangChain 實作文字分割和任務分解

LangChain 提供了多種工具和技術,可以用於實作文字分割和任務分解。例如,可以使用 TextSplitter 類別實作文字分割,可以使用 LLMChain 類別實作任務分解。

from langchain import TextSplitter, LLMChain

text_splitter = TextSplitter(chunk_size=300)
llm_chain = LLMChain(llm=llm)

texts = text_splitter.split_text(text)
outputs = llm_chain(texts)

內容解密:

上述程式碼展示瞭如何使用 LangChain 實作文字分割和任務分解。首先,建立一個 TextSplitter 物件,用於實作文字分割。然後,建立一個 LLMChain 物件,用於實作任務分解。最後,使用 TextSplitter 將文字分割成更小的塊,然後使用 LLMChain 對每個塊進行處理。

多步驟提示鏈:提升文字生成能力

在實際應用中,單一提示往往無法完成複雜任務。這時,提示鏈(Prompt Chaining)就成了解決方案。它涉及將多個提示輸入/輸出結合起來,使用特定的LLM提示來構建一個想法。讓我們以一個電影公司為例,它希望部分自動化電影創作過程。這個過程可以分解為多個關鍵元件,例如角色建立、情節生成、場景/世界構建等。

順序鏈

為了實作這個目標,我們可以將任務分解為多個鏈,並重新組合成一個單一鏈。每個鏈負責不同的任務:

  1. 角色生成鏈:根據給定的型別,建立多個角色。
  2. 情節生成鏈:根據角色和型別,建立情節。
  3. 場景生成鏈:根據情節,生成缺失的場景。

實作提示鏈

現在,讓我們為每個鏈定義一個提示範本:

from langchain_core.prompts.chat import ChatPromptTemplate

# 角色生成提示
character_generation_prompt = ChatPromptTemplate.from_template(
    """根據給定的型別,為我的短篇故事腦力激盪三到五個角色。型別是 {genre}。每個角色必須有一個名稱和簡介。
    你必須為每個角色提供名稱和簡介,這非常重要!
    
---
    示例回應:
    名稱:CharWiz,簡介:一位精通魔法的巫師。
    名稱:CharWar,簡介:一位精通劍術的戰士。
    
---
    角色:"""
)

# 情節生成提示
plot_generation_prompt = ChatPromptTemplate.from_template(
    """給定以下角色和型別,為短篇故事建立一個有效的情節:
    角色:
    
    {characters}
    
---
    型別:{genre}
    
---
    情節:"""
)

# 場景生成提示
scene_generation_plot_prompt = ChatPromptTemplate.from_template(
    """扮演一個有效的內容創作者。
    給定多個角色和一個情節,你負責為每個幕生成各種場景:
    你必須將情節分解為多個有效的場景:
    
---
    角色:
    
    {characters}
    
---
"""
)

圖表視覺化

以下是使用Plantuml語法對這個過程進行視覺化的示例:

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title LangChain文字分割任務分解與提示鏈應用

package "系統架構" {
    package "前端層" {
        component [使用者介面] as ui
        component [API 客戶端] as client
    }

    package "後端層" {
        component [API 服務] as api
        component [業務邏輯] as logic
        component [資料存取] as dao
    }

    package "資料層" {
        database [主資料庫] as db
        database [快取] as cache
    }
}

ui --> client : 使用者操作
client --> api : HTTP 請求
api --> logic : 處理邏輯
logic --> dao : 資料操作
dao --> db : 持久化
dao --> cache : 快取

note right of api
  RESTful API
  或 GraphQL
end note

@enduml

圖表翻譯:

這個流程圖展示瞭如何透過順序鏈的方式來生成短篇故事。首先,根據給定的型別生成角色,然後根據角色和型別生成情節,最後根據情節生成場景。這個過程透過多步驟的提示鏈來實作,最終產生出一個完整的短篇故事。

內容解密:

每個提示範本都設計用於完成特定的任務。角色生成提示要求根據型別腦力激盆出多個角色,每個角色都有一個名稱和簡介。情節生成提示根據給定的角色和型別建立一個有效的情節。場景生成提示根據情節和角色生成缺失的場景。這些提示透過順序鏈的方式組合起來,最終產生出一個完整的短篇故事。

使用LCEL進行聊天機器人提示連結

在進行聊天機器人提示連結時,如何確保下游的聊天機器人提示範本變數可用是一個重要的問題。為瞭解決這個問題,我們可以使用LCEL(LangChain Execution Language)中的itemgetter函式從前一步中提取鍵值。

使用itemgetter函式

首先,我們需要從operator包中匯入itemgetter函式。然後,我們可以使用RunnablePassthrough函式建立一個連結,該連結將前一步的輸入直接傳遞給下一步。

from operator import itemgetter
from langchain_core.runnables import RunnablePassthrough

chain = RunnablePassthrough() | {
    "genre": itemgetter("genre"),
}
chain.invoke({"genre": "fantasy"})

在這個例子中,itemgetter("genre")函式從前一步中提取了genre鍵的值,並將其作為新的鍵值新增到下一步的輸入中。

使用lambda函式

除了使用itemgetter函式外,我們還可以使用lambda函式或RunnableLambda函式來操縱前一步的字典值。

from langchain_core.runnables import RunnableLambda

chain = RunnablePassthrough() | {
    "genre": itemgetter("genre"),
    "upper_case_genre": lambda x: x["genre"].upper(),
    "lower_case_genre": RunnableLambda(lambda x: x["genre"].lower()),
}
chain.invoke({"genre": "fantasy"})

在這個例子中,lambda函式和RunnableLambda函式分別將genre鍵的值轉換為大寫和小寫,並將結果作為新的鍵值新增到下一步的輸入中。

使用RunnableParallel函式

最後,我們可以使用RunnableParallel函式來平行執行多個連結。

from langchain_core.runnables import RunnableParallel

master_chain = RunnablePassthrough() | {
    "genre": itemgetter("genre"),
    "upper_case_genre": lambda x: x["genre"].upper(),
    "lower_case_genre": RunnableLambda(lambda x: x["genre"].lower()),
}
master_chain_two = RunnablePassthrough() | RunnableParallel(
    genre=itemgetter("genre"),
    upper_case_genre=lambda x: x["genre"].upper(),
    lower_case_genre=RunnableLambda(lambda x: x["genre"].lower()),
)
story_result = master_chain.invoke({"genre": "Fantasy"})

在這個例子中,RunnableParallel函式平行執行了兩個連結,分別提取了genre鍵的值,並將其轉換為大寫和小寫。

透過使用這些函式和語法,我們可以建立複雜的聊天機器人提示連結,並確保下游的聊天機器人提示範本變數可用。

使用 LangChain 進行文字生成的高階技術

LangChain 是一個強大的工具,允許使用者建立複雜的文字生成工作流程。透過使用 LangChain 的可執行模組(Runnable),使用者可以建立多個鏈條(chain)來生成文字,並將這些鏈條組合起來以建立更複雜的工作流程。

建立 LCEL 鏈條

首先,需要建立兩個 LCEL 鏈條:master_chainmaster_chain_two。這些鏈條可以使用相同的引數進行呼叫,並傳回相同的結果。

from langchain.runnables import RunnableParallel
from langchain.runnables import RunnablePassthrough

master_chain = (
    {"genre": "Fantasy"}
    | RunnablePassthrough()
    | RunnableParallel(
        genre=itemgetter("genre"),
        upper_case_genre=lambda x: x["genre"].upper(),
        lower_case_genre=lambda x: x["genre"].lower(),
    )
)

master_chain_two = (
    {"genre": "Fantasy"}
    | RunnablePassthrough()
    | RunnableParallel(
        genre=itemgetter("genre"),
        upper_case_genre=lambda x: x["genre"].upper(),
        lower_case_genre=lambda x: x["genre"].lower(),
    )
)

執行 LCEL 鏈條

然後,可以執行這些鏈條並列印結果。

story_result = master_chain.invoke({"genre": "Fantasy"})
print(story_result)

story_result = master_chain_two.invoke({"genre": "Fantasy"})
print(story_result)

建立多個 LCEL 鏈條

接下來,需要建立三個 LCEL 鏈條:character_generation_chainplot_generation_chainscene_generation_plot_chain。這些鏈條可以使用不同的提示範本來生成文字。

from langchain.chat_models import ChatOpenAI
from langchain.output_parsers import StrOutputParser

model = ChatOpenAI()

character_generation_chain = (
    character_generation_prompt
    | model
    | StrOutputParser()
)

plot_generation_chain = (
    plot_generation_prompt
    | model
    | StrOutputParser()
)

scene_generation_plot_chain = (
    scene_generation_plot_prompt
    | model
    | StrOutputParser()
)

組合 LCEL 鏈條

然後,可以將這些鏈條組合起來以建立一個主 LCEL 鏈條。

from langchain.runnables import RunnableParallel
from operator import itemgetter

master_chain = (
    {
        "characters": character_generation_chain,
        "genre": RunnablePassthrough(),
    }
    | RunnableParallel(
        characters=itemgetter("characters"),
        genre=itemgetter("genre"),
        plot=plot_generation_chain,
    )
    | RunnableParallel(
        characters=itemgetter("characters"),
        genre=itemgetter("genre"),
        plot=itemgetter("plot"),
        scenes=scene_generation_plot_chain,
    )
)

執行主 LCEL 鏈條

最後,可以執行主 LCEL 鏈條並列印結果。

story_result = master_chain.invoke({"genre": "Fantasy"})
print(story_result)

這將生成一個包含角色、情節和場景的故事。場景將被分成單獨的專案 dalam 一個 Python 列表中。然後,可以建立兩個新的提示來生成角色指令碼和摘要提示。

Scene Generation and Summarization

為了建立一個有效的場景指令碼,需要考慮到前一個場景的摘要,以避免重複內容。這可以透過使用兩個 LCEL鏈來實作:一個用於生成每個場景的角色指令碼,另一個用於生成前一個場景的摘要。

載入聊天模型

首先,需要載入一個聊天模型,以便生成場景指令碼和摘要。這裡使用的是 gpt-3.5-turbo-16k 模型。

model = ChatOpenAI(model='gpt-3.5-turbo-16k')

建立 LCEL 鏈

接下來,需要建立兩個 LCEL 鏈:一個用於生成角色指令碼,另一個用於生成場景摘要。

character_script_generation_chain = (
    {
        "characters": RunnablePassthrough(),
        "genre": RunnablePassthrough(),
        "previous_scene_summary": RunnablePassthrough(),
        "plot": RunnablePassthrough(),
        "scene": RunnablePassthrough(),
        "index": RunnablePassthrough(),
    }
    | character_script_prompt
    | model
)

場景指令碼生成

場景指令碼生成鏈使用 character_script_prompt 來生成每個場景的角色指令碼。這個提示包含了角色、型別、前一個場景的摘要、情節、場景和索引等資訊。

summarize_prompt = ChatPromptTemplate.from_template(
    template="""Given a character script, create a summary of the scene.
    Character script: {character_script}""",
)

場景摘要生成

場景摘要生成鏈使用 summarize_prompt 來生成每個場景的摘要。這個提示包含了角色指令碼等資訊。

執行 LCEL 鏈

最後,需要執行 LCEL 鏈來生成場景指令碼和摘要。

generated_scenes = []
previous_scene_summary = ""

for index, scene in enumerate(scenes):
    # 生成角色指令碼
    character_script = character_script_generation_chain({
        "characters": characters,
        "genre": genre,
        "previous_scene_summary": previous_scene_summary,
        "plot": plot,
        "scene": scene,
        "index": index,
    })
    
    # 生成場景摘要
    scene_summary = model(summarize_prompt({
        "character_script": character_script,
    }))
    
    # 更新前一個場景的摘要
    previous_scene_summary = scene_summary
    
    # 新增生成的場景到列表中
    generated_scenes.append((scene, character_script, scene_summary))

這樣,就可以生成每個場景的角色指令碼和摘要,並且避免了重複內容。

##Prompt Chaining的應用:增強LLM的生成能力

在 Prompt Chaining 的架構中,我們可以透過串聯多個任務(Tasks)來增強大語言模型(LLM)的生成能力。這個過程涉及到建立一個任務鏈(Chain),其中每個任務都專注於特定的子任務,以達到最終的目標。

建立任務鏈

首先,我們需要建立一個任務鏈,包括多個任務,如character_script_generation_chain。這個鏈結合了多個可執行的任務(Runnables),如RunnablePassthrough,以確保資料流暢順暢。此外,還引入了一個強大的模型ChatOpenAI,具有16k的上下文視窗,非常適合用於廣泛的內容生成任務。

動態豐富場景

為了增強LLM的生成能力,我們可以動態豐富每個場景,透過建立一個簡單卻有效的緩衝記憶(Buffer Memory)。這種技術確保了敘述的連續性和上下文,從而增強了LLM生成連貫的角色指令碼的能力。

結構化輸出

透過StrOutputParser,我們可以將模型輸出轉換為結構化的字串,使得生成的內容易於使用和處理。

分工合作

設計任務時,遵循「分工合作」(Divide Labor)的原則,可以使整體輸出的品質大幅提高。將任務分解為更小、更易於管理的鏈結,可以增加每個鏈結對最終目標的貢獻。使用不同的模型,如使用智慧模型進行構思和使用廉價模型進行生成,通常可以得到最佳結果。

結構化LCEL鏈

在LCEL中,確保鏈結的第一部分是可執行的型別至關重要。錯誤的程式碼可能會導致執行失敗。因此,必須謹慎設計LCEL鏈結,以確保其正確性和有效性。

綜上所述,Prompt Chaining提供了一種強大的方法來增強LLM的生成能力,透過結合多個任務、動態豐富場景、結構化輸出和分工合作。這種方法不僅提高了生成內容的品質,也增強了LLM應用於複雜任務的能力。

從技術架構視角來看,LangChain 提供了靈活且強大的機制來實作複雜的文字生成任務。本文深入探討瞭如何利用 LangChain 的核心元件,例如 TextSplitterLLMChain、以及各種 Runnable 模組,構建多步驟提示鏈,並結合 LCEL 語法實作更精細的流程控制。透過剖析角色生成、情節生成、場景構建等案例,我們可以清楚地看到 LangChain 如何有效地組織和串聯多個 LLM 呼叫,從而完成原本單一提示難以實作的複雜目標。 然而,LangChain 的學習曲線較陡峭,需要開發者熟悉其核心概念和 API 設計。此外,在處理長文字和複雜邏輯時,效能調校和錯誤除錯也可能帶來一定的挑戰。對於追求精細化控制和高度客製化流程的開發者,深入理解 LCEL 的語法和執行機制至關重要。展望未來,隨著 LangChain 生態系統的持續發展,我們預計會有更多便捷的工具和更最佳化的執行引擎出現,進一步降低開發門檻並提升生成效率。對於希望構建高階文字生成應用的開發者而言,LangChain 值得深入研究和應用。