|
| 1 | +"""V2 router for read-only retrieval inspection.""" |
| 2 | + |
| 3 | +from typing import assert_never |
| 4 | + |
| 5 | +from fastapi import APIRouter, HTTPException |
| 6 | + |
| 7 | +from basic_memory.deps import ( |
| 8 | + FileServiceV2ExternalDep, |
| 9 | + LinkResolverV2ExternalDep, |
| 10 | + ProjectExternalIdPathDep, |
| 11 | + SearchRepositoryV2ExternalDep, |
| 12 | +) |
| 13 | +from basic_memory.schemas.inspect import ( |
| 14 | + InspectChunk, |
| 15 | + InspectChunkReadiness, |
| 16 | + InspectChunksRequest, |
| 17 | + InspectChunksResponse, |
| 18 | + InspectDetachedSearchRow, |
| 19 | + InspectIndexBehindRowsDetail, |
| 20 | + InspectRowsBehindFileDetail, |
| 21 | + InspectSearchRow, |
| 22 | +) |
| 23 | +from basic_memory.services.retrieval_inspect import ( |
| 24 | + ChunkFresh, |
| 25 | + ChunkFreshnessUnknown, |
| 26 | + ChunkNotIndexed, |
| 27 | + ChunkIndexBehindRows, |
| 28 | + ChunkRowsBehindFile, |
| 29 | + inspect_entity_chunks, |
| 30 | +) |
| 31 | + |
| 32 | +router = APIRouter(prefix="/inspect", tags=["inspect"]) |
| 33 | + |
| 34 | + |
| 35 | +@router.post("/chunks", response_model=InspectChunksResponse) |
| 36 | +async def inspect_chunks( |
| 37 | + data: InspectChunksRequest, |
| 38 | + project_id: ProjectExternalIdPathDep, |
| 39 | + link_resolver: LinkResolverV2ExternalDep, |
| 40 | + search_repository: SearchRepositoryV2ExternalDep, |
| 41 | + file_service: FileServiceV2ExternalDep, |
| 42 | +) -> InspectChunksResponse: |
| 43 | + """Show how one note is represented by search rows and vector chunks.""" |
| 44 | + entity = await link_resolver.resolve_entity( |
| 45 | + data.identifier, |
| 46 | + load_relations=False, |
| 47 | + ) |
| 48 | + if entity is None or entity.project_id != project_id: |
| 49 | + raise HTTPException(status_code=404, detail=f"Entity not found: '{data.identifier}'") |
| 50 | + |
| 51 | + inspection = await inspect_entity_chunks(search_repository, entity, file_service) |
| 52 | + freshness_detail: InspectIndexBehindRowsDetail | InspectRowsBehindFileDetail | None |
| 53 | + match inspection.freshness: |
| 54 | + case ChunkFresh() | ChunkNotIndexed(): |
| 55 | + freshness_detail = None |
| 56 | + case ChunkIndexBehindRows(): |
| 57 | + freshness_detail = InspectIndexBehindRowsDetail( |
| 58 | + entity_fingerprint_indexed=( |
| 59 | + list(inspection.freshness.entity_fingerprint_indexed) |
| 60 | + if isinstance( |
| 61 | + inspection.freshness.entity_fingerprint_indexed, |
| 62 | + tuple, |
| 63 | + ) |
| 64 | + else inspection.freshness.entity_fingerprint_indexed |
| 65 | + ), |
| 66 | + entity_fingerprint_current=(inspection.freshness.entity_fingerprint_current), |
| 67 | + missing_chunk_count=inspection.freshness.missing_chunk_count, |
| 68 | + ) |
| 69 | + case ChunkRowsBehindFile() | ChunkFreshnessUnknown(): |
| 70 | + evidence = inspection.freshness.evidence |
| 71 | + freshness_detail = InspectRowsBehindFileDetail( |
| 72 | + entity_checksum=evidence.entity_checksum, |
| 73 | + current_file_checksum=evidence.current_file_checksum, |
| 74 | + db_checksum=evidence.db_checksum, |
| 75 | + file_checksum=evidence.file_checksum, |
| 76 | + file_write_status=evidence.file_write_status, |
| 77 | + ) |
| 78 | + case unexpected: # pragma: no cover - EntityChunkFreshness is exhaustive |
| 79 | + assert_never(unexpected) |
| 80 | + return InspectChunksResponse( |
| 81 | + entity_id=entity.id, |
| 82 | + external_id=entity.external_id, |
| 83 | + permalink=entity.permalink, |
| 84 | + file_path=entity.file_path, |
| 85 | + title=entity.title, |
| 86 | + entity_checksum=entity.checksum, |
| 87 | + configured_embedding_model=inspection.configured_identity.embedding_model, |
| 88 | + configured_vector_index=inspection.configured_identity.vector_index, |
| 89 | + readiness=InspectChunkReadiness( |
| 90 | + total=inspection.readiness.total, |
| 91 | + ready=inspection.readiness.ready, |
| 92 | + pending=inspection.readiness.pending, |
| 93 | + stale=inspection.readiness.stale, |
| 94 | + orphaned=inspection.readiness.orphaned, |
| 95 | + missing=inspection.readiness.missing, |
| 96 | + ), |
| 97 | + entity_fingerprint_indexed=( |
| 98 | + list(inspection.entity_fingerprint_indexed) |
| 99 | + if isinstance(inspection.entity_fingerprint_indexed, tuple) |
| 100 | + else inspection.entity_fingerprint_indexed |
| 101 | + ), |
| 102 | + entity_fingerprint_current=inspection.entity_fingerprint_current, |
| 103 | + stale=inspection.stale, |
| 104 | + freshness=inspection.freshness.value, |
| 105 | + freshness_detail=freshness_detail, |
| 106 | + rows=[ |
| 107 | + InspectSearchRow( |
| 108 | + type=inspected_row.search_row.type, |
| 109 | + id=inspected_row.search_row.id, |
| 110 | + title=inspected_row.search_row.title, |
| 111 | + category=inspected_row.search_row.category, |
| 112 | + relation_type=inspected_row.search_row.relation_type, |
| 113 | + content_preview=inspected_row.search_row.content_snippet, |
| 114 | + chunks=[ |
| 115 | + InspectChunk( |
| 116 | + chunk_key=chunk.stored_row.chunk_key, |
| 117 | + ordinal=chunk.ordinal, |
| 118 | + text=chunk.stored_row.chunk_text, |
| 119 | + source_hash=chunk.stored_row.source_hash, |
| 120 | + embedding_model=chunk.stored_row.embedding_model, |
| 121 | + vector_index=chunk.stored_row.vector_index, |
| 122 | + status=chunk.status, |
| 123 | + updated_at=chunk.stored_row.updated_at, |
| 124 | + ) |
| 125 | + for chunk in inspected_row.chunks |
| 126 | + ], |
| 127 | + ) |
| 128 | + for inspected_row in inspection.rows |
| 129 | + ], |
| 130 | + detached=[ |
| 131 | + InspectDetachedSearchRow( |
| 132 | + type=detached_row.row_type, |
| 133 | + id=detached_row.row_id, |
| 134 | + chunks=[ |
| 135 | + InspectChunk( |
| 136 | + chunk_key=chunk.stored_row.chunk_key, |
| 137 | + ordinal=chunk.ordinal, |
| 138 | + text=chunk.stored_row.chunk_text, |
| 139 | + source_hash=chunk.stored_row.source_hash, |
| 140 | + embedding_model=chunk.stored_row.embedding_model, |
| 141 | + vector_index=chunk.stored_row.vector_index, |
| 142 | + status=chunk.status, |
| 143 | + updated_at=chunk.stored_row.updated_at, |
| 144 | + ) |
| 145 | + for chunk in detached_row.chunks |
| 146 | + ], |
| 147 | + ) |
| 148 | + for detached_row in inspection.detached |
| 149 | + ], |
| 150 | + ) |
0 commit comments