1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

どうも、水無月せきなです。

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で取得しました。

  1. https://makersuite.google.com/ にアクセスし、「Get API key」を押下する
    gemini-get-api-key.png

  2. APIキーの画面に遷移するので、「APIキーを作成」を押下する
    スクリーンショット 2025-07-14 161013.png

  3. 生成されるAPIキーを手元に控える

Gemini側の作業は以上です!

LangSmith

参考記事

  1. LangSmithへサインアップする
    LangSmithの公式サイトへアクセスし、アカウントを登録します。

  2. ログイン後、「Settings」→「API Keys」のページに移動する
    左ペインにある歯車のマークから移動できます。

  3. 「Create API Key」をクリックする
    LangSmith-api-key.png

  4. 表示されるモーダルでAPI Keyの設定をする
    ご自身のユースケースに合わせて適宜設定しください。
    langsmith-create-api-key-modal.png

  5. モーダルの「Create API Key」をクリックする
    API Key が表示されるので、手元に控えます。

  6. ホーム画面に戻り、「Set up tracing」をクリックする
    langsmith-top.png
    LANGSMITH_PROJECT=に記載されているプロジェクト名を手元に控えます。

LangSmith側の作業は以上です!

環境変数への設定

ルートディレクトリに.envファイルを作成し、API Keyを記載します。

.env
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が応答を返すコードを実装します。

src/chat_app/main.py
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 で確認

result-ai-studio.png

LangSmith で確認

result-langsmith.png

LLMへのメッセージの渡し方は、message オブジェクトの他に文字列・OpenAIフォーマットも可能とのことです。

model.invoke("Hello")

model.invoke([{"role": "user", "content": "Hello"}])

model.invoke([HumanMessage("Hello")])

Prompt Templates

実際にLLMへメッセージを渡す場合、何かしらの変更を加えて渡される場合が多いですが、それを支援するのがPrompt Templatesです。

Prompt Templatesを使って書き直してみます。

src/chat_app/main.py
# 変更箇所のみ

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に残りました。

langsmith-log.png

OutputParser

公式のチュートリアルだと前節の内容で終わってしまいます。
ここからは、参考記事を元にもう少し進めてみます。

モデルからの出力を整えるために、OutputParserを使用します。

src/chat_app/main.py
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によるチェーン化

src/chat_app/main.py
chain = prompt_template | model | parser
result = chain.invoke({"language": "Japanese", "text": "hi!"})

print(result)

結果

chain-langsmith.png

実行結果にほぼ相違はないものの、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で追加しています。

src/chat_app/api/translation_api.py
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でチェーンを定義して返しています。

src/chat_app/chains/translation_chain.py
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を作成して返します。

src/chat_app/models/model_factory.py
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に渡すプロンプトを作成します。

src/chat_app/prompts/translation_prompt.py
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

エントリーポイントになります。
環境変数の読み込みやルーティングの設定を別で切り出したため、こちらはシンプルになりました。

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/で動作を確認することもできます。

langserve-playground.png

ただ、自動生成されるはずのドキュメント(http://localhost:8000/docs)でエラーが発生しているため、今後解決できたらなと思っています。

まとめ

  • LangChainのチュートリアルを元に、Geminiで動作するアプリケーションの開発とLangSmithでの可視化ができました
  • LCELを用いた書き方を試しました
  • LangServeでサーバーを作り、動作を確認できました

おわりに

設定とかいろいろ大変なのかなと思っていたのですが、(もともと量は少ないとは言え)意外にもすんなり作れたので驚きました。

同じチュートリアルにあるチャットボットの章も読んで、アプリとして実践的なものにしていきたいなと思います!

ここまでお読みいただきありがとうございました。ご参考になれば幸いです。

参考資料

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?