跳至主要内容
System One

把截图变成 Agent 能直接用的类型化 JSON

![报错截图经 Clairic 转成类型化 JSON 字段](TODO-IMG: 封面。左:模拟的 App 报错截图;箭头;右:product / error_time / impact 三行 JSON。设计制作,仅用模拟数据。)

为什么 Agent 需要结构化输入

截图对程序来说只是一堆像素,路由逻辑没法直接拿它写 if。Agent 真正要的是产品名、时间、时长这类有明确类型的值。

Clairic 模型页里的「Agent 前置步骤」说的就是这件事:先把关键值结构化,再让 Agent 根据这些值做路由或判断。

定义字段(defineFields)

用 defineFields 加字段工厂函数描述你要的东西。本例用到 textField、dateTimeField 和 durationField({ unit: 'minutes' }),每个字段指什么,写在 instructions 里。

字段名需符合 [A-Za-z_][A-Za-z0-9_.-]{0,63}。

字段类型含义
producttext截图里报错的产品名
error_timedatetime报错发生的日期时间
impactduration(分钟)影响持续了多久

发送图片(ImageInput,state 留空)

ImageInput 有两种写法:{ mediaType, base64 },或者 base64 data URL。传了图片,state 就可以是 ''。远程图片 URL 会被拒绝,所以要先读文件再内嵌。

安装 SDK:

bashnpm install @system-one-ai/core@0.8.0 @system-one-ai/adapter-system-one@0.7.0 @system-one-ai/transport-fetch@0.8.0

保存为 screenshot-to-json.mts,在 .env 里设置 SYSTEM_ONE_API_KEY:

tsimport { readFile } from 'node:fs/promises';
import {
  createSystemOne, defineFields, textField, dateTimeField, durationField,
  type ImageInput,
} from '@system-one-ai/core';
import { systemOneAdapter } from '@system-one-ai/adapter-system-one';
import { createFetchTransport } from '@system-one-ai/transport-fetch';

const apiKey = process.env.SYSTEM_ONE_API_KEY;
const imagePath = process.argv[2];
if (!apiKey || !imagePath) throw new Error('Set SYSTEM_ONE_API_KEY and supply a PNG path.');

const images: readonly ImageInput[] = [{
  mediaType: 'image/png',
  base64: (await readFile(imagePath)).toString('base64'),
}];

const client = createSystemOne({
  adapter: systemOneAdapter,
  transport: createFetchTransport(),
  apiKey,
  timeoutMs: 30_000,
  maxRetries: 0,
});

const fields = defineFields({
  product: textField({ instructions: 'The product or service named in the error screenshot.' }),
  error_time: dateTimeField({ instructions: 'When the error happened, as shown in the screenshot.' }),
  impact: durationField({ unit: 'minutes', instructions: 'How long the problem has lasted.' }),
});

const result = await client.extract({
  model: 'clairic',
  state: '',
  images,
  fields,
});

console.log({
  model: result.model,
  product: result.answers.product.value,
  errorTime: result.answers.error_time.value,
  impactMinutes: result.answers.impact.value,
  impactPNull: result.answers.impact.pNull,
  truncated: result.truncated,
  usage: result.usage,
});

运行:node --env-file=.env screenshot-to-json.mts ./error.png。注意每运行一次,就会产生一次计费请求。

![作为 Clairic 图片输入的示例报错截图](TODO-IMG: 模拟报错截图,有产品名和时间,不含影响时长。设计制作。)

查看 Clairic SDK 文档

读结果:value、confidence、pNull、span

只有 value 是一定会返回的。标量字段在模型给出时,还会带 guess、confidence 和 pNull。pNull 超过 null 阈值(默认 0.5)时,value 为 null,guess 里则可能还有值。

输出大致长这样(示意,不是真实结果):

json{
  "model": "clairic",
  "product": "<product name from the screenshot>",
  "errorTime": "<datetime string>",
  "impactMinutes": null,
  "impactPNull": "<0–1, above the null threshold>",
  "truncated": false,
  "usage": { "inputTokens": "<n>", "outputTokens": "<n>", "totalTokens": "<n>" }
}

示例截图里没写影响了多久,所以 impact 返回 null,而不是随便猜个数(guess 里可能仍有值)。建议给 confidence、pNull 设好阈值,达不到的转人工。result.model 可能和你请求的模型不同,两个都记下来;truncated 一定是布尔值,usage 里是 token 用量。

![extract() 的终端输出,显示每个字段的 value 和 pNull](TODO-IMG: 真实运行 screenshot-to-json.mts 的终端截图,API key 打码为 sk-****。)

返回键何时出现含义
value总会有类型化的值,或 null
guess / confidence / pNull标量字段,模型返回时猜测值、置信度、输入确定不了该值的概率
spantext 字段,有 state 文本时该值在 state 里的起止字符位置
mean / median / p10 / p90 / pBelow / pAbove仅 estimate、forecast估计值的分布

串联 Jev、Clef 做决策

模型页里的「工单信息整理」就是这个思路:Clairic 取出产品名、报错时间、影响时长,再交给 Jev 或 Clef 判断分给哪个团队。接在上面的代码后面:

tsimport { choice } from '@system-one-ai/core';

const decision = await client.evaluate({
  model: 'jev',
  state: {
    product: result.answers.product.value,
    error_time: result.answers.error_time.value,
    impact_minutes: result.answers.impact.value,
  },
  questions: {
    team: choice('Choose the team that should handle this error report.', {
      billing: 'Payments and refunds',
      support: 'Technical issues',
      review: 'More information is needed',
    }),
  },
});

console.log(decision.answers.team.choice);

extract() 不传 questions,evaluate() 不传 fields。决策这一步拿到的是结构化的值,而不是原图。这个模式的完整说明见先抽取再决策,决策模型见 Jev。

![从截图到 Clairic 字段再到 Jev 分派决策的流程](TODO-IMG: 流程图:截图 → Clairic extract() → 类型化字段 → Jev evaluate() → 团队。设计制作。)

图片限制

限制值
每次请求图片数最多 4 张
格式PNG、JPEG、WebP
单张大小4MB
图片合计8MB
整个 JSON 请求体不超过 13MB
传法只能 base64,不收远程 URL

Clairic 按输入大小每次请求收一次费,不按字段数计,费率见 Clairic 模型页。

获取 API Key

获取 API Key

核验日期:2026-10-22

常见问题

Clairic 支持哪些图片格式?

PNG、JPEG 和 WebP,每次请求最多 4 张,单张不超过 4MB,合计不超过 8MB。

可以传图片 URL 吗?

不行。图片只能以 base64 内嵌,写成 data URL 或 { mediaType, base64 },远程 URL 会被拒绝。

读不出来时返回什么?

pNull 超过 null 阈值(默认 0.5)时,该字段的 value 返回 null。guess 里可能还有值,你可以决定要不要展示,或转人工确认。

一次能传几张图?

一次 extract() 请求最多 4 张。有图片时,state 可以是空字符串。

和直接让大模型输出 JSON 有什么区别?

Clairic 不生成文本,而是每个字段返回一个类型化的值;输入确定不了的值返回 null,标量字段还带 confidence 和 pNull。详见《LLM 抽取 vs Clairic》。

抽取的字段越多越贵吗?

不会。Clairic 按输入大小每次请求收一次费,不按字段数计。费率见 Clairic 模型页。