LangChain は、LLM(大規模言語モデル)を使うアプリを「モデル・プロンプト・ツール・検索」などの部品を組み合わせて作る Python のライブラリです。 1.x では、モデルは init_chat_model("openai:モデル名") で作り、ツールを使わせるなら create_agent() を使うのが基本の書き方です。

from langchain.chat_models import init_chat_model

model = init_chat_model("openai:gpt-6-luna", temperature=0)
print(model.invoke("LangChainを一言で").text)
やりたいこと 1.x の書き方 見出し
モデルを呼ぶ init_chat_model("openai:...") → .invoke() 最小コード
テンプレートで呼ぶ prompt | model | StrOutputParser() プロンプトと LCEL
決まった形で受け取る model.with_structured_output(Pydanticのクラス) 構造化出力
少しずつ表示する model.stream(...) ストリーム
関数を使わせる @tool → create_agent(model, tools=[...]) ツールとエージェント
社内文書で答えさせる InMemoryVectorStore → retriever RAG の最小構成
API を呼ばずにテスト FakeListChatModel・GenericFakeChatModel テスト
以前のコードを直す LLMChain → prompt | model など 書き換え表

確認環境:langchain 1.4.4(pip の最新版)・langchain-core 1.6.10・langchain-openai 1.7.1・langchain-anthropic 1.7.6・langchain-text-splitters 1.1.3・langchain-classic 1.0.8・langgraph 1.2.14・openai 3.29.0・Python 3.11.15(Linux)。モデルが送る URL と JSON、受け取ったメッセージの形、エージェントの往復、ストリームの区切り、構造化出力は、base_url を手元の擬似エンドポイント(OpenAI と Anthropic の API の形を返す小さな HTTP サーバー)に向けて確かめました。返答の文は擬似エンドポイントが返した固定の文です。ツールの往復は、API を呼ばない GenericFakeChatModel でも確かめました。既定値と API の切り替えの条件はパッケージのソースで、仕様は docs.langchain.com で確かめました(確認日 2026年10月13日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「LangChain入門|PythonでLLMアプリを作る基本とコピペ集」

LangChain 1.x のパッケージ構成

1.x では、本体の langchain と、モデル会社ごとのパッケージが分かれています。 pip install langchain だけでは OpenAI や Anthropic のモデルは呼べません。使う会社のパッケージを足します。

pip install -U langchain langchain-openai          # OpenAI を使う
pip install -U langchain-anthropic                 # Anthropic(Claude)も使う
pip install -U langchain-text-splitters numpy      # RAG で文書を分ける・インメモリ検索
export OPENAI_API_KEY="sk-..."
パッケージ 中身 手元の版
langchain init_chat_model・create_agent・ミドルウェア・@tool 1.4.4
langchain-core メッセージ・プロンプト・Runnable(| でつなぐ仕組み)・ベクトルストアの基本 1.6.10
langchain-openai ChatOpenAI・OpenAIEmbeddings 1.7.1
langchain-anthropic ChatAnthropic 1.7.6
langchain-text-splitters RecursiveCharacterTextSplitter など 1.1.3
langgraph エージェントの実行の土台(langchain が自動で入れる) 1.2.14
langchain-classic LLMChain など 0.x の機能(移行用) 1.0.8

pip install langchain が一緒に入れたのは langchain-core・langgraph・pydantic でした(pip show の Requires)。インストールのページには Python 3.10 以上が必要とあります。

最小コード:init_chat_model

init_chat_model は、「会社名:モデル名」の文字列からチャットモデルを作る関数です。 会社を変えても、呼び方(invoke・stream・batch)は同じです。

from langchain.chat_models import init_chat_model

model = init_chat_model("openai:gpt-6-luna", temperature=0)
res = model.invoke("LangChainを一言で")

print(res.text)              # 返答の文字
print(res.usage_metadata)    # トークン数
print(res.response_metadata["finish_reason"])

手元で動かした結果です。作られたのは ChatOpenAI で、OpenAI の Chat Completions の形で送られていました。

ChatOpenAI
LangChainは、LLMを使うアプリを部品の組み合わせで作るライブラリです。
{'input_tokens': 12, 'output_tokens': 20, 'total_tokens': 32, 'input_token_details': {}, 'output_token_details': {}}
stop

POST /v1/chat/completions
{"model": "gpt-6-luna", "stream": false, "messages": [{"content": "LangChainを一言で", "role": "user"}], "temperature": 0.0}

会社名の書き方と、推測できないとき

"gpt-6-luna" のように会社名を省いても、gpt- で始まる名前は OpenAI と推測されました。推測できない名前は、次のエラーになります。

ValueError: Unable to infer model provider for model='my-local-model'. Please specify 'model_provider' directly.

Supported providers: anthropic, anthropic_bedrock, azure_ai, azure_openai, baseten, bedrock, bedrock_converse, bedrock_mantle_anthropic, bedrock_mantle_openai, cohere, deepseek, fireworks, google_anthropic_vertex, google_genai, google_vertexai, groq, huggingface, ibm, langsmith, litellm, meta, mistralai, nvidia, ollama, openai, openrouter, perplexity, together, upstage, xai

init_chat_model("my-local-model", model_provider="ollama") のように model_provider で会社を指定します。各社のパッケージ(例:langchain-ollama)が入っている必要があります。

Anthropic(Claude)に切り替える

model = init_chat_model("anthropic:claude-sonnet-4-6")   # 環境変数 ANTHROPIC_API_KEY を読む
res = model.invoke([("system", "短く答える"), ("human", "LangChainを一言で")])
print(res.text)

作られたのは ChatAnthropic で、システム指示は Anthropic の API の system に移して送られていました。max_tokens は指定していませんが、モデルに合わせた値が入っていました(移行ガイドにも「Anthropic の max_tokens の既定は固定の 1024 ではなく、モデルごとの値になった」とあります)。

POST /v1/messages
{"max_tokens": 128000, "messages": [{"role": "user", "content": "LangChainを一言で"}], "model": "claude-sonnet-4-6", "system": "短く答える"}

モデル名は LangChain のドキュメントの例です。使える名前と料金は、各社の公式ページで確かめます。OpenAI の書き方は、関連記事「OpenAI API Python入門|Responses APIのコピペ集」で、Claude は関連記事「Claude API Python入門|Anthropic SDKコピペレシピ集」で扱っています。

よく使う引数と既定値

引数 意味 手元で見た既定値
temperature ランダムさ。低いほど同じ答えになりやすい 指定しなければ送らない
max_tokens 出力の上限 OpenAI は送らない。Anthropic はモデルごとの値
timeout 待つ秒数 ChatOpenAI は None(SDK のクライアントにも None が渡る)
max_retries 再試行の回数 ChatOpenAI は None で、SDK の既定の 2。ChatAnthropic は 2
base_url 送り先(社内プロキシ・互換サーバー) 環境変数 OPENAI_BASE_URL も読む

ChatOpenAI は timeout を指定しないと、SDK のクライアントの timeout も None になっていました(langchain-openai 1.7.1 のソースで、request_timeout をそのまま渡す作り)。止まったままにしたくない処理では、timeout=60 のように明示すると安心です。

プロンプトと LCEL:| でつなぐ

LCEL(LangChain Expression Language)は、部品を | でつないで1本の処理にする書き方です。 0.x の LLMChain の代わりに使います。

from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

prompt = ChatPromptTemplate.from_messages([
    ("system", "あなたは{role}です。100文字以内で答えます。"),
    ("human", "{question}"),
])
model = init_chat_model("openai:gpt-6-luna", temperature=0)
chain = prompt | model | StrOutputParser()

answer = chain.invoke({"role": "社内ヘルプデスク", "question": "VPNにつながりません"})
print(answer)

送られたのは、テンプレートに値を入れた system と user のメッセージでした。

{"model": "gpt-6-luna", "stream": false, "messages": [{"content": "あなたは社内ヘルプデスクです。100文字以内で答えます。", "role": "system"}, {"content": "VPNにつながりません", "role": "user"}], "temperature": 0.0}
部品 受け取るもの 渡すもの
ChatPromptTemplate 変数の dict メッセージのリスト
チャットモデル メッセージのリスト(または文字列) AIMessage
StrOutputParser AIMessage 文字列

変数が足りないときは、呼んだ時点で KeyError になりました。

KeyError: "Input to ChatPromptTemplate is missing variables {'question'}.  Expected: ['question', 'role'] Received: ['role']\nNote: if you intended {question} to be part of the string and not a variable, please escape it with double curly braces like: '{{question}}'.

テンプレートの中に { } をそのまま書きたいとき(JSON の例など)は、{{ }} と2つ重ねます。

チェーンも、モデルと同じく invoke・stream・batch で呼べます。batch は手元で並べて呼ぶだけで、ドキュメントにも「OpenAI や Anthropic のバッチ API とは別物」とあります。

answers = chain.batch(
    [{"role": "ヘルプデスク", "question": q} for q in ["VPN", "印刷", "パスワード"]],
    config={"max_concurrency": 2},   # 同時に呼ぶ数
)

プロンプトの書き方は、関連記事「プロンプトの書き方|エンジニア向けテンプレート20個と型の一覧」で扱っています。

構造化出力:with_structured_output

with_structured_output(Pydanticのクラス) を付けると、返り値がそのクラスのオブジェクトになります。 自分で JSON を解析しなくて済みます。

from pydantic import BaseModel, Field

class Ticket(BaseModel):
    """問い合わせの分類結果"""
    category: str = Field(description="障害・質問・要望のどれか")
    priority: str = Field(description="高・中・低")
    summary: str = Field(description="30文字以内の要約")

structured = model.with_structured_output(Ticket)
ticket = structured.invoke("VPNに朝からつながらず仕事が止まっています")
print(ticket)               # Ticket(category='障害', priority='高', summary='VPNに接続できない')
print(ticket.priority)

ChatOpenAI は、クラスを JSON Schema に変えて response_format で送っていました。strict: true と additionalProperties: false が自動で付き、Field の description もスキーマに入ります。クラスの docstring は、スキーマの description になります。

"response_format": {"type": "json_schema", "json_schema": {"schema": {"description": "問い合わせの分類結果", "properties": {"category": {"description": "障害・質問・要望のどれか", "title": "Category", "type": "string"}, ...}, "required": ["category", "priority", "summary"], "title": "Ticket", "type": "object", "additionalProperties": false}, "name": "Ticket", "strict": true}}
指定 送られ方(手元の結果) 使いどころ
指定なし(ChatOpenAI) response_format の json_schema 対応するモデルで、形を強く守らせる
method="function_calling" Ticket という名前のツールを1つ渡し、tool_choice でそれを強制 JSON Schema の出力に対応しないモデル
method="json_mode" ドキュメントでは、スキーマを強制しない JSON の出力 形のゆるい JSON でよいとき
include_raw=True 戻り値が raw・parsed・parsing_error の dict 解析に失敗したときも元の返答を見たい

include_raw=True では、raw に AIMessage、parsed に Ticket、parsing_error に None が入っていました。方式ごとの違いは、関連記事「LLMの構造化出力とJSON|4方式の違いとPydantic検証の実行結果」で、Pydantic 自体は関連記事「Pydantic v2の使い方|BaseModelとバリデーションをエラー例で解説」で扱っています。

ストリーム:少しずつ受け取る

model.stream() は、返答を AIMessageChunk の断片で順に返します。 チェーンでも同じです。

for chunk in model.stream("LangChainを一言で"):
    print(chunk.text, end="", flush=True)
print()

# チェーンなら文字列の断片が来る
for piece in chain.stream({"role": "ヘルプデスク", "question": "VPNにつながりません"}):
    print(piece, end="", flush=True)

擬似エンドポイントから文を3つに分けて返したとき、受け取った断片です。前後に中身の空の断片が届きます(最初は役割だけ、最後は終了の理由など)。

AIMessageChunk ''
AIMessageChunk 'LangChain'
AIMessageChunk 'は、LLMを使うアプリを部'
AIMessageChunk '品の組み合わせで作るライブラリです。'
AIMessageChunk ''
AIMessageChunk ''

断片は + で足し合わせると1つのメッセージになります(ドキュメントの例:full = chunk if full is None else full + chunk)。FastAPI などで画面に流すときは、astream()(async 版)を使います。

ツールとエージェント:@tool と create_agent

エージェントは、モデルが「どのツールを、どの引数で呼ぶか」を決め、LangChain がツールを実行して結果を返す、をくり返す仕組みです。 1.x では from langchain.agents import create_agent で作ります。

ツールを作る:@tool

from langchain.tools import tool

@tool
def get_weather(city: str) -> str:
    """都市名を受け取り、今日の天気を返す"""
    return f"{city}は晴れ、最高気温22度"   # 本当は天気APIを呼ぶ

print(get_weather.name)          # get_weather
print(get_weather.description)   # 都市名を受け取り、今日の天気を返す
print(get_weather.args)          # {'city': {'title': 'City', 'type': 'string'}}
print(get_weather.invoke({"city": "大阪"}))

関数名がツール名、docstring が説明、型ヒントが引数のスキーマになります。docstring と型ヒントは、モデルがツールを選ぶ手がかりになるので必ず書きます。 作られたのは StructuredTool でした。

エージェントを作って呼ぶ

from langchain.chat_models import init_chat_model
from langchain.agents import create_agent

model = init_chat_model("openai:gpt-6-luna", temperature=0)
agent = create_agent(
    model,                                   # "openai:gpt-6-luna" の文字列でもよい
    tools=[get_weather],
    system_prompt="あなたは天気案内です。短く答えます。",
)

result = agent.invoke({"messages": [{"role": "user", "content": "東京の天気は?"}]})
for m in result["messages"]:
    print(type(m).__name__, m.text)
print(result["messages"][-1].text)   # 最後の返答
HumanMessage 東京の天気は?
AIMessage                                  ← ツールを呼ぶ指示(tool_calls に get_weather, {'city': '東京'})
ToolMessage 東京は晴れ、最高気温22度         ← LangChain が get_weather を実行した結果
AIMessage 東京は晴れです(get_weather の結果より)。

戻り値は状態の dict(キーは messages)で、messages に会話の全部が入っています。モデルへは2回送られ、2回目にはツールの呼び出しと結果が足されていました(ここでは Responses API の形)。

1回目 POST /v1/responses
{"input": [{"content": "あなたは天気案内です。短く答えます。", "role": "system", ...}, {"content": "東京の天気は?", "role": "user", ...}], "tools": [{"type": "function", "name": "get_weather", "description": "都市名を受け取り、今日の天気を返す", "parameters": {...}}], ...}

2回目 POST /v1/responses
{"input": [..., {"arguments": "{\"city\": \"東京\"}", "call_id": "call_002", "name": "get_weather", "type": "function_call", ...}, {"type": "function_call_output", "output": "東京は晴れ、最高気温22度", "call_id": "call_002"}], ...}

注意:返答の content がリストになることがある

上の例では、最後の AIMessage の content が文字列ではなく、ブロックのリストでした。

content: [{'type': 'text', 'text': '東京は晴れです(get_weather の結果より)。', 'annotations': [], 'id': 'msg_003'}]
text:    '東京は晴れです(get_weather の結果より)。'

langchain-openai 1.7.1 のソースでは、gpt-6 で始まるモデルにツールを渡すと、Chat Completions ではなく Responses API で送る作りでした(use_responses_api を指定しないとき)。Responses API の返答は content がブロックのリストになります。ツールなしの model.invoke() では content は文字列でした。文字を取るときは、どちらでも同じに使える .text を使います。 .text は 1.x でプロパティになり、m.text() と呼ぶと「メソッドとしての呼び出しは非推奨」の警告が出ました。

会話を覚えさせる:checkpointer

from langgraph.checkpoint.memory import InMemorySaver

agent = create_agent(model, tools=[get_weather], checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "user-1"}}

agent.invoke({"messages": [{"role": "user", "content": "私は山田です"}]}, config)
result = agent.invoke({"messages": [{"role": "user", "content": "東京の天気は?"}]}, config)
print(len(result["messages"]))   # 1回目の分も入っている

同じ thread_id で2回呼ぶと、2回目の messages に1回目のやり取りも入っていました(手元では8件)。InMemorySaver はプロセスのメモリに持つだけなので、再起動で消えます。

エージェントの途中経過を流す:stream_mode

inputs = {"messages": [{"role": "user", "content": "東京の天気は?"}]}

# ステップごと(どのノードが何を足したか)
for chunk in agent.stream(inputs, stream_mode="updates"):
    for node, update in chunk.items():
        print(node, [type(m).__name__ for m in update["messages"]])

# トークンごと
for token, meta in agent.stream(inputs, stream_mode="messages"):
    if meta["langgraph_node"] == "model" and token.text:
        print(token.text, end="", flush=True)
model ['AIMessage']
tools ['ToolMessage']
model ['AIMessage']
stream_mode 届くもの
updates ステップごとの更新 {ノード名: 更新}。ノードは model と tools
messages (トークン, メタデータ)。meta["langgraph_node"] でノードが分かる
custom ツールの中で get_stream_writer() を使って出した任意のデータ

messages では、ツールを呼ぶ回は引数の JSON が {"city" と : "東京"} のように分かれて届き、tools ノードからは ToolMessage が1つ届きました。ドキュメントでは、version="v2" を付けるとどのモードでも type・ns・data を持つ dict で届くことと、新しいアプリには stream_events(..., version="v3") を勧めることが書かれています。

エージェントに決まった形で答えさせる:response_format

from langchain.agents.structured_output import ToolStrategy

agent = create_agent(model, tools=[], response_format=Ticket)
result = agent.invoke({"messages": [{"role": "user", "content": "VPNにつながらない"}]})
print(result["structured_response"])   # Ticket(category='障害', ...)

# ツール呼び出しで形を守らせる方式を明示
agent = create_agent(model, tools=[], response_format=ToolStrategy(Ticket))
方式 仕組み(ドキュメント) 手元で送られたもの
ProviderStrategy モデル会社の構造化出力の機能を使う response_format の json_schema(strict: false)
ToolStrategy 形をツールとして渡し、呼ばせる Ticket ツールと tool_choice: required
クラスだけ渡す 対応していれば ProviderStrategy、無ければ ToolStrategy 手元では ProviderStrategy の形

ToolStrategy では、messages の最後に ToolMessage が足されていました。ドキュメントでは、形が合わないときは handle_errors(既定 True)でモデルに直させます。

0.x の AgentExecutor・create_react_agent からの移り先

以前 1.x 備考(移行ガイド)
langgraph.prebuilt.create_react_agent langchain.agents.create_agent 引数 prompt は system_prompt に
AgentExecutor・initialize_agent create_agent langchain.agents からは import できない
pre_model_hook・post_model_hook ミドルウェア(before_model・after_model) 要約・人の承認などは組み込みのミドルウェア
ストリームのノード名 "agent" "model"
ToolNode を渡す ツールのリストを直接渡す エラー処理は wrap_tool_call

create_react_agent は今も import できますが、呼ぶと次の警告が出ました。

LangGraphDeprecatedSinceV10: create_react_agent has been moved to `langchain.agents`. Please update your import to `from langchain.agents import create_agent`. Deprecated in LangGraph V1.0 to be removed in V2.0.

1.4.4 の langchain.agents.middleware には、SummarizationMiddleware(長い会話の要約)・HumanInTheLoopMiddleware(ツール実行前の人の承認)・ModelRetryMiddleware・ModelFallbackMiddleware・ToolCallLimitMiddleware・PIIMiddleware などがありました。エージェントの考え方は関連記事「AIエージェントとは|仕組みとワークフローとの違い・作り方入門」で、Function Calling の仕組みは関連記事「Function Calling(tool use)の仕組みとPython実装」で扱っています。

RAG の最小構成:インメモリのベクトルストア

RAG(検索拡張生成)は、質問に近い文書を検索してプロンプトに入れ、その文書を根拠に答えさせる方法です。 ここではサーバーの要らない InMemoryVectorStore で作ります。

from langchain_core.documents import Document
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter

docs = [
    Document(page_content="有給休暇の申請は、取得日の前日までに勤怠システムから行います。急な体調不良の場合は当日の始業前に上長へ連絡し、事後申請します。", metadata={"source": "就業規則.md"}),
    Document(page_content="経費精算は月末締めで、翌月5営業日までに領収書の画像を添付して申請します。", metadata={"source": "経費規程.md"}),
    Document(page_content="VPNに接続できないときは、まずクライアントを再起動し、それでも直らなければ情報システム部へ連絡します。", metadata={"source": "ITヘルプ.md"}),
]

# 1. 分ける(ドキュメントのチュートリアルは chunk_size=1000, chunk_overlap=200。ここは短い文なので小さく)
splitter = RecursiveCharacterTextSplitter(chunk_size=40, chunk_overlap=10)
chunks = splitter.split_documents(docs)

# 2. ベクトルにして入れる
vector_store = InMemoryVectorStore(OpenAIEmbeddings(model="text-embedding-3-small"))
vector_store.add_documents(chunks)

# 3. 検索する
for doc, score in vector_store.similarity_search_with_score("有給休暇はいつまでに申請しますか", k=2):
    print(round(score, 3), doc.metadata["source"], doc.page_content[:30])

手元では3つの文書が5つのチャンクに分かれ、metadata はチャンクに引き継がれていました。重なり(chunk_overlap)の分、2つ目のチャンクは前のチャンクの終わりと同じ文から始まります。

'有給休暇の申請は、取得日の前日までに勤怠システムから行います。急な体調不良の場合' {'source': '就業規則.md'}
'。急な体調不良の場合は当日の始業前に上長へ連絡し、事後申請します。' {'source': '就業規則.md'}
'経費精算は月末締めで、翌月5営業日までに領収書の画像を添付して申請します。' {'source': '経費規程.md'}

add_documents は5つのチャンクを POST /v1/embeddings に1回でまとめて送り、encoding_format は base64 でした。チャンクの長さと重なりの決め方は、関連記事「RAGの精度改善の方法一覧|原因の切り分けと評価指標の計算」で扱っています。

手元で出た2つのエラー

エラー 原因 直し方
ImportError: cosine_similarity requires numpy to be installed. InMemoryVectorStore の検索は numpy を使うが、自動では入らない pip install numpy
openaipublic.blob.core.windows.net へ接続できない(ProxyError など) OpenAIEmbeddings は既定で tiktoken を使ってトークンの長さを調べ、初回に符号表をダウンロードする 外へ出られる環境で一度実行してキャッシュするか、check_embedding_ctx_length=False を付ける

check_embedding_ctx_length=False にすると、文字列のまま送られました。ソースの説明では、この引数は「入力のトークン数を調べ、embedding_ctx_length より長い入力を自動で分ける」かどうかで、既定(True)では文字列ではなくトークンにして送ります。False は自動の分割も外れるので、長い文はチャンクに分けてから入れます。ソースの説明には、OpenAI 以外の互換サーバー(OpenRouter・Ollama など)に送るときにも False が役立つとあります。

2段階で答える:retriever をチェーンに入れる

「検索 → プロンプトに入れて回答」を毎回必ず行う書き方です。 モデルを呼ぶのは1回で、動きを読みやすいのが利点です。

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

retriever = vector_store.as_retriever(search_kwargs={"k": 2})

def format_docs(docs):
    return "\n\n".join(f"[{d.metadata['source']}] {d.page_content}" for d in docs)

prompt = ChatPromptTemplate.from_messages([
    ("system", "次の資料だけを根拠に答えます。資料に無いことは「資料にありません」と答えます。\n\n{context}"),
    ("human", "{question}"),
])
rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt | model | StrOutputParser()
)
print(rag_chain.invoke("有給休暇はいつまでに申請しますか"))

モデルに送られた system メッセージには、検索で見つかったチャンクが出典つきで入っていました。

次の資料だけを根拠に答えます。資料に無いことは「資料にありません」と答えます。

[就業規則.md] 有給休暇の申請は、取得日の前日までに勤怠システムから行います。急な体調不良の場合

[就業規則.md] 。急な体調不良の場合は当日の始業前に上長へ連絡し、事後申請します。

エージェントで答える:検索をツールにする

検索するか、何で検索するかをモデルに決めさせる書き方です。 雑談には検索しない、言い換えて2回検索する、といった動きができます。そのかわりモデルを呼ぶ回数が増えます。

@tool
def search_docs(query: str) -> str:
    """社内規程を検索して、関係する文を返す"""
    return format_docs(vector_store.similarity_search(query, k=2))

rag_agent = create_agent(model, tools=[search_docs],
                         system_prompt="社内規程の質問には search_docs で調べてから答えます。")
result = rag_agent.invoke({"messages": [{"role": "user", "content": "有給休暇はいつまでに申請しますか"}]})
print(result["messages"][-1].text)

手元では HumanMessage → AIMessage(search_docs の呼び出し)→ ToolMessage(検索結果)→ AIMessage(回答)の順でした。

比べる点 2段階のチェーン エージェント
検索 毎回1回 モデルが決める(0回・複数回もある)
モデルの呼び出し 1回 2回以上
向く場面 社内 FAQ など、質問がほぼ全部検索の対象 雑談も混ざる、複数の資料を引き比べる

RAG の全体の作り方は、関連記事「RAGの作り方|Pythonで動かす最小構成と精度の上げ方」で、埋め込みの考え方は関連記事「エンベディングとは?ベクトル検索とコサイン類似度を計算例で解説」で扱っています。データが増えたら、関連記事「pgvectorの使い方|PostgreSQLでベクトル検索をコピペで構築」のような永続のベクトルストアに替えます。

テスト:API を呼ばずに動かす

langchain-core には、決まった返答を返す偽物のモデルがあります。 単体テストで、API のキーや料金なしにチェーンやエージェントを動かせます。

from langchain_core.language_models.fake_chat_models import FakeListChatModel

fake = FakeListChatModel(responses=["一つ目の返答", "二つ目の返答"])
chain = prompt | fake | StrOutputParser()
print(chain.invoke({"context": "x", "question": "q"}))   # 一つ目の返答
print(chain.invoke({"context": "x", "question": "q"}))   # 二つ目の返答
print(chain.invoke({"context": "x", "question": "q"}))   # 一つ目の返答(最初に戻る)

FakeListChatModel は、リストを使い切ると先頭に戻りました。stream() では1文字ずつ返しました。

エージェントのテストには、AIMessage をそのまま返す GenericFakeChatModel を使います。create_agent はモデルの bind_tools を呼ぶため、何もしない bind_tools を足します。

from langchain_core.language_models.fake_chat_models import GenericFakeChatModel
from langchain_core.messages import AIMessage

class FakeToolModel(GenericFakeChatModel):
    def bind_tools(self, tools, **kwargs):
        return self

fake_model = FakeToolModel(messages=iter([
    AIMessage(content="", tool_calls=[{"name": "get_weather", "args": {"city": "東京"}, "id": "call_1"}]),
    AIMessage(content="東京は晴れです。"),
]))
agent = create_agent(fake_model, tools=[get_weather])
out = agent.invoke({"messages": [{"role": "user", "content": "東京の天気は?"}]})

assert out["messages"][2].content == "東京は晴れ、最高気温22度"   # ツールが実行された
assert out["messages"][-1].text == "東京は晴れです。"

手元では HumanMessage・AIMessage・ToolMessage・AIMessage の4件になり、ツールが本当に呼ばれたことを ToolMessage で確かめられました。モデルの答えの質の確かめ方は、関連記事「LLMの評価方法と評価指標一覧|テストケース表のテンプレート付き」で扱っています。

以前の書き方から今の書き方への対応表

1.x の langchain からは、0.x の多くのモジュールが無くなりました。 手元の langchain 1.4.4 で import した結果と、移り先です。

以前の import・書き方 1.4.4 の結果 今の書き方
from langchain.chains import LLMChain ModuleNotFoundError prompt | model | StrOutputParser()
from langchain.prompts import PromptTemplate ModuleNotFoundError from langchain_core.prompts import ...
from langchain.llms import OpenAI ModuleNotFoundError init_chat_model("openai:...")
from langchain.chat_models import ChatOpenAI ImportError from langchain_openai import ChatOpenAI
from langchain.schema import HumanMessage ModuleNotFoundError from langchain.messages import HumanMessage
from langchain.agents import AgentExecutor ImportError create_agent
from langchain.agents import initialize_agent ImportError create_agent
from langchain.memory import ConversationBufferMemory ModuleNotFoundError checkpointer と thread_id
from langchain.text_splitter import ... ModuleNotFoundError from langchain_text_splitters import ...
from langchain.vectorstores import FAISS ModuleNotFoundError langchain_community か各社パッケージ
from langchain.embeddings import OpenAIEmbeddings ImportError from langchain_openai import OpenAIEmbeddings
from langchain import hub ImportError from langchain_classic import hub
llm.predict(...)・chain.run(...) - invoke(...)
message.text() 動くが非推奨の警告 message.text

すぐには書き換えられないとき:langchain-classic

移行ガイドでは、LLMChain・ConversationChain などのチェーン、retrievers、インデックスの API、hub は langchain-classic に移ったとあります。pip install langchain-classic で入れ、langchain_classic から import します。

from langchain_classic.chains import LLMChain
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI

chain = LLMChain(llm=ChatOpenAI(model="gpt-6-luna"), prompt=PromptTemplate.from_template("{q}を一言で"))
print(chain.invoke({"q": "LangChain"}))

手元では動き、次の結果と警告が出ました。

LangChainDeprecationWarning: The class `LLMChain` was deprecated in LangChain 0.1.17 and will be removed in 2.0.0. Use `RunnableSequence, e.g., `prompt | llm`` instead.
{'q': 'LangChain', 'text': 'LangChainは、LLMを使うアプリを部品の組み合わせで作るライブラリです。'}

langchain-classic は移行のつなぎとして使い、新しく書く部分は | と create_agent で書くと、2.0 での削除の影響を受けにくくなります。以前のコードを書き換える進め方は、関連記事「生成AIでレガシー移行|工程別プロンプトと検証チェック表」で扱っています。

よくある質問

Q. LangChain を使わずに、各社の SDK を直接呼ぶのと何が違いますか。 A. 各社の SDK は、その会社の API の形そのままです。LangChain は、メッセージ・ツール・構造化出力・ストリームを共通の形にそろえます。会社を切り替える、RAG やエージェントの部品を組み合わせる、といった用途で差が出ます。1社の API だけを薄く使うなら、SDK を直接呼ぶほうがコードは短くなります。

Q. ローカル LLM で使えますか。 A. langchain-ollama を入れて init_chat_model("ollama:モデル名") と書きます。Ollama 自体は関連記事「Ollamaの使い方|ローカルLLMのコマンド一覧とAPIの呼び方」で扱っています。

Q. LangGraph との違いは? A. create_agent は LangGraph の上に作られていて、pip install langchain で langgraph も入ります。create_agent は「モデル → ツール → モデル」をくり返す決まった形を作る関数です。決まった順番の処理や細かい分岐を自分で組みたいときは、LangGraph の StateGraph を直接使う方法があります。

Q. API キーをコードに書かずに済ませるには? A. 各社のクラスは環境変数(OPENAI_API_KEY・ANTHROPIC_API_KEY)を読みます。関連記事「.envの使い方と注意点|APIキーを安全に管理する方法」で扱っています。

Q. 429 エラーが出ます。 A. ChatOpenAI は SDK の既定で2回まで再試行します。回数は max_retries で変えられます。考え方は関連記事「LLM APIの429・529エラー対処|リトライ実装とエラー一覧」で扱っています。

まとめ

  • 1.x は langchain + 各社のパッケージ(langchain-openai など) を入れる。Python 3.10 以上
  • モデルは init_chat_model("openai:モデル名")、文字は .text で取る(content はリストのこともある)
  • LLMChain は prompt | model | StrOutputParser() に書き換える
  • 決まった形は with_structured_output(Pydanticのクラス)。エージェントでは response_format
  • ツールは @tool(docstring と型ヒントが説明になる) → create_agent。会話は checkpointer と thread_id
  • インメモリの RAG は numpy が必要。プロキシの内側では OpenAIEmbeddings の tiktoken の取得に注意
  • テストは FakeListChatModel・GenericFakeChatModel で API を呼ばずに回せる

参考・出典

確認日はいずれも 2026年10月13日です。

  • LangChain ドキュメント「Install LangChain」:https://docs.langchain.com/oss/python/langchain/install
  • LangChain ドキュメント「Models」:https://docs.langchain.com/oss/python/langchain/models
  • LangChain ドキュメント「Agents」:https://docs.langchain.com/oss/python/langchain/agents
  • LangChain ドキュメント「Structured output」:https://docs.langchain.com/oss/python/langchain/structured-output
  • LangChain ドキュメント「Streaming」:https://docs.langchain.com/oss/python/langchain/streaming
  • LangChain ドキュメント「RAG」:https://docs.langchain.com/oss/python/langchain/rag
  • LangChain ドキュメント「LangChain v1 migration guide」:https://docs.langchain.com/oss/python/migrate/langchain-v1
  • PyPI「langchain」:https://pypi.org/project/langchain/
  • GitHub「langchain-ai/langchain」:https://github.com/langchain-ai/langchain

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「LangChain入門|PythonでLLMアプリを作る基本とコピペ集」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?