文档 参考 产物格式

产物格式

产物是阶段产出的带类型交付物:可预览、可下载、可编辑、可通过公开链接分享,也可以串联到下一个阶段。

为什么要为输出指定类型。"帮我写一份摘要"和"帮我写一份 Markdown 摘要,并在 frontmatter 里以 YAML 数组形式列出 5 条关键事实"会产生差异极大的结果。 格式声明告诉智能体"什么样的产出才算成功"。同时它也让预览面板可以做正确的事: 渲染 Markdown、按 schema 校验 JSON、给代码做语法高亮、校验 CSV 的列。

智能体如何产生一个产物

代码库中存在两条提取路径,表面看起来相似,但适用于不同的场景。两者最终都会写入 app_artifact 表中的同一行;根据你当前所处的位置,选择对应的那一条。

路径 A:应用阶段(基于 Markdown 段落提取)

当一个阶段作为 AppExecution 的一部分运行时,执行器会构建一个系统提示词, 其末尾包含一个 ## Expected Outputs 区块,把每条 artifact_defs 列为 ### {{title}},并附带其 formatdescription。然后由 extractArtifactContent 按以下优先级扫描智能体的响应:

  1. 匹配一个 Markdown 标题,其文本与产物的 title 相同(不区分大小写, #####):取该标题之后、直到下一个同级或更高级标题之间的所有内容。
  2. 对于结构化格式(codejsonhtmlcsv):回退到第一个语言匹配的围栏代码块。
  3. 如果该阶段只声明了一个产物,回退到整段响应文本。

这就是为什么下面的示例使用 ## Stripe:Company Brief 作为段落标题, 而不是 XML 标签:这才是匹配器实际查找的形式。

## Stripe Company Brief

# Stripe Company Brief

## Business model
Stripe runs payment infrastructure as a service...

## Recent moves
...

每个被保存下来的产物都会得到一个 S3 key app-artifacts/{userId}/{executionId}/{stageId}/{defId}、一个生成的 文件名 {appSlug}_{stageSlug}_{defSlug}.{ext},以及从格式派生出的 MIME 类型 (text/markdownapplication/jsontext/csvtext/html 等)。对于 code,扩展名由该 def 上的 language 字段决定(python.pythonts.ts);其他类型则使用固定的映射表。原始内容 也会保存在 app_artifact 行上,对于过大的内容体则以 S3 作为兜底。

在应用阶段中奏效的写法。目标(goal)里 不存在 {{ stages.<id>.<artifactId> }} 这样的引用: 目标模板器只会替换表单输入。前置阶段的产物以另外两种方式抵达下一个阶段: (1)留在共享沙箱文件系统中的文件,以及(2)自动注入到系统提示词顶部的 ## Previous Stage Outputs 区块。

路径 B:聊天流(基于 XML 标签提取)

在自由聊天中,智能体会直接在输出流中以 XML 标签的形式发出产物。流式解析器 (artifact/parser.ts)能够安全处理分块边界,并读取以下三个属性:

<artifact title="Stripe Company Brief" type="markdown" language="">
# Stripe Company Brief

## Business model
Stripe runs payment infrastructure as a service...
</artifact>

可识别的属性是 titletypelanguage。 聊天流标签上没有 idformat 属性, 解析器会忽略其他任何属性。流结束时若仍有未闭合的标签,会作为字面文本展示出来, 而不会被悄悄丢弃。

选择合适的格式

输出是……使用
给人阅读的文档markdown
给其他机器消费的数据jsoncsv
页面或仪表板html
源代码(单文件或整棵树)code
图片或图表image
其他任何东西(PDF、ZIP、音频、视频)file

你在阶段的 artifact_defs 条目上设置格式。智能体会让其输出符合该类型, 或者在做不到时显式失败:这正是你想要的行为。

markdown 最常见

报告、摘要、简报、文档、草稿。散文类内容的默认选择。生产环境中大约 60% 的产物都是 markdown

预览

使用标准 CommonMark + GFM 扩展进行渲染:表格、任务列表、带语法高亮的围栏代码块。 数学公式(KaTeX)和 Mermaid 图表会内联渲染。点击任意标题可获得稳定的锚链接。

导出

  • .md:原始 Markdown 源文件。
  • PDF:服务端使用工作区品牌样式渲染。
  • DOCX:通过 Pandoc 转换;样式映射到 Word 标题。

串联

当下一步要做摘要、翻译或重组同一段内容时,把 Markdown 产物喂给下一个阶段。 接收阶段可以原样读取 Markdown,也可以根据提示指令提取某些段落。

code 单文件或文件树

任意语言的源文件。被生成脚本、配置、迁移文件、测试或整套项目骨架的应用所使用。

预览

带语法高亮的视图。语言根据文件扩展名自动识别,也可在阶段的 artifact_def.language 上指定。包含行号、复制按钮和代码块折叠。

多文件输出

单个 code 产物可以容纳一棵文件树。当应用生成的是小型项目而非单一文件时非常有用:

<artifact id="project" format="code">
<file path="src/main.ts">
import { greet } from './greet';
console.log(greet('world'));
</file>
<file path="src/greet.ts">
export const greet = (name: string) => `Hello, ${name}!`;
</file>
<file path="package.json">
{ "name": "demo", "version": "0.0.1" }
</file>
</artifact>

预览左侧展示文件树,右侧展示选中的文件。可下载单个文件,也可以把整棵树打包为 ZIP 下载。

html 自包含的页面

独立的网页、仪表板、幻灯片、品牌化报告。在预览面板里完整渲染。

预览

在 iframe 中按原样渲染 HTML,允许脚本运行。沙箱非常严格:无法访问父页面、 无法访问你的 Connect、没有 cookie、没有 localStorage 透传。可安全地通过公开链接分享。

导出

  • .html:单个自包含的文件。
  • PDF:服务端打印为 PDF。
  • PNG/JPEG 截图:整页或首屏。

什么时候用这个类型

只要你想得到一个自包含的可视化输出,让用户无需任何工具就能在浏览器里打开它。常见场景: 面向客户的报告、仪表板快照、含图表的周报、会议演讲幻灯片导出。

json 机器到机器

结构化数据。当输出将被另一个系统消费而非人类阅读时使用,或者当应用中的下一个阶段 希望读取特定字段、而不是重新解析自由文本时使用。

预览

带类型信息的可折叠树视图。如果阶段在 artifact_def.schema 中声明了 schema, 预览会进行校验并内联展示任何偏差。

Schema 声明

{
  "id": "extracted",
  "format": "json",
  "schema": {
    "type": "object",
    "properties": {
      "company": { "type": "string" },
      "founded_year": { "type": "number" },
      "employees": { "type": "number" }
    },
    "required": ["company"]
  }
}

Schema 给智能体提供了护栏(它知道应该产出怎样的形状),同时也免费让预览拥有一个校验器。

串联

多阶段的应用几乎总是在阶段之间使用 json:下一个阶段可以读取具体字段, 而不必重新解析自由文本。

csv 表格

表格类数据。用于线索列表、账户快照、财务明细、问卷回收等场景的常用格式。

预览

电子表格风格的表格视图,列可排序,支持列内过滤,并显示行数统计。宽表支持横向滚动, 长表使用虚拟滚动。

导出

  • .csv:带表头的 RFC 4180 格式。
  • .xlsx:Excel 文件。尽可能保留数据类型。
  • 推送到 Google Sheets:在已授权 Drive Connect 的前提下。

Schema 校验

如果阶段声明了列名和类型,智能体的输出会被逐行校验。不匹配的行会在预览中被标记并附上原因。 你可以就地修复:编辑该行、保存,运行串联到下游时会读到修正后的版本。

image 视觉

生成的或渲染出来的图片。图表、示意图、营销素材、原型稿。

预览

支持缩放和平移的原尺寸图片。支持 PNG、JPEG、SVG 和 WebP。

如何产生

有两种方式:

  • 通过 generate_image 工具(基于所配置提供商的文本转图像, 通常是 Gemini 的图像 API,如果宿主有相应的 key)。
  • 通过智能体沙箱中的代码:Matplotlib 图表、Graphviz 示意图、Pillow 合成、 Playwright 截图。

导出

  • 按原格式下载。
  • 对位图输出可以按不同分辨率重新渲染。
  • 对于 SVG:还可以下载为 PNG / PDF 的栅格化版本。

file 兜底通道

任何不适合其他格式的内容:PDF、ZIP、音频、视频、二进制格式。如果你拿不定主意, file 是安全的默认选项。

预览

按类型自适应:

  • PDF:用内置查看器内联渲染。
  • 音频:带波形的 HTML5 播放器。
  • 视频:可拖动进度条的 HTML5 播放器。
  • ZIP:文件列表;点击任意条目可作为独立产物来预览。
  • 未知:展示元数据(大小、MIME)和下载按钮。

导出

直接下载。文件在投递到邮件、Slack 或 Drive 时会保留原始文件名和 MIME 类型。MIME 类型 与产物一起存储在 app_artifact.mime_type 中。

在阶段之间串联产物

每个阶段的产物都会作为命名引用自动暴露给下一个阶段。接收阶段的目标可以直接引用它:

# 阶段 2 的目标引用阶段 1 的产物
Take the CSV from Stage 1 ({{ stages.find.leads }}) and
enrich each row with the company's funding history.
Output the same CSV with three new columns:
total_funding, last_round, last_round_date.

变量会解析为前一个阶段的输出,保持其原本的格式。智能体会根据类型自动选择读取方式: CSV 当作表格读、JSON 当作结构化对象读、Markdown 当作文档读。你不需要写任何解析逻辑。

产物如何存储:持久化布局

一旦执行器提取出内容(上面的路径 A 或路径 B),它会写入两处app_artifact 表中的一行,以及配置好的 S3 桶中的一个对象。两者被设计为可彼此独立存活 。DB 中的内容是快速路径,S3 是耐久副本,也是大体积内容的最终来源。

S3 key 的结构

所有从应用阶段发出的产物都会落到一个确定性的 key 上:

app-artifacts/{user_id}/{execution_id}/{stage_id}/{def_id}

这个结构让你能可预测地批量访问:一次运行的所有产物共享 app-artifacts/{user_id}/{execution_id}/ 前缀; 某个阶段历史上产生过的所有产物(跨同一应用的多次运行)都可以通过列出 app-artifacts/{user_id}/*/{stage_id}/{def_id} 来访问。 聊天发出的产物使用平行前缀(artifacts/{user_id}/…/{file_name}), 因为它们没有执行上下文。

文件名约定

行上的 file_name 由 slug 化的应用、阶段和 def 标题确定性地拼接而成: {app_slug}_{stage_slug}_{def_slug}.{ext}。扩展名来自下面的 格式 → 扩展名映射表,但 code 例外:它会原样使用 def 的 language 作为扩展名(所以 Python 代码产物最终的文件名是 analysis.python,而不是 analysis.py, 这是有意为之,便于类型的往返还原)。

格式 → MIME → 扩展名参考表

format保存到行上的 MIME默认扩展名
markdowntext/markdownmd
jsonapplication/jsonjson
htmltext/htmlhtml
csvtext/csvcsv
codetext/plaindef 的 language(例如 pythonts
imageapplication/octet-streamtxt(保存时可覆盖)
file 及其他一切text/plaintxt

imagefile 默认使用通用 MIME,因为在字节被检查之前实际内容类型 并不确定;上传管线会在嗅探之后覆盖该行上的 mime_type

内联内容 vs S3 兜底

为了便利,行的 content 列也会保存产物的原始内容; 产物是否能从 DB 热读取,取决于它的创建方式以及大小。在恢复执行时, 执行器读取前置阶段产物的规则是确定的:

  1. 如果 app_artifact.content 非空:直接使用。
  2. 否则如果 s3_key 已设置:从 S3 下载,按 UTF-8 解码后使用。
  3. 否则:以空内容继续(这是一种可恢复的退化状态)。

因此一个产物端到端的最坏情况只是"内容为空";即使 S3 对象消失,行、它的元数据 以及它的预览结构仍然存在。

JSON 校验

对于 format: "json",执行器会在保存之前尝试对提取到的内容执行 JSON.parse。如果解析失败,它仍然会保存原始内容(你不会丢失智能体的工作成果), 但会记录一条警告。预览会把该产物标记为"无效 JSON",让你一眼就能看到问题。

artifact SSE 事件

每当一个阶段的输出被持久化时,该次执行的 SSE 流就会发出一个 artifact 事件, 其载荷是完整的 AppArtifact 行,包括内联的 content(如果有的话)。 订阅方无需再发起额外请求即可渲染预览:

event: artifact
data: {
  "id": "art_8a3...",
  "execution_id":"exec_8f4...",
  "stage_id": "research",
  "def_id": "competitive_analysis",
  "title": "Stripe Competitive Analysis",
  "format": "markdown",
  "mime_type": "text/markdown",
  "file_name": "company_brief_research_competitive_analysis.md",
  "s3_key": "app-artifacts/usr_.../exec_.../research/competitive_analysis",
  "size_bytes": 8421,
  "content": "# Stripe Competitive Analysis\n\n..."
}

公开分享产物

每个产物都可以通过公开链接分享,而不会暴露你工作区中的其他内容:

1
打开产物并点击"分享"。 会生成一个带 token 的 URL:https://app.aitroop.net/s/<token>
2
配置访问权限。 只读,或读取 + 下载。可选过期时间。可选密码门禁。可选解锁提示 ("解锁前先索取邮箱")。
3
随时可撤销。 打开 设置 → 分享 并关掉开关,或调用 DELETE /api/shares/:id

app_share 数据模型

一个分享对应 app_share 表中的一行,以一个随机的 share_token 作为 key。 该行携带访问控制状态和一个可选的访问门禁:

用途
resource_type / resource_id正在分享的对象:一个产物、一次执行记录或一个应用。
access_modepublic(任何持有链接的人)或 token(任何用正确密钥解锁的人)。
access_token_hashaccess_mode = 'token' 时,解锁密钥的 Argon2/bcrypt 哈希。
expires_at可空。在此时间戳之后,分享会解析为 expired
max_views / view_count可选的访问上限及当前计数。每次成功解析都会原子地递增计数。
revoked_at撤销时被设置;之后会解析为 not_found
permission目前固定为 read;schema 为更高权限的分享预留了空间。

解析状态

请求 GET /api/public/shares/:token 会返回五种结果之一, 公开页面的渲染逻辑由它决定:

kind对应情况
public_ok公开链接、没有门禁。渲染资源。
requires_tokentoken 门禁;渲染解锁表单。
expiredexpires_at 已过期。
over_limitview_count >= max_views
not_found错误的 token、已删除的分享,或已撤销。

Token 解锁会把密钥 POST 到 POST /api/public/shares/:token/unlock; 成功后服务端会签发一个短期 JWT,将其作为 cookie 设置在 /s 路径下, 之后用户可以请求 /api/public/shares/:token/blob(内联)或 …/download(带 attachment 头),直到 cookie 过期。

版本与历史

每个产物都会被版本化。应用的每次运行都会产生一个新版本;在聊天中,每条产生产物的回复都是一个新版本。 你可以在预览面板中回看历史,并把任意一个历史版本恢复为当前版本。

产物的保留期等同于应用(或聊天)的生命周期。删除一个应用会归档它的产物;归档在 30 天内可恢复, 之后会从 S3 中永久删除。

编辑产物

在任意产物上点击 编辑,你会得到一个专门针对这一个产物的聊天。 在里面提出修改要求,如 "把表格做宽一点""加一行 Q4 的数据""把第二段重写得不要那么正式"。可以保存回同一个产物,也可以分叉出新版本。

对产物的编辑不会改变应用本身。如果你希望该改动应用到后续运行,需要在编辑器中打开应用并更新 阶段的目标。"在聊天中编辑"这个界面也是一个有用的调试工具: 聊天会带着该产物和原始目标作为上下文打开。

常见问题与排错

智能体明明产出了内容,但我的执行记录显示"0 个产物"。

对于应用阶段,执行器通过把产物的 title 与响应中的某个 ## 标题 进行匹配来提取内容(如果只有一个结构化产物,则尝试找一个对应语言的围栏代码块)。 如果两者都没命中,就不会持久化任何产物。两种修复方式:

  • 收紧阶段目标,让智能体给段落命名"把报告写在 ## Stripe Competitive Analysis 这个标题之下。"标题必须和产物的 title 一致。
  • 检查阶段的 artifact_defs:执行器至少需要一个条目才会开始查找。

在聊天中规则不同:流式解析器只会在 <artifact title="..." type="...">…</artifact> 标签上触发。 如果智能体忘了包裹,你会内联看到内容,但 app_artifact 表里不会有对应的行。

我的 JSON 产物里有尾随逗号 / 注释,解析不了。

智能体偶尔会不小心吐出"JSON5"。预览会标记这种情况。修一下目标: "输出严格符合 RFC 8259 的 JSON,不要注释、不要尾随逗号。" 对于反复发生的情况,给 artifact_def 加一个 schema,让智能体获得更严格的约束。

CSV 产物的列不对。

在 artifact_def 中声明列的 schema:

{
  "id": "leads",
  "format": "csv",
  "schema": {
    "columns": [
      { "name": "company", "type": "string", "required": true },
      { "name": "url", "type": "string" },
      { "name": "size", "type": "number" }
    ]
  }
}

智能体会把 schema 作为指令的一部分来读取;不匹配的行会未通过校验,并在预览中被标记出来。

我希望一个阶段产出多个产物。

artifact_defs 中列出多个条目:

"artifact_defs": [
  { "id": "brief", "format": "markdown" },
  { "id": "data", "format": "json" }
]

智能体会发出两个 <artifact> 块。两者都会被存储,都可以单独预览, 下游也都可以通过 ID 引用。

我能把一个产物作为新一次运行的输入吗?

可以。大多数文件输入字段都接受上传文件或对现有产物的引用。在表单中,点击文件字段旁边的 产物选择器图标,挑选一个产物即可。新运行会直接从 S3 读取它,无需重新下载/重新上传。

产物可以多大?

软性大小限制为每个产物 100 MB,硬性大小限制为 1 GB。对于更大的载荷,使用 file 格式并打包为 ZIP。超过 50 MB 的产物存储在较慢的存储层;超过 25 MB 的产物预览会关闭内联渲染。

产物的预览没问题,但下载下来损坏了。

几乎总是 MIME 类型不匹配:产物被声明为 text/csv,但实际字节是 XLSX, 或者反过来。平台会在保存时推断 MIME;如果你怀疑推断错了,打开产物,点击 编辑元数据,设置正确的 MIME,重新保存即可。