1-prompt
现在索引是spu粒度的,sku是作为spu的nested属性。召回和计算exact_image_knn_query都会计算图片的相似度,image_embedding字段是一个list,跟sku是一一对应的。 我希望,是因为哪个image_embedding被召回的、或者重排的时候,是取的哪个元素的exact_image_knn_query得分(image_embedding这个list内最高的得分),我希望将这个sku前置。
之前在重排之前有这个流程:
- 有款式意图的时候,才做sku筛选
- sku筛选的时机,改为在reranker之前,对所有内容(rerank输入的所有spus)做sku筛选 若有款式意图则对 rerank 窗口内全部 hits 做 SKU 预筛选并补充 rerank doc 文本 Searcher 只编排:解析 query -> 首次 ES 召回 -> 若有款式意图则对 rerank 窗口内全部 hits 做 SKU 预筛选并补充 rerank doc 文本 -> run_rerank -> 分页 但是,现在是关闭重排的,所以,这个流程可能没了?请你检查。
我的总的意图是,如果有款式方面的意图,需要做spu内部的sku的排序, 这两点也需要检查:
- 之前的流程,不知道是否仍然有效(因为现在关闭了reranker)
- 现在补充了image_embedding的召回和exact_image_knn_query得分,那么应该能知道哪个sku是最匹配的,希望把这个信号利用上, 1)召回的时候,是哪个图片向量相似度最高 2)重排的时候是用哪个算的相似度?这个信号如何利用 "rescore_query": { "bool": { "should": [ { "nested": { "path": "image_embedding", "name": "exact_image_knn_query", "score_mode": "max", "query": { "script_score": { "query": { "exists": { "field": "image_embedding.vector" } }, "script": { "source": "((dotProduct(params.queryvector, 'image_embedding.vector') + 1.0) / 2.0) * params.boost", "params": {
最终组织结果的时候,sku的排序,要把这个信号利用进来,要按照图片的相似度排序。 是直接用图片的信号,把图片得分最高的url,用于sku的筛选(选取对应url的sku。因为skus和image_embedding是两个字段,skus里面有image_src,可以用于url匹配,选取对应url的sku作为图片得分最高的sku)还是跟以前的策略融合在一起,还是,请思考
可以以query=白色T恤进行实验
1-response
我仔细读了当前 search/searcher.py、search/sku_intent_selector.py、search/es_query_builder.py、mappings/search_products.json 和 indexer/document_transformer.py,先回答你的两个核对问题,再给出如何把图片信号融入的设计。
1. 关闭 rerank 之后,原来的款式意图 SKU 流程是否还有效?
仍然有效。 关键点:
in_rank_window = (from_ + size) <= rerank_window(searcher.py:529)只依赖配置里的rerank_window,跟请求/配置里的do_rerank没关系。- 进入
in_rank_window分支后,款式意图处理是无条件执行的:searcher.py:1070-1081:在 coarse 完成、ranking 窗口 source fill 之后,调用_apply_style_intent_to_hits算出style_intent_decisions(带_style_rerank_suffix)。searcher.py:1083-1154:fine 和 rerank 阶段现在都通过_run_optional_stage包装,enabled=False时走 passthrough,不会丢前面挂在 hit 上的信号。searcher.py:1205-1209:page fill 覆盖_source之后,再调一次apply_precomputed_decisions,把 SKU 置顶 +image_url替换最终写回。
- 非 rank-window 分支(
searcher.py:1225-1231)走_apply_style_intent_to_hits并在result_processing前apply_precomputed_decisions(1254-1259)。
所以"先 prepare、后 apply"的契约没断。唯一的"废功"是 _style_rerank_suffix——关掉重排之后它挂在 hit 上但没人读;代价很小、一旦你重新打开 rerank 又立刻生效,建议保留。
2. 当前 image_embedding / exact_image_knn_query 的信号完全没喂到 SKU 选择里
我去看了索引 & 查询两端:
mappings/search_products.json:1054的image_embedding是 nested,每个元素{vector, url};索引时(indexer/document_transformer.py:490-536)把 SPU 主图 + SKU 图片 URL 合起来去重后逐个编码,url就是skus[].image_src里能对得上的那串。es_query_builder.py:193-281里build_image_knn_clause和build_exact_image_knn_rescore_clause都是 nested +score_mode: max,但都没带inner_hits。所以 ES 只告诉我们"nested 里最高分是多少",不告诉我们是哪条 url 命中的。StyleSkuSelector当前完全靠"意图词 ×optionN_value文本匹配"选 SKU(sku_intent_selector.py:252-284),没有任何 image 通路。
也就是说:"哪个 image_embedding 被召回 / 被 rescore 命中"这个你已经花代价算出来的信号,下游根本拿不到。
3. 融合设计(建议方案)
我倾向融合而不是二选一,理由是两类信号各有盲区:
| 信号 | 擅长 | 盲区 |
|---|---|---|
| 文本款式意图(现有) | "白色"能命中 option color=white |
店家把颜色塞在 title 或图里、option 是数字码时失效 |
| 图片 KNN 最佳 url(新增) | 视觉接近 query(文本走 CN-CLIP 后也和图片空间对齐) | 可能选出"卖相最好"的主图,和明确的颜色诉求冲突 |
契约上的改动最小化:依旧 SkuSelectionDecision + apply_precomputed_decisions
只是在 prepare 阶段多一个候选源、decision 里多几个字段做 debug 和融合依据。
具体改动点
a. es_query_builder.py:给两个 nested image 查询都加 inner_hits
build_exact_image_knn_rescore_clause:{"inner_hits": {"name": "exact_image_knn_hits", "size": 1, "_source": ["url"]}}build_image_knn_clause:{"inner_hits": {"name": "image_knn_hits", "size": 1, "_source": ["url"]}}
两个都加的原因:rescore 是 in_rank_window 才有;不在窗口内时还能吃到初召回的 inner_hits。代价:每个 hit 多返回一个 {url, _score},很轻。
b. sku_intent_selector.py:扩展 decision,新增 image-pick 通路
@dataclass(frozen=True)
class SkuSelectionDecision:
... # 保留现有字段
image_pick_sku_id: Optional[str] = None
image_pick_url: Optional[str] = None
image_pick_score: Optional[float] = None
final_source: str = "text" # "text" | "image" | "none"
- 新增
_pick_sku_by_image(hit, source):读hit["inner_hits"]["exact_image_knn_hits"](缺省回退到image_knn_hits),取 top url,在skus[]里按image_src归一化后等值匹配(注意处理//协议无关前缀、大小写、querystring 这些脏数据,按 indexer 那边的写入规则来)。 _select_for_source的新决策树:text_pick= 现在的文本匹配结果。image_pick= 上述 helper 的结果。- 有款式意图:
text_pick非空 → 选text_pick,final_source="text";把image_pick存进 decision 里仅用于 debug/验证(两者一致时在 debug 打标,不一致时也不翻盘——query 显式指名了颜色/尺码,视觉信号不该覆盖)。text_pick为空但image_pick非空 → 选image_pick,final_source="image"。
- 无款式意图:
- 若
image_pick的分数 ≥ 阈值(配置项,例如query_config.image_sku_selection_min_score),选image_pick。否则不做 SKU 选择(保持 SPU 原顺序)。
- 若
_apply_decision_to_source无需大改,仍然是"把选中 SKU 顶到前面 + 替换image_url为该 SKU 的image_src";只是selected_sku_id的来源更丰富了。
c. searcher.py:把 image-pick 路径也纳入 "prepare → apply"
- 把"
_has_style_intent才 prepare"改成"_has_style_intent或has_image_inner_hits就 prepare"。命名上把_apply_style_intent_to_hits改成_apply_sku_selection_to_hits更准确(可做可不做,看你对改动面范围的容忍度)。 _resolve_rerank_source_filter已经在有款式意图时补了skus/option*_name;image-only 路径还需要skus(至少sku_id、image_src),所以触发条件要扩到"只要可能选 SKU"。- inner_hits 会带在 top-level
hit上,不会被 page fill 的_source覆盖影响,但 page fill 用的是ids查询,本来就不会有 inner_hits。这正好符合现有契约:prepare 时把 image_pick 落到SkuSelectionDecision里,apply 阶段不再依赖 inner_hits,就不会丢。 context.store_intermediate_result("style_intent_profile", ...)附近把 image-pick 的诊断也写进 debug。
d. 配置
query_config:
image_sku_selection_enabled: true # 总开关
image_sku_selection_min_score: 0.55 # 无款式意图时的保护阈值
image_sku_selection_when_text_missing: true # 有意图但文本未命中时回退到图片
我为什么不建议"用图片分直接替换原策略"
两个理由:
- 你现在的文本策略对明确的词面命中是最可信的信号;query 说"白色",结果给了视觉更漂亮但色号不同的 SKU,这是产品上的倒退。
- 图片 KNN 的 max score 在 nested score_mode=max 下是"最相似那张图的分",但 CN-CLIP 的文本→图像相似度阈值校准很敏感,不同品类/拍摄风格分布差异大。拿来做候选稳,拿来做唯一决定不稳。
4. query=白色T恤 的实验
改完之后可以这样验证:
source activate.sh
python main.py search "白色T恤" --tenant-id 1 --size 10 --debug \
| jq '.debug_info.per_result[] | {spu_id, style_intent_sku, first_sku: .skus[0]?}'
预期关注点:
- 有 color option 的 SPU:
style_intent_sku.final_source == "text",并且image_pick_sku_id多数情况下与 text 选的一致(一致率是个好指标)。 - 没有 color option、但多图的 SPU:
final_source == "image",skus[0].image_src的图肉眼是白色。 - 两者冲突:debug 里应能看到
selected_sku_id != image_pick_sku_id的案例,确认当前默认"文本优先"没有被图片翻盘。 - 分别跑
--debug对比改前/改后同一 query 的results[].image_url和skus[0].image_src,观察白色主图替换的命中率提升。
如果想更系统,可以扩 scripts/evaluation/ 里已有的回归 case,加一个"图片颜色是否匹配意图"的人工抽检指标。
5. 两个我想先和你确认的产品决策
- 无款式意图时,是否允许图片信号单独触发 SKU 置顶?(我倾向:允许,但带阈值保护,默认开。)
- 文本命中 vs 图片命中冲突时,是硬性文本优先,还是做分数融合(先各自 min-max 归一再取加权 max)?我倾向硬性文本优先——实现简单、行为可解释;融合方案留作后续 A/B。
告诉我你的取舍,我就按上面的改动清单一次性改掉(es_query_builder.py 两个 inner_hits、sku_intent_selector.py 扩字段 + 新通路、searcher.py 触发条件扩容 + 命名收敛、配置项、配套单测),并跑 白色T恤 的实际 debug 对照给你看。
2-prompt
无款式意图时,允许图片信号单独触发 SKU 置顶
文本命中,硬性文本优先,当匹配失败的时候("selected_sku_id": null)使用图片最相近的
es中有这个字段enriched_taxonomy_attributes: "enriched_taxonomy_attributes": [ { "name": "Color", 文本匹配的时候,考虑更全面一点: 1)把这个字段考虑进去,enriched_taxonomy_attributes内Color对应的value匹配的时候 我在 source_fields:加了enriched_taxonomy_attributes之后好像还是没返回这个字段,你检查下是不是能拿到,可以考虑对query=卡其色裙子 首条结果验证下
还是颜色意图文本匹配的问题 query=卡其色裙子的首条结果: "selected_sku_id": null, "specifications": [ { "value_text": {...}, "name": "颜色", "sku_id": "447652", "value_keyword": "卡其色v领收腰长裙【常规款】" },
意图检测是成功的: intent_detection: active: yes intents: intent_type: color dimension_aliases: color, colors, colour, colours, 颜色, 色, 色系 matched_term: 卡其色 canonical_value: beige
但是 "selected_sku_id": null, 颜色没有匹配上,value_keyword 包含了“卡其色”,没有匹配成功,因此也应该没有享受到提权
2-response
好,那就直接在当前 agent 模式下按计划施工。施工顺序按"先不破坏现有行为、再扩新通路":
Phase 1(修复 + 显字段,低风险)
api/models.py扩SpuResult两个富化字段api/result_formatter.py透传sku_intent_selector.py:扩匹配词集合(zh+en+attr)+ 收敛为子串匹配;加enriched_taxonomy_attributes作为 SPU 级确认
Phase 2(image 回退通路)
es_query_builder.py两个 nested image 查询加inner_hitssku_intent_selector.py扩SkuSelectionDecision+_pick_sku_by_image+ text-first/image-fallback 决策树searcher.py触发条件扩为"款式意图 OR image inner_hits",_resolve_rerank_source_filter无意图时也带skus字段config.yaml加image_sku_selection.*
Phase 3(调试 + 回归)
- debug payload 扩 text_pick/image_pick/final_source
- 单测补四个关键场景
- 跑
卡其色裙子/白色T恤对照
开始吗?两件事需要你拍板后我再动:
enriched_taxonomy_attributes的语义级别:你要的是"作为另一个 SKU-级 value 匹配源",还是我上面提议的"仅作 SPU 级确认信号,不直接决定 SKU"?我倾向后者——因为这个字段是 SPU 级的,不能单独指定某个 SKU。前者会带来"SKU A 其实不是卡其色、却被 SPU 级信号顶上去"的错案。image-pick 的分数阈值:无意图时建议给个阈值(
min_score_no_intent),有意图 fallback 时不加阈值。你接受这个不对称吗?接受的话我用0.55起步,上线后调。
你确认这两点,我就一把改完。
3-prompt
请综合考虑、全部完成,enriched_taxonomy_attributes的Color对应到值是和option1_name/option1_values/option2/option3一样对待的,就是当前使用的属性值匹配,多一个字段来源,即,即使对于颜色意图,同时把me对应的option value和enriched_taxonomy_attributes的Color对应到值一起判断,要考虑统一设计、精炼代码。image-pick设计和代码保持精炼,遵循统一的最优的设计,尽量不要fallback。
3-response
27/27 通过。
问题 2:是不是所有"选中"都享受 1.2x 提权?
是的,目前无差别地都享受,这确实是个设计缺口。 机制如下:
return bool(str(hit.get("_style_rerank_suffix") or "").strip())
sku_selected = _has_selected_sku(hit)
style_boost = style_intent_selected_sku_boost if sku_selected else 1.0
只要 prepare_hits 给 hit 写了 _style_rerank_suffix(只要 selected_text 非空),下游 run_rerank / run_lightweight_rerank 就把 style_boost=1.2 乘进 fused score。
当前 _style_rerank_suffix 的写入条件是 decision.rerank_suffix 非空 —— 等价于 final_source ∈ {"text", "image"}。也就是说:
选中来源 (final_source) |
是否吃 1.2x 款式提权(现状) |
|---|---|
text(自有值命中意图词) |
✅ |
image(仅图像相似度置顶) |
✅ ← 语义上不该等价 |
none |
❌ |
把"图像置顶"和"文本命中"按同一强度做 SPU 级提权,存在两个隐患:
- 置信度不同:文本命中是显式属性匹配(强信号),图像 pick 是基于视觉相近(弱信号、还可能是 SPU 主图而非 SKU 图);给同样 1.2x 会让"图像近似 → SPU 排名拔高"发生在没有真正属性命中的情况下。
- 无意图纯图像触发时更夸张:query 无任何款式意图,但只要 image_knn 的 top url 能对上某个 SKU,整条 SPU 就吃到 1.2x,等于让向量相似度二次乘进融合。
建议的区分策略(只改 rerank_client,不改决策侧):
- 文本命中(
final_source="text")→ 保留 1.2x(可配置) - 图像命中(
final_source="image")→ 1.0x(不加提权,仅做 SKU 置顶 + 改封面图)
最简实现:把"是否命中"从"后缀是否非空"改为"后缀非空 AND 是文本来源"。具体做法:
prepare_hits只在final_source == "text"时写_style_rerank_suffix;image时不写,但仍把决策放进decisions(apply_precomputed_decisions照常置顶 SKU + 换封面图)。- 对应的 rerank doc 后缀也就不含图像源的颜色/尺码值(避免把一个"靠图选的 SKU"当作文本证据喂给 reranker)。
要我现在就把这条改了吗?或者你更希望 image 源也吃提权但用一个更小的系数(比如 1.05),这样保留视觉加分但不等同于文本命中。
4-prompt
两个问题,请修复:
option匹配,应该要能成功匹配才对: { "sku_id": "441679", "price": 13, "compare_at_price": 0, "sku_code": "", "stock": 100, "weight": 0, "weight_unit": "kg", "option1_value": "卡其色(无内衬)",
即使option没有匹配、而是依靠enriched_taxonomy_attributes 匹配的,那么rerank_suffix selected_text 也要翻译真实的匹配项,final_source也最好 enriched_taxonomy_attributes 和option有所区分 Selected SKU { "selected_sku_id": "441670", "rerank_suffix": "白色(无内衬)", "selected_text": "白色(无内衬)", "final_source": "text",
4-response
改动摘要
问题 1(option 匹配)
我上一条消息里修的 "SKU 自有值优先于 taxonomy" 就解决了这个问题。用你给的样本跑:
query=卡其色裙子
skus=[441670:白色(无内衬), 441679:卡其色(无内衬)]
→ selected_sku_id: 441679
selected_text: 卡其色(无内衬)
final_source: option
全角括号之所以能命中是两个机制叠加:
- 全角
(不在[\u4e00-\u9fff]范围内,分词器按["卡其色", "无内衬"]切分; - 纯中文词
卡其色落入 "pure-Han 子串回退" 分支,即使分词切分不一致也能匹配。
加了一个贴着真实数据命名的回归 case test_zh_color_intent_matches_noisy_option_value_with_fullwidth_parens 锁死。
问题 2(区分 taxonomy / option,selected_text 反映真实匹配项)
决策结构扩展:
SkuSelectionDecision:
final_source: "option" | "taxonomy" | "image" | "none"
matched_sources: Dict[intent_type, "option"|"taxonomy"] # 逐意图细分
selected_text / rerank_suffix: 真正命中的原始文本(option 源为 sku.optionN_value;taxonomy 源为 enriched_taxonomy_attributes 对应 value)
匹配逻辑(_find_text_matched_skus):per-intent 走两条互斥路径,保留权威原则:
- SKU 在该维度有自有值 → 只用自有值判断 → 命中时
source=option - SKU 该维度值为空(槽未解析 或 optionN 值空)→ 用 SPU taxonomy 判断 → 命中时
source=taxonomy
整体来源聚合:任一意图靠 taxonomy 命中 → 整体 final_source="taxonomy";全部由 option 命中 → final_source="option"。这保留了"最弱文本证据"的含义,下游(比如 rerank boost)可以据此做差异化。
真实匹配项回填示例:
option1_name=颜色, option1_value=卡其色(无内衬):selected_text="卡其色(无内衬)",final_source=optionoption1_name=Style, enriched_taxonomy_attributes=[{Color, {zh:"卡其色"}}]:selected_text="卡其色"(来自 taxonomy 原文),final_source=taxonomy
与 1.2x 款式提权的衔接(回答你上一条的后半问)
当前 rerank_client 判断 "是否选中" 的依据仍是 _style_rerank_suffix 是否非空——也就是说 option / taxonomy / image 三种来源目前仍一视同仁都吃 1.2x。现在有了 final_source 作为可区分的信号,你想怎么分级,我马上就能改。常见选择:
option:1.2x(强信号)taxonomy:1.1x(SPU 级,弱一档)image:1.0x(不加提权,仅做 SKU 置顶 + 换封面)
要不要我把这三档都拉到 config.yaml 里做成可配?或者先把 image 档位降到 1.0x、其它保持不变?
5-prompt
q=藏青百褶半身裙 第五条结果 "spu_id": "68501",
Selected SKU { "selected_sku_id": null, "rerank_suffix": "", "selected_text": "", "final_source": "none", "matched_sources": {}, "resolved_dimensions": { "color": "option1_value" }, "image_pick": null } 为什么image_pick也没生效
5-response
为什么当时 image_pick 也是 null
image_pick 只来自 ES 返回的 inner_hits(exact_image_knn_query_hits 优先,其次 image_knn_query_hits),再用其中的 URL 去对齐 skus[].image_src。下面任一情况都会变成 null:
没有
inner_hits- 例如:该文档不在 exact KNN rescore 的
window_size内(若exact_knn_rescore_window配得很小,排第 5 条也可能拿不到 exact 的 inner_hits)。你这边配置是 160,第 5 条一般仍在窗口内。 - 或:召回阶段 nested image KNN 对该父文档没有产生 inner_hits(只靠文本等子句进结果时,有时 nested 子句侧没有可返回的条目)。
- 例如:该文档不在 exact KNN rescore 的
有 inner_hits,但 URL 对不上任何 SKU(最常见)
inner_hits里是https://cdn/a.jpg?w=800,SKU 里是https://cdn/a.jpg- 或 CDN 域名不同、路径相同
- 或
_source.url与索引里image_embedding的 canonical 写法不一致,而 SKU 用的是另一种写法
以前预取
_source里没有image_embedding- ES 在 inner_hit 里给了
_nested.offset,但_source.url不可靠时,无法用image_embedding[offset].url再解析一次。
- ES 在 inner_hit 里给了
本次代码上做了什么(已提交到当前分支)
- URL 对齐放宽:去掉 query、路径归一、
host+path/ 仅 path / 文件名 多级比对,减少「明明是一张图却匹配不上」。 _nested.offset+image_embedding[offset].url回退:inner_hit 里若带 offset,会同时尝试_source.url和父文档里image_embedding[offset].url(与索引侧一致)。ImagePick.url改为记录真正用来对齐成功的那条候选 URL(便于 debug)。- Searcher:在存在 图像查询向量(
_has_image_signal)时,预取_source里增加image_embedding,否则 offset 回退拿不到数据。
这样在「藏青百褶半身裙」这类 文本未命中 SKU、指望图像兜底 的场景里,只要 ES 给了 inner_hits 且最终能和我们归一化后的 SKU 图对齐,image_pick 就会非空;若仍为空,请在 debug 里看该条 hit 是否还有 inner_hits,以及 exact_knn_rescore_window、是否关掉了 rescore 等。
关于「藏青」文本为什么也可能没命中
词表里 navy 已含「藏青、藏蓝、深蓝」。若 SKU 的 option1_value 写成 和词表不一致 的表述(例如只有英文色号、或「青色系」等),仍可能文本全挂,这时更依赖上面的 图像对齐;若图像也对不上,就会是 final_source: none + image_pick: null。
6-prompt
6-response
TODO
"inner_hits": {
"name": "exact_image_knn_query_hits",
"size": 1,
"_source": [
"url"
]
}
inner_hits获取exact_image_knn_query_hits的目的是:是因为哪个
参考: feat(search): 款式意图下统一 SKU 选取(option/taxonomy/图像)与属性值匹配增强
## 主要能力
- 在 rerank 窗口内对 hits 做 SKU 预决策:款式意图(多源同义词)+ 图像 KNN inner_hits URL 对齐 SKU.image_src,统一一次决策
、无级联 fallback。 - 区分文本证据强度:final_source ∈ {option, taxonomy, image, none};matched_sources 按意图记录 option 或 taxonomy;selected_text / rerank_suffix 回填真实命中片段(SKU option 原文或 taxonomy value 原文)。 - 权威规则:SKU 在已解析维度上有非空 option 值时仅以该值参与匹配;SPU 级 enriched_taxonomy_attributes 不覆盖与之一致的 SKU 级矛盾值(修复「taxonomy 把白色 SKU 当卡其色命中」)。 - 图像:nested image KNN / exact rescore 增加 inner_hits(url),用于 SKU 置顶时的视觉 tie-break(仅在文本命中集内)或无意图时纯图像置顶。 - 查询侧:DetectedStyleIntent 增加 all_terms(zh+en+attribute 并集),属性值匹配与意图词表一致。 - API:SpuResult 透出 enriched_attributes / enriched_taxonomy_attributes(避免 Pydantic 丢弃 ES 字段)。
## 属性值匹配(括号和分隔符)
- 在分词前对归一化后的 option/taxonomy 字符串执行 _with_segment_boundaries_for_matching:将全/半角括号、斜杠、顿号、中英文标点、中点、各类横线等替换为空格,再 simple_tokenize + 滑窗;无分隔的连续汉字仍走纯中文子串回退(如 卡其色棉)。
- 参数化测试覆盖多种括号与常见电商分隔写法。
## 编排与配置
- searcher:_should_run_sku_selection = 款式意图激活 或 存在 image_query_vector;prefetch _source 含 skus、option 名、enriched_taxonomy_attributes。
- es_query_builder:image knn / exact image rescore 的 nested 子句带 inner_hits。
## 测试与仓库
- tests/test_sku_intent_selector.py、tests/test_search_rerank_window.py 更新;移除已废弃的 embedding-fallback 集成断言。
- .gitignore:忽略 artifacts/search_evaluation/datasets/(本地评估大数据集,避免误提交)。
"rescore": { "window_size": 160, "query": { "score_mode": "total", "query_weight": 1, "rescore_query_weight": 0, "rescore_query": { "bool": { "should": [ { "script_score": { "name": "exact_text_knn_query", "query": { "exists": { "field": "title_embedding" } }, "script": { "source": "((dotProduct(params.query_vector, 'title_embedding') + 1.0) / 2.0) * params.boost", "params": { "boost": 4 } } } }, { "nested": { "path": "image_embedding", "name": "exact_image_knn_query", "score_mode": "max", "query": { "scriptscore": { "query": { "exists": { "field": "imageembedding.vector" } }, "script": { "source": "((dotProduct(params.query_vector, 'image_embedding.vector') + 1.0) / 2.0) * params.boost", "params": { "boost": 4 } } } }, "inner_hits": { "name": "exact_image_knn_query_hits", "size": 1, "source": [ "url" ] } } } ], "minimumshould_match": 1 } } } },