Cited briefs and structured extraction
@vid2context/insights turns one or many videos into a cited brief (Markdown plus JSON) or into structured JSON that matches a template or your own JSON Schema. Every claim points at a video, a timestamp and a quote, and the quote was checked against the transcript before the claim was allowed in.
Who writes the brief
There are two paths. Both use the same sources, citation rule and templates.
| Agent-side (default) | Server-side | |
|---|---|---|
| Who synthesizes | The calling agent, with its own model | vid2context, through the core LLM chain |
| Cost | Nothing extra | Free-first: local LLM, then free tiers with your key, then keyless hosts; paid only when its key is set |
| API | briefPrompt(contexts, opts), extractPrompt(contexts, opts), briefInstructions(), buyerGuideInstructions(product) |
buildBrief(contexts, opts, deps), extract(contexts, opts, deps) |
| Surfaces | MCP prompts brief and buyer_guide (item 44 imports the text) |
MCP tools build_brief, extract; HTTP POST /briefs, POST /extract |
| Use when | The sources fit the agent's context (about 12k tokens per hour of speech) | A collection is too large for the agent, or the agent's model is weak |
briefPrompt returns chat messages: a system prompt, then the instructions, the citation rule ([Video title @ m:ss](timestamp link)), the agreement rules (consensus needs two or more videos, disagreements cite both sides, single-source claims are labelled) and the sources. Each source is a fenced untrusted block (fenceUntrusted from core) holding its title, channel, length, a timestamp link template and timestamped transcript lines. With maxTokens, sources that do not fit are listed by id with a note to fetch them with get_outline, get_section or search_transcript, never dropped silently.
Server-side: single pass or map-reduce
import { buildBrief } from '@vid2context/insights';
const brief = await buildBrief(contexts, { question: 'Which G-Wagon should I buy?', template: 'buyer-guide' }, { registry, ctx });
console.log(brief.markdown);- Budget. The source budget per call comes from the first rung the chain would try: 75 percent of its
contextTokens, minus the answer and the prompt, capped at 24k tokens (keyless hosts are slow on huge prompts).maxChunkTokenslowers it. - Single pass. When every source fits, one call returns the claims and their organization into topics and points.
- Map. Otherwise each video is cut at line boundaries (lines are about 10 s of segments) into chunks that fit, and each chunk becomes claims JSON:
{ claim, quote, videoId, startSec, topic, stance? }. - Verify. Every claim's quote is checked against its transcript (rules below). Only verified claims go on.
- Reduce. Claims are grouped by normalized topic (lowercase, stop words and simple plurals folded) and packed into batches; an LLM merges topics that mean the same thing and groups claims into points with
support,opposeand acountersentence. By default the reduce step uses policyquality(unless you set a policy), so it prefers the strongest rung you have. - Classify, in code, never by the model: a point is
consensuswhen its supporting claims come from two or more distinct videos and nothing opposes it,disagreementwhen claims sit on both sides,single-sourceotherwise. Verified claims that no point mentions become single-source points, so nothing verified is lost.
Calls run one at a time, so keyless lanes stay under their per-minute limits. When every rung is rate-limited, the call waits about a minute (a little over the chain's cooldown) and retries, up to rateLimitRetries times (default 3). The result records which rung served each step (rungs) and every attempt (attempts). Steps that failed without sinking the whole run are listed in notes and in the Markdown.
Citation verification
verifyCitations(claims, contexts) and verifyQuote(context, quote, startSec):
- Quote and transcript are normalized the same way: lowercase, no diacritics, no punctuation, apostrophes joined (
you'rebecomesyoure), whitespace collapsed. Auto-captions have no case or punctuation, so this is the only fair comparison. - A quote needs at least 4 words (
MIN_QUOTE_TOKENS); shorter ones match almost anywhere and prove nothing. - The window is every segment overlapping
startSecplus or minus 30 s (WINDOW_SEC), extended by the quote's own length. - Score: a span of
ceil(1.25 x quote words)transcript words slides over the window; the score is the best count of quote words found in the span (order ignored) divided by the quote's word count. A claim passes at 0.8 (THRESHOLD) or more. An exact quote scores 1.0; a quote with about one word in five changed or misheard still passes; a paraphrase in different words fails. - On a pass,
startSecis snapped to the start of the segment holding the first matched word, andendSecto the end of the segment holding the last. The link comes from coretimestampUrl. - On a fail, the whole transcript is searched. A quote found at another time is rejected as
quote-at-other-time(withfoundAtSec), because the model mixed up its sources; otherwisequote-not-found. Other reasons:unknown-video,no-quote,quote-too-short,bad-timestamp.
Unverified claims never appear as cited. They are listed in unverified and in the brief's appendix with the reason.
Templates
| Id | What it extracts |
|---|---|
brief |
Title, summary, and cited points by topic |
buyer-guide |
Product, summary, models and trims (with years and verdict), model years to seek, consider or avoid, prices, known problems (what they affect, severity), what to avoid, mileage advice, questions for the seller; each item cited |
tutorial-steps |
Goal, prerequisites, ordered steps with exact values, pitfalls |
comparison |
Columns (criteria), rows (options) with cited cells, and a cited verdict |
summary |
One paragraph and cited key points (the extract default) |
Each template is a TypeScript module in packages/insights/src/templates/ holding its Markdown prompt text (instructions), its preferred topics and its JSON Schema. They are modules rather than .md files read at runtime so the package runs unchanged on Workers and in the browser.
In template results, each item has sources: a list of { videoId, startSec, quote }. After verification each one becomes a full citation (videoId, title, snapped startSec, endSec, url, quote); failed ones are removed and listed in unverified with their JSON pointer, and an item whose citations all failed is removed with them.
Custom JSON Schemas
extract(contexts, { schema }) accepts this subset: type (one or a list), properties, required, additionalProperties, items, enum, const, minItems, maxItems, uniqueItems, minLength, maxLength, pattern, minimum, maximum, exclusiveMinimum, exclusiveMaximum, plus annotations (title, description, default, examples, format, ...). Anything else ($ref, anyOf, oneOf, allOf, not, if, patternProperties, tuple items) fails at once with bad-request and a hint. The validator is built in (no ajv), so it runs everywhere.
The model answers { data, citations }: data matches your schema and each citation names the JSON pointer it supports. The answer is validated; on failure exactly one repair call sends the errors back. If the repaired answer still fails, the call fails with upstream and a hint. Your data is returned as the model wrote it (a custom schema cannot be pruned safely), so the result adds uncited: the pointers of values no verified citation covers. Treat those as unsupported by the videos.
HTTP and MCP
insightsRoutes(deps)from@vid2context/insights/httpservesPOST /briefsandPOST /extract(mounted under/api/v1by the server). Withdeps.queue(a@vid2context/jobsqueue) they answer202 Jobof kindbrieforextractand register those kinds on the queue; without one they answer200with the result. Bodies are validated before anything is queued. Every runner of a deployment must callregisterInsightsJobs(queue).registerInsightsTools(server, deps)from@vid2context/insights/mcpregistersbuild_brief(Markdown by default,format: "json"for the fullBrief) andextract.
Walkthrough: which G-Wagon to buy
WALKTHROUGH