LangChainのメモリ・キャッシュにMomentoを使う

2023.06.26

Introduction

サーバレスな高速キャッシュやスケーラブルなメッセージングを提供するMomento
このblogでも何度か記事にしてますが、
さまざまなライブラリやプロダクトと連携可能です。

今回は、ChatGPTをはじめとするLLMの拡張/実装を効率よくおこなうためのライブラリ、
LangChainと連携させてみましょう。

また、DevIOのMomento関連記事はここにあるので、
これらもご確認ください。

LangChain?

LangChainは(ChatGPTなど)LLMを利用したアプリを構築するためのフレーワークです。
現在(2023年4月)は、PythonとTypeScript用のライブラリが公開されています。
LangChainを使うことで、LLMへのプロンプト構築を効率よく作成できたり、
キャッシュを用いた処理の高速化や履歴管理などが可能になります。

LangChainについて弊社blogでも記事があるので
興味のある方はご確認ください。

Environment

今回試した環境は以下のとおりです。

  • MacBook Pro (13-inch, M1, 2020)
  • OS : MacOS 13.0.1
  • Node : v18.15.0

Setup

Momentoのセットアップ

LangChainと連携させるためのMomentoを準備します。
いつもどおりCLI・・・ではなく、Momento Consoleをつかって
Momentoの認証トークンを取得しましょう。
あとで使用するキャッシュもlangchainという名前で作成しておきます。

OpenAIのAPI KEY取得

Open AIのAPIにアクセスするため、API Keyを取得します。
このへんとかに手順があるので参考にしつつ、
取得したKeyを覚えておきましょう。

node用モジュールをインストール

LangChainを動かすために必要なモジュールをnpmでインストールします。

% mkdir langchain-example && cd langchain-example
% npm install -g typescript ts-node 
% npm install langchain openai dotenv serpapi --save

.envファイルを作成し、↓のようにさきほど取得したキーを記述します。

OPENAI_API_KEY="<OpenAIのAPI Key>"
MOMENTO_AUTH_TOKEN="<Momentoの認証キー>"

Write ts code

Momentoと連携させる前に、
LangChainのプログラムを動かしてみましょう。
まずはTemplateを使ってみます。
この機能を使えばプレースホルダをつかってプロンプトを簡単に構築できます。

//basic.ts
import { OpenAI } from "langchain/llms/openai";
import { PromptTemplate } from "langchain/prompts";
import { LLMChain } from "langchain/chains";
import "dotenv/config";

const template = "{item}を作る日本語の新会社名をを1つ提案してください?";
const prompt = new PromptTemplate({
  template: template,
  inputVariables: ["item"],
});

const model = new OpenAI({ 
    modelName: "gpt-4-0613",
    temperature: 0.9 
});

export const exec = async () => {
    const chain = new LLMChain({ llm: model, prompt: prompt });
    const res = await chain.call({ item: "Webアプリケーション" });
    console.log(res.text);    
};

exec();

「import "dotenv/config";」とすることで、
.envに記述した環境変数が使えるようになります。
あとはLLMChainにモデルとプロンプトを渡して実行すればOKです。

実行してみます。
API Keyやモジュールのインストールが適切にされていれば、
プロンプトの回答が返ってきます。

% ts-node basic.ts

「ファイブステップズ」

次にエージェント機能をつかってみます。
この機能は、ユーザーが実現したいことを、どんな手段、順序で解決するかを
LLMが自動で決定するような機能です。
何のツールを使って問題を解決するかはユーザーが指定します。
ツールにはSerpApiやTerminalを指定できます。
これにより最新の時事問題にも対応できたり、コマンドの実行結果をもとに
処理を行ったりすることができます。

では、SerpApiをつかってエージェント機能を試してみましょう。
SerpApiはサーチエンジン結果をスクレイピングしてくれるAPIです。
このサービスを使えばGoogleやBingなどの検索結果を取得し、プログラムで利用できます。

SerpApiを使うには、ここでサインアップし、API KEYを取得します。
取得したKEYは.envに追記しておきましょう。

プログラムは↓のようなかんじです。

//searchapi.ts
import { OpenAI } from "langchain/llms/openai";
import { initializeAgentExecutorWithOptions } from "langchain/agents";
import { SerpAPI } from "langchain/tools";
import { Calculator } from "langchain/tools/calculator";
import "dotenv/config";


export const exec = async () => {

    const model = new OpenAI({
        modelName: "gpt-4-0613",
        temperature: 0.9
    });

    const tools = [
        new SerpAPI(process.env.SERPAPI_API_KEY, {
            location: "Japan",
            hl: "en",
            gl: "us",
        }),
        new Calculator(),
    ];

    const executor = await initializeAgentExecutorWithOptions(tools, model, {
        agentType: "zero-shot-react-description",
    });

    const input ="くらにゃんって誰? 彼の性格は?";
    console.log(`Q. "${input}"...`);

    const result = await executor.call({ input });
    console.log(`A. ${result.output}`);
};

exec();

initializeAgentExecutorWithOptionsにエージェントを指定して実行しています。
zero-shot-react-descriptionとは、ReActフレームワークを使用し、
説明文章などからどのツールを用いるかを決める手法です。
※react.jsではない

↓が実行結果です。あってるような間違ってるような。
しかしまあSearchApiと連携してうごいてます。

% ts-node searchapi.ts

Q. "くらにゃんって誰? 彼の性格は?"...
A. "くらにゃん"はDevelopersIO CAFEに来る猫で、プログラミングに興味があり、リモートでも業務をこなす性格です。その性格は学習意欲が高く、責任感があると言えるでしょう。

Momento & LangChain

ではMomentoと連携させたプログラムを記述してみましょう。
Momento用SDKをインストールします。

% npm install @gomomento/sdk --save

Momentoをデータキャッシュとして使用して高速化する例をみてみます。
↓のコードでは、CacheClientを作成後、MomentoCache.fromPropsで
LangChain用のキャッシュオブジェクトを作成してOpenAIオブジェクト作成時に
設定しています。
こうすることで、同じ質問をした場合、キャッシュから回答を取得するため
高速化(だいたい10倍くらい違う)とトークンの節約が可能になります。

import {
    CacheClient,
    Configurations,
    CredentialProvider,
  } from "@gomomento/sdk";
  import { BufferMemory } from "langchain/memory";
  import { ChatOpenAI } from "langchain/chat_models/openai";
  import { ConversationChain } from "langchain/chains";
  import { MomentoChatMessageHistory } from "langchain/stores/message/momento";

  import { OpenAI } from "langchain/llms/openai";
  import { MomentoCache } from "langchain/cache/momento";

  import "dotenv/config";

  export const exec_basic = async () => {
    const cacheName = "langchain";

    // See https://github.com/momentohq/client-sdk-javascript for connection options
    const client = new CacheClient({
        configuration: Configurations.Laptop.v1(),
        credentialProvider: CredentialProvider.fromEnvironmentVariable({
        environmentVariableName: "MOMENTO_AUTH_TOKEN",
        }),
        defaultTtlSeconds: 60 * 60 * 24,
    });

    const cache = await MomentoCache.fromProps({
        client,
        cacheName: cacheName,
    });

    const llm = new OpenAI({ cache });

    const question = "国連加盟国の数は?";

    // LLMの呼び出し
    const startTime1 = performance.now();
    const res = await llm.call(question);
    console.log(res);
    const endTime1 = performance.now();
    console.log("Time1 : " + (endTime1 - startTime1));

    const startTime2 = performance.now();
    const res2 = await llm.call(question);
    console.log(res2);
    const endTime2 = performance.now();
    console.log("Time2 : " + (endTime2 - startTime2));
};

exec_basic();

LangChainにはメモリという機能があり、
LLMとの対話の履歴を保持する役割をもっています。
このメモリ機能にMomentoを指定することができ、
会話の自然さや継続性を確保することができます。

↓はLangChainのメモリにMomentoを指定した例です。
MomentoChatMessageHistory.fromPropsを作成して
BufferMemoryをに指定すればよいだけです。

//momento.ts
import {
    CacheClient,
    Configurations,
    CredentialProvider,
  } from "@gomomento/sdk";
  import { BufferMemory } from "langchain/memory";
  import { ChatOpenAI } from "langchain/chat_models/openai";
  import { ConversationChain } from "langchain/chains";
  import { MomentoChatMessageHistory } from "langchain/stores/message/momento";

  import { OpenAI } from "langchain/llms/openai";
  import { MomentoCache } from "langchain/cache/momento";

  import "dotenv/config";

  export const exec_history = async () => {

    // Momento SDKのCacheClient
    const client = new CacheClient({
      configuration: Configurations.Laptop.v1(),
      credentialProvider: CredentialProvider.fromEnvironmentVariable({
        environmentVariableName: "MOMENTO_AUTH_TOKEN",
      }),
      defaultTtlSeconds: 60 * 60 * 24,
    });

    // ユニークなセッションIDを作成
    const sessionId = new Date().toISOString();
    // Momentoのキャッシュ
    const cacheName = "langchain";

    //BufferMemoryを作成し、MomentoChatMessageHistoryを設定します
    const memory = new BufferMemory({
      chatHistory: await MomentoChatMessageHistory.fromProps({
        client,
        cacheName,
        sessionId,
        sessionTtl: 300,
      }),
    });
    console.log(`cacheName=${cacheName} and sessionId=${sessionId}.`);

    // ChatOpenAIモデルを作成
    const model = new ChatOpenAI({
      modelName: "gpt-4-0613",
      temperature: 0,
    });

    //OpenAIモデルとメモリを設定
    const chain = new ConversationChain({ llm: model, memory });

    // メッセージ1
    const res1 = await chain.call({ input: "こんにちわ。私の名前はCM太郎です。" });
    console.log({ res1 });

    // メッセージ2
    const res2 = await chain.call({ input: "私の名前をローマ字表記すると?" });
    console.log({ res2 });


    // Momentoでのチャット履歴を表示
    console.log(await memory.chatHistory.getMessages());
  };

exec_history();

実行してみると、会話の履歴が保持されています。

% ts-node momento.ts
{
  res1: { response: 'こんにちは、CM太郎さん。私の名前はOpenAIです。どのようにお手伝いできるかお知らせください。' }
}
{ res2: { response: 'あなたの名前をローマ字表記すると "CM Taro" になります。' } }
[
  HumanChatMessage {
    text: 'こんにちわ。私の名前はCM太郎です。',
    name: undefined,
    additional_kwargs: {}
  },
  AIChatMessage {
    text: 'こんにちは、CM太郎さん。私の名前はOpenAIです。どのようにお手伝いできるかお知らせください。',
    name: undefined,
    additional_kwargs: {}
  },
  HumanChatMessage {
    text: '私の名前をローマ字表記すると?',
    name: undefined,
    additional_kwargs: {}
  },
  AIChatMessage {
    text: 'あなたの名前をローマ字表記すると "CM Taro" になります。',
    name: undefined,
    additional_kwargs: {}
  }
]

Summary

今回はLangChainとMomentoの連携についてご紹介しました。
LangChainは標準でMomentoと統合されているので使用するのも容易です。
このように、Momentoはさまざまなプロダクトやサービスと連携することが可能なので、
ぜひ利用を検討してみてください。
毎月50GBまでなら無料で使用可能です。  

なお、Momentoについてのお問い合わせはこちらです。
こちらもお気軽にお問い合わせください。

References