<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
    <channel>
      <title>Neutron Universe</title>
      <link>https://blog.neutronuniverse.com</link>
      <description>Last 50 notes on Neutron Universe</description>
      <generator>Quartz -- quartz.jzhao.xyz</generator>
      <item>
    <title>Homelab Diagram</title>
    <link>https://blog.neutronuniverse.com/projects/12-09-25-homelab-diagram</link>
    <guid>https://blog.neutronuniverse.com/projects/12-09-25-homelab-diagram</guid>
    <description><![CDATA[ &lt;pre&gt;&lt;button class=&quot;expand-button&quot; aria-label=&quot;Expand mermaid diagram&quot; data-view-component&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 16 16&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M3.72 3.72a.75.75 0 011.06 1.06L2.56 7h10.88l-2.22-2.22a.75.75 0 011.06-1.06l3.5 3.5a.75.75 0 010 1.06l-3.5 3.5a.75.75 0 11-1.06-1.06l2.22-2.22H2.56l2.22 2.22a.75.75 0 11-1.06 1.06l-3.5-3.5a.75.75 0 010-1.06l3.5-3.5z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/button&gt;&lt;code class=&quot;mermaid&quot; data-clipboard=&quot;&amp;#x22;flowchart TB\n  %% WAN\n  Internet((\&amp;#x22;Internet\\n1 Gbps full duplex\&amp;#x22;))\n  Router[\&amp;#x22;GL.iNet GL-MT6000\\nWireGuard + AdGuard Home\&amp;#x22;]\n  Switch[\&amp;#x22;TP-Link TL-SG105S-M2\\n2.5G switch\&amp;#x22;]\n\n  Internet --&gt;|1 Gbps| Router\n  Router --&gt;|2.5 Gbps| Switch\n\n  %% Hosts\n  MiniPC[\&amp;#x22;Mini PC\\nmicrok8s node\\n1 Gbps\&amp;#x22;]\n  FullPC[\&amp;#x22;Server + GPU\\nmicrok8s node\\n2.5 Gbps\&amp;#x22;]\n  TrueNAS[\&amp;#x22;TrueNAS NAS\\n2x2 mirror\\n~32 TiB usable\&amp;#x22;]\n\n  Router --&gt;|1 Gbps| MiniPC\n  Switch --&gt;|2.5 Gbps| FullPC\n  Switch --&gt;|2.5 Gbps| TrueNAS\n\n  %% TrueNAS services\n  subgraph \&amp;#x22;TrueNAS services\&amp;#x22;\n    MinIO[\&amp;#x22;MinIO S3\&amp;#x22;]\n    Shares[\&amp;#x22;SMB / NFS shares\&amp;#x22;]\n  end\n\n  TrueNAS --&gt; MinIO\n  TrueNAS --&gt; Shares\n\n  %% microk8s clusters\n  subgraph \&amp;#x22;Server microk8s\&amp;#x22;\n    FullK8s[\&amp;#x22;MoneyMachine / OpenWebUI / MM\\nIngress + CF tunnels\&amp;#x22;]\n  end\n\n  subgraph \&amp;#x22;Mini PC microk8s\&amp;#x22;\n    MiniK8s[\&amp;#x22;Infra / monitoring\\nCF tunnels\&amp;#x22;]\n  end\n\n  FullPC --&gt; FullK8s\n  MiniPC --&gt; MiniK8s\n\n  %% External access\n  subgraph \&amp;#x22;External access\&amp;#x22;\n    CF[\&amp;#x22;Cloudflare tunnels / Zero Trust\&amp;#x22;]\n    WGClients[\&amp;#x22;WireGuard clients\\nlaptops, phone, etc\&amp;#x22;]\n  end\n\n  FullK8s --&gt; CF\n  MiniK8s --&gt; CF\n  CF --&gt; Internet\n  Router --&gt;|WireGuard| WGClients&amp;#x22;&quot;&gt;flowchart TB
  %% WAN
  Internet((&quot;Internet\n1 Gbps full duplex&quot;))
  Router[&quot;GL.iNet GL-MT6000\nWireGuard + AdGuard Home&quot;]
  Switch[&quot;TP-Link TL-SG105S-M2\n2.5G switch&quot;]

  Internet --&gt;|1 Gbps| Router
  Router --&gt;|2.5 Gbps| Switch

  %% Hosts
  MiniPC[&quot;Mini PC\nmicrok8s node\n1 Gbps&quot;]
  FullPC[&quot;Server + GPU\nmicrok8s node\n2.5 Gbps&quot;]
  TrueNAS[&quot;TrueNAS NAS\n2x2 mirror\n~32 TiB usable&quot;]

  Router --&gt;|1 Gbps| MiniPC
  Switch --&gt;|2.5 Gbps| FullPC
  Switch --&gt;|2.5 Gbps| TrueNAS

  %% TrueNAS services
  subgraph &quot;TrueNAS services&quot;
    MinIO[&quot;MinIO S3&quot;]
    Shares[&quot;SMB / NFS shares&quot;]
  end

  TrueNAS --&gt; MinIO
  TrueNAS --&gt; Shares

  %% microk8s clusters
  subgraph &quot;Server microk8s&quot;
    FullK8s[&quot;MoneyMachine / OpenWebUI / MM\nIngress + CF tunnels&quot;]
  end

  subgraph &quot;Mini PC microk8s&quot;
    MiniK8s[&quot;Infra / monitoring\nCF tunnels&quot;]
  end

  FullPC --&gt; FullK8s
  MiniPC --&gt; MiniK8s

  %% External access
  subgraph &quot;External access&quot;
    CF[&quot;Cloudflare tunnels / Zero Trust&quot;]
    WGClients[&quot;WireGuard clients\nlaptops, phone, etc&quot;]
  end

  FullK8s --&gt; CF
  MiniK8s --&gt; CF
  CF --&gt; Internet
  Router --&gt;|WireGuard| WGClients
&lt;/code&gt;&lt;div id=&quot;mermaid-container&quot; role=&quot;dialog&quot;&gt;&lt;div id=&quot;mermaid-space&quot;&gt;&lt;div class=&quot;mermaid-content&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;button class=&quot;expand-button&quot; aria-label=&quot;Expand mermaid diagram&quot; data-view-component&gt;&lt;svg width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 16 16&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M3.72 3.72a.75.75 0 011.06 1.06L2.56 7h10.88l-2.22-2.22a.75.75 0 011.06-1.06l3.5 3.5a.75.75 0 010 1.06l-3.5 3.5a.75.75 0 11-1.06-1.06l2.22-2.22H2.56l2.22 2.22a.75.75 0 11-1.06 1.06l-3.5-3.5a.75.75 0 010-1.06l3.5-3.5z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/button&gt;&lt;code class=&quot;mermaid&quot; data-clipboard=&quot;&amp;#x22;flowchart TB\n  subgraph \&amp;#x22;TrueNAS\&amp;#x22;\n    TN[TrueNAS 32 TiB]\n    TN --&gt; MinIO[(MinIO S3)]\n    TN --&gt; SMB[SMB shares]\n    TN --&gt; NFS[NFS exports]\n  end\n\n  subgraph \&amp;#x22;Server microk8s\&amp;#x22;\n    NSmm[namespace: neutron-mm]\n    NSmedia[namespace: neutron-media]\n\n    Selastic[Elasticsearch]\n    Skibana[Kibana]\n    Semby[Emby]\n    Snextcloud[Nextcloud]\n  end\n\n  subgraph \&amp;#x22;Mini PC microk8s\&amp;#x22;\n    NSinfra[namespace: neutron-infra]\n    Scloudflared[cloudflared tunnels]\n    Smonitoring[monitoring / observability]\n  end\n\n  NSmm --&gt; Selastic\n  NSmm --&gt; Skibana\n  NSmedia --&gt; Semby\n  NSmedia --&gt; Snextcloud\n\n  NSinfra --&gt; Scloudflared\n  NSinfra --&gt; Smonitoring&amp;#x22;&quot;&gt;flowchart TB
  subgraph &quot;TrueNAS&quot;
    TN[TrueNAS 32 TiB]
    TN --&gt; MinIO[(MinIO S3)]
    TN --&gt; SMB[SMB shares]
    TN --&gt; NFS[NFS exports]
  end

  subgraph &quot;Server microk8s&quot;
    NSmm[namespace: neutron-mm]
    NSmedia[namespace: neutron-media]

    Selastic[Elasticsearch]
    Skibana[Kibana]
    Semby[Emby]
    Snextcloud[Nextcloud]
  end

  subgraph &quot;Mini PC microk8s&quot;
    NSinfra[namespace: neutron-infra]
    Scloudflared[cloudflared tunnels]
    Smonitoring[monitoring / observability]
  end

  NSmm --&gt; Selastic
  NSmm --&gt; Skibana
  NSmedia --&gt; Semby
  NSmedia --&gt; Snextcloud

  NSinfra --&gt; Scloudflared
  NSinfra --&gt; Smonitoring
&lt;/code&gt;&lt;div id=&quot;mermaid-container&quot; role=&quot;dialog&quot;&gt;&lt;div id=&quot;mermaid-space&quot;&gt;&lt;div class=&quot;mermaid-content&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt; ]]></description>
    <pubDate>Tue, 09 Dec 2025 08:00:00 GMT</pubDate>
  </item><item>
    <title>Wikipedia Deep Research</title>
    <link>https://blog.neutronuniverse.com/projects/10-03-25-wikipedia-deep-research</link>
    <guid>https://blog.neutronuniverse.com/projects/10-03-25-wikipedia-deep-research</guid>
    <description><![CDATA[ &lt;h1 id=&quot;project-overview&quot;&gt;Project Overview&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#project-overview&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Realizing that there exists a majority of “Deep Research” tasks stemming from summarizing non-realtime components, I figured I could reduce research costs significantly by using the &lt;a href=&quot;https://github.com/langchain-ai/open_deep_research&quot; class=&quot;external external-link&quot;&gt;open sourced deep research from LangGraph&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; and a locally hosted Wikipedia instance for retrieving topic context.&lt;/p&gt;
&lt;p&gt;I setup a hosted Wikipedia site and “Open Deep Research” (bundled with Wikipedia embeddings) over FastAPI service to handle requests inside of my local &lt;a href=&quot;https://microk8s.io/&quot; class=&quot;external external-link&quot;&gt;microk8s&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; cluster.&lt;/p&gt;
&lt;p&gt;This took quite a bit of effort to setup/get working and, given my limited free time, it will take me another bit of effort to stand up a repo/submit a PR to the LangGraph repo for stable, portable code. Forgive me for not including the entire change code.&lt;/p&gt;
&lt;h2 id=&quot;goals&quot;&gt;Goals&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#goals&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Offline retrieval&lt;/strong&gt;: no external web search calls during research besides LLM calls&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Semantic search&lt;/strong&gt; over Wikipedia via &lt;a href=&quot;https://huggingface.co/NeuML/txtai-wikipedia&quot; class=&quot;external external-link&quot;&gt;txtai&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; embeddings&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deterministic citations&lt;/strong&gt; that point to your local &lt;a href=&quot;https://kiwix.org/en/&quot; class=&quot;external external-link&quot;&gt;Kiwix&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; URLs from latest Wikipedia &lt;a href=&quot;https://dumps.wikimedia.org/kiwix/zim/wikipedia/&quot; class=&quot;external external-link&quot;&gt;ZIM dump&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Drop-in tool&lt;/strong&gt; named &lt;code&gt;web_search&lt;/code&gt; so the supervisor/researcher loops reuse the same interface.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;dependencies&quot;&gt;Dependencies&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#dependencies&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/langchain-ai/open_deep_research&quot; class=&quot;external external-link&quot;&gt;LangGraph Open Deep Research&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://huggingface.co/NeuML/txtai-wikipedia&quot; class=&quot;external external-link&quot;&gt;txtai&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; embeddings for finding similar articles to an input query&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kiwix.org/en/&quot; class=&quot;external external-link&quot;&gt;Kiwix&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; hosted Wikipedia site using a recent &lt;a href=&quot;https://dumps.wikimedia.org/kiwix/zim/wikipedia/&quot; class=&quot;external external-link&quot;&gt;ZIM dump&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;high-level-architecture&quot;&gt;High-level architecture&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#high-level-architecture&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;LLM plan -&gt; researcher
		-&gt; `web_search` tool
        -&gt; `wiki_search_async(queries)`
        -&gt; txtai Embeddings
        -&gt; result doc ids
        -&gt; Kiwix-serve HTTP fetch (local ZIM)
        -&gt; HTML
        -&gt; Markdown sanitizer
        -&gt; structured “SOURCE i” blocks with local URLs
        -&gt; compression
        -&gt; final report
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Key Kubernetes components&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Kiwix mirror&lt;/strong&gt;: &lt;code&gt;kiwix-serve&lt;/code&gt; on Kubernetes, fronted by Ingress at &lt;code&gt;https://wiki.&amp;#x3C;domain&gt;&lt;/code&gt; with mounted ZIM volume&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embeddings store and Research API&lt;/strong&gt;: &lt;code&gt;txtai&lt;/code&gt; with &lt;code&gt;neuml/txtai-wikipedia&lt;/code&gt; used when calling the Deep Research &lt;code&gt;wiki_search&lt;/code&gt; search tool when setting the configuration &lt;code&gt;search_api=WIKIPEDIA&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;wikipedia-search-details&quot;&gt;Wikipedia Search details&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#wikipedia-search-details&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Query resolution&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;wiki_search_async&lt;/code&gt; fans out queries. Each query runs a &lt;code&gt;txtai&lt;/code&gt; &lt;code&gt;similar(...)&lt;/code&gt; SQL against the Wikipedia index. Keep hits above a percentile threshold (default &lt;code&gt;0.99&lt;/code&gt;) to avoid low-signal pages&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Document fetch&lt;/strong&gt;&lt;br&gt;
For each hit, format the Wikipedia title and fetch content from Kiwix:
&lt;ul&gt;
&lt;li&gt;Try direct &lt;code&gt;https://wiki.&amp;#x3C;ingress_domain&gt;/content/&amp;#x3C;ZIM&gt;/content/A/&amp;#x3C;Title&gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Else resolve via &lt;code&gt;https://wiki.&amp;#x3C;ingress_domain&gt;/content/&amp;#x3C;ZIM&gt;/search?pattern=...&amp;#x26;books.name=&amp;#x3C;ZIM&gt;&lt;/code&gt; and follow the content link&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Normalization&lt;/strong&gt;&lt;br&gt;
Strip HTML, remove references section, collapse whitespace, and output concise Markdown for model-friendly summarization&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Citations&lt;/strong&gt;&lt;br&gt;
Emit &lt;code&gt;SOURCE i&lt;/code&gt; blocks with the canonical local Kiwix URL:
&lt;pre&gt;&lt;code&gt;https://wiki.&amp;#x3C;domain&gt;/content/&amp;#x3C;ZIM&gt;/A/&amp;#x3C;Title&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Synthesis&lt;/strong&gt;&lt;br&gt;
The normal compression and final-report nodes run unchanged. The only difference is the source of notes.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;integration-with-other-search-providers&quot;&gt;Integration with other search providers&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#integration-with-other-search-providers&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;No changes to planners or reducers. The tool registry wires &lt;code&gt;wiki_search&lt;/code&gt; as the canonical &lt;code&gt;web_search&lt;/code&gt; when &lt;code&gt;search_api=WIKIPEDIA&lt;/code&gt;. You can still swap to Tavily or Exa by changing the config without touching graph logic.&lt;/p&gt;
&lt;h2 id=&quot;security-and-reproducibility&quot;&gt;Security and reproducibility&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#security-and-reproducibility&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;All fetches remain inside the cluster.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Results are pinned to your ZIM snapshot for auditability.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ingress TLS terminates at your controller using your wildcard cert.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;cost-difference&quot;&gt;Cost Difference&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#cost-difference&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Costs vary from my semi-structured testing (run report, check OpenAI usage cost difference as measurement) but both Open Deep Research instances use the same default LLM configuration (see the &lt;a href=&quot;https://github.com/langchain-ai/open_deep_research/blob/main/README.md#results&quot; class=&quot;external external-link&quot;&gt;README.md&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;) and just isolating whether using local/OpenAI Web Search significantly decreases cost per report&lt;/p&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Research Provider&lt;/th&gt;&lt;th&gt;Search Provider&lt;/th&gt;&lt;th&gt;Report Cost&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Open Deep Research&lt;/td&gt;&lt;td&gt;OpenAI Web Search&lt;/td&gt;&lt;td&gt;$2.36&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Open Deep Research&lt;/td&gt;&lt;td&gt;Wikipedia&lt;/td&gt;&lt;td&gt;$0.32&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#summary&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This adds a &lt;strong&gt;local, deterministic, citation-clean&lt;/strong&gt; retrieval path for the Deep Research agent. It keeps the LangGraph contract the same, swaps only the search backend, and produces final reports with reliable, linkable local sources.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Final note:&lt;/strong&gt; if you’re still looking for an external provider, from using several “Deep Research” providers, it seems like the lowest dollar cost is OpenAI’s &lt;code&gt;o4-mini-deep-research-2025-06-26&lt;/code&gt; &lt;a href=&quot;https://platform.openai.com/docs/models/o4-mini-deep-research&quot; class=&quot;external external-link&quot;&gt;model&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;example-wikipedia-report&quot;&gt;Example Wikipedia Report&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#example-wikipedia-report&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Query: Write a company overview report on NVIDIA, noting the key suppliers/customers/input materials/ and industry/sector dynamics&lt;/p&gt;
&lt;h2 id=&quot;nvidia-company-overview-report-october-2025&quot;&gt;NVIDIA Company Overview Report (October 2025)&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#nvidia-company-overview-report-october-2025&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;company-overview&quot;&gt;Company Overview&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#company-overview&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Corporation, headquartered in Santa Clara, California, is a global leader in accelerated computing and artificial intelligence (AI). Since its founding in 1993, NVIDIA has evolved from its original focus on graphics processing units (GPUs) for gaming into a multifaceted technology powerhouse developing solutions for data centers, AI, professional visualization, autonomous vehicles, and edge computing.&lt;/p&gt;
&lt;p&gt;For the fiscal year ending January 26, 2025, NVIDIA commands an overwhelming 92% of the discrete GPU market for desktops and laptops. Its principal business areas are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Gaming&lt;/strong&gt;: GeForce GPUs remain industry standards for gaming and creative workloads.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Data Center and AI&lt;/strong&gt;: Serving hyperscale cloud providers, enterprise AI workloads, and scientific research.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Professional Visualization&lt;/strong&gt;: Supplying graphics hardware and software to professionals, including designers and researchers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Automotive and Embedded Systems&lt;/strong&gt;: Providing system-on-chip (SoC) and AI solutions for driver assistance, autonomous vehicles, and robotics.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;NVIDIA’s product development is organized around advanced GPU architectures (e.g., Ada Lovelace, Hopper, and Blackwell), and its solutions increasingly blend hardware with proprietary software stacks, like CUDA for high-performance computing and the Omniverse platform for 3D collaboration and simulation [1].&lt;/p&gt;
&lt;p&gt;Recent strategic moves have deepened NVIDIA’s position in generative AI and cloud infrastructure, and expanded its ecosystem through acquisitions and partnerships with leading data center, automotive, and cloud companies. Subsidiaries, such as Mellanox Technologies (high-speed datacenter networking) and Cumulus Networks (software-defined networking), bolster its vertical integration strategy [1].&lt;/p&gt;
&lt;h3 id=&quot;key-suppliers&quot;&gt;Key Suppliers&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#key-suppliers&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;major-suppliers-and-their-roles&quot;&gt;Major Suppliers and Their Roles&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#major-suppliers-and-their-roles&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;NVIDIA follows a “fabless” semiconductor model, focusing on design while outsourcing manufacturing and major component production to third parties. The company’s crucial suppliers include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Semiconductor Foundries&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Taiwan Semiconductor Manufacturing Company (TSMC)&lt;/strong&gt;: The principal foundry partner for NVIDIA’s most advanced GPUs and AI processors. TSMC fabricates chips using leading-edge process nodes (e.g., 4nm, 5nm), which is essential for keeping NVIDIA’s products competitive in terms of performance and energy efficiency [2].&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Samsung&lt;/strong&gt;: Occasionally serves as a secondary foundry partner for select GPU lines, providing manufacturing diversification and capacity flexibility.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Memory Suppliers&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SK Hynix, Samsung, Micron&lt;/strong&gt;: Supply high-bandwidth memory (HBM), GDDR6/6X DRAM, and other specialized memory critical for GPU and AI workloads.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Substrate and Packaging Providers&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Unimicron, Ibiden, and others&lt;/strong&gt;: Produce advanced substrates essential for high-performance, multi-die GPU assemblies.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Component Suppliers&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Several companies provide capacitors, power management ICs, and connectors. Although individual firms are rarely named in disclosures, NVIDIA notes a dependence on a broad network of specialized component makers.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Software Partners&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Microsoft&lt;/strong&gt;: Collaborates with NVIDIA to integrate support for features like real-time ray tracing (RTX) with DirectX APIs, broadening NVIDIA’s platform reach [3].&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;supplier-importance&quot;&gt;Supplier Importance&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#supplier-importance&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;TSMC’s role is critical—any disruption in TSMC’s advanced process node capacity would have immediate repercussions for NVIDIA’s product roadmap and supply chain resilience [2].&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Memory suppliers are also vital, especially as advanced AI and GPU workloads require ever more bandwidth and capacity, making these relationships strategic for product competitiveness.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Substrate shortages have, in industry-wide fashion, periodically constrained GPU and AI accelerator shipments, emphasizing the importance of securing reliable substrate vendor partnerships.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;limitations&quot;&gt;Limitations&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#limitations&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Detailed supplier lists (by name, proportion, or spend) are confidential and not comprehensively disclosed publicly outside of general industry awareness and historic referencing in filings [1][2]. The analysis above reflects best-available industry insights.&lt;/p&gt;
&lt;h3 id=&quot;major-customers&quot;&gt;Major Customers&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#major-customers&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;customer-segments-and-examples&quot;&gt;Customer Segments and Examples&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#customer-segments-and-examples&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;NVIDIA’s products serve a widely diversified global customer base, but revenue and business influence are concentrated in a few main segments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cloud Service Providers (“Hyperscalers”)&lt;/strong&gt;: The largest customers by far are global cloud operators who deploy NVIDIA hardware for AI training, inference, and cloud-based GPU/AI compute services. These include industry giants—such as Amazon Web Services (AWS), Microsoft Azure, Google Cloud, and, to a lesser extent, Alibaba and Tencent—who each deploy tens or hundreds of thousands of GPUs in their data centers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Enterprise and High-Performance Computing (HPC)&lt;/strong&gt;: Large enterprises, universities, and national laboratories leverage NVIDIA hardware for AI research, simulations, and large-scale computation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Gaming OEMs and Channel Partners&lt;/strong&gt;: PC manufacturers, gaming system builders, and distributors who bundle GeForce GPUs into consumer systems.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Automotive&lt;/strong&gt;: Leading automotive OEMs and Tier-1 suppliers source NVIDIA’s DRIVE compute platforms for driver assistance and autonomous vehicle programs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Government and Research&lt;/strong&gt;: National projects related to AI, supercomputing, and scientific research.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;customer-concentration-and-analysis&quot;&gt;Customer Concentration and Analysis&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#customer-concentration-and-analysis&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The data center segment—driven by AI and cloud computing—accounted for the majority of NVIDIA’s recent revenue growth as of 2024–2025.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Specific customer identities are typically not disclosed in SEC filings for confidentiality. However, filings sometimes reference the risk of customer concentration, and it is well-understood that a handful of cloud hyperscalers account for a significant percentage of total revenue in data center sales.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Gaming remains a critical revenue base but is more diversified across OEM and channel partners, while the automotive and enterprise sectors show moderate but growing concentration.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;limitations-1&quot;&gt;Limitations&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#limitations-1&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Precise revenue assignments to specific named companies are generally not available due to customer privacy or contractual restrictions. Where public, the main hyperscalers are widely inferred as dominant clients through industry reporting and partner announcements.&lt;/p&gt;
&lt;h3 id=&quot;critical-input-materials&quot;&gt;Critical Input Materials&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#critical-input-materials&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Manufacturing of NVIDIA’s GPUs and AI accelerators is underpinned by a variety of sophisticated materials and components, which are critical to product performance and supply chain continuity.&lt;/p&gt;
&lt;h4 id=&quot;key-materials&quot;&gt;Key Materials&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#key-materials&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Semiconductor Wafers (Silicon)&lt;/strong&gt;: Advanced wafers—particularly 300 mm—manufactured at nodes as fine as 4 nm or below, primarily provided by TSMC.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Advanced Packaging and Substrates&lt;/strong&gt;: High-end, multi-layer substrates capable of handling power, heat, and multi-die interconnection (e.g., CoWoS, HBM interposers), produced by companies such as Unimicron and Ibiden.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;High-Bandwidth Memory (HBM, GDDR6/6X)&lt;/strong&gt;: Provided by SK Hynix, Samsung, and Micron. Used for rapid data access and parallel processing critical to AI workloads.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rare Earth Elements and Specialty Metals&lt;/strong&gt;: Materials like cobalt, tantalum, gold, palladium, and various rare earths are used in tiny amounts in semiconductor fabrication and board components.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Specialty Chemicals and Gases&lt;/strong&gt;: Used in lithography, etching, deposition, and cleaning processes during chip production (e.g., photoresists, gases like argon and fluorine).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;supply-chain-considerations-and-challenges&quot;&gt;Supply Chain Considerations and Challenges&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#supply-chain-considerations-and-challenges&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Supply of advanced substrates and memory has proven to be a chokepoint, with industry-wide constraints affecting not just NVIDIA but all advanced semiconductor vendors.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Geopolitical risks (e.g., US-China trade tensions, export controls on advanced tech to China) have prompted NVIDIA to redesign certain SKUs and adapt its sourcing in response to regulatory restrictions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The scale and complexity of AI hardware have increased per-unit material requirements, putting further pressure on upstream suppliers and making supply chain management a major focus for NVIDIA and its partners.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;limitations-2&quot;&gt;Limitations&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#limitations-2&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Granular breakdowns of supplier-specific sourcing by material quantity or value are not published for competitive and commercial sensitivity reasons. Broad categories and major vendor names are industry facts supported by public manufacturing announcements and supply chain analyses.&lt;/p&gt;
&lt;h3 id=&quot;industry-and-sector-dynamics&quot;&gt;Industry and Sector Dynamics&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#industry-and-sector-dynamics&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;competitive-landscape&quot;&gt;Competitive Landscape&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#competitive-landscape&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;NVIDIA operates within several overlapping yet competitive sectors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AI and Accelerated Computing&lt;/strong&gt;: Competitors include AMD (Instinct and Radeon), Intel (Xe accelerators, Gaudi AI processors), and rapidly scaling custom AI chip ventures by hyperscalers themselves (e.g., Google TPU, Amazon Trainium).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Semiconductors and GPUs&lt;/strong&gt;: NVIDIA maintains dominance in discrete GPUs but faces constant pressure from AMD in gaming and professional visualization.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Networking and Interconnect&lt;/strong&gt;: Mellanox (a NVIDIA business), Intel, Broadcom, and Marvell compete to define next-generation datacenter fabrics and storage interconnects.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;major-trends-20242025&quot;&gt;Major Trends (2024–2025)&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#major-trends-20242025&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AI Boom and AI Infrastructure Buildout&lt;/strong&gt;: Unprecedented demand for AI training/inference hardware driven by advances in large language models, generative AI, and enterprise AI adoption. Investments such as the proposed Stargate LLC (a $500 billion+ US AI infrastructure plan) dwarf historic data center buildouts, setting new demand records [5].&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Edge and Automotive Expansion&lt;/strong&gt;: AI and video processing are pushing computational intelligence to the edge and into vehicles; NVIDIA’s automotive platforms power critical ADAS and autonomous test fleets worldwide.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Supply Chain and Geopolitics&lt;/strong&gt;: Global tension has led to export restrictions, especially around sales of advanced AI chips to China, causing NVIDIA to adapt supply and design strategies to remain compliant yet competitive [4].&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Industry Consolidation and Ecosystem Building&lt;/strong&gt;: Mergers, acquisitions, and tighter software-hardware integration are defining the next wave of competitive differentiation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;challenges&quot;&gt;Challenges&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#challenges&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Supply Chain Volatility&lt;/strong&gt;: Shortages of substrates, wafers, and memory chips can limit NVIDIA’s ability to fulfill demand.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Regulatory Risks&lt;/strong&gt;: Export bans (notably to China) and technology transfer restrictions are persistent threats.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Customer Concentration&lt;/strong&gt;: Dependence on a small number of cloud hyperscaler customers exposes NVIDIA to cyclical procurement and market power risks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Talent and R&amp;#x26;D Pressure&lt;/strong&gt;: The global race for AI technical talent and the escalating costs of advanced chip R&amp;#x26;D create barriers for smaller rivals but drive up operational costs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;industry-outlook&quot;&gt;Industry Outlook&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#industry-outlook&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The intersection of AI, cloud computing, and high-performance hardware cements NVIDIA’s leading sector position as of October 2025, with market momentum but significant risks in supply chains, regulatory frameworks, and competitive rivalry.&lt;/p&gt;
&lt;h3 id=&quot;sources&quot;&gt;Sources&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#sources&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Nvidia&quot; class=&quot;external external-link&quot;&gt;Nvidia&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/GeForce_GTX_900_series&quot; class=&quot;external external-link&quot;&gt;GeForce GTX 900 series&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Nvidia_RTX&quot; class=&quot;external external-link&quot;&gt;Nvidia RTX&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/AI_Action_Summit&quot; class=&quot;external external-link&quot;&gt;AI Action Summit&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Stargate_LLC&quot; class=&quot;external external-link&quot;&gt;Stargate LLC&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;code-agent-prompt&quot;&gt;Code Agent Prompt&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#code-agent-prompt&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;While I am not including the code changes (formatting/submitting the PRs would take time and I’m already exhausted from a day job), I have pasted the necessary files into ChatGPT to generate a prompt for a coding agent to recreate the code as a starting template inside of a freshly cloned &lt;a href=&quot;https://github.com/langchain-ai/open_deep_research&quot; class=&quot;external external-link&quot;&gt;Open Deep Research&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; repo if you want to implement this project &lt;a href=&quot;../projects/resources/wikipedia-deep-research-prompt&quot; class=&quot;internal internal-link alias&quot; data-slug=&quot;projects/resources/wikipedia-deep-research-prompt&quot;&gt;here&lt;/a&gt;&lt;/p&gt; ]]></description>
    <pubDate>Fri, 03 Oct 2025 07:00:00 GMT</pubDate>
  </item><item>
    <title>OCR LLM Librarian</title>
    <link>https://blog.neutronuniverse.com/projects/05-30-25-ocr-llm-librarian</link>
    <guid>https://blog.neutronuniverse.com/projects/05-30-25-ocr-llm-librarian</guid>
    <description><![CDATA[ &lt;h1 id=&quot;introduction&quot;&gt;Introduction&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#introduction&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;I recently decided to digitize the books I’ve read the last 10 years with the intent of making the content searchable so I don’t have to spend time going through the index/remembering what ideas were on which pages to help better solidify my understanding and also to have LLM recommended next books to read based on the content of my reading history, not just the title and related books.&lt;/p&gt;
&lt;p&gt;The project breaks down into three layers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Acquisition&lt;/strong&gt;: photography of every page&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Understanding&lt;/strong&gt;: LLM-powered OCR that outputs &lt;em&gt;semantically-rich&lt;/em&gt; JSON (HTML, figures, highlights, footnotes, page numbers…)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retrieval-Augmented Generation (RAG)&lt;/strong&gt;: an Elasticsearch vector index plus an Open WebUI function that lets me chat with my entire library&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Only displaying some of the unique steps here as the rest of the code is pretty standard from quickstarts for &lt;a href=&quot;https://www.elastic.co/elasticsearch&quot; class=&quot;external external-link&quot;&gt;Elasticsearch&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; and &lt;a href=&quot;https://docs.llamaindex.ai/en/stable/&quot; class=&quot;external external-link&quot;&gt;llama index&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;camera-setup&quot;&gt;Camera setup&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#camera-setup&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/img_8174.jpg&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I used my old Canon Rebel T5i fix mounted to my desk along with an LED light connected to my desktop using a Mini USB to USB-C cable and &lt;a href=&quot;http://www.gphoto.org/&quot; class=&quot;external external-link&quot;&gt;gphoto&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; in bash to automate a timer taking a picture every 6 seconds which allowed me enough time to flip through each page of the book which outputs to a book-specific directory in an &lt;code&gt;input&lt;/code&gt; subdirectory&lt;/p&gt;
&lt;figure data-rehype-pretty-code-figure=&quot;&quot;&gt;&lt;pre tabindex=&quot;0&quot; data-language=&quot;bash&quot; data-theme=&quot;github-light github-dark&quot;&gt;&lt;code data-language=&quot;bash&quot; data-theme=&quot;github-light github-dark&quot; style=&quot;display: grid;&quot;&gt;&lt;span data-line=&quot;&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot; data-token-type=&quot;function&quot;&gt;gphoto2&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot; data-token-type=&quot;value&quot;&gt; --capture-image-and-download&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot; data-token-type=&quot;value&quot;&gt; --force-overwrite&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot; data-token-type=&quot;value&quot;&gt; --filename&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;normal&quot;&gt;$DEST&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;/%Y%m%d_%H%M%S.%C&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot; data-token-type=&quot;value&quot;&gt; --interval&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot; data-token-type=&quot;value&quot;&gt; 6&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h1 id=&quot;openai-batch-ocr&quot;&gt;OpenAI Batch OCR&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#openai-batch-ocr&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Used OpenAI’s &lt;code&gt;gpt-4o-mini&lt;/code&gt; model plus batch requests to send each image with a structured Pydantic schema to fill out containing the HTML of the page content, figures/images, and highlighted text&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Stage&lt;/th&gt;&lt;th&gt;Key function(s)&lt;/th&gt;&lt;th&gt;What happens&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;1. Pre-process&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;crop&lt;/code&gt;, &lt;code&gt;split&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Perspective-correct, crop both pages, detect centre gutter and save left/right images.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;2. Batch OCR&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;run_batch_ocr&lt;/code&gt; → OpenAI &lt;em&gt;Batch API&lt;/em&gt;&lt;/td&gt;&lt;td&gt;Each page is encoded to base-64, wrapped in a &lt;code&gt;BookOCR&lt;/code&gt; Pydantic schema, and submitted in ~170 MB JSONL chunks.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;3. Post-process&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;read_batch_response&lt;/code&gt;, &lt;code&gt;parse_objects&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Responses are validated back into Pydantic, objects/figures are cropped out and saved, token costs are tallied.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;p&gt;&lt;code&gt;BookOCR&lt;/code&gt; pydantic model that forces well-formed HTML and image placeholders (&lt;code&gt;&amp;#x3C;img src=&quot;Figure 3-2&quot;&gt;&lt;/code&gt;), so later scripts can inline saved crops&lt;/p&gt;
&lt;figure data-rehype-pretty-code-figure=&quot;&quot;&gt;&lt;pre tabindex=&quot;0&quot; data-language=&quot;python&quot; data-theme=&quot;github-light github-dark&quot;&gt;&lt;code data-language=&quot;python&quot; data-theme=&quot;github-light github-dark&quot; style=&quot;display: grid;&quot;&gt;&lt;span data-line=&quot;&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;keyword&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot; data-token-type=&quot;property&quot;&gt; Object&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot; data-token-type=&quot;property&quot;&gt;BaseModel&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt; &lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;    name: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot; data-token-type=&quot;property&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;function&quot;&gt; Field(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot; data-token-type=&quot;important&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;&#039;The object name, sometimes denoted by just a single word/phrase or the bold word &quot;Figure&quot; and a hierarchical number like &quot;Figure 1.x.x&quot;&#039;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot; data-token-type=&quot;important&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;&#039;&#039;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;    description: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot; data-token-type=&quot;property&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;function&quot;&gt; Field(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot; data-token-type=&quot;important&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;&#039;The object description (if available) of text below the name.&#039;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot; data-token-type=&quot;important&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;&#039;&#039;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;    bounding_box: BoundingBox &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;function&quot;&gt; Field(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot; data-token-type=&quot;important&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;&#039;Bounding box coordinates of the object.&#039;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;    index: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot; data-token-type=&quot;property&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;function&quot;&gt; Field(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot; data-token-type=&quot;important&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;&#039;Index integer starting from 0 at the top of the page and increasing downwards&#039;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot; data-token-type=&quot;important&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=-&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot; data-token-type=&quot;value&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt; &lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt; &lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;keyword&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot; data-token-type=&quot;property&quot;&gt; Highlight&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot; data-token-type=&quot;property&quot;&gt;BaseModel&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt; &lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;    text: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot; data-token-type=&quot;property&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;function&quot;&gt; Field(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot; data-token-type=&quot;important&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;&#039;The color highlighted text&#039;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot; data-token-type=&quot;important&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;&#039;&#039;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt; &lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt; &lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;keyword&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot; data-token-type=&quot;property&quot;&gt; BookOCR&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot; data-token-type=&quot;property&quot;&gt;BaseModel&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt; &lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;    html: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot; data-token-type=&quot;property&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;function&quot;&gt; Field(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot; data-token-type=&quot;important&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;&#039;Formatted output HTML of the image as a single text string&#039;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot; data-token-type=&quot;important&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;&#039;&#039;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;    objects: list[Object] &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;function&quot;&gt; Field(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot; data-token-type=&quot;important&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;&#039;List of objects in the image. This includes images, figures, or subsections.&#039;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot; data-token-type=&quot;important&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;[])&lt;/span&gt;&lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;    content_text: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot; data-token-type=&quot;property&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;function&quot;&gt; Field(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot; data-token-type=&quot;important&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;&#039;Text content of the main content without any other captions or references or footnotes&#039;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot; data-token-type=&quot;important&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;&#039;&#039;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;    highlights: list[Highlight] &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;function&quot;&gt; Field(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot; data-token-type=&quot;important&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;&#039;List of color highlighted text phrases or sentences often in colors such as yellow, orange, pink, blue, etc.&#039;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot; data-token-type=&quot;important&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;[])&lt;/span&gt;&lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;    footnotes: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot; data-token-type=&quot;property&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;function&quot;&gt; Field(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot; data-token-type=&quot;important&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;&#039;Text content of the footnotes (near bottom of page) content without any other captions or references or footnotes&#039;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot; data-token-type=&quot;important&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;&#039;&#039;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;    page_number: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot; data-token-type=&quot;property&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;function&quot;&gt; Field(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot; data-token-type=&quot;important&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;&#039;Optional if found integer page number of the book&#039;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot; data-token-type=&quot;important&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot; data-token-type=&quot;value&quot;&gt;None&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot; data-token-type=&quot;punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;hr&gt;
&lt;p&gt;System prompt:&lt;/p&gt;
&lt;figure data-rehype-pretty-code-figure=&quot;&quot;&gt;&lt;pre tabindex=&quot;0&quot; data-language=&quot;python&quot; data-theme=&quot;github-light github-dark&quot;&gt;&lt;code data-language=&quot;python&quot; data-theme=&quot;github-light github-dark&quot; style=&quot;display: grid;&quot;&gt;&lt;span data-line=&quot;&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot; data-token-type=&quot;value&quot;&gt;SYSTEM_MSG&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot; data-token-type=&quot;operator&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt; &quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;You are an expert OCR and layout analyst. When processing an image, use the defined JSON schemas (BoundingBox, Object, Highlight and BookOCR) as guidance for your structured output. Your output must be a well-formed HTML fragment (including &amp;#x3C;html&gt; and &amp;#x3C;body&gt; tags) that preserves headings, paragraphs, lists, tables, images, figures, and figure captions.&lt;/span&gt;&lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt; &lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;Extract images or any figures by obtaining their name, caption, bounding box, and page offset index. For each detection, output only the bounding box information in the format &amp;#x3C;x1&gt;&amp;#x3C;y1&gt;&amp;#x3C;x2&gt;&amp;#x3C;y2&gt;. If the figure/image is only text, do not output a Object.&lt;/span&gt;&lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt; &lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;When inserting images into the HTML, tag them using the &amp;#x3C;img&gt; element with a placeholder for the src attribute. Instead of a generic placeholder, incorporate the Object.name value so that each &amp;#x3C;img&gt; tag looks like &amp;#x3C;img src=&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot; data-token-type=&quot;value&quot;&gt;{Object.name}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;&quot; alt=&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot; data-token-type=&quot;value&quot;&gt;{Object.name}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;&quot;/&gt;. This links the parsed objects/images by their common name in the HTML, enabling downstream browsers to correctly load the images.&lt;/span&gt;&lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt; &lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;Extract any highlighted text phrases or sentences.&lt;/span&gt;&lt;/span&gt;
&lt;span data-line=&quot;&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot; data-token-type=&quot;string&quot;&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h1 id=&quot;elasticsearch-vectorstore&quot;&gt;Elasticsearch Vectorstore&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#elasticsearch-vectorstore&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Indexed all text content from JSON documents output from OCR step to use for RAG search&lt;/p&gt;
&lt;p&gt;For each document:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Strips text&lt;/strong&gt; from the &lt;code&gt;html&lt;/code&gt; field with BeautifulSoup.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embeds&lt;/strong&gt; with &lt;em&gt;all-MiniLM-L6-v2&lt;/em&gt; (384-dim).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Indexes&lt;/strong&gt; twice:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;books&lt;/code&gt; – global corpus.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;books_&amp;#x3C;slug&gt;&lt;/code&gt; – per-book index for scoped search.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Mappings include both a &lt;code&gt;dense_vector&lt;/code&gt; field and redundant &lt;code&gt;metadata.page_number / num_objects&lt;/code&gt; so RAG results can cite page numbers immediately&lt;/p&gt;
&lt;h1 id=&quot;open-webui-function&quot;&gt;Open WebUI Function&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#open-webui-function&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/screenshot-2025-06-10-at-17.56.32.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Finally, for using the vectors within a chat I built an &lt;a href=&quot;https://docs.openwebui.com/&quot; class=&quot;external external-link&quot;&gt;Open WebUI&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; &lt;a href=&quot;https://docs.openwebui.com/features/plugin/functions/&quot; class=&quot;external external-link&quot;&gt;function&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; that performs RAG search over the indexed book pages given a query&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uses the &lt;a href=&quot;https://docs.llamaindex.ai/en/stable/examples/query_engine/CustomRetrievers/&quot; class=&quot;external external-link&quot;&gt;&lt;code&gt;RetrieverQueryEngine&lt;/code&gt;&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; from &lt;a href=&quot;https://docs.llamaindex.ai/en/stable/&quot; class=&quot;external external-link&quot;&gt;llama index&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; with &lt;strong&gt;tree-summarize&lt;/strong&gt; mode to turn the top-k pages into a tidy, hierarchical answer&lt;/li&gt;
&lt;li&gt;Switch between &lt;strong&gt;global&lt;/strong&gt; or &lt;strong&gt;single-book&lt;/strong&gt; search by filling the &lt;code&gt;book&lt;/code&gt; valve inside the chat&lt;/li&gt;
&lt;li&gt;Ships as a simple plugin (&lt;code&gt;plugin.json&lt;/code&gt;) so any model chatting in WebUI can call it without code changes&lt;/li&gt;
&lt;/ul&gt; ]]></description>
    <pubDate>Fri, 30 May 2025 07:00:00 GMT</pubDate>
  </item><item>
    <title>AWS Lambda Parallelizing Invocations with S3 Batch Operations</title>
    <link>https://blog.neutronuniverse.com/projects/10-20-20-aws-lambda-parallelizing-invocations-with-s3-batch-operations</link>
    <guid>https://blog.neutronuniverse.com/projects/10-20-20-aws-lambda-parallelizing-invocations-with-s3-batch-operations</guid>
    <description><![CDATA[ &lt;p&gt;&lt;img src=&quot;../projects/attachments/1tdlbs3setgexoqxwtsgg2q.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-problem&quot;&gt;The Problem&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#the-problem&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have a simple Lambda function that downloads historical equity trades for a given symbol (“AAPL”) and date (“2020–01–01”) and saves the output as a parquet file to S3 for analysis but how can I parallelize the invocations when my input list is very large? If I use Boto3 and Python on my local machine, the downloads of symbol+date are sequentially invoked which results in O(N) performance. Searching around online for a better solution, I found a &lt;a href=&quot;https://medium.com/@klishch/how-to-run-1000s-of-aws-lambda-functions-each-minute-1658cedec7d2&quot; class=&quot;external external-link&quot;&gt;post&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; by &lt;a href=&quot;https://medium.com/u/728cd0c56a20?source=post_page---user_mention--264004b6fc6f---------------------------------------&quot; class=&quot;external external-link&quot;&gt;Ivan Klishch&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; in which he used a separate controller Lambda that accepted a range of input and invoked the base Lambda function in a “ &lt;a href=&quot;https://www.pubnub.com/learn/glossary/what-is-fan-out-software/&quot; class=&quot;external external-link&quot;&gt;fan-out&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; ” pattern. This works pretty well but I think I figured out a better solution using S3 batch operations.&lt;/p&gt;
&lt;h2 id=&quot;what-is-s3-batch-operations&quot;&gt;What is S3 Batch Operations?&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#what-is-s3-batch-operations&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.aws.amazon.com/AmazonS3/latest/user-guide/batch-ops.html&quot; class=&quot;external external-link&quot;&gt;AWS S3 Batch Operations&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; is designed to perform large-scale batch operations on S3 objects using a manifest that can be generated automatically using a &lt;a href=&quot;https://docs.aws.amazon.com/AmazonS3/latest/user-guide/configure-inventory.html&quot; class=&quot;external external-link&quot;&gt;S3 inventory report&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;. Among the batch operations that can be performed, you can specify a Lambda function to invoke which, for each invocation, the bucket and key name of an object is provided.&lt;/p&gt;
&lt;p&gt;Batch Operations seems useful for existing S3 objects but what about objects that don’t exist yet? Can we tell Batch Operations our desired objects and let it handle the Lambda invocations? The answer is yes, yes we can.&lt;/p&gt;
&lt;h2 id=&quot;generating-a-custom-manifest-with-lambda-arguments&quot;&gt;Generating a Custom Manifest with Lambda Arguments&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#generating-a-custom-manifest-with-lambda-arguments&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;According to the Batch Operations console, the manifest file can be any CSV with the columns “BucketName”, “S3Key”, and an optional “Version” column.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/116gwxyiks6wbrnqe48vzrg.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;p&gt;So to generate a custom CSV for downloading historical equity trades, I would just need to generate a CSV containing the arguments for my Lambda such as:&lt;/p&gt;
&lt;p&gt;“BucketName”,”ABBV/2020–01–02”&lt;br&gt;
“BucketName”,”ABBV/2020–01–03”&lt;br&gt;
“BucketName”,”ABBV/2020–01–04”&lt;br&gt;
“BucketName”,”ABBV/2020–01–05”&lt;br&gt;
………….etc…………..&lt;/p&gt;
&lt;p&gt;Here I am using the “S3Key” column value as my Lambda argument list delimited by a backslash “/” but really all three columns could be used as arguments to Lambda since they are bound to the event message by Batch Operations passed to each invocation.&lt;/p&gt;
&lt;p&gt;I use a simple Python script to loop over the desired symbols plus dates to generate the CSV (with no headers) “manifest.csv” and upload it to my S3 bucket so Batch Operations can find it:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/1zk9nlfxsju6kllsp86x4na.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;p&gt;Generating a test manifest with 1 symbol and 200 dates&lt;/p&gt;
&lt;p&gt;Then I setup a S3 Batch Operations job and specify my custom manifest as the input:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/1r3xzmosgwaxc3ljlwwuadq.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;p&gt;And specify my downloader Lambda:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/1hi1331w2bkqvzbdadkvfhw.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;p&gt;Configure an output directory for the Batch results including any download errors:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/1ofkb8io9ktfihrnpv9hcea.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then create the job config, activate it, and wait for results:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/1yjmo7etqmfx7ztvfqygxyg.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;p&gt;And quicker than you can say “mitochondria is the powerhouse of the cell”, the job completes and my data is available:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/175ve6xcxf5uncgrqzyycea.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#conclusion&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This method of using a custom input CSV of Lambda arguments lets AWS S3 Batch Operations handle all of the parallelization and is cost effective (only $0.25 to run a batch job compared to doing all scheduling/invocation manually). There are of course multiple ways of ingesting data like this and I welcome any feedback on better methods of doing so.&lt;/p&gt; ]]></description>
    <pubDate>Tue, 20 Oct 2020 07:00:00 GMT</pubDate>
  </item><item>
    <title>Realtime Stock Trade Analysis With Apache NiFi, Kafka, and Flink (and Python)</title>
    <link>https://blog.neutronuniverse.com/projects/10-15-20-realtime-stock-trade-analysis-with-apache-nifi,-kafka,-and-flink-(and-python)</link>
    <guid>https://blog.neutronuniverse.com/projects/10-15-20-realtime-stock-trade-analysis-with-apache-nifi,-kafka,-and-flink-(and-python)</guid>
    <description><![CDATA[ &lt;p&gt;&lt;img src=&quot;../projects/attachments/1egbvxym2-9omlg_gbigpyq.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;p&gt;The Grand Canyon: the ultimate data flow&lt;/p&gt;
&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#introduction&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In this article we will dive into a way to monitor and trade realtime stock trades using several Apache applications and Python. All resources are available on the &lt;a href=&quot;https://github.com/dlarsen5/TradePipeline&quot; class=&quot;external external-link&quot;&gt;GitHub repo&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;. When I say realtime I mean live streaming equity trades since the concept of “realtime” is tricky to define with a time series. This pipeline is able to handle all ~9000 equities (and all other trade feeds) at once since each stage is horizontally scalable (you get what you pay for). If you aren’t familiar with the technologies involved:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nifi.apache.org/&quot; class=&quot;external external-link&quot;&gt;NiFi&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;: data processing application that uses a directed graph to organize a “flow” of data&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kafka.apache.org/&quot; class=&quot;external external-link&quot;&gt;Kafka&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;: distributed log that acts as a streaming database using Producers/Consumers of messages&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flink.apache.org/&quot; class=&quot;external external-link&quot;&gt;Flink&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;: distributed processing engine with stateful computations&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=FWz0oBYMIxU&amp;#x26;ab_channel=R1ckM0rty&quot; class=&quot;external external-link&quot;&gt;Python&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;: Python&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the actual trading strategy, I will be using some stochastic variation functions from my own &lt;a href=&quot;https://link.springer.com/article/10.1007%2Fs11156-020-00944-3&quot; class=&quot;external external-link&quot;&gt;academic research&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; that require maintaining a state of past log returns.&lt;/p&gt;
&lt;h2 id=&quot;data-source&quot;&gt;Data Source&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#data-source&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To receive trade data, I use &lt;a href=&quot;https://polygon.io/&quot; class=&quot;external external-link&quot;&gt;Polygon&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; as a provider since they have full trade streaming available with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API&quot; class=&quot;external external-link&quot;&gt;WebSockets&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; because who would ever want a message based credit system. The stock trade event schema we’ll be working with is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Event Type: String&lt;/li&gt;
&lt;li&gt;Symbol Ticker: String&lt;/li&gt;
&lt;li&gt;Exchange ID: Integer&lt;/li&gt;
&lt;li&gt;Trade ID: String&lt;/li&gt;
&lt;li&gt;Tape: Integer (1=”A”, 2=”B”, 3=”C”)&lt;/li&gt;
&lt;li&gt;Price: Double&lt;/li&gt;
&lt;li&gt;Trade Size: Integer&lt;/li&gt;
&lt;li&gt;Trade Conditions: List[String]&lt;/li&gt;
&lt;li&gt;Trade Timestamp: Unix NS (you know, for nano seconds)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;nifi&quot;&gt;NiFi&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#nifi&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To actually ingest the streaming trade data, I use NiFi’s &lt;a href=&quot;https://nifi.apache.org/docs/nifi-docs/components/org.apache.nifi/nifi-websocket-processors-nar/1.12.0/org.apache.nifi.processors.websocket.ConnectWebSocket/index.html&quot; class=&quot;external external-link&quot;&gt;ConnectWebSocket&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; processor and an input list of equity symbols. Then I push all received trades to a Kafka instance.&lt;/p&gt;
&lt;p&gt;Here is an overview of the flow:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/1w1aikbhzfuwqsfkfox_wnw.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;p&gt;The flow centers around the ConnectWebSocket processor that handles the connection, authentication, subscription, and trade message reception which then pushes messages to Kafka with topics keyed by equity symbol.&lt;/p&gt;
&lt;p&gt;To be use this graph, simply build the custom NiFi docker image that adds the flow to the base Apache image. Also add your Polygon token to the docker-compose config so NiFi can pull data:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/1zfqdjgls7pagwrze3ans0a.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;kafka&quot;&gt;Kafka&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#kafka&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I use Kafka so that I can persist and receive the messages ingested by NiFi. In the event that a NiFi producer or Flink consumer goes down, it doesn’t interrupt the rest of the system and the producer/consumer can be restarted and pull messages from where they left off using consumer offsets. Kafka relies on &lt;a href=&quot;https://zookeeper.apache.org/&quot; class=&quot;external external-link&quot;&gt;Zookeeper&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; for broker metadata so we need to also add it to our docker-compose config as well:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/1lmqmpggjatvyhgeofou3sq.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;flink&quot;&gt;Flink&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#flink&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I use Flink to subscribe to the trade messages and perform windowed aggregations. Flink is good because you can implement stateful computations with a &lt;a href=&quot;https://ci.apache.org/projects/flink/flink-docs-stable/dev/stream/state/checkpointing.html&quot; class=&quot;external external-link&quot;&gt;checkpointing mechanism&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;. Thus if we required a substantial amount of stateful information (incremental aggregations or indicators) for our trade calculations, our pipeline would be fault tolerant enough to quickly recover from any downtime. Sure I could use &lt;a href=&quot;https://spark.apache.org/docs/latest/streaming-programming-guide.html#:~:text=Spark%20Streaming%20is%20an%20extension,processing%20of%20live%20data%20streams.&amp;#x26;text=Spark%20Streaming%20provides%20a%20high,a%20continuous%20stream%20of%20data.&quot; class=&quot;external external-link&quot;&gt;Spark Streaming&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; but Flink has more complex windowing classes that would prove to be useful in the future with more complexly timed strategies (click-session windowing for equity trades??).&lt;/p&gt;
&lt;p&gt;The Scala code for our Flink job (forgive any bad code, I’m still learning the art of Scala):&lt;/p&gt;
&lt;p&gt;The trade metrics we calculate are:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/0jbepsohhh5dmjv3j.webp&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;p&gt;Log of Price Change:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/0bdi51fba8u15yk7x.webp&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;p&gt;Quadratic Variation&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/0lfwbefukli1r5d-h.webp&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;p&gt;Realized Variation&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/0azdq4ug5npdmcwsk.webp&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;p&gt;Bipower Variation&lt;/p&gt;
&lt;p&gt;From a combination of these papers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://pdfs.semanticscholar.org/a2c3/876aa60c8b7944923b2e8fd637062631abeb.pdf&quot; class=&quot;external external-link&quot;&gt;Wang, Huang (2012)&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.203.3772&amp;#x26;rep=rep1&amp;#x26;type=pdf&quot; class=&quot;external external-link&quot;&gt;Barndorff-Nielsen (2004)&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://public.econ.duke.edu/~get/browse/courses/201/spr11/2010-PRESENTATIONS/2010-01-20-Tauchen/HT-JFEC-2005.pdf&quot; class=&quot;external external-link&quot;&gt;Huang, Tauchen (2005)&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And adding Flink’s containers to the docker-compose config:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/1udsdwfb6-wqias_kcujjla.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;python-agent&quot;&gt;Python Agent&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#python-agent&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I use a basic Python class to implement an agent that subscribes to aggregate signals and, on message polls, updates its state and places orders. The agent also uses several subclasses to hold state about the portfolio, namely a portfolio class that has handles to several asset instances to hold current portfolio positions and calculate stats. For the broker, I’m using &lt;a href=&quot;https://alpaca.markets/&quot; class=&quot;external external-link&quot;&gt;Alpaca&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt; since they have $0 commission equity trades and access to a sandbox API with a free account on sign-up.&lt;/p&gt;
&lt;p&gt;On each message poll to Kafka (given a polling frequency that can be altered to fit how often the agent should trade), the agent updates its internal state, ranks its portfolio, generates new equity weights, and submits orders to rebalance. Here is the same thing I just said but in box form:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/1ovlxtakmiojfrkyebpfi5a.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;p&gt;An interesting feature of this design is that one could have N number of agents each implementing a different portfolio, ranking strategy, and/or update frequency. A result of fully decoupling each layer in the pipeline, each component can be specialized and tuned.&lt;/p&gt;
&lt;p&gt;Once you have an account, add the API key and secret to the docker-compose config under the agent.environment key so the agent has access:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/1o5gb-mjhxhnddf4p9lzrqa.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;p&gt;Change APCA_URL based on Paper/Live trading&lt;/p&gt;
&lt;p&gt;And set the Dockerfile config with these steps:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/1ykl3ibbyf144tk5oqnrbkg.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;p&gt;Yes I know I can just use the base python image without ubuntu but future proofing for java apps&lt;/p&gt;
&lt;h2 id=&quot;build-and-run&quot;&gt;Build and Run&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#build-and-run&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We need to build the submittable Flink jar application and the custom NiFi image before running the pipeline. Using a &lt;a href=&quot;https://www.gnu.org/software/make/manual/make.html&quot; class=&quot;external external-link&quot;&gt;Makefile&lt;svg aria-hidden=&quot;true&quot; class=&quot;external-icon&quot; style=&quot;max-width:0.8em;max-height:0.8em&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;path d=&quot;M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;, I define two targets:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/1jlj6uozytceyx2neb15m-g.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;p&gt;And then add the entry points for our pipeline:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/1portmnqiwerwobytkaczng.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;p&gt;Since we didn’t bundle our Flink jar, we must submit the job manually to the cluster using the &lt;code&gt;submit-flink&lt;/code&gt; target but only after we first start all of our other services using the &lt;code&gt;run&lt;/code&gt; target. Running &lt;code&gt;make run&lt;/code&gt; first builds our Flink job:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/1chccnivrputyoquhjqalma.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/1fohitiek3f-vob77m49uuw.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;p&gt;And our custom NiFi image:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/1w_qfcgqvk9qqvlynjz-xlg.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;p&gt;Run &lt;code&gt;make run&lt;/code&gt; then, once the Kafka topics are created by NiFi, then run &lt;code&gt;make submit-flink&lt;/code&gt; to submit the Flink job. Be sure to run this during market hours or there won’t be any trade data to pull and operate on.&lt;/p&gt;
&lt;p&gt;NiFi will start pulling messages and pushing to their respective Kafka topics from which Flink will calculate our metrics over finally pushing the aggregates back to Kafka for the Python trading Agent to receive and trade upon.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../projects/attachments/1pdhuuqaamaa4tizh0xylcq.png&quot; alt=&quot;&quot; width=&quot;auto&quot; height=&quot;auto&quot;&gt;&lt;/p&gt;
&lt;p&gt;Tmux split with “make run” on top, “make submit-flink” on bottom&lt;/p&gt;
&lt;h2 id=&quot;thats-the-way-the-cookie-crumbles&quot;&gt;That’s the way the cookie crumbles&lt;a role=&quot;anchor&quot; aria-hidden tabindex=&quot;-1&quot; data-no-popover href=&quot;#thats-the-way-the-cookie-crumbles&quot; class=&quot;internal internal-link&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Thanks for reading, I’m not that interested in over-engineering this pipeline anymore so hopefully I passed at least a minuscule crumb of knowledge onto you. I pray to the code gods that your copy and paste of this project worked perfectly the first time 🎲 (honestly there have been random encoding errors but I swear this pipeline has worked most of the time).&lt;/p&gt; ]]></description>
    <pubDate>Thu, 15 Oct 2020 07:00:00 GMT</pubDate>
  </item>
    </channel>
  </rss>