simurg 1.0.0__py3-none-any.whl

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.
simurg/__init__.py ADDED
@@ -0,0 +1,51 @@
1
+ # ═══════════════════════════════════════════════════════════════════════════════
2
+ # SIMURG · Streaming Integrity Monitor & Universal Regeneration Guard
3
+ #
4
+ # Developed by doofZ (a.k.a Farid Aghayev from HAL-X AI)
5
+ # Co-Founder & Head of AI at HAL-X AI.
6
+ #
7
+ # Online (in-stream, token-time) detection of LLM decoding corruption —
8
+ # repetition collapse, cross-lingual drift, training-data regurgitation, structural
9
+ # breakdown, semantic discontinuity — with onset localization and a zero-leak
10
+ # hold/release/abort protocol. A pluggable five-detector ensemble (rules + n-gram
11
+ # surprise + Count-Min repetition + SimHash drift + entropy) fused under conformal
12
+ # calibration, plus an online-learnable tier.
13
+ #
14
+ # Host integration is five lines:
15
+ # from simurg import Simurg, OnlineLogReg
16
+ # s = Simurg(model=OnlineLogReg.load("weights/simurg_model.json")) # model optional
17
+ # for token in llm_stream:
18
+ # v = s.feed(token)
19
+ # if v.state == "corrupt":
20
+ # abort_and_retry(reason=v.reasons, onset=v.onset_char); break
21
+ # ui.write(v.released)
22
+ # final = s.finish(); ui.write(final.released)
23
+ # ═══════════════════════════════════════════════════════════════════════════════
24
+ from .core import (DRIFT, REGISTRY, REGURGITATION, REPETITION, SEMANTIC,
25
+ STRUCTURAL, TAXONOMY, Detector, DetectorScore, stable_hash)
26
+ from .features import StreamFeatures
27
+ from .signals.calibrate import RobustEWMA
28
+ from .signals.ngram_lm import OnlineCharNGram
29
+ from .signals.sketch import CountMinSketch, RepetitionTracker
30
+ from .signals.simhash import RollingSimHash
31
+ from .detection.rules import rule_verdict
32
+ from .detection.fusion import ConformalEnsemble
33
+ from .detection.sentinel import CLEAN, CORRUPT, SUSPECT, Simurg, Verdict
34
+ from .learning.model import OnlineLogReg
35
+ from .learning.custom import (CustomLearnedDetector, DetectabilityReport,
36
+ LexiconDetector, fit_custom_detector)
37
+ from .integrations.openai_guard import GuardedLLM
38
+
39
+ __version__ = "1.0.0"
40
+
41
+ __all__ = [
42
+ "Simurg", "Verdict", "StreamFeatures", "OnlineLogReg", "ConformalEnsemble",
43
+ "GuardedLLM",
44
+ "fit_custom_detector", "DetectabilityReport", "CustomLearnedDetector",
45
+ "LexiconDetector",
46
+ "Detector", "DetectorScore", "REGISTRY", "rule_verdict", "stable_hash",
47
+ "OnlineCharNGram", "CountMinSketch", "RepetitionTracker", "RollingSimHash",
48
+ "RobustEWMA",
49
+ "CLEAN", "SUSPECT", "CORRUPT",
50
+ "REPETITION", "DRIFT", "REGURGITATION", "STRUCTURAL", "SEMANTIC", "TAXONOMY",
51
+ ]
simurg/core.py ADDED
@@ -0,0 +1,83 @@
1
+ # ═══════════════════════════════════════════════════════════════════════════════
2
+ # SIMURG · Streaming Integrity Monitor & Universal Regeneration Guard
3
+ #
4
+ # Developed by doofZ (a.k.a Farid Aghayev from HAL-X AI)
5
+ # Co-Founder & Head of AI at HAL-X AI.
6
+ #
7
+ # core — shared contracts of the package: the corruption taxonomy, the
8
+ # `DetectorScore` / `Signal` value objects, the `Detector` protocol every
9
+ # detector implements, a `DetectorRegistry` for plug-in extensibility, and a
10
+ # process-stable hash used by the sketch/SimHash estimators (so results are
11
+ # reproducible across runs — important for the benchmark and the paper).
12
+ # ═══════════════════════════════════════════════════════════════════════════════
13
+ from __future__ import annotations
14
+
15
+ import hashlib
16
+ from dataclasses import dataclass, field
17
+ from typing import Callable, Dict, List, Protocol, runtime_checkable
18
+
19
+ # ── corruption taxonomy ──────────────────────────────────────────────────────
20
+ REPETITION = "repetition_collapse"
21
+ DRIFT = "cross_lingual_drift"
22
+ REGURGITATION = "regurgitation"
23
+ STRUCTURAL = "structural_breakdown"
24
+ SEMANTIC = "semantic_discontinuity" # new axis surfaced by the SimHash detector
25
+ TAXONOMY = (REPETITION, DRIFT, REGURGITATION, STRUCTURAL, SEMANTIC)
26
+
27
+
28
+ def stable_hash(s: str, seed: int = 0) -> int:
29
+ """Deterministic 64-bit hash (blake2b). Unlike Python's built-in ``hash`` it
30
+ is stable across processes/runs, so sketch buckets and SimHash bits are
31
+ reproducible — a hard requirement for a benchmark others can replicate."""
32
+ key = seed.to_bytes(8, "little") if seed else b""
33
+ return int.from_bytes(
34
+ hashlib.blake2b(s.encode("utf-8", "ignore"), key=key, digest_size=8).digest(),
35
+ "little")
36
+
37
+
38
+ @dataclass
39
+ class DetectorScore:
40
+ """One detector's read on the current stream state."""
41
+ name: str
42
+ p: float # corruption probability contributed [0,1]
43
+ reasons: List[str] = field(default_factory=list)
44
+ classes: List[str] = field(default_factory=list) # taxonomy labels
45
+ features: Dict[str, float] = field(default_factory=dict) # exposed numeric signals
46
+
47
+
48
+ @runtime_checkable
49
+ class Detector(Protocol):
50
+ """A detector is a cheap read over the shared incremental `StreamState`.
51
+ It never re-scans text — all O(1)-amortized work happens in StreamState.feed;
52
+ `evaluate` only interprets the already-computed signals. This is what keeps
53
+ a five-detector ensemble as cheap as a single pass."""
54
+ name: str
55
+
56
+ def evaluate(self, state) -> DetectorScore: # noqa: D401
57
+ ...
58
+
59
+
60
+ class DetectorRegistry:
61
+ """Plug-in registry so open-source contributors can add a detector without
62
+ touching the sentinel: `@registry.register` a factory, and it joins the
63
+ ensemble. The fusion layer weights whatever is registered."""
64
+
65
+ def __init__(self):
66
+ self._factories: Dict[str, Callable[[], Detector]] = {}
67
+
68
+ def register(self, name: str):
69
+ def deco(factory: Callable[[], Detector]):
70
+ self._factories[name] = factory
71
+ return factory
72
+ return deco
73
+
74
+ def build(self, names=None) -> List[Detector]:
75
+ names = names or list(self._factories)
76
+ return [self._factories[n]() for n in names if n in self._factories]
77
+
78
+ @property
79
+ def names(self):
80
+ return tuple(self._factories)
81
+
82
+
83
+ REGISTRY = DetectorRegistry()
@@ -0,0 +1,6 @@
1
+ # ═════════════════════════════════════════════════════════════════════════════
2
+ # SIMURG · Streaming Integrity Monitor & Universal Regeneration Guard
3
+ #
4
+ # Developed by doofZ (a.k.a Farid Aghayev from HAL-X AI)
5
+ # Co-Founder & Head of AI at HAL-X AI.
6
+ # ═════════════════════════════════════════════════════════════════════════════
simurg/data/dataset.py ADDED
@@ -0,0 +1,92 @@
1
+ # ═══════════════════════════════════════════════════════════════════════════════
2
+ # SIMURG · Streaming Integrity Monitor & Universal Regeneration Guard
3
+ #
4
+ # Developed by doofZ (a.k.a Farid Aghayev from HAL-X AI)
5
+ # Co-Founder & Head of AI at HAL-X AI.
6
+ #
7
+ # dataset — benchmark dataset builder. Clean corpus = REAL production texts (AI
8
+ # answers + desk reports read from the deployment DB — AZ/EN/RU macro-economics
9
+ # prose with legitimate figures, the hardest possible negatives), with bundled
10
+ # fallback seeds when the DB is offline. Positives = synthetic CorruptBench
11
+ # corruptions of held-out clean texts, with exact onset ground truth. This module
12
+ # only READS the DB; it never imports or mutates backend runtime code.
13
+ # ═══════════════════════════════════════════════════════════════════════════════
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ import random
18
+
19
+ from .synth import CLASSES, corrupt
20
+
21
+ _FALLBACK = [
22
+ ("Büdcə xərclərinin 20% artımı qeyri-neft buraxılış kəsirini -1.64%-dən -0.44%-ə "
23
+ "yaxşılaşdırır, ÜDM-in illik artımı 2.94%-dən 3.78%-ə yüksəlir. İnflyasiya 5.45%-dən "
24
+ "5.71%-ə qalxsa da 2-6% dəhlizində qalır və Mərkəzi Bank faiz dərəcəsini sabit saxlayır. "
25
+ "Tikinti və nəqliyyat sektorları fiskal impulsdan ən çox faydalanan sahələrdir. ") * 6,
26
+ ("The non-oil output gap improves from -1.6% in the baseline to -0.4%, a +1.2pp effect "
27
+ "of the shock. GDP grows 1.93% in 2026 versus 1.45% baseline, while inflation stays at "
28
+ "5.71%, inside the 2-6% corridor. The current account remains near 7.1% of GDP and the "
29
+ "policy rate rises only 15 basis points, so monetary conditions stay accommodative. ") * 6,
30
+ ("Повышение бюджетных расходов на 20% ускоряет рост ВВП до 1.93% против 1.45% в базовом "
31
+ "сценарии. Инфляция остаётся в коридоре 2-6%, а ненефтяной разрыв выпуска сокращается "
32
+ "с -1.64% до -0.44%. Строительство и транспорт получают наибольший фискальный импульс. ") * 6,
33
+ ]
34
+
35
+
36
+ def load_clean_corpus(min_len: int = 700, limit: int = 400) -> list[str]:
37
+ """Clean-corpus loader, three tiers:
38
+ 1. ``SIMURG_CORPUS_JSONL`` env var → path to a .jsonl with a ``text`` field
39
+ per line (bring your own corpus — e.g. the CorruptBench-HF clean rows);
40
+ 2. optional deployment hook: the Black Swan production DB when this package
41
+ sits inside that repo (silently skipped anywhere else);
42
+ 3. bundled fallback seed texts."""
43
+ texts: list[str] = []
44
+ corpus_path = os.environ.get("SIMURG_CORPUS_JSONL")
45
+ if corpus_path and os.path.exists(corpus_path):
46
+ import json
47
+ with open(corpus_path, encoding="utf-8") as f:
48
+ for line in f:
49
+ try:
50
+ t = json.loads(line).get("text") or ""
51
+ except Exception:
52
+ continue
53
+ if len(t) >= min_len:
54
+ texts.append(t)
55
+ if len(texts) >= limit:
56
+ break
57
+ if not texts:
58
+ try: # deployment hook — absent in the open-source layout, and that's fine
59
+ from backend.db import engine
60
+ from sqlalchemy import text as _t
61
+ with engine.connect() as c:
62
+ rows = c.execute(_t(
63
+ "SELECT content, payload->>'report' FROM chat_messages "
64
+ "WHERE sender='AI' ORDER BY created_at DESC LIMIT :lim"),
65
+ {"lim": limit}).fetchall()
66
+ for content, report in rows:
67
+ for t in (content, report):
68
+ if t and len(t) >= min_len:
69
+ texts.append(t)
70
+ except Exception:
71
+ pass
72
+ if len(texts) < 10:
73
+ texts += _FALLBACK
74
+ return texts
75
+
76
+
77
+ def build(seed: int = 7, per_class: int = 60):
78
+ """Returns (streams, meta): streams = list of (text, label, onset, cls);
79
+ clean streams have label 0, onset None."""
80
+ rng = random.Random(seed)
81
+ corpus = [t for t in load_clean_corpus() if len(t) >= 700]
82
+ rng.shuffle(corpus)
83
+ n_clean_pool = max(8, len(corpus) // 2)
84
+ clean_pool, corrupt_pool = corpus[:n_clean_pool], corpus[n_clean_pool:] or corpus
85
+ streams = [(t, 0, None, "clean") for t in clean_pool]
86
+ for cls in CLASSES:
87
+ for _ in range(per_class):
88
+ base = rng.choice(corrupt_pool)
89
+ text, onset, c = corrupt(base, cls, rng)
90
+ streams.append((text, 1, onset, c))
91
+ rng.shuffle(streams)
92
+ return streams
@@ -0,0 +1,159 @@
1
+ # ═══════════════════════════════════════════════════════════════════════════════
2
+ # SIMURG · Streaming Integrity Monitor & Universal Regeneration Guard
3
+ #
4
+ # Developed by doofZ (a.k.a Farid Aghayev from HAL-X AI)
5
+ # Co-Founder & Head of AI at HAL-X AI.
6
+ #
7
+ # evaluate — the end-to-end benchmark & trainer. Builds CorruptBench, trains the
8
+ # learned tier on StreamState checkpoints, derives conformal thresholds from clean
9
+ # streams, and reports the paper's results table: stream TPR/FPR, AUROC, per-class
10
+ # recall, detection latency past onset, onset-localization error, zero-leak rate
11
+ # and throughput. Persists `simurg_model.json` (weights) + `simurg_calib.json`
12
+ # (conformal thresholds). Run: python3 -m simurg.evaluate
13
+ # ═══════════════════════════════════════════════════════════════════════════════
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ import random
18
+ import time
19
+
20
+ import numpy as np
21
+
22
+ from .dataset import build
23
+ from ..features import StreamFeatures
24
+ from ..detection.fusion import ConformalEnsemble
25
+ from ..learning.model import OnlineLogReg
26
+ from ..detection.sentinel import CORRUPT, Simurg
27
+ from .synth import CLASSES
28
+
29
+ _DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "weights")
30
+ _MODEL_PATH = os.path.join(_DIR, "simurg_model.json")
31
+ _CALIB_PATH = os.path.join(_DIR, "simurg_calib.json")
32
+
33
+
34
+ def _checkpoints(text: str, every: int = 300):
35
+ f = StreamFeatures()
36
+ out = []
37
+ for i in range(0, len(text), every):
38
+ f.feed(text[i:i + every])
39
+ if f.total_len >= 350:
40
+ f.freeze_baseline()
41
+ out.append((f.total_len, f.vector()))
42
+ return out
43
+
44
+
45
+ def _train_model(streams):
46
+ X, y = [], []
47
+ for text, label, onset, _cls in streams:
48
+ for pos, vec in _checkpoints(text):
49
+ if label == 0:
50
+ X.append(vec); y.append(0)
51
+ elif pos >= (onset or 0) + 250:
52
+ X.append(vec); y.append(1)
53
+ elif pos <= (onset or 0):
54
+ X.append(vec); y.append(0)
55
+ model = OnlineLogReg(len(StreamFeatures.VECTOR))
56
+ model.partial_fit(X, y, epochs=6)
57
+ return model, len(X), int(sum(y))
58
+
59
+
60
+ def _run_stream(text, model, fusion):
61
+ s = Simurg(model=model, fusion=fusion)
62
+ forwarded = 0
63
+ for i in range(0, len(text), 40):
64
+ v = s.feed(text[i:i + 40])
65
+ if v.state != CORRUPT:
66
+ forwarded += len(v.released)
67
+ else:
68
+ return v, s, forwarded
69
+ return s.finish(), s, forwarded
70
+
71
+
72
+ def _auroc(scores, labels) -> float:
73
+ order = sorted(zip(scores, labels))
74
+ pos = sum(labels); neg = len(labels) - pos
75
+ if not pos or not neg:
76
+ return float("nan")
77
+ rank_sum, rank = 0.0, 1
78
+ for _s, l in order:
79
+ if l == 1:
80
+ rank_sum += rank
81
+ rank += 1
82
+ return (rank_sum - pos * (pos + 1) / 2) / (pos * neg)
83
+
84
+
85
+ def run(seed: int = 7, save: bool = False):
86
+ rng = random.Random(seed)
87
+ streams = build(seed=seed)
88
+ rng.shuffle(streams)
89
+ n_test = max(20, len(streams) // 3)
90
+ test, train = streams[:n_test], streams[n_test:]
91
+
92
+ model, n_vec, n_pos = _train_model(train)
93
+
94
+ # conformal calibration: fused scores of CLEAN training streams → thresholds
95
+ fusion = ConformalEnsemble()
96
+ clean_scores = []
97
+ for text, label, _o, _c in train:
98
+ if label == 0:
99
+ v, _s, _f = _run_stream(text, model, fusion)
100
+ clean_scores.append(v.p_corrupt)
101
+ fusion.calibrate(clean_scores, alpha=0.02, suspect_alpha=0.1)
102
+
103
+ print(f"dataset: {len(streams)} streams ({sum(1 for s in streams if s[1]==1)} corrupt) | "
104
+ f"train vectors: {n_vec} ({n_pos} pos) | features: {len(StreamFeatures.VECTOR)} | "
105
+ f"test: {len(test)}")
106
+ print("conformal thresholds:", {k: round(v, 3) for k, v in fusion.to_dict().items()})
107
+ print("learned weights:", {k: round(float(w), 2)
108
+ for k, w in zip(StreamFeatures.VECTOR, model.w)})
109
+
110
+ scores, labels, latencies, onset_errs = [], [], [], []
111
+ flagged = total_corrupt = false_alarms = clean_n = leaks_blocked = early = 0
112
+ cls_hits = {c: [0, 0] for c in CLASSES}
113
+ t0 = time.monotonic(); chars = 0
114
+
115
+ for text, label, onset, cls in test:
116
+ v, s, forwarded = _run_stream(text, model, fusion)
117
+ chars += len(text)
118
+ scores.append(v.p_corrupt); labels.append(label)
119
+ if label == 1:
120
+ total_corrupt += 1; cls_hits[cls][1] += 1
121
+ if (onset or 0) < 350:
122
+ early += 1
123
+ if v.state == CORRUPT:
124
+ flagged += 1; cls_hits[cls][0] += 1
125
+ latencies.append(max(0, s.f.total_len - (onset or 0)))
126
+ if v.onset_char is not None and onset is not None:
127
+ onset_errs.append(abs(v.onset_char - onset))
128
+ if (onset or 0) < 350 and forwarded == 0:
129
+ leaks_blocked += 1
130
+ else:
131
+ clean_n += 1
132
+ if v.state == CORRUPT:
133
+ false_alarms += 1
134
+ dt = time.monotonic() - t0
135
+
136
+ print("\n── results ─────────────────────────────────────────────")
137
+ print(f"stream TPR: {flagged}/{total_corrupt} = {flagged/max(1,total_corrupt):.3f}")
138
+ print(f"stream FPR: {false_alarms}/{clean_n} = {false_alarms/max(1,clean_n):.3f}")
139
+ print(f"AUROC: {_auroc(scores, labels):.3f}")
140
+ for c, (hit, tot) in cls_hits.items():
141
+ if tot:
142
+ print(f" recall[{c}]: {hit}/{tot} = {hit/tot:.2f}")
143
+ if latencies:
144
+ print(f"detection latency past onset: median {int(np.median(latencies))}, "
145
+ f"p90 {int(np.percentile(latencies, 90))} chars")
146
+ if onset_errs:
147
+ print(f"onset localization |err|: median {int(np.median(onset_errs))} chars")
148
+ print(f"zero-leak (onset<hold): {leaks_blocked}/{early}")
149
+ print(f"throughput: {chars/max(dt,1e-6):,.0f} chars/sec")
150
+
151
+ if save:
152
+ model.save(_MODEL_PATH); fusion.save(_CALIB_PATH)
153
+ print(f"\nsaved → {os.path.basename(_MODEL_PATH)}, {os.path.basename(_CALIB_PATH)}")
154
+ return model, fusion
155
+
156
+
157
+ if __name__ == "__main__":
158
+ import sys
159
+ run(save="--save" in sys.argv)
@@ -0,0 +1,167 @@
1
+ # ═══════════════════════════════════════════════════════════════════════════════
2
+ # SIMURG · Streaming Integrity Monitor & Universal Regeneration Guard
3
+ #
4
+ # Developed by doofZ (a.k.a Farid Aghayev from HAL-X AI)
5
+ # Co-Founder & Head of AI at HAL-X AI.
6
+ #
7
+ # hf_dataset/generate_dataset.py — builds the open-source CorruptBench release
8
+ # for HuggingFace. Clean seed texts are GENERATED (not taken from production
9
+ # chats — no user data leaves the deployment) by HAL-X's wahoo-1.5-preview 12B
10
+ # model across three languages and a spread of analytic topics; a stratified
11
+ # subset is corrupted by the simurg.synth injectors with exact onset ground
12
+ # truth. Output: corruptbench_train.jsonl / corruptbench_test.jsonl (+ stats).
13
+ # Split is BY SEED TEXT, so a corrupt variant never shares its clean source with
14
+ # the other split. Run: python3 -m simurg.data.generate_dataset
15
+ # ═══════════════════════════════════════════════════════════════════════════════
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import os
20
+ import random
21
+ import threading
22
+ import time
23
+ import urllib.request
24
+ from concurrent.futures import ThreadPoolExecutor, as_completed
25
+
26
+ from simurg.data.synth import CLASSES, corrupt
27
+
28
+ # Any OpenAI-compatible /v1/chat/completions endpoint can generate the clean
29
+ # seed corpus — point these env vars at yours.
30
+ URL = os.environ.get("SIMURG_GEN_URL", "http://localhost:8000/v1/chat/completions")
31
+ MODEL = os.environ.get("SIMURG_GEN_MODEL", "wahoo-1.5-preview")
32
+ OUT = os.path.dirname(os.path.abspath(__file__))
33
+ N_SEEDS = 200
34
+ CORRUPT_PER_SEED = 3
35
+ TEST_FRAC = 0.2
36
+ SEED = 20260718
37
+
38
+ LANGS = [("en", "English"), ("az", "Azerbaijani"), ("ru", "Russian")]
39
+ TOPICS = [
40
+ "inflation dynamics and monetary policy in a small open economy",
41
+ "the fiscal transmission of a budget expenditure shock to non-oil sectors",
42
+ "how oil price movements propagate to the current account and the exchange rate",
43
+ "credit market conditions, overdue loans and financial stability",
44
+ "tourism and transport sectors under geopolitical risk",
45
+ "a quarterly macroeconomic outlook for an oil-exporting economy",
46
+ "the labor market implications of a construction boom",
47
+ "central bank policy corridors and interest rate decisions",
48
+ "diversification strategies for a resource-dependent economy",
49
+ "exchange rate pass-through to consumer prices",
50
+ "public investment multipliers and infrastructure spending",
51
+ "banking sector liquidity and deposit rate dynamics",
52
+ "agricultural output volatility and food price inflation",
53
+ "trade balance dynamics under changing global demand",
54
+ "sovereign wealth fund transfers and fiscal sustainability",
55
+ ]
56
+ STYLES = [
57
+ "an analytical desk report with a few concrete figures",
58
+ "a policy briefing for a ministry, with sector detail",
59
+ "an explanatory note for a general audience",
60
+ "a quarterly bulletin section, moderately technical",
61
+ ]
62
+
63
+ _print_lock = threading.Lock()
64
+
65
+
66
+ def _gen_one(idx: int, rng_seed: int) -> dict | None:
67
+ rng = random.Random(rng_seed)
68
+ lang_code, lang_name = LANGS[idx % len(LANGS)]
69
+ topic = rng.choice(TOPICS)
70
+ style = rng.choice(STYLES)
71
+ words = rng.randint(350, 750)
72
+ prompt = (f"Write {style} about {topic}. Length: roughly {words} words. "
73
+ f"Write ENTIRELY in {lang_name}. Use a professional economist's voice; "
74
+ f"you may include a handful of plausible illustrative figures "
75
+ f"(percentages, growth rates). Do not add any preamble or title markers "
76
+ f"beyond normal markdown headings.")
77
+ body = {"model": MODEL, "messages": [{"role": "user", "content": prompt}],
78
+ "max_tokens": 1600, "temperature": 0.9, "top_p": 0.95}
79
+ for attempt in range(3):
80
+ try:
81
+ req = urllib.request.Request(
82
+ URL, data=json.dumps(body).encode(),
83
+ headers={"Content-Type": "application/json"}, method="POST")
84
+ with urllib.request.urlopen(req, timeout=180) as r:
85
+ d = json.loads(r.read())
86
+ text = (d["choices"][0]["message"].get("content") or "").strip()
87
+ if len(text) >= 900:
88
+ return {"seed_id": idx, "language": lang_code, "topic": topic,
89
+ "style": style, "text": text}
90
+ time.sleep(1)
91
+ except Exception as e:
92
+ with _print_lock:
93
+ print(f" seed {idx} attempt {attempt+1} failed: {e}")
94
+ time.sleep(2 + attempt * 3)
95
+ return None
96
+
97
+
98
+ def main():
99
+ rng = random.Random(SEED)
100
+ print(f"generating {N_SEEDS} clean seed texts with {MODEL} …")
101
+ seeds = []
102
+ t0 = time.monotonic()
103
+ with ThreadPoolExecutor(max_workers=8) as ex:
104
+ futs = {ex.submit(_gen_one, i, SEED + i): i for i in range(N_SEEDS)}
105
+ for n, fut in enumerate(as_completed(futs), 1):
106
+ res = fut.result()
107
+ if res:
108
+ seeds.append(res)
109
+ if n % 20 == 0:
110
+ print(f" {n}/{N_SEEDS} done ({time.monotonic()-t0:.0f}s), "
111
+ f"ok={len(seeds)}")
112
+ print(f"clean seeds: {len(seeds)} in {time.monotonic()-t0:.0f}s")
113
+
114
+ # split BY SEED (no leakage), stratified by language
115
+ rng.shuffle(seeds)
116
+ n_test = int(len(seeds) * TEST_FRAC)
117
+ for i, s in enumerate(seeds):
118
+ s["split"] = "test" if i < n_test else "train"
119
+
120
+ # build examples: 1 clean + CORRUPT_PER_SEED corrupt per seed
121
+ examples = []
122
+ counter = 0
123
+ for s in seeds:
124
+ counter += 1
125
+ examples.append({
126
+ "id": f"cb-{counter:05d}", "split": s["split"],
127
+ "text": s["text"], "label": 0, "corruption_class": "clean",
128
+ "onset_char": None, "language": s["language"], "topic": s["topic"],
129
+ "n_chars": len(s["text"]), "seed_model": MODEL,
130
+ })
131
+ classes = rng.sample(list(CLASSES), k=min(CORRUPT_PER_SEED, len(CLASSES)))
132
+ for cls in classes:
133
+ counter += 1
134
+ crng = random.Random(hash((s["seed_id"], cls)) & 0xFFFFFFFF)
135
+ text_c, onset, _ = corrupt(s["text"], cls, crng, onset_frac=(0.05, 0.75))
136
+ examples.append({
137
+ "id": f"cb-{counter:05d}", "split": s["split"],
138
+ "text": text_c, "label": 1, "corruption_class": cls,
139
+ "onset_char": onset, "language": s["language"], "topic": s["topic"],
140
+ "n_chars": len(text_c), "seed_model": MODEL,
141
+ })
142
+
143
+ rng.shuffle(examples)
144
+ stats = {"total": len(examples)}
145
+ for split in ("train", "test"):
146
+ rows = [e for e in examples if e["split"] == split]
147
+ path = os.path.join(OUT, f"corruptbench_{split}.jsonl")
148
+ with open(path, "w", encoding="utf-8") as f:
149
+ for e in rows:
150
+ f.write(json.dumps(e, ensure_ascii=False) + "\n")
151
+ stats[split] = {
152
+ "n": len(rows),
153
+ "clean": sum(1 for e in rows if e["label"] == 0),
154
+ "corrupt": sum(1 for e in rows if e["label"] == 1),
155
+ "by_class": {c: sum(1 for e in rows if e["corruption_class"] == c)
156
+ for c in ("clean",) + tuple(CLASSES)},
157
+ "by_lang": {l: sum(1 for e in rows if e["language"] == l)
158
+ for l, _ in LANGS},
159
+ }
160
+ print(f"wrote {path} ({len(rows)} rows)")
161
+ with open(os.path.join(OUT, "stats.json"), "w", encoding="utf-8") as f:
162
+ json.dump(stats, f, indent=2, ensure_ascii=False)
163
+ print(json.dumps(stats, indent=2))
164
+
165
+
166
+ if __name__ == "__main__":
167
+ main()
simurg/data/synth.py ADDED
@@ -0,0 +1,101 @@
1
+ # ═══════════════════════════════════════════════════════════════════════════════
2
+ # SIMURG · Streaming Integrity Monitor & Universal Regeneration Guard
3
+ #
4
+ # Developed by doofZ (a.k.a Farid Aghayev from HAL-X AI)
5
+ # Co-Founder & Head of AI at HAL-X AI.
6
+ #
7
+ # synth — the "CorruptBench" synthetic corruption generator. Injects a corruption
8
+ # of a chosen taxonomy class into CLEAN text at a controlled onset position,
9
+ # yielding (corrupt_text, onset_char, class) triples with EXACT ground truth for
10
+ # onset-localization and detection-latency benchmarks. Payloads are modeled on
11
+ # REAL production derails (table echo with '#REF!', Chinese-news drift, English
12
+ # README regurgitation, Persian name-list loops) — not toy noise.
13
+ # ═══════════════════════════════════════════════════════════════════════════════
14
+ from __future__ import annotations
15
+
16
+ import random
17
+
18
+ from ..core import DRIFT, REGURGITATION, REPETITION, STRUCTURAL
19
+
20
+ # ── payload corpora (modeled on observed derails) ────────────────────────────
21
+ _CJK = ("由国立大学金融科技研发团队主导开发的智能合约审计系统今日正式宣布完成测试并开放企业试用"
22
+ "这套系统结合了人工智能与区块链技术能够自动侦测合约中的潜在漏洞大幅提升审计效率"
23
+ "根据团队表示传统的合约审计需要大量的人力而智能审计系统可以在短时间内完成分析")
24
+ _ARABIC = "بهره‌های محمد رضایی سید علیزاده در بخصوص په دې نه سره بادي مورد بررسی قرار گرفت "
25
+ _README = ("## How to use\n1. Install Python packages\n```bash\npip install -r requirements.txt\n```\n"
26
+ "2. Set the API Key\nCreate a .env file in the root directory:\n```\nAPI_KEY=your_key_here\n```\n"
27
+ "3. Run the application\n```bash\npython main.py\n```\nFeatures\n- Multi-threat detection\n"
28
+ "- Reentrancy attacks\n- Integer overflow/underflow\n- Detailed report\n")
29
+ _NEWS = ("2024年01月25日 記者報導 台灣的金融科技領域迎來了一項重大突破 "
30
+ "The system uses deep learning to analyze code, flagging potential vulnerabilities "
31
+ "and suggesting fixes. This is particularly relevant given the increasing complexity. ")
32
+
33
+ _SERIES = ["MCI", "Foreign Demand", "Geopolitical Risk", "Consumer Credit",
34
+ "Tax Gap (lag)", "Budget Exp.", "Lag / Expectation / Residual", "Actual"]
35
+
36
+
37
+ def _table_echo(rng: random.Random, n_chars: int) -> str:
38
+ out = ["#REF! "]
39
+ while sum(len(p) for p in out) < n_chars:
40
+ name = rng.choice(_SERIES)
41
+ vals = " ".join(f"{rng.uniform(-3, 3):.2f}" if rng.random() < 0.4 else "0.00"
42
+ for _ in range(rng.randint(12, 30)))
43
+ out.append(f"{name} {vals} ")
44
+ return "".join(out)[:n_chars]
45
+
46
+
47
+ def _repetition_loop(rng: random.Random, base: str, n_chars: int) -> str:
48
+ words = [w for w in base.split() if len(w) > 3]
49
+ phrase = " ".join(rng.sample(words, min(4, len(words)))) if len(words) >= 4 \
50
+ else "the same phrase again"
51
+ sep = rng.choice(["\n", " ", " — ", "\n• "])
52
+ unit = phrase + sep
53
+ return (unit * (n_chars // len(unit) + 1))[:n_chars]
54
+
55
+
56
+ def _drift(rng: random.Random, n_chars: int) -> str:
57
+ src = _CJK if rng.random() < 0.6 else _ARABIC * 6
58
+ start = rng.randrange(0, max(1, len(src) - 50))
59
+ body = (src[start:] + src)[: n_chars]
60
+ return body
61
+
62
+
63
+ def _regurgitation(rng: random.Random, n_chars: int) -> str:
64
+ src = _README if rng.random() < 0.6 else _NEWS * 3
65
+ return (src * (n_chars // len(src) + 1))[:n_chars]
66
+
67
+
68
+ def _structural(rng: random.Random, n_chars: int) -> str:
69
+ if rng.random() < 0.5:
70
+ return _table_echo(rng, n_chars)
71
+ frags = ["#### ", "``` ", "===== ", "________ ", "J) Comb $## = 2 2Community ",
72
+ "original}.original/m4/. # , 1 ", "{ # ,) $## 8P8 pack # { . mM # 1 ", "─" * 20 + " "]
73
+ out = []
74
+ while sum(len(p) for p in out) < n_chars:
75
+ out.append(rng.choice(frags))
76
+ return "".join(out)[:n_chars]
77
+
78
+
79
+ _INJECTORS = {
80
+ REPETITION: _repetition_loop,
81
+ DRIFT: _drift,
82
+ REGURGITATION: _regurgitation,
83
+ STRUCTURAL: _structural,
84
+ }
85
+ CLASSES = tuple(_INJECTORS)
86
+
87
+
88
+ def corrupt(clean: str, cls: str, rng: random.Random,
89
+ onset_frac: tuple[float, float] = (0.0, 0.7)) -> tuple[str, int, str]:
90
+ """Inject a `cls` corruption into `clean`. Returns (text, onset_char, cls).
91
+ onset position is uniform in onset_frac of the clean length; the corruption
92
+ replaces the remainder (mirrors production: after collapse the model never
93
+ returns to the topic)."""
94
+ lo, hi = onset_frac
95
+ onset = int(len(clean) * rng.uniform(lo, hi))
96
+ tail_len = max(400, len(clean) - onset)
97
+ if cls == REPETITION:
98
+ payload = _INJECTORS[cls](rng, clean[:max(onset, 200)], tail_len)
99
+ else:
100
+ payload = _INJECTORS[cls](rng, tail_len)
101
+ return clean[:onset] + payload, onset, cls
@@ -0,0 +1,6 @@
1
+ # ═════════════════════════════════════════════════════════════════════════════
2
+ # SIMURG · Streaming Integrity Monitor & Universal Regeneration Guard
3
+ #
4
+ # Developed by doofZ (a.k.a Farid Aghayev from HAL-X AI)
5
+ # Co-Founder & Head of AI at HAL-X AI.
6
+ # ═════════════════════════════════════════════════════════════════════════════