はじめに
LangChain 1.0がリリースされました。
詳細は以下の公式Blogを確認ください。
主な変化点は以下のドキュメントにも掲載されています。
ドキュメント含めて大きく変更されており、またαリリースの際よりも組み込みMiddlewareも増えてましたので、個人的に気になる内容をピックアップしてDatabricks Free Edition上で検証していきます。
この記事では、「動的なシステムプロンプト制御」を扱います。
LangChain v1以降は、動的なシステムプロンプト設定もミドルウェアとして実装するようになりました。
今回はこれを実践してみます。
動的なシステムプロンプト制御
まずはノートブックを作成して必要なパッケージをインストール。
TracingのためにMLflowもインストールします。
%pip install -U langchain>=1.0.0 langchain_openai>=1.0.0 mlflow
%restart_python
次に利用するモデルをセットアップします。
利用するモデルは任意でOKですが、今回はdatabricks-gpt-oss-20bを利用しました。
from langchain.chat_models import init_chat_model
import mlflow
mlflow.langchain.autolog()
creds = mlflow.utils.databricks_utils.get_databricks_host_creds()
model = init_chat_model(
"openai:databricks-gpt-oss-20b",
api_key=creds.token,
base_url=creds.host + "/serving-endpoints",
)
次にエージェントに与えるコンテキストと、動的にシステムプロンプトを変更するロジックを実装します。
与えられたコンテキスト(user_role)の内容に応じてシステムプロンプトが変わる実装となります。
システムプロント切替を担う関数user_role_promptは、@dynamic_promptデコレータを付与することでシステムプロンプト設定時に割り込むエージェントミドルウェアとして動作するようになります。
from typing import TypedDict
from langchain.agents.middleware import dynamic_prompt, ModelRequest
class Context(TypedDict):
user_role: str
@dynamic_prompt
def user_role_prompt(request: ModelRequest) -> str:
"""ユーザーの役割に基づいてシステムプロンプトを生成します。"""
# 動作がわかるように、mlflow tracingを利用
with mlflow.start_span(name="user_role_prompt") as span:
user_role = request.runtime.context.get("user_role", "user")
base_prompt = "You are a helpful assistant."
final_prompt = base_prompt
span.set_inputs(user_role)
if user_role == "expert":
final_prompt = f"{base_prompt} Provide detailed technical responses."
elif user_role == "beginner":
final_prompt = f"{base_prompt} Explain concepts simply and avoid jargon."
span.set_outputs(final_prompt)
return final_prompt
最後に上記ミドルウェアを組み込んだ単純なエージェントを作成・実行します。
from langchain.agents import create_agent
from pprint import pprint
def get_weather(city: str) -> str:
"""指定した都市の天気を取得します。"""
return f"It's always sunny in {city}!"
agent = create_agent(
model=model,
tools=[get_weather],
middleware=[user_role_prompt],
context_schema=Context,
)
input = {"messages": [{"role": "user", "content": "SFの天気は?"}]}
context = {"user_role": "expert"}
for event in agent.stream(input, context=context, stream_mode="updates"):
pprint(event)
実行結果は以下のようになりました。
{'model': {'messages': [AIMessage(content='', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 485, 'prompt_tokens': 154, 'total_tokens': 639, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_provider': 'openai', 'model_name': 'gpt-oss-20b-080525', 'system_fingerprint': None, 'id': 'chatcmpl_13552dc2-73e6-445e-8fab-91c230bb9467', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--2a662f2d-13d9-4af0-bdc5-962b6ace0fe1-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'SF'}, 'id': 'call_44a7f953-851e-4b3e-a1f0-9a7cd9570443', 'type': 'tool_call'}], usage_metadata={'input_tokens': 154, 'output_tokens': 485, 'total_tokens': 639, 'input_token_details': {}, 'output_token_details': {}})]}}
{'tools': {'messages': [ToolMessage(content="It's always sunny in SF!", name='get_weather', id='9445c857-8c63-4fea-8491-81ce69370526', tool_call_id='call_44a7f953-851e-4b3e-a1f0-9a7cd9570443')]}}
{'model': {'messages': [AIMessage(content='**サンフランシスコ(SF)の現在の天気**(リアルタイムデータは省きます。ご利用のアプリやウェブサイトで最新情報をご確認ください)\n\n| 項目 | 内容 |\n|------|------|\n| 現在の天気 | 晴れ |\n| 現在の温度 | 18\u202f°C(約64\u202f°F) |\n| 高温(今日) | 18\u202f°C |\n| 最低温(今日) | 10\u202f°C |\n| 湿度 | 60\u202f% |\n| 風速 | 4\u202fm/s(約9\u202fmph) |\n| 風向 | 北西(NW) |\n| 雨確率 | 0\u202f% |\n| 空の見通し | ほぼ晴天、雲は薄い |\n\n---\n\n### 予報(3日間) \n| 日 | 最高気温 | 最低気温 | 天気 | 雨確率 |\n|----|----------|----------|------|--------|\n| 今日は | 18\u202f°C | 10\u202f°C | 晴れ | 0\u202f% |\n| 1日後 | 17\u202f°C | 9\u202f°C | 晴れ | 5\u202f% |\n| 2日後 | 16\u202f°C | 8\u202f°C | 晴れ | 10\u202f% |\n\n> **備考**: \n> - サンフランシスコは海に近く、微妙な温度変化があります。午後から夕暮れにかけて風が弱まることが多いです。 \n> - 夜間は昼間よりも30〜40\u202f%冷えてきやすいので、川沿いの散歩や屋外活動に行く際は軽いジャケットを持参すると快適です。 \n\n---\n\n#### さらに詳しい情報が必要な場合\n- **気象庁、NOAA** などの公式サイトで最新数値を確認してください。 \n- スマホアプリ(Weather Underground、Forecastなど)で通知設定をすると、リアルタイムで変化に追従できます。 \n\nご質問や他の都市・地域の天気についてもどうぞお気軽にどうぞ!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 612, 'prompt_tokens': 191, 'total_tokens': 803, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_provider': 'openai', 'model_name': 'gpt-oss-20b-080525', 'system_fingerprint': None, 'id': 'chatcmpl_b09e98f5-6423-44ca-8a1d-f9fd2a9f4d29', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--d7c693e6-084a-4081-be7b-7deee1bbf3ac-0', usage_metadata={'input_tokens': 191, 'output_tokens': 612, 'total_tokens': 803, 'input_token_details': {}, 'output_token_details': {}})]}}
MLflow Tracingの結果は以下になります。
LLM(ChatOpenAI_x)のクエリ実行前にuser_role_promptが呼ばれているのがわかりますね。
このようにコンテキストやエージェントの状態に応じてシステムプロンプトの入れ替えが動的に制御できます。
まとめ
LangChainエージェントのシステムプロント入力を動的に設定する方法を試してみました。
こういった動作のカスタムはほとんどミドルウェアでなんとかさせようという思想が見て取れますね。
少し後の記事で掲載予定ですが、deepagentsもミドルウェアを活用する実装になっており、これを見るとほとんどのカスタムはミドルウェアでなんとかなりそう感があります。
ミドルウェアの使い方については別記事でフォローしていきたいと思います。
