LangChain で Tools 呼び出す(Node.js)
はじめに
この記事では、LangChain で Tools を呼び出す方法を紹介します。具体的には以下の記事を参考に記述します。
TypeScript / JavaScript での GitHub リポジトリーを公開している実装例はすくないので記事化しました。作業リポジトリはこちらです。
LangChain x TypeScript での実装例を以下の記事で紹介しています。
- LangChain で 簡易LLMアプリを構築(Node.js)
- LangChain でチャットボットを構築(Node.js)
- LangChain で構造化データを取得(Node.js)
- LangChain で Tools 呼び出す(Node.js)
- LangChain で Runnable をシクエンシャルに結合(Node.js)
- LangChain で Runnable を並列実行(Node.js)
- LangChain で 外部からデータを参照 前編(Node.js)
- LangChain で 外部からデータを参照 後編(Node.js)
- LangChain で Fallbacks(Node.js)
LangChain とは
LangChain は、大規模言語モデル(LLM)を活用したアプリケーションの開発を支援するフレームワークです。
作業プロジェクトの準備
作業用の TypeScript プロジェクトを作成します。
長いので折りたたんでおきます。
package.json を作成
package.json
を作成します。
$ mkdir -p langchain-calling-tools-demo
$ cd langchain-calling-tools-demo
$ pnpm init
下記で package.json
を上書きします。
{
"name": "langchain-calling-tools-demo",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"scripts": {
"typecheck": "tsc --noEmit",
"dev": "vite-node index.ts",
"build": "tsc"
},
"keywords": [],
"author": "",
}
TypeScript & vite-node をインストール
TypeScript と vite-node をインストールします。補足としてこちらの理由のため ts-node
ではなく vite-node
を利用します。
$ pnpm install -D typescript vite-node @types/node
TypeScriptの設定ファイルを作成
tsconfig.json
を作成します。
$ npx tsc --init
tsconfig.json
を上書きします。
{
"compilerOptions": {
/* Base Options: */
"esModuleInterop": true,
"skipLibCheck": true,
"target": "ES2022",
"allowJs": true,
"resolveJsonModule": true,
"moduleDetection": "force",
"isolatedModules": true,
/* Strictness */
"strict": true,
"noUncheckedIndexedAccess": true,
"checkJs": true,
/* Bundled projects */
"noEmit": true,
"outDir": "dist",
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "preserve",
"incremental": true,
"sourceMap": true,
},
"include": ["**/*.ts", "**/*.js"],
"exclude": ["node_modules", "dist"]
}
git
を初期化します。
$ git init
.gitignore
を作成します。
$ touch .gitignore
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
dist/
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
.env
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
動作確認コードを作成
動作を確認するためのコードを作成します。
$ touch index.ts
console.log('Hello, World');
型チェック
型チェックします。
$ pnpm run typecheck
動作確認
動作確認を実施します。
$ pnpm run dev
Hello, World
コミットします。
$ git add .
$ git commit -m "初回コミット"
LangChain をインストール
LangChain をインストールします。
$ pnpm add langchain @langchain/core
コミットします。
$ git add .
$ git commit -m "LangChainをインストール"
言語モデルの選択
LangChain は、多くの異なる言語モデルをサポートしており、それらを自由に選んで使用できます。
例えば、以下のような言語モデルを選択できます。
- OpenAI
- Anthropic
- FireworksAI
- MistralAI
- Groq
- VertexAI
ここでは OpenAI を利用します。OpenAI を LangChain で利用するためのパッケージをインストールします。
$ pnpm add @langchain/openai
コミットします。
$ git add .
$ git commit -m "LangChainでOpenAIを利用するためのパッケージをインストール"
OpenAI API キーを取得
OpenAI API キーの取得方法はこちらを参照してください。
環境変数の設定
環境変数に OpenAI キーを追加します。<your-api-key>
に自身の API キーを設定してください。
$ touch .env
# OPENAI_API_KEY は OpenAI の API キーです。
OPENAI_API_KEY='<your-api-key>'
Node.js で環境変数を利用するために dotenv
をインストールします。
$ pnpm i -D dotenv
コミットします。
$ touch .env.example
# OPENAI_API_KEY は OpenAI の API キーです。
OPENAI_API_KEY='<your-api-key>'
$ git add .
$ git commit -m "環境変数を設定"
基礎編
まず、シンプルに LLM を使ってみます。
コードを作成
コードを作成します。
$ touch demo01.ts
import { ChatOpenAI } from "@langchain/openai";
import 'dotenv/config'
const model = new ChatOpenAI({
model: "gpt-3.5-turbo",
temperature: 0
});
const result = await model.invoke("猫についてジョークを言ってください");
console.log(result)
ローカルで実行します。
$ pnpm vite-node demo01.ts
AIMessage {
lc_serializable: true,
lc_kwargs: {
content: 'Q: 猫がパソコンを使うときに使うプログラミング言語は何でしょうか?\nA: プロクラスミング言語!',
tool_calls: [],
invalid_tool_calls: [],
additional_kwargs: { function_call: undefined, tool_calls: undefined },
response_metadata: {}
},
lc_namespace: [ 'langchain_core', 'messages' ],
content: 'Q: 猫がパソコンを使うときに使うプログラミング言語は何でしょうか?\nA: プロクラスミング言語!',
name: undefined,
additional_kwargs: { function_call: undefined, tool_calls: undefined },
response_metadata: {
tokenUsage: { completionTokens: 52, promptTokens: 22, totalTokens: 74 },
finish_reason: 'stop'
},
tool_calls: [],
invalid_tool_calls: []
}
コミットします。
$ git add .
$ git commit -m "LLMを使ってみる"
コードの解説
OpenAI の言語モデルを利用するために ChatOpenAI
をインポートします。
import { ChatOpenAI } from "@langchain/openai";
gpt-3.5-turbo
のモデルを選択します。temperature
は 0 に設定します。temperature
が低いほど、モデルの出力はより予測可能になります。
const model = new ChatOpenAI({
model: "gpt-3.5-turbo",
temperature: 0
});
invoke
メソッドを実行し、LLM にリクエストを送信します。AIMessage
のオブジェクトが返されます。
const result = await model.invoke("猫についてジョークを言ってください");
AIMessage {
lc_serializable: true,
lc_kwargs: {
content: 'Q: 猫がパソコンを使うときに使うプログラミング言語は何でしょうか?\nA: プロクラスミング言語!',
tool_calls: [],
invalid_tool_calls: [],
additional_kwargs: { function_call: undefined, tool_calls: undefined },
response_metadata: {}
},
lc_namespace: [ 'langchain_core', 'messages' ],
content: 'Q: 猫がパソコンを使うときに使うプログラミング言語は何でしょうか?\nA: プロクラスミング言語!',
name: undefined,
additional_kwargs: { function_call: undefined, tool_calls: undefined },
response_metadata: {
tokenUsage: { completionTokens: 52, promptTokens: 22, totalTokens: 74 },
finish_reason: 'stop'
},
tool_calls: [],
invalid_tool_calls: []
}
Tools の呼び出し
Tools を呼び出すことで、関数を実行し特定の処理を行うことができます。例えば、以下のように定義することで、計算機として足し算、引き算、掛け算、割り算を想定した Tools を作成します。
// schema
const calculatorSchema = z.object({
operation: z
.enum(["add", "subtract", "multiply", "divide"])
.describe("実行する操作の種類。"),
number1: z.number().describe("操作する最初の数値。"),
number2: z.number().describe("操作する2番目の数値。"),
});
// tools
const calculatorTool = new DynamicStructuredTool({
name: "calculator",
description: "計算機として足し算、引き算、掛け算、割り算を行います。",
schema: calculatorSchema,
func: async ({ operation, number1, number2 }) => {
// Functions must return strings
if (operation === "add") {
return `${number1 + number2}`;
} else if (operation === "subtract") {
return `${number1 - number2}`;
} else if (operation === "multiply") {
return `${number1 * number2}`;
} else if (operation === "divide") {
return `${number1 / number2}`;
} else {
throw new Error("無効な操作。");
}
},
});
Tools には、以下の情報が含まれます。
-
name
: Tools の名前 -
description
: Tools の説明 -
schema
: Tools のスキーマ -
func
: Tools の関数
schema
は、Tools に渡す引数の型を定義します。ユーザーの対話が合致している場合、schema
で指定されているフォーマットで情報が抽出されます。例えば以下のようにユーザーから 3 * 12 は?
という質問があった場合、schema
で定義されたフォーマットに合致しているため、operation
に multiply
、number1
に 3
、number2
に 12
が抽出されます。
const res = await llmWithTools.invoke("3 * 12 は?");
インストール
zod
をインストールします。
$ pnpm add zod
コードを作成
コードを作成します。
$ touch demo02.ts
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";
import { DynamicStructuredTool } from "@langchain/core/tools";
import 'dotenv/config'
// model
const llm = new ChatOpenAI({
model: "gpt-3.5-turbo",
temperature: 0
});
// schema
const calculatorSchema = z.object({
operation: z
.enum(["add", "subtract", "multiply", "divide"])
.describe("実行する操作の種類。"),
number1: z.number().describe("操作する最初の数値。"),
number2: z.number().describe("操作する2番目の数値。"),
});
// tools
const calculatorTool = new DynamicStructuredTool({
name: "calculator",
description: "計算機として足し算、引き算、掛け算、割り算を行います。",
schema: calculatorSchema,
func: async ({ operation, number1, number2 }) => {
// Functions must return strings
if (operation === "add") {
return `${number1 + number2}`;
} else if (operation === "subtract") {
return `${number1 - number2}`;
} else if (operation === "multiply") {
return `${number1 * number2}`;
} else if (operation === "divide") {
return `${number1 / number2}`;
} else {
throw new Error("無効な操作。");
}
},
});
const llmWithTools = llm.bindTools([calculatorTool]);
const res = await llmWithTools.invoke("3 * 12 は?");
console.log(res);
console.log("------------------------\n")
console.log(res.tool_calls);
console.log("------------------------\n\n")
res.tool_calls?.map(async (tool_call) => {
const res2 = await calculatorTool.invoke(tool_call.args)
console.log(res2);
});
const res2 = await llmWithTools.invoke("調子はどうですか?");
console.log(res2);
console.log("------------------------\n")
console.log(res2.tool_calls);
console.log("------------------------\n\n")
ローカルで実行します。
$ pnpm vite-node demo02.ts
AIMessage {
lc_serializable: true,
lc_kwargs: {
content: '',
tool_calls: [ [Object] ],
invalid_tool_calls: [],
additional_kwargs: { function_call: undefined, tool_calls: [Array] },
response_metadata: {}
},
lc_namespace: [ 'langchain_core', 'messages' ],
content: '',
name: undefined,
additional_kwargs: { function_call: undefined, tool_calls: [ [Object] ] },
response_metadata: {
tokenUsage: { completionTokens: 24, promptTokens: 134, totalTokens: 158 },
finish_reason: 'tool_calls'
},
tool_calls: [
{
name: 'calculator',
args: [Object],
id: 'call_aVkmk9folPLy6S8DDWYLp0KZ'
}
],
invalid_tool_calls: []
}
------------------------
[
{
name: 'calculator',
args: { operation: 'multiply', number1: 3, number2: 12 },
id: 'call_aVkmk9folPLy6S8DDWYLp0KZ'
}
]
------------------------
36
AIMessage {
lc_serializable: true,
lc_kwargs: {
content: '元気です!お手伝いできることがあれば教えてください。',
tool_calls: [],
invalid_tool_calls: [],
additional_kwargs: { function_call: undefined, tool_calls: undefined },
response_metadata: {}
},
lc_namespace: [ 'langchain_core', 'messages' ],
content: '元気です!お手伝いできることがあれば教えてください。',
name: undefined,
additional_kwargs: { function_call: undefined, tool_calls: undefined },
response_metadata: {
tokenUsage: { completionTokens: 25, promptTokens: 136, totalTokens: 161 },
finish_reason: 'stop'
},
tool_calls: [],
invalid_tool_calls: []
}
------------------------
[]
------------------------
コミットします。
$ git add .
$ git commit -m "Toolsを呼び出す"
コードの解説
Tools が呼び出されたときに出力する値の型を定義します。
import { z } from "zod";
// schema
const calculatorSchema = z.object({
operation: z
.enum(["add", "subtract", "multiply", "divide"])
.describe("実行する操作の種類。"),
number1: z.number().describe("操作する最初の数値。"),
number2: z.number().describe("操作する2番目の数値。"),
});
DynamicStructuredTool
で Tools を定義します。そして、.bindTools()
メソッドで LLM に Tools をバインドします。
Tools には、以下の情報が含まれます。
-
name
: Tools の名前 -
description
: Tools の説明 -
schema
: Tools のスキーマ -
func
: Tools の関数
func
はユーザー側で実行する関数となります。
import { ChatOpenAI } from "@langchain/openai";
import { DynamicStructuredTool } from "@langchain/core/tools";
// tools
const calculatorTool = new DynamicStructuredTool({
name: "calculator",
description: "Can perform mathematical operations.",
schema: calculatorSchema,
func: async ({ operation, number1, number2 }) => {
// Functions must return strings
if (operation === "add") {
return `${number1 + number2}`;
} else if (operation === "subtract") {
return `${number1 - number2}`;
} else if (operation === "multiply") {
return `${number1 * number2}`;
} else if (operation === "divide") {
return `${number1 / number2}`;
} else {
throw new Error("無効な操作。");
}
},
});
const llmWithTools = llm.bindTools([calculatorTool]);
schema
は、Tools に渡す引数の型を定義します。ユーザーの対話が合致している場合、schema
で指定されているフォーマットで情報が抽出されます。例えば以下のようにユーザーから 3 * 12 は?
という質問があった場合、schema
で定義されたフォーマットに合致しているため、operation
に multiply
、number1
に 3
、number2
に 12
が抽出されます。
const res = await llmWithTools.invoke("3 * 12 は?");
console.log(res);
console.log("------------------------\n")
console.log(res.tool_calls);
console.log("------------------------\n\n")
AIMessage {
lc_serializable: true,
lc_kwargs: {
content: '',
tool_calls: [ [Object] ],
invalid_tool_calls: [],
additional_kwargs: { function_call: undefined, tool_calls: [Array] },
response_metadata: {}
},
lc_namespace: [ 'langchain_core', 'messages' ],
content: '',
name: undefined,
additional_kwargs: { function_call: undefined, tool_calls: [ [Object] ] },
response_metadata: {
tokenUsage: { completionTokens: 24, promptTokens: 134, totalTokens: 158 },
finish_reason: 'tool_calls'
},
tool_calls: [
{
name: 'calculator',
args: [Object],
id: 'call_aVkmk9folPLy6S8DDWYLp0KZ'
}
],
invalid_tool_calls: []
}
------------------------
[
{
name: 'calculator',
args: { operation: 'multiply', number1: 3, number2: 12 },
id: 'call_aVkmk9folPLy6S8DDWYLp0KZ'
}
]
------------------------
さらに、.invoke()
を利用することで、Tools で定義された func
を呼び出し、計算結果を取得できます。
res.tool_calls?.map(async (tool_call) => {
const res2 = await calculatorTool.invoke(tool_call.args)
console.log(res2);
});
36
ちなみに、解析不可能な質問を投げた場合、以下のように tool_calls
が空となり、content
にメッセージが入ります。
const res2 = await llmWithTools.invoke("調子はどうですか?");
console.log(res2);
console.log("------------------------\n")
console.log(res2.tool_calls);
console.log("------------------------\n\n")
ストリーミング
ストリーミングで結果を取得する場合においての Tools の呼び出しを試してみます。
コードを作成
コードを作成します。
$ touch demo03.ts
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";
import { DynamicStructuredTool } from "@langchain/core/tools";
import 'dotenv/config'
import { AIMessageChunk } from "@langchain/core/messages";
// model
const llm = new ChatOpenAI({
model: "gpt-3.5-turbo",
temperature: 0
});
// schema
const calculatorSchema = z.object({
operation: z
.enum(["add", "subtract", "multiply", "divide"])
.describe("実行する操作の種類。"),
number1: z.number().describe("操作する最初の数値。"),
number2: z.number().describe("操作する2番目の数値。"),
});
// tools
const calculatorTool = new DynamicStructuredTool({
name: "calculator",
description: "Can perform mathematical operations.",
schema: calculatorSchema,
func: async ({ operation, number1, number2 }) => {
// Functions must return strings
if (operation === "add") {
return `${number1 + number2}`;
} else if (operation === "subtract") {
return `${number1 - number2}`;
} else if (operation === "multiply") {
return `${number1 * number2}`;
} else if (operation === "divide") {
return `${number1 / number2}`;
} else {
throw new Error("無効な操作。");
}
},
});
const llmWithTools = llm.bindTools([calculatorTool]);
const stream1 = await llmWithTools.stream("308 / 29 は?");
for await (const chunk of stream1) {
console.log(chunk.tool_call_chunks);
// console.log(chunk.invalid_tool_calls);
// console.log(chunk.tool_calls);
console.log("--------------------")
}
const stream2 = await llmWithTools.stream("308 / 29 は?");
let final : AIMessageChunk | undefined = undefined;
for await (const chunk of stream2) {
if (!final) {
final = chunk;
} else {
final = final.concat(chunk);
}
}
final && console.log(final.tool_calls);
ローカルで実行します。
$ pnpm vite-node demo03.ts
[
{
name: 'calculator',
args: '',
id: 'call_Zv1z2gIMFCq4mLwMaHPHO2fO',
index: 0
}
]
--------------------
[ { name: undefined, args: '{"', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: 'operation', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: '":"', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: 'divide', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: '","', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: 'number', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: '1', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: '":', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: '308', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: ',"', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: 'number', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: '2', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: '":', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: '29', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: '}', id: undefined, index: 0 } ]
--------------------
[]
--------------------
[
{
name: 'calculator',
args: { operation: 'divide', number1: 308, number2: 29 },
id: 'call_BaxBdBZ0pkbTJFh4FXLjIp5l'
}
]
10.620689655172415
コミットします。
$ git add .
$ git commit -m "ストリーミング"
コードの解説
stream
メソッドを利用した場合、tool_call_chunks
が出力されます。tool_call_chunks
は、name
, args
, id
, index
を含まれます。以下が tool_call_chunks
を出力した結果です。ストリーミングで出力されていることがわかります。
const stream1 = await llmWithTools.stream("308 / 29 は?");
for await (const chunk of stream1) {
console.log(chunk.tool_call_chunks);
// console.log(chunk.invalid_tool_calls);
// console.log(chunk.tool_calls);
console.log("--------------------")
}
[
{
name: 'calculator',
args: '',
id: 'call_Zv1z2gIMFCq4mLwMaHPHO2fO',
index: 0
}
]
--------------------
[ { name: undefined, args: '{"', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: 'operation', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: '":"', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: 'divide', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: '","', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: 'number', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: '1', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: '":', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: '308', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: ',"', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: 'number', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: '2', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: '":', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: '29', id: undefined, index: 0 } ]
--------------------
[ { name: undefined, args: '}', id: undefined, index: 0 } ]
--------------------
[]
--------------------
AIMessageChunk
の concat
を利用することで、ストリーミングの結果を結合できます。
const stream2 = await llmWithTools.stream("308 / 29 は?");
let final : AIMessageChunk | undefined = undefined;
for await (const chunk of stream2) {
if (!final) {
final = chunk;
} else {
final = final.concat(chunk);
}
}
if (final) {
console.log(final.tool_calls);
...
}
[
{
name: 'calculator',
args: { operation: 'divide', number1: 308, number2: 29 },
id: 'call_BaxBdBZ0pkbTJFh4FXLjIp5l'
}
]
さらに、.invoke()
を利用することで、Tools で定義された func
を呼び出すことができます。
if (final) {
...
final.tool_calls?.map(async (tool_call) => {
const res2 = await calculatorTool.invoke(tool_call.args)
console.log(res2);
});
}
10.620689655172415
Few shotting with tools
Few shotting とは、LLM に対して、少量の例文を与えることで、LLM に事前知識を与えることです。Few shotting と Tools を組み合わせることができます。例えば、ユーザーからの対話を通して LLM に学習させることで、「12 🦜 3 は?」を「12 / 3 は?」の割り算と認識させることができます。
コードを作成
コードを作成します。
$ touch demo04.ts
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";
import { DynamicStructuredTool } from "@langchain/core/tools";
import { HumanMessage, AIMessage, ToolMessage } from "@langchain/core/messages";
import 'dotenv/config'
// model
const llm = new ChatOpenAI({
model: "gpt-3.5-turbo",
temperature: 0
});
// schema
const calculatorSchema = z.object({
operation: z
.enum(["add", "subtract", "multiply", "divide"])
.describe("実行する操作の種類。"),
number1: z.number().describe("操作する最初の数値。"),
number2: z.number().describe("操作する2番目の数値。"),
});
// tools
const calculatorTool = new DynamicStructuredTool({
name: "calculator",
description: "計算機として足し算、引き算、掛け算、割り算を行います。",
schema: calculatorSchema,
func: async ({ operation, number1, number2 }) => {
// Functions must return strings
if (operation === "add") {
return `${number1 + number2}`;
} else if (operation === "subtract") {
return `${number1 - number2}`;
} else if (operation === "multiply") {
return `${number1 * number2}`;
} else if (operation === "divide") {
return `${number1 / number2}`;
} else {
throw new Error("無効な操作。");
}
},
});
const llmWithTools = llm.bindTools([calculatorTool]);
const res = await llmWithTools.invoke("12 🦜 3 は?");
console.log(res);
console.log("------------------------\n")
console.log(res.tool_calls);
console.log("------------------------\n\n")
const res2 = await llmWithTools.invoke([
new HumanMessage("333382 🦜 1932? は?"),
new AIMessage({
content: "",
tool_calls: [
{
id: "12345",
name: "calulator",
args: {
number1: 333382,
number2: 1932,
operation: "divide",
},
},
],
}),
new ToolMessage({
tool_call_id: "12345",
content: "答えは 172.558.",
}),
new AIMessage("答えは 172.558."),
new HumanMessage("12 🦜 3 は?"),
]);
console.log(res2);
console.log("------------------------\n")
console.log(res2.tool_calls);
console.log("------------------------\n\n")
ローカルで実行します。
$ pnpm vite-node demo04.ts
AIMessage {
lc_serializable: true,
lc_kwargs: {
content: '',
tool_calls: [ [Object] ],
invalid_tool_calls: [],
additional_kwargs: { function_call: undefined, tool_calls: [Array] },
response_metadata: {}
},
lc_namespace: [ 'langchain_core', 'messages' ],
content: '',
name: undefined,
additional_kwargs: { function_call: undefined, tool_calls: [ [Object] ] },
response_metadata: {
tokenUsage: { completionTokens: 24, promptTokens: 136, totalTokens: 160 },
finish_reason: 'tool_calls'
},
tool_calls: [
{
name: 'calculator',
args: [Object],
id: 'call_C0dW7jJMDD0uiOaJIF5ZmArT'
}
],
invalid_tool_calls: []
}
------------------------
[
{
name: 'calculator',
args: { operation: 'multiply', number1: 12, number2: 3 },
id: 'call_C0dW7jJMDD0uiOaJIF5ZmArT'
}
]
------------------------
AIMessage {
lc_serializable: true,
lc_kwargs: {
content: '',
tool_calls: [ [Object] ],
invalid_tool_calls: [],
additional_kwargs: { function_call: undefined, tool_calls: [Array] },
response_metadata: {}
},
lc_namespace: [ 'langchain_core', 'messages' ],
content: '',
name: undefined,
additional_kwargs: { function_call: undefined, tool_calls: [ [Object] ] },
response_metadata: {
tokenUsage: { completionTokens: 24, promptTokens: 208, totalTokens: 232 },
finish_reason: 'tool_calls'
},
tool_calls: [
{
name: 'calculator',
args: [Object],
id: 'call_bNk1umuJWOU2J4nnI3L3Vm0L'
}
],
invalid_tool_calls: []
}
------------------------
[
{
name: 'calculator',
args: { number1: 12, number2: 3, operation: 'divide' },
id: 'call_bNk1umuJWOU2J4nnI3L3Vm0L'
}
]
------------------------
[
{
name: 'calculator',
args: { operation: 'multiply', number1: 12, number2: 3 },
id: 'call_ZVJ97D5KTtfLfnLdRGI4kns1'
}
]
------------------------
AIMessage {
lc_serializable: true,
lc_kwargs: {
content: '',
tool_calls: [ [Object] ],
invalid_tool_calls: [],
additional_kwargs: { function_call: undefined, tool_calls: [Array] },
response_metadata: {}
},
lc_namespace: [ 'langchain_core', 'messages' ],
content: '',
name: undefined,
additional_kwargs: { function_call: undefined, tool_calls: [ [Object] ] },
response_metadata: {
tokenUsage: { completionTokens: 24, promptTokens: 208, totalTokens: 232 },
finish_reason: 'tool_calls'
},
tool_calls: [
{
name: 'calculator',
args: [Object],
id: 'call_vTQVGFrj5bgDDHcay2EEAVGt'
}
],
invalid_tool_calls: []
}
------------------------
[
{
name: 'calculator',
args: { number1: 12, number2: 3, operation: 'divide' },
id: 'call_vTQVGFrj5bgDDHcay2EEAVGt'
}
]
------------------------
コミットします。
$ git add .
$ git commit -m "Few shotting with tools"
コードの解説
解析不可能な文字列「12 🦜 3 は?」をユーザーが投げた場合、LLM はよしなに解釈し結果を返そうとします。ここでは掛け算と認識したようです。
const res = await llmWithTools.invoke("12 🦜 3 は?");
console.log(res);
console.log("------------------------\n")
console.log(res.tool_calls);
console.log("------------------------\n\n")
AIMessage {
lc_serializable: true,
lc_kwargs: {
content: '',
tool_calls: [ [Object] ],
invalid_tool_calls: [],
additional_kwargs: { function_call: undefined, tool_calls: [Array] },
response_metadata: {}
},
lc_namespace: [ 'langchain_core', 'messages' ],
content: '',
name: undefined,
additional_kwargs: { function_call: undefined, tool_calls: [ [Object] ] },
response_metadata: {
tokenUsage: { completionTokens: 24, promptTokens: 136, totalTokens: 160 },
finish_reason: 'tool_calls'
},
tool_calls: [
{
name: 'calculator',
args: [Object],
id: 'call_C0dW7jJMDD0uiOaJIF5ZmArT'
}
],
invalid_tool_calls: []
}
------------------------
[
{
name: 'calculator',
args: { operation: 'multiply', number1: 12, number2: 3 },
id: 'call_C0dW7jJMDD0uiOaJIF5ZmArT'
}
]
------------------------
ユーザーからの対話を通して LLM に学習させることで、「12 🦜 3 は?」を「12 / 3 は?」の割り算と認識させることができます。
const res2 = await llmWithTools.invoke([
new HumanMessage("333382 🦜 1932? は?"),
new AIMessage({
content: "",
tool_calls: [
{
id: "12345",
name: "calulator",
args: {
number1: 333382,
number2: 1932,
operation: "divide",
},
},
],
}),
new ToolMessage({
tool_call_id: "12345",
content: "答えは 172.558.",
}),
new AIMessage("答えは 172.558."),
new HumanMessage("12 🦜 3 は?"),
]);
console.log(res2);
console.log("------------------------\n")
console.log(res2.tool_calls);
console.log("------------------------\n\n")
AIMessage {
lc_serializable: true,
lc_kwargs: {
content: '',
tool_calls: [ [Object] ],
invalid_tool_calls: [],
additional_kwargs: { function_call: undefined, tool_calls: [Array] },
response_metadata: {}
},
lc_namespace: [ 'langchain_core', 'messages' ],
content: '',
name: undefined,
additional_kwargs: { function_call: undefined, tool_calls: [ [Object] ] },
response_metadata: {
tokenUsage: { completionTokens: 24, promptTokens: 208, totalTokens: 232 },
finish_reason: 'tool_calls'
},
tool_calls: [
{
name: 'calculator',
args: [Object],
id: 'call_bNk1umuJWOU2J4nnI3L3Vm0L'
}
],
invalid_tool_calls: []
}
------------------------
[
{
name: 'calculator',
args: { number1: 12, number2: 3, operation: 'divide' },
id: 'call_bNk1umuJWOU2J4nnI3L3Vm0L'
}
]
------------------------
言語モデルに合わせたフォーマットの利用
言語モデルに合わせた Format の Tools を利用できます。
例えば、OpenAI では、以下のようなフォーマットを利用できます。
-
type
: ツールのタイプ。現在は常に「function」です。 -
function
: ツールのパラメータを含むオブジェクト。 -
function.name
: 出力するスキーマの名前。 -
function.description
: スキーマの高レベルな説明。 -
function.parameters
: 抽出したいスキーマの詳細を、JSON スキーマオブジェクトとしてフォーマットします。
コードを作成
コードを作成します。
$ touch demo05.ts
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";
import { DynamicStructuredTool } from "@langchain/core/tools";
import "dotenv/config";
// model
const llm = new ChatOpenAI({
model: "gpt-3.5-turbo",
temperature: 0,
});
// schema
const calculatorSchema = z.object({
operation: z
.enum(["add", "subtract", "multiply", "divide"])
.describe("実行する操作の種類。"),
number1: z.number().describe("操作する最初の数値。"),
number2: z.number().describe("操作する2番目の数値。"),
});
const llmWithTools = llm.bind({
tools: [
{
type: "function",
function: {
name: "calculator",
description: "Can perform mathematical operations.",
parameters: {
type: "object",
properties: {
operation: {
type: "string",
description: "The type of operation to execute.",
enum: ["add", "subtract", "multiply", "divide"],
},
number1: { type: "number", description: "First integer" },
number2: { type: "number", description: "Second integer" },
},
required: ["number1", "number2"],
},
},
},
],
});
const res = await llmWithTools.invoke("3 * 12 は?");
console.log(res);
AIMessage {
lc_serializable: true,
lc_kwargs: {
content: '',
tool_calls: [ [Object] ],
invalid_tool_calls: [],
additional_kwargs: { function_call: undefined, tool_calls: [Array] },
response_metadata: {}
},
lc_namespace: [ 'langchain_core', 'messages' ],
content: '',
name: undefined,
additional_kwargs: { function_call: undefined, tool_calls: [ [Object] ] },
response_metadata: {
tokenUsage: { completionTokens: 24, promptTokens: 89, totalTokens: 113 },
finish_reason: 'tool_calls'
},
tool_calls: [
{
name: 'calculator',
args: [Object],
id: 'call_vnxjMOVYYg1yTcf2flkgmZtz'
}
],
invalid_tool_calls: []
}
コミットします。
$ git add .
$ git commit -m "言語モデルに合わせたフォーマットの利用"
コードの解説
以下のように、フォーマットを利用することで、言語モデルに合わせたスキーマを定義できます。
const llmWithTools = llm.bind({
tools: [
{
type: "function",
function: {
name: "calculator",
description: "Can perform mathematical operations.",
parameters: {
type: "object",
properties: {
operation: {
type: "string",
description: "The type of operation to execute.",
enum: ["add", "subtract", "multiply", "divide"],
},
number1: { type: "number", description: "First integer" },
number2: { type: "number", description: "Second integer" },
},
required: ["number1", "number2"],
},
},
},
],
});
const res = await llmWithTools.invoke("3 * 12 は?");
console.log(res);
AIMessage {
lc_serializable: true,
lc_kwargs: {
content: '',
tool_calls: [ [Object] ],
invalid_tool_calls: [],
additional_kwargs: { function_call: undefined, tool_calls: [Array] },
response_metadata: {}
},
lc_namespace: [ 'langchain_core', 'messages' ],
content: '',
name: undefined,
additional_kwargs: { function_call: undefined, tool_calls: [ [Object] ] },
response_metadata: {
tokenUsage: { completionTokens: 24, promptTokens: 89, totalTokens: 113 },
finish_reason: 'tool_calls'
},
tool_calls: [
{
name: 'calculator',
args: [Object],
id: 'call_X3SwB35DWJQ3KKNhaUminvCC'
}
],
invalid_tool_calls: []
}
OpenAI のフォーマットを利用できますが、Zod を利用した LangChain のフォーマットを利用するほうが直感的です。
さいごに
この記事では、LangChain で Tools を呼び出す方法を紹介しました。
作業リポジトリ
作業リポジトリは以下になります。
Discussion