889 lines
33 KiB
Python
889 lines
33 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 JSON, Column
|
|
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),
|
|
)
|
|
|
|
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.
|
|
[v0.7.0] Extended with monitoring fields for Dashboard statistics.
|
|
"""
|
|
|
|
__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"),
|
|
Index("ix_chat_messages_tenant_template", "tenant_id", "prompt_template_id"),
|
|
Index("ix_chat_messages_tenant_intent", "tenant_id", "intent_rule_id"),
|
|
Index("ix_chat_messages_tenant_flow", "tenant_id", "flow_instance_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", 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")
|
|
|
|
prompt_template_id: uuid.UUID | None = Field(
|
|
default=None,
|
|
description="[v0.7.0] ID of the Prompt template used",
|
|
foreign_key="prompt_templates.id",
|
|
)
|
|
intent_rule_id: uuid.UUID | None = Field(
|
|
default=None,
|
|
description="[v0.7.0] ID of the Intent rule that matched",
|
|
foreign_key="intent_rules.id",
|
|
)
|
|
flow_instance_id: uuid.UUID | None = Field(
|
|
default=None,
|
|
description="[v0.7.0] ID of the Flow instance if flow was active",
|
|
foreign_key="flow_instances.id",
|
|
)
|
|
guardrail_triggered: bool = Field(
|
|
default=False,
|
|
description="[v0.7.0] Whether output guardrail was triggered"
|
|
)
|
|
guardrail_words: dict[str, Any] | None = Field(
|
|
default=None,
|
|
sa_column=Column("guardrail_words", JSON, nullable=True),
|
|
description="[v0.7.0] Guardrail trigger details: words, categories, strategy"
|
|
)
|
|
|
|
|
|
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_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"),
|
|
)
|
|
|
|
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'}]"
|
|
)
|
|
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"),
|
|
)
|
|
|
|
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,
|
|
}
|
|
|
|
|
|
class ForbiddenWordCategory(str, Enum):
|
|
"""[AC-AISVC-78] Forbidden word category."""
|
|
COMPETITOR = "competitor"
|
|
SENSITIVE = "sensitive"
|
|
POLITICAL = "political"
|
|
CUSTOM = "custom"
|
|
|
|
|
|
class ForbiddenWordStrategy(str, Enum):
|
|
"""[AC-AISVC-78] Forbidden word replacement strategy."""
|
|
MASK = "mask"
|
|
REPLACE = "replace"
|
|
BLOCK = "block"
|
|
|
|
|
|
class ForbiddenWord(SQLModel, table=True):
|
|
"""
|
|
[AC-AISVC-78] Forbidden word entity with tenant isolation.
|
|
Supports mask/replace/block strategies for output filtering.
|
|
"""
|
|
|
|
__tablename__ = "forbidden_words"
|
|
__table_args__ = (
|
|
Index("ix_forbidden_words_tenant_enabled", "tenant_id", "is_enabled"),
|
|
)
|
|
|
|
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
|
tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
|
|
word: str = Field(..., description="Forbidden word to detect")
|
|
category: str = Field(..., description="Category: competitor/sensitive/political/custom")
|
|
strategy: str = Field(..., description="Replacement strategy: mask/replace/block")
|
|
replacement: str | None = Field(default=None, description="Replacement text for 'replace' strategy")
|
|
fallback_reply: str | None = Field(default=None, description="Fallback reply for 'block' strategy")
|
|
is_enabled: bool = Field(default=True, description="Whether the word is enabled")
|
|
hit_count: int = Field(default=0, ge=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 ForbiddenWordCreate(SQLModel):
|
|
"""[AC-AISVC-78] Schema for creating a new forbidden word."""
|
|
|
|
word: str
|
|
category: str
|
|
strategy: str
|
|
replacement: str | None = None
|
|
fallback_reply: str | None = None
|
|
|
|
|
|
class ForbiddenWordUpdate(SQLModel):
|
|
"""[AC-AISVC-80] Schema for updating a forbidden word."""
|
|
|
|
word: str | None = None
|
|
category: str | None = None
|
|
strategy: str | None = None
|
|
replacement: str | None = None
|
|
fallback_reply: str | None = None
|
|
is_enabled: bool | None = None
|
|
|
|
|
|
class BehaviorRuleCategory(str, Enum):
|
|
"""[AC-AISVC-84] Behavior rule category."""
|
|
COMPLIANCE = "compliance"
|
|
TONE = "tone"
|
|
BOUNDARY = "boundary"
|
|
CUSTOM = "custom"
|
|
|
|
|
|
class BehaviorRule(SQLModel, table=True):
|
|
"""
|
|
[AC-AISVC-84] Behavior rule entity with tenant isolation.
|
|
These rules are injected into Prompt system instruction as LLM behavior constraints.
|
|
"""
|
|
|
|
__tablename__ = "behavior_rules"
|
|
__table_args__ = (
|
|
Index("ix_behavior_rules_tenant_enabled", "tenant_id", "is_enabled"),
|
|
)
|
|
|
|
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
|
tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
|
|
rule_text: str = Field(
|
|
...,
|
|
description="Behavior constraint description, e.g., 'Do not promise specific compensation'"
|
|
)
|
|
category: str = Field(..., description="Category: compliance/tone/boundary/custom")
|
|
is_enabled: bool = Field(default=True, description="Whether the rule is enabled")
|
|
created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time")
|
|
updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")
|
|
|
|
|
|
class BehaviorRuleCreate(SQLModel):
|
|
"""[AC-AISVC-84] Schema for creating a new behavior rule."""
|
|
|
|
rule_text: str
|
|
category: str
|
|
|
|
|
|
class BehaviorRuleUpdate(SQLModel):
|
|
"""[AC-AISVC-85] Schema for updating a behavior rule."""
|
|
|
|
rule_text: str | None = None
|
|
category: str | None = None
|
|
is_enabled: bool | None = None
|
|
|
|
|
|
class GuardrailResult:
|
|
"""
|
|
[AC-AISVC-82] Result of guardrail filtering.
|
|
Contains filtered reply and trigger information.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
reply: str,
|
|
blocked: bool = False,
|
|
triggered_words: list[str] | None = None,
|
|
triggered_categories: list[str] | None = None,
|
|
):
|
|
self.reply = reply
|
|
self.blocked = blocked
|
|
self.triggered_words = triggered_words or []
|
|
self.triggered_categories = triggered_categories or []
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"reply": self.reply,
|
|
"blocked": self.blocked,
|
|
"triggered_words": self.triggered_words,
|
|
"triggered_categories": self.triggered_categories,
|
|
"guardrail_triggered": len(self.triggered_words) > 0,
|
|
}
|
|
|
|
|
|
class InputScanResult:
|
|
"""
|
|
[AC-AISVC-83] Result of input scanning.
|
|
Contains flagged status and matched words (for logging only, no blocking).
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
flagged: bool = False,
|
|
matched_words: list[str] | None = None,
|
|
matched_categories: list[str] | None = None,
|
|
):
|
|
self.flagged = flagged
|
|
self.matched_words = matched_words or []
|
|
self.matched_categories = matched_categories or []
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"input_flagged": self.flagged,
|
|
"matched_words": self.matched_words,
|
|
"matched_categories": self.matched_categories,
|
|
}
|
|
|
|
|
|
class FlowInstanceStatus(str, Enum):
|
|
"""[AC-AISVC-74~AC-AISVC-77] Flow instance status."""
|
|
ACTIVE = "active"
|
|
COMPLETED = "completed"
|
|
TIMEOUT = "timeout"
|
|
CANCELLED = "cancelled"
|
|
|
|
|
|
class TimeoutAction(str, Enum):
|
|
"""[AC-AISVC-71] Timeout action for flow steps."""
|
|
REPEAT = "repeat"
|
|
SKIP = "skip"
|
|
TRANSFER = "transfer"
|
|
|
|
|
|
class ScriptFlow(SQLModel, table=True):
|
|
"""
|
|
[AC-AISVC-71] Script flow entity with tenant isolation.
|
|
Stores flow definition with steps in JSONB format.
|
|
"""
|
|
|
|
__tablename__ = "script_flows"
|
|
__table_args__ = (
|
|
Index("ix_script_flows_tenant_enabled", "tenant_id", "is_enabled"),
|
|
)
|
|
|
|
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="Flow name")
|
|
description: str | None = Field(default=None, description="Flow description")
|
|
steps: list[dict[str, Any]] = Field(
|
|
default=[],
|
|
sa_column=Column("steps", JSON, nullable=False),
|
|
description="Flow steps list with step_no, content, wait_input, timeout_seconds"
|
|
)
|
|
is_enabled: bool = Field(default=True, description="Whether the flow is enabled")
|
|
created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time")
|
|
updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")
|
|
|
|
|
|
class FlowInstance(SQLModel, table=True):
|
|
"""
|
|
[AC-AISVC-74] Flow instance entity for runtime state.
|
|
Tracks active flow execution per session.
|
|
"""
|
|
|
|
__tablename__ = "flow_instances"
|
|
__table_args__ = (
|
|
Index("ix_flow_instances_tenant_session", "tenant_id", "session_id"),
|
|
Index("ix_flow_instances_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)
|
|
session_id: str = Field(..., description="Session ID for conversation tracking", index=True)
|
|
flow_id: uuid.UUID = Field(
|
|
...,
|
|
description="Foreign key to script_flows.id",
|
|
foreign_key="script_flows.id",
|
|
index=True
|
|
)
|
|
current_step: int = Field(default=1, ge=1, description="Current step number (1-indexed)")
|
|
status: str = Field(
|
|
default=FlowInstanceStatus.ACTIVE.value,
|
|
description="Instance status: active/completed/timeout/cancelled"
|
|
)
|
|
context: dict[str, Any] | None = Field(
|
|
default=None,
|
|
sa_column=Column("context", JSON, nullable=True),
|
|
description="Flow execution context, stores user inputs"
|
|
)
|
|
started_at: datetime = Field(default_factory=datetime.utcnow, description="Instance start time")
|
|
updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")
|
|
completed_at: datetime | None = Field(default=None, description="Completion time (nullable)")
|
|
|
|
|
|
class FlowStep(SQLModel):
|
|
"""[AC-AISVC-71] Schema for a single flow step."""
|
|
|
|
step_no: int = Field(..., ge=1, description="Step number (1-indexed)")
|
|
content: str = Field(..., description="Script content for this step")
|
|
wait_input: bool = Field(default=True, description="Whether to wait for user input")
|
|
timeout_seconds: int = Field(default=120, ge=1, description="Timeout in seconds")
|
|
timeout_action: str = Field(
|
|
default=TimeoutAction.REPEAT.value,
|
|
description="Action on timeout: repeat/skip/transfer"
|
|
)
|
|
next_conditions: list[dict[str, Any]] | None = Field(
|
|
default=None,
|
|
description="Conditions for next step: [{'keywords': [...], 'goto_step': N}]"
|
|
)
|
|
default_next: int | None = Field(default=None, description="Default next step if no condition matches")
|
|
|
|
|
|
class ScriptFlowCreate(SQLModel):
|
|
"""[AC-AISVC-71] Schema for creating a new script flow."""
|
|
|
|
name: str
|
|
description: str | None = None
|
|
steps: list[dict[str, Any]]
|
|
is_enabled: bool = True
|
|
|
|
|
|
class ScriptFlowUpdate(SQLModel):
|
|
"""[AC-AISVC-73] Schema for updating a script flow."""
|
|
|
|
name: str | None = None
|
|
description: str | None = None
|
|
steps: list[dict[str, Any]] | None = None
|
|
is_enabled: bool | None = None
|
|
|
|
|
|
class FlowAdvanceResult:
|
|
"""
|
|
[AC-AISVC-75] Result of flow step advancement.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
completed: bool,
|
|
message: str | None = None,
|
|
current_step: int | None = None,
|
|
total_steps: int | None = None,
|
|
timeout_action: str | None = None,
|
|
):
|
|
self.completed = completed
|
|
self.message = message
|
|
self.current_step = current_step
|
|
self.total_steps = total_steps
|
|
self.timeout_action = timeout_action
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
result = {
|
|
"completed": self.completed,
|
|
}
|
|
if self.message is not None:
|
|
result["message"] = self.message
|
|
if self.current_step is not None:
|
|
result["current_step"] = self.current_step
|
|
if self.total_steps is not None:
|
|
result["total_steps"] = self.total_steps
|
|
if self.timeout_action is not None:
|
|
result["timeout_action"] = self.timeout_action
|
|
return result
|
|
|
|
|
|
class FlowTestRecordStatus(str, Enum):
|
|
"""[AC-AISVC-93] Flow test record status."""
|
|
SUCCESS = "success"
|
|
PARTIAL = "partial"
|
|
FAILED = "failed"
|
|
|
|
|
|
class FlowTestRecord(SQLModel, table=True):
|
|
"""
|
|
[AC-AISVC-93] Flow test record entity for complete 12-step execution logging.
|
|
Records are retained for 7 days (TTL cleanup via background task).
|
|
"""
|
|
|
|
__tablename__ = "flow_test_records"
|
|
__table_args__ = (
|
|
Index("ix_flow_test_records_tenant_created", "tenant_id", "created_at"),
|
|
Index("ix_flow_test_records_session", "session_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 test session")
|
|
status: str = Field(
|
|
default=FlowTestRecordStatus.SUCCESS.value,
|
|
description="Overall status: success/partial/failed"
|
|
)
|
|
steps: list[dict[str, Any]] = Field(
|
|
default=[],
|
|
sa_column=Column("steps", JSON, nullable=False),
|
|
description="12-step execution logs with step, name, status, duration_ms, input, output, error, metadata"
|
|
)
|
|
final_response: dict[str, Any] | None = Field(
|
|
default=None,
|
|
sa_column=Column("final_response", JSON, nullable=True),
|
|
description="Final ChatResponse with reply, confidence, should_transfer"
|
|
)
|
|
total_duration_ms: int | None = Field(default=None, description="Total execution time in milliseconds")
|
|
created_at: datetime = Field(default_factory=datetime.utcnow, description="Record creation time", index=True)
|
|
|
|
|
|
class FlowTestStepResult(SQLModel):
|
|
"""[AC-AISVC-93] Schema for a single step result in flow test."""
|
|
|
|
step: int = Field(..., description="Step number (1-12)")
|
|
name: str = Field(..., description="Step name")
|
|
status: str = Field(..., description="Execution status: success/failed/skipped")
|
|
duration_ms: int | None = Field(default=None, description="Execution time in milliseconds")
|
|
input: dict[str, Any] | None = Field(default=None, description="Step input data")
|
|
output: dict[str, Any] | None = Field(default=None, description="Step output data")
|
|
error: str | None = Field(default=None, description="Error message if failed")
|
|
step_metadata: dict[str, Any] | None = Field(default=None, description="Step metadata (matched rule, template, etc.)")
|
|
|
|
|
|
class ExportTaskStatus(str, Enum):
|
|
"""[AC-AISVC-110] Export task status."""
|
|
PROCESSING = "processing"
|
|
COMPLETED = "completed"
|
|
FAILED = "failed"
|
|
|
|
|
|
class ExportTask(SQLModel, table=True):
|
|
"""
|
|
[AC-AISVC-110] Export task entity for conversation export.
|
|
Supports async export with file download.
|
|
"""
|
|
|
|
__tablename__ = "export_tasks"
|
|
__table_args__ = (
|
|
Index("ix_export_tasks_tenant_status", "tenant_id", "status"),
|
|
Index("ix_export_tasks_tenant_created", "tenant_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)
|
|
status: str = Field(
|
|
default=ExportTaskStatus.PROCESSING.value,
|
|
description="Task status: processing/completed/failed"
|
|
)
|
|
file_path: str | None = Field(default=None, description="Path to exported file")
|
|
file_name: str | None = Field(default=None, description="Generated file name")
|
|
file_size: int | None = Field(default=None, description="File size in bytes")
|
|
total_rows: int | None = Field(default=None, description="Total rows exported")
|
|
format: str = Field(default="json", description="Export format: json/csv")
|
|
filters: dict[str, Any] | None = Field(
|
|
default=None,
|
|
sa_column=Column("filters", JSON, nullable=True),
|
|
description="Export filters applied"
|
|
)
|
|
error_message: str | None = Field(default=None, description="Error message if failed")
|
|
expires_at: datetime | None = Field(default=None, description="File expiration time (for cleanup)")
|
|
created_at: datetime = Field(default_factory=datetime.utcnow, description="Task creation time")
|
|
completed_at: datetime | None = Field(default=None, description="Completion time")
|
|
|
|
|
|
class ExportTaskCreate(SQLModel):
|
|
"""[AC-AISVC-110] Schema for creating an export task."""
|
|
|
|
format: str = "json"
|
|
filters: dict[str, Any] | None = None
|
|
|
|
|
|
class ConversationDetail(SQLModel):
|
|
"""[AC-AISVC-109] Schema for conversation detail with execution chain."""
|
|
|
|
conversation_id: uuid.UUID
|
|
session_id: str
|
|
user_message: str
|
|
ai_reply: str | None = None
|
|
triggered_rules: list[dict[str, Any]] | None = None
|
|
used_template: dict[str, Any] | None = None
|
|
used_flow: dict[str, Any] | None = None
|
|
execution_time_ms: int | None = None
|
|
confidence: float | None = None
|
|
should_transfer: bool = False
|
|
execution_steps: list[dict[str, Any]] | None = None
|
|
created_at: datetime
|