Codex commited on
Commit
0c76f10
·
1 Parent(s): 0133877

Add candidate-local generation text transforms

Browse files
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
- chunk_tuple,
4322
  base_seed,
4323
  generation_context=initial_context,
4324
  )
4325
  else:
4326
- raw_initial_trajectory = candidate_generator(chunk_tuple, base_seed)
 
 
 
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
- chunk_tuple,
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
- eligible = [
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
- chunk_index = min(
4468
- eligible,
4469
- key=lambda index: (
4470
  len(pools[index]),
4471
  refill_attempts[index],
4472
  index,
4473
- ),
4474
- )
4475
- seed = base_seed + next_candidate_index
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4476
  refill_chunks = (chunk_tuple[chunk_index],)
4477
- refill_context = CandidateGenerationContext(
4478
- candidate_index=next_candidate_index,
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
- refill_chunks,
4487
  seed,
4488
  generation_context=refill_context,
4489
  )
4490
  else:
4491
- raw_refill_trajectory = candidate_generator(refill_chunks, seed)
 
 
 
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
- refill_chunks,
4498
  )
4499
  attempted_seeds.append(seed)
4500
  generated_chunks += 1
4501
- generated_units += chunk_units[chunk_index]
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)