# Auto-extracted from chat.py during Phase 1 refactor (Strangler Fig). # Source of truth: scripts/phase1_extract.py. Do not hand-merge back. from fastapi import APIRouter, Depends, HTTPException, Query, Response from sqlalchemy.orm import Session from sqlalchemy import and_, desc, asc, func from pydantic import BaseModel, Field from typing import Optional import uuid from datetime import datetime, timezone, timedelta import re import json import asyncio import math import httpx import os import logging from ..database import get_db, SessionLocal from ..models import ( User, Conversation, Message, AIModel, ModelProvider, TokenUsageLog, KnowledgeBase, KnowledgeDocument, KnowledgeDocumentChunk, EvolutionSkill, EvolutionTrajectory, MarketApp, AppPurchase, AppInstallation, MCPServer, gen_uuid ) from ..auth import get_current_user from ..platform_knowledge import get_platform_knowledge from ..mcp_client import list_mcp_tools, call_mcp_tool from ..middleware.log_service import LogService from ..capability_registry import resolve_auto_model from app.api.chat_utils import (cst_now) def _build_knowledge_graph(db: Session, kb: KnowledgeBase) -> dict: """从知识库文档构建知识图谱节点和边,返回 graph_data。""" documents = db.query(KnowledgeDocument).filter( and_( KnowledgeDocument.knowledge_base_id == kb.id, KnowledgeDocument.status == "completed", ) ).all() graph_nodes = [] graph_edges = [] doc_entity_types = [ {"type": "concept", "label": "核心概念"}, {"type": "entity", "label": "关键实体"}, {"type": "attribute", "label": "属性特征"}, {"type": "relation", "label": "关联关系"}, ] for idx, doc in enumerate(documents): doc_node_id = f"doc-{doc.id[:8]}" graph_nodes.append({ "id": doc_node_id, "label": doc.filename, "type": "document", "size": 18, "group": "document", }) filename_base = doc.filename.rsplit(".", 1)[0] words = filename_base.replace("_", " ").replace("-", " ").split() for wi, word in enumerate(words): if len(word) >= 2: entity_type = doc_entity_types[wi % len(doc_entity_types)] node_id = f"ent-{doc.id[:8]}-{wi}" graph_nodes.append({ "id": node_id, "label": word, "type": entity_type["type"], "size": 10 + (wi % 8), "group": entity_type["type"], }) graph_edges.append({ "source": doc_node_id, "target": node_id, "label": entity_type["label"], "weight": 0.5 + (0.5 / (wi + 1)), }) for i in range(len(documents)): for j in range(i + 1, len(documents)): fi = documents[i].filename.lower().rsplit(".", 1)[0] fj = documents[j].filename.lower().rsplit(".", 1)[0] words_i = set(fi.replace("_", " ").replace("-", " ").split()) words_j = set(fj.replace("_", " ").replace("-", " ").split()) common = words_i & words_j if common and len(common) > 0: graph_edges.append({ "source": f"doc-{documents[i].id[:8]}", "target": f"doc-{documents[j].id[:8]}", "label": f"共有关键词: {', '.join(list(common)[:3])}", "weight": min(0.9, 0.3 * len(common)), }) graph_data = { "nodes": graph_nodes, "edges": graph_edges, "stats": { "total_nodes": len(graph_nodes), "total_edges": len(graph_edges), "document_count": len(documents), }, } kb.knowledge_graph_count = len(graph_nodes) kb.updated_at = cst_now() return graph_data