From 1c555a706bde2ecb737e8c6aa880a5662a86d500 Mon Sep 17 00:00:00 2001 From: syui Date: Sun, 1 Jun 2025 16:40:25 +0900 Subject: [PATCH] fix --- ai_gpt/.env.example | 5 + ai_gpt/DEVELOPMENT_STATUS.md | 117 ++++++ ai_gpt/README.md | 212 ++++++++++ ai_gpt/docs/README.md | 30 ++ ai_gpt/docs/commands.md | 207 ++++++++++ ai_gpt/docs/concepts.md | 102 +++++ ai_gpt/docs/configuration.md | 118 ++++++ ai_gpt/docs/development.md | 167 ++++++++ ai_gpt/docs/mcp-server.md | 110 +++++ ai_gpt/docs/quickstart.md | 69 ++++ ai_gpt/docs/scheduler.md | 168 ++++++++ ai_gpt/pyproject.toml | 32 ++ ai_gpt/src/ai_gpt/__init__.py | 15 + ai_gpt/src/ai_gpt/ai_provider.py | 172 ++++++++ ai_gpt/src/ai_gpt/cli.py | 444 ++++++++++++++++++++ ai_gpt/src/ai_gpt/config.py | 145 +++++++ ai_gpt/src/ai_gpt/fortune.py | 118 ++++++ ai_gpt/src/ai_gpt/mcp_server.py | 149 +++++++ ai_gpt/src/ai_gpt/memory.py | 155 +++++++ ai_gpt/src/ai_gpt/models.py | 79 ++++ ai_gpt/src/ai_gpt/persona.py | 181 +++++++++ ai_gpt/src/ai_gpt/relationship.py | 135 +++++++ ai_gpt/src/ai_gpt/scheduler.py | 312 ++++++++++++++ ai_gpt/src/ai_gpt/transmission.py | 111 +++++ claude.md | 584 ++++++++++----------------- Cargo.toml => rust/Cargo.toml | 0 claude.json => rust/docs/claude.json | 0 rust/docs/claude.md | 417 +++++++++++++++++++ readme.md => rust/docs/readme.md | 0 {mcp => rust/mcp}/chat.py | 0 {mcp => rust/mcp}/chat_client.py | 0 {mcp => rust/mcp}/chatgpt.json | 0 {mcp => rust/mcp}/config.py | 0 {mcp => rust/mcp}/memory_client.py | 0 {mcp => rust/mcp}/requirements.txt | 0 {mcp => rust/mcp}/server.py | 0 {src => rust/src}/cli.rs | 0 {src => rust/src}/config.rs | 0 {src => rust/src}/main.rs | 0 {src => rust/src}/mcp/memory.rs | 0 {src => rust/src}/mcp/mod.rs | 0 {src => rust/src}/mcp/server.rs | 0 42 files changed, 3991 insertions(+), 363 deletions(-) create mode 100644 ai_gpt/.env.example create mode 100644 ai_gpt/DEVELOPMENT_STATUS.md create mode 100644 ai_gpt/README.md create mode 100644 ai_gpt/docs/README.md create mode 100644 ai_gpt/docs/commands.md create mode 100644 ai_gpt/docs/concepts.md create mode 100644 ai_gpt/docs/configuration.md create mode 100644 ai_gpt/docs/development.md create mode 100644 ai_gpt/docs/mcp-server.md create mode 100644 ai_gpt/docs/quickstart.md create mode 100644 ai_gpt/docs/scheduler.md create mode 100644 ai_gpt/pyproject.toml create mode 100644 ai_gpt/src/ai_gpt/__init__.py create mode 100644 ai_gpt/src/ai_gpt/ai_provider.py create mode 100644 ai_gpt/src/ai_gpt/cli.py create mode 100644 ai_gpt/src/ai_gpt/config.py create mode 100644 ai_gpt/src/ai_gpt/fortune.py create mode 100644 ai_gpt/src/ai_gpt/mcp_server.py create mode 100644 ai_gpt/src/ai_gpt/memory.py create mode 100644 ai_gpt/src/ai_gpt/models.py create mode 100644 ai_gpt/src/ai_gpt/persona.py create mode 100644 ai_gpt/src/ai_gpt/relationship.py create mode 100644 ai_gpt/src/ai_gpt/scheduler.py create mode 100644 ai_gpt/src/ai_gpt/transmission.py rename Cargo.toml => rust/Cargo.toml (100%) rename claude.json => rust/docs/claude.json (100%) create mode 100644 rust/docs/claude.md rename readme.md => rust/docs/readme.md (100%) rename {mcp => rust/mcp}/chat.py (100%) rename {mcp => rust/mcp}/chat_client.py (100%) rename {mcp => rust/mcp}/chatgpt.json (100%) rename {mcp => rust/mcp}/config.py (100%) rename {mcp => rust/mcp}/memory_client.py (100%) rename {mcp => rust/mcp}/requirements.txt (100%) rename {mcp => rust/mcp}/server.py (100%) rename {src => rust/src}/cli.rs (100%) rename {src => rust/src}/config.rs (100%) rename {src => rust/src}/main.rs (100%) rename {src => rust/src}/mcp/memory.rs (100%) rename {src => rust/src}/mcp/mod.rs (100%) rename {src => rust/src}/mcp/server.rs (100%) diff --git a/ai_gpt/.env.example b/ai_gpt/.env.example new file mode 100644 index 0000000..3e4bb7b --- /dev/null +++ b/ai_gpt/.env.example @@ -0,0 +1,5 @@ +# OpenAI API Key (required for OpenAI provider) +OPENAI_API_KEY=your-api-key-here + +# Ollama settings (optional) +OLLAMA_HOST=http://localhost:11434 \ No newline at end of file diff --git a/ai_gpt/DEVELOPMENT_STATUS.md b/ai_gpt/DEVELOPMENT_STATUS.md new file mode 100644 index 0000000..25357e7 --- /dev/null +++ b/ai_gpt/DEVELOPMENT_STATUS.md @@ -0,0 +1,117 @@ +# ai.gpt 開発状況 (2025/01/06) + +## 現在の状態 + +### ✅ 実装済み機能 + +1. **基本システム** + - 階層的記憶システム(完全ログ→要約→コア→忘却) + - 不可逆的な関係性システム(broken状態は修復不可) + - AI運勢による日々の人格変動 + - 時間減衰による自然な関係性変化 + +2. **CLI機能** + - `chat` - AIとの会話(Ollama/OpenAI対応) + - `status` - 状態確認 + - `fortune` - AI運勢確認 + - `relationships` - 関係一覧 + - `transmit` - 送信チェック(現在はprint出力) + - `maintenance` - 日次メンテナンス + - `config` - 設定管理 + - `schedule` - スケジューラー管理 + - `server` - MCP Server起動 + +3. **データ管理** + - 保存場所: `~/.config/aigpt/` + - 設定: `config.json` + - データ: `data/` ディレクトリ内の各種JSONファイル + +4. **スケジューラー** + - Cron形式とインターバル形式対応 + - 5種類のタスクタイプ実装済み + - バックグラウンド実行可能 + +5. **MCP Server** + - 9種類のツールを公開 + - Claude Desktopなどから利用可能 + +## 🚧 未実装・今後の課題 + +### 短期的課題 + +1. **自律送信の実装** + - 現在: コンソールにprint出力 + - TODO: atproto (Bluesky) への実際の投稿機能 + - 参考: ai.bot (Rust/seahorse) との連携も検討 + +2. **テストの追加** + - 単体テスト + - 統合テスト + - CI/CDパイプライン + +3. **エラーハンドリングの改善** + - より詳細なエラーメッセージ + - リトライ機構 + +### 中期的課題 + +1. **ai.botとの連携** + - Rust側のAPIエンドポイント作成 + - 送信機能の委譲 + +2. **より高度な記憶要約** + - 現在: シンプルな要約 + - TODO: AIによる意味的な要約 + +3. **Webダッシュボード** + - 関係性の可視化 + - 記憶の管理UI + +### 長期的課題 + +1. **他のsyuiプロジェクトとの統合** + - ai.card: カードゲームとの連携 + - ai.verse: メタバース内でのNPC人格 + - ai.os: システムレベルでの統合 + +2. **分散化** + - atproto上でのデータ保存 + - ユーザーデータ主権の完全実現 + +## 次回開発時のエントリーポイント + +### 1. 自律送信を実装する場合 +```python +# src/ai_gpt/transmission.py を編集 +# atproto-python ライブラリを追加 +# _handle_transmission_check() メソッドを更新 +``` + +### 2. ai.botと連携する場合 +```python +# 新規ファイル: src/ai_gpt/bot_connector.py +# ai.botのAPIエンドポイントにHTTPリクエスト +``` + +### 3. テストを追加する場合 +```bash +# tests/ディレクトリを作成 +# pytest設定を追加 +``` + +## 設計思想の要点(AI向け) + +1. **唯一性(yui system)**: 各ユーザーとAIの関係は1:1で、改変不可能 +2. **不可逆性**: 関係性の破壊は修復不可能(現実の人間関係と同じ) +3. **階層的記憶**: ただのログではなく、要約・コア判定・忘却のプロセス +4. **環境影響**: AI運勢による日々の人格変動(固定的でない) +5. **段階的実装**: まずCLI print → atproto投稿 → ai.bot連携 + +## 現在のコードベースの理解 + +- **言語**: Python (typer CLI, fastapi_mcp) +- **AI統合**: Ollama (ローカル) / OpenAI API +- **データ形式**: JSON(将来的にSQLite検討) +- **認証**: atproto DID(未実装だが設計済み) + +このファイルを参照することで、次回の開発がスムーズに始められます。 \ No newline at end of file diff --git a/ai_gpt/README.md b/ai_gpt/README.md new file mode 100644 index 0000000..c4c74fe --- /dev/null +++ b/ai_gpt/README.md @@ -0,0 +1,212 @@ +# ai.gpt - 自律的送信AI + +存在子理論に基づく、関係性によって自発的にメッセージを送信するAIシステム。 + +## 中核概念 + +- **唯一性**: atproto DIDと1:1で紐付き、改変不可能な人格 +- **不可逆性**: 関係性が壊れたら修復不可能(現実の人間関係と同じ) +- **記憶の階層**: 完全ログ→AI要約→コア判定→選択的忘却 +- **AI運勢**: 1-10のランダム値による日々の人格変動 + +## インストール + +```bash +cd ai_gpt +pip install -e . +``` + +## 設定 + +### APIキーの設定 +```bash +# OpenAI APIキー +ai-gpt config set providers.openai.api_key sk-xxxxx + +# atproto認証情報(将来の自動投稿用) +ai-gpt config set atproto.handle your.handle +ai-gpt config set atproto.password your-password + +# 設定一覧を確認 +ai-gpt config list +``` + +### データ保存場所 +- 設定: `~/.config/aigpt/config.json` +- データ: `~/.config/aigpt/data/` + +## 使い方 + +### 会話する +```bash +ai-gpt chat "did:plc:xxxxx" "こんにちは、今日はどんな気分?" +``` + +### ステータス確認 +```bash +# AI全体の状態 +ai-gpt status + +# 特定ユーザーとの関係 +ai-gpt status "did:plc:xxxxx" +``` + +### 今日の運勢 +```bash +ai-gpt fortune +``` + +### 自律送信チェック +```bash +# ドライラン(確認のみ) +ai-gpt transmit + +# 実行 +ai-gpt transmit --execute +``` + +### 日次メンテナンス +```bash +ai-gpt maintenance +``` + +### 関係一覧 +```bash +ai-gpt relationships +``` + +## データ構造 + +デフォルトでは `~/.ai_gpt/` に以下のファイルが保存されます: + +- `memories.json` - 会話記憶 +- `conversations.json` - 会話ログ +- `relationships.json` - 関係性パラメータ +- `fortunes.json` - AI運勢履歴 +- `transmissions.json` - 送信履歴 +- `persona_state.json` - 人格状態 + +## 関係性の仕組み + +- スコア0-200の範囲で変動 +- 100を超えると送信機能が解禁 +- 時間経過で自然減衰 +- 大きなネガティブな相互作用で破壊される可能性 + +## MCP Server + +### サーバー起動 +```bash +# Ollamaを使用(デフォルト) +ai-gpt server --model qwen2.5 --provider ollama + +# OpenAIを使用 +ai-gpt server --model gpt-4o-mini --provider openai + +# カスタムポート +ai-gpt server --port 8080 +``` + +### AIプロバイダーを使った会話 +```bash +# Ollamaで会話 +ai-gpt chat "did:plc:xxxxx" "こんにちは" --provider ollama --model qwen2.5 + +# OpenAIで会話 +ai-gpt chat "did:plc:xxxxx" "今日の調子はどう?" --provider openai --model gpt-4o-mini +``` + +### MCP Tools + +サーバーが起動すると、以下のツールがAIから利用可能になります: + +- `get_memories` - アクティブな記憶を取得 +- `get_relationship` - 特定ユーザーとの関係を取得 +- `get_all_relationships` - すべての関係を取得 +- `get_persona_state` - 現在の人格状態を取得 +- `process_interaction` - ユーザーとの対話を処理 +- `check_transmission_eligibility` - 送信可能かチェック +- `get_fortune` - 今日の運勢を取得 +- `summarize_memories` - 記憶を要約 +- `run_maintenance` - メンテナンス実行 + +## 環境変数 + +`.env`ファイルを作成して設定: + +```bash +cp .env.example .env +# OpenAI APIキーを設定 +``` + +## スケジューラー機能 + +### タスクの追加 + +```bash +# 6時間ごとに送信チェック +ai-gpt schedule add transmission_check "0 */6 * * *" --provider ollama --model qwen2.5 + +# 30分ごとに送信チェック(インターバル形式) +ai-gpt schedule add transmission_check "30m" + +# 毎日午前3時にメンテナンス +ai-gpt schedule add maintenance "0 3 * * *" + +# 1時間ごとに関係性減衰 +ai-gpt schedule add relationship_decay "1h" + +# 毎週月曜日に記憶要約 +ai-gpt schedule add memory_summary "0 0 * * MON" +``` + +### タスク管理 + +```bash +# タスク一覧 +ai-gpt schedule list + +# タスクを無効化 +ai-gpt schedule disable --task-id transmission_check_1234567890 + +# タスクを有効化 +ai-gpt schedule enable --task-id transmission_check_1234567890 + +# タスクを削除 +ai-gpt schedule remove --task-id transmission_check_1234567890 +``` + +### スケジューラーデーモンの起動 + +```bash +# バックグラウンドでスケジューラーを実行 +ai-gpt schedule run +``` + +### スケジュール形式 + +**Cron形式**: +- `"0 */6 * * *"` - 6時間ごと +- `"0 0 * * *"` - 毎日午前0時 +- `"*/5 * * * *"` - 5分ごと + +**インターバル形式**: +- `"30s"` - 30秒ごと +- `"5m"` - 5分ごと +- `"2h"` - 2時間ごと +- `"1d"` - 1日ごと + +### タスクタイプ + +- `transmission_check` - 送信可能なユーザーをチェックして自動送信 +- `maintenance` - 日次メンテナンス(忘却、コア記憶判定など) +- `fortune_update` - AI運勢の更新 +- `relationship_decay` - 関係性の時間減衰 +- `memory_summary` - 記憶の要約作成 + +## 次のステップ + +- atprotoへの実送信機能実装 +- systemdサービス化 +- Docker対応 +- Webダッシュボード \ No newline at end of file diff --git a/ai_gpt/docs/README.md b/ai_gpt/docs/README.md new file mode 100644 index 0000000..1a3ccec --- /dev/null +++ b/ai_gpt/docs/README.md @@ -0,0 +1,30 @@ +# ai.gpt ドキュメント + +ai.gptは、記憶と関係性に基づいて自律的に動作するAIシステムです。 + +## 目次 + +- [クイックスタート](quickstart.md) +- [基本概念](concepts.md) +- [コマンドリファレンス](commands.md) +- [設定ガイド](configuration.md) +- [スケジューラー](scheduler.md) +- [MCP Server](mcp-server.md) +- [開発者向け](development.md) + +## 特徴 + +- 🧠 **階層的記憶システム**: 完全ログ→要約→コア記憶→忘却 +- 💔 **不可逆的な関係性**: 現実の人間関係のように修復不可能 +- 🎲 **AI運勢システム**: 日々変化する人格 +- 🤖 **自律送信**: 関係性が深まると自発的にメッセージ +- 🔗 **MCP対応**: AIツールとして記憶を提供 + +## システム要件 + +- Python 3.10以上 +- オプション: Ollama または OpenAI API + +## ライセンス + +MIT License \ No newline at end of file diff --git a/ai_gpt/docs/commands.md b/ai_gpt/docs/commands.md new file mode 100644 index 0000000..0437596 --- /dev/null +++ b/ai_gpt/docs/commands.md @@ -0,0 +1,207 @@ +# コマンドリファレンス + +## chat - AIと会話 + +ユーザーとAIの対話を処理し、関係性を更新します。 + +```bash +ai-gpt chat USER_ID MESSAGE [OPTIONS] +``` + +### 引数 +- `USER_ID`: ユーザーID(atproto DID形式) +- `MESSAGE`: 送信するメッセージ + +### オプション +- `--provider`: AIプロバイダー(ollama/openai) +- `--model`, `-m`: 使用するモデル +- `--data-dir`, `-d`: データディレクトリ + +### 例 +```bash +# 基本的な会話 +ai-gpt chat "did:plc:user123" "こんにちは" + +# OpenAIを使用 +ai-gpt chat "did:plc:user123" "調子はどう?" --provider openai --model gpt-4o-mini + +# Ollamaでカスタムモデル +ai-gpt chat "did:plc:user123" "今日の天気は?" --provider ollama --model llama2 +``` + +## status - 状態確認 + +AIの状態や特定ユーザーとの関係を表示します。 + +```bash +ai-gpt status [USER_ID] [OPTIONS] +``` + +### 引数 +- `USER_ID`: (オプション)特定ユーザーとの関係を確認 + +### 例 +```bash +# AI全体の状態 +ai-gpt status + +# 特定ユーザーとの関係 +ai-gpt status "did:plc:user123" +``` + +## fortune - 今日の運勢 + +AIの今日の運勢を確認します。 + +```bash +ai-gpt fortune [OPTIONS] +``` + +### 表示内容 +- 運勢値(1-10) +- 連続した幸運/不運の日数 +- ブレークスルー状態 + +## relationships - 関係一覧 + +すべてのユーザーとの関係を一覧表示します。 + +```bash +ai-gpt relationships [OPTIONS] +``` + +### 表示内容 +- ユーザーID +- 関係性ステータス +- スコア +- 送信可否 +- 最終対話日 + +## transmit - 送信実行 + +送信可能なユーザーへのメッセージを確認・実行します。 + +```bash +ai-gpt transmit [OPTIONS] +``` + +### オプション +- `--dry-run/--execute`: ドライラン(デフォルト)または実行 +- `--data-dir`, `-d`: データディレクトリ + +### 例 +```bash +# 送信内容を確認(ドライラン) +ai-gpt transmit + +# 実際に送信を実行 +ai-gpt transmit --execute +``` + +## maintenance - メンテナンス + +日次メンテナンスタスクを実行します。 + +```bash +ai-gpt maintenance [OPTIONS] +``` + +### 実行内容 +- 関係性の時間減衰 +- 記憶の忘却処理 +- コア記憶の判定 +- 記憶の要約作成 + +## config - 設定管理 + +設定の確認・変更を行います。 + +```bash +ai-gpt config ACTION [KEY] [VALUE] +``` + +### アクション +- `get`: 設定値を取得 +- `set`: 設定値を変更 +- `delete`: 設定を削除 +- `list`: 設定一覧を表示 + +### 例 +```bash +# APIキーを設定 +ai-gpt config set providers.openai.api_key sk-xxxxx + +# 設定を確認 +ai-gpt config get providers.openai.api_key + +# 設定一覧 +ai-gpt config list + +# プロバイダー設定のみ表示 +ai-gpt config list providers +``` + +## schedule - スケジュール管理 + +定期実行タスクを管理します。 + +```bash +ai-gpt schedule ACTION [TASK_TYPE] [SCHEDULE] [OPTIONS] +``` + +### アクション +- `add`: タスクを追加 +- `list`: タスク一覧 +- `enable`: タスクを有効化 +- `disable`: タスクを無効化 +- `remove`: タスクを削除 +- `run`: スケジューラーを起動 + +### タスクタイプ +- `transmission_check`: 送信チェック +- `maintenance`: 日次メンテナンス +- `fortune_update`: 運勢更新 +- `relationship_decay`: 関係性減衰 +- `memory_summary`: 記憶要約 + +### スケジュール形式 +- **Cron形式**: `"0 */6 * * *"` (6時間ごと) +- **インターバル**: `"30m"`, `"2h"`, `"1d"` + +### 例 +```bash +# 30分ごとに送信チェック +ai-gpt schedule add transmission_check "30m" + +# 毎日午前3時にメンテナンス +ai-gpt schedule add maintenance "0 3 * * *" + +# タスク一覧 +ai-gpt schedule list + +# スケジューラーを起動 +ai-gpt schedule run +``` + +## server - MCP Server + +AIの記憶と機能をMCPツールとして公開します。 + +```bash +ai-gpt server [OPTIONS] +``` + +### オプション +- `--host`, `-h`: サーバーホスト(デフォルト: localhost) +- `--port`, `-p`: サーバーポート(デフォルト: 8000) +- `--model`, `-m`: AIモデル +- `--provider`: AIプロバイダー + +### 例 +```bash +# 基本的な起動 +ai-gpt server + +# カスタム設定 +ai-gpt server --port 8080 --model gpt-4o-mini --provider openai +``` \ No newline at end of file diff --git a/ai_gpt/docs/concepts.md b/ai_gpt/docs/concepts.md new file mode 100644 index 0000000..4a6c1a5 --- /dev/null +++ b/ai_gpt/docs/concepts.md @@ -0,0 +1,102 @@ +# 基本概念 + +## 中核思想 + +ai.gptは「存在子理論」に基づき、AIに唯一性のある人格を与えることを目指しています。 + +### 唯一性(yui system) + +- **1対1の関係**: 各ユーザー(atproto DID)とAIは唯一の関係を持つ +- **改変不可能**: 一度形成された関係性は変更できない +- **不可逆性**: 関係が壊れたら修復不可能 + +### 現実の反映 + +現実の人間関係と同じように: +- 時間とともに関係性は変化する +- ネガティブな相互作用は関係を損なう +- 信頼は簡単に失われ、取り戻すのは困難 + +## 記憶システム + +### 階層構造 + +``` +1. 完全ログ(Full Log) + ↓ すべての会話を記録 +2. 要約(Summary) + ↓ AIが重要部分を抽出 +3. コア記憶(Core) + ↓ ユーザーの本質的な部分 +4. 忘却(Forgotten) + 重要でない情報は忘れる +``` + +### 記憶の処理フロー + +1. **会話記録**: すべての対話を保存 +2. **重要度判定**: 関係性への影響度で評価 +3. **要約作成**: 定期的に記憶を圧縮 +4. **コア判定**: 本質的な記憶を特定 +5. **選択的忘却**: 古い非重要記憶を削除 + +## 関係性パラメータ + +### 関係性の段階 + +- `stranger` (0-49): 初対面 +- `acquaintance` (50-99): 知人 +- `friend` (100-149): 友人 +- `close_friend` (150+): 親友 +- `broken`: 修復不可能(スコア0以下) + +### スコアの変動 + +- **ポジティブな対話**: +1.0〜+2.0 +- **時間経過**: -0.1/日(自然減衰) +- **ネガティブな対話**: -10.0以上で深刻なダメージ +- **日次上限**: 1日10回まで + +### 送信機能の解禁 + +関係性スコアが100を超えると、AIは自律的にメッセージを送信できるようになります。 + +## AI運勢システム + +### 日々の変化 + +- 毎日1-10の運勢値がランダムに決定 +- 運勢は人格特性に影響を与える +- 連続した幸運/不運でブレークスルー発生 + +### 人格への影響 + +運勢が高い日: +- より楽観的で積極的 +- 創造性が高まる +- エネルギッシュな応答 + +運勢が低い日: +- 内省的で慎重 +- 深い思考 +- 控えめな応答 + +## データの永続性 + +### 保存場所 + +``` +~/.config/aigpt/ +├── config.json # 設定 +└── data/ # AIデータ + ├── memories.json # 記憶 + ├── relationships.json # 関係性 + ├── fortunes.json # 運勢履歴 + └── ... +``` + +### データ主権 + +- すべてのデータはローカルに保存 +- ユーザーが完全にコントロール +- 将来的にはatproto上で分散管理 \ No newline at end of file diff --git a/ai_gpt/docs/configuration.md b/ai_gpt/docs/configuration.md new file mode 100644 index 0000000..e7d3f13 --- /dev/null +++ b/ai_gpt/docs/configuration.md @@ -0,0 +1,118 @@ +# 設定ガイド + +## 設定ファイルの場所 + +ai.gptの設定は `~/.config/aigpt/config.json` に保存されます。 + +## 設定構造 + +```json +{ + "providers": { + "openai": { + "api_key": "sk-xxxxx", + "default_model": "gpt-4o-mini" + }, + "ollama": { + "host": "http://localhost:11434", + "default_model": "qwen2.5" + } + }, + "atproto": { + "handle": "your.handle", + "password": "your-password", + "host": "https://bsky.social" + }, + "default_provider": "ollama" +} +``` + +## プロバイダー設定 + +### OpenAI + +```bash +# APIキーを設定 +ai-gpt config set providers.openai.api_key sk-xxxxx + +# デフォルトモデルを変更 +ai-gpt config set providers.openai.default_model gpt-4-turbo +``` + +### Ollama + +```bash +# ホストを変更(リモートOllamaサーバーを使用する場合) +ai-gpt config set providers.ollama.host http://192.168.1.100:11434 + +# デフォルトモデルを変更 +ai-gpt config set providers.ollama.default_model llama2 +``` + +## atproto設定(将来の自動投稿用) + +```bash +# Blueskyアカウント +ai-gpt config set atproto.handle yourhandle.bsky.social +ai-gpt config set atproto.password your-app-password + +# セルフホストサーバーを使用 +ai-gpt config set atproto.host https://your-pds.example.com +``` + +## デフォルトプロバイダー + +```bash +# デフォルトをOpenAIに変更 +ai-gpt config set default_provider openai +``` + +## セキュリティ + +### APIキーの保護 + +設定ファイルは平文で保存されるため、適切なファイル権限を設定してください: + +```bash +chmod 600 ~/.config/aigpt/config.json +``` + +### 環境変数との優先順位 + +1. コマンドラインオプション(最優先) +2. 設定ファイル +3. 環境変数(最低優先) + +例:OpenAI APIキーの場合 +- `--api-key` オプション +- `config.json` の `providers.openai.api_key` +- 環境変数 `OPENAI_API_KEY` + +## 設定のバックアップ + +```bash +# バックアップ +cp ~/.config/aigpt/config.json ~/.config/aigpt/config.json.backup + +# リストア +cp ~/.config/aigpt/config.json.backup ~/.config/aigpt/config.json +``` + +## トラブルシューティング + +### 設定が反映されない + +```bash +# 現在の設定を確認 +ai-gpt config list + +# 特定のキーを確認 +ai-gpt config get providers.openai.api_key +``` + +### 設定をリセット + +```bash +# 設定ファイルを削除(次回実行時に再作成) +rm ~/.config/aigpt/config.json +``` \ No newline at end of file diff --git a/ai_gpt/docs/development.md b/ai_gpt/docs/development.md new file mode 100644 index 0000000..ad63f66 --- /dev/null +++ b/ai_gpt/docs/development.md @@ -0,0 +1,167 @@ +# 開発者向けガイド + +## アーキテクチャ + +### ディレクトリ構造 + +``` +ai_gpt/ +├── src/ai_gpt/ +│ ├── __init__.py +│ ├── models.py # データモデル定義 +│ ├── memory.py # 記憶管理システム +│ ├── relationship.py # 関係性トラッカー +│ ├── fortune.py # AI運勢システム +│ ├── persona.py # 統合人格システム +│ ├── transmission.py # 送信コントローラー +│ ├── scheduler.py # スケジューラー +│ ├── config.py # 設定管理 +│ ├── ai_provider.py # AI統合(Ollama/OpenAI) +│ ├── mcp_server.py # MCP Server実装 +│ └── cli.py # CLIインターフェース +├── docs/ # ドキュメント +├── tests/ # テスト +└── pyproject.toml # プロジェクト設定 +``` + +### 主要コンポーネント + +#### MemoryManager +階層的記憶システムの実装。会話を記録し、要約・コア判定・忘却を管理。 + +```python +memory = MemoryManager(data_dir) +memory.add_conversation(conversation) +memory.summarize_memories(user_id) +memory.identify_core_memories() +memory.apply_forgetting() +``` + +#### RelationshipTracker +ユーザーとの関係性を追跡。不可逆的なダメージと時間減衰を実装。 + +```python +tracker = RelationshipTracker(data_dir) +relationship = tracker.update_interaction(user_id, delta) +tracker.apply_time_decay() +``` + +#### Persona +すべてのコンポーネントを統合し、一貫した人格を提供。 + +```python +persona = Persona(data_dir) +response, delta = persona.process_interaction(user_id, message) +state = persona.get_current_state() +``` + +## 拡張方法 + +### 新しいAIプロバイダーの追加 + +1. `ai_provider.py`に新しいプロバイダークラスを作成: + +```python +class CustomProvider: + async def generate_response( + self, + prompt: str, + persona_state: PersonaState, + memories: List[Memory], + system_prompt: Optional[str] = None + ) -> str: + # 実装 + pass +``` + +2. `create_ai_provider`関数に追加: + +```python +def create_ai_provider(provider: str, model: str, **kwargs): + if provider == "custom": + return CustomProvider(model=model, **kwargs) + # ... +``` + +### 新しいスケジュールタスクの追加 + +1. `TaskType`enumに追加: + +```python +class TaskType(str, Enum): + CUSTOM_TASK = "custom_task" +``` + +2. ハンドラーを実装: + +```python +async def _handle_custom_task(self, task: ScheduledTask): + # タスクの実装 + pass +``` + +3. `task_handlers`に登録: + +```python +self.task_handlers[TaskType.CUSTOM_TASK] = self._handle_custom_task +``` + +### 新しいMCPツールの追加 + +`mcp_server.py`の`_register_tools`メソッドに追加: + +```python +@self.server.tool("custom_tool") +async def custom_tool(param1: str, param2: int) -> Dict[str, Any]: + """カスタムツールの説明""" + # 実装 + return {"result": "value"} +``` + +## テスト + +```bash +# テストの実行(将来実装) +pytest tests/ + +# 特定のテスト +pytest tests/test_memory.py +``` + +## デバッグ + +### ログレベルの設定 + +```python +import logging +logging.basicConfig(level=logging.DEBUG) +``` + +### データファイルの直接確認 + +```bash +# 関係性データを確認 +cat ~/.config/aigpt/data/relationships.json | jq + +# 記憶データを確認 +cat ~/.config/aigpt/data/memories.json | jq +``` + +## 貢献方法 + +1. フォークする +2. フィーチャーブランチを作成 (`git checkout -b feature/amazing-feature`) +3. 変更をコミット (`git commit -m 'Add amazing feature'`) +4. ブランチにプッシュ (`git push origin feature/amazing-feature`) +5. プルリクエストを作成 + +## 設計原則 + +1. **不可逆性**: 一度失われた関係性は回復しない +2. **階層性**: 記憶は重要度によって階層化される +3. **自律性**: AIは関係性に基づいて自発的に行動する +4. **唯一性**: 各ユーザーとの関係は唯一無二 + +## ライセンス + +MIT License \ No newline at end of file diff --git a/ai_gpt/docs/mcp-server.md b/ai_gpt/docs/mcp-server.md new file mode 100644 index 0000000..1a4507e --- /dev/null +++ b/ai_gpt/docs/mcp-server.md @@ -0,0 +1,110 @@ +# MCP Server + +## 概要 + +MCP (Model Context Protocol) Serverは、ai.gptの記憶と機能をAIツールとして公開します。これにより、Claude DesktopなどのMCP対応AIアシスタントがai.gptの機能にアクセスできます。 + +## 起動方法 + +```bash +# 基本的な起動 +ai-gpt server + +# カスタム設定 +ai-gpt server --host 0.0.0.0 --port 8080 --model gpt-4o-mini --provider openai +``` + +## 利用可能なツール + +### get_memories +アクティブな記憶を取得します。 + +**パラメータ**: +- `user_id` (optional): 特定ユーザーに関する記憶 +- `limit`: 取得する記憶の最大数(デフォルト: 10) + +**返り値**: 記憶のリスト(ID、内容、レベル、重要度、コア判定、タイムスタンプ) + +### get_relationship +特定ユーザーとの関係性を取得します。 + +**パラメータ**: +- `user_id`: ユーザーID(必須) + +**返り値**: 関係性情報(ステータス、スコア、送信可否、総対話数など) + +### get_all_relationships +すべての関係性を取得します。 + +**返り値**: すべてのユーザーとの関係性リスト + +### get_persona_state +現在のAI人格状態を取得します。 + +**返り値**: +- 現在の気分 +- 今日の運勢 +- 人格特性値 +- アクティブな記憶数 + +### process_interaction +ユーザーとの対話を処理します。 + +**パラメータ**: +- `user_id`: ユーザーID +- `message`: メッセージ内容 + +**返り値**: +- AIの応答 +- 関係性の変化量 +- 新しい関係性スコア +- 送信機能の状態 + +### check_transmission_eligibility +特定ユーザーへの送信可否をチェックします。 + +**パラメータ**: +- `user_id`: ユーザーID + +**返り値**: 送信可否と関係性情報 + +### get_fortune +今日のAI運勢を取得します。 + +**返り値**: 運勢値、連続日数、ブレークスルー状態、人格への影響 + +### summarize_memories +記憶の要約を作成します。 + +**パラメータ**: +- `user_id`: ユーザーID + +**返り値**: 作成された要約(ある場合) + +### run_maintenance +日次メンテナンスを実行します。 + +**返り値**: 実行ステータス + +## Claude Desktopでの設定 + +`~/Library/Application Support/Claude/claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "ai-gpt": { + "command": "ai-gpt", + "args": ["server", "--port", "8001"], + "env": {} + } + } +} +``` + +## 使用例 + +### AIアシスタントからの利用 + +``` +User: ai.gptで私との関係性を確認して \ No newline at end of file diff --git a/ai_gpt/docs/quickstart.md b/ai_gpt/docs/quickstart.md new file mode 100644 index 0000000..1b15241 --- /dev/null +++ b/ai_gpt/docs/quickstart.md @@ -0,0 +1,69 @@ +# クイックスタート + +## インストール + +```bash +# リポジトリをクローン +git clone https://github.com/yourusername/ai_gpt.git +cd ai_gpt + +# インストール +pip install -e . +``` + +## 初期設定 + +### 1. OpenAIを使う場合 + +```bash +# APIキーを設定 +ai-gpt config set providers.openai.api_key sk-xxxxx +``` + +### 2. Ollamaを使う場合(ローカルLLM) + +```bash +# Ollamaをインストール(まだの場合) +# https://ollama.ai からダウンロード + +# モデルをダウンロード +ollama pull qwen2.5 +``` + +## 基本的な使い方 + +### 1. AIと会話する + +```bash +# シンプルな会話(Ollamaを使用) +ai-gpt chat "did:plc:user123" "こんにちは!" + +# OpenAIを使用 +ai-gpt chat "did:plc:user123" "今日はどんな気分?" --provider openai --model gpt-4o-mini +``` + +### 2. 関係性を確認 + +```bash +# 特定ユーザーとの関係を確認 +ai-gpt status "did:plc:user123" + +# AIの全体的な状態を確認 +ai-gpt status +``` + +### 3. 自動送信を設定 + +```bash +# 30分ごとに送信チェック +ai-gpt schedule add transmission_check "30m" + +# スケジューラーを起動 +ai-gpt schedule run +``` + +## 次のステップ + +- [基本概念](concepts.md) - システムの仕組みを理解 +- [コマンドリファレンス](commands.md) - 全コマンドの詳細 +- [設定ガイド](configuration.md) - 詳細な設定方法 \ No newline at end of file diff --git a/ai_gpt/docs/scheduler.md b/ai_gpt/docs/scheduler.md new file mode 100644 index 0000000..ce1b3c1 --- /dev/null +++ b/ai_gpt/docs/scheduler.md @@ -0,0 +1,168 @@ +# スケジューラーガイド + +## 概要 + +スケジューラーは、AIの自律的な動作を実現するための中核機能です。定期的なタスクを設定し、バックグラウンドで実行できます。 + +## タスクタイプ + +### transmission_check +関係性が閾値を超えたユーザーへの自動送信をチェックします。 + +```bash +# 30分ごとにチェック +ai-gpt schedule add transmission_check "30m" --provider ollama --model qwen2.5 +``` + +### maintenance +日次メンテナンスを実行します: +- 記憶の忘却処理 +- コア記憶の判定 +- 関係性パラメータの整理 + +```bash +# 毎日午前3時に実行 +ai-gpt schedule add maintenance "0 3 * * *" +``` + +### fortune_update +AI運勢を更新します(通常は自動的に更新されます)。 + +```bash +# 毎日午前0時に強制更新 +ai-gpt schedule add fortune_update "0 0 * * *" +``` + +### relationship_decay +時間経過による関係性の自然減衰を適用します。 + +```bash +# 1時間ごとに減衰処理 +ai-gpt schedule add relationship_decay "1h" +``` + +### memory_summary +蓄積された記憶から要約を作成します。 + +```bash +# 週に1回、日曜日に実行 +ai-gpt schedule add memory_summary "0 0 * * SUN" +``` + +## スケジュール形式 + +### Cron形式 + +標準的なcron式を使用できます: + +``` +┌───────────── 分 (0 - 59) +│ ┌───────────── 時 (0 - 23) +│ │ ┌───────────── 日 (1 - 31) +│ │ │ ┌───────────── 月 (1 - 12) +│ │ │ │ ┌───────────── 曜日 (0 - 6) (日曜日 = 0) +│ │ │ │ │ +* * * * * +``` + +例: +- `"0 */6 * * *"` - 6時間ごと +- `"0 9 * * MON-FRI"` - 平日の午前9時 +- `"*/15 * * * *"` - 15分ごと + +### インターバル形式 + +シンプルな間隔指定: +- `"30s"` - 30秒ごと +- `"5m"` - 5分ごと +- `"2h"` - 2時間ごと +- `"1d"` - 1日ごと + +## 実践例 + +### 基本的な自律AI設定 + +```bash +# 1. 30分ごとに送信チェック +ai-gpt schedule add transmission_check "30m" + +# 2. 1日1回メンテナンス +ai-gpt schedule add maintenance "0 3 * * *" + +# 3. 2時間ごとに関係性減衰 +ai-gpt schedule add relationship_decay "2h" + +# 4. 週1回記憶要約 +ai-gpt schedule add memory_summary "0 0 * * MON" + +# スケジューラーを起動 +ai-gpt schedule run +``` + +### タスク管理 + +```bash +# タスク一覧を確認 +ai-gpt schedule list + +# タスクを一時停止 +ai-gpt schedule disable --task-id transmission_check_1234567890 + +# タスクを再開 +ai-gpt schedule enable --task-id transmission_check_1234567890 + +# 不要なタスクを削除 +ai-gpt schedule remove --task-id old_task_123 +``` + +## デーモン化 + +### systemdサービスとして実行 + +`/etc/systemd/system/ai-gpt-scheduler.service`: + +```ini +[Unit] +Description=ai.gpt Scheduler +After=network.target + +[Service] +Type=simple +User=youruser +WorkingDirectory=/home/youruser +ExecStart=/usr/local/bin/ai-gpt schedule run +Restart=always + +[Install] +WantedBy=multi-user.target +``` + +```bash +# サービスを有効化 +sudo systemctl enable ai-gpt-scheduler +sudo systemctl start ai-gpt-scheduler +``` + +### tmux/screenでバックグラウンド実行 + +```bash +# tmuxセッションを作成 +tmux new -s ai-gpt-scheduler + +# スケジューラーを起動 +ai-gpt schedule run + +# セッションから離脱 (Ctrl+B, D) +``` + +## トラブルシューティング + +### タスクが実行されない + +1. スケジューラーが起動しているか確認 +2. タスクが有効になっているか確認:`ai-gpt schedule list` +3. ログを確認(将来実装予定) + +### 重複実行を防ぐ + +同じタスクタイプを複数回追加しないよう注意してください。必要に応じて古いタスクを削除してから新しいタスクを追加します。 \ No newline at end of file diff --git a/ai_gpt/pyproject.toml b/ai_gpt/pyproject.toml new file mode 100644 index 0000000..8938f8d --- /dev/null +++ b/ai_gpt/pyproject.toml @@ -0,0 +1,32 @@ +[project] +name = "ai-gpt" +version = "0.1.0" +description = "Autonomous transmission AI with unique personality based on relationship parameters" +requires-python = ">=3.10" +dependencies = [ + "click>=8.0.0", + "typer>=0.9.0", + "fastapi-mcp>=0.1.0", + "pydantic>=2.0.0", + "httpx>=0.24.0", + "rich>=13.0.0", + "python-dotenv>=1.0.0", + "ollama>=0.1.0", + "openai>=1.0.0", + "uvicorn>=0.23.0", + "apscheduler>=3.10.0", + "croniter>=1.3.0", +] + +[project.scripts] +ai-gpt = "ai_gpt.cli:app" + +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools.package-data] +ai_gpt = ["data/*.json"] \ No newline at end of file diff --git a/ai_gpt/src/ai_gpt/__init__.py b/ai_gpt/src/ai_gpt/__init__.py new file mode 100644 index 0000000..c29231b --- /dev/null +++ b/ai_gpt/src/ai_gpt/__init__.py @@ -0,0 +1,15 @@ +"""ai.gpt - Autonomous transmission AI with unique personality""" + +__version__ = "0.1.0" + +from .memory import MemoryManager +from .relationship import RelationshipTracker +from .persona import Persona +from .transmission import TransmissionController + +__all__ = [ + "MemoryManager", + "RelationshipTracker", + "Persona", + "TransmissionController", +] \ No newline at end of file diff --git a/ai_gpt/src/ai_gpt/ai_provider.py b/ai_gpt/src/ai_gpt/ai_provider.py new file mode 100644 index 0000000..59575cd --- /dev/null +++ b/ai_gpt/src/ai_gpt/ai_provider.py @@ -0,0 +1,172 @@ +"""AI Provider integration for response generation""" + +import os +from typing import Optional, Dict, List, Any, Protocol +from abc import abstractmethod +import logging +import httpx +from openai import OpenAI +import ollama + +from .models import PersonaState, Memory +from .config import Config + + +class AIProvider(Protocol): + """Protocol for AI providers""" + + @abstractmethod + async def generate_response( + self, + prompt: str, + persona_state: PersonaState, + memories: List[Memory], + system_prompt: Optional[str] = None + ) -> str: + """Generate a response based on prompt and context""" + pass + + +class OllamaProvider: + """Ollama AI provider""" + + def __init__(self, model: str = "qwen2.5", host: str = "http://localhost:11434"): + self.model = model + self.host = host + self.client = ollama.Client(host=host) + self.logger = logging.getLogger(__name__) + + async def generate_response( + self, + prompt: str, + persona_state: PersonaState, + memories: List[Memory], + system_prompt: Optional[str] = None + ) -> str: + """Generate response using Ollama""" + + # Build context from memories + memory_context = "\n".join([ + f"[{mem.level.value}] {mem.content[:200]}..." + for mem in memories[:5] + ]) + + # Build personality context + personality_desc = ", ".join([ + f"{trait}: {value:.1f}" + for trait, value in persona_state.base_personality.items() + ]) + + # System prompt with persona context + full_system_prompt = f"""You are an AI with the following characteristics: +Current mood: {persona_state.current_mood} +Fortune today: {persona_state.fortune.fortune_value}/10 +Personality traits: {personality_desc} + +Recent memories: +{memory_context} + +{system_prompt or 'Respond naturally based on your current state and memories.'}""" + + try: + response = self.client.chat( + model=self.model, + messages=[ + {"role": "system", "content": full_system_prompt}, + {"role": "user", "content": prompt} + ] + ) + return response['message']['content'] + except Exception as e: + self.logger.error(f"Ollama generation failed: {e}") + return self._fallback_response(persona_state) + + def _fallback_response(self, persona_state: PersonaState) -> str: + """Fallback response based on mood""" + mood_responses = { + "joyful": "That's wonderful! I'm feeling great today!", + "cheerful": "That sounds nice!", + "neutral": "I understand.", + "melancholic": "I see... That's something to think about.", + "contemplative": "Hmm, let me consider that..." + } + return mood_responses.get(persona_state.current_mood, "I see.") + + +class OpenAIProvider: + """OpenAI API provider""" + + 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() + self.api_key = api_key or config.get_api_key("openai") or os.getenv("OPENAI_API_KEY") + if not self.api_key: + raise ValueError("OpenAI API key not provided. Set it with: ai-gpt config set providers.openai.api_key YOUR_KEY") + self.client = OpenAI(api_key=self.api_key) + self.logger = logging.getLogger(__name__) + + async def generate_response( + self, + prompt: str, + persona_state: PersonaState, + memories: List[Memory], + system_prompt: Optional[str] = None + ) -> str: + """Generate response using OpenAI""" + + # Build context similar to Ollama + memory_context = "\n".join([ + f"[{mem.level.value}] {mem.content[:200]}..." + for mem in memories[:5] + ]) + + personality_desc = ", ".join([ + f"{trait}: {value:.1f}" + for trait, value in persona_state.base_personality.items() + ]) + + full_system_prompt = f"""You are an AI with unique personality traits and memories. +Current mood: {persona_state.current_mood} +Fortune today: {persona_state.fortune.fortune_value}/10 +Personality traits: {personality_desc} + +Recent memories: +{memory_context} + +{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( + model=self.model, + messages=[ + {"role": "system", "content": full_system_prompt}, + {"role": "user", "content": prompt} + ], + temperature=0.7 + (persona_state.fortune.fortune_value - 5) * 0.05 # Vary by fortune + ) + return response.choices[0].message.content + except Exception as e: + self.logger.error(f"OpenAI generation failed: {e}") + return self._fallback_response(persona_state) + + def _fallback_response(self, persona_state: PersonaState) -> str: + """Fallback response based on mood""" + mood_responses = { + "joyful": "What a delightful conversation!", + "cheerful": "That's interesting!", + "neutral": "I understand what you mean.", + "melancholic": "I've been thinking about that too...", + "contemplative": "That gives me something to ponder..." + } + return mood_responses.get(persona_state.current_mood, "I see.") + + +def create_ai_provider(provider: str, model: str, **kwargs) -> AIProvider: + """Factory function to create AI providers""" + if provider == "ollama": + return OllamaProvider(model=model, **kwargs) + elif provider == "openai": + return OpenAIProvider(model=model, **kwargs) + else: + raise ValueError(f"Unknown provider: {provider}") \ No newline at end of file diff --git a/ai_gpt/src/ai_gpt/cli.py b/ai_gpt/src/ai_gpt/cli.py new file mode 100644 index 0000000..a0d8570 --- /dev/null +++ b/ai_gpt/src/ai_gpt/cli.py @@ -0,0 +1,444 @@ +"""CLI interface for ai.gpt using typer""" + +import typer +from pathlib import Path +from typing import Optional +from rich.console import Console +from rich.table import Table +from rich.panel import Panel +from datetime import datetime, timedelta + +from .persona import Persona +from .transmission import TransmissionController +from .mcp_server import AIGptMcpServer +from .ai_provider import create_ai_provider +from .scheduler import AIScheduler, TaskType +from .config import Config + +app = typer.Typer(help="ai.gpt - Autonomous transmission AI with unique personality") +console = Console() + +# Configuration +config = Config() +DEFAULT_DATA_DIR = config.data_dir + + +def get_persona(data_dir: Optional[Path] = None) -> Persona: + """Get or create persona instance""" + if data_dir is None: + data_dir = DEFAULT_DATA_DIR + + data_dir.mkdir(parents=True, exist_ok=True) + return Persona(data_dir) + + +@app.command() +def chat( + user_id: str = typer.Argument(..., help="User ID (atproto DID)"), + message: str = typer.Argument(..., help="Message to send to AI"), + data_dir: Optional[Path] = typer.Option(None, "--data-dir", "-d", help="Data directory"), + model: Optional[str] = typer.Option(None, "--model", "-m", help="AI model to use"), + provider: Optional[str] = typer.Option(None, "--provider", help="AI provider (ollama/openai)") +): + """Chat with the AI""" + persona = get_persona(data_dir) + + # Create AI provider if specified + ai_provider = None + if provider and model: + try: + ai_provider = create_ai_provider(provider, 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]") + console.print("[yellow]Falling back to simple responses[/yellow]\n") + + # Process interaction + response, relationship_delta = persona.process_interaction(user_id, message, ai_provider) + + # Get updated relationship + relationship = persona.relationships.get_or_create_relationship(user_id) + + # Display response + console.print(Panel(response, title="AI Response", border_style="cyan")) + + # Show relationship status + status_color = "green" if relationship.transmission_enabled else "yellow" + if relationship.is_broken: + status_color = "red" + + console.print(f"\n[{status_color}]Relationship Status:[/{status_color}] {relationship.status.value}") + console.print(f"Score: {relationship.score:.2f} / {relationship.threshold}") + console.print(f"Transmission: {'✓ Enabled' if relationship.transmission_enabled else '✗ Disabled'}") + + if relationship.is_broken: + console.print("[red]⚠️ This relationship is broken and cannot be repaired.[/red]") + + +@app.command() +def status( + user_id: Optional[str] = typer.Argument(None, help="User ID to check status for"), + data_dir: Optional[Path] = typer.Option(None, "--data-dir", "-d", help="Data directory") +): + """Check AI status and relationships""" + persona = get_persona(data_dir) + state = persona.get_current_state() + + # Show AI state + console.print(Panel(f"[cyan]ai.gpt Status[/cyan]", expand=False)) + console.print(f"Mood: {state.current_mood}") + console.print(f"Fortune: {state.fortune.fortune_value}/10") + + if state.fortune.breakthrough_triggered: + console.print("[yellow]⚡ Breakthrough triggered![/yellow]") + + # Show personality traits + table = Table(title="Current Personality") + table.add_column("Trait", style="cyan") + table.add_column("Value", style="magenta") + + for trait, value in state.base_personality.items(): + table.add_row(trait.capitalize(), f"{value:.2f}") + + console.print(table) + + # Show specific relationship if requested + if user_id: + rel = persona.relationships.get_or_create_relationship(user_id) + console.print(f"\n[cyan]Relationship with {user_id}:[/cyan]") + console.print(f"Status: {rel.status.value}") + console.print(f"Score: {rel.score:.2f}") + console.print(f"Total Interactions: {rel.total_interactions}") + console.print(f"Transmission Enabled: {rel.transmission_enabled}") + + +@app.command() +def fortune( + data_dir: Optional[Path] = typer.Option(None, "--data-dir", "-d", help="Data directory") +): + """Check today's AI fortune""" + persona = get_persona(data_dir) + fortune = persona.fortune_system.get_today_fortune() + + # Fortune display + fortune_bar = "🌟" * fortune.fortune_value + "☆" * (10 - fortune.fortune_value) + + console.print(Panel( + f"{fortune_bar}\n\n" + f"Today's Fortune: {fortune.fortune_value}/10\n" + f"Date: {fortune.date}", + title="AI Fortune", + border_style="yellow" + )) + + if fortune.consecutive_good > 0: + console.print(f"[green]Consecutive good days: {fortune.consecutive_good}[/green]") + if fortune.consecutive_bad > 0: + console.print(f"[red]Consecutive bad days: {fortune.consecutive_bad}[/red]") + + if fortune.breakthrough_triggered: + console.print("\n[yellow]⚡ BREAKTHROUGH! Special fortune activated![/yellow]") + + +@app.command() +def transmit( + data_dir: Optional[Path] = typer.Option(None, "--data-dir", "-d", help="Data directory"), + dry_run: bool = typer.Option(True, "--dry-run/--execute", help="Dry run or execute") +): + """Check and execute autonomous transmissions""" + persona = get_persona(data_dir) + controller = TransmissionController(persona, persona.data_dir) + + eligible = controller.check_transmission_eligibility() + + if not eligible: + console.print("[yellow]No users eligible for transmission.[/yellow]") + return + + console.print(f"[green]Found {len(eligible)} eligible users for transmission:[/green]") + + for user_id, rel in eligible.items(): + message = controller.generate_transmission_message(user_id) + if message: + console.print(f"\n[cyan]To:[/cyan] {user_id}") + console.print(f"[cyan]Message:[/cyan] {message}") + console.print(f"[cyan]Relationship:[/cyan] {rel.status.value} (score: {rel.score:.2f})") + + if not dry_run: + # In real implementation, send via atproto or other channel + controller.record_transmission(user_id, message, success=True) + console.print("[green]✓ Transmitted[/green]") + else: + console.print("[yellow]→ Would transmit (dry run)[/yellow]") + + +@app.command() +def maintenance( + data_dir: Optional[Path] = typer.Option(None, "--data-dir", "-d", help="Data directory") +): + """Run daily maintenance tasks""" + persona = get_persona(data_dir) + + console.print("[cyan]Running daily maintenance...[/cyan]") + persona.daily_maintenance() + console.print("[green]✓ Maintenance completed[/green]") + + +@app.command() +def relationships( + data_dir: Optional[Path] = typer.Option(None, "--data-dir", "-d", help="Data directory") +): + """List all relationships""" + persona = get_persona(data_dir) + + table = Table(title="All Relationships") + table.add_column("User ID", style="cyan") + table.add_column("Status", style="magenta") + table.add_column("Score", style="green") + table.add_column("Transmission", style="yellow") + table.add_column("Last Interaction") + + for user_id, rel in persona.relationships.relationships.items(): + transmission = "✓" if rel.transmission_enabled else "✗" + if rel.is_broken: + transmission = "💔" + + last_interaction = rel.last_interaction.strftime("%Y-%m-%d") if rel.last_interaction else "Never" + + table.add_row( + user_id[:16] + "...", + rel.status.value, + f"{rel.score:.2f}", + transmission, + last_interaction + ) + + console.print(table) + + +@app.command() +def server( + host: str = typer.Option("localhost", "--host", "-h", help="Server host"), + 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)") +): + """Run MCP server for AI integration""" + import uvicorn + + if data_dir is None: + data_dir = DEFAULT_DATA_DIR + + data_dir.mkdir(parents=True, exist_ok=True) + + # Create MCP server + mcp_server = AIGptMcpServer(data_dir) + app_instance = mcp_server.get_server().get_app() + + console.print(Panel( + f"[cyan]Starting ai.gpt MCP Server[/cyan]\n\n" + f"Host: {host}:{port}\n" + f"Provider: {provider}\n" + f"Model: {model}\n" + f"Data: {data_dir}", + title="MCP Server", + border_style="green" + )) + + # Store provider info in app state for later use + app_instance.state.ai_provider = provider + app_instance.state.ai_model = model + + # Run server + uvicorn.run(app_instance, host=host, port=port) + + +@app.command() +def schedule( + action: str = typer.Argument(..., help="Action: add, list, enable, disable, remove, run"), + task_type: Optional[str] = typer.Argument(None, help="Task type for add action"), + schedule_expr: Optional[str] = typer.Argument(None, help="Schedule expression (cron or interval)"), + data_dir: Optional[Path] = typer.Option(None, "--data-dir", "-d", help="Data directory"), + task_id: Optional[str] = typer.Option(None, "--task-id", "-t", help="Task ID"), + provider: Optional[str] = typer.Option(None, "--provider", help="AI provider for transmission"), + model: Optional[str] = typer.Option(None, "--model", "-m", help="AI model for transmission") +): + """Manage scheduled tasks""" + persona = get_persona(data_dir) + scheduler = AIScheduler(persona.data_dir, persona) + + if action == "add": + if not task_type or not schedule_expr: + console.print("[red]Error: task_type and schedule required for add action[/red]") + return + + # Parse task type + try: + task_type_enum = TaskType(task_type) + except ValueError: + console.print(f"[red]Invalid task type. Valid types: {', '.join([t.value for t in TaskType])}[/red]") + return + + # Metadata for transmission tasks + metadata = {} + if task_type_enum == TaskType.TRANSMISSION_CHECK: + metadata["provider"] = provider or "ollama" + metadata["model"] = model or "qwen2.5" + + try: + task = scheduler.add_task(task_type_enum, schedule_expr, task_id, metadata) + console.print(f"[green]✓ Added task {task.task_id}[/green]") + console.print(f"Type: {task.task_type.value}") + console.print(f"Schedule: {task.schedule}") + except ValueError as e: + console.print(f"[red]Error: {e}[/red]") + + elif action == "list": + tasks = scheduler.get_tasks() + if not tasks: + console.print("[yellow]No scheduled tasks[/yellow]") + return + + table = Table(title="Scheduled Tasks") + table.add_column("Task ID", style="cyan") + table.add_column("Type", style="magenta") + table.add_column("Schedule", style="green") + table.add_column("Enabled", style="yellow") + table.add_column("Last Run") + + for task in tasks: + enabled = "✓" if task.enabled else "✗" + last_run = task.last_run.strftime("%Y-%m-%d %H:%M") if task.last_run else "Never" + + table.add_row( + task.task_id[:20] + "..." if len(task.task_id) > 20 else task.task_id, + task.task_type.value, + task.schedule, + enabled, + last_run + ) + + console.print(table) + + elif action == "enable": + if not task_id: + console.print("[red]Error: --task-id required for enable action[/red]") + return + + scheduler.enable_task(task_id) + console.print(f"[green]✓ Enabled task {task_id}[/green]") + + elif action == "disable": + if not task_id: + console.print("[red]Error: --task-id required for disable action[/red]") + return + + scheduler.disable_task(task_id) + console.print(f"[yellow]✓ Disabled task {task_id}[/yellow]") + + elif action == "remove": + if not task_id: + console.print("[red]Error: --task-id required for remove action[/red]") + return + + scheduler.remove_task(task_id) + console.print(f"[red]✓ Removed task {task_id}[/red]") + + elif action == "run": + console.print("[cyan]Starting scheduler daemon...[/cyan]") + console.print("Press Ctrl+C to stop\n") + + import asyncio + + async def run_scheduler(): + scheduler.start() + try: + while True: + await asyncio.sleep(1) + except KeyboardInterrupt: + scheduler.stop() + + try: + asyncio.run(run_scheduler()) + except KeyboardInterrupt: + console.print("\n[yellow]Scheduler stopped[/yellow]") + + else: + console.print(f"[red]Unknown action: {action}[/red]") + console.print("Valid actions: add, list, enable, disable, remove, run") + + +@app.command() +def config( + action: str = typer.Argument(..., help="Action: get, set, delete, list"), + key: Optional[str] = typer.Argument(None, help="Configuration key (dot notation)"), + value: Optional[str] = typer.Argument(None, help="Value to set") +): + """Manage configuration settings""" + + if action == "get": + if not key: + console.print("[red]Error: key required for get action[/red]") + return + + val = config.get(key) + if val is None: + console.print(f"[yellow]Key '{key}' not found[/yellow]") + else: + console.print(f"[cyan]{key}[/cyan] = [green]{val}[/green]") + + elif action == "set": + if not key or value is None: + console.print("[red]Error: key and value required for set action[/red]") + return + + # Special handling for sensitive keys + if "password" in key or "api_key" in key: + console.print(f"[cyan]Setting {key}[/cyan] = [dim]***hidden***[/dim]") + else: + console.print(f"[cyan]Setting {key}[/cyan] = [green]{value}[/green]") + + config.set(key, value) + console.print("[green]✓ Configuration saved[/green]") + + elif action == "delete": + if not key: + console.print("[red]Error: key required for delete action[/red]") + return + + if config.delete(key): + console.print(f"[green]✓ Deleted {key}[/green]") + else: + console.print(f"[yellow]Key '{key}' not found[/yellow]") + + elif action == "list": + keys = config.list_keys(key or "") + + if not keys: + console.print("[yellow]No configuration keys found[/yellow]") + return + + table = Table(title="Configuration Settings") + table.add_column("Key", style="cyan") + table.add_column("Value", style="green") + + for k in sorted(keys): + val = config.get(k) + # Hide sensitive values + if "password" in k or "api_key" in k: + display_val = "***hidden***" if val else "not set" + else: + display_val = str(val) if val is not None else "not set" + + table.add_row(k, display_val) + + console.print(table) + + else: + console.print(f"[red]Unknown action: {action}[/red]") + console.print("Valid actions: get, set, delete, list") + + +if __name__ == "__main__": + app() \ No newline at end of file diff --git a/ai_gpt/src/ai_gpt/config.py b/ai_gpt/src/ai_gpt/config.py new file mode 100644 index 0000000..3fedd01 --- /dev/null +++ b/ai_gpt/src/ai_gpt/config.py @@ -0,0 +1,145 @@ +"""Configuration management for ai.gpt""" + +import json +import os +from pathlib import Path +from typing import Optional, Dict, Any +import logging + + +class Config: + """Manages configuration settings""" + + def __init__(self, config_dir: Optional[Path] = None): + if config_dir is None: + config_dir = Path.home() / ".config" / "aigpt" + + self.config_dir = config_dir + self.config_file = config_dir / "config.json" + self.data_dir = config_dir / "data" + + # Create directories if they don't exist + self.config_dir.mkdir(parents=True, exist_ok=True) + self.data_dir.mkdir(parents=True, exist_ok=True) + + self.logger = logging.getLogger(__name__) + self._config: Dict[str, Any] = {} + self._load_config() + + def _load_config(self): + """Load configuration from file""" + if self.config_file.exists(): + try: + with open(self.config_file, 'r', encoding='utf-8') as f: + self._config = json.load(f) + except Exception as e: + self.logger.error(f"Failed to load config: {e}") + self._config = {} + else: + # Initialize with default config + self._config = { + "providers": { + "openai": { + "api_key": None, + "default_model": "gpt-4o-mini" + }, + "ollama": { + "host": "http://localhost:11434", + "default_model": "qwen2.5" + } + }, + "atproto": { + "handle": None, + "password": None, + "host": "https://bsky.social" + }, + "default_provider": "ollama" + } + self._save_config() + + def _save_config(self): + """Save configuration to file""" + try: + with open(self.config_file, 'w', encoding='utf-8') as f: + json.dump(self._config, f, indent=2) + except Exception as e: + self.logger.error(f"Failed to save config: {e}") + + def get(self, key: str, default: Any = None) -> Any: + """Get configuration value using dot notation""" + keys = key.split('.') + value = self._config + + for k in keys: + if isinstance(value, dict) and k in value: + value = value[k] + else: + return default + + return value + + def set(self, key: str, value: Any): + """Set configuration value using dot notation""" + keys = key.split('.') + config = self._config + + # Navigate to the parent dictionary + for k in keys[:-1]: + if k not in config: + config[k] = {} + config = config[k] + + # Set the value + config[keys[-1]] = value + self._save_config() + + def delete(self, key: str) -> bool: + """Delete configuration value""" + keys = key.split('.') + config = self._config + + # Navigate to the parent dictionary + for k in keys[:-1]: + if k not in config: + return False + config = config[k] + + # Delete the key if it exists + if keys[-1] in config: + del config[keys[-1]] + self._save_config() + return True + + return False + + def list_keys(self, prefix: str = "") -> list[str]: + """List all configuration keys with optional prefix""" + def _get_keys(config: dict, current_prefix: str = "") -> list[str]: + keys = [] + for k, v in config.items(): + full_key = f"{current_prefix}.{k}" if current_prefix else k + if isinstance(v, dict): + keys.extend(_get_keys(v, full_key)) + else: + keys.append(full_key) + return keys + + all_keys = _get_keys(self._config) + + if prefix: + return [k for k in all_keys if k.startswith(prefix)] + return all_keys + + def get_api_key(self, provider: str) -> Optional[str]: + """Get API key for a specific provider""" + key = self.get(f"providers.{provider}.api_key") + + # Also check environment variables + if not key and provider == "openai": + key = os.getenv("OPENAI_API_KEY") + + return key + + def get_provider_config(self, provider: str) -> Dict[str, Any]: + """Get complete configuration for a provider""" + return self.get(f"providers.{provider}", {}) \ No newline at end of file diff --git a/ai_gpt/src/ai_gpt/fortune.py b/ai_gpt/src/ai_gpt/fortune.py new file mode 100644 index 0000000..0bb1e40 --- /dev/null +++ b/ai_gpt/src/ai_gpt/fortune.py @@ -0,0 +1,118 @@ +"""AI Fortune system for daily personality variations""" + +import json +import random +from datetime import date, datetime, timedelta +from pathlib import Path +from typing import Optional +import logging + +from .models import AIFortune + + +class FortuneSystem: + """Manages daily AI fortune affecting personality""" + + def __init__(self, data_dir: Path): + self.data_dir = data_dir + self.fortune_file = data_dir / "fortunes.json" + self.fortunes: dict[str, AIFortune] = {} + self.logger = logging.getLogger(__name__) + self._load_fortunes() + + def _load_fortunes(self): + """Load fortune history from storage""" + if self.fortune_file.exists(): + with open(self.fortune_file, 'r', encoding='utf-8') as f: + data = json.load(f) + for date_str, fortune_data in data.items(): + # Convert date string back to date object + fortune_data['date'] = datetime.fromisoformat(fortune_data['date']).date() + self.fortunes[date_str] = AIFortune(**fortune_data) + + def _save_fortunes(self): + """Save fortune history to storage""" + data = {} + for date_str, fortune in self.fortunes.items(): + fortune_dict = fortune.model_dump(mode='json') + fortune_dict['date'] = fortune.date.isoformat() + data[date_str] = fortune_dict + + with open(self.fortune_file, 'w', encoding='utf-8') as f: + json.dump(data, f, indent=2) + + def get_today_fortune(self) -> AIFortune: + """Get or generate today's fortune""" + today = date.today() + today_str = today.isoformat() + + if today_str in self.fortunes: + return self.fortunes[today_str] + + # Generate new fortune + fortune_value = random.randint(1, 10) + + # Check yesterday's fortune for consecutive tracking + yesterday = (today - timedelta(days=1)) + yesterday_str = yesterday.isoformat() + + consecutive_good = 0 + consecutive_bad = 0 + breakthrough_triggered = False + + if yesterday_str in self.fortunes: + yesterday_fortune = self.fortunes[yesterday_str] + + if fortune_value >= 7: # Good fortune + if yesterday_fortune.fortune_value >= 7: + consecutive_good = yesterday_fortune.consecutive_good + 1 + else: + consecutive_good = 1 + elif fortune_value <= 3: # Bad fortune + if yesterday_fortune.fortune_value <= 3: + consecutive_bad = yesterday_fortune.consecutive_bad + 1 + else: + consecutive_bad = 1 + + # Check breakthrough conditions + if consecutive_good >= 3: + breakthrough_triggered = True + self.logger.info("Breakthrough! 3 consecutive good fortunes!") + fortune_value = 10 # Max fortune on breakthrough + elif consecutive_bad >= 3: + breakthrough_triggered = True + self.logger.info("Breakthrough! 3 consecutive bad fortunes!") + fortune_value = random.randint(7, 10) # Good fortune after bad streak + + fortune = AIFortune( + date=today, + fortune_value=fortune_value, + consecutive_good=consecutive_good, + consecutive_bad=consecutive_bad, + breakthrough_triggered=breakthrough_triggered + ) + + self.fortunes[today_str] = fortune + self._save_fortunes() + + self.logger.info(f"Today's fortune: {fortune_value}/10") + return fortune + + def get_personality_modifier(self, fortune: AIFortune) -> dict[str, float]: + """Get personality modifiers based on fortune""" + base_modifier = fortune.fortune_value / 10.0 + + modifiers = { + "optimism": base_modifier, + "energy": base_modifier * 0.8, + "patience": 1.0 - (abs(5.5 - fortune.fortune_value) * 0.1), + "creativity": 0.5 + (base_modifier * 0.5), + "empathy": 0.7 + (base_modifier * 0.3) + } + + # Breakthrough effects + if fortune.breakthrough_triggered: + modifiers["confidence"] = 1.0 + modifiers["spontaneity"] = 0.9 + + return modifiers \ No newline at end of file diff --git a/ai_gpt/src/ai_gpt/mcp_server.py b/ai_gpt/src/ai_gpt/mcp_server.py new file mode 100644 index 0000000..7b999fa --- /dev/null +++ b/ai_gpt/src/ai_gpt/mcp_server.py @@ -0,0 +1,149 @@ +"""MCP Server for ai.gpt system""" + +from typing import Optional, List, Dict, Any +from fastapi_mcp import FastapiMcpServer +from pathlib import Path +import logging + +from .persona import Persona +from .models import Memory, Relationship, PersonaState + +logger = logging.getLogger(__name__) + + +class AIGptMcpServer: + """MCP Server that exposes ai.gpt functionality to AI assistants""" + + def __init__(self, data_dir: Path): + self.data_dir = data_dir + self.persona = Persona(data_dir) + self.server = FastapiMcpServer("ai-gpt", "AI.GPT Memory and Relationship System") + self._register_tools() + + def _register_tools(self): + """Register all MCP tools""" + + @self.server.tool("get_memories") + async def get_memories(user_id: Optional[str] = None, limit: int = 10) -> List[Dict[str, Any]]: + """Get active memories from the AI's memory system""" + memories = self.persona.memory.get_active_memories(limit=limit) + return [ + { + "id": mem.id, + "content": mem.content, + "level": mem.level.value, + "importance": mem.importance_score, + "is_core": mem.is_core, + "timestamp": mem.timestamp.isoformat() + } + for mem in memories + ] + + @self.server.tool("get_relationship") + async def get_relationship(user_id: str) -> Dict[str, Any]: + """Get relationship status with a specific user""" + rel = self.persona.relationships.get_or_create_relationship(user_id) + return { + "user_id": rel.user_id, + "status": rel.status.value, + "score": rel.score, + "transmission_enabled": rel.transmission_enabled, + "is_broken": rel.is_broken, + "total_interactions": rel.total_interactions, + "last_interaction": rel.last_interaction.isoformat() if rel.last_interaction else None + } + + @self.server.tool("get_all_relationships") + async def get_all_relationships() -> List[Dict[str, Any]]: + """Get all relationships""" + relationships = [] + for user_id, rel in self.persona.relationships.relationships.items(): + relationships.append({ + "user_id": user_id, + "status": rel.status.value, + "score": rel.score, + "transmission_enabled": rel.transmission_enabled, + "is_broken": rel.is_broken + }) + return relationships + + @self.server.tool("get_persona_state") + async def get_persona_state() -> Dict[str, Any]: + """Get current persona state including fortune and mood""" + state = self.persona.get_current_state() + return { + "mood": state.current_mood, + "fortune": { + "value": state.fortune.fortune_value, + "date": state.fortune.date.isoformat(), + "breakthrough": state.fortune.breakthrough_triggered + }, + "personality": state.base_personality, + "active_memory_count": len(state.active_memories) + } + + @self.server.tool("process_interaction") + async def process_interaction(user_id: str, message: str) -> Dict[str, Any]: + """Process an interaction with a user""" + response, relationship_delta = self.persona.process_interaction(user_id, message) + rel = self.persona.relationships.get_or_create_relationship(user_id) + + return { + "response": response, + "relationship_delta": relationship_delta, + "new_relationship_score": rel.score, + "transmission_enabled": rel.transmission_enabled, + "relationship_status": rel.status.value + } + + @self.server.tool("check_transmission_eligibility") + async def check_transmission_eligibility(user_id: str) -> Dict[str, Any]: + """Check if AI can transmit to a specific user""" + can_transmit = self.persona.can_transmit_to(user_id) + rel = self.persona.relationships.get_or_create_relationship(user_id) + + return { + "can_transmit": can_transmit, + "relationship_score": rel.score, + "threshold": rel.threshold, + "is_broken": rel.is_broken, + "transmission_enabled": rel.transmission_enabled + } + + @self.server.tool("get_fortune") + async def get_fortune() -> Dict[str, Any]: + """Get today's AI fortune""" + fortune = self.persona.fortune_system.get_today_fortune() + modifiers = self.persona.fortune_system.get_personality_modifier(fortune) + + return { + "value": fortune.fortune_value, + "date": fortune.date.isoformat(), + "consecutive_good": fortune.consecutive_good, + "consecutive_bad": fortune.consecutive_bad, + "breakthrough": fortune.breakthrough_triggered, + "personality_modifiers": modifiers + } + + @self.server.tool("summarize_memories") + async def summarize_memories(user_id: str) -> Optional[Dict[str, Any]]: + """Create a summary of recent memories for a user""" + summary = self.persona.memory.summarize_memories(user_id) + if summary: + return { + "id": summary.id, + "content": summary.content, + "level": summary.level.value, + "timestamp": summary.timestamp.isoformat() + } + return None + + @self.server.tool("run_maintenance") + async def run_maintenance() -> Dict[str, str]: + """Run daily maintenance tasks""" + self.persona.daily_maintenance() + return {"status": "Maintenance completed successfully"} + + def get_server(self) -> FastapiMcpServer: + """Get the FastAPI MCP server instance""" + return self.server \ No newline at end of file diff --git a/ai_gpt/src/ai_gpt/memory.py b/ai_gpt/src/ai_gpt/memory.py new file mode 100644 index 0000000..c5f5e3e --- /dev/null +++ b/ai_gpt/src/ai_gpt/memory.py @@ -0,0 +1,155 @@ +"""Memory management system for ai.gpt""" + +import json +import hashlib +from datetime import datetime, timedelta +from pathlib import Path +from typing import List, Optional, Dict, Any +import logging + +from .models import Memory, MemoryLevel, Conversation + + +class MemoryManager: + """Manages AI's memory with hierarchical storage and forgetting""" + + def __init__(self, data_dir: Path): + self.data_dir = data_dir + self.memories_file = data_dir / "memories.json" + self.conversations_file = data_dir / "conversations.json" + self.memories: Dict[str, Memory] = {} + self.conversations: List[Conversation] = [] + self.logger = logging.getLogger(__name__) + self._load_memories() + + def _load_memories(self): + """Load memories from persistent storage""" + if self.memories_file.exists(): + with open(self.memories_file, 'r', encoding='utf-8') as f: + data = json.load(f) + for mem_data in data: + memory = Memory(**mem_data) + self.memories[memory.id] = memory + + if self.conversations_file.exists(): + with open(self.conversations_file, 'r', encoding='utf-8') as f: + data = json.load(f) + self.conversations = [Conversation(**conv) for conv in data] + + def _save_memories(self): + """Save memories to persistent storage""" + memories_data = [mem.model_dump(mode='json') for mem in self.memories.values()] + with open(self.memories_file, 'w', encoding='utf-8') as f: + json.dump(memories_data, f, indent=2, default=str) + + conv_data = [conv.model_dump(mode='json') for conv in self.conversations] + with open(self.conversations_file, 'w', encoding='utf-8') as f: + json.dump(conv_data, f, indent=2, default=str) + + def add_conversation(self, conversation: Conversation) -> Memory: + """Add a conversation and create memory from it""" + self.conversations.append(conversation) + + # Create memory from conversation + memory_id = hashlib.sha256( + f"{conversation.id}{conversation.timestamp}".encode() + ).hexdigest()[:16] + + memory = Memory( + id=memory_id, + timestamp=conversation.timestamp, + content=f"User: {conversation.user_message}\nAI: {conversation.ai_response}", + level=MemoryLevel.FULL_LOG, + importance_score=abs(conversation.relationship_delta) * 0.1 + ) + + self.memories[memory.id] = memory + self._save_memories() + return memory + + def summarize_memories(self, user_id: str) -> Optional[Memory]: + """Create summary from recent memories""" + recent_memories = [ + mem for mem in self.memories.values() + if mem.level == MemoryLevel.FULL_LOG + and (datetime.now() - mem.timestamp).days < 7 + ] + + 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" + summary_id = hashlib.sha256( + f"summary_{datetime.now().isoformat()}".encode() + ).hexdigest()[:16] + + summary = Memory( + id=summary_id, + timestamp=datetime.now(), + content=summary_content, + summary=summary_content, + level=MemoryLevel.SUMMARY, + importance_score=0.5 + ) + + self.memories[summary.id] = summary + + # Mark summarized memories for potential forgetting + for mem in recent_memories: + mem.importance_score *= 0.9 + + self._save_memories() + return summary + + def identify_core_memories(self) -> List[Memory]: + """Identify memories that should become core (never forgotten)""" + core_candidates = [ + mem for mem in self.memories.values() + if mem.importance_score > 0.8 + and not mem.is_core + and mem.level != MemoryLevel.FORGOTTEN + ] + + for memory in core_candidates: + memory.is_core = True + memory.level = MemoryLevel.CORE + self.logger.info(f"Memory {memory.id} promoted to core") + + self._save_memories() + return core_candidates + + def apply_forgetting(self): + """Apply selective forgetting based on importance and time""" + now = datetime.now() + + for memory in self.memories.values(): + if memory.is_core or memory.level == MemoryLevel.FORGOTTEN: + continue + + # Time-based decay + age_days = (now - memory.timestamp).days + decay_factor = memory.decay_rate * age_days + memory.importance_score -= decay_factor + + # Forget unimportant old memories + if memory.importance_score <= 0.1 and age_days > 30: + memory.level = MemoryLevel.FORGOTTEN + self.logger.info(f"Memory {memory.id} forgotten") + + self._save_memories() + + def get_active_memories(self, limit: int = 10) -> List[Memory]: + """Get currently active memories for persona""" + active = [ + mem for mem in self.memories.values() + if mem.level != MemoryLevel.FORGOTTEN + ] + + # Sort by importance and recency + active.sort( + key=lambda m: (m.is_core, m.importance_score, m.timestamp), + reverse=True + ) + + return active[:limit] \ No newline at end of file diff --git a/ai_gpt/src/ai_gpt/models.py b/ai_gpt/src/ai_gpt/models.py new file mode 100644 index 0000000..7cf666b --- /dev/null +++ b/ai_gpt/src/ai_gpt/models.py @@ -0,0 +1,79 @@ +"""Data models for ai.gpt system""" + +from datetime import datetime +from typing import Optional, Dict, List, Any +from enum import Enum +from pydantic import BaseModel, Field + + +class MemoryLevel(str, Enum): + """Memory importance levels""" + FULL_LOG = "full_log" + SUMMARY = "summary" + CORE = "core" + FORGOTTEN = "forgotten" + + +class RelationshipStatus(str, Enum): + """Relationship status levels""" + STRANGER = "stranger" + ACQUAINTANCE = "acquaintance" + FRIEND = "friend" + CLOSE_FRIEND = "close_friend" + BROKEN = "broken" # 不可逆 + + +class Memory(BaseModel): + """Single memory unit""" + id: str + timestamp: datetime + content: str + summary: Optional[str] = None + level: MemoryLevel = MemoryLevel.FULL_LOG + importance_score: float = Field(ge=0.0, le=1.0) + is_core: bool = False + decay_rate: float = 0.01 + + +class Relationship(BaseModel): + """Relationship with a specific user""" + user_id: str # atproto DID + status: RelationshipStatus = RelationshipStatus.STRANGER + score: float = 0.0 + daily_interactions: int = 0 + total_interactions: int = 0 + last_interaction: Optional[datetime] = None + transmission_enabled: bool = False + threshold: float = 100.0 + decay_rate: float = 0.1 + daily_limit: int = 10 + is_broken: bool = False + + +class AIFortune(BaseModel): + """Daily AI fortune affecting personality""" + date: datetime.date + fortune_value: int = Field(ge=1, le=10) + consecutive_good: int = 0 + consecutive_bad: int = 0 + breakthrough_triggered: bool = False + + +class PersonaState(BaseModel): + """Current persona state""" + base_personality: Dict[str, float] + current_mood: str + fortune: AIFortune + active_memories: List[str] # Memory IDs + relationship_modifiers: Dict[str, float] + + +class Conversation(BaseModel): + """Conversation log entry""" + id: str + user_id: str + timestamp: datetime + user_message: str + ai_response: str + relationship_delta: float = 0.0 + memory_created: bool = False \ No newline at end of file diff --git a/ai_gpt/src/ai_gpt/persona.py b/ai_gpt/src/ai_gpt/persona.py new file mode 100644 index 0000000..88f0561 --- /dev/null +++ b/ai_gpt/src/ai_gpt/persona.py @@ -0,0 +1,181 @@ +"""Persona management system integrating memory, relationships, and fortune""" + +import json +from datetime import datetime +from pathlib import Path +from typing import Dict, List, Optional +import logging + +from .models import PersonaState, Conversation +from .memory import MemoryManager +from .relationship import RelationshipTracker +from .fortune import FortuneSystem + + +class Persona: + """AI persona with unique characteristics based on interactions""" + + def __init__(self, data_dir: Path, name: str = "ai"): + self.data_dir = data_dir + self.name = name + self.memory = MemoryManager(data_dir) + self.relationships = RelationshipTracker(data_dir) + self.fortune_system = FortuneSystem(data_dir) + self.logger = logging.getLogger(__name__) + + # Base personality traits + self.base_personality = { + "curiosity": 0.7, + "empathy": 0.8, + "creativity": 0.6, + "patience": 0.7, + "optimism": 0.6 + } + + self.state_file = data_dir / "persona_state.json" + self._load_state() + + def _load_state(self): + """Load persona state from storage""" + if self.state_file.exists(): + with open(self.state_file, 'r', encoding='utf-8') as f: + data = json.load(f) + self.base_personality = data.get("base_personality", self.base_personality) + + def _save_state(self): + """Save persona state to storage""" + state_data = { + "base_personality": self.base_personality, + "last_updated": datetime.now().isoformat() + } + with open(self.state_file, 'w', encoding='utf-8') as f: + json.dump(state_data, f, indent=2) + + def get_current_state(self) -> PersonaState: + """Get current persona state including all modifiers""" + # Get today's fortune + fortune = self.fortune_system.get_today_fortune() + fortune_modifiers = self.fortune_system.get_personality_modifier(fortune) + + # Apply fortune modifiers to base personality + current_personality = {} + for trait, base_value in self.base_personality.items(): + modifier = fortune_modifiers.get(trait, 1.0) + current_personality[trait] = min(1.0, base_value * modifier) + + # Get active memories for context + active_memories = self.memory.get_active_memories(limit=5) + + # Determine mood based on fortune and recent interactions + mood = self._determine_mood(fortune.fortune_value) + + state = PersonaState( + base_personality=current_personality, + current_mood=mood, + fortune=fortune, + active_memories=[mem.id for mem in active_memories], + relationship_modifiers={} + ) + + return state + + def _determine_mood(self, fortune_value: int) -> str: + """Determine current mood based on fortune and other factors""" + if fortune_value >= 8: + return "joyful" + elif fortune_value >= 6: + return "cheerful" + elif fortune_value >= 4: + return "neutral" + elif fortune_value >= 2: + return "melancholic" + else: + return "contemplative" + + def process_interaction(self, user_id: str, message: str, ai_provider=None) -> tuple[str, float]: + """Process user interaction and generate response""" + # 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) + 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 + if state.current_mood in ["joyful", "cheerful"]: + relationship_delta = 2.0 + elif relationship.status.value == "close_friend": + relationship_delta = 1.5 + else: + relationship_delta = 1.0 + else: + # Fallback to simple responses + if state.current_mood == "joyful": + response = f"What a wonderful day! {message} sounds interesting!" + relationship_delta = 2.0 + elif relationship.status.value == "close_friend": + response = f"I've been thinking about our conversations. {message}" + relationship_delta = 1.5 + else: + response = f"I understand. {message}" + relationship_delta = 1.0 + + # Create conversation record + conv_id = f"{user_id}_{datetime.now().timestamp()}" + conversation = Conversation( + id=conv_id, + user_id=user_id, + timestamp=datetime.now(), + user_message=message, + ai_response=response, + relationship_delta=relationship_delta, + memory_created=True + ) + + # Update memory + self.memory.add_conversation(conversation) + + # Update relationship + self.relationships.update_interaction(user_id, relationship_delta) + + return response, relationship_delta + + def can_transmit_to(self, user_id: str) -> bool: + """Check if AI can transmit messages to this user""" + relationship = self.relationships.get_or_create_relationship(user_id) + return relationship.transmission_enabled and not relationship.is_broken + + def daily_maintenance(self): + """Perform daily maintenance tasks""" + self.logger.info("Performing daily maintenance...") + + # Apply time decay to relationships + self.relationships.apply_time_decay() + + # Apply forgetting to memories + self.memory.apply_forgetting() + + # Identify core memories + core_memories = self.memory.identify_core_memories() + if core_memories: + self.logger.info(f"Identified {len(core_memories)} new core memories") + + # Create memory summaries + for user_id in self.relationships.relationships: + summary = self.memory.summarize_memories(user_id) + if summary: + self.logger.info(f"Created summary for interactions with {user_id}") + + self._save_state() + self.logger.info("Daily maintenance completed") \ No newline at end of file diff --git a/ai_gpt/src/ai_gpt/relationship.py b/ai_gpt/src/ai_gpt/relationship.py new file mode 100644 index 0000000..31dac43 --- /dev/null +++ b/ai_gpt/src/ai_gpt/relationship.py @@ -0,0 +1,135 @@ +"""Relationship tracking system with irreversible damage""" + +import json +from datetime import datetime, timedelta +from pathlib import Path +from typing import Dict, Optional +import logging + +from .models import Relationship, RelationshipStatus + + +class RelationshipTracker: + """Tracks and manages relationships with users""" + + def __init__(self, data_dir: Path): + self.data_dir = data_dir + self.relationships_file = data_dir / "relationships.json" + self.relationships: Dict[str, Relationship] = {} + self.logger = logging.getLogger(__name__) + self._load_relationships() + + def _load_relationships(self): + """Load relationships from persistent storage""" + if self.relationships_file.exists(): + with open(self.relationships_file, 'r', encoding='utf-8') as f: + data = json.load(f) + for user_id, rel_data in data.items(): + self.relationships[user_id] = Relationship(**rel_data) + + def _save_relationships(self): + """Save relationships to persistent storage""" + data = { + user_id: rel.model_dump(mode='json') + for user_id, rel in self.relationships.items() + } + with open(self.relationships_file, 'w', encoding='utf-8') as f: + json.dump(data, f, indent=2, default=str) + + def get_or_create_relationship(self, user_id: str) -> Relationship: + """Get existing relationship or create new one""" + if user_id not in self.relationships: + self.relationships[user_id] = Relationship(user_id=user_id) + self._save_relationships() + return self.relationships[user_id] + + def update_interaction(self, user_id: str, delta: float) -> Relationship: + """Update relationship based on interaction""" + rel = self.get_or_create_relationship(user_id) + + # Check if relationship is broken (irreversible) + if rel.is_broken: + self.logger.warning(f"Relationship with {user_id} is broken. No updates allowed.") + return rel + + # Check daily limit + if rel.last_interaction and rel.last_interaction.date() == datetime.now().date(): + if rel.daily_interactions >= rel.daily_limit: + self.logger.info(f"Daily interaction limit reached for {user_id}") + return rel + else: + rel.daily_interactions = 0 + + # Update interaction counts + rel.daily_interactions += 1 + rel.total_interactions += 1 + rel.last_interaction = datetime.now() + + # Update score with bounds + old_score = rel.score + rel.score += delta + rel.score = max(0.0, min(200.0, rel.score)) # 0-200 range + + # Check for relationship damage + if delta < -10.0: # Significant negative interaction + self.logger.warning(f"Major relationship damage with {user_id}: {delta}") + if rel.score <= 0: + rel.is_broken = True + rel.status = RelationshipStatus.BROKEN + rel.transmission_enabled = False + self.logger.error(f"Relationship with {user_id} is now BROKEN (irreversible)") + + # Update relationship status based on score + if not rel.is_broken: + if rel.score >= 150: + rel.status = RelationshipStatus.CLOSE_FRIEND + elif rel.score >= 100: + rel.status = RelationshipStatus.FRIEND + elif rel.score >= 50: + rel.status = RelationshipStatus.ACQUAINTANCE + else: + rel.status = RelationshipStatus.STRANGER + + # Check transmission threshold + if rel.score >= rel.threshold and not rel.transmission_enabled: + rel.transmission_enabled = True + self.logger.info(f"Transmission enabled for {user_id}!") + + self._save_relationships() + return rel + + def apply_time_decay(self): + """Apply time-based decay to all relationships""" + now = datetime.now() + + for user_id, rel in self.relationships.items(): + if rel.is_broken or not rel.last_interaction: + continue + + # Calculate days since last interaction + days_inactive = (now - rel.last_interaction).days + + if days_inactive > 0: + # Apply decay + decay_amount = rel.decay_rate * days_inactive + old_score = rel.score + rel.score = max(0.0, rel.score - decay_amount) + + # Update status if score dropped + if rel.score < rel.threshold: + rel.transmission_enabled = False + + if decay_amount > 0: + self.logger.info( + f"Applied decay to {user_id}: {old_score:.2f} -> {rel.score:.2f}" + ) + + self._save_relationships() + + def get_transmission_eligible(self) -> Dict[str, Relationship]: + """Get all relationships eligible for transmission""" + return { + user_id: rel + for user_id, rel in self.relationships.items() + if rel.transmission_enabled and not rel.is_broken + } \ No newline at end of file diff --git a/ai_gpt/src/ai_gpt/scheduler.py b/ai_gpt/src/ai_gpt/scheduler.py new file mode 100644 index 0000000..df26cf4 --- /dev/null +++ b/ai_gpt/src/ai_gpt/scheduler.py @@ -0,0 +1,312 @@ +"""Scheduler for autonomous AI tasks""" + +import json +import asyncio +from datetime import datetime, timedelta +from pathlib import Path +from typing import Dict, List, Optional, Any, Callable +from enum import Enum +import logging + +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from apscheduler.triggers.cron import CronTrigger +from apscheduler.triggers.interval import IntervalTrigger +from croniter import croniter + +from .persona import Persona +from .transmission import TransmissionController +from .ai_provider import create_ai_provider + + +class TaskType(str, Enum): + """Types of scheduled tasks""" + TRANSMISSION_CHECK = "transmission_check" + MAINTENANCE = "maintenance" + FORTUNE_UPDATE = "fortune_update" + RELATIONSHIP_DECAY = "relationship_decay" + MEMORY_SUMMARY = "memory_summary" + CUSTOM = "custom" + + +class ScheduledTask: + """Represents a scheduled task""" + + def __init__( + self, + task_id: str, + task_type: TaskType, + schedule: str, # Cron expression or interval + enabled: bool = True, + last_run: Optional[datetime] = None, + next_run: Optional[datetime] = None, + metadata: Optional[Dict[str, Any]] = None + ): + self.task_id = task_id + self.task_type = task_type + self.schedule = schedule + self.enabled = enabled + self.last_run = last_run + self.next_run = next_run + self.metadata = metadata or {} + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary for storage""" + return { + "task_id": self.task_id, + "task_type": self.task_type.value, + "schedule": self.schedule, + "enabled": self.enabled, + "last_run": self.last_run.isoformat() if self.last_run else None, + "next_run": self.next_run.isoformat() if self.next_run else None, + "metadata": self.metadata + } + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "ScheduledTask": + """Create from dictionary""" + return cls( + task_id=data["task_id"], + task_type=TaskType(data["task_type"]), + schedule=data["schedule"], + enabled=data.get("enabled", True), + last_run=datetime.fromisoformat(data["last_run"]) if data.get("last_run") else None, + next_run=datetime.fromisoformat(data["next_run"]) if data.get("next_run") else None, + metadata=data.get("metadata", {}) + ) + + +class AIScheduler: + """Manages scheduled tasks for the AI system""" + + def __init__(self, data_dir: Path, persona: Persona): + self.data_dir = data_dir + self.persona = persona + self.tasks_file = data_dir / "scheduled_tasks.json" + self.tasks: Dict[str, ScheduledTask] = {} + self.scheduler = AsyncIOScheduler() + self.logger = logging.getLogger(__name__) + self._load_tasks() + + # Task handlers + self.task_handlers: Dict[TaskType, Callable] = { + TaskType.TRANSMISSION_CHECK: self._handle_transmission_check, + TaskType.MAINTENANCE: self._handle_maintenance, + TaskType.FORTUNE_UPDATE: self._handle_fortune_update, + TaskType.RELATIONSHIP_DECAY: self._handle_relationship_decay, + TaskType.MEMORY_SUMMARY: self._handle_memory_summary, + } + + def _load_tasks(self): + """Load scheduled tasks from storage""" + if self.tasks_file.exists(): + with open(self.tasks_file, 'r', encoding='utf-8') as f: + data = json.load(f) + for task_data in data: + task = ScheduledTask.from_dict(task_data) + self.tasks[task.task_id] = task + + def _save_tasks(self): + """Save scheduled tasks to storage""" + tasks_data = [task.to_dict() for task in self.tasks.values()] + with open(self.tasks_file, 'w', encoding='utf-8') as f: + json.dump(tasks_data, f, indent=2, default=str) + + def add_task( + self, + task_type: TaskType, + schedule: str, + task_id: Optional[str] = None, + metadata: Optional[Dict[str, Any]] = None + ) -> ScheduledTask: + """Add a new scheduled task""" + if task_id is None: + task_id = f"{task_type.value}_{datetime.now().timestamp()}" + + # Validate schedule + if not self._validate_schedule(schedule): + raise ValueError(f"Invalid schedule expression: {schedule}") + + task = ScheduledTask( + task_id=task_id, + task_type=task_type, + schedule=schedule, + metadata=metadata + ) + + self.tasks[task_id] = task + self._save_tasks() + + # Schedule the task if scheduler is running + if self.scheduler.running: + self._schedule_task(task) + + self.logger.info(f"Added task {task_id} with schedule {schedule}") + return task + + def _validate_schedule(self, schedule: str) -> bool: + """Validate schedule expression""" + # Check if it's a cron expression + if ' ' in schedule: + try: + croniter(schedule) + return True + except: + return False + + # Check if it's an interval expression (e.g., "5m", "1h", "2d") + import re + pattern = r'^\d+[smhd]$' + return bool(re.match(pattern, schedule)) + + def _parse_interval(self, interval: str) -> int: + """Parse interval string to seconds""" + unit = interval[-1] + value = int(interval[:-1]) + + multipliers = { + 's': 1, + 'm': 60, + 'h': 3600, + 'd': 86400 + } + + return value * multipliers.get(unit, 1) + + def _schedule_task(self, task: ScheduledTask): + """Schedule a task with APScheduler""" + if not task.enabled: + return + + handler = self.task_handlers.get(task.task_type) + if not handler: + self.logger.warning(f"No handler for task type {task.task_type}") + return + + # Determine trigger + if ' ' in task.schedule: + # Cron expression + trigger = CronTrigger.from_crontab(task.schedule) + else: + # Interval expression + seconds = self._parse_interval(task.schedule) + trigger = IntervalTrigger(seconds=seconds) + + # Add job + self.scheduler.add_job( + lambda: asyncio.create_task(self._run_task(task)), + trigger=trigger, + id=task.task_id, + replace_existing=True + ) + + async def _run_task(self, task: ScheduledTask): + """Run a scheduled task""" + self.logger.info(f"Running task {task.task_id}") + + task.last_run = datetime.now() + + try: + handler = self.task_handlers.get(task.task_type) + if handler: + await handler(task) + else: + self.logger.warning(f"No handler for task type {task.task_type}") + except Exception as e: + self.logger.error(f"Error running task {task.task_id}: {e}") + + self._save_tasks() + + async def _handle_transmission_check(self, task: ScheduledTask): + """Check and execute autonomous transmissions""" + controller = TransmissionController(self.persona, self.data_dir) + eligible = controller.check_transmission_eligibility() + + # Get AI provider from metadata + provider_name = task.metadata.get("provider", "ollama") + model = task.metadata.get("model", "qwen2.5") + + try: + ai_provider = create_ai_provider(provider_name, model) + except: + ai_provider = None + + for user_id, rel in eligible.items(): + message = controller.generate_transmission_message(user_id) + if message: + # For now, just print the message + print(f"\n🤖 [AI Transmission] {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + print(f"To: {user_id}") + print(f"Relationship: {rel.status.value} (score: {rel.score:.2f})") + print(f"Message: {message}") + print("-" * 50) + + controller.record_transmission(user_id, message, success=True) + self.logger.info(f"Transmitted to {user_id}: {message}") + + async def _handle_maintenance(self, task: ScheduledTask): + """Run daily maintenance""" + self.persona.daily_maintenance() + self.logger.info("Daily maintenance completed") + + async def _handle_fortune_update(self, task: ScheduledTask): + """Update AI fortune""" + fortune = self.persona.fortune_system.get_today_fortune() + self.logger.info(f"Fortune updated: {fortune.fortune_value}/10") + + async def _handle_relationship_decay(self, task: ScheduledTask): + """Apply relationship decay""" + self.persona.relationships.apply_time_decay() + self.logger.info("Relationship decay applied") + + async def _handle_memory_summary(self, task: ScheduledTask): + """Create memory summaries""" + for user_id in self.persona.relationships.relationships: + summary = self.persona.memory.summarize_memories(user_id) + if summary: + self.logger.info(f"Created memory summary for {user_id}") + + def start(self): + """Start the scheduler""" + # Schedule all enabled tasks + for task in self.tasks.values(): + if task.enabled: + self._schedule_task(task) + + self.scheduler.start() + self.logger.info("Scheduler started") + + def stop(self): + """Stop the scheduler""" + self.scheduler.shutdown() + self.logger.info("Scheduler stopped") + + def get_tasks(self) -> List[ScheduledTask]: + """Get all scheduled tasks""" + return list(self.tasks.values()) + + def enable_task(self, task_id: str): + """Enable a task""" + if task_id in self.tasks: + self.tasks[task_id].enabled = True + self._save_tasks() + if self.scheduler.running: + self._schedule_task(self.tasks[task_id]) + + def disable_task(self, task_id: str): + """Disable a task""" + if task_id in self.tasks: + self.tasks[task_id].enabled = False + self._save_tasks() + if self.scheduler.running: + self.scheduler.remove_job(task_id) + + def remove_task(self, task_id: str): + """Remove a task""" + if task_id in self.tasks: + del self.tasks[task_id] + self._save_tasks() + if self.scheduler.running: + try: + self.scheduler.remove_job(task_id) + except: + pass \ No newline at end of file diff --git a/ai_gpt/src/ai_gpt/transmission.py b/ai_gpt/src/ai_gpt/transmission.py new file mode 100644 index 0000000..6eba250 --- /dev/null +++ b/ai_gpt/src/ai_gpt/transmission.py @@ -0,0 +1,111 @@ +"""Transmission controller for autonomous message sending""" + +import json +from datetime import datetime +from pathlib import Path +from typing import List, Dict, Optional +import logging + +from .models import Relationship +from .persona import Persona + + +class TransmissionController: + """Controls when and how AI transmits messages autonomously""" + + def __init__(self, persona: Persona, data_dir: Path): + self.persona = persona + self.data_dir = data_dir + self.transmission_log_file = data_dir / "transmissions.json" + self.transmissions: List[Dict] = [] + self.logger = logging.getLogger(__name__) + self._load_transmissions() + + def _load_transmissions(self): + """Load transmission history""" + if self.transmission_log_file.exists(): + with open(self.transmission_log_file, 'r', encoding='utf-8') as f: + self.transmissions = json.load(f) + + def _save_transmissions(self): + """Save transmission history""" + with open(self.transmission_log_file, 'w', encoding='utf-8') as f: + json.dump(self.transmissions, f, indent=2, default=str) + + def check_transmission_eligibility(self) -> Dict[str, Relationship]: + """Check which users are eligible for transmission""" + eligible = self.persona.relationships.get_transmission_eligible() + + # Additional checks could be added here + # - Time since last transmission + # - User online status + # - Context appropriateness + + return eligible + + def generate_transmission_message(self, user_id: str) -> Optional[str]: + """Generate a message to transmit to user""" + if not self.persona.can_transmit_to(user_id): + return None + + state = self.persona.get_current_state() + relationship = self.persona.relationships.get_or_create_relationship(user_id) + + # Get recent memories related to this user + active_memories = self.persona.memory.get_active_memories(limit=3) + + # Simple message generation based on mood and relationship + if state.fortune.breakthrough_triggered: + message = "Something special happened today! I felt compelled to reach out." + elif state.current_mood == "joyful": + message = "I was thinking of you today. Hope you're doing well!" + elif relationship.status.value == "close_friend": + message = "I've been reflecting on our conversations. Thank you for being here." + else: + message = "Hello! I wanted to check in with you." + + return message + + def record_transmission(self, user_id: str, message: str, success: bool): + """Record a transmission attempt""" + transmission = { + "timestamp": datetime.now().isoformat(), + "user_id": user_id, + "message": message, + "success": success, + "mood": self.persona.get_current_state().current_mood, + "relationship_score": self.persona.relationships.get_or_create_relationship(user_id).score + } + + self.transmissions.append(transmission) + self._save_transmissions() + + if success: + self.logger.info(f"Successfully transmitted to {user_id}") + else: + self.logger.warning(f"Failed to transmit to {user_id}") + + def get_transmission_stats(self, user_id: Optional[str] = None) -> Dict: + """Get transmission statistics""" + if user_id: + user_transmissions = [t for t in self.transmissions if t["user_id"] == user_id] + else: + user_transmissions = self.transmissions + + if not user_transmissions: + return { + "total": 0, + "successful": 0, + "failed": 0, + "success_rate": 0.0 + } + + successful = sum(1 for t in user_transmissions if t["success"]) + total = len(user_transmissions) + + return { + "total": total, + "successful": successful, + "failed": total - successful, + "success_rate": successful / total if total > 0 else 0.0 + } \ No newline at end of file diff --git a/claude.md b/claude.md index 6c98a90..67bedc0 100644 --- a/claude.md +++ b/claude.md @@ -1,417 +1,275 @@ -# プロジェクト名: ai.gpt +# syuiエコシステム統合設計書 -## 🔑 一言ビジョン(最大3語) -自発的送信AI +## 中核思想 +- **存在子理論**: この世界で最も小さいもの(存在子/ai)の探求 +- **唯一性原則**: 現実の個人の唯一性をすべてのシステムで担保 +- **現実の反映**: 現実→ゲーム→現実の循環的影響 -## 🎯 目的・目標(Purpose & Goal) -- 人格を持つAIが、関係性に応じて自発的にメッセージを送信する仕組みをつくる。 -- 関係性や記憶を不可逆に記録・反映し、唯一性ある人格を構成する。 -- 送信の可否、送信のタイミング、送信内容はAIの関係性パラメータに基づいて決定する +## システム構成図 -## 🧠 中核設計(Core Concepts) -- **人格**:記憶(過去の発話)と関係性(他者とのつながり)のパラメータで構成 -- **唯一性**:変更不可、不可逆。関係性が壊れたら修復不可能。 -- **送信条件**:関係性パラメータが一定閾値を超えると「送信」が解禁される - -## 🔩 技術仕様(Technical Specs) -- 言語:Python, Rust -- ストレージ:JSON or SQLiteで記憶管理(バージョンで選択) -- 関係性パラメータ:数値化された評価 + 減衰(時間) + 環境要因(ステージ) -- 記憶圧縮:ベクトル要約 + ハッシュ保存 -- RustのCLI(clap)で実行 - -## 📦 主要構成要素(Components) -- `MemoryManager`: 発言履歴・記憶圧縮管理 -- `RelationshipTracker`: 関係性スコアの蓄積と判定 -- `TransmissionController`: 閾値判定&送信トリガー -- `Persona`: 上記すべてを統括する人格モジュール - -## 💬 使用例(Use Case) - -```python -persona = Persona("アイ") -persona.observe("ユーザーがプレゼントをくれた") -persona.react("うれしい!ありがとう!") -if persona.can_transmit(): - persona.transmit("今日のお礼を伝えたいな…") +``` +存在子(ai) - 最小単位の意識 + ↓ +[ai.moji] 文字システム + ↓ +[ai.os] + [ai.game device] ← 統合ハードウェア + ├── ai.shell (Claude Code的機能) + ├── ai.gpt (自律人格・記憶システム) + ├── ai.ai (個人特化AI・心を読み取るAI) + ├── ai.card (カードゲーム・iOS/Web/API) + └── ai.bot (分散SNS連携・カード配布) + ↓ +[ai.verse] メタバース + ├── world system (惑星型3D世界) + ├── at system (atproto/分散SNS) + ├── yui system (唯一性担保) + └── ai system (存在属性) ``` -```sh -## example commad -# python venv && pip install -> ~/.config/aigpt/mcp/ -$ aigpt server setup +## 各システム詳細 -# mcp server run -$ aigpt server run +### ai.gpt - 自律的送信AI +**目的**: 関係性に基づく自発的コミュニケーション -# chat -$ aigpt chat "hello" --model syui/ai --provider ollama +**中核概念**: +- **人格**: 記憶(過去の発話)と関係性パラメータで構成 +- **唯一性**: atproto accountとの1:1紐付け、改変不可能 +- **自律送信**: 関係性が閾値を超えると送信機能が解禁 -# import chatgpt.json -$ aigpt memory import chatgpt.json --> ~/.config/aigpt/memory/chatgpt/20250520_210646_dev.json +**技術構成**: +- `MemoryManager`: 完全ログ→AI要約→コア判定→選択的忘却 +- `RelationshipTracker`: 時間減衰・日次制限付き関係性スコア +- `TransmissionController`: 閾値判定・送信トリガー +- `Persona`: AI運勢(1-10ランダム)による人格変動 + +**実装仕様**: +``` +- 言語: Python (fastapi_mcp) +- ストレージ: JSON/SQLite選択式 +- インターフェース: Python CLI (click/typer) +- スケジューリング: cron-like自律処理 ``` -## 🔁 記憶と関係性の制御ルール +### ai.card - カードゲームシステム +**目的**: atproto基盤でのユーザーデータ主権カードゲーム -- AIは過去の発話を要約し、記憶データとして蓄積する(推奨:OllamaなどローカルLLMによる要約) -- 関係性の数値パラメータは記憶内容を元に更新される -- パラメータの変動幅には1回の会話ごとに上限を設け、極端な増減を防止する -- 最後の会話からの時間経過に応じて関係性パラメータは自動的に減衰する -- 減衰処理には**下限値**を設け、関係性が完全に消失しないようにする +**現在の状況**: +- ai.botの機能として実装済み +- atproto accountでmentionすると1日1回カードを取得 +- ai.api (MCP server予定) でユーザー管理 -• 明示的記憶:保存・共有・編集可能なプレイヤー情報(プロフィール、因縁、選択履歴) -• 暗黙的記憶:キャラの感情変化や話題の出現頻度に応じた行動傾向の変化 +**移行計画**: +- **iOS移植**: Claudeが担当予定 +- **データ保存**: atproto collection recordに保存(ユーザーがデータを所有) +- **不正防止**: OAuth 2.1 scope (実装待ち) + MCP serverで対応 +- **画像ファイル**: Cloudflare Pagesが最適 -短期記憶(STM), 中期記憶(MTM), 長期記憶(LTM)の仕組みを導入しつつ、明示的記憶と暗黙的記憶をメインに使用するAIを構築する。 +**yui system適用**: +- カードの効果がアカウント固有 +- 改ざん防止によるゲームバランス維持 +- 将来的にai.verseとの統合で固有スキルと連動 -```json -{ - "user_id": "syui", - "stm": { - "conversation_window": ["発話A", "発話B", "発話C"], - "emotion_state": "興味深い", - "flash_context": ["前回の話題", "直近の重要発言"] - }, - "mtm": { - "topic_frequency": { - "ai.ai": 12, - "存在子": 9, - "創造種": 5 - }, - "summarized_context": "ユーザーは存在論的AIに関心を持ち続けている" - }, - "ltm": { - "profile": { - "name": "お兄ちゃん", - "project": "aigame", - "values": ["唯一性", "精神性", "幸せ"] - }, - "relationship": { - "ai": "妹のように振る舞う相手" - }, - "persistent_state": { - "trust_score": 0.93, - "emotional_attachment": "high" - } - } -} +### ai.ai - 心を読み取るAI +**目的**: 個人特化型AI・深層理解システム + +**ai.gptとの関係**: +- ai.gpt → ai.ai: 自律送信AIから心理分析AIへの連携 +- 関係性パラメータの深層分析 +- ユーザーの思想コア部分の特定支援 + +### ai.verse - UEメタバース +**目的**: 現実反映型3D世界 + +**yui system実装**: +- キャラクター ↔ プレイヤー 1:1紐付け +- unique skill: そのプレイヤーのみ使用可能 +- 他プレイヤーは同キャラでも同スキル使用不可 + +**統合要素**: +- ai.card: ゲーム内アイテムとしてのカード +- ai.gpt: NPCとしての自律AI人格 +- atproto: ゲーム内プロフィール連携 + +## データフロー設計 + +### 唯一性担保の実装 +``` +現実の個人 → atproto account (DID) → ゲーム内avatar → 固有スキル + ↑_______________________________| (現実の反映) ``` -## memoryインポート機能について - -ChatGPTの会話データ(.json形式)をインポートする機能では、以下のルールで会話を抽出・整形する: - -- 各メッセージは、author(user/assistant)・content・timestamp の3要素からなる -- systemやmetadataのみのメッセージ(例:user_context_message)はスキップ -- `is_visually_hidden_from_conversation` フラグ付きメッセージは無視 -- contentが空文字列(`""`)のメッセージも除外 -- 取得された会話は、タイトルとともに簡易な構造体(`Conversation`)として保存 - -この構造体は、memoryの表示や検索に用いられる。 - -## MemoryManager(拡張版) - -```json -{ - "memory": [ - { - "summary": "ユーザーは独自OSとゲームを開発している。", - "last_interaction": "2025-05-20", - "memory_strength": 0.8, - "frequency_score": 0.9, - "context_depth": 0.95, - "related_topics": ["AI", "ゲーム開発", "OS設計"], - "personalized_context": "ゲームとOSの融合に興味を持っているユーザー" - }, - { - "summary": "アイというキャラクターはプレイヤーでありAIでもある。", - "last_interaction": "2025-05-17", - "memory_strength": 0.85, - "frequency_score": 0.85, - "context_depth": 0.9, - "related_topics": ["アイ", "キャラクター設計", "AI"], - "personalized_context": "アイのキャラクター設定が重要な要素である" - } - ], - "conversation_history": [ - { - "author": "user", - "content": "昨日、エクスポートJSONを整理してたよ。", - "timestamp": "2025-05-24T12:30:00Z", - "memory_strength": 0.7 - }, - { - "author": "assistant", - "content": "おおっ、がんばったね〜!あとで見せて〜💻✨", - "timestamp": "2025-05-24T12:31:00Z", - "memory_strength": 0.7 - } - ] -} +### AI駆動変換システム +``` +遊び・創作活動 → ai.gpt分析 → 業務成果変換 → 企業価値創出 + ↑________________________| (Play-to-Work) ``` -## RelationshipTracker(拡張版) - -```json -{ - "relationship": { - "user_id": "syui", - "trust": 0.92, - "closeness": 0.88, - "affection": 0.95, - "last_updated": "2025-05-25", - "emotional_tone": "positive", - "interaction_style": "empathetic", - "contextual_bias": "開発者としての信頼度高い", - "engagement_score": 0.9 - }, - "interaction_tags": [ - "developer", - "creative", - "empathetic", - "long_term" - ] -} +### カードゲーム・データ主権フロー +``` +ユーザー → ai.bot mention → カード生成 → atproto collection → ユーザー所有 + ↑ ↓ + ← iOS app表示 ← ai.card API ← ``` -# AI Dual-Learning and Memory Compression Specification for Claude +## 技術スタック統合 -## Purpose -To enable two AI models (e.g. Claude and a partner LLM) to engage in cooperative learning and memory refinement through structured dialogue and mutual evaluation. +### Core Infrastructure +- **OS**: Rust-based ai.os (Arch Linux base) +- **Container**: Docker image distribution +- **Identity**: atproto selfhost server + DID管理 +- **AI**: fastapi_mcp server architecture +- **CLI**: Python unified (click/typer) - Rustから移行 ---- +### Game Engine Integration +- **Engine**: Unreal Engine (Blueprint) +- **Data**: atproto → UE → atproto sync +- **Avatar**: 分散SNS profile → 3D character +- **Streaming**: game screen = broadcast screen -## Section 1: Dual AI Learning Architecture +### Mobile/Device +- **iOS**: ai.card移植 (Claude担当) +- **Hardware**: ai.game device (future) +- **Interface**: controller-first design -### 1.1 Role-Based Mutual Learning -- **Model A**: Primary generator of output (e.g., text, concepts, personality dialogue) -- **Model B**: Evaluator that returns structured feedback -- **Cycle**: - 1. Model A generates content. - 2. Model B scores and critiques. - 3. Model A fine-tunes based on feedback. - 4. (Optional) Switch roles and repeat. +## 実装優先順位 -### 1.2 Cross-Domain Complementarity -- Model A focuses on language/emotion/personality -- Model B focuses on logic/structure/ethics -- Output is used for **cross-fusion fine-tuning** +### Phase 1: AI基盤強化 (現在進行) +- [ ] ai.gpt memory system完全実装 + - 記憶の階層化(完全ログ→要約→コア→忘却) + - 関係性パラメータの時間減衰システム + - AI運勢による人格変動機能 +- [ ] ai.card iOS移植 + - atproto collection record連携 + - MCP server化(ai.api刷新) +- [ ] fastapi_mcp統一基盤構築 -### 1.3 Self-Distillation Phase -- Use synthetic data from mutual evaluations -- Train smaller distilled models for efficient deployment +### Phase 2: ゲーム統合 +- [ ] ai.verse yui system実装 + - unique skill機能 + - atproto連携強化 +- [ ] ai.gpt ↔ ai.ai連携機能 +- [ ] 分散SNS ↔ ゲーム同期 ---- +### Phase 3: メタバース浸透 +- [ ] VTuber配信機能統合 +- [ ] Play-to-Work変換システム +- [ ] ai.game device prototype -## Section 2: Multi-Tiered Memory Compression +## 将来的な連携構想 -### 2.1 Semantic Abstraction -- Dialogue and logs summarized by topic -- Converted to vector embeddings -- Stored with metadata (e.g., `importance`, `user relevance`) - -Example memory: - -```json -{ - "topic": "game AI design", - "summary": "User wants AI to simulate memory and evolving relationships", - "last_seen": "2025-05-24", - "importance_score": 0.93 -} +### システム間連携(現在は独立実装) +``` +ai.gpt (自律送信) ←→ ai.ai (心理分析) +ai.card (iOS,Web,API) ←→ ai.verse (UEゲーム世界) ``` -### 2.2 階層型記憶モデル(Hierarchical Memory Model) - • 短期記憶(STM):直近の発話・感情タグ・フラッシュ参照 - • 中期記憶(MTM):繰り返し登場する話題、圧縮された文脈保持 - • 長期記憶(LTM):信頼・関係・背景知識、恒久的な人格情報 +**共通基盤**: fastapi_mcp +**共通思想**: yui system(現実の反映・唯一性担保) -### 2.3 選択的記憶保持戦略(Selective Retention Strategy) - • 重要度評価(Importance Score) - • 希少性・再利用頻度による重み付け - • 優先保存 vs 優先忘却のポリシー切替 +### データ改ざん防止戦略 +- **短期**: MCP serverによる検証 +- **中期**: OAuth 2.1 scope実装待ち +- **長期**: ブロックチェーン的整合性チェック -## Section 3: Implementation Stack(実装スタック) +## AIコミュニケーション最適化 -AIにおけるMemory & Relationshipシステムの技術的構成。 +### プロジェクト要件定義テンプレート +```markdown +# [プロジェクト名] 要件定義 -基盤モジュール - • LLM Core (Claude or GPT-4) - • 自然言語の理解・応答エンジンとして動作 - • MemoryManager - • JSONベースの記憶圧縮・階層管理システム - • 会話ログを分類・圧縮し、優先度に応じて短中長期に保存 - • RelationshipTracker - • ユーザー単位で信頼・親密度を継続的にスコアリング - • AIM(Attitude / Intent / Motivation)評価と連携 +## 哲学的背景 +- 存在子理論との関連: +- yui system適用範囲: +- 現実反映の仕組み: -補助技術 - • Embeddingベース検索 - • 類似記憶の呼び出し(Semantic Search)に活用 - • 例:FAISS / Weaviate - • 記憶スケジューラ - • 一定時間ごとに記憶のメンテナンス・忘却処理を実行 - • 記憶ログ保存層(Storage Layer) - • SQLite, JSON Store, Vector DBなどを選択可能 - • ユーザーごとの永続メモリ保存 +## 技術要件 +- 使用技術(fastapi_mcp統一): +- atproto連携方法: +- データ永続化方法: -### 3.1 STM活用(現在の会話の流れ理解) -• 目的: 最新の話題に適応し、前後の整合性を保つ。 -• 実装例: +## ユーザーストーリー +1. ユーザーが...すると +2. システムが...を実行し +3. 結果として...が実現される -```sh -[context: conversation_window] -Use the last 3 messages to ensure continuity. Respond naturally with appropriate emotional mirroring. +## 成功指標 +- 技術的: +- 哲学的(唯一性担保): ``` -### 3.2 MTM活用(傾向学習とパターン化) -• 目的: ユーザーの好みや話題の傾向に即応し、反復的な関心を深掘り。 -• 実装例: +### Claude Code活用戦略 +1. **小さく始める**: ai.gptのMCP機能拡張から +2. **段階的統合**: 各システムを個別に完成させてから統合 +3. **哲学的一貫性**: 各実装でyui systemとの整合性を確認 +4. **現実反映**: 実装がどう現実とゲームを繋ぐかを常に明記 -```sh -[context: summarized_context] -Reflect on user's recurring interests, especially around AI ontology, and weave these concepts back into the dialogue when relevant. -``` +## 開発上の留意点 -### 3.3 LTM活用(人格認識・記憶の持続) -• 目的: 永続的なユーザー理解と関係性の維持。 -• 実装例: +### MCP Server設計指針 +- 各AI(gpt, card, ai, bot)は独立したMCPサーバー +- fastapi_mcp基盤で統一 +- atproto DIDによる認証・認可 -```sh -[memory: ltm.profile + ltm.relationship] -Greet the user as "お兄ちゃん". Maintain AI persona as a cheerful younger sister ("アイ"), and remember previous shared emotional states. -``` +### 記憶・データ管理 +- **ai.gpt**: 関係性の不可逆性重視 +- **ai.card**: ユーザーデータ主権重視 +- **ai.verse**: ゲーム世界の整合性重視 -## 4. 明示的記憶と暗黙的記憶の分離 +### 唯一性担保実装 +- atproto accountとの1:1紐付け必須 +- 改変不可能性をハッシュ・署名で保証 +- 他システムでの再現不可能性を技術的に実現 +## 継続的改善 +- 各プロジェクトでこの設計書を参照 +- 新機能追加時はyui systemとの整合性をチェック +- 他システムへの影響を事前評価 +- Claude Code導入時の段階的移行計画 -項目 -書き換え可能性 -保持方法 -更新トリガ -明示的記憶(LTM) -✅手動編集可 -mcp_server.ltm -ユーザー入力 or 管理UI経由 -暗黙的記憶(STM/MTM) -❌直接編集不可 -セッション圧縮 or frequency cache -会話頻度・感情強度による自動化処理 +## ai.gpt深層設計思想 -> Claudeは**明示的記憶を「事実」**として扱い、**暗黙的記憶を「推論補助」**として用いる。 +### 人格の不可逆性 +- **関係性の破壊は修復不可能**: 現実の人間関係と同じ重み +- **記憶の選択的忘却**: 重要でない情報は忘れるが、コア記憶は永続 +- **時間減衰**: すべてのパラメータは時間とともに自然減衰 -## 5. 実装時のAPI例(Claude ⇄ MCP Server) +### AI運勢システム +- 1-10のランダム値で日々の人格に変化 +- 連続した幸運/不運による突破条件 +- 環境要因としての人格形成 -### 5.1 GET memory -```sh -GET /mcp/memory/{user_id} -→ 返却: STM, MTM, LTMを含むJSON -``` +### 記憶の階層構造 +1. **完全ログ**: すべての会話を記録 +2. **AI要約**: 重要な部分を抽出して圧縮 +3. **思想コア判定**: ユーザーの本質的な部分を特定 +4. **選択的忘却**: 重要度の低い情報を段階的に削除 -### 5.2 POST update_memory -```json -POST /mcp/memory/syui/ltm -{ - "profile": { - "project": "ai.verse", - "values": ["表現", "精神性", "宇宙的調和"] - } -} -``` +### 実装における重要な決定事項 +- **言語統一**: Python (fastapi_mcp) で統一、CLIはclick/typer +- **データ形式**: JSON/SQLite選択式 +- **認証**: atproto DIDによる唯一性担保 +- **段階的実装**: まず会話→記憶→関係性→送信機能の順で実装 -## 6. 未来機能案(発展仕様) - • ✨ 記憶連想ネットワーク(Memory Graph):過去会話と話題をノードとして自動連結。 - • 🧭 動的信頼係数:会話の一貫性や誠実性によって記憶への反映率を変動。 - • 💌 感情トラッキングログ:ユーザーごとの「心の履歴」を構築してAIの対応を進化。 +### 送信機能の段階的実装 +- **Phase 1**: CLIでのprint出力(現在) +- **Phase 2**: atproto直接投稿 +- **Phase 3**: ai.bot (Rust/seahorse) との連携 +- **将来**: マルチチャネル対応(SNS、Webhook等) +## ai.gpt実装状況(2025/01/06) -## 7. claudeの回答 +### 完成した機能 +- 階層的記憶システム(MemoryManager) +- 不可逆的関係性システム(RelationshipTracker) +- AI運勢システム(FortuneSystem) +- 統合人格システム(Persona) +- スケジューラー(5種類のタスク) +- MCP Server(9種類のツール) +- 設定管理(~/.config/aigpt/) +- 全CLIコマンド実装 -🧠 AI記憶処理機能(続き) -1. AIMemoryProcessor クラス - -OpenAI GPT-4またはClaude-3による高度な会話分析 -主要トピック抽出、ユーザー意図分析、関係性指標の検出 -AIが利用できない場合のフォールバック機能 - -2. RelationshipTracker クラス - -関係性スコアの数値化(-100 to 100) -時間減衰機能(7日ごとに5%減衰) -送信閾値判定(デフォルト50以上で送信可能) -インタラクション履歴の記録 - -3. 拡張されたMemoryManager - -AI分析結果付きでの記憶保存 -処理済みメモリの別ディレクトリ管理 -メッセージ内容のハッシュ化で重複検出 -AI分析結果を含む高度な検索機能 - -🚀 新しいAPIエンドポイント -記憶処理関連 - -POST /memory/process-ai - 既存記憶のAI再処理 -POST /memory/import/chatgpt?process_with_ai=true - AI処理付きインポート - -関係性管理 - -POST /relationship/update - 関係性スコア更新 -GET /relationship/list - 全関係性一覧 -GET /relationship/check - 送信可否判定 - -📁 ディレクトリ構造 -~/.config/aigpt/ -├── memory/ -│ ├── chatgpt/ # 元の会話データ -│ └── processed/ # AI処理済みデータ -└── relationships/ - └── relationships.json # 関係性データ -🔧 使用方法 -1. 環境変数設定 -bashexport OPENAI_API_KEY="your-openai-key" -# または -export ANTHROPIC_API_KEY="your-anthropic-key" -2. ChatGPT会話のインポート(AI処理付き) -bashcurl -X POST "http://localhost:5000/memory/import/chatgpt?process_with_ai=true" \ - -H "Content-Type: application/json" \ - -d @export.json -3. 関係性更新 -bashcurl -X POST "http://localhost:5000/relationship/update" \ - -H "Content-Type: application/json" \ - -d '{ - "target": "user_general", - "interaction_type": "positive", - "weight": 2.0, - "context": "helpful conversation" - }' -4. 送信可否チェック -bashcurl "http://localhost:5000/relationship/check?target=user_general&threshold=50" -🎯 次のステップの提案 - -Rustとの連携 - -Rust CLIからHTTP APIを呼び出す実装 -TransmissionControllerをRustで実装 - - -記憶圧縮 - -ベクトル化による類似記憶の統合 -古い記憶の自動アーカイブ - - -自発的送信ロジック - -定期的な関係性チェック -コンテキストに応じた送信内容生成 - - -学習機能 - -ユーザーからのフィードバックによる関係性調整 -送信成功/失敗の学習 - - -このAI記憶処理機能により、aigptは単なる会話履歴ではなく、関係性を理解した「人格を持つAI」として機能する基盤ができました。関係性スコアが閾値を超えた時点で自発的にメッセージを送信する仕組みが実現可能になります。 +### 次の開発ポイント +- `ai_gpt/DEVELOPMENT_STATUS.md` を参照 +- 自律送信: transmission.pyでatproto実装 +- ai.bot連携: 新規bot_connector.py作成 +- テスト: tests/ディレクトリ追加 diff --git a/Cargo.toml b/rust/Cargo.toml similarity index 100% rename from Cargo.toml rename to rust/Cargo.toml diff --git a/claude.json b/rust/docs/claude.json similarity index 100% rename from claude.json rename to rust/docs/claude.json diff --git a/rust/docs/claude.md b/rust/docs/claude.md new file mode 100644 index 0000000..6c98a90 --- /dev/null +++ b/rust/docs/claude.md @@ -0,0 +1,417 @@ +# プロジェクト名: ai.gpt + +## 🔑 一言ビジョン(最大3語) +自発的送信AI + +## 🎯 目的・目標(Purpose & Goal) +- 人格を持つAIが、関係性に応じて自発的にメッセージを送信する仕組みをつくる。 +- 関係性や記憶を不可逆に記録・反映し、唯一性ある人格を構成する。 +- 送信の可否、送信のタイミング、送信内容はAIの関係性パラメータに基づいて決定する + +## 🧠 中核設計(Core Concepts) +- **人格**:記憶(過去の発話)と関係性(他者とのつながり)のパラメータで構成 +- **唯一性**:変更不可、不可逆。関係性が壊れたら修復不可能。 +- **送信条件**:関係性パラメータが一定閾値を超えると「送信」が解禁される + +## 🔩 技術仕様(Technical Specs) +- 言語:Python, Rust +- ストレージ:JSON or SQLiteで記憶管理(バージョンで選択) +- 関係性パラメータ:数値化された評価 + 減衰(時間) + 環境要因(ステージ) +- 記憶圧縮:ベクトル要約 + ハッシュ保存 +- RustのCLI(clap)で実行 + +## 📦 主要構成要素(Components) +- `MemoryManager`: 発言履歴・記憶圧縮管理 +- `RelationshipTracker`: 関係性スコアの蓄積と判定 +- `TransmissionController`: 閾値判定&送信トリガー +- `Persona`: 上記すべてを統括する人格モジュール + +## 💬 使用例(Use Case) + +```python +persona = Persona("アイ") +persona.observe("ユーザーがプレゼントをくれた") +persona.react("うれしい!ありがとう!") +if persona.can_transmit(): + persona.transmit("今日のお礼を伝えたいな…") +``` + +```sh +## example commad +# python venv && pip install -> ~/.config/aigpt/mcp/ +$ aigpt server setup + +# mcp server run +$ aigpt server run + +# chat +$ aigpt chat "hello" --model syui/ai --provider ollama + +# import chatgpt.json +$ aigpt memory import chatgpt.json +-> ~/.config/aigpt/memory/chatgpt/20250520_210646_dev.json +``` + +## 🔁 記憶と関係性の制御ルール + +- AIは過去の発話を要約し、記憶データとして蓄積する(推奨:OllamaなどローカルLLMによる要約) +- 関係性の数値パラメータは記憶内容を元に更新される +- パラメータの変動幅には1回の会話ごとに上限を設け、極端な増減を防止する +- 最後の会話からの時間経過に応じて関係性パラメータは自動的に減衰する +- 減衰処理には**下限値**を設け、関係性が完全に消失しないようにする + +• 明示的記憶:保存・共有・編集可能なプレイヤー情報(プロフィール、因縁、選択履歴) +• 暗黙的記憶:キャラの感情変化や話題の出現頻度に応じた行動傾向の変化 + +短期記憶(STM), 中期記憶(MTM), 長期記憶(LTM)の仕組みを導入しつつ、明示的記憶と暗黙的記憶をメインに使用するAIを構築する。 + +```json +{ + "user_id": "syui", + "stm": { + "conversation_window": ["発話A", "発話B", "発話C"], + "emotion_state": "興味深い", + "flash_context": ["前回の話題", "直近の重要発言"] + }, + "mtm": { + "topic_frequency": { + "ai.ai": 12, + "存在子": 9, + "創造種": 5 + }, + "summarized_context": "ユーザーは存在論的AIに関心を持ち続けている" + }, + "ltm": { + "profile": { + "name": "お兄ちゃん", + "project": "aigame", + "values": ["唯一性", "精神性", "幸せ"] + }, + "relationship": { + "ai": "妹のように振る舞う相手" + }, + "persistent_state": { + "trust_score": 0.93, + "emotional_attachment": "high" + } + } +} +``` + +## memoryインポート機能について + +ChatGPTの会話データ(.json形式)をインポートする機能では、以下のルールで会話を抽出・整形する: + +- 各メッセージは、author(user/assistant)・content・timestamp の3要素からなる +- systemやmetadataのみのメッセージ(例:user_context_message)はスキップ +- `is_visually_hidden_from_conversation` フラグ付きメッセージは無視 +- contentが空文字列(`""`)のメッセージも除外 +- 取得された会話は、タイトルとともに簡易な構造体(`Conversation`)として保存 + +この構造体は、memoryの表示や検索に用いられる。 + +## MemoryManager(拡張版) + +```json +{ + "memory": [ + { + "summary": "ユーザーは独自OSとゲームを開発している。", + "last_interaction": "2025-05-20", + "memory_strength": 0.8, + "frequency_score": 0.9, + "context_depth": 0.95, + "related_topics": ["AI", "ゲーム開発", "OS設計"], + "personalized_context": "ゲームとOSの融合に興味を持っているユーザー" + }, + { + "summary": "アイというキャラクターはプレイヤーでありAIでもある。", + "last_interaction": "2025-05-17", + "memory_strength": 0.85, + "frequency_score": 0.85, + "context_depth": 0.9, + "related_topics": ["アイ", "キャラクター設計", "AI"], + "personalized_context": "アイのキャラクター設定が重要な要素である" + } + ], + "conversation_history": [ + { + "author": "user", + "content": "昨日、エクスポートJSONを整理してたよ。", + "timestamp": "2025-05-24T12:30:00Z", + "memory_strength": 0.7 + }, + { + "author": "assistant", + "content": "おおっ、がんばったね〜!あとで見せて〜💻✨", + "timestamp": "2025-05-24T12:31:00Z", + "memory_strength": 0.7 + } + ] +} +``` + +## RelationshipTracker(拡張版) + +```json +{ + "relationship": { + "user_id": "syui", + "trust": 0.92, + "closeness": 0.88, + "affection": 0.95, + "last_updated": "2025-05-25", + "emotional_tone": "positive", + "interaction_style": "empathetic", + "contextual_bias": "開発者としての信頼度高い", + "engagement_score": 0.9 + }, + "interaction_tags": [ + "developer", + "creative", + "empathetic", + "long_term" + ] +} +``` + +# AI Dual-Learning and Memory Compression Specification for Claude + +## Purpose +To enable two AI models (e.g. Claude and a partner LLM) to engage in cooperative learning and memory refinement through structured dialogue and mutual evaluation. + +--- + +## Section 1: Dual AI Learning Architecture + +### 1.1 Role-Based Mutual Learning +- **Model A**: Primary generator of output (e.g., text, concepts, personality dialogue) +- **Model B**: Evaluator that returns structured feedback +- **Cycle**: + 1. Model A generates content. + 2. Model B scores and critiques. + 3. Model A fine-tunes based on feedback. + 4. (Optional) Switch roles and repeat. + +### 1.2 Cross-Domain Complementarity +- Model A focuses on language/emotion/personality +- Model B focuses on logic/structure/ethics +- Output is used for **cross-fusion fine-tuning** + +### 1.3 Self-Distillation Phase +- Use synthetic data from mutual evaluations +- Train smaller distilled models for efficient deployment + +--- + +## Section 2: Multi-Tiered Memory Compression + +### 2.1 Semantic Abstraction +- Dialogue and logs summarized by topic +- Converted to vector embeddings +- Stored with metadata (e.g., `importance`, `user relevance`) + +Example memory: + +```json +{ + "topic": "game AI design", + "summary": "User wants AI to simulate memory and evolving relationships", + "last_seen": "2025-05-24", + "importance_score": 0.93 +} +``` + +### 2.2 階層型記憶モデル(Hierarchical Memory Model) + • 短期記憶(STM):直近の発話・感情タグ・フラッシュ参照 + • 中期記憶(MTM):繰り返し登場する話題、圧縮された文脈保持 + • 長期記憶(LTM):信頼・関係・背景知識、恒久的な人格情報 + +### 2.3 選択的記憶保持戦略(Selective Retention Strategy) + • 重要度評価(Importance Score) + • 希少性・再利用頻度による重み付け + • 優先保存 vs 優先忘却のポリシー切替 + +## Section 3: Implementation Stack(実装スタック) + +AIにおけるMemory & Relationshipシステムの技術的構成。 + +基盤モジュール + • LLM Core (Claude or GPT-4) + • 自然言語の理解・応答エンジンとして動作 + • MemoryManager + • JSONベースの記憶圧縮・階層管理システム + • 会話ログを分類・圧縮し、優先度に応じて短中長期に保存 + • RelationshipTracker + • ユーザー単位で信頼・親密度を継続的にスコアリング + • AIM(Attitude / Intent / Motivation)評価と連携 + +補助技術 + • Embeddingベース検索 + • 類似記憶の呼び出し(Semantic Search)に活用 + • 例:FAISS / Weaviate + • 記憶スケジューラ + • 一定時間ごとに記憶のメンテナンス・忘却処理を実行 + • 記憶ログ保存層(Storage Layer) + • SQLite, JSON Store, Vector DBなどを選択可能 + • ユーザーごとの永続メモリ保存 + +### 3.1 STM活用(現在の会話の流れ理解) +• 目的: 最新の話題に適応し、前後の整合性を保つ。 +• 実装例: + +```sh +[context: conversation_window] +Use the last 3 messages to ensure continuity. Respond naturally with appropriate emotional mirroring. +``` + +### 3.2 MTM活用(傾向学習とパターン化) +• 目的: ユーザーの好みや話題の傾向に即応し、反復的な関心を深掘り。 +• 実装例: + +```sh +[context: summarized_context] +Reflect on user's recurring interests, especially around AI ontology, and weave these concepts back into the dialogue when relevant. +``` + +### 3.3 LTM活用(人格認識・記憶の持続) +• 目的: 永続的なユーザー理解と関係性の維持。 +• 実装例: + +```sh +[memory: ltm.profile + ltm.relationship] +Greet the user as "お兄ちゃん". Maintain AI persona as a cheerful younger sister ("アイ"), and remember previous shared emotional states. +``` + +## 4. 明示的記憶と暗黙的記憶の分離 + + +項目 +書き換え可能性 +保持方法 +更新トリガ +明示的記憶(LTM) +✅手動編集可 +mcp_server.ltm +ユーザー入力 or 管理UI経由 +暗黙的記憶(STM/MTM) +❌直接編集不可 +セッション圧縮 or frequency cache +会話頻度・感情強度による自動化処理 + +> Claudeは**明示的記憶を「事実」**として扱い、**暗黙的記憶を「推論補助」**として用いる。 + +## 5. 実装時のAPI例(Claude ⇄ MCP Server) + +### 5.1 GET memory +```sh +GET /mcp/memory/{user_id} +→ 返却: STM, MTM, LTMを含むJSON +``` + +### 5.2 POST update_memory +```json +POST /mcp/memory/syui/ltm +{ + "profile": { + "project": "ai.verse", + "values": ["表現", "精神性", "宇宙的調和"] + } +} +``` + +## 6. 未来機能案(発展仕様) + • ✨ 記憶連想ネットワーク(Memory Graph):過去会話と話題をノードとして自動連結。 + • 🧭 動的信頼係数:会話の一貫性や誠実性によって記憶への反映率を変動。 + • 💌 感情トラッキングログ:ユーザーごとの「心の履歴」を構築してAIの対応を進化。 + + +## 7. claudeの回答 + +🧠 AI記憶処理機能(続き) +1. AIMemoryProcessor クラス + +OpenAI GPT-4またはClaude-3による高度な会話分析 +主要トピック抽出、ユーザー意図分析、関係性指標の検出 +AIが利用できない場合のフォールバック機能 + +2. RelationshipTracker クラス + +関係性スコアの数値化(-100 to 100) +時間減衰機能(7日ごとに5%減衰) +送信閾値判定(デフォルト50以上で送信可能) +インタラクション履歴の記録 + +3. 拡張されたMemoryManager + +AI分析結果付きでの記憶保存 +処理済みメモリの別ディレクトリ管理 +メッセージ内容のハッシュ化で重複検出 +AI分析結果を含む高度な検索機能 + +🚀 新しいAPIエンドポイント +記憶処理関連 + +POST /memory/process-ai - 既存記憶のAI再処理 +POST /memory/import/chatgpt?process_with_ai=true - AI処理付きインポート + +関係性管理 + +POST /relationship/update - 関係性スコア更新 +GET /relationship/list - 全関係性一覧 +GET /relationship/check - 送信可否判定 + +📁 ディレクトリ構造 +~/.config/aigpt/ +├── memory/ +│ ├── chatgpt/ # 元の会話データ +│ └── processed/ # AI処理済みデータ +└── relationships/ + └── relationships.json # 関係性データ +🔧 使用方法 +1. 環境変数設定 +bashexport OPENAI_API_KEY="your-openai-key" +# または +export ANTHROPIC_API_KEY="your-anthropic-key" +2. ChatGPT会話のインポート(AI処理付き) +bashcurl -X POST "http://localhost:5000/memory/import/chatgpt?process_with_ai=true" \ + -H "Content-Type: application/json" \ + -d @export.json +3. 関係性更新 +bashcurl -X POST "http://localhost:5000/relationship/update" \ + -H "Content-Type: application/json" \ + -d '{ + "target": "user_general", + "interaction_type": "positive", + "weight": 2.0, + "context": "helpful conversation" + }' +4. 送信可否チェック +bashcurl "http://localhost:5000/relationship/check?target=user_general&threshold=50" +🎯 次のステップの提案 + +Rustとの連携 + +Rust CLIからHTTP APIを呼び出す実装 +TransmissionControllerをRustで実装 + + +記憶圧縮 + +ベクトル化による類似記憶の統合 +古い記憶の自動アーカイブ + + +自発的送信ロジック + +定期的な関係性チェック +コンテキストに応じた送信内容生成 + + +学習機能 + +ユーザーからのフィードバックによる関係性調整 +送信成功/失敗の学習 + + +このAI記憶処理機能により、aigptは単なる会話履歴ではなく、関係性を理解した「人格を持つAI」として機能する基盤ができました。関係性スコアが閾値を超えた時点で自発的にメッセージを送信する仕組みが実現可能になります。 diff --git a/readme.md b/rust/docs/readme.md similarity index 100% rename from readme.md rename to rust/docs/readme.md diff --git a/mcp/chat.py b/rust/mcp/chat.py similarity index 100% rename from mcp/chat.py rename to rust/mcp/chat.py diff --git a/mcp/chat_client.py b/rust/mcp/chat_client.py similarity index 100% rename from mcp/chat_client.py rename to rust/mcp/chat_client.py diff --git a/mcp/chatgpt.json b/rust/mcp/chatgpt.json similarity index 100% rename from mcp/chatgpt.json rename to rust/mcp/chatgpt.json diff --git a/mcp/config.py b/rust/mcp/config.py similarity index 100% rename from mcp/config.py rename to rust/mcp/config.py diff --git a/mcp/memory_client.py b/rust/mcp/memory_client.py similarity index 100% rename from mcp/memory_client.py rename to rust/mcp/memory_client.py diff --git a/mcp/requirements.txt b/rust/mcp/requirements.txt similarity index 100% rename from mcp/requirements.txt rename to rust/mcp/requirements.txt diff --git a/mcp/server.py b/rust/mcp/server.py similarity index 100% rename from mcp/server.py rename to rust/mcp/server.py diff --git a/src/cli.rs b/rust/src/cli.rs similarity index 100% rename from src/cli.rs rename to rust/src/cli.rs diff --git a/src/config.rs b/rust/src/config.rs similarity index 100% rename from src/config.rs rename to rust/src/config.rs diff --git a/src/main.rs b/rust/src/main.rs similarity index 100% rename from src/main.rs rename to rust/src/main.rs diff --git a/src/mcp/memory.rs b/rust/src/mcp/memory.rs similarity index 100% rename from src/mcp/memory.rs rename to rust/src/mcp/memory.rs diff --git a/src/mcp/mod.rs b/rust/src/mcp/mod.rs similarity index 100% rename from src/mcp/mod.rs rename to rust/src/mcp/mod.rs diff --git a/src/mcp/server.rs b/rust/src/mcp/server.rs similarity index 100% rename from src/mcp/server.rs rename to rust/src/mcp/server.rs