world-model-optimizer 0.2.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.
- llm_waterfall/LICENSE +21 -0
- llm_waterfall/__init__.py +53 -0
- llm_waterfall/adapters/__init__.py +36 -0
- llm_waterfall/adapters/anthropic.py +105 -0
- llm_waterfall/adapters/aws_mantle.py +47 -0
- llm_waterfall/adapters/azure_openai.py +71 -0
- llm_waterfall/adapters/base.py +51 -0
- llm_waterfall/adapters/bedrock.py +309 -0
- llm_waterfall/adapters/openai.py +130 -0
- llm_waterfall/classify.py +184 -0
- llm_waterfall/pricing.py +110 -0
- llm_waterfall/py.typed +0 -0
- llm_waterfall/types.py +295 -0
- llm_waterfall/waterfall.py +255 -0
- wmo/__init__.py +38 -0
- wmo/agents/__init__.py +7 -0
- wmo/agents/default.py +29 -0
- wmo/agents/meta.py +55 -0
- wmo/agents/optimizer.py +55 -0
- wmo/agents/project.py +928 -0
- wmo/cli/__init__.py +5 -0
- wmo/cli/agent_session.py +1123 -0
- wmo/cli/app.py +2489 -0
- wmo/cli/e2b_cmds.py +212 -0
- wmo/cli/eval_closed_loop.py +207 -0
- wmo/cli/harness_app.py +1147 -0
- wmo/cli/harness_distill.py +659 -0
- wmo/cli/hosted_session.py +880 -0
- wmo/cli/ingest_cmd.py +165 -0
- wmo/cli/model_roles.py +82 -0
- wmo/cli/platform_cmds.py +372 -0
- wmo/cli/route_app.py +274 -0
- wmo/cli/session_state.py +243 -0
- wmo/cli/ui.py +1107 -0
- wmo/cli/workspace_sync.py +504 -0
- wmo/config/__init__.py +60 -0
- wmo/config/card.py +129 -0
- wmo/config/config.py +367 -0
- wmo/config/dotenv.py +67 -0
- wmo/config/settings.py +128 -0
- wmo/config/store.py +177 -0
- wmo/conftest.py +19 -0
- wmo/connect/__init__.py +88 -0
- wmo/connect/apps.py +78 -0
- wmo/connect/brave.py +284 -0
- wmo/connect/connector.py +79 -0
- wmo/connect/credentials.py +164 -0
- wmo/connect/github.py +321 -0
- wmo/connect/google.py +627 -0
- wmo/connect/notion.py +790 -0
- wmo/connect/oauth.py +461 -0
- wmo/connect/slack.py +555 -0
- wmo/connect/store.py +199 -0
- wmo/connect/types.py +156 -0
- wmo/core/__init__.py +21 -0
- wmo/core/parsing.py +281 -0
- wmo/core/render.py +271 -0
- wmo/core/text.py +40 -0
- wmo/core/types.py +116 -0
- wmo/distill/__init__.py +14 -0
- wmo/distill/agents.py +140 -0
- wmo/distill/config.py +1006 -0
- wmo/distill/cost.py +437 -0
- wmo/distill/data.py +921 -0
- wmo/distill/deadlines.py +254 -0
- wmo/distill/fake_tinker.py +734 -0
- wmo/distill/gate.py +122 -0
- wmo/distill/loop.py +3499 -0
- wmo/distill/renderers.py +399 -0
- wmo/distill/rendering.py +620 -0
- wmo/distill/rollouts.py +726 -0
- wmo/distill/samples.py +195 -0
- wmo/distill/store.py +829 -0
- wmo/distill/teacher.py +714 -0
- wmo/distill/tokens.py +535 -0
- wmo/distill/tracking.py +552 -0
- wmo/distill/tripwire.py +411 -0
- wmo/distill/xtoken/byte_offsets.py +152 -0
- wmo/distill/xtoken/chunks.py +457 -0
- wmo/distill/xtoken/prompt_logprobs.py +475 -0
- wmo/distill/xtoken/teacher_render.py +346 -0
- wmo/engine/__init__.py +28 -0
- wmo/engine/autoconfig.py +367 -0
- wmo/engine/build.py +346 -0
- wmo/engine/demo.py +77 -0
- wmo/engine/eval_suites.py +245 -0
- wmo/engine/grounding.py +491 -0
- wmo/engine/knowledge.py +291 -0
- wmo/engine/loader.py +36 -0
- wmo/engine/play.py +92 -0
- wmo/engine/prompts.py +99 -0
- wmo/engine/replay.py +443 -0
- wmo/engine/reporting.py +58 -0
- wmo/engine/workspace.py +468 -0
- wmo/engine/world_model.py +568 -0
- wmo/env/__init__.py +22 -0
- wmo/env/base.py +121 -0
- wmo/env/closed_loop.py +229 -0
- wmo/env/episode.py +107 -0
- wmo/env/llm_agent.py +93 -0
- wmo/env/scenarios.py +73 -0
- wmo/evals/__init__.py +52 -0
- wmo/evals/agreement.py +110 -0
- wmo/evals/base.py +45 -0
- wmo/evals/closed_loop.py +480 -0
- wmo/evals/failover.py +96 -0
- wmo/evals/gold.py +127 -0
- wmo/evals/grid.py +394 -0
- wmo/evals/grid_plot.py +205 -0
- wmo/evals/harbor/__init__.py +27 -0
- wmo/evals/harbor/agent.py +573 -0
- wmo/evals/harbor/ctrf.py +171 -0
- wmo/evals/harbor/e2b_environment.py +587 -0
- wmo/evals/harbor/e2b_template_policy.py +144 -0
- wmo/evals/harbor/scorer.py +875 -0
- wmo/evals/harbor/tasks.py +140 -0
- wmo/evals/open_loop.py +194 -0
- wmo/evals/tasks.py +53 -0
- wmo/harness/__init__.py +51 -0
- wmo/harness/code_runtime.py +288 -0
- wmo/harness/create.py +1191 -0
- wmo/harness/delta.py +220 -0
- wmo/harness/doc.py +556 -0
- wmo/harness/e2b_ledger.py +342 -0
- wmo/harness/e2b_reap.py +476 -0
- wmo/harness/e2b_sandbox.py +350 -0
- wmo/harness/environment.py +35 -0
- wmo/harness/live_session.py +543 -0
- wmo/harness/mutate.py +343 -0
- wmo/harness/pi_e2b.py +1710 -0
- wmo/harness/pi_entry/entry.ts +268 -0
- wmo/harness/pi_entry/runner_frames.ts +92 -0
- wmo/harness/pi_entry/runner_live.ts +587 -0
- wmo/harness/pi_entry/runner_service.ts +270 -0
- wmo/harness/pi_entry/runner_stdio.ts +374 -0
- wmo/harness/pi_entry/runner_termination.ts +142 -0
- wmo/harness/pi_local.py +262 -0
- wmo/harness/pi_runtime.py +495 -0
- wmo/harness/pi_vendor.py +65 -0
- wmo/harness/population.py +509 -0
- wmo/harness/project_proposer.py +569 -0
- wmo/harness/proposer.py +977 -0
- wmo/harness/runner_link.py +619 -0
- wmo/harness/runtime.py +389 -0
- wmo/harness/scoring.py +247 -0
- wmo/harness/skills.py +116 -0
- wmo/harness/source_tree.py +319 -0
- wmo/harness/store.py +176 -0
- wmo/harness/tools.py +105 -0
- wmo/harness/vendor/manifest.sha256 +58 -0
- wmo/harness/vendor/pi-agent/CHANGELOG.md +556 -0
- wmo/harness/vendor/pi-agent/LICENSE +21 -0
- wmo/harness/vendor/pi-agent/README.md +488 -0
- wmo/harness/vendor/pi-agent/VENDOR.md +39 -0
- wmo/harness/vendor/pi-agent/docs/agent-harness.md +486 -0
- wmo/harness/vendor/pi-agent/docs/durable-harness.md +212 -0
- wmo/harness/vendor/pi-agent/docs/hooks.md +445 -0
- wmo/harness/vendor/pi-agent/docs/models.md +966 -0
- wmo/harness/vendor/pi-agent/docs/observability.md +376 -0
- wmo/harness/vendor/pi-agent/package.json +60 -0
- wmo/harness/vendor/pi-agent/src/agent-loop.ts +748 -0
- wmo/harness/vendor/pi-agent/src/agent.ts +575 -0
- wmo/harness/vendor/pi-agent/src/harness/agent-harness.ts +1029 -0
- wmo/harness/vendor/pi-agent/src/harness/compaction/branch-summarization.ts +261 -0
- wmo/harness/vendor/pi-agent/src/harness/compaction/compaction.ts +747 -0
- wmo/harness/vendor/pi-agent/src/harness/compaction/utils.ts +144 -0
- wmo/harness/vendor/pi-agent/src/harness/env/nodejs.ts +550 -0
- wmo/harness/vendor/pi-agent/src/harness/messages.ts +164 -0
- wmo/harness/vendor/pi-agent/src/harness/prompt-templates.ts +267 -0
- wmo/harness/vendor/pi-agent/src/harness/session/jsonl-repo.ts +177 -0
- wmo/harness/vendor/pi-agent/src/harness/session/jsonl-storage.ts +293 -0
- wmo/harness/vendor/pi-agent/src/harness/session/memory-repo.ts +50 -0
- wmo/harness/vendor/pi-agent/src/harness/session/memory-storage.ts +131 -0
- wmo/harness/vendor/pi-agent/src/harness/session/repo-utils.ts +51 -0
- wmo/harness/vendor/pi-agent/src/harness/session/session.ts +267 -0
- wmo/harness/vendor/pi-agent/src/harness/session/uuid.ts +54 -0
- wmo/harness/vendor/pi-agent/src/harness/skills.ts +375 -0
- wmo/harness/vendor/pi-agent/src/harness/system-prompt.ts +34 -0
- wmo/harness/vendor/pi-agent/src/harness/types.ts +836 -0
- wmo/harness/vendor/pi-agent/src/harness/utils/shell-output.ts +135 -0
- wmo/harness/vendor/pi-agent/src/harness/utils/truncate.ts +344 -0
- wmo/harness/vendor/pi-agent/src/index.ts +44 -0
- wmo/harness/vendor/pi-agent/src/node.ts +2 -0
- wmo/harness/vendor/pi-agent/src/proxy.ts +367 -0
- wmo/harness/vendor/pi-agent/src/types.ts +428 -0
- wmo/harness/vendor/pi-agent/test/agent-loop.test.ts +1351 -0
- wmo/harness/vendor/pi-agent/test/agent.test.ts +699 -0
- wmo/harness/vendor/pi-agent/test/e2e.test.ts +404 -0
- wmo/harness/vendor/pi-agent/test/harness/agent-harness-stream.test.ts +213 -0
- wmo/harness/vendor/pi-agent/test/harness/agent-harness.test.ts +608 -0
- wmo/harness/vendor/pi-agent/test/harness/compaction.test.ts +655 -0
- wmo/harness/vendor/pi-agent/test/harness/nodejs-env.test.ts +321 -0
- wmo/harness/vendor/pi-agent/test/harness/prompt-templates.test.ts +90 -0
- wmo/harness/vendor/pi-agent/test/harness/repo.test.ts +68 -0
- wmo/harness/vendor/pi-agent/test/harness/resource-formatting.test.ts +24 -0
- wmo/harness/vendor/pi-agent/test/harness/session-test-utils.ts +55 -0
- wmo/harness/vendor/pi-agent/test/harness/session-uuid.test.ts +50 -0
- wmo/harness/vendor/pi-agent/test/harness/session.test.ts +156 -0
- wmo/harness/vendor/pi-agent/test/harness/skills.test.ts +116 -0
- wmo/harness/vendor/pi-agent/test/harness/storage.test.ts +299 -0
- wmo/harness/vendor/pi-agent/test/harness/system-prompt.test.ts +66 -0
- wmo/harness/vendor/pi-agent/test/harness/truncate.test.ts +169 -0
- wmo/harness/vendor/pi-agent/test/scratch/simple.ts +72 -0
- wmo/harness/vendor/pi-agent/test/utils/calculate.ts +32 -0
- wmo/harness/vendor/pi-agent/test/utils/get-current-time.ts +46 -0
- wmo/harness/vendor/pi-agent/tsconfig.build.json +13 -0
- wmo/harness/vendor/pi-agent/vitest.config.ts +19 -0
- wmo/harness/vendor/pi-agent/vitest.harness.config.ts +28 -0
- wmo/harness/vendor/vendor_pi.sh +59 -0
- wmo/harness/workspace_patch.py +270 -0
- wmo/ingest/__init__.py +47 -0
- wmo/ingest/adapter.py +72 -0
- wmo/ingest/base.py +114 -0
- wmo/ingest/braintrust.py +339 -0
- wmo/ingest/detect.py +126 -0
- wmo/ingest/langfuse.py +291 -0
- wmo/ingest/langsmith.py +444 -0
- wmo/ingest/mastra.py +330 -0
- wmo/ingest/messages.py +170 -0
- wmo/ingest/normalize.py +679 -0
- wmo/ingest/otel_genai.py +69 -0
- wmo/ingest/otel_writer.py +100 -0
- wmo/ingest/phoenix.py +150 -0
- wmo/ingest/postgres.py +246 -0
- wmo/ingest/posthog.py +320 -0
- wmo/ingest/quality.py +28 -0
- wmo/ingest/stream.py +209 -0
- wmo/ingest/testdata/sample_otlp.json +60 -0
- wmo/ingest/testdata/sample_spans.jsonl +3 -0
- wmo/optimize/__init__.py +25 -0
- wmo/optimize/base.py +143 -0
- wmo/optimize/gepa.py +806 -0
- wmo/optimize/judge.py +262 -0
- wmo/optimize/judge_quality.py +359 -0
- wmo/optimize/knn.py +468 -0
- wmo/optimize/numeric.py +152 -0
- wmo/optimize/outcomes.py +103 -0
- wmo/optimize/policy.py +669 -0
- wmo/optimize/report.py +231 -0
- wmo/optimize/reward.py +129 -0
- wmo/optimize/routing.py +373 -0
- wmo/platform/__init__.py +6 -0
- wmo/platform/auth.py +115 -0
- wmo/platform/client.py +551 -0
- wmo/platform/credentials.py +126 -0
- wmo/platform/transfer.py +158 -0
- wmo/providers/__init__.py +40 -0
- wmo/providers/_bedrock_chat.py +155 -0
- wmo/providers/_openai_common.py +182 -0
- wmo/providers/_responses_common.py +472 -0
- wmo/providers/anthropic.py +134 -0
- wmo/providers/azure_openai.py +296 -0
- wmo/providers/base.py +300 -0
- wmo/providers/bedrock.py +312 -0
- wmo/providers/models.py +205 -0
- wmo/providers/openai.py +143 -0
- wmo/providers/openai_responses.py +240 -0
- wmo/providers/pool.py +170 -0
- wmo/providers/registry.py +73 -0
- wmo/providers/retry.py +151 -0
- wmo/providers/tinker.py +936 -0
- wmo/providers/waterfall.py +336 -0
- wmo/research/__init__.py +81 -0
- wmo/research/ablation.py +133 -0
- wmo/research/concurrency_plot.py +523 -0
- wmo/research/concurrency_run.py +240 -0
- wmo/research/concurrency_scaling.py +270 -0
- wmo/research/gepa_scaling.py +274 -0
- wmo/research/pipeline.py +198 -0
- wmo/research/scaling_split.py +82 -0
- wmo/research/scenario_fidelity.py +198 -0
- wmo/research/scenario_recovery.py +92 -0
- wmo/research/seed_stability.py +90 -0
- wmo/research/trace_scaling.py +348 -0
- wmo/retrieval/__init__.py +6 -0
- wmo/retrieval/embedders.py +105 -0
- wmo/retrieval/leakfree.py +52 -0
- wmo/retrieval/retriever.py +173 -0
- wmo/scenarios/__init__.py +58 -0
- wmo/scenarios/builder.py +152 -0
- wmo/scenarios/mining/__init__.py +27 -0
- wmo/scenarios/mining/clustering.py +171 -0
- wmo/scenarios/mining/facets.py +226 -0
- wmo/scenarios/mining/selection.py +220 -0
- wmo/scenarios/synthesis/__init__.py +6 -0
- wmo/scenarios/synthesis/scenario_set.py +63 -0
- wmo/scenarios/synthesis/synthesizer.py +85 -0
- wmo/scenarios/verification/__init__.py +17 -0
- wmo/scenarios/verification/judge.py +97 -0
- wmo/scenarios/verification/verify.py +135 -0
- wmo/serving/__init__.py +5 -0
- wmo/serving/builds.py +451 -0
- wmo/serving/chat.py +878 -0
- wmo/serving/endpoint_config.py +64 -0
- wmo/serving/savings.py +250 -0
- wmo/serving/server.py +553 -0
- wmo/serving/traces_source.py +206 -0
- wmo/telemetry.py +213 -0
- wmo/tracking/__init__.py +36 -0
- wmo/tracking/clock.py +24 -0
- wmo/tracking/metered.py +125 -0
- wmo/tracking/pricing.py +99 -0
- wmo/tracking/store.py +31 -0
- wmo/tracking/tracker.py +149 -0
- world_model_optimizer-0.2.0.dist-info/METADATA +203 -0
- world_model_optimizer-0.2.0.dist-info/RECORD +308 -0
- world_model_optimizer-0.2.0.dist-info/WHEEL +4 -0
- world_model_optimizer-0.2.0.dist-info/entry_points.txt +2 -0
wmo/distill/samples.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""Human-readable sample episode rollouts: render, select, and assemble.
|
|
2
|
+
|
|
3
|
+
Metrics rows say how a batch scored; they never show WHAT the model actually
|
|
4
|
+
saw and produced. This module renders a small sample of each batch's episodes
|
|
5
|
+
into plain text for humans: the exact episode token stream decoded WITH the
|
|
6
|
+
chat template's special tokens (`<|im_start|>`, think blocks, tool-call
|
|
7
|
+
markers), because that framing is precisely what a reader needs to judge
|
|
8
|
+
whether the harness, renderer, and policy line up.
|
|
9
|
+
|
|
10
|
+
`render_episode_text` renders one trial: a prefix-clean episode decodes in a
|
|
11
|
+
single pass over the final span's prompt plus its sampled tokens (the full
|
|
12
|
+
conversation as the model saw it, template included), while an episode whose
|
|
13
|
+
history was edited mid-run decodes per fragment with a `FRAGMENT_BREAK`
|
|
14
|
+
marker line in between; over-long bodies keep head and tail
|
|
15
|
+
(`truncate_middle`). `sample_rollouts` picks and renders the first N
|
|
16
|
+
span-bearing trials of a batch, and `samples_markdown` joins them into the
|
|
17
|
+
document `DistillRunStore.write_samples` persists. The loop calls these after
|
|
18
|
+
every training batch, the warmup collection, and each eval batch
|
|
19
|
+
(`train.log_sample_rollouts` sets N; 0 disables).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from collections.abc import Sequence
|
|
25
|
+
from typing import Protocol
|
|
26
|
+
|
|
27
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
28
|
+
|
|
29
|
+
from wmo.distill.tokens import TrialRecord
|
|
30
|
+
from wmo.providers.tinker import TokenSpan
|
|
31
|
+
|
|
32
|
+
MAX_EPISODE_CHARS = 40_000
|
|
33
|
+
"""Rendered episode bodies longer than this keep head and tail; the middle is elided."""
|
|
34
|
+
|
|
35
|
+
FRAGMENT_BREAK = (
|
|
36
|
+
"----- FRAGMENT BREAK: the next call's prompt did not extend the episode "
|
|
37
|
+
"tokens, so the context re-rendered from scratch -----"
|
|
38
|
+
)
|
|
39
|
+
"""Marker line between the per-fragment decodes of a non-prefix-clean episode."""
|
|
40
|
+
|
|
41
|
+
SAMPLE_SEPARATOR = "\n\n" + "=" * 78 + "\n\n"
|
|
42
|
+
"""Separator between samples in one batch's samples document."""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SpecialsDecoder(Protocol):
|
|
46
|
+
"""The one rendering call episode logging needs; `ChatRendering` satisfies it."""
|
|
47
|
+
|
|
48
|
+
def decode_with_specials(self, token_ids: list[int]) -> str:
|
|
49
|
+
"""Decode token ids to text KEEPING special tokens."""
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class SampleRollout(BaseModel):
|
|
54
|
+
"""One rendered sample episode (a row of the tracker's samples table)."""
|
|
55
|
+
|
|
56
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
57
|
+
|
|
58
|
+
trial_name: str = Field(min_length=1)
|
|
59
|
+
reward: float
|
|
60
|
+
text: str
|
|
61
|
+
"""The full `render_episode_text` output: header plus decoded episode."""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _is_prefix(prefix: list[int], sequence: list[int]) -> bool:
|
|
65
|
+
"""Whether `prefix` equals the start of `sequence` (the datum builder's test)."""
|
|
66
|
+
return len(prefix) <= len(sequence) and sequence[: len(prefix)] == prefix
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _fragment_token_runs(spans: Sequence[TokenSpan]) -> list[list[int]]:
|
|
70
|
+
"""The episode's token stream, split where a prompt broke the prefix property.
|
|
71
|
+
|
|
72
|
+
Mirrors the datum builder's merge (`wmo.distill.data._merge_trial_spans`):
|
|
73
|
+
spans are walked in call order and each prompt that extends the
|
|
74
|
+
accumulated tokens verbatim contributes only its delta, so a prefix-clean
|
|
75
|
+
episode comes back as ONE run equal to the final span's prompt plus its
|
|
76
|
+
sampled tokens (the whole conversation, decodable in a single pass). A
|
|
77
|
+
non-extending prompt closes the current run and starts a fresh one.
|
|
78
|
+
"""
|
|
79
|
+
runs: list[list[int]] = []
|
|
80
|
+
tokens: list[int] = []
|
|
81
|
+
for span in sorted(spans, key=lambda item: item.call_index):
|
|
82
|
+
prompt = list(span.prompt_token_ids)
|
|
83
|
+
if tokens and _is_prefix(tokens, prompt):
|
|
84
|
+
delta = prompt[len(tokens) :]
|
|
85
|
+
else:
|
|
86
|
+
if tokens:
|
|
87
|
+
runs.append(tokens)
|
|
88
|
+
tokens = []
|
|
89
|
+
delta = prompt
|
|
90
|
+
tokens.extend(delta)
|
|
91
|
+
tokens.extend(span.sampled_token_ids)
|
|
92
|
+
if tokens:
|
|
93
|
+
runs.append(tokens)
|
|
94
|
+
return runs
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def truncate_middle(text: str, limit: int = MAX_EPISODE_CHARS) -> str:
|
|
98
|
+
"""Keep the head and tail of an over-long text, eliding the middle.
|
|
99
|
+
|
|
100
|
+
The head (system prompt and task setup) and the tail (the episode's final
|
|
101
|
+
turns and outcome) are what a reader checks first, so those survive and
|
|
102
|
+
the middle is replaced by a marker naming how much was cut.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
text: The rendered episode text.
|
|
106
|
+
limit: Max characters kept FROM `text` (the marker itself is extra).
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
`text` unchanged when it fits, else head plus marker plus tail.
|
|
110
|
+
|
|
111
|
+
Raises:
|
|
112
|
+
ValueError: If `limit` is not positive.
|
|
113
|
+
"""
|
|
114
|
+
if limit < 1:
|
|
115
|
+
raise ValueError(f"truncation limit must be >= 1, got {limit}")
|
|
116
|
+
if len(text) <= limit:
|
|
117
|
+
return text
|
|
118
|
+
head = limit // 2
|
|
119
|
+
tail = limit - head
|
|
120
|
+
omitted = len(text) - head - tail
|
|
121
|
+
marker = f"\n\n[... {omitted} chars elided from the middle of the episode ...]\n\n"
|
|
122
|
+
return text[:head] + marker + text[len(text) - tail :]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def render_episode_text(record: TrialRecord, renderer: SpecialsDecoder) -> str:
|
|
126
|
+
"""One trial's episode as readable text, chat-template framing included.
|
|
127
|
+
|
|
128
|
+
The header names the trial and its outcome (reward, passed, stop reason,
|
|
129
|
+
span/fragment/token counts); the body is the episode's exact token stream
|
|
130
|
+
decoded WITH special tokens. A prefix-clean episode decodes in one pass
|
|
131
|
+
(the final span's prompt plus its sampled tokens IS the full conversation
|
|
132
|
+
including every template marker); a fragmented episode decodes per
|
|
133
|
+
fragment with `FRAGMENT_BREAK` lines in between. Bodies beyond
|
|
134
|
+
`MAX_EPISODE_CHARS` keep head and tail (`truncate_middle`).
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
record: The scored trial with its recorded spans.
|
|
138
|
+
renderer: Supplies the specials-preserving decode
|
|
139
|
+
(`ChatRendering.decode_with_specials`).
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
The header plus decoded episode text, newline-terminated.
|
|
143
|
+
"""
|
|
144
|
+
runs = _fragment_token_runs(record.spans)
|
|
145
|
+
token_count = sum(len(run) for run in runs)
|
|
146
|
+
header = (
|
|
147
|
+
f"### trial {record.trial_name}\n"
|
|
148
|
+
f"reward: {record.reward:g} | passed: {record.passed} | "
|
|
149
|
+
f"stop reason: {record.stop_reason or 'unknown'} | "
|
|
150
|
+
f"spans: {len(record.spans)} | fragments: {len(runs)} | "
|
|
151
|
+
f"episode tokens: {token_count}\n"
|
|
152
|
+
)
|
|
153
|
+
if not runs:
|
|
154
|
+
return header + "\n(no token spans were recorded for this trial)\n"
|
|
155
|
+
break_line = "\n" + FRAGMENT_BREAK + "\n"
|
|
156
|
+
body = break_line.join(renderer.decode_with_specials(run) for run in runs)
|
|
157
|
+
return header + "\n" + truncate_middle(body) + "\n"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def sample_rollouts(
|
|
161
|
+
records: Sequence[TrialRecord], renderer: SpecialsDecoder, limit: int
|
|
162
|
+
) -> list[SampleRollout]:
|
|
163
|
+
"""Render the first `limit` span-bearing trials of a batch, in record order.
|
|
164
|
+
|
|
165
|
+
Record order is the scorer's deterministic cell order (task x attempt),
|
|
166
|
+
so the same batch always samples the same trials. Span-less trials carry
|
|
167
|
+
nothing to read and are skipped.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
records: One batch's trial records.
|
|
171
|
+
renderer: Supplies the specials-preserving decode.
|
|
172
|
+
limit: Max samples to render; 0 renders nothing.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
At most `limit` rendered samples.
|
|
176
|
+
"""
|
|
177
|
+
samples: list[SampleRollout] = []
|
|
178
|
+
for record in records:
|
|
179
|
+
if len(samples) >= limit:
|
|
180
|
+
break
|
|
181
|
+
if not record.spans:
|
|
182
|
+
continue
|
|
183
|
+
samples.append(
|
|
184
|
+
SampleRollout(
|
|
185
|
+
trial_name=record.trial_name,
|
|
186
|
+
reward=record.reward,
|
|
187
|
+
text=render_episode_text(record, renderer),
|
|
188
|
+
)
|
|
189
|
+
)
|
|
190
|
+
return samples
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def samples_markdown(samples: Sequence[SampleRollout]) -> str:
|
|
194
|
+
"""One batch's samples as a single document (the `samples/<name>.md` payload)."""
|
|
195
|
+
return SAMPLE_SEPARATOR.join(sample.text for sample in samples)
|