yoga1290.rag 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.
- yoga1290_rag-0.1.0/PKG-INFO +7 -0
- yoga1290_rag-0.1.0/pyproject.toml +16 -0
- yoga1290_rag-0.1.0/setup.cfg +4 -0
- yoga1290_rag-0.1.0/yoga1290/rag/api/main.py +48 -0
- yoga1290_rag-0.1.0/yoga1290/rag/application/__init__.py +0 -0
- yoga1290_rag-0.1.0/yoga1290/rag/application/pipelines/__init__.py +7 -0
- yoga1290_rag-0.1.0/yoga1290/rag/application/pipelines/indexing_pipeline.py +34 -0
- yoga1290_rag-0.1.0/yoga1290/rag/application/pipelines/ingestion_pipeline.py +54 -0
- yoga1290_rag-0.1.0/yoga1290/rag/application/pipelines/retrieval_pipeline.py +84 -0
- yoga1290_rag-0.1.0/yoga1290/rag/application/services/__init__.py +0 -0
- yoga1290_rag-0.1.0/yoga1290/rag/application/services/document_service.py +15 -0
- yoga1290_rag-0.1.0/yoga1290/rag/application/services/search_service.py +25 -0
- yoga1290_rag-0.1.0/yoga1290/rag/config/__init__.py +0 -0
- yoga1290_rag-0.1.0/yoga1290/rag/config/settings.py +12 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/__init__.py +0 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/models/__init__.py +20 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/models/chunk.py +13 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/models/classification.py +20 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/models/document.py +21 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/models/extracted_data.py +15 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/models/parsed_document.py +30 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/models/rag_result.py +17 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/models/search_document.py +16 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/models/search_result.py +12 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/ports/__init__.py +21 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/ports/document_classifier.py +31 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/ports/document_extractor.py +17 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/ports/document_parser.py +30 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/ports/document_source.py +29 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/ports/embedder.py +66 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/ports/llm.py +11 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/ports/retriever.py +16 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/ports/search_preparer.py +25 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/ports/vector_store.py +46 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/schemas/__init__.py +0 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/schemas/classification_schema.py +10 -0
- yoga1290_rag-0.1.0/yoga1290/rag/domain/schemas/extraction_schema.py +50 -0
- yoga1290_rag-0.1.0/yoga1290/rag/factories/__init__.py +5 -0
- yoga1290_rag-0.1.0/yoga1290/rag/factories/ai_provider.py +4 -0
- yoga1290_rag-0.1.0/yoga1290/rag/factories/llm_factory.py +48 -0
- yoga1290_rag-0.1.0/yoga1290/rag/factories/pipeline_factory.py +4 -0
- yoga1290_rag-0.1.0/yoga1290/rag/factories/storage_provider.py +4 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/__init__.py +0 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/databricks/__init__.py +0 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/databricks/classifier.py +8 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/databricks/extractor.py +9 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/databricks/parser.py +9 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/databricks/search_preparer.py +8 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/databricks/spark_session.py +4 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/embeddings/__init__.py +5 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/embeddings/gte_embedder.py +12 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/embeddings/llama_cpp_embedder.py +72 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/embeddings/qwen_embedder.py +12 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/local/__init__.py +0 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/local/classification/__init__.py +0 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/local/classification/qwen_classifier.py +6 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/local/extraction/__init__.py +5 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/local/extraction/llama_cpp_document_extractor.py +181 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/local/llm/__init__.py +5 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/local/llm/llama_cpp_llm.py +31 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/local/llm/qwen.py +3 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/local/llm/vllm.py +3 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/local/parsing/__init__.py +5 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/local/parsing/local_document_parser.py +54 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/local/search/__init__.py +0 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/local/search/local_search_preparer.py +31 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/local/search/recursive_character_search_preparer.py +62 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/local/sources/csv_document_source.py +62 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/vectorstores/__init__.py +7 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/vectorstores/databricks_vector_search.py +14 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/vectorstores/milvus.py +14 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/vectorstores/pgvector/__init__.py +7 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/vectorstores/pgvector/pgvector_retriever.py +43 -0
- yoga1290_rag-0.1.0/yoga1290/rag/infrastructure/vectorstores/pgvector/pgvector_store.py +165 -0
- yoga1290_rag-0.1.0/yoga1290/rag/yoga1290.rag.egg-info/PKG-INFO +7 -0
- yoga1290_rag-0.1.0/yoga1290/rag/yoga1290.rag.egg-info/SOURCES.txt +76 -0
- yoga1290_rag-0.1.0/yoga1290/rag/yoga1290.rag.egg-info/dependency_links.txt +1 -0
- yoga1290_rag-0.1.0/yoga1290/rag/yoga1290.rag.egg-info/top_level.txt +6 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "yoga1290.rag"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Document AI and RAG application"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
|
|
11
|
+
[tool.setuptools.packages.find]
|
|
12
|
+
where = ["yoga1290/rag"]
|
|
13
|
+
|
|
14
|
+
[project.urls]
|
|
15
|
+
Homepage = "https://github.com/yoga1290/rag"
|
|
16
|
+
Issues = "https://github.com/yoga1290/rag/issues"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from fastapi import FastAPI, File, HTTPException, Query, UploadFile
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from uuid import uuid4
|
|
4
|
+
|
|
5
|
+
app = FastAPI()
|
|
6
|
+
|
|
7
|
+
UPLOAD_DIR = Path("uploads")
|
|
8
|
+
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@app.post("/embed")
|
|
12
|
+
async def upload_file(
|
|
13
|
+
user_id: str = Query(...),
|
|
14
|
+
file: UploadFile = File(...),
|
|
15
|
+
):
|
|
16
|
+
try:
|
|
17
|
+
# Preserve the original file extension
|
|
18
|
+
extension = Path(file.filename).suffix
|
|
19
|
+
|
|
20
|
+
# Generate a unique filename
|
|
21
|
+
unique_filename = f"{uuid4()}{extension}"
|
|
22
|
+
|
|
23
|
+
# Optionally organize files by user
|
|
24
|
+
user_dir = UPLOAD_DIR / user_id
|
|
25
|
+
user_dir.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
|
|
27
|
+
destination = user_dir / unique_filename
|
|
28
|
+
|
|
29
|
+
with destination.open("wb") as buffer:
|
|
30
|
+
while chunk := await file.read(1024 * 1024):
|
|
31
|
+
buffer.write(chunk)
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
"user_id": user_id,
|
|
35
|
+
"filename": unique_filename,
|
|
36
|
+
"original_filename": file.filename,
|
|
37
|
+
"content_type": file.content_type,
|
|
38
|
+
"path": str(destination),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
except Exception as e:
|
|
42
|
+
raise HTTPException(
|
|
43
|
+
status_code=500,
|
|
44
|
+
detail=f"Failed to upload file: {e}",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
finally:
|
|
48
|
+
await file.close()
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from yoga1290.rag.domain.ports.embedder import Embedder
|
|
2
|
+
from yoga1290.rag.domain.ports.vector_store import VectorStore
|
|
3
|
+
|
|
4
|
+
class IndexingPipeline:
|
|
5
|
+
def __init__(
|
|
6
|
+
self,
|
|
7
|
+
embedder: Embedder,
|
|
8
|
+
vector_store: VectorStore,
|
|
9
|
+
) -> None:
|
|
10
|
+
self.embedder = embedder
|
|
11
|
+
self.vector_store = vector_store
|
|
12
|
+
|
|
13
|
+
def index(self, search_document):
|
|
14
|
+
chunks = list(search_document.chunks)
|
|
15
|
+
texts = [chunk.text for chunk in chunks]
|
|
16
|
+
embeddings = self.embedder.embed_batch(texts)
|
|
17
|
+
|
|
18
|
+
indexed_chunks = [
|
|
19
|
+
chunk.__class__(
|
|
20
|
+
id=chunk.id,
|
|
21
|
+
document_id=chunk.document_id,
|
|
22
|
+
text=chunk.text,
|
|
23
|
+
index=chunk.index,
|
|
24
|
+
embedding=embedding,
|
|
25
|
+
metadata=chunk.metadata,
|
|
26
|
+
)
|
|
27
|
+
for chunk, embedding in zip(
|
|
28
|
+
chunks,
|
|
29
|
+
embeddings,
|
|
30
|
+
strict=True,
|
|
31
|
+
)
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
self.vector_store.upsert(indexed_chunks)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from yoga1290.rag.domain.models import (
|
|
2
|
+
Document,
|
|
3
|
+
ParsedDocument,
|
|
4
|
+
SearchDocument)
|
|
5
|
+
from yoga1290.rag.domain.ports import (
|
|
6
|
+
DocumentParser,
|
|
7
|
+
DocumentExtractor,
|
|
8
|
+
DocumentClassifier,
|
|
9
|
+
SearchPreparer,
|
|
10
|
+
VectorStore,)
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
|
|
13
|
+
class IngestionPipeline:
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
parser: DocumentParser,
|
|
17
|
+
extractor: DocumentExtractor,
|
|
18
|
+
classifier: DocumentClassifier,
|
|
19
|
+
search_preparer: SearchPreparer,
|
|
20
|
+
vectorstore: VectorStore,
|
|
21
|
+
) -> None:
|
|
22
|
+
self.parser = parser
|
|
23
|
+
self.extractor = extractor
|
|
24
|
+
self.classifier = classifier
|
|
25
|
+
self.search_preparer = search_preparer
|
|
26
|
+
self.vectorstore = vectorstore
|
|
27
|
+
|
|
28
|
+
def process(self,
|
|
29
|
+
documents: Sequence[Document],
|
|
30
|
+
# extraction_schema,
|
|
31
|
+
# classification_schema,
|
|
32
|
+
):
|
|
33
|
+
search_documents = []
|
|
34
|
+
for document in documents:
|
|
35
|
+
parsed_document: ParsedDocument = (
|
|
36
|
+
self.parser.parse(document))
|
|
37
|
+
search_document: SearchDocument = (
|
|
38
|
+
self.search_preparer.prepare(parsed_document))
|
|
39
|
+
search_documents.append(search_document)
|
|
40
|
+
self.vectorstore.add_documents(search_documents)
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
# "parsed": parsed,
|
|
44
|
+
# "extracted": extracted,
|
|
45
|
+
# "classification": classification,
|
|
46
|
+
"search_document": search_documents,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# extracted = self.extractor.extract(parsed, extraction_schema)
|
|
51
|
+
# classification = self.classifier.classify(
|
|
52
|
+
# parsed,
|
|
53
|
+
# classification_schema,
|
|
54
|
+
# )
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from yoga1290.rag.domain.models.rag_result import (
|
|
2
|
+
RagResult,
|
|
3
|
+
Source,
|
|
4
|
+
)
|
|
5
|
+
from yoga1290.rag.domain.ports import (
|
|
6
|
+
LLM,
|
|
7
|
+
Retriever, )
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RetrievalPipeline:
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
llm: LLM,
|
|
15
|
+
retriever: Retriever,
|
|
16
|
+
top_k: int = 2,
|
|
17
|
+
) -> None:
|
|
18
|
+
self._llm = llm
|
|
19
|
+
self._retriever = retriever
|
|
20
|
+
self._top_k = top_k
|
|
21
|
+
|
|
22
|
+
def run(self, question: str) -> RagResult:
|
|
23
|
+
|
|
24
|
+
import time
|
|
25
|
+
|
|
26
|
+
start = time.perf_counter()
|
|
27
|
+
|
|
28
|
+
chunks = self._retriever.retrieve(
|
|
29
|
+
question,
|
|
30
|
+
top_k=self._top_k,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
context = self._build_context(chunks)
|
|
34
|
+
|
|
35
|
+
prompt = self._build_prompt(
|
|
36
|
+
question=question,
|
|
37
|
+
context=context,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
answer = self._llm.generate(prompt)
|
|
41
|
+
|
|
42
|
+
duration = time.perf_counter() - start
|
|
43
|
+
|
|
44
|
+
sources = tuple(
|
|
45
|
+
Source(
|
|
46
|
+
document_id=chunk.document_id,
|
|
47
|
+
chunk_id=chunk.id,
|
|
48
|
+
metadata=chunk.metadata,
|
|
49
|
+
)
|
|
50
|
+
for chunk in chunks
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
return RagResult(
|
|
54
|
+
question=question,
|
|
55
|
+
answer=answer,
|
|
56
|
+
sources=sources,
|
|
57
|
+
duration_seconds=duration,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def _build_context(chunks) -> str:
|
|
62
|
+
|
|
63
|
+
return "\n\n".join(
|
|
64
|
+
chunk.text
|
|
65
|
+
for chunk in chunks
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def _build_prompt(
|
|
70
|
+
question: str,
|
|
71
|
+
context: str,
|
|
72
|
+
) -> str:
|
|
73
|
+
|
|
74
|
+
return f"""
|
|
75
|
+
Use the following context to answer the question.
|
|
76
|
+
|
|
77
|
+
Context:
|
|
78
|
+
{context}
|
|
79
|
+
|
|
80
|
+
Question:
|
|
81
|
+
{question}
|
|
82
|
+
|
|
83
|
+
Answer:
|
|
84
|
+
""".strip()
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from yoga1290.rag.domain.ports.document_parser import DocumentParser
|
|
2
|
+
from yoga1290.rag.domain.ports.document_source import DocumentSource
|
|
3
|
+
|
|
4
|
+
class DocumentService:
|
|
5
|
+
def __init__(
|
|
6
|
+
self,
|
|
7
|
+
source: DocumentSource,
|
|
8
|
+
parser: DocumentParser,
|
|
9
|
+
) -> None:
|
|
10
|
+
self.source = source
|
|
11
|
+
self.parser = parser
|
|
12
|
+
|
|
13
|
+
def process(self, document_id: str):
|
|
14
|
+
document = self.source.get(document_id)
|
|
15
|
+
return self.parser.parse(document)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from yoga1290.rag.domain.ports.embedder import Embedder
|
|
2
|
+
from yoga1290.rag.domain.ports.vector_store import VectorStore
|
|
3
|
+
|
|
4
|
+
class SearchService:
|
|
5
|
+
def __init__(
|
|
6
|
+
self,
|
|
7
|
+
embedder: Embedder,
|
|
8
|
+
vector_store: VectorStore,
|
|
9
|
+
) -> None:
|
|
10
|
+
self.embedder = embedder
|
|
11
|
+
self.vector_store = vector_store
|
|
12
|
+
|
|
13
|
+
def search(
|
|
14
|
+
self,
|
|
15
|
+
query: str,
|
|
16
|
+
*,
|
|
17
|
+
top_k: int = 10,
|
|
18
|
+
filters: dict[str, object] | None = None,
|
|
19
|
+
):
|
|
20
|
+
embedding = self.embedder.embed(query)
|
|
21
|
+
return self.vector_store.search(
|
|
22
|
+
embedding,
|
|
23
|
+
top_k=top_k,
|
|
24
|
+
filters=filters,
|
|
25
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
@dataclass(frozen=True)
|
|
4
|
+
class Settings:
|
|
5
|
+
app_env: str = "local"
|
|
6
|
+
ai_provider: str = "local"
|
|
7
|
+
parser_provider: str = "docling"
|
|
8
|
+
extractor_provider: str = "qwen"
|
|
9
|
+
classifier_provider: str = "qwen"
|
|
10
|
+
search_preparer_provider: str = "local"
|
|
11
|
+
embedding_provider: str = "qwen"
|
|
12
|
+
vector_store_provider: str = "pgvector"
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from .document import Document
|
|
2
|
+
from .parsed_document import ParsedDocument, ParsedPage, ParsedTable
|
|
3
|
+
from .extracted_data import ExtractedData
|
|
4
|
+
from .classification import Classification, ClassificationLabel
|
|
5
|
+
from .search_document import SearchDocument
|
|
6
|
+
from .chunk import Chunk
|
|
7
|
+
from .search_result import SearchResult
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"Document",
|
|
11
|
+
"ParsedDocument",
|
|
12
|
+
"ParsedPage",
|
|
13
|
+
"ParsedTable",
|
|
14
|
+
"ExtractedData",
|
|
15
|
+
"Classification",
|
|
16
|
+
"ClassificationLabel",
|
|
17
|
+
"SearchDocument",
|
|
18
|
+
"Chunk",
|
|
19
|
+
"SearchResult",
|
|
20
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Mapping, Sequence
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True, slots=True)
|
|
7
|
+
class Chunk:
|
|
8
|
+
id: str
|
|
9
|
+
document_id: str
|
|
10
|
+
text: str
|
|
11
|
+
index: int
|
|
12
|
+
embedding: Sequence[float] | None = None
|
|
13
|
+
metadata: Mapping[str, object] = field(default_factory=dict)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Mapping, Sequence
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True, slots=True)
|
|
7
|
+
class ClassificationLabel:
|
|
8
|
+
name: str
|
|
9
|
+
confidence: float | None = None
|
|
10
|
+
metadata: Mapping[str, object] = field(default_factory=dict)
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True, slots=True)
|
|
13
|
+
class Classification:
|
|
14
|
+
document_id: str
|
|
15
|
+
labels: Sequence[ClassificationLabel]
|
|
16
|
+
provider: str | None = None
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def primary_label(self) -> ClassificationLabel | None:
|
|
20
|
+
return self.labels[0] if self.labels else None
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Mapping
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True, slots=True)
|
|
9
|
+
class Document:
|
|
10
|
+
id: str | None = None
|
|
11
|
+
size_bytes: int | None = None
|
|
12
|
+
uri: str | None = None
|
|
13
|
+
content: bytes | None = None
|
|
14
|
+
filename: str = None
|
|
15
|
+
content_type: str = None
|
|
16
|
+
metadata: Mapping[str, object] = field(default_factory=dict)
|
|
17
|
+
created_at: datetime | None = None
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def suffix(self) -> str:
|
|
21
|
+
return Path(self.filename).suffix.lower()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Generic, TypeVar
|
|
6
|
+
|
|
7
|
+
T = TypeVar("T")
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True, slots=True)
|
|
10
|
+
class ExtractedData(Generic[T]):
|
|
11
|
+
document_id: str
|
|
12
|
+
data: T
|
|
13
|
+
confidence: float | None = None
|
|
14
|
+
provider: str | None = None
|
|
15
|
+
extracted_at: datetime | None = None
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Mapping, Sequence
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True, slots=True)
|
|
7
|
+
class ParsedPage:
|
|
8
|
+
page_number: int
|
|
9
|
+
text: str
|
|
10
|
+
metadata: Mapping[str, object] = field(default_factory=dict)
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True, slots=True)
|
|
13
|
+
class ParsedTable:
|
|
14
|
+
table_id: str
|
|
15
|
+
page_number: int | None
|
|
16
|
+
headers: Sequence[str] = field(default_factory=tuple)
|
|
17
|
+
rows: Sequence[Sequence[object]] = field(default_factory=tuple)
|
|
18
|
+
metadata: Mapping[str, object] = field(default_factory=dict)
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True, slots=True)
|
|
21
|
+
class ParsedDocument:
|
|
22
|
+
document_id: str
|
|
23
|
+
text: str
|
|
24
|
+
pages: Sequence[ParsedPage] = field(default_factory=tuple)
|
|
25
|
+
tables: Sequence[ParsedTable] = field(default_factory=tuple)
|
|
26
|
+
metadata: Mapping[str, object] = field(default_factory=dict)
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def page_count(self) -> int:
|
|
30
|
+
return len(self.pages)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass(frozen=True, slots=True)
|
|
6
|
+
class Source:
|
|
7
|
+
document_id: str
|
|
8
|
+
chunk_id: str
|
|
9
|
+
metadata: dict[str, Any]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True, slots=True)
|
|
13
|
+
class RagResult:
|
|
14
|
+
question: str
|
|
15
|
+
answer: str
|
|
16
|
+
sources: tuple[Source, ...]
|
|
17
|
+
duration_seconds: float
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Mapping, Sequence
|
|
5
|
+
|
|
6
|
+
from .chunk import Chunk
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True, slots=True)
|
|
9
|
+
class SearchDocument:
|
|
10
|
+
document_id: str
|
|
11
|
+
chunks: Sequence[Chunk] = field(default_factory=tuple)
|
|
12
|
+
metadata: Mapping[str, object] = field(default_factory=dict)
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def chunk_count(self) -> int:
|
|
16
|
+
return len(self.chunks)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Mapping
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True, slots=True)
|
|
7
|
+
class SearchResult:
|
|
8
|
+
chunk_id: str
|
|
9
|
+
document_id: str
|
|
10
|
+
text: str
|
|
11
|
+
score: float
|
|
12
|
+
metadata: Mapping[str, object] = field(default_factory=dict)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .document_classifier import DocumentClassifier
|
|
2
|
+
from .document_extractor import DocumentExtractor
|
|
3
|
+
from .document_parser import DocumentParser
|
|
4
|
+
from .document_source import DocumentSource
|
|
5
|
+
from .embedder import Embedder
|
|
6
|
+
from .search_preparer import SearchPreparer
|
|
7
|
+
from .vector_store import VectorStore
|
|
8
|
+
from .llm import LLM
|
|
9
|
+
from .retriever import Retriever
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"DocumentSource",
|
|
13
|
+
"DocumentParser",
|
|
14
|
+
"DocumentExtractor",
|
|
15
|
+
"DocumentClassifier",
|
|
16
|
+
"SearchPreparer",
|
|
17
|
+
"Embedder",
|
|
18
|
+
"LLM",
|
|
19
|
+
"VectorStore",
|
|
20
|
+
"Retriever",
|
|
21
|
+
]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from yoga1290.rag.domain.models.classification import Classification
|
|
2
|
+
from yoga1290.rag.domain.models.parsed_document import ParsedDocument
|
|
3
|
+
from yoga1290.rag.domain.schemas.classification_schema import ClassificationSchema
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DocumentClassifier():
|
|
7
|
+
"""
|
|
8
|
+
Classifies a document according to a domain-defined classification schema.
|
|
9
|
+
|
|
10
|
+
Implementations may use:
|
|
11
|
+
- Databricks ai_classify
|
|
12
|
+
- Qwen
|
|
13
|
+
- BERT
|
|
14
|
+
- Other classifiers
|
|
15
|
+
"""
|
|
16
|
+
def classify(
|
|
17
|
+
self,
|
|
18
|
+
document: ParsedDocument,
|
|
19
|
+
schema: ClassificationSchema,
|
|
20
|
+
) -> Classification:
|
|
21
|
+
"""
|
|
22
|
+
Classify a document.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
document: Parsed document.
|
|
26
|
+
schema: Allowed categories and classification configuration.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Classification result.
|
|
30
|
+
"""
|
|
31
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Generic, TypeVar
|
|
3
|
+
|
|
4
|
+
from yoga1290.rag.domain.models.extracted_data import ExtractedData
|
|
5
|
+
from yoga1290.rag.domain.models.parsed_document import ParsedDocument
|
|
6
|
+
from yoga1290.rag.domain.schemas.extraction_schema import ExtractionSchema
|
|
7
|
+
|
|
8
|
+
T = TypeVar("T")
|
|
9
|
+
|
|
10
|
+
class DocumentExtractor(ABC, Generic[T]):
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def extract(
|
|
13
|
+
self,
|
|
14
|
+
document: ParsedDocument,
|
|
15
|
+
schema: ExtractionSchema[T],
|
|
16
|
+
) -> ExtractedData[T]:
|
|
17
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from yoga1290.rag.domain.models.document import Document
|
|
2
|
+
from yoga1290.rag.domain.models.parsed_document import ParsedDocument
|
|
3
|
+
|
|
4
|
+
class DocumentParser():
|
|
5
|
+
"""
|
|
6
|
+
Converts a raw document into a normalized ParsedDocument.
|
|
7
|
+
|
|
8
|
+
Implementations may use:
|
|
9
|
+
- Databricks ai_parse_document
|
|
10
|
+
- Docling
|
|
11
|
+
- PyMuPDF
|
|
12
|
+
- OCR
|
|
13
|
+
- Other document parsing engines
|
|
14
|
+
"""
|
|
15
|
+
def parse(self, document: Document) -> ParsedDocument:
|
|
16
|
+
"""
|
|
17
|
+
Parse a document into a normalized representation.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
document: Raw document metadata and content reference.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
ParsedDocument containing normalized text, pages,
|
|
24
|
+
tables, images, metadata, etc.
|
|
25
|
+
|
|
26
|
+
Raises:
|
|
27
|
+
DocumentParseError: If the document cannot be parsed.
|
|
28
|
+
"""
|
|
29
|
+
raise NotImplementedError
|
|
30
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from collections.abc import Iterator
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from yoga1290.rag.domain.models.document import Document
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DocumentSource:
|
|
7
|
+
"""
|
|
8
|
+
Port for retrieving documents from an external source.
|
|
9
|
+
|
|
10
|
+
Implementations may read from:
|
|
11
|
+
- Local filesystem
|
|
12
|
+
- S3
|
|
13
|
+
- Azure Blob Storage
|
|
14
|
+
- Google Cloud Storage
|
|
15
|
+
- Databricks Volumes
|
|
16
|
+
- Database
|
|
17
|
+
- Message queue
|
|
18
|
+
- HTTP/API
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def __iter__(self) -> Iterator[Document]:
|
|
23
|
+
"""
|
|
24
|
+
Iterate over documents available from the source.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Iterator of Document objects.
|
|
28
|
+
"""
|
|
29
|
+
raise NotImplementedError
|