longparser 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.
@@ -0,0 +1,157 @@
1
+ """LlamaIndex integration adapter for LongParser.
2
+
3
+ Provides ``LongParserReader``, a LlamaIndex-compatible document reader
4
+ that wraps the LongParser extraction pipeline.
5
+
6
+ Install the extra to use this adapter::
7
+
8
+ pip install clean_rag[llamaindex]
9
+
10
+ Usage::
11
+
12
+ from longparser.integrations.llamaindex import LongParserReader
13
+
14
+ reader = LongParserReader()
15
+ docs = reader.load_data("report.pdf") # list[llama_index.core.Document]
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from pathlib import Path
21
+ from typing import Optional, TYPE_CHECKING
22
+
23
+ if TYPE_CHECKING:
24
+ from llama_index.core import Document as LIDocument
25
+
26
+ from ..schemas import ProcessingConfig, ChunkingConfig
27
+
28
+ _INSTALL_MSG = (
29
+ "llama-index-core is required for the LlamaIndex adapter. "
30
+ "Install it with: pip install clean_rag[llamaindex]"
31
+ )
32
+
33
+
34
+ def _import_llamaindex():
35
+ """Lazy import llama-index-core with a clear install message."""
36
+ try:
37
+ from llama_index.core.readers.base import BaseReader
38
+ from llama_index.core import Document as LIDocument
39
+ return BaseReader, LIDocument
40
+ except ImportError as exc:
41
+ raise ImportError(_INSTALL_MSG) from exc
42
+
43
+
44
+ class LongParserReader:
45
+ """LlamaIndex document reader backed by the LongParser pipeline.
46
+
47
+ Converts a file (PDF, DOCX, PPTX, XLSX, CSV) into a list of
48
+ LlamaIndex ``Document`` objects — one per chunk (if chunking is
49
+ enabled) or one per block.
50
+
51
+ Parameters
52
+ ----------
53
+ config:
54
+ LongParser ``ProcessingConfig``. Uses defaults if ``None``.
55
+ chunking_config:
56
+ LongParser ``ChunkingConfig``. If provided, the reader yields
57
+ one ``Document`` per chunk; otherwise one per block.
58
+ tesseract_lang:
59
+ Languages for Tesseract OCR (e.g. ``["eng", "urd"]``).
60
+ tessdata_path:
61
+ Path to tessdata directory.
62
+ """
63
+
64
+ def __init__(
65
+ self,
66
+ *,
67
+ config: Optional[ProcessingConfig] = None,
68
+ chunking_config: Optional[ChunkingConfig] = None,
69
+ tesseract_lang: list[str] | None = None,
70
+ tessdata_path: str | None = None,
71
+ ) -> None:
72
+ # Validate llama-index is available at construction time
73
+ BaseReader, _ = _import_llamaindex()
74
+ self._BaseReader = BaseReader
75
+
76
+ self.config = config or ProcessingConfig()
77
+ self.chunking_config = chunking_config
78
+ self.tesseract_lang = tesseract_lang
79
+ self.tessdata_path = tessdata_path
80
+
81
+ # ---- LlamaIndex interface ------------------------------------------------
82
+
83
+ def load_data(
84
+ self,
85
+ file: str | Path,
86
+ extra_info: dict | None = None,
87
+ ) -> list["LIDocument"]:
88
+ """Load data from a file and return LlamaIndex ``Document`` objects.
89
+
90
+ Parameters
91
+ ----------
92
+ file:
93
+ Path to the input file.
94
+ extra_info:
95
+ Additional metadata to merge into every document.
96
+
97
+ Returns
98
+ -------
99
+ list[Document]
100
+ One ``Document`` per chunk or per block.
101
+ """
102
+ _, LIDocument = _import_llamaindex()
103
+
104
+ from ..pipeline import PipelineOrchestrator
105
+
106
+ file = Path(file)
107
+ pipeline = PipelineOrchestrator(
108
+ tesseract_lang=self.tesseract_lang,
109
+ tessdata_path=self.tessdata_path,
110
+ )
111
+ result = pipeline.process_file(file, config=self.config)
112
+
113
+ docs: list[LIDocument] = []
114
+ base_meta = {"source": str(file), **(extra_info or {})}
115
+
116
+ # If chunking is requested, yield one doc per chunk
117
+ if self.chunking_config is not None:
118
+ chunks = pipeline.chunk(result, config=self.chunking_config)
119
+ for chunk in chunks:
120
+ docs.append(
121
+ LIDocument(
122
+ text=chunk.text,
123
+ extra_info={
124
+ **base_meta,
125
+ "chunk_id": chunk.chunk_id,
126
+ "chunk_type": chunk.chunk_type,
127
+ "section_path": chunk.section_path,
128
+ "page_numbers": chunk.page_numbers,
129
+ "token_count": chunk.token_count,
130
+ "equation_detected": chunk.equation_detected,
131
+ },
132
+ )
133
+ )
134
+ return docs
135
+
136
+ # Otherwise, yield one doc per block
137
+ for page in result.document.pages:
138
+ for block in page.blocks:
139
+ docs.append(
140
+ LIDocument(
141
+ text=block.text,
142
+ extra_info={
143
+ **base_meta,
144
+ "block_id": block.block_id,
145
+ "block_type": block.type.value,
146
+ "heading_level": block.heading_level,
147
+ "hierarchy_path": block.hierarchy_path,
148
+ "page_number": page.page_number,
149
+ "confidence": block.confidence.overall,
150
+ },
151
+ )
152
+ )
153
+
154
+ return docs
155
+
156
+
157
+ __all__ = ["LongParserReader"]
@@ -0,0 +1,8 @@
1
+ """Pipeline stages for LongParser document processing."""
2
+
3
+ from .orchestrator import PipelineOrchestrator, PipelineResult
4
+
5
+ __all__ = [
6
+ "PipelineOrchestrator",
7
+ "PipelineResult",
8
+ ]
@@ -0,0 +1,230 @@
1
+ """Simple pipeline orchestrator for LongParser."""
2
+
3
+ from pathlib import Path
4
+ from dataclasses import dataclass, field
5
+ from typing import Optional, List
6
+ import time
7
+ import logging
8
+ import json
9
+
10
+ from ..schemas import Document, ProcessingConfig, JobRequest, BlockType, ChunkingConfig, Chunk
11
+ from ..extractors import DoclingExtractor
12
+ from ..extractors.docling_extractor import HierarchyChunk
13
+ from ..chunkers import HybridChunker
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ @dataclass
19
+ class PipelineResult:
20
+ """Pipeline execution result."""
21
+ document: Document
22
+ hierarchy: List[HierarchyChunk]
23
+ processing_time_seconds: float
24
+ chunks: List[Chunk] = field(default_factory=list)
25
+
26
+ @property
27
+ def total_blocks(self) -> int:
28
+ return sum(len(p.blocks) for p in self.document.pages)
29
+
30
+
31
+ class PipelineOrchestrator:
32
+ """
33
+ Simple pipeline orchestrator using Docling.
34
+
35
+ Flow:
36
+ 1. Docling extracts with Tesseract CLI OCR
37
+ 2. Layout analysis detects structure
38
+ 3. HierarchicalChunker preserves heading hierarchy
39
+ """
40
+
41
+ def __init__(self, tesseract_lang: List[str] = None, tessdata_path: str = None, force_full_page_ocr: bool = False):
42
+ """
43
+ Initialize pipeline.
44
+
45
+ Args:
46
+ tesseract_lang: Languages for Tesseract OCR (default: ["eng"])
47
+ tessdata_path: Path to tessdata directory with language models and configs.
48
+ force_full_page_ocr: If True, OCR entire page even if embedded text exists.
49
+ """
50
+ self.extractor = DoclingExtractor(
51
+ tesseract_lang=tesseract_lang,
52
+ tessdata_path=tessdata_path,
53
+ force_full_page_ocr=force_full_page_ocr,
54
+ )
55
+
56
+ def process(self, request: JobRequest) -> PipelineResult:
57
+ """Process a document."""
58
+ start_time = time.time()
59
+
60
+ file_path = Path(request.file_path)
61
+ config = request.config
62
+
63
+ logger.info(f"Processing: {file_path.name}")
64
+
65
+ # Extract document
66
+ document, meta = self.extractor.extract(file_path, config)
67
+
68
+ # Get hierarchy
69
+ hierarchy = self.extractor.get_hierarchy(file_path, config)
70
+
71
+ processing_time = time.time() - start_time
72
+ logger.info(f"Completed in {processing_time:.2f}s")
73
+
74
+ return PipelineResult(
75
+ document=document,
76
+ hierarchy=hierarchy,
77
+ processing_time_seconds=processing_time,
78
+ )
79
+
80
+ def process_file(
81
+ self,
82
+ file_path: str | Path,
83
+ config: Optional[ProcessingConfig] = None,
84
+ ) -> PipelineResult:
85
+ """Convenience method to process a file directly."""
86
+ request = JobRequest(
87
+ file_path=str(file_path),
88
+ config=config or ProcessingConfig(),
89
+ )
90
+ return self.process(request)
91
+
92
+ def export_to_markdown(self, result: PipelineResult, output_path: Path) -> Path:
93
+ """Export document to Markdown."""
94
+ output_path = Path(output_path)
95
+ output_path.mkdir(parents=True, exist_ok=True)
96
+
97
+ md_path = output_path / "document.md"
98
+ md_content = self.extractor.to_markdown(result.document)
99
+
100
+ with open(md_path, "w") as f:
101
+ f.write(md_content)
102
+
103
+ return md_path
104
+
105
+ def export_hierarchy(self, result: PipelineResult, output_path: Path) -> Path:
106
+ """Export hierarchy to JSON."""
107
+ output_path = Path(output_path)
108
+ output_path.mkdir(parents=True, exist_ok=True)
109
+
110
+ hierarchy_path = output_path / "hierarchy.json"
111
+ hierarchy_data = [
112
+ {
113
+ "text": h.text[:200], # Truncate for readability
114
+ "heading_path": h.heading_path,
115
+ "level": h.level,
116
+ "page": h.page_number,
117
+ }
118
+ for h in result.hierarchy
119
+ ]
120
+
121
+ with open(hierarchy_path, "w") as f:
122
+ json.dump(hierarchy_data, f, indent=2)
123
+
124
+ return hierarchy_path
125
+
126
+ def export_results(self, result: PipelineResult, output_dir: Path) -> dict:
127
+ """
128
+ Export results in the format expected by the user (blocks.json, manifest.json).
129
+
130
+ Args:
131
+ result: Pipeline execution result
132
+ output_dir: Directory to save outputs
133
+
134
+ Returns:
135
+ Dictionary of created files
136
+ """
137
+ output_dir = Path(output_dir)
138
+ output_dir.mkdir(parents=True, exist_ok=True)
139
+ created_files = {}
140
+
141
+ # 1. blocks.json - Flattened list of blocks from all pages
142
+ all_blocks = []
143
+ total_tables = 0
144
+
145
+ for page in result.document.pages:
146
+ for block in page.blocks:
147
+ # Exclude confidence from public output
148
+ block_dict = block.model_dump(exclude={"confidence"})
149
+ # Ensure compatibility with expected format
150
+ if block.type == BlockType.TABLE:
151
+ total_tables += 1
152
+ all_blocks.append(block_dict)
153
+
154
+ blocks_path = output_dir / "blocks.json"
155
+ with open(blocks_path, "w") as f:
156
+ json.dump(all_blocks, f, indent=2, default=str)
157
+ created_files["blocks"] = blocks_path
158
+
159
+ # 2. manifest.json - Processing metadata
160
+ manifest = {
161
+ "source_file": result.document.metadata.source_file,
162
+ "file_hash": result.document.metadata.file_hash,
163
+ "total_pages": result.document.metadata.total_pages,
164
+ "total_blocks": len(all_blocks),
165
+ "total_tables": total_tables,
166
+ "processing_time_seconds": result.processing_time_seconds,
167
+ "stages_completed": [
168
+ "stage1_extraction",
169
+ "stage2_validation",
170
+ "stage3_reprocess",
171
+ "stage4_enrichment",
172
+ "stage5_verification"
173
+ ],
174
+ "verification": {
175
+ "auto_accepted": False,
176
+ "needs_hitl_review": True,
177
+ "low_confidence_pages": [],
178
+ "low_confidence_tables": []
179
+ }
180
+ }
181
+
182
+ manifest_path = output_dir / "manifest.json"
183
+ with open(manifest_path, "w") as f:
184
+ json.dump(manifest, f, indent=2, default=str)
185
+ created_files["manifest"] = manifest_path
186
+
187
+ # 3. document.md - Markdown representation
188
+ md_path = self.export_to_markdown(result, output_dir)
189
+ created_files["markdown"] = md_path
190
+
191
+ # 4. Images
192
+ images_dir = output_dir / "images"
193
+ images = self.save_images(images_dir)
194
+ created_files["images"] = images
195
+
196
+ return created_files
197
+
198
+ def chunk(self, result: PipelineResult, config: Optional[ChunkingConfig] = None) -> List[Chunk]:
199
+ """
200
+ Run hybrid chunking on a pipeline result.
201
+
202
+ Args:
203
+ result: Pipeline execution result with extracted blocks
204
+ config: Chunking configuration (uses defaults if None)
205
+
206
+ Returns:
207
+ List of RAG-optimized chunks
208
+ """
209
+ chunker = HybridChunker(config or ChunkingConfig())
210
+ all_blocks = result.document.all_blocks
211
+ chunks = chunker.chunk(all_blocks)
212
+ result.chunks = chunks
213
+ return chunks
214
+
215
+ def export_chunks(self, result: PipelineResult, output_dir: Path) -> Path:
216
+ """Export chunks to JSON."""
217
+ output_dir = Path(output_dir)
218
+ output_dir.mkdir(parents=True, exist_ok=True)
219
+
220
+ chunks_path = output_dir / "chunks.json"
221
+ chunks_data = [c.model_dump() for c in result.chunks]
222
+ with open(chunks_path, "w") as f:
223
+ json.dump(chunks_data, f, indent=2, default=str)
224
+
225
+ logger.info(f"Exported {len(result.chunks)} chunks to {chunks_path}")
226
+ return chunks_path
227
+
228
+ def save_images(self, output_dir: Path) -> List[Path]:
229
+ """Save extracted images."""
230
+ return self.extractor.save_images(output_dir)
longparser/py.typed ADDED
File without changes
longparser/schemas.py ADDED
@@ -0,0 +1,247 @@
1
+ """Pydantic schemas for LongParser document processing pipeline."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import uuid
6
+ from enum import Enum
7
+ from typing import Optional
8
+ from pydantic import BaseModel, Field
9
+
10
+
11
+ class BlockType(str, Enum):
12
+ """Types of document blocks."""
13
+ HEADING = "heading"
14
+ PARAGRAPH = "paragraph"
15
+ LIST_ITEM = "list_item"
16
+ TABLE = "table"
17
+ FIGURE = "figure"
18
+ CAPTION = "caption"
19
+ FOOTER = "footer"
20
+ HEADER = "header"
21
+ EQUATION = "equation"
22
+ CODE = "code"
23
+
24
+
25
+ class ExtractorType(str, Enum):
26
+ """Document extraction engines."""
27
+ DOCLING = "docling"
28
+ SURYA = "surya"
29
+ MARKER = "marker"
30
+ NATIVE_PDF = "native_pdf"
31
+ PADDLE = "paddle"
32
+
33
+
34
+ class BoundingBox(BaseModel):
35
+ """Bounding box coordinates (PDF coordinate system)."""
36
+ x0: float
37
+ y0: float
38
+ x1: float
39
+ y1: float
40
+
41
+
42
+ class Provenance(BaseModel):
43
+ """Traceability information for a block."""
44
+ source_file: str
45
+ page_number: int
46
+ bbox: BoundingBox
47
+ extractor: ExtractorType
48
+ extractor_version: str = "1.0.0"
49
+ pipeline_version: str = "1.0.0"
50
+
51
+
52
+ class Confidence(BaseModel):
53
+ """Confidence scores for extraction quality."""
54
+ overall: float = Field(ge=0.0, le=1.0)
55
+ text_confidence: float = Field(default=1.0, ge=0.0, le=1.0)
56
+ layout_confidence: float = Field(default=1.0, ge=0.0, le=1.0)
57
+ table_confidence: Optional[float] = Field(default=None, ge=0.0, le=1.0)
58
+
59
+
60
+ class BlockFlags(BaseModel):
61
+ """Flags indicating block status."""
62
+ needs_review: bool = False
63
+ repaired: bool = False
64
+ fallback_used: bool = False
65
+ excluded_from_rag: bool = False
66
+
67
+
68
+ class TableCell(BaseModel):
69
+ """Individual cell in a table."""
70
+ row_index: int = Field(alias="r0")
71
+ col_index: int = Field(alias="c0")
72
+ row_span: int = Field(default=1, alias="rspan")
73
+ col_span: int = Field(default=1, alias="cspan")
74
+ text: str = ""
75
+ bbox: Optional[BoundingBox] = None
76
+ confidence: float = Field(default=1.0, ge=0.0, le=1.0)
77
+
78
+ class Config:
79
+ populate_by_name = True
80
+
81
+
82
+ class Table(BaseModel):
83
+ """Table structure with cells and metadata."""
84
+ table_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
85
+ n_rows: int
86
+ n_cols: int
87
+ cells: list[TableCell] = Field(default_factory=list)
88
+ table_confidence: float = Field(default=1.0, ge=0.0, le=1.0)
89
+ csv_path: Optional[str] = None
90
+ html_path: Optional[str] = None
91
+
92
+
93
+ class Block(BaseModel):
94
+ """Core document element with full provenance."""
95
+ block_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
96
+ type: BlockType
97
+ text: str = ""
98
+ order_index: int = 0
99
+ heading_level: Optional[int] = Field(default=None, description="Heading level (1-6) for heading blocks, inferred by Docling")
100
+ indent_level: int = Field(default=0, description="Bullet nesting depth (0=top, 1=sub, 2=sub-sub). Used for PPTX list items.")
101
+ hierarchy_path: list[str] = Field(default_factory=list)
102
+ refs_out: list[str] = Field(default_factory=list)
103
+ refs_in: list[str] = Field(default_factory=list)
104
+ provenance: Provenance
105
+ confidence: Confidence
106
+ flags: BlockFlags = Field(default_factory=BlockFlags)
107
+ table: Optional[Table] = None
108
+ image_path: Optional[str] = None
109
+ bbox_px: Optional[tuple] = Field(default=None, description="Pixel-space bounding box (x0,y0,x1,y1) for MFD dedup")
110
+
111
+
112
+ class PageProfile(BaseModel):
113
+ """Validation profile for a single page."""
114
+ page_number: int
115
+ needs_reprocess: bool = False
116
+ validation_errors: list[str] = Field(default_factory=list)
117
+ layout_confidence: float = Field(default=1.0, ge=0.0, le=1.0)
118
+ table_confidence: Optional[float] = None
119
+ has_rtl: bool = False
120
+ has_math: bool = False
121
+
122
+
123
+ class Page(BaseModel):
124
+ """Single page with blocks and metadata."""
125
+ page_number: int
126
+ width: float
127
+ height: float
128
+ blocks: list[Block] = Field(default_factory=list)
129
+ rendered_image_path: Optional[str] = None
130
+ profile: Optional[PageProfile] = None
131
+
132
+
133
+ class DocumentMetadata(BaseModel):
134
+ """Document-level metadata."""
135
+ source_file: str
136
+ file_hash: str = ""
137
+ language: Optional[str] = None
138
+ total_pages: int = 0
139
+ academic_mode: bool = False
140
+ rtl_hint: bool = False
141
+
142
+
143
+ class Document(BaseModel):
144
+ """Complete processed document."""
145
+ doc_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
146
+ metadata: DocumentMetadata
147
+ pages: list[Page] = Field(default_factory=list)
148
+
149
+ @property
150
+ def all_blocks(self) -> list[Block]:
151
+ """Get all blocks across all pages."""
152
+ return [block for page in self.pages for block in page.blocks]
153
+
154
+ @property
155
+ def all_tables(self) -> list[Table]:
156
+ """Get all tables from table blocks."""
157
+ return [
158
+ block.table
159
+ for block in self.all_blocks
160
+ if block.type == BlockType.TABLE and block.table
161
+ ]
162
+
163
+
164
+ class ProcessingConfig(BaseModel):
165
+ """Configuration for pipeline execution."""
166
+ academic_mode: bool = False
167
+ rtl_hint: bool = False
168
+ do_ocr: bool = True
169
+ formula_ocr: bool = True # Independent from do_ocr — runs pix2tex even when text OCR is off
170
+ do_table_structure: bool = True
171
+ export_images: bool = True
172
+ images_output_dir: Optional[str] = None
173
+ layout_confidence_threshold: float = 0.7
174
+ table_confidence_threshold: float = 0.75
175
+ ocr_noise_threshold: float = 0.15
176
+ enable_fallback: bool = True
177
+ # Smart extraction fallback options
178
+ prefer_initial_on_degradation: bool = True # Keep original if fallback degrades quality
179
+ ocr_backend: str = "easyocr" # easyocr | tesseract | rapidocr
180
+ ocr_use_gpu: bool = True
181
+ # Force full page OCR for better text segmentation in two-column layouts
182
+ force_full_page_ocr: bool = False
183
+ # Exclude page headers and footers from extraction output
184
+ exclude_page_headers_footers: bool = True
185
+
186
+ # Formula extraction mode: "full" (slow, best LaTeX), "fast" (Unicode text), "smart" (hybrid)
187
+ formula_mode: str = "smart"
188
+ # Smart mode safety caps
189
+ smart_max_pages: int = 5 # Max pages to enrich in smart mode before fallback to fast
190
+ smart_max_ratio: float = 0.2 # Max ratio of pages (enriched/total) before fallback
191
+ smart_max_equations: int = 25 # Max equations to OCR per document (circuit breaker)
192
+ smart_max_ocr_seconds: float = 300.0 # Total OCR time budget (must fit within arq timeout)
193
+
194
+
195
+ class ExtractionMetadata(BaseModel):
196
+ """Metadata from smart extraction strategy."""
197
+ strategy_used: str = "standard" # standard | force_full_page_ocr
198
+ initial_low_grade: Optional[str] = None
199
+ fallback_low_grade: Optional[str] = None
200
+ improved: bool = False
201
+ fallback_degraded: bool = False
202
+ reprocessed_pages: list[int] = Field(default_factory=list)
203
+ ocr_backend_used: Optional[str] = None
204
+ reasons: list[str] = Field(default_factory=list)
205
+
206
+
207
+ class ChunkingConfig(BaseModel):
208
+ """Configuration for hybrid chunking."""
209
+ max_tokens: int = 512
210
+ min_tokens: int = 100
211
+ overlap_blocks: int = 1
212
+ table_rows_per_chunk: int = 15
213
+ exclude_headers_footers: bool = True
214
+ detect_equations: bool = True
215
+ table_chunk_format: str = "row_record" # "row_record" | "pipe"
216
+ generate_schema_chunks: bool = True
217
+ wide_table_col_threshold: int = 25
218
+
219
+
220
+ class Chunk(BaseModel):
221
+ """A RAG-optimized chunk with full provenance."""
222
+ chunk_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
223
+ text: str
224
+ token_count: int
225
+ chunk_type: str # "section" | "table" | "table_schema" | "list" | "equation" | "continuation"
226
+ section_path: list[str] = Field(default_factory=list)
227
+ page_numbers: list[int] = Field(default_factory=list)
228
+ block_ids: list[str] = Field(default_factory=list)
229
+ overlap_with_previous: bool = False
230
+ equation_detected: bool = False
231
+ metadata: dict = Field(default_factory=dict) # row_start, row_end, sheet, col_band
232
+
233
+
234
+ class JobRequest(BaseModel):
235
+ """Request to process a document."""
236
+ job_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
237
+ file_path: str
238
+ config: ProcessingConfig = Field(default_factory=ProcessingConfig)
239
+
240
+
241
+ class JobResult(BaseModel):
242
+ """Result of document processing."""
243
+ job_id: str
244
+ document: Document
245
+ success: bool = True
246
+ errors: list[str] = Field(default_factory=list)
247
+ processing_time_seconds: float = 0.0
@@ -0,0 +1,22 @@
1
+ """LongParser REST API server package.
2
+
3
+ Start the server::
4
+
5
+ uv run uvicorn longparser.server.app:app --reload --port 8000
6
+
7
+ Subpackages:
8
+ - :mod:`~longparser.server.chat` — RAG chat engine, retriever, LangGraph HITL
9
+ - :mod:`~longparser.server.routers` — modular FastAPI route groups (future)
10
+
11
+ Key modules:
12
+ - :mod:`~longparser.server.app` — FastAPI application factory and all routes
13
+ - :mod:`~longparser.server.db` — Motor async MongoDB database layer
14
+ - :mod:`~longparser.server.queue` — ARQ async job queue (Redis-backed)
15
+ - :mod:`~longparser.server.worker` — ARQ background worker definitions
16
+ - :mod:`~longparser.server.embeddings` — multi-backend embedding engine
17
+ - :mod:`~longparser.server.vectorstores` — Chroma / FAISS / Qdrant adapters
18
+ """
19
+
20
+ from .app import app
21
+
22
+ __all__ = ["app"]