Skip to content

Commit e8ecbfd

Browse files
committed
fix(evaluation): 收紧报告脱敏与遥测完整性
1 parent 746fc7b commit e8ecbfd

2 files changed

Lines changed: 121 additions & 5 deletions

File tree

examples/optimization/eval_optimize_loop/report_builder.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@
2424
_MISSING_TOKEN_REASON = (
2525
"Reflection LM calls were observed but optimizer token usage was not reported."
2626
)
27+
_INVALID_TOKEN_REASON = "Optimizer token usage was malformed or inconsistent."
2728
_REDACTED = "[REDACTED]"
2829
_SENSITIVE_ENV_NAMES = ("TRPC_AGENT_API_KEY", "TRPC_AGENT_BASE_URL")
2930
_SENSITIVE_KEY_VALUE = re.compile(
30-
r"(?P<prefix>[\"']?(?:api_?key|base_?url|authorization)[\"']?\s*[:=]\s*)"
31+
r"(?P<prefix>[\"']?(?:api[_-]?key|base[_-]?url|authorization)[\"']?\s*[:=]\s*)"
3132
r"(?P<value>[\"'][^\"']*[\"']|(?:(?:bearer|basic|token)\s+)?[^\s,;}\]]+)",
3233
re.IGNORECASE,
3334
)
35+
_HTTP_URL = re.compile(r"https?://[^\s,;}\]<>\"']+", re.IGNORECASE)
3436
_BEARER_VALUE = re.compile(
3537
r"\bbearer(?:\s+|\s*[:=]\s*)[\"']?[^\s,;}\]\"']+[\"']?",
3638
re.IGNORECASE,
@@ -60,7 +62,19 @@ def _redact_error_message(error: Exception) -> str:
6062
lambda match: f"{match.group('prefix')}{_REDACTED}",
6163
message,
6264
)
63-
return _BEARER_VALUE.sub(f"Bearer {_REDACTED}", message)
65+
message = _BEARER_VALUE.sub(f"Bearer {_REDACTED}", message)
66+
return _HTTP_URL.sub(_REDACTED, message)
67+
68+
69+
def _is_complete_token_usage(value: object) -> bool:
70+
if not isinstance(value, dict):
71+
return False
72+
required = ("prompt", "completion", "total")
73+
if not all(key in value for key in required):
74+
return False
75+
if not all(type(value[key]) is int and value[key] >= 0 for key in required):
76+
return False
77+
return value["total"] == value["prompt"] + value["completion"]
6478

6579

6680
def _optimizer_resources(result: PipelineStageResult) -> OptimizerResourceObservation:
@@ -76,8 +90,12 @@ def _optimizer_resources(result: PipelineStageResult) -> OptimizerResourceObserv
7690
native = result.optimize_result
7791
reflection_calls = native.total_reflection_lm_calls
7892
cost_missing = reflection_calls > 0 and native.total_llm_cost <= 0
79-
token_usage = dict(native.total_token_usage)
80-
tokens_missing = reflection_calls > 0 and token_usage.get("total", 0) <= 0
93+
token_usage = native.total_token_usage
94+
token_usage_valid = _is_complete_token_usage(token_usage)
95+
tokens_missing = (
96+
not token_usage_valid
97+
or (reflection_calls > 0 and token_usage["total"] <= 0)
98+
)
8199
return OptimizerResourceObservation(
82100
scope_note=_OPTIMIZER_SCOPE,
83101
total_rounds=OptimizerResourceValue[int](
@@ -96,7 +114,11 @@ def _optimizer_resources(result: PipelineStageResult) -> OptimizerResourceObserv
96114
status="unavailable" if tokens_missing else "available",
97115
value=None if tokens_missing else token_usage,
98116
unit="tokens",
99-
reason=_MISSING_TOKEN_REASON if tokens_missing else None,
117+
reason=(
118+
_INVALID_TOKEN_REASON
119+
if tokens_missing and not token_usage_valid
120+
else _MISSING_TOKEN_REASON if tokens_missing else None
121+
),
100122
),
101123
duration_seconds=OptimizerResourceValue[float](
102124
status="available", value=native.duration_seconds, unit="seconds",

tests/evaluation/test_eval_optimize_loop_stage5_report_builder.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,100 @@ async def test_failure_report_redacts_environment_values_and_common_secret_forms
227227
assert "retryable=true" in report.error_message
228228

229229

230+
def test_failure_report_redacts_hyphenated_keys_and_bare_urls(tmp_path):
231+
root = _copy_example(tmp_path)
232+
prepared = prepare_run(root / "pipeline.json", run_id="report_failure_hyphenated")
233+
error = RuntimeError(
234+
"X-API-Key: hyphen-api-secret x-api-key=lower-hyphen-secret; "
235+
"endpoint https://private.example/v1?token=secret and http://10.0.0.8:8080/run"
236+
)
237+
238+
report = build_failure_report(
239+
prepared, progress=_progress(), error=error,
240+
source_prompt_hashes={}, existing_artifacts=[],
241+
generated_at=datetime(2026, 7, 18, 0, 1, tzinfo=timezone.utc),
242+
)
243+
244+
assert "hyphen-api-secret" not in report.error_message
245+
assert "lower-hyphen-secret" not in report.error_message
246+
assert "https://private.example/v1?token=secret" not in report.error_message
247+
assert "http://10.0.0.8:8080/run" not in report.error_message
248+
assert "endpoint [REDACTED]" in report.error_message
249+
250+
251+
@pytest.mark.asyncio
252+
@pytest.mark.parametrize(
253+
"token_usage",
254+
[
255+
{},
256+
{"prompt": 1, "completion": 2},
257+
{"prompt": 1, "completion": "2", "total": 3},
258+
{"prompt": True, "completion": 2, "total": 3},
259+
{"prompt": -1, "completion": 2, "total": 1},
260+
{"prompt": 1, "completion": 2, "total": 4},
261+
["not", "a", "dict"],
262+
],
263+
)
264+
async def test_real_report_marks_malformed_token_usage_unavailable(tmp_path, token_usage):
265+
root = _copy_example(tmp_path)
266+
prepared = prepare_run(root / "pipeline.json", run_id="report_real_bad_tokens")
267+
fake_result = await run_fake_stage(prepared, scenario="improve")
268+
candidate = OptimizerCandidateProposal.model_validate({
269+
**fake_result.candidate.model_dump(exclude={"scenario", "seed"}), "provider": "agent_optimizer",
270+
"optimizer_status": "SUCCEEDED", "finish_reason": "completed",
271+
"baseline_pass_rate": 1 / 3, "best_pass_rate": 1.0,
272+
"candidate_id": f"optimizer-{fake_result.candidate.candidate_id[-12:]}",
273+
})
274+
native = _optimize_result(
275+
await prepared.source_target.read_all(), candidate.prompts,
276+
).model_copy(update={"total_token_usage": token_usage})
277+
result = RealStageResult(
278+
**fake_result.model_dump(exclude={"scenario", "candidate"}), candidate=candidate,
279+
optimize_result=native,
280+
)
281+
282+
report = build_optimization_report(
283+
prepared, result, progress=_progress(),
284+
finished_at=datetime(2026, 7, 18, 0, 1, tzinfo=timezone.utc),
285+
)
286+
287+
assert report.optimizer_resources.token_usage.status == "unavailable"
288+
assert report.optimizer_resources.token_usage.value is None
289+
290+
291+
@pytest.mark.asyncio
292+
async def test_real_report_allows_consistent_zero_token_usage_without_reflection_calls(tmp_path):
293+
root = _copy_example(tmp_path)
294+
prepared = prepare_run(root / "pipeline.json", run_id="report_real_zero_tokens")
295+
fake_result = await run_fake_stage(prepared, scenario="improve")
296+
candidate = OptimizerCandidateProposal.model_validate({
297+
**fake_result.candidate.model_dump(exclude={"scenario", "seed"}), "provider": "agent_optimizer",
298+
"optimizer_status": "SUCCEEDED", "finish_reason": "completed",
299+
"baseline_pass_rate": 1 / 3, "best_pass_rate": 1.0,
300+
"candidate_id": f"optimizer-{fake_result.candidate.candidate_id[-12:]}",
301+
})
302+
native = _optimize_result(
303+
await prepared.source_target.read_all(), candidate.prompts,
304+
).model_copy(update={
305+
"total_reflection_lm_calls": 0,
306+
"total_token_usage": {"prompt": 0, "completion": 0, "total": 0, "cached": 7},
307+
})
308+
result = RealStageResult(
309+
**fake_result.model_dump(exclude={"scenario", "candidate"}), candidate=candidate,
310+
optimize_result=native,
311+
)
312+
313+
report = build_optimization_report(
314+
prepared, result, progress=_progress(),
315+
finished_at=datetime(2026, 7, 18, 0, 1, tzinfo=timezone.utc),
316+
)
317+
318+
assert report.optimizer_resources.token_usage.status == "available"
319+
assert report.optimizer_resources.token_usage.value == {
320+
"prompt": 0, "completion": 0, "total": 0, "cached": 7,
321+
}
322+
323+
230324
def test_report_dto_invariants_reject_inconsistent_artifact_states():
231325
with pytest.raises(ValueError, match="available artifacts require"):
232326
ArtifactReference(

0 commit comments

Comments
 (0)