feature/shell-integration #1
@ -24,7 +24,25 @@
|
||||
"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(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": []
|
||||
}
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ 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,290 @@
|
||||
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. **セキュリティ強化**: 隔離実行の権限制御・サンドボックス検証
|
||||
|
||||
## 前回セッション終了事項 (2025/06/02)
|
||||
|
||||
前回のセッションがAPI Errorで終了した。これはchatgptのjsonを読み込んでもらったあとの処理で起こった。今回継続して正常に動作確認完了。
|
||||
|
||||
> API Error (Request timed out.) · Retrying in 1 seconds… (attempt 1/10)
|
||||
|
||||
```sh
|
||||
⏺ 興味深い質問ですね!4,000件以上のChatGPT会話ログを見ていて、効果的な記憶
|
||||
構築について考えてみました。
|
||||
|
||||
🧠 理想的な記憶構築アプローチ
|
||||
|
||||
1. 階層的記憶の活用
|
||||
|
||||
完全ログ (FULL_LOG) → 要約 (SUMMARY) → コア記憶 (CORE)
|
||||
↓ ↓ ↓
|
||||
日常会話の蓄積 テーマ別の集約 人格形成要素
|
||||
|
||||
具体例:
|
||||
- FULL_LOG: "今日は雨だね" → "そうですね、傘を忘れずに"
|
||||
- SUMMARY: "天気について10回会話、ユーザーは雨を気にする傾向"
|
||||
- CORE: "このユーザーは日常的な観察を共有したがる、気遣いを重視"
|
||||
|
||||
2. コンテキスト認識記憶
|
||||
|
||||
現在のインポートは単発メッセージですが、会話の流れを記憶すべき:
|
||||
|
||||
# 現在: 単発記憶
|
||||
"User: プログラミングについて教えて"
|
||||
"[AI Response] プログラミングは..."
|
||||
|
||||
# 理想: 文脈付き記憶
|
||||
"Context: 技術相談 → プログラミング学習相談 → 具体的な言語選択"
|
||||
"User pattern: 段階的に深掘りする質問スタイル"
|
||||
"AI approach: 基礎から応用への誘導が効果的"
|
||||
|
||||
3. 感情・関係性の記憶
|
||||
|
||||
# 関係性変化の記録
|
||||
"初回: 礼儀正しい距離感"
|
||||
"3回目: 冗談を交える"
|
||||
"10回目: 個人的な悩み相談"
|
||||
"現在: 親しみやすい口調を好む"
|
||||
|
||||
4. 実用的な記憶カテゴリ
|
||||
|
||||
ユーザーの特徴記憶:
|
||||
- 専門分野・興味関心
|
||||
- コミュニケーションスタイル
|
||||
- 質問の傾向・深度
|
||||
- 時間帯・頻度パターン
|
||||
|
||||
効果的だった応答記憶:
|
||||
- どんな説明が理解されやすいか
|
||||
- どんな例え・比喩が刺さるか
|
||||
- ユーモアの好み
|
||||
- 情報量の適切さ
|
||||
|
||||
失敗・注意点記憶:
|
||||
- 避けるべき話題
|
||||
- 誤解を招いた表現
|
||||
- 不快にさせた応答パターン
|
||||
|
||||
🎯 ChatGPTログから見える改善ポイント
|
||||
|
||||
あなたのログを見ると:
|
||||
|
||||
1. 技術的な会話が多い → プログラミング・システム設計の文脈記憶が重要
|
||||
2. 段階的な問題解決 → 前の質問からの発展パターンを記憶
|
||||
3. 具体的な実装相談 → 過去の解決策と現在の問題の関連性
|
||||
|
||||
理想的な記憶構築例:
|
||||
[CORE] ユーザーsyuiは複雑なシステム設計を段階的に構築する
|
||||
[SUMMARY] atproto, MCP, Rustに精通、実装重視の相談スタイル
|
||||
[CONTEXT] 前回はai.card統合 → 今回はChatGPT記憶 → 次は自律送信?
|
||||
[PATTERN] 機能実装 → テスト → ドキュメント化の流れを好む
|
||||
|
||||
記憶検索の改善案:
|
||||
- キーワードマッチング("MCP", "記憶", "atproto")
|
||||
- 時系列での関連性(前回の続き?)
|
||||
- 問題解決パターンの類似性
|
||||
|
||||
ollama準備中に、こんな記憶活用の仕組みを作り込んでおけば、より自然で文脈
|
||||
を理解した会話ができそうですね!
|
||||
|
||||
> わかりました。そのようにしましょう。core, summaryという2つの記憶を用意し
|
||||
て、それを作成する処理を書き、AIが優先する順位を明確にしましょう。
|
||||
|
||||
⏺ Update Todos
|
||||
⎿ ☐ 記憶の優先順位とロジックを設計
|
||||
☐ CORE記憶生成処理を実装
|
||||
☐ SUMMARY記憶生成処理を実装
|
||||
☐ 記憶検索・選択ロジックを改善
|
||||
☐ AI応答での記憶活用を強化
|
||||
```
|
||||
|
||||
|
||||
このファイルを参照することで、次回の開発がスムーズに始められます。
|
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
@ -1 +1 @@
|
||||
Subproject commit 6dbe630b9d3d72c3da0da1edade8c47231b6863d
|
||||
Subproject commit 6cd8014f80ae5a2a3100cc199bf83237057d8dd0
|
@ -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"
|
||||
}
|
||||
]
|
@ -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
|
||||
|
@ -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)
|
252
src/aigpt/cli.py
252
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,13 +573,18 @@ 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]")
|
||||
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}")
|
||||
@ -574,10 +592,16 @@ def shell(
|
||||
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]")
|
||||
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}")
|
||||
@ -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
|
||||
@ -153,3 +321,88 @@ class MemoryManager:
|
||||
)
|
||||
|
||||
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":
|
||||
@ -173,9 +237,14 @@ class Persona:
|
||||
|
||||
# Create memory summaries
|
||||
for user_id in self.relationships.relationships:
|
||||
summary = self.memory.summarize_memories(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 summary for interactions with {user_id}")
|
||||
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)}"
|
Loading…
x
Reference in New Issue
Block a user