Skip to content

Commit f688f7b

Browse files
authored
Breaking: Remove legacy hayhooks pipeline deploy ; introduce hayhooks pipeline deploy-yaml to deploy YAML pipelines with required inputs/outputs fields (#161)
* resolve inputs and outputs from a Haystack pipeline YAML definition * Add method to add YAML pipeline to registry * Better types handling * Add deploy_pipeline_yaml ; Add route for YAML pipeline ; Add /deploy-yaml ; refactoring * Fix types * Fix lint * Fix for python 3.9 * Fix for last ruff version * Fix tests * Add route for YAML if app is present * Introduced InvalidYamlIOError 422 error for YAML deployments with missing inputs/outputs * Add deploy-yaml CLI command * Ensure inputs / outputs YAML pipelines are deployed at startup (so not using old YAML deploy logic) * Skip tests with old YAML logic * Cleanup of old YAML handling logic to avoid confusion * Remove old CLI deploy command * Add CLI alias: deploy -> deploy-files * Update README * Use AsyncPipeline when loading YAML pipelines (to avoid using run_in_threadpool when running it) * Fix lint * Add a section for loading pipelines or agents at startup * Enable YAML pipelines as MCP tools * Update README * Update README * Update README * Refactor: add skip_mcp to payload * Remove unneeded import * Restore disabled tests * Re-added required_variables on prompt_builder component * Update docstrings * Update docstrings * Update docstrings * Add warning when using removed hayhooks pipeline deploy command * Update README * Add warning when using hayhooks pipeline deploy command * Consistent method naming + add note about type:ignore * Update description / comments * Update README * Update error messages * Add note about using AsyncPipeline only ; Removed comment * Update README * Remove hayhooks pipeline deploy references
1 parent 94b23dd commit f688f7b

36 files changed

+1131
-932
lines changed

README.md

Lines changed: 148 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,17 @@ With Hayhooks, you can:
3232
- [Async Run API Method](#run_api_async)
3333
- [PipelineWrapper development with `overwrite` option](#pipelinewrapper-development-with-overwrite-option)
3434
- [Additional Dependencies](#additional-dependencies)
35+
- [Deploy a YAML Pipeline](#deploy-a-yaml-pipeline)
3536
- [Deploy an Agent](#deploy-an-agent)
37+
- [Load pipelines or agents at startup](#load-pipelines-or-agents-at-startup)
3638
- [Support file uploads](#support-file-uploads)
3739
- [Run pipelines from the CLI](#run-pipelines-from-the-cli)
3840
- [Run a pipeline from the CLI JSON-compatible parameters](#run-a-pipeline-from-the-cli-json-compatible-parameters)
3941
- [Run a pipeline from the CLI uploading files](#run-a-pipeline-from-the-cli-uploading-files)
4042
- [MCP support](#mcp-support)
4143
- [MCP Server](#mcp-server)
4244
- [Create a PipelineWrapper for exposing a Haystack pipeline as a MCP Tool](#create-a-pipelinewrapper-for-exposing-a-haystack-pipeline-as-a-mcp-tool)
45+
- [Expose a YAML pipeline as a MCP Tool](#expose-a-yaml-pipeline-as-a-mcp-tool)
4346
- [Using Hayhooks MCP Server with Claude Desktop](#using-hayhooks-mcp-server-with-claude-desktop)
4447
- [Using Hayhooks Core MCP Tools in IDEs like Cursor](#using-hayhooks-core-mcp-tools-in-ides-like-cursor)
4548
- [Development and deployment of Haystack pipelines directly from Cursor](#development-and-deployment-of-haystack-pipelines-directly-from-cursor)
@@ -61,15 +64,13 @@ With Hayhooks, you can:
6164
- [Run Hayhooks Programmatically](#run-hayhooks-programmatically)
6265
- [Sharing code between pipeline wrappers](#sharing-code-between-pipeline-wrappers)
6366
- [Deployment Guidelines](#deployment-guidelines)
64-
- [Legacy Features](#legacy-features)
65-
- [Deploy Pipeline Using YAML](#deploy-a-pipeline-using-only-its-yaml-definition)
6667
- [License](#license)
6768

6869
## Quick start with Docker Compose
6970

7071
To quickly get started with Hayhooks, we provide a ready-to-use Docker Compose 🐳 setup with pre-configured integration with [open-webui](https://openwebui.com/).
7172

72-
It's available [here](https://github.com/deepset-ai/hayhooks-open-webui-docker-compose).
73+
It's available in the [Hayhooks + Open WebUI Docker Compose repository](https://github.com/deepset-ai/hayhooks-open-webui-docker-compose).
7374

7475
## Quick start
7576

@@ -162,8 +163,8 @@ CLI commands are basically wrappers around the HTTP API of the server. The full
162163
hayhooks run # Start the server
163164
hayhooks status # Check the status of the server and show deployed pipelines
164165

165-
hayhooks pipeline deploy-files <path_to_dir> # Deploy a pipeline using PipelineWrapper
166-
hayhooks pipeline deploy <pipeline_name> # Deploy a pipeline from a YAML file
166+
hayhooks pipeline deploy-files <path_to_dir> # Deploy a pipeline using PipelineWrapper files (preferred)
167+
hayhooks pipeline deploy-yaml <path_to_yaml> # Deploy a pipeline from a YAML file
167168
hayhooks pipeline undeploy <pipeline_name> # Undeploy a pipeline
168169
hayhooks pipeline run <pipeline_name> # Run a pipeline
169170
```
@@ -195,7 +196,7 @@ The pipeline wrapper provides a flexible foundation for deploying Haystack pipel
195196
- Define custom execution logic with configurable inputs and outputs
196197
- Optionally expose OpenAI-compatible chat endpoints with streaming support for integration with interfaces like [open-webui](https://openwebui.com/)
197198

198-
The `pipeline_wrapper.py` file must contain an implementation of the `BasePipelineWrapper` class (see [here](src/hayhooks/server/utils/base_pipeline_wrapper.py) for more details).
199+
The `pipeline_wrapper.py` file must contain an implementation of the `BasePipelineWrapper` class (see [BasePipelineWrapper source](src/hayhooks/server/utils/base_pipeline_wrapper.py) for more details).
199200

200201
A minimal `PipelineWrapper` looks like this:
201202

@@ -274,6 +275,8 @@ hayhooks pipeline deploy-files -n chat_with_website examples/pipeline_wrappers/c
274275

275276
This will deploy the pipeline with the name `chat_with_website`. Any error encountered during development will be printed to the console and show in the server logs.
276277

278+
Alternatively, you can deploy via HTTP: `POST /deploy_files`.
279+
277280
#### PipelineWrapper development with `overwrite` option
278281

279282
During development, you can use the `--overwrite` flag to redeploy your pipeline without restarting the Hayhooks server:
@@ -318,6 +321,64 @@ Then, assuming you've installed the Hayhooks package in a virtual environment, y
318321
pip install trafilatura
319322
```
320323

324+
## Deploy a YAML Pipeline
325+
326+
You can deploy a Haystack pipeline directly from its YAML definition using the `/deploy-yaml` endpoint. This mode builds request/response schemas from the YAML-declared `inputs` and `outputs`.
327+
328+
Note: You can also deploy YAML pipelines from the CLI with `hayhooks pipeline deploy-yaml`. Wrapper-based deployments continue to use `/deploy_files`.
329+
330+
Tip: You can obtain a pipeline's YAML from an existing `Pipeline` instance using `pipeline.dumps()`. See the [Haystack serialization docs](https://docs.haystack.deepset.ai/docs/serialization) for details.
331+
332+
Requirements:
333+
334+
- The YAML must declare both `inputs` and `outputs` fields so the API request/response schemas can be generated. If you have generated the YAML from a `Pipeline` using `pipeline.dumps()`, you will need to add the `inputs` and `outputs` fields _manually_.
335+
- `inputs`/`outputs` entries map friendly names to pipeline component fields (e.g. `fetcher.urls`, `prompt.query`).
336+
337+
Minimal example:
338+
339+
```yaml
340+
# ... pipeline definition ...
341+
342+
inputs:
343+
urls:
344+
- fetcher.urls
345+
query:
346+
- prompt.query
347+
outputs:
348+
replies: llm.replies
349+
```
350+
351+
CLI:
352+
353+
```shell
354+
hayhooks pipeline deploy-yaml -n inputs_outputs_pipeline --description "My pipeline" pipelines/inputs_outputs_pipeline.yml
355+
```
356+
357+
Alternatively, you can deploy via HTTP: `POST /deploy-yaml`.
358+
359+
If successful, the server exposes a run endpoint at `/{name}/run` with a request/response schema derived from the YAML IO. For example:
360+
361+
```shell
362+
curl -X POST \
363+
http://HAYHOOKS_HOST:HAYHOOKS_PORT/inputs_outputs_pipeline/run \
364+
-H 'Content-Type: application/json' \
365+
-d '{"urls": ["https://haystack.deepset.ai"], "query": "What is Haystack?"}'
366+
```
367+
368+
Note: when deploying a YAML pipeline, Hayhooks will create an `AsyncPipeline` instance from the YAML source code. This is because we are in an async context, so we should avoid running sync methods using e.g. `run_in_threadpool`. With AsyncPipeline, we can await `run_async` directly, so we make use of the current event loop.
369+
370+
Limitations:
371+
372+
- YAML-deployed pipelines do not support OpenAI-compatible chat completion endpoints, so they cannot be used with Open WebUI. If you need chat completion/streaming, use a `PipelineWrapper` and implement `run_chat_completion` or `run_chat_completion_async` (see the OpenAI compatibility section below).
373+
374+
Available CLI options for `hayhooks pipeline deploy-yaml`:
375+
376+
- `--name, -n`: override the pipeline name (default: YAML file stem)
377+
- `--description`: optional human-readable description (used in MCP tool listing)
378+
- `--overwrite, -o`: overwrite if the pipeline already exists
379+
- `--skip-mcp`: skip exposing this pipeline as an MCP Tool
380+
- `--save-file/--no-save-file`: save the YAML under `pipelines/{name}.yml` on the server (default: `--save-file`)
381+
321382
## Deploy an Agent
322383

323384
Deploying a [Haystack Agent](https://docs.haystack.deepset.ai/docs/agents) is very similar to deploying a pipeline.
@@ -358,6 +419,41 @@ As you can see, the `run_chat_completion_async` method is the one that will be u
358419

359420
The `async_streaming_generator` function is a utility function that [will handle the streaming of the agent's responses](#async_streaming_generator).
360421

422+
## Load pipelines or agents at startup
423+
424+
Hayhooks can automatically deploy pipelines or agents on startup by scanning a pipelines directory.
425+
426+
- Set `HAYHOOKS_PIPELINES_DIR` (defaults to `./pipelines`).
427+
- On startup, Hayhooks will:
428+
- Deploy every YAML file at the directory root (`*.yml`/`*.yaml`) using the file name as the pipeline name.
429+
- Deploy every immediate subfolder as a wrapper-based pipeline/agent if it contains a `pipeline_wrapper.py`.
430+
431+
Example layout:
432+
433+
```text
434+
my-project/
435+
├── .env
436+
└── pipelines/
437+
├── inputs_outputs_pipeline.yml # YAML-only pipeline -> POST /inputs_outputs_pipeline/run
438+
├── chat_with_website/ # Wrapper-based pipeline -> POST /chat_with_website/run (+ chat endpoints if implemented)
439+
│ ├── pipeline_wrapper.py
440+
│ └── chat_with_website.yml
441+
└── agent_streaming/
442+
└── pipeline_wrapper.py
443+
```
444+
445+
Configure via environment or `.env`:
446+
447+
```shell
448+
# .env
449+
HAYHOOKS_PIPELINES_DIR=./pipelines
450+
```
451+
452+
Notes:
453+
454+
- YAML-deployed pipelines require `inputs` and `outputs` in the YAML and do not expose OpenAI-compatible chat endpoints. For chat/streaming, use a `PipelineWrapper` and implement `run_chat_completion`/`run_chat_completion_async`.
455+
- If your wrappers import shared code, set `HAYHOOKS_ADDITIONAL_PYTHON_PATH` (see “Sharing code between pipeline wrappers”).
456+
361457
## Support file uploads
362458

363459
Hayhooks can easily handle uploaded files in your pipeline wrapper `run_api` method by adding `files: Optional[List[UploadFile]] = None` as an argument.
@@ -445,6 +541,52 @@ hayhooks mcp run
445541

446542
This will start the Hayhooks MCP Server on `HAYHOOKS_MCP_HOST:HAYHOOKS_MCP_PORT`.
447543

544+
### Expose a YAML pipeline as a MCP Tool
545+
546+
Hayhooks can expose YAML-deployed pipelines as MCP Tools. When you deploy a pipeline via `/deploy-yaml` (or the CLI `hayhooks pipeline deploy-yaml`), Hayhooks:
547+
548+
- Builds flat request/response models from YAML-declared `inputs` and `outputs`.
549+
- Registers the pipeline as an `AsyncPipeline` and adds it to the registry with metadata required for MCP Tools.
550+
- Lists it in MCP `list_tools()` with:
551+
- `name`: the pipeline name (YAML file stem or provided `--name`)
552+
- `description`: the optional description you pass during deployment (defaults to the pipeline name)
553+
- `inputSchema`: JSON schema derived from YAML `inputs`
554+
555+
Calling a YAML pipeline via MCP `call_tool` executes the pipeline asynchronously and returns the pipeline result as a JSON string in `TextContent`.
556+
557+
Sample YAML for a simple `sum` pipeline using only the `haystack.testing.sample_components.sum.Sum` component:
558+
559+
```yaml
560+
components:
561+
sum:
562+
init_parameters: {}
563+
type: haystack.testing.sample_components.sum.Sum
564+
565+
connections: []
566+
567+
metadata: {}
568+
569+
inputs:
570+
values: sum.values
571+
572+
outputs:
573+
total: sum.total
574+
```
575+
576+
Example (Streamable HTTP via MCP client):
577+
578+
```python
579+
tools = await client.list_tools()
580+
# Find YAML tool by name, e.g., "sum" (the pipeline name)
581+
result = await client.call_tool("sum", {"values": [1, 2, 3]})
582+
assert result.content[0].text == '{"total": 6}'
583+
```
584+
585+
Notes and limitations:
586+
587+
- YAML pipelines must declare `inputs` and `outputs`.
588+
- YAML pipelines are run-only via MCP and return JSON text; if you need OpenAI-compatible chat endpoints or streaming, use a `PipelineWrapper` and implement `run_chat_completion`/`run_chat_completion_async`.
589+
448590
### Create a PipelineWrapper for exposing a Haystack pipeline as a MCP Tool
449591

450592
A [MCP Tool](https://modelcontextprotocol.io/docs/concepts/tools) requires the following properties:
@@ -971,24 +1113,6 @@ We have some dedicated documentation for deployment:
9711113

9721114
We also have some additional deployment guidelines, see [deployment_guidelines.md](docs/deployment_guidelines.md).
9731115

974-
### Legacy Features
975-
976-
#### Deploy a pipeline using only its YAML definition
977-
978-
**⚠️ This way of deployment is not maintained anymore and will be deprecated in the future**.
979-
980-
We're still supporting the Hayhooks _former_ way to deploy a pipeline.
981-
982-
The former command `hayhooks deploy` is now changed to `hayhooks pipeline deploy` and can be used to deploy a pipeline only from a YAML definition file.
983-
984-
For example:
985-
986-
```shell
987-
hayhooks pipeline deploy -n chat_with_website examples/pipeline_wrappers/chat_with_website/chat_with_website.yml
988-
```
989-
990-
This will deploy the pipeline with the name `chat_with_website` from the YAML definition file `examples/pipeline_wrappers/chat_with_website/chat_with_website.yml`. You then can check the generated docs at `http://HAYHOOKS_HOST:HAYHOOKS_PORT/docs` or `http://HAYHOOKS_HOST:HAYHOOKS_PORT/redoc`, looking at the `POST /chat_with_website` endpoint.
991-
9921116
### License
9931117

9941118
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.

src/hayhooks/cli/pipeline.py

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,23 +38,49 @@ def _deploy_with_progress(ctx: typer.Context, name: str, endpoint: str, payload:
3838
show_error_and_abort(f"Pipeline '[bold]{name}[/bold]' already exists! ⚠️")
3939

4040

41-
@pipeline.command()
42-
def deploy(
41+
@pipeline.command(name="deploy-yaml")
42+
def deploy_yaml( # noqa: PLR0913
4343
ctx: typer.Context,
44-
name: Annotated[Optional[str], typer.Option("--name", "-n", help="The name of the pipeline to deploy.")],
4544
pipeline_file: Path = typer.Argument( # noqa: B008
46-
help="The path to the pipeline file to deploy."
45+
help="The path to the YAML pipeline file to deploy."
4746
),
47+
name: Annotated[Optional[str], typer.Option("--name", "-n", help="The name of the pipeline to deploy.")] = None,
48+
overwrite: Annotated[
49+
bool, typer.Option("--overwrite", "-o", help="Whether to overwrite the pipeline if it already exists.")
50+
] = False,
51+
description: Annotated[
52+
Optional[str], typer.Option("--description", help="Optional description for the pipeline.")
53+
] = None,
54+
skip_mcp: Annotated[
55+
bool, typer.Option("--skip-mcp", help="If set, skip MCP integration for this pipeline.")
56+
] = False,
57+
save_file: Annotated[
58+
bool,
59+
typer.Option(
60+
"--save-file/--no-save-file",
61+
help="Whether to save the YAML under pipelines/{name}.yml on the server.",
62+
),
63+
] = True,
4864
) -> None:
49-
"""Deploy a pipeline to the Hayhooks server."""
65+
"""Deploy a YAML pipeline using the /deploy-yaml endpoint."""
5066
if not pipeline_file.exists():
5167
show_error_and_abort("Pipeline file does not exist.", str(pipeline_file))
5268

5369
if name is None:
5470
name = pipeline_file.stem
5571

56-
payload = {"name": name, "source_code": pipeline_file.read_text()}
57-
_deploy_with_progress(ctx=ctx, name=name, endpoint="deploy", payload=payload)
72+
payload = {
73+
"name": name,
74+
"source_code": pipeline_file.read_text(),
75+
"overwrite": overwrite,
76+
"save_file": save_file,
77+
"skip_mcp": skip_mcp,
78+
}
79+
80+
if description is not None:
81+
payload["description"] = description
82+
83+
_deploy_with_progress(ctx=ctx, name=name, endpoint="deploy-yaml", payload=payload)
5884

5985

6086
@pipeline.command()
@@ -91,6 +117,17 @@ def deploy_files(
91117
_deploy_with_progress(ctx=ctx, name=name, endpoint="deploy_files", payload=payload)
92118

93119

120+
@pipeline.command(name="deploy", context_settings={"ignore_unknown_options": True, "allow_extra_args": True})
121+
def deploy(_ctx: typer.Context) -> None:
122+
"""Removed command; use 'deploy-yaml' or 'deploy-files' instead."""
123+
show_warning_panel(
124+
"[bold yellow]`hayhooks pipeline deploy` has been removed.[/bold yellow]\n"
125+
"Use: \n"
126+
"`hayhooks pipeline deploy-yaml <pipeline.yml>` for YAML-based deployments or\n"
127+
"`hayhooks pipeline deploy-files <pipeline_dir>` for PipelineWrapper-based deployments."
128+
)
129+
130+
94131
@pipeline.command()
95132
def undeploy(
96133
ctx: typer.Context,

src/hayhooks/server/app.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,7 @@
1111

1212
from hayhooks.server.logger import log
1313
from hayhooks.server.routers import deploy_router, draw_router, openai_router, status_router, undeploy_router
14-
from hayhooks.server.utils.deploy_utils import (
15-
PipelineDefinition,
16-
deploy_pipeline_def,
17-
deploy_pipeline_files,
18-
read_pipeline_files_from_dir,
19-
)
14+
from hayhooks.server.utils.deploy_utils import deploy_pipeline_files, deploy_pipeline_yaml, read_pipeline_files_from_dir
2015
from hayhooks.settings import APP_DESCRIPTION, APP_TITLE, check_cors_settings, settings
2116

2217

@@ -35,8 +30,7 @@ def deploy_yaml_pipeline(app: FastAPI, pipeline_file_path: Path) -> dict:
3530
with open(pipeline_file_path) as pipeline_file:
3631
source_code = pipeline_file.read()
3732

38-
pipeline_definition = PipelineDefinition(name=name, source_code=source_code)
39-
deployed_pipeline = deploy_pipeline_def(app, pipeline_definition)
33+
deployed_pipeline = deploy_pipeline_yaml(pipeline_name=name, source_code=source_code, app=app)
4034
log.info(f"Deployed pipeline from yaml: {deployed_pipeline['name']}")
4135

4236
return deployed_pipeline

src/hayhooks/server/exceptions.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ class PipelineWrapperError(Exception):
1010
pass
1111

1212

13+
class PipelineYamlError(Exception):
14+
"""Exception for errors loading pipeline YAML."""
15+
16+
pass
17+
18+
1319
class PipelineModuleLoadError(Exception):
1420
"""Exception for errors loading pipeline module."""
1521

@@ -24,3 +30,9 @@ class PipelineNotFoundError(Exception):
2430
"""Exception for errors when a pipeline is not found."""
2531

2632
pass
33+
34+
35+
class InvalidYamlIOError(Exception):
36+
"""Exception for invalid or missing YAML inputs/outputs declarations."""
37+
38+
pass

0 commit comments

Comments
 (0)