Haystack uses Hatch for environment and dependency management.
Do not run python or pip directly.
Before running code on this project, you must be able to run hatch --version and get a correct output.
If not, ask the user where Hatch is or if they want to install it. For installation instructions, refer to https://hatch.pypa.io/latest/install/#installation.
hatch -e test run python SCRIPT.py
hatch -e test shell
uv pip install PACKAGE
hatch env prune
Tests run via Hatch and support pytest arguments.
Prefer running tests on a specific module or using -k, since the full suite is large.
hatch run test:unit
hatch run test:integration
hatch run test:types
Type checking targets Python 3.12, above the 3.11 floor in requires-python, because numpy's stubs use PEP 695 type statements that mypy rejects below 3.12. So mypy will not catch code that breaks on 3.11; the unit test matrix runs on 3.11 and covers that.
To fix type issues, avoid type: ignore, casts, or assertions when possible. If they are necessary, explain why.
hatch run fmt
Every user-facing PR (not docs, not CI) must include a release note:
hatch run release-note SHORT_DESCRIPTION
Edit the generated file in releasenotes/notes/. Release notes use reStructuredText formatting; see the release notes section in CONTRIBUTING.md for details.
Community contributors can have only one open pull request at a time in this repository. Before opening a PR on the user's behalf, check whether they already have one open (gh pr list --author @me); if so, tell them instead of opening another.
In addition, there are rules inferred from previous code reviews. Follow them like a reviewer's note: they encode reasons, not ceremony, so when a rule genuinely does not fit the change at hand, say why rather than contorting the code to satisfy it.
Also see directory-specific guidelines:
- docs-website/AGENTS.md
- docs-website/docs/AGENTS.md
- haystack/hooks/compaction/AGENTS.md
- releasenotes/notes/AGENTS.md
- test/AGENTS.md
- Target the 3.0 API:
ToolInvoker,AsyncPipeline, and non-chat generators were removed — letAgentown tool calls, usePipeline.run()/Pipeline.run_async(), and use ChatGenerators likeOpenAIChatGenerator - Serialize with the Haystack helpers (
component_to_dict,default_from_dict): infrom_dict()pass only the fields present to__init__()so defaults stay centralized, shallow-copy the payload instead ofdeepcopy(), and keep tools/toolsets nested underdata— preserves wire compatibility and round-trip fidelity - Keep public signatures explicit and compatible: optional params keyword-only after
*and appended rather than inserted, mirrored constructor params spelled out instead of*args/**kwargs, and chat generators keeping therun(messages, *, streaming_callback, generation_kwargs, tools)order — avoids breaking positional callers - Create API clients and load models in
warm_up(), not__init__; implementwarm_up()only for real setup and make it idempotent — keeps components constructible and serializable without credentials or network - Implement
run_asynconly when there is a real async execution path —Pipeline.run_async()already falls back toasyncio.to_thread(component.run, ...)when it is missing; where it exists, keeprun_async/warm_up_async/close_asyncgenuinely async with separate async state and hooks, and share sync/async logic through private helpers — prevents event-loop blocking and sync/async drift - Pass explicit
required_variablesfor optional prompt vars — avoids requiring optional inputs - Subclass
Toolsetonly when inherited collection APIs match — prefer composition or raiseNotImplementedError - Read live Haystack
Stateresources viastate.data.get(...)orstate.data[...]— avoids deep-copy bugs - Prefer existing
haystack/core/pipelineAPIs or inline logic — avoid duplicate or one-off public APIs - Reuse splitter chunk metadata in
haystack/components/preprocessors/— keep traceability fields likepage_number,source_id,header,parent_headers, and split positions; add new keys only for existing downstream contracts
- Keep inline comments and private-helper docs to what is non-obvious — remove restatements, keep durable caveats and rationale
- Keep docstrings current with signatures and behavior, in the existing Haystack style: each public
:paramby meaning, default, and constraints;:returns:contracts; exceptions in the existing:raises ValueError:style; aliases likeToolsTypereflected — stale docs mislead users and assistants - Keep doc examples minimal, runnable, and local: default constructors with required env vars like
OPENAI_API_KEYnoted nearby, only the imports the snippet uses, no restated defaults (name a model only for model-specific behavior), expected output as comments, generally# >> ... - When behavior, fields, or names change, update every relevant surface in the same PR:
haystack/components/docstrings and examples, documentation pages, andexperimentalwording,pydocIDs, and generated markdown filenames when promoting features - Choose which documentation versions to update: update
docs-website/docs/by default; also update the latest stableversioned_docs/version-X.Y/pages for corrections to stable docs or already-released core integration features.
- Use keyword args for multi-parameter calls — improves readability and prevents mix-ups
- Keep lint suppressions exact and current — a
# noqa: PLR0915only while the code needs it - Scope diffs to the stated goal and leave them clean: no unrelated refactors or formatting, no stray
print(), no filename-only header comments, no unused private helpers or redundant branches, no args that only restate callee defaults - Use
{placeholder}logger templates with kwargs — preserves structured logs and avoids eager formatting - Prefix internal helpers and module-level constants with
_and keep private constants local; never name locals after imported functions, decorators, or utilities — clarifies API boundaries and avoids shadowing
- Fix types at the source; where a suppression is unavoidable use a coded
# type: ignore[...]with a nearby why-safe comment, and add@overloadonly for real API variants - Resolve callable annotations with
typing.get_type_hints()— avoids bugs from raw or stringinspect.signature()annotations - Keep union aliases non-redundant and concept-named — include base types only when subclasses inherit
- Use
T | None, notOptional[T], in annotations and casts — keeps typing concise - Annotate helper params broadly and accurately —
type | types.UnionTypefor class-or-PEP-604 inputs,Anyfor arbitrary typing objects
- Import via public APIs in the narrowest clear form (keep module imports like
import httpxwhen namespaces matter); keep only used imports plus deliberate compatibility re-exports - Use top-level absolute
haystack...imports inhaystack/components/andhaystack/core/component/*.py— keeps component dependencies traceable - Use
LazyImportonly for optional third-party deps — otherwise import directly
- Pass
allowed_modules=or setHAYSTACK_DESERIALIZATION_ALLOWLISTfor YAML loads — never widenhaystack/core/serialization_security.pyallowlists in library code
- Centralize
DocumentStoretests inhaystack/testing/document_store.pymixins — useDocumentStoreBaseTestsas the minimum suite and compose capability mixins intoDocumentStoreBaseExtendedTests; add explicit integration skips only for unsupported backend features.
- Define package exports in
__init__.pyvia__all__; avoid__all__in implementation modules — keeps public APIs intentional - Update
pyproject.tomlfor new package APIs — declare deps and minimum versions used