bead 0.1.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.
- bead/__init__.py +11 -0
- bead/__main__.py +11 -0
- bead/active_learning/__init__.py +15 -0
- bead/active_learning/config.py +231 -0
- bead/active_learning/loop.py +566 -0
- bead/active_learning/models/__init__.py +24 -0
- bead/active_learning/models/base.py +852 -0
- bead/active_learning/models/binary.py +910 -0
- bead/active_learning/models/categorical.py +943 -0
- bead/active_learning/models/cloze.py +862 -0
- bead/active_learning/models/forced_choice.py +956 -0
- bead/active_learning/models/free_text.py +773 -0
- bead/active_learning/models/lora.py +365 -0
- bead/active_learning/models/magnitude.py +835 -0
- bead/active_learning/models/multi_select.py +795 -0
- bead/active_learning/models/ordinal_scale.py +811 -0
- bead/active_learning/models/peft_adapter.py +155 -0
- bead/active_learning/models/random_effects.py +639 -0
- bead/active_learning/selection.py +354 -0
- bead/active_learning/strategies.py +391 -0
- bead/active_learning/trainers/__init__.py +26 -0
- bead/active_learning/trainers/base.py +210 -0
- bead/active_learning/trainers/data_collator.py +172 -0
- bead/active_learning/trainers/dataset_utils.py +261 -0
- bead/active_learning/trainers/huggingface.py +304 -0
- bead/active_learning/trainers/lightning.py +324 -0
- bead/active_learning/trainers/metrics.py +424 -0
- bead/active_learning/trainers/mixed_effects.py +551 -0
- bead/active_learning/trainers/model_wrapper.py +509 -0
- bead/active_learning/trainers/registry.py +104 -0
- bead/adapters/__init__.py +11 -0
- bead/adapters/huggingface.py +61 -0
- bead/behavioral/__init__.py +116 -0
- bead/behavioral/analytics.py +646 -0
- bead/behavioral/extraction.py +343 -0
- bead/behavioral/merging.py +343 -0
- bead/cli/__init__.py +11 -0
- bead/cli/active_learning.py +513 -0
- bead/cli/active_learning_commands.py +779 -0
- bead/cli/completion.py +359 -0
- bead/cli/config.py +624 -0
- bead/cli/constraint_builders.py +286 -0
- bead/cli/deployment.py +859 -0
- bead/cli/deployment_trials.py +493 -0
- bead/cli/deployment_ui.py +332 -0
- bead/cli/display.py +378 -0
- bead/cli/items.py +960 -0
- bead/cli/items_factories.py +776 -0
- bead/cli/list_constraints.py +714 -0
- bead/cli/lists.py +490 -0
- bead/cli/main.py +430 -0
- bead/cli/models.py +877 -0
- bead/cli/resource_loaders.py +621 -0
- bead/cli/resources.py +1036 -0
- bead/cli/shell.py +356 -0
- bead/cli/simulate.py +840 -0
- bead/cli/templates.py +1158 -0
- bead/cli/training.py +1080 -0
- bead/cli/utils.py +614 -0
- bead/cli/workflow.py +1273 -0
- bead/config/__init__.py +68 -0
- bead/config/active_learning.py +1009 -0
- bead/config/config.py +192 -0
- bead/config/defaults.py +118 -0
- bead/config/deployment.py +217 -0
- bead/config/env.py +147 -0
- bead/config/item.py +45 -0
- bead/config/list.py +193 -0
- bead/config/loader.py +149 -0
- bead/config/logging.py +42 -0
- bead/config/model.py +49 -0
- bead/config/paths.py +46 -0
- bead/config/profiles.py +320 -0
- bead/config/resources.py +47 -0
- bead/config/serialization.py +210 -0
- bead/config/simulation.py +206 -0
- bead/config/template.py +238 -0
- bead/config/validation.py +267 -0
- bead/data/__init__.py +65 -0
- bead/data/base.py +87 -0
- bead/data/identifiers.py +97 -0
- bead/data/language_codes.py +61 -0
- bead/data/metadata.py +270 -0
- bead/data/range.py +123 -0
- bead/data/repository.py +358 -0
- bead/data/serialization.py +249 -0
- bead/data/timestamps.py +89 -0
- bead/data/validation.py +349 -0
- bead/data_collection/__init__.py +11 -0
- bead/data_collection/jatos.py +223 -0
- bead/data_collection/merger.py +154 -0
- bead/data_collection/prolific.py +198 -0
- bead/deployment/__init__.py +5 -0
- bead/deployment/distribution.py +402 -0
- bead/deployment/jatos/__init__.py +1 -0
- bead/deployment/jatos/api.py +200 -0
- bead/deployment/jatos/exporter.py +210 -0
- bead/deployment/jspsych/__init__.py +9 -0
- bead/deployment/jspsych/biome.json +44 -0
- bead/deployment/jspsych/config.py +411 -0
- bead/deployment/jspsych/generator.py +598 -0
- bead/deployment/jspsych/package.json +51 -0
- bead/deployment/jspsych/pnpm-lock.yaml +2141 -0
- bead/deployment/jspsych/randomizer.py +299 -0
- bead/deployment/jspsych/src/lib/list-distributor.test.ts +327 -0
- bead/deployment/jspsych/src/lib/list-distributor.ts +1282 -0
- bead/deployment/jspsych/src/lib/randomizer.test.ts +232 -0
- bead/deployment/jspsych/src/lib/randomizer.ts +367 -0
- bead/deployment/jspsych/src/plugins/cloze-dropdown.ts +252 -0
- bead/deployment/jspsych/src/plugins/forced-choice.ts +265 -0
- bead/deployment/jspsych/src/plugins/plugins.test.ts +141 -0
- bead/deployment/jspsych/src/plugins/rating.ts +248 -0
- bead/deployment/jspsych/src/slopit/index.ts +9 -0
- bead/deployment/jspsych/src/types/jatos.d.ts +256 -0
- bead/deployment/jspsych/src/types/jspsych.d.ts +228 -0
- bead/deployment/jspsych/templates/experiment.css +1 -0
- bead/deployment/jspsych/templates/experiment.js.template +289 -0
- bead/deployment/jspsych/templates/index.html +51 -0
- bead/deployment/jspsych/templates/randomizer.js +241 -0
- bead/deployment/jspsych/templates/randomizer.js.template +313 -0
- bead/deployment/jspsych/trials.py +723 -0
- bead/deployment/jspsych/tsconfig.json +23 -0
- bead/deployment/jspsych/tsup.config.ts +30 -0
- bead/deployment/jspsych/ui/__init__.py +1 -0
- bead/deployment/jspsych/ui/components.py +383 -0
- bead/deployment/jspsych/ui/styles.py +411 -0
- bead/dsl/__init__.py +80 -0
- bead/dsl/ast.py +168 -0
- bead/dsl/context.py +178 -0
- bead/dsl/errors.py +71 -0
- bead/dsl/evaluator.py +570 -0
- bead/dsl/grammar.lark +81 -0
- bead/dsl/parser.py +231 -0
- bead/dsl/stdlib.py +929 -0
- bead/evaluation/__init__.py +13 -0
- bead/evaluation/convergence.py +485 -0
- bead/evaluation/interannotator.py +398 -0
- bead/items/__init__.py +40 -0
- bead/items/adapters/__init__.py +70 -0
- bead/items/adapters/anthropic.py +224 -0
- bead/items/adapters/api_utils.py +167 -0
- bead/items/adapters/base.py +216 -0
- bead/items/adapters/google.py +259 -0
- bead/items/adapters/huggingface.py +1074 -0
- bead/items/adapters/openai.py +323 -0
- bead/items/adapters/registry.py +202 -0
- bead/items/adapters/sentence_transformers.py +224 -0
- bead/items/adapters/togetherai.py +309 -0
- bead/items/binary.py +515 -0
- bead/items/cache.py +558 -0
- bead/items/categorical.py +593 -0
- bead/items/cloze.py +757 -0
- bead/items/constructor.py +784 -0
- bead/items/forced_choice.py +413 -0
- bead/items/free_text.py +681 -0
- bead/items/generation.py +432 -0
- bead/items/item.py +396 -0
- bead/items/item_template.py +787 -0
- bead/items/magnitude.py +573 -0
- bead/items/multi_select.py +621 -0
- bead/items/ordinal_scale.py +569 -0
- bead/items/scoring.py +448 -0
- bead/items/validation.py +723 -0
- bead/lists/__init__.py +30 -0
- bead/lists/balancer.py +263 -0
- bead/lists/constraints.py +1067 -0
- bead/lists/experiment_list.py +286 -0
- bead/lists/list_collection.py +378 -0
- bead/lists/partitioner.py +1141 -0
- bead/lists/stratification.py +254 -0
- bead/participants/__init__.py +73 -0
- bead/participants/collection.py +699 -0
- bead/participants/merging.py +312 -0
- bead/participants/metadata_spec.py +491 -0
- bead/participants/models.py +276 -0
- bead/resources/__init__.py +29 -0
- bead/resources/adapters/__init__.py +19 -0
- bead/resources/adapters/base.py +104 -0
- bead/resources/adapters/cache.py +128 -0
- bead/resources/adapters/glazing.py +508 -0
- bead/resources/adapters/registry.py +117 -0
- bead/resources/adapters/unimorph.py +796 -0
- bead/resources/classification.py +856 -0
- bead/resources/constraint_builders.py +329 -0
- bead/resources/constraints.py +165 -0
- bead/resources/lexical_item.py +223 -0
- bead/resources/lexicon.py +744 -0
- bead/resources/loaders.py +209 -0
- bead/resources/template.py +441 -0
- bead/resources/template_collection.py +707 -0
- bead/resources/template_generation.py +349 -0
- bead/simulation/__init__.py +29 -0
- bead/simulation/annotators/__init__.py +15 -0
- bead/simulation/annotators/base.py +175 -0
- bead/simulation/annotators/distance_based.py +135 -0
- bead/simulation/annotators/lm_based.py +114 -0
- bead/simulation/annotators/oracle.py +182 -0
- bead/simulation/annotators/random.py +181 -0
- bead/simulation/dsl_extension/__init__.py +3 -0
- bead/simulation/noise_models/__init__.py +13 -0
- bead/simulation/noise_models/base.py +42 -0
- bead/simulation/noise_models/random_noise.py +82 -0
- bead/simulation/noise_models/systematic.py +132 -0
- bead/simulation/noise_models/temperature.py +86 -0
- bead/simulation/runner.py +144 -0
- bead/simulation/strategies/__init__.py +23 -0
- bead/simulation/strategies/base.py +123 -0
- bead/simulation/strategies/binary.py +103 -0
- bead/simulation/strategies/categorical.py +123 -0
- bead/simulation/strategies/cloze.py +224 -0
- bead/simulation/strategies/forced_choice.py +127 -0
- bead/simulation/strategies/free_text.py +105 -0
- bead/simulation/strategies/magnitude.py +116 -0
- bead/simulation/strategies/multi_select.py +129 -0
- bead/simulation/strategies/ordinal_scale.py +131 -0
- bead/templates/__init__.py +27 -0
- bead/templates/adapters/__init__.py +17 -0
- bead/templates/adapters/base.py +128 -0
- bead/templates/adapters/cache.py +178 -0
- bead/templates/adapters/huggingface.py +312 -0
- bead/templates/combinatorics.py +103 -0
- bead/templates/filler.py +605 -0
- bead/templates/renderers.py +177 -0
- bead/templates/resolver.py +178 -0
- bead/templates/strategies.py +1806 -0
- bead/templates/streaming.py +195 -0
- bead-0.1.0.dist-info/METADATA +212 -0
- bead-0.1.0.dist-info/RECORD +231 -0
- bead-0.1.0.dist-info/WHEEL +4 -0
- bead-0.1.0.dist-info/entry_points.txt +2 -0
- bead-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
"""Task-type-specific item creation commands for bead CLI.
|
|
2
|
+
|
|
3
|
+
This module provides CLI commands for creating experimental items for all 8 supported
|
|
4
|
+
task types. Each task type has specialized creation functions that wrap the core
|
|
5
|
+
utilities in bead.items.
|
|
6
|
+
|
|
7
|
+
Supported task types:
|
|
8
|
+
- forced_choice: N-AFC (2AFC, 3AFC, etc.)
|
|
9
|
+
- ordinal_scale: Likert scales, sliders
|
|
10
|
+
- categorical: Unordered categories (NLI, semantic relations)
|
|
11
|
+
- binary: Yes/No, True/False
|
|
12
|
+
- multi_select: Multiple selection (checkboxes)
|
|
13
|
+
- magnitude: Unbounded numeric (reading time, confidence)
|
|
14
|
+
- free_text: Open-ended text responses
|
|
15
|
+
- cloze: Fill-in-the-blank
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import itertools
|
|
21
|
+
import random
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
import click
|
|
25
|
+
from rich.console import Console
|
|
26
|
+
|
|
27
|
+
from bead.cli.display import (
|
|
28
|
+
create_progress,
|
|
29
|
+
display_file_stats,
|
|
30
|
+
print_error,
|
|
31
|
+
print_info,
|
|
32
|
+
print_success,
|
|
33
|
+
)
|
|
34
|
+
from bead.cli.utils import parse_key_value_pairs
|
|
35
|
+
from bead.items.binary import create_binary_items_from_texts
|
|
36
|
+
from bead.items.categorical import (
|
|
37
|
+
create_categorical_item,
|
|
38
|
+
create_nli_item,
|
|
39
|
+
)
|
|
40
|
+
from bead.items.cloze import create_simple_cloze_item
|
|
41
|
+
from bead.items.forced_choice import create_forced_choice_item
|
|
42
|
+
from bead.items.free_text import (
|
|
43
|
+
create_free_text_items_from_texts,
|
|
44
|
+
)
|
|
45
|
+
from bead.items.item import Item, MetadataValue
|
|
46
|
+
from bead.items.magnitude import (
|
|
47
|
+
create_magnitude_items_from_texts,
|
|
48
|
+
)
|
|
49
|
+
from bead.items.multi_select import create_multi_select_item
|
|
50
|
+
from bead.items.ordinal_scale import (
|
|
51
|
+
create_likert_7_item,
|
|
52
|
+
create_ordinal_scale_items_from_texts,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
console = Console()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# ==================== Forced Choice Commands ====================
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@click.command()
|
|
62
|
+
@click.argument("options", nargs=-1, required=True)
|
|
63
|
+
@click.option(
|
|
64
|
+
"--output",
|
|
65
|
+
"-o",
|
|
66
|
+
type=click.Path(path_type=Path),
|
|
67
|
+
required=True,
|
|
68
|
+
help="Output JSONL file",
|
|
69
|
+
)
|
|
70
|
+
@click.option(
|
|
71
|
+
"--metadata",
|
|
72
|
+
type=str,
|
|
73
|
+
help="Metadata as key=value pairs (comma-separated)",
|
|
74
|
+
)
|
|
75
|
+
def create_forced_choice(
|
|
76
|
+
options: tuple[str, ...],
|
|
77
|
+
output: Path,
|
|
78
|
+
metadata: str | None,
|
|
79
|
+
) -> None:
|
|
80
|
+
r"""Create a single forced-choice item from options.
|
|
81
|
+
|
|
82
|
+
Examples
|
|
83
|
+
--------
|
|
84
|
+
$ bead items create-forced-choice "Option A" "Option B" -o item.jsonl
|
|
85
|
+
|
|
86
|
+
$ bead items create-forced-choice "The cat" "The dog" "The bird" \\
|
|
87
|
+
--metadata "contrast=subject" -o item.jsonl
|
|
88
|
+
"""
|
|
89
|
+
try:
|
|
90
|
+
if len(options) < 2:
|
|
91
|
+
print_error("At least 2 options required")
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
# Parse metadata
|
|
95
|
+
meta_dict_str: dict[str, str] = (
|
|
96
|
+
parse_key_value_pairs(metadata) if metadata else {}
|
|
97
|
+
)
|
|
98
|
+
# Cast to MetadataValue dict (str is a valid MetadataValue)
|
|
99
|
+
meta_dict: dict[str, MetadataValue] = dict(meta_dict_str)
|
|
100
|
+
|
|
101
|
+
# Create item
|
|
102
|
+
item: Item = create_forced_choice_item(*options, metadata=meta_dict)
|
|
103
|
+
|
|
104
|
+
# Save
|
|
105
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
106
|
+
with open(output, "w") as f:
|
|
107
|
+
f.write(item.model_dump_json() + "\n")
|
|
108
|
+
|
|
109
|
+
print_success(f"Created forced-choice item: {output}")
|
|
110
|
+
|
|
111
|
+
except Exception as e:
|
|
112
|
+
print_error(f"Failed to create forced-choice item: {e}")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@click.command()
|
|
116
|
+
@click.option(
|
|
117
|
+
"--texts-file",
|
|
118
|
+
type=click.Path(exists=True, path_type=Path),
|
|
119
|
+
required=True,
|
|
120
|
+
help="File with text options (one per line)",
|
|
121
|
+
)
|
|
122
|
+
@click.option(
|
|
123
|
+
"--output",
|
|
124
|
+
"-o",
|
|
125
|
+
type=click.Path(path_type=Path),
|
|
126
|
+
required=True,
|
|
127
|
+
help="Output JSONL file",
|
|
128
|
+
)
|
|
129
|
+
@click.option(
|
|
130
|
+
"--n-alternatives",
|
|
131
|
+
type=int,
|
|
132
|
+
default=2,
|
|
133
|
+
help="Number of alternatives per item (default: 2)",
|
|
134
|
+
)
|
|
135
|
+
@click.option(
|
|
136
|
+
"--sample",
|
|
137
|
+
type=int,
|
|
138
|
+
help="Sample N items randomly",
|
|
139
|
+
)
|
|
140
|
+
def create_forced_choice_from_texts(
|
|
141
|
+
texts_file: Path,
|
|
142
|
+
output: Path,
|
|
143
|
+
n_alternatives: int,
|
|
144
|
+
sample: int | None,
|
|
145
|
+
) -> None:
|
|
146
|
+
r"""Create forced-choice items from text file.
|
|
147
|
+
|
|
148
|
+
Examples
|
|
149
|
+
--------
|
|
150
|
+
$ bead items create-forced-choice-from-texts \\
|
|
151
|
+
--texts-file sentences.txt --output items.jsonl
|
|
152
|
+
|
|
153
|
+
$ bead items create-forced-choice-from-texts \\
|
|
154
|
+
--texts-file sentences.txt --n-alternatives 3 \\
|
|
155
|
+
--sample 100 --output items.jsonl
|
|
156
|
+
"""
|
|
157
|
+
try:
|
|
158
|
+
# Load texts
|
|
159
|
+
texts: list[str] = [line.strip() for line in open(texts_file) if line.strip()]
|
|
160
|
+
print_info(f"Loaded {len(texts)} texts")
|
|
161
|
+
|
|
162
|
+
# Create items by generating all combinations of n_alternatives from texts
|
|
163
|
+
items: list[Item] = []
|
|
164
|
+
for combination in itertools.combinations(texts, n_alternatives):
|
|
165
|
+
item: Item = create_forced_choice_item(*combination)
|
|
166
|
+
items.append(item)
|
|
167
|
+
|
|
168
|
+
print_info(f"Created {len(items)} forced-choice items")
|
|
169
|
+
|
|
170
|
+
# Sample if requested
|
|
171
|
+
if sample and sample < len(items):
|
|
172
|
+
items = random.sample(items, sample)
|
|
173
|
+
print_info(f"Sampled {sample} items")
|
|
174
|
+
|
|
175
|
+
# Save
|
|
176
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
177
|
+
with open(output, "w") as f:
|
|
178
|
+
for item in items:
|
|
179
|
+
f.write(item.model_dump_json() + "\n")
|
|
180
|
+
|
|
181
|
+
display_file_stats(output, len(items), "forced-choice items")
|
|
182
|
+
|
|
183
|
+
except Exception as e:
|
|
184
|
+
print_error(f"Failed to create forced-choice items: {e}")
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# ==================== Ordinal Scale Commands ====================
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@click.command()
|
|
191
|
+
@click.option(
|
|
192
|
+
"--text",
|
|
193
|
+
type=str,
|
|
194
|
+
required=True,
|
|
195
|
+
help="Text to rate",
|
|
196
|
+
)
|
|
197
|
+
@click.option(
|
|
198
|
+
"--output",
|
|
199
|
+
"-o",
|
|
200
|
+
type=click.Path(path_type=Path),
|
|
201
|
+
required=True,
|
|
202
|
+
help="Output JSONL file",
|
|
203
|
+
)
|
|
204
|
+
@click.option(
|
|
205
|
+
"--prompt",
|
|
206
|
+
type=str,
|
|
207
|
+
default="Rate this item:",
|
|
208
|
+
help="Rating prompt",
|
|
209
|
+
)
|
|
210
|
+
def create_likert_7(
|
|
211
|
+
text: str,
|
|
212
|
+
output: Path,
|
|
213
|
+
prompt: str,
|
|
214
|
+
) -> None:
|
|
215
|
+
r"""Create a 7-point Likert scale item.
|
|
216
|
+
|
|
217
|
+
Examples
|
|
218
|
+
--------
|
|
219
|
+
$ bead items create-likert-7 --text "The cat sat on the mat" -o item.jsonl
|
|
220
|
+
|
|
221
|
+
$ bead items create-likert-7 --text "Sentence text" \\
|
|
222
|
+
--prompt "How natural is this?" -o item.jsonl
|
|
223
|
+
"""
|
|
224
|
+
try:
|
|
225
|
+
item = create_likert_7_item(text, prompt=prompt)
|
|
226
|
+
|
|
227
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
228
|
+
with open(output, "w") as f:
|
|
229
|
+
f.write(item.model_dump_json() + "\n")
|
|
230
|
+
|
|
231
|
+
print_success(f"Created Likert-7 item: {output}")
|
|
232
|
+
|
|
233
|
+
except Exception as e:
|
|
234
|
+
print_error(f"Failed to create Likert-7 item: {e}")
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@click.command()
|
|
238
|
+
@click.option(
|
|
239
|
+
"--texts-file",
|
|
240
|
+
type=click.Path(exists=True, path_type=Path),
|
|
241
|
+
required=True,
|
|
242
|
+
help="File with texts to rate (one per line)",
|
|
243
|
+
)
|
|
244
|
+
@click.option(
|
|
245
|
+
"--output",
|
|
246
|
+
"-o",
|
|
247
|
+
type=click.Path(path_type=Path),
|
|
248
|
+
required=True,
|
|
249
|
+
help="Output JSONL file",
|
|
250
|
+
)
|
|
251
|
+
@click.option(
|
|
252
|
+
"--scale-min",
|
|
253
|
+
type=int,
|
|
254
|
+
default=1,
|
|
255
|
+
help="Minimum scale value (default: 1)",
|
|
256
|
+
)
|
|
257
|
+
@click.option(
|
|
258
|
+
"--scale-max",
|
|
259
|
+
type=int,
|
|
260
|
+
default=7,
|
|
261
|
+
help="Maximum scale value (default: 7)",
|
|
262
|
+
)
|
|
263
|
+
@click.option(
|
|
264
|
+
"--prompt",
|
|
265
|
+
type=str,
|
|
266
|
+
default="Rate this item:",
|
|
267
|
+
help="Rating prompt",
|
|
268
|
+
)
|
|
269
|
+
def create_ordinal_scale_from_texts(
|
|
270
|
+
texts_file: Path,
|
|
271
|
+
output: Path,
|
|
272
|
+
scale_min: int,
|
|
273
|
+
scale_max: int,
|
|
274
|
+
prompt: str,
|
|
275
|
+
) -> None:
|
|
276
|
+
r"""Create ordinal scale items from text file.
|
|
277
|
+
|
|
278
|
+
Examples
|
|
279
|
+
--------
|
|
280
|
+
$ bead items create-ordinal-scale-from-texts \\
|
|
281
|
+
--texts-file sentences.txt --output items.jsonl
|
|
282
|
+
|
|
283
|
+
$ bead items create-ordinal-scale-from-texts \\
|
|
284
|
+
--texts-file sentences.txt --scale-min 1 --scale-max 5 \\
|
|
285
|
+
--prompt "How acceptable?" --output items.jsonl
|
|
286
|
+
"""
|
|
287
|
+
try:
|
|
288
|
+
# Load texts
|
|
289
|
+
texts = [line.strip() for line in open(texts_file) if line.strip()]
|
|
290
|
+
print_info(f"Loaded {len(texts)} texts")
|
|
291
|
+
|
|
292
|
+
# Create items
|
|
293
|
+
with create_progress() as progress:
|
|
294
|
+
task = progress.add_task(
|
|
295
|
+
"Creating ordinal scale items...", total=len(texts)
|
|
296
|
+
)
|
|
297
|
+
items = create_ordinal_scale_items_from_texts(
|
|
298
|
+
texts, scale_bounds=(scale_min, scale_max), prompt=prompt
|
|
299
|
+
)
|
|
300
|
+
progress.update(task, completed=len(texts))
|
|
301
|
+
|
|
302
|
+
# Save
|
|
303
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
304
|
+
with open(output, "w") as f:
|
|
305
|
+
for item in items:
|
|
306
|
+
f.write(item.model_dump_json() + "\n")
|
|
307
|
+
|
|
308
|
+
display_file_stats(output, len(items), "ordinal scale items")
|
|
309
|
+
|
|
310
|
+
except Exception as e:
|
|
311
|
+
print_error(f"Failed to create ordinal scale items: {e}")
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# ==================== Categorical Commands ====================
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@click.command()
|
|
318
|
+
@click.option(
|
|
319
|
+
"--premise",
|
|
320
|
+
type=str,
|
|
321
|
+
required=True,
|
|
322
|
+
help="Premise text",
|
|
323
|
+
)
|
|
324
|
+
@click.option(
|
|
325
|
+
"--hypothesis",
|
|
326
|
+
type=str,
|
|
327
|
+
required=True,
|
|
328
|
+
help="Hypothesis text",
|
|
329
|
+
)
|
|
330
|
+
@click.option(
|
|
331
|
+
"--output",
|
|
332
|
+
"-o",
|
|
333
|
+
type=click.Path(path_type=Path),
|
|
334
|
+
required=True,
|
|
335
|
+
help="Output JSONL file",
|
|
336
|
+
)
|
|
337
|
+
def create_nli(
|
|
338
|
+
premise: str,
|
|
339
|
+
hypothesis: str,
|
|
340
|
+
output: Path,
|
|
341
|
+
) -> None:
|
|
342
|
+
r"""Create an NLI (natural language inference) item.
|
|
343
|
+
|
|
344
|
+
Examples
|
|
345
|
+
--------
|
|
346
|
+
$ bead items create-nli \\
|
|
347
|
+
--premise "All dogs bark" \\
|
|
348
|
+
--hypothesis "Some dogs bark" \\
|
|
349
|
+
-o item.jsonl
|
|
350
|
+
"""
|
|
351
|
+
try:
|
|
352
|
+
item = create_nli_item(premise, hypothesis)
|
|
353
|
+
|
|
354
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
355
|
+
with open(output, "w") as f:
|
|
356
|
+
f.write(item.model_dump_json() + "\n")
|
|
357
|
+
|
|
358
|
+
print_success(f"Created NLI item: {output}")
|
|
359
|
+
|
|
360
|
+
except Exception as e:
|
|
361
|
+
print_error(f"Failed to create NLI item: {e}")
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
@click.command()
|
|
365
|
+
@click.option(
|
|
366
|
+
"--text",
|
|
367
|
+
type=str,
|
|
368
|
+
required=True,
|
|
369
|
+
help="Text for categorization",
|
|
370
|
+
)
|
|
371
|
+
@click.option(
|
|
372
|
+
"--categories",
|
|
373
|
+
type=str,
|
|
374
|
+
required=True,
|
|
375
|
+
help="Categories (comma-separated)",
|
|
376
|
+
)
|
|
377
|
+
@click.option(
|
|
378
|
+
"--output",
|
|
379
|
+
"-o",
|
|
380
|
+
type=click.Path(path_type=Path),
|
|
381
|
+
required=True,
|
|
382
|
+
help="Output JSONL file",
|
|
383
|
+
)
|
|
384
|
+
@click.option(
|
|
385
|
+
"--prompt",
|
|
386
|
+
type=str,
|
|
387
|
+
default="Categorize this item:",
|
|
388
|
+
help="Categorization prompt",
|
|
389
|
+
)
|
|
390
|
+
def create_categorical(
|
|
391
|
+
text: str,
|
|
392
|
+
categories: str,
|
|
393
|
+
output: Path,
|
|
394
|
+
prompt: str,
|
|
395
|
+
) -> None:
|
|
396
|
+
r"""Create a categorical item.
|
|
397
|
+
|
|
398
|
+
Examples
|
|
399
|
+
--------
|
|
400
|
+
$ bead items create-categorical --text "Example text" \\
|
|
401
|
+
--categories "entailment,contradiction,neutral" -o item.jsonl
|
|
402
|
+
"""
|
|
403
|
+
try:
|
|
404
|
+
cat_list = [c.strip() for c in categories.split(",")]
|
|
405
|
+
item = create_categorical_item(text, categories=cat_list, prompt=prompt)
|
|
406
|
+
|
|
407
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
408
|
+
with open(output, "w") as f:
|
|
409
|
+
f.write(item.model_dump_json() + "\n")
|
|
410
|
+
|
|
411
|
+
print_success(f"Created categorical item: {output}")
|
|
412
|
+
|
|
413
|
+
except Exception as e:
|
|
414
|
+
print_error(f"Failed to create categorical item: {e}")
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
# ==================== Binary Commands ====================
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@click.command()
|
|
421
|
+
@click.option(
|
|
422
|
+
"--texts-file",
|
|
423
|
+
type=click.Path(exists=True, path_type=Path),
|
|
424
|
+
required=True,
|
|
425
|
+
help="File with texts (one per line)",
|
|
426
|
+
)
|
|
427
|
+
@click.option(
|
|
428
|
+
"--output",
|
|
429
|
+
"-o",
|
|
430
|
+
type=click.Path(path_type=Path),
|
|
431
|
+
required=True,
|
|
432
|
+
help="Output JSONL file",
|
|
433
|
+
)
|
|
434
|
+
@click.option(
|
|
435
|
+
"--prompt",
|
|
436
|
+
type=str,
|
|
437
|
+
default="Is this acceptable?",
|
|
438
|
+
help="Binary judgment prompt",
|
|
439
|
+
)
|
|
440
|
+
def create_binary_from_texts(
|
|
441
|
+
texts_file: Path,
|
|
442
|
+
output: Path,
|
|
443
|
+
prompt: str,
|
|
444
|
+
) -> None:
|
|
445
|
+
r"""Create binary judgment items from text file.
|
|
446
|
+
|
|
447
|
+
Examples
|
|
448
|
+
--------
|
|
449
|
+
$ bead items create-binary-from-texts \\
|
|
450
|
+
--texts-file sentences.txt --output items.jsonl
|
|
451
|
+
|
|
452
|
+
$ bead items create-binary-from-texts \\
|
|
453
|
+
--texts-file sentences.txt \\
|
|
454
|
+
--prompt "Is this grammatical?" --output items.jsonl
|
|
455
|
+
"""
|
|
456
|
+
try:
|
|
457
|
+
# Load texts
|
|
458
|
+
texts = [line.strip() for line in open(texts_file) if line.strip()]
|
|
459
|
+
print_info(f"Loaded {len(texts)} texts")
|
|
460
|
+
|
|
461
|
+
# Create items
|
|
462
|
+
items = create_binary_items_from_texts(texts, prompt=prompt)
|
|
463
|
+
|
|
464
|
+
# Save
|
|
465
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
466
|
+
with open(output, "w") as f:
|
|
467
|
+
for item in items:
|
|
468
|
+
f.write(item.model_dump_json() + "\n")
|
|
469
|
+
|
|
470
|
+
display_file_stats(output, len(items), "binary items")
|
|
471
|
+
|
|
472
|
+
except Exception as e:
|
|
473
|
+
print_error(f"Failed to create binary items: {e}")
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
# ==================== Multi-Select Commands ====================
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
@click.command()
|
|
480
|
+
@click.option(
|
|
481
|
+
"--texts-file",
|
|
482
|
+
type=click.Path(exists=True, path_type=Path),
|
|
483
|
+
required=True,
|
|
484
|
+
help="File with texts (one per line)",
|
|
485
|
+
)
|
|
486
|
+
@click.option(
|
|
487
|
+
"--options",
|
|
488
|
+
type=str,
|
|
489
|
+
required=True,
|
|
490
|
+
help="Options (comma-separated)",
|
|
491
|
+
)
|
|
492
|
+
@click.option(
|
|
493
|
+
"--output",
|
|
494
|
+
"-o",
|
|
495
|
+
type=click.Path(path_type=Path),
|
|
496
|
+
required=True,
|
|
497
|
+
help="Output JSONL file",
|
|
498
|
+
)
|
|
499
|
+
@click.option(
|
|
500
|
+
"--min-selections",
|
|
501
|
+
type=int,
|
|
502
|
+
default=1,
|
|
503
|
+
help="Minimum selections (default: 1)",
|
|
504
|
+
)
|
|
505
|
+
@click.option(
|
|
506
|
+
"--max-selections",
|
|
507
|
+
type=int,
|
|
508
|
+
help="Maximum selections (default: all)",
|
|
509
|
+
)
|
|
510
|
+
def create_multi_select_from_texts(
|
|
511
|
+
texts_file: Path,
|
|
512
|
+
options: str,
|
|
513
|
+
output: Path,
|
|
514
|
+
min_selections: int,
|
|
515
|
+
max_selections: int | None,
|
|
516
|
+
) -> None:
|
|
517
|
+
r"""Create multi-select items from text file.
|
|
518
|
+
|
|
519
|
+
Examples
|
|
520
|
+
--------
|
|
521
|
+
$ bead items create-multi-select-from-texts \\
|
|
522
|
+
--texts-file sentences.txt \\
|
|
523
|
+
--options "Agent,Patient,Theme,Goal" \\
|
|
524
|
+
--output items.jsonl
|
|
525
|
+
|
|
526
|
+
$ bead items create-multi-select-from-texts \\
|
|
527
|
+
--texts-file sentences.txt \\
|
|
528
|
+
--options "Semantic,Syntactic,Pragmatic" \\
|
|
529
|
+
--min-selections 1 --max-selections 2 \\
|
|
530
|
+
--output items.jsonl
|
|
531
|
+
"""
|
|
532
|
+
try:
|
|
533
|
+
# Load texts
|
|
534
|
+
texts: list[str] = [line.strip() for line in open(texts_file) if line.strip()]
|
|
535
|
+
print_info(f"Loaded {len(texts)} texts")
|
|
536
|
+
|
|
537
|
+
# Parse options
|
|
538
|
+
option_list: list[str] = [o.strip() for o in options.split(",")]
|
|
539
|
+
|
|
540
|
+
# Create items - one multi-select item per text, using options as selections
|
|
541
|
+
items: list[Item] = []
|
|
542
|
+
for text in texts:
|
|
543
|
+
# For multi-select, we need options. Use the text as metadata
|
|
544
|
+
# and the option_list as the actual options
|
|
545
|
+
meta: dict[str, MetadataValue] = {"stimulus": text}
|
|
546
|
+
item: Item = create_multi_select_item(
|
|
547
|
+
*option_list,
|
|
548
|
+
min_selections=min_selections,
|
|
549
|
+
max_selections=max_selections,
|
|
550
|
+
metadata=meta,
|
|
551
|
+
)
|
|
552
|
+
items.append(item)
|
|
553
|
+
|
|
554
|
+
print_info(f"Created {len(items)} multi-select items")
|
|
555
|
+
|
|
556
|
+
# Save
|
|
557
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
558
|
+
with open(output, "w") as f:
|
|
559
|
+
for item in items:
|
|
560
|
+
f.write(item.model_dump_json() + "\n")
|
|
561
|
+
|
|
562
|
+
display_file_stats(output, len(items), "multi-select items")
|
|
563
|
+
|
|
564
|
+
except Exception as e:
|
|
565
|
+
print_error(f"Failed to create multi-select items: {e}")
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
# ==================== Magnitude Commands ====================
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
@click.command()
|
|
572
|
+
@click.option(
|
|
573
|
+
"--texts-file",
|
|
574
|
+
type=click.Path(exists=True, path_type=Path),
|
|
575
|
+
required=True,
|
|
576
|
+
help="File with texts (one per line)",
|
|
577
|
+
)
|
|
578
|
+
@click.option(
|
|
579
|
+
"--output",
|
|
580
|
+
"-o",
|
|
581
|
+
type=click.Path(path_type=Path),
|
|
582
|
+
required=True,
|
|
583
|
+
help="Output JSONL file",
|
|
584
|
+
)
|
|
585
|
+
@click.option(
|
|
586
|
+
"--measure",
|
|
587
|
+
type=str,
|
|
588
|
+
default="value",
|
|
589
|
+
help="Measure name (default: 'value')",
|
|
590
|
+
)
|
|
591
|
+
@click.option(
|
|
592
|
+
"--prompt",
|
|
593
|
+
type=str,
|
|
594
|
+
default="Enter value:",
|
|
595
|
+
help="Input prompt",
|
|
596
|
+
)
|
|
597
|
+
def create_magnitude_from_texts(
|
|
598
|
+
texts_file: Path,
|
|
599
|
+
output: Path,
|
|
600
|
+
measure: str,
|
|
601
|
+
prompt: str,
|
|
602
|
+
) -> None:
|
|
603
|
+
r"""Create magnitude estimation items from text file.
|
|
604
|
+
|
|
605
|
+
Examples
|
|
606
|
+
--------
|
|
607
|
+
$ bead items create-magnitude-from-texts \\
|
|
608
|
+
--texts-file sentences.txt --output items.jsonl
|
|
609
|
+
|
|
610
|
+
$ bead items create-magnitude-from-texts \\
|
|
611
|
+
--texts-file sentences.txt \\
|
|
612
|
+
--measure "reading_time_ms" \\
|
|
613
|
+
--prompt "Reading time (ms):" \\
|
|
614
|
+
--output items.jsonl
|
|
615
|
+
"""
|
|
616
|
+
try:
|
|
617
|
+
# Load texts
|
|
618
|
+
texts = [line.strip() for line in open(texts_file) if line.strip()]
|
|
619
|
+
print_info(f"Loaded {len(texts)} texts")
|
|
620
|
+
|
|
621
|
+
# Create items
|
|
622
|
+
items = create_magnitude_items_from_texts(texts, unit=measure, prompt=prompt)
|
|
623
|
+
|
|
624
|
+
# Save
|
|
625
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
626
|
+
with open(output, "w") as f:
|
|
627
|
+
for item in items:
|
|
628
|
+
f.write(item.model_dump_json() + "\n")
|
|
629
|
+
|
|
630
|
+
display_file_stats(output, len(items), "magnitude items")
|
|
631
|
+
|
|
632
|
+
except Exception as e:
|
|
633
|
+
print_error(f"Failed to create magnitude items: {e}")
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
# ==================== Free Text Commands ====================
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
@click.command()
|
|
640
|
+
@click.option(
|
|
641
|
+
"--texts-file",
|
|
642
|
+
type=click.Path(exists=True, path_type=Path),
|
|
643
|
+
required=True,
|
|
644
|
+
help="File with texts (one per line)",
|
|
645
|
+
)
|
|
646
|
+
@click.option(
|
|
647
|
+
"--output",
|
|
648
|
+
"-o",
|
|
649
|
+
type=click.Path(path_type=Path),
|
|
650
|
+
required=True,
|
|
651
|
+
help="Output JSONL file",
|
|
652
|
+
)
|
|
653
|
+
@click.option(
|
|
654
|
+
"--prompt",
|
|
655
|
+
type=str,
|
|
656
|
+
default="Provide your response:",
|
|
657
|
+
help="Response prompt",
|
|
658
|
+
)
|
|
659
|
+
def create_free_text_from_texts(
|
|
660
|
+
texts_file: Path,
|
|
661
|
+
output: Path,
|
|
662
|
+
prompt: str,
|
|
663
|
+
) -> None:
|
|
664
|
+
r"""Create free text response items from text file.
|
|
665
|
+
|
|
666
|
+
Examples
|
|
667
|
+
--------
|
|
668
|
+
$ bead items create-free-text-from-texts \\
|
|
669
|
+
--texts-file sentences.txt --output items.jsonl
|
|
670
|
+
|
|
671
|
+
$ bead items create-free-text-from-texts \\
|
|
672
|
+
--texts-file sentences.txt \\
|
|
673
|
+
--prompt "Paraphrase this sentence:" \\
|
|
674
|
+
--output items.jsonl
|
|
675
|
+
"""
|
|
676
|
+
try:
|
|
677
|
+
# Load texts
|
|
678
|
+
texts = [line.strip() for line in open(texts_file) if line.strip()]
|
|
679
|
+
print_info(f"Loaded {len(texts)} texts")
|
|
680
|
+
|
|
681
|
+
# Create items
|
|
682
|
+
items = create_free_text_items_from_texts(texts, prompt=prompt)
|
|
683
|
+
|
|
684
|
+
# Save
|
|
685
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
686
|
+
with open(output, "w") as f:
|
|
687
|
+
for item in items:
|
|
688
|
+
f.write(item.model_dump_json() + "\n")
|
|
689
|
+
|
|
690
|
+
display_file_stats(output, len(items), "free-text items")
|
|
691
|
+
|
|
692
|
+
except Exception as e:
|
|
693
|
+
print_error(f"Failed to create free-text items: {e}")
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
# ==================== Cloze Commands ====================
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
@click.command()
|
|
700
|
+
@click.option(
|
|
701
|
+
"--text",
|
|
702
|
+
type=str,
|
|
703
|
+
required=True,
|
|
704
|
+
help="Text with blank",
|
|
705
|
+
)
|
|
706
|
+
@click.option(
|
|
707
|
+
"--blank-position",
|
|
708
|
+
type=int,
|
|
709
|
+
required=True,
|
|
710
|
+
help="Position of blank (0-indexed word)",
|
|
711
|
+
)
|
|
712
|
+
@click.option(
|
|
713
|
+
"--output",
|
|
714
|
+
"-o",
|
|
715
|
+
type=click.Path(path_type=Path),
|
|
716
|
+
required=True,
|
|
717
|
+
help="Output JSONL file",
|
|
718
|
+
)
|
|
719
|
+
@click.option(
|
|
720
|
+
"--blank-label",
|
|
721
|
+
type=str,
|
|
722
|
+
default="blank",
|
|
723
|
+
help="Label for blank (default: 'blank')",
|
|
724
|
+
)
|
|
725
|
+
def create_simple_cloze(
|
|
726
|
+
text: str,
|
|
727
|
+
blank_position: int,
|
|
728
|
+
output: Path,
|
|
729
|
+
blank_label: str,
|
|
730
|
+
) -> None:
|
|
731
|
+
r"""Create a simple cloze item.
|
|
732
|
+
|
|
733
|
+
Examples
|
|
734
|
+
--------
|
|
735
|
+
$ bead items create-simple-cloze \\
|
|
736
|
+
--text "The quick brown fox" \\
|
|
737
|
+
--blank-position 1 \\
|
|
738
|
+
-o item.jsonl
|
|
739
|
+
|
|
740
|
+
$ bead items create-simple-cloze \\
|
|
741
|
+
--text "The cat sat on the mat" \\
|
|
742
|
+
--blank-position 3 \\
|
|
743
|
+
--blank-label "preposition" \\
|
|
744
|
+
-o item.jsonl
|
|
745
|
+
"""
|
|
746
|
+
try:
|
|
747
|
+
item = create_simple_cloze_item(
|
|
748
|
+
text=text,
|
|
749
|
+
blank_positions=[blank_position],
|
|
750
|
+
blank_labels=[blank_label],
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
754
|
+
with open(output, "w") as f:
|
|
755
|
+
f.write(item.model_dump_json() + "\n")
|
|
756
|
+
|
|
757
|
+
print_success(f"Created cloze item: {output}")
|
|
758
|
+
|
|
759
|
+
except Exception as e:
|
|
760
|
+
print_error(f"Failed to create cloze item: {e}")
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
# Export all commands
|
|
764
|
+
__all__ = [
|
|
765
|
+
"create_forced_choice",
|
|
766
|
+
"create_forced_choice_from_texts",
|
|
767
|
+
"create_likert_7",
|
|
768
|
+
"create_ordinal_scale_from_texts",
|
|
769
|
+
"create_nli",
|
|
770
|
+
"create_categorical",
|
|
771
|
+
"create_binary_from_texts",
|
|
772
|
+
"create_multi_select_from_texts",
|
|
773
|
+
"create_magnitude_from_texts",
|
|
774
|
+
"create_free_text_from_texts",
|
|
775
|
+
"create_simple_cloze",
|
|
776
|
+
]
|