Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
e0bdde4
Draft v1 nano refactor
willccbb Jun 6, 2026
122cc60
Remove v1 Schema helper
willccbb Jun 6, 2026
be7f1d8
Add v1 server stubs to init template
willccbb Jun 6, 2026
8f2f9f0
Migrate v1 environments to component packages
willccbb Jun 6, 2026
061a155
Finalize v1 nano toolset contract
willccbb Jun 7, 2026
6ceb3c7
checkpoint
willccbb Jun 7, 2026
a7b2dce
checkpoint
willccbb Jun 7, 2026
d392bd9
checkpoint
willccbb Jun 7, 2026
bae370c
checkpoint
willccbb Jun 7, 2026
8548a9b
checkpoint
willccbb Jun 7, 2026
d7fda73
Fix dynamic tool payload typing
willccbb Jun 7, 2026
ff16cd0
Tighten v1 state runtime contracts
willccbb Jun 7, 2026
b23ece4
Tighten v1 runtime and advantage defaults
willccbb Jun 7, 2026
b392de7
Fix v1 lifecycle review issues
willccbb Jun 7, 2026
06277b8
Fix Docker runtime binary reads
willccbb Jun 7, 2026
b4d896d
Fix v1 README env args
willccbb Jun 7, 2026
f3cd0f8
Fix v1 review regressions
willccbb Jun 7, 2026
1628aa5
Guard v1 output column collisions
willccbb Jun 7, 2026
d80b980
Tighten v1 state and tool visibility
willccbb Jun 7, 2026
06dbcf1
Use PyPI BFCL eval dependency
willccbb Jun 7, 2026
22ea80e
Fix v1 review followups
willccbb Jun 7, 2026
955dfcf
Close partial MCP registry startup
willccbb Jun 7, 2026
8322f9c
Guard BFCL multi-turn loop
willccbb Jun 7, 2026
0375af3
Record BFCL multi-turn timing
willccbb Jun 7, 2026
d29b396
Refresh create-environments v1 guidance
willccbb Jun 7, 2026
8c20d86
Tighten v1 taskset and README contracts
willccbb Jun 7, 2026
a42327f
Fix v1 OpenEnv dynamic tool setup
willccbb Jun 7, 2026
44df4ff
Remove stale OpenEnv setup branch
willccbb Jun 7, 2026
dd7dc93
Prune dead v1 utilities
willccbb Jun 7, 2026
cb2075d
Fix v1 reference doc drift
willccbb Jun 7, 2026
f23ba9b
Document v1 env advantage config
willccbb Jun 7, 2026
ca06336
Sync v1 Harbor runtime with vf-nano
willccbb Jun 8, 2026
13a5cab
Remove v1 runtime uv script helper
willccbb Jun 8, 2026
e9ab6c6
Align Harbor task staging with nano
willccbb Jun 8, 2026
914a75b
Add generic Harbor v1 environment
willccbb Jun 8, 2026
340b39b
Simplify Harbor v1 config and task spec
willccbb Jun 8, 2026
c249294
Handle missing Harbor tests directory
willccbb Jun 8, 2026
9eec896
Normalize empty Harbor list metadata
willccbb Jun 8, 2026
3f8a019
Reject malformed Harbor task sections
willccbb Jun 8, 2026
651db1c
Merge origin/main into v1 nano refactor draft
willccbb Jun 8, 2026
6fde401
Fix replay task typing and tool call normalization
willccbb Jun 8, 2026
68e6df0
checkpoint
willccbb Jun 8, 2026
95a7f99
Finalize v1 runtime lifecycle
willccbb Jun 8, 2026
ccbc20a
Restore Qwen3 VL endpoint aliases
willccbb Jun 8, 2026
b27ee8b
Retarget v1 prime runtime CI smoke
willccbb Jun 8, 2026
24e4093
Fix v1 environment smoke contracts
willccbb Jun 8, 2026
510e09c
Fix v1 runtime timeout and env startup cleanup
willccbb Jun 8, 2026
930b843
Normalize v1 runtime timeouts
willccbb Jun 8, 2026
3222e7b
Fix v1 env package smokes
willccbb Jun 8, 2026
5c6a92f
Fix BFCL v1 task serialization
willccbb Jun 8, 2026
5d1ef09
Fix v1 env-scope cleanup contracts
willccbb Jun 8, 2026
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
124 changes: 78 additions & 46 deletions environments/alphabet_sort/alphabet_sort_v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
import logging
import random
import re
import sys

from datasets import Dataset, load_dataset
from pydantic import model_validator
from pydantic import BaseModel

import verifiers as vf
import verifiers.v1 as vf

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -239,13 +240,13 @@ def score_response(


def eval_turn(
completion: list[vf.ConfigData],
completion: vf.Messages,
turn_num: int,
state: dict,
info: "AlphabetSortInfo",
similarity_power: int,
apply_power: bool,
) -> float:
ground_truths = state.get("info", {}).get("ground_truths", [])
ground_truths = info.ground_truths
if turn_num > len(ground_truths):
return 0.0
expected = ground_truths[turn_num - 1]
Expand Down Expand Up @@ -279,44 +280,32 @@ def eval_turn(
return attempt_scores[-1]


@vf.reward(weight=1.0)
async def weighted_reward(task, state) -> float:
completion = state.get("completion") or []
actual_turns = state["info"]["num_turns"]
similarity_power = int(task.get("similarity_power", 4))
power_per_turn = bool(task.get("power_per_turn", True))
total = 0.0
for turn_num in range(1, actual_turns + 1):
total += eval_turn(
completion,
turn_num,
state,
similarity_power,
apply_power=power_per_turn,
)
if actual_turns <= 0:
return 0.0
if power_per_turn:
return total / actual_turns
return (total / actual_turns) ** similarity_power
class AlphabetSortInfo(BaseModel, extra="forbid"):
follow_ups: list[str]
turn_names: list[list[str]]
ground_truths: list[list[str]]
num_turns: int
sort_by_first: bool


class AlphabetUserConfig(vf.UserConfig):
pass
class AlphabetSortTask(vf.Task):
answer: str
info: AlphabetSortInfo
similarity_power: int = 4
power_per_turn: bool = True


class AlphabetUser(vf.User[AlphabetUserConfig]):
async def get_response(self, task, state, messages) -> list[dict[str, str]]:
assistant_count = len(vf.get_messages(messages, role="assistant"))
follow_ups = state["info"]["follow_ups"]
if assistant_count <= 0 or assistant_count > len(follow_ups):
return []
return [{"role": "user", "content": follow_ups[assistant_count - 1]}]
def transcript_completion_messages(state: vf.State) -> vf.Messages:
messages: vf.Messages = []
for turn in state.transcript:
messages.extend(turn.completion)
return messages


class AlphabetSortTasksetConfig(vf.TasksetConfig):
user: AlphabetUserConfig | None = AlphabetUserConfig()
rewards: list[str] = ["weighted_reward"]
user: vf.User | None = vf.User(
server=vf.MCPServerSpec(command=[sys.executable, __file__, "--user-server"])
)
min_turns: int = 1
max_turns: int = 3
min_names_per_turn: int = 1
Expand All @@ -327,23 +316,15 @@ class AlphabetSortTasksetConfig(vf.TasksetConfig):
dataset_split: str = "train"
seed: int = 1337420

@model_validator(mode="after")
def validate_task_shape(self) -> "AlphabetSortTasksetConfig":
validate_parameters(
min_turns=self.min_turns,
max_turns=self.max_turns,
min_names_per_turn=self.min_names_per_turn,
max_names_per_turn=self.max_names_per_turn,
)
return self


class AlphabetSortEnvConfig(vf.EnvConfig):
taskset: AlphabetSortTasksetConfig = AlphabetSortTasksetConfig()
harness: vf.HarnessConfig = vf.HarnessConfig()


class AlphabetSortTaskset(vf.Taskset[AlphabetSortTasksetConfig]):
task_type = AlphabetSortTask

def load_tasks(self, split: vf.TaskSplit = "train") -> vf.Tasks:
return load_tasks(
min_turns=self.config.min_turns,
Expand All @@ -357,9 +338,60 @@ def load_tasks(self, split: vf.TaskSplit = "train") -> vf.Tasks:
seed=self.config.seed,
)

@vf.reward(weight=1.0)
async def weighted_reward(self, task: AlphabetSortTask, state: vf.State) -> float:
completion = transcript_completion_messages(state)
actual_turns = task.info.num_turns
total = 0.0
for turn_num in range(1, actual_turns + 1):
total += eval_turn(
completion,
turn_num,
task.info,
task.similarity_power,
apply_power=task.power_per_turn,
)
if actual_turns <= 0:
return 0.0
if task.power_per_turn:
return total / actual_turns
return (total / actual_turns) ** task.similarity_power


def load_environment(config: AlphabetSortEnvConfig) -> vf.Env:
return vf.Env(
taskset=AlphabetSortTaskset(config=config.taskset),
harness=vf.Harness(config=config.harness),
runtime=config.runtime,
)


def run_user_server() -> None:
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("alphabet-sort-user")

@mcp.tool()
def respond(task: dict, state: dict, transcript: list[dict]) -> dict:
_ = state
info = task.get("info") or {}
follow_ups = info.get("follow_ups") or []
assistant_count = 0
for turn in transcript:
completion = turn.get("completion") or []
assistant_count += sum(
1 for message in completion if message.get("role") == "assistant"
)
if assistant_count <= 0 or assistant_count > len(follow_ups):
return {"messages": []}
return {
"messages": [
{"role": "user", "content": str(follow_ups[assistant_count - 1])}
],
}

mcp.run(transport="stdio")


if __name__ == "__main__" and sys.argv[1:] == ["--user-server"]:
run_user_server()
Loading