<?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: Na'aman Hirschfeld (Goldziher)</title>
    <description>The latest articles on DEV Community by Na'aman Hirschfeld (Goldziher) (@nhirschfeld).</description>
    <link>https://dev.to/nhirschfeld</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%2F988181%2F746d781b-8ccb-4694-beaa-2f7e33782cee.jpeg</url>
      <title>DEV Community: Na'aman Hirschfeld (Goldziher)</title>
      <link>https://dev.to/nhirschfeld</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nhirschfeld"/>
    <language>en</language>
    <item>
      <title>Debugging basemind with Claude Code</title>
      <dc:creator>Na'aman Hirschfeld (Goldziher)</dc:creator>
      <pubDate>Wed, 08 Jul 2026 16:53:10 +0000</pubDate>
      <link>https://dev.to/nhirschfeld/debugging-basemind-with-claude-code-4c92</link>
      <guid>https://dev.to/nhirschfeld/debugging-basemind-with-claude-code-4c92</guid>
      <description>&lt;p&gt;basemind is my code-intelligence tool: a pure-Rust code map and scanner, tree-sitter across 300+ languages, a content-addressed blob store, a Fjall-backed inverted index. I harden it by pointing it at a repo big enough to hurt, a 20M-line, 200k-commit monorepo, and watching what it does.&lt;/p&gt;

&lt;p&gt;I did this debugging session with Claude Code. It started as "which version is installed?" and ended with a 7x speedup, two upstream PRs, and a feature turned off. Here is how I worked, and the methods that made it effective.&lt;/p&gt;

&lt;h2&gt;
  
  
  The symptom is not the bug
&lt;/h2&gt;

&lt;p&gt;The report: basemind's cache grew from 1.8 GB to 4 GB while CPU pinned for a long time. I did not open the scanner code. I looked at the live system. &lt;code&gt;ps&lt;/code&gt; and &lt;code&gt;lsof&lt;/code&gt; found a process at ~70% CPU holding the index open, and it was a &lt;em&gt;serve&lt;/em&gt; process, not a &lt;em&gt;scan&lt;/em&gt;. Different bug entirely.&lt;/p&gt;

&lt;p&gt;That process was an old release. So I had Claude dig through git instead of the debugger: &lt;code&gt;git log&lt;/code&gt; on the watcher, then &lt;code&gt;git merge-base --is-ancestor&lt;/code&gt; and commit dates. A commit titled "fix: stop watcher CPU runaway" was dated four days after the running binary shipped. The bug (a watcher re-scanning its own writes forever) was already fixed. It was a stale process left over from another editor session.&lt;/p&gt;

&lt;p&gt;Two rules, both applied before reading a line of source:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Verify the running artifact, not a proxy.&lt;/strong&gt; Homebrew, the PATH, and the editor plugin each reported a different version. Find the one that actually runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Live bug or old build?" is the first question.&lt;/strong&gt; Dating the fix against the running version can end an investigation before it starts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Measurement tells a scan from a leak
&lt;/h2&gt;

&lt;p&gt;I killed the ghost, and a fresh scan appeared at 488% CPU, nearly five cores. That is rayon pegging every core, and it is indistinguishable from a runaway loop on a single reading.&lt;/p&gt;

&lt;p&gt;So I sampled three signals over time: CPU%, cache size, blob count. Disk climbed then plateaued. Blob writes burst then stopped. CPU fell to one core. That is a one-time rebuild converging, not a leak. A leak keeps allocating, a loop keeps writing, a scan converges. The derivative is the diagnosis, not the number.&lt;/p&gt;

&lt;p&gt;To watch without babysitting, I had Claude write background monitors: shell loops sampling every 30s, printing only on a state transition. One fired on "SCAN COMPLETE + IDLE," another on "0% CPU and no disk growth for 4 minutes." The alert encodes the hypothesis: a hang is low CPU &lt;em&gt;and&lt;/em&gt; no progress, a healthy scan is high CPU &lt;em&gt;or&lt;/em&gt; growing disk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Isolate the confound
&lt;/h2&gt;

&lt;p&gt;A multi-worktree test then hung: scans wedged at 0% CPU, and &lt;code&gt;lsof&lt;/code&gt; on a stuck process itself hung for two minutes. A hanging &lt;code&gt;lsof&lt;/code&gt; means a stuck socket. The last log line was &lt;code&gt;Registering VLM OCR backend&lt;/code&gt;. The OCR tier made a network call with no timeout, a firewall dropped it, and the connection blocked forever.&lt;/p&gt;

&lt;p&gt;I did not debug the hang in place. That tier is opt-in behind a cargo feature, so I built basemind with default features, a binary that cannot make the call, and studied the scanner in isolation. When two failures tangle, change one variable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read for the mechanism, then measure the fix
&lt;/h2&gt;

&lt;p&gt;On a clean binary I took a baseline. A fresh worktree scan took 181s, re-parsed all 67,700 files though their blobs already existed, and rebuilt the git-history index per worktree at ~160s. Now I read code, to confirm the mechanism, not guess a patch.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The "unchanged file" fast paths keyed off the per-worktree index, empty on a fresh worktree. So every file fell through to a full re-parse, despite its blob already sitting in the shared store.&lt;/li&gt;
&lt;li&gt;The git-history index opened at the worktree's own cache dir, though it derives entirely from the shared &lt;code&gt;.git&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both fixes followed patterns already in the code, and each got a regression test written to fail first. Re-measured: 181s to 25s, ~7x, all 67,700 files reused, peak RSS about halved. The re-measurement is the point. It turns "should be faster" into "is 7x faster."&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow the failure across the boundary
&lt;/h2&gt;

&lt;p&gt;The network hang still deserved a fix. I traced it: the OCR and embedding models download through &lt;code&gt;hf_hub&lt;/code&gt;, whose builder in 0.4 and 0.5 constructs a &lt;code&gt;ureq&lt;/code&gt; agent with no timeout. A firewalled download blocks forever. The root cause was in the dependency.&lt;/p&gt;

&lt;p&gt;I forked it and had Claude map every download call site across two crates, then add a watchdog: run the blocking fetch on a detached thread, error on a tunable deadline, degrade by skipping the model instead of hanging. The mechanical multi-file edit went to a subagent with a tight spec. Then I read the diff and reran the build and tests myself. The subagent even corrected my assumption about a type not being &lt;code&gt;Clone&lt;/code&gt;. That shipped as an upstream PR plus an honest bug report. Delegate the mechanical work, verify with diffs and reruns, never a summary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sometimes the fix is deletion
&lt;/h2&gt;

&lt;p&gt;Monitoring caught the embedding pass stuck on a general-purpose model, an empty model dir with a stale lock, spinning a core. The sharper question was not why it was slow, but whether code embeddings should exist at all.&lt;/p&gt;

&lt;p&gt;I traced what gets embedded (symbol, signature, doc, body) against what embeds it (a general-English model), and basemind already builds a BM25 index over the same text. A prose model embeds code weakly, the one real win (natural language to symbol) is already covered by the keyword lane, and the cost is a 100 MB pull plus gigabytes of vectors. Verdict: code embeddings off by default, kept for docs and images. A feature that is off cannot hang, leak, or pin a core.&lt;/p&gt;

&lt;h2&gt;
  
  
  The methodology
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Observe the live system (ps, lsof, du) before reading source.&lt;/li&gt;
&lt;li&gt;Separate stale-artifact bugs from live bugs by dating the fix against the running version.&lt;/li&gt;
&lt;li&gt;Measure dynamics, not snapshots. The derivative separates converging from leaking.&lt;/li&gt;
&lt;li&gt;Encode the hypothesis into a monitor so the machine watches for you.&lt;/li&gt;
&lt;li&gt;Isolate confounds by changing one variable.&lt;/li&gt;
&lt;li&gt;Read code for the mechanism, then write the smallest fix.&lt;/li&gt;
&lt;li&gt;Prove it with a test that fails without the fix, and re-measure the metric you started from.&lt;/li&gt;
&lt;li&gt;Follow failures into dependencies, and report them honestly.&lt;/li&gt;
&lt;li&gt;Ask whether the costly feature should exist, not just why it broke.&lt;/li&gt;
&lt;li&gt;Delegate the mechanical work to an agent, but verify every diff yourself.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What shipped
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Worktree cache reuse: cold scan 181s to 25s (~7x), 67,700 files reused, peak RSS about halved.&lt;/li&gt;
&lt;li&gt;Code embeddings off by default, per-file &lt;code&gt;embed_exclude&lt;/code&gt;, safe preset swapping.&lt;/li&gt;
&lt;li&gt;An upstream timeout guardrail in the dependency, plus a bug report.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The method I kept coming back to: verify the artifact, watch the live system, measure the change over time, isolate the confound, read for the mechanism, and prove the fix by the number you started from. basemind lives at &lt;a href="https://github.com/Goldziher/basemind" rel="noopener noreferrer"&gt;github.com/Goldziher/basemind&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>performance</category>
      <category>debugging</category>
      <category>ai</category>
    </item>
    <item>
      <title>One Rust core, native SDKs in 16 languages: how we stopped hand-writing bindings</title>
      <dc:creator>Na'aman Hirschfeld (Goldziher)</dc:creator>
      <pubDate>Mon, 06 Jul 2026 08:47:33 +0000</pubDate>
      <link>https://dev.to/nhirschfeld/one-rust-core-native-sdks-in-16-languages-how-we-stopped-hand-writing-bindings-4mik</link>
      <guid>https://dev.to/nhirschfeld/one-rust-core-native-sdks-in-16-languages-how-we-stopped-hand-writing-bindings-4mik</guid>
      <description>&lt;p&gt;&lt;em&gt;Building &lt;a href="https://github.com/xberg-io" rel="noopener noreferrer"&gt;xberg&lt;/a&gt;, a document-intelligence engine with a Rust core, we needed the same engine callable from Python, TypeScript, Ruby, Go, PHP, and a dozen more. This is how we ship native SDKs in every one of them without hand-writing a single binding layer.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The polyglot binding tax
&lt;/h2&gt;

&lt;p&gt;A Rust core is a good decision right up until you have to ship it to other languages. Then you inherit a second, quieter job: keep a PyO3 wrapper, a NAPI-RS addon, a Magnus extension, a cgo layer, and a handful more in lockstep with a core that moves every week.&lt;/p&gt;

&lt;p&gt;That is where polyglot projects rot. The core adds a field, one language's wrapper lags, and nobody notices until a Ruby user files a bug that a Python user never sees. Multiply by sixteen languages and the binding layer becomes the most expensive code in the project, none of which is the actual product.&lt;/p&gt;

&lt;p&gt;We did not want to write that code, let alone maintain it. So we generate it.&lt;/p&gt;

&lt;h2&gt;
  
  
  One config, every target
&lt;/h2&gt;

&lt;p&gt;The tool is &lt;a href="https://github.com/xberg-io/alef" rel="noopener noreferrer"&gt;&lt;code&gt;alef&lt;/code&gt;&lt;/a&gt;. It extracts the Rust API surface and emits language-native bindings, package scaffolding, type stubs, docs, and e2e tests from one &lt;code&gt;alef.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[workspace]&lt;/span&gt;
&lt;span class="py"&gt;languages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"python"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ffi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"go"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;alef_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.32.7"&lt;/span&gt;

&lt;span class="nn"&gt;[[crates]]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"sample_core"&lt;/span&gt;
&lt;span class="py"&gt;sources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"src/lib.rs"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;version_from&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Cargo.toml"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;alef generate &lt;span class="nt"&gt;--format&lt;/span&gt;   &lt;span class="c"&gt;# bindings + type stubs&lt;/span&gt;
alef scaffold            &lt;span class="c"&gt;# package manifests + native build files&lt;/span&gt;
alef verify &lt;span class="nt"&gt;--exit-code&lt;/span&gt;  &lt;span class="c"&gt;# fail CI if generated files are stale&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each target gets an idiomatic package, not a lowest-common-denominator shim:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Backend&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;PyO3 + type stubs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript / Node&lt;/td&gt;
&lt;td&gt;NAPI-RS addon with &lt;code&gt;.d.ts&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ruby&lt;/td&gt;
&lt;td&gt;Magnus extension&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;cgo over a generated C FFI layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP&lt;/td&gt;
&lt;td&gt;native extension&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Elixir / Gleam&lt;/td&gt;
&lt;td&gt;Rustler NIF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Swift, Kotlin, C#, Dart, R, Zig, WASM&lt;/td&gt;
&lt;td&gt;native bridge per platform&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Sixteen languages, eighteen targets (C FFI and a JNI shim round out the list). The Rust author writes Rust; the Python user gets a dataclass with type stubs, the Go user gets a cgo package, and neither knows the other exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that keeps them honest
&lt;/h2&gt;

&lt;p&gt;Generating bindings is the easy half. The hard half is trust: how do you know all sixteen behave the same after you change the core?&lt;/p&gt;

&lt;p&gt;Alef's answer is shared fixtures. You write one set of JSON fixtures describing inputs and expected outputs, and it generates a cross-language e2e suite plus standalone registry-mode test apps from them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;alef e2e        &lt;span class="c"&gt;# generate the cross-language suite from shared fixtures&lt;/span&gt;
alef test-apps  &lt;span class="c"&gt;# standalone apps that install the published package and run it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now a change in the Rust core does not surface as a runtime surprise in one binding. It surfaces as the same failing assertion in every language at once, in CI, before release. The fixtures are the contract, and every SDK is held to it.&lt;/p&gt;

&lt;p&gt;Generated files also carry an Alef hash, so &lt;code&gt;alef verify --exit-code&lt;/code&gt; fails the build if someone hand-edits a generated binding or forgets to regenerate after a core change. The generated layer stays generated.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest tradeoffs
&lt;/h2&gt;

&lt;p&gt;This is not free, and it is not magic.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The core has to be designed for it.&lt;/strong&gt; A stable, extractable API surface is a constraint you accept up front. Exotic Rust signatures do not always have a clean home in every target language, and the ones that matter live behind adapters rather than leaking into the core.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idioms live in the adapter, not the core.&lt;/strong&gt; Async wrappers, context managers, callbacks, error types: these are per-language, generated per-language, and configured in &lt;code&gt;[crates.&amp;lt;language&amp;gt;]&lt;/code&gt; sections. Shared semantics stay in the core so they cannot drift; ergonomics stay local.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When codegen is not enough, you extend it.&lt;/strong&gt; Domain-specific generation (an HTTP service API, a plugin registry) is an &lt;code&gt;Extension&lt;/code&gt; trait rather than a fork, shipped as a linked binary, a dynamic library, or template-only blocks in &lt;code&gt;alef.toml&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We build xberg this way because the alternative, sixteen hand-maintained binding layers chasing one moving core, is exactly the kind of work that reads productive and quietly sinks a polyglot project. Generate the boring layer, test it against one fixture set, and spend the human time on the engine that is actually the product.&lt;/p&gt;

&lt;p&gt;Alef is MIT and standalone: &lt;a href="https://github.com/xberg-io/alef" rel="noopener noreferrer"&gt;github.com/xberg-io/alef&lt;/a&gt;. If you maintain a Rust library that people keep asking for bindings to, it is worth a look.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>programming</category>
      <category>opensource</category>
      <category>ffi</category>
    </item>
    <item>
      <title>Code review can't keep up with AI. Build a verification layer instead.</title>
      <dc:creator>Na'aman Hirschfeld (Goldziher)</dc:creator>
      <pubDate>Sun, 05 Jul 2026 10:48:07 +0000</pubDate>
      <link>https://dev.to/nhirschfeld/code-review-cant-keep-up-with-ai-build-a-verification-layer-instead-1oh4</link>
      <guid>https://dev.to/nhirschfeld/code-review-cant-keep-up-with-ai-build-a-verification-layer-instead-1oh4</guid>
      <description>&lt;p&gt;There is a popular argument going around that code review no longer matters. The reasoning is that a model wrote the code, another model can check it, and a human reading the diff is a slow, expensive step you can now skip. I think that gets the problem exactly backwards.&lt;/p&gt;

&lt;p&gt;Code review did not stop mattering. It stopped scaling. You can now generate code faster than any human can read it, let alone reason about it. The old model, where an engineer reading the diff is the last line of defense, quietly broke the moment a single afternoon started producing more change than a person can hold in their head. Skipping review does not fix that. It just removes the one check that was already failing and puts nothing in its place.&lt;/p&gt;

&lt;p&gt;The answer is to move verification onto things that keep up. I have been building xberg, a document-intelligence engine with a Rust core and a dozen language SDKs in one repo, and this is the setup that lets me ship at generation speed without shipping garbage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static analysis and types do the cheap work
&lt;/h2&gt;

&lt;p&gt;The first layer is everything a machine can check without running your program: linting, static analysis, type checks. This is the cheapest verification you will ever buy, and it catches whole classes of errors before a test ever runs. A type error, an unused binding, a dangerous cast, a lint rule you agreed to as a team, none of these need a human to notice.&lt;/p&gt;

&lt;p&gt;For a polyglot repo this is its own project. I ended up building polylint, two self-contained Rust binaries driven by one config, because wiring a separate linter and formatter per language into pre-commit was itself a maintenance tax. The point is not the tool. The point is that mechanical checks should run on every change, locally and in CI, and they should be fast enough that nobody is tempted to skip them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comprehensive tests prove behavior
&lt;/h2&gt;

&lt;p&gt;Static analysis tells you the code is well formed. It does not tell you the code is correct. That is what tests are for, and at generation speed the test suite is doing far more of the reviewing than any human is.&lt;/p&gt;

&lt;p&gt;The tests that earn their keep here are end-to-end and integration tests. Unit tests are useful, but a wall of them can be green while the system as a whole does the wrong thing. What you want is tests that exercise real paths through the software the way a user would: real inputs, real boundaries, real failure modes. When I add a format to xberg's extraction pipeline, the test that matters is the one that runs a real file of that format end to end and checks the output, not the one that mocks the parser and asserts it was called.&lt;/p&gt;

&lt;p&gt;Comprehensive coverage of behavior is what lets you accept a large generated change with confidence. The suite is the reviewer that never gets tired and never skims.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agents review against written guidelines
&lt;/h2&gt;

&lt;p&gt;Here is where the "another model can check it" idea is actually right, as long as you give it something to check against. An agent doing a first-pass review is fast, tireless, and consistent, but only if the standard it reviews against is explicit and written down. Vague instructions produce vague reviews.&lt;/p&gt;

&lt;p&gt;So write the guidelines. The conventions, the security rules, the patterns you want and the ones you have banned, the things a reviewer on your team would flag. I keep those rules in one place and generate the per-tool configs from it with ai-rulez, so Cursor, Claude, and Copilot all review against the same standard instead of three slightly different ones. An agent with clear guidelines catches a real fraction of what a human reviewer would, at a speed a human never will, and it frees the human to look at the things that actually need judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then you actually test it, by hand
&lt;/h2&gt;

&lt;p&gt;None of the above removes the need to run the software yourself. This is the step people are most tempted to skip now, and it is the one that catches what everything else misses.&lt;/p&gt;

&lt;p&gt;A green pipeline is a signal. It is not proof that your software does what you think it does. Automated tests only check the things you thought to check. Running the thing yourself, exercising the real paths, poking at the edges, is how you find the assumption the whole test suite quietly shares with the bug. QA is not a box you tick after the checks pass. At generation speed it becomes one of the highest-leverage things an engineer does, because it is the only layer that brings human judgment to bear on software no human wrote line by line.&lt;/p&gt;

&lt;p&gt;Teams that treat QA as a core engineering function, rather than a phase they graduated out of, are the ones this speed will not break.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open source makes the code better, for free
&lt;/h2&gt;

&lt;p&gt;There is one more layer, and it is the strongest one I know of. Open source your code and it gets tested at a scale you could never reproduce in-house. Thousands of people run it in environments you never imagined, on inputs you never thought to generate, and every bug they hit and report comes back and makes the code more robust. No internal test suite competes with real usage at that volume.&lt;/p&gt;

&lt;p&gt;Auditability and good citizenship are real, but the benefit that changes the code itself is simpler. Public code is massively tested code, and massively tested code is better code. Every one of xberg's libraries is open source partly for this reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of it
&lt;/h2&gt;

&lt;p&gt;Put together, the verification layer looks like this: static analysis and types catch the cheap errors, comprehensive end-to-end and integration tests prove the behavior, agents do first-pass review against written guidelines, you test it by hand to catch what the machines missed, and open source turns your users into the largest test suite you will ever have.&lt;/p&gt;

&lt;p&gt;Code review did not become optional. It became a system instead of a person. Build that system, and you can move as fast as the models let you without lying to yourself about whether it works.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>devtools</category>
      <category>softwarequality</category>
    </item>
    <item>
      <title>Deleting the toolchain: linting a polyglot monorepo with one Rust binary</title>
      <dc:creator>Na'aman Hirschfeld (Goldziher)</dc:creator>
      <pubDate>Sat, 04 Jul 2026 08:02:42 +0000</pubDate>
      <link>https://dev.to/nhirschfeld/deleting-the-toolchain-linting-a-polyglot-monorepo-with-one-rust-binary-7om</link>
      <guid>https://dev.to/nhirschfeld/deleting-the-toolchain-linting-a-polyglot-monorepo-with-one-rust-binary-7om</guid>
      <description>&lt;p&gt;A repository that mixes languages pays a tax most single-language repos never see. Python wants ruff, and ruff wants a Python. JavaScript and TypeScript want oxlint or prettier, and those want a Node. Shell wants shfmt and shellcheck. Then taplo for TOML, rumdl for Markdown, sqruff for SQL, hadolint for Dockerfiles, and one more for whatever you added last month. Each tool is installed separately, pinned separately, and wired into &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt; separately.&lt;/p&gt;

&lt;p&gt;The result is slow setup, version drift, CI images fat with language runtimes you only need for linting, and a hook stack that breaks the day a system dependency goes missing or bumps a version. I hit this hard while building xberg, a document-intelligence engine with a Rust core and a dozen language SDKs in one repo, which is where the itch to fix it came from.&lt;/p&gt;

&lt;p&gt;polylint is the fix I landed on. It ships two self-contained Rust binaries, &lt;code&gt;poly lint&lt;/code&gt; and &lt;code&gt;poly fmt&lt;/code&gt;, driven by one &lt;code&gt;poly.toml&lt;/code&gt;. No language runtime is required on the default path. The goal was not to add one more tool beside the stack. It was to delete the stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  What using it looks like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poly &lt;span class="nb"&gt;fmt&lt;/span&gt; &lt;span class="nt"&gt;--check&lt;/span&gt;
&lt;span class="go"&gt;would format crates/example/src/main.rs

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poly &lt;span class="nb"&gt;fmt&lt;/span&gt; &lt;span class="nt"&gt;--fix&lt;/span&gt;
&lt;span class="go"&gt;formatted 1 file

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poly lint &lt;span class="nt"&gt;--format&lt;/span&gt; toon
&lt;span class="go"&gt;path: crates/example/src/main.rs
diagnostics[0]: engine=ruff, code=F401, severity=warning, title="`os` imported but unused"

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poly hooks &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="go"&gt;installed git hooks: pre-commit, commit-msg
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;poly fmt&lt;/code&gt; is a dry run by default, which is what you want in CI. Add &lt;code&gt;--fix&lt;/code&gt; to write changes. &lt;code&gt;poly hooks install&lt;/code&gt; wires the git hooks once, and lint, format, and commit checks then run on every &lt;code&gt;git commit&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it avoids the runtimes
&lt;/h2&gt;

&lt;p&gt;There are three tiers, and a file falls through them until something can handle it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Curated Rust backends, in process.&lt;/strong&gt; The Rust ecosystem has grown real, high-quality linters and formatters that are libraries, not just CLIs. polylint links them directly: oxc for JS/TS, ruff internals for Python, taplo for TOML, rumdl for Markdown, sqruff for SQL, malva and markup_fmt for CSS and HTML, mago for PHP. Every backend implements one trait and returns one diagnostic shape, so reporting, caching, and the MCP server stay uniform no matter which tool did the work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;Engine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Send&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Sync&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Language&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Capabilities&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// lint / format / fix&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                 &lt;span class="c1"&gt;// folded into the cache key&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;lint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;SourceFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;EngineConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Diagnostic&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;SourceFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;EngineConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FormatOutput&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;&lt;strong&gt;A tree-sitter fallback for everything else.&lt;/strong&gt; For languages without a dedicated backend, polylint uses &lt;code&gt;tree-sitter-language-pack&lt;/code&gt;, parses the source, and re-emits it with structural reindentation and whitespace normalization. This covers hundreds of grammars, including Java, using each language's &lt;code&gt;indents.scm&lt;/code&gt; queries. So you get consistent indentation and clean whitespace on a Java file with no JDK on the machine. Grammars where whitespace carries meaning are left untouched on purpose, since a reindent there would corrupt the file rather than tidy it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An opt-in catalog for the rest.&lt;/strong&gt; Some tools have no Rust equivalent yet. For those, polylint vendors a tool catalog derived from the &lt;a href="https://github.com/hougesen/mdsf" rel="noopener noreferrer"&gt;mdsf&lt;/a&gt; project, which maps a tool to its binary, argument vector, and languages. It is data only, so polylint does not depend on or shell out to mdsf itself. Each catalog tool is probed on &lt;code&gt;PATH&lt;/code&gt; and skipped when absent, so enabling one never makes a run fail on a machine that lacks it.&lt;/p&gt;

&lt;p&gt;Configuration for all of this lives in one file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[defaults]&lt;/span&gt;
&lt;span class="py"&gt;line_length&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;
&lt;span class="py"&gt;line_ending&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"lf"&lt;/span&gt;

&lt;span class="nn"&gt;[lint.python.ruff]&lt;/span&gt;
&lt;span class="py"&gt;select&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"E"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"F"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"W"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nn"&gt;[per-file-ignores]&lt;/span&gt;
&lt;span class="py"&gt;"tests/**"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"F401"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nn"&gt;[hooks.builtin]&lt;/span&gt;
&lt;span class="py"&gt;polylint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;polyfmt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;commit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;stages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"commit-msg"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;file_safety&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;[hooks]&lt;/code&gt; table replaces &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt; outright. polylint dogfoods it: the commit-msg hook runs Conventional Commit checks and strips AI-attribution trailers, and the file-safety builtin catches merge-conflict markers, large files, leaked private keys, and case collisions, all in pure Rust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it is fast enough to run on every commit
&lt;/h2&gt;

&lt;p&gt;Two things do the work. Nothing spawns a subprocess per file on the default path, so there is no process-startup cost multiplied across a large tree, and the whole run is a rayon parallel loop over files.&lt;/p&gt;

&lt;p&gt;On top of that sits a blake3 result cache. The cache key is not just the file contents. It folds in the engine name, the engine's own version, and the resolved config, alongside the file bytes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;key = blake3(namespace, engine, engine_version, resolved_config, file_bytes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That detail matters more than it looks. A plain content hash would happily serve you a stale result after you upgraded a formatter or edited a rule, because the bytes did not change. Folding the tool version and config into the key means an upgrade or a config edit invalidates exactly the affected entries and nothing else.&lt;/p&gt;

&lt;p&gt;In CI, the binary itself is cached by version, OS, and architecture, so a run downloads it once and reuses it until you bump the version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lint and Format&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;lint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses: actions/checkout@v4      - uses: Goldziher/polylint@v1      - run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;poly lint .&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;poly fmt --check .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The tradeoffs
&lt;/h2&gt;

&lt;p&gt;I would rather name these than have you find them.&lt;/p&gt;

&lt;p&gt;The binary is large, around 70 MB per platform, because it bundles several big parsers and linters. For a tool you install once and cache in CI, I think that is the right trade against carrying zero system dependencies, but if you expected a 3 MB download, that is not this.&lt;/p&gt;

&lt;p&gt;The coverage is uneven by design. Some backends lint only, some format only, so it is not true that every language gets every capability. The tree-sitter tier is a formatter, not a linter, and it deliberately does nothing on whitespace-sensitive grammars rather than risk a destructive rewrite.&lt;/p&gt;

&lt;p&gt;And it is not fully subprocess-free in every case. &lt;code&gt;rustfmt&lt;/code&gt; and &lt;code&gt;gofmt&lt;/code&gt; are used when present, because reimplementing them in process is not worth it, and a few other native formatters are opt-in. The default path stays runtime-free; those are the documented exceptions.&lt;/p&gt;

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

&lt;p&gt;polylint is MIT licensed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;polylint
&lt;span class="c"&gt;# or&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;Goldziher/tap/polylint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source and docs are at &lt;a href="https://github.com/Goldziher/polylint" rel="noopener noreferrer"&gt;github.com/Goldziher/polylint&lt;/a&gt;. If you run a polyglot repo and your pre-commit config has quietly become its own maintenance project, point &lt;code&gt;poly lint&lt;/code&gt; at it and see what falls out.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>devtools</category>
      <category>tooling</category>
      <category>showdev</category>
    </item>
    <item>
      <title>When an LLM response fails validation, feed the error back into the retry</title>
      <dc:creator>Na'aman Hirschfeld (Goldziher)</dc:creator>
      <pubDate>Thu, 02 Jul 2026 05:17:08 +0000</pubDate>
      <link>https://dev.to/nhirschfeld/when-an-llm-response-fails-validation-feed-the-error-back-into-the-retry-2e1e</link>
      <guid>https://dev.to/nhirschfeld/when-an-llm-response-fails-validation-feed-the-error-back-into-the-retry-2e1e</guid>
      <description>&lt;p&gt;If you ask an LLM for structured output and validate it against a schema, you already know the failure&lt;br&gt;
mode: most of the time it is fine, and every so often it hands back something that does not parse or&lt;br&gt;
misses a required field. The usual reflex is to wrap the call in a retry and move on.&lt;/p&gt;

&lt;p&gt;The problem is that a plain retry is the same prompt, the same temperature, roughly the same odds. You&lt;br&gt;
are paying for another round and hoping the dice land differently.&lt;/p&gt;

&lt;p&gt;There is a better move, and it is almost free to add: when validation fails, put the validation error&lt;br&gt;
and the model's own bad output back into the next prompt, and ask it to fix that specific thing.&lt;/p&gt;

&lt;p&gt;Here is the core of the loop I used for this on a RAG platform I built (trimmed to the essential path):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;max_attempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;msgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;error_message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;msgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;msgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error_message&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;        &lt;span class="c1"&gt;# last attempt's error rides along
&lt;/span&gt;        &lt;span class="n"&gt;response&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;make_completion_request&lt;/span&gt;&lt;span class="p"&gt;(...,&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                  &lt;span class="c1"&gt;# raises ValidationError on bad output
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;error_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        The last response from the API failed validation due to the following error:
            &amp;lt;error&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;format_error_for_llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;/error&amp;gt;
        Your task is to fix the error and return the corrected response data:
            &amp;lt;data&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;/data&amp;gt;
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details do the work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The error is described for the model, not for a log.&lt;/strong&gt; &lt;code&gt;format_error_for_llm&lt;/code&gt; turns the raw
validation exception into a plain instruction ("field X must be an integer, you sent a string"). The
model is good at patching a concrete, named mistake; it is bad at guessing why an opaque retry keeps
failing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You hand back its own previous output as the thing to correct.&lt;/strong&gt; It is not regenerating from
scratch, it is editing. That keeps the parts that were already right and usually fixes the one field
that was wrong on the first pass.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The tradeoffs, because there always are some:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It costs an extra call on a bad response, and the follow-up prompt is longer (it carries the error
plus the prior payload). On a schema that fails often, that adds up. Cap the attempts.&lt;/li&gt;
&lt;li&gt;It only works when the bad response is parseable enough to serialize back into the prompt. Truly
empty or truncated output has nothing to correct, so you still need a normal retry underneath.&lt;/li&gt;
&lt;li&gt;Prompt semantics do not always transfer if you also fail over between providers mid-loop. If you do
that, do not count a provider swap as a real attempt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the whole idea. It is not a framework, it is about ten lines around a call you already have. If&lt;br&gt;
you are generating structured output at any volume, it turns a chunk of your "model was flaky" retries&lt;br&gt;
into first-try-after-feedback successes.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>python</category>
      <category>ai</category>
      <category>rag</category>
    </item>
    <item>
      <title>Benchmark-Driven Development: let agents build the harness you never had time for</title>
      <dc:creator>Na'aman Hirschfeld (Goldziher)</dc:creator>
      <pubDate>Tue, 30 Jun 2026 04:41:22 +0000</pubDate>
      <link>https://dev.to/nhirschfeld/benchmark-driven-development-let-agents-build-the-harness-you-never-had-time-for-5ha4</link>
      <guid>https://dev.to/nhirschfeld/benchmark-driven-development-let-agents-build-the-harness-you-never-had-time-for-5ha4</guid>
      <description>&lt;p&gt;Most teams ship on two signals: does it compile, and do the tests pass. Both are correctness signals. Neither tells you whether the thing is fast, whether it got slower this week, or whether the output is actually good against some ground truth. We close that gap with a third signal, and we let it drive the work. We call it benchmark-driven development.&lt;/p&gt;

&lt;p&gt;The loop is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write the plan and the spec first. Decide what the code should do before writing it.&lt;/li&gt;
&lt;li&gt;Use TDD to drive it to correct. Red, green, refactor, the usual.&lt;/li&gt;
&lt;li&gt;Test it for real with proper benchmarks, against ground truth, with profiling.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Correctness and performance are both requirements. So both get measured, on every change, not once at the end when a customer complains.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why benchmarks used to be a luxury
&lt;/h2&gt;

&lt;p&gt;Everyone agrees benchmarks are good. Almost no one builds a serious one. The reason is cost. A real harness is its own project: a fixture corpus with trustworthy ground truth, scoring that reflects quality and not just a checksum, resource sampling, a way to compare across implementations, and CI plumbing to run it all and publish results. That is weeks of work that ships no features. So it gets cut, and teams fall back to a stopwatch around a hot loop and a vibe about whether output looks right.&lt;/p&gt;

&lt;p&gt;This is the part that changed. With an agent in the loop, the harness is no longer a quarter of engineering budget. It is a few focused sessions. You describe the corpus, the metrics, and the comparison you want, and you iterate on the harness the same way you iterate on a feature. The economics flipped. The thing that was too expensive to justify is now the cheap part.&lt;/p&gt;

&lt;h2&gt;
  
  
  A concrete harness
&lt;/h2&gt;

&lt;p&gt;To make this concrete, here is the benchmark harness we run for xberg, our document-extraction engine. It is a Rust CLI, and it does the following.&lt;/p&gt;

&lt;p&gt;It compares 13 language bindings of our own engine against 7 external reference frameworks, across a corpus of 318 fixtures that carry markdown ground truth, spanning 17 formats: PDF, HTML, DOCX, ODT, RTF, XLSX, CSV, EPUB, PPTX, and more.&lt;/p&gt;

&lt;p&gt;It scores quality two ways, because text and structure fail differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Text F1 (TF1): token-level bag-of-words F1 between extracted text and ground truth, with a separate numeric-token score for number-heavy documents like financial and scientific PDFs.&lt;/li&gt;
&lt;li&gt;Structural F1 (SF1): block-level matching between extracted markdown and ground-truth markdown. Headings, code blocks, formulas, tables, list items, and images are weighted by how much they matter, matched greedily with fuzzy cross-type compatibility, and scored for ordering with a longest-increasing-subsequence pass.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When markdown ground truth is present it combines them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;quality_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;f1_text&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;f1_numeric&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;f1_layout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It samples CPU and memory during each run, and behind a feature flag it generates flamegraphs so a regression in latency points you straight at the function that caused it. It runs each binding in both single-file mode for fair latency and batch mode for throughput. And it runs as a CI matrix: build, validate ground truth, fan out one job per binding, gate, run the external frameworks, then consolidate everything into percentiles and publish the aggregate as a release artifact.&lt;/p&gt;

&lt;p&gt;None of that is exotic. All of it used to be too much work to justify for anything but a flagship. That is the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "real benchmarks" actually means
&lt;/h2&gt;

&lt;p&gt;A benchmark you can drive development with has to clear a higher bar than a stopwatch around a hot loop. Four properties matter.&lt;/p&gt;

&lt;p&gt;Ground truth you trust. A number is only as good as what it is measured against. We keep ground truth in version control, validate its integrity on every CI run, and clean known artifacts so the target does not drift silently. If you cannot defend the reference, you cannot defend the score.&lt;/p&gt;

&lt;p&gt;Metrics that reflect the goal. "Bytes match" is not quality for document extraction. A table flattened into a paragraph can have high text overlap and be useless. That is why structure gets its own score and its own weights. Pick metrics that punish the failures you actually care about.&lt;/p&gt;

&lt;p&gt;Resource truth, not just wall-clock. Throughput hides memory blowups. We sample CPU and memory and keep flamegraphs around so "it got slower" turns into "this function got slower."&lt;/p&gt;

&lt;p&gt;Comparison and regression gates. A score in isolation is trivia. The harness compares pipelines and implementations against each other and against history, and it can fail the build on a quality regression. That is what makes it a development driver rather than a report.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to adopt it without a big-bang rewrite
&lt;/h2&gt;

&lt;p&gt;You do not need 318 fixtures on day one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start with ten fixtures and one honest metric. Ten documents you understand beat a thousand you cannot defend.&lt;/li&gt;
&lt;li&gt;Write the spec, then the failing test, then make it pass. Keep TDD for correctness. The benchmark is the layer on top, not a replacement.&lt;/li&gt;
&lt;li&gt;Add a benchmark the first time you guess about performance. The moment you say "this is probably fine," measure it instead.&lt;/li&gt;
&lt;li&gt;Put it in CI and gate on regressions once the metric is stable. A benchmark nobody runs is documentation.&lt;/li&gt;
&lt;li&gt;Lean on an agent for the unglamorous parts: fixture wrangling, scoring code, CI matrix, percentile aggregation. This is exactly the work that used to make a harness too expensive, and it is exactly the work an agent is good at.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The payoff
&lt;/h2&gt;

&lt;p&gt;When correctness and speed are both measured on every change, you stop guessing and you stop arguing from intuition. A refactor either held the line on quality and latency or it did not, and the harness tells you which before the diff merges. Spec-driven, TDD for correctness, real benchmarks for the rest. Build the harness you never had time for, because now you do.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>performance</category>
      <category>ai</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Kreuzberg v4.0.0-RC.8 is Available</title>
      <dc:creator>Na'aman Hirschfeld (Goldziher)</dc:creator>
      <pubDate>Mon, 15 Dec 2025 13:06:14 +0000</pubDate>
      <link>https://dev.to/kreuzberg/kreuzberg-v400-rc8-is-available-4fma</link>
      <guid>https://dev.to/kreuzberg/kreuzberg-v400-rc8-is-available-4fma</guid>
      <description>&lt;p&gt;Hi Peeps,&lt;/p&gt;

&lt;p&gt;I'm excited to announce that &lt;a href="https://github.com/kreuzberg-dev/kreuzberg" rel="noopener noreferrer"&gt;Kreuzberg&lt;/a&gt; v4.0.0 is coming very soon. We will release v4.0.0 at the beginning of next year - in just a couple of weeks time. For now, v4.0.0-rc.8 has been released to all channels.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Kreuzberg?
&lt;/h2&gt;

&lt;p&gt;Kreuzberg is a document intelligence toolkit for extracting text, metadata, tables, images, and structured data from 56+ file formats. It was originally written in Python (v1-v3), where it demonstrated strong performance characteristics compared to alternatives in the ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's new in V4?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A Complete Rust Rewrite with Polyglot Bindings
&lt;/h3&gt;

&lt;p&gt;The new version of Kreuzberg represents a massive architectural evolution. &lt;strong&gt;Kreuzberg has been completely rewritten in Rust&lt;/strong&gt; - leveraging Rust's memory safety, zero-cost abstractions, and native performance. The new architecture consists of a high-performance Rust core with native bindings to multiple languages. That's right - it's no longer just a Python library.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kreuzberg v4 is now available for 7 languages across 8 runtime bindings:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rust&lt;/strong&gt; (native library)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python&lt;/strong&gt; (PyO3 native bindings)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt; - Node.js (NAPI-RS native bindings) + Deno/Browser/Edge (WASM)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ruby&lt;/strong&gt; (Magnus FFI)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Java 25+&lt;/strong&gt; (Panama Foreign Function &amp;amp; Memory API)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;C#&lt;/strong&gt; (P/Invoke)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go&lt;/strong&gt; (cgo bindings)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Post v4.0.0 roadmap includes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PHP&lt;/li&gt;
&lt;li&gt;Elixir (via Rustler - with Erlang and Gleam interop)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally, it's available as a &lt;strong&gt;CLI&lt;/strong&gt; (installable via &lt;code&gt;cargo&lt;/code&gt; or &lt;code&gt;homebrew&lt;/code&gt;), &lt;strong&gt;HTTP REST API server&lt;/strong&gt;, &lt;strong&gt;Model Context Protocol (MCP) server&lt;/strong&gt; for Claude Desktop/Continue.dev, and as &lt;strong&gt;public Docker images&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the Rust Rewrite? Performance and Architecture
&lt;/h3&gt;

&lt;p&gt;The Rust rewrite wasn't just about performance - though that's a major benefit. It was an opportunity to fundamentally rethink the architecture:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural improvements:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero-copy operations&lt;/strong&gt; via Rust's ownership model&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;True async concurrency&lt;/strong&gt; with Tokio runtime (no GIL limitations)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming parsers&lt;/strong&gt; for constant memory usage on multi-GB files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SIMD-accelerated text processing&lt;/strong&gt; for token reduction and string operations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory-safe FFI boundaries&lt;/strong&gt; for all language bindings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plugin system&lt;/strong&gt; with trait-based extensibility&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  v3 vs v4: What Changed?
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;v3 (Python)&lt;/th&gt;
&lt;th&gt;v4 (Rust Core)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Core Language&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pure Python&lt;/td&gt;
&lt;td&gt;Rust 2024 edition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;File Formats&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;30-40+ (via Pandoc)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;56+ (native parsers)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Language Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Python only&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;7 languages&lt;/strong&gt; (Rust/Python/TS/Ruby/Java/Go/C#)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dependencies&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requires Pandoc (system binary)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Zero system dependencies&lt;/strong&gt; (all native)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Embeddings&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;td&gt;✓ FastEmbed with ONNX (3 presets + custom)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Semantic Chunking&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Via semantic-text-splitter library&lt;/td&gt;
&lt;td&gt;✓ Built-in (text + markdown-aware)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Token Reduction&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built-in (TF-IDF based)&lt;/td&gt;
&lt;td&gt;✓ Enhanced with 3 modes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Language Detection&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Optional (fast-langdetect)&lt;/td&gt;
&lt;td&gt;✓ Built-in (68 languages)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Keyword Extraction&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Optional (KeyBERT)&lt;/td&gt;
&lt;td&gt;✓ Built-in (YAKE + RAKE algorithms)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OCR Backends&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tesseract/EasyOCR/PaddleOCR&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Same + better integration&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Plugin System&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Limited extractor registry&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Full trait-based&lt;/strong&gt; (4 plugin types)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Page Tracking&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Character-based indices&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Byte-based with O(1) lookup&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Servers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;REST API (Litestar)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;HTTP (Axum) + MCP + MCP-SSE&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Installation Size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~100MB base&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;16-31 MB complete&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory Model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Python heap management&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;RAII with streaming&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Concurrency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;asyncio (GIL-limited)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Tokio work-stealing&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Replacement of Pandoc - Native Performance
&lt;/h3&gt;

&lt;p&gt;Kreuzberg v3 relied on &lt;strong&gt;Pandoc&lt;/strong&gt; - an amazing tool, but one that had to be invoked via subprocess because of its GPL license. This had significant impacts:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v3 Pandoc limitations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;System dependency (installation required)&lt;/li&gt;
&lt;li&gt;Subprocess overhead on every document&lt;/li&gt;
&lt;li&gt;No streaming support&lt;/li&gt;
&lt;li&gt;Limited metadata extraction&lt;/li&gt;
&lt;li&gt;~500MB+ installation footprint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;v4 native parsers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero external dependencies&lt;/strong&gt; - everything is native Rust&lt;/li&gt;
&lt;li&gt;Direct parsing with full control over extraction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Substantially more metadata&lt;/strong&gt; extracted (e.g., DOCX document properties, section structure, style information)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming support&lt;/strong&gt; for massive files (tested on multi-GB XML documents with stable memory)&lt;/li&gt;
&lt;li&gt;Example: PPTX extractor is now a &lt;strong&gt;fully streaming parser&lt;/strong&gt; capable of handling gigabyte-scale presentations with constant memory usage and high throughput&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  New File Format Support
&lt;/h3&gt;

&lt;p&gt;v4 expanded format support from ~20 to &lt;strong&gt;56+ file formats&lt;/strong&gt;, including:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Added legacy format support:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.doc&lt;/code&gt; (Word 97-2003)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.ppt&lt;/code&gt; (PowerPoint 97-2003)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.xls&lt;/code&gt; (Excel 97-2003)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.eml&lt;/code&gt; (Email messages)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.msg&lt;/code&gt; (Outlook messages)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Added academic/technical formats:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LaTeX (&lt;code&gt;.tex&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;BibTeX (&lt;code&gt;.bib&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Typst (&lt;code&gt;.typ&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;JATS XML (scientific articles)&lt;/li&gt;
&lt;li&gt;DocBook XML&lt;/li&gt;
&lt;li&gt;FictionBook (&lt;code&gt;.fb2&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;OPML (&lt;code&gt;.opml&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Better Office support:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;XLSB, XLSM (Excel binary/macro formats)&lt;/li&gt;
&lt;li&gt;Better structured metadata extraction from DOCX/PPTX/XLSX&lt;/li&gt;
&lt;li&gt;Full table extraction from presentations&lt;/li&gt;
&lt;li&gt;Image extraction with deduplication&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  New Features: Full Document Intelligence Solution
&lt;/h3&gt;

&lt;p&gt;The v4 rewrite was also an opportunity to close gaps with commercial alternatives and add features specifically designed for &lt;strong&gt;RAG applications and LLM workflows&lt;/strong&gt;:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. &lt;strong&gt;Embeddings (NEW)&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FastEmbed integration&lt;/strong&gt; with full ONNX Runtime acceleration&lt;/li&gt;
&lt;li&gt;Three presets: &lt;code&gt;"fast"&lt;/code&gt; (384d), &lt;code&gt;"balanced"&lt;/code&gt; (512d), &lt;code&gt;"quality"&lt;/code&gt; (768d/1024d)&lt;/li&gt;
&lt;li&gt;Custom model support (bring your own ONNX model)&lt;/li&gt;
&lt;li&gt;Local generation (no API calls, no rate limits)&lt;/li&gt;
&lt;li&gt;Automatic model downloading and caching&lt;/li&gt;
&lt;li&gt;Per-chunk embedding generation
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;kreuzberg&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ExtractionConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EmbeddingConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EmbeddingModelType&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ExtractionConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;EmbeddingConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;EmbeddingModelType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;balanced&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kreuzberg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# result.embeddings contains vectors for each chunk
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. &lt;strong&gt;Semantic Text Chunking (NOW BUILT-IN)&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Now integrated directly into the core (v3 used external semantic-text-splitter library):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Structure-aware chunking&lt;/strong&gt; that respects document semantics&lt;/li&gt;
&lt;li&gt;Two strategies:

&lt;ul&gt;
&lt;li&gt;Generic text chunker (whitespace/punctuation-aware)&lt;/li&gt;
&lt;li&gt;Markdown chunker (preserves headings, lists, code blocks, tables)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Configurable chunk size and overlap&lt;/li&gt;
&lt;li&gt;Unicode-safe (handles CJK, emojis correctly)&lt;/li&gt;
&lt;li&gt;Automatic chunk-to-page mapping&lt;/li&gt;
&lt;li&gt;Per-chunk metadata with byte offsets&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3. &lt;strong&gt;Byte-Accurate Page Tracking (BREAKING CHANGE)&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;This is a critical improvement for LLM applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;v3&lt;/strong&gt;: Character-based indices (&lt;code&gt;char_start&lt;/code&gt;/&lt;code&gt;char_end&lt;/code&gt;) - incorrect for UTF-8 multi-byte characters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;v4&lt;/strong&gt;: Byte-based indices (&lt;code&gt;byte_start&lt;/code&gt;/&lt;code&gt;byte_end&lt;/code&gt;) - correct for all string operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additional page features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O(1) lookup: "which page is byte offset X on?" → instant answer&lt;/li&gt;
&lt;li&gt;Per-page content extraction&lt;/li&gt;
&lt;li&gt;Page markers in combined text (e.g., &lt;code&gt;--- Page 5 ---&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Automatic chunk-to-page mapping for citations&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  4. &lt;strong&gt;Enhanced Token Reduction for LLM Context&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Enhanced from v3 with three configurable modes to save on LLM costs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Light mode&lt;/strong&gt;: ~15% reduction (preserve most detail)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Moderate mode&lt;/strong&gt;: ~30% reduction (balanced)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aggressive mode&lt;/strong&gt;: ~50% reduction (key information only)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Uses TF-IDF sentence scoring with position-aware weighting and language-specific stopword filtering. SIMD-accelerated for improved performance over v3.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. &lt;strong&gt;Language Detection (NOW BUILT-IN)&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;68 language support with confidence scoring&lt;/li&gt;
&lt;li&gt;Multi-language detection (documents with mixed languages)&lt;/li&gt;
&lt;li&gt;ISO 639-1 and ISO 639-3 code support&lt;/li&gt;
&lt;li&gt;Configurable confidence thresholds&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  6. &lt;strong&gt;Keyword Extraction (NOW BUILT-IN)&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Now built into core (previously optional KeyBERT in v3):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;YAKE&lt;/strong&gt; (Yet Another Keyword Extractor): Unsupervised, language-independent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAKE&lt;/strong&gt; (Rapid Automatic Keyword Extraction): Fast statistical method&lt;/li&gt;
&lt;li&gt;Configurable n-grams (1-3 word phrases)&lt;/li&gt;
&lt;li&gt;Relevance scoring with language-specific stopwords&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  7. &lt;strong&gt;Plugin System (NEW)&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Four extensible plugin types for customization:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DocumentExtractor&lt;/strong&gt; - Custom file format handlers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OcrBackend&lt;/strong&gt; - Custom OCR engines (integrate your own Python models)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostProcessor&lt;/strong&gt; - Data transformation and enrichment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validator&lt;/strong&gt; - Pre-extraction validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plugins defined in Rust work across all language bindings. Python/TypeScript can define custom plugins with thread-safe callbacks into the Rust core.&lt;/p&gt;

&lt;h4&gt;
  
  
  8. &lt;strong&gt;Production-Ready Servers (NEW)&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTTP REST API&lt;/strong&gt;: Production-grade Axum server with OpenAPI docs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP Server&lt;/strong&gt;: Direct integration with Claude Desktop, Continue.dev, and other MCP clients&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP-SSE Transport&lt;/strong&gt; (RC.8): Server-Sent Events for cloud deployments without WebSocket support&lt;/li&gt;
&lt;li&gt;All three modes support the same feature set: extraction, batch processing, caching&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Performance: Benchmarked Against the Competition
&lt;/h2&gt;

&lt;p&gt;We maintain &lt;strong&gt;continuous benchmarks&lt;/strong&gt; comparing Kreuzberg against the leading OSS alternatives:&lt;/p&gt;

&lt;h3&gt;
  
  
  Benchmark Setup
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Platform&lt;/strong&gt;: Ubuntu 22.04 (GitHub Actions)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test Suite&lt;/strong&gt;: 30+ documents covering all formats&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metrics&lt;/strong&gt;: Latency (p50, p95), throughput (MB/s), memory usage, success rate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitors&lt;/strong&gt;: Apache Tika, Docling, Unstructured, MarkItDown&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How Kreuzberg Compares
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Installation Size&lt;/strong&gt; (critical for containers/serverless):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kreuzberg&lt;/strong&gt;: &lt;strong&gt;16-31 MB complete&lt;/strong&gt; (CLI: 16 MB, Python wheel: 22 MB, Java JAR: 31 MB - all features included)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MarkItDown&lt;/strong&gt;: ~251 MB installed (58.3 KB wheel, 25 dependencies)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unstructured&lt;/strong&gt;: ~146 MB minimal (open source base) - &lt;strong&gt;several GB with ML models&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docling&lt;/strong&gt;: ~1 GB base, &lt;strong&gt;9.74GB Docker image&lt;/strong&gt; (includes PyTorch CUDA)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apache Tika&lt;/strong&gt;: ~55 MB (tika-app JAR) + dependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GROBID&lt;/strong&gt;: 500MB (CRF-only) to &lt;strong&gt;8GB&lt;/strong&gt; (full deep learning)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Performance Characteristics:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Speed&lt;/th&gt;
&lt;th&gt;Accuracy&lt;/th&gt;
&lt;th&gt;Formats&lt;/th&gt;
&lt;th&gt;Installation&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Kreuzberg&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⚡ Fast (Rust-native)&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;56+&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;16-31 MB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;General-purpose, production-ready&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Docling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⚡ Fast (3.1s/pg x86, 1.27s/pg ARM)&lt;/td&gt;
&lt;td&gt;Best&lt;/td&gt;
&lt;td&gt;7+&lt;/td&gt;
&lt;td&gt;1-9.74 GB&lt;/td&gt;
&lt;td&gt;Complex documents, when accuracy &amp;gt; size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GROBID&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⚡⚡ Very Fast (10.6 PDF/s)&lt;/td&gt;
&lt;td&gt;Best&lt;/td&gt;
&lt;td&gt;PDF only&lt;/td&gt;
&lt;td&gt;0.5-8 GB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Academic/scientific papers only&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Unstructured&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⚡ Moderate&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;25-65+&lt;/td&gt;
&lt;td&gt;146 MB-several GB&lt;/td&gt;
&lt;td&gt;Python-native LLM pipelines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MarkItDown&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⚡ Fast (small files)&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;11+&lt;/td&gt;
&lt;td&gt;~251 MB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Lightweight Markdown conversion&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Apache Tika&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⚡ Moderate&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1000+&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~55 MB&lt;/td&gt;
&lt;td&gt;Enterprise, broadest format support&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Kreuzberg's sweet spot:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Smallest full-featured installation&lt;/strong&gt;: 16-31 MB complete (vs 146 MB-9.74 GB for competitors)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5-15x smaller&lt;/strong&gt; than Unstructured/MarkItDown, &lt;strong&gt;30-300x smaller&lt;/strong&gt; than Docling/GROBID&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rust-native performance&lt;/strong&gt; without ML model overhead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broad format support&lt;/strong&gt; (56+ formats) with native parsers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-language support&lt;/strong&gt; unique in the space (7 languages vs Python-only for most)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production-ready&lt;/strong&gt; with general-purpose design (vs specialized tools like GROBID)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Is Kreuzberg a SaaS Product?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No.&lt;/strong&gt; Kreuzberg is and will remain &lt;strong&gt;MIT-licensed open source&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;However, we are building &lt;strong&gt;Kreuzberg.cloud&lt;/strong&gt; - a commercial SaaS and self-hosted document intelligence solution built &lt;em&gt;on top of&lt;/em&gt; Kreuzberg. This follows the proven open-core model: the library stays free and open, while we offer a cloud service for teams that want managed infrastructure, APIs, and enterprise features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will Kreuzberg become commercially licensed?&lt;/strong&gt; Absolutely not. There is no BSL (Business Source License) in Kreuzberg's future. The library was MIT-licensed and will remain MIT-licensed. We're building the commercial offering as a separate product around the core library, not by restricting the library itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Target Audience
&lt;/h2&gt;

&lt;p&gt;Any developer or data scientist who needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Document text extraction (PDF, Office, images, email, archives, etc.)&lt;/li&gt;
&lt;li&gt;OCR (Tesseract, EasyOCR, PaddleOCR)&lt;/li&gt;
&lt;li&gt;Metadata extraction (authors, dates, properties, EXIF)&lt;/li&gt;
&lt;li&gt;Table and image extraction&lt;/li&gt;
&lt;li&gt;Document pre-processing for RAG pipelines&lt;/li&gt;
&lt;li&gt;Text chunking with embeddings&lt;/li&gt;
&lt;li&gt;Token reduction for LLM context windows&lt;/li&gt;
&lt;li&gt;Multi-language document intelligence in production systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Ideal for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RAG application developers&lt;/li&gt;
&lt;li&gt;Data engineers building document pipelines&lt;/li&gt;
&lt;li&gt;ML engineers preprocessing training data&lt;/li&gt;
&lt;li&gt;Enterprise developers handling document workflows&lt;/li&gt;
&lt;li&gt;DevOps teams needing lightweight, performant extraction in containers/serverless&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Comparison with Alternatives
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Open Source Python Libraries
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Unstructured.io&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strengths&lt;/strong&gt;: Established, modular, broad format support (25+ open source, 65+ enterprise), LLM-focused, good Python ecosystem integration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trade-offs&lt;/strong&gt;: Python GIL performance constraints, 146 MB minimal installation (several GB with ML models)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License&lt;/strong&gt;: Apache-2.0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When to choose&lt;/strong&gt;: Python-only projects where ecosystem fit &amp;gt; performance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;MarkItDown (Microsoft)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strengths&lt;/strong&gt;: Fast for small files, Markdown-optimized, simple API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trade-offs&lt;/strong&gt;: Limited format support (11 formats), less structured metadata, ~251 MB installed (despite small wheel), requires OpenAI API for images&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License&lt;/strong&gt;: MIT&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When to choose&lt;/strong&gt;: Markdown-only conversion, LLM consumption&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Docling (IBM)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strengths&lt;/strong&gt;: Excellent accuracy on complex documents (97.9% cell-level accuracy on tested sustainability report tables), state-of-the-art AI models for technical documents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trade-offs&lt;/strong&gt;: Massive installation (1-9.74 GB), high memory usage, GPU-optimized (underutilized on CPU)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License&lt;/strong&gt;: MIT&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When to choose&lt;/strong&gt;: Accuracy on complex documents &amp;gt; deployment size/speed, have GPU infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Open Source Java/Academic Tools
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Apache Tika&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strengths&lt;/strong&gt;: Mature, stable, broadest format support (1000+ types), proven at scale, Apache Foundation backing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trade-offs&lt;/strong&gt;: Java/JVM required, slower on large files, older architecture, complex dependency management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License&lt;/strong&gt;: Apache-2.0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When to choose&lt;/strong&gt;: Enterprise environments with JVM infrastructure, need for maximum format coverage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;GROBID&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strengths&lt;/strong&gt;: Best-in-class for academic papers (F1 0.87-0.90), extremely fast (10.6 PDF/sec sustained), proven at scale (34M+ documents at CORE)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trade-offs&lt;/strong&gt;: Academic papers only, large installation (500MB-8GB), complex Java+Python setup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License&lt;/strong&gt;: Apache-2.0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When to choose&lt;/strong&gt;: Scientific/academic document processing exclusively&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Commercial APIs
&lt;/h3&gt;

&lt;p&gt;There are numerous commercial options from startups (LlamaIndex, Unstructured.io paid tiers) to big cloud providers (AWS Textract, Azure Form Recognizer, Google Document AI). These are not OSS but offer managed infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kreuzberg's position&lt;/strong&gt;: As an open-source library, Kreuzberg provides a self-hosted alternative with no per-document API costs, making it suitable for high-volume workloads where cost efficiency matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Community &amp;amp; Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: Star us at &lt;a href="https://github.com/kreuzberg-dev/kreuzberg" rel="noopener noreferrer"&gt;https://github.com/kreuzberg-dev/kreuzberg&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discord&lt;/strong&gt;: Join our community server at &lt;a href="https://discord.gg/pXxagNK2zN" rel="noopener noreferrer"&gt;discord.gg/pXxagNK2zN&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subreddit&lt;/strong&gt;: Join the discussion at &lt;a href="https://www.reddit.com/r/kreuzberg_dev/" rel="noopener noreferrer"&gt;r/kreuzberg_dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt;: &lt;a href="https://kreuzberg.dev" rel="noopener noreferrer"&gt;kreuzberg.dev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'd love to hear your feedback, use cases, and contributions!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: Kreuzberg v4 is a complete Rust rewrite of a document intelligence library, offering native bindings for 7 languages (8 runtime targets), 56+ file formats, Rust-native performance, embeddings, semantic chunking, and production-ready servers - all in a 16-31 MB complete package (5-15x smaller than alternatives). Releasing January 2026. MIT licensed forever.&lt;/p&gt;

</description>
      <category>rag</category>
      <category>programming</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I benchmarked 4 Python text extraction libraries (2025)</title>
      <dc:creator>Na'aman Hirschfeld (Goldziher)</dc:creator>
      <pubDate>Sun, 06 Jul 2025 10:25:38 +0000</pubDate>
      <link>https://dev.to/nhirschfeld/i-benchmarked-4-python-text-extraction-libraries-2025-4e7j</link>
      <guid>https://dev.to/nhirschfeld/i-benchmarked-4-python-text-extraction-libraries-2025-4e7j</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: Comprehensive benchmarks of Kreuzberg, Docling, MarkItDown, and Unstructured across 94 real-world documents. Results might surprise you.&lt;/p&gt;

&lt;h2&gt;
  
  
  📊 &lt;strong&gt;Live Results&lt;/strong&gt;: &lt;a href="https://goldziher.github.io/python-text-extraction-libs-benchmarks/" rel="noopener noreferrer"&gt;https://goldziher.github.io/python-text-extraction-libs-benchmarks/&lt;/a&gt;
&lt;/h2&gt;




&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;As the author of &lt;a href="https://github.com/Goldziher/kreuzberg" rel="noopener noreferrer"&gt;Kreuzberg&lt;/a&gt;, I wanted to create an &lt;strong&gt;honest, comprehensive benchmark&lt;/strong&gt; of Python text extraction libraries. No cherry-picking, no marketing fluff - just real performance data across 94 documents (~210MB) ranging from tiny text files to 59MB academic papers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full disclosure&lt;/strong&gt;: I built Kreuzberg, but these benchmarks are automated, reproducible, and the methodology is completely open-source.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔬 &lt;strong&gt;What I Tested&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Libraries Benchmarked:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/Goldziher/kreuzberg" rel="noopener noreferrer"&gt;Kreuzberg&lt;/a&gt;&lt;/strong&gt; (71MB, 20 deps) - My library&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/DS4SD/docling" rel="noopener noreferrer"&gt;Docling&lt;/a&gt;&lt;/strong&gt; (1,032MB, 88 deps) - IBM's ML-powered solution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/microsoft/markitdown" rel="noopener noreferrer"&gt;MarkItDown&lt;/a&gt;&lt;/strong&gt; (251MB, 25 deps) - Microsoft's Markdown converter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/Unstructured-IO/unstructured" rel="noopener noreferrer"&gt;Unstructured&lt;/a&gt;&lt;/strong&gt; (146MB, 54 deps) - Enterprise document processing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Test Coverage:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;94 real documents&lt;/strong&gt;: PDFs, Word docs, HTML, images, spreadsheets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5 size categories&lt;/strong&gt;: Tiny (&amp;lt;100KB) to Huge (&amp;gt;50MB)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;6 languages&lt;/strong&gt;: English, Hebrew, German, Chinese, Japanese, Korean&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CPU-only processing&lt;/strong&gt;: No GPU acceleration for fair comparison&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple metrics&lt;/strong&gt;: Speed, memory usage, success rates, installation sizes&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🏆 &lt;strong&gt;Results Summary&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Speed Champions 🚀
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Kreuzberg&lt;/strong&gt;: 35+ files/second, handles everything&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unstructured&lt;/strong&gt;: Moderate speed, excellent reliability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MarkItDown&lt;/strong&gt;: Good on simple docs, struggles with complex files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docling&lt;/strong&gt;: Often 60+ minutes per file (!!)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Installation Footprint 📦
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kreuzberg&lt;/strong&gt;: 71MB, 20 dependencies ⚡&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unstructured&lt;/strong&gt;: 146MB, 54 dependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MarkItDown&lt;/strong&gt;: 251MB, 25 dependencies (includes ONNX)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docling&lt;/strong&gt;: 1,032MB, 88 dependencies 🐘&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reality Check ⚠️
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Docling&lt;/strong&gt;: Frequently fails/times out on medium files (&amp;gt;1MB)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MarkItDown&lt;/strong&gt;: Struggles with large/complex documents (&amp;gt;10MB)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kreuzberg&lt;/strong&gt;: Consistent across all document types and sizes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unstructured&lt;/strong&gt;: Most reliable overall (88%+ success rate)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🎯 &lt;strong&gt;When to Use What&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ⚡ &lt;strong&gt;&lt;a href="https://github.com/Goldziher/kreuzberg" rel="noopener noreferrer"&gt;Kreuzberg&lt;/a&gt;&lt;/strong&gt; (Disclaimer: I built this)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best for&lt;/strong&gt;: Production workloads, edge computing, AWS Lambda&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why&lt;/strong&gt;: Smallest footprint (71MB), fastest speed, handles everything&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bonus&lt;/strong&gt;: Both sync/async APIs with OCR support&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🏢 &lt;strong&gt;&lt;a href="https://github.com/Unstructured-IO/unstructured" rel="noopener noreferrer"&gt;Unstructured&lt;/a&gt;&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best for&lt;/strong&gt;: Enterprise applications, mixed document types&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why&lt;/strong&gt;: Most reliable overall, good enterprise features&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trade-off&lt;/strong&gt;: Moderate speed, larger installation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📝 &lt;strong&gt;&lt;a href="https://github.com/microsoft/markitdown" rel="noopener noreferrer"&gt;MarkItDown&lt;/a&gt;&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best for&lt;/strong&gt;: Simple documents, LLM preprocessing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why&lt;/strong&gt;: Good for basic PDFs/Office docs, optimized for Markdown&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limitation&lt;/strong&gt;: Fails on large/complex files&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔬 &lt;strong&gt;&lt;a href="https://github.com/DS4SD/docling" rel="noopener noreferrer"&gt;Docling&lt;/a&gt;&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best for&lt;/strong&gt;: Research environments (if you have patience)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why&lt;/strong&gt;: Advanced ML document understanding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reality&lt;/strong&gt;: Extremely slow, frequent timeouts, 1GB+ install&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📈 &lt;strong&gt;Key Insights&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Installation size matters&lt;/strong&gt;: Kreuzberg's 71MB vs Docling's 1GB+ makes a huge difference for deployment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance varies dramatically&lt;/strong&gt;: 35 files/second vs 60+ minutes per file&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document complexity is crucial&lt;/strong&gt;: Simple PDFs vs complex layouts show very different results&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliability vs features&lt;/strong&gt;: Sometimes the simplest solution works best&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🔧 &lt;strong&gt;Methodology&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automated CI/CD&lt;/strong&gt;: GitHub Actions run benchmarks on every release&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real documents&lt;/strong&gt;: Academic papers, business docs, multilingual content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple iterations&lt;/strong&gt;: 3 runs per document, statistical analysis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open source&lt;/strong&gt;: Full code, test documents, and results available&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory profiling&lt;/strong&gt;: psutil-based resource monitoring&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeout handling&lt;/strong&gt;: 5-minute limit per extraction&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🤔 &lt;strong&gt;Why I Built This&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Working on &lt;a href="https://github.com/Goldziher/kreuzberg" rel="noopener noreferrer"&gt;Kreuzberg&lt;/a&gt;, I worked on performance and stability, and then wanted a tool to see how it measures against other frameworks - which I could also use to further develop and improve Kreuzberg itself. I therefore created this benchmark. Since it was fun, I invested some time to pimp it out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses &lt;strong&gt;real-world documents&lt;/strong&gt;, not synthetic tests&lt;/li&gt;
&lt;li&gt;Tests &lt;strong&gt;installation overhead&lt;/strong&gt; (often ignored)&lt;/li&gt;
&lt;li&gt;Includes &lt;strong&gt;failure analysis&lt;/strong&gt; (libraries fail more than you think)&lt;/li&gt;
&lt;li&gt;Is &lt;strong&gt;completely reproducible&lt;/strong&gt; and open&lt;/li&gt;
&lt;li&gt;Updates &lt;strong&gt;automatically&lt;/strong&gt; with new releases&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📊 &lt;strong&gt;Data Deep Dive&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://goldziher.github.io/python-text-extraction-libs-benchmarks/" rel="noopener noreferrer"&gt;interactive dashboard&lt;/a&gt; shows some fascinating patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kreuzberg dominates&lt;/strong&gt; on speed and resource usage across all categories&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unstructured excels&lt;/strong&gt; at complex layouts and has the best reliability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MarkItDown is useful&lt;/strong&gt; for simple docs shows in the data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docling's ML models&lt;/strong&gt; create massive overhead for most use cases making it a hard sell&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🚀 &lt;strong&gt;Try It Yourself&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Goldziher/python-text-extraction-libs-benchmarks.git
&lt;span class="nb"&gt;cd &lt;/span&gt;python-text-extraction-libs-benchmarks
uv &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="nt"&gt;--all-extras&lt;/span&gt;
uv run python &lt;span class="nt"&gt;-m&lt;/span&gt; src.cli benchmark &lt;span class="nt"&gt;--framework&lt;/span&gt; kreuzberg_sync &lt;span class="nt"&gt;--category&lt;/span&gt; small
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or just check the live results: &lt;a href="https://goldziher.github.io/python-text-extraction-libs-benchmarks/" rel="noopener noreferrer"&gt;https://goldziher.github.io/python-text-extraction-libs-benchmarks/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🔗 &lt;strong&gt;Links&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;📊 Live Benchmark Results&lt;/strong&gt;: &lt;a href="https://goldziher.github.io/python-text-extraction-libs-benchmarks/" rel="noopener noreferrer"&gt;https://goldziher.github.io/python-text-extraction-libs-benchmarks/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;📁 Benchmark Repository&lt;/strong&gt;: &lt;a href="https://github.com/Goldziher/python-text-extraction-libs-benchmarks" rel="noopener noreferrer"&gt;https://github.com/Goldziher/python-text-extraction-libs-benchmarks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⚡ Kreuzberg (my library)&lt;/strong&gt;: &lt;a href="https://github.com/Goldziher/kreuzberg" rel="noopener noreferrer"&gt;https://github.com/Goldziher/kreuzberg&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🔬 Docling&lt;/strong&gt;: &lt;a href="https://github.com/DS4SD/docling" rel="noopener noreferrer"&gt;https://github.com/DS4SD/docling&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;📝 MarkItDown&lt;/strong&gt;: &lt;a href="https://github.com/microsoft/markitdown" rel="noopener noreferrer"&gt;https://github.com/microsoft/markitdown&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🏢 Unstructured&lt;/strong&gt;: &lt;a href="https://github.com/Unstructured-IO/unstructured" rel="noopener noreferrer"&gt;https://github.com/Unstructured-IO/unstructured&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🤝 &lt;strong&gt;Discussion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;What's your experience with these libraries? Any others I should benchmark? I tried benchmarking &lt;code&gt;marker&lt;/code&gt;, but the setup required a GPU.&lt;/p&gt;

&lt;p&gt;Some important points regarding how I used these benchmarks for Kreuzberg:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I fine tuned the default settings for Kreuzberg.&lt;/li&gt;
&lt;li&gt;I updated our docs to give recommendations on different settings for different use cases. E.g. Kreuzberg can actually get to 75% reliability, with about 15% slow-down.&lt;/li&gt;
&lt;li&gt;I made a best effort to configure the frameworks following the best practices of their docs and using their out of the box defaults. If you think something is off or needs adjustment, feel free to let me know here or open an issue in the repository.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>webdev</category>
      <category>machinelearning</category>
      <category>llm</category>
      <category>ai</category>
    </item>
    <item>
      <title>Announcing Kreuzberg v2.0: A Lightweight, Modern Python Text Extraction library</title>
      <dc:creator>Na'aman Hirschfeld (Goldziher)</dc:creator>
      <pubDate>Sat, 15 Feb 2025 18:44:30 +0000</pubDate>
      <link>https://dev.to/nhirschfeld/announcing-kreuzberg-v20-a-lightweight-modern-python-text-extraction-library-4ca4</link>
      <guid>https://dev.to/nhirschfeld/announcing-kreuzberg-v20-a-lightweight-modern-python-text-extraction-library-4ca4</guid>
      <description>&lt;h2&gt;
  
  
  🔍 What’s Kreuzberg?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Goldziher/kreuzberg" rel="noopener noreferrer"&gt;Kreuzberg&lt;/a&gt; is a Python library that provides a unified async/sync interface for extracting text from PDFs, images, Office documents, and more. &lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Async First&lt;/strong&gt;: Optimized async using &lt;code&gt;anyio&lt;/code&gt; and worker processes.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal Dependencies&lt;/strong&gt;: Much smaller footprint compared to alternatives.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serverless-and-Docker Ready&lt;/strong&gt;: Perfect for serverless functions and containerized deployments.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local Processing&lt;/strong&gt;: All processing is done locally, with no API calls or cloud services.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modern Python&lt;/strong&gt;: Built for Python 3.9+ with rigorous typing and extensive testing.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Versatile&lt;/strong&gt;: Supports various formats, including PDFs, spreadsheets, Markdown, LaTeX, and more.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🚀 What’s New in Version 2.0?
&lt;/h2&gt;

&lt;p&gt;Kreuzberg v2.0 brings significant enhancements to performance, usability, and feature set. Here’s what’s new:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sync APIs&lt;/strong&gt;: Kreuzberg supports synchronous extraction methods alongside async workflows.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch Processing&lt;/strong&gt;: Efficiently process multiple files or byte streams in parallel.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart PDF Handling&lt;/strong&gt;: Automatically fall back to OCR when direct text extraction fails.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metadata Extraction&lt;/strong&gt;: Retrieve metadata like document titles or creators using Pandoc.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Excel Multi-Sheet Support&lt;/strong&gt;: Handle even the most complex spreadsheets.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Performance&lt;/strong&gt;: Worker processes for faster, resource-efficient extraction.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out the &lt;a href="https://github.com/Goldziher/kreuzberg/releases/tag/v2.0.0" rel="noopener noreferrer"&gt;v2.0 changelog&lt;/a&gt; for more details.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 Who’s It For?
&lt;/h2&gt;

&lt;p&gt;Kreuzberg is ideal for developers building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval-Augmented Generation (RAG) systems&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM-powered applications&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document indexing, analysis, and automation tools&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re looking for a lightweight, efficient solution for text extraction, Kreuzberg is a great choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚖️ How Kreuzberg Compares
&lt;/h2&gt;

&lt;p&gt;Here’s how Kreuzberg stacks up against alternatives:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Python OSS Libraries&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unstructured.io&lt;/strong&gt;: Feature-rich but heavy, making it unsuitable for serverless or low-resource environments.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docling&lt;/strong&gt;: Another strong alternative but larger and heavier—better suited for high-volume, GPU-based workloads.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Non-Python OSS Libraries&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Apache Tika&lt;/strong&gt;: Requires a Java server running as a sidecar, with Python client libraries available.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grobid&lt;/strong&gt;: Excellent for structured research text extraction but comes with a ~20GB Docker image.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Commercial APIs&lt;/strong&gt;&lt;br&gt;
Paid solutions like Azure Document Intelligence or AWS Textract offer best-in-class OCR and layout extraction. However, they come with pricing concerns and cloud dependencies, unlike Kreuzberg.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Staring ⭐ is Caring
&lt;/h2&gt;

&lt;p&gt;If Kreuzberg sounds like the library you’ve been looking for, check it out on &lt;a href="https://github.com/Goldziher/kreuzberg" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Please star the repo ⭐—it helps others discover the project and motivates me to keep improving it!&lt;/p&gt;

</description>
      <category>llm</category>
      <category>rag</category>
      <category>programming</category>
      <category>ai</category>
    </item>
    <item>
      <title>Functional Snippet: withSuppress</title>
      <dc:creator>Na'aman Hirschfeld (Goldziher)</dc:creator>
      <pubDate>Sun, 01 Sep 2024 12:31:51 +0000</pubDate>
      <link>https://dev.to/nhirschfeld/functional-snippet-withsuppress-4ea0</link>
      <guid>https://dev.to/nhirschfeld/functional-snippet-withsuppress-4ea0</guid>
      <description>&lt;p&gt;Sometimes, suppressing errors is handy. &lt;/p&gt;

&lt;p&gt;E.g. you see this:&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;scrollableRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's create something to handle this in a cleaner fashion. We'll use a functional style that prefers wrap functions to extend functionality. &lt;/p&gt;

&lt;p&gt;We "wrap" a function with error handling, returning a function with an identical signature:&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isPromise&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="s1"&gt;@tool-belt/type-predicates&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Wraps a function with error suppression and optional logging.
 *
 * @param fn - The function to execute.
 * @param errorMessage - The error message to log if an exception is thrown.
 * @returns The wrapped function with error suppression.
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;withSuppress&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="nf"&gt;extends &lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&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;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Parameters&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;ReturnType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;U&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;U&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;ReturnType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;errorMessage&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Parameters&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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="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="k"&gt;try&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="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isPromise&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="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;ReturnType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;U&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;U&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now we can suppress errors elegantly. &lt;/p&gt;

&lt;p&gt;Here are some example uses:&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="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;generate&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a Vitest test suite for the function:&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;withSuppress&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="s1"&gt;./functional&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;withSuppress&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should return the value from a synchronous function without errors&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="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;syncFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;42&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;wrappedFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withSuppress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;syncFn&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="nf"&gt;wrappedFn&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="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should return undefined and log error when synchronous function throws&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="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;errorMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sync error occurred&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;syncFn&lt;/span&gt; &lt;span class="o"&gt;=&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test Error&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;consoleErrorSpy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spyOn&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mockImplementation&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;wrappedFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withSuppress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;syncFn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;errorMessage&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="nf"&gt;wrappedFn&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBeUndefined&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;consoleErrorSpy&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledWith&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;stringContaining&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\nError: Test Error`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;consoleErrorSpy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockRestore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should return the resolved value from an asynchronous function without errors&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;asyncFn&lt;/span&gt; &lt;span class="o"&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="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&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;wrappedFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withSuppress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;asyncFn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;wrappedFn&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;resolves&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="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should return undefined and log error when asynchronous function rejects&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;errorMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Async error occurred&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;asyncFn&lt;/span&gt; &lt;span class="o"&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="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test Error&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;consoleErrorSpy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spyOn&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mockImplementation&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;wrappedFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withSuppress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;asyncFn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;wrappedFn&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;resolves&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBeUndefined&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;consoleErrorSpy&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledWith&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;stringContaining&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\nError: Test Error`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;consoleErrorSpy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockRestore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should return undefined if no error message is provided and the function throws&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="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;syncFn&lt;/span&gt; &lt;span class="o"&gt;=&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test Error&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;consoleErrorSpy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spyOn&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mockImplementation&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;wrappedFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withSuppress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;syncFn&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="nf"&gt;wrappedFn&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBeUndefined&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;consoleErrorSpy&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;toHaveBeenCalled&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;consoleErrorSpy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockRestore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should return undefined if no error message is provided and the async function rejects&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;asyncFn&lt;/span&gt; &lt;span class="o"&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="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test Error&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;consoleErrorSpy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spyOn&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mockImplementation&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;wrappedFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withSuppress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;asyncFn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;wrappedFn&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;resolves&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBeUndefined&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;consoleErrorSpy&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;toHaveBeenCalled&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;consoleErrorSpy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockRestore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should handle functions that return promises without errors&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;asyncFn&lt;/span&gt; &lt;span class="o"&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="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;async result&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;wrappedFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withSuppress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;asyncFn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;wrappedFn&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;resolves&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;async result&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;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should handle synchronous functions returning undefined without errors&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="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;syncFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;wrappedFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withSuppress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;syncFn&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="nf"&gt;wrappedFn&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBeUndefined&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>typescript</category>
      <category>functional</category>
      <category>util</category>
    </item>
    <item>
      <title>Starlite December '22 Updates</title>
      <dc:creator>Na'aman Hirschfeld (Goldziher)</dc:creator>
      <pubDate>Sun, 11 Dec 2022 13:31:27 +0000</pubDate>
      <link>https://dev.to/nhirschfeld/starlite-december-22-updates-52dc</link>
      <guid>https://dev.to/nhirschfeld/starlite-december-22-updates-52dc</guid>
      <description>&lt;h1&gt;
  
  
  Starlite December '22 Updates
&lt;/h1&gt;

&lt;p&gt;Hi Pythonistas!&lt;/p&gt;

&lt;p&gt;This post is an update on the development status of &lt;a href="https://github.com/starlite-api/starlite" rel="noopener noreferrer"&gt;Starlite&lt;/a&gt;. Let me start, as usual, with a short intro about what is &lt;a href="https://github.com/starlite-api/starlite" rel="noopener noreferrer"&gt;Starlite&lt;/a&gt; - for those of you unfamiliar with it. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/starlite-api/starlite" rel="noopener noreferrer"&gt;Starlite&lt;/a&gt; is a Python ASGI API framework. ASGI is an async python API specification, originally from the good folk over at the Django project, and an ASGI API framework is a framework that follows this specification. You might be familiar with other ASGI frameworks, with the most famous one these days being FastAPI. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/starlite-api/starlite" rel="noopener noreferrer"&gt;Starlite&lt;/a&gt; began as an alternative to FastAPI - it was originally built on the same foundation- Starlette, which offers a ready to use ASGI "tool-kit". The name of the framework - Starlite, was meant to highlight this relationship. Yet, over time Starlite grew in sophistication and complextiy, and we made the decision to drop Starlette as a dependency because this no longer made any sense. &lt;/p&gt;

&lt;h2&gt;
  
  
  Dropping Starlette and Benchmarks
&lt;/h2&gt;

&lt;p&gt;Since version &lt;code&gt;v1.39.0&lt;/code&gt; Starlite (released on the 12.11.22) no longer has any dependency on Starlette. Between this version and &lt;code&gt;v1.45.0&lt;/code&gt; that was released today (11.12.22), we have invested significant effort into benchmarking and optimizing code. One of our maintainers, @provinzkraut (Janek Nouvertné), has done amazing work rewriting our benchmarking framework. You can read more about this in &lt;a href="https://starlite-api.github.io/starlite/benchmarks/" rel="noopener noreferrer"&gt;here&lt;/a&gt; and run the benchmarks on your own by cloning &lt;a href="https://github.com/starlite-api/api-performance-tests" rel="noopener noreferrer"&gt;the benchmark repository&lt;/a&gt;. The results are pretty impressive if I may say so myself:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flil7femi9ph9d1gw96y5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flil7femi9ph9d1gw96y5.png" alt="Response Cookies and Headers" width="799" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhrxzx5kga9vwng4903i1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhrxzx5kga9vwng4903i1.png" alt="File Download" width="799" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fup0r3smiashxlbgz1t96.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fup0r3smiashxlbgz1t96.png" alt="JSON Response" width="799" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7vkwv17fr0sirkhx7ik1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7vkwv17fr0sirkhx7ik1.png" alt="Query and Path Parameters" width="799" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F109capw1z7f95w0071kv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F109capw1z7f95w0071kv.png" alt="Plaintext Response" width="799" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7dkjsy8xvftfrw6xdfun.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7dkjsy8xvftfrw6xdfun.png" alt="URLEncoded and Multipart" width="799" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4az9ozzhp5sv635arx68.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4az9ozzhp5sv635arx68.png" alt="JSON Post Data" width="799" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz5atv1md5y9nls2jhbdz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz5atv1md5y9nls2jhbdz.png" alt="Pydantic and Dataclass Serialization" width="799" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Important Changes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Contrib and Security Backends
&lt;/h3&gt;

&lt;p&gt;Another important development is the inclusion of a &lt;code&gt;starlite.contrib&lt;/code&gt; namespace, which we will be expanding on in the future. &lt;/p&gt;

&lt;p&gt;This namespace includes an optional &lt;a href="https://starlite-api.github.io/starlite/usage/18-contrib/0-open-telemetry/" rel="noopener noreferrer"&gt;OpenTelemetry Integration&lt;/a&gt;, which was a long awaited feature.&lt;/p&gt;

&lt;p&gt;We also added &lt;a href="https://starlite-api.github.io/starlite/usage/8-security/0-intro/" rel="noopener noreferrer"&gt;Security Backend&lt;/a&gt; support, which was discussed in some length &lt;a href="https://github.com/starlite-api/starlite/discussions/794" rel="noopener noreferrer"&gt;here&lt;/a&gt;. The new security backend supports all of the different &lt;a href="https://starlite-api.github.io/starlite/usage/8-security/2-security-backends/" rel="noopener noreferrer"&gt;session backends&lt;/a&gt; Starlite supports, and there is also an optional &lt;a href="https://starlite-api.github.io/starlite/usage/18-contrib/1-jwt/" rel="noopener noreferrer"&gt;JWT Security Backend&lt;/a&gt; as part of contrib.&lt;/p&gt;

&lt;h3&gt;
  
  
  Yield Based Dependencies
&lt;/h3&gt;

&lt;p&gt;We made some updates to dependencies following a request on reddit - we now support yield based dependencies in the Starlite &lt;a href="https://starlite-api.github.io/starlite/usage/6-dependency-injection/0-dependency-injection-intro/" rel="noopener noreferrer"&gt;Dependency Injection&lt;/a&gt; framework. Additionally we made some optimizations to dependency injection which allows us to resolve dependencies in parallel and do some caching, the result is a significant boost in speed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpk3kq7r1i5pn8xvtabcg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpk3kq7r1i5pn8xvtabcg.png" alt="Dependency Injection Performance" width="799" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Call for Contributiors and Maintainers
&lt;/h2&gt;

&lt;p&gt;The original imperative for creating &lt;a href="https://github.com/starlite-api/starlite" rel="noopener noreferrer"&gt;Starlite&lt;/a&gt; was to create a community driven alternative to FastAPI. This was and remains a core pillar of Starlite- to have multiple maintainers and be as open, inviting and accessible for contributions as feasible. The project follows the &lt;a href="https://allcontributors.org/docs/en/specification" rel="noopener noreferrer"&gt;all-contributors specification&lt;/a&gt; and we attribute all types of contribution - code, testing, refactoring, code reviews, documentation, design of the docs, writing and evangelizing etc.&lt;/p&gt;

&lt;p&gt;We are a growing group of contributors and maintainers (5 maintainers at present), and we are always looking for more people to be involved. You're invited to join us on our &lt;a href="https://discord.gg/X3FJqy8d2j" rel="noopener noreferrer"&gt;discord server&lt;/a&gt;, or checkout our &lt;a href="https://github.com/starlite-api/starlite" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; where you are welcome to write in both discussions and issues.&lt;/p&gt;

</description>
      <category>starlite</category>
      <category>webdev</category>
      <category>python</category>
      <category>api</category>
    </item>
  </channel>
</rss>
