-
Notifications
You must be signed in to change notification settings - Fork 8
Aichatの記憶とパーソナライズ機能を追加 #191
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Co-authored-by: rei.mizushima <rei.mizushima@go.sfcollege.edu>
|
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 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. 📒 Files selected for processing (3)
WalkthroughAIチャットモジュールに高度なメモリ・パーソナライズ機能を統合しました。会話分析、メモリ管理、パーソナライズ、会話状態管理の各クラスを追加し、ユーザーごとのメモリとプロファイル管理を実現。コマンドや定期的なクリーンアップ、型定義、READMEも新規追加されています。 Changes
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: プロファイル更新
Poem
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this 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
-
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. ↩
There was a problem hiding this 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| userId = (aiChat as any).userId || aiChat.friendName; | |
| userId = aiChat.userId || aiChat.friendName; |
| if (userReaction.includes('絵文字') && userReaction.includes('多い')) { | ||
| profile.communicationStyle.emojiUsage = 'rarely'; | ||
| } else if (userReaction.includes('絵文字') && userReaction.includes('少ない')) { | ||
| profile.communicationStyle.emojiUsage = 'often'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| setInterval(() => { | ||
| this.cleanupOldMemories(); | ||
| }, 24 * 60 * 60 * 1000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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;
}| oldChats.forEach(chat => { | ||
| this.aichatHist.remove(chat); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this 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
📒 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: 実装フローとメンテナンス情報が包括的です処理の流れが明確に説明されており、自動クリーンアップやプライバシー保護についても適切に文書化されています。
| expiresAt?: Date; | ||
| } |
There was a problem hiding this comment.
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 tsLength 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.
| 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形式で返答してください。`; | ||
| } |
There was a problem hiding this comment.
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.
| 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`; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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); | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| setInterval(() => { | ||
| this.cleanupOldMemories(); | ||
| }, 24 * 60 * 60 * 1000); | ||
|
|
There was a problem hiding this comment.
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.
| constructor(db: loki) { | ||
| this.conversationStates = db.getCollection('conversationStates') || | ||
| db.addCollection('conversationStates', { | ||
| indices: ['userId'], | ||
| unique: ['userId'], | ||
| }); | ||
| } |
There was a problem hiding this comment.
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.
| @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(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
重要な機能が統合されていません
trackUnresolvedQuestionsメソッドが呼び出されていないため、未解決の質問が追跡されません。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.
| @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 }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
プロファイルの直接変更に注意が必要です
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.
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>
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
新機能
ドキュメント