From a46ceb94de9b0ad4ce9b9a684a6e48569280aab5 Mon Sep 17 00:00:00 2001 From: plf1996 Date: Thu, 18 Jun 2026 16:30:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E5=9B=BE=E8=B0=B1?= =?UTF-8?q?=E6=8A=BD=E5=8F=96=20=E2=80=94=20=E6=94=AF=E6=8C=81=E5=BB=BA?= =?UTF-8?q?=E7=AD=91=E8=A7=84=E8=8C=83=E6=96=87=E6=A1=A3=E7=9A=84=208=20?= =?UTF-8?q?=E7=A7=8D=E5=AE=9E=E4=BD=93=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实体类型改为:构件/设备/设施/指标/场所/材料/措施/条款 - 中文 prompt + 建筑规范领域 Few-shot 示例 - 前端图谱颜色、筛选项同步更新 - QA agent 系统提示词适配建筑规范领域 --- backend/pipeline/entity_extractor.py | 63 ++++++++++++++----- backend/pipeline/qa_agent.py | 31 +++++---- .../src/app/components/pages/KGExplorer.tsx | 2 +- .../src/app/components/pages/SearchPage.tsx | 2 +- frontend/src/app/mock-data.ts | 10 ++- frontend/src/app/store.tsx | 8 +-- 6 files changed, 77 insertions(+), 39 deletions(-) diff --git a/backend/pipeline/entity_extractor.py b/backend/pipeline/entity_extractor.py index 87cc98f..253dcbc 100644 --- a/backend/pipeline/entity_extractor.py +++ b/backend/pipeline/entity_extractor.py @@ -19,30 +19,61 @@ DEEPSEEK_BASE_URL = os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com") MODEL_ID = "deepseek-chat" PROMPT_DESCRIPTION = ( - "Extract named entities from the text in order of appearance. " - "Entity types: TECHNOLOGY (software, algorithms, models, tools), " - "ORGANIZATION (companies, research groups, institutions), " - "PERSON (individual people), " - "LOCATION (places, geographic entities), " - "CONCEPT (technical concepts, methodologies, frameworks)." + "从建筑规范文本中按出现顺序提取命名实体。" + "实体类型:" + "构件 — 建筑构件和部位(防火墙、防火门、楼梯间、电梯井、管道井、变形缝、外墙、屋面、楼板、梁、柱、承重墙、隔墙、门窗、幕墙、雨篷、阳台、走廊、前室、竖井等);" + "设备 — 消防设备和系统(消火栓、灭火器、喷淋系统、报警系统、防烟排烟系统、应急照明、疏散指示、防火卷帘、消防电梯、消防水箱、消防水泵、气体灭火系统、火灾自动报警系统、电气火灾监控系统等);" + "设施 — 建筑设施和配套(停车场、锅炉房、变配电室、发电机房、储油间、空调机房、通风机房、电梯机房、消防控制室、水泵房等);" + "指标 — 技术参数和量化要求(耐火等级、防火间距、疏散宽度、最大面积、高度限值、距离要求、时间要求、容量要求、温度限值、压力要求等);" + "场所 — 建筑分类和空间类型(高层建筑、地下建筑、公共建筑、住宅建筑、工业建筑、商业建筑、医疗建筑、教育建筑、娱乐场所、仓库、厂房、中庭、避难层、避难走道、敞开楼梯间等);" + "材料 — 建筑材料和产品(不燃材料、难燃材料、可燃材料、防火涂料、防火玻璃、防火密封件、保温材料、装饰材料、钢结构防火保护材料等);" + "措施 — 防火措施和策略(防火分隔、防火封堵、自然排烟、机械加压送风、机械排烟、安全疏散、防火保护、消防供电、消防水源、灭火救援等);" + "条款 — 规范条文引用(强制性条文、推荐性条文、术语定义、一般规定、基本要求等)。" ) EXAMPLES = [ lx.data.ExampleData( text=( - "LangChain is a framework created by Harrison Chase for building " - "LLM applications. It integrates with OpenAI models and Pinecone " - "vector database for semantic search." + "建筑高度大于100m的公共建筑应设置避难层。避难层应采用耐火极限不低于3.00h的防火墙和甲级防火门进行分隔。" + "建筑内的消防控制室应采用耐火极限不低于2.00h的防火隔墙与乙级防火门与其他部位分隔。" + "疏散走道两侧的隔墙应采用不燃材料,其耐火极限不应低于1.00h。" ), extractions=[ - lx.data.Extraction(extraction_class="TECHNOLOGY", extraction_text="LangChain"), - lx.data.Extraction(extraction_class="PERSON", extraction_text="Harrison Chase"), - lx.data.Extraction(extraction_class="CONCEPT", extraction_text="LLM applications"), - lx.data.Extraction(extraction_class="TECHNOLOGY", extraction_text="OpenAI models"), - lx.data.Extraction(extraction_class="TECHNOLOGY", extraction_text="Pinecone"), - lx.data.Extraction(extraction_class="CONCEPT", extraction_text="semantic search"), + lx.data.Extraction(extraction_class="场所", extraction_text="公共建筑"), + lx.data.Extraction(extraction_class="构件", extraction_text="避难层"), + lx.data.Extraction(extraction_class="指标", extraction_text="耐火极限不低于3.00h"), + lx.data.Extraction(extraction_class="构件", extraction_text="防火墙"), + lx.data.Extraction(extraction_class="构件", extraction_text="甲级防火门"), + lx.data.Extraction(extraction_class="设施", extraction_text="消防控制室"), + lx.data.Extraction(extraction_class="指标", extraction_text="耐火极限不低于2.00h"), + lx.data.Extraction(extraction_class="构件", extraction_text="防火隔墙"), + lx.data.Extraction(extraction_class="构件", extraction_text="乙级防火门"), + lx.data.Extraction(extraction_class="构件", extraction_text="疏散走道"), + lx.data.Extraction(extraction_class="构件", extraction_text="隔墙"), + lx.data.Extraction(extraction_class="材料", extraction_text="不燃材料"), + lx.data.Extraction(extraction_class="指标", extraction_text="耐火极限不应低于1.00h"), ], - ) + ), + lx.data.ExampleData( + text=( + "住宅建筑室内消火栓设计流量不应小于5L/s。高位消防水箱有效容积不应小于18m3," + "且应设置在建筑最高部位。消防电梯前室应设置室内消火栓。" + "公共建筑内每个防火分区的安全出口不应少于2个。" + ), + extractions=[ + lx.data.Extraction(extraction_class="场所", extraction_text="住宅建筑"), + lx.data.Extraction(extraction_class="设备", extraction_text="室内消火栓"), + lx.data.Extraction(extraction_class="指标", extraction_text="不应小于5L/s"), + lx.data.Extraction(extraction_class="设备", extraction_text="高位消防水箱"), + lx.data.Extraction(extraction_class="指标", extraction_text="不应小于18m3"), + lx.data.Extraction(extraction_class="构件", extraction_text="消防电梯前室"), + lx.data.Extraction(extraction_class="设备", extraction_text="室内消火栓"), + lx.data.Extraction(extraction_class="场所", extraction_text="公共建筑"), + lx.data.Extraction(extraction_class="构件", extraction_text="防火分区"), + lx.data.Extraction(extraction_class="构件", extraction_text="安全出口"), + lx.data.Extraction(extraction_class="指标", extraction_text="不应少于2个"), + ], + ), ] diff --git a/backend/pipeline/qa_agent.py b/backend/pipeline/qa_agent.py index 3b766ca..043deb1 100644 --- a/backend/pipeline/qa_agent.py +++ b/backend/pipeline/qa_agent.py @@ -83,17 +83,14 @@ def make_tools(G: nx.Graph) -> list: def get_entities_by_type(entity_type: str) -> str: """List all entities of a specific type. Args: - entity_type: TECHNOLOGY, CONCEPT, PERSON, ORGANIZATION, or LOCATION. + entity_type: 构件, 设备, 设施, 指标, 场所, 材料, 措施, 条款, etc. """ - t_upper = entity_type.strip().upper() - valid = {"TECHNOLOGY", "CONCEPT", "PERSON", "ORGANIZATION", "LOCATION"} - if t_upper not in valid: - present = sorted({d.get("type","") for _, d in G.nodes(data=True)}) - return f"Unknown type '{entity_type}'. Present: {present}" - matches = [d for _, d in G.nodes(data=True) if d.get("type","") == t_upper] + t = entity_type.strip() + matches = [d for _, d in G.nodes(data=True) if d.get("type","") == t] if not matches: - return f"No {t_upper} entities found." - lines = [f"Found {len(matches)} {t_upper} entities:"] + present = sorted({d.get("type","") for _, d in G.nodes(data=True)}) + return f"No '{entity_type}' entities found. Present types: {present}" + lines = [f"Found {len(matches)} {t} entities:"] for m in matches[:30]: lines.append(f" \"{m['name']}\" (page={m.get('page',0)}, id={m['id']})") if len(matches) > 30: @@ -148,15 +145,17 @@ def run_qa( ) system_prompt = ( - "You are a helpful assistant with access to a knowledge graph (KG) built from the user's documents.\n" + "You are a helpful assistant with access to a knowledge graph (KG) built from building code documents.\n" + "The KG contains entities extracted from building fire protection codes (建筑防火规范).\n" + "\n" + "Entity types include: 构件 (building components), 设备 (fire equipment), 设施 (facilities),\n" + "指标 (technical parameters), 场所 (building types/spaces), 材料 (materials),\n" + "措施 (fire measures), 条款 (regulation clauses).\n" "\n" "Guidelines:\n" - "- If the question is clearly unrelated to the KG (greetings, math, general knowledge, etc.), " - "answer directly WITHOUT using any tools.\n" - "- If the question might be answered by the KG (topics related to entities in the documents), " - "use the tools to search and explore before answering.\n" - "- When you DO use the KG, cite the entity names and types you found.\n" - "- If the KG has no relevant information, say so honestly and answer from general knowledge if possible.\n" + "- Use tools to search and explore the KG before answering.\n" + "- Cite the entity names and types you found.\n" + "- If the KG has no relevant information, say so honestly.\n" "\n" "Available tools: search entities by name, get neighbors, list entities by type, get graph overview." ) diff --git a/frontend/src/app/components/pages/KGExplorer.tsx b/frontend/src/app/components/pages/KGExplorer.tsx index c5b3e84..e116647 100644 --- a/frontend/src/app/components/pages/KGExplorer.tsx +++ b/frontend/src/app/components/pages/KGExplorer.tsx @@ -5,7 +5,7 @@ import { ZoomIn, ZoomOut, Maximize2, Search, Download, Image, X, MessageSquare, import { useAppState, type KGNode } from '../../store'; import { TYPE_COLORS } from '../../mock-data'; -const ENTITY_TYPES = ['TECHNOLOGY', 'CONCEPT', 'PERSON', 'ORGANIZATION', 'LOCATION'] as const; +const ENTITY_TYPES = ['构件', '设备', '设施', '指标', '场所', '材料', '措施', '条款', 'TECHNOLOGY', 'CONCEPT', 'PERSON', 'ORGANIZATION', 'LOCATION'] as const; const CONFIDENCE_LEVELS = ['match_exact', 'match_greater', 'match_lesser', 'match_fuzzy'] as const; export function KGExplorer() { diff --git a/frontend/src/app/components/pages/SearchPage.tsx b/frontend/src/app/components/pages/SearchPage.tsx index 191244b..1f56ae3 100644 --- a/frontend/src/app/components/pages/SearchPage.tsx +++ b/frontend/src/app/components/pages/SearchPage.tsx @@ -6,7 +6,7 @@ import { useAppState, mapApiNode, mapApiEdge, type KGNode } from '../../store'; import { api, ApiError } from '../../api'; import { TYPE_COLORS } from '../../mock-data'; -const ENTITY_TYPES_OPTIONS = ['全部类型', 'TECHNOLOGY', 'CONCEPT', 'PERSON', 'ORGANIZATION', 'LOCATION']; +const ENTITY_TYPES_OPTIONS = ['全部类型', '构件', '设备', '设施', '指标', '场所', '材料', '措施', '条款', 'TECHNOLOGY', 'CONCEPT', 'PERSON', 'ORGANIZATION', 'LOCATION']; export function SearchPage() { const { nodes, edges, getNeighbors } = useAppState(); diff --git a/frontend/src/app/mock-data.ts b/frontend/src/app/mock-data.ts index 8703d8a..9222b74 100644 --- a/frontend/src/app/mock-data.ts +++ b/frontend/src/app/mock-data.ts @@ -3,7 +3,7 @@ export interface KGNode { id: string; name: string; - type: 'TECHNOLOGY' | 'CONCEPT' | 'PERSON' | 'ORGANIZATION' | 'LOCATION'; + type: '构件' | '设备' | '设施' | '指标' | '场所' | '材料' | '措施' | '条款' | 'TECHNOLOGY' | 'CONCEPT' | 'PERSON' | 'ORGANIZATION' | 'LOCATION'; page: number; confidence: 'match_exact' | 'match_greater' | 'match_lesser' | 'match_fuzzy'; degree: number; @@ -66,6 +66,14 @@ export interface HistoryItem { // Entity type colors export const TYPE_COLORS: Record = { + '构件': '#58a6ff', + '设备': '#ff7b72', + '设施': '#d29922', + '指标': '#3fb950', + '场所': '#bc8cff', + '材料': '#ffa657', + '措施': '#f0f6fc', + '条款': '#8b949e', TECHNOLOGY: '#58a6ff', CONCEPT: '#bc8cff', PERSON: '#3fb950', diff --git a/frontend/src/app/store.tsx b/frontend/src/app/store.tsx index 00969dd..227d53a 100644 --- a/frontend/src/app/store.tsx +++ b/frontend/src/app/store.tsx @@ -6,7 +6,7 @@ import { api, type ApiDoc, type ApiKGNode, type ApiKGEdge, ApiError } from './ap export interface KGNode { id: string; name: string; - type: 'TECHNOLOGY' | 'CONCEPT' | 'PERSON' | 'ORGANIZATION' | 'LOCATION'; + type: '构件' | '设备' | '设施' | '指标' | '场所' | '材料' | '措施' | '条款' | 'TECHNOLOGY' | 'CONCEPT' | 'PERSON' | 'ORGANIZATION' | 'LOCATION'; page: number; confidence: 'match_exact' | 'match_greater' | 'match_lesser' | 'match_fuzzy'; degree: number; @@ -169,9 +169,9 @@ const DEFAULT_STATS: StatsData = { kg_nodes: 0, kg_edges: 0, documents: 0, queri const SUGGESTED_PROMPTS = [ '给我一个知识图谱的概览', - '列出所有 TECHNOLOGY 实体', - 'GraphRAG 与知识图谱有什么关系?', - '什么是检索增强生成?', + '列出所有 构件 实体', + '列出所有 设备 实体', + '文档中有哪些防火措施?', ]; // ─── Provider ─────────────────────────────────────────────────────────────────