Files
ewoooc/database/ai_models.py
OoO 14c5349b69
All checks were successful
CD Pipeline / deploy (push) Successful in 56s
補齊 AI 觀測表 ORM 與 embedding 簽名
2026-05-12 23:13:20 +08:00

329 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AI history and template models
from sqlalchemy import BigInteger, Column, DateTime, Integer, Numeric, String, Text, Boolean, Float
from sqlalchemy.dialects import postgresql
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.types import JSON, UserDefinedType
from database.models import Base
from datetime import datetime
class Vector(UserDefinedType):
"""pgvector column with a SQLite-safe fallback for local metadata tests."""
cache_ok = True
def __init__(self, dimensions):
self.dimensions = dimensions
def get_col_spec(self, **kw):
return f"VECTOR({self.dimensions})"
@compiles(Vector, "sqlite")
def _compile_vector_sqlite(type_, compiler, **kw):
return "TEXT"
def _jsonb_type():
return JSON().with_variant(postgresql.JSONB, "postgresql")
def _bigint_array_type():
return Text().with_variant(postgresql.ARRAY(BigInteger), "postgresql")
class AIGenerationHistory(Base):
"""
AI generation history tracking
"""
__tablename__ = 'ai_generation_history'
id = Column(Integer, primary_key=True, autoincrement=True)
generation_type = Column(String(50), nullable=False) # product_desc, marketing_copy, etc.
product_name = Column(String(200))
input_keywords = Column(Text) # JSON string
input_trend_topic = Column(String(500))
output_content = Column(Text)
generation_duration = Column(Float) # seconds
is_favorite = Column(Boolean, default=False)
is_used = Column(Boolean, default=False)
created_by = Column(String(100))
created_at = Column(DateTime, default=datetime.now)
def to_dict(self):
return {
'id': self.id,
'generation_type': self.generation_type,
'product_name': self.product_name,
'input_keywords': self.input_keywords,
'input_trend_topic': self.input_trend_topic,
'output_content': self.output_content,
'generation_duration': self.generation_duration,
'is_favorite': self.is_favorite,
'is_used': self.is_used,
'created_by': self.created_by,
'created_at': self.created_at.isoformat() if self.created_at else None
}
class AIPromptTemplate(Base):
"""
AI prompt templates
"""
__tablename__ = 'ai_prompt_templates'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(200), nullable=False)
description = Column(Text)
template_type = Column(String(50), nullable=False) # product_desc, marketing_copy, etc.
prompt_content = Column(Text, nullable=False)
variables = Column(Text) # JSON string of variable definitions
is_active = Column(Boolean, default=True)
is_system = Column(Boolean, default=False)
created_by = Column(String(100))
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
def to_dict(self):
return {
'id': self.id,
'name': self.name,
'description': self.description,
'template_type': self.template_type,
'prompt_content': self.prompt_content,
'variables': self.variables,
'is_active': self.is_active,
'is_system': self.is_system,
'created_by': self.created_by,
'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None
}
class AIUsageTracking(Base):
"""
AI usage tracking for analytics and billing
"""
__tablename__ = 'ai_usage_tracking'
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(String(100))
session_id = Column(String(100))
service_type = Column(String(50), nullable=False) # openai, ollama, etc.
model_name = Column(String(100))
prompt_tokens = Column(Integer, default=0)
completion_tokens = Column(Integer, default=0)
total_tokens = Column(Integer, default=0)
cost_usd = Column(Float, default=0.0)
request_type = Column(String(50)) # generation, analysis, etc.
status = Column(String(20), default='success') # success, failed
error_message = Column(Text)
duration_ms = Column(Float)
created_at = Column(DateTime, default=datetime.now)
def to_dict(self):
return {
'id': self.id,
'user_id': self.user_id,
'session_id': self.session_id,
'service_type': self.service_type,
'model_name': self.model_name,
'prompt_tokens': self.prompt_tokens,
'completion_tokens': self.completion_tokens,
'total_tokens': self.total_tokens,
'cost_usd': self.cost_usd,
'request_type': self.request_type,
'status': self.status,
'error_message': self.error_message,
'duration_ms': self.duration_ms,
'created_at': self.created_at.isoformat() if self.created_at else None
}
class AIInsight(Base):
"""
AI 洞察知識庫ai_insights 表)— KM 沉澱核心,支援 RAG 向量化。
對應 openclaw_learning_service / scheduler 各任務寫入。
"""
__tablename__ = 'ai_insights'
id = Column(Integer, primary_key=True, autoincrement=True)
insight_type = Column(String(50), nullable=False) # backup_status / human_review / auto_heal_playbook 等
period = Column(String(50)) # 分析週期e.g. "2026-04-20"
product_sku = Column(String(50))
content = Column(Text, nullable=False)
metadata_json = Column(Text) # JSON extra payload
avg_quality = Column(Float, default=0.5)
status = Column(String(20), default='approved') # approved / pending / rejected / archived
decay_exempt = Column(Boolean, default=False)
ai_model = Column(String(50))
feedback_up = Column(Integer, default=0)
feedback_down = Column(Integer, default=0)
confidence = Column(Float, default=0.5)
created_by = Column(String(50), default='system')
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
# embedding 欄位為 pgvector 型別,透過 raw SQL 寫入,此處不聲明以避免型別衝突
def to_dict(self):
return {
'id': self.id,
'insight_type': self.insight_type,
'period': self.period,
'product_sku': self.product_sku,
'content': self.content,
'avg_quality': self.avg_quality,
'status': self.status,
'ai_model': self.ai_model,
'feedback_up': self.feedback_up,
'feedback_down': self.feedback_down,
'confidence': self.confidence,
'created_by': self.created_by,
'created_at': self.created_at.isoformat() if self.created_at else None,
}
class AICall(Base):
"""ai_calls unified LLM call telemetry table (migration 024)."""
__tablename__ = 'ai_calls'
__table_args__ = {'extend_existing': True}
id = Column(BigInteger, primary_key=True, autoincrement=True)
called_at = Column(DateTime(timezone=True), default=datetime.now, nullable=False)
caller = Column(String(64), nullable=False)
provider = Column(String(32), nullable=False)
model = Column(String(128), nullable=False)
input_tokens = Column(Integer, default=0, nullable=False)
output_tokens = Column(Integer, default=0, nullable=False)
duration_ms = Column(Integer)
status = Column(String(16), nullable=False)
fallback_to = Column(String(64))
cost_usd = Column(Numeric(10, 6), default=0, nullable=False)
cache_hit = Column(Boolean, default=False, nullable=False)
rag_hit = Column(Boolean, default=False, nullable=False)
request_id = Column(String(64))
error = Column(Text)
meta = Column(_jsonb_type())
class MCPCall(Base):
"""mcp_calls MCP server call telemetry table (migration 025)."""
__tablename__ = 'mcp_calls'
__table_args__ = {'extend_existing': True}
id = Column(BigInteger, primary_key=True, autoincrement=True)
called_at = Column(DateTime(timezone=True), default=datetime.now, nullable=False)
caller = Column(String(64), nullable=False)
server = Column(String(64), nullable=False)
tool = Column(String(128), nullable=False)
input_args = Column(_jsonb_type())
output_size = Column(Integer)
duration_ms = Column(Integer)
status = Column(String(16), nullable=False)
error = Column(Text)
cost_usd = Column(Numeric(10, 6), default=0, nullable=False)
cache_hit = Column(Boolean, default=False, nullable=False)
request_id = Column(String(64))
insight_id = Column(BigInteger)
class AICallBudget(Base):
"""ai_call_budgets budget guardrail table (migration 025)."""
__tablename__ = 'ai_call_budgets'
__table_args__ = {'extend_existing': True}
id = Column(Integer, primary_key=True, autoincrement=True)
period = Column(String(16), nullable=False)
provider = Column(String(32))
budget_usd = Column(Numeric(10, 2), nullable=False)
alert_pct = Column(Integer, default=80, nullable=False)
updated_at = Column(DateTime(timezone=True), default=datetime.now)
class RAGQueryLog(Base):
"""rag_query_log RAG recall telemetry table (migration 027)."""
__tablename__ = 'rag_query_log'
__table_args__ = {'extend_existing': True}
id = Column(BigInteger, primary_key=True, autoincrement=True)
queried_at = Column(DateTime(timezone=True), default=datetime.now, nullable=False)
caller = Column(String(64), nullable=False)
query_text = Column(Text, nullable=False)
query_embedding = Column(Vector(1024))
embedding_signature = Column(String(64))
top_k = Column(Integer, default=5, nullable=False)
threshold = Column(Numeric(4, 3), default=0.85, nullable=False)
hit_count = Column(Integer, default=0, nullable=False)
used_results = Column(_bigint_array_type())
saved_call = Column(Boolean, default=False, nullable=False)
feedback_score = Column(Integer)
request_id = Column(String(64))
class LearningEpisode(Base):
"""learning_episodes PromotionGate staging table (migration 028)."""
__tablename__ = 'learning_episodes'
__table_args__ = {'extend_existing': True}
id = Column(BigInteger, primary_key=True, autoincrement=True)
created_at = Column(DateTime(timezone=True), default=datetime.now, nullable=False)
episode_type = Column(String(32), nullable=False)
source_table = Column(String(32))
source_id = Column(BigInteger)
distilled_text = Column(Text, nullable=False)
embedding = Column(Vector(1024))
embedding_signature = Column(String(64))
quality_score = Column(Numeric(4, 3), default=0.0, nullable=False)
weight = Column(Numeric(4, 3), default=0.5, nullable=False)
promotion_status = Column(String(32), default='pending', nullable=False)
insight_id = Column(BigInteger)
rejected_reason = Column(Text)
human_approver = Column(String(64))
reviewed_at = Column(DateTime(timezone=True))
class HostHealthProbe(Base):
"""host_health_probes Ollama failover health history table (migration 029)."""
__tablename__ = 'host_health_probes'
__table_args__ = {'extend_existing': True}
id = Column(BigInteger, primary_key=True, autoincrement=True)
probed_at = Column(DateTime(timezone=True), default=datetime.now, nullable=False)
host_label = Column(String(64), nullable=False)
host_url = Column(String(256), nullable=False)
healthy = Column(Boolean, nullable=False)
unhealthy_mark = Column(Boolean, default=False, nullable=False)
models_count = Column(Integer, default=0)
response_ms = Column(Integer)
error_msg = Column(Text)
class PPTAuditResult(Base):
"""ppt_audit_results PPT vision audit history table (migration 030)."""
__tablename__ = 'ppt_audit_results'
__table_args__ = {'extend_existing': True}
id = Column(BigInteger, primary_key=True, autoincrement=True)
audited_at = Column(DateTime(timezone=True), default=datetime.now, nullable=False)
pptx_filename = Column(String(256), nullable=False)
pptx_size_kb = Column(Integer)
pptx_mtime = Column(DateTime(timezone=True))
vision_enabled = Column(Boolean, nullable=False)
audit_status = Column(String(32), nullable=False)
issues_count = Column(Integer, default=0)
issues_found = Column(_jsonb_type())
confidence = Column(Numeric(4, 3))
duration_ms = Column(Integer)
error_msg = Column(Text)
reviewer_notes = Column(Text)
__all__ = [
"AIGenerationHistory", "AIPromptTemplate", "AIUsageTracking", "AIInsight",
"AICall", "MCPCall", "AICallBudget", "RAGQueryLog", "LearningEpisode",
"HostHealthProbe", "PPTAuditResult",
]