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.
Files changed (231) hide show
  1. bead/__init__.py +11 -0
  2. bead/__main__.py +11 -0
  3. bead/active_learning/__init__.py +15 -0
  4. bead/active_learning/config.py +231 -0
  5. bead/active_learning/loop.py +566 -0
  6. bead/active_learning/models/__init__.py +24 -0
  7. bead/active_learning/models/base.py +852 -0
  8. bead/active_learning/models/binary.py +910 -0
  9. bead/active_learning/models/categorical.py +943 -0
  10. bead/active_learning/models/cloze.py +862 -0
  11. bead/active_learning/models/forced_choice.py +956 -0
  12. bead/active_learning/models/free_text.py +773 -0
  13. bead/active_learning/models/lora.py +365 -0
  14. bead/active_learning/models/magnitude.py +835 -0
  15. bead/active_learning/models/multi_select.py +795 -0
  16. bead/active_learning/models/ordinal_scale.py +811 -0
  17. bead/active_learning/models/peft_adapter.py +155 -0
  18. bead/active_learning/models/random_effects.py +639 -0
  19. bead/active_learning/selection.py +354 -0
  20. bead/active_learning/strategies.py +391 -0
  21. bead/active_learning/trainers/__init__.py +26 -0
  22. bead/active_learning/trainers/base.py +210 -0
  23. bead/active_learning/trainers/data_collator.py +172 -0
  24. bead/active_learning/trainers/dataset_utils.py +261 -0
  25. bead/active_learning/trainers/huggingface.py +304 -0
  26. bead/active_learning/trainers/lightning.py +324 -0
  27. bead/active_learning/trainers/metrics.py +424 -0
  28. bead/active_learning/trainers/mixed_effects.py +551 -0
  29. bead/active_learning/trainers/model_wrapper.py +509 -0
  30. bead/active_learning/trainers/registry.py +104 -0
  31. bead/adapters/__init__.py +11 -0
  32. bead/adapters/huggingface.py +61 -0
  33. bead/behavioral/__init__.py +116 -0
  34. bead/behavioral/analytics.py +646 -0
  35. bead/behavioral/extraction.py +343 -0
  36. bead/behavioral/merging.py +343 -0
  37. bead/cli/__init__.py +11 -0
  38. bead/cli/active_learning.py +513 -0
  39. bead/cli/active_learning_commands.py +779 -0
  40. bead/cli/completion.py +359 -0
  41. bead/cli/config.py +624 -0
  42. bead/cli/constraint_builders.py +286 -0
  43. bead/cli/deployment.py +859 -0
  44. bead/cli/deployment_trials.py +493 -0
  45. bead/cli/deployment_ui.py +332 -0
  46. bead/cli/display.py +378 -0
  47. bead/cli/items.py +960 -0
  48. bead/cli/items_factories.py +776 -0
  49. bead/cli/list_constraints.py +714 -0
  50. bead/cli/lists.py +490 -0
  51. bead/cli/main.py +430 -0
  52. bead/cli/models.py +877 -0
  53. bead/cli/resource_loaders.py +621 -0
  54. bead/cli/resources.py +1036 -0
  55. bead/cli/shell.py +356 -0
  56. bead/cli/simulate.py +840 -0
  57. bead/cli/templates.py +1158 -0
  58. bead/cli/training.py +1080 -0
  59. bead/cli/utils.py +614 -0
  60. bead/cli/workflow.py +1273 -0
  61. bead/config/__init__.py +68 -0
  62. bead/config/active_learning.py +1009 -0
  63. bead/config/config.py +192 -0
  64. bead/config/defaults.py +118 -0
  65. bead/config/deployment.py +217 -0
  66. bead/config/env.py +147 -0
  67. bead/config/item.py +45 -0
  68. bead/config/list.py +193 -0
  69. bead/config/loader.py +149 -0
  70. bead/config/logging.py +42 -0
  71. bead/config/model.py +49 -0
  72. bead/config/paths.py +46 -0
  73. bead/config/profiles.py +320 -0
  74. bead/config/resources.py +47 -0
  75. bead/config/serialization.py +210 -0
  76. bead/config/simulation.py +206 -0
  77. bead/config/template.py +238 -0
  78. bead/config/validation.py +267 -0
  79. bead/data/__init__.py +65 -0
  80. bead/data/base.py +87 -0
  81. bead/data/identifiers.py +97 -0
  82. bead/data/language_codes.py +61 -0
  83. bead/data/metadata.py +270 -0
  84. bead/data/range.py +123 -0
  85. bead/data/repository.py +358 -0
  86. bead/data/serialization.py +249 -0
  87. bead/data/timestamps.py +89 -0
  88. bead/data/validation.py +349 -0
  89. bead/data_collection/__init__.py +11 -0
  90. bead/data_collection/jatos.py +223 -0
  91. bead/data_collection/merger.py +154 -0
  92. bead/data_collection/prolific.py +198 -0
  93. bead/deployment/__init__.py +5 -0
  94. bead/deployment/distribution.py +402 -0
  95. bead/deployment/jatos/__init__.py +1 -0
  96. bead/deployment/jatos/api.py +200 -0
  97. bead/deployment/jatos/exporter.py +210 -0
  98. bead/deployment/jspsych/__init__.py +9 -0
  99. bead/deployment/jspsych/biome.json +44 -0
  100. bead/deployment/jspsych/config.py +411 -0
  101. bead/deployment/jspsych/generator.py +598 -0
  102. bead/deployment/jspsych/package.json +51 -0
  103. bead/deployment/jspsych/pnpm-lock.yaml +2141 -0
  104. bead/deployment/jspsych/randomizer.py +299 -0
  105. bead/deployment/jspsych/src/lib/list-distributor.test.ts +327 -0
  106. bead/deployment/jspsych/src/lib/list-distributor.ts +1282 -0
  107. bead/deployment/jspsych/src/lib/randomizer.test.ts +232 -0
  108. bead/deployment/jspsych/src/lib/randomizer.ts +367 -0
  109. bead/deployment/jspsych/src/plugins/cloze-dropdown.ts +252 -0
  110. bead/deployment/jspsych/src/plugins/forced-choice.ts +265 -0
  111. bead/deployment/jspsych/src/plugins/plugins.test.ts +141 -0
  112. bead/deployment/jspsych/src/plugins/rating.ts +248 -0
  113. bead/deployment/jspsych/src/slopit/index.ts +9 -0
  114. bead/deployment/jspsych/src/types/jatos.d.ts +256 -0
  115. bead/deployment/jspsych/src/types/jspsych.d.ts +228 -0
  116. bead/deployment/jspsych/templates/experiment.css +1 -0
  117. bead/deployment/jspsych/templates/experiment.js.template +289 -0
  118. bead/deployment/jspsych/templates/index.html +51 -0
  119. bead/deployment/jspsych/templates/randomizer.js +241 -0
  120. bead/deployment/jspsych/templates/randomizer.js.template +313 -0
  121. bead/deployment/jspsych/trials.py +723 -0
  122. bead/deployment/jspsych/tsconfig.json +23 -0
  123. bead/deployment/jspsych/tsup.config.ts +30 -0
  124. bead/deployment/jspsych/ui/__init__.py +1 -0
  125. bead/deployment/jspsych/ui/components.py +383 -0
  126. bead/deployment/jspsych/ui/styles.py +411 -0
  127. bead/dsl/__init__.py +80 -0
  128. bead/dsl/ast.py +168 -0
  129. bead/dsl/context.py +178 -0
  130. bead/dsl/errors.py +71 -0
  131. bead/dsl/evaluator.py +570 -0
  132. bead/dsl/grammar.lark +81 -0
  133. bead/dsl/parser.py +231 -0
  134. bead/dsl/stdlib.py +929 -0
  135. bead/evaluation/__init__.py +13 -0
  136. bead/evaluation/convergence.py +485 -0
  137. bead/evaluation/interannotator.py +398 -0
  138. bead/items/__init__.py +40 -0
  139. bead/items/adapters/__init__.py +70 -0
  140. bead/items/adapters/anthropic.py +224 -0
  141. bead/items/adapters/api_utils.py +167 -0
  142. bead/items/adapters/base.py +216 -0
  143. bead/items/adapters/google.py +259 -0
  144. bead/items/adapters/huggingface.py +1074 -0
  145. bead/items/adapters/openai.py +323 -0
  146. bead/items/adapters/registry.py +202 -0
  147. bead/items/adapters/sentence_transformers.py +224 -0
  148. bead/items/adapters/togetherai.py +309 -0
  149. bead/items/binary.py +515 -0
  150. bead/items/cache.py +558 -0
  151. bead/items/categorical.py +593 -0
  152. bead/items/cloze.py +757 -0
  153. bead/items/constructor.py +784 -0
  154. bead/items/forced_choice.py +413 -0
  155. bead/items/free_text.py +681 -0
  156. bead/items/generation.py +432 -0
  157. bead/items/item.py +396 -0
  158. bead/items/item_template.py +787 -0
  159. bead/items/magnitude.py +573 -0
  160. bead/items/multi_select.py +621 -0
  161. bead/items/ordinal_scale.py +569 -0
  162. bead/items/scoring.py +448 -0
  163. bead/items/validation.py +723 -0
  164. bead/lists/__init__.py +30 -0
  165. bead/lists/balancer.py +263 -0
  166. bead/lists/constraints.py +1067 -0
  167. bead/lists/experiment_list.py +286 -0
  168. bead/lists/list_collection.py +378 -0
  169. bead/lists/partitioner.py +1141 -0
  170. bead/lists/stratification.py +254 -0
  171. bead/participants/__init__.py +73 -0
  172. bead/participants/collection.py +699 -0
  173. bead/participants/merging.py +312 -0
  174. bead/participants/metadata_spec.py +491 -0
  175. bead/participants/models.py +276 -0
  176. bead/resources/__init__.py +29 -0
  177. bead/resources/adapters/__init__.py +19 -0
  178. bead/resources/adapters/base.py +104 -0
  179. bead/resources/adapters/cache.py +128 -0
  180. bead/resources/adapters/glazing.py +508 -0
  181. bead/resources/adapters/registry.py +117 -0
  182. bead/resources/adapters/unimorph.py +796 -0
  183. bead/resources/classification.py +856 -0
  184. bead/resources/constraint_builders.py +329 -0
  185. bead/resources/constraints.py +165 -0
  186. bead/resources/lexical_item.py +223 -0
  187. bead/resources/lexicon.py +744 -0
  188. bead/resources/loaders.py +209 -0
  189. bead/resources/template.py +441 -0
  190. bead/resources/template_collection.py +707 -0
  191. bead/resources/template_generation.py +349 -0
  192. bead/simulation/__init__.py +29 -0
  193. bead/simulation/annotators/__init__.py +15 -0
  194. bead/simulation/annotators/base.py +175 -0
  195. bead/simulation/annotators/distance_based.py +135 -0
  196. bead/simulation/annotators/lm_based.py +114 -0
  197. bead/simulation/annotators/oracle.py +182 -0
  198. bead/simulation/annotators/random.py +181 -0
  199. bead/simulation/dsl_extension/__init__.py +3 -0
  200. bead/simulation/noise_models/__init__.py +13 -0
  201. bead/simulation/noise_models/base.py +42 -0
  202. bead/simulation/noise_models/random_noise.py +82 -0
  203. bead/simulation/noise_models/systematic.py +132 -0
  204. bead/simulation/noise_models/temperature.py +86 -0
  205. bead/simulation/runner.py +144 -0
  206. bead/simulation/strategies/__init__.py +23 -0
  207. bead/simulation/strategies/base.py +123 -0
  208. bead/simulation/strategies/binary.py +103 -0
  209. bead/simulation/strategies/categorical.py +123 -0
  210. bead/simulation/strategies/cloze.py +224 -0
  211. bead/simulation/strategies/forced_choice.py +127 -0
  212. bead/simulation/strategies/free_text.py +105 -0
  213. bead/simulation/strategies/magnitude.py +116 -0
  214. bead/simulation/strategies/multi_select.py +129 -0
  215. bead/simulation/strategies/ordinal_scale.py +131 -0
  216. bead/templates/__init__.py +27 -0
  217. bead/templates/adapters/__init__.py +17 -0
  218. bead/templates/adapters/base.py +128 -0
  219. bead/templates/adapters/cache.py +178 -0
  220. bead/templates/adapters/huggingface.py +312 -0
  221. bead/templates/combinatorics.py +103 -0
  222. bead/templates/filler.py +605 -0
  223. bead/templates/renderers.py +177 -0
  224. bead/templates/resolver.py +178 -0
  225. bead/templates/strategies.py +1806 -0
  226. bead/templates/streaming.py +195 -0
  227. bead-0.1.0.dist-info/METADATA +212 -0
  228. bead-0.1.0.dist-info/RECORD +231 -0
  229. bead-0.1.0.dist-info/WHEEL +4 -0
  230. bead-0.1.0.dist-info/entry_points.txt +2 -0
  231. bead-0.1.0.dist-info/licenses/LICENSE +21 -0
bead/cli/config.py ADDED
@@ -0,0 +1,624 @@
1
+ """Configuration commands for bead CLI.
2
+
3
+ This module provides commands for viewing, validating, and managing configuration.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ import click
12
+ import yaml
13
+ from pydantic import ValidationError
14
+
15
+ from bead.cli.utils import (
16
+ format_output,
17
+ get_nested_value,
18
+ load_config_for_cli,
19
+ merge_config_dicts,
20
+ print_error,
21
+ print_info,
22
+ print_success,
23
+ redact_sensitive_values,
24
+ )
25
+ from bead.config import list_profiles, validate_config
26
+
27
+
28
+ @click.group()
29
+ def config() -> None:
30
+ r"""Manage configuration commands.
31
+
32
+ Provides commands for viewing, validating, and exporting configuration.
33
+
34
+ \b
35
+ Examples:
36
+ $ bead config show
37
+ $ bead config show --format json
38
+ $ bead config show --key paths.data_dir
39
+ $ bead config validate
40
+ $ bead config export --output my-config.yaml
41
+ $ bead config profiles
42
+ """
43
+
44
+
45
+ @config.command()
46
+ @click.option(
47
+ "--format",
48
+ "-f",
49
+ "format_type",
50
+ type=click.Choice(["yaml", "json", "table"], case_sensitive=False),
51
+ default="yaml",
52
+ help="Output format (default: yaml)",
53
+ )
54
+ @click.option(
55
+ "--key",
56
+ "-k",
57
+ type=str,
58
+ default=None,
59
+ help="Show specific config value (e.g., paths.data_dir)",
60
+ )
61
+ @click.option(
62
+ "--no-redact",
63
+ is_flag=True,
64
+ default=False,
65
+ help="Show sensitive values (API keys, etc.)",
66
+ )
67
+ @click.pass_context
68
+ def show(
69
+ ctx: click.Context,
70
+ format_type: str,
71
+ key: str | None,
72
+ no_redact: bool,
73
+ ) -> None:
74
+ r"""Display current configuration.
75
+
76
+ Shows the merged configuration from profile, file, and environment variables.
77
+
78
+ \b
79
+ Examples:
80
+ $ bead config show
81
+ $ bead config show --format json
82
+ $ bead config show --key paths.data_dir
83
+ $ bead config show --no-redact # Show API keys
84
+ """
85
+ config_file = ctx.obj.get("config_file")
86
+ profile = ctx.obj.get("profile", "default")
87
+ verbose = ctx.obj.get("verbose", False)
88
+
89
+ try:
90
+ cfg = load_config_for_cli(
91
+ config_file=str(config_file) if config_file else None,
92
+ profile=profile,
93
+ verbose=verbose,
94
+ )
95
+
96
+ # Convert to dict
97
+ config_dict = cfg.model_dump()
98
+
99
+ # Redact sensitive values unless --no-redact
100
+ if not no_redact:
101
+ config_dict = redact_sensitive_values(config_dict)
102
+
103
+ # Show specific key if requested
104
+ if key:
105
+ try:
106
+ value = get_nested_value(config_dict, key)
107
+ click.echo(value)
108
+ except KeyError as e:
109
+ print_error(f"Configuration key not found: {e}")
110
+ return
111
+
112
+ # Format and display
113
+ try:
114
+ output = format_output(config_dict, format_type) # type: ignore[arg-type]
115
+ click.echo(output)
116
+ except ValueError as e:
117
+ print_error(f"Failed to format output: {e}")
118
+
119
+ except Exception as e:
120
+ print_error(f"Failed to load configuration: {e}")
121
+
122
+
123
+ @config.command()
124
+ @click.option(
125
+ "--config-file",
126
+ "-c",
127
+ type=click.Path(exists=True, dir_okay=False, path_type=Path),
128
+ default=None,
129
+ help="Configuration file to validate",
130
+ )
131
+ @click.pass_context
132
+ def validate(ctx: click.Context, config_file: Path | None) -> None:
133
+ r"""Validate configuration file.
134
+
135
+ Checks YAML syntax and validates against bead configuration schema.
136
+
137
+ \b
138
+ Examples:
139
+ $ bead config validate
140
+ $ bead config validate --config-file my-config.yaml
141
+
142
+ \b
143
+ Exit codes:
144
+ 0 - Configuration is valid
145
+ 1 - Configuration is invalid
146
+ """
147
+ # Use CLI context config-file if not explicitly provided
148
+ if config_file is None:
149
+ config_file = ctx.obj.get("config_file")
150
+
151
+ if config_file is None:
152
+ print_error("No configuration file specified. Use --config-file or -c.")
153
+ return
154
+
155
+ profile = ctx.obj.get("profile", "default")
156
+ verbose = ctx.obj.get("verbose", False)
157
+
158
+ try:
159
+ # Load and validate
160
+ cfg = load_config_for_cli(
161
+ config_file=str(config_file),
162
+ profile=profile,
163
+ verbose=verbose,
164
+ )
165
+
166
+ # Additional validation
167
+ errors = validate_config(cfg)
168
+
169
+ if errors:
170
+ print_error("Configuration validation failed:")
171
+ for error in errors:
172
+ click.echo(f" • {error}", err=True)
173
+ click.get_current_context().exit(1)
174
+ else:
175
+ print_success(f"Configuration is valid: {config_file}")
176
+
177
+ except ValidationError as e:
178
+ print_error("Configuration validation failed:")
179
+ for error in e.errors():
180
+ location = " → ".join(str(loc) for loc in error["loc"])
181
+ click.echo(f" • {location}: {error['msg']}", err=True)
182
+ click.get_current_context().exit(1)
183
+
184
+ except Exception as e:
185
+ print_error(f"Failed to validate configuration: {e}")
186
+ click.get_current_context().exit(1)
187
+
188
+
189
+ @config.command()
190
+ @click.option(
191
+ "--output",
192
+ "-o",
193
+ type=click.Path(dir_okay=False, path_type=Path),
194
+ default=None,
195
+ help="Output file (default: stdout)",
196
+ )
197
+ @click.option(
198
+ "--comments",
199
+ is_flag=True,
200
+ default=False,
201
+ help="Include comments explaining each field",
202
+ )
203
+ @click.option(
204
+ "--no-redact",
205
+ is_flag=True,
206
+ default=False,
207
+ help="Include sensitive values (API keys, etc.)",
208
+ )
209
+ @click.pass_context
210
+ def export(
211
+ ctx: click.Context,
212
+ output: Path | None,
213
+ comments: bool,
214
+ no_redact: bool,
215
+ ) -> None:
216
+ r"""Export current configuration to YAML.
217
+
218
+ Exports the merged configuration (profile + file + env) to a YAML file.
219
+
220
+ \b
221
+ Examples:
222
+ $ bead config export
223
+ $ bead config export --output my-config.yaml
224
+ $ bead config export --comments # Include field explanations
225
+ $ bead config export --no-redact --output full-config.yaml
226
+ """
227
+ config_file = ctx.obj.get("config_file")
228
+ profile = ctx.obj.get("profile", "default")
229
+ verbose = ctx.obj.get("verbose", False)
230
+
231
+ try:
232
+ cfg = load_config_for_cli(
233
+ config_file=str(config_file) if config_file else None,
234
+ profile=profile,
235
+ verbose=verbose,
236
+ )
237
+
238
+ # Convert to dict
239
+ config_dict = cfg.model_dump()
240
+
241
+ # Redact sensitive values unless --no-redact
242
+ if not no_redact:
243
+ config_dict = redact_sensitive_values(config_dict)
244
+
245
+ # Add comments if requested
246
+ yaml_content = _generate_yaml_with_comments(config_dict) if comments else None
247
+
248
+ # Save or print
249
+ if output:
250
+ if yaml_content:
251
+ output.write_text(yaml_content)
252
+ else:
253
+ # Write config dict directly to YAML file
254
+ with open(output, "w") as f:
255
+ yaml.dump(config_dict, f, default_flow_style=False, sort_keys=False)
256
+ print_success(f"Configuration exported to: {output}")
257
+ else:
258
+ if yaml_content:
259
+ click.echo(yaml_content)
260
+ else:
261
+ click.echo(
262
+ yaml.dump(config_dict, default_flow_style=False, sort_keys=False)
263
+ )
264
+
265
+ except Exception as e:
266
+ print_error(f"Failed to export configuration: {e}")
267
+
268
+
269
+ @config.command()
270
+ def profiles() -> None:
271
+ r"""List available configuration profiles.
272
+
273
+ Shows all built-in profiles with descriptions.
274
+
275
+ \b
276
+ Examples:
277
+ $ bead config profiles
278
+ """
279
+ available_profiles = list_profiles()
280
+
281
+ print_info("Available configuration profiles:")
282
+ click.echo()
283
+
284
+ for profile_name in available_profiles:
285
+ click.echo(f" • {profile_name}")
286
+
287
+ click.echo()
288
+ print_info("Use --profile to select a profile:")
289
+ click.echo(" $ bead --profile dev config show")
290
+
291
+
292
+ def _generate_yaml_with_comments(config_dict: dict[str, Any]) -> str:
293
+ """Generate YAML with comments explaining fields.
294
+
295
+ Parameters
296
+ ----------
297
+ config_dict : dict[str, Any]
298
+ Configuration dictionary.
299
+
300
+ Returns
301
+ -------
302
+ str
303
+ YAML content with comments.
304
+ """
305
+ lines = ["# bead Configuration", "# Generated with comments", ""]
306
+
307
+ # Add commented sections
308
+ sections = {
309
+ "profile": "Configuration profile (default, dev, prod, test)",
310
+ "logging": "Logging configuration (level, format, file)",
311
+ "paths": "Path configuration (directories for data, models, cache)",
312
+ "resources": "Resource management (auto-download, caching, language)",
313
+ "templates": "Template filling (strategy, constraints, MLM settings)",
314
+ "models": "Model configuration (default models, GPU, API keys)",
315
+ "items": "Item construction (validation, auto-save)",
316
+ "lists": "List construction (partitioning, balancing)",
317
+ "deployment": "Deployment configuration (platform, jsPsych, plugins)",
318
+ "training": "Training configuration (framework, hyperparameters)",
319
+ }
320
+
321
+ for section, description in sections.items():
322
+ if section in config_dict:
323
+ lines.append(f"# {description}")
324
+ section_yaml = yaml.dump(
325
+ {section: config_dict[section]},
326
+ default_flow_style=False,
327
+ sort_keys=False,
328
+ )
329
+ lines.append(section_yaml.rstrip())
330
+ lines.append("")
331
+
332
+ return "\n".join(lines)
333
+
334
+
335
+ @config.command()
336
+ @click.option(
337
+ "--output",
338
+ "-o",
339
+ type=click.Path(dir_okay=False, path_type=Path),
340
+ required=True,
341
+ help="Output configuration file path",
342
+ )
343
+ @click.option(
344
+ "--selection-strategy",
345
+ type=click.Choice(["uncertainty", "diversity", "hybrid"], case_sensitive=False),
346
+ default="uncertainty",
347
+ help="Selection strategy for active learning",
348
+ )
349
+ @click.option(
350
+ "--budget",
351
+ type=int,
352
+ default=1000,
353
+ help="Annotation budget (default: 1000)",
354
+ )
355
+ @click.option(
356
+ "--convergence-threshold",
357
+ type=float,
358
+ default=0.85,
359
+ help="Convergence threshold (default: 0.85)",
360
+ )
361
+ @click.option(
362
+ "--checkpoint-interval",
363
+ type=int,
364
+ default=100,
365
+ help="Checkpoint interval (default: 100)",
366
+ )
367
+ def create_active_learning(
368
+ output: Path,
369
+ selection_strategy: str,
370
+ budget: int,
371
+ convergence_threshold: float,
372
+ checkpoint_interval: int,
373
+ ) -> None:
374
+ r"""Create active learning configuration file.
375
+
376
+ Examples
377
+ --------
378
+ $ bead config create-active-learning --output al_config.yaml
379
+ $ bead config create-active-learning --output al_config.yaml \
380
+ --selection-strategy hybrid --budget 2000
381
+ """
382
+ config_dict = {
383
+ "active_learning": {
384
+ "selection_strategy": selection_strategy,
385
+ "budget": budget,
386
+ "convergence_threshold": convergence_threshold,
387
+ "checkpoint_interval": checkpoint_interval,
388
+ "min_iterations": 10,
389
+ }
390
+ }
391
+
392
+ with open(output, "w") as f:
393
+ yaml.dump(config_dict, f, default_flow_style=False, sort_keys=False)
394
+
395
+ print_success(f"Created active learning configuration: {output}")
396
+
397
+
398
+ @config.command()
399
+ @click.option(
400
+ "--output",
401
+ "-o",
402
+ type=click.Path(dir_okay=False, path_type=Path),
403
+ required=True,
404
+ help="Output configuration file path",
405
+ )
406
+ @click.option(
407
+ "--task-type",
408
+ type=click.Choice(
409
+ [
410
+ "forced_choice",
411
+ "ordinal_scale",
412
+ "categorical",
413
+ "binary",
414
+ "multi_select",
415
+ "magnitude",
416
+ "free_text",
417
+ "cloze",
418
+ ],
419
+ case_sensitive=False,
420
+ ),
421
+ required=True,
422
+ help="Task type for model",
423
+ )
424
+ @click.option(
425
+ "--base-model",
426
+ type=str,
427
+ default="bert-base-uncased",
428
+ help="Base model name (default: bert-base-uncased)",
429
+ )
430
+ @click.option(
431
+ "--mixed-effects-mode",
432
+ type=click.Choice(
433
+ ["fixed-only", "random-intercepts", "random-slopes"],
434
+ case_sensitive=False,
435
+ ),
436
+ default="fixed-only",
437
+ help="Mixed effects mode (default: fixed-only)",
438
+ )
439
+ @click.option(
440
+ "--use-lora",
441
+ is_flag=True,
442
+ help="Use LoRA parameter-efficient fine-tuning",
443
+ )
444
+ def create_model(
445
+ output: Path,
446
+ task_type: str,
447
+ base_model: str,
448
+ mixed_effects_mode: str,
449
+ use_lora: bool,
450
+ ) -> None:
451
+ r"""Create model configuration file.
452
+
453
+ Examples
454
+ --------
455
+ $ bead config create-model --output model_config.yaml \
456
+ --task-type forced_choice
457
+ $ bead config create-model --output model_config.yaml \
458
+ --task-type ordinal_scale --mixed-effects-mode random-intercepts
459
+ """
460
+ config_dict: dict[str, Any] = {
461
+ "model": {
462
+ "task_type": task_type,
463
+ "base_model": base_model,
464
+ "mixed_effects_mode": mixed_effects_mode,
465
+ }
466
+ }
467
+
468
+ if use_lora:
469
+ config_dict["model"]["lora"] = {
470
+ "enabled": True,
471
+ "rank": 8,
472
+ "alpha": 16,
473
+ "dropout": 0.1,
474
+ }
475
+
476
+ if mixed_effects_mode in ("random-intercepts", "random-slopes"):
477
+ config_dict["model"]["mixed_effects_config"] = {
478
+ "participant_intercept": True,
479
+ "item_intercept": True,
480
+ "interaction": mixed_effects_mode == "random-slopes",
481
+ "variance_components": {
482
+ "participant_variance": 1.0,
483
+ "item_variance": 1.0,
484
+ "interaction_variance": (
485
+ 0.5 if mixed_effects_mode == "random-slopes" else 0.0
486
+ ),
487
+ },
488
+ }
489
+
490
+ with open(output, "w") as f:
491
+ yaml.dump(config_dict, f, default_flow_style=False, sort_keys=False)
492
+
493
+ print_success(f"Created model configuration: {output}")
494
+
495
+
496
+ @config.command()
497
+ @click.option(
498
+ "--output",
499
+ "-o",
500
+ type=click.Path(dir_okay=False, path_type=Path),
501
+ required=True,
502
+ help="Output configuration file path",
503
+ )
504
+ @click.option(
505
+ "--annotator-type",
506
+ type=click.Choice(
507
+ ["oracle", "random", "lm-based", "distance-based"],
508
+ case_sensitive=False,
509
+ ),
510
+ default="oracle",
511
+ help="Annotator type (default: oracle)",
512
+ )
513
+ @click.option(
514
+ "--model-name",
515
+ type=str,
516
+ help="Model name for lm-based annotator",
517
+ )
518
+ @click.option(
519
+ "--noise-model",
520
+ type=click.Choice(["random", "systematic", "temperature"], case_sensitive=False),
521
+ help="Noise model type",
522
+ )
523
+ @click.option(
524
+ "--noise-level",
525
+ type=float,
526
+ default=0.05,
527
+ help="Noise level (default: 0.05)",
528
+ )
529
+ @click.option(
530
+ "--n-annotators",
531
+ type=int,
532
+ default=20,
533
+ help="Number of simulated annotators (default: 20)",
534
+ )
535
+ def create_simulation(
536
+ output: Path,
537
+ annotator_type: str,
538
+ model_name: str | None,
539
+ noise_model: str | None,
540
+ noise_level: float,
541
+ n_annotators: int,
542
+ ) -> None:
543
+ r"""Create simulation configuration file.
544
+
545
+ Examples
546
+ --------
547
+ $ bead config create-simulation --output sim_config.yaml
548
+ $ bead config create-simulation --output sim_config.yaml \
549
+ --annotator-type lm-based --model-name gpt-4
550
+ $ bead config create-simulation --output sim_config.yaml \
551
+ --noise-model random --noise-level 0.1
552
+ """
553
+ config_dict: dict[str, Any] = {
554
+ "simulation": {
555
+ "annotator_type": annotator_type,
556
+ "n_annotators": n_annotators,
557
+ }
558
+ }
559
+
560
+ if annotator_type == "lm-based" and model_name:
561
+ config_dict["simulation"]["model_name"] = model_name
562
+
563
+ if noise_model:
564
+ config_dict["simulation"]["noise_model"] = {
565
+ "type": noise_model,
566
+ "level": noise_level,
567
+ }
568
+
569
+ with open(output, "w") as f:
570
+ yaml.dump(config_dict, f, default_flow_style=False, sort_keys=False)
571
+
572
+ print_success(f"Created simulation configuration: {output}")
573
+
574
+
575
+ @config.command()
576
+ @click.option(
577
+ "--base",
578
+ type=click.Path(exists=True, dir_okay=False, path_type=Path),
579
+ required=True,
580
+ help="Base configuration file",
581
+ )
582
+ @click.option(
583
+ "--override",
584
+ type=click.Path(exists=True, dir_okay=False, path_type=Path),
585
+ required=True,
586
+ help="Override configuration file",
587
+ )
588
+ @click.option(
589
+ "--output",
590
+ "-o",
591
+ type=click.Path(dir_okay=False, path_type=Path),
592
+ required=True,
593
+ help="Output merged configuration file",
594
+ )
595
+ def merge(
596
+ base: Path,
597
+ override: Path,
598
+ output: Path,
599
+ ) -> None:
600
+ """Merge two configuration files.
601
+
602
+ Examples
603
+ --------
604
+ $ bead config merge --base base.yaml --override custom.yaml --output merged.yaml
605
+ """
606
+ try:
607
+ # Load both files
608
+ with open(base) as f:
609
+ base_config = yaml.safe_load(f)
610
+
611
+ with open(override) as f:
612
+ override_config = yaml.safe_load(f)
613
+
614
+ # Merge recursively
615
+ merged = merge_config_dicts(base_config, override_config)
616
+
617
+ # Write merged config
618
+ with open(output, "w") as f:
619
+ yaml.dump(merged, f, default_flow_style=False, sort_keys=False)
620
+
621
+ print_success(f"Merged configurations: {output}")
622
+
623
+ except Exception as e:
624
+ print_error(f"Failed to merge configurations: {e}")