Spaces:
Running on Zero
Running on Zero
Codex commited on
Commit ·
0c76f10
1
Parent(s): 0133877
Add candidate-local generation text transforms
Browse files- quality_runtime.py +139 -29
- tests/test_coverage_adaptive.py +161 -0
- tests/test_quality_runtime.py +34 -0
quality_runtime.py
CHANGED
|
@@ -4083,6 +4083,13 @@ def run_coverage_adaptive_cascade(
|
|
| 4083 |
TrajectoryGateResult,
|
| 4084 |
],
|
| 4085 |
generation_evidence_factory: Callable[..., CandidateGenerationEvidence] | None = None,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4086 |
max_generated_chunks: int = 32,
|
| 4087 |
max_generated_text_units: int = 800,
|
| 4088 |
max_sequence_paths: int = 3,
|
|
@@ -4105,6 +4112,13 @@ def run_coverage_adaptive_cascade(
|
|
| 4105 |
that explicitly accepts the keyword-only ``generation_context`` opts into
|
| 4106 |
per-chunk refill scheduling. Its evidence factory must accept the same
|
| 4107 |
keyword and report the supplied row-local candidate ordinals.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4108 |
"""
|
| 4109 |
|
| 4110 |
if not all(
|
|
@@ -4121,6 +4135,10 @@ def run_coverage_adaptive_cascade(
|
|
| 4121 |
generation_evidence_factory
|
| 4122 |
):
|
| 4123 |
raise ValueError("generation_evidence_factory must be callable")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4124 |
|
| 4125 |
def accepts_generation_context(callback: Callable[..., Any]) -> bool:
|
| 4126 |
try:
|
|
@@ -4149,6 +4167,46 @@ def run_coverage_adaptive_cascade(
|
|
| 4149 |
raise ValueError(
|
| 4150 |
"context-aware generation and evidence callbacks must opt in together"
|
| 4151 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4152 |
try:
|
| 4153 |
chunk_tuple = tuple(str(chunk) for chunk in chunks)
|
| 4154 |
except TypeError as error:
|
|
@@ -4257,7 +4315,6 @@ def run_coverage_adaptive_cascade(
|
|
| 4257 |
)
|
| 4258 |
|
| 4259 |
chunk_units = tuple(count_speech_units(chunk) for chunk in chunk_tuple)
|
| 4260 |
-
initial_units = sum(chunk_units)
|
| 4261 |
chunk_count = len(chunk_tuple)
|
| 4262 |
# A single local chunk still receives a fresh exact whole-waveform speaker
|
| 4263 |
# measurement before return. Allow the same narrowly bounded local proxy
|
|
@@ -4265,6 +4322,19 @@ def run_coverage_adaptive_cascade(
|
|
| 4265 |
dp_boundary_limit = boundary_limit
|
| 4266 |
if chunk_count > chunk_budget:
|
| 4267 |
raise ValueError("initial trajectory exceeds the generated-chunk budget")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4268 |
if initial_units > text_budget:
|
| 4269 |
raise ValueError("initial trajectory exceeds the generated-text-unit budget")
|
| 4270 |
|
|
@@ -4309,21 +4379,18 @@ def run_coverage_adaptive_cascade(
|
|
| 4309 |
except Exception as error:
|
| 4310 |
raise RuntimeError("candidate generation evidence is invalid") from error
|
| 4311 |
|
| 4312 |
-
initial_context = CandidateGenerationContext(
|
| 4313 |
-
candidate_index=0,
|
| 4314 |
-
seed=base_seed,
|
| 4315 |
-
chunk_indices=tuple(range(chunk_count)),
|
| 4316 |
-
chunk_candidate_ordinals=(0,) * chunk_count,
|
| 4317 |
-
)
|
| 4318 |
try:
|
| 4319 |
if context_aware_generator:
|
| 4320 |
raw_initial_trajectory = candidate_generator(
|
| 4321 |
-
|
| 4322 |
base_seed,
|
| 4323 |
generation_context=initial_context,
|
| 4324 |
)
|
| 4325 |
else:
|
| 4326 |
-
raw_initial_trajectory = candidate_generator(
|
|
|
|
|
|
|
|
|
|
| 4327 |
except Exception as error:
|
| 4328 |
raise RuntimeError("initial trajectory generation failed") from error
|
| 4329 |
initial_trajectory = _coverage_trajectory_tuple(
|
|
@@ -4332,7 +4399,7 @@ def run_coverage_adaptive_cascade(
|
|
| 4332 |
)
|
| 4333 |
initial_generation_evidence = generation_evidence(
|
| 4334 |
initial_context,
|
| 4335 |
-
|
| 4336 |
)
|
| 4337 |
attempted_seeds.append(base_seed)
|
| 4338 |
try:
|
|
@@ -4441,7 +4508,7 @@ def run_coverage_adaptive_cascade(
|
|
| 4441 |
refill_attempts = [0] * chunk_count
|
| 4442 |
next_candidate_index = 1
|
| 4443 |
while generated_chunks < chunk_budget:
|
| 4444 |
-
if any(
|
| 4445 |
not pool and generated_units + chunk_units[index] > text_budget
|
| 4446 |
for index, pool in enumerate(pools)
|
| 4447 |
):
|
|
@@ -4452,7 +4519,7 @@ def run_coverage_adaptive_cascade(
|
|
| 4452 |
for candidate in pool
|
| 4453 |
)
|
| 4454 |
needs_first_alternative = initial_all_strict and not has_noninitial_coverage
|
| 4455 |
-
|
| 4456 |
index
|
| 4457 |
for index, pool in enumerate(pools)
|
| 4458 |
if (
|
|
@@ -4460,45 +4527,88 @@ def run_coverage_adaptive_cascade(
|
|
| 4460 |
or len(pool) < path_limit
|
| 4461 |
or needs_first_alternative
|
| 4462 |
)
|
| 4463 |
-
and generated_units + chunk_units[index] <= text_budget
|
| 4464 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4465 |
if not eligible:
|
| 4466 |
break
|
| 4467 |
-
|
| 4468 |
-
|
| 4469 |
-
|
| 4470 |
len(pools[index]),
|
| 4471 |
refill_attempts[index],
|
| 4472 |
index,
|
| 4473 |
-
)
|
| 4474 |
-
|
| 4475 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4476 |
refill_chunks = (chunk_tuple[chunk_index],)
|
| 4477 |
-
|
| 4478 |
-
|
| 4479 |
-
seed=seed,
|
| 4480 |
-
chunk_indices=(chunk_index,),
|
| 4481 |
-
chunk_candidate_ordinals=(refill_attempts[chunk_index] + 1,),
|
| 4482 |
-
)
|
| 4483 |
try:
|
| 4484 |
if context_aware_generator:
|
| 4485 |
raw_refill_trajectory = candidate_generator(
|
| 4486 |
-
|
| 4487 |
seed,
|
| 4488 |
generation_context=refill_context,
|
| 4489 |
)
|
| 4490 |
else:
|
| 4491 |
-
raw_refill_trajectory = candidate_generator(
|
|
|
|
|
|
|
|
|
|
| 4492 |
except Exception as error:
|
| 4493 |
raise RuntimeError("chunk refill generation failed") from error
|
| 4494 |
refill_trajectory = _coverage_trajectory_tuple(raw_refill_trajectory, 1)
|
| 4495 |
refill_generation_evidence = generation_evidence(
|
| 4496 |
refill_context,
|
| 4497 |
-
|
| 4498 |
)
|
| 4499 |
attempted_seeds.append(seed)
|
| 4500 |
generated_chunks += 1
|
| 4501 |
-
generated_units +=
|
| 4502 |
refill_attempts[chunk_index] += 1
|
| 4503 |
try:
|
| 4504 |
raw_refill_verification = refill_candidate_verifier(
|
|
|
|
| 4083 |
TrajectoryGateResult,
|
| 4084 |
],
|
| 4085 |
generation_evidence_factory: Callable[..., CandidateGenerationEvidence] | None = None,
|
| 4086 |
+
candidate_generation_text_transform: (
|
| 4087 |
+
Callable[
|
| 4088 |
+
[tuple[str, ...], CandidateGenerationContext],
|
| 4089 |
+
Sequence[str],
|
| 4090 |
+
]
|
| 4091 |
+
| None
|
| 4092 |
+
) = None,
|
| 4093 |
max_generated_chunks: int = 32,
|
| 4094 |
max_generated_text_units: int = 800,
|
| 4095 |
max_sequence_paths: int = 3,
|
|
|
|
| 4112 |
that explicitly accepts the keyword-only ``generation_context`` opts into
|
| 4113 |
per-chunk refill scheduling. Its evidence factory must accept the same
|
| 4114 |
keyword and report the supplied row-local candidate ordinals.
|
| 4115 |
+
|
| 4116 |
+
``candidate_generation_text_transform`` may derive candidate-local text
|
| 4117 |
+
from the canonical verifier targets and immutable generation context. The
|
| 4118 |
+
transformed chunks are sent only to the generator and generation-evidence
|
| 4119 |
+
factory. Semantic verifiers continue to receive the canonical chunks.
|
| 4120 |
+
Generated-text-unit accounting uses the transformed text and is checked
|
| 4121 |
+
before every generator call.
|
| 4122 |
"""
|
| 4123 |
|
| 4124 |
if not all(
|
|
|
|
| 4135 |
generation_evidence_factory
|
| 4136 |
):
|
| 4137 |
raise ValueError("generation_evidence_factory must be callable")
|
| 4138 |
+
if candidate_generation_text_transform is not None and not callable(
|
| 4139 |
+
candidate_generation_text_transform
|
| 4140 |
+
):
|
| 4141 |
+
raise ValueError("candidate_generation_text_transform must be callable")
|
| 4142 |
|
| 4143 |
def accepts_generation_context(callback: Callable[..., Any]) -> bool:
|
| 4144 |
try:
|
|
|
|
| 4167 |
raise ValueError(
|
| 4168 |
"context-aware generation and evidence callbacks must opt in together"
|
| 4169 |
)
|
| 4170 |
+
|
| 4171 |
+
def generation_chunks(
|
| 4172 |
+
canonical_chunks: tuple[str, ...],
|
| 4173 |
+
context: CandidateGenerationContext,
|
| 4174 |
+
) -> tuple[str, ...]:
|
| 4175 |
+
if candidate_generation_text_transform is None:
|
| 4176 |
+
return canonical_chunks
|
| 4177 |
+
try:
|
| 4178 |
+
transformed = candidate_generation_text_transform(
|
| 4179 |
+
canonical_chunks,
|
| 4180 |
+
context,
|
| 4181 |
+
)
|
| 4182 |
+
except Exception as error:
|
| 4183 |
+
raise RuntimeError(
|
| 4184 |
+
"candidate generation text transform failed"
|
| 4185 |
+
) from error
|
| 4186 |
+
if isinstance(transformed, (str, bytes, bytearray, np.ndarray)):
|
| 4187 |
+
raise RuntimeError(
|
| 4188 |
+
"candidate generation text transform returned invalid chunks"
|
| 4189 |
+
)
|
| 4190 |
+
try:
|
| 4191 |
+
normalized = tuple(transformed)
|
| 4192 |
+
except TypeError as error:
|
| 4193 |
+
raise RuntimeError(
|
| 4194 |
+
"candidate generation text transform returned invalid chunks"
|
| 4195 |
+
) from error
|
| 4196 |
+
if (
|
| 4197 |
+
len(normalized) != len(canonical_chunks)
|
| 4198 |
+
or any(
|
| 4199 |
+
not isinstance(chunk, str)
|
| 4200 |
+
or not chunk
|
| 4201 |
+
or count_speech_units(chunk) <= 0
|
| 4202 |
+
for chunk in normalized
|
| 4203 |
+
)
|
| 4204 |
+
):
|
| 4205 |
+
raise RuntimeError(
|
| 4206 |
+
"candidate generation text transform returned invalid chunks"
|
| 4207 |
+
)
|
| 4208 |
+
return normalized
|
| 4209 |
+
|
| 4210 |
try:
|
| 4211 |
chunk_tuple = tuple(str(chunk) for chunk in chunks)
|
| 4212 |
except TypeError as error:
|
|
|
|
| 4315 |
)
|
| 4316 |
|
| 4317 |
chunk_units = tuple(count_speech_units(chunk) for chunk in chunk_tuple)
|
|
|
|
| 4318 |
chunk_count = len(chunk_tuple)
|
| 4319 |
# A single local chunk still receives a fresh exact whole-waveform speaker
|
| 4320 |
# measurement before return. Allow the same narrowly bounded local proxy
|
|
|
|
| 4322 |
dp_boundary_limit = boundary_limit
|
| 4323 |
if chunk_count > chunk_budget:
|
| 4324 |
raise ValueError("initial trajectory exceeds the generated-chunk budget")
|
| 4325 |
+
initial_context = CandidateGenerationContext(
|
| 4326 |
+
candidate_index=0,
|
| 4327 |
+
seed=base_seed,
|
| 4328 |
+
chunk_indices=tuple(range(chunk_count)),
|
| 4329 |
+
chunk_candidate_ordinals=(0,) * chunk_count,
|
| 4330 |
+
)
|
| 4331 |
+
initial_generation_chunks = generation_chunks(
|
| 4332 |
+
chunk_tuple,
|
| 4333 |
+
initial_context,
|
| 4334 |
+
)
|
| 4335 |
+
initial_units = sum(
|
| 4336 |
+
count_speech_units(chunk) for chunk in initial_generation_chunks
|
| 4337 |
+
)
|
| 4338 |
if initial_units > text_budget:
|
| 4339 |
raise ValueError("initial trajectory exceeds the generated-text-unit budget")
|
| 4340 |
|
|
|
|
| 4379 |
except Exception as error:
|
| 4380 |
raise RuntimeError("candidate generation evidence is invalid") from error
|
| 4381 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4382 |
try:
|
| 4383 |
if context_aware_generator:
|
| 4384 |
raw_initial_trajectory = candidate_generator(
|
| 4385 |
+
initial_generation_chunks,
|
| 4386 |
base_seed,
|
| 4387 |
generation_context=initial_context,
|
| 4388 |
)
|
| 4389 |
else:
|
| 4390 |
+
raw_initial_trajectory = candidate_generator(
|
| 4391 |
+
initial_generation_chunks,
|
| 4392 |
+
base_seed,
|
| 4393 |
+
)
|
| 4394 |
except Exception as error:
|
| 4395 |
raise RuntimeError("initial trajectory generation failed") from error
|
| 4396 |
initial_trajectory = _coverage_trajectory_tuple(
|
|
|
|
| 4399 |
)
|
| 4400 |
initial_generation_evidence = generation_evidence(
|
| 4401 |
initial_context,
|
| 4402 |
+
initial_generation_chunks,
|
| 4403 |
)
|
| 4404 |
attempted_seeds.append(base_seed)
|
| 4405 |
try:
|
|
|
|
| 4508 |
refill_attempts = [0] * chunk_count
|
| 4509 |
next_candidate_index = 1
|
| 4510 |
while generated_chunks < chunk_budget:
|
| 4511 |
+
if candidate_generation_text_transform is None and any(
|
| 4512 |
not pool and generated_units + chunk_units[index] > text_budget
|
| 4513 |
for index, pool in enumerate(pools)
|
| 4514 |
):
|
|
|
|
| 4519 |
for candidate in pool
|
| 4520 |
)
|
| 4521 |
needs_first_alternative = initial_all_strict and not has_noninitial_coverage
|
| 4522 |
+
structurally_eligible = [
|
| 4523 |
index
|
| 4524 |
for index, pool in enumerate(pools)
|
| 4525 |
if (
|
|
|
|
| 4527 |
or len(pool) < path_limit
|
| 4528 |
or needs_first_alternative
|
| 4529 |
)
|
|
|
|
| 4530 |
]
|
| 4531 |
+
if candidate_generation_text_transform is None:
|
| 4532 |
+
eligible = [
|
| 4533 |
+
index
|
| 4534 |
+
for index in structurally_eligible
|
| 4535 |
+
if generated_units + chunk_units[index] <= text_budget
|
| 4536 |
+
]
|
| 4537 |
+
else:
|
| 4538 |
+
eligible = structurally_eligible
|
| 4539 |
if not eligible:
|
| 4540 |
break
|
| 4541 |
+
|
| 4542 |
+
def refill_priority(index: int) -> tuple[int, int, int]:
|
| 4543 |
+
return (
|
| 4544 |
len(pools[index]),
|
| 4545 |
refill_attempts[index],
|
| 4546 |
index,
|
| 4547 |
+
)
|
| 4548 |
+
|
| 4549 |
+
refill_proposal = None
|
| 4550 |
+
for proposed_index in sorted(eligible, key=refill_priority):
|
| 4551 |
+
proposed_seed = base_seed + next_candidate_index
|
| 4552 |
+
proposed_canonical_chunks = (chunk_tuple[proposed_index],)
|
| 4553 |
+
proposed_context = CandidateGenerationContext(
|
| 4554 |
+
candidate_index=next_candidate_index,
|
| 4555 |
+
seed=proposed_seed,
|
| 4556 |
+
chunk_indices=(proposed_index,),
|
| 4557 |
+
chunk_candidate_ordinals=(
|
| 4558 |
+
refill_attempts[proposed_index] + 1,
|
| 4559 |
+
),
|
| 4560 |
+
)
|
| 4561 |
+
proposed_generation_chunks = generation_chunks(
|
| 4562 |
+
proposed_canonical_chunks,
|
| 4563 |
+
proposed_context,
|
| 4564 |
+
)
|
| 4565 |
+
proposed_units = sum(
|
| 4566 |
+
count_speech_units(chunk)
|
| 4567 |
+
for chunk in proposed_generation_chunks
|
| 4568 |
+
)
|
| 4569 |
+
if generated_units + proposed_units <= text_budget:
|
| 4570 |
+
refill_proposal = (
|
| 4571 |
+
proposed_index,
|
| 4572 |
+
proposed_seed,
|
| 4573 |
+
proposed_generation_chunks,
|
| 4574 |
+
proposed_context,
|
| 4575 |
+
proposed_units,
|
| 4576 |
+
)
|
| 4577 |
+
break
|
| 4578 |
+
if refill_proposal is None:
|
| 4579 |
+
break
|
| 4580 |
+
(
|
| 4581 |
+
chunk_index,
|
| 4582 |
+
seed,
|
| 4583 |
+
refill_generation_chunks,
|
| 4584 |
+
refill_context,
|
| 4585 |
+
refill_units,
|
| 4586 |
+
) = refill_proposal
|
| 4587 |
refill_chunks = (chunk_tuple[chunk_index],)
|
| 4588 |
+
if generated_chunks + len(refill_generation_chunks) > chunk_budget:
|
| 4589 |
+
break
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4590 |
try:
|
| 4591 |
if context_aware_generator:
|
| 4592 |
raw_refill_trajectory = candidate_generator(
|
| 4593 |
+
refill_generation_chunks,
|
| 4594 |
seed,
|
| 4595 |
generation_context=refill_context,
|
| 4596 |
)
|
| 4597 |
else:
|
| 4598 |
+
raw_refill_trajectory = candidate_generator(
|
| 4599 |
+
refill_generation_chunks,
|
| 4600 |
+
seed,
|
| 4601 |
+
)
|
| 4602 |
except Exception as error:
|
| 4603 |
raise RuntimeError("chunk refill generation failed") from error
|
| 4604 |
refill_trajectory = _coverage_trajectory_tuple(raw_refill_trajectory, 1)
|
| 4605 |
refill_generation_evidence = generation_evidence(
|
| 4606 |
refill_context,
|
| 4607 |
+
refill_generation_chunks,
|
| 4608 |
)
|
| 4609 |
attempted_seeds.append(seed)
|
| 4610 |
generated_chunks += 1
|
| 4611 |
+
generated_units += refill_units
|
| 4612 |
refill_attempts[chunk_index] += 1
|
| 4613 |
try:
|
| 4614 |
raw_refill_verification = refill_candidate_verifier(
|
tests/test_coverage_adaptive.py
CHANGED
|
@@ -4,6 +4,7 @@ import math
|
|
| 4 |
import numpy as np
|
| 5 |
import pytest
|
| 6 |
|
|
|
|
| 7 |
from quality_runtime import (
|
| 8 |
CASCADE_EVIDENCE_LOG_PREFIX,
|
| 9 |
CandidateGenerationContext,
|
|
@@ -861,6 +862,166 @@ def test_context_aware_refills_rotate_cfg_and_endpoint_policy_per_chunk():
|
|
| 861 |
]
|
| 862 |
|
| 863 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 864 |
def test_context_generation_and_evidence_must_opt_in_together_and_match():
|
| 865 |
def context_generator(chunks, seed, *, generation_context):
|
| 866 |
return tuple(chunks)
|
|
|
|
| 4 |
import numpy as np
|
| 5 |
import pytest
|
| 6 |
|
| 7 |
+
from production import count_speech_units
|
| 8 |
from quality_runtime import (
|
| 9 |
CASCADE_EVIDENCE_LOG_PREFIX,
|
| 10 |
CandidateGenerationContext,
|
|
|
|
| 862 |
]
|
| 863 |
|
| 864 |
|
| 865 |
+
def test_candidate_local_generation_text_is_separate_from_canonical_verification():
|
| 866 |
+
chunks = ("甲", "乙")
|
| 867 |
+
transformed = []
|
| 868 |
+
generated = []
|
| 869 |
+
evidenced = []
|
| 870 |
+
verified = []
|
| 871 |
+
|
| 872 |
+
def transform(candidate_chunks, generation_context):
|
| 873 |
+
assert isinstance(generation_context, CandidateGenerationContext)
|
| 874 |
+
transformed.append((candidate_chunks, generation_context))
|
| 875 |
+
if generation_context.candidate_index == 0:
|
| 876 |
+
return ("甲甲", "乙乙")
|
| 877 |
+
if generation_context.chunk_indices == (0,):
|
| 878 |
+
return ("甲甲甲甲",)
|
| 879 |
+
return ("乙乙乙",)
|
| 880 |
+
|
| 881 |
+
def generator(candidate_chunks, seed, *, generation_context):
|
| 882 |
+
generated.append((candidate_chunks, seed, generation_context))
|
| 883 |
+
return candidate_chunks
|
| 884 |
+
|
| 885 |
+
def generation_evidence(
|
| 886 |
+
candidate_index,
|
| 887 |
+
seed,
|
| 888 |
+
chunk_indices,
|
| 889 |
+
candidate_chunks,
|
| 890 |
+
*,
|
| 891 |
+
generation_context,
|
| 892 |
+
):
|
| 893 |
+
evidenced.append((candidate_chunks, generation_context))
|
| 894 |
+
ordinal = generation_context.chunk_candidate_ordinals[0]
|
| 895 |
+
assert all(
|
| 896 |
+
value == ordinal
|
| 897 |
+
for value in generation_context.chunk_candidate_ordinals
|
| 898 |
+
)
|
| 899 |
+
scheduled = generation_cfg_for_candidate_offset(ordinal)
|
| 900 |
+
return CandidateGenerationEvidence(
|
| 901 |
+
chunk_indices=chunk_indices,
|
| 902 |
+
chunk_text_units=tuple(
|
| 903 |
+
count_speech_units(chunk) for chunk in candidate_chunks
|
| 904 |
+
),
|
| 905 |
+
scheduled_cfg=scheduled,
|
| 906 |
+
effective_cfgs=tuple(scheduled for _ in candidate_chunks),
|
| 907 |
+
floor_reasons=tuple(() for _ in candidate_chunks),
|
| 908 |
+
chunk_candidate_ordinals=(
|
| 909 |
+
generation_context.chunk_candidate_ordinals
|
| 910 |
+
),
|
| 911 |
+
)
|
| 912 |
+
|
| 913 |
+
def whole_verifier(trajectory, candidate_chunks, seed):
|
| 914 |
+
verified.append(("whole", candidate_chunks))
|
| 915 |
+
return _joined_rejected_local(candidate_chunks)
|
| 916 |
+
|
| 917 |
+
def refill_verifier(trajectory, candidate_chunks, seed):
|
| 918 |
+
verified.append(("refill", candidate_chunks))
|
| 919 |
+
return _local_verification(candidate_chunks)
|
| 920 |
+
|
| 921 |
+
def final_verifier(result, candidate_chunks):
|
| 922 |
+
verified.append(("final", candidate_chunks))
|
| 923 |
+
return _exact_final(candidate_chunks)
|
| 924 |
+
|
| 925 |
+
result = run_coverage_adaptive_cascade(
|
| 926 |
+
chunks,
|
| 927 |
+
10,
|
| 928 |
+
generator,
|
| 929 |
+
whole_verifier,
|
| 930 |
+
refill_verifier,
|
| 931 |
+
sequence_final_verifier=final_verifier,
|
| 932 |
+
generation_evidence_factory=generation_evidence,
|
| 933 |
+
candidate_generation_text_transform=transform,
|
| 934 |
+
max_generated_chunks=3,
|
| 935 |
+
max_generated_text_units=7,
|
| 936 |
+
max_sequence_paths=1,
|
| 937 |
+
)
|
| 938 |
+
|
| 939 |
+
assert [(values, seed) for values, seed, _ in generated] == [
|
| 940 |
+
(("甲甲", "乙乙"), 10),
|
| 941 |
+
(("乙乙乙",), 11),
|
| 942 |
+
]
|
| 943 |
+
assert [values for values, _ in evidenced] == [
|
| 944 |
+
("甲甲", "乙乙"),
|
| 945 |
+
("乙乙乙",),
|
| 946 |
+
]
|
| 947 |
+
assert verified == [
|
| 948 |
+
("whole", chunks),
|
| 949 |
+
("refill", ("乙",)),
|
| 950 |
+
("final", chunks),
|
| 951 |
+
]
|
| 952 |
+
assert [context.chunk_indices for _, context in transformed] == [
|
| 953 |
+
(0, 1),
|
| 954 |
+
(0,),
|
| 955 |
+
(1,),
|
| 956 |
+
]
|
| 957 |
+
assert result.trajectory == ("甲甲", "乙乙乙")
|
| 958 |
+
assert result.chunk_candidate_indices == (0, 1)
|
| 959 |
+
assert result.generated_chunk_count == 3
|
| 960 |
+
assert result.generated_text_units == 7
|
| 961 |
+
assert result.diagnostics.attempts[0].chunk_text_units == (2, 2)
|
| 962 |
+
assert result.diagnostics.attempts[1].chunk_text_units == (3,)
|
| 963 |
+
|
| 964 |
+
|
| 965 |
+
def test_generation_evidence_must_report_transformed_text_units():
|
| 966 |
+
def evidence(candidate_index, seed, chunk_indices, candidate_chunks):
|
| 967 |
+
return CandidateGenerationEvidence(
|
| 968 |
+
chunk_indices=chunk_indices,
|
| 969 |
+
chunk_text_units=(1,),
|
| 970 |
+
scheduled_cfg=3.0,
|
| 971 |
+
effective_cfgs=(3.0,),
|
| 972 |
+
floor_reasons=((),),
|
| 973 |
+
)
|
| 974 |
+
|
| 975 |
+
with pytest.raises(
|
| 976 |
+
RuntimeError,
|
| 977 |
+
match="candidate generation evidence is invalid",
|
| 978 |
+
):
|
| 979 |
+
run_coverage_adaptive_cascade(
|
| 980 |
+
("甲",),
|
| 981 |
+
10,
|
| 982 |
+
lambda candidate_chunks, seed: candidate_chunks,
|
| 983 |
+
lambda trajectory, candidate_chunks, seed: _local_verification(
|
| 984 |
+
candidate_chunks
|
| 985 |
+
),
|
| 986 |
+
lambda trajectory, candidate_chunks, seed: _local_verification(
|
| 987 |
+
candidate_chunks
|
| 988 |
+
),
|
| 989 |
+
sequence_final_verifier=lambda result, candidate_chunks: (
|
| 990 |
+
_exact_final(candidate_chunks)
|
| 991 |
+
),
|
| 992 |
+
generation_evidence_factory=evidence,
|
| 993 |
+
candidate_generation_text_transform=(
|
| 994 |
+
lambda candidate_chunks, context: ("甲甲",)
|
| 995 |
+
),
|
| 996 |
+
)
|
| 997 |
+
|
| 998 |
+
|
| 999 |
+
@pytest.mark.parametrize(
|
| 1000 |
+
"transform",
|
| 1001 |
+
[
|
| 1002 |
+
lambda chunks, context: "甲",
|
| 1003 |
+
lambda chunks, context: ("甲", "乙"),
|
| 1004 |
+
lambda chunks, context: ("",),
|
| 1005 |
+
lambda chunks, context: (object(),),
|
| 1006 |
+
],
|
| 1007 |
+
)
|
| 1008 |
+
def test_candidate_generation_text_transform_must_return_aligned_speech_chunks(
|
| 1009 |
+
transform,
|
| 1010 |
+
):
|
| 1011 |
+
generated = []
|
| 1012 |
+
with pytest.raises(RuntimeError, match="transform returned invalid chunks"):
|
| 1013 |
+
run_coverage_adaptive_cascade(
|
| 1014 |
+
("甲",),
|
| 1015 |
+
10,
|
| 1016 |
+
lambda chunks, seed: generated.append((chunks, seed)),
|
| 1017 |
+
lambda trajectory, chunks, seed: _local_verification(chunks),
|
| 1018 |
+
lambda trajectory, chunks, seed: _local_verification(chunks),
|
| 1019 |
+
sequence_final_verifier=lambda result, chunks: _exact_final(chunks),
|
| 1020 |
+
candidate_generation_text_transform=transform,
|
| 1021 |
+
)
|
| 1022 |
+
assert generated == []
|
| 1023 |
+
|
| 1024 |
+
|
| 1025 |
def test_context_generation_and_evidence_must_opt_in_together_and_match():
|
| 1026 |
def context_generator(chunks, seed, *, generation_context):
|
| 1027 |
return tuple(chunks)
|
tests/test_quality_runtime.py
CHANGED
|
@@ -3070,6 +3070,40 @@ def test_network_endpoint_units_do_not_change_k32_or_800_work_budget():
|
|
| 3070 |
assert result.diagnostics.attempts[0].chunk_text_units == (public_units,)
|
| 3071 |
|
| 3072 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3073 |
def test_candidate_limit_requires_complete_positive_text_unit_budget():
|
| 3074 |
with pytest.raises(ValueError, match="provided together"):
|
| 3075 |
candidate_limit_for_chunk_budget(1, total_text_units=40)
|
|
|
|
| 3070 |
assert result.diagnostics.attempts[0].chunk_text_units == (public_units,)
|
| 3071 |
|
| 3072 |
|
| 3073 |
+
def test_transformed_initial_text_budget_is_checked_before_generation():
|
| 3074 |
+
generated = []
|
| 3075 |
+
verified = []
|
| 3076 |
+
contexts = []
|
| 3077 |
+
|
| 3078 |
+
def transform(chunks, generation_context):
|
| 3079 |
+
contexts.append(generation_context)
|
| 3080 |
+
return ("甲" * 801,)
|
| 3081 |
+
|
| 3082 |
+
with pytest.raises(
|
| 3083 |
+
ValueError,
|
| 3084 |
+
match="initial trajectory exceeds the generated-text-unit budget",
|
| 3085 |
+
):
|
| 3086 |
+
run_coverage_adaptive_cascade(
|
| 3087 |
+
("甲",),
|
| 3088 |
+
902,
|
| 3089 |
+
lambda chunks, seed: generated.append((chunks, seed)),
|
| 3090 |
+
lambda *args: verified.append(("whole", args)),
|
| 3091 |
+
lambda *args: verified.append(("refill", args)),
|
| 3092 |
+
sequence_final_verifier=lambda *args: verified.append(
|
| 3093 |
+
("final", args)
|
| 3094 |
+
),
|
| 3095 |
+
candidate_generation_text_transform=transform,
|
| 3096 |
+
max_generated_chunks=32,
|
| 3097 |
+
max_generated_text_units=800,
|
| 3098 |
+
)
|
| 3099 |
+
|
| 3100 |
+
assert len(contexts) == 1
|
| 3101 |
+
assert contexts[0].candidate_index == 0
|
| 3102 |
+
assert contexts[0].chunk_indices == (0,)
|
| 3103 |
+
assert generated == []
|
| 3104 |
+
assert verified == []
|
| 3105 |
+
|
| 3106 |
+
|
| 3107 |
def test_candidate_limit_requires_complete_positive_text_unit_budget():
|
| 3108 |
with pytest.raises(ValueError, match="provided together"):
|
| 3109 |
candidate_limit_for_chunk_budget(1, total_text_units=40)
|