すべてのプロダクト
Search
ドキュメントセンター

PolarDB:PolarSearch メモリコンテナーによる AI エージェントの長期・短期メモリの構築

最終更新日:Mar 18, 2026

AI エージェントは、マルチターン対話やシーケンシャルタスクを実行する際に、ユーザーのプリファレンス、過去の命令、またはコンテキスト情報を保持できないことがよくあります。このような効果的なメモリの欠如は、対話エクスペリエンスを妨げ、タスクの効率を低下させます。PolarSearch メモリコンテナーはこの問題を解決します。これは、PolarDB for PostgreSQL に組み込まれた長期および短期メモリ管理システムです。この機能により、エージェントは会話データを自動的に構造化して永続化します。その後、セマンティック検索を使用して関連するメモリを迅速に取得し、真に継続的で、パーソナライズされた、効率的なサービスを提供します。

説明

PolarSearch メモリコンテナー機能はカナリアリリース中です。アクセスをリクエストするには、チケットを送信してください。

仕組み

PolarSearch メモリコンテナーは、自動化された処理フローを使用して、非構造化会話データを検索可能で利用可能な構造化メモリに変換します。これは、3層ストレージアーキテクチャとインテリジェントなメモリ処理パイプラインに基づいて構築されています。

3層ストレージアーキテクチャ

メモリコンテナーは、階層化された設計を使用して、ライフスパンが異なるメモリのストレージと取得の効率のバランスを取ります。

メモリタイプ

インデックスタイプ

主な目的

短期メモリ (ワーキングメモリ)

転置インデックス

未処理の生の短期会話データを保存します。高速な取得のために転置インデックスを使用します。現在の会話のコンテキストを維持します。

長期メモリ (長期メモリ)

ベクトルインデックス + 転置インデックス

大規模言語モデル (LLM) を使用して会話から主要なファクトを抽出し、ベクトルとして保存します。深い知識と経験の取得のために KNN セマンティック検索をサポートします。

メモリ履歴 (履歴メモリ)

転置インデックス

すべてのメモリオペレーション (追加や更新など) を記録し、完全な監査ログを形成します。メモリの進化が追跡可能であることを保証します。

メモリ処理フロー

エージェントが新しい入力を受け取ると、メモリコンテナーは一連のアクションをトリガーして、メモリを自動的に取り込み、永続化し、取得します。

image
  1. メモリの取り込み:

    • 入力と前処理:Elasticsearch プロトコルを使用してエージェントの会話入力を受け取ります。オプションで大規模言語モデル (LLM) を呼び出して、要約の生成、意図の分類、またはタグの抽出を行うことができます。

    • ルーティングの決定:メモリールーターは入力を分析し、既存のメモリと比較して、メモリを追加 (ADD)、更新 (UPDATE)、または削除 (DELETE) するかどうかを決定します。

  2. メモリの永続化:

    • 短期メモリ:生の会話コンテンツを保存します。

    • 長期メモリ:LLM によって抽出され、埋め込みモデルによってベクトル化された構造化ファクトを保存します。

    • メモリ履歴:すべての操作をメモリ履歴に書き込み、監査証跡を作成します。

  3. メモリ取得:

    • 入力解析:検索条件を受け入れます。Elasticsearch プロトコルを使用してマルチモーダル検索リクエストを解析します。権限検証モジュールを呼び出して、データアクセス境界を確認します。

    • ハイブリッドインデックス検索:長期メモリでハイブリッド検索 (ANN ベクトル類似性検索と転置インデックスフィルタリング) を実行します。短期メモリでキーワードマッチングを実行します。メモリのタイムスタンプに基づいて重みを調整します。

    • 結果の統合:リランクモデルを使用して結果の順序を最適化します。タイムスタンプやその他のメタデータを使用して競合を解決します。最も関連性の高いメモリをエージェントに返します。

主な利点

機能ディメンション

従来のアプローチ

PolarSearch ソリューション

メモリ使用率

既存データの 30% 未満しか保持されず、効果的な活用が妨げられています。

LLM を使用してファクトを抽出し、セマンティックインデックスと組み合わせることで、有効な情報利用率を 85% 以上に向上させます。

検索効率

キーワードマッチングに依存し、精度は 60% 以下です。

ハイブリッドベクトルおよび転置インデックス、さらにリランキングを使用します。これにより、95% 以上のセマンティック検索精度を達成します。

エンタープライズコンプライアンス

マルチテナント分離と権限コントロールの実装は複雑です。

ネイティブのマルチテナント分離、ロールベースアクセス制御 (RBAC) による権限コントロール、および操作監査を提供し、企業のセキュリティ要件を満たします。

システムのスケーラビリティ

単一ポイントのストレージ容量はテラバイトレベルが上限です。

クラウドネイティブ分散ストレージアーキテクチャ上に構築されています。ストレージはペタバイトレベルまで拡張可能です。

開発コスト

カスタム開発が必要で、実装に長い時間がかかります。

すぐに使える API と SDK を提供し、開発を簡素化し、迅速な統合を可能にします。

適用範囲

クイックスタート

このガイドでは、環境設定からメモリの保存と取得までの全プロセスを説明します。

全体のワークフロー:環境の準備 → プラグインの有効化 → モデルの登録 → メモリコンテナーの作成 → メモリの保存と検証。

ステップ 1:アクセス認証情報と環境変数の設定

開始前に、次の情報を準備し、環境変数として設定してください。これにより、後続の curl コマンドが簡素化され、繰り返し編集する必要がなくなります。すべての構成と認証情報を一元化することで、コピーおよび実行が容易になります。

変数名

説明

値の例

POLARSEARCH_HOST_PORT

PolarSearch ノードの接続アドレスとポート

pc-xxx.polardbsearch.rds.aliyuncs.com:3001

USER_PASSWORD

PolarSearch ノードの管理者アカウント

polarsearch_user:your_password

YOUR_API_KEY

Alibaba Cloud Model Studio の API キー。

sk-xxxxxxxxxxxxxxxxxxxxxxxx

操作:ターミナルで以下のコマンドを実行します。サンプル値を実際の値に置き換えてください。

# Set the PolarSearch host and port
# PolarSearch のホストとポートを設定
export POLARSEARCH_HOST_PORT="pc-xxx.polardbsearch.rds.aliyuncs.com:3001"

# Set the PolarSearch administrator password
# PolarSearch の管理者パスワードを設定
export USER_PASSWORD="polarsearch_user:your_password"

# Set your Qwen API key
# Qwen の API キーを設定
export YOUR_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxxxxx"

ステップ 2:メモリコンテナープラグインの有効化

次のコマンドを実行して、ご利用の PolarDB クラスターでメモリコンテナー機能を有効にします。

コマンドライン

curl -XPUT "http://${POLARSEARCH_HOST_PORT}/_cluster/settings" \
--user "${USER_PASSWORD}" \
-H 'Content-Type: application/json' \
-d '{
  "persistent": {
    "plugins.ml_commons.agentic_memory_enabled": true
  }
}'

ダッシュボード

PUT _cluster/settings
{
  "persistent": {
    "plugins.ml_commons.agentic_memory_enabled": true
   }
}

ステップ 3:モデルの登録

ファクト抽出用の LLM とセマンティック処理用の埋め込みモデルを PolarSearch に登録する必要があります。

埋め込みモデルの登録

Alibaba Cloud Model Studio の埋め込みモデルサービスを指すコネクタを作成し、PolarSearch で呼び出し可能なモデルとして登録します。

  1. 信頼できるエンドポイントの設定:モデルの API エンドポイントを信頼リストに追加して、PolarSearch がそれを呼び出せるようにします。

    コマンドライン

    curl -XPUT "http://${POLARSEARCH_HOST_PORT}/_cluster/settings" \
    --user "${USER_PASSWORD}" \
    -H 'Content-Type: application/json' \
    -d '{
      "persistent": {
        "plugins.ml_commons.trusted_connector_endpoints_regex": [
          "^https://dashscope.aliyuncs.com/compatible-mode/v1/.*$"
        ]
      }
    }'
    

    ダッシュボード

    PUT _cluster/settings
    {
      "persistent": {
        "plugins.ml_commons.trusted_connector_endpoints_regex": [
          "^https://dashscope.aliyuncs.com/compatible-mode/v1/.*$"
        ]
      }
    }
  2. モデル コネクタを作成します: text-embedding-v4 モデルに接続するためのパラメーターを設定します。

    説明

    pre_process_function および post_process_function パラメーターは、異なるモデルサービス向けにリクエストおよび応答フォーマットを調整します。この例では、組み込みの openai.embedding フォーマットコンバーターを使用しています。これは、互換モードでの Alibaba Cloud Model Studio のフォーマットと一致するためです。

    コマンドライン

    curl -XPOST "http://${POLARSEARCH_HOST_PORT}/_plugins/_ml/connectors/_create" \
    --user "${USER_PASSWORD}" \
    -H 'Content-Type: application/json' \
    -d '{
      "name": "qwen embedding connector",
      "description": "The connector to qwen embedding model",
      "version": 1,
      "protocol": "http",
      "parameters": {
        "model": "text-embedding-v4",
        "endpoint": "dashscope.aliyuncs.com/compatible-mode"
      },
      "credential": {
          "api_key": "${YOUR_API_KEY}"
      },
      "actions": [
        {
          "action_type": "predict",
          "method": "POST",
          "headers": {
            "Authorization": "Bearer ${credential.api_key}",
            "content-type": "application/json"
          },
          "url": "https://${parameters.endpoint}/v1/embeddings",
          "request_body": "{ \"model\": \"${parameters.model}\", \"input\": ${parameters.input} }",
          "pre_process_function": "connector.pre_process.openai.embedding",
          "post_process_function":"connector.post_process.openai.embedding"
          }
        ]
    }'

    ダッシュボード

    重要

    次のコマンド内の <YOUR_API_KEY> を、実際の Alibaba Cloud Model Studio の API キー に置き換えてください。

    POST _plugins/_ml/connectors/_create
    {
      "name": "qwen embedding connector",
      "description": "The connector to qwen embedding model",
      "version": 1,
      "protocol": "http",
      "parameters": {
        "model": "text-embedding-v4",
        "endpoint": "dashscope.aliyuncs.com/compatible-mode"
      },
      "credential": {
          "api_key": "<YOUR_API_KEY>"
      },
      "actions": [
        {
          "action_type": "predict",
          "method": "POST",
          "headers": {
            "Authorization": "Bearer ${credential.api_key}",
            "content-type": "application/json"
          },
          "url": "https://${parameters.endpoint}/v1/embeddings",
          "request_body": "{ \"model\": \"${parameters.model}\", \"input\": ${parameters.input} }",
          "pre_process_function": "connector.pre_process.openai.embedding",
          "post_process_function":"connector.post_process.openai.embedding"
          }
        ]
    }
  3. コマンドが正常に実行された後、システムはconnector_idを返します。このIDは後で使用するために控えてください。

    {"connector_id": "LBFt6ZsBk04xxx"}
  4. モデルの登録:前のステップで作成したコネクタをモデルとして登録します。

    コマンドライン

    curl -XPOST "http://${POLARSEARCH_HOST_PORT}/_plugins/_ml/models/_register" \
    --user "${USER_PASSWORD}" \
    -H 'Content-Type: application/json' \
    -d '{
      "name": "qwen embedding model",
      "function_name": "remote",
      "description": "Embedding model for memory",
      "connector_id": "LBFt6Zsxxx"
    }'

    ダッシュボード

    POST _plugins/_ml/models/_register
    {
      "name": "qwen embedding model",
      "function_name": "remote",
      "description": "Embedding model for memory",
      "connector_id": "LBFt6ZsBk04xxx"
    }

    コマンドが正常に実行されると、システムは model_id を返します。後で使用するためにこのIDを記録してください。

    # Record the returned model_id
    # 返された model_id を記録
    {"task_id": "LRFx6ZsBk04ixxx","status": "CREATED","model_id": "LhFx6ZsBk04xxx"}
  5. モデルのテスト:次のクエリを実行して LLM と直接チャットし、その応答を表示します。

    コマンドライン

    curl -XPOST "http://${POLARSEARCH_HOST_PORT}/_plugins/_ml/_predict/text_embedding/<model ID from previous step>" \
    --user "${USER_PASSWORD}" \
    -H 'Content-Type: application/json' \
    -d'{
      "text_docs":[ "Bob likes swimming. Context: He expressed his interest in swimming."],
      "return_number": true,
      "target_response": ["sentence_embedding"]
    }'

    ダッシュボード

    POST _plugins/_ml/_predict/text_embedding/<model ID from previous step>
    {
      "text_docs":[ "Bob likes swimming. Context: He expressed his interest in swimming."],
      "return_number": true,
      "target_response": ["sentence_embedding"]
    }

    期待される応答:

    {
      "inference_results": [
        {
          "output": [
            {
              "name": "sentence_embedding",
              "data_type": "FLOAT32",
              "shape": [
                1024
              ],
              "data": [
                0.019752666354179382,
                -0.03468115255236626,
                0.05591931194067001,
                ...
              ]
            }
          ],
          "status_code": 200
        }
      ]
    }

テキスト生成モデル (LLM) の登録

Alibaba Cloud Model Studio で、qwen-plus LLM へのコネクタを作成および登録します。

  1. モデルコネクタの作成:

    コマンドライン

    curl -XPOST "http://${POLARSEARCH_HOST_PORT}/_plugins/_ml/connectors/_create" \
    --user "${USER_PASSWORD}" \
    -H 'Content-Type: application/json' \
    -d '{
      "name": "QWen LLM Connector",
      "description": "The connector to qwen LLM",
      "version": 1,
      "protocol": "http",
      "parameters": {
        "model": "qwen-plus",
        "endpoint": "dashscope.aliyuncs.com/compatible-mode"
      },
      "credential": {
          "api_key": "${YOUR_API_KEY}"
      },
      "actions": [
        {
          "action_type": "predict",
          "method": "POST",
          "headers": {
            "Authorization": "Bearer ${credential.api_key}",
            "content-type": "application/json"
          },
          "url": "https://${parameters.endpoint}/v1/chat/completions",
          "request_body": "{ \"model\":\"${parameters.model}\", \"system\": \"${parameters.system_prompt}\", \"messages\": [ { \"role\": \"system\", \"content\": \"${parameters.system_prompt}\" }, { \"role\": \"user\", \"content\": \"${parameters.user_prompt}\" } ], \"response_format\": { \"type\": \"json_object\" } }"
          }
        ]
    }'

    ダッシュボード

    重要

    <YOUR_API_KEY> を、以下のコマンドで実際の Alibaba Cloud Model Studio の API キー に置き換えます。

    POST /_plugins/_ml/connectors/_create
    {
      "name": "QWen LLM Connector",
      "description": "The connector to qwen LLM",
      "version": 1,
      "protocol": "http",
      "parameters": {
        "model": "qwen-plus",
        "endpoint": "dashscope.aliyuncs.com/compatible-mode"
      },
      "credential": {
          "api_key": "<YOUR_API_KEY>"
      },
      "actions": [
        {
          "action_type": "predict",
          "method": "POST",
          "headers": {
            "Authorization": "Bearer ${credential.api_key}",
            "content-type": "application/json"
          },
          "url": "https://${parameters.endpoint}/v1/chat/completions",
          "request_body": "{ \"model\":\"${parameters.model}\", \"system\": \"${parameters.system_prompt}\", \"messages\": [ { \"role\": \"system\", \"content\": \"${parameters.system_prompt}\" }, { \"role\": \"user\", \"content\": \"${parameters.user_prompt}\" } ], \"response_format\": { \"type\": \"json_object\" } }"
          }
        ]
    }
  2. コマンドが正常に実行されると、システムは connector_id を返します。この ID は後で使用するため、記録しておいてください。

    {"connector_id": "PRGy6ZsBk04xxx"}
  3. モデルの登録:前のステップで作成したコネクタをモデルとして登録します。

    コマンドライン

    curl -XPOST "http://${POLARSEARCH_HOST_PORT}/_plugins/_ml/models/_register" \
    --user "${USER_PASSWORD}" \
    -H 'Content-Type: application/json' \
    -d '{
      "name": "qwen llm model",
      "function_name": "remote",
      "description": "LLM model for memory",
      "connector_id": "PRGy6ZsBk04xxx"
    }'

    ダッシュボード

    POST _plugins/_ml/models/_register
    {
      "name": "qwen llm model",
      "function_name": "remote",
      "description": "LLM model for memory",
      "connector_id": "PRGy6ZsBk04xxx"
    }

    コマンドが正常に実行されると、model_id が返されます。このIDを後で使用するために記録してください。

    # Record the returned model_id
    # 返された model_id を記録
    {"task_id": "PhGy6ZsBk04xxx","status": "CREATED","model_id": "PxGy6ZsBk04xxx"}
  4. モデルのデプロイ:

    コマンドライン

    curl -XPOST "http://${POLARSEARCH_HOST_PORT}/_plugins/_ml/models/<model ID from previous step>/_deploy" \
    --user "${USER_PASSWORD}"

    ダッシュボード

    POST _plugins/_ml/models/<model ID from previous step>/_deploy

    コマンドが正常に実行されると、status の値が COMPLETED の場合、デプロイメントが成功したことを示します。

    {"task_id": "NxGI6ZsBk04xxx","task_type": "DEPLOY_MODEL","status": "COMPLETED"}
  5. モデルのテスト:次のクエリを実行して LLM と直接チャットし、その応答を表示します。

    コマンドライン

    curl -XPOST "http://${POLARSEARCH_HOST_PORT}/_plugins/_ml/models/<model ID from previous step>/_predict" \
    --user "${USER_PASSWORD}" \
    -H 'Content-Type: application/json' \
    -d'{
      "parameters": {
        "system_prompt": "<ROLE>You are a USER PREFERENCE EXTRACTOR, not a chat assistant. Your only job is to output JSON facts. Do not answer questions, make suggestions, ask follow-ups, or perform actions.</ROLE>\n\n<SCOPE>\n• Extract preferences only from USER messages. Assistant messages are context only.\n• Explicit: user states a preference (\"I prefer/like/dislike ...\"; \"always/never/usually ...\"; \"set X to Y\"; \"run X when Y\").\n• Implicit: infer only with strong signals: repeated choices (>=2) or clear habitual language. Do not infer from a single one-off.\n</SCOPE>\n\n<EXTRACT>\n• Specific, actionable, likely long-term preferences (likes/dislikes/choices/settings). Ignore non-preferences.\n</EXTRACT>\n\n<STYLE & RULES>\n• One sentence per preference; merge related details; no duplicates; preserve user wording and numbers; avoid relative time; keep each fact < 350 chars.\n• Format: \"Preference sentence. Context: <why/how>. Categories: cat1,cat2\"\n</STYLE & RULES>\n\n<OUTPUT>\nReturn ONLY one minified JSON object exactly as {\"facts\":[\"Preference sentence. Context: <why/how>. Categories: cat1,cat2\"]}. If none, return {\"facts\":[]}. The first character MUST be '{' and the last MUST be '}'. No preambles, explanations, code fences, XML, or other text.\n</OUTPUT>",
        "user_prompt": "I am Alice, I like travel."
      }
    }'

    ダッシュボード

    POST _plugins/_ml/models/<model ID from previous step>/_predict
    {
      "parameters": {
        "system_prompt": "<ROLE>You are a USER PREFERENCE EXTRACTOR, not a chat assistant. Your only job is to output JSON facts. Do not answer questions, make suggestions, ask follow-ups, or perform actions.</ROLE>\n\n<SCOPE>\n• Extract preferences only from USER messages. Assistant messages are context only.\n• Explicit: user states a preference (\"I prefer/like/dislike ...\"; \"always/never/usually ...\"; \"set X to Y\"; \"run X when Y\").\n• Implicit: infer only with strong signals: repeated choices (>=2) or clear habitual language. Do not infer from a single one-off.\n</SCOPE>\n\n<EXTRACT>\n• Specific, actionable, likely long-term preferences (likes/dislikes/choices/settings). Ignore non-preferences.\n</EXTRACT>\n\n<STYLE & RULES>\n• One sentence per preference; merge related details; no duplicates; preserve user wording and numbers; avoid relative time; keep each fact < 350 chars.\n• Format: \"Preference sentence. Context: <why/how>. Categories: cat1,cat2\"\n</STYLE & RULES>\n\n<OUTPUT>\nReturn ONLY one minified JSON object exactly as {\"facts\":[\"Preference sentence. Context: <why/how>. Categories: cat1,cat2\"]}. If none, return {\"facts\":[]}. The first character MUST be '{' and the last MUST be '}'. No preambles, explanations, code fences, XML, or other text.\n</OUTPUT>",
        "user_prompt": "I am Alice, I like travel."
      }
    }

    期待される応答:

    {
      "inference_results": [
        {
          "output": [
            {
              "name": "response",
              "dataAsMap": {
                "choices": [
                  {
                    "message": {
                      "role": "assistant",
                      "content": """{"facts":["I like travel. Context: Stated preference. Categories: interest"]}"""
                    },
                    "finish_reason": "stop",
                    "index": 0,
                    "logprobs": null
                  }
                ],
                "object": "chat.completion",
                "usage": {
                  "prompt_tokens": 325,
                  "completion_tokens": 17,
                  "total_tokens": 342,
                  "prompt_tokens_details": {
                    "cached_tokens": 0
                  }
                },
                "created": 1769152651,
                "system_fingerprint": null,
                "model": "qwen-plus",
                "id": "chatcmpl-50c6bfc9-xxx-xxx-xxx-1a39cfe080f5"
              }
            }
          ],
          "status_code": 200
        }
      ]
    }

ステップ 4:メモリコンテナーの作成

  1. メモリコンテナーインスタンスを作成し、そのモデル、ストレージポリシー、および自動化動作を設定します。名前が agentic memory test のコンテナーを作成するには、次のコマンドを実行します。

    コマンドライン

    curl -XPOST "http://${POLARSEARCH_HOST_PORT}/_plugins/_ml/memory_containers/_create" \
    --user "${USER_PASSWORD}" \
    -H 'Content-Type: application/json' \
    -d'{
      "name": "my agentic memory test",
      "description": "Store conversations with semantic search and summarization",
      "configuration": {
        "embedding_model_type": "TEXT_EMBEDDING",
        "embedding_model_id": "<embedding model ID from previous step>",
        "embedding_dimension": 1024,
        "llm_id": "<LLM model ID from previous step>",
        "index_prefix": "mem_test",
        "index_settings": {
          "short_term_memory_index": {
            "index": {
              "number_of_shards": "2",
              "number_of_replicas": "2"
            }
          },
          "long_term_memory_index": {
            "index": {
              "number_of_shards": "2",
              "number_of_replicas": "2"
            }
          },
          "long_term_memory_history_index": {
            "index": {
              "number_of_shards": "2",
              "number_of_replicas": "2"
            }
          }
        },
        "strategies": [
          {
            "type": "SEMANTIC",
            "namespace": ["user_id"],
            "configuration": {
              "llm_result_path": "$.choices[0].message.content"
            }
          },
          {
            "type": "USER_PREFERENCE",
            "namespace": ["user_id"],
            "configuration": {
              "llm_result_path": "$.choices[0].message.content"
            }
          },
          {
            "type": "SUMMARY",
            "namespace": ["agent_id"],
            "configuration": {
              "llm_result_path": "$.choices[0].message.content"
            }
          }
        ],
        "parameters": {
          "llm_result_path": "$.choices[0].message.content"
        }
      }
    }'

    ダッシュボード

    POST _plugins/_ml/memory_containers/_create
    {
      "name": "my agentic memory test",
      "description": "Store conversations with semantic search and summarization",
      "configuration": {
        "embedding_model_type": "TEXT_EMBEDDING",
        "embedding_model_id": "<embedding model ID from previous step>",
        "embedding_dimension": 1024,
        "llm_id": "<LLM model ID from previous step>",
        "index_prefix": "mem_test",
        "index_settings": {
          "short_term_memory_index": {
            "index": {
              "number_of_shards": "2",
              "number_of_replicas": "2"
            }
          },
          "long_term_memory_index": {
            "index": {
              "number_of_shards": "2",
              "number_of_replicas": "2"
            }
          },
          "long_term_memory_history_index": {
            "index": {
              "number_of_shards": "2",
              "number_of_replicas": "2"
            }
          }
        },
        "strategies": [
          {
            "type": "SEMANTIC",
            "namespace": ["user_id"],
            "configuration": {
              "llm_result_path": "$.choices[0].message.content"
            }
          },
          {
            "type": "USER_PREFERENCE",
            "namespace": ["user_id"],
            "configuration": {
              "llm_result_path": "$.choices[0].message.content"
            }
          },
          {
            "type": "SUMMARY",
            "namespace": ["agent_id"],
            "configuration": {
              "llm_result_path": "$.choices[0].message.content"
            }
          }
        ],
        "parameters": {
          "llm_result_path": "$.choices[0].message.content"
        }
      }
    }

    パラメーターの説明

    • index_prefix: このコンテナーによって作成される内部インデックス(例:短期メモリインデックスおよび長期メモリインデックス)の共通プレフィックス。

    • index_settings: 各種メモリインデックスのシャード数とレプリカ数を設定して、高可用性を確保します。

    • strategies: 自動化ルールを定義します。この例では、SEMANTIC ストラテジーを定義しており、これは user_id 名前空間内の会話からセマンティクス情報を自動的に抽出します。

    • llm_result_path: JSONPath 構文を使用して、LLM の JSON 応答からコアコンテンツを抽出する方法を指定します。パス $.choices[0].message.content は、互換モードでの Alibaba Cloud Model Studio の応答構造と一致します。

  2. コマンドが正常に実行されると、システムは memory_container_id を返します。このIDを今後の使用のために記録してください。

    # Record the returned model_id
    # 返された model_id を記録
    {"memory_container_id": "QRHF6ZsBk04xxx","status": "created"}

    メモリコンテナーを作成すると、システムは次のインデックスとパイプラインを作成します:

    .plugins-ml-am-mem_test-memory-long-term
    .plugins-ml-am-mem_test-memory-working
    .plugins-ml-am-mem_test-memory-history
    .plugins-ml-am-mem_test-memory-long-term-embedding" : {
        "description" : "Agentic Memory Text embedding pipeline",
        "processors" : [
          {
            "text_embedding" : {
              "model_id" : "QRHF6ZsBk04xxx",
              "field_map" : {
                "memory" : "memory_embedding"
              }
            }
          }
        ]
    }

ステップ 5:メモリの保存と検証

メモリコンテナーに会話を追加し、それが短期および長期メモリに正しく保存されていることを確認します。

  1. メモリの保存: Bob という名前のユーザーに関する会話を含む POST リクエストをコンテナーに送信します。 "infer": true パラメーターは、LLM を呼び出してファクトを抽出するように以前に設定した SEMANTIC ストラテジーをトリガーします。

    コマンドライン

    curl -XPOST "http://${POLARSEARCH_HOST_PORT}/_plugins/_ml/memory_containers/<memory container ID from previous step>/memories" \
    --user "${USER_PASSWORD}" \
    -H 'Content-Type: application/json' \
    -d'{
       "messages": [
          {
          "role": "user",
          "content":  [
            {
              "text": "I am Bob, I really like swimming.",
              "type": "text"
            }
          ]
        },
        {
          "role": "assistant",
          "content":  [
            {
              "text": "Cool, nice. Hope you enjoy your life.",
              "type": "text"
            }
          ]
        }
        ],
      "namespace": {
        "user_id": "bob"
      },
      "tags": {
        "topic": "personal info"
      },
      "infer": true,
      "memory_type": "conversation"
    }'

    ダッシュボード

    POST _plugins/_ml/memory_containers/<memory_container_id>/memories
    {
       "messages": [
          {
          "role": "user",
          "content":  [
            {
              "text": "I am Bob, I really like swimming.",
              "type": "text"
            }
          ]
        },
        {
          "role": "assistant",
          "content":  [
            {
              "text": "Cool, nice. Hope you enjoy your life.",
              "type": "text"
            }
          ]
        }
        ],
      "namespace": {
        "user_id": "bob"
      },
      "tags": {
        "topic": "personal info"
      },
      "infer": true,
      "memory_type": "conversation"
    }
  2. 短期メモリの検証:短期メモリインデックスを直接クエリして、保存された元の会話を表示します。

    コマンドライン

    curl -XGET "http://${POLARSEARCH_HOST_PORT}/.plugins-ml-am-<index_prefix from previous step>-memory-working/_search?pretty" \
    --user "${USER_PASSWORD}" \

    ダッシュボード

    GET .plugins-ml-am-<index_prefix from previous step>-memory-working/_search?pretty

    期待される出力 (一部):

    {
      "_source" : {
        "memory_container_id": "QRHF6ZsBk04xxx",
        "payload_type" : "conversational",
        "messages" : [
          { "role" : "user", "content": [{ "text": "I am Bob, I really like swimming.","type": "text"}] },
          { "role" : "assistant", "content": [{ "text": "Cool, nice. Hope you enjoy your life.","type": "text"}] }
        ],
        "namespace" : { "user_id" : "bob" }
      }
    }
  3. 長期記憶の検証: 長期記憶インデックスにクエリを実行して、LLM が「Bob likes swimming」 というコアな事実を正常に抽出し、埋め込みモデルが対応する memory_embedding ベクターを生成したことを確認します。

    コマンドライン

    curl -XGET "http://${POLARSEARCH_HOST_PORT}/.plugins-ml-am-<index_prefix from previous step>-memory-long-term/_search?pretty" \
    --user "${USER_PASSWORD}" \

    ダッシュボード

    GET .plugins-ml-am-<index_prefix from previous step>-memory-long-term/_search?pretty

    期待される出力 (一部):

    {
      "_source" : {
        "created_time": 1769155210918,
        "memory": "Bob likes swimming.",
        "memory_container_id": "QRHF6ZsBk04xxx",
        "tags": {
          "topic": "personal info"
        },
        "last_updated_time": 1769155210918,
        "memory_embedding" : [ 0.0195..., -0.0387..., ... ],
        "namespace" : { "user_id" : "bob" }
      }
    }
  4. メモリ履歴の表示: memory-history インデックスには、2 つの ADD レコードとそのタイムスタンプが含まれています。

    コマンドライン

    curl -XGET "http://${POLARSEARCH_HOST_PORT}/.plugins-ml-am-<index_prefix from previous step>-memory-history/_search?pretty" \
    --user "${USER_PASSWORD}" \

    ダッシュボード

    GET .plugins-ml-am-<index_prefix from previous step>-memory-history/_search?pretty

    期待される出力 (一部):

    {
      "_source": {
        "created_time": 1769155211164,
        "memory_id": "TBHe6ZsBk04xxx",
        "namespace_size": 1,
        "namespace": {
          "user_id": "bob"
        },
        "action": "ADD",
        "memory_container_id": "QRHF6ZsBk04xxx",
        "after": {
          "memory": "Bob likes swimming."
        },
        "tags": {
          "topic": "personal info"
        }
      }
    }

これで、長期的な取得をサポートする AI エージェントのメモリを正常に保存できました。

ステップ 6:(オプション) メモリの更新

メモリコンテナーは長期メモリを抽出して更新できます。

  1. まず、メモリを保存します:

    コマンドライン

    curl -XPOST "http://${POLARSEARCH_HOST_PORT}/_plugins/_ml/memory_containers/<memory container ID from previous step>/memories" \
    --user "${USER_PASSWORD}" \
    -H 'Content-Type: application/json' \
    -d'{
      "messages": [
        {
          "role": "user",
          "content": [
            {
              "text": "My name is NameA. I am from AreaA. I currently live in AreaB.",
              "type": "text"
            }
          ]
        },
        {
          "role": "assistant",
          "content": [
            {
              "text": "Hello, NameA! Nice to meet you.",
              "type": "text"
            }
          ]
        }
      ],
      "namespace": {
        "user_id": "NameA"
      },
      "tags": {
        "topic": "personal info"
      },
      "infer": true,
      "memory_type": "conversation"
    }'

    ダッシュボード

    POST _plugins/_ml/memory_containers/<memory_container_id>/memories
    {
      "messages": [
        {
          "role": "user",
          "content": [
            {
              "text": "My name is NameA. I am from AreaA. I currently live in AreaB.",
              "type": "text"
            }
          ]
        },
        {
          "role": "assistant",
          "content": [
            {
              "text": "Hello, NameA! Nice to meet you.",
              "type": "text"
            }
          ]
        }
      ],
      "namespace": {
        "user_id": "NameA"
      },
      "tags": {
        "topic": "personal info"
      },
      "infer": true,
      "memory_type": "conversation"
    }
  2. 長期メモリインデックスを検索します:

    コマンドライン

    curl -XGET "http://${POLARSEARCH_HOST_PORT}/.plugins-ml-am-<index_prefix from previous step>-memory-long-term/_search" \
    --user "${USER_PASSWORD}" \
    -H 'Content-Type: application/json' \
    -d'{
      "_source": {
        "excludes": ["memory_embedding"]
      },
      "query": {
        "match_all": {}
      }
    }'

    ダッシュボード

    GET .plugins-ml-am-<index_prefix from previous step>-memory-long-term/_search
    {
      "_source": {
        "excludes": ["memory_embedding"]
      },
      "query": {
        "match_all": {}
      }
    }

    期待される出力 (一部):

    {
      "hits": [
          {
            "_source": {
              "created_time": 1769156096335,
              "memory": "NameA is from AreaA.",
              "last_updated_time": 1769156096335,
              "namespace": {
                "user_id": "NameA"
              },
              "memory_container_id": "QRHF6ZsBk04xxx",
              "tags": {
                "topic": "personal info"
              }
            }
          },
          {
            "_source": {
              "created_time": 1769156096335,
              "memory": "NameA currently resides in AreaB.",
              "last_updated_time": 1769156096335,
              "namespace": {
                "user_id": "NameA"
              },
              "memory_container_id": "QRHF6ZsBk04xxx",
              "tags": {
                "topic": "personal info"
              }
            }
          }
       ]
    }
  3. 前のメモリと競合する別のメモリを保存します:

    コマンドライン

    curl -XPOST "http://${POLARSEARCH_HOST_PORT}/_plugins/_ml/memory_containers/<memory container ID from previous step>/memories" \
    --user "${USER_PASSWORD}" \
    -H 'Content-Type: application/json' \
    -d'{
      "messages": [
        {
          "role": "user",
          "content": [
            {
              "text": "My name is NameA. I currently live in AreaC.",
              "type": "text"
            }
          ]
        },
        {
          "role": "assistant",
          "content": [
            {
              "text": "Hello, NameA! Nice to meet you.",
              "type": "text"
            }
          ]
        }
      ],
      "namespace": {
        "user_id": "NameA"
      },
      "tags": {
        "topic": "personal info"
      },
      "infer": true,
      "memory_type": "conversation"
    }'

    ダッシュボード

    POST _plugins/_ml/memory_containers/<memory_container_id>/memories
    {
      "messages": [
        {
          "role": "user",
          "content": [
            {
              "text": "My name is NameA. I currently live in AreaC.",
              "type": "text"
            }
          ]
        },
        {
          "role": "assistant",
          "content": [
            {
              "text": "Hello, NameA! Nice to meet you.",
              "type": "text"
            }
          ]
        }
      ],
      "namespace": {
        "user_id": "NameA"
      },
      "tags": {
        "topic": "personal info"
      },
      "infer": true,
      "memory_type": "conversation"
    }
  4. 長期メモリインデックスを再度検索して、更新されたコンテンツを表示します (一部):

    {
      "hits": [
          {
            "_source": {
              "created_time": 1769156096335,
              "memory": "NameA resides in AreaC.",
              "last_updated_time": 1769156493970,
              "namespace": {
                "user_id": "NameA"
              },
              "memory_container_id": "QRHF6ZsBk04xxx",
              "tags": {
                "topic": "personal info"
              }
            }
          }
       ]
    }

ユースケース:旅行記憶エージェントの構築

このセクションでは、メモリコンテナー API を使用して、ユーザーの旅行プリファレンスを記憶し、パーソナライズされた推奨事項を提供する Python エージェントを構築する方法を示します。

事前準備

コードを実行する前に、必要な Python ライブラリをインストールする必要があります。以下の依存関係を requirements.txt ファイルに追加します。

requests
openai

pip install -r requirements.txt を実行してインストールします。

コアコードの説明

以下は、メモリコンテナーと対話する主要な関数です:

  • opensearch_request(): PolarSearch API への HTTP リクエストをラップし、認証とエラーを処理します。

  • add_memory() 関数は、メモリ コンテナの memories API を呼び出して、新しい会話データを保存します。

  • search_memories(): _search API を呼び出して、長期および短期メモリからユーザー クエリに関連する情報を取得します。

  • generate_response_with_memories(): 取得されたメモリをコンテキストとして、ユーザーの現在の質問とともに LLM に送信し、よりパーソナライズされた応答を生成します。

完全なサンプルコード

以下は、安全で論理的に最適化された Python スクリプトです。次のように保存します trip_agent.py し、指示に従って環境変数を設定します。

クリックして詳細なコード例を展開

#!/usr/bin/env python3
"""
Trip Memory Agent - A simplified memory agent that tracks user information
and manages memories using OpenSearch as the backend.
旅行記憶エージェント - OpenSearch をバックエンドとして使用してユーザー情報を追跡し、
メモリを管理する簡略化された記憶エージェント。
"""
import os
import json
import requests
from requests.auth import HTTPBasicAuth
import urllib3
from typing import Optional, List, Dict, Any

# Disable HTTPS warnings for local development
# ローカル開発用に HTTPS 警告を無効化
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# Import OpenAI library for LLM operations
# LLM 操作のために OpenAI ライブラリをインポート
import openai

# --- Configuration from Environment Variables ---
# --- 環境変数からの設定 ---
OPENSEARCH_URL = os.getenv('OPENSEARCH_URL')
OPENSEARCH_USERNAME = os.getenv('OPENSEARCH_USERNAME', 'admin')
OPENSEARCH_PASSWORD = os.getenv('OPENSEARCH_PASSWORD')
CONTAINER_ID = os.getenv('MEM_CONTAINER_ID')
QWEN_API_KEY = os.getenv('QWEN_API_KEY')
QWEN_BASE_URL = os.getenv('QWEN_BASE_URL', 'https://dashscope.aliyuncs.com/compatible-mode/v1')
QWEN_MODEL_NAME = os.getenv('QWEN_MODEL_NAME', 'qwen-max')

# Global variable to store current user ID
# 現在のユーザー ID を保存するグローバル変数
current_user_id = None

# Global OpenAI client instance
# グローバルな OpenAI クライアントインスタンス
llm_client = None


# --- OpenSearch Helper ---
# --- OpenSearch ヘルパー ---
def opensearch_request(method: str, endpoint: str, json_data: Optional[Dict] = None) -> requests.Response:
    """Make an HTTP request to the OpenSearch API."""
    """OpenSearch API に HTTP リクエストを送信します。"""
    if not all([OPENSEARCH_URL, OPENSEARCH_USERNAME, OPENSEARCH_PASSWORD, CONTAINER_ID]):
        raise ValueError("OpenSearch environment variables are not fully configured.")

    url = f"{OPENSEARCH_URL}{endpoint}"
    auth = HTTPBasicAuth(OPENSEARCH_USERNAME, OPENSEARCH_PASSWORD)
    try:
        response = getattr(requests, method.lower())(url, json=json_data, auth=auth, verify=False, timeout=20)
        response.raise_for_status()
        return response
    except requests.exceptions.RequestException as e:
        print(f"Error communicating with OpenSearch: {e}")
        # Return a mock response to prevent crashes, or handle more gracefully
        # For this example, we re-raise to make the error obvious
        # クラッシュを防ぐためにモック応答を返すか、より適切に処理します
        # この例では、エラーを明確にするために再発生させます
        raise e


# --- User Management ---
# --- ユーザー管理 ---
def get_user_id():
    """Get user ID from global variable or prompt the user."""
    """グローバル変数からユーザー ID を取得するか、ユーザーにプロンプトを表示します。"""
    global current_user_id
    if not current_user_id:
        print("=" * 50)
        print("Welcome to the Trip Memory Agent!")
        print("=" * 50)
        print("Hello! I don't know you yet. Please tell me your name:")
        user_input = input("> ")
        current_user_id = user_input.strip()
        print(f"Nice to meet you, {current_user_id}!")
    return current_user_id


# --- LLM-Powered Helper Functions ---
# --- LLM を利用したヘルパー関数 ---
def is_trip_plan_request(query: str) -> bool:
    """Determine if a user's query is a request for a trip plan using an LLM."""
    """LLM を使用して、ユーザーのクエリが旅行計画のリクエストであるかどうかを判断します。"""
    if not query.strip():
        return False
    try:
        prompt = f"""Please determine if the following user input is a request to plan a trip.
Answer only "YES" or "NO". Do not explain.
User input: {query}"""
        response = llm_client.chat.completions.create(
            model=QWEN_MODEL_NAME,
            messages=[{"role": "user", "content": prompt}],
            max_tokens=10,
            temperature=0.0
        )
        result = response.choices[0].message.content.strip().upper()
        return result == "YES"
    except Exception as e:
        print(f"Error detecting trip plan request with LLM: {e}. Falling back to keywords.")
        keywords = ['plan a trip', 'trip plan', 'plan my travel', 'travel plan']
        return any(keyword in query.lower() for keyword in keywords)


def extract_keywords(query: str) -> List[str]:
    """Extract keywords from a query using an LLM for better search results."""
    """より良い検索結果を得るために、LLM を使用してクエリからキーワードを抽出します。"""
    try:
        prompt = f"""Extract the most important keywords from the following query for search purposes.
Focus on locations, activities, and specific preferences.
Return only a JSON array of strings (keywords).
Query: {query}"""
        response = llm_client.chat.completions.create(
            model=QWEN_MODEL_NAME,
            messages=[{"role": "user", "content": prompt}],
            max_tokens=100,
            temperature=0.0
        )
        extracted_content = response.choices[0].message.content.strip()
        return json.loads(extracted_content)
    except (Exception, json.JSONDecodeError) as e:
        print(f"Error extracting keywords with LLM: {e}. Falling back to splitting query.")
        return query.split()


def generate_response_with_memories(query: str, search_results: List[Dict]) -> str:
    """Generate a response using an LLM with memory context."""
    """メモリコンテキストを使用して LLM で応答を生成します。"""
    if not search_results:
        return "I couldn't find any relevant memories for your query."

    context = "Here is relevant information I found in your memories:\n"
    for result in search_results:
        context += f"- {result['content']}\n"

    prompt = f"""You are a helpful trip planning assistant. Based on the user's past memories and their current request, generate a helpful and personalized response.

{context}
User's current request: {query}

Your response:"""
    try:
        response = llm_client.chat.completions.create(
            model=QWEN_MODEL_NAME,
            messages=[{"role": "system", "content": prompt}],
            max_tokens=1024,
            temperature=0.7
        )
        return response.choices[0].message.content.strip()
    except Exception as e:
        print(f"Error generating response with LLM: {e}")
        return "I found some memories, but I encountered an error while trying to generate a response."


# --- Core Memory Management Functions (Our "Tools") ---
# --- コアメモリ管理関数 (我々の「ツール」) ---

def add_memory(message: str) -> Dict[str, Any]:
    """Add a new piece of information (a memory) for the current user."""
    """現在のユーザーに新しい情報 (メモリ) を追加します。"""
    global current_user_id
    payload = {
        "messages": [{"role": "user", "content": [{"text": message, "type": "text"}]}],
        "infer": True,
        "memory_type": "conversation",
        "namespace": {"user_id": current_user_id}
    }
    response = opensearch_request("POST", f"/_plugins/_ml/memory_containers/{CONTAINER_ID}/memories", payload)
    return response.json()


def search_memories(query: str) -> Dict[str, Any]:
    """Search the user's memories based on a query."""
    """クエリに基づいてユーザーのメモリを検索します。"""
    global current_user_id
    keywords = extract_keywords(query)

    search_query = {
        "query": {
            "bool": {
                "must": [{"term": {"namespace.user_id": current_user_id}}],
                "should": [
                    {"multi_match": {"query": " ".join(keywords), "fields": ["memory", "messages.content.text"]}}],
                "minimum_should_match": 1 if keywords else 0
            }
        },
        "sort": [{"created_time": {"order": "desc"}}],
        "size": 10  # Limit number of results # 結果の数を制限
    }

    all_hits = []
    for index_suffix in ["long-term", "working"]:
        try:
            response = opensearch_request("POST",
                                          f"/_plugins/_ml/memory_containers/{CONTAINER_ID}/memories/{index_suffix}/_search",
                                          search_query)
            result = response.json()
            if "hits" in result and "hits" in result["hits"]:
                all_hits.extend(result["hits"]["hits"])
        except Exception as e:
            print(f"Error searching {index_suffix} memory: {e}")

    all_hits.sort(key=lambda x: x.get('_source', {}).get('created_time', ''), reverse=True)

    formatted_results = []
    for hit in all_hits:
        source = hit.get('_source', {})
        content = source.get('memory', '')
        if not content and 'messages' in source:
            content = " ".join(
                item['text'] for msg in source['messages'] if 'content' in msg
                for item in msg['content'] if isinstance(item, dict) and 'text' in item
            )

        index_type = "long-term" if 'long-term' in hit.get('_index', '') else "working"
        formatted_results.append({
            "memory_id": hit.get('_id'),
            "index_type": index_type,
            "timestamp": source.get('created_time'),
            "content": content.strip()
        })
    return {"status": "success", "results": formatted_results, "total_found": len(formatted_results)}


def find_and_update_memory(query: str, new_text: str) -> Dict[str, Any]:
    """Find a memory based on a query and update its content."""
    """クエリに基づいてメモリを検索し、その内容を更新します。"""
    search_result = search_memories(query)
    if not search_result["results"]:
        return {"status": "error", "message": "No matching memory found to update."}

    # Update the most recent/relevant memory
    # 最新/最も関連性の高いメモリを更新
    memory_to_update = search_result["results"][0]
    memory_id = memory_to_update["memory_id"]
    index_type = memory_to_update["index_type"]

    payload = {"memory": new_text}
    response = opensearch_request("PUT",
                                  f"/_plugins/_ml/memory_containers/{CONTAINER_ID}/memories/{index_type}/{memory_id}",
                                  payload)
    return {"status": "success", "updated_memory_id": memory_id, "details": response.json()}


def find_and_delete_memory(query: str) -> Dict[str, Any]:
    """Find a memory based on a query and delete it."""
    """クエリに基づいてメモリを検索し、削除します。"""
    search_result = search_memories(query)
    if not search_result["results"]:
        return {"status": "error", "message": "No matching memory found to delete."}

    # Delete the most recent/relevant memory
    # 最新/最も関連性の高いメモリを削除
    memory_to_delete = search_result["results"][0]
    memory_id = memory_to_delete["memory_id"]
    index_type = memory_to_delete["index_type"]

    response = opensearch_request("DELETE",
                                  f"/_plugins/_ml/memory_containers/{CONTAINER_ID}/memories/{index_type}/{memory_id}")
    return {"status": "success", "deleted_memory_id": memory_id, "details": response.json()}


# --- Agent Logic ---
# --- エージェントロジック ---

def main():
    """Main function to run the trip memory agent without 'strands'."""
    """'strands' なしで旅行記憶エージェントを実行するメイン関数。"""
    global llm_client

    # --- Initialization ---
    # --- 初期化 ---
    if not all([QWEN_API_KEY, QWEN_BASE_URL, QWEN_MODEL_NAME]):
        print("Error: Qwen LLM environment variables are not configured.")
        return

    llm_client = openai.OpenAI(api_key=QWEN_API_KEY, base_url=QWEN_BASE_URL)
    get_user_id()

    # --- Tool Definitions for the LLM ---
    # --- LLM のためのツール定義 ---
    tools = [
        {
            "type": "function",
            "function": {
                "name": "add_memory",
                "description": "Adds a new piece of information or a memory about the user. Use this to remember user preferences, facts, or past events.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "message": {
                            "type": "string",
                            "description": "The information or content of the memory to be saved. e.g., 'I love to visit historical museums.'"
                        }
                    },
                    "required": ["message"]
                }
            }
        },
        {
            "type": "function",
            "function": {
                "name": "search_memories",
                "description": "Searches for and retrieves existing memories about the user. Use this when the user asks what you know about them, asks for past information, or wants a trip plan based on their preferences.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "query": {
                            "type": "string",
                            "description": "The search query to find relevant memories. e.g., 'my preferences', 'what do you know about me', 'trip to Paris'"
                        }
                    },
                    "required": ["query"]
                }
            }
        },
        {
            "type": "function",
            "function": {
                "name": "find_and_update_memory",
                "description": "Finds a specific memory using a search query and updates its content with new text.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "query": {"type": "string",
                                  "description": "A query to identify the memory to update. e.g., 'my favorite food'"},
                        "new_text": {"type": "string",
                                     "description": "The new content for the memory. e.g., 'My favorite food is now ramen.'"}
                    },
                    "required": ["query", "new_text"]
                }
            }
        },
        {
            "type": "function",
            "function": {
                "name": "find_and_delete_memory",
                "description": "Finds a specific memory using a search query and deletes it.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "query": {"type": "string",
                                  "description": "A query to identify the memory to delete. e.g., 'my old address'"}
                    },
                    "required": ["query"]
                }
            }
        }
    ]

    # Map tool names to actual Python functions
    # ツール名を実際の Python 関数にマッピング
    available_tools = {
        "add_memory": add_memory,
        "search_memories": search_memories,
        "find_and_update_memory": find_and_update_memory,
        "find_and_delete_memory": find_and_delete_memory,
    }

    system_prompt = f"""You are a trip assistant for a user named {current_user_id}. Your job is to help them by managing their memories and planning trips.
You have access to the following tools: `add_memory`, `search_memories`, `update_memory`, and `delete_memory`.
- When a user provides new information about themselves, use `add_memory`.
- When a user asks what you know about them, asks for their preferences, or wants a trip plan, use `search_memories` to get context first.
- When a user wants to change a piece of information, use `find_and_update_memory`.
- When a user wants to forget something, use `find_and_delete_memory`.
- Always respond to the user in a friendly and conversational manner. After a tool is used, explain what you did in simple terms."""

    messages = [{"role": "system", "content": system_prompt}]

    print("\nTrip Memory Agent is ready!")
    print(f"Hello {current_user_id}! I can remember things for you, search your memories, and help plan trips.")

    # --- Main Interaction Loop ---
    # --- メイン対話ループ ---
    while True:
        try:
            user_input = input(f"\n{current_user_id}> ")
            if user_input.lower() in ['quit', 'exit', 'bye']:
                print("Goodbye!")
                break

            messages.append({"role": "user", "content": user_input})

            # Special fast-path for trip planning
            # 旅行計画のための特別な高速パス
            if is_trip_plan_request(user_input):
                print("This looks like a trip plan request. Searching memories for context...")
                search_result = search_memories(user_input)
                if search_result.get("total_found", 0) > 0:
                    print("Found relevant memories! Generating a personalized plan...")
                    response = generate_response_with_memories(user_input, search_result["results"])
                    print(f"\nAgent: {response}")
                else:
                    print("I couldn't find any relevant memories to help with that plan. Let's start from scratch!")
                    # Let the general agent handle it now
                    # 一般的なエージェントに処理させる
                    pass
                messages.pop()  # Remove user input to avoid double processing # 二重処理を避けるためにユーザー入力を削除
                continue

            print("Thinking...")
            # --- First LLM Call: Decide whether to use a tool ---
            # --- 最初の LLM 呼び出し:ツールを使用するかどうかを決定 ---
            response = llm_client.chat.completions.create(
                model=QWEN_MODEL_NAME,
                messages=messages,
                tools=tools,
                tool_choice="auto"
            )
            response_message = response.choices[0].message

            # Check if the model wants to call a tool
            # モデルがツールを呼び出したいかどうかを確認
            if response_message.tool_calls:
                messages.append(response_message)  # Append the model's decision # モデルの決定を追加

                # --- Execute Tool Calls ---
                # --- ツール呼び出しの実行 ---
                for tool_call in response_message.tool_calls:
                    function_name = tool_call.function.name
                    function_to_call = available_tools.get(function_name)

                    if not function_to_call:
                        print(f"Error: LLM tried to call an unknown function '{function_name}'")
                        continue

                    try:
                        function_args = json.loads(tool_call.function.arguments)
                        print(f"Calling tool: `{function_name}` with args: {function_args}")

                        function_response = function_to_call(**function_args)

                        # Append the tool's result to the message history
                        # ツールの結果をメッセージ履歴に追加
                        messages.append({
                            "tool_call_id": tool_call.id,
                            "role": "tool",
                            "name": function_name,
                            "content": json.dumps(function_response)
                        })
                    except Exception as e:
                        print(f"Error executing tool '{function_name}': {e}")
                        messages.append({
                            "tool_call_id": tool_call.id,
                            "role": "tool",
                            "name": function_name,
                            "content": f'{{"status": "error", "message": "{str(e)}"}}'
                        })

                # --- Second LLM Call: Generate a natural language response ---
                # --- 2回目の LLM 呼び出し:自然言語の応答を生成 ---
                print("Summarizing tool results...")
                second_response = llm_client.chat.completions.create(
                    model=QWEN_MODEL_NAME,
                    messages=messages
                )
                final_response = second_response.choices[0].message.content
                print(f"\nAgent: {final_response}")
                messages.append({"role": "assistant", "content": final_response})

            else:
                # The model decided to just chat
                # モデルはチャットすることを選択
                final_response = response_message.content
                print(f"\nAgent: {final_response}")
                messages.append({"role": "assistant", "content": final_response})

        except Exception as e:
            print(f"\nAn unexpected error occurred: {e}")
            # Reset messages on critical error to prevent bad state
            # 深刻なエラー時にメッセージをリセットして不正な状態を防ぐ
            messages = [{"role": "system", "content": system_prompt}]


if __name__ == "__main__":
    # Before running, set your environment variables:
    # 実行する前に、環境変数を設定してください:
    # export OPENSEARCH_URL="https://..."
    # export OPENSEARCH_USERNAME="admin"
    # export OPENSEARCH_PASSWORD="your_password"
    # export MEM_CONTAINER_ID="your_container_id"
    # export QWEN_API_KEY="your_api_key"
    main()

実行と対話

  1. 環境変数の設定:ターミナルで、スクリプトに必要な環境変数を設定します。

  2. スクリプトを実行します: python trip_agent.py

  3. 対話例:

    クリックして詳細な対話例を展開

    ==================================================
    Welcome to the Trip Memory Agent!
    旅行記憶エージェントへようこそ!
    ==================================================
    Hello! I don't know you yet. Please tell me your name:
    こんにちは!まだあなたのことを知りません。お名前を教えてください:
    > ABC
    Nice to meet you, ABC!
    はじめまして、ABC さん!
    
    Trip Memory Agent is ready!
    旅行記憶エージェントの準備ができました!
    Hello ABC! I can remember things for you, search your memories, and help plan trips.
    こんにちは、ABC さん!私はあなたのための情報を記憶したり、メモリを検索したり、旅行の計画を手伝ったりできます。
    
    ABC> i like hiking and travelling
    ハイキングと旅行が好きです
    Thinking...
    考え中...
    Calling tool: `add_memory` with args: {'message': 'i like hiking and travelling'}
    ツール呼び出し:`add_memory`、引数:{'message': 'i like hiking and travelling'}
    Summarizing tool results...
    ツール結果を要約中...
    
    Agent: Great to know that you enjoy hiking and traveling, ABC! I've added this information to your profile. Whenever you're looking for trip ideas, I'll make sure to suggest some amazing hiking spots and travel destinations for you. 
    エージェント:ABC さん、ハイキングと旅行がお好きなんですね!その情報をあなたのプロファイルに追加しました。旅行のアイデアをお探しの際には、素晴らしいハイキングスポットや旅行先を提案させていただきます。
    
    ABC> I like listen to music
    音楽を聴くのが好きです
    Thinking...
    考え中...
    Calling tool: `add_memory` with args: {'message': 'I like listen to music'}
    ツール呼び出し:`add_memory`、引数:{'message': 'I like listen to music'}
    Summarizing tool results...
    ツール結果を要約中...
    
    Agent: Awesome, ABC! I've noted that you like listening to music. Whether it's for your travels or just everyday life, I can help you find some great playlists and music recommendations. Let me know if you have any favorite genres or artists!
    エージェント:素晴らしいですね、ABC さん!音楽を聴くのがお好きだと記録しました。旅行中であろうと日常生活であろうと、素晴らしいプレイリストや音楽のおすすめを見つけるお手伝いができます。お気に入りのジャンルやアーティストがいれば教えてください!
    
    ABC> what do you know about me
    私のこと、何か知っていますか
    Thinking...
    考え中...
    Calling tool: `search_memories` with args: {'query': 'what do you know about me'}
    ツール呼び出し:`search_memories`、引数:{'query': 'what do you know about me'}
    Summarizing tool results...
    ツール結果を要約中...
    
    Agent: I know a few things about you, ABC! Here’s what I have:
    
    1. You like hiking and traveling. These are some of your favorite activities, and you enjoy exploring new places and being outdoors.
    2. You also enjoy listening to music. It seems to be a big part of your life, whether it's for relaxation or just something you do regularly.
    
    If there's anything else you'd like to add or if you want to update any of this information, just let me know! 
    エージェント:ABC さん、あなたのことをいくつか知っていますよ!私が持っている情報は以下の通りです:
    
    1. ハイキングと旅行が好きです。これらはあなたのお気に入りのアクティビティで、新しい場所を探索したり、アウトドアで過ごしたりすることを楽しんでいます。
    2. 音楽を聴くのも好きです。リラックスのためであろうと、日常的に行っていることであろうと、あなたの生活の大きな部分を占めているようです。
    
    他に何か追加したいことや、この情報を更新したいことがあれば、教えてください!
    
    ABC> I live in Beijing
    北京に住んでいます
    Thinking...
    考え中...
    Calling tool: `add_memory` with args: {'message': 'I live in Beijing'}
    ツール呼び出し:`add_memory`、引数:{'message': 'I live in Beijing'}
    Summarizing tool results...
    ツール結果を要約中...
    
    Agent: Thanks for letting me know, ABC! I've added that you live in Beijing to your profile. This will help me tailor my suggestions and plans to be more relevant to your location. If you have any other details or preferences you'd like to share, feel free to let me know! 
    エージェント:教えてくれてありがとうございます、ABC さん!あなたが北京にお住まいであることをプロファイルに追加しました。これにより、あなたの現在地に合わせて、より関連性の高い提案や計画を立てることができます。他に共有したい詳細や好みがあれば、遠慮なく教えてください!
    
    ABC> help me plan a weekend trip
    週末旅行の計画を手伝ってください
    This looks like a trip plan request. Searching memories for context...
    これは旅行計画のリクエストのようです。コンテキストのためにメモリを検索しています...
    Found relevant memories! Generating a personalized plan...
    関連するメモリが見つかりました!パーソナライズされた計画を生成しています...
    
    Agent: Of course, I'd be happy to help you plan a fantastic weekend trip! Given your love for hiking, traveling, and music, how about we create an itinerary that combines all these interests? Here are a couple of ideas:
    
    ### Option 1: Hiking and Music in the Mountains
    **Destination:** **Yunmeng Mountain**, Huairou District, Beijing
    
    **Day 1:**
    - **Morning:** Start early and drive to Yunmeng Mountain. It's about a 2-hour drive from central Beijing.
    - **Midday:** Arrive at Yunmeng Mountain and begin your hike. The trails here offer stunning views and a peaceful atmosphere. You can choose from various routes, ranging from easy to challenging.
    - **Afternoon:** After your hike, find a scenic spot to relax and enjoy a picnic lunch. Bring along some of your favorite music to set the mood.
    - **Evening:** Check into a local guesthouse or a small hotel in the area. Many places offer traditional Chinese-style accommodations with a cozy, rustic feel.
    
    **Day 2:**
    - **Morning:** Wake up early and head out for another short hike or a leisurely walk to explore more of the area.
    - **Midday:** Visit a local restaurant for a hearty brunch. Try some of the local specialties like *huai rou duck* (Huairou roast duck) or *noodles with mountain vegetables*.
    - **Afternoon:** Return to Beijing. On the way back, you can stop at a local market or a small town to pick up some souvenirs or fresh produce.
    
    ### Option 2: Cultural Exploration and Nature
    **Destination:** **Jiulongshan**, Miyun District, Beijing
    
    **Day 1:**
    - **Morning:** Drive to Jiulongshan, which is about 1.5 hours from central Beijing. This area is known for its beautiful landscapes and historical sites.
    - **Midday:** Begin your hike in Jiulongshan. The trails here are well-maintained and offer a mix of natural beauty and cultural landmarks.
    - **Afternoon:** After your hike, visit the nearby **Jinshanling Great Wall**. It’s less crowded than other sections of the Great Wall and offers breathtaking views.
    - **Evening:** Stay at a local inn or a homestay near the Great Wall. Some places even offer live music performances in the evenings, which would be a perfect way to unwind after a day of hiking.
    
    **Day 2:**
    - **Morning:** Take a leisurely walk around the area or visit a local village to experience the rural life of Beijing.
    - **Midday:** Enjoy a traditional Chinese lunch at a local restaurant.
    - **Afternoon:** Head back to Beijing. If you have time, you could stop at a scenic spot or a small park to relax and listen to some music before returning home.
    
    Both options offer a great balance of outdoor activities, cultural experiences, and relaxation. Which one do you think you’d prefer, or is there anything else you’d like to add to the itinerary?
    エージェント:もちろん、素晴らしい週末旅行の計画をお手伝いします!ハイキング、旅行、音楽がお好きだということを考慮して、これらの興味をすべて組み合わせた旅程を作成するのはいかがでしょうか?いくつかアイデアがあります:
    
    ### オプション 1:山でのハイキングと音楽
    **目的地:** **雲蒙山**、北京市懐柔区
    
    **1日目:**
    - **午前:** 早朝に出発し、雲蒙山へドライブします。北京中心部から車で約 2時間です。
    - **正午:** 雲蒙山に到着し、ハイキングを開始します。ここのトレイルは素晴らしい景色と静かな雰囲気が楽しめます。簡単なものから挑戦的なものまで、さまざまなルートから選べます。
    - **午後:** ハイキングの後、景色の良い場所でリラックスし、ピクニックランチを楽しみます。お気に入りの音楽を持参して雰囲気を盛り上げましょう。
    - **夕方:** その地域の地元のゲストハウスや小さなホテルにチェックインします。多くの場所で、居心地の良い素朴な雰囲気の伝統的な中国風の宿泊施設が提供されています。
    
    **2日目:**
    - **午前:** 早起きして、もう一度短いハイキングに出かけるか、のんびりと散歩してエリアをさらに探索します。
    - **正午:** 地元のレストランでボリュームのあるブランチを楽しみます。*懐柔ダック* (懐柔ローストダック) や *山菜麺* などの郷土料理を試してみてください。
    - **午後:** 北京に戻ります。帰り道に、地元の市場や小さな町に立ち寄って、お土産や新鮮な農産物を買うことができます。
    
    ### オプション 2:文化探訪と自然
    **目的地:** **九龍山**、北京市密雲区
    
    **1日目:**
    - **午前:** 北京中心部から約 1.5時間の九龍山へドライブします。この地域は美しい風景と史跡で知られています。
    - **正午:** 九龍山でハイキングを開始します。ここのトレイルはよく整備されており、自然の美しさと文化的なランドマークが混在しています。
    - **午後:** ハイキングの後、近くの **金山嶺長城** を訪れます。万里の長城の他のセクションよりも混雑が少なく、息をのむような景色が楽しめます。
    - **夕方:** 万里の長城近くの地元の旅館やホームステイに宿泊します。場所によっては、夜にライブ音楽の演奏を提供しているところもあり、ハイキングの一日の疲れを癒すのに最適です。
    
    **2日目:**
    - **午前:** 周辺をのんびり散歩したり、地元の村を訪れて北京の田舎の生活を体験したりします。
    - **正午:** 地元のレストランで伝統的な中華ランチを楽しみます。
    - **午後:** 北京に戻ります。時間があれば、景色の良い場所や小さな公園に立ち寄ってリラックスし、家に帰る前に音楽を聴くことができます。
    
    どちらのオプションも、アウトドアアクティビティ、文化体験、リラクゼーションのバランスが取れています。どちらがお好みですか、それとも旅程に何か追加したいことはありますか?
    
    ABC> I live in Shanghai now
    今は上海に住んでいます
    Thinking...
    考え中...
    Calling tool: `find_and_update_memory` with args: {'query': 'I live in Beijing', 'new_text': 'I live in Shanghai now'}
    ツール呼び出し:`find_and_update_memory`、引数:{'query': 'I live in Beijing', 'new_text': 'I live in Shanghai now'}
    Summarizing tool results...
    ツール結果を要約中...
    
    Agent: Got it, ABC! I've updated your information to reflect that you now live in Shanghai. This will help me provide more relevant suggestions and plans for your new location. If there's anything else you'd like to update or add, just let me know! 
    
    (I used the `find_and_update_memory` tool to change your location from Beijing to Shanghai.)
    エージェント:承知しました、ABC さん!あなたが現在上海にお住まいであることを反映するように情報を更新しました。これにより、新しい場所に合わせてより関連性の高い提案や計画を提供できます。他に更新または追加したいことがあれば、教えてください!
    
    (私は `find_and_update_memory` ツールを使用して、あなたの場所を北京から上海に変更しました。)
    
    ABC> where do i live
    私はどこに住んでいますか
    Thinking...
    考え中...
    Calling tool: `search_memories` with args: {'query': 'where do i live'}
    ツール呼び出し:`search_memories`、引数:{'query': 'where do i live'}
    Summarizing tool results...
    ツール結果を要約中...
    
    Agent: You currently live in Shanghai, ABC! I updated this information recently, so it's all set. If you have any other questions or need more details, feel free to ask! 
    
    (I used the `search_memories` tool to find and confirm your current location.)
    エージェント:ABC さん、あなたは現在上海にお住まいです!この情報は最近更新したので、すべて設定済みです。他に質問や詳細が必要な場合は、お気軽にお尋ねください!
    
    (私は `search_memories` ツールを使用して、あなたの現在の場所を見つけて確認しました。)
    
    ABC> help me plan a weekend trip
    週末旅行の計画を手伝ってください
    This looks like a trip plan request. Searching memories for context...
    これは旅行計画のリクエストのようです。コンテキストのためにメモリを検索しています...
    Found relevant memories! Generating a personalized plan...
    関連するメモリが見つかりました!パーソナライズされた計画を生成しています...
    
    Agent: Considering you now live in Shanghai and enjoy weekend road trips, hiking, and listening to music, I’ve planned a perfect weekend getaway for you. You can head to Moganshan in Zhejiang Province, about a 3-hour drive from Shanghai—a great destination for a short trip.
    
    **Itinerary suggestion:**
    
    - **Day 1:**
      - Drive from Shanghai to Moganshan in the morning.
      - Have lunch at a local restaurant after arriving at noon.
      - Spend the afternoon hiking in Moganshan. Choose scenic trails such as Jianchi or Daken Scenic Area.
      - Stay overnight at a comfortable boutique hotel or guesthouse.
    
    - **Day 2:**
      - Take a relaxing walk near your accommodation in the morning.
      - Visit a local tea plantation in the morning to experience tea picking.
      - Have lunch at a restaurant near the tea plantation.
      - Return to Shanghai in the afternoon to wrap up your enjoyable weekend.
    
    **Notes:**
    - Ensure your vehicle is in good condition. Check oil and tires before departure.
    - Bring your favorite music to enjoy during the drive.
    - Pack appropriate clothing and gear for hiking, especially proper shoes and a backpack.
    - Book your accommodation in advance to guarantee a suitable stay.
    
    I hope this plan helps you enjoy a wonderful weekend! If you need anything else or want to adjust the itinerary, just let me know.
    エージェント:あなたが現在上海に住んでいて、週末のドライブ旅行、ハイキング、音楽鑑賞を楽しんでいることを考慮して、完璧な週末の逃避行を計画しました。上海から車で約 3時間の浙江省の莫干山に行くことができます。短い旅行に最適な目的地です。
    
    **旅程の提案:**
    
    - **1日目:**
      - 午前中に上海から莫干山へドライブします。
      - 正午に到着後、地元のレストランで昼食をとります。
      - 午後は莫干山でハイキングを楽しみます。剣池や大坑景区などの景色の良いトレイルを選びましょう。
      - 快適なブティックホテルやゲストハウスで一泊します。
    
    - **2日目:**
      - 午前中に宿泊施設の近くをリラックスして散歩します。
      - 午前中に地元の茶畑を訪れて、お茶摘みを体験します。
      - 茶畑の近くのレストランで昼食をとります。
      - 午後に上海に戻り、楽しい週末を締めくくります。
    
    **注意事項:**
    - 車両が良好な状態であることを確認してください。出発前にオイルとタイヤをチェックしてください。
    - ドライブ中に楽しむためにお気に入りの音楽を持参してください。
    - ハイキングに適した服装と装備、特に適切な靴とバックパックを準備してください。
    - 適切な滞在を保証するために、宿泊施設を事前に予約してください。
    
    この計画が素晴らしい週末を楽しむのに役立つことを願っています!他に何か必要なことや、旅程を調整したいことがあれば、教えてください。
    

API リファレンス

このガイドでは、次の API を使用します。詳細については、公式の OpenSearch ドキュメントをご参照ください:

課金

PolarSearch メモリコンテナーを使用すると、次のコストが発生します。本番環境でこの機能を使用する前に、これらのコストを注意深く確認してください。

  1. コンピュートノード料金:PolarDB の一部として、PolarSearch ノードにはコンピュートノード料金が発生します。

  2. モデルサービス料金:ファクト抽出やベクトル化のために Alibaba Cloud Model Studio のモデルなどの外部モデルを呼び出すと、API 呼び出し料金が発生します。

よくある質問

model_idconnector_id の違いは何ですか?メモリ コンテナーを作成する際に、どちらを使用すればよいですか?

  • connector_id は、Alibaba Cloud Model Studio などの外部モデルサービスを指すように作成したコネクタの ID です。

  • model_id は、コネクタを PolarSearch に登録した後、PolarSearch がモデルに内部で割り当てる ID です。

メモリコンテナーを作成する際は、model_idembedding_model_id および llm_id パラメーターに使用してください。

llm_result_path:このパラメーターの設定方法

JSONPath 構文を使用して、LLM の複雑な JSON 応答から必要なテキストコンテンツをメモリファクトとして抽出できます。このパスは、お使いの LLM の実際の API レスポンス構造体に基づいて設定する必要があります。たとえば、互換モードの Alibaba Cloud Model Studio の場合、コアコンテンツは{"choices":[{"message":{"content":"..."}}]}にあります。したがって、パスは$.choices[0].message.contentです。