取数与流水线(pipeline)
读者:动作不仅写本地库,还要从工作区 数据连接 拉数、再组装结果的人。
来源 sdk/appsdk/取数与流水线.md
读者:动作不仅写本地库,还要从工作区 数据连接 拉数、再组装结果的人。 关联:动作与handlers · 应用包结构 · 范例技能包 backend/internal/skillfromchat/bundled/employee-profile/
1. 什么时候需要取数流水线?
| 场景 | 做法 |
|---|---|
| 只在本应用 SQLite 里增删查 | 内置 CRUD,不要 pipeline |
| 点一次按钮,按固定步骤查多个预定义查询,再拼报告 | references/pipeline.json + 编排脚本 |
| 步骤经常变、要给助手/人可读 | 把步骤写在 pipeline,而不是硬编码在十几处 Python |
员工画像是标准范例:主档 → 联系方式 → 组织 → 合同 → 薪资 → 证件照 → 组装 HTML。
2. 推荐代码结构
logic/
handlers.py # 只分发动作;成功/失败写历史
generate_xxx.py # 读 pipeline、逐步 query_run、调组装、save_upload
scripts/
assemble_xxx.py # 纯函数:数据 + 模板 + 映射 → HTML 字符串
assets/
xxx.html # 结果版式
references/
pipeline.json # 步骤声明
field-mapping.json # 字段如何填进模板(可选)
职责划分:
- pipeline:声明「查什么、参数从哪来、结果存成哪一段」
- generate_*:执行与容错(可选步骤、备用查询、warnings)
- assemble_*:展示,不直接打数据连接
- handlers:产品动作边界与登记
3. pipeline.json 核心形状
{
"version": 1,
"id": "my-report-v1",
"display_name": "我的报表",
"input": {
"emp_name": { "type": "string", "required": true, "label": "员工姓名" }
},
"steps": [
{
"id": "resolve_employee",
"label": "解析员工主档",
"query_id": "employee_by_name",
"params": { "empName": "{{input.emp_name}}" },
"save_as": "employee",
"optional": false
},
{
"id": "fetch_org",
"label": "组织岗位",
"query_id": "employee_org_by_emp_id",
"params": { "empId": "{{employee.rows[0].empId}}" },
"save_as": "org",
"optional": true
}
],
"assemble": {
"script_path": "scripts/assemble_xxx.py",
"template_path": "assets/xxx.html",
"field_mapping_path": "references/field-mapping.json"
}
}
3.1 步骤字段说明
| 字段 | 含义 |
|---|---|
query_id | 数据连接里的 预定义查询 id(必须已配置) |
params | 查询参数;支持 {{input.xxx}}、{{employee.rows[0].empId}} 这类占位 |
save_as | 本步结果在后续绑定中的名字 |
optional | true:失败或空结果记入 warnings,不整单失败 |
fallback_step | 主查询无数据时的备用 query_id + params(如证件照) |
应用内编排脚本会读取这些字段;
tool/action等键在技能侧对话执行时更有意义,应用内通常直接platform.query_run。
3.2 与数据连接的关系
- 工作区需已配置数据连接,且查询 id 与 pipeline 一致。
- 用户须有权执行这些查询。
- 可用
platform.query_list()先看当前有哪些 id,避免硬失败。
4. 编排脚本在做什么(逻辑骨架)
伪代码(与员工画像 generate_profile.py 同类):
run_generate(params, ctx):
校验必填输入
platform = ctx["platform"]
pipeline = 读取 references/pipeline.json
bindings = { input: params }
warnings = []
for step in pipeline.steps:
解析 params 模板
block = platform.query_run(query_id, params)
若空且有 fallback → 再查一次
若仍失败且 optional → warnings.append(...); block = 空
若失败且非 optional → return 错误
bindings[save_as] = block
html = assemble(bindings, template, field_mapping)
up = platform.save_upload(文件名, html, "text/html; charset=utf-8")
return { ok, upload_id, warnings, ... }
消歧:主档多行时返回 code: "disambiguate" + candidates,让用户补填员工 ID 后再提交(界面已支持展示这类错误)。
5. 组装与字段映射
- 模板:
assets/*.html,可用占位符或由脚本替换。 - field-mapping.json:描述「报告某一栏来自哪次查询的哪个字段」、空值、打码等(员工画像有完整样例)。
- 按图做结果页:见 视图与布局 §4 路径 A。
组装脚本应尽量 无平台桥调用,便于单测与在技能侧复用。
6. handlers 如何挂上流水线
from generate_xxx import run_generate
def handle(action, params, ctx):
if action == "report.generate":
result = run_generate(params, ctx)
# 可选:把 status / upload_id 写入 app.db 历史表
return result
...
界面:action_form → report.generate;历史:list + 实体含 upload_id/status。
7. 必须具备的清单(取数类应用)
- [ ] 工作区数据连接可用,pipeline 中每个非 optional 的
query_id存在 - [ ]
logic/handlers.py动作 id 与app.json一致 - [ ] 编排脚本使用
ctx["platform"],无禁用网络库 - [ ]
references/pipeline.json(或脚本内等效步骤)与真实查询参数名一致 - [ ] 需要预览时:
save_upload+ 返回upload_id - [ ] 部分失败时返回
warnings,避免静默丢字段
8. 应用包面板(浏览与白名单编辑)
打开应用后可用「应用包」查看包内文件树并预览内容。
| 能力 | 说明 |
|---|---|
| 浏览 | app.json、manifest.json、以及 logic/ scripts/ references/ assets/ skills/ 下的文本文件 |
| 编辑并保存 | 仅白名单路径(与应用开发助手可写范围一致,如 logic/*.py、scripts/*.py、references/*.{json,md}、assets/profile.html、skills/SKILL.md) |
| 只读 | app.json / manifest.json 等:界面描述请用应用开发助手或对照规范另改 |
HTTP(成员):
GET .../apps/{appId}/package/filesGET .../apps/{appId}/package/file?rel_path=PUT .../apps/{appId}/package/filebody{ rel_path, content }
9. 不要把这些放进 pipeline
| 内容 | 应放在 |
|---|---|
| 按钮文案、列表列 | app.json |
| 权限模型 | 数据连接 ACL / 工作区成员 |
| 任意 SQL | 预定义查询(管理端或对话写入),脚本只 query_run |
| 像素级操作台 UI | 平台插件,不是 pipeline |