Azure Function未触发:自定义Web API技能提取PDF页码失败排查
问题:Azure搜索技能集未调用自定义Web API,导致
page_number字段始终为null 我正在提取纯文本PDF的页码,已在搜索索引中创建page_number字段。使用Azure Functions搭建了自定义Web API技能,并在索引器定义中通过OutputFieldMappings数组映射技能enrichment输出。
疑问
- 我假设技能集发送给自定义API的请求数据格式如下,是否符合实际?
{ "data": "BASE64 ENCODED STRING OF A JPEG IMAGE", "width": 500, "height": 300, "originalWidth": 5000, "originalHeight": 3000, "rotationFromOriginal": 90, "contentOffset": 500, "pageNumber": 2 } - 如何测试技能集的enrichment步骤?
当前现象
运行索引器后,Azure Function应用的日志流无任何信息日志,怀疑技能集未触发自定义Web API调用,导致搜索索引中page_number字段始终为null。尝试修改自定义字段的上下文,无效果。
相关定义
技能集定义
{ "@odata.context": "https://ai-studio-search-test.search.windows.net/$metadata#skillsets/$entity", "@odata.etag": "\"0x8DC9745F955DC29\"", "name": "test-skillset", "description": "Skillset to chunk documents and generating 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, "inputs": [ { "name": "text", "source": "/document/content" } ], "outputs": [ { "name": "textItems", "targetName": "pages" } ] }, { "@odata.type": "#Microsoft.Skills.Text.AzureOpenAIEmbeddingSkill", "name": "#2", "description": "Skill to generate embeddings via Azure OpenAI", "context": "/document/pages/*", "resourceUri": "https://crowemind-non-prd-us-east-2.openai.azure.com", "apiKey": "<redacted>", "deploymentId": "text-embedding-ada-002", "dimensions": 1536, "modelName": "text-embedding-ada-002", "inputs": [ { "name": "text", "source": "/document/pages/*" } ], "outputs": [ { "name": "embedding", "targetName": "vector" } ], "authIdentity": null }, { "@odata.type": "#Microsoft.Skills.Custom.WebApiSkill", "name": "#3", "description": "", "context": "/document/normalized_images/*", "uri": "https://azure-searchskill-apis.azurewebsites.net/api/pageno_skillapi_anonymous", "httpMethod": "POST", "timeout": "PT30S", "batchSize": 1, "degreeOfParallelism": 1, "authResourceId": null, "inputs": [ { "name": "normalized_images", "source": "/document/normalized_images/*" } ], "outputs": [ { "name": "page_number", "targetName": "page_number" } ], "httpHeaders": {}, "authIdentity": null } ], "cognitiveServices": null, "knowledgeStore": null, "indexProjections": { "selectors": [ { "targetIndexName": "test-index-secondary", "parentKeyFieldName": "parent_id", "sourceContext": "/document", "mappings": [ { "name": "chunk", "source": "/document/pages/*", "sourceContext": null, "inputs": [] }, { "name": "text_vector", "source": "/document/pages/*/vector", "sourceContext": null, "inputs": [] }, { "name": "title", "source": "/document/metadata_storage_name", "sourceContext": null, "inputs": [] }, { "name": "page_number", "source": "/document/normalized_images/*/page_number", "sourceContext": null, "inputs": [] } ] } ], "parameters": { "projectionMode": "skipIndexingParentDocuments" } }, "encryptionKey": null }
Azure Function代码
@app.route(route="pageno_skillapi_anonymous", auth_level=func.AuthLevel.ANONYMOUS) def pageno_skillapi_anonymous(req: func.HttpRequest) -> func.HttpResponse: payload = req.get_json() if payload: logging.info("request body received from /document/normalized_images/* context") logging.info(f"payload: ${payload}") if "pageNumber" in payload: page_number = payload return func.HttpResponse(str(payload["pageNumber"]), status_code=200) else: return func.HttpResponse("No pageNumber parameter in /document/normalized_images/* context", status_code=200) else: logging.info("request body is empty") return func.HttpResponse("request body is empty", status_code=200)
索引器定义
{ "@odata.context": "https://ai-studio-search-test.search.windows.net/$metadata#indexers/$entity", "@odata.etag": "\"0x8DC976AFC2C0D01\"", "name": "test-indexer", "description": "Indexer to index documents and generate embeddings", "dataSourceName": "test-blob", "skillsetName": "test-skillset", "targetIndexName": "test-index", "disabled": false, "schedule": null, "parameters": { "batchSize": null, "maxFailedItems": null, "maxFailedItemsPerBatch": null, "base64EncodeKeys": null, "configuration": { "dataToExtract": "contentAndMetadata", "parsingMode": "default", "imageAction": "generateNormalizedImagePerPage" } }, "fieldMappings": [ { "sourceFieldName": "metadata_storage_name", "targetFieldName": "title", "mappingFunction": null } ], "outputFieldMappings": [ { "sourceFieldName": "/document/normalized_images/*/page_number", "targetFieldName": "page_number" } ], "cache": null, "encryptionKey": null }
排查与修复建议
技能输入格式与Function适配问题
- Azure自定义Web API技能的请求格式是固定的,会发送包含
values数组的JSON,每个元素含recordId和data字段,而非你假设的单个对象。示例请求格式:{ "values": [ { "recordId": "1", "data": { "pageNumber": 2 } } ] } - 技能输入需修改为单独映射
pageNumber字段:"inputs": [ { "name": "pageNumber", "source": "/document/normalized_images/*/pageNumber" } ] - Function代码需适配该格式,示例修改:
@app.route(route="pageno_skillapi_anonymous", auth_level=func.AuthLevel.ANONYMOUS) def pageno_skillapi_anonymous(req: func.HttpRequest) -> func.HttpResponse: payload = req.get_json() results = [] if payload and "values" in payload: for item in payload["values"]: record_id = item["recordId"] page_num = item["data"].get("pageNumber", None) results.append({ "recordId": record_id, "data": {"page_number": page_num}, "errors": [], "warnings": [] }) return func.HttpResponse(json.dumps({"values": results}), status_code=200, mimetype="application/json") else: logging.info("request body is invalid") return func.HttpResponse("invalid request format", status_code=400)
- Azure自定义Web API技能的请求格式是固定的,会发送包含
上下文与索引投影关联问题
当前技能集拆分的pages(文本块)和normalized_images(页码来源)是独立数组,无关联逻辑,导致即使获取到页码,也无法对应到正确的文本块。需通过contentOffset字段建立关联,或调整技能上下文为/document/pages/*,通过文本块的偏移量匹配对应页码。测试方法
- 用Postman/curl模拟技能集的标准请求格式调用Function,验证返回结果是否符合要求。
- 在Azure门户查看搜索索引器的运行历史和详细日志,检查是否有技能调用失败、跳过的记录。
- 启用技能集调试模式,查看enrichment流程中的数据流动,确认
normalized_images是否生成、自定义技能是否触发。
内容的提问来源于stack exchange,提问作者MajorMajorMajorMajor
相关产品推荐
相关产品推荐

