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.
@@ -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
+ [![PyPI version](https://badge.fury.io/py/chunk-metadata-adapter.svg)](https://badge.fury.io/py/chunk-metadata-adapter)
21
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.7.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ chunk_metadata_adapter