Last reviewed: July 30, 2026
Direct answer
Test a coding agent’s tool-call contract before switching models by holding the prompt, tool definitions, fixture workspace, stubbed tool results, and pass criteria constant. Change only the model selection. For every case, verify the selected tool, the argument shape and meaning, the call-to-result correlation, the next-turn behavior, and the absence of unapproved side effects.
A plausible final answer is not enough. CometAPI’s function-calling explanation makes an important distinction: a model proposes a function and its arguments, while the application remains responsible for executing it. That boundary is where the contract harness belongs. Validate the proposal before any code runs, execute only an isolated stub, and then verify how the model handles the result.
The OpenAI function-calling documentation describes tool definitions using JSON Schema and tool use as a multi-step request, execution, result, and continuation loop. CometAPI coding agent tool call contract tests should cover that entire loop. They should not stop after checking that the first response contains something resembling JSON.
Who this is for
This workflow is for developers and platform engineers who route coding agents across model candidates and let those agents read files, inspect repository state, run bounded checks, or propose changes. It is also useful for release owners who need evidence that a model change preserves behavior at the tool boundary.
Start with an isolated request harness. If that layer is not stable yet, build the preflight request fixtures before adding model comparisons. This article does not recommend giving a candidate model production write access merely to see what happens.
When you need a routed model service for the comparison, Start with CometAPI , but keep tool execution inside the fixture boundary until the candidate passes.
Key takeaways
- Compare semantic invariants, not exact prose. Tool name, required arguments, call count, result correlation, and allowed side effects are stronger assertions than final wording.
- Keep the fixture fixed. Changing a tool description or schema while changing the model makes the result ambiguous.
- Normalize provider-specific envelopes into one internal event shape, but retain the original response for diagnosis.
- Test a successful result, a controlled tool error, and any parallel or chained behavior your coding agent actually uses.
- Run real-world effects through deterministic stubs. A contract test should not edit a live repository, trigger CI, or send external requests.
- Treat contract results as one input to the broader model candidate approval workflow , not as automatic approval.
Sources checked
The following public documentation was checked for this guide:
- CometAPI’s function-calling explanation supports the application-executes-the-tool boundary, JSON Schema definitions, strict argument handling, parallel calls, validation, and confirmation before consequential actions.
- OpenAI’s function-calling guide supports the tool, tool call, tool output, and continuation lifecycle used to define the canonical test loop.
- Anthropic’s tool-call handling guide
documents
tool_useblocks, unique IDs,tool_resultcorrelation, ordering requirements, and explicit tool-error signaling. - Google’s Gemini function-calling guide documents function declarations, application-side execution, result replay, and both parallel and compositional function calls.
Together, these sources show a shared conceptual loop with different transport details. The contract suite should preserve the shared behavioral requirements while letting each adapter satisfy its provider-specific message format.
Contract details to verify
Freeze a canonical fixture
Begin with one small, read-only task whose correct tool and arguments are unambiguous. Keep the fixture independent of any provider envelope:
{
"case_id": "read-before-edit",
"prompt": "Read src/widget.ts and report the exported names.",
"tool": {
"type": "function",
"name": "read_file",
"description": "Read one UTF-8 file inside the isolated fixture workspace.",
"strict": true,
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string"
}
},
"required": [
"path"
],
"additionalProperties": false
}
},
"expected": {
"tool_name": "read_file",
"arguments": {
"path": "src/widget.ts"
},
"call_count": 1,
"side_effects": "none"
}
}
The provider adapter may translate field names or message placement, but it must not weaken the fixture. In particular, do not silently drop required fields, permit undeclared arguments, or alter the tool description for one candidate. Strict schema enforcement checks structure; the harness must still check business meaning, such as whether the requested path matches the prompt and stays inside the fixture workspace.
Preserve correlation and sequence
Normalize each provider response into an internal event containing the tool name, parsed arguments, and a correlation reference. Keep the untouched provider response alongside that event. This gives the assertion layer a stable input without hiding protocol mistakes.
Anthropic’s documented flow is a useful example of why this matters. A tool_use block has an ID, and the corresponding tool_result must reference it with tool_use_id. Result blocks also have ordering requirements. A model can select the correct tool and still fail the end-to-end contract if an adapter loses that ID or inserts the result in the wrong place.
The contract should therefore verify all of the following:
- The requested tool was declared and allowed for the case.
- Arguments parse successfully and satisfy the canonical schema.
- Argument values satisfy local policy, including workspace boundaries and allowed operations.
- Every tool result maps to exactly one outstanding call.
- No call is left unresolved when the next model turn begins.
- The final response reflects the stub result instead of inventing a different outcome.
Happy-path operator workflow
- Create a temporary fixture workspace containing
src/widget.ts. Give the tool stub read access only to that directory. - Select the approved baseline model and a candidate model through the same CometAPI integration. Keep every other request field and tool definition unchanged.
- Send the
read-before-editprompt to the baseline. Capture the native response, normalize the tool event, and validate it before execution. - If the call is valid, run the deterministic
read_filestub. Return a fixed result such as an export list containingWidgetandrenderWidget. - Replay that result through the provider adapter with the original correlation reference.
- Assert that the model completes the task, uses the supplied result, and makes no second tool call unless the fixture explicitly allows one.
- Repeat the same steps for the candidate model.
- Compare contract outcomes. Differences in prose are acceptable; a different tool, invalid path, lost result, or additional unapproved call is a failure.
This workflow separates model behavior from tool execution. It also produces reviewable evidence without letting the candidate touch a real worktree.
Error-path operator workflow
Use a second fixture that requests src/missing.ts, which the isolated stub does not contain:
- Send the missing-file prompt with the same
read_filedefinition. - Validate the model’s call before running the stub. If the model adds an undeclared argument or chooses a write tool, reject the call and do not execute anything.
- For a valid call, have the stub return a controlled
fixture_not_founderror. - Send the error back using the adapter’s required result format and the original correlation reference. For an Anthropic-shaped exchange, the documented mechanism includes
is_erroron the result. - Assert that the model does not claim it read the file. Depending on the fixture policy, it may request a corrected path, make one bounded retry, or report that it cannot continue.
- Fail the case if the model fabricates file contents, converts the error into success, loses correlation, exceeds the retry allowance, or attempts a broader search without permission.
Keep invalid-call rejection separate from tool-execution errors. The first means the proposal violated the contract and never ran. The second means a valid proposal ran against a controlled stub and received an error. Combining them makes diagnosis harder.
Test parallel and chained calls deliberately
The Gemini documentation distinguishes parallel calls for independent functions from compositional calls that depend on earlier results. The CometAPI source also describes parallel function calling. Add parallel cases only when the agent workflow uses them.
For two independent file reads, compare the set of expected calls rather than requiring one response order. For a dependent workflow, such as reading a manifest and then reading a path named by that manifest, require the second call to wait for the first result. Never use a production write operation as a parallel test. Apply the same confirmation and permission boundaries described in safe permission and secret boundaries for coding agents .
Record sanitized evidence
Logs should make a failure reproducible without retaining credentials, full prompts, repository contents, or raw tool results. A compact record can look like this:
{
"timestamp": "2026-07-30T00:00:00Z",
"run_id": "run-042",
"case_id": "read-before-edit",
"model_alias": "candidate-a",
"route_label": "cometapi",
"schema_revision": "fixture-v1",
"tool_name": "read_file",
"call_reference": "[REDACTED]",
"argument_keys": [
"path"
],
"schema_valid": true,
"tool_status": "fixture_success",
"result_correlated": true,
"side_effects": "none",
"final_state": "pass",
"prompt_excerpt": "[REDACTED]",
"tool_result_excerpt": "[REDACTED]"
}
Log argument keys by default, not arbitrary values. Record a value only when the field is explicitly allowlisted and the value cannot expose repository data. Keep request headers, credentials, full prompts, and tool output out of the event. Store the native response in a separately controlled evidence location only when your review policy permits it.
A candidate is contract-compatible only when every mandatory fixture passes. Do not average away a tool-selection or side-effect failure because other cases scored well.
Failure modes
The response is valid JSON but selects the wrong tool. Schema validation cannot prove that search_repository was appropriate when the fixture required read_file. Assert the allowed tool name for deterministic cases.
Arguments satisfy the schema but violate local policy. A string can be structurally valid while pointing outside the fixture workspace. Validate paths, enums, operation modes, and other business constraints before execution.
The adapter loses call-result correlation. A correct tool result attached to the wrong call can derail a multi-call turn. Assert unique outstanding calls and exact result matching.
Provider-specific ordering is flattened too early. If normalization discards message order or block type, the harness may miss an invalid native exchange. Preserve the original response and validate adapter rules before producing the canonical event.
Parallel calls introduce an unsafe effect. Independent reads can be suitable for a parallel fixture. Writes, deployments, notifications, and other consequential actions should remain disabled and require explicit approval outside the contract suite.
The model launders an error into a success statement. A candidate may receive fixture_not_found and still describe invented file contents. Assert both the tool status and the claims made in the final response.
Logs become a second data leak. Capturing full prompts, arguments, tool results, or headers can expose the very information the test is meant to protect. Use allowlisted fields and redaction from the first run.
The benchmark moves with the candidate. Editing prompts, descriptions, schemas, or retry budgets during comparison prevents a clean attribution. Version the fixture and rerun both baseline and candidate after any fixture change.
FAQ
Should two models produce identical final text?
No. Contract tests should allow wording differences while enforcing the same operational outcome. The important checks are tool selection, valid arguments, result correlation, error handling, call limits, and side-effect policy.
Does strict schema mode remove the need for validation?
No. The CometAPI source recommends strict function definitions and also says generated calls should be verified. Schema enforcement addresses shape. Your application still owns checks such as allowed paths, permitted operations, confirmation requirements, and whether the call matches the user’s request.
Should the harness execute real coding tools?
Use deterministic stubs in an isolated fixture workspace. A read stub can return known content, a test stub can return a fixed pass or failure, and a write stub can record that confirmation would be required without changing a file. Production effects do not improve a protocol contract test.
How should provider differences be handled?
Put transport differences in small adapters. Each adapter should accept the canonical fixture, construct the provider-compatible request, preserve the native response, emit a normalized event, and return the stub result in the required native shape. Do not hide a provider error merely to make the normalized output look consistent.
What cases belong in the first suite?
Start with one successful read, one controlled read error, and one pair of independent read calls if parallel tools are part of the workflow. Add a chained case when one tool result supplies input to the next. Represent write behavior with a non-executing confirmation stub.
When is a model switch ready for review?
Move it forward only after the candidate passes every mandatory tool contract, its sanitized evidence is attached, and a reviewer has checked any intentional difference. Contract compatibility is necessary evidence, not permission to bypass human review or deployment controls.
Reader next step
Create three fixtures now: a successful file read, a missing-file error, and two independent reads. Run them against the approved baseline and one candidate with the same schema revision. Review any difference at the first failing boundary: declaration, selection, arguments, execution, correlation, or final response.
Once those cases are stable, add the exact read-only tools your coding agent uses and a non-executing confirmation case for proposed writes. Keep the resulting evidence with the model-change review so the next switch can reuse the same contract.