全部產品
Search
文件中心

PolarDB:使用PolarSearch記憶容器為AI智能體(Agent)構建長短期記憶

更新時間:Mar 14, 2026

當AI 智能體(Agent)在多輪對話或執行連續任務時,往往因為缺乏對歷史資訊的有效記憶而無法記住使用者的偏好、歷史指令或上下文,導致互動體驗中斷、任務執行效率低下。PolarSearch記憶容器(Memory Container)專為解決此問題而設計。它為AI智能體提供了一個內建於PolarDB PostgreSQL版的長短期記憶管理系統。通過該功能,智能體能夠自動地將對話資訊結構化、持久化,並能在後續互動中通過語義檢索快速調用相關記憶,從而為使用者提供真正連續、個人化且高效的服務。

說明

PolarSearch記憶容器功能目前處於灰階階段。若您有相關需求,請提交工單聯絡我們為您處理。

工作原理

PolarSearch記憶容器通過一套自動化的處理流程,將非結構化的對話資料轉化為可檢索、可利用的結構化記憶。其核心在於三級儲存架構和智能化的記憶處理流程。

三級儲存架構

為了平衡不同時效性記憶的儲存和檢索效率,記憶容器採用了分層設計:

記憶類型

索引類型

核心作用

短期記憶 (Working Memory)

倒排索引

儲存原始的、未經處理的短期會話資訊,使用倒排索引進行快速檢索,主要用於維持目前的交談的上下文。

長期記憶 (Long-Term Memory)

向量索引+倒排索引

通過大語言模型(LLM)從對話中提取關鍵事實(Facts),並將其向量化後儲存。這部分記憶支援KNN語義搜尋,用於深層次的經驗和知識檢索。

記憶歷史 (Memory History)

倒排索引

記錄每一次記憶操作(如新增、修改),形成完整的審計日誌,確保記憶的演變過程可追溯。

記憶處理流程

當智能體接收到新的資訊時,記憶容器會觸發一系列處理動作,實現記憶的自動攝取、持久化和檢索。

image
  1. 記憶攝取:

    • 輸入與預先處理:使用Elasticsearch協議接收來自智能體的對話輸入,並可調用大語言模型(LLM)進行摘要產生、意圖分類或標籤提取等預先處理。

    • 路由決策:記憶路由(Memory Router)分析輸入內容,並與現有記憶進行比對,決策是新增(ADD)、更新(UPDATE)還是刪除(DELETE)一條記憶。

  2. 記憶持久化:

    • 短期記憶:原始對話內容存入短期記憶。

    • 長期記憶:由LLM抽取的結構化記憶事實,經過Embedding模型向量化後,存入長期記憶。

    • 記憶歷史:所有操作記錄寫入記憶歷史,形成審計軌跡。

  3. 記憶檢索:

    • 輸入解析:接收檢索條件,通過Elasticsearch協議解析多模態檢索請求,調用許可權驗證模組確認資料訪問邊界。

    • 混合索引檢索:在長期記憶中執行混合檢索(ANN向量相似性搜尋和倒排索引過濾),並在短期記憶中進行關鍵詞匹配。同時根據記憶時間戳記進行權重調整。

    • 結果整合:重排模型(Reranker)對檢索結果進行最佳化排序,並根據時間戳記等資訊消除衝突,最終將最相關的記憶返回給智能體。

功能優勢

能力維度

傳統方案

PolarSearch解決方案

記憶利用率

歷史資料沉澱低於30%,難以有效利用。

通過LLM抽取事實,結合語義索引,將有效資訊利用率提升至85%以上。

檢索效率

依賴關鍵詞匹配,準確率≤60%。

採用向量與倒排混合索引,並進行重排序(Rerank),語義檢索準確率可達95%以上。

企業合規

多租戶隔離和許可權管控實現複雜。

原生支援多租戶隔離、RBAC許可權控制和Action Trail,滿足企業級安全要求。

系統擴充性

單點儲存容量上限TB級。

雲端式原生分布式儲存架構,儲存容量可擴充至PB級。

開發成本

功能需定製開發,周期長。

提供開箱即用的API和SDK,簡化開發流程,協助您快速整合。

適用範圍

快速開始

本指南將引導您完成從環境準備到成功存入並檢索一條記憶的全過程。

整體流程:準備環境 -> 啟用外掛程式 -> 註冊模型 -> 建立記憶容器 -> 存入與驗證記憶。

步驟一:配置訪問憑證與環境變數

在開始操作前,請先準備好以下資訊,並設定為環境變數。這將有效簡化後續的curl命令,避免重複修改。統一管理所有配置和憑證,方便後續命令的複製和執行。

變數名

含義

樣本值

POLARSEARCH_HOST_PORT

PolarSearch節點的串連地址與連接埠

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

USER_PASSWORD

PolarSearch節點的管理員帳號

polarsearch_user:your_assword

YOUR_API_KEY

阿里雲大模型服務平台百鍊的API Key

sk-xxxxxxxxxxxxxxxxxxxxxxxx

操作步驟: 在您的終端中執行以下命令,將樣本值替換為您的真實資訊。

# 設定 PolarSearch 訪問地址和連接埠
export POLARSEARCH_HOST_PORT="pc-xxx.polardbsearch.rds.aliyuncs.com:3001"

# 設定 PolarSearch 管理員密碼
export USER_PASSWORD="polarsearch_user:your_assword"

# 設定您的千問 API Key
export YOUR_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxxxxx"

步驟二:啟用記憶容器外掛程式

執行以下命令,在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
  }
}'

Dashboard

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

步驟三:註冊模型

您需要向PolarSearch註冊一個用於事實抽取的LLM和一個用於語義化的Embedding模型。

註冊文本向量化模型(Embedding)

建立一個連接器(Connector)指向阿里雲大模型服務平台百鍊的Embedding模型服務,並將其註冊為PolarSearch可調用的模型。

  1. 設定可信端點:將模型的API Endpoint添加到可信列表,允許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/.*$"
        ]
      }
    }'
    

    Dashboard

    PUT _cluster/settings
    {
      "persistent": {
        "plugins.ml_commons.trusted_connector_endpoints_regex": [
          "^https://dashscope.aliyuncs.com/compatible-mode/v1/.*$"
        ]
      }
    }
  2. 建立模型連接器:配置串連text-embedding-v4模型的參數。

    說明

    pre_process_functionpost_process_function用於適配不同模型服務的請求和響應格式。此處使用內建的openai.embedding格式轉換函式,因為它與阿里雲大模型服務平台百鍊相容模式的格式一致。

    命令列

    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"
          }
        ]
    }'

    Dashboard

    重要

    請替換以下命令中的<YOUR_API_KEY>為您真實的阿里雲大模型服務平台百鍊的API Key

    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. 註冊模型:此步驟將上一步建立的Connector註冊為一個模型。

    命令列

    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"
    }'

    Dashboard

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

    執行成功後,系統會返回一個 model_id。請記錄該 ID,後續步驟將使用。

    # 記錄返回的 model_id
    {"task_id": "LRFx6ZsBk04ixxx","status": "CREATED","model_id": "LhFx6ZsBk04xxx"}
  5. 測試模型:通過下述查詢可以直接與大模型對話並查看大模型返回結果。

    命令列

    curl -XPOST "http://${POLARSEARCH_HOST_PORT}/_plugins/_ml/_predict/text_embedding/<上一步擷取的模型ID>" \
    --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"]
    }'

    Dashboard

    POST _plugins/_ml/_predict/text_embedding/<上一步擷取的模型ID>
    {
      "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)

建立並註冊一個串連到阿里雲大模型服務平台百鍊qwen-plus大模型的連接器。

  1. 建立模型連接器(Connector):

    命令列

    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\" } }"
          }
        ]
    }'

    Dashboard

    重要

    請替換以下命令中的<YOUR_API_KEY>為您真實的阿里雲大模型服務平台百鍊的API Key

    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. 註冊模型:此步驟將上一步建立的Connector註冊為一個模型。

    命令列

    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"
    }'

    Dashboard

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

    執行成功後,系統會返回一個 model_id。請記錄該 ID,後續步驟將使用。

    # 記錄返回的 model_id
    {"task_id": "PhGy6ZsBk04xxx","status": "CREATED","model_id": "PxGy6ZsBk04xxx"}
  4. 發布模型:

    命令列

    curl -XPOST "http://${POLARSEARCH_HOST_PORT}/_plugins/_ml/models/<上一步擷取的模型ID>/_deploy" \
    --user "${USER_PASSWORD}"

    Dashboard

    POST _plugins/_ml/models/<上一步擷取的模型ID>/_deploy

    執行成功後,如果statusCOMPLETED代表已部署成功。

    {"task_id": "NxGI6ZsBk04xxx","task_type": "DEPLOY_MODEL","status": "COMPLETED"}
  5. 測試模型:通過下述查詢可以直接與大模型對話並查看大模型返回結果。

    命令列

    curl -XPOST "http://${POLARSEARCH_HOST_PORT}/_plugins/_ml/models/<上一步擷取的模型ID>/_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."
      }
    }'

    Dashboard

    POST _plugins/_ml/models/<上一步擷取的模型ID>/_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
        }
      ]
    }

步驟四:建立記憶容器

  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模型ID>",
        "embedding_dimension": 1024,
        "llm_id": "<上一步擷取的LLM模型ID>",
        "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"
        }
      }
    }'

    Dashboard

    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模型ID>",
        "embedding_dimension": 1024,
        "llm_id": "<上一步擷取的LLM模型ID>",
        "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:可為不同記憶索引配置分區(shards)和副本(replicas)數量,以實現高可用。

    • strategies:定義自動化策略。本例中定義了一個SEMANTIC策略,它會在user_id這個命名空間 (Namespace) 下,自動從對話中提取語義事實。

    • llm_result_path:使用JSONPath文法,指定如何從LLM返回的JSON響應中提取核心內容。$.choices[0].message.content適配了阿里雲大模型服務平台百鍊相容模式的響應結構。

  2. 執行成功後,系統會返回一個memory_container_id。請記錄該 ID,後續步驟將使用。

    # 記錄返回的 model_id
    {"memory_container_id": "QRHF6ZsBk04xxx","status": "created"}

    建立記憶容器後,實際將建立以下索引和管道(pipeline):

    .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"
              }
            }
          }
        ]
    }

步驟五:存入與驗證記憶

向記憶容器中添加一段對話,並驗證其是否被正確地存入短期和長期記憶中。

  1. 存入記憶: 向容器中POST一段關於使用者Bob的對話。"infer": true參數會觸發之前配置的SEMANTIC策略,調用LLM提取事實。

    命令列

    curl -XPOST "http://${POLARSEARCH_HOST_PORT}/_plugins/_ml/memory_containers/<上一步建立的記憶容器ID>/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"
    }'

    Dashboard

    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>-memory-working/_search?pretty" \
    --user "${USER_PASSWORD}" \

    Dashboard

    GET .plugins-ml-am-<上一步建立的記憶容器index_prefix>-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”,並由Embedding模型產生了對應的向量memory_embedding

    命令列

    curl -XGET "http://${POLARSEARCH_HOST_PORT}/.plugins-ml-am-<上一步建立的記憶容器index_prefix>-memory-long-term/_search?pretty" \
    --user "${USER_PASSWORD}" \

    Dashboard

    GET .plugins-ml-am-<上一步建立的記憶容器index_prefix>-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索引中有兩條ADD記錄和對應的記憶時間戳記。

    命令列

    curl -XGET "http://${POLARSEARCH_HOST_PORT}/.plugins-ml-am-<上一步建立的記憶容器index_prefix>-memory-history/_search?pretty" \
    --user "${USER_PASSWORD}" \

    Dashboard

    GET .plugins-ml-am-<上一步建立的記憶容器index_prefix>-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智能體存入了一條可供長期檢索的記憶。

步驟六:(可選)更新記憶

記憶容器能夠提取和更新長期記憶。

  1. 首先插入一段記憶:

    命令列

    curl -XPOST "http://${POLARSEARCH_HOST_PORT}/_plugins/_ml/memory_containers/<上一步建立的記憶容器ID>/memories" \
    --user "${USER_PASSWORD}" \
    -H 'Content-Type: application/json' \
    -d'{
      "messages": [
        {
          "role": "user",
          "content": [
            {
              "text": "我叫NameA,我來自AreaA,目前我居住在AreaB.",
              "type": "text"
            }
          ]
        },
        {
          "role": "assistant",
          "content": [
            {
              "text": "你好,NameA!很高興認識你.",
              "type": "text"
            }
          ]
        }
      ],
      "namespace": {
        "user_id": "NameA"
      },
      "tags": {
        "topic": "personal info"
      },
      "infer": true,
      "memory_type": "conversation"
    }'

    Dashboard

    POST _plugins/_ml/memory_containers/<memory_container_id>/memories
    {
      "messages": [
        {
          "role": "user",
          "content": [
            {
              "text": "我叫NameA,我來自AreaA,目前我居住在AreaB.",
              "type": "text"
            }
          ]
        },
        {
          "role": "assistant",
          "content": [
            {
              "text": "你好,NameA!很高興認識你.",
              "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>-memory-long-term/_search" \
    --user "${USER_PASSWORD}" \
    -H 'Content-Type: application/json' \
    -d'{
      "_source": {
        "excludes": ["memory_embedding"]
      },
      "query": {
        "match_all": {}
      }
    }'

    Dashboard

    GET .plugins-ml-am-<上一步建立的記憶容器index_prefix>-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/<上一步建立的記憶容器ID>/memories" \
    --user "${USER_PASSWORD}" \
    -H 'Content-Type: application/json' \
    -d'{
      "messages": [
        {
          "role": "user",
          "content": [
            {
              "text": "我叫NameA,目前我居住在AreaC.",
              "type": "text"
            }
          ]
        },
        {
          "role": "assistant",
          "content": [
            {
              "text": "你好,NameA!很高興認識你.",
              "type": "text"
            }
          ]
        }
      ],
      "namespace": {
        "user_id": "NameA"
      },
      "tags": {
        "topic": "personal info"
      },
      "infer": true,
      "memory_type": "conversation"
    }'

    Dashboard

    POST _plugins/_ml/memory_containers/<memory_container_id>/memories
    {
      "messages": [
        {
          "role": "user",
          "content": [
            {
              "text": "我叫NameA,目前我居住在AreaC.",
              "type": "text"
            }
          ]
        },
        {
          "role": "assistant",
          "content": [
            {
              "text": "你好,NameA!很高興認識你.",
              "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介面,將新的對話資訊存入記憶。

  • search_memories():調用_search介面,在長期和短期記憶中檢索與使用者查詢相關的資訊。

  • 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.
"""
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
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# Import OpenAI library for LLM operations
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
current_user_id = None

# Global OpenAI client instance
llm_client = None


# --- OpenSearch Helper ---
def opensearch_request(method: str, endpoint: str, json_data: Optional[Dict] = None) -> requests.Response:
    """Make an HTTP request to the OpenSearch API."""
    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."""
    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 ---
def is_trip_plan_request(query: str) -> bool:
    """Determine if a user's query is a request for a trip plan using an LLM."""
    if not query.strip():
        return False
    try:
        prompt = f"""請判斷以下使用者輸入是否是在請求制定旅行計劃(旅行規劃)。
請只回答"YES"或"NO",不需要其他解釋。
使用者輸入: {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."""
    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."""
    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'."""
    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 ---
    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
    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 ---
            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 ---
                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!
    
    Trip Memory Agent is ready!
    Hello ABC! I can remember things for you, search your memories, and help plan trips.
    
    ABC> i like hiking and travelling
    Thinking...
    Calling tool: `add_memory` with args: {'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> I like listen to music
    Thinking...
    Calling tool: `add_memory` with args: {'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> what do you know about me
    Thinking...
    Calling tool: `search_memories` with args: {'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> I live in Beijing
    Thinking...
    Calling tool: `add_memory` with args: {'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> 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* (懷柔烤鴨) 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?
    
    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'}
    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> where do i live
    Thinking...
    Calling tool: `search_memories` with args: {'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> 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: 考慮到你目前居住在上海,並且喜歡周末自駕遊、遠足和聽音樂,我為你規划了一個完美的周末旅行。你可以前往浙江省的莫幹山,那裡距離上海大約3小時車程,是一個非常適合短途旅行的目的地。
    
    **行程建議:**
    
    - **第一天:**
      - 上午從上海出發,駕車前往莫幹山。
      - 中午抵達後,在當地的特色餐館享用午餐。
      - 下午開始徒步探索莫幹山,享受自然風光。你可以選擇一些風景優美的步道,如劍池、大坑景區等。
      - 晚上入住一家舒適的民宿,放鬆身心。
    
    - **第二天:**
      - 早上可以在民宿附近散步,呼吸新鮮空氣。
      - 上午可以選擇繼續徒步或參觀當地的茶園,體驗採茶樂趣。
      - 中午在茶園附近的餐廳享用午餐。
      - 下午返回上海,結束愉快的周末之旅。
    
    **注意事項:**
    - 請確保車輛狀況良好,提前檢查油量和輪胎。
    - 準備一些你喜歡的音樂,讓旅途更加愉快。
    - 根據天氣情況準備適當的衣物和裝備,尤其是徒步時需要的鞋子和背包。
    - 提前預訂民宿,以確保有合適的住宿。
    
    希望這個計劃能讓你度過一個愉快的周末!如果你有任何其他需求或想要調整行程,請隨時告訴我。
    

API 參考

本指南中主要使用了以下API,更多詳細用法請參考OpenSearch官方文檔:

計費說明

使用 PolarSearch 記憶容器功能會涉及以下幾部分費用,請在生產使用前仔細評估:

  1. 計算節點費用:作為PolarDB的一部分,PolarSearch節點會產生相應的節點計算節點費用。

  2. 模型服務費用:調用外部模型(如阿里雲大模型服務平台百鍊)進行事實抽取和向量化會產生相應的API調用費用。

常見問題

model_idconnector_id有什麼區別,建立記憶容器時應該用哪個?

  • connector_id是您建立的指向外部模型服務(如阿里雲大模型服務平台百鍊)的連接器的ID。

  • model_id是您將該連接器註冊到PolarSearch後,PolarSearch內部為該模型分配的ID。

在建立記憶容器時,embedding_model_idllm_id欄位需使用model_id

llm_result_path參數如何配置?

該參數使用JSONPath文法,用於從LLM返回的複雜JSON響應中精確提取需要作為記憶事實的常值內容。您需要根據所使用LLM的實際API響應結構來配置此路徑。例如,對於阿里雲大模型服務平台百鍊相容模式,其核心內容位於{"choices":[{"message":{"content":"..."}}]},因此路徑配置為$.choices[0].message.content