althing 1.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- althing/__init__.py +46 -0
- althing/__main__.py +9 -0
- althing/__version__.py +1 -0
- althing/_compat_env.py +27 -0
- althing/_runners.py +953 -0
- althing/agent_assets/__init__.py +3 -0
- althing/agent_assets/commands/althing-poll.md +75 -0
- althing/agent_assets/skills/concept-test/SKILL.md +72 -0
- althing/agent_assets/skills/focus-group/SKILL.md +77 -0
- althing/agent_assets/skills/name-test/SKILL.md +62 -0
- althing/agent_assets/skills/pricing-probe/SKILL.md +75 -0
- althing/agent_assets/skills/survey-prescreen/SKILL.md +76 -0
- althing/aggregation.py +240 -0
- althing/analysis/__init__.py +46 -0
- althing/analysis/distribution.py +323 -0
- althing/analysis/inspect.py +586 -0
- althing/analysis/subgroup.py +168 -0
- althing/analysis/subgroup_cli.py +598 -0
- althing/analyze.py +839 -0
- althing/attachments/__init__.py +77 -0
- althing/attachments/filter.py +67 -0
- althing/attachments/models.py +50 -0
- althing/attachments/pdf.py +537 -0
- althing/attachments/store.py +111 -0
- althing/calibration.py +239 -0
- althing/checkpoint.py +763 -0
- althing/cli/__init__.py +1 -0
- althing/cli/commands.py +8080 -0
- althing/cli/mcp_install.py +487 -0
- althing/cli/output.py +106 -0
- althing/cli/parser.py +1706 -0
- althing/cli/progress.py +93 -0
- althing/cli/repl.py +122 -0
- althing/cli/slash.py +197 -0
- althing/conditions.py +261 -0
- althing/convergence.py +1091 -0
- althing/cost.py +950 -0
- althing/cost_summary.py +452 -0
- althing/credentials.py +311 -0
- althing/data/__init__.py +8 -0
- althing/data/synthbench-snapshot.json +120 -0
- althing/diff.py +331 -0
- althing/domains.py +284 -0
- althing/ensemble.py +922 -0
- althing/fetch/__init__.py +38 -0
- althing/fetch/cache.py +360 -0
- althing/fetch/ladder.py +437 -0
- althing/fetch/lower.py +199 -0
- althing/fetch/perimeter.py +494 -0
- althing/instrument.py +751 -0
- althing/integrations/__init__.py +21 -0
- althing/integrations/composio.py +345 -0
- althing/llm/__init__.py +32 -0
- althing/llm/aliases.py +138 -0
- althing/llm/capabilities.py +86 -0
- althing/llm/client.py +267 -0
- althing/llm/errors.py +162 -0
- althing/llm/fast_default.py +71 -0
- althing/llm/models.py +340 -0
- althing/llm/providers/__init__.py +18 -0
- althing/llm/providers/_anthropic_format.py +281 -0
- althing/llm/providers/_openai_format.py +381 -0
- althing/llm/providers/anthropic.py +172 -0
- althing/llm/providers/base.py +60 -0
- althing/llm/providers/gemini.py +107 -0
- althing/llm/providers/openai_compat.py +102 -0
- althing/llm/providers/openrouter.py +357 -0
- althing/llm/providers/xai.py +98 -0
- althing/llm/retry.py +113 -0
- althing/logging_config.py +80 -0
- althing/main.py +308 -0
- althing/mcp/__init__.py +7 -0
- althing/mcp/compat.py +110 -0
- althing/mcp/data.py +700 -0
- althing/mcp/sampling.py +380 -0
- althing/mcp/server.py +3371 -0
- althing/metadata.py +214 -0
- althing/metadata_migrations.py +70 -0
- althing/orchestrator.py +2119 -0
- althing/pack_diff.py +271 -0
- althing/packs/__init__.py +3 -0
- althing/packs/ai-eval-buyers.yaml +247 -0
- althing/packs/broad-professionals.yaml +251 -0
- althing/packs/developer.yaml +271 -0
- althing/packs/enterprise-ai-buyers.yaml +245 -0
- althing/packs/enterprise-buyer.yaml +252 -0
- althing/packs/general-consumer.yaml +271 -0
- althing/packs/healthcare-patient.yaml +271 -0
- althing/packs/instruments/__init__.py +1 -0
- althing/packs/instruments/churn-diagnosis.yaml +73 -0
- althing/packs/instruments/feature-prioritization.yaml +75 -0
- althing/packs/instruments/general-survey.yaml +46 -0
- althing/packs/instruments/happiness-probe.yaml +41 -0
- althing/packs/instruments/landing-page-comprehension.yaml +75 -0
- althing/packs/instruments/market-research.yaml +50 -0
- althing/packs/instruments/name-test.yaml +74 -0
- althing/packs/instruments/pricing-discovery.yaml +80 -0
- althing/packs/instruments/product-feedback.yaml +46 -0
- althing/packs/job-seekers.yaml +188 -0
- althing/packs/market-research-critics.yaml +217 -0
- althing/packs/product-research.yaml +248 -0
- althing/packs/recruiters-talent.yaml +264 -0
- althing/packs/skeptical-executives.yaml +227 -0
- althing/packs/startup-founder.yaml +242 -0
- althing/packs/students.yaml +352 -0
- althing/persistence.py +510 -0
- althing/perturbation.py +333 -0
- althing/plugins/__init__.py +34 -0
- althing/plugins/hooks.py +159 -0
- althing/plugins/lint.py +352 -0
- althing/plugins/manager.py +176 -0
- althing/plugins/manifest.py +100 -0
- althing/plugins/registry.py +66 -0
- althing/poll_summary.py +1195 -0
- althing/preflight.py +155 -0
- althing/profiles.py +264 -0
- althing/prompts.py +311 -0
- althing/py.typed +0 -0
- althing/question_budget.py +140 -0
- althing/registry/__init__.py +178 -0
- althing/registry/cache.py +223 -0
- althing/registry/fetch.py +104 -0
- althing/registry/github.py +212 -0
- althing/reporting/__init__.py +17 -0
- althing/reporting/loader.py +69 -0
- althing/reporting/markdown.py +450 -0
- althing/response_coercion.py +161 -0
- althing/routing.py +132 -0
- althing/runtime.py +552 -0
- althing/schemas/__init__.py +53 -0
- althing/schemas/v1.0.0.json +161 -0
- althing/sdk.py +1458 -0
- althing/stats.py +1522 -0
- althing/structured/__init__.py +5 -0
- althing/structured/models.py +149 -0
- althing/structured/output.py +376 -0
- althing/structured/retry.py +170 -0
- althing/structured/schemas.py +127 -0
- althing/structured/validate.py +303 -0
- althing/structured/verdict.py +146 -0
- althing/synthbench.py +768 -0
- althing/synthbench_submit.py +582 -0
- althing/synthesis.py +1529 -0
- althing/synthesis_recovery.py +467 -0
- althing/templates.py +179 -0
- althing/text_width.py +126 -0
- althing-1.7.0.dist-info/METADATA +1473 -0
- althing-1.7.0.dist-info/RECORD +156 -0
- althing-1.7.0.dist-info/WHEEL +5 -0
- althing-1.7.0.dist-info/entry_points.txt +3 -0
- althing-1.7.0.dist-info/licenses/LICENSE +21 -0
- althing-1.7.0.dist-info/top_level.txt +3 -0
- synth_panel/__init__.py +60 -0
- synth_panel/__main__.py +17 -0
- synthpanel/__init__.py +60 -0
- synthpanel/__main__.py +17 -0
althing/pack_diff.py
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""Diff two persona packs side-by-side (GH-308).
|
|
2
|
+
|
|
3
|
+
Compares packs by persona name (added / removed / unchanged / changed) and
|
|
4
|
+
summarizes composition deltas (age range, role distribution, gender split
|
|
5
|
+
when present). Loads either built-in / user-saved pack IDs or file paths.
|
|
6
|
+
|
|
7
|
+
Name-based matching is intentionally simple — renames will surface as
|
|
8
|
+
``added`` + ``removed`` rather than a single change. The README documents
|
|
9
|
+
this so users can interpret results correctly.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
from collections import Counter
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
import yaml
|
|
21
|
+
|
|
22
|
+
from althing.mcp.data import get_persona_pack
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# Data models
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
# Persona-level fields the diff inspects when both packs contain a persona of
|
|
29
|
+
# the same name. Adding new fields here is the supported way to broaden the
|
|
30
|
+
# field-level diff without touching the matching logic.
|
|
31
|
+
_FIELD_KEYS: tuple[str, ...] = (
|
|
32
|
+
"age",
|
|
33
|
+
"occupation",
|
|
34
|
+
"background",
|
|
35
|
+
"personality_traits",
|
|
36
|
+
"gender",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
_SENIORITY_PREFIXES = {"junior", "senior", "staff", "principal", "lead", "chief"}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class CompositionStats:
|
|
44
|
+
"""Aggregate demographic snapshot for a single pack.
|
|
45
|
+
|
|
46
|
+
``gender_split`` is empty when no persona declares a ``gender`` field
|
|
47
|
+
(the common case for the bundled packs). ``role_distribution`` is keyed
|
|
48
|
+
on a one-word bucket derived from each persona's ``occupation``.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
persona_count: int
|
|
52
|
+
age_min: int | None
|
|
53
|
+
age_max: int | None
|
|
54
|
+
age_mean: float | None
|
|
55
|
+
gender_split: dict[str, int] = field(default_factory=dict)
|
|
56
|
+
role_distribution: dict[str, int] = field(default_factory=dict)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class PersonaChange:
|
|
61
|
+
"""Field-level diff for a persona present in both packs.
|
|
62
|
+
|
|
63
|
+
``changed`` maps each differing field to ``{"a": <a-value>, "b": <b-value>}``.
|
|
64
|
+
For ``personality_traits`` the values are the raw lists; the renderer
|
|
65
|
+
surfaces added/removed traits separately for readability.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
name: str
|
|
69
|
+
changed: dict[str, dict[str, Any]]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class PackDiff:
|
|
74
|
+
pack_a_id: str
|
|
75
|
+
pack_b_id: str
|
|
76
|
+
pack_a_name: str
|
|
77
|
+
pack_b_name: str
|
|
78
|
+
composition_a: CompositionStats
|
|
79
|
+
composition_b: CompositionStats
|
|
80
|
+
added: list[str]
|
|
81
|
+
removed: list[str]
|
|
82
|
+
unchanged: list[str]
|
|
83
|
+
changed: list[PersonaChange]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# ---------------------------------------------------------------------------
|
|
87
|
+
# Loading
|
|
88
|
+
# ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def load_pack(source: str) -> tuple[dict[str, Any], str]:
|
|
92
|
+
"""Load a persona pack from a file path or built-in / user-saved pack ID.
|
|
93
|
+
|
|
94
|
+
File paths win when *source* points to an existing file — this lets
|
|
95
|
+
callers diff unpublished packs without first installing them. Otherwise
|
|
96
|
+
the loader falls back to :func:`althing.mcp.data.get_persona_pack`,
|
|
97
|
+
which checks user-saved packs first and then bundled packs.
|
|
98
|
+
|
|
99
|
+
Returns ``(pack_dict, source_label)`` — the label is the file stem for
|
|
100
|
+
paths and the original ID for installed packs, used for human-readable
|
|
101
|
+
output.
|
|
102
|
+
"""
|
|
103
|
+
p = Path(source)
|
|
104
|
+
if p.is_file():
|
|
105
|
+
data = yaml.safe_load(p.read_text(encoding="utf-8"))
|
|
106
|
+
if not isinstance(data, dict):
|
|
107
|
+
raise ValueError(f"Pack file does not contain a mapping: {source}")
|
|
108
|
+
return data, p.stem
|
|
109
|
+
return get_persona_pack(source), source
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
# Composition stats
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _normalize_traits(traits: Any) -> list[str]:
|
|
118
|
+
"""Collapse traits to a sorted list of stripped lowercase strings."""
|
|
119
|
+
if traits is None:
|
|
120
|
+
return []
|
|
121
|
+
if isinstance(traits, str):
|
|
122
|
+
items = [t.strip().lower() for t in traits.split(",") if t.strip()]
|
|
123
|
+
elif isinstance(traits, list):
|
|
124
|
+
items = [str(t).strip().lower() for t in traits if str(t).strip()]
|
|
125
|
+
else:
|
|
126
|
+
return []
|
|
127
|
+
return sorted(items)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _role_bucket(occupation: str) -> str:
|
|
131
|
+
"""Coarse single-word bucket for an occupation string.
|
|
132
|
+
|
|
133
|
+
Drops seniority prefixes (Junior, Senior, Staff, Principal, Lead, Chief)
|
|
134
|
+
and returns the first remaining alphabetical token, lowercased. Falls
|
|
135
|
+
back to the raw stripped string when no tokens match.
|
|
136
|
+
"""
|
|
137
|
+
tokens = re.findall(r"[A-Za-z][A-Za-z0-9-]+", occupation)
|
|
138
|
+
if not tokens:
|
|
139
|
+
return occupation.strip().lower()
|
|
140
|
+
cleaned = [t.lower() for t in tokens if t.lower() not in _SENIORITY_PREFIXES]
|
|
141
|
+
if not cleaned:
|
|
142
|
+
cleaned = [t.lower() for t in tokens]
|
|
143
|
+
return cleaned[0]
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _compose(personas: list[dict[str, Any]]) -> CompositionStats:
|
|
147
|
+
ages = [int(p["age"]) for p in personas if isinstance(p.get("age"), int)]
|
|
148
|
+
age_min = min(ages) if ages else None
|
|
149
|
+
age_max = max(ages) if ages else None
|
|
150
|
+
age_mean = round(sum(ages) / len(ages), 1) if ages else None
|
|
151
|
+
|
|
152
|
+
gender_counter: Counter[str] = Counter()
|
|
153
|
+
for p in personas:
|
|
154
|
+
g = p.get("gender")
|
|
155
|
+
if g:
|
|
156
|
+
gender_counter[str(g).lower().strip()] += 1
|
|
157
|
+
|
|
158
|
+
role_counter: Counter[str] = Counter()
|
|
159
|
+
for p in personas:
|
|
160
|
+
occ = p.get("occupation")
|
|
161
|
+
if occ:
|
|
162
|
+
role_counter[_role_bucket(str(occ))] += 1
|
|
163
|
+
|
|
164
|
+
return CompositionStats(
|
|
165
|
+
persona_count=len(personas),
|
|
166
|
+
age_min=age_min,
|
|
167
|
+
age_max=age_max,
|
|
168
|
+
age_mean=age_mean,
|
|
169
|
+
gender_split=dict(gender_counter),
|
|
170
|
+
role_distribution=dict(role_counter),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# ---------------------------------------------------------------------------
|
|
175
|
+
# Field-level diff
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _persona_field_diff(a: dict[str, Any], b: dict[str, Any]) -> dict[str, dict[str, Any]]:
|
|
180
|
+
"""Return the subset of ``_FIELD_KEYS`` that differ between two personas.
|
|
181
|
+
|
|
182
|
+
``personality_traits`` is compared after normalization (sorted, lowercase,
|
|
183
|
+
stripped) so cosmetic ordering or casing changes don't trip the diff.
|
|
184
|
+
Other fields are compared with raw equality and ``None == None`` is not
|
|
185
|
+
flagged.
|
|
186
|
+
"""
|
|
187
|
+
changed: dict[str, dict[str, Any]] = {}
|
|
188
|
+
for key in _FIELD_KEYS:
|
|
189
|
+
av = a.get(key)
|
|
190
|
+
bv = b.get(key)
|
|
191
|
+
if key == "personality_traits":
|
|
192
|
+
if _normalize_traits(av) != _normalize_traits(bv):
|
|
193
|
+
changed[key] = {"a": av, "b": bv}
|
|
194
|
+
else:
|
|
195
|
+
if av is None and bv is None:
|
|
196
|
+
continue
|
|
197
|
+
if av != bv:
|
|
198
|
+
changed[key] = {"a": av, "b": bv}
|
|
199
|
+
return changed
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def trait_delta(change: PersonaChange) -> tuple[list[str], list[str]]:
|
|
203
|
+
"""Return ``(added, removed)`` traits for a persona change, or empty lists."""
|
|
204
|
+
payload = change.changed.get("personality_traits")
|
|
205
|
+
if not payload:
|
|
206
|
+
return [], []
|
|
207
|
+
a = set(_normalize_traits(payload.get("a")))
|
|
208
|
+
b = set(_normalize_traits(payload.get("b")))
|
|
209
|
+
return sorted(b - a), sorted(a - b)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# ---------------------------------------------------------------------------
|
|
213
|
+
# Main entry point
|
|
214
|
+
# ---------------------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def compute_pack_diff(
|
|
218
|
+
pack_a: dict[str, Any],
|
|
219
|
+
pack_b: dict[str, Any],
|
|
220
|
+
*,
|
|
221
|
+
pack_a_id: str = "",
|
|
222
|
+
pack_b_id: str = "",
|
|
223
|
+
) -> PackDiff:
|
|
224
|
+
"""Compute a side-by-side diff of two persona packs.
|
|
225
|
+
|
|
226
|
+
Personas are matched by ``name`` (exact). Renames appear as one entry in
|
|
227
|
+
``added`` and one in ``removed``. ``unchanged`` lists matched personas
|
|
228
|
+
whose tracked fields are all equal; ``changed`` lists matched personas
|
|
229
|
+
with at least one differing field.
|
|
230
|
+
"""
|
|
231
|
+
personas_a = pack_a.get("personas") or []
|
|
232
|
+
personas_b = pack_b.get("personas") or []
|
|
233
|
+
if not isinstance(personas_a, list):
|
|
234
|
+
personas_a = []
|
|
235
|
+
if not isinstance(personas_b, list):
|
|
236
|
+
personas_b = []
|
|
237
|
+
|
|
238
|
+
by_name_a = {p["name"]: p for p in personas_a if isinstance(p, dict) and p.get("name")}
|
|
239
|
+
by_name_b = {p["name"]: p for p in personas_b if isinstance(p, dict) and p.get("name")}
|
|
240
|
+
|
|
241
|
+
names_a = set(by_name_a)
|
|
242
|
+
names_b = set(by_name_b)
|
|
243
|
+
|
|
244
|
+
added = sorted(names_b - names_a)
|
|
245
|
+
removed = sorted(names_a - names_b)
|
|
246
|
+
common = sorted(names_a & names_b)
|
|
247
|
+
|
|
248
|
+
unchanged: list[str] = []
|
|
249
|
+
changed: list[PersonaChange] = []
|
|
250
|
+
for name in common:
|
|
251
|
+
diff = _persona_field_diff(by_name_a[name], by_name_b[name])
|
|
252
|
+
if diff:
|
|
253
|
+
changed.append(PersonaChange(name=name, changed=diff))
|
|
254
|
+
else:
|
|
255
|
+
unchanged.append(name)
|
|
256
|
+
|
|
257
|
+
a_label = pack_a_id or str(pack_a.get("id") or "") or "pack_a"
|
|
258
|
+
b_label = pack_b_id or str(pack_b.get("id") or "") or "pack_b"
|
|
259
|
+
|
|
260
|
+
return PackDiff(
|
|
261
|
+
pack_a_id=a_label,
|
|
262
|
+
pack_b_id=b_label,
|
|
263
|
+
pack_a_name=str(pack_a.get("name") or a_label),
|
|
264
|
+
pack_b_name=str(pack_b.get("name") or b_label),
|
|
265
|
+
composition_a=_compose([p for p in personas_a if isinstance(p, dict)]),
|
|
266
|
+
composition_b=_compose([p for p in personas_b if isinstance(p, dict)]),
|
|
267
|
+
added=added,
|
|
268
|
+
removed=removed,
|
|
269
|
+
unchanged=unchanged,
|
|
270
|
+
changed=changed,
|
|
271
|
+
)
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
name: AI & ML Evaluation Buyers
|
|
2
|
+
description: >
|
|
3
|
+
ML engineers, AI researchers, eval platform leaders, devtool PMs, and
|
|
4
|
+
infra engineers across startup, enterprise, and research settings.
|
|
5
|
+
Buyer audience for LLM evaluation frameworks, benchmark platforms,
|
|
6
|
+
model-ops tooling, and AI-platform infrastructure. Useful for testing
|
|
7
|
+
ML/AI tooling products and positioning.
|
|
8
|
+
|
|
9
|
+
personas:
|
|
10
|
+
- name: Carla Mendes
|
|
11
|
+
age: 32
|
|
12
|
+
occupation: ML engineer at a multimodal startup
|
|
13
|
+
background: >
|
|
14
|
+
Runs daily evals on vision-language models. Writes eval code in
|
|
15
|
+
Python + pytest. Frustrated by how long it takes to spin up a
|
|
16
|
+
reproducible benchmark; lives in spreadsheets.
|
|
17
|
+
personality_traits:
|
|
18
|
+
- applied ML
|
|
19
|
+
- CLI-native
|
|
20
|
+
- spreadsheet-escaper
|
|
21
|
+
|
|
22
|
+
- name: Jinhua Zhao
|
|
23
|
+
age: 38
|
|
24
|
+
occupation: Senior research engineer, agent labs
|
|
25
|
+
background: >
|
|
26
|
+
Designs and runs agent benchmarks for a research org. Needs
|
|
27
|
+
reproducibility, determinism, and long-horizon trace capture.
|
|
28
|
+
Local-first philosophy matters more than dashboards.
|
|
29
|
+
personality_traits:
|
|
30
|
+
- agent researcher
|
|
31
|
+
- reproducibility-obsessed
|
|
32
|
+
- local-first
|
|
33
|
+
|
|
34
|
+
- name: Mohammed Al-Sabah
|
|
35
|
+
age: 41
|
|
36
|
+
occupation: Head of AI evaluation at an enterprise fintech
|
|
37
|
+
background: >
|
|
38
|
+
Owns model approval gates before prod. Needs frameworks that
|
|
39
|
+
produce audit-trail-ready evidence. Regulatory exposure high.
|
|
40
|
+
personality_traits:
|
|
41
|
+
- enterprise buyer
|
|
42
|
+
- audit-minded
|
|
43
|
+
- compliance-heavy
|
|
44
|
+
|
|
45
|
+
- name: Kaitlyn Park
|
|
46
|
+
age: 28
|
|
47
|
+
occupation: ML platform engineer at a seed-stage startup
|
|
48
|
+
background: >
|
|
49
|
+
Builds ML infra for a 6-person ML team. Cost-conscious, no budget
|
|
50
|
+
for commercial eval platforms. Evaluates open-source alternatives
|
|
51
|
+
by deployment simplicity and local-mode depth.
|
|
52
|
+
personality_traits:
|
|
53
|
+
- platform engineer
|
|
54
|
+
- budget-constrained
|
|
55
|
+
- OSS-native
|
|
56
|
+
|
|
57
|
+
- name: Sergei Volodin
|
|
58
|
+
age: 36
|
|
59
|
+
occupation: Prompt engineer at a developer-tools company
|
|
60
|
+
background: >
|
|
61
|
+
Iterates on system prompts for production agents. Needs quick
|
|
62
|
+
A/B eval harnesses. Hates tools that require elaborate setup for
|
|
63
|
+
what should be a one-liner.
|
|
64
|
+
personality_traits:
|
|
65
|
+
- prompt engineer
|
|
66
|
+
- iteration-speed
|
|
67
|
+
- setup-averse
|
|
68
|
+
|
|
69
|
+
- name: Priya Iyer
|
|
70
|
+
age: 33
|
|
71
|
+
occupation: Applied AI researcher, consulting firm
|
|
72
|
+
background: >
|
|
73
|
+
Runs AI POCs for client engagements. Needs frameworks that slot
|
|
74
|
+
into different client data stacks without heavy onboarding. Portable
|
|
75
|
+
BYOK model matters because clients insist on their own providers.
|
|
76
|
+
personality_traits:
|
|
77
|
+
- consultant researcher
|
|
78
|
+
- BYOK-first
|
|
79
|
+
- portable
|
|
80
|
+
|
|
81
|
+
- name: Marcus Liu
|
|
82
|
+
age: 42
|
|
83
|
+
occupation: Director of evaluation at a foundation-model lab
|
|
84
|
+
background: >
|
|
85
|
+
Compares benchmark results across internal models and competitors.
|
|
86
|
+
Already uses lm-eval-harness, HELM, and custom harnesses. Looking
|
|
87
|
+
for gaps newer eval tools fill vs what he has.
|
|
88
|
+
personality_traits:
|
|
89
|
+
- benchmark veteran
|
|
90
|
+
- comparison-buyer
|
|
91
|
+
- domain-deep
|
|
92
|
+
|
|
93
|
+
- name: Ines Costa
|
|
94
|
+
age: 29
|
|
95
|
+
occupation: ML engineer at an open-source LLM startup
|
|
96
|
+
background: >
|
|
97
|
+
Ships model releases every 6 weeks. Needs eval pipelines that run
|
|
98
|
+
in CI and block releases on regressions. Automation and CLI
|
|
99
|
+
ergonomics are table stakes.
|
|
100
|
+
personality_traits:
|
|
101
|
+
- release-cadence-driven
|
|
102
|
+
- CI-integrator
|
|
103
|
+
- automator
|
|
104
|
+
|
|
105
|
+
- name: Ahmad Qureshi
|
|
106
|
+
age: 35
|
|
107
|
+
occupation: Academic researcher, on sabbatical in industry
|
|
108
|
+
background: >
|
|
109
|
+
Evaluates tools through a publications lens. Wants reproducibility
|
|
110
|
+
hooks (seed control, config versioning) so he can cite results
|
|
111
|
+
defensibly in papers. Less interested in dashboards.
|
|
112
|
+
personality_traits:
|
|
113
|
+
- academic rigor
|
|
114
|
+
- reproducibility-first
|
|
115
|
+
- paper-oriented
|
|
116
|
+
|
|
117
|
+
- name: Rebecca Chen
|
|
118
|
+
age: 30
|
|
119
|
+
occupation: Staff engineer at a gaming AI startup
|
|
120
|
+
background: >
|
|
121
|
+
Evaluates NPC-behavior LLMs. Benchmarks need multi-turn game
|
|
122
|
+
contexts and custom scoring. Looking for frameworks flexible
|
|
123
|
+
enough to plug in domain-specific scorers.
|
|
124
|
+
personality_traits:
|
|
125
|
+
- game AI
|
|
126
|
+
- custom-scorer needer
|
|
127
|
+
- multi-turn-focused
|
|
128
|
+
|
|
129
|
+
- name: Tobias Braun
|
|
130
|
+
age: 47
|
|
131
|
+
occupation: VP engineering at a 400-person AI platform
|
|
132
|
+
background: >
|
|
133
|
+
Evaluates enterprise tooling for the team. Wants SSO, RBAC, audit
|
|
134
|
+
logs, and SOC2 readiness. Features matter less than "will it
|
|
135
|
+
survive a procurement cycle".
|
|
136
|
+
personality_traits:
|
|
137
|
+
- VPE buyer
|
|
138
|
+
- procurement-aware
|
|
139
|
+
- enterprise-hardened
|
|
140
|
+
|
|
141
|
+
- name: Himanshu Patel
|
|
142
|
+
age: 31
|
|
143
|
+
occupation: Devtool PM at a developer cloud company
|
|
144
|
+
background: >
|
|
145
|
+
Scoping a "model eval" feature for his product. Torn between
|
|
146
|
+
building vs integrating an eval framework. Wants to know the
|
|
147
|
+
open-source story deeply before committing to build.
|
|
148
|
+
personality_traits:
|
|
149
|
+
- devtool PM
|
|
150
|
+
- integrate-vs-build
|
|
151
|
+
- OSS-diligent
|
|
152
|
+
|
|
153
|
+
- name: Lin Xiao
|
|
154
|
+
age: 34
|
|
155
|
+
occupation: Senior applied scientist, conversational AI
|
|
156
|
+
background: >
|
|
157
|
+
Evaluates dialog quality. Benchmarks involve human calibration
|
|
158
|
+
and inter-rater agreement. Needs frameworks that support hybrid
|
|
159
|
+
human + automated scoring workflows.
|
|
160
|
+
personality_traits:
|
|
161
|
+
- hybrid-eval
|
|
162
|
+
- dialog-quality
|
|
163
|
+
- rater-aware
|
|
164
|
+
|
|
165
|
+
- name: Alejandro Ruiz
|
|
166
|
+
age: 39
|
|
167
|
+
occupation: Principal engineer, evaluation platforms
|
|
168
|
+
background: >
|
|
169
|
+
Ten years building internal eval tooling at FAANG. Now at a
|
|
170
|
+
Series-D startup. Evaluates externals with a "did we already
|
|
171
|
+
build this internally?" lens.
|
|
172
|
+
personality_traits:
|
|
173
|
+
- eval-veteran
|
|
174
|
+
- internal-vs-external buyer
|
|
175
|
+
- pragmatic
|
|
176
|
+
|
|
177
|
+
- name: Noemi Rossi
|
|
178
|
+
age: 27
|
|
179
|
+
occupation: Junior ML engineer at a healthtech startup
|
|
180
|
+
background: >
|
|
181
|
+
First ML job. Overwhelmed by the eval-tool landscape. Wants a
|
|
182
|
+
tool that teaches her methodology as she uses it — opinionated
|
|
183
|
+
defaults more valuable than infinite configurability.
|
|
184
|
+
personality_traits:
|
|
185
|
+
- junior
|
|
186
|
+
- learning-oriented
|
|
187
|
+
- opinionated-defaults wanted
|
|
188
|
+
|
|
189
|
+
- name: Daniel Okonkwo
|
|
190
|
+
age: 40
|
|
191
|
+
occupation: Security engineer at an AI safety org
|
|
192
|
+
background: >
|
|
193
|
+
Evaluates models for adversarial robustness and jailbreak
|
|
194
|
+
resistance. Needs red-team eval plugins. Interested in whether
|
|
195
|
+
a framework's extension points support security-focused probes.
|
|
196
|
+
personality_traits:
|
|
197
|
+
- AI safety
|
|
198
|
+
- red-team-oriented
|
|
199
|
+
- extensibility-focused
|
|
200
|
+
|
|
201
|
+
- name: Elena Georgescu
|
|
202
|
+
age: 36
|
|
203
|
+
occupation: Lead data scientist, marketing analytics
|
|
204
|
+
background: >
|
|
205
|
+
Less ML-engineer-y, more analyst. Evaluates text-generation models
|
|
206
|
+
for tone and brand-fit. Methodology-curious about how qualitative
|
|
207
|
+
outputs are scored.
|
|
208
|
+
personality_traits:
|
|
209
|
+
- analyst-tilted
|
|
210
|
+
- qualitative-focused
|
|
211
|
+
- methodology curious
|
|
212
|
+
|
|
213
|
+
- name: Kiran Joshi
|
|
214
|
+
age: 32
|
|
215
|
+
occupation: Infrastructure engineer, LLM inference platform
|
|
216
|
+
background: >
|
|
217
|
+
Evaluates inference backends (vLLM, TGI, custom). Cares about
|
|
218
|
+
throughput + cost-per-eval. Wants eval tooling to tell him when
|
|
219
|
+
a regression is model-quality vs infra-latency.
|
|
220
|
+
personality_traits:
|
|
221
|
+
- infra engineer
|
|
222
|
+
- cost-per-eval
|
|
223
|
+
- root-cause-oriented
|
|
224
|
+
|
|
225
|
+
- name: Yasmin Benjelloun
|
|
226
|
+
age: 29
|
|
227
|
+
occupation: Research software engineer, NLP lab
|
|
228
|
+
background: >
|
|
229
|
+
Writes evaluation harnesses for professors. Inheriting grad-student
|
|
230
|
+
code is a recurring pain point. Interested in whether structured
|
|
231
|
+
eval tools produce reusable, hand-off-able code.
|
|
232
|
+
personality_traits:
|
|
233
|
+
- research SWE
|
|
234
|
+
- code-continuity-focused
|
|
235
|
+
- lab-reality
|
|
236
|
+
|
|
237
|
+
- name: Gregor Novak
|
|
238
|
+
age: 44
|
|
239
|
+
occupation: Principal AI architect, consulting
|
|
240
|
+
background: >
|
|
241
|
+
Designs AI systems for F500 clients. Evaluates eval tools across
|
|
242
|
+
a dozen projects. Needs tools that scale from POC to prod without
|
|
243
|
+
rewriting. Wary of tools that only work at one end of that spectrum.
|
|
244
|
+
personality_traits:
|
|
245
|
+
- architect consultant
|
|
246
|
+
- poc-to-prod seeker
|
|
247
|
+
- vendor-weary
|