このページではlangchainを通じて、ローカルのLLMモデルに自作ツールを使うエージェントを構築し、対話する試みを紹介します。
ローカルモデルはOllamaを通じて実行します。
前提
- TypeScriptを使用しています。
- denoを使用しています。
- npmを使う場合は事前にlangchainをインストールする必要があります。
- Ollamaがインストールされており、正常に動作していること
- meta製のllamaモデルなど、Ollamaで実行できるモデルがダウンロード済みであること
-
ollama pull llama3.2:1b
などでダウンロードできます
環境構築の手順
# Linux, macOSの場合
curl -fsSL https://deno.land/install.sh | sh
# windowの場合
irm https://deno.land/install.ps1 | iex
- Node.js + npmの場合は以下のパッケージをインストールする必要があります:
npm install langchain
npm install @langchain/community
npm install @langchain/core
npm install zod
# macOSの場合
brew install ollama
# Linuxの場合
curl -fsSL https://ollama.com/install.sh | sh
- Ollamaの起動
ollama serve
- モデルのダウンロード
ollama pull llama2
# もしくは
ollama pull llama3.2:1b
- モデルの動作確認
ollama run llama3.2:1b "Hello!"
注意事項
- Ollamaは常時起動している必要があります
- 初回実行時はモデルのロードに時間がかかる場合があります
- Silicon Mac以外の場合、実行速度が遅くなる可能性があります
- Windows環境の場合はWSL2経由での実行を推奨します
計算を実行するエージェントの構築
ディレクトリ構成は以下のようになります。
.
├── calculator.ts
├── llm.ts
└── main.ts
計算ツール
計算ツールを自作します。
例えば、以下のようにして、質問を自然言語で理解して、どの四則演算を使うか判断し、計算を実行する「計算機tool」を自作します。
参考:
/** 計算ツールの定義 */
import { DynamicStructuredTool } from "npm:@langchain/core/tools";
import { z } from "npm:zod";
const calculatorSchema = z.object({
operation: z
.enum(["add", "subtract", "multiply", "divide"])
.describe("実行する操作の種類"),
number1: z.number().describe("操作する最初の数値"),
number2: z.number().describe("操作する2番目の数値"),
});
// tools
export const calculatorTool = new DynamicStructuredTool({
name: "calculator",
description: "計算機として足し算、引き算、掛け算、割り算を行います。",
schema: calculatorSchema,
func: async ({ operation, number1, number2 }) => {
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("Invalid operation.");
}
},
});
カスタムクラス
ChatOpenAIでは実行できましたが、ChatOllamaではツール実行時にエラーが出てしまいます。
error: Uncaught (in promise) TypeError: llm.bindTools is not a function
export const llmWithTools = llm.bindTools([calculatorTool]);
下記ページで一時的な対策を見つけました。
自作クラスでChatOllamaを拡張しています。
/** ChatOllamaにbindTools()メソッドを追加するクラス拡張 */
import { ChatOllama } from "npm:@langchain/community/chat_models/ollama";
interface ModelInterface {
model: string;
baseUrl: string;
temperature: number;
}
export class ChatOllamaWithTools extends ChatOllama {
constructor({ model, baseUrl, temperature }: ModelInterface) {
super({ model, baseUrl, temperature });
}
bindTools(tools) {
// Implement the logic to bind tools to your model
this.tools = tools;
return this;
}
// Implement other necessary methods from BaseChatModel
}
実際に使う
queryに質問を入力して実行します。
const query = "123456と654321の和は?";
const result = await askAgent(query);
console.log(result);
// => { input: "123456と654321の和は?", output: "123456 + 654321 = 777777" }
質問の形式は
- 計算の文章題(例: 3と2の和は?, 56かける12は?, 4+6=)
- 数字を2ついれる
/** agentを使うメイン */
import { AgentExecutor, createToolCallingAgent } from "npm:langchain/agents";
import { ChatOllamaWithTools } from "./llm.ts";
import { calculatorTool } from "./calculator.ts";
import { ChatPromptTemplate } from "npm:@langchain/core/prompts";
interface AgentInterface {
input: string;
output: string;
}
const askAgent = async (input: string): Promise<AgentInterface> => {
// Ollamaの代わりに OpenAIを使う場合
//
// import { ChatOpenAI } from "npm:@langchain/openai";
// const llm = new ChatOpenAI({
// model: "gpt-4o-mini",
// temperature: 0,
// });
const llm = new ChatOllamaWithTools({
model: "llama3.2:3b",
baseUrl: "http://192.168.100.123:11434",
temperature: 0,
});
const tools = [calculatorTool];
const prompt = ChatPromptTemplate.fromMessages([
["system", "You are a helpful assistant"],
["placeholder", "{chat_history}"],
["human", "{input}"],
["placeholder", "{agent_scratchpad}"],
]);
// エージェントの作成
const agent = await createToolCallingAgent({ llm, tools, prompt });
const agentExecutor = new AgentExecutor({ agent, tools });
// エージェントに問い合わせ
const result = await agentExecutor.invoke({ input });
return result;
};
const main = async () => {
const query = "123456と654321の和は?";
const result = await askAgent(query);
console.log(result);
// => { input: "123456と654321の和は?", output: "123456 + 654321 = 777777" }
};
await main();
llm.tsからモデルをインポートします。
calculator.tsから計算用のツールをインポートします。
llmにチャットモデルを定義します。
ここではOllamaを使うので、モデル名とURLを適宜変更してください。
モデルはmeta製llamaの最新版を使いました。
URLは外部のホストでなければhttp://localhost:11434
で良いです。
localhostの場合はChatOllama内部でデフォルトhttp://localhost:11434
を設定してくれるそうです? 未確認。localhostであればbaseUrlは設定しなくても良さそうです。
誤った結果を出さないために、ツールを使うときにはtemperatureを0にしておくのが定石のようです。
OpenAIモデルなどを使うときはコメントアウト内を参照してください。
toolsにインポートしたcalculatorToolを入れます。
プロンプトはChatPromptTemplateを使って適宜変更します。
プロンプトの解説は公式 How to use legacy LangChain Agents (AgentExecutor) を参照してください。
agentを上記で定義したllm, tools, promptにより組み立て、 AgentExecuter.invoke()
で質問に答えてもらいます。
まとめ
この記事では、以下の内容を解説しました:
- LangChainを使用して四則演算を行うカスタムツールの作成方法
- ChatOllamaをツール機能ありで使用するための拡張クラスの実装
- エージェントを構築して自然言語での計算問題を解決する方法
主なポイント:
- zodによる型安全な計算ツールの定義
- ChatOllamaの制限を回避するためのカスタムクラス実装
- ローカルでLLMを動かすためのOllama環境の準備
これにより、OpenAIのAPIに依存せず、ローカル環境でカスタムツールを扱うLLMを実現できます。