1816 lines
68 KiB
Python
1816 lines
68 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.
|
||
[v0.8.0] Extended with route_trace for hybrid routing observability.
|
||
"""
|
||
|
||
__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"
|
||
)
|
||
route_trace: dict[str, Any] | None = Field(
|
||
default=None,
|
||
sa_column=Column("route_trace", JSON, nullable=True),
|
||
description="[v0.8.0] Intent routing trace log for hybrid routing observability"
|
||
)
|
||
|
||
|
||
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 UserMemory(SQLModel, table=True):
|
||
"""
|
||
[AC-IDMP-14] 用户级记忆存储(滚动摘要)
|
||
支持多租户隔离,存储最新 summary + facts/preferences/open_issues
|
||
"""
|
||
|
||
__tablename__ = "user_memories"
|
||
__table_args__ = (
|
||
Index("ix_user_memories_tenant_user", "tenant_id", "user_id"),
|
||
Index("ix_user_memories_tenant_user_updated", "tenant_id", "user_id", "updated_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)
|
||
user_id: str = Field(..., description="User ID for memory storage", index=True)
|
||
summary: str | None = Field(default=None, description="Rolling summary for user")
|
||
facts: list[str] | None = Field(
|
||
default=None,
|
||
sa_column=Column("facts", JSON, nullable=True),
|
||
description="Extracted stable facts list",
|
||
)
|
||
preferences: dict[str, Any] | None = Field(
|
||
default=None,
|
||
sa_column=Column("preferences", JSON, nullable=True),
|
||
description="User preferences as structured JSON",
|
||
)
|
||
open_issues: list[str] | None = Field(
|
||
default=None,
|
||
sa_column=Column("open_issues", JSON, nullable=True),
|
||
description="Open issues list",
|
||
)
|
||
summary_version: int = Field(default=1, description="Summary version / update round")
|
||
last_turn_id: str | None = Field(default=None, description="Last turn identifier (optional)")
|
||
expires_at: datetime | None = Field(default=None, description="Expiration time (optional)")
|
||
created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time")
|
||
updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")
|
||
|
||
|
||
class SharedSession(SQLModel, table=True):
|
||
"""
|
||
[AC-IDMP-SHARE] Shared session entity for dialogue sharing.
|
||
Allows sharing chat sessions via unique token with expiration and concurrent user limits.
|
||
"""
|
||
|
||
__tablename__ = "shared_sessions"
|
||
__table_args__ = (
|
||
Index("ix_shared_sessions_share_token", "share_token", unique=True),
|
||
Index("ix_shared_sessions_tenant_session", "tenant_id", "session_id"),
|
||
Index("ix_shared_sessions_expires_at", "expires_at"),
|
||
)
|
||
|
||
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
||
share_token: str = Field(..., description="Unique share token (UUID)", index=True)
|
||
session_id: str = Field(..., description="Associated session ID", index=True)
|
||
tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
|
||
title: str | None = Field(default=None, description="Share title")
|
||
description: str | None = Field(default=None, description="Share description")
|
||
expires_at: datetime = Field(..., description="Expiration time")
|
||
is_active: bool = Field(default=True, description="Whether share is active")
|
||
max_concurrent_users: int = Field(default=10, description="Maximum concurrent users allowed")
|
||
current_users: int = Field(default=0, description="Current number of online users")
|
||
created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time")
|
||
updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")
|
||
|
||
|
||
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")
|
||
doc_metadata: dict | None = Field(default=None, sa_type=JSON, description="Document metadata as JSON")
|
||
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")
|
||
expires_at: datetime | None = Field(default=None, description="Expiration time; null means never expires")
|
||
allowed_ips: list[str] | None = Field(
|
||
default=None,
|
||
sa_column=Column("allowed_ips", JSON, nullable=True),
|
||
description="Optional IP allowlist for this key",
|
||
)
|
||
rate_limit_qpm: int | None = Field(default=60, description="Per-minute quota for this key")
|
||
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
|
||
expires_at: datetime | None = None
|
||
allowed_ips: list[str] | None = None
|
||
rate_limit_qpm: int | None = 60
|
||
|
||
|
||
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.
|
||
[AC-IDSMETA-16] Extended with metadata field for unified storage structure.
|
||
"""
|
||
|
||
__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")
|
||
metadata_: dict[str, Any] | None = Field(
|
||
default=None,
|
||
sa_column=Column("metadata", JSON, nullable=True),
|
||
description="[AC-IDSMETA-16] Structured metadata for the prompt template"
|
||
)
|
||
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
|
||
metadata_: dict[str, Any] | None = None
|
||
|
||
|
||
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
|
||
metadata_: dict[str, Any] | 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.
|
||
[AC-IDSMETA-16] Extended with metadata field for unified storage structure.
|
||
[v0.8.0] Extended with intent_vector and semantic_examples for hybrid routing.
|
||
"""
|
||
|
||
__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")
|
||
metadata_: dict[str, Any] | None = Field(
|
||
default=None,
|
||
sa_column=Column("metadata", JSON, nullable=True),
|
||
description="[AC-IDSMETA-16] Structured metadata for the intent rule"
|
||
)
|
||
intent_vector: list[float] | None = Field(
|
||
default=None,
|
||
sa_column=Column("intent_vector", JSON, nullable=True),
|
||
description="[v0.8.0] Pre-computed intent vector for semantic matching"
|
||
)
|
||
semantic_examples: list[str] | None = Field(
|
||
default=None,
|
||
sa_column=Column("semantic_examples", JSON, nullable=True),
|
||
description="[v0.8.0] Semantic example sentences for dynamic vector computation"
|
||
)
|
||
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
|
||
metadata_: dict[str, Any] | None = None
|
||
intent_vector: list[float] | None = None
|
||
semantic_examples: list[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
|
||
metadata_: dict[str, Any] | None = None
|
||
intent_vector: list[float] | None = None
|
||
semantic_examples: list[str] | 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.
|
||
[AC-IDSMETA-16] Extended with metadata field for unified storage structure.
|
||
"""
|
||
|
||
__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")
|
||
metadata_: dict[str, Any] | None = Field(
|
||
default=None,
|
||
sa_column=Column("metadata", JSON, nullable=True),
|
||
description="[AC-IDSMETA-16] Structured metadata for the script flow"
|
||
)
|
||
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 ScriptMode(str, Enum):
|
||
"""[AC-IDS-01] Script generation mode for flow steps."""
|
||
FIXED = "fixed"
|
||
FLEXIBLE = "flexible"
|
||
TEMPLATE = "template"
|
||
|
||
|
||
class FlowStep(SQLModel):
|
||
"""
|
||
[AC-AISVC-71] Schema for a single flow step.
|
||
[AC-IDS-01] Extended with intent-driven script generation fields.
|
||
"""
|
||
|
||
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")
|
||
|
||
script_mode: str = Field(
|
||
default=ScriptMode.FIXED.value,
|
||
description="[AC-IDS-01] Script mode: fixed/flexible/template"
|
||
)
|
||
intent: str | None = Field(
|
||
default=None,
|
||
description="[AC-IDS-01] Step intent for flexible mode (e.g., '获取用户姓名')"
|
||
)
|
||
intent_description: str | None = Field(
|
||
default=None,
|
||
description="[AC-IDS-01] Detailed intent description for better AI understanding"
|
||
)
|
||
script_constraints: list[str] | None = Field(
|
||
default=None,
|
||
description="[AC-IDS-01] Script constraints for flexible mode (e.g., ['必须礼貌', '语气自然'])"
|
||
)
|
||
expected_variables: list[str] | None = Field(
|
||
default=None,
|
||
description="[AC-IDS-01] Expected variables to extract from user input"
|
||
)
|
||
rag_config: dict[str, Any] | None = Field(
|
||
default=None,
|
||
description="RAG configuration for this step: {'enabled': true, 'tag_filter': {'grade': '${context.grade}', 'type': '痛点'}}"
|
||
)
|
||
allowed_kb_ids: list[str] | None = Field(
|
||
default=None,
|
||
description="[Step-KB-Binding] Allowed knowledge base IDs for this step. If set, KB search will be restricted to these KBs."
|
||
)
|
||
preferred_kb_ids: list[str] | None = Field(
|
||
default=None,
|
||
description="[Step-KB-Binding] Preferred knowledge base IDs for this step. These KBs will be searched first."
|
||
)
|
||
kb_query_hint: str | None = Field(
|
||
default=None,
|
||
description="[Step-KB-Binding] Query hint for KB search in this step, helps improve retrieval accuracy."
|
||
)
|
||
max_kb_calls_per_step: int | None = Field(
|
||
default=None,
|
||
ge=1,
|
||
le=5,
|
||
description="[Step-KB-Binding] Max KB calls allowed per step. Default is 1 if not set."
|
||
)
|
||
|
||
|
||
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
|
||
metadata_: dict[str, Any] | None = None
|
||
|
||
|
||
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
|
||
metadata_: dict[str, Any] | 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
|
||
|
||
|
||
class MetadataFieldType(str, Enum):
|
||
"""元数据字段类型"""
|
||
STRING = "string"
|
||
NUMBER = "number"
|
||
BOOLEAN = "boolean"
|
||
ENUM = "enum"
|
||
ARRAY_ENUM = "array_enum"
|
||
|
||
|
||
class FieldRole(str, Enum):
|
||
"""
|
||
[AC-MRS-01] 字段角色枚举
|
||
用于标识元数据字段的职责分层
|
||
"""
|
||
RESOURCE_FILTER = "resource_filter"
|
||
SLOT = "slot"
|
||
PROMPT_VAR = "prompt_var"
|
||
ROUTING_SIGNAL = "routing_signal"
|
||
|
||
|
||
class MetadataFieldStatus(str, Enum):
|
||
"""[AC-IDSMETA-13] 元数据字段状态"""
|
||
DRAFT = "draft"
|
||
ACTIVE = "active"
|
||
DEPRECATED = "deprecated"
|
||
|
||
|
||
class MetadataScope(str, Enum):
|
||
"""[AC-IDSMETA-15] 元数据字段适用范围"""
|
||
KB_DOCUMENT = "kb_document"
|
||
INTENT_RULE = "intent_rule"
|
||
SCRIPT_FLOW = "script_flow"
|
||
PROMPT_TEMPLATE = "prompt_template"
|
||
|
||
|
||
class MetadataField(SQLModel):
|
||
"""元数据字段定义(非持久化,用于嵌套结构)"""
|
||
name: str = Field(..., description="字段名称,如 grade, subject, industry")
|
||
label: str = Field(..., description="字段显示名称,如 年级, 学科, 行业")
|
||
field_type: str = Field(
|
||
default=MetadataFieldType.STRING.value,
|
||
description="字段类型: string/number/boolean/enum/array_enum"
|
||
)
|
||
options: list[str] | None = Field(
|
||
default=None,
|
||
description="选项列表,用于 enum/array_enum 类型,如 ['初一', '初二', '初三']"
|
||
)
|
||
required: bool = Field(default=False, description="是否必填")
|
||
default_value: str | None = Field(default=None, description="默认值")
|
||
description: str | None = Field(default=None, description="字段描述")
|
||
sort_order: int = Field(default=0, description="排序顺序")
|
||
|
||
|
||
class MetadataFieldDefinition(SQLModel, table=True):
|
||
"""
|
||
[AC-IDSMETA-13] 元数据字段定义表
|
||
[AC-MRS-01,02,03] 支持字段角色分层配置
|
||
每个字段独立存储,支持字段级状态管理(draft/active/deprecated)
|
||
"""
|
||
|
||
__tablename__ = "metadata_field_definitions"
|
||
__table_args__ = (
|
||
Index("ix_metadata_field_definitions_tenant", "tenant_id"),
|
||
Index("ix_metadata_field_definitions_tenant_status", "tenant_id", "status"),
|
||
Index("ix_metadata_field_definitions_tenant_field_key", "tenant_id", "field_key", 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)
|
||
field_key: str = Field(
|
||
...,
|
||
description="字段键名,仅允许小写字母数字下划线,如 grade, subject, industry",
|
||
min_length=1,
|
||
max_length=64,
|
||
)
|
||
label: str = Field(..., description="字段显示名称", min_length=1, max_length=64)
|
||
type: str = Field(
|
||
default=MetadataFieldType.STRING.value,
|
||
description="字段类型: string/number/boolean/enum/array_enum"
|
||
)
|
||
required: bool = Field(default=False, description="是否必填")
|
||
options: list[str] | None = Field(
|
||
default=None,
|
||
sa_column=Column("options", JSON, nullable=True),
|
||
description="选项列表,用于 enum/array_enum 类型"
|
||
)
|
||
default_value: str | None = Field(default=None, description="默认值", sa_column=Column("default_value", JSON, nullable=True))
|
||
scope: list[str] = Field(
|
||
default_factory=lambda: [MetadataScope.KB_DOCUMENT.value],
|
||
sa_column=Column("scope", JSON, nullable=False),
|
||
description="适用范围: kb_document/intent_rule/script_flow/prompt_template"
|
||
)
|
||
is_filterable: bool = Field(default=True, description="是否可用于过滤")
|
||
is_rank_feature: bool = Field(default=False, description="是否用于排序特征")
|
||
usage_description: str | None = Field(default=None, description="用途说明")
|
||
field_roles: list[str] = Field(
|
||
default_factory=list,
|
||
sa_column=Column("field_roles", JSON, nullable=False, server_default="'[]'"),
|
||
description="[AC-MRS-01] 字段角色列表: resource_filter/slot/prompt_var/routing_signal"
|
||
)
|
||
status: str = Field(
|
||
default=MetadataFieldStatus.DRAFT.value,
|
||
description="字段状态: draft/active/deprecated"
|
||
)
|
||
version: int = Field(default=1, description="版本号")
|
||
created_at: datetime = Field(default_factory=datetime.utcnow, description="创建时间")
|
||
updated_at: datetime = Field(default_factory=datetime.utcnow, description="更新时间")
|
||
|
||
|
||
class MetadataFieldDefinitionCreate(SQLModel):
|
||
"""[AC-IDSMETA-13] [AC-MRS-01] 创建元数据字段定义"""
|
||
|
||
field_key: str = Field(..., min_length=1, max_length=64)
|
||
label: str = Field(..., min_length=1, max_length=64)
|
||
type: str = Field(default=MetadataFieldType.STRING.value)
|
||
required: bool = Field(default=False)
|
||
options: list[str] | None = None
|
||
default_value: str | int | float | bool | None = None
|
||
scope: list[str] = Field(default_factory=lambda: [MetadataScope.KB_DOCUMENT.value])
|
||
is_filterable: bool = Field(default=True)
|
||
is_rank_feature: bool = Field(default=False)
|
||
usage_description: str | None = None
|
||
field_roles: list[str] = Field(default_factory=list)
|
||
status: str = Field(default=MetadataFieldStatus.DRAFT.value)
|
||
|
||
|
||
class MetadataFieldDefinitionUpdate(SQLModel):
|
||
"""[AC-IDSMETA-14] [AC-MRS-01] 更新元数据字段定义"""
|
||
|
||
label: str | None = Field(default=None, min_length=1, max_length=64)
|
||
required: bool | None = None
|
||
options: list[str] | None = None
|
||
default_value: str | int | float | bool | None = None
|
||
scope: list[str] | None = None
|
||
is_filterable: bool | None = None
|
||
is_rank_feature: bool | None = None
|
||
usage_description: str | None = None
|
||
field_roles: list[str] | None = None
|
||
status: str | None = None
|
||
|
||
|
||
class ExtractStrategy(str, Enum):
|
||
"""
|
||
[AC-MRS-07] 槽位值提取策略
|
||
"""
|
||
RULE = "rule"
|
||
LLM = "llm"
|
||
USER_INPUT = "user_input"
|
||
|
||
|
||
class ExtractFailureType(str, Enum):
|
||
"""
|
||
[AC-MRS-07-UPGRADE] 提取失败类型
|
||
统一失败分类,用于追踪和日志
|
||
"""
|
||
EXTRACT_EMPTY = "EXTRACT_EMPTY" # 提取结果为空
|
||
EXTRACT_PARSE_FAIL = "EXTRACT_PARSE_FAIL" # 解析失败
|
||
EXTRACT_VALIDATION_FAIL = "EXTRACT_VALIDATION_FAIL" # 校验失败
|
||
EXTRACT_RUNTIME_ERROR = "EXTRACT_RUNTIME_ERROR" # 运行时错误
|
||
|
||
|
||
class SlotValueSource(str, Enum):
|
||
"""
|
||
[AC-MRS-09] 槽位值来源
|
||
"""
|
||
USER_CONFIRMED = "user_confirmed"
|
||
RULE_EXTRACTED = "rule_extracted"
|
||
LLM_INFERRED = "llm_inferred"
|
||
DEFAULT = "default"
|
||
|
||
|
||
class SlotDefinition(SQLModel, table=True):
|
||
"""
|
||
[AC-MRS-07,08] 槽位定义表
|
||
独立的槽位定义模型,与元数据字段解耦但可复用
|
||
[AC-MRS-07-UPGRADE] 支持提取策略链 extract_strategies
|
||
"""
|
||
|
||
__tablename__ = "slot_definitions"
|
||
__table_args__ = (
|
||
Index("ix_slot_definitions_tenant", "tenant_id"),
|
||
Index("ix_slot_definitions_tenant_key", "tenant_id", "slot_key", unique=True),
|
||
Index("ix_slot_definitions_linked_field", "linked_field_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)
|
||
slot_key: str = Field(
|
||
...,
|
||
description="槽位键名,可与元数据字段 field_key 关联",
|
||
min_length=1,
|
||
max_length=100,
|
||
)
|
||
display_name: str | None = Field(
|
||
default=None,
|
||
description="槽位名称,给运营/教研看的中文名,例:grade -> '当前年级'",
|
||
max_length=100,
|
||
)
|
||
description: str | None = Field(
|
||
default=None,
|
||
description="槽位说明,解释这个槽位采集什么、用于哪里",
|
||
max_length=500,
|
||
)
|
||
type: str = Field(
|
||
default=MetadataFieldType.STRING.value,
|
||
description="槽位类型: string/number/boolean/enum/array_enum"
|
||
)
|
||
required: bool = Field(default=False, description="是否必填槽位")
|
||
# [AC-MRS-07-UPGRADE] 保留旧字段用于兼容读取
|
||
extract_strategy: str | None = Field(
|
||
default=None,
|
||
description="[兼容字段] 提取策略: rule/llm/user_input,已废弃,请使用 extract_strategies"
|
||
)
|
||
# [AC-MRS-07-UPGRADE] 新增策略链字段
|
||
extract_strategies: list[str] | None = Field(
|
||
default=None,
|
||
sa_column=Column("extract_strategies", JSON, nullable=True),
|
||
description="[AC-MRS-07-UPGRADE] 提取策略链:有序数组,元素为 rule/llm/user_input,按顺序执行直到成功"
|
||
)
|
||
validation_rule: str | None = Field(
|
||
default=None,
|
||
description="校验规则(正则或 JSON Schema)"
|
||
)
|
||
ask_back_prompt: str | None = Field(
|
||
default=None,
|
||
description="追问提示语模板"
|
||
)
|
||
default_value: dict[str, Any] | None = Field(
|
||
default=None,
|
||
sa_column=Column("default_value", JSON, nullable=True),
|
||
description="默认值"
|
||
)
|
||
linked_field_id: uuid.UUID | None = Field(
|
||
default=None,
|
||
description="关联的元数据字段 ID",
|
||
foreign_key="metadata_field_definitions.id",
|
||
)
|
||
created_at: datetime = Field(default_factory=datetime.utcnow, description="创建时间")
|
||
updated_at: datetime = Field(default_factory=datetime.utcnow, description="更新时间")
|
||
|
||
def get_effective_strategies(self) -> list[str]:
|
||
"""
|
||
[AC-MRS-07-UPGRADE] 获取有效的提取策略链
|
||
优先使用 extract_strategies,如果不存在则兼容读取 extract_strategy
|
||
"""
|
||
if self.extract_strategies and len(self.extract_strategies) > 0:
|
||
return self.extract_strategies
|
||
if self.extract_strategy:
|
||
return [self.extract_strategy]
|
||
return []
|
||
|
||
def validate_strategies(self) -> tuple[bool, str]:
|
||
"""
|
||
[AC-MRS-07-UPGRADE] 校验提取策略链的有效性
|
||
|
||
Returns:
|
||
Tuple of (是否有效, 错误信息)
|
||
"""
|
||
valid_strategies = {"rule", "llm", "user_input"}
|
||
strategies = self.get_effective_strategies()
|
||
|
||
if not strategies:
|
||
return True, "" # 空策略链视为有效(使用默认行为)
|
||
|
||
# 校验至少1个策略
|
||
if len(strategies) == 0:
|
||
return False, "提取策略链不能为空"
|
||
|
||
# 校验不允许重复策略
|
||
if len(strategies) != len(set(strategies)):
|
||
return False, "提取策略链中不允许重复的策略"
|
||
|
||
# 校验策略值有效
|
||
invalid = [s for s in strategies if s not in valid_strategies]
|
||
if invalid:
|
||
return False, f"无效的提取策略: {invalid},有效值为: {list(valid_strategies)}"
|
||
|
||
return True, ""
|
||
|
||
|
||
class SlotDefinitionCreate(SQLModel):
|
||
"""[AC-MRS-07,08] 创建槽位定义"""
|
||
|
||
slot_key: str = Field(..., min_length=1, max_length=100)
|
||
display_name: str | None = Field(
|
||
default=None,
|
||
description="槽位名称,给运营/教研看的中文名",
|
||
max_length=100,
|
||
)
|
||
description: str | None = Field(
|
||
default=None,
|
||
description="槽位说明,解释这个槽位采集什么、用于哪里",
|
||
max_length=500,
|
||
)
|
||
type: str = Field(default=MetadataFieldType.STRING.value)
|
||
required: bool = Field(default=False)
|
||
# [AC-MRS-07-UPGRADE] 支持策略链
|
||
extract_strategies: list[str] | None = Field(
|
||
default=None,
|
||
description="提取策略链:有序数组,元素为 rule/llm/user_input,按顺序执行直到成功"
|
||
)
|
||
# [AC-MRS-07-UPGRADE] 保留旧字段用于兼容
|
||
extract_strategy: str | None = Field(
|
||
default=None,
|
||
description="[兼容字段] 单提取策略,已废弃,请使用 extract_strategies"
|
||
)
|
||
validation_rule: str | None = None
|
||
ask_back_prompt: str | None = None
|
||
default_value: dict[str, Any] | None = None
|
||
linked_field_id: str | None = None
|
||
|
||
|
||
class SlotDefinitionUpdate(SQLModel):
|
||
"""[AC-MRS-07] 更新槽位定义"""
|
||
|
||
display_name: str | None = Field(
|
||
default=None,
|
||
description="槽位名称,给运营/教研看的中文名",
|
||
max_length=100,
|
||
)
|
||
description: str | None = Field(
|
||
default=None,
|
||
description="槽位说明,解释这个槽位采集什么、用于哪里",
|
||
max_length=500,
|
||
)
|
||
type: str | None = None
|
||
required: bool | None = None
|
||
# [AC-MRS-07-UPGRADE] 支持策略链
|
||
extract_strategies: list[str] | None = Field(
|
||
default=None,
|
||
description="提取策略链:有序数组,元素为 rule/llm/user_input,按顺序执行直到成功"
|
||
)
|
||
# [AC-MRS-07-UPGRADE] 保留旧字段用于兼容
|
||
extract_strategy: str | None = Field(
|
||
default=None,
|
||
description="[兼容字段] 单提取策略,已废弃,请使用 extract_strategies"
|
||
)
|
||
validation_rule: str | None = None
|
||
ask_back_prompt: str | None = None
|
||
default_value: dict[str, Any] | None = None
|
||
linked_field_id: str | None = None
|
||
|
||
|
||
class SlotValue(SQLModel):
|
||
"""
|
||
[AC-MRS-09] 运行时槽位值
|
||
"""
|
||
|
||
key: str = Field(..., description="槽位键名")
|
||
value: Any = Field(..., description="槽位值")
|
||
source: str = Field(
|
||
default=SlotValueSource.DEFAULT.value,
|
||
description="来源: user_confirmed/rule_extracted/llm_inferred/default"
|
||
)
|
||
confidence: float = Field(
|
||
default=1.0,
|
||
ge=0.0,
|
||
le=1.0,
|
||
description="置信度 0.0~1.0"
|
||
)
|
||
updated_at: datetime = Field(
|
||
default_factory=datetime.utcnow,
|
||
description="最后更新时间"
|
||
)
|
||
|
||
|
||
class MetadataSchema(SQLModel, table=True):
|
||
"""
|
||
元数据模式定义(保留兼容性)
|
||
每个租户可以定义自己的元数据字段配置
|
||
"""
|
||
|
||
__tablename__ = "metadata_schemas"
|
||
__table_args__ = (
|
||
Index("ix_metadata_schemas_tenant", "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="模式名称,如 教育行业元数据")
|
||
description: str | None = Field(default=None, description="模式描述")
|
||
fields: list[dict[str, Any]] = Field(
|
||
default=[],
|
||
sa_column=Column("fields", JSON, nullable=False),
|
||
description="字段定义列表"
|
||
)
|
||
is_default: bool = Field(default=False, description="是否为租户默认模式")
|
||
is_enabled: bool = Field(default=True, description="是否启用")
|
||
created_at: datetime = Field(default_factory=datetime.utcnow, description="创建时间")
|
||
updated_at: datetime = Field(default_factory=datetime.utcnow, description="更新时间")
|
||
|
||
|
||
class MetadataSchemaCreate(SQLModel):
|
||
"""创建元数据模式"""
|
||
|
||
name: str
|
||
description: str | None = None
|
||
fields: list[dict[str, Any]]
|
||
is_default: bool = False
|
||
|
||
|
||
class MetadataSchemaUpdate(SQLModel):
|
||
"""更新元数据模式"""
|
||
|
||
name: str | None = None
|
||
description: str | None = None
|
||
fields: list[dict[str, Any]] | None = None
|
||
is_default: bool | None = None
|
||
is_enabled: bool | None = None
|
||
|
||
|
||
class MetadataFieldCreate(SQLModel):
|
||
"""创建元数据字段"""
|
||
|
||
name: str
|
||
label: str
|
||
field_type: str = "string"
|
||
options: list[str] | None = None
|
||
required: bool = False
|
||
default_value: str | None = None
|
||
description: str | None = None
|
||
sort_order: int = 0
|
||
|
||
|
||
class DecompositionTemplateStatus(str, Enum):
|
||
"""[AC-IDSMETA-22] 拆解模板状态"""
|
||
DRAFT = "draft"
|
||
PUBLISHED = "published"
|
||
ARCHIVED = "archived"
|
||
|
||
|
||
class DecompositionTemplate(SQLModel, table=True):
|
||
"""
|
||
[AC-IDSMETA-22] 拆解模板表
|
||
用于将待录入文本按固定模板拆解为结构化数据
|
||
"""
|
||
|
||
__tablename__ = "decomposition_templates"
|
||
__table_args__ = (
|
||
Index("ix_decomposition_templates_tenant", "tenant_id"),
|
||
Index("ix_decomposition_templates_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)
|
||
name: str = Field(..., description="模板名称")
|
||
description: str | None = Field(default=None, description="模板描述")
|
||
version: int = Field(default=1, description="版本号")
|
||
status: str = Field(
|
||
default=DecompositionTemplateStatus.DRAFT.value,
|
||
description="模板状态: draft/published/archived"
|
||
)
|
||
template_schema: dict[str, Any] = Field(
|
||
default_factory=dict,
|
||
sa_column=Column("template_schema", JSON, nullable=False),
|
||
description="输出模板结构定义,包含字段名、类型、描述等"
|
||
)
|
||
extraction_hints: dict[str, Any] | None = Field(
|
||
default=None,
|
||
sa_column=Column("extraction_hints", JSON, nullable=True),
|
||
description="提取提示,用于指导 LLM 提取特定字段"
|
||
)
|
||
example_input: str | None = Field(default=None, description="示例输入文本")
|
||
example_output: dict[str, Any] | None = Field(
|
||
default=None,
|
||
sa_column=Column("example_output", JSON, nullable=True),
|
||
description="示例输出 JSON"
|
||
)
|
||
created_at: datetime = Field(default_factory=datetime.utcnow, description="创建时间")
|
||
updated_at: datetime = Field(default_factory=datetime.utcnow, description="更新时间")
|
||
|
||
|
||
class DecompositionTemplateCreate(SQLModel):
|
||
"""[AC-IDSMETA-22] 创建拆解模板"""
|
||
|
||
name: str
|
||
description: str | None = None
|
||
template_schema: dict[str, Any]
|
||
extraction_hints: dict[str, Any] | None = None
|
||
example_input: str | None = None
|
||
example_output: dict[str, Any] | None = None
|
||
|
||
|
||
class DecompositionTemplateUpdate(SQLModel):
|
||
"""[AC-IDSMETA-22] 更新拆解模板"""
|
||
|
||
name: str | None = None
|
||
description: str | None = None
|
||
template_schema: dict[str, Any] | None = None
|
||
extraction_hints: dict[str, Any] | None = None
|
||
example_input: str | None = None
|
||
example_output: dict[str, Any] | None = None
|
||
status: str | None = None
|
||
|
||
|
||
class DecompositionRequest(SQLModel):
|
||
"""[AC-IDSMETA-21] 拆解请求"""
|
||
|
||
text: str = Field(..., description="待拆解的文本")
|
||
template_id: str | None = Field(default=None, description="指定模板 ID(可选)")
|
||
hints: dict[str, Any] | None = Field(default=None, description="额外提取提示")
|
||
|
||
|
||
class DecompositionResult(SQLModel):
|
||
"""[AC-IDSMETA-21] 拆解结果"""
|
||
|
||
success: bool = Field(..., description="是否成功")
|
||
data: dict[str, Any] | None = Field(default=None, description="拆解后的结构化数据")
|
||
template_id: str | None = Field(default=None, description="使用的模板 ID")
|
||
template_version: int | None = Field(default=None, description="使用的模板版本")
|
||
confidence: float | None = Field(default=None, description="拆解置信度")
|
||
error: str | None = Field(default=None, description="错误信息")
|
||
latency_ms: int | None = Field(default=None, description="处理耗时(毫秒)")
|
||
|
||
|
||
class HighRiskScenarioType(str, Enum):
|
||
"""[AC-IDMP-20] 高风险场景类型"""
|
||
REFUND = "refund"
|
||
COMPLAINT_ESCALATION = "complaint_escalation"
|
||
PRIVACY_SENSITIVE_PROMISE = "privacy_sensitive_promise"
|
||
TRANSFER = "transfer"
|
||
|
||
|
||
class HighRiskPolicy(SQLModel, table=True):
|
||
"""
|
||
[AC-IDMP-20] 高风险场景策略配置
|
||
定义高风险场景的最小集,支持动态配置
|
||
"""
|
||
|
||
__tablename__ = "high_risk_policies"
|
||
__table_args__ = (
|
||
Index("ix_high_risk_policies_tenant", "tenant_id"),
|
||
Index("ix_high_risk_policies_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)
|
||
scenario: str = Field(..., description="场景类型: refund/complaint_escalation/privacy_sensitive_promise/transfer")
|
||
handler_mode: str = Field(
|
||
default="micro_flow",
|
||
description="处理模式: micro_flow/transfer"
|
||
)
|
||
flow_id: uuid.UUID | None = Field(
|
||
default=None,
|
||
description="微流程ID (handler_mode=micro_flow时使用)",
|
||
foreign_key="script_flows.id"
|
||
)
|
||
transfer_message: str | None = Field(
|
||
default=None,
|
||
description="转人工消息 (handler_mode=transfer时使用)"
|
||
)
|
||
keywords: list[str] | None = Field(
|
||
default=None,
|
||
sa_column=Column("keywords", JSON, nullable=True),
|
||
description="触发关键词列表"
|
||
)
|
||
patterns: list[str] | None = Field(
|
||
default=None,
|
||
sa_column=Column("patterns", JSON, nullable=True),
|
||
description="触发正则模式列表"
|
||
)
|
||
priority: int = Field(default=0, description="优先级 (值越高优先级越高)")
|
||
is_enabled: bool = Field(default=True, description="是否启用")
|
||
created_at: datetime = Field(default_factory=datetime.utcnow, description="创建时间")
|
||
updated_at: datetime = Field(default_factory=datetime.utcnow, description="更新时间")
|
||
|
||
|
||
class HighRiskPolicyCreate(SQLModel):
|
||
"""[AC-IDMP-20] 创建高风险策略"""
|
||
|
||
scenario: str
|
||
handler_mode: str = "micro_flow"
|
||
flow_id: str | None = None
|
||
transfer_message: str | None = None
|
||
keywords: list[str] | None = None
|
||
patterns: list[str] | None = None
|
||
priority: int = 0
|
||
|
||
|
||
class HighRiskPolicyUpdate(SQLModel):
|
||
"""[AC-IDMP-20] 更新高风险策略"""
|
||
|
||
handler_mode: str | None = None
|
||
flow_id: str | None = None
|
||
transfer_message: str | None = None
|
||
keywords: list[str] | None = None
|
||
patterns: list[str] | None = None
|
||
priority: int | None = None
|
||
is_enabled: bool | None = None
|
||
|
||
|
||
class SessionModeRecord(SQLModel, table=True):
|
||
"""
|
||
[AC-IDMP-09] 会话模式记录
|
||
记录会话的当前模式状态
|
||
"""
|
||
|
||
__tablename__ = "session_mode_records"
|
||
__table_args__ = (
|
||
Index("ix_session_mode_records_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="会话ID", index=True)
|
||
mode: str = Field(
|
||
default="BOT_ACTIVE",
|
||
description="会话模式: BOT_ACTIVE/HUMAN_ACTIVE"
|
||
)
|
||
reason: str | None = Field(default=None, description="模式切换原因")
|
||
switched_at: datetime | None = Field(default=None, description="模式切换时间")
|
||
created_at: datetime = Field(default_factory=datetime.utcnow, description="创建时间")
|
||
updated_at: datetime = Field(default_factory=datetime.utcnow, description="更新时间")
|
||
|
||
|
||
class MidAuditLog(SQLModel, table=True):
|
||
"""
|
||
[AC-IDMP-07] 中台审计日志
|
||
记录 generation/request 维度的审计字段
|
||
"""
|
||
|
||
__tablename__ = "mid_audit_logs"
|
||
__table_args__ = (
|
||
Index("ix_mid_audit_logs_tenant_session", "tenant_id", "session_id"),
|
||
Index("ix_mid_audit_logs_tenant_request", "tenant_id", "request_id"),
|
||
Index("ix_mid_audit_logs_tenant_generation", "tenant_id", "generation_id"),
|
||
Index("ix_mid_audit_logs_created", "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="会话ID", index=True)
|
||
request_id: str = Field(..., description="请求ID", index=True)
|
||
generation_id: str = Field(..., description="生成ID", index=True)
|
||
mode: str = Field(..., description="执行模式: agent/micro_flow/fixed/transfer")
|
||
intent: str | None = Field(default=None, description="意图")
|
||
tool_calls: list[dict[str, Any]] | None = Field(
|
||
default=None,
|
||
sa_column=Column("tool_calls", JSON, nullable=True),
|
||
description="工具调用记录"
|
||
)
|
||
guardrail_triggered: bool = Field(default=False, description="护栏是否触发")
|
||
fallback_reason_code: str | None = Field(default=None, description="降级原因码")
|
||
react_iterations: int | None = Field(default=None, description="ReAct循环次数")
|
||
high_risk_scenario: str | None = Field(default=None, description="触发的高风险场景")
|
||
latency_ms: int | None = Field(default=None, description="总耗时(ms)")
|
||
created_at: datetime = Field(default_factory=datetime.utcnow, description="创建时间", index=True)
|
||
|
||
|
||
class SceneSlotBundleStatus(str, Enum):
|
||
"""[AC-SCENE-SLOT-01] 场景槽位包状态"""
|
||
DRAFT = "draft"
|
||
ACTIVE = "active"
|
||
DEPRECATED = "deprecated"
|
||
|
||
|
||
class SceneSlotBundle(SQLModel, table=True):
|
||
"""
|
||
[AC-SCENE-SLOT-01] 场景-槽位映射配置
|
||
定义每个场景需要采集的槽位集合
|
||
|
||
三层关系:
|
||
- 层1:slot ↔ metadata(通过 linked_field_id)
|
||
- 层2:scene ↔ slot_bundle(本模型)
|
||
- 层3:step.expected_variables ↔ slot_key(话术步骤引用)
|
||
"""
|
||
|
||
__tablename__ = "scene_slot_bundles"
|
||
__table_args__ = (
|
||
Index("ix_scene_slot_bundles_tenant", "tenant_id"),
|
||
Index("ix_scene_slot_bundles_tenant_scene", "tenant_id", "scene_key", unique=True),
|
||
Index("ix_scene_slot_bundles_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)
|
||
scene_key: str = Field(
|
||
...,
|
||
description="场景标识,如 'open_consult', 'refund_apply', 'course_recommend'",
|
||
min_length=1,
|
||
max_length=100,
|
||
)
|
||
scene_name: str = Field(
|
||
...,
|
||
description="场景名称,如 '开放咨询', '退款申请', '课程推荐'",
|
||
min_length=1,
|
||
max_length=100,
|
||
)
|
||
description: str | None = Field(
|
||
default=None,
|
||
description="场景描述"
|
||
)
|
||
required_slots: list[str] = Field(
|
||
default_factory=list,
|
||
sa_column=Column("required_slots", JSON, nullable=False),
|
||
description="必填槽位 slot_key 列表"
|
||
)
|
||
optional_slots: list[str] = Field(
|
||
default_factory=list,
|
||
sa_column=Column("optional_slots", JSON, nullable=False),
|
||
description="可选槽位 slot_key 列表"
|
||
)
|
||
slot_priority: list[str] | None = Field(
|
||
default=None,
|
||
sa_column=Column("slot_priority", JSON, nullable=True),
|
||
description="槽位采集优先级顺序(slot_key 列表)"
|
||
)
|
||
completion_threshold: float = Field(
|
||
default=1.0,
|
||
ge=0.0,
|
||
le=1.0,
|
||
description="完成阈值(0.0-1.0),必填槽位填充比例达到此值视为完成"
|
||
)
|
||
ask_back_order: str = Field(
|
||
default="priority",
|
||
description="追问顺序策略: priority/required_first/parallel"
|
||
)
|
||
status: str = Field(
|
||
default=SceneSlotBundleStatus.DRAFT.value,
|
||
description="状态: draft/active/deprecated"
|
||
)
|
||
version: int = Field(default=1, description="版本号")
|
||
created_at: datetime = Field(default_factory=datetime.utcnow, description="创建时间")
|
||
updated_at: datetime = Field(default_factory=datetime.utcnow, description="更新时间")
|
||
|
||
|
||
class SceneSlotBundleCreate(SQLModel):
|
||
"""[AC-SCENE-SLOT-01] 创建场景槽位包"""
|
||
|
||
scene_key: str = Field(..., min_length=1, max_length=100)
|
||
scene_name: str = Field(..., min_length=1, max_length=100)
|
||
description: str | None = None
|
||
required_slots: list[str] = Field(default_factory=list)
|
||
optional_slots: list[str] = Field(default_factory=list)
|
||
slot_priority: list[str] | None = None
|
||
completion_threshold: float = Field(default=1.0, ge=0.0, le=1.0)
|
||
ask_back_order: str = Field(default="priority")
|
||
status: str = Field(default=SceneSlotBundleStatus.DRAFT.value)
|
||
|
||
|
||
class SceneSlotBundleUpdate(SQLModel):
|
||
"""[AC-SCENE-SLOT-01] 更新场景槽位包"""
|
||
|
||
scene_name: str | None = Field(default=None, min_length=1, max_length=100)
|
||
description: str | None = None
|
||
required_slots: list[str] | None = None
|
||
optional_slots: list[str] | None = None
|
||
slot_priority: list[str] | None = None
|
||
completion_threshold: float | None = Field(default=None, ge=0.0, le=1.0)
|
||
ask_back_order: str | None = None
|
||
status: str | None = None
|