Files
novelty-seeking/backend/app/prompts/attribute_prompt.py
gbanyan 1ed1dab78f feat: Migrate to React Flow and add Fixed + Dynamic category mode
Frontend:
- Migrate MindmapDAG from D3.js to React Flow (@xyflow/react)
- Add custom node components (QueryNode, CategoryHeaderNode, AttributeNode)
- Add useDAGLayout hook for column-based layout
- Add "AI" badge for LLM-suggested categories
- Update CategorySelector with Fixed + Dynamic mode option
- Improve dark/light theme support

Backend:
- Add FIXED_PLUS_DYNAMIC category mode
- Filter duplicate category names in LLM suggestions
- Update prompts to exclude fixed categories when suggesting new ones
- Improve LLM service with better error handling and logging
- Auto-remove /no_think prefix for non-Qwen models
- Add smart JSON format detection for model compatibility
- Improve JSON extraction with multiple parsing strategies

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 01:22:57 +08:00

269 lines
8.5 KiB
Python
Raw Permalink 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.
from typing import List, Optional, Dict
import json
DEFAULT_CATEGORIES = ["材料", "功能", "用途", "使用族群", "特性"]
CATEGORY_DESCRIPTIONS = {
"材料": "物件由什麼材料組成",
"功能": "物件能做什麼",
"用途": "物件在什麼場景使用",
"使用族群": "誰會使用這個物件",
"特性": "物件有什麼特徵",
}
def get_attribute_prompt(query: str, categories: Optional[List[str]] = None) -> str:
"""Generate prompt with causal chain structure."""
prompt = f"""分析「{query}」的屬性,以因果鏈方式呈現:材料→功能→用途→使用族群。
請列出 3-5 種材料,每種材料延伸出完整因果鏈。
JSON 格式:
{{"name": "{query}", "children": [{{"name": "材料名", "category": "材料", "children": [{{"name": "功能名", "category": "功能", "children": [{{"name": "用途名", "category": "用途", "children": [{{"name": "族群名", "category": "使用族群"}}]}}]}}]}}]}}
只回傳 JSON。"""
return prompt
def get_step1_attributes_prompt(query: str) -> str:
"""Step 1: 生成各類別的屬性列表(平行結構)"""
return f"""/no_think
分析「{query}」,列出以下四個類別的屬性。每個類別列出 3-5 個常見屬性。
只回傳 JSON格式如下
{{"materials": ["材料1", "材料2", "材料3"], "functions": ["功能1", "功能2", "功能3"], "usages": ["用途1", "用途2", "用途3"], "users": ["族群1", "族群2", "族群3"]}}
物件:{query}"""
def get_step2_causal_chain_prompt(
query: str,
materials: List[str],
functions: List[str],
usages: List[str],
users: List[str],
existing_chains: List[dict],
chain_index: int
) -> str:
"""Step 2: 生成單條因果鏈"""
existing_chains_text = ""
if existing_chains:
chains_list = [
f"- {c['material']}{c['function']}{c['usage']}{c['user']}"
for c in existing_chains
]
existing_chains_text = f"""
【已生成的因果鏈,請勿重複】
{chr(10).join(chains_list)}
"""
return f"""/no_think
為「{query}」生成第 {chain_index} 條因果鏈。
【可選材料】{', '.join(materials)}
【可選功能】{', '.join(functions)}
【可選用途】{', '.join(usages)}
【可選族群】{', '.join(users)}
{existing_chains_text}
【規則】
1. 從每個類別選擇一個屬性,組成合理的因果鏈
2. 因果關係必須合邏輯(材料決定功能,功能決定用途,用途決定族群)
3. 不要與已生成的因果鏈重複
只回傳 JSON
{{"material": "選擇的材料", "function": "選擇的功能", "usage": "選擇的用途", "user": "選擇的族群"}}"""
def get_flat_attribute_prompt(query: str, categories: Optional[List[str]] = None) -> str:
"""Generate prompt with flat/parallel categories (original design)."""
cats = categories if categories else DEFAULT_CATEGORIES
# Build category list
category_lines = []
for cat in cats:
desc = CATEGORY_DESCRIPTIONS.get(cat, f"{cat}的相關屬性")
category_lines.append(f"- {cat}{desc}")
categories_text = "\n".join(category_lines)
prompt = f"""/no_think
你是一個物件屬性分析專家。請將用戶輸入的物件拆解成以下屬性類別。
【必須包含的類別】
{categories_text}
【重要】回傳格式必須是合法的 JSON每個節點都必須有 "name" 欄位:
```json
{{
"name": "物件名稱",
"children": [
{{
"name": "類別名稱",
"children": [
{{"name": "屬性1"}},
{{"name": "屬性2"}}
]
}}
]
}}
```
只回傳 JSON不要有任何其他文字。
用戶輸入:{query}"""
return prompt
# ===== Dynamic category system prompts =====
def get_step0_category_analysis_prompt(
query: str,
suggested_count: int = 3,
exclude_categories: List[str] | None = None
) -> str:
"""Step 0: LLM 分析建議類別"""
exclude_text = ""
if exclude_categories:
exclude_text = f"\n【禁止使用的類別】{', '.join(exclude_categories)}(這些已經是固定類別,不要重複建議)\n"
return f"""/no_think
分析「{query}」,建議 {suggested_count} 個最適合的屬性類別來描述它。
【常見類別參考】特性、形狀、顏色、尺寸、品牌、價格區間、重量、風格、場合、季節、技術規格
{exclude_text}
【重要】
1. 選擇最能描述此物件本質的類別
2. 類別之間應該有邏輯關係
3. 不要選擇過於抽象或重複的類別
4. 必須建議與參考列表不同的、有創意的類別
只回傳 JSON
{{
"categories": [
{{"name": "類別1", "description": "說明1", "order": 0}},
{{"name": "類別2", "description": "說明2", "order": 1}}
]
}}
物件:{query}"""
def get_step1_dynamic_attributes_prompt(
query: str,
categories: List # List[CategoryDefinition]
) -> str:
"""動態 Step 1 - 根據類別列表生成屬性"""
# 按 order 排序並構建描述
sorted_cats = sorted(categories, key=lambda x: x.order if hasattr(x, 'order') else x.get('order', 0))
category_desc = "\n".join([
f"- {cat.name if hasattr(cat, 'name') else cat['name']}: {cat.description if hasattr(cat, 'description') else cat.get('description', '相關屬性')}"
for cat in sorted_cats
])
category_keys = [cat.name if hasattr(cat, 'name') else cat['name'] for cat in sorted_cats]
json_template = {cat: ["屬性1", "屬性2", "屬性3"] for cat in category_keys}
return f"""/no_think
分析「{query}」,列出以下類別的屬性。每個類別列出 3-5 個常見屬性。
【類別列表】
{category_desc}
只回傳 JSON
{json.dumps(json_template, ensure_ascii=False, indent=2)}
物件:{query}"""
def get_step2_dynamic_causal_chain_prompt(
query: str,
categories: List, # List[CategoryDefinition]
attributes_by_category: Dict[str, List[str]],
existing_chains: List[Dict[str, str]],
chain_index: int
) -> str:
"""動態 Step 2 - 生成動態類別的因果鏈"""
sorted_cats = sorted(categories, key=lambda x: x.order if hasattr(x, 'order') else x.get('order', 0))
# 構建可選屬性
available_attrs = "\n".join([
f"{cat.name if hasattr(cat, 'name') else cat['name']}{', '.join(attributes_by_category.get(cat.name if hasattr(cat, 'name') else cat['name'], []))}"
for cat in sorted_cats
])
# 已生成的因果鏈
existing_text = ""
if existing_chains:
chains_list = [
"".join([chain.get(cat.name if hasattr(cat, 'name') else cat['name'], '?') for cat in sorted_cats])
for chain in existing_chains
]
existing_text = f"\n【已生成,請勿重複】\n" + "\n".join([f"- {c}" for c in chains_list])
# JSON 模板
json_template = {cat.name if hasattr(cat, 'name') else cat['name']: f"選擇的{cat.name if hasattr(cat, 'name') else cat['name']}" for cat in sorted_cats}
return f"""/no_think
為「{query}」生成第 {chain_index} 條因果鏈。
【可選屬性】
{available_attrs}
{existing_text}
【規則】
1. 從每個類別選擇一個屬性
2. 因果關係必須合理
3. 不要重複
只回傳 JSON
{json.dumps(json_template, ensure_ascii=False, indent=2)}"""
# ===== DAG relationship prompt =====
def get_step2_dag_relationships_prompt(
query: str,
categories: List, # List[CategoryDefinition]
attributes_by_category: Dict[str, List[str]],
) -> str:
"""生成相鄰類別之間的自然關係"""
sorted_cats = sorted(categories, key=lambda x: x.order if hasattr(x, 'order') else x.get('order', 0))
# Build attribute listing
attr_listing = "\n".join([
f"{cat.name if hasattr(cat, 'name') else cat['name']}{', '.join(attributes_by_category.get(cat.name if hasattr(cat, 'name') else cat['name'], []))}"
for cat in sorted_cats
])
# Build direction hints
direction_hints = "".join([cat.name if hasattr(cat, 'name') else cat['name'] for cat in sorted_cats])
return f"""/no_think
分析「{query}」的屬性關係。
{attr_listing}
【關係方向】{direction_hints}
【規則】
1. 只建立相鄰類別之間的關係(例如:材料→功能,功能→用途)
2. 只輸出真正有因果或關聯關係的配對
3. 一個屬性可連接多個下游屬性,也可以不連接任何屬性
4. 不需要每個屬性都有連接
5. 關係應該合理且有意義
回傳 JSON
{{
"relationships": [
{{"source_category": "類別A", "source": "屬性名", "target_category": "類別B", "target": "屬性名"}},
...
]
}}
只回傳 JSON。"""