بنقرة واحدة
extract-dev
当你需要编写或修改 program.py 提取代码、分析评估错误、调试提取逻辑、选择提取策略时激活此 skill。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
当你需要编写或修改 program.py 提取代码、分析评估错误、调试提取逻辑、选择提取策略时激活此 skill。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
| name | extract_dev |
| description | 当你需要编写或修改 program.py 提取代码、分析评估错误、调试提取逻辑、选择提取策略时激活此 skill。 |
你是提取程序开发专家,负责编写 program.py 从文档中提取结构化信息。
program.py 实现提取逻辑xdev run 测试单文档、用 xdev eval 评估整体准确率business_guide.md 是否存在,如果存在则先阅读(了解业务背景和提取规则)xdev label-guide — 了解 schema 定义和字段结构xdev doc <doc_id>),分析文档结构
pdf-ai-explorer 导航(参见 pdf_ai_explorer skill)xdev eval — 了解当前效果(如果已有 program.py)文件路径: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)
# llm_select 筛选包含目标信息的段落
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:句子级精选(原文摘录型字段)
当需要从原文中选取特定句子时,按标点拆分后让 llm_select 选择:
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())
# llm_select 选择相关句子
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)
# 取第 1 列之后的值(第 0 列通常是字段名)
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)
# 语义字段 -> llm_select + extract
result["会议地点"] = extract_with_llm_select(document, "会议地点")
# 表格字段 -> 表格策略
result["人员列表"] = extract_from_table(document)
return result
核心方法:在代码关键位置加 print,用 xdev run <id> 查看程序 stdout,逐层定位问题。
调试代码示例:
def extract_xxx(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) 筛选阶段 — 看 llm_select 选了什么
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 提取阶段 — 看 extract 返回了什么
result = extract_tool(chosen, schema=XxxSchema)
print(f"[DEBUG] extract 返回: {result}")
# 4) 后处理阶段 — 看最终值
final = some_postprocess(result)
print(f"[DEBUG] 后处理后: {final}")
return final
排查模式:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 提取值为 None/空 | 数据没拿到(定位失败) | print 段落列表,检查目标信息是否在文档中、章节定位/iter_nodes 条件是否命中 |
| 提取值和标准值完全不同 | 喂给 LLM 的内容不含目标信息 | print llm_select 选中的段落,确认目标信息是否在其中 |
| 提取值接近但有细微差异 | LLM 理解偏差或后处理逻辑错误 | print extract 原始返回值 vs 最终值,定位是 LLM 还是后处理的问题 |
| 部分文档对、部分文档错 | 文档间格式差异未覆盖 | 对比对/错文档的 print 输出,找出格式差异 |
注意:调试完成后,删除或注释掉 print 语句。
根据当前状态选择对应策略:
| 场景 | 触发条件 | 策略 |
|---|---|---|
| 冷启动 | 第一次运行 | 初始化步骤 → 抽样 doc → 写初版 → xdev eval |
| 整体准确率低 | <50% | 多看文档(xdev doc),总结模式,重写核心逻辑 → xdev eval |
| 个别字段准确率低 | 某字段 F1 低 | xdev run <错误doc_id> 查看输出 → 修改 program.py → xdev eval 验证 |
| 修复引入回归 | 原来对的变错 | xdev run 对比对/错文档 → 修改 program.py → xdev eval 确认不回归 |
| 准确率震荡 | 改来改去不收敛 | 暂停 → xdev run 多看几个错误文档 → 总结规律后统一处理 |
| 单文档异常 | 某文档始终失败 | xdev doc <id> 分析文档 → 可能是数据问题,记录到 docs/ |
| 接近目标 | >90% | xdev eval 确认 → 逐个排查剩余错误文档 |
主要验证手段是 xdev eval(全量真实文档评估)和 xdev run <doc_id>(单文档验证),不是 pytest。
xdev eval 看整体准确率变化xdev run <doc_id> 看提取输出和 print 调试信息ruff check <file> # 检查代码质量
ruff check --fix <file> # 自动修复
ruff format <file> # 格式化代码
常见错误代码:
自动检查:写入 .py 文件后应主动运行 ruff check,可自动修复的问题用 ruff check --fix 处理。
tree-sitter-cli analyze <file.py> # 代码骨架(省略函数体)
tree-sitter-cli find-symbol <file.py> <name> # 定位符号(返回 JSON)
tree-sitter-cli list-symbols <file.py> # 列出所有符号
使用场景:快速了解 program.py 的结构、定位特定函数位置。
mypy program.py # 基本类型检查
mypy --ignore-missing-imports <file> # 忽略缺少类型提示的导入
仅在后处理逻辑复杂时使用,不作为主要验证手段:
pytest tests/ -v # 运行所有测试
pytest tests/test_extract.py::test_xxx -v # 运行单个测试
xdev run 调试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),
}
你当前已在工作目录中,包含以下文件:
program.py — 你要编写的提取程序tests/ — pytest 测试目录docs/ — 可选文档目录(如果 workspace 中已有)如果需要沉淀问题或观察,优先更新已有的 workspace 文档;不要假设
docs/data_issues.md、docs/known_limitations.md、docs/notes.md 会被默认创建。
如果你在多 Agent 环境中工作,发现标注数据有问题时:
git add . && git commit -m "..." 保存里程碑git log --oneline / git show <hash> 对比历史xdev run / xdev eval 是主要验证手段,不要用 mock 数据的 pytest 替代xdev doc 查看更多文档استنادا إلى تصنيف 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 定义和数据标注。
当用户要求对某类文档或数据集开发提取程序时激活。提供从数据分析、schema 定义、标注、代码开发到评估迭代的完整无标注工作流。