在介紹完 runnable 組件和 Langchain 的 chain 設計之後,接著就要來介紹 LCEL 的核心概念,也就是串接這些組件的方式,我們先用簡表呈現:
| 工具名稱 | 描述 | 
|---|---|
| RunnableSequence | 依序執行多個任務,前一個任務的輸出作為下個任務的輸入。 | 
| RunnableParallel | 並行執行多個任務,並返回每個任務的結果。 | 
| RunnableBranch | 根據條件選擇要執行的任務,當所有條件都不成立時,執行預設的任務。 | 
| RunnablePassthrough | 保留輸入不變,可同時對輸入進行額外處理的場景 | 
前天的示範就是一個最簡單的案例,先將 SI Dream Engineering 傳遞到 prompt 當中,再給 LLM。
from langchain_mistralai import ChatMistralAI
from langchain.prompts import ChatPromptTemplate
llm = ChatMistralAI(model="mistral-large-latest")
prompt = ChatPromptTemplate.from_template("Tell me a joke about {topic}")
chain = prompt | llm
chain.invoke({"topic":"SI Dream Engineering"})
RunnableParallel 則是將相同的 input 放到多個 chain,同時生成答案,以下的範例有沒有昨天 MultiQuery 的感覺?
from langchain.prompts import PromptTemplate
from langchain_mistralai import ChatMistralAI
from langchain_core.runnables import RunnableParallel
original_query = PromptTemplate(template="Answer this question: '{query}'")
new_query = PromptTemplate(template="Generate a different version of this question and answer: '{query}'")
llm = ChatMistralAI(model="mistral-large-latest")
parallel_queries = RunnableParallel({
    "original_answer": original_query | llm,
    "new_question_answer": new_query | llm,
})
result = parallel_queries.invoke({"query": "Tell me a story about SI Dream Engineering."})
print(result)
RunnableBranch 的使用場景則是可以設定條件,並將 input 導到執行不同任務,如下就是一個簡單的 router retriever 的應用場景。
retriever_law_db = ...
retriever_news_db = ...
retriever_db = ...
retriever_branch = RunnableBranch(
    (lambda x: "law" in x["query"], retriever_law_db),    # 如果 "law" 在 query 中
    (lambda x: "news" in x["query"], retriever_news_db),  # 如果 "news" 在 query 中
    retriever_db                                          # 否則執行 retriever_db
)
而 RunnablePassthrough 則是可以保留 input 原貌並往下傳遞給後續的任務,以下方語法為例,不僅保留了原始問題,並將其與檢索到的文件一起傳遞給 LLM 處理,相當於一個簡單版本的 RAG。
from langchain.prompts import PromptTemplate
from langchain_mistralai import ChatMistralAI
llm = ChatMistralAI(model="mistral-large-latest")
prompt = PromptTemplate(template="Given the document {doc}, please answer: {query}")
retriever = ...
retrieve_chain = RunnableParallel(
    query=RunnablePassthrough(), 
    doc=retriever
) | prompt | llm
result = retrieve_chain.invoke({"query": "Tell me a story about SI Dream Engineering."})
print(result)
以 RunnableParallel 案例為例,我們可以透過 chain.get_graph().print_ascii() 查看目前 chain 的設計架構,也可以透過 chain.input_schema.schema()、chain.output_schema.schema() 幫助檢視 chain 輸入與輸出資訊,幫助開發過程。
+----------------------------------------------------+   
| Parallel<original_answer,new_question_answer>Input |   
+----------------------------------------------------+   
                  ***              ***                   
               ***                    ***                
             **                          **              
 +----------------+                 +----------------+   
 | PromptTemplate |                 | PromptTemplate |   
 +----------------+                 +----------------+   
          *                                  *           
          *                                  *           
          *                                  *           
  +---------------+                 +---------------+    
  | ChatMistralAI |                 | ChatMistralAI |    
  +---------------+                 +---------------+    
                  ***              ***                   
                     ***        ***                      
                        **    **                         
+-----------------------------------------------------+  
| Parallel<original_answer,new_question_answer>Output |  
+-----------------------------------------------------+  
ref.