haiku.rag 0.3.3__py3-none-any.whl → 0.3.4__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.

Potentially problematic release.


This version of haiku.rag might be problematic. Click here for more details.

haiku/rag/app.py CHANGED
@@ -74,7 +74,7 @@ class HaikuRAGApp:
74
74
  self.console.print(f"[red]Error: {e}[/red]")
75
75
 
76
76
  async def rebuild(self):
77
- async with HaikuRAG(db_path=self.db_path) as client:
77
+ async with HaikuRAG(db_path=self.db_path, skip_validation=True) as client:
78
78
  try:
79
79
  documents = await client.list_documents()
80
80
  total_docs = len(documents)
haiku/rag/cli.py CHANGED
@@ -5,7 +5,7 @@ import typer
5
5
  from rich.console import Console
6
6
 
7
7
  from haiku.rag.app import HaikuRAGApp
8
- from haiku.rag.utils import get_default_data_dir
8
+ from haiku.rag.utils import get_default_data_dir, is_up_to_date
9
9
 
10
10
  cli = typer.Typer(
11
11
  context_settings={"help_option_names": ["-h", "--help"]}, no_args_is_help=True
@@ -15,6 +15,23 @@ console = Console()
15
15
  event_loop = asyncio.get_event_loop()
16
16
 
17
17
 
18
+ async def check_version():
19
+ """Check if haiku.rag is up to date and show warning if not."""
20
+ up_to_date, current_version, latest_version = await is_up_to_date()
21
+ if not up_to_date:
22
+ console.print(
23
+ f"[yellow]Warning: haiku.rag is outdated. Current: {current_version}, Latest: {latest_version}[/yellow]"
24
+ )
25
+ console.print("[yellow]Please update.[/yellow]")
26
+
27
+
28
+ @cli.callback()
29
+ def main():
30
+ """haiku.rag CLI - SQLite-based RAG system"""
31
+ # Run version check before any command
32
+ event_loop.run_until_complete(check_version())
33
+
34
+
18
35
  @cli.command("list", help="List all stored documents")
19
36
  def list_documents(
20
37
  db: Path = typer.Option(
haiku/rag/client.py CHANGED
@@ -24,12 +24,13 @@ class HaikuRAG:
24
24
  self,
25
25
  db_path: Path | Literal[":memory:"] = Config.DEFAULT_DATA_DIR
26
26
  / "haiku.rag.sqlite",
27
+ skip_validation: bool = False,
27
28
  ):
28
29
  """Initialize the RAG client with a database path."""
29
30
  if isinstance(db_path, Path):
30
31
  if not db_path.parent.exists():
31
32
  Path.mkdir(db_path.parent, parents=True)
32
- self.store = Store(db_path)
33
+ self.store = Store(db_path, skip_validation=skip_validation)
33
34
  self.document_repository = DocumentRepository(self.store)
34
35
  self.chunk_repository = ChunkRepository(self.store)
35
36
 
@@ -165,29 +166,26 @@ class HaikuRAG:
165
166
 
166
167
  # Create a temporary file with the appropriate extension
167
168
  with tempfile.NamedTemporaryFile(
168
- mode="wb", suffix=file_extension, delete=False
169
+ mode="wb", suffix=file_extension
169
170
  ) as temp_file:
170
171
  temp_file.write(response.content)
172
+ temp_file.flush() # Ensure content is written to disk
171
173
  temp_path = Path(temp_file.name)
172
174
 
173
- try:
174
175
  # Parse the content using FileReader
175
176
  content = FileReader.parse_file(temp_path)
176
177
 
177
- # Merge metadata with contentType and md5
178
- metadata.update({"contentType": content_type, "md5": md5_hash})
179
-
180
- if existing_doc:
181
- existing_doc.content = content
182
- existing_doc.metadata = metadata
183
- return await self.update_document(existing_doc)
184
- else:
185
- return await self.create_document(
186
- content=content, uri=url, metadata=metadata
187
- )
188
- finally:
189
- # Clean up temporary file
190
- temp_path.unlink(missing_ok=True)
178
+ # Merge metadata with contentType and md5
179
+ metadata.update({"contentType": content_type, "md5": md5_hash})
180
+
181
+ if existing_doc:
182
+ existing_doc.content = content
183
+ existing_doc.metadata = metadata
184
+ return await self.update_document(existing_doc)
185
+ else:
186
+ return await self.create_document(
187
+ content=content, uri=url, metadata=metadata
188
+ )
191
189
 
192
190
  def _get_extension_from_content_type_or_url(
193
191
  self, url: str, content_type: str
@@ -277,12 +275,16 @@ class HaikuRAG:
277
275
  Yields:
278
276
  int: The ID of the document currently being processed
279
277
  """
280
- documents = await self.list_documents()
278
+ await self.chunk_repository.delete_all()
279
+ self.store.recreate_embeddings_table()
281
280
 
282
- if not documents:
283
- return
281
+ # Update settings to current config
282
+ from haiku.rag.store.repositories.settings import SettingsRepository
284
283
 
285
- await self.chunk_repository.delete_all()
284
+ settings_repo = SettingsRepository(self.store)
285
+ settings_repo.save()
286
+
287
+ documents = await self.list_documents()
286
288
 
287
289
  for doc in documents:
288
290
  if doc.id is not None:
haiku/rag/store/engine.py CHANGED
@@ -1,23 +1,65 @@
1
1
  import sqlite3
2
2
  import struct
3
+ from importlib import metadata
3
4
  from pathlib import Path
4
5
  from typing import Literal
5
6
 
6
7
  import sqlite_vec
8
+ from packaging.version import parse
9
+ from rich.console import Console
7
10
 
11
+ from haiku.rag.config import Config
8
12
  from haiku.rag.embeddings import get_embedder
13
+ from haiku.rag.store.upgrades import upgrades
14
+ from haiku.rag.utils import int_to_semantic_version, semantic_version_to_int
9
15
 
10
16
 
11
17
  class Store:
12
- def __init__(self, db_path: Path | Literal[":memory:"]):
18
+ def __init__(
19
+ self, db_path: Path | Literal[":memory:"], skip_validation: bool = False
20
+ ):
13
21
  self.db_path: Path | Literal[":memory:"] = db_path
14
- self._connection = self.create_db()
22
+ self.create_or_update_db()
15
23
 
16
- def create_db(self) -> sqlite3.Connection:
24
+ # Validate config compatibility after connection is established
25
+ if not skip_validation:
26
+ from haiku.rag.store.repositories.settings import SettingsRepository
27
+
28
+ settings_repo = SettingsRepository(self)
29
+ settings_repo.validate_config_compatibility()
30
+ current_version = metadata.version("haiku.rag")
31
+ self.set_user_version(current_version)
32
+
33
+ def create_or_update_db(self):
17
34
  """Create the database and tables with sqlite-vec support for embeddings."""
35
+ current_version = metadata.version("haiku.rag")
36
+
18
37
  db = sqlite3.connect(self.db_path)
19
38
  db.enable_load_extension(True)
20
39
  sqlite_vec.load(db)
40
+ self._connection = db
41
+ existing_tables = [
42
+ row[0]
43
+ for row in db.execute(
44
+ "SELECT name FROM sqlite_master WHERE type='table';"
45
+ ).fetchall()
46
+ ]
47
+
48
+ # If we have a db already, perform upgrades and return
49
+ if self.db_path != ":memory:" and "documents" in existing_tables:
50
+ # Upgrade database
51
+ console = Console()
52
+ db_version = self.get_user_version()
53
+ for version, steps in upgrades:
54
+ if parse(current_version) >= parse(version) and parse(version) > parse(
55
+ db_version
56
+ ):
57
+ for step in steps:
58
+ step(db)
59
+ console.print(
60
+ f"[green][b]DB Upgrade: [/b]{step.__doc__}[/green]"
61
+ )
62
+ return
21
63
 
22
64
  # Create documents table
23
65
  db.execute("""
@@ -30,7 +72,6 @@ class Store:
30
72
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
31
73
  )
32
74
  """)
33
-
34
75
  # Create chunks table
35
76
  db.execute("""
36
77
  CREATE TABLE IF NOT EXISTS chunks (
@@ -41,7 +82,6 @@ class Store:
41
82
  FOREIGN KEY (document_id) REFERENCES documents (id) ON DELETE CASCADE
42
83
  )
43
84
  """)
44
-
45
85
  # Create vector table for chunk embeddings
46
86
  embedder = get_embedder()
47
87
  db.execute(f"""
@@ -50,7 +90,6 @@ class Store:
50
90
  embedding FLOAT[{embedder._vector_dim}]
51
91
  )
52
92
  """)
53
-
54
93
  # Create FTS5 table for full-text search
55
94
  db.execute("""
56
95
  CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(
@@ -59,14 +98,61 @@ class Store:
59
98
  content_rowid='id'
60
99
  )
61
100
  """)
62
-
101
+ # Create settings table for storing current configuration
102
+ db.execute("""
103
+ CREATE TABLE IF NOT EXISTS settings (
104
+ id INTEGER PRIMARY KEY DEFAULT 1,
105
+ settings TEXT NOT NULL DEFAULT '{}'
106
+ )
107
+ """)
108
+ # Save current settings to the new database
109
+ settings_json = Config.model_dump_json()
110
+ db.execute(
111
+ "INSERT OR IGNORE INTO settings (id, settings) VALUES (1, ?)",
112
+ (settings_json,),
113
+ )
63
114
  # Create indexes for better performance
64
115
  db.execute(
65
116
  "CREATE INDEX IF NOT EXISTS idx_chunks_document_id ON chunks(document_id)"
66
117
  )
67
-
68
118
  db.commit()
69
- return db
119
+
120
+ def get_user_version(self) -> str:
121
+ """Returns the SQLite user version"""
122
+ if self._connection is None:
123
+ raise ValueError("Store connection is not available")
124
+
125
+ cursor = self._connection.execute("PRAGMA user_version;")
126
+ version = cursor.fetchone()
127
+ return int_to_semantic_version(version[0])
128
+
129
+ def set_user_version(self, version: str) -> None:
130
+ """Updates the SQLite user version"""
131
+ if self._connection is None:
132
+ raise ValueError("Store connection is not available")
133
+
134
+ self._connection.execute(
135
+ f"PRAGMA user_version = {semantic_version_to_int(version)};"
136
+ )
137
+
138
+ def recreate_embeddings_table(self) -> None:
139
+ """Recreate the embeddings table with current vector dimensions."""
140
+ if self._connection is None:
141
+ raise ValueError("Store connection is not available")
142
+
143
+ # Drop existing embeddings table
144
+ self._connection.execute("DROP TABLE IF EXISTS chunk_embeddings")
145
+
146
+ # Recreate with current dimensions
147
+ embedder = get_embedder()
148
+ self._connection.execute(f"""
149
+ CREATE VIRTUAL TABLE chunk_embeddings USING vec0(
150
+ chunk_id INTEGER PRIMARY KEY,
151
+ embedding FLOAT[{embedder._vector_dim}]
152
+ )
153
+ """)
154
+
155
+ self._connection.commit()
70
156
 
71
157
  @staticmethod
72
158
  def serialize_embedding(embedding: list[float]) -> bytes:
@@ -0,0 +1,78 @@
1
+ import json
2
+ from typing import Any
3
+
4
+ from haiku.rag.store.engine import Store
5
+
6
+
7
+ class ConfigMismatchError(Exception):
8
+ """Raised when current config doesn't match stored settings."""
9
+
10
+ pass
11
+
12
+
13
+ class SettingsRepository:
14
+ def __init__(self, store: Store):
15
+ self.store = store
16
+
17
+ def get(self) -> dict[str, Any]:
18
+ """Get all settings from the database."""
19
+ if self.store._connection is None:
20
+ raise ValueError("Store connection is not available")
21
+
22
+ cursor = self.store._connection.execute("SELECT settings FROM settings LIMIT 1")
23
+ row = cursor.fetchone()
24
+ if row:
25
+ return json.loads(row[0])
26
+ return {}
27
+
28
+ def save(self) -> None:
29
+ """Sync settings from the current AppConfig to database."""
30
+ if self.store._connection is None:
31
+ raise ValueError("Store connection is not available")
32
+
33
+ from haiku.rag.config import Config
34
+
35
+ settings_json = Config.model_dump_json()
36
+
37
+ self.store._connection.execute(
38
+ "INSERT INTO settings (id, settings) VALUES (1, ?) ON CONFLICT(id) DO UPDATE SET settings = excluded.settings",
39
+ (settings_json,),
40
+ )
41
+
42
+ self.store._connection.commit()
43
+
44
+ def validate_config_compatibility(self) -> None:
45
+ """Check if current config is compatible with stored settings.
46
+
47
+ Raises ConfigMismatchError if there are incompatible differences.
48
+ If no settings exist, saves current config.
49
+ """
50
+ db_settings = self.get()
51
+ if not db_settings:
52
+ # No settings in DB, save current config
53
+ self.save()
54
+ return
55
+
56
+ from haiku.rag.config import Config
57
+
58
+ current_config = Config.model_dump(mode="json")
59
+
60
+ # Critical settings that must match
61
+ critical_settings = [
62
+ "EMBEDDINGS_PROVIDER",
63
+ "EMBEDDINGS_MODEL",
64
+ "EMBEDDINGS_VECTOR_DIM",
65
+ "CHUNK_SIZE",
66
+ "CHUNK_OVERLAP",
67
+ ]
68
+
69
+ errors = []
70
+ for setting in critical_settings:
71
+ if db_settings.get(setting) != current_config.get(setting):
72
+ errors.append(
73
+ f"{setting}: current={current_config.get(setting)}, stored={db_settings.get(setting)}"
74
+ )
75
+
76
+ if errors:
77
+ error_msg = f"Config mismatch detected: {'; '.join(errors)}. Consider rebuilding the database with the current configuration."
78
+ raise ConfigMismatchError(error_msg)
@@ -0,0 +1,3 @@
1
+ from haiku.rag.store.upgrades.v0_3_4 import upgrades as v0_3_4_upgrades
2
+
3
+ upgrades = v0_3_4_upgrades
@@ -0,0 +1,26 @@
1
+ from collections.abc import Callable
2
+ from sqlite3 import Connection
3
+
4
+ from haiku.rag.config import Config
5
+
6
+
7
+ def add_settings_table(db: Connection) -> None:
8
+ """Create settings table for storing current configuration"""
9
+ db.execute("""
10
+ CREATE TABLE settings (
11
+ id INTEGER PRIMARY KEY DEFAULT 1,
12
+ settings TEXT NOT NULL DEFAULT '{}'
13
+ )
14
+ """)
15
+
16
+ settings_json = Config.model_dump_json()
17
+ db.execute(
18
+ "INSERT INTO settings (id, settings) VALUES (1, ?)",
19
+ (settings_json,),
20
+ )
21
+ db.commit()
22
+
23
+
24
+ upgrades: list[tuple[str, list[Callable[[Connection], None]]]] = [
25
+ ("0.3.4", [add_settings_table])
26
+ ]
haiku/rag/utils.py CHANGED
@@ -1,6 +1,10 @@
1
1
  import sys
2
+ from importlib import metadata
2
3
  from pathlib import Path
3
4
 
5
+ import httpx
6
+ from packaging.version import Version, parse
7
+
4
8
 
5
9
  def get_default_data_dir() -> Path:
6
10
  """
@@ -23,3 +27,54 @@ def get_default_data_dir() -> Path:
23
27
 
24
28
  data_path = system_paths[sys.platform]
25
29
  return data_path
30
+
31
+
32
+ def semantic_version_to_int(version: str) -> int:
33
+ """
34
+ Convert a semantic version string to an integer.
35
+
36
+ :param version: Semantic version string
37
+ :type version: str
38
+ :return: Integer representation of semantic version
39
+ :rtype: int
40
+ """
41
+ major, minor, patch = version.split(".")
42
+ major = int(major) << 16
43
+ minor = int(minor) << 8
44
+ patch = int(patch)
45
+ return major + minor + patch
46
+
47
+
48
+ def int_to_semantic_version(version: int) -> str:
49
+ """
50
+ Convert an integer to a semantic version string.
51
+
52
+ :param version: Integer representation of semantic version
53
+ :type version: int
54
+ :return: Semantic version string
55
+ :rtype: str
56
+ """
57
+ major = version >> 16
58
+ minor = (version >> 8) & 255
59
+ patch = version & 255
60
+ return f"{major}.{minor}.{patch}"
61
+
62
+
63
+ async def is_up_to_date() -> tuple[bool, Version, Version]:
64
+ """
65
+ Checks whether haiku.rag is current.
66
+
67
+ :return: A tuple containing a boolean indicating whether haiku.rag is current, the running version and the latest version
68
+ :rtype: tuple[bool, Version, Version]
69
+ """
70
+
71
+ async with httpx.AsyncClient() as client:
72
+ running_version = parse(metadata.version("haiku.rag"))
73
+ try:
74
+ response = await client.get("https://pypi.org/pypi/haiku.rag/json")
75
+ data = response.json()
76
+ pypi_version = parse(data["info"]["version"])
77
+ except Exception:
78
+ # If no network connection, do not raise alarms.
79
+ pypi_version = running_version
80
+ return running_version >= pypi_version, running_version, pypi_version
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haiku.rag
3
- Version: 0.3.3
3
+ Version: 0.3.4
4
4
  Summary: Retrieval Augmented Generation (RAG) with SQLite
5
5
  Author-email: Yiorgis Gozadinos <ggozadinos@gmail.com>
6
6
  License: MIT
@@ -1,14 +1,14 @@
1
1
  haiku/rag/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- haiku/rag/app.py,sha256=Foi_K-sAqHWsIAAaxY2Tb0hyXnMCi06LqIFCPiBS5n0,7627
2
+ haiku/rag/app.py,sha256=FpLVyP1-zAq_XPmU8CPVLkuIAeuhBOGvMqhYS8RbN40,7649
3
3
  haiku/rag/chunker.py,sha256=lSSPWgNAe7gNZL_yNLmDtqxJix4YclOiG7gbARcEpV8,1871
4
- haiku/rag/cli.py,sha256=9F64IIm2c1nBKn7p9D5yYkVZr8HcjDemrzjF9SRGIY8,5017
5
- haiku/rag/client.py,sha256=qoVgdsP_MH8wVcDTvPIcMgW7323tTjOXH8JKugz5snY,10847
4
+ haiku/rag/cli.py,sha256=8PC7r5odIVLyksSm_BXor2rznIZ2KDug-YhzqbFPvms,5605
5
+ haiku/rag/client.py,sha256=AeRXw67E1dr6ICI6EJE1q0WwZgA6ezwFw55v6QVydYk,11014
6
6
  haiku/rag/config.py,sha256=ctD_pu7nDOieirJofhNMO-OJIONLC5myvcru9iTm_ps,1433
7
7
  haiku/rag/logging.py,sha256=zTTGpGq5tPdcd7RpCbd9EGw1IZlQDbYkrCg9t9pqRc4,580
8
8
  haiku/rag/mcp.py,sha256=tMN6fNX7ZtAER1R6DL1GkC9HZozTC4HzuQs199p7icI,4551
9
9
  haiku/rag/monitor.py,sha256=r386nkhdlsU8UECwIuVwnrSlgMk3vNIuUZGNIzkZuec,2770
10
10
  haiku/rag/reader.py,sha256=S7-Z72pDvSHedvgt4-RkTOwZadG88Oed9keJ69SVITk,962
11
- haiku/rag/utils.py,sha256=6xVM6z2OmhzB4FEDlPbMsr_ZBBmCbMQb83nP6E2UdxY,629
11
+ haiku/rag/utils.py,sha256=flQqO12OIqApINYAfkg8VDXBgRDFVR_HRaIaydk_OBQ,2310
12
12
  haiku/rag/embeddings/__init__.py,sha256=4jUPe2FyIf8BGZ7AncWSlBdNXG3URejBbnkhQf3JiD0,1505
13
13
  haiku/rag/embeddings/base.py,sha256=PTAWKTU-Q-hXIhbRK1o6pIdpaW7DFdzJXQ0Nzc6VI-w,379
14
14
  haiku/rag/embeddings/ollama.py,sha256=hWdrTiuJwNSRYCqP0WP-z6XXA3RBGkAiknZMsPLH0qU,441
@@ -21,7 +21,7 @@ haiku/rag/qa/ollama.py,sha256=-UtNFErYlA_66g3WLU6lK38a1Y5zhAL6s_uZ5AP0TFs,2381
21
21
  haiku/rag/qa/openai.py,sha256=dF32sGgVt8mZi5oVxByaeECs9NqLjvDiZnnpJBsrHm8,3968
22
22
  haiku/rag/qa/prompts.py,sha256=578LJGZJ0LQ_q7ccyj5hLabtHo8Zcfw5-DiLGN9lC-w,1200
23
23
  haiku/rag/store/__init__.py,sha256=hq0W0DAC7ysqhWSP2M2uHX8cbG6kbr-sWHxhq6qQcY0,103
24
- haiku/rag/store/engine.py,sha256=BeYZRZ08zaYeeu375ysnAL3tGz4roA3GzP7WRNwznCo,2603
24
+ haiku/rag/store/engine.py,sha256=4ouAD0s-TFwEoEHjVVw_KnV6aaw5nwhe9fdT8PRXfok,6061
25
25
  haiku/rag/store/models/__init__.py,sha256=s0E72zneGlowvZrFWaNxHYjOAUjgWdLxzdYsnvNRVlY,88
26
26
  haiku/rag/store/models/chunk.py,sha256=lmbPOOTz-N4PXhrA5XCUxyRcSTZBo135fqkV1mwnGcE,309
27
27
  haiku/rag/store/models/document.py,sha256=TVXVY-nQs-1vCORQEs9rA7zOtndeGC4dgCoujLAS054,396
@@ -29,8 +29,11 @@ haiku/rag/store/repositories/__init__.py,sha256=uIBhxjQh-4o3O-ck8b7BQ58qXQTuJdPv
29
29
  haiku/rag/store/repositories/base.py,sha256=cm3VyQXhtxvRfk1uJHpA0fDSxMpYN-mjQmRiDiLsQ68,1008
30
30
  haiku/rag/store/repositories/chunk.py,sha256=gik7ZPOK3gCoG6tU1pGueAZBPmJxIb7obYFUhwINrYg,16497
31
31
  haiku/rag/store/repositories/document.py,sha256=xpWOpjHFbhVwNJ1gpusEKNY6l_Qyibg9y_bdHCwcfpk,7133
32
- haiku_rag-0.3.3.dist-info/METADATA,sha256=nDI-sy2F8h7qr9hK1S7VQLOMRcWYP1clxJYxNVB1AaA,4019
33
- haiku_rag-0.3.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
34
- haiku_rag-0.3.3.dist-info/entry_points.txt,sha256=G1U3nAkNd5YDYd4v0tuYFbriz0i-JheCsFuT9kIoGCI,48
35
- haiku_rag-0.3.3.dist-info/licenses/LICENSE,sha256=eXZrWjSk9PwYFNK9yUczl3oPl95Z4V9UXH7bPN46iPo,1065
36
- haiku_rag-0.3.3.dist-info/RECORD,,
32
+ haiku/rag/store/repositories/settings.py,sha256=dme3_ulQdQvyF9daavSjAd-SjZ5hh0MJoxP7iXgap-A,2492
33
+ haiku/rag/store/upgrades/__init__.py,sha256=kKS1YWT_P-CYKhKtokOLTIFNKf9jlfjFFr8lyIMeogM,100
34
+ haiku/rag/store/upgrades/v0_3_4.py,sha256=GLogKZdZ40NX1vBHKdOJju7fFzNUCHoEnjSZg17Hm2U,663
35
+ haiku_rag-0.3.4.dist-info/METADATA,sha256=9FEVS2pZkPrRYVGd1qaMmfjyxr4fc9sHx1NTeyCbTo0,4019
36
+ haiku_rag-0.3.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
37
+ haiku_rag-0.3.4.dist-info/entry_points.txt,sha256=G1U3nAkNd5YDYd4v0tuYFbriz0i-JheCsFuT9kIoGCI,48
38
+ haiku_rag-0.3.4.dist-info/licenses/LICENSE,sha256=eXZrWjSk9PwYFNK9yUczl3oPl95Z4V9UXH7bPN46iPo,1065
39
+ haiku_rag-0.3.4.dist-info/RECORD,,