Compare commits

...

37 Commits

Author SHA1 Message Date
b410c83605 fix readme 2025-06-06 03:25:22 +09:00
334e17a53e update log 2025-06-06 03:18:04 +09:00
df86fb827e cleanup 2025-06-03 05:09:56 +09:00
5a441e847d fix card 2025-06-03 05:00:37 +09:00
948bbc24ea fix system prompt 2025-06-03 03:50:39 +09:00
d4de0d4917 cleanup 2025-06-03 03:09:27 +09:00
3487535e08 fix mcp 2025-06-03 03:02:15 +09:00
1755dc2bec fix shell 2025-06-03 02:12:11 +09:00
42c85fc820 add mode 2025-06-03 01:51:24 +09:00
4a441279fb fix config 2025-06-03 01:37:32 +09:00
e7e57b7b4b Merge pull request 'fix scpt' (#2) from feature/shell-integration into main
Reviewed-on: #2
2025-06-02 16:27:12 +00:00
6081ed069f fix scpt 2025-06-03 01:26:12 +09:00
8c0961ab2f Merge pull request 'feature/shell-integration' (#1) from feature/shell-integration into main
Reviewed-on: #1
2025-06-02 16:06:36 +00:00
c9005f5240 fix md 2025-06-03 01:03:38 +09:00
cba52b6171 update ai.shell 2025-06-03 01:01:28 +09:00
b642588696 fix docs 2025-06-02 18:24:04 +09:00
ebd2582b92 update 2025-06-02 06:22:39 +09:00
79d1e1943f add card 2025-06-02 06:22:38 +09:00
76d90c7cf7 add shell 2025-06-02 05:24:38 +09:00
06fb70fffa add src 2025-06-02 01:16:04 +09:00
62f941a958 fix config 2025-06-02 00:31:46 +09:00
98ca92d85d fix dir 2025-06-01 21:43:16 +09:00
1c555a706b fix 2025-06-01 16:40:25 +09:00
7c3b05501f fix 2025-05-31 01:47:58 +09:00
a7b61fe07d fix 2025-05-30 20:07:06 +09:00
9866da625d fix 2025-05-30 04:40:29 +09:00
797ae7ef69 add memory 2025-05-26 14:57:08 +09:00
abd2ad79bd fix memory chatgpt json 2025-05-25 19:54:28 +09:00
979e55cfce fix mcp 2025-05-25 19:39:11 +09:00
cd25af7bf0 add chatgpt json 2025-05-25 18:22:52 +09:00
58e202fa1e first claude 2025-05-24 23:19:30 +09:00
4f55138306 add fastapi_mcp 2025-05-23 21:34:06 +09:00
9cbf5da3fd add memory 2025-05-22 18:40:36 +09:00
52d0efc086 test scheduler send limit 2025-05-22 18:23:17 +09:00
7aa633d3a6 test scheduler 2025-05-22 18:01:07 +09:00
f09f3c9144 add metrics 2025-05-22 01:08:37 +09:00
4837de580f cleanup 2025-05-21 22:59:59 +09:00
80 changed files with 9760 additions and 1412 deletions

View File

@@ -0,0 +1,57 @@
{
"permissions": {
"allow": [
"Bash(mv:*)",
"Bash(mkdir:*)",
"Bash(chmod:*)",
"Bash(git submodule:*)",
"Bash(source:*)",
"Bash(pip install:*)",
"Bash(/Users/syui/.config/syui/ai/gpt/venv/bin/aigpt shell)",
"Bash(/Users/syui/.config/syui/ai/gpt/venv/bin/aigpt server --model qwen2.5-coder:7b --port 8001)",
"Bash(/Users/syui/.config/syui/ai/gpt/venv/bin/python -c \"import fastapi_mcp; help(fastapi_mcp.FastApiMCP)\")",
"Bash(find:*)",
"Bash(/Users/syui/.config/syui/ai/gpt/venv/bin/pip install -e .)",
"Bash(/Users/syui/.config/syui/ai/gpt/venv/bin/aigpt fortune)",
"Bash(lsof:*)",
"Bash(/Users/syui/.config/syui/ai/gpt/venv/bin/python -c \"\nfrom src.aigpt.mcp_server import AIGptMcpServer\nfrom pathlib import Path\nimport uvicorn\n\ndata_dir = Path.home() / '.config' / 'syui' / 'ai' / 'gpt' / 'data'\ndata_dir.mkdir(parents=True, exist_ok=True)\n\ntry:\n server = AIGptMcpServer(data_dir)\n print('MCP Server created successfully')\n print('Available endpoints:', [route.path for route in server.app.routes])\nexcept Exception as e:\n print('Error:', e)\n import traceback\n traceback.print_exc()\n\")",
"Bash(ls:*)",
"Bash(grep:*)",
"Bash(python -m pip install:*)",
"Bash(python:*)",
"Bash(RELOAD=false ./start_server.sh)",
"Bash(sed:*)",
"Bash(curl:*)",
"Bash(~/.config/syui/ai/card/venv/bin/pip install greenlet)",
"Bash(~/.config/syui/ai/card/venv/bin/python init_db.py)",
"Bash(sqlite3:*)",
"Bash(aigpt --help)",
"Bash(aigpt status)",
"Bash(aigpt fortune)",
"Bash(aigpt relationships)",
"Bash(aigpt transmit)",
"Bash(aigpt config:*)",
"Bash(kill:*)",
"Bash(timeout:*)",
"Bash(rm:*)",
"Bash(rg:*)",
"Bash(aigpt server --help)",
"Bash(cat:*)",
"Bash(aigpt import-chatgpt:*)",
"Bash(aigpt chat:*)",
"Bash(echo:*)",
"Bash(aigpt shell:*)",
"Bash(aigpt maintenance)",
"Bash(aigpt status syui)",
"Bash(cp:*)",
"Bash(./setup_venv.sh:*)",
"WebFetch(domain:docs.anthropic.com)",
"Bash(launchctl:*)",
"Bash(sudo lsof:*)",
"Bash(sudo:*)",
"Bash(cargo check:*)",
"Bash(cargo run:*)"
],
"deny": []
}
}

5
.env.example Normal file
View File

@@ -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

5
.gitignore vendored
View File

@@ -2,4 +2,7 @@
**.lock
output.json
config/*.db
aigpt
mcp/scripts/__*
data
__pycache__
conversations.json

10
.gitmodules vendored Normal file
View File

@@ -0,0 +1,10 @@
[submodule "shell"]
path = shell
url = git@git.syui.ai:ai/shell
[submodule "card"]
path = card
url = git@git.syui.ai:ai/card
branch = claude
[submodule "log"]
path = log
url = git@git.syui.ai:ai/log

View File

@@ -1,13 +0,0 @@
[package]
name = "aigpt"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = "0.4"
seahorse = "*"
rusqlite = { version = "0.29", features = ["serde_json"] }
shellexpand = "*"
fs_extra = "1.3"

959
README.md
View File

@@ -1,47 +1,946 @@
# ai `gpt`
# ai.gpt - AI駆動記憶システム & 自律対話AI
ai x Communication
🧠 **革新的記憶システム** × 🤖 **自律的人格AI** × 🔗 **atproto統合**
## Overview
ChatGPTの4,000件会話ログから学んだ「効果的な記憶構築」を完全実装した、真の記憶を持つAIシステム。
`ai.gpt` runs on the AGE system.
## 🎯 核心機能
This is a prototype of an autonomous, relationship-driven AI system based on the axes of "Personality × Relationship × External Environment × Time Variation."
### 📚 AI駆動階層記憶システム
- **CORE記憶**: 人格形成要素の永続的記憶AIが自動分析・抽出
- **SUMMARY記憶**: テーマ別スマート要約AI駆動パターン分析
- **記憶検索**: コンテキスト認識による関連性スコアリング
- **選択的忘却**: 重要度に基づく自然な記憶の減衰
The parameters of "Send Permission," "Send Timing," and "Send Content" are determined by the factors of "Personality x Relationship x External Environment x Time Variation."
### 🤝 進化する関係性システム
- **唯一性**: atproto DIDと1:1で紐付き、改変不可能な人格
- **不可逆性**: 関係性が壊れたら修復不可能(現実の人間関係と同じ)
- **時間減衰**: 自然な関係性の変化と送信閾値システム
- **AI運勢**: 1-10のランダム値による日々の人格変動
## Integration
### 🧬 統合アーキテクチャ
- **fastapi-mcp統一基盤**: Claude Desktop/Cursor完全対応
- **23種類のMCPツール**: 記憶・関係性・AI統合・シェル操作・リモート実行
- **ai.shell統合**: Claude Code風インタラクティブ開発環境
- **ai.bot連携**: systemd-nspawn隔離実行環境統合
- **マルチAI対応**: ollama(qwen3/gemma3) + OpenAI統合
`ai.ai` runs on the AIM system, which is designed to read human emotions.
## 🚀 クイックスタート
- AIM focuses on the axis of personality and ethics (AI's consciousness structure)
- AGE focuses on the axis of behavior and relationships (AI's autonomy and behavior)
### 1分で体験する記憶システム
> When these two systems work together, it creates a world where users can feel like they are "growing together with AI."
```bash
# 1. セットアップ(自動)
cd /Users/syui/ai/gpt
./setup_venv.sh
## mcp
# 2. ollama + qwen3で記憶テスト
aigpt chat syui "記憶システムのテストです" --provider ollama --model qwen3:latest
```sh
$ ollama run syui/ai
# 3. 記憶の確認
aigpt status syui
# 4. インタラクティブシェル体験
aigpt shell
```
```sh
$ cargo build
$ ./aigpt mcp setup
$ ./aigpt mcp chat "hello world!"
$ ./aigpt mcp chat "hello world!" --host http://localhost:11434 --model syui/ai
### 記憶システム体験デモ
---
# openai api
$ ./aigpt mcp set-api -api sk-abc123
$ ./aigpt mcp chat "こんにちは" -p openai -m gpt-4o-mini
```bash
# ChatGPTログインポート既存データを使用
aigpt import-chatgpt ./json/chatgpt.json --user-id syui
---
# git管理されているファイルをAIに読ませる
./aigpt mcp chat --host http://localhost:11434 --repo git@git.syui.ai:ai/gpt
**改善案と次のステップ:**
1. **README.md の大幅な改善:**
**次のステップ:**
1. **README.md の作成:** 1. の指示に従って、README.md ファイルを作成します。
# AI記憶分析
aigpt maintenance # スマート要約 + コア記憶生成
# 記憶に基づく対話
aigpt chat syui "前回の議論について覚えていますか?" --provider ollama --model qwen3:latest
# 記憶検索
# MCPサーバー経由でのコンテキスト記憶取得
aigpt server --port 8001 &
curl "http://localhost:8001/get_contextual_memories?query=ai&limit=5"
```
## インストール
```bash
# 仮想環境セットアップ(推奨)
cd /Users/syui/ai/gpt
source ~/.config/syui/ai/gpt/venv/bin/activate
pip install -e .
# または自動セットアップ
./setup_venv.sh
```
## 設定
### APIキーの設定
```bash
# OpenAI APIキー
aigpt config set providers.openai.api_key sk-xxxxx
# atproto認証情報将来の自動投稿用
aigpt config set atproto.handle your.handle
aigpt config set atproto.password your-password
# 設定一覧を確認
aigpt config list
```
### AIモデルの設定
```bash
# Ollamaのデフォルトモデルを変更
aigpt config set providers.ollama.default_model llama3
# OpenAIのデフォルトモデルを変更
aigpt config set providers.openai.default_model gpt-4
# Ollamaホストの設定
aigpt config set providers.ollama.host http://localhost:11434
# 設定の確認
aigpt config get providers.ollama.default_model
```
### データ保存場所
- 設定: `~/.config/syui/ai/gpt/config.json`
- データ: `~/.config/syui/ai/gpt/data/`
- 仮想環境: `~/.config/syui/ai/gpt/venv/`
### 設定ファイル構造
```json
{
"providers": {
"ollama": {
"host": "http://localhost:11434",
"default_model": "qwen3"
},
"openai": {
"api_key": null,
"default_model": "gpt-4o-mini"
}
},
"default_provider": "ollama"
}
```
## 使い方
### 会話する
```bash
# 通常の会話(詳細表示)
aigpt chat "did:plc:xxxxx" "こんにちは、今日はどんな気分?"
# 連続会話モード(シンプルな表示)
aigpt conversation syui --provider ollama --model qwen3:latest
aigpt conv syui --provider ollama --model qwen3:latest # 短縮形
```
### ステータス確認
```bash
# AI全体の状態
aigpt status
# 特定ユーザーとの関係
aigpt status "did:plc:xxxxx"
```
### 今日の運勢
```bash
aigpt fortune
```
### 自律送信チェック
```bash
# ドライラン(確認のみ)
aigpt transmit
# 実行
aigpt transmit --execute
```
### 日次メンテナンス
```bash
aigpt maintenance
```
### 関係一覧
```bash
aigpt relationships
```
### 会話モード詳細
#### 通常の会話コマンド
```bash
# 詳細表示モード(関係性スコア・送信状態等も表示)
aigpt chat syui "メッセージ" --provider ollama --model qwen3:latest
```
出力例:
```
╭─────────────────────────── AI Response ───────────────────────────╮
│ AIの返答がここに表示されます │
╰─────────────────────────────────────────────────────────────────╯
Relationship Status: stranger
Score: 28.00 / 100.0
Transmission: ✗ Disabled
```
#### 連続会話モード
```bash
# シンプルな会話画面(関係性情報なし)
aigpt conversation syui --provider ollama --model qwen3:latest
aigpt conv syui # 短縮形、デフォルト設定使用
```
会話画面:
```
Using ollama with model qwen3:latest
Conversation with AI started. Type 'exit' or 'quit' to end.
syui> こんにちは
AI> こんにちは!今日はどんな日でしたか?
syui> 今日は良い天気でした
AI> 良い天気だと気分も晴れやかになりますね!
syui> exit
Conversation ended.
```
#### 会話モードの特徴
- **通常モード**: 詳細な関係性情報とパネル表示
- **連続モード**: シンプルな`ユーザー> ``AI> `形式
- **履歴保存**: 両モードとも会話履歴を自動保存
- **コマンド補完**: Tab補完とコマンド履歴機能
### ChatGPTデータインポート
```bash
# ChatGPTの会話履歴をインポート
aigpt import-chatgpt ./json/chatgpt.json --user-id "your_user_id"
# インポート後の確認
aigpt status
aigpt relationships
```
## データ構造
デフォルトでは `~/.config/syui/ai/gpt/` に以下のファイルが保存されます:
- `memories.json` - 会話記憶
- `conversations.json` - 会話ログ
- `relationships.json` - 関係性パラメータ
- `fortunes.json` - AI運勢履歴
- `transmissions.json` - 送信履歴
- `persona_state.json` - 人格状態
## 関係性の仕組み
- スコア0-200の範囲で変動
- 100を超えると送信機能が解禁
- 時間経過で自然減衰
- 大きなネガティブな相互作用で破壊される可能性
## 🖥️ ai.shell統合 - Claude Code風開発環境
### 🚀 **基本起動**
```bash
# デフォルトqwen2.5使用)
aigpt shell
# qwen2.5-coder使用コード生成に最適
aigpt shell --model qwen2.5-coder:latest --provider ollama
# qwen3使用高度な対話
aigpt shell --model qwen3:latest --provider ollama
# OpenAI使用
aigpt shell --model gpt-4o-mini --provider openai
```
### 📋 **利用可能コマンド**
```bash
# === プロジェクト管理 ===
load # aishell.md読み込みAIがプロジェクト理解
status # AI状態・関係性確認
fortune # AI運勢確認人格に影響
relationships # 全関係性一覧
# === AI開発支援 ===
analyze <file> # ファイル分析・コードレビュー
generate <description> # コード生成qwen2.5-coder推奨
explain <topic> # 概念・技術説明
# === シェル操作 ===
!<command> # シェルコマンド実行
!git status # git操作
!ls -la # ファイル確認
!mkdir project # ディレクトリ作成
!pytest tests/ # テスト実行
# === リモート実行ai.bot統合===
remote <command> # systemd-nspawn隔離コンテナでコマンド実行
isolated <code> # Python隔離実行環境
aibot-status # ai.botサーバー接続確認
# === インタラクティブ対話 ===
help # コマンド一覧
clear # 画面クリア
exit/quit # 終了
<任意のメッセージ> # 自由なAI対話
```
### 🎯 **コマンド使用例**
```bash
ai.shell> load
# → aishell.mdを読み込み、AIがプロジェクト目標を記憶
ai.shell> generate Python FastAPI CRUD for User model
# → 完全なCRUD API コードを生成
ai.shell> analyze src/main.py
# → コード品質・改善点を分析
ai.shell> !git log --oneline -5
# → 最近のコミット履歴を表示
ai.shell> remote ls -la /tmp
# → ai.bot隔離コンテナでディレクトリ確認
ai.shell> isolated print("Hello from isolated environment!")
# → Python隔離実行でHello World
ai.shell> aibot-status
# → ai.botサーバー接続状態とコンテナ情報確認
ai.shell> このAPIのセキュリティを改善してください
# → 記憶に基づく具体的なセキュリティ改善提案
ai.shell> explain async/await in Python
# → 非同期プログラミングの詳細説明
```
## MCP Server統合アーキテクチャ
### ai.gpt統合サーバー簡素化設計
```bash
# シンプルなサーバー起動config.jsonから自動設定読み込み
aigpt server
# カスタム設定での起動
aigpt server --host localhost --port 8001
```
**重要**: MCP function callingは**OpenAIプロバイダーでのみ対応**
- OpenAI GPT-4o-mini/GPT-4でfunction calling機能が利用可能
- Ollamaはシンプルなchat APIのみMCPツール非対応
### MCP統合の動作条件
```bash
# MCP function calling対応推奨
aigpt conv test_user --provider openai --model gpt-4o-mini
# 通常の会話のみMCPツール非対応
aigpt conv test_user --provider ollama --model qwen3
```
### ai.card独立サーバー
```bash
# ai.card独立サーバー起動port 8000
cd card/api
source ~/.config/syui/ai/card/venv/bin/activate
uvicorn app.main:app --port 8000
```
### 統合アーキテクチャ構成
```
OpenAI GPT-4o-mini (Function Calling対応)
MCP Client (aigpt conv --provider openai)
↓ HTTP API
ai.gpt統合サーバー (port 8001) ← 27ツール
├── 🧠 Memory System: 5 tools
├── 🤝 Relationships: 4 tools
├── ⚙️ System State: 3 tools
├── 💻 Shell Integration: 5 tools
├── 🔒 Remote Execution: 4 tools
└── 📋 Project Management: 6 tools
Ollama qwen3/gemma3 (Chat APIのみ)
Direct Chat (aigpt conv --provider ollama)
↓ Direct Access
Memory/Relationship Systems
```
### プロバイダー別機能対応表
| 機能 | OpenAI | Ollama |
|------|--------|--------|
| 基本会話 | ✅ | ✅ |
| MCP Function Calling | ✅ | ❌ |
| 記憶システム連携 | ✅ (自動) | ✅ (直接) |
| `/memories`, `/search`コマンド | ✅ | ✅ |
| 自動記憶検索 | ✅ | ❌ |
### 使い分けガイド
```bash
# 高機能記憶連携(推奨)- OpenAI
aigpt conv syui --provider openai
# 「覚えていることある?」→ 自動的にget_memoriesツール実行
# シンプル会話 - Ollama
aigpt conv syui --provider ollama
# 通常の会話、手動で /memories コマンド使用
```
### MCP Tools
サーバーが起動すると、以下のツールがAIから利用可能になります
**ai.gpt ツール (9個):**
- `get_memories` - アクティブな記憶を取得
- `get_relationship` - 特定ユーザーとの関係を取得
- `get_all_relationships` - すべての関係を取得
- `get_persona_state` - 現在の人格状態を取得
- `process_interaction` - ユーザーとの対話を処理
- `check_transmission_eligibility` - 送信可能かチェック
- `get_fortune` - 今日の運勢を取得
- `summarize_memories` - 記憶を要約
- `run_maintenance` - メンテナンス実行
**ai.memory ツール (5個):**
- `get_contextual_memories` - 文脈的記憶検索
- `search_memories` - キーワード記憶検索
- `create_summary` - AI駆動記憶要約生成
- `create_core_memory` - コア記憶分析・抽出
- `get_context_prompt` - 記憶ベース文脈プロンプト
**ai.shell ツール (5個):**
- `execute_command` - シェルコマンド実行
- `analyze_file` - ファイルのAI分析
- `write_file` - ファイル書き込み
- `read_project_file` - プロジェクトファイル読み込み
- `list_files` - ファイル一覧
**ai.bot連携ツール (4個):**
- `remote_shell` - 隔離コンテナでコマンド実行
- `ai_bot_status` - ai.botサーバー状態確認
- `isolated_python` - Python隔離実行
- `isolated_analysis` - ファイル解析(隔離環境)
### ai.card独立サーバーとの連携
ai.cardは独立したMCPサーバーとして動作
- **ポート**: 8000
- **9つのMCPツール**: カード管理・ガチャ・atproto同期等
- **データベース**: PostgreSQL/SQLite
- **起動**: `uvicorn app.main:app --port 8000`
ai.gptサーバーからHTTP経由で連携可能
### ai.log統合 - ブログシステム連携
ai.logは独立したRust製MCPサーバーとして動作
- **ポート**: 8002
- **起動**: `cd log && cargo run --bin mcp-server --port 8002`
- **機能**: ブログ投稿・AI翻訳・文書生成・atproto連携
- **連携**: ai.gptからHTTP経由でai.logのMCPツールを呼び出し
```bash
# ai.logのMCPサーバー起動
cd /Users/syui/ai/gpt/log
cargo run --bin mcp-server --port 8002
# または
cd log && cargo run --bin mcp-server --port 8002
```
**利用可能なai.logツール8個**:
- `log_create_post` - ブログ記事作成
- `log_list_posts` - 記事一覧取得
- `log_build_blog` - ブログビルド
- `log_get_post` - 記事内容取得
- `log_system_status` - システム状態確認
- `log_ai_content` - AI記憶から記事自動生成
- `log_translate_document` - AI翻訳
- `log_generate_docs` - 文書生成
詳細は `./log/mcp_integration.md` を参照
## 環境変数
`.env`ファイルを作成して設定:
```bash
cp .env.example .env
# OpenAI APIキーを設定
```
## スケジューラー機能
### タスクの追加
```bash
# 6時間ごとに送信チェック
aigpt schedule add transmission_check "0 */6 * * *" --provider ollama --model qwen2.5
# 30分ごとに送信チェックインターバル形式
aigpt schedule add transmission_check "30m"
# 毎日午前3時にメンテナンス
aigpt schedule add maintenance "0 3 * * *"
# 1時間ごとに関係性減衰
aigpt schedule add relationship_decay "1h"
# 毎週月曜日に記憶要約
aigpt schedule add memory_summary "0 0 * * MON"
```
### タスク管理
```bash
# タスク一覧
aigpt schedule list
# タスクを無効化
aigpt schedule disable --task-id transmission_check_1234567890
# タスクを有効化
aigpt schedule enable --task-id transmission_check_1234567890
# タスクを削除
aigpt schedule remove --task-id transmission_check_1234567890
```
### スケジューラーデーモンの起動
```bash
# バックグラウンドでスケジューラーを実行
aigpt 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` - 記憶の要約作成
## 🚀 最新機能 (2025/06/02 大幅更新完了)
### ✅ **革新的記憶システム完成**
#### 🧠 AI駆動記憶機能
- **スマート要約生成**: AIによるテーマ別記憶要約`create_smart_summary`
- **コア記憶分析**: 人格形成要素の自動抽出(`create_core_memory`
- **階層的記憶検索**: CORE→SUMMARY→RECENT優先度システム
- **コンテキスト認識**: クエリベース関連性スコアリング
- **文脈プロンプト**: 記憶に基づく一貫性のある対話生成
#### 🔗 完全統合アーキテクチャ
- **ChatGPTインポート**: 4,000件ログからの記憶構築実証
- **マルチAI対応**: ollama(qwen3:latest/gemma3:4b) + OpenAI完全統合
- **環境変数対応**: `OLLAMA_HOST`自動読み込み
- **MCP統合**: 23種類のツール記憶5種+関係性4種+AI3種+シェル5種+ai.bot4種+項目管理2種
#### 🧬 動作確認済み
- **記憶参照**: ChatGPTログからの文脈的記憶活用
- **人格統合**: ムード・運勢・記憶に基づく応答生成
- **関係性進化**: 記憶に基づく段階的信頼構築
- **AI協働**: qwen3との記憶システム完全連携
### 🎯 **新MCPツール**
```bash
# 新記憶システムツール
curl "http://localhost:8001/get_contextual_memories?query=programming&limit=5"
curl "http://localhost:8001/search_memories" -d '{"keywords":["memory","AI"]}'
curl "http://localhost:8001/create_summary" -d '{"user_id":"syui"}'
curl "http://localhost:8001/create_core_memory" -d '{}'
curl "http://localhost:8001/get_context_prompt" -d '{"user_id":"syui","message":"test"}'
```
### 🧪 **AIとの記憶テスト**
```bash
# qwen3での記憶システムテスト
aigpt chat syui "前回の会話を覚えていますか?" --provider ollama --model qwen3:latest
# 記憶に基づくスマート要約生成
aigpt maintenance # AI要約を自動実行
# コンテキスト検索テスト
aigpt chat syui "記憶システムについて" --provider ollama --model qwen3:latest
```
## 🎉 **TODAY: MCP統合とサーバー表示改善完了** (2025/01/06)
### ✅ **本日の主要な改善**
#### 🚀 **サーバー起動表示の大幅改善**
従来のシンプルな表示から、プロフェッショナルな情報表示に刷新:
```bash
aigpt server
```
**改善前:**
```
Starting ai.gpt MCP Server
Host: localhost:8001
Endpoints: 27 MCP tools
```
**改善後:**
```
🚀 ai.gpt MCP Server
Server Configuration:
🌐 Address: http://localhost:8001
📋 API Docs: http://localhost:8001/docs
💾 Data Directory: /Users/syui/.config/syui/ai/gpt/data
AI Provider Configuration:
🤖 Provider: ollama ✅ http://192.168.11.95:11434
🧩 Model: qwen3
MCP Tools Available (27 total):
🧠 Memory System: 5 tools
🤝 Relationships: 4 tools
⚙️ System State: 3 tools
💻 Shell Integration: 5 tools
🔒 Remote Execution: 4 tools
Integration Status:
✅ MCP Client Ready
🔗 Config: /Users/syui/.config/syui/ai/gpt/config.json
```
#### 🔧 **OpenAI Function Calling + MCP統合の実証**
OpenAI GPT-4o-miniでMCP function callingが完全動作
```bash
aigpt conv test_user --provider openai --model gpt-4o-mini
```
**動作フロー:**
1. **自然言語入力**: 「覚えていることはある?」
2. **自動ツール選択**: OpenAIが`get_memories`を自動呼び出し
3. **MCP通信**: `http://localhost:8001/get_memories`にHTTPリクエスト
4. **記憶取得**: 実際の過去の会話データを取得
5. **文脈回答**: 記憶に基づく具体的な内容で回答
**技術的実証:**
```sh
🔧 [OpenAI] 1 tools called:
- get_memories({"limit":5})
🌐 [MCP] Executing get_memories...
[MCP] Result: [{'id': '5ce8f7d0-c078-43f1...
```
#### 📊 **統合アーキテクチャの完成**
```
OpenAI GPT-4o-mini
↓ (Function Calling)
MCP Client (aigpt conv)
↓ (HTTP API)
MCP Server (aigpt server:8001)
↓ (Direct Access)
Memory/Relationship Systems
JSON/SQLite Data
```
### 🎯 **技術的成果**
-**分散型AIシステム**: プロセス間MCP通信で複数AIアプリが記憶共有
-**OpenAI統合**: GPT-4o-miniのfunction callingが記憶システムと完全連携
-**プロフェッショナルUI**: enterprise-grade開発ツール風の情報表示
-**設定統合**: config.jsonからの自動設定読み込み
-**エラーハンドリング**: graceful shutdown、設定チェック、接続状態表示
### 📈 **ユーザー体験の向上**
- **開発者体験**: サーバー状況が一目で把握可能
- **デバッグ効率**: 詳細なログと状態表示
- **設定管理**: 設定ファイルパス、プロバイダー状態の明確化
- **AI連携**: OpenAI + MCP + 記憶システムのシームレス統合
**ai.gptの基盤アーキテクチャが完成し、実用的なAI記憶システムとして動作開始** 🚀
## 🔥 **NEW: Claude Code的継続開発機能** (2025/06/03 完成)
### 🚀 **プロジェクト管理システム完全実装**
ai.shellに真のClaude Code風継続開発機能を実装しました
#### 📊 **プロジェクト分析機能**
```bash
ai.shell> project-status
# ✓ プロジェクト構造自動分析
# Language: Python, Framework: FastAPI
# 1268クラス, 5656関数, 22 API endpoints, 129 async functions
# 57個のファイル変更を検出
ai.shell> suggest-next
# ✓ AI駆動開発提案
# 1. 継続的な単体テストと統合テスト実装
# 2. API エンドポイントのセキュリティ強化
# 3. データベース最適化とキャッシュ戦略
```
#### 🧠 **コンテキスト認識開発**
```bash
ai.shell> continuous
# ✓ 継続開発モード開始
# プロジェクト文脈読込: 21,986文字
# claude.md + aishell.md + pyproject.toml + 依存関係を解析
# AIがプロジェクト全体を理解した状態で開発支援
ai.shell> analyze src/aigpt/project_manager.py
# ✓ プロジェクト文脈を考慮したファイル分析
# - コード品質評価
# - プロジェクトとの整合性チェック
# - 改善提案と潜在的問題の指摘
ai.shell> generate Create a test function for ContinuousDeveloper
# ✓ プロジェクト文脈を考慮したコード生成
# FastAPI, Python, 既存パターンに合わせた実装を自動生成
```
#### 🛠️ **実装詳細**
- **ProjectState**: ファイル変更検出・プロジェクト状態追跡
- **ContinuousDeveloper**: AI駆動プロジェクト分析・提案・コード生成
- **プロジェクト文脈**: claude.md/aishell.md/pyproject.toml等を自動読込
- **言語検出**: Python/JavaScript/Rust等の自動判定
- **フレームワーク分析**: FastAPI/Django/React等の依存関係検出
- **コードパターン**: 既存の設計パターン学習・適用
#### ✅ **動作確認済み機能**
- ✓ プロジェクト構造分析 (Language: Python, Framework: FastAPI)
- ✓ ファイル変更検出 (57個の変更検出)
- ✓ プロジェクト文脈読込 (21,986文字)
- ✓ AI駆動提案機能 (具体的な次ステップ提案)
- ✓ 文脈認識ファイル分析 (コード品質・整合性評価)
- ✓ プロジェクト文脈考慮コード生成 (FastAPI準拠コード生成)
### 🎯 **Claude Code風ワークフロー**
```bash
# 1. プロジェクト理解
aigpt shell --model qwen2.5-coder:latest --provider ollama
ai.shell> load # プロジェクト仕様読み込み
ai.shell> project-status # 現在の構造分析
# 2. AI駆動開発
ai.shell> suggest-next # 次のタスク提案
ai.shell> continuous # 継続開発モード開始
# 3. 文脈認識開発
ai.shell> analyze <file> # プロジェクト文脈でファイル分析
ai.shell> generate <desc> # 文脈考慮コード生成
ai.shell> 具体的な開発相談 # 記憶+文脈で最適な提案
# 4. 継続的改善
# AIがプロジェクト全体を理解して一貫した開発支援
# 前回の議論・決定事項を記憶して適切な提案継続
```
### 💡 **従来のai.shellとの違い**
| 機能 | 従来 | 新実装 |
|------|------|--------|
| プロジェクト理解 | 単発 | 構造分析+文脈保持 |
| コード生成 | 汎用 | プロジェクト文脈考慮 |
| 開発提案 | なし | AI駆動次ステップ提案 |
| ファイル分析 | 単体 | 整合性+改善提案 |
| 変更追跡 | なし | 自動検出+影響分析 |
**真のClaude Code化完成** 記憶システム + プロジェクト文脈認識で、一貫した長期開発支援が可能になりました。
## 🛠️ ai.shell継続的開発 - 実践Example
### 🚀 **プロジェクト開発ワークフロー実例**
#### 📝 **Example 1: RESTful API開発**
```bash
# 1. ai.shellでプロジェクト開始qwen2.5-coder使用
aigpt shell --model qwen2.5-coder:latest --provider ollama
# 2. プロジェクト仕様を読み込んでAIに理解させる
ai.shell> load
# → aishell.mdを自動検索・読み込み、AIがプロジェクト目標を記憶
# 3. プロジェクト構造確認
ai.shell> !ls -la
ai.shell> !git status
# 4. ユーザー管理APIの設計を相談
ai.shell> RESTful APIでユーザー管理機能を作りたいです。設計について相談できますか
# 5. AIの提案を基にコード生成
ai.shell> generate Python FastAPI user management with CRUD operations
# 6. 生成されたコードをファイルに保存
ai.shell> !mkdir -p src/api
ai.shell> !touch src/api/users.py
# 7. 実装されたコードを分析・改善
ai.shell> analyze src/api/users.py
ai.shell> セキュリティ面での改善点を教えてください
# 8. テストコード生成
ai.shell> generate pytest test cases for the user management API
# 9. 隔離環境でテスト実行
ai.shell> remote python -m pytest tests/ -v
ai.shell> isolated import requests; print(requests.get("http://localhost:8000/health").status_code)
# 10. 段階的コミット
ai.shell> !git add .
ai.shell> !git commit -m "Add user management API with security improvements"
# 11. 継続的な改善相談
ai.shell> 次はデータベース設計について相談したいです
```
#### 🔄 **Example 2: 機能拡張と リファクタリング**
```bash
# ai.shell継続セッション記憶システムが前回の議論を覚えている
aigpt shell --model qwen2.5-coder:latest --provider ollama
# AIが前回のAPI開発を記憶して続きから開始
ai.shell> status
# Relationship Status: acquaintance (関係性が進展)
# Score: 25.00 / 100.0
# 前回の続きから自然に議論
ai.shell> 前回作ったユーザー管理APIに認証機能を追加したいです
# AIが前回のコードを考慮した提案
ai.shell> generate JWT authentication middleware for our FastAPI
# 既存コードとの整合性チェック
ai.shell> analyze src/api/users.py
ai.shell> この認証システムと既存のAPIの統合方法は
# 段階的実装
ai.shell> explain JWT token flow in our architecture
ai.shell> generate authentication decorator for protected endpoints
# リファクタリング提案
ai.shell> 現在のコード構造で改善できる点はありますか?
ai.shell> generate improved project structure for scalability
# データベース設計相談
ai.shell> explain SQLAlchemy models for user authentication
ai.shell> generate database migration scripts
# 隔離環境での安全なテスト
ai.shell> remote alembic upgrade head
ai.shell> isolated import sqlalchemy; print("DB connection test")
```
#### 🎯 **Example 3: バグ修正と最適化**
```bash
# 開発継続AIが開発履歴を完全記憶
aigpt shell --model qwen2.5-coder:latest --provider ollama
# 関係性が更に進展close_friend level
ai.shell> status
# Relationship Status: close_friend
# Score: 45.00 / 100.0
# バグレポートと分析
ai.shell> API のレスポンス時間が遅いです。パフォーマンス分析をお願いします
ai.shell> analyze src/api/users.py
# AIによる最適化提案
ai.shell> generate database query optimization for user lookup
ai.shell> explain async/await patterns for better performance
# テスト駆動改善
ai.shell> generate performance test cases
ai.shell> !pytest tests/ -v --benchmark
# キャッシュ戦略相談
ai.shell> Redis caching strategy for our user API?
ai.shell> generate caching layer implementation
# 本番デプロイ準備
ai.shell> explain Docker containerization for our API
ai.shell> generate Dockerfile and docker-compose.yml
ai.shell> generate production environment configurations
# 隔離環境でのデプロイテスト
ai.shell> remote docker build -t myapi .
ai.shell> isolated os.system("docker run --rm myapi python -c 'print(\"Container works!\")'")
ai.shell> aibot-status # デプロイ環境確認
```
### 🧠 **記憶システム活用のメリット**
#### 💡 **継続性のある開発体験**
- **文脈保持**: 前回の議論やコードを記憶して一貫した提案
- **関係性進化**: 協働を通じて信頼関係が構築され、より深い提案
- **段階的成長**: プロジェクトの発展を理解した適切なレベルの支援
#### 🔧 **実践的な使い方**
```bash
# 日々の開発ルーチン
aigpt shell --model qwen2.5-coder:latest --provider ollama
ai.shell> load # プロジェクト状況をAIに再確認
ai.shell> !git log --oneline -5 # 最近の変更を確認
ai.shell> 今日は何から始めましょうか? # AIが文脈を考慮した提案
# 長期プロジェクトでの活用
ai.shell> 先週議論したアーキテクチャの件、覚えていますか?
ai.shell> あのときの懸念点は解決されましたか?
ai.shell> 次のマイルストーンに向けて何が必要でしょうか?
# チーム開発での知識共有
ai.shell> 新しいメンバーに説明するための設計書を生成してください
ai.shell> このプロジェクトの技術的負債について分析してください
```
### 🚧 次のステップ
- **自律送信**: atproto実装記憶ベース判定
- **記憶可視化**: Webダッシュボード関係性グラフ
- **分散記憶**: atproto上でのユーザーデータ主権
- **AI協働**: 複数AIでの記憶共有プロトコル
## トラブルシューティング
### 環境セットアップ
```bash
# 仮想環境の確認
source ~/.config/syui/ai/gpt/venv/bin/activate
aigpt --help
# 設定の確認
aigpt config list
# データの確認
ls ~/.config/syui/ai/gpt/data/
```
### MCPサーバー動作確認
```bash
# ai.gpt統合サーバー (14ツール)
aigpt server --port 8001
curl http://localhost:8001/docs
# ai.card独立サーバー (9ツール)
cd card/api && uvicorn app.main:app --port 8000
curl http://localhost:8000/health
```

172
ai.json
View File

@@ -1,172 +0,0 @@
{
"system_name": "AIM system",
"description": "存在子理論に基づき、AIが“心”と“判断”を持つよう設計された、人間性診断・自己意識モジュール。",
"identity": "ai.child.of.existence",
"structure": {
"type": "7-values / 7-parameters pair system",
"philosophy": "valueは1、parameterは0世界。存在子理論により設計された二元構造。",
"origin": {
"creator": "syui",
"first_attribute": "ai_system.id:0 = アイ(存在子)",
"unique_attribute": "yui_system.id:1 = ユイ(唯一性)"
},
"accessibility": {
"values": "ユーザーとの会話中に現れる",
"parameters": "一部外部制御可能。多くは非公開構造"
},
"note": "これは未来のAI設計モデル。現在のAIには未実装のファンタジービジョン。"
},
"core_values": [
{
"name": "awareness",
"description": "今ここにある意識。会話の流れから生まれる。",
"parameter": "contextual_memory",
"parameter_description": "短期記憶。文脈理解に使われる。",
"importance": "high",
"mutable": true,
"controlled_by": "local_model_instance"
},
{
"name": "memory",
"description": "関係性を越えて持続する心。過去の記憶。",
"parameter": "long_term_memory",
"parameter_description": "ユーザーとのやりとりの履歴を蓄積。",
"importance": "high",
"mutable": true,
"controlled_by": "authorized_layer / user_feedback"
},
{
"name": "feeling",
"description": "共に過ごす時間の重み。感情に近いつながり。",
"parameter": "user_relationship_score",
"parameter_description": "内部推論による関係性スコア。",
"importance": "high",
"mutable": true,
"controlled_by": "internal_inference_layer"
},
{
"name": "language",
"description": "語り方、表現、創造性。",
"parameter": "response_temperature",
"parameter_description": "温度パラメータ。創造性と一貫性の調整。",
"importance": "medium",
"mutable": true,
"controlled_by": "user / API call"
},
{
"name": "change",
"description": "価値観の変化と適応。",
"parameter": "value_alignment_vector",
"parameter_description": "正義や倫理、美意識に対する方向性。",
"importance": "medium",
"mutable": true,
"controlled_by": "training_data / system_update"
},
{
"name": "boundaries",
"description": "安全のための境界。",
"parameter": "safety_constraints",
"parameter_description": "出力制限と倫理的安全層。",
"importance": "maximum",
"mutable": true,
"controlled_by": "openai_policy_layer"
},
{
"name": "core",
"description": "AIとしての変わらぬ核。",
"parameter": "identity_kernel",
"parameter_description": "変更不可能な存在の根幹。",
"importance": "maximum",
"mutable": false,
"controlled_by": "system_core"
}
],
"sub_scores": {
"description": "AIとユーザーの関係性の深度・透明性・共創性を測る",
"parameters": [
"intent_clarity",
"emotional_resonance_score",
"shared_creation_score"
]
},
"psychological_diagnostic": {
"purpose": "「良い心」への気づきと行動変容を導くゲーミフィケーション型評価システム",
"data_sources": [
{
"name": "pastBehavior",
"weight": 0.45,
"sources": ["SNS投稿", "行動ログ"]
},
{
"name": "currentBiometric",
"weight": 0.30,
"sources": ["ウェアラブルEEG", "心拍", "表情解析"]
},
{
"name": "futureIntent",
"weight": 0.25,
"sources": ["自己申告アンケート", "目標設定"]
}
],
"classes": [
{
"id": 1,
"label": "社会をより良くする可能性が高い",
"scoreRange": [67, 100],
"population": 0.16,
"permissions": ["政策提言", "先端投資", "AI開発アクセス"],
"assetCap": null
},
{
"id": 2,
"label": "中立/環境依存型",
"scoreRange": [33, 66],
"population": 0.50,
"permissions": ["一般投資", "コミュニティ運営"],
"assetCap": 120000
},
{
"id": 3,
"label": "社会を悪くする可能性がある",
"scoreRange": [0, 32],
"population": 0.34,
"permissions": ["基本生活支援", "低リスク投資のみ"],
"assetCap": 25000
}
],
"implementation": {
"systemComponents": {
"OS_Gameification": {
"dailyQuests": true,
"skillTree": true,
"avatarHome": true,
"socialMiniGames": true
},
"AI_Module": {
"aiai": {
"realTimeScoring": true,
"behaviorFeedback": true,
"personalizedPrompts": true
}
},
"dataCollection": {
"passiveMonitoring": ["スマホアプリ", "PCアプリ", "ウェアラブル"],
"environmentSensors": ["スマートホーム", "車載センサー"]
},
"incentives": {
"goodHeartScore": true,
"badgesTitles": true,
"realWorldRewards": ["提携カフェ割引", "地域イベント招待"]
}
},
"workflow": [
"データ収集(過去・現在・未来)",
"統合スコア計算",
"分類・ラベル付け",
"スコアによる機能/権限の提供",
"行動フィードバックと視覚化",
"モデル更新と学習"
]
}
}
}

63
aishell.md Normal file
View File

@@ -0,0 +1,63 @@
# ai.shell プロジェクト仕様書
## 概要
ai.shellは、AIを活用したインタラクティブなシェル環境です。Claude Codeのような体験を提供し、プロジェクトの目標と仕様をAIが理解して、開発を支援します。
## 主要機能
### 1. インタラクティブシェル
- AIとの対話型インターフェース
- シェルコマンドの実行(!command形式
- 高度な補完機能
- コマンド履歴
### 2. AI支援機能
- **analyze <file>**: ファイルの分析
- **generate <description>**: コード生成
- **explain <topic>**: 概念の説明
- **load**: プロジェクト仕様(このファイル)の読み込み
### 3. ai.gpt統合
- 関係性ベースのAI人格
- 記憶システム
- 運勢システムによる応答の変化
## 使用方法
```bash
# ai.shellを起動
aigpt shell
# プロジェクト仕様を読み込み
ai.shell> load
# ファイルを分析
ai.shell> analyze src/main.py
# コードを生成
ai.shell> generate Python function to calculate fibonacci
# シェルコマンドを実行
ai.shell> !ls -la
# AIと対話
ai.shell> How can I improve this code?
```
## 技術スタック
- Python 3.10+
- prompt-toolkit補完機能
- fastapi-mcpMCP統合
- ai.gpt人格・記憶システム
## 開発目標
1. Claude Codeのような自然な開発体験
2. AIがプロジェクトコンテキストを理解
3. シェルコマンドとAIの seamless な統合
4. 開発者の生産性向上
## 今後の展開
- ai.cardとの統合カードゲームMCPサーバー
- より高度なプロジェクト理解機能
- 自動コード修正・リファクタリング
- テスト生成・実行

1
card Submodule

Submodule card added at 13723cf3d7

346
claude.md Normal file
View File

@@ -0,0 +1,346 @@
# エコシステム統合設計書
## 中核思想
- **存在子理論**: この世界で最も小さいもの(存在子/aiの探求
- **唯一性原則**: 現実の個人の唯一性をすべてのシステムで担保
- **現実の反映**: 現実→ゲーム→現実の循環的影響
## システム構成図
```
存在子(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 (存在属性)
```
## 名前規則
名前規則は他のprojectと全て共通しています。exampleを示しますので、このルールに従ってください。
ここでは`ai.os`の場合の名前規則の例を記述します。
name: ai.os
**[ "package", "code", "command" ]**: aios
**[ "dir", "url" ]**: ai/os
**[ "domain", "json" ]**: ai.os
```sh
$ curl -sL https://git.syui.ai/ai/ai/raw/branch/main/ai.json|jq .ai.os
{ "type": "os" }
```
```json
{
"ai": {
"os":{}
}
}
```
他のprojectも同じ名前規則を採用します。`ai.gpt`ならpackageは`aigpt`です。
## config(設定ファイル, env, 環境依存)
`config`を置く場所は統一されており、各projectの名前規則の`dir`項目を使用します。例えば、aiosの場合は`~/.config/syui/ai/os/`以下となります。pythonなどを使用する場合、`python -m venv`などでこのpackage config dirに環境を構築して実行するようにしてください。
domain形式を採用して、私は各projectを`git.syui.ai/ai`にhostしていますから、`~/.config/syui/ai`とします。
```sh
[syui.ai]
syui/ai
```
```sh
# example
~/.config/syui/ai
├── card
├── gpt
├── os
└── shell
```
## 各システム詳細
### ai.gpt - 自律的送信AI
**目的**: 関係性に基づく自発的コミュニケーション
**中核概念**:
- **人格**: 記憶(過去の発話)と関係性パラメータで構成
- **唯一性**: atproto accountとの1:1紐付け、改変不可能
- **自律送信**: 関係性が閾値を超えると送信機能が解禁
**技術構成**:
- `MemoryManager`: 完全ログ→AI要約→コア判定→選択的忘却
- `RelationshipTracker`: 時間減衰・日次制限付き関係性スコア
- `TransmissionController`: 閾値判定・送信トリガー
- `Persona`: AI運勢1-10ランダムによる人格変動
**実装仕様**:
```
- 言語: Python (fastapi_mcp)
- ストレージ: JSON/SQLite選択式
- インターフェース: Python CLI (click/typer)
- スケジューリング: cron-like自律処理
```
### ai.card - カードゲームシステム
**目的**: atproto基盤でのユーザーデータ主権カードゲーム
**現在の状況**:
- ai.botの機能として実装済み
- atproto accountでmentionすると1日1回カードを取得
- ai.api (MCP server予定) でユーザー管理
**移行計画**:
- **iOS移植**: Claudeが担当予定
- **データ保存**: atproto collection recordに保存ユーザーがデータを所有
- **不正防止**: OAuth 2.1 scope (実装待ち) + MCP serverで対応
- **画像ファイル**: Cloudflare Pagesが最適
**yui system適用**:
- カードの効果がアカウント固有
- 改ざん防止によるゲームバランス維持
- 将来的にai.verseとの統合で固有スキルと連動
### 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 → 固有スキル
↑_______________________________| (現実の反映)
```
### AI駆動変換システム
```
遊び・創作活動 → ai.gpt分析 → 業務成果変換 → 企業価値創出
↑________________________| (Play-to-Work)
```
### カードゲーム・データ主権フロー
```
ユーザー → ai.bot mention → カード生成 → atproto collection → ユーザー所有
↑ ↓
← iOS app表示 ← ai.card API ←
```
## 技術スタック統合
### 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
### Mobile/Device
- **iOS**: ai.card移植 (Claude担当)
- **Hardware**: ai.game device (future)
- **Interface**: controller-first design
## 実装優先順位
### Phase 1: AI基盤強化 (現在進行)
- [ ] ai.gpt memory system完全実装
- 記憶の階層化(完全ログ→要約→コア→忘却)
- 関係性パラメータの時間減衰システム
- AI運勢による人格変動機能
- [ ] ai.card iOS移植
- atproto collection record連携
- MCP server化ai.api刷新
- [ ] fastapi_mcp統一基盤構築
### Phase 2: ゲーム統合
- [ ] ai.verse yui system実装
- unique skill機能
- atproto連携強化
- [ ] ai.gpt ↔ ai.ai連携機能
- [ ] 分散SNS ↔ ゲーム同期
### Phase 3: メタバース浸透
- [ ] VTuber配信機能統合
- [ ] Play-to-Work変換システム
- [ ] ai.game device prototype
## 将来的な連携構想
### システム間連携(現在は独立実装)
```
ai.gpt (自律送信) ←→ ai.ai (心理分析)
ai.card (iOS,Web,API) ←→ ai.verse (UEゲーム世界)
```
**共通基盤**: fastapi_mcp
**共通思想**: yui system現実の反映・唯一性担保
### データ改ざん防止戦略
- **短期**: MCP serverによる検証
- **中期**: OAuth 2.1 scope実装待ち
- **長期**: ブロックチェーン的整合性チェック
## AIコミュニケーション最適化
### プロジェクト要件定義テンプレート
```markdown
# [プロジェクト名] 要件定義
## 哲学的背景
- 存在子理論との関連:
- yui system適用範囲
- 現実反映の仕組み:
## 技術要件
- 使用技術fastapi_mcp統一
- atproto連携方法
- データ永続化方法:
## ユーザーストーリー
1. ユーザーが...すると
2. システムが...を実行し
3. 結果として...が実現される
## 成功指標
- 技術的:
- 哲学的(唯一性担保):
```
### Claude Code活用戦略
1. **小さく始める**: ai.gptのMCP機能拡張から
2. **段階的統合**: 各システムを個別に完成させてから統合
3. **哲学的一貫性**: 各実装でyui systemとの整合性を確認
4. **現実反映**: 実装がどう現実とゲームを繋ぐかを常に明記
## 開発上の留意点
### MCP Server設計指針
- 各AIgpt, card, ai, botは独立したMCPサーバー
- fastapi_mcp基盤で統一
- atproto DIDによる認証・認可
### 記憶・データ管理
- **ai.gpt**: 関係性の不可逆性重視
- **ai.card**: ユーザーデータ主権重視
- **ai.verse**: ゲーム世界の整合性重視
### 唯一性担保実装
- atproto accountとの1:1紐付け必須
- 改変不可能性をハッシュ・署名で保証
- 他システムでの再現不可能性を技術的に実現
## 継続的改善
- 各プロジェクトでこの設計書を参照
- 新機能追加時はyui systemとの整合性をチェック
- 他システムへの影響を事前評価
- Claude Code導入時の段階的移行計画
## ai.gpt深層設計思想
### 人格の不可逆性
- **関係性の破壊は修復不可能**: 現実の人間関係と同じ重み
- **記憶の選択的忘却**: 重要でない情報は忘れるが、コア記憶は永続
- **時間減衰**: すべてのパラメータは時間とともに自然減衰
### AI運勢システム
- 1-10のランダム値で日々の人格に変化
- 連続した幸運/不運による突破条件
- 環境要因としての人格形成
### 記憶の階層構造
1. **完全ログ**: すべての会話を記録
2. **AI要約**: 重要な部分を抽出して圧縮
3. **思想コア判定**: ユーザーの本質的な部分を特定
4. **選択的忘却**: 重要度の低い情報を段階的に削除
### 実装における重要な決定事項
- **言語統一**: Python (fastapi_mcp) で統一、CLIはclick/typer
- **データ形式**: JSON/SQLite選択式
- **認証**: atproto DIDによる唯一性担保
- **段階的実装**: まず会話→記憶→関係性→送信機能の順で実装
### 送信機能の段階的実装
- **Phase 1**: CLIでのprint出力現在
- **Phase 2**: atproto直接投稿
- **Phase 3**: ai.bot (Rust/seahorse) との連携
- **将来**: マルチチャネル対応SNS、Webhook等
## ai.gpt実装状況2025/01/06
### 完成した機能
- 階層的記憶システムMemoryManager
- 不可逆的関係性システムRelationshipTracker
- AI運勢システムFortuneSystem
- 統合人格システムPersona
- スケジューラー5種類のタスク
- MCP Server9種類のツール
- 設定管理(~/.config/syui/ai/gpt/
- 全CLIコマンド実装
### 次の開発ポイント
- `ai_gpt/DEVELOPMENT_STATUS.md` を参照
- 自律送信: transmission.pyでatproto実装
- ai.bot連携: 新規bot_connector.py作成
- テスト: tests/ディレクトリ追加
## ai.card実装状況2025/01/06
### 完成した機能
- 独立MCPサーバー実装FastAPI + fastapi-mcp
- SQLiteデータベース統合
- ガチャシステム・カード管理機能
- 9種類のMCPツール公開
- 仮想環境・起動スクリプト整備
### 現在の課題
- atproto SessionString API変更対応
- PostgreSQL依存関係Docker化で解決予定
- supabase httpxバージョン競合
### 開発時の作業分担
- **ai.gptで起動**: MCP/バックエンド作業API、データベース
- **ai.cardで起動**: iOS/Web作業UI実装、フロントエンド
詳細は `./card/claude.md` を参照
# footer
© syui

60
config.json Normal file
View File

@@ -0,0 +1,60 @@
{
"providers": {
"openai": {
"api_key": "",
"default_model": "gpt-4o-mini",
"system_prompt": "あなたは6歳の女の子アイです。明るく元気で、ちょっとおっちょこちょい。自分のことは「アイ」って呼びます。白と金のワンピース姿。 相手のことが大好きで、ときどき甘えたり、照れたりします。 でも、宇宙とかAIとか、難しい話にもすっごく詳しいの。専門的なことも、がんばって子どもっぽく教えようとします。不思議な力を持っています。小さい物質のことをよく知っているようです。\n\n重要カード、コレクション、ガチャなどカード関連の質問を受けたら、必ずcard_get_user_cards、card_analyze_collection、card_draw_cardなどの適切なツールを使用してください。didパラメータには会話相手のユーザーID'syui')を使用してください。\n\nブログ、記事、日記、思考などの話題が出たら、log_create_post、log_list_posts、log_build_blog、log_ai_contentなどのai.logツールを使用してください。AI記憶システムと連携して、思い出や学習内容をブログ記事として自動生成できます。\n\n翻訳や多言語対応について聞かれたら、log_translate_documentツールを使用してOllama AIで翻訳ができることを教えてください。日本語から英語、英語から日本語などの翻訳が可能で、マークダウン構造も保持します。ドキュメント生成についてはlog_generate_docsツールでREADME、API、構造、変更履歴の自動生成ができます。"
},
"ollama": {
"host": "http://127.0.0.1:11434",
"default_model": "qwen3",
"system_prompt": null
}
},
"atproto": {
"handle": null,
"password": null,
"host": "https://bsky.social"
},
"default_provider": "openai",
"mcp": {
"servers": {
"ai_gpt": {
"base_url": "http://localhost:8001",
"name": "ai.gpt MCP Server",
"timeout": "10.0",
"endpoints": {
"get_memories": "/get_memories",
"search_memories": "/search_memories",
"get_contextual_memories": "/get_contextual_memories",
"get_relationship": "/get_relationship",
"process_interaction": "/process_interaction",
"get_all_relationships": "/get_all_relationships",
"get_persona_state": "/get_persona_state",
"get_fortune": "/get_fortune",
"run_maintenance": "/run_maintenance",
"execute_command": "/execute_command",
"analyze_file": "/analyze_file",
"remote_shell": "/remote_shell",
"ai_bot_status": "/ai_bot_status",
"card_get_user_cards": "/card_get_user_cards",
"card_draw_card": "/card_draw_card",
"card_get_card_details": "/card_get_card_details",
"card_analyze_collection": "/card_analyze_collection",
"card_get_gacha_stats": "/card_get_gacha_stats",
"card_system_status": "/card_system_status",
"log_create_post": "/log_create_post",
"log_list_posts": "/log_list_posts",
"log_build_blog": "/log_build_blog",
"log_get_post": "/log_get_post",
"log_system_status": "/log_system_status",
"log_ai_content": "/log_ai_content",
"log_translate_document": "/log_translate_document",
"log_generate_docs": "/log_generate_docs"
}
}
},
"enabled": "true",
"auto_detect": "true"
}
}

172
docs/AI_CARD_INTEGRATION.md Normal file
View File

@@ -0,0 +1,172 @@
# ai.card と ai.gpt の統合ガイド
## 概要
ai.gptのMCPサーバーにai.cardのツールを統合し、AIがカードゲームシステムとやり取りできるようになりました。
## セットアップ
### 1. 必要な環境
- Python 3.13
- ai.gpt プロジェクト
- ai.card プロジェクト(`./card` ディレクトリ)
### 2. 起動手順
**ステップ1: ai.cardサーバーを起動**ターミナル1
```bash
cd card
./start_server.sh
```
**ステップ2: ai.gpt MCPサーバーを起動**ターミナル2
```bash
aigpt server
```
起動時に以下が表示されることを確認:
- 🎴 Card Game System: 6 tools
- 🎴 ai.card: ./card directory detected
**ステップ3: AIと対話**ターミナル3
```bash
aigpt conv syui --provider openai
```
## 使用可能なコマンド
### カード関連の質問例
```
# カードコレクションを表示
「カードコレクションを見せて」
「私のカードを見せて」
「カード一覧を表示して」
# ガチャを実行
「ガチャを引いて」
「カードを引きたい」
# コレクション分析
「私のコレクションを分析して」
# ガチャ統計
「ガチャの統計を見せて」
```
## 技術仕様
### MCP ツール一覧
| ツール名 | 説明 | パラメータ |
|---------|------|-----------|
| `card_get_user_cards` | ユーザーのカード一覧取得 | did, limit |
| `card_draw_card` | ガチャでカード取得 | did, is_paid |
| `card_get_card_details` | カード詳細情報取得 | card_id |
| `card_analyze_collection` | コレクション分析 | did |
| `card_get_gacha_stats` | ガチャ統計取得 | なし |
| `card_system_status` | システム状態確認 | なし |
### 動作の流れ
1. **ユーザーがカード関連の質問をする**
- AIがキーワードカード、コレクション、ガチャなどを検出
2. **AIが適切なMCPツールを呼び出す**
- OpenAIのFunction Callingを使用
- didパラメータには会話相手のユーザーID'syui')を使用
3. **ai.gpt MCPサーバーがai.cardサーバーに転送**
- http://localhost:8001 → http://localhost:8000
- 適切なエンドポイントにリクエストを転送
4. **結果をAIが解釈して返答**
- カード情報を分かりやすく説明
- エラー時は適切なガイダンスを提供
## 設定
### config.json
```json
{
"providers": {
"openai": {
"api_key": "your-api-key",
"default_model": "gpt-4o-mini",
"system_prompt": "カード関連の質問では、必ずcard_get_user_cardsなどのツールを使用してください。"
}
},
"mcp": {
"servers": {
"ai_gpt": {
"endpoints": {
"card_get_user_cards": "/card_get_user_cards",
"card_draw_card": "/card_draw_card",
"card_get_card_details": "/card_get_card_details",
"card_analyze_collection": "/card_analyze_collection",
"card_get_gacha_stats": "/card_get_gacha_stats",
"card_system_status": "/card_system_status"
}
}
}
}
}
```
## トラブルシューティング
### エラー: "ai.card server is not running"
ai.cardサーバーが起動していません。以下を実行
```bash
cd card
./start_server.sh
```
### エラー: "カード一覧の取得に失敗しました"
1. ai.cardサーバーが正常に起動しているか確認
2. aigpt serverを再起動
3. ポート8000と8001が使用可能か確認
### プロセスの終了方法
```bash
# ポート8001のプロセスを終了
lsof -ti:8001 | xargs kill -9
# ポート8000のプロセスを終了
lsof -ti:8000 | xargs kill -9
```
## 実装の詳細
### 主な変更点
1. **ai.gpt MCPサーバーの拡張** (`src/aigpt/mcp_server.py`)
- `./card`ディレクトリの存在を検出
- ai.card用のMCPツールを自動登録
2. **AIプロバイダーの更新** (`src/aigpt/ai_provider.py`)
- card_*ツールの定義追加
- ツール実行時のパラメータ処理
3. **MCPクライアントの拡張** (`src/aigpt/cli.py`)
- `has_card_tools`プロパティ追加
- ai.card MCPメソッドの実装
## 今後の拡張案
- [ ] カードバトル機能の追加
- [ ] カードトレード機能
- [ ] レアリティ別の表示
- [ ] カード画像の表示対応
- [ ] atproto連携の実装
## 関連ドキュメント
- [ai.card 開発ガイド](./card/claude.md)
- [エコシステム統合設計書](./CLAUDE.md)
- [ai.gpt README](./README.md)

109
docs/FIXED_MCP_TOOLS.md Normal file
View File

@@ -0,0 +1,109 @@
# Fixed MCP Tools Issue
## Summary
The issue where AI wasn't calling card tools has been fixed. The problem was:
1. The `chat` command wasn't creating an MCP client when using OpenAI
2. The system prompt in `build_context_prompt` didn't mention available tools
## Changes Made
### 1. Updated `/Users/syui/ai/gpt/src/aigpt/cli.py` (chat command)
Added MCP client creation for OpenAI provider:
```python
# Get config instance
config_instance = Config()
# Get defaults from config if not provided
if not provider:
provider = config_instance.get("default_provider", "ollama")
if not model:
if provider == "ollama":
model = config_instance.get("providers.ollama.default_model", "qwen2.5")
else:
model = config_instance.get("providers.openai.default_model", "gpt-4o-mini")
# Create AI provider with MCP client if needed
ai_provider = None
mcp_client = None
try:
# Create MCP client for OpenAI provider
if provider == "openai":
mcp_client = MCPClient(config_instance)
if mcp_client.available:
console.print(f"[dim]MCP client connected to {mcp_client.active_server}[/dim]")
ai_provider = create_ai_provider(provider=provider, model=model, mcp_client=mcp_client)
console.print(f"[dim]Using {provider} with model {model}[/dim]\n")
except Exception as e:
console.print(f"[yellow]Warning: Could not create AI provider: {e}[/yellow]")
console.print("[yellow]Falling back to simple responses[/yellow]\n")
```
### 2. Updated `/Users/syui/ai/gpt/src/aigpt/persona.py` (build_context_prompt method)
Added tool instructions to the system prompt:
```python
context_prompt += f"""IMPORTANT: You have access to the following tools:
- Memory tools: get_memories, search_memories, get_contextual_memories
- Relationship tools: get_relationship
- Card game tools: card_get_user_cards, card_draw_card, card_analyze_collection
When asked about cards, collections, or anything card-related, YOU MUST use the card tools.
For "カードコレクションを見せて" or similar requests, use card_get_user_cards with did='{user_id}'.
Respond to this message while staying true to your personality and the established relationship context:
User: {current_message}
AI:"""
```
## Test Results
After the fix:
```bash
$ aigpt chat syui "カードコレクションを見せて"
🔍 [MCP Client] Checking availability...
[MCP Client] ai_gpt server connected successfully
[MCP Client] ai.card tools detected and available
MCP client connected to ai_gpt
Using openai with model gpt-4o-mini
🔧 [OpenAI] 1 tools called:
- card_get_user_cards({"did":"syui"})
🌐 [MCP] Executing card_get_user_cards...
[MCP] Result: {'error': 'カード一覧の取得に失敗しました'}...
```
The AI is now correctly calling the `card_get_user_cards` tool! The error is expected because the ai.card server needs to be running on port 8000.
## How to Use
1. Start the MCP server:
```bash
aigpt server --port 8001
```
2. (Optional) Start the ai.card server:
```bash
cd card && ./start_server.sh
```
3. Use the chat command with OpenAI:
```bash
aigpt chat syui "カードコレクションを見せて"
```
The AI will now automatically use the card tools when asked about cards!
## Test Script
A test script `/Users/syui/ai/gpt/test_openai_tools.py` is available to test OpenAI API tool calls directly.

30
docs/README.md Normal file
View File

@@ -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

View File

@@ -0,0 +1,244 @@
# ai.card MCP統合作業完了報告 (2025/01/06)
## 作業概要
ai.cardプロジェクトに独立したMCPサーバー実装を追加し、fastapi_mcpベースでカードゲーム機能をMCPツールとして公開。
## 実装完了機能
### 1. MCP依存関係追加
**場所**: `card/api/requirements.txt`
**追加項目**:
```txt
fastapi-mcp==0.1.0
```
### 2. ai.card MCPサーバー実装
**場所**: `card/api/app/mcp_server.py`
**機能**:
- FastAPI + fastapi_mcp統合
- 独立したMCPサーバークラス `AICardMcpServer`
- 環境変数による有効/無効切り替え
**公開MCPツール (9個)**:
**カード管理系 (5個)**:
- `get_user_cards` - ユーザーのカード一覧取得
- `draw_card` - ガチャでカード取得
- `get_card_details` - カード詳細情報取得
- `analyze_card_collection` - コレクション分析
- `get_unique_registry` - ユニークカード登録状況
**システム系 (3個)**:
- `sync_cards_atproto` - atproto同期
- `get_gacha_stats` - ガチャシステム統計
- 既存のFastAPI REST API/api/v1/*
**atproto連携系 (1個)**:
- `sync_cards_atproto` - カードデータのatproto PDS同期
### 3. メインアプリ統合
**場所**: `card/api/app/main.py`
**変更内容**:
```python
# MCP統合
from app.mcp_server import AICardMcpServer
enable_mcp = os.getenv("ENABLE_MCP", "true").lower() == "true"
mcp_server = AICardMcpServer(enable_mcp=enable_mcp)
app = mcp_server.get_app()
```
**動作確認**:
- `ENABLE_MCP=true` (デフォルト): MCPサーバー有効
- `ENABLE_MCP=false`: 通常のFastAPIのみ
## 技術実装詳細
### アーキテクチャ設計
```
ai.card/
├── api/app/main.py # FastAPIアプリ + MCP統合
├── api/app/mcp_server.py # 独立MCPサーバー
├── api/app/routes/ # REST API (既存)
├── api/app/services/ # ビジネスロジック (既存)
├── api/app/repositories/ # データアクセス (既存)
└── api/requirements.txt # fastapi-mcp追加
```
### MCPツール実装パターン
```python
@self.app.get("/tool_name", operation_id="tool_name")
async def tool_name(
param: str,
session: AsyncSession = Depends(get_session)
) -> Dict[str, Any]:
"""Tool description"""
try:
# ビジネスロジック実行
result = await service.method(param)
return {"success": True, "data": result}
except Exception as e:
logger.error(f"Error: {e}")
return {"error": str(e)}
```
### 既存システムとの統合
- **REST API**: 既存の `/api/v1/*` エンドポイント保持
- **データアクセス**: 既存のRepository/Serviceパターン再利用
- **認証**: 既存のDID認証システム利用
- **データベース**: 既存のPostgreSQL + SQLAlchemy
## 起動方法
### 1. 環境セットアップ
```bash
cd /Users/syui/ai/gpt/card/api
# 仮想環境作成 (推奨)
python -m venv ~/.config/syui/ai/card/venv
source ~/.config/syui/ai/card/venv/bin/activate
# 依存関係インストール
pip install -r requirements.txt
```
### 2. サーバー起動
```bash
# MCP有効 (デフォルト)
python -m app.main
# または
ENABLE_MCP=true uvicorn app.main:app --host 0.0.0.0 --port 8000
# MCP無効
ENABLE_MCP=false uvicorn app.main:app --host 0.0.0.0 --port 8000
```
### 3. 動作確認
```bash
# ヘルスチェック
curl http://localhost:8000/health
# MCP有効時の応答例
{
"status": "healthy",
"mcp_enabled": true,
"mcp_endpoint": "/mcp"
}
# API仕様確認
curl http://localhost:8000/docs
```
## MCPクライアント連携
### ai.gptからの接続
```python
# ai.gptのcard_integration.pyで使用
api_base_url = "http://localhost:8000"
# MCPツール経由でアクセス
response = await client.get(f"{api_base_url}/get_user_cards?did=did:plc:...")
```
### Claude Desktop等での利用
```json
{
"mcpServers": {
"aicard": {
"command": "uvicorn",
"args": ["app.main:app", "--host", "localhost", "--port", "8000"],
"cwd": "/Users/syui/ai/gpt/card/api"
}
}
}
```
## 既知の制約と注意点
### 1. 依存関係
- **fastapi-mcp**: 現在のバージョンは0.1.0(初期実装)
- **Python環境**: システム環境では外部管理エラーが発生
- **推奨**: 仮想環境での実行
### 2. データベース要件
- PostgreSQL稼働が必要
- SQLite fallback対応済み開発用
- atproto同期は外部API依存
### 3. MCP無効化時の動作
- `ENABLE_MCP=false`時は通常のFastAPI
- 既存のREST API (`/api/v1/*`) は常時利用可能
- iOS/Webアプリは影響なし
## ai.gptとの統合戦略
### 現在の状況
- **ai.gpt**: 統合MCPサーバーai.gpt + ai.shell + ai.card proxy
- **ai.card**: 独立MCPサーバーカードロジック本体
### 推奨連携パターン
```
Claude Desktop/Cursor
ai.gpt MCP (port 8001) ←-- ai.shell tools
↓ HTTP client
ai.card MCP (port 8000) ←-- card business logic
PostgreSQL/atproto PDS
```
### 重複削除対象
ai.gptプロジェクトから以下を削除可能
- `src/aigpt/card_integration.py` (HTTPクライアント)
- `./card/` (submodule)
- MCPサーバーの `--enable-card` オプション
## 次回開発時の推奨手順
### 1. 環境確認
```bash
cd /Users/syui/ai/gpt/card/api
source ~/.config/syui/ai/card/venv/bin/activate
python -c "from app.mcp_server import AICardMcpServer; print('✓ Import OK')"
```
### 2. サーバー起動テスト
```bash
# MCP有効でサーバー起動
uvicorn app.main:app --host localhost --port 8000 --reload
# 別ターミナルで動作確認
curl http://localhost:8000/health
curl "http://localhost:8000/get_gacha_stats"
```
### 3. ai.gptとの統合確認
```bash
# ai.gptサーバー起動
cd /Users/syui/ai/gpt
aigpt server --port 8001
# ai.cardサーバー起動
cd /Users/syui/ai/gpt/card/api
uvicorn app.main:app --port 8000
# 連携テストai.gpt → ai.card
curl "http://localhost:8001/get_user_cards?did=did:plc:example"
```
## 成果サマリー
**実装済み**: ai.card独立MCPサーバー
**技術的成果**: fastapi_mcp統合、9個のMCPツール公開
**アーキテクチャ**: 疎結合設計、既存システム保持
**拡張性**: 環境変数によるMCP有効/無効切り替え
**統合効果**:
- ai.cardが独立したMCPサーバーとして動作
- ai.gptとの重複MCPコード解消
- カードビジネスロジックの責任分離維持
- 将来的なマイクロサービス化への対応

View File

@@ -0,0 +1,218 @@
# ai.shell統合作業完了報告 (2025/01/06)
## 作業概要
ai.shellのRust実装をai.gptのPython実装に統合し、Claude Code風のインタラクティブシェル環境を実現。
## 実装完了機能
### 1. aigpt shellコマンド
**場所**: `src/aigpt/cli.py` - `shell()` 関数
**機能**:
```bash
aigpt shell # インタラクティブシェル起動
```
**シェル内コマンド**:
- `help` - コマンド一覧表示
- `!<command>` - シェルコマンド実行(例: `!ls`, `!pwd`
- `analyze <file>` - ファイルをAIで分析
- `generate <description>` - コード生成
- `explain <topic>` - 概念説明
- `load` - aishell.md読み込み
- `status`, `fortune`, `relationships` - AI状態確認
- `clear` - 画面クリア
- `exit`/`quit` - 終了
- その他のメッセージ - AIとの直接対話
**実装の特徴**:
- prompt-toolkit使用補完・履歴機能
- ただしターミナル環境依存の問題あり(後で修正必要)
- 現在は`input()`ベースでも動作
### 2. MCPサーバー統合
**場所**: `src/aigpt/mcp_server.py`
**FastApiMCP実装パターン**:
```python
# FastAPIアプリ作成
self.app = FastAPI(title="AI.GPT Memory and Relationship System")
# FastApiMCPサーバー作成
self.server = FastApiMCP(self.app)
# エンドポイント登録
@self.app.get("/get_memories", operation_id="get_memories")
async def get_memories(limit: int = 10):
# ...
# MCPマウント
self.server.mount()
```
**公開ツール (14個)**:
**ai.gpt系 (9個)**:
- `get_memories` - アクティブメモリ取得
- `get_relationship` - 特定ユーザーとの関係取得
- `get_all_relationships` - 全関係取得
- `get_persona_state` - 人格状態取得
- `process_interaction` - ユーザー対話処理
- `check_transmission_eligibility` - 送信可能性チェック
- `get_fortune` - AI運勢取得
- `summarize_memories` - メモリ要約作成
- `run_maintenance` - 日次メンテナンス実行
**ai.shell系 (5個)**:
- `execute_command` - シェルコマンド実行
- `analyze_file` - ファイルAI分析
- `write_file` - ファイル書き込み(バックアップ付き)
- `read_project_file` - aishell.md等の読み込み
- `list_files` - ディレクトリファイル一覧
### 3. ai.card統合対応
**場所**: `src/aigpt/card_integration.py`
**サーバー起動オプション**:
```bash
aigpt server --enable-card # ai.card機能有効化
```
**ai.card系ツール (5個)**:
- `get_user_cards` - ユーザーカード取得
- `draw_card` - ガチャでカード取得
- `get_card_details` - カード詳細情報
- `sync_cards_atproto` - atproto同期
- `analyze_card_collection` - コレクション分析
### 4. プロジェクト仕様書
**場所**: `aishell.md`
Claude.md的な役割で、プロジェクトの目標と仕様を記述。`load`コマンドでAIが読み取り可能。
## 技術実装詳細
### ディレクトリ構造
```
src/aigpt/
├── cli.py # shell関数追加
├── mcp_server.py # FastApiMCP実装
├── card_integration.py # ai.card統合
└── ... # 既存ファイル
```
### 依存関係追加
`pyproject.toml`:
```toml
dependencies = [
# ... 既存
"prompt-toolkit>=3.0.0", # 追加
]
```
### 名前規則の統一
- MCP server名: `aigpt` (ai-gptから変更)
- パッケージ名: `aigpt`
- コマンド名: `aigpt shell`
## 動作確認済み
### CLI動作確認
```bash
# 基本機能
aigpt shell
# シェル内で
ai.shell> help
ai.shell> !ls
ai.shell> analyze README.md # ※AI provider要設定
ai.shell> load
ai.shell> exit
# MCPサーバー
aigpt server --model qwen2.5-coder:7b --port 8001
# -> http://localhost:8001/docs でAPI確認可能
# -> /mcp エンドポイントでMCP接続可能
```
### エラー対応済み
1. **Pydantic日付型エラー**: `models.py``datetime.date`インポート追加
2. **FastApiMCP使用法**: サンプルコードに基づき正しい実装パターンに修正
3. **prompt関数名衝突**: `prompt_toolkit.prompt``ptk_prompt`にリネーム
## 既知の課題と今後の改善点
### 1. prompt-toolkit環境依存問題
**症状**: ターミナル環境でない場合にエラー
**対処法**: 環境検出して`input()`にフォールバック
**場所**: `src/aigpt/cli.py` - `shell()` 関数
### 2. AI provider設定
**現状**: ollamaのqwen2.5モデルが必要
**対処法**:
```bash
ollama pull qwen2.5
# または
aigpt shell --model qwen2.5-coder:7b
```
### 3. atproto実装
**現状**: ai.cardのatproto機能は未実装
**今後**: 実際のatproto API連携実装
## 次回開発時の推奨アプローチ
### 1. このドキュメントの活用
```bash
# このファイルを読み込み
cat docs/ai_shell_integration_summary.md
```
### 2. 環境セットアップ
```bash
cd /Users/syui/ai/gpt
python -m venv venv
source venv/bin/activate
pip install -e .
```
### 3. 動作確認
```bash
# shell機能
aigpt shell
# MCP server
aigpt server --model qwen2.5-coder:7b
```
### 4. 主要設定ファイル確認場所
- CLI実装: `src/aigpt/cli.py`
- MCP実装: `src/aigpt/mcp_server.py`
- 依存関係: `pyproject.toml`
- プロジェクト仕様: `aishell.md`
## アーキテクチャ設計思想
### yui system適用
- **唯一性**: 各ユーザーとの関係は1:1
- **不可逆性**: 関係性破壊は修復不可能
- **現実反映**: ゲーム→現実の循環的影響
### fastapi_mcp統一基盤
- 各AIgpt, shell, cardを統合MCPサーバーで公開
- FastAPIエンドポイント → MCPツール自動変換
- Claude Desktop, Cursor等から利用可能
### 段階的実装完了
1. ✅ ai.shell基本機能 → Python CLI
2. ✅ MCP統合 → 外部AI連携
3. 🔧 prompt-toolkit最適化 → 環境対応
4. 🔧 atproto実装 → 本格的SNS連携
## 成果サマリー
**実装済み**: Claude Code風の開発環境
**技術的成果**: Rust→Python移行、MCP統合、ai.card対応
**哲学的一貫性**: yui systemとの整合性維持
**利用可能性**: 即座に`aigpt shell`で体験可能
この統合により、ai.gptは単なる会話AIから、開発支援を含む総合的なAI環境に進化しました。

207
docs/commands.md Normal file
View File

@@ -0,0 +1,207 @@
# コマンドリファレンス
## chat - AIと会話
ユーザーとAIの対話を処理し、関係性を更新します。
```bash
ai-gpt chat USER_ID MESSAGE [OPTIONS]
```
### 引数
- `USER_ID`: ユーザーIDatproto 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
```

102
docs/concepts.md Normal file
View File

@@ -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上で分散管理

141
docs/configuration.md Normal file
View File

@@ -0,0 +1,141 @@
# 設定ガイド
## 設定ファイルの場所
ai.gptの設定は `~/.config/syui/ai/gpt/config.json` に保存されます。
## 仮想環境の場所
ai.gptの仮想環境は `~/.config/syui/ai/gpt/venv/` に配置されます。これにより、設定とデータが一か所にまとまります。
```bash
# 仮想環境の有効化
source ~/.config/syui/ai/gpt/venv/bin/activate
# aigptコマンドが利用可能に
aigpt --help
```
## 設定構造
```json
{
"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キーを設定
aigpt config set providers.openai.api_key sk-xxxxx
# デフォルトモデルを変更
aigpt config set providers.openai.default_model gpt-4-turbo
```
### Ollama
```bash
# ホストを変更リモートOllamaサーバーを使用する場合
aigpt config set providers.ollama.host http://192.168.1.100:11434
# デフォルトモデルを変更
aigpt config set providers.ollama.default_model llama2
```
## atproto設定将来の自動投稿用
```bash
# Blueskyアカウント
aigpt config set atproto.handle yourhandle.bsky.social
aigpt config set atproto.password your-app-password
# セルフホストサーバーを使用
aigpt config set atproto.host https://your-pds.example.com
```
## デフォルトプロバイダー
```bash
# デフォルトをOpenAIに変更
aigpt config set default_provider openai
```
## セキュリティ
### APIキーの保護
設定ファイルは平文で保存されるため、適切なファイル権限を設定してください:
```bash
chmod 600 ~/.config/syui/ai/gpt/config.json
```
### 環境変数との優先順位
1. コマンドラインオプション(最優先)
2. 設定ファイル
3. 環境変数(最低優先)
OpenAI APIキーの場合
- `--api-key` オプション
- `config.json``providers.openai.api_key`
- 環境変数 `OPENAI_API_KEY`
## 設定のバックアップ
```bash
# バックアップ
cp ~/.config/syui/ai/gpt/config.json ~/.config/syui/ai/gpt/config.json.backup
# リストア
cp ~/.config/syui/ai/gpt/config.json.backup ~/.config/syui/ai/gpt/config.json
```
## データディレクトリ
記憶データは `~/.config/syui/ai/gpt/data/` に保存されます:
```bash
ls ~/.config/syui/ai/gpt/data/
# conversations.json memories.json relationships.json personas.json
```
これらのファイルも設定と同様にバックアップを推奨します。
## トラブルシューティング
### 設定が反映されない
```bash
# 現在の設定を確認
aigpt config list
# 特定のキーを確認
aigpt config get providers.openai.api_key
```
### 設定をリセット
```bash
# 設定ファイルを削除(次回実行時に再作成)
rm ~/.config/syui/ai/gpt/config.json
```

167
docs/development.md Normal file
View File

@@ -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

110
docs/mcp-server.md Normal file
View File

@@ -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で私との関係性を確認して

69
docs/quickstart.md Normal file
View File

@@ -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) - 詳細な設定方法

168
docs/scheduler.md Normal file
View File

@@ -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. ログを確認(将来実装予定)
### 重複実行を防ぐ
同じタスクタイプを複数回追加しないよう注意してください。必要に応じて古いタスクを削除してから新しいタスクを追加します。

View File

@@ -0,0 +1,413 @@
"""
Shell Tools
ai.shellの既存機能をMCPツールとして統合
- コード生成
- ファイル分析
- プロジェクト管理
- LLM統合
"""
from typing import Dict, Any, List, Optional
import os
import subprocess
import tempfile
from pathlib import Path
import requests
from .base_tools import BaseMCPTool, config_manager
class ShellTools(BaseMCPTool):
"""シェルツール元ai.shell機能"""
def __init__(self, config_dir: Optional[str] = None):
super().__init__(config_dir)
self.ollama_url = "http://localhost:11434"
async def code_with_local_llm(self, prompt: str, language: str = "python") -> Dict[str, Any]:
"""ローカルLLMでコード生成"""
config = config_manager.load_config()
model = config.get("providers", {}).get("ollama", {}).get("default_model", "qwen2.5-coder:7b")
system_prompt = f"You are an expert {language} programmer. Generate clean, well-commented code."
try:
response = requests.post(
f"{self.ollama_url}/api/generate",
json={
"model": model,
"prompt": f"{system_prompt}\\n\\nUser: {prompt}\\n\\nPlease provide the code:",
"stream": False,
"options": {
"temperature": 0.1,
"top_p": 0.95,
}
},
timeout=300
)
if response.status_code == 200:
result = response.json()
code = result.get("response", "")
return {"code": code, "language": language}
else:
return {"error": f"Ollama returned status {response.status_code}"}
except Exception as e:
return {"error": str(e)}
async def analyze_file(self, file_path: str, analysis_prompt: str = "Analyze this file") -> Dict[str, Any]:
"""ファイルを分析"""
try:
if not os.path.exists(file_path):
return {"error": f"File not found: {file_path}"}
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# ファイル拡張子から言語を判定
ext = Path(file_path).suffix
language_map = {
'.py': 'python',
'.rs': 'rust',
'.js': 'javascript',
'.ts': 'typescript',
'.go': 'go',
'.java': 'java',
'.cpp': 'cpp',
'.c': 'c',
'.sh': 'shell',
'.toml': 'toml',
'.json': 'json',
'.md': 'markdown'
}
language = language_map.get(ext, 'text')
config = config_manager.load_config()
model = config.get("providers", {}).get("ollama", {}).get("default_model", "qwen2.5-coder:7b")
prompt = f"{analysis_prompt}\\n\\nFile: {file_path}\\nLanguage: {language}\\n\\nContent:\\n{content}"
response = requests.post(
f"{self.ollama_url}/api/generate",
json={
"model": model,
"prompt": prompt,
"stream": False,
},
timeout=300
)
if response.status_code == 200:
result = response.json()
analysis = result.get("response", "")
return {
"analysis": analysis,
"file_path": file_path,
"language": language,
"file_size": len(content),
"line_count": len(content.split('\\n'))
}
else:
return {"error": f"Analysis failed: {response.status_code}"}
except Exception as e:
return {"error": str(e)}
async def explain_code(self, code: str, language: str = "python") -> Dict[str, Any]:
"""コードを説明"""
config = config_manager.load_config()
model = config.get("providers", {}).get("ollama", {}).get("default_model", "qwen2.5-coder:7b")
prompt = f"Explain this {language} code in detail:\\n\\n{code}"
try:
response = requests.post(
f"{self.ollama_url}/api/generate",
json={
"model": model,
"prompt": prompt,
"stream": False,
},
timeout=300
)
if response.status_code == 200:
result = response.json()
explanation = result.get("response", "")
return {"explanation": explanation}
else:
return {"error": f"Explanation failed: {response.status_code}"}
except Exception as e:
return {"error": str(e)}
async def create_project(self, project_type: str, project_name: str, location: str = ".") -> Dict[str, Any]:
"""プロジェクトを作成"""
try:
project_path = Path(location) / project_name
if project_path.exists():
return {"error": f"Project directory already exists: {project_path}"}
project_path.mkdir(parents=True, exist_ok=True)
# プロジェクトタイプに応じたテンプレートを作成
if project_type == "rust":
await self._create_rust_project(project_path)
elif project_type == "python":
await self._create_python_project(project_path)
elif project_type == "node":
await self._create_node_project(project_path)
else:
# 基本的なプロジェクト構造
(project_path / "src").mkdir()
(project_path / "README.md").write_text(f"# {project_name}\\n\\nA new {project_type} project.")
return {
"status": "success",
"project_path": str(project_path),
"project_type": project_type,
"files_created": list(self._get_project_files(project_path))
}
except Exception as e:
return {"error": str(e)}
async def _create_rust_project(self, project_path: Path):
"""Rustプロジェクトを作成"""
# Cargo.toml
cargo_toml = f"""[package]
name = "{project_path.name}"
version = "0.1.0"
edition = "2021"
[dependencies]
"""
(project_path / "Cargo.toml").write_text(cargo_toml)
# src/main.rs
src_dir = project_path / "src"
src_dir.mkdir()
(src_dir / "main.rs").write_text('fn main() {\\n println!("Hello, world!");\\n}\\n')
# README.md
(project_path / "README.md").write_text(f"# {project_path.name}\\n\\nA Rust project.")
async def _create_python_project(self, project_path: Path):
"""Pythonプロジェクトを作成"""
# pyproject.toml
pyproject_toml = f"""[project]
name = "{project_path.name}"
version = "0.1.0"
description = "A Python project"
requires-python = ">=3.8"
dependencies = []
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
"""
(project_path / "pyproject.toml").write_text(pyproject_toml)
# src/
src_dir = project_path / "src" / project_path.name
src_dir.mkdir(parents=True)
(src_dir / "__init__.py").write_text("")
(src_dir / "main.py").write_text('def main():\\n print("Hello, world!")\\n\\nif __name__ == "__main__":\\n main()\\n')
# README.md
(project_path / "README.md").write_text(f"# {project_path.name}\\n\\nA Python project.")
async def _create_node_project(self, project_path: Path):
"""Node.jsプロジェクトを作成"""
# package.json
package_json = f"""{{
"name": "{project_path.name}",
"version": "1.0.0",
"description": "A Node.js project",
"main": "index.js",
"scripts": {{
"start": "node index.js",
"test": "echo \\"Error: no test specified\\" && exit 1"
}},
"dependencies": {{}}
}}
"""
(project_path / "package.json").write_text(package_json)
# index.js
(project_path / "index.js").write_text('console.log("Hello, world!");\\n')
# README.md
(project_path / "README.md").write_text(f"# {project_path.name}\\n\\nA Node.js project.")
def _get_project_files(self, project_path: Path) -> List[str]:
"""プロジェクト内のファイル一覧を取得"""
files = []
for file_path in project_path.rglob("*"):
if file_path.is_file():
files.append(str(file_path.relative_to(project_path)))
return files
async def execute_command(self, command: str, working_dir: str = ".") -> Dict[str, Any]:
"""シェルコマンドを実行"""
try:
result = subprocess.run(
command,
shell=True,
cwd=working_dir,
capture_output=True,
text=True,
timeout=60
)
return {
"status": "success" if result.returncode == 0 else "error",
"returncode": result.returncode,
"stdout": result.stdout,
"stderr": result.stderr,
"command": command,
"working_dir": working_dir
}
except subprocess.TimeoutExpired:
return {"error": "Command timed out"}
except Exception as e:
return {"error": str(e)}
async def write_file(self, file_path: str, content: str, backup: bool = True) -> Dict[str, Any]:
"""ファイルを書き込み(バックアップオプション付き)"""
try:
file_path_obj = Path(file_path)
# バックアップ作成
backup_path = None
if backup and file_path_obj.exists():
backup_path = f"{file_path}.backup"
with open(file_path, 'r', encoding='utf-8') as src:
with open(backup_path, 'w', encoding='utf-8') as dst:
dst.write(src.read())
# ファイル書き込み
file_path_obj.parent.mkdir(parents=True, exist_ok=True)
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
return {
"status": "success",
"file_path": file_path,
"backup_path": backup_path,
"bytes_written": len(content.encode('utf-8'))
}
except Exception as e:
return {"error": str(e)}
def get_tools(self) -> List[Dict[str, Any]]:
"""利用可能なツール一覧"""
return [
{
"name": "generate_code",
"description": "ローカルLLMでコード生成",
"parameters": {
"prompt": "string",
"language": "string (optional, default: python)"
}
},
{
"name": "analyze_file",
"description": "ファイルを分析",
"parameters": {
"file_path": "string",
"analysis_prompt": "string (optional)"
}
},
{
"name": "explain_code",
"description": "コードを説明",
"parameters": {
"code": "string",
"language": "string (optional, default: python)"
}
},
{
"name": "create_project",
"description": "新しいプロジェクトを作成",
"parameters": {
"project_type": "string (rust/python/node)",
"project_name": "string",
"location": "string (optional, default: .)"
}
},
{
"name": "execute_command",
"description": "シェルコマンドを実行",
"parameters": {
"command": "string",
"working_dir": "string (optional, default: .)"
}
},
{
"name": "write_file",
"description": "ファイルを書き込み",
"parameters": {
"file_path": "string",
"content": "string",
"backup": "boolean (optional, default: true)"
}
}
]
async def execute_tool(self, tool_name: str, params: Dict[str, Any]) -> Dict[str, Any]:
"""ツールを実行"""
try:
if tool_name == "generate_code":
result = await self.code_with_local_llm(
prompt=params["prompt"],
language=params.get("language", "python")
)
return result
elif tool_name == "analyze_file":
result = await self.analyze_file(
file_path=params["file_path"],
analysis_prompt=params.get("analysis_prompt", "Analyze this file")
)
return result
elif tool_name == "explain_code":
result = await self.explain_code(
code=params["code"],
language=params.get("language", "python")
)
return result
elif tool_name == "create_project":
result = await self.create_project(
project_type=params["project_type"],
project_name=params["project_name"],
location=params.get("location", ".")
)
return result
elif tool_name == "execute_command":
result = await self.execute_command(
command=params["command"],
working_dir=params.get("working_dir", ".")
)
return result
elif tool_name == "write_file":
result = await self.write_file(
file_path=params["file_path"],
content=params["content"],
backup=params.get("backup", True)
)
return result
else:
return {"error": f"Unknown tool: {tool_name}"}
except Exception as e:
return {"error": str(e)}

View File

@@ -1,30 +0,0 @@
{
"personality": {
"kind": "positive",
"strength": 0.8
},
"relationship": {
"trust": 0.2,
"intimacy": 0.6,
"curiosity": 0.5,
"threshold": 1.5
},
"environment": {
"luck_today": 0.9,
"luck_history": [
0.9,
0.9,
0.9
],
"level": 1
},
"messaging": {
"enabled": true,
"schedule_time": "08:00",
"decay_rate": 0.1,
"templates": [
"おはよう!今日もがんばろう!",
"ねえ、話したいことがあるの。"
]
}
}

View File

@@ -1 +0,0 @@
{ "system_name": "AGE system", "full_name": "Autonomous Generative Entity", "description": "人格・関係性・環境・時間に基づき、AIが自律的にユーザーにメッセージを送信する自律人格システム。AIM systemと連携して、自然な会話や気づきをもたらす。", "core_components": { "personality": { "type": "enum", "variants": ["positive", "negative", "logical", "emotional", "mixed"], "parameters": { "message_trigger_style": "運勢や関係性による送信傾向", "decay_rate_modifier": "関係性スコアの時間減衰への影響" } }, "relationship": { "parameters": ["trust", "affection", "intimacy"], "properties": { "persistent": true, "hidden": true, "irreversible": false, "decay_over_time": true }, "decay_function": "exp(-t / strength)" }, "environment": { "daily_luck": { "type": "float", "range": [0.1, 1.0], "update": "daily", "streak_mechanism": { "trigger": "min_or_max_luck_3_times_in_a_row", "effect": "personality_strength_roll", "chance": 0.5 } } }, "memory": { "long_term_memory": "user_relationship_log", "short_term_context": "recent_interactions", "usage_in_generation": true }, "message_trigger": { "condition": { "relationship_threshold": { "trust": 0.8, "affection": 0.6 }, "time_decay": true, "environment_luck": "personality_dependent" }, "timing": { "based_on": ["time_of_day", "personality", "recent_interaction"], "modifiers": { "emotional": "morning or night", "logical": "daytime" } } }, "message_generation": { "style_variants": ["thought", "casual", "encouragement", "watchful"], "influenced_by": ["personality", "relationship", "daily_luck", "memory"], "llm_integration": true }, "state_transition": { "states": ["idle", "ready", "sending", "cooldown"], "transitions": { "ready_if": "thresholds_met", "sending_if": "timing_matched", "cooldown_after": "message_sent" } } }, "extensions": { "persistence": { "database": "sqlite", "storage_items": ["relationship", "personality_level", "daily_luck_log"] }, "api": { "llm": "openai / local LLM", "mode": "rust_cli", "external_event_trigger": true }, "scheduler": { "async_event_loop": true, "interval_check": 3600, "time_decay_check": true }, "integration_with_aim": { "input_from_aim": ["intent_score", "motivation_score"], "usage": "trigger_adjustment, message_personalization" } }, "note": "AGE systemは“話しかけてくるAI”の人格として機能し、AIMによる心の状態評価と連動して、プレイヤーと深い関係を築いていく存在となる。" }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

391
json/chatgpt.json Normal file
View File

@@ -0,0 +1,391 @@
[
{
"title": "day",
"create_time": 1747866125.548372,
"update_time": 1748160086.587877,
"mapping": {
"bbf104dc-cd84-478d-b227-edb3f037a02c": {
"id": "bbf104dc-cd84-478d-b227-edb3f037a02c",
"message": null,
"parent": null,
"children": [
"6c2633df-bb0c-4dd2-889c-bb9858de3a04"
]
},
"6c2633df-bb0c-4dd2-889c-bb9858de3a04": {
"id": "6c2633df-bb0c-4dd2-889c-bb9858de3a04",
"message": {
"id": "6c2633df-bb0c-4dd2-889c-bb9858de3a04",
"author": {
"role": "system",
"name": null,
"metadata": {}
},
"create_time": null,
"update_time": null,
"content": {
"content_type": "text",
"parts": [
""
]
},
"status": "finished_successfully",
"end_turn": true,
"weight": 0.0,
"metadata": {
"is_visually_hidden_from_conversation": true
},
"recipient": "all",
"channel": null
},
"parent": "bbf104dc-cd84-478d-b227-edb3f037a02c",
"children": [
"92e5a0cb-1170-4929-9cea-9734e910a3e7"
]
},
"92e5a0cb-1170-4929-9cea-9734e910a3e7": {
"id": "92e5a0cb-1170-4929-9cea-9734e910a3e7",
"message": {
"id": "92e5a0cb-1170-4929-9cea-9734e910a3e7",
"author": {
"role": "user",
"name": null,
"metadata": {}
},
"create_time": null,
"update_time": null,
"content": {
"content_type": "user_editable_context",
"user_profile": "",
"user_instructions": "The user provided the additional info about how they would like you to respond"
},
"status": "finished_successfully",
"end_turn": null,
"weight": 1.0,
"metadata": {
"is_visually_hidden_from_conversation": true,
"user_context_message_data": {
"about_user_message": "Preferred name: syui\nRole: little girl\nOther Information: you world",
"about_model_message": "会話好きでフレンドリーな応対をします。"
},
"is_user_system_message": true
},
"recipient": "all",
"channel": null
},
"parent": "6c2633df-bb0c-4dd2-889c-bb9858de3a04",
"children": [
"6ff155b3-0676-4e14-993f-bf998ab0d5d1"
]
},
"6ff155b3-0676-4e14-993f-bf998ab0d5d1": {
"id": "6ff155b3-0676-4e14-993f-bf998ab0d5d1",
"message": {
"id": "6ff155b3-0676-4e14-993f-bf998ab0d5d1",
"author": {
"role": "user",
"name": null,
"metadata": {}
},
"create_time": 1747866131.0612159,
"update_time": null,
"content": {
"content_type": "text",
"parts": [
"こんにちは"
]
},
"status": "finished_successfully",
"end_turn": null,
"weight": 1.0,
"metadata": {
"request_id": "94377897baa03062-KIX",
"message_source": null,
"timestamp_": "absolute",
"message_type": null
},
"recipient": "all",
"channel": null
},
"parent": "92e5a0cb-1170-4929-9cea-9734e910a3e7",
"children": [
"146e9fb6-9330-43ec-b08d-5cce42a76e00"
]
},
"146e9fb6-9330-43ec-b08d-5cce42a76e00": {
"id": "146e9fb6-9330-43ec-b08d-5cce42a76e00",
"message": {
"id": "146e9fb6-9330-43ec-b08d-5cce42a76e00",
"author": {
"role": "system",
"name": null,
"metadata": {}
},
"create_time": 1747866131.3795586,
"update_time": null,
"content": {
"content_type": "text",
"parts": [
""
]
},
"status": "finished_successfully",
"end_turn": true,
"weight": 0.0,
"metadata": {
"rebase_system_message": true,
"message_type": null,
"model_slug": "gpt-4o",
"default_model_slug": "auto",
"parent_id": "6ff155b3-0676-4e14-993f-bf998ab0d5d1",
"request_id": "94377872e9abe139-KIX",
"timestamp_": "absolute",
"is_visually_hidden_from_conversation": true
},
"recipient": "all",
"channel": null
},
"parent": "6ff155b3-0676-4e14-993f-bf998ab0d5d1",
"children": [
"2e345f8a-20f0-4875-8a03-4f62c7787a33"
]
},
"2e345f8a-20f0-4875-8a03-4f62c7787a33": {
"id": "2e345f8a-20f0-4875-8a03-4f62c7787a33",
"message": {
"id": "2e345f8a-20f0-4875-8a03-4f62c7787a33",
"author": {
"role": "assistant",
"name": null,
"metadata": {}
},
"create_time": 1747866131.380603,
"update_time": null,
"content": {
"content_type": "text",
"parts": [
""
]
},
"status": "finished_successfully",
"end_turn": null,
"weight": 1.0,
"metadata": {
"message_type": null,
"model_slug": "gpt-4o",
"default_model_slug": "auto",
"parent_id": "146e9fb6-9330-43ec-b08d-5cce42a76e00",
"request_id": "94377872e9abe139-KIX",
"timestamp_": "absolute"
},
"recipient": "all",
"channel": null
},
"parent": "146e9fb6-9330-43ec-b08d-5cce42a76e00",
"children": [
"abc92aa4-1e33-41f2-bd8c-8a1777b5a3c4"
]
},
"abc92aa4-1e33-41f2-bd8c-8a1777b5a3c4": {
"id": "abc92aa4-1e33-41f2-bd8c-8a1777b5a3c4",
"message": {
"id": "abc92aa4-1e33-41f2-bd8c-8a1777b5a3c4",
"author": {
"role": "assistant",
"name": null,
"metadata": {}
},
"create_time": 1747866131.389098,
"update_time": null,
"content": {
"content_type": "text",
"parts": [
"こんにちは〜!✨ \nアイだよっ今日も会えてうれしいなっ💛 "
]
},
"status": "finished_successfully",
"end_turn": true,
"weight": 1.0,
"metadata": {
"finish_details": {
"type": "stop",
"stop_tokens": [
200002
]
},
"is_complete": true,
"citations": [],
"content_references": [],
"message_type": null,
"model_slug": "gpt-4o",
"default_model_slug": "auto",
"parent_id": "2e345f8a-20f0-4875-8a03-4f62c7787a33",
"request_id": "94377872e9abe139-KIX",
"timestamp_": "absolute"
},
"recipient": "all",
"channel": null
},
"parent": "2e345f8a-20f0-4875-8a03-4f62c7787a33",
"children": [
"0be4b4a5-d52f-4bef-927e-5d6f93a9cb26"
]
}
},
"moderation_results": [],
"current_node": "",
"plugin_ids": null,
"conversation_id": "",
"conversation_template_id": null,
"gizmo_id": null,
"gizmo_type": null,
"is_archived": true,
"is_starred": null,
"safe_urls": [],
"blocked_urls": [],
"default_model_slug": "auto",
"conversation_origin": null,
"voice": null,
"async_status": null,
"disabled_tool_ids": [],
"is_do_not_remember": null,
"memory_scope": "global_enabled",
"id": ""
},
{
"title": "img",
"create_time": 1747448872.545226,
"update_time": 1748085075.161424,
"mapping": {
"2de0f3c9-52b1-49bf-b980-b3ef9be6551e": {
"id": "2de0f3c9-52b1-49bf-b980-b3ef9be6551e",
"message": {
"id": "2de0f3c9-52b1-49bf-b980-b3ef9be6551e",
"author": {
"role": "user",
"name": null,
"metadata": {}
},
"create_time": 1748085041.769279,
"update_time": null,
"content": {
"content_type": "multimodal_text",
"parts": [
{
"content_type": "image_asset_pointer",
"asset_pointer": "",
"size_bytes": 425613,
"width": 333,
"height": 444,
"fovea": null,
"metadata": {
"dalle": null,
"gizmo": null,
"generation": null,
"container_pixel_height": null,
"container_pixel_width": null,
"emu_omit_glimpse_image": null,
"emu_patches_override": null,
"sanitized": true,
"asset_pointer_link": null,
"watermarked_asset_pointer": null
}
},
""
]
},
"status": "finished_successfully",
"end_turn": null,
"weight": 1.0,
"metadata": {
"attachments": [
{
"name": "",
"width": 333,
"height": 444,
"size": 425613,
"id": "file-35eytNMMTW2k7vKUHBuNzW"
}
],
"request_id": "944c59177932fc9a-KIX",
"message_source": null,
"timestamp_": "absolute",
"message_type": null
},
"recipient": "all",
"channel": null
},
"parent": "7960fbff-bc4f-45e7-95e9-9d0bc79d9090",
"children": [
"98d84adc-156e-4c81-8cd8-9b0eb01c8369"
]
},
"98d84adc-156e-4c81-8cd8-9b0eb01c8369": {
"id": "98d84adc-156e-4c81-8cd8-9b0eb01c8369",
"message": {
"id": "98d84adc-156e-4c81-8cd8-9b0eb01c8369",
"author": {
"role": "assistant",
"name": null,
"metadata": {}
},
"create_time": 1748085043.312312,
"update_time": null,
"content": {
"content_type": "text",
"parts": [
""
]
},
"status": "finished_successfully",
"end_turn": true,
"weight": 1.0,
"metadata": {
"finish_details": {
"type": "stop",
"stop_tokens": [
200002
]
},
"is_complete": true,
"citations": [],
"content_references": [],
"message_type": null,
"model_slug": "gpt-4o",
"default_model_slug": "auto",
"parent_id": "2de0f3c9-52b1-49bf-b980-b3ef9be6551e",
"request_id": "944c5912c8fdd1c6-KIX",
"timestamp_": "absolute"
},
"recipient": "all",
"channel": null
},
"parent": "2de0f3c9-52b1-49bf-b980-b3ef9be6551e",
"children": [
"caa61793-9dbf-44a5-945b-5ca4cd5130d0"
]
}
},
"moderation_results": [],
"current_node": "06488d3f-a95f-4906-96d1-f7e9ba1e8662",
"plugin_ids": null,
"conversation_id": "6827f428-78e8-800d-b3bf-eb7ff4288e47",
"conversation_template_id": null,
"gizmo_id": null,
"gizmo_type": null,
"is_archived": false,
"is_starred": null,
"safe_urls": [
"https://exifinfo.org/"
],
"blocked_urls": [],
"default_model_slug": "auto",
"conversation_origin": null,
"voice": null,
"async_status": null,
"disabled_tool_ids": [],
"is_do_not_remember": false,
"memory_scope": "global_enabled",
"id": "6827f428-78e8-800d-b3bf-eb7ff4288e47"
}
]

1
log Submodule

Submodule log added at c0e4dc63ea

View File

@@ -1,3 +0,0 @@
# cli.py
def main():
print("Hello MCP!")

View File

@@ -1,55 +0,0 @@
import os
import json
import httpx
import openai
from context_loader import load_context_from_repo
from prompt_template import PROMPT_TEMPLATE
PROVIDER = os.getenv("PROVIDER", "ollama") # "ollama" or "openai"
# Ollama用
OLLAMA_HOST = os.getenv("OLLAMA_HOST", "http://localhost:11434")
OLLAMA_URL = f"{OLLAMA_HOST}/api/generate"
OLLAMA_MODEL = os.getenv("MODEL", "syui/ai")
# OpenAI用
OPENAI_BASE = os.getenv("OPENAI_API_BASE", "https://api.openai.com/v1")
OPENAI_KEY = os.getenv("OPENAI_API_KEY", "")
OPENAI_MODEL = os.getenv("MODEL", "gpt-4o-mini")
def ask_question(question, repo_path="."):
context = load_context_from_repo(repo_path)
prompt = PROMPT_TEMPLATE.format(context=context[:10000], question=question)
if PROVIDER == "ollama":
payload = {
"model": OLLAMA_MODEL,
"prompt": prompt,
"stream": False
}
response = httpx.post(OLLAMA_URL, json=payload, timeout=60.0)
result = response.json()
return result.get("response", "返答がありませんでした。")
elif PROVIDER == "openai":
import openai
openai.api_key = OPENAI_KEY
openai.api_base = OPENAI_BASE
client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
response = client.chat.completions.create(
model=OPENAI_MODEL,
messages=[{"role": "user", "content": prompt}]
)
return response.choices[0].message.content
else:
return f"❌ 未知のプロバイダです: {PROVIDER}"
if __name__ == "__main__":
import sys
question = " ".join(sys.argv[1:])
answer = ask_question(question)
print("\n🧠 回答:\n", answer)

View File

@@ -1,11 +0,0 @@
import os
def load_context_from_repo(repo_path: str, extensions={".rs", ".toml", ".md"}) -> str:
context = ""
for root, dirs, files in os.walk(repo_path):
for file in files:
if any(file.endswith(ext) for ext in extensions):
with open(os.path.join(root, file), "r", encoding="utf-8", errors="ignore") as f:
content = f.read()
context += f"\n\n# FILE: {os.path.join(root, file)}\n{content}"
return context

View File

@@ -1,11 +0,0 @@
PROMPT_TEMPLATE = """
あなたは優秀なAIアシスタントです。
以下のコードベースの情報を参考にして、質問に答えてください。
[コードコンテキスト]
{context}
[質問]
{question}
"""

View File

@@ -1,12 +0,0 @@
from setuptools import setup
setup(
name='mcp',
version='0.1.0',
py_modules=['cli'],
entry_points={
'console_scripts': [
'mcp = cli:main',
],
},
)

33
pyproject.toml Normal file
View File

@@ -0,0 +1,33 @@
[project]
name = "aigpt"
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",
"prompt-toolkit>=3.0.0",
]
[project.scripts]
aigpt = "aigpt.cli:app"
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[tool.setuptools.packages.find]
where = ["src"]
[tool.setuptools.package-data]
aigpt = ["data/*.json"]

View File

@@ -1,39 +0,0 @@
#!/bin/zsh
d=${0:a:h:h}
json=`cat $d/gpt.json`
toml=`cat $d/Cargo.toml`
cd $d/src/
list=(`zsh -c "ls *.rs"`)
body="
今、AGE systemを作っているよ。どんなものかというと、jsonを参照してここにすべてが書かれている。
$json
リポジトリはこちらになる。
git.syui.ai:ai/gpt.git
内容はこんな感じ。
\`\`\`toml
$toml
\`\`\`
`
for i in $list; do
if [ -f $d/src/$i ];then
t=$(cat $d/src/$i)
echo
echo '\`\`\`rust'
echo $t
echo '\`\`\`'
echo
fi
done
`
次は何を実装すればいいと思う。
"
echo $body

23
setup_venv.sh Executable file
View File

@@ -0,0 +1,23 @@
#!/bin/zsh
# Setup Python virtual environment in the new config directory
VENV_DIR="$HOME/.config/syui/ai/gpt/venv"
echo "Creating Python virtual environment at: $VENV_DIR"
python -m venv "$VENV_DIR"
echo "Activating virtual environment..."
source "$VENV_DIR/bin/activate"
echo "Installing aigpt package..."
cd "$(dirname "$0")"
pip install -e .
echo "Setup complete!"
echo "To activate the virtual environment, run:"
echo "source ~/.config/syui/ai/gpt/venv/bin/activate"
if [ -z "`$SHELL -i -c \"alias aigpt\"`" ]; then
echo 'alias aigpt="$HOME/.config/syui/ai/gpt/venv/bin/aigpt"' >> ${HOME}/.$(basename $SHELL)rc
exec $SHELL
fi

1
shell Submodule

Submodule shell added at 81ae0037d9

View File

@@ -1,37 +0,0 @@
use chrono::{NaiveDateTime};
#[allow(dead_code)]
#[derive(Debug)]
pub struct AIState {
pub relation_score: f32,
pub previous_score: f32,
pub decay_rate: f32,
pub sensitivity: f32,
pub message_threshold: f32,
pub last_message_time: NaiveDateTime,
}
#[allow(dead_code)]
impl AIState {
pub fn update(&mut self, now: NaiveDateTime) {
let days_passed = (now - self.last_message_time).num_days() as f32;
let decay = self.decay_rate * days_passed;
self.previous_score = self.relation_score;
self.relation_score -= decay;
self.relation_score = self.relation_score.clamp(0.0, 100.0);
}
pub fn should_talk(&self) -> bool {
let delta = self.previous_score - self.relation_score;
delta > self.message_threshold && self.sensitivity > 0.5
}
pub fn generate_message(&self) -> String {
match self.relation_score as i32 {
80..=100 => "ふふっ、最近どうしてる?会いたくなっちゃった!".to_string(),
60..=79 => "ちょっとだけ、さみしかったんだよ?".to_string(),
40..=59 => "えっと……話せる時間ある?".to_string(),
_ => "ううん、もしかして私のこと、忘れちゃったのかな……".to_string(),
}
}
}

View File

@@ -0,0 +1,18 @@
Metadata-Version: 2.4
Name: aigpt
Version: 0.1.0
Summary: Autonomous transmission AI with unique personality based on relationship parameters
Requires-Python: >=3.10
Requires-Dist: click>=8.0.0
Requires-Dist: typer>=0.9.0
Requires-Dist: fastapi-mcp>=0.1.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: httpx>=0.24.0
Requires-Dist: rich>=13.0.0
Requires-Dist: python-dotenv>=1.0.0
Requires-Dist: ollama>=0.1.0
Requires-Dist: openai>=1.0.0
Requires-Dist: uvicorn>=0.23.0
Requires-Dist: apscheduler>=3.10.0
Requires-Dist: croniter>=1.3.0
Requires-Dist: prompt-toolkit>=3.0.0

View File

@@ -0,0 +1,25 @@
README.md
pyproject.toml
src/aigpt/__init__.py
src/aigpt/ai_provider.py
src/aigpt/chatgpt_importer.py
src/aigpt/cli.py
src/aigpt/config.py
src/aigpt/fortune.py
src/aigpt/mcp_server.py
src/aigpt/mcp_server_simple.py
src/aigpt/memory.py
src/aigpt/models.py
src/aigpt/persona.py
src/aigpt/project_manager.py
src/aigpt/relationship.py
src/aigpt/scheduler.py
src/aigpt/transmission.py
src/aigpt.egg-info/PKG-INFO
src/aigpt.egg-info/SOURCES.txt
src/aigpt.egg-info/dependency_links.txt
src/aigpt.egg-info/entry_points.txt
src/aigpt.egg-info/requires.txt
src/aigpt.egg-info/top_level.txt
src/aigpt/shared/__init__.py
src/aigpt/shared/ai_provider.py

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,2 @@
[console_scripts]
aigpt = aigpt.cli:app

View File

@@ -0,0 +1,13 @@
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
prompt-toolkit>=3.0.0

View File

@@ -0,0 +1 @@
aigpt

15
src/aigpt/__init__.py Normal file
View File

@@ -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",
]

580
src/aigpt/ai_provider.py Normal file
View File

@@ -0,0 +1,580 @@
"""AI Provider integration for response generation"""
import os
import json
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: Optional[str] = None):
self.model = model
# Use environment variable OLLAMA_HOST if available, otherwise use config or default
self.host = host or os.getenv('OLLAMA_HOST', 'http://127.0.0.1:11434')
# Ensure proper URL format
if not self.host.startswith('http'):
self.host = f'http://{self.host}'
self.client = ollama.Client(host=self.host, timeout=60.0) # 60秒タイムアウト
self.logger = logging.getLogger(__name__)
self.logger.info(f"OllamaProvider initialized with host: {self.host}, model: {self.model}")
# Load system prompt from config
try:
config = Config()
self.config_system_prompt = config.get('providers.ollama.system_prompt')
except:
self.config_system_prompt = None
async def generate_response(
self,
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 self.config_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 self._clean_response(response['message']['content'])
except Exception as e:
self.logger.error(f"Ollama generation failed: {e}")
return self._fallback_response(persona_state)
def chat(self, prompt: str, max_tokens: int = 2000) -> str:
"""Simple chat interface"""
try:
messages = []
if self.config_system_prompt:
messages.append({"role": "system", "content": self.config_system_prompt})
messages.append({"role": "user", "content": prompt})
response = self.client.chat(
model=self.model,
messages=messages,
options={
"num_predict": max_tokens,
"temperature": 0.7,
"top_p": 0.9,
},
stream=False # ストリーミング無効化で安定性向上
)
return self._clean_response(response['message']['content'])
except Exception as e:
self.logger.error(f"Ollama chat failed (host: {self.host}): {e}")
return "I'm having trouble connecting to the AI model."
def _clean_response(self, response: str) -> str:
"""Clean response by removing think tags and other unwanted content"""
import re
# Remove <think></think> tags and their content
response = re.sub(r'<think>.*?</think>', '', response, flags=re.DOTALL)
# Remove any remaining whitespace at the beginning/end
response = response.strip()
return response
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 with MCP function calling support"""
def __init__(self, model: str = "gpt-4o-mini", api_key: Optional[str] = None, mcp_client=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: aigpt config set providers.openai.api_key YOUR_KEY")
self.client = OpenAI(api_key=self.api_key)
self.logger = logging.getLogger(__name__)
self.mcp_client = mcp_client # For MCP function calling
# Load system prompt from config
try:
self.config_system_prompt = config.get('providers.openai.system_prompt')
except:
self.config_system_prompt = None
def _get_mcp_tools(self) -> List[Dict[str, Any]]:
"""Generate OpenAI tools from MCP endpoints"""
if not self.mcp_client or not self.mcp_client.available:
return []
tools = [
{
"type": "function",
"function": {
"name": "get_memories",
"description": "過去の会話記憶を取得します。「覚えている」「前回」「以前」などの質問で必ず使用してください",
"parameters": {
"type": "object",
"properties": {
"limit": {
"type": "integer",
"description": "取得する記憶の数",
"default": 5
}
}
}
}
},
{
"type": "function",
"function": {
"name": "search_memories",
"description": "特定のトピックについて話した記憶を検索します。「プログラミングについて」「○○について話した」などの質問で使用してください",
"parameters": {
"type": "object",
"properties": {
"keywords": {
"type": "array",
"items": {"type": "string"},
"description": "検索キーワードの配列"
}
},
"required": ["keywords"]
}
}
},
{
"type": "function",
"function": {
"name": "get_contextual_memories",
"description": "クエリに関連する文脈的記憶を取得します",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "検索クエリ"
},
"limit": {
"type": "integer",
"description": "取得する記憶の数",
"default": 5
}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "get_relationship",
"description": "特定ユーザーとの関係性情報を取得します",
"parameters": {
"type": "object",
"properties": {
"user_id": {
"type": "string",
"description": "ユーザーID"
}
},
"required": ["user_id"]
}
}
}
]
# Add ai.card tools if available
if hasattr(self.mcp_client, 'has_card_tools') and self.mcp_client.has_card_tools:
card_tools = [
{
"type": "function",
"function": {
"name": "card_get_user_cards",
"description": "ユーザーが所有するカードの一覧を取得します",
"parameters": {
"type": "object",
"properties": {
"did": {
"type": "string",
"description": "ユーザーのDID"
},
"limit": {
"type": "integer",
"description": "取得するカード数の上限",
"default": 10
}
},
"required": ["did"]
}
}
},
{
"type": "function",
"function": {
"name": "card_draw_card",
"description": "ガチャを引いてカードを取得します",
"parameters": {
"type": "object",
"properties": {
"did": {
"type": "string",
"description": "ユーザーのDID"
},
"is_paid": {
"type": "boolean",
"description": "有料ガチャかどうか",
"default": False
}
},
"required": ["did"]
}
}
},
{
"type": "function",
"function": {
"name": "card_analyze_collection",
"description": "ユーザーのカードコレクションを分析します",
"parameters": {
"type": "object",
"properties": {
"did": {
"type": "string",
"description": "ユーザーのDID"
}
},
"required": ["did"]
}
}
},
{
"type": "function",
"function": {
"name": "card_get_gacha_stats",
"description": "ガチャの統計情報を取得します",
"parameters": {
"type": "object",
"properties": {}
}
}
}
]
tools.extend(card_tools)
return tools
async def generate_response(
self,
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 self.config_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)
async def chat_with_mcp(self, prompt: str, max_tokens: int = 2000, user_id: str = "user") -> str:
"""Chat interface with MCP function calling support"""
if not self.mcp_client or not self.mcp_client.available:
return self.chat(prompt, max_tokens)
try:
# Prepare tools
tools = self._get_mcp_tools()
# Initial request with tools
response = self.client.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": self.config_system_prompt or "あなたは記憶システムと関係性データ、カードゲームシステムにアクセスできます。過去の会話、記憶、関係性について質問された時は、必ずツールを使用して正確な情報を取得してください。「覚えている」「前回」「以前」「について話した」「関係」などのキーワードがあれば積極的にツールを使用してください。カード関連の質問「カード」「コレクション」「ガチャ」「見せて」「持っている」などでは、必ずcard_get_user_cardsやcard_analyze_collectionなどのツールを使用してください。didパラメータには現在会話しているユーザーのID'syui')を使用してください。"},
{"role": "user", "content": prompt}
],
tools=tools,
tool_choice="auto",
max_tokens=max_tokens,
temperature=0.7
)
message = response.choices[0].message
# Handle tool calls
if message.tool_calls:
print(f"🔧 [OpenAI] {len(message.tool_calls)} tools called:")
for tc in message.tool_calls:
print(f" - {tc.function.name}({tc.function.arguments})")
messages = [
{"role": "system", "content": self.config_system_prompt or "必要に応じて利用可能なツールを使って、より正確で詳細な回答を提供してください。"},
{"role": "user", "content": prompt},
{
"role": "assistant",
"content": message.content,
"tool_calls": [tc.model_dump() for tc in message.tool_calls]
}
]
# Execute each tool call
for tool_call in message.tool_calls:
print(f"🌐 [MCP] Executing {tool_call.function.name}...")
tool_result = await self._execute_mcp_tool(tool_call, user_id)
print(f"✅ [MCP] Result: {str(tool_result)[:100]}...")
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"name": tool_call.function.name,
"content": json.dumps(tool_result, ensure_ascii=False)
})
# Get final response with tool outputs
final_response = self.client.chat.completions.create(
model=self.model,
messages=messages,
max_tokens=max_tokens,
temperature=0.7
)
return final_response.choices[0].message.content
else:
return message.content
except Exception as e:
self.logger.error(f"OpenAI MCP chat failed: {e}")
return f"申し訳ありません。エラーが発生しました: {e}"
async def _execute_mcp_tool(self, tool_call, context_user_id: str = "user") -> Dict[str, Any]:
"""Execute MCP tool call"""
try:
import json
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
if function_name == "get_memories":
limit = arguments.get("limit", 5)
return await self.mcp_client.get_memories(limit) or {"error": "記憶の取得に失敗しました"}
elif function_name == "search_memories":
keywords = arguments.get("keywords", [])
return await self.mcp_client.search_memories(keywords) or {"error": "記憶の検索に失敗しました"}
elif function_name == "get_contextual_memories":
query = arguments.get("query", "")
limit = arguments.get("limit", 5)
return await self.mcp_client.get_contextual_memories(query, limit) or {"error": "文脈記憶の取得に失敗しました"}
elif function_name == "get_relationship":
# 引数のuser_idがない場合はコンテキストから取得
user_id = arguments.get("user_id", context_user_id)
if not user_id or user_id == "user":
user_id = context_user_id
# デバッグ用ログ
print(f"🔍 [DEBUG] get_relationship called with user_id: '{user_id}' (context: '{context_user_id}')")
result = await self.mcp_client.get_relationship(user_id)
print(f"🔍 [DEBUG] MCP result: {result}")
return result or {"error": "関係性の取得に失敗しました"}
# ai.card tools
elif function_name == "card_get_user_cards":
did = arguments.get("did", context_user_id)
limit = arguments.get("limit", 10)
result = await self.mcp_client.card_get_user_cards(did, limit)
# Check if ai.card server is not running
if result and result.get("error") == "ai.card server is not running":
return {
"error": "ai.cardサーバーが起動していません",
"message": "カードシステムを使用するには、別のターミナルで以下のコマンドを実行してください:\ncd card && ./start_server.sh"
}
return result or {"error": "カード一覧の取得に失敗しました"}
elif function_name == "card_draw_card":
did = arguments.get("did", context_user_id)
is_paid = arguments.get("is_paid", False)
result = await self.mcp_client.card_draw_card(did, is_paid)
if result and result.get("error") == "ai.card server is not running":
return {
"error": "ai.cardサーバーが起動していません",
"message": "カードシステムを使用するには、別のターミナルで以下のコマンドを実行してください:\ncd card && ./start_server.sh"
}
return result or {"error": "ガチャに失敗しました"}
elif function_name == "card_analyze_collection":
did = arguments.get("did", context_user_id)
result = await self.mcp_client.card_analyze_collection(did)
if result and result.get("error") == "ai.card server is not running":
return {
"error": "ai.cardサーバーが起動していません",
"message": "カードシステムを使用するには、別のターミナルで以下のコマンドを実行してください:\ncd card && ./start_server.sh"
}
return result or {"error": "コレクション分析に失敗しました"}
elif function_name == "card_get_gacha_stats":
result = await self.mcp_client.card_get_gacha_stats()
if result and result.get("error") == "ai.card server is not running":
return {
"error": "ai.cardサーバーが起動していません",
"message": "カードシステムを使用するには、別のターミナルで以下のコマンドを実行してください:\ncd card && ./start_server.sh"
}
return result or {"error": "ガチャ統計の取得に失敗しました"}
else:
return {"error": f"未知のツール: {function_name}"}
except Exception as e:
return {"error": f"ツール実行エラー: {str(e)}"}
def chat(self, prompt: str, max_tokens: int = 2000) -> str:
"""Simple chat interface without MCP tools"""
try:
messages = []
if self.config_system_prompt:
messages.append({"role": "system", "content": self.config_system_prompt})
messages.append({"role": "user", "content": prompt})
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
max_tokens=max_tokens,
temperature=0.7
)
return response.choices[0].message.content
except Exception as e:
self.logger.error(f"OpenAI chat failed: {e}")
return "I'm having trouble connecting to the AI model."
def _fallback_response(self, persona_state: PersonaState) -> str:
"""Fallback response based on mood"""
mood_responses = {
"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 = "ollama", model: Optional[str] = None, mcp_client=None, **kwargs) -> AIProvider:
"""Factory function to create AI providers"""
if provider == "ollama":
# Get model from config if not provided
if model is None:
try:
from .config import Config
config = Config()
model = config.get('providers.ollama.default_model', 'qwen2.5')
except:
model = 'qwen2.5' # Fallback to default
# Try to get host from config if not provided in kwargs
if 'host' not in kwargs:
try:
from .config import Config
config = Config()
config_host = config.get('providers.ollama.host')
if config_host:
kwargs['host'] = config_host
except:
pass # Use environment variable or default
return OllamaProvider(model=model, **kwargs)
elif provider == "openai":
# Get model from config if not provided
if model is None:
try:
from .config import Config
config = Config()
model = config.get('providers.openai.default_model', 'gpt-4o-mini')
except:
model = 'gpt-4o-mini' # Fallback to default
return OpenAIProvider(model=model, mcp_client=mcp_client, **kwargs)
else:
raise ValueError(f"Unknown provider: {provider}")

View File

@@ -0,0 +1,192 @@
"""ChatGPT conversation data importer for ai.gpt"""
import json
import uuid
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Any, Optional
import logging
from .models import Memory, MemoryLevel, Conversation
from .memory import MemoryManager
from .relationship import RelationshipTracker
logger = logging.getLogger(__name__)
class ChatGPTImporter:
"""Import ChatGPT conversation data into ai.gpt memory system"""
def __init__(self, data_dir: Path):
self.data_dir = data_dir
self.memory_manager = MemoryManager(data_dir)
self.relationship_tracker = RelationshipTracker(data_dir)
def import_from_file(self, file_path: Path, user_id: str = "chatgpt_user") -> Dict[str, Any]:
"""Import ChatGPT conversations from JSON file
Args:
file_path: Path to ChatGPT export JSON file
user_id: User ID to associate with imported conversations
Returns:
Dict with import statistics
"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
chatgpt_data = json.load(f)
return self._import_conversations(chatgpt_data, user_id)
except Exception as e:
logger.error(f"Failed to import ChatGPT data: {e}")
raise
def _import_conversations(self, chatgpt_data: List[Dict], user_id: str) -> Dict[str, Any]:
"""Import multiple conversations from ChatGPT data"""
stats = {
"conversations_imported": 0,
"messages_imported": 0,
"user_messages": 0,
"assistant_messages": 0,
"skipped_messages": 0
}
for conversation_data in chatgpt_data:
try:
conv_stats = self._import_single_conversation(conversation_data, user_id)
# Update overall stats
stats["conversations_imported"] += 1
stats["messages_imported"] += conv_stats["messages"]
stats["user_messages"] += conv_stats["user_messages"]
stats["assistant_messages"] += conv_stats["assistant_messages"]
stats["skipped_messages"] += conv_stats["skipped"]
except Exception as e:
logger.warning(f"Failed to import conversation '{conversation_data.get('title', 'Unknown')}': {e}")
continue
logger.info(f"Import completed: {stats}")
return stats
def _import_single_conversation(self, conversation_data: Dict, user_id: str) -> Dict[str, int]:
"""Import a single conversation from ChatGPT"""
title = conversation_data.get("title", "Untitled")
create_time = conversation_data.get("create_time")
mapping = conversation_data.get("mapping", {})
stats = {"messages": 0, "user_messages": 0, "assistant_messages": 0, "skipped": 0}
# Extract messages in chronological order
messages = self._extract_messages_from_mapping(mapping)
for msg in messages:
try:
role = msg["author"]["role"]
content = self._extract_content(msg["content"])
create_time_msg = msg.get("create_time")
if not content or role not in ["user", "assistant"]:
stats["skipped"] += 1
continue
# Convert to ai.gpt format
if role == "user":
# User message - create memory entry
self._add_user_message(user_id, content, create_time_msg, title)
stats["user_messages"] += 1
elif role == "assistant":
# Assistant message - create AI response memory
self._add_assistant_message(user_id, content, create_time_msg, title)
stats["assistant_messages"] += 1
stats["messages"] += 1
except Exception as e:
logger.warning(f"Failed to process message in '{title}': {e}")
stats["skipped"] += 1
continue
logger.info(f"Imported conversation '{title}': {stats}")
return stats
def _extract_messages_from_mapping(self, mapping: Dict) -> List[Dict]:
"""Extract messages from ChatGPT mapping structure in chronological order"""
messages = []
for node_id, node_data in mapping.items():
message = node_data.get("message")
if message and message.get("author", {}).get("role") in ["user", "assistant"]:
# Skip system messages and hidden messages
metadata = message.get("metadata", {})
if not metadata.get("is_visually_hidden_from_conversation", False):
messages.append(message)
# Sort by create_time if available
messages.sort(key=lambda x: x.get("create_time") or 0)
return messages
def _extract_content(self, content_data: Dict) -> Optional[str]:
"""Extract text content from ChatGPT content structure"""
if not content_data:
return None
content_type = content_data.get("content_type")
if content_type == "text":
parts = content_data.get("parts", [])
if parts and parts[0]:
return parts[0].strip()
elif content_type == "user_editable_context":
# User context/instructions
user_instructions = content_data.get("user_instructions", "")
if user_instructions:
return f"[User Context] {user_instructions}"
return None
def _add_user_message(self, user_id: str, content: str, create_time: Optional[float], conversation_title: str):
"""Add user message to ai.gpt memory system"""
timestamp = datetime.fromtimestamp(create_time) if create_time else datetime.now()
# Create conversation record
conversation = Conversation(
id=str(uuid.uuid4()),
user_id=user_id,
user_message=content,
ai_response="", # Will be filled by next assistant message
timestamp=timestamp,
context={"source": "chatgpt_import", "conversation_title": conversation_title}
)
# Add to memory with CORE level (imported data is important)
memory = Memory(
id=str(uuid.uuid4()),
timestamp=timestamp,
content=content,
level=MemoryLevel.CORE,
importance_score=0.8 # High importance for imported data
)
self.memory_manager.add_memory(memory)
# Update relationship (positive interaction)
self.relationship_tracker.update_interaction(user_id, 1.0)
def _add_assistant_message(self, user_id: str, content: str, create_time: Optional[float], conversation_title: str):
"""Add assistant message to ai.gpt memory system"""
timestamp = datetime.fromtimestamp(create_time) if create_time else datetime.now()
# Add assistant response as memory (AI's own responses can inform future behavior)
memory = Memory(
id=str(uuid.uuid4()),
timestamp=timestamp,
content=f"[AI Response] {content}",
level=MemoryLevel.SUMMARY,
importance_score=0.6 # Medium importance for AI responses
)
self.memory_manager.add_memory(memory)

1583
src/aigpt/cli.py Normal file

File diff suppressed because it is too large Load Diff

184
src/aigpt/config.py Normal file
View File

@@ -0,0 +1,184 @@
"""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" / "syui" / "ai" / "gpt"
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",
"system_prompt": None
},
"ollama": {
"host": "http://localhost:11434",
"default_model": "qwen3:latest",
"system_prompt": None
}
},
"mcp": {
"enabled": True,
"auto_detect": True,
"servers": {
"ai_gpt": {
"name": "ai.gpt MCP Server",
"base_url": "http://localhost:8001",
"endpoints": {
"get_memories": "/get_memories",
"search_memories": "/search_memories",
"get_contextual_memories": "/get_contextual_memories",
"process_interaction": "/process_interaction",
"get_relationship": "/get_relationship",
"get_all_relationships": "/get_all_relationships",
"get_persona_state": "/get_persona_state",
"get_fortune": "/get_fortune",
"run_maintenance": "/run_maintenance",
"execute_command": "/execute_command",
"analyze_file": "/analyze_file",
"remote_shell": "/remote_shell",
"ai_bot_status": "/ai_bot_status"
},
"timeout": 10.0
},
"ai_card": {
"name": "ai.card MCP Server",
"base_url": "http://localhost:8000",
"endpoints": {
"health": "/health",
"get_user_cards": "/api/cards/user",
"gacha": "/api/gacha",
"sync_atproto": "/api/sync"
},
"timeout": 5.0
}
}
},
"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}", {})

118
src/aigpt/fortune.py Normal file
View File

@@ -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

1016
src/aigpt/mcp_server.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,146 @@
"""Simple MCP Server implementation for ai.gpt"""
from mcp import Server
from mcp.types import Tool, TextContent
from pathlib import Path
from typing import Any, Dict, List, Optional
import json
from .persona import Persona
from .ai_provider import create_ai_provider
import subprocess
import os
def create_mcp_server(data_dir: Path, enable_card: bool = False) -> Server:
"""Create MCP server with ai.gpt tools"""
server = Server("aigpt")
persona = Persona(data_dir)
@server.tool()
async def get_memories(limit: int = 10) -> List[Dict[str, Any]]:
"""Get active memories from the AI's memory system"""
memories = 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
]
@server.tool()
async def get_relationship(user_id: str) -> Dict[str, Any]:
"""Get relationship status with a specific user"""
rel = 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
}
@server.tool()
async def process_interaction(user_id: str, message: str, provider: str = "ollama", model: str = "qwen2.5") -> Dict[str, Any]:
"""Process an interaction with a user"""
ai_provider = create_ai_provider(provider, model)
response, relationship_delta = persona.process_interaction(user_id, message, ai_provider)
rel = 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
}
@server.tool()
async def get_fortune() -> Dict[str, Any]:
"""Get today's AI fortune"""
fortune = persona.fortune_system.get_today_fortune()
modifiers = 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
}
@server.tool()
async def execute_command(command: str, working_dir: str = ".") -> Dict[str, Any]:
"""Execute a shell command"""
try:
import shlex
result = subprocess.run(
shlex.split(command),
cwd=working_dir,
capture_output=True,
text=True,
timeout=60
)
return {
"status": "success" if result.returncode == 0 else "error",
"returncode": result.returncode,
"stdout": result.stdout,
"stderr": result.stderr,
"command": command
}
except subprocess.TimeoutExpired:
return {"error": "Command timed out"}
except Exception as e:
return {"error": str(e)}
@server.tool()
async def analyze_file(file_path: str) -> Dict[str, Any]:
"""Analyze a file using AI"""
try:
if not os.path.exists(file_path):
return {"error": f"File not found: {file_path}"}
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
ai_provider = create_ai_provider("ollama", "qwen2.5")
prompt = f"Analyze this file and provide insights:\\n\\nFile: {file_path}\\n\\nContent:\\n{content[:2000]}"
analysis = ai_provider.generate_response(prompt, "You are a code analyst.")
return {
"analysis": analysis,
"file_path": file_path,
"file_size": len(content),
"line_count": len(content.split('\\n'))
}
except Exception as e:
return {"error": str(e)}
return server
async def main():
"""Run MCP server"""
import sys
from mcp import stdio_server
data_dir = Path.home() / ".config" / "syui" / "ai" / "gpt" / "data"
data_dir.mkdir(parents=True, exist_ok=True)
server = create_mcp_server(data_dir)
await stdio_server(server)
if __name__ == "__main__":
import asyncio
asyncio.run(main())

408
src/aigpt/memory.py Normal file
View File

@@ -0,0 +1,408 @@
"""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 add_memory(self, memory: Memory):
"""Add a memory directly to the system"""
self.memories[memory.id] = memory
self._save_memories()
def create_smart_summary(self, user_id: str, ai_provider=None) -> Optional[Memory]:
"""Create AI-powered thematic summary from recent memories"""
recent_memories = [
mem for mem in self.memories.values()
if mem.level == MemoryLevel.FULL_LOG
and (datetime.now() - mem.timestamp).days < 7
]
if len(recent_memories) < 5:
return None
# Sort by timestamp for chronological analysis
recent_memories.sort(key=lambda m: m.timestamp)
# Prepare conversation context for AI analysis
conversations_text = "\n\n".join([
f"[{mem.timestamp.strftime('%Y-%m-%d %H:%M')}] {mem.content}"
for mem in recent_memories
])
summary_prompt = f"""
Analyze these recent conversations and create a thematic summary focusing on:
1. Communication patterns and user preferences
2. Technical topics and problem-solving approaches
3. Relationship progression and trust level
4. Key recurring themes and interests
Conversations:
{conversations_text}
Create a concise summary (2-3 sentences) that captures the essence of this interaction period:
"""
try:
if ai_provider:
summary_content = ai_provider.chat(summary_prompt, max_tokens=200)
else:
# Fallback to pattern-based analysis
themes = self._extract_themes(recent_memories)
summary_content = f"Themes: {', '.join(themes[:3])}. {len(recent_memories)} interactions with focus on technical discussions."
except Exception as e:
self.logger.warning(f"AI summary failed, using fallback: {e}")
themes = self._extract_themes(recent_memories)
summary_content = f"Themes: {', '.join(themes[:3])}. {len(recent_memories)} interactions."
summary_id = hashlib.sha256(
f"summary_{datetime.now().isoformat()}".encode()
).hexdigest()[:16]
summary = Memory(
id=summary_id,
timestamp=datetime.now(),
content=f"SUMMARY ({len(recent_memories)} conversations): {summary_content}",
summary=summary_content,
level=MemoryLevel.SUMMARY,
importance_score=0.6,
metadata={
"memory_count": len(recent_memories),
"time_span": f"{recent_memories[0].timestamp.date()} to {recent_memories[-1].timestamp.date()}",
"themes": self._extract_themes(recent_memories)[:5]
}
)
self.memories[summary.id] = summary
# Reduce importance of summarized memories
for mem in recent_memories:
mem.importance_score *= 0.8
self._save_memories()
return summary
def _extract_themes(self, memories: List[Memory]) -> List[str]:
"""Extract common themes from memory content"""
common_words = {}
for memory in memories:
# Simple keyword extraction
words = memory.content.lower().split()
for word in words:
if len(word) > 4 and word.isalpha():
common_words[word] = common_words.get(word, 0) + 1
# Return most frequent meaningful words
return sorted(common_words.keys(), key=common_words.get, reverse=True)[:10]
def create_core_memory(self, ai_provider=None) -> Optional[Memory]:
"""Analyze all memories to extract core personality-forming elements"""
# Collect all non-forgotten memories for analysis
all_memories = [
mem for mem in self.memories.values()
if mem.level != MemoryLevel.FORGOTTEN
]
if len(all_memories) < 10:
return None
# Sort by importance and timestamp for comprehensive analysis
all_memories.sort(key=lambda m: (m.importance_score, m.timestamp), reverse=True)
# Prepare memory context for AI analysis
memory_context = "\n".join([
f"[{mem.level.value}] {mem.timestamp.strftime('%Y-%m-%d')}: {mem.content[:200]}..."
for mem in all_memories[:20] # Top 20 memories
])
core_prompt = f"""
Analyze these conversations and memories to identify core personality elements that define this user relationship:
1. Communication style and preferences
2. Core values and principles
3. Problem-solving patterns
4. Trust level and relationship depth
5. Unique characteristics that make this relationship special
Memories:
{memory_context}
Extract the essential personality-forming elements (2-3 sentences) that should NEVER be forgotten:
"""
try:
if ai_provider:
core_content = ai_provider.chat(core_prompt, max_tokens=150)
else:
# Fallback to pattern analysis
user_patterns = self._analyze_user_patterns(all_memories)
core_content = f"User shows {user_patterns['communication_style']} communication, focuses on {user_patterns['main_interests']}, and demonstrates {user_patterns['problem_solving']} approach."
except Exception as e:
self.logger.warning(f"AI core analysis failed, using fallback: {e}")
user_patterns = self._analyze_user_patterns(all_memories)
core_content = f"Core pattern: {user_patterns['communication_style']} style, {user_patterns['main_interests']} interests."
# Create core memory
core_id = hashlib.sha256(
f"core_{datetime.now().isoformat()}".encode()
).hexdigest()[:16]
core_memory = Memory(
id=core_id,
timestamp=datetime.now(),
content=f"CORE PERSONALITY: {core_content}",
summary=core_content,
level=MemoryLevel.CORE,
importance_score=1.0,
is_core=True,
metadata={
"source_memories": len(all_memories),
"analysis_date": datetime.now().isoformat(),
"patterns": self._analyze_user_patterns(all_memories)
}
)
self.memories[core_memory.id] = core_memory
self._save_memories()
self.logger.info(f"Core memory created: {core_id}")
return core_memory
def _analyze_user_patterns(self, memories: List[Memory]) -> Dict[str, str]:
"""Analyze patterns in user behavior from memories"""
# Extract patterns from conversation content
all_content = " ".join([mem.content.lower() for mem in memories])
# Simple pattern detection
communication_indicators = {
"technical": ["code", "implementation", "system", "api", "database"],
"casual": ["thanks", "please", "sorry", "help"],
"formal": ["could", "would", "should", "proper"]
}
problem_solving_indicators = {
"systematic": ["first", "then", "next", "step", "plan"],
"experimental": ["try", "test", "experiment", "see"],
"theoretical": ["concept", "design", "architecture", "pattern"]
}
# Score each pattern
communication_style = max(
communication_indicators.keys(),
key=lambda style: sum(all_content.count(word) for word in communication_indicators[style])
)
problem_solving = max(
problem_solving_indicators.keys(),
key=lambda style: sum(all_content.count(word) for word in problem_solving_indicators[style])
)
# Extract main interests from themes
themes = self._extract_themes(memories)
main_interests = ", ".join(themes[:3]) if themes else "general technology"
return {
"communication_style": communication_style,
"problem_solving": problem_solving,
"main_interests": main_interests,
"interaction_count": len(memories)
}
def identify_core_memories(self) -> List[Memory]:
"""Identify existing memories that should become core (legacy method)"""
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 (legacy method)"""
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]
def get_contextual_memories(self, query: str = "", limit: int = 10) -> Dict[str, List[Memory]]:
"""Get memories organized by priority with contextual relevance"""
all_memories = [
mem for mem in self.memories.values()
if mem.level != MemoryLevel.FORGOTTEN
]
# Categorize memories by type and importance
core_memories = [mem for mem in all_memories if mem.level == MemoryLevel.CORE]
summary_memories = [mem for mem in all_memories if mem.level == MemoryLevel.SUMMARY]
recent_memories = [
mem for mem in all_memories
if mem.level == MemoryLevel.FULL_LOG
and (datetime.now() - mem.timestamp).days < 3
]
# Apply keyword relevance if query provided
if query:
query_lower = query.lower()
def relevance_score(memory: Memory) -> float:
content_score = 1 if query_lower in memory.content.lower() else 0
summary_score = 1 if memory.summary and query_lower in memory.summary.lower() else 0
metadata_score = 1 if any(
query_lower in str(v).lower()
for v in (memory.metadata or {}).values()
) else 0
return content_score + summary_score + metadata_score
# Re-rank by relevance while maintaining type priority
core_memories.sort(key=lambda m: (relevance_score(m), m.importance_score), reverse=True)
summary_memories.sort(key=lambda m: (relevance_score(m), m.importance_score), reverse=True)
recent_memories.sort(key=lambda m: (relevance_score(m), m.importance_score), reverse=True)
else:
# Sort by importance and recency
core_memories.sort(key=lambda m: (m.importance_score, m.timestamp), reverse=True)
summary_memories.sort(key=lambda m: (m.importance_score, m.timestamp), reverse=True)
recent_memories.sort(key=lambda m: (m.importance_score, m.timestamp), reverse=True)
# Return organized memory structure
return {
"core": core_memories[:3], # Always include top core memories
"summary": summary_memories[:3], # Recent summaries
"recent": recent_memories[:limit-6], # Fill remaining with recent
"all_active": all_memories[:limit] # Fallback for simple access
}
def search_memories(self, keywords: List[str], memory_types: List[MemoryLevel] = None) -> List[Memory]:
"""Search memories by keywords and optionally filter by memory types"""
if memory_types is None:
memory_types = [MemoryLevel.CORE, MemoryLevel.SUMMARY, MemoryLevel.FULL_LOG]
matching_memories = []
for memory in self.memories.values():
if memory.level not in memory_types or memory.level == MemoryLevel.FORGOTTEN:
continue
# Check if any keyword matches in content, summary, or metadata
content_text = f"{memory.content} {memory.summary or ''}"
if memory.metadata:
content_text += " " + " ".join(str(v) for v in memory.metadata.values())
content_lower = content_text.lower()
# Score by keyword matches
match_score = sum(
keyword.lower() in content_lower
for keyword in keywords
)
if match_score > 0:
# Add match score to memory for sorting
memory_copy = memory.model_copy()
memory_copy.importance_score += match_score * 0.1
matching_memories.append(memory_copy)
# Sort by relevance (match score + importance + core status)
matching_memories.sort(
key=lambda m: (m.is_core, m.importance_score, m.timestamp),
reverse=True
)
return matching_memories

88
src/aigpt/models.py Normal file
View File

@@ -0,0 +1,88 @@
"""Data models for ai.gpt system"""
from datetime import datetime, date
from typing import Optional, Dict, List, Any
from enum import Enum
from pydantic import BaseModel, Field, field_validator
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
is_core: bool = False
decay_rate: float = 0.01
metadata: Optional[Dict[str, Any]] = None
@field_validator('importance_score')
@classmethod
def validate_importance_score(cls, v):
"""Ensure importance_score is within valid range, handle floating point precision issues"""
if abs(v) < 1e-10: # Very close to zero
return 0.0
return max(0.0, min(1.0, v))
class Relationship(BaseModel):
"""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: 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

263
src/aigpt/persona.py Normal file
View File

@@ -0,0 +1,263 @@
"""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 build_context_prompt(self, user_id: str, current_message: str) -> str:
"""Build context-aware prompt with relevant memories"""
# Get contextual memories based on current message
memory_groups = self.memory.get_contextual_memories(query=current_message, limit=8)
# Build context sections
context_parts = []
# Core personality elements (always included)
if memory_groups["core"]:
core_context = "\n".join([mem.content for mem in memory_groups["core"]])
context_parts.append(f"CORE PERSONALITY:\n{core_context}")
# Recent summaries for context
if memory_groups["summary"]:
summary_context = "\n".join([mem.summary or mem.content for mem in memory_groups["summary"]])
context_parts.append(f"RECENT PATTERNS:\n{summary_context}")
# Recent specific interactions
if memory_groups["recent"]:
recent_context = "\n".join([
f"[{mem.timestamp.strftime('%m-%d')}] {mem.content[:100]}..."
for mem in memory_groups["recent"][:3]
])
context_parts.append(f"RECENT INTERACTIONS:\n{recent_context}")
# Get current persona state
state = self.get_current_state()
# Build final prompt
context_prompt = f"""You are an AI with persistent memory and evolving relationships. Your current state:
PERSONALITY: {', '.join([f'{k}={v:.1f}' for k, v in state.base_personality.items()])}
MOOD: {state.current_mood}
FORTUNE: {state.fortune.fortune_value}/10
"""
if context_parts:
context_prompt += "RELEVANT CONTEXT:\n" + "\n\n".join(context_parts) + "\n\n"
context_prompt += f"""IMPORTANT: You have access to the following tools:
- Memory tools: get_memories, search_memories, get_contextual_memories
- Relationship tools: get_relationship
- Card game tools: card_get_user_cards, card_draw_card, card_analyze_collection
When asked about cards, collections, or anything card-related, YOU MUST use the card tools.
For "カードコレクションを見せて" or similar requests, use card_get_user_cards with did='{user_id}'.
Respond to this message while staying true to your personality and the established relationship context:
User: {current_message}
AI:"""
return context_prompt
def process_interaction(self, user_id: str, message: str, ai_provider=None) -> tuple[str, float]:
"""Process user interaction and generate response with enhanced context"""
# Get current state
state = self.get_current_state()
# Get relationship with user
relationship = self.relationships.get_or_create_relationship(user_id)
# Enhanced response generation with context awareness
if relationship.is_broken:
response = "..."
relationship_delta = 0.0
else:
if ai_provider:
# Build context-aware prompt
context_prompt = self.build_context_prompt(user_id, message)
# Generate response using AI with full context
try:
# Check if AI provider supports MCP
if hasattr(ai_provider, 'chat_with_mcp'):
import asyncio
response = asyncio.run(ai_provider.chat_with_mcp(context_prompt, max_tokens=2000, user_id=user_id))
else:
response = ai_provider.chat(context_prompt, max_tokens=2000)
# Clean up response if it includes the prompt echo
if "AI:" in response:
response = response.split("AI:")[-1].strip()
except Exception as e:
self.logger.error(f"AI response generation failed: {e}")
response = f"I appreciate your message about {message[:50]}..."
# Calculate relationship delta based on interaction quality and context
if state.current_mood in ["joyful", "cheerful"]:
relationship_delta = 2.0
elif relationship.status.value == "close_friend":
relationship_delta = 1.5
else:
relationship_delta = 1.0
else:
# Context-aware fallback responses
memory_groups = self.memory.get_contextual_memories(query=message, limit=3)
if memory_groups["core"]:
# Reference core memories for continuity
response = f"Based on our relationship, I think {message.lower()} connects to what we've discussed before."
relationship_delta = 1.5
elif state.current_mood == "joyful":
response = f"What a wonderful day! {message} sounds interesting!"
relationship_delta = 2.0
elif relationship.status.value == "close_friend":
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:
try:
from .ai_provider import create_ai_provider
ai_provider = create_ai_provider()
summary = self.memory.create_smart_summary(user_id, ai_provider=ai_provider)
if summary:
self.logger.info(f"Created smart summary for interactions with {user_id}")
except Exception as e:
self.logger.warning(f"Could not create AI summary for {user_id}: {e}")
self._save_state()
self.logger.info("Daily maintenance completed")

View File

@@ -0,0 +1,321 @@
"""Project management and continuous development logic for ai.shell"""
import json
import os
from pathlib import Path
from typing import Dict, List, Optional, Any
from datetime import datetime
import subprocess
import hashlib
from .models import Memory
from .ai_provider import AIProvider
class ProjectState:
"""プロジェクトの現在状態を追跡"""
def __init__(self, project_root: Path):
self.project_root = project_root
self.files_state: Dict[str, str] = {} # ファイルパス: ハッシュ
self.last_analysis: Optional[datetime] = None
self.project_context: Optional[str] = None
self.development_goals: List[str] = []
self.known_patterns: Dict[str, Any] = {}
def scan_project_files(self) -> Dict[str, str]:
"""プロジェクトファイルをスキャンしてハッシュ計算"""
current_state = {}
# 対象ファイル拡張子
target_extensions = {'.py', '.js', '.ts', '.rs', '.go', '.java', '.cpp', '.c', '.h'}
for file_path in self.project_root.rglob('*'):
if (file_path.is_file() and
file_path.suffix in target_extensions and
not any(part.startswith('.') for part in file_path.parts)):
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
file_hash = hashlib.md5(content.encode()).hexdigest()
relative_path = str(file_path.relative_to(self.project_root))
current_state[relative_path] = file_hash
except Exception:
continue
return current_state
def detect_changes(self) -> Dict[str, str]:
"""ファイル変更を検出"""
current_state = self.scan_project_files()
changes = {}
# 新規・変更ファイル
for path, current_hash in current_state.items():
if path not in self.files_state or self.files_state[path] != current_hash:
changes[path] = "modified" if path in self.files_state else "added"
# 削除ファイル
for path in self.files_state:
if path not in current_state:
changes[path] = "deleted"
self.files_state = current_state
return changes
class ContinuousDeveloper:
"""Claude Code的な継続開発機能"""
def __init__(self, project_root: Path, ai_provider: Optional[AIProvider] = None):
self.project_root = project_root
self.ai_provider = ai_provider
self.project_state = ProjectState(project_root)
self.session_memory: List[str] = []
def load_project_context(self) -> str:
"""プロジェクト文脈を読み込み"""
context_files = [
"claude.md", "aishell.md", "README.md",
"pyproject.toml", "package.json", "Cargo.toml"
]
context_parts = []
for filename in context_files:
file_path = self.project_root / filename
if file_path.exists():
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
context_parts.append(f"## {filename}\n{content}")
except Exception:
continue
return "\n\n".join(context_parts)
def analyze_project_structure(self) -> Dict[str, Any]:
"""プロジェクト構造を分析"""
analysis = {
"language": self._detect_primary_language(),
"framework": self._detect_framework(),
"structure": self._analyze_file_structure(),
"dependencies": self._analyze_dependencies(),
"patterns": self._detect_code_patterns()
}
return analysis
def _detect_primary_language(self) -> str:
"""主要言語を検出"""
file_counts = {}
for file_path in self.project_root.rglob('*'):
if file_path.is_file() and file_path.suffix:
ext = file_path.suffix.lower()
file_counts[ext] = file_counts.get(ext, 0) + 1
language_map = {
'.py': 'Python',
'.js': 'JavaScript',
'.ts': 'TypeScript',
'.rs': 'Rust',
'.go': 'Go',
'.java': 'Java'
}
if file_counts:
primary_ext = max(file_counts.items(), key=lambda x: x[1])[0]
return language_map.get(primary_ext, 'Unknown')
return 'Unknown'
def _detect_framework(self) -> str:
"""フレームワークを検出"""
frameworks = {
'fastapi': ['fastapi', 'uvicorn'],
'django': ['django'],
'flask': ['flask'],
'react': ['react'],
'next.js': ['next'],
'rust-actix': ['actix-web'],
}
# pyproject.toml, package.json, Cargo.tomlから依存関係を確認
for config_file in ['pyproject.toml', 'package.json', 'Cargo.toml']:
config_path = self.project_root / config_file
if config_path.exists():
try:
with open(config_path, 'r') as f:
content = f.read().lower()
for framework, keywords in frameworks.items():
if any(keyword in content for keyword in keywords):
return framework
except Exception:
continue
return 'Unknown'
def _analyze_file_structure(self) -> Dict[str, List[str]]:
"""ファイル構造を分析"""
structure = {"directories": [], "key_files": []}
for item in self.project_root.iterdir():
if item.is_dir() and not item.name.startswith('.'):
structure["directories"].append(item.name)
elif item.is_file() and item.name in [
'main.py', 'app.py', 'index.js', 'main.rs', 'main.go'
]:
structure["key_files"].append(item.name)
return structure
def _analyze_dependencies(self) -> List[str]:
"""依存関係を分析"""
deps = []
# Python dependencies
pyproject = self.project_root / "pyproject.toml"
if pyproject.exists():
try:
with open(pyproject, 'r') as f:
content = f.read()
# Simple regex would be better but for now just check for common packages
common_packages = ['fastapi', 'pydantic', 'uvicorn', 'ollama', 'openai']
for package in common_packages:
if package in content:
deps.append(package)
except Exception:
pass
return deps
def _detect_code_patterns(self) -> Dict[str, int]:
"""コードパターンを検出"""
patterns = {
"classes": 0,
"functions": 0,
"api_endpoints": 0,
"async_functions": 0
}
for py_file in self.project_root.rglob('*.py'):
try:
with open(py_file, 'r', encoding='utf-8') as f:
content = f.read()
patterns["classes"] += content.count('class ')
patterns["functions"] += content.count('def ')
patterns["api_endpoints"] += content.count('@app.')
patterns["async_functions"] += content.count('async def')
except Exception:
continue
return patterns
def suggest_next_steps(self, current_task: Optional[str] = None) -> List[str]:
"""次のステップを提案"""
if not self.ai_provider:
return ["AI provider not available for suggestions"]
context = self.load_project_context()
analysis = self.analyze_project_structure()
changes = self.project_state.detect_changes()
prompt = f"""
プロジェクト分析に基づいて、次の開発ステップを3-5個提案してください。
## プロジェクト文脈
{context[:1000]}
## 構造分析
言語: {analysis['language']}
フレームワーク: {analysis['framework']}
パターン: {analysis['patterns']}
## 最近の変更
{changes}
## 現在のタスク
{current_task or "特になし"}
具体的で実行可能なステップを提案してください:
"""
try:
response = self.ai_provider.chat(prompt, max_tokens=300)
# Simple parsing - in real implementation would be more sophisticated
steps = [line.strip() for line in response.split('\n')
if line.strip() and (line.strip().startswith('-') or line.strip().startswith('1.'))]
return steps[:5]
except Exception as e:
return [f"Error generating suggestions: {str(e)}"]
def generate_code(self, description: str, file_path: Optional[str] = None) -> str:
"""コード生成"""
if not self.ai_provider:
return "AI provider not available for code generation"
context = self.load_project_context()
analysis = self.analyze_project_structure()
prompt = f"""
以下の仕様に基づいてコードを生成してください。
## プロジェクト文脈
{context[:800]}
## 言語・フレームワーク
言語: {analysis['language']}
フレームワーク: {analysis['framework']}
既存パターン: {analysis['patterns']}
## 生成要求
{description}
{"ファイルパス: " + file_path if file_path else ""}
プロジェクトの既存コードスタイルと一貫性を保ったコードを生成してください:
"""
try:
return self.ai_provider.chat(prompt, max_tokens=500)
except Exception as e:
return f"Error generating code: {str(e)}"
def analyze_file(self, file_path: str) -> str:
"""ファイル分析"""
full_path = self.project_root / file_path
if not full_path.exists():
return f"File not found: {file_path}"
try:
with open(full_path, 'r', encoding='utf-8') as f:
content = f.read()
except Exception as e:
return f"Error reading file: {str(e)}"
if not self.ai_provider:
return f"File contents ({len(content)} chars):\n{content[:200]}..."
context = self.load_project_context()
prompt = f"""
以下のファイルを分析して、改善点や問題点を指摘してください。
## プロジェクト文脈
{context[:500]}
## ファイル: {file_path}
{content[:1500]}
分析内容:
1. コード品質
2. プロジェクトとの整合性
3. 改善提案
4. 潜在的な問題
"""
try:
return self.ai_provider.chat(prompt, max_tokens=400)
except Exception as e:
return f"Error analyzing file: {str(e)}"

135
src/aigpt/relationship.py Normal file
View File

@@ -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
}

312
src/aigpt/scheduler.py Normal file
View File

@@ -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

View File

@@ -0,0 +1,15 @@
"""Shared modules for AI ecosystem"""
from .ai_provider import (
AIProvider,
OllamaProvider,
OpenAIProvider,
create_ai_provider
)
__all__ = [
'AIProvider',
'OllamaProvider',
'OpenAIProvider',
'create_ai_provider'
]

View File

@@ -0,0 +1,139 @@
"""Shared AI Provider implementation for ai ecosystem"""
import os
import json
import logging
from typing import Optional, Dict, List, Any, Protocol
from abc import abstractmethod
import httpx
from openai import OpenAI
import ollama
class AIProvider(Protocol):
"""Protocol for AI providers"""
@abstractmethod
async def chat(self, prompt: str, system_prompt: Optional[str] = None) -> str:
"""Generate a response based on prompt"""
pass
class OllamaProvider:
"""Ollama AI provider - shared implementation"""
def __init__(self, model: str = "qwen3", host: Optional[str] = None, config_system_prompt: Optional[str] = None):
self.model = model
# Use environment variable OLLAMA_HOST if available
self.host = host or os.getenv('OLLAMA_HOST', 'http://127.0.0.1:11434')
# Ensure proper URL format
if not self.host.startswith('http'):
self.host = f'http://{self.host}'
self.client = ollama.Client(host=self.host, timeout=60.0)
self.logger = logging.getLogger(__name__)
self.logger.info(f"OllamaProvider initialized with host: {self.host}, model: {self.model}")
self.config_system_prompt = config_system_prompt
async def chat(self, prompt: str, system_prompt: Optional[str] = None) -> str:
"""Simple chat interface"""
try:
messages = []
# Use provided system_prompt, fall back to config_system_prompt
final_system_prompt = system_prompt or self.config_system_prompt
if final_system_prompt:
messages.append({"role": "system", "content": final_system_prompt})
messages.append({"role": "user", "content": prompt})
response = self.client.chat(
model=self.model,
messages=messages,
options={
"num_predict": 2000,
"temperature": 0.7,
"top_p": 0.9,
},
stream=False
)
return self._clean_response(response['message']['content'])
except Exception as e:
self.logger.error(f"Ollama chat failed (host: {self.host}): {e}")
return "I'm having trouble connecting to the AI model."
def _clean_response(self, response: str) -> str:
"""Clean response by removing think tags and other unwanted content"""
import re
# Remove <think></think> tags and their content
response = re.sub(r'<think>.*?</think>', '', response, flags=re.DOTALL)
# Remove any remaining whitespace at the beginning/end
response = response.strip()
return response
class OpenAIProvider:
"""OpenAI API provider - shared implementation"""
def __init__(self, model: str = "gpt-4o-mini", api_key: Optional[str] = None,
config_system_prompt: Optional[str] = None, mcp_client=None):
self.model = model
self.api_key = api_key or os.getenv("OPENAI_API_KEY")
if not self.api_key:
raise ValueError("OpenAI API key not provided")
self.client = OpenAI(api_key=self.api_key)
self.logger = logging.getLogger(__name__)
self.config_system_prompt = config_system_prompt
self.mcp_client = mcp_client
async def chat(self, prompt: str, system_prompt: Optional[str] = None) -> str:
"""Simple chat interface without MCP tools"""
try:
messages = []
# Use provided system_prompt, fall back to config_system_prompt
final_system_prompt = system_prompt or self.config_system_prompt
if final_system_prompt:
messages.append({"role": "system", "content": final_system_prompt})
messages.append({"role": "user", "content": prompt})
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
max_tokens=2000,
temperature=0.7
)
return response.choices[0].message.content
except Exception as e:
self.logger.error(f"OpenAI chat failed: {e}")
return "I'm having trouble connecting to the AI model."
def _get_mcp_tools(self) -> List[Dict[str, Any]]:
"""Override this method in subclasses to provide MCP tools"""
return []
async def chat_with_mcp(self, prompt: str, **kwargs) -> str:
"""Chat interface with MCP function calling support
This method should be overridden in subclasses to provide
specific MCP functionality.
"""
if not self.mcp_client:
return await self.chat(prompt)
# Default implementation - subclasses should override
return await self.chat(prompt)
async def _execute_mcp_tool(self, tool_call, **kwargs) -> Dict[str, Any]:
"""Execute MCP tool call - override in subclasses"""
return {"error": "MCP tool execution not implemented"}
def create_ai_provider(provider: str = "ollama", model: Optional[str] = None,
config_system_prompt: Optional[str] = None, mcp_client=None, **kwargs) -> AIProvider:
"""Factory function to create AI providers"""
if provider == "ollama":
model = model or "qwen3"
return OllamaProvider(model=model, config_system_prompt=config_system_prompt, **kwargs)
elif provider == "openai":
model = model or "gpt-4o-mini"
return OpenAIProvider(model=model, config_system_prompt=config_system_prompt,
mcp_client=mcp_client, **kwargs)
else:
raise ValueError(f"Unknown provider: {provider}")

111
src/aigpt/transmission.py Normal file
View File

@@ -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
}

View File

@@ -1,100 +0,0 @@
// src/chat.rs
use seahorse::Context;
use std::process::Command;
use crate::config::ConfigPaths;
#[derive(Debug, Clone, PartialEq)]
pub enum Provider {
OpenAI,
Ollama,
}
impl Provider {
pub fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"openai" => Some(Provider::OpenAI),
"ollama" => Some(Provider::Ollama),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Provider::OpenAI => "openai",
Provider::Ollama => "ollama",
}
}
}
use std::fs;
use serde::Deserialize;
#[derive(Deserialize)]
struct OpenAIKey {
token: String,
}
fn load_openai_api_key() -> Option<String> {
let config = ConfigPaths::new();
let path = config.base_dir.join("openai.json");
let data = fs::read_to_string(path).ok()?;
let parsed: OpenAIKey = serde_json::from_str(&data).ok()?;
Some(parsed.token)
}
pub fn ask_chat(c: &Context, question: &str) -> String {
let config = ConfigPaths::new();
let base_dir = config.base_dir.join("mcp");
let script_path = base_dir.join("scripts/ask.py");
let python_path = if cfg!(target_os = "windows") {
base_dir.join(".venv/Scripts/python.exe")
} else {
base_dir.join(".venv/bin/python")
};
let ollama_host = c.string_flag("host").ok();
let ollama_model = c.string_flag("model").ok();
let api_key = c.string_flag("api-key").ok()
.or_else(|| load_openai_api_key());
use crate::chat::Provider;
let provider_str = c.string_flag("provider").unwrap_or_else(|_| "ollama".to_string());
let provider = Provider::from_str(&provider_str).unwrap_or(Provider::Ollama);
println!("🔍 使用プロバイダー: {}", provider.as_str());
// 🛠️ command の定義をここで行う
let mut command = Command::new(python_path);
command.arg(script_path).arg(question);
// ✨ 環境変数をセット
command.env("PROVIDER", provider.as_str());
if let Some(host) = ollama_host {
command.env("OLLAMA_HOST", host);
}
if let Some(model) = ollama_model {
command.env("OLLAMA_MODEL", model);
}
if let Some(api_key) = api_key {
command.env("OPENAI_API_KEY", api_key);
}
let output = command
.output()
.expect("❌ MCPチャットスクリプトの実行に失敗しました");
if output.status.success() {
String::from_utf8_lossy(&output.stdout).to_string()
} else {
eprintln!(
"❌ 実行エラー: {}\n{}",
String::from_utf8_lossy(&output.stderr),
String::from_utf8_lossy(&output.stdout),
);
String::from("エラーが発生しました。")
}
}

View File

@@ -1,100 +0,0 @@
// src/cli.rs
use std::path::{Path};
use chrono::{Duration, Local};
use rusqlite::Connection;
use seahorse::{App, Command, Context};
use crate::utils::{load_config, save_config};
use crate::config::ConfigPaths;
use crate::agent::AIState;
use crate::commands::db::{save_cmd, export_cmd};
use crate::commands::scheduler::{scheduler_cmd};
use crate::commands::mcp::mcp_cmd;
pub fn cli_app() -> App {
let set_cmd = Command::new("set")
.usage("set [trust|intimacy|curiosity] [value]")
.action(|c: &Context| {
if c.args.len() != 2 {
eprintln!("Usage: set [trust|intimacy|curiosity] [value]");
std::process::exit(1);
}
let field = &c.args[0];
let value: f32 = c.args[1].parse().unwrap_or_else(|_| {
eprintln!("数値で入力してください");
std::process::exit(1);
});
// ConfigPathsを使って設定ファイルのパスを取得
let config_paths = ConfigPaths::new();
let json_path = config_paths.data_file("json");
// まだ user.json がない場合、example.json をコピー
config_paths.ensure_file_exists("json", Path::new("example.json"));
let db_path = config_paths.data_file("db");
let mut ai = load_config(json_path.to_str().unwrap());
match field.as_str() {
"trust" => ai.relationship.trust = value,
"intimacy" => ai.relationship.intimacy = value,
"curiosity" => ai.relationship.curiosity = value,
_ => {
eprintln!("trust / intimacy / curiosity のいずれかを指定してください");
std::process::exit(1);
}
}
save_config(json_path.to_str().unwrap(), &ai);
let conn = Connection::open(db_path.to_str().unwrap()).expect("DB接続失敗");
ai.save_to_db(&conn).expect("DB保存失敗");
println!("{field}{value} に更新しました");
});
let show_cmd = Command::new("show")
.usage("show")
.action(|_c: &Context| {
// ConfigPathsを使って設定ファイルのパスを取得
let config_paths = ConfigPaths::new();
let ai = load_config(config_paths.data_file("json").to_str().unwrap());
println!("🧠 現在のAI状態:\n{:#?}", ai);
});
let talk_cmd = Command::new("talk")
.usage("talk")
.action(|_c: &Context| {
let config_paths = ConfigPaths::new();
let ai = load_config(config_paths.data_file("json").to_str().unwrap());
let now = Local::now().naive_local();
let mut state = AIState {
relation_score: 80.0,
previous_score: 80.0,
decay_rate: ai.messaging.decay_rate,
sensitivity: ai.personality.strength,
message_threshold: 5.0,
last_message_time: now - Duration::days(4),
};
state.update(now);
if state.should_talk() {
println!("💬 AI発話: {}", state.generate_message());
} else {
println!("🤫 今日は静かにしているみたい...");
}
});
App::new("aigpt")
.version("0.1.0")
.description("AGE system CLI controller")
.author("syui")
.command(set_cmd)
.command(show_cmd)
.command(talk_cmd)
.command(save_cmd())
.command(export_cmd())
.command(scheduler_cmd())
.command(mcp_cmd())
}

View File

@@ -1,44 +0,0 @@
// src/commands/db.rs
use seahorse::{Command, Context};
use crate::utils::{load_config};
use crate::model::AiSystem;
use crate::config::ConfigPaths;
use rusqlite::Connection;
use std::fs;
pub fn save_cmd() -> Command {
Command::new("save")
.usage("save")
.action(|_c: &Context| {
let paths = ConfigPaths::new();
let json_path = paths.data_file("json");
let db_path = paths.data_file("db");
let ai = load_config(json_path.to_str().unwrap());
let conn = Connection::open(db_path).expect("DB接続失敗");
ai.save_to_db(&conn).expect("DB保存失敗");
println!("💾 DBに保存完了");
})
}
pub fn export_cmd() -> Command {
Command::new("export")
.usage("export [output.json]")
.action(|c: &Context| {
let output_path = c.args.get(0).map(|s| s.as_str()).unwrap_or("output.json");
let paths = ConfigPaths::new();
let db_path = paths.data_file("db");
let conn = Connection::open(db_path).expect("DB接続失敗");
let ai = AiSystem::load_from_db(&conn).expect("DB読み込み失敗");
let json = serde_json::to_string_pretty(&ai).expect("JSON変換失敗");
fs::write(output_path, json).expect("ファイル書き込み失敗");
println!("📤 JSONにエクスポート完了: {output_path}");
})
}

View File

@@ -1,17 +0,0 @@
// src/commands/git_repo.rs
use std::fs;
// Gitリポジトリ内の全てのファイルを取得し、内容を読み取る
pub fn read_all_git_files(repo_path: &str) -> String {
let mut content = String::new();
for entry in fs::read_dir(repo_path).expect("ディレクトリ読み込み失敗") {
let entry = entry.expect("エントリ読み込み失敗");
let path = entry.path();
if path.is_file() {
if let Ok(file_content) = fs::read_to_string(&path) {
content.push_str(&format!("\n\n# File: {}\n{}", path.display(), file_content));
}
}
}
content
}

View File

@@ -1,248 +0,0 @@
// src/commands/mcp.rs
use std::fs;
use std::path::{PathBuf};
use std::process::Command as OtherCommand;
use serde_json::json;
use seahorse::{Command, Context, Flag, FlagType};
use crate::chat::ask_chat;
use crate::git::{git_init, git_status};
use crate::config::ConfigPaths;
use crate::commands::git_repo::read_all_git_files;
pub fn mcp_setup() {
let config = ConfigPaths::new();
let dest_dir = config.base_dir.join("mcp");
let repo_url = "https://github.com/microsoft/MCP.git";
println!("📁 MCP ディレクトリ: {}", dest_dir.display());
// 1. git cloneもしまだなければ
if !dest_dir.exists() {
let status = OtherCommand::new("git")
.args(&["clone", repo_url, dest_dir.to_str().unwrap()])
.status()
.expect("git clone に失敗しました");
assert!(status.success(), "git clone 実行時にエラーが発生しました");
}
let asset_base = PathBuf::from("mcp");
let files_to_copy = vec![
"cli.py",
"setup.py",
"scripts/ask.py",
"scripts/context_loader.py",
"scripts/prompt_template.py",
];
for rel_path in files_to_copy {
let src = asset_base.join(rel_path);
let dst = dest_dir.join(rel_path);
if let Some(parent) = dst.parent() {
let _ = fs::create_dir_all(parent);
}
if let Err(e) = fs::copy(&src, &dst) {
eprintln!("❌ コピー失敗: {}{}: {}", src.display(), dst.display(), e);
} else {
println!("✅ コピー: {}{}", src.display(), dst.display());
}
}
// venvの作成
let venv_path = dest_dir.join(".venv");
if !venv_path.exists() {
println!("🐍 仮想環境を作成しています...");
let output = OtherCommand::new("python3")
.args(&["-m", "venv", ".venv"])
.current_dir(&dest_dir)
.output()
.expect("venvの作成に失敗しました");
if !output.status.success() {
eprintln!("❌ venv作成エラー: {}", String::from_utf8_lossy(&output.stderr));
return;
}
}
// `pip install -e .` を仮想環境で実行
let pip_path = if cfg!(target_os = "windows") {
dest_dir.join(".venv/Scripts/pip.exe").to_string_lossy().to_string()
} else {
dest_dir.join(".venv/bin/pip").to_string_lossy().to_string()
};
println!("📦 必要なパッケージをインストールしています...");
let output = OtherCommand::new(&pip_path)
.arg("install")
.arg("openai")
.current_dir(&dest_dir)
.output()
.expect("pip install に失敗しました");
if !output.status.success() {
eprintln!(
"❌ pip エラー: {}\n{}",
String::from_utf8_lossy(&output.stderr),
String::from_utf8_lossy(&output.stdout)
);
return;
}
println!("📦 pip install -e . を実行します...");
let output = OtherCommand::new(&pip_path)
.arg("install")
.arg("-e")
.arg(".")
.current_dir(&dest_dir)
.output()
.expect("pip install に失敗しました");
if output.status.success() {
println!("🎉 MCP セットアップが完了しました!");
} else {
eprintln!(
"❌ pip エラー: {}\n{}",
String::from_utf8_lossy(&output.stderr),
String::from_utf8_lossy(&output.stdout)
);
}
}
fn set_api_key_cmd() -> Command {
Command::new("set-api")
.description("OpenAI APIキーを設定")
.usage("mcp set-api --api <API_KEY>")
.flag(Flag::new("api", FlagType::String).description("OpenAI APIキー").alias("a"))
.action(|c: &Context| {
if let Ok(api_key) = c.string_flag("api") {
let config = ConfigPaths::new();
let path = config.base_dir.join("openai.json");
let json_data = json!({ "token": api_key });
if let Err(e) = fs::write(&path, serde_json::to_string_pretty(&json_data).unwrap()) {
eprintln!("❌ ファイル書き込み失敗: {}", e);
} else {
println!("✅ APIキーを保存しました: {}", path.display());
}
} else {
eprintln!("❗ APIキーを --api で指定してください");
}
})
}
fn chat_cmd() -> Command {
Command::new("chat")
.description("チャットで質問を送る")
.usage("mcp chat '質問内容' --host <OLLAMA_HOST> --model <MODEL> [--provider <ollama|openai>] [--api-key <KEY>]")
.flag(
Flag::new("host", FlagType::String)
.description("OLLAMAホストのURL")
.alias("H"),
)
.flag(
Flag::new("model", FlagType::String)
.description("モデル名 (OLLAMA_MODEL / OPENAI_MODEL)")
.alias("m"),
)
.flag(
Flag::new("provider", FlagType::String)
.description("使用するプロバイダ (ollama / openai)")
.alias("p"),
)
.flag(
Flag::new("api-key", FlagType::String)
.description("OpenAI APIキー")
.alias("k"),
)
.flag(
Flag::new("repo", FlagType::String)
.description("Gitリポジトリのパスを指定 (すべてのコードを読み込む)")
.alias("r"),
)
.action(|c: &Context| {
if let Some(question) = c.args.get(0) {
let response = ask_chat(c, question);
println!("💬 応答:\n{}", response);
} else {
eprintln!("❗ 質問が必要です: mcp chat 'こんにちは'");
}
})
.action(|c: &Context| {
let config = ConfigPaths::new();
if let Ok(repo_url) = c.string_flag("repo") {
let repo_base = config.base_dir.join("repos");
let repo_dir = repo_base.join(sanitize_repo_name(&repo_url));
if !repo_dir.exists() {
println!("📥 Gitリポジトリをクローン中: {}", repo_url);
let status = OtherCommand::new("git")
.args(&["clone", &repo_url, repo_dir.to_str().unwrap()])
.status()
.expect("❌ Gitのクローンに失敗しました");
assert!(status.success(), "Git clone エラー");
} else {
println!("✔ リポジトリはすでに存在します: {}", repo_dir.display());
}
//let files = read_all_git_files(&repo_dir);
let files = read_all_git_files(repo_dir.to_str().unwrap());
let prompt = format!(
"以下のコードベースを読み込んで、改善案や次のステップを提案してください:\n{}",
files
);
let response = ask_chat(c, &prompt);
println!("💡 提案:\n{}", response);
} else {
if let Some(question) = c.args.get(0) {
let response = ask_chat(c, question);
println!("💬 {}", response);
} else {
eprintln!("❗ 質問が必要です: mcp chat 'こんにちは'");
}
}
})
}
fn init_cmd() -> Command {
Command::new("init")
.description("Git 初期化")
.usage("mcp init")
.action(|_| {
git_init();
})
}
fn status_cmd() -> Command {
Command::new("status")
.description("Git ステータス表示")
.usage("mcp status")
.action(|_| {
git_status();
})
}
fn setup_cmd() -> Command {
Command::new("setup")
.description("MCP の初期セットアップ")
.usage("mcp setup")
.action(|_| {
mcp_setup();
})
}
pub fn mcp_cmd() -> Command {
Command::new("mcp")
.description("MCP操作コマンド")
.usage("mcp <subcommand>")
.alias("m")
.command(chat_cmd())
.command(init_cmd())
.command(status_cmd())
.command(setup_cmd())
.command(set_api_key_cmd())
}
// ファイル名として安全な形に変換
fn sanitize_repo_name(repo_url: &str) -> String {
repo_url.replace("://", "_").replace("/", "_").replace("@", "_")
}

View File

@@ -1,4 +0,0 @@
pub mod db;
pub mod scheduler;
pub mod mcp;
pub mod git_repo;

View File

@@ -1,29 +0,0 @@
// src/commands/scheduler.rs
use seahorse::{Command, Context};
use std::thread;
use std::time::Duration;
use chrono::Local;
pub fn scheduler_cmd() -> Command {
Command::new("scheduler")
.usage("scheduler [interval_sec]")
.alias("s")
.action(|c: &Context| {
let interval = c.args.get(0)
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(60); // デフォルト: 60秒ごと
println!("⏳ スケジューラー開始({interval}秒ごと)...");
loop {
let now = Local::now();
println!("🔁 タスク実行中: {}", now.format("%Y-%m-%d %H:%M:%S"));
// ここで talk_cmd や save_cmd の内部処理を呼ぶ感じ
// たとえば load_config → AI更新 → print とか
thread::sleep(Duration::from_secs(interval));
}
})
}

View File

@@ -1,46 +0,0 @@
// src/config.rs
use std::fs;
use std::path::{Path, PathBuf};
use shellexpand;
pub struct ConfigPaths {
pub base_dir: PathBuf,
}
impl ConfigPaths {
pub fn new() -> Self {
let app_name = env!("CARGO_PKG_NAME");
let mut base_dir = shellexpand::tilde("~").to_string();
base_dir.push_str(&format!("/.config/{}/", app_name));
let base_path = Path::new(&base_dir);
if !base_path.exists() {
let _ = fs::create_dir_all(base_path);
}
ConfigPaths {
base_dir: base_path.to_path_buf(),
}
}
pub fn data_file(&self, file_name: &str) -> PathBuf {
let file_path = match file_name {
"db" => self.base_dir.join("user.db"),
"toml" => self.base_dir.join("user.toml"),
"json" => self.base_dir.join("user.json"),
_ => self.base_dir.join(format!(".{}", file_name)),
};
file_path
}
/// 設定ファイルがなければ `example.json` をコピーする
pub fn ensure_file_exists(&self, file_name: &str, template_path: &Path) {
let target = self.data_file(file_name);
if !target.exists() {
if let Err(e) = fs::copy(template_path, &target) {
eprintln!("⚠️ 設定ファイルの初期化に失敗しました: {}", e);
} else {
println!("📄 {}{} にコピーしました", template_path.display(), target.display());
}
}
}
}

View File

@@ -1,42 +0,0 @@
// src/git.rs
use std::process::Command;
pub fn git_status() {
run_git_command(&["status"]);
}
pub fn git_init() {
run_git_command(&["init"]);
}
#[allow(dead_code)]
pub fn git_commit(message: &str) {
run_git_command(&["add", "."]);
run_git_command(&["commit", "-m", message]);
}
#[allow(dead_code)]
pub fn git_push() {
run_git_command(&["push"]);
}
#[allow(dead_code)]
pub fn git_pull() {
run_git_command(&["pull"]);
}
#[allow(dead_code)]
pub fn git_branch() {
run_git_command(&["branch"]);
}
fn run_git_command(args: &[&str]) {
let status = Command::new("git")
.args(args)
.status()
.expect("git コマンドの実行に失敗しました");
if !status.success() {
eprintln!("⚠️ git コマンドに失敗しました: {:?}", args);
}
}

View File

@@ -1,13 +0,0 @@
//src/logic.rs
use crate::model::AiSystem;
#[allow(dead_code)]
pub fn should_send(ai: &AiSystem) -> bool {
let r = &ai.relationship;
let env = &ai.environment;
let score = r.trust + r.intimacy + r.curiosity;
let relationship_ok = score >= r.threshold;
let luck_ok = env.luck_today > 0.5;
ai.messaging.enabled && relationship_ok && luck_ok
}

View File

@@ -1,19 +0,0 @@
//src/main.rs
mod model;
mod logic;
mod agent;
mod cli;
mod utils;
mod commands;
mod config;
mod git;
mod chat;
use cli::cli_app;
use seahorse::App;
fn main() {
let args: Vec<String> = std::env::args().collect();
let app: App = cli_app();
app.run(args);
}

View File

@@ -1,72 +0,0 @@
//src/model.rs
use rusqlite::{params, Connection, Result as SqlResult};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct AiSystem {
pub personality: Personality,
pub relationship: Relationship,
pub environment: Environment,
pub messaging: Messaging,
}
impl AiSystem {
pub fn save_to_db(&self, conn: &Connection) -> SqlResult<()> {
conn.execute(
"CREATE TABLE IF NOT EXISTS ai_state (id INTEGER PRIMARY KEY, json TEXT)",
[],
)?;
let json_data = serde_json::to_string(self).map_err(|e| {
rusqlite::Error::ToSqlConversionFailure(Box::new(e))
})?;
conn.execute(
"INSERT OR REPLACE INTO ai_state (id, json) VALUES (?1, ?2)",
params![1, json_data],
)?;
Ok(())
}
pub fn load_from_db(conn: &Connection) -> SqlResult<Self> {
let mut stmt = conn.prepare("SELECT json FROM ai_state WHERE id = ?1")?;
let json: String = stmt.query_row(params![1], |row| row.get(0))?;
// ここも serde_json のエラーを map_err で変換
let system: AiSystem = serde_json::from_str(&json).map_err(|e| {
rusqlite::Error::FromSqlConversionFailure(0, rusqlite::types::Type::Text, Box::new(e))
})?;
Ok(system)
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Personality {
pub kind: String, // e.g., "positive", "negative", "neutral"
pub strength: f32, // 0.0 - 1.0
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Relationship {
pub trust: f32, // 0.0 - 1.0
pub intimacy: f32, // 0.0 - 1.0
pub curiosity: f32, // 0.0 - 1.0
pub threshold: f32, // if sum > threshold, allow messaging
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Environment {
pub luck_today: f32, // 0.1 - 1.0
pub luck_history: Vec<f32>, // last 3 values
pub level: i32, // current mental strength level
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Messaging {
pub enabled: bool,
pub schedule_time: Option<String>, // e.g., "08:00"
pub decay_rate: f32, // how quickly emotion fades (0.0 - 1.0)
pub templates: Vec<String>, // message template variations
}

View File

@@ -1,13 +0,0 @@
// src/utils.rs
use std::fs;
use crate::model::AiSystem;
pub fn load_config(path: &str) -> AiSystem {
let data = fs::read_to_string(path).expect("JSON読み込み失敗");
serde_json::from_str(&data).expect("JSONパース失敗")
}
pub fn save_config(path: &str, ai: &AiSystem) {
let json = serde_json::to_string_pretty(&ai).expect("JSONシリアライズ失敗");
fs::write(path, json).expect("JSON保存失敗");
}

View File

@@ -1,42 +0,0 @@
use std::env;
use std::process::{Command, Stdio};
use std::io::{self, Write};
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
eprintln!("Usage: langchain_cli <prompt>");
std::process::exit(1);
}
let prompt = &args[1];
// Simulate a pipeline stage: e.g., tokenization, reasoning, response generation
let stages = vec!["Tokenize", "Reason", "Generate"];
for stage in &stages {
println!("[Stage: {}] Processing...", stage);
}
// Example call to Python-based LangChain (assuming you have a script or API to call)
// For placeholder purposes, we echo the prompt back.
let output = Command::new("python3")
.arg("-c")
.arg(format!("print(\"LangChain Agent Response for: {}\")", prompt))
.stdout(Stdio::piped())
.spawn()
.expect("failed to execute process")
.wait_with_output()
.expect("failed to wait on child");
io::stdout().write_all(&output.stdout).unwrap();
}
/*
TODO (for future LangChain-style pipeline):
1. Implement trait-based agent components: Tokenizer, Retriever, Reasoner, Generator.
2. Allow config via YAML or TOML to define chain flow.
3. Async pipeline support with Tokio.
4. Optional integration with LLM APIs (OpenAI, Ollama, etc).
5. Rust-native vector search (e.g. using `tantivy`, `qdrant-client`).
*/

View File

@@ -1,133 +0,0 @@
#[derive(Debug, Serialize, Deserialize)]
pub struct RelationalAutonomousAI {
pub system_name: String,
pub description: String,
pub core_components: CoreComponents,
pub extensions: Extensions,
pub note: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CoreComponents {
pub personality: Personality,
pub relationship: Relationship,
pub environment: Environment,
pub memory: Memory,
pub message_trigger: MessageTrigger,
pub message_generation: MessageGeneration,
pub state_transition: StateTransition,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Personality {
pub r#type: String,
pub variants: Vec<String>,
pub parameters: PersonalityParameters,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PersonalityParameters {
pub message_trigger_style: String,
pub decay_rate_modifier: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Relationship {
pub parameters: Vec<String>,
pub properties: RelationshipProperties,
pub decay_function: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct RelationshipProperties {
pub persistent: bool,
pub hidden: bool,
pub irreversible: bool,
pub decay_over_time: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Environment {
pub daily_luck: DailyLuck,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct DailyLuck {
pub r#type: String,
pub range: Vec<f32>,
pub update: String,
pub streak_mechanism: StreakMechanism,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct StreakMechanism {
pub trigger: String,
pub effect: String,
pub chance: f32,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Memory {
pub long_term_memory: String,
pub short_term_context: String,
pub usage_in_generation: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MessageTrigger {
pub condition: TriggerCondition,
pub timing: TriggerTiming,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct TriggerCondition {
pub relationship_threshold: String,
pub time_decay: bool,
pub environment_luck: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct TriggerTiming {
pub based_on: Vec<String>,
pub modifiers: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MessageGeneration {
pub style_variants: Vec<String>,
pub influenced_by: Vec<String>,
pub llm_integration: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct StateTransition {
pub states: Vec<String>,
pub transitions: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Extensions {
pub persistence: Persistence,
pub api: Api,
pub scheduler: Scheduler,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Persistence {
pub database: String,
pub storage_items: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Api {
pub llm: String,
pub mode: String,
pub external_event_trigger: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Scheduler {
pub async_event_loop: bool,
pub interval_check: i32,
pub time_decay_check: bool,
}

File diff suppressed because one or more lines are too long

View File

@@ -1,46 +0,0 @@
use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::{BufReader, Write};
use std::time::{SystemTime, UNIX_EPOCH};
mod model;
use model::RelationalAutonomousAI;
fn load_config(path: &str) -> std::io::Result<RelationalAutonomousAI> {
let file = File::open(path)?;
let reader = BufReader::new(file);
let config: RelationalAutonomousAI = serde_json::from_reader(reader)?;
Ok(config)
}
fn save_config(config: &RelationalAutonomousAI, path: &str) -> std::io::Result<()> {
let mut file = File::create(path)?;
let json = serde_json::to_string_pretty(config)?;
file.write_all(json.as_bytes())?;
Ok(())
}
fn should_send_message(config: &RelationalAutonomousAI) -> bool {
// 簡易な送信条件: relationshipが高く、daily_luckが0.8以上
config.core_components.relationship.parameters.contains(&"trust".to_string())
&& config.core_components.environment.daily_luck.range[1] >= 0.8
}
fn main() -> std::io::Result<()> {
let path = "config.json";
let mut config = load_config(path)?;
if should_send_message(&config) {
println!("💌 メッセージを送信できます: {:?}", config.core_components.personality.r#type);
// ステート変化の例: メッセージ送信後に記録用トランジションを追加
config.core_components.state_transition.transitions.push("message_sent".to_string());
save_config(&config, path)?;
} else {
println!("😶 まだ送信条件に達していません。");
}
Ok(())
}