Eigent PDF Skill 实战指南:SKILL.md、配套脚本与 PDF 处理全工作流
2026/9/14 3:18:05 网站建设 项目流程

Eigent PDF Skill 实战指南:SKILL.md、配套脚本与 PDF 处理全工作流

【免费下载链接】eigentEigent: The Open Source Cowork Desktop - Local and Free Alternative to Claude Cowork and Codex项目地址: https://gitcode.com/GitHub_Trending/ei/eigent

Eigent 是一个开源的 Cowork 桌面应用,它通过「Skill(技能)」机制把可复用的领域知识以SKILL.md+ 配套脚本的形式交给 Agent。本文以仓库中内置的示例技能 PDF 技能 为主体,完整讲解它声明的能力边界、触发条件、核心代码与命令、表单填充工作流,并结合 Eigent 的源码说明这个技能是如何被分发、注册、按用户和 Agent 授权加载的——读完后你既掌握一套可直接运行的 PDF 处理工具链(pypdf / pdfplumber / reportlab / qpdf / OCR),也理解 Eigent 中 Skill 的完整生命周期。

技能定位:SKILL.md 的 frontmatter 与触发条件

resources/example-skills/pdf/SKILL.md 是一份典型的 Eigent/CAMEL 风格技能文件,开头使用 YAML frontmatter 声明元数据:

--- name: pdf description: Use this skill whenever the user wants to do anything with PDF files. This includes reading or extracting text/tables from PDFs, combining or merging multiple PDFs into one, splitting PDFs apart, rotating pages, adding watermarks, creating new PDFs, filling PDF forms, encrypting/decrypting PDFs, extracting images, and OCR on scanned PDFs to make them searchable. If the user mentions a .pdf file or asks to produce one, use this skill. license: Proprietary. LICENSE.txt has complete terms ---
  • name:技能的唯一标识名。Eigent 后端在扫描技能目录时,正是用正则^\s*name\s*:\s*(.+)$从 frontmatter 中解析它(见 _parse_skill_frontmatter),并以此作为 skills-config.json 中的技能键。
  • description:这段描述实际上是给 LLM 的「触发器」。它列举了全部触发场景——读取/提取文本与表格、合并、拆分、旋转、加水印、新建 PDF、填表单、加解密、提取图片、扫描件 OCR——并明确「只要用户提到.pdf文件或要求生成 PDF,就应使用本技能」。在 Eigent 的 Agent 框架中,技能描述会被纳入 Agent 的工具上下文,description 写得越具体,命中越准。
  • license:指向同目录的 LICENSE.txt,说明该技能内容为专有授权。

正文部分按「Quick Start → Python 库 → 命令行工具 → 常见任务 → Quick Reference → Next Steps」组织,并明确把进阶内容分流到 reference.md、把表单填充分流到 forms.md。下面按这条脉络展开。

技能如何进入 Eigent:分发、注册与访问控制

resources/example-skills/是仓库内置的「示例技能源」。Eigent 后端启动扫描时会把它同步到用户主目录:

  • skill_service.py 中SKILLS_ROOT = ~/.eigent/skillssync_example_skills()会逐个检查resources/example-skills/下带SKILL.md的目录:不存在的复制过去并写入标记文件.eigent-example-skill;已存在且内容变化的由托管副本自动更新;若本地已被用户改过(无标记且名称不匹配)则跳过并记skipped,避免覆盖用户修改(见 sync_example_skills)。
  • 示例源目录的查找顺序由_candidate_example_skill_roots()决定:环境变量EIGENT_EXAMPLE_SKILLS_DIR优先,其次是打包应用内的example-skills、开发仓库的resources/example-skills等(源码 L56-L84),因此开发环境和 Electron 打包环境行为一致。

注册之后,技能的启用状态与授权范围记录在用户级配置~/.eigent/user_<id>/skills-config.json中(skill_config_service.py)。对每个示例技能,skill_config_init()会写入形如:

{ "version": 1, "skills": { "pdf": { "enabled": true, "scope": { "isGlobal": true, "selectedAgents": [] }, "addedAt": 1730000000000, "isExample": true } } }

Agent 侧的裁决逻辑在 SkillToolkit 中:

  • 配置合并_get_merged_skill_config()先加载用户级全局配置,再叠加项目级<wd>/.eigent/skills-config.json,项目级优先(L82-L117)。
  • 启用判定:配置中不存在的技能默认启用(Not configured = enabled by default);enabled: false则禁用。
  • Agent 授权scope.isGlobal: true时所有 Agent 可用;否则只有selectedAgents列表中的 Agent 可访问;没有提供agent_name时受限技能一律拒绝。
  • 技能发现优先级_skill_roots):仓库级<wd>/skills<wd>/.eigent/skills<wd>/.camel/skills<wd>/.agents/skills> 用户级~/.eigent/skills~/.camel/skills~/.config/camel/skills> 系统级/etc/camel/skills

也就是说:内置 PDF 技能会被同步到~/.eigent/skills/pdf/,默认全局启用;你可以用 init_skills_config.py 这类脚本初始化配置,也可以在项目下放置.eigent/skills-config.json按项目粒度覆盖。

Quick Start:pypdf 读写 PDF

技能正文给出的最小可用示例(SKILL.md L13-L26):

from pypdf import PdfReader, PdfWriter # 读取 PDF reader = PdfReader("document.pdf") print(f"页数: {len(reader.pages)}") # 提取文本 text = "" for page in reader.pages: text += page.extract_text()

Python 库层:pypdf / pdfplumber / reportlab

pypdf —— 基础操作

合并多个 PDF:

from pypdf import PdfWriter, PdfReader writer = PdfWriter() for pdf_file in ["doc1.pdf", "doc2.pdf", "doc3.pdf"]: reader = PdfReader(pdf_file) for page in reader.pages: writer.add_page(page) with open("merged.pdf", "wb") as output: writer.write(output)

按页拆分:

reader = PdfReader("input.pdf") for i, page in enumerate(reader.pages): writer = PdfWriter() writer.add_page(page) with open(f"page_{i+1}.pdf", "wb") as output: writer.write(output)

提取元数据:

reader = PdfReader("document.pdf") meta = reader.metadata print(f"Title: {meta.title}") print(f"Author: {meta.author}") print(f"Subject: {meta.subject}") print(f"Creator: {meta.creator}")

旋转页面(顺时针 90 度):

reader = PdfReader("input.pdf") writer = PdfWriter() page = reader.pages[0] page.rotate(90) writer.add_page(page) with open("rotated.pdf", "wb") as output: writer.write(output)

pdfplumber —— 文本与表格抽取

保留版式提取文本:

import pdfplumber with pdfplumber.open("document.pdf") as pdf: for page in pdf.pages: text = page.extract_text() print(text)

提取表格:

with pdfplumber.open("document.pdf") as pdf: for i, page in enumerate(pdf.pages): tables = page.extract_tables() for j, table in enumerate(tables): print(f"第 {i+1} 页 表 {j+1}:") for row in table: print(row)

进阶:多页表格合并导出 Excel(依赖pandas):

import pandas as pd with pdfplumber.open("document.pdf") as pdf: all_tables = [] for page in pdf.pages: tables = page.extract_tables() for table in tables: if table: # 检查表格非空 df = pd.DataFrame(table[1:], columns=table[0]) all_tables.append(df) # 合并所有表格 if all_tables: combined_df = pd.concat(all_tables, ignore_index=True) combined_df.to_excel("extracted_tables.xlsx", index=False)

reportlab —— 创建 PDF

Canvas 基础创建:

from reportlab.lib.pagesizes import letter from reportlab.pdfgen import canvas c = canvas.Canvas("hello.pdf", pagesize=letter) width, height = letter # 添加文本 c.drawString(100, height - 100, "Hello World!") c.drawString(100, height - 120, "This is a PDF created with reportlab") # 添加一条线 c.line(100, height - 140, 400, height - 140) # 保存 c.save()

Platypus 多页文档:

from reportlab.lib.pagesizes import letter from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak from reportlab.lib.styles import getSampleStyleSheet doc = SimpleDocTemplate("report.pdf", pagesize=letter) styles = getSampleStyleSheet() story = [] # 添加内容 title = Paragraph("Report Title", styles['Title']) story.append(title) story.append(Spacer(1, 12)) body = Paragraph("This is the body of the report. " * 20, styles['Normal']) story.append(body) story.append(PageBreak()) # 第 2 页 story.append(Paragraph("Page 2", styles['Heading1'])) story.append(Paragraph("Content for page 2", styles['Normal'])) # 构建 PDF doc.build(story)

上下标的正确姿势(重要陷阱):绝不要在 ReportLab PDF 中使用 Unicode 上下标字符(₀₁₂₃₄₅₆₇₈₉⁰¹²³⁴⁵⁶⁷⁸⁹)——内置字体不含这些字形,会渲染成实心黑块。应使用Paragraph的 XML 标记:

from reportlab.platypus import Paragraph from reportlab.lib.styles import getSampleStyleSheet styles = getSampleStyleSheet() # 下标:使用 <sub> 标签 chemical = Paragraph("H<sub>2</sub>O", styles['Normal']) # 上标:使用 <super> 标签 squared = Paragraph("x<super>2</super> + y<super>2</super>", styles['Normal'])

若用 Canvas 直接绘制文本,则需手动调整字号与坐标位置,同样避免 Unicode 上下标。

命令行工具层:poppler-utils / qpdf / pdftk

pdftotext(poppler-utils)

# 提取文本 pdftotext input.pdf output.txt # 保留版式提取 pdftotext -layout input.pdf output.txt # 提取指定页(第 1-5 页) pdftotext -f 1 -l 5 input.pdf output.txt

qpdf

# 合并 qpdf --empty --pages file1.pdf file2.pdf -- merged.pdf # 拆分页 qpdf input.pdf --pages . 1-5 -- pages1-5.pdf qpdf input.pdf --pages . 6-10 -- pages6-10.pdf # 旋转(第 1 页顺时针 90 度) qpdf input.pdf output.pdf --rotate=+90:1 # 去除密码 qpdf --password=mypassword --decrypt encrypted.pdf decrypted.pdf

pdftk(若系统可用)

# 合并 pdftk file1.pdf file2.pdf cat output merged.pdf # 按页拆分 pdftk input.pdf burst # 旋转 pdftk input.pdf rotate 1east output rotated.pdf

常见任务实战

扫描件 OCR(pytesseract + pdf2image)

依赖安装:pip install pytesseract pdf2image,系统需另装 Tesseract OCR 引擎。

import pytesseract from pdf2image import convert_from_path # PDF 转图片 images = convert_from_path('scanned.pdf') # 逐页 OCR text = "" for i, image in enumerate(images): text += f"Page {i+1}:\n" text += pytesseract.image_to_string(image) text += "\n\n" print(text)

添加水印

from pypdf import PdfReader, PdfWriter # 创建或加载水印 watermark = PdfReader("watermark.pdf").pages[0] # 应用到所有页 reader = PdfReader("document.pdf") writer = PdfWriter() for page in reader.pages: page.merge_page(watermark) writer.add_page(page) with open("watermarked.pdf", "wb") as output: writer.write(output)

提取内嵌图片(pdfimages,poppler-utils)

pdfimages -j input.pdf output_prefix # 输出 output_prefix-000.jpg、output_prefix-001.jpg 等

密码保护

from pypdf import PdfReader, PdfWriter reader = PdfReader("input.pdf") writer = PdfWriter() for page in reader.pages: writer.add_page(page) # 添加密码 writer.encrypt("userpassword", "ownerpassword") with open("encrypted.pdf", "wb") as output: writer.write(output)

Quick Reference:任务—工具速查表

任务最佳工具命令/代码
合并 PDFpypdfwriter.add_page(page)
拆分 PDFpypdf每页输出到单独文件
提取文本pdfplumberpage.extract_text()
提取表格pdfplumberpage.extract_tables()
创建 PDFreportlabCanvas 或 Platypus
命令行合并qpdfqpdf --empty --pages ...
扫描件 OCRpytesseract先转成图片再识别
填写 PDF 表单pdf-lib 或 pypdf(见 forms.md)见 forms.md

进阶参考:reference.md 中的高级能力

resources/example-skills/pdf/reference.md 是 SKILL.md 明确指向的进阶文档,覆盖渲染、JavaScript 生态、命令行高级用法与性能调优。

pypdfium2(PDFium/Chromium 的 Python 绑定):适合高速渲染与转图,可视为 PyMuPDF 替代。

import pypdfium2 as pdfium pdf = pdfium.PdfDocument("document.pdf") page = pdf[0] bitmap = page.render(scale=2.0, rotation=0) # scale 越大分辨率越高 img = bitmap.to_pil() img.save("page_1.png", "PNG") # 批量渲染 for i, page in enumerate(pdf): bitmap = page.render(scale=1.5) bitmap.to_pil().save(f"page_{i+1}.jpg", "JPEG", quality=90)

pdf-lib(MIT,任意 JS 环境):可加载既有 PDF 加页加字,也可从零创建带字体、矩形、表格布局的发票类文档;合并时用copyPages(source, indices)精确挑选页(如copyPages(pdf2, [0, 2, 4]))。

pdfjs-dist(Apache,Mozilla PDF.js):浏览器端渲染主力——配置workerSrcgetDocument加载、page.render()画到 canvas;getTextContent()可拿到带坐标(transform[4]/[5])的文本项,getAnnotations()可枚举批注与表单对象。

命令行高级操作

# 带包围盒坐标的文本抽取(结构化数据关键) pdftotext -bbox-layout document.pdf output.xml # 高分辨率转图 pdftoppm -png -r 300 document.pdf output_prefix pdftoppm -jpeg -jpegopt quality=85 -r 200 document.pdf jpeg_output # 查看内嵌图片清单 / 原样抽取 pdfimages -list document.pdf pdfimages -all document.pdf images/img # 按每 3 页分组拆分 qpdf --split-pages=3 input.pdf output_group_%02d.pdf # 复杂页范围合并 qpdf --empty --pages doc1.pdf 1-3 doc2.pdf 5-7 doc3.pdf 2,4 -- combined.pdf # 优化:线性化、压缩、检查、修复 qpdf --linearize input.pdf optimized.pdf qpdf --optimize-level=all input.pdf compressed.pdf qpdf --check input.pdf qpdf --fix-qdf damaged.pdf repaired.pdf # 256 位加密并限制权限 qpdf --encrypt user_pass owner_pass 256 --print=none --modify=none -- input.pdf encrypted.pdf qpdf --show-encryption encrypted.pdf

pdfplumber 高级特性page.chars可拿到每个字符的x0/y0坐标;page.within_bbox((100,100,400,200)).extract_text()按区域抽文本;复杂表格可传{"vertical_strategy": "lines", "horizontal_strategy": "lines", "snap_tolerance": 3, "intersection_tolerance": 15}并配合page.to_image(resolution=150)可视化调试版式。

reportlab 专业报表:用Table+TableStyle可做带表头底色、网格线、字号字体的季度销售报表(BACKGROUND/TEXTCOLOR/ALIGN/GRID等指令按 (col_start,row_start)-(col_end,row_end) 区域生效)。

性能与排障要点(reference.md 的 Performance / Troubleshooting 节):大 PDF 用流式分块处理(每 10 页写一个PdfWriter分块输出);纯文本抽取pdftotext -bbox-layout最快、表格用 pdfplumber、超大文档避免pypdf.extract_text();图片抽取用pdfimages比整页渲染快得多;加密 PDF 先reader.is_encrypted判断再reader.decrypt("password");损坏 PDF 用qpdf --check检查、qpdf --replace-input修复;文本抽取为空时回退 OCR。文档末尾还附了各库的许可证清单(pypdf: BSD、pdfplumber: MIT、pypdfium2: Apache/BSD、reportlab: BSD、poppler-utils: GPL-2、qpdf: Apache、pdf-lib: MIT、pdfjs-dist: Apache)。

表单填充工作流:forms.md 的分步流程

resources/example-skills/pdf/forms.md 把「填表单」定义为一个必须按顺序执行、不得跳步的流程,且所有命令都要求在该技能目录下运行(python scripts/xxx <args>)。这套脚本真实存在于 resources/example-skills/pdf/scripts/,包括check_fillable_fields.pyextract_form_field_info.pyconvert_pdf_to_images.pyextract_form_structure.pycheck_bounding_boxes.pyfill_fillable_fields.pyfill_pdf_form_with_annotations.pycreate_validation_image.py

第 0 步:判断表单类型。先运行:

python scripts/check_fillable_fields <file.pdf>

结果决定走「可填字段」还是「不可填字段」分支。

分支一:可填字段(AcroForm)

  1. python scripts/extract_form_field_info.py <input.pdf> <field_info.json>生成字段清单 JSON,每个字段含field_idpage(1 基)、rect(PDF 坐标[left, bottom, right, top],y=0 在页面底部)、typetext/checkbox/radio_group/choice);复选框另有checked_value/unchecked_value,单选组含radio_options,下拉框含choice_options
  2. python scripts/convert_pdf_to_images.py <file.pdf> <output_directory>把每页转成 PNG,结合字段包围盒(注意 PDF 坐标到图像坐标的换算)判断每个字段的语义。
  3. 手工创建field_values.json
[ { "field_id": "last_name", "description": "The user's last name", "page": 1, "value": "Simpson" }, { "field_id": "Checkbox12", "description": "Checkbox to be checked if the user is 18 or over", "page": 1, "value": "/On" } ]

其中复选框/单选框的value必须取自第 1 步 JSON 里的checked_valueradio_options[].value

  1. python scripts/fill_fillable_fields.py <input.pdf> <field_values.json> <output.pdf>执行填充。脚本会校验字段 ID 与取值合法性,报错时按提示修正field_values.json再试。

分支二:不可填字段(走文本批注)

核心思想:优先结构抽取,视觉估计兜底,二者可混合

方式 A(首选):基于结构。运行:

python scripts/extract_form_structure.py <input.pdf> form_structure.json

输出包含labels(每个文本元素及 PDF 点坐标 x0/top/x1/bottom)、lines(定义行边界的横线)、checkboxes(小方形复选框及中心坐标)、row_boundaries。若能拿到有意义的标签,就按结构推算录入区:文本字段 entry 起点取「标签 x1 + 5」,终点取下一个标签的 x0 或行边界;复选框直接复用其矩形。写入fields.json时用pdf_width/pdf_height声明坐标系(PDF 坐标下 y=0 在页面顶部、向下增大):

{ "pages": [ {"page_number": 1, "pdf_width": 612, "pdf_height": 792} ], "form_fields": [ { "page_number": 1, "description": "Last name entry field", "field_label": "Last Name", "label_bounding_box": [43, 63, 87, 73], "entry_bounding_box": [92, 63, 260, 79], "entry_text": {"text": "Smith", "font_size": 10} } ] }

已知盲区:圆形复选框、装饰性图形、浅色元素可能漏检——对这些字段改走视觉分析(混合模式)。

方式 B(兜底):视觉估计。适用于扫描件/图像 PDF(文本显示为(cid:X))。流程:

  1. python scripts/convert_pdf_to_images.py <input.pdf> <images_dir/>转图;
  2. 目测每页,粗估字段标签、录入区、复选框位置;
  3. 放大精修(关键):用 ImageMagick 裁剪字段邻域——magick <page_image> -crop <w>x<h>+<x>+<y> +repage <crop.png>magick不可用时用convert),在裁剪图中确定录入区起点、终点与上下边界,再换算回全图坐标:full_x = crop_x + crop_offset_x
  4. fields.json,此时用image_width/image_height声明像素坐标系。

混合模式:结构命中的字段用 PDF 坐标,视觉估计的字段按pdf_x = image_x * (pdf_width / image_width)(y 同理)换算成 PDF 坐标,最终统一为一种坐标系。

统一收尾:无论哪条路径,填充前必须校验包围盒——

python scripts/check_bounding_boxes.py fields.json

它检查相交的包围盒(会导致文字重叠)和相对字号过小的录入框,报错先修fields.json。然后:

# 填充(脚本自动识别坐标系并做换算) python scripts/fill_pdf_form_with_annotations.py <input.pdf> fields.json <output.pdf> # 验证:输出转图检查文字落位 python scripts/convert_pdf_to_images.py <output.pdf> <verify_images/>

落位偏差的排查方向:方式 A 检查是否用了form_structure.json的 PDF 坐标与pdf_width/pdf_height;方式 B 检查图像尺寸与像素坐标;混合模式检查换算公式。

运行前提与适用边界

  • Python 侧需自行安装文档中出现的库:pypdfpdfplumber(表格进阶还需pandas)、reportlabpytesseract+pdf2imagepypdfium2Pillow;JS 侧为pdf-libpdfjs-dist
  • 命令行工具依赖系统安装:pdftotext/pdftoppm/pdfimages来自 poppler-utils(GPL-2),qpdf(Apache),pdftk视平台可选,OCR 需 Tesseract 引擎,表单视觉精修建议有 ImageMagick。
  • 该技能随 Eigent 启动自动同步到~/.eigent/skills/pdf/,默认enabled: true且全局可见;如需限定 Agent 范围,修改skills-config.json中的scopeisGlobal/selectedAgents)即可,项目级.eigent/skills-config.json可进一步覆盖。
  • 注意 LICENSE.txt 声明的专有授权条款,复制技能内容前先确认许可范围。

小结

这份 PDF 技能展示了 Eigent 技能的完整范式:SKILL.md用 frontmatter 声明触发条件、正文给出分层的可直接运行代码(pypdf 基础操作、pdfplumber 抽取、reportlab 生成、qpdf/pdftotext 命令行、OCR 与水印等常见任务、任务速查表),reference.md 提供渲染/JS 生态/性能排障等纵深,forms.md 把表单填充固化成「结构优先、视觉兜底、强制校验」的分步流程,配套scripts/目录提供全部可执行脚本;而 skill_service.py、skill_config_service.py 与 skill_toolkit.py 则负责把技能从仓库同步到用户目录、按用户/项目/Agent 三级维度授权加载——这正是「文档即能力」在 Eigent 中的完整落地。

【免费下载链接】eigentEigent: The Open Source Cowork Desktop - Local and Free Alternative to Claude Cowork and Codex项目地址: https://gitcode.com/GitHub_Trending/ei/eigent

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询