一、LangChain 模塊和體系
LangChain 是一個用于開發由大型語言模型(LLMs)驅動的應用程序的框架。
官方文檔:https://python.langchain.com/docs/introduction/
LangChain 簡化了LLM應用程序生命周期的每個階段:
- 開發:使用LangChain的開源構建模塊和組件構建您的應用程序。利用第三方集成和模板快速啟動。
- 生產部署:使用LangSmith檢查、監控和評估您的鏈,以便您可以持續優化并自信地部署。
- 部署:使用LangServe將任何鏈轉換為API。
具體而言,該框架包括以下開源庫:
langchain-core
:基本抽象和LangChain表達語言。langchain-community
:第三方集成。- 合作伙伴包(例如
langchain-openai
,langchain-anthropic
等):一些集成已進一步拆分為僅依賴于langchain-core
的輕量級包。
- 合作伙伴包(例如
langchain
:構成應用程序認知架構的鏈、代理和檢索策略。- langgraph:通過將步驟建模為圖中的邊緣和節點,使用LLMs構建穩健且有狀態的多參與者應用程序。
- langserve:將LangChain鏈部署為REST API。
- LangSmith:一個開發平臺,可讓您調試、測試、評估和監控LLM應用程序。
LLM & Chat models PromptTemplates, OutputParses Chains
LLMs
將字符串作為輸入并返回字符串的語言模型。 這些通常是較舊的模型(較新的模型通常是 ChatModels
,見上文)。 盡管底層模型是字符串輸入、字符串輸出,LangChain 封裝器還允許這些模型接受消息作為輸入。 這使它們可以與 ChatModels 互換使用。 當消息作為輸入傳入時,它們將在傳遞給底層模型之前在內部格式化為字符串。 LangChain 不提供任何 LLMs,而是依賴于第三方集成。
Messages(消息)
一些語言模型將消息列表作為輸入并返回消息。 有幾種不同類型的消息。 所有消息都有 role
、content
和 response_metadata
屬性。 role
描述了消息的發出者是誰。 LangChain 為不同的角色設計了不同的消息類。 content
屬性描述了消息的內容。 這可以是幾種不同的內容:
- 一個字符串(大多數模型處理這種類型的內容)
- 一個字典列表(用于多模態輸入,其中字典包含有關該輸入類型和該輸入位置的信息)
HumanMessage
這代表用戶發送的消息。
AIMessage
這代表模型發送的消息。除了 content
屬性外,這些消息還有: response_metadata
response_metadata
屬性包含有關響應的其他元數據。這里的數據通常針對每個模型提供者具體化。 這是存儲對數概率和標記使用等信息的地方。 tool_calls
這些表示語言模型調用工具的決定。它們作為 AIMessage
輸出的一部分包含在內。 可以通過 .tool_calls
屬性從中訪問。 此屬性返回一個字典列表。每個字典具有以下鍵:
name
:應調用的工具的名稱。arg
:該工具的參數。id
:該工具調用的 id。
SystemMessage
這代表系統消息,告訴模型如何行為。并非每個模型提供者都支持這一點。
FunctionMessage
這代表函數調用的結果。除了 role
和 content
,此消息還有一個 name
參數,傳達了生成此結果所調用的函數的名稱。
ToolMessage
這代表工具調用的結果。這與 FunctionMessage 不同,以匹配 OpenAI 的 function
和 tool
消息類型。除了 role
和 content
,此消息還有一個 tool_call_id
參數,傳達了調用生成此結果的工具的 id。
Prompt templates(提示模板)
提示模板有助于將用戶輸入和參數轉換為語言模型的指令。 這可用于引導模型的響應,幫助其理解上下文并生成相關和連貫的基于語言的輸出。 提示模板以字典作為輸入,其中每個鍵代表要填充的提示模板中的變量。 提示模板輸出一個 PromptValue。這個 PromptValue 可以傳遞給 LLM 或 ChatModel,并且還可以轉換為字符串或消息列表。 存在 PromptValue 的原因是為了方便在字符串和消息之間切換。 有幾種不同類型的提示模板
String PromptTemplates
這些提示模板用于格式化單個字符串,通常用于更簡單的輸入。 例如,構建和使用 PromptTemplate 的常見方法如下:
from langchain_core.prompts import PromptTemplate
prompt_template = PromptTemplate.from_template("Tell me a joke about {topic}")
prompt_template.invoke({"topic": "cats"})
ChatPromptTemplates
這些提示模板用于格式化消息列表。這些“模板”本身是模板列表。 例如,構建和使用 ChatPromptTemplate 的常見方法如下:
from langchain_core.prompts import ChatPromptTemplate
prompt_template = ChatPromptTemplate.from_messages([("system", "You are a helpful assistant"),("user", "Tell me a joke about {topic}")
])
prompt_template.invoke({"topic": "cats"})
在上面的示例中,當調用此 ChatPromptTemplate 時,將構建兩條消息。 第一條是系統消息,沒有要格式化的變量。 第二條是 HumanMessage,并將根據用戶傳入的 topic
變量進行格式化。
MessagesPlaceholder
這個提示模板負責在特定位置添加消息列表。 在上面的 ChatPromptTemplate 中,我們看到了如何格式化兩條消息,每條消息都是一個字符串。 但是,如果我們希望用戶傳入一個消息列表,我們將其插入到特定位置,該怎么辦? 這就是您使用 MessagesPlaceholder 的方式。
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import HumanMessage
prompt_template = ChatPromptTemplate.from_messages([("system", "You are a helpful assistant"),MessagesPlaceholder("msgs")
])
prompt_template.invoke({"msgs": [HumanMessage(content="hi!")]})
這將生成兩條消息,第一條是系統消息,第二條是我們傳入的 HumanMessage。 如果我們傳入了 5 條消息,那么總共會生成 6 條消息(系統消息加上傳入的 5 條消息)。 這對于將一系列消息插入到特定位置非常有用。 另一種實現相同效果的替代方法是,不直接使用 MessagesPlaceholder
類,而是:
prompt_template = ChatPromptTemplate.from_messages([("system", "You are a helpful assistant"),("placeholder", "{msgs}") # <-- 這是更改的部分
])
Output parsers(輸出解析器)
這里提到的是將模型的文本輸出進行解析,轉換為更結構化表示的解析器。 越來越多的模型支持函數(或工具)調用,可以自動處理這一過程。 建議使用函數/工具調用,而不是輸出解析。
負責接收模型的輸出并將其轉換為更適合下游任務的格式。 在使用LLMs生成結構化數據或規范化聊天模型和LLMs的輸出時非常有用。 LangChain有許多不同類型的輸出解析器。下表列出了LangChain支持的各種輸出解析器及相關信息:
名稱:輸出解析器的名稱
支持流式處理:輸出解析器是否支持流式處理
具有格式說明:輸出解析器是否具有格式說明。通常是可用的,除非在提示中未指定所需模式,而是在其他參數中指定(如OpenAI函數調用),或者當OutputParser包裝另一個OutputParser時。
調用LLM:此輸出解析器是否調用LLM。通常只有嘗試糾正格式不正確的輸出的輸出解析器才會這樣做。 輸入類型:預期的輸入類型。大多數輸出解析器適用于字符串和消息,但有些(如OpenAI函數)需要具有特定kwargs的消息。
輸出類型:解析器返回的對象的輸出類型。
描述:我們對此輸出解析器的評論以及何時使用它的說明。
示例代碼
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.output_parsers import StrOutputParsermodel = ChatOpenAI(model="gpt-4")messages = [SystemMessage(content="將以下內容從英語翻譯成中文"),HumanMessage(content="It's a nice day today"),
]
parser = StrOutputParser()
result = model.invoke(messages)
#使用parser處理model返回的結果
response = parser.invoke(result)
print(response)
#今天天氣很好
Chains(鏈式調用)
Chains 是 LangChain 中用于將多個步驟組合成一個工作流程的模塊。它們允許你定義一系列操作,并將它們鏈接在一起。比如在這個Chain中,每次都會調用輸出解析器。這個鏈條的輸入類型是語言模型的輸出(字符串或消息列表),輸出類型是輸出解析器的輸出(字符串)。
我們可以使用 |
運算符輕松創建這個Chain。|
運算符在 LangChain 中用于將兩個元素組合在一起。
如果我們現在看一下 LangSmith,我們會發現這個鏈條有兩個步驟:首先調用語言模型,然后將其結果傳遞給輸出解析器。我們可以在 LangSmith 跟蹤 中看到這一點。
https://smith.langchain.com/public/f1bdf656-2739-42f7-ac7f-0f1dd712322f/r
示例代碼
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.output_parsers import StrOutputParsermodel = ChatOpenAI(model="gpt-4")messages = [SystemMessage(content="將以下內容從英語翻譯成中文"),HumanMessage(content="Let's go for a run"),
]
parser = StrOutputParser()# 使用Chains方式調用
chain = model | parser #等于 model.invoke() + parser.invoke()
response = chain.invoke(messages)
print(response)
#我們去跑步吧
LCEL & Runable interface
LCEL 英文全稱 LangChain Execution Language(LangChain 表達語言) 是一種聲明性的方式來鏈接 LangChain 組件。 LCEL 從第一天起就被設計為支持將原型投入生產,無需更改代碼,從最簡單的“提示 + LLM”鏈到最復雜的鏈(我們已經看到有人成功地在生產中運行了包含數百步的 LCEL 鏈)。以下是您可能想要使用 LCEL 的一些原因的幾個亮點:
一流的流式支持 當您使用 LCEL 構建鏈時,您將獲得可能的最佳時間到第一個標記(直到輸出的第一塊內容出現所經過的時間)。對于某些鏈,這意味著我們直接從 LLM 流式傳輸標記到流式輸出解析器,您將以與 LLM 提供程序輸出原始標記的速率相同的速度獲得解析的增量輸出塊。
異步支持 使用 LCEL 構建的任何鏈都可以使用同步 API(例如,在您的 Jupyter 筆記本中進行原型設計)以及異步 API(例如,在 LangServe 服務器中)進行調用。這使得可以在原型和生產中使用相同的代碼,具有出色的性能,并且能夠在同一服務器中處理許多并發請求。
優化的并行執行 每當您的 LCEL 鏈具有可以并行執行的步驟時(例如,如果您從多個檢索器中獲取文檔),我們會自動執行,無論是在同步接口還是異步接口中,以獲得可能的最小延遲。
重試和回退 為 LCEL 鏈的任何部分配置重試和回退。這是使您的鏈在規模上更可靠的好方法。我們目前正在努力為重試/回退添加流式支持,這樣您就可以獲得額外的可靠性而無需任何延遲成本。
訪問中間結果 對于更復雜的鏈,訪問中間步驟的結果通常非常有用,即使在生成最終輸出之前。這可以用于讓最終用戶知道正在發生的事情,甚至只是用于調試您的鏈。您可以流式傳輸中間結果,并且在每個 LangServe 服務器上都可以使用。
輸入和輸出模式 輸入和輸出模式為每個 LCEL 鏈提供了從鏈的結構推斷出的 Pydantic 和 JSONSchema 模式。這可用于驗證輸入和輸出,并且是 LangServe 的一個組成部分。
無縫 LangSmith 追蹤 隨著您的鏈變得越來越復雜,準確理解每一步發生的事情變得越來越重要。 使用 LCEL,所有步驟都會自動記錄到 LangSmith 中,以實現最大的可觀察性和可調試性。
無縫 LangServe 部署 使用 LCEL 創建的任何鏈都可以輕松地通過 LangServe 部署。
Runable interface(可運行接口)
為了盡可能簡化創建自定義鏈的過程,我們實現了一個 “Runnable” 協議。許多 LangChain 組件都實現了 Runnable
協議,包括聊天模型、LLMs、輸出解析器、檢索器、提示模板等等。此外,還有一些有用的基本組件可用于處理可運行對象,您可以在下面了解更多。 這是一個標準接口,可以輕松定義自定義鏈,并以標準方式調用它們。 標準接口包括:
- stream: 返回響應的數據塊
- invoke: 對輸入調用鏈
- batch: 對輸入列表調用鏈
這些還有相應的異步方法,應該與 asyncio 一起使用 awai
語法以實現并發:
astream
: 異步返回響應的數據塊ainvoke
: 異步對輸入調用鏈abatch
: 異步對輸入列表調用鏈astream_log
: 異步返回中間步驟,以及最終響應astream_events
: beta 流式傳輸鏈中發生的事件(在langchain-core
0.1.14 中引入)
輸入類型 和 輸出類型 因組件而異:
組件 | 輸入類型 | 輸出類型 |
---|---|---|
提示 | 字典 | 提示值 |
聊天模型 | 單個字符串、聊天消息列表或提示值 | 聊天消息 |
LLM | 單個字符串、聊天消息列表或提示值 | 字符串 |
輸出解析器 | LLM 或聊天模型的輸出 | 取決于解析器 |
檢索器 | 單個字符串 | 文檔列表 |
工具 | 單個字符串或字典,取決于工具 | 取決于工具 |
所有可運行對象都公開輸入和輸出 模式 以檢查輸入和輸出:
input_schema
: 從可運行對象結構自動生成的輸入 Pydantic 模型output_schema
: 從可運行對象結構自動生成的輸出 Pydantic 模型
流式運行對于使基于 LLM 的應用程序對最終用戶具有響應性至關重要。 重要的 LangChain 原語,如聊天模型、輸出解析器、提示模板、檢索器和代理都實現了 LangChain Runnable 接口。 該接口提供了兩種通用的流式內容方法:
- 同步
stream
和異步astream
:流式傳輸鏈中的最終輸出的默認實現。 - 異步
astream_events
和異步astream_log
:這些方法提供了一種從鏈中流式傳輸中間步驟和最終輸出的方式。 讓我們看看這兩種方法,并嘗試理解如何使用它們。
Stream(流)
所有 Runnable
對象都實現了一個名為 stream
的同步方法和一個名為 astream
的異步變體。 這些方法旨在以塊的形式流式傳輸最終輸出,盡快返回每個塊。 只有在程序中的所有步驟都知道如何處理輸入流時,才能進行流式傳輸;即,逐個處理輸入塊,并產生相應的輸出塊。 這種處理的復雜性可以有所不同,從簡單的任務,如發出 LLM 生成的令牌,到更具挑戰性的任務,如在整個 JSON 完成之前流式傳輸 JSON 結果的部分。 開始探索流式傳輸的最佳方法是從 LLM 應用程序中最重要的組件開始——LLM 本身!
LLM 和聊天模型
大型語言模型及其聊天變體是基于 LLM 的應用程序的主要瓶頸。 大型語言模型可能需要幾秒鐘才能對查詢生成完整的響應。這比應用程序對最終用戶具有響應性的約 200-300 毫秒的閾值要慢得多。 使應用程序具有更高的響應性的關鍵策略是顯示中間進度;即,逐個令牌流式傳輸模型的輸出。 我們將展示使用聊天模型進行流式傳輸的示例。從以下選項中選擇一個:
讓我們從同步 stream
API 開始:
chunks = []
for chunk in model.stream("天空是什么顏色?"):chunks.append(chunk)print(chunk.content, end="|", flush=True)
天|空|是|什|么|顏|色|?|
或者,如果您在異步環境中工作,可以考慮使用異步 astream
API:
chunks = []
async for chunk in model.astream("天空是什么顏色?"):chunks.append(chunk)print(chunk.content, end="|", flush=True)
天|空|是|什|么|顏|色|?|
讓我們檢查其中一個塊:
chunks[1]
AIMessageChunk(content='天', id='run-b36bea64-5511-4d7a-b6a3-a07b3db0c8e7')
我們得到了一個稱為 AIMessageChunk
的東西。該塊表示 AIMessage
的一部分。 消息塊是可疊加的——可以簡單地將它們相加以獲得到目前為止的響應狀態!
chunks[0] + chunks[1] + chunks[2] + chunks[3] + chunks[4]
AIMessageChunk(content='天空是什么顏色', id='run-b36bea64-5511-4d7a-b6a3-a07b3db0c8e7')
Chain(鏈)
幾乎所有的 LLM 應用程序都涉及不止一步的操作,而不僅僅是調用語言模型。 讓我們使用 LangChain 表達式語言
(LCEL
) 構建一個簡單的鏈,該鏈結合了一個提示、模型和解析器,并驗證流式傳輸是否正常工作。 我們將使用 StrOutputParser 來解析模型的輸出。這是一個簡單的解析器,從 AIMessageChunk
中提取 content
字段,給出模型返回的 token
。
LCEL 是一種_聲明式_的方式,通過將不同的 LangChain 原語鏈接在一起來指定一個“程序”。使用 LCEL 創建的鏈可以自動實現 stream
和 astream
,從而實現對最終輸出的流式傳輸。事實上,使用 LCEL 創建的鏈實現了整個標準 Runnable 接口。
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_template("給我講一個關于{topic}的笑話")
parser = StrOutputParser()
chain = prompt | model | parser
async for chunk in chain.astream({"topic": "鸚鵡"}):print(chunk, end="|", flush=True)
|一個|人|去|寵|物|店|買|鸚|鵡|。|店|員|說|:“|這|只|鸚|鵡|會|說|話|。”|
|買|回|家|后|,|那|人|發|現|鸚|鵡|只|會|說|一|句|話|:“|我|是|鸚|鵡|。”|
|那|人|就|去|找|店|員|,|說|:“|你|不|是|說|這|只|鸚|鵡|會|說|話|嗎|?|它|只|會|說|‘|我|是|鸚|鵡|’|。”|
|店|員|回|答|:“|它|確|實|會|說|話|,|你|想|它|怎|么|可能|知|道|自|己|是|鸚|鵡|呢|?”||
請注意,即使我們在上面的鏈條末尾使用了parser
,我們仍然可以獲得流式輸出。parser
會對每個流式塊進行操作。許多LCEL基元也支持這種轉換式的流式傳遞,這在構建應用程序時非常方便。
自定義函數可以被設計為返回生成器,這樣就能夠操作流。
某些可運行實體,如提示模板和聊天模型,無法處理單個塊,而是聚合所有先前的步驟。這些可運行實體可以中斷流處理。
LangChain表達語言允許您將鏈的構建與使用模式(例如同步/異步、批處理/流式等)分開。如果這與您構建的內容無關,您也可以依賴于標準的命令式編程方法,通過在每個組件上調用invoke、batch或stream,將結果分配給變量,然后根據需要在下游使用它們。
使用輸入流
如果您想要在輸出生成時從中流式傳輸JSON,該怎么辦呢?
如果您依賴json.loads
來解析部分JSON,那么解析將失敗,因為部分JSON不會是有效的JSON。
您可能會束手無策,聲稱無法流式傳輸JSON。
事實證明,有一種方法可以做到這一點——解析器需要在輸入流上操作,并嘗試將部分JSON“自動完成”為有效狀態。
讓我們看看這樣一個解析器的運行,以了解這意味著什么。
model = ChatOpenAI(model="gpt-4")
parser = StrOutputParser()
chain = (model | JsonOutputParser()# 由于Langchain舊版本中的一個錯誤,JsonOutputParser未能從某些模型中流式傳輸結果
)
async def async_stream():async for text in chain.astream("以JSON 格式輸出法國、西班牙和日本的國家及其人口列表。"'使用一個帶有“countries”外部鍵的字典,其中包含國家列表。'"每個國家都應該有鍵`name`和`population`"):print(text, flush=True)
{}
{'countries': []}
{'countries': [{}]}
{'countries': [{'name': ''}]}
{'countries': [{'name': 'France'}]}
{'countries': [{'name': 'France', 'population': 670}]}
{'countries': [{'name': 'France', 'population': 670810}]}
{'countries': [{'name': 'France', 'population': 67081000}]}
{'countries': [{'name': 'France', 'population': 67081000}, {}]}
{'countries': [{'name': 'France', 'population': 67081000}, {'name': ''}]}
{'countries': [{'name': 'France', 'population': 67081000}, {'name': 'Spain'}]}
{'countries': [{'name': 'France', 'population': 67081000}, {'name': 'Spain', 'population': 467}]}
{'countries': [{'name': 'France', 'population': 67081000}, {'name': 'Spain', 'population': 467330}]}
{'countries': [{'name': 'France', 'population': 67081000}, {'name': 'Spain', 'population': 46733038}]}
{'countries': [{'name': 'France', 'population': 67081000}, {'name': 'Spain', 'population': 46733038}, {}]}
{'countries': [{'name': 'France', 'population': 67081000}, {'name': 'Spain', 'population': 46733038}, {'name': ''}]}
{'countries': [{'name': 'France', 'population': 67081000}, {'name': 'Spain', 'population': 46733038}, {'name': 'Japan'}]}
{'countries': [{'name': 'France', 'population': 67081000}, {'name': 'Spain', 'population': 46733038}, {'name': 'Japan', 'population': 126}]}
{'countries': [{'name': 'France', 'population': 67081000}, {'name': 'Spain', 'population': 46733038}, {'name': 'Japan', 'population': 126300}]}
{'countries': [{'name': 'France', 'population': 67081000}, {'name': 'Spain', 'population': 46733038}, {'name': 'Japan', 'population': 126300000}]}
Stream events(事件流)
現在我們已經了解了stream
和astream
的工作原理,讓我們進入事件流的世界。🏞?
事件流是一個beta API。這個API可能會根據反饋略微更改。
本指南演示了V2
API,并且需要 langchain-core >= 0.2。對于與舊版本 LangChain 兼容的V1
API,請參閱這里。
import langchain_core
langchain_core.__version__
為了使astream_events
API正常工作:
- 在代碼中盡可能使用
async
(例如,異步工具等) - 如果定義自定義函數/可運行項,請傳播回調
- 在沒有 LCEL 的情況下使用可運行項時,請確保在LLMs上調用
.astream(
而不是.ainvoke
以強制LLM流式傳輸令牌
事件參考
下面是一個參考表,顯示各種可運行對象可能發出的一些事件。
當流式傳輸正確實現時,對于可運行項的輸入直到輸入流完全消耗后才會知道。這意味著inputs
通常僅包括end
事件,而不包括start
事件。
事件 | 名稱 | 塊 | 輸入 | 輸出 |
---|---|---|---|---|
on_chat_model_start | [模型名稱] | {“messages”: [[SystemMessage, HumanMessage]]} | ||
on_chat_model_end | [模型名稱] | {“messages”: [[SystemMessage, HumanMessage]]} | AIMessageChunk(content=“hello world”) | |
on_llm_start | [模型名稱] | {‘input’: ‘hello’} | ||
on_llm_stream | [模型名稱] | ‘Hello’ | ||
on_llm_end | [模型名稱] | ‘Hello human!’ | ||
on_chain_start | format_docs | |||
on_chain_stream | format_docs | “hello world!, goodbye world!” | ||
on_chain_end | format_docs | [Document(…)] | “hello world!, goodbye world!” | |
on_tool_start | some_tool | {“x”: 1, “y”: “2”} | ||
on_tool_end | some_tool | {“x”: 1, “y”: “2”} | ||
on_retriever_start | [檢索器名稱] | {“query”: “hello”} | ||
on_retriever_end | [檢索器名稱] | {“query”: “hello”} | [Document(…), …] | |
on_prompt_start | [模板名稱] | {“question”: “hello”} | ||
on_prompt_end | [模板名稱] | {“question”: “hello”} | ChatPromptValue(messages: [SystemMessage, …]) |
聊天模型
讓我們首先看一下聊天模型產生的事件。
events = []
async for event in model.astream_events("hello", version="v2"):events.append(event)
/home/eugene/src/langchain/libs/core/langchain_core/_api/beta_decorator.py:87: LangChainBetaWarning: This API is in beta and may change in the future.warn_beta(
嘿,API中那個有趣的version="v2"
參數是什么意思?😾 這是一個beta API,我們幾乎肯定會對其進行一些更改(事實上,我們已經做了!) 這個版本參數將允許我們最小化對您代碼的破壞性更改。 簡而言之,我們現在讓您感到煩惱,這樣以后就不必再煩惱了。 v2
僅適用于 langchain-core>=0.2.0。
讓我們看一下一些開始事件和一些結束事件。
events[:3]
[{'event': 'on_chat_model_start','data': {'input': 'hello'},'name': 'ChatAnthropic','tags': [],'run_id': 'a81e4c0f-fc36-4d33-93bc-1ac25b9bb2c3','metadata': {}},{'event': 'on_chat_model_stream','data': {'chunk': AIMessageChunk(content='Hello', id='run-a81e4c0f-fc36-4d33-93bc-1ac25b9bb2c3')},'run_id': 'a81e4c0f-fc36-4d33-93bc-1ac25b9bb2c3','name': 'ChatAnthropic','tags': [],'metadata': {}},{'event': 'on_chat_model_stream','data': {'chunk': AIMessageChunk(content='!', id='run-a81e4c0f-fc36-4d33-93bc-1ac25b9bb2c3')},'run_id': 'a81e4c0f-fc36-4d33-93bc-1ac25b9bb2c3','name': 'ChatAnthropic','tags': [],'metadata': {}}]
events[-2:]
[{'event': 'on_chat_model_stream','data': {'chunk': AIMessageChunk(content='?', id='run-a81e4c0f-fc36-4d33-93bc-1ac25b9bb2c3')},'run_id': 'a81e4c0f-fc36-4d33-93bc-1ac25b9bb2c3','name': 'ChatAnthropic','tags': [],'metadata': {}},{'event': 'on_chat_model_end','data': {'output': AIMessageChunk(content='Hello! How can I assist you today?', id='run-a81e4c0f-fc36-4d33-93bc-1ac25b9bb2c3')},'run_id': 'a81e4c0f-fc36-4d33-93bc-1ac25b9bb2c3','name': 'ChatAnthropic','tags': [],'metadata': {}}]
LLM apps debug: LangSmith Tracing & Verbose, Debug Mode
與構建任何類型的軟件一樣,使用LLM構建時,總會有調試的需求。模型調用可能會失敗,模型輸出可能格式錯誤,或者可能存在一些嵌套的模型調用,不清楚在哪一步出現了錯誤的輸出。 有三種主要的調試方法:
- 詳細模式(Verbose):為你的鏈中的“重要”事件添加打印語句。
- 調試模式(Debug):為你的鏈中的所有事件添加日志記錄語句。
- LangSmith跟蹤:將事件記錄到LangSmith,以便在那里進行可視化。
詳細模式(Verbose Mode) | 調試模式(Debug Mode) | LangSmith跟蹤 | |
---|---|---|---|
免費 | ? | ? | ? |
用戶界面 | ? | ? | ? |
持久化 | ? | ? | ? |
查看所有事件 | ? | ? | ? |
查看“重要”事件 | ? | ? | ? |
本地運行 | ? | ? | ? |
LangSmith Tracing(跟蹤)
使用LangChain構建的許多應用程序將包含多個步驟,其中包含多次LLM調用。 隨著這些應用程序變得越來越復雜,能夠檢查鏈或代理內部發生了什么變得至關重要。 這樣做的最佳方式是使用LangSmith。 在上面的鏈接上注冊后,請確保設置你的環境變量以開始記錄跟蹤:
#windows導入環境變量
setx LANGCHAIN_TRACING_V2 "true"
setx LANGCHAIN_API_KEY "..." #獲取到key
setx TAVILY_API_KEY "..." #獲取到key#mac 導入環境變量
export LANGCHAIN_TRACING_V2="true"
export LANGCHAIN_API_KEY="..."
export TAVILY_API_KEY="..."
假設我們有一個代理,并且希望可視化它所采取的操作和接收到的工具輸出。在沒有任何調試的情況下,這是我們看到的:
import os
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.prompts import ChatPromptTemplate
from langchain.globals import set_verbosellm = ChatOpenAI(model="gpt-4o")
tools = [TavilySearchResults(max_results=1)]
prompt = ChatPromptTemplate.from_messages([("system","你是一位得力的助手。",),("placeholder", "{chat_history}"),("human", "{input}"),("placeholder", "{agent_scratchpad}"),]
)
# 構建工具代理
agent = create_tool_calling_agent(llm, tools, prompt)
set_verbose(True)
# 通過傳入代理和工具來創建代理執行器
agent_executor = AgentExecutor(agent=agent, tools=tools)
agent_executor.invoke({"input": "誰執導了2023年的電影《奧本海默》,他多少歲了?"}
)
{'input': '誰執導了2023年的電影《奧本海默》,他多少歲了?', 'output': '克里斯托弗·諾蘭(Christopher Nolan)出生于1970年7月30日。截至2023年,他53歲。'}
我們沒有得到太多輸出,但由于我們設置了LangSmith,我們可以輕松地看到發生了什么: https://smith.langchain.com/public/a89ff88f-9ddc-4757-a395-3a1b365655bf/r
Verbose(詳細日志打印)
如果你在Jupyter筆記本中進行原型設計或運行Python腳本,打印出鏈運行的中間步驟可能會有所幫助。 有許多方法可以以不同程度的詳細程度啟用打印。 注意:即使啟用了LangSmith,這些仍然有效,因此你可以同時打開并運行它們。
set_verbose(True)
設置 verbose
標志將以稍微更易讀的格式打印出輸入和輸出,并將跳過記錄某些原始輸出(例如 LLM 調用的令牌使用統計信息),以便您可以專注于應用程序邏輯。
from langchain.globals import set_verbose
set_verbose(True)
agent_executor = AgentExecutor(agent=agent, tools=tools)
agent_executor.invoke({"input": "Who directed the 2023 film Oppenheimer and what is their age in days?"}
)
> Entering new AgentExecutor chain...Invoking: `tavily_search_results_json` with `{'query': '2023 movie Oppenheimer director'}`[{'url': 'https://www.imdb.com/title/tt15398776/fullcredits/', 'content': 'Oppenheimer (2023) cast and crew credits, including actors, actresses, directors, writers and more. Menu. ... director of photography: behind-the-scenes Jason Gary ... best boy grip ... film loader Luc Poullain ... aerial coordinator'}]
Invoking: `tavily_search_results_json` with `{'query': 'Christopher Nolan age'}`[{'url': 'https://www.nme.com/news/film/christopher-nolan-fans-are-celebrating-his-54th-birthday-youve-changed-things-forever-3779396', 'content': "Christopher Nolan is 54 Still my fave bit of Nolan trivia: Joey Pantoliano on creating Ralph Cifaretto's look in The Sopranos: 'The wig I had them build as an homage to Chris Nolan, I like ..."}]2023年的電影《奧本海默》由克里斯托弗·諾蘭(Christopher Nolan)執導。他目前54歲。> Finished chain.
{'input': '誰執導了2023年的電影《奧本海默》,他多少歲了?', 'output': '克里斯托弗·諾蘭(Christopher Nolan)出生于1970年7月30日。截至2023年,他53歲。'}
Debug(調試日志打印)
set_debug(True)
設置全局的 debug
標志將導致所有具有回調支持的 LangChain 組件(鏈、模型、代理、工具、檢索器)打印它們接收的輸入和生成的輸出。這是最詳細的設置,將完全記錄原始輸入和輸出。
from langchain.globals import set_debug
# 構建工具代理
agent = create_tool_calling_agent(llm, tools, prompt)
#打印調試日志
set_debug(True)
#不輸出詳細日志
set_verbose(False)
# 通過傳入代理和工具來創建代理執行器
agent_executor = AgentExecutor(agent=agent, tools=tools)
agent_executor.invoke({"input": "誰執導了2023年的電影《奧本海默》,他多少歲了?"}
)
[chain/start] [chain:AgentExecutor] Entering Chain run with input:
{"input": "誰執導了2023年的電影《奧本海默》,他多少歲了?"
}
[chain/start] [chain:AgentExecutor > chain:RunnableSequence] Entering Chain run with input:
{"input": ""
}
[chain/start] [chain:AgentExecutor > chain:RunnableSequence > chain:RunnableAssign<agent_scratchpad>] Entering Chain run with input:
{"input": ""
}
[chain/start] [chain:AgentExecutor > chain:RunnableSequence > chain:RunnableAssign<agent_scratchpad> > chain:RunnableParallel<agent_scratchpad>] Entering Chain run with input:
{"input": ""
}
[chain/start] [chain:AgentExecutor > chain:RunnableSequence > chain:RunnableAssign<agent_scratchpad> > chain:RunnableParallel<agent_scratchpad> > chain:RunnableLambda] Entering Chain run with input:
{"input": ""
}
[chain/end] [chain:AgentExecutor > chain:RunnableSequence > chain:RunnableAssign<agent_scratchpad> > chain:RunnableParallel<agent_scratchpad> > chain:RunnableLambda] [1ms] Exiting Chain run with output:
{"output": []
}
[chain/end] [chain:AgentExecutor > chain:RunnableSequence > chain:RunnableAssign<agent_scratchpad> > chain:RunnableParallel<agent_scratchpad>] [4ms] Exiting Chain run with output:
{"agent_scratchpad": []
}
[chain/end] [chain:AgentExecutor > chain:RunnableSequence > chain:RunnableAssign<agent_scratchpad>] [10ms] Exiting Chain run with output:
{"input": "誰執導了2023年的電影《奧本海默》,他多少歲了?","intermediate_steps": [],"agent_scratchpad": []
}
[chain/start] [chain:AgentExecutor > chain:RunnableSequence > prompt:ChatPromptTemplate] Entering Prompt run with input:
{"input": "誰執導了2023年的電影《奧本海默》,他多少歲了?","intermediate_steps": [],"agent_scratchpad": []
}
[chain/end] [chain:AgentExecutor > chain:RunnableSequence > prompt:ChatPromptTemplate] [1ms] Exiting Prompt run with output:
[outputs]
[llm/start] [chain:AgentExecutor > chain:RunnableSequence > llm:ChatOpenAI] Entering LLM run with input:
{"prompts": ["System: 你是一位得力的助手。\nHuman: 誰執導了2023年的電影《奧本海默》,他多少歲了?"]
}
[llm/end] [chain:AgentExecutor > chain:RunnableSequence > llm:ChatOpenAI] [1.81s] Exiting LLM run with output:
{"generations": [[{"text": "","generation_info": {"finish_reason": "tool_calls","model_name": "gpt-4o-2024-05-13","system_fingerprint": "fp_4e2b2da518"},"type": "ChatGenerationChunk","message": {"lc": 1,"type": "constructor","id": ["langchain","schema","messages","AIMessageChunk"],"kwargs": {"content": "","additional_kwargs": {"tool_calls": [{"index": 0,"id": "call_Rhv2KLzFTU0XhJso5F79EiUp","function": {"arguments": "{\"query\":\"2023年電影《奧本海默》導演\"}","name": "tavily_search_results_json"},"type": "function"}]},"response_metadata": {"finish_reason": "tool_calls","model_name": "gpt-4o-2024-05-13","system_fingerprint": "fp_4e2b2da518"},"type": "AIMessageChunk","id": "run-cbeb35e8-b4ee-4c78-b663-e338ef90382d","tool_calls": [{"name": "tavily_search_results_json","args": {"query": "2023年電影《奧本海默》導演"},"id": "call_Rhv2KLzFTU0XhJso5F79EiUp","type": "tool_call"}],"tool_call_chunks": [{"name": "tavily_search_results_json","args": "{\"query\":\"2023年電影《奧本海默》導演\"}","id": "call_Rhv2KLzFTU0XhJso5F79EiUp","index": 0,"type": "tool_call_chunk"}],"invalid_tool_calls": []}}}]],"llm_output": null,"run": null
}
[chain/start] [chain:AgentExecutor > chain:RunnableSequence > parser:ToolsAgentOutputParser] Entering Parser run with input:
[inputs]
[chain/end] [chain:AgentExecutor > chain:RunnableSequence > parser:ToolsAgentOutputParser] [2ms] Exiting Parser run with output:
[outputs]
[chain/end] [chain:AgentExecutor > chain:RunnableSequence] [1.85s] Exiting Chain run with output:
[outputs]
[tool/start] [chain:AgentExecutor > tool:tavily_search_results_json] Entering Tool run with input:
"{'query': '2023年電影《奧本海默》導演'}"
[tool/end] [chain:AgentExecutor > tool:tavily_search_results_json] [2.06s] Exiting Tool run with output:
"[{'url': 'https://baike.baidu.com/item/奧本海默/58802734', 'content': '《奧本海默》是克里斯托弗·諾蘭自編自導的,由基里安·墨菲主演的傳記電影,該片于2023年7月21日在北美上映,8月30日在中國內地上映,2024年3月29日在日本上映。該片改編自Kai Bird、Martin J. Sherwin的《美國普羅米修斯:奧本海默的勝與悲》,影片《奧本海默》講述了美國"原子彈之父"羅伯特· ...'}]"
[chain/start] [chain:AgentExecutor > chain:RunnableSequence] Entering Chain run with input:
{"input": ""
}
[chain/start] [chain:AgentExecutor > chain:RunnableSequence > chain:RunnableAssign<agent_scratchpad>] Entering Chain run with input:
{"input": ""
}
[chain/start] [chain:AgentExecutor > chain:RunnableSequence > chain:RunnableAssign<agent_scratchpad> > chain:RunnableParallel<agent_scratchpad>] Entering Chain run with input:
{"input": ""
}
[chain/start] [chain:AgentExecutor > chain:RunnableSequence > chain:RunnableAssign<agent_scratchpad> > chain:RunnableParallel<agent_scratchpad> > chain:RunnableLambda] Entering Chain run with input:
{"input": ""
}
[chain/end] [chain:AgentExecutor > chain:RunnableSequence > chain:RunnableAssign<agent_scratchpad> > chain:RunnableParallel<agent_scratchpad> > chain:RunnableLambda] [1ms] Exiting Chain run with output:
[outputs]
[chain/end] [chain:AgentExecutor > chain:RunnableSequence > chain:RunnableAssign<agent_scratchpad> > chain:RunnableParallel<agent_scratchpad>] [4ms] Exiting Chain run with output:
[outputs]
[chain/end] [chain:AgentExecutor > chain:RunnableSequence > chain:RunnableAssign<agent_scratchpad>] [10ms] Exiting Chain run with output:
[outputs]
[chain/start] [chain:AgentExecutor > chain:RunnableSequence > prompt:ChatPromptTemplate] Entering Prompt run with input:
[inputs]
[chain/end] [chain:AgentExecutor > chain:RunnableSequence > prompt:ChatPromptTemplate] [1ms] Exiting Prompt run with output:
[outputs]
[llm/start] [chain:AgentExecutor > chain:RunnableSequence > llm:ChatOpenAI] Entering LLM run with input:
{"prompts": ["System: 你是一位得力的助手。\nHuman: 誰執導了2023年的電影《奧本海默》,他多少歲了?\nAI: \nTool: [{\"url\": \"https://baike.baidu.com/item/奧本海默/58802734\", \"content\": \"《奧本海默》是克里斯托弗·諾蘭自編自導的,由基里安·墨菲主演的傳記電影,該片于2023年7月21日在北美上映,8月30日在中國內地上映,2024年3月29日在日本上映。該片改編自Kai Bird、Martin J. Sherwin的《美國普羅米修斯:奧本海默的勝與悲》,影片《奧本海默》講述了美國\\\"原子彈之父\\\"羅伯特· ...\"}]"]
}
[llm/end] [chain:AgentExecutor > chain:RunnableSequence > llm:ChatOpenAI] [1.39s] Exiting LLM run with output:
{"generations": [[{"text": "2023年電影《奧本海默》的導演是克里斯托弗·諾蘭。接下來我將查詢他的年齡。","generation_info": {"finish_reason": "tool_calls","model_name": "gpt-4o-2024-05-13","system_fingerprint": "fp_4e2b2da518"},"type": "ChatGenerationChunk","message": {"lc": 1,"type": "constructor","id": ["langchain","schema","messages","AIMessageChunk"],"kwargs": {"content": "2023年電影《奧本海默》的導演是克里斯托弗·諾蘭。接下來我將查詢他的年齡。","additional_kwargs": {"tool_calls": [{"index": 0,"id": "call_QuKQUKd6YLsgTgZeYcWpk2lN","function": {"arguments": "{\"query\":\"克里斯托弗·諾蘭年齡\"}","name": "tavily_search_results_json"},"type": "function"}]},"response_metadata": {"finish_reason": "tool_calls","model_name": "gpt-4o-2024-05-13","system_fingerprint": "fp_4e2b2da518"},"type": "AIMessageChunk","id": "run-b7ee6125-1af5-4073-b81e-076a859755bd","tool_calls": [{"name": "tavily_search_results_json","args": {"query": "克里斯托弗·諾蘭年齡"},"id": "call_QuKQUKd6YLsgTgZeYcWpk2lN","type": "tool_call"}],"tool_call_chunks": [{"name": "tavily_search_results_json","args": "{\"query\":\"克里斯托弗·諾蘭年齡\"}","id": "call_QuKQUKd6YLsgTgZeYcWpk2lN","index": 0,"type": "tool_call_chunk"}],"invalid_tool_calls": []}}}]],"llm_output": null,"run": null
}
[chain/start] [chain:AgentExecutor > chain:RunnableSequence > parser:ToolsAgentOutputParser] Entering Parser run with input:
[inputs]
[chain/end] [chain:AgentExecutor > chain:RunnableSequence > parser:ToolsAgentOutputParser] [1ms] Exiting Parser run with output:
[outputs]
[chain/end] [chain:AgentExecutor > chain:RunnableSequence] [1.43s] Exiting Chain run with output:
[outputs]
[tool/start] [chain:AgentExecutor > tool:tavily_search_results_json] Entering Tool run with input:
"{'query': '克里斯托弗·諾蘭年齡'}"
[tool/end] [chain:AgentExecutor > tool:tavily_search_results_json] [2.89s] Exiting Tool run with output:
"[{'url': 'https://baike.baidu.com/item/克里斯托弗·諾蘭/5306405', 'content': '克里斯托弗·諾蘭(Christopher Nolan),1970年7月30日出生于英國倫敦,導演、編劇、制片人。1998年4月24日克里斯托弗·諾蘭拍攝的首部故事片《追隨》在舊金山電影節上映。2000年,克里斯托弗·諾蘭憑借著他的《記憶碎片》為他獲得第74屆奧斯卡的提名。2005年,執導《蝙蝠俠》三部曲系列首部電影 ...'}]"
[chain/start] [chain:AgentExecutor > chain:RunnableSequence] Entering Chain run with input:
{"input": ""
}
[chain/start] [chain:AgentExecutor > chain:RunnableSequence > chain:RunnableAssign<agent_scratchpad>] Entering Chain run with input:
{"input": ""
}
[chain/start] [chain:AgentExecutor > chain:RunnableSequence > chain:RunnableAssign<agent_scratchpad> > chain:RunnableParallel<agent_scratchpad>] Entering Chain run with input:
{"input": ""
}
[chain/start] [chain:AgentExecutor > chain:RunnableSequence > chain:RunnableAssign<agent_scratchpad> > chain:RunnableParallel<agent_scratchpad> > chain:RunnableLambda] Entering Chain run with input:
{"input": ""
}
[chain/end] [chain:AgentExecutor > chain:RunnableSequence > chain:RunnableAssign<agent_scratchpad> > chain:RunnableParallel<agent_scratchpad> > chain:RunnableLambda] [1ms] Exiting Chain run with output:
[outputs]
[chain/end] [chain:AgentExecutor > chain:RunnableSequence > chain:RunnableAssign<agent_scratchpad> > chain:RunnableParallel<agent_scratchpad>] [4ms] Exiting Chain run with output:
[outputs]
[chain/end] [chain:AgentExecutor > chain:RunnableSequence > chain:RunnableAssign<agent_scratchpad>] [9ms] Exiting Chain run with output:
[outputs]
[chain/start] [chain:AgentExecutor > chain:RunnableSequence > prompt:ChatPromptTemplate] Entering Prompt run with input:
[inputs]
[chain/end] [chain:AgentExecutor > chain:RunnableSequence > prompt:ChatPromptTemplate] [2ms] Exiting Prompt run with output:
[outputs]
[llm/start] [chain:AgentExecutor > chain:RunnableSequence > llm:ChatOpenAI] Entering LLM run with input:
{"prompts": ["System: 你是一位得力的助手。\nHuman: 誰執導了2023年的電影《奧本海默》,他多少歲了?\nAI: \nTool: [{\"url\": \"https://baike.baidu.com/item/奧本海默/58802734\", \"content\": \"《奧本海默》是克里斯托弗·諾蘭自編自導的,由基里安·墨菲主演的傳記電影,該片于2023年7月21日在北美上映,8月30日在中國內地上映,2024年3月29日在日本上映。該片改編自Kai Bird、Martin J. Sherwin的《美國普羅米修斯:奧本海默的勝與悲》,影片《奧本海默》講述了美國\\\"原子彈之父\\\"羅伯特· ...\"}]\nAI: 2023年電影《奧本海默》的導演是克里斯托弗·諾蘭。接下來我將查詢他的年齡。\nTool: [{\"url\": \"https://baike.baidu.com/item/克里斯托弗·諾蘭/5306405\", \"content\": \"克里斯托弗·諾蘭(Christopher Nolan),1970年7月30日出生于英國倫敦,導演、編劇、制片人。1998年4月24日克里斯托弗·諾蘭拍攝的首部故事片《追隨》在舊金山電影節上映。2000年,克里斯托弗·諾蘭憑借著他的《記憶碎片》為他獲得第74屆奧斯卡的提名。2005年,執導《蝙蝠俠》三部曲系列首部電影 ...\"}]"]
}
[llm/end] [chain:AgentExecutor > chain:RunnableSequence > llm:ChatOpenAI] [885ms] Exiting LLM run with output:
{"generations": [[{"text": "克里斯托弗·諾蘭(Christopher Nolan)出生于1970年7月30日。根據當前時間(2023年),他53歲。","generation_info": {"finish_reason": "stop","model_name": "gpt-4o-2024-05-13","system_fingerprint": "fp_4e2b2da518"},"type": "ChatGenerationChunk","message": {"lc": 1,"type": "constructor","id": ["langchain","schema","messages","AIMessageChunk"],"kwargs": {"content": "克里斯托弗·諾蘭(Christopher Nolan)出生于1970年7月30日。根據當前時間(2023年),他53歲。","response_metadata": {"finish_reason": "stop","model_name": "gpt-4o-2024-05-13","system_fingerprint": "fp_4e2b2da518"},"type": "AIMessageChunk","id": "run-0cc2156a-5a9d-41c2-b8bc-ecb2a291f408","tool_calls": [],"invalid_tool_calls": []}}}]],"llm_output": null,"run": null
}
[chain/start] [chain:AgentExecutor > chain:RunnableSequence > parser:ToolsAgentOutputParser] Entering Parser run with input:
[inputs]
[chain/end] [chain:AgentExecutor > chain:RunnableSequence > parser:ToolsAgentOutputParser] [1ms] Exiting Parser run with output:
[outputs]
[chain/end] [chain:AgentExecutor > chain:RunnableSequence] [914ms] Exiting Chain run with output:
[outputs]
[chain/end] [chain:AgentExecutor] [9.25s] Exiting Chain run with output:
{"output": "克里斯托弗·諾蘭(Christopher Nolan)出生于1970年7月30日。根據當前時間(2023年),他53歲。"
}
{'input': '誰執導了2023年的電影《奧本海默》,他多少歲了?', 'output': '克里斯托弗·諾蘭(Christopher Nolan)出生于1970年7月30日。根據當前時間(2023年),他53歲。'}