產物格式
產物是階段產出的帶型別交付物:可預覽、可下載、可編輯、可透過公開連結分享,也可以串接到下一個階段。
智慧體如何產生一個產物
程式碼庫中存在兩條提取路徑,表面看起來相似,但適用於不同的場景。兩者最終都會寫入 app_artifact 表中的同一列;根據你目前所處的位置,選擇對應的那一條。
路徑 A:應用階段(基於 Markdown 段落提取)
當一個階段作為 AppExecution 的一部分執行時,執行器會建立一個系統提示詞, 其末尾包含一個 ## Expected Outputs 區塊,把每條 artifact_defs 列為 ### {{title}},並附帶其 format 和 description。然後由 extractArtifactContent 依以下優先順序掃描智慧體的回應:
- 比對一個 Markdown 標題,其文字與產物的
title相同(不分大小寫,#到####),取該標題之後、直到下一個同級或更高級標題之間的所有內容。 - 對於結構化格式(
code、json、html、csv),回退到第一個語言相符的圍欄程式碼區塊。 - 如果該階段只宣告了一個產物,回退到整段回應文字。
這就是為什麼下方的範例使用 ## Stripe,Company Brief 作為段落標題, 而不是 XML 標籤:這才是比對器實際尋找的形式。
# 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/markdown、application/json、text/csv、 text/html 等)。對於 code,副檔名由該 def 上的 language 欄位決定(python → .python、 ts → .ts);其他類型則使用固定的對應表。原始內容 也會保存在 app_artifact 列上,對於過大的內容體則以 S3 作為兜底。
{{ stages.<id>.<artifactId> }} 這樣的參照,目標模板器只會替換表單輸入。前置階段的產物以另外兩種方式抵達下一個階段: (1)留在共享沙盒檔案系統中的檔案,以及(2)自動注入到系統提示詞頂部的 ## Previous Stage Outputs 區塊。路徑 B:聊天串流(基於 XML 標籤提取)
在自由聊天中,智慧體會直接在輸出串流中以 XML 標籤的形式發出產物。串流解析器 (artifact/parser.ts)能夠安全處理分塊邊界,並讀取以下三個屬性:
# Stripe — Company Brief
## Business model
Stripe runs payment infrastructure as a service...
</artifact>
可辨識的屬性是 title、type 和 language。 聊天串流標籤上沒有 id 或 format 屬性,解析器會忽略其他任何屬性。串流結束時若仍有未閉合的標籤,會以字面文字呈現, 而不會被靜默捨棄。
選擇合適的格式
| 輸出是…… | 使用 |
|---|---|
| 給人閱讀的文件 | markdown |
| 給其他機器消費的資料 | json 或 csv |
| 頁面或儀表板 | 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 產物可以容納一棵檔案樹,當應用生成的是小型專案而非單一檔案時非常實用:
<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 中。
在階段之間串接產物
每個階段的產物都會作為命名參照自動暴露給下一個階段。接收階段的目標可以直接參照它:
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}/ 前綴; 某個階段歷史上產生過的所有產物(跨同一應用的多次執行)都可以透過列出 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 | 預設副檔名 |
|---|---|---|
markdown | text/markdown | md |
json | application/json | json |
html | text/html | html |
csv | text/csv | csv |
code | text/plain | def 的 language(例如 python、ts) |
image | application/octet-stream | txt(儲存時可覆寫) |
file 及其他一切 | text/plain | txt |
image 和 file 預設使用通用 MIME,因為在位元組被檢視之前實際內容型別 並不確定;上傳管線會在嗅探之後覆寫該列上的 mime_type。
內嵌內容 vs S3 兜底
為了便利,列的 content 欄位也會保存產物的原始內容,產物是否能從 DB 熱讀取,取決於它的建立方式以及大小。在恢復執行時, 執行器讀取前置階段產物的規則是確定的:
- 如果
app_artifact.content非空,直接使用。 - 否則如果
s3_key已設定,從 S3 下載,依 UTF-8 解碼後使用。 - 否則,以空內容繼續(這是一種可恢復的退化狀態)。
因此一個產物端到端的最壞情況只是「內容為空」;即使 S3 物件消失,列、它的中繼資料 以及它的預覽結構仍然存在。
JSON 校驗
對於 format: "json",執行器會在儲存之前嘗試對擷取到的內容執行 JSON.parse。如果解析失敗,它仍然會儲存原始內容(你不會失去智慧體的工作成果), 但會記錄一條警告。預覽會把該產物標記為「無效 JSON」,讓你一眼就能看到問題。
artifact SSE 事件
每當一個階段的輸出被持久化時,該次執行的 SSE 串流就會發出一個 artifact 事件, 其酬載是完整的 AppArtifact 列,包括內嵌的 content(如果有的話)。 訂閱方無需再發起額外請求即可渲染預覽:
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..."
}
公開分享產物
每個產物都可以透過公開連結分享,而不會曝光你工作區中的其他內容:
https://app.aitroop.net/s/<token>。DELETE /api/shares/:id。app_share 資料模型
一個分享對應 app_share 表中的一列,以一個隨機的 share_token 作為 key。 該列攜帶存取控制狀態和一個可選的存取門禁:
| 欄位 | 用途 |
|---|---|
resource_type / resource_id | 正在分享的物件,一個產物、一次執行記錄或一個應用。 |
access_mode | public(任何持有連結的人)或 token(任何用正確密鑰解鎖的人)。 |
access_token_hash | 當 access_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_token | token 門禁;渲染解鎖表單。 |
expired | expires_at 已過期。 |
over_limit | view_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 中列出多個條目:
{ "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,重新儲存即可。