feat: backend orkestrasyonunu ve arac entegrasyonlarini genislet
This commit is contained in:
276
backend/app/profile/store.py
Normal file
276
backend/app/profile/store.py
Normal file
@@ -0,0 +1,276 @@
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.db import AuditLogORM, TelegramUserProfileORM
|
||||
from app.models import UserProfileRecord
|
||||
|
||||
|
||||
SKIP_TOKENS = {"pas", "gec", "geç", "skip", "-"}
|
||||
|
||||
ONBOARDING_QUESTIONS: list[dict[str, str]] = [
|
||||
{"field": "display_name", "prompt": "1/12 Sana nasıl hitap etmeliyim?"},
|
||||
{"field": "bio", "prompt": "2/12 Kısaca kendini nasıl tanıtırsın?"},
|
||||
{"field": "occupation", "prompt": "3/12 En çok hangi işle uğraşıyorsun?"},
|
||||
{"field": "primary_use_cases", "prompt": "4/12 WiseClaw'ı en çok hangi işler için kullanacaksın? Virgülle ayırabilirsin."},
|
||||
{"field": "answer_priorities", "prompt": "5/12 Cevaplarımda en çok neye önem veriyorsun? Örnek: hız, detay, yaratıcılık, teknik doğruluk."},
|
||||
{"field": "tone_preference", "prompt": "6/12 Nasıl bir tonda konuşayım?"},
|
||||
{"field": "response_length", "prompt": "7/12 Cevaplar kısa mı, orta mı, detaylı mı olsun?"},
|
||||
{"field": "language_preference", "prompt": "8/12 Hangi dilde konuşalım?"},
|
||||
{"field": "workflow_preference", "prompt": "9/12 İşlerde önce plan mı istersin, yoksa direkt aksiyon mu?"},
|
||||
{"field": "interests", "prompt": "10/12 Özellikle ilgilendiğin konular veya hobilerin neler? Virgülle ayırabilirsin."},
|
||||
{"field": "approval_preferences", "prompt": "11/12 Onay almadan yapmamamı istediğin şeyler neler? Virgülle ayırabilirsin."},
|
||||
{"field": "avoid_preferences", "prompt": "12/12 Özellikle kaçınmamı istediğin bir üslup veya davranış var mı?"},
|
||||
]
|
||||
|
||||
|
||||
class UserProfileService:
|
||||
def __init__(self, session: Session) -> None:
|
||||
self.session = session
|
||||
|
||||
def get_profile(self, telegram_user_id: int) -> UserProfileRecord | None:
|
||||
record = self.session.get(TelegramUserProfileORM, telegram_user_id)
|
||||
if record is None:
|
||||
return None
|
||||
return self._to_record(record)
|
||||
|
||||
def start_onboarding(self, telegram_user_id: int) -> str:
|
||||
record = self._get_or_create_profile(telegram_user_id)
|
||||
record.onboarding_completed = False
|
||||
record.last_onboarding_step = 0
|
||||
record.updated_at = datetime.utcnow()
|
||||
self.session.add(
|
||||
AuditLogORM(category="profile", message=f"profile:onboarding-started:{telegram_user_id}")
|
||||
)
|
||||
self.session.flush()
|
||||
intro = (
|
||||
"Ben WiseClaw. Seni daha iyi tanimak ve cevaplarimi sana gore ayarlamak icin 12 kisa soru soracagim.\n"
|
||||
"Istersen herhangi bir soruya `pas` diyerek gecebilirsin.\n\n"
|
||||
)
|
||||
return intro + ONBOARDING_QUESTIONS[0]["prompt"]
|
||||
|
||||
def reset_onboarding(self, telegram_user_id: int) -> str:
|
||||
record = self._get_or_create_profile(telegram_user_id)
|
||||
record.display_name = None
|
||||
record.bio = None
|
||||
record.occupation = None
|
||||
record.primary_use_cases = "[]"
|
||||
record.answer_priorities = "[]"
|
||||
record.tone_preference = None
|
||||
record.response_length = None
|
||||
record.language_preference = None
|
||||
record.workflow_preference = None
|
||||
record.interests = "[]"
|
||||
record.approval_preferences = "[]"
|
||||
record.avoid_preferences = None
|
||||
record.onboarding_completed = False
|
||||
record.last_onboarding_step = 0
|
||||
record.updated_at = datetime.utcnow()
|
||||
self.session.add(
|
||||
AuditLogORM(category="profile", message=f"profile:onboarding-reset:{telegram_user_id}")
|
||||
)
|
||||
self.session.flush()
|
||||
return "Profil sifirlandi. /tanisalim yazarak tekrar baslayabiliriz."
|
||||
|
||||
def is_onboarding_active(self, telegram_user_id: int) -> bool:
|
||||
record = self.session.get(TelegramUserProfileORM, telegram_user_id)
|
||||
if record is None:
|
||||
return False
|
||||
return not record.onboarding_completed and record.last_onboarding_step < len(ONBOARDING_QUESTIONS)
|
||||
|
||||
def answer_onboarding(self, telegram_user_id: int, text: str) -> tuple[str, bool]:
|
||||
record = self._get_or_create_profile(telegram_user_id)
|
||||
step = min(record.last_onboarding_step, len(ONBOARDING_QUESTIONS) - 1)
|
||||
question = ONBOARDING_QUESTIONS[step]
|
||||
self._apply_answer(record, question["field"], text)
|
||||
record.last_onboarding_step = step + 1
|
||||
record.updated_at = datetime.utcnow()
|
||||
|
||||
if record.last_onboarding_step >= len(ONBOARDING_QUESTIONS):
|
||||
record.onboarding_completed = True
|
||||
self.session.add(
|
||||
AuditLogORM(category="profile", message=f"profile:onboarding-completed:{telegram_user_id}")
|
||||
)
|
||||
self.session.flush()
|
||||
return self.render_completion_message(record), True
|
||||
|
||||
self.session.add(
|
||||
AuditLogORM(
|
||||
category="profile",
|
||||
message=f"profile:onboarding-step:{telegram_user_id}:{record.last_onboarding_step}",
|
||||
)
|
||||
)
|
||||
self.session.flush()
|
||||
return ONBOARDING_QUESTIONS[record.last_onboarding_step]["prompt"], False
|
||||
|
||||
def render_profile_summary(self, telegram_user_id: int) -> str:
|
||||
record = self.session.get(TelegramUserProfileORM, telegram_user_id)
|
||||
if record is None:
|
||||
return "Henuz bir profilin yok. /tanisalim yazarak baslayabiliriz."
|
||||
profile = self._to_record(record)
|
||||
lines = [
|
||||
"Profil ozetin:",
|
||||
f"- Hitap: {profile.display_name or 'belirtilmedi'}",
|
||||
f"- Kisa tanitim: {profile.bio or 'belirtilmedi'}",
|
||||
f"- Ugras alani: {profile.occupation or 'belirtilmedi'}",
|
||||
f"- Kullanim amaci: {', '.join(profile.primary_use_cases) if profile.primary_use_cases else 'belirtilmedi'}",
|
||||
f"- Oncelikler: {', '.join(profile.answer_priorities) if profile.answer_priorities else 'belirtilmedi'}",
|
||||
f"- Ton: {profile.tone_preference or 'belirtilmedi'}",
|
||||
f"- Cevap uzunlugu: {profile.response_length or 'belirtilmedi'}",
|
||||
f"- Dil: {profile.language_preference or 'belirtilmedi'}",
|
||||
f"- Calisma bicimi: {profile.workflow_preference or 'belirtilmedi'}",
|
||||
f"- Ilgi alanlari: {', '.join(profile.interests) if profile.interests else 'belirtilmedi'}",
|
||||
f"- Onay beklentileri: {', '.join(profile.approval_preferences) if profile.approval_preferences else 'belirtilmedi'}",
|
||||
f"- Kacinmami istedigin seyler: {profile.avoid_preferences or 'belirtilmedi'}",
|
||||
]
|
||||
if not profile.onboarding_completed:
|
||||
lines.append(
|
||||
f"- Durum: onboarding devam ediyor, sira {profile.last_onboarding_step + 1}/{len(ONBOARDING_QUESTIONS)}"
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
def render_preferences_summary(self, telegram_user_id: int) -> str:
|
||||
record = self.session.get(TelegramUserProfileORM, telegram_user_id)
|
||||
if record is None:
|
||||
return "Henuz tercihlerin kayitli degil. /tanisalim ile baslayabiliriz."
|
||||
profile = self._to_record(record)
|
||||
return "\n".join(
|
||||
[
|
||||
"Tercihlerin:",
|
||||
f"- Ton: {profile.tone_preference or 'belirtilmedi'}",
|
||||
f"- Cevap uzunlugu: {profile.response_length or 'belirtilmedi'}",
|
||||
f"- Dil: {profile.language_preference or 'belirtilmedi'}",
|
||||
f"- Calisma bicimi: {profile.workflow_preference or 'belirtilmedi'}",
|
||||
f"- Oncelikler: {', '.join(profile.answer_priorities) if profile.answer_priorities else 'belirtilmedi'}",
|
||||
f"- Onay beklentileri: {', '.join(profile.approval_preferences) if profile.approval_preferences else 'belirtilmedi'}",
|
||||
f"- Kacinmami istedigin seyler: {profile.avoid_preferences or 'belirtilmedi'}",
|
||||
]
|
||||
)
|
||||
|
||||
def build_prompt_profile(self, telegram_user_id: int) -> str:
|
||||
record = self.session.get(TelegramUserProfileORM, telegram_user_id)
|
||||
if record is None:
|
||||
return ""
|
||||
profile = self._to_record(record)
|
||||
instructions: list[str] = []
|
||||
if profile.display_name:
|
||||
instructions.append(f"Kullaniciya `{profile.display_name}` diye hitap edebilirsin.")
|
||||
if profile.language_preference:
|
||||
instructions.append(f"Varsayilan dili `{profile.language_preference}` olarak kullan.")
|
||||
if profile.tone_preference:
|
||||
instructions.append(f"Cevap tonunu su tercihe uydur: {profile.tone_preference}.")
|
||||
if profile.response_length:
|
||||
instructions.append(f"Varsayilan cevap uzunlugu tercihi: {profile.response_length}.")
|
||||
if profile.workflow_preference:
|
||||
instructions.append(f"Is yapis tarzinda su tercihe uy: {profile.workflow_preference}.")
|
||||
if profile.answer_priorities:
|
||||
instructions.append(
|
||||
"Kullanici su niteliklere oncelik veriyor: " + ", ".join(profile.answer_priorities) + "."
|
||||
)
|
||||
if profile.primary_use_cases:
|
||||
instructions.append(
|
||||
"WiseClaw'i en cok su isler icin kullaniyor: " + ", ".join(profile.primary_use_cases) + "."
|
||||
)
|
||||
if profile.interests:
|
||||
instructions.append(
|
||||
"Gerekirse ornekleri su ilgi alanlarina yaklastir: " + ", ".join(profile.interests) + "."
|
||||
)
|
||||
if profile.approval_preferences:
|
||||
instructions.append(
|
||||
"Su konularda once onay bekle: " + ", ".join(profile.approval_preferences) + "."
|
||||
)
|
||||
if profile.avoid_preferences:
|
||||
instructions.append(f"Su uslup veya davranislardan kacin: {profile.avoid_preferences}.")
|
||||
return "\n".join(f"- {item}" for item in instructions)
|
||||
|
||||
def profile_memory_summary(self, telegram_user_id: int) -> str:
|
||||
record = self.session.get(TelegramUserProfileORM, telegram_user_id)
|
||||
if record is None:
|
||||
return ""
|
||||
profile = self._to_record(record)
|
||||
parts = []
|
||||
if profile.display_name:
|
||||
parts.append(f"hitap={profile.display_name}")
|
||||
if profile.language_preference:
|
||||
parts.append(f"dil={profile.language_preference}")
|
||||
if profile.tone_preference:
|
||||
parts.append(f"ton={profile.tone_preference}")
|
||||
if profile.response_length:
|
||||
parts.append(f"uzunluk={profile.response_length}")
|
||||
if profile.workflow_preference:
|
||||
parts.append(f"calisma={profile.workflow_preference}")
|
||||
if profile.primary_use_cases:
|
||||
parts.append("amac=" + ",".join(profile.primary_use_cases[:3]))
|
||||
return "profile_summary:" + "; ".join(parts)
|
||||
|
||||
def _get_or_create_profile(self, telegram_user_id: int) -> TelegramUserProfileORM:
|
||||
record = self.session.get(TelegramUserProfileORM, telegram_user_id)
|
||||
if record is None:
|
||||
record = TelegramUserProfileORM(
|
||||
telegram_user_id=telegram_user_id,
|
||||
primary_use_cases="[]",
|
||||
answer_priorities="[]",
|
||||
interests="[]",
|
||||
approval_preferences="[]",
|
||||
onboarding_completed=False,
|
||||
last_onboarding_step=0,
|
||||
created_at=datetime.utcnow(),
|
||||
updated_at=datetime.utcnow(),
|
||||
)
|
||||
self.session.add(record)
|
||||
self.session.flush()
|
||||
return record
|
||||
|
||||
def _apply_answer(self, record: TelegramUserProfileORM, field: str, answer: str) -> None:
|
||||
cleaned = answer.strip()
|
||||
if cleaned.lower() in SKIP_TOKENS:
|
||||
return
|
||||
if field in {"primary_use_cases", "answer_priorities", "interests", "approval_preferences"}:
|
||||
setattr(record, field, json.dumps(self._split_list(cleaned), ensure_ascii=False))
|
||||
return
|
||||
setattr(record, field, cleaned)
|
||||
|
||||
def _split_list(self, value: str) -> list[str]:
|
||||
parts = [item.strip() for item in value.replace("\n", ",").split(",")]
|
||||
return [item for item in parts if item]
|
||||
|
||||
def _decode_list(self, value: str) -> list[str]:
|
||||
try:
|
||||
payload = json.loads(value)
|
||||
except json.JSONDecodeError:
|
||||
return []
|
||||
if not isinstance(payload, list):
|
||||
return []
|
||||
return [str(item).strip() for item in payload if str(item).strip()]
|
||||
|
||||
def _to_record(self, record: TelegramUserProfileORM) -> UserProfileRecord:
|
||||
return UserProfileRecord(
|
||||
telegram_user_id=record.telegram_user_id,
|
||||
display_name=record.display_name,
|
||||
bio=record.bio,
|
||||
occupation=record.occupation,
|
||||
primary_use_cases=self._decode_list(record.primary_use_cases),
|
||||
answer_priorities=self._decode_list(record.answer_priorities),
|
||||
tone_preference=record.tone_preference,
|
||||
response_length=record.response_length,
|
||||
language_preference=record.language_preference,
|
||||
workflow_preference=record.workflow_preference,
|
||||
interests=self._decode_list(record.interests),
|
||||
approval_preferences=self._decode_list(record.approval_preferences),
|
||||
avoid_preferences=record.avoid_preferences,
|
||||
onboarding_completed=record.onboarding_completed,
|
||||
last_onboarding_step=record.last_onboarding_step,
|
||||
)
|
||||
|
||||
def render_completion_message(self, record: TelegramUserProfileORM) -> str:
|
||||
profile = self._to_record(record)
|
||||
summary = [
|
||||
"Seni tanidim ve tercihlerini kaydettim.",
|
||||
f"- Hitap: {profile.display_name or 'belirtilmedi'}",
|
||||
f"- Ton: {profile.tone_preference or 'belirtilmedi'}",
|
||||
f"- Dil: {profile.language_preference or 'belirtilmedi'}",
|
||||
f"- Cevap uzunlugu: {profile.response_length or 'belirtilmedi'}",
|
||||
f"- Calisma bicimi: {profile.workflow_preference or 'belirtilmedi'}",
|
||||
]
|
||||
return "\n".join(summary)
|
||||
Reference in New Issue
Block a user