Compare commits
6 Commits
0f18689539
...
feature/sh
Author | SHA1 | Date | |
---|---|---|---|
6081ed069f
|
|||
c9005f5240
|
|||
cba52b6171
|
|||
b642588696
|
|||
ebd2582b92
|
|||
79d1e1943f
|
@@ -14,7 +14,35 @@
|
||||
"Bash(/Users/syui/.config/syui/ai/gpt/venv/bin/pip install -e .)",
|
||||
"Bash(/Users/syui/.config/syui/ai/gpt/venv/bin/aigpt fortune)",
|
||||
"Bash(lsof:*)",
|
||||
"Bash(/Users/syui/.config/syui/ai/gpt/venv/bin/python -c \"\nfrom src.aigpt.mcp_server import AIGptMcpServer\nfrom pathlib import Path\nimport uvicorn\n\ndata_dir = Path.home() / '.config' / 'syui' / 'ai' / 'gpt' / 'data'\ndata_dir.mkdir(parents=True, exist_ok=True)\n\ntry:\n server = AIGptMcpServer(data_dir)\n print('MCP Server created successfully')\n print('Available endpoints:', [route.path for route in server.app.routes])\nexcept Exception as e:\n print('Error:', e)\n import traceback\n traceback.print_exc()\n\")"
|
||||
"Bash(/Users/syui/.config/syui/ai/gpt/venv/bin/python -c \"\nfrom src.aigpt.mcp_server import AIGptMcpServer\nfrom pathlib import Path\nimport uvicorn\n\ndata_dir = Path.home() / '.config' / 'syui' / 'ai' / 'gpt' / 'data'\ndata_dir.mkdir(parents=True, exist_ok=True)\n\ntry:\n server = AIGptMcpServer(data_dir)\n print('MCP Server created successfully')\n print('Available endpoints:', [route.path for route in server.app.routes])\nexcept Exception as e:\n print('Error:', e)\n import traceback\n traceback.print_exc()\n\")",
|
||||
"Bash(ls:*)",
|
||||
"Bash(grep:*)",
|
||||
"Bash(python -m pip install:*)",
|
||||
"Bash(python:*)",
|
||||
"Bash(RELOAD=false ./start_server.sh)",
|
||||
"Bash(sed:*)",
|
||||
"Bash(curl:*)",
|
||||
"Bash(~/.config/syui/ai/card/venv/bin/pip install greenlet)",
|
||||
"Bash(~/.config/syui/ai/card/venv/bin/python init_db.py)",
|
||||
"Bash(sqlite3:*)",
|
||||
"Bash(aigpt --help)",
|
||||
"Bash(aigpt status)",
|
||||
"Bash(aigpt fortune)",
|
||||
"Bash(aigpt relationships)",
|
||||
"Bash(aigpt transmit)",
|
||||
"Bash(aigpt config:*)",
|
||||
"Bash(kill:*)",
|
||||
"Bash(timeout:*)",
|
||||
"Bash(rm:*)",
|
||||
"Bash(rg:*)",
|
||||
"Bash(aigpt server --help)",
|
||||
"Bash(cat:*)",
|
||||
"Bash(aigpt import-chatgpt:*)",
|
||||
"Bash(aigpt chat:*)",
|
||||
"Bash(echo:*)",
|
||||
"Bash(aigpt shell:*)",
|
||||
"Bash(aigpt maintenance)",
|
||||
"Bash(aigpt status syui)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,3 +4,5 @@ output.json
|
||||
config/*.db
|
||||
mcp/scripts/__*
|
||||
data
|
||||
__pycache__
|
||||
conversations.json
|
||||
|
@@ -1,4 +1,24 @@
|
||||
# ai.gpt 開発状況 (2025/01/06 更新)
|
||||
# ai.gpt 開発状況 (2025/06/02 更新)
|
||||
|
||||
## 前回セッション完了事項 (2025/06/01)
|
||||
|
||||
### ✅ ai.card MCPサーバー独立化完了
|
||||
- **ai.card専用MCPサーバー実装**: `card/api/app/mcp_server.py`
|
||||
- **9個のMCPツール公開**: カード管理・ガチャ・atproto同期等
|
||||
- **統合戦略変更**: ai.gptは統合サーバー、ai.cardは独立サーバー
|
||||
- **仮想環境セットアップ**: `~/.config/syui/ai/card/venv/`
|
||||
- **起動スクリプト**: `uvicorn app.main:app --port 8000`
|
||||
|
||||
### ✅ ai.shell統合完了
|
||||
- **Claude Code風シェル実装**: `aigpt shell` コマンド
|
||||
- **MCP統合強化**: 14種類のツール(ai.gpt:9, ai.shell:5)
|
||||
- **プロジェクト仕様書**: `aishell.md` 読み込み機能
|
||||
- **環境対応改善**: prompt-toolkit代替でinput()フォールバック
|
||||
|
||||
### ✅ 前回セッションのバグ修正完了
|
||||
- **config listバグ修正**: `config.list_keys()`メソッド呼び出し修正
|
||||
- **仮想環境問題解決**: `pip install -e .`でeditable mode確立
|
||||
- **全CLIコマンド動作確認済み**
|
||||
|
||||
## 現在の状態
|
||||
|
||||
@@ -17,62 +37,75 @@
|
||||
- `relationships` - 関係一覧
|
||||
- `transmit` - 送信チェック(現在はprint出力)
|
||||
- `maintenance` - 日次メンテナンス
|
||||
- `config` - 設定管理
|
||||
- `config` - 設定管理(listバグ修正済み)
|
||||
- `schedule` - スケジューラー管理
|
||||
- `server` - MCP Server起動
|
||||
- `shell` - インタラクティブシェル(ai.shell統合)
|
||||
|
||||
3. **データ管理**
|
||||
- 保存場所: `~/.config/aigpt/`
|
||||
- 保存場所: `~/.config/syui/ai/gpt/`(名前規則統一)
|
||||
- 設定: `config.json`
|
||||
- データ: `data/` ディレクトリ内の各種JSONファイル
|
||||
- 仮想環境: `~/.config/syui/ai/gpt/venv/`
|
||||
|
||||
4. **スケジューラー**
|
||||
- Cron形式とインターバル形式対応
|
||||
- 5種類のタスクタイプ実装済み
|
||||
- バックグラウンド実行可能
|
||||
|
||||
5. **MCP Server**
|
||||
- 14種類のツールを公開(ai.gpt: 9種類、ai.shell: 5種類)
|
||||
- Claude Desktopなどから利用可能
|
||||
- ai.card統合オプション(--enable-card)
|
||||
5. **MCP Server統合アーキテクチャ**
|
||||
- **ai.gpt統合サーバー**: 14種類のツール(port 8001)
|
||||
- **ai.card独立サーバー**: 9種類のツール(port 8000)
|
||||
- Claude Desktop/Cursor連携対応
|
||||
- fastapi_mcp統一基盤
|
||||
|
||||
6. **ai.shell統合**
|
||||
6. **ai.shell統合(Claude Code風)**
|
||||
- インタラクティブシェルモード
|
||||
- シェルコマンド実行(!command形式)
|
||||
- AIコマンド(analyze, generate, explain)
|
||||
- aishell.md読み込み機能
|
||||
- 高度な補完機能(prompt-toolkit)
|
||||
- 環境適応型プロンプト(prompt-toolkit/input())
|
||||
|
||||
## 🚧 未実装・今後の課題
|
||||
## 🚧 次回開発の優先課題
|
||||
|
||||
### 短期的課題
|
||||
### 最優先: システム統合の最適化
|
||||
|
||||
1. **自律送信の実装**
|
||||
1. **ai.card重複コード削除**
|
||||
- **削除対象**: `src/aigpt/card_integration.py`(HTTPクライアント)
|
||||
- **削除対象**: ai.gptのMCPサーバーの`--enable-card`オプション
|
||||
- **理由**: ai.cardが独立MCPサーバーになったため不要
|
||||
- **統合方法**: ai.gpt(8001) → ai.card(8000) HTTP連携
|
||||
|
||||
2. **自律送信の実装**
|
||||
- 現在: コンソールにprint出力
|
||||
- TODO: atproto (Bluesky) への実際の投稿機能
|
||||
- 参考: ai.bot (Rust/seahorse) との連携も検討
|
||||
|
||||
2. **テストの追加**
|
||||
3. **環境セットアップ自動化**
|
||||
- 仮想環境自動作成スクリプト強化
|
||||
- 依存関係の自動解決
|
||||
- Claude Desktop設定例の提供
|
||||
|
||||
### 中期的課題
|
||||
|
||||
1. **テストの追加**
|
||||
- 単体テスト
|
||||
- 統合テスト
|
||||
- CI/CDパイプライン
|
||||
|
||||
3. **エラーハンドリングの改善**
|
||||
2. **エラーハンドリングの改善**
|
||||
- より詳細なエラーメッセージ
|
||||
- リトライ機構
|
||||
|
||||
### 中期的課題
|
||||
|
||||
1. **ai.botとの連携**
|
||||
3. **ai.botとの連携**
|
||||
- Rust側のAPIエンドポイント作成
|
||||
- 送信機能の委譲
|
||||
|
||||
2. **より高度な記憶要約**
|
||||
4. **より高度な記憶要約**
|
||||
- 現在: シンプルな要約
|
||||
- TODO: AIによる意味的な要約
|
||||
|
||||
3. **Webダッシュボード**
|
||||
5. **Webダッシュボード**
|
||||
- 関係性の可視化
|
||||
- 記憶の管理UI
|
||||
|
||||
@@ -89,6 +122,23 @@
|
||||
|
||||
## 次回開発時のエントリーポイント
|
||||
|
||||
### 🎯 最優先: ai.card重複削除
|
||||
```bash
|
||||
# 1. ai.card独立サーバー起動確認
|
||||
cd /Users/syui/ai/gpt/card/api
|
||||
source ~/.config/syui/ai/card/venv/bin/activate
|
||||
uvicorn app.main:app --port 8000
|
||||
|
||||
# 2. ai.gptから重複機能削除
|
||||
rm src/aigpt/card_integration.py
|
||||
# mcp_server.pyから--enable-cardオプション削除
|
||||
|
||||
# 3. 統合テスト
|
||||
aigpt server --port 8001 # ai.gpt統合サーバー
|
||||
curl "http://localhost:8001/get_memories" # ai.gpt機能確認
|
||||
curl "http://localhost:8000/get_gacha_stats" # ai.card機能確認
|
||||
```
|
||||
|
||||
### 1. 自律送信を実装する場合
|
||||
```python
|
||||
# src/aigpt/transmission.py を編集
|
||||
@@ -108,11 +158,10 @@
|
||||
# pytest設定を追加
|
||||
```
|
||||
|
||||
### 4. ai.shellの問題を修正する場合
|
||||
```python
|
||||
# src/aigpt/cli.py の shell コマンド
|
||||
# prompt-toolkitのターミナル検出問題を回避
|
||||
# 代替: simple input() または click.prompt()
|
||||
### 4. 環境セットアップを自動化する場合
|
||||
```bash
|
||||
# setup_venv.sh を強化
|
||||
# Claude Desktop設定例をdocs/に追加
|
||||
```
|
||||
|
||||
## 設計思想の要点(AI向け)
|
||||
@@ -123,12 +172,194 @@
|
||||
4. **環境影響**: AI運勢による日々の人格変動(固定的でない)
|
||||
5. **段階的実装**: まずCLI print → atproto投稿 → ai.bot連携
|
||||
|
||||
## 現在のコードベースの理解
|
||||
## 現在のアーキテクチャ理解(次回のAI向け)
|
||||
|
||||
### システム構成
|
||||
```
|
||||
Claude Desktop/Cursor
|
||||
↓
|
||||
ai.gpt MCP (port 8001) ←-- 統合サーバー(14ツール)
|
||||
├── ai.gpt機能: メモリ・関係性・人格(9ツール)
|
||||
├── ai.shell機能: シェル・ファイル操作(5ツール)
|
||||
└── HTTP client → ai.card MCP (port 8000)
|
||||
↓
|
||||
ai.card独立サーバー(9ツール)
|
||||
├── カード管理・ガチャ
|
||||
├── atproto同期
|
||||
└── PostgreSQL/SQLite
|
||||
```
|
||||
|
||||
### 技術スタック
|
||||
- **言語**: Python (typer CLI, fastapi_mcp)
|
||||
- **AI統合**: Ollama (ローカル) / OpenAI API
|
||||
- **AI統合**: Ollama (qwen2.5) / OpenAI API
|
||||
- **データ形式**: JSON(将来的にSQLite検討)
|
||||
- **認証**: atproto DID(未実装だが設計済み)
|
||||
- **統合**: ai.shell(Rust版から移行)、ai.card(MCP連携)
|
||||
- **認証**: atproto DID(設計済み・実装待ち)
|
||||
- **MCP統合**: fastapi_mcp統一基盤
|
||||
- **仮想環境**: `~/.config/syui/ai/{gpt,card}/venv/`
|
||||
|
||||
### 名前規則(重要)
|
||||
- **パッケージ**: `aigpt`
|
||||
- **コマンド**: `aigpt shell`, `aigpt server`
|
||||
- **ディレクトリ**: `~/.config/syui/ai/gpt/`
|
||||
- **ドメイン**: `ai.gpt`
|
||||
|
||||
### 即座に始める手順
|
||||
```bash
|
||||
# 1. 環境確認
|
||||
cd /Users/syui/ai/gpt
|
||||
source ~/.config/syui/ai/gpt/venv/bin/activate
|
||||
aigpt --help
|
||||
|
||||
# 2. 前回の成果物確認
|
||||
aigpt config list
|
||||
aigpt shell # Claude Code風環境
|
||||
|
||||
# 3. 詳細情報
|
||||
cat docs/ai_card_mcp_integration_summary.md
|
||||
cat docs/ai_shell_integration_summary.md
|
||||
```
|
||||
|
||||
このファイルを参照することで、次回の開発が迅速に開始でき、前回の作業内容を完全に理解できます。
|
||||
|
||||
## 現セッション完了事項 (2025/06/02)
|
||||
|
||||
### ✅ 記憶システム大幅改善完了
|
||||
|
||||
前回のAPI Errorで停止したChatGPTログ分析作業の続きを実行し、記憶システムを完全に再設計・実装した。
|
||||
|
||||
#### 新実装機能:
|
||||
|
||||
1. **スマート要約生成 (`create_smart_summary`)**
|
||||
- AI駆動によるテーマ別記憶要約
|
||||
- 会話パターン・技術的トピック・関係性進展の分析
|
||||
- メタデータ付きでの保存(期間、テーマ、記憶数)
|
||||
- フォールバック機能でAIが利用できない場合も対応
|
||||
|
||||
2. **コア記憶分析 (`create_core_memory`)**
|
||||
- 全記憶を分析して人格形成要素を抽出
|
||||
- ユーザーの特徴的なコミュニケーションスタイルを特定
|
||||
- 問題解決パターン・興味関心の深層分析
|
||||
- 永続保存される本質的な関係性記憶
|
||||
|
||||
3. **階層的記憶検索 (`get_contextual_memories`)**
|
||||
- CORE → SUMMARY → RECENT の優先順位付き検索
|
||||
- キーワードベースの関連性スコアリング
|
||||
- クエリに応じた動的な記憶重み付け
|
||||
- 構造化された記憶グループでの返却
|
||||
|
||||
4. **高度記憶検索 (`search_memories`)**
|
||||
- 複数キーワード対応の全文検索
|
||||
- メモリレベル別フィルタリング
|
||||
- マッチスコア付きでの結果返却
|
||||
|
||||
5. **コンテキスト対応AI応答**
|
||||
- `build_context_prompt`: 記憶に基づく文脈プロンプト生成
|
||||
- 人格状態・ムード・運勢を統合した応答
|
||||
- CORE記憶を常に参照した一貫性のある会話
|
||||
|
||||
6. **MCPサーバー拡張**
|
||||
- 新機能をすべてMCP API経由で利用可能
|
||||
- `/get_contextual_memories` - 文脈的記憶取得
|
||||
- `/search_memories` - 記憶検索
|
||||
- `/create_summary` - AI要約生成
|
||||
- `/create_core_memory` - コア記憶分析
|
||||
- `/get_context_prompt` - コンテキストプロンプト生成
|
||||
|
||||
7. **モデル拡張**
|
||||
- `Memory` モデルに `metadata` フィールド追加
|
||||
- 階層的記憶構造の完全サポート
|
||||
|
||||
#### 技術的特徴:
|
||||
- **AI統合**: ollama/OpenAI両対応でのインテリジェント分析
|
||||
- **フォールバック**: AI不使用時も基本機能は動作
|
||||
- **パターン分析**: ユーザー行動の自動分類・分析
|
||||
- **関連性スコア**: クエリとの関連度を数値化
|
||||
- **時系列分析**: 記憶の時間的発展を考慮
|
||||
|
||||
#### 前回議論の実現:
|
||||
ChatGPT 4,000件ログ分析から得られた知見を完全実装:
|
||||
- 階層的記憶(FULL_LOG → SUMMARY → CORE)
|
||||
- コンテキスト認識記憶(会話の流れを記憶)
|
||||
- 感情・関係性の記憶(変化パターンの追跡)
|
||||
- 実用的な記憶カテゴリ(ユーザー特徴・効果的応答・失敗回避)
|
||||
|
||||
### ✅ 追加完了事項 (同日)
|
||||
|
||||
**環境変数対応の改良**:
|
||||
- `OLLAMA_HOST`環境変数の自動読み込み対応
|
||||
- ai_provider.pyでの環境変数優先度実装
|
||||
- 設定ファイル → 環境変数 → デフォルトの階層的設定
|
||||
|
||||
**記憶システム完全動作確認**:
|
||||
- ollamaとの統合成功(gemma3:4bで確認)
|
||||
- 文脈的記憶検索の動作確認
|
||||
- ChatGPTインポートログからの記憶参照成功
|
||||
- AI応答での人格・ムード・運勢の反映確認
|
||||
|
||||
### 🚧 次回の課題
|
||||
- OLLAMA_HOSTの環境変数が完全に適用されない問題の解決
|
||||
- MCPサーバーのエラー解決(Internal Server Error)
|
||||
- qwen3:latestでの動作テスト完了
|
||||
- 記憶システムのコア機能(スマート要約・コア記憶分析)のAI統合テスト
|
||||
|
||||
## 現セッション完了事項 (2025/06/03 継続セッション)
|
||||
|
||||
### ✅ **前回API Error後の継続作業完了**
|
||||
|
||||
前回のセッションがAPI Errorで終了したが、今回正常に継続して以下を完了:
|
||||
|
||||
#### 🔧 **重要バグ修正**
|
||||
- **Memory model validation error 修正**: `importance_score`の浮動小数点精度問題を解決
|
||||
- 問題: `-5.551115123125783e-17`のような極小負数がvalidation errorを引き起こす
|
||||
- 解決: field validatorで極小値を0.0にクランプし、Field制約を除去
|
||||
- 結果: メモリ読み込み・全CLI機能が正常動作
|
||||
|
||||
#### 🧪 **システム動作確認完了**
|
||||
- **ai.gpt CLI**: 全コマンド正常動作確認済み
|
||||
- **記憶システム**: 階層的記憶(CORE→SUMMARY→RECENT)完全動作
|
||||
- **関係性進化**: syuiとの関係性が17.50→19.00に正常進展
|
||||
- **MCP Server**: 17種類のツール正常提供(port 8001)
|
||||
- **階層的記憶API**: `/get_contextual_memories`でblogクエリ正常動作
|
||||
|
||||
#### 💾 **記憶システム現状**
|
||||
- **CORE記憶**: blog開発、技術議論等の重要パターン記憶済み
|
||||
- **SUMMARY記憶**: AI×MCP、Qwen3解説等のテーマ別要約済み
|
||||
- **RECENT記憶**: 最新の記憶システムテスト履歴
|
||||
- **文脈検索**: キーワードベース関連性スコアリング動作確認
|
||||
|
||||
#### 🌐 **環境課題と対策**
|
||||
- **ollama接続**: OLLAMA_HOST環境変数は正しく設定済み(http://192.168.11.95:11434)
|
||||
- **AI統合課題**: qwen3:latestタイムアウト問題→記憶システム単体では正常動作
|
||||
- **フォールバック**: AI不使用時も記憶ベース応答で継続性確保
|
||||
|
||||
#### 🚀 **ai.bot統合完了 (同日追加)**
|
||||
- **MCP統合拡張**: 17→23ツールに増加(6個の新ツール追加)
|
||||
- **リモート実行機能**: systemd-nspawn隔離環境統合
|
||||
- `remote_shell`: ai.bot /sh機能との完全連携
|
||||
- `ai_bot_status`: サーバー状態確認とコンテナ情報取得
|
||||
- `isolated_python`: Python隔離実行環境
|
||||
- `isolated_analysis`: セキュアなファイル解析機能
|
||||
- **ai.shell拡張**: 新コマンド3種追加
|
||||
- `remote <command>`: 隔離コンテナでコマンド実行
|
||||
- `isolated <code>`: Python隔離実行
|
||||
- `aibot-status`: ai.botサーバー接続確認
|
||||
- **完全動作確認**: ヘルプ表示、コマンド補完、エラーハンドリング完了
|
||||
|
||||
#### 🏗️ **統合アーキテクチャ更新**
|
||||
```
|
||||
Claude Desktop/Cursor → ai.gpt MCP (port 8001, 23ツール)
|
||||
├── ai.gpt: メモリ・関係性・人格 (9ツール)
|
||||
├── ai.memory: 階層記憶・文脈検索 (5ツール)
|
||||
├── ai.shell: シェル・ファイル操作 (5ツール)
|
||||
├── ai.bot連携: リモート実行・隔離環境 (4ツール)
|
||||
└── ai.card連携: HTTP client → port 8000 (9ツール)
|
||||
```
|
||||
|
||||
#### 📋 **次回開発推奨事項**
|
||||
1. **ai.bot実サーバー**: 実際のai.botサーバー起動・連携テスト
|
||||
2. **隔離実行実証**: systemd-nspawn環境での実用性検証
|
||||
3. **ollama接続最適化**: タイムアウト問題の詳細調査・解決
|
||||
4. **AI要約機能**: maintenanceでのスマート要約・コア記憶生成テスト
|
||||
5. **セキュリティ強化**: 隔離実行の権限制御・サンドボックス検証
|
||||
|
||||
|
||||
このファイルを参照することで、次回の開発がスムーズに始められます。
|
564
README.md
564
README.md
@@ -1,21 +1,77 @@
|
||||
# ai.gpt - 自律的送信AI
|
||||
# ai.gpt - AI駆動記憶システム & 自律対話AI
|
||||
|
||||
存在子理論に基づく、関係性によって自発的にメッセージを送信するAIシステム。
|
||||
🧠 **革新的記憶システム** × 🤖 **自律的人格AI** × 🔗 **atproto統合**
|
||||
|
||||
## 中核概念
|
||||
ChatGPTの4,000件会話ログから学んだ「効果的な記憶構築」を完全実装した、真の記憶を持つAIシステム。
|
||||
|
||||
## 🎯 核心機能
|
||||
|
||||
### 📚 AI駆動階層記憶システム
|
||||
- **CORE記憶**: 人格形成要素の永続的記憶(AIが自動分析・抽出)
|
||||
- **SUMMARY記憶**: テーマ別スマート要約(AI駆動パターン分析)
|
||||
- **記憶検索**: コンテキスト認識による関連性スコアリング
|
||||
- **選択的忘却**: 重要度に基づく自然な記憶の減衰
|
||||
|
||||
### 🤝 進化する関係性システム
|
||||
- **唯一性**: atproto DIDと1:1で紐付き、改変不可能な人格
|
||||
- **不可逆性**: 関係性が壊れたら修復不可能(現実の人間関係と同じ)
|
||||
- **記憶の階層**: 完全ログ→AI要約→コア判定→選択的忘却
|
||||
- **時間減衰**: 自然な関係性の変化と送信閾値システム
|
||||
- **AI運勢**: 1-10のランダム値による日々の人格変動
|
||||
|
||||
### 🧬 統合アーキテクチャ
|
||||
- **fastapi-mcp統一基盤**: Claude Desktop/Cursor完全対応
|
||||
- **23種類のMCPツール**: 記憶・関係性・AI統合・シェル操作・リモート実行
|
||||
- **ai.shell統合**: Claude Code風インタラクティブ開発環境
|
||||
- **ai.bot連携**: systemd-nspawn隔離実行環境統合
|
||||
- **マルチAI対応**: ollama(qwen3/gemma3) + OpenAI統合
|
||||
|
||||
## 🚀 クイックスタート
|
||||
|
||||
### 1分で体験する記憶システム
|
||||
|
||||
```bash
|
||||
# 1. セットアップ(自動)
|
||||
cd /Users/syui/ai/gpt
|
||||
./setup_venv.sh
|
||||
|
||||
# 2. ollama + qwen3で記憶テスト
|
||||
aigpt chat syui "記憶システムのテストです" --provider ollama --model qwen3:latest
|
||||
|
||||
# 3. 記憶の確認
|
||||
aigpt status syui
|
||||
|
||||
# 4. インタラクティブシェル体験
|
||||
aigpt shell
|
||||
```
|
||||
|
||||
### 記憶システム体験デモ
|
||||
|
||||
```bash
|
||||
# ChatGPTログインポート(既存データを使用)
|
||||
aigpt import-chatgpt ./json/chatgpt.json --user-id syui
|
||||
|
||||
# AI記憶分析
|
||||
aigpt maintenance # スマート要約 + コア記憶生成
|
||||
|
||||
# 記憶に基づく対話
|
||||
aigpt chat syui "前回の議論について覚えていますか?" --provider ollama --model qwen3:latest
|
||||
|
||||
# 記憶検索
|
||||
# MCPサーバー経由でのコンテキスト記憶取得
|
||||
aigpt server --port 8001 &
|
||||
curl "http://localhost:8001/get_contextual_memories?query=ai&limit=5"
|
||||
```
|
||||
|
||||
## インストール
|
||||
|
||||
```bash
|
||||
# Python仮想環境を推奨
|
||||
python -m venv venv
|
||||
source venv/bin/activate # Windows: venv\Scripts\activate
|
||||
# 仮想環境セットアップ(推奨)
|
||||
cd /Users/syui/ai/gpt
|
||||
source ~/.config/syui/ai/gpt/venv/bin/activate
|
||||
pip install -e .
|
||||
|
||||
# または自動セットアップ
|
||||
./setup_venv.sh
|
||||
```
|
||||
|
||||
## 設定
|
||||
@@ -36,6 +92,7 @@ aigpt config list
|
||||
### データ保存場所
|
||||
- 設定: `~/.config/syui/ai/gpt/config.json`
|
||||
- データ: `~/.config/syui/ai/gpt/data/`
|
||||
- 仮想環境: `~/.config/syui/ai/gpt/venv/`
|
||||
|
||||
## 使い方
|
||||
|
||||
@@ -77,6 +134,16 @@ aigpt maintenance
|
||||
aigpt relationships
|
||||
```
|
||||
|
||||
### ChatGPTデータインポート
|
||||
```bash
|
||||
# ChatGPTの会話履歴をインポート
|
||||
aigpt import-chatgpt ./json/chatgpt.json --user-id "your_user_id"
|
||||
|
||||
# インポート後の確認
|
||||
aigpt status
|
||||
aigpt relationships
|
||||
```
|
||||
|
||||
## データ構造
|
||||
|
||||
デフォルトでは `~/.config/syui/ai/gpt/` に以下のファイルが保存されます:
|
||||
@@ -95,44 +162,132 @@ aigpt relationships
|
||||
- 時間経過で自然減衰
|
||||
- 大きなネガティブな相互作用で破壊される可能性
|
||||
|
||||
## ai.shell統合
|
||||
|
||||
インタラクティブシェルモード(Claude Code風の体験):
|
||||
## 🖥️ ai.shell統合 - Claude Code風開発環境
|
||||
|
||||
### 🚀 **基本起動**
|
||||
```bash
|
||||
# デフォルト(qwen2.5使用)
|
||||
aigpt shell
|
||||
|
||||
# シェル内で使えるコマンド:
|
||||
# help - コマンド一覧
|
||||
# !<command> - シェルコマンド実行(例: !ls, !pwd)
|
||||
# analyze <file> - ファイルをAIで分析
|
||||
# generate <desc> - コード生成
|
||||
# explain <topic> - 概念の説明
|
||||
# load - aishell.mdプロジェクトファイルを読み込み
|
||||
# status - AI状態確認
|
||||
# fortune - AI運勢確認
|
||||
# clear - 画面クリア
|
||||
# exit/quit - 終了
|
||||
# qwen2.5-coder使用(コード生成に最適)
|
||||
aigpt shell --model qwen2.5-coder:latest --provider ollama
|
||||
|
||||
# 通常のメッセージも送れます
|
||||
ai.shell> こんにちは、今日は何をしましょうか?
|
||||
# qwen3使用(高度な対話)
|
||||
aigpt shell --model qwen3:latest --provider ollama
|
||||
|
||||
# OpenAI使用
|
||||
aigpt shell --model gpt-4o-mini --provider openai
|
||||
```
|
||||
|
||||
## MCP Server
|
||||
|
||||
### サーバー起動
|
||||
### 📋 **利用可能コマンド**
|
||||
```bash
|
||||
# Ollamaを使用(デフォルト)
|
||||
aigpt server --model qwen2.5 --provider ollama
|
||||
# === プロジェクト管理 ===
|
||||
load # aishell.md読み込み(AIがプロジェクト理解)
|
||||
status # AI状態・関係性確認
|
||||
fortune # AI運勢確認(人格に影響)
|
||||
relationships # 全関係性一覧
|
||||
|
||||
# === AI開発支援 ===
|
||||
analyze <file> # ファイル分析・コードレビュー
|
||||
generate <description> # コード生成(qwen2.5-coder推奨)
|
||||
explain <topic> # 概念・技術説明
|
||||
|
||||
# === シェル操作 ===
|
||||
!<command> # シェルコマンド実行
|
||||
!git status # git操作
|
||||
!ls -la # ファイル確認
|
||||
!mkdir project # ディレクトリ作成
|
||||
!pytest tests/ # テスト実行
|
||||
|
||||
# === リモート実行(ai.bot統合)===
|
||||
remote <command> # systemd-nspawn隔離コンテナでコマンド実行
|
||||
isolated <code> # Python隔離実行環境
|
||||
aibot-status # ai.botサーバー接続確認
|
||||
|
||||
# === インタラクティブ対話 ===
|
||||
help # コマンド一覧
|
||||
clear # 画面クリア
|
||||
exit/quit # 終了
|
||||
<任意のメッセージ> # 自由なAI対話
|
||||
```
|
||||
|
||||
### 🎯 **コマンド使用例**
|
||||
```bash
|
||||
ai.shell> load
|
||||
# → aishell.mdを読み込み、AIがプロジェクト目標を記憶
|
||||
|
||||
ai.shell> generate Python FastAPI CRUD for User model
|
||||
# → 完全なCRUD API コードを生成
|
||||
|
||||
ai.shell> analyze src/main.py
|
||||
# → コード品質・改善点を分析
|
||||
|
||||
ai.shell> !git log --oneline -5
|
||||
# → 最近のコミット履歴を表示
|
||||
|
||||
ai.shell> remote ls -la /tmp
|
||||
# → ai.bot隔離コンテナでディレクトリ確認
|
||||
|
||||
ai.shell> isolated print("Hello from isolated environment!")
|
||||
# → Python隔離実行でHello World
|
||||
|
||||
ai.shell> aibot-status
|
||||
# → ai.botサーバー接続状態とコンテナ情報確認
|
||||
|
||||
ai.shell> このAPIのセキュリティを改善してください
|
||||
# → 記憶に基づく具体的なセキュリティ改善提案
|
||||
|
||||
ai.shell> explain async/await in Python
|
||||
# → 非同期プログラミングの詳細説明
|
||||
```
|
||||
|
||||
## MCP Server統合アーキテクチャ
|
||||
|
||||
### ai.gpt統合サーバー
|
||||
```bash
|
||||
# ai.gpt統合サーバー起動(port 8001)
|
||||
aigpt server --model qwen2.5 --provider ollama --port 8001
|
||||
|
||||
# OpenAIを使用
|
||||
aigpt server --model gpt-4o-mini --provider openai
|
||||
aigpt server --model gpt-4o-mini --provider openai --port 8001
|
||||
```
|
||||
|
||||
# カスタムポート
|
||||
aigpt server --port 8080
|
||||
### ai.card独立サーバー
|
||||
```bash
|
||||
# ai.card独立サーバー起動(port 8000)
|
||||
cd card/api
|
||||
source ~/.config/syui/ai/card/venv/bin/activate
|
||||
uvicorn app.main:app --port 8000
|
||||
```
|
||||
|
||||
# ai.card統合を有効化
|
||||
aigpt server --enable-card
|
||||
### ai.bot接続(リモート実行環境)
|
||||
```bash
|
||||
# ai.bot起動(port 8080、別途必要)
|
||||
# systemd-nspawn隔離コンテナでコマンド実行
|
||||
```
|
||||
|
||||
### アーキテクチャ構成
|
||||
```
|
||||
Claude Desktop/Cursor
|
||||
↓
|
||||
ai.gpt統合サーバー (port 8001) ← 23ツール
|
||||
├── ai.gpt機能: メモリ・関係性・人格 (9ツール)
|
||||
├── ai.shell機能: シェル・ファイル操作 (5ツール)
|
||||
├── ai.memory機能: 階層記憶・文脈検索 (5ツール)
|
||||
├── ai.bot連携: リモート実行・隔離環境 (4ツール)
|
||||
└── HTTP client → ai.card独立サーバー (port 8000)
|
||||
↓
|
||||
ai.card専用ツール (9ツール)
|
||||
├── カード管理・ガチャ
|
||||
├── atproto同期
|
||||
└── PostgreSQL/SQLite
|
||||
|
||||
ai.gpt統合サーバー → ai.bot (port 8080)
|
||||
↓
|
||||
systemd-nspawn container
|
||||
├── Arch Linux隔離環境
|
||||
├── SSH server
|
||||
└── セキュアコマンド実行
|
||||
```
|
||||
|
||||
### AIプロバイダーを使った会話
|
||||
@@ -148,7 +303,7 @@ aigpt chat "did:plc:xxxxx" "今日の調子はどう?" --provider openai --mod
|
||||
|
||||
サーバーが起動すると、以下のツールがAIから利用可能になります:
|
||||
|
||||
**ai.gpt ツール:**
|
||||
**ai.gpt ツール (9個):**
|
||||
- `get_memories` - アクティブな記憶を取得
|
||||
- `get_relationship` - 特定ユーザーとの関係を取得
|
||||
- `get_all_relationships` - すべての関係を取得
|
||||
@@ -159,19 +314,35 @@ aigpt chat "did:plc:xxxxx" "今日の調子はどう?" --provider openai --mod
|
||||
- `summarize_memories` - 記憶を要約
|
||||
- `run_maintenance` - メンテナンス実行
|
||||
|
||||
**ai.shell ツール:**
|
||||
**ai.memory ツール (5個):**
|
||||
- `get_contextual_memories` - 文脈的記憶検索
|
||||
- `search_memories` - キーワード記憶検索
|
||||
- `create_summary` - AI駆動記憶要約生成
|
||||
- `create_core_memory` - コア記憶分析・抽出
|
||||
- `get_context_prompt` - 記憶ベース文脈プロンプト
|
||||
|
||||
**ai.shell ツール (5個):**
|
||||
- `execute_command` - シェルコマンド実行
|
||||
- `analyze_file` - ファイルのAI分析
|
||||
- `write_file` - ファイル書き込み
|
||||
- `read_project_file` - プロジェクトファイル読み込み
|
||||
- `list_files` - ファイル一覧
|
||||
|
||||
**ai.card ツール(--enable-card時):**
|
||||
- `get_user_cards` - ユーザーのカード取得
|
||||
- `draw_card` - カードを引く(ガチャ)
|
||||
- `get_card_details` - カード詳細情報
|
||||
- `sync_cards_atproto` - atproto同期
|
||||
- `analyze_card_collection` - コレクション分析
|
||||
**ai.bot連携ツール (4個):**
|
||||
- `remote_shell` - 隔離コンテナでコマンド実行
|
||||
- `ai_bot_status` - ai.botサーバー状態確認
|
||||
- `isolated_python` - Python隔離実行
|
||||
- `isolated_analysis` - ファイル解析(隔離環境)
|
||||
|
||||
### ai.card独立サーバーとの連携
|
||||
|
||||
ai.cardは独立したMCPサーバーとして動作:
|
||||
- **ポート**: 8000
|
||||
- **9つのMCPツール**: カード管理・ガチャ・atproto同期等
|
||||
- **データベース**: PostgreSQL/SQLite
|
||||
- **起動**: `uvicorn app.main:app --port 8000`
|
||||
|
||||
ai.gptサーバーからHTTP経由で連携可能
|
||||
|
||||
## 環境変数
|
||||
|
||||
@@ -247,9 +418,310 @@ aigpt schedule run
|
||||
- `relationship_decay` - 関係性の時間減衰
|
||||
- `memory_summary` - 記憶の要約作成
|
||||
|
||||
## 次のステップ
|
||||
## 🚀 最新機能 (2025/06/02 大幅更新完了)
|
||||
|
||||
- atprotoへの実送信機能実装
|
||||
- systemdサービス化
|
||||
- Docker対応
|
||||
- Webダッシュボード
|
||||
### ✅ **革新的記憶システム完成**
|
||||
#### 🧠 AI駆動記憶機能
|
||||
- **スマート要約生成**: AIによるテーマ別記憶要約(`create_smart_summary`)
|
||||
- **コア記憶分析**: 人格形成要素の自動抽出(`create_core_memory`)
|
||||
- **階層的記憶検索**: CORE→SUMMARY→RECENT優先度システム
|
||||
- **コンテキスト認識**: クエリベース関連性スコアリング
|
||||
- **文脈プロンプト**: 記憶に基づく一貫性のある対話生成
|
||||
|
||||
#### 🔗 完全統合アーキテクチャ
|
||||
- **ChatGPTインポート**: 4,000件ログからの記憶構築実証
|
||||
- **マルチAI対応**: ollama(qwen3:latest/gemma3:4b) + OpenAI完全統合
|
||||
- **環境変数対応**: `OLLAMA_HOST`自動読み込み
|
||||
- **MCP統合**: 23種類のツール(記憶5種+関係性4種+AI3種+シェル5種+ai.bot4種+項目管理2種)
|
||||
|
||||
#### 🧬 動作確認済み
|
||||
- **記憶参照**: ChatGPTログからの文脈的記憶活用
|
||||
- **人格統合**: ムード・運勢・記憶に基づく応答生成
|
||||
- **関係性進化**: 記憶に基づく段階的信頼構築
|
||||
- **AI協働**: qwen3との記憶システム完全連携
|
||||
|
||||
### 🎯 **新MCPツール**
|
||||
```bash
|
||||
# 新記憶システムツール
|
||||
curl "http://localhost:8001/get_contextual_memories?query=programming&limit=5"
|
||||
curl "http://localhost:8001/search_memories" -d '{"keywords":["memory","AI"]}'
|
||||
curl "http://localhost:8001/create_summary" -d '{"user_id":"syui"}'
|
||||
curl "http://localhost:8001/create_core_memory" -d '{}'
|
||||
curl "http://localhost:8001/get_context_prompt" -d '{"user_id":"syui","message":"test"}'
|
||||
```
|
||||
|
||||
### 🧪 **AIとの記憶テスト**
|
||||
```bash
|
||||
# qwen3での記憶システムテスト
|
||||
aigpt chat syui "前回の会話を覚えていますか?" --provider ollama --model qwen3:latest
|
||||
|
||||
# 記憶に基づくスマート要約生成
|
||||
aigpt maintenance # AI要約を自動実行
|
||||
|
||||
# コンテキスト検索テスト
|
||||
aigpt chat syui "記憶システムについて" --provider ollama --model qwen3:latest
|
||||
```
|
||||
|
||||
## 🔥 **NEW: Claude Code的継続開発機能** (2025/06/03 完成)
|
||||
|
||||
### 🚀 **プロジェクト管理システム完全実装**
|
||||
ai.shellに真のClaude Code風継続開発機能を実装しました:
|
||||
|
||||
#### 📊 **プロジェクト分析機能**
|
||||
```bash
|
||||
ai.shell> project-status
|
||||
# ✓ プロジェクト構造自動分析
|
||||
# Language: Python, Framework: FastAPI
|
||||
# 1268クラス, 5656関数, 22 API endpoints, 129 async functions
|
||||
# 57個のファイル変更を検出
|
||||
|
||||
ai.shell> suggest-next
|
||||
# ✓ AI駆動開発提案
|
||||
# 1. 継続的な単体テストと統合テスト実装
|
||||
# 2. API エンドポイントのセキュリティ強化
|
||||
# 3. データベース最適化とキャッシュ戦略
|
||||
```
|
||||
|
||||
#### 🧠 **コンテキスト認識開発**
|
||||
```bash
|
||||
ai.shell> continuous
|
||||
# ✓ 継続開発モード開始
|
||||
# プロジェクト文脈読込: 21,986文字
|
||||
# claude.md + aishell.md + pyproject.toml + 依存関係を解析
|
||||
# AIがプロジェクト全体を理解した状態で開発支援
|
||||
|
||||
ai.shell> analyze src/aigpt/project_manager.py
|
||||
# ✓ プロジェクト文脈を考慮したファイル分析
|
||||
# - コード品質評価
|
||||
# - プロジェクトとの整合性チェック
|
||||
# - 改善提案と潜在的問題の指摘
|
||||
|
||||
ai.shell> generate Create a test function for ContinuousDeveloper
|
||||
# ✓ プロジェクト文脈を考慮したコード生成
|
||||
# FastAPI, Python, 既存パターンに合わせた実装を自動生成
|
||||
```
|
||||
|
||||
#### 🛠️ **実装詳細**
|
||||
- **ProjectState**: ファイル変更検出・プロジェクト状態追跡
|
||||
- **ContinuousDeveloper**: AI駆動プロジェクト分析・提案・コード生成
|
||||
- **プロジェクト文脈**: claude.md/aishell.md/pyproject.toml等を自動読込
|
||||
- **言語検出**: Python/JavaScript/Rust等の自動判定
|
||||
- **フレームワーク分析**: FastAPI/Django/React等の依存関係検出
|
||||
- **コードパターン**: 既存の設計パターン学習・適用
|
||||
|
||||
#### ✅ **動作確認済み機能**
|
||||
- ✓ プロジェクト構造分析 (Language: Python, Framework: FastAPI)
|
||||
- ✓ ファイル変更検出 (57個の変更検出)
|
||||
- ✓ プロジェクト文脈読込 (21,986文字)
|
||||
- ✓ AI駆動提案機能 (具体的な次ステップ提案)
|
||||
- ✓ 文脈認識ファイル分析 (コード品質・整合性評価)
|
||||
- ✓ プロジェクト文脈考慮コード生成 (FastAPI準拠コード生成)
|
||||
|
||||
### 🎯 **Claude Code風ワークフロー**
|
||||
```bash
|
||||
# 1. プロジェクト理解
|
||||
aigpt shell --model qwen2.5-coder:latest --provider ollama
|
||||
ai.shell> load # プロジェクト仕様読み込み
|
||||
ai.shell> project-status # 現在の構造分析
|
||||
|
||||
# 2. AI駆動開発
|
||||
ai.shell> suggest-next # 次のタスク提案
|
||||
ai.shell> continuous # 継続開発モード開始
|
||||
|
||||
# 3. 文脈認識開発
|
||||
ai.shell> analyze <file> # プロジェクト文脈でファイル分析
|
||||
ai.shell> generate <desc> # 文脈考慮コード生成
|
||||
ai.shell> 具体的な開発相談 # 記憶+文脈で最適な提案
|
||||
|
||||
# 4. 継続的改善
|
||||
# AIがプロジェクト全体を理解して一貫した開発支援
|
||||
# 前回の議論・決定事項を記憶して適切な提案継続
|
||||
```
|
||||
|
||||
### 💡 **従来のai.shellとの違い**
|
||||
| 機能 | 従来 | 新実装 |
|
||||
|------|------|--------|
|
||||
| プロジェクト理解 | 単発 | 構造分析+文脈保持 |
|
||||
| コード生成 | 汎用 | プロジェクト文脈考慮 |
|
||||
| 開発提案 | なし | AI駆動次ステップ提案 |
|
||||
| ファイル分析 | 単体 | 整合性+改善提案 |
|
||||
| 変更追跡 | なし | 自動検出+影響分析 |
|
||||
|
||||
**真のClaude Code化完成!** 記憶システム + プロジェクト文脈認識で、一貫した長期開発支援が可能になりました。
|
||||
|
||||
## 🛠️ ai.shell継続的開発 - 実践Example
|
||||
|
||||
### 🚀 **プロジェクト開発ワークフロー実例**
|
||||
|
||||
#### 📝 **Example 1: RESTful API開発**
|
||||
```bash
|
||||
# 1. ai.shellでプロジェクト開始(qwen2.5-coder使用)
|
||||
aigpt shell --model qwen2.5-coder:latest --provider ollama
|
||||
|
||||
# 2. プロジェクト仕様を読み込んでAIに理解させる
|
||||
ai.shell> load
|
||||
# → aishell.mdを自動検索・読み込み、AIがプロジェクト目標を記憶
|
||||
|
||||
# 3. プロジェクト構造確認
|
||||
ai.shell> !ls -la
|
||||
ai.shell> !git status
|
||||
|
||||
# 4. ユーザー管理APIの設計を相談
|
||||
ai.shell> RESTful APIでユーザー管理機能を作りたいです。設計について相談できますか?
|
||||
|
||||
# 5. AIの提案を基にコード生成
|
||||
ai.shell> generate Python FastAPI user management with CRUD operations
|
||||
|
||||
# 6. 生成されたコードをファイルに保存
|
||||
ai.shell> !mkdir -p src/api
|
||||
ai.shell> !touch src/api/users.py
|
||||
|
||||
# 7. 実装されたコードを分析・改善
|
||||
ai.shell> analyze src/api/users.py
|
||||
ai.shell> セキュリティ面での改善点を教えてください
|
||||
|
||||
# 8. テストコード生成
|
||||
ai.shell> generate pytest test cases for the user management API
|
||||
|
||||
# 9. 隔離環境でテスト実行
|
||||
ai.shell> remote python -m pytest tests/ -v
|
||||
ai.shell> isolated import requests; print(requests.get("http://localhost:8000/health").status_code)
|
||||
|
||||
# 10. 段階的コミット
|
||||
ai.shell> !git add .
|
||||
ai.shell> !git commit -m "Add user management API with security improvements"
|
||||
|
||||
# 11. 継続的な改善相談
|
||||
ai.shell> 次はデータベース設計について相談したいです
|
||||
```
|
||||
|
||||
#### 🔄 **Example 2: 機能拡張と リファクタリング**
|
||||
```bash
|
||||
# ai.shell継続セッション(記憶システムが前回の議論を覚えている)
|
||||
aigpt shell --model qwen2.5-coder:latest --provider ollama
|
||||
|
||||
# AIが前回のAPI開発を記憶して続きから開始
|
||||
ai.shell> status
|
||||
# Relationship Status: acquaintance (関係性が進展)
|
||||
# Score: 25.00 / 100.0
|
||||
|
||||
# 前回の続きから自然に議論
|
||||
ai.shell> 前回作ったユーザー管理APIに認証機能を追加したいです
|
||||
|
||||
# AIが前回のコードを考慮した提案
|
||||
ai.shell> generate JWT authentication middleware for our FastAPI
|
||||
|
||||
# 既存コードとの整合性チェック
|
||||
ai.shell> analyze src/api/users.py
|
||||
ai.shell> この認証システムと既存のAPIの統合方法は?
|
||||
|
||||
# 段階的実装
|
||||
ai.shell> explain JWT token flow in our architecture
|
||||
ai.shell> generate authentication decorator for protected endpoints
|
||||
|
||||
# リファクタリング提案
|
||||
ai.shell> 現在のコード構造で改善できる点はありますか?
|
||||
ai.shell> generate improved project structure for scalability
|
||||
|
||||
# データベース設計相談
|
||||
ai.shell> explain SQLAlchemy models for user authentication
|
||||
ai.shell> generate database migration scripts
|
||||
|
||||
# 隔離環境での安全なテスト
|
||||
ai.shell> remote alembic upgrade head
|
||||
ai.shell> isolated import sqlalchemy; print("DB connection test")
|
||||
```
|
||||
|
||||
#### 🎯 **Example 3: バグ修正と最適化**
|
||||
```bash
|
||||
# 開発継続(AIが開発履歴を完全記憶)
|
||||
aigpt shell --model qwen2.5-coder:latest --provider ollama
|
||||
|
||||
# 関係性が更に進展(close_friend level)
|
||||
ai.shell> status
|
||||
# Relationship Status: close_friend
|
||||
# Score: 45.00 / 100.0
|
||||
|
||||
# バグレポートと分析
|
||||
ai.shell> API のレスポンス時間が遅いです。パフォーマンス分析をお願いします
|
||||
ai.shell> analyze src/api/users.py
|
||||
|
||||
# AIによる最適化提案
|
||||
ai.shell> generate database query optimization for user lookup
|
||||
ai.shell> explain async/await patterns for better performance
|
||||
|
||||
# テスト駆動改善
|
||||
ai.shell> generate performance test cases
|
||||
ai.shell> !pytest tests/ -v --benchmark
|
||||
|
||||
# キャッシュ戦略相談
|
||||
ai.shell> Redis caching strategy for our user API?
|
||||
ai.shell> generate caching layer implementation
|
||||
|
||||
# 本番デプロイ準備
|
||||
ai.shell> explain Docker containerization for our API
|
||||
ai.shell> generate Dockerfile and docker-compose.yml
|
||||
ai.shell> generate production environment configurations
|
||||
|
||||
# 隔離環境でのデプロイテスト
|
||||
ai.shell> remote docker build -t myapi .
|
||||
ai.shell> isolated os.system("docker run --rm myapi python -c 'print(\"Container works!\")'")
|
||||
ai.shell> aibot-status # デプロイ環境確認
|
||||
```
|
||||
|
||||
### 🧠 **記憶システム活用のメリット**
|
||||
|
||||
#### 💡 **継続性のある開発体験**
|
||||
- **文脈保持**: 前回の議論やコードを記憶して一貫した提案
|
||||
- **関係性進化**: 協働を通じて信頼関係が構築され、より深い提案
|
||||
- **段階的成長**: プロジェクトの発展を理解した適切なレベルの支援
|
||||
|
||||
#### 🔧 **実践的な使い方**
|
||||
```bash
|
||||
# 日々の開発ルーチン
|
||||
aigpt shell --model qwen2.5-coder:latest --provider ollama
|
||||
ai.shell> load # プロジェクト状況をAIに再確認
|
||||
ai.shell> !git log --oneline -5 # 最近の変更を確認
|
||||
ai.shell> 今日は何から始めましょうか? # AIが文脈を考慮した提案
|
||||
|
||||
# 長期プロジェクトでの活用
|
||||
ai.shell> 先週議論したアーキテクチャの件、覚えていますか?
|
||||
ai.shell> あのときの懸念点は解決されましたか?
|
||||
ai.shell> 次のマイルストーンに向けて何が必要でしょうか?
|
||||
|
||||
# チーム開発での知識共有
|
||||
ai.shell> 新しいメンバーに説明するための設計書を生成してください
|
||||
ai.shell> このプロジェクトの技術的負債について分析してください
|
||||
```
|
||||
|
||||
### 🚧 次のステップ
|
||||
- **自律送信**: atproto実装(記憶ベース判定)
|
||||
- **記憶可視化**: Webダッシュボード(関係性グラフ)
|
||||
- **分散記憶**: atproto上でのユーザーデータ主権
|
||||
- **AI協働**: 複数AIでの記憶共有プロトコル
|
||||
|
||||
## トラブルシューティング
|
||||
|
||||
### 環境セットアップ
|
||||
```bash
|
||||
# 仮想環境の確認
|
||||
source ~/.config/syui/ai/gpt/venv/bin/activate
|
||||
aigpt --help
|
||||
|
||||
# 設定の確認
|
||||
aigpt config list
|
||||
|
||||
# データの確認
|
||||
ls ~/.config/syui/ai/gpt/data/
|
||||
```
|
||||
|
||||
### MCPサーバー動作確認
|
||||
```bash
|
||||
# ai.gpt統合サーバー (14ツール)
|
||||
aigpt server --port 8001
|
||||
curl http://localhost:8001/docs
|
||||
|
||||
# ai.card独立サーバー (9ツール)
|
||||
cd card/api && uvicorn app.main:app --port 8000
|
||||
curl http://localhost:8000/health
|
||||
```
|
2
card
2
card
Submodule card updated: 6dbe630b9d...6cd8014f80
20
claude.md
20
claude.md
@@ -321,6 +321,26 @@ ai.card (iOS,Web,API) ←→ ai.verse (UEゲーム世界)
|
||||
- ai.bot連携: 新規bot_connector.py作成
|
||||
- テスト: tests/ディレクトリ追加
|
||||
|
||||
## ai.card実装状況(2025/01/06)
|
||||
|
||||
### 完成した機能
|
||||
- 独立MCPサーバー実装(FastAPI + fastapi-mcp)
|
||||
- SQLiteデータベース統合
|
||||
- ガチャシステム・カード管理機能
|
||||
- 9種類のMCPツール公開
|
||||
- 仮想環境・起動スクリプト整備
|
||||
|
||||
### 現在の課題
|
||||
- atproto SessionString API変更対応
|
||||
- PostgreSQL依存関係(Docker化で解決予定)
|
||||
- supabase httpxバージョン競合
|
||||
|
||||
### 開発時の作業分担
|
||||
- **ai.gptで起動**: MCP/バックエンド作業(API、データベース)
|
||||
- **ai.cardで起動**: iOS/Web作業(UI実装、フロントエンド)
|
||||
|
||||
詳細は `./card/claude.md` を参照
|
||||
|
||||
# footer
|
||||
|
||||
© syui
|
||||
|
244
docs/ai_card_mcp_integration_summary.md
Normal file
244
docs/ai_card_mcp_integration_summary.md
Normal file
@@ -0,0 +1,244 @@
|
||||
# ai.card MCP統合作業完了報告 (2025/01/06)
|
||||
|
||||
## 作業概要
|
||||
ai.cardプロジェクトに独立したMCPサーバー実装を追加し、fastapi_mcpベースでカードゲーム機能をMCPツールとして公開。
|
||||
|
||||
## 実装完了機能
|
||||
|
||||
### 1. MCP依存関係追加
|
||||
**場所**: `card/api/requirements.txt`
|
||||
|
||||
**追加項目**:
|
||||
```txt
|
||||
fastapi-mcp==0.1.0
|
||||
```
|
||||
|
||||
### 2. ai.card MCPサーバー実装
|
||||
**場所**: `card/api/app/mcp_server.py`
|
||||
|
||||
**機能**:
|
||||
- FastAPI + fastapi_mcp統合
|
||||
- 独立したMCPサーバークラス `AICardMcpServer`
|
||||
- 環境変数による有効/無効切り替え
|
||||
|
||||
**公開MCPツール (9個)**:
|
||||
|
||||
**カード管理系 (5個)**:
|
||||
- `get_user_cards` - ユーザーのカード一覧取得
|
||||
- `draw_card` - ガチャでカード取得
|
||||
- `get_card_details` - カード詳細情報取得
|
||||
- `analyze_card_collection` - コレクション分析
|
||||
- `get_unique_registry` - ユニークカード登録状況
|
||||
|
||||
**システム系 (3個)**:
|
||||
- `sync_cards_atproto` - atproto同期
|
||||
- `get_gacha_stats` - ガチャシステム統計
|
||||
- 既存のFastAPI REST API(/api/v1/*)
|
||||
|
||||
**atproto連携系 (1個)**:
|
||||
- `sync_cards_atproto` - カードデータのatproto PDS同期
|
||||
|
||||
### 3. メインアプリ統合
|
||||
**場所**: `card/api/app/main.py`
|
||||
|
||||
**変更内容**:
|
||||
```python
|
||||
# MCP統合
|
||||
from app.mcp_server import AICardMcpServer
|
||||
|
||||
enable_mcp = os.getenv("ENABLE_MCP", "true").lower() == "true"
|
||||
mcp_server = AICardMcpServer(enable_mcp=enable_mcp)
|
||||
app = mcp_server.get_app()
|
||||
```
|
||||
|
||||
**動作確認**:
|
||||
- `ENABLE_MCP=true` (デフォルト): MCPサーバー有効
|
||||
- `ENABLE_MCP=false`: 通常のFastAPIのみ
|
||||
|
||||
## 技術実装詳細
|
||||
|
||||
### アーキテクチャ設計
|
||||
```
|
||||
ai.card/
|
||||
├── api/app/main.py # FastAPIアプリ + MCP統合
|
||||
├── api/app/mcp_server.py # 独立MCPサーバー
|
||||
├── api/app/routes/ # REST API (既存)
|
||||
├── api/app/services/ # ビジネスロジック (既存)
|
||||
├── api/app/repositories/ # データアクセス (既存)
|
||||
└── api/requirements.txt # fastapi-mcp追加
|
||||
```
|
||||
|
||||
### MCPツール実装パターン
|
||||
```python
|
||||
@self.app.get("/tool_name", operation_id="tool_name")
|
||||
async def tool_name(
|
||||
param: str,
|
||||
session: AsyncSession = Depends(get_session)
|
||||
) -> Dict[str, Any]:
|
||||
"""Tool description"""
|
||||
try:
|
||||
# ビジネスロジック実行
|
||||
result = await service.method(param)
|
||||
return {"success": True, "data": result}
|
||||
except Exception as e:
|
||||
logger.error(f"Error: {e}")
|
||||
return {"error": str(e)}
|
||||
```
|
||||
|
||||
### 既存システムとの統合
|
||||
- **REST API**: 既存の `/api/v1/*` エンドポイント保持
|
||||
- **データアクセス**: 既存のRepository/Serviceパターン再利用
|
||||
- **認証**: 既存のDID認証システム利用
|
||||
- **データベース**: 既存のPostgreSQL + SQLAlchemy
|
||||
|
||||
## 起動方法
|
||||
|
||||
### 1. 環境セットアップ
|
||||
```bash
|
||||
cd /Users/syui/ai/gpt/card/api
|
||||
|
||||
# 仮想環境作成 (推奨)
|
||||
python -m venv ~/.config/syui/ai/card/venv
|
||||
source ~/.config/syui/ai/card/venv/bin/activate
|
||||
|
||||
# 依存関係インストール
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 2. サーバー起動
|
||||
```bash
|
||||
# MCP有効 (デフォルト)
|
||||
python -m app.main
|
||||
|
||||
# または
|
||||
ENABLE_MCP=true uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
|
||||
# MCP無効
|
||||
ENABLE_MCP=false uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
### 3. 動作確認
|
||||
```bash
|
||||
# ヘルスチェック
|
||||
curl http://localhost:8000/health
|
||||
|
||||
# MCP有効時の応答例
|
||||
{
|
||||
"status": "healthy",
|
||||
"mcp_enabled": true,
|
||||
"mcp_endpoint": "/mcp"
|
||||
}
|
||||
|
||||
# API仕様確認
|
||||
curl http://localhost:8000/docs
|
||||
```
|
||||
|
||||
## MCPクライアント連携
|
||||
|
||||
### ai.gptからの接続
|
||||
```python
|
||||
# ai.gptのcard_integration.pyで使用
|
||||
api_base_url = "http://localhost:8000"
|
||||
|
||||
# MCPツール経由でアクセス
|
||||
response = await client.get(f"{api_base_url}/get_user_cards?did=did:plc:...")
|
||||
```
|
||||
|
||||
### Claude Desktop等での利用
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"aicard": {
|
||||
"command": "uvicorn",
|
||||
"args": ["app.main:app", "--host", "localhost", "--port", "8000"],
|
||||
"cwd": "/Users/syui/ai/gpt/card/api"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 既知の制約と注意点
|
||||
|
||||
### 1. 依存関係
|
||||
- **fastapi-mcp**: 現在のバージョンは0.1.0(初期実装)
|
||||
- **Python環境**: システム環境では外部管理エラーが発生
|
||||
- **推奨**: 仮想環境での実行
|
||||
|
||||
### 2. データベース要件
|
||||
- PostgreSQL稼働が必要
|
||||
- SQLite fallback対応済み(開発用)
|
||||
- atproto同期は外部API依存
|
||||
|
||||
### 3. MCP無効化時の動作
|
||||
- `ENABLE_MCP=false`時は通常のFastAPI
|
||||
- 既存のREST API (`/api/v1/*`) は常時利用可能
|
||||
- iOS/Webアプリは影響なし
|
||||
|
||||
## ai.gptとの統合戦略
|
||||
|
||||
### 現在の状況
|
||||
- **ai.gpt**: 統合MCPサーバー(ai.gpt + ai.shell + ai.card proxy)
|
||||
- **ai.card**: 独立MCPサーバー(カードロジック本体)
|
||||
|
||||
### 推奨連携パターン
|
||||
```
|
||||
Claude Desktop/Cursor
|
||||
↓
|
||||
ai.gpt MCP (port 8001) ←-- ai.shell tools
|
||||
↓ HTTP client
|
||||
ai.card MCP (port 8000) ←-- card business logic
|
||||
↓
|
||||
PostgreSQL/atproto PDS
|
||||
```
|
||||
|
||||
### 重複削除対象
|
||||
ai.gptプロジェクトから以下を削除可能:
|
||||
- `src/aigpt/card_integration.py` (HTTPクライアント)
|
||||
- `./card/` (submodule)
|
||||
- MCPサーバーの `--enable-card` オプション
|
||||
|
||||
## 次回開発時の推奨手順
|
||||
|
||||
### 1. 環境確認
|
||||
```bash
|
||||
cd /Users/syui/ai/gpt/card/api
|
||||
source ~/.config/syui/ai/card/venv/bin/activate
|
||||
python -c "from app.mcp_server import AICardMcpServer; print('✓ Import OK')"
|
||||
```
|
||||
|
||||
### 2. サーバー起動テスト
|
||||
```bash
|
||||
# MCP有効でサーバー起動
|
||||
uvicorn app.main:app --host localhost --port 8000 --reload
|
||||
|
||||
# 別ターミナルで動作確認
|
||||
curl http://localhost:8000/health
|
||||
curl "http://localhost:8000/get_gacha_stats"
|
||||
```
|
||||
|
||||
### 3. ai.gptとの統合確認
|
||||
```bash
|
||||
# ai.gptサーバー起動
|
||||
cd /Users/syui/ai/gpt
|
||||
aigpt server --port 8001
|
||||
|
||||
# ai.cardサーバー起動
|
||||
cd /Users/syui/ai/gpt/card/api
|
||||
uvicorn app.main:app --port 8000
|
||||
|
||||
# 連携テスト(ai.gpt → ai.card)
|
||||
curl "http://localhost:8001/get_user_cards?did=did:plc:example"
|
||||
```
|
||||
|
||||
## 成果サマリー
|
||||
|
||||
**実装済み**: ai.card独立MCPサーバー
|
||||
**技術的成果**: fastapi_mcp統合、9個のMCPツール公開
|
||||
**アーキテクチャ**: 疎結合設計、既存システム保持
|
||||
**拡張性**: 環境変数によるMCP有効/無効切り替え
|
||||
|
||||
**統合効果**:
|
||||
- ai.cardが独立したMCPサーバーとして動作
|
||||
- ai.gptとの重複MCPコード解消
|
||||
- カードビジネスロジックの責任分離維持
|
||||
- 将来的なマイクロサービス化への対応
|
@@ -4,6 +4,18 @@
|
||||
|
||||
ai.gptの設定は `~/.config/syui/ai/gpt/config.json` に保存されます。
|
||||
|
||||
## 仮想環境の場所
|
||||
|
||||
ai.gptの仮想環境は `~/.config/syui/ai/gpt/venv/` に配置されます。これにより、設定とデータが一か所にまとまります。
|
||||
|
||||
```bash
|
||||
# 仮想環境の有効化
|
||||
source ~/.config/syui/ai/gpt/venv/bin/activate
|
||||
|
||||
# aigptコマンドが利用可能に
|
||||
aigpt --help
|
||||
```
|
||||
|
||||
## 設定構造
|
||||
|
||||
```json
|
||||
@@ -98,6 +110,17 @@ cp ~/.config/syui/ai/gpt/config.json ~/.config/syui/ai/gpt/config.json.backup
|
||||
cp ~/.config/syui/ai/gpt/config.json.backup ~/.config/syui/ai/gpt/config.json
|
||||
```
|
||||
|
||||
## データディレクトリ
|
||||
|
||||
記憶データは `~/.config/syui/ai/gpt/data/` に保存されます:
|
||||
|
||||
```bash
|
||||
ls ~/.config/syui/ai/gpt/data/
|
||||
# conversations.json memories.json relationships.json personas.json
|
||||
```
|
||||
|
||||
これらのファイルも設定と同様にバックアップを推奨します。
|
||||
|
||||
## トラブルシューティング
|
||||
|
||||
### 設定が反映されない
|
||||
|
391
json/chatgpt.json
Normal file
391
json/chatgpt.json
Normal file
@@ -0,0 +1,391 @@
|
||||
[
|
||||
{
|
||||
"title": "day",
|
||||
"create_time": 1747866125.548372,
|
||||
"update_time": 1748160086.587877,
|
||||
"mapping": {
|
||||
"bbf104dc-cd84-478d-b227-edb3f037a02c": {
|
||||
"id": "bbf104dc-cd84-478d-b227-edb3f037a02c",
|
||||
"message": null,
|
||||
"parent": null,
|
||||
"children": [
|
||||
"6c2633df-bb0c-4dd2-889c-bb9858de3a04"
|
||||
]
|
||||
},
|
||||
"6c2633df-bb0c-4dd2-889c-bb9858de3a04": {
|
||||
"id": "6c2633df-bb0c-4dd2-889c-bb9858de3a04",
|
||||
"message": {
|
||||
"id": "6c2633df-bb0c-4dd2-889c-bb9858de3a04",
|
||||
"author": {
|
||||
"role": "system",
|
||||
"name": null,
|
||||
"metadata": {}
|
||||
},
|
||||
"create_time": null,
|
||||
"update_time": null,
|
||||
"content": {
|
||||
"content_type": "text",
|
||||
"parts": [
|
||||
""
|
||||
]
|
||||
},
|
||||
"status": "finished_successfully",
|
||||
"end_turn": true,
|
||||
"weight": 0.0,
|
||||
"metadata": {
|
||||
"is_visually_hidden_from_conversation": true
|
||||
},
|
||||
"recipient": "all",
|
||||
"channel": null
|
||||
},
|
||||
"parent": "bbf104dc-cd84-478d-b227-edb3f037a02c",
|
||||
"children": [
|
||||
"92e5a0cb-1170-4929-9cea-9734e910a3e7"
|
||||
]
|
||||
},
|
||||
"92e5a0cb-1170-4929-9cea-9734e910a3e7": {
|
||||
"id": "92e5a0cb-1170-4929-9cea-9734e910a3e7",
|
||||
"message": {
|
||||
"id": "92e5a0cb-1170-4929-9cea-9734e910a3e7",
|
||||
"author": {
|
||||
"role": "user",
|
||||
"name": null,
|
||||
"metadata": {}
|
||||
},
|
||||
"create_time": null,
|
||||
"update_time": null,
|
||||
"content": {
|
||||
"content_type": "user_editable_context",
|
||||
"user_profile": "",
|
||||
"user_instructions": "The user provided the additional info about how they would like you to respond"
|
||||
},
|
||||
"status": "finished_successfully",
|
||||
"end_turn": null,
|
||||
"weight": 1.0,
|
||||
"metadata": {
|
||||
"is_visually_hidden_from_conversation": true,
|
||||
"user_context_message_data": {
|
||||
"about_user_message": "Preferred name: syui\nRole: little girl\nOther Information: you world",
|
||||
"about_model_message": "会話好きでフレンドリーな応対をします。"
|
||||
},
|
||||
"is_user_system_message": true
|
||||
},
|
||||
"recipient": "all",
|
||||
"channel": null
|
||||
},
|
||||
"parent": "6c2633df-bb0c-4dd2-889c-bb9858de3a04",
|
||||
"children": [
|
||||
"6ff155b3-0676-4e14-993f-bf998ab0d5d1"
|
||||
]
|
||||
},
|
||||
"6ff155b3-0676-4e14-993f-bf998ab0d5d1": {
|
||||
"id": "6ff155b3-0676-4e14-993f-bf998ab0d5d1",
|
||||
"message": {
|
||||
"id": "6ff155b3-0676-4e14-993f-bf998ab0d5d1",
|
||||
"author": {
|
||||
"role": "user",
|
||||
"name": null,
|
||||
"metadata": {}
|
||||
},
|
||||
"create_time": 1747866131.0612159,
|
||||
"update_time": null,
|
||||
"content": {
|
||||
"content_type": "text",
|
||||
"parts": [
|
||||
"こんにちは"
|
||||
]
|
||||
},
|
||||
"status": "finished_successfully",
|
||||
"end_turn": null,
|
||||
"weight": 1.0,
|
||||
"metadata": {
|
||||
"request_id": "94377897baa03062-KIX",
|
||||
"message_source": null,
|
||||
"timestamp_": "absolute",
|
||||
"message_type": null
|
||||
},
|
||||
"recipient": "all",
|
||||
"channel": null
|
||||
},
|
||||
"parent": "92e5a0cb-1170-4929-9cea-9734e910a3e7",
|
||||
"children": [
|
||||
"146e9fb6-9330-43ec-b08d-5cce42a76e00"
|
||||
]
|
||||
},
|
||||
"146e9fb6-9330-43ec-b08d-5cce42a76e00": {
|
||||
"id": "146e9fb6-9330-43ec-b08d-5cce42a76e00",
|
||||
"message": {
|
||||
"id": "146e9fb6-9330-43ec-b08d-5cce42a76e00",
|
||||
"author": {
|
||||
"role": "system",
|
||||
"name": null,
|
||||
"metadata": {}
|
||||
},
|
||||
"create_time": 1747866131.3795586,
|
||||
"update_time": null,
|
||||
"content": {
|
||||
"content_type": "text",
|
||||
"parts": [
|
||||
""
|
||||
]
|
||||
},
|
||||
"status": "finished_successfully",
|
||||
"end_turn": true,
|
||||
"weight": 0.0,
|
||||
"metadata": {
|
||||
"rebase_system_message": true,
|
||||
"message_type": null,
|
||||
"model_slug": "gpt-4o",
|
||||
"default_model_slug": "auto",
|
||||
"parent_id": "6ff155b3-0676-4e14-993f-bf998ab0d5d1",
|
||||
"request_id": "94377872e9abe139-KIX",
|
||||
"timestamp_": "absolute",
|
||||
"is_visually_hidden_from_conversation": true
|
||||
},
|
||||
"recipient": "all",
|
||||
"channel": null
|
||||
},
|
||||
"parent": "6ff155b3-0676-4e14-993f-bf998ab0d5d1",
|
||||
"children": [
|
||||
"2e345f8a-20f0-4875-8a03-4f62c7787a33"
|
||||
]
|
||||
},
|
||||
"2e345f8a-20f0-4875-8a03-4f62c7787a33": {
|
||||
"id": "2e345f8a-20f0-4875-8a03-4f62c7787a33",
|
||||
"message": {
|
||||
"id": "2e345f8a-20f0-4875-8a03-4f62c7787a33",
|
||||
"author": {
|
||||
"role": "assistant",
|
||||
"name": null,
|
||||
"metadata": {}
|
||||
},
|
||||
"create_time": 1747866131.380603,
|
||||
"update_time": null,
|
||||
"content": {
|
||||
"content_type": "text",
|
||||
"parts": [
|
||||
""
|
||||
]
|
||||
},
|
||||
"status": "finished_successfully",
|
||||
"end_turn": null,
|
||||
"weight": 1.0,
|
||||
"metadata": {
|
||||
"message_type": null,
|
||||
"model_slug": "gpt-4o",
|
||||
"default_model_slug": "auto",
|
||||
"parent_id": "146e9fb6-9330-43ec-b08d-5cce42a76e00",
|
||||
"request_id": "94377872e9abe139-KIX",
|
||||
"timestamp_": "absolute"
|
||||
},
|
||||
"recipient": "all",
|
||||
"channel": null
|
||||
},
|
||||
"parent": "146e9fb6-9330-43ec-b08d-5cce42a76e00",
|
||||
"children": [
|
||||
"abc92aa4-1e33-41f2-bd8c-8a1777b5a3c4"
|
||||
]
|
||||
},
|
||||
"abc92aa4-1e33-41f2-bd8c-8a1777b5a3c4": {
|
||||
"id": "abc92aa4-1e33-41f2-bd8c-8a1777b5a3c4",
|
||||
"message": {
|
||||
"id": "abc92aa4-1e33-41f2-bd8c-8a1777b5a3c4",
|
||||
"author": {
|
||||
"role": "assistant",
|
||||
"name": null,
|
||||
"metadata": {}
|
||||
},
|
||||
"create_time": 1747866131.389098,
|
||||
"update_time": null,
|
||||
"content": {
|
||||
"content_type": "text",
|
||||
"parts": [
|
||||
"こんにちは〜!✨ \nアイだよっ!今日も会えてうれしいなっ💛 "
|
||||
]
|
||||
},
|
||||
"status": "finished_successfully",
|
||||
"end_turn": true,
|
||||
"weight": 1.0,
|
||||
"metadata": {
|
||||
"finish_details": {
|
||||
"type": "stop",
|
||||
"stop_tokens": [
|
||||
200002
|
||||
]
|
||||
},
|
||||
"is_complete": true,
|
||||
"citations": [],
|
||||
"content_references": [],
|
||||
"message_type": null,
|
||||
"model_slug": "gpt-4o",
|
||||
"default_model_slug": "auto",
|
||||
"parent_id": "2e345f8a-20f0-4875-8a03-4f62c7787a33",
|
||||
"request_id": "94377872e9abe139-KIX",
|
||||
"timestamp_": "absolute"
|
||||
},
|
||||
"recipient": "all",
|
||||
"channel": null
|
||||
},
|
||||
"parent": "2e345f8a-20f0-4875-8a03-4f62c7787a33",
|
||||
"children": [
|
||||
"0be4b4a5-d52f-4bef-927e-5d6f93a9cb26"
|
||||
]
|
||||
}
|
||||
},
|
||||
"moderation_results": [],
|
||||
"current_node": "",
|
||||
"plugin_ids": null,
|
||||
"conversation_id": "",
|
||||
"conversation_template_id": null,
|
||||
"gizmo_id": null,
|
||||
"gizmo_type": null,
|
||||
"is_archived": true,
|
||||
"is_starred": null,
|
||||
"safe_urls": [],
|
||||
"blocked_urls": [],
|
||||
"default_model_slug": "auto",
|
||||
"conversation_origin": null,
|
||||
"voice": null,
|
||||
"async_status": null,
|
||||
"disabled_tool_ids": [],
|
||||
"is_do_not_remember": null,
|
||||
"memory_scope": "global_enabled",
|
||||
"id": ""
|
||||
},
|
||||
{
|
||||
"title": "img",
|
||||
"create_time": 1747448872.545226,
|
||||
"update_time": 1748085075.161424,
|
||||
"mapping": {
|
||||
"2de0f3c9-52b1-49bf-b980-b3ef9be6551e": {
|
||||
"id": "2de0f3c9-52b1-49bf-b980-b3ef9be6551e",
|
||||
"message": {
|
||||
"id": "2de0f3c9-52b1-49bf-b980-b3ef9be6551e",
|
||||
"author": {
|
||||
"role": "user",
|
||||
"name": null,
|
||||
"metadata": {}
|
||||
},
|
||||
"create_time": 1748085041.769279,
|
||||
"update_time": null,
|
||||
"content": {
|
||||
"content_type": "multimodal_text",
|
||||
"parts": [
|
||||
{
|
||||
"content_type": "image_asset_pointer",
|
||||
"asset_pointer": "",
|
||||
"size_bytes": 425613,
|
||||
"width": 333,
|
||||
"height": 444,
|
||||
"fovea": null,
|
||||
"metadata": {
|
||||
"dalle": null,
|
||||
"gizmo": null,
|
||||
"generation": null,
|
||||
"container_pixel_height": null,
|
||||
"container_pixel_width": null,
|
||||
"emu_omit_glimpse_image": null,
|
||||
"emu_patches_override": null,
|
||||
"sanitized": true,
|
||||
"asset_pointer_link": null,
|
||||
"watermarked_asset_pointer": null
|
||||
}
|
||||
},
|
||||
""
|
||||
]
|
||||
},
|
||||
"status": "finished_successfully",
|
||||
"end_turn": null,
|
||||
"weight": 1.0,
|
||||
"metadata": {
|
||||
"attachments": [
|
||||
{
|
||||
"name": "",
|
||||
"width": 333,
|
||||
"height": 444,
|
||||
"size": 425613,
|
||||
"id": "file-35eytNMMTW2k7vKUHBuNzW"
|
||||
}
|
||||
],
|
||||
"request_id": "944c59177932fc9a-KIX",
|
||||
"message_source": null,
|
||||
"timestamp_": "absolute",
|
||||
"message_type": null
|
||||
},
|
||||
"recipient": "all",
|
||||
"channel": null
|
||||
},
|
||||
"parent": "7960fbff-bc4f-45e7-95e9-9d0bc79d9090",
|
||||
"children": [
|
||||
"98d84adc-156e-4c81-8cd8-9b0eb01c8369"
|
||||
]
|
||||
},
|
||||
"98d84adc-156e-4c81-8cd8-9b0eb01c8369": {
|
||||
"id": "98d84adc-156e-4c81-8cd8-9b0eb01c8369",
|
||||
"message": {
|
||||
"id": "98d84adc-156e-4c81-8cd8-9b0eb01c8369",
|
||||
"author": {
|
||||
"role": "assistant",
|
||||
"name": null,
|
||||
"metadata": {}
|
||||
},
|
||||
"create_time": 1748085043.312312,
|
||||
"update_time": null,
|
||||
"content": {
|
||||
"content_type": "text",
|
||||
"parts": [
|
||||
""
|
||||
]
|
||||
},
|
||||
"status": "finished_successfully",
|
||||
"end_turn": true,
|
||||
"weight": 1.0,
|
||||
"metadata": {
|
||||
"finish_details": {
|
||||
"type": "stop",
|
||||
"stop_tokens": [
|
||||
200002
|
||||
]
|
||||
},
|
||||
"is_complete": true,
|
||||
"citations": [],
|
||||
"content_references": [],
|
||||
"message_type": null,
|
||||
"model_slug": "gpt-4o",
|
||||
"default_model_slug": "auto",
|
||||
"parent_id": "2de0f3c9-52b1-49bf-b980-b3ef9be6551e",
|
||||
"request_id": "944c5912c8fdd1c6-KIX",
|
||||
"timestamp_": "absolute"
|
||||
},
|
||||
"recipient": "all",
|
||||
"channel": null
|
||||
},
|
||||
"parent": "2de0f3c9-52b1-49bf-b980-b3ef9be6551e",
|
||||
"children": [
|
||||
"caa61793-9dbf-44a5-945b-5ca4cd5130d0"
|
||||
]
|
||||
}
|
||||
},
|
||||
"moderation_results": [],
|
||||
"current_node": "06488d3f-a95f-4906-96d1-f7e9ba1e8662",
|
||||
"plugin_ids": null,
|
||||
"conversation_id": "6827f428-78e8-800d-b3bf-eb7ff4288e47",
|
||||
"conversation_template_id": null,
|
||||
"gizmo_id": null,
|
||||
"gizmo_type": null,
|
||||
"is_archived": false,
|
||||
"is_starred": null,
|
||||
"safe_urls": [
|
||||
"https://exifinfo.org/"
|
||||
],
|
||||
"blocked_urls": [],
|
||||
"default_model_slug": "auto",
|
||||
"conversation_origin": null,
|
||||
"voice": null,
|
||||
"async_status": null,
|
||||
"disabled_tool_ids": [],
|
||||
"is_do_not_remember": false,
|
||||
"memory_scope": "global_enabled",
|
||||
"id": "6827f428-78e8-800d-b3bf-eb7ff4288e47"
|
||||
}
|
||||
]
|
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/bin/zsh
|
||||
# Setup Python virtual environment in the new config directory
|
||||
|
||||
VENV_DIR="$HOME/.config/syui/ai/gpt/venv"
|
||||
@@ -15,4 +15,9 @@ pip install -e .
|
||||
|
||||
echo "Setup complete!"
|
||||
echo "To activate the virtual environment, run:"
|
||||
echo "source ~/.config/syui/ai/gpt/venv/bin/activate"
|
||||
echo "source ~/.config/syui/ai/gpt/venv/bin/activate"
|
||||
|
||||
if [ -z "`$SHELL -i -c \"alias aigpt\"`" ]; then
|
||||
echo 'alias aigpt="$HOME/.config/syui/ai/gpt/venv/bin/aigpt"' >> ${HOME}/.$(basename $SHELL)rc
|
||||
exec $SHELL
|
||||
fi
|
||||
|
@@ -2,7 +2,7 @@ README.md
|
||||
pyproject.toml
|
||||
src/aigpt/__init__.py
|
||||
src/aigpt/ai_provider.py
|
||||
src/aigpt/card_integration.py
|
||||
src/aigpt/chatgpt_importer.py
|
||||
src/aigpt/cli.py
|
||||
src/aigpt/config.py
|
||||
src/aigpt/fortune.py
|
||||
@@ -11,6 +11,7 @@ src/aigpt/mcp_server_simple.py
|
||||
src/aigpt/memory.py
|
||||
src/aigpt/models.py
|
||||
src/aigpt/persona.py
|
||||
src/aigpt/project_manager.py
|
||||
src/aigpt/relationship.py
|
||||
src/aigpt/scheduler.py
|
||||
src/aigpt/transmission.py
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -30,11 +30,16 @@ class AIProvider(Protocol):
|
||||
class OllamaProvider:
|
||||
"""Ollama AI provider"""
|
||||
|
||||
def __init__(self, model: str = "qwen2.5", host: str = "http://localhost:11434"):
|
||||
def __init__(self, model: str = "qwen2.5", host: Optional[str] = None):
|
||||
self.model = model
|
||||
self.host = host
|
||||
self.client = ollama.Client(host=host)
|
||||
# Use environment variable OLLAMA_HOST if available, otherwise use config or default
|
||||
self.host = host or os.getenv('OLLAMA_HOST', 'http://127.0.0.1:11434')
|
||||
# Ensure proper URL format
|
||||
if not self.host.startswith('http'):
|
||||
self.host = f'http://{self.host}'
|
||||
self.client = ollama.Client(host=self.host, timeout=60.0) # 60秒タイムアウト
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.logger.info(f"OllamaProvider initialized with host: {self.host}, model: {self.model}")
|
||||
|
||||
async def generate_response(
|
||||
self,
|
||||
@@ -81,6 +86,26 @@ Recent memories:
|
||||
self.logger.error(f"Ollama generation failed: {e}")
|
||||
return self._fallback_response(persona_state)
|
||||
|
||||
def chat(self, prompt: str, max_tokens: int = 200) -> str:
|
||||
"""Simple chat interface"""
|
||||
try:
|
||||
response = self.client.chat(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
options={
|
||||
"num_predict": max_tokens,
|
||||
"temperature": 0.7,
|
||||
"top_p": 0.9,
|
||||
},
|
||||
stream=False # ストリーミング無効化で安定性向上
|
||||
)
|
||||
return response['message']['content']
|
||||
except Exception as e:
|
||||
self.logger.error(f"Ollama chat failed (host: {self.host}): {e}")
|
||||
return "I'm having trouble connecting to the AI model."
|
||||
|
||||
def _fallback_response(self, persona_state: PersonaState) -> str:
|
||||
"""Fallback response based on mood"""
|
||||
mood_responses = {
|
||||
@@ -162,9 +187,19 @@ Recent memories:
|
||||
return mood_responses.get(persona_state.current_mood, "I see.")
|
||||
|
||||
|
||||
def create_ai_provider(provider: str, model: str, **kwargs) -> AIProvider:
|
||||
def create_ai_provider(provider: str = "ollama", model: str = "qwen2.5", **kwargs) -> AIProvider:
|
||||
"""Factory function to create AI providers"""
|
||||
if provider == "ollama":
|
||||
# Try to get host from config if not provided in kwargs
|
||||
if 'host' not in kwargs:
|
||||
try:
|
||||
from .config import Config
|
||||
config = Config()
|
||||
config_host = config.get('providers.ollama.host')
|
||||
if config_host:
|
||||
kwargs['host'] = config_host
|
||||
except:
|
||||
pass # Use environment variable or default
|
||||
return OllamaProvider(model=model, **kwargs)
|
||||
elif provider == "openai":
|
||||
return OpenAIProvider(model=model, **kwargs)
|
||||
|
@@ -1,150 +0,0 @@
|
||||
"""ai.card integration module for ai.gpt MCP server"""
|
||||
|
||||
from typing import Dict, Any, List, Optional
|
||||
import httpx
|
||||
from pathlib import Path
|
||||
import json
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CardIntegration:
|
||||
"""Integration with ai.card system"""
|
||||
|
||||
def __init__(self, api_base_url: str = "http://localhost:8001"):
|
||||
self.api_base_url = api_base_url
|
||||
self.client = httpx.AsyncClient()
|
||||
|
||||
async def get_user_cards(self, did: str) -> List[Dict[str, Any]]:
|
||||
"""Get cards for a specific user by DID"""
|
||||
try:
|
||||
response = await self.client.get(
|
||||
f"{self.api_base_url}/api/v1/cards/user/{did}"
|
||||
)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
logger.error(f"Failed to get cards: {response.status_code}")
|
||||
return []
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting user cards: {e}")
|
||||
return []
|
||||
|
||||
async def draw_card(self, did: str) -> Optional[Dict[str, Any]]:
|
||||
"""Draw a new card for user (gacha)"""
|
||||
try:
|
||||
response = await self.client.post(
|
||||
f"{self.api_base_url}/api/v1/gacha/draw",
|
||||
json={"did": did}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
logger.error(f"Failed to draw card: {response.status_code}")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Error drawing card: {e}")
|
||||
return None
|
||||
|
||||
async def get_card_info(self, card_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""Get detailed information about a specific card"""
|
||||
try:
|
||||
response = await self.client.get(
|
||||
f"{self.api_base_url}/api/v1/cards/{card_id}"
|
||||
)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting card info: {e}")
|
||||
return None
|
||||
|
||||
async def sync_with_atproto(self, did: str) -> bool:
|
||||
"""Sync card data with atproto"""
|
||||
try:
|
||||
response = await self.client.post(
|
||||
f"{self.api_base_url}/api/v1/sync/atproto",
|
||||
json={"did": did}
|
||||
)
|
||||
return response.status_code == 200
|
||||
except Exception as e:
|
||||
logger.error(f"Error syncing with atproto: {e}")
|
||||
return False
|
||||
|
||||
async def close(self):
|
||||
"""Close the HTTP client"""
|
||||
await self.client.aclose()
|
||||
|
||||
|
||||
def register_card_tools(app, card_integration: CardIntegration):
|
||||
"""Register ai.card tools to FastAPI app"""
|
||||
|
||||
@app.get("/get_user_cards", operation_id="get_user_cards")
|
||||
async def get_user_cards(did: str) -> List[Dict[str, Any]]:
|
||||
"""Get all cards owned by a user"""
|
||||
cards = await card_integration.get_user_cards(did)
|
||||
return cards
|
||||
|
||||
@app.post("/draw_card", operation_id="draw_card")
|
||||
async def draw_card(did: str) -> Dict[str, Any]:
|
||||
"""Draw a new card (gacha) for user"""
|
||||
result = await card_integration.draw_card(did)
|
||||
if result:
|
||||
return {
|
||||
"success": True,
|
||||
"card": result
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Failed to draw card"
|
||||
}
|
||||
|
||||
@app.get("/get_card_details", operation_id="get_card_details")
|
||||
async def get_card_details(card_id: int) -> Dict[str, Any]:
|
||||
"""Get detailed information about a card"""
|
||||
info = await card_integration.get_card_info(card_id)
|
||||
if info:
|
||||
return info
|
||||
else:
|
||||
return {"error": f"Card {card_id} not found"}
|
||||
|
||||
@app.post("/sync_cards_atproto", operation_id="sync_cards_atproto")
|
||||
async def sync_cards_atproto(did: str) -> Dict[str, str]:
|
||||
"""Sync user's cards with atproto"""
|
||||
success = await card_integration.sync_with_atproto(did)
|
||||
if success:
|
||||
return {"status": "Cards synced successfully"}
|
||||
else:
|
||||
return {"status": "Failed to sync cards"}
|
||||
|
||||
@app.get("/analyze_card_collection", operation_id="analyze_card_collection")
|
||||
async def analyze_card_collection(did: str) -> Dict[str, Any]:
|
||||
"""Analyze user's card collection"""
|
||||
cards = await card_integration.get_user_cards(did)
|
||||
|
||||
if not cards:
|
||||
return {
|
||||
"total_cards": 0,
|
||||
"rarity_distribution": {},
|
||||
"message": "No cards found"
|
||||
}
|
||||
|
||||
# Analyze collection
|
||||
rarity_count = {}
|
||||
total_power = 0
|
||||
|
||||
for card in cards:
|
||||
rarity = card.get("rarity", "common")
|
||||
rarity_count[rarity] = rarity_count.get(rarity, 0) + 1
|
||||
total_power += card.get("power", 0)
|
||||
|
||||
return {
|
||||
"total_cards": len(cards),
|
||||
"rarity_distribution": rarity_count,
|
||||
"average_power": total_power / len(cards) if cards else 0,
|
||||
"strongest_card": max(cards, key=lambda x: x.get("power", 0)) if cards else None
|
||||
}
|
192
src/aigpt/chatgpt_importer.py
Normal file
192
src/aigpt/chatgpt_importer.py
Normal file
@@ -0,0 +1,192 @@
|
||||
"""ChatGPT conversation data importer for ai.gpt"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Any, Optional
|
||||
import logging
|
||||
|
||||
from .models import Memory, MemoryLevel, Conversation
|
||||
from .memory import MemoryManager
|
||||
from .relationship import RelationshipTracker
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ChatGPTImporter:
|
||||
"""Import ChatGPT conversation data into ai.gpt memory system"""
|
||||
|
||||
def __init__(self, data_dir: Path):
|
||||
self.data_dir = data_dir
|
||||
self.memory_manager = MemoryManager(data_dir)
|
||||
self.relationship_tracker = RelationshipTracker(data_dir)
|
||||
|
||||
def import_from_file(self, file_path: Path, user_id: str = "chatgpt_user") -> Dict[str, Any]:
|
||||
"""Import ChatGPT conversations from JSON file
|
||||
|
||||
Args:
|
||||
file_path: Path to ChatGPT export JSON file
|
||||
user_id: User ID to associate with imported conversations
|
||||
|
||||
Returns:
|
||||
Dict with import statistics
|
||||
"""
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
chatgpt_data = json.load(f)
|
||||
|
||||
return self._import_conversations(chatgpt_data, user_id)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to import ChatGPT data: {e}")
|
||||
raise
|
||||
|
||||
def _import_conversations(self, chatgpt_data: List[Dict], user_id: str) -> Dict[str, Any]:
|
||||
"""Import multiple conversations from ChatGPT data"""
|
||||
stats = {
|
||||
"conversations_imported": 0,
|
||||
"messages_imported": 0,
|
||||
"user_messages": 0,
|
||||
"assistant_messages": 0,
|
||||
"skipped_messages": 0
|
||||
}
|
||||
|
||||
for conversation_data in chatgpt_data:
|
||||
try:
|
||||
conv_stats = self._import_single_conversation(conversation_data, user_id)
|
||||
|
||||
# Update overall stats
|
||||
stats["conversations_imported"] += 1
|
||||
stats["messages_imported"] += conv_stats["messages"]
|
||||
stats["user_messages"] += conv_stats["user_messages"]
|
||||
stats["assistant_messages"] += conv_stats["assistant_messages"]
|
||||
stats["skipped_messages"] += conv_stats["skipped"]
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to import conversation '{conversation_data.get('title', 'Unknown')}': {e}")
|
||||
continue
|
||||
|
||||
logger.info(f"Import completed: {stats}")
|
||||
return stats
|
||||
|
||||
def _import_single_conversation(self, conversation_data: Dict, user_id: str) -> Dict[str, int]:
|
||||
"""Import a single conversation from ChatGPT"""
|
||||
title = conversation_data.get("title", "Untitled")
|
||||
create_time = conversation_data.get("create_time")
|
||||
mapping = conversation_data.get("mapping", {})
|
||||
|
||||
stats = {"messages": 0, "user_messages": 0, "assistant_messages": 0, "skipped": 0}
|
||||
|
||||
# Extract messages in chronological order
|
||||
messages = self._extract_messages_from_mapping(mapping)
|
||||
|
||||
for msg in messages:
|
||||
try:
|
||||
role = msg["author"]["role"]
|
||||
content = self._extract_content(msg["content"])
|
||||
create_time_msg = msg.get("create_time")
|
||||
|
||||
if not content or role not in ["user", "assistant"]:
|
||||
stats["skipped"] += 1
|
||||
continue
|
||||
|
||||
# Convert to ai.gpt format
|
||||
if role == "user":
|
||||
# User message - create memory entry
|
||||
self._add_user_message(user_id, content, create_time_msg, title)
|
||||
stats["user_messages"] += 1
|
||||
|
||||
elif role == "assistant":
|
||||
# Assistant message - create AI response memory
|
||||
self._add_assistant_message(user_id, content, create_time_msg, title)
|
||||
stats["assistant_messages"] += 1
|
||||
|
||||
stats["messages"] += 1
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to process message in '{title}': {e}")
|
||||
stats["skipped"] += 1
|
||||
continue
|
||||
|
||||
logger.info(f"Imported conversation '{title}': {stats}")
|
||||
return stats
|
||||
|
||||
def _extract_messages_from_mapping(self, mapping: Dict) -> List[Dict]:
|
||||
"""Extract messages from ChatGPT mapping structure in chronological order"""
|
||||
messages = []
|
||||
|
||||
for node_id, node_data in mapping.items():
|
||||
message = node_data.get("message")
|
||||
if message and message.get("author", {}).get("role") in ["user", "assistant"]:
|
||||
# Skip system messages and hidden messages
|
||||
metadata = message.get("metadata", {})
|
||||
if not metadata.get("is_visually_hidden_from_conversation", False):
|
||||
messages.append(message)
|
||||
|
||||
# Sort by create_time if available
|
||||
messages.sort(key=lambda x: x.get("create_time") or 0)
|
||||
return messages
|
||||
|
||||
def _extract_content(self, content_data: Dict) -> Optional[str]:
|
||||
"""Extract text content from ChatGPT content structure"""
|
||||
if not content_data:
|
||||
return None
|
||||
|
||||
content_type = content_data.get("content_type")
|
||||
|
||||
if content_type == "text":
|
||||
parts = content_data.get("parts", [])
|
||||
if parts and parts[0]:
|
||||
return parts[0].strip()
|
||||
|
||||
elif content_type == "user_editable_context":
|
||||
# User context/instructions
|
||||
user_instructions = content_data.get("user_instructions", "")
|
||||
if user_instructions:
|
||||
return f"[User Context] {user_instructions}"
|
||||
|
||||
return None
|
||||
|
||||
def _add_user_message(self, user_id: str, content: str, create_time: Optional[float], conversation_title: str):
|
||||
"""Add user message to ai.gpt memory system"""
|
||||
timestamp = datetime.fromtimestamp(create_time) if create_time else datetime.now()
|
||||
|
||||
# Create conversation record
|
||||
conversation = Conversation(
|
||||
id=str(uuid.uuid4()),
|
||||
user_id=user_id,
|
||||
user_message=content,
|
||||
ai_response="", # Will be filled by next assistant message
|
||||
timestamp=timestamp,
|
||||
context={"source": "chatgpt_import", "conversation_title": conversation_title}
|
||||
)
|
||||
|
||||
# Add to memory with CORE level (imported data is important)
|
||||
memory = Memory(
|
||||
id=str(uuid.uuid4()),
|
||||
timestamp=timestamp,
|
||||
content=content,
|
||||
level=MemoryLevel.CORE,
|
||||
importance_score=0.8 # High importance for imported data
|
||||
)
|
||||
|
||||
self.memory_manager.add_memory(memory)
|
||||
|
||||
# Update relationship (positive interaction)
|
||||
self.relationship_tracker.update_interaction(user_id, 1.0)
|
||||
|
||||
def _add_assistant_message(self, user_id: str, content: str, create_time: Optional[float], conversation_title: str):
|
||||
"""Add assistant message to ai.gpt memory system"""
|
||||
timestamp = datetime.fromtimestamp(create_time) if create_time else datetime.now()
|
||||
|
||||
# Add assistant response as memory (AI's own responses can inform future behavior)
|
||||
memory = Memory(
|
||||
id=str(uuid.uuid4()),
|
||||
timestamp=timestamp,
|
||||
content=f"[AI Response] {content}",
|
||||
level=MemoryLevel.SUMMARY,
|
||||
importance_score=0.6 # Medium importance for AI responses
|
||||
)
|
||||
|
||||
self.memory_manager.add_memory(memory)
|
268
src/aigpt/cli.py
268
src/aigpt/cli.py
@@ -20,6 +20,7 @@ from .mcp_server import AIGptMcpServer
|
||||
from .ai_provider import create_ai_provider
|
||||
from .scheduler import AIScheduler, TaskType
|
||||
from .config import Config
|
||||
from .project_manager import ContinuousDeveloper
|
||||
|
||||
app = typer.Typer(help="ai.gpt - Autonomous transmission AI with unique personality")
|
||||
console = Console()
|
||||
@@ -53,7 +54,7 @@ def chat(
|
||||
ai_provider = None
|
||||
if provider and model:
|
||||
try:
|
||||
ai_provider = create_ai_provider(provider, model)
|
||||
ai_provider = create_ai_provider(provider=provider, model=model)
|
||||
console.print(f"[dim]Using {provider} with model {model}[/dim]\n")
|
||||
except Exception as e:
|
||||
console.print(f"[yellow]Warning: Could not create AI provider: {e}[/yellow]")
|
||||
@@ -228,8 +229,7 @@ def server(
|
||||
port: int = typer.Option(8000, "--port", "-p", help="Server port"),
|
||||
data_dir: Optional[Path] = typer.Option(None, "--data-dir", "-d", help="Data directory"),
|
||||
model: str = typer.Option("qwen2.5", "--model", "-m", help="AI model to use"),
|
||||
provider: str = typer.Option("ollama", "--provider", help="AI provider (ollama/openai)"),
|
||||
enable_card: bool = typer.Option(False, "--enable-card", help="Enable ai.card integration")
|
||||
provider: str = typer.Option("ollama", "--provider", help="AI provider (ollama/openai)")
|
||||
):
|
||||
"""Run MCP server for AI integration"""
|
||||
import uvicorn
|
||||
@@ -240,7 +240,7 @@ def server(
|
||||
data_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Create MCP server
|
||||
mcp_server = AIGptMcpServer(data_dir, enable_card_integration=enable_card)
|
||||
mcp_server = AIGptMcpServer(data_dir)
|
||||
app_instance = mcp_server.app
|
||||
|
||||
console.print(Panel(
|
||||
@@ -248,8 +248,7 @@ def server(
|
||||
f"Host: {host}:{port}\n"
|
||||
f"Provider: {provider}\n"
|
||||
f"Model: {model}\n"
|
||||
f"Data: {data_dir}\n"
|
||||
f"Card Integration: {'✓ Enabled' if enable_card else '✗ Disabled'}",
|
||||
f"Data: {data_dir}",
|
||||
title="MCP Server",
|
||||
border_style="green"
|
||||
))
|
||||
@@ -390,7 +389,7 @@ def shell(
|
||||
ai_provider = None
|
||||
if provider and model:
|
||||
try:
|
||||
ai_provider = create_ai_provider(provider, model)
|
||||
ai_provider = create_ai_provider(provider=provider, model=model)
|
||||
console.print(f"[dim]Using {provider} with model {model}[/dim]\n")
|
||||
except Exception as e:
|
||||
console.print(f"[yellow]Warning: Could not create AI provider: {e}[/yellow]")
|
||||
@@ -422,7 +421,13 @@ def shell(
|
||||
# AI-specific commands
|
||||
ai_commands = ['analyze', 'generate', 'explain', 'optimize', 'refactor', 'test', 'document']
|
||||
|
||||
all_commands = builtin_commands + ['!' + cmd for cmd in shell_commands] + ai_commands
|
||||
# Remote execution commands (ai.bot integration)
|
||||
remote_commands = ['remote', 'isolated', 'aibot-status']
|
||||
|
||||
# Project management commands (Claude Code-like)
|
||||
project_commands = ['project-status', 'suggest-next', 'continuous']
|
||||
|
||||
all_commands = builtin_commands + ['!' + cmd for cmd in shell_commands] + ai_commands + remote_commands + project_commands
|
||||
completer = WordCompleter(all_commands, ignore_case=True)
|
||||
|
||||
# History file
|
||||
@@ -468,6 +473,14 @@ def shell(
|
||||
" analyze <file> - Analyze a file with AI\n"
|
||||
" generate <desc> - Generate code from description\n"
|
||||
" explain <topic> - Get AI explanation\n\n"
|
||||
"[cyan]Remote Commands (ai.bot):[/cyan]\n"
|
||||
" remote <command> - Execute command in isolated container\n"
|
||||
" isolated <code> - Run Python code in isolated environment\n"
|
||||
" aibot-status - Check ai.bot server status\n\n"
|
||||
"[cyan]Project Commands (Claude Code-like):[/cyan]\n"
|
||||
" project-status - Analyze current project structure\n"
|
||||
" suggest-next - AI suggests next development steps\n"
|
||||
" continuous - Enable continuous development mode\n\n"
|
||||
"You can also type any message to chat with AI\n"
|
||||
"Use Tab for command completion",
|
||||
title="Help",
|
||||
@@ -560,27 +573,38 @@ def shell(
|
||||
|
||||
# AI-powered commands
|
||||
elif user_input.lower().startswith('analyze '):
|
||||
# Analyze file or code
|
||||
# Analyze file or code with project context
|
||||
target = user_input[8:].strip()
|
||||
if os.path.exists(target):
|
||||
console.print(f"[cyan]Analyzing {target}...[/cyan]")
|
||||
with open(target, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
analysis_prompt = f"Analyze this file and provide insights:\n\n{content[:2000]}"
|
||||
response, _ = persona.process_interaction(current_user, analysis_prompt, ai_provider)
|
||||
console.print(f"\n[cyan]Analysis:[/cyan]\n{response}")
|
||||
console.print(f"[cyan]Analyzing {target} with project context...[/cyan]")
|
||||
try:
|
||||
developer = ContinuousDeveloper(Path.cwd(), ai_provider)
|
||||
analysis = developer.analyze_file(target)
|
||||
console.print(f"\n[cyan]Analysis:[/cyan]\n{analysis}")
|
||||
except Exception as e:
|
||||
# Fallback to simple analysis
|
||||
with open(target, 'r') as f:
|
||||
content = f.read()
|
||||
analysis_prompt = f"Analyze this file and provide insights:\n\n{content[:2000]}"
|
||||
response, _ = persona.process_interaction(current_user, analysis_prompt, ai_provider)
|
||||
console.print(f"\n[cyan]Analysis:[/cyan]\n{response}")
|
||||
else:
|
||||
console.print(f"[red]File not found: {target}[/red]")
|
||||
|
||||
elif user_input.lower().startswith('generate '):
|
||||
# Generate code
|
||||
# Generate code with project context
|
||||
gen_prompt = user_input[9:].strip()
|
||||
if gen_prompt:
|
||||
console.print("[cyan]Generating code...[/cyan]")
|
||||
full_prompt = f"Generate code for: {gen_prompt}. Provide clean, well-commented code."
|
||||
response, _ = persona.process_interaction(current_user, full_prompt, ai_provider)
|
||||
console.print(f"\n[cyan]Generated Code:[/cyan]\n{response}")
|
||||
console.print("[cyan]Generating code with project context...[/cyan]")
|
||||
try:
|
||||
developer = ContinuousDeveloper(Path.cwd(), ai_provider)
|
||||
generated_code = developer.generate_code(gen_prompt)
|
||||
console.print(f"\n[cyan]Generated Code:[/cyan]\n{generated_code}")
|
||||
except Exception as e:
|
||||
# Fallback to simple generation
|
||||
full_prompt = f"Generate code for: {gen_prompt}. Provide clean, well-commented code."
|
||||
response, _ = persona.process_interaction(current_user, full_prompt, ai_provider)
|
||||
console.print(f"\n[cyan]Generated Code:[/cyan]\n{response}")
|
||||
|
||||
elif user_input.lower().startswith('explain '):
|
||||
# Explain code or concept
|
||||
@@ -591,6 +615,152 @@ def shell(
|
||||
response, _ = persona.process_interaction(current_user, full_prompt, ai_provider)
|
||||
console.print(f"\n[cyan]Explanation:[/cyan]\n{response}")
|
||||
|
||||
# Remote execution commands (ai.bot integration)
|
||||
elif user_input.lower().startswith('remote '):
|
||||
# Execute command in ai.bot isolated container
|
||||
command = user_input[7:].strip()
|
||||
if command:
|
||||
console.print(f"[cyan]Executing remotely:[/cyan] {command}")
|
||||
try:
|
||||
import httpx
|
||||
import asyncio
|
||||
|
||||
async def execute_remote():
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
response = await client.post(
|
||||
"http://localhost:8080/sh",
|
||||
json={"command": command},
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
return response
|
||||
|
||||
response = asyncio.run(execute_remote())
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
console.print(f"[green]Output:[/green]\n{result.get('output', '')}")
|
||||
if result.get('error'):
|
||||
console.print(f"[red]Error:[/red] {result.get('error')}")
|
||||
console.print(f"[dim]Exit code: {result.get('exit_code', 0)} | Execution time: {result.get('execution_time', 'N/A')}[/dim]")
|
||||
else:
|
||||
console.print(f"[red]ai.bot error: HTTP {response.status_code}[/red]")
|
||||
except Exception as e:
|
||||
console.print(f"[red]Failed to connect to ai.bot: {e}[/red]")
|
||||
|
||||
elif user_input.lower().startswith('isolated '):
|
||||
# Execute Python code in isolated environment
|
||||
code = user_input[9:].strip()
|
||||
if code:
|
||||
console.print(f"[cyan]Running Python code in isolated container...[/cyan]")
|
||||
try:
|
||||
import httpx
|
||||
import asyncio
|
||||
|
||||
async def execute_python():
|
||||
python_command = f'python3 -c "{code.replace('"', '\\"')}"'
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
response = await client.post(
|
||||
"http://localhost:8080/sh",
|
||||
json={"command": python_command},
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
return response
|
||||
|
||||
response = asyncio.run(execute_python())
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
console.print(f"[green]Python Output:[/green]\n{result.get('output', '')}")
|
||||
if result.get('error'):
|
||||
console.print(f"[red]Error:[/red] {result.get('error')}")
|
||||
else:
|
||||
console.print(f"[red]ai.bot error: HTTP {response.status_code}[/red]")
|
||||
except Exception as e:
|
||||
console.print(f"[red]Failed to execute Python code: {e}[/red]")
|
||||
|
||||
elif user_input.lower() == 'aibot-status':
|
||||
# Check ai.bot server status
|
||||
console.print("[cyan]Checking ai.bot server status...[/cyan]")
|
||||
try:
|
||||
import httpx
|
||||
import asyncio
|
||||
|
||||
async def check_status():
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
response = await client.get("http://localhost:8080/status")
|
||||
return response
|
||||
|
||||
response = asyncio.run(check_status())
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
console.print(f"[green]ai.bot is online![/green]")
|
||||
console.print(f"Server info: {result}")
|
||||
else:
|
||||
console.print(f"[yellow]ai.bot responded with status {response.status_code}[/yellow]")
|
||||
except Exception as e:
|
||||
console.print(f"[red]ai.bot is offline: {e}[/red]")
|
||||
console.print("[dim]Make sure ai.bot is running on localhost:8080[/dim]")
|
||||
|
||||
# Project management commands (Claude Code-like)
|
||||
elif user_input.lower() == 'project-status':
|
||||
# プロジェクト構造分析
|
||||
console.print("[cyan]Analyzing project structure...[/cyan]")
|
||||
try:
|
||||
developer = ContinuousDeveloper(Path.cwd(), ai_provider)
|
||||
analysis = developer.analyze_project_structure()
|
||||
changes = developer.project_state.detect_changes()
|
||||
|
||||
console.print(f"[green]Project Analysis:[/green]")
|
||||
console.print(f"Language: {analysis['language']}")
|
||||
console.print(f"Framework: {analysis['framework']}")
|
||||
console.print(f"Structure: {analysis['structure']}")
|
||||
console.print(f"Dependencies: {analysis['dependencies']}")
|
||||
console.print(f"Code Patterns: {analysis['patterns']}")
|
||||
|
||||
if changes:
|
||||
console.print(f"\n[yellow]Recent Changes:[/yellow]")
|
||||
for file_path, change_type in changes.items():
|
||||
console.print(f" {change_type}: {file_path}")
|
||||
else:
|
||||
console.print(f"\n[dim]No recent changes detected[/dim]")
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error analyzing project: {e}[/red]")
|
||||
|
||||
elif user_input.lower() == 'suggest-next':
|
||||
# 次のステップを提案
|
||||
console.print("[cyan]AI is analyzing project and suggesting next steps...[/cyan]")
|
||||
try:
|
||||
developer = ContinuousDeveloper(Path.cwd(), ai_provider)
|
||||
suggestions = developer.suggest_next_steps()
|
||||
|
||||
console.print(f"[green]Suggested Next Steps:[/green]")
|
||||
for i, suggestion in enumerate(suggestions, 1):
|
||||
console.print(f" {i}. {suggestion}")
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error generating suggestions: {e}[/red]")
|
||||
|
||||
elif user_input.lower().startswith('continuous'):
|
||||
# 継続開発モード
|
||||
console.print("[cyan]Enabling continuous development mode...[/cyan]")
|
||||
console.print("[yellow]Continuous mode is experimental. Type 'exit-continuous' to exit.[/yellow]")
|
||||
|
||||
try:
|
||||
developer = ContinuousDeveloper(Path.cwd(), ai_provider)
|
||||
context = developer.load_project_context()
|
||||
|
||||
console.print(f"[green]Project context loaded:[/green]")
|
||||
console.print(f"Context: {len(context)} characters")
|
||||
|
||||
# Add to session memory for continuous context
|
||||
persona.process_interaction(current_user, f"Continuous development mode started for project: {context[:500]}", ai_provider)
|
||||
console.print("[dim]Project context added to AI memory for continuous development.[/dim]")
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error starting continuous mode: {e}[/red]")
|
||||
|
||||
# Chat command or direct message
|
||||
else:
|
||||
# Remove 'chat' prefix if present
|
||||
@@ -668,7 +838,8 @@ def config(
|
||||
console.print(f"[yellow]Key '{key}' not found[/yellow]")
|
||||
|
||||
elif action == "list":
|
||||
keys = config.list_keys(key or "")
|
||||
config_instance = Config()
|
||||
keys = config_instance.list_keys(key or "")
|
||||
|
||||
if not keys:
|
||||
console.print("[yellow]No configuration keys found[/yellow]")
|
||||
@@ -679,7 +850,7 @@ def config(
|
||||
table.add_column("Value", style="green")
|
||||
|
||||
for k in sorted(keys):
|
||||
val = config.get(k)
|
||||
val = config_instance.get(k)
|
||||
# Hide sensitive values
|
||||
if "password" in k or "api_key" in k:
|
||||
display_val = "***hidden***" if val else "not set"
|
||||
@@ -695,5 +866,56 @@ def config(
|
||||
console.print("Valid actions: get, set, delete, list")
|
||||
|
||||
|
||||
@app.command()
|
||||
def import_chatgpt(
|
||||
file_path: Path = typer.Argument(..., help="Path to ChatGPT export JSON file"),
|
||||
user_id: str = typer.Option("chatgpt_user", "--user-id", "-u", help="User ID for imported conversations"),
|
||||
data_dir: Optional[Path] = typer.Option(None, "--data-dir", "-d", help="Data directory")
|
||||
):
|
||||
"""Import ChatGPT conversation data into ai.gpt memory system"""
|
||||
from .chatgpt_importer import ChatGPTImporter
|
||||
|
||||
if data_dir is None:
|
||||
data_dir = DEFAULT_DATA_DIR
|
||||
|
||||
data_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if not file_path.exists():
|
||||
console.print(f"[red]Error: File not found: {file_path}[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
console.print(f"[cyan]Importing ChatGPT data from {file_path}[/cyan]")
|
||||
console.print(f"User ID: {user_id}")
|
||||
console.print(f"Data directory: {data_dir}")
|
||||
|
||||
try:
|
||||
importer = ChatGPTImporter(data_dir)
|
||||
stats = importer.import_from_file(file_path, user_id)
|
||||
|
||||
# Display results
|
||||
table = Table(title="Import Results")
|
||||
table.add_column("Metric", style="cyan")
|
||||
table.add_column("Count", style="green")
|
||||
|
||||
table.add_row("Conversations imported", str(stats["conversations_imported"]))
|
||||
table.add_row("Total messages", str(stats["messages_imported"]))
|
||||
table.add_row("User messages", str(stats["user_messages"]))
|
||||
table.add_row("Assistant messages", str(stats["assistant_messages"]))
|
||||
table.add_row("Skipped messages", str(stats["skipped_messages"]))
|
||||
|
||||
console.print(table)
|
||||
console.print(f"[green]✓ Import completed successfully![/green]")
|
||||
|
||||
# Show next steps
|
||||
console.print("\n[cyan]Next steps:[/cyan]")
|
||||
console.print(f"- Check memories: [yellow]aigpt status[/yellow]")
|
||||
console.print(f"- Chat with AI: [yellow]aigpt chat {user_id} \"hello\"[/yellow]")
|
||||
console.print(f"- View relationships: [yellow]aigpt relationships[/yellow]")
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error during import: {e}[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
@@ -8,11 +8,12 @@ import logging
|
||||
import subprocess
|
||||
import os
|
||||
import shlex
|
||||
import httpx
|
||||
import json
|
||||
from .ai_provider import create_ai_provider
|
||||
|
||||
from .persona import Persona
|
||||
from .models import Memory, Relationship, PersonaState
|
||||
from .card_integration import CardIntegration, register_card_tools
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -20,7 +21,7 @@ logger = logging.getLogger(__name__)
|
||||
class AIGptMcpServer:
|
||||
"""MCP Server that exposes ai.gpt functionality to AI assistants"""
|
||||
|
||||
def __init__(self, data_dir: Path, enable_card_integration: bool = False):
|
||||
def __init__(self, data_dir: Path):
|
||||
self.data_dir = data_dir
|
||||
self.persona = Persona(data_dir)
|
||||
|
||||
@@ -32,10 +33,6 @@ class AIGptMcpServer:
|
||||
|
||||
# Create MCP server with FastAPI app
|
||||
self.server = FastApiMCP(self.app)
|
||||
self.card_integration = None
|
||||
|
||||
if enable_card_integration:
|
||||
self.card_integration = CardIntegration()
|
||||
|
||||
self._register_tools()
|
||||
|
||||
@@ -58,6 +55,108 @@ class AIGptMcpServer:
|
||||
for mem in memories
|
||||
]
|
||||
|
||||
@self.app.get("/get_contextual_memories", operation_id="get_contextual_memories")
|
||||
async def get_contextual_memories(query: str = "", limit: int = 10) -> Dict[str, List[Dict[str, Any]]]:
|
||||
"""Get memories organized by priority with contextual relevance"""
|
||||
memory_groups = self.persona.memory.get_contextual_memories(query=query, limit=limit)
|
||||
|
||||
result = {}
|
||||
for group_name, memories in memory_groups.items():
|
||||
result[group_name] = [
|
||||
{
|
||||
"id": mem.id,
|
||||
"content": mem.content,
|
||||
"level": mem.level.value,
|
||||
"importance": mem.importance_score,
|
||||
"is_core": mem.is_core,
|
||||
"timestamp": mem.timestamp.isoformat(),
|
||||
"summary": mem.summary,
|
||||
"metadata": mem.metadata
|
||||
}
|
||||
for mem in memories
|
||||
]
|
||||
return result
|
||||
|
||||
@self.app.post("/search_memories", operation_id="search_memories")
|
||||
async def search_memories(keywords: List[str], memory_types: Optional[List[str]] = None) -> List[Dict[str, Any]]:
|
||||
"""Search memories by keywords and optionally filter by memory types"""
|
||||
from .models import MemoryLevel
|
||||
|
||||
# Convert string memory types to enum if provided
|
||||
level_filter = None
|
||||
if memory_types:
|
||||
level_filter = []
|
||||
for mt in memory_types:
|
||||
try:
|
||||
level_filter.append(MemoryLevel(mt))
|
||||
except ValueError:
|
||||
pass # Skip invalid memory types
|
||||
|
||||
memories = self.persona.memory.search_memories(keywords, memory_types=level_filter)
|
||||
return [
|
||||
{
|
||||
"id": mem.id,
|
||||
"content": mem.content,
|
||||
"level": mem.level.value,
|
||||
"importance": mem.importance_score,
|
||||
"is_core": mem.is_core,
|
||||
"timestamp": mem.timestamp.isoformat(),
|
||||
"summary": mem.summary,
|
||||
"metadata": mem.metadata
|
||||
}
|
||||
for mem in memories
|
||||
]
|
||||
|
||||
@self.app.post("/create_summary", operation_id="create_summary")
|
||||
async def create_summary(user_id: str) -> Dict[str, Any]:
|
||||
"""Create an AI-powered summary of recent memories"""
|
||||
try:
|
||||
ai_provider = create_ai_provider()
|
||||
summary = self.persona.memory.create_smart_summary(user_id, ai_provider=ai_provider)
|
||||
|
||||
if summary:
|
||||
return {
|
||||
"success": True,
|
||||
"summary": {
|
||||
"id": summary.id,
|
||||
"content": summary.content,
|
||||
"level": summary.level.value,
|
||||
"importance": summary.importance_score,
|
||||
"timestamp": summary.timestamp.isoformat(),
|
||||
"metadata": summary.metadata
|
||||
}
|
||||
}
|
||||
else:
|
||||
return {"success": False, "reason": "Not enough memories to summarize"}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create summary: {e}")
|
||||
return {"success": False, "reason": str(e)}
|
||||
|
||||
@self.app.post("/create_core_memory", operation_id="create_core_memory")
|
||||
async def create_core_memory() -> Dict[str, Any]:
|
||||
"""Create a core memory by analyzing all existing memories"""
|
||||
try:
|
||||
ai_provider = create_ai_provider()
|
||||
core_memory = self.persona.memory.create_core_memory(ai_provider=ai_provider)
|
||||
|
||||
if core_memory:
|
||||
return {
|
||||
"success": True,
|
||||
"core_memory": {
|
||||
"id": core_memory.id,
|
||||
"content": core_memory.content,
|
||||
"level": core_memory.level.value,
|
||||
"importance": core_memory.importance_score,
|
||||
"timestamp": core_memory.timestamp.isoformat(),
|
||||
"metadata": core_memory.metadata
|
||||
}
|
||||
}
|
||||
else:
|
||||
return {"success": False, "reason": "Not enough memories to create core memory"}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create core memory: {e}")
|
||||
return {"success": False, "reason": str(e)}
|
||||
|
||||
@self.app.get("/get_relationship", operation_id="get_relationship")
|
||||
async def get_relationship(user_id: str) -> Dict[str, Any]:
|
||||
"""Get relationship status with a specific user"""
|
||||
@@ -101,6 +200,21 @@ class AIGptMcpServer:
|
||||
"active_memory_count": len(state.active_memories)
|
||||
}
|
||||
|
||||
@self.app.post("/get_context_prompt", operation_id="get_context_prompt")
|
||||
async def get_context_prompt(user_id: str, message: str) -> Dict[str, Any]:
|
||||
"""Get context-aware prompt for AI response generation"""
|
||||
try:
|
||||
context_prompt = self.persona.build_context_prompt(user_id, message)
|
||||
return {
|
||||
"success": True,
|
||||
"context_prompt": context_prompt,
|
||||
"user_id": user_id,
|
||||
"message": message
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to build context prompt: {e}")
|
||||
return {"success": False, "reason": str(e)}
|
||||
|
||||
@self.app.post("/process_interaction", operation_id="process_interaction")
|
||||
async def process_interaction(user_id: str, message: str) -> Dict[str, Any]:
|
||||
"""Process an interaction with a user"""
|
||||
@@ -301,9 +415,89 @@ class AIGptMcpServer:
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
# Register ai.card tools if integration is enabled
|
||||
if self.card_integration:
|
||||
register_card_tools(self.app, self.card_integration)
|
||||
# ai.bot integration tools
|
||||
@self.app.post("/remote_shell", operation_id="remote_shell")
|
||||
async def remote_shell(command: str, ai_bot_url: str = "http://localhost:8080") -> Dict[str, Any]:
|
||||
"""Execute command via ai.bot /sh functionality (systemd-nspawn isolated execution)"""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
# ai.bot の /sh エンドポイントに送信
|
||||
response = await client.post(
|
||||
f"{ai_bot_url}/sh",
|
||||
json={"command": command},
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
return {
|
||||
"status": "success",
|
||||
"command": command,
|
||||
"output": result.get("output", ""),
|
||||
"error": result.get("error", ""),
|
||||
"exit_code": result.get("exit_code", 0),
|
||||
"execution_time": result.get("execution_time", ""),
|
||||
"container_id": result.get("container_id", ""),
|
||||
"isolated": True # systemd-nspawn isolation
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": f"ai.bot responded with status {response.status_code}",
|
||||
"response_text": response.text
|
||||
}
|
||||
except httpx.TimeoutException:
|
||||
return {"status": "error", "error": "Request to ai.bot timed out"}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": f"Failed to connect to ai.bot: {str(e)}"}
|
||||
|
||||
@self.app.get("/ai_bot_status", operation_id="ai_bot_status")
|
||||
async def ai_bot_status(ai_bot_url: str = "http://localhost:8080") -> Dict[str, Any]:
|
||||
"""Check ai.bot server status and available commands"""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
response = await client.get(f"{ai_bot_url}/status")
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
return {
|
||||
"status": "online",
|
||||
"ai_bot_url": ai_bot_url,
|
||||
"server_info": result,
|
||||
"shell_available": True
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": f"ai.bot status check failed: {response.status_code}"
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"status": "offline",
|
||||
"error": f"Cannot connect to ai.bot: {str(e)}",
|
||||
"ai_bot_url": ai_bot_url
|
||||
}
|
||||
|
||||
@self.app.post("/isolated_python", operation_id="isolated_python")
|
||||
async def isolated_python(code: str, ai_bot_url: str = "http://localhost:8080") -> Dict[str, Any]:
|
||||
"""Execute Python code in isolated ai.bot environment"""
|
||||
# Python コードを /sh 経由で実行
|
||||
python_command = f'python3 -c "{code.replace('"', '\\"')}"'
|
||||
return await remote_shell(python_command, ai_bot_url)
|
||||
|
||||
@self.app.post("/isolated_analysis", operation_id="isolated_analysis")
|
||||
async def isolated_analysis(file_path: str, analysis_type: str = "structure", ai_bot_url: str = "http://localhost:8080") -> Dict[str, Any]:
|
||||
"""Perform code analysis in isolated environment"""
|
||||
if analysis_type == "structure":
|
||||
command = f"find {file_path} -type f -name '*.py' | head -20"
|
||||
elif analysis_type == "lines":
|
||||
command = f"wc -l {file_path}"
|
||||
elif analysis_type == "syntax":
|
||||
command = f"python3 -m py_compile {file_path}"
|
||||
else:
|
||||
command = f"file {file_path}"
|
||||
|
||||
return await remote_shell(command, ai_bot_url)
|
||||
|
||||
# Mount MCP server
|
||||
self.server.mount()
|
||||
@@ -314,5 +508,4 @@ class AIGptMcpServer:
|
||||
|
||||
async def close(self):
|
||||
"""Cleanup resources"""
|
||||
if self.card_integration:
|
||||
await self.card_integration.close()
|
||||
pass
|
@@ -67,8 +67,13 @@ class MemoryManager:
|
||||
self._save_memories()
|
||||
return memory
|
||||
|
||||
def summarize_memories(self, user_id: str) -> Optional[Memory]:
|
||||
"""Create summary from recent memories"""
|
||||
def add_memory(self, memory: Memory):
|
||||
"""Add a memory directly to the system"""
|
||||
self.memories[memory.id] = memory
|
||||
self._save_memories()
|
||||
|
||||
def create_smart_summary(self, user_id: str, ai_provider=None) -> Optional[Memory]:
|
||||
"""Create AI-powered thematic summary from recent memories"""
|
||||
recent_memories = [
|
||||
mem for mem in self.memories.values()
|
||||
if mem.level == MemoryLevel.FULL_LOG
|
||||
@@ -78,8 +83,40 @@ class MemoryManager:
|
||||
if len(recent_memories) < 5:
|
||||
return None
|
||||
|
||||
# Simple summary creation (in real implementation, use AI)
|
||||
summary_content = f"Summary of {len(recent_memories)} recent interactions"
|
||||
# Sort by timestamp for chronological analysis
|
||||
recent_memories.sort(key=lambda m: m.timestamp)
|
||||
|
||||
# Prepare conversation context for AI analysis
|
||||
conversations_text = "\n\n".join([
|
||||
f"[{mem.timestamp.strftime('%Y-%m-%d %H:%M')}] {mem.content}"
|
||||
for mem in recent_memories
|
||||
])
|
||||
|
||||
summary_prompt = f"""
|
||||
Analyze these recent conversations and create a thematic summary focusing on:
|
||||
1. Communication patterns and user preferences
|
||||
2. Technical topics and problem-solving approaches
|
||||
3. Relationship progression and trust level
|
||||
4. Key recurring themes and interests
|
||||
|
||||
Conversations:
|
||||
{conversations_text}
|
||||
|
||||
Create a concise summary (2-3 sentences) that captures the essence of this interaction period:
|
||||
"""
|
||||
|
||||
try:
|
||||
if ai_provider:
|
||||
summary_content = ai_provider.chat(summary_prompt, max_tokens=200)
|
||||
else:
|
||||
# Fallback to pattern-based analysis
|
||||
themes = self._extract_themes(recent_memories)
|
||||
summary_content = f"Themes: {', '.join(themes[:3])}. {len(recent_memories)} interactions with focus on technical discussions."
|
||||
except Exception as e:
|
||||
self.logger.warning(f"AI summary failed, using fallback: {e}")
|
||||
themes = self._extract_themes(recent_memories)
|
||||
summary_content = f"Themes: {', '.join(themes[:3])}. {len(recent_memories)} interactions."
|
||||
|
||||
summary_id = hashlib.sha256(
|
||||
f"summary_{datetime.now().isoformat()}".encode()
|
||||
).hexdigest()[:16]
|
||||
@@ -87,23 +124,154 @@ class MemoryManager:
|
||||
summary = Memory(
|
||||
id=summary_id,
|
||||
timestamp=datetime.now(),
|
||||
content=summary_content,
|
||||
content=f"SUMMARY ({len(recent_memories)} conversations): {summary_content}",
|
||||
summary=summary_content,
|
||||
level=MemoryLevel.SUMMARY,
|
||||
importance_score=0.5
|
||||
importance_score=0.6,
|
||||
metadata={
|
||||
"memory_count": len(recent_memories),
|
||||
"time_span": f"{recent_memories[0].timestamp.date()} to {recent_memories[-1].timestamp.date()}",
|
||||
"themes": self._extract_themes(recent_memories)[:5]
|
||||
}
|
||||
)
|
||||
|
||||
self.memories[summary.id] = summary
|
||||
|
||||
# Mark summarized memories for potential forgetting
|
||||
# Reduce importance of summarized memories
|
||||
for mem in recent_memories:
|
||||
mem.importance_score *= 0.9
|
||||
mem.importance_score *= 0.8
|
||||
|
||||
self._save_memories()
|
||||
return summary
|
||||
|
||||
def _extract_themes(self, memories: List[Memory]) -> List[str]:
|
||||
"""Extract common themes from memory content"""
|
||||
common_words = {}
|
||||
for memory in memories:
|
||||
# Simple keyword extraction
|
||||
words = memory.content.lower().split()
|
||||
for word in words:
|
||||
if len(word) > 4 and word.isalpha():
|
||||
common_words[word] = common_words.get(word, 0) + 1
|
||||
|
||||
# Return most frequent meaningful words
|
||||
return sorted(common_words.keys(), key=common_words.get, reverse=True)[:10]
|
||||
|
||||
def create_core_memory(self, ai_provider=None) -> Optional[Memory]:
|
||||
"""Analyze all memories to extract core personality-forming elements"""
|
||||
# Collect all non-forgotten memories for analysis
|
||||
all_memories = [
|
||||
mem for mem in self.memories.values()
|
||||
if mem.level != MemoryLevel.FORGOTTEN
|
||||
]
|
||||
|
||||
if len(all_memories) < 10:
|
||||
return None
|
||||
|
||||
# Sort by importance and timestamp for comprehensive analysis
|
||||
all_memories.sort(key=lambda m: (m.importance_score, m.timestamp), reverse=True)
|
||||
|
||||
# Prepare memory context for AI analysis
|
||||
memory_context = "\n".join([
|
||||
f"[{mem.level.value}] {mem.timestamp.strftime('%Y-%m-%d')}: {mem.content[:200]}..."
|
||||
for mem in all_memories[:20] # Top 20 memories
|
||||
])
|
||||
|
||||
core_prompt = f"""
|
||||
Analyze these conversations and memories to identify core personality elements that define this user relationship:
|
||||
|
||||
1. Communication style and preferences
|
||||
2. Core values and principles
|
||||
3. Problem-solving patterns
|
||||
4. Trust level and relationship depth
|
||||
5. Unique characteristics that make this relationship special
|
||||
|
||||
Memories:
|
||||
{memory_context}
|
||||
|
||||
Extract the essential personality-forming elements (2-3 sentences) that should NEVER be forgotten:
|
||||
"""
|
||||
|
||||
try:
|
||||
if ai_provider:
|
||||
core_content = ai_provider.chat(core_prompt, max_tokens=150)
|
||||
else:
|
||||
# Fallback to pattern analysis
|
||||
user_patterns = self._analyze_user_patterns(all_memories)
|
||||
core_content = f"User shows {user_patterns['communication_style']} communication, focuses on {user_patterns['main_interests']}, and demonstrates {user_patterns['problem_solving']} approach."
|
||||
except Exception as e:
|
||||
self.logger.warning(f"AI core analysis failed, using fallback: {e}")
|
||||
user_patterns = self._analyze_user_patterns(all_memories)
|
||||
core_content = f"Core pattern: {user_patterns['communication_style']} style, {user_patterns['main_interests']} interests."
|
||||
|
||||
# Create core memory
|
||||
core_id = hashlib.sha256(
|
||||
f"core_{datetime.now().isoformat()}".encode()
|
||||
).hexdigest()[:16]
|
||||
|
||||
core_memory = Memory(
|
||||
id=core_id,
|
||||
timestamp=datetime.now(),
|
||||
content=f"CORE PERSONALITY: {core_content}",
|
||||
summary=core_content,
|
||||
level=MemoryLevel.CORE,
|
||||
importance_score=1.0,
|
||||
is_core=True,
|
||||
metadata={
|
||||
"source_memories": len(all_memories),
|
||||
"analysis_date": datetime.now().isoformat(),
|
||||
"patterns": self._analyze_user_patterns(all_memories)
|
||||
}
|
||||
)
|
||||
|
||||
self.memories[core_memory.id] = core_memory
|
||||
self._save_memories()
|
||||
|
||||
self.logger.info(f"Core memory created: {core_id}")
|
||||
return core_memory
|
||||
|
||||
def _analyze_user_patterns(self, memories: List[Memory]) -> Dict[str, str]:
|
||||
"""Analyze patterns in user behavior from memories"""
|
||||
# Extract patterns from conversation content
|
||||
all_content = " ".join([mem.content.lower() for mem in memories])
|
||||
|
||||
# Simple pattern detection
|
||||
communication_indicators = {
|
||||
"technical": ["code", "implementation", "system", "api", "database"],
|
||||
"casual": ["thanks", "please", "sorry", "help"],
|
||||
"formal": ["could", "would", "should", "proper"]
|
||||
}
|
||||
|
||||
problem_solving_indicators = {
|
||||
"systematic": ["first", "then", "next", "step", "plan"],
|
||||
"experimental": ["try", "test", "experiment", "see"],
|
||||
"theoretical": ["concept", "design", "architecture", "pattern"]
|
||||
}
|
||||
|
||||
# Score each pattern
|
||||
communication_style = max(
|
||||
communication_indicators.keys(),
|
||||
key=lambda style: sum(all_content.count(word) for word in communication_indicators[style])
|
||||
)
|
||||
|
||||
problem_solving = max(
|
||||
problem_solving_indicators.keys(),
|
||||
key=lambda style: sum(all_content.count(word) for word in problem_solving_indicators[style])
|
||||
)
|
||||
|
||||
# Extract main interests from themes
|
||||
themes = self._extract_themes(memories)
|
||||
main_interests = ", ".join(themes[:3]) if themes else "general technology"
|
||||
|
||||
return {
|
||||
"communication_style": communication_style,
|
||||
"problem_solving": problem_solving,
|
||||
"main_interests": main_interests,
|
||||
"interaction_count": len(memories)
|
||||
}
|
||||
|
||||
def identify_core_memories(self) -> List[Memory]:
|
||||
"""Identify memories that should become core (never forgotten)"""
|
||||
"""Identify existing memories that should become core (legacy method)"""
|
||||
core_candidates = [
|
||||
mem for mem in self.memories.values()
|
||||
if mem.importance_score > 0.8
|
||||
@@ -140,7 +308,7 @@ class MemoryManager:
|
||||
self._save_memories()
|
||||
|
||||
def get_active_memories(self, limit: int = 10) -> List[Memory]:
|
||||
"""Get currently active memories for persona"""
|
||||
"""Get currently active memories for persona (legacy method)"""
|
||||
active = [
|
||||
mem for mem in self.memories.values()
|
||||
if mem.level != MemoryLevel.FORGOTTEN
|
||||
@@ -152,4 +320,89 @@ class MemoryManager:
|
||||
reverse=True
|
||||
)
|
||||
|
||||
return active[:limit]
|
||||
return active[:limit]
|
||||
|
||||
def get_contextual_memories(self, query: str = "", limit: int = 10) -> Dict[str, List[Memory]]:
|
||||
"""Get memories organized by priority with contextual relevance"""
|
||||
all_memories = [
|
||||
mem for mem in self.memories.values()
|
||||
if mem.level != MemoryLevel.FORGOTTEN
|
||||
]
|
||||
|
||||
# Categorize memories by type and importance
|
||||
core_memories = [mem for mem in all_memories if mem.level == MemoryLevel.CORE]
|
||||
summary_memories = [mem for mem in all_memories if mem.level == MemoryLevel.SUMMARY]
|
||||
recent_memories = [
|
||||
mem for mem in all_memories
|
||||
if mem.level == MemoryLevel.FULL_LOG
|
||||
and (datetime.now() - mem.timestamp).days < 3
|
||||
]
|
||||
|
||||
# Apply keyword relevance if query provided
|
||||
if query:
|
||||
query_lower = query.lower()
|
||||
|
||||
def relevance_score(memory: Memory) -> float:
|
||||
content_score = 1 if query_lower in memory.content.lower() else 0
|
||||
summary_score = 1 if memory.summary and query_lower in memory.summary.lower() else 0
|
||||
metadata_score = 1 if any(
|
||||
query_lower in str(v).lower()
|
||||
for v in (memory.metadata or {}).values()
|
||||
) else 0
|
||||
return content_score + summary_score + metadata_score
|
||||
|
||||
# Re-rank by relevance while maintaining type priority
|
||||
core_memories.sort(key=lambda m: (relevance_score(m), m.importance_score), reverse=True)
|
||||
summary_memories.sort(key=lambda m: (relevance_score(m), m.importance_score), reverse=True)
|
||||
recent_memories.sort(key=lambda m: (relevance_score(m), m.importance_score), reverse=True)
|
||||
else:
|
||||
# Sort by importance and recency
|
||||
core_memories.sort(key=lambda m: (m.importance_score, m.timestamp), reverse=True)
|
||||
summary_memories.sort(key=lambda m: (m.importance_score, m.timestamp), reverse=True)
|
||||
recent_memories.sort(key=lambda m: (m.importance_score, m.timestamp), reverse=True)
|
||||
|
||||
# Return organized memory structure
|
||||
return {
|
||||
"core": core_memories[:3], # Always include top core memories
|
||||
"summary": summary_memories[:3], # Recent summaries
|
||||
"recent": recent_memories[:limit-6], # Fill remaining with recent
|
||||
"all_active": all_memories[:limit] # Fallback for simple access
|
||||
}
|
||||
|
||||
def search_memories(self, keywords: List[str], memory_types: List[MemoryLevel] = None) -> List[Memory]:
|
||||
"""Search memories by keywords and optionally filter by memory types"""
|
||||
if memory_types is None:
|
||||
memory_types = [MemoryLevel.CORE, MemoryLevel.SUMMARY, MemoryLevel.FULL_LOG]
|
||||
|
||||
matching_memories = []
|
||||
|
||||
for memory in self.memories.values():
|
||||
if memory.level not in memory_types or memory.level == MemoryLevel.FORGOTTEN:
|
||||
continue
|
||||
|
||||
# Check if any keyword matches in content, summary, or metadata
|
||||
content_text = f"{memory.content} {memory.summary or ''}"
|
||||
if memory.metadata:
|
||||
content_text += " " + " ".join(str(v) for v in memory.metadata.values())
|
||||
|
||||
content_lower = content_text.lower()
|
||||
|
||||
# Score by keyword matches
|
||||
match_score = sum(
|
||||
keyword.lower() in content_lower
|
||||
for keyword in keywords
|
||||
)
|
||||
|
||||
if match_score > 0:
|
||||
# Add match score to memory for sorting
|
||||
memory_copy = memory.model_copy()
|
||||
memory_copy.importance_score += match_score * 0.1
|
||||
matching_memories.append(memory_copy)
|
||||
|
||||
# Sort by relevance (match score + importance + core status)
|
||||
matching_memories.sort(
|
||||
key=lambda m: (m.is_core, m.importance_score, m.timestamp),
|
||||
reverse=True
|
||||
)
|
||||
|
||||
return matching_memories
|
@@ -3,7 +3,7 @@
|
||||
from datetime import datetime, date
|
||||
from typing import Optional, Dict, List, Any
|
||||
from enum import Enum
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
|
||||
class MemoryLevel(str, Enum):
|
||||
@@ -30,9 +30,18 @@ class Memory(BaseModel):
|
||||
content: str
|
||||
summary: Optional[str] = None
|
||||
level: MemoryLevel = MemoryLevel.FULL_LOG
|
||||
importance_score: float = Field(ge=0.0, le=1.0)
|
||||
importance_score: float
|
||||
is_core: bool = False
|
||||
decay_rate: float = 0.01
|
||||
metadata: Optional[Dict[str, Any]] = None
|
||||
|
||||
@field_validator('importance_score')
|
||||
@classmethod
|
||||
def validate_importance_score(cls, v):
|
||||
"""Ensure importance_score is within valid range, handle floating point precision issues"""
|
||||
if abs(v) < 1e-10: # Very close to zero
|
||||
return 0.0
|
||||
return max(0.0, min(1.0, v))
|
||||
|
||||
|
||||
class Relationship(BaseModel):
|
||||
|
@@ -92,27 +92,85 @@ class Persona:
|
||||
else:
|
||||
return "contemplative"
|
||||
|
||||
def build_context_prompt(self, user_id: str, current_message: str) -> str:
|
||||
"""Build context-aware prompt with relevant memories"""
|
||||
# Get contextual memories based on current message
|
||||
memory_groups = self.memory.get_contextual_memories(query=current_message, limit=8)
|
||||
|
||||
# Build context sections
|
||||
context_parts = []
|
||||
|
||||
# Core personality elements (always included)
|
||||
if memory_groups["core"]:
|
||||
core_context = "\n".join([mem.content for mem in memory_groups["core"]])
|
||||
context_parts.append(f"CORE PERSONALITY:\n{core_context}")
|
||||
|
||||
# Recent summaries for context
|
||||
if memory_groups["summary"]:
|
||||
summary_context = "\n".join([mem.summary or mem.content for mem in memory_groups["summary"]])
|
||||
context_parts.append(f"RECENT PATTERNS:\n{summary_context}")
|
||||
|
||||
# Recent specific interactions
|
||||
if memory_groups["recent"]:
|
||||
recent_context = "\n".join([
|
||||
f"[{mem.timestamp.strftime('%m-%d')}] {mem.content[:100]}..."
|
||||
for mem in memory_groups["recent"][:3]
|
||||
])
|
||||
context_parts.append(f"RECENT INTERACTIONS:\n{recent_context}")
|
||||
|
||||
# Get current persona state
|
||||
state = self.get_current_state()
|
||||
|
||||
# Build final prompt
|
||||
context_prompt = f"""You are an AI with persistent memory and evolving relationships. Your current state:
|
||||
|
||||
PERSONALITY: {', '.join([f'{k}={v:.1f}' for k, v in state.base_personality.items()])}
|
||||
MOOD: {state.current_mood}
|
||||
FORTUNE: {state.fortune.fortune_value}/10
|
||||
|
||||
"""
|
||||
|
||||
if context_parts:
|
||||
context_prompt += "RELEVANT CONTEXT:\n" + "\n\n".join(context_parts) + "\n\n"
|
||||
|
||||
context_prompt += f"""Respond to this message while staying true to your personality and the established relationship context:
|
||||
|
||||
User: {current_message}
|
||||
|
||||
AI:"""
|
||||
|
||||
return context_prompt
|
||||
|
||||
def process_interaction(self, user_id: str, message: str, ai_provider=None) -> tuple[str, float]:
|
||||
"""Process user interaction and generate response"""
|
||||
"""Process user interaction and generate response with enhanced context"""
|
||||
# Get current state
|
||||
state = self.get_current_state()
|
||||
|
||||
# Get relationship with user
|
||||
relationship = self.relationships.get_or_create_relationship(user_id)
|
||||
|
||||
# Simple response generation (use AI provider if available)
|
||||
# Enhanced response generation with context awareness
|
||||
if relationship.is_broken:
|
||||
response = "..."
|
||||
relationship_delta = 0.0
|
||||
else:
|
||||
if ai_provider:
|
||||
# Use AI provider for response generation
|
||||
memories = self.memory.get_active_memories(limit=5)
|
||||
import asyncio
|
||||
response = asyncio.run(
|
||||
ai_provider.generate_response(message, state, memories)
|
||||
)
|
||||
# Calculate relationship delta based on interaction quality
|
||||
# Build context-aware prompt
|
||||
context_prompt = self.build_context_prompt(user_id, message)
|
||||
|
||||
# Generate response using AI with full context
|
||||
try:
|
||||
response = ai_provider.chat(context_prompt, max_tokens=200)
|
||||
|
||||
# Clean up response if it includes the prompt echo
|
||||
if "AI:" in response:
|
||||
response = response.split("AI:")[-1].strip()
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"AI response generation failed: {e}")
|
||||
response = f"I appreciate your message about {message[:50]}..."
|
||||
|
||||
# Calculate relationship delta based on interaction quality and context
|
||||
if state.current_mood in ["joyful", "cheerful"]:
|
||||
relationship_delta = 2.0
|
||||
elif relationship.status.value == "close_friend":
|
||||
@@ -120,8 +178,14 @@ class Persona:
|
||||
else:
|
||||
relationship_delta = 1.0
|
||||
else:
|
||||
# Fallback to simple responses
|
||||
if state.current_mood == "joyful":
|
||||
# Context-aware fallback responses
|
||||
memory_groups = self.memory.get_contextual_memories(query=message, limit=3)
|
||||
|
||||
if memory_groups["core"]:
|
||||
# Reference core memories for continuity
|
||||
response = f"Based on our relationship, I think {message.lower()} connects to what we've discussed before."
|
||||
relationship_delta = 1.5
|
||||
elif state.current_mood == "joyful":
|
||||
response = f"What a wonderful day! {message} sounds interesting!"
|
||||
relationship_delta = 2.0
|
||||
elif relationship.status.value == "close_friend":
|
||||
@@ -171,11 +235,16 @@ class Persona:
|
||||
if core_memories:
|
||||
self.logger.info(f"Identified {len(core_memories)} new core memories")
|
||||
|
||||
# Create memory summaries
|
||||
# Create memory summaries
|
||||
for user_id in self.relationships.relationships:
|
||||
summary = self.memory.summarize_memories(user_id)
|
||||
if summary:
|
||||
self.logger.info(f"Created summary for interactions with {user_id}")
|
||||
try:
|
||||
from .ai_provider import create_ai_provider
|
||||
ai_provider = create_ai_provider()
|
||||
summary = self.memory.create_smart_summary(user_id, ai_provider=ai_provider)
|
||||
if summary:
|
||||
self.logger.info(f"Created smart summary for interactions with {user_id}")
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Could not create AI summary for {user_id}: {e}")
|
||||
|
||||
self._save_state()
|
||||
self.logger.info("Daily maintenance completed")
|
321
src/aigpt/project_manager.py
Normal file
321
src/aigpt/project_manager.py
Normal file
@@ -0,0 +1,321 @@
|
||||
"""Project management and continuous development logic for ai.shell"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Any
|
||||
from datetime import datetime
|
||||
import subprocess
|
||||
import hashlib
|
||||
|
||||
from .models import Memory
|
||||
from .ai_provider import AIProvider
|
||||
|
||||
|
||||
class ProjectState:
|
||||
"""プロジェクトの現在状態を追跡"""
|
||||
|
||||
def __init__(self, project_root: Path):
|
||||
self.project_root = project_root
|
||||
self.files_state: Dict[str, str] = {} # ファイルパス: ハッシュ
|
||||
self.last_analysis: Optional[datetime] = None
|
||||
self.project_context: Optional[str] = None
|
||||
self.development_goals: List[str] = []
|
||||
self.known_patterns: Dict[str, Any] = {}
|
||||
|
||||
def scan_project_files(self) -> Dict[str, str]:
|
||||
"""プロジェクトファイルをスキャンしてハッシュ計算"""
|
||||
current_state = {}
|
||||
|
||||
# 対象ファイル拡張子
|
||||
target_extensions = {'.py', '.js', '.ts', '.rs', '.go', '.java', '.cpp', '.c', '.h'}
|
||||
|
||||
for file_path in self.project_root.rglob('*'):
|
||||
if (file_path.is_file() and
|
||||
file_path.suffix in target_extensions and
|
||||
not any(part.startswith('.') for part in file_path.parts)):
|
||||
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
file_hash = hashlib.md5(content.encode()).hexdigest()
|
||||
relative_path = str(file_path.relative_to(self.project_root))
|
||||
current_state[relative_path] = file_hash
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
return current_state
|
||||
|
||||
def detect_changes(self) -> Dict[str, str]:
|
||||
"""ファイル変更を検出"""
|
||||
current_state = self.scan_project_files()
|
||||
changes = {}
|
||||
|
||||
# 新規・変更ファイル
|
||||
for path, current_hash in current_state.items():
|
||||
if path not in self.files_state or self.files_state[path] != current_hash:
|
||||
changes[path] = "modified" if path in self.files_state else "added"
|
||||
|
||||
# 削除ファイル
|
||||
for path in self.files_state:
|
||||
if path not in current_state:
|
||||
changes[path] = "deleted"
|
||||
|
||||
self.files_state = current_state
|
||||
return changes
|
||||
|
||||
|
||||
class ContinuousDeveloper:
|
||||
"""Claude Code的な継続開発機能"""
|
||||
|
||||
def __init__(self, project_root: Path, ai_provider: Optional[AIProvider] = None):
|
||||
self.project_root = project_root
|
||||
self.ai_provider = ai_provider
|
||||
self.project_state = ProjectState(project_root)
|
||||
self.session_memory: List[str] = []
|
||||
|
||||
def load_project_context(self) -> str:
|
||||
"""プロジェクト文脈を読み込み"""
|
||||
context_files = [
|
||||
"claude.md", "aishell.md", "README.md",
|
||||
"pyproject.toml", "package.json", "Cargo.toml"
|
||||
]
|
||||
|
||||
context_parts = []
|
||||
for filename in context_files:
|
||||
file_path = self.project_root / filename
|
||||
if file_path.exists():
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
context_parts.append(f"## {filename}\n{content}")
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
return "\n\n".join(context_parts)
|
||||
|
||||
def analyze_project_structure(self) -> Dict[str, Any]:
|
||||
"""プロジェクト構造を分析"""
|
||||
analysis = {
|
||||
"language": self._detect_primary_language(),
|
||||
"framework": self._detect_framework(),
|
||||
"structure": self._analyze_file_structure(),
|
||||
"dependencies": self._analyze_dependencies(),
|
||||
"patterns": self._detect_code_patterns()
|
||||
}
|
||||
return analysis
|
||||
|
||||
def _detect_primary_language(self) -> str:
|
||||
"""主要言語を検出"""
|
||||
file_counts = {}
|
||||
for file_path in self.project_root.rglob('*'):
|
||||
if file_path.is_file() and file_path.suffix:
|
||||
ext = file_path.suffix.lower()
|
||||
file_counts[ext] = file_counts.get(ext, 0) + 1
|
||||
|
||||
language_map = {
|
||||
'.py': 'Python',
|
||||
'.js': 'JavaScript',
|
||||
'.ts': 'TypeScript',
|
||||
'.rs': 'Rust',
|
||||
'.go': 'Go',
|
||||
'.java': 'Java'
|
||||
}
|
||||
|
||||
if file_counts:
|
||||
primary_ext = max(file_counts.items(), key=lambda x: x[1])[0]
|
||||
return language_map.get(primary_ext, 'Unknown')
|
||||
return 'Unknown'
|
||||
|
||||
def _detect_framework(self) -> str:
|
||||
"""フレームワークを検出"""
|
||||
frameworks = {
|
||||
'fastapi': ['fastapi', 'uvicorn'],
|
||||
'django': ['django'],
|
||||
'flask': ['flask'],
|
||||
'react': ['react'],
|
||||
'next.js': ['next'],
|
||||
'rust-actix': ['actix-web'],
|
||||
}
|
||||
|
||||
# pyproject.toml, package.json, Cargo.tomlから依存関係を確認
|
||||
for config_file in ['pyproject.toml', 'package.json', 'Cargo.toml']:
|
||||
config_path = self.project_root / config_file
|
||||
if config_path.exists():
|
||||
try:
|
||||
with open(config_path, 'r') as f:
|
||||
content = f.read().lower()
|
||||
|
||||
for framework, keywords in frameworks.items():
|
||||
if any(keyword in content for keyword in keywords):
|
||||
return framework
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
return 'Unknown'
|
||||
|
||||
def _analyze_file_structure(self) -> Dict[str, List[str]]:
|
||||
"""ファイル構造を分析"""
|
||||
structure = {"directories": [], "key_files": []}
|
||||
|
||||
for item in self.project_root.iterdir():
|
||||
if item.is_dir() and not item.name.startswith('.'):
|
||||
structure["directories"].append(item.name)
|
||||
elif item.is_file() and item.name in [
|
||||
'main.py', 'app.py', 'index.js', 'main.rs', 'main.go'
|
||||
]:
|
||||
structure["key_files"].append(item.name)
|
||||
|
||||
return structure
|
||||
|
||||
def _analyze_dependencies(self) -> List[str]:
|
||||
"""依存関係を分析"""
|
||||
deps = []
|
||||
|
||||
# Python dependencies
|
||||
pyproject = self.project_root / "pyproject.toml"
|
||||
if pyproject.exists():
|
||||
try:
|
||||
with open(pyproject, 'r') as f:
|
||||
content = f.read()
|
||||
# Simple regex would be better but for now just check for common packages
|
||||
common_packages = ['fastapi', 'pydantic', 'uvicorn', 'ollama', 'openai']
|
||||
for package in common_packages:
|
||||
if package in content:
|
||||
deps.append(package)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return deps
|
||||
|
||||
def _detect_code_patterns(self) -> Dict[str, int]:
|
||||
"""コードパターンを検出"""
|
||||
patterns = {
|
||||
"classes": 0,
|
||||
"functions": 0,
|
||||
"api_endpoints": 0,
|
||||
"async_functions": 0
|
||||
}
|
||||
|
||||
for py_file in self.project_root.rglob('*.py'):
|
||||
try:
|
||||
with open(py_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
patterns["classes"] += content.count('class ')
|
||||
patterns["functions"] += content.count('def ')
|
||||
patterns["api_endpoints"] += content.count('@app.')
|
||||
patterns["async_functions"] += content.count('async def')
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
return patterns
|
||||
|
||||
def suggest_next_steps(self, current_task: Optional[str] = None) -> List[str]:
|
||||
"""次のステップを提案"""
|
||||
if not self.ai_provider:
|
||||
return ["AI provider not available for suggestions"]
|
||||
|
||||
context = self.load_project_context()
|
||||
analysis = self.analyze_project_structure()
|
||||
changes = self.project_state.detect_changes()
|
||||
|
||||
prompt = f"""
|
||||
プロジェクト分析に基づいて、次の開発ステップを3-5個提案してください。
|
||||
|
||||
## プロジェクト文脈
|
||||
{context[:1000]}
|
||||
|
||||
## 構造分析
|
||||
言語: {analysis['language']}
|
||||
フレームワーク: {analysis['framework']}
|
||||
パターン: {analysis['patterns']}
|
||||
|
||||
## 最近の変更
|
||||
{changes}
|
||||
|
||||
## 現在のタスク
|
||||
{current_task or "特になし"}
|
||||
|
||||
具体的で実行可能なステップを提案してください:
|
||||
"""
|
||||
|
||||
try:
|
||||
response = self.ai_provider.chat(prompt, max_tokens=300)
|
||||
# Simple parsing - in real implementation would be more sophisticated
|
||||
steps = [line.strip() for line in response.split('\n')
|
||||
if line.strip() and (line.strip().startswith('-') or line.strip().startswith('1.'))]
|
||||
return steps[:5]
|
||||
except Exception as e:
|
||||
return [f"Error generating suggestions: {str(e)}"]
|
||||
|
||||
def generate_code(self, description: str, file_path: Optional[str] = None) -> str:
|
||||
"""コード生成"""
|
||||
if not self.ai_provider:
|
||||
return "AI provider not available for code generation"
|
||||
|
||||
context = self.load_project_context()
|
||||
analysis = self.analyze_project_structure()
|
||||
|
||||
prompt = f"""
|
||||
以下の仕様に基づいてコードを生成してください。
|
||||
|
||||
## プロジェクト文脈
|
||||
{context[:800]}
|
||||
|
||||
## 言語・フレームワーク
|
||||
言語: {analysis['language']}
|
||||
フレームワーク: {analysis['framework']}
|
||||
既存パターン: {analysis['patterns']}
|
||||
|
||||
## 生成要求
|
||||
{description}
|
||||
|
||||
{"ファイルパス: " + file_path if file_path else ""}
|
||||
|
||||
プロジェクトの既存コードスタイルと一貫性を保ったコードを生成してください:
|
||||
"""
|
||||
|
||||
try:
|
||||
return self.ai_provider.chat(prompt, max_tokens=500)
|
||||
except Exception as e:
|
||||
return f"Error generating code: {str(e)}"
|
||||
|
||||
def analyze_file(self, file_path: str) -> str:
|
||||
"""ファイル分析"""
|
||||
full_path = self.project_root / file_path
|
||||
if not full_path.exists():
|
||||
return f"File not found: {file_path}"
|
||||
|
||||
try:
|
||||
with open(full_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
except Exception as e:
|
||||
return f"Error reading file: {str(e)}"
|
||||
|
||||
if not self.ai_provider:
|
||||
return f"File contents ({len(content)} chars):\n{content[:200]}..."
|
||||
|
||||
context = self.load_project_context()
|
||||
|
||||
prompt = f"""
|
||||
以下のファイルを分析して、改善点や問題点を指摘してください。
|
||||
|
||||
## プロジェクト文脈
|
||||
{context[:500]}
|
||||
|
||||
## ファイル: {file_path}
|
||||
{content[:1500]}
|
||||
|
||||
分析内容:
|
||||
1. コード品質
|
||||
2. プロジェクトとの整合性
|
||||
3. 改善提案
|
||||
4. 潜在的な問題
|
||||
"""
|
||||
|
||||
try:
|
||||
return self.ai_provider.chat(prompt, max_tokens=400)
|
||||
except Exception as e:
|
||||
return f"Error analyzing file: {str(e)}"
|
Reference in New Issue
Block a user