0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

こんにちは〜。
@sori883です。

本記事は「エーピーコミュニケーションズ Advent Calendar 2025」の21日目です。

はじめに

AIへのリクエストはステートレスで、過去の会話内容は全く覚えていません。
そのため、会話の継続性を持たせるには、過去の会話を外部に永続化させ、リクエストの度にコンテキストへ会話履歴を含める必要があります。
このリクエストの会話内容を永続化させ、リクエストの度にコンテキストへ会話履歴を含めることを記憶と呼びます。

記憶の永続化方法

現時点では短期記憶、長期記憶と別々に管理するのが一般的な実装かと思います。

  1. 短期記憶
  • 特徴:会話の文脈を保持
  • 用途:現在進行中の会話スレッド、直近のやり取り
  • 実装:キーバリューストアでセッションID毎に管理
  1. 長期記憶
  • 特徴:短期記憶を要約しベクトル、またはグラフで永続化
  • 用途:好みや過去の重要な会話内容の保持
  • 実装:複数の短期記憶を分析、要約して管理

長期記憶の要約には様々戦略があり、代表的なものを以下に挙げます。

  • Semantic Memory(意味記憶)
    • LLMが会話から抽出して保存しておいた情報を検索してシステムプロンプトに入れるようなもの
  • Episodic Memory(エピソード記憶)
    • 過去に成功したアクションをFew-shotプロンプティングの例としてプロンプトにいれるようなもの

下記が分かりやすいです。

AWSで記憶を実装する方法

  • Amazon Bedrock Agentsのメモリ機能
    • 短期記憶:会話履歴をセッションID毎に自動保存
    • 長期記憶:会話履歴を自動で要約
    • 検索方法:要約した会話履歴を自動でプロンプトに挿入
    • コスト:小
  • Amazon Bedrock AgentCoreのメモリ機能
    • 短期記憶:会話履歴をセッションID毎に保存
    • 長期記憶:戦略に沿った記憶を、短期記憶から非同期で要約
      • Semantic(重要な事実情報と文脈知識)
      • User preferences(ユーザーの嗜好、選択、スタイル)
      • Session summaries(会話の要約)
      • Episodic(エピソード記憶)
      • その他カスタム戦略を実装可能
    • 検索方法:セマンティック検索
    • コスト:中
  • AWSサービスを組み合わせて自前実装
    • 短期記憶:DynamoDBに保存
    • 長期記憶:DynamoDBからデータを取得し、LambdaやECSで要約
      • OpenSearch、Neptuneに保存
      • 完全な独自戦略が実装可能
    • 検索方法:ハイブリッド検索等、任意の検索方法
    • コスト:特大

多くの場合はマネージドなメモリ機能で事足りると思いますが、自前実装したいパターンもあるかと思います。
今回は、自前実装する場合のアーキテクチャについて検討してみようと思います。

記憶を実装してみる

成果物はこちらです。
生成AIで試験的に作成したプロジェクトであるため、脆弱な実装である点に注意してください。

記憶処理の仕組みをおさらい

アーキテクチャを考える前に記憶の仕組みをおさらいします。

  • AIとの会話はキーバリューで短期記憶のDBに登録する
  • 短期記憶のDBから記憶取得し、解析、チャンキング、ベクトル化やグラフ化を行い長期記憶のDBに保存する
  • 短期記憶、長期記憶のDBに保存した記憶は都度取得し、プロンプトに埋め込む

長期記憶_仕組み.jpg

アーキテクチャ

仕組みをもとにアーキテクチャを考えてみました。
因みにECSで作ってみましたが、処理時間的にはLambdaでも全然良いと思います。

  • 短期記憶DB
    • DynamoDB
  • 長期記憶DB
    • OpenSearch
    • Neptune
  • 記憶要約ワーカー
    • ECS
  • 記憶要約のトリガー
    • EventBridge
    • SQS

アーキテクチャ図にすると以下のイメージです。

長期記憶_アーキテクチャ.jpg

記憶の動作確認をする会話

分量少ないですが、下記の会話分を使用することにします。

今日、田中さんと山田さんがオフィスで新しい新時代創生プロジェクトについて話していました。
田中さんはマーケティング部で働いています。
山田さんは開発チームのリーダーです。
田中さんと山田さんは大学時代の同級生です。
プロジェクトは来月から始まる予定です。

短期記憶の保存取得

短期記憶は、AgentCoreと同じくセッションID単位で簡単に会話を管理するだけで良さそうです。
今回は簡易的にフロントエンド側でレンダリング毎に生成したセッションIDを使用して管理します。

LangChain CommunityのDynamoDBChatMessageHistoryを使って簡単に記憶保存と記憶取得をしてみようと思います。

記憶保存:セッションIDをキーにDynamoDBへ保存。

memory = DynamoDBChatMessageHistory(
    table_name=os.getenv("DYNAMODB_TABLE_NAME"),
    session_id=session_id,
    key={"session_id": session_id}
)

memory.add_messages([
    HumanMessage(content=user_prompt), 
    AIMessage(content=full_response)
])

少し整形していますが、短期記憶は以下のように原文そのまま全て保存されます。

[
  {
    "role": "human",
    "content": "今日、田中さんと山田さんがオフィスで新しい新時代創生プロジェクトについて話していました。"
  },
  {
    "role": "ai",
    "content": "ふーん、そういうプロジェクトがあるのね。まあ、彼らが意欲的に取り組んでいるんだと思うわ。\n\n新しいプロジェクトって、いつもクリエイティブで刺激的だけど、現実的な課題もあるものよね。実現に向けては適切なリソースの確保や、上長の理解を得ることが重要だと思う。\n\n私も以前、似たような経験があったわ。プロジェクトを進めるなら、計画性と柔軟性のバランスが大切だったと感じたわ。提案を丁寧に積み重ねて、着実に前に進んでいくのがコツよ。\n\nまあ、それぞれ工夫しながら頑張ってみるしかないわね。新しいことにチャレンジするのは刺激的だけど、時には面倒なこともあるわよね。頑張ってね。"
  },
  {
    "role": "human",
    "content": "田中さんはマーケティング部で働いています。"
  },
  {
    "role": "ai",
    "content": "そうですね。マーケティング部っていうと、消費者の動向分析やブランディング、広告戦略など、企業の売上や競争力を高めるための重要な役割を担っているところですね。\n\n田中さんも、日々変化するマーケットの情報を収集し、効果的なプロモーションを企画しているのかもしれません。マーケティング部は企業にとって欠かせない部門だと思いますが、常に新しい取り組みを模索しながら、スピード感を持って動く必要がありますよね。\n\n田中さんには、柔軟な発想力や情報収集力、分析力など、幅広い能力が求められているのかもしれません。経験を積んでいけば、ますます活躍の場が広がるはずです。頑張ってほしいですね。"
  },
  {
    "role": "human",
    "content": "山田さんは開発チームのリーダーです。"
  },
  {
    "role": "ai",
    "content": "そうですか、開発チームのリーダーなんですね。開発部門の責任者ということは、山田さんは技術的な専門性だけでなく、チームマネジメントの能力も必要とされているのでしょう。\n\n開発リーダーは、プロジェクトの進捗管理や、メンバーのスキル育成、コミュニケーションの取り方など、幅広い役割を担っていると思います。技術的な課題解決はもちろんのこと、チームを適切にコントロールしながら成果を上げていくことが重要ですからね。\n\n山田さんも、優れた開発能力と、メンバーを引っ張っていくリーダーシップを発揮しているのでしょう。新しいプロジェクトにも、豊富な経験を活かして、強いリーダーシップを発揮できるはずです。\n\nまあ、開発プロジェクトは予期せぬ課題も出てくるものですから、そこはしっかりと対応していく必要がありますね。でも、山田さんなら、メンバーとチームワークを発揮しながら、乗り越えていけるはずよ。期待しているわ。"
  },
  {
    "role": "human",
    "content": "田中さんと山田さんは大学時代の同級生です。"
  },
  {
    "role": "ai",
    "content": "なるほど、そうだったのね。大学からの付き合いなら、きっと仲が良いのでしょう。\n\n同級生だと、お互いの長所や短所、性格なども良く分かっているはずよね。その上で、今回の新プロジェクトに取り組もうとしているのなら、信頼関係もあるのかもしれません。\n\n大学時代からの付き合いだと、きっと意気投合しながら、お互いの長所を活かせるプロジェクトになるんじゃないでしょうか。\n\n同期同士なら、時には厳しい意見も言い合えるでしょうし、刺激し合えるでしょう。そういった切磋琢磨する関係性が、良質なアウトプットを生み出すのに役立つと思います。\n\n田中さんと山田さん、ぜひ大学時代の絆を活かしつつ、お互いの個性や能力を最大限に発揮して、素晴らしいプロジェクトを完成させてほしいですね。応援しています。"
  },
  {
    "role": "human",
    "content": "プロジェクトは来月から始まる予定です。"
  },
  {
    "role": "ai",
    "content": "そうですね。新しいプロジェクトの始動は、ワクワクする反面、準備に気を使うこともあるでしょう。\n\n前にも似たような経験があったんですが、立ち上げ時は特に綿密な計画が大切でしたね。期限や予算、リソースの確保など、細かい部分までしっかり見通しを立てておく必要がありましたから。\n\nでも同時に、状況に応じて柔軟に対応できるよう、逐次修正していくフレキシビリティも必要でしたよ。完璧な計画を立てようとしても、予期せぬことが起きるものですから。\n\n田中さんと山田さんには、その辺りのバランスが取れるといいですね。プロジェクトの成功に向けて、着実に準備を進めつつ、変化にもすばやく対応していってほしいです。期待しているわ。"
  }
]

記憶取得:セッションIDを検索しDynamoDBから取得。

# 発話の度に同じセッションIDの記憶を取得
short_term_messages = list(memory.messages)

DynamoDBから取得した短期記憶は下記のイメージでコンテキストに追加してあげることで、AIが文脈を考慮してくれるようになります。

### 短期記憶(このセッションの最近の会話)
ユーザー: 今日、田中さんと山田さんがオフィスで新しい新時代創生プロジェクトについて話していました。
あなた: ふーん、そういうプロジェクトがあるのね。まあ、彼らが意欲的に取り組んでいるんだと思うわ。
新しいプロジェクトって、いつもクリエイティブで刺激的だけど、現実的な課題もあるものよね。実現に向けては適切なリソースの確保や、上長の理解を得ることが重要だと思う。
私も以前、似たような経験があったわ。プロジェクトを進めるなら、計画性と柔軟性のバランスが大切だったと感じたわ。提案を丁寧に積み重ねて、着実に前に進んでいくのがコツよ。
まあ、それぞれ工夫しながら頑張ってみるしかないわね。新しいことにチャレンジするのは刺激的だけど、時には面倒なこともあるわよね。頑張ってね。
ユーザー: 田中さんはマーケティング部で働いています。
あなた: そうですね。マーケティング部っていうと、消費者の動向分析やブランディング、広告戦略など、企業の売上や競争力を高めるための重要な役割を担っているところですね。
田中さんも、日々変化するマーケットの情報を収集し、効果的なプロモーションを企画しているのかもしれません。マーケティング部は企業にとって欠かせない部門だと思いますが、常に新しい取り組みを模索しながら、スピード感を持って動く必要がありますよね。
田中さんには、柔軟な発想力や情報収集力、分析力など、幅広い能力が求められているのかもしれません。経験を積んでいけば、ますます活躍の場が広がるはずです。頑張ってほしいですね。
ユーザー: 山田さんは開発チームのリーダーです。
あなた: そうですか、開発チームのリーダーなんですね。開発部門の責任者ということは、山田さんは技術的な専門性だけでなく、チームマネジメントの能力も必要とされているのでしょう。
開発リーダーは、プロジェクトの進捗管理や、メンバーのスキル育成、コミュニケーションの取り方など、幅広い役割を担っていると思います。技術的な課題解決はもちろんのこと、チームを適切にコントロールしながら成果を上げていくことが重要ですからね。
山田さんも、優れた開発能力と、メンバーを引っ張っていくリーダーシップを発揮しているのでしょう。新しいプロジェクトにも、豊富な経験を活かして、強いリーダーシップを発揮できるはずです。
まあ、開発プロジェクトは予期せぬ課題も出てくるものですから、そこはしっかりと対応していく必要がありますね。でも、山田さんなら、メンバーとチームワークを発揮しながら、乗り越えていけるはずよ。期待しているわ。
ユーザー: 田中さんと山田さんは大学時代の同級生です。
あなた: なるほど、そうだったのね。大学からの付き合いなら、きっと仲が良いのでしょう。
同級生だと、お互いの長所や短所、性格なども良く分かっているはずよね。その上で、今回の新プロジェクトに取り組もうとしているのなら、信頼関係もあるのかもしれません。
大学時代からの付き合いだと、きっと意気投合しながら、お互いの長所を活かせるプロジェクトになるんじゃないでしょうか。
同期同士なら、時には厳しい意見も言い合えるでしょうし、刺激し合えるでしょう。そういった切磋琢磨する関係性が、良質なアウトプットを生み出すのに役立つと思います。
田中さんと山田さん、ぜひ大学時代の絆を活かしつつ、お互いの個性や能力を最大限に発揮して、素晴らしいプロジェクトを完成させてほしいですね。応援しています。

長期記憶の保存

DynamoDBに保存した会話履歴をEventBridgeで定期的に読み取り、長期記憶として使える形に要約していきます。

ベクトルDB

短期記憶を受け取り、要約(チャンキング、ベクトル化)してOpenSearchへ保存します。

チャンキング:短期記憶を会話ペア毎に分割。

def chunking(history: list[dict[str, Any]]) -> list[dict[str, str]]:
    pairs = []
    i = 0

    while i < len(history):
        if history[i]["type"] == "human":
            human_content = history[i]["content"]

            if i + 1 < len(history) and history[i + 1]["type"] == "ai":
                ai_content = history[i + 1]["content"]
                pairs.append({
                    'human': human_content,
                    'ai': ai_content
                })
                i += 2
            else:
                pairs.append({
                    'human': human_content,
                    'ai': ''
                })
                i += 1
        else:
            i += 1

    return pairs

チャンキングした後の結果が以下です。
チャンク毎にベクトル化するので5回Embeddingして、OpenSearchに5レコード登録します。

[
  {
    "human": "今日、田中さんと山田さんがオフィスで新しい新時代創生プロジェクトについて話していました。",
    "ai": "ふーん、そういうプロジェクトがあるのね。まあ、彼らが意欲的に取り組んでいるんだと思うわ。\n\n新しいプロジェクトって、いつもクリエイティブで刺激的だけど、現実的な課題もあるものよね。実現に向けては適切なリソースの確保や、上長の理解を得ることが重要だと思う。\n\n私も以前、似たような経験があったわ。プロジェクトを進めるなら、計画性と柔軟性のバランスが大切だったと感じたわ。提案を丁寧に積み重ねて、着実に前に進んでいくのがコツよ。\n\nまあ、それぞれ工夫しながら頑張ってみるしかないわね。新しいことにチャレンジするのは刺激的だけど、時には面倒なこともあるわよね。頑張ってね。"
  },
  {
    "human": "田中さんはマーケティング部で働いています。",
    "ai": "そうですね。マーケティング部っていうと、消費者の動向分析やブランディング、広告戦略など、企業の売上や競争力を高めるための重要な役割を担っているところですね。\n\n田中さんも、日々変化するマーケットの情報を収集し、効果的なプロモーションを企画しているのかもしれません。マーケティング部は企業にとって欠かせない部門だと思いますが、常に新しい取り組みを模索しながら、スピード感を持って動く必要がありますよね。\n\n田中さんには、柔軟な発想力や情報収集力、分析力など、幅広い能力が求められているのかもしれません。経験を積んでいけば、ますます活躍の場が広がるはずです。頑張ってほしいですね。"
  },
  {
    "human": "山田さんは開発チームのリーダーです。",
    "ai": "そうですか、開発チームのリーダーなんですね。開発部門の責任者ということは、山田さんは技術的な専門性だけでなく、チームマネジメントの能力も必要とされているのでしょう。\n\n開発リーダーは、プロジェクトの進捗管理や、メンバーのスキル育成、コミュニケーションの取り方など、幅広い役割を担っていると思います。技術的な課題解決はもちろんのこと、チームを適切にコントロールしながら成果を上げていくことが重要ですからね。\n\n山田さんも、優れた開発能力と、メンバーを引っ張っていくリーダーシップを発揮しているのでしょう。新しいプロジェクトにも、豊富な経験を活かして、強いリーダーシップを発揮できるはずです。\n\nまあ、開発プロジェクトは予期せぬ課題も出てくるものですから、そこはしっかりと対応していく必要がありますね。でも、山田さんなら、メンバーとチームワークを発揮しながら、乗り越えていけるはずよ。期待しているわ。"
  },
  {
    "human": "田中さんと山田さんは大学時代の同級生です。",
    "ai": "なるほど、そうだったのね。大学からの付き合いなら、きっと仲が良いのでしょう。\n\n同級生だと、お互いの長所や短所、性格なども良く分かっているはずよね。その上で、今回の新プロジェクトに取り組もうとしているのなら、信頼関係もあるのかもしれません。\n\n大学時代からの付き合いだと、きっと意気投合しながら、お互いの長所を活かせるプロジェクトになるんじゃないでしょうか。\n\n同期同士なら、時には厳しい意見も言い合えるでしょうし、刺激し合えるでしょう。そういった切磋琢磨する関係性が、良質なアウトプットを生み出すのに役立つと思います。\n\n田中さんと山田さん、ぜひ大学時代の絆を活かしつつ、お互いの個性や能力を最大限に発揮して、素晴らしいプロジェクトを完成させてほしいですね。応援しています。"
  },
  {
    "human": "プロジェクトは来月から始まる予定です。",
    "ai": "そうですね。新しいプロジェクトの始動は、ワクワクする反面、準備に気を使うこともあるでしょう。\n\n前にも似たような経験があったんですが、立ち上げ時は特に綿密な計画が大切でしたね。期限や予算、リソースの確保など、細かい部分までしっかり見通しを立てておく必要がありましたから。\n\nでも同時に、状況に応じて柔軟に対応できるよう、逐次修正していくフレキシビリティも必要でしたよ。完璧な計画を立てようとしても、予期せぬことが起きるものですから。\n\n田中さんと山田さんには、その辺りのバランスが取れるといいですね。プロジェクトの成功に向けて、着実に準備を進めつつ、変化にもすばやく対応していってほしいです。期待しているわ。"
  }
]

記憶保存:チャンキングしたテキストをベクトル化して、OpenSearchに保存します。
LangChain CommunityのOpenSearchVectorSearchを使って簡単にベクトル化、保存をしてみます。

# Embeddings初期化
self.embeddings = BedrockEmbeddings(
    model_id="amazon.titan-embed-text-v2:0",
    region_name="ap-northeast-1"
)

# OpenSearch VectorStore初期化
self.vector_store = OpenSearchVectorSearch(
    opensearch_url=opensearch_url,
    index_name=index,
    embedding_function=self.embeddings,
    engine="nmslib",
    space_type="cosinesimil",
    ef_construction=512,
    m=16
)

# チャンクテキストを渡して保存
self.vector_store.add_texts(
    texts=[chunk_text]
)

OpenSearch DashbordsでOpenSearchにチャンクとそれに対するベクトルが下記の通り登録されます。

"_source": {
  "vector_field": [
    -0.015861526131629944,
    0.10692664980888367,
    -0.026177063584327698,
    0.053463324904441833,
    .... 超絶長いベクトルフィールド
  ],
  "text": """Human: 田中さんはマーケティング部で働いています。
AI: そうですね。マーケティング部っていうと、消費者の動向分析やブランディング、広告戦略など、企業の売上や競争力を高めるための重要な役割を担っているところですね。

田中さんも、日々変化するマーケットの情報を収集し、効果的なプロモーションを企画しているのかもしれません。マーケティング部は企業にとって欠かせない部門だと思いますが、常に新しい取り組みを模索しながら、スピード感を持って動く必要がありますよね。

田中さんには、柔軟な発想力や情報収集力、分析力など、幅広い能力が求められているのかもしれません。経験を積んでいけば、ますます活躍の場が広がるはずです。頑張ってほしいですね。""",

グラフDB

次にグラフDBです。
こちらも短期記憶を受け取り、要約(チャンキング、グラフ化)してNeptuneDBへ保存します。

チャンキング:短期記憶を会話ペア毎に分割。
ベクトルDBの例と同じように会話毎に分割します。

記憶保存:チャンキングしたテキストから関心グラフを生成し保存します。
LangChainのLLMGraphTransformerが動かなかったので下記を参考に実装してます。

# LLMでチャンクからノードとリレーションを抽出
structured_llm = self.llm.with_structured_output(GraphData) # pydanticでLLM出力形式を指定
chain = self.prompt | structured_llm
graph_data = chain.invoke({"text": text})


# ノードとリレーションをLangChainのGraphDocument形式に整形
# ノードを作成
nodes_dict = {node_dict["id"]: Node(id=node_dict["id"], type=node_dict["type"])
              for node_dict in graph_data.nodes}

# リレーションを作成
for rel_dict in graph_data.relationships:
    source_id = rel_dict["source"]
    target_id = rel_dict["target"]

    if source_id not in nodes_dict:
        nodes_dict[source_id] = Node(id=source_id, type="Unknown")
    if target_id not in nodes_dict:
        nodes_dict[target_id] = Node(id=target_id, type="Unknown")

    relationships.append(
        Relationship(
            source=nodes_dict[source_id],
            target=nodes_dict[target_id],
            type=rel_dict["type"]
        )
    )
GraphDocument(
    nodes=list(nodes_dict.values()),
    relationships=relationships,
    source=Document(page_content=text)
)

# Neptuneに接続
self.graph = NeptuneGraph(neptune_endpoint=neptune_endpoint)
# ノードを保存
self.graph.add_graph_nodes(
    graph_doc.nodes,
)
# リレーションを保存
self.graph.add_graph_relationships(graph_doc.relationships)

ノードやリレーションは下記の形で抽出され、リレーションが組まれていることが確認できます。

# ノード
[
  Node(
    id='田中さん', type='人物'
  ),
  Node(
    id='山田さん', type='人物'
  ),
  Node(
    id='新時代創生プロジェクト', type='プロジェクト'
  ),
  Node(
    id='私', type='人物'), Node(id='上長', type='人物'
  )
]

# リレーション
[
  Relationship(
    source=Node(id='田中さん', type='人物'),
    target=Node(id='山田さん', type='人物'),
    type='話し合いの関係'
  ),
  Relationship(
    source=Node(id='田中さん', type='人物'),
    target=Node(id='新時代創生プロジェクト', type='プロジェクト'),
    type='参加関係'
  ),
  Relationship(
    source=Node(id='山田さん', type='人物'),
    target=Node(id='新時代創生プロジェクト', type='プロジェクト'),
    type='参加関係'
  ),
  Relationship(
    source=Node(id='私', type='人物'),
    target=Node(id='新時代創生プロジェクト', type='プロジェクト'),
    type='過去の経験'
  ),
  Relationship(
    source=Node(id='上長', type='人物'),
    target=Node(id='新時代創生プロジェクト', type='プロジェクト'),
    type='理解関係'
  )
]

因み、グラフを可視化するとこんな感じ。なるほど分からん。

長期記憶_グラフ.jpg

長期記憶の取得

次に保存した長期記憶を取得し、LLM回答時に文脈として注入してみます。
短期記憶と同様に、以下の文言を適当に質問してみます。

田中と山田について教えて下さい。関係性やステータスについて。
プロジェクトについて教えて下さい。担当者やそれぞれの立場について。

ベクトルDB

記憶検索:ユーザーの質問をベクトル化して、長期記憶をベクトル検索します。
保存時と同様にLangChain CommunityのOpenSearchVectorSearchを使って簡単にベクトル化して検索してみます。

# Embeddings初期化
self.embeddings = BedrockEmbeddings(
    model_id="amazon.titan-embed-text-v2:0",
    region_name="ap-northeast-1"
)

# OpenSearch VectorStore初期化
self.vector_store = OpenSearchVectorSearch(
    opensearch_url=opensearch_url,
    index_name=index,
    embedding_function=self.embeddings,
    engine="nmslib",
    space_type="cosinesimil",
    ef_construction=512,
    m=16
)

# ベクトルでベクトルDBを検索
self.vector_store.similarity_search_with_score(
    query=query,
    k=k
)

ベクトルDBから取得した記憶top-3は、以下の通りプロンプトに追加しておきます。

### 長期記憶(過去の関連する会話)
#### 意味的に関連する過去の会話(ベクトル検索)
[記憶V1] (類似度: 0.50)
Human: 田中さんと山田さんは大学時代の同級生です。
AI: なるほど、そうだったのね。大学からの付き合いなら、きっと仲が良いのでしょう。
同級生だと、お互いの長所や短所、性格なども良く分かっているはずよね。その上で、今回の新プロジェクトに取り組もうとしているのなら、信頼関係もあるのかもしれません。
大学時代からの付き合いだと、きっと意気投合しながら、お互いの長所を活かせるプロジェクトになるんじゃないでしょうか。
同期同士なら、時には厳しい意見も言い合えるでしょうし、刺激し合えるでしょう。そういった切磋琢磨する関係性が、良質なアウトプットを生み出すのに役立つと思います。
田中さんと山田さん、ぜひ大学時代の絆を活かしつつ、お互いの個性や能力を最大限に発揮して、素晴らしいプロジェクトを完成させてほしいですね。応援しています。
[記憶V2] (類似度: 0.50)
Human: 田中さんと山田さんは大学時代の同級生です。
AI: なるほど、そうだったのね。大学からの付き合いなら、きっと仲が良いのでしょう。
同級生だと、お互いの長所や短所、性格なども良く分かっているはずよね。その上で、今回の新プロジェクトに取り組もうとしているのなら、信頼関係もあるのかもしれません。
大学時代からの付き合いだと、きっと意気投合しながら、お互いの長所を活かせるプロジェクトになるんじゃないでしょうか。
同期同士なら、時には厳しい意見も言い合えるでしょうし、刺激し合えるでしょう。そういった切磋琢磨する関係性が、良質なアウトプットを生み出すのに役立つと思います。
田中さんと山田さん、ぜひ大学時代の絆を活かしつつ、お互いの個性や能力を最大限に発揮して、素晴らしいプロジェクトを完成させてほしいですね。応援しています。
[記憶V3] (類似度: 0.40)
Human: 山田さんは開発チームのリーダーです。
AI: そうですか、開発チームのリーダーなんですね。開発部門の責任者ということは、山田さんは技術的な専門性だけでなく、チームマネジメントの能力も必要とされているのでしょう。
開発リーダーは、プロジェクトの進捗管理や、メンバーのスキル育成、コミュニケーションの取り方など、幅広い役割を担っていると思います。技術的な課題解決はもちろんのこと、チームを適切にコントロールしながら成果を上げていくことが重要ですからね。
山田さんも、優れた開発能力と、メンバーを引っ張っていくリーダーシップを発揮しているのでしょう。新しいプロジェクトにも、豊富な経験を活かして、強いリーダーシップを発揮できるはずです。
まあ、開発プロジェクトは予期せぬ課題も出てくるものですから、そこはしっかりと対応していく必要がありますね。でも、山田さんなら、メンバーとチームワークを発揮しながら、乗り越えていけるはずよ。期待しているわ。

グラフDB

記憶検索:ユーザーの質問からノードとリレーションを抽出し、長期記憶を検索します。

# 保存時と同様に、LLMで質問からノードとリレーションを抽出
structured_llm = self.llm.with_structured_output(QueryGraphData)
messages = [
    SystemMessage(content=system_prompt),
    HumanMessage(content=user_prompt)
]
graph_data = structured_llm.invoke(messages)


# ノードからグラフDB検索
graph.query(f"""
        g.V()
          .has('name', '{graph_data.entity_name_esc}') # ノード名
          .limit(5)
          .project('name', 'type', 'memory_id', 'timestamp', 'outEdges', 'inEdges')
          .by('name')
          .by(label)
          .by(coalesce(values('memory_id'), constant('')))
          .by(coalesce(values('timestamp'), constant('')))
          .by(outE().label().fold())
          .by(inE().label().fold())
        """)

# リレーションからノードをグラフDB検索
graph.query(f"""
        g.V()
          .has('name', '{source_esc}').as('source') # リレーションの始点名
          .outE('{rel_type_esc}') # リレーションタイプ(
          .inV()
          .has('name', '{target_esc}').as('target') # リレーションの終点名
          .select('source', 'target')
          .by(coalesce(values('timestamp'), constant('')))
          .limit(5)
        """)

# 上記の検索結果ノードから関連する記憶を取得
graph.query(f"""
        g.V()
          .has('{entity_type_esc}', 'id', '{entity_name_esc}') # ノード名、タイプ
          .out('抽出元')
          .hasLabel('Memory')
          .project('conversation_text', 'memory_id', 'timestamp')
          .by(coalesce(values('conversation_text'), constant('')))
          .by(coalesce(values('memory_id'), constant('')))
          .by(coalesce(values('timestamp'), constant('')))
        """)

グラフDBから取得した記憶も、以下の通りプロンプトに追加しておきます。

#### キーワードで一致する過去の会話(グラフ検索)
[記憶G1] (一致度: 1.00)
Human: 今日、田中さんと山田さんがオフィスで新しい新時代創生プロジェクトについて話していました。
AI: ふーん、そういうプロジェクトがあるのね。まあ、彼らが意欲的に取り組んでいるんだと思うわ。
新しいプロジェクトって、いつもクリエイティブで刺激的だけど、現実的な課題もあるものよね。実現に向けては適切なリソースの確保や、上長の理解を得ることが重要だと思う。
私も以前、似たような経験があったわ。プロジェクトを進めるなら、計画性と柔軟性のバランスが大切だったと感じたわ。提案を丁寧に積み重ねて、着実に前に進んでいくのがコツよ。
まあ、それぞれ工夫しながら頑張ってみるしかないわね。新しいことにチャレンジするのは刺激的だけど、時には面倒なこともあるわよね。頑張ってね。
[記憶G2] (一致度: 1.00)
Human: 田中さんはマーケティング部で働いています。
AI: そうですね。マーケティング部っていうと、消費者の動向分析やブランディング、広告戦略など、企業の売上や競争力を高めるための重要な役割を担っているところですね。
田中さんも、日々変化するマーケットの情報を収集し、効果的なプロモーションを企画しているのかもしれません。マーケティング部は企業にとって欠かせない部門だと思いますが、常に新しい取り組みを模索しながら、スピード感を持って動く必要がありますよね。
田中さんには、柔軟な発想力や情報収集力、分析力など、幅広い能力が求められているのかもしれません。経験を積んでいけば、ますます活躍の場が広がるはずです。頑張ってほしいですね。
[記憶G3] (一致度: 1.00)
Human: 山田さんは開発チームのリーダーです。
AI: そうですか、開発チームのリーダーなんですね。開発部門の責任者ということは、山田さんは技術的な専門性だけでなく、チームマネジメントの能力も必要とされているのでしょう。
開発リーダーは、プロジェクトの進捗管理や、メンバーのスキル育成、コミュニケーションの取り方など、幅広い役割を担っていると思います。技術的な課題解決はもちろんのこと、チームを適切にコントロールしながら成果を上げていくことが重要ですからね。
山田さんも、優れた開発能力と、メンバーを引っ張っていくリーダーシップを発揮しているのでしょう。新しいプロジェクトにも、豊富な経験を活かして、強いリーダーシップを発揮できるはずです。
まあ、開発プロジェクトは予期せぬ課題も出てくるものですから、そこはしっかりと対応していく必要がありますね。でも、山田さんなら、メンバーとチームワークを発揮しながら、乗り越えていけるはずよ。期待しているわ。

最終的なプロンプトコンテキスト

最終的に短期記憶、長期記憶、システムプロンプトが合体されて回答生成AIに提供します。
ただ単に記憶を渡すだけでは、「[短期記憶1:]ではこんなことを言っていましたね」と言い出すので、記憶の使い方もシステムプロンプトで明記してあげましょう。

## 記憶の活用について
以下の記憶情報を活用して会話してください:

### 短期記憶(このセッションの最近の会話)
ユーザー: 今日、田中さんと山田さんがオフィスで新しい新時代創生プロジェクトについて話していました。
あなた: ふーん、そういうプロジェクトがあるのね。まあ、彼らが意欲的に取り組んでいるんだと思うわ。
新しいプロジェクトって、いつもクリエイティブで刺激的だけど、現実的な課題もあるものよね。実現に向けては適切なリソースの確保や、上長の理解を得ることが重要だと思う。
私も以前、似たような経験があったわ。プロジェクトを進めるなら、計画性と柔軟性のバランスが大切だったと感じたわ。提案を丁寧に積み重ねて、着実に前に進んでいくのがコツよ。
まあ、それぞれ工夫しながら頑張ってみるしかないわね。新しいことにチャレンジするのは刺激的だけど、時には面倒なこともあるわよね。頑張ってね。
ユーザー: 田中さんはマーケティング部で働いています。
あなた: そうですね。マーケティング部っていうと、消費者の動向分析やブランディング、広告戦略など、企業の売上や競争力を高めるための重要な役割を担っているところですね。
田中さんも、日々変化するマーケットの情報を収集し、効果的なプロモーションを企画しているのかもしれません。マーケティング部は企業にとって欠かせない部門だと思いますが、常に新しい取り組みを模索しながら、スピード感を持って動く必要がありますよね。
田中さんには、柔軟な発想力や情報収集力、分析力など、幅広い能力が求められているのかもしれません。経験を積んでいけば、ますます活躍の場が広がるはずです。頑張ってほしいですね。
ユーザー: 山田さんは開発チームのリーダーです。
あなた: そうですか、開発チームのリーダーなんですね。開発部門の責任者ということは、山田さんは技術的な専門性だけでなく、チームマネジメントの能力も必要とされているのでしょう。
開発リーダーは、プロジェクトの進捗管理や、メンバーのスキル育成、コミュニケーションの取り方など、幅広い役割を担っていると思います。技術的な課題解決はもちろんのこと、チームを適切にコントロールしながら成果を上げていくことが重要ですからね。
山田さんも、優れた開発能力と、メンバーを引っ張っていくリーダーシップを発揮しているのでしょう。新しいプロジェクトにも、豊富な経験を活かして、強いリーダーシップを発揮できるはずです。
まあ、開発プロジェクトは予期せぬ課題も出てくるものですから、そこはしっかりと対応していく必要がありますね。でも、山田さんなら、メンバーとチームワークを発揮しながら、乗り越えていけるはずよ。期待しているわ。
ユーザー: 田中さんと山田さんは大学時代の同級生です。
あなた: なるほど、そうだったのね。大学からの付き合いなら、きっと仲が良いのでしょう。
同級生だと、お互いの長所や短所、性格なども良く分かっているはずよね。その上で、今回の新プロジェクトに取り組もうとしているのなら、信頼関係もあるのかもしれません。
大学時代からの付き合いだと、きっと意気投合しながら、お互いの長所を活かせるプロジェクトになるんじゃないでしょうか。
同期同士なら、時には厳しい意見も言い合えるでしょうし、刺激し合えるでしょう。そういった切磋琢磨する関係性が、良質なアウトプットを生み出すのに役立つと思います。
田中さんと山田さん、ぜひ大学時代の絆を活かしつつ、お互いの個性や能力を最大限に発揮して、素晴らしいプロジェクトを完成させてほしいですね。応援しています。
### 長期記憶(過去の関連する会話)
#### 意味的に関連する過去の会話(ベクトル検索)
[記憶V1] (類似度: 0.50)
Human: 田中さんと山田さんは大学時代の同級生です。
AI: なるほど、そうだったのね。大学からの付き合いなら、きっと仲が良いのでしょう。
同級生だと、お互いの長所や短所、性格なども良く分かっているはずよね。その上で、今回の新プロジェクトに取り組もうとしているのなら、信頼関係もあるのかもしれません。
大学時代からの付き合いだと、きっと意気投合しながら、お互いの長所を活かせるプロジェクトになるんじゃないでしょうか。
同期同士なら、時には厳しい意見も言い合えるでしょうし、刺激し合えるでしょう。そういった切磋琢磨する関係性が、良質なアウトプットを生み出すのに役立つと思います。
田中さんと山田さん、ぜひ大学時代の絆を活かしつつ、お互いの個性や能力を最大限に発揮して、素晴らしいプロジェクトを完成させてほしいですね。応援しています。
[記憶V2] (類似度: 0.50)
Human: 田中さんと山田さんは大学時代の同級生です。
AI: なるほど、そうだったのね。大学からの付き合いなら、きっと仲が良いのでしょう。
同級生だと、お互いの長所や短所、性格なども良く分かっているはずよね。その上で、今回の新プロジェクトに取り組もうとしているのなら、信頼関係もあるのかもしれません。
大学時代からの付き合いだと、きっと意気投合しながら、お互いの長所を活かせるプロジェクトになるんじゃないでしょうか。
同期同士なら、時には厳しい意見も言い合えるでしょうし、刺激し合えるでしょう。そういった切磋琢磨する関係性が、良質なアウトプットを生み出すのに役立つと思います。
田中さんと山田さん、ぜひ大学時代の絆を活かしつつ、お互いの個性や能力を最大限に発揮して、素晴らしいプロジェクトを完成させてほしいですね。応援しています。
[記憶V3] (類似度: 0.40)
Human: 山田さんは開発チームのリーダーです。
AI: そうですか、開発チームのリーダーなんですね。開発部門の責任者ということは、山田さんは技術的な専門性だけでなく、チームマネジメントの能力も必要とされているのでしょう。
開発リーダーは、プロジェクトの進捗管理や、メンバーのスキル育成、コミュニケーションの取り方など、幅広い役割を担っていると思います。技術的な課題解決はもちろんのこと、チームを適切にコントロールしながら成果を上げていくことが重要ですからね。
山田さんも、優れた開発能力と、メンバーを引っ張っていくリーダーシップを発揮しているのでしょう。新しいプロジェクトにも、豊富な経験を活かして、強いリーダーシップを発揮できるはずです。
まあ、開発プロジェクトは予期せぬ課題も出てくるものですから、そこはしっかりと対応していく必要がありますね。でも、山田さんなら、メンバーとチームワークを発揮しながら、乗り越えていけるはずよ。期待しているわ。

## 短期記憶、長期記憶を使う際の注意点
- 記憶を自然な会話の中で参照すること
- 記憶をそのままの言葉でユーザーに伝えないこと
- 「[過去の会話 1]」のような形式で列挙しないこと
  - 絶対に行っては行けない例:「[過去の会話 1] Human: ...」
  - 正しい例:「前にそんな話してたわね」
- 記憶にない内容は正直に「覚えてない」と伝える

記憶を自前で実装する時の注意点

自前実装は完全にコントロールできる反面、AWSコストがかなり大きいです。
また、記憶の設計には明確な王道がなく、AWSコストと回答精度を天秤にかけながらの調整となるためかなり辛みを感じました。

  • DBがとにかく高額
    • DynamoDB
    • OpenSearch
    • NeptuneDB
  • コンピューティングも必要
    • ECS
    • Lambda
  • 長期記憶の要約にLLMコストも必要
    • ベクトルの抽出
    • ノードやリレーションの抽出
    • 会話の要約単位

終わりに

よほど目的がない限りAgentCoreで良い。
また、ユーザーとの会話履歴を保持するだけなら細かくチャンキングしてOpenSearchからハイブリッド検索するのがコスパ良いのかと思います。
そもそもグラフDBを設計、実装出来るノウハウも少ない気がしていて、会話履歴のためだけに安易に導入しないほうが良いかもと思いました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?