VOOZH about

URL: https://zenn.dev/hayato94087/articles/4f31c689ac3ae4

⇱ LangChain で Tools 呼び出す(Node.js)


JavaScript
TypeScript
OpenAI
LangChain
tech

はじめに

この記事では、LangChain で Tools を呼び出す方法を紹介します。具体的には以下の記事を参考に記述します。

https://js.langchain.com/v0.2/docs/how_to/tool_calling/

TypeScript / JavaScript での GitHub リポジトリーを公開している実装例はすくないので記事化しました。作業リポジトリはこちらです。

https://github.com/hayato94087/langchain-calling-tools-demo

LangChain x TypeScript での実装例を以下の記事で紹介しています。

LangChain とは

LangChain は、大規模言語モデル(LLM)を活用したアプリケーションの開発を支援するフレームワークです。

https://js.langchain.com/v0.2/docs/introduction/

作業プロジェクトの準備

作業用の TypeScript プロジェクトを作成します。

LangChain をインストール

LangChain をインストールします。

$ pnpm add langchain @langchain/core

言語モデルの選択

LangChain は、多くの異なる言語モデルをサポートしており、それらを自由に選んで使用できます。

例えば、以下のような言語モデルを選択できます。

  • OpenAI
  • Anthropic
  • FireworksAI
  • MistralAI
  • Groq
  • VertexAI

ここでは OpenAI を利用します。OpenAI を LangChain で利用するためのパッケージをインストールします。

$ pnpm add @langchain/openai

OpenAI API キーを取得

OpenAI API キーの取得方法はこちらを参照してください。

https://zenn.dev/hayato94087/articles/85378e1f7bc0e5#openai-の-apiキーの取得

環境変数の設定

環境変数に OpenAI キーを追加します。<your-api-key> に自身の API キーを設定してください。

$ touch .env
.env
# OPENAI_API_KEY は OpenAI の API キーです。
OPENAI_API_KEY='<your-api-key>'

Node.js で環境変数を利用するために dotenv をインストールします。

$ pnpm i -D dotenv

基礎編

まず、シンプルに LLM を使ってみます。

コードを作成

コードを作成します。

$ touch demo01.ts
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: []
}

コードの解説

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 で定義されたフォーマットに合致しているため、operationmultiplynumber13number212 が抽出されます。

const res = await llmWithTools.invoke("3 * 12 は?");

インストール

zod をインストールします。

$ pnpm add zod

コードを作成

コードを作成します。

$ touch demo02.ts
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: []
}
------------------------

[]
------------------------

コードの解説

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 で定義されたフォーマットに合致しているため、operationmultiplynumber13number212 が抽出されます。

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
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

コードの解説

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 } ]
--------------------
[]
--------------------

AIMessageChunkconcat を利用することで、ストリーミングの結果を結合できます。

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
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'
 }
]
------------------------

コードの解説

解析不可能な文字列「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
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: []
}

コードの解説

以下のように、フォーマットを利用することで、言語モデルに合わせたスキーマを定義できます。

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 を呼び出す方法を紹介しました。

作業リポジトリ

作業リポジトリは以下になります。

https://github.com/hayato94087/langchain-calling-tools-demo

Discussion

👁 Image