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
36 changes: 36 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,42 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project uses **CalVer `YY.M.PP`** (PEP 440 may normalise patch numbers
for the Python wheel — e.g. `26.06.00` → `26.6.0`).

## [26.6.10] - 2026-06-15

### Changed

- **Adopted pyfly's separate management port (app `8080`, management `9090`).**
Upgraded to pyfly `v26.06.103`, which serves the actuator (`/actuator/*`) and
the admin dashboard (`/admin`) on a dedicated management port
(`pyfly.management.server.port`, default `9090`) instead of the business API
port. flydocs now runs the API on **`8080`** (`pyfly.server.port`, was `8400`)
and exposes actuator/admin/health on **`9090`**:
- `pyfly.yaml`, `IDPSettings.port` and `FLYDOCS_PORT` default to `8080`;
`pyfly.management.server.port: 9090` is configured explicitly.
- `Dockerfile` exposes `8080` + `9090`; `docker-compose` maps both for the API
and exposes each worker's management port (`9091`/`9092`), and every
health-check now probes `:9090/actuator/health/readiness`.
- Worker health server (`worker_health_port`) defaults to `9090` to match.
- **Migration:** point load balancers / clients at `:8080` for the API and
Kubernetes probes / Prometheus at `:9090`. Set
`PYFLY_MANAGEMENT_SERVER_PORT=8080` to collapse back to a single port.

- **`pyfly` is now consumed from its published GitHub tag.** `[tool.uv.sources]`
pins `pyfly` to `git tag v26.06.103` (matching the `fireflyframework-agentic`
pattern) instead of the local editable path; the dependency floor is
`>=26.6.103`.

### Fixed

- `main.py` read the removed `pyfly.web.host` key; it now reads
`pyfly.server.host` (Spring `server.address` parity).

### Added

- **Python SDK:** `Client` / `AsyncClient` accept an optional `management_url`
so `health()` can target the management port (`:9090`) while API calls use the
business `base_url` (`:8080`); back-compatible — unset falls back to `base_url`.

## [26.6.9] - 2026-06-15

### Fixed
Expand Down
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,9 @@ RUN find /app -type f -exec chmod a+r {} + \
ENV PYTHONPATH=/app/src

USER idp
EXPOSE 8400
# 8080 = business API (pyfly.server.port); 9090 = management (actuator + admin,
# pyfly.management.server.port) and the worker health server.
EXPOSE 8080 9090

ENTRYPOINT ["/app/docker-entrypoint.sh"]
CMD ["serve"]
8 changes: 4 additions & 4 deletions QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ The repo ships with a docker-compose stack that brings up the service, a Postgre
```bash
git clone https://github.com/firefly-operationOS/flydocs.git
cd flydocs
task docker:up:test # serves http://localhost:8400 backed by a mock LLM
task docker:up:test # serves http://localhost:8080 backed by a mock LLM
```

While it boots, verify the readiness probe:

```bash
curl http://localhost:8400/actuator/health/readiness
curl http://localhost:9090/actuator/health/readiness
# {"status":"UP","components":{"database_health":...,"eda_health":...}}
```

Expand All @@ -35,7 +35,7 @@ B64=$(base64 < invoice.pdf | tr -d '\n')

# 2. POST a minimal ExtractionRequest. ``document_types[]`` declares what to extract;
# ``files[]`` carries the binary. Everything else has sensible defaults.
curl -sS http://localhost:8400/api/v1/extract \
curl -sS http://localhost:8080/api/v1/extract \
-H 'Content-Type: application/json' \
-d @- <<JSON | jq
{
Expand Down Expand Up @@ -115,7 +115,7 @@ That's the **mandatory pipeline** — multimodal extract + bbox. Everything else

| Symptom | Likely cause |
|-------------------------------------------------------------------------------|---------------------------------------------------------------------------|
| `curl: (7) Failed to connect to localhost port 8400` | Service not up yet. `docker compose ps` and check `task docker:logs`. |
| `curl: (7) Failed to connect to localhost port 8080` | Service not up yet. `docker compose ps` and check `task docker:logs`. |
| `400 Bad Request` / `422 invalid_base64` | `content_base64` not strict base64 (e.g. literal newlines). Use `base64 \| tr -d '\\n'`. |
| `413 file_too_large` | File over `FLYDOCS_MAX_BYTES`. Split or compress. |
| `408 timeout` | Pipeline exceeded the sync ceiling. Retry through `POST /api/v1/extractions`. |
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ Two terminals — the API serves HTTP, the worker drains the EDA bus.

```bash
# Terminal A
task dev:serve # uvicorn on http://localhost:8400
task dev:serve # uvicorn on http://localhost:8080
# OpenAPI: /docs
# Health: /actuator/health/readiness
# PyFly admin: /admin
Expand All @@ -181,7 +181,7 @@ sending traffic.
### 5. Your first synchronous extraction

```bash
curl -s http://localhost:8400/api/v1/extract \
curl -s http://localhost:8080/api/v1/extract \
-H 'content-type: application/json' \
-d @docs/examples/extract.json | jq '.documents[0].field_groups'
```
Expand All @@ -201,7 +201,7 @@ then POST it. The submit returns immediately with a `202` + an
to your `callback_url` when it finishes:

```bash
curl -s http://localhost:8400/api/v1/extractions \
curl -s http://localhost:8080/api/v1/extractions \
-H 'content-type: application/json' \
-H 'idempotency-key: '"$(uuidgen)" \
-d '{
Expand All @@ -228,8 +228,8 @@ Poll state if you don't want to wait for the webhook:

```bash
EXT_ID=ext_01HEM2ZZ7M0Q8...
curl -s http://localhost:8400/api/v1/extractions/$EXT_ID
curl -s http://localhost:8400/api/v1/extractions/$EXT_ID/result | jq
curl -s http://localhost:8080/api/v1/extractions/$EXT_ID
curl -s http://localhost:8080/api/v1/extractions/$EXT_ID/result | jq
```

The webhook payload is the unified `EventEnvelope` (`event_id`,
Expand Down Expand Up @@ -464,7 +464,7 @@ task deps:install # uv sync --extra dev
task lint:check # ruff + pyright
task test # unit suite (~26 tests, <1s)
task test:llm # real-LLM smoke test (needs the provider key matching FLYDOCS_MODEL)
task dev:serve # API on :8400
task dev:serve # API on :8080
task dev:worker # async job consumer
task migrate # alembic upgrade head
task docker:build # build the production image
Expand Down
11 changes: 6 additions & 5 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
version: "3"

vars:
PORT: '{{.PORT | default "8400"}}'
PORT: '{{.PORT | default "8080"}}'
MGMT_PORT: '{{.MGMT_PORT | default "9090"}}'
COMPOSE: docker compose
COMPOSE_TEST: docker compose -f docker-compose.yml -f docker-compose.test.yml

Expand Down Expand Up @@ -145,7 +146,7 @@ tasks:
- '{{.COMPOSE}} up -d --build'
- 'echo "API -> http://localhost:{{.PORT}}"'
- 'echo "docs -> http://localhost:{{.PORT}}/docs"'
- 'echo "health-> http://localhost:{{.PORT}}/actuator/health"'
- 'echo "health-> http://localhost:{{.MGMT_PORT}}/actuator/health"'

docker:logs:
desc: Tail logs from every container.
Expand Down Expand Up @@ -179,17 +180,17 @@ tasks:
health:
desc: Curl the /actuator/health endpoint of the running API.
cmds:
- 'curl -fsS http://localhost:{{.PORT}}/actuator/health | jq .'
- 'curl -fsS http://localhost:{{.MGMT_PORT}}/actuator/health | jq .'

health:readiness:
desc: Curl /actuator/health/readiness -- shows the database + EDA components.
cmds:
- 'curl -fsS http://localhost:{{.PORT}}/actuator/health/readiness | jq .'
- 'curl -fsS http://localhost:{{.MGMT_PORT}}/actuator/health/readiness | jq .'

health:liveness:
desc: Curl /actuator/health/liveness -- cheap, no broker pings.
cmds:
- 'curl -fsS http://localhost:{{.PORT}}/actuator/health/liveness | jq .'
- 'curl -fsS http://localhost:{{.MGMT_PORT}}/actuator/health/liveness | jq .'

eda:outbox:
desc: |
Expand Down
17 changes: 13 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ services:
container_name: flydocs-api
command: ["serve"]
ports:
- "${IDP_PORT:-8400}:8400"
- "${IDP_PORT:-8080}:8080"
- "${IDP_MGMT_PORT:-9090}:9090"
# Optional .env -- compose v2 honours ``required: false`` so the stack
# boots even without a local .env (provider keys fall back to the host
# shell env). ``task docker:up`` also auto-creates .env from env_template.
Expand All @@ -97,7 +98,7 @@ services:
gotenberg:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "--fail", "http://localhost:8400/actuator/health/readiness"]
test: ["CMD", "curl", "--fail", "http://localhost:9090/actuator/health/readiness"]
interval: 5s
timeout: 3s
retries: 30
Expand All @@ -107,6 +108,10 @@ services:
image: flydocs:latest
container_name: flydocs-worker
command: ["worker"]
# Worker health/management server (pyfly actuator) on 9090 inside the
# container; mapped to a distinct host port to avoid clashing with the API.
ports:
- "${IDP_WORKER_MGMT_PORT:-9091}:9090"
env_file:
- path: .env
required: false
Expand All @@ -126,7 +131,7 @@ services:
gotenberg:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "--fail", "http://localhost:8400/actuator/health/readiness"]
test: ["CMD", "curl", "--fail", "http://localhost:9090/actuator/health/readiness"]
interval: 5s
timeout: 3s
retries: 30
Expand All @@ -144,6 +149,10 @@ services:
image: flydocs:latest
container_name: flydocs-bbox-worker
command: ["bbox-worker"]
# Worker health/management server (pyfly actuator) on 9090 inside the
# container; mapped to a distinct host port to avoid clashing with the API.
ports:
- "${IDP_BBOX_WORKER_MGMT_PORT:-9092}:9090"
env_file:
- path: .env
required: false
Expand All @@ -169,7 +178,7 @@ services:
gotenberg:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "--fail", "http://localhost:8400/actuator/health/readiness"]
test: ["CMD", "curl", "--fail", "http://localhost:9090/actuator/health/readiness"]
interval: 5s
timeout: 3s
retries: 30
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ DTOs documented here. Two paths:

```bash
# Against a running service:
curl -s http://localhost:8400/openapi.json | jq
curl -s http://localhost:8080/openapi.json | jq

# Or via the task target (writes to ./openapi.json):
task openapi
Expand Down
4 changes: 2 additions & 2 deletions docs/cicd.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,11 +242,11 @@ spec:
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8400
port: 9090
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8400
port: 9090
```

`/actuator/health/readiness` reflects the DB + EDA bus state via the
Expand Down
8 changes: 4 additions & 4 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ The full list lives in [`env_template`](../env_template). The hot
ones:

```env
FLYDOCS_PORT=8400
FLYDOCS_PORT=8080
FLYDOCS_LOG_LEVEL=INFO

FLYDOCS_DATABASE_URL=postgresql+asyncpg://idp:s3cret@db:5432/flydocs
Expand Down Expand Up @@ -206,11 +206,11 @@ step, not a request handler.

```yaml
livenessProbe:
httpGet: { path: /actuator/health/liveness, port: 8400 }
httpGet: { path: /actuator/health/liveness, port: 9090 }
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet: { path: /actuator/health/readiness, port: 8400 }
httpGet: { path: /actuator/health/readiness, port: 9090 }
initialDelaySeconds: 5
periodSeconds: 5
```
Expand Down Expand Up @@ -255,7 +255,7 @@ task inside the worker process, so all three workloads take the probes
above. Specifics:

- **Port.** `FLYDOCS_WORKER_HEALTH_PORT` when set, otherwise
`FLYDOCS_PORT` (default 8400). `0` disables the server — useful when
`FLYDOCS_PORT` (default 8080). `0` disables the server — useful when
`serve` and `worker` share a host in dev. The server binds `0.0.0.0`
because the kubelet probes the pod IP, never loopback.
- **Probe semantics.** Indicators discovered from the DI container
Expand Down
8 changes: 4 additions & 4 deletions docs/migration-v0-to-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,7 @@ import com.firefly.flydocs.sdk.model.WebhookEnvelope;
**Python (v0):**

```python
client = FlydocsClient(base_url="http://localhost:8400")
client = FlydocsClient(base_url="http://localhost:8080")
result = client.extract(ExtractionRequest(
documents=[DocumentInput.from_path("invoice.pdf")],
docs=[DocSpec(
Expand All @@ -788,7 +788,7 @@ print(result.request_id, result.model)
**Python (v1):**

```python
client = FlydocsClient(base_url="http://localhost:8400")
client = FlydocsClient(base_url="http://localhost:8080")
result = client.extract(ExtractionRequest(
files=[FileInput.from_path("invoice.pdf")],
document_types=[DocumentTypeSpec(
Expand All @@ -802,7 +802,7 @@ print(result.id, result.pipeline.model)
**Java (v0):**

```java
FlydocsClient client = FlydocsClient.builder().baseUrl("http://localhost:8400").build();
FlydocsClient client = FlydocsClient.builder().baseUrl("http://localhost:8080").build();
ExtractionResult result = client.extract(ExtractionRequest.of(
List.of(DocumentInput.ofPath(Path.of("invoice.pdf"))),
List.of(DocSpec.builder()
Expand All @@ -815,7 +815,7 @@ System.out.println(result.requestId() + " " + result.model());
**Java (v1):**

```java
FlydocsClient client = FlydocsClient.builder().baseUrl("http://localhost:8400").build();
FlydocsClient client = FlydocsClient.builder().baseUrl("http://localhost:8080").build();
ExtractionResult result = client.extract(ExtractionRequest.of(
List.of(FileInput.ofPath(Path.of("invoice.pdf"))),
List.of(DocumentTypeSpec.builder()
Expand Down
6 changes: 3 additions & 3 deletions docs/payload-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -699,18 +699,18 @@ with `?wait_for_bboxes=true&timeout=60` on

```bash
# 1. Submit
curl -sS http://localhost:8400/api/v1/extractions \
curl -sS http://localhost:8080/api/v1/extractions \
-H 'content-type: application/json' \
-H 'idempotency-key: '"$(uuidgen)" \
-d @request.json
# → 202 {"id": "ext_01HEM...", "status": "queued", ...}

# 2. Poll state
curl -sS http://localhost:8400/api/v1/extractions/ext_01HEM...
curl -sS http://localhost:8080/api/v1/extractions/ext_01HEM...
# → 200 {"id":"ext_01HEM...","status":"running",...}

# 3. Fetch result (long-poll for grounded bboxes)
curl -sS 'http://localhost:8400/api/v1/extractions/ext_01HEM.../result?wait_for_bboxes=true&timeout=120'
curl -sS 'http://localhost:8080/api/v1/extractions/ext_01HEM.../result?wait_for_bboxes=true&timeout=120'
# → 200 {"id":"ext_01HEM...","result":{...ExtractionResult...}}
```

Expand Down
14 changes: 10 additions & 4 deletions env_template
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,18 @@
# Service
# ----------------------------------------------------------------------------
FLYDOCS_LOG_LEVEL=INFO
FLYDOCS_PORT=8400
# Business API port (pyfly.server.port). The actuator (/actuator/*) and the
# admin dashboard (/admin) are served on the separate management port 9090
# (pyfly.management.server.port), not this port.
FLYDOCS_PORT=8080
# Port for the HTTP health server the worker modes (``flydocs worker`` /
# ``flydocs bbox-worker``) run for Kubernetes httpGet probes against
# /actuator/health/{liveness,readiness}. Unset reuses FLYDOCS_PORT;
# 0 disables it (dev setups running serve + worker on the same host).
#FLYDOCS_WORKER_HEALTH_PORT=8400
# /actuator/health/{liveness,readiness}. Defaults to the management port 9090
# (matching where serve-mode exposes the actuator); 0 disables it (dev setups
# running serve + worker on the same host, where serve already owns 9090).
#FLYDOCS_WORKER_HEALTH_PORT=9090
# Override the management (actuator + admin) port if 9090 clashes locally.
#PYFLY_MANAGEMENT_SERVER_PORT=9090

# ----------------------------------------------------------------------------
# Persistence
Expand Down
8 changes: 7 additions & 1 deletion pyfly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ pyfly:
server:
enabled: true
host: 0.0.0.0
port: 8400
port: 8080
# Management server (Spring management.server.* parity): the actuator
# (/actuator/*) and the admin dashboard (/admin) are served on this dedicated
# port, NOT the business API port above. Probes / Prometheus target 9090.
management:
server:
port: 9090

# Observability -- Prometheus metrics + OpenTelemetry tracing.
observability:
Expand Down
Loading
Loading