mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-08-30 04:40:16 +00:00
Compare commits
4
Commits
embeddings
..
parse
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
957c122116 | ||
|
|
31ca2e4446 | ||
|
|
b08c13364b | ||
|
|
01dd0c97ab |
@@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
- Fixed model switching bug in `TextEmbedder` where internal state was not cleared, preventing dynamic updates between `fastembed` and `sentence_transformers` (#160).
|
||||||
|
- Implemented model-intrinsic embedding dimension detection in `TextEmbedder` to ensure consistency between models and vector databases.
|
||||||
|
- Updated `set_model` to properly refresh configuration and dimensions during model switches.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Added comprehensive unit test suite `tests/embeddings/test_model_switching.py` for verifying dynamic model transitions and dimension updates.
|
||||||
- Fixed `TypeError: unhashable type: 'Entity'` in `GraphAnalyzer` when processing graphs with raw `Entity` objects or dictionaries in relationships (#159).
|
- Fixed `TypeError: unhashable type: 'Entity'` in `GraphAnalyzer` when processing graphs with raw `Entity` objects or dictionaries in relationships (#159).
|
||||||
- Robustified ID extraction across `CentralityCalculator`, `CommunityDetector`, and `ConnectivityAnalyzer` to handle various entity formats.
|
- Robustified ID extraction across `CentralityCalculator`, `CommunityDetector`, and `ConnectivityAnalyzer` to handle various entity formats.
|
||||||
- Improved `Entity` class hashability and equality logic in `utils/types.py`.
|
- Improved `Entity` class hashability and equality logic in `utils/types.py`.
|
||||||
|
|||||||
@@ -271,9 +271,13 @@ parsed = parser.parse("document.pdf", format="auto")
|
|||||||
|
|
||||||
# Enhanced parsing with Docling (recommended for complex layouts/tables)
|
# Enhanced parsing with Docling (recommended for complex layouts/tables)
|
||||||
# Requires: pip install docling
|
# Requires: pip install docling
|
||||||
docling_parser = DoclingParser()
|
docling_parser = DoclingParser(enable_ocr=True)
|
||||||
docling_result = docling_parser.parse("complex_table.pdf")
|
result = docling_parser.parse("complex_table.pdf")
|
||||||
print(f"Extracted {len(docling_result.tables)} tables")
|
|
||||||
|
print(f"Text (Markdown): {result['full_text'][:100]}...")
|
||||||
|
print(f"Extracted {len(result['tables'])} tables")
|
||||||
|
for i, table in enumerate(result['tables']):
|
||||||
|
print(f"Table {i+1} headers: {table.get('headers', [])}")
|
||||||
|
|
||||||
# Normalize text
|
# Normalize text
|
||||||
normalizer = TextNormalizer()
|
normalizer = TextNormalizer()
|
||||||
|
|||||||
@@ -138,6 +138,39 @@ async for item in feed_processor.stream_items():
|
|||||||
knowledge_graph.add_triplets(core.generate_triplets(semantics))
|
knowledge_graph.add_triplets(core.generate_triplets(semantics))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 🦆 Docling Clear Code Example
|
||||||
|
|
||||||
|
High-accuracy document parsing with structural understanding:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from semantica.parse import DoclingParser
|
||||||
|
|
||||||
|
# 1. Initialize DoclingParser
|
||||||
|
# Docling provides superior table extraction and structure understanding
|
||||||
|
# Requires: pip install docling
|
||||||
|
parser = DoclingParser(
|
||||||
|
enable_ocr=True, # Enable OCR for scanned documents
|
||||||
|
export_format="markdown" # Options: "markdown", "html", "json"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2. Parse a complex document
|
||||||
|
# Supports PDF, DOCX, PPTX, XLSX, HTML, and images
|
||||||
|
result = parser.parse("complex_invoice.pdf")
|
||||||
|
|
||||||
|
# 3. Access structured content
|
||||||
|
print(f"Content (Markdown):\n{result['full_text']}")
|
||||||
|
|
||||||
|
# 4. Extract and iterate over tables with high precision
|
||||||
|
for i, table in enumerate(result['tables']):
|
||||||
|
print(f"\nTable {i+1}:")
|
||||||
|
print(f"Headers: {table.get('headers', [])}")
|
||||||
|
print(f"Data rows: {len(table.get('rows', []))}")
|
||||||
|
|
||||||
|
# 5. Get document metadata
|
||||||
|
metadata = result['metadata']
|
||||||
|
print(f"\nMetadata: {metadata.get('title')} ({result.get('total_pages')} pages)")
|
||||||
|
```
|
||||||
|
|
||||||
### 📊 Structured Data Processing Module
|
### 📊 Structured Data Processing Module
|
||||||
|
|
||||||
Handle structured and semi-structured data formats:
|
Handle structured and semi-structured data formats:
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
# Docling Integration
|
||||||
|
|
||||||
|
Semantica features a native integration with **Docling**, the powerful document parsing library that excels at extracting structured data from complex documents like PDFs, DOCX, and PPTX.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Docling is integrated into Semantica's `parse` module via the `DoclingParser`. This allows you to seamlessly convert unstructured documents into semantic structures that can be indexed, searched, and analyzed within the Semantica framework.
|
||||||
|
|
||||||
|
- 📖 **Semantica Docling Integration Docs**: [Reference Guide](../reference/parse.md)
|
||||||
|
- 💻 **Semantica Docling Integration GitHub**: [Source Code](https://github.com/Hawksight-AI/semantica/blob/main/semantica/parse/docling_parser.py)
|
||||||
|
- 🧑🏽🍳 **Semantica Docling Integration Example**: [Docling Clear Code Example](../CodeExamples.md#docling-clear-code-example)
|
||||||
|
- 📦 **Semantica Docling Integration PyPI**: [Installation Guide](../installation.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📖 Integration Documentation
|
||||||
|
|
||||||
|
The `DoclingParser` provides a high-level interface for document processing. It supports:
|
||||||
|
|
||||||
|
* **Multi-format support**: PDF, DOCX, PPTX, HTML, and more.
|
||||||
|
* **Table Extraction**: High-fidelity table extraction with header detection.
|
||||||
|
* **OCR Support**: Built-in Optical Character Recognition for scanned documents.
|
||||||
|
* **Markdown Export**: Clean markdown output optimized for LLM consumption.
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
```python
|
||||||
|
from semantica.parse import DoclingParser
|
||||||
|
|
||||||
|
# Initialize with OCR enabled
|
||||||
|
parser = DoclingParser(enable_ocr=True)
|
||||||
|
|
||||||
|
# Parse a complex document
|
||||||
|
result = parser.parse("financial_report.pdf")
|
||||||
|
|
||||||
|
# Access the structured data
|
||||||
|
print(f"Content: {result['full_text'][:200]}...")
|
||||||
|
print(f"Found {len(result['tables'])} tables")
|
||||||
|
```
|
||||||
|
|
||||||
|
For more details, see the [Parse Reference](../reference/parse.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧑🏽🍳 Integration Example
|
||||||
|
|
||||||
|
We provide a detailed cookbook and clear code examples to help you get started quickly.
|
||||||
|
|
||||||
|
### Docling Clear Code Example
|
||||||
|
|
||||||
|
```python
|
||||||
|
from semantica.parse import DoclingParser
|
||||||
|
import json
|
||||||
|
|
||||||
|
# 1. Initialize the Docling Parser with advanced config
|
||||||
|
parser = DoclingParser(
|
||||||
|
enable_ocr=True,
|
||||||
|
export_format="markdown"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2. Parse a complex document (PDF, DOCX, etc.)
|
||||||
|
result = parser.parse("complex_invoice.pdf")
|
||||||
|
|
||||||
|
# 3. Access the clean Markdown text
|
||||||
|
print(f"--- Document Content ---\n{result['full_text']}")
|
||||||
|
|
||||||
|
# 4. Iterate through extracted tables
|
||||||
|
for i, table in enumerate(result['tables']):
|
||||||
|
print(f"\nTable {i+1} headers: {table.get('headers', [])}")
|
||||||
|
# Access table rows as a list of lists
|
||||||
|
for row in table.get('rows', [])[:3]: # Print first 3 rows
|
||||||
|
print(f" Row: {row}")
|
||||||
|
|
||||||
|
# 5. Get document metadata
|
||||||
|
metadata = result['metadata']
|
||||||
|
print(f"\n--- Metadata ---\nTitle: {metadata.get('title')}")
|
||||||
|
print(f"Total Pages: {result.get('total_pages')}")
|
||||||
|
```
|
||||||
|
|
||||||
|
See more in our [Code Examples](../CodeExamples.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💻 GitHub Source
|
||||||
|
|
||||||
|
The integration is open-source and available on GitHub. You can explore the implementation, contribute improvements, or report issues.
|
||||||
|
|
||||||
|
- [docling_parser.py](https://github.com/Hawksight-AI/semantica/blob/main/semantica/parse/docling_parser.py) - The core implementation of the Docling integration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 PyPI & Installation
|
||||||
|
|
||||||
|
Docling is an optional but highly recommended dependency for Semantica. You can install it along with Semantica or as a separate requirement.
|
||||||
|
|
||||||
|
### Install via Semantica
|
||||||
|
```bash
|
||||||
|
pip install semantica
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install Docling manually
|
||||||
|
If you are working in a custom environment:
|
||||||
|
```bash
|
||||||
|
pip install docling
|
||||||
|
```
|
||||||
|
|
||||||
|
For full installation details, see the [Installation Guide](../installation.md).
|
||||||
@@ -159,11 +159,11 @@ parser = DoclingParser()
|
|||||||
result = parser.parse("complex_table.pdf")
|
result = parser.parse("complex_table.pdf")
|
||||||
|
|
||||||
# Access high-accuracy tables
|
# Access high-accuracy tables
|
||||||
for table in result.tables:
|
for table in result["tables"]:
|
||||||
print(table.headers)
|
print(table["headers"])
|
||||||
|
|
||||||
# Get markdown representation
|
# Get markdown representation
|
||||||
print(result.markdown)
|
print(result["full_text"])
|
||||||
```
|
```
|
||||||
|
|
||||||
### WebParser
|
### WebParser
|
||||||
|
|||||||
@@ -139,6 +139,8 @@ nav:
|
|||||||
- examples.md
|
- examples.md
|
||||||
- Code Examples: CodeExamples.md
|
- Code Examples: CodeExamples.md
|
||||||
- learning-more.md
|
- learning-more.md
|
||||||
|
- Integrations:
|
||||||
|
- Docling: integrations/docling.md
|
||||||
- Cookbook: cookbook.md
|
- Cookbook: cookbook.md
|
||||||
- Resources:
|
- Resources:
|
||||||
- community-projects.md
|
- community-projects.md
|
||||||
|
|||||||
@@ -170,17 +170,17 @@ result = parser.parse("complex_invoice.pdf")
|
|||||||
|
|
||||||
# 2. Extract structured content
|
# 2. Extract structured content
|
||||||
# result contains the full Docling document object if available
|
# result contains the full Docling document object if available
|
||||||
print(f"Extracted Text (Markdown): {result.markdown}")
|
print(f"Extracted Text (Markdown): {result['full_text']}")
|
||||||
|
|
||||||
# 3. Access extracted tables with high accuracy
|
# 3. Access extracted tables with high accuracy
|
||||||
for i, table in enumerate(result.tables):
|
for i, table in enumerate(result['tables']):
|
||||||
print(f"Table {i+1} headers: {table.headers}")
|
print(f"Table {i+1} headers: {table.get('headers', [])}")
|
||||||
print(f"Table {i+1} row count: {len(table.rows)}")
|
print(f"Table {i+1} row count: {len(table.get('rows', []))}")
|
||||||
|
|
||||||
# 4. Extract metadata
|
# 4. Extract metadata
|
||||||
metadata = result.metadata
|
metadata = result['metadata']
|
||||||
print(f"Title: {metadata.title}")
|
print(f"Title: {metadata.get('title')}")
|
||||||
print(f"Page Count: {metadata.page_count}")
|
print(f"Page Count: {metadata.get('page_count')}")
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Advanced Configuration
|
#### Advanced Configuration
|
||||||
@@ -198,7 +198,7 @@ parser = DoclingParser(
|
|||||||
|
|
||||||
# Parse with specific export format
|
# Parse with specific export format
|
||||||
result = parser.parse("scanned_document.pdf")
|
result = parser.parse("scanned_document.pdf")
|
||||||
print(f"HTML Content: {result.html}")
|
print(f"HTML Content: {result['full_text']}")
|
||||||
|
|
||||||
# Batch processing
|
# Batch processing
|
||||||
results = parser.parse_batch(["doc1.pdf", "doc2.docx"])
|
results = parser.parse_batch(["doc1.pdf", "doc2.docx"])
|
||||||
@@ -504,23 +504,22 @@ pdf_parser = PDFParser()
|
|||||||
pdf_data = pdf_parser.parse("document.pdf", extract_text=True, extract_tables=True)
|
pdf_data = pdf_parser.parse("document.pdf", extract_text=True, extract_tables=True)
|
||||||
|
|
||||||
# Access pages
|
# Access pages
|
||||||
for page_dict in pdf_data.get("pages", []):
|
for page in pdf_data.get("pages", []):
|
||||||
page = PDFPage(**page_dict)
|
print(f"Page {page['page_number']}: {len(page['text'])} characters")
|
||||||
print(f"Page {page.page_number}: {len(page.text)} characters")
|
print(f" Tables: {len(page['tables'])}")
|
||||||
print(f" Tables: {len(page.tables)}")
|
print(f" Images: {len(page['images'])}")
|
||||||
print(f" Images: {len(page.images)}")
|
|
||||||
|
|
||||||
# Access metadata
|
# Access metadata
|
||||||
metadata = PDFMetadata(**pdf_data.get("metadata", {}))
|
metadata = pdf_data.get("metadata", {})
|
||||||
print(f"Title: {metadata.title}")
|
print(f"Title: {metadata.get('title')}")
|
||||||
print(f"Author: {metadata.author}")
|
print(f"Author: {metadata.get('author')}")
|
||||||
print(f"Page Count: {metadata.page_count}")
|
print(f"Page Count: {metadata.get('page_count')}")
|
||||||
```
|
```
|
||||||
|
|
||||||
### DOCX Parser
|
### DOCX Parser
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from semantica.parse import DOCXParser, DocxSection, DocxMetadata
|
from semantica.parse import DOCXParser
|
||||||
|
|
||||||
docx_parser = DOCXParser()
|
docx_parser = DOCXParser()
|
||||||
|
|
||||||
@@ -528,15 +527,14 @@ docx_parser = DOCXParser()
|
|||||||
docx_data = docx_parser.parse("document.docx", extract_tables=True)
|
docx_data = docx_parser.parse("document.docx", extract_tables=True)
|
||||||
|
|
||||||
# Access sections
|
# Access sections
|
||||||
for section_dict in docx_data.get("sections", []):
|
for section in docx_data.get("sections", []):
|
||||||
section = DocxSection(**section_dict)
|
print(f"Section: {section['heading']} (Level {section['level']})")
|
||||||
print(f"Section: {section.heading} (Level {section.level})")
|
print(f" Content: {section['content'][:100]}...")
|
||||||
print(f" Content: {section.content[:100]}...")
|
|
||||||
|
|
||||||
# Access metadata
|
# Access metadata
|
||||||
metadata = DocxMetadata(**docx_data.get("metadata", {}))
|
metadata = docx_data.get("metadata", {})
|
||||||
print(f"Title: {metadata.title}")
|
print(f"Title: {metadata.get('title')}")
|
||||||
print(f"Author: {metadata.author}")
|
print(f"Author: {metadata.get('author')}")
|
||||||
```
|
```
|
||||||
|
|
||||||
### JSON Parser
|
### JSON Parser
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import unittest
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
from pathlib import Path
|
||||||
|
from semantica.parse.docling_parser import DoclingParser, DoclingMetadata
|
||||||
|
|
||||||
|
class TestDoclingParser(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
# Patch DOCLING_AVAILABLE to True for testing logic
|
||||||
|
self.available_patcher = patch('semantica.parse.docling_parser.DOCLING_AVAILABLE', True)
|
||||||
|
self.available_patcher.start()
|
||||||
|
|
||||||
|
# Mock the DocumentConverter
|
||||||
|
self.mock_converter_cls = patch('semantica.parse.docling_parser.DocumentConverter').start()
|
||||||
|
self.mock_converter = self.mock_converter_cls.return_value
|
||||||
|
|
||||||
|
self.parser = DoclingParser()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
patch.stopall()
|
||||||
|
|
||||||
|
def test_parse_returns_dict(self):
|
||||||
|
# Mock the result of converter.convert
|
||||||
|
mock_result = MagicMock()
|
||||||
|
mock_result.document.export_to_markdown.return_value = "# Test Content"
|
||||||
|
mock_result.document.tables = []
|
||||||
|
mock_result.document.pages = []
|
||||||
|
|
||||||
|
# Mock metadata
|
||||||
|
mock_result.input.file.name = "test.pdf"
|
||||||
|
mock_result.document.name = "test.pdf"
|
||||||
|
|
||||||
|
self.mock_converter.convert.return_value = mock_result
|
||||||
|
|
||||||
|
# Create a dummy file for Path.exists()
|
||||||
|
with patch.object(Path, 'exists', return_value=True):
|
||||||
|
result = self.parser.parse("test.pdf")
|
||||||
|
|
||||||
|
# Verify result is a dict and has expected keys
|
||||||
|
self.assertIsInstance(result, dict)
|
||||||
|
self.assertIn("full_text", result)
|
||||||
|
self.assertIn("tables", result)
|
||||||
|
self.assertIn("metadata", result)
|
||||||
|
self.assertIn("total_pages", result)
|
||||||
|
|
||||||
|
# Verify we are using dict access for tables (as per our doc fix)
|
||||||
|
self.assertIsInstance(result["tables"], list)
|
||||||
|
self.assertEqual(result["full_text"], "# Test Content")
|
||||||
|
|
||||||
|
def test_extract_text_uses_dict_access(self):
|
||||||
|
# Mock parse to return a dict
|
||||||
|
mock_parse_result = {
|
||||||
|
"full_text": "Extracted Text",
|
||||||
|
"tables": [],
|
||||||
|
"metadata": {},
|
||||||
|
"total_pages": 1
|
||||||
|
}
|
||||||
|
|
||||||
|
with patch.object(DoclingParser, 'parse', return_value=mock_parse_result):
|
||||||
|
text = self.parser.extract_text("test.pdf")
|
||||||
|
self.assertEqual(text, "Extracted Text")
|
||||||
|
|
||||||
|
def test_extract_tables_uses_dict_access(self):
|
||||||
|
# Mock parse to return a dict
|
||||||
|
mock_tables = [{"headers": ["Col1"], "rows": [["Val1"]]}]
|
||||||
|
mock_parse_result = {
|
||||||
|
"full_text": "Text",
|
||||||
|
"tables": mock_tables,
|
||||||
|
"metadata": {},
|
||||||
|
"total_pages": 1
|
||||||
|
}
|
||||||
|
|
||||||
|
with patch.object(DoclingParser, 'parse', return_value=mock_parse_result):
|
||||||
|
tables = self.parser.extract_tables("test.pdf")
|
||||||
|
self.assertEqual(tables, mock_tables)
|
||||||
|
self.assertEqual(tables[0]["headers"], ["Col1"])
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user