diff --git a/backend/app/prompts/expert_transformation_prompt.py b/backend/app/prompts/expert_transformation_prompt.py index 6cc304f..60d136e 100644 --- a/backend/app/prompts/expert_transformation_prompt.py +++ b/backend/app/prompts/expert_transformation_prompt.py @@ -10,20 +10,40 @@ def get_expert_generation_prompt( custom_experts: Optional[List[str]] = None ) -> str: """Step 0: 生成專家團隊(不依賴主題,純隨機多元)""" + import time + import random + custom_text = "" if custom_experts and len(custom_experts) > 0: 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 隨機組建 {expert_count} 個來自完全不同領域的專家團隊{custom_text}。 +【創新要求】(隨機種子:{seed}) +- 優先選擇{hint}的專家 +- 避免常見職業(如醫生、工程師、教師、律師等) +- 每個專家必須來自完全不相關的領域 +- 越罕見、越創新越好 + 回傳 JSON: {{"experts": [{{"id": "expert-0", "name": "職業", "domain": "領域", "perspective": "角度"}}, ...]}} 規則: - id 為 expert-0 到 expert-{expert_count - 1} - name 填寫職業名稱(非人名),2-5字 -- 各專家的 domain 必須來自截然不同的領域,越多元越好""" +- domain 要具體且獨特,不可重複類型""" def get_expert_keyword_generation_prompt( @@ -48,31 +68,20 @@ def get_expert_keyword_generation_prompt( 共需 {len(experts) * keywords_per_expert} 個關鍵字。""" -def get_expert_batch_description_prompt( +def get_single_description_prompt( query: str, - category: str, - expert_keywords: List[dict] # List[ExpertKeyword] + keyword: str, + expert_id: str, + expert_name: str, + expert_domain: str ) -> str: - """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 - ]) - + """Step 2: 為單一關鍵字生成描述""" return f"""/no_think 物件:「{query}」 -關鍵字(專家:詞彙):{keywords_info} -對照:{keyword_expert_map} +專家:{expert_name}({expert_domain}) +關鍵字:{keyword} -為每個關鍵字生成創新描述(15-30字),說明如何將該概念應用到「{query}」上。 +從這位專家的視角,生成一段創新應用描述(15-30字),說明如何將「{keyword}」的概念應用到「{query}」上。 回傳 JSON: -{{"descriptions": [{{"keyword": "詞彙", "expert_id": "expert-X", "expert_name": "名稱", "description": "應用描述"}}, ...]}} - -共需 {len(expert_keywords)} 個描述。""" +{{"description": "應用描述"}}""" diff --git a/backend/app/routers/expert_transformation.py b/backend/app/routers/expert_transformation.py index 8a0d737..a88d3ad 100644 --- a/backend/app/routers/expert_transformation.py +++ b/backend/app/routers/expert_transformation.py @@ -17,7 +17,7 @@ from ..models.schemas import ( from ..prompts.expert_transformation_prompt import ( get_expert_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 @@ -119,34 +119,48 @@ async def generate_expert_transformation_events( yield f"event: error\ndata: {json.dumps({'error': '無法生成關鍵字'}, ensure_ascii=False)}\n\n" return - # ========== Step 2: Generate descriptions for each expert keyword ========== - yield f"event: description_start\ndata: {json.dumps({'message': '為專家關鍵字生成創新應用描述...'}, ensure_ascii=False)}\n\n" + # ========== Step 2: Generate descriptions one by one ========== + yield f"event: description_start\ndata: {json.dumps({'message': '為專家關鍵字生成創新應用描述...', 'total': len(all_expert_keywords)}, ensure_ascii=False)}\n\n" descriptions: List[ExpertTransformationDescription] = [] - try: - desc_prompt = get_expert_batch_description_prompt( - query=request.query, - category=request.category, - expert_keywords=[kw.model_dump() for kw in all_expert_keywords] - ) - logger.info(f"Description prompt: {desc_prompt[:300]}") + # Build expert lookup for domain info + expert_lookup = {exp.id: exp for exp in experts} - desc_response = await ollama_provider.generate( - desc_prompt, model=model, temperature=temperature - ) - logger.info(f"Description response: {desc_response[:500]}") + for idx, kw in enumerate(all_expert_keywords): + try: + expert = expert_lookup.get(kw.expert_id) + expert_domain = expert.domain if expert else "" - desc_data = extract_json_from_response(desc_response) - descriptions_raw = desc_data.get("descriptions", []) + desc_prompt = get_single_description_prompt( + query=request.query, + keyword=kw.keyword, + expert_id=kw.expert_id, + expert_name=kw.expert_name, + expert_domain=expert_domain + ) - for desc in descriptions_raw: - if isinstance(desc, dict) and all(k in desc for k in ["keyword", "expert_id", "expert_name", "description"]): - descriptions.append(ExpertTransformationDescription(**desc)) + desc_response = await ollama_provider.generate( + desc_prompt, model=model, temperature=temperature + ) - except Exception as e: - logger.warning(f"Failed to generate descriptions: {e}") - # Continue without descriptions - at least we have keywords + desc_data = extract_json_from_response(desc_response) + desc_text = desc_data.get("description", "") + + if desc_text: + descriptions.append(ExpertTransformationDescription( + 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: + logger.warning(f"Failed to generate description for '{kw.keyword}': {e}") + # Continue with next keyword yield f"event: description_complete\ndata: {json.dumps({'count': len(descriptions)}, ensure_ascii=False)}\n\n" diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 9c28d1f..9282250 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -33,7 +33,7 @@ function App() { // Transformation Agent settings const [transformModel, setTransformModel] = useState(''); - const [transformTemperature, setTransformTemperature] = useState(0.7); + const [transformTemperature, setTransformTemperature] = useState(0.95); const [expertConfig, setExpertConfig] = useState<{ expert_count: number; keywords_per_expert: number;