chunk-metadata-adapter 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.
- chunk_metadata_adapter/__init__.py +30 -0
- chunk_metadata_adapter/examples.py +173 -0
- chunk_metadata_adapter/metadata_builder.py +249 -0
- chunk_metadata_adapter/models.py +364 -0
- chunk_metadata_adapter-1.0.0.dist-info/METADATA +218 -0
- chunk_metadata_adapter-1.0.0.dist-info/RECORD +8 -0
- chunk_metadata_adapter-1.0.0.dist-info/WHEEL +5 -0
- chunk_metadata_adapter-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Chunk Metadata Adapter - A package for managing metadata for chunked content.
|
|
3
|
+
|
|
4
|
+
This package provides tools for creating, managing, and converting metadata
|
|
5
|
+
for chunks of content in various systems, including RAG pipelines, document
|
|
6
|
+
processing, and machine learning training datasets.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .metadata_builder import ChunkMetadataBuilder
|
|
10
|
+
from .models import (
|
|
11
|
+
SemanticChunk,
|
|
12
|
+
FlatSemanticChunk,
|
|
13
|
+
ChunkType,
|
|
14
|
+
ChunkRole,
|
|
15
|
+
ChunkStatus,
|
|
16
|
+
ChunkMetrics,
|
|
17
|
+
FeedbackMetrics
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__version__ = "1.0.0"
|
|
21
|
+
__all__ = [
|
|
22
|
+
"ChunkMetadataBuilder",
|
|
23
|
+
"SemanticChunk",
|
|
24
|
+
"FlatSemanticChunk",
|
|
25
|
+
"ChunkType",
|
|
26
|
+
"ChunkRole",
|
|
27
|
+
"ChunkStatus",
|
|
28
|
+
"ChunkMetrics",
|
|
29
|
+
"FeedbackMetrics",
|
|
30
|
+
]
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Examples of how to use the Chunk Metadata Adapter.
|
|
3
|
+
|
|
4
|
+
This module contains practical examples for various use cases.
|
|
5
|
+
"""
|
|
6
|
+
import uuid
|
|
7
|
+
|
|
8
|
+
from chunk_metadata_adapter import (
|
|
9
|
+
ChunkMetadataBuilder,
|
|
10
|
+
SemanticChunk,
|
|
11
|
+
FlatSemanticChunk,
|
|
12
|
+
ChunkType,
|
|
13
|
+
ChunkRole,
|
|
14
|
+
ChunkStatus
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def example_basic_flat_metadata():
|
|
19
|
+
"""Example of creating basic flat metadata for a chunk."""
|
|
20
|
+
# Create a builder instance for a specific project
|
|
21
|
+
builder = ChunkMetadataBuilder(project="MyProject", unit_id="chunker-service-1")
|
|
22
|
+
|
|
23
|
+
# Generate UUID for the source document
|
|
24
|
+
source_id = str(uuid.uuid4())
|
|
25
|
+
|
|
26
|
+
# Create metadata for a piece of code
|
|
27
|
+
metadata = builder.build_flat_metadata(
|
|
28
|
+
text="def hello_world():\n print('Hello, World!')",
|
|
29
|
+
source_id=source_id,
|
|
30
|
+
ordinal=1, # First chunk in the document
|
|
31
|
+
type=ChunkType.CODE_BLOCK, # Using enum
|
|
32
|
+
language="python",
|
|
33
|
+
source_path="src/hello.py",
|
|
34
|
+
source_lines_start=10,
|
|
35
|
+
source_lines_end=12,
|
|
36
|
+
tags="example,hello",
|
|
37
|
+
role=ChunkRole.DEVELOPER
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Access the metadata
|
|
41
|
+
print(f"Generated UUID: {metadata['uuid']}")
|
|
42
|
+
print(f"SHA256: {metadata['sha256']}")
|
|
43
|
+
print(f"Created at: {metadata['created_at']}")
|
|
44
|
+
|
|
45
|
+
return metadata
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def example_structured_chunk():
|
|
49
|
+
"""Example of creating a structured SemanticChunk instance."""
|
|
50
|
+
# Create a builder for a project with task
|
|
51
|
+
builder = ChunkMetadataBuilder(
|
|
52
|
+
project="DocumentationProject",
|
|
53
|
+
unit_id="docs-generator"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Generate a source document ID
|
|
57
|
+
source_id = str(uuid.uuid4())
|
|
58
|
+
|
|
59
|
+
# Create a structured chunk
|
|
60
|
+
chunk = builder.build_semantic_chunk(
|
|
61
|
+
text="# Introduction\n\nThis is the documentation for the system.",
|
|
62
|
+
language="markdown",
|
|
63
|
+
type=ChunkType.DOC_BLOCK,
|
|
64
|
+
source_id=source_id,
|
|
65
|
+
summary="Project introduction section",
|
|
66
|
+
role=ChunkRole.DEVELOPER,
|
|
67
|
+
source_path="docs/intro.md",
|
|
68
|
+
source_lines=[1, 3],
|
|
69
|
+
ordinal=0,
|
|
70
|
+
task_id="DOC-123",
|
|
71
|
+
subtask_id="DOC-123-A",
|
|
72
|
+
tags=["introduction", "documentation", "overview"],
|
|
73
|
+
links=[f"parent:{str(uuid.uuid4())}"]
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Access the data
|
|
77
|
+
print(f"Chunk UUID: {chunk.uuid}")
|
|
78
|
+
print(f"Content summary: {chunk.summary}")
|
|
79
|
+
print(f"Links: {chunk.links}")
|
|
80
|
+
|
|
81
|
+
return chunk
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def example_conversion_between_formats():
|
|
85
|
+
"""Example of converting between structured and flat formats."""
|
|
86
|
+
# Create a builder instance
|
|
87
|
+
builder = ChunkMetadataBuilder(project="ConversionExample")
|
|
88
|
+
|
|
89
|
+
# Start with a structured chunk
|
|
90
|
+
structured_chunk = builder.build_semantic_chunk(
|
|
91
|
+
text="This is a sample chunk for conversion demonstration.",
|
|
92
|
+
language="text",
|
|
93
|
+
type=ChunkType.COMMENT,
|
|
94
|
+
source_id=str(uuid.uuid4()),
|
|
95
|
+
role=ChunkRole.REVIEWER
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Convert to flat dictionary
|
|
99
|
+
flat_dict = builder.semantic_to_flat(structured_chunk)
|
|
100
|
+
print(f"Flat representation has {len(flat_dict)} fields")
|
|
101
|
+
|
|
102
|
+
# Convert back to structured format
|
|
103
|
+
restored_chunk = builder.flat_to_semantic(flat_dict)
|
|
104
|
+
print(f"Restored structured chunk: {restored_chunk.uuid}")
|
|
105
|
+
|
|
106
|
+
# Verify they're equivalent
|
|
107
|
+
assert restored_chunk.uuid == structured_chunk.uuid
|
|
108
|
+
assert restored_chunk.text == structured_chunk.text
|
|
109
|
+
assert restored_chunk.type == structured_chunk.type
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
"original": structured_chunk,
|
|
113
|
+
"flat": flat_dict,
|
|
114
|
+
"restored": restored_chunk
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def example_chain_processing():
|
|
119
|
+
"""Example of a chain of processing for document chunks."""
|
|
120
|
+
# Create a document with multiple chunks
|
|
121
|
+
builder = ChunkMetadataBuilder(project="ChainExample", unit_id="processor")
|
|
122
|
+
source_id = str(uuid.uuid4())
|
|
123
|
+
|
|
124
|
+
# Create a sequence of chunks from a document
|
|
125
|
+
chunks = []
|
|
126
|
+
for i, text in enumerate([
|
|
127
|
+
"# Document Title",
|
|
128
|
+
"## Section 1\n\nThis is the content of section 1.",
|
|
129
|
+
"## Section 2\n\nThis is the content of section 2.",
|
|
130
|
+
"## Conclusion\n\nFinal thoughts on the topic."
|
|
131
|
+
]):
|
|
132
|
+
chunk = builder.build_semantic_chunk(
|
|
133
|
+
text=text,
|
|
134
|
+
language="markdown",
|
|
135
|
+
type=ChunkType.DOC_BLOCK,
|
|
136
|
+
source_id=source_id,
|
|
137
|
+
ordinal=i,
|
|
138
|
+
summary=f"Section {i}" if i > 0 else "Title"
|
|
139
|
+
)
|
|
140
|
+
chunks.append(chunk)
|
|
141
|
+
|
|
142
|
+
# Create explicit links between chunks (parent-child relationships)
|
|
143
|
+
for i in range(1, len(chunks)):
|
|
144
|
+
# Add parent link to the title chunk
|
|
145
|
+
chunks[i].links.append(f"parent:{chunks[0].uuid}")
|
|
146
|
+
# Update status to show progress
|
|
147
|
+
chunks[i].status = ChunkStatus.INDEXED
|
|
148
|
+
|
|
149
|
+
# Simulate processing and updating metrics
|
|
150
|
+
for chunk in chunks:
|
|
151
|
+
# Update metrics based on some processing
|
|
152
|
+
chunk.metrics.quality_score = 0.95
|
|
153
|
+
chunk.metrics.used_in_generation = True
|
|
154
|
+
chunk.metrics.matches = 3
|
|
155
|
+
|
|
156
|
+
# Add feedback
|
|
157
|
+
chunk.metrics.feedback.accepted = 2
|
|
158
|
+
|
|
159
|
+
# Print the processed chain
|
|
160
|
+
print(f"Processed {len(chunks)} chunks from source {source_id}")
|
|
161
|
+
for i, chunk in enumerate(chunks):
|
|
162
|
+
print(f"Chunk {i}: {chunk.summary} - Status: {chunk.status.value}")
|
|
163
|
+
|
|
164
|
+
return chunks
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
if __name__ == "__main__":
|
|
168
|
+
print("Running examples...")
|
|
169
|
+
example_basic_flat_metadata()
|
|
170
|
+
example_structured_chunk()
|
|
171
|
+
example_conversion_between_formats()
|
|
172
|
+
example_chain_processing()
|
|
173
|
+
print("All examples completed.")
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Builder for chunk metadata.
|
|
3
|
+
|
|
4
|
+
This module provides tools for creating and manipulating chunk metadata
|
|
5
|
+
for semantic chunking systems.
|
|
6
|
+
"""
|
|
7
|
+
import uuid
|
|
8
|
+
import hashlib
|
|
9
|
+
from typing import Optional, Dict, Any, List, Union
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
|
|
12
|
+
from .models import (
|
|
13
|
+
SemanticChunk,
|
|
14
|
+
FlatSemanticChunk,
|
|
15
|
+
ChunkType,
|
|
16
|
+
ChunkRole,
|
|
17
|
+
ChunkStatus,
|
|
18
|
+
ChunkMetrics,
|
|
19
|
+
FeedbackMetrics
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ChunkMetadataBuilder:
|
|
24
|
+
"""
|
|
25
|
+
Builder for universal chunk metadata.
|
|
26
|
+
|
|
27
|
+
Used after chunk text is formed to augment it with metadata fields:
|
|
28
|
+
- uuid, sha256, created_at
|
|
29
|
+
- project, status, role, tags, etc.
|
|
30
|
+
|
|
31
|
+
Supports both flat and structured formats.
|
|
32
|
+
"""
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
project: Optional[str] = None,
|
|
36
|
+
unit_id: Optional[str] = None,
|
|
37
|
+
chunking_version: str = "1.0"
|
|
38
|
+
):
|
|
39
|
+
"""
|
|
40
|
+
Initialize a new metadata builder.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
project: Optional project identifier
|
|
44
|
+
unit_id: Optional identifier for the chunking unit/service
|
|
45
|
+
chunking_version: Version of chunking algorithm used
|
|
46
|
+
"""
|
|
47
|
+
self.project = project
|
|
48
|
+
self.unit_id = unit_id or "chunker"
|
|
49
|
+
self.chunking_version = chunking_version
|
|
50
|
+
|
|
51
|
+
def generate_uuid(self) -> str:
|
|
52
|
+
"""Generate a new UUIDv4 string"""
|
|
53
|
+
return str(uuid.uuid4())
|
|
54
|
+
|
|
55
|
+
def compute_sha256(self, text: str) -> str:
|
|
56
|
+
"""Compute SHA256 hash of the given text"""
|
|
57
|
+
return hashlib.sha256(text.encode("utf-8")).hexdigest()
|
|
58
|
+
|
|
59
|
+
def _get_iso_timestamp(self) -> str:
|
|
60
|
+
"""Get current timestamp in ISO8601 format with UTC timezone"""
|
|
61
|
+
return datetime.now(timezone.utc).isoformat()
|
|
62
|
+
|
|
63
|
+
def build_flat_metadata(
|
|
64
|
+
self, *,
|
|
65
|
+
text: str,
|
|
66
|
+
source_id: str,
|
|
67
|
+
ordinal: int,
|
|
68
|
+
type: Union[str, ChunkType],
|
|
69
|
+
language: str,
|
|
70
|
+
source_path: Optional[str] = None,
|
|
71
|
+
source_lines_start: Optional[int] = None,
|
|
72
|
+
source_lines_end: Optional[int] = None,
|
|
73
|
+
summary: Optional[str] = None,
|
|
74
|
+
tags: Optional[str] = None,
|
|
75
|
+
role: Optional[Union[str, ChunkRole]] = None,
|
|
76
|
+
task_id: Optional[str] = None,
|
|
77
|
+
subtask_id: Optional[str] = None,
|
|
78
|
+
link_parent: Optional[str] = None,
|
|
79
|
+
link_related: Optional[str] = None,
|
|
80
|
+
status: Union[str, ChunkStatus] = ChunkStatus.NEW,
|
|
81
|
+
) -> Dict[str, Any]:
|
|
82
|
+
"""
|
|
83
|
+
Build chunk metadata in a flat dictionary format.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
text: The text content of the chunk
|
|
87
|
+
source_id: Identifier of the source (file, document, dialogue)
|
|
88
|
+
ordinal: Order of the chunk within the source
|
|
89
|
+
type: Type of the chunk (see ChunkType)
|
|
90
|
+
language: Programming or natural language of the content
|
|
91
|
+
source_path: Optional path to the source file
|
|
92
|
+
source_lines_start: Start line in source file
|
|
93
|
+
source_lines_end: End line in source file
|
|
94
|
+
summary: Brief summary of the chunk content
|
|
95
|
+
tags: Comma-separated tags
|
|
96
|
+
role: Role of the content creator
|
|
97
|
+
task_id: Task identifier
|
|
98
|
+
subtask_id: Subtask identifier
|
|
99
|
+
link_parent: UUID of the parent chunk
|
|
100
|
+
link_related: UUID of a related chunk
|
|
101
|
+
status: Processing status of the chunk
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Dictionary with flat metadata
|
|
105
|
+
"""
|
|
106
|
+
# Verify UUIDs
|
|
107
|
+
if not isinstance(source_id, str) or not uuid.UUID(source_id, version=4):
|
|
108
|
+
raise ValueError(f"source_id must be a valid UUIDv4 string: {source_id}")
|
|
109
|
+
|
|
110
|
+
if link_parent is not None and (not isinstance(link_parent, str) or not uuid.UUID(link_parent, version=4)):
|
|
111
|
+
raise ValueError(f"link_parent must be a valid UUIDv4 string: {link_parent}")
|
|
112
|
+
|
|
113
|
+
if link_related is not None and (not isinstance(link_related, str) or not uuid.UUID(link_related, version=4)):
|
|
114
|
+
raise ValueError(f"link_related must be a valid UUIDv4 string: {link_related}")
|
|
115
|
+
|
|
116
|
+
# Convert enum types to string values if needed
|
|
117
|
+
if isinstance(type, ChunkType):
|
|
118
|
+
type = type.value
|
|
119
|
+
if isinstance(role, ChunkRole):
|
|
120
|
+
role = role.value
|
|
121
|
+
if isinstance(status, ChunkStatus):
|
|
122
|
+
status = status.value
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
"uuid": self.generate_uuid(),
|
|
126
|
+
"source_id": source_id,
|
|
127
|
+
"ordinal": ordinal,
|
|
128
|
+
"sha256": self.compute_sha256(text),
|
|
129
|
+
"text": text,
|
|
130
|
+
"summary": summary,
|
|
131
|
+
"language": language,
|
|
132
|
+
"type": type,
|
|
133
|
+
"source_path": source_path,
|
|
134
|
+
"source_lines_start": source_lines_start,
|
|
135
|
+
"source_lines_end": source_lines_end,
|
|
136
|
+
"project": self.project,
|
|
137
|
+
"task_id": task_id,
|
|
138
|
+
"subtask_id": subtask_id,
|
|
139
|
+
"status": status,
|
|
140
|
+
"unit_id": self.unit_id,
|
|
141
|
+
"created_at": self._get_iso_timestamp(),
|
|
142
|
+
"tags": tags,
|
|
143
|
+
"role": role,
|
|
144
|
+
"link_parent": link_parent,
|
|
145
|
+
"link_related": link_related,
|
|
146
|
+
"quality_score": None,
|
|
147
|
+
"used_in_generation": False,
|
|
148
|
+
"feedback_accepted": 0,
|
|
149
|
+
"feedback_rejected": 0
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
# Alias for backward compatibility
|
|
153
|
+
build_metadata = build_flat_metadata
|
|
154
|
+
|
|
155
|
+
def build_semantic_chunk(
|
|
156
|
+
self, *,
|
|
157
|
+
text: str,
|
|
158
|
+
language: str,
|
|
159
|
+
type: Union[str, ChunkType],
|
|
160
|
+
source_id: Optional[str] = None,
|
|
161
|
+
summary: Optional[str] = None,
|
|
162
|
+
role: Optional[Union[str, ChunkRole]] = None,
|
|
163
|
+
source_path: Optional[str] = None,
|
|
164
|
+
source_lines: Optional[List[int]] = None,
|
|
165
|
+
ordinal: Optional[int] = None,
|
|
166
|
+
task_id: Optional[str] = None,
|
|
167
|
+
subtask_id: Optional[str] = None,
|
|
168
|
+
links: Optional[List[str]] = None,
|
|
169
|
+
tags: Optional[List[str]] = None,
|
|
170
|
+
status: Union[str, ChunkStatus] = ChunkStatus.NEW,
|
|
171
|
+
) -> SemanticChunk:
|
|
172
|
+
"""
|
|
173
|
+
Build a fully-structured SemanticChunk object.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
text: The text content of the chunk
|
|
177
|
+
language: Programming or natural language of the content
|
|
178
|
+
type: Type of the chunk
|
|
179
|
+
source_id: Optional identifier of the source
|
|
180
|
+
summary: Brief summary of the chunk content
|
|
181
|
+
role: Role of the content creator
|
|
182
|
+
source_path: Optional path to the source file
|
|
183
|
+
source_lines: List of line numbers [start, end]
|
|
184
|
+
ordinal: Order of the chunk within the source
|
|
185
|
+
task_id: Task identifier
|
|
186
|
+
subtask_id: Subtask identifier
|
|
187
|
+
links: List of links to other chunks (format: "relation:uuid")
|
|
188
|
+
tags: List of tags
|
|
189
|
+
status: Processing status of the chunk
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Fully populated SemanticChunk instance
|
|
193
|
+
"""
|
|
194
|
+
# Verify UUIDs
|
|
195
|
+
if source_id is not None and (not isinstance(source_id, str) or not uuid.UUID(source_id, version=4)):
|
|
196
|
+
raise ValueError(f"source_id must be a valid UUIDv4 string: {source_id}")
|
|
197
|
+
|
|
198
|
+
# Validate links format and UUIDs
|
|
199
|
+
if links:
|
|
200
|
+
for link in links:
|
|
201
|
+
parts = link.split(":", 1)
|
|
202
|
+
if len(parts) != 2 or not parts[0] or not parts[1]:
|
|
203
|
+
raise ValueError(f"Link must follow 'relation:uuid' format: {link}")
|
|
204
|
+
try:
|
|
205
|
+
uuid.UUID(parts[1], version=4)
|
|
206
|
+
except (ValueError, AttributeError):
|
|
207
|
+
raise ValueError(f"Invalid UUID4 in link: {link}")
|
|
208
|
+
|
|
209
|
+
# Convert string types to enums if needed
|
|
210
|
+
if isinstance(type, str):
|
|
211
|
+
type = ChunkType(type)
|
|
212
|
+
if isinstance(role, str) and role:
|
|
213
|
+
role = ChunkRole(role)
|
|
214
|
+
if isinstance(status, str):
|
|
215
|
+
status = ChunkStatus(status)
|
|
216
|
+
|
|
217
|
+
return SemanticChunk(
|
|
218
|
+
uuid=self.generate_uuid(),
|
|
219
|
+
source_id=source_id,
|
|
220
|
+
project=self.project,
|
|
221
|
+
task_id=task_id,
|
|
222
|
+
subtask_id=subtask_id,
|
|
223
|
+
unit_id=self.unit_id,
|
|
224
|
+
type=type,
|
|
225
|
+
role=role,
|
|
226
|
+
language=language,
|
|
227
|
+
text=text,
|
|
228
|
+
summary=summary,
|
|
229
|
+
source_path=source_path,
|
|
230
|
+
source_lines=source_lines,
|
|
231
|
+
ordinal=ordinal,
|
|
232
|
+
sha256=self.compute_sha256(text),
|
|
233
|
+
chunking_version=self.chunking_version,
|
|
234
|
+
status=status,
|
|
235
|
+
links=links or [],
|
|
236
|
+
tags=tags or [],
|
|
237
|
+
metrics=ChunkMetrics(),
|
|
238
|
+
created_at=self._get_iso_timestamp()
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
def flat_to_semantic(self, flat_chunk: Dict[str, Any]) -> SemanticChunk:
|
|
242
|
+
"""Convert flat dictionary metadata to SemanticChunk model"""
|
|
243
|
+
flat_model = FlatSemanticChunk(**flat_chunk)
|
|
244
|
+
return flat_model.to_semantic_chunk()
|
|
245
|
+
|
|
246
|
+
def semantic_to_flat(self, chunk: SemanticChunk) -> Dict[str, Any]:
|
|
247
|
+
"""Convert SemanticChunk model to flat dictionary format"""
|
|
248
|
+
flat_model = FlatSemanticChunk.from_semantic_chunk(chunk)
|
|
249
|
+
return flat_model.model_dump()
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Models for chunk metadata representation using Pydantic.
|
|
3
|
+
"""
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import List, Dict, Optional, Union, Any, Pattern
|
|
6
|
+
import re
|
|
7
|
+
import uuid
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from pydantic import BaseModel, Field, validator, field_validator
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# UUID4 регулярное выражение для валидации
|
|
13
|
+
UUID4_PATTERN: Pattern = re.compile(
|
|
14
|
+
r'^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$',
|
|
15
|
+
re.IGNORECASE
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# ISO 8601 с таймзоной
|
|
19
|
+
ISO8601_PATTERN: Pattern = re.compile(
|
|
20
|
+
r'^([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T([2][0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\.[0-9]+)?(Z|[+-][0-9]{2}:[0-9]{2})$'
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ChunkType(str, Enum):
|
|
25
|
+
"""Types of semantic chunks"""
|
|
26
|
+
DOC_BLOCK = "DocBlock"
|
|
27
|
+
CODE_BLOCK = "CodeBlock"
|
|
28
|
+
MESSAGE = "Message"
|
|
29
|
+
DRAFT = "Draft"
|
|
30
|
+
TASK = "Task"
|
|
31
|
+
SUBTASK = "Subtask"
|
|
32
|
+
TZ = "TZ"
|
|
33
|
+
COMMENT = "Comment"
|
|
34
|
+
LOG = "Log"
|
|
35
|
+
METRIC = "Metric"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ChunkRole(str, Enum):
|
|
39
|
+
"""Roles in the system"""
|
|
40
|
+
SYSTEM = "system"
|
|
41
|
+
USER = "user"
|
|
42
|
+
ASSISTANT = "assistant"
|
|
43
|
+
TOOL = "tool"
|
|
44
|
+
REVIEWER = "reviewer"
|
|
45
|
+
DEVELOPER = "developer"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ChunkStatus(str, Enum):
|
|
49
|
+
"""Status of a chunk processing"""
|
|
50
|
+
NEW = "new"
|
|
51
|
+
INDEXED = "indexed"
|
|
52
|
+
VERIFIED = "verified"
|
|
53
|
+
OBSOLETE = "obsolete"
|
|
54
|
+
REJECTED = "rejected"
|
|
55
|
+
IN_PROGRESS = "in_progress"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class FeedbackMetrics(BaseModel):
|
|
59
|
+
"""Feedback metrics for a chunk"""
|
|
60
|
+
accepted: int = Field(default=0, description="How many times the chunk was accepted")
|
|
61
|
+
rejected: int = Field(default=0, description="How many times the chunk was rejected")
|
|
62
|
+
modifications: int = Field(default=0, description="Number of modifications made after generation")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ChunkMetrics(BaseModel):
|
|
66
|
+
"""Metrics related to chunk quality and usage"""
|
|
67
|
+
quality_score: Optional[float] = Field(default=None, ge=0, le=1, description="Quality score between 0 and 1")
|
|
68
|
+
coverage: Optional[float] = Field(default=None, ge=0, le=1, description="Coverage score between 0 and 1")
|
|
69
|
+
matches: Optional[int] = Field(default=None, ge=0, description="How many times matched in retrieval")
|
|
70
|
+
used_in_generation: bool = Field(default=False, description="Whether used in generation")
|
|
71
|
+
used_as_input: bool = Field(default=False, description="Whether used as input")
|
|
72
|
+
used_as_context: bool = Field(default=False, description="Whether used as context")
|
|
73
|
+
feedback: FeedbackMetrics = Field(default_factory=FeedbackMetrics, description="Feedback metrics")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class SemanticChunk(BaseModel):
|
|
77
|
+
"""
|
|
78
|
+
Main model representing a universal semantic chunk with metadata.
|
|
79
|
+
|
|
80
|
+
This is the full, structured representation based on the specification.
|
|
81
|
+
"""
|
|
82
|
+
uuid: str = Field(..., description="Unique identifier (UUIDv4)")
|
|
83
|
+
type: ChunkType = Field(..., description="Type of chunk")
|
|
84
|
+
role: Optional[ChunkRole] = Field(default=None, description="Role of the creator")
|
|
85
|
+
project: Optional[str] = Field(default=None, description="Project identifier")
|
|
86
|
+
task_id: Optional[str] = Field(default=None, description="Task identifier")
|
|
87
|
+
subtask_id: Optional[str] = Field(default=None, description="Subtask identifier")
|
|
88
|
+
unit_id: Optional[str] = Field(default=None, description="Unit/service identifier")
|
|
89
|
+
|
|
90
|
+
text: str = Field(..., description="Chunk content")
|
|
91
|
+
summary: Optional[str] = Field(default=None, description="Brief summary")
|
|
92
|
+
language: str = Field(..., description="Content language/format")
|
|
93
|
+
|
|
94
|
+
source_id: Optional[str] = Field(default=None, description="Source identifier (UUIDv4)")
|
|
95
|
+
source_path: Optional[str] = Field(default=None, description="Path to source")
|
|
96
|
+
source_lines: Optional[List[int]] = Field(default=None, description="Source line numbers")
|
|
97
|
+
ordinal: Optional[int] = Field(default=None, ge=0, description="Order in source")
|
|
98
|
+
|
|
99
|
+
created_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat(), description="Creation timestamp (ISO8601)")
|
|
100
|
+
status: ChunkStatus = Field(default=ChunkStatus.NEW, description="Processing status")
|
|
101
|
+
chunking_version: Optional[str] = Field(default="1.0", description="Chunking algorithm version")
|
|
102
|
+
|
|
103
|
+
sha256: str = Field(..., description="Content hash for integrity")
|
|
104
|
+
embedding: Optional[Any] = Field(default=None, description="Vector embedding")
|
|
105
|
+
|
|
106
|
+
links: List[str] = Field(default_factory=list, description="References to other chunks")
|
|
107
|
+
tags: List[str] = Field(default_factory=list, description="Categorical tags")
|
|
108
|
+
|
|
109
|
+
metrics: ChunkMetrics = Field(default_factory=ChunkMetrics, description="Quality and usage metrics")
|
|
110
|
+
|
|
111
|
+
@field_validator('uuid')
|
|
112
|
+
@classmethod
|
|
113
|
+
def validate_uuid(cls, v: str) -> str:
|
|
114
|
+
"""Validate UUID format"""
|
|
115
|
+
if not UUID4_PATTERN.match(v):
|
|
116
|
+
try:
|
|
117
|
+
# Try to parse and see if it's a valid UUID
|
|
118
|
+
uuid_obj = uuid.UUID(v, version=4)
|
|
119
|
+
if str(uuid_obj) != v.lower():
|
|
120
|
+
raise ValueError("UUID version or format doesn't match")
|
|
121
|
+
except (ValueError, AttributeError):
|
|
122
|
+
raise ValueError(f"Invalid UUID4 format: {v}")
|
|
123
|
+
return v
|
|
124
|
+
|
|
125
|
+
@field_validator('source_id')
|
|
126
|
+
@classmethod
|
|
127
|
+
def validate_source_id(cls, v: Optional[str]) -> Optional[str]:
|
|
128
|
+
"""Validate source_id UUID format if present"""
|
|
129
|
+
if v is not None and not UUID4_PATTERN.match(v):
|
|
130
|
+
try:
|
|
131
|
+
uuid_obj = uuid.UUID(v, version=4)
|
|
132
|
+
if str(uuid_obj) != v.lower():
|
|
133
|
+
raise ValueError("Source ID UUID version or format doesn't match")
|
|
134
|
+
except (ValueError, AttributeError):
|
|
135
|
+
raise ValueError(f"Invalid source_id UUID4 format: {v}")
|
|
136
|
+
return v
|
|
137
|
+
|
|
138
|
+
@field_validator('created_at')
|
|
139
|
+
@classmethod
|
|
140
|
+
def validate_created_at(cls, v: str) -> str:
|
|
141
|
+
"""Validate that created_at is in ISO8601 format with timezone"""
|
|
142
|
+
if not ISO8601_PATTERN.match(v):
|
|
143
|
+
try:
|
|
144
|
+
# Try to parse and see if it's valid ISO format
|
|
145
|
+
dt = datetime.fromisoformat(v.replace('Z', '+00:00'))
|
|
146
|
+
# Ensure timezone is present
|
|
147
|
+
if dt.tzinfo is None:
|
|
148
|
+
raise ValueError("Missing timezone information")
|
|
149
|
+
# Return canonicalized format
|
|
150
|
+
return dt.isoformat()
|
|
151
|
+
except (ValueError, AttributeError):
|
|
152
|
+
raise ValueError(f"Invalid ISO8601 format with timezone: {v}")
|
|
153
|
+
return v
|
|
154
|
+
|
|
155
|
+
@field_validator('links')
|
|
156
|
+
@classmethod
|
|
157
|
+
def validate_links(cls, links: List[str]) -> List[str]:
|
|
158
|
+
"""Validate that links follow the format 'relation:uuid'"""
|
|
159
|
+
for link in links:
|
|
160
|
+
parts = link.split(":", 1)
|
|
161
|
+
if len(parts) != 2 or not parts[0] or not parts[1]:
|
|
162
|
+
raise ValueError(f"Link must follow 'relation:uuid' format: {link}")
|
|
163
|
+
|
|
164
|
+
# Validate that UUID part is valid
|
|
165
|
+
uuid_part = parts[1]
|
|
166
|
+
if not UUID4_PATTERN.match(uuid_part):
|
|
167
|
+
try:
|
|
168
|
+
uuid_obj = uuid.UUID(uuid_part, version=4)
|
|
169
|
+
if str(uuid_obj) != uuid_part.lower():
|
|
170
|
+
raise ValueError("Link UUID version or format doesn't match")
|
|
171
|
+
except (ValueError, AttributeError):
|
|
172
|
+
raise ValueError(f"Invalid UUID4 format in link: {link}")
|
|
173
|
+
return links
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class FlatSemanticChunk(BaseModel):
|
|
177
|
+
"""
|
|
178
|
+
Flat representation of the semantic chunk with all fields in a flat structure.
|
|
179
|
+
|
|
180
|
+
Used for storage systems that prefer flat structures.
|
|
181
|
+
"""
|
|
182
|
+
# Core identifiers
|
|
183
|
+
uuid: str
|
|
184
|
+
source_id: Optional[str] = None
|
|
185
|
+
project: Optional[str] = None
|
|
186
|
+
task_id: Optional[str] = None
|
|
187
|
+
subtask_id: Optional[str] = None
|
|
188
|
+
unit_id: Optional[str] = None
|
|
189
|
+
|
|
190
|
+
# Content info
|
|
191
|
+
type: str
|
|
192
|
+
role: Optional[str] = None
|
|
193
|
+
language: str
|
|
194
|
+
text: str
|
|
195
|
+
summary: Optional[str] = None
|
|
196
|
+
|
|
197
|
+
# Source tracking
|
|
198
|
+
ordinal: Optional[int] = None
|
|
199
|
+
sha256: str
|
|
200
|
+
created_at: str
|
|
201
|
+
status: str = ChunkStatus.NEW.value
|
|
202
|
+
source_path: Optional[str] = None
|
|
203
|
+
source_lines_start: Optional[int] = None
|
|
204
|
+
source_lines_end: Optional[int] = None
|
|
205
|
+
|
|
206
|
+
# Tags & Links
|
|
207
|
+
tags: Optional[str] = None # Comma-separated tags
|
|
208
|
+
link_related: Optional[str] = None
|
|
209
|
+
link_parent: Optional[str] = None
|
|
210
|
+
|
|
211
|
+
# Flat metrics
|
|
212
|
+
quality_score: Optional[float] = None
|
|
213
|
+
used_in_generation: bool = False
|
|
214
|
+
feedback_accepted: int = 0
|
|
215
|
+
feedback_rejected: int = 0
|
|
216
|
+
|
|
217
|
+
@field_validator('uuid')
|
|
218
|
+
@classmethod
|
|
219
|
+
def validate_uuid(cls, v: str) -> str:
|
|
220
|
+
"""Validate UUID format"""
|
|
221
|
+
if not UUID4_PATTERN.match(v):
|
|
222
|
+
try:
|
|
223
|
+
uuid_obj = uuid.UUID(v, version=4)
|
|
224
|
+
if str(uuid_obj) != v.lower():
|
|
225
|
+
raise ValueError("UUID version or format doesn't match")
|
|
226
|
+
except (ValueError, AttributeError):
|
|
227
|
+
raise ValueError(f"Invalid UUID4 format: {v}")
|
|
228
|
+
return v
|
|
229
|
+
|
|
230
|
+
@field_validator('source_id')
|
|
231
|
+
@classmethod
|
|
232
|
+
def validate_source_id(cls, v: Optional[str]) -> Optional[str]:
|
|
233
|
+
"""Validate source_id UUID format if present"""
|
|
234
|
+
if v is not None and not UUID4_PATTERN.match(v):
|
|
235
|
+
try:
|
|
236
|
+
uuid_obj = uuid.UUID(v, version=4)
|
|
237
|
+
if str(uuid_obj) != v.lower():
|
|
238
|
+
raise ValueError("Source ID UUID version or format doesn't match")
|
|
239
|
+
except (ValueError, AttributeError):
|
|
240
|
+
raise ValueError(f"Invalid source_id UUID4 format: {v}")
|
|
241
|
+
return v
|
|
242
|
+
|
|
243
|
+
@field_validator('created_at')
|
|
244
|
+
@classmethod
|
|
245
|
+
def validate_created_at(cls, v: str) -> str:
|
|
246
|
+
"""Validate that created_at is in ISO8601 format with timezone"""
|
|
247
|
+
if not ISO8601_PATTERN.match(v):
|
|
248
|
+
try:
|
|
249
|
+
dt = datetime.fromisoformat(v.replace('Z', '+00:00'))
|
|
250
|
+
if dt.tzinfo is None:
|
|
251
|
+
raise ValueError("Missing timezone information")
|
|
252
|
+
return dt.isoformat()
|
|
253
|
+
except (ValueError, AttributeError):
|
|
254
|
+
raise ValueError(f"Invalid ISO8601 format with timezone: {v}")
|
|
255
|
+
return v
|
|
256
|
+
|
|
257
|
+
@field_validator('link_related', 'link_parent')
|
|
258
|
+
@classmethod
|
|
259
|
+
def validate_link_uuid(cls, v: Optional[str]) -> Optional[str]:
|
|
260
|
+
"""Validate link UUIDs if present"""
|
|
261
|
+
if v is not None and not UUID4_PATTERN.match(v):
|
|
262
|
+
try:
|
|
263
|
+
uuid_obj = uuid.UUID(v, version=4)
|
|
264
|
+
if str(uuid_obj) != v.lower():
|
|
265
|
+
raise ValueError("Link UUID version or format doesn't match")
|
|
266
|
+
except (ValueError, AttributeError):
|
|
267
|
+
raise ValueError(f"Invalid UUID4 format in link: {v}")
|
|
268
|
+
return v
|
|
269
|
+
|
|
270
|
+
@classmethod
|
|
271
|
+
def from_semantic_chunk(cls, chunk: SemanticChunk) -> 'FlatSemanticChunk':
|
|
272
|
+
"""Convert a full SemanticChunk to flat representation"""
|
|
273
|
+
source_lines_start = None
|
|
274
|
+
source_lines_end = None
|
|
275
|
+
if chunk.source_lines and len(chunk.source_lines) >= 2:
|
|
276
|
+
source_lines_start = chunk.source_lines[0]
|
|
277
|
+
source_lines_end = chunk.source_lines[1]
|
|
278
|
+
|
|
279
|
+
# Extract link references
|
|
280
|
+
link_parent = None
|
|
281
|
+
link_related = None
|
|
282
|
+
for link in chunk.links:
|
|
283
|
+
if link.startswith("parent:"):
|
|
284
|
+
link_parent = link.split(":", 1)[1]
|
|
285
|
+
elif link.startswith("related:"):
|
|
286
|
+
link_related = link.split(":", 1)[1]
|
|
287
|
+
|
|
288
|
+
return cls(
|
|
289
|
+
uuid=chunk.uuid,
|
|
290
|
+
source_id=chunk.source_id,
|
|
291
|
+
project=chunk.project,
|
|
292
|
+
task_id=chunk.task_id,
|
|
293
|
+
subtask_id=chunk.subtask_id,
|
|
294
|
+
unit_id=chunk.unit_id,
|
|
295
|
+
type=chunk.type.value,
|
|
296
|
+
role=chunk.role.value if chunk.role else None,
|
|
297
|
+
language=chunk.language,
|
|
298
|
+
text=chunk.text,
|
|
299
|
+
summary=chunk.summary,
|
|
300
|
+
ordinal=chunk.ordinal,
|
|
301
|
+
sha256=chunk.sha256,
|
|
302
|
+
created_at=chunk.created_at,
|
|
303
|
+
status=chunk.status.value,
|
|
304
|
+
source_path=chunk.source_path,
|
|
305
|
+
source_lines_start=source_lines_start,
|
|
306
|
+
source_lines_end=source_lines_end,
|
|
307
|
+
tags=",".join(chunk.tags) if chunk.tags else None,
|
|
308
|
+
link_related=link_related,
|
|
309
|
+
link_parent=link_parent,
|
|
310
|
+
quality_score=chunk.metrics.quality_score,
|
|
311
|
+
used_in_generation=chunk.metrics.used_in_generation,
|
|
312
|
+
feedback_accepted=chunk.metrics.feedback.accepted,
|
|
313
|
+
feedback_rejected=chunk.metrics.feedback.rejected
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
def to_semantic_chunk(self) -> SemanticChunk:
|
|
317
|
+
"""Convert flat representation to full SemanticChunk"""
|
|
318
|
+
# Prepare links
|
|
319
|
+
links = []
|
|
320
|
+
if self.link_parent:
|
|
321
|
+
links.append(f"parent:{self.link_parent}")
|
|
322
|
+
if self.link_related:
|
|
323
|
+
links.append(f"related:{self.link_related}")
|
|
324
|
+
|
|
325
|
+
# Prepare tags
|
|
326
|
+
tags = self.tags.split(",") if self.tags else []
|
|
327
|
+
|
|
328
|
+
# Prepare source lines
|
|
329
|
+
source_lines = None
|
|
330
|
+
if self.source_lines_start is not None and self.source_lines_end is not None:
|
|
331
|
+
source_lines = [self.source_lines_start, self.source_lines_end]
|
|
332
|
+
|
|
333
|
+
# Prepare metrics
|
|
334
|
+
metrics = ChunkMetrics(
|
|
335
|
+
quality_score=self.quality_score,
|
|
336
|
+
used_in_generation=self.used_in_generation,
|
|
337
|
+
feedback=FeedbackMetrics(
|
|
338
|
+
accepted=self.feedback_accepted,
|
|
339
|
+
rejected=self.feedback_rejected
|
|
340
|
+
)
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
return SemanticChunk(
|
|
344
|
+
uuid=self.uuid,
|
|
345
|
+
type=self.type,
|
|
346
|
+
role=self.role,
|
|
347
|
+
project=self.project,
|
|
348
|
+
task_id=self.task_id,
|
|
349
|
+
subtask_id=self.subtask_id,
|
|
350
|
+
unit_id=self.unit_id,
|
|
351
|
+
text=self.text,
|
|
352
|
+
summary=self.summary,
|
|
353
|
+
language=self.language,
|
|
354
|
+
source_id=self.source_id,
|
|
355
|
+
source_path=self.source_path,
|
|
356
|
+
source_lines=source_lines,
|
|
357
|
+
ordinal=self.ordinal,
|
|
358
|
+
created_at=self.created_at,
|
|
359
|
+
status=self.status,
|
|
360
|
+
sha256=self.sha256,
|
|
361
|
+
links=links,
|
|
362
|
+
tags=tags,
|
|
363
|
+
metrics=metrics
|
|
364
|
+
)
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chunk_metadata_adapter
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Reusable metadata builder for chunk-based systems
|
|
5
|
+
Author: SmartAssistant
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yourusername/chunk_metadata_adapter
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/yourusername/chunk_metadata_adapter/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Requires-Python: >=3.8
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: pydantic>=2.0.0
|
|
17
|
+
|
|
18
|
+
# Chunk Metadata Adapter
|
|
19
|
+
|
|
20
|
+
[](https://badge.fury.io/py/chunk-metadata-adapter)
|
|
21
|
+
[](https://opensource.org/licenses/MIT)
|
|
22
|
+
|
|
23
|
+
A library for creating, transforming, and managing universal metadata for content chunks for RAG systems, documentation processing, and machine learning training.
|
|
24
|
+
|
|
25
|
+
[Русская версия](README.md)
|
|
26
|
+
|
|
27
|
+
## Purpose
|
|
28
|
+
|
|
29
|
+
The package provides tools for:
|
|
30
|
+
|
|
31
|
+
1. Creating structured metadata for content chunks (text, code, messages)
|
|
32
|
+
2. Converting between flat and structured formats
|
|
33
|
+
3. Safely storing and validating connections between chunks
|
|
34
|
+
4. Tracking quality and usage metrics of chunks
|
|
35
|
+
5. Ensuring data integrity through SHA256 hashing
|
|
36
|
+
6. Strict validation of identifiers (UUID4) and timestamps (ISO8601 with timezone)
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install chunk-metadata-adapter
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Data Models
|
|
45
|
+
|
|
46
|
+
The package provides two main data formats:
|
|
47
|
+
|
|
48
|
+
### SemanticChunk
|
|
49
|
+
|
|
50
|
+
A fully structured model with nested objects, convenient for programmatic processing:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
chunk = SemanticChunk(
|
|
54
|
+
uuid="550e8400-e29b-41d4-a716-446655440000",
|
|
55
|
+
type=ChunkType.DOC_BLOCK,
|
|
56
|
+
role=ChunkRole.DEVELOPER,
|
|
57
|
+
project="MyProject",
|
|
58
|
+
task_id="TASK-123",
|
|
59
|
+
subtask_id="TASK-123-A",
|
|
60
|
+
unit_id="chunker-service",
|
|
61
|
+
text="# Introduction\n\nThis is the system documentation.",
|
|
62
|
+
summary="Documentation introduction",
|
|
63
|
+
language="markdown",
|
|
64
|
+
source_id="48bb4273-1f56-4015-8b14-3d685b8cc9ae",
|
|
65
|
+
source_path="docs/intro.md",
|
|
66
|
+
source_lines=[1, 3],
|
|
67
|
+
ordinal=0,
|
|
68
|
+
created_at="2023-10-15T12:34:56.789+00:00",
|
|
69
|
+
status=ChunkStatus.INDEXED,
|
|
70
|
+
chunking_version="1.0",
|
|
71
|
+
sha256="abcdef1234567890...",
|
|
72
|
+
links=["parent:a1b2c3d4-e5f6-4a5b-8c7d-9e0f1a2b3c4d"],
|
|
73
|
+
tags=["introduction", "documentation"],
|
|
74
|
+
metrics=ChunkMetrics(quality_score=0.95, used_in_generation=True)
|
|
75
|
+
)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### FlatSemanticChunk
|
|
79
|
+
|
|
80
|
+
A flat structure for storage systems that prefer simple key-value formats:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
flat_chunk = FlatSemanticChunk(
|
|
84
|
+
uuid="550e8400-e29b-41d4-a716-446655440000",
|
|
85
|
+
source_id="48bb4273-1f56-4015-8b14-3d685b8cc9ae",
|
|
86
|
+
project="MyProject",
|
|
87
|
+
task_id="TASK-123",
|
|
88
|
+
subtask_id="TASK-123-A",
|
|
89
|
+
unit_id="chunker-service",
|
|
90
|
+
type="DocBlock",
|
|
91
|
+
role="developer",
|
|
92
|
+
language="markdown",
|
|
93
|
+
text="# Introduction\n\nThis is the system documentation.",
|
|
94
|
+
summary="Documentation introduction",
|
|
95
|
+
ordinal=0,
|
|
96
|
+
sha256="abcdef1234567890...",
|
|
97
|
+
created_at="2023-10-15T12:34:56.789+00:00",
|
|
98
|
+
status="indexed",
|
|
99
|
+
source_path="docs/intro.md",
|
|
100
|
+
source_lines_start=1,
|
|
101
|
+
source_lines_end=3,
|
|
102
|
+
tags="introduction,documentation",
|
|
103
|
+
link_parent="a1b2c3d4-e5f6-4a5b-8c7d-9e0f1a2b3c4d",
|
|
104
|
+
quality_score=0.95,
|
|
105
|
+
used_in_generation=True
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Usage
|
|
110
|
+
|
|
111
|
+
### Creating Chunk Metadata
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
import uuid
|
|
115
|
+
from chunk_metadata_adapter import ChunkMetadataBuilder, ChunkType, ChunkRole
|
|
116
|
+
|
|
117
|
+
# Create a metadata builder for the project
|
|
118
|
+
builder = ChunkMetadataBuilder(project="MyProject", unit_id="chunker-service-1")
|
|
119
|
+
|
|
120
|
+
# Generate a UUID for the source document
|
|
121
|
+
source_id = str(uuid.uuid4())
|
|
122
|
+
|
|
123
|
+
# Create metadata for a code chunk
|
|
124
|
+
metadata = builder.build_flat_metadata(
|
|
125
|
+
text="def hello_world():\n print('Hello, World!')",
|
|
126
|
+
source_id=source_id,
|
|
127
|
+
ordinal=1,
|
|
128
|
+
type=ChunkType.CODE_BLOCK,
|
|
129
|
+
language="python",
|
|
130
|
+
source_path="src/hello.py",
|
|
131
|
+
source_lines_start=10,
|
|
132
|
+
source_lines_end=12,
|
|
133
|
+
tags="example,hello",
|
|
134
|
+
role=ChunkRole.DEVELOPER
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
print(f"Chunk UUID: {metadata['uuid']}")
|
|
138
|
+
print(f"SHA256: {metadata['sha256']}")
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Creating a Structured Chunk
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from chunk_metadata_adapter import ChunkMetadataBuilder, ChunkType, ChunkRole
|
|
145
|
+
|
|
146
|
+
# Create a builder for a documentation project
|
|
147
|
+
builder = ChunkMetadataBuilder(
|
|
148
|
+
project="DocumentationProject",
|
|
149
|
+
unit_id="docs-generator"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Create a structured chunk
|
|
153
|
+
chunk = builder.build_semantic_chunk(
|
|
154
|
+
text="# Introduction\n\nThis is the system documentation.",
|
|
155
|
+
language="markdown",
|
|
156
|
+
type=ChunkType.DOC_BLOCK,
|
|
157
|
+
source_id=str(uuid.uuid4()),
|
|
158
|
+
summary="Project introduction section",
|
|
159
|
+
role=ChunkRole.DEVELOPER,
|
|
160
|
+
source_path="docs/intro.md",
|
|
161
|
+
source_lines=[1, 3],
|
|
162
|
+
ordinal=0,
|
|
163
|
+
task_id="DOC-123",
|
|
164
|
+
subtask_id="DOC-123-A",
|
|
165
|
+
tags=["introduction", "documentation", "overview"],
|
|
166
|
+
links=[f"parent:{str(uuid.uuid4())}"]
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
print(f"Chunk UUID: {chunk.uuid}")
|
|
170
|
+
print(f"Summary: {chunk.summary}")
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Converting Between Formats
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
# Convert from structured to flat format
|
|
177
|
+
flat_dict = builder.semantic_to_flat(chunk)
|
|
178
|
+
|
|
179
|
+
# Convert from flat to structured format
|
|
180
|
+
restored_chunk = builder.flat_to_semantic(flat_dict)
|
|
181
|
+
|
|
182
|
+
# Verify they are equivalent
|
|
183
|
+
assert restored_chunk.uuid == chunk.uuid
|
|
184
|
+
assert restored_chunk.text == chunk.text
|
|
185
|
+
assert restored_chunk.type == chunk.type
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Features
|
|
189
|
+
|
|
190
|
+
- **Strict Type Validation:** All UUIDs are validated against the UUIDv4 format, dates are validated against ISO8601 with timezone.
|
|
191
|
+
- **Flexible Formats:** Support for both structured and flat data representations for different storage systems.
|
|
192
|
+
- **Built-in Integrity:** Automatic calculation of SHA256 hashes for integrity verification.
|
|
193
|
+
- **Compatibility:** Designed to work in the RAG and ML system ecosystem.
|
|
194
|
+
- **Type Safety:** Full typing support for IDEs and static analysis.
|
|
195
|
+
|
|
196
|
+
## Documentation
|
|
197
|
+
|
|
198
|
+
Documentation is available in English and Russian:
|
|
199
|
+
|
|
200
|
+
### English Documentation
|
|
201
|
+
- [Documentation Overview](docs/README.md)
|
|
202
|
+
- [Metadata Structure](docs/Metadata.md)
|
|
203
|
+
- [Usage Guide](docs/Usage.md)
|
|
204
|
+
- [Component Interaction](docs/Component_Interaction.md)
|
|
205
|
+
|
|
206
|
+
### Russian Documentation
|
|
207
|
+
- [Обзор документации](docs/README.ru.md)
|
|
208
|
+
- [Структура метаданных](docs/Metadata.ru.md)
|
|
209
|
+
- [Руководство по использованию](docs/Usage.ru.md)
|
|
210
|
+
- [Взаимодействие компонентов](docs/Component_Interaction.ru.md)
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
MIT
|
|
215
|
+
|
|
216
|
+
## Author
|
|
217
|
+
|
|
218
|
+
SmartAssistant Team
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
chunk_metadata_adapter/__init__.py,sha256=SgB0vjmvwkT29GOLYShHHeEvcHys1sZIRJkepFU3_3M,703
|
|
2
|
+
chunk_metadata_adapter/examples.py,sha256=HBS0coKebzCqs43k90jzwd0aWqlahgN7tEJpplsplRE,5518
|
|
3
|
+
chunk_metadata_adapter/metadata_builder.py,sha256=kQfWnaIt8mM-LKLv5Q6ijKd8gO37nIPpQjCW1_9jzo0,9213
|
|
4
|
+
chunk_metadata_adapter/models.py,sha256=0tYn7PWvLQbqBnV0l4TiK_DVg3N4jAz21yiMv9Hahso,14396
|
|
5
|
+
chunk_metadata_adapter-1.0.0.dist-info/METADATA,sha256=5_vFkZyZdaCGr47pju70F1JaQBI2RZd6Z9M4yfXwuoQ,6663
|
|
6
|
+
chunk_metadata_adapter-1.0.0.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
|
7
|
+
chunk_metadata_adapter-1.0.0.dist-info/top_level.txt,sha256=HKjlcPxdFwCG406zu8eysl3hd2x3cGY6N2JlNJ8y704,23
|
|
8
|
+
chunk_metadata_adapter-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
chunk_metadata_adapter
|