Skip to content

date: 2026-06-06 tags: [testing, laravel-ai, agents, tools, fakes] status: active graduated_to:

laravel/ai Agent::fake() runs the REAL tool when a faked response is a ToolCall

Symptom — Unclear whether a faked agent (ChatAgent::fake([...])) could drive a tool to actually execute — e.g. to test the "tone wasn't saved to a helper skill" bug deterministically — or whether faking short-circuits the tool loop entirely.

Root causeFakeTextGateway::generateText inspects each scripted response: if it's a Laravel\Ai\Responses\Data\ToolCall, handleFakeToolCalls finds the agent's matching tool and calls the real Tool::handle() (side effects and all), then pops the next array element as the final assistant reply. Tool name is matched via ToolNameResolver = class_basename when the tool defines no name() (so RememberToneExample, PascalCase). One tool call per faked turn — it does not loop MaxSteps.

FixChatAgent::fake([new ToolCall('id', 'RememberToneExample', ['helper_skill_id' => $id, 'example' => '…']), 'Saved it.']), drive the real /chat route, then assert the side effect (assertDatabaseHas('helper_skill_examples', …)). Proven in 592d731 (tests/Feature/ChatToolCallFakeSpikeTest.php).

Guard — Pattern documented for epic #250 / Sprint 4 task #277 (all task-chat tools tested via the ToolCall fake).