Add LLMRecommender for LLM-based experimental design - #855
Conversation
tobiasploetz
left a comment
There was a problem hiding this comment.
Converting discussion comments into review threads for easier inline discussion.
|
@Scienfitz @AdrianSosic @kalama-ai I will do the first round of reviews/feedback, feel free to ignore until we move it out of draft :) |
AVHopp
left a comment
There was a problem hiding this comment.
First round of comments. I hope that impementing those will solve the seralization issues as well as the most important design choice such that we can then see the CI in action. Note that I did not yet investigate the tests or the inner workings of the class in detail, but I think that doing small iterations like this is better than me providing too many comments :)
e419a9d to
f62c14b
Compare
8184fa5 to
84ed98c
Compare
There was a problem hiding this comment.
Pull request overview
This PR introduces an LLMRecommender to BayBE’s pure recommender stack, enabling LLM-driven experiment suggestions (via LiteLLM) for warm-starting campaigns before surrogate-based Bayesian optimization becomes effective.
Changes:
- Add
LLMRecommenderimplementation with prompt construction and robust JSON response parsing (including one-shot recovery). - Add optional dependency group
llm(Jinja2 + LiteLLM) plus optional-import plumbing and mypy config. - Add tests, documentation, and an end-to-end example (with pre-generated comparison plots) and adjust iteration tests to avoid auto-instantiating non-default-constructible recommenders.
Reviewed changes
Copilot reviewed 19 out of 22 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
baybe/recommenders/pure/llm/llm.py |
Implements LLMRecommender with LiteLLM call, recovery flow, and prompt/parse integration. |
baybe/recommenders/pure/llm/_prompts.py |
Builds Jinja2 prompts from search space + descriptions + optional measurements/objective. |
baybe/recommenders/pure/llm/_parsing.py |
Extracts/validates JSON suggestions into a DataFrame. |
baybe/recommenders/pure/llm/__init__.py |
Exposes LLMRecommender from the llm subpackage. |
baybe/recommenders/pure/__init__.py |
Re-exports LLMRecommender in pure recommenders. |
baybe/recommenders/__init__.py |
Re-exports LLMRecommender at the recommender package level. |
baybe/_optional/llm.py |
Adds optional imports for Jinja2 Template + LiteLLM completion. |
baybe/_optional/info.py |
Adds LLM_INSTALLED check based on Jinja2 + LiteLLM presence. |
baybe/exceptions.py |
Adds LLMResponseError and LLMResponseWarning. |
pyproject.toml |
Adds baybe[llm] extra with jinja2 + litellm. |
mypy.ini |
Adds mypy ignore section for litellm.*. |
tests/test_llm_recommender.py |
Adds mocked unit tests for prompting, parsing branches, recovery, and warnings. |
tests/serialization/test_llm_recommender_serialization.py |
Adds serialization roundtrip test via meta recommender wrapper. |
tests/conftest.py |
Adds helper to exclude non-default-constructible recommenders from subclass enumeration tests. |
tests/test_iterations.py |
Switches subclass enumeration to the new default-constructible helper. |
docs/components/recommenders.md |
Documents the new LLM recommender and the baybe[llm] extra. |
examples/LLM/llm_recommender.py |
Adds runnable warm-start example with mock fallback for CI/offline runs. |
examples/LLM/LLM_Header.md |
Adds header page for the LLM examples section. |
examples/LLM/llm_recommender_light.svg |
Adds pre-generated plot asset (light theme). |
examples/LLM/llm_recommender_dark.svg |
Adds pre-generated plot asset (dark theme). |
CHANGELOG.md |
Adds changelog entry for LLMRecommender. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Scienfitz
left a comment
There was a problem hiding this comment.
currently does not use all available metadata
| serialized through an enclosing (meta) recommender, which is what we exercise here. | ||
| Serialization does not touch LiteLLM, so no optional dependencies are required. | ||
| """ | ||
| recommender = TwoPhaseMetaRecommender( |
There was a problem hiding this comment.
any particular reason why this si going via TwoPhaseMetaRecommender and not hjust smply via LLMRecommender?
normally Id expect a hypothesis strat and a serialziaiton test absed on that, why was it don different here?
There was a problem hiding this comment.
Yes - none of our recommenders can be serialized directly as none of them uses the SerialMixin. Not sure if intentional, and until this has been made clear, I'll just wrap it inside the meta recommender, but already added a hypothesis strategy for the LLMRecommender.
There was a problem hiding this comment.
hmmm im clearly missing something here but if the pure reocmmenders cant be serialzied how can the meta-recommender usign them be serialized?
the fact that pure recommenders cannot be serialized is proably
- a bug or forgotten thing
- on purpose due to something only @AdrianSosic remembers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements a recommender that uses LiteLLM to query language models for experimental design suggestions. Leverages the metadata system to auto-extract parameter descriptions from the search space. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Inherit from NonPredictiveRecommender instead of RecommenderProtocol - Add field validators for required string fields - Improve error handling in _attempt_recovery (split broad except) - Raise IncompatibilityError for unsupported parameter types - Add batch_size count validation with warning - Improve error messages with invalid/allowed values detail - Add Raises section to recommend() docstring - Fix dict type annotations to dict[str, Any] - Add LLMRecommender to pure/__init__.py for consistent hierarchy - Refactor tests: parametrize error cases, use batch_size=3, add NumericalDiscreteParameter, test related_data/recovery_model/ format_instructions, test feasibility edge cases Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add pending_experiments rejection and objective warning in recommend() to match NonPredictiveRecommender contract - Guard test_naive_hybrid_serialization against classes with required args - Split optional imports to give accurate error for jinja2 vs litellm - Add overflow_experiments ge(0) validator - Add litellm_args defensive copy and reserved-key validation - Add empty suggestions guard in _parse_llm_response - Add math.isfinite check for continuous parameter values - Add discrete parameter canonical type coercion Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
NonPredictiveRecommender rejects pending experiments and ignores measurements, but the LLMRecommender feeds both into the prompt. Inherit from PureRecommender instead and fully override recommend accordingly.
The field only existed to over-generate suggestions for feasibility filtering, which no longer exists. Request exactly `batch_size` suggestions.
The recommender has required constructor arguments and cannot be default-constructed, so route the PureRecommender enumerations through a dedicated exclusion helper
We de facto require JSON format, so the code should not give the impression that other formats are supported.
If users would pass credential keys to the recommender, those would be stored in plain text in the object. This is prevented by checking the keys in the constructor and raising an error if any of the reserved keys are used.
560855c to
80ff257
Compare
Necessary since there is a `# type: ignore` inside of the `__init__.py` file of `litellm`. This has been fixed in `litellm` version 1.97.0, so this can be removed once we bump to that version.
Since none of our current recommenders currently uses our `SerialMixin`, the recommender is wrapped in a meta recommender.
The recommender now uses the description of the objective itself insted of exponsing an additional field for it. Furthermore, the targets as well as their metadata is now being used within the recommender.
As a consequence, the prompt template was also extended such that it contains whatever is in the `misc` field of the metadata dict.
For discrete constraints, we check for validity using the existing mechanisms and raise an `LLMResponseError` if violated. Continuous constraints can currently not be validated, so an `LLMResponseWarning` is issued if any such constraint is present.
6720e6a to
54a9150
Compare
Inspired by how the same issue is handled in the hybrid recommender.
6e2022a to
b1c8cb4
Compare
Add LLMRecommender for LLM-based experimental design
Summary
Adds an LLMRecommender that queries a large language model (via LiteLLM) to propose experiments. It is primarily intended for warm-starting a campaign: before enough data exists to train a surrogate, the model can use parameter semantics and domain/literature priors to suggest more sensible points than random sampling. It slots into the recommender hierarchy like any other pure recommender and is typically combined with a Bayesian recommender via a TwoPhaseMetaRecommender.
Ported from #561 and substantially reworked for the current codebase.
What it does
Key design decisions
PureRecommender. It legitimately consumesmeasurements/pending_experimentsfor the prompt but is neither non-predictive nor Bayesian.recommend()is fully overridden and builds the result directly from the model response, so the recommend* hooks are intentionally not implemented.OPENAI_API_KEY,ANTHROPIC_API_KEY) selected by the model prefix — so there is deliberately no API-key attribute.related_dataor a feasibility check. These have been removed._prompts.py) and response parsing/validation (_parsing.py) live in dedicated submodules;llm.pyholds the recommender class.litellm/jinja2are lazy-imported so import baybe stays light.Testing
Docs & example