- Add complete experiments directory with pilot study infrastructure - 5 experimental conditions (direct, expert-only, attribute-only, full-pipeline, random-perspective) - Human assessment tool with React frontend and FastAPI backend - AUT flexibility analysis with jump signal detection - Result visualization and metrics computation - Add novelty-driven agent loop module (experiments/novelty_loop/) - NoveltyDrivenTaskAgent with expert perspective perturbation - Three termination strategies: breakthrough, exhaust, coverage - Interactive CLI demo with colored output - Embedding-based novelty scoring - Add DDC knowledge domain classification data (en/zh) - Add CLAUDE.md project documentation - Update research report with experiment findings Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
179 lines
5.7 KiB
Python
179 lines
5.7 KiB
Python
"""
|
||
Condition 5: Random-Perspective Control
|
||
|
||
Uses random words as "perspectives" instead of domain experts.
|
||
Tests whether the benefit from expert perspectives comes from
|
||
domain knowledge or simply from any perspective shift.
|
||
"""
|
||
|
||
import sys
|
||
import json
|
||
import random
|
||
from pathlib import Path
|
||
|
||
# Add backend to path for imports
|
||
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "backend"))
|
||
|
||
from typing import List, Dict, Any
|
||
from app.services.llm_service import ollama_provider, extract_json_from_response
|
||
from experiments.config import (
|
||
MODEL, TEMPERATURE, EXPERT_COUNT, IDEAS_PER_EXPERT,
|
||
PROMPT_LANGUAGE, RANDOM_SEED, DATA_DIR
|
||
)
|
||
|
||
|
||
def load_random_words() -> List[str]:
|
||
"""Load the random word pool from data file."""
|
||
words_file = DATA_DIR / "random_words.json"
|
||
with open(words_file, "r", encoding="utf-8") as f:
|
||
data = json.load(f)
|
||
return data.get("words", [])
|
||
|
||
|
||
def get_random_perspective_prompt(
|
||
query: str,
|
||
perspective_word: str,
|
||
idea_count: int,
|
||
lang: str = "en"
|
||
) -> str:
|
||
"""Generate prompt for random-perspective idea generation."""
|
||
if lang == "en":
|
||
return f"""/no_think
|
||
Generate {idea_count} creative and innovative ideas for "{query}" inspired by the concept of "{perspective_word}".
|
||
|
||
Requirements:
|
||
1. Each idea should draw inspiration from "{perspective_word}" - its qualities, characteristics, or associations
|
||
2. Think about how concepts related to "{perspective_word}" could improve or reimagine "{query}"
|
||
3. Ideas should be specific and actionable (15-30 words each)
|
||
4. Be creative in connecting "{perspective_word}" to "{query}"
|
||
|
||
Return JSON only:
|
||
{{"ideas": ["idea 1", "idea 2", "idea 3", ...]}}
|
||
|
||
Generate exactly {idea_count} ideas inspired by "{perspective_word}"."""
|
||
else:
|
||
return f"""/no_think
|
||
為「{query}」生成 {idea_count} 個創意點子,靈感來自「{perspective_word}」這個概念。
|
||
|
||
要求:
|
||
1. 每個點子要從「{perspective_word}」獲得靈感——它的特質、特徵或聯想
|
||
2. 思考與「{perspective_word}」相關的概念如何改進或重新想像「{query}」
|
||
3. 點子要具體可行(每個 15-30 字)
|
||
4. 創意地連接「{perspective_word}」和「{query}」
|
||
|
||
只回傳 JSON:
|
||
{{"ideas": ["點子1", "點子2", "點子3", ...]}}
|
||
|
||
生成正好 {idea_count} 個受「{perspective_word}」啟發的點子。"""
|
||
|
||
|
||
async def generate_ideas(
|
||
query: str,
|
||
model: str = None,
|
||
temperature: float = None,
|
||
word_count: int = None,
|
||
ideas_per_word: int = None,
|
||
lang: str = None,
|
||
seed: int = None
|
||
) -> Dict[str, Any]:
|
||
"""
|
||
Generate ideas using random word perspectives (C5 control).
|
||
|
||
Args:
|
||
query: The object/concept to generate ideas for
|
||
model: LLM model to use
|
||
temperature: Generation temperature
|
||
word_count: Number of random words to use (matches expert count)
|
||
ideas_per_word: Ideas to generate per word
|
||
lang: Language for prompts
|
||
seed: Random seed for reproducibility
|
||
|
||
Returns:
|
||
Dict with ideas and metadata
|
||
"""
|
||
model = model or MODEL
|
||
temperature = temperature or TEMPERATURE
|
||
word_count = word_count or EXPERT_COUNT
|
||
ideas_per_word = ideas_per_word or IDEAS_PER_EXPERT
|
||
lang = lang or PROMPT_LANGUAGE
|
||
seed = seed or RANDOM_SEED
|
||
|
||
# Load word pool and sample random words
|
||
word_pool = load_random_words()
|
||
|
||
# Use seeded random for reproducibility
|
||
# Create a unique seed per query to get different words for different queries
|
||
# but same words for same query across runs
|
||
query_seed = seed + hash(query) % 10000
|
||
rng = random.Random(query_seed)
|
||
selected_words = rng.sample(word_pool, min(word_count, len(word_pool)))
|
||
|
||
all_ideas = []
|
||
word_details = []
|
||
|
||
for word in selected_words:
|
||
prompt = get_random_perspective_prompt(
|
||
query=query,
|
||
perspective_word=word,
|
||
idea_count=ideas_per_word,
|
||
lang=lang
|
||
)
|
||
|
||
response = await ollama_provider.generate(
|
||
prompt=prompt,
|
||
model=model,
|
||
temperature=temperature
|
||
)
|
||
|
||
result = extract_json_from_response(response)
|
||
ideas = result.get("ideas", [])
|
||
|
||
# Tag ideas with perspective word source
|
||
for idea in ideas:
|
||
all_ideas.append({
|
||
"idea": idea,
|
||
"perspective_word": word
|
||
})
|
||
|
||
word_details.append({
|
||
"word": word,
|
||
"ideas_generated": len(ideas)
|
||
})
|
||
|
||
return {
|
||
"condition": "c5_random_perspective",
|
||
"query": query,
|
||
"ideas": [item["idea"] for item in all_ideas],
|
||
"ideas_with_source": all_ideas,
|
||
"idea_count": len(all_ideas),
|
||
"metadata": {
|
||
"model": model,
|
||
"temperature": temperature,
|
||
"prompt_language": lang,
|
||
"word_count": word_count,
|
||
"ideas_per_word": ideas_per_word,
|
||
"random_seed": seed,
|
||
"query_seed": query_seed,
|
||
"selected_words": selected_words,
|
||
"word_details": word_details,
|
||
"word_pool_size": len(word_pool),
|
||
"mechanism": "random_perspective_control"
|
||
}
|
||
}
|
||
|
||
|
||
# For testing
|
||
if __name__ == "__main__":
|
||
import asyncio
|
||
|
||
async def test():
|
||
result = await generate_ideas("Chair")
|
||
print(f"Generated {result['idea_count']} ideas from {len(result['metadata']['selected_words'])} random words:")
|
||
print(f" Words used: {', '.join(result['metadata']['selected_words'])}")
|
||
print(f" Seed: {result['metadata']['random_seed']}, Query seed: {result['metadata']['query_seed']}")
|
||
print("\nSample ideas:")
|
||
for i, item in enumerate(result['ideas_with_source'][:5], 1):
|
||
print(f" {i}. [{item['perspective_word']}] {item['idea']}")
|
||
|
||
asyncio.run(test())
|