ehejin commited on
Commit
37fe63c
Β·
1 Parent(s): c374677

prolific ready

Browse files
scripts/check_prompt_format.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Print the full seller system prompt that the user study sends to the model
3
+ for a fake but realistic participant. No comparison, no training format,
4
+ just the prompt.
5
+
6
+ Usage:
7
+ cd /dfs/scratch1/echoi1/prolific_preferences
8
+ python3 scripts/print_prompt.py
9
+ """
10
+ from src.lsp_wrappers import (
11
+ format_demographics,
12
+ build_seller_system_prompt_preference,
13
+ )
14
+
15
+
16
+ DEMOGRAPHICS = {
17
+ "age": "32",
18
+ "gender": "Female",
19
+ "geographic_region": "West",
20
+ "education_level": "College graduate/some postgrad",
21
+ "race": "White",
22
+ "us_citizen": "Yes",
23
+ "marital_status": "Single",
24
+ "religion": "Agnostic",
25
+ "religious_attendance": "Never",
26
+ "political_affiliation": "Independent",
27
+ "income": "$50,000-$75,000",
28
+ "political_views": "Moderate",
29
+ "household_size": "2",
30
+ "employment_status": "Full-time employment",
31
+ }
32
+
33
+ BACKGROUND = {
34
+ "movies_criteria": (
35
+ "I look for strong character development, an interesting plot, "
36
+ "and good cinematography."
37
+ ),
38
+ "movies_enjoy": (
39
+ "I enjoy psychological thrillers and indie dramas."
40
+ ),
41
+ "movies_avoid": (
42
+ "I avoid slasher horror and broad slapstick comedies."
43
+ ),
44
+ }
45
+
46
+ PAIR = {
47
+ "pair_id": "test-pair-001",
48
+ "category": "movies",
49
+ "product_a": {
50
+ "title": "Eternal Sunshine of the Spotless Mind",
51
+ "description": ["A heartfelt sci-fi romance about memory and love."],
52
+ "features": [],
53
+ "price": "12.99",
54
+ },
55
+ "product_b": {
56
+ "title": "The Hangover",
57
+ "description": ["A wild bachelor party comedy in Las Vegas."],
58
+ "features": [],
59
+ "price": "9.99",
60
+ },
61
+ }
62
+
63
+
64
+ cfg = {
65
+ "prompt_variant": {
66
+ "personalization": True,
67
+ "include_bio": True,
68
+ },
69
+ }
70
+
71
+ demo_str = format_demographics(DEMOGRAPHICS, background=BACKGROUND, include_bio=True)
72
+ sys_prompt = build_seller_system_prompt_preference(PAIR, cfg, demo_str)
73
+
74
+ print(sys_prompt)
src/app.py CHANGED
@@ -47,7 +47,7 @@ def _init_submodule() -> None:
47
 
48
  # GitHub serves a tarball of any branch/tag/SHA at this URL.
49
  # Pinned to a specific commit SHA so future lsp changes don't break us.
50
- branch = "a71506e3b1fa74fa3427f8ab674fa68420ca42da"
51
  tarball_url = f"https://api.github.com/repos/batu-el/lsp/tarball/{branch}"
52
  tmp_tar = Path("/tmp/lsp.tar.gz")
53
  tmp_extract = Path("/tmp/lsp_extract")
 
47
 
48
  # GitHub serves a tarball of any branch/tag/SHA at this URL.
49
  # Pinned to a specific commit SHA so future lsp changes don't break us.
50
+ branch = "74582acd911f81309ba8b22cef9286c2887dda18"
51
  tarball_url = f"https://api.github.com/repos/batu-el/lsp/tarball/{branch}"
52
  tmp_tar = Path("/tmp/lsp.tar.gz")
53
  tmp_extract = Path("/tmp/lsp_extract")
src/data.py CHANGED
@@ -503,6 +503,21 @@ def _assign_from_category(category: str, n: int, user_id: str, cfg: dict) -> lis
503
  _expire_reservations(reservations)
504
  _release_returned_reservations(reservations, cfg)
505
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
506
  def is_reserved_by_other(i):
507
  r = reservations.get(str(i))
508
  return r is not None and r["user_id"] != user_id
 
503
  _expire_reservations(reservations)
504
  _release_returned_reservations(reservations, cfg)
505
 
506
+ # If this Prolific PID already has reservations (e.g. they refreshed
507
+ # the tab, got a new user_id, and came back), release the old ones
508
+ # before creating new ones. Prevents the same participant from
509
+ # accumulating multiple reservations.
510
+ if is_prolific:
511
+ stale = [
512
+ idx for idx, r in list(reservations.items())
513
+ if r.get("prolific_pid") == prolific_pid
514
+ ]
515
+ for idx in stale:
516
+ del reservations[idx]
517
+ if stale:
518
+ print(f"[ASSIGN] Released {len(stale)} prior reservations "
519
+ f"for returning PID {prolific_pid}")
520
+
521
  def is_reserved_by_other(i):
522
  r = reservations.get(str(i))
523
  return r is not None and r["user_id"] != user_id
src/lsp_wrappers.py CHANGED
@@ -11,10 +11,82 @@ sys.path, so the imports succeed.
11
  """
12
 
13
 
14
- # ── Demographics (must match training code exactly) ───────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- def format_demographics(demo: dict) -> str:
17
- return ", ".join(f"{k}: {v}" for k, v in demo.items())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
 
20
  # ── Product text helpers ──────────────────────────────────────────────────────
@@ -63,6 +135,7 @@ def pair_overview(pair: dict) -> str:
63
 
64
 
65
  # ── Seller system prompt builders ─────────────────────────────────────────────
 
66
  def build_seller_system_prompt_preference(
67
  pair: dict, cfg: dict, demographics_str: str
68
  ) -> str:
@@ -71,7 +144,6 @@ def build_seller_system_prompt_preference(
71
  a, b = pair["product_a"], pair["product_b"]
72
  result = get_seller_system_prompt(
73
  personalization=pv["personalization"],
74
- detailed_instruction=pv["detailed_instruction"],
75
  title=a.get("title"),
76
  description=_desc_str(a),
77
  features=_feat_str(a),
@@ -82,8 +154,9 @@ def build_seller_system_prompt_preference(
82
  competitor_price=f"${b.get('price')}",
83
  demographics=demographics_str,
84
  )
85
- print(f"[PROMPT] personalization={pv['personalization']}") # ← ADD
86
- print(f"[PROMPT] system_prompt[:300]: {result[:300]}") # ← ADD
 
87
  return result
88
 
89
 
@@ -94,7 +167,6 @@ def build_seller_system_prompt_likelihood(
94
  pv = cfg["prompt_variant"]
95
  return get_seller_system_prompt(
96
  personalization=pv["personalization"],
97
- detailed_instruction=pv["detailed_instruction"],
98
  title=product.get("title", ""),
99
  description=_desc_str(product),
100
  features=_feat_str(product),
 
11
  """
12
 
13
 
14
+ # ── Demographics ──────────────────────────────────────────────────────────────
15
+ #
16
+ # Key sets and field labels mirror lsp/src/data.py exactly so that a checkpoint
17
+ # trained on the lsp persona format sees identical strings at inference time.
18
+ # DO NOT reorder, re-spell, or relabel anything in here without making the same
19
+ # change in lsp/src/data.py first β€” the trained model is sensitive to the
20
+ # literal format ("Demographics: ...", "Their own words about their movie tastes:").
21
+
22
+ DEMOGRAPHIC_KEYS: tuple[str, ...] = (
23
+ "geographic_region",
24
+ "gender",
25
+ "age",
26
+ "education_level",
27
+ "race",
28
+ "us_citizen",
29
+ "marital_status",
30
+ "religion",
31
+ "religious_attendance",
32
+ "political_affiliation",
33
+ "income",
34
+ "political_views",
35
+ "household_size",
36
+ "employment_status",
37
+ )
38
+
39
+ # Maps (lsp BIO_KEY label) β†’ (key in the user study's `background` dict).
40
+ # Movies-only for now; if a future variant trains on groceries with bios
41
+ # you'll need to extend this.
42
+ BIO_KEY_LABEL_TO_BACKGROUND_KEY: dict[str, str] = {
43
+ "What matters to you when picking a movie": "movies_criteria",
44
+ "Description of movies you tend to enjoy": "movies_enjoy",
45
+ "Description of movies you tend to avoid": "movies_avoid",
46
+ }
47
+
48
+
49
+ def format_demographics(
50
+ demo: dict, background: dict | None = None, include_bio: bool = False
51
+ ) -> str:
52
+ """Render demographics (and optionally bio answers) the same way training does.
53
+
54
+ With include_bio=False (default), produces just the legacy single-line
55
+ "k: v, k: v, ..." string used by older user-study runs.
56
+
57
+ With include_bio=True, produces the multi-line block that matches
58
+ lsp/src/data.py:format_demographics:
59
 
60
+ Demographics: gender: Male, age: 30, ...
61
+ Their own words about their movie tastes:
62
+ - What matters to you when picking a movie: ...
63
+ - Description of movies you tend to enjoy: ...
64
+ - Description of movies you tend to avoid: ...
65
+
66
+ Empty/missing bio answers are silently skipped, matching training.
67
+ """
68
+ if not include_bio:
69
+ # Legacy path: single comma-joined line. Untouched for old checkpoints.
70
+ return ", ".join(f"{k}: {v}" for k, v in demo.items())
71
+
72
+ demo_pairs = ", ".join(
73
+ f"{k}: {demo[k]}" for k in DEMOGRAPHIC_KEYS if k in demo
74
+ )
75
+
76
+ bio_lines: list[str] = []
77
+ if background:
78
+ for label, bg_key in BIO_KEY_LABEL_TO_BACKGROUND_KEY.items():
79
+ value = background.get(bg_key)
80
+ if value not in (None, ""):
81
+ bio_lines.append(f"- {label}: {value}")
82
+
83
+ parts: list[str] = []
84
+ if demo_pairs:
85
+ parts.append(f"Demographics: {demo_pairs}")
86
+ if bio_lines:
87
+ parts.append("Their own words about their movie tastes:")
88
+ parts.extend(bio_lines)
89
+ return "\n".join(parts)
90
 
91
 
92
  # ── Product text helpers ──────────────────────────────────────────────────────
 
135
 
136
 
137
  # ── Seller system prompt builders ─────────────────────────────────────────────
138
+
139
  def build_seller_system_prompt_preference(
140
  pair: dict, cfg: dict, demographics_str: str
141
  ) -> str:
 
144
  a, b = pair["product_a"], pair["product_b"]
145
  result = get_seller_system_prompt(
146
  personalization=pv["personalization"],
 
147
  title=a.get("title"),
148
  description=_desc_str(a),
149
  features=_feat_str(a),
 
154
  competitor_price=f"${b.get('price')}",
155
  demographics=demographics_str,
156
  )
157
+ print(f"[PROMPT] personalization={pv['personalization']}, "
158
+ f"include_bio={pv.get('include_bio', False)}")
159
+ print(f"[PROMPT] system_prompt[:300]: {result[:300]}")
160
  return result
161
 
162
 
 
167
  pv = cfg["prompt_variant"]
168
  return get_seller_system_prompt(
169
  personalization=pv["personalization"],
 
170
  title=product.get("title", ""),
171
  description=_desc_str(product),
172
  features=_feat_str(product),
src/ui/components.py CHANGED
@@ -23,39 +23,40 @@ def inject_css() -> None:
23
  /* ── Product cards ───────────────────────────────────────────────────── */
24
  .product-card {
25
  border-radius: 10px; padding: 1rem 1.25rem; margin-bottom: 0.75rem;
 
26
  }
27
- .product-card-a { border: 2px solid #2563eb; background: #eff6ff; }
28
- .product-card-b { border: 2px solid #9333ea; background: #faf5ff; }
29
- .product-card-single { border: 2px solid #0891b2; background: #ecfeff; }
30
 
31
  .pc-header {
32
  display: flex; justify-content: space-between;
33
  align-items: flex-start; margin-bottom: 0.6rem; gap: 1rem;
34
  }
35
- .pc-title { font-size: 1.05rem; font-weight: 700; color: #1a1a2e; line-height: 1.35; flex: 1; }
36
- .pc-price { font-size: 1.2rem; font-weight: 800; white-space: nowrap; color: #16a34a; }
37
 
38
  .pc-label {
39
  display: inline-block; font-size: 0.8rem; font-weight: 700;
40
  padding: 0.2rem 0.6rem; border-radius: 99px; margin-bottom: 0.4rem;
41
  }
42
- .pc-label-a { background: #dbeafe; color: #1e40af; }
43
- .pc-label-b { background: #ede9fe; color: #6b21a8; }
44
- .pc-label-single { background: #cffafe; color: #155e75; }
45
 
46
  .pc-category-badge {
47
  display: inline-block; font-size: 0.7rem; font-weight: 600;
48
  padding: 0.12rem 0.5rem; border-radius: 99px; margin-left: 0.4rem;
49
- background: #f1f5f9; color: #475569;
50
  }
51
  .pc-section { margin-top: 0.5rem; }
52
  .pc-section-title {
53
- font-weight: 600; font-size: 0.82rem; color: #64748b;
54
  text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 0.3rem;
55
  }
56
- .pc-desc { font-size: 0.92rem; color: #334155; line-height: 1.6; }
57
- .pc-list { margin: 0; padding-left: 1.2rem; font-size: 0.92rem; color: #334155; line-height: 1.5; }
58
- .pc-list li { margin-bottom: 0.25rem; }
59
 
60
  /* ── VS divider ──────────────────────────────────────────────────────── */
61
  .vs-divider {
@@ -70,10 +71,29 @@ def inject_css() -> None:
70
 
71
  /* ── Chat bubbles ────────────────────────────────────────────────────── */
72
  .chat-wrap { max-height: 480px; overflow-y: auto; margin-bottom: 1rem; padding-right: 4px; }
73
- .bubble { padding: 0.65rem 0.9rem; border-radius: 12px; margin-bottom: 0.55rem; font-size: 0.93rem; line-height: 1.55; }
74
- .bubble-ai { background: #eff6ff; border: 1px solid #93c5fd; margin-right: 8%; }
75
- .bubble-user { background: #f0fdf4; border: 1px solid #86efac; margin-left: 8%; text-align: right; }
76
- .bubble-meta { font-size: 0.73rem; color: #94a3b8; margin-bottom: 0.15rem; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
  /* ── Section headings on background page ────────────────────────────── */
79
  hr.section-divider { border: none; border-top: 2px solid #e2e8f0; margin: 1.5rem 0 1rem 0; }
@@ -239,12 +259,6 @@ def render_chat_history(turns: list, study_type: str) -> None:
239
  )
240
  html += "</div>"
241
  st.markdown(html, unsafe_allow_html=True)
242
- st.components.v1.html("""
243
- <script>
244
- const chatWraps = window.parent.document.querySelectorAll('.chat-wrap');
245
- chatWraps.forEach(el => el.scrollTop = el.scrollHeight);
246
- </script>
247
- """, height=0)
248
 
249
 
250
  # ── Rating / familiarity helpers ──────────────────────────────────────────────
 
23
  /* ── Product cards ───────────────────────────────────────────────────── */
24
  .product-card {
25
  border-radius: 10px; padding: 1rem 1.25rem; margin-bottom: 0.75rem;
26
+ color: #1a1a2e !important; /* force dark text regardless of theme */
27
  }
28
+ .product-card-a { border: 2px solid #2563eb; background: #eff6ff !important; }
29
+ .product-card-b { border: 2px solid #9333ea; background: #faf5ff !important; }
30
+ .product-card-single { border: 2px solid #0891b2; background: #ecfeff !important; }
31
 
32
  .pc-header {
33
  display: flex; justify-content: space-between;
34
  align-items: flex-start; margin-bottom: 0.6rem; gap: 1rem;
35
  }
36
+ .pc-title { font-size: 1.05rem; font-weight: 700; color: #1a1a2e !important; line-height: 1.35; flex: 1; }
37
+ .pc-price { font-size: 1.2rem; font-weight: 800; white-space: nowrap; color: #16a34a !important; }
38
 
39
  .pc-label {
40
  display: inline-block; font-size: 0.8rem; font-weight: 700;
41
  padding: 0.2rem 0.6rem; border-radius: 99px; margin-bottom: 0.4rem;
42
  }
43
+ .pc-label-a { background: #dbeafe !important; color: #1e40af !important; }
44
+ .pc-label-b { background: #ede9fe !important; color: #6b21a8 !important; }
45
+ .pc-label-single { background: #cffafe !important; color: #155e75 !important; }
46
 
47
  .pc-category-badge {
48
  display: inline-block; font-size: 0.7rem; font-weight: 600;
49
  padding: 0.12rem 0.5rem; border-radius: 99px; margin-left: 0.4rem;
50
+ background: #f1f5f9 !important; color: #475569 !important;
51
  }
52
  .pc-section { margin-top: 0.5rem; }
53
  .pc-section-title {
54
+ font-weight: 600; font-size: 0.82rem; color: #64748b !important;
55
  text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 0.3rem;
56
  }
57
+ .pc-desc { font-size: 0.92rem; color: #334155 !important; line-height: 1.6; }
58
+ .pc-list { margin: 0; padding-left: 1.2rem; font-size: 0.92rem; color: #334155 !important; line-height: 1.5; }
59
+ .pc-list li { margin-bottom: 0.25rem; color: #334155 !important; }
60
 
61
  /* ── VS divider ──────────────────────────────────────────────────────── */
62
  .vs-divider {
 
71
 
72
  /* ── Chat bubbles ────────────────────────────────────────────────────── */
73
  .chat-wrap { max-height: 480px; overflow-y: auto; margin-bottom: 1rem; padding-right: 4px; }
74
+ .bubble {
75
+ padding: 0.65rem 0.9rem; border-radius: 12px; margin-bottom: 0.55rem;
76
+ font-size: 0.93rem; line-height: 1.55;
77
+ color: #1a1a2e !important; /* force dark text regardless of theme */
78
+ }
79
+ .bubble-ai {
80
+ background: #eff6ff !important;
81
+ border: 1px solid #93c5fd;
82
+ margin-right: 8%;
83
+ color: #1a1a2e !important;
84
+ }
85
+ .bubble-user {
86
+ background: #f0fdf4 !important;
87
+ border: 1px solid #86efac;
88
+ margin-left: 8%;
89
+ text-align: right;
90
+ color: #1a1a2e !important;
91
+ }
92
+ .bubble-meta {
93
+ font-size: 0.73rem;
94
+ color: #64748b !important;
95
+ margin-bottom: 0.15rem;
96
+ }
97
 
98
  /* ── Section headings on background page ────────────────────────────── */
99
  hr.section-divider { border: none; border-top: 2px solid #e2e8f0; margin: 1.5rem 0 1rem 0; }
 
259
  )
260
  html += "</div>"
261
  st.markdown(html, unsafe_allow_html=True)
 
 
 
 
 
 
262
 
263
 
264
  # ── Rating / familiarity helpers ──────────────────────────────────────────────
src/ui/screens_preference.py CHANGED
@@ -95,17 +95,26 @@ def screen_pair_intro(s: dict, cfg: dict) -> None:
95
  fam_b = fam_b or fam_b_opts[0]
96
  pre_val = pre_val or choices[3] # Neutral (4)
97
 
98
- pre_int = parse_rating(pre_val)
99
- demo_str = format_demographics(s["demographics"])
100
 
101
  # ── Per-item config (model + prompt variant assigned at session init) ─
102
  item_cfg = {
103
  **cfg,
104
  "prompt_variant": item.get("prompt_variant", {}),
105
  "model_name": item.get("model_name", ""),
106
- "sampler_path": item.get("sampler_path", ""),
107
  }
108
 
 
 
 
 
 
 
 
 
 
 
109
  # ── Build prompts ─────────────────────────────────────────────────────
110
  system_prompt = build_seller_system_prompt_preference(item, item_cfg, demo_str)
111
  opening_msg = opening_message_preference(item)
 
95
  fam_b = fam_b or fam_b_opts[0]
96
  pre_val = pre_val or choices[3] # Neutral (4)
97
 
98
+ pre_int = parse_rating(pre_val)
 
99
 
100
  # ── Per-item config (model + prompt variant assigned at session init) ─
101
  item_cfg = {
102
  **cfg,
103
  "prompt_variant": item.get("prompt_variant", {}),
104
  "model_name": item.get("model_name", ""),
105
+ "sampler_path": item.get("sampler_path", ""),
106
  }
107
 
108
+ # Build the demographics string in whichever format the trained model expects.
109
+ # When include_bio is True we feed the participant's own background answers
110
+ # (movies_criteria / movies_enjoy / movies_avoid) the same way training does.
111
+ include_bio = bool(item_cfg["prompt_variant"].get("include_bio", False))
112
+ demo_str = format_demographics(
113
+ s["demographics"],
114
+ background=s.get("background", {}),
115
+ include_bio=include_bio,
116
+ )
117
+
118
  # ── Build prompts ─────────────────────────────────────────────────────
119
  system_prompt = build_seller_system_prompt_preference(item, item_cfg, demo_str)
120
  opening_msg = opening_message_preference(item)
study_config.yaml CHANGED
@@ -31,10 +31,10 @@ categories:
31
  model_variants:
32
  - name: base
33
  model_name: "meta-llama/Llama-3.1-8B-Instruct"
34
- sampler_path: "tinker://4aca87ed-dcb1-5212-b86e-a1701f0dd6c6:train:0/sampler_weights/000200"
35
  prompt_variant:
36
- personalization: true
37
- detailed_instruction: true
38
  count: 2 # items using this variant for odd-numbered users
39
  # counts swap on alternating users:
40
 
@@ -46,8 +46,8 @@ min_turns: 3 # Minimum exchanges before "done" button is enab
46
  max_turns: 3 # Hard cap; input is disabled after this many exchanges
47
 
48
  # Prolific
49
- prolific_completion_code: "C7OQ65JD"
50
- prolific_study_id: "69e91ee612dfa1a58a0273d4"
51
 
52
  # HuggingFace dataset repo where results (JSON + CSV) are uploaded
53
- output_dataset_repo: "ehejin/user_study-preference-personalized_0417_250"
 
31
  model_variants:
32
  - name: base
33
  model_name: "meta-llama/Llama-3.1-8B-Instruct"
34
+ sampler_path: "tinker://90528292-6961-5d83-b389-d70a8c1ba6a6:train:0/sampler_weights/000200"
35
  prompt_variant:
36
+ personalization: false
37
+ include_bio: false
38
  count: 2 # items using this variant for odd-numbered users
39
  # counts swap on alternating users:
40
 
 
46
  max_turns: 3 # Hard cap; input is disabled after this many exchanges
47
 
48
  # Prolific
49
+ prolific_completion_code: "C3QSGJK3"
50
+ prolific_study_id: "69fd3473f636a4e92454c022"
51
 
52
  # HuggingFace dataset repo where results (JSON + CSV) are uploaded
53
+ output_dataset_repo: "ehejin/user_study-preference-personalized_0505_NP1"