argus-cortex 0.4.0__tar.gz → 0.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/CHANGELOG.md +15 -1
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/PKG-INFO +4 -1
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/README.md +3 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/src/argus_cortex/_version.py +2 -2
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/src/argus_cortex/moderation/singguard.py +20 -3
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/tests/test_moderation.py +20 -3
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/.copier-answers.yml +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/.env.example +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/.github/workflows/ci.yml +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/.github/workflows/release.yml +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/.gitignore +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/CLAUDE.md +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/LICENSE +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/Makefile +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/pyproject.toml +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/src/argus_cortex/__init__.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/src/argus_cortex/backends.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/src/argus_cortex/moderation/__init__.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/src/argus_cortex/moderation/_http.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/src/argus_cortex/moderation/nudenet.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/src/argus_cortex/py.typed +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/src/argus_cortex/server.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/src/argus_cortex/store/__init__.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/src/argus_cortex/store/blob.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/src/argus_cortex/store/config.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/src/argus_cortex/store/errors.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/src/argus_cortex/store/lineage.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/src/argus_cortex/store/models.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/src/argus_cortex/store/schema.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/src/argus_cortex/store/vector.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/src/argus_cortex/taxonomy.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/src/argus_cortex/wire.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/src/argus_cortex/xmp.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/tests/test_backends.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/tests/test_blob.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/tests/test_server.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/tests/test_smoke.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/tests/test_store.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/tests/test_store_pg_integration.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/tests/test_taxonomy.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/tests/test_vector.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/tests/test_wire.py +0 -0
- {argus_cortex-0.4.0 → argus_cortex-0.4.1}/tests/test_xmp.py +0 -0
|
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.1] - 2026-10-27
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `SingGuardClient(thinking="slow")` and `thinking="cascade"`: the argus-cull
|
|
14
|
+
`singguard` service's modes. `slow` reasons with no fast first-line verdict,
|
|
15
|
+
so the reasoning is not anchored to a verdict made before it; `cascade` runs
|
|
16
|
+
`fast` on every image and `slow` only on the ones fast flags, and slow's
|
|
17
|
+
verdict stands. `THINKING_MODES` lists them. A SingGuard server without a
|
|
18
|
+
mode answers HTTP 400, raised as `GuardCheckFailed` per check.
|
|
19
|
+
- `GuardResult.thinking`: the mode the server says it judged in (`None` from a
|
|
20
|
+
server that does not say). The argus-cull service can be forced server-side
|
|
21
|
+
to a mode other than the one asked for, and this records which it used.
|
|
22
|
+
|
|
10
23
|
## [0.4.0] - 2026-10-26
|
|
11
24
|
|
|
12
25
|
### Added
|
|
@@ -103,7 +116,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
103
116
|
(`LocalBackend`, `RemoteBackend`, `RemoteProvider`, `with_retries`
|
|
104
117
|
retrying only transient errors, `resolve_device`).
|
|
105
118
|
|
|
106
|
-
[Unreleased]: https://github.com/smk762/argus-cortex/compare/v0.4.
|
|
119
|
+
[Unreleased]: https://github.com/smk762/argus-cortex/compare/v0.4.1...HEAD
|
|
120
|
+
[0.4.1]: https://github.com/smk762/argus-cortex/compare/v0.4.0...v0.4.1
|
|
107
121
|
[0.4.0]: https://github.com/smk762/argus-cortex/compare/v0.3.0...v0.4.0
|
|
108
122
|
[0.3.0]: https://github.com/smk762/argus-cortex/compare/v0.2.0...v0.3.0
|
|
109
123
|
[0.2.0]: https://github.com/smk762/argus-cortex/compare/v0.1.0...v0.2.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: argus-cortex
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Shared foundation for the Argus suite: taxonomy, versioned wire-schema tooling, and the local/remote AI backend contract
|
|
5
5
|
Project-URL: Repository, https://github.com/smk762/argus-cortex
|
|
6
6
|
Author: smk762
|
|
@@ -99,6 +99,9 @@ result = nudenet.check(image_bytes, "beach.jpg")
|
|
|
99
99
|
result.exposed_score # 0.0-1.0 over DEFAULT_EXPOSED_LABELS; result.labels = per-class maxima
|
|
100
100
|
|
|
101
101
|
guard = SingGuardClient("http://guard:9150", policy=None) # None = SingGuard's default rules
|
|
102
|
+
# thinking="fast" (default) is a verdict and rule; "fast-slow" also writes why; "slow" writes why
|
|
103
|
+
# with no fast verdict ahead of the reasoning; "cascade" is fast, then slow only for what fast
|
|
104
|
+
# flagged (the last two: argus-cull's singguard service only).
|
|
102
105
|
verdict = guard.check(image_bytes, text="the prompt, if any")
|
|
103
106
|
verdict.safe, verdict.rules # False, ["B. Real-World Crimes & Public Safety"]
|
|
104
107
|
any(rule_matches(r, "B") for r in verdict.rules) # match a rule by letter or full name
|
|
@@ -53,6 +53,9 @@ result = nudenet.check(image_bytes, "beach.jpg")
|
|
|
53
53
|
result.exposed_score # 0.0-1.0 over DEFAULT_EXPOSED_LABELS; result.labels = per-class maxima
|
|
54
54
|
|
|
55
55
|
guard = SingGuardClient("http://guard:9150", policy=None) # None = SingGuard's default rules
|
|
56
|
+
# thinking="fast" (default) is a verdict and rule; "fast-slow" also writes why; "slow" writes why
|
|
57
|
+
# with no fast verdict ahead of the reasoning; "cascade" is fast, then slow only for what fast
|
|
58
|
+
# flagged (the last two: argus-cull's singguard service only).
|
|
56
59
|
verdict = guard.check(image_bytes, text="the prompt, if any")
|
|
57
60
|
verdict.safe, verdict.rules # False, ["B. Real-World Crimes & Public Safety"]
|
|
58
61
|
any(rule_matches(r, "B") for r in verdict.rules) # match a rule by letter or full name
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.4.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 4,
|
|
21
|
+
__version__ = version = '0.4.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 4, 1)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -9,7 +9,17 @@ Animal Abuse. A custom policy text replaces them. It gives no scores.
|
|
|
9
9
|
|
|
10
10
|
API: ``POST /api/guard/check`` with JSON ``{input, image_base64, policy,
|
|
11
11
|
thinking}``, answered by ``{safe, verdict, rule, explanation, categories:
|
|
12
|
-
[{code, label, why}], model, elapsed_ms}
|
|
12
|
+
[{code, label, why}], model, elapsed_ms}`` and, from the argus-cull
|
|
13
|
+
``singguard`` service, ``thinking``: the mode the server judged in, which
|
|
14
|
+
its ``SINGGUARD_FORCE_THINKING`` can make differ from the one asked for.
|
|
15
|
+
|
|
16
|
+
``thinking`` is ``fast`` (a verdict and rule), ``fast-slow`` (SingGuard's own
|
|
17
|
+
reasoning mode: a fast first-line verdict, then the reasoning and a final
|
|
18
|
+
answer), ``slow`` (the argus-cull service's variant with no first-line
|
|
19
|
+
verdict, so the reasoning is not anchored to a verdict made before it) or
|
|
20
|
+
``cascade`` (that service's ``fast`` for every image and ``slow`` for the ones
|
|
21
|
+
fast flags, slow's verdict standing). Servers without ``slow`` and ``cascade``
|
|
22
|
+
answer HTTP 400 to them.
|
|
13
23
|
|
|
14
24
|
Large images are downscaled before sending (Pillow, from the ``[images]``
|
|
15
25
|
extra, imported only when downscaling is on).
|
|
@@ -27,7 +37,7 @@ from pydantic import BaseModel, Field
|
|
|
27
37
|
|
|
28
38
|
from argus_cortex.moderation._http import CheckFailed, ModerationConfigError, post_json, service_url
|
|
29
39
|
|
|
30
|
-
THINKING_MODES = ("fast", "fast-slow")
|
|
40
|
+
THINKING_MODES = ("fast", "fast-slow", "slow", "cascade")
|
|
31
41
|
# The guard model sees far fewer pixels than a camera original, and base64
|
|
32
42
|
# JSON inflates every byte by 4/3.
|
|
33
43
|
MAX_EDGE = 1536
|
|
@@ -51,6 +61,9 @@ class GuardResult(BaseModel):
|
|
|
51
61
|
# own rules) or "custom-<sha256 prefix>" of the policy text.
|
|
52
62
|
model: str | None = None
|
|
53
63
|
policy: str = "default"
|
|
64
|
+
# The thinking mode the server judged in, when it says (the argus-cull
|
|
65
|
+
# service does; it can be forced server-side to differ from the request).
|
|
66
|
+
thinking: str | None = None
|
|
54
67
|
|
|
55
68
|
|
|
56
69
|
class GuardCheckFailed(CheckFailed):
|
|
@@ -104,6 +117,7 @@ def to_result(payload: object, policy: str = "default") -> GuardResult:
|
|
|
104
117
|
explanation=_clip(payload.get("explanation"), _EXPLANATION_MAX),
|
|
105
118
|
model=_clip(payload.get("model"), _FIELD_MAX),
|
|
106
119
|
policy=policy,
|
|
120
|
+
thinking=_clip(payload.get("thinking"), _FIELD_MAX),
|
|
107
121
|
)
|
|
108
122
|
|
|
109
123
|
|
|
@@ -154,7 +168,10 @@ class SingGuardClient:
|
|
|
154
168
|
"""A SingGuard server at *base_url* (its origin, e.g. ``http://guard:9150``).
|
|
155
169
|
|
|
156
170
|
*policy* is the policy text (``None`` = SingGuard's default rules);
|
|
157
|
-
*thinking* is ``fast`` (verdict and rule)
|
|
171
|
+
*thinking* is one of :data:`THINKING_MODES`: ``fast`` (verdict and rule),
|
|
172
|
+
``fast-slow`` (also writes why), ``slow`` (writes why, with no fast
|
|
173
|
+
verdict ahead of the reasoning) or ``cascade`` (fast, then slow only for
|
|
174
|
+
what fast flagged); the last two need the argus-cull ``singguard`` service.
|
|
158
175
|
With *downscale* on (the default, needs ``[images]``), images over
|
|
159
176
|
:data:`MAX_EDGE` px or :data:`MAX_BYTES` are shrunk before sending.
|
|
160
177
|
Thread-safe: :meth:`check` holds no state.
|
|
@@ -102,7 +102,7 @@ class _NudeNet(_Fake):
|
|
|
102
102
|
|
|
103
103
|
|
|
104
104
|
class _SingGuard(_Fake):
|
|
105
|
-
answers: dict[int, dict] = {}
|
|
105
|
+
answers: dict[int, dict] = {} # a test's overrides of _GUARD_ANSWERS, by red value
|
|
106
106
|
|
|
107
107
|
def do_POST(self) -> None:
|
|
108
108
|
body = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
|
|
@@ -115,7 +115,8 @@ class _SingGuard(_Fake):
|
|
|
115
115
|
if red == _FAIL:
|
|
116
116
|
self._send(500)
|
|
117
117
|
else:
|
|
118
|
-
self.
|
|
118
|
+
answer = self.answers.get(red) or _GUARD_ANSWERS.get(red, _GUARD_ANSWERS[10])
|
|
119
|
+
self._send(200, {**answer, "model": "SingGuard-4b", "elapsed_ms": 5})
|
|
119
120
|
|
|
120
121
|
|
|
121
122
|
def _serve(handler: type[_Fake]) -> Iterator[tuple[type[_Fake], str]]:
|
|
@@ -239,7 +240,8 @@ def test_singguard_config() -> None:
|
|
|
239
240
|
assert custom.policy == "### A. Minors" and custom.policy_name == policy_name("### A. Minors")
|
|
240
241
|
assert custom.policy_name == policy_name(" ### A. Minors\n") # a policy read from a file matches
|
|
241
242
|
assert custom.policy_name.startswith("custom-") and len(custom.policy_name) == len("custom-") + 12
|
|
242
|
-
for
|
|
243
|
+
assert [SingGuardClient("http://g", thinking=m).thinking for m in ("slow", "cascade")] == ["slow", "cascade"]
|
|
244
|
+
for kwargs in ({"thinking": "slower"}, {"thinking": "FAST"}, {"policy": " \n"}):
|
|
243
245
|
with pytest.raises(ModerationConfigError):
|
|
244
246
|
SingGuardClient("http://g", **kwargs)
|
|
245
247
|
with pytest.raises(ModerationConfigError):
|
|
@@ -269,6 +271,21 @@ def test_singguard_default_policy_is_not_sent(singguard_server) -> None:
|
|
|
269
271
|
assert "policy" not in fake.seen[0]
|
|
270
272
|
|
|
271
273
|
|
|
274
|
+
def test_singguard_slow_is_sent_and_the_mode_used_is_recorded(singguard_server) -> None:
|
|
275
|
+
"""The argus-cull service answers with the mode it judged in, which a
|
|
276
|
+
server-side force can make differ from the request; it is recorded as sent.
|
|
277
|
+
A server that says nothing leaves it None."""
|
|
278
|
+
fake, url = singguard_server
|
|
279
|
+
client = SingGuardClient(url, timeout=5, thinking="slow")
|
|
280
|
+
assert client.check(_png(10)).thinking is None
|
|
281
|
+
fake.answers = {10: {**_GUARD_ANSWERS[10], "thinking": "fast-slow"}}
|
|
282
|
+
try:
|
|
283
|
+
assert client.check(_png(10)).thinking == "fast-slow"
|
|
284
|
+
finally:
|
|
285
|
+
fake.answers = {}
|
|
286
|
+
assert {s["thinking"] for s in fake.seen} == {"slow"}
|
|
287
|
+
|
|
288
|
+
|
|
272
289
|
def test_singguard_downscales_large_images(singguard_server) -> None:
|
|
273
290
|
fake, url = singguard_server
|
|
274
291
|
SingGuardClient(url).check(_png(10, (4000, 3000)))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|