first claude

This commit is contained in:
2025-05-24 23:19:30 +09:00
parent 4f55138306
commit 58e202fa1e
36 changed files with 440 additions and 1687 deletions

125
mcp/chat.py Normal file
View File

@ -0,0 +1,125 @@
# mcp/chat.py
"""
Chat client for aigpt CLI
"""
import sys
import json
import requests
from datetime import datetime
from config import init_directories, load_config, MEMORY_DIR
def save_conversation(user_message, ai_response):
"""会話をファイルに保存"""
init_directories()
conversation = {
"timestamp": datetime.now().isoformat(),
"user": user_message,
"ai": ai_response
}
# 日付ごとのファイルに保存
today = datetime.now().strftime("%Y-%m-%d")
chat_file = MEMORY_DIR / f"chat_{today}.jsonl"
with open(chat_file, "a", encoding="utf-8") as f:
f.write(json.dumps(conversation, ensure_ascii=False) + "\n")
def chat_with_ollama(config, message):
"""Ollamaとチャット"""
try:
payload = {
"model": config["model"],
"prompt": message,
"stream": False
}
response = requests.post(config["url"], json=payload, timeout=30)
response.raise_for_status()
result = response.json()
return result.get("response", "No response received")
except requests.exceptions.RequestException as e:
return f"Error connecting to Ollama: {e}"
except Exception as e:
return f"Error: {e}"
def chat_with_openai(config, message):
"""OpenAIとチャット"""
try:
headers = {
"Authorization": f"Bearer {config['api_key']}",
"Content-Type": "application/json"
}
payload = {
"model": config["model"],
"messages": [
{"role": "user", "content": message}
]
}
response = requests.post(config["url"], json=payload, headers=headers, timeout=30)
response.raise_for_status()
result = response.json()
return result["choices"][0]["message"]["content"]
except requests.exceptions.RequestException as e:
return f"Error connecting to OpenAI: {e}"
except Exception as e:
return f"Error: {e}"
def chat_with_mcp(config, message):
"""MCPサーバーとチャット"""
try:
payload = {
"message": message,
"model": config["model"]
}
response = requests.post(config["url"], json=payload, timeout=30)
response.raise_for_status()
result = response.json()
return result.get("response", "No response received")
except requests.exceptions.RequestException as e:
return f"Error connecting to MCP server: {e}"
except Exception as e:
return f"Error: {e}"
def main():
if len(sys.argv) != 2:
print("Usage: python chat.py <message>", file=sys.stderr)
sys.exit(1)
message = sys.argv[1]
try:
config = load_config()
print(f"🤖 Using {config['provider']} with model {config['model']}", file=sys.stderr)
# プロバイダに応じてチャット実行
if config["provider"] == "ollama":
response = chat_with_ollama(config, message)
elif config["provider"] == "openai":
response = chat_with_openai(config, message)
elif config["provider"] == "mcp":
response = chat_with_mcp(config, message)
else:
response = f"Unsupported provider: {config['provider']}"
# 会話を保存
save_conversation(message, response)
# レスポンスを出力
print(response)
except Exception as e:
print(f"❌ Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()

View File

@ -1,28 +0,0 @@
# cli.py
import sys
import subprocess
from pathlib import Path
SCRIPT_DIR = Path.home() / ".config" / "aigpt" / "mcp" / "scripts"
def run_script(name):
script_path = SCRIPT_DIR / f"{name}.py"
if not script_path.exists():
print(f"❌ スクリプトが見つかりません: {script_path}")
sys.exit(1)
args = sys.argv[2:] # ← "ask" の後の引数を取り出す
result = subprocess.run(["python", str(script_path)] + args, capture_output=True, text=True)
print(result.stdout)
if result.stderr:
print(result.stderr)
def main():
if len(sys.argv) < 2:
print("Usage: mcp <script>")
return
command = sys.argv[1]
if command in {"summarize", "ask", "setup", "server"}:
run_script(command)
else:
print(f"❓ 未知のコマンド: {command}")

View File

@ -1,5 +1,4 @@
# scripts/config.py
# scripts/config.py
# mcp/config.py
import os
from pathlib import Path
@ -9,11 +8,13 @@ MEMORY_DIR = BASE_DIR / "memory"
SUMMARY_DIR = MEMORY_DIR / "summary"
def init_directories():
"""必要なディレクトリを作成"""
BASE_DIR.mkdir(parents=True, exist_ok=True)
MEMORY_DIR.mkdir(parents=True, exist_ok=True)
SUMMARY_DIR.mkdir(parents=True, exist_ok=True)
def load_config():
"""環境変数から設定を読み込み"""
provider = os.getenv("PROVIDER", "ollama")
model = os.getenv("MODEL", "syui/ai" if provider == "ollama" else "gpt-4o-mini")
api_key = os.getenv("OPENAI_API_KEY", "")

3
mcp/requirements.txt Normal file
View File

@ -0,0 +1,3 @@
fastmcp>=0.1.0
uvicorn>=0.24.0
requests>=2.31.0

View File

@ -1,198 +0,0 @@
## scripts/ask.py
import sys
import json
import requests
from config import load_config
from datetime import datetime, timezone
def build_payload_openai(cfg, message: str):
return {
"model": cfg["model"],
"tools": [
{
"type": "function",
"function": {
"name": "ask_message",
"description": "過去の記憶を検索します",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "検索したい語句"
}
},
"required": ["query"]
}
}
}
],
"tool_choice": "auto",
"messages": [
{"role": "system", "content": "あなたは親しみやすいAIで、必要に応じて記憶から情報を検索して応答します。"},
{"role": "user", "content": message}
]
}
def build_payload_mcp(message: str):
return {
"tool": "ask_message", # MCPサーバー側で定義されたツール名
"input": {
"message": message
}
}
def build_payload_openai(cfg, message: str):
return {
"model": cfg["model"],
"messages": [
{"role": "system", "content": "あなたは思いやりのあるAIです。"},
{"role": "user", "content": message}
],
"temperature": 0.7
}
def call_mcp(cfg, message: str):
payload = build_payload_mcp(message)
headers = {"Content-Type": "application/json"}
response = requests.post(cfg["url"], headers=headers, json=payload)
response.raise_for_status()
return response.json().get("output", {}).get("response", "❓ 応答が取得できませんでした")
def call_openai(cfg, message: str):
# ツール定義
tools = [
{
"type": "function",
"function": {
"name": "memory",
"description": "記憶を検索する",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "検索する語句"
}
},
"required": ["query"]
}
}
}
]
# 最初のメッセージ送信
payload = {
"model": cfg["model"],
"messages": [
{"role": "system", "content": "あなたはAIで、必要に応じてツールmemoryを使って記憶を検索します。"},
{"role": "user", "content": message}
],
"tools": tools,
"tool_choice": "auto"
}
headers = {
"Authorization": f"Bearer {cfg['api_key']}",
"Content-Type": "application/json",
}
res1 = requests.post(cfg["url"], headers=headers, json=payload)
res1.raise_for_status()
result = res1.json()
# 🧠 tool_call されたか確認
if "tool_calls" in result["choices"][0]["message"]:
tool_call = result["choices"][0]["message"]["tool_calls"][0]
if tool_call["function"]["name"] == "memory":
args = json.loads(tool_call["function"]["arguments"])
query = args.get("query", "")
print(f"🛠️ ツール実行: memory(query='{query}')")
# MCPエンドポイントにPOST
memory_res = requests.post("http://127.0.0.1:5000/memory/search", json={"query": query})
memory_json = memory_res.json()
tool_output = memory_json.get("result", "なし")
# tool_outputをAIに返す
followup = {
"model": cfg["model"],
"messages": [
{"role": "system", "content": "あなたはAIで、必要に応じてツールmemoryを使って記憶を検索します。"},
{"role": "user", "content": message},
{"role": "assistant", "tool_calls": result["choices"][0]["message"]["tool_calls"]},
{"role": "tool", "tool_call_id": tool_call["id"], "name": "memory", "content": tool_output}
]
}
res2 = requests.post(cfg["url"], headers=headers, json=followup)
res2.raise_for_status()
final_response = res2.json()
return final_response["choices"][0]["message"]["content"]
#print(tool_output)
#print(cfg["model"])
#print(final_response)
# ツール未使用 or 通常応答
return result["choices"][0]["message"]["content"]
def call_ollama(cfg, message: str):
payload = {
"model": cfg["model"],
"prompt": message, # `prompt` → `message` にすべき(変数未定義エラー回避)
"stream": False
}
headers = {"Content-Type": "application/json"}
response = requests.post(cfg["url"], headers=headers, json=payload)
response.raise_for_status()
return response.json().get("response", "❌ 応答が取得できませんでした")
def main():
if len(sys.argv) < 2:
print("Usage: ask.py 'your message'")
return
message = sys.argv[1]
cfg = load_config()
print(f"🔍 使用プロバイダー: {cfg['provider']}")
try:
if cfg["provider"] == "openai":
response = call_openai(cfg, message)
elif cfg["provider"] == "mcp":
response = call_mcp(cfg, message)
elif cfg["provider"] == "ollama":
response = call_ollama(cfg, message)
else:
raise ValueError(f"未対応のプロバイダー: {cfg['provider']}")
print("💬 応答:")
print(response)
# ログ保存(オプション)
save_log(message, response)
except Exception as e:
print(f"❌ 実行エラー: {e}")
def save_log(user_msg, ai_msg):
from config import MEMORY_DIR
date_str = datetime.now().strftime("%Y-%m-%d")
path = MEMORY_DIR / f"{date_str}.json"
path.parent.mkdir(parents=True, exist_ok=True)
if path.exists():
with open(path, "r") as f:
logs = json.load(f)
else:
logs = []
now = datetime.now(timezone.utc).isoformat()
logs.append({"timestamp": now, "sender": "user", "message": user_msg})
logs.append({"timestamp": now, "sender": "ai", "message": ai_msg})
with open(path, "w") as f:
json.dump(logs, f, indent=2, ensure_ascii=False)
if __name__ == "__main__":
main()

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,92 +0,0 @@
# scripts/memory_store.py
import json
from pathlib import Path
from config import MEMORY_DIR
from datetime import datetime, timezone
def load_logs(date_str=None):
if date_str is None:
date_str = datetime.now().strftime("%Y-%m-%d")
path = MEMORY_DIR / f"{date_str}.json"
if path.exists():
with open(path, "r") as f:
return json.load(f)
return []
def save_message(sender, message):
date_str = datetime.now().strftime("%Y-%m-%d")
path = MEMORY_DIR / f"{date_str}.json"
logs = load_logs(date_str)
now = datetime.now(timezone.utc).isoformat()
logs.append({"timestamp": now, "sender": sender, "message": message})
with open(path, "w") as f:
json.dump(logs, f, indent=2, ensure_ascii=False)
def search_memory(query: str):
from glob import glob
all_logs = []
pattern = re.compile(re.escape(query), re.IGNORECASE)
for file_path in sorted(MEMORY_DIR.glob("*.json")):
with open(file_path, "r") as f:
logs = json.load(f)
matched = [entry for entry in logs if pattern.search(entry["message"])]
all_logs.extend(matched)
return all_logs[-5:]
# scripts/memory_store.py
import json
from datetime import datetime
from pathlib import Path
from config import MEMORY_DIR
# ログを読み込む(指定日または当日)
def load_logs(date_str=None):
if date_str is None:
date_str = datetime.now().strftime("%Y-%m-%d")
path = MEMORY_DIR / f"{date_str}.json"
if path.exists():
with open(path, "r") as f:
return json.load(f)
return []
# メッセージを保存する
def save_message(sender, message):
date_str = datetime.now().strftime("%Y-%m-%d")
path = MEMORY_DIR / f"{date_str}.json"
logs = load_logs(date_str)
#now = datetime.utcnow().isoformat() + "Z"
now = datetime.now(timezone.utc).isoformat()
logs.append({"timestamp": now, "sender": sender, "message": message})
with open(path, "w") as f:
json.dump(logs, f, indent=2, ensure_ascii=False)
def search_memory(query: str):
from glob import glob
all_logs = []
for file_path in sorted(MEMORY_DIR.glob("*.json")):
with open(file_path, "r") as f:
logs = json.load(f)
matched = [
entry for entry in logs
if entry["sender"] == "user" and query in entry["message"]
]
all_logs.extend(matched)
return all_logs[-5:] # 最新5件だけ返す
def search_memory(query: str):
from glob import glob
all_logs = []
seen_messages = set() # すでに見たメッセージを保持
for file_path in sorted(MEMORY_DIR.glob("*.json")):
with open(file_path, "r") as f:
logs = json.load(f)
for entry in logs:
if entry["sender"] == "user" and query in entry["message"]:
# すでに同じメッセージが結果に含まれていなければ追加
if entry["message"] not in seen_messages:
all_logs.append(entry)
seen_messages.add(entry["message"])
return all_logs[-5:] # 最新5件だけ返す

View File

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

View File

@ -1,56 +0,0 @@
# server.py
from fastapi import FastAPI, Body
from fastapi_mcp import FastApiMCP
from pydantic import BaseModel
from memory_store import save_message, load_logs, search_memory as do_search_memory
app = FastAPI()
mcp = FastApiMCP(app, name="aigpt-agent", description="MCP Server for AI memory")
class ChatInput(BaseModel):
message: str
class MemoryInput(BaseModel):
sender: str
message: str
class MemoryQuery(BaseModel):
query: str
@app.post("/chat", operation_id="chat")
async def chat(input: ChatInput):
save_message("user", input.message)
response = f"AI: 「{input.message}」を受け取りました!"
save_message("ai", response)
return {"response": response}
@app.post("/memory", operation_id="save_memory")
async def memory_post(input: MemoryInput):
save_message(input.sender, input.message)
return {"status": "saved"}
@app.get("/memory", operation_id="get_memory")
async def memory_get():
return {"messages": load_messages()}
@app.post("/ask_message", operation_id="ask_message")
async def ask_message(input: MemoryQuery):
results = search_memory(input.query)
return {
"response": f"🔎 記憶から {len(results)} 件ヒット:\n" + "\n".join([f"{r['sender']}: {r['message']}" for r in results])
}
@app.post("/memory/search", operation_id="memory")
async def memory_search(query: MemoryQuery):
hits = do_search_memory(query.query)
if not hits:
return {"result": "🔍 記憶の中に該当する内容は見つかりませんでした。"}
summary = "\n".join([f"{e['sender']}: {e['message']}" for e in hits])
return {"result": f"🔎 見つかった記憶:\n{summary}"}
mcp.mount()
if __name__ == "__main__":
import uvicorn
print("🚀 Starting MCP server...")
uvicorn.run(app, host="127.0.0.1", port=5000)

View File

@ -1,76 +0,0 @@
# scripts/summarize.py
import json
from datetime import datetime
from config import MEMORY_DIR, SUMMARY_DIR, load_config
import requests
def load_memory(date_str):
path = MEMORY_DIR / f"{date_str}.json"
if not path.exists():
print(f"⚠️ メモリファイルが見つかりません: {path}")
return None
with open(path, "r") as f:
return json.load(f)
def save_summary(date_str, content):
SUMMARY_DIR.mkdir(parents=True, exist_ok=True)
path = SUMMARY_DIR / f"{date_str}_summary.json"
with open(path, "w") as f:
json.dump(content, f, indent=2, ensure_ascii=False)
print(f"✅ 要約を保存しました: {path}")
def build_prompt(logs):
messages = [
{"role": "system", "content": "あなたは要約AIです。以下の会話ログを要約してください。"},
{"role": "user", "content": "\n".join(f"{entry['sender']}: {entry['message']}" for entry in logs)}
]
return messages
def summarize_with_llm(messages):
cfg = load_config()
if cfg["provider"] == "openai":
headers = {
"Authorization": f"Bearer {cfg['api_key']}",
"Content-Type": "application/json",
}
payload = {
"model": cfg["model"],
"messages": messages,
"temperature": 0.7
}
response = requests.post(cfg["url"], headers=headers, json=payload)
response.raise_for_status()
return response.json()["choices"][0]["message"]["content"]
elif cfg["provider"] == "ollama":
payload = {
"model": cfg["model"],
"prompt": "\n".join(m["content"] for m in messages),
"stream": False,
}
response = requests.post(cfg["url"], json=payload)
response.raise_for_status()
return response.json()["response"]
else:
raise ValueError(f"Unsupported provider: {cfg['provider']}")
def main():
date_str = datetime.now().strftime("%Y-%m-%d")
logs = load_memory(date_str)
if not logs:
return
prompt_messages = build_prompt(logs)
summary_text = summarize_with_llm(prompt_messages)
summary = {
"date": date_str,
"summary": summary_text,
"total_messages": len(logs)
}
save_summary(date_str, summary)
if __name__ == "__main__":
main()

79
mcp/server.py Normal file
View File

@ -0,0 +1,79 @@
# mcp/server.py
"""
MCP Server for aigpt CLI
"""
from fastmcp import FastMCP
import platform
import os
import sys
mcp = FastMCP("AigptMCP")
@mcp.tool()
def process_text(text: str) -> str:
"""テキストを処理する"""
return f"Processed: {text}"
@mcp.tool()
def get_system_info() -> dict:
"""システム情報を取得"""
return {
"platform": platform.system(),
"version": platform.version(),
"python_version": sys.version,
"current_dir": os.getcwd()
}
@mcp.tool()
def execute_command(command: str) -> dict:
"""安全なコマンドを実行する"""
# セキュリティのため、許可されたコマンドのみ実行
allowed_commands = ["ls", "pwd", "date", "whoami"]
cmd_parts = command.split()
if not cmd_parts or cmd_parts[0] not in allowed_commands:
return {
"error": f"Command '{command}' is not allowed",
"allowed": allowed_commands
}
try:
import subprocess
result = subprocess.run(
cmd_parts,
capture_output=True,
text=True,
timeout=10
)
return {
"stdout": result.stdout,
"stderr": result.stderr,
"returncode": result.returncode
}
except subprocess.TimeoutExpired:
return {"error": "Command timed out"}
except Exception as e:
return {"error": str(e)}
@mcp.tool()
def file_operations(operation: str, filepath: str, content: str = None) -> dict:
"""ファイル操作を行う"""
try:
if operation == "read":
with open(filepath, 'r', encoding='utf-8') as f:
return {"content": f.read(), "success": True}
elif operation == "write" and content is not None:
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
return {"message": f"File written to {filepath}", "success": True}
elif operation == "exists":
return {"exists": os.path.exists(filepath), "success": True}
else:
return {"error": "Invalid operation or missing content", "success": False}
except Exception as e:
return {"error": str(e), "success": False}
if __name__ == "__main__":
print("🚀 AigptMCP Server starting...")
mcp.run()

View File

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