1216 lines
45 KiB
Python
1216 lines
45 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.
|
||
[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.
|
||
"""
|
||
|
||
__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"
|
||
)
|
||
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
|
||
|
||
|
||
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
|
||
|
||
|
||
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': '痛点'}}"
|
||
)
|
||
|
||
|
||
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 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] 元数据字段定义表
|
||
每个字段独立存储,支持字段级状态管理(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="是否用于排序特征")
|
||
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] 创建元数据字段定义"""
|
||
|
||
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)
|
||
status: str = Field(default=MetadataFieldStatus.DRAFT.value)
|
||
|
||
|
||
class MetadataFieldDefinitionUpdate(SQLModel):
|
||
"""[AC-IDSMETA-14] 更新元数据字段定义"""
|
||
|
||
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
|
||
status: str | None = None
|
||
|
||
|
||
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="处理耗时(毫秒)")
|