作者:来自 Elastic Mike Pellegrini
我们最新的 semantic_text 迭代带来了大量改进。除了简化 _source 中的表示之外,其好处还包括减少冗长程度、更高效的磁盘利用率以及更好地与其他 Elasticsearch 功能集成。你现在可以使用突出显示来检索与你的查询最相关的块。也许最重要的是,它现在是一个正式发的功能!
我们在 [semantic_text](https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-text.html "semantic_text")
字段类型上经历了一段漫长的旅程,而这一最新版本承诺让语义搜索变得前所未有的简单。除了在 _source
中简化 semantic_text
的表示外,它还带来了减少冗余、更高效的磁盘利用率以及更好的 Elasticsearch 其他功能集成等优势。你现在可以使用高亮功能来检索与你查询最相关的文本片段。而最重要的是,它现已成为一个正式发布的功能!
语义搜索的演进
多年来,我们对语义搜索的方法不断发展,目标是让它尽可能简单。在 semantic_text
字段类型推出之前,执行语义搜索需要手动完成以下步骤:
- 配置映射以兼容你的嵌入向量。
- 配置一个摄取管道,以使用机器学习模型生成嵌入向量。
- 使用该管道来摄取你的文档。
- 在查询时使用相同的机器学习模型为查询文本生成嵌入向量。
当时,我们称这种设置为 "简单",但我们知道,我们可以让它变得更简单。于是,semantic_text
诞生了。
初始阶段
我们在 Elasticsearch 8.15 中引入了 semantic_text
,目标是简化语义搜索。如果你对 semantic_text
还不熟悉,建议先阅读我们的原始博客文章,以了解我们的方法背景。
我们最初将 semantic_text
作为 Beta 功能发布,是有充分理由的。在软件开发中,有一句广为流传的真理:让某样东西变得简单,往往是极其困难的,semantic_text
也不例外。在它的背后,有许多复杂的组件协同工作,以实现这种 "魔法般" 的语义搜索体验。我们希望花时间确保这些组件正确无误,然后再将该功能正式推出。事实证明,这段时间的投入是值得的 ------ 我们在最初的方法上进行了多次迭代,增加了新功能,并优化了存储,使 semantic_text
变得更简单、更精炼,并且更具长期可维护性。
我们最初的实现依赖于修改 _source
来存储推理结果。这导致 semantic_text
字段具有相对复杂的子字段结构:
bash
1. GET test-index/_doc/doc1
2. {
3. "_index": "test-sparse",
4. "_id": "doc1",
5. "_source": {
6. "infer_field": {
7. "text": "these are not the droids you're looking for. He's free to go around",
8. "inference": {
9. "inference_id": "my-elser-endpoint",
10. "model_settings": {
11. "task_type": "sparse_embedding"
12. },
13. "chunks": [
14. {
15. "text": "these are not the droids you're looking for. He's free to go around",
16. "embeddings": {
17. "##oid": 1.9103845,
18. "##oids": 1.768872,
19. "free": 1.693662,
20. "dr": 1.6103356,
21. "around": 1.4376559,
22. "these": 1.1396849
24. ...
25. }
26. }
27. ]
28. }
29. }
30. }
31. }
这种结构带来了一些问题:
- 过于冗长。 除了原始值外,它还包含元数据和文本分片信息,使 API 响应既难以阅读,又比实际需要的更大。
- 增加了磁盘上的索引大小。 由于嵌入向量可能非常大,它们实际上被存储了两次:一次用于 Lucene 索引以支持检索,另一次存储在
_source
中。这极大地影响了semantic_text
在大规模数据集上的可扩展性。 - 管理起来不够直观。 原始提供的文本值存储在
text
子字段中,因此需要特殊处理才能在后续操作中获取该值。这导致semantic_text
字段的行为与文本字段族中的其他字段不一致,从而带来了一系列复杂的连锁反应,使得它更难集成到更高级的工作流中。
语义文本即文本
我们的改进版本优雅地解决了这些问题,通过优化 semantic_text
在 _source
中的表示方式,使其更加简洁。与其在 semantic_text
字段内直接存储元数据和文本分片信息的复杂子字段结构,我们现在改用一个 隐藏的元字段(metafield) 来处理这些内容。这意味着,我们不再需要修改 _source
来存储推理结果。
从实际效果来看,这一改进意味着你提交用于索引的文档 _source
,在检索时依然会以相同的 _source
返回,不会再被额外修改或扩展。
bash
1. GET test-index/_doc/doc1
2. {
3. "_index": "test-sparse",
4. "_id": "doc1",
5. "_source": {
6. "infer_field": "these are not the droids you're looking for. He's free to go around"
7. }
8. }
结构简化带来的变化
你会注意到,_source
结构中不再包含 text
或 inference
之类的子字段。现在,_source
保持与索引时提供的内容完全一致,变得更加简单!
🚨 注意 :如果你的代码依赖于解析
semantic_text
字段在搜索结果或 Get API 返回值中的子字段结构,这将是一个 破坏性变更 。也就是说,如果你以前解析的是infer_field.text
子字段的值,你需要更新代码,使其改为解析infer_field
的值。我们尽力避免破坏性变更,但由于_source
结构调整而移除子字段结构,这次变更不可避免。
这一简化带来的主要优势:
- 更易使用。 不再需要解析复杂的子字段结构来获取原始文本值,直接读取字段值即可。
- 减少冗余。 元数据和文本分片信息不会再干扰 API 响应,使其更加简洁。
- 提高磁盘利用率。 嵌入向量不再存储在
_source
中,减少了存储占用。 - 更好的集成性。 使
semantic_text
更好地支持 Elasticsearch 其他功能,如多字段(multi-fields)、文档的部分更新(partial updates)以及重新索引(reindexing)。
让我们稍微扩展一下最后一点,因为它涵盖了几个领域。通过这种简化,semantic_text 字段现在可以用作 multi-fields 的源和目标
perl
1. PUT multi-field-source-example-index
2. {
3. "mappings": {
4. "properties": {
5. "inference": {
6. "type": "semantic_text",
7. "fields": {
8. "text": {
9. "type": "text"
10. }
11. }
12. }
13. }
14. }
15. }
17. PUT multi-field-target-example-index
18. {
19. "mappings": {
20. "properties": {
21. "text": {
22. "type": "text",
23. "fields": {
24. "inference": {
25. "type": "semantic_text"
26. }
27. }
28. }
29. }
30. }
31. }
semantic_text
字段现在还支持通过 Bulk API 进行部分文档更新:
perl
1. PUT partial-update-example-index
2. {
3. "mappings": {
4. "properties": {
5. "inference": {
6. "type": "semantic_text"
7. },
8. "source_field": {
9. "type": "text",
10. "copy_to": "inference"
11. }
12. }
13. }
14. }
16. POST my-index/_doc/1
17. {
18. "inference": "a test value",
19. "source_field": "another test value"
20. }
22. POST my-index/_bulk
23. { "update": {"_id": "1"} }
24. { "doc": {"source_field": "a different test value"} }
现在,你可以将数据重新索引到使用不同 inference_id
的 semantic_text
字段中。
markdown
1. PUT source-index
2. {
3. "mappings": {
4. "properties": {
5. "inference": {
6. "type": "semantic_text",
7. "inference_id": "my-elser-endpoint"
8. }
9. }
10. }
11. }
13. PUT dest-index
14. {
15. "mappings": {
16. "properties": {
17. "inference": {
18. "type": "semantic_text",
19. "inference_id": "my-e5-endpoint"
20. }
21. }
22. }
23. }
25. POST dest-index/_doc/1
26. {
27. "inference": "a test value"
28. }
30. POST _reindex
31. {
32. "source": {
33. "index": "source-index"
34. },
35. "dest": {
36. "index": "dest-index"
37. }
38. }
语义高亮(Semantic Highlighting)
semantic_text
最受欢迎的功能请求之一,就是能够在字段内检索最相关的文本片段。这一功能对于 RAG(检索增强生成)应用至关重要。此前,我们(非官方地)通过 inner_hits
提供了一些临时解决方案,但现在我们决定淘汰 inner_hits
,转而采用更精简的方案:高亮(highlighting)。
高亮是一种常见的 词法搜索技术 ,通常用于文本字段。由于 semantic_text
属于文本字段家族,因此将高亮技术适配到 semantic_text
是合理的。为此,我们新增了 语义高亮(semantic highlighter),它可以帮助你检索与查询最相关的文本片段:
bash
1. PUT highlighting-index
2. {
3. "mappings": {
4. "properties": {
5. "inference": {
6. "type": "semantic_text"
7. }
8. }
9. }
10. }
12. POST highlighting-index/_doc/1
13. {
14. "inference": ["Yosemite is one of the most popular national parks in the USA", "Park visitors should dispose of their trash properly in bear-proof trash bins to avoid attracting bears", "Bears are quite clever and curious though, so visitors should always be on the lookout for bear activity regardless"]
15. }
17. GET highlighting-index/_search
18. {
19. "query": {
20. "semantic": {
21. "field": "inference",
22. "query": "Where should I throw away my trash?"
23. }
24. },
25. "highlight": {
26. "fields": {
27. "inference": {
28. "order": "score",
29. "number_of_fragments": 1
30. }
31. }
32. }
33. }
35. {
36. "took": 6,
37. "timed_out": false,
38. "_shards": {
39. "total": 1,
40. "successful": 1,
41. "skipped": 0,
42. "failed": 0
43. },
44. "hits": {
45. "total": {
46. "value": 1,
47. "relation": "eq"
48. },
49. "max_score": 15.898441,
50. "hits": [
51. {
52. "_index": "highlighting-index",
53. "_id": "1",
54. "_score": 15.898441,
55. "_source": {
56. "inference": [
57. "Yosemite is one of the most popular national parks in the USA",
58. "Park visitors should dispose of their trash properly in bear-proof trash bins to avoid attracting bears",
59. "Bears are quite clever and curious though, so visitors should always be on the lookout for bear activity regardless"
60. ]
61. },
62. "highlight": {
63. "inference": [
64. "Park visitors should dispose of their trash properly in bear-proof trash bins to avoid attracting bears"
65. ]
66. }
67. }
68. ]
69. }
70. }
73. GET highlighting-index/_search
74. {
75. "query": {
76. "semantic": {
77. "field": "inference",
78. "query": "Are bears smart?"
79. }
80. },
81. "highlight": {
82. "fields": {
83. "inference": {
84. "order": "score",
85. "number_of_fragments": 1
86. }
87. }
88. }
89. }
91. {
92. "took": 20,
93. "timed_out": false,
94. "_shards": {
95. "total": 1,
96. "successful": 1,
97. "skipped": 0,
98. "failed": 0
99. },
100. "hits": {
101. "total": {
102. "value": 1,
103. "relation": "eq"
104. },
105. "max_score": 18.584934,
106. "hits": [
107. {
108. "_index": "highlighting-index",
109. "_id": "1",
110. "_score": 18.584934,
111. "_source": {
112. "inference": [
113. "Yosemite is one of the most popular national parks in the USA",
114. "Park visitors should dispose of their trash properly in bear-proof trash bins to avoid attracting bears",
115. "Bears are quite clever and curious though, so visitors should always be on the lookout for bear activity regardless"
116. ]
117. },
118. "highlight": {
119. "inference": [
120. "Bears are quite clever and curious though, so visitors should always be on the lookout for bear activity regardless"
121. ]
122. }
123. }
124. ]
125. }
126. }
请参阅 semantic_text 文档,了解如何使用高亮功能的更多信息。
正式上线
随着 _source
表示方式的调整,我们现在正式宣布 semantic_text
已成为正式发布的功能 🎉!这意味着我们承诺不再对该功能进行破坏性更改,并正式支持在生产环境中使用。作为用户,你可以放心地将 semantic_text
集成到生产工作流中,Elastic 也将持续提供支持,确保长期稳定性。
从 Beta 迁移
为了确保从 Beta 版本的平稳迁移,所有在 Elasticsearch 8.15 至 8.17 版本创建的索引 ,或 在 1 月 30 日之前在 Serverless 中创建的索引 ,仍将按照 Beta _source
结构运行。换句话说,它们仍将使用 Beta 版本的 _source
表示方式。
我们建议尽早迁移到正式发布版本的 _source
结构。你可以通过 重新索引(reindexing)到新索引 来完成迁移:
markdown
1. PUT my-new-index
2. {
3. "mappings": {
4. "properties": {
5. "inference": {
6. "type": "semantic_text"
7. }
8. }
9. }
10. }
12. POST _reindex
13. {
14. "source": {
15. "index": "my-old-index"
16. },
17. "dest": {
18. "index": "my-new-index"
19. },
20. "script": {
21. "source": "ctx._source.inference = ctx._source.inference.text"
22. }
23. }
请注意 script
参数的使用,它用于适配 _source
表示方式的更改。该脚本会从 text
子字段提取值,并直接赋给 semantic_text
字段。
立即尝试
这些更改将在 Elasticsearch 8.18+ (Stack 版)中提供,但如果你想现在就体验,它们已在 Serverless 版本中可用。此外,它们还与我们同期推出的 语义搜索简化 方案完美结合。使用这两者,可以让你的语义搜索能力更上一层楼!
你可以通过 自助式 Search AI 实践课程 亲自体验向量搜索。现在就 开启免费云试用 ,或在本地安装 Elastic 进行尝试!
原文:Semantic Text: Simpler, better, leaner, stronger - Elasticsearch Labs