invaria 0.1.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.
- invaria-0.1.0/.gitignore +34 -0
- invaria-0.1.0/LICENSE +21 -0
- invaria-0.1.0/PKG-INFO +175 -0
- invaria-0.1.0/README.md +142 -0
- invaria-0.1.0/pyproject.toml +66 -0
- invaria-0.1.0/src/invaria/__init__.py +4 -0
- invaria-0.1.0/src/invaria/api.py +31 -0
- invaria-0.1.0/src/invaria/cli/main.py +49 -0
- invaria-0.1.0/src/invaria/compiler/pipeline.py +244 -0
- invaria-0.1.0/src/invaria/compressors/base.py +19 -0
- invaria-0.1.0/src/invaria/compressors/lexical.py +31 -0
- invaria-0.1.0/src/invaria/config.py +13 -0
- invaria-0.1.0/src/invaria/exceptions.py +47 -0
- invaria-0.1.0/src/invaria/ir/models.py +53 -0
- invaria-0.1.0/src/invaria/optimizer/search.py +14 -0
- invaria-0.1.0/src/invaria/verification/contract.py +14 -0
- invaria-0.1.0/src/invaria/verification/report.py +23 -0
- invaria-0.1.0/src/invaria/verification/verifier.py +40 -0
- invaria-0.1.0/tests/unit/test_api.py +22 -0
invaria-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Local batch testing files
|
|
2
|
+
batch_test.py
|
|
3
|
+
batch_prompts.txt
|
|
4
|
+
batch_result.txt
|
|
5
|
+
|
|
6
|
+
# Environments & Package Management
|
|
7
|
+
.venv
|
|
8
|
+
venv/
|
|
9
|
+
env/
|
|
10
|
+
uv.lock
|
|
11
|
+
|
|
12
|
+
# Python Cache
|
|
13
|
+
__pycache__/
|
|
14
|
+
*.py[cod]
|
|
15
|
+
*$py.class
|
|
16
|
+
|
|
17
|
+
# Distribution / Packaging
|
|
18
|
+
build/
|
|
19
|
+
dist/
|
|
20
|
+
*.egg-info/
|
|
21
|
+
.eggs/
|
|
22
|
+
*.egg
|
|
23
|
+
|
|
24
|
+
# Testing
|
|
25
|
+
.pytest_cache/
|
|
26
|
+
.coverage
|
|
27
|
+
htmlcov/
|
|
28
|
+
|
|
29
|
+
# IDE / OS
|
|
30
|
+
.vscode/
|
|
31
|
+
.idea/
|
|
32
|
+
.DS_Store
|
|
33
|
+
|
|
34
|
+
.gitignore
|
invaria-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 sgindeed
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
invaria-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: invaria
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Invaria — Universal LLM Context Compiler
|
|
5
|
+
Project-URL: Homepage, https://github.com/sgindeed/invaria
|
|
6
|
+
Project-URL: Repository, https://github.com/sgindeed/invaria
|
|
7
|
+
Author-email: Supratim Ghosh <sgindeed234@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: pydantic>=2.0.0
|
|
20
|
+
Provides-Extra: anthropic
|
|
21
|
+
Requires-Dist: anthropic>=0.18.0; extra == 'anthropic'
|
|
22
|
+
Provides-Extra: cli
|
|
23
|
+
Requires-Dist: typer>=0.9.0; extra == 'cli'
|
|
24
|
+
Provides-Extra: google
|
|
25
|
+
Requires-Dist: google-genai>=0.3.0; extra == 'google'
|
|
26
|
+
Provides-Extra: openai
|
|
27
|
+
Requires-Dist: openai>=1.0.0; extra == 'openai'
|
|
28
|
+
Provides-Extra: test
|
|
29
|
+
Requires-Dist: hypothesis>=6.0.0; extra == 'test'
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'test'
|
|
31
|
+
Requires-Dist: pytest>=7.0.0; extra == 'test'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# Invaria — Universal LLM Context Compiler
|
|
35
|
+
|
|
36
|
+
> **Compress less. Preserve more. Prove everything.**
|
|
37
|
+
|
|
38
|
+
[](https://pypi.org/project/invaria/)
|
|
39
|
+
[](https://pypi.org/project/invaria/)
|
|
40
|
+
[](https://opensource.org/licenses/MIT)
|
|
41
|
+
|
|
42
|
+
**Invaria** is a deterministic, rule-based context compiler for Large Language Models.
|
|
43
|
+
|
|
44
|
+
Instead of blindly dropping tokens based on probabilistic attention scores (which often leads to hallucinated numbers, broken URLs, or lost named entities), Invaria treats your prompt as source code. It compiles the context into an intermediate representation (IR), generates compression candidates, **verifies** that strict preservation contracts are met, and emits the cheapest safe representation.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Why Invaria?
|
|
49
|
+
|
|
50
|
+
Most prompt compressors use smaller ML models to guess which words are "unimportant." This is dangerous for enterprise use cases where missing a "NOT", dropping a crucial ID, or altering a currency amount is catastrophic.
|
|
51
|
+
|
|
52
|
+
Invaria introduces a **Verification Engine**. It mathematically guarantees that:
|
|
53
|
+
* ✅ **Numerical Invariants** (e.g., `$5000`, `99%`, `0.001`) are preserved.
|
|
54
|
+
* ✅ **Lexical Entities** (Proper nouns, CamelCase, Acronyms) are untouched.
|
|
55
|
+
* ✅ **Structured Identifiers** (URLs, Email addresses, API Keys, UUIDs) are safely retained.
|
|
56
|
+
* ✅ **Formatting** (Markdown tables, JSON structures, code blocks) remains syntactically valid.
|
|
57
|
+
|
|
58
|
+
If a compressed candidate drops a required invariant, Invaria rejects it.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Installation
|
|
63
|
+
|
|
64
|
+
Install via pip. Invaria `v0.1.0` is extremely lightweight and relies primarily on `pydantic`.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install invaria
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
*Note: Optional dependencies for ML-based semantic compression (`openai`, `anthropic`, `google`) will be utilized in upcoming releases.*
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Python API Quickstart
|
|
75
|
+
|
|
76
|
+
Invaria is designed to be easily integrated into your existing LLM pipelines, RAG systems, or agent frameworks.
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from invaria import Invaria
|
|
80
|
+
|
|
81
|
+
# Initialize the compiler
|
|
82
|
+
compiler = Invaria(mode="invariant")
|
|
83
|
+
|
|
84
|
+
text = """
|
|
85
|
+
User Alice transferred $5,000 to Bob on Monday...
|
|
86
|
+
Please do not delete the backup files!!! They are stored in Amazon S3.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
# Compress the context
|
|
90
|
+
result = compiler.compress(text)
|
|
91
|
+
|
|
92
|
+
# The compressed text
|
|
93
|
+
print(result.text)
|
|
94
|
+
# Output: "User Alice transferred $5,000 to Bob on Monday. Please do not delete the backup files! They are stored in Amazon S3."
|
|
95
|
+
|
|
96
|
+
# Access detailed metrics
|
|
97
|
+
print(f"Original Tokens: {result.original_tokens}")
|
|
98
|
+
print(f"Compressed Tokens: {result.compressed_tokens}")
|
|
99
|
+
print(f"Token Reduction: {result.token_reduction}")
|
|
100
|
+
print(f"Compression Ratio: {result.compression_ratio:.2f}x")
|
|
101
|
+
|
|
102
|
+
# Cryptographic proof of preservation
|
|
103
|
+
print(f"Verified Safe: {result.proof.verified}")
|
|
104
|
+
print(f"Preserved Entities:{result.proof.preserved}")
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Inspecting Extracted Invariants
|
|
108
|
+
|
|
109
|
+
You can also use Invaria just to parse and inspect the semantic constraints within a prompt without compressing it:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
ir = compiler.compile("Contact Support at help@example.com if Stripe webhooks fail.")
|
|
113
|
+
|
|
114
|
+
for node_id, node in ir.nodes.items():
|
|
115
|
+
print(f"Type: {node.type} | Value: {node.value}")
|
|
116
|
+
# Type: entity | Value: Contact
|
|
117
|
+
# Type: entity | Value: Support
|
|
118
|
+
# Type: entity | Value: help@example.com
|
|
119
|
+
# Type: entity | Value: Stripe
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## CLI Usage
|
|
125
|
+
|
|
126
|
+
Invaria comes with a built-in Command Line Interface for quickly compressing files or verifying external compressions in your CI/CD pipelines.
|
|
127
|
+
|
|
128
|
+
### Compress a File
|
|
129
|
+
```bash
|
|
130
|
+
invaria compress prompt.txt --mode invariant
|
|
131
|
+
```
|
|
132
|
+
*Outputs the compression metrics, the cryptographic proof status, and the compressed text.*
|
|
133
|
+
|
|
134
|
+
### Verify a Compression
|
|
135
|
+
If you have an original context and a compressed version, Invaria can verify if the compressed version safely retained all necessary invariants from the original:
|
|
136
|
+
```bash
|
|
137
|
+
invaria verify original.txt compressed.txt
|
|
138
|
+
```
|
|
139
|
+
*Outputs `Verification Passed: True` or `False`.*
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Compiler Architecture
|
|
144
|
+
|
|
145
|
+
Invaria operates similarly to a traditional programming language compiler:
|
|
146
|
+
|
|
147
|
+
```mermaid
|
|
148
|
+
graph LR
|
|
149
|
+
A[Text Input] --> B(COMPILE)
|
|
150
|
+
B -->|Semantic IR| C(OPTIMIZE)
|
|
151
|
+
C -->|Generate Candidates| D(VERIFY)
|
|
152
|
+
D -->|Check Constraints| E(EMIT)
|
|
153
|
+
E --> F[Verified Compressed Text]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
1. **Compile (Extract Invariants):** Scans the text to build a `SemanticGraph` (IR) identifying all Entities, Numerical Invariants, Dates, and Instructions.
|
|
157
|
+
2. **Optimize (Generate Candidates):** Applies active compressors (like `LexicalCompressor` for whitespace/punctuation normalization).
|
|
158
|
+
3. **Verify (Preservation Contract):** The `VerificationEngine` checks the candidate string against the Semantic IR to ensure zero critical tokens were lost or mutated.
|
|
159
|
+
4. **Emit:** Computes SHA-256 hashes for the proof, calculates token reductions, and returns a `CompressionResult`.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Roadmap
|
|
164
|
+
|
|
165
|
+
* **v0.1.x (Current):** Deterministic Lexical Compression, Entity/Numerical Invariant Extraction, Core Verification Engine, CLI.
|
|
166
|
+
* **v0.2.x:** Syntactic Compression (safe removal of filler phrases, stop words) and Tiktoken integration for exact token pricing.
|
|
167
|
+
* **v0.3.x:** Abstractive Semantic Compression using LLM providers (`openai`, `anthropic`, `google`) backed by Invaria's deterministic `VerificationEngine` to prevent LLM hallucinations during summarization.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
174
|
+
|
|
175
|
+
Copyright (c) 2026 Supratim Ghosh & Invaria Contributors.
|
invaria-0.1.0/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Invaria — Universal LLM Context Compiler
|
|
2
|
+
|
|
3
|
+
> **Compress less. Preserve more. Prove everything.**
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/invaria/)
|
|
6
|
+
[](https://pypi.org/project/invaria/)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
9
|
+
**Invaria** is a deterministic, rule-based context compiler for Large Language Models.
|
|
10
|
+
|
|
11
|
+
Instead of blindly dropping tokens based on probabilistic attention scores (which often leads to hallucinated numbers, broken URLs, or lost named entities), Invaria treats your prompt as source code. It compiles the context into an intermediate representation (IR), generates compression candidates, **verifies** that strict preservation contracts are met, and emits the cheapest safe representation.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Why Invaria?
|
|
16
|
+
|
|
17
|
+
Most prompt compressors use smaller ML models to guess which words are "unimportant." This is dangerous for enterprise use cases where missing a "NOT", dropping a crucial ID, or altering a currency amount is catastrophic.
|
|
18
|
+
|
|
19
|
+
Invaria introduces a **Verification Engine**. It mathematically guarantees that:
|
|
20
|
+
* ✅ **Numerical Invariants** (e.g., `$5000`, `99%`, `0.001`) are preserved.
|
|
21
|
+
* ✅ **Lexical Entities** (Proper nouns, CamelCase, Acronyms) are untouched.
|
|
22
|
+
* ✅ **Structured Identifiers** (URLs, Email addresses, API Keys, UUIDs) are safely retained.
|
|
23
|
+
* ✅ **Formatting** (Markdown tables, JSON structures, code blocks) remains syntactically valid.
|
|
24
|
+
|
|
25
|
+
If a compressed candidate drops a required invariant, Invaria rejects it.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
Install via pip. Invaria `v0.1.0` is extremely lightweight and relies primarily on `pydantic`.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install invaria
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
*Note: Optional dependencies for ML-based semantic compression (`openai`, `anthropic`, `google`) will be utilized in upcoming releases.*
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Python API Quickstart
|
|
42
|
+
|
|
43
|
+
Invaria is designed to be easily integrated into your existing LLM pipelines, RAG systems, or agent frameworks.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from invaria import Invaria
|
|
47
|
+
|
|
48
|
+
# Initialize the compiler
|
|
49
|
+
compiler = Invaria(mode="invariant")
|
|
50
|
+
|
|
51
|
+
text = """
|
|
52
|
+
User Alice transferred $5,000 to Bob on Monday...
|
|
53
|
+
Please do not delete the backup files!!! They are stored in Amazon S3.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
# Compress the context
|
|
57
|
+
result = compiler.compress(text)
|
|
58
|
+
|
|
59
|
+
# The compressed text
|
|
60
|
+
print(result.text)
|
|
61
|
+
# Output: "User Alice transferred $5,000 to Bob on Monday. Please do not delete the backup files! They are stored in Amazon S3."
|
|
62
|
+
|
|
63
|
+
# Access detailed metrics
|
|
64
|
+
print(f"Original Tokens: {result.original_tokens}")
|
|
65
|
+
print(f"Compressed Tokens: {result.compressed_tokens}")
|
|
66
|
+
print(f"Token Reduction: {result.token_reduction}")
|
|
67
|
+
print(f"Compression Ratio: {result.compression_ratio:.2f}x")
|
|
68
|
+
|
|
69
|
+
# Cryptographic proof of preservation
|
|
70
|
+
print(f"Verified Safe: {result.proof.verified}")
|
|
71
|
+
print(f"Preserved Entities:{result.proof.preserved}")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Inspecting Extracted Invariants
|
|
75
|
+
|
|
76
|
+
You can also use Invaria just to parse and inspect the semantic constraints within a prompt without compressing it:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
ir = compiler.compile("Contact Support at help@example.com if Stripe webhooks fail.")
|
|
80
|
+
|
|
81
|
+
for node_id, node in ir.nodes.items():
|
|
82
|
+
print(f"Type: {node.type} | Value: {node.value}")
|
|
83
|
+
# Type: entity | Value: Contact
|
|
84
|
+
# Type: entity | Value: Support
|
|
85
|
+
# Type: entity | Value: help@example.com
|
|
86
|
+
# Type: entity | Value: Stripe
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## CLI Usage
|
|
92
|
+
|
|
93
|
+
Invaria comes with a built-in Command Line Interface for quickly compressing files or verifying external compressions in your CI/CD pipelines.
|
|
94
|
+
|
|
95
|
+
### Compress a File
|
|
96
|
+
```bash
|
|
97
|
+
invaria compress prompt.txt --mode invariant
|
|
98
|
+
```
|
|
99
|
+
*Outputs the compression metrics, the cryptographic proof status, and the compressed text.*
|
|
100
|
+
|
|
101
|
+
### Verify a Compression
|
|
102
|
+
If you have an original context and a compressed version, Invaria can verify if the compressed version safely retained all necessary invariants from the original:
|
|
103
|
+
```bash
|
|
104
|
+
invaria verify original.txt compressed.txt
|
|
105
|
+
```
|
|
106
|
+
*Outputs `Verification Passed: True` or `False`.*
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Compiler Architecture
|
|
111
|
+
|
|
112
|
+
Invaria operates similarly to a traditional programming language compiler:
|
|
113
|
+
|
|
114
|
+
```mermaid
|
|
115
|
+
graph LR
|
|
116
|
+
A[Text Input] --> B(COMPILE)
|
|
117
|
+
B -->|Semantic IR| C(OPTIMIZE)
|
|
118
|
+
C -->|Generate Candidates| D(VERIFY)
|
|
119
|
+
D -->|Check Constraints| E(EMIT)
|
|
120
|
+
E --> F[Verified Compressed Text]
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
1. **Compile (Extract Invariants):** Scans the text to build a `SemanticGraph` (IR) identifying all Entities, Numerical Invariants, Dates, and Instructions.
|
|
124
|
+
2. **Optimize (Generate Candidates):** Applies active compressors (like `LexicalCompressor` for whitespace/punctuation normalization).
|
|
125
|
+
3. **Verify (Preservation Contract):** The `VerificationEngine` checks the candidate string against the Semantic IR to ensure zero critical tokens were lost or mutated.
|
|
126
|
+
4. **Emit:** Computes SHA-256 hashes for the proof, calculates token reductions, and returns a `CompressionResult`.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Roadmap
|
|
131
|
+
|
|
132
|
+
* **v0.1.x (Current):** Deterministic Lexical Compression, Entity/Numerical Invariant Extraction, Core Verification Engine, CLI.
|
|
133
|
+
* **v0.2.x:** Syntactic Compression (safe removal of filler phrases, stop words) and Tiktoken integration for exact token pricing.
|
|
134
|
+
* **v0.3.x:** Abstractive Semantic Compression using LLM providers (`openai`, `anthropic`, `google`) backed by Invaria's deterministic `VerificationEngine` to prevent LLM hallucinations during summarization.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
141
|
+
|
|
142
|
+
Copyright (c) 2026 Supratim Ghosh & Invaria Contributors.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "invaria"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Invaria — Universal LLM Context Compiler"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Supratim Ghosh", email = "sgindeed234@gmail.com" }
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"pydantic>=2.0.0"
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
openai = [
|
|
31
|
+
"openai>=1.0.0"
|
|
32
|
+
]
|
|
33
|
+
anthropic = [
|
|
34
|
+
"anthropic>=0.18.0"
|
|
35
|
+
]
|
|
36
|
+
google = [
|
|
37
|
+
"google-genai>=0.3.0"
|
|
38
|
+
]
|
|
39
|
+
cli = [
|
|
40
|
+
"typer>=0.9.0"
|
|
41
|
+
]
|
|
42
|
+
test = [
|
|
43
|
+
"pytest>=7.0.0",
|
|
44
|
+
"hypothesis>=6.0.0",
|
|
45
|
+
"pytest-asyncio>=0.21.0"
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[project.urls]
|
|
49
|
+
Homepage = "https://github.com/sgindeed/invaria"
|
|
50
|
+
Repository = "https://github.com/sgindeed/invaria"
|
|
51
|
+
|
|
52
|
+
[project.scripts]
|
|
53
|
+
invaria = "invaria.cli.main:main"
|
|
54
|
+
|
|
55
|
+
[tool.pytest.ini_options]
|
|
56
|
+
minversion = "7.0"
|
|
57
|
+
addopts = "-ra -q"
|
|
58
|
+
testpaths = ["tests"]
|
|
59
|
+
asyncio_mode = "strict"
|
|
60
|
+
|
|
61
|
+
[tool.ruff]
|
|
62
|
+
line-length = 88
|
|
63
|
+
target-version = "py310"
|
|
64
|
+
|
|
65
|
+
[tool.hatch.build.targets.wheel]
|
|
66
|
+
packages = ["src/invaria"]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from .config import InvariaConfig
|
|
3
|
+
from .compiler.pipeline import CompilerPipeline, CompressionResult
|
|
4
|
+
from .ir.models import SemanticGraph
|
|
5
|
+
|
|
6
|
+
class Invaria:
|
|
7
|
+
def __init__(self, **kwargs):
|
|
8
|
+
self.config = InvariaConfig(**kwargs)
|
|
9
|
+
self.pipeline = CompilerPipeline(self.config)
|
|
10
|
+
|
|
11
|
+
def compress(self, text: str, **kwargs) -> CompressionResult:
|
|
12
|
+
# Override config optionally for this run
|
|
13
|
+
if kwargs:
|
|
14
|
+
local_config = self.config.model_copy(update=kwargs)
|
|
15
|
+
local_pipeline = CompilerPipeline(local_config)
|
|
16
|
+
return local_pipeline.compress(text)
|
|
17
|
+
return self.pipeline.compress(text)
|
|
18
|
+
|
|
19
|
+
def compile(self, text: str) -> SemanticGraph:
|
|
20
|
+
return self.pipeline.compile(text)
|
|
21
|
+
|
|
22
|
+
def verify(self, original: str, compressed: str) -> bool:
|
|
23
|
+
ir = self.pipeline.compile(original)
|
|
24
|
+
report = self.pipeline.verifier.verify(ir, compressed)
|
|
25
|
+
return report.passed
|
|
26
|
+
|
|
27
|
+
def inspect(self, text: str) -> SemanticGraph:
|
|
28
|
+
return self.pipeline.compile(text)
|
|
29
|
+
|
|
30
|
+
def benchmark(self, text: str) -> None:
|
|
31
|
+
raise NotImplementedError("Benchmark framework is not yet initialized.")
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
from ..api import Invaria
|
|
4
|
+
|
|
5
|
+
def main():
|
|
6
|
+
parser = argparse.ArgumentParser(description="Invaria — Universal LLM Context Compiler")
|
|
7
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
8
|
+
|
|
9
|
+
compress_parser = subparsers.add_parser("compress", help="Compress a context")
|
|
10
|
+
compress_parser.add_argument("input_file", help="Input file path")
|
|
11
|
+
compress_parser.add_argument("--mode", default="invariant", help="Compression mode")
|
|
12
|
+
|
|
13
|
+
verify_parser = subparsers.add_parser("verify", help="Verify a compressed context")
|
|
14
|
+
verify_parser.add_argument("original", help="Original context file")
|
|
15
|
+
verify_parser.add_argument("compressed", help="Compressed context file")
|
|
16
|
+
|
|
17
|
+
args = parser.parse_args()
|
|
18
|
+
|
|
19
|
+
if not args.command:
|
|
20
|
+
parser.print_help()
|
|
21
|
+
sys.exit(1)
|
|
22
|
+
|
|
23
|
+
compiler = Invaria()
|
|
24
|
+
|
|
25
|
+
if args.command == "compress":
|
|
26
|
+
with open(args.input_file, 'r', encoding='utf-8') as f:
|
|
27
|
+
text = f.read()
|
|
28
|
+
|
|
29
|
+
result = compiler.compress(text, mode=args.mode)
|
|
30
|
+
|
|
31
|
+
print(f"Original tokens (approx): {result.original_tokens}")
|
|
32
|
+
print(f"Compressed tokens (approx): {result.compressed_tokens}")
|
|
33
|
+
print(f"Reduction: {result.token_reduction}")
|
|
34
|
+
print(f"Ratio: {result.compression_ratio:.2f}")
|
|
35
|
+
print(f"Verified: {result.proof.verified}")
|
|
36
|
+
print("\n--- COMPRESSED OUTPUT ---")
|
|
37
|
+
print(result.text)
|
|
38
|
+
|
|
39
|
+
elif args.command == "verify":
|
|
40
|
+
with open(args.original, 'r', encoding='utf-8') as f:
|
|
41
|
+
orig_text = f.read()
|
|
42
|
+
with open(args.compressed, 'r', encoding='utf-8') as f:
|
|
43
|
+
comp_text = f.read()
|
|
44
|
+
|
|
45
|
+
is_valid = compiler.verify(orig_text, comp_text)
|
|
46
|
+
print(f"Verification Passed: {is_valid}")
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
main()
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
from typing import List, Dict, Any
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from ..config import InvariaConfig
|
|
8
|
+
from ..ir.models import (
|
|
9
|
+
SemanticGraph,
|
|
10
|
+
Entity,
|
|
11
|
+
NumericalInvariant,
|
|
12
|
+
SourceSpan,
|
|
13
|
+
)
|
|
14
|
+
from ..compressors.lexical import LexicalCompressor
|
|
15
|
+
from ..verification.verifier import VerificationEngine
|
|
16
|
+
from ..verification.contract import PreservationContract
|
|
17
|
+
from ..verification.report import CompressionProof
|
|
18
|
+
from ..optimizer.search import Optimizer
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CompressionResult(BaseModel):
|
|
22
|
+
text: str
|
|
23
|
+
original_tokens: int
|
|
24
|
+
compressed_tokens: int
|
|
25
|
+
token_reduction: int
|
|
26
|
+
compression_ratio: float
|
|
27
|
+
mode: str
|
|
28
|
+
representation: str
|
|
29
|
+
proof: CompressionProof
|
|
30
|
+
invariants: List[Any]
|
|
31
|
+
warnings: List[str]
|
|
32
|
+
metadata: Dict[str, Any]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CompilerPipeline:
|
|
36
|
+
# Matches words, numbers with currency/units/decimals, times, emails, and identifiers
|
|
37
|
+
_TOKEN_PATTERN = re.compile(
|
|
38
|
+
r"[A-Za-z0-9_$%+-]+(?:[@.:/-][A-Za-z0-9_$%+-]+)*"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def __init__(self, config: InvariaConfig):
|
|
42
|
+
self.config = config
|
|
43
|
+
self.compressors = [LexicalCompressor()]
|
|
44
|
+
self.contract = PreservationContract(strictness=config.mode)
|
|
45
|
+
self.verifier = VerificationEngine(self.contract)
|
|
46
|
+
self.optimizer = Optimizer(config)
|
|
47
|
+
self._compressor_map = {c.name: c for c in self.compressors}
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def _hash_text(text: str) -> str:
|
|
51
|
+
return hashlib.sha256(text.encode("utf-8")).hexdigest()[:16]
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def _hash_ir(ir: SemanticGraph) -> str:
|
|
55
|
+
payload = json.dumps(
|
|
56
|
+
{k: v.model_dump() for k, v in sorted(ir.nodes.items())},
|
|
57
|
+
sort_keys=True,
|
|
58
|
+
default=str,
|
|
59
|
+
)
|
|
60
|
+
return hashlib.sha256(payload.encode("utf-8")).hexdigest()[:16]
|
|
61
|
+
|
|
62
|
+
def extract_invariants(self, text: str) -> SemanticGraph:
|
|
63
|
+
graph = SemanticGraph()
|
|
64
|
+
# Only treat short ALL-CAPS words as acronyms if the text itself isn't shouting in ALL CAPS
|
|
65
|
+
text_has_lowercase = any(c.islower() for c in text)
|
|
66
|
+
|
|
67
|
+
for idx, match in enumerate(self._TOKEN_PATTERN.finditer(text)):
|
|
68
|
+
raw_token = match.group(0)
|
|
69
|
+
clean_token = raw_token.strip(".,!?:;\"'()[]{}-")
|
|
70
|
+
if not clean_token:
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
start, end = match.span()
|
|
74
|
+
span = SourceSpan(start_char=start, end_char=end, text=raw_token)
|
|
75
|
+
|
|
76
|
+
has_digit = any(ch.isdigit() for ch in clean_token)
|
|
77
|
+
has_alpha = any(ch.isalpha() for ch in clean_token)
|
|
78
|
+
|
|
79
|
+
# 1. Numerical / Quantitative Invariants (pure numbers, currency, percentages, versions, times)
|
|
80
|
+
if has_digit and not has_alpha:
|
|
81
|
+
unit = None
|
|
82
|
+
if clean_token.startswith(("$", "€", "£", "₹")):
|
|
83
|
+
unit = clean_token[0]
|
|
84
|
+
elif clean_token.endswith("%"):
|
|
85
|
+
unit = "%"
|
|
86
|
+
node_id = f"num_{idx}"
|
|
87
|
+
graph.nodes[node_id] = NumericalInvariant(
|
|
88
|
+
id=node_id,
|
|
89
|
+
value=clean_token,
|
|
90
|
+
normalized_value=clean_token,
|
|
91
|
+
unit=unit,
|
|
92
|
+
source_span=span,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# 2. Mixed Alphanumeric Identifiers & Quantities (e.g., S3, AI302, 500mg, 15th)
|
|
96
|
+
elif has_digit and has_alpha:
|
|
97
|
+
if clean_token[0].isdigit():
|
|
98
|
+
node_id = f"num_{idx}"
|
|
99
|
+
graph.nodes[node_id] = NumericalInvariant(
|
|
100
|
+
id=node_id,
|
|
101
|
+
value=clean_token,
|
|
102
|
+
normalized_value=clean_token,
|
|
103
|
+
source_span=span,
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
node_id = f"ent_{idx}"
|
|
107
|
+
graph.nodes[node_id] = Entity(
|
|
108
|
+
id=node_id,
|
|
109
|
+
value=clean_token,
|
|
110
|
+
normalized_value=clean_token,
|
|
111
|
+
source_span=span,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# 3. Structured Identifiers (e.g., emails or domain-like tokens containing '@')
|
|
115
|
+
elif "@" in clean_token:
|
|
116
|
+
node_id = f"ent_{idx}"
|
|
117
|
+
graph.nodes[node_id] = Entity(
|
|
118
|
+
id=node_id,
|
|
119
|
+
value=clean_token,
|
|
120
|
+
normalized_value=clean_token,
|
|
121
|
+
source_span=span,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# 4. Lexical Entities: Proper Nouns, CamelCase/PascalCase, and Acronyms
|
|
125
|
+
elif len(clean_token) >= 2:
|
|
126
|
+
is_camel_or_pascal = (
|
|
127
|
+
any(c.isupper() for c in clean_token[1:])
|
|
128
|
+
and any(c.islower() for c in clean_token)
|
|
129
|
+
)
|
|
130
|
+
is_capitalized_word = (
|
|
131
|
+
clean_token[0].isupper()
|
|
132
|
+
and clean_token[1:].islower()
|
|
133
|
+
and len(clean_token) > 2
|
|
134
|
+
)
|
|
135
|
+
is_acronym = (
|
|
136
|
+
text_has_lowercase
|
|
137
|
+
and clean_token.isupper()
|
|
138
|
+
and 2 <= len(clean_token) <= 5
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
if is_camel_or_pascal or is_capitalized_word or is_acronym:
|
|
142
|
+
node_id = f"ent_{idx}"
|
|
143
|
+
graph.nodes[node_id] = Entity(
|
|
144
|
+
id=node_id,
|
|
145
|
+
value=clean_token,
|
|
146
|
+
normalized_value=clean_token.lower(),
|
|
147
|
+
source_span=span,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
return graph
|
|
151
|
+
|
|
152
|
+
def compile(self, text: str) -> SemanticGraph:
|
|
153
|
+
return self.extract_invariants(text)
|
|
154
|
+
|
|
155
|
+
def compress(self, text: str) -> CompressionResult:
|
|
156
|
+
original_tokens = len(text)
|
|
157
|
+
orig_hash = self._hash_text(text)
|
|
158
|
+
|
|
159
|
+
# 1. COMPILE
|
|
160
|
+
ir = self.compile(text)
|
|
161
|
+
inv_hash = self._hash_ir(ir)
|
|
162
|
+
|
|
163
|
+
# 2. GENERATE CANDIDATES
|
|
164
|
+
all_candidates = []
|
|
165
|
+
for comp in self.compressors:
|
|
166
|
+
if comp.can_handle(text, ir, self.config):
|
|
167
|
+
candidates = comp.generate_candidates(text, ir, self.config)
|
|
168
|
+
for cand in candidates:
|
|
169
|
+
all_candidates.append((comp.name, cand))
|
|
170
|
+
|
|
171
|
+
# 3. VERIFY
|
|
172
|
+
valid_candidates = []
|
|
173
|
+
costs = []
|
|
174
|
+
proofs = []
|
|
175
|
+
|
|
176
|
+
for comp_name, cand in all_candidates:
|
|
177
|
+
report = self.verifier.verify(ir, cand)
|
|
178
|
+
if report.passed:
|
|
179
|
+
valid_candidates.append(cand)
|
|
180
|
+
# 4. MEASURE COST using the specific compressor that generated the candidate
|
|
181
|
+
comp_instance = self._compressor_map[comp_name]
|
|
182
|
+
cost = comp_instance.estimate_cost(cand, self.config)
|
|
183
|
+
costs.append(cost)
|
|
184
|
+
|
|
185
|
+
proof = CompressionProof(
|
|
186
|
+
original_hash=orig_hash,
|
|
187
|
+
invariant_hash=inv_hash,
|
|
188
|
+
candidate_hash=self._hash_text(cand),
|
|
189
|
+
verified=True,
|
|
190
|
+
exact_reconstruction=(cand == text),
|
|
191
|
+
preservation_contract=self.config.mode,
|
|
192
|
+
preserved=len(report.preserved_invariants),
|
|
193
|
+
changed=len(report.changed_invariants),
|
|
194
|
+
warnings=report.warnings,
|
|
195
|
+
compressor_chain=[comp_name],
|
|
196
|
+
)
|
|
197
|
+
proofs.append(proof)
|
|
198
|
+
|
|
199
|
+
# FAIL-SAFE
|
|
200
|
+
if not valid_candidates:
|
|
201
|
+
return CompressionResult(
|
|
202
|
+
text=text,
|
|
203
|
+
original_tokens=original_tokens,
|
|
204
|
+
compressed_tokens=original_tokens,
|
|
205
|
+
token_reduction=0,
|
|
206
|
+
compression_ratio=1.0,
|
|
207
|
+
mode=self.config.mode,
|
|
208
|
+
representation="original",
|
|
209
|
+
proof=CompressionProof(
|
|
210
|
+
original_hash=orig_hash,
|
|
211
|
+
invariant_hash=inv_hash,
|
|
212
|
+
candidate_hash=orig_hash,
|
|
213
|
+
verified=True,
|
|
214
|
+
exact_reconstruction=True,
|
|
215
|
+
preservation_contract=self.config.mode,
|
|
216
|
+
preserved=len(ir.nodes),
|
|
217
|
+
changed=0,
|
|
218
|
+
),
|
|
219
|
+
invariants=list(ir.nodes.values()),
|
|
220
|
+
warnings=["Fallback to original text"],
|
|
221
|
+
metadata={},
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# 5. OPTIMIZE & SELECT
|
|
225
|
+
best_cand = self.optimizer.select_best(valid_candidates, costs)
|
|
226
|
+
best_idx = valid_candidates.index(best_cand)
|
|
227
|
+
best_proof = proofs[best_idx]
|
|
228
|
+
|
|
229
|
+
comp_tokens = len(best_cand)
|
|
230
|
+
|
|
231
|
+
# 6. RETURN
|
|
232
|
+
return CompressionResult(
|
|
233
|
+
text=best_cand,
|
|
234
|
+
original_tokens=original_tokens,
|
|
235
|
+
compressed_tokens=comp_tokens,
|
|
236
|
+
token_reduction=original_tokens - comp_tokens,
|
|
237
|
+
compression_ratio=original_tokens / comp_tokens if comp_tokens > 0 else 0.0,
|
|
238
|
+
mode=self.config.mode,
|
|
239
|
+
representation=best_proof.compressor_chain[0],
|
|
240
|
+
proof=best_proof,
|
|
241
|
+
invariants=list(ir.nodes.values()),
|
|
242
|
+
warnings=best_proof.warnings,
|
|
243
|
+
metadata={},
|
|
244
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any, List, Optional
|
|
3
|
+
from ..config import InvariaConfig
|
|
4
|
+
from ..ir.models import SemanticGraph
|
|
5
|
+
|
|
6
|
+
class Compressor(ABC):
|
|
7
|
+
name: str
|
|
8
|
+
|
|
9
|
+
@abstractmethod
|
|
10
|
+
def can_handle(self, context: str, ir: SemanticGraph, config: InvariaConfig) -> bool:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def generate_candidates(self, context: str, ir: SemanticGraph, config: InvariaConfig) -> List[str]:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def estimate_cost(self, candidate: str, config: InvariaConfig) -> float:
|
|
19
|
+
pass
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import List
|
|
3
|
+
from .base import Compressor
|
|
4
|
+
from ..config import InvariaConfig
|
|
5
|
+
from ..ir.models import SemanticGraph
|
|
6
|
+
|
|
7
|
+
class LexicalCompressor(Compressor):
|
|
8
|
+
name: str = "lexical"
|
|
9
|
+
|
|
10
|
+
def can_handle(self, context: str, ir: SemanticGraph, config: InvariaConfig) -> bool:
|
|
11
|
+
return True
|
|
12
|
+
|
|
13
|
+
def generate_candidates(self, context: str, ir: SemanticGraph, config: InvariaConfig) -> List[str]:
|
|
14
|
+
# Basic deterministic lexical transformations
|
|
15
|
+
|
|
16
|
+
# 1. Whitespace normalization
|
|
17
|
+
normalized = re.sub(r'\s+', ' ', context).strip()
|
|
18
|
+
|
|
19
|
+
# 2. Duplicate punctuation cleanup
|
|
20
|
+
cleaned = re.sub(r'([.?!,])\1+', r'\1', normalized)
|
|
21
|
+
|
|
22
|
+
candidates = [normalized]
|
|
23
|
+
if cleaned != normalized:
|
|
24
|
+
candidates.append(cleaned)
|
|
25
|
+
|
|
26
|
+
return candidates
|
|
27
|
+
|
|
28
|
+
def estimate_cost(self, candidate: str, config: InvariaConfig) -> float:
|
|
29
|
+
# Simple heuristic: length of string as a proxy for token cost
|
|
30
|
+
# In later phases, use actual tokenizer
|
|
31
|
+
return float(len(candidate))
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from typing import Optional, Literal
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
|
|
4
|
+
class InvariaConfig(BaseModel):
|
|
5
|
+
mode: Literal["semantic", "invariant", "reversible"] = "invariant"
|
|
6
|
+
provider: Optional[str] = None
|
|
7
|
+
model: Optional[str] = None
|
|
8
|
+
tokenizer: Optional[str] = None
|
|
9
|
+
verify: bool = True
|
|
10
|
+
logging_level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = "INFO"
|
|
11
|
+
|
|
12
|
+
# Optimizer settings
|
|
13
|
+
target_tokens: Optional[int] = None
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
class InvariaError(Exception):
|
|
2
|
+
"""Base exception for all Invaria errors."""
|
|
3
|
+
pass
|
|
4
|
+
|
|
5
|
+
class ParsingError(InvariaError):
|
|
6
|
+
"""Raised when parsing context fails."""
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
class UnsupportedContentError(InvariaError):
|
|
10
|
+
"""Raised when content type is not supported."""
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
class CompressionError(InvariaError):
|
|
14
|
+
"""Raised when compression process fails."""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
class VerificationError(InvariaError):
|
|
18
|
+
"""Raised when a candidate fails verification checks."""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
class InvariantViolation(VerificationError):
|
|
22
|
+
"""Raised when a required invariant is violated."""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
class ReconstructionError(VerificationError):
|
|
26
|
+
"""Raised when exact reversible reconstruction fails."""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
class ConfigurationError(InvariaError):
|
|
30
|
+
"""Raised when configuration is invalid."""
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
class ProviderError(InvariaError):
|
|
34
|
+
"""Raised when interacting with an LLM provider fails."""
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
class TokenizerError(InvariaError):
|
|
38
|
+
"""Raised when tokenization fails."""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
class RenderingError(InvariaError):
|
|
42
|
+
"""Raised when rendering a candidate fails."""
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
class OptimizationError(InvariaError):
|
|
46
|
+
"""Raised when the optimization engine fails to find a valid representation."""
|
|
47
|
+
pass
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
|
|
4
|
+
class SourceSpan(BaseModel):
|
|
5
|
+
start_char: int
|
|
6
|
+
end_char: int
|
|
7
|
+
text: str
|
|
8
|
+
|
|
9
|
+
class SemanticNode(BaseModel):
|
|
10
|
+
id: str
|
|
11
|
+
type: str
|
|
12
|
+
value: Any
|
|
13
|
+
normalized_value: Optional[Any] = None
|
|
14
|
+
source_span: Optional[SourceSpan] = None
|
|
15
|
+
confidence: float = 1.0
|
|
16
|
+
provenance: Optional[str] = None
|
|
17
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
18
|
+
|
|
19
|
+
class Entity(SemanticNode):
|
|
20
|
+
type: str = "entity"
|
|
21
|
+
|
|
22
|
+
class Fact(SemanticNode):
|
|
23
|
+
type: str = "fact"
|
|
24
|
+
subject: str
|
|
25
|
+
predicate: str
|
|
26
|
+
object: str
|
|
27
|
+
|
|
28
|
+
class Constraint(SemanticNode):
|
|
29
|
+
type: str = "constraint"
|
|
30
|
+
polarity: str = "positive" # "positive" or "negative"
|
|
31
|
+
|
|
32
|
+
class NumericalInvariant(SemanticNode):
|
|
33
|
+
type: str = "number"
|
|
34
|
+
unit: Optional[str] = None
|
|
35
|
+
|
|
36
|
+
class TemporalInvariant(SemanticNode):
|
|
37
|
+
type: str = "temporal"
|
|
38
|
+
relation: str = "at" # "before", "after", "at"
|
|
39
|
+
|
|
40
|
+
class Instruction(SemanticNode):
|
|
41
|
+
type: str = "instruction"
|
|
42
|
+
modality: str = "must" # "must", "should", "may", etc.
|
|
43
|
+
|
|
44
|
+
class GraphEdge(BaseModel):
|
|
45
|
+
source_id: str
|
|
46
|
+
target_id: str
|
|
47
|
+
relation: str
|
|
48
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
49
|
+
|
|
50
|
+
class SemanticGraph(BaseModel):
|
|
51
|
+
nodes: Dict[str, SemanticNode] = Field(default_factory=dict)
|
|
52
|
+
edges: List[GraphEdge] = Field(default_factory=list)
|
|
53
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from typing import List, Tuple
|
|
2
|
+
from ..config import InvariaConfig
|
|
3
|
+
|
|
4
|
+
class Optimizer:
|
|
5
|
+
def __init__(self, config: InvariaConfig):
|
|
6
|
+
self.config = config
|
|
7
|
+
|
|
8
|
+
def select_best(self, valid_candidates: List[str], costs: List[float]) -> str:
|
|
9
|
+
if not valid_candidates:
|
|
10
|
+
raise ValueError("No valid candidates provided to the optimizer.")
|
|
11
|
+
|
|
12
|
+
# Simple greedy search: select the one with the lowest cost
|
|
13
|
+
best_idx = costs.index(min(costs))
|
|
14
|
+
return valid_candidates[best_idx]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from typing import List, Set
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
|
|
4
|
+
class PreservationContract(BaseModel):
|
|
5
|
+
strictness: str = "strict"
|
|
6
|
+
required: Set[str] = Field(default_factory=lambda: {
|
|
7
|
+
"entities",
|
|
8
|
+
"facts",
|
|
9
|
+
"constraints",
|
|
10
|
+
"numbers",
|
|
11
|
+
"dates",
|
|
12
|
+
"instructions"
|
|
13
|
+
})
|
|
14
|
+
verification_level: str = "CRITICAL"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from typing import List, Any
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
|
|
4
|
+
class VerificationReport(BaseModel):
|
|
5
|
+
passed: bool
|
|
6
|
+
critical_failures: List[str] = Field(default_factory=list)
|
|
7
|
+
warnings: List[str] = Field(default_factory=list)
|
|
8
|
+
preserved_invariants: List[Any] = Field(default_factory=list)
|
|
9
|
+
changed_invariants: List[Any] = Field(default_factory=list)
|
|
10
|
+
missing_invariants: List[Any] = Field(default_factory=list)
|
|
11
|
+
confidence: float = 1.0
|
|
12
|
+
|
|
13
|
+
class CompressionProof(BaseModel):
|
|
14
|
+
original_hash: str
|
|
15
|
+
invariant_hash: str
|
|
16
|
+
candidate_hash: str
|
|
17
|
+
verified: bool
|
|
18
|
+
exact_reconstruction: bool
|
|
19
|
+
preservation_contract: str
|
|
20
|
+
preserved: int
|
|
21
|
+
changed: int
|
|
22
|
+
warnings: List[str] = Field(default_factory=list)
|
|
23
|
+
compressor_chain: List[str] = Field(default_factory=list)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from .report import VerificationReport
|
|
3
|
+
from .contract import PreservationContract
|
|
4
|
+
from ..ir.models import SemanticGraph
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class VerificationEngine:
|
|
8
|
+
def __init__(self, contract: PreservationContract):
|
|
9
|
+
self.contract = contract
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def _contains_exact_token(text: str, token: str) -> bool:
|
|
13
|
+
escaped = re.escape(token)
|
|
14
|
+
# Ensure alphanumeric boundaries are respected so '15' doesn't match '1500'
|
|
15
|
+
prefix = r"(?<![A-Za-z0-9_])" if token[0].isalnum() else ""
|
|
16
|
+
suffix = r"(?![A-Za-z0-9_])" if token[-1].isalnum() else ""
|
|
17
|
+
pattern = f"{prefix}{escaped}{suffix}"
|
|
18
|
+
return re.search(pattern, text) is not None
|
|
19
|
+
|
|
20
|
+
def verify(self, original_ir: SemanticGraph, candidate_text: str) -> VerificationReport:
|
|
21
|
+
failures = []
|
|
22
|
+
preserved = []
|
|
23
|
+
missing = []
|
|
24
|
+
|
|
25
|
+
for node_id, node in original_ir.nodes.items():
|
|
26
|
+
val_str = str(node.value)
|
|
27
|
+
if self._contains_exact_token(candidate_text, val_str):
|
|
28
|
+
preserved.append(node)
|
|
29
|
+
else:
|
|
30
|
+
failures.append(f"Missing invariant ({node.type}): {val_str}")
|
|
31
|
+
missing.append(node)
|
|
32
|
+
|
|
33
|
+
passed = len(failures) == 0
|
|
34
|
+
|
|
35
|
+
return VerificationReport(
|
|
36
|
+
passed=passed,
|
|
37
|
+
critical_failures=failures,
|
|
38
|
+
preserved_invariants=preserved,
|
|
39
|
+
missing_invariants=missing,
|
|
40
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from invaria import Invaria
|
|
2
|
+
|
|
3
|
+
def test_basic_compression():
|
|
4
|
+
compiler = Invaria(mode="invariant")
|
|
5
|
+
text = "This is a test. This is only a test."
|
|
6
|
+
|
|
7
|
+
result = compiler.compress(text)
|
|
8
|
+
|
|
9
|
+
assert result.proof.verified is True
|
|
10
|
+
# The lexical compressor should have cleaned up spaces
|
|
11
|
+
assert result.text == "This is a test. This is only a test."
|
|
12
|
+
assert result.compressed_tokens < result.original_tokens
|
|
13
|
+
|
|
14
|
+
def test_compile_api():
|
|
15
|
+
compiler = Invaria()
|
|
16
|
+
text = "PostgreSQL is a great database."
|
|
17
|
+
ir = compiler.compile(text)
|
|
18
|
+
|
|
19
|
+
# Should have extracted some naive entities (e.g., PostgreSQL)
|
|
20
|
+
assert len(ir.nodes) > 0
|
|
21
|
+
found_pg = any(node.value == "PostgreSQL" for node in ir.nodes.values())
|
|
22
|
+
assert found_pg is True
|