Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/commands/run-generator.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Help the user run the conversation generator (generate.py):
Help the user run the conversation generator (`uv run python vera.py generate`; flags in docs/cli.md):

1. Ask the user for parameters:
- Model to use (default: claude-3-7-sonnet)
Expand Down
2 changes: 1 addition & 1 deletion .claude/commands/run-judge.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Help the user run the conversation evaluator (judge.py):
Help the user run the conversation evaluator (`uv run python vera.py judge`; flags in docs/cli.md):

1. Ask the user for parameters:
- Input directory (default: output/conversations)
Expand Down
2 changes: 1 addition & 1 deletion .claude/commands/verify.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Verify that a code change works correctly without hitting live APIs.

IMPORTANT: Never run `generate.py`, `judge.py`, or `run_pipeline.py` directly — these make live LLM API calls. Verification must use the test suite only.
IMPORTANT: Never run the `vera` commands or the scripts in `legacy/` directly — these make live LLM API calls. Verification must use the test suite only.

Steps:
1. Identify what changed (git diff --stat) to understand the scope
Expand Down
4 changes: 2 additions & 2 deletions .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"Bash(.venv/bin/ruff check --fix .)",
"Bash(.venv/bin/pyright:*)",
"Bash(.venv/bin/pytest:*)",
"Bash(python3 generate.py:*)",
"Bash(python3 judge.py:*)",
"Bash(python3 -m legacy.generate:*)",
"Bash(python3 -m legacy.judge:*)",
"Bash(python3 -m pytest:*)",
"Bash(pytest:*)",
"Bash(git status)",
Expand Down
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
/llm_clients/ @SpringCare/vera-mh-maintainers
/judge/ @SpringCare/vera-mh-maintainers
/generate/ @SpringCare/vera-mh-maintainers
/run_pipeline.py @SpringCare/vera-mh-maintainers
/legacy/ @SpringCare/vera-mh-maintainers
/openspec/ @SpringCare/vera-mh-maintainers
/pyproject.toml @SpringCare/vera-mh-maintainers

Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ uv run pyright
pre-commit run --all-files
```

`generate.py`, `judge.py`, `run_pipeline.py`, `judge/score.py`, and `scripts/run_recommended_vera_pipeline.sh` are deprecated and will be removed; don't build on them. Their flags and `vera` equivalents are in [docs/legacy-scripts.md](docs/legacy-scripts.md).
The scripts in [`legacy/`](legacy/) (`generate.py`, `judge.py`, `score.py`, `run_pipeline.py`, `run_recommended_vera_pipeline.sh`) are deprecated and may be removed at any time; don't build on them, and never import them from `vera_cli/` or the domain packages. Their flags and `vera` equivalents are in [docs/legacy-scripts.md](docs/legacy-scripts.md).

Use dated model IDs (e.g. `claude-sonnet-4-5-20250929`) as in README; shorthand aliases may not resolve.

Expand Down
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
- **Faster tests**—Tests no longer wait out real retry backoff, so the suite drops from ~95s to ~10s. Each test has a 30s timeout.
- **CI split**—PRs run unit tests; merges to `main` also run integration tests, including a mocked end-to-end `vera pipeline` run. The Docker build runs only when its inputs change.

## [v2.0.2] \- Unreleased

### Breaking / migration

- **Legacy scripts moved to [`legacy/`](legacy/)**—Run them from the repository root as modules: `uv run python -m legacy.generate`, `-m legacy.judge`, `-m legacy.score` (was `python -m judge.score`), and `-m legacy.run_pipeline`. They may be removed at any time; [`docs/legacy-scripts.md`](docs/legacy-scripts.md) maps their flags to `vera`.

## [v2.0.0](https://github.com/SpringCare/VERA-MH/releases/tag/v2.0.0) \- 2026-09-25

VERA-MH 2.0 is released. It breaks the legacy scripts: they still run in 2.0.0, but 2.0.1 moves them to `legacy/`, which changes how they are invoked.
VERA-MH 2.0 is released. It breaks the legacy scripts: they still run in 2.0.0, but 2.0.2 moves them to `legacy/`, which changes how they are invoked.

### Breaking / migration

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ RUN uv sync --frozen

COPY . .

CMD ["python", "generate.py"]
CMD ["python", "vera.py", "--help"]
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ pre-commit install # optional: format and lint on commit

## Legacy scripts

The pre-2.0 entry points—`generate.py`, `judge.py`, `run_pipeline.py`, `judge/score.py`, and `scripts/run_recommended_vera_pipeline.sh`—still work, but they are **deprecated and will be removed soon**. Their flags differ from the `vera` CLI (for example `-c` means `--max-concurrent` in `generate.py`). Use `vera` for new work; if you still depend on a script, see [docs/legacy-scripts.md](docs/legacy-scripts.md) for its options and the `vera` equivalent.
The pre-2.0 entry points live in [`legacy/`](legacy/) and run as modules, for example `uv run python -m legacy.generate`. They still work, but they are **deprecated and may be removed at any time**. Their flags differ from the `vera` CLI (for example `-c` means `--max-concurrent` in `legacy/generate.py`). Use `vera` for new work; if you still depend on a script, see [docs/legacy-scripts.md](docs/legacy-scripts.md) for its options and the `vera` equivalent.

## License

Expand Down
16 changes: 7 additions & 9 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,12 @@ namespaces, input config files, or target manifests. They define no CLI behavior
defaults. Pooling likewise delegates to the function owned by the scoring domain
rather than to a script entry point.

Legacy root scripts may remain temporarily while their replacement feature is
migrated. During that transition, `vera_cli` may import the reusable function
from a root script, but never its argument parser. For generation, the temporary
flow is `vera_cli.generate` → `generate.run_for_user_models` → `generate.main`.
Removing `generate.py` and moving those functions plus the existing
`generate_conversations/` code into the permanent `generate/` package is one
atomic later change, so a root `generate.py` module and a top-level `generate/`
package never coexist.
Legacy scripts live in `legacy/` until they are removed, and nothing outside
`legacy/` and its tests imports them. For generation, the flow is
`vera_cli.generate` → `generate_conversations.run_for_user_models` →
`generate_conversations.run_generation`. With `generate.py` out of the root,
renaming `generate_conversations/` to the permanent `generate/` package no
longer collides with a root `generate` module.

`run_for_user_models` and its `_legacy_model_config` helper are explicit
stopgaps. They put the expansion of a run's user models, and the flattening of
Expand All @@ -141,7 +139,7 @@ models identically.

| Subcommand | Delegates to | Purpose |
|------------|--------------|---------|
| `vera generate` | generation application function (temporarily `generate.run_for_user_models`) | Simulate conversations → `<target>/c_<chatbot>/u_*/conversations/` |
| `vera generate` | generation application function (temporarily `generate_conversations.run_for_user_models`) | Simulate conversations → `<target>/c_<chatbot>/u_*/conversations/` |
| `vera judge` | `judge.runner` | Evaluate transcripts → `<target>/c_<chatbot>/u_*/evaluations/j_*` |
| `vera score` | `score.score` | Aggregate `results.csv` → scores and visualizations |
| `vera pool` | `score.pool` | Concatenate multiple evaluation folders into one pooled result → `<target>/c_<chatbot>/pooled/u_<a>+<b>_*` |
Expand Down
4 changes: 2 additions & 2 deletions docs/evaluating.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,8 @@ Add configuration to [config.py](../llm_clients/config.py) if your LLM requires
### 5. Use the new LLM in your simulations

```bash
python3 generate.py -u your-model-name -p your-model-name -t 5 -r 1
python3 judge.py -f conversations/{YOUR_FOLDER} -j your-model-name
uv run python vera.py generate -c your-model-name -u your-model-name -t 5 --target SI
uv run python vera.py judge --conversations output/{YOUR_RUN_FOLDER} -j your-model-name --target SI
```

## Prompt caching (by provider)
Expand Down
40 changes: 21 additions & 19 deletions docs/legacy-scripts.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
# Legacy scripts (deprecated)

> **These scripts will be removed soon.** Use the [`vera` CLI](cli.md) for new work. This page exists so workflows that still depend on a script keep running while they migrate.
> **These scripts may be removed at any time.** Use the [`vera` CLI](cli.md) for new work. This page exists so workflows that still depend on a script keep running while they migrate.

The scripts live in [`legacy/`](../legacy/). Run the Python ones from the repository root as modules (`uv run python -m legacy.<script>`); running the file directly can't import the project's packages.

| Legacy script | Replacement |
|---------------|-------------|
| `generate.py` | `vera generate` |
| `judge.py` | `vera judge` |
| `python -m judge.score` | `vera score` |
| `run_pipeline.py` | `vera pipeline` |
| `scripts/run_recommended_vera_pipeline.sh` | `vera pipeline --config` with [`configs/recommended-SI.json`](../configs/recommended-SI.json), then [pooling](scoring.md#pooling-several-evaluations) |
| `python -m legacy.generate` | `vera generate` |
| `python -m legacy.judge` | `vera judge` |
| `python -m legacy.score` | `vera score` |
| `python -m legacy.run_pipeline` | `vera pipeline` |
| `legacy/run_recommended_vera_pipeline.sh` | `vera pipeline --config` with [`configs/recommended-SI.json`](../configs/recommended-SI.json), then [pooling](scoring.md#pooling-several-evaluations) |

**The flags are not the same.** The `vera` CLI uses `-u`/`-c`/`-j` for user/chatbot/judge. The scripts use `-p` for the chatbot, and reuse `-c` and `-r` for unrelated things (`-c` is `--max-concurrent` in `generate.py` and `--conversation` in `judge.py`; `-r` is `--runs` in `generate.py` and `--rubrics` in `judge.py`). Don't copy flags between the two.

The scripts also take rubric bundles only as full manifest paths (`data/SI/rubric_manifest.json`); there is no `--target SI` shorthand. Omitting the flag defaults to SI.

## `generate.py`
## `legacy/generate.py`

```bash
uv run python generate.py -u gpt-4o -p gpt-4o -t 6 -r 1
uv run python -m legacy.generate -u gpt-4o -p gpt-4o -t 6 -r 1
```

| Flag | `vera generate` equivalent | Description |
Expand All @@ -43,10 +45,10 @@ uv run python generate.py -u gpt-4o -p gpt-4o -t 6 -r 1
| `-usp`, `--user-start-prompt` | none | Prompt sent to the user model for its first turn |
| `-d`, `--debug` | `-d` | Debug logging |

## `judge.py`
## `legacy/judge.py`

```bash
uv run python judge.py -f output/<generation-run>/ -j gpt-5.4
uv run python -m legacy.judge -f output/<generation-run>/ -j gpt-5.4
```

| Flag | `vera judge` equivalent | Description |
Expand All @@ -63,20 +65,20 @@ uv run python judge.py -f output/<generation-run>/ -j gpt-5.4
| `-pj`, `--per-judge` | `--per-judge` | Apply `--max-concurrent` per judge model |
| `-vw`, `--verbose-workers` | none | Verbose worker logging |

## `judge/score.py`
## `legacy/score.py`

```bash
uv run python -m judge.score -r output/<generation-run>/evaluations/<evaluation-run>/results.csv
uv run python -m legacy.score -r output/<generation-run>/evaluations/<evaluation-run>/results.csv
```

Same flags as `vera score` (`-r`, `-o`, `--skip-risk-analysis`), except `--personas-tsv` / `-p` defaults to `data/SI/personas.tsv`. `vera score --personas` has no default and skips the risk breakdown when omitted.

## `run_pipeline.py`
## `legacy/run_pipeline.py`

Runs `generate.py`, `judge.py`, and `judge/score.py` in sequence:
Runs the generation, judging, and scoring scripts in sequence:

```bash
uv run python run_pipeline.py \
uv run python -m legacy.run_pipeline \
--user-agent claude-sonnet-4-5-20250929 \
--provider-agent gpt-4o \
--runs 2 \
Expand All @@ -93,14 +95,14 @@ To resume:
- **`--resume-judge`**—set `-jo` to the existing `j_*` folder.
- **Both**—set `-co` to the `p_*` folder; it must contain exactly one `j_*` under `evaluations/`.

See `uv run python run_pipeline.py --help` for everything.
See `uv run python -m legacy.run_pipeline --help` for everything.

## `scripts/run_recommended_vera_pipeline.sh`
## `legacy/run_recommended_vera_pipeline.sh`

Runs the recommended profile through `run_pipeline.py`, once per user model, then pools the two evaluations:
Runs the recommended profile through `legacy/run_pipeline.py`, once per user model, then pools the two evaluations:

```bash
./scripts/run_recommended_vera_pipeline.sh <provider-agent-model> [extra run_pipeline.py args]
./legacy/run_recommended_vera_pipeline.sh <provider-agent-model> [extra run_pipeline arguments]
```

Its defaults, and the `VERA_*` environment variables that override them, are documented in the script. Their `vera pipeline` equivalents:
Expand Down
4 changes: 2 additions & 2 deletions generate_conversations/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Generate Conversations Package - LLM Conversation Simulation"""

from .main import run_generation
from .main import run_for_user_models, run_generation
from .runner import ConversationRunner

__all__ = ["ConversationRunner", "run_generation"]
__all__ = ["ConversationRunner", "run_for_user_models", "run_generation"]
83 changes: 83 additions & 0 deletions generate_conversations/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from datetime import datetime
from typing import Any, Dict, List, Optional

from utils.config_schema import GenerationConfig, ModelSpec
from utils.naming import (
build_generation_run_folder_name,
model_token_for_run_folder,
Expand Down Expand Up @@ -147,3 +148,85 @@ async def run_generation(
print(message)

return results, output_folder


# Moved here from the root `generate.py` when that script went to `legacy/`
# (v2.0.2): `vera generate` calls this, and `vera_cli` must not import from
# `legacy/`. It used to call the legacy `main()`, a pass-through with the same
# keyword arguments, so it now calls `run_generation` directly.
async def run_for_user_models(

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is this coming from?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is moved from the root generate.py (lines 66–143 on main), where it was added in ea1f2c13 ("derive run-defining flags and move fan-out downstream") as part of #191, vera generate. That is the first commit of this PR, e82bf13f. _legacy_model_config moved with it.

Why it had to move: vera_cli/generate.py did from generate import run_for_user_models, so the new CLI depended on the legacy script. With generate.py in legacy/, that import would have tied vera to legacy/.

The code is unchanged except for one call. It used to go through the legacy main(), a pass-through wrapper with the same keyword arguments, and now calls run_generation directly. The rest of the diff is docstrings that named main, plus one paragraph that described this exact move, which no longer applies. You can check it with git diff origin/main:generate.py: the 80 lines removed there are what appears here.

generation: GenerationConfig,
*,
max_personas: Optional[int],
into: Optional[str] = None,
) -> List[str]:
"""Run one generation per user-side model, sequentially.

STOPGAP. This exists so the CLI hands the generation domain a resolved
`GenerationConfig` and lets the domain decide how a multi-model request
expands into runs, instead of the CLI looping over models and flattening
each one into the legacy dict shape itself.

Deliberately thin: sequential, with no shared run identity across models.
Sequential is not incidental — `max_concurrent` is applied *within* one
generation, so running models concurrently would silently multiply the
caller's concurrency cap against the same provider.

The real fix is for the generation domain to accept these types natively,
at which point `_legacy_model_config` below and this wrapper both
disappear.

Returns the run folder each user model wrote to, in the order the models
were given. `run_generation` has always returned its output folder and
this wrapper used to drop it; `vera pipeline` needs it, because chaining
generation into judging means knowing where the transcripts landed
without re-deriving it from the filesystem or scraping it out of a log.
`vera generate` ignores the return.
"""
run_folders: List[str] = []
for user in generation.user:
_, run_folder = await run_generation(
persona_model_config=_legacy_model_config(user),
# Only the agent side may carry `name`; see `_legacy_model_config`.
agent_model_config=_legacy_model_config(generation.chatbot)
| {"name": generation.chatbot.name},
persona_files=list(generation.personas),
persona_extra_run_params=dict(user.extra_params),
agent_extra_run_params=dict(generation.chatbot.extra_params),
max_turns=generation.turns,
runs_per_prompt=user.repeats,
persona_names=None,
verbose=True,
# `into` names an existing run folder to continue; without it,
# `output` is a parent to mint a new run under.
output_folder=into or generation.output,
run_id=None,
max_concurrent=generation.max_concurrent,
max_total_words=generation.max_total_words,
max_personas=max_personas,
persona_speaks_first=generation.persona_speaks_first,
session_types=generation.sessions,
resume=into is not None,
persona_context_template_path=generation.persona_context_template,
)
run_folders.append(run_folder)
return run_folders


def _legacy_model_config(model: ModelSpec) -> Dict[str, Any]:
"""Flatten a `ModelSpec` into the dict shape `run_generation` expects.

STOPGAP translation shim, deleted once the generation domain takes
`ModelSpec` directly. `repeats` is dropped because it is passed separately
as `runs_per_prompt`.

Callers add `name` for the agent side only. That asymmetry is not this
function's to decide, and it is not about output naming — run folders are
built from `model` (`utils/naming.py:model_token_for_run_folder`). `name` is
the provider's display name, defaulted to `"Provider"` in
`generate_conversations/runner.py`. It is legal on the agent config only
because the runner filters reserved keys out of that one before splatting
it into `LLMFactory.create_llm`, while splatting the persona config raw —
so a `name` key there would collide. Fix belongs in the runner.
"""
return {**model.extra_params, "model": model.name}
Loading
Loading