feat: 重构图谱抽取 — 支持建筑规范文档的 8 种实体类型
GraphRAG CI/CD / build-and-deploy (push) Successful in 6m15s

- 实体类型改为:构件/设备/设施/指标/场所/材料/措施/条款
- 中文 prompt + 建筑规范领域 Few-shot 示例
- 前端图谱颜色、筛选项同步更新
- QA agent 系统提示词适配建筑规范领域
This commit is contained in:
2026-06-18 16:30:58 +08:00
parent 4f203e3b56
commit a46ceb94de
6 changed files with 77 additions and 39 deletions
+47 -16
View File
@@ -19,30 +19,61 @@ DEEPSEEK_BASE_URL = os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com")
MODEL_ID = "deepseek-chat" MODEL_ID = "deepseek-chat"
PROMPT_DESCRIPTION = ( 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 = [ EXAMPLES = [
lx.data.ExampleData( lx.data.ExampleData(
text=( text=(
"LangChain is a framework created by Harrison Chase for building " "建筑高度大于100m的公共建筑应设置避难层。避难层应采用耐火极限不低于3.00h的防火墙和甲级防火门进行分隔。"
"LLM applications. It integrates with OpenAI models and Pinecone " "建筑内的消防控制室应采用耐火极限不低于2.00h的防火隔墙与乙级防火门与其他部位分隔。"
"vector database for semantic search." "疏散走道两侧的隔墙应采用不燃材料,其耐火极限不应低于1.00h。"
), ),
extractions=[ extractions=[
lx.data.Extraction(extraction_class="TECHNOLOGY", extraction_text="LangChain"), lx.data.Extraction(extraction_class="场所", extraction_text="公共建筑"),
lx.data.Extraction(extraction_class="PERSON", extraction_text="Harrison Chase"), lx.data.Extraction(extraction_class="构件", extraction_text="避难层"),
lx.data.Extraction(extraction_class="CONCEPT", extraction_text="LLM applications"), lx.data.Extraction(extraction_class="指标", extraction_text="耐火极限不低于3.00h"),
lx.data.Extraction(extraction_class="TECHNOLOGY", extraction_text="OpenAI models"), lx.data.Extraction(extraction_class="构件", extraction_text="防火墙"),
lx.data.Extraction(extraction_class="TECHNOLOGY", extraction_text="Pinecone"), lx.data.Extraction(extraction_class="构件", extraction_text="甲级防火门"),
lx.data.Extraction(extraction_class="CONCEPT", extraction_text="semantic search"), 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个"),
],
),
] ]
+15 -16
View File
@@ -83,17 +83,14 @@ def make_tools(G: nx.Graph) -> list:
def get_entities_by_type(entity_type: str) -> str: def get_entities_by_type(entity_type: str) -> str:
"""List all entities of a specific type. """List all entities of a specific type.
Args: Args:
entity_type: TECHNOLOGY, CONCEPT, PERSON, ORGANIZATION, or LOCATION. entity_type: 构件, 设备, 设施, 指标, 场所, 材料, 措施, 条款, etc.
""" """
t_upper = entity_type.strip().upper() t = entity_type.strip()
valid = {"TECHNOLOGY", "CONCEPT", "PERSON", "ORGANIZATION", "LOCATION"} matches = [d for _, d in G.nodes(data=True) if d.get("type","") == t]
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]
if not matches: if not matches:
return f"No {t_upper} entities found." present = sorted({d.get("type","") for _, d in G.nodes(data=True)})
lines = [f"Found {len(matches)} {t_upper} entities:"] return f"No '{entity_type}' entities found. Present types: {present}"
lines = [f"Found {len(matches)} {t} entities:"]
for m in matches[:30]: for m in matches[:30]:
lines.append(f" \"{m['name']}\" (page={m.get('page',0)}, id={m['id']})") lines.append(f" \"{m['name']}\" (page={m.get('page',0)}, id={m['id']})")
if len(matches) > 30: if len(matches) > 30:
@@ -148,15 +145,17 @@ def run_qa(
) )
system_prompt = ( 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" "\n"
"Guidelines:\n" "Guidelines:\n"
"- If the question is clearly unrelated to the KG (greetings, math, general knowledge, etc.), " "- Use tools to search and explore the KG before answering.\n"
"answer directly WITHOUT using any tools.\n" "- Cite the entity names and types you found.\n"
"- If the question might be answered by the KG (topics related to entities in the documents), " "- If the KG has no relevant information, say so honestly.\n"
"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"
"\n" "\n"
"Available tools: search entities by name, get neighbors, list entities by type, get graph overview." "Available tools: search entities by name, get neighbors, list entities by type, get graph overview."
) )
@@ -5,7 +5,7 @@ import { ZoomIn, ZoomOut, Maximize2, Search, Download, Image, X, MessageSquare,
import { useAppState, type KGNode } from '../../store'; import { useAppState, type KGNode } from '../../store';
import { TYPE_COLORS } from '../../mock-data'; 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; const CONFIDENCE_LEVELS = ['match_exact', 'match_greater', 'match_lesser', 'match_fuzzy'] as const;
export function KGExplorer() { export function KGExplorer() {
@@ -6,7 +6,7 @@ import { useAppState, mapApiNode, mapApiEdge, type KGNode } from '../../store';
import { api, ApiError } from '../../api'; import { api, ApiError } from '../../api';
import { TYPE_COLORS } from '../../mock-data'; 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() { export function SearchPage() {
const { nodes, edges, getNeighbors } = useAppState(); const { nodes, edges, getNeighbors } = useAppState();
+9 -1
View File
@@ -3,7 +3,7 @@
export interface KGNode { export interface KGNode {
id: string; id: string;
name: string; name: string;
type: 'TECHNOLOGY' | 'CONCEPT' | 'PERSON' | 'ORGANIZATION' | 'LOCATION'; type: '构件' | '设备' | '设施' | '指标' | '场所' | '材料' | '措施' | '条款' | 'TECHNOLOGY' | 'CONCEPT' | 'PERSON' | 'ORGANIZATION' | 'LOCATION';
page: number; page: number;
confidence: 'match_exact' | 'match_greater' | 'match_lesser' | 'match_fuzzy'; confidence: 'match_exact' | 'match_greater' | 'match_lesser' | 'match_fuzzy';
degree: number; degree: number;
@@ -66,6 +66,14 @@ export interface HistoryItem {
// Entity type colors // Entity type colors
export const TYPE_COLORS: Record<string, string> = { export const TYPE_COLORS: Record<string, string> = {
'构件': '#58a6ff',
'设备': '#ff7b72',
'设施': '#d29922',
'指标': '#3fb950',
'场所': '#bc8cff',
'材料': '#ffa657',
'措施': '#f0f6fc',
'条款': '#8b949e',
TECHNOLOGY: '#58a6ff', TECHNOLOGY: '#58a6ff',
CONCEPT: '#bc8cff', CONCEPT: '#bc8cff',
PERSON: '#3fb950', PERSON: '#3fb950',
+4 -4
View File
@@ -6,7 +6,7 @@ import { api, type ApiDoc, type ApiKGNode, type ApiKGEdge, ApiError } from './ap
export interface KGNode { export interface KGNode {
id: string; id: string;
name: string; name: string;
type: 'TECHNOLOGY' | 'CONCEPT' | 'PERSON' | 'ORGANIZATION' | 'LOCATION'; type: '构件' | '设备' | '设施' | '指标' | '场所' | '材料' | '措施' | '条款' | 'TECHNOLOGY' | 'CONCEPT' | 'PERSON' | 'ORGANIZATION' | 'LOCATION';
page: number; page: number;
confidence: 'match_exact' | 'match_greater' | 'match_lesser' | 'match_fuzzy'; confidence: 'match_exact' | 'match_greater' | 'match_lesser' | 'match_fuzzy';
degree: number; degree: number;
@@ -169,9 +169,9 @@ const DEFAULT_STATS: StatsData = { kg_nodes: 0, kg_edges: 0, documents: 0, queri
const SUGGESTED_PROMPTS = [ const SUGGESTED_PROMPTS = [
'给我一个知识图谱的概览', '给我一个知识图谱的概览',
'列出所有 TECHNOLOGY 实体', '列出所有 构件 实体',
'GraphRAG 与知识图谱有什么关系?', '列出所有 设备 实体',
'什么是检索增强生成', '文档中有哪些防火措施',
]; ];
// ─── Provider ───────────────────────────────────────────────────────────────── // ─── Provider ─────────────────────────────────────────────────────────────────