diff --git a/database/ai_models.py b/database/ai_models.py index ed06e83..68e5a98 100644 --- a/database/ai_models.py +++ b/database/ai_models.py @@ -1,89 +1,125 @@ # database/ai_models.py -from sqlalchemy import Column, Integer, String, DateTime, Text, Float, ForeignKey, Index -from sqlalchemy.orm import relationship +# ⚠️ 這四個 class 的原始定義已移至 autoheal_models.py(ADR-013 統一管理)。 +# 此檔僅作向後相容 re-export shim,不再重複定義 SQLAlchemy Table, +# 以避免 "Table already defined for this MetaData instance" 衝突。 +from .autoheal_models import ( # noqa: F401 + AgentContext, + ActionPlan, + ActionOutcome, + AgentStrategyWeights, +) + +# AI history and template models +from sqlalchemy import Column, Integer, String, DateTime, Text, Boolean, Float from database.models import Base -from datetime import datetime, timezone +from datetime import datetime -# helper for default timestamps -datetime_now = lambda: datetime.now(timezone.utc) - - -class AgentContext(Base): +class AIGenerationHistory(Base): """ - Shared context table (replaces hardcoded chain), supporting multi-agent access and TTL. - Index: (session_id, agent_name, context_key) for fast cross-agent queries. + AI generation history tracking """ - __tablename__ = 'agent_context' + __tablename__ = 'ai_generation_history' id = Column(Integer, primary_key=True, autoincrement=True) - session_id = Column(String(64), nullable=False, index=True) - agent_name = Column(String(50), nullable=False, index=True) - context_key = Column(String(100), nullable=False) - context_val = Column(Text) # JSON string - created_at = Column(DateTime, default=datetime_now) - ttl_minutes = Column(Integer, default=60) + 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) - __table_args__ = ( - Index('idx_agent_context_session_key', 'session_id', 'agent_name', 'context_key'), - Index('idx_agent_context_session_ttl', 'session_id', 'created_at'), - ) + 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 ActionPlan(Base): +class AIPromptTemplate(Base): """ - Action plan table (NemoTron output, awaiting review/execution tracking). + AI prompt templates """ - __tablename__ = 'action_plans' + __tablename__ = 'ai_prompt_templates' id = Column(Integer, primary_key=True, autoincrement=True) - session_id = Column(String(64), nullable=True) - plan_type = Column(String(50), nullable=True) # price_adjust / restock / campaign - sku = Column(String(100), nullable=True, index=True) - payload = Column(Text) # JSON payload - status = Column(String(20), default='pending') # pending/approved/rejected/executed - created_by = Column(String(50)) # nemotron / openclaw - approved_by = Column(String(100), nullable=True) # Telegram user_id - created_at = Column(DateTime, default=datetime_now) - executed_at = Column(DateTime, nullable=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) - __table_args__ = ( - Index('idx_action_plan_sku_status', 'sku', 'status'), - Index('idx_action_plan_created', 'created_at'), - ) + 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 ActionOutcome(Base): +class AIUsageTracking(Base): """ - Action outcome tracking (closed-loop learning core). + AI usage tracking for analytics and billing """ - __tablename__ = 'action_outcomes' + __tablename__ = 'ai_usage_tracking' id = Column(Integer, primary_key=True, autoincrement=True) - plan_id = Column(Integer, ForeignKey('action_plans.id'), nullable=False) - metric_type = Column(String(50), nullable=True) # sales_7d / price_rank / conversion - before_val = Column(Float) - after_val = Column(Float) - measured_at = Column(DateTime) - verdict = Column(String(20)) # effective / neutral / backfired - created_at = Column(DateTime, default=datetime_now) + 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) - plan = relationship("ActionPlan", backref="outcomes") + 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 AgentStrategyWeights(Base): - """ - Agent strategy weights (OpenClaw learning accumulation). - Index: strategy_key for fast updates/query. - """ - __tablename__ = 'agent_strategy_weights' - - id = Column(Integer, primary_key=True, autoincrement=True) - strategy_key = Column(String(100), unique=True, nullable=False) # e.g. price_cut_when_gap_gt_5pct - weight = Column(Float, default=1.0) - success_cnt = Column(Integer, default=0) - fail_cnt = Column(Integer, default=0) - updated_at = Column(DateTime, default=datetime_now) - - __table_args__ = ( - Index('idx_strategy_key', 'strategy_key'), - ) +__all__ = [ + "AgentContext", "ActionPlan", "ActionOutcome", "AgentStrategyWeights", + "AIGenerationHistory", "AIPromptTemplate", "AIUsageTracking" +]