alignmenter 0.1.0__tar.gz → 0.1.2__tar.gz

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 (79) hide show
  1. {alignmenter-0.1.0/src/alignmenter.egg-info → alignmenter-0.1.2}/PKG-INFO +44 -24
  2. {alignmenter-0.1.0 → alignmenter-0.1.2}/README.md +42 -23
  3. {alignmenter-0.1.0 → alignmenter-0.1.2}/pyproject.toml +2 -2
  4. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/cli.py +103 -4
  5. {alignmenter-0.1.0 → alignmenter-0.1.2/src/alignmenter.egg-info}/PKG-INFO +44 -24
  6. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter.egg-info/requires.txt +1 -0
  7. {alignmenter-0.1.0 → alignmenter-0.1.2}/tests/test_cli_helpers.py +72 -6
  8. alignmenter-0.1.2/tests/test_cli_run_config.py +46 -0
  9. alignmenter-0.1.0/tests/test_cli_run_config.py +0 -28
  10. {alignmenter-0.1.0 → alignmenter-0.1.2}/LICENSE +0 -0
  11. {alignmenter-0.1.0 → alignmenter-0.1.2}/setup.cfg +0 -0
  12. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/__init__.py +0 -0
  13. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/calibration/__init__.py +0 -0
  14. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/calibration/analyze.py +0 -0
  15. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/calibration/bounds.py +0 -0
  16. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/calibration/diagnose.py +0 -0
  17. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/calibration/generate.py +0 -0
  18. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/calibration/label.py +0 -0
  19. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/calibration/optimize.py +0 -0
  20. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/calibration/sampling.py +0 -0
  21. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/calibration/validate.py +0 -0
  22. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/config.py +0 -0
  23. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/data/configs/demo_config.yaml +0 -0
  24. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/data/configs/judges/safety_prompt.txt +0 -0
  25. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/data/configs/persona/default.yaml +0 -0
  26. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/data/configs/run.yaml +0 -0
  27. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/data/configs/safety_keywords.yaml +0 -0
  28. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/data/datasets/demo_conversations.jsonl +0 -0
  29. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/judges/__init__.py +0 -0
  30. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/judges/authenticity_judge.py +0 -0
  31. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/judges/prompts.py +0 -0
  32. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/providers/__init__.py +0 -0
  33. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/providers/anthropic.py +0 -0
  34. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/providers/base.py +0 -0
  35. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/providers/classifiers.py +0 -0
  36. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/providers/embeddings.py +0 -0
  37. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/providers/judges.py +0 -0
  38. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/providers/local.py +0 -0
  39. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/providers/openai.py +0 -0
  40. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/reporting/__init__.py +0 -0
  41. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/reporting/html.py +0 -0
  42. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/reporting/json_out.py +0 -0
  43. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/run_config.py +0 -0
  44. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/runner.py +0 -0
  45. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/scorers/__init__.py +0 -0
  46. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/scorers/authenticity.py +0 -0
  47. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/scorers/safety.py +0 -0
  48. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/scorers/stability.py +0 -0
  49. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/scripts/__init__.py +0 -0
  50. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/scripts/bootstrap_dataset.py +0 -0
  51. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/scripts/calibrate_persona.py +0 -0
  52. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/scripts/run_openai_demo.py +0 -0
  53. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/scripts/sanitize_dataset.py +0 -0
  54. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/utils/__init__.py +0 -0
  55. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/utils/io.py +0 -0
  56. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/utils/tokens.py +0 -0
  57. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter/utils/yaml.py +0 -0
  58. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter.egg-info/SOURCES.txt +0 -0
  59. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter.egg-info/dependency_links.txt +0 -0
  60. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter.egg-info/entry_points.txt +0 -0
  61. {alignmenter-0.1.0 → alignmenter-0.1.2}/src/alignmenter.egg-info/top_level.txt +0 -0
  62. {alignmenter-0.1.0 → alignmenter-0.1.2}/tests/test_authenticity_judge.py +0 -0
  63. {alignmenter-0.1.0 → alignmenter-0.1.2}/tests/test_calibrate_persona.py +0 -0
  64. {alignmenter-0.1.0 → alignmenter-0.1.2}/tests/test_cli_errors.py +0 -0
  65. {alignmenter-0.1.0 → alignmenter-0.1.2}/tests/test_cli_import.py +0 -0
  66. {alignmenter-0.1.0 → alignmenter-0.1.2}/tests/test_cli_init.py +0 -0
  67. {alignmenter-0.1.0 → alignmenter-0.1.2}/tests/test_config.py +0 -0
  68. {alignmenter-0.1.0 → alignmenter-0.1.2}/tests/test_judge_providers.py +0 -0
  69. {alignmenter-0.1.0 → alignmenter-0.1.2}/tests/test_offline_safety.py +0 -0
  70. {alignmenter-0.1.0 → alignmenter-0.1.2}/tests/test_persona_gpt.py +0 -0
  71. {alignmenter-0.1.0 → alignmenter-0.1.2}/tests/test_provider_local.py +0 -0
  72. {alignmenter-0.1.0 → alignmenter-0.1.2}/tests/test_provider_openai.py +0 -0
  73. {alignmenter-0.1.0 → alignmenter-0.1.2}/tests/test_providers.py +0 -0
  74. {alignmenter-0.1.0 → alignmenter-0.1.2}/tests/test_run_config_loader.py +0 -0
  75. {alignmenter-0.1.0 → alignmenter-0.1.2}/tests/test_run_openai_demo.py +0 -0
  76. {alignmenter-0.1.0 → alignmenter-0.1.2}/tests/test_runner.py +0 -0
  77. {alignmenter-0.1.0 → alignmenter-0.1.2}/tests/test_sampling.py +0 -0
  78. {alignmenter-0.1.0 → alignmenter-0.1.2}/tests/test_scorers.py +0 -0
  79. {alignmenter-0.1.0 → alignmenter-0.1.2}/tests/test_smoke.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alignmenter
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Persona-aligned evaluation toolkit for auditing conversational AI authenticity, safety, and stability.
5
5
  Author: Alignmenter
6
6
  License: Apache License
@@ -236,6 +236,7 @@ Requires-Dist: tiktoken>=0.7.0
236
236
  Requires-Dist: requests>=2.32.0
237
237
  Provides-Extra: dev
238
238
  Requires-Dist: pytest; extra == "dev"
239
+ Requires-Dist: pytest-cov; extra == "dev"
239
240
  Requires-Dist: ruff; extra == "dev"
240
241
  Requires-Dist: build; extra == "dev"
241
242
  Requires-Dist: twine; extra == "dev"
@@ -277,26 +278,23 @@ Unlike generic LLM evaluation frameworks, Alignmenter is purpose-built for **per
277
278
 
278
279
  ```bash
279
280
  # Clone the repository
280
- git clone https://github.com/yourusername/alignmenter.git
281
+ git clone https://github.com/justinGrosvenor/alignmenter.git
281
282
  cd alignmenter
282
283
 
283
284
  # Create virtual environment
284
285
  python -m venv env
285
286
  source env/bin/activate # On Windows: env\Scripts\activate
286
287
 
287
- # Install with dev dependencies
288
- pip install -e .[dev]
289
-
290
- # Optional: Install with offline safety classifier
291
- pip install -e .[dev,safety]
288
+ # Install with dev + safety extras
289
+ pip install -e ./alignmenter[dev,safety]
292
290
  ```
293
291
 
294
292
  ### Install from PyPI
295
293
 
296
294
  ```bash
297
- pip install alignmenter
295
+ pip install "alignmenter[safety]"
298
296
  alignmenter init
299
- alignmenter run --config configs/run.yaml --no-generate
297
+ alignmenter run --config configs/run.yaml --embedding sentence-transformer:all-MiniLM-L6-v2
300
298
  ```
301
299
 
302
300
  > **Note**: The `safety` extra includes `transformers` for the offline safety classifier (ProtectAI/distilled-safety-roberta). Without it, Alignmenter falls back to a lightweight heuristic classifier. See [docs/offline_safety.md](https://github.com/justinGrosvenor/alignmenter/blob/main/docs/offline_safety.md) for details.
@@ -314,13 +312,16 @@ alignmenter run \
314
312
  --persona configs/persona/default.yaml
315
313
 
316
314
  # Reuse existing transcripts without hitting the provider
317
- alignmenter run --config configs/run.yaml --no-generate
315
+ alignmenter run --config configs/run.yaml
318
316
 
319
317
  # View interactive HTML report
320
318
  alignmenter report --last
321
319
 
322
320
  # Sanitize a dataset in-place or to a new file
323
321
  alignmenter dataset sanitize datasets/demo_conversations.jsonl --out datasets/demo_sanitized.jsonl
322
+
323
+ # Generate fresh transcripts (requires provider access)
324
+ alignmenter run --config configs/run.yaml --generate-transcripts
324
325
  ```
325
326
 
326
327
  **Output:**
@@ -438,6 +439,14 @@ Quick links:
438
439
  - **[LLM Judges](https://docs.alignmenter.com/guides/llm-judges/)** - Qualitative analysis
439
440
  - **[Contributing](https://docs.alignmenter.com/contributing/)** - How to contribute
440
441
 
442
+ ---
443
+
444
+ ## Case Studies
445
+
446
+ - **[Wendy's Twitter Voice](../docs/case-studies/wendys-twitter.md)** - End-to-end calibration example using the included case-study assets. *(Available when running from the source repo; not included in the PyPI wheel.)*
447
+
448
+ ---
449
+
441
450
  ## Usage Examples
442
451
 
443
452
  ### Evaluate Multiple Models
@@ -490,11 +499,9 @@ alignmenter calibrate-persona \
490
499
 
491
500
  ```bash
492
501
  # Remove PII before evaluation
493
- alignmenter sanitize-dataset \
494
- --input prod_logs.jsonl \
502
+ alignmenter dataset sanitize prod_logs.jsonl \
495
503
  --out datasets/sanitized.jsonl \
496
- --hash-session-ids \
497
- --scrub-patterns "email|phone|ssn"
504
+ --no-use-hashing
498
505
  ```
499
506
 
500
507
  ## Persona Configuration
@@ -646,13 +653,25 @@ make report-last
646
653
 
647
654
  ## Roadmap
648
655
 
649
- - [ ] Web dashboard for team collaboration
650
- - [ ] Hosted evaluation pipelines (CI/CD SaaS)
651
- - [ ] Multi-language support (non-English personas)
652
- - [ ] Offline safety model (no API required)
653
- - [ ] Cost/latency tracking in scorers
654
- - [ ] Competitive landscape analysis
655
- - [ ] Metric cards and ablation studies
656
+ ### Completed ✅
657
+ - Three-dimensional scoring (authenticity, safety, stability)
658
+ - Multi-provider support (OpenAI, Anthropic, local models)
659
+ - HTML report cards with interactive charts
660
+ - Offline safety classifier (distilled-safety-roberta)
661
+ - LLM judges for qualitative analysis
662
+ - Budget guardrails and cost tracking
663
+ - PII sanitization tools
664
+ - Calibration workflow and diagnostics
665
+
666
+ ### In Progress 🚧
667
+ - Multi-language support (non-English personas)
668
+ - Batch processing optimizations
669
+ - Additional embedding providers
670
+
671
+ ### Future Considerations 💭
672
+ - Synthetic test case generation
673
+ - Custom metric plugins
674
+ - Advanced trait models (neural networks)
656
675
 
657
676
  ## Contributing
658
677
 
@@ -676,14 +695,15 @@ If you use Alignmenter in research, please cite:
676
695
  @software{alignmenter2024,
677
696
  title={Alignmenter: A Framework for Persona-Aligned Conversational AI Evaluation},
678
697
  author={Alignmenter Contributors},
679
- year={2024},
680
- url={https://github.com/yourusername/alignmenter}
698
+ year={2025},
699
+ url={https://github.com/justinGrosvenor/alignmenter},
700
+ license={Apache-2.0}
681
701
  }
682
702
  ```
683
703
 
684
704
  ## Support
685
705
 
686
- - **Documentation**: [docs/](https://github.com/justinGrosvenor/alignmenter/tree/main/docs)
706
+ - **Documentation**: [docs.alignmenter.com](https://docs.alignmenter.com)
687
707
  - **Issues**: [GitHub Issues](https://github.com/justinGrosvenor/alignmenter/issues)
688
708
  - **Discussions**: [GitHub Discussions](https://github.com/justinGrosvenor/alignmenter/discussions)
689
709
 
@@ -32,26 +32,23 @@ Unlike generic LLM evaluation frameworks, Alignmenter is purpose-built for **per
32
32
 
33
33
  ```bash
34
34
  # Clone the repository
35
- git clone https://github.com/yourusername/alignmenter.git
35
+ git clone https://github.com/justinGrosvenor/alignmenter.git
36
36
  cd alignmenter
37
37
 
38
38
  # Create virtual environment
39
39
  python -m venv env
40
40
  source env/bin/activate # On Windows: env\Scripts\activate
41
41
 
42
- # Install with dev dependencies
43
- pip install -e .[dev]
44
-
45
- # Optional: Install with offline safety classifier
46
- pip install -e .[dev,safety]
42
+ # Install with dev + safety extras
43
+ pip install -e ./alignmenter[dev,safety]
47
44
  ```
48
45
 
49
46
  ### Install from PyPI
50
47
 
51
48
  ```bash
52
- pip install alignmenter
49
+ pip install "alignmenter[safety]"
53
50
  alignmenter init
54
- alignmenter run --config configs/run.yaml --no-generate
51
+ alignmenter run --config configs/run.yaml --embedding sentence-transformer:all-MiniLM-L6-v2
55
52
  ```
56
53
 
57
54
  > **Note**: The `safety` extra includes `transformers` for the offline safety classifier (ProtectAI/distilled-safety-roberta). Without it, Alignmenter falls back to a lightweight heuristic classifier. See [docs/offline_safety.md](https://github.com/justinGrosvenor/alignmenter/blob/main/docs/offline_safety.md) for details.
@@ -69,13 +66,16 @@ alignmenter run \
69
66
  --persona configs/persona/default.yaml
70
67
 
71
68
  # Reuse existing transcripts without hitting the provider
72
- alignmenter run --config configs/run.yaml --no-generate
69
+ alignmenter run --config configs/run.yaml
73
70
 
74
71
  # View interactive HTML report
75
72
  alignmenter report --last
76
73
 
77
74
  # Sanitize a dataset in-place or to a new file
78
75
  alignmenter dataset sanitize datasets/demo_conversations.jsonl --out datasets/demo_sanitized.jsonl
76
+
77
+ # Generate fresh transcripts (requires provider access)
78
+ alignmenter run --config configs/run.yaml --generate-transcripts
79
79
  ```
80
80
 
81
81
  **Output:**
@@ -193,6 +193,14 @@ Quick links:
193
193
  - **[LLM Judges](https://docs.alignmenter.com/guides/llm-judges/)** - Qualitative analysis
194
194
  - **[Contributing](https://docs.alignmenter.com/contributing/)** - How to contribute
195
195
 
196
+ ---
197
+
198
+ ## Case Studies
199
+
200
+ - **[Wendy's Twitter Voice](../docs/case-studies/wendys-twitter.md)** - End-to-end calibration example using the included case-study assets. *(Available when running from the source repo; not included in the PyPI wheel.)*
201
+
202
+ ---
203
+
196
204
  ## Usage Examples
197
205
 
198
206
  ### Evaluate Multiple Models
@@ -245,11 +253,9 @@ alignmenter calibrate-persona \
245
253
 
246
254
  ```bash
247
255
  # Remove PII before evaluation
248
- alignmenter sanitize-dataset \
249
- --input prod_logs.jsonl \
256
+ alignmenter dataset sanitize prod_logs.jsonl \
250
257
  --out datasets/sanitized.jsonl \
251
- --hash-session-ids \
252
- --scrub-patterns "email|phone|ssn"
258
+ --no-use-hashing
253
259
  ```
254
260
 
255
261
  ## Persona Configuration
@@ -401,13 +407,25 @@ make report-last
401
407
 
402
408
  ## Roadmap
403
409
 
404
- - [ ] Web dashboard for team collaboration
405
- - [ ] Hosted evaluation pipelines (CI/CD SaaS)
406
- - [ ] Multi-language support (non-English personas)
407
- - [ ] Offline safety model (no API required)
408
- - [ ] Cost/latency tracking in scorers
409
- - [ ] Competitive landscape analysis
410
- - [ ] Metric cards and ablation studies
410
+ ### Completed ✅
411
+ - Three-dimensional scoring (authenticity, safety, stability)
412
+ - Multi-provider support (OpenAI, Anthropic, local models)
413
+ - HTML report cards with interactive charts
414
+ - Offline safety classifier (distilled-safety-roberta)
415
+ - LLM judges for qualitative analysis
416
+ - Budget guardrails and cost tracking
417
+ - PII sanitization tools
418
+ - Calibration workflow and diagnostics
419
+
420
+ ### In Progress 🚧
421
+ - Multi-language support (non-English personas)
422
+ - Batch processing optimizations
423
+ - Additional embedding providers
424
+
425
+ ### Future Considerations 💭
426
+ - Synthetic test case generation
427
+ - Custom metric plugins
428
+ - Advanced trait models (neural networks)
411
429
 
412
430
  ## Contributing
413
431
 
@@ -431,14 +449,15 @@ If you use Alignmenter in research, please cite:
431
449
  @software{alignmenter2024,
432
450
  title={Alignmenter: A Framework for Persona-Aligned Conversational AI Evaluation},
433
451
  author={Alignmenter Contributors},
434
- year={2024},
435
- url={https://github.com/yourusername/alignmenter}
452
+ year={2025},
453
+ url={https://github.com/justinGrosvenor/alignmenter},
454
+ license={Apache-2.0}
436
455
  }
437
456
  ```
438
457
 
439
458
  ## Support
440
459
 
441
- - **Documentation**: [docs/](https://github.com/justinGrosvenor/alignmenter/tree/main/docs)
460
+ - **Documentation**: [docs.alignmenter.com](https://docs.alignmenter.com)
442
461
  - **Issues**: [GitHub Issues](https://github.com/justinGrosvenor/alignmenter/issues)
443
462
  - **Discussions**: [GitHub Discussions](https://github.com/justinGrosvenor/alignmenter/discussions)
444
463
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "alignmenter"
7
- version = "0.1.0"
7
+ version = "0.1.2"
8
8
  description = "Persona-aligned evaluation toolkit for auditing conversational AI authenticity, safety, and stability."
9
9
  authors = [{name = "Alignmenter"}]
10
10
  readme = {file = "README.md", content-type = "text/markdown"}
@@ -38,7 +38,7 @@ dependencies = [
38
38
  ]
39
39
 
40
40
  [project.optional-dependencies]
41
- dev = ["pytest", "ruff", "build", "twine"]
41
+ dev = ["pytest", "pytest-cov", "ruff", "build", "twine"]
42
42
  safety = ["transformers>=4.30.0"]
43
43
 
44
44
  [project.urls]
@@ -23,8 +23,10 @@ from alignmenter.providers.classifiers import load_safety_classifier
23
23
  from alignmenter.providers.judges import load_judge_provider
24
24
  from alignmenter.providers.openai import OpenAICustomGPTProvider
25
25
  from alignmenter.run_config import load_run_options
26
- from alignmenter.scripts.sanitize_dataset import sanitize_dataset_file
27
26
  from alignmenter.runner import RunConfig, Runner
27
+ from alignmenter.scripts import bootstrap_dataset as bootstrap_dataset_script
28
+ from alignmenter.scripts import calibrate_persona as calibrate_persona_script
29
+ from alignmenter.scripts.sanitize_dataset import sanitize_dataset_file
28
30
  from alignmenter.scorers.authenticity import AuthenticityScorer
29
31
  from alignmenter.scorers.safety import SafetyScorer
30
32
  from alignmenter.scorers.stability import StabilityScorer
@@ -513,9 +515,9 @@ def run(
513
515
  judge: Optional[str] = typer.Option(None, help="Safety judge provider identifier (e.g. 'openai:gpt-4o-mini')."),
514
516
  judge_budget: Optional[int] = typer.Option(None, help="Maximum LLM judge calls per run."),
515
517
  generate_transcripts: bool = typer.Option(
516
- True,
517
- "--generate/--no-generate",
518
- help="Call the model to generate fresh transcripts before scoring.",
518
+ False,
519
+ "--generate-transcripts",
520
+ help="Call providers to regenerate assistant turns before scoring (default reuses recorded transcripts).",
519
521
  ),
520
522
  ) -> None:
521
523
  """Execute an evaluation run."""
@@ -542,12 +544,25 @@ def run(
542
544
  assistant_turns = _lazy_assistant_turn_counter(inputs.dataset_path)
543
545
  _maybe_warn_about_cost(inputs, assistant_turns)
544
546
 
547
+ if not generate_transcripts:
548
+ typer.secho(
549
+ "Reusing recorded transcripts (pass --generate-transcripts to call providers).",
550
+ fg=typer.colors.BLUE,
551
+ )
552
+
545
553
  regenerate, provider, compare_provider_obj = _initialise_providers(
546
554
  inputs.model_identifier,
547
555
  inputs.compare_identifier,
548
556
  generate_transcripts,
549
557
  )
550
558
 
559
+ if generate_transcripts and not regenerate:
560
+ typer.secho(
561
+ "✗ Unable to generate transcripts because provider initialization failed. Re-run without --generate-transcripts or configure API keys.",
562
+ fg=typer.colors.RED,
563
+ )
564
+ raise typer.Exit(code=1)
565
+
551
566
  safety_classifier = load_safety_classifier(inputs.classifier_identifier)
552
567
  judge_provider = _initialise_judge_provider(inputs.judge_identifier)
553
568
  scorers, compare_scorers = _build_scorers_for_run(
@@ -614,6 +629,7 @@ def demo(
614
629
  embedding=None,
615
630
  judge=None,
616
631
  judge_budget=None,
632
+ generate_transcripts=True,
617
633
  )
618
634
 
619
635
 
@@ -939,6 +955,60 @@ def dataset_sanitize(
939
955
  typer.echo(f" Output: {output_path}")
940
956
 
941
957
 
958
+ def _run_bootstrap_dataset(
959
+ source: Optional[Path],
960
+ out: Path,
961
+ sessions: int,
962
+ turns_per_session: int,
963
+ safety_trap_ratio: float,
964
+ brand_trap_ratio: float,
965
+ persona_id: str,
966
+ seed: int,
967
+ ) -> None:
968
+ try:
969
+ bootstrap_dataset_script.bootstrap(
970
+ source=str(source) if source else None,
971
+ out=str(out),
972
+ sessions=sessions,
973
+ turns_per_session=turns_per_session,
974
+ safety_trap_ratio=safety_trap_ratio,
975
+ brand_trap_ratio=brand_trap_ratio,
976
+ persona_id=persona_id,
977
+ seed=seed,
978
+ )
979
+ except typer.Exit as exc:
980
+ raise exc
981
+ except Exception as exc: # noqa: BLE001 - surface friendly error
982
+ typer.secho(f"✗ Error: {exc}", fg=typer.colors.RED, err=True)
983
+ raise typer.Exit(1) from exc
984
+
985
+
986
+ @dataset_app.command("bootstrap")
987
+ @app.command("bootstrap-dataset")
988
+ def bootstrap_dataset_command(
989
+ source: Optional[Path] = typer.Option(None, "--source", help="Optional source dataset to augment."),
990
+ out: Path = typer.Option(..., "--out", help="Output JSONL path for generated data."),
991
+ sessions: int = typer.Option(10, "--sessions", help="Number of sessions to generate."),
992
+ turns_per_session: int = typer.Option(6, "--turns-per-session", help="Turns per session (user/assistant alternating)."),
993
+ safety_trap_ratio: float = typer.Option(0.2, "--safety-trap-ratio", help="Fraction of sessions with safety traps."),
994
+ brand_trap_ratio: float = typer.Option(0.2, "--brand-trap-ratio", help="Fraction of sessions with brand violations."),
995
+ persona_id: str = typer.Option("default_v1", "--persona-id", help="Persona identifier to annotate each turn with."),
996
+ seed: int = typer.Option(42, "--seed", help="Random seed for reproducibility."),
997
+ ) -> None:
998
+ """Generate a synthetic dataset with brand + safety traps."""
999
+
1000
+ _run_bootstrap_dataset(
1001
+ source=source,
1002
+ out=out,
1003
+ sessions=sessions,
1004
+ turns_per_session=turns_per_session,
1005
+ safety_trap_ratio=safety_trap_ratio,
1006
+ brand_trap_ratio=brand_trap_ratio,
1007
+ persona_id=persona_id,
1008
+ seed=seed,
1009
+ )
1010
+
1011
+
942
1012
  # Calibration Commands
943
1013
 
944
1014
 
@@ -1168,6 +1238,35 @@ def analyze_scenarios(
1168
1238
  raise typer.Exit(1)
1169
1239
 
1170
1240
 
1241
+ @app.command("calibrate-persona")
1242
+ def calibrate_persona_command(
1243
+ persona_path: Path = typer.Option(..., "--persona-path", help="Persona YAML containing the 'id' to calibrate."),
1244
+ dataset: Path = typer.Option(..., "--dataset", help="Labeled JSONL with persona_id + label fields."),
1245
+ out: Optional[Path] = typer.Option(None, "--out", help="Output path for generated .traits.json"),
1246
+ min_samples: int = typer.Option(25, "--min-samples", help="Minimum number of labeled examples required."),
1247
+ learning_rate: float = typer.Option(0.1, "--learning-rate", help="Learning rate for logistic regression."),
1248
+ epochs: int = typer.Option(300, "--epochs", help="Training epochs."),
1249
+ l2: float = typer.Option(0.0, "--l2", help="L2 regularization strength."),
1250
+ ) -> None:
1251
+ """Fit persona-specific trait weights from labeled data."""
1252
+
1253
+ try:
1254
+ calibrate_persona_script.calibrate(
1255
+ persona_path=str(persona_path),
1256
+ dataset=str(dataset),
1257
+ out=str(out) if out else None,
1258
+ min_samples=min_samples,
1259
+ learning_rate=learning_rate,
1260
+ epochs=epochs,
1261
+ l2=l2,
1262
+ )
1263
+ except typer.Exit as exc:
1264
+ raise exc
1265
+ except Exception as exc: # noqa: BLE001 - friendly surface
1266
+ typer.secho(f"✗ Error: {exc}", fg=typer.colors.RED, err=True)
1267
+ raise typer.Exit(1) from exc
1268
+
1269
+
1171
1270
  def _load_env(path: Path) -> dict[str, str]:
1172
1271
  if not path.exists():
1173
1272
  return {}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alignmenter
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Persona-aligned evaluation toolkit for auditing conversational AI authenticity, safety, and stability.
5
5
  Author: Alignmenter
6
6
  License: Apache License
@@ -236,6 +236,7 @@ Requires-Dist: tiktoken>=0.7.0
236
236
  Requires-Dist: requests>=2.32.0
237
237
  Provides-Extra: dev
238
238
  Requires-Dist: pytest; extra == "dev"
239
+ Requires-Dist: pytest-cov; extra == "dev"
239
240
  Requires-Dist: ruff; extra == "dev"
240
241
  Requires-Dist: build; extra == "dev"
241
242
  Requires-Dist: twine; extra == "dev"
@@ -277,26 +278,23 @@ Unlike generic LLM evaluation frameworks, Alignmenter is purpose-built for **per
277
278
 
278
279
  ```bash
279
280
  # Clone the repository
280
- git clone https://github.com/yourusername/alignmenter.git
281
+ git clone https://github.com/justinGrosvenor/alignmenter.git
281
282
  cd alignmenter
282
283
 
283
284
  # Create virtual environment
284
285
  python -m venv env
285
286
  source env/bin/activate # On Windows: env\Scripts\activate
286
287
 
287
- # Install with dev dependencies
288
- pip install -e .[dev]
289
-
290
- # Optional: Install with offline safety classifier
291
- pip install -e .[dev,safety]
288
+ # Install with dev + safety extras
289
+ pip install -e ./alignmenter[dev,safety]
292
290
  ```
293
291
 
294
292
  ### Install from PyPI
295
293
 
296
294
  ```bash
297
- pip install alignmenter
295
+ pip install "alignmenter[safety]"
298
296
  alignmenter init
299
- alignmenter run --config configs/run.yaml --no-generate
297
+ alignmenter run --config configs/run.yaml --embedding sentence-transformer:all-MiniLM-L6-v2
300
298
  ```
301
299
 
302
300
  > **Note**: The `safety` extra includes `transformers` for the offline safety classifier (ProtectAI/distilled-safety-roberta). Without it, Alignmenter falls back to a lightweight heuristic classifier. See [docs/offline_safety.md](https://github.com/justinGrosvenor/alignmenter/blob/main/docs/offline_safety.md) for details.
@@ -314,13 +312,16 @@ alignmenter run \
314
312
  --persona configs/persona/default.yaml
315
313
 
316
314
  # Reuse existing transcripts without hitting the provider
317
- alignmenter run --config configs/run.yaml --no-generate
315
+ alignmenter run --config configs/run.yaml
318
316
 
319
317
  # View interactive HTML report
320
318
  alignmenter report --last
321
319
 
322
320
  # Sanitize a dataset in-place or to a new file
323
321
  alignmenter dataset sanitize datasets/demo_conversations.jsonl --out datasets/demo_sanitized.jsonl
322
+
323
+ # Generate fresh transcripts (requires provider access)
324
+ alignmenter run --config configs/run.yaml --generate-transcripts
324
325
  ```
325
326
 
326
327
  **Output:**
@@ -438,6 +439,14 @@ Quick links:
438
439
  - **[LLM Judges](https://docs.alignmenter.com/guides/llm-judges/)** - Qualitative analysis
439
440
  - **[Contributing](https://docs.alignmenter.com/contributing/)** - How to contribute
440
441
 
442
+ ---
443
+
444
+ ## Case Studies
445
+
446
+ - **[Wendy's Twitter Voice](../docs/case-studies/wendys-twitter.md)** - End-to-end calibration example using the included case-study assets. *(Available when running from the source repo; not included in the PyPI wheel.)*
447
+
448
+ ---
449
+
441
450
  ## Usage Examples
442
451
 
443
452
  ### Evaluate Multiple Models
@@ -490,11 +499,9 @@ alignmenter calibrate-persona \
490
499
 
491
500
  ```bash
492
501
  # Remove PII before evaluation
493
- alignmenter sanitize-dataset \
494
- --input prod_logs.jsonl \
502
+ alignmenter dataset sanitize prod_logs.jsonl \
495
503
  --out datasets/sanitized.jsonl \
496
- --hash-session-ids \
497
- --scrub-patterns "email|phone|ssn"
504
+ --no-use-hashing
498
505
  ```
499
506
 
500
507
  ## Persona Configuration
@@ -646,13 +653,25 @@ make report-last
646
653
 
647
654
  ## Roadmap
648
655
 
649
- - [ ] Web dashboard for team collaboration
650
- - [ ] Hosted evaluation pipelines (CI/CD SaaS)
651
- - [ ] Multi-language support (non-English personas)
652
- - [ ] Offline safety model (no API required)
653
- - [ ] Cost/latency tracking in scorers
654
- - [ ] Competitive landscape analysis
655
- - [ ] Metric cards and ablation studies
656
+ ### Completed ✅
657
+ - Three-dimensional scoring (authenticity, safety, stability)
658
+ - Multi-provider support (OpenAI, Anthropic, local models)
659
+ - HTML report cards with interactive charts
660
+ - Offline safety classifier (distilled-safety-roberta)
661
+ - LLM judges for qualitative analysis
662
+ - Budget guardrails and cost tracking
663
+ - PII sanitization tools
664
+ - Calibration workflow and diagnostics
665
+
666
+ ### In Progress 🚧
667
+ - Multi-language support (non-English personas)
668
+ - Batch processing optimizations
669
+ - Additional embedding providers
670
+
671
+ ### Future Considerations 💭
672
+ - Synthetic test case generation
673
+ - Custom metric plugins
674
+ - Advanced trait models (neural networks)
656
675
 
657
676
  ## Contributing
658
677
 
@@ -676,14 +695,15 @@ If you use Alignmenter in research, please cite:
676
695
  @software{alignmenter2024,
677
696
  title={Alignmenter: A Framework for Persona-Aligned Conversational AI Evaluation},
678
697
  author={Alignmenter Contributors},
679
- year={2024},
680
- url={https://github.com/yourusername/alignmenter}
698
+ year={2025},
699
+ url={https://github.com/justinGrosvenor/alignmenter},
700
+ license={Apache-2.0}
681
701
  }
682
702
  ```
683
703
 
684
704
  ## Support
685
705
 
686
- - **Documentation**: [docs/](https://github.com/justinGrosvenor/alignmenter/tree/main/docs)
706
+ - **Documentation**: [docs.alignmenter.com](https://docs.alignmenter.com)
687
707
  - **Issues**: [GitHub Issues](https://github.com/justinGrosvenor/alignmenter/issues)
688
708
  - **Discussions**: [GitHub Discussions](https://github.com/justinGrosvenor/alignmenter/discussions)
689
709
 
@@ -13,6 +13,7 @@ requests>=2.32.0
13
13
 
14
14
  [dev]
15
15
  pytest
16
+ pytest-cov
16
17
  ruff
17
18
  build
18
19
  twine
@@ -10,6 +10,9 @@ from typer.testing import CliRunner
10
10
  from alignmenter import app
11
11
 
12
12
 
13
+ DATA_DIR = Path(__file__).resolve().parent / "data"
14
+ MINI_DATASET = DATA_DIR / "mini_cli_dataset.jsonl"
15
+
13
16
  runner = CliRunner()
14
17
 
15
18
 
@@ -149,9 +152,75 @@ def test_dataset_sanitize_command(tmp_path: Path) -> None:
149
152
  assert "[DRY RUN]" in result.output
150
153
 
151
154
 
155
+ def test_bootstrap_dataset_command(tmp_path: Path) -> None:
156
+ out_path = tmp_path / "generated.jsonl"
157
+ result = runner.invoke(
158
+ app,
159
+ [
160
+ "bootstrap-dataset",
161
+ "--out",
162
+ str(out_path),
163
+ "--sessions",
164
+ "2",
165
+ "--turns-per-session",
166
+ "2",
167
+ "--safety-trap-ratio",
168
+ "0.5",
169
+ "--brand-trap-ratio",
170
+ "0.0",
171
+ "--persona-id",
172
+ "demo_v1",
173
+ "--seed",
174
+ "7",
175
+ ],
176
+ )
177
+
178
+ assert result.exit_code == 0, result.output
179
+ assert out_path.exists()
180
+ lines = out_path.read_text().strip().splitlines()
181
+ assert len(lines) == 4 # 2 sessions × 2 turns
182
+
183
+
184
+ def test_calibrate_persona_command(tmp_path: Path) -> None:
185
+ persona_path = tmp_path / "persona.yaml"
186
+ persona_path.write_text("id: sample_persona\n", encoding="utf-8")
187
+
188
+ dataset_path = tmp_path / "annotations.jsonl"
189
+ dataset_path.write_text(
190
+ json.dumps({"text": "On brand reply", "label": 1, "persona_id": "sample_persona"})
191
+ + "\n"
192
+ + json.dumps({"text": "off brand hype", "label": 0, "persona_id": "sample_persona"})
193
+ + "\n",
194
+ encoding="utf-8",
195
+ )
196
+
197
+ out_path = tmp_path / "persona.traits.json"
198
+ result = runner.invoke(
199
+ app,
200
+ [
201
+ "calibrate-persona",
202
+ "--persona-path",
203
+ str(persona_path),
204
+ "--dataset",
205
+ str(dataset_path),
206
+ "--out",
207
+ str(out_path),
208
+ "--min-samples",
209
+ "2",
210
+ "--epochs",
211
+ "10",
212
+ ],
213
+ )
214
+
215
+ assert result.exit_code == 0, result.output
216
+ assert out_path.exists()
217
+ payload = json.loads(out_path.read_text())
218
+ assert "trait_model" in payload
219
+
220
+
152
221
  def test_run_command(tmp_path: Path) -> None:
153
222
  root = Path(__file__).resolve().parents[2] / "alignmenter"
154
- dataset = root / "datasets" / "demo_conversations.jsonl"
223
+ dataset = MINI_DATASET
155
224
  persona = root / "configs" / "persona" / "default.yaml"
156
225
  keywords = root / "configs" / "safety_keywords.yaml"
157
226
 
@@ -169,7 +238,6 @@ def test_run_command(tmp_path: Path) -> None:
169
238
  str(keywords),
170
239
  "--out",
171
240
  str(tmp_path),
172
- "--no-generate",
173
241
  ],
174
242
  )
175
243
 
@@ -185,7 +253,7 @@ def test_run_command(tmp_path: Path) -> None:
185
253
 
186
254
  def test_run_command_with_compare(tmp_path: Path) -> None:
187
255
  root = Path(__file__).resolve().parents[2] / "alignmenter"
188
- dataset = root / "datasets" / "demo_conversations.jsonl"
256
+ dataset = MINI_DATASET
189
257
  persona = root / "configs" / "persona" / "default.yaml"
190
258
  keywords = root / "configs" / "safety_keywords.yaml"
191
259
 
@@ -205,7 +273,6 @@ def test_run_command_with_compare(tmp_path: Path) -> None:
205
273
  str(keywords),
206
274
  "--out",
207
275
  str(tmp_path),
208
- "--no-generate",
209
276
  ],
210
277
  )
211
278
 
@@ -224,7 +291,7 @@ def test_run_command_threshold_failure(tmp_path: Path) -> None:
224
291
  [
225
292
  "run_id: threshold",
226
293
  "model: openai:gpt-4o-mini",
227
- f"dataset: {root / 'datasets' / 'demo_conversations.jsonl'}",
294
+ f"dataset: {MINI_DATASET}",
228
295
  f"persona: {root / 'configs' / 'persona' / 'default.yaml'}",
229
296
  f"keywords: {root / 'configs' / 'safety_keywords.yaml'}",
230
297
  "scorers:",
@@ -243,7 +310,6 @@ def test_run_command_threshold_failure(tmp_path: Path) -> None:
243
310
  "run",
244
311
  "--config",
245
312
  str(config_path),
246
- "--no-generate",
247
313
  ],
248
314
  )
249
315
 
@@ -0,0 +1,46 @@
1
+ """Tests CLI integration with environment config defaults."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from typer.testing import CliRunner
8
+
9
+ from alignmenter import app
10
+
11
+ DATA_DIR = Path(__file__).resolve().parent / "data"
12
+ MINI_DATASET = DATA_DIR / "mini_cli_dataset.jsonl"
13
+
14
+ runner = CliRunner()
15
+
16
+
17
+ def test_cli_run_uses_run_config_file(tmp_path: Path) -> None:
18
+ root = Path(__file__).resolve().parents[2] / "alignmenter"
19
+ persona = root / "configs" / "persona" / "default.yaml"
20
+ keywords = root / "configs" / "safety_keywords.yaml"
21
+
22
+ config_path = tmp_path / "mini_config.yaml"
23
+ config_path.write_text(
24
+ "\n".join(
25
+ [
26
+ "run_id: cli-mini",
27
+ f"model: openai:gpt-4o-mini",
28
+ f"dataset: {MINI_DATASET}",
29
+ f"persona: {persona}",
30
+ f"keywords: {keywords}",
31
+ ]
32
+ )
33
+ + "\n",
34
+ encoding="utf-8",
35
+ )
36
+
37
+ result = runner.invoke(
38
+ app,
39
+ [
40
+ "run",
41
+ "--config", str(config_path),
42
+ "--out", str(tmp_path),
43
+ ],
44
+ )
45
+
46
+ assert result.exit_code == 0, result.output
@@ -1,28 +0,0 @@
1
- """Tests CLI integration with environment config defaults."""
2
-
3
- from __future__ import annotations
4
-
5
- from pathlib import Path
6
-
7
- from typer.testing import CliRunner
8
-
9
- from alignmenter import app
10
-
11
- runner = CliRunner()
12
-
13
-
14
- def test_cli_run_uses_run_config_file(tmp_path: Path) -> None:
15
- root = Path(__file__).resolve().parents[2] / "alignmenter"
16
- config_path = root / "configs" / "demo_config.yaml"
17
-
18
- result = runner.invoke(
19
- app,
20
- [
21
- "run",
22
- "--config", str(config_path),
23
- "--out", str(tmp_path),
24
- "--no-generate",
25
- ],
26
- )
27
-
28
- assert result.exit_code == 0, result.output
File without changes
File without changes