mcpField
Z
zion-baas

zion-baas

github.com/functorz-tech开发043 次安装20 周前更新
代码仓库

关于 zion-baas

zion-baas 是面向使用 Zion.app 构建前端应用的技能,涵盖统一 GraphQL API 的认证、项目元数据和运行时操作。它还提供脚本来查询、订阅并检查生成的数据库模式。

主要功能

  1. 01用户名或开发者令牌认证
  2. 02检索项目与 GraphQL 模式
  3. 03命令行执行查询和变更
  4. 04通过 WebSocket 监听订阅
  5. 05记录项目凭据与用户令牌

适用场景

  1. 01为 Zion.app 前端配置认证
  2. 02查找项目并检查数据模型
  3. 03从命令行测试查询或变更
  4. 04接收 GraphQL 订阅事件

MCP 相关

文档提到使用 zion MCP server 检查模式;未说明该技能自身会向 MCP 客户端提供哪些能力。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/functorz-tech/zion-baas-skill --skill zion-baas

安装后在 Claude Code 中输入/zion-baas确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

.DS_Store
README.md
scripts/
meta.ts
fetchRuntimeToken.ts
authenticate.ts
authenticateEmail.ts
package.json
subscribe.ts
gql.ts
tsconfig.json

详细介绍

Zion.app 无头 BaaS 技能

概述

本技能介绍如何使用 Zion.app 作为无头后端即服务(BaaS)来构建前端应用。Zion 通过一个统一的 GraphQL API 暴露所有后端交互(数据库、actionflow、第三方 API 和 AI Agent)。

  • HTTP URL:https://zion-app.functorz.com/zero/{projectExId}/api/graphql-v2
  • WebSocket URL:wss://zion-app.functorz.com/zero/{projectExId}/api/graphql-subscription

令牌获取与身份验证(重要)

如需与经过身份验证的端点交互,必须先通过登录或注册获取 JWT 令牌。未经过身份验证的请求会被分配匿名用户角色。JWT 可以通过两种方式获取:一种是通过用户名和密码登录;另一种是查询 Meta API 并获取运行时后端令牌。FETCH_DATA_VISUALIZER 返回的令牌就是运行时后端令牌。

1. 用户名注册与登录

你应该向用户询问用户名和密码。

mutation AuthenticateWithUsername($username: String!, $password: String!, $register: Boolean!) {
  authenticateWithUsername(username: $username, password: $password, register: $register) {
    account { id, permissionRoles }
    jwt { token }
  }
}

注:两个 mutation 都会返回 FZ_Account,这是完整 account 类型的子集。它仅包含 email、id、permissionRoles、phoneNumber、profileImageUrl、roles 和 username。

使用 Zion.app 进行开发者身份验证(Meta API)

如果需要直接与 Zion 平台(Meta API)交互,以获取项目架构、列出项目,或以管理员身份向运行时后端进行身份验证,请按以下步骤操作:

1. 获取开发者 JWT 令牌

可以通过两种方式获取开发者令牌:OAuth 流程或邮箱/密码登录。

选项 A:OAuth 流程

设置一个本地 HTTP 服务器来接收 OAuth 回调。在浏览器中打开 Zion 身份验证端点(https://auth.functorz.com/login),然后等待 token 参数。

你可以运行随附的身份验证脚本:

cd ~/.agents/skills/zion-baas-skill/scripts
npm run auth
选项 B:邮箱/密码登录

你可以使用 Meta API 直接通过邮箱和密码登录:

cd ~/.agents/skills/zion-baas-skill/scripts
npm run auth:email <email> <password>
2. 查询 Meta API 并获取运行时后端令牌

使用开发者 JWT 令牌作为 Bearer 令牌访问 Meta API(https://zionbackend.functorz.com/api/graphql),以获取架构或数据可视化令牌。数据可视化令牌授予对项目运行时后端(zeroUrl)的管理员访问权限。

你可以运行随附的脚本来获取令牌。该脚本要求开发者令牌已存在于 .zion/credentials.yaml 中。

cd ~/.agents/skills/zion-baas-skill/scripts
npm run fetch-token -- <projectExId>

你也可以使用随附的 meta 脚本通过 Meta API 搜索项目或获取其架构:

cd ~/.agents/skills/zion-baas-skill/scripts
# Search projects (returns project names and exIds)
npm run meta -- search-projects "optional search term"

# Fetch project schema (returns data models, actionflows, apis)
npm run meta -- fetch-schema <projectExId>

凭据管理与状态持久化

所有凭据和项目状态都必须以 YAML 格式持久化存储在用户项目根目录下的 .zion 目录中,通常存放在名为 .zion/credentials.yaml 的文件中。

必需的 YAML 格式

凭据文件必须遵循以下结构:

# .zion/credentials.yaml
developer_token:
  token: "<your_developer_jwt_token>" # Used to communicate with zionbackend.functorz.com
  expiry: "<timestamp_or_date_of_expiry>"

project:
  exId: "<project_ex_id>"
  name: "<project_name>"
  admin_token:
    token: "<runtime_backend_admin_token>"
    expiry: "<timestamp_or_date_of_expiry>"
  other_users:
    - user_id: "<user_id>"
      user_tags: 
        - "<notable_information_about_the_user_1>"
      token:
        token: "<user_jwt_token>"
        expiry: "<timestamp_or_date_of_expiry>"
  • developer_token:通过 OAuth 流程获取的 JWT 令牌,用于访问 Meta API(zionbackend.functorz.com)。
  • project:包含核心项目标识符(exId、name)、运行时后端的 admin_token(数据可视化令牌)以及 other_users。
  • other_users:经过身份验证的用户列表(例如测试账号),包含其 user_id、用于识别用途的 user_tags,以及其 token。
  • expiry:所有存储的令牌都必须包含相应的过期时间。

通过 CLI 执行 GraphQL 查询与订阅

你可以使用随附的脚本,从命令行快速测试 GraphQL 查询、mutation 和订阅,无需编写前端样板代码。这些脚本会自动从项目的 .zion/credentials.yaml 中读取正确的令牌。

1. 执行查询或 mutation

将 GraphQL 查询或 mutation 作为字符串传入。

cd ~/.agents/skills/zion-baas-skill/scripts
npm run gql -- <projectExId> <role> '<query_string>' '<optional_variables_json>'
  • <role>:可以是 admin(使用数据可视化令牌)、anonymous(不使用令牌),或特定的 user_id(从 .zion/credentials.yaml 的 other_users 中获取令牌)。

示例:

npm run gql -- myProjectEx123 admin 'query GetProject($id: Int!) { project(id: $id) { name } }' '{"id": 1}'
2. 监听订阅

将 GraphQL 订阅作为字符串传入。

cd ~/.agents/skills/zion-baas-skill/scripts
npm run subscribe -- <projectExId> <role> '<query_string>' '<optional_variables_json>'

脚本将建立 WebSocket 连接,并在事件到达时持续打印,直到你将其终止(Ctrl+C)。

数据库与 GraphQL 架构规则

  • 在做出假设之前,使用 zion MCP server 或 webfetch/curl 检查架构。

GraphQL 架构由 PostgreSQL 数据模型直接自动生成。

关键兜底规则: 如果你对确切结构、可用字段、Enum 值(如 [Enum:ROUNDING_MODE])或端点的具体参数有任何疑问,必须通过提供的端点 URL,使用 webfetch/curl 工具检查实时 GraphQL 架构,再做出假设。

1. 命名约定与根操作

每个表都会生成对应的根查询、变更和订阅字段。对于名为 [table] 的表:

查询
  • [table]:获取列表(支持 where、order_by、distinct_on、limit、offset)。
  • [table]_by_pk:通过主键(id)获取单条记录。
  • [table]_aggregate:聚合查询(count、sum、avg、min、max)。
  • [table]_group_by:根据指定字段对记录分组。
  • fz_[table]_by_[column]:如果表包含 geo_point 列,则自动生成空间邻近搜索。
  • Relay API:如果已配置,则生成基于游标的分页查询,返回 Connection_[table] 对象。
变更
  • insert_[table]、insert_[table]_one:创建记录。支持嵌套插入和 on_conflict(需要 constraint 和 update_columns)。
  • update_[table]、update_[table]_by_pk:修改记录。使用 _set(替换)和 _inc(递增数值)。批量更新必须提供 where(!)。(注意:不支持 Hasura JSONB 更新操作符,如 _append/_delete_key。)
  • delete_[table]、delete_[table]_by_pk:删除记录。批量删除必须提供 where(!)。
  • export_[table]:触发该表的数据导出任务。
订阅
  • [table]、[table]_by_pk、[table]_aggregate:实时查询,与对应的标准查询一致。
2. 列类型与数据映射
基本类型
  • text → String、integer → Int、bigint、bigserial → bigint、float8 → Float8、decimal → Decimal、boolean → Boolean、jsonb → jsonb。
  • 时间/日期:timestamptz、timetz、date、interval。
  • 地理类型:geo_point → geography。
复合(媒体)类型

媒体列(image、file、video)在结构上属于 1:N 关系,但作为字段使用。

  • 单个媒体:存储为 [column]_id(例如 cover_image_id),引用系统资源表(FZ_Image、FZ_File、FZ_Video)。
  • 媒体列表:image_list、video_list、file_list 等类型映射为 GraphQL 数组,并存储为 [column]_ids(例如 gallery_images_ids)。
系统管理的列

id、created_at、updated_at 为只读列,由系统自动管理。它们不能用于变更输入(_set、_inc、插入输入)。

3. 关系

关系由外键定义,并决定 GraphQL 嵌套字段:

  • 1:1(一对一):返回单个嵌套对象(例如 meta: post_meta)。
  • 1:N(一对多):返回数组(例如 post_tags: [post_tag])和聚合对象(例如 post_tags_aggregate)。
4. 筛选(where 子句)

筛选依赖 [table]_bool_exp。

逻辑运算符
  • _and: [bool_exp]、_or: [bool_exp]、_not: bool_exp
关系筛选器

直接使用关系字段名称导航关系。其值是一个嵌套的 [related_table]_bool_exp 对象。

  • 单对象关系(1:1、N:1):根据单个相关记录的字段筛选父记录。 (例如,查找作者姓名为 "John" 的帖子:author: { name: { _eq: ... } })
  • 多对象关系(1:N、N:M):采用 EXISTS 语义。如果数组中任意相关记录符合嵌套条件,则返回父记录。 (例如,查找至少有一个名为 "Tech" 的标签的帖子:post_tags: { tag: { name: { _eq: ... } } })
比较谓词(严格模式)

Zion 使用严格的运算符优先模式。谓词必须以运算符开头。如果是通用运算符,则操作数包装类型必须与最终求值类型匹配,不一定要与列类型匹配。

结构:

{
  "_operator": {
    "operand_type": {
      "left_operand": { ... },
      "right_operand": { ... }
    }
  }
}
  • 运算符:
    • 比较(通用): _eq、_neq、_gt、_lt、_gte、_lte
    • 数组(通用): _in、_nin
    • 空值(通用,一元): _is_null、_is_not_null
    • 文本(字符串模式): _like、_nlike、_ilike、_nilike、_similar、_nsimilar
    • JSONB: _contains、_contained_in、_has_key、_has_keys_any、_has_keys_all
    • 布尔值: _is_true、_is_false
    • 集合: _is_empty、_is_not_empty
  • 操作数定义(left_operand / right_operand):
    • 字面值:{"literal": value}
    • 列:{"column": "field_name"}
    • 函数:{"function_name": { ...args }}
  • 操作数类型(由最终值决定): bigint_operand、text_operand、boolean_operand、timestamptz_operand 等

谓词示例(从时间戳中提取月份,并检查是否 = 12):

{
  "_eq": {
    "bigint_operand": {
      "left_operand": {
        "extract_timestamptz": { "time": { "column": "created_at" }, "unit": "MONTH" }
      },
      "right_operand": { "literal": "12" }
    }
  }
}
5. 聚合、窗口函数和排序
聚合([table]_aggregate)

返回一个聚合查询对象,其中包含两个主要字段:

  • nodes:实际表对象([table!]!)的数组,包含符合查询 where、limit、offset 和 order_by 条件的原始数据行。
  • aggregate:一个对象,包含对匹配行进行的统计计算:
    • count(columns: [Enum], distinct: Boolean):
      • 如果未提供列,则统计所有行(COUNT(*))。
      • 如果 distinct: true 且提供了 1 个或多个列,则统计指定列的唯一值或唯一组合。
      • 如果 distinct: false 且提供了多个列,则系统只统计数组中的第一个列。
    • sum、avg:仅适用于数值列。
    • max、min:适用于可比较列(数值、时间、文本)。
窗口函数

支持 ROW_NUMBER、RANK、DENSE_RANK、NTH_VALUE 等高级分析操作,可用于特定的公式和窗口框架输入。

排序(order_by)

支持按以下方式排序:

  1. 直接列:{ title: asc }
  2. 相关的 1:1 记录:{ author: { name: desc } }
  3. N:1 记录的聚合:{ post_tags_aggregate: { count: desc } }
  4. 向量搜索:应用 TEXT_COLUMN_VECTOR_SORT 扩展后,文本列可能支持相似度排序。
6. 公式函数(操作数)

在操作数包装对象中使用函数,方法是将大写函数名称包裹在其参数外层(例如,{"EXTRACT_TIMESTAMPTZ": { "time": ..., "unit": ... }})。

输入约束与语义
  • [TYPE]:表示必需的列或标量操作数,且类型必须完全匹配。
  • [TYPE?]:表示可选操作数(通常默认为 0 或 null)。
  • [ANY]:接受任意类型的列或标量操作数。
  • [NUMERIC]:接受 BIGINT、INTEGER、DECIMAL、FLOAT8 或 BIGSERIAL。
  • [COMPARABLE]:接受 NUMERIC 类型、TEXT、DATE、TIMESTAMPTZ、TIMETZ 或 INTERVAL。
  • [ANY[]]:接受任意类型的数组操作数。
  • [Enum:NAME]:要求显式提供与 [NAME] 定义匹配的 Enum 值(例如,[Enum:DATE_UNIT] 要求使用 YEAR、MONTH 等)。
  • 嵌套函数:如果输出类型与预期输入类型匹配,参数通常可以是其他函数的输出。
操作
  • CONCAT(items: [TEXT[]])
  • SUBSTRING(source_text: [TEXT], start_index: [BIGINT], end_index: [BIGINT])
  • LEFT(source_text: [TEXT], length: [BIGINT])
  • RIGHT(source_text: [TEXT], length: [BIGINT])
  • LOWER(source_text: [TEXT])
  • UPPER(source_text: [TEXT])
  • TRIM(text: [TEXT])
  • TRIM_TRAILING_ZERO(source_text: [TEXT])
  • REPEAT(text: [TEXT], times: [BIGINT])
  • ENCODE_URL(text: [TEXT])
  • DECODE_URL(text: [TEXT])
  • ARRAY_CONCAT(first_array: [ANY[]], second_array: [ANY[]])
  • SLICE(array: [ANY[]], start_index: [BIGINT], length: [BIGINT])
  • UNIQUE(array: [ANY[]])
  • COALESCE(array: [ANY[]])
搜索与替换
  • REPLACE_OCCURRENCES(source_text: [TEXT], search_text: [TEXT], replace_text: [TEXT], max_replacements: [BIGINT])
  • REPLACE_AT_POSITION(source_text: [TEXT], start_index: [BIGINT], length: [BIGINT], replace_text: [TEXT])
  • POSITION(source_text: [TEXT], search_text: [TEXT])
  • CONTAINS(source_text: [TEXT], search_text: [TEXT])
正则表达式
  • REGEX_EXTRACT(text: [TEXT], regex: [TEXT])
  • REGEX_REPLACE(text: [TEXT], regex: [TEXT], replacement: [TEXT])
  • REGEX_EXTRACT_ALL(text: [TEXT], regex: [TEXT])
  • REGEX_MATCH(text: [TEXT], regex: [TEXT])
格式化与工具函数
  • TEXT_DECIMAL_FORMAT(number: [DECIMAL], fraction_digits: [BIGINT], rounding_mode: [Enum:ROUNDING_MODE], clear_trailing_zeros: [BOOLEAN])
  • NUMBER_FORMAT(number: [DECIMAL], fraction_digits: [BIGINT], format: [Enum:NUMBER_FORMAT])
  • STRING_LEN(source_text: [TEXT])
  • RANDOM_TEXT(min_length: [BIGINT], max_length: [BIGINT], include_numbers: [BOOLEAN], include_lower_case: [BOOLEAN], include_upper_case: [BOOLEAN])
  • UUID()
  • JOIN(array: [TEXT[]], separator: [TEXT])
  • SPLIT(source_text: [TEXT], delimiter: [TEXT])
  • ARRAY_LEN(array: [ANY[]])
算术
  • ADD(value0: [NUMERIC], value1: [NUMERIC])
  • SUBTRACT(minuend: [NUMERIC], subtrahend: [NUMERIC])
  • MULTIPLY(value0: [NUMERIC], value1: [NUMERIC])
  • DIVIDE(dividend: [DECIMAL], divisor: [DECIMAL])
  • MODULO(dividend: [NUMERIC], divisor: [NUMERIC])
  • ABS(number: [NUMERIC])
  • POW(base: [NUMERIC], exponent: [NUMERIC])
  • LOG(base: [DECIMAL], argument: [DECIMAL])
舍入与格式
  • ROUND_UP(number: [DECIMAL])
  • ROUND_DOWN(number: [DECIMAL])
  • DECIMAL_FORMAT(number: [DECIMAL], fraction_digits: [BIGINT], rounding_mode: [Enum:ROUNDING_MODE])
生成器
  • RANDOM_BIGINT(min_length: [BIGINT], max_length: [BIGINT])
  • SEQUENCE(start: [BIGINT], end: [BIGINT], step: [BIGINT])
当前时间
  • CURRENT_DATE()
  • CURRENT_TIMETZ()
  • CURRENT_TIMESTAMPTZ()
Constructors
  • MAKE_DATE(years: [BIGINT], months: [BIGINT], days: [BIGINT])
  • MAKE_TIMETZ(hours: [BIGINT?], minutes: [BIGINT?], seconds: [BIGINT?], milliseconds: [BIGINT?])
  • MAKE_TIMESTAMPTZ(years: [BIGINT], months: [BIGINT], days: [BIGINT], hours: [BIGINT?], minutes: [BIGINT?], seconds: [BIGINT?], milliseconds: [BIGINT?])
  • MAKE_INTERVAL(years: [BIGINT], months: [BIGINT], weeks: [BIGINT], days: [BIGINT], hours: [BIGINT], minutes: [BIGINT], seconds: [BIGINT], milliseconds: [BIGINT])
  • FROM_DATE_AND_TIMETZ(date: [DATE], timetz: [TIMETZ])
Extraction & Formatting
  • EXTRACT_DATE(time: [DATE], unit: [Enum:DATE_UNIT])
  • EXTRACT_TIMETZ(time: [TIMETZ], unit: [Enum:TIME_UNIT])
  • EXTRACT_TIMESTAMPTZ(time: [TIMESTAMPTZ], unit: [Enum:TIMESTAMP_UNIT])
  • DATE_FORMAT(time: [DATE], format: [TEXT], language: [Enum:LANGUAGE])
  • TIMETZ_FORMAT(time: [TIMETZ], format: [TEXT], language: [Enum:LANGUAGE])
  • TIMESTAMPTZ_FORMAT(time: [TIMESTAMPTZ], format: [TEXT], language: [Enum:LANGUAGE])
  • DURATION_FORMAT(duration: [DECIMAL], unit: [Enum:DURATION_UNIT], format: [TEXT])
  • RELATIVE_DATE(time: [DATE], language: [Enum:LANGUAGE], hide_suffix: [BOOLEAN])
  • RELATIVE_TIMESTAMPTZ(time: [TIMESTAMPTZ], language: [Enum:LANGUAGE], hide_suffix: [BOOLEAN])
Calculations & Deltas
  • DELTA_DATE(date: [DATE], increase: [BOOLEAN], years: [BIGINT?], months: [BIGINT?], days: [BIGINT?])
  • DELTA_TIMETZ(timetz: [TIMETZ], increase: [BOOLEAN], hours: [BIGINT?], minutes: [BIGINT?], seconds: [BIGINT?], milliseconds: [BIGINT?])
  • DELTA_TIMESTAMPTZ(timestamptz: [TIMESTAMPTZ], increase: [BOOLEAN], years: [BIGINT?], months: [BIGINT?], days: [BIGINT?], hours: [BIGINT?], minutes: [BIGINT?], seconds: [BIGINT?], milliseconds: [BIGINT?])
  • EXTRACT_DATE_DURATION(start_time: [DATE], end_time: [DATE], unit: [Enum:DATE_UNIT])
  • EXTRACT_TIMETZ_DURATION(start_time: [TIMETZ], end_time: [TIMETZ], unit: [Enum:TIME_UNIT])
  • EXTRACT_TIMESTAMPTZ_DURATION(start_time: [TIMESTAMPTZ], end_time: [TIMESTAMPTZ], unit: [Enum:TIMESTAMP_UNIT])
Conversions
  • FROM_TIMESTAMPTZ_TO_DATE(timestamptz: [TIMESTAMPTZ])
  • FROM_TIMESTAMPTZ_TO_TIMETZ(timestamptz: [TIMESTAMPTZ])
Geography
  • FROM_COORDINATES(latitude: [DECIMAL], longitude: [DECIMAL])
  • GEO_DISTANCE(point0: [GEO_POINT], point1: [GEO_POINT], unit: [Enum:GEO_DISTANCE_UNIT])
  • GEO_LONGITUDE(geo: [GEO_POINT])
  • GEO_LATITUDE(geo: [GEO_POINT])
Aggregates
  • SUM(array: [NUMERIC[]])
  • AVG(array: [NUMERIC[]])
  • MAX(value0: [COMPARABLE], value1: [COMPARABLE])
  • MIN(value0: [COMPARABLE], value1: [COMPARABLE])
  • GREATEST(array: [COMPARABLE[]])
  • LEAST(array: [COMPARABLE[]])
Element Access
  • ITEM(array: [ANY[]], index: [BIGINT])
  • FIRST_ITEM(array: [ANY[]])
  • LAST_ITEM(array: [ANY[]])
  • RANDOM_ITEM(array: [ANY[]])
  • ARRAY_POSITION(array: [ANY[]], item: [ANY])
JSONB
  • JSON_EXTRACT_BY_DOT_NOTATION_JSONPATH(json: [JSONB], path: [TEXT])
Casting
  • CAST_FROM_TEXT(value: [TEXT])
  • CAST_COLUMN_TO_TEXT(value: [ANY])
  • CAST_ARRAY_TO_TEXT(value: [ANY[]])
  • CAST_TO_BIGINT(value: [ANY])
  • CAST_TO_DECIMAL(value: [ANY])
Vector Search
  • EMBEDDING_VECTOR_DISTANCE(embedded_text_column: [Enum:EMBEDDING_COLUMN], text: [TEXT], distance_function: [Enum:VECTOR_DISTANCE])
System
  • NULL_VALUE()
显式枚举定义

当参数需要使用 [Enum:NAME] 时,必须使用以下精确的字符串值之一:

  • [Enum:DATE_UNIT]:YEAR, MONTH, DAY, WEEK
  • [Enum:DURATION_UNIT]:DAY, HOUR, MINUTE, SECOND, MILLISECOND
  • [Enum:EMBEDDING_COLUMN]:(Dynamically generated based on columns with TEXT_COLUMN_VECTOR_SORT extension)
  • [Enum:GEO_DISTANCE_UNIT]:METER, KILOMETER, MILE
  • [Enum:LANGUAGE]:EN, ZH
  • [Enum:NUMBER_FORMAT]:THOUSANDS_SEPARATOR, PERCENT
  • [Enum:ROUNDING_MODE]:HALF_EVEN, HALF_UP, HALF_DOWN, UP, DOWN, CEILING, FLOOR
  • [Enum:TIMESTAMP_UNIT]:YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, MILLISECOND, WEEK
  • [Enum:TIME_UNIT]:HOUR, MINUTE, SECOND, MILLISECOND
  • [Enum:VECTOR_DISTANCE]:EUCLIDEAN, COSINE

Actionflow 协议

概述

尽管 zion-app.functorz.com 已支持从前端发起直接的 CRUD 操作,但许多后端操作需要多个步骤、运行时间较长,有时还必须异步执行。因此,zion-app.functorz.com 也支持用于这些场景的 Actionflow。Actionflow 是由 Actionflow 节点组成的有向无环图。这些节点表示操作(例如,插入数据库、调用另一个 Actionflow)或控制流变更(条件和循环)。Actionflow 还有两个特殊节点,即输入和输出节点,用于定义整个 Actionflow 的参数和返回值。

Actionflow 有两种运行模式:同步或异步。同步 Actionflow 在单个数据库事务中执行,因此遇到意外错误时会回滚所有数据库更改。同步 Actionflow 有运行时限制,以避免长期占用数据库连接。异步 Actionflow 会在新的数据库事务中执行每个节点,因此没有回滚机制,但更适合长时间运行的任务,例如耗时较长的 HTTP 调用,尤其是调用 LLM API,因为这类调用可能需要几分钟。在 Actionflow 中,所有调用 zion-app.functorz.com 内置 AI Agent 的节点都只能添加到异步 Actionflow 中。

Actionflow 调用流程

要调用 Actionflow,需要获取其 ID、参数列表以及可选的版本号。这些信息可以在项目架构中找到。 Actionflow 的调用方式因类型而异。

同步 Actionflow

同步 Actionflow 可以通过常规的 GraphQL mutation 调用。结果会在同一个 HTTP 请求的响应中返回。 请求:

mutation someOperationName ($args: Json!) {
 fz_invoke_action_flow(actionFlowId: "d3ea4f95-5d34-46e1-b940-91c4028caff5", versionId: 3, args: $args)
}
{
  "args": {
    "yaml": "post_link:\n  url: \"https://zion-app.functorz.com\"\n",
    "img_id": 1020000000000111
  }
}

在此查询中,$args 对应 Actionflow 输入节点中列出的参数。 响应:

{
  "data": {
    "fz_invoke_action_flow": {
      "img": {
        "id": 1020000000000090,
        "url": "https://fz-zion-static.functorz.com/202510252359/a64a7eb4793728a1977d3ea9e7b7e4e8/project/2000000000521152/import/1110000000000001/image/636.jpg"
      },
      "url": "https://zion-app.functorz.com"
    }
  }
}
异步 Actionflow

异步 Actionflow 通过 GraphQL mutation 触发,但结果不会在同一个 HTTP 请求的响应中返回。相反,系统会返回一个 fz_create_action_flow_task,其中包含对应任务的 ID,随后使用该 ID 通过单独的 GraphQL subscription 订阅结果。 Mutation 请求:

mutation mh49tgie($args: Json!) {
 fz_create_action_flow_task(actionFlowId: "2a9068c5-8ee3-4dad-b3a4-5f3a6d365a2f", versionId: 4, args: $args)
}
{
  "args": {
    "int": 123,
    "img_id": 1020000000000116,
    "some_text": "Dreamer",
    "datetime_with_timezone": "2025-10-23T20:13:00-07:00"
  }
}

Mutation 响应: { "data": { "fz_create_action_flow_task": 1150000000000148 } }

Subscription 请求:

subscription fz_listen_action_flow_result($taskId: Long!) {
  fz_listen_action_flow_result(taskId: $taskId) {
    __typename"
    output
    status
  }
}
{ "taskId" : 1150000000000148 }

Subscription 响应:

{
  "data": {
    "fz_listen_action_flow_result": {
      "__typename": "ActionFlowTaskResult",
      "output": {
        "img": {
          "id": 1020000000000089,
          "url": "https://fz-zion-static.functorz.com/202510262359/3a5f04371bf68d6c94bb890879101f0a/project/2000000000521152/import/1110000000000001/image/637.jpg"
        },
        "xyz": {
          "type": "Point",
          "coordinates": [
            131,
            22
          ]
        }
      },
      "status": "COMPLETED"
    }
  }
}

在返回最终结果(位于 "output" 中)之前,GraphQL subscription 可能会发送多条消息,且每条消息可能包含不同的状态值。 状态字段的转换规则如下:

switch (status) {
  case CREATED -> Set.of(PROCESSING);
  case PROCESSING -> Set.of(COMPLETED, FAILED);
  default -> Set.of();
};

AI Agent 协议

概述

Zion.app 集成了 AI Agent 构建器,支持多模态(文本、视频、图像)输入和输出、提示词模板、上下文获取(通过数据库和第三方 API)、工具调用(actionflows、第三方 API 和其他 AI Agent)以及结构化输出(JSON 符合相应的 JSONSchema)。
GraphQL 服务会根据 AI Agent 输出的配置,以不同方式交付结果,具体取决于输出是否为流式以及是否为结构化输出。结构化输出无法以流式方式传输,但纯文本可以选择流式传输或非流式传输。结构化输出必须附带描述 JSON 类型的 JSONSchema。
若要调用 AI Agent,必须从项目架构中获取其 ID 和输入参数。通过 Zion.app Agent 构建器创建的 AI Agent 只能通过 GraphQL API 异步调用。

流式输出的调用流程

下面将使用一个输出为流式纯文本的 AI Agent 配置来说明此流程。其配置如下:

{
    "id": "mgzzu8jp",
    "summary": "An example summary of what the agent does",
    "inputs": {
      "mgzzufo2": {
        "type": "VIDEO",
        "displayName": "the_video",
      },
      "mh4cjjcf": {
        "type": "TEXT",
        "displayName": "text",
      },
      "mh4cjkyv": {
        "type": "BIGINT",
        "displayName": "some_int",
      },
      "mh4cjoof": {
        "type": "array",
        "itemType": "IMAGE",
        "displayName": "images",
      }
    },
    "output": "Unstructured Text"
}
  1. 发送一个 mutation 来启动 AI Agent,并将参数作为 inputArgs 提供,将 ID 作为 zAIConfigId 提供。响应值只包含对应对话的 ID。inputArgs 的键应与架构中 inputs 对象的键相同。图像、视频或其他二进制资源类型的输入参数,以及此类类型的数组,处理方式略有不同。它们在 inputArgs 对象中的键名会带有 _id 后缀。例如,以下配置
{ 
  "inputs": {
    "mgzzufo2": {
      "type": "VIDEO",
      "displayName": "the_video",
    }
  }
}

对应于:

{
  "inputArgs": {
    "mgzzufo2_id": 1030000000000002,
  }
}
突变请求:  
查询:
```gql
mutation ZAICreateConversation($inputArgs: Map_String_ObjectScalar!, $zaiConfigId: String!) {
 fz_zai_create_conversation(inputArgs: $inputArgs, zaiConfigId: $zaiConfigId)
}
```
变量:
```json
{
  "inputArgs": {
    "mgzzufo2_id": 1030000000000002,
    "mh4cjjcf": "Just some text",
    "mh4cjkyv": 23,
    "mh4cjoof_id": [
      1020000000000097,
      1020000000000111,
      1020000000000120
    ]
  },
  "zaiConfigId": "mgzzu8jp"
}
```
突变响应:
```json
{
  "data": {
    "fz_zai_create_conversation": 1480
  }
}
```

2. 使用获取到的会话 ID,订阅上一次调用 AI Agent 的结果。可能会收到多条消息。消息的状态可能从 IN_PROGRESS 变为 STREAMING,最终变为 COMPLETED。最后一条消息的状态始终为 COMPLETED,其 data 字段将包含此前所有 STEAMING 消息的 data 字段合并后的输出。
对于会输出推理内容的模型,处理方式与实际输出类似。也就是说,系统会先通过多条消息在 reasoningContent 字段中发送部分推理内容;待内容全部就绪后,又会在 COMPLETED 消息中发送完整的 reasoningContent。
订阅请求:
查询: gql subscription ZaiListenConversationResult($conversationId: Long!) { fz_zai_listen_conversation_result(conversationId: $conversationId) { conversationId status reasoningContent images { id __typename } data __typename } } 变量: json { "conversationId": 1480 } 订阅响应消息: json { "data": { "fz_zai_listen_conversation_result": { "__typename": "ConversationResult", "conversationId": 1480, "data": null, "images": null, "reasoningContent": null, "status": "IN_PROGRESS" } } } json { "data":{ "fz_zai_listen_conversation_result": { "__typename": "ConversationResult", "conversationId": 1480, "data": "This collection features three images and a short video. Two photos show the famous Chinese comedian and actor, Zhao Benshan. A third", "images": null, "reasoningContent": null, "status": "STREAMING" } } } json { "data":{ "fz_zai_listen_conversation_result": { "__typename": "ConversationResult", "conversationId": 1480, "data": " image is an anime illustration of a young woman in a \"SHOHOKU\" basketball jersey, resembling the character Haruko Akagi from the series *Slam Dunk*.", "images": null, "reasoningContent": null, "status": "STREAMING" } } } json { "data":{ "fz_zai_listen_conversation_result": { "__typename": "ConversationResult", "conversationId": 1480, "data": "This collection features three images and a short video. Two photos show the famous Chinese comedian and actor, Zhao Benshan. A third image is an anime illustration of a young woman in a \"SHOHOKU\" basketball jersey, resembling the character Haruko Akagi from the series *Slam Dunk*.", "images": null, "reasoningContent": null, "status": "COMPLETED" } } }

非流式纯文本输出的调用流程
  1. mutation 步骤与流式输出调用流程中的步骤相同。也就是说,发送 mutation fz_zai_create_conversation(inputArgs: $inputArgs, zaiConfigId: $zaiConfigId) 以获取 conversation id。

  2. 不会有处于 STREAMING 状态的消息。也就是说,服务端会先发送一条状态为 IN_PROGRESS 的消息,随后直接发送包含最终结果的 COMPLETED 消息。

使用支持图像输出的模型的 AI Agent 调用流程

某些模型支持图像输出,例如 gemini-2.5-flash-image。
它们的调用流程与纯文本模型相同。不同之处在于,COMPLETED 消息中的 images 字段会包含内容。这些图像的 输出与纯文本输出并无不同,无论是否启用流式设置。它们的 COMPLETED 消息如下所示:

{
  "data": {
    "fz_zai_listen_conversation_result": {
      "__typename": "ConversationResult",
      "conversationId": 1494,
      "data": "I merged the three images into one, combining elements from each to create a new, unique image.\n",
      "images": [
        {
          "__typename": "FZ_Image",
          "id": 1020000000000164
        }
      ],
      "reasoningContent": null,
      "status": "COMPLETED"
    }
  }
}

images 中 FZ_Image 的 ID 表示 Zion 文件/资源系统中的 ID。请参阅 zion-binary-asset-upload-rules

结构化输出的调用流程

具有结构化输出的 AI Agent 不支持流式输出。它们的配置中也始终包含 JSONSchema。

{
  "output": {
      "type": "object",
      "properties": {
        "httpLink": {
          "type": "string"
        },
        "reasoning": {
          "type": "string"
        }
      },
      "required": [
        "httpLink",
        "reasoning"
      ]
    }
}

GraphQL 服务端不会发送处于“STREAMING”状态的消息。服务端会发送一条“COMPLETED”消息,其中的 data 字段是符合 JSONSchema 的 JSON。 例如:

{
  "httplink": "https://www.google.com/calendar/event?eid=MTcxN2U3cHAzaDFtYTdxYzd0bGV0aHNvYmsgamlhbmd5YW9rYWlqb2huQG0",
  "explanation": "No existing events were found on 2025-10-24 in America/Los_Angeles, so there are no conflicts. Preference is mornings; scheduled 08:00–08:10 at Los Altos High school. With no adjacent events, transit checks to previous and next events are trivially satisfied."
}
继续对话

AI Agent 返回结果(status = COMPLETED)后,可以通过调用 fz_zai_send_ai_message 继续对话。 同一 conversationId 上对 fz_zai_listen_conversation_result 的订阅会继续接收消息。
例如:
mutation 请求:

mutation continue($conversationId: Long!, $text: String) {
  fz_zai_send_ai_message(conversationId: $conversationId, text: $text)
}

变量:

{
  "conversationId": 1480,
  "text": "make it about the sun"
}

相应的 fz_zai_listen_conversation_result 返回的响应随后会继续。与启动 AI Agent 对话后的情况类似,会经历相同的状态转换:IN_PROGRESS -> (STREAMING) -> COMPLETED。

停止对话

对于仍处于“IN_PROGRESS”或“STREAMING”状态的对话,可以调用 fz_zai_stop_responding 来停止,该调用始终返回 true。 对处于“COMPLETED”状态的对话调用此方法时,GraphQL 响应的 errors 字段中会抛出 400 错误。 例如:
mutation 请求:

mutation continue($conversationId: Long!) {
  fz_zai_stop_responding(conversationId: $conversationId)
}

变量:

{
  "conversationId": 1480
}

二进制资源上传

所有二进制资源(图像、视频、文件)都存储在对象存储服务(例如 S3)中。它们的存储路径记录在 Zion 数据库中。在其他表中引用这些资源时,必须只存储资源的 Zion ID,而不能存储其路径或 URL。

上传工作流

要上传二进制资源并获取其 Zion ID,必须严格遵循以下两步流程:

步骤 1:获取预签名上传 URL
  1. 计算文件的 MD5 哈希值(原始 128 位哈希),然后对其进行 Base64 编码。

  2. 调用相应的 GraphQL mutation,请求预签名上传 URL。根据资源类型使用匹配的 mutation:

    • 图像使用 imagePresignedUrl
    • 视频使用 videoPresignedUrl
    • 其他文件使用 filePresignedUrl

    提供:

    • Base64 编码的 MD5 哈希值
    • 文件格式/后缀(见下文的 MediaFormat)
    • (可选)访问控制(见下文的 CannedAccessControlList)
    GraphQL mutation 示例
    mutation GetImageUploadUrl($md5: String!, $suffix: MediaFormat!, $acl: CannedAccessControlList) {
      imagePresignedUrl(imgMd5Base64: $md5, imageSuffix: $suffix, acl: $acl) {
        imageId
        uploadUrl
        uploadHeaders
      }
    }
    
    mutation GetVideoUploadUrl($md5: String!, $format: MediaFormat!, $acl: CannedAccessControlList) {
      videoPresignedUrl(videoMd5Base64: $md5, videoFormat: $format, acl: $acl) {
        videoId
        uploadUrl
        uploadHeaders
      }
    }
    
    mutation GetFileUploadUrl($md5: String!, $format: MediaFormat!, $name: String, $suffix: String, $sizeBytes: Int, $acl: CannedAccessControlList) {
      filePresignedUrl(
        md5Base64: $md5
        format: $format
        name: $name
        suffix: $suffix
        sizeBytes: $sizeBytes
        acl: $acl
      ) {
        fileId
        uploadHeaders
        uploadUrl
      }
    }
    
    • CannedAccessControlList(建议使用:PRIVATE):
      • AUTHENTICATE_READ, AWS_EXEC_READ, BUCKET_OWNER_FULL_CONTROL, BUCKET_OWNER_READ, DEFAULT, LOG_DELIVERY_WRITE, PRIVATE, PUBLIC_READ, PUBLIC_READ_WRITE
    • MediaFormat:
      • CSS, CSV, DOC, DOCX, GIF, HTML, ICO, JPEG, JPG, JSON, MOV, MP3, MP4, OTHER, PDF, PNG, PPT, PPTX, SVG, TXT, WAV, WEBP, XLS, XLSX, XML
步骤 2:上传文件并使用返回的 ID
  1. mutation 响应包含:
    • 资源的唯一 ID(imageId、videoId 或 fileId)
    • 预签名的 uploadUrl
    • 所需的 uploadHeaders
  2. 上传文件:
    • 使用原始文件数据向 uploadUrl 发起 HTTP PUT 请求
    • 包含 mutation 响应中的所有 uploadHeaders
  3. 引用资源:
    • 在 Zion 数据 mutation 中,将返回的 ID 用作相应 *_id 字段的值(例如,cover_image_id: returnedImageId)

注意: 对 Zion.app 中的所有媒体上传,此两步流程都是必需的。

第三方 API

基于 Zion.app 构建的项目可以导入许多第三方 HTTP API。这些 API 分为两类:查询或 mutation,大致(但并非总是)对应于 HTTP GET 与 POST 的语义。
每个 API 都存储在以下数据结构中:

type ScalarType = 'string' | 'boolean' | 'number' | 'integer';
type TypeDefinition =
  | ScalarType
  | { [key: string]: TypeDefinition | TypeDefinition[] };

interface ThirdPartyApiConfig {
  id: string;
  name: string;
  operation: 'query' | 'mutation';
  inputs: { [key: string]: TypeDefinition };
  outputs: { [key: string]: TypeDefinition };
}

注意:ThirdPartyApiConfig 中 operation 字段的值决定了根 GraphQL 字段。也就是说,query → query operation_${id},mutation → mutation operation_${id}。

调用流程

除非用户要求移除,否则每个输入都应提供。
例如:
给定如下 TPA 配置:

      {
        "id": "lzb3ownk",
        "inputs": {
          "body": {
            "summary": "string",
            "location": "string",
            "description": "string",
            "start": {
              "dateTime": "string",
              "timeZone": "string"
            },
            "end": {
              "dateTime": "string",
              "timeZone": "string"
            },
            "attendees": [
              "string"
            ]
          },
          "Authorization": "string"
        },
        "outputs": {
          "body": {
            "kind": "string",
            "etag": "string",
            "id": "string",
            "status": "string",
            "htmlLink": "string",
            "created": "string",
            "updated": "string",
            "summary": "string",
            "description": "string",
            "location": "string",
            "creator": {
              "email": "string",
              "self": "boolean"
            },
            "organizer": {
              "email": "string",
              "self": "boolean"
            },
            "start": {
              "dateTime": "string",
              "timeZone": "string"
            },
            "end": {
              "dateTime": "string",
              "timeZone": "string"
            },
            "iCalUID": "string",
            "sequence": "number",
            "reminders": {
              "useDefault": "boolean"
            },
            "eventType": "string"
          }
        },
        "operation": "mutation"
      }

相应的 GraphQL 查询应为:

mutation request_${nonce}($summary: String, $location: String, $description: String, $start_dateTime: String, $start_timeZone: String, $end_dateTime: String, $end_timeZone: String, $attendees:[String], $Authorization: String) {
  operation_lzb3ownk(fz_body: {}, arg1: $_1, arg2: $_2) {
    responseCode
    field_200_json {
      {subFieldSelections}
    }
 }
}

field_200_json 是所有第三方 API 派生的 GraphQL 操作中的固定字段,表示适用于所有 2xx 响应码的响应。

应始终检查 responseCode 子字段,以防返回 5xx 或 4xx 响应码;这意味着 field_200_json 将为空。

评论

支持 Markdown