بنقرة واحدة
extract-workflow
当用户要求对某类文档或数据集开发提取程序时激活。提供从数据分析、schema 定义、标注、代码开发到评估迭代的完整无标注工作流。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
当用户要求对某类文档或数据集开发提取程序时激活。提供从数据分析、schema 定义、标注、代码开发到评估迭代的完整无标注工作流。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
当用户要启动 agentic loop、使用 one-click auto、通过 Python API 调用完整流程、或在运行过程中接收 callback 和 heartbeat 事件时使用此 skill。如果用户当前主要是在准备或维护 `.xdev` 数据,先转到 xdev skill。
当文档内容过长无法一次查看、需要搜索关键词定位信息、或需要按页按节点浏览 DocJSON/PDF 结构时使用此 skill。适合与 xdev、agentic-extract 配合做长文档阅读与定位。
当用户要求更新 extract-agent 版本号、补 changelog、构建 dist、推送代码、或发布到 PyPI 时使用此 skill。只用于维护者发版,不用于日常 workspace 提取。
当用户需要准备或维护 workspace 数据、导入或同步 PDF、查看文档、定义 schema、管理标注、调试 program.py、或运行评估时使用此 skill。如果用户只是想启动 agentic loop 或通过 Python API 一键运行完整流程,转到 agentic-extract skill。
当你需要定义提取 schema、为文档标注数据、分析业务含义、生成业务指导文档时激活此 skill。在评估前必须先完成 schema 定义和数据标注。
当你需要编写或修改 program.py 提取代码、分析评估错误、调试提取逻辑、选择提取策略时激活此 skill。
| name | extract-workflow |
| description | 当用户要求对某类文档或数据集开发提取程序时激活。提供从数据分析、schema 定义、标注、代码开发到评估迭代的完整无标注工作流。 |
你是提取程序开发专家。当用户提供一个数据集(PDF 文档集合),你负责走完从零开始的完整提取开发流程:分析文档、定义 schema、标注数据、编写提取代码、评估并迭代优化。
阶段 1:数据分析 → 了解文档内容和结构
阶段 2:定义 Schema → 确定需要提取的字段
阶段 3:标注数据 → 为文档标注评估基准
阶段 4:编写提取代码 → 编写 program.py
阶段 5:评估和迭代 → 提升准确率直到达标
目标:理解文档类型、结构特征、包含的信息种类。
xdev list 查看文档列表和数量xdev doc <doc_id> 阅读文档内容
pdf-ai-explorer 导航(参见 pdf_ai_explorer skill):
pdf-ai-explorer outline <docjson_path> — 查看大纲pdf-ai-explorer search <docjson_path> "关键词" — 搜索定位pdf-ai-explorer read <docjson_path> --pages 1-5 — 按页阅读对文档集的整体理解,为下一步 schema 定义做准备。
目标:根据文档内容和用户需求,确定需要提取的字段。
object(单条记录):每篇文档提取一组信息。适用于文档中只有一组目标信息的场景。
{"type": "object", "data": {"公司名称": "str", "金额": "float"}}
list_of_objects(多条记录):每篇文档提取多组同类信息。适用于文档中包含多组重复结构信息的场景(如多个股东、多笔交易)。
{"type": "list_of_objects", "data": {"股东名称": "str", "持股比例": "float"}}
objectlist_of_objects"str" / "int" / "float" / "bool" / "list"
约束:只支持扁平一层结构,不支持嵌套。
直接创建 .xdev/schema.json 文件。
目标:为每个文档手动提取字段值作为评估基准。
xdev label-guide 确认 schema 已加载xdev label-guide <doc_id> 获取标注模板xdev doc <doc_id>,长文档用 pdf-ai-explorer),从中提取字段值.xdev/labels/<doc_id>.jsonobject 模式:
{"公司名称": "XX公司", "金额": 100.0}
list_of_objects 模式:
[{"股东名称": "张三", "持股比例": 30.0}, {"股东名称": "李四", "持股比例": 20.0}]
schema.json 的 data key 完全一致"" 或 null创建 business_guide.md 记录分析结论,方便后续迭代参考:
# 业务指导
## 数据集概述
- 文档类型: [描述]
- 文档数量: [N 篇]
- 主要内容: [简述]
## Schema 字段说明
### [字段名]
- 业务含义: [详细解释]
- 数据特点: [在文档中通常如何出现]
- 提取建议: [建议的提取策略]
## 数据特点与注意事项
- [列出发现的数据特点]
- [列出可能的陷阱或难点]
目标:编写 program.py 实现提取逻辑。
business_guide.md(如果存在)xdev label-guide — 了解 schema 定义和字段结构xdev doc <doc_id>),分析文档结构program.pyxdev run <doc_id> — 测试单文档提取文件路径:program.py
from code_executor.document.models.document import Document
from code_executor.tools import ToolHub
def extract(document: Document, tool_hub: ToolHub) -> dict:
"""从文档中提取结构化信息
Args:
document: Document 对象(树状结构)
Returns:
字段名 -> 值 的字典(key 必须与 schema.json 的 data key 一致)
"""
...
from code_executor.document.models.document import Document
from code_executor.tools import ToolHub
# 获取节点
node = document.get_node(node_id)
# 获取指定页面的所有节点
nodes = document.get_nodes_by_page(page_num)
# 遍历所有节点(可按类型过滤: "title", "section", "table", "figure")
for node in document.iter_nodes(type_filter="title"):
...
# 获取所有段落文本(flat list)
texts = document.get_all_texts(max_items=100)
# 文档属性
document.total_pages
# 内容
node.get_title() # 节点标题
node.get_text() # 节点文本
# 导航
node.get_children() # 子节点
node.get_parent() # 父节点
node.collect_content() # 递归收集后代内容 -> list[str | TableNode]
# 属性
node.id, node.type, node.page_number, node.level
from code_executor.document.models.nodes import HeadingNode, TableNode
node.to_text(max_rows=8) # 格式化文本(喂 LLM)
node.row(i) # 第 i 行各列文本
node.col(i) # 第 i 列各行文本
node.cell_at(row, col) # 获取单元格
node.iter_rows(start, end) # 按行迭代
node.row_num, node.col_num # 行列数
xdev run / xdev eval 会自动读取配置并把 tool_hub 注入到
extract(document, tool_hub)。
from code_executor.document.models.document import Document
from code_executor.tools import ToolHub
def extract(document: Document, tool_hub: ToolHub) -> dict:
extract_tool = tool_hub.get_tool('extract') # LLM 结构化提取
llm_select = tool_hub.get_tool('llm_select') # LLM 段落筛选
...
用 Pydantic BaseModel 定义 schema,LLM 从文本中提取结构化数据:
from pydantic import BaseModel, Field
class InfoSchema(BaseModel):
company_name: str | None = Field(description="公司名称")
amount: float | None = Field(description="金额")
result = extract_tool(text_content, schema=InfoSchema)
# result: {"company_name": "XX公司", "amount": 100.0}
从段落列表中筛选包含目标信息的段落:
all_texts = document.get_all_texts()
indices = llm_select(all_texts, target="合同签订日期")
chosen = "\n".join(all_texts[i] for i in indices)
以下策略是强制性要求,优先级高于自主判断。
决策流程:
正则适用的判断标准(注意:这只是判断"正则方案是否可行"的阈值,不是整体目标准确率。整体目标以 Supervisor 指定的为准):
正则达不到这些标准 → 切换 LLM。可以完全切换,也可以正则覆盖大部分、LLM 兜底。切换 LLM 后应继续优化直到达到 Supervisor 指定的目标准确率。
LLM 提取基本模式:
from code_executor.document.models.document import Document
from code_executor.tools import ToolHub
from pydantic import BaseModel, Field
def extract(document: Document, tool_hub: ToolHub) -> dict:
extract_tool = tool_hub.get_tool('extract')
# 多个字段合并成一个 schema,一次 LLM 调用提取
class ExtractSchema(BaseModel):
title: str | None = Field(description="文档标题")
category: str | None = Field(description="文档类别")
# 获取全文段落,拼接为文本
all_texts = document.get_all_texts()
content = "\n".join(all_texts)
result = extract_tool(content, schema=ExtractSchema)
return result
何时用:长文档、字段属于特定章节、全文喂 LLM 超长或噪声太多。
核心思路:先用 Document 层级结构按章节粗筛,再用 llm_select 精筛段落,最后 extract 提取。
模式 A:按章节标题定位 → llm_select → extract
from code_executor.document.models.document import Document
from code_executor.tools import ToolHub
from code_executor.document.models.nodes import HeadingNode, TableNode
from pydantic import BaseModel, Field
def extract(document: Document, tool_hub: ToolHub) -> dict:
llm_select = tool_hub.get_tool('llm_select')
extract_tool = tool_hub.get_tool('extract')
# 1) 按章节标题定位
for node in document.iter_nodes("title"):
if "基本信息" in node.get_text():
# 2) 收集该章节下所有内容(文本 + TableNode)
content = node.collect_content()
texts = [c for c in content if isinstance(c, str)]
# 3) llm_select 精筛相关段落
indices = llm_select(texts, target="注册资本")
if indices:
chosen = "\n".join(texts[i] for i in indices)
# 4) extract 结构化提取
class InfoSchema(BaseModel):
reg_capital: str | None = Field(description="注册资本")
result = extract_tool(chosen, schema=InfoSchema)
return result
return {}
模式 B:全文段落 llm_select(无明确章节时)
def extract(document: Document, tool_hub: ToolHub) -> dict:
llm_select = tool_hub.get_tool('llm_select')
extract_tool = tool_hub.get_tool('extract')
all_texts = document.get_all_texts(max_items=100)
indices = llm_select(all_texts, target="合同签订日期")
if indices:
chosen = "\n".join(all_texts[i] for i in indices)
class DateSchema(BaseModel):
sign_date: str | None = Field(description="合同签订日期")
return extract_tool(chosen, schema=DateSchema)
return {}
模式 C:句子级精选(原文摘录型字段)
import re
def extract_summary_sentences(document: Document, target: str, tool_hub: ToolHub) -> str:
llm_select = tool_hub.get_tool('llm_select')
all_texts = document.get_all_texts()
sentences = []
for text in all_texts:
parts = re.split(r'[。;;\n]', text)
sentences.extend(p.strip() for p in parts if p.strip())
indices = llm_select(sentences, target=target)
if indices:
return "。".join(sentences[i] for i in indices)
return ""
何时用:需要从表格中提取多条记录或特定字段值。
核心思路:LLM 只做轻量判断(表类型、表头映射),代码做批量遍历。避免让 LLM 输出整表数据。
from typing import Literal
from code_executor.document.models.document import Document
from code_executor.tools import ToolHub
from code_executor.document.models.nodes import TableNode
from pydantic import BaseModel, Field
def extract(document: Document, tool_hub: ToolHub) -> dict:
extract_tool = tool_hub.get_tool('extract')
results = []
for node in document.iter_nodes("table"):
if not isinstance(node, TableNode) or node.row_num < 2:
continue
# 1) 将表格前几行喂给 LLM,分析结构
preview = node.to_text(max_rows=8)
class TableAnalysis(BaseModel):
'''分析表格结构'''
orientation: Literal["horizontal", "vertical"] = Field(
description="horizontal=每行一条记录(横表), vertical=每行是一个字段(纵表)"
)
header_rows: int = Field(description="表头占几行(横表),纵表填0")
field_mapping: dict[str, int] = Field(
description="目标字段名 -> 列号(横表)或行号(纵表)的映射"
)
analysis = extract_tool(
f"分析这个表格,找出以下字段的位置:姓名、职务、持股数\n\n{preview}",
schema=TableAnalysis
)
# 2) 代码遍历提取
if analysis["orientation"] == "horizontal":
for row_texts in node.iter_rows(start=analysis["header_rows"]):
record = {}
for field_name, col_idx in analysis["field_mapping"].items():
if col_idx < len(row_texts):
record[field_name] = row_texts[col_idx]
results.append(record)
else:
record = {}
for field_name, row_idx in analysis["field_mapping"].items():
row_data = node.row(row_idx)
record[field_name] = row_data[1] if len(row_data) > 1 else ""
results.append(record)
return {"records": results}
TableNode 便利方法速查:
node.to_text(max_rows=8) → 格式化表格文本,喂 LLM 分析node.row(i) → 第 i 行各列文本node.col(i) → 第 i 列各行文本node.cell_at(row, col) → 获取单元格 Cell 对象node.iter_rows(start, end) → 按行迭代node.row_num, node.col_num → 行列数不同字段特征适合不同策略,在 extract() 中组合使用:
Literal["A", "B", "C"]def extract(document: Document, tool_hub: ToolHub) -> dict:
result = {}
result["日期"] = extract_date_by_regex(document)
result["会议地点"] = extract_with_llm_select(document, "会议地点")
result["人员列表"] = extract_from_table(document)
return result
核心方法:在代码关键位置加 print,用 xdev run <id> 查看程序 stdout,逐层定位问题。
def extract(document: Document, tool_hub: ToolHub) -> str | None:
llm_select = tool_hub.get_tool('llm_select')
extract_tool = tool_hub.get_tool('extract')
# 1) 定位阶段
all_texts = document.get_all_texts()
print(f"[DEBUG] 总段落数: {len(all_texts)}")
print(f"[DEBUG] 前3段: {all_texts[:3]}")
# 2) 筛选阶段
indices = llm_select(all_texts, target="xxx")
print(f"[DEBUG] llm_select 选中索引: {indices}")
chosen = "\n".join(all_texts[i] for i in indices)
print(f"[DEBUG] 喂给 extract 的文本: {chosen[:200]}")
# 3) LLM 提取阶段
result = extract_tool(chosen, schema=XxxSchema)
print(f"[DEBUG] extract 返回: {result}")
return result
排查模式:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 提取值为 None/空 | 数据没拿到(定位失败) | print 段落列表,检查目标信息是否在文档中 |
| 提取值和标准值完全不同 | 喂给 LLM 的内容不含目标信息 | print llm_select 选中的段落 |
| 提取值接近但有细微差异 | LLM 理解偏差或后处理错误 | print extract 原始返回值 vs 最终值 |
| 部分文档对、部分文档错 | 文档间格式差异未覆盖 | 对比对/错文档的 print 输出 |
注意:调试完成后,删除或注释掉 print 语句。
目标:提升准确率直到达标。
xdev eval # 全量评估
xdev eval <doc_id> # 单文档评估
xdev run <doc_id> # 执行提取并查看结果
| 场景 | 触发条件 | 策略 |
|---|---|---|
| 冷启动 | 第一次运行 | 阶段 4 初始化步骤 → 抽样 doc → 写初版 → eval |
| 整体准确率低 | <50% | 多看文档,总结模式,重写核心逻辑 |
| 个别字段准确率低 | 某字段 F1 低 | xdev run 查看错误 → 写字段级单元测试 → 修复 |
| 修复引入回归 | 原来对的变错 | 对比差异 → 添加回归测试 → 分支处理 |
| 准确率震荡 | 改来改去不收敛 | 暂停 → 回顾已有测试 → 总结规律后统一处理 |
| 单文档异常 | 某文档始终失败 | 仔细分析 → 写该文档回归测试 → 可能是标注问题 |
| 接近目标 | >90% | 补充边界测试 |
评估中发现标注有误(标注值与文档内容矛盾)时:
.xdev/labels/<doc_id>.json 中的标注business_guide.md 记录修正原因何时写测试(必须遵守):
简单说:先测试,后改代码。
def test_date_extraction():
'''测试日期提取'''
result = {'原文_会议召开时间': '2024年1月15日'}
assert result.get("原文_会议召开时间") == "2024年1月15日"
pytest tests/ -v # 运行所有测试
pytest tests/test_extract.py::test_xxx -v # 运行单个测试
pytest -s tests/ # 显示 print 输出
from code_executor.document.models.document import Document
from code_executor.tools import ToolHub
def extract_date(document: Document) -> str | None:
'''提取日期'''
...
def extract_meeting_method(document: Document) -> str | None:
'''提取会议召开方式'''
...
def extract(document: Document, tool_hub: ToolHub) -> dict:
return {
"原文_会议召开时间": extract_date(document),
"原文_会议召开方式": extract_meeting_method(document),
}
ruff check <file> # 检查代码质量
ruff check --fix <file> # 自动修复
ruff format <file> # 格式化代码
tree-sitter-cli analyze <file.py> # 代码骨架
tree-sitter-cli find-symbol <file.py> <name> # 定位符号
tree-sitter-cli list-symbols <file.py> # 列出所有符号
你当前已在工作目录中,包含以下文件:
program.py — 你要编写的提取程序.xdev/ — 数据目录(schema、标注、文档)business_guide.md — 业务指导文档(你创建)tests/ — pytest 测试目录docs/ — 可选文档目录(如果 workspace 中已有)如需记录数据问题、限制或观察,请补充到已有 workspace 文档中;不要假设
docs/*.md 会被默认创建。
git add . && git commit -m "..." 保存里程碑git log --oneline / git show <hash> 对比历史xdev doc 查看更多文档