Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.git
.venv
__pycache__
*.pyc
.pytest_cache
.ruff_cache
.mypy_cache
.DS_Store
logs
notebooks
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,9 @@ venv.bak
data/sample/
data/output/
logs/

# Deployment
temp/

# W&B files
wandb/
wandb/
9 changes: 9 additions & 0 deletions Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{$SERVER_NAME} {
encode zstd gzip

request_body {
max_size 50MB
}

reverse_proxy metadata-demo:8501
}
19 changes: 19 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM python:3.11-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1

WORKDIR /app

RUN apt-get update \
&& apt-get install -y --no-install-recommends make \
&& rm -rf /var/lib/apt/lists/* \
&& pip install --no-cache-dir uv

COPY . .
RUN make docker-install-demo

EXPOSE 8501

CMD ["make", "docker-run-demo"]
26 changes: 21 additions & 5 deletions demo/workflows/metadata_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@
from pathlib import Path
from typing import Any, Callable

import numpy as np
import pandas as pd

from src.config import LLM_PROVIDER, PLANNING_TEMPERATURE, get_model_name
from src.config import (
DEFAULT_TOPOLOGY,
LLM_PROVIDER,
PLANNING_TEMPERATURE,
get_model_name,
)
from src.context import create_context
from src.orchestrator import Orchestrator
from src.standards import METADATA_STANDARDS, load_metadata_standard
Expand Down Expand Up @@ -69,7 +75,7 @@ def generate_metadata(
file_name: str,
file_bytes: bytes,
standard_name: str,
topology_name: str = "default",
topology_name: str = DEFAULT_TOPOLOGY,
progress_callback: Callable[[str, Any | None], None] | None = None,
) -> dict[str, Any]:
"""Run metadata extraction for an uploaded file and return JSON-friendly output.
Expand Down Expand Up @@ -103,7 +109,7 @@ def generate_metadata(
try:
dataset_name = Path(file_name).stem
metadata_standard = load_metadata_standard(standard_name)
context = create_context(temp_path, name=dataset_name)
context = create_context({"data": temp_path}, name=dataset_name)
_publish_progress(progress_callback, "context_created", context.to_dict())

orchestrator = Orchestrator(
Expand Down Expand Up @@ -213,7 +219,17 @@ def _to_displayable(value: Any) -> Any:
A JSON-friendly representation of the value.
"""
if hasattr(value, "model_dump"):
return value.model_dump(mode="json")
if isinstance(value, (dict, list, str, int, float, bool)) or value is None:
return _to_displayable(value.model_dump(mode="python"))
if isinstance(value, dict):
return {str(key): _to_displayable(item) for key, item in value.items()}
if isinstance(value, (list, tuple, set)):
return [_to_displayable(item) for item in value]
if isinstance(value, np.ndarray):
return _to_displayable(value.tolist())
if isinstance(value, np.generic):
return _to_displayable(value.item())
if isinstance(value, pd.Timestamp):
return value.isoformat()
if isinstance(value, (str, int, float, bool)) or value is None:
return value
return json.loads(json.dumps(value, default=str))
3 changes: 3 additions & 0 deletions deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
git clone --single-branch --branch deploy https://github.com/naliATsurf/metadata_agent.git
cd metadata_agent
sudo docker compose up --build
27 changes: 27 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
services:
metadata-demo:
build: .
restart: unless-stopped
env_file:
- .env
expose:
- "8501"

caddy:
image: caddy:2-alpine
restart: unless-stopped
depends_on:
- metadata-demo
environment:
SERVER_NAME: ${SERVER_NAME:?Set SERVER_NAME in .env to your public domain}
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config

volumes:
caddy_data:
caddy_config:
149 changes: 148 additions & 1 deletion docs/demo.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,163 @@
# Streamlit Demo

## Run

```bash
uv run --group demo streamlit run demo_app.py
```
or

or

```bash
make demo
```

## Deploy as a web server with Docker Compose

This repository includes a Docker Compose setup for running the Streamlit demo
behind Caddy with automatic HTTPS:

- `metadata-demo`: builds the app image from `Dockerfile` and runs Streamlit on
port `8501` inside the Compose network.
- `caddy`: exposes ports `80` and `443`, gets and renews TLS certificates, and
proxies browser traffic to the Streamlit container.

### 1. Prepare the server

Install Docker Engine and the Compose plugin on the server, then clone the
repository:

```bash
git clone <repository-url>
cd metadata_agent
```

The server must allow inbound traffic on ports `80` and `443`. Caddy uses port
`80` for the HTTP-to-HTTPS redirect and Let's Encrypt validation, then serves
the app over HTTPS on port `443`.

### 2. Create `.env`

Docker Compose loads `.env` for both Compose variables and application
configuration. Create it in the repository root:

```bash
LLM_PROVIDER=surf
SURF_API_KEY=your_surf_api_key
SURF_API_BASE=
LLM_MODEL=
DEFAULT_TOPOLOGY=single
SERVER_NAME=metadata.example.org
```

Provider choices:

```bash
# Google
LLM_PROVIDER=google
GOOGLE_API_KEY=your_google_api_key

# OpenAI
LLM_PROVIDER=openai
OPENAI_API_KEY=your_openai_api_key

# SURF/OpenAI-compatible endpoint
LLM_PROVIDER=surf
SURF_API_KEY=your_surf_api_key
SURF_API_BASE=
```

`DEFAULT_TOPOLOGY=single` is a good web-demo default because it avoids the extra
multi-player debate calls used by heavier topologies.

Set `SERVER_NAME` to the public domain that resolves to your server. Caddy needs
a real hostname for public Let's Encrypt certificates.

### 3. Build and start the service

```bash
docker compose up -d --build
```

Open the app:

```text
https://metadata.example.org/
```

For local testing on the server:

```bash
curl -I https://metadata.example.org/
```

### 4. Watch logs

Use the repository helper:

```bash
make docker-logs
```

or plain Compose:

```bash
docker compose logs -f metadata-demo
docker compose logs -f caddy
```

The Streamlit container should show `streamlit run demo_app.py`. Caddy should
show certificate provisioning logs the first time it starts, then proxy requests
to `metadata-demo:8501`.

### 5. Update the deployment

Pull the latest code, rebuild the image, and restart:

```bash
git pull
docker compose up -d --build
```

To stop the service:

```bash
docker compose down
```

The Caddy certificate cache is stored in named Docker volumes. Use plain
`docker compose down` for normal restarts so certificates are preserved.

### 6. Troubleshooting

If the page does not load, check:

```bash
docker compose ps
docker compose logs metadata-demo
docker compose logs caddy
```

Common causes:

- Missing API key in `.env`: the app starts, but metadata generation fails when
the selected provider is called.
- Port `80` or `443` already in use: stop the existing service or change the
published ports in `docker-compose.yml`.
- `SERVER_NAME` is missing or is not a public DNS name: set it in `.env` to the
domain that points at the server.
- Domain not routed to the server: test with:
```bash
curl -H 'Host: metadata.example.org' http://SERVER_IP/
```
- Long metadata generation: keep `DEFAULT_TOPOLOGY=single` for the demo, then
use `fast`, `default`, or `thorough` only when you explicitly want more LLM
calls.

## Folder roles

### `demo_app.py`

Streamlit entry point that configures and launches the demo app.

### `demo/`
Expand Down
1 change: 1 addition & 0 deletions examples/generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ def log_step_timing(step_name: str, start: float) -> float:

# 5. Collect and persist the final metadata output produced by the plan.
log_step_section(5, "Write metadata")

metadata_output = result.final_workspace['metadata_output']
output_dir = Path(example_config.OUTPUT_DIR)
output_dir.mkdir(parents=True, exist_ok=True)
Expand Down
31 changes: 30 additions & 1 deletion makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ PYTHON-VERSION := 3.11
PYTHON := $(VENV)/bin/python
RUFF := $(VENV)/bin/ruff

.PHONY: help uv-setup uv-clean activate install-docs docs docs-update docs-clean lint compile test ci
.PHONY: help uv-setup uv-clean activate install-docs docs docs-update docs-clean install-demo demo docker-install-demo docker-run-demo docker-build docker-up docker-down docker-logs lint compile test ci

help:
@printf '%s\n' \
Expand All @@ -18,6 +18,13 @@ help:
' make docs - build the documentation and open it in the browser' \
' make docs-update - update the documentation' \
' make docs-clean - clean the generated documentation files' \
' make demo - install demo dependencies and launch Streamlit locally' \
' make docker-install-demo - install locked demo dependencies for Docker' \
' make docker-run-demo - launch Streamlit for Docker' \
' make docker-build - build the Docker image' \
' make docker-up - build and start the Docker Compose service' \
' make docker-down - stop the Docker Compose service' \
' make docker-logs - follow Docker Compose service logs' \
' make lint - run ruff linter on the codebase' \
' make compile - compile the source code to check for syntax errors' \
' make test - run unit tests' \
Expand Down Expand Up @@ -64,6 +71,24 @@ install-demo:
demo: install-demo
$(UV) run --group demo streamlit run demo_app.py

docker-install-demo:
$(UV) sync --locked --no-default-groups --group demo

docker-run-demo:
$(UV) run --no-sync --group demo streamlit run demo_app.py --server.address=0.0.0.0 --server.port=8501

docker-build:
docker compose build

docker-up:
docker compose up -d --build

docker-down:
docker compose down

docker-logs:
docker compose logs -f metadata-demo caddy

lint:
$(UV) run ruff check .

Expand All @@ -77,3 +102,7 @@ ci-install:
$(UV) sync --locked --no-default-groups

ci: lint compile test

tui:
$(UV) pip install -e .
metadata-agent --tui
Loading
Loading