feat: Improve expert diversity and description reliability
- Add random seed and diversity hints to expert generation prompt - Explicitly avoid common professions (醫生、工程師、教師、律師等) - Change description generation from batch to one-by-one for reliability - Increase default temperature from 0.7 to 0.95 for more creative output - Add description_progress SSE event for real-time feedback 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -10,20 +10,40 @@ def get_expert_generation_prompt(
|
|||||||
custom_experts: Optional[List[str]] = None
|
custom_experts: Optional[List[str]] = None
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Step 0: 生成專家團隊(不依賴主題,純隨機多元)"""
|
"""Step 0: 生成專家團隊(不依賴主題,純隨機多元)"""
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
|
||||||
custom_text = ""
|
custom_text = ""
|
||||||
if custom_experts and len(custom_experts) > 0:
|
if custom_experts and len(custom_experts) > 0:
|
||||||
custom_text = f"(已指定:{', '.join(custom_experts[:expert_count])})"
|
custom_text = f"(已指定:{', '.join(custom_experts[:expert_count])})"
|
||||||
|
|
||||||
|
# 加入時間戳和隨機數來增加多樣性
|
||||||
|
seed = int(time.time() * 1000) % 10000
|
||||||
|
diversity_hints = [
|
||||||
|
"冷門、非主流、跨領域",
|
||||||
|
"罕見職業、新興領域、邊緣學科",
|
||||||
|
"非傳統、創新、小眾專業",
|
||||||
|
"未來趨向、實驗性、非常規",
|
||||||
|
"跨文化、混合領域、獨特視角"
|
||||||
|
]
|
||||||
|
hint = random.choice(diversity_hints)
|
||||||
|
|
||||||
return f"""/no_think
|
return f"""/no_think
|
||||||
隨機組建 {expert_count} 個來自完全不同領域的專家團隊{custom_text}。
|
隨機組建 {expert_count} 個來自完全不同領域的專家團隊{custom_text}。
|
||||||
|
|
||||||
|
【創新要求】(隨機種子:{seed})
|
||||||
|
- 優先選擇{hint}的專家
|
||||||
|
- 避免常見職業(如醫生、工程師、教師、律師等)
|
||||||
|
- 每個專家必須來自完全不相關的領域
|
||||||
|
- 越罕見、越創新越好
|
||||||
|
|
||||||
回傳 JSON:
|
回傳 JSON:
|
||||||
{{"experts": [{{"id": "expert-0", "name": "職業", "domain": "領域", "perspective": "角度"}}, ...]}}
|
{{"experts": [{{"id": "expert-0", "name": "職業", "domain": "領域", "perspective": "角度"}}, ...]}}
|
||||||
|
|
||||||
規則:
|
規則:
|
||||||
- id 為 expert-0 到 expert-{expert_count - 1}
|
- id 為 expert-0 到 expert-{expert_count - 1}
|
||||||
- name 填寫職業名稱(非人名),2-5字
|
- name 填寫職業名稱(非人名),2-5字
|
||||||
- 各專家的 domain 必須來自截然不同的領域,越多元越好"""
|
- domain 要具體且獨特,不可重複類型"""
|
||||||
|
|
||||||
|
|
||||||
def get_expert_keyword_generation_prompt(
|
def get_expert_keyword_generation_prompt(
|
||||||
@@ -48,31 +68,20 @@ def get_expert_keyword_generation_prompt(
|
|||||||
共需 {len(experts) * keywords_per_expert} 個關鍵字。"""
|
共需 {len(experts) * keywords_per_expert} 個關鍵字。"""
|
||||||
|
|
||||||
|
|
||||||
def get_expert_batch_description_prompt(
|
def get_single_description_prompt(
|
||||||
query: str,
|
query: str,
|
||||||
category: str,
|
keyword: str,
|
||||||
expert_keywords: List[dict] # List[ExpertKeyword]
|
expert_id: str,
|
||||||
|
expert_name: str,
|
||||||
|
expert_domain: str
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Step 2: 批次生成專家關鍵字的描述"""
|
"""Step 2: 為單一關鍵字生成描述"""
|
||||||
keywords_info = ", ".join([
|
|
||||||
f"{kw['expert_name']}:{kw['keyword']}"
|
|
||||||
for kw in expert_keywords
|
|
||||||
])
|
|
||||||
|
|
||||||
# 建立 keyword -> (expert_id, expert_name) 的對照
|
|
||||||
keyword_expert_map = ", ".join([
|
|
||||||
f"{kw['keyword']}→{kw['expert_id']}/{kw['expert_name']}"
|
|
||||||
for kw in expert_keywords
|
|
||||||
])
|
|
||||||
|
|
||||||
return f"""/no_think
|
return f"""/no_think
|
||||||
物件:「{query}」
|
物件:「{query}」
|
||||||
關鍵字(專家:詞彙):{keywords_info}
|
專家:{expert_name}({expert_domain})
|
||||||
對照:{keyword_expert_map}
|
關鍵字:{keyword}
|
||||||
|
|
||||||
為每個關鍵字生成創新描述(15-30字),說明如何將該概念應用到「{query}」上。
|
從這位專家的視角,生成一段創新應用描述(15-30字),說明如何將「{keyword}」的概念應用到「{query}」上。
|
||||||
|
|
||||||
回傳 JSON:
|
回傳 JSON:
|
||||||
{{"descriptions": [{{"keyword": "詞彙", "expert_id": "expert-X", "expert_name": "名稱", "description": "應用描述"}}, ...]}}
|
{{"description": "應用描述"}}"""
|
||||||
|
|
||||||
共需 {len(expert_keywords)} 個描述。"""
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from ..models.schemas import (
|
|||||||
from ..prompts.expert_transformation_prompt import (
|
from ..prompts.expert_transformation_prompt import (
|
||||||
get_expert_generation_prompt,
|
get_expert_generation_prompt,
|
||||||
get_expert_keyword_generation_prompt,
|
get_expert_keyword_generation_prompt,
|
||||||
get_expert_batch_description_prompt,
|
get_single_description_prompt,
|
||||||
)
|
)
|
||||||
from ..services.llm_service import ollama_provider, extract_json_from_response
|
from ..services.llm_service import ollama_provider, extract_json_from_response
|
||||||
|
|
||||||
@@ -119,34 +119,48 @@ async def generate_expert_transformation_events(
|
|||||||
yield f"event: error\ndata: {json.dumps({'error': '無法生成關鍵字'}, ensure_ascii=False)}\n\n"
|
yield f"event: error\ndata: {json.dumps({'error': '無法生成關鍵字'}, ensure_ascii=False)}\n\n"
|
||||||
return
|
return
|
||||||
|
|
||||||
# ========== Step 2: Generate descriptions for each expert keyword ==========
|
# ========== Step 2: Generate descriptions one by one ==========
|
||||||
yield f"event: description_start\ndata: {json.dumps({'message': '為專家關鍵字生成創新應用描述...'}, ensure_ascii=False)}\n\n"
|
yield f"event: description_start\ndata: {json.dumps({'message': '為專家關鍵字生成創新應用描述...', 'total': len(all_expert_keywords)}, ensure_ascii=False)}\n\n"
|
||||||
|
|
||||||
descriptions: List[ExpertTransformationDescription] = []
|
descriptions: List[ExpertTransformationDescription] = []
|
||||||
|
|
||||||
|
# Build expert lookup for domain info
|
||||||
|
expert_lookup = {exp.id: exp for exp in experts}
|
||||||
|
|
||||||
|
for idx, kw in enumerate(all_expert_keywords):
|
||||||
try:
|
try:
|
||||||
desc_prompt = get_expert_batch_description_prompt(
|
expert = expert_lookup.get(kw.expert_id)
|
||||||
|
expert_domain = expert.domain if expert else ""
|
||||||
|
|
||||||
|
desc_prompt = get_single_description_prompt(
|
||||||
query=request.query,
|
query=request.query,
|
||||||
category=request.category,
|
keyword=kw.keyword,
|
||||||
expert_keywords=[kw.model_dump() for kw in all_expert_keywords]
|
expert_id=kw.expert_id,
|
||||||
|
expert_name=kw.expert_name,
|
||||||
|
expert_domain=expert_domain
|
||||||
)
|
)
|
||||||
logger.info(f"Description prompt: {desc_prompt[:300]}")
|
|
||||||
|
|
||||||
desc_response = await ollama_provider.generate(
|
desc_response = await ollama_provider.generate(
|
||||||
desc_prompt, model=model, temperature=temperature
|
desc_prompt, model=model, temperature=temperature
|
||||||
)
|
)
|
||||||
logger.info(f"Description response: {desc_response[:500]}")
|
|
||||||
|
|
||||||
desc_data = extract_json_from_response(desc_response)
|
desc_data = extract_json_from_response(desc_response)
|
||||||
descriptions_raw = desc_data.get("descriptions", [])
|
desc_text = desc_data.get("description", "")
|
||||||
|
|
||||||
for desc in descriptions_raw:
|
if desc_text:
|
||||||
if isinstance(desc, dict) and all(k in desc for k in ["keyword", "expert_id", "expert_name", "description"]):
|
descriptions.append(ExpertTransformationDescription(
|
||||||
descriptions.append(ExpertTransformationDescription(**desc))
|
keyword=kw.keyword,
|
||||||
|
expert_id=kw.expert_id,
|
||||||
|
expert_name=kw.expert_name,
|
||||||
|
description=desc_text
|
||||||
|
))
|
||||||
|
|
||||||
|
# Send progress update
|
||||||
|
yield f"event: description_progress\ndata: {json.dumps({'current': idx + 1, 'total': len(all_expert_keywords), 'keyword': kw.keyword}, ensure_ascii=False)}\n\n"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Failed to generate descriptions: {e}")
|
logger.warning(f"Failed to generate description for '{kw.keyword}': {e}")
|
||||||
# Continue without descriptions - at least we have keywords
|
# Continue with next keyword
|
||||||
|
|
||||||
yield f"event: description_complete\ndata: {json.dumps({'count': len(descriptions)}, ensure_ascii=False)}\n\n"
|
yield f"event: description_complete\ndata: {json.dumps({'count': len(descriptions)}, ensure_ascii=False)}\n\n"
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ function App() {
|
|||||||
|
|
||||||
// Transformation Agent settings
|
// Transformation Agent settings
|
||||||
const [transformModel, setTransformModel] = useState<string>('');
|
const [transformModel, setTransformModel] = useState<string>('');
|
||||||
const [transformTemperature, setTransformTemperature] = useState<number>(0.7);
|
const [transformTemperature, setTransformTemperature] = useState<number>(0.95);
|
||||||
const [expertConfig, setExpertConfig] = useState<{
|
const [expertConfig, setExpertConfig] = useState<{
|
||||||
expert_count: number;
|
expert_count: number;
|
||||||
keywords_per_expert: number;
|
keywords_per_expert: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user