Sequential / Stateful Responses
Use sequenceIndex in fixture match criteria to return different responses for
the same query on each successive call. This enables testing multi-step agent
conversations and retry logic.
How It Works
- The router tracks how many times each unique match pattern has been hit
-
sequenceIndex: 0matches the first request,sequenceIndex: 1the second, etc. - Different match patterns have independent counters
- If a sequenceIndex fixture does not match the current count, routing falls through to the next fixture
-
Fixtures without
sequenceIndexmatch any occurrence (backward compatible) - Counters reset on
mock.reset()
Unit Test: 2-Step Sequence
const mock = new LLMock();
await mock.start();
mock.on({ userMessage: "plan", sequenceIndex: 0 }, { content: "Step 1: planning..." });
mock.on({ userMessage: "plan", sequenceIndex: 1 }, { content: "Step 2: done!" });
// First request → first response
const res1 = await fetch(`${mock.url}/v1/chat/completions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "gpt-4",
messages: [{ role: "user", content: "plan" }],
stream: false,
}),
});
const body1 = await res1.json();
expect(body1.choices[0].message.content).toBe("Step 1: planning...");
// Second request → second response
const res2 = await fetch(`${mock.url}/v1/chat/completions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "gpt-4",
messages: [{ role: "user", content: "plan" }],
stream: false,
}),
});
const body2 = await res2.json();
expect(body2.choices[0].message.content).toBe("Step 2: done!");
Fallback After Sequence Exhaustion
// First call matches sequenceIndex 0, subsequent calls fall through to fallback
mock.on({ userMessage: "once", sequenceIndex: 0 }, { content: "only-first-time" });
mock.on({ userMessage: "once" }, { content: "fallback" });
// Request 1 → "only-first-time" (sequenceIndex 0 matches)
// Request 2 → "fallback" (sequenceIndex 0 won't match, falls through)
JSON Fixture
{
"fixtures": [
{
"match": { "userMessage": "plan", "sequenceIndex": 0 },
"response": { "content": "Step 1: planning..." }
},
{
"match": { "userMessage": "plan", "sequenceIndex": 1 },
"response": { "content": "Step 2: done!" }
}
]
}
Sequence counters are per-fixture-match, not global. If you have fixtures matching "alpha" and "beta", their counters are tracked independently.
Need stateless matching? sequenceIndex uses a server-side
counter that resets on mock.reset() and can drift when multiple clients
share an instance. If your use case is “different response based on conversation
depth” (not “different response for the Nth identical request”),
prefer the stateless turnIndex or hasToolResult fields
instead. See Multi-Turn Conversations.