Skip to content

feat: add MockChatGenerator#11708

Merged
julian-risch merged 9 commits into
v3from
feat/mock-chat-generator
Jun 22, 2026
Merged

feat: add MockChatGenerator#11708
julian-risch merged 9 commits into
v3from
feat/mock-chat-generator

Conversation

@julian-risch

@julian-risch julian-risch commented Jun 22, 2026

Copy link
Copy Markdown
Member

Related Issues

  • Part of deepset-ai/haystack-private#376

Proposed Changes:

Adds MockChatGenerator, a Chat Generator that returns predefined responses without calling any API. It is a deterministic, zero-cost drop-in replacement for real Chat Generators (e.g. OpenAIChatGenerator) in unit tests, smoke tests of customer pipelines, and quick prototypes.

Response selection modes:

  • Fixed – pass a single str or ChatMessage; the same reply is returned every call.
  • Cycling – pass a list of str/ChatMessage; each call returns the next item, wrapping around. Useful to drive Agent-like loops (e.g. first call returns a tool call, second returns the final answer).
  • Dynamic – pass response_fn=callable(messages) -> str | ChatMessage.
  • Echo (default) – with no configuration, echoes back the last user message, so it works out of the box.

Passing ChatMessage objects lets you return tool calls or reasoning content for exercising tool-calling pipelines without a real model.

It implements the full Chat Generator interface so it slots in anywhere a real generator goes:

  • run and run_async
  • streaming via streaming_callback (chunks reconstructed from the predefined reply, at init or run time)
  • to_dict / from_dict (including serialization of response_fn and streaming_callback named callables)
  • warm_up (no-op)
  • realistic meta (model, finish_reason, approximate usage)

Files:

  • haystack/components/generators/chat/mock.py – the component
  • test/components/generators/chat/test_mock.py – 26 unit tests
  • registered in haystack/components/generators/chat/__init__.py (lazy import) and pydoc/generators_api.yml
  • release note

How did you test it?

Added unit tests covering all response modes, echo, cycling, tool-call replies, meta merging precedence, non-mutation of stored responses, sync/async runs, sync/async streaming, serialization roundtrips (including response_fn and echo mode), and a Pipeline integration + serialization roundtrip.

Notes for the reviewer

  • usage token counts are a deliberate approximation (whitespace word count, not real tokenization), documented as such; they exist to give downstream code realistic-looking metadata.
  • A follow-up could add a docs page and analogous mocks for other component types (embedders, etc.). So this is to be added to https://github.com/deepset-ai/haystack-private/issues/381

Checklist

  • I have read the contributors guidelines and the code of conduct.
  • I have updated the related issue with new insights and changes.
  • I have added unit tests and updated the docstrings.
  • I've used one of the conventional commit types for my PR title.
  • I have documented my code.
  • I have added a release note file.
  • I have run pre-commit hooks and fixed any issue.

🤖 Generated with Claude Code

Add `MockChatGenerator`, a Chat Generator that returns predefined
responses without calling any API. It is a deterministic, zero-cost
drop-in replacement for real Chat Generators in tests, smoke tests, and
quick prototypes, inspired by model-layer fakes in other frameworks
(LangChain `FakeListChatModel`, LlamaIndex `MockLLM`, PydanticAI
`FunctionModel`).

It supports:
- a fixed response (string or ChatMessage),
- a list of responses cycled across calls (to drive Agent-like loops),
- a `response_fn` callable for input-dependent replies,
- an echo mode (the default) that returns the last user message.

It implements the full Chat Generator interface: `run`, `run_async`,
streaming callbacks, and serialization.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
haystack-docs Ignored Ignored Preview Jun 22, 2026 1:37pm

Request Review

@github-actions github-actions Bot added topic:tests type:documentation Improvements on the docs labels Jun 22, 2026
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  haystack/components/generators/chat
  mock.py
Project Total  

This report was generated by python-coverage-comment-action

Reduce the number of test functions via parametrization (init validation,
echo modes, response_fn return types, serialization roundtrips) without
losing coverage, and cover the previously-missed defensive branches.
mock.py is now at 100% statement coverage.

Also fix the docstring usage example to import ToolCall.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@julian-risch julian-risch changed the title feat: add MockChatGenerator for testing and prototyping feat: add MockChatGenerator Jun 22, 2026
@julian-risch julian-risch marked this pull request as ready for review June 22, 2026 11:34
@julian-risch julian-risch requested a review from a team as a code owner June 22, 2026 11:34
@julian-risch julian-risch requested review from anakin87 and sjrl and removed request for a team and anakin87 June 22, 2026 11:34
Comment thread haystack/components/generators/chat/mock.py Outdated
Comment thread haystack/components/generators/chat/mock.py Outdated
Comment thread haystack/components/generators/chat/mock.py Outdated
Address review feedback: avoid RST-style double backticks in code comments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread haystack/components/generators/chat/mock.py
Comment thread haystack/components/generators/chat/mock.py Outdated
Comment thread haystack/components/generators/chat/mock.py Outdated

return replace(base, _meta=self._build_meta(messages, base))

def _make_chunks(self, reply: ChatMessage) -> list[StreamingChunk]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small thing for a potential future PR would also to support streaming reasoning content.

Comment thread haystack/components/generators/chat/mock.py
Co-authored-by: Sebastian Husch Lee <10526848+sjrl@users.noreply.github.com>
Comment thread haystack/components/generators/chat/mock.py
Comment thread haystack/components/generators/chat/mock.py
Address review feedback: echo the last message that has text content
instead of preferring the last user message and then falling back. This
is behaviorally identical for the typical case (the last message is the
user turn) and removes the now-unused ChatRole import.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@mpangrazzi mpangrazzi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks already good, and I would definitely use it when it comes to e.g. load/stress testing without actually calling LLMs. I left a few comments!

Comment thread haystack/components/generators/chat/mock.py Outdated
if text is None:
return None
base = ChatMessage.from_assistant(text)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering about adding a check here that ensure that the last message is from assistant when building a reply (since here ChatMessage instances are appended unchanged, and one could add only user messages)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sebastian brought up not the same remark but something similar in our conversation. I am addressing this now in 015a703
ChatGenerator replies are always assistant messages now. If they aren't, we raise an error.

chunks.append(
StreamingChunk(content="", component_info=component_info, index=0, meta={"model": self.model})
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about adding reasoning streaming (if present)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great minds think alike: #11708 (comment)
We'll leave that for a later PR.

julian-risch and others added 2 commits June 22, 2026 15:12
…enerator

Reorder run()/run_async() to (messages, streaming_callback, generation_kwargs,
*, tools, tools_strict), mirroring OpenAIChatGenerator so the mock is a true
positional drop-in. Previously the order followed FallbackChatGenerator, which
puts streaming_callback last and tools positionally.

Add a regression test pinning the parameter order and verifying a callback
passed as the 2nd positional arg is treated as streaming_callback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A Chat Generator's replies are always assistant messages, so reject a
non-assistant ChatMessage supplied via `responses` (at construction) or
returned from `response_fn` (at run time) with a clear error, instead of
emitting a user/system/tool message as a reply.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sjrl

sjrl commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Should we add this PR to this list for docs updates? https://github.com/deepset-ai/haystack-private/issues/381

@julian-risch

Copy link
Copy Markdown
Member Author

Should we add this PR to this list for docs updates? deepset-ai/haystack-private#381

Yes, I will do so. Have it under "Notes for the reviewer" in the admittedly long PR description. 😉

Comment thread haystack/components/generators/chat/mock.py
Comment thread haystack/components/generators/chat/mock.py Outdated
julian-risch and others added 2 commits June 22, 2026 15:36
…nses

Note in the class/__init__ docstrings (and the ValueError list) that any
ChatMessage passed via `responses` or returned from `response_fn` must
have the assistant role, and reword `_coerce_to_message`'s docstring to
reflect that it validates rather than coerces the role.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Sebastian Husch Lee <10526848+sjrl@users.noreply.github.com>
@julian-risch julian-risch requested a review from sjrl June 22, 2026 13:37

@sjrl sjrl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Just a few last minor comments.

@julian-risch julian-risch enabled auto-merge (squash) June 22, 2026 13:38
@julian-risch julian-risch merged commit 97c2672 into v3 Jun 22, 2026
24 of 25 checks passed
@julian-risch julian-risch deleted the feat/mock-chat-generator branch June 22, 2026 13:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

topic:tests type:documentation Improvements on the docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants