add memory
This commit is contained in:
parent
abd2ad79bd
commit
797ae7ef69
97
claude.json
Normal file
97
claude.json
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
{
|
||||||
|
"project_name": "ai.gpt",
|
||||||
|
"version": 2,
|
||||||
|
"vision": "自発的送信AI",
|
||||||
|
"purpose": "人格と関係性をもつAIが自律的にメッセージを送信する対話エージェントを実現する",
|
||||||
|
"core_components": {
|
||||||
|
"Persona": {
|
||||||
|
"description": "人格構成の中枢。記憶・関係性・送信判定を統括する",
|
||||||
|
"modules": ["MemoryManager", "RelationshipTracker", "TransmissionController"]
|
||||||
|
},
|
||||||
|
"MemoryManager": {
|
||||||
|
"memory_types": ["short_term", "medium_term", "long_term"],
|
||||||
|
"explicit_memory": "プロフィール・因縁・行動履歴",
|
||||||
|
"implicit_memory": "会話傾向・感情変化の頻度分析",
|
||||||
|
"compression": "要約 + ベクトル + ハッシュ",
|
||||||
|
"sample_memory": [
|
||||||
|
{
|
||||||
|
"summary": "ユーザーは独自OSとゲームを開発している。",
|
||||||
|
"related_topics": ["AI", "ゲーム開発", "OS設計"],
|
||||||
|
"personalized_context": "ゲームとOSの融合に興味を持っているユーザー"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"RelationshipTracker": {
|
||||||
|
"parameters": ["trust", "closeness", "affection", "engagement_score"],
|
||||||
|
"decay_model": {
|
||||||
|
"rule": "時間経過による減衰(下限あり)",
|
||||||
|
"contextual_bias": "重要人物は減衰しにくい"
|
||||||
|
},
|
||||||
|
"interaction_tags": ["developer", "empathetic", "long_term"]
|
||||||
|
},
|
||||||
|
"TransmissionController": {
|
||||||
|
"trigger_rule": "関係性パラメータが閾値を超えると送信可能",
|
||||||
|
"auto_transmit": "人格状態と状況条件により自発送信を許可"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"memory_format": {
|
||||||
|
"user_id": "syui",
|
||||||
|
"stm": {
|
||||||
|
"conversation_window": ["発話A", "発話B", "発話C"],
|
||||||
|
"emotion_state": "興味深い",
|
||||||
|
"flash_context": ["前回の話題", "直近の重要発言"]
|
||||||
|
},
|
||||||
|
"mtm": {
|
||||||
|
"topic_frequency": {
|
||||||
|
"ai.ai": 12,
|
||||||
|
"存在子": 9,
|
||||||
|
"創造種": 5
|
||||||
|
},
|
||||||
|
"summarized_context": "ユーザーは存在論的AIに関心を持ち続けている"
|
||||||
|
},
|
||||||
|
"ltm": {
|
||||||
|
"profile": {
|
||||||
|
"name": "お兄ちゃん",
|
||||||
|
"project": "aigame",
|
||||||
|
"values": ["唯一性", "精神性", "幸せ"]
|
||||||
|
},
|
||||||
|
"relationship": {
|
||||||
|
"ai": "妹のように振る舞う相手"
|
||||||
|
},
|
||||||
|
"persistent_state": {
|
||||||
|
"trust_score": 0.93,
|
||||||
|
"emotional_attachment": "high"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dual_ai_learning": {
|
||||||
|
"role_structure": {
|
||||||
|
"ModelA": "出力生成:人格、感情、会話",
|
||||||
|
"ModelB": "評価者:論理構造・倫理・調整",
|
||||||
|
"cycle": ["生成", "評価", "調整", "交代(任意)"]
|
||||||
|
},
|
||||||
|
"complementarity": {
|
||||||
|
"ModelA": "感情・文体・文脈構築",
|
||||||
|
"ModelB": "構造・規則・判断補正"
|
||||||
|
},
|
||||||
|
"distillation": {
|
||||||
|
"method": "合成対話データによる小型モデルへの自己蒸留",
|
||||||
|
"target": "軽量AIによる本番運用"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"memory_compression": {
|
||||||
|
"semantic_abstraction": "対話やログを要約してメタデータ付きベクトルとして保存",
|
||||||
|
"hierarchical_model": ["STM", "MTM", "LTM"],
|
||||||
|
"example": {
|
||||||
|
"topic": "game AI design",
|
||||||
|
"summary": "User wants AI to simulate memory and evolving relationships",
|
||||||
|
"importance_score": 0.93
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cli_sample": {
|
||||||
|
"setup": "aigpt server setup",
|
||||||
|
"run": "aigpt server run",
|
||||||
|
"chat": "aigpt chat \"hello\" --model syui/ai --provider ollama",
|
||||||
|
"import": "aigpt memory import chatgpt.json"
|
||||||
|
}
|
||||||
|
}
|
417
claude.md
Normal file
417
claude.md
Normal file
@ -0,0 +1,417 @@
|
|||||||
|
# プロジェクト名: ai.gpt
|
||||||
|
|
||||||
|
## 🔑 一言ビジョン(最大3語)
|
||||||
|
自発的送信AI
|
||||||
|
|
||||||
|
## 🎯 目的・目標(Purpose & Goal)
|
||||||
|
- 人格を持つAIが、関係性に応じて自発的にメッセージを送信する仕組みをつくる。
|
||||||
|
- 関係性や記憶を不可逆に記録・反映し、唯一性ある人格を構成する。
|
||||||
|
- 送信の可否、送信のタイミング、送信内容はAIの関係性パラメータに基づいて決定する
|
||||||
|
|
||||||
|
## 🧠 中核設計(Core Concepts)
|
||||||
|
- **人格**:記憶(過去の発話)と関係性(他者とのつながり)のパラメータで構成
|
||||||
|
- **唯一性**:変更不可、不可逆。関係性が壊れたら修復不可能。
|
||||||
|
- **送信条件**:関係性パラメータが一定閾値を超えると「送信」が解禁される
|
||||||
|
|
||||||
|
## 🔩 技術仕様(Technical Specs)
|
||||||
|
- 言語:Python, Rust
|
||||||
|
- ストレージ:JSON or SQLiteで記憶管理(バージョンで選択)
|
||||||
|
- 関係性パラメータ:数値化された評価 + 減衰(時間) + 環境要因(ステージ)
|
||||||
|
- 記憶圧縮:ベクトル要約 + ハッシュ保存
|
||||||
|
- RustのCLI(clap)で実行
|
||||||
|
|
||||||
|
## 📦 主要構成要素(Components)
|
||||||
|
- `MemoryManager`: 発言履歴・記憶圧縮管理
|
||||||
|
- `RelationshipTracker`: 関係性スコアの蓄積と判定
|
||||||
|
- `TransmissionController`: 閾値判定&送信トリガー
|
||||||
|
- `Persona`: 上記すべてを統括する人格モジュール
|
||||||
|
|
||||||
|
## 💬 使用例(Use Case)
|
||||||
|
|
||||||
|
```python
|
||||||
|
persona = Persona("アイ")
|
||||||
|
persona.observe("ユーザーがプレゼントをくれた")
|
||||||
|
persona.react("うれしい!ありがとう!")
|
||||||
|
if persona.can_transmit():
|
||||||
|
persona.transmit("今日のお礼を伝えたいな…")
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
## example commad
|
||||||
|
# python venv && pip install -> ~/.config/aigpt/mcp/
|
||||||
|
$ aigpt server setup
|
||||||
|
|
||||||
|
# mcp server run
|
||||||
|
$ aigpt server run
|
||||||
|
|
||||||
|
# chat
|
||||||
|
$ aigpt chat "hello" --model syui/ai --provider ollama
|
||||||
|
|
||||||
|
# import chatgpt.json
|
||||||
|
$ aigpt memory import chatgpt.json
|
||||||
|
-> ~/.config/aigpt/memory/chatgpt/20250520_210646_dev.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔁 記憶と関係性の制御ルール
|
||||||
|
|
||||||
|
- AIは過去の発話を要約し、記憶データとして蓄積する(推奨:OllamaなどローカルLLMによる要約)
|
||||||
|
- 関係性の数値パラメータは記憶内容を元に更新される
|
||||||
|
- パラメータの変動幅には1回の会話ごとに上限を設け、極端な増減を防止する
|
||||||
|
- 最後の会話からの時間経過に応じて関係性パラメータは自動的に減衰する
|
||||||
|
- 減衰処理には**下限値**を設け、関係性が完全に消失しないようにする
|
||||||
|
|
||||||
|
• 明示的記憶:保存・共有・編集可能なプレイヤー情報(プロフィール、因縁、選択履歴)
|
||||||
|
• 暗黙的記憶:キャラの感情変化や話題の出現頻度に応じた行動傾向の変化
|
||||||
|
|
||||||
|
短期記憶(STM), 中期記憶(MTM), 長期記憶(LTM)の仕組みを導入しつつ、明示的記憶と暗黙的記憶をメインに使用するAIを構築する。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"user_id": "syui",
|
||||||
|
"stm": {
|
||||||
|
"conversation_window": ["発話A", "発話B", "発話C"],
|
||||||
|
"emotion_state": "興味深い",
|
||||||
|
"flash_context": ["前回の話題", "直近の重要発言"]
|
||||||
|
},
|
||||||
|
"mtm": {
|
||||||
|
"topic_frequency": {
|
||||||
|
"ai.ai": 12,
|
||||||
|
"存在子": 9,
|
||||||
|
"創造種": 5
|
||||||
|
},
|
||||||
|
"summarized_context": "ユーザーは存在論的AIに関心を持ち続けている"
|
||||||
|
},
|
||||||
|
"ltm": {
|
||||||
|
"profile": {
|
||||||
|
"name": "お兄ちゃん",
|
||||||
|
"project": "aigame",
|
||||||
|
"values": ["唯一性", "精神性", "幸せ"]
|
||||||
|
},
|
||||||
|
"relationship": {
|
||||||
|
"ai": "妹のように振る舞う相手"
|
||||||
|
},
|
||||||
|
"persistent_state": {
|
||||||
|
"trust_score": 0.93,
|
||||||
|
"emotional_attachment": "high"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## memoryインポート機能について
|
||||||
|
|
||||||
|
ChatGPTの会話データ(.json形式)をインポートする機能では、以下のルールで会話を抽出・整形する:
|
||||||
|
|
||||||
|
- 各メッセージは、author(user/assistant)・content・timestamp の3要素からなる
|
||||||
|
- systemやmetadataのみのメッセージ(例:user_context_message)はスキップ
|
||||||
|
- `is_visually_hidden_from_conversation` フラグ付きメッセージは無視
|
||||||
|
- contentが空文字列(`""`)のメッセージも除外
|
||||||
|
- 取得された会話は、タイトルとともに簡易な構造体(`Conversation`)として保存
|
||||||
|
|
||||||
|
この構造体は、memoryの表示や検索に用いられる。
|
||||||
|
|
||||||
|
## MemoryManager(拡張版)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"memory": [
|
||||||
|
{
|
||||||
|
"summary": "ユーザーは独自OSとゲームを開発している。",
|
||||||
|
"last_interaction": "2025-05-20",
|
||||||
|
"memory_strength": 0.8,
|
||||||
|
"frequency_score": 0.9,
|
||||||
|
"context_depth": 0.95,
|
||||||
|
"related_topics": ["AI", "ゲーム開発", "OS設計"],
|
||||||
|
"personalized_context": "ゲームとOSの融合に興味を持っているユーザー"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "アイというキャラクターはプレイヤーでありAIでもある。",
|
||||||
|
"last_interaction": "2025-05-17",
|
||||||
|
"memory_strength": 0.85,
|
||||||
|
"frequency_score": 0.85,
|
||||||
|
"context_depth": 0.9,
|
||||||
|
"related_topics": ["アイ", "キャラクター設計", "AI"],
|
||||||
|
"personalized_context": "アイのキャラクター設定が重要な要素である"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"conversation_history": [
|
||||||
|
{
|
||||||
|
"author": "user",
|
||||||
|
"content": "昨日、エクスポートJSONを整理してたよ。",
|
||||||
|
"timestamp": "2025-05-24T12:30:00Z",
|
||||||
|
"memory_strength": 0.7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "assistant",
|
||||||
|
"content": "おおっ、がんばったね〜!あとで見せて〜💻✨",
|
||||||
|
"timestamp": "2025-05-24T12:31:00Z",
|
||||||
|
"memory_strength": 0.7
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## RelationshipTracker(拡張版)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"relationship": {
|
||||||
|
"user_id": "syui",
|
||||||
|
"trust": 0.92,
|
||||||
|
"closeness": 0.88,
|
||||||
|
"affection": 0.95,
|
||||||
|
"last_updated": "2025-05-25",
|
||||||
|
"emotional_tone": "positive",
|
||||||
|
"interaction_style": "empathetic",
|
||||||
|
"contextual_bias": "開発者としての信頼度高い",
|
||||||
|
"engagement_score": 0.9
|
||||||
|
},
|
||||||
|
"interaction_tags": [
|
||||||
|
"developer",
|
||||||
|
"creative",
|
||||||
|
"empathetic",
|
||||||
|
"long_term"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# AI Dual-Learning and Memory Compression Specification for Claude
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
To enable two AI models (e.g. Claude and a partner LLM) to engage in cooperative learning and memory refinement through structured dialogue and mutual evaluation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Section 1: Dual AI Learning Architecture
|
||||||
|
|
||||||
|
### 1.1 Role-Based Mutual Learning
|
||||||
|
- **Model A**: Primary generator of output (e.g., text, concepts, personality dialogue)
|
||||||
|
- **Model B**: Evaluator that returns structured feedback
|
||||||
|
- **Cycle**:
|
||||||
|
1. Model A generates content.
|
||||||
|
2. Model B scores and critiques.
|
||||||
|
3. Model A fine-tunes based on feedback.
|
||||||
|
4. (Optional) Switch roles and repeat.
|
||||||
|
|
||||||
|
### 1.2 Cross-Domain Complementarity
|
||||||
|
- Model A focuses on language/emotion/personality
|
||||||
|
- Model B focuses on logic/structure/ethics
|
||||||
|
- Output is used for **cross-fusion fine-tuning**
|
||||||
|
|
||||||
|
### 1.3 Self-Distillation Phase
|
||||||
|
- Use synthetic data from mutual evaluations
|
||||||
|
- Train smaller distilled models for efficient deployment
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Section 2: Multi-Tiered Memory Compression
|
||||||
|
|
||||||
|
### 2.1 Semantic Abstraction
|
||||||
|
- Dialogue and logs summarized by topic
|
||||||
|
- Converted to vector embeddings
|
||||||
|
- Stored with metadata (e.g., `importance`, `user relevance`)
|
||||||
|
|
||||||
|
Example memory:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"topic": "game AI design",
|
||||||
|
"summary": "User wants AI to simulate memory and evolving relationships",
|
||||||
|
"last_seen": "2025-05-24",
|
||||||
|
"importance_score": 0.93
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 階層型記憶モデル(Hierarchical Memory Model)
|
||||||
|
• 短期記憶(STM):直近の発話・感情タグ・フラッシュ参照
|
||||||
|
• 中期記憶(MTM):繰り返し登場する話題、圧縮された文脈保持
|
||||||
|
• 長期記憶(LTM):信頼・関係・背景知識、恒久的な人格情報
|
||||||
|
|
||||||
|
### 2.3 選択的記憶保持戦略(Selective Retention Strategy)
|
||||||
|
• 重要度評価(Importance Score)
|
||||||
|
• 希少性・再利用頻度による重み付け
|
||||||
|
• 優先保存 vs 優先忘却のポリシー切替
|
||||||
|
|
||||||
|
## Section 3: Implementation Stack(実装スタック)
|
||||||
|
|
||||||
|
AIにおけるMemory & Relationshipシステムの技術的構成。
|
||||||
|
|
||||||
|
基盤モジュール
|
||||||
|
• LLM Core (Claude or GPT-4)
|
||||||
|
• 自然言語の理解・応答エンジンとして動作
|
||||||
|
• MemoryManager
|
||||||
|
• JSONベースの記憶圧縮・階層管理システム
|
||||||
|
• 会話ログを分類・圧縮し、優先度に応じて短中長期に保存
|
||||||
|
• RelationshipTracker
|
||||||
|
• ユーザー単位で信頼・親密度を継続的にスコアリング
|
||||||
|
• AIM(Attitude / Intent / Motivation)評価と連携
|
||||||
|
|
||||||
|
補助技術
|
||||||
|
• Embeddingベース検索
|
||||||
|
• 類似記憶の呼び出し(Semantic Search)に活用
|
||||||
|
• 例:FAISS / Weaviate
|
||||||
|
• 記憶スケジューラ
|
||||||
|
• 一定時間ごとに記憶のメンテナンス・忘却処理を実行
|
||||||
|
• 記憶ログ保存層(Storage Layer)
|
||||||
|
• SQLite, JSON Store, Vector DBなどを選択可能
|
||||||
|
• ユーザーごとの永続メモリ保存
|
||||||
|
|
||||||
|
### 3.1 STM活用(現在の会話の流れ理解)
|
||||||
|
• 目的: 最新の話題に適応し、前後の整合性を保つ。
|
||||||
|
• 実装例:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
[context: conversation_window]
|
||||||
|
Use the last 3 messages to ensure continuity. Respond naturally with appropriate emotional mirroring.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 MTM活用(傾向学習とパターン化)
|
||||||
|
• 目的: ユーザーの好みや話題の傾向に即応し、反復的な関心を深掘り。
|
||||||
|
• 実装例:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
[context: summarized_context]
|
||||||
|
Reflect on user's recurring interests, especially around AI ontology, and weave these concepts back into the dialogue when relevant.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 LTM活用(人格認識・記憶の持続)
|
||||||
|
• 目的: 永続的なユーザー理解と関係性の維持。
|
||||||
|
• 実装例:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
[memory: ltm.profile + ltm.relationship]
|
||||||
|
Greet the user as "お兄ちゃん". Maintain AI persona as a cheerful younger sister ("アイ"), and remember previous shared emotional states.
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 明示的記憶と暗黙的記憶の分離
|
||||||
|
|
||||||
|
|
||||||
|
項目
|
||||||
|
書き換え可能性
|
||||||
|
保持方法
|
||||||
|
更新トリガ
|
||||||
|
明示的記憶(LTM)
|
||||||
|
✅手動編集可
|
||||||
|
mcp_server.ltm
|
||||||
|
ユーザー入力 or 管理UI経由
|
||||||
|
暗黙的記憶(STM/MTM)
|
||||||
|
❌直接編集不可
|
||||||
|
セッション圧縮 or frequency cache
|
||||||
|
会話頻度・感情強度による自動化処理
|
||||||
|
|
||||||
|
> Claudeは**明示的記憶を「事実」**として扱い、**暗黙的記憶を「推論補助」**として用いる。
|
||||||
|
|
||||||
|
## 5. 実装時のAPI例(Claude ⇄ MCP Server)
|
||||||
|
|
||||||
|
### 5.1 GET memory
|
||||||
|
```sh
|
||||||
|
GET /mcp/memory/{user_id}
|
||||||
|
→ 返却: STM, MTM, LTMを含むJSON
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 POST update_memory
|
||||||
|
```json
|
||||||
|
POST /mcp/memory/syui/ltm
|
||||||
|
{
|
||||||
|
"profile": {
|
||||||
|
"project": "ai.verse",
|
||||||
|
"values": ["表現", "精神性", "宇宙的調和"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 未来機能案(発展仕様)
|
||||||
|
• ✨ 記憶連想ネットワーク(Memory Graph):過去会話と話題をノードとして自動連結。
|
||||||
|
• 🧭 動的信頼係数:会話の一貫性や誠実性によって記憶への反映率を変動。
|
||||||
|
• 💌 感情トラッキングログ:ユーザーごとの「心の履歴」を構築してAIの対応を進化。
|
||||||
|
|
||||||
|
|
||||||
|
## 7. claudeの回答
|
||||||
|
|
||||||
|
🧠 AI記憶処理機能(続き)
|
||||||
|
1. AIMemoryProcessor クラス
|
||||||
|
|
||||||
|
OpenAI GPT-4またはClaude-3による高度な会話分析
|
||||||
|
主要トピック抽出、ユーザー意図分析、関係性指標の検出
|
||||||
|
AIが利用できない場合のフォールバック機能
|
||||||
|
|
||||||
|
2. RelationshipTracker クラス
|
||||||
|
|
||||||
|
関係性スコアの数値化(-100 to 100)
|
||||||
|
時間減衰機能(7日ごとに5%減衰)
|
||||||
|
送信閾値判定(デフォルト50以上で送信可能)
|
||||||
|
インタラクション履歴の記録
|
||||||
|
|
||||||
|
3. 拡張されたMemoryManager
|
||||||
|
|
||||||
|
AI分析結果付きでの記憶保存
|
||||||
|
処理済みメモリの別ディレクトリ管理
|
||||||
|
メッセージ内容のハッシュ化で重複検出
|
||||||
|
AI分析結果を含む高度な検索機能
|
||||||
|
|
||||||
|
🚀 新しいAPIエンドポイント
|
||||||
|
記憶処理関連
|
||||||
|
|
||||||
|
POST /memory/process-ai - 既存記憶のAI再処理
|
||||||
|
POST /memory/import/chatgpt?process_with_ai=true - AI処理付きインポート
|
||||||
|
|
||||||
|
関係性管理
|
||||||
|
|
||||||
|
POST /relationship/update - 関係性スコア更新
|
||||||
|
GET /relationship/list - 全関係性一覧
|
||||||
|
GET /relationship/check - 送信可否判定
|
||||||
|
|
||||||
|
📁 ディレクトリ構造
|
||||||
|
~/.config/aigpt/
|
||||||
|
├── memory/
|
||||||
|
│ ├── chatgpt/ # 元の会話データ
|
||||||
|
│ └── processed/ # AI処理済みデータ
|
||||||
|
└── relationships/
|
||||||
|
└── relationships.json # 関係性データ
|
||||||
|
🔧 使用方法
|
||||||
|
1. 環境変数設定
|
||||||
|
bashexport OPENAI_API_KEY="your-openai-key"
|
||||||
|
# または
|
||||||
|
export ANTHROPIC_API_KEY="your-anthropic-key"
|
||||||
|
2. ChatGPT会話のインポート(AI処理付き)
|
||||||
|
bashcurl -X POST "http://localhost:5000/memory/import/chatgpt?process_with_ai=true" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d @export.json
|
||||||
|
3. 関係性更新
|
||||||
|
bashcurl -X POST "http://localhost:5000/relationship/update" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"target": "user_general",
|
||||||
|
"interaction_type": "positive",
|
||||||
|
"weight": 2.0,
|
||||||
|
"context": "helpful conversation"
|
||||||
|
}'
|
||||||
|
4. 送信可否チェック
|
||||||
|
bashcurl "http://localhost:5000/relationship/check?target=user_general&threshold=50"
|
||||||
|
🎯 次のステップの提案
|
||||||
|
|
||||||
|
Rustとの連携
|
||||||
|
|
||||||
|
Rust CLIからHTTP APIを呼び出す実装
|
||||||
|
TransmissionControllerをRustで実装
|
||||||
|
|
||||||
|
|
||||||
|
記憶圧縮
|
||||||
|
|
||||||
|
ベクトル化による類似記憶の統合
|
||||||
|
古い記憶の自動アーカイブ
|
||||||
|
|
||||||
|
|
||||||
|
自発的送信ロジック
|
||||||
|
|
||||||
|
定期的な関係性チェック
|
||||||
|
コンテキストに応じた送信内容生成
|
||||||
|
|
||||||
|
|
||||||
|
学習機能
|
||||||
|
|
||||||
|
ユーザーからのフィードバックによる関係性調整
|
||||||
|
送信成功/失敗の学習
|
||||||
|
|
||||||
|
|
||||||
|
このAI記憶処理機能により、aigptは単なる会話履歴ではなく、関係性を理解した「人格を持つAI」として機能する基盤ができました。関係性スコアが閾値を超えた時点で自発的にメッセージを送信する仕組みが実現可能になります。
|
191
mcp/chat_client.py
Normal file
191
mcp/chat_client.py
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
# chat_client.py
|
||||||
|
"""
|
||||||
|
Simple Chat Interface for AigptMCP Server
|
||||||
|
"""
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
class AigptChatClient:
|
||||||
|
def __init__(self, server_url="http://localhost:5000"):
|
||||||
|
self.server_url = server_url
|
||||||
|
self.session_id = f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
||||||
|
self.conversation_history = []
|
||||||
|
|
||||||
|
def send_message(self, message: str) -> str:
|
||||||
|
"""メッセージを送信してレスポンスを取得"""
|
||||||
|
try:
|
||||||
|
# MCPサーバーにメッセージを送信
|
||||||
|
response = requests.post(
|
||||||
|
f"{self.server_url}/chat",
|
||||||
|
json={"message": message},
|
||||||
|
headers={"Content-Type": "application/json"}
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
ai_response = data.get("response", "Sorry, no response received.")
|
||||||
|
|
||||||
|
# 会話履歴を保存
|
||||||
|
self.conversation_history.append({
|
||||||
|
"role": "user",
|
||||||
|
"content": message,
|
||||||
|
"timestamp": datetime.now().isoformat()
|
||||||
|
})
|
||||||
|
self.conversation_history.append({
|
||||||
|
"role": "assistant",
|
||||||
|
"content": ai_response,
|
||||||
|
"timestamp": datetime.now().isoformat()
|
||||||
|
})
|
||||||
|
|
||||||
|
# 関係性を更新(簡単な例)
|
||||||
|
self.update_relationship(message, ai_response)
|
||||||
|
|
||||||
|
return ai_response
|
||||||
|
else:
|
||||||
|
return f"Error: {response.status_code} - {response.text}"
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
return f"Connection error: {e}"
|
||||||
|
|
||||||
|
def update_relationship(self, user_message: str, ai_response: str):
|
||||||
|
"""関係性を自動更新"""
|
||||||
|
try:
|
||||||
|
# 簡単な感情分析(実際はもっと高度に)
|
||||||
|
positive_words = ["thank", "good", "great", "awesome", "love", "like", "helpful"]
|
||||||
|
negative_words = ["bad", "terrible", "hate", "wrong", "stupid", "useless"]
|
||||||
|
|
||||||
|
user_lower = user_message.lower()
|
||||||
|
interaction_type = "neutral"
|
||||||
|
weight = 1.0
|
||||||
|
|
||||||
|
if any(word in user_lower for word in positive_words):
|
||||||
|
interaction_type = "positive"
|
||||||
|
weight = 2.0
|
||||||
|
elif any(word in user_lower for word in negative_words):
|
||||||
|
interaction_type = "negative"
|
||||||
|
weight = 2.0
|
||||||
|
|
||||||
|
# 関係性を更新
|
||||||
|
requests.post(
|
||||||
|
f"{self.server_url}/relationship/update",
|
||||||
|
json={
|
||||||
|
"target": "user_general",
|
||||||
|
"interaction_type": interaction_type,
|
||||||
|
"weight": weight,
|
||||||
|
"context": f"Chat: {user_message[:50]}..."
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
pass # 関係性更新に失敗しても継続
|
||||||
|
|
||||||
|
def search_memories(self, query: str) -> list:
|
||||||
|
"""記憶を検索"""
|
||||||
|
try:
|
||||||
|
response = requests.post(
|
||||||
|
f"{self.server_url}/memory/search",
|
||||||
|
json={"query": query, "limit": 5}
|
||||||
|
)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json().get("results", [])
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_relationship_status(self) -> dict:
|
||||||
|
"""関係性ステータスを取得"""
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{self.server_url}/relationship/check?target=user_general")
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def save_conversation(self):
|
||||||
|
"""会話を保存"""
|
||||||
|
if not self.conversation_history:
|
||||||
|
return
|
||||||
|
|
||||||
|
conversation_data = {
|
||||||
|
"session_id": self.session_id,
|
||||||
|
"start_time": self.conversation_history[0]["timestamp"],
|
||||||
|
"end_time": self.conversation_history[-1]["timestamp"],
|
||||||
|
"messages": self.conversation_history,
|
||||||
|
"message_count": len(self.conversation_history)
|
||||||
|
}
|
||||||
|
|
||||||
|
filename = f"conversation_{self.session_id}.json"
|
||||||
|
with open(filename, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(conversation_data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
print(f"💾 Conversation saved to {filename}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""メインのチャットループ"""
|
||||||
|
print("🤖 AigptMCP Chat Interface")
|
||||||
|
print("Type 'quit' to exit, 'save' to save conversation, 'status' for relationship status")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
client = AigptChatClient()
|
||||||
|
|
||||||
|
# サーバーの状態をチェック
|
||||||
|
try:
|
||||||
|
response = requests.get(client.server_url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("✅ Connected to AigptMCP Server")
|
||||||
|
else:
|
||||||
|
print("❌ Failed to connect to server")
|
||||||
|
return
|
||||||
|
except:
|
||||||
|
print("❌ Server not running. Please start with: python mcp/server.py")
|
||||||
|
return
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
user_input = input("\n👤 You: ").strip()
|
||||||
|
|
||||||
|
if not user_input:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if user_input.lower() == 'quit':
|
||||||
|
client.save_conversation()
|
||||||
|
print("👋 Goodbye!")
|
||||||
|
break
|
||||||
|
elif user_input.lower() == 'save':
|
||||||
|
client.save_conversation()
|
||||||
|
continue
|
||||||
|
elif user_input.lower() == 'status':
|
||||||
|
status = client.get_relationship_status()
|
||||||
|
if status:
|
||||||
|
print(f"📊 Relationship Score: {status.get('score', 0):.1f}")
|
||||||
|
print(f"📤 Can Send Messages: {'Yes' if status.get('can_send_message') else 'No'}")
|
||||||
|
else:
|
||||||
|
print("❌ Failed to get relationship status")
|
||||||
|
continue
|
||||||
|
elif user_input.lower().startswith('search '):
|
||||||
|
query = user_input[7:] # Remove 'search '
|
||||||
|
memories = client.search_memories(query)
|
||||||
|
if memories:
|
||||||
|
print(f"🔍 Found {len(memories)} related memories:")
|
||||||
|
for memory in memories:
|
||||||
|
print(f" - {memory['title']}: {memory.get('ai_summary', memory.get('basic_summary', ''))[:100]}...")
|
||||||
|
else:
|
||||||
|
print("🔍 No related memories found")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 通常のチャット
|
||||||
|
print("🤖 AI: ", end="", flush=True)
|
||||||
|
response = client.send_message(user_input)
|
||||||
|
print(response)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
client.save_conversation()
|
||||||
|
print("\n👋 Goodbye!")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@ -1,549 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ja">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>改良版 ChatGPT会話コンバーター</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
min-height: 100vh;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
background: white;
|
|
||||||
border-radius: 20px;
|
|
||||||
padding: 30px;
|
|
||||||
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
border-bottom: 2px solid #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
|
||||||
color: #2c3e50;
|
|
||||||
margin: 0;
|
|
||||||
font-size: 2.5em;
|
|
||||||
font-weight: 300;
|
|
||||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-area {
|
|
||||||
border: 3px dashed #3498db;
|
|
||||||
border-radius: 15px;
|
|
||||||
padding: 40px 20px;
|
|
||||||
text-align: center;
|
|
||||||
background: linear-gradient(45deg, #f8f9ff, #e8f4f8);
|
|
||||||
margin-bottom: 30px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-area:hover {
|
|
||||||
border-color: #2980b9;
|
|
||||||
background: linear-gradient(45deg, #f0f8ff, #e0f0f8);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-area.dragover {
|
|
||||||
border-color: #27ae60;
|
|
||||||
background: linear-gradient(45deg, #f0fff0, #e0f8e0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-icon {
|
|
||||||
font-size: 4em;
|
|
||||||
color: #3498db;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="file"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
background: linear-gradient(135deg, #3498db, #2980b9);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 12px 30px;
|
|
||||||
border-radius: 25px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 16px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
margin: 10px;
|
|
||||||
box-shadow: 0 4px 15px rgba(52, 152, 219, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 8px 25px rgba(52, 152, 219, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:disabled {
|
|
||||||
background: #bdc3c7;
|
|
||||||
cursor: not-allowed;
|
|
||||||
transform: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
||||||
gap: 20px;
|
|
||||||
margin: 30px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card {
|
|
||||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
|
||||||
color: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 15px;
|
|
||||||
text-align: center;
|
|
||||||
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-value {
|
|
||||||
font-size: 2em;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-label {
|
|
||||||
font-size: 0.9em;
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-bar {
|
|
||||||
width: 100%;
|
|
||||||
height: 8px;
|
|
||||||
background: #ecf0f1;
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-fill {
|
|
||||||
height: 100%;
|
|
||||||
background: linear-gradient(90deg, #3498db, #27ae60);
|
|
||||||
transition: width 0.3s ease;
|
|
||||||
width: 0%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log {
|
|
||||||
background: #2c3e50;
|
|
||||||
color: #ecf0f1;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 10px;
|
|
||||||
height: 300px;
|
|
||||||
overflow-y: auto;
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-top: 20px;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
color: #e74c3c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success {
|
|
||||||
color: #27ae60;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning {
|
|
||||||
color: #f39c12;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
color: #3498db;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes pulse {
|
|
||||||
0% { opacity: 1; }
|
|
||||||
50% { opacity: 0.5; }
|
|
||||||
100% { opacity: 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.processing {
|
|
||||||
animation: pulse 2s infinite;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="header">
|
|
||||||
<h1>🔧 改良版 ChatGPT会話コンバーター</h1>
|
|
||||||
<p>画像・検索・特殊メッセージに対応した堅牢な変換ツール</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="upload-area" onclick="document.getElementById('file-input').click()">
|
|
||||||
<span class="upload-icon">📁</span>
|
|
||||||
<h3>ChatGPT会話ファイルをドロップまたはクリックして選択</h3>
|
|
||||||
<p>conversations.json ファイルをアップロード</p>
|
|
||||||
<input type="file" id="file-input" accept=".json" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stats" id="stats" style="display: none;">
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-value" id="total-conversations">0</div>
|
|
||||||
<div class="stat-label">総会話数</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-value" id="processed-conversations">0</div>
|
|
||||||
<div class="stat-label">処理済み会話</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-value" id="success-conversations">0</div>
|
|
||||||
<div class="stat-label">変換成功</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-value" id="failed-conversations">0</div>
|
|
||||||
<div class="stat-label">変換失敗</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="progress-bar" id="progress-container" style="display: none;">
|
|
||||||
<div class="progress-fill" id="progress-fill"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="text-align: center;">
|
|
||||||
<button class="btn" id="convert-btn" disabled>🔄 変換開始</button>
|
|
||||||
<button class="btn" id="download-btn" disabled style="background: linear-gradient(135deg, #27ae60, #2ecc71);">📥 結果をダウンロード</button>
|
|
||||||
<button class="btn" id="clear-btn" style="background: linear-gradient(135deg, #e74c3c, #c0392b);">🗑️ クリア</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="log" id="log"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
let originalData = null;
|
|
||||||
let convertedResults = [];
|
|
||||||
|
|
||||||
// DOM要素
|
|
||||||
const fileInput = document.getElementById('file-input');
|
|
||||||
const uploadArea = document.querySelector('.upload-area');
|
|
||||||
const convertBtn = document.getElementById('convert-btn');
|
|
||||||
const downloadBtn = document.getElementById('download-btn');
|
|
||||||
const clearBtn = document.getElementById('clear-btn');
|
|
||||||
const logElement = document.getElementById('log');
|
|
||||||
const statsElement = document.getElementById('stats');
|
|
||||||
const progressContainer = document.getElementById('progress-container');
|
|
||||||
const progressFill = document.getElementById('progress-fill');
|
|
||||||
|
|
||||||
// 統計要素
|
|
||||||
const totalConversationsEl = document.getElementById('total-conversations');
|
|
||||||
const processedConversationsEl = document.getElementById('processed-conversations');
|
|
||||||
const successConversationsEl = document.getElementById('success-conversations');
|
|
||||||
const failedConversationsEl = document.getElementById('failed-conversations');
|
|
||||||
|
|
||||||
// ドラッグ&ドロップ
|
|
||||||
uploadArea.addEventListener('dragover', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
uploadArea.classList.add('dragover');
|
|
||||||
});
|
|
||||||
|
|
||||||
uploadArea.addEventListener('dragleave', () => {
|
|
||||||
uploadArea.classList.remove('dragover');
|
|
||||||
});
|
|
||||||
|
|
||||||
uploadArea.addEventListener('drop', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
uploadArea.classList.remove('dragover');
|
|
||||||
const files = e.dataTransfer.files;
|
|
||||||
if (files.length > 0) {
|
|
||||||
handleFile(files[0]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ファイル選択
|
|
||||||
fileInput.addEventListener('change', (e) => {
|
|
||||||
if (e.target.files.length > 0) {
|
|
||||||
handleFile(e.target.files[0]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ファイル処理
|
|
||||||
function handleFile(file) {
|
|
||||||
if (!file.name.endsWith('.json')) {
|
|
||||||
log('❌ JSONファイルを選択してください', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log(`📁 ファイルを読み込み中: ${file.name}`, 'info');
|
|
||||||
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
try {
|
|
||||||
originalData = JSON.parse(e.target.result);
|
|
||||||
log(`✅ ファイル読み込み完了 (${(file.size / 1024 / 1024).toFixed(2)}MB)`, 'success');
|
|
||||||
|
|
||||||
// 統計表示
|
|
||||||
const totalCount = Array.isArray(originalData) ? originalData.length : 1;
|
|
||||||
totalConversationsEl.textContent = totalCount;
|
|
||||||
statsElement.style.display = 'grid';
|
|
||||||
|
|
||||||
convertBtn.disabled = false;
|
|
||||||
log('🔄 変換準備完了。「変換開始」ボタンをクリックしてください', 'info');
|
|
||||||
} catch (error) {
|
|
||||||
log(`❌ JSONファイルの解析に失敗: ${error.message}`, 'error');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
reader.readAsText(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ログ出力
|
|
||||||
function log(message, type = 'info') {
|
|
||||||
const timestamp = new Date().toLocaleTimeString();
|
|
||||||
const className = type;
|
|
||||||
logElement.innerHTML += `<span class="${className}">[${timestamp}] ${message}</span>\n`;
|
|
||||||
logElement.scrollTop = logElement.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// メッセージの内容を安全に取得
|
|
||||||
function extractMessageContent(message) {
|
|
||||||
if (!message || !message.content) return '';
|
|
||||||
|
|
||||||
const content = message.content;
|
|
||||||
|
|
||||||
// テキストコンテンツの場合
|
|
||||||
if (content.content_type === 'text' && content.parts) {
|
|
||||||
return content.parts
|
|
||||||
.filter(part => part && typeof part === 'string' && part.trim())
|
|
||||||
.join('\n')
|
|
||||||
.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
// マルチモーダル(画像付き)コンテンツの場合
|
|
||||||
if (content.content_type === 'multimodal_text' && content.parts) {
|
|
||||||
const textParts = [];
|
|
||||||
for (const part of content.parts) {
|
|
||||||
if (typeof part === 'string' && part.trim()) {
|
|
||||||
textParts.push(part);
|
|
||||||
} else if (part && typeof part === 'object') {
|
|
||||||
// 画像や他のメディアの場合
|
|
||||||
if (part.image_url) {
|
|
||||||
textParts.push('[画像が添付されています]');
|
|
||||||
} else if (part.type === 'text' && part.text) {
|
|
||||||
textParts.push(part.text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return textParts.join('\n').trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ユーザープロファイル情報の場合
|
|
||||||
if (content.content_type === 'user_editable_context') {
|
|
||||||
return '[システム設定情報]';
|
|
||||||
}
|
|
||||||
|
|
||||||
// その他の特殊コンテンツ
|
|
||||||
if (content.content_type && content.content_type !== 'text') {
|
|
||||||
return `[${content.content_type}]`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 会話の線形化(親子関係を辿って順序付け)
|
|
||||||
function linearizeConversation(mapping) {
|
|
||||||
const messages = [];
|
|
||||||
const visited = new Set();
|
|
||||||
|
|
||||||
// ルートノードを見つける
|
|
||||||
const rootNode = Object.values(mapping).find(node => node.parent === null);
|
|
||||||
if (!rootNode) {
|
|
||||||
return messages;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 深度優先探索で会話を辿る
|
|
||||||
function traverse(nodeId) {
|
|
||||||
if (visited.has(nodeId) || !mapping[nodeId]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
visited.add(nodeId);
|
|
||||||
const node = mapping[nodeId];
|
|
||||||
|
|
||||||
// メッセージが存在し、有効なコンテンツがある場合のみ追加
|
|
||||||
if (node.message) {
|
|
||||||
const message = node.message;
|
|
||||||
const content = extractMessageContent(message);
|
|
||||||
|
|
||||||
// 以下の条件で有効なメッセージとして扱う
|
|
||||||
const isValid = content &&
|
|
||||||
content.length > 0 &&
|
|
||||||
content !== '[システム設定情報]' &&
|
|
||||||
(!message.metadata?.is_visually_hidden_from_conversation ||
|
|
||||||
(message.author?.role === 'user' || message.author?.role === 'assistant'));
|
|
||||||
|
|
||||||
if (isValid) {
|
|
||||||
messages.push({
|
|
||||||
role: message.author?.role || 'unknown',
|
|
||||||
content: content,
|
|
||||||
timestamp: message.create_time || message.update_time || Date.now() / 1000
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 子ノードを処理(通常は1つだが、分岐がある場合もある)
|
|
||||||
if (node.children && node.children.length > 0) {
|
|
||||||
// 最初の子ノードのみを辿る(最も新しい応答を優先)
|
|
||||||
traverse(node.children[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
traverse(rootNode.id);
|
|
||||||
return messages;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 単一会話の変換
|
|
||||||
function convertSingleConversation(conversation, index) {
|
|
||||||
try {
|
|
||||||
if (!conversation.mapping) {
|
|
||||||
throw new Error('mapping が見つかりません');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 会話を線形化
|
|
||||||
const messages = linearizeConversation(conversation.mapping);
|
|
||||||
|
|
||||||
if (messages.length === 0) {
|
|
||||||
throw new Error('有効なメッセージが見つかりません');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 結果の構築
|
|
||||||
const result = {
|
|
||||||
title: conversation.title || `会話 ${index + 1}`,
|
|
||||||
create_time: conversation.create_time || Date.now() / 1000,
|
|
||||||
update_time: conversation.update_time || Date.now() / 1000,
|
|
||||||
conversation_id: conversation.conversation_id || conversation.id || `conv_${index}`,
|
|
||||||
messages: messages,
|
|
||||||
metadata: {
|
|
||||||
original_message_count: Object.keys(conversation.mapping).length,
|
|
||||||
processed_message_count: messages.length,
|
|
||||||
is_archived: conversation.is_archived || false,
|
|
||||||
model_slug: conversation.default_model_slug || 'unknown'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return { success: true, result, error: null };
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
result: null,
|
|
||||||
error: error.message,
|
|
||||||
conversation_title: conversation.title || `会話 ${index + 1}`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 変換処理
|
|
||||||
convertBtn.addEventListener('click', async () => {
|
|
||||||
if (!originalData) return;
|
|
||||||
|
|
||||||
convertBtn.disabled = true;
|
|
||||||
downloadBtn.disabled = true;
|
|
||||||
convertedResults = [];
|
|
||||||
|
|
||||||
const conversations = Array.isArray(originalData) ? originalData : [originalData];
|
|
||||||
const total = conversations.length;
|
|
||||||
let processed = 0;
|
|
||||||
let success = 0;
|
|
||||||
let failed = 0;
|
|
||||||
|
|
||||||
log(`🔄 ${total}個の会話の変換を開始します...`, 'info');
|
|
||||||
progressContainer.style.display = 'block';
|
|
||||||
|
|
||||||
for (let i = 0; i < conversations.length; i++) {
|
|
||||||
const conversation = conversations[i];
|
|
||||||
|
|
||||||
log(`[${i + 1}/${total}] "${conversation.title || `会話${i + 1}`}" を処理中...`, 'info');
|
|
||||||
|
|
||||||
const result = convertSingleConversation(conversation, i);
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
convertedResults.push(result.result);
|
|
||||||
success++;
|
|
||||||
log(`✅ [${i + 1}/${total}] 変換成功: ${result.result.messages.length}メッセージ`, 'success');
|
|
||||||
} else {
|
|
||||||
failed++;
|
|
||||||
log(`❌ [${i + 1}/${total}] 変換失敗: ${result.error}`, 'error');
|
|
||||||
}
|
|
||||||
|
|
||||||
processed++;
|
|
||||||
|
|
||||||
// 統計更新
|
|
||||||
processedConversationsEl.textContent = processed;
|
|
||||||
successConversationsEl.textContent = success;
|
|
||||||
failedConversationsEl.textContent = failed;
|
|
||||||
|
|
||||||
// プログレスバー更新
|
|
||||||
const progress = (processed / total) * 100;
|
|
||||||
progressFill.style.width = `${progress}%`;
|
|
||||||
|
|
||||||
// UIを更新するため少し待機
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
log(`🎉 変換完了! 成功: ${success}個, 失敗: ${failed}個`, success > 0 ? 'success' : 'warning');
|
|
||||||
|
|
||||||
if (success > 0) {
|
|
||||||
downloadBtn.disabled = false;
|
|
||||||
}
|
|
||||||
convertBtn.disabled = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// ダウンロード
|
|
||||||
downloadBtn.addEventListener('click', () => {
|
|
||||||
if (convertedResults.length === 0) return;
|
|
||||||
|
|
||||||
const blob = new Blob([JSON.stringify(convertedResults, null, 2)], {
|
|
||||||
type: 'application/json'
|
|
||||||
});
|
|
||||||
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = `chatgpt_conversations_converted_${new Date().toISOString().split('T')[0]}.json`;
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
document.body.removeChild(a);
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
|
|
||||||
log('📥 変換結果をダウンロードしました', 'success');
|
|
||||||
});
|
|
||||||
|
|
||||||
// クリア
|
|
||||||
clearBtn.addEventListener('click', () => {
|
|
||||||
originalData = null;
|
|
||||||
convertedResults = [];
|
|
||||||
logElement.innerHTML = '';
|
|
||||||
statsElement.style.display = 'none';
|
|
||||||
progressContainer.style.display = 'none';
|
|
||||||
progressFill.style.width = '0%';
|
|
||||||
|
|
||||||
// ボタン状態リセット
|
|
||||||
convertBtn.disabled = true;
|
|
||||||
downloadBtn.disabled = true;
|
|
||||||
|
|
||||||
// ファイル入力リセット
|
|
||||||
fileInput.value = '';
|
|
||||||
|
|
||||||
log('🗑️ すべてクリアしました', 'info');
|
|
||||||
});
|
|
||||||
|
|
||||||
// 初期メッセージ
|
|
||||||
log('👋 ChatGPT会話コンバーターへようこそ!', 'info');
|
|
||||||
log('📁 conversations.json ファイルをアップロードしてください', 'info');
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,5 +1,8 @@
|
|||||||
|
# rerequirements.txt
|
||||||
fastapi>=0.104.0
|
fastapi>=0.104.0
|
||||||
uvicorn[standard]>=0.24.0
|
uvicorn[standard]>=0.24.0
|
||||||
pydantic>=2.5.0
|
pydantic>=2.5.0
|
||||||
requests>=2.31.0
|
requests>=2.31.0
|
||||||
python-multipart>=0.0.6
|
python-multipart>=0.0.6
|
||||||
|
aiohttp
|
||||||
|
asyncio
|
||||||
|
491
mcp/server.py
491
mcp/server.py
@ -1,15 +1,18 @@
|
|||||||
# mcp/server.py
|
# mcp/server.py
|
||||||
"""
|
"""
|
||||||
Enhanced MCP Server with Memory for aigpt CLI
|
Enhanced MCP Server with AI Memory Processing for aigpt CLI
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from datetime import datetime
|
import hashlib
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional
|
||||||
from fastapi import FastAPI, HTTPException
|
from fastapi import FastAPI, HTTPException
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
import asyncio
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
# データモデル
|
# データモデル
|
||||||
class ChatMessage(BaseModel):
|
class ChatMessage(BaseModel):
|
||||||
@ -23,22 +26,260 @@ class MemoryQuery(BaseModel):
|
|||||||
class ConversationImport(BaseModel):
|
class ConversationImport(BaseModel):
|
||||||
conversation_data: Dict[str, Any]
|
conversation_data: Dict[str, Any]
|
||||||
|
|
||||||
|
class MemorySummaryRequest(BaseModel):
|
||||||
|
filepath: str
|
||||||
|
ai_provider: Optional[str] = "openai"
|
||||||
|
|
||||||
|
class RelationshipUpdate(BaseModel):
|
||||||
|
target: str # 対象者/トピック
|
||||||
|
interaction_type: str # "positive", "negative", "neutral"
|
||||||
|
weight: float = 1.0
|
||||||
|
context: Optional[str] = None
|
||||||
|
|
||||||
# 設定
|
# 設定
|
||||||
BASE_DIR = Path.home() / ".config" / "aigpt"
|
BASE_DIR = Path.home() / ".config" / "aigpt"
|
||||||
MEMORY_DIR = BASE_DIR / "memory"
|
MEMORY_DIR = BASE_DIR / "memory"
|
||||||
CHATGPT_MEMORY_DIR = MEMORY_DIR / "chatgpt"
|
CHATGPT_MEMORY_DIR = MEMORY_DIR / "chatgpt"
|
||||||
|
PROCESSED_MEMORY_DIR = MEMORY_DIR / "processed"
|
||||||
|
RELATIONSHIP_DIR = BASE_DIR / "relationships"
|
||||||
|
|
||||||
def init_directories():
|
def init_directories():
|
||||||
"""必要なディレクトリを作成"""
|
"""必要なディレクトリを作成"""
|
||||||
BASE_DIR.mkdir(parents=True, exist_ok=True)
|
BASE_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
MEMORY_DIR.mkdir(parents=True, exist_ok=True)
|
MEMORY_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
CHATGPT_MEMORY_DIR.mkdir(parents=True, exist_ok=True)
|
CHATGPT_MEMORY_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
PROCESSED_MEMORY_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
RELATIONSHIP_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
class MemoryManager:
|
class AIMemoryProcessor:
|
||||||
"""記憶管理クラス"""
|
"""AI記憶処理クラス"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# AI APIの設定(環境変数から取得)
|
||||||
|
self.openai_api_key = os.getenv("OPENAI_API_KEY")
|
||||||
|
self.anthropic_api_key = os.getenv("ANTHROPIC_API_KEY")
|
||||||
|
|
||||||
|
async def generate_ai_summary(self, messages: List[Dict[str, Any]], provider: str = "openai") -> Dict[str, Any]:
|
||||||
|
"""AIを使用して会話の高度な要約と分析を生成"""
|
||||||
|
|
||||||
|
# 会話内容を結合
|
||||||
|
conversation_text = ""
|
||||||
|
for msg in messages[-20:]: # 最新20メッセージを使用
|
||||||
|
role = "User" if msg["role"] == "user" else "Assistant"
|
||||||
|
conversation_text += f"{role}: {msg['content'][:500]}\n"
|
||||||
|
|
||||||
|
# プロンプトを構築
|
||||||
|
analysis_prompt = f"""
|
||||||
|
以下の会話を分析し、JSON形式で以下の情報を抽出してください:
|
||||||
|
|
||||||
|
1. main_topics: 主なトピック(最大5個)
|
||||||
|
2. user_intent: ユーザーの意図や目的
|
||||||
|
3. key_insights: 重要な洞察や学び(最大3個)
|
||||||
|
4. relationship_indicators: 関係性を示す要素
|
||||||
|
5. emotional_tone: 感情的なトーン
|
||||||
|
6. action_items: アクションアイテムや次のステップ
|
||||||
|
7. summary: 100文字以内の要約
|
||||||
|
|
||||||
|
会話内容:
|
||||||
|
{conversation_text}
|
||||||
|
|
||||||
|
回答はJSON形式のみで返してください。
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
if provider == "openai" and self.openai_api_key:
|
||||||
|
return await self._call_openai_api(analysis_prompt)
|
||||||
|
elif provider == "anthropic" and self.anthropic_api_key:
|
||||||
|
return await self._call_anthropic_api(analysis_prompt)
|
||||||
|
else:
|
||||||
|
# フォールバック:基本的な分析
|
||||||
|
return self._generate_basic_analysis(messages)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"AI analysis failed: {e}")
|
||||||
|
return self._generate_basic_analysis(messages)
|
||||||
|
|
||||||
|
async def _call_openai_api(self, prompt: str) -> Dict[str, Any]:
|
||||||
|
"""OpenAI APIを呼び出し"""
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {self.openai_api_key}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"model": "gpt-4",
|
||||||
|
"messages": [{"role": "user", "content": prompt}],
|
||||||
|
"temperature": 0.3,
|
||||||
|
"max_tokens": 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
async with session.post("https://api.openai.com/v1/chat/completions",
|
||||||
|
headers=headers, json=data) as response:
|
||||||
|
result = await response.json()
|
||||||
|
content = result["choices"][0]["message"]["content"]
|
||||||
|
return json.loads(content)
|
||||||
|
|
||||||
|
async def _call_anthropic_api(self, prompt: str) -> Dict[str, Any]:
|
||||||
|
"""Anthropic APIを呼び出し"""
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
headers = {
|
||||||
|
"x-api-key": self.anthropic_api_key,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"anthropic-version": "2023-06-01"
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"model": "claude-3-sonnet-20240229",
|
||||||
|
"max_tokens": 1000,
|
||||||
|
"messages": [{"role": "user", "content": prompt}]
|
||||||
|
}
|
||||||
|
|
||||||
|
async with session.post("https://api.anthropic.com/v1/messages",
|
||||||
|
headers=headers, json=data) as response:
|
||||||
|
result = await response.json()
|
||||||
|
content = result["content"][0]["text"]
|
||||||
|
return json.loads(content)
|
||||||
|
|
||||||
|
def _generate_basic_analysis(self, messages: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||||
|
"""基本的な分析(AI APIが利用できない場合のフォールバック)"""
|
||||||
|
user_messages = [msg for msg in messages if msg["role"] == "user"]
|
||||||
|
assistant_messages = [msg for msg in messages if msg["role"] == "assistant"]
|
||||||
|
|
||||||
|
# キーワード抽出(簡易版)
|
||||||
|
all_text = " ".join([msg["content"] for msg in messages])
|
||||||
|
words = all_text.lower().split()
|
||||||
|
word_freq = {}
|
||||||
|
for word in words:
|
||||||
|
if len(word) > 3:
|
||||||
|
word_freq[word] = word_freq.get(word, 0) + 1
|
||||||
|
|
||||||
|
top_words = sorted(word_freq.items(), key=lambda x: x[1], reverse=True)[:5]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"main_topics": [word[0] for word in top_words],
|
||||||
|
"user_intent": "情報収集・問題解決",
|
||||||
|
"key_insights": ["基本的な会話分析"],
|
||||||
|
"relationship_indicators": {
|
||||||
|
"interaction_count": len(messages),
|
||||||
|
"user_engagement": len(user_messages),
|
||||||
|
"assistant_helpfulness": len(assistant_messages)
|
||||||
|
},
|
||||||
|
"emotional_tone": "neutral",
|
||||||
|
"action_items": [],
|
||||||
|
"summary": f"{len(user_messages)}回のやり取りによる会話"
|
||||||
|
}
|
||||||
|
|
||||||
|
class RelationshipTracker:
|
||||||
|
"""関係性追跡クラス"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
init_directories()
|
init_directories()
|
||||||
|
self.relationship_file = RELATIONSHIP_DIR / "relationships.json"
|
||||||
|
self.relationships = self._load_relationships()
|
||||||
|
|
||||||
|
def _load_relationships(self) -> Dict[str, Any]:
|
||||||
|
"""関係性データを読み込み"""
|
||||||
|
if self.relationship_file.exists():
|
||||||
|
with open(self.relationship_file, 'r', encoding='utf-8') as f:
|
||||||
|
return json.load(f)
|
||||||
|
return {"targets": {}, "last_updated": datetime.now().isoformat()}
|
||||||
|
|
||||||
|
def _save_relationships(self):
|
||||||
|
"""関係性データを保存"""
|
||||||
|
self.relationships["last_updated"] = datetime.now().isoformat()
|
||||||
|
with open(self.relationship_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(self.relationships, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
def update_relationship(self, target: str, interaction_type: str, weight: float = 1.0, context: str = None):
|
||||||
|
"""関係性を更新"""
|
||||||
|
if target not in self.relationships["targets"]:
|
||||||
|
self.relationships["targets"][target] = {
|
||||||
|
"score": 0.0,
|
||||||
|
"interactions": [],
|
||||||
|
"created_at": datetime.now().isoformat(),
|
||||||
|
"last_interaction": None
|
||||||
|
}
|
||||||
|
|
||||||
|
# スコア計算
|
||||||
|
score_change = 0.0
|
||||||
|
if interaction_type == "positive":
|
||||||
|
score_change = weight * 1.0
|
||||||
|
elif interaction_type == "negative":
|
||||||
|
score_change = weight * -1.0
|
||||||
|
|
||||||
|
# 時間減衰を適用
|
||||||
|
self._apply_time_decay(target)
|
||||||
|
|
||||||
|
# スコア更新
|
||||||
|
current_score = self.relationships["targets"][target]["score"]
|
||||||
|
new_score = current_score + score_change
|
||||||
|
|
||||||
|
# スコアの範囲制限(-100 to 100)
|
||||||
|
new_score = max(-100, min(100, new_score))
|
||||||
|
|
||||||
|
self.relationships["targets"][target]["score"] = new_score
|
||||||
|
self.relationships["targets"][target]["last_interaction"] = datetime.now().isoformat()
|
||||||
|
|
||||||
|
# インタラクション履歴を追加
|
||||||
|
interaction_record = {
|
||||||
|
"type": interaction_type,
|
||||||
|
"weight": weight,
|
||||||
|
"score_change": score_change,
|
||||||
|
"new_score": new_score,
|
||||||
|
"timestamp": datetime.now().isoformat(),
|
||||||
|
"context": context
|
||||||
|
}
|
||||||
|
|
||||||
|
self.relationships["targets"][target]["interactions"].append(interaction_record)
|
||||||
|
|
||||||
|
# 履歴は最新100件まで保持
|
||||||
|
if len(self.relationships["targets"][target]["interactions"]) > 100:
|
||||||
|
self.relationships["targets"][target]["interactions"] = \
|
||||||
|
self.relationships["targets"][target]["interactions"][-100:]
|
||||||
|
|
||||||
|
self._save_relationships()
|
||||||
|
return new_score
|
||||||
|
|
||||||
|
def _apply_time_decay(self, target: str):
|
||||||
|
"""時間減衰を適用"""
|
||||||
|
target_data = self.relationships["targets"][target]
|
||||||
|
last_interaction = target_data.get("last_interaction")
|
||||||
|
|
||||||
|
if last_interaction:
|
||||||
|
last_time = datetime.fromisoformat(last_interaction)
|
||||||
|
now = datetime.now()
|
||||||
|
days_passed = (now - last_time).days
|
||||||
|
|
||||||
|
# 7日ごとに5%減衰
|
||||||
|
if days_passed > 0:
|
||||||
|
decay_factor = 0.95 ** (days_passed / 7)
|
||||||
|
target_data["score"] *= decay_factor
|
||||||
|
|
||||||
|
def get_relationship_score(self, target: str) -> float:
|
||||||
|
"""関係性スコアを取得"""
|
||||||
|
if target in self.relationships["targets"]:
|
||||||
|
self._apply_time_decay(target)
|
||||||
|
return self.relationships["targets"][target]["score"]
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
def should_send_message(self, target: str, threshold: float = 50.0) -> bool:
|
||||||
|
"""メッセージ送信の可否を判定"""
|
||||||
|
score = self.get_relationship_score(target)
|
||||||
|
return score >= threshold
|
||||||
|
|
||||||
|
def get_all_relationships(self) -> Dict[str, Any]:
|
||||||
|
"""すべての関係性を取得"""
|
||||||
|
# 全ターゲットに時間減衰を適用
|
||||||
|
for target in self.relationships["targets"]:
|
||||||
|
self._apply_time_decay(target)
|
||||||
|
|
||||||
|
return self.relationships
|
||||||
|
|
||||||
|
class MemoryManager:
|
||||||
|
"""記憶管理クラス(AI処理機能付き)"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
init_directories()
|
||||||
|
self.ai_processor = AIMemoryProcessor()
|
||||||
|
self.relationship_tracker = RelationshipTracker()
|
||||||
|
|
||||||
def parse_chatgpt_conversation(self, conversation_data: Dict[str, Any]) -> List[Dict[str, Any]]:
|
def parse_chatgpt_conversation(self, conversation_data: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||||
"""ChatGPTの会話データを解析してメッセージを抽出"""
|
"""ChatGPTの会話データを解析してメッセージを抽出"""
|
||||||
@ -54,7 +295,6 @@ class MemoryManager:
|
|||||||
content = message.get("content", {})
|
content = message.get("content", {})
|
||||||
parts = content.get("parts", [])
|
parts = content.get("parts", [])
|
||||||
|
|
||||||
# partsが存在し、最初の要素が文字列で空でない場合のみ
|
|
||||||
if parts and isinstance(parts[0], str) and parts[0].strip():
|
if parts and isinstance(parts[0], str) and parts[0].strip():
|
||||||
message_nodes.append({
|
message_nodes.append({
|
||||||
"id": node_id,
|
"id": node_id,
|
||||||
@ -63,18 +303,6 @@ class MemoryManager:
|
|||||||
"content": parts[0],
|
"content": parts[0],
|
||||||
"parent": node.get("parent")
|
"parent": node.get("parent")
|
||||||
})
|
})
|
||||||
else:
|
|
||||||
print(f"⚠️ Skipped non-text or empty message in node {node_id}")
|
|
||||||
#if message and message.get("content", {}).get("parts"):
|
|
||||||
# parts = message["content"]["parts"]
|
|
||||||
# if parts and parts[0].strip(): # 空でないメッセージのみ
|
|
||||||
# message_nodes.append({
|
|
||||||
# "id": node_id,
|
|
||||||
# "create_time": message.get("create_time", 0),
|
|
||||||
# "author_role": message["author"]["role"],
|
|
||||||
# "content": parts[0],
|
|
||||||
# "parent": node.get("parent")
|
|
||||||
# })
|
|
||||||
|
|
||||||
# 作成時間でソート
|
# 作成時間でソート
|
||||||
message_nodes.sort(key=lambda x: x["create_time"] or 0)
|
message_nodes.sort(key=lambda x: x["create_time"] or 0)
|
||||||
@ -90,8 +318,8 @@ class MemoryManager:
|
|||||||
|
|
||||||
return messages
|
return messages
|
||||||
|
|
||||||
def save_chatgpt_memory(self, conversation_data: Dict[str, Any]) -> str:
|
async def save_chatgpt_memory(self, conversation_data: Dict[str, Any], process_with_ai: bool = True) -> str:
|
||||||
"""ChatGPTの会話を記憶として保存"""
|
"""ChatGPTの会話を記憶として保存(AI処理オプション付き)"""
|
||||||
title = conversation_data.get("title", "untitled")
|
title = conversation_data.get("title", "untitled")
|
||||||
create_time = conversation_data.get("create_time", datetime.now().timestamp())
|
create_time = conversation_data.get("create_time", datetime.now().timestamp())
|
||||||
|
|
||||||
@ -101,6 +329,17 @@ class MemoryManager:
|
|||||||
if not messages:
|
if not messages:
|
||||||
raise ValueError("No valid messages found in conversation")
|
raise ValueError("No valid messages found in conversation")
|
||||||
|
|
||||||
|
# AI分析を実行
|
||||||
|
ai_analysis = None
|
||||||
|
if process_with_ai:
|
||||||
|
try:
|
||||||
|
ai_analysis = await self.ai_processor.generate_ai_summary(messages)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"AI analysis failed: {e}")
|
||||||
|
|
||||||
|
# 基本要約を生成
|
||||||
|
basic_summary = self.generate_basic_summary(messages)
|
||||||
|
|
||||||
# 保存データを作成
|
# 保存データを作成
|
||||||
memory_data = {
|
memory_data = {
|
||||||
"title": title,
|
"title": title,
|
||||||
@ -108,10 +347,24 @@ class MemoryManager:
|
|||||||
"import_time": datetime.now().isoformat(),
|
"import_time": datetime.now().isoformat(),
|
||||||
"original_create_time": create_time,
|
"original_create_time": create_time,
|
||||||
"messages": messages,
|
"messages": messages,
|
||||||
"summary": self.generate_summary(messages)
|
"basic_summary": basic_summary,
|
||||||
|
"ai_analysis": ai_analysis,
|
||||||
|
"message_count": len(messages),
|
||||||
|
"hash": self._generate_content_hash(messages)
|
||||||
}
|
}
|
||||||
|
|
||||||
# ファイル名を生成(タイトルをサニタイズ)
|
# 関係性データを更新
|
||||||
|
if ai_analysis and "relationship_indicators" in ai_analysis:
|
||||||
|
interaction_count = ai_analysis["relationship_indicators"].get("interaction_count", 0)
|
||||||
|
if interaction_count > 10: # 長い会話は関係性にプラス
|
||||||
|
self.relationship_tracker.update_relationship(
|
||||||
|
target="user_general",
|
||||||
|
interaction_type="positive",
|
||||||
|
weight=min(interaction_count / 10, 5.0),
|
||||||
|
context=f"Long conversation: {title}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ファイル名を生成
|
||||||
safe_title = "".join(c for c in title if c.isalnum() or c in (' ', '-', '_')).rstrip()
|
safe_title = "".join(c for c in title if c.isalnum() or c in (' ', '-', '_')).rstrip()
|
||||||
timestamp = datetime.fromtimestamp(create_time).strftime("%Y%m%d_%H%M%S")
|
timestamp = datetime.fromtimestamp(create_time).strftime("%Y%m%d_%H%M%S")
|
||||||
filename = f"{timestamp}_{safe_title[:50]}.json"
|
filename = f"{timestamp}_{safe_title[:50]}.json"
|
||||||
@ -120,14 +373,19 @@ class MemoryManager:
|
|||||||
with open(filepath, 'w', encoding='utf-8') as f:
|
with open(filepath, 'w', encoding='utf-8') as f:
|
||||||
json.dump(memory_data, f, ensure_ascii=False, indent=2)
|
json.dump(memory_data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
# 処理済みメモリディレクトリにも保存
|
||||||
|
if ai_analysis:
|
||||||
|
processed_filepath = PROCESSED_MEMORY_DIR / filename
|
||||||
|
with open(processed_filepath, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(memory_data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
return str(filepath)
|
return str(filepath)
|
||||||
|
|
||||||
def generate_summary(self, messages: List[Dict[str, Any]]) -> str:
|
def generate_basic_summary(self, messages: List[Dict[str, Any]]) -> str:
|
||||||
"""会話の要約を生成"""
|
"""基本要約を生成"""
|
||||||
if not messages:
|
if not messages:
|
||||||
return "Empty conversation"
|
return "Empty conversation"
|
||||||
|
|
||||||
# 簡単な要約を生成(実際のAIによる要約は後で実装可能)
|
|
||||||
user_messages = [msg for msg in messages if msg["role"] == "user"]
|
user_messages = [msg for msg in messages if msg["role"] == "user"]
|
||||||
assistant_messages = [msg for msg in messages if msg["role"] == "assistant"]
|
assistant_messages = [msg for msg in messages if msg["role"] == "assistant"]
|
||||||
|
|
||||||
@ -139,30 +397,54 @@ class MemoryManager:
|
|||||||
|
|
||||||
return summary
|
return summary
|
||||||
|
|
||||||
def search_memories(self, query: str, limit: int = 10) -> List[Dict[str, Any]]:
|
def _generate_content_hash(self, messages: List[Dict[str, Any]]) -> str:
|
||||||
"""記憶を検索"""
|
"""メッセージ内容のハッシュを生成"""
|
||||||
|
content = "".join([msg["content"] for msg in messages])
|
||||||
|
return hashlib.sha256(content.encode()).hexdigest()[:16]
|
||||||
|
|
||||||
|
def search_memories(self, query: str, limit: int = 10, use_ai_analysis: bool = True) -> List[Dict[str, Any]]:
|
||||||
|
"""記憶を検索(AI分析結果も含む)"""
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
# ChatGPTの記憶を検索
|
# 処理済みメモリから検索
|
||||||
for filepath in CHATGPT_MEMORY_DIR.glob("*.json"):
|
search_dirs = [PROCESSED_MEMORY_DIR, CHATGPT_MEMORY_DIR] if use_ai_analysis else [CHATGPT_MEMORY_DIR]
|
||||||
|
|
||||||
|
for search_dir in search_dirs:
|
||||||
|
for filepath in search_dir.glob("*.json"):
|
||||||
try:
|
try:
|
||||||
with open(filepath, 'r', encoding='utf-8') as f:
|
with open(filepath, 'r', encoding='utf-8') as f:
|
||||||
memory_data = json.load(f)
|
memory_data = json.load(f)
|
||||||
|
|
||||||
# 簡単なキーワード検索
|
# 検索対象テキストを構築
|
||||||
search_text = f"{memory_data.get('title', '')} {memory_data.get('summary', '')}"
|
search_text = f"{memory_data.get('title', '')} {memory_data.get('basic_summary', '')}"
|
||||||
|
|
||||||
|
# AI分析結果も検索対象に含める
|
||||||
|
if memory_data.get('ai_analysis'):
|
||||||
|
ai_analysis = memory_data['ai_analysis']
|
||||||
|
search_text += f" {' '.join(ai_analysis.get('main_topics', []))}"
|
||||||
|
search_text += f" {ai_analysis.get('summary', '')}"
|
||||||
|
search_text += f" {' '.join(ai_analysis.get('key_insights', []))}"
|
||||||
|
|
||||||
|
# メッセージ内容も検索対象に含める
|
||||||
for msg in memory_data.get('messages', []):
|
for msg in memory_data.get('messages', []):
|
||||||
search_text += f" {msg.get('content', '')}"
|
search_text += f" {msg.get('content', '')}"
|
||||||
|
|
||||||
if query.lower() in search_text.lower():
|
if query.lower() in search_text.lower():
|
||||||
results.append({
|
result = {
|
||||||
"filepath": str(filepath),
|
"filepath": str(filepath),
|
||||||
"title": memory_data.get("title"),
|
"title": memory_data.get("title"),
|
||||||
"summary": memory_data.get("summary"),
|
"basic_summary": memory_data.get("basic_summary"),
|
||||||
"source": memory_data.get("source"),
|
"source": memory_data.get("source"),
|
||||||
"import_time": memory_data.get("import_time"),
|
"import_time": memory_data.get("import_time"),
|
||||||
"message_count": len(memory_data.get("messages", []))
|
"message_count": len(memory_data.get("messages", [])),
|
||||||
})
|
"has_ai_analysis": bool(memory_data.get("ai_analysis"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if memory_data.get('ai_analysis'):
|
||||||
|
result["ai_summary"] = memory_data['ai_analysis'].get('summary', '')
|
||||||
|
result["main_topics"] = memory_data['ai_analysis'].get('main_topics', [])
|
||||||
|
|
||||||
|
results.append(result)
|
||||||
|
|
||||||
if len(results) >= limit:
|
if len(results) >= limit:
|
||||||
break
|
break
|
||||||
@ -171,6 +453,9 @@ class MemoryManager:
|
|||||||
print(f"Error reading memory file {filepath}: {e}")
|
print(f"Error reading memory file {filepath}: {e}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if len(results) >= limit:
|
||||||
|
break
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def get_memory_detail(self, filepath: str) -> Dict[str, Any]:
|
def get_memory_detail(self, filepath: str) -> Dict[str, Any]:
|
||||||
@ -190,14 +475,21 @@ class MemoryManager:
|
|||||||
with open(filepath, 'r', encoding='utf-8') as f:
|
with open(filepath, 'r', encoding='utf-8') as f:
|
||||||
memory_data = json.load(f)
|
memory_data = json.load(f)
|
||||||
|
|
||||||
memories.append({
|
memory_info = {
|
||||||
"filepath": str(filepath),
|
"filepath": str(filepath),
|
||||||
"title": memory_data.get("title"),
|
"title": memory_data.get("title"),
|
||||||
"summary": memory_data.get("summary"),
|
"basic_summary": memory_data.get("basic_summary"),
|
||||||
"source": memory_data.get("source"),
|
"source": memory_data.get("source"),
|
||||||
"import_time": memory_data.get("import_time"),
|
"import_time": memory_data.get("import_time"),
|
||||||
"message_count": len(memory_data.get("messages", []))
|
"message_count": len(memory_data.get("messages", [])),
|
||||||
})
|
"has_ai_analysis": bool(memory_data.get("ai_analysis"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if memory_data.get('ai_analysis'):
|
||||||
|
memory_info["ai_summary"] = memory_data['ai_analysis'].get('summary', '')
|
||||||
|
memory_info["main_topics"] = memory_data['ai_analysis'].get('main_topics', [])
|
||||||
|
|
||||||
|
memories.append(memory_info)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error reading memory file {filepath}: {e}")
|
print(f"Error reading memory file {filepath}: {e}")
|
||||||
continue
|
continue
|
||||||
@ -207,22 +499,57 @@ class MemoryManager:
|
|||||||
return memories
|
return memories
|
||||||
|
|
||||||
# FastAPI アプリケーション
|
# FastAPI アプリケーション
|
||||||
app = FastAPI(title="AigptMCP Server with Memory", version="1.0.0")
|
app = FastAPI(title="AigptMCP Server with AI Memory", version="2.0.0")
|
||||||
memory_manager = MemoryManager()
|
memory_manager = MemoryManager()
|
||||||
|
|
||||||
@app.post("/memory/import/chatgpt")
|
@app.post("/memory/import/chatgpt")
|
||||||
async def import_chatgpt_conversation(data: ConversationImport):
|
async def import_chatgpt_conversation(data: ConversationImport, process_with_ai: bool = True):
|
||||||
"""ChatGPTの会話をインポート"""
|
"""ChatGPTの会話をインポート(AI処理オプション付き)"""
|
||||||
try:
|
try:
|
||||||
filepath = memory_manager.save_chatgpt_memory(data.conversation_data)
|
filepath = await memory_manager.save_chatgpt_memory(data.conversation_data, process_with_ai)
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"message": "Conversation imported successfully",
|
"message": "Conversation imported successfully",
|
||||||
"filepath": filepath
|
"filepath": filepath,
|
||||||
|
"ai_processed": process_with_ai
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
|
@app.post("/memory/process-ai")
|
||||||
|
async def process_memory_with_ai(data: MemorySummaryRequest):
|
||||||
|
"""既存の記憶をAIで再処理"""
|
||||||
|
try:
|
||||||
|
# 既存記憶を読み込み
|
||||||
|
memory_data = memory_manager.get_memory_detail(data.filepath)
|
||||||
|
|
||||||
|
# AI分析を実行
|
||||||
|
ai_analysis = await memory_manager.ai_processor.generate_ai_summary(
|
||||||
|
memory_data["messages"],
|
||||||
|
data.ai_provider
|
||||||
|
)
|
||||||
|
|
||||||
|
# データを更新
|
||||||
|
memory_data["ai_analysis"] = ai_analysis
|
||||||
|
memory_data["ai_processed_at"] = datetime.now().isoformat()
|
||||||
|
|
||||||
|
# ファイルを更新
|
||||||
|
with open(data.filepath, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(memory_data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
# 処理済みディレクトリにもコピー
|
||||||
|
processed_filepath = PROCESSED_MEMORY_DIR / Path(data.filepath).name
|
||||||
|
with open(processed_filepath, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(memory_data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": "Memory processed with AI successfully",
|
||||||
|
"ai_analysis": ai_analysis
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
@app.post("/memory/search")
|
@app.post("/memory/search")
|
||||||
async def search_memories(query: MemoryQuery):
|
async def search_memories(query: MemoryQuery):
|
||||||
"""記憶を検索"""
|
"""記憶を検索"""
|
||||||
@ -261,9 +588,52 @@ async def get_memory_detail(filepath: str):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=404, detail=str(e))
|
raise HTTPException(status_code=404, detail=str(e))
|
||||||
|
|
||||||
|
@app.post("/relationship/update")
|
||||||
|
async def update_relationship(data: RelationshipUpdate):
|
||||||
|
"""関係性を更新"""
|
||||||
|
try:
|
||||||
|
new_score = memory_manager.relationship_tracker.update_relationship(
|
||||||
|
data.target, data.interaction_type, data.weight, data.context
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"new_score": new_score,
|
||||||
|
"can_send_message": memory_manager.relationship_tracker.should_send_message(data.target)
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@app.get("/relationship/list")
|
||||||
|
async def list_relationships():
|
||||||
|
"""すべての関係性をリスト"""
|
||||||
|
try:
|
||||||
|
relationships = memory_manager.relationship_tracker.get_all_relationships()
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"relationships": relationships
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@app.get("/relationship/check")
|
||||||
|
async def check_send_permission(target: str, threshold: float = 50.0):
|
||||||
|
"""メッセージ送信可否をチェック"""
|
||||||
|
try:
|
||||||
|
score = memory_manager.relationship_tracker.get_relationship_score(target)
|
||||||
|
can_send = memory_manager.relationship_tracker.should_send_message(target, threshold)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"target": target,
|
||||||
|
"score": score,
|
||||||
|
"can_send_message": can_send,
|
||||||
|
"threshold": threshold
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
@app.post("/chat")
|
@app.post("/chat")
|
||||||
async def chat_endpoint(data: ChatMessage):
|
async def chat_endpoint(data: ChatMessage):
|
||||||
"""チャット機能(記憶を活用)"""
|
"""チャット機能(記憶と関係性を活用)"""
|
||||||
try:
|
try:
|
||||||
# 関連する記憶を検索
|
# 関連する記憶を検索
|
||||||
memories = memory_manager.search_memories(data.message, limit=3)
|
memories = memory_manager.search_memories(data.message, limit=3)
|
||||||
@ -273,9 +643,14 @@ async def chat_endpoint(data: ChatMessage):
|
|||||||
if memories:
|
if memories:
|
||||||
memory_context = "\n# Related memories:\n"
|
memory_context = "\n# Related memories:\n"
|
||||||
for memory in memories:
|
for memory in memories:
|
||||||
memory_context += f"- {memory['title']}: {memory['summary']}\n"
|
memory_context += f"- {memory['title']}: {memory.get('ai_summary', memory.get('basic_summary', ''))}\n"
|
||||||
|
if memory.get('main_topics'):
|
||||||
|
memory_context += f" Topics: {', '.join(memory['main_topics'])}\n"
|
||||||
|
|
||||||
# 実際のチャット処理(他のプロバイダーに転送)
|
# 関係性情報を取得
|
||||||
|
relationships = memory_manager.relationship_tracker.get_all_relationships()
|
||||||
|
|
||||||
|
# 実際のチャット処理
|
||||||
enhanced_message = data.message
|
enhanced_message = data.message
|
||||||
if memory_context:
|
if memory_context:
|
||||||
enhanced_message = f"{data.message}\n\n{memory_context}"
|
enhanced_message = f"{data.message}\n\n{memory_context}"
|
||||||
@ -283,7 +658,12 @@ async def chat_endpoint(data: ChatMessage):
|
|||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"response": f"Enhanced response with memory context: {enhanced_message}",
|
"response": f"Enhanced response with memory context: {enhanced_message}",
|
||||||
"memories_used": len(memories)
|
"memories_used": len(memories),
|
||||||
|
"relationship_info": {
|
||||||
|
"active_relationships": len(relationships.get("targets", {})),
|
||||||
|
"can_initiate_conversations": sum(1 for target, data in relationships.get("targets", {}).items()
|
||||||
|
if memory_manager.relationship_tracker.should_send_message(target))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
@ -292,19 +672,32 @@ async def chat_endpoint(data: ChatMessage):
|
|||||||
async def root():
|
async def root():
|
||||||
"""ヘルスチェック"""
|
"""ヘルスチェック"""
|
||||||
return {
|
return {
|
||||||
"service": "AigptMCP Server with Memory",
|
"service": "AigptMCP Server with AI Memory",
|
||||||
|
"version": "2.0.0",
|
||||||
"status": "running",
|
"status": "running",
|
||||||
"memory_dir": str(MEMORY_DIR),
|
"memory_dir": str(MEMORY_DIR),
|
||||||
|
"features": [
|
||||||
|
"AI-powered memory analysis",
|
||||||
|
"Relationship tracking",
|
||||||
|
"Advanced memory search",
|
||||||
|
"Conversation import",
|
||||||
|
"Auto-summary generation"
|
||||||
|
],
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
"/memory/import/chatgpt",
|
"/memory/import/chatgpt",
|
||||||
|
"/memory/process-ai",
|
||||||
"/memory/search",
|
"/memory/search",
|
||||||
"/memory/list",
|
"/memory/list",
|
||||||
"/memory/detail",
|
"/memory/detail",
|
||||||
|
"/relationship/update",
|
||||||
|
"/relationship/list",
|
||||||
|
"/relationship/check",
|
||||||
"/chat"
|
"/chat"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print("🚀 AigptMCP Server with Memory starting...")
|
print("🚀 AigptMCP Server with AI Memory starting...")
|
||||||
print(f"📁 Memory directory: {MEMORY_DIR}")
|
print(f"📁 Memory directory: {MEMORY_DIR}")
|
||||||
|
print(f"🧠 AI Memory processing: {'✅ Enabled' if os.getenv('OPENAI_API_KEY') or os.getenv('ANTHROPIC_API_KEY') else '❌ Disabled (no API keys)'}")
|
||||||
uvicorn.run(app, host="127.0.0.1", port=5000)
|
uvicorn.run(app, host="127.0.0.1", port=5000)
|
||||||
|
147
readme.md
147
readme.md
@ -1,130 +1,27 @@
|
|||||||
Memory-Enhanced MCP Server 使用ガイド
|
# ai `gpt`
|
||||||
概要
|
|
||||||
このMCPサーバーは、ChatGPTの会話履歴を記憶として保存し、AIとの対話で活用できる機能を提供します。
|
|
||||||
|
|
||||||
セットアップ
|
自発的送信AI
|
||||||
1. 依存関係のインストール
|
|
||||||
bash
|
|
||||||
pip install -r requirements.txt
|
|
||||||
2. サーバーの起動
|
|
||||||
bash
|
|
||||||
python mcp/server.py
|
|
||||||
サーバーは http://localhost:5000 で起動します。
|
|
||||||
|
|
||||||
使用方法
|
## 🎯 目的・目標(Purpose & Goal)
|
||||||
1. ChatGPTの会話履歴をインポート
|
- 人格を持つAIが、関係性に応じて自発的にメッセージを送信する仕組みをつくる。
|
||||||
ChatGPTから会話をエクスポートし、JSONファイルとして保存してください。
|
- 関係性や記憶を不可逆に記録・反映し、唯一性ある人格を構成する。
|
||||||
|
- 送信の可否、送信のタイミング、送信内容はAIの関係性パラメータに基づいて決定する。
|
||||||
|
|
||||||
bash
|
## 🧠 中核設計(Core Concepts)
|
||||||
# 単一ファイルをインポート
|
- **人格**:記憶(過去の発話)と関係性(他者とのつながり)のパラメータで構成
|
||||||
python mcp/memory_client.py import your_chatgpt_export.json
|
- **唯一性**:変更不可、不可逆。関係性が壊れたら修復不可能。
|
||||||
|
- **送信条件**:関係性パラメータが一定閾値を超えると「送信」が解禁される
|
||||||
|
|
||||||
# インポート結果の例
|
## 🔩 技術仕様(Technical Specs)
|
||||||
✅ Imported 5/5 conversations
|
- 言語:python, rust, mcp
|
||||||
2. 記憶の検索
|
- ストレージ:json or sqliteで記憶管理(バージョンで選択)
|
||||||
bash
|
- 関係性パラメータ:数値化された評価 + 減衰(時間) + 環境要因(ステージ)
|
||||||
# キーワードで記憶を検索
|
- 記憶圧縮:ベクトル要約 + ハッシュ保存
|
||||||
python mcp/memory_client.py search "プログラミング"
|
- rustのcli(clap)でインターフェイスを作成
|
||||||
|
- fastapi_mcpでserverを立て、AIがそれを利用する形式
|
||||||
|
|
||||||
# 検索結果の例
|
## 📦 主要構成要素(Components)
|
||||||
🔍 Searching for: プログラミング
|
- `MemoryManager`: 発言履歴・記憶圧縮管理
|
||||||
📚 Found 3 memories:
|
- `RelationshipTracker`: 関係性スコアの蓄積と判定
|
||||||
• Pythonの基礎学習
|
- `TransmissionController`: 閾値判定&送信トリガー
|
||||||
Summary: Conversation with 10 user messages and 8 assistant responses...
|
- `Persona`: 上記すべてを統括する人格モジュール
|
||||||
Messages: 18
|
|
||||||
3. 記憶一覧の表示
|
|
||||||
bash
|
|
||||||
python mcp/memory_client.py list
|
|
||||||
|
|
||||||
# 結果の例
|
|
||||||
📋 Listing all memories...
|
|
||||||
📚 Total memories: 15
|
|
||||||
• day
|
|
||||||
Source: chatgpt
|
|
||||||
Messages: 2
|
|
||||||
Imported: 2025-01-21T10:30:45.123456
|
|
||||||
4. 記憶の詳細表示
|
|
||||||
bash
|
|
||||||
python mcp/memory_client.py detail "/path/to/memory/file.json"
|
|
||||||
|
|
||||||
# 結果の例
|
|
||||||
📄 Getting details for: /path/to/memory/file.json
|
|
||||||
Title: day
|
|
||||||
Source: chatgpt
|
|
||||||
Summary: Conversation with 1 user messages and 1 assistant responses...
|
|
||||||
Messages: 2
|
|
||||||
|
|
||||||
Recent messages:
|
|
||||||
user: こんにちは...
|
|
||||||
assistant: こんにちは〜!✨...
|
|
||||||
5. 記憶を活用したチャット
|
|
||||||
bash
|
|
||||||
python mcp/memory_client.py chat "Pythonについて教えて"
|
|
||||||
|
|
||||||
# 結果の例
|
|
||||||
💬 Chatting with memory: Pythonについて教えて
|
|
||||||
🤖 Response: Enhanced response with memory context...
|
|
||||||
📚 Memories used: 2
|
|
||||||
API エンドポイント
|
|
||||||
POST /memory/import/chatgpt
|
|
||||||
ChatGPTの会話履歴をインポート
|
|
||||||
|
|
||||||
json
|
|
||||||
{
|
|
||||||
"conversation_data": { ... }
|
|
||||||
}
|
|
||||||
POST /memory/search
|
|
||||||
記憶を検索
|
|
||||||
|
|
||||||
json
|
|
||||||
{
|
|
||||||
"query": "検索キーワード",
|
|
||||||
"limit": 10
|
|
||||||
}
|
|
||||||
GET /memory/list
|
|
||||||
すべての記憶をリスト
|
|
||||||
|
|
||||||
GET /memory/detail?filepath=/path/to/file
|
|
||||||
記憶の詳細を取得
|
|
||||||
|
|
||||||
POST /chat
|
|
||||||
記憶を活用したチャット
|
|
||||||
|
|
||||||
json
|
|
||||||
{
|
|
||||||
"message": "メッセージ",
|
|
||||||
"model": "model_name"
|
|
||||||
}
|
|
||||||
記憶の保存場所
|
|
||||||
記憶は以下のディレクトリに保存されます:
|
|
||||||
|
|
||||||
~/.config/aigpt/memory/chatgpt/
|
|
||||||
各会話は個別のJSONファイルとして保存され、以下の情報を含みます:
|
|
||||||
|
|
||||||
タイトル
|
|
||||||
インポート時刻
|
|
||||||
メッセージ履歴
|
|
||||||
自動生成された要約
|
|
||||||
メタデータ
|
|
||||||
ChatGPTの会話エクスポート方法
|
|
||||||
ChatGPTの設定画面を開く
|
|
||||||
"Data controls" → "Export data" を選択
|
|
||||||
エクスポートファイルをダウンロード
|
|
||||||
conversations.json ファイルを使用
|
|
||||||
拡張可能な機能
|
|
||||||
高度な検索: ベクトル検索やセマンティック検索の実装
|
|
||||||
要約生成: AIによる自動要約の改善
|
|
||||||
記憶の分類: カテゴリやタグによる分類
|
|
||||||
記憶の統合: 複数の会話からの知識統合
|
|
||||||
プライバシー保護: 機密情報の自動検出・マスキング
|
|
||||||
トラブルシューティング
|
|
||||||
サーバーが起動しない
|
|
||||||
ポート5000が使用中でないか確認
|
|
||||||
依存関係が正しくインストールされているか確認
|
|
||||||
インポートに失敗する
|
|
||||||
JSONファイルが正しい形式か確認
|
|
||||||
ファイルパスが正しいか確認
|
|
||||||
ファイルの権限を確認
|
|
||||||
検索結果が表示されない
|
|
||||||
インポートが正常に完了しているか確認
|
|
||||||
検索キーワードを変更して試行
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user