处理除第一张以外的其他图片,使用首图的 CSV 结果作为 Few-shot 上下文。
(semaphore: asyncio.Semaphore, img_file: Path, csv_file: Path, output_path: Path)
| 319 | content_list.extend([ |
| 320 | {"type": "text", "text": "参考示例(第一页图片及其正确的 CSV 格式层级分析结果):"}, |
| 321 | {"type": "image_url", "image_url": {"url": FIRST_PAGE_EXAMPLE["image_base64"]}}, |
| 322 | {"type": "text", "text": f"参考结果 (CSV 格式):\n{FIRST_PAGE_EXAMPLE['result_csv_str']}"}, |
| 323 | {"type": "text", "text": "---\n请严格参照上述示例的 CSV 格式和层级判断标准,分析以下当前页图片:"} |
| 324 | ]) |
| 325 | else: |
| 326 | write_log(f"警告:未找到首图示例,将无参考处理 {img_file.name}") |
| 327 | |
| 328 | # 添加当前页图片和提示词 |
| 329 | content_list.extend([ |
| 330 | {"type": "image_url", "image_url": {"url": get_encoded_image(img_file)}}, |
| 331 | {"type": "text", "text": f"{PROMPT_TEXT}\n\n当前页提取的原始 CSV 数据如下:\n{csv_content}"} |
| 332 | ]) |
| 333 | |
| 334 | messages = [{"role": "user", "content": content_list}] |
| 335 | |
| 336 | try: |
| 337 | for attempt in range(MAX_RETRIES): |
| 338 | try: |
| 339 | response = await client.chat.completions.create( |
| 340 | model=LLM_CONFIG["model"], |
| 341 | messages=messages, |
| 342 | extra_body={"enable_thinking": False}, |
| 343 | timeout=REQUEST_TIMEOUT, |
| 344 | temperature=0, |
| 345 | ) |
| 346 | |
| 347 | content = response.choices[0].message.content.strip() |
| 348 | |
| 349 | # 本地解析 CSV 转为 JSON |
| 350 | try: |
| 351 | parsed_data = parse_csv_response(content, img_file.name) |
| 352 | except Exception as parse_err: |
| 353 | write_log(f"第 {attempt+1} 次尝试解析 CSV 失败:{parse_err}") |
| 354 | if attempt == MAX_RETRIES - 1: |
| 355 | return False |
| 356 | await asyncio.sleep(2 ** attempt) |
| 357 | continue |
| 358 | |
| 359 | if parsed_data: |
| 360 | sorted_data = sorted(parsed_data, key=lambda x: x['number']) |
| 361 | |
| 362 | with open(output_file, 'w', encoding='utf-8') as f: |
| 363 | json.dump(sorted_data, f, ensure_ascii=False, indent=2) |
| 364 | |
| 365 | print(f"已判断层级:{img_file.name}") |
| 366 | return True |
| 367 | else: |
| 368 | write_log(f"解析结果为空:{img_file.name}") |
| 369 | if attempt == MAX_RETRIES - 1: |
| 370 | return False |
| 371 | await asyncio.sleep(2 ** attempt) |
| 372 | |
| 373 | except (APIError, Timeout) as e: |
| 374 | write_log(f"第 {attempt+1} 次 API 请求失败 ({type(e).__name__}): {str(e)}") |
| 375 | if attempt == MAX_RETRIES - 1: |
| 376 | return False |
| 377 | await asyncio.sleep(2 ** attempt) |
| 378 | except Exception as e: |
no test coverage detected