关于 zion-baas
zion-baas 是面向使用 Zion.app 构建前端应用的技能,涵盖统一 GraphQL API 的认证、项目元数据和运行时操作。它还提供脚本来查询、订阅并检查生成的数据库模式。
主要功能
- 01用户名或开发者令牌认证
- 02检索项目与 GraphQL 模式
- 03命令行执行查询和变更
- 04通过 WebSocket 监听订阅
- 05记录项目凭据与用户令牌
适用场景
- 01为 Zion.app 前端配置认证
- 02查找项目并检查数据模型
- 03从命令行测试查询或变更
- 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_StoreREADME.mdscripts/meta.tsfetchRuntimeToken.tsauthenticate.tsauthenticateEmail.tspackage.jsonsubscribe.tsgql.tstsconfig.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)
支持按以下方式排序:
- 直接列:
{ title: asc } - 相关的 1:1 记录:
{ author: { name: desc } } - N:1 记录的聚合:
{ post_tags_aggregate: { count: desc } } - 向量搜索:应用
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"
}
- 发送一个 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" } } }
非流式纯文本输出的调用流程
-
mutation 步骤与流式输出调用流程中的步骤相同。也就是说,发送 mutation fz_zai_create_conversation(inputArgs: $inputArgs, zaiConfigId: $zaiConfigId) 以获取 conversation id。
-
不会有处于 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
-
计算文件的 MD5 哈希值(原始 128 位哈希),然后对其进行 Base64 编码。
-
调用相应的 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
- mutation 响应包含:
- 资源的唯一 ID(
imageId、videoId或fileId) - 预签名的
uploadUrl - 所需的
uploadHeaders
- 资源的唯一 ID(
- 上传文件:
- 使用原始文件数据向
uploadUrl发起 HTTPPUT请求 - 包含 mutation 响应中的所有
uploadHeaders
- 使用原始文件数据向
- 引用资源:
- 在 Zion 数据 mutation 中,将返回的 ID 用作相应
*_id字段的值(例如,cover_image_id: returnedImageId)
- 在 Zion 数据 mutation 中,将返回的 ID 用作相应
注意: 对 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 将为空。