顧客ごとのメモリ
会話が顧客に関する永続的な事実となる仕組み — 導出、重複排除、重要性、および何が想起されるかについて。
ドキュメントから回答を取得します。記憶はもう半分です:エージェントがこの特定の顧客について何を知っているかであり、セッション、チャネル、月を跨いで保持されます。
記憶はログからではなく、派生する
会話のターンが終了します。まだ何も保存されません——トランスクリプトはここでは記憶の単位ではありません。
モデルが、エージェント自身の返信ではなく、顧客が言ったことから正規化されたエントリを抽出します:事実(「4台の車両からなる fleet を運営している」)とトピックであり、それぞれ重要度スコアを持ち、軽い一言と厳格な制約が同様に扱われないようにします。
ターンのエントリは、一度に一つではなくバッチとして埋め込みされます。埋め込みエンドポイントは並行度が1で実行されているため、単一の呼び出しを連続して行うと、他のユーザーのクエリがその後にブロックされてしまいます。
各エントリは、既に存在する内容と比較されます——トピックは名前で、事実は閾値内のベクトル近傍で。これがなければ、5つの会話にまたがって言及された同じ事実が5つの記憶となり、他のすべての情報を押し退けてしまいます。
一致した場合、新しい行を追加するのではなく既存の行を更新します:コンテンツと埋め込みを更新し、重要度を両者の高い方へ引き上げ、最も最新のセッションを指し示すようにして、鮮度がライブな関係を追跡します。一致しない場合は、新しいエントリとして保存され、暗号化され、スペースにスコープされます。
次のターンでは、記憶は文書が使用するものよりも厳格な閾値の下で類似度により検索されます——部分的に関連する文書の passage は単に役に立たないだけですが、部分的に関連するある人についての「事実」は、積極的に誤りとなります。
ターンの後に実行されます。重複排除ステップは、関係が長くなるにつれて検索精度が低下しないようにするものです。
全体のトランスクリプトを保存してそれを検索するのは明白なアプローチであり、かつ不適切なものです。トランスクリプトの大部分は filler であり、同じ事実が20の異なる表現で現れ、関係が長くなるにつれて検索精度が低下します——これは完全に逆です。
その代わりに、プラットフォームはターンの後、顧客が言ったことから正規化されたエントリを抽出するためにモデルに依頼します。その種類は2つです。
- 事実 — 顧客に関する永続的な声明。「4台の車両からなる fleet を運営している」「更新は5月だ」「WhatsAppを好む」
- トピック — 関係が繰り返し戻ってくる再発する主題で、それぞれトピック名を持つ。
各エントリには重要度スコアが含まれており、スペースが限られている場合でも、軽い一言と厳格な制約が同等に扱われることはありません。
会話の顧客側の半分のみ
抽出は顧客が言ったことを読み取ります。エージェント自身の返信は、顧客に関する事実となる資格がなく、これは聞こえる以上に重要です。
一度推測を行うエージェント——この顧客の会社が何を作っているかについての文を、1つの返信での推測として提示する——は、そうでなければその推測が永続的な事実として抽出され、その後のすべてのターンで前提として読み返されます。それ以降、それは推測ではなく、エージェントが知っていることとなります。顧客は原因を一切見ずに結果だけを見ます:間違った事業についてずっと答え続けるが、画面にはその理由を示すものは何もない。
防御は2つあります。1つ目はリクエストであり、2つ目はチェックです。抽出は顧客側の半分のみが表示されます。その後、何も書き込む前に、ソフトウェアが「事実」の文章に顧客が実際に言ったことの根拠があるかを確認します——これはモデルの外で、すべてのエントリ、すべてのターンで実行されるチェックです。
重複排除がゲーム全体である
顧客が5つの会話にわたって同じことを言及します。重複排除がなければ、5つのほぼ同一の記憶が蓄積され、検索時に他のすべての情報を押し退け、エージェントは自分自身を繰り返し始めます。
エントリが書き込まれる前に、プラットフォームは代わりにマージすべきエントリを探します。
- トピックはトピック名で一致します——正確に一致します。トピックはすでに正規化されたラベルであるためです。
- 事実はベクトル近傍で一致します——同じ種類の既存の最も近い記憶で、コサイン距離が設定された閾値内にある場合にのみ重複として受け入れられます。
一致した場合、既存の行は複製ではなく更新されます——コンテンツと埋め込みを更新し、重要度を両者の高い方へ引き上げ、後でより重要であることが判明した事実が、異なる重みで二度保存されるのではなく昇格されるようにします。エントリはまた、それをトリガーとした最新のセッションとメッセージを指し示すように再設定され、その鮮度がライブな関係を反映します。
バッチ埋め込み
1つのターンからしばしば複数のエントリが得られます。それらを一度に一つずつ埋め込むことは、埋め込みエンドポイントへの複数の順次ラウンドトリップを意味します——そして、そのエンドポイントは並行度が1で実行されているため、そのシーケンスは他のユーザーのクエリ埋め込みをその後にブロックします。
そのため、ターンのエントリはバッチとして埋め込まれ、ベクトルは書き込みパスに渡されます。3エントリのターンで測定:順次117 ms 対 バッチ46 ms ——顧客とその回答の間に位置するパスで2.4倍の違いです。
検索
ターンの開始時、記憶は顧客ごとにベクトル類似度により、独自の距離閾値の下で検索されます——文書検索に使用されるものよりも厳格です。部分的に関連する文書の passage は単に役に立たないだけですが、部分的に関連するある人についての「事実」は、積極的に誤りとなるためです。
検索された記憶は、検索された文書のチャンクとともにプロンプトに加わります。エージェントは、あなたが提供したドキュメントから回答し、すでに知っている人物に宛てて話します。
忘却
上記のすべてが蓄積されます。エントリはマージされ、重要度は上昇し、何も消去されません——これは記憶にとって正しいデフォルトですが、誤った記憶にとっては誤りです。
誤った記憶は顧客側からはほぼ見えません。彼らはストアを見ることができないため、誤った事実が含まれていることを知ることができません。彼々が見るのは、何かを当然のこととして扱し続けるエージェントです。会話の中で悪いエントリを識別できるのは、誤解されている本人だけで、それを行う唯一の方法は、それを言うことです。
そのため、言うことは機能します。「私たちはそれを作っていない」「それは私の会社ではない」「Xについてのあなたの知識を忘れた」。エージェントはそれを保存された内容と照合し、見つかったエントリを削除します。設定ページを開くことなく、どの言語でも。
文が記憶されたものを否定しているかどうかを判断するのは、本質的に言語の問題です——「私たちはそれをしない」は修正であり得るし、単なる事業についての声明であり得る——そのため、モデルがその判断を行います。モデルに決定を任されないのは、どれだけの損害を与えてよいかという制限であり、その制限はコード内にあります。
- リクエストあたり最大で数個のエントリのみ。1つの誤読がbounded(制限)されるようにするため。
- 実際に一致するのに十分な近さのエントリのみ。類似度検索は常に最も近い結果を返します。関連するものが何もないアカウントであってもです。距離の下限がなければ、「Xについてのあなたの知識を忘れた」という unrelated なアカウントでの発言は、たまたま第一位にランクされたものを削除してしまいます。
- 削除された内容は顧客に読み返されます。静かに削除することは、誤って記憶することと同じくらい悪く、今回はそれが正しかったかどうかを判断できる唯一の人物がすでに会話の中にいます。
削除は永久的です——顧客が忘れられるよう依頼したもののコピーを保持するアーカイブはありません。それはそのリクエストを意味のあるものとする唯一の解釈です。
スコーピングと暗号化
記憶はエンドユーザーのスペースにスコープされ、他のテナント所有のレコードと同様にデータベースレベルで強制され、スペースごとに暗号化されて保存されます。1人の顧客の履歴が別の顧客の会話に表示されることはなく、保存されたテキストはデータベースのみからは読み取れません。
なぜこれを後から付け加えるのが難しいか
顧客ごとの記憶はデータモデルの形状を変えます:エンドユーザーごとのアイデンティティ、それをスコープするためのスペース、そのスコープに鍵を合わせた暗号化、そしてターンループ内の派生ステップ。単一の共有アシスタントとして始まるシステムは、どこかにCRMフィールドを持ち、それを記憶と呼ぶ傾向があります——これが機能リストではなく会話の中で違いとして現れる理由です。
これに対するビジネス上の議論、メカニズムではなく、は Per-customer memoryにあります。