embedding nv3
parent
87fdae9f21
commit
19a02cdcf7
@ -0,0 +1,43 @@
|
|||||||
|
# NV3 — Generic semantic search on the new embedding subsystem
|
||||||
|
|
||||||
|
This phase keeps the new embedding subsystem parallel to the legacy flow and plugs semantic retrieval
|
||||||
|
into the generic search architecture.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- query embeddings are generated through `at.procon.dip.embedding.service.QueryEmbeddingService`
|
||||||
|
- semantic search uses `DOC.doc_embedding`
|
||||||
|
- retrieval joins `DOC.doc_text_representation` and `DOC.doc_document`
|
||||||
|
- chunk-aware document collapse remains in the generic search fusion layer
|
||||||
|
- no structured TED/mail search in this phase
|
||||||
|
- no legacy cutover in this phase
|
||||||
|
|
||||||
|
## Main classes
|
||||||
|
|
||||||
|
- `at.procon.dip.search.service.SemanticQueryEmbeddingService`
|
||||||
|
- `at.procon.dip.search.engine.semantic.PgVectorSemanticSearchEngine`
|
||||||
|
- `at.procon.dip.search.repository.DocumentSemanticSearchRepository`
|
||||||
|
|
||||||
|
## Query model selection
|
||||||
|
|
||||||
|
Order of precedence:
|
||||||
|
|
||||||
|
1. `SearchRequest.semanticModelKey`
|
||||||
|
2. `dip.embedding.default-query-model`
|
||||||
|
|
||||||
|
The selected model is ensured in `DOC.doc_embedding_model` through
|
||||||
|
`EmbeddingModelCatalogService` before the query runs.
|
||||||
|
|
||||||
|
## Search flow
|
||||||
|
|
||||||
|
1. planner includes `PGVECTOR_SEMANTIC` for `SEMANTIC` or `HYBRID`
|
||||||
|
2. `SemanticQueryEmbeddingService` builds a query vector
|
||||||
|
3. `DocumentSemanticSearchRepository` searches `DOC.doc_embedding`
|
||||||
|
4. generic fusion/collapse merges semantic hits with lexical hits
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- the SQL uses `public.vector` explicitly in casts to avoid search-path related surprises
|
||||||
|
- the repository returns representation metadata (`representation_type`, chunk offsets, etc.)
|
||||||
|
- `SearchRequest.semanticModelKey` is optional and keeps the API model-aware without forcing users
|
||||||
|
to choose a model for every request
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
package at.procon.dip.search.integration;
|
||||||
|
|
||||||
|
import at.procon.dip.domain.document.DocumentFamily;
|
||||||
|
import at.procon.dip.domain.document.DocumentType;
|
||||||
|
import at.procon.dip.domain.document.RepresentationType;
|
||||||
|
import at.procon.dip.embedding.service.RepresentationEmbeddingOrchestrator;
|
||||||
|
import at.procon.dip.search.dto.SearchEngineType;
|
||||||
|
import at.procon.dip.search.dto.SearchMode;
|
||||||
|
import at.procon.dip.search.dto.SearchRepresentationSelectionMode;
|
||||||
|
import at.procon.dip.search.dto.SearchRequest;
|
||||||
|
import at.procon.dip.search.dto.SearchResponse;
|
||||||
|
import at.procon.dip.search.service.SearchOrchestrator;
|
||||||
|
import at.procon.dip.search.spi.SearchDocumentScope;
|
||||||
|
import at.procon.dip.testsupport.AbstractSearchIntegrationTest;
|
||||||
|
import at.procon.dip.testsupport.SearchTestDataFactory;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.test.context.TestPropertySource;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
@TestPropertySource(properties = {
|
||||||
|
"dip.embedding.enabled=true",
|
||||||
|
"dip.embedding.default-document-model=mock-search",
|
||||||
|
"dip.embedding.default-query-model=mock-search",
|
||||||
|
"dip.embedding.providers.mock-default.type=mock",
|
||||||
|
"dip.embedding.providers.mock-default.dimensions=16",
|
||||||
|
"dip.embedding.models.mock-search.provider-config-key=mock-default",
|
||||||
|
"dip.embedding.models.mock-search.provider-model-key=mock-search",
|
||||||
|
"dip.embedding.models.mock-search.dimensions=16",
|
||||||
|
"dip.embedding.models.mock-search.active=true",
|
||||||
|
"dip.embedding.jobs.enabled=true",
|
||||||
|
"ted.search.similarity-threshold=0.10",
|
||||||
|
"ted.search.semantic-candidate-limit=50"
|
||||||
|
})
|
||||||
|
class GenericSemanticSearchOrchestratorIntegrationTest extends AbstractSearchIntegrationTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SearchTestDataFactory dataFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RepresentationEmbeddingOrchestrator embeddingOrchestrator;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SearchOrchestrator searchOrchestrator;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void semanticMode_should_return_document_from_chunk_embeddings() {
|
||||||
|
var created = dataFactory.createDocumentWithPrimaryAndChunks(
|
||||||
|
"Energy optimization strategy",
|
||||||
|
"Strategy overview",
|
||||||
|
"This primary representation only contains a high level overview.",
|
||||||
|
"en",
|
||||||
|
List.of(
|
||||||
|
"Chunk one is introductory and does not contain the target phrase.",
|
||||||
|
"District heating optimization strategy for municipal energy systems is described here."
|
||||||
|
));
|
||||||
|
|
||||||
|
embeddingOrchestrator.enqueueDocument(created.document().getId(), "mock-search");
|
||||||
|
int processed = embeddingOrchestrator.processNextReadyBatch();
|
||||||
|
assertThat(processed).isGreaterThan(0);
|
||||||
|
|
||||||
|
SearchRequest request = SearchRequest.builder()
|
||||||
|
.queryText("district heating optimization strategy")
|
||||||
|
.modes(Set.of(SearchMode.SEMANTIC))
|
||||||
|
.representationSelectionMode(SearchRepresentationSelectionMode.PRIMARY_AND_CHUNKS)
|
||||||
|
.semanticModelKey("mock-search")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
SearchResponse response = searchOrchestrator.search(
|
||||||
|
request,
|
||||||
|
new SearchDocumentScope(Set.of(), Set.of(DocumentType.TEXT), Set.of(DocumentFamily.GENERIC), null, null));
|
||||||
|
|
||||||
|
assertThat(response.getHits()).isNotEmpty();
|
||||||
|
assertThat(response.getHits().getFirst().getTitle()).isEqualTo("Energy optimization strategy");
|
||||||
|
assertThat(response.getHits().getFirst().getPrimaryEngine()).isEqualTo(SearchEngineType.PGVECTOR_SEMANTIC);
|
||||||
|
assertThat(response.getHits().getFirst().getRepresentationType()).isEqualTo(RepresentationType.CHUNK);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue