先前有試著將 Python Langchain 換成 Node 版,但 Langchain node 版的文件真的有點少,因此,轉換起來有點辛苦,因為常常就算有文件,還是漏了參數,程式就炸了,最後還是得對照Python 版本。
首先,我們來看代理組件(AgentExecutor),代理組件在Langchain 框架中蠻核心的組件,代理能根據用戶輸入的內容,由AI 動態決定要使用那個工具,並給出最終的答案。
// 初化模式 const llm = new ChatOllama({ model: 'llama3.2', temperature: 0, maxRetries: 2, baseUrl: 'http://localhost:11434', }); const tools = []; // 工具清單 // 初始化聊天範本 const prompt = ChatPromptTemplate.fromMessages([ ['system', 'You are a helpful assistant'], // default role ['placeholder', '{chat_history}'], // default role ['human', '{input}'], ['placeholder', '{agent_scratchpad}'], ]); const agent = createToolCallingAgent({ llm, tools, prompt, }); const result = await agentExecutor.invoke({ input: 'your question ' });
前面提到,當代理組件,接收使用者問題 > 會依據工具的描述,並讓 AI 決定使用那個工具 > 執行工具 > 最終回應結果給用戶。
import { tool } from '@langchain/core/tools'; ... const greetingTool = tool( async ({ input }: { input: string }) => { return input + ' =='; }, { name: 'greetingTool', description: 'if some one say hello', schema: z.object({ input: z.string(), }) } ); ...
參數說明
SerpAPI 是一個專門設計用來與搜尋引擎互動的 API,特別是 Google 搜尋,SerpAPI 自動化了網頁抓取的過程,讓開發者無需自行管理搜尋引擎的網頁解析和資料提取,簡化了搜尋結果的獲取過程。
import { SerpAPI } from '@langchain/community/tools/serpapi'; let serpApiTool = new SerpAPI('your key ', { location: 'Austin,Texas,United States', // ,代表你要模擬的搜尋位置。在這裡,它指定了搜索應基於美國德州奧斯汀的位置,這可以影響搜尋結果的區域相關性。 hl: 'en', // 語言參數,代表要以哪種語言來顯示搜尋結果。'en' 表示結果會以英文顯示。 gl: 'us', // 國家/地區代碼,用來設定你希望搜尋結果的區域依據。'us' 表示結果會根據美國的地區來產生。 }),
大型語言模型,本身是不具備記憶功能,之所謂能讀懂上下文的問題,因為每次將討論的過程,再傳遞給大型語言模型,去詢問。
Langchain 的momory ,也是透過這個機制,因此將每次用戶問的問題存在 Redis ,讓AI Bot 也具備記憶功能。
npm i @langchain/redis @langchain/core redis @langchain/openai --save
import { RedisChatMessageHistory } from '@langchain/community/stores/message/ioredis'; import { BufferMemory } from 'langchain/memory'; ... const client = new Redis('redis://localhost:30001'); const memory = new BufferMemory({ chatHistory: new RedisChatMessageHistory({ sessionId: 'sessionId:' + new Date().toISOString(), sessionTTL: 300, client, }), aiPrefix: 'ollama', outputKey: 'output', // 沒有會爆錯 memoryKey: 'chat_history', // 沒有會爆錯 inputKey: 'input', // 沒有會爆錯 returnMessages: true, const agentExecutor = new AgentExecutor({ agent, tools, verbose: true, // enable debug handleParsingErrors: true, memory, // redis 記憶功能 returnIntermediateSteps: true, }); ...
import { RedisChatMessageHistory } from '@langchain/community/stores/message/ioredis'; import { Calculator } from '@langchain/community/tools/calculator'; import { SerpAPI } from '@langchain/community/tools/serpapi'; import { ChatPromptTemplate } from '@langchain/core/prompts'; import { tool } from '@langchain/core/tools'; import { ChatOllama } from '@langchain/ollama'; import Redis from 'ioredis'; import { AgentExecutor, createToolCallingAgent } from 'langchain/agents'; import { BufferMemory } from 'langchain/memory'; import { NextApiRequest, NextApiResponse } from 'next/types'; import { z } from 'zod'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method !== 'GET') { return res.status(405).json({ message: 'Only GET requests are allowed' }); } try { const magicTool = tool( async ({ input }: { input: number }) => { return `${input + 2}`; }, { name: 'magic_function', description: 'Applies a magic function to an input.', schema: z.object({ input: z.number(), }), } ); const llm = new ChatOllama({ model: 'llama3.2', temperature: 0, maxRetries: 2, baseUrl: 'http://localhost:11434', // other params... }); const tools = [ magicTool, // custom tool new Calculator(), new SerpAPI('your serp key', { location: 'Austin,Texas,United States', hl: 'en', gl: 'us', }), ]; const prompt = ChatPromptTemplate.fromMessages([ ['system', 'You are a helpful assistant'], // default role ['placeholder', '{chat_history}'], // default role ['human', '{input}'], ['placeholder', '{agent_scratchpad}'], ]); const agent = createToolCallingAgent({ llm, tools, prompt, }); const client = new Redis('redis://localhost:30001'); const memory = new BufferMemory({ chatHistory: new RedisChatMessageHistory({ sessionId: 'sessionId:' + new Date().toISOString(), sessionTTL: 300, client, }), aiPrefix: 'ollama', outputKey: 'output', // 沒有會爆錯 memoryKey: 'chat_history', // 沒有會爆錯 inputKey: 'input', // 沒有會爆錯 returnMessages: true, }); const agentExecutor = new AgentExecutor({ agent, tools, verbose: true, // enable debug handleParsingErrors: true, memory, // redis 記憶功能 returnIntermediateSteps: true, }); const result = await agentExecutor.invoke({ input: 'what is the value of magic_function(3)?' }); const serpResult2 = await agentExecutor.invoke({ input: 'what is the Pokomon?' }); const result3 = await agentExecutor.invoke({ input: 'what is 5 + 2 *5 =' }); const result4 = await agentExecutor.invoke({ input: 'hello', outputKey: 'key1' }); const result5 = await agentExecutor.invoke({ input: '幫我推薦電腦' }); return res.status(200).json({ result, serpResult2, result3, result4, result5 }); } catch (error) { return res.status(500).json({ message: 'Internal server error' }); } }