<?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: Nikit Kashyap</title>
    <description>The latest articles on DEV Community by Nikit Kashyap (@nikit117).</description>
    <link>https://dev.to/nikit117</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%2F4225889%2F2f59bb0b-56d3-448a-a887-54e085aa8d5e.jpg</url>
      <title>DEV Community: Nikit Kashyap</title>
      <link>https://dev.to/nikit117</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nikit117"/>
    <language>en</language>
    <item>
      <title>How a GPT-5.4 harness scored above GPT-6 Astra and Claude Code on ExtractBench</title>
      <dc:creator>Nikit Kashyap</dc:creator>
      <pubDate>Wed, 04 Nov 2026 10:18:16 +0000</pubDate>
      <link>https://dev.to/nikit117/how-a-gpt-54-harness-scored-above-gpt-6-astra-and-claude-code-on-extractbench-27o5</link>
      <guid>https://dev.to/nikit117/how-a-gpt-54-harness-scored-above-gpt-6-astra-and-claude-code-on-extractbench-27o5</guid>
      <description>&lt;p&gt;On LlamaIndex ExtractBench (370 documents, 4,869 pages), my open-source harness &lt;strong&gt;KYNEX&lt;/strong&gt; scores &lt;strong&gt;92.97 F1&lt;/strong&gt;. It is ranked &lt;strong&gt;12th of 53 systems&lt;/strong&gt; on the leaderboard, and the result was &lt;strong&gt;independently re-run and verified by the LlamaIndex team&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It runs on &lt;strong&gt;GPT-5.4&lt;/strong&gt;. On the same leaderboard:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System&lt;/th&gt;
&lt;th&gt;F1&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LlamaExtract Agentic Plus (commercial)&lt;/td&gt;
&lt;td&gt;96.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reducto Deep Extract (commercial)&lt;/td&gt;
&lt;td&gt;96.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;KYNEX Harness (GPT-5.4)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;92.97&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-6 Astra&lt;/td&gt;
&lt;td&gt;91.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code (Opus)&lt;/td&gt;
&lt;td&gt;87.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 3.8 Flash&lt;/td&gt;
&lt;td&gt;80.7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The top two are commercial APIs with their own infrastructure. A cheaper model inside a well-built harness still finished above several frontier models.&lt;/p&gt;

&lt;p&gt;This post covers what the harness does differently and the specific failures each part fixes. My takeaway: for structured extraction, &lt;strong&gt;the model's access to the document mattered more than the model itself&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The failure that started it: a 26,725-row table
&lt;/h2&gt;

&lt;p&gt;One benchmark document asks for every row of a large unclaimed-property table: &lt;strong&gt;26,725 rows&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One-shot LLM call:&lt;/strong&gt; the model cannot emit an array that long. It returned &lt;code&gt;null&lt;/code&gt;, and the score was &lt;strong&gt;0.00&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAG:&lt;/strong&gt; chunking separates table headers from their rows, and embeddings flatten table structure. Top-k retrieval also caps how many rows you can get back, so you can't retrieve 26,725 rows with k=10.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither approach is a prompt problem. Both are &lt;strong&gt;access&lt;/strong&gt; problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea: give the model tools instead of retrieved chunks
&lt;/h2&gt;

&lt;p&gt;KYNEX has no chunking, no vector store and no retrieval step. The document is parsed once into a page-level "world model" with a lexical index. The model then gets typed tools and decides what to read, the way an analyst flips through pages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Page text extraction&lt;/strong&gt;: read specific pages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Table parsing&lt;/strong&gt;: get rows with their headers preserved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full-text search&lt;/strong&gt;: find where a field is mentioned.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OCR&lt;/strong&gt;: for scanned pages with no text layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vision&lt;/strong&gt;: look at a rendered page when the layout matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure Document Intelligence&lt;/strong&gt;: for complex layouts and tables.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every tool result carries &lt;strong&gt;provenance&lt;/strong&gt; (page, location, snippet), so each answer can be traced back to evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;documents ──&amp;gt; parse once ──&amp;gt; WorldModel (pages, tables, lexical index)
                                  │
schema ──&amp;gt; per-field contracts ───┤
                                  ▼
                 Agent (LangGraph, 3 phases)  &amp;lt;── Supervisor
                   │ calls typed tools          (tracks progress,
                   │ (text/table/search/        resumes stalls)
                   │  OCR/vision/Azure DI)
                   ▼
           Admission gate (deterministic, outside the model)
             - numbers must appear literally in the evidence
             - text needs enough token overlap with the evidence
                   ▼
           Repair pipeline (5 rules, each with a kill switch)
                   ▼
                JSON output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two design choices matter most here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The supervisor sits outside the agent loop.&lt;/strong&gt; Long documents make agents stall or loop. The supervisor watches progress per field and resumes the agent where it stopped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verification is not the model's job.&lt;/strong&gt; A candidate answer is admitted only if a deterministic check finds it in the evidence. Numbers have to match literally. The model can propose, but it can't approve its own answers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Five repair rules, measured one at a time
&lt;/h2&gt;

&lt;p&gt;After the agent finishes, five deterministic post-processing rules run. Each one targets a failure I actually saw, each has its own environment-variable &lt;strong&gt;kill switch&lt;/strong&gt;, and each was ablated on its own:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Failure it fixes&lt;/th&gt;
&lt;th&gt;Off → On&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Page-wise enumeration&lt;/td&gt;
&lt;td&gt;26,725-row array returned as &lt;code&gt;null&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;0.00 → 0.97&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-record pruning&lt;/td&gt;
&lt;td&gt;Contact blocks counted as data rows&lt;/td&gt;
&lt;td&gt;0.23 → 0.83&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redaction markers&lt;/td&gt;
&lt;td&gt;Withheld cells read as real values&lt;/td&gt;
&lt;td&gt;0.47 → 0.88&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structured table rows&lt;/td&gt;
&lt;td&gt;Values drift between columns&lt;/td&gt;
&lt;td&gt;0.57 → 1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OCR fallback&lt;/td&gt;
&lt;td&gt;Pages with no text layer&lt;/td&gt;
&lt;td&gt;0.10 → 0.97&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The biggest gain is page-wise enumeration. Instead of asking for the whole array at once, the harness probes two pages to learn the row shape. It then enumerates the table page by page and merges the results. The output never has to fit in one generation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why architecture beat model size here
&lt;/h2&gt;

&lt;p&gt;Frontier models are smarter, but in a single pass they all hit the same limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Output length&lt;/strong&gt;: they can't write 26k rows in one response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hallucination on missing values&lt;/strong&gt;: redacted or empty cells get "filled in".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Layout&lt;/strong&gt;: scanned pages and multi-column tables.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these get fixed by more model intelligence. They get fixed by &lt;strong&gt;what the model can see, how its work is split up, and what checks its output&lt;/strong&gt;. A harness turns those into engineering problems you can measure, ablate and switch off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations (please read before quoting the number)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Possible leakage:&lt;/strong&gt; the repair rules were developed while looking at failures on the same benchmark documents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Long documents are weak:&lt;/strong&gt; short docs score 94.29, medium 92.48, long only &lt;strong&gt;78.77&lt;/strong&gt;. Long documents are also slow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run-to-run variance&lt;/strong&gt; is about 0.02 F1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grounding metrics are 0:&lt;/strong&gt; provenance is tracked internally but not yet exported as citation boxes. This is open as &lt;a href="https://github.com/Nikit117/kynex-harness/issues/5" rel="noopener noreferrer"&gt;issue #5&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The leaderboard compares &lt;em&gt;systems&lt;/em&gt;, not raw models. Other entries may use different prompting and setups.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Used in production
&lt;/h2&gt;

&lt;p&gt;The same harness runs at ABB on engineering RFQs. It produces RFQ summaries and CPQ configuration sheets from 200+ page technical documents with tens of thousands of table rows.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Repo (Apache-2.0): &lt;a href="https://github.com/Nikit117/kynex-harness" rel="noopener noreferrer"&gt;https://github.com/Nikit117/kynex-harness&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;LlamaIndex verification: &lt;a href="https://github.com/run-llama/ExtractBench/pull/84" rel="noopener noreferrer"&gt;https://github.com/run-llama/ExtractBench/pull/84&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Good first issues are open: local models (Ollama/vLLM), a 2-minute example, Docling tables, citation export, and long-document speed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've worked on extraction from long, table-heavy documents: &lt;strong&gt;did RAG or an agentic, tool-based approach work better for you, and where did it break?&lt;/strong&gt; I'd like to hear about the failure cases.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>python</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
