428 lines
16 KiB
Python
428 lines
16 KiB
Python
"""
|
|
Memory layer entities for AI Service.
|
|
[AC-AISVC-13] SQLModel entities for chat sessions and messages with tenant isolation.
|
|
"""
|
|
|
|
import uuid
|
|
from datetime import datetime
|
|
from enum import Enum
|
|
from typing import Any
|
|
|
|
from sqlalchemy import Column, JSON
|
|
from sqlmodel import Field, Index, SQLModel
|
|
|
|
|
|
class ChatSession(SQLModel, table=True):
|
|
"""
|
|
[AC-AISVC-13] Chat session entity with tenant isolation.
|
|
Primary key: (tenant_id, session_id) composite unique constraint.
|
|
"""
|
|
|
|
__tablename__ = "chat_sessions"
|
|
__table_args__ = (
|
|
Index("ix_chat_sessions_tenant_session", "tenant_id", "session_id", unique=True),
|
|
Index("ix_chat_sessions_tenant_id", "tenant_id"),
|
|
)
|
|
|
|
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
|
tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
|
|
session_id: str = Field(..., description="Session ID for conversation tracking")
|
|
channel_type: str | None = Field(default=None, description="Channel type: wechat, douyin, jd")
|
|
metadata_: dict[str, Any] | None = Field(
|
|
default=None,
|
|
sa_column=Column("metadata", JSON, nullable=True),
|
|
description="Session metadata"
|
|
)
|
|
created_at: datetime = Field(default_factory=datetime.utcnow, description="Session creation time")
|
|
updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")
|
|
|
|
|
|
class ChatMessage(SQLModel, table=True):
|
|
"""
|
|
[AC-AISVC-13] Chat message entity with tenant isolation.
|
|
Messages are scoped by (tenant_id, session_id) for multi-tenant security.
|
|
"""
|
|
|
|
__tablename__ = "chat_messages"
|
|
__table_args__ = (
|
|
Index("ix_chat_messages_tenant_session", "tenant_id", "session_id"),
|
|
Index("ix_chat_messages_tenant_session_created", "tenant_id", "session_id", "created_at"),
|
|
)
|
|
|
|
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
|
tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
|
|
session_id: str = Field(..., description="Session ID for conversation tracking", index=True)
|
|
role: str = Field(..., description="Message role: user or assistant")
|
|
content: str = Field(..., description="Message content")
|
|
prompt_tokens: int | None = Field(default=None, description="Number of prompt tokens used")
|
|
completion_tokens: int | None = Field(default=None, description="Number of completion tokens used")
|
|
total_tokens: int | None = Field(default=None, description="Total tokens used")
|
|
latency_ms: int | None = Field(default=None, description="Response latency in milliseconds")
|
|
first_token_ms: int | None = Field(default=None, description="Time to first token in milliseconds (for streaming)")
|
|
is_error: bool = Field(default=False, description="Whether this message is an error response")
|
|
error_message: str | None = Field(default=None, description="Error message if any")
|
|
created_at: datetime = Field(default_factory=datetime.utcnow, description="Message creation time")
|
|
|
|
|
|
class ChatSessionCreate(SQLModel):
|
|
"""Schema for creating a new chat session."""
|
|
|
|
tenant_id: str
|
|
session_id: str
|
|
channel_type: str | None = None
|
|
metadata_: dict[str, Any] | None = None
|
|
|
|
|
|
class ChatMessageCreate(SQLModel):
|
|
"""Schema for creating a new chat message."""
|
|
|
|
tenant_id: str
|
|
session_id: str
|
|
role: str
|
|
content: str
|
|
|
|
|
|
class DocumentStatus(str, Enum):
|
|
PENDING = "pending"
|
|
PROCESSING = "processing"
|
|
COMPLETED = "completed"
|
|
FAILED = "failed"
|
|
|
|
|
|
class IndexJobStatus(str, Enum):
|
|
PENDING = "pending"
|
|
PROCESSING = "processing"
|
|
COMPLETED = "completed"
|
|
FAILED = "failed"
|
|
|
|
|
|
class SessionStatus(str, Enum):
|
|
ACTIVE = "active"
|
|
CLOSED = "closed"
|
|
EXPIRED = "expired"
|
|
|
|
|
|
class Tenant(SQLModel, table=True):
|
|
"""
|
|
[AC-AISVC-10] Tenant entity for storing tenant information.
|
|
Tenant ID format: name@ash@year (e.g., szmp@ash@2026)
|
|
"""
|
|
|
|
__tablename__ = "tenants"
|
|
|
|
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
|
tenant_id: str = Field(..., description="Full tenant ID (format: name@ash@year)", unique=True, index=True)
|
|
name: str = Field(..., description="Tenant display name (first part of tenant_id)")
|
|
year: str = Field(..., description="Year part from tenant_id")
|
|
created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time")
|
|
updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")
|
|
|
|
|
|
class KBType(str, Enum):
|
|
PRODUCT = "product"
|
|
FAQ = "faq"
|
|
SCRIPT = "script"
|
|
POLICY = "policy"
|
|
GENERAL = "general"
|
|
|
|
|
|
class KnowledgeBase(SQLModel, table=True):
|
|
"""
|
|
[AC-ASA-01, AC-AISVC-59] Knowledge base entity with tenant isolation.
|
|
[v0.6.0] Extended with kb_type, priority, is_enabled, doc_count for multi-KB management.
|
|
"""
|
|
|
|
__tablename__ = "knowledge_bases"
|
|
__table_args__ = (
|
|
Index("ix_knowledge_bases_tenant_id", "tenant_id"),
|
|
Index("ix_knowledge_bases_tenant_kb_type", "tenant_id", "kb_type"),
|
|
)
|
|
|
|
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
|
tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
|
|
name: str = Field(..., description="Knowledge base name")
|
|
kb_type: str = Field(default=KBType.GENERAL.value, description="Knowledge base type: product/faq/script/policy/general")
|
|
description: str | None = Field(default=None, description="Knowledge base description")
|
|
priority: int = Field(default=0, ge=0, description="Priority weight, higher value means higher priority")
|
|
is_enabled: bool = Field(default=True, description="Whether the knowledge base is enabled")
|
|
doc_count: int = Field(default=0, ge=0, description="Document count (cached statistic)")
|
|
created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time")
|
|
updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")
|
|
|
|
|
|
class Document(SQLModel, table=True):
|
|
"""
|
|
[AC-ASA-01, AC-ASA-08] Document entity with tenant isolation.
|
|
"""
|
|
|
|
__tablename__ = "documents"
|
|
__table_args__ = (
|
|
Index("ix_documents_tenant_kb", "tenant_id", "kb_id"),
|
|
Index("ix_documents_tenant_status", "tenant_id", "status"),
|
|
)
|
|
|
|
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
|
tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
|
|
kb_id: str = Field(..., description="Knowledge base ID")
|
|
file_name: str = Field(..., description="Original file name")
|
|
file_path: str | None = Field(default=None, description="Storage path")
|
|
file_size: int | None = Field(default=None, description="File size in bytes")
|
|
file_type: str | None = Field(default=None, description="File MIME type")
|
|
status: str = Field(default=DocumentStatus.PENDING.value, description="Document status")
|
|
error_msg: str | None = Field(default=None, description="Error message if failed")
|
|
created_at: datetime = Field(default_factory=datetime.utcnow, description="Upload time")
|
|
updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")
|
|
|
|
|
|
class IndexJob(SQLModel, table=True):
|
|
"""
|
|
[AC-ASA-02] Index job entity for tracking document indexing progress.
|
|
"""
|
|
|
|
__tablename__ = "index_jobs"
|
|
__table_args__ = (
|
|
Index("ix_index_jobs_tenant_doc", "tenant_id", "doc_id"),
|
|
Index("ix_index_jobs_tenant_status", "tenant_id", "status"),
|
|
)
|
|
|
|
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
|
tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
|
|
doc_id: uuid.UUID = Field(..., description="Document ID being indexed")
|
|
status: str = Field(default=IndexJobStatus.PENDING.value, description="Job status")
|
|
progress: int = Field(default=0, ge=0, le=100, description="Progress percentage")
|
|
error_msg: str | None = Field(default=None, description="Error message if failed")
|
|
created_at: datetime = Field(default_factory=datetime.utcnow, description="Job creation time")
|
|
updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")
|
|
|
|
|
|
class KnowledgeBaseCreate(SQLModel):
|
|
"""Schema for creating a new knowledge base."""
|
|
|
|
name: str
|
|
kb_type: str = KBType.GENERAL.value
|
|
description: str | None = None
|
|
priority: int = 0
|
|
|
|
|
|
class KnowledgeBaseUpdate(SQLModel):
|
|
"""Schema for updating a knowledge base."""
|
|
|
|
name: str | None = None
|
|
kb_type: str | None = None
|
|
description: str | None = None
|
|
priority: int | None = None
|
|
is_enabled: bool | None = None
|
|
|
|
|
|
class DocumentCreate(SQLModel):
|
|
"""Schema for creating a new document."""
|
|
|
|
tenant_id: str
|
|
kb_id: str
|
|
file_name: str
|
|
file_path: str | None = None
|
|
file_size: int | None = None
|
|
file_type: str | None = None
|
|
|
|
|
|
class ApiKey(SQLModel, table=True):
|
|
"""
|
|
[AC-AISVC-50] API Key entity for lightweight authentication.
|
|
Keys are loaded into memory on startup for fast validation.
|
|
"""
|
|
|
|
__tablename__ = "api_keys"
|
|
|
|
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
|
key: str = Field(..., description="API Key (unique)", unique=True, index=True)
|
|
name: str = Field(..., description="Key name/description for identification")
|
|
is_active: bool = Field(default=True, description="Whether the key is active")
|
|
created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time")
|
|
updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")
|
|
|
|
|
|
class ApiKeyCreate(SQLModel):
|
|
"""Schema for creating a new API key."""
|
|
|
|
key: str
|
|
name: str
|
|
is_active: bool = True
|
|
|
|
|
|
class TemplateVersionStatus(str, Enum):
|
|
DRAFT = "draft"
|
|
PUBLISHED = "published"
|
|
ARCHIVED = "archived"
|
|
|
|
|
|
class PromptTemplate(SQLModel, table=True):
|
|
"""
|
|
[AC-AISVC-51, AC-AISVC-52] Prompt template entity with tenant isolation.
|
|
Main table for storing template metadata.
|
|
"""
|
|
|
|
__tablename__ = "prompt_templates"
|
|
__table_args__ = (
|
|
Index("ix_prompt_templates_tenant_scene", "tenant_id", "scene"),
|
|
Index("ix_prompt_templates_tenant_id", "tenant_id"),
|
|
)
|
|
|
|
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
|
tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
|
|
name: str = Field(..., description="Template name, e.g., 'Default Customer Service Persona'")
|
|
scene: str = Field(..., description="Scene tag: chat/rag_qa/greeting/farewell")
|
|
description: str | None = Field(default=None, description="Template description")
|
|
is_default: bool = Field(default=False, description="Whether this is the default template for the scene")
|
|
created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time")
|
|
updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")
|
|
|
|
|
|
class PromptTemplateVersion(SQLModel, table=True):
|
|
"""
|
|
[AC-AISVC-53] Prompt template version entity.
|
|
Stores versioned content with status management.
|
|
"""
|
|
|
|
__tablename__ = "prompt_template_versions"
|
|
__table_args__ = (
|
|
Index("ix_template_versions_template_status", "template_id", "status"),
|
|
)
|
|
|
|
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
|
template_id: uuid.UUID = Field(..., description="Foreign key to prompt_templates.id", foreign_key="prompt_templates.id", index=True)
|
|
version: int = Field(..., description="Version number (auto-incremented per template)")
|
|
status: str = Field(default=TemplateVersionStatus.DRAFT.value, description="Version status: draft/published/archived")
|
|
system_instruction: str = Field(..., description="System instruction content with {{variable}} placeholders")
|
|
variables: list[dict[str, Any]] | None = Field(
|
|
default=None,
|
|
sa_column=Column("variables", JSON, nullable=True),
|
|
description="Variable definitions, e.g., [{'name': 'persona_name', 'default': '小N', 'description': '人设名称'}]"
|
|
)
|
|
created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time")
|
|
|
|
|
|
class PromptTemplateCreate(SQLModel):
|
|
"""Schema for creating a new prompt template."""
|
|
|
|
name: str
|
|
scene: str
|
|
description: str | None = None
|
|
system_instruction: str
|
|
variables: list[dict[str, Any]] | None = None
|
|
is_default: bool = False
|
|
|
|
|
|
class PromptTemplateUpdate(SQLModel):
|
|
"""Schema for updating a prompt template."""
|
|
|
|
name: str | None = None
|
|
scene: str | None = None
|
|
description: str | None = None
|
|
system_instruction: str | None = None
|
|
variables: list[dict[str, Any]] | None = None
|
|
is_default: bool | None = None
|
|
|
|
|
|
class ResponseType(str, Enum):
|
|
"""[AC-AISVC-65] Response type for intent rules."""
|
|
FIXED = "fixed"
|
|
RAG = "rag"
|
|
FLOW = "flow"
|
|
TRANSFER = "transfer"
|
|
|
|
|
|
class IntentRule(SQLModel, table=True):
|
|
"""
|
|
[AC-AISVC-65] Intent rule entity with tenant isolation.
|
|
Supports keyword and regex matching for intent recognition.
|
|
"""
|
|
|
|
__tablename__ = "intent_rules"
|
|
__table_args__ = (
|
|
Index("ix_intent_rules_tenant_enabled_priority", "tenant_id", "is_enabled"),
|
|
Index("ix_intent_rules_tenant_id", "tenant_id"),
|
|
)
|
|
|
|
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
|
tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
|
|
name: str = Field(..., description="Intent name, e.g., 'Return Intent'")
|
|
keywords: list[str] | None = Field(
|
|
default=None,
|
|
sa_column=Column("keywords", JSON, nullable=True),
|
|
description="Keyword list for matching, e.g., ['return', 'refund']"
|
|
)
|
|
patterns: list[str] | None = Field(
|
|
default=None,
|
|
sa_column=Column("patterns", JSON, nullable=True),
|
|
description="Regex pattern list for matching, e.g., ['return.*goods', 'how to return']"
|
|
)
|
|
priority: int = Field(default=0, description="Priority (higher value = higher priority)")
|
|
response_type: str = Field(..., description="Response type: fixed/rag/flow/transfer")
|
|
target_kb_ids: list[str] | None = Field(
|
|
default=None,
|
|
sa_column=Column("target_kb_ids", JSON, nullable=True),
|
|
description="Target knowledge base IDs for rag type"
|
|
)
|
|
flow_id: uuid.UUID | None = Field(default=None, description="Flow ID for flow type")
|
|
fixed_reply: str | None = Field(default=None, description="Fixed reply content for fixed type")
|
|
transfer_message: str | None = Field(default=None, description="Transfer message for transfer type")
|
|
is_enabled: bool = Field(default=True, description="Whether the rule is enabled")
|
|
hit_count: int = Field(default=0, description="Hit count for statistics")
|
|
created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time")
|
|
updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")
|
|
|
|
|
|
class IntentRuleCreate(SQLModel):
|
|
"""[AC-AISVC-65] Schema for creating a new intent rule."""
|
|
|
|
name: str
|
|
keywords: list[str] | None = None
|
|
patterns: list[str] | None = None
|
|
priority: int = 0
|
|
response_type: str
|
|
target_kb_ids: list[str] | None = None
|
|
flow_id: str | None = None
|
|
fixed_reply: str | None = None
|
|
transfer_message: str | None = None
|
|
|
|
|
|
class IntentRuleUpdate(SQLModel):
|
|
"""[AC-AISVC-67] Schema for updating an intent rule."""
|
|
|
|
name: str | None = None
|
|
keywords: list[str] | None = None
|
|
patterns: list[str] | None = None
|
|
priority: int | None = None
|
|
response_type: str | None = None
|
|
target_kb_ids: list[str] | None = None
|
|
flow_id: str | None = None
|
|
fixed_reply: str | None = None
|
|
transfer_message: str | None = None
|
|
is_enabled: bool | None = None
|
|
|
|
|
|
class IntentMatchResult:
|
|
"""
|
|
[AC-AISVC-69] Result of intent matching.
|
|
Contains the matched rule and match details.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
rule: IntentRule,
|
|
match_type: str,
|
|
matched: str,
|
|
):
|
|
self.rule = rule
|
|
self.match_type = match_type
|
|
self.matched = matched
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"rule_id": str(self.rule.id),
|
|
"rule_name": self.rule.name,
|
|
"match_type": self.match_type,
|
|
"matched": self.matched,
|
|
"response_type": self.rule.response_type,
|
|
}
|