import uuid from django.conf import settings from django.db import models from pgvector.django import VectorField class KnowledgeBase(models.Model): name = models.CharField(max_length=160) description = models.TextField(blank=True) scope = models.CharField(max_length=120, blank=True) enabled = models.BooleanField(default=True) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ["-updated_at"] def __str__(self) -> str: return self.name class KnowledgeDocument(models.Model): public_id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False) knowledge_base = models.ForeignKey( KnowledgeBase, on_delete=models.CASCADE, related_name="documents", ) file_name = models.CharField(max_length=255) file_type = models.CharField(max_length=80, blank=True) extension = models.CharField(max_length=16, blank=True) mime_type = models.CharField(max_length=160, blank=True) file_size = models.PositiveBigIntegerField(default=0) sha256 = models.CharField(max_length=64, blank=True) storage_path = models.CharField(max_length=500, blank=True) content_text = models.TextField(blank=True) scan_status = models.CharField(max_length=40, default="pending") scan_result = models.JSONField(default=dict, blank=True) parse_status = models.CharField(max_length=40, default="pending") parse_error = models.TextField(blank=True) processing_attempts = models.PositiveSmallIntegerField(default=0) task_id = models.CharField(max_length=120, blank=True) indexed_at = models.DateTimeField(null=True, blank=True) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ["-created_at"] indexes = [ models.Index(fields=["knowledge_base", "parse_status", "created_at"]), models.Index(fields=["sha256"]), ] @property def has_original(self) -> bool: return bool(self.storage_path) class KnowledgeItem(models.Model): knowledge_base = models.ForeignKey( KnowledgeBase, on_delete=models.CASCADE, related_name="items", ) title = models.CharField(max_length=200) problem = models.TextField() applicable_scope = models.TextField(blank=True) category_code = models.CharField(max_length=80, blank=True) related_system = models.CharField(max_length=120, blank=True) environment_scope = models.CharField(max_length=120, blank=True) error_code = models.CharField(max_length=120, blank=True) root_cause = models.TextField(blank=True) troubleshooting_steps = models.TextField(blank=True) solution = models.TextField() risk_note = models.TextField(blank=True) status = models.CharField(max_length=40, default="draft") source_type = models.CharField(max_length=40, default="manual") source_ticket_id = models.BigIntegerField(null=True, blank=True) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name="created_knowledge_items", ) reviewed_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name="reviewed_knowledge_items", ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ["-updated_at"] class TroubleshootingPlan(models.Model): knowledge_base = models.ForeignKey( KnowledgeBase, on_delete=models.CASCADE, related_name="troubleshooting_plans", ) title = models.CharField(max_length=200) problem_pattern = models.TextField() applicable_scope = models.TextField(blank=True) category_code = models.CharField(max_length=80, blank=True) related_system = models.CharField(max_length=120, blank=True) environment_scope = models.CharField(max_length=120, blank=True) decision_rules = models.TextField(blank=True) troubleshooting_steps = models.TextField() risk_note = models.TextField(blank=True) status = models.CharField(max_length=40, default="draft") source_ticket = models.ForeignKey( "tickets.Ticket", null=True, blank=True, on_delete=models.SET_NULL, related_name="generated_troubleshooting_plans", ) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name="created_troubleshooting_plans", ) reviewed_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name="reviewed_troubleshooting_plans", ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ["-updated_at"] class GovernedAssetType(models.TextChoices): KNOWLEDGE_ITEM = "knowledge_item", "知识" TROUBLESHOOTING_PLAN = "troubleshooting_plan", "排查方案" SOLUTION = "solution", "解决方案" class GovernedAsset(models.Model): public_id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False) asset_type = models.CharField(max_length=40, choices=GovernedAssetType.choices) object_id = models.PositiveBigIntegerField() current_published_version = models.ForeignKey( "GovernedAssetVersion", null=True, blank=True, on_delete=models.SET_NULL, related_name="+", ) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name="created_governed_assets", ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: constraints = [ models.UniqueConstraint( fields=["asset_type", "object_id"], name="unique_governed_asset_object", ) ] indexes = [models.Index(fields=["asset_type", "object_id"])] class GovernedAssetVersionStatus(models.TextChoices): DRAFT = "draft", "草稿" PENDING_REVIEW = "pending_review", "待审核" REJECTED = "rejected", "已驳回" PUBLISHED = "published", "已发布" OFFLINE = "offline", "已下线" class GovernedAssetVersion(models.Model): asset = models.ForeignKey( GovernedAsset, on_delete=models.CASCADE, related_name="versions", ) version = models.PositiveIntegerField() status = models.CharField( max_length=40, choices=GovernedAssetVersionStatus.choices, default=GovernedAssetVersionStatus.DRAFT, ) content = models.JSONField(default=dict) change_summary = models.CharField(max_length=500, blank=True) source_version = models.ForeignKey( "self", null=True, blank=True, on_delete=models.SET_NULL, related_name="derived_versions", ) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name="created_asset_versions", ) submitted_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name="submitted_asset_versions", ) reviewed_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name="reviewed_asset_versions", ) review_comment = models.TextField(blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) submitted_at = models.DateTimeField(null=True, blank=True) reviewed_at = models.DateTimeField(null=True, blank=True) published_at = models.DateTimeField(null=True, blank=True) offline_at = models.DateTimeField(null=True, blank=True) class Meta: ordering = ["-version"] constraints = [ models.UniqueConstraint( fields=["asset", "version"], name="unique_governed_asset_version", ), models.UniqueConstraint( fields=["asset"], condition=models.Q(status=GovernedAssetVersionStatus.DRAFT), name="unique_draft_governed_asset_version", ), models.UniqueConstraint( fields=["asset"], condition=models.Q(status=GovernedAssetVersionStatus.PENDING_REVIEW), name="unique_pending_governed_asset_version", ), ] indexes = [ models.Index(fields=["status", "updated_at"]), models.Index(fields=["asset", "status", "version"]), ] class TicketKnowledgeDistillation(models.Model): ticket = models.ForeignKey( "tickets.Ticket", on_delete=models.CASCADE, related_name="knowledge_distillations", ) run = models.OneToOneField( "agents.AgentRun", on_delete=models.PROTECT, related_name="ticket_knowledge_distillation", ) references = models.JSONField(default=list) retrieval_policy = models.JSONField(default=dict) should_distill = models.BooleanField(null=True, blank=True) reason = models.TextField(blank=True) assessment = models.JSONField(default=dict, blank=True) knowledge_item = models.ForeignKey( KnowledgeItem, null=True, blank=True, on_delete=models.SET_NULL, related_name="distillation_records", ) troubleshooting_plan = models.ForeignKey( TroubleshootingPlan, null=True, blank=True, on_delete=models.SET_NULL, related_name="distillation_records", ) solution = models.ForeignKey( "solutions.Solution", null=True, blank=True, on_delete=models.SET_NULL, related_name="distillation_records", ) processed_at = models.DateTimeField(null=True, blank=True) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name="created_knowledge_distillations", ) created_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ["-created_at"] indexes = [models.Index(fields=["ticket", "created_at"])] class KnowledgeChunk(models.Model): knowledge_base = models.ForeignKey( KnowledgeBase, on_delete=models.CASCADE, related_name="chunks", ) document = models.ForeignKey( KnowledgeDocument, null=True, blank=True, on_delete=models.CASCADE, related_name="chunks", ) item = models.ForeignKey( KnowledgeItem, null=True, blank=True, on_delete=models.CASCADE, related_name="chunks", ) troubleshooting_plan = models.ForeignKey( TroubleshootingPlan, null=True, blank=True, on_delete=models.CASCADE, related_name="chunks", ) historical_ticket = models.ForeignKey( "tickets.Ticket", null=True, blank=True, on_delete=models.CASCADE, related_name="knowledge_chunks", ) chunk_index = models.PositiveIntegerField(default=0) content = models.TextField() embedding = models.JSONField(default=list, blank=True) embedding_vector = VectorField(dimensions=1536, null=True, blank=True) embedding_model = models.CharField(max_length=120, default="local-hash-v1") metadata = models.JSONField(default=dict, blank=True) created_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = [ "knowledge_base_id", "document_id", "item_id", "troubleshooting_plan_id", "historical_ticket_id", "chunk_index", ] indexes = [ models.Index(fields=["knowledge_base", "chunk_index"]), ]