How to Test AI Agents Without Calling More LLMs A developer argues that AI-agent testing should separate orchestration correctness from language quality, using deterministic checks for tool order, retry limits, and state transitions instead of relying on expensive LLM judges. The approach uses dependency injection and scripted fakes to test execution contracts on every commit, reserving semantic evaluations for a smaller, calibrated workflow. AI-agent testing often starts with an expensive loop: call the agent, send its answer to another model, ask for a quality score, and hope the score is stable enough for CI. LLM judges can be useful for genuinely semantic questions. They are a poor default for verifying tool order, retry limits, validation, budgets, state transitions, or error handling. Those behaviors have explicit contracts and can usually be tested with ordinary deterministic techniques. The most effective agent test suite separates orchestration correctness from language quality . Test the first on every commit with fakes, traces, and rules. Evaluate the second with a smaller, calibrated model-and-human workflow. An agent may produce variable language while still following a predictable execution contract. Useful deterministic checks include: None of these questions requires a second model. Most do not require a first model either. | Layer | Model access | Purpose | |---|---|---| | Pure unit tests | None | Routing, validation, budgeting, state reducers | | Orchestration tests | Scripted fake | Tool order, retries, fallbacks, termination | | Tool contract tests | None or sandbox | Schemas, timeouts, error mapping | | Trace replay tests | None | Regression checks against recorded execution metadata | | Model integration tests | Real model | Provider compatibility and a small set of end-to-end paths | | Semantic evaluations | Real model and/or human | Helpfulness, groundedness, tone, nuanced correctness | The lower layers should contain most of the suite. They are fast, reproducible, and actionable. The upper layers are valuable, but they should be deliberately smaller. Dependency injection makes the orchestration testable without network calls. type ModelReply = | { type: 'tool call'; tool: string; args: unknown } | { type: 'final'; text: string; usage: { input: number; output: number } }; interface ModelClient { generate input: { messages: Array<{ role: string; content: string } ; tools: string ; } : Promise