LangChain の DynamicTool を使って DevelopersIO を参考に回答してもらった

2023.03.18

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

背景

ChatGPT に独自の情報を元に検索して欲しいケースがあります。

embeddings を使って事前に情報をベクトル化し 検索した内容のベクトルと照会し、その情報を埋め込むようなアプローチもありますが、 事前に index を作成しておく必要があるため、下準備が必要なこと、膨大なデータ量の場合はコストがかかることが懸念されます。

LangChain の tool の中に Google 検索することができるので、その仕組みを知りたくなって調べてみました。

LangChain とは

LangChain は LLM(Large language model) と LLM にはない他のソースや LLM にはできない計算方法を組み合わせることを目的としたライブラリです。 2023.03.18 時点では javascript, python で提供されています。

Agents とは

LangChain における Agent は LLM がどのアクションを実行するかを決定し、そのアクションを取り、観察を見て、それを繰り返すことで完了するものです。

例えば ZeroShotAgent は以下のようなプロンプトを補完するものです。

ソース

日本語だとこんな感じらしいです。

次の質問にできるだけ答えてください。 次のツールにアクセスできます。

ツールあ: 天気を知りたい時に使用します。入力には日時をいれてください。
ツールい: 今日が何日か知りたい時に使用します。入力は不要です。

次の形式を使用します。
   Question: 回答する必要がある入力質問
   Thought: 何をすべきかを常に考える必要があります
   Action: 実行するアクション。[ツールあ, ツールい] のいずれかである必要があります。
   Action Input: アクションへの入力
   Observation: アクションの結果
   ... (Thought/Action/Action Input/Observation は N 回繰り返すことができます)
   Thought: 最終的な答えがわかりました
   Final Answer: 元の入力質問に対する最終回答

始めてください!

Question: 今日はいい天気ですか?
Thought:

そして、返ってきた返事に対して処理を行い続けます。

Tool とは

上記の Agent が行うアクションを提供するものになります。 ツール名、ツールの説明、ツールが入力を受け取ったときの動作が定義されています。

知りたかった グーグル検索をするアクションも Tool で提供されています。

ソース

DynamicTool とは

https://hwchase17.github.io/langchainjs/docs/modules/agents/custom_tool

独自のツールを定義したい場合、DynamicTool を使用すると、簡単に定義できます。

例えば、クラスメソッドの社長が誰かを提供するツールを作ってみましょう。

コードは以下のようになります。

import { OpenAI } from "langchain/llms";
import { AgentExecutor, ZeroShotAgent } from "langchain/agents";
import { DynamicTool } from "langchain/tools";

export const createExecutor = (openAIApiKey: string) => {
  const llm = new OpenAI({ openAIApiKey });
  const tools = [
    new DynamicTool({
      name: "CLASSMETHODCEO",
      description: "クラスメソッド株式会社の社長が誰か調べる時に使います。",
      func: async (_: string) => "くらにゃん",
    }),
  ];
  const agent = ZeroShotAgent.fromLLMAndTools(llm, tools);
  return AgentExecutor.fromAgentAndTools({ agent, tools, verbose: true });
};

(async function () {
  const executor = createExecutor(process.env.OPENAI_API_KEY || "");
  const resp = await executor.run(process.argv.slice(2).join(" "));
  console.log("%j", { resp });
})();

実際に実行してみます。

$ npm install --save langchain esbuild typescript
$ npx esbuild --bundle --platform=node --external:typeorm index.ts --outfile=dist/index.js

  dist/index.js  3.9mb ⚠️

⚡ Done in 91ms

$ node dist/index.js クラスメソッドの代表の名前を知っていますか?
Entering new agent_executor chain...
 クラスメソッドの代表の名前を調べるには、どのツールを使えばいいか?
Action: CLASSMETHODCEO
Action Input: クラスメソッド
くらにゃん
 確かにクラスメソッドの社長はくらにゃんであることがわかりました。
Final Answer: クラスメソッドの株式会社の社長の名前は、くらにゃんです。
Finished chain.
{"resp":"クラスメソッドの株式会社の社長の名前は、くらにゃんです。"}

DevelopersIO を検索する DynamicTool を作る

では実際に DevelopersIO の検索 を使って記事の URL とタイトルを検索する Tool を定義します。

API では本文の取得も可能でしたが、トークンが大幅に増えてしまい上限に引っかかったため、URL とタイトルのみを渡します。

diff --git a/index.ts b/index.ts
index af1389f..67bfc73 100644
--- a/index.ts
+++ b/index.ts
@@ -1,7 +1,19 @@
 import { OpenAI } from "langchain/llms";
 import { AgentExecutor, ZeroShotAgent } from "langchain/agents";
 import { DynamicTool } from "langchain/tools";
+import { searchDevelopersIO } from "./devio";

+const developersIO = new DynamicTool({
+  name: "DEVELOPERSIO",
+  description:
+    "技術に関する記事のURLとタイトルの一覧を取得します。入力には3つ以内の単語を指定してください。",
+  func: async (query: string) => {
+    const { hits } = await searchDevelopersIO({ query, hitsPerPage: 10 });
+    return (
+      "url,title\n" + hits.map(({ url, title }) => `${url},${title}`).join("\n")
+    );
+  },
+});
 export const createExecutor = (openAIApiKey: string) => {
   const llm = new OpenAI({ openAIApiKey });
   const tools = [
@@ -10,6 +22,7 @@ export const createExecutor = (openAIApiKey: string) => {
       description: "クラスメソッド株式会社の社長が誰か調べる時に使います。",
       func: async (_: string) => "くらにゃん",
     }),
+    developersIO,
   ];
   const agent = ZeroShotAgent.fromLLMAndTools(llm, tools);
   return AgentExecutor.fromAgentAndTools({ agent, tools, verbose: true });
   const tools = [

では実際に動かしてみます。

$ node dist/index.js zapier と langchain を統合することってできるの?
Entering new agent_executor chain...
 どんなサービスを使うべきか
Action: DEVELOPERSIO
Action Input: zapier langchain
url,title
https://dev.classmethod.jp/articles/demo-zapier-nla-api-beta-langchain/,Zapier Natural Language Actions API(beta)とLangChainの統合をやってみた
https://dev.classmethod.jp/articles/demo-zapier-nla/,Zapier Natural Language Actions (NLA)を触ってみた
 LangChainとZapier Natural Language Actions APIを使って統合する方法がわかった
Final Answer: LangChainとZapier Natural Language Actions APIを使用してzapierとlangchainを統合することが可能です。
Finished chain.
{"resp":"LangChainとZapier Natural Language Actions APIを使用してzapierとlangchainを統合することが可能です。"}

実際の ChatGPT の画面での回答は以下のようでした。

> zapier と langchain を統合することってできるの?

Zapierは、さまざまなアプリケーションやサービスを統合するためのオンラインプラットフォームであり、LangChainはブロックチェーン技術を活用した言語学習プラットフォームです。ZapierとLangChainを統合することは、技術的には可能ですが、そのためにはAPIを使用して双方のプラットフォームを接続する必要があります。

例えば、LangChainに新しい単語が追加された場合、Zapierを使用してそれらの単語をGoogleスプレッドシートに自動的に記録することができます。また、Zapierを使用してLangChain上で学習した成果を他のアプリケーションやサービスと共有することもできます。

ただし、具体的な統合方法や必要な手順は、ZapierやLangChainのAPIの仕様によって異なるため、詳細についてはそれぞれの公式キュメントを参照することをおすすめします。また、開発者のサポートが必要な場合は、両社のサポートチームに問い合わせることもできます。

まとめ

LangChain に沿って簡単に独自のソースを提供する実装を行うことができました。 また、自然言語が入ってくるため、実装は簡単な反面、不安定さも感じています。 llamaindex を使用する場合と比べて、検索 API の実行結果に大きく依存するため、良し悪しはあると思いますが、スモールスタートで始めるにはこちらで簡単に実装できるので、試してみる価値があります。

参考

LangChain の Googleカスタム検索 連携を試す