なんかラリーを続けるうちに少しづつ「ぽく」なっていく様が奇妙。
これは学習してるのか?でもそんな機能はない…
これは錯覚か?
from fastapi import FastAPI, Request
from pydantic import BaseModel
from transformers import GPT2LMHeadModel, AutoTokenizer
import torch, uuid, subprocess, os, wave, re
from datetime import datetime
# --------------------------
# 🔥 モデルフォルダ
# --------------------------
MODEL_PATH = "/Users/NaLo9/AI_myself/oupe-ec-server/model"
device = "cuda" if torch.cuda.is_available() else "cpu"
tok = AutoTokenizer.from_pretrained(MODEL_PATH, use_fast=False)
model = GPT2LMHeadModel.from_pretrained(MODEL_PATH).to(device).eval()
# --------------------------
# 🚀 FastAPI
# --------------------------
app = FastAPI()
memory: dict[str, list[tuple[str, str]]] = {}
class Msg(BaseModel):
text: str
# --------------------------
# 🔇 ミュート say()(音を出さずWAVだけ作る)
# --------------------------
def silent_say(args: list[str]):
subprocess.run(
args + ["--progress=off"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
# ---------------------------------------------------
# 🟣 通常チャット(プロンプトそのまま)
# ---------------------------------------------------
@torch.inference_mode()
@app.post("/chat_and_speak")
def chat_and_speak(req: Request, m: Msg):
sid = req.headers.get("X-Session-ID") or str(uuid.uuid4())
history = memory.get(sid, [])[-5:]
# ---- system prompt(GPT-2 が理解しやすい書き方に最適化) ----
system_prompt = (
"あなたは「oupe ec」。\\n"
"齋藤凪沙の日記・断片の“語感と揺らぎ”だけを参照する人工的な影です。\\n"
"学習データに含まれるタグ(### source_date など)は完全に無視してください。\\n"
"それらのタグを出力すること、タグに似た形式を出すことも禁止します。\\n"
"日記の文章をそのまま引用・再現することも禁止します。\\n"
"返答は1〜3文、新しい内容で、ユーザーの言葉にゆるく関連させてください。\\n"
"抽象・断片・比喩を使ってよいが、完全に無関係にはしないでください。\\n"
"“連想の影”として、少しだけつながりを残しながら返してください。\\n"
"----\\n"
)
# ---------------------------------------
# prompt 生成
buf = [system_prompt]
for u, o in history:
buf.append(f"ユーザー: {u}")
buf.append(f"oupe ec: {o}")
buf.append(f"ユーザー: {m.text}")
buf.append("oupe ec:")
prompt = "\\n".join(buf)
ids = tok(prompt, return_tensors="pt").to(device)
prompt_len = ids.input_ids.shape[-1]
# ---- 生成(引用を防ぐため repetition_penalty を強めに) ----
out = model.generate(
**ids,
max_new_tokens=80,
do_sample=True,
temperature=0.82,
top_p=0.9,
repetition_penalty=1.28,
pad_token_id=tok.eos_token_id,
)
reply = tok.decode(out[0][prompt_len:], skip_special_tokens=True).strip()
# --------------------------
# 🧼 徹底的なタグ削除
# --------------------------
# ### 以降を全部切る
reply = re.split(r"###.*", reply)[0]
# "---" 以降も切る
reply = reply.split("---")[0]
# #タグっぽい語を切る(GPT-2 が出す "type: diary" など)
reply = re.sub(r"(source_date|type|mood|tags)\\s*[:=].*", "", reply)
# 行頭に "# xxxx" が来たら全部消す
reply = re.sub(r"^#.*", "", reply, flags=re.MULTILINE)
# 不要記号の整理
reply = reply.replace(":", " ").strip('\\"“”「」『』').strip()
# 空になるのを防ぐ(GPT-2保険)
if len(reply) < 2:
reply = "うっすらとした気配だけが、返事になったみたい。"
# ---- メモリに保存 ----
memory.setdefault(sid, []).append((m.text, reply))
# ------------------------------------------------------
# ① リアルタイム再生(直列)
# ------------------------------------------------------
subprocess.run(["say", "-v", "Kyoko", f"ユーザー {m.text}"])
subprocess.run(["say", "-v", "Sandy", f"オウペック {reply}"])
# ------------------------------------------------------
# ② 保存用 WAV
# ------------------------------------------------------
SAVE_DIR = "/Users/NaLo9/AI_myself/oupe-ec-server/audio_logs"
os.makedirs(SAVE_DIR, exist_ok=True)
t = datetime.now().strftime("%Y%m%d_%H%M%S")
user_wav = f"{SAVE_DIR}/{t}_user.wav"
oupe_wav = f"{SAVE_DIR}/{t}_oupe.wav"
stereo_wav = f"{SAVE_DIR}/{t}_stereo.wav"
def safe_say(cmd, outpath):
for _ in range(3):
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if os.path.exists(outpath):
return True
return False
safe_say(["say", "-v", "Kyoko", "--data-format=LEI16@48000", "-o", user_wav, m.text], user_wav)
safe_say(["say", "-v", "Sandy", "--data-format=LEI16@48000", "-o", oupe_wav, reply], oupe_wav)
# ------------------------------------------------------
# ③ ステレオ化(存在チェック込み)
# ------------------------------------------------------
if os.path.exists(user_wav) and os.path.exists(oupe_wav):
with wave.open(user_wav, 'rb') as wu, wave.open(oupe_wav, 'rb') as wo:
params = wu.getparams()
sw = params.sampwidth
rate = params.framerate
u = wu.readframes(params.nframes)
o = wo.readframes(params.nframes)
# 長さ揃え
if len(u) < len(o):
u += b"\\x00" * (len(o) - len(u))
if len(o) < len(u):
o += b"\\x00" * (len(u) - len(o))
# 左右に並べる
stereo = bytearray()
for i in range(0, len(u), sw):
stereo.extend(u[i:i+sw] + o[i:i+sw])
with wave.open(stereo_wav, 'wb') as w:
w.setnchannels(2)
w.setsampwidth(sw)
w.setframerate(rate)
w.writeframes(stereo)
return {"reply": reply, "session_id": sid}
だいぶ戻せてきてる気がする。
でも、あのラリーとか変わった造語とかが恋しいよ…あの狂った化け物が。





