小説家全員猫にしてみた
はじめに
畑田です。
LangChainを使って青空文庫の文章を猫にしてみました。
コード
以下が実際に書いたコードです。めっちゃ短いです。
import { config } from 'dotenv'
import { ChatGoogleGenerativeAI } from '@langchain/google-genai'
import { HarmBlockThreshold, HarmCategory } from '@google/generative-ai'
import { createInterface } from 'readline/promises'
config()
const model = new ChatGoogleGenerativeAI({
modelName: 'gemini-pro',
maxOutputTokens: 2048,
safetySettings: [
{
category: HarmCategory.HARM_CATEGORY_HARASSMENT,
threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
},
],
})
const reader = createInterface({
input: process.stdin,
output: process.stdout,
})
const main = async () => {
process.stdin.setEncoding('utf8')
const paragraph = await reader.question('文章を入力してください: ')
const novelist = await reader.question('小説家を入力してください: ')
reader.close()
const prompt = `以下の文章を、いかにも${novelist}が書いたもののように変換してください。
${paragraph}`
const { content } = await model.invoke([['human', prompt]])
console.log(content)
}
main()
.env
ファイルを作って、Google CloudのCredentialsで作成したAPI keyを以下の形式で置いておきます。
GOOGLE_API_KEY=xxxxxxxxxxxxxxxx
入出力
以下の入力でpromptを作らせました。
「ゴーシュは町の活動写真館でセロを弾く係りでした。けれどもあんまり上手でないという評判でした。上手でないどころではなく実は仲間の楽手のなかではいちばん下手でしたから、いつでも楽長にいじめられるのでした。 ひるすぎみんなは楽屋に円くならんで今度の町の音楽会へ出す第六交響曲こうきょうきょくの練習をしていました。 トランペットは一生けん命歌っています。 ヴァイオリンも二いろ風のように鳴っています。 クラリネットもボーボーとそれに手伝っています。 ゴーシュも口をりんと結んで眼めを皿さらのようにして楽譜がくふを見つめながらもう一心に弾いています。 にわかにぱたっと楽長が両手を鳴らしました。みんなぴたりと曲をやめてしんとしました。楽長がどなりました。「セロがおくれた。トォテテ テテテイ、ここからやり直し。はいっ。」 みんなは今の所の少し前の所からやり直しました。ゴーシュは顔をまっ赤にして額に汗あせを出しながらやっといま云いわれたところを通りました。ほっと安心しながら、つづけて弾いていますと楽長がまた手をぱっと拍うちました。」
青空文庫より
小説家を入力してください: 村上春樹
こちらがGeminiから返ってきたコンテンツです。
「夕暮れが町の輪郭をぼんやりと曖昧に染め上げていた頃、ゴーシュは薄暗い活動写真館のステージの端に佇んでいた。彼はセロを担当していたが、実はその腕前は評判どおりお世辞にも上手いとは言えなかったのだ。仲間の楽団員の中では、最も下手であるとの定評を得ており、楽長からはいつも陰口を叩かれていた。
その日の午後、楽員たちは楽屋に円陣を作って、近々開催される町音楽会での演奏曲、第六交響曲の練習に取り組んでいた。トランペットが懸命に咆哮を上げ、バイオリンが風に乗り舞うかのように軽快に弾かれ、クラリネットがそれらに呼応するようにブーブーと鳴き声を響かせていた。ゴーシュもまた、眉間に深い皺を刻み、目を皿のように見開き、楽譜を食い入るように凝視しながら、気勢を上げて弾いていた。
すると突然、楽長が両手をばたんと叩いた。演奏は一瞬にして止み、会場はシーンと静まり返った。楽長が激高した声音で言った。「セロが遅れている。トゥトゥッテ、テテテ。ここからやり直しだ。さあ、始めるぞ。」楽団員たちは、指定された小節の少し前から演奏を再開した。ゴーシュは真っ赤になった顔から汗を滲ませながら、辛うじて楽長に言われた箇所を弾き終えた。安堵のため息をつき、再び弾き始めようとすると、またしても楽長が手を叩いた。」
もっと劇的に村上春樹にしてくれると思っていたのに、、、
ということで、猫に書かせたら面白いのではないかと思い、入力を変えました。
小説家を入力してください: 猫のにゃんすけ
回答がこちらです。
「にゃん、こんにちは。ボクは町いちばんの活動写真館で、セロをネコッタするゴーシュにゃ。でもね、ボクはちょっとおヘタさんなの。仲間のにゃんこよりもずっとヘタっぴだから、いつも楽長にゃんにニャジられるの。
ある日のお昼過ぎ、ボクたちは楽屋の円座で、町の大演奏会用の音楽を練習していたにゃ。トランペットにゃんは一生懸命ニャーニャー言ってるし、バイオリンにゃんはビュービュー鳴かしてるし、クラリネットにゃんもボーボーって手伝ってる。ボクも口をきゅって結んで、お目目をお皿みたいに大きく開けて、楽譜をジッと見つめながら、必死に弾いてたよ。
すると、突然、楽長にゃんがパタッと両手を叩いたの。みんなピタッて曲を止めて、シーンとしちゃった。楽長にゃんが「セロが遅れた!トォテテ テテテイ、ここからやり直し!ナオミ!」って怒鳴ったにゃ。みんなはちょっと前のところから弾き直したんだ。ボクは真っ赤になって、おでこに汗だくになりながら、やっと言われたところを弾けたよ。ホッとして弾き続けてたら、また楽長にゃんがパッと手を叩いたにゃ。プンスカって!」
無理なお題だからなのか、若干ハルシネーションを起こしていますが、ちゃんと猫が書いていますね!
これは何?
LangChainをサクッと使って、その素晴らしい体験を共有したかったのです!
業務でOpenAIのGPT-4oやGoogleのVertex AIをWeb API経由や個別のSDK経由で使用していました。
このとき、モデルの移り変わりが激しかったり、使用場面によってモデルの優劣が出たりするので、いろんな生成AIに対応するようなアダプタを自前で実装していました。
しかし、LangChainであればそのアダプタの実装を行うことなく、モデルを使い分けるインターフェイスが提供されており、いろんなモデルを簡単に扱えます。
例えば上記のコードではGeminiを使用していますが、OpenAIのGPT-4を使用することもできます。
どのモデルを使用しても画一的なインターフェイスで実装できるのが、LangChainの使い心地が良すぎるポイントです!
import { config } from 'dotenv'
- import { ChatGoogleGenerativeAI } from '@langchain/google-genai'
- import { HarmBlockThreshold, HarmCategory } from '@google/generative-ai'
import { createInterface } from 'readline/promises'
+ import { ChatOpenAI } from '@langchain/openai'
config()
- const model = new ChatGoogleGenerativeAI({
- modelName: 'gemini-pro',
- maxOutputTokens: 2048,
- safetySettings: [
- {
- category: HarmCategory.HARM_CATEGORY_HARASSMENT,
- threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
- },
- ],
- })
+ const model = new ChatOpenAI({
+ model: 'gpt-4',
+ temperature: 0,
+ })
const reader = createInterface({
input: process.stdin,
output: process.stdout,
})
const main = async () => {
process.stdin.setEncoding('utf8')
const paragraph = await reader.question('文章を入力してください: ')
const novelist = await reader.question('小説家を入力してください: ')
reader.close()
const prompt = `以下の文章を、いかにも${novelist}が書いたもののように変換してください。
${paragraph}`
const { content } = await model.invoke([['human', prompt]])
console.log(content)
}
main()
.env
ファイルには、OpenAIのAPI key管理画面で作成したAPI keyを以下の形式で置いておきます。
GOOGLE_API_KEY=xxxxxxxxxxxxxxxx
+ OPENAI_API_KEY=xxxxxxxxxxxxxxxx
ちなみに回答は以下でした。にゃんすけが小説を書くのではなく、ゴーシュになりきってしまいました。
「にゃんすけは町の映画館でセロを弾く役目だったのにゃ。でも、あんまり上手じゃないという評判だったのにゃ。上手じゃないどころか、実は仲間の楽手の中では一番下手だったから、いつも楽長にいじめられてたのにゃ。昼過ぎ、みんなは楽屋で円くなって、今度の町の音楽会に出す第六交響曲の練習をしてたのにゃ。トランペットは一生懸命鳴らしてるにゃ。ヴァイオリンも風のように鳴らしてるにゃ。クラリネットもボーボーとそれに合わせてるにゃ。にゃんすけも口をきゅっと結んで、目を大きく開けて楽譜を見つめながら、一心に弾いてたのにゃ。突然、楽長が両手を鳴らしたのにゃ。みんなが曲を止めて静かになったのにゃ。楽長が怒鳴ったにゃ。「セロが遅れた。トォテテ テテテイ、ここからやり直し。はいっ。」みんなは今のところの少し前からやり直したのにゃ。にゃんすけは顔を真っ赤にして、汗を出しながらやっと言われたところを通過したのにゃ。ほっと安心しながら、続けて弾いてたら、楽長がまた手を鳴らしたのにゃ。」
LangChainについて
元々IBMが開発していた、LLMのためのライブラリでしたが、今はOSSとして開発されています。
LangChainは、componentという思想により、AIを扱うための各タスクがモジュールとして抽象化されており、拡張に強く設計されています。
サードパーティのLLM開発者は、自分の使用したいモデルをLangChainに合わせて実装することで、LangChainから利用可能にできるのです。
まとめ
LangChainの使い心地は素晴らしいです。IBM様様です。
実は業務でも使い倒しており、弊社のAIプロジェクトの重要なエンジンとなっています。
弊社ではアプリの環境ごと再現性を高めながらデプロイメントの簡単化及び高頻度化を図ることで、質の高いアプリを量産しております。アプリの本質的なロジックからUI/UXまでAIを用いた最適な体験を提供し続けていきます。
記載内容の間違いのご指摘や訂正案、より良い内容のご提案など、忌憚なきご意見を頂けますと幸いです。
参考
Discussion