← 提示词库 Meta/muse-agent/skills/spaces/ts-runtime/docs/vertical-tool-schemas.md 原文 md
🌐 中英双语对照

ctx.tool vertical schemas / ctx.tool 垂直领域 schema

Source of truth:

事实来源(source of truth):

Landing now vs. follow-up / 已落地与后续事项

This documents what is landed and mapped today:

本文记录的是今日已落地并完成映射的内容:

Design principles that still hold:

仍然成立的设计原则:

  1. Structure lives in vertical_data. The typed contract below is what we map
    out of MASE's per-result vertical_data payload. Display text (summary,
    excerpt) is display-only — do not parse it; read the structured fields.
    结构位于 vertical_data。 下面的类型化契约就是我们从 MASE 每条结果的 vertical_data 载荷映射出的内容。展示文本(summary、excerpt)仅用于展示——不要解析它;读取结构化字段。
  2. Every observation is nullable/optional. MASE may omit a field or surface it
    only in text, so the builders degrade missing data to null and emit a
    schema-valid stub on an empty payload rather than throwing.
    每个观测值都可空/可选。 MASE 可能省略某字段或只在文本中出现,因此构建器把缺失数据降级为 null,在载荷为空时发出 schema 合法的存根而不是抛错。
  3. Caps live in the contract. Heavy lists carry a .max(...) ceiling
    (forecast_hourly ≤48, finance history.points ≤400); the builders slice to
    the cap so a large upstream series never trips .parse().
    上限写在契约里。 重量级列表带有 .max(...) 上限(forecast_hourly ≤48,金融 history.points ≤400);构建器按上限切片,使大的上游序列永远不会让 .parse() 失败。

【评论】“全部字段可空 + 空载荷也发合法存根 + 大列表封顶”三者共同构成防御性 schema 设计:在上游字段不可信且随时缺失的前提下,保证下游解析既不抛错也不因超量数据而失败。

Request knobs not yet plumbed to the backend / 尚未接通后端的请求参数

ToolSearchOptions.until, ToolWeatherOptions.hourly_hours, and the
ToolFinanceOptions.interval selector are not backend request fields today;
the backend returns its full forecast or candle sets. Both runtimes apply the
model-selected variants client-side: hourly_hours caps the hourly forecast,
interval selects one candle set, and since/until bound finance history.

ToolSearchOptions.until、ToolWeatherOptions.hourly_hours 和 ToolFinanceOptions.interval 选择器目前都不是后端请求字段;后端返回其完整的预报或 K 线集合。两个运行时都在客户端应用模型选定的变体:hourly_hours 限制每小时预报的点数,interval 选择一组 K 线,since/until 界定金融历史的范围。


1. Weather / 1. 天气

Request / 请求

export interface ToolWeatherOptions extends ToolSearchOptions {
  /** Cap the upstream hourly forecast series at this many points, ≤48. */
  readonly hourly_hours?: number;
}

Result (TOOL_WEATHER_RESULT_SCHEMA) / 结果(TOOL_WEATHER_RESULT_SCHEMA)

location, summary, conditions, forecast_days, forecast_hourly?,
alerts?, sources.

即 location(位置)、summary(摘要)、conditions(当前天气)、forecast_days(每日预报)、forecast_hourly?(每小时预报,可选)、alerts?(预警,可选)、sources(来源)。

Supported (landed) fields:

已支持(已落地)字段:

Upstream vertical_data arrives with measures as { value, unit } objects
(temperature, wind_speed, feels_like, precipitation_*, daily high/low);
humidity, uv_index, air_quality_index are bare numbers;
air_quality_description, sunrise, sunset are strings.

上游 vertical_data 的度量值以 { value, unit } 对象形式到达(temperature、wind_speed、feels_like、precipitation_*、每日高低温);humidity、uv_index、air_quality_index 是裸数字;air_quality_description、sunrise、sunset 是字符串。

Landed via the KES weather thrift + NLQ + MSL/WWW decoder work
(D107777759, D107957780, D107729843, D108000766); live-verified against
P2371491675. (The earlier "feels_like / precipitation are future KES work"
caveat no longer applies — they landed.)

通过 KES 天气 thrift + NLQ + MSL/WWW 解码器工作落地
(D107777759、D107957780、D107729843、D108000766);已对照
P2371491675 完成线上验证。(先前“feels_like / 降水属于 KES 后续工作”的
注意事项不再适用——它们已落地。)


2. Finance / 2. 金融

Request / 请求

export interface ToolFinanceOptions extends ToolSearchOptions {
  /** Selects which candle set maps into `instruments[].history`. Omit for quote only. */
  readonly interval?: "1m" | "30m" | "1d" | "1w" | "1mo";
  // `since`/`until` (inherited) bound the history window — see below.
}

Result (TOOL_FINANCE_RESULT_SCHEMA) / 结果(TOOL_FINANCE_RESULT_SCHEMA)

Per instruments[] entry: name, symbol, summary, price, currency,
change, change_percent, market_status, as_of, url, and an opt-in
history.

每个 instruments[] 条目包含:name(名称)、symbol(代码)、summary(摘要)、price(价格)、currency(币种)、change(涨跌额)、change_percent(涨跌幅)、market_status(市场状态)、as_of(截至时间)、url,以及可选的(opt-in)history(历史)。

Supported (landed) fields:

已支持(已落地)字段:

vd.candles is a top-level sibling of entity, keyed
{ daily, weekly, monthly, thirty_minute, one_minute }; each bar is
{ open, high, low, close, volume, timestamp } (timestamp = epoch seconds). The
interval maps 1m→one_minute, 30m→thirty_minute, 1d→daily,
1w→weekly, and 1mo→monthly. The caller selects exactly one series;
30m is not derived from 1m. For current-day/current-session charts choose
1m; choose 30m for coarser multi-day intraday charts.

vd.candles 是 entity 的顶层兄弟字段,键为 { daily, weekly, monthly, thirty_minute, one_minute };每根 K 线是 { open, high, low, close, volume, timestamp }(timestamp = epoch 秒)。interval 映射为 1m→one_minute、30m→thirty_minute、1d→daily、1w→weekly、1mo→monthly。调用者只选择一个序列;30m 不是从 1m 派生的。当日/当前交易时段图表选 1m;更粗的多日盘中图表选 30m。

market_status is not surfaced by the integration yet, so it maps to null.
Monthly candles are upstream-gated (empty today), so history for
interval: "1mo" returns empty points until that lands. Landed via the
finance NLQ + WWW decoder work (D107985840, D108000766); monthly tracked in
D107789956.

集成尚未提供 market_status,因此它映射为 null。
月线被上游门禁(当前为空),因此 interval: "1mo" 的 history 在其落地前返回空的 points。通过
金融 NLQ + WWW 解码器工作落地(D107985840、D108000766);月线在
D107789956 中跟踪。


3. Sports / 3. 体育

Landed support is the flat per-event list, now carrying the normalized
scope (sport/league/season) and explicit home/away slots that the
upstream decoder surfaces at the top level of vertical_data:

已落地的支持是扁平的逐赛事列表,现在承载上游解码器在 vertical_data 顶层提供的规范化范围(sport/league/season)和显式的 home/away 槽位:

export const TOOL_SPORTS_DATA_RESULT_SCHEMA = z.object({
  summary: z.string(), // display-only
  items: z.array(
    z.object({
      title: z.string(),
      summary: z.string(),       // display-only (short excerpt body)
      url: nullableString.optional(),
      sport: nullableString.optional(),   // normalized token, e.g. "basketball"
      league: nullableString.optional(),  // "NBA" | "NFL" | "MLB" | … (omitted when ambiguous)
      season: z.object({                  // flat label OR structured object
        label: nullableString.optional(), // e.g. "2025-26" | "2026 REG"
        year: nullableNumber.optional(),  // e.g. 2025 (coerced from int or "2026")
        type: nullableString.optional(),  // "REG" | "PST"
        name: nullableString.optional(),  // "Regular Season" | "World Cup 2026"
        start_date: nullableString.optional(), // YYYY-MM-DD when provided
        end_date: nullableString.optional(),
      }).nullable().optional(),
      teams: z.array(z.string()).optional(), // order is not a home/away signal
      home: nullableString.optional(),    // home team name (when split upstream)
      away: nullableString.optional(),    // away team name (when split upstream)
      score: nullableString.optional(),      // "home-away", e.g. "90-94" (from home/away.score)
      status: nullableString.optional(),     // "closed" | "inprogress" | "scheduled" | …
      starts_at: nullableString.optional(),  // ISO 8601 UTC (legacy payloads only — see note)
      player_statistics: z.array(z.object({  // per-player, universal across sports
        player: z.string(),
        team: nullableString.optional(),
        position: nullableString.optional(),
        stats: z.record(z.string(), z.union([z.number(), z.string()])),
      })).optional(),                        // absent/empty when the feed omits it
      team_statistics: z.array(z.object({
        team: z.string(),
        qualifier: nullableString.optional(),
        stats: z.record(z.string(), z.union([z.number(), z.string()])),
      })).optional(),
    }),
  ),
  sources: z.array(toolSourceSchema),
});

Mapping notes (worker/src/web_search.ts):

映射说明(worker/src/web_search.ts):

Payload trim (D108695383): the decoder no longer emits the verbatim
per-event summary (a ~24 KB blob) or the raw event.attributes map; the
consumer reads only the small parsed fields. The item summary is retained but
now sources the short excerpt body (the giant blob is gone); the redundant
player_statistics_text raw fallback is dropped.

载荷精简(D108695383): 解码器不再发出逐字的逐赛事 summary(约 24 KB 的大块)或原始的 event.attributes 映射;消费方只读取较小的已解析字段。条目的 summary 保留,但现在来源于较短的 excerpt 正文(大块已移除);冗余的 player_statistics_text 原始回退被删除。

Deferred (not in this change): a sport-discriminated games union (per-sport
period/score models) and canonical (cross-sport normalized) stat keys — today
stat keys are kept verbatim from the feed. These remain a future follow-up.

延后(不在本次变更中): 按运动区分的 games 联合类型(每种运动的节次/比分模型)和规范化的(跨运动统一的)统计键——目前统计键按数据源原样保留。这些仍是未来的后续工作。


4. Web search (web_search; web-only, no vertical_data) / 4. Web 搜索(web_search;仅 web,无 vertical_data)

web_search(query) is a general web search run in the action — the exception to
the "structure lives in vertical_data" principle: it sends no vertical
(empty verticals), the same plain search the agent's browser_search runs by
default, and MASE returns no vertical_data for plain web results. So
buildWebSearchResult maps summary.top[] web entries directly into a typed
results[] (it does not use selectByVertical, which keys off
vertical_data).

web_search(query) 是在 action 中运行的通用 web 搜索——是“结构位于 vertical_data”原则的例外:它发送无垂直领域的请求(verticals 为空),与代理的 browser_search 默认运行的普通搜索相同,而 MASE 对普通 web 结果返回没有 vertical_data。因此 buildWebSearchResult 把 summary.top[] 的 web 条目直接映射进类型化的 results[](它不使用以 vertical_data 为键的 selectByVertical)。

Result (TOOL_WEB_SEARCH_RESULT_SCHEMA) / 结果(TOOL_WEB_SEARCH_RESULT_SCHEMA)

results[], capped at 20 in upstream rank order, each:

results[],按上游排名顺序上限 20 条,每条包含:

The result's vertical field is "" (web_search pins no vertical).

结果的 vertical 字段为 ""(web_search 不固定任何垂直领域)。

Routing / 路由

web_search runs in the action — the same search the agent's browser search
runs — and is the path for any web query on any subject. Summarize results
with ctx.inference.complete when you need a narrative. For a stock
quote/price/history use finance_ticker; for scores/schedules/stats use
sports_data.
Don't spawn a Space task just to search — spawnTask runs this same search.
Reserve a task for what the agent loop adds beyond search: opening and reading
full pages (browser.open), browsing across several sites, multi-step research,
or other agent tools.

web_search 在 action 中运行——与代理的浏览器搜索运行的是同一个搜索——是任何主题的任何 web 查询的路径。需要叙述性内容时,用 ctx.inference.complete 汇总结果。股票报价/价格/历史用 finance_ticker;比分/赛程/统计用 sports_data。
不要只为搜索而生成一个 Space 任务——spawnTask 运行的就是这个搜索。
任务要留给代理循环在搜索之外增加的能力:打开并阅读完整页面(browser.open)、跨多个站点浏览、多步骤研究,或其他代理工具。