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
相关产品推荐
相关产品推荐

