跳到正文
DEV Community· LachlanHolm6518·· 3 小時前AI 評分61

用 JSON Schema 建立媒體檔案庫的結構化摘要:Node.js GPT 端點實作

Use JSON Schema for Structured Summarization: A Node.js GPT Endpoint for Media Archives

AI 導讀

這份 Node.js 教學示範以 JSON Schema 約束媒體檔案庫摘要服務的模型輸出,並在寫入資料前驗證結構與內容語義。摘要物件包含 sourceId、abstract、keyPoints 和 confidence;結構驗證不代表內容有據,應用程式還要核對來源識別碼及證據。文章並建議分開監測請求、結構驗證與證據檢查的失敗,避免預設記錄逐字稿,並將低信心結果送交審核。

正文

A media archive has a stricter constraint than ordinary summarization: the result must help a person and remain valid enough for the next system to trust. I use a JSON Schema contract at the model boundary, then validate meaning in application code. The model produces a candidate. The runtime decides whether it is acceptable.

The before-and-after model is small:

  • Before: text goes in, an attractive paragraph comes out, and downstream code guesses what it means.
  • After: text goes in, a typed summary candidate comes out, the candidate passes schema validation, and an explicit policy decides whether to publish, retry, or quarantine it.

Keep the boundary boring.

No guesswork.

That separation matters for a private media knowledge base. A missing topics array is easy to detect. A confident summary that mixes two broadcasts is much harder, so the contract needs provenance fields and observable rejection reasons.

How should a Node.js service use JSON Schema for structured summarization?

Start with fields that support a real archive workflow. For one transcript, I want a concise abstract, a bounded list of key points, the source identifier, and an uncertainty signal. The schema below is deliberately boring. Boring contracts are easy to test.

const summarySchema = {
  type: "object",
  additionalProperties: false,
  required: ["sourceId", "abstract", "keyPoints", "confidence"],
  properties: {
    sourceId: { type: "string", minLength: 1 },
    abstract: { type: "string", minLength: 40, maxLength: 800 },
    keyPoints: {
      type: "array",
      minItems: 2,
      maxItems: 6,
      items: { type: "string", minLength: 10, maxLength: 240 }
    },
    confidence: { enum: ["high", "medium", "low"] }
  }
};

This is a shape contract, not a truth detector. sourceId can still be wrong. An abstract can still claim that a guest said something that appears nowhere in the transcript. Keep that distinction visible in the design and in the metrics.

Why is schema-valid output still unsafe?

Because JSON Schema checks structure, not evidence. A structural failure means the response cannot be consumed safely. A semantic failure means it can be consumed, but should not be trusted. Mixing them creates noisy alerts and encourages someone to disable useful ones.

Here is a compact Node.js boundary using a generic GPT-compatible endpoint. The endpoint name is configuration. The validation policy belongs to your application.

type Summary = {
  sourceId: string;
  abstract: string;
  keyPoints: string[];
  confidence: "high" | "medium" | "low";
};

type ModelResponse = { output_text?: string };

async function summarizeTranscript(
  transcript: string, sourceId: string, endpoint: string, apiKey: string
): Promise<Summary> {
  const response = await fetch(endpoint, {
    method: "POST",
    headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
    body: JSON.stringify({
      input: [
        { role: "system", content: "Summarize only the supplied transcript and return one JSON object matching the schema." },
        { role: "user", content: `sourceId: ${sourceId}\n\n${transcript}` }
      ],
      response_format: { type: "json_schema", json_schema: { name: "media_summary", strict: true, schema: summarySchema } }
    })
  });

  if (!response.ok) throw new Error(`model_request_failed:${response.status}`);
  const payload = (await response.json()) as ModelResponse;
  if (!payload.output_text) throw new Error("missing_structured_output");
  const candidate = JSON.parse(payload.output_text) as Summary;
  assertSummary(candidate, sourceId, transcript);
  return candidate;
}

The hard edge is intentional: parsing and validation happen before persistence. In production, use a standards-compliant JSON Schema validator rather than relying on the TypeScript cast. A cast changes the compiler's view; it does not inspect runtime data.

I also give each request a stable idempotency key. A timeout can happen after the endpoint accepted the request; retrying without that key may create duplicate work or duplicate review items.

The semantic check can begin with cheap invariants. Require the returned source identifier to equal the requested identifier. Reject an empty transcript before calling the model. For stronger evidence, compare extracted names or timestamps against retrieved chunks, and route low-confidence items to review. A string match narrows one known failure mode; it does not prove factual correctness.

How should failures appear in observability?

Think of the pipeline as a three-lane diagram in words: request lane, contract lane, evidence lane. The request lane records latency and transport failures. The contract lane records parse and schema failures. The evidence lane records grounding or policy failures. Each lane has a different owner and a different next action.

A useful event is small and stable: summary_validation_failed, sourceId as a non-sensitive identifier, stage as parse, schema, or evidence, and a bounded reasonCode. Avoid logging the transcript by default. Private media text can contain personal data, licensing-sensitive material, or unreleased reporting.

Metrics should answer operational questions. Track the rate of each rejection stage, the fraction routed to review, model request latency, and the age of the oldest review item. Alert on a change in structural rejection rate after a prompt or schema deployment. A rising evidence rejection rate may point to retrieval drift instead. Those are different investigations.

A dashboard panel that says summary_failed: 4.2% tells you almost nothing. Three panels saying parse: 0.1%, schema: 0.7%, and evidence: 3.4% tell you where the contract is doing its job and where the content path needs attention. These numbers are illustrative thresholds, not a benchmark; choose limits from your own baseline.

For a 429 or another transient rate limit, retry with bounded exponential backoff and jitter. Do not retry validation failures as if they were network failures. The former needs a contract or prompt decision; the latter may need a later attempt. In a queue-backed importer, the retry record should retain the idempotency key, attempt count, and next eligible time; otherwise a worker restart can turn one transcript into several review items.

What do the two common objections miss?

“Strict output will make the summary less natural.” Sometimes it will. That is an acceptable trade when another service needs predictable fields. Keep prose quality inside abstract, and keep machine-facing fields narrow. If editors want richer language, render a human view from the validated object instead of letting downstream code parse prose.

“Validation means the answer is correct.” It does not. Schema validation is a gate for shape and selected invariants. Retrieval quality, prompt injection resistance, source permissions, and human review remain separate controls. OWASP lists prompt injection and sensitive information disclosure among risks needing application-level treatment, so a valid object should not bypass access checks or publication policy.

The limitation is real: this contract is a poor fit for exploratory notes where fields change every hour. A looser text workflow may be better there. The trade-off is worthwhile for archive search and publication because those consumers need stable boundaries.

The decision rule is straightforward: use a JSON Schema contract when a summary crosses a service boundary or drives a workflow; add semantic checks when a wrong-but-valid field can harm archive search or publication; keep a review path when the evidence cannot be judged automatically. That is the operational boundary.

Sources

來源:DEV Community · dev.to