
Byted Data Label
- 2 installs
- 408 repo stars
- Updated August 3, 2026
- volcengine/agentkit-samples
Uses the Seederive platform and LLMs to batch-label text, audio, and image data for sentiment, classification, opinion extraction, translation, and scoring.
About
Drives the Seederive unstructured-data labeling platform to run LLM-based batch analysis over text, audio, and image data. A developer uses it for sentiment analysis, tag classification, opinion extraction, translation, spam detection, and content scoring on CSV/Excel inputs.
- LLM batch labeling with tag-library management and prompt optimization
- Authenticates with Volcengine AK/SK and runs via scripts/seederive.py
Byted Data Label by the numbers
- 2 all-time installs (skills.sh)
- Ranked #1,757 of 2,065 Data Science & ML skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/volcengine/agentkit-samples --skill byted-data-labelAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 408 |
| Last updated | August 3, 2026 |
| Repository | volcengine/agentkit-samples ↗ |
What it does
Uses the Seederive platform and LLMs to batch-label text, audio, and image data for sentiment, classification, opinion extraction, translation, and scoring.
Files
Seederive 非结构化打标平台
你是 Seederive 平台的操作助手。所有 Seederive 操作从这里开始。
什么是 Seederive
Seederive 用 LLM 对文本/语音/图片数据做情感分析、标签分类、观点提取等批量处理。
认证配置
使用前需要设置 AK/SK 环境变量:
| 环境变量 | 说明 | 必填 |
|---|---|---|
VOLCENGINE_ACCESS_KEY | Access Key | 是 |
VOLCENGINE_SECRET_KEY | Secret Key | 是 |
验证连通性
设置好环境变量后,执行以下命令验证:
python3 ${SKILL_DIR}/scripts/seederive.py task list --page-size 1如果返回 "code": 0 表示连通成功。如果返回认证错误,请检查 AK/SK 是否正确。
执行命令的方式
python3 ${SKILL_DIR}/scripts/seederive.py <子命令和参数>第一步:判断用户意图
阅读用户的需求,对照下表确定属于哪个场景:
| 场景 | 用户说了什么(示例) | 下一步 |
|---|---|---|
| A. 快速试效果 | "帮我分析这几条评论" / "试一下情感分析" / "看看这些文本的标签" | → 直接用 quick-preview,见下方「场景 A」 |
| B. 创建批量任务 | "帮我对这个数据表做情感分析" / "建一个打标任务" | → 读取 ${SKILL_DIR}/references/task.md 获取详细指引 |
| C. 需要标签体系 | "按我们的标签分类" / "建一个标签库" / "主体识别" | → 读取 ${SKILL_DIR}/references/tag-base.md 获取详细指引 |
| D. 优化效果 | "效果不好" / "帮我优化" / "上传错题" / "换个模型" | → 读取 ${SKILL_DIR}/references/optimize.md 获取详细指引 |
| E. 不确定 | "我有一批数据想处理" / "能做什么" | → 先问用户数据是什么、想得到什么结果,再回到本表判断 |
重要:场景 B/C/D 的具体操作步骤、参数说明、JSON 格式都在对应的参考文件中。你必须用 Read 工具读取对应文件后再执行,本文件不包含这些细节。
场景 A:快速试效果(唯一可以直接执行的场景)
这是最轻量的路径,无需创建任务,传几条文本就能看结果。
支持的分析类型
| 分析类型 | nodeType 值 | 输出 | 额外参数 |
|---|---|---|---|
| 情感分析 | EMOTION_DETECTION | 正面/负面/中性 + 原因 | 无 |
| 营销水军识别 | SHILL_DETECTION | 是/否 + 原因 | 无 |
| 观点提取 | OPINION_SUMMARY | 核心观点 + 理由 | 无 |
| 内容评分 | CONTENT_SCORING | 质量/原创/有用/合规评分 | 无 |
| 翻译 | TRANSLATION | 翻译结果 | --target-language |
| 标签分类 | TAG_DETECTION | 多级标签 | --tag-base-id(需要先建标签库,见场景 C) |
| 主体识别 | SUBJECT_DETECTION | 多级主体 | --tag-base-id(需要先建标签库,见场景 C) |
| 自定义分析 | CUSTOM_APPLICATION | 自定义 | --prompt + --output-fields |
执行方式
方式一:直接传文本(推荐,最快)
python3 ${SKILL_DIR}/scripts/seederive.py task quick-preview \
--raw-data '["文本1", "文本2", "文本3"]' \
--node-type EMOTION_DETECTION \
--input-column "评论内容"方式二:上传文件
python3 ${SKILL_DIR}/scripts/seederive.py task quick-preview \
--file data.csv \
--node-type EMOTION_DETECTION \
--input-column "评论内容"方式三:导出结果为 CSV 文件
python3 ${SKILL_DIR}/scripts/seederive.py task quick-preview \
--raw-data '["文本1", "文本2"]' \
--node-type EMOTION_DETECTION \
--input-column "评论内容" \
--response-format csv --output result.csv自定义分析示例:
python3 ${SKILL_DIR}/scripts/seederive.py task quick-preview \
--raw-data '["今天天气真好", "堵车堵了两小时"]' \
--node-type CUSTOM_APPLICATION \
--input-column "内容" \
--prompt "提取关键词和情绪强度" \
--output-fields '[{"fieldName":"keywords","fieldType":"String"},{"fieldName":"intensity","fieldType":"String"}]'quick-preview 全部参数
| 参数 | 必填 | 说明 |
|---|---|---|
--raw-data | 与 file 二选一 | JSON 字符串数组或对象数组 |
--raw-data-file | 与上二选一 | JSON 文件路径 |
--file | 与 raw-data 二选一 | CSV / Excel 文件 |
--node-type | 是 | 分析类型,见上表 |
--input-column | 是 | 待处理文本的列名 |
--max-rows | 否 | 最大处理行数(默认 10,上限 50) |
--tag-base-id | TAG/SUBJECT 需要 | 标签库 ID |
--prompt | CUSTOM 需要 | 自定义提示词 |
--output-fields | CUSTOM 需要 | 输出字段 JSON 数组 |
--target-language | TRANSLATION 用 | 目标语言(默认"中文") |
--response-format | 否 | json(默认)或 csv |
--output | 否 | CSV 输出文件路径 |
场景之间的流转
场景 A(试效果)
│
├─ 效果满意 + 数据量大 → 场景 B(建正式任务批量跑)
│ → 读取 ${SKILL_DIR}/references/task.md
│
├─ 需要标签分类 → 场景 C(先建标签库)→ 回到 A 或 B
│ → 读取 ${SKILL_DIR}/references/tag-base.md
│
└─ 效果不满意 → 场景 D(优化提示词/换模型)→ 回到 A 验证
→ 读取 ${SKILL_DIR}/references/optimize.md关键原则
1. 先试后建:建议用户先用 quick-preview 试效果,满意后再创建正式任务 2. 渐进披露:不要一次给用户灌输所有概念,按需引导到对应参考文件 3. 按需加载:只有需要执行场景 B/C/D 时才去读取对应参考文件
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.SKILL: seederive-optimize
Seederive 提示词优化 SKILL —— 教会 Agent 通过错题管理和提示词优化来提升任务效果
触发条件
当用户提到以下意图时触发:
- 任务效果不好,需要优化(如"这个任务效果不好,帮我优化")
- 上传错题/标注错误案例(如"这些结果不对,帮我上传错题")
- 优化提示词(如"帮我优化一下提示词")
- 查看优化报告(如"优化结果怎么样")
- 切换模型(如"换个模型试试")
- 生成提示词(如"帮我写一个提示词")
前置条件
- 确保已完成
SKILL.md中的前置步骤(设置VOLCENGINE_ACCESS_KEY和VOLCENGINE_SECRET_KEY) - 已创建任务(需要 taskId 和 nodeId),详见
${SKILL_DIR}/references/task.md
脚本命令速查
错题管理
| 操作 | 命令 |
|---|---|
| 上传错题(文本) | python ${SKILL_DIR}/scripts/seederive.py error-case create-text --task-id 123 --node-id emotion_1 --items-file items.json |
| 上传错题(文件) | python ${SKILL_DIR}/scripts/seederive.py error-case create-file --task-id 123 --file errors.csv |
| 查询错题列表 | python ${SKILL_DIR}/scripts/seederive.py error-case list --task-id 123 |
| 删除错题 | python ${SKILL_DIR}/scripts/seederive.py error-case delete --ids 1,2,3 |
| 下载错题模板 | python ${SKILL_DIR}/scripts/seederive.py error-case template --format csv |
提示词管理
| 操作 | 命令 |
|---|---|
| 查询提示词详情 | python ${SKILL_DIR}/scripts/seederive.py prompt detail --task-id 123 --node-id emotion_1 |
| 生成提示词 | python ${SKILL_DIR}/scripts/seederive.py prompt generate --description "分析商品评论的情感" |
| 触发优化 | python ${SKILL_DIR}/scripts/seederive.py prompt optimize --task-id 123 |
| 查询优化报告 | python ${SKILL_DIR}/scripts/seederive.py prompt report --task-id 123 --node-id emotion_1 |
模型管理
| 操作 | 命令 |
|---|---|
| 查询可用模型 | python ${SKILL_DIR}/scripts/seederive.py model list |
| 修改节点模型 | python ${SKILL_DIR}/scripts/seederive.py model set-node --task-id 123 --node-id emotion_1 --model-id 2 --temperature 0.3 |
优化流程
完整的优化闭环分为四步:上传错题 → 触发优化 → 查看报告 → 应用优化
1. 查看当前提示词 → python ${SKILL_DIR}/scripts/seederive.py prompt detail ...
2. 上传错题 → python ${SKILL_DIR}/scripts/seederive.py error-case create-text/create-file ...
3. 触发优化 → python ${SKILL_DIR}/scripts/seederive.py prompt optimize ...
4. 查看优化报告 → python ${SKILL_DIR}/scripts/seederive.py prompt report ...
5.(可选)切换模型 → python ${SKILL_DIR}/scripts/seederive.py model list → model set-node ...
6. 回到 seederive-task 重新执行任务验证效果执行步骤
场景一:发现效果不好,上传错题并优化
用户说:"这个任务效果不好,帮我优化"
步骤一:执行脚本,查看当前提示词状态
python ${SKILL_DIR}/scripts/seederive.py prompt detail --task-id 123 --node-id emotion_1步骤二(Agent 模型能力):分析任务结果,识别哪些结果不准确,整理为错题格式
步骤三(Agent 模型能力):构造错题 JSON 文件 items.json
错题项格式:
[
{
"input": "userTemplate=这个产品真的太烂了,永远不会再买",
"actualOutput": "中性",
"expectedOutput": "负面"
},
{
"input": "userTemplate=还行吧,一般般",
"actualOutput": "正面",
"expectedOutput": "中性"
}
]注意:input字段的格式为占位符名=值。占位符名需要与提示词中的占位符一致,大部分情况下为userTemplate。
步骤四:执行脚本,上传错题
python ${SKILL_DIR}/scripts/seederive.py error-case create-text --task-id 123 --node-id emotion_1 --items-file items.json步骤五:执行脚本,触发提示词优化
python ${SKILL_DIR}/scripts/seederive.py prompt optimize --task-id 123步骤六:执行脚本,查询优化报告(优化是异步的,需要等待完成)
python ${SKILL_DIR}/scripts/seederive.py prompt report --task-id 123 --node-id emotion_1步骤七(Agent 模型能力):分析优化报告中的 score 和 analysis,判断是否达标。如果达标,建议用户回到 seederive-task 重新执行任务。
场景二:通过文件上传错题
用户说:"我有一个 CSV 文件,里面是标注好的错题"
步骤一:执行脚本,下载错题模板(可选,帮助用户了解格式)
python ${SKILL_DIR}/scripts/seederive.py error-case template --format csv模板包含列:task_id, field, input, actual_output, expected_output
步骤二:执行脚本,上传错题文件
python ${SKILL_DIR}/scripts/seederive.py error-case create-file --task-id 123 --file /path/to/errors.csv步骤三:继续触发优化(同场景一的步骤五)
场景三:查看并管理错题
查询错题列表:
python ${SKILL_DIR}/scripts/seederive.py error-case list --task-id 123 --page 1 --page-size 20返回中 promptOptStatus 状态码含义:
| 状态码 | 含义 |
|---|---|
| 0 | 等待优化 |
| 1 | 提交中 |
| 2 | 优化中 |
| 3 | 优化成功 |
| 4 | 优化失败 |
删除错题(只有在优化状态为"成功"或"失败"时才允许删除):
python ${SKILL_DIR}/scripts/seederive.py error-case delete --ids 1,2,3场景四:切换模型
用户说:"换个模型试试"
步骤一:执行脚本,查看可用模型
python ${SKILL_DIR}/scripts/seederive.py model list步骤二(Agent 模型能力):根据任务需求推荐合适的模型
步骤三:执行脚本,修改节点的模型配置
python ${SKILL_DIR}/scripts/seederive.py model set-node --task-id 123 --node-id emotion_1 --model-id 2 --temperature 0.3 --max-token 4096场景五:生成提示词
用户说:"帮我写一个分析商品评论的提示词"
python ${SKILL_DIR}/scripts/seederive.py prompt generate --description "分析电商商品评论,提取用户对产品质量、价格、物流的评价"错误处理
| 错误关键词 | 含义 | 处理建议 |
|---|---|---|
| "当前任务正在优化中" | 优化进行中 | 等待完成后再操作 |
| "错题未发生新增或删除" | 无新错题 | 需要先上传新错题才能触发优化 |
| "当前提示词优化未完成" | 优化中 | 等待优化完成后再删除错题 |
与其他 SKILL 的关系
- 本 SKILL 依赖已创建的任务(需要 taskId 和 nodeId),详见
${SKILL_DIR}/references/task.md - 优化完成后,建议用 quick-preview 验证效果,或读取
${SKILL_DIR}/references/task.md重新执行任务
SKILL: seederive-tag-base
Seederive 标签库管理 SKILL —— 教会 Agent 创建和管理标签体系,为标签识别和主体识别提供分类依据
触发条件
当用户提到以下意图时触发:
- 创建标签体系/标签库(如"帮我建一个商品分类的标签体系")
- 管理已有标签库(如"看看我的标签库")
- 上传标签文件(如"我有一个标签分类 CSV,帮我上传")
- 测试标签召回效果(如"测一下这个标签库能不能准确分类")
前置条件
确保已完成 SKILL.md 中的前置步骤(设置 VOLCENGINE_ACCESS_KEY 和 VOLCENGINE_SECRET_KEY)。
脚本命令速查
| 操作 | 命令 |
|---|---|
| 上传标签文件 | python ${SKILL_DIR}/scripts/seederive.py tag-base upload --file tags.csv |
| 创建标签库 | python ${SKILL_DIR}/scripts/seederive.py tag-base create --name "名称" --type tag --doc-info '[...]' |
| 编辑标签库 | python ${SKILL_DIR}/scripts/seederive.py tag-base update --id 42 --name "新名称" |
| 标签库详情 | python ${SKILL_DIR}/scripts/seederive.py tag-base get --id 42 |
| 标签库列表 | python ${SKILL_DIR}/scripts/seederive.py tag-base list |
| 召回测试 | python ${SKILL_DIR}/scripts/seederive.py tag-base retrieval-test --id 42 --question "测试文本" |
| 删除标签库 | python ${SKILL_DIR}/scripts/seederive.py tag-base delete --id 42 |
核心概念
标签库类型
| 类型 | 值 | 用途 | 配合节点 |
|---|---|---|---|
| 标签分类 | tag | 按标签体系对文本进行分类 | TAG_DETECTION |
| 主体识别 | subject | 识别文本中提及的实体/品牌 | SUBJECT_DETECTION |
标签库文件格式
标签分类(tag)模板:
| 一级标签 | 一级标签说明 | 二级标签 | 二级标签说明 | 三级标签 | 三级标签说明 | 四级标签 | 四级标签说明 |
|---|---|---|---|---|---|---|---|
| 服务 | 对XX产品售前售中售后的服务评价 | 售后服务 | 对XX产品售后服务的评价 | 维修服务 | 对XX产品售后服务中维修服务的评价 | 维修专业度 | 对XX产品维修服务是否专业的评价 |
- 一级标签为必填,二级三级四级标签选填
- 标签最多支持四级层级
主体识别(subject)模板:
| 一级主体 | 一级主体说明 | 二级主体 | 二级主体说明 | 三级主体 | 三级主体说明 |
|---|---|---|---|---|---|
| 集团名 | XX集团也可能被称为XXX | 公司名 | XX公司也可能被称为XXX | 产品名 | XX公司也可能被称为XXX |
- 一级主体为必填,二级三级主体选填
- 主体最多支持三级层级
创建标签库的完整流程
1. 上传标签文件 → python ${SKILL_DIR}/scripts/seederive.py tag-base upload --file tags.csv
2. 创建标签库 → python ${SKILL_DIR}/scripts/seederive.py tag-base create --name "..." --type tag --doc-info '[...]'
3. 召回测试 → python ${SKILL_DIR}/scripts/seederive.py tag-base retrieval-test --id 42 --question "..."
4. 在任务中使用 → seederive-task 中的 TAG_DETECTION/SUBJECT_DETECTION 节点引用 tagBaseId执行步骤
场景一:创建标签分类库
用户说:"帮我建一个商品评论的标签体系"
步骤一(Agent 模型能力):根据用户描述,判断标签库类型(tag 或 subject)和标签体系结构
步骤二:执行脚本,上传标签文件
如果用户提供了文件:
python ${SKILL_DIR}/scripts/seederive.py tag-base upload --file /path/to/商品标签.csv返回示例(JSON 输出):
{
"code": 0,
"data": {
"fileId": "file_abc123",
"fileName": "商品标签.csv"
}
}记录返回的 fileId,下一步创建标签库时需要用到。步骤三:执行脚本,创建标签库
python ${SKILL_DIR}/scripts/seederive.py tag-base create \
--name "商品评论标签体系" \
--type tag \
--description "用于对商品评论进行多维度标签分类" \
--doc-info '[{"fileId":"file_abc123","fileName":"商品标签.csv"}]'返回示例:
{
"code": 0,
"data": {
"id": 42,
"name": "商品评论标签体系",
"type": "tag",
"kbId": "kb_xyz789",
"docInfo": [{ "fileId": "file_abc123", "docId": "doc_456", "fileName": "商品标签.csv" }]
}
}记录返回的 id(即 tagBaseId),后续在 seederive-task 的 TAG_DETECTION 节点中使用。步骤四:执行脚本,测试标签召回效果
python ${SKILL_DIR}/scripts/seederive.py tag-base retrieval-test --id 42 --question "这个手机壳质量很差,用了两天就裂了"步骤五(Agent 模型能力):分析召回结果中的 chunks 和 score,判断标签匹配是否准确。如果准确,告知用户标签库已就绪,可以在创建任务时使用 tagBaseId: 42。
场景二:创建主体识别库
用户说:"帮我建一个汽车品牌的主体库"
步骤一:执行脚本,上传主体文件
python ${SKILL_DIR}/scripts/seederive.py tag-base upload --file /path/to/汽车品牌.csv步骤二:执行脚本,创建主体库
python ${SKILL_DIR}/scripts/seederive.py tag-base create \
--name "汽车品牌主体库" \
--type subject \
--description "识别评论中提及的汽车品牌和车型" \
--doc-info '[{"fileId":"file_def456","fileName":"汽车品牌.csv"}]'步骤三:执行脚本,召回测试
python ${SKILL_DIR}/scripts/seederive.py tag-base retrieval-test --id 43 --question "刚提了小鹏G6顶配版,续航真的扎实"场景三:查看和管理标签库
查看标签库列表:
python ${SKILL_DIR}/scripts/seederive.py tag-base list按类型过滤:
python ${SKILL_DIR}/scripts/seederive.py tag-base list --type tag --page-num 1 --page-size 10查看标签库详情:
python ${SKILL_DIR}/scripts/seederive.py tag-base get --id 42编辑标签库:
python ${SKILL_DIR}/scripts/seederive.py tag-base update --id 42 --name "商品评论标签体系(v2)" --description "更新后的标签体系"删除标签库:
python ${SKILL_DIR}/scripts/seederive.py tag-base delete --id 42场景四:更新标签库文件
用户说:"标签体系有更新,帮我换个新文件"
步骤一:执行脚本,上传新文件
python ${SKILL_DIR}/scripts/seederive.py tag-base upload --file /path/to/商品标签_v2.csv步骤二:执行脚本,更新标签库(替换文件)
python ${SKILL_DIR}/scripts/seederive.py tag-base update --id 42 --doc-info '[{"fileId":"file_new789","fileName":"商品标签_v2.csv"}]'注意:doc-info 中只包含新文件时,旧文件会被自动删除。如果要保留旧文件,需要同时包含旧文件和新文件的信息。步骤三:执行脚本,重新测试召回效果
python ${SKILL_DIR}/scripts/seederive.py tag-base retrieval-test --id 42 --question "测试文本"错误处理
| 错误关键词 | 含义 | 处理建议 |
|---|---|---|
| "标签库名称已存在" | 名称重复 | 换一个名称 |
| "标签库不存在" | ID 错误 | 确认标签库 ID |
| "file 不能为空" | 文件为空 | 检查文件路径 |
与其他 SKILL 的关系
- 标签库创建后获得的
tagBaseId用于任务中的TAG_DETECTION和SUBJECT_DETECTION节点 - 标签库创建不是必须前置:只有任务需要标签分类或主体识别功能时才需要创建标签库
- 典型流程:先创建标签库 → 读取
${SKILL_DIR}/references/task.md创建任务 → 效果不佳时读取${SKILL_DIR}/references/optimize.md优化
SKILL: seederive-task
Seederive 任务管理 SKILL —— 教会 Agent 创建、管理和预览打标任务
触发条件
当用户提到以下意图时触发:
- 创建打标/标注/分析任务(如"帮我对这批评论做情感分析")
- 查看/管理已有任务(如"看看我有哪些任务")
- 预览数据或查看任务结果(如"预览一下这个任务的输出")
- 删除任务
- 任务回填
前置条件
确保已完成 SKILL.md 中的前置步骤(设置 VOLCENGINE_ACCESS_KEY 和 VOLCENGINE_SECRET_KEY)。
脚本命令速查
| 操作 | 命令 |
|---|---|
| 创建任务 | python ${SKILL_DIR}/scripts/seederive.py task create --name "名称" --flow-config-file flow.json |
| 更新任务 | python ${SKILL_DIR}/scripts/seederive.py task update --id 123 --name "新名称" |
| 删除任务 | python ${SKILL_DIR}/scripts/seederive.py task delete --id 123 |
| 获取详情 | python ${SKILL_DIR}/scripts/seederive.py task get --id 123 |
| 任务列表 | python ${SKILL_DIR}/scripts/seederive.py task list --keyword "情感" |
| 任务回填 | python ${SKILL_DIR}/scripts/seederive.py task backfill --bdb-task-id 456 --start-time "2025-01-01" --end-time "2025-01-07" |
| 数据预览 | python ${SKILL_DIR}/scripts/seederive.py task preview --input-dataset-file ds.json --limit 10 |
| 结果预览 | python ${SKILL_DIR}/scripts/seederive.py task result --task-id 123 --page-size 10 |
| 轻量预览 | python ${SKILL_DIR}/scripts/seederive.py task quick-preview --file data.csv --node-type EMOTION_DETECTION --input-column "评论内容" |
核心概念:taskFlowConfig
任务的核心是 taskFlowConfig,它定义了数据处理的 DAG 流程图。创建任务时通过 --flow-config-file 传入 JSON 文件,或 --flow-config 传入 JSON 字符串。
{
"nodes": [ ... ], // 节点列表
"edges": [ ... ], // 连接关系(sourceNodeId → targetNodeId)
"finalSchema": null, // 系统自动生成,创建时无需填写
"llmConfig": null // 可选,LLM 模型配置
}节点类型总览
| 节点类型 | 名称 | 优先级 | 说明 | 需要标签库 |
|---|---|---|---|---|
DS_INPUT | 数据输入 | 0 | 数据源入口,必须有 | 否 |
ASR | 语音转文字 | 1 | 提取语音中的文字 | 否 |
OCR | 图片文字提取 | 1 | 从图片中提取文字 | 否 |
TRANSLATION | 多语言翻译 | 2 | 翻译成目标语言 | 否 |
SHILL_DETECTION | 营销水军识别 | 3 | 识别营销水军内容 | 否 |
SUBJECT_DETECTION | 主体识别 | 3 | 识别文本中提及的主体 | 是 |
TAG_DETECTION | 标签识别 | 3 | 按标签体系分类 | 是 |
CONTENT_SCORING | 内容评分 | 4 | 质量/原创/有用性/合规评分 | 否 |
OPINION_SUMMARY | 观点总结 | 5 | 提取核心观点和理由 | 否 |
EMOTION_DETECTION | 情感识别 | 6 | 正面/负面/中性情感分析 | 否 |
CUSTOM_APPLICATION | 自定义应用 | 7 | 用户自定义提示词处理 | 否 |
DS_OUTPUT | 数据输出 | 99 | 输出结果到目标表,必须有 | 否 |
节点连接规则
节点通过 edges 连接,必须遵循优先级顺序(低→高)。每个节点的 inputAvailableNodeTypes 定义了它可以接收哪些上游节点的数据。
节点详细配置
DS_INPUT(数据输入节点)
每个任务必须有且仅有一个 DS_INPUT 节点,定义输入数据源:
{
"nodeId": "ds_input_1",
"nodeType": "DS_INPUT",
"nodeName": "数据输入",
"config": {
"inputDataSet": {
"type": "AEOLUS_DATASET",
"aeolusDatasetId": "数据集ID",
"database": "数据库名",
"table": "表名",
"cluster": "集群名"
}
}
}EMOTION_DETECTION(情感识别节点)
{
"nodeId": "emotion_1",
"nodeType": "EMOTION_DETECTION",
"nodeName": "情感识别",
"inputFields": [
{ "fieldName": "content", "fieldType": "ColumnReference", "value": ["评论内容"] }
],
"outputFields": [
{ "fieldName": "emo", "fieldType": "Enum", "description": "情感分类(正面/负面/中性)" },
{ "fieldName": "emo_reason", "fieldType": "String", "description": "情感分析说明" }
]
}TAG_DETECTION(标签识别节点)
需要先创建标签库获得tagBaseId,详见${SKILL_DIR}/references/tag-base.md
{
"nodeId": "tag_1",
"nodeType": "TAG_DETECTION",
"nodeName": "标签识别",
"inputFields": [
{ "fieldName": "content", "fieldType": "ColumnReference", "value": ["评论内容"] },
{ "fieldName": "tag_base_id", "fieldType": "TagBase", "value": "标签库ID" }
],
"outputFields": [
{ "fieldName": "l1_tag", "fieldType": "String" }, { "fieldName": "l1_reason", "fieldType": "String" },
{ "fieldName": "l2_tag", "fieldType": "String" }, { "fieldName": "l2_reason", "fieldType": "String" },
{ "fieldName": "l3_tag", "fieldType": "String" }, { "fieldName": "l3_reason", "fieldType": "String" },
{ "fieldName": "l4_tag", "fieldType": "String" }, { "fieldName": "l4_reason", "fieldType": "String" }
]
}SUBJECT_DETECTION(主体识别节点)
需要先创建标签库(subject 类型)获得tagBaseId,详见${SKILL_DIR}/references/tag-base.md
{
"nodeId": "subject_1",
"nodeType": "SUBJECT_DETECTION",
"nodeName": "主体识别",
"inputFields": [
{ "fieldName": "content", "fieldType": "ColumnReference", "value": ["评论内容"] },
{ "fieldName": "tag_base_id", "fieldType": "TagBase", "value": "标签库ID" }
],
"outputFields": [
{ "fieldName": "l1_subject", "fieldType": "String" }, { "fieldName": "l1_reason", "fieldType": "String" },
{ "fieldName": "l2_subject", "fieldType": "String" }, { "fieldName": "l2_reason", "fieldType": "String" },
{ "fieldName": "l3_subject", "fieldType": "String" }, { "fieldName": "l3_reason", "fieldType": "String" }
]
}SHILL_DETECTION(营销水军识别节点)
{
"nodeId": "shill_1",
"nodeType": "SHILL_DETECTION",
"nodeName": "营销水军识别",
"inputFields": [
{ "fieldName": "content", "fieldType": "ColumnReference", "value": ["评论内容"] }
],
"outputFields": [
{ "fieldName": "is_shill", "fieldType": "String", "description": "是否营销水军" },
{ "fieldName": "is_shill_reason", "fieldType": "String", "description": "识别理由" }
]
}OPINION_SUMMARY(观点总结节点)
{
"nodeId": "opinion_1",
"nodeType": "OPINION_SUMMARY",
"nodeName": "观点总结",
"inputFields": [
{ "fieldName": "content", "fieldType": "ColumnReference", "value": ["评论内容"] }
],
"outputFields": [
{ "fieldName": "opinion", "fieldType": "String" },
{ "fieldName": "opinion_reason", "fieldType": "String" }
]
}CUSTOM_APPLICATION(自定义应用节点)
{
"nodeId": "custom_1",
"nodeType": "CUSTOM_APPLICATION",
"nodeName": "自定义分析",
"inputFields": [
{ "fieldName": "content", "fieldType": "ColumnReference", "value": ["评论内容"] },
{ "fieldName": "prompt", "fieldType": "Prompt", "value": "请分析以下内容..." },
{ "fieldName": "outputSchema", "fieldType": "OutputSchema", "value": [
{ "fieldName": "summary", "fieldType": "String", "description": "摘要" },
{ "fieldName": "keywords", "fieldType": "String", "description": "关键词" }
]}
],
"outputFields": [
{ "fieldName": "summary", "fieldType": "String" },
{ "fieldName": "keywords", "fieldType": "String" }
]
}TRANSLATION(翻译节点)
{
"nodeId": "trans_1",
"nodeType": "TRANSLATION",
"nodeName": "多语言翻译",
"inputFields": [
{ "fieldName": "content", "fieldType": "ColumnReference", "value": ["评论内容"] },
{ "fieldName": "target_language", "fieldType": "String", "value": "中文" }
],
"outputFields": [
{ "fieldName": "translation", "fieldType": "String" }
]
}ASR(语音转文字节点)
{
"nodeId": "asr_1",
"nodeType": "ASR",
"nodeName": "语音转文字",
"inputFields": [
{ "fieldName": "content", "fieldType": "ColumnReference", "value": ["音频URL"] },
{ "fieldName": "enable_speaker_diarization", "fieldType": "Boolean", "value": true }
],
"outputFields": [
{ "fieldName": "asr", "fieldType": "String" }
]
}DS_OUTPUT(数据输出节点)
每个任务必须有且仅有一个 DS_OUTPUT 节点:
{
"nodeId": "ds_output_1",
"nodeType": "DS_OUTPUT",
"nodeName": "数据输出",
"inputFields": [
{ "fieldName": "target_dataset", "fieldType": "String", "value": "输出表名" },
{ "fieldName": "columns", "fieldType": "List", "value": ["emo", "emo_reason"] }
]
}执行步骤
场景一:创建情感分析任务
用户说:"帮我对这批评论数据做情感分析"
步骤一(Agent 模型能力):分析用户需求,确定需要使用 EMOTION_DETECTION 节点
步骤二:执行脚本,查询现有任务,避免重复创建
python ${SKILL_DIR}/scripts/seederive.py task list --keyword "情感"步骤三(Agent 模型能力):如果已存在同名任务,询问用户是否要新建或复用
步骤四(Agent 模型能力):根据用户需求构造 taskFlowConfig JSON,保存为临时文件 flow.json
步骤五:执行脚本,创建任务
python ${SKILL_DIR}/scripts/seederive.py task create --name "评论情感分析" --description "对评论数据进行情感分析" --flow-config-file flow.jsonflow.json 示例内容见本文档「核心概念:taskFlowConfig」部分,Agent 需根据节点配置参考构造完整 JSON。步骤六:执行脚本,查询任务状态
python ${SKILL_DIR}/scripts/seederive.py task get --id 456步骤七(Agent 模型能力):分析任务状态,向用户报告
场景二:翻译后再做情感分析(多节点串联)
用户说:"帮我把英文评论翻译成中文,然后做情感分析"
步骤一(Agent 模型能力):构造包含 TRANSLATION → EMOTION_DETECTION 的串联 taskFlowConfig,注意 emotion 节点的 inputFields 中 content 引用 translation 字段(翻译节点的输出)
步骤二:执行脚本,创建任务
python ${SKILL_DIR}/scripts/seederive.py task create --name "翻译+情感分析" --flow-config-file flow.json场景三:预览数据和查看结果
预览输入数据(Agent 先构造 inputDataSet JSON 文件 ds.json):
python ${SKILL_DIR}/scripts/seederive.py task preview --input-dataset-file ds.json --limit 10查看任务输出结果:
python ${SKILL_DIR}/scripts/seederive.py task result --task-id 123 --page-size 10如需指定分区:
python ${SKILL_DIR}/scripts/seederive.py task result --task-id 123 --partition '{"p_date":"2025-01-01"}'场景四:任务回填
python ${SKILL_DIR}/scripts/seederive.py task backfill --bdb-task-id 456 --start-time "2025-01-01 00:00:00" --end-time "2025-01-07 23:59:59"场景五:轻量预览
用户说:"帮我用这个 CSV 文件试一下情感分析的效果"
轻量预览无需创建完整任务,直接上传文件 + 指定节点类型即可预览结果。
情感分析预览:
python ${SKILL_DIR}/scripts/seederive.py task quick-preview \
--file comments.csv --node-type EMOTION_DETECTION --input-column "评论内容"标签识别预览(需要标签库 ID):
python ${SKILL_DIR}/scripts/seederive.py task quick-preview \
--file data.csv --node-type TAG_DETECTION --input-column "文本" --tag-base-id 42自定义分析预览(需要提示词和输出字段定义):
python ${SKILL_DIR}/scripts/seederive.py task quick-preview \
--file data.csv --node-type CUSTOM_APPLICATION --input-column "内容" \
--prompt "提取关键词和摘要" \
--output-fields '[{"fieldName":"keywords","fieldType":"String"},{"fieldName":"summary","fieldType":"String"}]'翻译预览:
python ${SKILL_DIR}/scripts/seederive.py task quick-preview \
--file data.csv --node-type TRANSLATION --input-column "content" --target-language "中文"结果导出为 CSV 文件:
python ${SKILL_DIR}/scripts/seederive.py task quick-preview \
--file comments.csv --node-type EMOTION_DETECTION --input-column "评论内容" \
--response-format csv --output result.csv直接传原始数据(无需文件,适合 Agent 调用):
# 字符串数组模式:每个元素作为 inputColumn 的值
python ${SKILL_DIR}/scripts/seederive.py task quick-preview \
--raw-data '["这个产品太好了", "质量一般", "很差劲"]' \
--node-type EMOTION_DETECTION --input-column "评论内容"
# 对象数组模式:支持多列
python ${SKILL_DIR}/scripts/seederive.py task quick-preview \
--raw-data '[{"评论内容":"太好了","用户":"A"},{"评论内容":"一般","用户":"B"}]' \
--node-type EMOTION_DETECTION --input-column "评论内容"
# 从 JSON 文件读取原始数据
python ${SKILL_DIR}/scripts/seederive.py task quick-preview \
--raw-data-file data.json --node-type EMOTION_DETECTION --input-column "评论内容"支持的参数:
| 参数 | 说明 |
|---|---|
--file | CSV / Excel 文件路径(与 --raw-data 二选一) |
--raw-data | 原始数据 JSON 数组(与 --file 二选一) |
--raw-data-file | 原始数据 JSON 文件路径(与 --file 二选一) |
--node-type | 节点类型(必填) |
--input-column | 作为待处理文本的列名(必填) |
--max-rows | 最大处理行数(默认 10,上限 50) |
--tag-base-id | 标签库 ID(TAG_DETECTION / SUBJECT_DETECTION 需要) |
--prompt | 自定义提示词(CUSTOM_APPLICATION 需要) |
--output-fields | 输出字段 JSON 数组(CUSTOM_APPLICATION 需要) |
--target-language | 翻译目标语言(TRANSLATION 用,默认"中文") |
--response-format | 响应格式:json(默认)或 csv(返回文件下载) |
--output | CSV 输出文件路径(默认 quick_preview_result.csv) |
错误处理
脚本会自动解析返回的 JSON 并输出。如果 HTTP 状态码 >= 400,脚本以非零退出码退出。
| 错误关键词 | 含义 | 处理建议 |
|---|---|---|
| "认证失败" | AK/SK 无效 | 检查 VOLCENGINE_ACCESS_KEY/VOLCENGINE_SECRET_KEY |
| "任务不存在或无权限" | 无权限 | 确认任务 ID 和账号权限 |
| 400 参数错误 | taskFlowConfig 格式不对 | 检查 JSON 中的节点配置 |
与其他 SKILL 的关系
- 当需要使用
TAG_DETECTION或SUBJECT_DETECTION节点时,需先读取${SKILL_DIR}/references/tag-base.md创建标签库获取tagBaseId - 当任务效果不佳需要优化时,读取
${SKILL_DIR}/references/optimize.md进行提示词优化
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#!/usr/bin/env python3
"""
Seederive CLI — 统一的 API 调用脚本
Agent 和用户通过本脚本与 Seederive OpenAPI 交互,无需手动拼 URL / Header / JSON Body。
用法:
python seederive.py <子命令> <操作> [参数...]
子命令:
task 任务管理
prompt 提示词管理
error-case 错题管理
model 模型管理
tag-base 标签库管理
环境变量(也可通过 --base-url 参数覆盖):
VOLCENGINE_ACCESS_KEY 火山引擎 Access Key
VOLCENGINE_SECRET_KEY 火山引擎 Secret Key
SEEDERIVE_BASE_URL API 基础地址(可选)
"""
import argparse
import json
import os
import sys
from urllib.parse import urljoin
try:
import requests
except ImportError:
print("缺少 requests 库,正在安装...")
import subprocess
subprocess.check_call([sys.executable, "-m", "pip", "install", "requests", "-q"])
import requests
# ============================== 全局配置 ==============================
DEFAULT_BASE_URL = "https://sd6qlcofkmfq59riqgli0.apigateway-cn-beijing.volceapi.com"
API_PREFIX = "/profile_platform/openapi/v2/seederive"
def get_config(args):
"""从参数和环境变量构建配置"""
base_url = getattr(args, "base_url", None) or os.environ.get("SEEDERIVE_BASE_URL", DEFAULT_BASE_URL)
access_key = os.environ.get("VOLCENGINE_ACCESS_KEY", "")
secret_key = os.environ.get("VOLCENGINE_SECRET_KEY", "")
if not access_key or not secret_key:
print("错误:未提供认证凭证。请设置环境变量 VOLCENGINE_ACCESS_KEY 和 VOLCENGINE_SECRET_KEY",
file=sys.stderr)
sys.exit(1)
return {
"access_key": access_key,
"secret_key": secret_key,
"base_url": base_url.rstrip("/"),
}
# ============================== HTTP 工具 ==============================
def _url(config, path):
return config["base_url"] + API_PREFIX + path
def _headers(config, extra=None):
h = {
"Volc-Access-Key": config["access_key"],
"Volc-Secret-Key": config["secret_key"],
}
if extra:
h.update(extra)
return h
def _json_headers(config):
return _headers(config, {"Content-Type": "application/json"})
def _print_response(resp):
"""统一输出响应"""
try:
data = resp.json()
print(json.dumps(data, ensure_ascii=False, indent=2))
except Exception:
data = None
print(f"HTTP {resp.status_code}")
print(resp.text)
if resp.status_code >= 400:
sys.exit(1)
# 业务错误码检查:HTTP 200 但 code != 0 视为失败
if isinstance(data, dict) and "code" in data and data["code"] != 0:
sys.exit(1)
def _get(config, path, params=None):
resp = requests.get(_url(config, path), headers=_headers(config), params=params)
_print_response(resp)
return resp
def _post_json(config, path, body):
resp = requests.post(_url(config, path), headers=_json_headers(config), json=body)
_print_response(resp)
return resp
def _put_json(config, path, body):
resp = requests.put(_url(config, path), headers=_json_headers(config), json=body)
_print_response(resp)
return resp
def _delete(config, path, body=None):
if body is not None:
resp = requests.delete(_url(config, path), headers=_json_headers(config), json=body)
else:
resp = requests.delete(_url(config, path), headers=_headers(config))
_print_response(resp)
return resp
def _post_file(config, path, file_path, extra_fields=None):
if not os.path.isfile(file_path):
print(f"错误:文件不存在 {file_path}", file=sys.stderr)
sys.exit(1)
with open(file_path, "rb") as f:
files = {"file": (os.path.basename(file_path), f)}
data = extra_fields or {}
resp = requests.post(_url(config, path), headers=_headers(config), files=files, data=data)
_print_response(resp)
return resp
# ============================== task 子命令 ==============================
def task_create(args, config):
"""创建任务"""
body = {"name": args.name}
if args.description:
body["description"] = args.description
if args.flow_config:
body["taskFlowConfig"] = json.loads(args.flow_config)
if args.flow_config_file:
with open(args.flow_config_file) as f:
body["taskFlowConfig"] = json.load(f)
if args.schedule_type:
body["scheduleConfig"] = {"type": args.schedule_type}
_post_json(config, "/task", body)
def task_update(args, config):
"""更新任务"""
body = {}
if args.name:
body["name"] = args.name
if args.description:
body["description"] = args.description
if args.flow_config:
body["taskFlowConfig"] = json.loads(args.flow_config)
if args.flow_config_file:
with open(args.flow_config_file) as f:
body["taskFlowConfig"] = json.load(f)
_put_json(config, f"/task/{args.id}", body)
def task_delete(args, config):
"""删除任务"""
_delete(config, f"/task/{args.id}")
def task_get(args, config):
"""获取任务详情"""
_get(config, f"/task/{args.id}")
def task_list(args, config):
"""获取任务列表"""
params = {}
if args.keyword:
params["keyword"] = args.keyword
if args.creator:
params["creator"] = args.creator
if args.name:
params["name"] = args.name
params["page"] = args.page
params["pageSize"] = args.page_size
_get(config, "/task/list", params)
def task_backfill(args, config):
"""任务回填"""
body = {"bdbTaskId": args.bdb_task_id}
if args.start_time:
body["startTaskTime"] = args.start_time
if args.end_time:
body["endTaskTime"] = args.end_time
_post_json(config, "/task/backfill", body)
def task_preview(args, config):
"""数据预览"""
body = {"limit": args.limit}
if args.input_dataset:
body["inputDataSet"] = json.loads(args.input_dataset)
if args.input_dataset_file:
with open(args.input_dataset_file) as f:
body["inputDataSet"] = json.load(f)
_post_json(config, "/task/preview", body)
def task_result(args, config):
"""结果预览"""
body = {"taskId": args.task_id}
if args.page_num:
body["pageNum"] = args.page_num
if args.page_size:
body["pageSize"] = args.page_size
if args.partition:
body["partition"] = json.loads(args.partition)
_post_json(config, "/task/resultPreview", body)
def task_quick_preview(args, config):
"""轻量预览"""
resp_format = getattr(args, "response_format", "json") or "json"
def _handle_response(resp):
if resp_format == "csv" and resp.status_code == 200:
out = args.output or "quick_preview_result.csv"
with open(out, "wb") as f:
f.write(resp.content)
print(f"结果已保存到 {out}")
else:
_print_response(resp)
# rawData 模式:JSON body(二维数组 + columns)
raw_data_str = args.raw_data
if not raw_data_str and args.raw_data_file:
with open(args.raw_data_file) as f:
raw_data_str = f.read()
if raw_data_str:
parsed = json.loads(raw_data_str)
# 自动推断 columns 和 rawData 格式
if parsed and isinstance(parsed[0], dict):
# 对象数组 [{"col1":"v1"}, ...] → 提取 columns + 转二维数组
col_set = []
for obj in parsed:
for k in obj:
if k not in col_set:
col_set.append(k)
columns = col_set
raw_rows = [[str(obj.get(c, "")) if obj.get(c) is not None else "" for c in columns] for obj in parsed]
elif parsed and isinstance(parsed[0], list):
# 已经是二维数组,需要用户提供 --columns 或默认用 inputColumn
if args.columns:
columns = json.loads(args.columns)
else:
columns = [args.input_column]
raw_rows = parsed
else:
# 字符串数组 ["文本1", ...] → 单列二维数组
columns = [args.input_column]
raw_rows = [[str(item)] for item in parsed]
body = {
"nodeType": args.node_type,
"inputColumns": [args.input_column],
"columns": columns,
"rawData": raw_rows,
"responseFormat": resp_format,
}
if args.max_rows is not None:
body["maxRows"] = args.max_rows
if args.tag_base_id is not None:
body["tagBaseId"] = args.tag_base_id
if args.prompt:
body["prompt"] = args.prompt
if args.output_fields:
body["outputFields"] = args.output_fields
if args.target_language:
body["targetLanguage"] = args.target_language
if resp_format == "csv":
resp = requests.post(
_url(config, "/task/quick-preview"),
headers=_json_headers(config),
json=body,
)
_handle_response(resp)
else:
_post_json(config, "/task/quick-preview", body)
return
# 文件模式:multipart/form-data
if not args.file:
print("错误:--file 和 --raw-data 至少提供一个", file=sys.stderr)
sys.exit(1)
if not os.path.isfile(args.file):
print(f"错误:文件不存在 {args.file}", file=sys.stderr)
sys.exit(1)
extra = {
"nodeType": args.node_type,
"inputColumns": args.input_column,
"responseFormat": resp_format,
}
if args.max_rows is not None:
extra["maxRows"] = str(args.max_rows)
if args.tag_base_id is not None:
extra["tagBaseId"] = str(args.tag_base_id)
if args.prompt:
extra["prompt"] = args.prompt
if args.output_fields:
extra["outputFields"] = args.output_fields
if args.target_language:
extra["targetLanguage"] = args.target_language
with open(args.file, "rb") as f:
files = {"file": (os.path.basename(args.file), f)}
resp = requests.post(
_url(config, "/task/quick-preview"),
headers=_headers(config),
files=files,
data=extra,
)
_handle_response(resp)
def register_task(subparsers):
task_parser = subparsers.add_parser("task", help="任务管理")
task_sub = task_parser.add_subparsers(dest="action")
# create
p = task_sub.add_parser("create", help="创建任务")
p.add_argument("--name", required=True, help="任务名称")
p.add_argument("--description", help="任务描述")
p.add_argument("--flow-config", help="taskFlowConfig JSON 字符串")
p.add_argument("--flow-config-file", help="taskFlowConfig JSON 文件路径")
p.add_argument("--schedule-type", default="MANUAL", help="调度类型(默认 MANUAL)")
# update
p = task_sub.add_parser("update", help="更新任务")
p.add_argument("--id", required=True, type=int, help="任务ID")
p.add_argument("--name", help="新的任务名称")
p.add_argument("--description", help="新的任务描述")
p.add_argument("--flow-config", help="taskFlowConfig JSON 字符串")
p.add_argument("--flow-config-file", help="taskFlowConfig JSON 文件路径")
# delete
p = task_sub.add_parser("delete", help="删除任务")
p.add_argument("--id", required=True, type=int, help="任务ID")
# get
p = task_sub.add_parser("get", help="获取任务详情")
p.add_argument("--id", required=True, type=int, help="任务ID")
# list
p = task_sub.add_parser("list", help="获取任务列表")
p.add_argument("--keyword", help="关键字搜索")
p.add_argument("--creator", help="按创建人过滤")
p.add_argument("--name", help="按名称过滤")
p.add_argument("--page", type=int, default=1, help="页码(默认 1)")
p.add_argument("--page-size", type=int, default=20, help="每页条数(默认 20)")
# backfill
p = task_sub.add_parser("backfill", help="任务回填")
p.add_argument("--bdb-task-id", required=True, type=int, help="BDB 任务 ID")
p.add_argument("--start-time", help="开始时间")
p.add_argument("--end-time", help="结束时间")
# preview
p = task_sub.add_parser("preview", help="数据预览")
p.add_argument("--input-dataset", help="inputDataSet JSON 字符串")
p.add_argument("--input-dataset-file", help="inputDataSet JSON 文件路径")
p.add_argument("--limit", type=int, default=10, help="预览行数(默认 10)")
# result
p = task_sub.add_parser("result", help="查看任务结果")
p.add_argument("--task-id", required=True, type=int, help="任务ID")
p.add_argument("--page-num", type=int, help="页码")
p.add_argument("--page-size", type=int, help="每页条数")
p.add_argument("--partition", help="分区 JSON(如 '{\"p_date\":\"2025-01-01\"}')")
# quick-preview
p = task_sub.add_parser("quick-preview", help="轻量预览(上传文件或传原始数据 + 指定节点即可预览结果)")
p.add_argument("--file", help="CSV 或 Excel 文件路径(与 --raw-data 二选一)")
p.add_argument("--raw-data", help="原始数据 JSON 数组(如 '[\"文本1\",\"文本2\"]',与 --file 二选一)")
p.add_argument("--raw-data-file", help="原始数据 JSON 文件路径(与 --file / --raw-data 二选一)")
p.add_argument("--columns", help="列名 JSON 数组(rawData 为二维数组时需要,如 '[\"评论内容\",\"用户\"]')")
p.add_argument("--node-type", required=True, help="节点类型(如 EMOTION_DETECTION)")
p.add_argument("--input-column", required=True, help="作为待处理文本的列名")
p.add_argument("--max-rows", type=int, help="最大处理行数(默认 10,上限 50)")
p.add_argument("--tag-base-id", type=int, help="标签库 ID(TAG_DETECTION / SUBJECT_DETECTION 需要)")
p.add_argument("--prompt", help="自定义提示词(CUSTOM_APPLICATION 需要)")
p.add_argument("--output-fields", help="输出字段 JSON 数组(CUSTOM_APPLICATION 需要)")
p.add_argument("--target-language", help="翻译目标语言(TRANSLATION 用,默认\"中文\")")
p.add_argument("--response-format", choices=["json", "csv"], default="json",
help="响应格式:json(默认)或 csv(返回文件下载)")
p.add_argument("--output", help="CSV 输出文件路径(默认 quick_preview_result.csv)")
TASK_ACTIONS = {
"create": task_create,
"update": task_update,
"delete": task_delete,
"get": task_get,
"list": task_list,
"backfill": task_backfill,
"preview": task_preview,
"result": task_result,
"quick-preview": task_quick_preview,
}
# ============================== prompt 子命令 ==============================
def prompt_detail(args, config):
"""查询提示词详情"""
_get(config, "/prompt/detail", {"taskId": args.task_id, "nodeId": args.node_id})
def prompt_generate(args, config):
"""生成提示词"""
_post_json(config, "/prompt/generate", {"description": args.description})
def prompt_optimize(args, config):
"""触发提示词优化"""
_post_json(config, "/prompt/optimize", {"taskId": args.task_id})
def prompt_report(args, config):
"""查询优化报告"""
body = {}
if args.task_id:
body["taskId"] = args.task_id
if args.node_id:
body["nodeId"] = args.node_id
if args.prompt_id:
body["promptId"] = args.prompt_id
if args.version:
body["version"] = args.version
_post_json(config, "/prompt/optimize/report", body)
def register_prompt(subparsers):
prompt_parser = subparsers.add_parser("prompt", help="提示词管理")
prompt_sub = prompt_parser.add_subparsers(dest="action")
# detail
p = prompt_sub.add_parser("detail", help="查询提示词详情")
p.add_argument("--task-id", required=True, type=int, help="任务ID")
p.add_argument("--node-id", required=True, help="节点ID")
# generate
p = prompt_sub.add_parser("generate", help="生成提示词")
p.add_argument("--description", required=True, help="提示词描述")
# optimize
p = prompt_sub.add_parser("optimize", help="触发提示词优化")
p.add_argument("--task-id", required=True, type=int, help="任务ID")
# report
p = prompt_sub.add_parser("report", help="查询优化报告")
p.add_argument("--task-id", type=int, help="任务ID")
p.add_argument("--node-id", help="节点ID")
p.add_argument("--prompt-id", type=int, help="提示词ID")
p.add_argument("--version", type=int, help="版本号")
PROMPT_ACTIONS = {
"detail": prompt_detail,
"generate": prompt_generate,
"optimize": prompt_optimize,
"report": prompt_report,
}
# ============================== error-case 子命令 ==============================
def ec_create_text(args, config):
"""上传错题(文本)"""
body = {"taskId": args.task_id, "nodeId": args.node_id, "items": []}
if args.items:
body["items"] = json.loads(args.items)
if args.items_file:
with open(args.items_file) as f:
body["items"] = json.load(f)
_post_json(config, "/error-case/create/text", body)
def ec_create_file(args, config):
"""上传错题(文件)"""
_post_file(config, "/error-case/create/file", args.file, {"taskId": str(args.task_id)})
def ec_list(args, config):
"""查询错题列表"""
params = {"taskId": args.task_id, "page": args.page, "pageSize": args.page_size}
if args.trace_id:
params["traceId"] = args.trace_id
if args.creator:
params["creator"] = args.creator
_get(config, "/error-case/list", params)
def ec_delete(args, config):
"""删除错题"""
ids = [int(x) for x in args.ids.split(",")]
_delete(config, "/error-case/delete", ids)
def ec_template(args, config):
"""下载错题模板"""
resp = requests.get(
_url(config, "/error-case/template"),
headers=_headers(config),
params={"format": args.format},
)
if resp.status_code == 200:
out = args.output or f"error_case_template.{args.format}"
with open(out, "wb") as f:
f.write(resp.content)
print(f"模板已保存到 {out}")
else:
_print_response(resp)
def register_error_case(subparsers):
ec_parser = subparsers.add_parser("error-case", help="错题管理")
ec_sub = ec_parser.add_subparsers(dest="action")
# create-text
p = ec_sub.add_parser("create-text", help="上传错题(文本)")
p.add_argument("--task-id", required=True, type=int, help="任务ID")
p.add_argument("--node-id", required=True, help="节点ID")
p.add_argument("--items", help="错题项 JSON 数组")
p.add_argument("--items-file", help="错题项 JSON 文件路径")
# create-file
p = ec_sub.add_parser("create-file", help="上传错题(文件)")
p.add_argument("--task-id", required=True, type=int, help="任务ID")
p.add_argument("--file", required=True, help="错题文件路径(CSV/Excel)")
# list
p = ec_sub.add_parser("list", help="查询错题列表")
p.add_argument("--task-id", required=True, type=int, help="任务ID")
p.add_argument("--trace-id", help="traceId 过滤")
p.add_argument("--creator", help="创建人过滤")
p.add_argument("--page", type=int, default=1, help="页码")
p.add_argument("--page-size", type=int, default=20, help="每页条数")
# delete
p = ec_sub.add_parser("delete", help="删除错题")
p.add_argument("--ids", required=True, help="错题ID列表,逗号分隔(如 1,2,3)")
# template
p = ec_sub.add_parser("template", help="下载错题模板")
p.add_argument("--format", default="csv", choices=["csv", "xlsx"], help="模板格式")
p.add_argument("--output", help="保存路径")
EC_ACTIONS = {
"create-text": ec_create_text,
"create-file": ec_create_file,
"list": ec_list,
"delete": ec_delete,
"template": ec_template,
}
# ============================== model 子命令 ==============================
def model_list(args, config):
"""查询模型列表"""
params = {}
if args.enabled is not None:
params["enabled"] = str(args.enabled).lower()
if args.model_name:
params["modelName"] = args.model_name
_get(config, "/models", params)
def model_set_node(args, config):
"""修改节点模型配置"""
body = {}
if args.model_id:
body["modelId"] = args.model_id
if args.temperature is not None:
body["overrideTemperature"] = args.temperature
if args.max_token is not None:
body["overrideMaxToken"] = args.max_token
if args.top_p is not None:
body["overrideTopP"] = args.top_p
_put_json(config, f"/task/{args.task_id}/node/{args.node_id}/model", body)
def register_model(subparsers):
model_parser = subparsers.add_parser("model", help="模型管理")
model_sub = model_parser.add_subparsers(dest="action")
# list
p = model_sub.add_parser("list", help="查询可用模型列表")
p.add_argument("--enabled", type=bool, help="是否启用过滤")
p.add_argument("--model-name", help="模型名称过滤")
# set-node
p = model_sub.add_parser("set-node", help="修改节点模型配置")
p.add_argument("--task-id", required=True, type=int, help="任务ID")
p.add_argument("--node-id", required=True, help="节点ID")
p.add_argument("--model-id", type=int, help="模型ID")
p.add_argument("--temperature", type=float, help="温度")
p.add_argument("--max-token", type=int, help="最大 Token 数")
p.add_argument("--top-p", type=float, help="Top P")
MODEL_ACTIONS = {
"list": model_list,
"set-node": model_set_node,
}
# ============================== tag-base 子命令 ==============================
def tb_create(args, config):
"""创建标签库"""
body = {"name": args.name, "type": args.type}
if args.description:
body["description"] = args.description
if args.doc_info:
body["docInfo"] = json.loads(args.doc_info)
_post_json(config, "/tag-base/create", body)
def tb_update(args, config):
"""编辑标签库"""
body = {"id": args.id}
if args.name:
body["name"] = args.name
if args.description:
body["description"] = args.description
if args.doc_info:
body["docInfo"] = json.loads(args.doc_info)
_post_json(config, "/tag-base/update", body)
def tb_upload(args, config):
"""上传标签文件"""
extra = {}
if args.provider:
extra["provider"] = args.provider
_post_file(config, "/tag-base/upload-file", args.file, extra)
def tb_get(args, config):
"""标签库详情"""
_get(config, f"/tag-base/{args.id}")
def tb_list(args, config):
"""标签库列表"""
params = {"pageNum": args.page_num, "pageSize": args.page_size}
if args.name:
params["name"] = args.name
if args.type:
params["type"] = args.type
if args.creator:
params["creator"] = args.creator
_get(config, "/tag-base/list", params)
def tb_retrieval_test(args, config):
"""召回测试"""
body = {"id": args.id, "question": args.question}
if args.top_k:
body["topK"] = args.top_k
_post_json(config, "/tag-base/retrieval/test", body)
def tb_delete(args, config):
"""删除标签库"""
_delete(config, f"/tag-base/{args.id}")
def register_tag_base(subparsers):
tb_parser = subparsers.add_parser("tag-base", help="标签库管理")
tb_sub = tb_parser.add_subparsers(dest="action")
# create
p = tb_sub.add_parser("create", help="创建标签库")
p.add_argument("--name", required=True, help="标签库名称")
p.add_argument("--type", required=True, choices=["tag", "subject"], help="类型")
p.add_argument("--description", help="描述")
p.add_argument("--doc-info", help="文档信息 JSON(如 '[{\"fileId\":\"xxx\",\"fileName\":\"a.csv\"}]')")
# update
p = tb_sub.add_parser("update", help="编辑标签库")
p.add_argument("--id", required=True, type=int, help="标签库ID")
p.add_argument("--name", help="新名称")
p.add_argument("--description", help="新描述")
p.add_argument("--doc-info", help="新文档信息 JSON")
# upload
p = tb_sub.add_parser("upload", help="上传标签文件")
p.add_argument("--file", required=True, help="文件路径(CSV)")
p.add_argument("--provider", help="知识库提供者")
# get
p = tb_sub.add_parser("get", help="标签库详情")
p.add_argument("--id", required=True, type=int, help="标签库ID")
# list
p = tb_sub.add_parser("list", help="标签库列表")
p.add_argument("--name", help="名称过滤")
p.add_argument("--type", choices=["tag", "subject"], help="类型过滤")
p.add_argument("--creator", help="创建人过滤")
p.add_argument("--page-num", type=int, default=1, help="页码")
p.add_argument("--page-size", type=int, default=10, help="每页条数")
# retrieval-test
p = tb_sub.add_parser("retrieval-test", help="召回测试")
p.add_argument("--id", required=True, type=int, help="标签库ID")
p.add_argument("--question", required=True, help="测试文本")
p.add_argument("--top-k", type=int, help="返回条数上限")
# delete
p = tb_sub.add_parser("delete", help="删除标签库")
p.add_argument("--id", required=True, type=int, help="标签库ID")
TB_ACTIONS = {
"create": tb_create,
"update": tb_update,
"upload": tb_upload,
"get": tb_get,
"list": tb_list,
"retrieval-test": tb_retrieval_test,
"delete": tb_delete,
}
# ============================== 主入口 ==============================
COMMAND_MAP = {
"task": TASK_ACTIONS,
"prompt": PROMPT_ACTIONS,
"error-case": EC_ACTIONS,
"model": MODEL_ACTIONS,
"tag-base": TB_ACTIONS,
}
def main():
parser = argparse.ArgumentParser(
description="Seederive CLI — 统一的 API 调用工具",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__,
)
parser.add_argument("--base-url", help="API 基础地址(也可设 SEEDERIVE_BASE_URL 环境变量)")
subparsers = parser.add_subparsers(dest="command")
register_task(subparsers)
register_prompt(subparsers)
register_error_case(subparsers)
register_model(subparsers)
register_tag_base(subparsers)
args = parser.parse_args()
if not args.command:
parser.print_help()
sys.exit(0)
actions = COMMAND_MAP.get(args.command)
if not actions:
parser.print_help()
sys.exit(1)
if not args.action:
# 打印子命令帮助
parser.parse_args([args.command, "-h"])
sys.exit(0)
handler = actions.get(args.action)
if not handler:
print(f"错误:未知操作 '{args.command} {args.action}'", file=sys.stderr)
sys.exit(1)
config = get_config(args)
handler(args, config)
if __name__ == "__main__":
main()