# File Response Support

Hayhooks supports returning binary files (images, PDFs, audio, etc.) directly from `run_api` endpoints. When `run_api` returns a FastAPI [`Response`](https://fastapi.tiangolo.com/advanced/response-directly/) object, Hayhooks passes it straight to the client — bypassing JSON serialization entirely. See also the FastAPI docs on [custom responses](https://fastapi.tiangolo.com/advanced/custom-response/).

## Overview

File response support enables you to:

- Return images, PDFs, audio files, or any binary content from pipelines
- Use FastAPI's `FileResponse`, `StreamingResponse`, or plain `Response`
- Serve generated content (e.g. AI-generated images) directly to clients
- Set custom headers like `Content-Disposition` for download behavior

## Basic Implementation

To return a file from your pipeline, return a FastAPI `Response` (or any subclass) from `run_api`:

```python
from fastapi.responses import FileResponse

class PipelineWrapper(BasePipelineWrapper):
    def setup(self) -> None:
        pass

    def run_api(self, prompt: str) -> FileResponse:
        # Generate or retrieve a file...
        image_path = generate_image(prompt)

        return FileResponse(
            path=image_path,
            media_type="image/png",
            filename="result.png",
        )
```

## Response Types

You can use any FastAPI/Starlette response class:

### FileResponse

Best for serving files from disk:

```python
from fastapi.responses import FileResponse

def run_api(self, document_id: str) -> FileResponse:
    path = self.get_document_path(document_id)
    return FileResponse(
        path=path,
        media_type="application/pdf",
        filename="document.pdf",
    )
```

### Response

Best for returning in-memory bytes:

```python
from fastapi.responses import Response

def run_api(self, prompt: str) -> Response:
    image_bytes = self.generate_image(prompt)
    return Response(
        content=image_bytes,
        media_type="image/png",
        headers={"Content-Disposition": 'inline; filename="image.png"'},
    )
```

### StreamingResponse

Best for large files or on-the-fly generation:

```python
import io
from fastapi.responses import StreamingResponse

def run_api(self, query: str) -> StreamingResponse:
    audio_buffer = self.generate_audio(query)
    return StreamingResponse(
        content=io.BytesIO(audio_buffer),
        media_type="audio/wav",
        headers={"Content-Disposition": 'attachment; filename="audio.wav"'},
    )
```

## How It Works

When Hayhooks deploys a pipeline whose `run_api` return type is a `Response` subclass (or a generator), three things happen at deploy time:

1. **`response_model=None`**: `create_response_model_from_callable` detects the `Response` return type and returns `None` instead of a Pydantic model. This tells FastAPI to skip response validation and not generate a JSON schema for this endpoint.

2. **`response_class`**: `get_response_class_from_callable` returns the concrete response class (e.g. `FileResponse`, `StreamingResponse`) so that OpenAPI docs show the correct Content-Type for the endpoint instead of `application/json`.

3. **At runtime**: The endpoint handler checks if the result is a `Response` instance and returns it directly, skipping JSON wrapping:

    ```python
    # From deploy_utils.py
    if isinstance(result, Response):
        return result
    ```

This is the same mechanism used for streaming generators — both bypass Pydantic serialization. Generators additionally get `StreamingResponse` as their `response_class`.

## API Usage

### curl

```bash
# Generate and download an image
curl -X POST "http://localhost:1416/image_pipeline/run" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "A sunset over mountains"}' \
  --output result.png
```

### Python

```python
import requests

response = requests.post(
    "http://localhost:1416/image_pipeline/run",
    json={"prompt": "A sunset over mountains"},
)

# Save the binary content
with open("result.png", "wb") as f:
    f.write(response.content)
```

### Browser

File responses with `Content-Disposition: inline` will display directly in the browser. Use `Content-Disposition: attachment` to trigger a download.

## Complete Example: Image Generation

This example uses the Hugging Face Inference API to generate images from text prompts:

```python
import tempfile

from fastapi.responses import FileResponse

from hayhooks import BasePipelineWrapper, log

DEFAULT_MODEL = "black-forest-labs/FLUX.1-schnell"


class PipelineWrapper(BasePipelineWrapper):
    """Generate images from text prompts using Hugging Face Inference API."""

    def setup(self) -> None:
        from huggingface_hub import InferenceClient

        self.client = InferenceClient()

    def run_api(
        self,
        prompt: str,
        width: int = 512,
        height: int = 512,
        model: str = DEFAULT_MODEL,
    ) -> FileResponse:
        """Generate an image from a text prompt and return it as a PNG file."""
        log.info("Generating image for prompt: '{}'", prompt)

        image = self.client.text_to_image(
            prompt=prompt, model=model, width=width, height=height,
        )

        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
            image.save(tmp, format="PNG")

        return FileResponse(
            path=tmp.name,
            media_type="image/png",
            filename="generated_image.png",
        )
```

Deploy and test:

```bash
# Deploy
hayhooks run --pipelines-dir examples/pipeline_wrappers/image_generation

# Generate an image
curl -X POST "http://localhost:1416/image_generation/run" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "A cat sitting on a rainbow"}' \
  --output generated_image.png
```

For the full example code, see [examples/pipeline_wrappers/image_generation](https://github.com/deepset-ai/hayhooks/tree/main/examples/pipeline_wrappers/image_generation).

## Next Steps

- [PipelineWrapper](../concepts/pipeline-wrapper.md) - Learn about wrapper implementation
- [File Upload Support](file-upload-support.md) - Accept file uploads in pipelines
- [Examples](../examples/overview.md) - See working examples
