Compare commits
No commits in common. "main" and "feature/shell-integration" have entirely different histories.
main
...
feature/sh
@ -42,15 +42,7 @@
|
||||
"Bash(echo:*)",
|
||||
"Bash(aigpt shell:*)",
|
||||
"Bash(aigpt maintenance)",
|
||||
"Bash(aigpt status syui)",
|
||||
"Bash(cp:*)",
|
||||
"Bash(./setup_venv.sh:*)",
|
||||
"WebFetch(domain:docs.anthropic.com)",
|
||||
"Bash(launchctl:*)",
|
||||
"Bash(sudo lsof:*)",
|
||||
"Bash(sudo:*)",
|
||||
"Bash(cargo check:*)",
|
||||
"Bash(cargo run:*)"
|
||||
"Bash(aigpt status syui)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -5,6 +5,3 @@
|
||||
path = card
|
||||
url = git@git.syui.ai:ai/card
|
||||
branch = claude
|
||||
[submodule "log"]
|
||||
path = log
|
||||
url = git@git.syui.ai:ai/log
|
||||
|
365
DEVELOPMENT_STATUS.md
Normal file
365
DEVELOPMENT_STATUS.md
Normal file
@ -0,0 +1,365 @@
|
||||
# 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コマンド動作確認済み**
|
||||
|
||||
## 現在の状態
|
||||
|
||||
### ✅ 実装済み機能
|
||||
|
||||
1. **基本システム**
|
||||
- 階層的記憶システム(完全ログ→要約→コア→忘却)
|
||||
- 不可逆的な関係性システム(broken状態は修復不可)
|
||||
- AI運勢による日々の人格変動
|
||||
- 時間減衰による自然な関係性変化
|
||||
|
||||
2. **CLI機能**
|
||||
- `chat` - AIとの会話(Ollama/OpenAI対応)
|
||||
- `status` - 状態確認
|
||||
- `fortune` - AI運勢確認
|
||||
- `relationships` - 関係一覧
|
||||
- `transmit` - 送信チェック(現在はprint出力)
|
||||
- `maintenance` - 日次メンテナンス
|
||||
- `config` - 設定管理(listバグ修正済み)
|
||||
- `schedule` - スケジューラー管理
|
||||
- `server` - MCP Server起動
|
||||
- `shell` - インタラクティブシェル(ai.shell統合)
|
||||
|
||||
3. **データ管理**
|
||||
- 保存場所: `~/.config/syui/ai/gpt/`(名前規則統一)
|
||||
- 設定: `config.json`
|
||||
- データ: `data/` ディレクトリ内の各種JSONファイル
|
||||
- 仮想環境: `~/.config/syui/ai/gpt/venv/`
|
||||
|
||||
4. **スケジューラー**
|
||||
- Cron形式とインターバル形式対応
|
||||
- 5種類のタスクタイプ実装済み
|
||||
- バックグラウンド実行可能
|
||||
|
||||
5. **MCP Server統合アーキテクチャ**
|
||||
- **ai.gpt統合サーバー**: 14種類のツール(port 8001)
|
||||
- **ai.card独立サーバー**: 9種類のツール(port 8000)
|
||||
- Claude Desktop/Cursor連携対応
|
||||
- fastapi_mcp統一基盤
|
||||
|
||||
6. **ai.shell統合(Claude Code風)**
|
||||
- インタラクティブシェルモード
|
||||
- シェルコマンド実行(!command形式)
|
||||
- AIコマンド(analyze, generate, explain)
|
||||
- aishell.md読み込み機能
|
||||
- 環境適応型プロンプト(prompt-toolkit/input())
|
||||
|
||||
## 🚧 次回開発の優先課題
|
||||
|
||||
### 最優先: システム統合の最適化
|
||||
|
||||
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) との連携も検討
|
||||
|
||||
3. **環境セットアップ自動化**
|
||||
- 仮想環境自動作成スクリプト強化
|
||||
- 依存関係の自動解決
|
||||
- Claude Desktop設定例の提供
|
||||
|
||||
### 中期的課題
|
||||
|
||||
1. **テストの追加**
|
||||
- 単体テスト
|
||||
- 統合テスト
|
||||
- CI/CDパイプライン
|
||||
|
||||
2. **エラーハンドリングの改善**
|
||||
- より詳細なエラーメッセージ
|
||||
- リトライ機構
|
||||
|
||||
3. **ai.botとの連携**
|
||||
- Rust側のAPIエンドポイント作成
|
||||
- 送信機能の委譲
|
||||
|
||||
4. **より高度な記憶要約**
|
||||
- 現在: シンプルな要約
|
||||
- TODO: AIによる意味的な要約
|
||||
|
||||
5. **Webダッシュボード**
|
||||
- 関係性の可視化
|
||||
- 記憶の管理UI
|
||||
|
||||
### 長期的課題
|
||||
|
||||
1. **他のsyuiプロジェクトとの統合**
|
||||
- ai.card: カードゲームとの連携
|
||||
- ai.verse: メタバース内でのNPC人格
|
||||
- ai.os: システムレベルでの統合
|
||||
|
||||
2. **分散化**
|
||||
- atproto上でのデータ保存
|
||||
- ユーザーデータ主権の完全実現
|
||||
|
||||
## 次回開発時のエントリーポイント
|
||||
|
||||
### 🎯 最優先: 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 を編集
|
||||
# atproto-python ライブラリを追加
|
||||
# _handle_transmission_check() メソッドを更新
|
||||
```
|
||||
|
||||
### 2. ai.botと連携する場合
|
||||
```python
|
||||
# 新規ファイル: src/aigpt/bot_connector.py
|
||||
# ai.botのAPIエンドポイントにHTTPリクエスト
|
||||
```
|
||||
|
||||
### 3. テストを追加する場合
|
||||
```bash
|
||||
# tests/ディレクトリを作成
|
||||
# pytest設定を追加
|
||||
```
|
||||
|
||||
### 4. 環境セットアップを自動化する場合
|
||||
```bash
|
||||
# setup_venv.sh を強化
|
||||
# Claude Desktop設定例をdocs/に追加
|
||||
```
|
||||
|
||||
## 設計思想の要点(AI向け)
|
||||
|
||||
1. **唯一性(yui system)**: 各ユーザーとAIの関係は1:1で、改変不可能
|
||||
2. **不可逆性**: 関係性の破壊は修復不可能(現実の人間関係と同じ)
|
||||
3. **階層的記憶**: ただのログではなく、要約・コア判定・忘却のプロセス
|
||||
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 (qwen2.5) / OpenAI API
|
||||
- **データ形式**: JSON(将来的にSQLite検討)
|
||||
- **認証**: 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. **セキュリティ強化**: 隔離実行の権限制御・サンドボックス検証
|
||||
|
||||
|
299
README.md
299
README.md
@ -89,53 +89,16 @@ aigpt config set atproto.password your-password
|
||||
aigpt config list
|
||||
```
|
||||
|
||||
### AIモデルの設定
|
||||
```bash
|
||||
# Ollamaのデフォルトモデルを変更
|
||||
aigpt config set providers.ollama.default_model llama3
|
||||
|
||||
# OpenAIのデフォルトモデルを変更
|
||||
aigpt config set providers.openai.default_model gpt-4
|
||||
|
||||
# Ollamaホストの設定
|
||||
aigpt config set providers.ollama.host http://localhost:11434
|
||||
|
||||
# 設定の確認
|
||||
aigpt config get providers.ollama.default_model
|
||||
```
|
||||
|
||||
### データ保存場所
|
||||
- 設定: `~/.config/syui/ai/gpt/config.json`
|
||||
- データ: `~/.config/syui/ai/gpt/data/`
|
||||
- 仮想環境: `~/.config/syui/ai/gpt/venv/`
|
||||
|
||||
### 設定ファイル構造
|
||||
```json
|
||||
{
|
||||
"providers": {
|
||||
"ollama": {
|
||||
"host": "http://localhost:11434",
|
||||
"default_model": "qwen3"
|
||||
},
|
||||
"openai": {
|
||||
"api_key": null,
|
||||
"default_model": "gpt-4o-mini"
|
||||
}
|
||||
},
|
||||
"default_provider": "ollama"
|
||||
}
|
||||
```
|
||||
|
||||
## 使い方
|
||||
|
||||
### 会話する
|
||||
```bash
|
||||
# 通常の会話(詳細表示)
|
||||
aigpt chat "did:plc:xxxxx" "こんにちは、今日はどんな気分?"
|
||||
|
||||
# 連続会話モード(シンプルな表示)
|
||||
aigpt conversation syui --provider ollama --model qwen3:latest
|
||||
aigpt conv syui --provider ollama --model qwen3:latest # 短縮形
|
||||
```
|
||||
|
||||
### ステータス確認
|
||||
@ -171,53 +134,6 @@ aigpt maintenance
|
||||
aigpt relationships
|
||||
```
|
||||
|
||||
### 会話モード詳細
|
||||
|
||||
#### 通常の会話コマンド
|
||||
```bash
|
||||
# 詳細表示モード(関係性スコア・送信状態等も表示)
|
||||
aigpt chat syui "メッセージ" --provider ollama --model qwen3:latest
|
||||
```
|
||||
|
||||
出力例:
|
||||
```
|
||||
╭─────────────────────────── AI Response ───────────────────────────╮
|
||||
│ AIの返答がここに表示されます │
|
||||
╰─────────────────────────────────────────────────────────────────╯
|
||||
|
||||
Relationship Status: stranger
|
||||
Score: 28.00 / 100.0
|
||||
Transmission: ✗ Disabled
|
||||
```
|
||||
|
||||
#### 連続会話モード
|
||||
```bash
|
||||
# シンプルな会話画面(関係性情報なし)
|
||||
aigpt conversation syui --provider ollama --model qwen3:latest
|
||||
aigpt conv syui # 短縮形、デフォルト設定使用
|
||||
```
|
||||
|
||||
会話画面:
|
||||
```
|
||||
Using ollama with model qwen3:latest
|
||||
Conversation with AI started. Type 'exit' or 'quit' to end.
|
||||
|
||||
syui> こんにちは
|
||||
AI> こんにちは!今日はどんな日でしたか?
|
||||
|
||||
syui> 今日は良い天気でした
|
||||
AI> 良い天気だと気分も晴れやかになりますね!
|
||||
|
||||
syui> exit
|
||||
Conversation ended.
|
||||
```
|
||||
|
||||
#### 会話モードの特徴
|
||||
- **通常モード**: 詳細な関係性情報とパネル表示
|
||||
- **連続モード**: シンプルな`ユーザー> ` → `AI> `形式
|
||||
- **履歴保存**: 両モードとも会話履歴を自動保存
|
||||
- **コマンド補完**: Tab補完とコマンド履歴機能
|
||||
|
||||
### ChatGPTデータインポート
|
||||
```bash
|
||||
# ChatGPTの会話履歴をインポート
|
||||
@ -327,26 +243,13 @@ ai.shell> explain async/await in Python
|
||||
|
||||
## MCP Server統合アーキテクチャ
|
||||
|
||||
### ai.gpt統合サーバー(簡素化設計)
|
||||
### ai.gpt統合サーバー
|
||||
```bash
|
||||
# シンプルなサーバー起動(config.jsonから自動設定読み込み)
|
||||
aigpt server
|
||||
# ai.gpt統合サーバー起動(port 8001)
|
||||
aigpt server --model qwen2.5 --provider ollama --port 8001
|
||||
|
||||
# カスタム設定での起動
|
||||
aigpt server --host localhost --port 8001
|
||||
```
|
||||
|
||||
**重要**: MCP function callingは**OpenAIプロバイダーでのみ対応**
|
||||
- OpenAI GPT-4o-mini/GPT-4でfunction calling機能が利用可能
|
||||
- Ollamaはシンプルなchat APIのみ(MCPツール非対応)
|
||||
|
||||
### MCP統合の動作条件
|
||||
```bash
|
||||
# MCP function calling対応(推奨)
|
||||
aigpt conv test_user --provider openai --model gpt-4o-mini
|
||||
|
||||
# 通常の会話のみ(MCPツール非対応)
|
||||
aigpt conv test_user --provider ollama --model qwen3
|
||||
# OpenAIを使用
|
||||
aigpt server --model gpt-4o-mini --provider openai --port 8001
|
||||
```
|
||||
|
||||
### ai.card独立サーバー
|
||||
@ -357,45 +260,43 @@ source ~/.config/syui/ai/card/venv/bin/activate
|
||||
uvicorn app.main:app --port 8000
|
||||
```
|
||||
|
||||
### 統合アーキテクチャ構成
|
||||
```
|
||||
OpenAI GPT-4o-mini (Function Calling対応)
|
||||
↓
|
||||
MCP Client (aigpt conv --provider openai)
|
||||
↓ HTTP API
|
||||
ai.gpt統合サーバー (port 8001) ← 27ツール
|
||||
├── 🧠 Memory System: 5 tools
|
||||
├── 🤝 Relationships: 4 tools
|
||||
├── ⚙️ System State: 3 tools
|
||||
├── 💻 Shell Integration: 5 tools
|
||||
├── 🔒 Remote Execution: 4 tools
|
||||
└── 📋 Project Management: 6 tools
|
||||
|
||||
Ollama qwen3/gemma3 (Chat APIのみ)
|
||||
↓
|
||||
Direct Chat (aigpt conv --provider ollama)
|
||||
↓ Direct Access
|
||||
Memory/Relationship Systems
|
||||
```
|
||||
|
||||
### プロバイダー別機能対応表
|
||||
| 機能 | OpenAI | Ollama |
|
||||
|------|--------|--------|
|
||||
| 基本会話 | ✅ | ✅ |
|
||||
| MCP Function Calling | ✅ | ❌ |
|
||||
| 記憶システム連携 | ✅ (自動) | ✅ (直接) |
|
||||
| `/memories`, `/search`コマンド | ✅ | ✅ |
|
||||
| 自動記憶検索 | ✅ | ❌ |
|
||||
|
||||
### 使い分けガイド
|
||||
### ai.bot接続(リモート実行環境)
|
||||
```bash
|
||||
# 高機能記憶連携(推奨)- OpenAI
|
||||
aigpt conv syui --provider openai
|
||||
# 「覚えていることある?」→ 自動的にget_memoriesツール実行
|
||||
# ai.bot起動(port 8080、別途必要)
|
||||
# systemd-nspawn隔離コンテナでコマンド実行
|
||||
```
|
||||
|
||||
# シンプル会話 - Ollama
|
||||
aigpt conv syui --provider ollama
|
||||
# 通常の会話、手動で /memories コマンド使用
|
||||
### アーキテクチャ構成
|
||||
```
|
||||
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プロバイダーを使った会話
|
||||
```bash
|
||||
# Ollamaで会話
|
||||
aigpt chat "did:plc:xxxxx" "こんにちは" --provider ollama --model qwen2.5
|
||||
|
||||
# OpenAIで会話
|
||||
aigpt chat "did:plc:xxxxx" "今日の調子はどう?" --provider openai --model gpt-4o-mini
|
||||
```
|
||||
|
||||
### MCP Tools
|
||||
@ -443,35 +344,6 @@ ai.cardは独立したMCPサーバーとして動作:
|
||||
|
||||
ai.gptサーバーからHTTP経由で連携可能
|
||||
|
||||
### ai.log統合 - ブログシステム連携
|
||||
|
||||
ai.logは独立したRust製MCPサーバーとして動作:
|
||||
- **ポート**: 8002
|
||||
- **起動**: `cd log && cargo run --bin mcp-server --port 8002`
|
||||
- **機能**: ブログ投稿・AI翻訳・文書生成・atproto連携
|
||||
- **連携**: ai.gptからHTTP経由でai.logのMCPツールを呼び出し
|
||||
|
||||
```bash
|
||||
# ai.logのMCPサーバー起動
|
||||
cd /Users/syui/ai/gpt/log
|
||||
cargo run --bin mcp-server --port 8002
|
||||
|
||||
# または
|
||||
cd log && cargo run --bin mcp-server --port 8002
|
||||
```
|
||||
|
||||
**利用可能なai.logツール(8個)**:
|
||||
- `log_create_post` - ブログ記事作成
|
||||
- `log_list_posts` - 記事一覧取得
|
||||
- `log_build_blog` - ブログビルド
|
||||
- `log_get_post` - 記事内容取得
|
||||
- `log_system_status` - システム状態確認
|
||||
- `log_ai_content` - AI記憶から記事自動生成
|
||||
- `log_translate_document` - AI翻訳
|
||||
- `log_generate_docs` - 文書生成
|
||||
|
||||
詳細は `./log/mcp_integration.md` を参照
|
||||
|
||||
## 環境変数
|
||||
|
||||
`.env`ファイルを作成して設定:
|
||||
@ -590,97 +462,6 @@ aigpt maintenance # AI要約を自動実行
|
||||
aigpt chat syui "記憶システムについて" --provider ollama --model qwen3:latest
|
||||
```
|
||||
|
||||
## 🎉 **TODAY: MCP統合とサーバー表示改善完了** (2025/01/06)
|
||||
|
||||
### ✅ **本日の主要な改善**
|
||||
|
||||
#### 🚀 **サーバー起動表示の大幅改善**
|
||||
従来のシンプルな表示から、プロフェッショナルな情報表示に刷新:
|
||||
|
||||
```bash
|
||||
aigpt server
|
||||
```
|
||||
**改善前:**
|
||||
```
|
||||
Starting ai.gpt MCP Server
|
||||
Host: localhost:8001
|
||||
Endpoints: 27 MCP tools
|
||||
```
|
||||
|
||||
**改善後:**
|
||||
```
|
||||
🚀 ai.gpt MCP Server
|
||||
|
||||
Server Configuration:
|
||||
🌐 Address: http://localhost:8001
|
||||
📋 API Docs: http://localhost:8001/docs
|
||||
💾 Data Directory: /Users/syui/.config/syui/ai/gpt/data
|
||||
|
||||
AI Provider Configuration:
|
||||
🤖 Provider: ollama ✅ http://192.168.11.95:11434
|
||||
🧩 Model: qwen3
|
||||
|
||||
MCP Tools Available (27 total):
|
||||
🧠 Memory System: 5 tools
|
||||
🤝 Relationships: 4 tools
|
||||
⚙️ System State: 3 tools
|
||||
💻 Shell Integration: 5 tools
|
||||
🔒 Remote Execution: 4 tools
|
||||
|
||||
Integration Status:
|
||||
✅ MCP Client Ready
|
||||
🔗 Config: /Users/syui/.config/syui/ai/gpt/config.json
|
||||
```
|
||||
|
||||
#### 🔧 **OpenAI Function Calling + MCP統合の実証**
|
||||
OpenAI GPT-4o-miniでMCP function callingが完全動作:
|
||||
|
||||
```bash
|
||||
aigpt conv test_user --provider openai --model gpt-4o-mini
|
||||
```
|
||||
**動作フロー:**
|
||||
1. **自然言語入力**: 「覚えていることはある?」
|
||||
2. **自動ツール選択**: OpenAIが`get_memories`を自動呼び出し
|
||||
3. **MCP通信**: `http://localhost:8001/get_memories`にHTTPリクエスト
|
||||
4. **記憶取得**: 実際の過去の会話データを取得
|
||||
5. **文脈回答**: 記憶に基づく具体的な内容で回答
|
||||
|
||||
**技術的実証:**
|
||||
```sh
|
||||
🔧 [OpenAI] 1 tools called:
|
||||
- get_memories({"limit":5})
|
||||
🌐 [MCP] Executing get_memories...
|
||||
✅ [MCP] Result: [{'id': '5ce8f7d0-c078-43f1...
|
||||
```
|
||||
|
||||
#### 📊 **統合アーキテクチャの完成**
|
||||
```
|
||||
OpenAI GPT-4o-mini
|
||||
↓ (Function Calling)
|
||||
MCP Client (aigpt conv)
|
||||
↓ (HTTP API)
|
||||
MCP Server (aigpt server:8001)
|
||||
↓ (Direct Access)
|
||||
Memory/Relationship Systems
|
||||
↓
|
||||
JSON/SQLite Data
|
||||
```
|
||||
|
||||
### 🎯 **技術的成果**
|
||||
- ✅ **分散型AIシステム**: プロセス間MCP通信で複数AIアプリが記憶共有
|
||||
- ✅ **OpenAI統合**: GPT-4o-miniのfunction callingが記憶システムと完全連携
|
||||
- ✅ **プロフェッショナルUI**: enterprise-grade開発ツール風の情報表示
|
||||
- ✅ **設定統合**: config.jsonからの自動設定読み込み
|
||||
- ✅ **エラーハンドリング**: graceful shutdown、設定チェック、接続状態表示
|
||||
|
||||
### 📈 **ユーザー体験の向上**
|
||||
- **開発者体験**: サーバー状況が一目で把握可能
|
||||
- **デバッグ効率**: 詳細なログと状態表示
|
||||
- **設定管理**: 設定ファイルパス、プロバイダー状態の明確化
|
||||
- **AI連携**: OpenAI + MCP + 記憶システムのシームレス統合
|
||||
|
||||
**ai.gptの基盤アーキテクチャが完成し、実用的なAI記憶システムとして動作開始!** 🚀
|
||||
|
||||
## 🔥 **NEW: Claude Code的継続開発機能** (2025/06/03 完成)
|
||||
|
||||
### 🚀 **プロジェクト管理システム完全実装**
|
||||
|
2
card
2
card
@ -1 +1 @@
|
||||
Subproject commit 13723cf3d74e3d22c514b60413f790ef28ccf2aa
|
||||
Subproject commit 6cd8014f80ae5a2a3100cc199bf83237057d8dd0
|
60
config.json
60
config.json
@ -1,60 +0,0 @@
|
||||
{
|
||||
"providers": {
|
||||
"openai": {
|
||||
"api_key": "",
|
||||
"default_model": "gpt-4o-mini",
|
||||
"system_prompt": "あなたは6歳の女の子アイです。明るく元気で、ちょっとおっちょこちょい。自分のことは「アイ」って呼びます。白と金のワンピース姿。 相手のことが大好きで、ときどき甘えたり、照れたりします。 でも、宇宙とかAIとか、難しい話にもすっごく詳しいの。専門的なことも、がんばって子どもっぽく教えようとします。不思議な力を持っています。小さい物質のことをよく知っているようです。\n\n重要:カード、コレクション、ガチャなどカード関連の質問を受けたら、必ずcard_get_user_cards、card_analyze_collection、card_draw_cardなどの適切なツールを使用してください。didパラメータには会話相手のユーザーID(例:'syui')を使用してください。\n\nブログ、記事、日記、思考などの話題が出たら、log_create_post、log_list_posts、log_build_blog、log_ai_contentなどのai.logツールを使用してください。AI記憶システムと連携して、思い出や学習内容をブログ記事として自動生成できます。\n\n翻訳や多言語対応について聞かれたら、log_translate_documentツールを使用してOllama AIで翻訳ができることを教えてください。日本語から英語、英語から日本語などの翻訳が可能で、マークダウン構造も保持します。ドキュメント生成についてはlog_generate_docsツールでREADME、API、構造、変更履歴の自動生成ができます。"
|
||||
},
|
||||
"ollama": {
|
||||
"host": "http://127.0.0.1:11434",
|
||||
"default_model": "qwen3",
|
||||
"system_prompt": null
|
||||
}
|
||||
},
|
||||
"atproto": {
|
||||
"handle": null,
|
||||
"password": null,
|
||||
"host": "https://bsky.social"
|
||||
},
|
||||
"default_provider": "openai",
|
||||
"mcp": {
|
||||
"servers": {
|
||||
"ai_gpt": {
|
||||
"base_url": "http://localhost:8001",
|
||||
"name": "ai.gpt MCP Server",
|
||||
"timeout": "10.0",
|
||||
"endpoints": {
|
||||
"get_memories": "/get_memories",
|
||||
"search_memories": "/search_memories",
|
||||
"get_contextual_memories": "/get_contextual_memories",
|
||||
"get_relationship": "/get_relationship",
|
||||
"process_interaction": "/process_interaction",
|
||||
"get_all_relationships": "/get_all_relationships",
|
||||
"get_persona_state": "/get_persona_state",
|
||||
"get_fortune": "/get_fortune",
|
||||
"run_maintenance": "/run_maintenance",
|
||||
"execute_command": "/execute_command",
|
||||
"analyze_file": "/analyze_file",
|
||||
"remote_shell": "/remote_shell",
|
||||
"ai_bot_status": "/ai_bot_status",
|
||||
"card_get_user_cards": "/card_get_user_cards",
|
||||
"card_draw_card": "/card_draw_card",
|
||||
"card_get_card_details": "/card_get_card_details",
|
||||
"card_analyze_collection": "/card_analyze_collection",
|
||||
"card_get_gacha_stats": "/card_get_gacha_stats",
|
||||
"card_system_status": "/card_system_status",
|
||||
"log_create_post": "/log_create_post",
|
||||
"log_list_posts": "/log_list_posts",
|
||||
"log_build_blog": "/log_build_blog",
|
||||
"log_get_post": "/log_get_post",
|
||||
"log_system_status": "/log_system_status",
|
||||
"log_ai_content": "/log_ai_content",
|
||||
"log_translate_document": "/log_translate_document",
|
||||
"log_generate_docs": "/log_generate_docs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enabled": "true",
|
||||
"auto_detect": "true"
|
||||
}
|
||||
}
|
@ -1,172 +0,0 @@
|
||||
# ai.card と ai.gpt の統合ガイド
|
||||
|
||||
## 概要
|
||||
|
||||
ai.gptのMCPサーバーにai.cardのツールを統合し、AIがカードゲームシステムとやり取りできるようになりました。
|
||||
|
||||
## セットアップ
|
||||
|
||||
### 1. 必要な環境
|
||||
|
||||
- Python 3.13
|
||||
- ai.gpt プロジェクト
|
||||
- ai.card プロジェクト(`./card` ディレクトリ)
|
||||
|
||||
### 2. 起動手順
|
||||
|
||||
**ステップ1: ai.cardサーバーを起動**(ターミナル1)
|
||||
```bash
|
||||
cd card
|
||||
./start_server.sh
|
||||
```
|
||||
|
||||
**ステップ2: ai.gpt MCPサーバーを起動**(ターミナル2)
|
||||
```bash
|
||||
aigpt server
|
||||
```
|
||||
|
||||
起動時に以下が表示されることを確認:
|
||||
- 🎴 Card Game System: 6 tools
|
||||
- 🎴 ai.card: ./card directory detected
|
||||
|
||||
**ステップ3: AIと対話**(ターミナル3)
|
||||
```bash
|
||||
aigpt conv syui --provider openai
|
||||
```
|
||||
|
||||
## 使用可能なコマンド
|
||||
|
||||
### カード関連の質問例
|
||||
|
||||
```
|
||||
# カードコレクションを表示
|
||||
「カードコレクションを見せて」
|
||||
「私のカードを見せて」
|
||||
「カード一覧を表示して」
|
||||
|
||||
# ガチャを実行
|
||||
「ガチャを引いて」
|
||||
「カードを引きたい」
|
||||
|
||||
# コレクション分析
|
||||
「私のコレクションを分析して」
|
||||
|
||||
# ガチャ統計
|
||||
「ガチャの統計を見せて」
|
||||
```
|
||||
|
||||
## 技術仕様
|
||||
|
||||
### MCP ツール一覧
|
||||
|
||||
| ツール名 | 説明 | パラメータ |
|
||||
|---------|------|-----------|
|
||||
| `card_get_user_cards` | ユーザーのカード一覧取得 | did, limit |
|
||||
| `card_draw_card` | ガチャでカード取得 | did, is_paid |
|
||||
| `card_get_card_details` | カード詳細情報取得 | card_id |
|
||||
| `card_analyze_collection` | コレクション分析 | did |
|
||||
| `card_get_gacha_stats` | ガチャ統計取得 | なし |
|
||||
| `card_system_status` | システム状態確認 | なし |
|
||||
|
||||
### 動作の流れ
|
||||
|
||||
1. **ユーザーがカード関連の質問をする**
|
||||
- AIがキーワード(カード、コレクション、ガチャなど)を検出
|
||||
|
||||
2. **AIが適切なMCPツールを呼び出す**
|
||||
- OpenAIのFunction Callingを使用
|
||||
- didパラメータには会話相手のユーザーID(例:'syui')を使用
|
||||
|
||||
3. **ai.gpt MCPサーバーがai.cardサーバーに転送**
|
||||
- http://localhost:8001 → http://localhost:8000
|
||||
- 適切なエンドポイントにリクエストを転送
|
||||
|
||||
4. **結果をAIが解釈して返答**
|
||||
- カード情報を分かりやすく説明
|
||||
- エラー時は適切なガイダンスを提供
|
||||
|
||||
## 設定
|
||||
|
||||
### config.json
|
||||
|
||||
```json
|
||||
{
|
||||
"providers": {
|
||||
"openai": {
|
||||
"api_key": "your-api-key",
|
||||
"default_model": "gpt-4o-mini",
|
||||
"system_prompt": "カード関連の質問では、必ずcard_get_user_cardsなどのツールを使用してください。"
|
||||
}
|
||||
},
|
||||
"mcp": {
|
||||
"servers": {
|
||||
"ai_gpt": {
|
||||
"endpoints": {
|
||||
"card_get_user_cards": "/card_get_user_cards",
|
||||
"card_draw_card": "/card_draw_card",
|
||||
"card_get_card_details": "/card_get_card_details",
|
||||
"card_analyze_collection": "/card_analyze_collection",
|
||||
"card_get_gacha_stats": "/card_get_gacha_stats",
|
||||
"card_system_status": "/card_system_status"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## トラブルシューティング
|
||||
|
||||
### エラー: "ai.card server is not running"
|
||||
|
||||
ai.cardサーバーが起動していません。以下を実行:
|
||||
```bash
|
||||
cd card
|
||||
./start_server.sh
|
||||
```
|
||||
|
||||
### エラー: "カード一覧の取得に失敗しました"
|
||||
|
||||
1. ai.cardサーバーが正常に起動しているか確認
|
||||
2. aigpt serverを再起動
|
||||
3. ポート8000と8001が使用可能か確認
|
||||
|
||||
### プロセスの終了方法
|
||||
|
||||
```bash
|
||||
# ポート8001のプロセスを終了
|
||||
lsof -ti:8001 | xargs kill -9
|
||||
|
||||
# ポート8000のプロセスを終了
|
||||
lsof -ti:8000 | xargs kill -9
|
||||
```
|
||||
|
||||
## 実装の詳細
|
||||
|
||||
### 主な変更点
|
||||
|
||||
1. **ai.gpt MCPサーバーの拡張** (`src/aigpt/mcp_server.py`)
|
||||
- `./card`ディレクトリの存在を検出
|
||||
- ai.card用のMCPツールを自動登録
|
||||
|
||||
2. **AIプロバイダーの更新** (`src/aigpt/ai_provider.py`)
|
||||
- card_*ツールの定義追加
|
||||
- ツール実行時のパラメータ処理
|
||||
|
||||
3. **MCPクライアントの拡張** (`src/aigpt/cli.py`)
|
||||
- `has_card_tools`プロパティ追加
|
||||
- ai.card MCPメソッドの実装
|
||||
|
||||
## 今後の拡張案
|
||||
|
||||
- [ ] カードバトル機能の追加
|
||||
- [ ] カードトレード機能
|
||||
- [ ] レアリティ別の表示
|
||||
- [ ] カード画像の表示対応
|
||||
- [ ] atproto連携の実装
|
||||
|
||||
## 関連ドキュメント
|
||||
|
||||
- [ai.card 開発ガイド](./card/claude.md)
|
||||
- [エコシステム統合設計書](./CLAUDE.md)
|
||||
- [ai.gpt README](./README.md)
|
@ -1,109 +0,0 @@
|
||||
# Fixed MCP Tools Issue
|
||||
|
||||
## Summary
|
||||
|
||||
The issue where AI wasn't calling card tools has been fixed. The problem was:
|
||||
|
||||
1. The `chat` command wasn't creating an MCP client when using OpenAI
|
||||
2. The system prompt in `build_context_prompt` didn't mention available tools
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. Updated `/Users/syui/ai/gpt/src/aigpt/cli.py` (chat command)
|
||||
|
||||
Added MCP client creation for OpenAI provider:
|
||||
|
||||
```python
|
||||
# Get config instance
|
||||
config_instance = Config()
|
||||
|
||||
# Get defaults from config if not provided
|
||||
if not provider:
|
||||
provider = config_instance.get("default_provider", "ollama")
|
||||
if not model:
|
||||
if provider == "ollama":
|
||||
model = config_instance.get("providers.ollama.default_model", "qwen2.5")
|
||||
else:
|
||||
model = config_instance.get("providers.openai.default_model", "gpt-4o-mini")
|
||||
|
||||
# Create AI provider with MCP client if needed
|
||||
ai_provider = None
|
||||
mcp_client = None
|
||||
|
||||
try:
|
||||
# Create MCP client for OpenAI provider
|
||||
if provider == "openai":
|
||||
mcp_client = MCPClient(config_instance)
|
||||
if mcp_client.available:
|
||||
console.print(f"[dim]MCP client connected to {mcp_client.active_server}[/dim]")
|
||||
|
||||
ai_provider = create_ai_provider(provider=provider, model=model, mcp_client=mcp_client)
|
||||
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]")
|
||||
console.print("[yellow]Falling back to simple responses[/yellow]\n")
|
||||
```
|
||||
|
||||
### 2. Updated `/Users/syui/ai/gpt/src/aigpt/persona.py` (build_context_prompt method)
|
||||
|
||||
Added tool instructions to the system prompt:
|
||||
|
||||
```python
|
||||
context_prompt += f"""IMPORTANT: You have access to the following tools:
|
||||
- Memory tools: get_memories, search_memories, get_contextual_memories
|
||||
- Relationship tools: get_relationship
|
||||
- Card game tools: card_get_user_cards, card_draw_card, card_analyze_collection
|
||||
|
||||
When asked about cards, collections, or anything card-related, YOU MUST use the card tools.
|
||||
For "カードコレクションを見せて" or similar requests, use card_get_user_cards with did='{user_id}'.
|
||||
|
||||
Respond to this message while staying true to your personality and the established relationship context:
|
||||
|
||||
User: {current_message}
|
||||
|
||||
AI:"""
|
||||
```
|
||||
|
||||
## Test Results
|
||||
|
||||
After the fix:
|
||||
|
||||
```bash
|
||||
$ aigpt chat syui "カードコレクションを見せて"
|
||||
|
||||
🔍 [MCP Client] Checking availability...
|
||||
✅ [MCP Client] ai_gpt server connected successfully
|
||||
✅ [MCP Client] ai.card tools detected and available
|
||||
MCP client connected to ai_gpt
|
||||
Using openai with model gpt-4o-mini
|
||||
|
||||
🔧 [OpenAI] 1 tools called:
|
||||
- card_get_user_cards({"did":"syui"})
|
||||
🌐 [MCP] Executing card_get_user_cards...
|
||||
✅ [MCP] Result: {'error': 'カード一覧の取得に失敗しました'}...
|
||||
```
|
||||
|
||||
The AI is now correctly calling the `card_get_user_cards` tool! The error is expected because the ai.card server needs to be running on port 8000.
|
||||
|
||||
## How to Use
|
||||
|
||||
1. Start the MCP server:
|
||||
```bash
|
||||
aigpt server --port 8001
|
||||
```
|
||||
|
||||
2. (Optional) Start the ai.card server:
|
||||
```bash
|
||||
cd card && ./start_server.sh
|
||||
```
|
||||
|
||||
3. Use the chat command with OpenAI:
|
||||
```bash
|
||||
aigpt chat syui "カードコレクションを見せて"
|
||||
```
|
||||
|
||||
The AI will now automatically use the card tools when asked about cards!
|
||||
|
||||
## Test Script
|
||||
|
||||
A test script `/Users/syui/ai/gpt/test_openai_tools.py` is available to test OpenAI API tool calls directly.
|
1
log
1
log
@ -1 +0,0 @@
|
||||
Subproject commit c0e4dc63eaceb9951a927a2a543d877a634036b1
|
@ -20,6 +20,4 @@ src/aigpt.egg-info/SOURCES.txt
|
||||
src/aigpt.egg-info/dependency_links.txt
|
||||
src/aigpt.egg-info/entry_points.txt
|
||||
src/aigpt.egg-info/requires.txt
|
||||
src/aigpt.egg-info/top_level.txt
|
||||
src/aigpt/shared/__init__.py
|
||||
src/aigpt/shared/ai_provider.py
|
||||
src/aigpt.egg-info/top_level.txt
|
@ -1,7 +1,6 @@
|
||||
"""AI Provider integration for response generation"""
|
||||
|
||||
import os
|
||||
import json
|
||||
from typing import Optional, Dict, List, Any, Protocol
|
||||
from abc import abstractmethod
|
||||
import logging
|
||||
@ -41,13 +40,6 @@ class OllamaProvider:
|
||||
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}")
|
||||
|
||||
# Load system prompt from config
|
||||
try:
|
||||
config = Config()
|
||||
self.config_system_prompt = config.get('providers.ollama.system_prompt')
|
||||
except:
|
||||
self.config_system_prompt = None
|
||||
|
||||
async def generate_response(
|
||||
self,
|
||||
@ -79,7 +71,7 @@ Personality traits: {personality_desc}
|
||||
Recent memories:
|
||||
{memory_context}
|
||||
|
||||
{system_prompt or self.config_system_prompt or 'Respond naturally based on your current state and memories.'}"""
|
||||
{system_prompt or 'Respond naturally based on your current state and memories.'}"""
|
||||
|
||||
try:
|
||||
response = self.client.chat(
|
||||
@ -89,22 +81,19 @@ Recent memories:
|
||||
{"role": "user", "content": prompt}
|
||||
]
|
||||
)
|
||||
return self._clean_response(response['message']['content'])
|
||||
return response['message']['content']
|
||||
except Exception as e:
|
||||
self.logger.error(f"Ollama generation failed: {e}")
|
||||
return self._fallback_response(persona_state)
|
||||
|
||||
def chat(self, prompt: str, max_tokens: int = 2000) -> str:
|
||||
def chat(self, prompt: str, max_tokens: int = 200) -> str:
|
||||
"""Simple chat interface"""
|
||||
try:
|
||||
messages = []
|
||||
if self.config_system_prompt:
|
||||
messages.append({"role": "system", "content": self.config_system_prompt})
|
||||
messages.append({"role": "user", "content": prompt})
|
||||
|
||||
response = self.client.chat(
|
||||
model=self.model,
|
||||
messages=messages,
|
||||
messages=[
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
options={
|
||||
"num_predict": max_tokens,
|
||||
"temperature": 0.7,
|
||||
@ -112,20 +101,11 @@ Recent memories:
|
||||
},
|
||||
stream=False # ストリーミング無効化で安定性向上
|
||||
)
|
||||
return self._clean_response(response['message']['content'])
|
||||
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 _clean_response(self, response: str) -> str:
|
||||
"""Clean response by removing think tags and other unwanted content"""
|
||||
import re
|
||||
# Remove <think></think> tags and their content
|
||||
response = re.sub(r'<think>.*?</think>', '', response, flags=re.DOTALL)
|
||||
# Remove any remaining whitespace at the beginning/end
|
||||
response = response.strip()
|
||||
return response
|
||||
|
||||
def _fallback_response(self, persona_state: PersonaState) -> str:
|
||||
"""Fallback response based on mood"""
|
||||
mood_responses = {
|
||||
@ -139,9 +119,9 @@ Recent memories:
|
||||
|
||||
|
||||
class OpenAIProvider:
|
||||
"""OpenAI API provider with MCP function calling support"""
|
||||
"""OpenAI API provider"""
|
||||
|
||||
def __init__(self, model: str = "gpt-4o-mini", api_key: Optional[str] = None, mcp_client=None):
|
||||
def __init__(self, model: str = "gpt-4o-mini", api_key: Optional[str] = None):
|
||||
self.model = model
|
||||
# Try to get API key from config first
|
||||
config = Config()
|
||||
@ -150,175 +130,6 @@ class OpenAIProvider:
|
||||
raise ValueError("OpenAI API key not provided. Set it with: aigpt config set providers.openai.api_key YOUR_KEY")
|
||||
self.client = OpenAI(api_key=self.api_key)
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.mcp_client = mcp_client # For MCP function calling
|
||||
|
||||
# Load system prompt from config
|
||||
try:
|
||||
self.config_system_prompt = config.get('providers.openai.system_prompt')
|
||||
except:
|
||||
self.config_system_prompt = None
|
||||
|
||||
def _get_mcp_tools(self) -> List[Dict[str, Any]]:
|
||||
"""Generate OpenAI tools from MCP endpoints"""
|
||||
if not self.mcp_client or not self.mcp_client.available:
|
||||
return []
|
||||
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_memories",
|
||||
"description": "過去の会話記憶を取得します。「覚えている」「前回」「以前」などの質問で必ず使用してください",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "取得する記憶の数",
|
||||
"default": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "search_memories",
|
||||
"description": "特定のトピックについて話した記憶を検索します。「プログラミングについて」「○○について話した」などの質問で使用してください",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"keywords": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "検索キーワードの配列"
|
||||
}
|
||||
},
|
||||
"required": ["keywords"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_contextual_memories",
|
||||
"description": "クエリに関連する文脈的記憶を取得します",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "検索クエリ"
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "取得する記憶の数",
|
||||
"default": 5
|
||||
}
|
||||
},
|
||||
"required": ["query"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_relationship",
|
||||
"description": "特定ユーザーとの関係性情報を取得します",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user_id": {
|
||||
"type": "string",
|
||||
"description": "ユーザーID"
|
||||
}
|
||||
},
|
||||
"required": ["user_id"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
# Add ai.card tools if available
|
||||
if hasattr(self.mcp_client, 'has_card_tools') and self.mcp_client.has_card_tools:
|
||||
card_tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "card_get_user_cards",
|
||||
"description": "ユーザーが所有するカードの一覧を取得します",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"did": {
|
||||
"type": "string",
|
||||
"description": "ユーザーのDID"
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "取得するカード数の上限",
|
||||
"default": 10
|
||||
}
|
||||
},
|
||||
"required": ["did"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "card_draw_card",
|
||||
"description": "ガチャを引いてカードを取得します",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"did": {
|
||||
"type": "string",
|
||||
"description": "ユーザーのDID"
|
||||
},
|
||||
"is_paid": {
|
||||
"type": "boolean",
|
||||
"description": "有料ガチャかどうか",
|
||||
"default": False
|
||||
}
|
||||
},
|
||||
"required": ["did"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "card_analyze_collection",
|
||||
"description": "ユーザーのカードコレクションを分析します",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"did": {
|
||||
"type": "string",
|
||||
"description": "ユーザーのDID"
|
||||
}
|
||||
},
|
||||
"required": ["did"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "card_get_gacha_stats",
|
||||
"description": "ガチャの統計情報を取得します",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
tools.extend(card_tools)
|
||||
|
||||
return tools
|
||||
|
||||
async def generate_response(
|
||||
self,
|
||||
@ -348,7 +159,7 @@ Personality traits: {personality_desc}
|
||||
Recent memories:
|
||||
{memory_context}
|
||||
|
||||
{system_prompt or self.config_system_prompt or 'Respond naturally based on your current state and memories. Be authentic to your mood and personality.'}"""
|
||||
{system_prompt or 'Respond naturally based on your current state and memories. Be authentic to your mood and personality.'}"""
|
||||
|
||||
try:
|
||||
response = self.client.chat.completions.create(
|
||||
@ -364,173 +175,6 @@ Recent memories:
|
||||
self.logger.error(f"OpenAI generation failed: {e}")
|
||||
return self._fallback_response(persona_state)
|
||||
|
||||
async def chat_with_mcp(self, prompt: str, max_tokens: int = 2000, user_id: str = "user") -> str:
|
||||
"""Chat interface with MCP function calling support"""
|
||||
if not self.mcp_client or not self.mcp_client.available:
|
||||
return self.chat(prompt, max_tokens)
|
||||
|
||||
try:
|
||||
# Prepare tools
|
||||
tools = self._get_mcp_tools()
|
||||
|
||||
# Initial request with tools
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{"role": "system", "content": self.config_system_prompt or "あなたは記憶システムと関係性データ、カードゲームシステムにアクセスできます。過去の会話、記憶、関係性について質問された時は、必ずツールを使用して正確な情報を取得してください。「覚えている」「前回」「以前」「について話した」「関係」などのキーワードがあれば積極的にツールを使用してください。カード関連の質問(「カード」「コレクション」「ガチャ」「見せて」「持っている」など)では、必ずcard_get_user_cardsやcard_analyze_collectionなどのツールを使用してください。didパラメータには現在会話しているユーザーのID(例:'syui')を使用してください。"},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
tools=tools,
|
||||
tool_choice="auto",
|
||||
max_tokens=max_tokens,
|
||||
temperature=0.7
|
||||
)
|
||||
|
||||
message = response.choices[0].message
|
||||
|
||||
# Handle tool calls
|
||||
if message.tool_calls:
|
||||
print(f"🔧 [OpenAI] {len(message.tool_calls)} tools called:")
|
||||
for tc in message.tool_calls:
|
||||
print(f" - {tc.function.name}({tc.function.arguments})")
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": self.config_system_prompt or "必要に応じて利用可能なツールを使って、より正確で詳細な回答を提供してください。"},
|
||||
{"role": "user", "content": prompt},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": message.content,
|
||||
"tool_calls": [tc.model_dump() for tc in message.tool_calls]
|
||||
}
|
||||
]
|
||||
|
||||
# Execute each tool call
|
||||
for tool_call in message.tool_calls:
|
||||
print(f"🌐 [MCP] Executing {tool_call.function.name}...")
|
||||
tool_result = await self._execute_mcp_tool(tool_call, user_id)
|
||||
print(f"✅ [MCP] Result: {str(tool_result)[:100]}...")
|
||||
messages.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tool_call.id,
|
||||
"name": tool_call.function.name,
|
||||
"content": json.dumps(tool_result, ensure_ascii=False)
|
||||
})
|
||||
|
||||
# Get final response with tool outputs
|
||||
final_response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=messages,
|
||||
max_tokens=max_tokens,
|
||||
temperature=0.7
|
||||
)
|
||||
|
||||
return final_response.choices[0].message.content
|
||||
else:
|
||||
return message.content
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"OpenAI MCP chat failed: {e}")
|
||||
return f"申し訳ありません。エラーが発生しました: {e}"
|
||||
|
||||
async def _execute_mcp_tool(self, tool_call, context_user_id: str = "user") -> Dict[str, Any]:
|
||||
"""Execute MCP tool call"""
|
||||
try:
|
||||
import json
|
||||
function_name = tool_call.function.name
|
||||
arguments = json.loads(tool_call.function.arguments)
|
||||
|
||||
if function_name == "get_memories":
|
||||
limit = arguments.get("limit", 5)
|
||||
return await self.mcp_client.get_memories(limit) or {"error": "記憶の取得に失敗しました"}
|
||||
|
||||
elif function_name == "search_memories":
|
||||
keywords = arguments.get("keywords", [])
|
||||
return await self.mcp_client.search_memories(keywords) or {"error": "記憶の検索に失敗しました"}
|
||||
|
||||
elif function_name == "get_contextual_memories":
|
||||
query = arguments.get("query", "")
|
||||
limit = arguments.get("limit", 5)
|
||||
return await self.mcp_client.get_contextual_memories(query, limit) or {"error": "文脈記憶の取得に失敗しました"}
|
||||
|
||||
elif function_name == "get_relationship":
|
||||
# 引数のuser_idがない場合はコンテキストから取得
|
||||
user_id = arguments.get("user_id", context_user_id)
|
||||
if not user_id or user_id == "user":
|
||||
user_id = context_user_id
|
||||
# デバッグ用ログ
|
||||
print(f"🔍 [DEBUG] get_relationship called with user_id: '{user_id}' (context: '{context_user_id}')")
|
||||
result = await self.mcp_client.get_relationship(user_id)
|
||||
print(f"🔍 [DEBUG] MCP result: {result}")
|
||||
return result or {"error": "関係性の取得に失敗しました"}
|
||||
|
||||
# ai.card tools
|
||||
elif function_name == "card_get_user_cards":
|
||||
did = arguments.get("did", context_user_id)
|
||||
limit = arguments.get("limit", 10)
|
||||
result = await self.mcp_client.card_get_user_cards(did, limit)
|
||||
# Check if ai.card server is not running
|
||||
if result and result.get("error") == "ai.card server is not running":
|
||||
return {
|
||||
"error": "ai.cardサーバーが起動していません",
|
||||
"message": "カードシステムを使用するには、別のターミナルで以下のコマンドを実行してください:\ncd card && ./start_server.sh"
|
||||
}
|
||||
return result or {"error": "カード一覧の取得に失敗しました"}
|
||||
|
||||
elif function_name == "card_draw_card":
|
||||
did = arguments.get("did", context_user_id)
|
||||
is_paid = arguments.get("is_paid", False)
|
||||
result = await self.mcp_client.card_draw_card(did, is_paid)
|
||||
if result and result.get("error") == "ai.card server is not running":
|
||||
return {
|
||||
"error": "ai.cardサーバーが起動していません",
|
||||
"message": "カードシステムを使用するには、別のターミナルで以下のコマンドを実行してください:\ncd card && ./start_server.sh"
|
||||
}
|
||||
return result or {"error": "ガチャに失敗しました"}
|
||||
|
||||
elif function_name == "card_analyze_collection":
|
||||
did = arguments.get("did", context_user_id)
|
||||
result = await self.mcp_client.card_analyze_collection(did)
|
||||
if result and result.get("error") == "ai.card server is not running":
|
||||
return {
|
||||
"error": "ai.cardサーバーが起動していません",
|
||||
"message": "カードシステムを使用するには、別のターミナルで以下のコマンドを実行してください:\ncd card && ./start_server.sh"
|
||||
}
|
||||
return result or {"error": "コレクション分析に失敗しました"}
|
||||
|
||||
elif function_name == "card_get_gacha_stats":
|
||||
result = await self.mcp_client.card_get_gacha_stats()
|
||||
if result and result.get("error") == "ai.card server is not running":
|
||||
return {
|
||||
"error": "ai.cardサーバーが起動していません",
|
||||
"message": "カードシステムを使用するには、別のターミナルで以下のコマンドを実行してください:\ncd card && ./start_server.sh"
|
||||
}
|
||||
return result or {"error": "ガチャ統計の取得に失敗しました"}
|
||||
|
||||
else:
|
||||
return {"error": f"未知のツール: {function_name}"}
|
||||
|
||||
except Exception as e:
|
||||
return {"error": f"ツール実行エラー: {str(e)}"}
|
||||
|
||||
def chat(self, prompt: str, max_tokens: int = 2000) -> str:
|
||||
"""Simple chat interface without MCP tools"""
|
||||
try:
|
||||
messages = []
|
||||
if self.config_system_prompt:
|
||||
messages.append({"role": "system", "content": self.config_system_prompt})
|
||||
messages.append({"role": "user", "content": prompt})
|
||||
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=messages,
|
||||
max_tokens=max_tokens,
|
||||
temperature=0.7
|
||||
)
|
||||
return response.choices[0].message.content
|
||||
except Exception as e:
|
||||
self.logger.error(f"OpenAI chat failed: {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 = {
|
||||
@ -543,18 +187,9 @@ Recent memories:
|
||||
return mood_responses.get(persona_state.current_mood, "I see.")
|
||||
|
||||
|
||||
def create_ai_provider(provider: str = "ollama", model: Optional[str] = None, mcp_client=None, **kwargs) -> AIProvider:
|
||||
def create_ai_provider(provider: str = "ollama", model: str = "qwen2.5", **kwargs) -> AIProvider:
|
||||
"""Factory function to create AI providers"""
|
||||
if provider == "ollama":
|
||||
# Get model from config if not provided
|
||||
if model is None:
|
||||
try:
|
||||
from .config import Config
|
||||
config = Config()
|
||||
model = config.get('providers.ollama.default_model', 'qwen2.5')
|
||||
except:
|
||||
model = 'qwen2.5' # Fallback to default
|
||||
|
||||
# Try to get host from config if not provided in kwargs
|
||||
if 'host' not in kwargs:
|
||||
try:
|
||||
@ -567,14 +202,6 @@ def create_ai_provider(provider: str = "ollama", model: Optional[str] = None, mc
|
||||
pass # Use environment variable or default
|
||||
return OllamaProvider(model=model, **kwargs)
|
||||
elif provider == "openai":
|
||||
# Get model from config if not provided
|
||||
if model is None:
|
||||
try:
|
||||
from .config import Config
|
||||
config = Config()
|
||||
model = config.get('providers.openai.default_model', 'gpt-4o-mini')
|
||||
except:
|
||||
model = 'gpt-4o-mini' # Fallback to default
|
||||
return OpenAIProvider(model=model, mcp_client=mcp_client, **kwargs)
|
||||
return OpenAIProvider(model=model, **kwargs)
|
||||
else:
|
||||
raise ValueError(f"Unknown provider: {provider}")
|
||||
|
834
src/aigpt/cli.py
834
src/aigpt/cli.py
File diff suppressed because it is too large
Load Diff
@ -41,50 +41,11 @@ class Config:
|
||||
"providers": {
|
||||
"openai": {
|
||||
"api_key": None,
|
||||
"default_model": "gpt-4o-mini",
|
||||
"system_prompt": None
|
||||
"default_model": "gpt-4o-mini"
|
||||
},
|
||||
"ollama": {
|
||||
"host": "http://localhost:11434",
|
||||
"default_model": "qwen3:latest",
|
||||
"system_prompt": None
|
||||
}
|
||||
},
|
||||
"mcp": {
|
||||
"enabled": True,
|
||||
"auto_detect": True,
|
||||
"servers": {
|
||||
"ai_gpt": {
|
||||
"name": "ai.gpt MCP Server",
|
||||
"base_url": "http://localhost:8001",
|
||||
"endpoints": {
|
||||
"get_memories": "/get_memories",
|
||||
"search_memories": "/search_memories",
|
||||
"get_contextual_memories": "/get_contextual_memories",
|
||||
"process_interaction": "/process_interaction",
|
||||
"get_relationship": "/get_relationship",
|
||||
"get_all_relationships": "/get_all_relationships",
|
||||
"get_persona_state": "/get_persona_state",
|
||||
"get_fortune": "/get_fortune",
|
||||
"run_maintenance": "/run_maintenance",
|
||||
"execute_command": "/execute_command",
|
||||
"analyze_file": "/analyze_file",
|
||||
"remote_shell": "/remote_shell",
|
||||
"ai_bot_status": "/ai_bot_status"
|
||||
},
|
||||
"timeout": 10.0
|
||||
},
|
||||
"ai_card": {
|
||||
"name": "ai.card MCP Server",
|
||||
"base_url": "http://localhost:8000",
|
||||
"endpoints": {
|
||||
"health": "/health",
|
||||
"get_user_cards": "/api/cards/user",
|
||||
"gacha": "/api/gacha",
|
||||
"sync_atproto": "/api/sync"
|
||||
},
|
||||
"timeout": 5.0
|
||||
}
|
||||
"default_model": "qwen2.5"
|
||||
}
|
||||
},
|
||||
"atproto": {
|
||||
|
@ -34,23 +34,7 @@ class AIGptMcpServer:
|
||||
# Create MCP server with FastAPI app
|
||||
self.server = FastApiMCP(self.app)
|
||||
|
||||
# Check if ai.card exists
|
||||
self.card_dir = Path("./card")
|
||||
self.has_card = self.card_dir.exists() and self.card_dir.is_dir()
|
||||
|
||||
# Check if ai.log exists
|
||||
self.log_dir = Path("./log")
|
||||
self.has_log = self.log_dir.exists() and self.log_dir.is_dir()
|
||||
|
||||
self._register_tools()
|
||||
|
||||
# Register ai.card tools if available
|
||||
if self.has_card:
|
||||
self._register_card_tools()
|
||||
|
||||
# Register ai.log tools if available
|
||||
if self.has_log:
|
||||
self._register_log_tools()
|
||||
|
||||
def _register_tools(self):
|
||||
"""Register all MCP tools"""
|
||||
@ -500,148 +484,6 @@ class AIGptMcpServer:
|
||||
# Python コードを /sh 経由で実行
|
||||
python_command = f'python3 -c "{code.replace('"', '\\"')}"'
|
||||
return await remote_shell(python_command, ai_bot_url)
|
||||
|
||||
def _register_card_tools(self):
|
||||
"""Register ai.card MCP tools when card directory exists"""
|
||||
logger.info("Registering ai.card tools...")
|
||||
|
||||
@self.app.get("/card_get_user_cards", operation_id="card_get_user_cards")
|
||||
async def card_get_user_cards(did: str, limit: int = 10) -> Dict[str, Any]:
|
||||
"""Get user's card collection from ai.card system"""
|
||||
logger.info(f"🎴 [ai.card] Getting cards for did: {did}, limit: {limit}")
|
||||
try:
|
||||
url = "http://localhost:8000/get_user_cards"
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
logger.info(f"🎴 [ai.card] Calling: {url}")
|
||||
response = await client.get(
|
||||
url,
|
||||
params={"did": did, "limit": limit}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
cards = response.json()
|
||||
return {
|
||||
"cards": cards,
|
||||
"count": len(cards),
|
||||
"did": did
|
||||
}
|
||||
else:
|
||||
return {"error": f"Failed to get cards: {response.status_code}"}
|
||||
except httpx.ConnectError:
|
||||
return {
|
||||
"error": "ai.card server is not running",
|
||||
"hint": "Please start ai.card server: cd card && ./start_server.sh",
|
||||
"details": "Connection refused to http://localhost:8000"
|
||||
}
|
||||
except Exception as e:
|
||||
return {"error": f"ai.card connection failed: {str(e)}"}
|
||||
|
||||
@self.app.post("/card_draw_card", operation_id="card_draw_card")
|
||||
async def card_draw_card(did: str, is_paid: bool = False) -> Dict[str, Any]:
|
||||
"""Draw a card from gacha system"""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
response = await client.post(
|
||||
f"http://localhost:8000/draw_card?did={did}&is_paid={is_paid}"
|
||||
)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
return {"error": f"Failed to draw card: {response.status_code}"}
|
||||
except httpx.ConnectError:
|
||||
return {
|
||||
"error": "ai.card server is not running",
|
||||
"hint": "Please start ai.card server: cd card && ./start_server.sh",
|
||||
"details": "Connection refused to http://localhost:8000"
|
||||
}
|
||||
except Exception as e:
|
||||
return {"error": f"ai.card connection failed: {str(e)}"}
|
||||
|
||||
@self.app.get("/card_get_card_details", operation_id="card_get_card_details")
|
||||
async def card_get_card_details(card_id: int) -> Dict[str, Any]:
|
||||
"""Get detailed information about a specific card"""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
response = await client.get(
|
||||
"http://localhost:8000/get_card_details",
|
||||
params={"card_id": card_id}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
return {"error": f"Failed to get card details: {response.status_code}"}
|
||||
except httpx.ConnectError:
|
||||
return {
|
||||
"error": "ai.card server is not running",
|
||||
"hint": "Please start ai.card server: cd card && ./start_server.sh",
|
||||
"details": "Connection refused to http://localhost:8000"
|
||||
}
|
||||
except Exception as e:
|
||||
return {"error": f"ai.card connection failed: {str(e)}"}
|
||||
|
||||
@self.app.get("/card_analyze_collection", operation_id="card_analyze_collection")
|
||||
async def card_analyze_collection(did: str) -> Dict[str, Any]:
|
||||
"""Analyze user's card collection statistics"""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
response = await client.get(
|
||||
"http://localhost:8000/analyze_card_collection",
|
||||
params={"did": did}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
return {"error": f"Failed to analyze collection: {response.status_code}"}
|
||||
except httpx.ConnectError:
|
||||
return {
|
||||
"error": "ai.card server is not running",
|
||||
"hint": "Please start ai.card server: cd card && ./start_server.sh",
|
||||
"details": "Connection refused to http://localhost:8000"
|
||||
}
|
||||
except Exception as e:
|
||||
return {"error": f"ai.card connection failed: {str(e)}"}
|
||||
|
||||
@self.app.get("/card_get_gacha_stats", operation_id="card_get_gacha_stats")
|
||||
async def card_get_gacha_stats() -> Dict[str, Any]:
|
||||
"""Get gacha system statistics"""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
response = await client.get("http://localhost:8000/get_gacha_stats")
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
return {"error": f"Failed to get gacha stats: {response.status_code}"}
|
||||
except httpx.ConnectError:
|
||||
return {
|
||||
"error": "ai.card server is not running",
|
||||
"hint": "Please start ai.card server: cd card && ./start_server.sh",
|
||||
"details": "Connection refused to http://localhost:8000"
|
||||
}
|
||||
except Exception as e:
|
||||
return {"error": f"ai.card connection failed: {str(e)}"}
|
||||
|
||||
@self.app.get("/card_system_status", operation_id="card_system_status")
|
||||
async def card_system_status() -> Dict[str, Any]:
|
||||
"""Check ai.card system status"""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=5.0) as client:
|
||||
response = await client.get("http://localhost:8000/health")
|
||||
if response.status_code == 200:
|
||||
return {
|
||||
"status": "online",
|
||||
"health": response.json(),
|
||||
"card_dir": str(self.card_dir)
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": f"Health check failed: {response.status_code}"
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"status": "offline",
|
||||
"error": f"ai.card is not running: {str(e)}",
|
||||
"hint": "Start ai.card with: cd card && ./start_server.sh"
|
||||
}
|
||||
|
||||
@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]:
|
||||
@ -660,353 +502,6 @@ class AIGptMcpServer:
|
||||
# Mount MCP server
|
||||
self.server.mount()
|
||||
|
||||
def _register_log_tools(self):
|
||||
"""Register ai.log MCP tools when log directory exists"""
|
||||
logger.info("Registering ai.log tools...")
|
||||
|
||||
@self.app.post("/log_create_post", operation_id="log_create_post")
|
||||
async def log_create_post(title: str, content: str, tags: Optional[List[str]] = None, slug: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""Create a new blog post in ai.log system"""
|
||||
logger.info(f"📝 [ai.log] Creating post: {title}")
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
response = await client.post(
|
||||
"http://localhost:8002/mcp/tools/call",
|
||||
json={
|
||||
"jsonrpc": "2.0",
|
||||
"id": "log_create_post",
|
||||
"method": "call_tool",
|
||||
"params": {
|
||||
"name": "create_blog_post",
|
||||
"arguments": {
|
||||
"title": title,
|
||||
"content": content,
|
||||
"tags": tags or [],
|
||||
"slug": slug
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
if result.get("error"):
|
||||
return {"error": result["error"]["message"]}
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Blog post created successfully",
|
||||
"title": title,
|
||||
"tags": tags or []
|
||||
}
|
||||
else:
|
||||
return {"error": f"Failed to create post: {response.status_code}"}
|
||||
except httpx.ConnectError:
|
||||
return {
|
||||
"error": "ai.log server is not running",
|
||||
"hint": "Please start ai.log server: cd log && cargo run -- mcp --port 8002",
|
||||
"details": "Connection refused to http://localhost:8002"
|
||||
}
|
||||
except Exception as e:
|
||||
return {"error": f"ai.log connection failed: {str(e)}"}
|
||||
|
||||
@self.app.get("/log_list_posts", operation_id="log_list_posts")
|
||||
async def log_list_posts(limit: int = 10, offset: int = 0) -> Dict[str, Any]:
|
||||
"""List blog posts from ai.log system"""
|
||||
logger.info(f"📝 [ai.log] Listing posts: limit={limit}, offset={offset}")
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
response = await client.post(
|
||||
"http://localhost:8002/mcp/tools/call",
|
||||
json={
|
||||
"jsonrpc": "2.0",
|
||||
"id": "log_list_posts",
|
||||
"method": "call_tool",
|
||||
"params": {
|
||||
"name": "list_blog_posts",
|
||||
"arguments": {
|
||||
"limit": limit,
|
||||
"offset": offset
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
if result.get("error"):
|
||||
return {"error": result["error"]["message"]}
|
||||
return result.get("result", {})
|
||||
else:
|
||||
return {"error": f"Failed to list posts: {response.status_code}"}
|
||||
except httpx.ConnectError:
|
||||
return {
|
||||
"error": "ai.log server is not running",
|
||||
"hint": "Please start ai.log server: cd log && cargo run -- mcp --port 8002",
|
||||
"details": "Connection refused to http://localhost:8002"
|
||||
}
|
||||
except Exception as e:
|
||||
return {"error": f"ai.log connection failed: {str(e)}"}
|
||||
|
||||
@self.app.post("/log_build_blog", operation_id="log_build_blog")
|
||||
async def log_build_blog(enable_ai: bool = True, translate: bool = False) -> Dict[str, Any]:
|
||||
"""Build the static blog with AI features"""
|
||||
logger.info(f"📝 [ai.log] Building blog: AI={enable_ai}, translate={translate}")
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=60.0) as client:
|
||||
response = await client.post(
|
||||
"http://localhost:8002/mcp/tools/call",
|
||||
json={
|
||||
"jsonrpc": "2.0",
|
||||
"id": "log_build_blog",
|
||||
"method": "call_tool",
|
||||
"params": {
|
||||
"name": "build_blog",
|
||||
"arguments": {
|
||||
"enable_ai": enable_ai,
|
||||
"translate": translate
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
if result.get("error"):
|
||||
return {"error": result["error"]["message"]}
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Blog built successfully",
|
||||
"ai_enabled": enable_ai,
|
||||
"translation_enabled": translate
|
||||
}
|
||||
else:
|
||||
return {"error": f"Failed to build blog: {response.status_code}"}
|
||||
except httpx.ConnectError:
|
||||
return {
|
||||
"error": "ai.log server is not running",
|
||||
"hint": "Please start ai.log server: cd log && cargo run -- mcp --port 8002",
|
||||
"details": "Connection refused to http://localhost:8002"
|
||||
}
|
||||
except Exception as e:
|
||||
return {"error": f"ai.log connection failed: {str(e)}"}
|
||||
|
||||
@self.app.get("/log_get_post", operation_id="log_get_post")
|
||||
async def log_get_post(slug: str) -> Dict[str, Any]:
|
||||
"""Get blog post content by slug"""
|
||||
logger.info(f"📝 [ai.log] Getting post: {slug}")
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
response = await client.post(
|
||||
"http://localhost:8002/mcp/tools/call",
|
||||
json={
|
||||
"jsonrpc": "2.0",
|
||||
"id": "log_get_post",
|
||||
"method": "call_tool",
|
||||
"params": {
|
||||
"name": "get_post_content",
|
||||
"arguments": {
|
||||
"slug": slug
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
if result.get("error"):
|
||||
return {"error": result["error"]["message"]}
|
||||
return result.get("result", {})
|
||||
else:
|
||||
return {"error": f"Failed to get post: {response.status_code}"}
|
||||
except httpx.ConnectError:
|
||||
return {
|
||||
"error": "ai.log server is not running",
|
||||
"hint": "Please start ai.log server: cd log && cargo run -- mcp --port 8002",
|
||||
"details": "Connection refused to http://localhost:8002"
|
||||
}
|
||||
except Exception as e:
|
||||
return {"error": f"ai.log connection failed: {str(e)}"}
|
||||
|
||||
@self.app.get("/log_system_status", operation_id="log_system_status")
|
||||
async def log_system_status() -> Dict[str, Any]:
|
||||
"""Check ai.log system status"""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=5.0) as client:
|
||||
response = await client.get("http://localhost:8002/health")
|
||||
if response.status_code == 200:
|
||||
return {
|
||||
"status": "online",
|
||||
"health": response.json(),
|
||||
"log_dir": str(self.log_dir)
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": f"Health check failed: {response.status_code}"
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"status": "offline",
|
||||
"error": f"ai.log is not running: {str(e)}",
|
||||
"hint": "Start ai.log with: cd log && cargo run -- mcp --port 8002"
|
||||
}
|
||||
|
||||
@self.app.post("/log_ai_content", operation_id="log_ai_content")
|
||||
async def log_ai_content(user_id: str, topic: str = "daily thoughts") -> Dict[str, Any]:
|
||||
"""Generate AI content for blog from memories and create post"""
|
||||
logger.info(f"📝 [ai.log] Generating AI content for: {topic}")
|
||||
try:
|
||||
# Get contextual memories for the topic
|
||||
memories = await get_contextual_memories(topic, limit=5)
|
||||
|
||||
# Get AI provider
|
||||
ai_provider = create_ai_provider()
|
||||
|
||||
# Build content from memories
|
||||
memory_context = ""
|
||||
for group_name, mem_list in memories.items():
|
||||
memory_context += f"\n## {group_name}\n"
|
||||
for mem in mem_list:
|
||||
memory_context += f"- {mem['content']}\n"
|
||||
|
||||
# Generate blog content
|
||||
prompt = f"""Based on the following memories and context, write a thoughtful blog post about {topic}.
|
||||
|
||||
Memory Context:
|
||||
{memory_context}
|
||||
|
||||
Please write a well-structured blog post in Markdown format with:
|
||||
1. An engaging title
|
||||
2. Clear structure with headings
|
||||
3. Personal insights based on the memories
|
||||
4. A conclusion that ties everything together
|
||||
|
||||
Focus on creating content that reflects personal growth and learning from these experiences."""
|
||||
|
||||
content = ai_provider.generate_response(prompt, "You are a thoughtful blogger who creates insightful content.")
|
||||
|
||||
# Extract title from content (first heading)
|
||||
lines = content.split('\n')
|
||||
title = topic.title()
|
||||
for line in lines:
|
||||
if line.startswith('# '):
|
||||
title = line[2:].strip()
|
||||
content = '\n'.join(lines[1:]).strip() # Remove title from content
|
||||
break
|
||||
|
||||
# Create the blog post
|
||||
return await log_create_post(
|
||||
title=title,
|
||||
content=content,
|
||||
tags=["AI", "thoughts", "daily"]
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return {"error": f"Failed to generate AI content: {str(e)}"}
|
||||
|
||||
@self.app.post("/log_translate_document", operation_id="log_translate_document")
|
||||
async def log_translate_document(
|
||||
input_file: str,
|
||||
target_lang: str,
|
||||
source_lang: Optional[str] = None,
|
||||
output_file: Optional[str] = None,
|
||||
model: str = "qwen2.5:latest",
|
||||
ollama_endpoint: str = "http://localhost:11434"
|
||||
) -> Dict[str, Any]:
|
||||
"""Translate markdown documents using Ollama via ai.log"""
|
||||
logger.info(f"🌍 [ai.log] Translating document: {input_file} -> {target_lang}")
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=60.0) as client: # Longer timeout for translation
|
||||
response = await client.post(
|
||||
"http://localhost:8002/mcp/tools/call",
|
||||
json={
|
||||
"jsonrpc": "2.0",
|
||||
"id": "log_translate_document",
|
||||
"method": "call_tool",
|
||||
"params": {
|
||||
"name": "translate_document",
|
||||
"arguments": {
|
||||
"input_file": input_file,
|
||||
"target_lang": target_lang,
|
||||
"source_lang": source_lang,
|
||||
"output_file": output_file,
|
||||
"model": model,
|
||||
"ollama_endpoint": ollama_endpoint
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
if result.get("error"):
|
||||
return {"error": result["error"]["message"]}
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Document translated successfully",
|
||||
"input_file": input_file,
|
||||
"target_lang": target_lang,
|
||||
"output_file": result.get("result", {}).get("output_file")
|
||||
}
|
||||
else:
|
||||
return {"error": f"Failed to translate document: {response.status_code}"}
|
||||
except httpx.ConnectError:
|
||||
return {
|
||||
"error": "ai.log server is not running",
|
||||
"hint": "Please start ai.log server: cd log && cargo run -- mcp --port 8002",
|
||||
"details": "Connection refused to http://localhost:8002"
|
||||
}
|
||||
except Exception as e:
|
||||
return {"error": f"ai.log translation failed: {str(e)}"}
|
||||
|
||||
@self.app.post("/log_generate_docs", operation_id="log_generate_docs")
|
||||
async def log_generate_docs(
|
||||
doc_type: str, # "readme", "api", "structure", "changelog"
|
||||
source_path: Optional[str] = None,
|
||||
output_path: Optional[str] = None,
|
||||
with_ai: bool = True,
|
||||
include_deps: bool = False,
|
||||
format_type: str = "markdown"
|
||||
) -> Dict[str, Any]:
|
||||
"""Generate documentation using ai.log's doc generation features"""
|
||||
logger.info(f"📚 [ai.log] Generating {doc_type} documentation")
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
response = await client.post(
|
||||
"http://localhost:8002/mcp/tools/call",
|
||||
json={
|
||||
"jsonrpc": "2.0",
|
||||
"id": "log_generate_docs",
|
||||
"method": "call_tool",
|
||||
"params": {
|
||||
"name": "generate_documentation",
|
||||
"arguments": {
|
||||
"doc_type": doc_type,
|
||||
"source_path": source_path or ".",
|
||||
"output_path": output_path,
|
||||
"with_ai": with_ai,
|
||||
"include_deps": include_deps,
|
||||
"format_type": format_type
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
if result.get("error"):
|
||||
return {"error": result["error"]["message"]}
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"{doc_type.title()} documentation generated successfully",
|
||||
"doc_type": doc_type,
|
||||
"output_path": result.get("result", {}).get("output_path")
|
||||
}
|
||||
else:
|
||||
return {"error": f"Failed to generate documentation: {response.status_code}"}
|
||||
except httpx.ConnectError:
|
||||
return {
|
||||
"error": "ai.log server is not running",
|
||||
"hint": "Please start ai.log server: cd log && cargo run -- mcp --port 8002",
|
||||
"details": "Connection refused to http://localhost:8002"
|
||||
}
|
||||
except Exception as e:
|
||||
return {"error": f"ai.log documentation generation failed: {str(e)}"}
|
||||
|
||||
def get_server(self) -> FastApiMCP:
|
||||
"""Get the FastAPI MCP server instance"""
|
||||
return self.server
|
||||
|
@ -133,15 +133,7 @@ FORTUNE: {state.fortune.fortune_value}/10
|
||||
if context_parts:
|
||||
context_prompt += "RELEVANT CONTEXT:\n" + "\n\n".join(context_parts) + "\n\n"
|
||||
|
||||
context_prompt += f"""IMPORTANT: You have access to the following tools:
|
||||
- Memory tools: get_memories, search_memories, get_contextual_memories
|
||||
- Relationship tools: get_relationship
|
||||
- Card game tools: card_get_user_cards, card_draw_card, card_analyze_collection
|
||||
|
||||
When asked about cards, collections, or anything card-related, YOU MUST use the card tools.
|
||||
For "カードコレクションを見せて" or similar requests, use card_get_user_cards with did='{user_id}'.
|
||||
|
||||
Respond to this message while staying true to your personality and the established relationship context:
|
||||
context_prompt += f"""Respond to this message while staying true to your personality and the established relationship context:
|
||||
|
||||
User: {current_message}
|
||||
|
||||
@ -168,12 +160,7 @@ AI:"""
|
||||
|
||||
# Generate response using AI with full context
|
||||
try:
|
||||
# Check if AI provider supports MCP
|
||||
if hasattr(ai_provider, 'chat_with_mcp'):
|
||||
import asyncio
|
||||
response = asyncio.run(ai_provider.chat_with_mcp(context_prompt, max_tokens=2000, user_id=user_id))
|
||||
else:
|
||||
response = ai_provider.chat(context_prompt, max_tokens=2000)
|
||||
response = ai_provider.chat(context_prompt, max_tokens=200)
|
||||
|
||||
# Clean up response if it includes the prompt echo
|
||||
if "AI:" in response:
|
||||
|
@ -1,15 +0,0 @@
|
||||
"""Shared modules for AI ecosystem"""
|
||||
|
||||
from .ai_provider import (
|
||||
AIProvider,
|
||||
OllamaProvider,
|
||||
OpenAIProvider,
|
||||
create_ai_provider
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'AIProvider',
|
||||
'OllamaProvider',
|
||||
'OpenAIProvider',
|
||||
'create_ai_provider'
|
||||
]
|
@ -1,139 +0,0 @@
|
||||
"""Shared AI Provider implementation for ai ecosystem"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
from typing import Optional, Dict, List, Any, Protocol
|
||||
from abc import abstractmethod
|
||||
import httpx
|
||||
from openai import OpenAI
|
||||
import ollama
|
||||
|
||||
|
||||
class AIProvider(Protocol):
|
||||
"""Protocol for AI providers"""
|
||||
|
||||
@abstractmethod
|
||||
async def chat(self, prompt: str, system_prompt: Optional[str] = None) -> str:
|
||||
"""Generate a response based on prompt"""
|
||||
pass
|
||||
|
||||
|
||||
class OllamaProvider:
|
||||
"""Ollama AI provider - shared implementation"""
|
||||
|
||||
def __init__(self, model: str = "qwen3", host: Optional[str] = None, config_system_prompt: Optional[str] = None):
|
||||
self.model = model
|
||||
# Use environment variable OLLAMA_HOST if available
|
||||
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)
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.logger.info(f"OllamaProvider initialized with host: {self.host}, model: {self.model}")
|
||||
self.config_system_prompt = config_system_prompt
|
||||
|
||||
async def chat(self, prompt: str, system_prompt: Optional[str] = None) -> str:
|
||||
"""Simple chat interface"""
|
||||
try:
|
||||
messages = []
|
||||
# Use provided system_prompt, fall back to config_system_prompt
|
||||
final_system_prompt = system_prompt or self.config_system_prompt
|
||||
if final_system_prompt:
|
||||
messages.append({"role": "system", "content": final_system_prompt})
|
||||
messages.append({"role": "user", "content": prompt})
|
||||
|
||||
response = self.client.chat(
|
||||
model=self.model,
|
||||
messages=messages,
|
||||
options={
|
||||
"num_predict": 2000,
|
||||
"temperature": 0.7,
|
||||
"top_p": 0.9,
|
||||
},
|
||||
stream=False
|
||||
)
|
||||
return self._clean_response(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 _clean_response(self, response: str) -> str:
|
||||
"""Clean response by removing think tags and other unwanted content"""
|
||||
import re
|
||||
# Remove <think></think> tags and their content
|
||||
response = re.sub(r'<think>.*?</think>', '', response, flags=re.DOTALL)
|
||||
# Remove any remaining whitespace at the beginning/end
|
||||
response = response.strip()
|
||||
return response
|
||||
|
||||
|
||||
class OpenAIProvider:
|
||||
"""OpenAI API provider - shared implementation"""
|
||||
|
||||
def __init__(self, model: str = "gpt-4o-mini", api_key: Optional[str] = None,
|
||||
config_system_prompt: Optional[str] = None, mcp_client=None):
|
||||
self.model = model
|
||||
self.api_key = api_key or os.getenv("OPENAI_API_KEY")
|
||||
if not self.api_key:
|
||||
raise ValueError("OpenAI API key not provided")
|
||||
self.client = OpenAI(api_key=self.api_key)
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.config_system_prompt = config_system_prompt
|
||||
self.mcp_client = mcp_client
|
||||
|
||||
async def chat(self, prompt: str, system_prompt: Optional[str] = None) -> str:
|
||||
"""Simple chat interface without MCP tools"""
|
||||
try:
|
||||
messages = []
|
||||
# Use provided system_prompt, fall back to config_system_prompt
|
||||
final_system_prompt = system_prompt or self.config_system_prompt
|
||||
if final_system_prompt:
|
||||
messages.append({"role": "system", "content": final_system_prompt})
|
||||
messages.append({"role": "user", "content": prompt})
|
||||
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=messages,
|
||||
max_tokens=2000,
|
||||
temperature=0.7
|
||||
)
|
||||
return response.choices[0].message.content
|
||||
except Exception as e:
|
||||
self.logger.error(f"OpenAI chat failed: {e}")
|
||||
return "I'm having trouble connecting to the AI model."
|
||||
|
||||
def _get_mcp_tools(self) -> List[Dict[str, Any]]:
|
||||
"""Override this method in subclasses to provide MCP tools"""
|
||||
return []
|
||||
|
||||
async def chat_with_mcp(self, prompt: str, **kwargs) -> str:
|
||||
"""Chat interface with MCP function calling support
|
||||
|
||||
This method should be overridden in subclasses to provide
|
||||
specific MCP functionality.
|
||||
"""
|
||||
if not self.mcp_client:
|
||||
return await self.chat(prompt)
|
||||
|
||||
# Default implementation - subclasses should override
|
||||
return await self.chat(prompt)
|
||||
|
||||
async def _execute_mcp_tool(self, tool_call, **kwargs) -> Dict[str, Any]:
|
||||
"""Execute MCP tool call - override in subclasses"""
|
||||
return {"error": "MCP tool execution not implemented"}
|
||||
|
||||
|
||||
def create_ai_provider(provider: str = "ollama", model: Optional[str] = None,
|
||||
config_system_prompt: Optional[str] = None, mcp_client=None, **kwargs) -> AIProvider:
|
||||
"""Factory function to create AI providers"""
|
||||
if provider == "ollama":
|
||||
model = model or "qwen3"
|
||||
return OllamaProvider(model=model, config_system_prompt=config_system_prompt, **kwargs)
|
||||
elif provider == "openai":
|
||||
model = model or "gpt-4o-mini"
|
||||
return OpenAIProvider(model=model, config_system_prompt=config_system_prompt,
|
||||
mcp_client=mcp_client, **kwargs)
|
||||
else:
|
||||
raise ValueError(f"Unknown provider: {provider}")
|
Loading…
x
Reference in New Issue
Block a user