test(model-onboarding): fix false passes in the file-reading assertion - #1022
Open
denispetre wants to merge 2 commits into
Open
test(model-onboarding): fix false passes in the file-reading assertion#1022denispetre wants to merge 2 commits into
denispetre wants to merge 2 commits into
Conversation
Follow-up to #1009, addressing review findings 2, 3 and 7. Each was confirmed by executing the merged code, not by reading it. The core problem: the test could pass without the model reading the file. _matches("dog", "There is no dog in this image; it is a cat.") -> True _matches("dog", "I don't have access... probably a dog.") -> True Root cause was the fixtures, not the matcher. "What animal is in this image?" over dog.jpg has "dog" as its most likely answer with no image at all, and the file name was visible in the prompt. dummy.pdf's text ("Dummy PDF file") reads as a placeholder, so the model editorialized about whether it was real instead of reporting it — flaky 3-of-6 runs. Compensating for that needed a refusal-phrase blocklist, which then rejected correct answers carrying commentary (finding 2). Replaced both with generated local fixtures whose answers cannot be guessed: a PDF containing "Verification code: PDF-CODE-74915", and a purple square asked for its colour. With no prior to fall back on, the blocklist becomes unnecessary — refusals now fail simply because they do not contain the code. _matches is a plain case-insensitive whole-token match; 17-case suite covers correct formattings, refusals and near misses. Also fixes finding 7: create_messages interpolated state.model_dump(), rendering the attachment as {'id': UUID('8da6...'), 'full_name': ...} while the Analyze Files tool documents {"ID": "8da6..."}. A model copying UUID('...') gets INVALID_ATTACHMENT_ID; one emitting lowercase `id` has the item skipped and analyzes nothing — and then passed the permissive matcher. Finding 3 was masking finding 7, so fixing either alone would have misled. Now model_dump(by_alias=True, mode="json"). Fixtures are local so the expected answer is a property of bytes in this repo rather than a third-party host that could change a file and quietly weaken the test. Pinned -text in .gitattributes (EOL translation would corrupt them); document.pdf is uncompressed so the code stays greppable. Verified against alpha: image and pdf pass 3/3 (the PDF was 3/6). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Tightens the model-onboarding file-processing probe so it can’t “pass by guessing”, by switching to locally committed, unguessable fixtures and simplifying the answer matcher accordingly. It also updates the coded FileProcessingAgent prompt serialization to better align attachment rendering with the Analyze Files tool’s documented schema.
Changes:
- Replaces remote/guessable fixtures (
dog.jpg,dummy.pdf) with local, generated fixtures (fixtures/shape.png,fixtures/document.pdf) whose answers are intended to be unguessable. - Simplifies
_matches()to a case-insensitive whole-token (token-sequence) matcher, removing refusal-phrase heuristics. - Updates the file-processing coded agent to serialize state with
model_dump(by_alias=True, mode="json")and to load fixture bytes from disk (or HTTP) before uploading as a platform attachment. - Pins
*.pdf/*.pngas-textin.gitattributesto prevent EOL translation from corrupting binary fixtures.
Reviewed changes
Copilot reviewed 4 out of 6 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| testcases/model-onboarding/src/main.py | Updates file registry to use local, unguessable fixtures and replaces the matcher with a token-sequence match. |
| testcases/model-onboarding/src/agents/file_processing/agent.py | Adjusts prompt interpolation inputs for attachment IDs (aliases + JSON mode) and adds local-path fixture reading for upload. |
| testcases/model-onboarding/fixtures/README.md | Documents the new generated fixtures, rationale, and regeneration notes. |
| .gitattributes | Disables text normalization for PDF/PNG to avoid fixture corruption on checkout. |
| # INVALID_ATTACHMENT_ID, and one emitting lowercase `id` has the item | ||
| # skipped and analyzes nothing. mode="json" is required — without it the | ||
| # UUID stays a UUID object and serialization raises downstream. | ||
| state_values = state.model_dump(by_alias=True, mode="json") |
Review finding 1. Confirmed: settings and each get_chat_model call sat
outside any try, so one unusable flavor killed the node — `uipath run`
exits non-zero and `set -e` in run.sh stops the script before
validate_output.sh. No summary, no trace assertion, and every flavor that
already passed was discarded. That is the most likely condition during
real onboarding, and it is exactly what this test should be reporting.
Reproduced with api_flavors ["bogusvendor:whatever", "openai:responses"]
(bad one first, so an abort would hide the good one). Before: the run
died. After:
bogusvendor:whatever:
build: ✗ AgentStartupError: The model '...' is not available...
openai:responses:
build: ✓ UiPathAzureChatOpenAI
image: ✓ purple
pdf: ✓ PDF-CODE-74915
success=False, exit 0, summary intact.
Also from the review:
- Logs the class actually built (finding 6). Discovery can override the
requested api_flavor, and an unrecognized Bedrock flavor falls through
to Converse, so echoing the requested string alone would sign off on a
surface never exercised.
- Empty api_flavors now fails with "no api_flavors supplied" instead of
reporting a vacuous success on an empty summary.
- Summary lines are logged as they are produced, so a run that dies for
an unforeseen reason still leaves partial results in the job log.
Happy path unchanged; full assert.py incl. trace assertions passes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Follow-up to #1009, addressing review findings 2, 3 and 7. Each was confirmed by executing the merged code rather than reading it.
The problem
The test could pass without the model reading the file. Run against the code now on
main:A model that never opened the attachment was graded correct — the one thing this test exists to prevent.
Root cause was the fixtures, not the matcher
Credit to @tudormatei1, whose review identified this: the fixtures were guessable.
dog.jpgwas asked "what animal is in this image?" — "dog" is the most likely answer to that question with no image at all, and the file name was visible in the prompt.dummy.pdf's text is the literal"Dummy PDF file", which reads as a placeholder. The model kept commenting on whether the content was real instead of reporting it: flaky in 3 of 6 runs.Compensating for guessable answers required a refusal-phrase blocklist — which then rejected correct answers that carried commentary (finding 2), because the model says "placeholder" while reporting the text accurately.
The fix
Generated local fixtures whose answers cannot be guessed:
fixtures/document.pdf(608 B)Verification code: PDF-CODE-74915PDF-CODE-74915fixtures/shape.png(1.9 KB)purpleWith no prior to fall back on, the blocklist becomes unnecessary — refusals now fail simply because they don't contain the code.
_matchesis a plain case-insensitive whole-token match. A 17-case suite covers correct formattings (markdown, parser prefixes, capitalisation), refusals, and near misses (PDF-CODE-11111,magenta).Also fixes finding 7 — and why it mattered
create_messagesinterpolatedstate.model_dump(), rendering the attachment as:{'id': UUID('8da6…'), 'full_name': 'animal.jpg', 'mime_type': …}while the Analyze Files tool documents
{"ID": "8da6…", "FullName": …}. A model copyingUUID('…')getsINVALID_ATTACHMENT_ID; one emitting lowercaseidhas the item skipped, analyzes nothing, and then passed the permissive matcher.Finding 3 was masking finding 7 — fixing either alone would have looked worse or falsely better. Now
model_dump(by_alias=True, mode="json")(mode="json"is required, or the UUID stays an object and serialization raises downstream).Notes
*.pdf/*.pngas-textin.gitattributes— EOL translation on checkout would corrupt them and fail the test for a reason nobody would connect to line endings.document.pdfis written uncompressed on purpose, sogrep PDF-CODE-74915 fixtures/document.pdfworks without a PDF library.Verification
Ran against alpha: image and pdf pass 3/3 (the PDF was 3/6 before).
Not included
Remaining review findings, deliberately out of scope:
On @tudormatei1's f-string/3.11 comment: the mechanism is real (
requires-python = ">=3.11"while CI runs the python3.12 image), but it doesn't apply here — I parsed all six testcase files under real Python 3.11 and they're clean, so those line numbers were from an earlier push.🤖 Generated with Claude Code
Update: findings 1 and 6 also fixed
Finding 1 — a bad flavor no longer aborts the run.
PlatformSettingsand eachget_chat_modelcall sat outside anytry, so one unusable flavor killed the node:uipath runexits non-zero andset -einrun.shstops the script beforevalidate_output.sh. No summary, no trace assertion, and every flavor that already passed was thrown away — during model onboarding, the most likely condition of all.Reproduced with
["bogusvendor:whatever", "openai:responses"], bad one first so an abort would hide the good one. Before, the run died. Now:success=False, exit 0, summary intact — the failure is reported as a finding instead of a crash.Finding 6 — the built client is now logged.
build: ✓ UiPathAzureChatOpenAInames the class actually constructed. Discovery can override the requestedapi_flavor, and an unrecognized Bedrock flavor falls through to Converse, so echoing the requested string alone would sign off on a surface never exercised.Also: empty
api_flavorsnow fails with✗ no api_flavors suppliedrather than reporting a vacuous success on an empty summary, and summary lines are logged as they are produced so a run that dies unexpectedly still leaves partial results in the job log.Happy path unchanged. Full
assert.py, including trace assertions, passes.Still not included
awsbedrock:AnthropicMessagesis advertised in the workflow input but[bedrock]shipslangchain-aws[anthropic], notlangchain-anthropic, so that module raises on import. Now surfaces as abuild: ✗cell rather than a whole-run abort, but the extra is still wrong.FILE_REGISTRYis iterated unconditionally andrun()raises without a file.Analyze FilesTOOL span assertion doesn't exist (the span name would beAnalyze_Files, sanitized).These are scope decisions rather than bugs in the assertion, so I've left them for you to prioritize.