Coerce AgentPolicyConfig fields to string (fixes #169) - #170
Conversation
f97aa40 to
d8029a4
Compare
jeqcho
left a comment
There was a problem hiding this comment.
The fix is right, and I checked it end to end rather than by eye. On main, -P api_key_env=0 records 0 in the log and -P model=42 dies with AttributeError: 'int' object has no attribute 'partition' at _llm.py:111. On this branch every non-None case records as str and None passes through untouched. Your new test fails on main with that same AttributeError, so it's a real regression test and not just a shape assertion.
The str(x) if x else "" looks like a slip but it's load-bearing, and I'd put a line of comment on it. Coercing False to "False" would flip the truthiness that not api_key_env and api_key_env or _OPENROUTER_KEY depend on, and the lookup would go hunting for an env var literally named False. Falsy to "" keeps the OpenRouter guard and the ANTHROPIC_API_KEY fallback working, which I confirmed. Without a comment someone will simplify it to str(x) later and quietly break that.
Two things before this can go in.
The description doesn't match the diff. It says you corrected a build_toolset call signature inside bind() and added a missing state_labels() helper to Toolset in _tools.py. Neither is in the diff, and both already exist on main, from #110 and #134. The test counts don't line up either: the agent plugin suite is 264 tests, not 35, and there's no 398-test workspace run. The three files you actually touched are fine, so I think the body got pasted in from somewhere else. Please rewrite it to describe the real change, since it lands in merge history.
CHANGELOG entry under Unreleased, per CONTRIBUTING.md:131. This changes what the public LLMAgentPolicy constructor writes into the eval log, so it counts as user-visible.
Minor: test_policy_e2e.py:1010 sets env={"False": "test-key"}, which is never looked up, since False coerces to "" rather than "False". It reads as though the opposite were true. A plain dummy env would be clearer.
One more: the tests/test_rerun_sink.py change is unrelated to #169. It is correct, and I ran that file both with and without rerun installed, so I don't mind keeping it here. Just say so in the description.
|
Hi @jeqcho, Please review this PR |
|
CHANGELOG entry looks right, and good call spelling out the falsy-to- Still need the description sorted before I merge. The |
|
Hi @jeqcho, Thanks for catching this. The remaining CI checks are now running. |
|
Heads up: main's history was rewritten today to purge 3 GB of accidentally committed eval logs (#212), and this branch's history includes the old rewritten commits (it looks like main was merged into the branch at some point). Rebasing it is not mechanical because the original commits conflict with the current AgentPolicyConfig code, so those conflicts are yours to resolve. Suggested path: Happy to review as soon as it is rebased. |
b2f68b4 to
51411e2
Compare
jeqcho
left a comment
There was a problem hiding this comment.
Thanks for taking this on — the coercion lands in the right place (before all the validation in LLMAgentPolicy.__init__, so validators see the coerced values), it covers all five unvalidated str | None params including speed, CI is green across every platform, and the new test passes locally for me too. A few things need attention before this is mergeable, though.
1. The falsy → "" special case is inconsistent with its own rationale (plugins/inspect-robots-agent/src/inspect_robots_agent/policy.py:258-271)
The comment justifies str(x) if x else "" by saying api_key_env=False coerced to "False" would look up an env var literally named False and bypass the key fallbacks. But the same logic applies to truthy values, and those are not protected: -P api_key_env=true still coerces to "True" and produces a lookup for an env var literally named True (verified — policy.config.api_key_env == "True"). So the special case only fixes half of the problem it describes, while introducing new inconsistencies:
-P model=0→""→ falls into the "no model configured" unset path, while-P model=1→"1". Same kind of typo, opposite outcomes, and the user's value is silently discarded in the falsy case.-P effort=falsenow raisesConfigError: effort must be one of [...], got ''— the user typedfalsebut the error shows'', which makes the message harder to act on thangot 'False'would be.
I'd suggest one of two directions:
- Uniform
str(x)with no falsy special case, letting the existing validation and provider resolution fail loudly with the actual value in the message; or - Reject non-strings with a guided
ConfigError, which is what this same constructor already does for exactly this failure mode elsewhere — seeprior_learnings(policy.py:277-283, whose message even explains "the -P parser coerces unquoted values; pass -P 'prior_learnings="..."'") and the explicitisinstance(..., bool)guards onmax_output_tokensandimage_horizon. Issue #169 offered "narrowing at the constructor" or widening the annotation; the fail-fast spelling matches the repo's established style (cf. the #154 changelog entry) and I'd lean toward it — either way the truthy/falsy asymmetry should go.
2. The PR description is stale after the rebase
The body describes a build_toolset signature fix, a state_labels() helper in _tools.py, and timeline-transcript lint cleanups — none of these are in the current diff; they already exist on main. Please update the description to match what the PR actually changes now (coercion + test, rerun import check, CHANGELOG), and refresh the verification numbers if they were from the old branch state.
3. tests/test_rerun_sink.py change is unrelated to #169
The find_spec → try/import rerun switch for _RERUN_INSTALLED (tests/test_rerun_sink.py:34-39) is a reasonable robustness improvement on its own, but it has nothing to do with config coercion — it would be cleaner as its own small PR with its motivation stated there. If it stays, one note: pytest.importorskip("rerun") at the old line 1035 already skips on any import failure, so replacing it (tests/test_rerun_sink.py:1039-1042) isn't strictly needed — only the module-level flag change is load-bearing. Keeping the flag as the single source of truth is defensible, so this part is your call, but please say so explicitly in the description.
4. CHANGELOG (CHANGELOG.md:208)
- The entry is one long unwrapped line; the surrounding entries wrap at roughly 80 columns — please match that.
- The sentence "Falsy values are coerced to
""to preserve API key and OpenRouter fallback logic" documents the behavior questioned in item 1; it will need rewording once that's settled.
5. Test coverage (plugins/inspect-robots-agent/tests/test_policy_e2e.py:2524-2538)
test_non_string_param_coercion is a good direct check. Once item 1 is resolved, please also cover the currently-untested asymmetry cases: a truthy non-string api_key_env (e.g. True) and a falsy non-string model (e.g. 0), asserting whichever behavior you land on.
Happy to take another look after these — the core of the change is close, it's mainly the falsy special case and the housekeeping around the bundling that need tightening. Thanks again for the contribution! 🙏
5ee4371 to
67726a1
Compare
|
Hi @jeqcho, I've addressed the latest review comments:
Whenever you have time, I'd really appreciate another review. Thanks again for all the detailed feedback! |
Fixes #169
Description
This PR fixes #169 by strictly rejecting non-string parameters (
model,base_url,api_key_env,effort,speed) in theLLMAgentPolicyconstructor and raising a guidedConfigError.This resolves the issue where unquoted CLI options parsed as booleans or integers (e.g.
-P api_key_env=falseor-P model=42) either recorded non-string values verbatim in the evaluation log or caused downstream tracebacks. Users are now guided to quote these values.Changes
ConfigErrorwhen non-strings are passed tomodel,base_url,api_key_env,effort, orspeed._RERUN_INSTALLEDintest_rerun_sink.pyto attempt to importreruninstead offind_spec, preventing false positives in environments with DLL load policies.CHANGELOG.mdentry under "Fixed" to reflect the strict validation change, wrapped at 80 columns.test_non_string_param_coercionto run parameterized checks for both truthy and falsy non-string parameters across all five fields, asserting that the proper guidedConfigErroris raised.test_falsy_api_key_env_does_not_send_the_openrouter_key_to_a_gatewayintest_anthropic.pyto only passNoneand""since non-strings are now caught and rejected at construction.Verification
uv run ruff check plugins/inspect-robots-agent(all passed).uv run python -m pytest plugins/inspect-robots-agent/tests(396/396 passed).uv run python -m pytest tests/test_rerun_sink.py(55/55 passed).