ragit 0.6__py3-none-any.whl → 0.7.1__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.
@@ -0,0 +1,105 @@
1
+ #
2
+ # Copyright RODMENA LIMITED 2025
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ #
5
+ """
6
+ Ragit utilities module.
7
+ """
8
+
9
+ from collections import deque
10
+ from collections.abc import Hashable, Sequence
11
+ from datetime import datetime
12
+ from math import floor
13
+ from typing import Any
14
+
15
+ import pandas as pd
16
+
17
+
18
+ def get_hashable_repr(dct: dict[str, object]) -> tuple[tuple[str, object, float, int | None], ...]:
19
+ """
20
+ Returns a hashable representation of the provided dictionary.
21
+ """
22
+ queue: deque[tuple[str, object, float, int | None]] = deque((k, v, 0.0, None) for k, v in dct.items())
23
+ dict_unpacked: list[tuple[str, object, float, int | None]] = []
24
+ while queue:
25
+ key, val, lvl, p_ref = queue.pop()
26
+ if hasattr(val, "items"): # we have a nested dict
27
+ dict_unpacked.append((key, "+", lvl, p_ref))
28
+ if hash(key) != p_ref:
29
+ lvl += 1
30
+ queue.extendleft((k, v, lvl, hash(key)) for k, v in val.items())
31
+ elif isinstance(val, Hashable):
32
+ dict_unpacked.append((key, val, lvl, p_ref))
33
+ elif isinstance(val, Sequence):
34
+ dict_unpacked.append((key, "+", lvl, p_ref))
35
+ queue.extendleft((key, vv, floor(lvl) + ind * 0.01, hash(key)) for ind, vv in enumerate(val, 1))
36
+ else:
37
+ raise ValueError(f"Unsupported type in dict: {type(val)}")
38
+
39
+ return tuple(sorted(dict_unpacked, key=lambda it: (it[2], it[0])))
40
+
41
+
42
+ def remove_duplicates(items: list[dict[str, Any]]) -> list[dict[str, Any]]:
43
+ """
44
+ Deduplicates list of provided dictionary items.
45
+
46
+ Parameters
47
+ ----------
48
+ items : list[dict]
49
+ List of items to deduplicate.
50
+
51
+ Returns
52
+ -------
53
+ list[dict]
54
+ A deduplicated list of input items.
55
+ """
56
+ duplicate_tracker = set()
57
+ deduplicated_items = []
58
+ for ind, elem in enumerate(map(get_hashable_repr, items)):
59
+ if elem not in duplicate_tracker:
60
+ duplicate_tracker.add(elem)
61
+ deduplicated_items.append(items[ind])
62
+ return deduplicated_items
63
+
64
+
65
+ def handle_missing_values_in_combinations(df: pd.DataFrame) -> pd.DataFrame:
66
+ """
67
+ Handle missing values in experiment data combinations.
68
+
69
+ Parameters
70
+ ----------
71
+ df : pd.DataFrame
72
+ Experiment data with combinations being explored.
73
+
74
+ Returns
75
+ -------
76
+ pd.DataFrame
77
+ Data with NaN values properly replaced.
78
+ """
79
+ if "chunk_overlap" in df.columns:
80
+ df["chunk_overlap"] = df["chunk_overlap"].map(lambda el: 0 if pd.isna(el) else el)
81
+
82
+ return df
83
+
84
+
85
+ def datetime_str_to_epoch_time(timestamp: str | int) -> str | int:
86
+ """
87
+ Convert datetime string to epoch time.
88
+
89
+ Parameters
90
+ ----------
91
+ timestamp : str | int
92
+ Either a datetime string or a unix timestamp.
93
+
94
+ Returns
95
+ -------
96
+ int
97
+ Unix timestamp or -1 if parsing fails.
98
+ """
99
+ if not isinstance(timestamp, str):
100
+ return timestamp
101
+ try:
102
+ iso_parseable = datetime.fromisoformat(timestamp)
103
+ except ValueError:
104
+ return -1
105
+ return int(iso_parseable.timestamp())
ragit/version.py ADDED
@@ -0,0 +1,5 @@
1
+ #
2
+ # Copyright RODMENA LIMITED 2025
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ #
5
+ __version__ = "0.7.1"
@@ -0,0 +1,476 @@
1
+ Metadata-Version: 2.4
2
+ Name: ragit
3
+ Version: 0.7.1
4
+ Summary: Automatic RAG Pattern Optimization Engine
5
+ Author: RODMENA LIMITED
6
+ Maintainer-email: RODMENA LIMITED <info@rodmena.co.uk>
7
+ License-Expression: Apache-2.0
8
+ Project-URL: Homepage, https://github.com/rodmena-limited/ragit
9
+ Project-URL: Repository, https://github.com/rodmena-limited/ragit
10
+ Project-URL: Issues, https://github.com/rodmena-limited/ragit/issues
11
+ Keywords: AI,RAG,LLM,GenAI,Optimization,Ollama
12
+ Classifier: Development Status :: 2 - Pre-Alpha
13
+ Classifier: Natural Language :: English
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Operating System :: MacOS :: MacOS X
18
+ Classifier: Operating System :: POSIX :: Linux
19
+ Requires-Python: <3.14,>=3.12
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: requests>=2.31.0
23
+ Requires-Dist: numpy>=1.26.0
24
+ Requires-Dist: pandas>=2.2.0
25
+ Requires-Dist: pydantic>=2.0.0
26
+ Requires-Dist: python-dotenv>=1.0.0
27
+ Requires-Dist: scikit-learn>=1.5.0
28
+ Requires-Dist: tqdm>=4.66.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: ragit[test]; extra == "dev"
31
+ Requires-Dist: pytest; extra == "dev"
32
+ Requires-Dist: pytest-cov; extra == "dev"
33
+ Requires-Dist: issuedb[web]; extra == "dev"
34
+ Requires-Dist: ruff; extra == "dev"
35
+ Requires-Dist: mypy; extra == "dev"
36
+ Provides-Extra: test
37
+ Requires-Dist: pytest; extra == "test"
38
+ Requires-Dist: pytest-cov; extra == "test"
39
+ Requires-Dist: pytest-mock; extra == "test"
40
+ Dynamic: license-file
41
+
42
+ # ragit
43
+
44
+ A Python toolkit for building Retrieval-Augmented Generation (RAG) applications. Ragit provides document loading, chunking, vector search, and LLM integration out of the box, allowing you to build document Q&A systems and code generators with minimal boilerplate.
45
+
46
+ ## Table of Contents
47
+
48
+ 1. [Installation](#installation)
49
+ 2. [Configuration](#configuration)
50
+ 3. [Tutorial: Using Ragit](#tutorial-using-ragit)
51
+ - [Loading Documents](#loading-documents)
52
+ - [The RAGAssistant Class](#the-ragassistant-class)
53
+ - [Asking Questions](#asking-questions)
54
+ - [Generating Code](#generating-code)
55
+ - [Custom Retrieval](#custom-retrieval)
56
+ 4. [Tutorial: Platform Integration](#tutorial-platform-integration)
57
+ - [Flask Integration](#flask-integration)
58
+ - [FastAPI Integration](#fastapi-integration)
59
+ - [Command-Line Tools](#command-line-tools)
60
+ - [Batch Processing](#batch-processing)
61
+ 5. [Advanced: Hyperparameter Optimization](#advanced-hyperparameter-optimization)
62
+ 6. [API Reference](#api-reference)
63
+ 7. [License](#license)
64
+
65
+ ## Installation
66
+
67
+ ```bash
68
+ pip install ragit
69
+ ```
70
+
71
+ Ragit requires an Ollama-compatible API for embeddings and LLM inference. You can use:
72
+ - A local Ollama instance (https://ollama.ai)
73
+ - A cloud-hosted Ollama API
74
+ - Any OpenAI-compatible API endpoint
75
+
76
+ ## Configuration
77
+
78
+ Ragit reads configuration from environment variables. Create a `.env` file in your project root:
79
+
80
+ ```bash
81
+ # LLM API (cloud or local)
82
+ OLLAMA_BASE_URL=https://your-ollama-api.com
83
+ OLLAMA_API_KEY=your-api-key
84
+
85
+ # Embedding API (can be different from LLM)
86
+ OLLAMA_EMBEDDING_URL=http://localhost:11434
87
+
88
+ # Default models
89
+ RAGIT_DEFAULT_LLM_MODEL=llama3.1:8b
90
+ RAGIT_DEFAULT_EMBEDDING_MODEL=mxbai-embed-large
91
+ ```
92
+
93
+ A common setup is to use a cloud API for LLM inference (faster, more capable models) while running embeddings locally (lower latency, no API costs for indexing).
94
+
95
+ ## Tutorial: Using Ragit
96
+
97
+ This section covers the core functionality of ragit: loading documents, creating a RAG assistant, and querying your knowledge base.
98
+
99
+ ### Loading Documents
100
+
101
+ Ragit provides several functions for loading and chunking documents.
102
+
103
+ **Loading a single file:**
104
+
105
+ ```python
106
+ from ragit import load_text
107
+
108
+ doc = load_text("docs/api-reference.md")
109
+ print(doc.id) # "api-reference"
110
+ print(doc.content) # Full file contents
111
+ ```
112
+
113
+ **Loading a directory:**
114
+
115
+ ```python
116
+ from ragit import load_directory
117
+
118
+ # Load all markdown files
119
+ docs = load_directory("docs/", "*.md")
120
+
121
+ # Load recursively
122
+ docs = load_directory("docs/", "**/*.md", recursive=True)
123
+
124
+ # Load multiple file types
125
+ txt_docs = load_directory("docs/", "*.txt")
126
+ rst_docs = load_directory("docs/", "*.rst")
127
+ all_docs = txt_docs + rst_docs
128
+ ```
129
+
130
+ **Custom chunking:**
131
+
132
+ For fine-grained control over how documents are split:
133
+
134
+ ```python
135
+ from ragit import chunk_text, chunk_by_separator, chunk_rst_sections
136
+
137
+ # Fixed-size chunks with overlap
138
+ chunks = chunk_text(
139
+ text,
140
+ chunk_size=512, # Characters per chunk
141
+ chunk_overlap=50, # Overlap between chunks
142
+ doc_id="my-doc"
143
+ )
144
+
145
+ # Split by paragraph
146
+ chunks = chunk_by_separator(text, separator="\n\n")
147
+
148
+ # Split RST documents by section headers
149
+ chunks = chunk_rst_sections(rst_content, doc_id="tutorial")
150
+ ```
151
+
152
+ ### The RAGAssistant Class
153
+
154
+ The `RAGAssistant` class is the main interface for RAG operations. It handles document indexing, retrieval, and generation in a single object.
155
+
156
+ ```python
157
+ from ragit import RAGAssistant
158
+
159
+ # Create from a directory
160
+ assistant = RAGAssistant("docs/")
161
+
162
+ # Create from a single file
163
+ assistant = RAGAssistant("docs/tutorial.rst")
164
+
165
+ # Create from Document objects
166
+ from ragit import Document
167
+
168
+ docs = [
169
+ Document(id="intro", content="Introduction to the API..."),
170
+ Document(id="auth", content="Authentication uses JWT tokens..."),
171
+ Document(id="endpoints", content="Available endpoints: /users, /items..."),
172
+ ]
173
+ assistant = RAGAssistant(docs)
174
+ ```
175
+
176
+ **Configuration options:**
177
+
178
+ ```python
179
+ assistant = RAGAssistant(
180
+ "docs/",
181
+ embedding_model="mxbai-embed-large", # Model for embeddings
182
+ llm_model="llama3.1:70b", # Model for generation
183
+ chunk_size=512, # Characters per chunk
184
+ chunk_overlap=50, # Overlap between chunks
185
+ )
186
+ ```
187
+
188
+ ### Asking Questions
189
+
190
+ The `ask()` method retrieves relevant context and generates an answer:
191
+
192
+ ```python
193
+ assistant = RAGAssistant("docs/")
194
+
195
+ answer = assistant.ask("How do I authenticate API requests?")
196
+ print(answer)
197
+ ```
198
+
199
+ **Customizing the query:**
200
+
201
+ ```python
202
+ answer = assistant.ask(
203
+ "How do I authenticate API requests?",
204
+ top_k=5, # Number of chunks to retrieve
205
+ temperature=0.3, # Lower = more focused answers
206
+ system_prompt="You are a technical documentation assistant. "
207
+ "Answer concisely and include code examples."
208
+ )
209
+ ```
210
+
211
+ ### Generating Code
212
+
213
+ The `generate_code()` method is optimized for producing clean, runnable code:
214
+
215
+ ```python
216
+ assistant = RAGAssistant("framework-docs/")
217
+
218
+ code = assistant.generate_code(
219
+ "Create a REST API endpoint for user registration",
220
+ language="python"
221
+ )
222
+ print(code)
223
+ ```
224
+
225
+ The output is clean code without markdown formatting. The assistant uses your documentation as context to generate framework-specific, idiomatic code.
226
+
227
+ ### Custom Retrieval
228
+
229
+ For advanced use cases, you can access the retrieval and generation steps separately:
230
+
231
+ ```python
232
+ assistant = RAGAssistant("docs/")
233
+
234
+ # Step 1: Retrieve relevant chunks
235
+ results = assistant.retrieve("authentication", top_k=5)
236
+ for chunk, score in results:
237
+ print(f"Score: {score:.3f}")
238
+ print(f"Content: {chunk.content[:200]}...")
239
+ print()
240
+
241
+ # Step 2: Get formatted context string
242
+ context = assistant.get_context("authentication", top_k=3)
243
+
244
+ # Step 3: Generate with custom prompt
245
+ prompt = f"""Based on this documentation:
246
+
247
+ {context}
248
+
249
+ Write a Python function that validates a JWT token."""
250
+
251
+ response = assistant.generate(
252
+ prompt,
253
+ system_prompt="You are an expert Python developer.",
254
+ temperature=0.2
255
+ )
256
+ ```
257
+
258
+ ## Tutorial: Platform Integration
259
+
260
+ This section shows how to integrate ragit into web applications and other platforms.
261
+
262
+ ### Flask Integration
263
+
264
+ ```python
265
+ from flask import Flask, request, jsonify
266
+ from ragit import RAGAssistant
267
+
268
+ app = Flask(__name__)
269
+
270
+ # Initialize once at startup
271
+ assistant = RAGAssistant("docs/")
272
+
273
+ @app.route("/ask", methods=["POST"])
274
+ def ask():
275
+ data = request.get_json()
276
+ question = data.get("question", "")
277
+
278
+ if not question:
279
+ return jsonify({"error": "question is required"}), 400
280
+
281
+ answer = assistant.ask(question, top_k=3)
282
+ return jsonify({"answer": answer})
283
+
284
+ @app.route("/search", methods=["GET"])
285
+ def search():
286
+ query = request.args.get("q", "")
287
+ top_k = int(request.args.get("top_k", 5))
288
+
289
+ results = assistant.retrieve(query, top_k=top_k)
290
+ return jsonify({
291
+ "results": [
292
+ {"content": chunk.content, "score": score}
293
+ for chunk, score in results
294
+ ]
295
+ })
296
+
297
+ if __name__ == "__main__":
298
+ app.run(debug=True)
299
+ ```
300
+
301
+ ### FastAPI Integration
302
+
303
+ ```python
304
+ from fastapi import FastAPI, HTTPException
305
+ from pydantic import BaseModel
306
+ from ragit import RAGAssistant
307
+
308
+ app = FastAPI()
309
+
310
+ # Initialize once at startup
311
+ assistant = RAGAssistant("docs/")
312
+
313
+ class Question(BaseModel):
314
+ question: str
315
+ top_k: int = 3
316
+ temperature: float = 0.7
317
+
318
+ class Answer(BaseModel):
319
+ answer: str
320
+
321
+ @app.post("/ask", response_model=Answer)
322
+ async def ask(q: Question):
323
+ if not q.question.strip():
324
+ raise HTTPException(status_code=400, detail="question is required")
325
+
326
+ answer = assistant.ask(
327
+ q.question,
328
+ top_k=q.top_k,
329
+ temperature=q.temperature
330
+ )
331
+ return Answer(answer=answer)
332
+
333
+ @app.get("/search")
334
+ async def search(q: str, top_k: int = 5):
335
+ results = assistant.retrieve(q, top_k=top_k)
336
+ return {
337
+ "results": [
338
+ {"content": chunk.content, "score": score}
339
+ for chunk, score in results
340
+ ]
341
+ }
342
+ ```
343
+
344
+ ### Command-Line Tools
345
+
346
+ Build CLI tools using argparse or click:
347
+
348
+ ```python
349
+ #!/usr/bin/env python3
350
+ import argparse
351
+ from ragit import RAGAssistant
352
+
353
+ def main():
354
+ parser = argparse.ArgumentParser(description="Query documentation")
355
+ parser.add_argument("question", help="Question to ask")
356
+ parser.add_argument("--docs", default="docs/", help="Documentation path")
357
+ parser.add_argument("--top-k", type=int, default=3, help="Context chunks")
358
+ args = parser.parse_args()
359
+
360
+ assistant = RAGAssistant(args.docs)
361
+ answer = assistant.ask(args.question, top_k=args.top_k)
362
+ print(answer)
363
+
364
+ if __name__ == "__main__":
365
+ main()
366
+ ```
367
+
368
+ Usage:
369
+
370
+ ```bash
371
+ python ask.py "How do I configure logging?"
372
+ python ask.py "What are the API rate limits?" --docs api-docs/ --top-k 5
373
+ ```
374
+
375
+ ### Batch Processing
376
+
377
+ Process multiple questions or generate reports:
378
+
379
+ ```python
380
+ from ragit import RAGAssistant
381
+
382
+ assistant = RAGAssistant("docs/")
383
+
384
+ questions = [
385
+ "What authentication methods are supported?",
386
+ "How do I handle errors?",
387
+ "What are the rate limits?",
388
+ ]
389
+
390
+ # Process questions
391
+ results = {}
392
+ for question in questions:
393
+ results[question] = assistant.ask(question)
394
+
395
+ # Generate a report
396
+ with open("qa-report.md", "w") as f:
397
+ f.write("# Documentation Q&A Report\n\n")
398
+ for question, answer in results.items():
399
+ f.write(f"## {question}\n\n")
400
+ f.write(f"{answer}\n\n")
401
+ ```
402
+
403
+ ## Advanced: Hyperparameter Optimization
404
+
405
+ Ragit includes tools to find the optimal RAG configuration for your specific documents and use case.
406
+
407
+ ```python
408
+ from ragit import RagitExperiment, Document, BenchmarkQuestion
409
+
410
+ # Your documents
411
+ documents = [
412
+ Document(id="auth", content="Authentication uses Bearer tokens..."),
413
+ Document(id="api", content="The API supports GET, POST, PUT, DELETE..."),
414
+ ]
415
+
416
+ # Benchmark questions with expected answers
417
+ benchmark = [
418
+ BenchmarkQuestion(
419
+ question="What authentication method does the API use?",
420
+ ground_truth="The API uses Bearer token authentication."
421
+ ),
422
+ BenchmarkQuestion(
423
+ question="What HTTP methods are supported?",
424
+ ground_truth="GET, POST, PUT, and DELETE methods are supported."
425
+ ),
426
+ ]
427
+
428
+ # Run optimization
429
+ experiment = RagitExperiment(documents, benchmark)
430
+ results = experiment.run(max_configs=20)
431
+
432
+ # Get the best configuration
433
+ best = results[0]
434
+ print(f"Best config: chunk_size={best.config.chunk_size}, "
435
+ f"chunk_overlap={best.config.chunk_overlap}, "
436
+ f"top_k={best.config.top_k}")
437
+ print(f"Score: {best.score:.3f}")
438
+ ```
439
+
440
+ The experiment tests different combinations of chunk sizes, overlaps, and retrieval parameters to find what works best for your content.
441
+
442
+ ## API Reference
443
+
444
+ ### Document Loading
445
+
446
+ | Function | Description |
447
+ |----------|-------------|
448
+ | `load_text(path)` | Load a single text file as a Document |
449
+ | `load_directory(path, pattern, recursive=False)` | Load files matching a glob pattern |
450
+ | `chunk_text(text, chunk_size, chunk_overlap, doc_id)` | Split text into overlapping chunks |
451
+ | `chunk_document(doc, chunk_size, chunk_overlap)` | Split a Document into chunks |
452
+ | `chunk_by_separator(text, separator, doc_id)` | Split text by a delimiter |
453
+ | `chunk_rst_sections(text, doc_id)` | Split RST by section headers |
454
+
455
+ ### RAGAssistant
456
+
457
+ | Method | Description |
458
+ |--------|-------------|
459
+ | `retrieve(query, top_k=3)` | Return list of (Chunk, score) tuples |
460
+ | `get_context(query, top_k=3)` | Return formatted context string |
461
+ | `generate(prompt, system_prompt, temperature)` | Generate text without retrieval |
462
+ | `ask(question, system_prompt, top_k, temperature)` | Retrieve context and generate answer |
463
+ | `generate_code(request, language, top_k, temperature)` | Generate clean code |
464
+
465
+ ### Properties
466
+
467
+ | Property | Description |
468
+ |----------|-------------|
469
+ | `assistant.num_documents` | Number of loaded documents |
470
+ | `assistant.num_chunks` | Number of indexed chunks |
471
+ | `assistant.embedding_model` | Current embedding model |
472
+ | `assistant.llm_model` | Current LLM model |
473
+
474
+ ## License
475
+
476
+ Apache-2.0 - RODMENA LIMITED
@@ -0,0 +1,18 @@
1
+ ragit/__init__.py,sha256=PjQogIWMlydZFWVECqhmxw-X9i7lEXdUTe2XlT6qYUQ,2213
2
+ ragit/assistant.py,sha256=lXjZRUr_WsYLP3XLOktabgfPVyKOZPdREzyL7cSRufk,11251
3
+ ragit/config.py,sha256=uKLchJQHjH8MImZ2OJahDjSzyasFqgrFb9Z4aHxJ7og,1495
4
+ ragit/loaders.py,sha256=keusuPzXPBiLDVj4hKfPCcge-rm-cnzNRk50fGXvTJs,5571
5
+ ragit/version.py,sha256=eQW9_26GRu30Dsudn-wxi1RA5_L4JTlTW38oZqGpCjo,97
6
+ ragit/core/__init__.py,sha256=j53PFfoSMXwSbK1rRHpMbo8mX2i4R1LJ5kvTxBd7-0w,100
7
+ ragit/core/experiment/__init__.py,sha256=4vAPOOYlY5Dcr2gOolyhBSPGIUxZKwEkgQffxS9BodA,452
8
+ ragit/core/experiment/experiment.py,sha256=Qh1NJkY9LbKaidRfiI8GOwBZqopjK-MSVBuD_JEgO-k,16582
9
+ ragit/core/experiment/results.py,sha256=KHpN3YSLJ83_JUfIMccRPS-q7LEt0S9p8ehDRawk_4k,3487
10
+ ragit/providers/__init__.py,sha256=iliJt74Lt3mFUlKGfSFW-D0cMonUygY6sRZ6lLjeU7M,435
11
+ ragit/providers/base.py,sha256=MJ8mVeXuGWhkX2XGTbkWIY3cVoTOPr4h5XBXw8rAX2Q,3434
12
+ ragit/providers/ollama.py,sha256=Wm2P72mONaFrLXCAauhQqHggtZISFadE06H1s-XKv5E,9279
13
+ ragit/utils/__init__.py,sha256=-UsE5oJSnmEnBDswl-ph0A09Iu8yKNbPhd1-_7Lcb8Y,3051
14
+ ragit-0.7.1.dist-info/licenses/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
15
+ ragit-0.7.1.dist-info/METADATA,sha256=tDEnxgm8bewjWKPlXBHbk7ZR28Do_0MIathdQvHxLNk,13488
16
+ ragit-0.7.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
+ ragit-0.7.1.dist-info/top_level.txt,sha256=pkPbG7yrw61wt9_y_xcLE2vq2a55fzockASD0yq0g4s,6
18
+ ragit-0.7.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5