Skip to main content

6-工作流与触发器

概览

目录:doc/product/financial/flowflow_triggerflow_variable

该模块基于 ai-link 工作流引擎,将「发票文件导入 → OCR 解析 → LLM 结构化 → 风险评估」串成可视化可编排的流程,并通过触发器与变量实现与业务系统的解耦。

  • 工作流定义:doc/product/financial/flow/*.json
  • 工作流触发器:doc/product/financial/flow_trigger/*.json
  • 工作流环境变量:doc/product/financial/flow_variable/*.json

下面选取核心 OCR 工作流 FinInvoiceOcr 的完整 JSON 作为示例,其余工作流可在目录中按需查看与调整。

工作流:FinInvoiceOcr(发票 OCR 与结构化)

文件:flow/FinInvoiceOcr.json

功能要点:

  • 接收来自 fin_invoice_files 的变更事件
  • 通过文件存储 key 解析出对象存储内部访问地址
  • 调用本地 OCR 服务进行票面识别
  • 调用本地 LLM 服务对 OCR 结果做结构化提取
  • 将结果写回发票文件记录,为后续建模与风控准备数据

完整 JSON:

{
"id": "2def135a-9d05-4a80-9c49-a20c9b046523",
"apiKey": "FinInvoiceOcr",
"name": "FinInvoiceOcr",
"description": "",
"version": "1",
"nodes": [
{
"type": "start",
"id": "start_0",
"name": null,
"meta": {
"position": {
"x": 180.0,
"y": 361.0
}
},
"data": {
"title": "Start",
"outputs": {
"type": "object",
"properties": {
"payload": {
"type": "object",
"default": "Hello Flow.",
"properties": {},
"required": []
}
},
"required": [
"payload"
]
}
}
},
{
"type": "end",
"id": "end_0",
"name": null,
"meta": {
"position": {
"x": 6620.0,
"y": 361.0
}
},
"data": {
"title": "End",
"inputsValues": {
"success": {
"type": "constant",
"content": true,
"schema": {
"type": "boolean"
}
},
"query": {
"type": "ref",
"content": [
"start_0",
"payload"
]
}
},
"inputs": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"query": {
"type": "object",
"required": [],
"properties": {}
}
}
}
}
},
{
"type": "code",
"id": "code_e7J71",
"name": null,
"meta": {
"position": {
"x": 640.0,
"y": 331.0
}
},
"data": {
"title": "Code_1",
"inputsValues": {
"input": {
"type": "ref",
"content": [
"start_0",
"payload"
],
"extra": {
"index": 0
}
}
},
"script": {
"language": "javascript",
"content": "function main(params) {\n var payload = params.input || {};\n var data = payload.data || payload || {};\n\n var after = data.after || {};\n if (Array.isArray(after) && after.length > 0) {\n after = after[0] || {};\n }\n var before = data.before || {};\n if (Array.isArray(before) && before.length > 0) {\n before = before[0] || {};\n }\n\n var records = [];\n if (after && typeof after === \"object\") {\n records.push(after);\n }\n\n var ocrBefore = before.ocr_result;\n var llmBefore = before.llm_result;\n\n var beforeOcrJson = null;\n if (ocrBefore != null) {\n if (typeof ocrBefore === \"string\") {\n beforeOcrJson = ocrBefore;\n } else {\n try {\n beforeOcrJson = JSON.stringify(ocrBefore);\n } catch (e) {\n beforeOcrJson = null;\n }\n }\n }\n\n var beforeLlmJson = null;\n if (llmBefore != null) {\n if (typeof llmBefore === \"string\") {\n beforeLlmJson = llmBefore;\n } else {\n try {\n beforeLlmJson = JSON.stringify(llmBefore);\n } catch (e) {\n beforeLlmJson = null;\n }\n }\n }\n\n var hasOcrBefore = !!beforeOcrJson;\n var hasLlmBefore = !!beforeLlmJson;\n\n return {\n records: records,\n before_ocr_result: beforeOcrJson,\n before_llm_result: beforeLlmJson,\n has_ocr_before: hasOcrBefore,\n has_llm_before: hasLlmBefore\n };\n}"
},
"outputs": {
"type": "object",
"properties": {
"records": {
"type": "array",
"extra": {
"index": 1
},
"items": {
"type": "object"
}
},
"before_ocr_result": {
"type": "string"
},
"before_llm_result": {
"type": "string"
},
"has_ocr_before": {
"type": "boolean"
},
"has_llm_before": {
"type": "boolean"
}
},
"required": []
},
"inputs": {
"type": "object",
"properties": {
"input": {
"type": "object",
"required": [],
"properties": {}
}
}
}
}
},
{
"type": "code",
"id": "code_Upo7W",
"name": null,
"meta": {
"position": {
"x": 2020.0,
"y": 343.5
}
},
"data": {
"title": "获取文件key",
"inputsValues": {
"record": {
"type": "ref",
"content": [
"code_e7J71",
"records"
],
"extra": {
"index": 0
}
}
},
"script": {
"language": "javascript",
"content": "function main(params) {\n var record = params.record[0] || {};\n var key = record.storage_key || record.storageKey || null;\n var keys = [];\n keys.push(key);\n var fileId = record.id || record.file_id || null;\n var hash = record.sha256 || record.sha_256 || record.md5 || null;\n var id = record.id || null;\n return {\n id: id,\n key: key,\n keys: keys,\n file_id: fileId,\n file_hash: hash\n };\n}"
},
"outputs": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"file_id": {
"type": "string"
},
"file_hash": {
"type": "string"
},
"id": {
"type": "string",
"extra": {
"index": 4
}
},
"keys": {
"type": "array",
"extra": {
"index": 5
},
"items": {
"type": "string"
}
}
},
"required": []
},
"inputs": {
"type": "object",
"properties": {
"record": {
"type": "array",
"items": {
"type": "object",
"required": [],
"properties": {}
}
}
}
}
}
},
{
"type": "http",
"id": "http_GO4Dv",
"name": null,
"meta": {
"position": {
"x": 3400.0,
"y": 169.5
}
},
"data": {
"title": "本地ocr",
"api": {
"method": "POST",
"url": {
"type": "template",
"content": "{{global.ocrServiceBaseUrl}}/parse"
}
},
"body": {
"bodyType": "JSON",
"json": {
"type": "template",
"content": "{\"url\": \"{{code_bSqfI.url}}\"}"
}
},
"params": {
"type": "object",
"properties": {}
},
"outputs": {
"type": "object",
"properties": {
"body": {
"type": "string"
},
"headers": {
"type": "object"
},
"statusCode": {
"type": "integer"
}
}
},
"timeout": {
"timeout": 300000,
"retryTimes": 1
},
"paramsValues": {},
"headersValues": {
"Content-Type": {
"type": "constant",
"content": "application/json",
"schema": {
"type": "string"
},
"extra": {
"index": 0
}
}
},
"headers": {
"type": "object",
"properties": {
"Content-Type": {
"type": "string"
}
}
}
}
},
{
"type": "code",
"id": "code_bSqfI",
"name": null,
"meta": {
"position": {
"x": 2940.0,
"y": 343.5
}
},
"data": {
"title": "截取url",
"inputsValues": {
"input": {
"type": "ref",
"content": [
"http_YhPHU",
"body"
],
"extra": {
"index": 0
}
}
},
"script": {
"language": "javascript",
"content": "// Here, you can retrieve input variables from the node using 'params' and output results using 'ret'.\n// 'params' has been correctly injected into the environment.\n// Here's an example of getting the value of the parameter named 'input' from the node input:\n// const input = params.input;\n// Here's an example of outputting a 'ret' object containing multiple data types:\n// const ret = { \"name\": 'Xiaoming', \"hobbies\": [\"Reading\", \"Traveling\"] };\n\nfunction main(params) {\n let input = params.input;\n let output = JSON.parse(input);\n let item = output && output.data && output.data.items && output.data.items[0];\n let url = item && (item.internalUrl || item.url || \"\");\n const ret = {\n url: url\n };\n\n return ret;\n}"
},
"outputs": {
"type": "object",
"properties": {
"url": {
"type": "string"
}
},
"required": []
},
"inputs": {
"type": "object",
"properties": {
"input": {
"type": "string"
}
}
}
}
}
]
}

工作流触发器与变量

  • 触发器示例:flow_trigger/FinInvoiceOcr_trigger.json
  • 变量示例:flow_variable/FinInvoiceOcr.json

其中触发器将 FinInvoiceOcr 工作流暴露为带签名的 Webhook 接口,变量文件则统一配置 OCR 服务、数据服务、鉴权服务与 LLM 服务的基础地址,便于在不同环境之间切换。