Skip to content

Commit da15bbf

Browse files
Merge pull request #89 from tag1consulting/fix/wire-expand-primary-weight
Wire expand_primary_weight config through to expand-query and merge logic
2 parents b5dcce7 + c78a6bd commit da15bbf

6 files changed

Lines changed: 71 additions & 27 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ This project uses [Semantic Versioning](https://semver.org/). Major versions are
66

77
## [Unreleased]
88

9-
_No changes yet._
9+
### Fixed
10+
11+
- **`expand_primary_weight` now correctly weights original vs. expansion results.** The config key existed in `ScoltaConfig` and was exported to the browser as `EXPAND_PRIMARY_WEIGHT`, but the JS merge step applied the value to expanded results instead of original results — the opposite of what the config name and docs describe. `scolta.js` now passes `expand_primary_weight` as the weight for the original result set and `1 − expand_primary_weight` for the expansion set in `merge_results()`. Per-term expansion weights likewise start at `1 − expand_primary_weight` instead of `expand_primary_weight`. `AiEndpointHandler` now accepts `expandPrimaryWeight` as a constructor parameter and includes it in the expand-query response payload alongside the terms array. Fixes [#86](https://github.com/tag1consulting/scolta-php/issues/86).
1012

1113
## [1.0.0-rc2] - 2026-05-12
1214

assets/js/scolta.js

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,7 +1034,13 @@
10341034
}
10351035

10361036
// Merge scored results, keeping highest score per URL.
1037-
function mergeResults(currentResults, newResults) {
1037+
// currentWeight / expandedWeight: explicit set weights for the WASM merge. Defaults to
1038+
// 1.0/1.0 (equal weight) for intra-expansion merges; the expand-vs-primary merge passes
1039+
// CONFIG.EXPAND_PRIMARY_WEIGHT / (1 - CONFIG.EXPAND_PRIMARY_WEIGHT) so that a higher
1040+
// expand_primary_weight value gives more weight to original results, as the config name implies.
1041+
function mergeResults(currentResults, newResults, currentWeight, expandedWeight) {
1042+
const cw = (currentWeight !== undefined) ? currentWeight : 1.0;
1043+
const ew = (expandedWeight !== undefined) ? expandedWeight : 1.0;
10381044
if (scoltaWasm) {
10391045
const original = currentResults.map(r => ({
10401046
title: r.data.meta?.title || '',
@@ -1052,8 +1058,8 @@
10521058
}));
10531059
const input = JSON.stringify({
10541060
sets: [
1055-
{ results: original, weight: 1.0 },
1056-
{ results: expanded, weight: getInstanceConfig().EXPAND_PRIMARY_WEIGHT },
1061+
{ results: original, weight: cw },
1062+
{ results: expanded, weight: ew },
10571063
],
10581064
deduplicate_by: "url",
10591065
normalize_urls: true,
@@ -1194,16 +1200,19 @@
11941200

11951201
const queries = [];
11961202
let weightIndex = 0;
1203+
// Per-term scores are scaled by (1 - expand_primary_weight) so that expansion terms
1204+
// start at the correct base weight relative to the primary query results.
1205+
const expandBase = 1.0 - CONFIG.EXPAND_PRIMARY_WEIGHT;
11971206
for (const term of validTerms) {
1198-
const weight = Math.max(CONFIG.EXPAND_PRIMARY_WEIGHT - (weightIndex * 0.05), 0.4);
1207+
const weight = Math.max(expandBase - (weightIndex * 0.05), 0.1);
11991208
queries.push({ term, weight });
12001209
weightIndex++;
12011210

12021211
const words = extractSearchTerms(term);
12031212
if (words.length > 1) {
12041213
for (const word of words) {
12051214
if (word.length > 2 && !queries.some(q => q.term === word)) {
1206-
const wordWeight = Math.max(CONFIG.EXPAND_PRIMARY_WEIGHT - (weightIndex * 0.05), 0.4);
1215+
const wordWeight = Math.max(expandBase - (weightIndex * 0.05), 0.1);
12071216
queries.push({ term: word, weight: wordWeight });
12081217
weightIndex++;
12091218
}
@@ -1218,7 +1227,12 @@
12181227
return;
12191228
}
12201229

1221-
allScoredResults = mergeResults(allScoredResults, expandedResults);
1230+
allScoredResults = mergeResults(
1231+
allScoredResults,
1232+
expandedResults,
1233+
CONFIG.EXPAND_PRIMARY_WEIGHT,
1234+
1.0 - CONFIG.EXPAND_PRIMARY_WEIGHT
1235+
);
12221236
allScoredResults.sort((a, b) => b.score - a.score);
12231237
allScoredResults = deduplicateByTitle(allScoredResults);
12241238
displayedCount = 0;

assets/js/scolta.js.sha256

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2ce9911483b6df304b6d3e6fe5e46e6d11010b8ca0a09899868399d161be1966
1+
c87078463cce601fe2f475817f8197f29086e4996d7941b6656e2807cd892b0a

src/Http/AiControllerTrait.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ final protected function createHandler(object $aiService, ScoltaConfig $config):
7575
aiExpandQuery: $config->aiExpandQuery,
7676
aiSummarize: $config->aiSummarize,
7777
aiSummaryMaxTokens: $config->aiSummaryMaxTokens,
78+
expandPrimaryWeight: $config->expandPrimaryWeight,
7879
);
7980
}
8081
}

src/Http/AiEndpointHandler.php

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,18 @@
3131
class AiEndpointHandler
3232
{
3333
/**
34-
* @param object $aiService AI service (duck-typed).
35-
* @param CacheDriverInterface $cache Cache driver.
36-
* @param int $generation Generation counter for cache invalidation.
37-
* @param int $cacheTtl Cache TTL in seconds (0 = disabled).
38-
* @param int $maxFollowUps Maximum follow-up exchanges allowed.
39-
* @param PromptEnricherInterface $promptEnricher Prompt enricher for site-specific context injection.
40-
* @param array $aiLanguages Supported languages for multilingual responses.
41-
* @param LoggerInterface $logger PSR-3 logger (defaults to NullLogger).
42-
* @param bool $aiExpandQuery Whether the expand-query feature is enabled.
43-
* @param bool $aiSummarize Whether the summarize feature is enabled.
44-
* @param int $aiSummaryMaxTokens Max tokens for AI summary responses.
34+
* @param object $aiService AI service (duck-typed).
35+
* @param CacheDriverInterface $cache Cache driver.
36+
* @param int $generation Generation counter for cache invalidation.
37+
* @param int $cacheTtl Cache TTL in seconds (0 = disabled).
38+
* @param int $maxFollowUps Maximum follow-up exchanges allowed.
39+
* @param PromptEnricherInterface $promptEnricher Prompt enricher for site-specific context injection.
40+
* @param array $aiLanguages Supported languages for multilingual responses.
41+
* @param LoggerInterface $logger PSR-3 logger (defaults to NullLogger).
42+
* @param bool $aiExpandQuery Whether the expand-query feature is enabled.
43+
* @param bool $aiSummarize Whether the summarize feature is enabled.
44+
* @param int $aiSummaryMaxTokens Max tokens for AI summary responses.
45+
* @param float $expandPrimaryWeight Weight of original results vs expansion results (0–1).
4546
*/
4647
public function __construct(
4748
private readonly object $aiService,
@@ -55,6 +56,7 @@ public function __construct(
5556
private readonly bool $aiExpandQuery = true,
5657
private readonly bool $aiSummarize = true,
5758
private readonly int $aiSummaryMaxTokens = 1024,
59+
private readonly float $expandPrimaryWeight = 0.5,
5860
) {
5961
}
6062

@@ -102,13 +104,18 @@ public function handleExpandQuery(string $query): array
102104

103105
$terms = $this->parseExpansionResponse($response, $query);
104106

107+
$payload = [
108+
'terms' => $terms,
109+
'expand_primary_weight' => $this->expandPrimaryWeight,
110+
];
111+
105112
if ($this->cacheTtl > 0) {
106-
$this->cache->set($cacheKey, $terms, $this->cacheTtl);
113+
$this->cache->set($cacheKey, $payload, $this->cacheTtl);
107114
}
108115

109-
return ['ok' => true, 'data' => $terms];
116+
return ['ok' => true, 'data' => $payload];
110117
} catch (ApiKeyMissingException $e) {
111-
return ['ok' => true, 'data' => [$query]];
118+
return ['ok' => true, 'data' => ['terms' => [$query], 'expand_primary_weight' => $this->expandPrimaryWeight]];
112119
} catch (\Exception $e) {
113120
$this->logger->error('Scolta query expansion failed', ['exception' => $e]);
114121

tests/Http/AiEndpointHandlerTest.php

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,15 +132,15 @@ public function testExpandQueryReturnsCachedResult(): void
132132
$cache = new InMemoryCacheDriver();
133133
$ai = new MockAiService('should not be called');
134134

135-
// Pre-populate cache.
135+
// Pre-populate cache with new payload format.
136136
$handler = $this->makeHandler(aiService: $ai, cache: $cache, cacheTtl: 3600);
137137
$cacheKey = $handler->cacheKey('expand', 'test query');
138-
$cache->set($cacheKey, ['cached term'], 3600);
138+
$cache->set($cacheKey, ['terms' => ['cached term'], 'expand_primary_weight' => 0.5], 3600);
139139

140140
$result = $handler->handleExpandQuery('test query');
141141

142142
$this->assertTrue($result['ok']);
143-
$this->assertEquals(['cached term'], $result['data']);
143+
$this->assertEquals(['cached term'], $result['data']['terms']);
144144
$this->assertEquals(0, $ai->callCount, 'AI service should not have been called');
145145
}
146146

@@ -344,7 +344,8 @@ public function testExpandQueryReturns200WithOriginalQueryWhenNoApiKey(): void
344344
$result = $handler->handleExpandQuery('my search query');
345345

346346
$this->assertTrue($result['ok']);
347-
$this->assertEquals(['my search query'], $result['data']);
347+
$this->assertEquals(['my search query'], $result['data']['terms']);
348+
$this->assertArrayHasKey('expand_primary_weight', $result['data']);
348349
$this->assertArrayNotHasKey('status', $result);
349350
}
350351

@@ -406,7 +407,26 @@ public function testExpandQueryHandlesEmptyAiResponse(): void
406407

407408
// Empty response should fallback to original query.
408409
$this->assertTrue($result['ok']);
409-
$this->assertEquals(['test query'], $result['data']);
410+
$this->assertEquals(['test query'], $result['data']['terms']);
411+
}
412+
413+
public function testExpandQueryResponseIncludesExpandPrimaryWeight(): void
414+
{
415+
$ai = new MockAiService('["term1", "term2"]');
416+
$handler = new AiEndpointHandler(
417+
aiService: $ai,
418+
cache: new InMemoryCacheDriver(),
419+
generation: 1,
420+
cacheTtl: 0,
421+
maxFollowUps: 3,
422+
expandPrimaryWeight: 0.8,
423+
);
424+
425+
$result = $handler->handleExpandQuery('test query');
426+
427+
$this->assertTrue($result['ok']);
428+
$this->assertIsArray($result['data']['terms']);
429+
$this->assertSame(0.8, $result['data']['expand_primary_weight']);
410430
}
411431

412432
// ===================================================================

0 commit comments

Comments
 (0)