You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用自定义技能填充Azure AI Search的ParsedDate字段?

问题:Azure Search中ParsedDate字段始终为Null的排查与解决

我正尝试填充Azure Search Index中的ParsedDate字段,以便后续借助Freshness特性创建评分配置文件优化搜索结果。已按文档定义索引、技能集和索引器,但运行索引器后该字段始终为Null,尝试过在索引器中使用out_field_mappings也无效。

技能集定义

{
    "@odata.type": "#Microsoft.Skills.Custom.WebApiSkill",
    "name": "#0",
    "description": "A custom skill that parses dates from file names",
    "uri": "https://az-function.azurewebsites.net/api/custom-skill-date-extraction?code=ABC",
    "httpMethod": "POST",
    "timeout": "PT30S",
    "batchSize": 1,
    "context": "/document",
    "inputs": [
        {
            "name": "fileName",
            "source": "/document/metadata_storage_path" 
        }
    ],
    "outputs": [
        {
            "name": "parsedDate",
            "targetName": "parsedDate"
        }
    ]
}

索引定义

def main(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Python HTTP trigger function processed a request.')

    # Environment Variables
    endpoint = os.environ["AZURE_SEARCH_SERVICE_ENDPOINT"]
    endpoint_openai = os.environ["AZURE_OPENAI_ENDPOINT"]
    deployment_id = os.environ["AZURE_OPENAI_EMBEDDING_DEPLOYMENT_ID"]
    credential_search = AzureKeyCredential(os.environ["AZURE_SEARCH_ADMIN_KEY"]) if os.environ["AZURE_SEARCH_ADMIN_KEY"] else DefaultAzureCredential()
    credential = DefaultAzureCredential()
    customer = readRequestBody(req)
    _ , datasource_name = utils.getStorageAccountInfo(customer, credential)
    index_name = utils.get_index_name( datasource_name)

    # Logic for creating a search index
    try:
        index_client = SearchIndexClient(endpoint=endpoint, credential=credential_search)
        fields = [
            SearchField(name="parent_id", type=SearchFieldDataType.String, sortable=True, filterable=True, facetable=True),
            SearchField(name="parsedDate", type=SearchFieldDataType.DateTimeOffset, sortable=True, filterable=True, facetable=True),
            SearchField(name="title", type=SearchFieldDataType.String),
            SearchField(name="chunk_id", type=SearchFieldDataType.String, key=True, sortable=True, filterable=True, facetable=True, analyzer_name="keyword"),
            SearchField(name="chunk", type=SearchFieldDataType.String, sortable=False, filterable=False, facetable=False),
            SearchField(name="vector", type=SearchFieldDataType.Collection(SearchFieldDataType.Single), vector_search_dimensions=1536, vector_search_profile_name="myHnswProfile"),
        ]

        vector_search = VectorSearch(  
    algorithms=[  
        HnswAlgorithmConfiguration(  
            name="myHnsw",  
            parameters=HnswParameters(  
                m=4,  
                ef_construction=400,  
                ef_search=500,  
                metric=VectorSearchAlgorithmMetric.COSINE,  
            ),  
        ),  
        ExhaustiveKnnAlgorithmConfiguration(  
            name="myExhaustiveKnn",  
            parameters=ExhaustiveKnnParameters(  
                metric=VectorSearchAlgorithmMetric.COSINE,  
            ),  
        ),  
    ],  
    profiles=[  
        VectorSearchProfile(  
            name="myHnswProfile",  
            algorithm_configuration_name="myHnsw",  
            vectorizer="myOpenAI",  
        ),  
        VectorSearchProfile(  
            name="myExhaustiveKnnProfile",  
            algorithm_configuration_name="myExhaustiveKnn",  
            vectorizer="myOpenAI",  
        ),  
    ],  
    vectorizers=[  
        AzureOpenAIVectorizer(  
            name="myOpenAI",  
            kind="azureOpenAI",  
            azure_open_ai_parameters=AzureOpenAIParameters(  
                resource_uri=endpoint_openai,  
                deployment_id=deployment_id,  
                api_key=credential_search,  
            ),  
        ),  
    ],  
)  
        semantic_search = SemanticSearch(configurations=[SemanticConfiguration(
            name="my-semantic-config",
            prioritized_fields=SemanticPrioritizedFields(content_fields=[SemanticField(field_name="chunk"),SemanticField(field_name="title")] )
        )])

        index = SearchIndex(name=index_name, fields=fields, vector_search=vector_search, semantic_search=semantic_search)
        result = index_client.create_or_update_index(index)
        return func.HttpResponse(f"{result.name} created", status_code=200)
    except Exception as e:
        return func.HttpResponse(f"Failed to create or update the index. Error: {str(e)}", status_code=500)

索引器配置

def main(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Python HTTP trigger function processed a request.')
    
    # Environment Variables
    endpoint = os.environ["AZURE_SEARCH_SERVICE_ENDPOINT"]
    credential_search = AzureKeyCredential(os.environ["AZURE_SEARCH_ADMIN_KEY"])
    customer = readRequestBody(req)
    credential = DefaultAzureCredential()
    _, data_source_name= utils.getStorageAccountInfo(customer, credential)
    index_name = utils.get_index_name(data_source_name)
    skillset_name = utils.get_skillset_name(data_source_name)

    
    # Indexer creation logic
    try:
        indexer_name = f"{data_source_name}-indexer"
        indexer = SearchIndexer(
            name=indexer_name,
            description="Indexer to index documents and generate embeddings",
            skillset_name=skillset_name,
            target_index_name=index_name,
            data_source_name=data_source_name,
            field_mappings=[FieldMapping(source_field_name="metadata_storage_name", target_field_name="title"),
                            FieldMapping(source_field_name="parsedDate", target_field_name="parsedDate")],
            parameters=IndexingParameters(
                configuration={
                    "dataToExtract": "contentAndMetadata",
                    "imageAction": "generateNormalizedImages"
                }
            )
        )
        
        indexer_client = SearchIndexerClient(endpoint, credential_search)
        indexer_result = indexer_client.create_or_update_indexer(indexer)
        
        # Run the indexer
        indexer_client.run_indexer(indexer_name)
        message = f'{indexer_name} is created and running. If queries return no results, please wait a bit and try again.'
        logging.info(message)
        return func.HttpResponse(message, status_code=200)
    except Exception as e:
        error_message = f"Failed to create or run the indexer. Error: {str(e)}"
        logging.error(error_message)
        return func.HttpResponse(error_message, status_code=500)

解决建议

  • 检查自定义WebApi技能的输出格式:确保Azure Function返回的JSON严格遵循技能要求的格式,必须包含values数组,每个元素需有recordId和data字段,data中的parsedDate需为ISO 8601标准的DateTimeOffset格式(如2024-05-20T12:34:56Z)。示例返回格式:
{
  "values": [
    {
      "recordId": "1",
      "data": {
        "parsedDate": "2024-05-20T12:34:56Z"
      }
    }
  ]
}
  • 修正索引器的字段映射逻辑:技能生成的字段需使用output_field_mappings而非普通field_mappings,因为后者仅映射数据源原始字段。修改索引器配置中的映射部分:
output_field_mappings=[
    OutputFieldMapping(
        source_field_name="/document/parsedDate",
        target_field_name="parsedDate"
    )
]

同时移除field_mappings中关于parsedDate的条目。

  • 验证技能输入的数据源字段:确认/document/metadata_storage_path传递给自定义技能的内容是否为预期的文件名(可能包含存储容器前缀,需在技能中先截取有效部分再解析日期)。可通过索引器执行日志查看输入值是否正确。

  • 检查索引器执行日志:在Azure门户的Azure Search资源中查看索引器运行历史,排查是否存在技能调用失败、返回格式错误或数据类型不匹配的警告,日志会明确指出问题原因。

  • 确认字段类型兼容性:确保自定义技能返回的parsedDate格式与索引中DateTimeOffset类型完全兼容,非ISO标准的日期字符串会导致转换失败,字段值变为Null。

内容的提问来源于stack exchange,提问作者E.Brum

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 03:04:56