<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Devil's Dev</title>
    <description>The latest articles on DEV Community by Devil's Dev (@devildev).</description>
    <link>https://dev.to/devildev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4229856%2F54f9826f-7636-4ccf-b2dd-4ae9d0016f45.jpg</url>
      <title>DEV Community: Devil's Dev</title>
      <link>https://dev.to/devildev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/devildev"/>
    <language>en</language>
    <item>
      <title>RAG Pipeline Utils v3.8: Testing RAG Answers in TypeScript</title>
      <dc:creator>Devil's Dev</dc:creator>
      <pubDate>Fri, 06 Nov 2026 10:07:03 +0000</pubDate>
      <link>https://dev.to/devildev/rag-pipeline-utils-v38-testing-rag-answers-in-typescript-4dle</link>
      <guid>https://dev.to/devildev/rag-pipeline-utils-v38-testing-rag-answers-in-typescript-4dle</guid>
      <description>&lt;p&gt;v3.8.0 is about testing RAG in TypeScript. The answer matchers now compile with types, and a new guide walks through the whole setup with Vitest, from a unit test to a test set that fails CI when answers get worse. Nothing breaks for existing code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @devilsdev/rag-pipeline-utils@^3.8.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The matchers, with types
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@devilsdev/rag-pipeline-utils/testing&lt;/code&gt; has had three matchers for a while: &lt;code&gt;toBeGroundedIn&lt;/code&gt; (every sentence of the answer is supported by its sources, numbers and negation included), &lt;code&gt;toCite&lt;/code&gt; and &lt;code&gt;toContainNoPII&lt;/code&gt;. They worked in JavaScript. In TypeScript, &lt;code&gt;expect(result).toBeGroundedIn()&lt;/code&gt; didn't compile, because nothing told TypeScript the matchers existed. Worse, the declaration our own Testing page suggested failed under Vitest 5 with "All declarations of 'Assertion' must have identical type parameters".&lt;/p&gt;

&lt;p&gt;3.8.0 exports their signatures as &lt;code&gt;RagMatchers&lt;/code&gt;. Add them to Vitest's &lt;code&gt;Matchers&lt;/code&gt; once, in a declaration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RagMatchers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@devilsdev/rag-pipeline-utils/testing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Matchers&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;RagMatchers&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the matchers type-check like any other assertion. A misspelled matcher is an error, and so is a wrong argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expect(answer).toCite(123)
Argument of type 'number' is not assignable to parameter of type 'string | string[]'.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Jest works the same way, with &lt;code&gt;jest.Matchers&amp;lt;R&amp;gt;&lt;/code&gt;; the &lt;a href="https://devilsdev.github.io/rag-pipeline-utils/docs/Testing#matchers-in-typescript" rel="noopener noreferrer"&gt;Testing page&lt;/a&gt; has both.&lt;/p&gt;

&lt;h2&gt;
  
  
  A guide: Testing RAG in TypeScript
&lt;/h2&gt;

&lt;p&gt;RAG code rarely fails loudly: someone changes the chunk size, the prompt or the model, everything still runs, and an answer quietly says thirty days where the document says twenty. The new guide sets up a project that catches that, in three layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Unit tests&lt;/strong&gt; with the typed matchers, on an offline test pipeline: deterministic, no API key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Answer checks&lt;/strong&gt; with &lt;code&gt;checkAnswer&lt;/code&gt;, which return the exact sentence the sources don't support, for answers from any framework.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A test set&lt;/strong&gt; scored by &lt;code&gt;rag-pipeline eval&lt;/code&gt;, with minimums or a baseline from your main branch, failing the build when scores drop.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a wrong number is caught&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;pageContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Full-time employees receive twenty days of paid vacation per year.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Full-time employees receive thirty days of paid vacation per year.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBeGroundedIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;checkAnswer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sources&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grounded&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unsupported&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The guide is built from a runnable project, &lt;a href="https://github.com/DevilsDev/rag-pipeline-utils/tree/main/examples/testing-typescript" rel="noopener noreferrer"&gt;examples/testing-typescript&lt;/a&gt;. A test keeps the two identical. Before each release, a check installs the packed package with the latest Vitest and TypeScript, then type-checks the project, runs its tests and runs its evaluation, so the code in the guide works as written. &lt;a href="https://devilsdev.github.io/rag-pipeline-utils/docs/Testing-RAG-TypeScript" rel="noopener noreferrer"&gt;Read the guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Also
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Releases announce themselves.&lt;/strong&gt; This post reached you through it: publishing a release now posts to GitHub Discussions, dev.to, Bluesky and Mastodon, built from the release notes and this blog post. Every post passes checks first: each number must appear in the files it came from, and each link must be live.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fewer development dependencies.&lt;/strong&gt; Eight unused OpenTelemetry SDK packages and three modules nothing loaded are gone, 237 packages in all. &lt;code&gt;npm audit&lt;/code&gt; findings in development tooling fell from 64 to 14, none high. What you install from npm had none and still has none.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The scoped packages, &lt;code&gt;@devilsdev/rag-mcp-server&lt;/code&gt; and &lt;code&gt;create-rag-pipeline&lt;/code&gt; are released at 3.8.0 too. Full details: &lt;a href="https://github.com/DevilsDev/rag-pipeline-utils/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;CHANGELOG&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://devilsdev.github.io/rag-pipeline-utils/blog/v3-8-release" rel="noopener noreferrer"&gt;rag-pipeline-utils blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>ai</category>
      <category>rag</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Meet rag-pipeline-utils: RAG for Node.js that tells you when your LLM is making things up</title>
      <dc:creator>Devil's Dev</dc:creator>
      <pubDate>Fri, 06 Nov 2026 07:53:38 +0000</pubDate>
      <link>https://dev.to/devildev/meet-rag-pipeline-utils-rag-for-nodejs-that-tells-you-when-your-llm-is-making-things-up-4o3n</link>
      <guid>https://dev.to/devildev/meet-rag-pipeline-utils-rag-for-nodejs-that-tells-you-when-your-llm-is-making-things-up-4o3n</guid>
      <description>&lt;p&gt;Your RAG chatbot just told an employee they get &lt;strong&gt;thirty&lt;/strong&gt; vacation days. The handbook it retrieved says &lt;strong&gt;twenty&lt;/strong&gt;. Nothing crashed, no test failed, and the answer reads perfectly well. That's how retrieval-augmented generation usually breaks: quietly.&lt;/p&gt;

&lt;p&gt;I maintain &lt;a href="https://www.npmjs.com/package/@devilsdev/rag-pipeline-utils" rel="noopener noreferrer"&gt;&lt;code&gt;@devilsdev/rag-pipeline-utils&lt;/code&gt;&lt;/a&gt;, an open-source RAG library for Node.js built around one idea: &lt;strong&gt;you should be able to check every answer against the sources it came from&lt;/strong&gt;, cheaply, in your app and in CI. This post is a quick tour.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @devilsdev/rag-pipeline-utils
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Node.js 22+, with CommonJS, ESM and TypeScript types included.&lt;/p&gt;

&lt;h2&gt;
  
  
  Catch the made-up sentence
&lt;/h2&gt;

&lt;p&gt;You don't have to build your pipeline with this library to use it. Bring an answer from anywhere (LangChain, Mastra, the Vercel AI SDK, LlamaIndex or your own code) and pass it in with the sources it was given:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;checkAnswer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@devilsdev/rag-pipeline-utils&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;checkAnswer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Full-time employees receive thirty days of paid vacation per year. &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Remote work is allowed two days per week.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;pageContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Full-time employees receive twenty days of paid vacation per year.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;pageContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Remote work is allowed two days per week with manager approval.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grounded&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;groundedness&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 0.5&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unsupported&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ["Full-time employees receive thirty days of paid vacation per year."]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It checks each sentence against the sources: the words, but also the numbers and the negations, which is where answers tend to go wrong. It runs locally with no API key and makes no model calls, so the same input gives the same verdict every time, offline, in tests.&lt;/p&gt;

&lt;p&gt;If you're worried about correct paraphrases being flagged, pass a &lt;code&gt;judge&lt;/code&gt; and your LLM is asked about the sentences that failed on wording alone, and nothing else.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pipeline in a dozen lines
&lt;/h2&gt;

&lt;p&gt;If you do want the library to run the pipeline, &lt;code&gt;createRagPipeline()&lt;/code&gt; wires an embedder, a retriever, an optional reranker and an LLM. This one runs offline, with the built-in TF-IDF embedder and an in-memory store:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;createRagPipeline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;LocalEmbedder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;MemoryRetriever&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@devilsdev/rag-pipeline-utils&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vacation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Full-time employees get twenty vacation days.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;remote&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Remote work is allowed two days per week.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;embedder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LocalEmbedder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;embedder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;retriever&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MemoryRetriever&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;retriever&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;embedder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;})),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createRagPipeline&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;embedder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;retriever&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How many vacation days?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "vacation"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap in OpenAI, Anthropic, Cohere or Ollama for the model, and pgvector, Qdrant, Pinecone or Weaviate for the store, when you're ready. Anything with an &lt;code&gt;embed()&lt;/code&gt; or &lt;code&gt;generate()&lt;/code&gt; method works too, including Vercel AI SDK models.&lt;/p&gt;

&lt;p&gt;Prefer to start from a working app? &lt;code&gt;npm create rag-pipeline@latest&lt;/code&gt; scaffolds one for Next.js with the AI SDK, Mastra, or Cloudflare Workers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test answers like any other value
&lt;/h2&gt;

&lt;p&gt;RAG regressions don't throw. Someone changes the chunk size, the prompt, the model or the embedder, everything still runs, and the answers drift. So the library ships offline test helpers and matchers for Jest and Vitest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// or Jest's global expect&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;createTestPipeline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ragMatchers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@devilsdev/rag-pipeline-utils/testing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ragMatchers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;answers vacation questions from the handbook&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createTestPipeline&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How many vacation days do I get?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeGroundedIn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// every sentence supported by result.results&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toCite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vacation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toContainNoPII&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For whole test sets there's a CLI: &lt;code&gt;rag-pipeline eval&lt;/code&gt; scores a dataset and fails when answers get worse than a saved baseline, and the &lt;a href="https://github.com/DevilsDev/rag-eval-action" rel="noopener noreferrer"&gt;rag-eval-action&lt;/a&gt; GitHub Action runs it on every pull request and posts the comparison as a comment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What else is in the box
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Framework integrations.&lt;/strong&gt; &lt;code&gt;groundingMiddleware&lt;/code&gt; adds retrieval and grounding to any Vercel AI SDK model without changing the rest of your code; there are scorers and guardrails for Mastra.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guardrails.&lt;/strong&gt; Prompt-injection detection and PII redaction, with &lt;a href="https://devilsdev.github.io/rag-pipeline-utils/docs/Benchmarks#guardrail-accuracy" rel="noopener noreferrer"&gt;published precision and recall&lt;/a&gt;, plus access-control filtering before anything reaches the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production details.&lt;/strong&gt; Hybrid search, reranking, token budgets, timeouts and cancellation, retries, caching, and OpenTelemetry (or Langfuse) traces with the evaluation scores attached.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The edge.&lt;/strong&gt; A &lt;code&gt;/edge&lt;/code&gt; build for Cloudflare Workers, Vercel Edge and Deno.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude, via MCP.&lt;/strong&gt; &lt;code&gt;@devilsdev/rag-mcp-server&lt;/code&gt; lets Claude search a folder of your documents and check its own answers against them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Take only what you need.&lt;/strong&gt; The main package has everything; smaller packages like &lt;code&gt;@devilsdev/rag-eval&lt;/code&gt; (about 6 MB installed) are there if install size matters.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The fastest way in is the answer check on &lt;a href="https://stackblitz.com/github/DevilsDev/rag-pipeline-utils/tree/main/examples/use-cases?file=check-your-answer.js" rel="noopener noreferrer"&gt;StackBlitz&lt;/a&gt;: paste an answer of your own, with no install or API key.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://devilsdev.github.io/rag-pipeline-utils/" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/DevilsDev/rag-pipeline-utils" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; (issues and ideas welcome, and a star helps other developers find it)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@devilsdev/rag-pipeline-utils" rel="noopener noreferrer"&gt;npm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd love to hear how you test your RAG apps today, and what you'd want a library like this to catch. Tell me in the comments.&lt;/p&gt;

</description>
      <category>node</category>
      <category>ai</category>
      <category>rag</category>
      <category>javascript</category>
    </item>
    <item>
      <title>RAG Pipeline Utils v3.7.1: Security Fixes From a Code-Scanning Review, and a New npm Page</title>
      <dc:creator>Devil's Dev</dc:creator>
      <pubDate>Fri, 06 Nov 2026 07:44:06 +0000</pubDate>
      <link>https://dev.to/devildev/rag-pipeline-utils-v371-security-fixes-from-a-code-scanning-review-and-a-new-npm-page-4bko</link>
      <guid>https://dev.to/devildev/rag-pipeline-utils-v371-security-fixes-from-a-code-scanning-review-and-a-new-npm-page-4bko</guid>
      <description>&lt;p&gt;v3.7.1 is a patch release with no breaking changes. It fixes a slow pattern in &lt;code&gt;QueryPlanner&lt;/code&gt; that a long query could use to tie up a server, and two ways a crafted &lt;code&gt;__proto__&lt;/code&gt; key could write to &lt;code&gt;Object.prototype&lt;/code&gt; in the CLI. It also gives the npm page a new README. Upgrading is recommended if you pass user queries to &lt;code&gt;QueryPlanner&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @devilsdev/rag-pipeline-utils@^3.7.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How these were found
&lt;/h2&gt;

&lt;p&gt;Main had 52 open code-scanning alerts from CodeQL and OpenSSF Scorecard, most of them old. We went through every one of them against the code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;timed each slow-pattern alert;&lt;/li&gt;
&lt;li&gt;reproduced each prototype-pollution alert;&lt;/li&gt;
&lt;li&gt;checked whether the flagged code is actually shipped in the published package.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;19 are fixed in this release:&lt;/strong&gt; the code issues below, plus Docker image pins and tighter workflow permissions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;27 were false alarms,&lt;/strong&gt; and each was dismissed on GitHub with its reason. Examples: modules that aren't in the published package, test fixtures with attack strings, and a random session label that was never a secret.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 6 still open&lt;/strong&gt; are repository settings (code review, branch protection, fuzzing) or an example that has no lockfile on purpose.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;QueryPlanner&lt;/code&gt; and long runs of spaces
&lt;/h2&gt;

&lt;p&gt;CodeQL flagged &lt;code&gt;QueryPlanner&lt;/code&gt;'s &lt;code&gt;;&lt;/code&gt; pattern as slow on long input. Timing the whole &lt;code&gt;plan()&lt;/code&gt; call found a worse one it hadn't flagged:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The pattern for "…, and how …" clauses was &lt;strong&gt;cubic&lt;/strong&gt; in the length of a run of spaces: 4,000 spaces took 21 seconds, and 20,000 didn't finish within a minute.&lt;/li&gt;
&lt;li&gt;The "vs" and comparison patterns were quadratic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;QueryPlanner&lt;/code&gt; is exported from the main entry and from &lt;code&gt;/edge&lt;/code&gt;, and it's given user queries, so one long query could hold a server's CPU.&lt;/p&gt;

&lt;p&gt;The planner now reads each run of whitespace as one space before matching, and the clause boundaries can split a run only one way. On every query in the test suite and the edge cases we added, it splits exactly as before, and 200,000 characters of any shape we tried plan in under 100 ms. The one visible change: a sub-query has single spaces where the query had runs of whitespace.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;__proto__&lt;/code&gt; in the CLI
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;rag-pipeline plugin install&lt;/code&gt;.&lt;/strong&gt; A plugin whose &lt;code&gt;package.json&lt;/code&gt; declared &lt;code&gt;"stage": "__proto__"&lt;/code&gt; passed the stage check, and registering it wrote to &lt;code&gt;Object.prototype&lt;/code&gt; in the CLI process. Only the stage names the installer knows are accepted now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;rag-pipeline config set&lt;/code&gt;.&lt;/strong&gt; The key path was checked, but a &lt;code&gt;__proto__&lt;/code&gt; key inside the JSON value was merged into &lt;code&gt;Object.prototype&lt;/code&gt;. Such keys are skipped now, and the merge only goes into the config's own objects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both fixes come with regression tests that fail on 3.7.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  A new README on npm
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.npmjs.com/package/@devilsdev/rag-pipeline-utils" rel="noopener noreferrer"&gt;npm page&lt;/a&gt; now speaks to developers meeting the library for the first time. It opens with the problem the library solves, an answer that says thirty vacation days when the source says twenty, and shows &lt;code&gt;checkAnswer&lt;/code&gt; catching exactly that sentence. Then a pipeline in a dozen lines, and what else is in the box. Both examples run in CI, so they stay correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  Also
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;eval&lt;/code&gt; Markdown report escapes backslashes, so an error message containing &lt;code&gt;\|&lt;/code&gt; no longer breaks a table cell.&lt;/li&gt;
&lt;li&gt;Docker base images are pinned by digest, and Dependabot keeps those digests current.&lt;/li&gt;
&lt;li&gt;The Security and Supply Chain workflows are read-only by default.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full list is in the &lt;a href="https://github.com/DevilsDev/rag-pipeline-utils/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;CHANGELOG&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://devilsdev.github.io/rag-pipeline-utils/blog/v3-7-1-release" rel="noopener noreferrer"&gt;rag-pipeline-utils blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>ai</category>
      <category>rag</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
