From ba8d33818ec4f49641684900579d137806d13d16 Mon Sep 17 00:00:00 2001 From: Richard Oliver Bray Date: Thu, 24 Sep 2026 18:11:39 +0100 Subject: [PATCH 1/9] docs: add Jev guide and Jev examples in use cases Adds a Firecrawl + Jev (TypeSafe) guide under LLM SDKs and Frameworks with four examples: classify a page, qualify leads by confidence, tag a crawl, and triage change-tracking diffs. Links it from the lead enrichment and competitive intelligence use cases. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../llm-sdks-and-frameworks/jev.mdx | 194 ++++++++++++++++++ docs.json | 2 + use-cases/competitive-intelligence.mdx | 6 +- use-cases/lead-enrichment.mdx | 3 + 4 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 developer-guides/llm-sdks-and-frameworks/jev.mdx diff --git a/developer-guides/llm-sdks-and-frameworks/jev.mdx b/developer-guides/llm-sdks-and-frameworks/jev.mdx new file mode 100644 index 000000000..8c4241e83 --- /dev/null +++ b/developer-guides/llm-sdks-and-frameworks/jev.mdx @@ -0,0 +1,194 @@ +--- +title: "Jev" +description: "Use Firecrawl with TypeSafe's Jev to make fast, structured decisions on web data" +--- + +[Jev](https://typesafe.ai/blog/introducing-system-one-models-and-jev) is a decision model from TypeSafe. It doesn't generate text. You send it a `state` and a set of typed questions, and it returns structured answers with probabilities, usually in under half a second. Firecrawl turns the web into that state, and Jev decides what to do with it: classify a page, qualify a lead, or flag a change worth alerting on. + +Jev answers three kinds of questions, all evaluated in parallel in a single call: + +| Question | Asks | Returns | +| --- | --- | --- | +| `choice` | Which option fits best? | `choice`, `probabilities`, `confidence` | +| `score` | Where does this land on a rubric? | `score`, `probabilities`, `confidence` | +| `noul` | Is this statement true? | `noul`, a probability from 0 to 1 | + + + Jev is in early access. Request access at [typesafe.ai](https://typesafe.ai/), then create an API key in the [TypeSafe console](https://console.typesafe.ai/keys). + + +## Setup + +```bash +npm install firecrawl @typesafe-ai/sdk +``` + +Create `.env` file: +```bash +FIRECRAWL_API_KEY=your_firecrawl_key +TYPESAFE_API_KEY=your_typesafe_key +``` + +> **Note:** The TypeSafe SDK requires Node 20 or newer. + +## Scrape + Classify + +This example scrapes a page and asks Jev three questions about it at once. + +```typescript +import { Firecrawl } from 'firecrawl'; +import { choice, noul, score, TypeSafeClient } from '@typesafe-ai/sdk'; + +const firecrawl = new Firecrawl({ apiKey: process.env.FIRECRAWL_API_KEY }); +const typesafe = new TypeSafeClient(); + +const url = 'https://www.firecrawl.dev/pricing'; +const page = await firecrawl.scrape(url, { formats: ['markdown'] }); + +const { answers } = await typesafe.systemOne({ + state: { url, markdown: page.markdown ?? '' }, + questions: { + pageType: choice('What kind of page is this?', { + pricing: 'Plans, prices, or billing details', + product: 'Describes a product or feature', + blog: 'An article or announcement', + docs: 'Technical documentation or an API reference', + other: null + }), + hasFreeTier: noul('The page offers a free plan or free credits'), + audience: score('How technical the intended reader is', [ + 'A general audience', + 'Readers with some technical background', + 'Software engineers' + ]) + } +}); + +console.log('Page type:', answers.pageType.choice, answers.pageType.confidence); +console.log('Free tier:', answers.hasFreeTier.noul); +console.log('Audience:', answers.audience.score); +``` + +## Qualify Leads + +This example scrapes a list of company homepages, asks Jev whether each company fits your target market, and routes it by confidence. Confident matches go straight to your CRM, and anything uncertain goes to a person. + +```typescript +import { Firecrawl } from 'firecrawl'; +import { choice, noul, TypeSafeClient } from '@typesafe-ai/sdk'; + +const firecrawl = new Firecrawl({ apiKey: process.env.FIRECRAWL_API_KEY }); +const typesafe = new TypeSafeClient(); + +const companies = ['https://ramp.com', 'https://mercury.com', 'https://www.jpmorganchase.com']; +const { data: pages } = await firecrawl.batchScrape(companies, { options: { formats: ['markdown'] } }); + +const questions = { + isFintech: noul('The company builds financial technology such as payments, banking, lending, or spend management software'), + sellsToBusinesses: noul('The company primarily sells to businesses rather than consumers'), + stage: choice('How established the company appears to be', { + startup: 'Early stage, with a small team or a single product', + growth: 'Several products or a sizable customer base', + enterprise: 'A large, long-established company' + }) +}; + +const results = await Promise.all(pages.map(async (page) => { + const { answers } = await typesafe.systemOne({ + // Jev accepts up to 32k tokens of state, so long pages are trimmed + state: { url: page.metadata?.sourceURL ?? '', homepage: page.markdown?.slice(0, 50_000) ?? '' }, + questions + }); + + const fit = answers.isFintech.noul * answers.sellsToBusinesses.noul; + const { choice: stage, confidence } = answers.stage; + + let route = 'skip'; + if (confidence < 0.6) route = 'review'; + else if (fit > 0.8 && stage !== 'enterprise') route = 'crm'; + else if (fit > 0.4) route = 'review'; + + return { url: page.metadata?.sourceURL, fit, stage, confidence, route }; +})); + +console.table(results); +``` + +Each question answers one narrow thing, and the weighting lives in your code. To change what counts as a good lead, adjust a threshold instead of rewriting a prompt. + +## Tag a Crawl + +This example crawls a docs site and labels every page. Because Jev charges only for input tokens and answers in parallel, tagging hundreds of pages takes seconds. + +```typescript +import { Firecrawl } from 'firecrawl'; +import { choice, TypeSafeClient } from '@typesafe-ai/sdk'; + +const firecrawl = new Firecrawl({ apiKey: process.env.FIRECRAWL_API_KEY }); +const typesafe = new TypeSafeClient(); + +const crawl = await firecrawl.crawl('https://docs.firecrawl.dev', { + limit: 50, + scrapeOptions: { formats: ['markdown'] } +}); + +const tagged = await Promise.all(crawl.data.map(async (page) => { + const { answers } = await typesafe.systemOne({ + state: { title: page.metadata?.title ?? '', markdown: page.markdown?.slice(0, 50_000) ?? '' }, + questions: { + kind: choice('What kind of documentation page is this?', { + tutorial: 'Walks the reader through building something step by step', + howTo: 'Explains how to accomplish one specific task', + reference: 'Lists parameters, endpoints, or options', + concept: 'Explains an idea or how something works' + }) + } + }); + return { url: page.metadata?.sourceURL, kind: answers.kind.choice }; +})); + +console.table(tagged); +``` + +## Triage Page Changes + +This example combines [change tracking](/features/change-tracking) with Jev to decide whether a change on a competitor's page is worth an alert. Firecrawl finds what changed, and Jev rates how much it matters. + +```typescript +import { Firecrawl } from 'firecrawl'; +import { score, TypeSafeClient } from '@typesafe-ai/sdk'; + +const firecrawl = new Firecrawl({ apiKey: process.env.FIRECRAWL_API_KEY }); +const typesafe = new TypeSafeClient(); + +const url = 'https://example.com/pricing'; +const page = await firecrawl.scrape(url, { + formats: ['markdown', { type: 'changeTracking', modes: ['git-diff'] }] +}); + +const diff = page.changeTracking?.diff as { text: string } | undefined; + +if (page.changeTracking?.changeStatus === 'changed' && diff) { + const { answers } = await typesafe.systemOne({ + state: { url, diff: diff.text }, + questions: { + impact: score('How much this change matters to a competitor', [ + 'Cosmetic: layout, typos, dates, or reordered content', + 'Minor: reworded copy or small feature mentions', + 'Material: prices, plans, limits, or a new product' + ]) + } + }); + + if (answers.impact.score >= 1.5) { + console.log('Alert:', url, answers.impact.score); + } +} +``` + +The first scrape of a URL stores a baseline and returns `changeStatus: "new"`. Run the script on a schedule, and later runs compare against that baseline. + +## Learn More + +- [TypeSafe docs](https://docs.typesafe.ai/) cover each question type, confidence, and patterns such as [confidence-gated routing](https://docs.typesafe.ai/patterns/confidence-routing). +- [Jev models and limits](https://docs.typesafe.ai/models) lists pricing, rate limits, and context length. diff --git a/docs.json b/docs.json index 281946b52..d1a0cfeef 100755 --- a/docs.json +++ b/docs.json @@ -304,6 +304,7 @@ "developer-guides/llm-sdks-and-frameworks/openai", "developer-guides/llm-sdks-and-frameworks/anthropic", "developer-guides/llm-sdks-and-frameworks/gemini", + "developer-guides/llm-sdks-and-frameworks/jev", "developer-guides/llm-sdks-and-frameworks/google-adk", "developer-guides/llm-sdks-and-frameworks/vercel-ai-sdk", "developer-guides/llm-sdks-and-frameworks/langchain", @@ -659,6 +660,7 @@ "developer-guides/llm-sdks-and-frameworks/openai", "developer-guides/llm-sdks-and-frameworks/anthropic", "developer-guides/llm-sdks-and-frameworks/gemini", + "developer-guides/llm-sdks-and-frameworks/jev", "developer-guides/llm-sdks-and-frameworks/google-adk", "developer-guides/llm-sdks-and-frameworks/vercel-ai-sdk", "developer-guides/llm-sdks-and-frameworks/langchain", diff --git a/use-cases/competitive-intelligence.mdx b/use-cases/competitive-intelligence.mdx index 66f83922c..35b3e9064 100644 --- a/use-cases/competitive-intelligence.mdx +++ b/use-cases/competitive-intelligence.mdx @@ -49,6 +49,10 @@ Set up a pipeline that scrapes competitor sites on a schedule, extracts the data - **Strategy**: Positioning, target markets, pricing approaches, go-to-market - **Technical**: API changes, integrations, technology stack updates +## Filter Out Noise + +Most page changes don't matter. A new date in a footer or a reworded headline shouldn't page your team. Use [change tracking](/features/change-tracking) to get a diff of what changed, then have [Jev](/developer-guides/llm-sdks-and-frameworks/jev), a fast decision model from TypeSafe, rate how material each diff is. Alert only when a change touches prices, plans, limits, or products. See [Triage Page Changes](/developer-guides/llm-sdks-and-frameworks/jev#triage-page-changes) for a working example. + ## FAQs @@ -61,7 +65,7 @@ Set up a pipeline that scrapes competitor sites on a schedule, extracts the data - When building your monitoring system, implement filters to ignore minor changes like timestamps or dynamic content. Compare extracted data over time and use your own logic to determine what constitutes a meaningful change. + When building your monitoring system, implement filters to ignore minor changes like timestamps or dynamic content. Compare extracted data over time and use your own logic to determine what constitutes a meaningful change, or pass each diff to a decision model like [Jev](/developer-guides/llm-sdks-and-frameworks/jev) to score how much it matters. diff --git a/use-cases/lead-enrichment.mdx b/use-cases/lead-enrichment.mdx index 24c6f5cf0..b7fe7bd85 100644 --- a/use-cases/lead-enrichment.mdx +++ b/use-cases/lead-enrichment.mdx @@ -27,6 +27,9 @@ Point Firecrawl at a business directory, trade association, or conference attend ### Keep CRM data fresh automatically Firecrawl pulls information directly from live company websites instead of a static database. Your team always sees the latest company news, team changes, and growth signals. +### Qualify leads before they reach your CRM +Scraped company data still needs a judgment call: is this account in your market, and is it the right size? Pair Firecrawl with [Jev](/developer-guides/llm-sdks-and-frameworks/jev), a fast decision model from TypeSafe, to answer those questions for every company in under a second each. Jev returns a confidence score with each answer, so you can send clear matches straight to your CRM and route uncertain ones to a person for review. + ## Customer Stories From 76ffcd32354c708f17ac650e9aaffd6826e84ff6 Mon Sep 17 00:00:00 2001 From: Richard Oliver Bray Date: Thu, 24 Sep 2026 18:14:11 +0100 Subject: [PATCH 2/9] docs: trim Jev guide intro to match other LLM guides Co-Authored-By: Claude Opus 5.5 (1M context) --- .../llm-sdks-and-frameworks/jev.mdx | 31 +++---------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/developer-guides/llm-sdks-and-frameworks/jev.mdx b/developer-guides/llm-sdks-and-frameworks/jev.mdx index 8c4241e83..ccf9c2c11 100644 --- a/developer-guides/llm-sdks-and-frameworks/jev.mdx +++ b/developer-guides/llm-sdks-and-frameworks/jev.mdx @@ -3,19 +3,7 @@ title: "Jev" description: "Use Firecrawl with TypeSafe's Jev to make fast, structured decisions on web data" --- -[Jev](https://typesafe.ai/blog/introducing-system-one-models-and-jev) is a decision model from TypeSafe. It doesn't generate text. You send it a `state` and a set of typed questions, and it returns structured answers with probabilities, usually in under half a second. Firecrawl turns the web into that state, and Jev decides what to do with it: classify a page, qualify a lead, or flag a change worth alerting on. - -Jev answers three kinds of questions, all evaluated in parallel in a single call: - -| Question | Asks | Returns | -| --- | --- | --- | -| `choice` | Which option fits best? | `choice`, `probabilities`, `confidence` | -| `score` | Where does this land on a rubric? | `score`, `probabilities`, `confidence` | -| `noul` | Is this statement true? | `noul`, a probability from 0 to 1 | - - - Jev is in early access. Request access at [typesafe.ai](https://typesafe.ai/), then create an API key in the [TypeSafe console](https://console.typesafe.ai/keys). - +Integrate Firecrawl with TypeSafe's Jev to make fast, structured decisions on web data. ## Setup @@ -29,7 +17,7 @@ FIRECRAWL_API_KEY=your_firecrawl_key TYPESAFE_API_KEY=your_typesafe_key ``` -> **Note:** The TypeSafe SDK requires Node 20 or newer. +> **Note:** Jev is in early access. Request access at [typesafe.ai](https://typesafe.ai/). The TypeSafe SDK requires Node 20 or newer. ## Scrape + Classify @@ -71,7 +59,7 @@ console.log('Audience:', answers.audience.score); ## Qualify Leads -This example scrapes a list of company homepages, asks Jev whether each company fits your target market, and routes it by confidence. Confident matches go straight to your CRM, and anything uncertain goes to a person. +This example scrapes company homepages and routes each company to your CRM, a review queue, or the bin based on Jev's answers and confidence. ```typescript import { Firecrawl } from 'firecrawl'; @@ -114,11 +102,9 @@ const results = await Promise.all(pages.map(async (page) => { console.table(results); ``` -Each question answers one narrow thing, and the weighting lives in your code. To change what counts as a good lead, adjust a threshold instead of rewriting a prompt. - ## Tag a Crawl -This example crawls a docs site and labels every page. Because Jev charges only for input tokens and answers in parallel, tagging hundreds of pages takes seconds. +This example crawls a docs site and labels every page. ```typescript import { Firecrawl } from 'firecrawl'; @@ -152,7 +138,7 @@ console.table(tagged); ## Triage Page Changes -This example combines [change tracking](/features/change-tracking) with Jev to decide whether a change on a competitor's page is worth an alert. Firecrawl finds what changed, and Jev rates how much it matters. +This example uses [change tracking](/features/change-tracking) to get a page diff, then has Jev rate whether the change is worth an alert. ```typescript import { Firecrawl } from 'firecrawl'; @@ -185,10 +171,3 @@ if (page.changeTracking?.changeStatus === 'changed' && diff) { } } ``` - -The first scrape of a URL stores a baseline and returns `changeStatus: "new"`. Run the script on a schedule, and later runs compare against that baseline. - -## Learn More - -- [TypeSafe docs](https://docs.typesafe.ai/) cover each question type, confidence, and patterns such as [confidence-gated routing](https://docs.typesafe.ai/patterns/confidence-routing). -- [Jev models and limits](https://docs.typesafe.ai/models) lists pricing, rate limits, and context length. From 4dfcf75a3132c1f545e2c0e9371e1b628e350b57 Mon Sep 17 00:00:00 2001 From: Richard Oliver Bray Date: Thu, 24 Sep 2026 18:18:22 +0100 Subject: [PATCH 3/9] docs: replace Jev classify example with search + filter Co-Authored-By: Claude Opus 5.5 (1M context) --- .../llm-sdks-and-frameworks/jev.mdx | 68 +++++++++++-------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/developer-guides/llm-sdks-and-frameworks/jev.mdx b/developer-guides/llm-sdks-and-frameworks/jev.mdx index ccf9c2c11..397aacbda 100644 --- a/developer-guides/llm-sdks-and-frameworks/jev.mdx +++ b/developer-guides/llm-sdks-and-frameworks/jev.mdx @@ -19,42 +19,54 @@ TYPESAFE_API_KEY=your_typesafe_key > **Note:** Jev is in early access. Request access at [typesafe.ai](https://typesafe.ai/). The TypeSafe SDK requires Node 20 or newer. -## Scrape + Classify +## Search + Filter -This example scrapes a page and asks Jev three questions about it at once. +This example searches the web, has Jev judge each result from its title and description, and scrapes only the results worth reading. ```typescript -import { Firecrawl } from 'firecrawl'; -import { choice, noul, score, TypeSafeClient } from '@typesafe-ai/sdk'; +import { Firecrawl, type SearchResultWeb } from 'firecrawl'; +import { noul, score, TypeSafeClient } from '@typesafe-ai/sdk'; const firecrawl = new Firecrawl({ apiKey: process.env.FIRECRAWL_API_KEY }); const typesafe = new TypeSafeClient(); -const url = 'https://www.firecrawl.dev/pricing'; -const page = await firecrawl.scrape(url, { formats: ['markdown'] }); - -const { answers } = await typesafe.systemOne({ - state: { url, markdown: page.markdown ?? '' }, - questions: { - pageType: choice('What kind of page is this?', { - pricing: 'Plans, prices, or billing details', - product: 'Describes a product or feature', - blog: 'An article or announcement', - docs: 'Technical documentation or an API reference', - other: null - }), - hasFreeTier: noul('The page offers a free plan or free credits'), - audience: score('How technical the intended reader is', [ - 'A general audience', - 'Readers with some technical background', - 'Software engineers' - ]) - } -}); +const query = 'how to rotate Postgres credentials without downtime'; +const search = await firecrawl.search(query, { limit: 10 }); +const results = (search.web ?? []).filter((r): r is SearchResultWeb => 'url' in r); + +const judged = await Promise.all(results.map(async (result) => { + const { answers } = await typesafe.systemOne({ + state: { query, url: result.url, title: result.title ?? '', description: result.description ?? '' }, + questions: { + relevance: score('How well this result answers the query', [ + 'Unrelated to the query', + 'Related topic, but does not answer the query', + 'Directly answers the query' + ]), + primarySource: noul('This is official documentation, an engineering blog, or another primary source rather than an aggregator'), + marketing: noul('This is mainly a product or marketing page') + } + }); + return { + url: result.url, + relevance: answers.relevance.score, + primarySource: answers.primarySource.noul, + marketing: answers.marketing.noul + }; +})); -console.log('Page type:', answers.pageType.choice, answers.pageType.confidence); -console.log('Free tier:', answers.hasFreeTier.noul); -console.log('Audience:', answers.audience.score); +console.table(judged); + +const worthReading = judged + .filter((r) => r.relevance >= 1.5 && r.marketing < 0.5) + .sort((a, b) => b.primarySource - a.primarySource) + .slice(0, 3) + .map((r) => r.url); + +if (worthReading.length > 0) { + const { data: pages } = await firecrawl.batchScrape(worthReading, { options: { formats: ['markdown'] } }); + console.log(`Scraped ${pages.length} of ${results.length} results`); +} ``` ## Qualify Leads From 348cb8a84d667939209dd6deaf0a5868d9679a3b Mon Sep 17 00:00:00 2001 From: Richard Oliver Bray Date: Thu, 24 Sep 2026 18:28:04 +0100 Subject: [PATCH 4/9] docs: simplify search results line in Jev guide Co-Authored-By: Claude Opus 5.5 (1M context) --- developer-guides/llm-sdks-and-frameworks/jev.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/developer-guides/llm-sdks-and-frameworks/jev.mdx b/developer-guides/llm-sdks-and-frameworks/jev.mdx index 397aacbda..b9391092b 100644 --- a/developer-guides/llm-sdks-and-frameworks/jev.mdx +++ b/developer-guides/llm-sdks-and-frameworks/jev.mdx @@ -32,7 +32,8 @@ const typesafe = new TypeSafeClient(); const query = 'how to rotate Postgres credentials without downtime'; const search = await firecrawl.search(query, { limit: 10 }); -const results = (search.web ?? []).filter((r): r is SearchResultWeb => 'url' in r); +// Without scrapeOptions, search returns only the URL, title, and description of each result +const results = (search.web ?? []) as SearchResultWeb[]; const judged = await Promise.all(results.map(async (result) => { const { answers } = await typesafe.systemOne({ From 2f2d2aae8b4fd8b4ec0d7ddcff9bc9353274b5e0 Mon Sep 17 00:00:00 2001 From: Richard Oliver Bray Date: Thu, 24 Sep 2026 18:28:46 +0100 Subject: [PATCH 5/9] docs: drop comment in Jev search example Co-Authored-By: Claude Opus 5.5 (1M context) --- developer-guides/llm-sdks-and-frameworks/jev.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/developer-guides/llm-sdks-and-frameworks/jev.mdx b/developer-guides/llm-sdks-and-frameworks/jev.mdx index b9391092b..d31e1eba2 100644 --- a/developer-guides/llm-sdks-and-frameworks/jev.mdx +++ b/developer-guides/llm-sdks-and-frameworks/jev.mdx @@ -32,7 +32,6 @@ const typesafe = new TypeSafeClient(); const query = 'how to rotate Postgres credentials without downtime'; const search = await firecrawl.search(query, { limit: 10 }); -// Without scrapeOptions, search returns only the URL, title, and description of each result const results = (search.web ?? []) as SearchResultWeb[]; const judged = await Promise.all(results.map(async (result) => { From a7e0c470dfcd752e2de4c9f1b35089574c06df10 Mon Sep 17 00:00:00 2001 From: Richard Oliver Bray Date: Thu, 24 Sep 2026 18:30:00 +0100 Subject: [PATCH 6/9] docs: remove setup note from Jev guide Co-Authored-By: Claude Opus 5.5 (1M context) --- developer-guides/llm-sdks-and-frameworks/jev.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/developer-guides/llm-sdks-and-frameworks/jev.mdx b/developer-guides/llm-sdks-and-frameworks/jev.mdx index d31e1eba2..a4d485c8c 100644 --- a/developer-guides/llm-sdks-and-frameworks/jev.mdx +++ b/developer-guides/llm-sdks-and-frameworks/jev.mdx @@ -17,8 +17,6 @@ FIRECRAWL_API_KEY=your_firecrawl_key TYPESAFE_API_KEY=your_typesafe_key ``` -> **Note:** Jev is in early access. Request access at [typesafe.ai](https://typesafe.ai/). The TypeSafe SDK requires Node 20 or newer. - ## Search + Filter This example searches the web, has Jev judge each result from its title and description, and scrapes only the results worth reading. From bb725489eedb07f5b6cfc6859a7e637873b8aa14 Mon Sep 17 00:00:00 2001 From: Richard Oliver Bray Date: Thu, 24 Sep 2026 18:30:12 +0100 Subject: [PATCH 7/9] docs: remove code comments from Jev guide Co-Authored-By: Claude Opus 5.5 (1M context) --- developer-guides/llm-sdks-and-frameworks/jev.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/developer-guides/llm-sdks-and-frameworks/jev.mdx b/developer-guides/llm-sdks-and-frameworks/jev.mdx index a4d485c8c..ac8499375 100644 --- a/developer-guides/llm-sdks-and-frameworks/jev.mdx +++ b/developer-guides/llm-sdks-and-frameworks/jev.mdx @@ -93,7 +93,6 @@ const questions = { const results = await Promise.all(pages.map(async (page) => { const { answers } = await typesafe.systemOne({ - // Jev accepts up to 32k tokens of state, so long pages are trimmed state: { url: page.metadata?.sourceURL ?? '', homepage: page.markdown?.slice(0, 50_000) ?? '' }, questions }); From d8b08fa3d77663a5e64571963e427ec23743ee3e Mon Sep 17 00:00:00 2001 From: Richard Oliver Bray Date: Fri, 25 Sep 2026 14:51:30 +0100 Subject: [PATCH 8/9] docs: simplify Jev guide examples and explain question types Add a minimal first example, trim the remaining examples to one idea each, drop Tag a Crawl, move Jev to the end of the LLM SDKs sidebar, and shorten the lead enrichment mention. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../llm-sdks-and-frameworks/jev.mdx | 127 +++++++----------- docs.json | 8 +- use-cases/lead-enrichment.mdx | 2 +- 3 files changed, 54 insertions(+), 83 deletions(-) diff --git a/developer-guides/llm-sdks-and-frameworks/jev.mdx b/developer-guides/llm-sdks-and-frameworks/jev.mdx index ac8499375..acd9f8c47 100644 --- a/developer-guides/llm-sdks-and-frameworks/jev.mdx +++ b/developer-guides/llm-sdks-and-frameworks/jev.mdx @@ -17,13 +17,36 @@ FIRECRAWL_API_KEY=your_firecrawl_key TYPESAFE_API_KEY=your_typesafe_key ``` +## Scrape + Decide + +This example scrapes a page and asks Jev a single yes/no question about it. A `noul` question returns the probability of a yes answer, from 0 to 1. + +```typescript +import { Firecrawl } from 'firecrawl'; +import { noul, TypeSafeClient } from '@typesafe-ai/sdk'; + +const firecrawl = new Firecrawl({ apiKey: process.env.FIRECRAWL_API_KEY }); +const typesafe = new TypeSafeClient(); + +const page = await firecrawl.scrape('https://firecrawl.dev', { formats: ['markdown'] }); + +const { answers } = await typesafe.systemOne({ + state: { page: page.markdown ?? '' }, + questions: { + hasFreeTier: noul('The product offers a free plan or free credits') + } +}); + +console.log('Free tier:', answers.hasFreeTier.noul); +``` + ## Search + Filter -This example searches the web, has Jev judge each result from its title and description, and scrapes only the results worth reading. +This example searches the web, has Jev rate each result from its title and description, and scrapes only the relevant ones. A `score` question takes a rubric ordered from 0, and returns the expected score, which can fall between levels. With three levels, a score of 1.5 or more leans toward "Directly answers the query". ```typescript import { Firecrawl, type SearchResultWeb } from 'firecrawl'; -import { noul, score, TypeSafeClient } from '@typesafe-ai/sdk'; +import { score, TypeSafeClient } from '@typesafe-ai/sdk'; const firecrawl = new Firecrawl({ apiKey: process.env.FIRECRAWL_API_KEY }); const typesafe = new TypeSafeClient(); @@ -32,44 +55,31 @@ const query = 'how to rotate Postgres credentials without downtime'; const search = await firecrawl.search(query, { limit: 10 }); const results = (search.web ?? []) as SearchResultWeb[]; -const judged = await Promise.all(results.map(async (result) => { +const rated = await Promise.all(results.map(async (result) => { const { answers } = await typesafe.systemOne({ - state: { query, url: result.url, title: result.title ?? '', description: result.description ?? '' }, + state: { query, title: result.title ?? '', description: result.description ?? '' }, questions: { relevance: score('How well this result answers the query', [ 'Unrelated to the query', 'Related topic, but does not answer the query', 'Directly answers the query' - ]), - primarySource: noul('This is official documentation, an engineering blog, or another primary source rather than an aggregator'), - marketing: noul('This is mainly a product or marketing page') + ]) } }); - return { - url: result.url, - relevance: answers.relevance.score, - primarySource: answers.primarySource.noul, - marketing: answers.marketing.noul - }; + return { url: result.url, relevance: answers.relevance.score }; })); -console.table(judged); +const relevant = rated.filter((r) => r.relevance >= 1.5).map((r) => r.url); -const worthReading = judged - .filter((r) => r.relevance >= 1.5 && r.marketing < 0.5) - .sort((a, b) => b.primarySource - a.primarySource) - .slice(0, 3) - .map((r) => r.url); - -if (worthReading.length > 0) { - const { data: pages } = await firecrawl.batchScrape(worthReading, { options: { formats: ['markdown'] } }); +if (relevant.length > 0) { + const { data: pages } = await firecrawl.batchScrape(relevant, { options: { formats: ['markdown'] } }); console.log(`Scraped ${pages.length} of ${results.length} results`); } ``` ## Qualify Leads -This example scrapes company homepages and routes each company to your CRM, a review queue, or the bin based on Jev's answers and confidence. +This example scrapes company homepages and sends each one to your CRM or a person for review. A `choice` question returns the selected label along with a `confidence`, so you can hand uncertain answers to a person instead of trusting them. ```typescript import { Firecrawl } from 'firecrawl'; @@ -81,68 +91,27 @@ const typesafe = new TypeSafeClient(); const companies = ['https://ramp.com', 'https://mercury.com', 'https://www.jpmorganchase.com']; const { data: pages } = await firecrawl.batchScrape(companies, { options: { formats: ['markdown'] } }); -const questions = { - isFintech: noul('The company builds financial technology such as payments, banking, lending, or spend management software'), - sellsToBusinesses: noul('The company primarily sells to businesses rather than consumers'), - stage: choice('How established the company appears to be', { - startup: 'Early stage, with a small team or a single product', - growth: 'Several products or a sizable customer base', - enterprise: 'A large, long-established company' - }) -}; - -const results = await Promise.all(pages.map(async (page) => { +const leads = await Promise.all(pages.map(async (page) => { const { answers } = await typesafe.systemOne({ - state: { url: page.metadata?.sourceURL ?? '', homepage: page.markdown?.slice(0, 50_000) ?? '' }, - questions - }); - - const fit = answers.isFintech.noul * answers.sellsToBusinesses.noul; - const { choice: stage, confidence } = answers.stage; - - let route = 'skip'; - if (confidence < 0.6) route = 'review'; - else if (fit > 0.8 && stage !== 'enterprise') route = 'crm'; - else if (fit > 0.4) route = 'review'; - - return { url: page.metadata?.sourceURL, fit, stage, confidence, route }; -})); - -console.table(results); -``` - -## Tag a Crawl - -This example crawls a docs site and labels every page. - -```typescript -import { Firecrawl } from 'firecrawl'; -import { choice, TypeSafeClient } from '@typesafe-ai/sdk'; - -const firecrawl = new Firecrawl({ apiKey: process.env.FIRECRAWL_API_KEY }); -const typesafe = new TypeSafeClient(); - -const crawl = await firecrawl.crawl('https://docs.firecrawl.dev', { - limit: 50, - scrapeOptions: { formats: ['markdown'] } -}); - -const tagged = await Promise.all(crawl.data.map(async (page) => { - const { answers } = await typesafe.systemOne({ - state: { title: page.metadata?.title ?? '', markdown: page.markdown?.slice(0, 50_000) ?? '' }, + state: { homepage: page.markdown ?? '' }, questions: { - kind: choice('What kind of documentation page is this?', { - tutorial: 'Walks the reader through building something step by step', - howTo: 'Explains how to accomplish one specific task', - reference: 'Lists parameters, endpoints, or options', - concept: 'Explains an idea or how something works' + isFintech: noul('The company builds financial technology such as payments, banking, or spend management software'), + stage: choice('How established the company appears to be', { + startup: 'Early stage, with a small team or a single product', + growth: 'Several products or a sizable customer base', + enterprise: 'A large, long-established company' }) } }); - return { url: page.metadata?.sourceURL, kind: answers.kind.choice }; + + const { choice: stage, confidence } = answers.stage; + const qualified = answers.isFintech.noul > 0.8 && stage !== 'enterprise'; + const route = qualified && confidence >= 0.6 ? 'crm' : 'review'; + + return { url: page.metadata?.sourceURL, stage, confidence, route }; })); -console.table(tagged); +console.table(leads); ``` ## Triage Page Changes @@ -180,3 +149,5 @@ if (page.changeTracking?.changeStatus === 'changed' && diff) { } } ``` + +For more on question types and options, see the [TypeSafe docs](https://docs.typesafe.ai/). diff --git a/docs.json b/docs.json index d1a0cfeef..3c5719211 100755 --- a/docs.json +++ b/docs.json @@ -304,14 +304,14 @@ "developer-guides/llm-sdks-and-frameworks/openai", "developer-guides/llm-sdks-and-frameworks/anthropic", "developer-guides/llm-sdks-and-frameworks/gemini", - "developer-guides/llm-sdks-and-frameworks/jev", "developer-guides/llm-sdks-and-frameworks/google-adk", "developer-guides/llm-sdks-and-frameworks/vercel-ai-sdk", "developer-guides/llm-sdks-and-frameworks/langchain", "developer-guides/llm-sdks-and-frameworks/langgraph", "developer-guides/llm-sdks-and-frameworks/llamaindex", "developer-guides/llm-sdks-and-frameworks/mastra", - "developer-guides/llm-sdks-and-frameworks/elevenagents" + "developer-guides/llm-sdks-and-frameworks/elevenagents", + "developer-guides/llm-sdks-and-frameworks/jev" ] }, { @@ -660,14 +660,14 @@ "developer-guides/llm-sdks-and-frameworks/openai", "developer-guides/llm-sdks-and-frameworks/anthropic", "developer-guides/llm-sdks-and-frameworks/gemini", - "developer-guides/llm-sdks-and-frameworks/jev", "developer-guides/llm-sdks-and-frameworks/google-adk", "developer-guides/llm-sdks-and-frameworks/vercel-ai-sdk", "developer-guides/llm-sdks-and-frameworks/langchain", "developer-guides/llm-sdks-and-frameworks/langgraph", "developer-guides/llm-sdks-and-frameworks/llamaindex", "developer-guides/llm-sdks-and-frameworks/mastra", - "developer-guides/llm-sdks-and-frameworks/elevenagents" + "developer-guides/llm-sdks-and-frameworks/elevenagents", + "developer-guides/llm-sdks-and-frameworks/jev" ] }, { diff --git a/use-cases/lead-enrichment.mdx b/use-cases/lead-enrichment.mdx index b7fe7bd85..794092486 100644 --- a/use-cases/lead-enrichment.mdx +++ b/use-cases/lead-enrichment.mdx @@ -28,7 +28,7 @@ Point Firecrawl at a business directory, trade association, or conference attend Firecrawl pulls information directly from live company websites instead of a static database. Your team always sees the latest company news, team changes, and growth signals. ### Qualify leads before they reach your CRM -Scraped company data still needs a judgment call: is this account in your market, and is it the right size? Pair Firecrawl with [Jev](/developer-guides/llm-sdks-and-frameworks/jev), a fast decision model from TypeSafe, to answer those questions for every company in under a second each. Jev returns a confidence score with each answer, so you can send clear matches straight to your CRM and route uncertain ones to a person for review. +Pair Firecrawl with a decision model like [Jev](/developer-guides/llm-sdks-and-frameworks/jev#qualify-leads) to check whether each company fits your market, sending clear matches to your CRM and uncertain ones to a person for review. ## Customer Stories From 1b5ec0715ed73b596da3e994bedc8e2fb43c64f9 Mon Sep 17 00:00:00 2001 From: Richard Oliver Bray Date: Fri, 25 Sep 2026 17:33:48 +0100 Subject: [PATCH 9/9] docs: remove Jev change triage in favor of built-in monitor judging Monitoring already judges meaningful changes with a goal, so drop the Triage Page Changes example and the competitive intelligence mentions. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../llm-sdks-and-frameworks/jev.mdx | 36 ------------------- use-cases/competitive-intelligence.mdx | 6 +--- 2 files changed, 1 insertion(+), 41 deletions(-) diff --git a/developer-guides/llm-sdks-and-frameworks/jev.mdx b/developer-guides/llm-sdks-and-frameworks/jev.mdx index acd9f8c47..386a0d629 100644 --- a/developer-guides/llm-sdks-and-frameworks/jev.mdx +++ b/developer-guides/llm-sdks-and-frameworks/jev.mdx @@ -114,40 +114,4 @@ const leads = await Promise.all(pages.map(async (page) => { console.table(leads); ``` -## Triage Page Changes - -This example uses [change tracking](/features/change-tracking) to get a page diff, then has Jev rate whether the change is worth an alert. - -```typescript -import { Firecrawl } from 'firecrawl'; -import { score, TypeSafeClient } from '@typesafe-ai/sdk'; - -const firecrawl = new Firecrawl({ apiKey: process.env.FIRECRAWL_API_KEY }); -const typesafe = new TypeSafeClient(); - -const url = 'https://example.com/pricing'; -const page = await firecrawl.scrape(url, { - formats: ['markdown', { type: 'changeTracking', modes: ['git-diff'] }] -}); - -const diff = page.changeTracking?.diff as { text: string } | undefined; - -if (page.changeTracking?.changeStatus === 'changed' && diff) { - const { answers } = await typesafe.systemOne({ - state: { url, diff: diff.text }, - questions: { - impact: score('How much this change matters to a competitor', [ - 'Cosmetic: layout, typos, dates, or reordered content', - 'Minor: reworded copy or small feature mentions', - 'Material: prices, plans, limits, or a new product' - ]) - } - }); - - if (answers.impact.score >= 1.5) { - console.log('Alert:', url, answers.impact.score); - } -} -``` - For more on question types and options, see the [TypeSafe docs](https://docs.typesafe.ai/). diff --git a/use-cases/competitive-intelligence.mdx b/use-cases/competitive-intelligence.mdx index 35b3e9064..66f83922c 100644 --- a/use-cases/competitive-intelligence.mdx +++ b/use-cases/competitive-intelligence.mdx @@ -49,10 +49,6 @@ Set up a pipeline that scrapes competitor sites on a schedule, extracts the data - **Strategy**: Positioning, target markets, pricing approaches, go-to-market - **Technical**: API changes, integrations, technology stack updates -## Filter Out Noise - -Most page changes don't matter. A new date in a footer or a reworded headline shouldn't page your team. Use [change tracking](/features/change-tracking) to get a diff of what changed, then have [Jev](/developer-guides/llm-sdks-and-frameworks/jev), a fast decision model from TypeSafe, rate how material each diff is. Alert only when a change touches prices, plans, limits, or products. See [Triage Page Changes](/developer-guides/llm-sdks-and-frameworks/jev#triage-page-changes) for a working example. - ## FAQs @@ -65,7 +61,7 @@ Most page changes don't matter. A new date in a footer or a reworded headline sh - When building your monitoring system, implement filters to ignore minor changes like timestamps or dynamic content. Compare extracted data over time and use your own logic to determine what constitutes a meaningful change, or pass each diff to a decision model like [Jev](/developer-guides/llm-sdks-and-frameworks/jev) to score how much it matters. + When building your monitoring system, implement filters to ignore minor changes like timestamps or dynamic content. Compare extracted data over time and use your own logic to determine what constitutes a meaningful change.