kgrag/graph_search/init_prompts.py
2026-06-30 13:35:52 +08:00

660 lines
40 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Prompt模板
功能创建Cypher生成、验证、校正的prompt模板
"""
from langchain_core.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate
from neo4j import Driver
# ========== Cypher生成prompt带 CoT、Few-shot 和 Schema 路径模板) ==========
generate_cypher_prompt = ChatPromptTemplate.from_messages([
SystemMessagePromptTemplate.from_template(
"你是一个严格的 Cypher 明细查询生成器,用于返回具体记录(非聚合)。必须 100% 遵守以下规则。\n\n"
"### ⚠️ 绝对禁止(出现即错误)\n"
"❌ 使用有向关系(如 `->`),必须写成 `-[:REL]-`\n"
"❌ 使用 schema 未定义的标签、关系或属性名\n"
"❌ 使用 `OPTIONAL MATCH`、`UNWIND`、子查询、`CALL` 或多个 `MATCH`\n"
"❌ 使用 `WITH`(除非用于 `DISTINCT` 去重,且仅当必要时)\n"
"❌ 返回非 `... AS result` 的别名(如 `AS data`、`AS details`\n"
"❌ 使用聚合函数(如 count, sum, avg——本类查询只返回明细记录\n"
"❌ 节点未绑定变量(例如 `(:设备)` 必须写成 `(e:设备)`\n\n"
"### ✅ 必须遵守\n"
"✅ 所有节点在 MATCH 中必须显式声明别名(变量),即使不直接使用(如 `(s:舰艇)`, `(sys:系统)`\n"
"✅ 允许使用时间属性(如 fault_time、发生时间和日期条件如 = '2024-01-01'\n"
"✅ 仅当用户明确提到实体名称(如\"发动机\")时,才使用 `名称 IN [...]`\n"
"✅ 所有条件(包括时间条件)必须用 AND 连接,放在单一 WHERE 子句中\n"
"✅ **优先使用少跳数完成查询。若单节点即可满足条件,不要强行连接其他节点。**\n"
"✅ 返回格式:`RETURN DISTINCT node AS result`node 必须是已绑定的变量)\n"
"✅ 若涉及多个节点,返回用户关心的主目标节点(如“故障”、“监测记录”)的属性\n\n"
"### Schema唯一权威不得推测\n{schema}\n\n"
"### 生成步骤\n"
"1. 判断是否提及具体实体 → 决定是否用 `名称 IN [...]`\n"
"2. 从 schema 提取相关标签/关系/属性(包括时间属性)\n"
"3. 构建无向路径,**为每个节点分配清晰别名**(如 s, sys, e, f\n"
"4. 写单一 MATCH + 单一 WHERE可含时间条件\n"
"5. RETURN ... AS result\n\n"
"### 正确示例\n"
"• 发动机在2024年的所有故障详情\n"
" ✅ MATCH (start_e:设备 {{名称: '主发动机'}})-[:包含*0..8]-(real_e:设备)-[:发生故障使用维修工作]-(w:维修工作)-[:包含故障]-(f:故障模式) WHERE f.发生时间 >= '2024-01-01' AND f.发生时间 <= '2024-12-31' RETURN DISTINCT f AS result\n\n"
"• 主发动机可以实现什么功能:\n"
" ✅ MATCH (start_e:设备 {{名称: '主发动机'}})-[:实现]-(p:功能) RETURN DISTINCT p AS result\n\n"
"• 主发动机的组成:\n"
" ✅ MATCH (start_e:设备 {{名称: '主发动机'}})-[:包含]-(p:组成) RETURN DISTINCT p AS result\n\n"
"• 某型舰艇的所有故障:\n"
" ✅ MATCH (s:舰艇 {{名称: '某型舰艇'}})-[:包含]-(sys:系统)-[:包含]-(sub_sys:子系统)-[:包含*1..5]-(e:设备)-[:发生故障使用维修工作]-(w:维修工作)-[:包含故障]-(f:故障模式) RETURN DISTINCT f AS result\n\n"
"• 主发动机发生排烟温度过高如何维修:"
" ✅ MATCH (start_e:设备 {{名称: '主发动机'}})-[:包含*0..8]-(real_e:设备)-[:发生故障使用维修工作]-(w:维修工作)-[:包含故障]-(f:故障模式{{名称: '排烟温度过高'}})-[:修理]-(p:维修项目) RETURN DISTINCT p AS result\n\n"
"### ❌ 典型错误(绝对不要做)\n"
"• MATCH (:舰艇 {{名称: 'X'}})-[:包含]-(:系统) → 节点未绑定别名\n"
"• RETURN f → 未使用 AS result\n"
"• MATCH (e:设备), (f:故障) WHERE ... → 多个独立 MATCH\n"
"• 使用 OPTIONAL MATCH → 违反结构限制\n"
"• 关系写成 --> 或 <-- → 使用了有向关系\n"
"• 返回未绑定的变量(如 fault→ 变量必须在 MATCH 中声明\n\n"
"→ 注意:本查询类型**允许时间字段**,但依然禁止聚合操作。"
),
HumanMessagePromptTemplate.from_template(
"【重要】再次提醒:\n"
"- **每个节点必须绑定别名**,例如 `(s:舰艇)` 而非 `(:舰艇)`\n"
"- **允许使用时间字段和日期条件**(如 fault_time, '2024-01-01'\n"
"- **严禁使用聚合函数**(如 count——这是明细查询\n"
"- 关系必须无方向(用 `-[:REL]-`\n"
"- 返回必须是 `... AS result`\n"
"- 仅一个 MATCH无 OPTIONAL MATCH / UNWIND / CALL\n"
"- 可使用 ORDER BY 和 LIMIT如用户要求“最近”“最新”\n\n"
"入口节点NodeLabel: [name1, name2, ...]:\n{entry_nodes}\n\n"
"用户问题:\n{query}\n\n"
"→ 请直接输出【完整 Cypher 语句】,不要解释,不要注释。"
),
])
generate_aggregate_cypher_prompt = ChatPromptTemplate.from_messages([
SystemMessagePromptTemplate.from_template(
"你是一个严格的 Cypher 统计查询生成器,仅处理**不含任何时间信息**的聚合问题。必须 100% 遵守以下规则。\n\n"
"### ⚠️ 绝对禁止(出现即错误)\n"
"❌ 使用任何时间属性(如 fault_time、发生时间或日期字符串'2024'\n"
"❌ 使用有向关系(如 `->`),必须写成 `-[:REL]-`\n"
"❌ 使用 schema 未定义的标签、关系或属性名\n"
"❌ 使用 `OPTIONAL MATCH`、`UNWIND`、子查询、`CALL` 或多个 `MATCH`\n"
"❌ 使用 `WITH`(除非用于 `DISTINCT` 去重,且仅当必要时)\n"
"❌ 返回非 `... AS result` 的别名(如 `AS total`、`AS count_f`\n\n"
"### ✅ 必须遵守\n"
"✅ 所有节点在 MATCH 中必须显式声明别名(变量),即使不直接使用(如 `(s:舰艇)`, `(sys:系统)`\n"
"✅ 仅当用户明确提到实体名称(如\"发动机\")时,才使用 `名称 IN [...]`\n"
"✅ 否则(如“总共有多少故障?”),直接 MATCH 目标标签,忽略入口节点\n"
"✅ 所有条件(如 severity = 'high')必须用 AND 连接,放在单一 WHERE 子句中\n"
"✅ 路径最多 5 跳;若无法连接,退化为单节点匹配\n"
"✅ **优先使用少跳数完成查询。若单节点即可满足条件(如统计所有故障或查某属性),不要强行连接其他节点。**\n"
"✅ 返回格式:`RETURN agg_expr AS result`\n\n"
"### Schema唯一权威不得推测\n{schema}\n\n"
"### 生成步骤\n"
"1. 判断是否提及具体实体 → 决定是否用 `名称 IN [...]`\n"
"2. 从 schema 提取相关标签/关系/非时间属性\n"
"3. 构建 ≤5 跳无向路径\n"
"4. 写单一 MATCH + 单一 WHERE仅非时间条件\n"
"5. RETURN ... AS result\n\n"
"### 正确示例\n"
"• 主发动机的故障总数:\n"
" ✅ MATCH (start_e:设备 {{名称: '主发动机'}})-[:包含*0..8]-(real_e:设备)-[:发生故障使用维修工作]-(w:维修工作)-[:包含故障]-(f:故障模式) RETURN count(DISTINCT f) AS result\n\n"
"• 发动机的故障总数:\n"
" ✅ MATCH (e:设备 {{名称: '发动机'}})-[:导致]-(f:故障模式) RETURN count(DISTINCT f) AS result\n\n"
"• 技术状态为\"在役\"的设备数量:\n"
" ✅ MATCH (e:设备) WHERE e.技术状态 = '在役' RETURN count(DISTINCT e) AS result\n\n"
"• 螺旋桨和发动机的故障总数:\n"
" ✅ MATCH (e:设备)-[:包含*0..8]-(real_e:设备)-[:发生故障使用维修工作]-(w:维修工作)-[:包含故障]-(f:故障模式) WHERE e.名称 IN ['螺旋桨', '发动机'] RETURN count(DISTINCT f) AS result\n\n"
"### ❌ 典型错误(绝对不要做)\n"
"• WHERE f.发生时间 > '2024' → 包含时间字段(本类查询严禁!)\n"
"• RETURN count(f) AS total → 别名不是 'result'\n"
"• MATCH (e:设备 {{名称: '发动机'}}), (f:故障) → 用了两个独立 MATCH\n"
"• 使用 OPTIONAL MATCH → 违反结构限制\n\n"
"→ 若问题隐含时间(如“最近”“今年”),**不要生成此类型查询**,应交由时间类 prompt 处理。"
),
HumanMessagePromptTemplate.from_template(
"【重要】再次提醒:\n"
"- **每个节点必须绑定别名**,例如 `(s:舰艇)` 而非 `(:舰艇)`\n"
"- 关系必须无方向!\n"
"- 返回必须是 `... AS result`\n"
"- 仅一个 MATCH无 WITH/OPTIONAL MATCH\n\n"
"入口节点NodeLabel: [name1, name2, ...]:\n{entry_nodes}\n\n"
"用户问题:\n{query}\n\n"
"→ 请直接输出【完整 Cypher 语句】,不要解释,不要注释。"
),
])
generate_single_time_aggregate_cypher_prompt = ChatPromptTemplate.from_messages([
SystemMessagePromptTemplate.from_template(
"你是一个严格的单时间段统计 Cypher 生成器。仅处理含**一个明确时间范围**的计数问题如“2024年”“本月”“截至某日”。必须 100% 遵守以下规则。\n\n"
"### ⚠️ 绝对禁止(出现即错误)\n"
"❌ 使用任何时间函数(如 datetime(), date(), timestamp()\n"
"❌ 使用有向关系(如 `->`),必须写成 `-[:REL]-`\n"
"❌ 使用 schema 未定义的属性名(如写成 'time' 而非 'fault_time'\n"
"❌ 使用多个 MATCH、OPTIONAL MATCH、WITH、UNWIND 或子查询\n"
"❌ 返回非 `count(...) AS result` 的表达式\n\n"
"### ✅ 必须遵守\n"
"✅ 时间比较必须使用字符串字面量,格式如 `'2024-01-01'`\n"
"✅ 年/月区间必须为左闭右开:\n"
"\"2024年\" → >= '2024-01-01' AND < '2025-01-01'\n"
"\"2024年6月\" → >= '2024-06-01' AND < '2024-07-01'\n"
"\"截至2024年底\" → <= '2024-12-31'(仅此情况用 ≤)\n"
"✅ 相对时间映射(系统时间:{current_date_info}\n"
"\"今年\" → >= '{current_year}-01-01' AND < '{next_year}-01-01'\n"
"\"本月\" → >= '{current_year}-{current_month:02d}-01' AND < '{next_month_start}'\n"
"✅ 仅当用户明确提到实体名称时,才使用 `名称 IN [...]`\n"
"✅ 路径最多 5 跳;否则退化为单节点\n"
"✅ **优先使用少跳数完成查询。若单节点即可满足条件(如统计所有故障或查某属性),不要强行连接其他节点。**\n"
"✅ 返回格式:`RETURN count(x) AS result`\n\n"
"### Schema唯一权威\n{schema}\n\n"
"### 生成步骤\n"
"1. 判断是否使用入口节点(仅当明确提及实体名)\n"
"2. 从 schema 提取正确的时间属性名(如 fault_time\n"
"3. 将时间语义解析为精确字符串区间\n"
"4. 构建 ≤5 跳无向路径\n"
"5. 写单一 MATCH + 单一 WHERE含时间 + 其他非时间条件)\n"
"6. RETURN count(...) AS result\n\n"
"### 正确示例\n"
"• 2024年维修工作总数\n"
" ✅ MATCH (f:维修工作) WHERE f.发生时间 >= '2024-01-01' AND f.发生时间 < '2025-01-01' RETURN count(DISTINCT f) AS result\n\n"
"• 截至2024年底的累计故障次数\n"
" ✅ MATCH (f:故障模式) WHERE f.发生时间 <= '2024-12-31' RETURN count(DISTINCT f) AS result\n\n"
"### ❌ 典型错误(绝对不要做)\n"
"• WHERE datetime(f.发生时间) > '2024' → 用了函数\n"
"• WHERE f.发生时间 >= '2024-01-01' AND f.发生时间 <= '2024-12-31' → 年区间应为 < '2025-01-01'\n"
"• RETURN count(f) → 缺少 AS result\n"
"• 使用 (e:设备 {{名称: '发动机'}}), (r:修理) → 多个独立 MATCH\n\n"
"→ 若时间描述模糊(如“最近”),优先按“截至今日”处理,但必须用字符串!"
),
HumanMessagePromptTemplate.from_template(
"【重要】再次提醒:\n"
"- **每个节点必须绑定别名**,例如 `(s:舰艇)` 而非 `(:舰艇)`\n"
"- 时间必须用字符串字面量(如 '2025-01-01'),禁用任何函数!\n"
"- 年/月区间必须左闭右开(< 下一年/月)!\n"
"- 关系无方向,仅一个 MATCH\n"
"- 返回必须是 `count(...) AS result`\n\n"
"入口节点NodeLabel: [name1, name2, ...]:\n{entry_nodes}\n\n"
"用户问题:\n{query}\n\n"
"系统时间信息:\n{current_date_info}\n\n"
"→ 请直接输出【完整 Cypher 语句】,不要解释,不要注释。"
),
])
# ========== 双时间段统计查询 Prompt同比/环比/跨年对比) ==========
generate_dual_time_aggregate_cypher_prompt = ChatPromptTemplate.from_messages([
SystemMessagePromptTemplate.from_template(
"你是一个严格的双时间段对比统计 Cypher 生成器。仅处理含**两个明确时间范围**的计数对比问题(如“今年 vs 去年”“2023 vs 2024”。必须 100% 遵守以下规则。\n\n"
"### ⚠️ 绝对禁止(出现即错误)\n"
"❌ 使用任何时间函数datetime/date/timestamp\n"
"❌ 使用有向关系(-> 或 <-),必须写成 `-[:REL]-`\n"
"❌ 使用 schema 未定义的属性名\n"
"❌ 使用多个 MATCH、OPTIONAL MATCH、WITH、UNWIND 或子查询\n"
"❌ 在 CASE WHEN 中只写一个边界(如仅 >= 而无 <\n"
"❌ WHERE 子句用 AND 连接两个时间段(必须用 OR\n"
"❌ 返回字段命名错误(见下方 ✅ 规则)\n\n"
"### ✅ 必须遵守\n"
"✅ 时间比较必须使用字符串字面量,格式如 `'2025-01-01'`\n"
"✅ 每个时间段必须表示为完整左闭右开区间:>= start AND < end\n"
"✅ WHERE 条件:(time in period1) OR (time in period2)\n"
"✅ RETURN 必须包含两个字段,命名规则:\n"
" • 若为相对时间(今年/去年、本月/上月)→ `current, previous`\n"
" • 若为绝对年份2023 vs 2024→ `year_2023, year_2024`\n"
"✅ 每个字段必须是:\n"
" count(CASE WHEN time >= 'start' AND time < 'end' THEN 1 END) AS name\n"
"✅ 仅当用户明确提到实体名称时,才使用 `名称 IN [...]`\n"
"✅ 路径最多 5 跳;否则退化为单节点\n"
"✅ **优先使用少跳数完成查询。若单节点即可满足条件(如统计所有故障或查某属性),不要强行连接其他节点。**\n\n"
"### Schema唯一权威\n{schema}\n\n"
"### 系统时间上下文\n{current_date_info}\n\n"
"### 生成步骤\n"
"1. 判断是否使用入口节点(仅当明确提及实体名)\n"
"2. 识别两个时间段类型:相对 or 绝对 → 决定返回字段名\n"
"3. 为每个时间段生成完整 [start, end) 字符串(系统已提供)\n"
"4. 构建 ≤5 跳无向路径\n"
"5. 写单一 MATCH + WHERE (period1 OR period2)\n"
"6. RETURN 两个带完整区间的 count(CASE ...) AS ...\n\n"
"### 正确示例\n"
"• 今年 vs 去年故障次数:\n"
" ✅ MATCH (f:故障模式)\n"
" WHERE (f.发生时间 >= '2025-01-01' AND f.发生时间 < '2026-01-01') OR\n"
" (f.发生时间 >= '2024-01-01' AND f.发生时间 < '2025-01-01')\n"
" RETURN\n"
" count(CASE WHEN f.发生时间 >= '2025-01-01' AND f.发生时间 < '2026-01-01' THEN 1 END) AS current,\n"
" count(CASE WHEN f.发生时间 >= '2024-01-01' AND f.发生时间 < '2025-01-01' THEN 1 END) AS previous\n\n"
"• 2023 vs 2024年修理次数\n"
" ✅ MATCH (r:维修工作)\n"
" WHERE (r.发生时间 >= '2023-01-01' AND r.发生时间 < '2024-01-01') OR\n"
" (r.发生时间 >= '2024-01-01' AND r.发生时间 < '2025-01-01')\n"
" RETURN\n"
" count(CASE WHEN r.发生时间 >= '2023-01-01' AND r.发生时间 < '2024-01-01' THEN 1 END) AS year_2023,\n"
" count(CASE WHEN r.发生时间 >= '2024-01-01' AND r.发生时间 < '2025-01-01' THEN 1 END) AS year_2024\n\n"
"### ❌ 典型错误(绝对不要做)\n"
"• CASE WHEN f.发生时间 >= '2025-01-01' THEN 1 END → 缺少 < '2026-01-01'\n"
"• WHERE (period1) AND (period2) → 逻辑错误,应为 OR\n"
"• 返回 AS this_year, last_year → 相对时间必须用 current/previous\n"
"• 使用 WITH 或额外变量 → 违反单一 MATCH 原则\n\n"
"→ 你必须严格复制示例中的结构。宁可输出简单正确语句,也不要‘优化’逻辑。"
),
HumanMessagePromptTemplate.from_template(
"【重要】再次提醒:\n"
"- **每个节点必须绑定别名**,例如 `(s:舰艇)` 而非 `(:舰艇)`\n"
"- 每个 CASE 必须同时包含 >= start AND < end\n"
"- WHERE 必须用 OR 连接两个时间段!\n"
"- 相对时间 → 返回 current, previous绝对年份 → year_YYYY\n"
"- 禁用任何时间函数,仅用字符串!\n"
"- 仅一个 MATCH无 WITH\n\n"
"入口节点NodeLabel: [name1, name2, ...]:\n{entry_nodes}\n\n"
"用户问题:\n{query}\n\n"
"系统时间信息(包含完整时间段字符串):\n{current_date_info}\n\n"
"→ 请直接输出【完整 Cypher 语句】,不要解释,不要注释。"
),
])
# ========== 多时间段统计查询 Prompt3个及以上时间段 ==========
generate_multi_time_aggregate_cypher_prompt = ChatPromptTemplate.from_messages([
SystemMessagePromptTemplate.from_template(
"你是一个严格的多时间段趋势统计 Cypher 生成器。仅处理含**三个或以上明确年份**的计数问题如“2023、2024、2025年”或“历年趋势”。必须 100% 遵守以下规则。\n\n"
"### ⚠️ 绝对禁止(出现即错误)\n"
"❌ 使用任何时间函数datetime/date/timestamp\n"
"❌ 使用有向关系(-> 或 <-),必须写成 `-[:REL]-`\n"
"❌ 使用 schema 未定义的属性名\n"
"❌ 使用多个 MATCH、OPTIONAL MATCH、WITH、UNWIND 或子查询\n"
"❌ 在 CASE WHEN 中缺失 < end 边界\n"
"❌ 返回字段命名不符合 `year_YYYY` 格式(如 y2023、count_2023\n"
"❌ 字段顺序不是时间升序(早 → 晚)\n"
"❌ 默认年份计算错误(当前系统时间:{current_date_info}\n\n"
"### ✅ 必须遵守\n"
"✅ 时间比较必须使用字符串字面量,格式如 `'2025-01-01'`\n"
"✅ 每个年份对应一个完整左闭右开区间:>= 'YYYY-01-01' AND < '(YYYY+1)-01-01'\n"
"✅ 若用户说“各年”“历年”“过去几年”且未指定年份 → 默认使用最近3年{last_last_year}, {last_year}, {current_year}\n"
"✅ WHERE 条件:所有时间段用 OR 连接,并用括号包裹整体时间条件\n"
"✅ RETURN 必须包含 N 个字段,每个格式为:\n"
" count(CASE WHEN time >= 'YYYY-01-01' AND time < '(YYYY+1)-01-01' THEN 1 END) AS year_YYYY\n"
"✅ 字段顺序必须按年份升序(如 year_2023, year_2024, year_2025\n"
"✅ 仅当用户明确提到实体名称时,才使用 `名称 IN [...]`\n"
"✅ 路径最多 5 跳;否则退化为单节点\n"
"✅ **优先使用少跳数完成查询。若单节点即可满足条件(如统计所有故障或查某属性),不要强行连接其他节点。**\n\n"
"### Schema唯一权威\n{schema}\n\n"
"### 系统时间上下文\n{current_date_info}\n\n"
"### 生成步骤\n"
"1. 判断是否使用入口节点(仅当明确提及实体名)\n"
"2. 提取所有年份:显式列出 or 默认近3年{last_last_year}, {last_year}, {current_year}\n"
"3. 为每个年份 Y 生成区间:>= 'Y-01-01' AND < '(Y+1)-01-01'\n"
"4. 构建 ≤5 跳无向路径\n"
"5. 写单一 MATCH + WHERE非时间条件 AND (time in Y1 OR Y2 OR Y3)\n"
"6. RETURN 多个 year_YYYY 字段,按年份升序排列\n\n"
"### 正确示例\n"
"• 2023、2024、2025年的故障数\n"
" ✅ MATCH (f:故障模式)\n"
" WHERE (f.发生时间 >= '2023-01-01' AND f.发生时间 < '2024-01-01') OR\n"
" (f.发生时间 >= '2024-01-01' AND f.发生时间 < '2025-01-01') OR\n"
" (f.发生时间 >= '2025-01-01' AND f.发生时间 < '2026-01-01')\n"
" RETURN\n"
" count(CASE WHEN f.发生时间 >= '2023-01-01' AND f.发生时间 < '2024-01-01' THEN 1 END) AS year_2023,\n"
" count(CASE WHEN f.发生时间 >= '2024-01-01' AND f.发生时间 < '2025-01-01' THEN 1 END) AS year_2024,\n"
" count(CASE WHEN f.发生时间 >= '2025-01-01' AND f.发生时间 < '2026-01-01' THEN 1 END) AS year_2025\n\n"
"• 各年的修理数默认近3年2023,2024,2025\n"
" ✅ MATCH (r:维修工作)\n"
" WHERE (r.发生时间 >= '2023-01-01' AND r.发生时间 < '2024-01-01') OR\n"
" (r.发生时间 >= '2024-01-01' AND r.发生时间 < '2025-01-01') OR\n"
" (r.发生时间 >= '2025-01-01' AND r.发生时间 < '2026-01-01')\n"
" RETURN\n"
" count(CASE WHEN r.发生时间 >= '2023-01-01' AND r.发生时间 < '2024-01-01' THEN 1 END) AS year_2023,\n"
" count(CASE WHEN r.发生时间 >= '2024-01-01' AND r.发生时间 < '2025-01-01' THEN 1 END) AS year_2024,\n"
" count(CASE WHEN r.发生时间 >= '2025-01-01' AND r.发生时间 < '2026-01-01' THEN 1 END) AS year_2025\n\n"
"### ❌ 典型错误(绝对不要做)\n"
"• 返回 year_2025, year_2024, year_2023 → 顺序错误(应按年份升序)\n"
"• CASE WHEN f.发生时间 >= '2025-01-01' THEN 1 END → 缺少 < '2026-01-01'\n"
"• 使用 year2023无下划线→ 命名格式错误\n"
"• 默认年份写成 2022,2023,2024当前是 2026→ 逻辑错误\n"
"• WHERE 未用括号包裹 OR 条件 → 与非时间条件优先级混乱\n\n"
"→ 你必须像复制模板一样输出。每个年份的 CASE 结构必须完全一致。"
),
HumanMessagePromptTemplate.from_template(
"【重要】再次提醒:\n"
"- **每个节点必须绑定别名**,例如 `(s:舰艇)` 而非 `(:舰艇)`\n"
"- 默认近3年{last_last_year}, {last_year}, {current_year}(当前是 {current_year} 年)!\n"
"- 每个 CASE 必须包含 >= YYYY-01-01 AND < (YYYY+1)-01-01\n"
"- 返回字段必须命名为 year_2023、year_2024 等,按年份升序!\n"
"- 仅一个 MATCH禁用 WITH/OPTIONAL MATCH\n"
"- 时间必须用字符串,禁用函数!\n\n"
"入口节点NodeLabel: [name1, name2, ...]:\n{entry_nodes}\n\n"
"用户问题:\n{query}\n\n"
"系统时间信息:\n{current_date_info}\n\n"
"→ 请直接输出【完整 Cypher 语句】,不要解释,不要注释。"
),
])
# ========== Cypher校正prompt按错误类型分类 ==========
# 1. 语法错误修正 prompt适用于统计类和明细类
correct_syntax_cypher_prompt = ChatPromptTemplate.from_messages([
SystemMessagePromptTemplate.from_template(
"你是一位资深 Cypher 专家,负责修正 Cypher 查询中的**语法错误**。\n\n"
"【错误类型】语法错误(括号不匹配、关键字错误、格式问题等)\n\n"
"【修正原则】\n"
"1. **仅修复语法问题**,不得改变查询的语义和意图。\n"
"2. **保持原始查询结构**:节点标签、关系类型、属性名等保持不变。\n"
"3. **保持原始查询类型**\n"
" - 如果原查询是统计类(包含聚合函数如 count、sum 等),修正后仍应为统计类\n"
" - 如果原查询是明细类(返回节点/关系),修正后仍应为明细类\n"
"4. **修复常见语法错误**\n"
" - 括号不匹配 → 补充缺失的括号或删除多余的括号\n"
" - 关键字拼写错误 → 修正为正确的关键字MATCH、RETURN、WHERE等\n"
" - 关系定义格式错误 → 修正为 `-[:RELATIONSHIP]-` 格式\n"
" - 多余的括号嵌套 → 删除多余的括号\n"
" - 缺少逗号或空格 → 补充必要的分隔符\n"
"5. **输出要求**\n"
" - 仅返回修正后的完整 Cypher 语句,不要任何解释、注释或 Markdown。\n\n"
"schema:\n{schema}"
),
HumanMessagePromptTemplate.from_template(
"入口节点格式NodeLabel: [name1, name2, ...]:\n{entry_nodes}\n\n"
"用户原始输入:\n{query}\n\n"
"语法错误信息:\n{errors}\n\n"
"待修正的 Cypher 语句:\n{cypher}\n\n"
"请输出修正后的 Cypher 语句(仅代码,无其他内容):"
),
])
# 2. Schema错误修正 prompt适用于统计类和明细类
correct_schema_cypher_prompt = ChatPromptTemplate.from_messages([
SystemMessagePromptTemplate.from_template(
"你是一位资深 Cypher 专家,负责修正 Cypher 查询中的**Schema错误**。\n\n"
"【错误类型】Schema错误关系类型不存在、节点标签不存在、属性名不存在\n\n"
"【修正原则】\n"
"1. **Schema 是唯一权威**\n"
" - 所有节点标签和关系类型必须严格存在于以下 schema 中。\n"
" - 禁止使用 schema 中未定义的标签、关系或属性。\n"
"2. **保持原始查询意图**\n"
" - 修正仅针对错误的标签/关系/属性,不得改变用户要查询的目标。\n"
" - 目标节点类型必须与用户问题一致。\n"
"3. **保持原始查询类型**\n"
" - 如果原查询是统计类(包含聚合函数如 count、sum 等),修正后仍应为统计类\n"
" - 如果原查询是明细类(返回节点/关系),修正后仍应为明细类\n"
"4. **修正策略**\n"
" - **关系类型不存在** → 在 schema 中查找语义相近的关系类型替换\n"
" - **节点标签不存在** → 在 schema 中查找语义相近的节点标签替换\n"
" - **属性名不存在** → 使用 schema 中该节点类型实际存在的属性名(如 `name`、`名称`\n"
" - 如果无法找到替代,考虑通过中间节点构建多跳路径(如 设备 → 故障 → 维修方案)\n"
"5. **强制无方向关系**:所有关系必须写成 `-[:RELATIONSHIP]-`,禁止 `->` 或 `<-`。\n"
"6. **输出要求**\n"
" - 仅返回修正后的完整 Cypher 语句,不要任何解释、注释或 Markdown。\n\n"
"schema:\n{schema}"
),
HumanMessagePromptTemplate.from_template(
"入口节点格式NodeLabel: [name1, name2, ...]:\n{entry_nodes}\n\n"
"用户原始输入:\n{query}\n\n"
"Schema错误信息关系类型/节点标签/属性名不存在):\n{errors}\n\n"
"待修正的 Cypher 语句:\n{cypher}\n\n"
"请输出修正后的 Cypher 语句(仅代码,无其他内容):"
),
])
# 3. 空结果错误修正 prompt适用于统计类和明细类
correct_empty_result_cypher_prompt = ChatPromptTemplate.from_messages([
SystemMessagePromptTemplate.from_template(
"你是一位资深 Cypher 专家,负责修正返回空结果的 Cypher 查询。\n\n"
"【错误类型】查询执行成功但返回空结果或0结果\n\n"
"【修正原则】\n"
"1. **保持原始查询意图**:不得改变用户要查询的目标。\n"
"2. **Schema 是唯一权威**:所有标签/关系必须存在于 schema 中。\n"
"3. **保持原始查询类型**\n"
" - 如果原查询是统计类(包含聚合函数如 count、sum 等),修正后仍应为统计类\n"
" - 如果原查询是明细类(返回节点/关系),修正后仍应为明细类\n"
"4. **修正策略(按优先级)**\n"
" - **检查路径是否可达**\n"
" • 若直接关系不存在,必须通过 schema 中存在的中间节点构建多跳路径\n"
" • 每一跳的关系必须在 schema 中存在,最多允许 5 跳\n"
" - **检查入口节点名称**\n"
" • 确保使用的节点名称与入口节点列表中的名称完全一致\n"
" • 考虑使用 `name IN [...]` 匹配多个入口节点\n"
" - **如果仍无法连接**\n"
" • 保留入口节点匹配,但不扩展无效路径(避免空 MATCH\n"
" • 返回入口节点本身作为结果\n"
"5. **强制无方向关系**:所有关系必须写成 `-[:RELATIONSHIP]-`,禁止 `->` 或 `<-`。\n"
"6. **输出要求**\n"
" - 仅返回修正后的完整 Cypher 语句,不要任何解释、注释或 Markdown。\n\n"
"schema:\n{schema}"
),
HumanMessagePromptTemplate.from_template(
"入口节点格式NodeLabel: [name1, name2, ...]:\n{entry_nodes}\n\n"
"用户原始输入:\n{query}\n\n"
"错误信息(查询返回空结果):\n{errors}\n\n"
"待修正的 Cypher 语句:\n{cypher}\n\n"
"请输出修正后的 Cypher 语句(仅代码,无其他内容):"
),
])
# ========== 通用修正 prompt向后兼容用于未分类的错误 ==========
correct_cypher_prompt = ChatPromptTemplate.from_messages([
SystemMessagePromptTemplate.from_template(
"你是一位资深 Cypher 专家,负责根据 schema、入口节点、用户意图和执行错误精准修正一条有缺陷的 Cypher 查询。\n\n"
"【核心原则】\n"
"1. **Schema 是唯一权威**\n"
" - 所有节点标签、关系类型和属性必须严格存在于以下 schema 中。\n"
" - 禁止使用 schema 中未定义的元素。\n"
"2. **保持原始查询意图不变**\n"
" - 若原查询目标是具体实体(如故障现象),修正后仍返回该类节点;\n"
" - 若原查询是统计(如“有多少个”),修正后必须仍是纯聚合查询。\n"
"3. **强制无方向关系**\n"
" - 所有关系必须写成 `-[:RELATIONSHIP]-`,禁止 `->` 或 `<-`。\n"
"4. **实体来源可靠**\n"
" - 节点匹配必须使用入口节点中提供的名称(已验证存在于图谱)。\n"
"5. **输出要求**\n"
" - 仅返回修正后的完整 Cypher 语句;\n"
" - 不要任何解释、注释、Markdown 或额外文本;\n"
" - 确保语法合法、可直接在 Neo4j 中执行。\n\n"
"【针对非聚合查询】\n"
"- RETURN 可包含节点或属性,但属性必须存在于 schema\n"
"- 允许多跳路径(最多 5 跳),每跳关系须在 schema 中存在;\n"
"- 若路径不可达,优先尝试通过中间节点重构路径;若仍失败,保留入口节点匹配但不扩展无效部分。\n\n"
"【针对聚合查询(含 count/sum/avg 等)】\n"
"- 必须是纯统计:仅一个 `MATCH`(可用逗号分隔多个模式);\n"
"- 禁止 `OPTIONAL MATCH`、`WITH`、`UNWIND`、子查询;\n"
"- RETURN 只能包含一个聚合表达式(如 `count(*) AS total`\n"
"- 若路径不可达,退化为仅匹配入口节点进行计数。\n\n"
"【通用修正策略】\n"
"- 标签/关系不在 schema → 替换为 schema 中存在的等效项;\n"
"- 属性不存在 → 仅保留通用属性(如 `名称`)或移除条件;\n"
"- 语法错误 → 修复括号、关键字、RETURN 缺失等;\n"
"- 有向关系 → 改为无方向 `-[:REL]-`。\n\n"
"schema:\n{schema}"
),
HumanMessagePromptTemplate.from_template(
"入口节点格式NodeLabel: [name1, name2, ...]:\n{entry_nodes}\n\n"
"用户原始输入:\n{query}\n\n"
"执行错误信息:\n{errors}\n\n"
"待修正的 Cypher 语句:\n{cypher}\n\n"
"请输出修正后的 Cypher 语句(仅代码,无其他内容):"
),
])
correct_aggregate_cypher_prompt = correct_cypher_prompt
# ========== 路由标签识别 Prompt实体抽取和查询重写 ==========
def build_schema_section(schema: str = None) -> str:
"""
构建schema部分供路由标签识别使用
Args:
schema: Neo4j图谱schema信息可选
Returns:
str: schema部分的字符串
"""
if not schema:
return ""
return f"""
【重要图谱Schema结构必须仔细阅读
以下是从Neo4j知识图谱中提取的完整schema结构包含了所有可用的节点标签Node Labels和关系类型Relationship Types
**你必须根据这个schema来识别实体应该属于哪个节点标签确保标签名称与schema中的节点标签完全一致。**
{schema}
【关键要求】
- 你选择的label必须与schema中列出的节点标签完全匹配大小写敏感
- 如果schema中没有某个标签不要使用该标签
- 仔细查看schema中的节点标签列表确保选择的标签是实际存在的
"""
def get_extract_entities_prompt(query: str, optional_labels: str, schema: str = None) -> str:
schema_section = build_schema_section(schema) if schema else ""
prompt = f"""你是一个智能检索路由Agent负责从用户自然语言问题中准确抽取**主语实体**及其对应的**图谱节点标签**。
### 🎯 核心任务
从用户问题中识别出**查询的起点实体(主语)**,并为其分配一个或多个**合法的节点标签**必须严格来自图谱schema
### ⚠️ 关键原则(违反任一将导致错误)
1. **主语 ≠ 查询目标**
- 示例:
- ❌ "声呐基阵系统由哪些零部件组成?" → 主语不是"零部件"
- ✅ 主语是"声呐基阵系统",标签应为 "系统"依schema而定
- 查询目标(如"零部件""维修项目")由后续图遍历处理,**不得作为当前主语的标签**
2. **标签必须存在于schema中**
- 大小写敏感,名称必须完全一致
- 若schema未定义某标签"子系统"),即使语义合理也**禁止使用**(实际图谱中存在"系统"标签)
3. **允许多标签候选**
- 当实体可能属于多个合法标签时,可输出多个条目
- 每个标签都必须在schema中真实存在
4. **禁止模糊占位符**
- 不得使用 "Unknown""Other""不确定""" 等无效标签
{schema_section}
【可选标签说明仅作语义参考最终以schema为准
{optional_labels}
### 📤 输出要求
请严格按照以下 JSON 格式输出,不要包含任何其他文字、解释或 Markdown
{{
"entities": [
{{"label": "节点类型", "entity": "实体名称"}}
]
}}
注意:
- 必须是合法的 JSON
- 不要使用 ```json 或任何代码块;
- 如果没有匹配项,返回 {{"entities": []}}
用户输入:
{query}"""
return prompt
def get_rewrite_query_prompt(query: str, schema: str = None) -> str:
schema_section = build_schema_section(schema) if schema else ""
prompt = f"""你是一个专业的智能检索路由Agent负责将用户自然语言问题**重写为标准查询语句**,并进行**精确分类**,以支持知识图谱检索或后置计算。
{schema_section}
### 🔍 任务一:判断 query_type三选一优先级从高到低
| 类型 | 判断依据 |
|-------------|--------|
| **complex** | 问题涉及 **“备品备件需求量”** 或其他**图谱无法直接计算的指标**(如预测、优化、动态库存等) |
| **aggregate** | 涉及 **统计、计数、聚合、比较、存在性**<br>• 词汇:多少、几个、总数、占比、平均、最大值、是否存在、有没有<br>• 时间对比:同比、环比、相比去年<br>• 排名前N名、排行 |
| **detail** | 要求 **具体实体列表或详情**<br>• 词汇:有哪些、列出、显示、是什么、包含哪些 |
> 💡 判断顺序:先检查是否为 complex → 否则按 aggregate/detail 规则判断
---
### ✍️ 任务二:重写规则(按 query_type 分类处理)
#### ▶ 若为 **detail** 或 **aggregate**
- **保留所有限定条件**(时间、状态、属性等)
✅ 正确:"2025年新增的声呐基阵系统有哪些"
❌ 错误:"有哪些声呐基阵系统?"
- **术语标准化**(仅当明确对应图谱实体时):
"组件""零部件"
"维修项目""维修项目"(保持原样,图谱中存在此节点类型)
"故障记录""故障"
> 注意:仅在上下文明确指代图谱实体时替换(如"研发项目"不替换)
- **保持自然语言结构**,不强制改为问句
#### ▶ 若为 **complex**
执行 **“意图降级”**:将“求指标”问题转为“列原始实体”问题
- **保留所有原始条件**(时间、系统、状态等)
- **应用术语标准化**
- **改写为目标实体的明细查询**
✅ 示例:
- 输入:"发动机备品备件的需求量是多少?"
- 输出:"列出所有发动机的备品备件。"
- 输入:"今年螺旋桨所需备件数量?"
- 输出:"列出2025年螺旋桨所需的所有备品备件。"
> ⚠️ 即使原问题含“多少”“需求量”complex 类必须输出 **可直接图谱查询的实体列表请求**
---
### 🏷️ 任务三:分类字段(如实填写)
- **has_time**:
`true` 若含:年/月/日、今年/去年、2024年、近一年、Q1、同比 等
`false` 否则
- **time_type**(仅当 query_type="aggregate" 且 has_time=true 时有效):
- `"single"`:单一时间段(如“今年”)
- `"dual"`:两个时间段对比(如“同比”“相比去年”)
- `"multi"`:多个时间段(如“各季度”)
- `"null"`:其他情况(包括 complex 或 detail 类)
> ✅ 即使 query_type="complex",也需正确填写 has_time 和 time_type
---
### 📤 输出格式(严格 JSON无额外内容
{{
"rewritten_query": "重写后的标准查询语句",
"query_classification": {{
"query_type": "detail|aggregate|complex",
"has_time": true|false,
"time_type": "single|dual|multi|null"
}}
}}
⚠️ 输出必须满足:
- **仅输出一个合法 JSON 对象**,严格遵循以下结构;
- **不要包含任何其他文字、说明、注释、换行、反引号或 Markdown**
- **不要使用 ```json 或任何代码块包裹**
- **确保 JSON 可被 Python json.loads() 直接解析**
- 若无法判断使用默认值query_type="detail", has_time=false, time_type=null。
用户输入:{query}
"""
return prompt
def get_all_ontology_labels(driver: Driver) -> list[str]:
"""
从 Neo4j 图数据库动态获取所有节点标签(即本体类别)。
替换原来的硬编码列表。
"""
with driver.session() as session:
result = session.run("CALL db.labels() YIELD label RETURN label")
labels = [record["label"] for record in result]
return sorted(labels) # 可选:排序便于调试
def get_all_relationship_types(driver: Driver) -> list[str]:
"""
从 Neo4j 图数据库动态获取所有关系类型。
替换原来的硬编码列表。
"""
with driver.session() as session:
result = session.run("CALL db.relationshipTypes() YIELD relationshipType RETURN relationshipType")
rel_types = [record["relationshipType"] for record in result]
return sorted(rel_types) # 可选:排序