"""
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 语句】,不要解释,不要注释。"
),
])
# ========== 多时间段统计查询 Prompt(3个及以上时间段) ==========
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** | 涉及 **统计、计数、聚合、比较、存在性**:
• 词汇:多少、几个、总数、占比、平均、最大值、是否存在、有没有
• 时间对比:同比、环比、相比去年
• 排名:前N名、排行 |
| **detail** | 要求 **具体实体列表或详情**:
• 词汇:有哪些、列出、显示、是什么、包含哪些 |
> 💡 判断顺序:先检查是否为 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) # 可选:排序