生成 flow.json
根据用户的自然语言需求,从本技能的组件说明里选择组件,组成一条新流程,输出可导入的 flow.json。流程运行时基于 Langflow 1.3.5(langflow-base 0.2.0)。
「AI录单」只是一种常见结构:对话输入、条件分支、语言模型、JSON 清洁器、对话输出。用户要的是别的流程时,按需求重新选组件和连线,不要套那张图。
外部没有平台源码,也没有组件文档。组件说明按文件拆开,先查索引,再打开用到的那几份,不要通读。
- 读 references/components/index.md,按「类型」列选出本次要用的组件。
- 只打开这些组件对应的说明文件,确认参数和输出。
- 连线前只在 references/wiring.json 里查这几个类型的输出名和输入字段名。
索引里没有的能力,用 CustomComponent。写代码前先读 references/custom-script-rules.md,脚本在服务端执行,必须遵守其中的安全和性能规则。然后再读 custom_component-OverviewAndBasicComponentStructure.md;输入输出类型对不上时再读 custom_component-InputTypesGuide.md 和 custom_component-OutputTypesGuide.md。自定义代码的导入使用文档中的 q7link。
自定义组件里的平台服务先查 references/services/index.md,再只打开本次要用的服务说明。当前只允许调用 tenant 服务,且只能调用 get_user_info、get_attachment_download_url、get_object_types、get_object_fields。索引未列出的服务和方法不要写进代码。
流程要用到当前用户、当前单据、业务日期等运行时信息时,先读 references/context.md。系统上下文变量名都以 context. 开头;自定义变量没有这个前缀。自定义组件里按该文件的 graph.flow_vars 方式读取,提示词里写成 {context.objectId} 或 {fieldSchema}。
用户要做单据预审,或说明是系统固定调用的流程时,先读 references/scenarios/index.md,再只打开对应场景。单据预审的入参和 JSON 出参以该场景说明为准,不要改成别的字段。预审调用的 message 是空的,输入只有 context.objectId、context.objectType、inputPrompt、outputPrompt 这 4 个变量。不要从对话输入里拿单据或检查内容,要用这 4 个变量自己查询并组装参数。
对象类型、字段 apiName、枚举值和引用对象不在技能里。客户端已经安装并可直接执行 qiqi。涉及业务对象时,先用它读取当前企业的元数据,再写提示词、查询条件和字段映射。不要编造对象名或字段名,也不要安排安装或登录。
用 qiqi 取元数据
自动化调用都加 --format json。参数不确定时先看该子命令的 --help,以当前命令帮助为准。
- 对象类型不清楚:
qiqi objects,用业务关键词在返回结果里选定apiName。 - 字段不清楚:
qiqi describe fields <ObjectType> --format json。提示词、fieldSchema、EQL、ObjectQuery的query_fields/criteria_str只使用这里返回的字段名。 - 需要看列表默认字段和少量样例:
qiqi describe list <ObjectType> --format json。样例不是全集,也不能据此推断必填。 - 枚举或引用要写成查询条件:
qiqi query values <ObjectType.field> --format json。不要把中文标题当成枚举值或记录 ID。 - 身份或企业不对导致查询失败时,看
qiqi auth status --format json,然后停下来说明,不要改去别的企业。
查到的字段写进提示词时同时保留标题和 apiName。运行时若调用方还会传入 {fieldSchema},提示词仍以该变量为准,qiqi 的结果用来核对对象是否存在、字段是否叫这个名字。
步骤
- 用自己的话复述要自动化的事:输入是什么,经过哪些处理,最终要返回什么。涉及单据或档案时,先按上一节用
qiqi取到对象和字段。 - 从索引里为每一步选一个组件,只打开对应说明,并写出为什么用它。
- 画出数据从哪个输出进哪个输入。输出类型必须落在目标输入的
input_types里,类型以wiring.json为准。 - 一个流程只有一个
ChatInput。要给调用方看的结果必须接到ChatOutput。 - 模型要输出 JSON 时,在
ChatOutput前接JSONCleaner,并把它的「验证 JSON」保持关闭。 - 每个 plan 必须带
note:用 Markdown 说明整体思路和实现方式。脚本会把它做成画布上的注释节点,不参与连线。内容不超过 2500 个字符,写清输入、每一步用什么组件、数据怎么传、最终输出什么。 - 写成
flow.plan.json,执行:
python3 skills/ai-record-flow/scripts/build_flow.py \
--plan flow.plan.json \
--output flow.json
当前目录已是技能目录时,脚本路径用 scripts/build_flow.py。
- 脚本成功后把
flow.json交给用户。导入入口是 AI 平台应用列表的导入。文件是单个 flow 对象。
plan 格式
{
"name": "合同识别",
"description": "从附件提取合同字段并返回 JSON。",
"file_types": [".jpg", ".jpeg", ".png", ".pdf"],
"max_file_size": 10,
"max_file_count": 3,
"note": "# 合同识别\n\n## 思路\n从对话附件读取合同,按字段定义提取结构化结果。\n\n## 实现\n1. ChatInput 接收附件和补充说明。\n2. 提示词带上 {fieldSchema},交给 LLMModel 提取。\n3. JSONCleaner 整理模型输出,ChatOutput 返回 JSON。",
"support_vars": [
{"name": "fieldSchema", "type": "json", "title": "字段定义", "defaultValue": null}
],
"nodes": [
{"id": "in", "type": "ChatInput"},
{"id": "prompt", "type": "Prompt", "values": {"template": "按 {fieldSchema} 提取,只输出 JSON。"}},
{"id": "llm", "type": "LLMModel", "values": {"temperature": 0.2, "stream": false}},
{"id": "clean", "type": "JSONCleaner", "values": {"validate_json": false}},
{"id": "out", "type": "ChatOutput"}
],
"edges": [
{"source": "prompt", "source_output": "prompt", "target": "llm", "target_field": "system_message"},
{"source": "in", "source_output": "message", "target": "llm", "target_field": "input_value"},
{"source": "llm", "source_output": "text_output", "target": "clean", "target_field": "json_str"},
{"source": "clean", "source_output": "output", "target": "out", "target_field": "input_value"}
]
}
含 {变量} 的提示词不要用 Prompt。Prompt 会把 {context.objectId} 拆成非法变量,检查更新直接失败。这类文本放在 MessageTextInput 上,脚本会把带变量的 Prompt 改成使用 MessageTextInput 的自定义组件,输出名仍是 prompt。没有变量的固定文本才可以用 Prompt。
自定义组件:
{
"id": "map",
"type": "CustomComponent",
"display_name": "整理字段",
"code": "from q7link.custom import Component\nfrom q7link.io import MessageTextInput, Output\nfrom q7link.schema.message import Message\n\nclass CustomComponent(Component):\n display_name = \"整理字段\"\n name = \"CustomComponent\"\n inputs = [MessageTextInput(name=\"input_value\", display_name=\"输入\")]\n outputs = [Output(display_name=\"输出\", name=\"output\", method=\"build_output\")]\n def build_output(self) -> Message:\n return Message(text=self.input_value)\n",
"outputs": {"output": ["Message"]},
"inputs": {"input_value": {"type": "str", "input_types": ["Message"]}}
}
提示词里的系统上下文用 {context.变量名},自定义变量用 {变量名},例如 {context.objectId}、{fieldSchema}。自定义组件代码里的取值方式见 references/context.md。不要把租户里的模型 ID、API Key 写进 plan。
导入后
用户需要在「语言模型」或「Agent」上选择本租户模型。依赖第三方密钥的节点留空,由用户在导入后填写。