Files
AI-Horde/pyproject.toml
tazlin 1fbc7e448e feat(moderation): record prompt evidence when a submission is rejected
- Record filter, NSFW-model and worker CSAM rejections in prompt_moderation_events through record_prompt_evidence on a separate connection, so a rolled-back rejection keeps its evidence; a repeated worker report returns the existing event through the unique job_id.
- Capture from GenerateTemplate._record_prompt_rejection and User.record_problem_job, keeping each prompt stage distinct and never filling one from another; the effective prompt is null on a rejection.
- Store the request origin as its IP subject and its pseudonym (ip_subject_key); an origin with no subject (free text in a trusted proxy's Proxied-For header) keeps its text as origin_text, clipped to MAX_ORIGIN_TEXT_CHARACTERS (subjectless_origin_text), on the event and on the worker report record. Clip each prompt stage at 16,000 characters (text_truncated).
- Drop the user_problem_jobs.worker_id foreign key and both ORM relationships, so deleting a worker keeps the reports it made: they are evidence about the reported account, not about the worker. 5.1.12 drops the constraint.
- Name the reasons and outcomes as PromptModerationReason and PromptModerationOutcome, and default capture times to UTC on every dialect, matching the migration, through UtcNow in horde/classes/base/sql_constructs.py, which holds per-dialect SQL expressions any model can use.
- Add the prompt_moderation_events and prompt_moderation_notes tables to 5.1.12 with an index per subject filter (account, proxied account, pseudonym, unkeyed address, worker) and on the capture time; MAX_NOTE_CHARACTERS sizes the note column.
- Lock the models to the migration (test_orm_models_build_the_same_moderation_tables_as_the_migration) and the capture paths in tests.
- Document in .env_template that secret_key keys the address pseudonym and must not rotate.
- Rewrite the problem-job alerts (User.record_problem_job) to carry the event ID, the count, the models and LoRAs and a frontpage link (HORDE_MODERATION_FRONTPAGE_URL, moderation_event_url) instead of the prompt and the address, since a Discord message outlives evidence retention and Discord drops content over 2,000 characters; IP alerts carry the first 12 hex characters of the pseudonym, and a report whose origin has no subject sends no IP alert, since it would pool with every other subjectless report. Count the IP daily window over a day.
- Bump HORDE_VERSION to 5.1.12.
2026-10-01 18:10:04 -04:00

207 lines
5.4 KiB
TOML

# SPDX-FileCopyrightText: 2022 Konstantinos Thoukydidis <mail@dbzer0.com>
# SPDX-FileCopyrightText: 2023 Tazlin
#
# SPDX-License-Identifier: AGPL-3.0-or-later
[project]
name = "AI-Horde"
version = "5.1.12"
description = "A Crowdsourced Generative AI cluster for everyone."
authors = [
{name = "db0", email = "mail@dbzer0.com"},
{name = "Tazlin", email = "tazlin@haidra.net"},
]
readme = "README.md"
requires-python = ">=3.12,<3.14"
dependencies = [
"Werkzeug~=2.2.2",
"Flask~=2.2.2",
"flask-restx",
"flask-limiter~=3.0",
"Flask-Caching",
"waitress~=2.1.2",
"requests>=2.27",
"markdown>=3.7.0,<4.0.0",
"flask-dance[sqla]",
"blinker",
"python-dotenv",
"loguru",
"bleach~=5.0.1",
"alt-profanity-check",
"safetext",
"python-dateutil~=2.8.2",
"redis~=4.3.5",
"pillow>=10.0.1",
"flask-sqlalchemy~=3.1.1",
"oauthlib~=3.2.2",
"sqlalchemy~=2.0.35",
"psycopg2-binary",
"boto3>=1.33.7",
"regex",
"unidecode",
"patreon @ git+https://github.com/Patreon/patreon-python.git",
"emoji",
"semver>=3.0.2",
# Used by the tiny kudos pricing model and resolved deterministically by uv.lock.
"numpy",
# The single source of truth for image sampler capabilities: which solver knobs each sampler
# accepts and over what range, how many model evaluations a step of it costs, and which
# sampler/scheduler/baseline combinations cannot produce an image. Request validation, pricing and
# the published constraints endpoint all read it, so the vocabulary cannot drift between this API,
# the workers, and the clients that share this package.
"horde_sdk~=0.31.0",
# The image model reference loader (REPLICA fetch plus the pending/beta overlay) and the
# baseline vocabulary both come from here, so it is pinned explicitly rather than being
# inherited from horde_sdk.
"horde_model_reference>=8.5.1,<9.0.0",
"markdownify",
"stripe>=15,<16",
"environs",
"setuptools<82",
# logfire is hard-pinned deliberately: the OTel/Logfire bridge API is not yet
# stable across minors and our telemetry wiring (horde/telemetry.py) is tested
# against this exact version. Bump only with a telemetry smoke re-run.
"logfire[flask,sqlalchemy,redis,requests]==4.32.1",
"chardet<6",
]
license = {file = "LICENSE"}
classifiers = [
"Programming Language :: Python :: 3",
"Operating System :: OS Independent",
"License :: OSI Approved :: GNU Affero General Public License v3",
]
[project.urls]
"Homepage" = "https://aihorde.net"
"Bug Tracker" = "https://github.com/Haidra-Org/AI-Horde"
[dependency-groups]
dev = [
"pytest==9.0.2",
"ruff==0.15.12",
"tox~=4.14.1",
"pytest-asyncio",
"pytest-cov",
"sqlparse",
"fakeredis",
"freezegun",
"testcontainers[postgres,redis]",
"mypy",
"pip-audit",
"reuse",
"pre-commit",
"locust>=2.43.4",
"types-python-dateutil>=2.9.0.20260716",
]
# Optional runtime group used by the Dockerfile's AI_HORDE_DEPENDENCY_GROUPS
# build arg. Keep profiler wheels out of the default image; publish/use the
# telemetry image variant when PYROSCOPE_ENABLED=true.
telemetry-profiling = [
"pyroscope-io",
"pyroscope-otel",
]
docs = [
"zensical>=0.0.56",
]
[tool.ruff]
line-length = 140
lint.select = [
#"A",
"I",
"E",
"W",
"F",
"UP",
"YTT",
#"B",
#"C4",
"PIE",
#"RET",
#"SIM",
"COM",
# "D",
#"ANN"
]
lint.ignore = [
"COM812", # The following rule may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling this rule
# Ignore D rules for non-google docstring standard
"D203",
"D204",
"D213",
"D215",
"D400",
"D404",
"D406",
"D407",
"D408",
"D409",
"D413",]
[tool.ruff.lint.per-file-ignores]
"horde/sandbox.py" = ["F401"]
"server.py" = ["E402"]
[tool.black]
line-length = 140
include = '\.pyi?$'
[tool.pytest.ini_options]
testpaths = ["tests/unit"]
markers = [
"unit: Unit tests (in-process; Postgres + fakeredis via fixtures)",
"integration: Integration tests (in-process Flask client; real services via fixtures)",
"object_storage: Integration tests that require S3-compatible object storage",
]
[tool.coverage.run]
branch = true
source = ["horde"]
omit = [
"horde/templates/*",
"horde/data/*",
"horde/sandbox.py",
]
[tool.coverage.report]
show_missing = true
skip_covered = false
precision = 1
[tool.coverage.xml]
output = "coverage.xml"
[tool.mypy]
# Permissive baseline so existing untyped modules don't block adoption.
# Files explicitly listed under [[tool.mypy.overrides]] with strict = true
# form the "typed allowlist".
plugins = ["sqlalchemy.ext.mypy.plugin"]
python_version = "3.12"
ignore_missing_imports = true
follow_imports = "silent"
warn_unused_configs = true
warn_redundant_casts = true
warn_unused_ignores = true
# --- Strict allowlist ---
# Rule of thumb: a module joins this list when its public surface is fully
# typed AND mypy passes strict on it.
[[tool.mypy.overrides]]
module = [
"horde.telemetry",
"horde.metrics",
"horde.request_scheduling",
"horde.classes.base.kudos",
"horde.database.kudos_counters",
"horde.database.kudos_db",
"horde.database.kudos_ledger",
"horde.database.kudos_legacy_projection",
"horde.database.kudos_reconciliation",
"horde.database.kudos_reservations",
"horde.sampler_constraints",
"horde.sampler_work_policy",
]
strict = true