raglite-toolkit 1.0.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.
Files changed (45) hide show
  1. raglite/__init__.py +114 -0
  2. raglite/api/__init__.py +4 -0
  3. raglite/api/schemas.py +26 -0
  4. raglite/api/server.py +198 -0
  5. raglite/chunking/__init__.py +4 -0
  6. raglite/chunking/base.py +13 -0
  7. raglite/chunking/recursive.py +34 -0
  8. raglite/cli.py +212 -0
  9. raglite/config.py +65 -0
  10. raglite/constants.py +18 -0
  11. raglite/core/__init__.py +1 -0
  12. raglite/core/document.py +361 -0
  13. raglite/embeddings/__init__.py +13 -0
  14. raglite/embeddings/base.py +41 -0
  15. raglite/embeddings/factory.py +18 -0
  16. raglite/embeddings/local.py +66 -0
  17. raglite/embeddings/models.py +11 -0
  18. raglite/embeddings/remote.py +158 -0
  19. raglite/errors.py +47 -0
  20. raglite/llm/__init__.py +14 -0
  21. raglite/llm/answer.py +311 -0
  22. raglite/llm/factory.py +119 -0
  23. raglite/llm/models.py +12 -0
  24. raglite/llm/prompt.py +62 -0
  25. raglite/loaders/__init__.py +34 -0
  26. raglite/loaders/base.py +11 -0
  27. raglite/loaders/docx.py +15 -0
  28. raglite/loaders/json.py +40 -0
  29. raglite/loaders/markdown.py +11 -0
  30. raglite/loaders/pdf.py +17 -0
  31. raglite/loaders/txt.py +15 -0
  32. raglite/retrieval/__init__.py +3 -0
  33. raglite/retrieval/retriever.py +57 -0
  34. raglite/types.py +95 -0
  35. raglite/utils/__init__.py +1 -0
  36. raglite/utils/hash.py +26 -0
  37. raglite/utils/logger.py +44 -0
  38. raglite/vectordb/__init__.py +4 -0
  39. raglite/vectordb/base.py +55 -0
  40. raglite/vectordb/memory.py +108 -0
  41. raglite_toolkit-1.0.0.dist-info/METADATA +388 -0
  42. raglite_toolkit-1.0.0.dist-info/RECORD +45 -0
  43. raglite_toolkit-1.0.0.dist-info/WHEEL +4 -0
  44. raglite_toolkit-1.0.0.dist-info/entry_points.txt +2 -0
  45. raglite_toolkit-1.0.0.dist-info/licenses/LICENSE +21 -0
raglite/__init__.py ADDED
@@ -0,0 +1,114 @@
1
+ from .constants import PACKAGE_NAME, PACKAGE_VERSION
2
+
3
+ VERSION = PACKAGE_VERSION
4
+
5
+ from .errors import (
6
+ RagLiteError,
7
+ UnsupportedFileTypeError,
8
+ FileNotIndexedError,
9
+ LoaderError,
10
+ ChunkingError,
11
+ EmbeddingError,
12
+ VectorDBError,
13
+ LLMError,
14
+ ConfigError,
15
+ )
16
+
17
+ from .types import (
18
+ LLMProviderName,
19
+ EmbeddingProviderName,
20
+ LLMProviderConfig,
21
+ EmbeddingProviderConfig,
22
+ ChunkMetadata,
23
+ StoredChunk,
24
+ SearchResult,
25
+ AnswerResult,
26
+ IndexMetadata,
27
+ )
28
+
29
+ from .config import DocumentOptions, resolve_config
30
+
31
+ from .utils.logger import create_logger
32
+
33
+ from .loaders import (
34
+ BaseLoader,
35
+ TxtLoader,
36
+ MarkdownLoader,
37
+ JsonLoader,
38
+ PdfLoader,
39
+ DocxLoader,
40
+ get_loader,
41
+ )
42
+
43
+ from .chunking import BaseChunker, RecursiveChunker
44
+
45
+ from .vectordb import MemoryVectorStore
46
+
47
+ from .retrieval import Retriever
48
+
49
+ from .embeddings import (
50
+ DEFAULT_EMBEDDING_MODELS,
51
+ LocalEmbedder,
52
+ RemoteEmbedder,
53
+ create_embedder,
54
+ )
55
+
56
+ from .llm import (
57
+ DEFAULT_LLM_MODELS,
58
+ build_system_prompt,
59
+ build_user_prompt,
60
+ create_llm,
61
+ generate_answer,
62
+ stream_answer,
63
+ )
64
+
65
+ from .core.document import Document
66
+
67
+ __all__ = [
68
+ "PACKAGE_NAME",
69
+ "PACKAGE_VERSION",
70
+ "VERSION",
71
+ "RagLiteError",
72
+ "UnsupportedFileTypeError",
73
+ "FileNotIndexedError",
74
+ "LoaderError",
75
+ "ChunkingError",
76
+ "EmbeddingError",
77
+ "VectorDBError",
78
+ "LLMError",
79
+ "ConfigError",
80
+ "LLMProviderName",
81
+ "EmbeddingProviderName",
82
+ "LLMProviderConfig",
83
+ "EmbeddingProviderConfig",
84
+ "ChunkMetadata",
85
+ "StoredChunk",
86
+ "SearchResult",
87
+ "AnswerResult",
88
+ "IndexMetadata",
89
+ "DocumentOptions",
90
+ "resolve_config",
91
+ "create_logger",
92
+ "BaseLoader",
93
+ "TxtLoader",
94
+ "MarkdownLoader",
95
+ "JsonLoader",
96
+ "PdfLoader",
97
+ "DocxLoader",
98
+ "get_loader",
99
+ "BaseChunker",
100
+ "RecursiveChunker",
101
+ "MemoryVectorStore",
102
+ "Retriever",
103
+ "DEFAULT_EMBEDDING_MODELS",
104
+ "LocalEmbedder",
105
+ "RemoteEmbedder",
106
+ "create_embedder",
107
+ "DEFAULT_LLM_MODELS",
108
+ "build_system_prompt",
109
+ "build_user_prompt",
110
+ "create_llm",
111
+ "generate_answer",
112
+ "stream_answer",
113
+ "Document",
114
+ ]
@@ -0,0 +1,4 @@
1
+ from .schemas import SearchRequest, AskRequest
2
+ from .server import build_app, create_server, ServerHandle
3
+
4
+ __all__ = ["SearchRequest", "AskRequest", "build_app", "create_server", "ServerHandle"]
raglite/api/schemas.py ADDED
@@ -0,0 +1,26 @@
1
+ from typing import Optional
2
+ from pydantic import BaseModel, Field, ConfigDict
3
+
4
+
5
+ class SearchRequest(BaseModel):
6
+ model_config = ConfigDict(populate_by_name=True, extra="allow")
7
+
8
+ query: str = Field(..., min_length=1)
9
+ topK: Optional[int] = Field(default=None, alias="topK", gt=0, le=50)
10
+ scoreThreshold: Optional[float] = Field(
11
+ default=None, alias="scoreThreshold", ge=-1.0, le=1.0
12
+ )
13
+
14
+
15
+ class AskRequest(BaseModel):
16
+ model_config = ConfigDict(populate_by_name=True, extra="allow")
17
+
18
+ question: str = Field(..., min_length=1)
19
+ topK: Optional[int] = Field(default=None, alias="topK", gt=0, le=50)
20
+ scoreThreshold: Optional[float] = Field(
21
+ default=None, alias="scoreThreshold", ge=-1.0, le=1.0
22
+ )
23
+ includeCitations: Optional[bool] = Field(
24
+ default=None, alias="includeCitations"
25
+ )
26
+ stream: Optional[bool] = None
raglite/api/server.py ADDED
@@ -0,0 +1,198 @@
1
+ import time
2
+ import threading
3
+ from typing import Optional, Dict, Any, Union
4
+ import uvicorn
5
+ from fastapi import FastAPI, HTTPException, Request, Depends
6
+ from fastapi.responses import StreamingResponse, JSONResponse
7
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
8
+ from pydantic import ValidationError
9
+
10
+ from ..constants import PACKAGE_VERSION, DEFAULT_HOST, DEFAULT_PORT
11
+ from ..errors import RagLiteError
12
+ from ..embeddings.models import DEFAULT_EMBEDDING_MODELS
13
+ from .schemas import SearchRequest, AskRequest
14
+
15
+ security = HTTPBearer(auto_error=False)
16
+
17
+
18
+ class ServerHandle:
19
+ def __init__(
20
+ self,
21
+ server_thread: threading.Thread,
22
+ uvicorn_server: uvicorn.Server,
23
+ url: str,
24
+ ):
25
+ self.server_thread = server_thread
26
+ self.uvicorn_server = uvicorn_server
27
+ self.url = url
28
+
29
+ def close(self) -> None:
30
+ """Stop the background API server."""
31
+ self.uvicorn_server.should_exit = True
32
+ self.server_thread.join()
33
+
34
+
35
+ def build_app(document: Any, options: Dict[str, Any]) -> FastAPI:
36
+ app = FastAPI(title="RAGLite REST API")
37
+
38
+ bearer_token = options.get("bearerToken") or options.get("bearer_token")
39
+
40
+ if options.get("requestLogging"):
41
+
42
+ @app.middleware("http")
43
+ async def log_requests(request: Request, call_next):
44
+ start = time.time()
45
+ response = await call_next(request)
46
+ duration = int((time.time() - start) * 1000)
47
+ print(
48
+ f"[raglite] {request.method} {request.url.path} -> {response.status_code} ({duration}ms)"
49
+ )
50
+ return response
51
+
52
+ def authorize(
53
+ credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
54
+ ):
55
+ if bearer_token:
56
+ if not credentials or credentials.credentials != bearer_token:
57
+ raise HTTPException(status_code=401, detail="Unauthorized")
58
+ return True
59
+
60
+ @app.exception_handler(ValidationError)
61
+ async def validation_exception_handler(request: Request, exc: ValidationError):
62
+ return JSONResponse(
63
+ status_code=400,
64
+ content={"error": "ValidationError", "details": exc.errors()},
65
+ )
66
+
67
+ @app.exception_handler(RagLiteError)
68
+ async def raglite_exception_handler(request: Request, exc: RagLiteError):
69
+ return JSONResponse(
70
+ status_code=400,
71
+ content={"error": exc.__class__.__name__, "message": str(exc)},
72
+ )
73
+
74
+ @app.exception_handler(Exception)
75
+ async def general_exception_handler(request: Request, exc: Exception):
76
+ print("[raglite] unhandled error", exc)
77
+ return JSONResponse(
78
+ status_code=500,
79
+ content={"error": "InternalServerError"},
80
+ )
81
+
82
+ @app.get("/health")
83
+ async def health():
84
+ return {
85
+ "status": "ok",
86
+ "version": PACKAGE_VERSION,
87
+ "chunks": document.chunk_count,
88
+ "namespace": document.store_namespace,
89
+ }
90
+
91
+ dependencies = [Depends(authorize)] if bearer_token else []
92
+
93
+ @app.get("/info", dependencies=dependencies)
94
+ async def info():
95
+ cfg = document.resolved_config
96
+ llm_config = options.get("llm") or cfg.llm
97
+ return {
98
+ "version": PACKAGE_VERSION,
99
+ "chunkSize": cfg.chunkSize,
100
+ "overlap": cfg.overlap,
101
+ "topK": cfg.topK,
102
+ "embeddings": {
103
+ "provider": cfg.embeddings.provider,
104
+ "model": cfg.embeddings.model
105
+ or DEFAULT_EMBEDDING_MODELS.get(cfg.embeddings.provider),
106
+ },
107
+ "llmProvider": llm_config.provider if llm_config else None,
108
+ "chunks": document.chunk_count,
109
+ }
110
+
111
+ @app.post("/search", dependencies=dependencies)
112
+ async def search(req: SearchRequest):
113
+ results = document.search(
114
+ req.query,
115
+ top_k=req.topK,
116
+ score_threshold=req.scoreThreshold,
117
+ )
118
+ return {
119
+ "results": [r.model_dump(by_alias=True) for r in results]
120
+ }
121
+
122
+ ask_provider = options.get("llm")
123
+
124
+ if ask_provider:
125
+
126
+ @app.post("/ask", dependencies=dependencies)
127
+ async def ask(req: AskRequest):
128
+ ask_options = {
129
+ "llm": ask_provider,
130
+ "topK": req.topK,
131
+ "scoreThreshold": req.scoreThreshold,
132
+ "includeCitations": req.includeCitations,
133
+ }
134
+
135
+ if req.stream:
136
+
137
+ def event_generator():
138
+ for chunk in document.ask_stream(req.question, ask_options):
139
+ yield chunk
140
+
141
+ return StreamingResponse(
142
+ event_generator(), media_type="text/plain"
143
+ )
144
+
145
+ answer = document.ask(req.question, ask_options)
146
+ return answer.model_dump(by_alias=True)
147
+
148
+ else:
149
+
150
+ @app.post("/ask", dependencies=dependencies)
151
+ async def ask(req: AskRequest):
152
+ return JSONResponse(
153
+ status_code=503,
154
+ content={
155
+ "error": "AskDisabled",
156
+ "message": "No LLM provider configured on the server.",
157
+ },
158
+ )
159
+
160
+ return app
161
+
162
+
163
+ def create_server(
164
+ document: Any,
165
+ options: Dict[str, Any],
166
+ *,
167
+ host: Optional[str] = None,
168
+ port: Optional[int] = None,
169
+ bearer_token: Optional[str] = None,
170
+ ) -> ServerHandle:
171
+ srv_host = host or options.get("host") or DEFAULT_HOST
172
+ srv_port = port or options.get("port") or DEFAULT_PORT
173
+
174
+ # Create server options clone
175
+ server_options = dict(options)
176
+ if bearer_token:
177
+ server_options["bearerToken"] = bearer_token
178
+
179
+ app = build_app(document, server_options)
180
+
181
+ config = uvicorn.Config(
182
+ app=app, host=srv_host, port=srv_port, log_level="warning"
183
+ )
184
+ server = uvicorn.Server(config)
185
+
186
+ thread = threading.Thread(target=server.run)
187
+ thread.daemon = True
188
+ thread.start()
189
+
190
+ url = f"http://{srv_host}:{srv_port}"
191
+
192
+ # Wait up to 2 seconds for server to boot
193
+ retries = 20
194
+ while retries > 0 and not server.started:
195
+ time.sleep(0.1)
196
+ retries -= 1
197
+
198
+ return ServerHandle(server_thread=thread, uvicorn_server=server, url=url)
@@ -0,0 +1,4 @@
1
+ from .base import BaseChunker
2
+ from .recursive import RecursiveChunker
3
+
4
+ __all__ = ["BaseChunker", "RecursiveChunker"]
@@ -0,0 +1,13 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import List
3
+
4
+
5
+ class BaseChunker(ABC):
6
+ def __init__(self, chunk_size: int, overlap: int):
7
+ self.chunk_size = chunk_size
8
+ self.overlap = overlap
9
+
10
+ @abstractmethod
11
+ def split(self, text: str) -> List[str]:
12
+ """Split text into chunks."""
13
+ pass
@@ -0,0 +1,34 @@
1
+ import re
2
+ from typing import List
3
+ from .base import BaseChunker
4
+ from ..errors import ChunkingError
5
+
6
+
7
+ class RecursiveChunker(BaseChunker):
8
+ def split(self, text: str) -> List[str]:
9
+ if not text.strip():
10
+ return []
11
+ if self.overlap >= self.chunk_size:
12
+ raise ChunkingError(
13
+ f"overlap ({self.overlap}) must be smaller than chunkSize ({self.chunk_size})"
14
+ )
15
+
16
+ words = [w for w in re.split(r"\s+", text) if w]
17
+ if len(words) <= self.chunk_size:
18
+ return [" ".join(words)]
19
+
20
+ step = self.chunk_size - self.overlap
21
+ chunks: List[str] = []
22
+
23
+ start = 0
24
+ while start < len(words):
25
+ end = start + self.chunk_size
26
+ slice_words = words[start:end]
27
+ if not slice_words:
28
+ break
29
+ chunks.append(" ".join(slice_words))
30
+ if end >= len(words):
31
+ break
32
+ start += step
33
+
34
+ return chunks
raglite/cli.py ADDED
@@ -0,0 +1,212 @@
1
+ import sys
2
+ import json
3
+ import argparse
4
+ import time
5
+ from typing import Optional
6
+
7
+ from .constants import PACKAGE_VERSION
8
+ from .core.document import Document
9
+
10
+ HELP = f"""raglite v{PACKAGE_VERSION}
11
+
12
+ Usage:
13
+ raglite index <file> [--chunk-size N] [--overlap N] [--embed-provider P] [--embed-model M] [--embed-key K] [--rebuild]
14
+ raglite search <file> "query" [--top-k N]
15
+ raglite ask <file> "question" --llm-provider P [--llm-model M] [--llm-key K] [--stream]
16
+ raglite serve <file> --llm-provider P [--llm-key K] [--host H] [--port N] [--token T]
17
+ raglite --help
18
+ raglite --version
19
+
20
+ Providers:
21
+ LLM: openai, anthropic, google, mistral, cohere, groq, xai, ollama
22
+ Embeddings: openai, google, mistral, cohere, voyage, ollama, local
23
+ """
24
+
25
+
26
+ def parse_common_embedding(args_dict: dict) -> dict:
27
+ provider = args_dict.get("embed_provider") or "local"
28
+ config = {"provider": provider}
29
+ if args_dict.get("embed_model"):
30
+ config["model"] = args_dict["embed_model"]
31
+ if args_dict.get("embed_key"):
32
+ config["apiKey"] = args_dict["embed_key"]
33
+ return config
34
+
35
+
36
+ def parse_llm(args_dict: dict) -> Optional[dict]:
37
+ provider = args_dict.get("llm_provider")
38
+ if not provider:
39
+ return None
40
+ config = {"provider": provider}
41
+ if args_dict.get("llm_model"):
42
+ config["model"] = args_dict["llm_model"]
43
+ if args_dict.get("llm_key"):
44
+ config["apiKey"] = args_dict["llm_key"]
45
+ return config
46
+
47
+
48
+ def run_index(args):
49
+ parser = argparse.ArgumentParser(add_help=False)
50
+ parser.add_argument("file")
51
+ parser.add_argument("--chunk-size", type=int)
52
+ parser.add_argument("--overlap", type=int)
53
+ parser.add_argument("--embed-provider")
54
+ parser.add_argument("--embed-model")
55
+ parser.add_argument("--embed-key")
56
+ parser.add_argument("--rebuild", action="store_true")
57
+
58
+ parsed = parser.parse_args(args)
59
+
60
+ embeddings = parse_common_embedding(vars(parsed))
61
+ doc = Document(parsed.file, {"embeddings": embeddings})
62
+
63
+ build_opts = {}
64
+ if parsed.chunk_size is not None:
65
+ build_opts["chunkSize"] = parsed.chunk_size
66
+ if parsed.overlap is not None:
67
+ build_opts["overlap"] = parsed.overlap
68
+ if parsed.rebuild:
69
+ build_opts["rebuild"] = True
70
+
71
+ result = doc.build(build_opts)
72
+ sys.stdout.write(f"{json.dumps(result, indent=2)}\n")
73
+
74
+
75
+ def run_search(args):
76
+ parser = argparse.ArgumentParser(add_help=False)
77
+ parser.add_argument("file")
78
+ parser.add_argument("query")
79
+ parser.add_argument("--top-k", type=int)
80
+ parser.add_argument("--embed-provider")
81
+ parser.add_argument("--embed-model")
82
+ parser.add_argument("--embed-key")
83
+
84
+ parsed = parser.parse_args(args)
85
+
86
+ embeddings = parse_common_embedding(vars(parsed))
87
+ doc = Document(parsed.file, {"embeddings": embeddings})
88
+
89
+ search_opts = {}
90
+ if parsed.top_k is not None:
91
+ search_opts["topK"] = parsed.top_k
92
+
93
+ results = doc.search(parsed.query, search_opts)
94
+ serialized = [r.model_dump(by_alias=True) for r in results]
95
+ sys.stdout.write(f"{json.dumps(serialized, indent=2)}\n")
96
+
97
+
98
+ def run_ask(args):
99
+ parser = argparse.ArgumentParser(add_help=False)
100
+ parser.add_argument("file")
101
+ parser.add_argument("question")
102
+ parser.add_argument("--top-k", type=int)
103
+ parser.add_argument("--embed-provider")
104
+ parser.add_argument("--embed-model")
105
+ parser.add_argument("--embed-key")
106
+ parser.add_argument("--llm-provider", required=True)
107
+ parser.add_argument("--llm-model")
108
+ parser.add_argument("--llm-key")
109
+ parser.add_argument("--stream", action="store_true")
110
+
111
+ parsed = parser.parse_args(args)
112
+
113
+ embeddings = parse_common_embedding(vars(parsed))
114
+ llm = parse_llm(vars(parsed))
115
+
116
+ doc = Document(parsed.file, {"embeddings": embeddings, "llm": llm})
117
+
118
+ opts = {}
119
+ if parsed.top_k is not None:
120
+ opts["topK"] = parsed.top_k
121
+
122
+ if parsed.stream:
123
+ for chunk in doc.ask_stream(parsed.question, opts):
124
+ sys.stdout.write(chunk)
125
+ sys.stdout.flush()
126
+ sys.stdout.write("\n")
127
+ else:
128
+ answer = doc.ask(parsed.question, opts)
129
+ sys.stdout.write(f"{answer.text}\n")
130
+
131
+
132
+ def run_serve(args):
133
+ parser = argparse.ArgumentParser(add_help=False)
134
+ parser.add_argument("file")
135
+ parser.add_argument("--embed-provider")
136
+ parser.add_argument("--embed-model")
137
+ parser.add_argument("--embed-key")
138
+ parser.add_argument("--llm-provider")
139
+ parser.add_argument("--llm-model")
140
+ parser.add_argument("--llm-key")
141
+ parser.add_argument("--host")
142
+ parser.add_argument("--port", type=int)
143
+ parser.add_argument("--token")
144
+
145
+ parsed = parser.parse_args(args)
146
+
147
+ embeddings = parse_common_embedding(vars(parsed))
148
+ llm = parse_llm(vars(parsed))
149
+
150
+ doc = Document(
151
+ parsed.file,
152
+ {"embeddings": embeddings, **({"llm": llm} if llm else {})},
153
+ )
154
+ doc.build()
155
+
156
+ serve_opts = {}
157
+ if llm:
158
+ serve_opts["llm"] = llm
159
+ if parsed.host:
160
+ serve_opts["host"] = parsed.host
161
+ if parsed.port is not None:
162
+ serve_opts["port"] = parsed.port
163
+ if parsed.token:
164
+ serve_opts["bearerToken"] = parsed.token
165
+
166
+ handle = doc.serve(serve_opts)
167
+ sys.stdout.write(f"RagLite listening on {handle.url}\n")
168
+ sys.stdout.flush()
169
+
170
+ try:
171
+ while True:
172
+ time.sleep(1)
173
+ except KeyboardInterrupt:
174
+ handle.close()
175
+ sys.exit(0)
176
+
177
+
178
+ def main():
179
+ argv = sys.argv[1:]
180
+ if not argv or argv[0] in ("--help", "-h", "help"):
181
+ sys.stdout.write(HELP)
182
+ sys.stdout.flush()
183
+ sys.exit(0)
184
+ if argv[0] in ("--version", "-v", "version"):
185
+ sys.stdout.write(f"{PACKAGE_VERSION}\n")
186
+ sys.stdout.flush()
187
+ sys.exit(0)
188
+
189
+ command = argv[0]
190
+ args = argv[1:]
191
+
192
+ try:
193
+ if command == "index":
194
+ run_index(args)
195
+ elif command == "search":
196
+ run_search(args)
197
+ elif command == "ask":
198
+ run_ask(args)
199
+ elif command == "serve":
200
+ run_serve(args)
201
+ else:
202
+ sys.stderr.write(f"Unknown command: {command}\n\n{HELP}")
203
+ sys.stderr.flush()
204
+ sys.exit(2)
205
+ except Exception as e:
206
+ sys.stderr.write(f"Error: {e}\n")
207
+ sys.stderr.flush()
208
+ sys.exit(1)
209
+
210
+
211
+ if __name__ == "__main__":
212
+ main()
raglite/config.py ADDED
@@ -0,0 +1,65 @@
1
+ from typing import Optional, Union, Dict, Any
2
+ from pydantic import BaseModel, Field, ConfigDict
3
+
4
+ from .types import EmbeddingProviderConfig, LLMProviderConfig
5
+ from .constants import (
6
+ DEFAULT_CHUNK_SIZE,
7
+ DEFAULT_CHUNK_OVERLAP,
8
+ DEFAULT_TOP_K,
9
+ DEFAULT_SCORE_THRESHOLD,
10
+ DEFAULT_STORE_DIRNAME,
11
+ )
12
+
13
+
14
+ class DocumentOptions(BaseModel):
15
+ model_config = ConfigDict(populate_by_name=True, extra="allow")
16
+
17
+ chunkSize: Optional[int] = Field(default=None, alias="chunkSize")
18
+ overlap: Optional[int] = None
19
+ topK: Optional[int] = Field(default=None, alias="topK")
20
+ scoreThreshold: Optional[float] = Field(default=None, alias="scoreThreshold")
21
+ storeDir: Optional[str] = Field(default=None, alias="storeDir")
22
+ embeddings: Optional[EmbeddingProviderConfig] = None
23
+ llm: Optional[LLMProviderConfig] = None
24
+ logLevel: Optional[str] = Field(default=None, alias="logLevel")
25
+
26
+
27
+ class ResolvedConfig(BaseModel):
28
+ model_config = ConfigDict(populate_by_name=True, extra="allow")
29
+
30
+ chunkSize: int = Field(..., alias="chunkSize")
31
+ overlap: int
32
+ topK: int = Field(..., alias="topK")
33
+ scoreThreshold: float = Field(..., alias="scoreThreshold")
34
+ storeDir: str = Field(..., alias="storeDir")
35
+ embeddings: EmbeddingProviderConfig
36
+ llm: Optional[LLMProviderConfig] = None
37
+ logLevel: str = Field(..., alias="logLevel")
38
+
39
+
40
+ def resolve_config(options: Optional[Union[DocumentOptions, Dict[str, Any]]] = None) -> ResolvedConfig:
41
+ if options is None:
42
+ opts = DocumentOptions()
43
+ elif isinstance(options, dict):
44
+ opts = DocumentOptions.model_validate(options)
45
+ else:
46
+ opts = options
47
+
48
+ chunk_size = opts.chunkSize if opts.chunkSize is not None else DEFAULT_CHUNK_SIZE
49
+ overlap = opts.overlap if opts.overlap is not None else DEFAULT_CHUNK_OVERLAP
50
+ top_k = opts.topK if opts.topK is not None else DEFAULT_TOP_K
51
+ score_threshold = opts.scoreThreshold if opts.scoreThreshold is not None else DEFAULT_SCORE_THRESHOLD
52
+ store_dir = opts.storeDir if opts.storeDir is not None else DEFAULT_STORE_DIRNAME
53
+ embeddings = opts.embeddings if opts.embeddings is not None else EmbeddingProviderConfig(provider="local")
54
+ log_level = opts.logLevel if opts.logLevel is not None else "info"
55
+
56
+ return ResolvedConfig(
57
+ chunkSize=chunk_size,
58
+ overlap=overlap,
59
+ topK=top_k,
60
+ scoreThreshold=score_threshold,
61
+ storeDir=store_dir,
62
+ embeddings=embeddings,
63
+ llm=opts.llm,
64
+ logLevel=log_level
65
+ )
raglite/constants.py ADDED
@@ -0,0 +1,18 @@
1
+ PACKAGE_NAME = "raglite-toolkit"
2
+ PACKAGE_VERSION = "0.1.0"
3
+
4
+ SUPPORTED_EXTENSIONS = {".pdf", ".txt", ".json", ".md", ".markdown", ".docx"}
5
+
6
+ DEFAULT_CHUNK_SIZE = 500
7
+ DEFAULT_CHUNK_OVERLAP = 50
8
+
9
+ DEFAULT_TOP_K = 5
10
+ DEFAULT_SCORE_THRESHOLD = 0.0
11
+
12
+ DEFAULT_TEMPERATURE = 0.0
13
+
14
+ DEFAULT_STORE_DIRNAME = ".raglite"
15
+ DEFAULT_COLLECTION_NAME = "default"
16
+
17
+ DEFAULT_HOST = "127.0.0.1"
18
+ DEFAULT_PORT = 8085
@@ -0,0 +1 @@
1
+ # core module