diff --git a/.env.example b/.env.example index 98cc3da8..c92cea6b 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,10 @@ -# Host for the FastAPI app +# Hayhooks Host HAYHOOKS_HOST="localhost" -# Port for the FastAPI app +# Hayhooks Port HAYHOOKS_PORT=1416 -# Root path for the FastAPI app +# Root path HAYHOOKS_ROOT_PATH="" # Path to the directory containing the pipelines diff --git a/CONTRIBUTING_DOCS.md b/CONTRIBUTING_DOCS.md new file mode 100644 index 00000000..f1464dd7 --- /dev/null +++ b/CONTRIBUTING_DOCS.md @@ -0,0 +1,262 @@ +# Documentation Development + +This guide covers how to work with and contribute to the Hayhooks documentation. + +## Prerequisites + +- Python 3.9+ +- Hatch installed (`pip install hatch`) + +## Building Documentation + +### Using Hatch (Recommended) + +The documentation can be built and served using Hatch commands: + +#### Development Server (Recommended) + +Start a local development server with live reloading using the dedicated docs environment: + +```bash +# Using the dedicated docs environment (auto-installs all dependencies) +hatch run docs:serve + +# With custom options +hatch run docs:serve --open --dirty +``` + +The server will be available at `http://localhost:8000/hayhooks/` + +#### Build Production Site + +Build the documentation for deployment: + +```bash +# Using the docs environment (recommended) +hatch run docs:build + +# Clean build (removes old files first) +hatch run docs:build --clean + +# Strict build (fails on warnings) +hatch run docs:build --strict +``` + +The built site will be available in the `site/` directory. + +#### Deploy Documentation + +Deploy the documentation to GitHub Pages: + +```bash +# Deploy to GitHub Pages +hatch run docs:deploy + +# With custom options +hatch run docs:deploy --force +``` + +### Direct MkDocs Usage + +If you prefer to use MkDocs directly: + +```bash +# Install dependencies +pip install mkdocs-material + +# Serve documentation +mkdocs serve + +# Build documentation +mkdocs build +``` + +## Documentation Structure + +```text +docs/ +├── mkdocs.yml # Main configuration +├── index.md # Homepage +├── getting-started/ # Getting started guides +├── concepts/ # Core concepts +├── features/ # Feature documentation +├── advanced/ # Advanced topics +├── deployment/ # Deployment guides +├── examples/ # Example documentation +├── reference/ # Reference documentation +├── about/ # About information +├── assets/ # Images and static assets +├── stylesheets/ # Custom CSS +└── javascripts/ # Custom JavaScript +``` + +## Adding New Documentation + +### 1. Create New Files + +Add new Markdown files in the appropriate directory: + +```bash +# Create a new feature document +touch docs/features/new-feature.md + +# Create a new example +touch docs/examples/new-example.md +``` + +### 2. Update Navigation + +Update `mkdocs.yml` to include your new documentation in the navigation: + +```yaml +nav: + - Features: + - New Feature: features/new-feature.md # Add this line + - OpenAI Compatibility: features/openai-compatibility.md + - MCP Support: features/mcp-support.md +``` + +### 3. Add Images + +Place images in the `docs/assets/` directory: + +```bash +# Add an image +mv screenshot.png docs/assets/ + +# Reference in Markdown +![Screenshot](../assets/screenshot.png) +``` + +## Documentation Style Guide + +### Writing Style + +- Use clear, concise language +- Include practical examples +- Provide step-by-step instructions +- Use proper Markdown formatting + +### Avoiding Redundancy + +To maintain consistency and reduce maintenance burden: + +- **Single Source of Truth**: Each topic should have one canonical location + - README.md: Quick overview and getting started examples + - docs/concepts/: Detailed conceptual explanations + - docs/reference/: Complete API and configuration references + +- **Cross-Referencing**: Link to the canonical source instead of duplicating content + - Good: "For complete configuration options, see [Environment Variables Reference](../reference/environment-variables.md)" + - Bad: Copying all environment variables into multiple pages + +- **Next Steps**: Keep to 2-3 most relevant links per page + - Focus on logical next actions for the reader + - Avoid circular references (page A → page B → page A) + +### Code Examples + +Use proper code block formatting: + +```python +# Python code +def example_function(): + return "Hello, World!" +``` + +```bash +# Shell commands +hatch run docs-serve +``` + +### Links + +- **Internal Documentation**: Use relative links (e.g., `../concepts/pipeline-wrapper.md`) +- **External Resources**: Use absolute links (e.g., `https://haystack.deepset.ai/`) +- **README Links**: When linking from docs to README, use absolute GitHub URLs +- **Test Links**: Always verify links work before committing + +## Testing Documentation + +### Build Verification + +Always test the documentation builds successfully: + +```bash +hatch run docs:build --strict +``` + +### Link Verification + +Check for broken links: + +```bash +# Test locally +hatch run docs:serve + +# Or use a link checker tool +pip install linkchecker +linkchecker http://localhost:8000/hayhooks/ +``` + +### Preview Changes + +Preview your changes in the browser: + +```bash +hatch run docs:serve --open +``` + +## Deployment + +### GitHub Pages + +> **Note:** Automatic deployment via GitHub Actions is not yet configured. For now, documentation must be deployed manually. A GitHub Actions workflow will be added in the future. + +### Manual Deployment + +To deploy manually: + +```bash +# Deploy to GitHub Pages (builds and deploys in one command) +hatch run docs:deploy + +# Or build only without deploying +hatch run docs:build +# The site is now ready in the site/ directory +``` + +## Troubleshooting + +### Common Issues + +1. **Build Fails** + - Check for Markdown syntax errors + - Verify all links are valid + - Ensure image paths are correct + +2. **Navigation Issues** + - Check `mkdocs.yml` syntax + - Verify all referenced files exist + - Test navigation structure + +3. **Style Issues** + - Check CSS file paths + - Verify JavaScript syntax + - Test in multiple browsers + +### Getting Help + +- Check MkDocs documentation: +- Review Material theme docs: +- Open an issue on GitHub: + +## Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your documentation changes +4. Test locally with `hatch run docs:serve` +5. Submit a pull request + +Thank you for contributing to Hayhooks documentation! diff --git a/README.md b/README.md index b25c8c0a..d4e55bc3 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ With Hayhooks, you can: - 📦 **Deploy your Haystack pipelines and agents as REST APIs** with maximum flexibility and minimal boilerplate code. - 🛠️ **Expose your Haystack pipelines and agents over the MCP protocol**, making them available as tools in AI dev environments like [Cursor](https://cursor.com) or [Claude Desktop](https://claude.ai/download). Under the hood, Hayhooks runs as an [MCP Server](https://modelcontextprotocol.io/docs/concepts/architecture), exposing each pipeline and agent as an [MCP Tool](https://modelcontextprotocol.io/docs/concepts/tools). -- 💬 **Integrate your Haystack pipelines and agents with [open-webui](https://openwebui.com)** as OpenAI-compatible chat completion backends with streaming support. +- 💬 **Integrate your Haystack pipelines and agents with [Open WebUI](https://openwebui.com)** as OpenAI-compatible chat completion backends with streaming support. - 🕹️ **Control Hayhooks core API endpoints through chat** - deploy, undeploy, list, or run Haystack pipelines and agents by chatting with [Claude Desktop](https://claude.ai/download), [Cursor](https://cursor.com), or any other MCP client. [![PyPI - Version](https://img.shields.io/pypi/v/hayhooks.svg)](https://pypi.org/project/hayhooks) @@ -14,392 +14,68 @@ With Hayhooks, you can: [![Docker image release](https://github.com/deepset-ai/hayhooks/actions/workflows/docker.yml/badge.svg)](https://github.com/deepset-ai/hayhooks/actions/workflows/docker.yml) [![Tests](https://github.com/deepset-ai/hayhooks/actions/workflows/tests.yml/badge.svg)](https://github.com/deepset-ai/hayhooks/actions/workflows/tests.yml) -- [Quick Start with Docker Compose](#quick-start-with-docker-compose) -- [Quick Start](#quick-start) -- [Install the package](#install-the-package) -- [Configuration](#configuration) - - [Environment Variables](#environment-variables) - - [CORS Settings](#cors-settings) -- [Logging](#logging) - - [Using the logger](#using-the-logger) - - [Changing the log level](#changing-the-log-level) -- [CLI Commands](#cli-commands) -- [Start Hayhooks](#start-hayhooks) -- [Deploy a Pipeline](#deploy-a-pipeline) - - [PipelineWrapper](#why-a-pipeline-wrapper) - - [Setup Method](#setup) - - [Run API Method](#run_api) - - [Async Run API Method](#run_api_async) - - [PipelineWrapper development with `overwrite` option](#pipelinewrapper-development-with-overwrite-option) - - [Additional Dependencies](#additional-dependencies) -- [Deploy a YAML Pipeline](#deploy-a-yaml-pipeline) -- [Deploy an Agent](#deploy-an-agent) -- [Load pipelines or agents at startup](#load-pipelines-or-agents-at-startup) -- [Support file uploads](#support-file-uploads) -- [Run pipelines from the CLI](#run-pipelines-from-the-cli) - - [Run a pipeline from the CLI JSON-compatible parameters](#run-a-pipeline-from-the-cli-json-compatible-parameters) - - [Run a pipeline from the CLI uploading files](#run-a-pipeline-from-the-cli-uploading-files) -- [MCP support](#mcp-support) - - [MCP Server](#mcp-server) - - [Create a PipelineWrapper for exposing a Haystack pipeline as a MCP Tool](#create-a-pipelinewrapper-for-exposing-a-haystack-pipeline-as-a-mcp-tool) - - [Expose a YAML pipeline as a MCP Tool](#expose-a-yaml-pipeline-as-a-mcp-tool) - - [Using Hayhooks MCP Server with Claude Desktop](#using-hayhooks-mcp-server-with-claude-desktop) - - [Using Hayhooks Core MCP Tools in IDEs like Cursor](#using-hayhooks-core-mcp-tools-in-ides-like-cursor) - - [Development and deployment of Haystack pipelines directly from Cursor](#development-and-deployment-of-haystack-pipelines-directly-from-cursor) - - [Skip MCP Tool listing](#skip-mcp-tool-listing) -- [Hayhooks as an OpenAPI Tool Server in `open-webui`](#hayhooks-as-an-openapi-tool-server-in-open-webui) - - [Example: Deploy a Haystack pipeline from `open-webui` chat interface](#example-deploy-a-haystack-pipeline-from-open-webui-chat-interface) -- [OpenAI Compatibility and `open-webui` integration](#openai-compatibility-and-open-webui-integration) - - [OpenAI-compatible endpoints generation](#openai-compatible-endpoints-generation) - - [Using Hayhooks as `open-webui` backend](#using-hayhooks-as-open-webui-backend) - - [Run Chat Completion Method](#run_chat_completion) - - [Async Run Chat Completion Method](#run_chat_completion_async) - - [Streaming Responses](#streaming-responses-in-openai-compatible-endpoints) - - [Async Streaming Generator](#async_streaming_generator) - - [Integration with Haystack OpenAIChatGenerator](#integration-with-haystack-openaichatgenerator) -- [Sending `open-webui` events enhancing the user experience](#sending-open-webui-events-enhancing-the-user-experience) -- [Hooks](#hooks) - - [Intercepting tool calls when using `open-webui` and streaming responses](#intercepting-tool-calls-when-using-open-webui-and-streaming-responses) -- [Advanced Usage](#advanced-usage) - - [Run Hayhooks Programmatically](#run-hayhooks-programmatically) - - [Sharing code between pipeline wrappers](#sharing-code-between-pipeline-wrappers) -- [Deployment Guidelines](#deployment-guidelines) -- [License](#license) +## Documentation -## Quick start with Docker Compose +> 📚 **For detailed guides, examples, and API reference, check out our [comprehensive documentation](https://deepset-ai.github.io/hayhooks/).** -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/). +## Quick Start -It's available in the [Hayhooks + Open WebUI Docker Compose repository](https://github.com/deepset-ai/hayhooks-open-webui-docker-compose). +### 1. Install Hayhooks -## Quick start - -### Install the package - -Start by installing the package: - -```shell +```bash +# Install Hayhooks pip install hayhooks ``` -If you want to use the [MCP Server](#mcp-server), you need to install the `hayhooks[mcp]` package: - -```shell -pip install hayhooks[mcp] -``` - -**NOTE: You'll need to run at least Python 3.10+ to use the MCP Server.** - -### Configuration - -Currently, you can configure Hayhooks by: - -- Set the environment variables in an `.env` file in the root of your project. -- Pass the supported arguments and options to `hayhooks run` command. -- Pass the environment variables to the `hayhooks` command. - -#### Environment variables - -The following environment variables are supported: - -- `HAYHOOKS_HOST`: The host on which the server will listen. -- `HAYHOOKS_PORT`: The port on which the server will listen. -- `HAYHOOKS_MCP_PORT`: The port on which the MCP Server will listen. -- `HAYHOOKS_MCP_HOST`: The host on which the MCP Server will listen. -- `HAYHOOKS_PIPELINES_DIR`: The path to the directory containing the pipelines. -- `HAYHOOKS_ROOT_PATH`: The root path of the server. -- `HAYHOOKS_ADDITIONAL_PYTHON_PATH`: Additional Python path to be added to the Python path. -- `HAYHOOKS_DISABLE_SSL`: Boolean flag to disable SSL verification when making requests from the CLI. -- `HAYHOOKS_USE_HTTPS`: Boolean flag to use HTTPS when using CLI commands to interact with the server (e.g. `hayhooks status` will call `https://HAYHOOKS_HOST:HAYHOOKS_PORT/status`). -- `HAYHOOKS_SHOW_TRACEBACKS`: Boolean flag to show tracebacks on errors during pipeline execution and deployment. -- `LOG`: The log level to use (default: `INFO`). - -##### CORS Settings - -- `HAYHOOKS_CORS_ALLOW_ORIGINS`: List of allowed origins (default: ["*"]) -- `HAYHOOKS_CORS_ALLOW_METHODS`: List of allowed HTTP methods (default: ["*"]) -- `HAYHOOKS_CORS_ALLOW_HEADERS`: List of allowed headers (default: ["*"]) -- `HAYHOOKS_CORS_ALLOW_CREDENTIALS`: Allow credentials (default: false) -- `HAYHOOKS_CORS_ALLOW_ORIGIN_REGEX`: Regex pattern for allowed origins (default: null) -- `HAYHOOKS_CORS_EXPOSE_HEADERS`: Headers to expose in response (default: []) -- `HAYHOOKS_CORS_MAX_AGE`: Maximum age for CORS preflight responses in seconds (default: 600) - -### Logging - -#### Using the logger - -Hayhooks comes with a default logger based on [loguru](https://loguru.readthedocs.io/en/stable/). - -To use it, you can import the `log` object from the `hayhooks` package: - -```python -from hayhooks import log -``` - -#### Changing the log level - -To change the log level, you can set the `LOG` environment variable [to one of the levels supported by loguru](https://loguru.readthedocs.io/en/stable/api/logger.html). - -For example, to use the `DEBUG` level, you can set: - -```shell -LOG=DEBUG hayhooks run - -# or -LOG=debug hayhooks run - -# or in an .env file -LOG=debug -``` - -### CLI commands - -The `hayhooks` package provides a CLI to manage the server and the pipelines. -Any command can be run with `hayhooks --help` to get more information. - -CLI commands are basically wrappers around the HTTP API of the server. The full API reference is available at [//HAYHOOKS_HOST:HAYHOOKS_PORT/docs](http://HAYHOOKS_HOST:HAYHOOKS_PORT/docs) or [//HAYHOOKS_HOST:HAYHOOKS_PORT/redoc](http://HAYHOOKS_HOST:HAYHOOKS_PORT/redoc). - -```shell -hayhooks run # Start the server -hayhooks status # Check the status of the server and show deployed pipelines +### 2. Start Hayhooks -hayhooks pipeline deploy-files # Deploy a pipeline using PipelineWrapper files (preferred) -hayhooks pipeline deploy-yaml # Deploy a pipeline from a YAML file -hayhooks pipeline undeploy # Undeploy a pipeline -hayhooks pipeline run # Run a pipeline -``` - -### Start Hayhooks - -Let's start Hayhooks: - -```shell +```bash hayhooks run ``` -This will start the Hayhooks server on `HAYHOOKS_HOST:HAYHOOKS_PORT`. - -### Deploy a Pipeline - -Now, we will deploy a pipeline to chat with a website. We have created an example in the [examples/pipeline_wrappers/chat_with_website_streaming](examples/pipeline_wrappers/chat_with_website_streaming) folder. +### 3. Create a simple agent -In the example folder, we have two files: - -- `chat_with_website.yml`: The pipeline definition in YAML format. -- `pipeline_wrapper.py` (mandatory): A pipeline wrapper that uses the pipeline definition. - -#### Why a pipeline wrapper? - -The pipeline wrapper provides a flexible foundation for deploying Haystack pipelines, agents or any other component by allowing users to: - -- Choose their preferred initialization method (YAML files, Haystack templates, or inline code) -- Define custom execution logic with configurable inputs and outputs -- Optionally expose OpenAI-compatible chat endpoints with streaming support for integration with interfaces like [open-webui](https://openwebui.com/) - -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). - -A minimal `PipelineWrapper` looks like this: - -```python -from pathlib import Path -from typing import List -from haystack import Pipeline -from hayhooks import BasePipelineWrapper - -class PipelineWrapper(BasePipelineWrapper): - def setup(self) -> None: - pipeline_yaml = (Path(__file__).parent / "chat_with_website.yml").read_text() - self.pipeline = Pipeline.loads(pipeline_yaml) - - def run_api(self, urls: List[str], question: str) -> str: - result = self.pipeline.run({"fetcher": {"urls": urls}, "prompt": {"query": question}}) - return result["llm"]["replies"][0] -``` - -It contains two methods: - -#### setup() - -This method will be called when the pipeline is deployed. It should initialize the `self.pipeline` attribute as a Haystack pipeline. - -You can initialize the pipeline in many ways: - -- Load it from a YAML file. -- Define it inline as a Haystack pipeline code. -- Load it from a [Haystack pipeline template](https://docs.haystack.deepset.ai/docs/pipeline-templates). - -#### run_api(...) - -This method will be used to run the pipeline in API mode, when you call the `{pipeline_name}/run` endpoint. - -**You can define the input arguments of the method according to your needs**. - -```python -def run_api(self, urls: List[str], question: str, any_other_user_defined_argument: Any) -> str: - ... -``` - -The input arguments will be used to generate a Pydantic model that will be used to validate the request body. The same will be done for the response type. - -**NOTE**: Since Hayhooks will _dynamically_ create the Pydantic models, you need to make sure that the input arguments are JSON-serializable. - -#### run_api_async(...) - -This method is the asynchronous version of `run_api`. It will be used to run the pipeline in API mode when you call the `{pipeline_name}/run` endpoint, but handles requests asynchronously for better performance under high load. - -**You can define the input arguments of the method according to your needs**, just like with `run_api`. - -```python -async def run_api_async(self, urls: List[str], question: str, any_other_user_defined_argument: Any) -> str: - # Use async/await with AsyncPipeline or async operations - result = await self.pipeline.run_async({"fetcher": {"urls": urls}, "prompt": {"query": question}}) - return result["llm"]["replies"][0] -``` - -This is particularly useful when: - -- Working with `AsyncPipeline` instances that support async execution -- Integrating with async-compatible Haystack components (e.g., `OpenAIChatGenerator` with async support) -- Handling I/O-bound operations more efficiently -- Deploying pipelines that need to handle many concurrent requests - -**NOTE**: You can implement either `run_api`, `run_api_async`, or both. Hayhooks will automatically detect which methods are implemented and route requests accordingly. - -You can find complete working examples of async pipeline wrappers in the [test files](tests/test_files/files/async_question_answer) and [async streaming examples](tests/test_files/files/async_chat_with_website_streaming). - -To deploy the pipeline, run: - -```shell -hayhooks pipeline deploy-files -n chat_with_website examples/pipeline_wrappers/chat_with_website_streaming -``` - -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. - -Alternatively, you can deploy via HTTP: `POST /deploy_files`. - -#### PipelineWrapper development with `overwrite` option - -During development, you can use the `--overwrite` flag to redeploy your pipeline without restarting the Hayhooks server: - -```shell -hayhooks pipeline deploy-files -n {pipeline_name} --overwrite {pipeline_dir} -``` - -This is particularly useful when: - -- Iterating on your pipeline wrapper implementation -- Debugging pipeline setup issues -- Testing different pipeline configurations - -The `--overwrite` flag will: - -1. Remove the existing pipeline from the registry -2. Delete the pipeline files from disk -3. Deploy the new version of your pipeline - -For even faster development iterations, you can combine `--overwrite` with `--skip-saving-files` to avoid writing files to disk: - -```shell -hayhooks pipeline deploy-files -n {pipeline_name} --overwrite --skip-saving-files {pipeline_dir} -``` - -This is useful when: - -- You're making frequent changes during development -- You want to test a pipeline without persisting it -- You're running in an environment with limited disk access - -#### Additional dependencies - -After installing the Hayhooks package, it might happen that during pipeline deployment you need to install additional dependencies in order to correctly initialize the pipeline instance when calling the wrapper's `setup()` method. For instance, the `chat_with_website` pipeline requires the `trafilatura` package, which is **not installed by default**. - -⚠️ Sometimes you may need to enable tracebacks in hayhooks to see the full error message. You can do this by setting the `HAYHOOKS_SHOW_TRACEBACKS` environment variable to `true` or `1`. - -Then, assuming you've installed the Hayhooks package in a virtual environment, you will need to install the additional required dependencies yourself by running: - -```shell -pip install trafilatura -``` - -## Deploy a YAML Pipeline - -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`. - -Note: You can also deploy YAML pipelines from the CLI with `hayhooks pipeline deploy-yaml`. Wrapper-based deployments continue to use `/deploy_files`. - -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. - -Requirements: - -- 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_. -- `inputs`/`outputs` entries map friendly names to pipeline component fields (e.g. `fetcher.urls`, `prompt.query`). - -Minimal example: - -```yaml -# ... pipeline definition ... - -inputs: - urls: - - fetcher.urls - query: - - prompt.query -outputs: - replies: llm.replies -``` - -CLI: - -```shell -hayhooks pipeline deploy-yaml -n inputs_outputs_pipeline --description "My pipeline" pipelines/inputs_outputs_pipeline.yml -``` - -Alternatively, you can deploy via HTTP: `POST /deploy-yaml`. - -If successful, the server exposes a run endpoint at `/{name}/run` with a request/response schema derived from the YAML IO. For example: - -```shell -curl -X POST \ - http://HAYHOOKS_HOST:HAYHOOKS_PORT/inputs_outputs_pipeline/run \ - -H 'Content-Type: application/json' \ - -d '{"urls": ["https://haystack.deepset.ai"], "query": "What is Haystack?"}' -``` - -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. - -Limitations: - -- 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). - -Available CLI options for `hayhooks pipeline deploy-yaml`: - -- `--name, -n`: override the pipeline name (default: YAML file stem) -- `--description`: optional human-readable description (used in MCP tool listing) -- `--overwrite, -o`: overwrite if the pipeline already exists -- `--skip-mcp`: skip exposing this pipeline as an MCP Tool -- `--save-file/--no-save-file`: save the YAML under `pipelines/{name}.yml` on the server (default: `--save-file`) - -## Deploy an Agent - -Deploying a [Haystack Agent](https://docs.haystack.deepset.ai/docs/agents) is very similar to deploying a pipeline. - -You simply need to create a `PipelineWrapper` which will wrap the Haystack Agent instance. The following example is the bare minimum to deploy an agent and make it usable through `open-webui`, supporting streaming responses: +Create a minimal agent wrapper with streaming chat support and a simple HTTP POST API: ```python from typing import AsyncGenerator from haystack.components.agents import Agent from haystack.dataclasses import ChatMessage +from haystack.tools import Tool from haystack.components.generators.chat import OpenAIChatGenerator from hayhooks import BasePipelineWrapper, async_streaming_generator +# Define a Haystack Tool that provides weather information for a given location. +def weather_function(location): + return f"The weather in {location} is sunny." + +weather_tool = Tool( + name="weather_tool", + description="Provides weather information for a given location.", + parameters={ + "type": "object", + "properties": {"location": {"type": "string"}}, + "required": ["location"], + }, + function=weather_function, +) + class PipelineWrapper(BasePipelineWrapper): def setup(self) -> None: self.agent = Agent( chat_generator=OpenAIChatGenerator(model="gpt-4o-mini"), system_prompt="You're a helpful agent", + tools=[weather_tool], ) + # This will create a POST /my_agent/run endpoint + # `question` will be the input argument and will be auto-validated by a Pydantic model + async def run_api_async(self, question: str) -> str: + result = await self.agent.run_async({"messages": [ChatMessage.from_user(question)]}) + return result["replies"][0].text + + # This will create an OpenAI-compatible /chat/completions endpoint async def run_chat_completion_async( self, model: str, messages: list[dict], body: dict ) -> AsyncGenerator[str, None]: @@ -415,704 +91,74 @@ class PipelineWrapper(BasePipelineWrapper): ) ``` -As you can see, the `run_chat_completion_async` method is the one that will be used to run the agent. You can of course implement also `run_api` or `run_api_async` methods if you need to. - -The `async_streaming_generator` function is a utility function that [will handle the streaming of the agent's responses](#async_streaming_generator). - -## Load pipelines or agents at startup - -Hayhooks can automatically deploy pipelines or agents on startup by scanning a pipelines directory. - -- Set `HAYHOOKS_PIPELINES_DIR` (defaults to `./pipelines`). -- On startup, Hayhooks will: - - Deploy every YAML file at the directory root (`*.yml`/`*.yaml`) using the file name as the pipeline name. - - Deploy every immediate subfolder as a wrapper-based pipeline/agent if it contains a `pipeline_wrapper.py`. - -Example layout: - -```text -my-project/ -├── .env -└── pipelines/ - ├── inputs_outputs_pipeline.yml # YAML-only pipeline -> POST /inputs_outputs_pipeline/run - ├── chat_with_website/ # Wrapper-based pipeline -> POST /chat_with_website/run (+ chat endpoints if implemented) - │ ├── pipeline_wrapper.py - │ └── chat_with_website.yml - └── agent_streaming/ - └── pipeline_wrapper.py -``` - -Configure via environment or `.env`: - -```shell -# .env -HAYHOOKS_PIPELINES_DIR=./pipelines -``` - -Notes: - -- 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`. -- If your wrappers import shared code, set `HAYHOOKS_ADDITIONAL_PYTHON_PATH` (see “Sharing code between pipeline wrappers”). - -## Support file uploads - -Hayhooks can easily handle uploaded files in your pipeline wrapper `run_api` method by adding `files: Optional[List[UploadFile]] = None` as an argument. - -Here's a simple example: - -```python -def run_api(self, files: Optional[List[UploadFile]] = None) -> str: - if files and len(files) > 0: - filenames = [f.filename for f in files if f.filename is not None] - file_contents = [f.file.read() for f in files] - - return f"Received files: {', '.join(filenames)}" - - return "No files received" -``` - -This will make Hayhooks handle automatically the file uploads (if they are present) and pass them to the `run_api` method. -This also means that the HTTP request **needs to be a `multipart/form-data` request**. +Save as `my_agent_dir/pipeline_wrapper.py`. -Note also that you can handle **both files and parameters in the same request**, simply adding them as arguments to the `run_api` method. +### 4. Deploy it -```python -def run_api(self, files: Optional[List[UploadFile]] = None, additional_param: str = "default") -> str: - ... +```bash +hayhooks pipeline deploy-files -n my_agent ./my_agent_dir ``` -You can find a full example in the [examples/rag_indexing_query](examples/rag_indexing_query) folder. - -## Run pipelines from the CLI - -### Run a pipeline from the CLI JSON-compatible parameters - -You can run a pipeline by using the `hayhooks pipeline run` command. Under the hood, this will call the `run_api` method of the pipeline wrapper, passing parameters as the JSON body of the request. -This is convenient when you want to do a test run of the deployed pipeline from the CLI without having to write any code. - -To run a pipeline from the CLI, you can use the following command: - -```shell -hayhooks pipeline run --param 'question="is this recipe vegan?"' -``` - -### Run a pipeline from the CLI uploading files - -This is useful when you want to run a pipeline that requires a file as input. In that case, the request will be a `multipart/form-data` request. You can pass both files and parameters in the same request. - -**NOTE**: To use this feature, you need to deploy a pipeline which is handling files (see [Support file uploads](#support-file-uploads) and [examples/rag_indexing_query](examples/rag_indexing_query) for more details). - -```shell -# Upload a whole directory -hayhooks pipeline run --dir files_to_index - -# Upload a single file -hayhooks pipeline run --file file.pdf - -# Upload multiple files -hayhooks pipeline run --dir files_to_index --file file1.pdf --file file2.pdf - -# Upload a single file passing also a parameter -hayhooks pipeline run --file file.pdf --param 'question="is this recipe vegan?"' -``` - -## MCP support - -**NOTE: You'll need to run at least Python 3.10+ to use the MCP Server.** - -### MCP Server - -Hayhooks now supports the [Model Context Protocol](https://modelcontextprotocol.io/) and can act as a [MCP Server](https://modelcontextprotocol.io/docs/concepts/architecture). - -It will: - -- Expose [Core Tools](#using-hayhooks-core-mcp-tools-in-ides-like-cursor) to make it possible to control Hayhooks directly from an IDE like [Cursor](https://www.cursor.com/) or any other MCP client. -- Expose the deployed Haystack pipelines as usable [MCP Tools](https://modelcontextprotocol.io/docs/concepts/tools), using both [Server-Sent Events (SSE)](https://modelcontextprotocol.io/docs/concepts/transports#server-sent-events-sse) and (stateless) [Streamable HTTP](https://modelcontextprotocol.io/docs/concepts/transports#streamable-http) MCP transports. - -(Note that **SSE transport is deprecated** and it's maintained only for backward compatibility). - -To run the Hayhooks MCP Server, you can use the following command: - -```shell -hayhooks mcp run - -# Hint: check --help to see all the available options -``` - -This will start the Hayhooks MCP Server on `HAYHOOKS_MCP_HOST:HAYHOOKS_MCP_PORT`. - -### Expose a YAML pipeline as a MCP Tool - -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: - -- Builds flat request/response models from YAML-declared `inputs` and `outputs`. -- Registers the pipeline as an `AsyncPipeline` and adds it to the registry with metadata required for MCP Tools. -- Lists it in MCP `list_tools()` with: - - `name`: the pipeline name (YAML file stem or provided `--name`) - - `description`: the optional description you pass during deployment (defaults to the pipeline name) - - `inputSchema`: JSON schema derived from YAML `inputs` - -Calling a YAML pipeline via MCP `call_tool` executes the pipeline asynchronously and returns the pipeline result as a JSON string in `TextContent`. - -Sample YAML for a simple `sum` pipeline using only the `haystack.testing.sample_components.sum.Sum` component: - -```yaml -components: - sum: - init_parameters: {} - type: haystack.testing.sample_components.sum.Sum - -connections: [] - -metadata: {} - -inputs: - values: sum.values - -outputs: - total: sum.total -``` - -Example (Streamable HTTP via MCP client): - -```python -tools = await client.list_tools() -# Find YAML tool by name, e.g., "sum" (the pipeline name) -result = await client.call_tool("sum", {"values": [1, 2, 3]}) -assert result.content[0].text == '{"total": 6}' -``` - -Notes and limitations: - -- YAML pipelines must declare `inputs` and `outputs`. -- 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`. - -### Create a PipelineWrapper for exposing a Haystack pipeline as a MCP Tool - -A [MCP Tool](https://modelcontextprotocol.io/docs/concepts/tools) requires the following properties: - -- `name`: The name of the tool. -- `description`: The description of the tool. -- `inputSchema`: A JSON Schema object describing the tool's input parameters. - -For each deployed pipeline, Hayhooks will: +### 5. Run it -- Use the pipeline wrapper `name` as MCP Tool `name` (always present). -- Parse **`run_api` method docstring**: - - If you use Google-style or reStructuredText-style docstrings, use the first line as MCP Tool `description` and the rest as `parameters` (if present). - - Each parameter description will be used as the `description` of the corresponding Pydantic model field (if present). -- Generate a Pydantic model from the `inputSchema` using the **`run_api` method arguments as fields**. +Call the HTTP POST API (`/my_agent/run`): -Here's an example of a PipelineWrapper implementation for the `chat_with_website` pipeline which can be used as a MCP Tool: - -```python -from pathlib import Path -from typing import List -from haystack import Pipeline -from hayhooks import BasePipelineWrapper - - -class PipelineWrapper(BasePipelineWrapper): - def setup(self) -> None: - pipeline_yaml = (Path(__file__).parent / "chat_with_website.yml").read_text() - self.pipeline = Pipeline.loads(pipeline_yaml) - - def run_api(self, urls: List[str], question: str) -> str: - # - # NOTE: The following docstring will be used as MCP Tool description - # - """ - Ask a question about one or more websites using a Haystack pipeline. - """ - result = self.pipeline.run({"fetcher": {"urls": urls}, "prompt": {"query": question}}) - return result["llm"]["replies"][0] -``` - -### Skip MCP Tool listing - -You can skip the MCP Tool listing by setting the `skip_mcp` class attribute to `True` in your `PipelineWrapper` class. -This way, the pipeline will be deployed on Hayhooks but **will not be listed as a MCP Tool** when you run the `hayhooks mcp run` command. - -```python -class PipelineWrapper(BasePipelineWrapper): - # This will skip the MCP Tool listing - skip_mcp = True - - def setup(self) -> None: - ... - - def run_api(self, urls: List[str], question: str) -> str: - ... -``` - -### Using Hayhooks MCP Server with Claude Desktop - -As stated in [Anthropic's documentation](https://support.anthropic.com/en/articles/11503834-building-custom-integrations-via-remote-mcp-servers), Claude Desktop supports SSE and Streamable HTTP as MCP Transports only on "Claude.ai & Claude for Desktop for the Pro, Max, Teams, and Enterprise tiers". - -If you are using the _free_ tier, only STDIO transport is supported, so you need to use [supergateway](https://github.com/supercorp-ai/supergateway) to connect to the Hayhooks MCP Server via **SSE or Streamable HTTP**. - -After starting the Hayhooks MCP Server, open **Settings → Developer** in Claude Desktop and update the config file with the following examples: - -#### Using supergateway to bridge Streamable HTTP transport - -```json -{ - "mcpServers": { - "hayhooks": { - "command": "npx", - "args": [ - "-y", - "supergateway", - "--streamableHttp", - "http://HAYHOOKS_MCP_HOST:HAYHOOKS_MCP_PORT/mcp" - ] - } - } -} -``` - -#### Using supergateway to bridge SSE transport - -```json -{ - "mcpServers": { - "hayhooks": { - "command": "npx", - "args": [ - "-y", - "supergateway", - "--sse", - "http://HAYHOOKS_MCP_HOST:HAYHOOKS_MCP_PORT/sse" - ] - } - } -} -``` - -Make sure [Node.js](https://nodejs.org/) is installed, as the `npx` command depends on it. - -### Using Hayhooks Core MCP Tools in IDEs like Cursor - -Since Hayhooks MCP Server provides by default a set of **Core MCP Tools**, the MCP server will enable one to interact with Hayhooks in an agentic manner using IDEs like [Cursor](https://www.cursor.com/). - -The exposed tools are: - -- `get_all_pipeline_statuses`: Get the status of all pipelines and list available pipeline names. -- `get_pipeline_status`: Get status of a specific pipeline. Requires `pipeline_name` as an argument. -- `undeploy_pipeline`: Undeploy a pipeline. Removes a pipeline from the registry, its API routes, and deletes its files. Requires `pipeline_name` as an argument. -- `deploy_pipeline`: Deploy a pipeline from files (`pipeline_wrapper.py` and other files). Requires `name` (pipeline name), `files` (list of file contents), `save_files` (boolean), and `overwrite` (boolean) as arguments. - -From `Cursor Settings -> MCP`, you can add a new **MCP Server** by specifying the following parameters (assuming you have Hayhooks MCP Server running on `http://localhost:1417` with Streamable HTTP transport): - -```json -{ - "mcpServers": { - "hayhooks": { - "url": "http://localhost:1417/mcp" - } - } -} -``` - -Or if you need to use the SSE transport: - -```json -{ - "mcpServers": { - "hayhooks": { - "url": "http://localhost:1417/sse" - } - } -} -``` - -After adding the MCP Server, you should see the Hayhooks Core MCP Tools in the list of available tools: - -![cursor-mcp-settings](./docs/assets/cursor-mcp-settings.png) - -Now in the Cursor chat interface you can use the Hayhooks Core MCP Tools by mentioning them in your messages. - -### Development and deployment of Haystack pipelines directly from Cursor - -Here's a video example of how to develop and deploy a Haystack pipeline directly from Cursor: - -![hayhooks-cursor-dev-deploy-overwrite.gif](./docs/assets/hayhooks-cursor-dev-deploy-overwrite.gif) - -## Hayhooks as an OpenAPI Tool Server in `open-webui` - -Since Hayhooks expose openapi-schema at `/openapi.json`, it can be used as an OpenAPI Tool Server. - -[open-webui](https://openwebui.com) has recently added support for [OpenAPI Tool Servers](https://docs.openwebui.com/openapi-servers), meaning that you can use the API endpoints of Hayhooks as tools in your chat interface. - -You simply need to configure the OpenAPI Tool Server in the `Settings -> Tools` section, adding the URL of the Hayhooks server and the path to the `openapi.json` file: - -![open-webui-settings](./docs/assets/open-webui-openapi-tools.png) - -### Example: Deploy a Haystack pipeline from `open-webui` chat interface - -Here's a video example of how to deploy a Haystack pipeline from the `open-webui` chat interface: - -![open-webui-deploy-pipeline-from-chat-example](./docs/assets/open-webui-deploy-pipeline-from-chat.gif) - -## OpenAI compatibility and `open-webui` integration - -### OpenAI-compatible endpoints generation - -Hayhooks now can automatically generate OpenAI-compatible endpoints if you implement the `run_chat_completion` method in your pipeline wrapper. - -This will make Hayhooks compatible with fully-featured chat interfaces like [open-webui](https://openwebui.com/), so you can use it as a backend for your chat interface. - -### Using Hayhooks as `open-webui` backend - -Requirements: - -- Ensure you have [open-webui](https://openwebui.com/) up and running (you can do it easily using `docker`, check [their quick start guide](https://docs.openwebui.com/getting-started/quick-start)). -- Ensure you have Hayhooks server running somewhere. We will run it locally on `http://localhost:1416`. - -#### Configuring `open-webui` - -First, you need to **turn off `tags`, `title` and `follow-up` generation from `Admin settings -> Interface`**: - -![open-webui-settings](./docs/assets/open-webui-settings.png) - -This is needed to avoid `open-webui` to make calls to your deployed pipelines or agents asking for generating tags, title and follow-up messages (they may be not suited for this use case). Of course, if you want to use them, you can leave them enabled. - -Then you have two options to connect Hayhooks as a backend. - -Add a **Direct Connection** from `Settings -> Connections`: - -NOTE: **Fill a random value as API key as it's not needed** - -![open-webui-settings-connections](./docs/assets/open-webui-settings-connections.png) - -Alternatively, you can add an additional **OpenAI API Connections** from `Admin settings -> Connections`: - -![open-webui-admin-settings-connections](./docs/assets/open-webui-admin-settings-connections.png) - -Even in this case, remember to **Fill a random value as API key**. - -#### run_chat_completion(...) - -To enable the automatic generation of OpenAI-compatible endpoints, you need only to implement the `run_chat_completion` method in your pipeline wrapper. - -```python -def run_chat_completion(self, model: str, messages: List[dict], body: dict) -> Union[str, Generator]: - ... -``` - -Let's update the previous example to add a streaming response: - -```python -from pathlib import Path -from typing import Generator, List, Union -from haystack import Pipeline -from hayhooks import get_last_user_message, BasePipelineWrapper, log - - -URLS = ["https://haystack.deepset.ai", "https://www.redis.io", "https://ssi.inc"] - - -class PipelineWrapper(BasePipelineWrapper): - def setup(self) -> None: - ... # Same as before - - def run_api(self, urls: List[str], question: str) -> str: - ... # Same as before - - def run_chat_completion(self, model: str, messages: List[dict], body: dict) -> Union[str, Generator]: - log.trace(f"Running pipeline with model: {model}, messages: {messages}, body: {body}") - - question = get_last_user_message(messages) - log.trace(f"Question: {question}") - - # Plain pipeline run, will return a string - result = self.pipeline.run({"fetcher": {"urls": URLS}, "prompt": {"query": question}}) - return result["llm"]["replies"][0] -``` - -Differently from the `run_api` method, the `run_chat_completion` has a **fixed signature** and will be called with the arguments specified in the OpenAI-compatible endpoint. - -- `model`: The `name` of the Haystack pipeline which is called. -- `messages`: The list of messages from the chat in the OpenAI format. -- `body`: The full body of the request. - -Some notes: - -- Since we have only the user messages as input here, the `question` is extracted from the last user message and the `urls` argument is hardcoded. -- In this example, the `run_chat_completion` method is returning a string, so the `open-webui` will receive a string as response and show the pipeline output in the chat all at once. -- The `body` argument contains the full request body, which may be used to extract more information like the `temperature` or the `max_tokens` (see the [OpenAI API reference](https://platform.openai.com/docs/api-reference/chat/create) for more information). - -Finally, to use non-streaming responses in `open-webui` you need also to turn off `Stream Chat Response` chat settings. - -Here's a video example: - -![chat-completion-example](./docs/assets/chat-completion.gif) - -#### run_chat_completion_async(...) - -This method is the asynchronous version of `run_chat_completion`. It handles OpenAI-compatible chat completion requests asynchronously, which is particularly useful for streaming responses and high-concurrency scenarios. - -```python -from hayhooks import async_streaming_generator, get_last_user_message, log - -async def run_chat_completion_async(self, model: str, messages: List[dict], body: dict) -> Union[str, AsyncGenerator]: - log.trace(f"Running pipeline with model: {model}, messages: {messages}, body: {body}") - - question = get_last_user_message(messages) - log.trace(f"Question: {question}") - - # For async streaming responses - return async_streaming_generator( - pipeline=self.pipeline, - pipeline_run_args={"fetcher": {"urls": URLS}, "prompt": {"query": question}}, - ) -``` - -Like `run_chat_completion`, this method has a **fixed signature** and will be called with the same arguments. The key differences are: - -- It's declared as `async` and can use `await` for asynchronous operations -- It can return an `AsyncGenerator` for streaming responses using `async_streaming_generator` -- It provides better performance for concurrent chat requests -- It's required when using async streaming with components that support async streaming callbacks - -**NOTE**: You can implement either `run_chat_completion`, `run_chat_completion_async`, or both. When both are implemented, Hayhooks will prefer the async version for better performance. - -You can find complete working examples combining async chat completion with streaming in the [async streaming test examples](tests/test_files/files/async_question_answer). - -### Streaming responses in OpenAI-compatible endpoints - -Hayhooks provides `streaming_generator` and `async_streaming_generator` utility functions that can be used to stream the pipeline output to the client. - -Let's update the `run_chat_completion` method of the previous example: - -```python -from pathlib import Path -from typing import Generator, List, Union -from haystack import Pipeline -from hayhooks import get_last_user_message, BasePipelineWrapper, log, streaming_generator - - -URLS = ["https://haystack.deepset.ai", "https://www.redis.io", "https://ssi.inc"] - - -class PipelineWrapper(BasePipelineWrapper): - def setup(self) -> None: - ... # Same as before - - def run_api(self, urls: List[str], question: str) -> str: - ... # Same as before - - def run_chat_completion(self, model: str, messages: List[dict], body: dict) -> Union[str, Generator]: - log.trace(f"Running pipeline with model: {model}, messages: {messages}, body: {body}") - - question = get_last_user_message(messages) - log.trace(f"Question: {question}") - - # Streaming pipeline run, will return a generator - return streaming_generator( - pipeline=self.pipeline, - pipeline_run_args={"fetcher": {"urls": URLS}, "prompt": {"query": question}}, - ) -``` - -Now, if you run the pipeline and call one of the following endpoints: - -- `{pipeline_name}/chat` -- `/chat/completions` -- `/v1/chat/completions` - -You will see the pipeline output being streamed [in OpenAI-compatible format](https://platform.openai.com/docs/api-reference/chat/streaming) to the client and you'll be able to see the output in chunks. - -Since output will be streamed to `open-webui` there's **no need to change `Stream Chat Response`** chat setting (leave it as `Default` or `On`). - -You can find a complete working example of `streaming_generator` usage in the [examples/pipeline_wrappers/chat_with_website_streaming](examples/pipeline_wrappers/chat_with_website_streaming) directory. - -Here's a video example: - -![chat-completion-streaming-example](./docs/assets/chat-completion-streaming.gif) - -#### async_streaming_generator - -For asynchronous pipelines or agents, Hayhooks also provides an `async_streaming_generator` utility function: - -```python -from pathlib import Path -from typing import AsyncGenerator, List, Union -from haystack import AsyncPipeline -from hayhooks import get_last_user_message, BasePipelineWrapper, log, async_streaming_generator - - -URLS = ["https://haystack.deepset.ai", "https://www.redis.io", "https://ssi.inc"] - - -class PipelineWrapper(BasePipelineWrapper): - def setup(self) -> None: - pipeline_yaml = (Path(__file__).parent / "chat_with_website.yml").read_text() - self.pipeline = AsyncPipeline.loads(pipeline_yaml) # Note: AsyncPipeline - - async def run_chat_completion_async(self, model: str, messages: List[dict], body: dict) -> AsyncGenerator: - log.trace(f"Running pipeline with model: {model}, messages: {messages}, body: {body}") - - question = get_last_user_message(messages) - log.trace(f"Question: {question}") - - # Async streaming pipeline run, will return an async generator - return async_streaming_generator( - pipeline=self.pipeline, - pipeline_run_args={"fetcher": {"urls": URLS}, "prompt": {"query": question}}, - ) -``` - -The `async_streaming_generator` function: - -- Works with both `Pipeline` and `AsyncPipeline` instances -- Requires **components that support async streaming callbacks** (e.g., `OpenAIChatGenerator` instead of `OpenAIGenerator`) -- Provides better performance for concurrent streaming requests -- Returns an `AsyncGenerator` that yields chunks asynchronously -- Automatically handles async pipeline execution and cleanup - -**NOTE**: The streaming component in your pipeline must support async streaming callbacks. If you get an error about async streaming support, either use the sync `streaming_generator` or switch to async-compatible components. - -### Integration with haystack OpenAIChatGenerator - -Since Hayhooks is OpenAI-compatible, it can be used as a backend for the [haystack OpenAIChatGenerator](https://docs.haystack.deepset.ai/docs/openaichatgenerator). - -Assuming you have a Haystack pipeline named `chat_with_website_streaming` and you have deployed it using Hayhooks, here's an example script of how to use it with the `OpenAIChatGenerator`: - -```python -from haystack.components.generators.chat.openai import OpenAIChatGenerator -from haystack.utils import Secret -from haystack.dataclasses import ChatMessage -from haystack.components.generators.utils import print_streaming_chunk - -client = OpenAIChatGenerator( - model="chat_with_website_streaming", - api_key=Secret.from_token("not-relevant"), # This is not used, you can set it to anything - api_base_url="http://localhost:1416/v1/", - streaming_callback=print_streaming_chunk, -) - -client.run([ChatMessage.from_user("Where are the offices or SSI?")]) -# > The offices of Safe Superintelligence Inc. (SSI) are located in Palo Alto, California, and Tel Aviv, Israel. - -# > {'replies': [ChatMessage(_role=, _content=[TextContent(text='The offices of Safe >Superintelligence Inc. (SSI) are located in Palo Alto, California, and Tel Aviv, Israel.')], _name=None, _meta={'model': >'chat_with_website_streaming', 'index': 0, 'finish_reason': 'stop', 'completion_start_time': '2025-02-11T15:31:44.599726', >'usage': {}})]} +```bash +curl -X POST http://localhost:1416/my_agent/run \ + -H 'Content-Type: application/json' \ + -d '{"question": "What can you do?"}' ``` -## Sending `open-webui` events enhancing the user experience - -Hayhooks provides support to [some `open-webui` events](https://docs.openwebui.com/features/plugin/events/) to enhance the user experience. - -The idea is to enhance the user experience by sending events to the client before, after or when the pipeline is running. - -You can use those events to: - -- 🔄 Show a loading spinner -- 💬 Update the chat messages -- 🍞 Show a toast notification - -You can find a complete example in the [examples/pipeline_wrappers/open_webui_agent_events](examples/pipeline_wrappers/open_webui_agent_events) folder. - -Here's a preview: - -![open-webui-hayhooks-events](./docs/assets/open-webui-hayhooks-events.gif) - -## Hooks - -### Intercepting tool calls when using `open-webui` and streaming responses - -When using `open-webui` and streaming responses, both `streaming_generator` and `async_streaming_generator` provide hooks to intercept tool calls. +Call the OpenAI-compatible chat completion API (streaming enabled): -The hooks (parameters of `streaming_generator` and `async_streaming_generator`) are: - -- `on_tool_call_start`: Called when a tool call starts. It receives the following arguments: - - `tool_name`: The name of the tool that is being called. - - `arguments`: The arguments passed to the tool. - - `id`: The id of the tool call. - -- `on_tool_call_end`: Called when a tool call ends. It receives the following arguments: - - `tool_name`: The name of the tool that is being called. - - `arguments`: The arguments passed to the tool. - - `result`: The result of the tool call. - - `error`: Whether the tool call ended with an error. - -You can find a complete example in the [examples/pipeline_wrappers/open_webui_agent_on_tool_calls](examples/pipeline_wrappers/open_webui_agent_on_tool_calls) folder. - -Here's a preview: - -![open-webui-hayhooks-agent-on-tool-calls](./docs/assets/open-webui-hayhooks-agent-on-tool-calls.gif) - -## Advanced usage - -### Run Hayhooks programmatically - -A Hayhooks app instance can be programmatically created by using the `create_app` function. This is useful if you want to add custom routes or middleware to Hayhooks. - -Here's an example script: - -```python -import uvicorn -from hayhooks.settings import settings -from fastapi import Request -from hayhooks import create_app - -# Create the Hayhooks app -hayhooks = create_app() - - -# Add a custom route -@hayhooks.get("/custom") -async def custom_route(): - return {"message": "Hi, this is a custom route!"} - - -# Add a custom middleware -@hayhooks.middleware("http") -async def custom_middleware(request: Request, call_next): - response = await call_next(request) - response.headers["X-Custom-Header"] = "custom-header-value" - return response - - -if __name__ == "__main__": - uvicorn.run("app:hayhooks", host=settings.host, port=settings.port) +```bash +curl -X POST http://localhost:1416/chat/completions \ + -H 'Content-Type: application/json' \ + -d '{ + "model": "my_agent", + "messages": [{"role": "user", "content": "What can you do?"}] + }' ``` -### Sharing code between pipeline wrappers - -Hayhooks allows you to use your custom code in your pipeline wrappers adding a specific path to the Hayhooks Python Path. +Or [integrate it with Open WebUI](features/openai-compatibility.md#open-webui-integration) and start chatting with it! -You can do this in three ways: +## Key Features -1. Set the `HAYHOOKS_ADDITIONAL_PYTHON_PATH` environment variable to the path of the folder containing your custom code. -2. Add `HAYHOOKS_ADDITIONAL_PYTHON_PATH` to the `.env` file. -3. Use the `--additional-python-path` flag when launching Hayhooks. +### 🚀 Easy Deployment -For example, if you have a folder called `common` with a `my_custom_lib.py` module which contains the `my_function` function, you can deploy your pipelines by using the following command: +- Deploy Haystack pipelines and agents as REST APIs with minimal setup +- Support for both YAML-based and wrapper-based pipeline deployment +- Automatic OpenAI-compatible endpoint generation -```shell -export HAYHOOKS_ADDITIONAL_PYTHON_PATH='./common' -hayhooks run -``` +### 🌐 Multiple Integration Options -Then you can use the custom code in your pipeline wrappers by importing it like this: +- **MCP Protocol**: Expose pipelines as MCP tools for use in AI development environments +- **Open WebUI Integration**: Use Hayhooks as a backend for Open WebUI with streaming support +- **OpenAI Compatibility**: Seamless integration with OpenAI-compatible tools and frameworks -```python -from my_custom_lib import my_function -``` +### 🔧 Developer Friendly -Note that you can use both absolute and relative paths (relative to the current working directory). +- CLI for easy pipeline management +- Flexible configuration options +- Comprehensive logging and debugging support +- Custom route and middleware support -You can check out a complete example in the [examples/shared_code_between_wrappers](examples/shared_code_between_wrappers) folder. +### 📁 File Upload Support -### Deployment guidelines +- Built-in support for handling file uploads in pipelines +- Perfect for RAG systems and document processing -We have some dedicated documentation for deployment: +## Next Steps -- Docker-based deployments: -- Kubernetes-based deployments: +- [Quick Start Guide](getting-started/quick-start.md) - Get started with Hayhooks +- [Installation](getting-started/installation.md) - Install Hayhooks and dependencies +- [Configuration](getting-started/configuration.md) - Configure Hayhooks for your needs +- [Examples](examples/overview.md) - Explore example implementations -We also have some additional deployment guidelines, see [deployment_guidelines.md](docs/deployment_guidelines.md). +## Community & Support -### License +- **GitHub**: [deepset-ai/hayhooks](https://github.com/deepset-ai/hayhooks) +- **Issues**: [GitHub Issues](https://github.com/deepset-ai/hayhooks/issues) +- **Documentation**: [Full Documentation](https://deepset-ai.github.io/hayhooks/) -This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details. +Hayhooks is actively maintained by the [deepset](https://deepset.ai/) team. diff --git a/docs/about/license.md b/docs/about/license.md new file mode 100644 index 00000000..fe450630 --- /dev/null +++ b/docs/about/license.md @@ -0,0 +1,21 @@ +# License + +Hayhooks is open-source software released under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). + +## Trademarks + +Haystack and the Haystack logo are trademarks of [deepset GmbH](https://www.deepset.ai/). All other trademarks are the property of their respective owners. + +## Contact + +For licensing questions or inquiries: + +- Email: +- Website: +- GitHub: + +## Additional Resources + +- [Apache License 2.0 FAQ](https://www.apache.org/foundation/license-faq.html) +- [Open Source Initiative](https://opensource.org/) +- [Software Freedom Law Center](https://www.softwarefreedom.org/) diff --git a/docs/advanced/advanced-configuration.md b/docs/advanced/advanced-configuration.md new file mode 100644 index 00000000..85da8cb4 --- /dev/null +++ b/docs/advanced/advanced-configuration.md @@ -0,0 +1,58 @@ +# Advanced Configuration + +This guide covers programmatic customization, custom routes, and middleware for advanced Hayhooks usage. + +For basic configuration, see [Configuration](../getting-started/configuration.md). For deployment and performance tuning, see [Deployment Guidelines](../deployment/deployment_guidelines.md). + +## Custom Routes and Middleware + +### When to add custom routes + +- Add specialized endpoints for application-specific logic +- Provide admin/operations endpoints (restart, status, maintenance tasks) +- Expose health checks, metrics, and webhook handlers for integrations +- Implement authentication/authorization flows +- Offer file management or other utility endpoints + +### When to add middleware + +- Apply cross-cutting concerns (logging/tracing, correlation IDs) +- Enforce security controls (authn/z, rate limiting, quotas) +- Control headers, CORS, compression, and caching +- Normalize inputs/outputs and error handling consistently + +## Programmatic Customization + +You can create a custom Hayhooks app instance to add routes or middleware: + +```python +import uvicorn +from hayhooks.settings import settings +from fastapi import Request +from hayhooks import create_app + +# Create the Hayhooks app +hayhooks = create_app() + +# Add a custom route +@hayhooks.get("/custom") +async def custom_route(): + return {"message": "Custom route"} + +# Add custom middleware +@hayhooks.middleware("http") +async def custom_middleware(request: Request, call_next): + response = await call_next(request) + response.headers["X-Custom-Header"] = "value" + return response + +if __name__ == "__main__": + uvicorn.run("app:hayhooks", host=settings.host, port=settings.port) +``` + +This allows you to build custom applications with Hayhooks as the core engine while adding your own business logic and integrations. + +## Next Steps + +- [Deployment Guidelines](../deployment/deployment_guidelines.md) - Performance tuning, workers, scaling, and deployment strategies +- [Code Sharing](code-sharing.md) - Reusable components across pipelines diff --git a/docs/advanced/code-sharing.md b/docs/advanced/code-sharing.md new file mode 100644 index 00000000..7c60a225 --- /dev/null +++ b/docs/advanced/code-sharing.md @@ -0,0 +1,41 @@ +# Code Sharing Between Wrappers + +Share common code between pipeline wrappers by adding a folder to the Hayhooks Python path. + +## Configuration + +Set `HAYHOOKS_ADDITIONAL_PYTHON_PATH` to point to your shared code directory: + +=== "Environment Variable" + ```bash + export HAYHOOKS_ADDITIONAL_PYTHON_PATH='./common' + hayhooks run + ``` + +=== ".env File" + ```bash + # .env + HAYHOOKS_ADDITIONAL_PYTHON_PATH=./common + ``` + +=== "CLI Flag" + ```bash + hayhooks run --additional-python-path ./common + ``` + +## Usage + +Once configured, import shared code in your wrappers: + +```python +# In your pipeline_wrapper.py +from my_custom_lib import sum_two_numbers + +class PipelineWrapper(BasePipelineWrapper): + def run_api(self, a: int, b: int) -> int: + return sum_two_numbers(a, b) +``` + +## Example + +See [examples/shared_code_between_wrappers](https://github.com/deepset-ai/hayhooks/tree/main/examples/shared_code_between_wrappers) for a complete working example. diff --git a/docs/advanced/running-pipelines.md b/docs/advanced/running-pipelines.md new file mode 100644 index 00000000..4f2ac4fa --- /dev/null +++ b/docs/advanced/running-pipelines.md @@ -0,0 +1,167 @@ +# Running Pipelines + +Execute deployed pipelines via CLI, HTTP API, or programmatically. + +## Quick Reference + +=== "HayhooksCLI" + + ```bash + hayhooks pipeline run my_pipeline --param 'query="What is Haystack?"' + ``` + +=== "Hayhooks HTTP API" + + ```bash + curl -X POST http://localhost:1416/my_pipeline/run \ + -H 'Content-Type: application/json' \ + -d '{"query":"What is Haystack?"}' + ``` + +=== "Python" + + ```python + import requests + + resp = requests.post( + "http://localhost:1416/my_pipeline/run", + json={"query": "What is Haystack?"} + ) + print(resp.json()) + ``` + +=== "Async Python" + + ```python + import httpx + import asyncio + + async def main(): + async with httpx.AsyncClient() as client: + r = await client.post( + "http://localhost:1416/my_pipeline/run", + json={"query": "What is Haystack?"} + ) + print(r.json()) + + asyncio.run(main()) + ``` + +=== "OpenAI Chat Completion (`curl`)" + + ```bash + curl -X POST http://localhost:1416/v1/chat/completions \ + -H 'Content-Type: application/json' \ + -d '{ + "model": "my_pipeline", + "messages": [{"role": "user", "content": "What is Haystack?"}] + }' + ``` + +=== "Open WebUI" + See [Open WebUI Integration](../features/openwebui-integration.md) for setup details. + +For more CLI examples, see [CLI Commands](../features/cli-commands.md). + +## File Uploads + +### CLI + +```bash +# Single file +hayhooks pipeline run my_pipeline --file document.pdf --param 'query="Summarize"' + +# Multiple files +hayhooks pipeline run my_pipeline --file doc1.pdf --file doc2.txt + +# Directory +hayhooks pipeline run my_pipeline --dir ./documents +``` + +### HTTP + +```bash +curl -X POST http://localhost:1416/my_pipeline/run \ + -F 'files=@document.pdf' \ + -F 'query="Summarize this document"' +``` + +See [File Upload Support](../features/file-upload-support.md) for implementation details. + +## Python Integration + +### Requests + +```python +import requests + +resp = requests.post( + "http://localhost:1416/my_pipeline/run", + json={"query": "What is Haystack?"} +) +print(resp.json()) +``` + +### Async (httpx) + +```python +import httpx +import asyncio + +async def main(): + async with httpx.AsyncClient() as client: + r = await client.post( + "http://localhost:1416/my_pipeline/run", + json={"query": "What is Haystack?"} + ) + print(r.json()) + +asyncio.run(main()) +``` + +## Streaming + +Implement `run_chat_completion` or `run_chat_completion_async` in your wrapper. See [OpenAI Compatibility](../features/openai-compatibility.md) for details. + +## Error Handling & Retry Logic + +```python +import requests +from requests.exceptions import RequestException +import time + +def run_with_retry(pipeline_name, params, max_retries=3): + url = f"http://localhost:1416/{pipeline_name}/run" + + for attempt in range(max_retries): + try: + response = requests.post(url, json=params) + response.raise_for_status() + return response.json() + except RequestException as e: + if attempt == max_retries - 1: + raise + time.sleep(2 ** attempt) # Exponential backoff +``` + +## Logging + +Add logging to your pipeline wrappers: + +```python +from hayhooks import log + +class PipelineWrapper(BasePipelineWrapper): + def run_api(self, query: str) -> str: + log.info(f"Processing query: {query}") + result = self.pipeline.run({"prompt": {"query": query}}) + log.info("Pipeline completed") + return result["llm"]["replies"][0] +``` + +See [Logging Reference](../reference/logging.md) for details. + +## Next Steps + +- [CLI Commands](../features/cli-commands.md) - Complete CLI reference +- [Examples](../examples/overview.md) - Working examples diff --git a/docs/assets/cursor-mcp-settings.png b/docs/assets/cursor-mcp-settings.png deleted file mode 100644 index fcbb4e00..00000000 Binary files a/docs/assets/cursor-mcp-settings.png and /dev/null differ diff --git a/docs/assets/deepset-square-logo.png b/docs/assets/deepset-square-logo.png new file mode 100644 index 00000000..40039864 Binary files /dev/null and b/docs/assets/deepset-square-logo.png differ diff --git a/docs/assets/open-webui-disable-generated-content.png b/docs/assets/open-webui-disable-generated-content.png new file mode 100644 index 00000000..ff51febd Binary files /dev/null and b/docs/assets/open-webui-disable-generated-content.png differ diff --git a/docs/assets/open-webui-enable-direct-connections.png b/docs/assets/open-webui-enable-direct-connections.png new file mode 100644 index 00000000..c4598f23 Binary files /dev/null and b/docs/assets/open-webui-enable-direct-connections.png differ diff --git a/docs/concepts/agent-deployment.md b/docs/concepts/agent-deployment.md new file mode 100644 index 00000000..26cc023f --- /dev/null +++ b/docs/concepts/agent-deployment.md @@ -0,0 +1,126 @@ +# Agent Deployment + +This page summarizes how to deploy Haystack Agents with Hayhooks and points you to the canonical examples. + +## Overview + +Agents are deployed using the same `PipelineWrapper` mechanism as pipelines. Implement `run_chat_completion` or `run_chat_completion_async` to expose OpenAI-compatible chat endpoints (with streaming support). + +## Quick Start + +Deploy agents using the same `PipelineWrapper` mechanism as pipelines. The key is implementing `run_chat_completion` or `run_chat_completion_async` for OpenAI-compatible chat endpoints with streaming support. + +See the example below for a complete agent setup with tools, streaming, and Open WebUI events. + +## Example + +An agent deployment with tools, streaming, and Open WebUI events: + +### Agent with tool call interception and Open WebUI events + +This example demonstrates: + +- Agent setup with tools +- Async streaming chat completion +- Tool call lifecycle hooks (`on_tool_call_start`, `on_tool_call_end`) +- Open WebUI status events and notifications + +See the full file: [open_webui_agent_on_tool_calls/pipeline_wrapper.py](https://github.com/deepset-ai/hayhooks/blob/main/examples/pipeline_wrappers/open_webui_agent_on_tool_calls/pipeline_wrapper.py) + +```python +import time +from collections.abc import AsyncGenerator + +from haystack.components.agents import Agent +from haystack.components.generators.chat import OpenAIChatGenerator +from haystack.dataclasses import ChatMessage +from haystack.tools import Tool + +from hayhooks import BasePipelineWrapper, async_streaming_generator +from hayhooks.open_webui import ( + OpenWebUIEvent, + create_details_tag, + create_notification_event, + create_status_event, +) + + +def weather_function(location): + """Mock weather API with a small delay""" + weather_info = { + "Berlin": {"weather": "mostly sunny", "temperature": 7, "unit": "celsius"}, + "Paris": {"weather": "mostly cloudy", "temperature": 8, "unit": "celsius"}, + "Rome": {"weather": "sunny", "temperature": 14, "unit": "celsius"}, + } + time.sleep(3) + return weather_info.get(location, {"weather": "unknown", "temperature": 0, "unit": "celsius"}) + + +weather_tool = Tool( + name="weather_tool", + description="Provides weather information for a given location.", + parameters={ + "type": "object", + "properties": {"location": {"type": "string"}}, + "required": ["location"], + }, + function=weather_function, +) + + +class PipelineWrapper(BasePipelineWrapper): + def setup(self) -> None: + self.agent = Agent( + chat_generator=OpenAIChatGenerator(model="gpt-4o-mini"), + system_prompt="You're a helpful agent", + tools=[weather_tool], + ) + + def on_tool_call_start( + self, + tool_name: str, + arguments: dict, # noqa: ARG002 + id: str, # noqa: ARG002, A002 + ) -> list[OpenWebUIEvent]: + return [ + create_status_event(description=f"Tool call started: {tool_name}"), + create_notification_event(notification_type="info", content=f"Tool call started: {tool_name}"), + ] + + def on_tool_call_end( + self, + tool_name: str, + arguments: dict, + result: str, + error: bool, # noqa: ARG002 + ) -> list[OpenWebUIEvent]: + return [ + create_status_event(description=f"Tool call ended: {tool_name}", done=True), + create_notification_event(notification_type="success", content=f"Tool call ended: {tool_name}"), + create_details_tag( + tool_name=tool_name, + summary=f"Tool call result for {tool_name}", + content=(f"```\nArguments:\n{arguments}\n\nResponse:\n{result}\n```"), + ), + ] + + async def run_chat_completion_async( + self, + model: str, # noqa: ARG002 + messages: list[dict], + body: dict, # noqa: ARG002 + ) -> AsyncGenerator[str, None]: + chat_messages = [ChatMessage.from_openai_dict_format(message) for message in messages] + + return async_streaming_generator( + on_tool_call_start=self.on_tool_call_start, + on_tool_call_end=self.on_tool_call_end, + pipeline=self.agent, + pipeline_run_args={"messages": chat_messages}, + ) +``` + +## Next Steps + +- [PipelineWrapper Guide](pipeline-wrapper.md) - Detailed implementation patterns +- [Open WebUI Events Example](../examples/openwebui-events.md) - Interactive agent features diff --git a/docs/concepts/pipeline-deployment.md b/docs/concepts/pipeline-deployment.md new file mode 100644 index 00000000..77ba9720 --- /dev/null +++ b/docs/concepts/pipeline-deployment.md @@ -0,0 +1,67 @@ +# Pipeline Deployment + +Hayhooks provides flexible options for deploying Haystack pipelines and agents. This section covers the core concepts of pipeline deployment. + +## Deployment Methods + +### 1. PipelineWrapper Deployment (Recommended) + +The most flexible approach using a `PipelineWrapper` class that encapsulates your pipeline logic. This method provides maximum flexibility for initialization, custom execution logic, OpenAI-compatible endpoint support, streaming capabilities, and file upload handling. + +See [PipelineWrapper Details](pipeline-wrapper.md) for complete implementation guide and examples. + +### 2. YAML Pipeline Deployment + +Deploy pipelines directly from YAML definitions with automatic schema generation. This approach offers simple deployment from YAML files with automatic request/response schema generation, no wrapper code required, making it perfect for straightforward pipelines. The YAML must include `inputs` and `outputs` sections with properly defined pipeline components. + +For a complete YAML example and detailed requirements, see [YAML Pipeline Deployment](yaml-pipeline-deployment.md). + +!!! warning "YAML Pipeline Limitations" + YAML-deployed pipelines do not support OpenAI-compatible chat endpoints or streaming. For chat/streaming (e.g., Open WebUI), use a `PipelineWrapper` and implement `run_chat_completion`/`run_chat_completion_async` (see [OpenAI Compatibility](../features/openai-compatibility.md)). + +## Core Components + +### BasePipelineWrapper Class + +All pipeline wrappers inherit from `BasePipelineWrapper`: + +#### Required Methods + +**`setup()`** is called once when the pipeline is deployed to initialize your pipeline instance and set up any required resources. + +**`run_api()`** is called for each API request to define your custom execution logic and return the pipeline result. + +#### Optional Methods + +**`run_api_async()`** is the async version of `run_api()`, providing better performance for concurrent requests and supporting async pipeline execution. + +**`run_chat_completion()`** enables OpenAI-compatible chat endpoints for Open WebUI integration with chat completion format support (see [OpenAI Compatibility](../features/openai-compatibility.md)). + +**`run_chat_completion_async()`** provides async chat completion with streaming support for chat interfaces and better performance for concurrent chat requests (see [OpenAI Compatibility](../features/openai-compatibility.md)). + +### Input/Output Handling + +Hayhooks automatically handles request validation using Pydantic models, JSON serialization of responses, multipart/form-data requests for file uploads (see [File Upload Support](../features/file-upload-support.md)), and automatic type conversion between JSON and Python types. + +## Lifecycle Management + +### Pipeline Registration + +When you deploy a pipeline, Hayhooks validates the wrapper implementation, creates the pipeline instance using `setup()`, registers the pipeline with the server, generates API endpoints and schemas, and creates OpenAI-compatible endpoints if implemented. + +### Pipeline Execution + +For each request, Hayhooks validates the request against the schema, calls the appropriate method (`run_api`, `run_chat_completion`, etc.), handles errors and exceptions, and returns the response in the correct format. + +### Pipeline Undeployment + +When you undeploy a pipeline, Hayhooks removes the pipeline from the registry, deletes the pipeline files if saved, unregisters all API endpoints, and cleans up resources. + +### MCP Integration + +All deployed pipelines can be exposed as MCP tools. Pipelines are automatically listed as available tools with input schemas generated from method signatures. Tools can be called from MCP clients (see [MCP Support](../features/mcp-support.md)). + +## Next Steps + +- [PipelineWrapper Details](pipeline-wrapper.md) - Learn about PipelineWrapper implementation +- [YAML Pipeline Deployment](yaml-pipeline-deployment.md) - Deploy from YAML files diff --git a/docs/concepts/pipeline-wrapper.md b/docs/concepts/pipeline-wrapper.md new file mode 100644 index 00000000..0372d85d --- /dev/null +++ b/docs/concepts/pipeline-wrapper.md @@ -0,0 +1,326 @@ +# PipelineWrapper + +The `PipelineWrapper` class is the core component for deploying Haystack pipelines with Hayhooks. It provides maximum flexibility for pipeline initialization and execution. + +## Why PipelineWrapper? + +The pipeline wrapper provides a flexible foundation for deploying Haystack pipelines, agents or any other component by allowing users to: + +- Choose their preferred initialization method (YAML files, Haystack templates, or inline code) +- Define custom execution logic with configurable inputs and outputs +- Optionally expose OpenAI-compatible chat endpoints with streaming support for integration with interfaces like [open-webui](https://openwebui.com/) + +## Basic Structure + +```python +from pathlib import Path +from typing import List, Generator, Union, AsyncGenerator +from haystack import Pipeline, AsyncPipeline +from hayhooks import BasePipelineWrapper, get_last_user_message, streaming_generator, async_streaming_generator + +class PipelineWrapper(BasePipelineWrapper): + def setup(self) -> None: + pipeline_yaml = (Path(__file__).parent / "pipeline.yml").read_text() + self.pipeline = Pipeline.loads(pipeline_yaml) + + def run_api(self, urls: List[str], question: str) -> str: + result = self.pipeline.run({"fetcher": {"urls": urls}, "prompt": {"query": question}}) + return result["llm"]["replies"][0] +``` + +## Required Methods + +### setup() + +The `setup()` method is called once when the pipeline is deployed. It should initialize the `self.pipeline` attribute as a Haystack pipeline. + +```python +def setup(self) -> None: + # Initialize your pipeline here + pass +``` + +**Initialization patterns:** + +### 1. Programmatic Initialization (Recommended) + +Define your pipeline directly in code for maximum flexibility and control: + +```python +def setup(self) -> None: + from haystack import Pipeline + from haystack.components.fetchers import LinkContentFetcher + from haystack.components.converters import HTMLToDocument + from haystack.components.builders import PromptBuilder + from haystack.components.generators import OpenAIGenerator + + # Create components + fetcher = LinkContentFetcher() + converter = HTMLToDocument() + prompt_builder = PromptBuilder( + template="Based on: {{documents}}\nAnswer: {{query}}" + ) + llm = OpenAIGenerator(model="gpt-4o-mini") + + # Build pipeline + self.pipeline = Pipeline() + self.pipeline.add_component("fetcher", fetcher) + self.pipeline.add_component("converter", converter) + self.pipeline.add_component("prompt", prompt_builder) + self.pipeline.add_component("llm", llm) + + # Connect components + self.pipeline.connect("fetcher.streams", "converter.sources") + self.pipeline.connect("converter.documents", "prompt.documents") + self.pipeline.connect("prompt.prompt", "llm.prompt") +``` + +!!! success "Benefits of Programmatic Initialization" + - :material-code-braces: Full IDE support with autocomplete and type checking + - :material-bug: Easier debugging and testing + - :material-pencil: Better refactoring capabilities + - :material-cog: Dynamic component configuration based on runtime conditions + +### 2. Load from YAML + +Load an existing YAML pipeline file: + +```python +def setup(self) -> None: + from pathlib import Path + from haystack import Pipeline + + pipeline_yaml = (Path(__file__).parent / "pipeline.yml").read_text() + self.pipeline = Pipeline.loads(pipeline_yaml) +``` + +**When to use:** + +- You already have a YAML pipeline definition +- You want to version control pipeline structure separately +- You need to share pipeline definitions across different deployments + +!!! tip "Consider YAML-only deployment" + If your pipeline is simple and doesn't need custom logic, consider using [YAML Pipeline Deployment](yaml-pipeline-deployment.md) instead, which doesn't require a wrapper at all. + +### run_api() + +The `run_api()` method is called for each API request to the `{pipeline_name}/run` endpoint. + +```python +def run_api(self, urls: List[str], question: str) -> str: + result = self.pipeline.run({"fetcher": {"urls": urls}, "prompt": {"query": question}}) + return result["llm"]["replies"][0] +``` + +**Key features:** + +- **Flexible Input**: You can define any input arguments you need +- **Automatic Validation**: Hayhooks creates Pydantic models for request validation +- **Type Safety**: Use proper type hints for better validation +- **Error Handling**: Implement proper error handling for production use + +**Input argument rules:** + +- Arguments must be JSON-serializable +- Use proper type hints (`List[str]`, `Optional[int]`, etc.) +- Default values are supported +- Complex types like `Dict[str, Any]` are allowed + +## Optional Methods + +### run_api_async() + +The asynchronous version of `run_api()` for better performance under high load. + +```python +async def run_api_async(self, urls: List[str], question: str) -> str: + result = await self.pipeline.run_async({"fetcher": {"urls": urls}, "prompt": {"query": question}}) + return result["llm"]["replies"][0] +``` + +**When to use `run_api_async`:** + +- Working with `AsyncPipeline` instances +- Handling many concurrent requests +- Integrating with async-compatible components +- Better performance for I/O-bound operations + +### run_chat_completion() + +Enable OpenAI-compatible chat endpoints for integration with chat interfaces. + +```python +def run_chat_completion(self, model: str, messages: List[dict], body: dict) -> Union[str, Generator]: + question = get_last_user_message(messages) + result = self.pipeline.run({"prompt": {"query": question}}) + return result["llm"]["replies"][0] +``` + +**Fixed signature:** + +- `model`: The pipeline name +- `messages`: OpenAI-format message list +- `body`: Full request body (for additional parameters) + +### run_chat_completion_async() + +Async version of chat completion with streaming support. + +```python +async def run_chat_completion_async(self, model: str, messages: List[dict], body: dict) -> AsyncGenerator: + question = get_last_user_message(messages) + return async_streaming_generator( + pipeline=self.pipeline, + pipeline_run_args={"prompt": {"query": question}}, + ) +``` + +## File Upload Support + +Hayhooks can handle file uploads by adding a `files` parameter: + +```python +from fastapi import UploadFile +from typing import Optional, List + +def run_api(self, files: Optional[List[UploadFile]] = None, query: str = "") -> str: + if files: + # Process uploaded files + filenames = [f.filename for f in files if f.filename] + file_contents = [f.file.read() for f in files] + return f"Processed {len(files)} files: {', '.join(filenames)}" + + return "No files uploaded" +``` + +## PipelineWrapper Development + +### During Development + +Use the `--overwrite` flag for rapid development: + +```bash +hayhooks pipeline deploy-files -n my_pipeline --overwrite ./path/to/pipeline +``` + +**Development workflow:** + +1. Make changes to your pipeline wrapper +2. Redeploy with `--overwrite` +3. Test the changes +4. Repeat as needed + +### For even faster iterations + +Combine `--overwrite` with `--skip-saving-files`: + +```bash +hayhooks pipeline deploy-files -n my_pipeline --overwrite --skip-saving-files ./path/to/pipeline +``` + +This avoids writing files to disk and speeds up development. + +## Additional Dependencies + +Your pipeline wrapper may require additional dependencies: + +```python +# pipeline_wrapper.py +import trafilatura # Additional dependency + +def run_api(self, urls: List[str], question: str) -> str: + # Use additional library + content = trafilatura.fetch(urls[0]) + # ... rest of pipeline logic +``` + +**Install dependencies:** + +```bash +pip install trafilatura +``` + +**Debugging tip:** Enable tracebacks to see full error messages: + +```bash +HAYHOOKS_SHOW_TRACEBACKS=true hayhooks run +``` + +## Error Handling + +Implement proper error handling in production: + +```python +from hayhooks import log +from fastapi import HTTPException + +class PipelineWrapper(BasePipelineWrapper): + def setup(self) -> None: + try: + self.pipeline = self._create_pipeline() + except Exception as e: + log.error(f"Failed to initialize pipeline: {e}") + raise + + def run_api(self, query: str) -> str: + if not query or not query.strip(): + raise HTTPException(status_code=400, detail="Query cannot be empty") + + try: + result = self.pipeline.run({"prompt": {"query": query}}) + return result["llm"]["replies"][0] + except Exception as e: + log.error(f"Pipeline execution failed: {e}") + raise HTTPException(status_code=500, detail="Pipeline execution failed") +``` + +## MCP Tool Configuration + +### Skip MCP Tool Listing + +To skip MCP tool registration: + +```python +class PipelineWrapper(BasePipelineWrapper): + skip_mcp = True # This pipeline won't be listed as an MCP tool + + def setup(self) -> None: + ... + + def run_api(self, ...) -> str: + ... +``` + +### MCP Tool Description + +Use docstrings to provide MCP tool descriptions: + +```python +def run_api(self, urls: List[str], question: str) -> str: + """ + Ask questions about website content. + + Args: + urls: List of website URLs to analyze + question: Question to ask about the content + + Returns: + Answer to the question based on the website content + """ + result = self.pipeline.run({"fetcher": {"urls": urls}, "prompt": {"query": question}}) + return result["llm"]["replies"][0] +``` + +## Examples + +For complete, working examples see: + +- **[Chat with Website (Streaming)](https://github.com/deepset-ai/hayhooks/tree/main/examples/pipeline_wrappers/chat_with_website_streaming)** - Pipeline with streaming chat completion support +- **[Async Question Answer](https://github.com/deepset-ai/hayhooks/tree/main/examples/pipeline_wrappers/async_question_answer)** - Async pipeline patterns with streaming +- **[RAG Indexing & Query](https://github.com/deepset-ai/hayhooks/tree/main/examples/rag_indexing_query)** - Complete RAG system with file uploads and Elasticsearch + +## Next Steps + +- [YAML Pipeline Deployment](yaml-pipeline-deployment.md) - Alternative deployment method +- [Agent Deployment](agent-deployment.md) - Deploy Haystack agents diff --git a/docs/concepts/yaml-pipeline-deployment.md b/docs/concepts/yaml-pipeline-deployment.md new file mode 100644 index 00000000..adfe034c --- /dev/null +++ b/docs/concepts/yaml-pipeline-deployment.md @@ -0,0 +1,287 @@ +# YAML Pipeline Deployment + +Hayhooks supports deploying Haystack pipelines directly from YAML definitions. This approach builds request/response schemas automatically from the YAML-declared `inputs` and `outputs`. + +## Overview + +YAML pipeline deployment is ideal for: + +- Simple pipelines with clear inputs and outputs +- Quick deployment without wrapper code +- Automatic schema generation +- CI/CD pipeline deployments + +!!! tip "Converting Existing Pipelines" + If you already have a Haystack `Pipeline` instance, you can serialize it with `pipeline.dumps()` and then manually add the required `inputs` and `outputs` sections before deploying. + +## Requirements + +### YAML Structure + +Your YAML file must include both `inputs` and `outputs` sections: + +```yaml +components: + fetcher: + type: haystack.components.fetchers.LinkContentFetcher + prompt_builder: + type: haystack.components.builders.PromptBuilder + init_parameters: + template: "Answer this question: {{query}} based on this content: {{documents}}" + llm: + type: haystack.components.generators.OpenAIGenerator + +connections: + - sender: fetcher.content + receiver: prompt_builder.documents + - sender: prompt_builder + receiver: llm + +inputs: + urls: fetcher.urls + query: prompt_builder.query + +outputs: + replies: llm.replies +``` + +### Key Requirements + +1. **`inputs` Section**: Maps friendly names to pipeline component fields +2. **`outputs` Section**: Maps pipeline outputs to response fields +3. **Valid Components**: All components must be properly defined +4. **Valid Connections**: All connections must reference existing components + +## Deployment Methods + +=== "CLI" + + ```bash + # Deploy with default settings + hayhooks pipeline deploy-yaml pipelines/chat_pipeline.yml + + # Deploy with custom name + hayhooks pipeline deploy-yaml -n my_chat_pipeline pipelines/chat_pipeline.yml + + # Deploy with description + hayhooks pipeline deploy-yaml -n my_chat_pipeline --description "Chat pipeline for Q&A" pipelines/chat_pipeline.yml + + # Overwrite existing pipeline + hayhooks pipeline deploy-yaml -n my_chat_pipeline --overwrite pipelines/chat_pipeline.yml + ``` + +=== "HTTP API" + + ```bash + curl -X POST \ + http://localhost:1416/deploy-yaml \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "my_chat_pipeline", + "description": "Chat pipeline for Q&A", + "yaml_content": "...", + "overwrite": false + }' + ``` + +=== "Python" + + ```python + import requests + + response = requests.post( + "http://localhost:1416/deploy-yaml", + json={ + "name": "my_chat_pipeline", + "description": "Chat pipeline for Q&A", + "yaml_content": "...", # Your YAML content as string + "overwrite": False + } + ) + print(response.json()) + ``` + +## CLI Options + +| Option | Short | Description | Default | +|--------|-------|-------------|---------| +| `--name` | `-n` | Override the pipeline name | YAML file stem | +| `--description` | | Human-readable description | Pipeline name | +| `--overwrite` | `-o` | Overwrite if pipeline exists | `false` | +| `--skip-mcp` | | Skip MCP tool registration | `false` | +| `--save-file` | | Save YAML to server | `true` | +| `--no-save-file` | | Don't save YAML to server | `false` | + +## Input/Output Mapping + +### Input Mapping + +The `inputs` section maps friendly names to pipeline component fields: + +```yaml +inputs: + # friendly_name: component.field + urls: fetcher.urls + query: prompt_builder.query +``` + +**Mapping rules:** + +- Use `component.field` syntax +- Field must exist in the component +- Multiple inputs can map to the same component field +- Input names become API parameters + +### Output Mapping + +The `outputs` section maps pipeline outputs to response fields: + +```yaml +outputs: + # response_field: component.field + replies: llm.replies + documents: fetcher.documents + metadata: prompt_builder.metadata +``` + +**Mapping rules:** + +- Use `component.field` syntax +- Field must exist in the component +- Response fields are serialized to JSON +- Complex objects are automatically serialized + +## API Usage + +### After Deployment + +Once deployed, your pipeline is available at: + +- **Run Endpoint**: `/{pipeline_name}/run` +- **Schema**: `/{pipeline_name}/schema` +- **OpenAPI**: `/openapi.json` + +### Example Request + +```bash +curl -X POST \ + http://localhost:1416/my_chat_pipeline/run \ + -H 'Content-Type: application/json' \ + -d '{ + "urls": ["https://haystack.deepset.ai"], + "query": "What is Haystack?" + }' +``` + +### Example Response + +```json +{ + "replies": ["Haystack is an open source framework..."], + "documents": [...], + "metadata": {...} +} +``` + +## Schema Generation + +Hayhooks automatically generates: + +### Request Schema + +```json +{ + "type": "object", + "properties": { + "urls": { + "type": "array", + "items": {"type": "string"} + }, + "query": {"type": "string"} + }, + "required": ["urls", "query"] +} +``` + +### Response Schema + +```json +{ + "type": "object", + "properties": { + "replies": {"type": "array"}, + "documents": {"type": "array"}, + "metadata": {"type": "object"} + } +} +``` + +## Obtaining YAML from Existing Pipelines + +You can obtain YAML from existing Haystack pipelines: + +```python +from haystack import Pipeline + +# Create or load your pipeline +pipeline = Pipeline() +# ... add components and connections ... + +# Get YAML representation +yaml_content = pipeline.dumps() + +# Save to file +with open("pipeline.yml", "w") as f: + f.write(yaml_content) +``` + +!!! note + You'll need to manually add the `inputs` and `outputs` sections to the generated YAML. + +## Limitations + +!!! warning "YAML Pipeline Limitations" + YAML-deployed pipelines have the following limitations: + + - :octicons-x-circle-16: **No OpenAI Compatibility**: Don't support OpenAI-compatible chat endpoints + - :octicons-x-circle-16: **No Streaming**: Streaming responses are not supported + - :octicons-x-circle-16: **No File Uploads**: File upload handling is not available + - :material-lightning-bolt: **Async Only**: Pipelines are run as `AsyncPipeline` instances + +!!! tip "Using PipelineWrapper for Advanced Features" + For advanced features, use `PipelineWrapper` instead: + +```python +# For OpenAI compatibility +class PipelineWrapper(BasePipelineWrapper): + def run_chat_completion(self, model: str, messages: List[dict], body: dict) -> Union[str, Generator]: + ... + +# For file uploads +class PipelineWrapper(BasePipelineWrapper): + def run_api(self, files: Optional[List[UploadFile]] = None, query: str = "") -> str: + ... + +# For streaming +class PipelineWrapper(BasePipelineWrapper): + def run_chat_completion_async(self, model: str, messages: List[dict], body: dict) -> AsyncGenerator: + ... +``` + +## Example + +For a complete working example of a YAML pipeline with proper `inputs` and `outputs`, see: + +- [tests/test_files/yaml/inputs_outputs_pipeline.yml](https://github.com/deepset-ai/hayhooks/blob/main/tests/test_files/yaml/inputs_outputs_pipeline.yml) + +This example demonstrates: + +- Complete pipeline structure with components and connections +- Proper `inputs` mapping to component fields +- Proper `outputs` mapping from component results +- Real-world usage with `LinkContentFetcher`, `HTMLToDocument`, `PromptBuilder`, and `OpenAIGenerator` + +## Next Steps + +- [PipelineWrapper](pipeline-wrapper.md) - For advanced features like streaming and chat completion +- [Examples](../examples/overview.md) - See working examples diff --git a/docs/deployment/deployment_guidelines.md b/docs/deployment/deployment_guidelines.md new file mode 100644 index 00000000..b3bd6113 --- /dev/null +++ b/docs/deployment/deployment_guidelines.md @@ -0,0 +1,316 @@ +# Deployment Guidelines + +This guide describes how to deploy Hayhooks in production environments. + +Since Hayhooks is a FastAPI application, you can deploy it using any standard ASGI server deployment strategy. For comprehensive deployment concepts, see the [FastAPI deployment documentation](https://fastapi.tiangolo.com/deployment/concepts/). + +This guide focuses on Hayhooks-specific considerations for production deployments. + +## Quick Recommendations + +- Use `HAYHOOKS_PIPELINES_DIR` to deploy pipelines in production environments +- Start with a single worker for I/O-bound pipelines, use multiple workers for CPU-bound workloads +- Implement async methods (`run_api_async`) for better I/O performance +- Configure health checks for container orchestration +- Set appropriate resource limits and environment variables +- Review security settings (CORS, tracebacks, logging levels) + +!!! info "Configuration Resources" + Review [Configuration](../getting-started/configuration.md) and [Environment Variables Reference](../reference/environment-variables.md) before deploying. + +## Pipeline Deployment Strategy + +For production deployments, use `HAYHOOKS_PIPELINES_DIR` to deploy pipelines at startup. + +### Using HAYHOOKS_PIPELINES_DIR + +Set the environment variable to point to a directory containing your pipeline definitions: + +```bash +export HAYHOOKS_PIPELINES_DIR=/app/pipelines +hayhooks run +``` + +When Hayhooks starts, it automatically loads all pipelines from this directory. + +**Benefits:** + +- Pipelines are available immediately on startup +- Consistent across all workers/instances +- No runtime deployment API calls needed +- Simple to version control and deploy + +**Directory structure:** + +```text +pipelines/ +├── my_pipeline/ +│ ├── pipeline_wrapper.py +│ └── pipeline.yml +└── another_pipeline/ + ├── pipeline_wrapper.py + └── pipeline.yml +``` + +See [YAML Pipeline Deployment](../concepts/yaml-pipeline-deployment.md) and [PipelineWrapper](../concepts/pipeline-wrapper.md) for file structure details. + +!!! tip "Development vs Production" + For local development, you can use CLI commands (`hayhooks pipeline deploy-files`) or API endpoints (`POST /deploy-files`). For production, always use `HAYHOOKS_PIPELINES_DIR`. + +## Performance Tuning + +### Single Worker vs Multiple Workers + +**Single Worker Environment:** + +```bash +hayhooks run +``` + +Best for: + +- Development and testing +- I/O-bound pipelines (HTTP requests, file operations, database queries) +- Low to moderate concurrent requests +- Simpler deployment and debugging + +**Multiple Workers Environment:** + +```bash +hayhooks run --workers 4 +``` + +Best for: + +- CPU-bound pipelines (embedding generation, heavy computation) +- High concurrent request volumes +- Production environments with available CPU cores + +!!! tip "Worker Count Formula" + A common starting point: `workers = (2 x CPU_cores) + 1`. Adjust based on your workload - I/O-bound: More workers can help; CPU-bound: Match CPU cores to avoid context switching overhead. + +### Concurrency Behavior + +Pipeline `run()` methods execute synchronously but are wrapped in `run_in_threadpool` to avoid blocking the async event loop. + +**I/O-bound pipelines** (HTTP requests, file operations, database queries): + +- Can handle concurrent requests effectively in a single worker +- Worker switches between tasks during I/O waits +- Consider implementing async methods for even better performance + +**CPU-bound pipelines** (embedding generation, heavy computation): + +- Limited by Python's Global Interpreter Lock (GIL) +- Requests are queued and processed sequentially in a single worker +- Use multiple workers or horizontal scaling to improve throughput + +### Async Pipelines + +Implement async methods for better I/O-bound performance: + +```python +from haystack import AsyncPipeline +from hayhooks import BasePipelineWrapper + +class PipelineWrapper(BasePipelineWrapper): + def setup(self) -> None: + self.pipeline = AsyncPipeline.loads( + (Path(__file__).parent / "pipeline.yml").read_text() + ) + + async def run_api_async(self, query: str) -> str: + result = await self.pipeline.run_async({"prompt": {"query": query}}) + return result["llm"]["replies"][0] +``` + +### Streaming + +Use streaming for chat endpoints to reduce perceived latency: + +```python +from hayhooks import async_streaming_generator, get_last_user_message + +async def run_chat_completion_async(self, model: str, messages: list[dict], body: dict): + question = get_last_user_message(messages) + return async_streaming_generator( + pipeline=self.pipeline, + pipeline_run_args={"prompt": {"query": question}}, + ) +``` + +See [OpenAI Compatibility](../features/openai-compatibility.md) for more details on streaming. + +### Horizontal Scaling + +Deploy multiple instances behind a load balancer for increased throughput. + +**Key considerations:** + +- Use `HAYHOOKS_PIPELINES_DIR` to ensure all instances have the same pipelines +- Configure session affinity if using stateful components +- Distribute traffic evenly across instances +- Monitor individual instance health + +**Example setup** (Docker Swarm, Kubernetes, or cloud load balancers): + +```bash +# Each instance should use the same pipeline directory +export HAYHOOKS_PIPELINES_DIR=/app/pipelines +hayhooks run +``` + +!!! info "GIL Limitations" + Even with multiple workers, individual workers have GIL limitations. CPU-bound pipelines benefit more from horizontal scaling (multiple instances) than vertical scaling (multiple workers per instance). + +## Docker Deployment + +### Single Container + +```bash +docker run -d \ + -p 1416:1416 \ + -e HAYHOOKS_HOST=0.0.0.0 \ + -e HAYHOOKS_PIPELINES_DIR=/app/pipelines \ + -v "$PWD/pipelines:/app/pipelines:ro" \ + deepset/hayhooks:latest +``` + +### Docker Compose + +```yaml +version: '3.8' +services: + hayhooks: + image: deepset/hayhooks:latest + ports: + - "1416:1416" + environment: + HAYHOOKS_HOST: 0.0.0.0 + HAYHOOKS_PIPELINES_DIR: /app/pipelines + LOG: INFO + volumes: + - ./pipelines:/app/pipelines:ro + restart: unless-stopped +``` + +See [Quick Start with Docker Compose](../getting-started/quick-start-docker.md) for a complete example with Open WebUI integration. + +### Health Checks + +Add health checks to monitor container health: + +```yaml +services: + hayhooks: + image: deepset/hayhooks:latest + ports: + - "1416:1416" + environment: + HAYHOOKS_HOST: 0.0.0.0 + HAYHOOKS_PIPELINES_DIR: /app/pipelines + volumes: + - ./pipelines:/app/pipelines:ro + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:1416/status"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + restart: unless-stopped +``` + +The `/status` endpoint returns the server status and can be used for health monitoring. + +## Production Deployment Options + +### Docker + +Deploy Hayhooks using Docker containers for consistent, portable deployments across environments. Docker provides isolation, easy versioning, and simplified dependency management. See the [Docker documentation](https://docs.docker.com/get-started/) for container deployment best practices. + +### Kubernetes + +Deploy Hayhooks on Kubernetes for automated scaling, self-healing, and advanced orchestration capabilities. Use Deployments, Services, and ConfigMaps to manage pipeline definitions and configuration. See the [Kubernetes documentation](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) for deployment strategies. + +### Server/VPS Deployment + +Deploy Hayhooks directly on a server or VPS using systemd or process managers like supervisord for production reliability. This approach offers full control over the environment and is suitable for dedicated workloads. See the [FastAPI deployment documentation](https://fastapi.tiangolo.com/deployment/manually/) for manual deployment guidance. + +### AWS ECS + +Deploy Hayhooks on AWS Elastic Container Service for managed container orchestration in the AWS ecosystem. ECS handles container scheduling, load balancing, and integrates seamlessly with other AWS services. See the [AWS ECS documentation](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html) for deployment details. + +## Production Best Practices + +### Environment Variables + +Store sensitive configuration in environment variables or secrets: + +```bash +# Use a .env file +HAYHOOKS_PIPELINES_DIR=/app/pipelines +LOG=INFO +HAYHOOKS_SHOW_TRACEBACKS=false +``` + +See [Environment Variables Reference](../reference/environment-variables.md) for all options. + +### Logging + +Configure appropriate log levels for production: + +```bash +# Production: INFO or WARNING +export LOG=INFO + +# Development: DEBUG +export LOG=DEBUG +``` + +See [Logging](../reference/logging.md) for details. + +### CORS Configuration + +Configure CORS for production environments: + +```bash +# Restrict to specific origins +export HAYHOOKS_CORS_ALLOW_ORIGINS='["https://yourdomain.com"]' +export HAYHOOKS_CORS_ALLOW_CREDENTIALS=true +``` + +## Troubleshooting + +### Pipeline Not Available + +If pipelines aren't available after startup: + +1. Check `HAYHOOKS_PIPELINES_DIR` is correctly set +2. Verify pipeline files exist in the directory +3. Check logs for deployment errors: `docker logs ` +4. Verify pipeline wrapper syntax and imports + +### High Memory Usage + +For memory-intensive pipelines: + +1. Increase container memory limits in Docker Compose +2. Profile pipeline components for memory leaks +3. Optimize component initialization and caching +4. Consider using smaller models or batch sizes + +### Slow Response Times + +For performance issues: + +1. Check component initialization in `setup()` vs `run_api()` +2. Verify pipeline directory is mounted correctly +3. Review logs for errors or warnings +4. Consider implementing async methods or adding workers (see [Performance Tuning](#performance-tuning) above) + +## Next Steps + +- [Advanced Configuration](../advanced/advanced-configuration.md) - Custom routes, middleware, and programmatic customization +- [Environment Variables Reference](../reference/environment-variables.md) - Complete configuration reference +- [Pipeline Deployment](../concepts/pipeline-deployment.md) - Pipeline deployment concepts +- [Quick Start with Docker Compose](../getting-started/quick-start-docker.md) - Complete Docker Compose example diff --git a/docs/deployment_guidelines.md b/docs/deployment_guidelines.md deleted file mode 100644 index 8b65837f..00000000 --- a/docs/deployment_guidelines.md +++ /dev/null @@ -1,76 +0,0 @@ -# Hayhooks deployment guidelines - -This document describes how to deploy Hayhooks in a production environment. -Since Hayhooks is a FastAPI application, it can be deployed in a variety of ways as well described in [its documentation](https://fastapi.tiangolo.com/deployment/concepts/?h=deploy). - -Following are some guidelines about deploying and running Haystack pipelines. - -## TL;DR - -- Use a **single worker environment** if you have mainly I/O operations in your pipeline and/or a low number of concurrent requests. -- Use a **multi-worker environment** if you have mainly CPU-bound operations in your pipeline and/or a high number of concurrent requests. -- In any case, use `HAYHOOKS_PIPELINES_DIR` to share pipeline definitions across workers (if possible). -- You can use [any additional supported `uvicorn` environment variables](https://www.uvicorn.org/settings) to the `hayhooks run` command (or put them in a `.env` file). - -## Single worker environment - -In a single worker environment, you typically run the application using: - -```bash -hayhooks run -``` - -command (or having a single Docker container running). This will launch a **single `uvicorn` worker** to serve the application. - -### Pipelines deployment (single worker) - -You can deploy a pipeline using: - -```bash -hayhooks deploy-files # recommended - -# or - -hayhooks deploy ... -``` - -or make `POST /deploy` / `POST /deploy-files` requests. - -### Handling concurrent requests (single worker) - -The `run()` method of the pipeline instance is _synchronous_ code, and it's executed using `run_in_threadpool` to avoid blocking the main async event loop. - -- If your pipeline is doing **mainly I/O operations** (like making HTTP requests, reading/writing files, etc.), the single worker should be able to handle concurrent requests. -- If your pipeline is doing **mainly CPU-bound operations** (like computing embeddings), the GIL (Global Interpreter Lock) will prevent the worker from handling concurrent requests, so they will be queued. - -## Multiple workers environment - -### Using `uvicorn` with multiple workers - -Hayhooks supports multiple `uvicorn` workers running on a single instance, you can use the `hayhooks run` command with the `--workers` flag to start the application with the desired number of workers. - -For example, if you have enough cores to run 4 workers, you can use the following command: - -```bash -hayhooks run --workers 4 -``` - -This vertical scaling approach allows you to handle more concurrent requests (depending on environment available resources). - -### Multiple single-worker instances behind a load balancer - -In a multi-worker environment (for example on a Kubernetes `Deployment`) you typically have a `LoadBalancer` Service, which distributes the traffic to a number of `Pod`s running the application (using `hayhooks run` command). - -This horizontal scaling approach allows you to handle more concurrent requests. - -### Pipeline deployment (multiple workers) - -In both the above scenarios, **it's NOT recommended** to deploy a pipeline using Hayhooks CLI commands (or corresponding API requests) as **it will deploy the pipeline only on one of the workers**, which is not ideal. - -Instead, set the environment variable `HAYHOOKS_PIPELINES_DIR` to point to a shared directory accessible by all workers. When Hayhooks starts up, each worker will load pipeline definitions from this shared location, ensuring consistent pipeline availability across all workers when handling API requests. - -### Handling concurrent requests (multiple workers) - -When having multiple workers and pipelines deployed using `HAYHOOKS_PIPELINES_DIR`, you will be able to handle concurrent requests as each worker should be able to run a pipeline independently. This may be enough to make your application scalable, according to your needs. - -Note that even in a multiple-workers environment, the individual single worker will have the same GIL limitations discussed above, so if your pipeline is mainly CPU-bound, you will need to scale horizontally according to your needs. diff --git a/docs/examples/async-operations.md b/docs/examples/async-operations.md new file mode 100644 index 00000000..2a6358d4 --- /dev/null +++ b/docs/examples/async-operations.md @@ -0,0 +1,36 @@ +# Async Operations Example + +Patterns for async pipelines in Hayhooks: streaming responses, concurrency, and background work. Use these patterns when you need high throughput or to avoid blocking. + +## Where is the code? + +- Async wrapper: [async_question_answer](https://github.com/deepset-ai/hayhooks/tree/main/examples/pipeline_wrappers/async_question_answer) +- See main docs for async `run_api_async` and `run_chat_completion_async` + +## Deploy (example) + +```bash +hayhooks pipeline deploy-files -n async-question-answer examples/pipeline_wrappers/async_question_answer +``` + +## Run + +- OpenAI-compatible chat (async streaming): + +```bash +curl -X POST http://localhost:1416/v1/chat/completions \ + -H 'Content-Type: application/json' \ + -d '{ + "model": "async-question-answer", + "messages": [{"role": "user", "content": "Tell me a joke about programming"}] + }' +``` + +!!! tip "Best Practices" + - Prefer `run_chat_completion_async` for streaming and concurrency + - Ensure components support async streaming callbacks; otherwise use the sync `streaming_generator` + +## Related + +- General guide: [Main docs](../index.md) +- Examples index: [Examples Overview](overview.md) diff --git a/docs/examples/chat-with-website.md b/docs/examples/chat-with-website.md new file mode 100644 index 00000000..bd5e15c5 --- /dev/null +++ b/docs/examples/chat-with-website.md @@ -0,0 +1,44 @@ +# Chat with Website Example + +Build a pipeline that answers questions about one or more websites. Uses fetching, cleaning and an LLM to generate answers, and supports streaming via OpenAI-compatible chat endpoints when implemented in the wrapper. + +## Where is the code? + +- Wrapper example directory: [examples/pipeline_wrappers/chat_with_website_streaming](https://github.com/deepset-ai/hayhooks/tree/main/examples/pipeline_wrappers/chat_with_website_streaming) +- See the main docs for `PipelineWrapper` basics and OpenAI compatibility + +## Deploy + +```bash +hayhooks pipeline deploy-files -n chat_with_website examples/pipeline_wrappers/chat_with_website_streaming +``` + +## Run + +- API mode: + +```bash +curl -X POST http://localhost:1416/chat_with_website/run \ + -H 'Content-Type: application/json' \ + -d '{"query": "What is this website about?", "urls": ["https://python.org"]}' +``` + +- Chat (OpenAI-compatible), when `run_chat_completion`/`_async` is implemented: + +```bash +curl -X POST http://localhost:1416/v1/chat/completions \ + -H 'Content-Type: application/json' \ + -d '{ + "model": "chat_with_website", + "messages": [{"role": "user", "content": "Tell me about https://github.com"}] + }' +``` + +!!! tip "Development Tips" + - For development, use `--overwrite` to redeploy a changed wrapper: `hayhooks pipeline deploy-files -n chat_with_website --overwrite ` + - Some examples may require extra Python packages (e.g., `trafilatura`). Install as needed. + +## Related + +- General guide: [Main docs](../index.md) +- Examples index: [Examples Overview](overview.md) diff --git a/docs/examples/openwebui-events.md b/docs/examples/openwebui-events.md new file mode 100644 index 00000000..b1d85480 --- /dev/null +++ b/docs/examples/openwebui-events.md @@ -0,0 +1,37 @@ +# Open WebUI Events Example + +Send status updates and UI events to Open WebUI during streaming, and optionally intercept tool calls for richer feedback. + +## Where is the code? + +- Event examples: [open_webui_agent_events](https://github.com/deepset-ai/hayhooks/tree/main/examples/pipeline_wrappers/open_webui_agent_events), [open_webui_agent_on_tool_calls](https://github.com/deepset-ai/hayhooks/tree/main/examples/pipeline_wrappers/open_webui_agent_on_tool_calls) +- See the main docs → Open WebUI integration and event hooks + +## Deploy (example) + +```bash +hayhooks pipeline deploy-files -n agent_events examples/pipeline_wrappers/open_webui_agent_events +``` + +## Run + +- OpenAI-compatible chat (events stream to Open WebUI): + +```bash +curl -X POST http://localhost:1416/v1/chat/completions \ + -H 'Content-Type: application/json' \ + -d '{ + "model": "agent_events", + "messages": [{"role": "user", "content": "Tell me about machine learning"}] + }' +``` + +!!! tip "Working with Events" + - Use helpers from `hayhooks.open_webui`: `create_status_event`, `create_message_event`, `create_replace_event`, `create_source_event`, `create_notification_event`, `create_details_tag` + - Intercept tool calls via `on_tool_call_start`/`on_tool_call_end` with `streaming_generator`/`async_streaming_generator` + - For recommended Open WebUI settings, see the [Open WebUI Integration](../features/openwebui-integration.md) guide + +## Related + +- General guide: [Main docs](../index.md) +- Examples index: [Examples Overview](overview.md) diff --git a/docs/examples/overview.md b/docs/examples/overview.md new file mode 100644 index 00000000..0c6fb83a --- /dev/null +++ b/docs/examples/overview.md @@ -0,0 +1,32 @@ +# Examples Overview + +This page lists all maintained Hayhooks examples with detailed descriptions and links to the source code. + +## Pipeline wrapper examples + +| Example | Docs | Code | Description | +|---|---|---|---| +| Chat with Website (Streaming) | [chat-with-website.md](chat-with-website.md) | [GitHub](https://github.com/deepset-ai/hayhooks/tree/main/examples/pipeline_wrappers/chat_with_website_streaming) | Website Q&A with streaming | +| Chat with Website (basic) | — | [GitHub](https://github.com/deepset-ai/hayhooks/tree/main/examples/pipeline_wrappers/chat_with_website) | Minimal website Q&A wrapper | +| Chat with Website (MCP) | — | [GitHub](https://github.com/deepset-ai/hayhooks/tree/main/examples/pipeline_wrappers/chat_with_website_mcp) | Exposes website Q&A as MCP Tool | +| Async Question Answer (Streaming) | [async-operations.md](async-operations.md) | [GitHub](https://github.com/deepset-ai/hayhooks/tree/main/examples/pipeline_wrappers/async_question_answer) | Async pipeline and streaming patterns | +| Open WebUI Agent Events | [openwebui-events.md](openwebui-events.md) | [GitHub](https://github.com/deepset-ai/hayhooks/tree/main/examples/pipeline_wrappers/open_webui_agent_events) | UI events and status updates | +| Open WebUI Agent on Tool Calls | [openwebui-events.md](openwebui-events.md) | [GitHub](https://github.com/deepset-ai/hayhooks/tree/main/examples/pipeline_wrappers/open_webui_agent_on_tool_calls) | Tool call interception & feedback | +| Shared Code Between Wrappers | — | [GitHub](https://github.com/deepset-ai/hayhooks/tree/main/examples/shared_code_between_wrappers) | Reusing code across wrappers | + +## End-to-end examples & patterns + +| Example | Docs | Code | Description | +|---|---|---|---| +| RAG: Indexing and Query with Elasticsearch | [rag-system.md](rag-system.md) | [GitHub](https://github.com/deepset-ai/hayhooks/tree/main/examples/rag_indexing_query) | Full indexing/query pipelines with Elasticsearch | + +## How to use examples + +**Prerequisites:** +- Install Hayhooks: `pip install hayhooks` (additional deps per example may apply) + +**Deployment:** +- Pipeline wrappers: deploy directly with `hayhooks pipeline deploy-files -n ` and run via API (`POST //run`) or OpenAI-compatible chat endpoints if implemented +- End-to-end examples: follow the example's documentation for full setup (services like Elasticsearch, multi-pipeline deployment, datasets, etc.) + +For general usage and CLI commands, see the [Getting Started Guide](../getting-started/quick-start.md). diff --git a/docs/examples/rag-system.md b/docs/examples/rag-system.md new file mode 100644 index 00000000..91d4836e --- /dev/null +++ b/docs/examples/rag-system.md @@ -0,0 +1,48 @@ +# RAG System Example + +Build a Retrieval-Augmented Generation flow: ingest documents, embed and store them, retrieve by similarity, and answer questions with an LLM. + +## Where is the code? + +- End-to-end example: [examples/rag_indexing_query](https://github.com/deepset-ai/hayhooks/tree/main/examples/rag_indexing_query) + - `indexing_pipeline/` - Handles document upload and indexing + - `query_pipeline/` - Retrieves and generates answers + +## Quick Start (from repository root) + +```bash +# 1) Enter the example +cd examples/rag_indexing_query + +# 2) (Recommended) Create and activate a virtual env, then install deps +python -m venv .venv && source .venv/bin/activate +pip install -r requirements.txt + +# 3) Launch Hayhooks (in a separate terminal if you prefer) +hayhooks run + +# 4) Launch Elasticsearch +docker compose up + +# 5) Deploy the pipelines +hayhooks pipeline deploy-files -n indexing indexing_pipeline +hayhooks pipeline deploy-files -n query query_pipeline + +# 6) Index sample files +hayhooks pipeline run indexing --dir files_to_index + +# 7) Ask a question +hayhooks pipeline run query --param 'question="is this recipe vegan?"' + +# Optional: check API docs +# http://localhost:1416/docs +``` + +!!! info "Additional Information" + - See [File Upload Support](../features/file-upload-support.md) for wrapper signature and request format + - Choose appropriate embedding models and document stores for your scale + +## Related + +- General guide: [Main docs](../index.md) +- Examples index: [Examples Overview](overview.md) diff --git a/docs/features/cli-commands.md b/docs/features/cli-commands.md new file mode 100644 index 00000000..2d4bde0a --- /dev/null +++ b/docs/features/cli-commands.md @@ -0,0 +1,399 @@ +# CLI Commands + +Hayhooks provides a comprehensive command-line interface for managing pipelines and the server. This section covers all available CLI commands and their usage. + +## Overview + +The Hayhooks CLI allows you to: + +- Start and manage the Hayhooks server +- Deploy and undeploy pipelines +- Run pipelines with custom inputs +- Monitor server status and health +- Manage MCP server operations + +## Installation + +The CLI is automatically installed with the Hayhooks package: + +```bash +pip install hayhooks +``` + +## Global Commands + +### Help + +Get help for any command: + +```bash +# Show main help +hayhooks --help + +# Show help for specific command +hayhooks run --help +hayhooks pipeline --help +``` + +### Version + +Check the installed version: + +```bash +hayhooks --version +``` + +## Server Commands + +### run (HTTP vs CLI example) + +Start the Hayhooks server: + +```bash +# Basic server start +hayhooks run + +# With custom host and port +hayhooks run --host 0.0.0.0 --port 1416 + +# With multiple workers +hayhooks run --workers 4 + +# With custom pipelines directory +hayhooks run --pipelines-dir ./my_pipelines + +# With additional Python path +hayhooks run --additional-python-path ./custom_code + +# Reload on changes (development) +hayhooks run --reload +``` + +#### Options for `run` + +| Option | Short | Description | Default | +|--------|-------|-------------|---------| +| `--host` | | Host to bind to | `127.0.0.1` | +| `--port` | | Port to listen on | `1416` | +| `--workers` | | Number of worker processes | `1` | +| `--pipelines-dir` | | Directory for pipeline definitions | `./pipelines` | +| `--additional-python-path` | | Additional Python path | `None` | +| `--root-path` | | Root path for API | `/` | +| `--reload` | | Reload on code changes (development) | `false` | + +### mcp run + +Start the MCP server: + +```bash +# Start MCP server +hayhooks mcp run + +# With custom host and port +hayhooks mcp run --host 0.0.0.0 --port 1417 +``` + +#### Options for `mcp run` + +| Option | Short | Description | Default | +|--------|-------|-------------|---------| +| `--host` | | MCP server host | `127.0.0.1` | +| `--port` | | MCP server port | `1417` | +| `--pipelines-dir` | | Directory for pipeline definitions | `./pipelines` | +| `--additional-python-path` | | Additional Python path | `None` | + +## Pipeline Management Commands + +### pipeline deploy-files + +Deploy a pipeline from wrapper files: + +```bash +# Basic deployment +hayhooks pipeline deploy-files -n my_pipeline ./path/to/pipeline + +# With custom name and description +hayhooks pipeline deploy-files -n my_pipeline --description "My pipeline" ./path/to/pipeline + +# Overwrite existing pipeline +hayhooks pipeline deploy-files -n my_pipeline --overwrite ./path/to/pipeline + +# Skip saving files to server +hayhooks pipeline deploy-files -n my_pipeline --skip-saving-files ./path/to/pipeline + +# Skip MCP tool registration +hayhooks pipeline deploy-files -n my_pipeline --skip-mcp ./path/to/pipeline +``` + +#### Options for `pipeline deploy-files` + +| Option | Short | Description | Default | +|--------|-------|-------------|---------| +| `--name` | `-n` | Pipeline name | Required | +| `--description` | | Human-readable description | Pipeline name | +| `--overwrite` | `-o` | Overwrite existing pipeline | `false` | +| `--skip-saving-files` | | Don't save files to server | `false` | +| `--skip-mcp` | | Skip MCP tool registration | `false` | + +### pipeline deploy-yaml + +Deploy a pipeline from YAML definition: + +```bash +# Deploy from YAML file +hayhooks pipeline deploy-yaml pipelines/my_pipeline.yml + +# With custom name +hayhooks pipeline deploy-yaml -n my_custom_name pipelines/my_pipeline.yml + +# With description +hayhooks pipeline deploy-yaml -n my_pipeline --description "YAML pipeline" pipelines/my_pipeline.yml + +# Overwrite existing +hayhooks pipeline deploy-yaml -n my_pipeline --overwrite pipelines/my_pipeline.yml + +# Don't save YAML file +hayhooks pipeline deploy-yaml -n my_pipeline --no-save-file pipelines/my_pipeline.yml +``` + +#### Options for `pipeline deploy-yaml` + +| Option | Short | Description | Default | +|--------|-------|-------------|---------| +| `--name` | `-n` | Pipeline name | YAML file stem | +| `--description` | | Human-readable description | Pipeline name | +| `--overwrite` | `-o` | Overwrite existing pipeline | `false` | +| `--skip-mcp` | | Skip MCP tool registration | `false` | +| `--save-file` | | Save YAML to server | `true` | +| `--no-save-file` | | Don't save YAML to server | `false` | + +### pipeline undeploy + +Undeploy a pipeline: + +```bash +# Undeploy by name +hayhooks pipeline undeploy my_pipeline + +# Force undeploy (ignore errors) +hayhooks pipeline undeploy my_pipeline --force +``` + +#### Options for `pipeline undeploy` + +| Option | Short | Description | Default | +|--------|-------|-------------|---------| +| `--force` | | Force undeploy (if needed) | `false` | + +### pipeline run + +Run a deployed pipeline: + +```bash +# Run with JSON parameters +hayhooks pipeline run my_pipeline --param 'query="What is Haystack?"' + +# Run with multiple parameters +hayhooks pipeline run my_pipeline --param 'query="What is Haystack?"' --param 'max_results=5' + +# Upload files +hayhooks pipeline run my_pipeline --file document.pdf --param 'query="Summarize this"' + +# Upload directory +hayhooks pipeline run my_pipeline --dir ./documents --param 'query="Analyze all documents"' + +# Upload multiple files +hayhooks pipeline run my_pipeline --file doc1.pdf --file doc2.txt --param 'query="Compare documents"' +``` + +#### Options for `pipeline run` + +| Option | Short | Description | Default | +|--------|-------|-------------|---------| +| `--file` | | Upload single file | None | +| `--dir` | | Upload directory | None | +| `--param` | | Pass parameters as JSON | None | + +## Status and Monitoring Commands + +### status + +Check server and pipeline status: + +```bash +# Check server status +hayhooks status +``` + +!!! note + There is no `hayhooks health` command in the CLI. Use `hayhooks status` or call HTTP endpoints directly. + +!!! info "CLI Limitations" + - Configuration is managed via environment variables and CLI flags. See [Configuration](../getting-started/configuration.md). + - Development flows (testing, linting) are not exposed as CLI commands. + - Use your process manager or container logs to view logs; Hayhooks uses standard output. + - Advanced export/import/migrate commands are not provided by the Hayhooks CLI at this time. + +## HTTP API Commands + +All CLI commands have corresponding HTTP API endpoints. + +!!! tip "Interactive API Documentation" + Explore and test all HTTP API endpoints interactively: + + - **Swagger UI**: `http://localhost:1416/docs` + - **ReDoc**: `http://localhost:1416/redoc` + + See the [API Reference](../reference/api-reference.md) for complete documentation. + +**Common HTTP API equivalents:** + +### deploy-files + +```bash +# CLI +hayhooks pipeline deploy-files -n my_pipeline ./path/to/pipeline + +# HTTP API +curl -X POST http://localhost:1416/deploy_files \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "my_pipeline", + "description": "My pipeline", + "files": [...], + "overwrite": false + }' +``` + +### deploy-yaml + +```bash +# CLI +hayhooks pipeline deploy-yaml -n my_pipeline pipelines/my_pipeline.yml + +# HTTP API +curl -X POST http://localhost:1416/deploy-yaml \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "my_pipeline", + "description": "My pipeline", + "source_code": "...", + "overwrite": false + }' +``` + +### run + +```bash +# CLI +hayhooks pipeline run my_pipeline --param 'query="What is Haystack?"' + +# HTTP API +curl -X POST http://localhost:1416/my_pipeline/run \ + -H 'Content-Type: application/json' \ + -d '{"query": "What is Haystack?"}' +``` + +## Configuration Files + +### .env File + +Create a `.env` file for configuration: + +```bash +# .env +HAYHOOKS_HOST=0.0.0.0 +HAYHOOKS_PORT=1416 +HAYHOOKS_MCP_PORT=1417 +HAYHOOKS_PIPELINES_DIR=./pipelines +LOG=INFO +``` + +## Error Handling + +### Common Errors + +1. **Server already running** + + ```bash + # Check if server is running + hayhooks status + + # Kill existing process + pkill -f "hayhooks run" + ``` + +2. **Pipeline deployment failed** + + ```bash + # Check server logs with your process manager or container runtime + + # Enable debug logging + LOG=DEBUG hayhooks run + ``` + +3. **Permission denied** + + ```bash + # Check file permissions + ls -la ./path/to/pipeline + + # Fix permissions if needed + chmod +x ./path/to/pipeline/pipeline_wrapper.py + ``` + +### Debug Mode + +Enable debug mode for troubleshooting: + +```bash +# Set debug logging +export LOG=DEBUG + +# Start server with debug logging +hayhooks run +``` + +## Examples + +### Basic Workflow + +```bash +# 1. Start server +hayhooks run --port 1416 + +# 2. In another terminal, deploy pipeline +hayhooks pipeline deploy-files -n chat_pipeline ./pipelines/chat + +# 3. Check status +hayhooks status + +# 4. Run pipeline +hayhooks pipeline run chat_pipeline --param 'query="Hello!"' + +# 5. Check logs +# Use your process manager or container logs +``` + +### Production Deployment + +```bash +# 1. Set environment variables +export HAYHOOKS_HOST=0.0.0.0 +export HAYHOOKS_PORT=1416 +export LOG=INFO + +# 2. Start server with multiple workers +hayhooks run --workers 4 + +# 3. Deploy pipelines +hayhooks pipeline deploy-files -n production_pipeline ./pipelines/production + +# 4. Monitor status +hayhooks status +``` + +## Next Steps + +- [Configuration](../getting-started/configuration.md) - Configuration options +- [Examples](../examples/overview.md) - Working examples diff --git a/docs/features/file-upload-support.md b/docs/features/file-upload-support.md new file mode 100644 index 00000000..e3c746b6 --- /dev/null +++ b/docs/features/file-upload-support.md @@ -0,0 +1,139 @@ +# File Upload Support + +Hayhooks provides built-in support for handling file uploads in your pipelines, making it easy to create applications that process documents, images, and other files. + +## Overview + +File upload support enables you to: + +- Accept file uploads through REST APIs +- Process multiple files in a single request +- Combine file uploads with other parameters +- Build document processing and RAG pipelines + +## Basic Implementation + +To accept file uploads in your pipeline, add a `files` parameter to your `run_api` method: + +```python +from fastapi import UploadFile +from typing import Optional, List + +class PipelineWrapper(BasePipelineWrapper): + def run_api(self, files: Optional[List[UploadFile]] = None, query: str = "") -> str: + if not files: + return "No files provided" + + # Process files here... + return f"Processed {len(files)} files" +``` + +For a complete implementation, see the [RAG System Example](../examples/rag-system.md). + +## API Usage + +### Multipart Form Data Requests + +File uploads use `multipart/form-data` format: + +```bash +# Upload single file +curl -X POST \ + http://localhost:1416/my_pipeline/run \ + -F 'files=@document.pdf' \ + -F 'query="Summarize this document"' + +# Upload multiple files +curl -X POST \ + http://localhost:1416/my_pipeline/run \ + -F 'files=@document1.pdf' \ + -F 'files=@document2.txt' \ + -F 'query="Compare these documents"' + +# Upload with additional parameters +curl -X POST \ + http://localhost:1416/my_pipeline/run \ + -F 'files=@document.pdf' \ + -F 'query="Analyze this document"' +``` + +### Python Client Example + +```python +import requests + +# Upload files +files = [ + ('files', open('document.pdf', 'rb')), + ('files', open('notes.txt', 'rb')) +] + +data = { + 'query': 'Analyze these documents' +} + +response = requests.post( + 'http://localhost:1416/my_pipeline/run', + files=files, + data=data +) + +print(response.json()) +``` + +## CLI Usage + +Hayhooks CLI supports file uploads: + +```bash +# Upload single file +hayhooks pipeline run my_pipeline --file document.pdf --param 'query="Summarize this"' + +# Upload directory +hayhooks pipeline run my_pipeline --dir ./documents --param 'query="Analyze all documents"' + +# Upload multiple files +hayhooks pipeline run my_pipeline --file doc1.pdf --file doc2.txt --param 'query="Compare documents"' + +# Upload with parameters +hayhooks pipeline run my_pipeline --file document.pdf --param 'query="Analyze"' +``` + +## Combining Files with Other Parameters + +You can handle both files and parameters in the same request by adding them as arguments to the `run_api` method: + +```python +from fastapi import UploadFile +from typing import Optional, List + +class PipelineWrapper(BasePipelineWrapper): + def run_api( + self, + files: Optional[List[UploadFile]] = None, + query: str = "", + additional_param: str = "default" + ) -> str: + if files and len(files) > 0: + filenames = [f.filename for f in files if f.filename is not None] + return f"Received files: {', '.join(filenames)} with query: {query}" + + return "No files received" +``` + +## Complete Example: RAG System with File Upload + +For a complete, production-ready example of a RAG system with file uploads, including document indexing and querying with Elasticsearch, see: + +- [RAG System Example](../examples/rag-system.md) - Full RAG implementation guide +- [examples/rag_indexing_query](https://github.com/deepset-ai/hayhooks/tree/main/examples/rag_indexing_query) - Complete working code with: + - Document indexing pipeline with file upload support + - Query pipeline for retrieving and generating answers + - Elasticsearch integration + - Support for PDF, Markdown, and text files + +## Next Steps + +- [PipelineWrapper](../concepts/pipeline-wrapper.md) - Learn about wrapper implementation +- [Examples](../examples/overview.md) - See working examples +- [CLI Commands](cli-commands.md) - CLI usage for file uploads diff --git a/docs/features/mcp-support.md b/docs/features/mcp-support.md new file mode 100644 index 00000000..dc0fb8d5 --- /dev/null +++ b/docs/features/mcp-support.md @@ -0,0 +1,342 @@ +# MCP Support + +Hayhooks supports the [Model Context Protocol](https://modelcontextprotocol.io/) and can act as an [MCP Server](https://modelcontextprotocol.io/docs/concepts/architecture), exposing pipelines and agents as MCP tools for use in AI development environments. + +## Overview + +The Hayhooks MCP Server: + +- Exposes [Core Tools](#core-mcp-tools) for controlling Hayhooks directly from IDEs +- Exposes deployed Haystack pipelines as usable [MCP Tools](#pipeline-tools) +- Supports both [Server-Sent Events (SSE)](#sse-transport) and [Streamable HTTP](#streamable-http-transport) transports +- Integrates with AI development environments like [Cursor](https://cursor.com) and [Claude Desktop](https://claude.ai/download) + +## Requirements + +- Python 3.10+ for MCP support +- Install with `pip install hayhooks[mcp]` + +## Getting Started + +### Install with MCP Support + +```bash +pip install hayhooks[mcp] +``` + +### Start MCP Server + +```bash +hayhooks mcp run +``` + +This starts the MCP server on `HAYHOOKS_MCP_HOST:HAYHOOKS_MCP_PORT` (default: `127.0.0.1:1417`). + +### Configuration + +Environment variables for MCP server: + +```bash +HAYHOOKS_MCP_HOST=127.0.0.1 # MCP server host +HAYHOOKS_MCP_PORT=1417 # MCP server port +``` + +## Transports + +### Streamable HTTP (Recommended) {#streamable-http-transport} + +The preferred transport for modern MCP clients: + +```python +import mcp + +client = mcp.Client("http://localhost:1417/mcp") +``` + +### Server-Sent Events (SSE) {#sse-transport} + +Legacy transport maintained for backward compatibility: + +```python +import mcp + +client = mcp.Client("http://localhost:1417/sse") +``` + +## Core MCP Tools + +Hayhooks provides core tools for managing pipelines: + +### get_all_pipeline_statuses + +Get status of all deployed pipelines: + +```python +result = await client.call_tool("get_all_pipeline_statuses") +``` + +### get_pipeline_status + +Get status of a specific pipeline: + +```python +result = await client.call_tool("get_pipeline_status", {"pipeline_name": "my_pipeline"}) +``` + +### deploy_pipeline + +Deploy a pipeline from files: + +```python +result = await client.call_tool("deploy_pipeline", { + "name": "my_pipeline", + "files": [ + {"name": "pipeline_wrapper.py", "content": "..."}, + {"name": "pipeline.yml", "content": "..."} + ], + "save_files": True, + "overwrite": False +}) +``` + +### undeploy_pipeline + +Undeploy a pipeline: + +```python +result = await client.call_tool("undeploy_pipeline", {"pipeline_name": "my_pipeline"}) +``` + +## Pipeline Tools + +### PipelineWrapper as MCP Tool + +When you deploy a pipeline with `PipelineWrapper`, it's automatically exposed as an MCP tool. + +**MCP Tool Requirements:** + +A [MCP Tool](https://modelcontextprotocol.io/docs/concepts/tools) requires: + +- `name`: The name of the tool +- `description`: The description of the tool +- `inputSchema`: JSON Schema describing the tool's input parameters + +**How Hayhooks Creates MCP Tools:** + +For each deployed pipeline, Hayhooks will: + +- Use the pipeline wrapper `name` as MCP Tool `name` (always present) +- Parse **`run_api` method docstring**: + - If you use Google-style or reStructuredText-style docstrings, use the first line as MCP Tool `description` and the rest as `parameters` (if present) + - Each parameter description will be used as the `description` of the corresponding Pydantic model field (if present) +- Generate a Pydantic model from the `inputSchema` using the **`run_api` method arguments as fields** + +**Example:** + +```python +from pathlib import Path +from typing import List +from haystack import Pipeline +from hayhooks import BasePipelineWrapper + + +class PipelineWrapper(BasePipelineWrapper): + def setup(self) -> None: + pipeline_yaml = (Path(__file__).parent / "chat_with_website.yml").read_text() + self.pipeline = Pipeline.loads(pipeline_yaml) + + def run_api(self, urls: List[str], question: str) -> str: + # + # NOTE: The following docstring will be used as MCP Tool description + # + """ + Ask a question about one or more websites using a Haystack pipeline. + """ + result = self.pipeline.run({"fetcher": {"urls": urls}, "prompt": {"query": question}}) + return result["llm"]["replies"][0] +``` + +### YAML Pipeline as MCP Tool + +YAML-deployed pipelines are also automatically exposed as MCP tools. When you deploy via `hayhooks pipeline deploy-yaml`, the pipeline becomes available as an MCP tool with its input schema derived from the YAML `inputs` section. + +For complete examples and detailed information, see [YAML Pipeline Deployment](../concepts/yaml-pipeline-deployment.md). + +### Skip MCP Tool Listing + +To prevent a pipeline from being listed as an MCP tool: + +```python +class PipelineWrapper(BasePipelineWrapper): + skip_mcp = True # This pipeline won't be listed as an MCP tool + + def setup(self) -> None: + ... + + def run_api(self, ...) -> str: + ... +``` + +## IDE Integration + +### Cursor Integration + +Add Hayhooks MCP Server in Cursor Settings → MCP: + +```json +{ + "mcpServers": { + "hayhooks": { + "url": "http://localhost:1417/mcp" + } + } +} +``` + +Once configured, you can deploy, manage, and run pipelines directly from Cursor chat using the Core MCP Tools. + +For more information about MCP in Cursor, see the [Cursor MCP Documentation](https://cursor.com/docs/context/mcp). + +### Claude Desktop Integration + +Configure Claude Desktop to connect to Hayhooks MCP Server: + +!!! info "Claude Desktop Tiers" + === "Free Tier" + Use [supergateway](https://github.com/supercorp-ai/supergateway) to bridge the connection + + ```json + { + "mcpServers": { + "hayhooks": { + "command": "npx", + "args": ["-y", "supergateway", "--streamableHttp", "http://localhost:1417/mcp"] + } + } + } + ``` + + === "Pro/Max/Teams/Enterprise" + Direct connection via Streamable HTTP or SSE + + ```json + { + "mcpServers": { + "hayhooks": { + "url": "http://localhost:1417/mcp" + } + } + } + ``` + +## Development Workflow + +**Basic workflow:** + +1. Start Hayhooks server: `hayhooks run` +2. Start MCP server: `hayhooks mcp run` (in another terminal) +3. Configure your IDE to connect to the MCP server +4. Deploy and manage pipelines through your IDE using natural language + +## Tool Development + +### Custom Tool Descriptions + +Use docstrings to provide better tool descriptions: + +```python +def run_api(self, urls: List[str], question: str) -> str: + """ + Ask questions about website content using AI. + + This tool analyzes website content and provides answers to user questions. + It's perfect for research, content analysis, and information extraction. + + Args: + urls: List of website URLs to analyze + question: Question to ask about the content + + Returns: + Answer to the question based on the website content + """ + result = self.pipeline.run({"fetcher": {"urls": urls}, "prompt": {"query": question}}) + return result["llm"]["replies"][0] +``` + +### Input Validation + +Hayhooks automatically validates inputs based on your method signature: + +```python +def run_api( + self, + urls: List[str], # Required: List of URLs + question: str, # Required: User question + max_tokens: int = 1000 # Optional: Max tokens +) -> str: + ... +``` + +## Security Considerations + +### Authentication + +Currently, Hayhooks MCP server doesn't include built-in authentication. Consider: + +- Running behind a reverse proxy with authentication +- Using network-level security (firewalls, VPNs) +- Implementing custom middleware for authentication + +### Resource Management + +- Monitor tool execution for resource usage +- Implement timeouts for long-running operations +- Consider rate limiting for production deployments + +## Troubleshooting + +### Common Issues + +#### Connection Refused + +If you cannot connect to the MCP server, ensure the MCP server is running with `hayhooks mcp run`. Check that the port configuration matches (default is `1417`), and verify network connectivity between the client and server. + +#### Tool Not Found + +If an MCP tool is not showing up, verify that the pipeline is properly deployed using `hayhooks status`. Check if the `skip_mcp` class attribute is set to `True` in your `PipelineWrapper`, which would prevent it from being listed. Ensure the `run_api` method is properly implemented with correct type hints. + +#### Input Validation Errors + +If you're getting validation errors when calling tools, check that your method signatures match the expected input types. Verify that all required parameters are being passed and that data types match the type hints in your `run_api` method signature. Review the MCP tool's `inputSchema` to ensure parameter names and types are correct. + +### Debug Commands + +The MCP server exposes the following endpoints: + +- **Streamable HTTP endpoint**: `http://localhost:1417/mcp` - Main MCP protocol endpoint +- **SSE endpoint**: `http://localhost:1417/sse` - Server-Sent Events transport (deprecated) +- **Status/Health Check**: `http://localhost:1417/status` - Returns `{"status": "ok"}` for health monitoring + +#### Testing the health endpoint + +```bash +# Check if MCP server is running +curl http://localhost:1417/status + +# Expected response: +# {"status":"ok"} +``` + +This status endpoint is useful for: + +- Container health checks in Docker/Kubernetes deployments +- Load balancer health probes +- Monitoring and alerting systems +- Verifying the MCP server is running before connecting clients + +Use an MCP-capable client like [supergateway](https://github.com/supercorp-ai/supergateway), [Cursor](https://cursor.com), or [Claude Desktop](https://claude.ai/download) to list and call tools. Example supergateway usage is shown above. + +## Next Steps + +- [PipelineWrapper Guide](../concepts/pipeline-wrapper.md) - Learn how to create MCP-compatible pipeline wrappers +- [Examples](../examples/overview.md) - See working examples of deployed pipelines diff --git a/docs/features/openai-compatibility.md b/docs/features/openai-compatibility.md new file mode 100644 index 00000000..e359771c --- /dev/null +++ b/docs/features/openai-compatibility.md @@ -0,0 +1,286 @@ +# OpenAI Compatibility + +Hayhooks provides OpenAI-compatible endpoints for Haystack pipelines and agents, enabling integration with OpenAI-compatible tools and frameworks. + +!!! tip "Open WebUI Integration" + Looking to integrate with Open WebUI? Check out the complete [Open WebUI Integration](openwebui-integration.md) guide for detailed setup instructions, event handling, and advanced features. + +## Overview + +Hayhooks can automatically generate OpenAI-compatible endpoints if you implement the `run_chat_completion` or `run_chat_completion_async` method in your pipeline wrapper. This makes Hayhooks compatible with any OpenAI-compatible client or tool, including chat interfaces, agent frameworks, and custom applications. + +## Key Features + +- **Automatic Endpoint Generation**: OpenAI-compatible endpoints are created automatically +- **Streaming Support**: Real-time streaming responses for chat interfaces +- **Async Support**: High-performance async chat completion +- **Multiple Integration Options**: Works with various OpenAI-compatible clients +- **Open WebUI Ready**: Full support for [Open WebUI](openwebui-integration.md) with events and tool call interception + +## Implementation + +### Basic Chat Completion + +```python +from typing import List, Union, Generator +from haystack import Pipeline +from hayhooks import get_last_user_message, BasePipelineWrapper, log + +class PipelineWrapper(BasePipelineWrapper): + def setup(self) -> None: + # Initialize your pipeline + pipeline_yaml = (Path(__file__).parent / "pipeline.yml").read_text() + self.pipeline = Pipeline.loads(pipeline_yaml) + + def run_chat_completion(self, model: str, messages: List[dict], body: dict) -> Union[str, Generator]: + log.trace(f"Running pipeline with model: {model}, messages: {messages}, body: {body}") + + question = get_last_user_message(messages) + log.trace(f"Question: {question}") + + # Pipeline run, returns a string + result = self.pipeline.run({"prompt": {"query": question}}) + return result["llm"]["replies"][0] +``` + +### Async Chat Completion with Streaming + +```python +from typing import AsyncGenerator +from hayhooks import async_streaming_generator, get_last_user_message, log + +class PipelineWrapper(BasePipelineWrapper): + def setup(self) -> None: + # Initialize async pipeline + pipeline_yaml = (Path(__file__).parent / "pipeline.yml").read_text() + self.pipeline = AsyncPipeline.loads(pipeline_yaml) + + async def run_chat_completion_async(self, model: str, messages: List[dict], body: dict) -> AsyncGenerator: + log.trace(f"Running pipeline with model: {model}, messages: {messages}, body: {body}") + + question = get_last_user_message(messages) + log.trace(f"Question: {question}") + + # Async streaming pipeline run + return async_streaming_generator( + pipeline=self.pipeline, + pipeline_run_args={"prompt": {"query": question}}, + ) +``` + +## Method Signatures + +### run_chat_completion(...) + +```python +def run_chat_completion(self, model: str, messages: List[dict], body: dict) -> Union[str, Generator]: + """ + Run the pipeline for OpenAI-compatible chat completion. + + Args: + model: The pipeline name + messages: List of messages in OpenAI format + body: Full request body with additional parameters + + Returns: + str: Non-streaming response + Generator: Streaming response generator + """ +``` + +### run_chat_completion_async(...) + +```python +async def run_chat_completion_async(self, model: str, messages: List[dict], body: dict) -> Union[str, AsyncGenerator]: + """ + Async version of run_chat_completion. + + Args: + model: The pipeline name + messages: List of messages in OpenAI format + body: Full request body with additional parameters + + Returns: + str: Non-streaming response + AsyncGenerator: Streaming response generator + """ +``` + +## Generated Endpoints + +When you implement chat completion methods, Hayhooks automatically creates: + +### Chat Endpoints + +- `/{pipeline_name}/chat` - Direct chat endpoint for a specific pipeline +- `/chat/completions` - OpenAI-compatible endpoint (routes to the model specified in request) +- `/v1/chat/completions` - OpenAI API v1 compatible endpoint + +All endpoints support the standard OpenAI chat completion request format: + +```json +{ + "model": "pipeline_name", + "messages": [ + {"role": "user", "content": "Your message"} + ], + "stream": false +} +``` + +### Available Models + +Use the `/v1/models` endpoint to list all deployed pipelines that support chat completion: + +```bash +curl http://localhost:1416/v1/models +``` + +## Streaming Responses + +### Streaming Generator + +```python +from hayhooks import streaming_generator + +def run_chat_completion(self, model: str, messages: List[dict], body: dict) -> Generator: + question = get_last_user_message(messages) + + return streaming_generator( + pipeline=self.pipeline, + pipeline_run_args={"prompt": {"query": question}}, + ) +``` + +### Async Streaming Generator + +```python +from hayhooks import async_streaming_generator + +async def run_chat_completion_async(self, model: str, messages: List[dict], body: dict) -> AsyncGenerator: + question = get_last_user_message(messages) + + return async_streaming_generator( + pipeline=self.pipeline, + pipeline_run_args={"prompt": {"query": question}}, + ) +``` + +## Using Hayhooks with Haystack's OpenAIChatGenerator + +Hayhooks' OpenAI-compatible endpoints can be used as a backend for Haystack's `OpenAIChatGenerator`, enabling you to create pipelines that consume other Hayhooks-deployed pipelines: + +```python +from haystack.components.generators.chat import OpenAIChatGenerator +from haystack.utils import Secret +from haystack.dataclasses import ChatMessage + +# Connect to a Hayhooks-deployed pipeline +client = OpenAIChatGenerator( + model="chat_with_website", # Your deployed pipeline name + api_key=Secret.from_token("not-used"), # Hayhooks doesn't require authentication + api_base_url="http://localhost:1416/v1/", + streaming_callback=lambda chunk: print(chunk.content, end="") +) + +# Use it like any OpenAI client +result = client.run([ChatMessage.from_user("What is Haystack?")]) +print(result["replies"][0].content) +``` + +This enables powerful use cases: + +- **Pipeline Composition**: Chain multiple Hayhooks pipelines together +- **Testing**: Test your pipelines using Haystack's testing tools +- **Hybrid Deployments**: Mix local and remote pipeline execution + +## Examples + +### Sync Chat Pipeline (Non-Streaming) + +```python +class SyncChatWrapper(BasePipelineWrapper): + def setup(self) -> None: + from haystack.components.builders import ChatPromptBuilder + from haystack.components.generators.chat import OpenAIChatGenerator + from haystack.dataclasses import ChatMessage + + template = [ChatMessage.from_user("Answer: {{query}}")] + chat_prompt_builder = ChatPromptBuilder(template=template) + llm = OpenAIChatGenerator(model="gpt-4o-mini") + + self.pipeline = Pipeline() + self.pipeline.add_component("chat_prompt_builder", chat_prompt_builder) + self.pipeline.add_component("llm", llm) + self.pipeline.connect("chat_prompt_builder.prompt", "llm.messages") + + def run_chat_completion(self, model: str, messages: List[dict], body: dict) -> str: + question = get_last_user_message(messages) + result = self.pipeline.run({"chat_prompt_builder": {"query": question}}) + return result["llm"]["replies"][0].content +``` + +### Async Streaming Pipeline + +```python +class AsyncStreamingWrapper(BasePipelineWrapper): + def setup(self) -> None: + from haystack.components.builders import ChatPromptBuilder + from haystack.components.generators.chat import OpenAIChatGenerator + from haystack.dataclasses import ChatMessage + + template = [ChatMessage.from_user("Answer: {{query}}")] + chat_prompt_builder = ChatPromptBuilder(template=template) + llm = OpenAIChatGenerator(model="gpt-4o") + + self.pipeline = Pipeline() + self.pipeline.add_component("chat_prompt_builder", chat_prompt_builder) + self.pipeline.add_component("llm", llm) + self.pipeline.connect("chat_prompt_builder.prompt", "llm.messages") + + async def run_chat_completion_async(self, model: str, messages: List[dict], body: dict) -> AsyncGenerator: + question = get_last_user_message(messages) + return async_streaming_generator( + pipeline=self.pipeline, + pipeline_run_args={"chat_prompt_builder": {"query": question}}, + ) +``` + +## Request Parameters + +The OpenAI-compatible endpoints support standard parameters from the `body` argument: + +```python +def run_chat_completion(self, model: str, messages: List[dict], body: dict) -> str: + # Access additional parameters + temperature = body.get("temperature", 0.7) + max_tokens = body.get("max_tokens", 150) + stream = body.get("stream", False) + + # Use them in your pipeline + result = self.pipeline.run({ + "llm": { + "generation_kwargs": { + "temperature": temperature, + "max_tokens": max_tokens + } + } + }) + return result["llm"]["replies"][0].content +``` + +**Common parameters include:** + +- `temperature`: Controls randomness (0.0 to 2.0) +- `max_tokens`: Maximum number of tokens to generate +- `stream`: Enable streaming responses +- `stop`: Stop sequences +- `top_p`: Nucleus sampling parameter + +See the [OpenAI API reference](https://platform.openai.com/docs/api-reference/chat/create) for the complete list of parameters. + +## Next Steps + +- [Open WebUI Integration](openwebui-integration.md) - Use Hayhooks with Open WebUI chat interface +- [Examples](../examples/overview.md) - Working examples and use cases +- [File Upload Support](file-upload-support.md) - Handle file uploads in pipelines diff --git a/docs/features/openwebui-integration.md b/docs/features/openwebui-integration.md new file mode 100644 index 00000000..a7426074 --- /dev/null +++ b/docs/features/openwebui-integration.md @@ -0,0 +1,311 @@ +# Open WebUI Integration + +Hayhooks provides seamless integration with [Open WebUI](https://openwebui.com/), enabling you to use Haystack pipelines and agents as chat completion backends with full feature support. + +## Overview + +Open WebUI integration allows you to: + +- Use Haystack pipelines as OpenAI-compatible chat backends +- Support streaming responses in real-time +- Send status events to enhance user experience +- Intercept tool calls for better feedback + +## Getting Started + +### Prerequisites + +- Open WebUI instance running +- Hayhooks server running +- Pipeline with chat completion support + +### Configuration + +#### 1. Install Open WebUI + +Please follow the [Open WebUI installation guide](https://docs.openwebui.com/getting-started/quick-start) to install Open WebUI. + +We recommend [using Docker to install Open WebUI](https://docs.openwebui.com/getting-started/quick-start/#quick-start-with-docker-). + +A quick command to install Open WebUI using Docker is: + +```bash +docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -e WEBUI_AUTH=False -v open-webui:/app/backend/data --name open-webui ghcr.io/open-webui/open-webui:main +``` + +This will start Open WebUI on local port 3000, with no authentication, and with the data stored in the `open-webui` volume. It's the easiest way to get started. + +#### 2. Disable Auto-generated Content + +Open WebUI automatically generates content for your pipelines. More precisely, it calls your pipelines to generate tags, title and follow-up messages. Depending on your pipeline, this may not be suitable for this use case. + +We recommend disabling those features as a starting point, then you can enable them if you need them. + +Go to **Admin Settings → Interface** and turn off the following features: + +![open-webui-disable-generated-content](../assets/open-webui-disable-generated-content.png) + +#### 3. Add Hayhooks as an OpenAI compatible API endpoint + +You have two options to connect Hayhooks to Open WebUI: + +##### Option 1: Direct Connection (Recommended) + +First, enable **Direct Connections** in Open WebUI. + +Go to **Admin Settings → Connections** and enable **Direct Connections**: + +![open-webui-enable-direct-connections](../assets/open-webui-enable-direct-connections.png) + +Then go to **Settings → Connections** and add a new connection: + +- **API Base URL**: `http://localhost:1416` +- **API Key**: `any-value` (or leave it empty, it's not used by Hayhooks) + +![open-webui-settings-connections](../assets/open-webui-settings-connections.png) + +##### Option 2: OpenAI API Connection + +Alternatively, you can add Hayhooks as an additional **OpenAI API Connection** from **Admin Settings → Connections**: + +![open-webui-admin-settings-connections](../assets/open-webui-admin-settings-connections.png) + +In both cases, remember to **fill a random value as API key** (Hayhooks doesn't require authentication). + +## Pipeline Implementation + +To make your pipeline work with Open WebUI, implement the `run_chat_completion` or `run_chat_completion_async` method in your `PipelineWrapper`. See the [OpenAI Compatibility](openai-compatibility.md) guide for detailed implementation examples. + +### Non-Streaming Example + +Here's how a non-streaming chat completion looks in Open WebUI: + +![chat-completion-example](../assets/chat-completion.gif) + +### Streaming Example + +With streaming enabled, responses appear in real-time: + +![chat-completion-streaming-example](../assets/chat-completion-streaming.gif) + +## Open WebUI Events + +Hayhooks supports sending events to Open WebUI for enhanced user experience: + +### Available Events + +- **status**: Show progress updates and loading indicators +- **message**: Append content to the current message +- **replace**: Replace the current message content entirely +- **notification**: Show toast notifications (info, success, warning, error) +- **source**: Add references, citations, or code execution results + +### Event Implementation + +```python +from typing import AsyncGenerator, List +from hayhooks import async_streaming_generator, get_last_user_message, BasePipelineWrapper +from hayhooks.open_webui import create_status_event, create_message_event, OpenWebUIEvent + +class PipelineWrapper(BasePipelineWrapper): + async def run_chat_completion_async(self, model: str, messages: List[dict], body: dict) -> AsyncGenerator[str | OpenWebUIEvent, None]: + # Indicate loading + yield create_status_event("Processing your request...", done=False) + + question = get_last_user_message(messages) + + try: + # Stream model output alongside events + result = async_streaming_generator( + pipeline=self.pipeline, + pipeline_run_args={"prompt_builder": {"query": question}}, + ) + + # Optional UI hint + yield create_message_event("✍️ Generating response...") + + async for chunk in result: + yield chunk + + yield create_status_event("Request completed successfully", done=True) + except Exception as e: + yield create_status_event("Request failed", done=True) + yield create_message_event(f"Error: {str(e)}") + raise +``` + +Here's how Open WebUI events enhance the user experience: + +![open-webui-hayhooks-events](../assets/open-webui-hayhooks-events.gif) + +## Tool Call Interception + +For agent pipelines, you can intercept tool calls to provide real-time feedback: + +```python +def on_tool_call_start(tool_name: str, arguments: dict, tool_id: str): + """Called when a tool call starts""" + print(f"Tool call started: {tool_name}") + + +def on_tool_call_end(tool_name: str, arguments: dict, result: dict, error: bool): + """Called when a tool call ends""" + print(f"Tool call ended: {tool_name}, Error: {error}") + + +class PipelineWrapper(BasePipelineWrapper): + def run_chat_completion(self, model: str, messages: List[dict], body: dict) -> Generator: + return streaming_generator( + pipeline=self.pipeline, + pipeline_run_args={"messages": messages}, + on_tool_call_start=on_tool_call_start, + on_tool_call_end=on_tool_call_end, + ) +``` + +Here's an example of tool call interception in action: + +![open-webui-hayhooks-agent-on-tool-calls](../assets/open-webui-hayhooks-agent-on-tool-calls.gif) + +## OpenAPI Tool Server + +Hayhooks can serve as an [OpenAPI Tool Server](https://docs.openwebui.com/openapi-servers/) for Open WebUI, exposing its core API endpoints as tools that can be used directly from the chat interface. + +[OpenAPI Tool Servers](https://docs.openwebui.com/openapi-servers/) are a standard way to integrate external tools and data sources into LLM agents using the widely-adopted OpenAPI specification. This approach offers several advantages: + +- **Standard Protocol**: Uses the established OpenAPI specification - no proprietary protocols to learn +- **Easy Integration**: If you build REST APIs today, you're already familiar with the approach +- **Secure**: Built on HTTP/REST with standard authentication methods (OAuth, JWT, API Keys) +- **Flexible Deployment**: Can be hosted locally or externally without vendor lock-in + +Since Hayhooks exposes its OpenAPI schema at `/openapi.json`, Open WebUI can automatically discover and integrate all available Hayhooks endpoints as tools. + +### Setup + +1. Go to **Settings → Tools** in Open WebUI +2. Add OpenAPI Tool Server: + - **Name**: Hayhooks + - **URL**: `http://localhost:1416/openapi.json` + +![open-webui-settings](../assets/open-webui-openapi-tools.png) + +### Available Tools + +Once configured, the following Hayhooks operations become available as tools in your Open WebUI chat: + +- **Deploy Pipeline**: Deploy new pipelines from your chat interface +- **Undeploy Pipeline**: Remove existing pipelines +- **Run Pipeline**: Execute deployed pipelines with parameters +- **Get Status**: Check the status and list of all deployed pipelines + +This enables you to manage your entire Hayhooks deployment directly through natural language conversations. + +### Example: Deploy a Haystack pipeline from `open-webui` chat interface + +Here's a video example of how to deploy a Haystack pipeline from the `open-webui` chat interface: + +![open-webui-deploy-pipeline-from-chat-example](../assets/open-webui-deploy-pipeline-from-chat.gif) + +## Example: Chat with Website + +Here's a complete example for a website chat pipeline: + +```python +from typing import AsyncGenerator, List +from haystack import Pipeline +from haystack.components.fetchers import LinkContentFetcher +from haystack.components.converters import HTMLToDocument +from haystack.components.builders import ChatPromptBuilder +from haystack.components.generators.chat import OpenAIChatGenerator +from haystack.dataclasses import ChatMessage +from hayhooks import BasePipelineWrapper, async_streaming_generator, get_last_user_message + +class PipelineWrapper(BasePipelineWrapper): + def setup(self) -> None: + fetcher = LinkContentFetcher() + converter = HTMLToDocument() + + template = [ + ChatMessage.from_user( + "Based on this content: {{documents}}\nAnswer: {{query}}" + ) + ] + chat_prompt_builder = ChatPromptBuilder(template=template) + + llm = OpenAIChatGenerator(model="gpt-4o") + + self.pipeline = Pipeline() + self.pipeline.add_component("fetcher", fetcher) + self.pipeline.add_component("converter", converter) + self.pipeline.add_component("chat_prompt_builder", chat_prompt_builder) + self.pipeline.add_component("llm", llm) + self.pipeline.connect("fetcher.content", "converter") + self.pipeline.connect("converter.documents", "chat_prompt_builder.documents") + self.pipeline.connect("chat_prompt_builder.prompt", "llm.messages") + + async def run_chat_completion_async(self, model: str, messages: List[dict], body: dict) -> AsyncGenerator: + question = get_last_user_message(messages) + + # Extract URLs from messages or use defaults + urls = ["https://haystack.deepset.ai"] # Default URL + + return async_streaming_generator( + pipeline=self.pipeline, + pipeline_run_args={ + "fetcher": {"urls": urls}, + "chat_prompt_builder": {"query": question} + }, + ) +``` + +## Troubleshooting + +### Common Issues + +1. **Connection Failed** + - Verify Hayhooks server is running + - Check API URL in Open WebUI settings + - Ensure correct port (1416 by default) + +2. **No Response** + - Check if pipeline implements `run_chat_completion` + - Verify pipeline is deployed + - Check server logs for errors + +3. **Streaming Not Working** + - Ensure `streaming_callback` is set on generator + - Check if `run_chat_completion_async` is implemented + - Verify Open WebUI streaming is enabled + +### Debug Commands + +```bash +# Check Hayhooks status +hayhooks status + +# Check deployed pipelines +curl http://localhost:1416/status + +# Test chat completion endpoint (OpenAI-compatible) +curl -X POST http://localhost:1416/v1/chat/completions \ + -H 'Content-Type: application/json' \ + -d '{ + "model": "my_pipeline", + "messages": [{"role": "user", "content": "test message"}], + "stream": false + }' + +# Test streaming chat completion +curl -X POST http://localhost:1416/v1/chat/completions \ + -H 'Content-Type: application/json' \ + -d '{ + "model": "my_pipeline", + "messages": [{"role": "user", "content": "test message"}], + "stream": true + }' +``` + +## Next Steps + +- [OpenAI Compatibility](openai-compatibility.md) - Implementation details for chat completion methods +- [Open WebUI Events Example](../examples/openwebui-events.md) - Advanced UI integration patterns diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md new file mode 100644 index 00000000..9ba37947 --- /dev/null +++ b/docs/getting-started/configuration.md @@ -0,0 +1,74 @@ +# Configuration + +Hayhooks can be configured through environment variables, command-line arguments, or `.env` files. + +## Configuration Methods + +### Environment Variables + +Set environment variables before starting Hayhooks: + +```bash +export HAYHOOKS_HOST=0.0.0.0 +export HAYHOOKS_PORT=1416 +hayhooks run +``` + +### .env File + +Create a `.env` file in your project root: + +```bash +# .env +HAYHOOKS_HOST=0.0.0.0 +HAYHOOKS_PORT=1416 +HAYHOOKS_PIPELINES_DIR=./pipelines +LOG=INFO +``` + +### Command Line Arguments + +Pass options directly to `hayhooks run`: + +```bash +hayhooks run --host 0.0.0.0 --port 1416 --pipelines-dir ./pipelines +``` + +## Common Configuration Options + +The most frequently used options: + +- `HAYHOOKS_HOST` - Host to bind to (default: `127.0.0.1`) +- `HAYHOOKS_PORT` - Port to listen on (default: `1416`) +- `HAYHOOKS_PIPELINES_DIR` - Pipeline directory for auto-deployment (default: `./pipelines`) +- `LOG` - Log level: `DEBUG`, `INFO`, `WARNING`, `ERROR` (default: `INFO`) + +For the complete list of all environment variables and detailed descriptions, see the [Environment Variables Reference](../reference/environment-variables.md). + +## Example Configurations + +### Development + +```bash +# .env.development +HAYHOOKS_HOST=127.0.0.1 +HAYHOOKS_PORT=1416 +LOG=DEBUG +HAYHOOKS_SHOW_TRACEBACKS=true +``` + +### Production + +```bash +# .env.production +HAYHOOKS_HOST=0.0.0.0 +HAYHOOKS_PORT=1416 +LOG=INFO +HAYHOOKS_SHOW_TRACEBACKS=false +``` + +## Next Steps + +- [Quick Start](quick-start.md) - Get started with basic usage +- [Pipeline Deployment](../concepts/pipeline-deployment.md) - Learn how to deploy pipelines +- [Environment Variables Reference](../reference/environment-variables.md) - Complete configuration reference diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md new file mode 100644 index 00000000..45cadcf5 --- /dev/null +++ b/docs/getting-started/installation.md @@ -0,0 +1,135 @@ +# Installation + +This guide covers how to install Hayhooks and its dependencies. + +## System Requirements + +- Python 3.9+ +- Operating System: Linux, macOS, or Windows +- Memory: Minimum 512MB RAM, 2GB+ recommended +- Storage: Minimum 100MB free space + +## Install from PyPI + +=== "Standard Installation" + + ```bash + pip install hayhooks + ``` + + This includes all core features for deploying and running pipelines. + +=== "With MCP Support" + + ```bash + pip install hayhooks[mcp] + ``` + + Includes all standard features plus [MCP Server](../features/mcp-support.md) support for integration with AI development tools like Cursor and Claude Desktop. + + !!! warning "Python 3.10+ Required" + You'll need to run at least Python 3.10+ to use the MCP Server. + +=== "From Source" + + ```bash + git clone https://github.com/deepset-ai/hayhooks.git + cd hayhooks + pip install -e . + ``` + + Useful for development or testing the latest unreleased features. + +## Verify Installation + +After installation, verify that Hayhooks is installed correctly: + +```bash +# Check version +hayhooks --version + +# Show help +hayhooks --help +``` + +## Development Installation + +If you want to contribute to Hayhooks, we recommend using [Hatch](https://hatch.pypa.io/), the project's build and environment management tool: + +```bash +# Clone the repository +git clone https://github.com/deepset-ai/hayhooks.git +cd hayhooks + +# Install Hatch (if not already installed) +pip install hatch + +# Run unit tests +hatch run test:unit + +# Run integration tests +hatch run test:integration + +# Run tests +hatch run test:all + +# Format code +hatch run fmt + +# Serve documentation locally +hatch run docs:serve +``` + +Hatch automatically manages virtual environments and dependencies for you. See available commands in `pyproject.toml`. + +### Alternative: Manual Installation + +If you prefer manual setup: + +```bash +# Clone the repository +git clone https://github.com/deepset-ai/hayhooks.git +cd hayhooks + +# Create a virtual environment +python -m venv venv +source venv/bin/activate # On Windows: venv\Scripts\activate + +# Install in development mode +pip install -e . +``` + +## Docker Installation + +### Using Docker Hub + +```bash +# Pull the latest image +docker pull deepset/hayhooks:latest + +# Run Hayhooks +docker run -p 1416:1416 deepset/hayhooks:latest +``` + +### Building from Source + +```bash +# Clone the repository +git clone https://github.com/deepset-ai/hayhooks.git +cd hayhooks + +# Build with Docker Buildx Bake (current platform) and load into Docker +cd docker +IMAGE_NAME=hayhooks IMAGE_TAG_SUFFIX=local docker buildx bake --load + +# Run the image +docker run -p 1416:1416 hayhooks:local +``` + +## Next Steps + +After successful installation: + +- [Quick Start](quick-start.md) - Get started with basic usage +- [Configuration](configuration.md) - Configure Hayhooks for your needs +- [Examples](../examples/overview.md) - Explore example implementations diff --git a/docs/getting-started/quick-start-docker.md b/docs/getting-started/quick-start-docker.md new file mode 100644 index 00000000..f7bd5ae8 --- /dev/null +++ b/docs/getting-started/quick-start-docker.md @@ -0,0 +1,153 @@ +# Quick Start with Docker Compose + +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/). + +It's available in the [Hayhooks + Open WebUI Docker Compose repository](https://github.com/deepset-ai/hayhooks-open-webui-docker-compose). + +## Setup Instructions + +### 1. Clone the Repository + +```bash +git clone https://github.com/deepset-ai/hayhooks-open-webui-docker-compose.git +cd hayhooks-open-webui-docker-compose +``` + +### 2. Configure Environment Variables + +Copy the example environment file: + +```bash +cp .env.example .env +``` + +Edit the `.env` file to configure your settings: + +```ini +# Hayhooks Configuration +HAYHOOKS_HOST=0.0.0.0 +HAYHOOKS_PORT=1416 +HAYHOOKS_MCP_PORT=1417 +HAYHOOKS_PIPELINES_DIR=/app/pipelines + +# OpenWebUI Configuration +OPENWEBUI_PORT=3000 +``` + +### 3. Start the Services + +```bash +docker-compose up -d +``` + +This will start: + +- Hayhooks server on port 1416 +- Hayhooks MCP server on port 1417 +- Open WebUI on port 3000 + +### 4. Access the Services + +- **Hayhooks API**: +- **Open WebUI**: +- **Hayhooks API Documentation**: + +### 5. Deploy Example Pipelines + +Install Hayhooks locally to use the CLI: + +```bash +pip install hayhooks +``` + +Then deploy example pipelines: + +```bash +# Deploy a sample pipeline +hayhooks pipeline deploy-files -n chat_with_website examples/pipeline_wrappers/chat_with_website_streaming +``` + +!!! tip "Alternative: Deploy via API" + You can also deploy pipelines using the HTTP API endpoints: `POST /deploy_files` (PipelineWrapper files) or `POST /deploy-yaml` (YAML pipeline definition). See the [API Reference](../reference/api-reference.md#pipeline-management) for details. + +## Configuration Options + +### Environment Variables + +The following environment variables can be configured in `.env`: + +| Variable | Description | Default | +|----------|-------------|---------| +| `HAYHOOKS_HOST` | Host to bind to | `0.0.0.0` | +| `HAYHOOKS_PORT` | Port for Hayhooks API | `1416` | +| `HAYHOOKS_MCP_PORT` | Port for Hayhooks MCP server | `1417` | +| `HAYHOOKS_PIPELINES_DIR` | Directory for pipeline definitions | `/app/pipelines` | +| `OPENWEBUI_PORT` | Port for Open WebUI | `3000` | + +### Volume Mounts + +The Docker Compose setup includes the following volume mounts: + +- **Pipeline Directory**: `/app/pipelines` – Directory mounted inside the Hayhooks container where your pipeline wrappers or YAML files live. Hayhooks auto-deploys anything it finds here at startup. + +## Integrating with Open WebUI + +The Docker Compose setup comes pre-configured to integrate Hayhooks with Open WebUI: + +### 1. Configure Open WebUI + +1. Access Open WebUI at +2. Go to **Settings → Connections** +3. Add a new connection with: + - **API Base URL**: `http://hayhooks:1416/v1` + - **API Key**: `any-value` (not used by Hayhooks) + +### 2. Deploy a Pipeline + +Deploy a pipeline that supports chat completion: + +```bash +hayhooks pipeline deploy-files -n chat_agent examples/pipeline_wrappers/open_webui_agent_events +``` + +Alternatively, use the [API endpoints](../reference/api-reference.md#pipeline-management) (`POST /deploy_files` or `POST /deploy-yaml`). + +### 3. Test the Integration + +1. In Open WebUI, select the Hayhooks backend +2. Start a conversation with your deployed pipeline +3. The pipeline will respond through the Open WebUI interface + +## Troubleshooting + +### Common Issues + +1. **Port Conflicts**: Ensure ports 1416, 1417, and 3000 are available +2. **Permission Issues**: Make sure Docker has proper permissions +3. **Network Issues**: Check that containers can communicate with each other + +### Logs + +Check logs for troubleshooting: + +```bash +# Hayhooks logs +docker-compose logs -f hayhooks + +# Open WebUI logs +docker-compose logs -f openwebui +``` + +### Cleanup + +To stop and remove all containers: + +```bash +docker-compose down -v +``` + +## Next Steps + +- [Configuration](../getting-started/configuration.md) - Learn about advanced configuration +- [Examples](../examples/overview.md) - Explore more examples +- [Open WebUI Integration](../features/openwebui-integration.md) - Deep dive into Open WebUI integration diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md new file mode 100644 index 00000000..909ee781 --- /dev/null +++ b/docs/getting-started/quick-start.md @@ -0,0 +1,89 @@ +# Quick Start + +This guide will help you get started with Hayhooks quickly. + +## Prerequisites + +- Python 3.9+ +- A Haystack pipeline or agent to deploy + +## Installation + +See [Installation](installation.md) for detailed setup instructions. + +Quick install: + +```bash +pip install hayhooks +``` + +## Basic Usage + +### 1. Start Hayhooks + +```bash +hayhooks run +``` + +This will start the Hayhooks server on `http://localhost:1416` by default. + +### 2. Deploy a Pipeline + +Deploy a pipeline using the `deploy-files` command: + +```bash +hayhooks pipeline deploy-files -n chat_with_website examples/pipeline_wrappers/chat_with_website_streaming +``` + +### 3. Check Status + +Verify your pipeline is deployed: + +```bash +hayhooks status +``` + +### 4. Run Your Pipeline + +Run your pipeline via the API: + +=== "cURL" + + ```bash + curl -X POST \ + http://localhost:1416/chat_with_website/run \ + -H 'Content-Type: application/json' \ + -d '{"urls": ["https://haystack.deepset.ai"], "question": "What is Haystack?"}' + ``` + +=== "Python" + + ```python + import requests + + response = requests.post( + "http://localhost:1416/chat_with_website/run", + json={ + "urls": ["https://haystack.deepset.ai"], + "question": "What is Haystack?" + } + ) + print(response.json()) + ``` + +=== "Hayhooks CLI" + + ```bash + hayhooks pipeline run chat_with_website \ + --param 'urls=["https://haystack.deepset.ai"]' \ + --param 'question="What is Haystack?"' + ``` + +## Quick Start with Docker Compose + +For the fastest setup with Open WebUI integration, see [Quick Start with Docker Compose](quick-start-docker.md). + +## Next Steps + +- [Pipeline Deployment](../concepts/pipeline-deployment.md) - Learn deployment methods +- [Examples](../examples/overview.md) - Explore example implementations diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..e618f990 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,160 @@ +# Hayhooks + +**Hayhooks** makes it easy to deploy and serve [Haystack](https://haystack.deepset.ai/) [Pipelines](https://docs.haystack.deepset.ai/docs/pipelines) and [Agents](https://docs.haystack.deepset.ai/docs/agents). + +With Hayhooks, you can: + +- 📦 **Deploy your Haystack pipelines and agents as REST APIs** with maximum flexibility and minimal boilerplate code. +- 🛠️ **Expose your Haystack pipelines and agents over the MCP protocol**, making them available as tools in AI dev environments like [Cursor](https://cursor.com) or [Claude Desktop](https://claude.ai/download). Under the hood, Hayhooks runs as an [MCP Server](https://modelcontextprotocol.io/docs/concepts/architecture), exposing each pipeline and agent as an [MCP Tool](https://modelcontextprotocol.io/docs/concepts/tools). +- 💬 **Integrate your Haystack pipelines and agents with [Open WebUI](https://openwebui.com)** as OpenAI-compatible chat completion backends with streaming support. +- 🕹️ **Control Hayhooks core API endpoints through chat** - deploy, undeploy, list, or run Haystack pipelines and agents by chatting with [Claude Desktop](https://claude.ai/download), [Cursor](https://cursor.com), or any other MCP client. + +[![PyPI - Version](https://img.shields.io/pypi/v/hayhooks.svg)](https://pypi.org/project/hayhooks) +[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/hayhooks.svg)](https://pypi.org/project/hayhooks) +[![Docker image release](https://github.com/deepset-ai/hayhooks/actions/workflows/docker.yml/badge.svg)](https://github.com/deepset-ai/hayhooks/actions/workflows/docker.yml) +[![Tests](https://github.com/deepset-ai/hayhooks/actions/workflows/tests.yml/badge.svg)](https://github.com/deepset-ai/hayhooks/actions/workflows/tests.yml) + +## Quick Start + +### 1. Install Hayhooks + +```bash +# Install Hayhooks +pip install hayhooks +``` + +### 2. Start Hayhooks + +```bash +hayhooks run +``` + +### 3. Create a simple agent + +Create a minimal agent wrapper with streaming chat support and a simple HTTP POST API: + +```python +from typing import AsyncGenerator +from haystack.components.agents import Agent +from haystack.dataclasses import ChatMessage +from haystack.tools import Tool +from haystack.components.generators.chat import OpenAIChatGenerator +from hayhooks import BasePipelineWrapper, async_streaming_generator + + +# Define a Haystack Tool that provides weather information for a given location. +def weather_function(location): + return f"The weather in {location} is sunny." + +weather_tool = Tool( + name="weather_tool", + description="Provides weather information for a given location.", + parameters={ + "type": "object", + "properties": {"location": {"type": "string"}}, + "required": ["location"], + }, + function=weather_function, +) + +class PipelineWrapper(BasePipelineWrapper): + def setup(self) -> None: + self.agent = Agent( + chat_generator=OpenAIChatGenerator(model="gpt-4o-mini"), + system_prompt="You're a helpful agent", + tools=[weather_tool], + ) + + # This will create a POST /my_agent/run endpoint + # `question` will be the input argument and will be auto-validated by a Pydantic model + async def run_api_async(self, question: str) -> str: + result = await self.agent.run_async({"messages": [ChatMessage.from_user(question)]}) + return result["replies"][0].text + + # This will create an OpenAI-compatible /chat/completions endpoint + async def run_chat_completion_async( + self, model: str, messages: list[dict], body: dict + ) -> AsyncGenerator[str, None]: + chat_messages = [ + ChatMessage.from_openai_dict_format(message) for message in messages + ] + + return async_streaming_generator( + pipeline=self.agent, + pipeline_run_args={ + "messages": chat_messages, + }, + ) +``` + +Save as `my_agent_dir/pipeline_wrapper.py`. + +### 4. Deploy it + +```bash +hayhooks pipeline deploy-files -n my_agent ./my_agent_dir +``` + +### 5. Run it + +Call the HTTP POST API (`/my_agent/run`): + +```bash +curl -X POST http://localhost:1416/my_agent/run \ + -H 'Content-Type: application/json' \ + -d '{"question": "What can you do?"}' +``` + +Call the OpenAI-compatible chat completion API (streaming enabled): + +```bash +curl -X POST http://localhost:1416/chat/completions \ + -H 'Content-Type: application/json' \ + -d '{ + "model": "my_agent", + "messages": [{"role": "user", "content": "What can you do?"}] + }' +``` + +Or [integrate it with Open WebUI](features/openai-compatibility.md#open-webui-integration) and start chatting with it! + +## Key Features + +### 🚀 Easy Deployment + +- Deploy Haystack pipelines and agents as REST APIs with minimal setup +- Support for both YAML-based and wrapper-based pipeline deployment +- Automatic OpenAI-compatible endpoint generation + +### 🌐 Multiple Integration Options + +- **MCP Protocol**: Expose pipelines as MCP tools for use in AI development environments +- **Open WebUI Integration**: Use Hayhooks as a backend for Open WebUI with streaming support +- **OpenAI Compatibility**: Seamless integration with OpenAI-compatible tools and frameworks + +### 🔧 Developer Friendly + +- CLI for easy pipeline management +- Flexible configuration options +- Comprehensive logging and debugging support +- Custom route and middleware support + +### 📁 File Upload Support + +- Built-in support for handling file uploads in pipelines +- Perfect for RAG systems and document processing + +## Next Steps + +- [Quick Start Guide](getting-started/quick-start.md) - Get started with Hayhooks +- [Installation](getting-started/installation.md) - Install Hayhooks and dependencies +- [Configuration](getting-started/configuration.md) - Configure Hayhooks for your needs +- [Examples](examples/overview.md) - Explore example implementations + +## Community & Support + +- **GitHub**: [deepset-ai/hayhooks](https://github.com/deepset-ai/hayhooks) +- **Issues**: [GitHub Issues](https://github.com/deepset-ai/hayhooks/issues) +- **Documentation**: [Full Documentation](https://deepset-ai.github.io/hayhooks/) + +Hayhooks is actively maintained by the [deepset](https://deepset.ai/) team. diff --git a/docs/reference/api-reference.md b/docs/reference/api-reference.md new file mode 100644 index 00000000..eef7674b --- /dev/null +++ b/docs/reference/api-reference.md @@ -0,0 +1,322 @@ +# API Reference + +Hayhooks provides a comprehensive REST API for managing and executing Haystack pipelines and agents. + +## Base URL + +```text +http://localhost:1416 +``` + +## Authentication + +Currently, Hayhooks does not include built-in authentication. Consider implementing: + +- Reverse proxy authentication +- Network-level security +- Custom middleware + +## Endpoints + +### Pipeline Management + +#### Deploy Pipeline (files) + +```http +POST /deploy_files +``` + +**Request Body:** + +```json +{ + "name": "pipeline_name", + "files": { + "pipeline_wrapper.py": "...file content...", + "other.py": "..." + }, + "save_files": true, + "overwrite": false +} +``` + +**Response:** + +```json +{ + "status": "success", + "message": "Pipeline deployed successfully" +} +``` + +#### Undeploy Pipeline + +```http +POST /undeploy/{pipeline_name} +``` + +Remove a deployed pipeline. + +**Response:** + +```json +{ + "status": "success", + "message": "Pipeline undeployed successfully" +} +``` + +#### Get Pipeline Status + +```http +GET /status/{pipeline_name} +``` + +Check the status of a specific pipeline. + +**Response:** + +```json +{ + "status": "Up!", + "pipeline": "pipeline_name" +} +``` + +#### Get All Pipeline Statuses + +```http +GET /status +``` + +Get status of all deployed pipelines. + +**Response:** + +```json +{ + "pipelines": [ + "pipeline1", + "pipeline2" + ], + "status": "Up!" +} +``` + +### Pipeline Execution + +#### Run Pipeline + +```http +POST /{pipeline_name}/run +``` + +Execute a deployed pipeline. + +**Request Body:** + +```json +{ + "query": "What is the capital of France?" +} +``` + +**Response:** + +```json +{ + "result": "The capital of France is Paris." +} +``` + +### OpenAI Compatibility + +#### Chat Completion + +```http +POST /chat/completions +POST /v1/chat/completions +``` + +OpenAI-compatible chat completion endpoint. + +**Request Body:** + +```json +{ + "model": "pipeline_name", + "messages": [ + { + "role": "user", + "content": "Hello, how are you?" + } + ], + "stream": false +} +``` + +**Response:** + +```json +{ + "id": "chat-123", + "object": "chat.completion", + "created": 1677652288, + "model": "pipeline_name", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Hello! I'm doing well, thank you for asking." + }, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 12, + "completion_tokens": 20, + "total_tokens": 32 + } +} +``` + +#### Streaming Chat Completion + +Use the same endpoints with `"stream": true`. Hayhooks streams chunks in OpenAI-compatible format. + +### MCP Server + +> MCP runs in a separate Starlette app when invoked via `hayhooks mcp run`. Use the configured Streamable HTTP endpoint `/mcp` or SSE `/sse` depending on your client. See the MCP feature page for details. + +### Interactive API Documentation + +Hayhooks provides interactive API documentation for exploring and testing endpoints: + +- **Swagger UI**: `http://localhost:1416/docs` - Interactive API explorer with built-in request testing +- **ReDoc**: `http://localhost:1416/redoc` - Clean, responsive API documentation + +### OpenAPI Schema + +#### Get OpenAPI Schema + +```http +GET /openapi.json +GET /openapi.yaml +``` + +Get the complete OpenAPI specification for programmatic access or tooling integration. + +## Error Handling + +### Error Response Format + +```json +{ + "error": { + "message": "Error description", + "type": "invalid_request_error", + "code": 400 + } +} +``` + +### Common Error Codes + +- **400 Bad Request**: Invalid request parameters +- **404 Not Found**: Pipeline or endpoint not found +- **500 Internal Server Error**: Server-side error + +## Rate Limiting + +Currently, Hayhooks does not include built-in rate limiting. Consider implementing: + +- Reverse proxy rate limiting +- Custom middleware +- Request throttling + +## Examples + +### Running a Pipeline + +=== "cURL" + + ```bash + curl -X POST http://localhost:1416/chat_pipeline/run \ + -H 'Content-Type: application/json' \ + -d '{"query": "Hello!"}' + ``` + +=== "Python" + + ```python + import requests + + response = requests.post( + "http://localhost:1416/chat_pipeline/run", + json={"query": "Hello!"} + ) + print(response.json()) + ``` + +=== "Hayhooks CLI" + + ```bash + hayhooks pipeline run chat_pipeline --param 'query="Hello!"' + ``` + +### OpenAI-Compatible Chat Completion + +=== "cURL" + + ```bash + curl -X POST http://localhost:1416/v1/chat/completions \ + -H 'Content-Type: application/json' \ + -d '{ + "model": "chat_pipeline", + "messages": [ + {"role": "user", "content": "Hello!"} + ] + }' + ``` + +=== "Python" + + ```python + import requests + + response = requests.post( + "http://localhost:1416/v1/chat/completions", + json={ + "model": "chat_pipeline", + "messages": [ + {"role": "user", "content": "Hello!"} + ] + } + ) + print(response.json()) + ``` + +=== "OpenAI Python SDK" + + ```python + from openai import OpenAI + + client = OpenAI( + base_url="http://localhost:1416/v1", + api_key="not-needed" # Hayhooks doesn't require auth by default + ) + + response = client.chat.completions.create( + model="chat_pipeline", + messages=[ + {"role": "user", "content": "Hello!"} + ] + ) + print(response.choices[0].message.content) + ``` + +## Next Steps + +- [Environment Variables](environment-variables.md) - Configuration options +- [Logging](logging.md) - Logging configuration diff --git a/docs/reference/environment-variables.md b/docs/reference/environment-variables.md new file mode 100644 index 00000000..71302b99 --- /dev/null +++ b/docs/reference/environment-variables.md @@ -0,0 +1,168 @@ +# Environment Variables + +Hayhooks can be configured via environment variables (loaded with prefix `HAYHOOKS_` or `LOG`). This page lists the canonical variables supported by the codebase. + +## Server + +### HAYHOOKS_HOST + +- Default: `localhost` +- Description: Host for the FastAPI app + +### HAYHOOKS_PORT + +- Default: `1416` +- Description: Port for the FastAPI app + +### HAYHOOKS_ROOT_PATH + +- Default: `""` +- Description: Root path to mount the API under (FastAPI `root_path`) + +### HAYHOOKS_PIPELINES_DIR + +- Default: `./pipelines` +- Description: Directory containing pipelines to auto-deploy on startup + +### HAYHOOKS_ADDITIONAL_PYTHON_PATH + +- Default: `""` +- Description: Additional path appended to `sys.path` for wrapper imports + +### HAYHOOKS_USE_HTTPS + +- Default: `false` +- Description: Use HTTPS when the CLI calls the server (affects CLI only) + +### HAYHOOKS_DISABLE_SSL + +- Default: `false` +- Description: Disable SSL verification for CLI calls + +### HAYHOOKS_SHOW_TRACEBACKS + +- Default: `false` +- Description: Include tracebacks in error messages (server and MCP) + +## MCP + +### HAYHOOKS_MCP_HOST + +- Default: `localhost` +- Description: Host for the MCP server + +### HAYHOOKS_MCP_PORT + +- Default: `1417` +- Description: Port for the MCP server + +## CORS + +These map 1:1 to FastAPI CORSMiddleware and the settings in `hayhooks.settings.AppSettings`. + +### HAYHOOKS_CORS_ALLOW_ORIGINS + +- Default: `["*"]` +- Description: List of allowed origins + +### HAYHOOKS_CORS_ALLOW_METHODS + +- Default: `["*"]` +- Description: List of allowed HTTP methods + +### HAYHOOKS_CORS_ALLOW_HEADERS + +- Default: `["*"]` +- Description: List of allowed headers + +### HAYHOOKS_CORS_ALLOW_CREDENTIALS + +- Default: `false` +- Description: Allow credentials + +### HAYHOOKS_CORS_ALLOW_ORIGIN_REGEX + +- Default: `null` +- Description: Regex pattern for allowed origins + +### HAYHOOKS_CORS_EXPOSE_HEADERS + +- Default: `[]` +- Description: Headers to expose in response + +### HAYHOOKS_CORS_MAX_AGE + +- Default: `600` +- Description: Maximum age for CORS preflight responses in seconds + +## Logging + +### LOG (log level) + +- Default: `INFO` +- Description: Global log level (consumed by Loguru). Example: `LOG=DEBUG hayhooks run` + +!!! info "Logging Configuration" + Format/handlers are configured internally; Hayhooks does not expose `HAYHOOKS_LOG_FORMAT` or `HAYHOOKS_LOG_FILE` env vars at this time. + +## Usage Examples + +### Docker + +```bash +docker run -d \ + -e HAYHOOKS_HOST=0.0.0.0 \ + -e HAYHOOKS_PORT=1416 \ + -e HAYHOOKS_PIPELINES_DIR=/app/pipelines \ + -v "$PWD/pipelines:/app/pipelines:ro" \ + -p 1416:1416 \ + deepset/hayhooks:latest +``` + +!!! warning "Pipeline Directory Required" + Without mounting a pipelines directory (or baking pipelines into the image), the server will start but no pipelines will be deployed. + +### Development + +```bash +export HAYHOOKS_HOST=127.0.0.1 +export HAYHOOKS_PORT=1416 +export HAYHOOKS_PIPELINES_DIR=./pipelines +export LOG=DEBUG + +hayhooks run +``` + +### MCP Server startup + +```bash +export HAYHOOKS_MCP_HOST=0.0.0.0 +export HAYHOOKS_MCP_PORT=1417 + +hayhooks mcp run +``` + +### .env file example + +```env +HAYHOOKS_HOST=0.0.0.0 +HAYHOOKS_PORT=1416 +HAYHOOKS_MCP_HOST=0.0.0.0 +HAYHOOKS_MCP_PORT=1417 +HAYHOOKS_PIPELINES_DIR=./pipelines +HAYHOOKS_ADDITIONAL_PYTHON_PATH=./custom_code +HAYHOOKS_USE_HTTPS=false +HAYHOOKS_DISABLE_SSL=false +HAYHOOKS_SHOW_TRACEBACKS=false +HAYHOOKS_CORS_ALLOW_ORIGINS=["*"] +LOG=INFO +``` + +!!! info "Configuration Note" + - Worker count, timeouts, and other server process settings are CLI flags (e.g., `hayhooks run --workers 4`). + - YAML/file saving and MCP exposure are controlled per-deploy via API/CLI flags, not global env vars. + +## Next Steps + +- [Configuration](../getting-started/configuration.md) +- [Logging](logging.md) diff --git a/docs/reference/logging.md b/docs/reference/logging.md new file mode 100644 index 00000000..46555fd3 --- /dev/null +++ b/docs/reference/logging.md @@ -0,0 +1,110 @@ +# Logging + +Hayhooks provides comprehensive logging capabilities for monitoring, debugging, and auditing pipeline execution and server operations. + +## Log Levels + +### Available Levels + +- **TRACE**: Detailed information for debugging +- **DEBUG**: Detailed information for debugging +- **INFO**: General information about server operations +- **SUCCESS**: Success messages +- **WARNING**: Warning messages that don't stop execution +- **ERROR**: Error messages that affect functionality +- **CRITICAL**: Critical errors that may cause server failure + +### Setting Log Level + +```bash +export LOG=debug # or LOG=DEBUG +hayhooks run +``` + +Or in your `.env` file: + +```ini +LOG=info # or LOG=INFO +``` + +Or inline: + +```bash +LOG=debug hayhooks run +``` + +## Log Configuration + +### Environment Variables + +#### LOG + +- **Default**: `info` +- **Description**: Minimum log level to display (consumed by Loguru) +- **Options**: `debug`, `info`, `warning`, `error` + +> Note: Hayhooks does not expose `HAYHOOKS_LOG_FORMAT` or `HAYHOOKS_LOG_FILE` env vars; formatting/handlers are configured internally in the code. + +### Custom Log Format + +> If you need custom formatting, handle it in your host app via Loguru sinks. + +## File Logging + +### Basic File Logging + +> Configure file sinks in your host app using `log.add(...)`. + +### Rotating File Logs + +If you embed Hayhooks programmatically and want custom logging, set up logging in your host app and direct Hayhooks logs there. + +## Pipeline Logging + +The `log` object in Hayhooks is a [Loguru](https://loguru.readthedocs.io/) logger instance. You can use all Loguru features and capabilities in your pipeline code. + +### Basic Usage + +```python +from hayhooks import log + +class PipelineWrapper(BasePipelineWrapper): + def setup(self) -> None: + log.info("Setting up pipeline") + # ... setup code + + def run_api(self, query: str) -> str: + log.debug(f"Processing query: {query}") + try: + result = self.pipeline.run({"prompt": {"query": query}}) + log.info("Pipeline execution completed successfully") + return result["llm"]["replies"][0] + except Exception as e: + log.error(f"Pipeline execution failed: {e}") + raise +``` + +### Execution Time Logging + +```python +import time +from hayhooks import log + +class PipelineWrapper(BasePipelineWrapper): + def run_api(self, query: str) -> str: + start_time = time.time() + + result = self.pipeline.run({"prompt": {"query": query}}) + + execution_time = time.time() - start_time + log.info(f"Pipeline executed in {execution_time:.2f} seconds") + + return result["llm"]["replies"][0] +``` + +For more advanced logging patterns (structured logging, custom sinks, formatting, etc.), refer to the [Loguru documentation](https://loguru.readthedocs.io/). + +## Next Steps + +- [API Reference](api-reference.md) - Complete API documentation +- [Environment Variables](environment-variables.md) - Configuration options diff --git a/examples/README.md b/examples/README.md index 40764b49..2eafe761 100644 --- a/examples/README.md +++ b/examples/README.md @@ -28,7 +28,7 @@ Each example includes: Most examples require: -- Python 3.9+ +- Python 3.9+ (note: MCP-related examples and features require Python 3.10+) - A virtual environment (recommended) - The Hayhooks package: `pip install hayhooks` - Additional dependencies (as specified in each example) diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..4615b4a8 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,131 @@ +site_name: Hayhooks +site_description: Deploy and serve Haystack Pipelines and Agents as REST APIs +site_author: deepset +site_url: https://deepset-ai.github.io/hayhooks + +repo_name: deepset-ai/hayhooks +repo_url: https://github.com/deepset-ai/hayhooks + +nav: + - Home: index.md + - Getting Started: + - Quick Start: getting-started/quick-start.md + - Quick Start with Docker Compose: getting-started/quick-start-docker.md + - Installation: getting-started/installation.md + - Configuration: getting-started/configuration.md + - Core Concepts: + - Pipeline Deployment: concepts/pipeline-deployment.md + - PipelineWrapper: concepts/pipeline-wrapper.md + - YAML Pipeline Deployment: concepts/yaml-pipeline-deployment.md + - Agent Deployment: concepts/agent-deployment.md + - Features: + - OpenAI Compatibility: features/openai-compatibility.md + - MCP Support: features/mcp-support.md + - Open WebUI Integration: features/openwebui-integration.md + - File Upload Support: features/file-upload-support.md + - CLI Commands: features/cli-commands.md + - Advanced Usage: + - Running Pipelines: advanced/running-pipelines.md + - Advanced Configuration: advanced/advanced-configuration.md + - Code Sharing: advanced/code-sharing.md + - Deployment: + - Deployment Guidelines: deployment/deployment_guidelines.md + - Examples: + - Examples Overview: examples/overview.md + - Chat with Website: examples/chat-with-website.md + - RAG System: examples/rag-system.md + - Async Operations: examples/async-operations.md + - Open WebUI Events: examples/openwebui-events.md + - Reference: + - API Reference: reference/api-reference.md + - Environment Variables: reference/environment-variables.md + - Logging: reference/logging.md + - About: + - License: about/license.md + +theme: + logo: assets/deepset-square-logo.png + name: material + language: en + features: + - navigation.tabs + - navigation.sections + - navigation.instant + - navigation.expand + - navigation.top + - search.suggest + - search.highlight + - content.code.copy + - content.code.annotate + - toc.integrate + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: blue + accent: blue + toggle: + icon: material/weather-night + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: blue + accent: blue + toggle: + icon: material/weather-sunny + name: Switch to light mode + +plugins: + - search + - tags + +markdown_extensions: + - admonition + - attr_list + - codehilite: + guess_lang: false + - def_list + - footnotes + - meta + - md_in_html + - toc: + permalink: true + - pymdownx.arithmatex: + generic: true + - pymdownx.betterem: + smart_enable: all + - pymdownx.caret + - pymdownx.critic + - pymdownx.details + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + - pymdownx.highlight + - pymdownx.inlinehilite + - pymdownx.keys + - pymdownx.mark + - pymdownx.smartsymbols + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.tabbed: + alternate_style: true + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.tilde + +extra: + version: + provider: mike + social: + - icon: fontawesome/brands/github + link: https://github.com/deepset-ai/hayhooks + - icon: fontawesome/brands/docker + link: https://hub.docker.com/r/deepset/hayhooks + - icon: fontawesome/brands/twitter + link: https://twitter.com/deepset_ai + +extra_css: [] + +extra_javascript: [] diff --git a/pyproject.toml b/pyproject.toml index 9c2fb401..39acf2da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,6 +64,21 @@ dependencies = [ fmt = "ruff check --fix {args} && ruff format {args}" fmt-check = "ruff check {args} && ruff format --check {args}" +[tool.hatch.envs.docs] +detached = true +dependencies = [ + "mkdocs-material", + "mkdocstrings", + "mkdocs-mermaid2-plugin", + "mkdocs-minify-plugin", + "mkdocs-git-revision-date-localized-plugin", +] + +[tool.hatch.envs.docs.scripts] +serve = "mkdocs serve {args}" +build = "mkdocs build {args}" +deploy = "mkdocs gh-deploy --remote-name origin --force --no-history -m \"Deploy docs via automation\" {args}" + [tool.hatch.envs.test] features = ["mcp"] extra-dependencies = [ diff --git a/src/hayhooks/settings.py b/src/hayhooks/settings.py index 46fd50bf..4ee70e63 100644 --- a/src/hayhooks/settings.py +++ b/src/hayhooks/settings.py @@ -24,10 +24,10 @@ class AppSettings(BaseSettings): # Additional Python path to be added to the Python path additional_python_path: str = "" - # Host for the FastAPI app + # Hayhooks Host host: str = "localhost" - # Port for the FastAPI app + # Hayhooks Port port: int = 1416 # Whether to use HTTPS when running CLI commands