M2: agent logic in text mode
- LLMProvider interface: AnthropicLLM + ScriptedLLM test fake - System prompt composition from super-admin template + structured tenant data only (§6.3); auto-generated greetings with recording disclosure, out-of-hours variant, chat variant - Agent tools (§6.5): get_salon_info, check_availability (≤8 slots to model, agent offers ≤3), submit_booking_request (caller-ID fallback, hallucinated service-id guard), take_message - Conversation loop with tool dispatch, round-limit guard, transcript capture, outcome classification (request_created/message_taken/info_only/abandoned) - Terminal playground (python -m gogo.agent.cli) + demo seed (python -m gogo.seed) - Deterministic loop tests (ScriptedLLM) + Appendix A golden-scenario acceptance tests against a real LLM (skipped without an API key) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
117
gogo/seed.py
Normal file
117
gogo/seed.py
Normal file
@@ -0,0 +1,117 @@
|
||||
"""Seed demo data: the fictional 'Salon Merima' (Appendix A) + super-admin.
|
||||
|
||||
Usage: python -m gogo.seed [--partner]
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
from gogo.crypto import encrypt
|
||||
from gogo.db import Base, get_engine, get_sessionmaker
|
||||
from gogo.models import Admin, ProviderConfig, Service, Tenant, User
|
||||
|
||||
|
||||
def hash_password(plain: str) -> str:
|
||||
import bcrypt
|
||||
|
||||
return bcrypt.hashpw(plain.encode(), bcrypt.gensalt()).decode()
|
||||
|
||||
|
||||
MERIMA_HOURS = {
|
||||
"mon": [["09:00", "18:00"]],
|
||||
"tue": [["09:00", "18:00"]],
|
||||
"wed": [["09:00", "18:00"]],
|
||||
"thu": [["09:00", "18:00"]],
|
||||
"fri": [["09:00", "18:00"]],
|
||||
"sat": [["09:00", "14:00"]],
|
||||
"sun": [],
|
||||
}
|
||||
|
||||
MERIMA_SERVICES = [
|
||||
# name, duration, price_min, price_max, home_visit, note
|
||||
("Šišanje i feniranje", 45, 25, 35, False, ""),
|
||||
("Farbanje cijele dužine", 90, 60, 90, False, "cijena zavisi od dužine kose"),
|
||||
("Pramenovi", 120, 80, 120, False, ""),
|
||||
("Keratinski tretman", 90, 70, 100, False, "za oštećenu kosu"),
|
||||
("Dubinska njega", 45, 30, 40, False, "za oštećenu kosu"),
|
||||
("Manikir", 45, 20, 25, False, ""),
|
||||
("Gel nokti", 90, 50, 70, False, ""),
|
||||
("Pedikir", 60, 30, 30, True, "moguć dolazak na kućnu adresu"),
|
||||
("Depilacija nogu", 30, 15, 20, False, ""),
|
||||
]
|
||||
|
||||
|
||||
async def seed(partner: bool = False) -> None:
|
||||
engine = get_engine()
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
async with get_sessionmaker()() as session:
|
||||
existing = (
|
||||
await session.execute(select(Tenant).where(Tenant.slug == "salon-merima"))
|
||||
).scalar_one_or_none()
|
||||
if existing:
|
||||
print("salon-merima already seeded")
|
||||
return
|
||||
|
||||
tenant = Tenant(
|
||||
name="Salon Merima",
|
||||
slug="salon-merima",
|
||||
address="Ulica Veselina Masleše 12",
|
||||
city="Banja Luka",
|
||||
phone="+38751123456",
|
||||
working_hours=MERIMA_HOURS,
|
||||
scheduling_provider="partner_api" if partner else "mock",
|
||||
notify_emails=["vlasnica@salon-merima.ba"],
|
||||
agent_notes="Parking iza zgrade. Ne primamo djecu ispod 7 godina.",
|
||||
price_mode="range",
|
||||
)
|
||||
session.add(tenant)
|
||||
await session.flush()
|
||||
|
||||
for name, dur, pmin, pmax, home, note in MERIMA_SERVICES:
|
||||
session.add(
|
||||
Service(
|
||||
tenant_id=tenant.id, name=name, duration_min=dur,
|
||||
price_min=pmin, price_max=pmax, home_visit=home, agent_note=note,
|
||||
)
|
||||
)
|
||||
|
||||
if partner:
|
||||
session.add(
|
||||
ProviderConfig(
|
||||
tenant_id=tenant.id,
|
||||
provider_type="partner_api",
|
||||
config={
|
||||
"base_url": "http://localhost:9100",
|
||||
"api_key_encrypted": encrypt("test-partner-key"),
|
||||
"webhook_secret_encrypted": encrypt("test-webhook-secret"),
|
||||
"catalog_sync": False,
|
||||
"email_to_owner": True,
|
||||
"polling_fallback": False,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
session.add(
|
||||
User(
|
||||
tenant_id=tenant.id,
|
||||
email="vlasnica@salon-merima.ba",
|
||||
password_hash=hash_password("merima123"),
|
||||
)
|
||||
)
|
||||
session.add(
|
||||
Admin(email="admin@gogotelefon.ba", password_hash=hash_password("admin123"))
|
||||
)
|
||||
await session.commit()
|
||||
print(f"seeded tenant {tenant.slug} ({tenant.id})")
|
||||
print("owner login: vlasnica@salon-merima.ba / merima123")
|
||||
print("super-admin: admin@gogotelefon.ba / admin123")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(seed(partner="--partner" in sys.argv))
|
||||
Reference in New Issue
Block a user