test(model-onboarding): cover chat, tool calls, file processing and IS activity tools - #1009
test(model-onboarding): cover chat, tool calls, file processing and IS activity tools#1009denispetre wants to merge 32 commits into
Conversation
… agents Every code path now runs four capability payloads, rolled into the single success boolean: - simple: plain ainvoke, non-empty completion - tools: full tool-calling round trip (get_weather; executes the tool and asserts the final answer uses the result) - files/<name>: the coded file-processing agent — Studio Web's "Clone as Coded Agent" of the low-code FileProcessingAgent — run through the production ReAct stack (create_agent + Analyze Files internal tool) with the test file uploaded as a real platform attachment - is_tools: call-only Activity-tool check — bind the coded copy's real IS tools (Slack Send Message to Channel, Outlook 365 Send Email) and assert the model produces a well-formed tool call (known bound name + required args). Tools are never executed; nothing is sent; no connections needed. Agents live under src/agents/, one subfolder per coded agent. Each agent.py is the Studio Web generated main.py with exactly one structural change: the hardcoded llm becomes build_graph(llm, ...)/build_tools(...) so the model built from model_spec is injected — model + API flavor stay configurable. The vertex path pins vendor_type + api_flavor=GENERATE_CONTENT explicitly. utils.py is the generated interpolation helper, verbatim. Verified: graph compiles, input.json validates, both agents construct, the call-only assertion covers well-formed/missing-args/unknown-tool/no-call, and `uipath init` loads the graph. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…acks jq First live dispatch failed with exit 127: the uv:python3.12-bookworm container does not ship jq. python3 is guaranteed in the image; same JSON shape, inputs still validated against the testcase's GraphInput. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Live dispatch surfaced it: the helper lives in uipath._utils._ssl_context, not uipath_langchain._utils. files/* cells now load. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…l errors 60 chars hid the real failure in live dispatch diagnostics; cells now carry the exception type and up to 220 chars. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ts are forbidden Live dispatch showed 403 on POST /odata/Attachments for the CI principal. The files cells now try the full generated-agent path (platform attachment + Analyze Files) and degrade to the proven direct multimodal invoke with an explicit marker when attachment creation is denied. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Live proof via the dispatch workflow — Dispatching also surfaced and fixed three real issues now in this PR: the workflow's input builder used |
paths entries can now be 'vendor_type:api_flavor' pairs passed straight to get_chat_model (which accepts them as strings) alongside the registry keys — any API flavor can be supplied directly, and every payload including the coded agents runs with the model built for it. 'vendor:' alone lets the factory autodetect the flavor. Workflow paths description documents both forms. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Added: forgiving string coercion in
|
aa36733 to
e0abde2
Compare
|
Removed the |
…odels claude-opus-5 (vertex path) burns output budget on adaptive-thinking blocks before emitting tool-call JSON; at max_tokens=200 the is_tools cell fails with truncated args (stop_reason=max_tokens, args cut mid-object) while the identical call at 2000 produces both tool calls fully formed (verified against alpha). Raised uniformly across all path builders - non-thinking models are unaffected (they stop early), and 2000 keeps the run cheap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Vertex-hosted Anthropic models (claude-opus-5 is the first through this testcase) resolve to UiPathChatAnthropic in the client factory; the trace assertion's span-name alternation only listed the five previously exercised client classes, failing an otherwise fully green run (all payload cells passed on alpha + staging). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| except Exception as e: # model construction itself can fail | ||
| logger.error(f" construction failed: {e}") | ||
| model_results[path] = {"__build__": f"✗ {str(e)[:60]}"} | ||
| model_results[path] = {"__build__": f"✗ {f"{type(e).__name__}: {e}"[:220]}"} |
There was a problem hiding this comment.
i think f-string nested inside the other f-string with the same quotes is a problem. looks like this pattern became legal in pyrthon 3.12 but pyproject says requires-python = ">=3.11" so i suppose if you try running it on 3.11 it might break. I suppose CI uses a newer version so thats why it goes green? If u plan on changing it it also happens on line 372, 380, 392 and 404. link to this: https://peps.python.org/pep-0701/
Replaces the multi-payload grid (simple/tools/files/is_tools) with a single probe over the coded file_processing agent, and drops the is_tools agent and the AGENT_REGISTRY indirection that only existed to multiplex agents. The probe now records what the model actually returned: run() yields the model's own answer prefixed with the route it took ([agent] via a real platform attachment + Analyze Files, or [direct] when the org forbids attachment creation) and raises on empty output instead of collapsing everything to a bare tick. expected_traces.json follows the node rename (run_model_onboarding -> probe_file_processing). paths keeps both shorthands and vendor:flavor pairs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
.agent/, AGENTS.md, CLAUDE.md, agent.mermaid, bindings.json, entry-points.json and project.uiproj are regenerated by `uipath init` in run.sh; no sibling testcase tracks them. Swept in by an over-broad git add. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… output The live run showed the Responses API returns content as a list of block dicts, so the probe recorded the whole raw structure including a per-response 'id' that changes every run. Extract just the text so results are readable and deterministic. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The field named code paths but its values are api flavors, so rename it end to end: model_spec.api_flavors, FLAVOR_SHORTHANDS, the workflow's api_flavors dispatch input, and the docs. Both value forms are unchanged (shorthands and vendor_type:api_flavor pairs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keeps only what running the agent needs: the file registry, the model spec, one probe node that builds the model per api_flavor and calls the agent per file, and the graph the runtime entrypoint requires. Drops the second graph node, the GraphState dict, the flavor shorthand map (vendor_type:api_flavor is the general form), and the defensive guards around settings/model build/ unknown files. The one try/except left wraps the agent call so a model failure yields success=False instead of aborting the run. 205 -> 116 lines. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The runtime recompiles the graph with its own checkpointer (runtime/factory.py _compile_graph unwraps .builder), so the local .compile() result was discarded. Drop build_graph() and the redundant compile, build the StateGraph at module level, and let add_node infer the node name from the function. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Falling back to a plain multimodal call when attachment creation is denied reported a pass for a run that never exercised the agent path — the false confidence this testcase exists to catch. Let the failure surface: the cell records it and success goes false. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A non-empty answer proved little — a model can describe 'a PDF document' from the file name alone. Two checks now establish the file was read: - FILE_EVIDENCE: the answer must contain words only the contents reveal (the dummy PDF's literal 'Dummy PDF' text; village + sea for the photo), else the cell fails with what was missing and what came back. - expected_traces.json now requires an 'Analyze Files' TOOL span, so the run fails if the agent never invoked the file-reading tool. Also collapse multi-line SDK errors onto one line so failing cells stay readable (the 403 detail was rendering across newlines). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keyword matching on a descriptive answer was fuzzy and could pass on a lucky guess. Each file now carries its own question with a single correct word that only its contents reveal, and the file names are neutral so the answer cannot leak from them: - animal.jpg (a photo of a dog) -> 'What animal is in this image? Answer with one word only.' -> must contain 'dog' - document.pdf (content: 'Dummy PDF file') -> 'What is the first word of the text inside this document?' -> must contain 'dummy' FILE_REGISTRY entries become FileCase(file, question, expected); the global prompt input is gone from GraphInput, input.json and the workflow, since each file asks its own question. Docs now describe only the vendor_type:api_flavor form (the shorthand map was removed earlier). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The files knob was near-useless (you would rarely skip a format) and had to be kept in sync across the workflow input, ModelSpec and input.json. The probe now iterates FILE_REGISTRY directly, so adding a format is a single registry entry. Dispatch inputs are now model_name / api_flavors / agenthub_config / environments. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AGENTS.md, CLAUDE.md, .agent/, agent.mermaid and project.uiproj are regenerated by `uipath init` in run.sh and kept sweeping into commits. entry-points.json/bindings.json are deliberately left tracked because debug-breakpoints and dev-console commit theirs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Attachment creation is folder-scoped in Orchestrator (the candidate permissions BLOBFILES/EXECUTIONMEDIA/JOBS are all ScopeType=FOLDER), but the upload was called with no folder at all, which may be why it 403s rather than a missing grant. Pass folder_path from UIPATH_FOLDER_PATH and set that to 'Shared' in the dispatch workflow to find out. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…achment" This reverts commit 9eac02b.
Attachment creation 403s and the client id cannot be recovered (GitHub secrets are write-only and masked; APIM App Insights logs no caller identity). Decode the non-secret JWT claims after auth (sub / client_id / org / tenant / scope) so the principal's Orchestrator permissions can be checked. Prints no token and no secret; whitelisted claims only. Remove once the 403 is resolved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root cause of the attachment 403: `uipath auth --scope` defaults to 'OR.Execution' alone, so the S2S token carried exactly that one scope even though the external app (UiPath-Python-Integration-Tests in agenthubdri) grants all 73 Orchestrator scopes. POST /odata/Attachments was rejected at the OAuth scope layer, which is why no role or permission change had any effect. Request OR.Jobs (attachments are job artifacts), OR.Administration and OR.Folders alongside OR.Execution. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e success gate
The 'Analyze Files' TOOL span I asserted does not exist: the runtime emits
AGENT/CHAIN/LLM kinds only and internal tools execute inside the agent span.
The tool call itself is visible in the LLM span output
(tool_calls: [{name: Analyze_Files, args: {attachments: [...]}}]).
Assert what the traces actually contain and what proves the file was read:
attachments_upload, attachments_get_blob_uri (the blob was fetched), the agent
AGENT span, plus the existing probe CHAIN and LLM spans.
Also move assert_traces above the success assert. It previously sat after it,
so on any failing run the trace checks never executed and the trace dump was
never printed - which is precisely when they are needed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
It served its purpose: the token claims showed scope=['OR.Execution'], which led to the real fix (requesting the needed scopes in run.sh). No longer needed now that attachment creation succeeds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
One run failed with the agent raising AgentRuntimeError while the tool had in fact returned 'Dummy': asking for 'the first word of the text inside' invited reasoning about extraction quality rather than an answer. Ask what the document says instead. (Same question passed in two earlier runs, so this is phrasing flakiness, not a broken path.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The last run passed the pdf cell on 'Unable to reliably read the PDF contents: the analysis tool returned a non-informative placeholder result (Dummy), so the document's text cannot be determined.' — a substring check cannot tell a real answer from a hedge that quotes the word while asserting the opposite. Each question asks for one word, so require the expected word as an actual token AND an answer of at most four words. Verified against nine cases including that exact hedge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Asking what the document 'says' let the model judge whether 'Dummy' was real
content or a placeholder; it repeatedly reported the file unreadable while
quoting the exact word the tool had returned. Asking for a transcription
('three words long') removes that judgement. Still within the 4-word answer
limit, so the strict matcher applies unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
dummy.pdf's content is the literal string 'Dummy PDF file', which reads as a placeholder: across six runs the model answered correctly three times and three times refused, reporting the file unreadable while quoting the text the tool had returned. No PDF library is available here to verify the text of an alternative public file, so keep the known file and instruct the model to repeat the tool output verbatim without evaluating it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 4-word limit rejected a correct answer: obeying 'verbatim' the model
returned '<PARSED TEXT FOR PAGE: 1 / 1> Dummy PDF file', where the prefix is
the platform parser's own marker. Drop the length rule and reject refusal
phrasing instead ('cannot', 'placeholder', 'unable', 'no actual', ...), which
is what actually distinguishes an answer from a hedge that quotes the word.
Validated against every answer observed in the live runs: four genuine answers
accepted (including the parser-prefixed one) and three real refusals rejected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
| # Phrases a model uses when it quotes the expected word while denying it read | ||
| # the file. A plain substring check passed those, asserting the opposite of | ||
| # what the answer said. | ||
| _REFUSAL_MARKERS = ( |
There was a problem hiding this comment.
these seem quite flaky no? refusal can be stated in many ways, i dont think its reliable to test like this.
my idea to test would be to have a simple token in the pdf like a code lets say: 12345, now theres no way the model can just guess it or say the file is a placeholder, it either read it or it didnt.
and then you can just do an exact match and drop the marker list entirely (cause you just check for code).
for images its a bit different i think cause its quite unfair to make it read a code from the image, so maybe use something that is less probable to be guessed (in this case dog is the first thing that comes to mind so it might just say dog without looking at the image)
one example: have a purple duck image and ask what color it is
| "image": FileCase( | ||
| # A white Samoyed sitting on grass. | ||
| file=FileInfo( | ||
| url="https://raw.githubusercontent.com/pytorch/hub/master/images/dog.jpg", |
There was a problem hiding this comment.
im not really sure about this but does the file name in this case the "dog.jpg" create a bias? maybe the model cannot open the file and just says dog.jpg because of the filename? i believe the model has direct access to the file name inside:
return [
SystemMessage(content=enhanced_system_prompt),
HumanMessage(content=interpolate_legacy_message("""{{prompt}}
{{fileIn}}""", state.model_dump())),
]
this can be solved with the comment from below i guess
|
im not sure about this but i though we talked yesterday to only include one simple tool call like you had with get_weather_data (or whatever it was called)? we had that discussion with either keeping one simple tool call test like that or have a really difficult and complicated one. right now we only test the analyse file tool maybe thats enough? |



What
The model-onboarding testcase now runs four capability payloads for every
get_chat_modelcode path, all rolled into the singlesuccessboolean:simpleainvoke→ non-empty completiontoolsget_weather): the tool is executed and the final answer must use its resultfiles/<name>create_agent+ Analyze Files internal tool), test file uploaded as a real platform attachment. When the org forbids attachment creation (403 for the CI principal), degrades to the direct multimodal invoke with an explicit markeris_toolsAPI flavor as input
pathsentries (workflow input orinput.json) accept two forms, freely mixed:azure_responses,azure_chat_completions,vertex,bedrock_converse,bedrock_invoke,anthropic_sdkvendor_type:api_flavorpairs passed straight toget_chat_model(exact enum values), e.g.awsbedrock:converse,openai:responses,vertexai:generate-content,awsbedrock:AnthropicMessages.vendor:alone autodetects the flavor.Every payload — including the coded agents — runs with the model built for that path/flavor.
Design
src/agents/, one subfolder per coded agent (src/agents/README.md).agent.pyis the Studio Web generatedmain.py(downloaded viauip agent file get) with exactly one structural change: the hardcodedllm = get_chat_model('gpt-5.4', ...)becomesbuild_graph(llm, ...)/build_tools(...)so the testcase injects the model frommodel_spec— model + API flavor stay configurable;run.sh/input.json/ dispatch contract unchanged.llm_gateway_automated_testing): FileProcessingAgent (solution1823d2d6-…, coded copye7b8598c-…), IsToolsAgent (solution925fc5db-…, coded copy0959f23b-…).vertexregistry path pinsvendor_type+api_flavor=GENERATE_CONTENTexplicitly (was autodetect-only).python3— theuvcontainer does not shipjq(first live dispatch failed with exit 127).Live proof (dispatched via
model_onboarding.ymlon this branch)Run 30250006856 — registry path, and run 30277990899 — flavor-pair path (
openai:chat-completions), both success on alpha with gpt-5.2-2025-12-11:Supersedes #1007. No
src/changes — test + workflow only.🤖 Generated with Claude Code