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

Azure自定义技能提取D&D PDF表格遇验证错误,求解决与优化方案

问题描述

我正在开发Azure自定义技能,用于从大型文档(尤其是Dungeons & Dragons PDF)中提取表格。目前尝试通过识别属性块(stat block)中特有的挑战等级(Challenge Rating)来实现该功能,此逻辑在本地测试环境可正常运行,但部署为Azure自定义技能后,即便搭配OCR技能提取并转换文本数据,仍出现“data does not contain text”错误。所有待处理文档均为PDF格式。

我想了解是否可以移除该验证且不破坏技能功能,或者有无更优的技能编写方案?当前方案仅能提取挑战等级的实例,效果不够理想。

附上当前使用的Python代码:

import azure.functions as func
import json
import re
def main(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Python HTTP trigger function processed a request.')
    try:
        body = json.dumps(req.get_json())
    except ValueError:
        return func.HttpResponse(
             "Invalid body",
             status_code=400
        )
    if body:
        result = compose_response(body, identify_stat_blocks)
        return func.HttpResponse(result, mimetype="application/json")
    else:
        return func.HttpResponse(
             "Invalid body",
             status_code=400
        )
def identify_stat_blocks(text):
    if type(text) is not str:
        raise TypeError('Input data is not text/str')
    challenge = get_all_matches(text, ['(?i)Challenge[\\s]+[0-9\\s]+'])
    extracteds = {"Challenge Rating":challenge}
    return extracteds
def get_all_matches(text, reference_re):
    retlist = []
    for i in reference_re:
        matches = re.findall(i, text)
        if matches:
            retlist.extend(matches)
    if len(retlist) > 0:
        return retlist
    else:
        return None
def compose_response(json_data, func):
    values = json.loads(json_data)['values']
    # Prepare the Output before the loop
    results = {}
    results["values"] = []
    for value in values:
        output_record = transform_value(value, func)
        if output_record != None:
            results["values"].append(output_record)
    return json.dumps(results, ensure_ascii=False)
# Perform an operation on a record
def transform_value(value, func):
    try:
        recordId = value['recordId']
    except AssertionError  as error:
        return None
 # Validate the inputs
    try:         
        assert ('data' in value), "'data' field is required."
        data = value['data']        
        assert ('text' in data), "'text' field is required in 'data' object."      
    except AssertionError  as error:
        return (
            {
            "recordId": recordId,
            "errors": [ { "message": "Error:" + error.args[0] }   ]       
            })
    challenge = func(data['text'])
    return ({
            "recordId": recordId,
            "data": {
                "text": challenge
                    }
            })
解决方案

一、解决“data does not contain text”错误

1. 适配OCR输出字段

Azure认知搜索的OCR技能默认输出文本字段为content,而非你代码中校验的text。修改transform_value函数的校验逻辑,兼容两种字段:

# 修改transform_value中的验证与文本获取逻辑
def transform_value(value, func):
    try:
        recordId = value['recordId']
    except KeyError:
        return None
    # Validate the inputs
    try:         
        assert ('data' in value), "'data' field is required."
        data = value['data']        
        # 优先取OCR输出的content字段,兼容text字段
        input_text = data.get('content') or data.get('text')
        assert input_text is not None, "'text' or 'content' field is required in 'data' object."
    except AssertionError  as error:
        return (
            {
            "recordId": recordId,
            "errors": [ { "message": "Error:" + error.args[0] }   ]       
            })
    challenge = func(input_text)
    return ({
            "recordId": recordId,
            "data": {
                "text": challenge
                    }
            })

2. 移除验证的风险与替代方案

直接移除assert ('text' in data)会导致后续func(data['text'])抛出KeyError,破坏功能。如果要保留容错性,可改为:

# 修改transform_value中的文本获取逻辑
data = value['data']
input_text = data.get('text') or data.get('content') or ''

# 同步修改identify_stat_blocks处理空文本情况
def identify_stat_blocks(text):
    if not isinstance(text, str):
        text = str(text) if text is not None else ''
    if not text:
        return {"Challenge Rating": []}
    challenge = get_all_matches(text, ['(?i)Challenge[\\s]+[0-9\\s]+'])
    # 将None转为空列表,避免返回null引发后续问题
    challenge = challenge if challenge is not None else []
    extracteds = {"Challenge Rating": challenge}
    return extracteds

二、优化技能提取能力(从单一挑战等级到完整属性块)

针对D&D属性块的固定结构,增强正则匹配规则,提取更多核心字段:

def identify_stat_blocks(text):
    if not isinstance(text, str):
        text = str(text) if text is not None else ''
    if not text:
        return {}
    
    # 覆盖D&D属性块核心字段的正则规则
    stat_patterns = {
        "Challenge Rating": r'(?i)Challenge\s+([0-9]+(?:\/[0-9]+)?)\s*(?:\(([^\)]+)\))?',
        "Armor Class": r'(?i)Armor Class\s+([0-9]+)(?:\s*\(([^\)]+)\))?',
        "Hit Points": r'(?i)Hit Points\s+([0-9]+)\s*\(([^\)]+)\)',
        "Speed": r'(?i)Speed\s+([^\n]+)',
        "Strength": r'(?i)Strength\s+([0-9]+)\s*\(([+-][0-9]+)\)',
        "Dexterity": r'(?i)Dexterity\s+([0-9]+)\s*\(([+-][0-9]+)\)',
        "Constitution": r'(?i)Constitution\s+([0-9]+)\s*\(([+-][0-9]+)\)',
        "Intelligence": r'(?i)Intelligence\s+([0-9]+)\s*\(([+-][0-9]+)\)',
        "Wisdom": r'(?i)Wisdom\s+([0-9]+)\s*\(([+-][0-9]+)\)',
        "Charisma": r'(?i)Charisma\s+([0-9]+)\s*\(([+-][0-9]+)\)'
    }
    
    extracted_data = {}
    for field, pattern in stat_patterns.items():
        match = re.search(pattern, text)
        if match:
            if len(match.groups()) == 2:
                extracted_data[field] = f"{match.group(1)} ({match.group(2)})"
            else:
                extracted_data[field] = match.group(1)
    
    return extracted_data

批量提取多属性块

如果文档包含多个怪物属性块,可通过分割文本实现批量提取:

def identify_stat_blocks(text):
    if not isinstance(text, str):
        text = str(text) if text is not None else ''
    if not text:
        return []
    
    # 以"Challenge"或空行+大写名称为分隔符,拆分多个属性块
    block_separators = r'(?=\nChallenge\s+|\n\n[A-Z][a-zA-Z\s]+:)'
    stat_blocks = re.split(block_separators, text)
    
    extracted_blocks = []
    stat_patterns = {
        # 同上的正则规则字典
        "Challenge Rating": r'(?i)Challenge\s+([0-9]+(?:\/[0-9]+)?)\s*(?:\(([^\)]+)\))?',
        "Armor Class": r'(?i)Armor Class\s+([0-9]+)(?:\s*\(([^\)]+)\))?',
        # ...其他字段
    }
    
    for block in stat_blocks:
        block_data = {}
        for field, pattern in stat_patterns.items():
            match = re.search(pattern, block)
            if match:
                if len(match.groups()) == 2:
                    block_data[field] = f"{match.group(1)} ({match.group(2)})"
                else:
                    block_data[field] = match.group(1)
        if block_data:
            extracted_blocks.append(block_data)
    
    return extracted_blocks

三、部署后调试建议

  • 在Azure函数日志中查看实际传入的data结构,确认OCR输出字段名;
  • 使用Postman模拟认知搜索的输入格式,直接向Azure函数发送请求验证逻辑;
  • 在transform_value函数中添加print(data)日志,排查输入数据的实际内容。

内容的提问来源于stack exchange,提问作者howlieT

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 10:51:11