jevascript
esc

    Type to search · ↑↓ to move · ↵ to open

    Get started

    Scenarios

    RAG gate

    Judge every retrieved passage against the actual question, in one request, and answer the question retrieval cannot. Does the corpus contain an answer at all?

    Source: examples/launch/04-rag-gate.ts

    The problem#

    Vector search returns passages that are similar to the question. Similar is not the same as useful. Sending all of them to an expensive model costs tokens, and worse, gives it enough adjacent material to write a confident paragraph when the real answer is not in the corpus.

    Pack the passages into one state#

    Twelve short passages fit comfortably in one state. Put them in under stable ids, and ask one question per passage plus one about the whole set:

    gate.ts
    import { is, semantic, type QuestionSpec } from "jevascript"
    
    const question = "Can I issue a partial refund after a payout has already been sent to the seller?"
    const passages: string[] = await vectorSearch(question, { k: 12 })
    
    const ids = passages.map((_, i) => `p${i}`)
    const context = semantic({
      question,
      passages: Object.fromEntries(passages.map((text, i) => [ids[i], text])),
    })
    
    const questions: Record<string, QuestionSpec<boolean>> = {
      ...Object.fromEntries(
        ids.map((id) => [
          id,
          is(`Passage ${id} contains information that helps answer the question.`, {
            trueWhen: "It states a rule, limit or behaviour that bears directly on the question.",
            falseWhen: "It is about a neighbouring topic, or merely uses the same words.",
          }),
        ]),
      ),
      // Retrieval always returns something. This is the question it cannot answer.
      answerable: is("The passages together contain enough to answer the question.", {
        falseWhen: "They discuss the topic but never state the specific rule being asked about.",
      }),
    }
    
    const verdicts = await context.batch(questions)
    const kept = ids.filter((id) => verdicts[id] === true)

    Thirteen questions, one request. The corpus is sent once. In the recorded run, the gate cut the context by 78% for a fraction of a cent.

    Dynamic keys mean TypeScript cannot infer the result shape through Object.fromEntries; declaring the record type once keeps the rest of the file typed as Record<string, boolean>.

    The decision#

    answer.ts
    if (!verdicts.answerable) {
      return "I don't have enough information to answer that."
    }
    
    const prompt = buildPrompt(question, kept.map((id) => passages[Number(id.slice(1))]))
    return await expensiveModel.generate(prompt)

    An empty corpus now produces "I don't know" instead of a plausible fabrication built from neighbouring passages.

    When to use filter instead#

    This pattern is one request because the passages are short. If each item were a full document, they would not fit in one state, and semantic.filter at one request per item would be the right tool. The break-even depends on maxStateTokens for the provider, 32 000 for Jev.

    What to test#

    • Stub p3 true and the rest false → kept is ["p3"].
    • Stub everything false and answerable false → the fallback message, and the expensive model is never called.
    • provider.requestCount === 1 for the whole gate.