simurg 1.0.0__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.
- simurg-1.0.0/LICENSE +190 -0
- simurg-1.0.0/PKG-INFO +498 -0
- simurg-1.0.0/README.md +479 -0
- simurg-1.0.0/pyproject.toml +35 -0
- simurg-1.0.0/setup.cfg +4 -0
- simurg-1.0.0/src/simurg/__init__.py +51 -0
- simurg-1.0.0/src/simurg/core.py +83 -0
- simurg-1.0.0/src/simurg/data/__init__.py +6 -0
- simurg-1.0.0/src/simurg/data/dataset.py +92 -0
- simurg-1.0.0/src/simurg/data/evaluate.py +159 -0
- simurg-1.0.0/src/simurg/data/generate_dataset.py +167 -0
- simurg-1.0.0/src/simurg/data/synth.py +101 -0
- simurg-1.0.0/src/simurg/detection/__init__.py +6 -0
- simurg-1.0.0/src/simurg/detection/detectors.py +114 -0
- simurg-1.0.0/src/simurg/detection/fusion.py +103 -0
- simurg-1.0.0/src/simurg/detection/rules.py +68 -0
- simurg-1.0.0/src/simurg/detection/sentinel.py +157 -0
- simurg-1.0.0/src/simurg/features.py +194 -0
- simurg-1.0.0/src/simurg/guard_dashboard.py +331 -0
- simurg-1.0.0/src/simurg/guard_ui/index.html +433 -0
- simurg-1.0.0/src/simurg/integrations/__init__.py +6 -0
- simurg-1.0.0/src/simurg/integrations/openai_guard.py +153 -0
- simurg-1.0.0/src/simurg/learning/__init__.py +6 -0
- simurg-1.0.0/src/simurg/learning/custom.py +203 -0
- simurg-1.0.0/src/simurg/learning/model.py +69 -0
- simurg-1.0.0/src/simurg/signals/__init__.py +6 -0
- simurg-1.0.0/src/simurg/signals/calibrate.py +50 -0
- simurg-1.0.0/src/simurg/signals/changepoint.py +38 -0
- simurg-1.0.0/src/simurg/signals/ngram_lm.py +71 -0
- simurg-1.0.0/src/simurg/signals/simhash.py +62 -0
- simurg-1.0.0/src/simurg/signals/sketch.py +70 -0
- simurg-1.0.0/src/simurg/training/__init__.py +6 -0
- simurg-1.0.0/src/simurg/training/train_live.py +198 -0
- simurg-1.0.0/src/simurg/training/train_ui/index.html +196 -0
- simurg-1.0.0/src/simurg/weights/simurg_calib.json +1 -0
- simurg-1.0.0/src/simurg/weights/simurg_model.json +1 -0
- simurg-1.0.0/src/simurg.egg-info/PKG-INFO +498 -0
- simurg-1.0.0/src/simurg.egg-info/SOURCES.txt +41 -0
- simurg-1.0.0/src/simurg.egg-info/dependency_links.txt +1 -0
- simurg-1.0.0/src/simurg.egg-info/requires.txt +7 -0
- simurg-1.0.0/src/simurg.egg-info/top_level.txt +1 -0
- simurg-1.0.0/tests/test_guard_dashboard.py +143 -0
- simurg-1.0.0/tests/test_sentinel.py +56 -0
simurg-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
Copyright 2026 HAL-X AI / Farid Aghayev (doofZ)
|
|
179
|
+
|
|
180
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
181
|
+
you may not use this file except in compliance with the License.
|
|
182
|
+
You may obtain a copy of the License at
|
|
183
|
+
|
|
184
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
185
|
+
|
|
186
|
+
Unless required by applicable law or agreed to in writing, software
|
|
187
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
188
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
189
|
+
See the License for the specific language governing permissions and
|
|
190
|
+
limitations under the License.
|
simurg-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: simurg
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Zero-leak online detection of LLM decoding corruption in production streams
|
|
5
|
+
Author-email: "Farid Aghayev (doofZ)" <farid.a@hal-x.ai>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://hal-x.ai
|
|
8
|
+
Project-URL: Repository, https://github.com/doofzoff/SIMURG
|
|
9
|
+
Keywords: llm,llm-safety,guardrails,streaming,hallucination,anomaly-detection,inference,vllm,openai-api,online-learning
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: numpy>=1.24
|
|
14
|
+
Provides-Extra: figures
|
|
15
|
+
Requires-Dist: matplotlib>=3.7; extra == "figures"
|
|
16
|
+
Provides-Extra: test
|
|
17
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
<h1 align="center">SIMURG</h1>
|
|
21
|
+
<p align="center"><b>Streaming Integrity Monitor & Universal Regeneration Guard</b></p>
|
|
22
|
+
<p align="center">
|
|
23
|
+
Catch LLM decoding corruption <b>while the answer is still being generated</b> and cut the
|
|
24
|
+
stream <b>mid-flight</b>: corruption that starts in the hold window never reaches the
|
|
25
|
+
user, and mid-stream corruption is aborted within a few hundred characters of onset,
|
|
26
|
+
so the host regenerates the answer.
|
|
27
|
+
</p>
|
|
28
|
+
<p align="center">
|
|
29
|
+
<img alt="license" src="https://img.shields.io/badge/license-Apache--2.0-blue">
|
|
30
|
+
<img alt="python" src="https://img.shields.io/badge/python-%E2%89%A53.10-blue">
|
|
31
|
+
<img alt="dependencies" src="https://img.shields.io/badge/dependencies-numpy%20only-green">
|
|
32
|
+
<img alt="cpu" src="https://img.shields.io/badge/runtime-CPU%2C%20no%20GPU-lightgrey">
|
|
33
|
+
</p>
|
|
34
|
+
|
|
35
|
+
| throughput | detection latency | false-alarm budget | footprint | setup |
|
|
36
|
+
|:---:|:---:|:---:|:---:|:---:|
|
|
37
|
+
| **197,632 chars/sec** on a laptop CPU | **~590 chars** past corruption onset | configurable, conformal-calibrated | numpy only, no model, no GPU | **3 lines**, zero training |
|
|
38
|
+
|
|
39
|
+
The guard runs hundreds of times faster than a typical LLM produces text, so it is
|
|
40
|
+
never the bottleneck: a model streaming at 50 tokens/sec writes ~250 chars/sec, and
|
|
41
|
+
SIMURG reads 197,000.
|
|
42
|
+
|
|
43
|
+
<details>
|
|
44
|
+
<summary><b>Table of contents</b></summary>
|
|
45
|
+
|
|
46
|
+
1. [The problem](#the-problem)
|
|
47
|
+
2. [How SIMURG differs](#how-simurg-differs)
|
|
48
|
+
3. [How it works](#how-it-works)
|
|
49
|
+
4. [Zero-leak in action](#zero-leak-in-action)
|
|
50
|
+
5. [Benchmark](#benchmark)
|
|
51
|
+
6. [Install](#install)
|
|
52
|
+
7. [Quick start](#quick-start)
|
|
53
|
+
8. [Teach it your domain and your failure modes](#teach-it-your-domain-and-your-failure-modes)
|
|
54
|
+
9. [Live guard dashboard](#live-guard-dashboard)
|
|
55
|
+
10. [What SIMURG is NOT](#what-simurg-is-not)
|
|
56
|
+
11. [Repository layout](#repository-layout)
|
|
57
|
+
12. [Roadmap](#roadmap)
|
|
58
|
+
13. [FAQ](#faq)
|
|
59
|
+
14. [Citation](#citation)
|
|
60
|
+
15. [License](#license)
|
|
61
|
+
|
|
62
|
+
</details>
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## The problem
|
|
67
|
+
|
|
68
|
+
When you run an LLM in production, especially a **quantized, small, or self-hosted**
|
|
69
|
+
model, it sometimes **derails mid-generation**. The decoded stream stops doing the
|
|
70
|
+
task and collapses into one of a handful of pathologies:
|
|
71
|
+
|
|
72
|
+
| failure mode | what it looks like |
|
|
73
|
+
|---|---|
|
|
74
|
+
| **repetition collapse** | the same phrase, list, or token repeated until the token budget runs out |
|
|
75
|
+
| **cross-lingual drift** | an English answer that quietly slides into Chinese, Arabic, or Cyrillic |
|
|
76
|
+
| **regurgitation** | the model dumps a README, boilerplate, or training text |
|
|
77
|
+
| **structural breakdown** | `#REF! -0.00 -0.00 ... 0.00`: number and symbol garbage |
|
|
78
|
+
| **template leakage** | `<|im_start|>`, `</s>`, `[INST]`, "As an AI language model..." spilling into the answer |
|
|
79
|
+
|
|
80
|
+
This is **not** factual hallucination. A fluent-but-wrong sentence (see
|
|
81
|
+
[What SIMURG is NOT](#what-simurg-is-not)) has no statistical scar. What is shown
|
|
82
|
+
above is **decoding corruption**, and it leaves a *statistical signature in the
|
|
83
|
+
token stream*: repetition rate, lexical variety, script distribution,
|
|
84
|
+
compressibility, and predictive surprise all move in measurable ways.
|
|
85
|
+
|
|
86
|
+
SIMURG watches that signature character by character, decides in real time whether
|
|
87
|
+
the stream has gone bad, tells you **where** it started, and lets you **abort and
|
|
88
|
+
retry** before the user ever sees the corruption.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## How SIMURG differs
|
|
93
|
+
|
|
94
|
+
| | **SIMURG** | post-hoc linter | LLM-as-judge | perplexity threshold |
|
|
95
|
+
|---|---|---|---|---|
|
|
96
|
+
| when it fires | **mid-generation**, ~590 chars past onset | after the full answer | after the full answer | post-hoc, or needs logprob access |
|
|
97
|
+
| what the user sees | **zero bad tokens when onset is in the hold window**; otherwise the clean prefix plus a bad tail of at most ~900 chars, replaced by the retry | the whole corrupt answer | the whole corrupt answer | varies |
|
|
98
|
+
| why it fired | a named, human-readable reason on every alarm | a pattern list | the judge's opinion, if any | one number |
|
|
99
|
+
| model-agnostic | any OpenAI-compatible endpoint, or any stream you feed | any | any | needs a logprob-capable backend |
|
|
100
|
+
| overhead | numpy-only, ~197k chars/sec on one CPU core | trivial | one extra LLM call per answer | per-token logprobs |
|
|
101
|
+
|
|
102
|
+
The zero-leak property is the point: post-hoc checks can only tell you that the
|
|
103
|
+
answer was bad *after the user read it*. SIMURG holds the opening of every stream
|
|
104
|
+
in a buffer, releases it only once it is verified clean, keeps re-checking, and
|
|
105
|
+
cuts the stream the moment it crosses the calibrated threshold.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## How it works
|
|
110
|
+
|
|
111
|
+
SIMURG makes **one O(1)-per-character pass** over the stream, maintaining a set of
|
|
112
|
+
incremental features (digit fraction, foreign-script fraction, repetition rate,
|
|
113
|
+
compressibility, type-token ratio, script-switch rate, structural-artifact density,
|
|
114
|
+
...), and feeds a pluggable detector ensemble on top of them:
|
|
115
|
+
|
|
116
|
+
```mermaid
|
|
117
|
+
flowchart TD
|
|
118
|
+
A["token stream"] --> B["stream features<br/>one O(1) per character incremental pass"]
|
|
119
|
+
B --> C1["char n-gram surprise<br/>self-calibrating, no reference corpus"]
|
|
120
|
+
B --> C2["Count-Min repetition sketch<br/>constant memory, 8k counters"]
|
|
121
|
+
B --> C3["rolling SimHash drift<br/>topic collapse detection"]
|
|
122
|
+
B --> C4["robust-z self-calibration<br/>baselines frozen on the clean prefix"]
|
|
123
|
+
B --> C5["rule tier<br/>interpretable thresholds, zero training"]
|
|
124
|
+
C1 --> D["conformal fusion<br/>finite-sample false-alarm budget"]
|
|
125
|
+
C2 --> D
|
|
126
|
+
C3 --> D
|
|
127
|
+
C4 --> D
|
|
128
|
+
C5 --> D
|
|
129
|
+
L["learned tier<br/>15-weight online logistic model"] --> D
|
|
130
|
+
D --> E["CLEAN / SUSPECT / CORRUPT<br/>plus Page-Hinkley onset localization"]
|
|
131
|
+
E --> F["zero-leak protocol<br/>HOLD first 350 chars, RELEASE if clean,<br/>re-check every 400, ABORT on corrupt"]
|
|
132
|
+
F --> G["bad tokens never reach the UI"]
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### The five detectors
|
|
136
|
+
|
|
137
|
+
| detector | what it measures | why it catches corruption |
|
|
138
|
+
|---|---|---|
|
|
139
|
+
| **char n-gram surprise** | predictive surprise of each char against an in-stream 3-gram model | loops and garbage drive surprise toward zero |
|
|
140
|
+
| **Count-Min repetition** | n-gram repetition rate in a constant-memory sketch | repetition collapse is the most common production failure |
|
|
141
|
+
| **rolling SimHash drift** | distance of a 48-token fingerprint from the clean-prefix baseline | topic collapse and regurgitation move the fingerprint |
|
|
142
|
+
| **robust-z self-calibration** | every feature z-scored against its own frozen clean-prefix baseline | no hand-tuned magic numbers, adapts to any domain |
|
|
143
|
+
| **rules** | interpretable thresholds (digit fraction, script switch, template markers, ...) | day-one coverage, every alarm is a sentence a human can read |
|
|
144
|
+
|
|
145
|
+
### Two tiers cooperate
|
|
146
|
+
|
|
147
|
+
- **Rule tier.** Interpretable thresholds on the stream features. Works on day one
|
|
148
|
+
with **zero training**, and every alarm is explainable: `"repetition loop
|
|
149
|
+
rate=0.71"`, `"digit fraction 0.57"`, `"script switch en to zh"`.
|
|
150
|
+
- **Learned tier.** A small **online logistic regression** (15 weights, a few KB)
|
|
151
|
+
that adds robustness and **keeps learning in production** via `partial_fit`.
|
|
152
|
+
|
|
153
|
+
### Conformal calibration: a budget, not a hope
|
|
154
|
+
|
|
155
|
+
The fusion layer sets its thresholds from the score distribution on *clean*
|
|
156
|
+
streams, which gives a **finite-sample guarantee on the false-alarm rate**. "Flag
|
|
157
|
+
at most 2% of clean outputs" is a knob you set and the calibration enforces, not a
|
|
158
|
+
threshold you hope holds.
|
|
159
|
+
|
|
160
|
+
### The zero-leak protocol
|
|
161
|
+
|
|
162
|
+
1. **HOLD** the first 350 characters. A stream that is corrupt from the start is
|
|
163
|
+
killed before a single character reaches the UI.
|
|
164
|
+
2. **RELEASE** the prefix if it scores clean, and freeze the self-calibrated
|
|
165
|
+
baselines on it.
|
|
166
|
+
3. **Re-check** every 400 characters for the rest of the stream.
|
|
167
|
+
4. **ABORT** on a calibrated threshold crossing (with a 2-hit or hard-rule
|
|
168
|
+
hysteresis so a single noisy checkpoint does not kill a good answer).
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Zero-leak in action
|
|
173
|
+
|
|
174
|
+
A synthetic stream that is clean prose and then collapses into a repetition loop
|
|
175
|
+
at character 339. SIMURG holds the opening, verifies the clean prefix, scores
|
|
176
|
+
the stream at every 400-char checkpoint, and aborts 821 characters after the
|
|
177
|
+
loop starts. Corrupt streams that are already bad at the 350-char checkpoint
|
|
178
|
+
are blocked fully (12 of 21 in the benchmark, see below); for this mid-stream
|
|
179
|
+
onset the user sees the clean prefix plus a short bad tail, and the guard's
|
|
180
|
+
contract with the host is a **retry**: `GuardedLLM` regenerates the answer and
|
|
181
|
+
the host replaces the shown text, so the bad tail never becomes the final
|
|
182
|
+
output:
|
|
183
|
+
|
|
184
|
+

|
|
185
|
+
|
|
186
|
+
Every alarm carries the reasons that fired it. For the stream above:
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
repetition loop rate=0.66 zlib=0.10
|
|
190
|
+
vocabulary collapse ttr=0.09
|
|
191
|
+
surprise collapse low_frac=1.00
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Benchmark
|
|
197
|
+
|
|
198
|
+
Reproducible end-to-end benchmark: builds the **CorruptBench** synthetic set
|
|
199
|
+
(243 streams, 4 failure classes), trains the learned tier, calibrates the
|
|
200
|
+
conformal thresholds, and reports the full table:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
pip install -e .
|
|
204
|
+
python3 -m simurg.data.evaluate # seed 7, deterministic dataset
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Test split (81 streams), seed 7:
|
|
208
|
+
|
|
209
|
+
| metric | value |
|
|
210
|
+
|---|---|
|
|
211
|
+
| stream-level TPR | **78/80 = 0.975** |
|
|
212
|
+
| recall, repetition collapse | 16/18 = 0.89 |
|
|
213
|
+
| recall, cross-lingual drift | 25/25 = 1.00 |
|
|
214
|
+
| recall, regurgitation | 19/19 = 1.00 |
|
|
215
|
+
| recall, structural breakdown | 18/18 = 1.00 |
|
|
216
|
+
| detection latency past onset | **median 590**, p90 868 chars |
|
|
217
|
+
| onset localization error | median 532 chars |
|
|
218
|
+
| zero-leak (onset inside hold window) | 12/21 blocked fully |
|
|
219
|
+
| throughput | **197,632 chars/sec** |
|
|
220
|
+
| stream-level AUROC (final score) | 0.55, dragged down by ties at p=1.0 and a 1-stream clean test split; TPR/FPR at the calibrated threshold is the operating metric |
|
|
221
|
+
|
|
222
|
+
In addition, the shipped detector **flagged 0 false alarms on 121 real production
|
|
223
|
+
texts** from a self-hosted reasoning-model deployment.
|
|
224
|
+
|
|
225
|
+

|
|
226
|
+
|
|
227
|
+

|
|
228
|
+
|
|
229
|
+
**Those numbers describe the bundled domain.** The detector is only as good as the
|
|
230
|
+
clean corpus it calibrates against, so retrain on your own traffic before you
|
|
231
|
+
trust it in production. It takes seconds, see
|
|
232
|
+
[below](#teach-it-your-domain-and-your-failure-modes).
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Install
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
pip install simurg # numpy only
|
|
240
|
+
pip install simurg[figures] # + matplotlib, for the paper plots
|
|
241
|
+
pip install simurg[test] # + pytest
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
From source:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
git clone https://github.com/doofzoff/SIMURG.git
|
|
248
|
+
cd SIMURG
|
|
249
|
+
pip install -e .
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Quick start
|
|
255
|
+
|
|
256
|
+
### 1. Guard any OpenAI-compatible endpoint (3 lines)
|
|
257
|
+
|
|
258
|
+
Works with **vLLM, llama.cpp server, TGI, Ollama, OpenAI, OpenRouter**: anything
|
|
259
|
+
that speaks `/v1/chat/completions`. Batteries included: the zero-leak protocol
|
|
260
|
+
plus an **abort, retry, fallback-model** ladder.
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
from simurg import GuardedLLM
|
|
264
|
+
|
|
265
|
+
llm = GuardedLLM(
|
|
266
|
+
"http://localhost:8000/v1", model="my-model",
|
|
267
|
+
retries=1,
|
|
268
|
+
fallback=GuardedLLM("https://openrouter.ai/api/v1",
|
|
269
|
+
model="qwen/qwen3", api_key="sk-..."), # optional
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
result = llm.chat(
|
|
273
|
+
[{"role": "user", "content": "Explain how oil prices affect a small economy."}],
|
|
274
|
+
on_token=lambda t: print(t, end="", flush=True), # only CLEAN text is ever forwarded
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
print(result.ok) # True if a clean answer was produced
|
|
278
|
+
print(result.verdict) # "clean" | "suspect" | "corrupt"
|
|
279
|
+
print(result.attempts) # the full ladder: what each attempt did and why
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
If an attempt corrupts, **nothing from it reaches `on_token`**. A corrupt attempt
|
|
283
|
+
is retried; if all retries fail, the fallback model is tried.
|
|
284
|
+
|
|
285
|
+
### 2. Guard a stream from any source (5 lines)
|
|
286
|
+
|
|
287
|
+
Not on an OpenAI-style API? Wrap your own token loop:
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
from simurg import Simurg
|
|
291
|
+
|
|
292
|
+
s = Simurg() # rule tier works with zero setup
|
|
293
|
+
for token in my_llm_stream():
|
|
294
|
+
v = s.feed(token)
|
|
295
|
+
if v.state == "corrupt":
|
|
296
|
+
abort_and_retry(reason=v.reasons, onset=v.onset_char)
|
|
297
|
+
break
|
|
298
|
+
ui.write(v.released) # text cleared for display (may lag while holding)
|
|
299
|
+
final = s.finish()
|
|
300
|
+
ui.write(final.released)
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### 3. Post-hoc check of a finished text
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
from simurg import Simurg
|
|
307
|
+
|
|
308
|
+
s = Simurg()
|
|
309
|
+
s.feed(whole_text)
|
|
310
|
+
print(s.finish().state) # "clean" / "suspect" / "corrupt"
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Teach it your domain and your failure modes
|
|
316
|
+
|
|
317
|
+
### Retrain on your traffic
|
|
318
|
+
|
|
319
|
+
Feed the calibration step **your** good outputs so the thresholds fit your domain:
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
# bring your own clean corpus (.jsonl with a "text" field per line)
|
|
323
|
+
SIMURG_CORPUS_JSONL=/path/to/my_clean_outputs.jsonl python3 -m simurg.data.evaluate --save
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Full guide, including the quick path, the live dashboard, and the production
|
|
327
|
+
flywheel: **[docs/TRAINING.md](docs/TRAINING.md)**.
|
|
328
|
+
|
|
329
|
+
### Teach it a NEW failure mode from examples, with an honesty gate
|
|
330
|
+
|
|
331
|
+
Give SIMURG examples of *your* model's bad outputs. It tells you **whether that
|
|
332
|
+
failure is even catchable** in stream statistics, and hands you a fitted detector
|
|
333
|
+
if it is:
|
|
334
|
+
|
|
335
|
+
```python
|
|
336
|
+
from simurg import fit_custom_detector
|
|
337
|
+
|
|
338
|
+
report, detector = fit_custom_detector(
|
|
339
|
+
"template_leak",
|
|
340
|
+
clean_texts = my_good_outputs, # 50+
|
|
341
|
+
corrupt_texts = my_bad_outputs, # 20+
|
|
342
|
+
)
|
|
343
|
+
print(report)
|
|
344
|
+
# verdict: DETECTABLE held-out AUROC: 0.98 -> auto-registered into every Simurg()
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
The gate is the point: fluent factual lies come back **`NOT DETECTABLE`** instead
|
|
348
|
+
of a false promise. Details, plus the zero-training `LexiconDetector` for known
|
|
349
|
+
bad markers like `<|im_start|>`: **[docs/CUSTOM.md](docs/CUSTOM.md)**.
|
|
350
|
+
|
|
351
|
+
### Watch it train, live
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
python3 -m simurg.training.train_live # writes metrics for the bundled dashboard
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
A real-time web dashboard: log-loss, accuracy, AUROC, **all 15 weights animating
|
|
358
|
+
per epoch**, memory, and the final held-out TPR/FPR verdict.
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Live guard dashboard
|
|
363
|
+
|
|
364
|
+
A second web page for *runtime*: connect it to any OpenAI-compatible endpoint,
|
|
365
|
+
send a prompt, and watch the answer get guarded while it is generated. The
|
|
366
|
+
dashboard renders in real time:
|
|
367
|
+
|
|
368
|
+
- the **released stream text** (what the user would actually see),
|
|
369
|
+
- the **fused corruption score** with the calibrated SUSPECT/ABORT thresholds
|
|
370
|
+
and the 350-char hold zone,
|
|
371
|
+
- the **corruption onset marker** and the human-readable **reasons**,
|
|
372
|
+
- **all 15 stream features** as sparklines, sampled at every checkpoint.
|
|
373
|
+
|
|
374
|
+
Every run is recorded as a **session** (timestamped frames with score, state,
|
|
375
|
+
released text, features and reasons). The sessions panel lists them, deletes
|
|
376
|
+
them, and **replays any session at up to 128x** for postmortem analysis, so a
|
|
377
|
+
corrupt answer from Tuesday can be re-watched the way a crash log is read.
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
python3 -m simurg.guard_dashboard --port 8321
|
|
381
|
+
# open http://127.0.0.1:8321, point it at your endpoint, guard a stream
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Pasted texts can also be analyzed at full speed in the same UI. Same
|
|
385
|
+
self-contained dark style as the training dashboard, zero new dependencies:
|
|
386
|
+
the server is stdlib-only and acts as a CORS-free proxy to your endpoint.
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## What SIMURG is NOT
|
|
391
|
+
|
|
392
|
+
SIMURG detects **corrupt or degenerate decoding**, not **factual wrongness**. A
|
|
393
|
+
fluent, well-formed sentence that is simply *false* ("the capital of Australia is
|
|
394
|
+
Sydney") has no stream-statistical signature: it looks exactly like a true
|
|
395
|
+
sentence. For that you need **grounding** (constrain the model to retrieved facts
|
|
396
|
+
and make it quote them), retrieval verification, or a factuality checker.
|
|
397
|
+
|
|
398
|
+
SIMURG guards the *delivery*; grounding guards the *content*. Use both.
|
|
399
|
+
`fit_custom_detector` will explicitly refuse to pretend it can catch this class.
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## Repository layout
|
|
404
|
+
|
|
405
|
+
```
|
|
406
|
+
src/simurg/
|
|
407
|
+
├── core.py taxonomy, detector protocol, registry
|
|
408
|
+
├── features.py the single O(1)/char stream-feature pass
|
|
409
|
+
├── signals/ the raw estimators: n-gram surprise, Count-Min sketch,
|
|
410
|
+
│ rolling SimHash, robust-z calibration, Page-Hinkley
|
|
411
|
+
├── detection/ rules, detectors, conformal fusion, sentinel (protocol)
|
|
412
|
+
├── learning/ online logistic model, custom-failure-mode training (BYOC)
|
|
413
|
+
├── integrations/ GuardedLLM, the OpenAI-compatible drop-in guard
|
|
414
|
+
├── data/ CorruptBench synth, dataset builder, benchmark, generator
|
|
415
|
+
├── training/ live-training run + real-time web dashboard
|
|
416
|
+
├── guard_dashboard.py live guard dashboard server (stdlib-only, SSE, sessions)
|
|
417
|
+
├── guard_ui/ live guard dashboard front-end + recorded sessions
|
|
418
|
+
└── weights/ shipped model + conformal thresholds (use as a pair)
|
|
419
|
+
docs/ TRAINING.md, CUSTOM.md
|
|
420
|
+
examples/ runnable quickstart
|
|
421
|
+
tests/ sentinel regressions + end-to-end dashboard tests
|
|
422
|
+
figures/ benchmark figures referenced by this README
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
## Roadmap
|
|
428
|
+
|
|
429
|
+
Ideas under active consideration, in rough priority order:
|
|
430
|
+
|
|
431
|
+
1. **Engine-level abort.** Ship integrations that stop generation *inside* the
|
|
432
|
+
inference engine (a vLLM streaming hook and a generic SSE middleware proxy),
|
|
433
|
+
so an abort frees GPU time instead of just saving the UI. The guard already
|
|
434
|
+
exposes everything a host needs; what is missing is the wiring.
|
|
435
|
+
2. **Fleet telemetry.** Export `p(corrupt)`, verdict transitions, and onset
|
|
436
|
+
positions as Prometheus metrics or OpenTelemetry spans, so a Grafana panel can
|
|
437
|
+
show a *corruption rate per model and endpoint* and alert when a quantization
|
|
438
|
+
or a prompt change starts producing bad streams.
|
|
439
|
+
3. **Zero-dependency runtime.** Export the guard core (features, sketches,
|
|
440
|
+
fusion) to ONNX or a small C library that runs inside the inference server
|
|
441
|
+
with no Python, for hosts that cannot take a numpy dependency on the hot path.
|
|
442
|
+
4. **CI regression suite.** A golden corpus of labeled clean and corrupt streams
|
|
443
|
+
with fixed expected verdicts, plus latency and throughput budgets, run as a
|
|
444
|
+
GitHub Action on every pull request: the build fails when a threshold tweak
|
|
445
|
+
quietly degrades detection.
|
|
446
|
+
5. **Multi-stream fleet mode.** Guard N parallel live streams in one process,
|
|
447
|
+
with per-stream sessions and a single dashboard that compares corruption
|
|
448
|
+
rates across endpoints, so a bad quantization shows up as one lane going red
|
|
449
|
+
while the others stay green.
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## FAQ
|
|
454
|
+
|
|
455
|
+
**Will it catch factual hallucinations?**
|
|
456
|
+
No, and it will tell you so. Factual errors have no stream-statistical signature.
|
|
457
|
+
Use grounding or a factuality checker for content, SIMURG for delivery.
|
|
458
|
+
|
|
459
|
+
**What is the overhead?**
|
|
460
|
+
One O(1) pass per character, ~197k chars/sec on a laptop CPU. A 50 tok/s model
|
|
461
|
+
writes ~250 chars/sec, so the guard is hundreds of times faster than the model it
|
|
462
|
+
guards. Memory is bounded per stream: 8,192 sketch counters, a 48-token SimHash
|
|
463
|
+
window, and an n-gram table capped at 60k contexts.
|
|
464
|
+
|
|
465
|
+
**Does it only work with English?**
|
|
466
|
+
No. Script features are language-agnostic (per-script fractions, switch rates),
|
|
467
|
+
and you can declare your expected scripts at construction time
|
|
468
|
+
(`Simurg(expected_scripts=("cyrillic",))`). Retrain on your traffic for best
|
|
469
|
+
results.
|
|
470
|
+
|
|
471
|
+
**What is the SUSPECT state for?**
|
|
472
|
+
It is a non-blocking warning tier between CLEAN and CORRUPT. Your host can use it
|
|
473
|
+
to slow the UI down, show a subtle indicator, or pre-stage a retry, without
|
|
474
|
+
discarding a stream that may still turn out clean.
|
|
475
|
+
|
|
476
|
+
**How do I retrain on my own domain?**
|
|
477
|
+
`SIMURG_CORPUS_JSONL=... python3 -m simurg.data.evaluate --save` over your clean
|
|
478
|
+
outputs. It rebuilds the weights and the conformal thresholds in seconds. Full
|
|
479
|
+
guide: [docs/TRAINING.md](docs/TRAINING.md).
|
|
480
|
+
|
|
481
|
+
---
|
|
482
|
+
|
|
483
|
+
## Citation
|
|
484
|
+
|
|
485
|
+
```bibtex
|
|
486
|
+
@techreport{aghayev2026simurg,
|
|
487
|
+
title = {SIMURG: Zero-Leak Online Detection of LLM Decoding Corruption in Production Streams},
|
|
488
|
+
author = {Aghayev, Farid},
|
|
489
|
+
institution = {HAL-X AI},
|
|
490
|
+
year = {2026},
|
|
491
|
+
url = {https://github.com/doofzoff/SIMURG}
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
## License
|
|
496
|
+
|
|
497
|
+
**Apache-2.0**. See [LICENSE](LICENSE). Developed by **doofZ (Farid Aghayev)**,
|
|
498
|
+
HAL-X AI.
|