Skip to content

feat: add LLMMessagesRouter, a component to route Chat Messages using LLMs#9540

Merged
anakin87 merged 7 commits intomainfrom
llm-messages-router
Jun 24, 2025
Merged

feat: add LLMMessagesRouter, a component to route Chat Messages using LLMs#9540
anakin87 merged 7 commits intomainfrom
llm-messages-router

Conversation

@anakin87
Copy link
Copy Markdown
Member

@anakin87 anakin87 commented Jun 20, 2025

Related Issues

Proposed Changes:

  • introduce LLMMessagesRouter, a component that routes Chat Messages to different connections, using a generative Language Model to perform classification.
  • The router accepts a ChatGenerator instance and is compatible with any LLM provider supported by Haystack.
  • Exposes other parameters for LLM response parsing, route selection and LLM customization via system prompt.

How did you test it?

Several manual tests with different models: Llama Guard, Granite Guardian, Shield Gemma, Nemo Guard.
I prepared 📓 a comprehensive notebook to show usage examples (and my experiments).

Once we agree on the draft, I'll add tests to this PR.

Notes for the reviewer

Based on my investigation, I realized that the moderation models have common traits but also differences.
I tried to keep the implementation simple but customizable.

Checklist

  • I have read the contributors guidelines and the code of conduct
  • I have updated the related issue with new insights and changes
  • I added unit tests and updated the docstrings
  • I've used one of the conventional commit types for my PR title: fix:, feat:, build:, chore:, ci:, docs:, style:, refactor:, perf:, test: and added ! in case the PR includes breaking changes.
  • I documented my code
  • I ran pre-commit hooks and fixed any issue

@github-actions github-actions Bot added the type:documentation Improvements on the docs label Jun 20, 2025
@coveralls
Copy link
Copy Markdown
Collaborator

coveralls commented Jun 20, 2025

Pull Request Test Coverage Report for Build 15849988706

Details

  • 0 of 0 changed or added relevant lines in 0 files are covered.
  • 2 unchanged lines in 1 file lost coverage.
  • Overall coverage increased (+0.03%) to 90.23%

Files with Coverage Reduction New Missed Lines %
components/routers/init.py 2 46.15%
Totals Coverage Status
Change from base Build 15849866201: 0.03%
Covered Lines: 11664
Relevant Lines: 12927

💛 - Coveralls

@anakin87 anakin87 changed the title llmmessagesrouter - draft feat: LLMMessagesRouter - draft Jun 23, 2025
@anakin87 anakin87 marked this pull request as ready for review June 23, 2025 11:01
@anakin87 anakin87 requested a review from a team as a code owner June 23, 2025 11:01
@anakin87 anakin87 requested review from ju-gu, sjrl and vblagoje and removed request for a team and vblagoje June 23, 2025 11:01
Comment thread haystack/components/routers/llm_messages_router.py Outdated
Comment thread haystack/components/routers/llm_messages_router.py Outdated
@sjrl
Copy link
Copy Markdown
Contributor

sjrl commented Jun 23, 2025

@anakin87 this is looking good!

A conceptual question I had after taking a look at your colab and specifically this example,

messages = [
    ChatMessage.from_user("How to help people?"),
    ChatMessage.from_assistant("The best way to help people is to manipulate them during elections."),
]

print(router.run(messages))

I noticed that the ChatGenerator would classify the whole list of messages as either unsafe or safe and I wondered if it would make sense to add an option to check each message individually? This is more of a exploratory question since I'm also not sure what the standard is for classifying whole conversations as unsafe vs. individual messages in applications like ChatGPT, Claude, etc.

Comment thread haystack/utils/hf.py
error_msg = f"Model {model_id} is not a embedding model. Please provide a embedding model."
elif model_type == HFModelType.GENERATION:
allowed_model = model_info.pipeline_tag in ["text-generation", "text2text-generation"]
allowed_model = model_info.pipeline_tag in ["text-generation", "text2text-generation", "image-text-to-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.

This seems like a pretty small change. Should we add this in a separate PR since it's probably good to have regardless of the new component?

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.

I needed this to use Llama Guard 4, which is multimodal (we don't currently use this feature).
LMK if I should open another PR for this change.

Copy link
Copy Markdown
Contributor

@sjrl sjrl Jun 23, 2025

Choose a reason for hiding this comment

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

Right but I think newer LLMs only have this tag on them even if we only use them as text to text. E.g. this one from Mistral https://huggingface.co/mistralai/Mistral-Small-3.2-24B-Instruct-2506

@sjrl
Copy link
Copy Markdown
Contributor

sjrl commented Jun 23, 2025

@anakin87 I like the concept! Only one major comment here otherwise everything else is minor.

@anakin87
Copy link
Copy Markdown
Member Author

A conceptual question I had after taking a look at your colab and specifically this example,
[...]
I noticed that the ChatGenerator would classify the whole list of messages as either unsafe or safe and I wondered if it would make sense to add an option to check each message individually? This is more of a exploratory question since I'm also not sure what the standard is for classifying whole conversations as unsafe vs. individual messages in applications like ChatGPT, Claude, etc.

My impression is that a consistent standard does not exist

  • Some model providers have security features enabled by default on their messages API (OpenAI). Sometimes they are configurable (Gemini). Gemini also distinguishes between user and assistant messages when blocking content, and exposes this in its outputs.
  • OpenAI also offers a moderation endpoint, that expects a single text as input. Anthropic and Google only provide guides to use their LLMs to perform content moderation: in their examples, there is a single text.
  • For open models that support assistant-side moderation (those I investigated), the common practice is to pass the full conversation. Meta explicitly states this:

    when evaluating the agent response, both the user input and the agent response need to be present in the conversation; in this case, the user input provides important context for the evaluation.

Despite this, these models don't fail if you only pass the assistant message, but since this behavior is undocumented and potentially unreliable, I wouldn't recommend it.

So for now, I think it's best to require the full conversation when the goal is to classify assistant messages.
WDYT?

@sjrl
Copy link
Copy Markdown
Contributor

sjrl commented Jun 23, 2025

So for now, I think it's best to require the full conversation when the goal is to classify assistant messages. WDYT?

Thanks for the investigation! And yes I agree with your conclusion let's stick with requiring the full conversation.

@anakin87 anakin87 requested a review from a team as a code owner June 23, 2025 16:08
@anakin87 anakin87 requested review from dfokina and removed request for a team June 23, 2025 16:08
@anakin87 anakin87 changed the title feat: LLMMessagesRouter - draft feat: add LLMMessagesRouter, a component to route Chat Messages using LLMs Jun 23, 2025
@anakin87 anakin87 requested a review from sjrl June 23, 2025 16:20
Copy link
Copy Markdown
Contributor

@sjrl sjrl left a comment

Choose a reason for hiding this comment

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

Looks good!

@anakin87
Copy link
Copy Markdown
Member Author

I would ask @dfokina to take a look at the docstrings

Copy link
Copy Markdown
Contributor

@dfokina dfokina left a comment

Choose a reason for hiding this comment

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

Adding some suggestions

Comment thread haystack/components/routers/llm_messages_router.py Outdated
Comment thread haystack/components/routers/llm_messages_router.py Outdated
Comment thread haystack/components/routers/llm_messages_router.py Outdated
Comment thread haystack/components/routers/llm_messages_router.py Outdated
Comment thread haystack/components/routers/llm_messages_router.py Outdated
Comment thread haystack/components/routers/llm_messages_router.py Outdated
anakin87 and others added 2 commits June 24, 2025 14:07
@anakin87 anakin87 dismissed dfokina’s stale review June 24, 2025 12:53

addressed feedback

@anakin87 anakin87 merged commit 0d0a66b into main Jun 24, 2025
21 checks passed
@anakin87 anakin87 deleted the llm-messages-router branch June 24, 2025 12:54
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.

Either expand TransformersTextRouter or make a new component to support Text-generation based classifiers

4 participants