チュートリアル: 既存のエージェントにセッションとメモリを追加する
ベータ版
この機能はベータ版です。
Python で既にエージェントを構築している場合は、書き換えることなく、Databricks 上で永続的な会話履歴と長期メモリを提供できます。このチュートリアルでは、各会話を保持するセッションストア、会話全体で永続的な事実を保持するメモリストア、格納された会話をメモリに抽出するメモリパイプラインにエージェントを接続します。ステップは、エージェントがどこで実行されるかに関係なく、OpenAI Agents SDK、LangGraph、またはその他の Python フレームワークで構築されたエージェントを対象としています。
エージェントがまだない場合は、代わりにAgent Bricksクイックスタートを起動してください。Agent Bricks CLI は、セッションとメモリが既に設定された新しいエージェントをスキャフォールドします。
この例では、サポートエージェントは顧客の会話を中断したところから再開し、後で別の会話を行うときに顧客の設定を記憶する必要があります。
セッションとメモリの関連性については、エージェントメモリとセッションを参照してください。
要件
- Python 3.10 以上で記述された既存のエージェント。フレームワーク固有のステップでは、OpenAI Agents SDKとLangGraphについて説明しています。その他のフレームワークのステップでは、AgentKit SDKを直接呼び出します。
- Databricks認証。AgentKit SDKは、Databricks SDK for Pythonのdefault認証を使用します。ローカル開発の場合は、
databricks auth login --host <workspace-url>をランするか、DATABRICKS_HOSTおよびDATABRICKS_TOKEN環境変数を設定します。 - メモリパイプラインのランのステータスを確認するためのDatabricks CLI。
- メモリパイプラインを作成するには、パイプラインが使用するモデルに対するクエリーアクセス許可、およびメモリパイプラインの要件にリストされている他のアクセス許可が必要です。
ステップ1:AgentKit SDKとAgent Bricks CLIをインストールする
databricks-agentbricks パッケージには、以下が含まれます。
- AgentKit SDK (
databricks_agentkit) は、セッションおよびメモリストア用の Python クライアントです。 - Agent Bricks CLI (
agentbricks)。 - セッションストアとメモリストアを各フレームワークの既存のセッションおよびツールインターフェイスに接続する、OpenAI Agents SDKおよびLangGraph用のアダプター。アダプターは、エージェントにすでにインストールされているフレームワークパッケージを使用します。
エージェントの環境にバージョン0.5.0以上をインストールします。
pip install --upgrade "databricks-agentbricks>=0.5.0"
ステップ2: セッションストアとメモリストアを作成する
エージェントは、Databricksが管理する次の2つのストアを使用します:
- セッションストア には会話の履歴が保持されます。その中の各セッションは1つの会話の記録であり、エージェントはそれを再ロードしてその会話が中断したところから再開します。
- メモリストア は、設定や過去の決定など、ユーザーに関する永続的な事実を保持します。エージェントは、以前の別の会話で学習した内容を思い出すために検索します。
ストアはセキュリティ境界であり、ストアへのアクセス権を持つプリンシパルは、そのすべてのエントリを読み書きできます。テナントを互いに分離するには、テナントごとに別々のストアのペアを作成します。メモリのパーティション分割と保護を参照してください。
各ストアを一度作成し、すべての会話で再利用します。エージェントは両方のストアを名前で参照します。
- AgentKit SDK
- Agent Bricks CLI
- REST API
from databricks.sdk import WorkspaceClient
from databricks_agentkit import AgentKitClient
client = AgentKitClient(WorkspaceClient())
client.session_stores.create("support-agent-sessions", description="Support agent conversations")
client.memory_stores.create("support-agent-memory", description="Support agent long-term memory")
agentbricks sessions stores create --name support-agent-sessions --description "Support agent conversations"
agentbricks memory stores create --display-name support-agent-memory --description "Support agent long-term memory"
DATABRICKS_HOST をワークスペースのホスト名に設定し、以下をランします。
export DATABRICKS_TOKEN=$(databricks auth token --profile <profile> | jq -r .access_token)
curl -X POST "https://${DATABRICKS_HOST}/api/2.0/agents/session-stores?session_store_name=support-agent-sessions" \
-H "Authorization: Bearer ${DATABRICKS_TOKEN}" -H "Content-Type: application/json" \
-d '{"description": "Support agent conversations"}'
curl -X POST "https://${DATABRICKS_HOST}/api/2.0/agents/memory-stores" \
-H "Authorization: Bearer ${DATABRICKS_TOKEN}" -H "Content-Type: application/json" \
-d '{"display_name": "support-agent-memory", "description": "Support agent long-term memory"}'
ストア名はワークスペース内で一意である必要があります。メモリストア名は3~56文字で、小文字で始まり、文字または数字で終わり、小文字、数字、およびハイフンのみを含める必要があります。セッションストア名は3~63文字である必要があります。
ステップ 3: アクター ID とセッション ID を選択する
エージェントがストアに対して行うすべての呼び出しには、次の 2 つの識別子が含まれます。
actor_idは、誰の会話とメモリかを識別します。両方のストアがこれを使用しており、メモリパイプラインは各アクターのメモリをそのアクターのセッションと同じactor_idの下に書き込むため、エージェントは両者が一致する場合にのみ抽出されたメモリを呼び出します。session_idは1つの会話を識別するため、フォローアップリクエストによって会話が再開されます。
このチュートリアルでは、test-userをactor_idとして、conversation-1をsession_idとして使用します。以下のステップでは、handle_message関数にそれらをuser_idおよびconversation_idとして渡します。
本番運用では、サインインユーザーの検証済みIDなど、信頼できるリクエストコンテキストからアプリケーションコード内でactor_idを設定し、モデルやユーザーのメッセージにその値を選択させないでください。チーム間でメモリを共有したり、テナントを分離したりするには、メモリを分割して保護するをご覧ください。session_idには、アプリの既存の会話IDまたはスレッドIDを使用するか、会話の起動時にUUIDを生成します。
ステップ4:セッションストアに会話を保存する
会話履歴とは、ユーザーのメッセージ、エージェントの返信、およびツール呼び出しと結果など、1つの会話の順序付けられた記録です。セッションストアは各会話の履歴を保存するため、エージェントは再起動後でも、次のリクエストで履歴を再ロードできます。エージェントのインプロセス会話履歴を、ステップ 2 で作成したセッションストアに置き換えます。
フレームワークアダプターは各フレームワークがセッション状態に既に使っているインターフェースにプラグインされるため、エージェントのモデル、ツール、プロンプトは変更されません。
- OpenAI Agents SDK
- LangGraph
- Other frameworks
OpenAIエージェントSDKは、Sessionオブジェクトを通じて会話履歴の読み取りと書き込みを行います。session_store()は、管理されたセッションストアに支えられたSessionを返します。それをRunner.runに渡します:
from agents import Agent, Runner
from databricks_agentkit.openai import session_store
SESSION_STORE = "support-agent-sessions"
# Your existing agent, unchanged.
agent = Agent(name="Support agent", instructions="You help customers with their Databricks workspaces.")
async def handle_message(user_id: str, conversation_id: str, message: str) -> str:
session = session_store(conversation_id, actor=user_id, store=SESSION_STORE)
result = await Runner.run(agent, message, session=session)
return result.final_output
会話の最初のメッセージで、アダプターはセッションを作成します。すべてのランで、SDKはセッションから会話の履歴をロードし、新しいユーザーメッセージ、モデル出力、およびツールアイテムを追加します。
LangGraph は、スレッドIDによってキー設定されたチェックポインターを通じてグラフの状態を永続化します。 checkpointer() はマネージドセッションストアによってバックアップされたチェックポインターを返し、 thread_config() は会話とそのユーザーをスレッドにマッピングします。
from langchain.agents import create_agent
from databricks_agentkit.langgraph import checkpointer, thread_config
SESSION_STORE = "support-agent-sessions"
# Your existing model and tools, unchanged, with the managed checkpointer added.
agent = create_agent(model=model, tools=tools, checkpointer=checkpointer(store=SESSION_STORE))
async def handle_message(user_id: str, conversation_id: str, message: str) -> str:
result = await agent.ainvoke(
{"messages": [{"role": "user", "content": message}]},
config=thread_config(conversation_id, actor=user_id),
)
return result["messages"][-1].content
エージェントが既にチェッカーポインターを使用している場合は、それを checkpointer() に置き換えます。StateGraph を直接ビルドする場合は、チェックポインターを compile() に渡します。チェッカーポインターは、人間による割り込みを含むグラフの状態全体を保存するため、停止するランを再起動後に再開できます。
チェックポインターはシリアル化されたグラフのチェックポイントを保存しますが、メモリパイプラインはこれを読み取りません。LangGraphエージェントでメモリパイプラインを使用するには、トランスクリプトも記録します。
その他のフレームワークや直接のモデル呼び出しについては、AgentKit SDKを使用して会話履歴を読み取り、追加します。各メッセージを {"type": "message", "role": "user" | "assistant", "content": "<text>"} として格納します。メモリパイプラインは、ユーザーおよびアシスタントのメッセージをこの形状に、またテキストコンテンツ部分を持つOpenAI Responsesメッセージの形状に蒸留します。ツール呼び出しやツール結果など、他のすべての項目をスキップします。
from databricks.sdk import WorkspaceClient
from databricks_agentkit import AgentKitClient
client = AgentKitClient(WorkspaceClient())
session_store = client.session_stores.get("support-agent-sessions")
def start_conversation(user_id: str, conversation_id: str) -> None:
session_store.add(actor_id=user_id, session_id=conversation_id)
def handle_message(user_id: str, conversation_id: str, message: str) -> str:
session = session_store.get(conversation_id)
if session.actor_id != user_id:
raise PermissionError("This conversation belongs to a different user.")
# Rebuild context from the stored history, oldest first.
history = [item.data for item in session.list_items(order_by="create_time asc")]
user_item = {"type": "message", "role": "user", "content": message}
reply = run_agent([*history, user_item]) # Your framework or model call.
session.append_items([user_item, {"type": "message", "role": "assistant", "content": reply}])
return reply
session_id によるセッションの検索はアクターにスコープされません。そのため、セッションを再開する前に、セッションがリクエストユーザーに属していることを確認してください。
セッションストアに切り替えても、エージェントが他の場所に保存した会話は移動しません。既存の会話を引き継ぐには、その項目を新しいセッションに追加します。
ステップ 5: エージェントにメモリを与える
セッション履歴は、1回の会話を対象としています。エージェントが過去の会話で学習した内容を使用するには、エージェントをメモリストアに接続します。2つのパターンがあり、それらを組み合わせることができます。
- メモリツール: モデルは、いつメモリを呼び出し、いつ新しい事実を保存するかを決定します。
- 各ランの前に呼び出す: アプリケーションは、メモリ内で受信メッセージを検索し、その結果をエージェントのコンテキストに追加します。そのため、エージェントはツールを呼び出すことなく関連するメモリを確認できます。
メモリパイプラインがメモリを書き込む場合(ステップ6を参照)、エージェントはメモリを呼び出すだけで済みます。メモリを保存するツールはスキップできます。
- OpenAI Agents SDK
- LangGraph
- Other frameworks
次の例では、エージェントの要求ごとのコピーで両方のパターンを使用しています。
memory_tools()ユーザーのメモリを検索するrecallツールと、事実を保存するrememberツールを返します。ツールは1つのアクターにバインドされているため、モデルは他のユーザーのメモリを読み書きできません。recalled_facts()ランの前に着信メッセージのメモリを検索し、その結果をエージェントの指示に追加します。
from databricks.sdk import WorkspaceClient
from databricks_agentkit import AgentKitClient
from databricks_agentkit.openai import memory_tools, session_store
MEMORY_STORE = "support-agent-memory"
memory_store = AgentKitClient(WorkspaceClient()).memory_stores.get(MEMORY_STORE)
def recalled_facts(user_id: str, message: str) -> str:
results = memory_store.search(actor_id=user_id, query=message, limit=5)
return "\n".join(f"- {r.memory.content}" for r in results)
async def handle_message(user_id: str, conversation_id: str, message: str) -> str:
instructions = agent.instructions # Your agent's existing instructions, as a string.
if facts := recalled_facts(user_id, message): # Recall before the run.
instructions += f"\n\nWhat you know about this user:\n{facts}"
user_agent = agent.clone(
instructions=instructions,
tools=[*agent.tools, *memory_tools(user_id, store=MEMORY_STORE)], # Memory tools.
)
session = session_store(conversation_id, actor=user_id, store=SESSION_STORE)
result = await Runner.run(user_agent, message, session=session)
return result.final_output
呼び出された事実は入力ではなく指示に入り、セッションには保存されません。いずれか1つのパターンのみを使用するには、もう一方を削除します。memory_tools()行を削除するか、recalled_facts()呼び出しを削除します。ツールを維持する場合は、エージェントの指示を更新して使用するタイミングを伝えます。例えば、「ユーザーが回答する前に、メモリを使用してユーザーの好みを検索する」などです。"ユーザーが共有する永続的な事実を保存するには、記憶を使用します。"
以下の例では両方のパターンを使用し、リクエストごとにエージェントを構築します。チェックポインターは一度作成され、共有されます:
memory_tools()ユーザーのメモリを検索するrecallツールと、事実を保存するrememberツールを返します。ツールは1つのアクターにバインドされているため、モデルは他のユーザーのメモリを読み書きできません。recalled_facts()ランの前に受信メッセージをメモリで検索し、結果をシステム プロンプトに追加します。
from databricks.sdk import WorkspaceClient
from databricks_agentkit import AgentKitClient
from langchain.agents import create_agent
from databricks_agentkit.langgraph import checkpointer, memory_tools, thread_config
MEMORY_STORE = "support-agent-memory"
memory_store = AgentKitClient(WorkspaceClient()).memory_stores.get(MEMORY_STORE)
def recalled_facts(user_id: str, message: str) -> str:
results = memory_store.search(actor_id=user_id, query=message, limit=5)
return "\n".join(f"- {r.memory.content}" for r in results)
async def handle_message(user_id: str, conversation_id: str, message: str) -> str:
system_prompt = SYSTEM_PROMPT # Tell the agent when to use recall and remember.
if facts := recalled_facts(user_id, message): # Recall before the run.
system_prompt += f"\n\nWhat you know about this user:\n{facts}"
agent = create_agent(
model=model,
tools=[*tools, *memory_tools(user_id, store=MEMORY_STORE)], # Memory tools.
system_prompt=system_prompt,
checkpointer=checkpointer(store=SESSION_STORE),
)
result = await agent.ainvoke(
{"messages": [{"role": "user", "content": message}]},
config=thread_config(conversation_id, actor=user_id),
)
return result["messages"][-1].content
LangGraphはシステムプロンプトをグラフ状態に保存せずに各モデル呼び出しに追加するため、チェックポインターは呼び出された事実を保存しません。一方のパターンのみを使用するには、もう一方のパターンを削除します。memory_tools()行を削除するか、recalled_facts()呼び出しを削除します。
各ランの前にメモリ内の着信メッセージを検索し、結果をコンテキストとしてエージェントに渡します。検索は全テキストの関連性によってエントリをランク付けするため、ユーザー自身の言葉から構築されたクエリーがうまく機能します。
memory_store = client.memory_stores.get("support-agent-memory")
def recall(user_id: str, message: str) -> list[dict]:
results = memory_store.search(actor_id=user_id, query=message, limit=5)
if not results:
return []
facts = "\n".join(f"- {r.memory.content}" for r in results)
return [{"role": "system", "content": f"What you know about this user:\n{facts}"}]
ステップ 4 の handle_message で、呼び出されたコンテキストをエージェントの入力に追加します。セッションに追加しないでください:
reply = run_agent([*recall(user_id, message), *history, user_item])
エージェント自身にメモリを保存させるには、ストアの search および add 操作をフレームワークのツールとしてラップします。エージェントにメモりツールを与えるを参照してください。
ステップ 6: メモリパイプラインを作成する
メモリパイプラインは、セッションストア内の会話を読み取り、各アクターの永続的な事実、好み、決定をメモリストアに抽出します。また、時間が経過しても重複するエントリをMergeするため、エージェントのメモリは追加のエージェントコードなしで統合されたままになります。
-
2つのストアを接続するパイプラインを作成します:
Bashagentbricks memory pipeline create \
--session-store support-agent-sessions \
--memory-store support-agent-memory \
--trigger scheduled \
--model system.ai.claude-sonnet-5 \
--instructions "Focus on durable customer preferences and account details. Ignore one-off troubleshooting steps."--trigger scheduled約24時間ごとに自動的にパイプラインを実行する。これがない場合、パイプラインはdefault Triggerであるmanualを使用し、ランを起動したときにのみランします。スケジュールに基づいてパイプラインをランするを参照してください。--model記憶を抽出するモデルを設定します。クエリーの実行権限がある任意のモデルを使用できます。--modelを省略すると、パイプラインはDatabricksが選択したdefaultのモデルを使用します。--instructionsはオプションであり、パイプラインが保持するものを制御します。指示による蒸留の制御を参照してください。
コマンドは、パイプラインのリソース名を
memory-pipelines/<pipeline-id>の形式で返します。オプションごとに、agentbricks memory pipeline create --helpを実行するか、 Agent Bricks CLI コマンド リファレンスを参照してください。 -
スケジューラは、最初のスケジュールされたランを起動するタイミングを選択します。セッションストアに既に存在する会話を抽出するには、今すぐ実行を起動してください:
Bashagentbricks memory pipeline run <pipeline-id>コマンドは、
memory-pipelines/<pipeline-id>/runs/<run-id>の形式でランのリソース名を返します。 -
REST APIでランの結果を確認します。
DATABRICKS_HOSTをワークスペースのホスト名に設定してから、以下を実行します:Bashexport DATABRICKS_TOKEN=$(databricks auth token --profile <profile> | jq -r .access_token)
curl "https://${DATABRICKS_HOST}/api/2.0/agents/memory-pipelines/<pipeline-id>/runs/<run-id>" \
-H "Authorization: Bearer ${DATABRICKS_TOKEN}"ランの
stateがSUCCEEDEDの場合、抽出されたメモリはメモリストアにあります。
パイプラインのランでは、エージェントのIDではなく、パイプラインを作成したIDが使用されます。パイプラインは、エージェントがメモリツールで保存したエントリを含め、処理する actor_id の下にあるエントリを書き換え、Merge、または削除できます。REST API、ランの状態、制限事項については、エージェントのメモリパイプラインを参照してください。
LangGraphエージェントのトランスクリプトを記録する
メモリパイプラインは、ユーザーとアシスタントのメッセージのみを抽出します。LangGraph チェックポインタはシリアル化されたチェックポイントを格納するため、チェックポインタのセッションストアを読み取るパイプラインは抽出するメッセージを見つけられません。LangGraph エージェントでパイプラインを使用するには、各ターンのメッセージを別のセッションストアに追加し、そのストアにパイプラインを作成します。
transcripts = client.session_stores.create("support-agent-transcripts") # Create once.
def start_conversation(user_id: str, conversation_id: str) -> None:
transcripts.add(actor_id=user_id, session_id=conversation_id)
def record_turn(conversation_id: str, message: str, reply: str) -> None:
transcripts.get(conversation_id).append_items(
[
{"type": "message", "role": "user", "content": message},
{"type": "message", "role": "assistant", "content": reply},
]
)
会話が開始された時、最初の ainvoke の前に start_conversation を呼び出し、各 ainvoke の後に record_turn を呼び出します。パイプラインを作成するときに --session-store support-agent-transcripts を渡します。あるいは、パイプラインをスキップして、エージェントがメモリツールでメモリを保存できるようにします。
ステップ 7: デプロイされたエージェントにアクセス権を付与する
エージェントをローカルでランすると、独自の資格情報が使用されます。デプロイされたエージェントは通常、Service Principalとして実行されます。これには、読み取りまたは書き込みを行うすべてのストアへのアクセスが必要です。AgentKit SDKでアクセス権を付与し、Service Principalのアプリケーション(クライアント)IDを渡します:
from databricks.sdk import WorkspaceClient
from databricks_agentkit import AgentKitClient
w = WorkspaceClient()
client = AgentKitClient(w)
# For an agent deployed as a Databricks app, look up the app's service principal.
sp_client_id = w.apps.get("support-agent").service_principal_client_id
client.session_stores.get("support-agent-sessions").grant_permission(sp_client_id)
client.memory_stores.get("support-agent-memory").grant_permission(sp_client_id)
grant_permission defaultで書き込みアクセス権を付与します。読み取り専用アクセス権を付与するには、permission="READ" を渡します。LangGraphエージェントのトランスクリプトストアを作成した場合は、それへのアクセス権も付与します。メモリパイプラインはエージェントのService Principalに対する権限を必要としません。これは、パイプラインの実行がパイプラインを作成したIDを使用するためです。
ステップ8: エージェントをエンドツーエンドでテストする
-
user_id="test-user"とconversation_id="conversation-1"でhandle_messageを呼び出し、「電話よりEメールがよく、太平洋タイムゾーンにいます」といった永続的な好みを共有します。他のフレームワークのステップに従った場合は、最初にstart_conversation("test-user", "conversation-1")を呼び出してください。同じ会話でフォローアップメッセージを送信し、エージェントが以前のコンテキストを保持していることを確認します。 -
セッションストアに会話が保持されていることを確認します:
Bashagentbricks sessions items list --store support-agent-sessions --session-id conversation-1 -
メモリパイプラインの実行を起動し、ステップ6と同様に、
stateがSUCCEEDEDになるまで待ちます。 -
メモリストアにテストユーザーの設定が保持されていることを確認します。
Bashagentbricks memory entries search --store support-agent-memory --actor-id test-user --query "contact preferences" -
test-userについてconversation_id="conversation-2"との新しい会話を起動し、「どのように連絡すればよいですか?」と質問します。エージェントはメモリから回答します。
トラブルシューティング
PERMISSION_DENIED(HTTP 403)をデプロイ済みエージェントから取得しました。 エージェントのService Principalがストアにアクセスできません。ステップ 7 のように、アクセス権を付与してください。- パイプラインのランは成功しますが、エントリは書き込まれません。 セッションストアに、パイプラインが読み取る形式でユーザーメッセージとアシスタントメッセージが保持されていることを確認します。パイプラインは、ツール呼び出し、ツールの結果、および LangGraph チェックポイントをスキップします。ランは、そのアクターの最後の成功したラン以降のアクターのセッションのみを処理します。
- エージェントは蒸留されたメモリを想起しません。 セッションで使用されているものと同じ
actor_idを使って、エージェントがメモリを検索することを確認します。 - ランの開始が
ABORTEDで失敗しました。 パイプラインの別のランが進行中です。終了するまで待ち、新しいランを起動してください。
クリーンアップ
このチュートリアルで作成したリソースを削除するには、以下を実行してください。ストアを削除すると、その中のすべてが削除されます。
agentbricks memory pipeline delete <pipeline-id>
agentbricks sessions stores delete support-agent-sessions
agentbricks memory stores delete support-agent-memory