Youtu-Parsing模型ComfyUI插件开发指南:可视化文档解析流程搭建

你是不是也遇到过这样的烦恼?手里有一堆PDF、图片格式的文档,想快速提取里面的文字、表格,甚至把图片和文字分开,手动操作起来又慢又容易出错。虽然市面上有一些现成的文档解析工具,但往往不够灵活,没法根据自己的需求定制流程。

如果你恰好又是ComfyUI的爱好者,喜欢那种通过拖拽节点就能搭建复杂AI工作流的直观方式,那么今天这个教程就是为你准备的。我们将一起动手,为强大的Youtu-Parsing模型开发一个ComfyUI自定义节点插件。这样一来,原本需要写代码调用的复杂文档解析能力,就能变成一个可以随意拖拽、连接的可视化节点,无论是图文分离、表格识别还是关键信息提取,都能在你的工作流里轻松完成。

这个教程的目标很明确:即使你不是专业的Python开发者,只要对ComfyUI有一定了解,就能跟着步骤,一步步打造出属于自己的文档解析工具节点。我们会从最基础的节点定义讲起,一直到如何与解析API对接,最后再编排一个实用的工作流示例。整个过程,就像搭积木一样简单有趣。

1. 环境准备与项目初始化

在开始敲代码之前,我们得先把“工地”准备好。这里不需要复杂的深度学习环境,重点在于ComfyUI的插件开发框架。

首先,确保你已经安装好了ComfyUI。如果还没装,可以去官方仓库按照说明进行安装,过程并不复杂。我们的插件将作为一个独立的文件夹,放置在ComfyUI的 custom_nodes 目录下。

接下来,我们创建插件的核心目录结构。在你的 custom_nodes 文件夹里,新建一个文件夹,名字可以起得直观一些,比如 comfyui-youtu-parsing。然后,在这个文件夹里创建以下几个必要的文件:

comfyui-youtu-parsing/
├── __init__.py
├── nodes.py
└── web
    └── logo.png (可选,用于在节点列表中显示图标)
  • __init__.py:这是一个空文件,它的存在告诉Python,这个文件夹是一个可导入的包。
  • nodes.py:这是我们插件的心脏,所有自定义节点的代码都将写在这里。
  • web/logo.png:可选,放一个图标文件,可以让你的节点在ComfyUI的节点列表里更醒目。

现在,打开 nodes.py 文件,我们先来搭建一个最基础的骨架。导入ComfyUI开发所需的模块:

import torch
import numpy as np
import json
import requests
from PIL import Image
import io
import folder_paths
from comfy.sd import CLIP
from comfy.utils import common_upscale
import comfy.utils
import nodes

# 我们后续的节点类都将在这里定义

准备工作就绪,我们的“开发车间”已经搭建好了。接下来,就要开始设计这个解析节点的“蓝图”了。

2. 定义核心解析节点类

在ComfyUI里,一切功能都封装在节点(Node)中。我们要创建一个节点,它接收文档(图片或PDF路径),调用Youtu-Parsing服务,然后输出结构化的解析结果。

我们先来定义这个节点的类。在 nodes.py 文件中,添加如下代码:

class YoutuParsingNode:
    """
    一个ComfyUI自定义节点,用于调用Youtu-Parsing模型解析文档。
    支持图片和PDF文件,输出图文分离、表格结构化的JSON结果。
    """
    
    # 这两个变量是ComfyUI识别节点的关键
    @classmethod
    def INPUT_TYPES(cls):
        """
        定义节点的输入参数类型和UI显示。
        """
        return {
            "required": {
                "document_path": ("STRING", {
                    "default": "",
                    "multiline": False,
                    "dynamicPrompts": False,
                    "placeholder": "输入文档的完整路径,如:C:/docs/report.pdf"
                }),
                "parse_mode": (["full", "text_only", "table_only", "layout_only"], {
                    "default": "full"
                }),
            },
            "optional": {
                "api_endpoint": ("STRING", {
                    "default": "https://your-youtu-parsing-service.com/v1/parse",
                    "multiline": False,
                }),
                "api_key": ("STRING", {
                    "default": "your-api-key-here",
                    "multiline": False,
                }),
            }
        }
    
    # 这个字符串会显示在节点上
    RETURN_TYPES = ("STRING", "JSON")
    # 这是输出在UI上的标签名
    RETURN_NAMES = ("parsed_text", "structured_data")
    # 节点在菜单中的分类
    FUNCTION = "parse_document"
    CATEGORY = "Youtu-Parsing"
    
    # 节点的核心执行函数
    def parse_document(self, document_path, parse_mode, api_endpoint="", api_key=""):
        """
        调用Youtu-Parsing API解析文档。
        
        参数:
            document_path: 待解析文档的路径。
            parse_mode: 解析模式,如全文、仅文本等。
            api_endpoint: API服务地址。
            api_key: API密钥。
            
        返回:
            解析后的纯文本和结构化JSON数据。
        """
        
        # 1. 首先检查文件是否存在(简单示例)
        import os
        if not os.path.exists(document_path):
            # 在实际开发中,这里可以添加更友好的错误处理或使用ComfyUI的文件加载器
            print(f"[Youtu-Parsing] 警告:文件不存在 - {document_path}")
            # 返回空结果占位
            return ("", {})
        
        # 2. 准备调用API(这里用伪代码展示逻辑)
        # 实际调用需要根据Youtu-Parsing API的具体要求来构建请求
        print(f"[Youtu-Parsing] 开始解析文档: {document_path}, 模式: {parse_mode}")
        
        # 模拟一个API调用和响应处理的过程
        try:
            # 假设我们有一个函数来实际处理API调用
            structured_result = self._call_parsing_api(document_path, parse_mode, api_endpoint, api_key)
            
            # 从结构化结果中提取纯文本部分(示例)
            extracted_text = self._extract_text_from_result(structured_result)
            
            print(f"[Youtu-Parsing] 文档解析完成。")
            return (extracted_text, structured_result)
            
        except Exception as e:
            print(f"[Youtu-Parsing] 解析过程中发生错误: {e}")
            # 返回错误信息或空数据
            return (f"解析失败: {str(e)}", {})
    
    def _call_parsing_api(self, file_path, mode, endpoint, key):
        """
        模拟调用解析API。
        在实际开发中,这里需要替换为真实的HTTP请求代码。
        """
        # 这是一个占位函数。你需要根据Youtu-Parsing API的实际文档来实现它。
        # 通常包括:读取文件、构建请求头和数据、发送POST请求、处理响应。
        print(f"[模拟API调用] 向 {endpoint} 发送 {file_path} 的解析请求,模式: {mode}")
        
        # 返回一个模拟的结构化数据
        mock_result = {
            "document_info": {"filename": file_path.split("/")[-1], "mode": mode},
            "layout": [{"type": "text", "bbox": [10, 20, 100, 50], "content": "这是一个标题"}],
            "text_blocks": ["这是一个标题", "这是第一段正文..."],
            "tables": [{"data": [["姓名", "年龄"], ["张三", "25"]]}],
            "images": [{"bbox": [200, 300, 300, 400], "caption": "示例图片"}]
        }
        return mock_result
    
    def _extract_text_from_result(self, structured_data):
        """
        从结构化数据中拼接出纯文本。
        """
        text_blocks = structured_data.get("text_blocks", [])
        return "\n".join(text_blocks)

代码看起来有点长,但其实结构很清晰。INPUT_TYPES 定义了节点需要什么参数(比如文档路径、解析模式),FUNCTION 指定了当节点被执行时,该运行哪个函数。parse_document 方法就是我们的主逻辑,它负责组织整个解析流程。

现在,节点类定义好了,但我们还需要让ComfyUI知道它的存在。

3. 注册节点与对接真实API

定义好的类只是一个模板,我们需要把它“注册”到ComfyUI的系统里。在 nodes.py 文件的末尾,添加以下代码:

# 所有自定义节点都需要在这个字典里注册
NODE_CLASS_MAPPINGS = {
    "YoutuParsingNode": YoutuParsingNode
}

# 节点在UI菜单中显示的名字
NODE_DISPLAY_NAME_MAPPINGS = {
    "YoutuParsingNode": "Youtu Parsing (文档解析)"
}

这样,当你重启ComfyUI后,在节点搜索框里输入“Youtu Parsing”,应该就能找到我们刚刚创建的节点了,可以把它拖到画布上使用。

不过,我们之前的 _call_parsing_api 函数还是个“模拟器”。要让它真正工作,我们需要对接真实的Youtu-Parsing API。由于我无法得知该API的具体端点、请求格式和认证方式,这里提供一个通用的、基于 requests 库的实现框架,你需要根据官方文档填充细节:

    def _call_parsing_api_real(self, file_path, mode, endpoint, key):
        """
        真实调用Youtu-Parsing API的示例框架。
        **请根据实际API文档修改此函数。**
        """
        import requests
        
        # 1. 读取文件内容
        with open(file_path, 'rb') as f:
            file_data = f.read()
        
        # 2. 构建请求,根据API要求可能是multipart/form-data或JSON
        files = {'document': (file_path, file_data)}
        # 或者,如果API接受base64编码
        # import base64
        # payload = {
        #     'image': base64.b64encode(file_data).decode('utf-8'),
        #     'mode': mode
        # }
        
        headers = {
            'Authorization': f'Bearer {key}',  # 如果使用Bearer Token
            # 'X-API-Key': key,  # 或者其他认证头
        }
        
        params = {
            'parse_mode': mode
        }
        
        try:
            # 3. 发送POST请求
            # 注意:是使用`files`参数还是`data`/`json`参数,取决于API
            response = requests.post(
                endpoint,
                files=files,
                headers=headers,
                params=params,
                timeout=30  # 设置超时
            )
            response.raise_for_status()  # 如果状态码不是200,抛出异常
            
            # 4. 解析响应
            result = response.json()
            return result
            
        except requests.exceptions.RequestException as e:
            print(f"[Youtu-Parsing] API请求失败: {e}")
            if hasattr(e.response, 'text'):
                print(f"错误响应: {e.response.text}")
            raise  # 将异常抛给上层处理

parse_document 方法中的 self._call_parsing_api 调用替换为 self._call_parsing_api_real,并确保你的 api_endpointapi_key 输入正确,节点就应该能真正调用服务了。

一个功能完整的节点,除了核心逻辑,通常还需要一些“贴心”的辅助功能。

4. 完善节点功能与UI体验

为了让节点更好用,我们可以添加一些提升体验的功能。比如,直接从ComfyUI内置的文件选择器里选文件,而不是手动输入路径。

修改 INPUT_TYPES 中的 document_path 定义,我们可以尝试将其与ComfyUI的输入类型关联。不过,ComfyUI对文件路径输入没有直接的内置类型。一个更实用的方法是提供一个示例,并引导用户使用“Load Image”等节点获取路径,或者我们创建一个简单的文件选择字符串输入。

另一种思路是,我们的节点可以接收一个 IMAGE 类型的输入,直接处理ComfyUI工作流中的图片张量。我们来扩展一下节点,使其更灵活:

class YoutuParsingNodeAdvanced:
    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                # 方式一:直接输入图像张量(来自ComfyUI其他节点)
                "image_input": ("IMAGE",),
                "parse_mode": (["full", "text_only", "table_only", "layout_only"], {"default": "full"}),
            },
            "optional": {
                "api_endpoint": ("STRING", {"default": "https://your-api.com/parse"}),
                "api_key": ("STRING", {"default": ""}),
            }
        }
    
    RETURN_TYPES = ("STRING", "JSON", "IMAGE")
    RETURN_NAMES = ("parsed_text", "structured_data", "annotated_image")
    FUNCTION = "parse_image_tensor"
    CATEGORY = "Youtu-Parsing"
    OUTPUT_IS_LIST = (False, False, False)
    
    def parse_image_tensor(self, image_input, parse_mode, api_endpoint="", api_key=""):
        """
        接收IMAGE张量,将其转换为图片文件后调用解析API。
        并返回一个标注了解析结果的示意图(模拟)。
        """
        # 1. 将ComfyUI的IMAGE张量(形状为[N, H, W, C])转换为PIL Image
        # 这里假设我们处理批次中的第一张图
        import torch
        from PIL import Image
        import numpy as np
        
        i = 255. * image_input[0].cpu().numpy()
        img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
        
        # 2. 将PIL Image保存为临时文件,或直接转换为字节流用于API调用
        import io
        img_byte_arr = io.BytesIO()
        img.save(img_byte_arr, format='PNG')
        img_byte_arr = img_byte_arr.getvalue()
        
        # 3. 调用解析API(这里省略具体实现,沿用之前的框架)
        print(f"[Youtu-Parsing Adv] 解析图像张量,模式: {parse_mode}")
        # structured_data = self._call_api_with_bytes(img_byte_arr, ...)
        
        # 4. 模拟生成一个带标注的图像(例如,把原图加上一个边框作为示意)
        # 这里简单地将原图转换为张量返回
        annotated_tensor = image_input.clone()
        
        # 模拟解析结果
        mock_text = f"解析模式: {parse_mode}\n从图像中提取的模拟文本内容..."
        mock_json = {"status": "success", "mode": parse_mode}
        
        return (mock_text, mock_json, annotated_tensor)

这个高级版本节点可以直接连入图像处理流程。别忘了把它也注册到 NODE_CLASS_MAPPINGS 里。

节点功能丰富了,我们最终要把它用起来。来看看如何在工作流中编排它。

5. 编排可视化工作流示例

节点开发完成后,最大的乐趣就是用它来搭建工作流。我们设计一个简单的场景:加载一个包含文字和表格的文档图片,解析它,然后将提取出的结构化表格数据用文本形式展示出来。

在ComfyUI中,你可以通过右键菜单搜索并添加节点。一个可能的工作流编排如下(用文字描述节点连接):

  1. 加载图像:使用 Load Image 节点,载入你的文档图片。
  2. 文档解析:添加我们刚创建的 Youtu Parsing (文档解析) 节点。
    • Load Image 节点的 IMAGE 输出,连接到本节点的 image_input 输入。
    • 在节点的参数面板中,选择 parse_modefull(全面解析)。
    • 填写正确的 api_endpointapi_key
  3. 结果预览与使用
    • 节点的 parsed_text 输出可以连接到一个 Preview Text 节点,快速查看提取出的所有纯文本。
    • 节点的 structured_data 输出(JSON)可以连接到一个 JSON Path 或自定义的文本处理节点,来提取特定字段。例如,你可以写一个简单的Python节点,接收这个JSON,专门提取其中的表格数据,并格式化为Markdown表格字符串。
  4. 格式化输出:将处理后的表格字符串,再连接到一个 Preview Text 节点,就能在工作流界面直接看到从文档中识别并提取出来的规整表格了。

这个工作流的好处是可视化。你可以清晰地看到“原始图片” -> “解析服务” -> “提取文本/表格”的数据流动过程。如果解析结果不理想,你可以很容易地回到上一步,调整解析模式,或者对原图进行预处理(比如使用 Image Scale 节点调整大小,或用 Image Filter 节点增强对比度),然后再尝试解析,整个过程无需修改代码。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐