feat(prompt): add history tracking for prompt releases
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import uuid
|
|
||||||
import json
|
import json
|
||||||
|
import uuid
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Path
|
from fastapi import APIRouter, Depends, Path
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -8,9 +8,13 @@ from starlette.responses import StreamingResponse
|
|||||||
from app.core.logging_config import get_api_logger
|
from app.core.logging_config import get_api_logger
|
||||||
from app.core.response_utils import success
|
from app.core.response_utils import success
|
||||||
from app.dependencies import get_current_user, get_db
|
from app.dependencies import get_current_user, get_db
|
||||||
from app.models.prompt_optimizer_model import RoleType
|
from app.schemas.prompt_optimizer_schema import (
|
||||||
from app.schemas.prompt_optimizer_schema import PromptOptMessage, PromptOptModelSet, CreateSessionResponse, \
|
PromptOptMessage,
|
||||||
OptimizePromptResponse, SessionHistoryResponse, SessionMessage
|
CreateSessionResponse,
|
||||||
|
SessionHistoryResponse,
|
||||||
|
SessionMessage,
|
||||||
|
PromptSaveRequest
|
||||||
|
)
|
||||||
from app.schemas.response_schema import ApiResponse
|
from app.schemas.response_schema import ApiResponse
|
||||||
from app.services.prompt_optimizer_service import PromptOptimizerService
|
from app.services.prompt_optimizer_service import PromptOptimizerService
|
||||||
|
|
||||||
@@ -135,3 +139,109 @@ async def get_prompt_opt(
|
|||||||
"X-Accel-Buffering": "no"
|
"X-Accel-Buffering": "no"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/releases",
|
||||||
|
summary="Get prompt optimization",
|
||||||
|
response_model=ApiResponse
|
||||||
|
)
|
||||||
|
def save_prompt(
|
||||||
|
data: PromptSaveRequest,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user=Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Save a prompt release for the current tenant.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data (PromptSaveRequest): Request body containing session_id, title, and prompt.
|
||||||
|
db (Session): SQLAlchemy database session, injected via dependency.
|
||||||
|
current_user: Currently authenticated user object, injected via dependency.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ApiResponse: Standard API response containing the saved prompt release info:
|
||||||
|
- id: UUID of the prompt release
|
||||||
|
- session_id: associated session
|
||||||
|
- title: prompt title
|
||||||
|
- prompt: prompt content
|
||||||
|
- created_at: timestamp of creation
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
Any database or service exceptions are propagated to the global exception handler.
|
||||||
|
"""
|
||||||
|
service = PromptOptimizerService(db)
|
||||||
|
prompt_info = service.save_prompt(
|
||||||
|
tenant_id=current_user.tenant_id,
|
||||||
|
session_id=data.session_id,
|
||||||
|
title=data.title,
|
||||||
|
prompt=data.prompt
|
||||||
|
)
|
||||||
|
return success(data=prompt_info)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete(
|
||||||
|
"/releases/{prompt_id}",
|
||||||
|
summary="Delete prompt (soft delete)",
|
||||||
|
response_model=ApiResponse
|
||||||
|
)
|
||||||
|
def delete_prompt(
|
||||||
|
prompt_id: uuid.UUID = Path(..., description="Prompt ID"),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user=Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Soft delete a prompt release.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prompt_id
|
||||||
|
db (Session): Database session
|
||||||
|
current_user: Current logged-in user
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ApiResponse: Success message confirming deletion
|
||||||
|
"""
|
||||||
|
service = PromptOptimizerService(db)
|
||||||
|
service.delete_prompt(
|
||||||
|
tenant_id=current_user.tenant_id,
|
||||||
|
prompt_id=prompt_id
|
||||||
|
)
|
||||||
|
return success(msg="Prompt deleted successfully")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/releases/list",
|
||||||
|
summary="Get paginated list of released prompts with optional filter",
|
||||||
|
response_model=ApiResponse
|
||||||
|
)
|
||||||
|
def get_release_list(
|
||||||
|
page: int = 1,
|
||||||
|
page_size: int = 20,
|
||||||
|
keyword: str | None = None,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user=Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Retrieve paginated list of released prompts for the current tenant.
|
||||||
|
Optionally filter by keyword in title.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
page (int): Page number (starting from 1)
|
||||||
|
page_size (int): Number of items per page (max 100)
|
||||||
|
keyword (str | None): Optional keyword to filter prompt titles
|
||||||
|
db (Session): Database session
|
||||||
|
current_user: Current logged-in user
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ApiResponse: Contains paginated list of prompt releases with metadata
|
||||||
|
"""
|
||||||
|
service = PromptOptimizerService(db)
|
||||||
|
result = service.get_release_list(
|
||||||
|
tenant_id=current_user.tenant_id,
|
||||||
|
page=max(1, page),
|
||||||
|
page_size=min(max(1, page_size), 100),
|
||||||
|
filter_keyword=keyword
|
||||||
|
)
|
||||||
|
return success(data=result)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import datetime
|
|||||||
import uuid
|
import uuid
|
||||||
from enum import StrEnum
|
from enum import StrEnum
|
||||||
|
|
||||||
from sqlalchemy import Column, ForeignKey, Text, DateTime, String, Index
|
from sqlalchemy import Column, ForeignKey, Text, DateTime, String, Index, Boolean
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
|
|
||||||
from app.db import Base
|
from app.db import Base
|
||||||
@@ -121,10 +121,33 @@ class PromptOptimizerSessionHistory(Base):
|
|||||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
|
||||||
tenant_id = Column(UUID(as_uuid=True), ForeignKey("tenants.id"), nullable=False, comment="Tenant ID")
|
tenant_id = Column(UUID(as_uuid=True), ForeignKey("tenants.id"), nullable=False, comment="Tenant ID")
|
||||||
# app_id = Column(UUID(as_uuid=True), ForeignKey("apps.id"), nullable=False, comment="Application ID")
|
# app_id = Column(UUID(as_uuid=True), ForeignKey("apps.id"), nullable=False, comment="Application ID")
|
||||||
session_id = Column(UUID(as_uuid=True), ForeignKey("prompt_opt_session_list.id"),nullable=False, comment="Session ID")
|
session_id = Column(
|
||||||
|
UUID(as_uuid=True),
|
||||||
|
ForeignKey("prompt_opt_session_list.id"),
|
||||||
|
nullable=False,
|
||||||
|
comment="Session ID"
|
||||||
|
)
|
||||||
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False, comment="User ID")
|
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False, comment="User ID")
|
||||||
role = Column(String, nullable=False, comment="Message Role")
|
role = Column(String, nullable=False, comment="Message Role")
|
||||||
content = Column(Text, nullable=False, comment="Message Content")
|
content = Column(Text, nullable=False, comment="Message Content")
|
||||||
# prompt = Column(Text, nullable=False, comment="Prompt")
|
# prompt = Column(Text, nullable=False, comment="Prompt")
|
||||||
|
|
||||||
created_at = Column(DateTime, default=datetime.datetime.now, comment="Creation Time", index=True)
|
created_at = Column(DateTime, default=datetime.datetime.now, comment="Creation Time", index=True)
|
||||||
|
|
||||||
|
|
||||||
|
class PromptHistory(Base):
|
||||||
|
__tablename__ = "prompt_history"
|
||||||
|
|
||||||
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
|
||||||
|
tenant_id = Column(UUID(as_uuid=True), ForeignKey("tenants.id"), nullable=False, comment="Tenant ID")
|
||||||
|
|
||||||
|
session_id = Column(
|
||||||
|
UUID(as_uuid=True),
|
||||||
|
ForeignKey("prompt_opt_session_list.id"),
|
||||||
|
nullable=False,
|
||||||
|
comment="Session ID"
|
||||||
|
)
|
||||||
|
title = Column(String, nullable=False, comment="Title")
|
||||||
|
prompt = Column(Text, nullable=False, comment="Prompt")
|
||||||
|
created_at = Column(DateTime, default=datetime.datetime.now, comment="Creation Time", index=True)
|
||||||
|
is_delete = Column(Boolean, default=False, comment="Delete")
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ from sqlalchemy.orm import Session
|
|||||||
|
|
||||||
from app.core.logging_config import get_db_logger
|
from app.core.logging_config import get_db_logger
|
||||||
from app.models.prompt_optimizer_model import (
|
from app.models.prompt_optimizer_model import (
|
||||||
PromptOptimizerSession, PromptOptimizerSessionHistory, RoleType
|
PromptOptimizerSession,
|
||||||
|
PromptOptimizerSessionHistory,
|
||||||
|
RoleType,
|
||||||
|
PromptHistory
|
||||||
)
|
)
|
||||||
|
|
||||||
db_logger = get_db_logger()
|
db_logger = get_db_logger()
|
||||||
@@ -16,6 +19,12 @@ class PromptOptimizerSessionRepository:
|
|||||||
def __init__(self, db: Session):
|
def __init__(self, db: Session):
|
||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
|
def get_session_by_id(self, session_id: uuid.UUID) -> PromptOptimizerSession | None:
|
||||||
|
session = self.db.query(PromptOptimizerSession).filter(
|
||||||
|
PromptOptimizerSession.id == session_id,
|
||||||
|
).first()
|
||||||
|
return session
|
||||||
|
|
||||||
def create_session(
|
def create_session(
|
||||||
self,
|
self,
|
||||||
tenant_id: uuid.UUID,
|
tenant_id: uuid.UUID,
|
||||||
@@ -38,12 +47,9 @@ class PromptOptimizerSessionRepository:
|
|||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
)
|
)
|
||||||
self.db.add(session)
|
self.db.add(session)
|
||||||
self.db.commit()
|
|
||||||
self.db.refresh(session)
|
|
||||||
db_logger.debug(f"Prompt optimization session created: ID:{session.id}")
|
|
||||||
return session
|
return session
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db_logger.error(f"Error creating prompt optimization session: user_id={user_id} - {str(e)}")
|
db_logger.error(f"Error creating prompt optimization session: - {str(e)}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def get_session_history(
|
def get_session_history(
|
||||||
@@ -71,10 +77,10 @@ class PromptOptimizerSessionRepository:
|
|||||||
PromptOptimizerSession.id == session_id,
|
PromptOptimizerSession.id == session_id,
|
||||||
PromptOptimizerSession.user_id == user_id
|
PromptOptimizerSession.user_id == user_id
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if not session:
|
if not session:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
history = self.db.query(PromptOptimizerSessionHistory).filter(
|
history = self.db.query(PromptOptimizerSessionHistory).filter(
|
||||||
PromptOptimizerSessionHistory.session_id == session.id,
|
PromptOptimizerSessionHistory.session_id == session.id,
|
||||||
PromptOptimizerSessionHistory.user_id == user_id
|
PromptOptimizerSessionHistory.user_id == user_id
|
||||||
@@ -104,11 +110,11 @@ class PromptOptimizerSessionRepository:
|
|||||||
PromptOptimizerSession.user_id == user_id,
|
PromptOptimizerSession.user_id == user_id,
|
||||||
PromptOptimizerSession.tenant_id == tenant_id
|
PromptOptimizerSession.tenant_id == tenant_id
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if not session:
|
if not session:
|
||||||
db_logger.error(f"Session {session_id} not found for user {user_id}")
|
db_logger.error(f"Session {session_id} not found for user {user_id}")
|
||||||
raise ValueError(f"Session {session_id} not found for user {user_id}")
|
raise ValueError(f"Session {session_id} not found for user {user_id}")
|
||||||
|
|
||||||
message = PromptOptimizerSessionHistory(
|
message = PromptOptimizerSessionHistory(
|
||||||
tenant_id=tenant_id,
|
tenant_id=tenant_id,
|
||||||
session_id=session.id,
|
session_id=session.id,
|
||||||
@@ -117,8 +123,199 @@ class PromptOptimizerSessionRepository:
|
|||||||
content=content,
|
content=content,
|
||||||
)
|
)
|
||||||
self.db.add(message)
|
self.db.add(message)
|
||||||
self.db.commit()
|
|
||||||
return message
|
return message
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db_logger.error(f"Error creating prompt optimization session history: session_id={session_id} - {str(e)}")
|
db_logger.error(f"Error creating prompt optimization session history: session_id={session_id} - {str(e)}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
def get_first_user_message(self, session_id: uuid.UUID) -> str | None:
|
||||||
|
"""
|
||||||
|
Get the first user message from a session.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
session_id (uuid.UUID): The session ID.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str | None: The content of the first user message, or None if not found.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
message = self.db.query(PromptOptimizerSessionHistory).filter(
|
||||||
|
PromptOptimizerSessionHistory.session_id == session_id,
|
||||||
|
PromptOptimizerSessionHistory.role == RoleType.USER.value
|
||||||
|
).order_by(
|
||||||
|
PromptOptimizerSessionHistory.created_at.asc()
|
||||||
|
).first()
|
||||||
|
|
||||||
|
return message.content if message else None
|
||||||
|
except Exception as e:
|
||||||
|
db_logger.error(f"Error getting first user message: session_id={session_id} - {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
class PromptReleaseRepository:
|
||||||
|
def __init__(self, db: Session):
|
||||||
|
self.db = db
|
||||||
|
|
||||||
|
def get_prompt_by_session_id(self, session_id: uuid.UUID) -> PromptHistory | None:
|
||||||
|
prompt_obj = self.db.query(PromptHistory).filter(
|
||||||
|
PromptHistory.session_id == session_id,
|
||||||
|
PromptHistory.is_delete.is_(False)
|
||||||
|
).first()
|
||||||
|
return prompt_obj
|
||||||
|
|
||||||
|
def create_prompt_release(
|
||||||
|
self,
|
||||||
|
tenant_id: uuid.UUID,
|
||||||
|
title: str,
|
||||||
|
session_id: uuid.UUID,
|
||||||
|
prompt: str,
|
||||||
|
) -> PromptHistory:
|
||||||
|
try:
|
||||||
|
prompt_obj = PromptHistory(
|
||||||
|
tenant_id=tenant_id,
|
||||||
|
title=title,
|
||||||
|
session_id=session_id,
|
||||||
|
prompt=prompt,
|
||||||
|
)
|
||||||
|
self.db.add(prompt_obj)
|
||||||
|
return prompt_obj
|
||||||
|
except Exception as e:
|
||||||
|
db_logger.error(f"Error creating prompt release: session_id={session_id} - {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def soft_delete_prompt(self, prompt_obj: PromptHistory) -> None:
|
||||||
|
"""
|
||||||
|
Soft delete a prompt release by setting is_delete flag to True.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prompt_obj (PromptHistory): The prompt release object to delete.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
prompt_obj.is_delete = True
|
||||||
|
db_logger.debug(f"Soft deleted prompt release: id={prompt_obj.id}, session_id={prompt_obj.session_id}")
|
||||||
|
except Exception as e:
|
||||||
|
db_logger.error(f"Error soft deleting prompt release: id={prompt_obj.id} - {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def get_prompt_by_id(self, prompt_id: uuid.UUID) -> PromptHistory | None:
|
||||||
|
"""
|
||||||
|
Get a prompt release by its ID.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prompt_id (uuid.UUID): The prompt release ID.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PromptHistory | None: The prompt release object or None if not found.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
prompt_obj = self.db.query(PromptHistory).filter(
|
||||||
|
PromptHistory.id == prompt_id
|
||||||
|
).first()
|
||||||
|
return prompt_obj
|
||||||
|
except Exception as e:
|
||||||
|
db_logger.error(f"Error getting prompt release by id: id={prompt_id} - {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def count_prompts(self, tenant_id: uuid.UUID) -> int:
|
||||||
|
"""
|
||||||
|
Count total number of non-deleted prompts for a tenant.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tenant_id (uuid.UUID): The tenant ID.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Total count of prompts.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
count = self.db.query(PromptHistory).filter(
|
||||||
|
PromptHistory.tenant_id == tenant_id,
|
||||||
|
PromptHistory.is_delete.is_(False)
|
||||||
|
).count()
|
||||||
|
return count
|
||||||
|
except Exception as e:
|
||||||
|
db_logger.error(f"Error counting prompts: tenant_id={tenant_id} - {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def get_prompts_paginated(
|
||||||
|
self,
|
||||||
|
tenant_id: uuid.UUID,
|
||||||
|
offset: int,
|
||||||
|
limit: int
|
||||||
|
) -> list[PromptHistory]:
|
||||||
|
"""
|
||||||
|
Get paginated list of prompt releases for a tenant.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tenant_id (uuid.UUID): The tenant ID.
|
||||||
|
offset (int): Number of records to skip.
|
||||||
|
limit (int): Maximum number of records to return.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[PromptHistory]: List of prompt releases.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
prompts = self.db.query(PromptHistory).filter(
|
||||||
|
PromptHistory.tenant_id == tenant_id,
|
||||||
|
PromptHistory.is_delete.is_(False)
|
||||||
|
).order_by(
|
||||||
|
PromptHistory.created_at.desc()
|
||||||
|
).offset(offset).limit(limit).all()
|
||||||
|
return prompts
|
||||||
|
except Exception as e:
|
||||||
|
db_logger.error(f"Error getting paginated prompts: tenant_id={tenant_id} - {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def count_prompts_by_keyword(self, tenant_id: uuid.UUID, keyword: str) -> int:
|
||||||
|
"""
|
||||||
|
Count total number of non-deleted prompts matching keyword for a tenant.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tenant_id (uuid.UUID): The tenant ID.
|
||||||
|
keyword (str): Search keyword for title.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Total count of matching prompts.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
count = self.db.query(PromptHistory).filter(
|
||||||
|
PromptHistory.tenant_id == tenant_id,
|
||||||
|
PromptHistory.is_delete.is_(False),
|
||||||
|
PromptHistory.title.ilike(f"%{keyword}%")
|
||||||
|
).count()
|
||||||
|
return count
|
||||||
|
except Exception as e:
|
||||||
|
db_logger.error(f"Error counting prompts by keyword: tenant_id={tenant_id}, keyword={keyword} - {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def search_prompts_paginated(
|
||||||
|
self,
|
||||||
|
tenant_id: uuid.UUID,
|
||||||
|
keyword: str,
|
||||||
|
offset: int,
|
||||||
|
limit: int
|
||||||
|
) -> list[PromptHistory]:
|
||||||
|
"""
|
||||||
|
Search prompt releases by keyword in title with pagination.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tenant_id (uuid.UUID): The tenant ID.
|
||||||
|
keyword (str): Search keyword for title.
|
||||||
|
offset (int): Number of records to skip.
|
||||||
|
limit (int): Maximum number of records to return.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[PromptHistory]: List of matching prompt releases.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
prompts = self.db.query(PromptHistory).filter(
|
||||||
|
PromptHistory.tenant_id == tenant_id,
|
||||||
|
PromptHistory.is_delete.is_(False),
|
||||||
|
PromptHistory.title.ilike(f"%{keyword}%")
|
||||||
|
).order_by(
|
||||||
|
PromptHistory.created_at.desc()
|
||||||
|
).offset(offset).limit(limit).all()
|
||||||
|
return prompts
|
||||||
|
except Exception as e:
|
||||||
|
db_logger.error(f"Error searching prompts: tenant_id={tenant_id}, keyword={keyword} - {str(e)}")
|
||||||
|
raise
|
||||||
|
|||||||
@@ -22,6 +22,23 @@ class PromptOptMessage(BaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PromptSaveRequest(BaseModel):
|
||||||
|
session_id: UUID = Field(
|
||||||
|
...,
|
||||||
|
description="Session ID"
|
||||||
|
)
|
||||||
|
|
||||||
|
title: str = Field(
|
||||||
|
...,
|
||||||
|
description="Prompt Title"
|
||||||
|
)
|
||||||
|
|
||||||
|
prompt: str = Field(
|
||||||
|
...,
|
||||||
|
description="Optimized prompt content"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PromptOptModelSet(BaseModel):
|
class PromptOptModelSet(BaseModel):
|
||||||
id: UUID | None = Field(
|
id: UUID | None = Field(
|
||||||
default=None,
|
default=None,
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ from app.models.prompt_optimizer_model import (
|
|||||||
)
|
)
|
||||||
from app.repositories.model_repository import ModelConfigRepository
|
from app.repositories.model_repository import ModelConfigRepository
|
||||||
from app.repositories.prompt_optimizer_repository import (
|
from app.repositories.prompt_optimizer_repository import (
|
||||||
PromptOptimizerSessionRepository
|
PromptOptimizerSessionRepository,
|
||||||
|
PromptReleaseRepository
|
||||||
)
|
)
|
||||||
from app.schemas.prompt_optimizer_schema import OptimizePromptResult
|
from app.schemas.prompt_optimizer_schema import OptimizePromptResult
|
||||||
|
|
||||||
@@ -28,6 +29,8 @@ logger = get_business_logger()
|
|||||||
class PromptOptimizerService:
|
class PromptOptimizerService:
|
||||||
def __init__(self, db: Session):
|
def __init__(self, db: Session):
|
||||||
self.db = db
|
self.db = db
|
||||||
|
self.optim_repo = PromptOptimizerSessionRepository(self.db)
|
||||||
|
self.release_repo = PromptReleaseRepository(self.db)
|
||||||
|
|
||||||
def get_model_config(
|
def get_model_config(
|
||||||
self,
|
self,
|
||||||
@@ -78,10 +81,12 @@ class PromptOptimizerService:
|
|||||||
Returns:
|
Returns:
|
||||||
PromptOptimzerSession: The newly created prompt optimization session.
|
PromptOptimzerSession: The newly created prompt optimization session.
|
||||||
"""
|
"""
|
||||||
session = PromptOptimizerSessionRepository(self.db).create_session(
|
session = self.optim_repo.create_session(
|
||||||
tenant_id=tenant_id,
|
tenant_id=tenant_id,
|
||||||
user_id=user_id
|
user_id=user_id
|
||||||
)
|
)
|
||||||
|
self.db.commit()
|
||||||
|
self.db.refresh(session)
|
||||||
return session
|
return session
|
||||||
|
|
||||||
def get_session_message_history(
|
def get_session_message_history(
|
||||||
@@ -106,7 +111,7 @@ class PromptOptimizerService:
|
|||||||
- role (str): The role of the message sender, e.g., 'system', 'user', or 'assistant'.
|
- role (str): The role of the message sender, e.g., 'system', 'user', or 'assistant'.
|
||||||
- content (str): The content of the message.
|
- content (str): The content of the message.
|
||||||
"""
|
"""
|
||||||
history = PromptOptimizerSessionRepository(self.db).get_session_history(
|
history = self.optim_repo.get_session_history(
|
||||||
session_id=session_id,
|
session_id=session_id,
|
||||||
user_id=user_id
|
user_id=user_id
|
||||||
)
|
)
|
||||||
@@ -295,4 +300,165 @@ class PromptOptimizerService:
|
|||||||
role=role,
|
role=role,
|
||||||
content=content
|
content=content
|
||||||
)
|
)
|
||||||
|
self.db.commit()
|
||||||
|
self.db.refresh(message)
|
||||||
return message
|
return message
|
||||||
|
|
||||||
|
def save_prompt(
|
||||||
|
self,
|
||||||
|
tenant_id: uuid.UUID,
|
||||||
|
session_id: uuid.UUID,
|
||||||
|
title: str,
|
||||||
|
prompt: str
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
Create and save a new prompt release for a given session.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tenant_id (uuid.UUID): The ID of the tenant owning the prompt.
|
||||||
|
session_id (uuid.UUID): The ID of the session to associate with this prompt.
|
||||||
|
title (str): The title of the prompt release.
|
||||||
|
prompt (str): The content of the prompt.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary containing:
|
||||||
|
- id (UUID): The unique ID of the created prompt release.
|
||||||
|
- session_id (UUID): The session ID linked to the release.
|
||||||
|
- title (str): The title of the prompt.
|
||||||
|
- prompt (str): The prompt content.
|
||||||
|
- created_at (int): Timestamp (in milliseconds) of when the prompt was created.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
BusinessException: If a prompt release already exists for the given session.
|
||||||
|
"""
|
||||||
|
session = self.optim_repo.get_session_by_id(session_id)
|
||||||
|
if session is None or session.tenant_id != tenant_id:
|
||||||
|
raise BusinessException(
|
||||||
|
"Session does not exist or the current user has no access",
|
||||||
|
BizCode.BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.release_repo.get_prompt_by_session_id(session_id):
|
||||||
|
raise BusinessException(
|
||||||
|
"A release already exists for the current session",
|
||||||
|
BizCode.BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
prompt_obj = self.release_repo.create_prompt_release(
|
||||||
|
tenant_id=tenant_id,
|
||||||
|
title=title,
|
||||||
|
session_id=session_id,
|
||||||
|
prompt=prompt
|
||||||
|
)
|
||||||
|
self.db.commit()
|
||||||
|
self.db.refresh(prompt_obj)
|
||||||
|
return {
|
||||||
|
"id": prompt_obj.id,
|
||||||
|
"session_id": prompt_obj.session_id,
|
||||||
|
"title": prompt_obj.title,
|
||||||
|
"prompt": prompt_obj.prompt,
|
||||||
|
"created_at": int(prompt_obj.created_at.timestamp() * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
def delete_prompt(
|
||||||
|
self,
|
||||||
|
tenant_id: uuid.UUID,
|
||||||
|
prompt_id: uuid.UUID
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Soft delete a prompt release by prompt_id.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tenant_id (uuid.UUID): Tenant identifier.
|
||||||
|
prompt_id (uuid.UUID): Prompt identifier.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
BusinessException: If the prompt does not exist or already deleted.
|
||||||
|
"""
|
||||||
|
prompt_obj = self.release_repo.get_prompt_by_id(prompt_id)
|
||||||
|
if not prompt_obj or prompt_obj.is_delete:
|
||||||
|
raise BusinessException(
|
||||||
|
"Prompt does not exist or has already been deleted",
|
||||||
|
BizCode.NOT_FOUND
|
||||||
|
)
|
||||||
|
|
||||||
|
if prompt_obj.tenant_id != tenant_id:
|
||||||
|
raise BusinessException(
|
||||||
|
"No permission to delete this prompt",
|
||||||
|
BizCode.FORBIDDEN
|
||||||
|
)
|
||||||
|
|
||||||
|
self.release_repo.soft_delete_prompt(prompt_obj)
|
||||||
|
self.db.commit()
|
||||||
|
logger.info(f"Prompt soft deleted, prompt_id={prompt_id}, tenant_id={tenant_id}")
|
||||||
|
|
||||||
|
def get_release_list(
|
||||||
|
self,
|
||||||
|
tenant_id: uuid.UUID,
|
||||||
|
page: int,
|
||||||
|
page_size: int,
|
||||||
|
filter_keyword: str | None = None
|
||||||
|
) -> dict[str, int | list[Any]]:
|
||||||
|
"""
|
||||||
|
Get paginated list of prompt releases with optional filter.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tenant_id (uuid.UUID): Tenant identifier.
|
||||||
|
page (int): Page number (starting from 1).
|
||||||
|
page_size (int): Number of items per page.
|
||||||
|
filter_keyword (str | None): Optional keyword to filter by title.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Contains total count, pagination info, and list of releases.
|
||||||
|
"""
|
||||||
|
offset = (page - 1) * page_size
|
||||||
|
|
||||||
|
# Get total count and releases based on filter
|
||||||
|
if filter_keyword:
|
||||||
|
total = self.release_repo.count_prompts_by_keyword(tenant_id, filter_keyword)
|
||||||
|
releases = self.release_repo.search_prompts_paginated(
|
||||||
|
tenant_id=tenant_id,
|
||||||
|
keyword=filter_keyword,
|
||||||
|
offset=offset,
|
||||||
|
limit=page_size
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
total = self.release_repo.count_prompts(tenant_id)
|
||||||
|
releases = self.release_repo.get_prompts_paginated(
|
||||||
|
tenant_id=tenant_id,
|
||||||
|
offset=offset,
|
||||||
|
limit=page_size
|
||||||
|
)
|
||||||
|
|
||||||
|
items = []
|
||||||
|
for release in releases:
|
||||||
|
# Get first user message from session
|
||||||
|
first_message = self.optim_repo.get_first_user_message(
|
||||||
|
session_id=release.session_id
|
||||||
|
)
|
||||||
|
|
||||||
|
items.append({
|
||||||
|
"id": release.id,
|
||||||
|
"title": release.title,
|
||||||
|
"prompt": release.prompt,
|
||||||
|
"created_at": int(release.created_at.timestamp() * 1000),
|
||||||
|
"first_message": first_message
|
||||||
|
})
|
||||||
|
|
||||||
|
log_msg = f"Retrieved {len(items)} prompt releases, page={page}, tenant_id={tenant_id}"
|
||||||
|
if filter_keyword:
|
||||||
|
log_msg += f", filter='{filter_keyword}'"
|
||||||
|
logger.info(log_msg)
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"page": {
|
||||||
|
"total": total,
|
||||||
|
"page": page,
|
||||||
|
"page_size": page_size,
|
||||||
|
"hasnext": page * page_size < total
|
||||||
|
},
|
||||||
|
"keyword": filter_keyword,
|
||||||
|
"items": items
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
|||||||
Reference in New Issue
Block a user