- Python
LangChain入門|PythonでLLMアプリを作る基本とコピペ集
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期連続の黒字決算。