Langchain 的核心模組與 LLM 應用程式開發基礎
⚠️ 未經同意禁止轉載與再散布。引用時請註明出處 ONDA(온다)。
大家好!我是 ONDA 的 Elliot(이은규, Lee Eun-gyu),負責公司的開發相關業務。
最近 GPT-4、Bard、LLaMa 等科技巨頭的大型語言模型(LLM)競賽成了市場最熱門的話題。在這波典範轉移中,開源主導的小型語言模型與其應用專案也展現亮眼成長,生成式 AI 與語言模型的應用能力有多重要,大家也愈來愈有共識。
LLM 的應用徹底革新了我們處理文字資料的方式,提供分析、生成、應用資料的全新途徑。我們可以用 LLM 自動生成與摘要某個主題的文章,也能得到問題的主動回覆。以前做不到或極度困難的發想,像是把非結構化資料聰明地轉成結構化資料,現在都能輕鬆實現。
**最近這類 LLM 應用的有趣實作與嘗試更是層出不窮。**比如用 LLM 構建能互相交流的 AI 角色群體,在虛擬空間裡進行模擬的研究專案,或是能自主思考、規劃、執行任務的 Auto-GPT 代理專案。
看到這些曾經想像過的點子真的被實作出來,你一定會好奇這些專案是怎麼操作與構建 LLM 的。
這些專案乍看之下好像建立在極其複雜的工程設計與艱澀的數學之上,但當你發現它們的核心概念其實比想像中簡單時,大多數人都會大吃一驚。
今天要介紹的 Langchain 是一個框架,將 LLM 的複雜連鎖作用模組化並簡化控制,讓你能輕鬆打造以語言模型為驅動力的應用程式。
什麼是 Langchain?
Langchain 是 2022 年 10 月推出的開源框架。截至 2023 年 6 月,在 GitHub 上已經獲得將近 44,000 顆星,社群本身的成長爆發力驚人,正在框架之上逐步建立起龐大的生態系。
從名稱就能看出,Langchain 的核心概念是將 LLM 提示詞(Prompt)的執行與外部來源的執行(計算機、Google 搜尋、發送 Slack 訊息或執行程式碼等)串連(Chaining)起來。
可以把它想像成將具有輸入與輸出的媒介一個接一個串起來,形成一個流程(Flow),再把這些流程模組化並串接成一個應用程式的框架。看一下專為 Langchain 設計打造的 GUI 專案 Langflow 的畫面,會更容易理解。
![[出處:Langflow]](https://zqcfqfqgiyckyhazcfrk.supabase.co/storage/v1/object/public/blog-images/webflow/6316f3f75130788ec2d762dd/65942ba2890a8b0c8d799b02_648845871d4f69cdb0ebe3db_OMC3ATW-nMrwn7joRxGwjFVpZtMvR8cKUwry9VSIPq4Mfb9QnS69ykpfr7vOmSm1P89qu8nDFVc19Yb1o9OSlj4bJu2o2FQno0m45foi1vhnOB1vncnxeLZ8-Nh5DWTHg7PyfQRC2AiMM4BT9dw70zA.gif)
Langchain 有預先設定好的模組(Module),使用者可以適當混合這些模組,組合成多個組件(Component),再設定各組件之間的管線(Pipeline)。
模組種類繁多,隨著框架生態系的發展持續增加。多個模組組成一個組件,多個組件再串成鏈(Chain),就像堆疊樂高積木一樣,建構出一個完整的應用程式。
Langchain 的模組種類很多,其中最核心的模組如下:
- LLMs
- Prompt Templates
- Agents
- Tools
這篇文章會說明各模組的角色,並用簡單的範例說明模組如何運作。要注意的是,Langchain 的模組並非全部在同一層級運作(有些模組會要求其他種類的模組作為必要組件),同一個模組也可能依範例性質用於不同用途。就像同一個職務在不同公司有不同 R&R 一樣。
💡除了上述模組,還有 Memory、Vectorstore、Retriever、Text Splitter、Document Loader 等各種領域的模組。也有 Agent Executor、Toolkit 等更細部或更抽象的模組,而且持續在增加,想深入使用框架的話可以去看看。
接下來我們來看各個模組。本文範例使用 Langchain 官方的 JavaScript 移植版 langchain-js。
1. LLMs(Large Language Models)
**LLM 模組是 Langchain 的引擎。**它將不同語言模型或語言模型服務所提供的 API 標準化成 Langchain 其他模組都能使用的介面。
連接到模組的 LLM 可以是任何種類的模型:OpenAI 的 GPT-4、GPT-3.5、Davinci、Ada 模型,Hugging Face Inference API 託管的模型,或是在本機執行的 LLaMa 基礎模型都可以。隨著社群快速發展,大部分的端點都有支援,需要時自己構建介面也不會太難。
而且一個應用程式不一定只能用一個 LLM。開發應用程式時,可以針對需要輕量推理能力的簡單提示詞用 Ada 模型,需要更深入推理能力的工作用自己託管的 Vicuna 模型,需要複雜推理與行動計畫擬定的 Agent 模組用 GPT-4 模型,這樣組合能減少不必要的資源浪費並降低成本。
建立 LLM 模組實例非常簡單。以下是使用 GPT-4 模型建立 LLM 模組實例的範例。
import { ChatOpenAI } from 'langchain/chat_models';
export const gpt4Model = new ChatOpenAI({
temperature: 0.6,
modelName: 'gpt-4',
verbose: true,
streaming: true,
});
這樣建立的 LLM 實例會在建立其他模組時傳入,負責執行提示詞。
2. Prompt Templates
Prompt Template 是將變數輕鬆插入預設提示詞的模板模組。
舉例來說,假設你設計了下面這樣簡單的語言偵測提示詞。
Detect the language of text in <input></input>. Your answer must be in english. don't use language code, return full name of the language in english.
if the language seems like one of programming language (like code blocks) return "english".
if detected language is ambigous, return most popular one.
if you can't detect language, return "english".
use the following format for your answer:
Detected: <detected language>
Example:
<input>
안녕하세요.
</input>
Detected: korean
User Input:
<input>
이 글의 언어를 감지하려고 합니다.
</input>
想要重複使用這個提示詞,並在 [이 글의 언어를 감지하려고 합니다.] 的部分動態傳入不同值做成模板時,可以寫出下面的程式碼。
import { BaseOutputParser } from 'langchain/schema';
import { PromptTemplate } from 'langchain';
const LANG_DETECTION = `Detect the language of text in <input></input>. Your answer must be in english. don't use language code, return full name of the language in english.
if the language seems like one of programming language (like code blocks) return "english".
if detected language is ambigous, return most popular one.
if you can't detect language, return "english".
use the following format for your answer:
Detected: <detected language>
Example:
<input>
안녕하세요.
</input>
Detected: korean
User Input:
<input>
{input}
</input>`;
class LanguageDetectionParser extends BaseOutputParser {
parse = async (output: string): Promise<string> => {
const result = output.match(/Detected: (.*)/);
if (result) {
return result[1];
}
return 'english';
};
getFormatInstructions(): string {
return `Your response should be in following format.
Detected: <detected language>`;
}
}
export const languageDetectionPrompt = new PromptTemplate({
template: LANG_DETECTION,
inputVariables: ['input'],
outputParser: new LanguageDetectionParser(),
});
LANG_DETECTION 字串將撰寫好的提示詞中想要替換的部分改成 {變數名}。如果要使用大括號本身,就寫成 {{}} 兩個大括號。
LanguageDetectionParser 是 OutputParser,負責解析提示詞執行結果並只提取需要的結果。範例中只解析 Detected: 後面接的字串。如果 LLM 模型對該提示詞的執行結果回應 Detected: korean,解析後的 Output 就會是 korean。
languageDetectionPrompt 是完成的 PromptTemplate。除了上述組件,還將使用在模板中的變數名 input 以 inputVariables 傳入。
這樣宣告的 Prompt Template 會透過 LLMChain 模組連接到 LLM 直接使用,或傳給 Agent 由 Agent 執行,成為各種需要提示詞輸入的模組的輸入來源。
3. Agents
Agent 不只是 Langchain 中最核心的模組,也負責執行最複雜且精密的思考任務。
Agent 的概念源自幾篇核心的生成式 AI 論文,每當有新的方法提出就會隨之持續改進。為了更完整理解 Agent 的概念,以下列出幾個重要的參考資料:
- Chain-of-Thought Prompting Elicits Reasoning in Large Language Models
- Toolformer: Language Models Can Teach Themselves to Use Tools
- ReAct: Synergizing Reasoning and Acting in Language Models
不需要全部讀完,但讀過的話對掌握 Agent 的概念與運作原理會很有幫助。
簡單來說,Agent 會
1. 為了執行交付的任務(Task),根據可用的工具(Tools)與當前狀況進行思考(Thought),設計下一步需要的行動(Action planning/Reasoning)。
2. 設計完成後,以適當的輸入執行目前需要的 Action。
3. Action 執行完畢後分析執行結果(Observation),並根據分析結果與至今執行過的 Action 結果重複 1~3 的作業(Chain of thought)。
4. 分析結果後,如果任務已完成或可以完成,就結束作業。
這樣一來,Agent 就能主動活用給定的資源執行任務。
Agent 負責的角色複雜,實作方式也不一而足。**即使使用同樣的 Langchain,構建 Agent 的方法也有很多種,依使用用途需要的模組組成或提示詞形態可能完全不同。**因此,本文將參考 Langchain 官方文件提供的 Agent 基本範例,說明使用的各個元素。
💡即使是同樣角色的 Agent,根據 Agent Executor 的實作,執行樣態也可能不同。上面說明的步驟是最基本的 Action Agent,也有不只推理當前步驟的 Action、而是一開始就構想整個執行的所有步驟並逐步執行的 Plan-and-Execute Agent 等其他 Agent。
import { initializeAgentExecutorWithOptions } from "langchain/agents";
import { OpenAI } from "langchain/llms/openai";
import { SerpAPI } from "langchain/tools";
import { Calculator } from "langchain/tools/calculator";
const model = new OpenAI({ temperature: 0 });
const tools = [
new SerpAPI(process.env.SERPAPI_API_KEY, {
location: "Austin,Texas,United States",
hl: "en",
gl: "us",
}),
new Calculator(),
];
const executor = await initializeAgentExecutorWithOptions(tools, model, {
agentType: "zero-shot-react-description",
verbose: true,
});
const input = `Who is Olivia Wilde's boyfriend? What is his current age raised to the 0.23 power?`;
const result = await executor.call({ input });
這是 LangchainJs 官方文件參考的 MRKL(Modular Reasoning, Knowledge and Language;讀作「miracle」)Agent 範例。
和上面說的不同,範例程式碼非常簡單。因為定義成 zero-shot-react-description 的 agentType 是一種預設組態,包含了 Agent 構建需要的許多元素。
I need to find out who Olivia Wilde's boyfriend is and then calculate his age raised to the 0.23 power.
Action: Search
Action Input: "Olivia Wilde boyfriend"
Observation: Olivia Wilde started dating Harry Styles after ending her years-long engagement to Jason Sudeikis — see their relationship timeline.
Thought: I need to find out Harry Styles' age.
Action: Search
Action Input: "Harry Styles age"
Observation: 29 years
Thought: I need to calculate 29 raised to the 0.23 power.
Action: Calculator
Action Input: 29^0.23
Observation: Answer: 2.169459462491557
Thought: I now know the final answer.
Final Answer: Harry Styles, Olivia Wilde's boyfriend, is 29 years old and his age raised to the 0.23 power is 2.169459462491557.
執行這段程式碼後,可以觀察到 Agent 收到使用者的第一個問題 [Who is Olivia Wilde's boyfriend? What is his current age raised to the 0.23 power?] 後
1. 使用 SerpAPI 在 Google 搜尋 Olivia Wilde 的男友是誰
2. 從上面得知 Harry Styles 是 Olivia Wilde 的男友,搜尋他的年齡
3. 得知他 29 歲後,使用 Calculator Tool 計算 29 的 0.23 次方
4. 計算結果出來後,生成對使用者問題的最終回答
它會自行判斷並依序執行上述過程。
💡如果想知道上述過程如何運作,可以提供自訂 CallBackManager 來監控 LLM 模型的輸入/輸出並進行分析。也可以閱讀官方文件的 Custom Agents 部分,分析程式碼層級的運作過程。Langchain 框架相較於提供的功能,文件化(Documentation)的範圍相當有限。直接參考程式碼儲存庫來使用的話,能更輕鬆使用更多樣的進階功能。
用過 Auto GPT 的人應該對這樣的運作過程很熟悉。Auto GPT 也是在精心編排的 Agent 鏈之上提供各種可用 Tool 的方式設計的大型 Agent,好奇 Auto GPT 如何運作的人,看完應該能解開部分疑惑。
4. Tools
Tool 是 Agent 執行各 Action 時可使用的抽象化函式。
Agent 會收到可用 Tool 的清單,並連同以下數值一起當作提示詞的一部分傳入:
- Tool 的名稱
- 該 Tool 的說明(Description)(執行什麼角色、輸入該如何傳遞等)
interface Tool {
call(arg: string): Promise<string>;
name: string;
description: string;
}
Langchain 定義的 Tool Interface 如上。簡單但靈活,只要以字串回傳結果值,call 內部執行什麼動作都可以。
連由多個不同模組構成的複雜 Agent 也能以 Tool 的形式提供給其他 Agent,這個簡單但強大的 Interface 是讓 Langchain 的應用可能性無窮無盡的要素之一。
以下範例是使用簡單 LLMChain 的 Calculator,在將輸入的算式值傳給 Calculator 之前,為了確保算式值以 Calculator 能理解的形態輸入而設計用來淨化輸入值的 Calculator 工具。
import { LLMChain, PromptTemplate } from 'langchain';
import { BaseOutputParser } from 'langchain/schema';
import { Calculator, Tool } from 'langchain/tools';
const CALC_EQUATION_GENERATE = `
Generate a calculation equation from contents of <input> block. generated equation must be valid numeric calculation equation.
You need to convert words to numbers, if feasible.
Do not try to calculate the equation, just generate it.
If the input cannot be converted to a valid equation, return <EOF/>.
If the input is already a valid equation, return itself.
Provide your response in <output> block.
Example
\`\`\`
<input>
(1 billion plus 1 million) * 12
</input>
<output>
(1000000000 + 1000000) * 12
</output>
\`\`\`
Now, Here's your input.
<input>
{input}
</input>
`;
class CalcEquationParser extends BaseOutputParser {
parse = async (output: string): Promise<string> => {
try {
if (/^<EOF\/>$/.test(output)) {
return '';
}
return /<output>([\s\S]*)<\/output>/.exec(output)[1].trim();
} catch (err) {
return '';
}
};
getFormatInstructions(): string {
return `Provide your response in <output> block.`;
}
}
const calcEquationPrompt = new PromptTemplate({
template: CALC_EQUATION_GENERATE,
inputVariables: ['input'],
outputParser: new CalcEquationParser(),
});
const generateCalcEquationChain = new LLMChain({
llm: gpt3Model,
prompt: calcEquationPrompt,
outputKey: 'data',
});
class GenericCalculator extends Tool {
name = 'calculator';
async _call(input: string) {
const { data: generatedEquation } = await generateCalcEquationChain.call({
input,
});
if (!generatedEquation) {
return `Invalid equation. Please provide a valid equation. Try not to use word for numbers. For example, use 2 instead of two.`;
}
return new Calculator().call(generatedEquation);
}
description =
'Useful for getting the result of a math expression. The input to this tool should be a valid mathmatical expression that could be executed by a simple calculator.';
}
上面的範例除了 Agent 之外,使用了本文介紹的所有模組以及 LLMChain 這個鏈模組。請看看範例,了解 LLMChain 如何運作、PromptTemplate 與 LLM 模型如何連動執行。
本文介紹了 Langchain 與 Langchain 幾個核心模組,以及使用該框架輕鬆開發 LLM 應用程式的基礎概念。
Langchain 現在仍以非常快的速度持續開發中。有本文沒涵蓋的應用範例與更多樣的模組,其中也有許多非常有趣的使用案例,像是應用 VectorStore 實作長期記憶等。如果想應用生成式 AI 開發應用程式,務必參考看看。謝謝。