Skip to content
This repository was archived by the owner on Oct 14, 2026. It is now read-only.

Commit bfa885a

Browse files
committed
Merge branch 'main' of github.com:adobe/spacecat-shared into oas
2 parents ba836e4 + e17d4da commit bfa885a

24 files changed

Lines changed: 2490 additions & 1266 deletions

‎docs/adr/0002-embedding-client-and-semantic-index-utils.md‎

Lines changed: 25 additions & 10 deletions
Large diffs are not rendered by default.

‎package-lock.json‎

Lines changed: 29 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/spacecat-shared-data-access/CHANGELOG.md‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
## [@adobe/spacecat-shared-data-access-v4.40.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v4.39.1...@adobe/spacecat-shared-data-access-v4.40.0) (2026-10-02)
2+
3+
### Features
4+
5+
* **data-access:** semantic claim functions and in-table vector reuse (LLMO-7445) ([#1968](https://github.com/adobe/spacecat-shared/issues/1968)) ([72b7ac0](https://github.com/adobe/spacecat-shared/commit/72b7ac0f4d377c76cf982295dc4530f2ad624abb))
6+
7+
## [@adobe/spacecat-shared-data-access-v4.39.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v4.39.0...@adobe/spacecat-shared-data-access-v4.39.1) (2026-10-02)
8+
9+
### Bug Fixes
10+
11+
* **data-access:** rename MAX_SOURCE_TEXT_LENGTH to MAX_TEXT_LENGTH (LLMO-7445) ([#1967](https://github.com/adobe/spacecat-shared/issues/1967)) ([d20b120](https://github.com/adobe/spacecat-shared/commit/d20b1207c5633598788e96ff8ec9bbea1668ddb2)), closes [#1966](https://github.com/adobe/spacecat-shared/issues/1966) [spacecat-api-service#79](https://github.com/adobe/spacecat-api-service/issues/79) [#79](https://github.com/adobe/spacecat-shared/issues/79) [#79](https://github.com/adobe/spacecat-shared/issues/79)
12+
13+
## [@adobe/spacecat-shared-data-access-v4.39.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v4.38.0...@adobe/spacecat-shared-data-access-v4.39.0) (2026-10-02)
14+
15+
### Features
16+
17+
* **data-access:** batched semantic index writer and suggestion lookup (LLMO-7445) ([#1966](https://github.com/adobe/spacecat-shared/issues/1966)) ([3479136](https://github.com/adobe/spacecat-shared/commit/3479136b0e67923291376b036f9399d8c52f4c0b)), closes [mysticat-data-service#1153](https://github.com/adobe/mysticat-data-service/issues/1153) [mysticat-data-service#1153](https://github.com/adobe/mysticat-data-service/issues/1153) [#1153](https://github.com/adobe/spacecat-shared/issues/1153) [#1153](https://github.com/adobe/spacecat-shared/issues/1153) [#1153](https://github.com/adobe/spacecat-shared/issues/1153)
18+
119
## [@adobe/spacecat-shared-data-access-v4.38.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v4.37.0...@adobe/spacecat-shared-data-access-v4.38.0) (2026-09-30)
220

321
### Features

‎packages/spacecat-shared-data-access/README.md‎

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -142,48 +142,76 @@ See the package `CLAUDE.md` ("URL Index") for the canonicalization, single-write
142142

143143
## Semantic Index
144144

145-
For the topic vector index (`opportunity_semantic_embedding`) and the global query-embedding cache (`semantic_query_embedding`), use the shared helpers so the writer and reader share one text normalization and one storage path. They store and read pre-embedded vectors; embedding is the caller's job (e.g. `AzureEmbeddingClient` from `@adobe/spacecat-shared-gpt-client`).
145+
For the vector indexes (`opportunity_semantic_embedding`, `suggestion_semantic_embedding`) and the global query-embedding cache (`semantic_query_embedding`), use the shared helpers so the writer and reader share one text normalization, one embedding generation and one storage path. The helpers take an embedding client with `createEmbeddings(texts)` (e.g. `AzureEmbeddingClient` from `@adobe/spacecat-shared-gpt-client`) and do the embedding themselves.
146146

147147
```js
148148
import {
149-
syncOpportunitySemantic, copyEntityVectors, lookupOpportunitiesByVectors,
150-
getQueryEmbeddings, upsertQueryEmbeddings, touchQueryEmbeddings,
151-
OPPORTUNITY_SEMANTIC_SOURCE_TYPES, OPPORTUNITY_SEMANTIC_ENTITY_TYPES, SEMANTIC_MATCHING_CONFIG,
149+
indexSemanticTopics, indexSemanticClaims, embedQueries,
150+
lookupOpportunitiesByTopic, lookupSuggestionsByTopic, lookupSuggestionsByClaim,
151+
SEMANTIC_TARGETS,
152152
} from '@adobe/spacecat-shared-data-access';
153153
154154
const { postgrestClient } = dataAccess.services;
155-
const scope = { ...SEMANTIC_MATCHING_CONFIG.embedding }; // { model, dims }
156-
const { TOPIC } = OPPORTUNITY_SEMANTIC_SOURCE_TYPES;
157-
const { CITED_ANALYSIS } = OPPORTUNITY_SEMANTIC_ENTITY_TYPES;
158155
159-
// writer (needs the postgrest_writer role): full-replace an opportunity's topic vectors
160-
await syncOpportunitySemantic(postgrestClient, {
161-
siteId, entityId, entityType: CITED_ANALYSIS, sourceType: TOPIC, sources: [{ text, vector, ...scope }],
156+
// writer (needs the postgrest_writer role): full-replace each (entity, matchFieldType) topic group
157+
// in one call. Unchanged texts are kept; a new text reuses a vector already stored in the same
158+
// table for the site, then the query cache, and is embedded otherwise (in batches); stale texts
159+
// are deleted; fields not listed are left alone. texts: [] clears a field.
160+
const result = await indexSemanticTopics(postgrestClient, embeddingClient, {
161+
target: SEMANTIC_TARGETS.OPPORTUNITY, // or SEMANTIC_TARGETS.SUGGESTION
162+
siteId,
163+
entities: [{
164+
entityId, entityType: 'cited-analysis',
165+
fields: [{ matchFieldType: 'topic', texts: ['running shoes'] }],
166+
}],
167+
});
168+
// result: { entities: [{ entityId, fields: [{ matchFieldType, rejected, inserted, deleted, unchanged, ... }] }],
169+
// embedded, indexHits, cacheHits, indexError?, cacheError? }
170+
171+
// claim writer: same parameters and result, stored under the claim match type.
172+
await indexSemanticClaims(postgrestClient, embeddingClient, {
173+
target: SEMANTIC_TARGETS.SUGGESTION,
174+
siteId,
175+
entities: [{
176+
entityId: suggestionId, entityType: 'cited-analysis',
177+
fields: [
178+
{ matchFieldType: 'title', texts: ['Publish a pricing comparison page'] },
179+
{ matchFieldType: 'topic', texts: ['running shoes'] },
180+
],
181+
}],
162182
});
163183
164-
// writer: copy an opportunity's vector rows to another opportunity in the same site
165-
await copyEntityVectors(postgrestClient, { siteId, fromEntityId, toEntityId });
166-
167-
// query cache, batched: one entry per text, in input order (null on a miss)
168-
const cached = await getQueryEmbeddings(postgrestClient, { texts, ...scope });
169-
await upsertQueryEmbeddings(postgrestClient, { entries: [{ text, vector }], ...scope });
170-
await touchQueryEmbeddings(postgrestClient, { textHashes, ...scope });
184+
// query side: validate (at most MAX_QUERY_TEXTS texts, each up to 2048 chars once normalized), read the
185+
// cache (best-effort), embed the distinct misses in one call within a time budget.
186+
// cacheWrites (touch hits + store misses) is best-effort; await it or hand it to waitUntil.
187+
const { vectors, cacheWrites, cacheError } = await embedQueries(postgrestClient, embeddingClient, texts, { log });
171188
172189
// reader, batched: one best-first list of { entityId, entityType, score } per query vector.
173-
// entityTypes is optional (omitted or [] searches all entity types). statuses is optional too
174-
// (omitted or [] searches all; values from Opportunity.STATUSES); it is applied before k, so
175-
// filtered-out opportunities never take result slots. Needs a data-service with p_statuses.
176-
const matches = await lookupOpportunitiesByVectors(postgrestClient, {
177-
siteId, sourceTypes: [TOPIC], entityTypes: [CITED_ANALYSIS], statuses: ['NEW', 'IN_PROGRESS'],
178-
vectors, k: 10, minScore: 0.5, ...scope,
190+
// matchFieldTypes / entityTypes / statuses are optional filters (omitted or [] means no filter).
191+
// statuses is applied before k, so filtered-out rows never take result slots.
192+
// Searches run in the SEMANTIC_MATCHING_CONFIG.embedding generation, like the writer.
193+
const opportunities = await lookupOpportunitiesByTopic(postgrestClient, {
194+
siteId, matchFieldTypes: ['topic'], entityTypes: ['cited-analysis'],
195+
statuses: ['NEW', 'IN_PROGRESS'], vectors, k: 10, minScore: 0.5,
196+
});
197+
// suggestions: entityTypes are parent opportunity types, statuses are Suggestion.STATUSES,
198+
// opportunityStatuses filter on the parent opportunity.
199+
const suggestions = await lookupSuggestionsByTopic(postgrestClient, {
200+
siteId, opportunityStatuses: ['NEW'], vectors,
201+
});
202+
// claim reader: same parameters, over the claim rows.
203+
const claimMatches = await lookupSuggestionsByClaim(postgrestClient, {
204+
siteId, matchFieldTypes: ['title'], vectors,
179205
});
180206
```
181207

182-
`OPPORTUNITY_SEMANTIC_SOURCE_TYPES` (kinds of embedded text) and `OPPORTUNITY_SEMANTIC_ENTITY_TYPES` (opportunity types) are the registries for `source_type` and `entity_type`: the writer and reader reject any value outside them. They are defined in `@adobe/spacecat-shared-utils` and re-exported here. To index a new kind or opportunity type, add it to the utils registry first.
208+
The match type (`topic`, `claim`) is never a parameter: each dimension has its own writer and readers (`indexSemanticTopics` / `...ByTopic`, `indexSemanticClaims` / `...ByClaim`), which set `match_type` internally, so a topic search never sees claim rows and vice versa. Claim writes accept either target, though only suggestions have a claim reader today. `matchFieldType` (which field the text came from) and `entityType` are free-form, length-bounded strings: there is no registry, so a producer can index a new field or entity type without a shared release.
209+
210+
`SEMANTIC_MATCHING_CONFIG.embedding` (`model`, `dims`) is the embedding generation the writer and reader both use; the writer, `embedQueries` and the lookups read it from there rather than taking it as a parameter. It is a code constant, not env config, so writer and reader agree as long as both run the same data-access version; changing it means a re-embed plus upgrading both consumers. The re-embed is automatic: the writer treats rows from another generation as stale and overwrites them on the entity's next refresh.
183211
184-
`SEMANTIC_MATCHING_CONFIG.embedding` (`model`, `dims`) is the embedding generation the writer and reader both use; spread it into the helpers rather than hardcoding the values. It is a code constant, not env config, so writer and reader agree as long as both run the same data-access version; changing it means a re-embed plus upgrading both consumers.
212+
The writer reuses vectors it already stored in the same table for the site (any entity, match type or field, current generation only), so indexing a suggestion's claims after its topics embeds nothing twice; it does not read the other target's table. It reads the query cache but never writes it; only `embedQueries` does. The low-level cache helpers are internal, so nothing outside data-access can write vectors that the writer and readers trust.
185213
186-
The batched helpers group their calls internally (search in groups of up to 20 vectors, cache reads/touches in groups of 50 hashes), so callers pass whole lists rather than looping per item.
214+
Calls are grouped internally (embeddings in batches of 256, writes and searches in groups of 20, id/hash reads and deletes in groups of 50), so callers pass whole lists rather than looping per item.
187215
188216
## Field Mapping Behavior
189217

‎packages/spacecat-shared-data-access/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@adobe/spacecat-shared-data-access",
3-
"version": "4.38.0",
3+
"version": "4.40.0",
44
"description": "Shared modules of the Spacecat Services - Data Access",
55
"type": "module",
66
"engines": {

‎packages/spacecat-shared-data-access/src/service/index.js‎

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,27 +52,32 @@ export * from '../models/index.js';
5252
export * from '../util/index.js';
5353
// Not in util/index.js: semantic-index imports the Opportunity model, which imports util/index.js.
5454
export {
55+
SEMANTIC_TARGETS,
5556
SEMANTIC_INDEX_TABLES,
57+
SEMANTIC_SEARCH_RPCS,
5658
QUERY_EMBEDDING_TABLE,
57-
SEMANTIC_SEARCH_RPC,
58-
COPY_VECTORS_RPC,
5959
SEMANTIC_CHUNK_SIZE,
6060
QUERY_HASH_CHUNK_SIZE,
61-
MAX_SOURCE_TEXT_LENGTH,
62-
OPPORTUNITY_SEMANTIC_SOURCE_TYPES,
63-
OPPORTUNITY_SEMANTIC_ENTITY_TYPES,
61+
EMBEDDING_BATCH_SIZE,
62+
MAX_QUERY_TEXTS,
63+
MAX_TEXT_LENGTH,
64+
MAX_MATCH_FIELD_TYPE_LENGTH,
65+
MAX_ENTITY_TYPE_LENGTH,
66+
MAX_TYPE_FILTER_ITEMS,
67+
EMBEDDING_TIMEOUT_MS,
6468
SEMANTIC_MATCHING_CONFIG,
69+
EmbeddingUnavailableError,
6570
normalizeText,
6671
hashText,
6772
cleanTopicText,
6873
serializeVector,
6974
parseVector,
70-
syncOpportunitySemantic,
71-
copyEntityVectors,
72-
lookupOpportunitiesByVectors,
73-
getQueryEmbeddings,
74-
upsertQueryEmbeddings,
75-
touchQueryEmbeddings,
75+
indexSemanticTopics,
76+
indexSemanticClaims,
77+
embedQueries,
78+
lookupOpportunitiesByTopic,
79+
lookupSuggestionsByTopic,
80+
lookupSuggestionsByClaim,
7681
} from '../util/semantic-index.utils.js';
7782

7883
const createPostgrestService = (config, client = undefined) => {

0 commit comments

Comments
 (0)