Skip to content

Conversation

@lqvp
Copy link
Owner

@lqvp lqvp commented Jul 19, 2025

Add user-specific memory and personalization features to the aichat module.

This PR introduces a comprehensive memory and personalization system, allowing the AI to remember past conversations, learn user preferences, and adapt its responses for a more personalized and engaging chat experience. Key components include an LLM-based analyzer for conversation insights, a smart memory manager for storing and retrieving user-specific information, an adaptive personalization engine to tailor response styles, and a conversation manager to track flow and context. Users can interact with new commands to manage their memories and view their profile.

Summary by CodeRabbit

新機能

  • AIチャットにユーザーごとの記憶とパーソナライズ機能を追加し、会話内容や個人の好みに基づいた応答が可能になりました。
  • 会話分析、記憶管理、パーソナライズ、会話状態管理の各機能が統合され、より文脈に沿った自然な対話を実現します。
  • ユーザーは記憶の表示・検索・リセットやプロフィールの確認、記憶統計の閲覧などのコマンドが利用できます。
  • 30日以上前の古い会話や記憶は自動でクリーンアップされます。

ドキュメント

  • 記憶・パーソナライズ機能の詳細なREADMEを追加しました。

Co-authored-by: rei.mizushima <rei.mizushima@go.sfcollege.edu>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 19, 2025

Warning

Rate limit exceeded

@lqvp has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 25 minutes and 8 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between e0c6863 and d0bb207.

📒 Files selected for processing (3)
  • src/modules/aichat/index.ts (16 hunks)
  • src/modules/aichat/memory/LLMAnalyzer.ts (1 hunks)
  • src/modules/aichat/memory/SmartMemoryManager.ts (1 hunks)

Walkthrough

AIチャットモジュールに高度なメモリ・パーソナライズ機能を統合しました。会話分析、メモリ管理、パーソナライズ、会話状態管理の各クラスを追加し、ユーザーごとのメモリとプロファイル管理を実現。コマンドや定期的なクリーンアップ、型定義、READMEも新規追加されています。

Changes

ファイル/グループ 変更概要
src/modules/aichat/index.ts メモリ・パーソナライズ統合、型拡張、会話分析・メモリ取得・プロファイル反映・クリーニング処理追加、コマンド拡充
src/modules/aichat/memory/AdaptivePersonalization.ts ユーザー特性・会話分析に基づくパーソナライズ応答スタイル生成クラス新規追加
src/modules/aichat/memory/ConversationManager.ts 会話状態管理・話題遷移・感情トレンド・未解決質問追跡等のクラス新規追加
src/modules/aichat/memory/LLMAnalyzer.ts Gemini APIを用いた多角的会話分析クラス新規追加
src/modules/aichat/memory/SmartMemoryManager.ts LokiJSベースのメモリ保存・検索・関連メモリ抽出・プロファイル管理クラス新規追加
src/modules/aichat/memory/types.ts 会話分析・ユーザープロファイル・メモリエントリ等の型定義新規追加
src/modules/aichat/memory/index.ts メモリ関連各モジュールのエクスポート集約
src/modules/aichat/memory/README.md メモリ・パーソナライズ機能の詳細仕様・利用方法ドキュメント新規追加

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant AIChat
    participant LLMAnalyzer
    participant MemoryManager
    participant Personalization
    participant ConversationManager

    User->>AIChat: メッセージ送信
    AIChat->>LLMAnalyzer: 会話分析要求
    LLMAnalyzer-->>AIChat: 解析結果返却
    AIChat->>MemoryManager: 関連メモリ・プロファイル取得
    MemoryManager-->>AIChat: メモリ・プロファイル返却
    AIChat->>Personalization: パーソナライズスタイル生成
    Personalization-->>AIChat: スタイル・プロンプト返却
    AIChat->>ConversationManager: 会話状態更新
    ConversationManager-->>AIChat: 状態・サマリ返却
    AIChat->>AIChat: 応答生成用プロンプト組み立て
    AIChat-->>User: パーソナライズ応答返却
    AIChat->>MemoryManager: 新規メモリ保存
    AIChat->>MemoryManager: プロファイル更新
Loading

Poem

うさぎの耳で覚えたよ、
君の言葉も、気持ちもそっと。
メモリの森で跳ねながら、
パーソナライズの花を咲かせる。
会話の流れも、感情の波も、
みんなで育てるAIチャット。
ぴょんぴょん進化、今日もよろしく! 🐇✨


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of Changes

Hello @lqvp, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly upgrades the Aichat module by integrating a sophisticated memory and personalization system. The AI can now remember past interactions, understand user preferences, and adapt its communication style accordingly, leading to a more personalized and engaging chat experience. This is achieved through new LLM-powered components for analysis, memory management, personalization, and conversation tracking.

Highlights

  • Comprehensive Memory System: Introduced an LLM-based memory system that analyzes conversations, saves important information, and retrieves relevant past memories to enhance AI responses. This includes automatic memory consolidation for efficient storage management.
  • Adaptive Personalization Engine: Implemented a personalization engine that learns user preferences and personality traits (e.g., openness, conscientiousness, formality, humor) to dynamically adjust the AI's response style, tone, length, and even emoji usage.
  • Conversation Flow Management: Added a conversation manager to track ongoing conversation states, including topic history, emotional journey, and unresolved questions, ensuring more consistent and context-aware interactions.
  • New User Commands: Provided users with new commands to interact with their memories and profiles, such as viewing recent memories, checking their personalized profile summary, searching for specific memories, seeing memory statistics, and resetting their memories.
  • Automated Data Cleanup: Set up a daily task to automatically clean up old conversation history and conversation states (older than 30 days) to manage database size and ensure data relevance.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The code changes introduce user-specific memory and personalization features to the aichat module, enhancing the AI's ability to remember past conversations and tailor responses. The implementation is well-structured with distinct classes for analysis, memory management, personalization, and conversation management. This review suggests improvements in type safety, efficiency, maintainability, and correctness to refine the already strong implementation.

if (aiChat.history && aiChat.history.length > 0) {
// AiChatオブジェクトにuserIdを追加する必要があるため、
// 呼び出し元でuserIdを設定するように変更する
userId = (aiChat as any).userId || aiChat.friendName;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The use of (aiChat as any) bypasses TypeScript's type safety and is unnecessary here. The userId property is already defined as an optional string on the AiChat type, so you can access it directly with aiChat.userId.

Additionally, relying on aiChat.friendName as a fallback for a user identifier could be risky if friend names are not guaranteed to be unique. This could potentially lead to memory data being associated with the wrong user.

Suggested change
userId = (aiChat as any).userId || aiChat.friendName;
userId = aiChat.userId || aiChat.friendName;

Comment on lines +286 to +289
if (userReaction.includes('絵文字') && userReaction.includes('多い')) {
profile.communicationStyle.emojiUsage = 'rarely';
} else if (userReaction.includes('絵文字') && userReaction.includes('少ない')) {
profile.communicationStyle.emojiUsage = 'often';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This part of the function modifies the profile argument directly (profile.communicationStyle.emojiUsage = ...). This is a side effect that can lead to unexpected behavior and makes the code harder to reason about. A function should ideally not mutate its arguments.

Instead, this change should be added to the adjustments object that is returned. This will likely require changing the type of adjustments and the function's return signature to accommodate changes to communicationStyle.

Comment on lines +244 to +246
setInterval(() => {
this.cleanupOldMemories();
}, 24 * 60 * 60 * 1000);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using magic numbers like 24 * 60 * 60 * 1000 for time intervals can make the code harder to read and maintain. It's a good practice to define them as named constants at the top of the file or in a shared constants file.

Comment on lines +1037 to +1058
if (msg.text.match(/記憶を検索[::]\s*(.+)/)) {
const match = msg.text.match(/記憶を検索[::]\s*(.+)/);
if (match) {
const searchQuery = match[1];
const memories = await this.memoryManager.searchMemories({
userId: msg.userId,
query: searchQuery,
limit: 5,
});

if (memories.entries.length === 0) {
msg.reply(`「${searchQuery}」に関する記憶は見つかりませんでした。`);
} else {
let response = `「${searchQuery}」に関する記憶:\n`;
memories.entries.forEach((memory, i) => {
response += `${i + 1}. ${memory.summary} (${new Date(memory.metadata.createdAt).toLocaleDateString('ja-JP')})\n`;
});
msg.reply(response);
}
}
return true;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The regular expression is executed twice: once in the if condition and again inside the block to extract the captured group. You can make this more efficient and readable by performing the match only once and storing the result in a variable.

    const match = msg.text.match(/[:]\s*(.+)/);
    if (match) {
      const searchQuery = match[1];
      const memories = await this.memoryManager.searchMemories({
        userId: msg.userId,
        query: searchQuery,
        limit: 5,
      });
      
      if (memories.entries.length === 0) {
        msg.reply(`「${searchQuery}」に関する記憶は見つかりませんでした。`);
      } else {
        let response = `「${searchQuery}」に関する記憶:\n`;
        memories.entries.forEach((memory, i) => {
          response += `${i + 1}. ${memory.summary} (${new Date(memory.metadata.createdAt).toLocaleDateString('ja-JP')})\n`;
        });
        msg.reply(response);
      }
      return true;
    }

Comment on lines +1562 to +1564
oldChats.forEach(chat => {
this.aichatHist.remove(chat);
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Removing documents from the database one by one inside a forEach loop can be inefficient, especially if there's a large number of old chats to remove. LokiJS's remove() method can accept an array of documents, which is more performant.

this.aichatHist.remove(oldChats);

Comment on lines +38 to +52
if (personality.openness > 0.7) {
systemPromptAdditions.push('創造的で新しいアイデアを含めた応答を心がけてください。');
temperatureAdjustment += 0.1;
} else if (personality.openness < 0.3) {
systemPromptAdditions.push('実用的で具体的な応答を心がけてください。');
temperatureAdjustment -= 0.1;
}

// 誠実性が高い場合は詳細で構造化された応答を促す
if (personality.conscientiousness > 0.7) {
systemPromptAdditions.push('詳細で整理された情報を提供してください。');
maxTokensAdjustment += 200;
} else if (personality.conscientiousness < 0.3) {
systemPromptAdditions.push('要点を簡潔にまとめて応答してください。');
maxTokensAdjustment -= 200;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This class uses several "magic numbers" like 0.7, 0.3, 200, etc., for various thresholds and adjustments. To improve readability and make the logic easier to maintain, consider defining these values as named constants at the top of the class.

cursor[bot]

This comment was marked as outdated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

🧹 Nitpick comments (6)
src/modules/aichat/memory/types.ts (2)

4-15: 数値の範囲制約が型定義に含まれていません

confidence や各感情のスコアは 0-1 の範囲であることがコメントで示されていますが、TypeScript の型システムでは強制されません。実行時の検証が必要です。

値の範囲を明確にするため、ブランド型やユーティリティ型の使用を検討してください:

+type Percentage = number & { readonly __brand: 'percentage' };
+
 export interface EmotionAnalysis {
   sentiment: 'positive' | 'negative' | 'neutral';
-  confidence: number;
+  confidence: Percentage; // 0-1
   emotions: {
-    joy: number;
-    sadness: number;
-    anger: number;
-    fear: number;
-    surprise: number;
-    love: number;
+    joy: Percentage;
+    sadness: Percentage;
+    anger: Percentage;
+    fear: Percentage;
+    surprise: Percentage;
+    love: Percentage;
   };
 }

126-126: 配列の要素型をより明確に定義すべきです

interests フィールドは配列要素のインターフェースをインラインで定義していますが、別の型として定義した方が再利用性と可読性が向上します。

+export interface UserInterest {
+  topic: string;
+  score: number;
+  lastMentioned: Date;
+}
+
 export interface UserProfile {
   // ... other fields
-  interests: {
-    topic: string;
-    score: number;
-    lastMentioned: Date;
-  }[];
+  interests: UserInterest[];
src/modules/aichat/index.ts (1)

1078-1084: 感情傾向の集計ロジックが非効率です

配列を3回フィルタリングしていますが、1回のループで集計可能です。

-        const positiveCount = recentSentiments.filter(s => s.sentiment === 'positive').length;
-        const negativeCount = recentSentiments.filter(s => s.sentiment === 'negative').length;
-        const neutralCount = recentSentiments.filter(s => s.sentiment === 'neutral').length;
+        const sentimentCounts = recentSentiments.reduce((acc, s) => {
+          acc[s.sentiment] = (acc[s.sentiment] || 0) + 1;
+          return acc;
+        }, { positive: 0, negative: 0, neutral: 0 });
+        
+        const { positive: positiveCount, negative: negativeCount, neutral: neutralCount } = sentimentCounts;
src/modules/aichat/memory/ConversationManager.ts (2)

36-53: 不要なasyncキーワードを削除してください

このメソッドは非同期操作を含んでいないため、asyncキーワードは不要です。

  @bindThis
- async updateConversationState(
+ updateConversationState(
    context: ConversationContext
  ): Promise<ConversationState> {

134-169: messageパラメータの使用を再検討してください

現在、このメソッドはmessageパラメータを受け取っていますが、実際には使用していません。また、300行目で空文字列が渡されています。analysis.memory.summaryを使用しているので、messageパラメータは不要かもしれません。

  @bindThis
  extractKeyPoints(
-   analysis: ConversationAnalysis,
-   message: string
+   analysis: ConversationAnalysis
  ): string[] {
src/modules/aichat/memory/AdaptivePersonalization.ts (1)

255-265: 型定義を改善することを推奨します

baseConfigパラメータの型がanyになっています。より具体的な型定義を使用することで、型安全性が向上します。

+ interface GenerationConfig {
+   temperature?: number;
+   maxOutputTokens?: number;
+   [key: string]: any;
+ }
+
  @bindThis
  adjustGenerationConfig(
-   baseConfig: any,
+   baseConfig: GenerationConfig,
    style: ResponseStyle
- ): any {
+ ): GenerationConfig {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6aab9ec and e0c6863.

📒 Files selected for processing (8)
  • src/modules/aichat/index.ts (16 hunks)
  • src/modules/aichat/memory/AdaptivePersonalization.ts (1 hunks)
  • src/modules/aichat/memory/ConversationManager.ts (1 hunks)
  • src/modules/aichat/memory/LLMAnalyzer.ts (1 hunks)
  • src/modules/aichat/memory/README.md (1 hunks)
  • src/modules/aichat/memory/SmartMemoryManager.ts (1 hunks)
  • src/modules/aichat/memory/index.ts (1 hunks)
  • src/modules/aichat/memory/types.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/modules/aichat/memory/ConversationManager.ts (3)
src/modules/aichat/memory/types.ts (4)
  • ConversationState (146-165)
  • ConversationAnalysis (70-78)
  • MemoryEntry (81-102)
  • EmotionAnalysis (4-15)
src/ai.ts (1)
  • db (67-69)
src/decorators.ts (1)
  • bindThis (8-45)
src/modules/aichat/memory/AdaptivePersonalization.ts (2)
src/modules/aichat/memory/types.ts (2)
  • UserProfile (105-143)
  • ConversationAnalysis (70-78)
src/decorators.ts (1)
  • bindThis (8-45)
🔇 Additional comments (12)
src/modules/aichat/memory/index.ts (1)

1-5: モジュールの公開APIが適切に構成されています

バレルエクスポートパターンを使用して、メモリ機能のすべてのコンポーネントを一元的に公開している点は良い設計です。

src/modules/aichat/index.ts (1)

1037-1058: 記憶検索の正規表現が脆弱です

ユーザー入力を直接正規表現で処理しているため、特殊文字によってエラーが発生する可能性があります。

-    if (msg.text.match(/記憶を検索[::]\s*(.+)/)) {
-      const match = msg.text.match(/記憶を検索[::]\s*(.+)/);
+    const searchMatch = msg.text.match(/記憶を検索[::]\s*(.+)/);
+    if (searchMatch) {
       if (match) {
-        const searchQuery = match[1];
+        // 特殊文字をエスケープまたはサニタイズ
+        const searchQuery = match[1].trim().slice(0, 100); // 長さ制限も追加

Likely an incorrect or invalid review comment.

src/modules/aichat/memory/ConversationManager.ts (4)

1-22: インポートとインターフェース定義が適切です

型定義とデコレーターのインポートが正しく行われており、インターフェースも明確に定義されています。


98-132: 感情分析の実装が優れています

最近の感情データからトレンド、変動性、主要な感情を分析する実装が適切に行われています。エッジケースの処理も含まれています。


312-384: ヘルパーメソッドの実装が適切です

数学的計算(トレンド、変動性)と感情分析のロジックが正しく実装されています。日本語への翻訳機能も含まれており、ローカライゼーションが考慮されています。


76-96: メソッドシグネチャにmessageパラメータが欠落しています

80行目でmessageパラメータを使用していますが、メソッドの引数リストに含まれていません。

  @bindThis
  trackUnresolvedQuestions(
    state: ConversationState,
    analysis: ConversationAnalysis,
-   message: string
  ): void {

また、このメソッドはupdateExistingStateから呼び出されていないようです。会話状態の更新時に未解決の質問を追跡する必要があります。

Likely an incorrect or invalid review comment.

src/modules/aichat/memory/AdaptivePersonalization.ts (3)

25-133: パーソナライゼーションロジックが包括的で優れています

ビッグファイブ性格特性とユーザー設定に基づいた応答スタイルの生成が適切に実装されています。温度とトークン数の調整も含まれており、きめ細かなカスタマイズが可能です。


135-156: 時間帯に応じた調整が適切です

24時間をカバーし、各時間帯に適したメッセージスタイルを提供しています。ユーザー体験を向上させる優れた機能です。


240-253: システムプロンプト構築が明確で適切です

パーソナライゼーション指示を整理された形式でベースプロンプトに追加しています。

src/modules/aichat/memory/README.md (3)

1-66: ドキュメントの概要とコンポーネント説明が優れています

モジュールの目的と主要コンポーネントが明確に説明されており、各機能の詳細も適切に記載されています。


78-146: データ構造の説明が正確で詳細です

TypeScriptの型定義を使用してデータ構造を明確に文書化しています。実際のコードと一致しており、開発者にとって有用な参照となります。


148-172: 実装フローとメンテナンス情報が包括的です

処理の流れが明確に説明されており、自動クリーンアップやプライバシー保護についても適切に文書化されています。

Comment on lines +45 to +46
expiresAt?: Date;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

expiresAt の使用目的が不明確です

MemoryDecision インターフェースに expiresAt フィールドがありますが、メモリの有効期限がどのように処理されるかが不明です。期限切れメモリの自動削除機能が実装されているか確認が必要です。


🏁 Script executed:

#!/bin/bash
# メモリの有効期限処理を確認
rg -A 5 "expiresAt" --type ts

Length of output: 344


expiresAt の有効期限処理が未実装です

MemoryDecision.expiresAt は定義されていますが、実際に期限切れメモリを判定/削除するロジックが見当たりません。期限切れを正しく扱うため、以下を検討してください。

  • src/modules/aichat/memory/types.ts: expiresAt?: Date; の定義のみで、処理コードが存在しない
  • 既存のメモリ永続化・読み込み機構に、expiresAt を考慮したフィルタリングやクリーンアップ処理を追加
  • ドキュメントやコメントで expiresAt の使用フローを明示

必要に応じて、定期的に期限切れを削除するバッチ処理や、データ取得時のフィルタリングを実装してください。

🤖 Prompt for AI Agents
In src/modules/aichat/memory/types.ts around lines 45 to 46, the expiresAt field
is defined but there is no logic to handle expiration of memory entries.
Implement filtering in the memory loading and persistence functions to exclude
expired entries based on expiresAt. Additionally, add cleanup logic, such as a
periodic batch job, to remove expired memories from storage. Update
documentation and comments to clarify how expiresAt is used in the expiration
and deletion process.

Comment on lines +70 to +129
return `あなたは会話分析の専門家です。以下の会話を分析し、JSON形式で結果を返してください。

${userInfo}
${contextStr}
現在のメッセージ: ${message}

以下の観点で分析してください:

1. 感情分析 (emotion):
- sentiment: "positive", "negative", "neutral" のいずれか
- confidence: 0-1の数値
- emotions: 各感情の強さを0-1で評価
- joy(喜び)
- sadness(悲しみ)
- anger(怒り)
- fear(恐れ)
- surprise(驚き)
- love(愛)

2. トピック分析 (topic):
- mainTopic: メイントピック(日本語)
- subTopics: サブトピックの配列(日本語)
- keywords: キーワードの配列(日本語)
- entities: エンティティ認識
- people: 人名の配列
- places: 場所名の配列
- organizations: 組織名の配列
- products: 製品名の配列
- events: イベント名の配列

3. 意図分類 (intent):
- type: "question", "statement", "request", "greeting", "farewell", "emotion", "other" のいずれか
- confidence: 0-1の数値
- subType: より詳細な分類(任意)

4. 記憶判定 (memory):
- shouldSave: true/false(保存すべきか)
- importance: 0-1の数値(重要度)
- category: カテゴリ名(日本語)
- summary: 要約(日本語、50文字以内)
- tags: タグの配列(日本語)

5. パーソナライゼーション提案 (personalization):
- preferredStyle: 好みのスタイル(各項目0-1)
- formality: フォーマル度
- emotionalExpression: 感情表現の度合い
- humor: ユーモアの度合い
- technicalDepth: 技術的深さ
- creativity: 創造性
- adjustments: 推奨される調整の配列(日本語)

6. 応答ガイダンス (guidance):
- tone: "friendly", "professional", "casual", "empathetic", "humorous" のいずれか
- length: "brief", "moderate", "detailed" のいずれか
- useEmoji: true/false
- emphasisPoints: 強調すべきポイントの配列(日本語)
- avoidTopics: 避けるべきトピックの配列(日本語)

必ず有効なJSON形式で返答してください。`;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

プロンプトが長すぎてトークン制限を超える可能性があります

詳細な分析指示を含むプロンプトは有用ですが、会話履歴が長い場合にトークン制限を超える可能性があります。

プロンプトのサイズを管理する仕組みを追加することを推奨します:

private truncateContextIfNeeded(context: string[], maxLength: number = 2000): string {
  let contextStr = context.join('\n');
  if (contextStr.length > maxLength) {
    // 最新のメッセージを優先して切り詰める
    const recentContext = context.slice(-Math.floor(context.length / 2));
    contextStr = `...(以前の会話は省略)...\n${recentContext.join('\n')}`;
  }
  return contextStr;
}
🤖 Prompt for AI Agents
In src/modules/aichat/memory/LLMAnalyzer.ts between lines 70 and 129, the prompt
string is very long and may exceed token limits when the conversation history is
lengthy. To fix this, implement a method that truncates or summarizes the
context string to keep it within a manageable size, prioritizing the most recent
messages. Then, use this truncated context in the prompt construction to prevent
exceeding token limits while preserving essential recent conversation data.

Comment on lines +29 to +32
this.apiKey = config.gemini?.apiKey || '';
this.model = config.gemini?.model || 'gemini-2.5-flash';
this.apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${this.model}:generateContent`;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

APIキーが空の場合のエラーハンドリングが不足しています

config.gemini?.apiKey が空文字列の場合、API呼び出しが失敗しますが、早期にエラーを検出できません。

 constructor() {
   this.apiKey = config.gemini?.apiKey || '';
+  if (!this.apiKey) {
+    throw new Error('Gemini API key is not configured');
+  }
   this.model = config.gemini?.model || 'gemini-2.5-flash';
   this.apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${this.model}:generateContent`;
 }
🤖 Prompt for AI Agents
In src/modules/aichat/memory/LLMAnalyzer.ts around lines 29 to 32, the code
assigns the API key from config.gemini?.apiKey but does not handle the case when
the key is an empty string, which causes API calls to fail silently. Add a check
after assigning this.apiKey to verify it is not empty, and if it is, throw an
error or handle it appropriately to prevent proceeding with an invalid API key.

Comment on lines +245 to +295
private async consolidateOldMemories(userId: string): Promise<void> {
const oldMemories = this.memories
.chain()
.find({ userId })
.simplesort('metadata.createdAt')
.limit(this.consolidationThreshold)
.data();

if (oldMemories.length === 0) return;

const summaryPrompt = `以下の古い記憶を1つの要約にまとめてください。重要な情報は保持し、冗長な部分は削除してください。

記憶リスト:
${oldMemories.map(m => `- ${m.summary}`).join('\n')}

100文字以内で要約してください。`;

try {
const consolidatedSummary = await this.callGeminiAPI(summaryPrompt);

// 統合された記憶を作成
const consolidatedMemory: MemoryEntry = {
id: uuid(),
userId,
content: `[統合された記憶: ${oldMemories.length}件の記憶から生成]`,
summary: consolidatedSummary,
analysis: oldMemories[0].analysis, // 最初の記憶の分析を使用
context: {
previousMessages: [],
isChat: false,
},
metadata: {
createdAt: oldMemories[0].metadata.createdAt,
updatedAt: new Date(),
accessCount: oldMemories.reduce((sum, m) => sum + m.metadata.accessCount, 0),
lastAccessedAt: new Date(),
importance: Math.max(...oldMemories.map(m => m.metadata.importance)),
category: '統合記憶',
tags: [...new Set(oldMemories.flatMap(m => m.metadata.tags))],
},
};

// 古い記憶を削除
oldMemories.forEach(m => this.memories.remove(m));

// 統合された記憶を保存
this.memories.insert(consolidatedMemory);
} catch (error) {
console.error('Error consolidating memories:', error);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

メモリ統合時に重要な情報が失われる可能性があります

古いメモリを要約する際、個々の詳細な情報が失われる可能性があります。統合前にバックアップを作成することを推奨します。

統合前のメモリをアーカイブコレクションに保存する機能を実装しますか?これにより、必要に応じて詳細な履歴を参照できます。

🤖 Prompt for AI Agents
In src/modules/aichat/memory/SmartMemoryManager.ts around lines 245 to 295,
before deleting the old memories during consolidation, implement functionality
to save these old memories into a separate archive collection or storage. This
backup will preserve detailed historical data and prevent loss of important
information. Modify the method to insert the oldMemories into an archive
collection before calling this.memories.remove on them.

Comment on lines +244 to +247
setInterval(() => {
this.cleanupOldMemories();
}, 24 * 60 * 60 * 1000);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

メモリクリーンアップの間隔がハードコードされています

24時間ごとの固定間隔ではなく、設定可能にすべきです。

-    // メモリークリーンアップタスクの設定(1日1回)
-    setInterval(() => {
-      this.cleanupOldMemories();
-    }, 24 * 60 * 60 * 1000);
+    // メモリークリーンアップタスクの設定
+    const cleanupIntervalHours = config.memory?.cleanupIntervalHours || 24;
+    setInterval(() => {
+      this.cleanupOldMemories();
+    }, cleanupIntervalHours * 60 * 60 * 1000);
+    this.log(`Memory cleanup scheduled every ${cleanupIntervalHours} hours`);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/modules/aichat/index.ts around lines 244 to 247, the interval for memory
cleanup is hardcoded to 24 hours. Modify the code to use a configurable setting
for the cleanup interval instead of a fixed 24-hour period. This can be done by
introducing a configuration parameter or environment variable that defines the
interval duration, and then use that value in the setInterval call.

Comment on lines +28 to +34
constructor(db: loki) {
this.conversationStates = db.getCollection('conversationStates') ||
db.addCollection('conversationStates', {
indices: ['userId'],
unique: ['userId'],
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

データベース操作にエラーハンドリングを追加することを検討してください

コンストラクタ内でのコレクション作成/取得時にエラーが発生する可能性があります。try-catchブロックでラップすることを推奨します。

  constructor(db: loki) {
+   try {
      this.conversationStates = db.getCollection('conversationStates') || 
        db.addCollection('conversationStates', {
          indices: ['userId'],
          unique: ['userId'],
        });
+   } catch (error) {
+     console.error('Failed to initialize conversation states collection:', error);
+     throw error;
+   }
  }
🤖 Prompt for AI Agents
In src/modules/aichat/memory/ConversationManager.ts around lines 28 to 34, the
constructor accesses or creates a collection in the database without error
handling. Wrap the collection retrieval and creation code in a try-catch block
to catch any exceptions, and handle or log the error appropriately to prevent
unhandled exceptions during initialization.

Comment on lines +263 to +309
@bindThis
private updateExistingState(
state: ConversationState,
analysis: ConversationAnalysis
): void {
// トピックの更新
if (this.detectTopicTransition(
analysis.topic.mainTopic,
state.currentTopic,
analysis.topic.keywords
)) {
// 現在のトピックを終了
const currentTopicEntry = state.topicHistory[state.topicHistory.length - 1];
if (currentTopicEntry && !currentTopicEntry.endedAt) {
currentTopicEntry.endedAt = new Date();
}

// 新しいトピックを開始
state.topicHistory.push({
topic: analysis.topic.mainTopic,
startedAt: new Date(),
});
state.currentTopic = analysis.topic.mainTopic;
}

// 感情の記録
state.emotionalJourney.push({
timestamp: new Date(),
emotion: analysis.emotion,
});

// 古い感情記録を削除(最新100件のみ保持)
if (state.emotionalJourney.length > 100) {
state.emotionalJourney = state.emotionalJourney.slice(-100);
}

// キーポイントの追加
const newKeyPoints = this.extractKeyPoints(analysis, '');
state.keyPoints.push(...newKeyPoints);

// 古いキーポイントを削除(最新50件のみ保持)
if (state.keyPoints.length > 50) {
state.keyPoints = state.keyPoints.slice(-50);
}

state.lastInteraction = new Date();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

重要な機能が統合されていません

  1. trackUnresolvedQuestionsメソッドが呼び出されていないため、未解決の質問が追跡されません。
  2. extractKeyPointsに空文字列が渡されています。実際のメッセージが必要です。

これらの機能を適切に統合するには、ConversationContextからcurrentMessageを使用する必要があります:

  private updateExistingState(
    state: ConversationState,
-   analysis: ConversationAnalysis
+   analysis: ConversationAnalysis,
+   context: ConversationContext
  ): void {
    // ... existing code ...

+   // 未解決の質問を追跡
+   this.trackUnresolvedQuestions(state, analysis, context.currentMessage);

    // キーポイントの追加
-   const newKeyPoints = this.extractKeyPoints(analysis, '');
+   const newKeyPoints = this.extractKeyPoints(analysis);
    state.keyPoints.push(...newKeyPoints);

また、updateConversationStateメソッドでも対応する変更が必要です。

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/modules/aichat/memory/ConversationManager.ts around lines 263 to 309, the
updateExistingState method does not call the trackUnresolvedQuestions method, so
unresolved questions are not tracked, and it passes an empty string to
extractKeyPoints instead of the actual message. To fix this, modify
updateExistingState to accept the current message from ConversationContext and
pass it to both trackUnresolvedQuestions and extractKeyPoints. Also, ensure that
updateConversationState is updated accordingly to provide the current message
when calling updateExistingState.

Comment on lines +267 to +300
@bindThis
analyzeResponseFeedback(
response: string,
userReaction: string,
profile: UserProfile
): { adjustments: Partial<UserProfile['preferences']> } {
// ユーザーの反応から好みを学習
const adjustments: Partial<UserProfile['preferences']> = {};

// 応答の長さ分析
const responseLength = response.length;
if (userReaction.includes('長い') || userReaction.includes('詳しすぎ')) {
adjustments.responseLength = 'brief';
} else if (userReaction.includes('短い') || userReaction.includes('もっと詳しく')) {
adjustments.responseLength = 'detailed';
}

// 絵文字使用の分析
const emojiCount = (response.match(/[\u{1F300}-\u{1F9FF}]/gu) || []).length;
if (userReaction.includes('絵文字') && userReaction.includes('多い')) {
profile.communicationStyle.emojiUsage = 'rarely';
} else if (userReaction.includes('絵文字') && userReaction.includes('少ない')) {
profile.communicationStyle.emojiUsage = 'often';
}

// フォーマル度の分析
if (userReaction.includes('堅い') || userReaction.includes('フォーマル')) {
adjustments.formality = Math.max(0, profile.preferences.formality - 0.1);
} else if (userReaction.includes('カジュアル') || userReaction.includes('親しみ')) {
adjustments.formality = Math.min(1, profile.preferences.formality + 0.1);
}

return { adjustments };
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

プロファイルの直接変更に注意が必要です

287行目と289行目でprofileパラメータを直接変更していますが、これは副作用を引き起こす可能性があります。調整のみを返すべきです。

    // 絵文字使用の分析
    const emojiCount = (response.match(/[\u{1F300}-\u{1F9FF}]/gu) || []).length;
    if (userReaction.includes('絵文字') && userReaction.includes('多い')) {
-     profile.communicationStyle.emojiUsage = 'rarely';
+     // adjustmentsに含めるべき
    } else if (userReaction.includes('絵文字') && userReaction.includes('少ない')) {
-     profile.communicationStyle.emojiUsage = 'often';
+     // adjustmentsに含めるべき
    }

communicationStyleの変更もadjustmentsとして返すか、別のメソッドで処理することを推奨します。

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/modules/aichat/memory/AdaptivePersonalization.ts around lines 267 to 300,
the method analyzeResponseFeedback directly modifies the profile parameter's
communicationStyle.emojiUsage, which can cause side effects. Instead, remove
direct modifications to profile and include any changes to communicationStyle
within the adjustments object returned by the method. Refactor the code so all
preference changes, including communicationStyle updates, are captured in
adjustments without mutating the input profile.

lqvp and others added 4 commits July 21, 2025 19:20
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants