diff --git a/ai-service/app/models/entities.py b/ai-service/app/models/entities.py index e21c90c..b88c214 100644 --- a/ai-service/app/models/entities.py +++ b/ai-service/app/models/entities.py @@ -110,6 +110,33 @@ class ChatMessageCreate(SQLModel): content: str +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" @@ -956,6 +983,17 @@ class MetadataFieldType(str, 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" @@ -992,6 +1030,7 @@ class MetadataField(SQLModel): class MetadataFieldDefinition(SQLModel, table=True): """ [AC-IDSMETA-13] 元数据字段定义表 + [AC-MRS-01,02,03] 支持字段角色分层配置 每个字段独立存储,支持字段级状态管理(draft/active/deprecated) """ @@ -1029,6 +1068,11 @@ class MetadataFieldDefinition(SQLModel, table=True): ) is_filterable: bool = Field(default=True, description="是否可用于过滤") is_rank_feature: bool = Field(default=False, 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" @@ -1039,7 +1083,7 @@ class MetadataFieldDefinition(SQLModel, table=True): class MetadataFieldDefinitionCreate(SQLModel): - """[AC-IDSMETA-13] 创建元数据字段定义""" + """[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) @@ -1050,11 +1094,12 @@ class MetadataFieldDefinitionCreate(SQLModel): scope: list[str] = Field(default_factory=lambda: [MetadataScope.KB_DOCUMENT.value]) is_filterable: bool = Field(default=True) is_rank_feature: bool = Field(default=False) + field_roles: list[str] = Field(default_factory=list) status: str = Field(default=MetadataFieldStatus.DRAFT.value) class MetadataFieldDefinitionUpdate(SQLModel): - """[AC-IDSMETA-14] 更新元数据字段定义""" + """[AC-IDSMETA-14] [AC-MRS-01] 更新元数据字段定义""" label: str | None = Field(default=None, min_length=1, max_length=64) required: bool | None = None @@ -1063,9 +1108,129 @@ class MetadataFieldDefinitionUpdate(SQLModel): scope: list[str] | None = None is_filterable: bool | None = None is_rank_feature: bool | 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 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] 槽位定义表 + 独立的槽位定义模型,与元数据字段解耦但可复用 + """ + + __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, + ) + type: str = Field( + default=MetadataFieldType.STRING.value, + description="槽位类型: string/number/boolean/enum/array_enum" + ) + required: bool = Field(default=False, description="是否必填槽位") + extract_strategy: str | None = Field( + default=None, + description="提取策略: 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="更新时间") + + +class SlotDefinitionCreate(SQLModel): + """[AC-MRS-07,08] 创建槽位定义""" + + slot_key: str = Field(..., min_length=1, max_length=100) + type: str = Field(default=MetadataFieldType.STRING.value) + required: bool = Field(default=False) + extract_strategy: str | None = None + 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] 更新槽位定义""" + + type: str | None = None + required: bool | None = None + extract_strategy: str | None = None + 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): """ 元数据模式定义(保留兼容性) @@ -1213,3 +1378,137 @@ class DecompositionResult(SQLModel): 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)