mirror of
https://github.com/Haidra-Org/AI-Horde.git
synced 2026-11-06 05:17:31 +00:00
fix: cache sampler constraints per process
Replace the endpoint's timed response cache with a process-local cached serialized constraints document. This makes the published sampler constraints a pure function of the running code, avoids stale cross-deploy cache entries, updates the developer docs to match, and adds an integration test to ensure repeated requests return the same document.
This commit is contained in:
1 parent
f317017ee8
commit
d1e9067158
4 files changed
+39
-6
No files matched your search
@@ -167,6 +167,9 @@ Rules that follow from those being separate:
|
||||
- **Version pins.** A new SDK symbol means bumping the `horde_sdk` bound in `pyproject.toml` and
|
||||
`requirements.txt`, and regenerating `uv.lock`. reGen pins the SDK separately, so a worker-visible
|
||||
addition needs both bumps before the capability gate can open.
|
||||
- **Publishing the change.** The sampler constraints endpoint compiles its document once per process
|
||||
and holds it, with no response cache in front, so a deployed change serves as soon as the process
|
||||
restarts. Nothing needs invalidating and no cache flush belongs in the deploy.
|
||||
- **Tests.** `tests/unit/test_consts.py` pins the vocabularies, `tests/unit/test_sampler_constraints.py`
|
||||
compares the published document with the registry, `tests/unit/test_kudos_pricing.py` pins prices for
|
||||
pre-existing requests, and `tests/unit/test_bridge_reference.py` pins capability gating. The Locust
|
||||
|
||||
@@ -55,7 +55,7 @@ from horde.metrics import (
|
||||
)
|
||||
from horde.model_reference import model_reference
|
||||
from horde.patreon import patrons
|
||||
from horde.sampler_constraints import compile_sampler_constraints
|
||||
from horde.sampler_constraints import published_sampler_constraints
|
||||
from horde.telemetry import (
|
||||
get_traceparent,
|
||||
pyroscope_tag,
|
||||
@@ -1520,7 +1520,6 @@ class ImageSamplerConstraints(Resource):
|
||||
decorators = [limiter.exempt]
|
||||
|
||||
@logger.catch(reraise=True)
|
||||
@cache.cached(timeout=3600)
|
||||
@api.expect(get_parser)
|
||||
@api.response(200, "Constraints Published", models.response_model_sampler_constraints)
|
||||
def get(self):
|
||||
@@ -1532,6 +1531,7 @@ class ImageSamplerConstraints(Resource):
|
||||
Recommendations are advisory and each carries the provenance of the claim, because they range
|
||||
from statements by the image backend's own author to third-party folklore.
|
||||
"""
|
||||
# The document is typed all the way up, and is rendered to plain JSON types only here, at the
|
||||
# boundary, so nothing downstream of the compiler works in anonymous dicts.
|
||||
return compile_sampler_constraints().model_dump(mode="json"), 200
|
||||
# The document is a pure function of the installed code, so it is compiled once per process and
|
||||
# held rather than cached between them: a response cache could only ever return what this
|
||||
# process already has, and would outlive the build that filled it.
|
||||
return published_sampler_constraints(), 200
|
||||
@@ -13,11 +13,15 @@ The document's type also lives in horde_sdk, as
|
||||
:class:`~horde_sdk.generation_parameters.image.constraints_document.SamplerConstraintsDocument`, so a
|
||||
python client parses the response back into the same models this module builds it from.
|
||||
|
||||
The public entry point is :func:`compile_sampler_constraints`.
|
||||
The endpoint serves :func:`published_sampler_constraints`, which renders what
|
||||
:func:`compile_sampler_constraints` builds.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
from typing import Any
|
||||
|
||||
from horde_model_reference.meta_consts import KNOWN_IMAGE_GENERATION_BASELINE
|
||||
from horde_sdk.generation_parameters.image.constraints import (
|
||||
CFG_PP_SAMPLERS,
|
||||
@@ -264,3 +268,20 @@ def compile_sampler_constraints() -> SamplerConstraintsDocument:
|
||||
recommended=sorted(sampler for sampler in RECOMMENDED_SAMPLERS if str(sampler) in KNOWN_SAMPLERS),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@functools.cache
|
||||
def published_sampler_constraints() -> dict[str, Any]:
|
||||
"""Return the document the endpoint publishes, compiled and serialised once per process.
|
||||
|
||||
The document is a pure function of the installed code: no request input, no database read, nothing a
|
||||
running process can change. Holding it removes any window in which a process could publish a document
|
||||
other than the one its own code produces, and it is cheaper than fetching one from a shared cache.
|
||||
|
||||
The returned mapping is shared by every caller and must not be mutated.
|
||||
|
||||
Returns:
|
||||
The document rendered to plain JSON types.
|
||||
|
||||
"""
|
||||
return compile_sampler_constraints().model_dump(mode="json")
|
||||
@@ -147,3 +147,12 @@ def test_both_measured_cost_ratios_are_served_with_their_basis(client):
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def test_repeated_requests_serve_an_unchanged_document(client):
|
||||
# The document is compiled once per process and handed to every caller, so anything that mutated it
|
||||
# on the way out would corrupt it for every later request rather than just its own.
|
||||
first = client.get(CONSTRAINTS_URL).get_json()
|
||||
second = client.get(CONSTRAINTS_URL).get_json()
|
||||
|
||||
assert first == second
|
||||
Reference in new issue
Block a user