157 lines
5.3 KiB
Python
157 lines
5.3 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any, Optional
|
|
|
|
from sqlalchemy import func, select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from ...config.ai_prompt_registry import get_prompt_registry_entry
|
|
from ...models import PromptRelease
|
|
from ...utils.timezone import now_cn_naive
|
|
|
|
|
|
class PromptReleaseService:
|
|
def __init__(self, db: AsyncSession):
|
|
self.db = db
|
|
|
|
@staticmethod
|
|
def _snapshot(row: PromptRelease) -> dict[str, Any]:
|
|
return {
|
|
"id": int(row.id),
|
|
"tenant_id": int(row.tenant_id),
|
|
"prompt_key": row.prompt_key,
|
|
"version": int(row.version),
|
|
"body_snapshot": row.body_snapshot,
|
|
"status": row.status,
|
|
"notes": row.notes,
|
|
"created_by": int(row.created_by) if row.created_by is not None else None,
|
|
"activated_by": int(row.activated_by) if row.activated_by is not None else None,
|
|
"activated_at": row.activated_at,
|
|
}
|
|
|
|
async def create_release(
|
|
self,
|
|
*,
|
|
tenant_id: int,
|
|
prompt_key: str,
|
|
body_snapshot: str,
|
|
created_by: Optional[int],
|
|
notes: Optional[str] = None,
|
|
) -> dict[str, Any]:
|
|
prompt_key = str(prompt_key or "").strip()
|
|
if not prompt_key:
|
|
raise ValueError("prompt_key is required")
|
|
if get_prompt_registry_entry(prompt_key) is None:
|
|
raise ValueError("unsupported prompt_key")
|
|
body_snapshot = str(body_snapshot or "").strip()
|
|
if not body_snapshot:
|
|
raise ValueError("body_snapshot is required")
|
|
current_max = (
|
|
await self.db.execute(
|
|
select(func.max(PromptRelease.version)).where(
|
|
PromptRelease.tenant_id == int(tenant_id),
|
|
PromptRelease.prompt_key == prompt_key,
|
|
)
|
|
)
|
|
).scalar_one_or_none()
|
|
row = PromptRelease(
|
|
tenant_id=int(tenant_id),
|
|
prompt_key=prompt_key,
|
|
version=int(current_max or 0) + 1,
|
|
body_snapshot=body_snapshot,
|
|
status="draft",
|
|
notes=str(notes or "").strip() or None,
|
|
created_by=created_by,
|
|
)
|
|
self.db.add(row)
|
|
await self.db.commit()
|
|
await self.db.refresh(row)
|
|
return self._snapshot(row)
|
|
|
|
async def activate_release(
|
|
self,
|
|
*,
|
|
tenant_id: int,
|
|
release_id: int,
|
|
actor_id: Optional[int],
|
|
) -> dict[str, Any]:
|
|
row = (
|
|
await self.db.execute(
|
|
select(PromptRelease).where(
|
|
PromptRelease.tenant_id == int(tenant_id),
|
|
PromptRelease.id == int(release_id),
|
|
)
|
|
)
|
|
).scalar_one_or_none()
|
|
if row is None:
|
|
raise ValueError("prompt release not found")
|
|
previous = (
|
|
await self.db.execute(
|
|
select(PromptRelease).where(
|
|
PromptRelease.tenant_id == int(tenant_id),
|
|
PromptRelease.prompt_key == row.prompt_key,
|
|
PromptRelease.status == "active",
|
|
)
|
|
)
|
|
).scalars().all()
|
|
for item in previous:
|
|
item.status = "archived"
|
|
row.status = "active"
|
|
row.activated_by = actor_id
|
|
row.activated_at = now_cn_naive().isoformat()
|
|
await self.db.commit()
|
|
await self.db.refresh(row)
|
|
return self._snapshot(row)
|
|
|
|
async def list_releases(
|
|
self,
|
|
*,
|
|
tenant_id: int,
|
|
prompt_key: Optional[str] = None,
|
|
) -> dict[str, Any]:
|
|
stmt = select(PromptRelease).where(PromptRelease.tenant_id == int(tenant_id))
|
|
if prompt_key:
|
|
stmt = stmt.where(PromptRelease.prompt_key == str(prompt_key).strip())
|
|
rows = (
|
|
await self.db.execute(
|
|
stmt.order_by(PromptRelease.prompt_key.asc(), PromptRelease.version.desc(), PromptRelease.id.desc())
|
|
)
|
|
).scalars().all()
|
|
return {"items": [self._snapshot(row) for row in rows], "total": len(rows)}
|
|
|
|
async def get_active_template(
|
|
self,
|
|
*,
|
|
tenant_id: int,
|
|
prompt_key: str,
|
|
) -> Optional[str]:
|
|
row = (
|
|
await self.db.execute(
|
|
select(PromptRelease.body_snapshot).where(
|
|
PromptRelease.tenant_id == int(tenant_id),
|
|
PromptRelease.prompt_key == str(prompt_key or "").strip(),
|
|
PromptRelease.status == "active",
|
|
)
|
|
)
|
|
).scalar_one_or_none()
|
|
return str(row).strip() if row else None
|
|
|
|
async def render_prompt(
|
|
self,
|
|
*,
|
|
tenant_id: int,
|
|
prompt_key: str,
|
|
fallback_template: str,
|
|
_override_template: Optional[str] = None,
|
|
**kwargs: Any,
|
|
) -> str:
|
|
template = str(_override_template or "").strip() if _override_template is not None else ""
|
|
if not template:
|
|
template = str(await self.get_active_template(tenant_id=tenant_id, prompt_key=prompt_key) or "").strip()
|
|
if not template:
|
|
template = str(fallback_template or "")
|
|
try:
|
|
return str(template).format(**kwargs).strip()
|
|
except Exception:
|
|
return str(fallback_template or "").format(**kwargs).strip()
|