Major refactor: Switch to complete local operation

- Remove external AI API dependency (no more OpenAI/Claude API calls)
- Claude Code now does all interpretation and scoring locally
- Zero cost: No API fees
- Complete privacy: No data sent to external servers
- Simplified dependencies: Removed openai crate and ai-analysis feature

Changes:
- ai_interpreter.rs: Simplified to lightweight wrapper
- Cargo.toml: Removed ai-analysis feature and openai dependency
- mcp/base.rs: Updated create_memory_with_ai to accept interpreted_content and priority_score from Claude Code
- memory.rs: Added create_memory_with_interpretation() method
- Documentation: Updated README, QUICKSTART, USAGE to reflect local-only operation
- Added CHANGELOG.md to track changes

How it works now:
User → Claude Code (interprets & scores) → aigpt (stores) → game result

Benefits:
 完全ローカル (Fully local)
 ゼロコスト (Zero cost)
 プライバシー保護 (Privacy protected)
 高速 (Faster - no network latency)
 シンプル (Simpler - fewer dependencies)
This commit is contained in:
Claude
2025-11-05 15:55:59 +00:00
parent dbb86cebe5
commit a235f42c61
8 changed files with 441 additions and 141 deletions

View File

@@ -1,138 +1,31 @@
use anyhow::{Context, Result};
use anyhow::Result;
#[cfg(feature = "ai-analysis")]
use openai::{
chat::{ChatCompletion, ChatCompletionMessage, ChatCompletionMessageRole},
set_key,
};
pub struct AIInterpreter {
#[cfg(feature = "ai-analysis")]
api_key: Option<String>,
}
/// AIInterpreter - Claude Code による解釈を期待する軽量ラッパー
///
/// このモジュールは外部 AI API を呼び出しません。
/// 代わりに、Claude Code 自身がコンテンツを解釈し、スコアを計算することを期待します。
///
/// 完全にローカルで動作し、API コストはゼロです。
pub struct AIInterpreter;
impl AIInterpreter {
pub fn new() -> Self {
#[cfg(feature = "ai-analysis")]
{
let api_key = std::env::var("OPENAI_API_KEY").ok();
if let Some(ref key) = api_key {
set_key(key.clone());
}
AIInterpreter { api_key }
}
#[cfg(not(feature = "ai-analysis"))]
{
AIInterpreter {}
}
AIInterpreter
}
/// AI解釈: 元のコンテンツを解釈して新しい表現を生成
#[cfg(feature = "ai-analysis")]
pub async fn interpret_content(&self, content: &str) -> Result<String> {
if self.api_key.is_none() {
return Ok(content.to_string());
}
let messages = vec![
ChatCompletionMessage {
role: ChatCompletionMessageRole::System,
content: Some("あなたは記憶を解釈するAIです。与えられたテキストを解釈し、より深い意味や文脈を抽出してください。元のテキストの本質を保ちながら、新しい視点や洞察を加えてください。".to_string()),
name: None,
function_call: None,
},
ChatCompletionMessage {
role: ChatCompletionMessageRole::User,
content: Some(format!("以下のテキストを解釈してください:\n\n{}", content)),
name: None,
function_call: None,
},
];
let chat_completion = ChatCompletion::builder("gpt-3.5-turbo", messages.clone())
.create()
.await
.context("Failed to create chat completion")?;
let response = chat_completion
.choices
.first()
.and_then(|choice| choice.message.content.clone())
.unwrap_or_else(|| content.to_string());
Ok(response)
}
#[cfg(not(feature = "ai-analysis"))]
/// コンテンツをそのまま返すClaude Code が解釈を担当)
pub async fn interpret_content(&self, content: &str) -> Result<String> {
Ok(content.to_string())
}
/// 心理判定: テキストの重要度を0.0-1.0のスコアで評価
#[cfg(feature = "ai-analysis")]
pub async fn calculate_priority_score(&self, content: &str, user_context: Option<&str>) -> Result<f32> {
if self.api_key.is_none() {
return Ok(0.5); // デフォルトスコア
}
let context_info = user_context
.map(|ctx| format!("\n\nユーザーコンテキスト: {}", ctx))
.unwrap_or_default();
let messages = vec![
ChatCompletionMessage {
role: ChatCompletionMessageRole::System,
content: Some(format!(
"あなたは記憶の重要度を評価するAIです。以下の基準で0.0-1.0のスコアをつけてください:\n\
- 感情的インパクト (0.0-0.25)\n\
- ユーザーとの関連性 (0.0-0.25)\n\
- 新規性・独自性 (0.0-0.25)\n\
- 実用性 (0.0-0.25)\n\n\
スコアのみを小数で返してください。例: 0.75{}", context_info
)),
name: None,
function_call: None,
},
ChatCompletionMessage {
role: ChatCompletionMessageRole::User,
content: Some(format!("以下のテキストの重要度を評価してください:\n\n{}", content)),
name: None,
function_call: None,
},
];
let chat_completion = ChatCompletion::builder("gpt-3.5-turbo", messages.clone())
.create()
.await
.context("Failed to create chat completion")?;
let response = chat_completion
.choices
.first()
.and_then(|choice| choice.message.content.clone())
.unwrap_or_else(|| "0.5".to_string());
// スコアを抽出(小数を含む数字)
let score = response
.trim()
.parse::<f32>()
.unwrap_or(0.5)
.min(1.0)
.max(0.0);
Ok(score)
}
#[cfg(not(feature = "ai-analysis"))]
/// デフォルトスコアを返すClaude Code が実際のスコアを決定)
pub async fn calculate_priority_score(&self, _content: &str, _user_context: Option<&str>) -> Result<f32> {
Ok(0.5) // デフォルトスコア
Ok(0.5) // デフォルト
}
/// AI解釈と心理判定を同時に実行
pub async fn analyze(&self, content: &str, user_context: Option<&str>) -> Result<(String, f32)> {
let interpreted = self.interpret_content(content).await?;
let score = self.calculate_priority_score(content, user_context).await?;
Ok((interpreted, score))
/// 解釈とスコアリングを Claude Code に委ねる
pub async fn analyze(&self, content: &str, _user_context: Option<&str>) -> Result<(String, f32)> {
Ok((content.to_string(), 0.5))
}
}