7 Commits
Author SHA1 Message Date
tazlin 1a5d2ee30a feat(oai): OpenAI-compatible chat front
`horde/oai` is a separate ASGI process that serves `/v1/chat/completions` and reaches the Horde over HTTP and Redis; it does not import the Flask application (ADR 0023).

- `FrontSettings` reads every `HORDE_OAI_*` variable through pydantic-settings; an invalid value stops the start and states the variable
- `horde/oai/schema.find_refused_parameter` returns the first unsupported key, role, content part or `horde` option for a 400 that states it, and `drop_default_no_ops` removes the `DEFAULT_NO_OP_PARAMETERS` keys holding their default, since stock clients send them unprompted; `ChatCompletionRequest` validates the rest against the shared `ChatHordeOptions`
- `HordeClient` rotates calls across the configured Horde API roots, skips and backs off a root that refuses or drops a connection, and sends cancels through a separate smaller pool, so slow cancels do not starve submits
- `CompletionSignalListener` holds one pattern subscription per process; after `HORDE_OAI_REDIS_LIVENESS_SECONDS` without a message it sends a PING with a token and subscribes again when the PONG does not arrive in time, because a black-holed connection does not deliver or raise anything
- `ChatRequestWatcher` reads the status on each signal, on a jittered fallback poll that stretches with the reported wait, and once more at the deadline; fail-fast is decided only from a status read in the current wake, and a failed deciding read is retried after `signal_recheck_seconds`, so a transient error does not cancel a request whose worker may have started; a request seen before that answers 404 waits up to the same interval for its faulted signal
- `create_app` serves `POST /v1/chat/completions`, `GET /v1/models`, `GET /v1/models/{id}` and `GET /healthz`; a completion request is read under a byte ceiling and read timeout (413 `RequestTooLarge`, 408 `RequestBodyTimeout`), screened, admitted through a per-source then a global in-flight cap, submitted to `/v2/generate/chat/async`, and answered as `chat.completion` or as server-sent events
- A bearer token is accepted only when every character is printable non-space ASCII, so a token that cannot be sent as a native header is 401 `InvalidAuthorizationHeader`
- `horde/oai/sse.py` buffers a stream: keepalive comments with the queue position while the request waits, then the role, content, finish and optional usage chunks and `[DONE]`; `horde/oai/mapping.py` builds the native body, the completion with `x_horde` details and estimated usage when the worker did not report any, and the OpenAI error envelope
- Once the Horde holds a request, or may hold one after a lost submit answer, every error carries `x-should-retry: false`, so a client SDK does not create a second charged job
- Cancels on disconnect, deadline, fail-fast and shutdown are bounded, shielded from the client's disconnect and sent at most once per request
- `python -m horde.oai` runs the app under uvicorn, trusting `X-Forwarded-For` from `HORDE_OAI_FORWARDED_ALLOW_IPS` and draining open requests for `HORDE_OAI_SHUTDOWN_DRAIN_SECONDS` on stop; `docker-compose.yaml` adds the `aihorde_oai` service
- Runtime dependencies fastapi, uvicorn, httpx, sse-starlette, pydantic-settings and sniffio, dev dependencies respx and openai; sniffio is declared because httpcore imports it on every request and a failed import is not cached
- `tests/unit/oai` drives the app against a respx-mocked Horde and fakeredis: settings, poll cadence, the completion and model routes, error envelopes, the request lifecycle and cancels, screening, admission, the Horde client and the listener's liveness ping
2026-10-30 23:46:01 -04:00
tazlin 03a1e0b01e build: improve compose health-check resiliency 2026-08-19 14:02:10 -04:00
tazlin 415277541a feat: build out test framework, CI test stack, and load-surfaced hardening
Establishes additional unit/integration testing, the CI to run it, and the runtime fixes those tests surfaced

Testing
- Add unit (ORM/function) and integration (API route) tiers over a shared dependency runtime that auto-provisions Postgres, Redis, and S3-compatible object storage (Garage) via testcontainers, with an external-deps mode.
- Add fixtures/factories plus coverage for user roles, kudos pricing, worker.can_generate, countermeasures, WP activation, and adversarial malformed-input route sweeps.
- Modularize the locust suite into locustsuite/ and add a stability-only smoke gate (check_smoke_results.py).

CI / tooling
- Rework pr/main/trusted-fork/publish workflows around composite actions (setup-test-python, start-test-stack) and a Dockerized PG+Redis+Garage stack.
- Bump the Python floor to 3.12 (+3.13 matrix) and consolidate on uv.

Runtime hardening
- Make telemetry activation opt-in (telemetry_enabled): the image stays telemetry-capable but a bare server.py is inert unless an OTLP endpoint or AI_HORDE_TELEMETRY_ENABLED is set.
- Turn NUL bytes, out-of-range user ids, and duplicate worker names into clean 4xx responses instead of session-poisoning 500s.
- Fix get_anon() to resolve the anon account by oauth_id (re-enabling the kudos-transfer guards); shrink and instrument the SQLAlchemy pool (15/5) to expose connection-hold inefficiency as pool timeouts rather than PG refusals.
2026-07-05 13:36:59 -04:00
tazlin 41b3400b0c docs: readme intro; reworked registration instructions (#470)
* docs: readme.md introduction; register info rework

My hope is that a more non-technical opening will help explain the project better to a wider audience of people. Additionally, I reworked the registration section to hopefully be clearer at a glance without having to parse multiple paragraphs of prose.

* chore: add tazlin's email for license notices

* docs: rewrites, links to expanded docs; md linting fixes

* docs: add links to defs/detailed explanations

* docs: fix typo

* docs/style: fix formatting typo

* docs: add info about kudos econ. on discord

* docs: fix typo

* docs: link to `haidra-assets/docs`
2024-12-07 10:45:31 +01:00
ceruleandeep aaba202a06 Fix example Docker Compose environment
Pull Horde's custom postgres image

Build aihorde Docker image in two stages to minimise rebuild time

Establish minimal necessary configuration to bring up a working composition

Fix pre-commit config so black and ruff hooks run

Catch and report exceptions in is_redis_up()

Add missing final EOL markers to some files (per black)

Update documentation
2024-09-27 15:28:18 +02:00
Lina Ceballosanddb0 2a75039034 NGI - Updating licensing aspects according to REUSE (#440)
* creating LICENSES directory

* adding headers with copyright and license info to various files

* fix: switch to AGPL 3-or-later

* feat: added all missing licenses

* chore: linting

* ci: Added reuse lint job

---------

Co-authored-by: db0 <mail@dbzer0.com>
2024-08-10 14:00:03 +02:00
Namwon Kim 153b1a0865 docker image build (#260)
* docker image build

* Added Docker image usage method
2023-07-12 09:52:54 +02:00