- Added support for public Realtime API, including new routes for managing API keys and handling WebRTC connections. - Introduced RealtimeApiKey model and associated CRUD operations for admin management of API keys. - Implemented authentication mechanisms for API keys and client secrets. - Enhanced environment configuration with new secrets for Realtime API. - Created OpenAIRealtime session management and event processing for real-time interactions. - Updated schemas and settings to accommodate new features and ensure compatibility with existing systems.
563 lines
22 KiB
Python
563 lines
22 KiB
Python
"""数据表定义(SQLAlchemy 2.0)。
|
||
|
||
模型注册表由接口定义驱动:
|
||
- InterfaceDefinition:具体接入协议及其动态表单字段
|
||
- ModelResource:模型配置与鉴权值
|
||
- AssistantModelBinding:助手按能力选择模型资源
|
||
|
||
"""
|
||
|
||
from datetime import datetime
|
||
|
||
from sqlalchemy import (
|
||
JSON,
|
||
Boolean,
|
||
DateTime,
|
||
ForeignKey,
|
||
Integer,
|
||
String,
|
||
Text,
|
||
UniqueConstraint,
|
||
func,
|
||
)
|
||
from sqlalchemy.dialects.postgresql import JSONB
|
||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||
from pgvector.sqlalchemy import Vector
|
||
|
||
|
||
class Base(DeclarativeBase):
|
||
pass
|
||
|
||
|
||
class InterfaceDefinition(Base):
|
||
"""具体接入协议,例如 xfyun-tts 与 xfyun-super-tts。"""
|
||
|
||
__tablename__ = "interface_definitions"
|
||
|
||
interface_type: Mapped[str] = mapped_column(String(64), primary_key=True)
|
||
name: Mapped[str] = mapped_column(String(128))
|
||
capability: Mapped[str] = mapped_column(String(16), index=True)
|
||
field_schema: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||
enabled: Mapped[bool] = mapped_column(Boolean, default=True)
|
||
version: Mapped[int] = mapped_column(default=1)
|
||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||
updated_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
||
)
|
||
|
||
|
||
class ModelResource(Base):
|
||
"""统一模型资源:接口类型决定能力、鉴权字段和调用参数。"""
|
||
|
||
__tablename__ = "model_resources"
|
||
|
||
id: Mapped[str] = mapped_column(String(40), primary_key=True)
|
||
name: Mapped[str] = mapped_column(String(128), default="")
|
||
capability: Mapped[str] = mapped_column(String(16), index=True)
|
||
interface_type: Mapped[str] = mapped_column(
|
||
String(64),
|
||
ForeignKey("interface_definitions.interface_type", ondelete="RESTRICT"),
|
||
index=True,
|
||
)
|
||
values: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||
secrets: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||
support_image_input: Mapped[bool] = mapped_column(Boolean, default=False)
|
||
enabled: Mapped[bool] = mapped_column(Boolean, default=True)
|
||
is_default: Mapped[bool] = mapped_column(Boolean, default=False)
|
||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||
updated_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
||
)
|
||
|
||
|
||
class KnowledgeBase(Base):
|
||
"""知识库注册表。本身引用一个 Embedding 模型资源。
|
||
|
||
文档/分块(pgvector)是 KB 内部实现,这里先不展开;助手侧只认 knowledge_base_id。
|
||
"""
|
||
|
||
__tablename__ = "knowledge_bases"
|
||
|
||
id: Mapped[str] = mapped_column(String(40), primary_key=True) # kb_xxx
|
||
name: Mapped[str] = mapped_column(String(128))
|
||
description: Mapped[str] = mapped_column(String(2048), default="")
|
||
embedding_model_resource_id: Mapped[str | None] = mapped_column(
|
||
String(40),
|
||
ForeignKey("model_resources.id", ondelete="SET NULL"),
|
||
index=True,
|
||
nullable=True,
|
||
)
|
||
status: Mapped[str] = mapped_column(String(16), default="active") # active|archived
|
||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||
updated_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
||
)
|
||
|
||
|
||
class KnowledgeDocument(Base):
|
||
"""A file or pasted text ingested into one knowledge base."""
|
||
|
||
__tablename__ = "knowledge_documents"
|
||
|
||
id: Mapped[str] = mapped_column(String(40), primary_key=True)
|
||
knowledge_base_id: Mapped[str] = mapped_column(
|
||
String(40), ForeignKey("knowledge_bases.id", ondelete="CASCADE"), index=True
|
||
)
|
||
name: Mapped[str] = mapped_column(String(255))
|
||
source_type: Mapped[str] = mapped_column(String(16)) # file|text
|
||
mime_type: Mapped[str] = mapped_column(String(128), default="text/plain")
|
||
storage_key: Mapped[str | None] = mapped_column(String(1024), nullable=True)
|
||
size_bytes: Mapped[int] = mapped_column(Integer, default=0)
|
||
status: Mapped[str] = mapped_column(String(16), default="processing")
|
||
error_message: Mapped[str] = mapped_column(String(2048), default="")
|
||
chunk_count: Mapped[int] = mapped_column(Integer, default=0)
|
||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||
updated_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
||
)
|
||
|
||
|
||
class KnowledgeChunk(Base):
|
||
"""Searchable text chunk; Vector has no fixed dimension to support configured models."""
|
||
|
||
__tablename__ = "knowledge_chunks"
|
||
__table_args__ = (
|
||
UniqueConstraint("document_id", "chunk_index", name="uq_knowledge_chunk_position"),
|
||
)
|
||
|
||
id: Mapped[str] = mapped_column(String(40), primary_key=True)
|
||
knowledge_base_id: Mapped[str] = mapped_column(
|
||
String(40), ForeignKey("knowledge_bases.id", ondelete="CASCADE"), index=True
|
||
)
|
||
document_id: Mapped[str] = mapped_column(
|
||
String(40), ForeignKey("knowledge_documents.id", ondelete="CASCADE"), index=True
|
||
)
|
||
chunk_index: Mapped[int] = mapped_column(Integer)
|
||
content: Mapped[str] = mapped_column(Text)
|
||
embedding: Mapped[list[float]] = mapped_column(Vector())
|
||
|
||
|
||
class Assistant(Base):
|
||
"""助手(单表,无版本化)。type 为可变普通列,5 种类型共用此表。
|
||
|
||
模型/KB 以 FK 引用注册表;类型专属字段塞进 config(JSON)。
|
||
"""
|
||
|
||
__tablename__ = "assistants"
|
||
|
||
id: Mapped[str] = mapped_column(String(40), primary_key=True) # asst_xxx
|
||
name: Mapped[str] = mapped_column(String(128))
|
||
# prompt|workflow|dify|fastgpt|opencode;创建后可改
|
||
type: Mapped[str] = mapped_column(String(16), index=True, default="prompt")
|
||
runtime_mode: Mapped[str] = mapped_column(String(16), default="pipeline")
|
||
greeting: Mapped[str] = mapped_column(String(2048), default="")
|
||
enable_interrupt: Mapped[bool] = mapped_column(Boolean, default=True)
|
||
turn_config: Mapped[dict] = mapped_column(JSON, default=dict)
|
||
startup: Mapped[dict] = mapped_column(JSON, default=dict)
|
||
vision_enabled: Mapped[bool] = mapped_column(Boolean, default=False)
|
||
vision_model_resource_id: Mapped[str | None] = mapped_column(
|
||
String(40),
|
||
ForeignKey("model_resources.id", ondelete="SET NULL"),
|
||
nullable=True,
|
||
)
|
||
|
||
# KB 引用:被引用时禁止删 KB(RESTRICT),无默认兜底
|
||
knowledge_base_id: Mapped[str | None] = mapped_column(
|
||
String(40), ForeignKey("knowledge_bases.id", ondelete="RESTRICT"), nullable=True
|
||
)
|
||
knowledge_retrieval_config: Mapped[dict] = mapped_column(
|
||
JSON,
|
||
default=lambda: {"mode": "automatic", "top_n": 5, "score_threshold": 0.0},
|
||
)
|
||
|
||
# ---- 瘦类型专属字段(真列,稀疏:按 type 用其中几列) ----
|
||
prompt: Mapped[str] = mapped_column(Text, default="") # prompt / opencode
|
||
dynamic_variable_definitions: Mapped[dict] = mapped_column(JSON, default=dict)
|
||
analysis_config: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||
api_url: Mapped[str] = mapped_column(String(512), default="") # dify / fastgpt / opencode
|
||
api_key: Mapped[str] = mapped_column(String(512), default="") # dify / fastgpt / opencode(打码/哨兵)
|
||
app_id: Mapped[str] = mapped_column(String(128), default="") # fastgpt
|
||
# workflow 专属:图(nodes/edges)。要版本化时再迁出到 assistant_workflow 表
|
||
graph: Mapped[dict] = mapped_column(JSON, default=dict)
|
||
|
||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||
updated_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
||
)
|
||
|
||
|
||
class AssistantModelBinding(Base):
|
||
"""助手按能力绑定统一模型资源;config 可覆盖资源默认 options。"""
|
||
|
||
__tablename__ = "assistant_model_bindings"
|
||
|
||
assistant_id: Mapped[str] = mapped_column(
|
||
String(40),
|
||
ForeignKey("assistants.id", ondelete="CASCADE"),
|
||
primary_key=True,
|
||
)
|
||
capability: Mapped[str] = mapped_column(String(16), primary_key=True)
|
||
model_resource_id: Mapped[str] = mapped_column(
|
||
String(40),
|
||
ForeignKey("model_resources.id", ondelete="RESTRICT"),
|
||
index=True,
|
||
)
|
||
config: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||
updated_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
||
)
|
||
|
||
|
||
class McpServer(Base):
|
||
"""Workspace-level remote MCP connection used to discover executable tools."""
|
||
|
||
__tablename__ = "mcp_servers"
|
||
|
||
id: Mapped[str] = mapped_column(String(40), primary_key=True)
|
||
name: Mapped[str] = mapped_column(String(128))
|
||
description: Mapped[str] = mapped_column(String(2048), default="")
|
||
transport: Mapped[str] = mapped_column(String(32), default="streamable_http")
|
||
url: Mapped[str] = mapped_column(String(2048))
|
||
config: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||
secrets: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||
status: Mapped[str] = mapped_column(String(16), index=True, default="active")
|
||
last_synced_at: Mapped[datetime | None] = mapped_column(
|
||
DateTime(timezone=True), nullable=True
|
||
)
|
||
created_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now()
|
||
)
|
||
updated_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
||
)
|
||
|
||
|
||
class Tool(Base):
|
||
"""Reusable LLM tool definition; supported types are executed at runtime."""
|
||
|
||
__tablename__ = "tools"
|
||
__table_args__ = (
|
||
UniqueConstraint(
|
||
"mcp_server_id",
|
||
"remote_tool_name",
|
||
name="uq_tools_mcp_server_remote_name",
|
||
),
|
||
)
|
||
|
||
id: Mapped[str] = mapped_column(String(40), primary_key=True)
|
||
name: Mapped[str] = mapped_column(String(128))
|
||
function_name: Mapped[str] = mapped_column(String(64), unique=True, index=True)
|
||
type: Mapped[str] = mapped_column(String(24), index=True)
|
||
mcp_server_id: Mapped[str | None] = mapped_column(
|
||
String(40),
|
||
ForeignKey("mcp_servers.id", ondelete="CASCADE"),
|
||
nullable=True,
|
||
index=True,
|
||
)
|
||
remote_tool_name: Mapped[str | None] = mapped_column(
|
||
String(255), nullable=True
|
||
)
|
||
description: Mapped[str] = mapped_column(String(2048), default="")
|
||
definition: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||
secrets: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||
status: Mapped[str] = mapped_column(String(16), index=True, default="active")
|
||
created_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now()
|
||
)
|
||
updated_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
||
)
|
||
|
||
|
||
class AssistantToolBinding(Base):
|
||
"""Prompt assistant to reusable tool many-to-many binding."""
|
||
|
||
__tablename__ = "assistant_tool_bindings"
|
||
|
||
assistant_id: Mapped[str] = mapped_column(
|
||
String(40),
|
||
ForeignKey("assistants.id", ondelete="CASCADE"),
|
||
primary_key=True,
|
||
)
|
||
tool_id: Mapped[str] = mapped_column(
|
||
String(40),
|
||
ForeignKey("tools.id", ondelete="RESTRICT"),
|
||
primary_key=True,
|
||
index=True,
|
||
)
|
||
created_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now()
|
||
)
|
||
|
||
|
||
class ConversationSession(Base):
|
||
"""一次完整连接对应的对话会话;媒体文件以后作为 artifact 关联进来。"""
|
||
|
||
__tablename__ = "conversation_sessions"
|
||
|
||
id: Mapped[str] = mapped_column(String(40), primary_key=True)
|
||
assistant_id: Mapped[str | None] = mapped_column(
|
||
String(40),
|
||
ForeignKey("assistants.id", ondelete="SET NULL"),
|
||
nullable=True,
|
||
index=True,
|
||
)
|
||
assistant_name: Mapped[str] = mapped_column(String(128), default="")
|
||
channel: Mapped[str] = mapped_column(String(24), index=True)
|
||
runtime_mode: Mapped[str] = mapped_column(String(16), default="pipeline")
|
||
status: Mapped[str] = mapped_column(String(16), index=True, default="active")
|
||
message_count: Mapped[int] = mapped_column(Integer, default=0)
|
||
started_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now(), index=True
|
||
)
|
||
ended_at: Mapped[datetime | None] = mapped_column(
|
||
DateTime(timezone=True), nullable=True
|
||
)
|
||
analysis_status: Mapped[str] = mapped_column(
|
||
String(16), index=True, default="none"
|
||
)
|
||
analysis_data: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||
analysis_error: Mapped[str] = mapped_column(String(2048), default="")
|
||
extra: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||
|
||
|
||
class ConversationMessage(Base):
|
||
"""会话中的有序消息;当前只写 text,结构预留其他内容类型。"""
|
||
|
||
__tablename__ = "conversation_messages"
|
||
__table_args__ = (
|
||
UniqueConstraint(
|
||
"session_id", "sequence", name="uq_conversation_message_sequence"
|
||
),
|
||
)
|
||
|
||
id: Mapped[str] = mapped_column(String(40), primary_key=True)
|
||
session_id: Mapped[str] = mapped_column(
|
||
String(40),
|
||
ForeignKey("conversation_sessions.id", ondelete="CASCADE"),
|
||
index=True,
|
||
)
|
||
sequence: Mapped[int] = mapped_column(Integer)
|
||
role: Mapped[str] = mapped_column(String(16))
|
||
content_type: Mapped[str] = mapped_column(String(16), default="text")
|
||
content: Mapped[str] = mapped_column(Text, default="")
|
||
occurred_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now()
|
||
)
|
||
extra: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||
|
||
|
||
class ConversationArtifact(Base):
|
||
"""未来的音频、视频、图片等大对象索引;二进制本体不进入数据库。"""
|
||
|
||
__tablename__ = "conversation_artifacts"
|
||
|
||
id: Mapped[str] = mapped_column(String(40), primary_key=True)
|
||
session_id: Mapped[str] = mapped_column(
|
||
String(40),
|
||
ForeignKey("conversation_sessions.id", ondelete="CASCADE"),
|
||
index=True,
|
||
)
|
||
message_id: Mapped[str | None] = mapped_column(
|
||
String(40),
|
||
ForeignKey("conversation_messages.id", ondelete="SET NULL"),
|
||
nullable=True,
|
||
index=True,
|
||
)
|
||
kind: Mapped[str] = mapped_column(String(16), index=True)
|
||
storage_uri: Mapped[str] = mapped_column(String(1024))
|
||
mime_type: Mapped[str] = mapped_column(String(128), default="")
|
||
size_bytes: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||
duration_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||
created_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now()
|
||
)
|
||
extra: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||
|
||
|
||
class SiteSetting(Base):
|
||
"""Deployment-wide settings, split into public values and write-only secrets."""
|
||
|
||
__tablename__ = "site_settings"
|
||
|
||
key: Mapped[str] = mapped_column(String(64), primary_key=True)
|
||
value: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||
secrets: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||
updated_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
||
)
|
||
|
||
|
||
class RealtimeApiKey(Base):
|
||
"""Hashed deployment-level credential for the public Realtime API."""
|
||
|
||
__tablename__ = "realtime_api_keys"
|
||
|
||
id: Mapped[str] = mapped_column(String(40), primary_key=True)
|
||
name: Mapped[str] = mapped_column(String(128))
|
||
key_prefix: Mapped[str] = mapped_column(String(32), unique=True, index=True)
|
||
key_hash: Mapped[str] = mapped_column(String(64))
|
||
expires_at: Mapped[datetime | None] = mapped_column(
|
||
DateTime(timezone=True), nullable=True
|
||
)
|
||
last_used_at: Mapped[datetime | None] = mapped_column(
|
||
DateTime(timezone=True), nullable=True
|
||
)
|
||
revoked_at: Mapped[datetime | None] = mapped_column(
|
||
DateTime(timezone=True), nullable=True
|
||
)
|
||
created_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now()
|
||
)
|
||
updated_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
||
)
|
||
|
||
|
||
class WebhookDelivery(Base):
|
||
"""Immutable event snapshot plus its asynchronous delivery state."""
|
||
|
||
__tablename__ = "webhook_deliveries"
|
||
|
||
id: Mapped[str] = mapped_column(String(40), primary_key=True)
|
||
event_id: Mapped[str] = mapped_column(String(40), unique=True)
|
||
event_type: Mapped[str] = mapped_column(String(128), index=True)
|
||
conversation_id: Mapped[str | None] = mapped_column(
|
||
String(40),
|
||
ForeignKey("conversation_sessions.id", ondelete="SET NULL"),
|
||
nullable=True,
|
||
index=True,
|
||
)
|
||
payload: Mapped[dict] = mapped_column(JSONB)
|
||
url: Mapped[str] = mapped_column(String(2048))
|
||
secrets: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||
status: Mapped[str] = mapped_column(String(16), index=True, default="pending")
|
||
attempt_count: Mapped[int] = mapped_column(Integer, default=0)
|
||
next_attempt_at: Mapped[datetime | None] = mapped_column(
|
||
DateTime(timezone=True), nullable=True, index=True
|
||
)
|
||
last_status_code: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||
last_error: Mapped[str] = mapped_column(String(2048), default="")
|
||
delivered_at: Mapped[datetime | None] = mapped_column(
|
||
DateTime(timezone=True), nullable=True
|
||
)
|
||
created_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now()
|
||
)
|
||
updated_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
||
)
|
||
|
||
|
||
class TestSuite(Base):
|
||
"""A persisted group of text-based assistant test cases."""
|
||
|
||
__tablename__ = "test_suites"
|
||
|
||
id: Mapped[str] = mapped_column(String(40), primary_key=True)
|
||
name: Mapped[str] = mapped_column(String(128))
|
||
description: Mapped[str] = mapped_column(String(2048), default="")
|
||
created_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now()
|
||
)
|
||
updated_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
||
)
|
||
|
||
|
||
class TestCase(Base):
|
||
"""One editable test definition; nested turns remain one JSON document."""
|
||
|
||
__tablename__ = "test_cases"
|
||
__table_args__ = (
|
||
UniqueConstraint("suite_id", "sort_order", name="uq_test_case_suite_order"),
|
||
)
|
||
|
||
id: Mapped[str] = mapped_column(String(40), primary_key=True)
|
||
suite_id: Mapped[str] = mapped_column(
|
||
String(40),
|
||
ForeignKey("test_suites.id", ondelete="CASCADE"),
|
||
index=True,
|
||
)
|
||
name: Mapped[str] = mapped_column(String(128))
|
||
description: Mapped[str] = mapped_column(String(2048), default="")
|
||
input_mode: Mapped[str] = mapped_column(
|
||
String(40), default="fixed_script_text", index=True
|
||
)
|
||
definition: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||
sort_order: Mapped[int] = mapped_column(Integer, default=0)
|
||
last_result: Mapped[str] = mapped_column(String(16), default="not_run")
|
||
created_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now()
|
||
)
|
||
updated_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
||
)
|
||
|
||
|
||
class TestRun(Base):
|
||
"""One immutable batch-run request plus its aggregate lifecycle state."""
|
||
|
||
__tablename__ = "test_runs"
|
||
|
||
id: Mapped[str] = mapped_column(String(40), primary_key=True)
|
||
assistant_id: Mapped[str | None] = mapped_column(
|
||
String(40),
|
||
ForeignKey("assistants.id", ondelete="SET NULL"),
|
||
nullable=True,
|
||
index=True,
|
||
)
|
||
assistant_name: Mapped[str] = mapped_column(String(128), default="")
|
||
title: Mapped[str] = mapped_column(String(256), default="批量测试")
|
||
status: Mapped[str] = mapped_column(String(16), index=True, default="queued")
|
||
config: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||
stop_reason: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
||
cancel_requested: Mapped[bool] = mapped_column(Boolean, default=False)
|
||
created_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now()
|
||
)
|
||
started_at: Mapped[datetime | None] = mapped_column(
|
||
DateTime(timezone=True), nullable=True
|
||
)
|
||
finished_at: Mapped[datetime | None] = mapped_column(
|
||
DateTime(timezone=True), nullable=True
|
||
)
|
||
|
||
|
||
class TestRunCase(Base):
|
||
"""Run-local test case snapshot and detailed execution result."""
|
||
|
||
__tablename__ = "test_run_cases"
|
||
__table_args__ = (
|
||
UniqueConstraint("run_id", "test_case_id", name="uq_test_run_case"),
|
||
)
|
||
|
||
id: Mapped[str] = mapped_column(String(40), primary_key=True)
|
||
run_id: Mapped[str] = mapped_column(
|
||
String(40),
|
||
ForeignKey("test_runs.id", ondelete="CASCADE"),
|
||
index=True,
|
||
)
|
||
test_case_id: Mapped[str | None] = mapped_column(
|
||
String(40),
|
||
ForeignKey("test_cases.id", ondelete="SET NULL"),
|
||
nullable=True,
|
||
index=True,
|
||
)
|
||
test_case_name: Mapped[str] = mapped_column(String(128))
|
||
suite_id: Mapped[str | None] = mapped_column(String(40), nullable=True)
|
||
position: Mapped[int] = mapped_column(Integer, default=0)
|
||
case_snapshot: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||
status: Mapped[str] = mapped_column(String(16), index=True, default="waiting")
|
||
attempt_count: Mapped[int] = mapped_column(Integer, default=0)
|
||
max_attempts: Mapped[int] = mapped_column(Integer, default=1)
|
||
result: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||
execution_error: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
|
||
started_at: Mapped[datetime | None] = mapped_column(
|
||
DateTime(timezone=True), nullable=True
|
||
)
|
||
finished_at: Mapped[datetime | None] = mapped_column(
|
||
DateTime(timezone=True), nullable=True
|
||
)
|