

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# LangGraph 代理程式的語意長期記憶體
<a name="ddb-langgraph-memory"></a>

LangGraph 會分隔兩種類型的代理程式狀態。短期狀態是對話執行緒本身，檢查點程式會持續存在，以便執行緒可以繼續、重播和復原 （請參閱 [使用 DynamoDB 做為 LangGraph 代理程式的檢查點存放區](ddb-langgraph-checkpoint.md))。長期記憶體是代理程式跨執行緒知道的，LangGraph 將其建模為存放區：代理程式刻意寫入並稍後讀取的命名空間鍵值表面。

在 [langgraph-checkpoint-aws](https://pypi.org/project/langgraph-checkpoint-aws/) 套件中， `DynamoDBStore`類別是該存放區的 DynamoDB 實作。它使用階層式命名空間處理鍵值端、過期記憶體的存留時間，以及基本篩選。設定向量索引後 （請參閱 [在 DynamoDB 中使用向量索引](VectorSearch.md))，其`search()`方法會執行語意搜尋：記憶體會在寫入時內嵌、非同步編製索引，並依意義叫用，並依相似性進行排名，從保留它們的相同資料表進行排序。沒有要佈建的個別向量資料庫，也沒有管道將資料複製到其中。

## 先決條件
<a name="langgraph-memory-prerequisites"></a>
+  AWS 帳戶 具有建立 DynamoDB 資料表和叫用 Amazon Bedrock 模型許可的
+ 存取您區域中 Amazon Bedrock 中的內嵌模型。這些範例使用 Amazon Titan Text Embeddings V2
+ Python 3.10 或更新版本，搭配 `langgraph-checkpoint-aws` 1.2.2 或更新版本和 `boto3` 1.43.64 或更新版本 （舊版`boto3`沒有`SearchVectors`操作）

使用 pip 安裝程式庫：

```
pip install langgraph langgraph-checkpoint-aws langchain-aws
```

## 設定 存放區
<a name="langgraph-memory-setup"></a>

使用 `index`區塊設定存放區並呼叫 `setup()`：

```
from langchain_aws import BedrockEmbeddings
from langgraph_checkpoint_aws import DynamoDBStore

store = DynamoDBStore(
    table_name="support-agent-memory",
    region_name="us-east-1",
    index={
        "embed": BedrockEmbeddings(model_id="amazon.titan-embed-text-v2:0"),
        "dims": 1024,
        "fields": ["text"],
        "distance_function": "COSINE",
    },
)

store.setup()
```

`index` 區塊中的四個設定值得了解：
+ `embed` 接受任何 LangChain 內嵌物件，或將字串清單映射至向量清單的純可呼叫物件。
+ `dims` 必須符合模型的輸出大小。Titan Text Embeddings V2 預設會傳回 1，024 個維度。如果這些不同，則存放區會引發維度不相符錯誤，在第一次寫入時命名這兩個數字，而不是編寫索引無法使用的向量。
+ `fields` 會選取要內嵌的值部分。此處只會內嵌 `text` 欄位。預設 會將整個值`["$"]`序列化為 JSON，並嵌入該值，這很方便，但也會嵌入您的簿記屬性。
+ `distance_function` 預設為 `COSINE`，也接受 `EUCLIDEAN`和 `DOT_PRODUCT`。

`setup()` 會在資料表不存在時建立資料表、連接向量索引，並在您設定時啟用存留時間。在現有資料表上，它只會新增遺失的項目，因此現有`DynamoDBStore`部署可以採用語意搜尋，而無需重新建立資料表或遷移項目。請記住，在索引報告`ACTIVE`且不再回填之前， `setup()`不會傳回：在回填`ACTIVE`時報告 的索引會拒絕`SearchVectors`呼叫。在新的空白資料表上，等待時間很短。只要回填需要的時間，即可在具有現有項目的資料表上重新調整。

## 寫入記憶體
<a name="langgraph-memory-write"></a>

撰寫是一般 `put()`。內嵌會為您進行：

```
namespace = ("memories", "acme-corp", "user-8812")

store.put(namespace, "mem-1", {
    "text": "Account runs a proxy that closes idle sockets after 60 seconds",
    "source": "ticket-4471",
})
store.put(namespace, "mem-2", {
    "text": "Customer prefers email, asked not to be called by phone",
    "source": "ticket-4471",
})
store.put(namespace, "mem-3", {
    "text": "Uses a custom build of the SDK pinned to version 2.14",
    "source": "ticket-4502",
})
```

## 依意義叫用
<a name="langgraph-memory-recall"></a>

客戶開啟新的對話，並說他們的連線在大約一分鐘後持續下降：

```
results = store.search(
    namespace,
    query="connection drops after a minute of inactivity",
    limit=3,
)

for item in results:
    print(round(item.score, 3), item.value["text"])
```

輸出：

```
0.324 Account runs a proxy that closes idle sockets after 60 seconds
0.039 Customer prefers email, asked not to be called by phone
0.031 Uses a custom build of the SDK pinned to version 2.14
```

相關的記憶體會先排名，且查詢中沒有任何索引鍵符合任何項目。 `SearchItem.score`遵循 LangGraph 慣例，其中較高者更為相關。DynamoDB 會傳回距離，其中下限更接近，因此存放區會轉換：`COSINE`對於分數為 `1 - distance`，對於`EUCLIDEAN`分數為 `1 / (1 + distance)`，而`DOT_PRODUCT`分數會保持不變。如果您進入絕對分數以判斷記憶體是否足以插入提示，請針對您自己的資料和您選擇的距離函數校正該閾值。

## 使用搜尋結構描述的範圍記憶體
<a name="langgraph-memory-scoping"></a>

當 `DynamoDBStore`建立向量索引時，它會將資料表的分割區索引鍵宣告為索引搜尋結構描述中的`HASH`元素。由於存在該元素，因此每次搜尋都必須提供其條件，而 存放區會提供命名空間：命名空間元組會聯結以形成分割區索引鍵值，因此每次語意搜尋都會固定到剛好一個命名空間。三個後果如下：
+ **隔離是結構性的，不是您記得寫入的篩選條件。**搜尋無法到達不同命名空間中的記憶體，因此服務許多租用戶的商店沒有傳回其他租用戶記憶體的查詢形狀。這是查詢範圍而非授權：具有資料表`dynamodb:SearchVectors`許可的主體可以直接搜尋任何命名空間值，因此決定發起人可能讀取的命名空間仍屬於 IAM 和應用程式的授權層。
+ **召回成本會追蹤一個使用者的記憶體，而不是整個資料表。**搜尋工作受限於一個命名空間容納多少，而不是整個產品保留多少記憶體，這會隨著您的成長而保持低延遲和向量搜尋成本穩定。
+ **語意搜尋是確切命名空間，而不是字首。**搜尋會完全到達您傳遞的命名空間，而且其中沒有任何內容。搜尋`("memories", "acme-corp")`不會達到 `("memories", "acme-corp", "user-8812")`，因為這些是不同的分割區索引鍵值。您的命名空間是您的召回範圍，因此請選擇它以符合您希望單一搜尋看到的範圍。

下表摘要說明如何選擇命名空間形狀。


| 命名空間形狀 | 一個`search()`達到 | 在 時選擇它 | 
| --- | --- | --- | 
| `("memories", user_id)` | 該使用者的記憶 | 每個使用者召回的單一租戶產品 | 
| `("memories", tenant_id, user_id)` | 該使用者在該租用戶 | 多租戶，常見案例 | 
| `("memories", tenant_id, user_id, agent_name)` | 一位客服人員對該使用者的備註 | 多個不應讀取彼此備註的專業客服人員 | 
| `("account_facts", tenant_id)` | 租用戶整體知識 | 適用於帳戶中每個使用者的事實 | 

避免將每個記憶體放在一個命名空間中，並在中繼資料上進行篩選：在 DynamoDB 已在整個命名空間中選擇最接近的相符項目之後套用篩選條件，且單一搜尋最多傳回前 100 個相符項目，因此一旦一個忙碌租用戶填滿常見查詢的前 100 個，即使存在其記憶體，安靜租用戶的搜尋仍會傳回少於請求的結果。對於決定正確性的任何項目，偏好透過篩選條件進行命名空間範圍調整。比您的召回界限更精細也是一種合法的設計：需要使用者特定和全帳戶內容的代理程式會同時搜尋命名空間並合併結果。

## 考量事項
<a name="langgraph-memory-considerations"></a>
+ 索引最終一致，與全域次要索引的模型相同。在 之後立即`search()`發行的 `put()`可能尚未包含新的記憶體。對於寫入記憶體並在相同轉彎內叫用記憶體的代理程式，請改用 金鑰將其讀回。
+ 單一搜尋最多傳回前 100 個相符項目。`limit` 加號`offset`超過它的請求會因明確的錯誤而被拒絕，而不是以無提示的方式傳回超過上限的任何內容。
+ 如果您使用存留時間來過期過時的記憶體，並在讀取時啟用重新整理，則代理程式實際召回的記憶體會推斷其過期，因此作用中使用的記憶體不會無聲地消失。
+ 向量搜尋失敗會導致您的應用程式做出反應。如果儲存區在失敗時傳回空清單，則調節、許可問題和真正的空白結果看起來會相同。
+ 向量操作會以自己的單位計費，與基底資料表讀取和寫入請求單位分開計量，並且兩者都會隨著維度數量擴展。每次寫入和每次搜尋時，較小的內嵌維度較便宜，因此請使用具有召回品質的最小維度。
+ 在已設定的 中沒有文字撰寫的記憶`fields`會儲存為沒有內嵌，且不會出現在語意結果中。發生這種情況時，存放區會記錄警告。

## 其他資源
<a name="langgraph-memory-resources"></a>
+ [GitHub 上的 DynamoDBStore 文件](https://github.com/langchain-ai/langchain-aws/blob/main/libs/langgraph-checkpoint-aws/langgraph_checkpoint_aws/store/dynamodb/DynamoDBStore.md)
+ [PyPI 上的 langgraph-checkpoint-aws ](https://pypi.org/project/langgraph-checkpoint-aws/)
+ [LangGraph 文件](https://langchain-ai.github.io/langgraph/)