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

Unit Test: 2-Step Sequence

sequence.test.ts ts
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

sequence-fallback.test.ts ts
// 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/sequence.json json
{
  "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.