Skip to content

Commit 5740a9a

Browse files
committed
feat(parsex): 支持文档解析与字段抽取命令
1 parent fc97baa commit 5740a9a

23 files changed

Lines changed: 2033 additions & 36 deletions

File tree

‎README.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,19 @@ Knowledge bases accrue running-time charges from creation, even without queries.
139139

140140
Keep the returned IDs and recovery record. Closing the CLI or a later failure does not stop billing; follow the reported cleanup actions to delete unused knowledge bases. See the [Knowledge Studio quick start](packages/kscli/README.md#quick-start) for retrieval and cleanup. The `bailian-knowledge` Skill is available through `bl skill init`.
141141

142+
## ParseX
143+
144+
Parse documents or audio/video and extract structured fields through the workspace-scoped ParseX API:
145+
146+
```bash
147+
bl parsex parse submit --workspace-id <workspace-id> --file-url "https://example.com/document.pdf" --file-name document.pdf --output-file-format markdown --output json
148+
bl parsex parse result --workspace-id <workspace-id> --biz-id <parse-biz-id> --follow --output json
149+
bl parsex extract submit --workspace-id <workspace-id> --parsed-file-biz-id <parse-biz-id> --schema-file schema.json --output json
150+
bl parsex extract result --workspace-id <workspace-id> --biz-id <extract-biz-id> --follow --output json
151+
```
152+
153+
Use an ordinary API key and a workspace with ParseX access. Submission returns `data.biz_id` immediately; `result --follow` waits for completion. `schema.json` contains a JSON Schema object, for example `{"type":"object","properties":{"title":{"type":"string"}}}`. Inputs are accessible HTTP(S) URLs; extraction also accepts a retained document parsing task. See the [ParseX command guide](docs/parsex.md) for processing options, result slices, OSS credentials, dry-run and polling behavior.
154+
142155
## Batch Inference
143156

144157
```bash

‎README.zh.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,19 @@ bl knowledge init --workspace-id <workspace-id> --dry-run
138138

139139
保留返回的 ID 和恢复记录。退出 CLI 或后续步骤失败不会停止计费;按返回的清理动作删除不再需要的知识库。检索及清理示例见 [Knowledge Studio 快速开始](packages/kscli/README.zh.md#快速开始)。知识库 Skill `bailian-knowledge` 通过 `bl skill init` 安装。
140140

141+
## ParseX
142+
143+
通过业务空间专属 ParseX API 解析文档、音视频,并按 Schema 抽取结构化字段:
144+
145+
```bash
146+
bl parsex parse submit --workspace-id <workspace-id> --file-url "https://example.com/document.pdf" --file-name document.pdf --output-file-format markdown --output json
147+
bl parsex parse result --workspace-id <workspace-id> --biz-id <parse-biz-id> --follow --output json
148+
bl parsex extract submit --workspace-id <workspace-id> --parsed-file-biz-id <parse-biz-id> --schema-file schema.json --output json
149+
bl parsex extract result --workspace-id <workspace-id> --biz-id <extract-biz-id> --follow --output json
150+
```
151+
152+
需要普通 API Key 和已获 ParseX 访问权限的业务空间。提交立即返回 `data.biz_id`,`result --follow` 等待完成。`schema.json` 为 JSON Schema 对象,例如 `{"type":"object","properties":{"title":{"type":"string"}}}`。输入使用可访问的 HTTP(S) URL;抽取还可复用保留期内的文档解析任务。处理配置、结果分片、OSS 凭证、dry-run 和轮询约定见 [ParseX 命令指南](docs/parsex.md)。
153+
141154
## 批量推理
142155

143156
```bash

‎docs/parsex.md‎

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# ParseX CLI
2+
3+
ParseX exposes asynchronous document/media parsing and structured field extraction. The CLI calls the four public REST endpoints with the ordinary API key resolved by the existing authentication system.
4+
5+
Sources: [integration overview](https://docs.agent.bailian.aliyun.com/zh/parse/integration/overview), [API overview](https://docs.agent.bailian.aliyun.com/zh/api/parsex/overview), [parse submit](https://docs.agent.bailian.aliyun.com/zh/api/parsex/parse-submit), [parse result](https://docs.agent.bailian.aliyun.com/zh/api/parsex/parse-result), [extract submit](https://docs.agent.bailian.aliyun.com/zh/api/parsex/extract-submit), [extract result](https://docs.agent.bailian.aliyun.com/zh/api/parsex/extract-result), [OSS integration](https://docs.agent.bailian.aliyun.com/zh/parse/integration/parse-x-oss-integration).
6+
7+
## Setup
8+
9+
- Supply an ordinary API key through the existing profile or `DASHSCOPE_API_KEY`. Token Plan subscription keys are not ParseX credentials.
10+
- Workspace resolution: `--workspace-id` → `BAILIAN_WORKSPACE_ID` → profile `workspace_id`.
11+
- Default origin: `https://<workspace-id>.cn-beijing.maas.aliyuncs.com`; prefix: `/api/v2/apps/parse-x`.
12+
- An explicit `--base-url`, `DASHSCOPE_BASE_URL`, or profile `base_url` overrides the origin. The shared CLI resolver discards any URL path. Workspace origins under `cn-beijing.pre-maas.aliyuncs.com` select `/api/v2/apps/pre-parse-x`. Use the environment assigned to your workspace.
13+
- The integration overview still labels production rollout as pending. Actual availability depends on the workspace/environment; service errors pass through unchanged.
14+
- Inputs must be accessible HTTP(S) URLs, including signed OSS URLs. No local upload endpoint is documented for ParseX; these commands do not upload local files or create storage resources.
15+
16+
命令复用现有百炼鉴权和 Profile,默认使用北京业务空间域名。输入使用可访问 URL;本地文件需先由调用方存放到可访问的位置。
17+
18+
## Parse documents and media
19+
20+
```bash
21+
bl parsex parse submit --workspace-id ws-example \
22+
--file-url "https://example.com/document.pdf" --file-name document.pdf \
23+
--output-file-format markdown --output json
24+
25+
# Use data.biz_id from the submission response.
26+
bl parsex parse result --workspace-id ws-example \
27+
--biz-id parse-example --follow --poll-timeout 600 --output json
28+
```
29+
30+
`--file-name-extension pdf` is an alternative to `--file-name`. Use `--config-id` for saved settings or `--processing-file processing.json` for the contents of the API `processing` field. Inline processing takes precedence over saved settings when both are provided. Optional booleans preserve explicit `false`; omitted values retain service defaults.
31+
32+
Document `processing.json`:
33+
34+
```json
35+
{
36+
"doc_processing_config": {
37+
"page_index": "1-10",
38+
"head_foot": false,
39+
"layout_position": true,
40+
"image_caption": true
41+
}
42+
}
43+
```
44+
45+
Media `processing.json` (use the same command with a media URL):
46+
47+
```json
48+
{
49+
"media_processing_config": {
50+
"enable_diarization": true,
51+
"enable_synopsis_parse": true,
52+
"enable_synopsis_segments": true,
53+
"enable_synopsis_summary": true,
54+
"frame_extraction": { "mode": "auto", "output_image_width": 1024 }
55+
}
56+
}
57+
```
58+
59+
The alternative `frame_rate` mode requires `frame_rate` from 0.2 to 5. Frame dimensions, when set, must be integers from 32 to 4096. Media segments and synopsis fields are preserved in JSON output. `--layout-table-format html` selects HTML tables inside Markdown output.
60+
61+
## Extract fields
62+
63+
Save a JSON Schema **object** to `schema.json`:
64+
65+
```json
66+
{
67+
"type": "object",
68+
"properties": {
69+
"title": { "type": "string", "description": "Document title" }
70+
}
71+
}
72+
```
73+
74+
```bash
75+
bl parsex extract submit --workspace-id ws-example \
76+
--parsed-file-biz-id parse-example --schema-file schema.json \
77+
--citation-required true --allow-inference false --output json
78+
79+
bl parsex extract result --workspace-id ws-example \
80+
--biz-id extract-example --follow --output json
81+
```
82+
83+
Alternatively, supply `--file-url "https://example.com/document.pdf"` instead of `--parsed-file-biz-id`. Exactly one source is required. Extraction does not support audio/video; reused parsing results must still exist and be reusable. No retention duration is hard-coded because the published pages differ on that limit.
84+
85+
`--schema-file` constructs `processing.extract_processing_config.extract_schema`. `--user-prompt`, `--citation-required` and `--allow-inference` are available with this shortcut. For a full inline configuration, use `--processing-file` instead:
86+
87+
```json
88+
{
89+
"user_prompt": "Extract the document title",
90+
"extract_processing_config": {
91+
"extract_schema": {
92+
"type": "object",
93+
"properties": { "title": { "type": "string" } }
94+
},
95+
"citation_required": true,
96+
"allow_inference": false
97+
}
98+
}
99+
```
100+
101+
Or use `--config-id` for a saved Extract configuration. Schema is sent as an object, consistent with the API field definition and examples; the conflicting sentence describing a JSON string is not used. Results retain `extract_result_json`, `fields`, citations and `request_id` without flattening.
102+
103+
## OSS output
104+
105+
`--oss-config-file /secure/path/oss.json` reads an object containing `bucket`, `endpoint`, `access_key_id`, `access_key_secret`, and optional `security_token`. Keep the file outside source control, restrict its permissions, and use fresh STS credentials when appropriate.
106+
107+
- Submit maps the object to `output.oss_config`; `bucket` and `endpoint` are required.
108+
- Result maps credential fields to **top-level** `oss_config`, following the individual endpoint parameter tables. The same file can be reused; `bucket` and `endpoint` are not sent on queries.
109+
- The CLI does not persist these credentials. Dry-run redacts all three credential fields and source URL query strings. Verbose HTTP logging does not print the request body.
110+
111+
The individual result parameter tables take precedence over the general OSS page's conflicting nesting description. OSS policies, credential issuance and bucket creation remain caller responsibilities.
112+
113+
## Polling and output
114+
115+
| Behavior | Contract |
116+
| -------------------------- | ------------------------------------------------------------------------------------------------- |
117+
| `submit` | One POST; immediately returns `data.biz_id`; never automatically retries |
118+
| `result` | One POST; preserves the response; successfully querying a `failed` task exits 0 |
119+
| `result --follow` | Polls until `success` or `failed`; unknown nonterminal states do not imply completion |
120+
| `ResultNotReady` | Continues polling only with `--follow`; other service errors pass through |
121+
| `--interval` | Integer seconds, 1–3600; default 2; requires `--follow` |
122+
| `--poll-timeout` | Total deadline including in-flight HTTP requests, 1–86400 seconds; default 600 |
123+
| `--timeout` | Existing per-request HTTP timeout |
124+
| Exit codes with `--follow` | Success 0; failed task/service error 1; invalid arguments 2; client deadline 5; Ctrl-C 130 |
125+
| `--output json` | One final response on stdout; progress on stderr |
126+
| `--quiet` | Submit prints only biz_id; result prints only status |
127+
| `--dry-run` | Validates local input and prints a redacted request; no business requests or business file writes |
128+
129+
Ctrl-C or timeout stops local polling without cancelling the task. Keep `biz_id` and use `result` to resume. A lost submit response does not prove that no task was created; check before resubmitting. Framework telemetry/update checks follow their existing settings.
130+
131+
Parsing queries support `--step-start` and `--step-size` (nonnegative integers). They request one slice of document `layouts` or media `segments`. `--follow` waits for completion for that slice; it does not aggregate or automatically advance slices. Advance the offset explicitly for further results. Output file URLs may appear only after all result slices have been fetched. URLs are returned as provided, without downloading or overwriting files.
132+
133+
单次查询保留服务端完整响应。持续查询的 stdout 只输出最终结果,进度写入 stderr;超时或中断后可用原 `biz_id` 继续查询。分片需显式推进,CLI 不将首个分片当作完整文档,也不自动下载结果 URL。
134+
135+
## Verification
136+
137+
Offline tests exercise the real CLI parser, auth middleware and HTTP client against local fixtures:
138+
139+
```bash
140+
pnpm exec vp test packages/commands/tests/e2e/parsex.e2e.test.ts
141+
```
142+
143+
The live parse/extract chain in the same file is opt-in: set `DASHSCOPE_API_KEY`, `BAILIAN_WORKSPACE_ID` and `BAILIAN_E2E_PARSEX_FILE_URL` (an accessible PDF URL). It submits real jobs and may incur normal service charges. Offline fixture success alone is not evidence of service acceptance.

‎packages/cli/src/commands.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ import {
2121
videoDownload,
2222
visionDescribe,
2323
decisionPredict,
24+
parsexParseSubmit,
25+
parsexParseResult,
26+
parsexExtractSubmit,
27+
parsexExtractResult,
2428
configShow,
2529
configSet,
2630
configList,
@@ -349,6 +353,10 @@ export const commands: Record<string, AnyCommand> = {
349353
"video download": videoDownload,
350354
"vision describe": visionDescribe,
351355
decision: decisionPredict,
356+
"parsex parse submit": parsexParseSubmit,
357+
"parsex parse result": parsexParseResult,
358+
"parsex extract submit": parsexExtractSubmit,
359+
"parsex extract result": parsexExtractResult,
352360
"config show": configShow,
353361
"config set": configSet,
354362
"config list": configList,
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import { createResultCommand } from "./result.ts";
2+
export default createResultCommand("extract");
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { defineCommand, UsageError, type ParsedFlags } from "bailian-cli-core";
2+
import {
3+
buildSubmitBody,
4+
nonEmpty,
5+
objectValue,
6+
PARSEX_NOTE,
7+
readObjectFile,
8+
runSubmit,
9+
SUBMIT_FLAGS,
10+
SUBMIT_NOTE,
11+
WORKSPACE_FLAG,
12+
} from "./shared.ts";
13+
14+
const FLAGS = {
15+
...SUBMIT_FLAGS,
16+
parsedFileBizId: {
17+
type: "string",
18+
valueHint: "<id>",
19+
description: {
20+
"en-US": "Completed parsing task to reuse (alternative to --file-url)",
21+
"zh-CN": "复用已完成的解析任务(与 --file-url 二选一)",
22+
},
23+
},
24+
schemaFile: {
25+
type: "string",
26+
valueHint: "<path>",
27+
description: {
28+
"en-US": "JSON Schema object file (alternative to --processing-file)",
29+
"zh-CN": "JSON Schema 对象文件(与 --processing-file 二选一)",
30+
},
31+
},
32+
userPrompt: {
33+
type: "string",
34+
valueHint: "<text>",
35+
description: {
36+
"en-US": "Extraction instruction (with --schema-file)",
37+
"zh-CN": "抽取指令(配合 --schema-file)",
38+
},
39+
},
40+
citationRequired: {
41+
type: "boolean",
42+
valueHint: "<boolean>",
43+
description: {
44+
"en-US": "Include citations (with --schema-file; service default: true)",
45+
"zh-CN": "返回引用(配合 --schema-file;服务默认 true)",
46+
},
47+
},
48+
allowInference: {
49+
type: "boolean",
50+
valueHint: "<boolean>",
51+
description: {
52+
"en-US": "Allow inference (with --schema-file; service default: false)",
53+
"zh-CN": "允许推断(配合 --schema-file;服务默认 false)",
54+
},
55+
},
56+
...WORKSPACE_FLAG,
57+
} as const;
58+
59+
async function buildBody(flags: ParsedFlags<typeof FLAGS>) {
60+
nonEmpty(flags.parsedFileBizId, "--parsed-file-biz-id");
61+
if ((flags.fileUrl !== undefined) === (flags.parsedFileBizId !== undefined))
62+
throw new UsageError(
63+
"Provide exactly one of --file-url or --parsed-file-biz-id. / --file-url 与 --parsed-file-biz-id 必须二选一。",
64+
);
65+
if (flags.schemaFile !== undefined && flags.processingFile !== undefined)
66+
throw new UsageError(
67+
"--schema-file and --processing-file are mutually exclusive. / --schema-file 与 --processing-file 不能同时使用。",
68+
);
69+
if (!flags.schemaFile && !flags.processingFile && !flags.configId)
70+
throw new UsageError(
71+
"Provide --schema-file, --processing-file or --config-id. / 请提供 --schema-file、--processing-file 或 --config-id。",
72+
);
73+
if (
74+
!flags.schemaFile &&
75+
[flags.userPrompt, flags.citationRequired, flags.allowInference].some(
76+
(value) => value !== undefined,
77+
)
78+
)
79+
throw new UsageError(
80+
"--user-prompt, --citation-required and --allow-inference require --schema-file. / 抽取快捷参数需配合 --schema-file。",
81+
);
82+
const body = await buildSubmitBody(flags);
83+
if (flags.parsedFileBizId !== undefined) body.parsed_file_biz_id = flags.parsedFileBizId;
84+
if (flags.schemaFile)
85+
body.processing = {
86+
...(flags.userPrompt === undefined ? {} : { user_prompt: flags.userPrompt }),
87+
extract_processing_config: {
88+
extract_schema: await readObjectFile(flags.schemaFile, "--schema-file"),
89+
...(flags.citationRequired === undefined
90+
? {}
91+
: { citation_required: flags.citationRequired }),
92+
...(flags.allowInference === undefined ? {} : { allow_inference: flags.allowInference }),
93+
},
94+
};
95+
if (body.processing) {
96+
if (
97+
body.processing.user_prompt !== undefined &&
98+
typeof body.processing.user_prompt !== "string"
99+
)
100+
throw new UsageError(
101+
"processing.user_prompt must be a string. / processing.user_prompt 必须为字符串。",
102+
);
103+
const config = objectValue(
104+
body.processing.extract_processing_config,
105+
"processing.extract_processing_config",
106+
);
107+
objectValue(config.extract_schema, "extract_schema");
108+
for (const key of ["citation_required", "allow_inference"]) {
109+
if (config[key] !== undefined && typeof config[key] !== "boolean")
110+
throw new UsageError(`${key} must be boolean. / ${key} 必须为布尔值。`);
111+
}
112+
}
113+
return body;
114+
}
115+
116+
export default defineCommand({
117+
description: {
118+
"en-US": "Submit ParseX structured field extraction",
119+
"zh-CN": "提交 ParseX 结构化字段抽取任务",
120+
},
121+
auth: "apiKey",
122+
flags: FLAGS,
123+
usageArgs:
124+
"(--file-url <url> | --parsed-file-biz-id <id>) (--schema-file <path> | --processing-file <path> | --config-id <id>) [flags]",
125+
notes: [
126+
PARSEX_NOTE,
127+
SUBMIT_NOTE,
128+
{
129+
"en-US":
130+
'schema.json example: {"type":"object","properties":{"title":{"type":"string"}}}. The schema is sent as an object, not a JSON string. Extraction does not support audio/video; reused parse results must still be retained by the service.',
131+
"zh-CN":
132+
'schema.json 示例:{"type":"object","properties":{"title":{"type":"string"}}}。Schema 按对象传输,不转为 JSON 字符串。抽取不支持音视频;复用的解析结果必须仍在服务保留期内。',
133+
},
134+
],
135+
exampleArgs: [
136+
'--file-url "https://example.com/contract.pdf" --schema-file schema.json --workspace-id ws-example',
137+
"--parsed-file-biz-id parse-example --schema-file schema.json --citation-required true --allow-inference false",
138+
'--file-url "https://example.com/contract.pdf" --config-id config-example --output json',
139+
],
140+
async validate(flags) {
141+
await buildBody(flags);
142+
},
143+
async run(ctx) {
144+
await runSubmit(ctx, "extract", await buildBody(ctx.flags));
145+
},
146+
});
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import { createResultCommand } from "./result.ts";
2+
export default createResultCommand("parse");

0 commit comments

Comments
 (0)