はじめに
どうも、水無月せきなです。
LangChainのチュートリアルに取り組み始めました。
今回は、チュートリアルの最初であるChat models and promptsを元にやってみたことを書こうと思います。
この記事でわかること
- チュートリアルや他の記事を元にした実践コード
- GeminiのAPIキー取得とアプリからの使用方法
- LangSmithの設定とアプリとの連携方法
- LangServeでのAPIサーバー作成方法
リポジトリ
開発環境の準備
開発環境
Windows 11 24H2
WSL2
Docker Desktop 4.43.1
Cursor 1.2.2
Python 3.12
使用したテンプレート
最近お世話になりっぱなしの、下記テンプレートリポジトリを使用しました。
ライブラリのインストール
LangChain
今回のメインです。
uv add langchain
LangSmith
必須ではないのですが、せっかくなので入れました。
uv add langsmith
ChatGoogleGenerativeAI
今回はGeminiのAPIを使用するので、LangChainから呼び出すために入れます。
uv add "langchain[google-genai]"
python-dotenv
.env
から環境変数を読み込むために入れます。
uv add python-dotenv
LangServe
チュートリアルを行う上で必須ではありませんが、APIとして作るために入れました。
uv add "langserve[all]"
uv add "langchain[google-genai]"
やuv add "langserve[all]"
の"
は必要です。"
が無いと[
・]
が特殊文字として解釈されるためコマンドが失敗します。
APIキーの取得と設定
Gemini API
参考記事
AI StudioとGCPの二つがあるようですが、今回はAI Studioで取得しました。
-
https://makersuite.google.com/ にアクセスし、「Get API key」を押下する
-
生成されるAPIキーを手元に控える
Gemini側の作業は以上です!
LangSmith
参考記事
-
LangSmithへサインアップする
LangSmithの公式サイトへアクセスし、アカウントを登録します。 -
ログイン後、「Settings」→「API Keys」のページに移動する
左ペインにある歯車のマークから移動できます。 -
モーダルの「Create API Key」をクリックする
API Key が表示されるので、手元に控えます。 -
ホーム画面に戻り、「Set up tracing」をクリックする
LANGSMITH_PROJECT=
に記載されているプロジェクト名を手元に控えます。
LangSmith側の作業は以上です!
環境変数への設定
ルートディレクトリに.env
ファイルを作成し、API Keyを記載します。
LANGSMITH_TRACING=true
LANGSMITH_ENDPOINT="https://api.smith.langchain.com"
LANGSMITH_API_KEY="{your-langsmith-api-key}"
LANGSMITH_PROJECT="{your-langsmith-project-name}"
GOOGLE_API_KEY="{your-google-api-key}"
{your-langsmith-api-key}
はLangSmithで発行したAPI Key、{your-langsmith-project-name}
はLangSmithのプロジェクト名、{your-google-api-key}
はAI Studioで発行したAPI Keyです。
チュートリアルをやってみる
ここまで準備をしてきたところで、いよいよ実装に入ります。
まず、シンプルにメッセージを受け取ってLLMが応答を返すコードを実装します。
import getpass
import os
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage, SystemMessage
# 環境変数の読み込み
try:
# load environment variables from .env file (requires `python-dotenv`)
from dotenv import load_dotenv
load_dotenv()
except ImportError:
pass
os.environ["LANGSMITH_TRACING"] = "true"
if "LANGSMITH_API_KEY" not in os.environ:
os.environ["LANGSMITH_API_KEY"] = getpass.getpass(
prompt="Enter your LangSmith API key (optional): "
)
if "LANGSMITH_PROJECT" not in os.environ:
os.environ["LANGSMITH_PROJECT"] = getpass.getpass(
prompt='Enter your LangSmith Project Name (default = "default"): '
)
if not os.environ.get("LANGSMITH_PROJECT"):
os.environ["LANGSMITH_PROJECT"] = "default"
if not os.environ.get("GOOGLE_API_KEY"):
os.environ["GOOGLE_API_KEY"] = getpass.getpass("Enter API key for Google Gemini: ")
# LLMのモデル
model = init_chat_model("gemini-2.0-flash", model_provider="google_genai")
# メッセージの作成
messages = [
SystemMessage("Translate the following from English into Japanese"),
HumanMessage("hi!"),
]
# 実行
result = model.invoke(messages)
print(result)
結果
content='こんにちは! (Konnichiwa!)' additional_kwargs={} response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.0-flash', 'safety_ratings': []} id='run--cfe32f61-8104-46c7-b74a-ac1574c71dcf-0' usage_metadata={'input_tokens': 9, 'output_tokens': 9, 'total_tokens': 18, 'input_token_details': {'cache_read': 0}}
AI Studio で確認
LangSmith で確認
LLMへのメッセージの渡し方は、message オブジェクトの他に文字列・OpenAIフォーマットも可能とのことです。
model.invoke("Hello")
model.invoke([{"role": "user", "content": "Hello"}])
model.invoke([HumanMessage("Hello")])
Prompt Templates
実際にLLMへメッセージを渡す場合、何かしらの変更を加えて渡される場合が多いですが、それを支援するのがPrompt Templates
です。
Prompt Templates
を使って書き直してみます。
# 変更箇所のみ
system_template = "Translate the following from English into {language}"
prompt_template = ChatPromptTemplate.from_messages(
[("system", system_template), ("user", "{text}")]
)
prompt = prompt_template.invoke({"language": "Japanese", "text": "hi!"})
print(prompt)
result = model.invoke(prompt)
print(result)
結果
messages=[SystemMessage(content='Translate the following from English into Japanese', additional_kwargs={}, response_metadata={}), HumanMessage(content='hi!', additional_kwargs={}, response_metadata={})]
content='こんにちは! (Konnichiwa!)' additional_kwargs={} response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'model_name': 'gemini-2.0-flash', 'safety_ratings': []} id='run--66acba60-ce32-4e25-9c6b-c879da289b1b-0' usage_metadata={'input_tokens': 9, 'output_tokens': 9, 'total_tokens': 18, 'input_token_details': {'cache_read': 0}}
渡している内容自体は同じなので結果は変わっていませんね。
ただ、プロンプトの作成ログがLangSmithに残りました。
OutputParser
公式のチュートリアルだと前節の内容で終わってしまいます。
ここからは、参考記事を元にもう少し進めてみます。
モデルからの出力を整えるために、OutputParser
を使用します。
from langchain_core.output_parsers import StrOutputParser
result = model.invoke(prompt)
# 出力を文字列として取得するためのOutputParserをインスタンス化します。
parser = StrOutputParser()
parsed_result = parser.invoke(result)
print(parsed_result)
結果
こんにちは! (Konnichiwa!)
LLMからの返答以外も含まれていたのが、返答のみになりましたね。
LCELで書く
LCELとは
LangChainのコンポーネントを、宣言的にチェーンする(つなげる)ための独自の記法です。
LCELを使うことで、LangChainが下記のような最適化を行えます。
- 最適な並列実行
- RunnableParallelやRunnable Batch APIによる並列実行が可能
- 非同期実行のサポート
- Runnable Async APIで非同期に実行することが可能
- 容易なストリーミング
- ストリーミングにより、チェーン実行時にインクリメンタルな出力ができる。また、最初のトークンが出力されるまでの時間を最小とするためにストリーミングを最適化できる
その他の利点
- シームレスなLangSmithでのトレース
- すべてのステップがLangSmithに記録され、最大限の観測とデバッグを可能にする
- 統一されたAPI
- すべてのチェーンは
Runnable
インターフェースで構築されるため、どれも同じように扱える
- すべてのチェーンは
- LangSmithを用いたデプロイ
- プロダクションとしてそのままデプロイできる
LCELによるチェーン化
chain = prompt_template | model | parser
result = chain.invoke({"language": "Japanese", "text": "hi!"})
print(result)
結果
実行結果にほぼ相違はないものの、LangSmith上ではチェーンが一つにまとまり、詳細で個別ステップが確認できるようになりました。これも一つの効果かもしれません。
(これまでは、個別のステップがバラバラに表示されていました)
LangServeでAPIサーバーを立てる
こちらの記事ではLangServeによるAPIサーバーまでやっていましたので、私もやってみます。
それにあたり、ファイルも分割します。
ディレクトリ構造
src
└── chat_app
├── api
│ ├── __init__.py
│ └── translation_api.py
├── chains
│ ├── __init__.py
│ └── translation_chain.py
├── environment
│ ├── env_loader.py
│ └── __init__.py
├── __init__.py
├── main.py
├── models
│ ├── __init__.py
│ └── model_factory.py
└── prompts
├── __init__.py
└── translation_prompt.py
ディレクトリ | 役割 |
---|---|
api | エンドポイント |
chains | LCELによるチェーン定義 |
environment | 環境設定 |
main.py | エントリーポイント |
models | ChatModelを作成 |
prompts | プロンプトを作成 |
主要モジュールについて
translation_api.py
APIのルーティングをadd_routes
で追加しています。
from fastapi import FastAPI
from langserve import add_routes
from chat_app.chains.translation_chain import create_translation_chain
def setup_translation_api(app: FastAPI) -> None:
"""翻訳APIのルートを設定"""
translation_chain = create_translation_chain()
add_routes(
app,
translation_chain,
path="/translate",
)
translation_chain.py
LCELでチェーンを定義して返しています。
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import Runnable
from chat_app.models.model_factory import create_model
from chat_app.prompts.translation_prompt import create_translation_prompt
def create_translation_chain() -> Runnable:
"""翻訳チェーンを作成"""
model = create_model()
prompt = create_translation_prompt()
parser = StrOutputParser()
return prompt | model | parser
model_factory.py
ChatModelを作成して返します。
from langchain.chat_models import init_chat_model
from langchain.chat_models.base import BaseChatModel
def create_model(
model_name: str = "gemini-2.0-flash", model_provider: str = "google_genai"
) -> BaseChatModel:
"""モデルインスタンスを作成するファクトリ関数"""
return init_chat_model(model_name, model_provider=model_provider)
translation_prompt.py
LLMに渡すプロンプトを作成します。
from langchain_core.prompts import ChatPromptTemplate
def create_translation_prompt() -> ChatPromptTemplate:
"""翻訳用のプロンプトテンプレートを作成"""
system_template = "Translate the following from English into {language}"
return ChatPromptTemplate.from_messages([("system", system_template), ("user", "{text}")])
src/chat_app/main.py
エントリーポイントになります。
環境変数の読み込みやルーティングの設定を別で切り出したため、こちらはシンプルになりました。
from fastapi import FastAPI
from chat_app.api.translation_api import setup_translation_api
from chat_app.environment.env_loader import load_env
# 環境変数を読み込む
load_env()
# FastAPIアプリケーションを作成
app = FastAPI()
# 翻訳APIのルーティングを設定
setup_translation_api(app)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
起動と動作確認
uv run src/chat_app/main.py
http://localhost:8000/translate
にJSON(例:{"language": "italian", "text": "hi"}
)を送ると、翻訳されたものが返ってくると思います。
また、http://localhost:8000/translate/playground/
で動作を確認することもできます。
ただ、自動生成されるはずのドキュメント(http://localhost:8000/docs
)でエラーが発生しているため、今後解決できたらなと思っています。
まとめ
- LangChainのチュートリアルを元に、Geminiで動作するアプリケーションの開発とLangSmithでの可視化ができました
- LCELを用いた書き方を試しました
- LangServeでサーバーを作り、動作を確認できました
おわりに
設定とかいろいろ大変なのかなと思っていたのですが、(もともと量は少ないとは言え)意外にもすんなり作れたので驚きました。
同じチュートリアルにあるチャットボットの章も読んで、アプリとして実践的なものにしていきたいなと思います!
ここまでお読みいただきありがとうございました。ご参考になれば幸いです。