序言
最近在学习Azure AI Search(其实我好几年前接触过它,我记得2021年它还叫Azure Search,但那个时候还是传统search的路数,现在真的变化很大),想归纳整理一下我自己的理解。抛砖引玉,欢迎大家指教!
RAG
首先说一下RAG吧。
RAG(Retrieval Augmented Generation,检索增强生成)是现在非常火的AI技术,让LLM模型依据咱自己的知识库进行回答。RAG有两个核心步骤
- 检索(Retrieval):首先它从数据库、文档或 Web 等外部源检索相关信息。
- 生成(Generation):然后收集到相关信息后,就会将其用于指导和增强生成的回复
截止2025年的微软的技术体系里,其实有好几种方法来实现RAG(可能不止我说的这几种)
- Copilot Studio 这个我玩过,可以用public web site/sharepoint pages and documents/dataverse table/Azure AI Search来作为知识库,dataverse的数据它还能进行简单的聚合计算
- SharePoint Agent 用SharePoint站点内容创建的应答机器人,要是有Copilot 365license那就是免费的。去年出来的,我没有试过。。。
- Power BI Copilot这个好像要那个5000美刀一个月的PowerBI Premium Capacity license才行,实在没实力接触。。。Power BI的data model现在都叫"Semantic model"了(语义模型),这个功能应该很牛逼。
- 最后就是本文要说的Azure AI Search+Azure Open AI了. 检索(Retrieval) 交给Azure AI Search,生成(Generation) 交给Azure Open AI. 这个方案应该是最支持扩展的。
基于Azure AI Search的RAG Solution
以下是我自己的理解,可能有偏颇,欢迎大家指正。 (这个是微软官方的资料: learn.microsoft.com/en-us/azure... ,术语有点多,建议中文英文切换着看)
总的来说,Azure AI Search的RAG solution的后台部分主要由三大部分组成
- 数据源, 如果是文件的话一般是 Azure Blob Storage(大家可以理解成网盘),图中以下service都能成为Azure AI Search的数据源
- Azure AI Search,用来创建索引以及检索
- Azure Open AI,大语言模型,用以生成回复,在对源数据进行矢量化(vectorization)的时候也需要Azure Open AI参与
创建Azure AI Search Index(索引)
Index
创建Azure AI Search Index第一步就是连接数据源,然后创建index。这个章节讲的"index"其实是一个专门的术语,如果你把Azure AI Search想象成一个数据库,search这个行为想象成对数据库进行查询,那么"index"就类似数据库里的表,"index field"是表里的字段,具体的索引文件类似表里的每个具体的record。"index"就好比数据库里表的定义(类似SharePoint的Search Schema),如何填充这张表是"Indexer"的事情,index只关心这张表本身。
选择数据源后,Azure AI Search的向导会根据数据源的schema自动生成一些字段,你也可以自己做一些调整。
Azure AI Search Index里的字段其实可以分成两类,一种是支持传统full text search那种字段,字段有一堆是否searchable/facetable/retriveable/sortable的属性,和SharePoint Search非常相似。
你可以用这些属性来进一步缩小搜索范围,比如learn.microsoft.com/zh-cn/azure... 这个例子里就拿价格做过滤,然后按照地理位置远近排序
sql
POST /indexes/hotels/docs/search?api-version=2024-07-01
{ "search": "Spacious, air-condition* +\"Ocean view\"",
"searchFields": "description, title",
"searchMode": "any",
"filter": "price ge 60 and price lt 300",
"orderby": "geo.distance(location, geography'POINT(-159.476235 22.227659)')",
"queryType": "full" }
还有一种矢量索引字段 (Vector Index)。
Vector这个词我感觉我们国内通常翻译成"向量",高等数学,线性代数好像都用"向量",但是微软官网翻译成"矢量",咱们这里就沿用官方的御用翻译吧。
体验过传统search(现在各大主流搜索引擎好像都加了语义识别来精炼出关键词,现在体验又不一样了 )与基于大语言模型的AI的话,会发现他们的查询输入方式完全不一样。传统search实际上是关键词的search,把最主要的关键词放到查询里即可。你要是多加了一些次要的词的话很有可能什么都匹配不到了。传统search一般也不会让你打很多字上去。
大语言模型AI则是你说得越多,AI越是能理解你到底要什么,给出的回复质量越是高。它会判断你的具体意图到底是什么,如果你言简意赅的话还会帮你脑补补完你的意图(这点DeepSeek上体现得很明显,特别是你要选了"深度思考"的话,它会脑补出好多好多东西)
然后把分析出来的你的具体意图转化成高维空间向量(这个过程叫做"Embedding(嵌入)")
比如我输入"什么是人工智能",LLM的嵌入模型 (例如text-embedding-3-large)会把它转换为一个高维向量(例如:[0.23, -0.45, 0.89, ..., 0.12]
)。这个"Embedding "可能有点抽象,我(一个没怎么系统学过机器学习的学渣 )个人理解是这个向量体现对应事物的特征 (想起很久很久以前学校学的主成分分析 )。特征相似(语义接近)的事物在这个向量空间的位置也比较接近,这样就可以做比对查找了。这种搜索就是矢量搜索(Vector Search).
万事万物皆可"Embedding",传统search只能根据图片的标签来搜索,矢量索引就可以直接对比图片本身。
下图"text_vector"这个field就是一个矢量字段,它有3072个维度。 某个index document里具体的vector数据(可以看到右边缩略图里有很长的数据)
Vector Index有很多设置(比如哪些字段和vector index相关,embedding模型选哪种,向量化用的是哪种算法,如何压缩索引),需要配置相应的Vector Profile,概念还蛮多的。
创建包含矢量索引的Search Indexer的大致步骤
这是微软的原文:learn.microsoft.com/en-us/azure... ,我讲讲自己的理解。
有了数据库的表(index)之后,我们还得导入数据进去,这个活是由Indexer来干的。
一个比较典型的含有Vector的Indexer一般会包含以下步骤
- 连接数据源
- Document Trunk(把文件分成小块,不然模型一口吞不下)
- Embedding(嵌入)
- Content Enrichment(不知道怎么翻译成中文最好,"内容扩充"?这一步会产生一些额外的metadata)
- 最后一步叫"index mapping",把上述步骤产生的output给组装成json文件,最后塞到index(塞到数据库)里去。"index mapping"本身还分index projection/field mapping/output field mapping等好几种。
(这个是一个我项目里debug索引的截图,我感觉很适合拿来讲解创建索引的流程)
下面我们分别来介绍一下。
对文件进行分块(Chunk)
我们需要用嵌入模型(embedding model)来对文件向量化。大家知道,模型是有最大token的输入限制的(比如text-embedding-ada-002 model的最大token是8191,一个token对应4个character,8191个token大致是五六千英文单词),太大的文件不拆分肯定没法塞进去。所以第一步要对文件进行分块(Chunk)。
Chunk Document里详细介绍了文件分块的策略以及注意事项。Chunk通常是整个RAG解决方案的第一步,Chunk策略选择得当与否对整个RAG的效果有关键影响。要是Chunk做得不理想搞不好就"断章取义"了。
微软现在比较推荐用一种叫"Document Layout"的"Skill(技能)"来对文件进行Chunk。这种技能可以
根据段落或句子表示的语义上连贯的片段对内容进行分块。 然后,这些片段可以独立处理,并重新组合为语义表征,而不会丢失信息、解释或语义相关性。 文本的固有含义会用作分块过程的指导。
现在这个Skill还处于preview阶段,大家可以自己试试。
Chunk把一个大文件拆分成好几个小块,这样就会产生一对多索引(one to many index)以及父子层级关系.一般要求子级的索引也能追溯父节点的相关属性,例如文件名,作者,修改时间等,这就涉及了索引投影(index projection)以及创建父子索引对(简单地说就是Parend Index有个parent id字段,Child index也有parent id字段,这样把他们联系起来。
详情可以看learn.microsoft.com/zh-cn/azure...
Embedding
提到Embedding首先要选Embedding Model,Azure AI Search自己不提供,你得去Azure Open AI上创建embedding model
注意:Azure Open AI Service和Azure AI Search必须在同一个region(区域),不然他们之间没法互相调用
Content Enrichment
简单的说,Content Enrichment就是对原始数据进行处理,扩充索引字段,比如说用Entity Extract把内容里的邮箱地址给抓取出来,或者用OCR把图片里的文字给识别出来。从广义上来说,文件向量化这件事本身也是一种Content Enrichment。
每一种Content Enrichment的处理方式都是一个"Skill".微软有很多现成的Skill可以直接用。
Skill (技能)
Azure AI Search Index Pipeline的每一步操作都要用skill 来实现,比如上文说的"Chunk",你要选具体的skill来实现(前面说的"Document Layout"就是一个用于Chunk的skill)。skill设置里会有context(上下文),input(输入),output(输出)
一群skill组成一个Skill Set(技能集合),某些skill的output会是另一些skill的input,由此形成它们之间的依赖关系,成为整个一个pipeline。
一个skill set的实例
json
{
"@odata.etag": "\"0x8DD65BCE8953FA9\"",
"name": "vector-index-nasa-skillset",
"description": "Skillset to chunk documents and generate embeddings",
"skills": [
{
"@odata.type": "#Microsoft.Skills.Text.SplitSkill",
"name": "#1",
"description": "Split skill to chunk documents",
"context": "/document",
"defaultLanguageCode": "en",
"textSplitMode": "pages",
"maximumPageLength": 2000,
"pageOverlapLength": 500,
"maximumPagesToTake": 0,
"unit": "characters",
"inputs": [
{
"name": "text",
"source": "/document/content",
"inputs": []
}
],
"outputs": [
{
"name": "textItems",
"targetName": "pages"
}
]
},
{
"@odata.type": "#Microsoft.Skills.Text.AzureOpenAIEmbeddingSkill",
"name": "#2",
"context": "/document/pages/*",
"resourceUri": "https://xxx-ai-service-east-us.openai.azure.com",
"apiKey": "<redacted>",
"deploymentId": "text-embedding-3-large-3",
"dimensions": 3072,
"modelName": "text-embedding-3-large",
"inputs": [
{
"name": "text",
"source": "/document/pages/*",
"inputs": []
}
],
"outputs": [
{
"name": "embedding",
"targetName": "text_vector"
}
]
},
{
"@odata.type": "#Microsoft.Skills.Text.V3.EntityRecognitionSkill",
"name": "#3",
"context": "/document/pages/*",
"categories": [
"Location"
],
"defaultLanguageCode": "en",
"inputs": [
{
"name": "text",
"source": "/document/pages/*",
"inputs": []
}
],
"outputs": [
{
"name": "locations",
"targetName": "locations"
}
]
}
],
"cognitiveServices": {
"@odata.type": "#Microsoft.Azure.Search.AIServicesByKey",
"subdomainUrl": "https://xxx-ai-service-us-east.cognitiveservices.azure.com/"
},
"indexProjections": {
"selectors": [
{
"targetIndexName": "vector-index-nasa",
"parentKeyFieldName": "parent_id",
"sourceContext": "/document/pages/*",
"mappings": [
{
"name": "text_vector",
"source": "/document/pages/*/text_vector",
"inputs": []
},
{
"name": "chunk",
"source": "/document/pages/*",
"inputs": []
},
{
"name": "title",
"source": "/document/title",
"inputs": []
},
{
"name": "locations",
"source": "/document/pages/*/locations",
"inputs": []
}
]
}
],
"parameters": {
"projectionMode": "skipIndexingParentDocuments"
}
}
}
我个人感觉Skill很像Power Automate里的action,但是它的UI很弱,大多数时候你设置Skill都是直接编辑json文件,很多时候也不知道自己设得对不对。。。搞不清楚的话,在Debug Session里可以对Skill Set进行调试.
如果原生的Skill满足不了你,你还可以创建自定义的Skill,按照要求整一个Web API就行,具体例子可以看learn.microsoft.com/en-us/azure... 顺便说一句,这个扩展的方法和SharePoint Search (on-Premise)的Content Enrichment Web Service很像(我十多年前就搞过这个。。。)
Index Mapping
这是创建index pipeline的最后一步,把源数据或者之前产生的中间结果给mapping到之前定义好的search index字段里,最后生成index文件(azure ai search的索引的每一条记录都是一个json document)
它实际上有好几种情况,一种是对数据源本身的字段做mapping,比如说数据源是一个cosmos db,它的json schema有*_city这个字段,我们要把它mapping到 city*这个index字段。这个叫Field Mapping
json
"fieldMappings": [
{
"sourceFieldName": "_city",
"targetFieldName": "city",
"mappingFunction": null
}
]
output field mapping用来mapping content enrichment新产生的数据。但是我感觉index projection也能处理同样的事情(至少在nasa earth book那个教程里,我没用到output field mapping),我对这块不是很了解。
前两个mapping都是配置在indexer(中文好像翻译成索引器,下文会介绍)里,但是index projection是配置在skill set里。官网说index projection的典型应用是处理Chunk Document产生一对多的父子关系。
json
"indexProjections": {
"selectors": [
{
"targetIndexName": "vector-index-nasa",
"parentKeyFieldName": "parent_id",
"sourceContext": "/document/pages/*",
"mappings": [
{
"name": "text_vector",
"source": "/document/pages/*/text_vector",
"inputs": []
},
{
"name": "chunk",
"source": "/document/pages/*",
"inputs": []
},
{
"name": "title",
"source": "/document/title",
"inputs": []
},
{
"name": "locations",
"source": "/document/pages/*/locations",
"inputs": []
}
]
}
],
"parameters": {
"projectionMode": "skipIndexingParentDocuments"
}
}
Indexer
上述所有步骤/配置(连接数据源,创建index字段,Skill Set,index mapping等等)会组成Indexer。Indexer是管理Azure Search Index数据的枢纽,你可以看到这个index的具体配置信息
以及执行的结果
待续
我本来想先把基本概念一次性讲完的,结果写search index就断断续续花了一周时间。下篇会讲Azure AI Search如何查询数据,以及如何用检索出来的数据来增强LLM生成的答案。