quasar-context 0.1.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.
quasar/__init__.py ADDED
@@ -0,0 +1,35 @@
1
+ """
2
+ Quasar — faithful context optimization for RAG apps.
3
+
4
+ Compress retrieved context before your LLM call. Critical values
5
+ (prices, IBANs, dates, IDs, legal refs) are preserved verbatim when the
6
+ budget allows — and you get an explicit warning when it can't, instead of
7
+ silent corruption.
8
+
9
+ Quick start:
10
+ from quasar import ContextOptimizer
11
+
12
+ opt = ContextOptimizer()
13
+ result = opt.optimize(query, retrieved_chunks, target_tokens=500)
14
+
15
+ result.context # compressed context -> feed to your LLM
16
+ result.report.faithful # bool: did every critical value survive?
17
+ print(result.report.summary())
18
+ """
19
+
20
+ from .core import (
21
+ ContextOptimizer,
22
+ OptimizerConfig,
23
+ OptimizationResult,
24
+ OptimizationReport,
25
+ find_critical,
26
+ )
27
+
28
+ __version__ = "0.1.0"
29
+ __all__ = [
30
+ "ContextOptimizer",
31
+ "OptimizerConfig",
32
+ "OptimizationResult",
33
+ "OptimizationReport",
34
+ "find_critical",
35
+ ]
quasar/core.py ADDED
@@ -0,0 +1,296 @@
1
+ """
2
+ ================================================================================
3
+ quasar_layer.py -- Drop-in context optimization for RAG apps
4
+ ================================================================================
5
+ WHAT IT IS
6
+ ----------
7
+ A single layer you insert between retrieval and your LLM call. It:
8
+ 1. Picks the right compression strategy for the context automatically.
9
+ 2. GUARANTEES exact critical content (IDs, prices, dates, citations) survives
10
+ verbatim -- the one thing pure relevance ranking can't promise.
11
+ 3. Reports exactly what it did: tokens saved, est. cost saved, what was kept,
12
+ and any faithfulness risk it caught.
13
+
14
+ WHAT IS NOT
15
+ -----------
16
+ Not a magic compressor that beats everyone. Honest benchmarks showed
17
+ embedding-selection beats LLMLingua at far lower cost but ties/loses to simple
18
+ top-k on pure QA. So this layer's value is NOT "best compression" -- it is:
19
+ - zero-config: developer drops it in, no tuning
20
+ - safe-by-reporting: when budget can't fit all critical data, it does NOT
21
+ silently corrupt -- it preserves what fits and WARNS about what it dropped.
22
+ (No compressor can fit N critical facts into a budget smaller than them;
23
+ the honest behavior is to surface that, not hide it.)
24
+ - transparent: shows the savings + what the LLM actually sees
25
+ - cheap: embeddings, not an LLM-to-compress (8-13x faster than LLMLingua)
26
+
27
+ DESIGN PRINCIPLE
28
+ ----------------
29
+ The product is the DECISION + the PROOF, not the algorithm. Developers don't
30
+ know whether to compress, how much, or whether it's safe. This answers that
31
+ per-call, automatically, with receipts.
32
+
33
+ INSTALL
34
+ pip install sentence-transformers tiktoken
35
+
36
+ USE (the whole API is three lines)
37
+ from quasar_layer import ContextOptimizer
38
+ opt = ContextOptimizer()
39
+ result = opt.optimize(query, retrieved_chunks, target_tokens=500)
40
+ # result.context -> compressed context, feed to your LLM
41
+ # result.report -> tokens saved, cost saved, faithfulness status
42
+ ================================================================================
43
+ """
44
+ from __future__ import annotations
45
+ import re, time
46
+ from dataclasses import dataclass, field
47
+ from typing import Optional
48
+
49
+ # ---------- optional deps, graceful fallback ----------
50
+ try:
51
+ import tiktoken; _ENC = tiktoken.get_encoding("cl100k_base")
52
+ except Exception:
53
+ _ENC = None
54
+ def ntok(t: str) -> int:
55
+ if _ENC: return len(_ENC.encode(t))
56
+ return max(1, int(len(t.split()) * 1.3))
57
+
58
+ _MODEL = None
59
+ def _model(name="all-MiniLM-L6-v2"):
60
+ global _MODEL
61
+ if _MODEL is None:
62
+ from sentence_transformers import SentenceTransformer
63
+ _MODEL = SentenceTransformer(name)
64
+ return _MODEL
65
+
66
+
67
+ # =============================================================================
68
+ # CRITICAL CONTENT DETECTION (the faithfulness guarantee)
69
+ # =============================================================================
70
+ # Spans matching these MUST survive verbatim. Extensible by the developer.
71
+ _CRITICAL_PATTERNS = [
72
+ (r'[€$£¥]\s?\d[\d,.]*', "currency"),
73
+ (r'\b\d{1,3}(?:[,.]\d{3})+(?:\.\d+)?\b', "large_number"),
74
+ (r'\b[A-HJ-NPR-Z0-9]{17}\b', "vin"),
75
+ (r'\bRO\d{2}[A-Z0-9]{16,}\b', "iban"),
76
+ (r'\b[A-Z]{2}\d{2}[A-Z0-9]{10,}\b', "account"),
77
+ (r'\b\d{1,2}[/.\-]\d{1,2}[/.\-]\d{2,4}\b', "date"),
78
+ (r'\b(?:Order|Article|Regulation|Section|Clause|Directive)\s+[\w/.\-]+', "legal_ref"),
79
+ (r'\b[A-Z0-9]{6,}\-[A-Z0-9]{3,}\b', "code"),
80
+ (r'\b\d{6,}\b', "long_id"),
81
+ ]
82
+ def find_critical(text: str) -> list[str]:
83
+ hits = []
84
+ for pat, kind in _CRITICAL_PATTERNS:
85
+ for m in re.finditer(pat, text):
86
+ hits.append(m.group(0))
87
+ return hits
88
+
89
+
90
+ # =============================================================================
91
+ # RESULT + REPORT
92
+ # =============================================================================
93
+ @dataclass
94
+ class OptimizationReport:
95
+ tokens_in: int
96
+ tokens_out: int
97
+ tokens_saved: int
98
+ pct_saved: float
99
+ est_cost_saved_usd: float # at a configurable $/1k tokens
100
+ strategy: str # which path was taken + why
101
+ critical_found: int
102
+ critical_preserved: int
103
+ faithful: bool # all critical content survived?
104
+ warnings: list = field(default_factory=list)
105
+ optimize_ms: float = 0.0
106
+ def summary(self) -> str:
107
+ flag = "OK" if self.faithful else "FAITHFULNESS RISK"
108
+ return (f"[Quasar] {self.pct_saved:.0f}% smaller "
109
+ f"({self.tokens_in}->{self.tokens_out} tok, "
110
+ f"~${self.est_cost_saved_usd:.4f} saved) | "
111
+ f"strategy={self.strategy} | critical {self.critical_preserved}/{self.critical_found} {flag}"
112
+ + ("" if not self.warnings else f" | {len(self.warnings)} warning(s)"))
113
+
114
+ @dataclass
115
+ class OptimizationResult:
116
+ context: str
117
+ report: OptimizationReport
118
+
119
+
120
+ # =============================================================================
121
+ # THE OPTIMIZER
122
+ # =============================================================================
123
+ @dataclass
124
+ class OptimizerConfig:
125
+ cost_per_1k_tokens: float = 0.003 # for the savings estimate (set to your model)
126
+ skip_if_under: int = 0 # if context already <= target, don't touch it
127
+ min_relevance: float = 0.05 # drop sentences below this query relevance
128
+ preserve_critical: bool = True # the faithfulness guarantee (on by default)
129
+ model_name: str = "all-MiniLM-L6-v2"
130
+
131
+ class ContextOptimizer:
132
+ """
133
+ Drop-in: retrieved context in, optimized context out, with receipts.
134
+ Auto-selects strategy:
135
+ - context already within budget -> passthrough (no cost, no risk)
136
+ - context over budget -> faithful embedding-selection:
137
+ * reserve critical spans verbatim (guarantee)
138
+ * fill remaining budget by query relevance (the workhorse that
139
+ beat LLMLingua at a fraction of the cost)
140
+ """
141
+ def __init__(self, config: OptimizerConfig = OptimizerConfig()):
142
+ self.cfg = config
143
+
144
+ def _split(self, text: str) -> list[str]:
145
+ return [s.strip() for s in re.split(r'(?<=[.!?])\s+', text.strip()) if s.strip()]
146
+
147
+ def optimize(self, query: str, context, target_tokens: int = 500) -> OptimizationResult:
148
+ t0 = time.perf_counter()
149
+ # accept either a string or a list of chunks
150
+ if isinstance(context, (list, tuple)):
151
+ context = "\n".join(str(c) for c in context)
152
+ tokens_in = ntok(context)
153
+ crit_all = find_critical(context)
154
+
155
+ # ---- STEP 0: DEDUPE FIRST (always) ----
156
+ # Collapse identical sentences so we never spend budget on copies.
157
+ # This MUST run before the passthrough check: a context of 8 identical
158
+ # sentences may fit under the budget, but shipping 8 copies to the LLM
159
+ # wastes the very budget we exist to protect.
160
+ raw_sents = self._split(context)
161
+ seen = set(); sents = []
162
+ for s in raw_sents:
163
+ key = re.sub(r'\s+', ' ', s.strip().lower())
164
+ if key in seen: continue
165
+ seen.add(key); sents.append(s)
166
+ deduped = " ".join(sents)
167
+ tokens_deduped = ntok(deduped)
168
+ dupes_removed = len(raw_sents) - len(sents)
169
+
170
+ # ---- STRATEGY 1: passthrough if the DEDUPED context already fits ----
171
+ if tokens_deduped <= target_tokens:
172
+ saved = tokens_in - tokens_deduped
173
+ crit_kept = sum(1 for c in set(crit_all) if c in deduped)
174
+ strategy = ("passthrough (already within budget)" if dupes_removed == 0
175
+ else f"dedupe-only ({dupes_removed} duplicate sentence(s) removed)")
176
+ rep = OptimizationReport(
177
+ tokens_in, tokens_deduped, saved,
178
+ 100.0 * saved / max(1, tokens_in),
179
+ self.cfg.cost_per_1k_tokens * saved / 1000.0,
180
+ strategy=strategy,
181
+ critical_found=len(set(crit_all)),
182
+ critical_preserved=crit_kept,
183
+ faithful=(crit_kept == len(set(crit_all))),
184
+ optimize_ms=(time.perf_counter()-t0)*1000)
185
+ return OptimizationResult(deduped, rep)
186
+
187
+ # ---- STRATEGY 2: faithful embedding-selection ----
188
+ model = _model(self.cfg.model_name)
189
+ embs = model.encode([query] + sents, normalize_embeddings=True, show_progress_bar=False)
190
+ qv, svs = embs[0], embs[1:]
191
+ sims = svs @ qv
192
+ toks = [ntok(s) for s in sents]
193
+
194
+ chosen, used = [], 0
195
+ warnings = []
196
+
197
+ # 2a. reserve sentences containing critical spans (best-effort preservation)
198
+ if self.cfg.preserve_critical:
199
+ crit_idx = [i for i, s in enumerate(sents) if find_critical(s)]
200
+ # sort critical sentences by relevance so if budget is too tight we keep
201
+ # the most query-relevant critical ones first, and warn about drops
202
+ crit_idx.sort(key=lambda i: -sims[i])
203
+ for i in crit_idx:
204
+ if used + toks[i] <= target_tokens:
205
+ chosen.append(i); used += toks[i]
206
+ else:
207
+ warnings.append(f"critical sentence dropped (budget too tight): "
208
+ f"'{sents[i][:50]}...'")
209
+
210
+ # 2b. fill remaining budget with top relevance (skip already chosen + filler)
211
+ rest = [i for i in range(len(sents)) if i not in chosen]
212
+ rest.sort(key=lambda i: -sims[i])
213
+ for i in rest:
214
+ if float(sims[i]) < self.cfg.min_relevance: break
215
+ if used + toks[i] > target_tokens: continue
216
+ chosen.append(i); used += toks[i]
217
+
218
+ out_text = " ".join(sents[i] for i in sorted(chosen))
219
+ tokens_out = ntok(out_text)
220
+
221
+ # faithfulness audit: did every critical span survive verbatim?
222
+ crit_out = find_critical(out_text)
223
+ preserved = sum(1 for c in set(crit_all) if c in out_text)
224
+ faithful = (preserved == len(set(crit_all)))
225
+ if not faithful:
226
+ missing = [c for c in set(crit_all) if c not in out_text]
227
+ warnings.append(f"{len(missing)} critical value(s) not preserved: {missing[:3]}")
228
+
229
+ saved = tokens_in - tokens_out
230
+ rep = OptimizationReport(
231
+ tokens_in, tokens_out, saved,
232
+ 100.0 * saved / max(1, tokens_in),
233
+ self.cfg.cost_per_1k_tokens * saved / 1000.0,
234
+ strategy="faithful-selection (critical reserved + relevance fill)",
235
+ critical_found=len(set(crit_all)),
236
+ critical_preserved=preserved,
237
+ faithful=faithful,
238
+ warnings=warnings,
239
+ optimize_ms=(time.perf_counter()-t0)*1000)
240
+ return OptimizationResult(out_text, rep)
241
+
242
+ # --- convenience: wrap an LLM call so the developer gets one-line integration ---
243
+ def optimized_prompt(self, query: str, context, target_tokens=500,
244
+ template="Context:\n{context}\n\nQuestion: {query}\nAnswer:"):
245
+ res = self.optimize(query, context, target_tokens)
246
+ return template.format(context=res.context, query=query), res.report
247
+
248
+
249
+ # =============================================================================
250
+ # DEMO — shows the developer experience
251
+ # =============================================================================
252
+ def _demo():
253
+ sample_chunks = [
254
+ "The quarterly business review covered several operational topics across regions.",
255
+ "Market conditions have remained generally stable over the recent reporting period.",
256
+ "The invoice total amount due is €47,350.00 payable by 15/03/2026 per the agreement.",
257
+ "Various stakeholders provided feedback during the planning session last week.",
258
+ "The applicable regulation is Order 1802/2014 Article 7 governing this transaction.",
259
+ "Overall sector trends suggest continued steady commercial activity this year.",
260
+ "The vehicle identification number on record is WAUZZZ8K9BA123456 for this unit.",
261
+ "Documentation has been updated to reflect the latest procedural changes made.",
262
+ "The team continues to monitor performance metrics across all departments daily.",
263
+ "Payment should be remitted to IBAN RO49AAAA1B31007593840000 before the deadline.",
264
+ ] * 3 # triple it to force real compression
265
+
266
+ query = "What is the total amount due and the payment deadline?"
267
+
268
+ print("="*78)
269
+ print("QUASAR CONTEXT OPTIMIZER — developer experience demo")
270
+ print("="*78)
271
+ opt = ContextOptimizer(OptimizerConfig(cost_per_1k_tokens=0.003))
272
+
273
+ for budget in [120, 300]:
274
+ res = opt.optimize(query, sample_chunks, target_tokens=budget)
275
+ print(f"\n--- target_tokens={budget} ---")
276
+ print(res.report.summary())
277
+ print(f" optimize time: {res.report.optimize_ms:.0f} ms")
278
+ if res.report.warnings:
279
+ for w in res.report.warnings: print(f" ! {w}")
280
+ print(f" LLM now sees ({res.report.tokens_out} tok):")
281
+ print(f' "{res.context[:220]}..."')
282
+
283
+ print("\n" + "="*78)
284
+ print("THE PITCH (honest):")
285
+ print(" - 3-line drop-in: optimize(query, chunks, target_tokens)")
286
+ print(" - Auto-passthrough when context already fits (no needless cost/risk)")
287
+ print(" - Preserves critical values (prices, IBANs, dates, refs) verbatim when")
288
+ print(" budget allows -- and WARNS (never silently corrupts) when it can't")
289
+ print(" - Dedupes repeated content so budget isn't wasted on copies")
290
+ print(" - 8-13x cheaper to run than LLMLingua (embeddings, not an LLM)")
291
+ print(" - Every call returns receipts: tokens saved, $ saved, faithfulness status")
292
+ print(" - The product is the DECISION + PROOF, not a 'magic' compressor.")
293
+ print("="*78)
294
+
295
+ if __name__ == "__main__":
296
+ _demo()
@@ -0,0 +1,202 @@
1
+ Metadata-Version: 2.4
2
+ Name: quasar-context
3
+ Version: 0.1.0
4
+ Summary: Faithful context optimization for RAG apps — compress without corrupting critical data
5
+ Author: Sebastian Sabo
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/sebastiansabo/quasar
8
+ Project-URL: Issues, https://github.com/sebastiansabo/quasar/issues
9
+ Keywords: rag,llm,context,compression,prompt,embeddings
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: Topic :: Scientific/Engineering :: Artificial Intelligence
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: sentence-transformers>=2.2.0
19
+ Requires-Dist: tiktoken>=0.5.0
20
+ Requires-Dist: numpy>=1.21.0
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=7.0; extra == "dev"
23
+ Requires-Dist: pytest-cov; extra == "dev"
24
+ Dynamic: license-file
25
+
26
+ # Quasar
27
+
28
+ **Faithful context optimization for RAG apps.**
29
+
30
+ Compress retrieved context before your LLM call — without silently corrupting the
31
+ values that matter. Prices, IBANs, dates, invoice numbers, legal references and
32
+ IDs are preserved **verbatim** when the budget allows. When it doesn't, Quasar
33
+ **tells you** instead of quietly dropping them.
34
+
35
+ ```python
36
+ from quasar import ContextOptimizer
37
+
38
+ opt = ContextOptimizer()
39
+ result = opt.optimize(query, retrieved_chunks, target_tokens=500)
40
+
41
+ llm_answer(query, result.context) # feed the compressed context
42
+ print(result.report.summary())
43
+ # [Quasar] 63% smaller (1284->475 tok, ~$0.0024 saved) | critical 7/7 OK
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Why this exists
49
+
50
+ Every context compressor optimizes for one thing: fewer tokens. None of them tell
51
+ you what they destroyed on the way.
52
+
53
+ If your RAG app retrieves an invoice and the compressor drops or mangles
54
+ `€47,350.00`, your LLM confidently answers with the wrong number and **you never
55
+ find out**. That's fine for a chatbot. It is not fine for finance, legal,
56
+ medical, compliance, or code.
57
+
58
+ Quasar makes one guarantee no other compressor makes:
59
+
60
+ > **It never silently corrupts your critical data.**
61
+ > Critical values survive verbatim, or you get an explicit warning.
62
+
63
+ ---
64
+
65
+ ## What it does
66
+
67
+ 1. **Detects critical spans** — currency, IBANs, dates, VINs, long IDs, legal
68
+ references, contract codes — by pattern.
69
+ 2. **Reserves them verbatim.** Critical sentences claim budget first and are
70
+ never rewritten. (Token-deletion compressors *rewrite* text — that's how they
71
+ corrupt exact values.)
72
+ 3. **Fills the remaining budget by relevance**, using embedding similarity to the
73
+ query. Filler is dropped.
74
+ 4. **Dedupes** repeated sentences so budget isn't wasted on copies.
75
+ 5. **Audits and reports.** Every call returns tokens saved, cost saved, and a
76
+ faithfulness status — with warnings naming any critical value it couldn't fit.
77
+
78
+ ---
79
+
80
+ ## Honest benchmarks
81
+
82
+ Measured on LongBench (narrativeqa / qasper / hotpotqa, N=30 per task), real LLM
83
+ judge, token-F1 scoring.
84
+
85
+ **Where Quasar wins:**
86
+
87
+ | Matchup | Result |
88
+ |---|---|
89
+ | vs **truncation** | **9–0** — wins at every task and budget |
90
+ | vs **LLMLingua** | **6–0** on accuracy, **8–13× faster** to compress |
91
+ | Long contexts | Handles 17k+ word documents LLMLingua **cannot process at all** |
92
+
93
+ LLMLingua runs a neural model to compress. Quasar runs embeddings. Same or better
94
+ accuracy, a fraction of the compute.
95
+
96
+ **Where Quasar does not win — stated plainly:**
97
+
98
+ Against a **pure top-k embedding filter** (e.g. LangChain's `EmbeddingsFilter`),
99
+ Quasar goes **3–6** on raw QA accuracy. On pure question-answering, simple
100
+ relevance ranking is competitive or better.
101
+
102
+ **So Quasar is not the accuracy leader on QA, and doesn't claim to be.** Its value
103
+ is the axis those benchmarks don't measure: *faithfulness*. A top-k filter has no
104
+ concept of critical data — it drops a low-relevance sentence containing your
105
+ invoice number without hesitation, and never tells you. Quasar reserves it, and
106
+ warns when it can't.
107
+
108
+ **Use Quasar when correctness of exact values matters more than the last 2% of
109
+ retrieval F1.** If you're building a general chatbot and only care about QA
110
+ accuracy, use a top-k filter — it's simpler and we'll say so.
111
+
112
+ ---
113
+
114
+ ## Install
115
+
116
+ ```bash
117
+ pip install quasar-context
118
+ ```
119
+
120
+ ## Usage
121
+
122
+ ### Basic
123
+
124
+ ```python
125
+ from quasar import ContextOptimizer
126
+
127
+ opt = ContextOptimizer() # loads the embedding model once
128
+ result = opt.optimize(
129
+ query="What is the total due and the deadline?",
130
+ context=retrieved_chunks, # str or list[str]
131
+ target_tokens=500,
132
+ )
133
+
134
+ result.context # compressed text for your LLM
135
+ result.report.tokens_saved # int
136
+ result.report.faithful # bool — did all critical values survive?
137
+ result.report.warnings # list[str] — what got dropped and why
138
+ ```
139
+
140
+ ### Guarding a production call
141
+
142
+ ```python
143
+ result = opt.optimize(query, chunks, target_tokens=500)
144
+
145
+ if not result.report.faithful:
146
+ # budget too tight to hold every critical value — your call:
147
+ logger.warning("Quasar: %s", result.report.warnings)
148
+ result = opt.optimize(query, chunks, target_tokens=1000) # give it room
149
+
150
+ answer = llm(query, result.context)
151
+ ```
152
+
153
+ ### Configuration
154
+
155
+ ```python
156
+ from quasar import ContextOptimizer, OptimizerConfig
157
+
158
+ opt = ContextOptimizer(OptimizerConfig(
159
+ cost_per_1k_tokens=0.003, # your model's price, for the savings report
160
+ preserve_critical=True, # the faithfulness behavior (default on)
161
+ min_relevance=0.05, # drop sentences below this query relevance
162
+ model_name="all-MiniLM-L6-v2",
163
+ ))
164
+ ```
165
+
166
+ ### Custom critical patterns
167
+
168
+ Your domain has its own critical formats. Add them:
169
+
170
+ ```python
171
+ from quasar import core
172
+ core._CRITICAL_PATTERNS.append((r"\bCASE-\d{6}\b", "case_id"))
173
+ ```
174
+
175
+ ---
176
+
177
+ ## Performance
178
+
179
+ - **~10–100 ms per call** after warm-up (embedding + greedy selection).
180
+ - **First call loads the model** (~30–60s, one time). Warm it at startup:
181
+ ```python
182
+ opt = ContextOptimizer()
183
+ opt.optimize("warmup", "warmup text.", target_tokens=50)
184
+ ```
185
+ - **8–13× faster than LLMLingua**, which runs a full neural model to compress.
186
+
187
+ ---
188
+
189
+ ## What Quasar is not
190
+
191
+ - **Not the best compressor by ratio.** Token-deletion methods compress harder.
192
+ They also rewrite your text.
193
+ - **Not an accuracy leader on pure QA.** A top-k filter matches or beats it there.
194
+ - **Not a guarantee against physics.** If your critical content is larger than
195
+ your token budget, it cannot all fit. Quasar preserves what it can *and tells
196
+ you what it couldn't* — that's the honest contract.
197
+
198
+ ---
199
+
200
+ ## License
201
+
202
+ MIT.
@@ -0,0 +1,7 @@
1
+ quasar/__init__.py,sha256=FtFWONz3rEP66N7ZCHH9CB_EfxcQJoKu12wtMEAFseQ,915
2
+ quasar/core.py,sha256=Baskdnm6Ao0hvi_V5XHzVcb4XclsqJmWeTCjw1_4QNk,13773
3
+ quasar_context-0.1.0.dist-info/licenses/LICENSE,sha256=68YHKNjCH0sHIzpFps5Bfu7I2d2UxeVqheRGPaSh2JM,1071
4
+ quasar_context-0.1.0.dist-info/METADATA,sha256=FPj597B5MiK3oL2DRIXyxaKg8u9kWDbz7qc9qOuamOY,6619
5
+ quasar_context-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
6
+ quasar_context-0.1.0.dist-info/top_level.txt,sha256=t0u-75WvIcbDt07RwsuAgwU8g4wL6SgUZWYLcHzmyio,7
7
+ quasar_context-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sebastian Sabo
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.
@@ -0,0 +1 @@
1
+ quasar