<?xml version='1.0' encoding='UTF-8'?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/" xmlns:blogger="http://schemas.google.com/blogger/2008" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" version="2.0"><channel><atom:id>tag:blogger.com,1999:blog-7022755517551007355</atom:id><lastBuildDate>Sun, 29 Mar 2026 15:48:08 +0000</lastBuildDate><category>Delphi</category><category>News</category><category>TCanvas</category><category>Open Source</category><category>Java</category><category>Maths</category><category>Delphi 2010</category><category>Pragmatic Programming</category><category>Tools</category><category>VLO Framework</category><category>Components</category><category>Computational geometry</category><category>English</category><category>Extreme Programming</category><category>Python</category><category>Delphi XE</category><category>AGILE</category><category>Videos</category><category>Internet</category><category>Design patterns</category><category>Utilities</category><category>Artificial Intelligence</category><category>IDE Delphi</category><category>Physics</category><category>Eclipse</category><category>Operating System</category><category>.NET</category><category>Vijeo Citect</category><category>Productivity</category><category>Windows</category><category>Encryption Algorithms</category><category>P-Zaggy</category><category>Robotics</category><category>API Windows</category><category>Force-Directed Graph</category><category>Image processing</category><category>Indy</category><category>TED</category><category>openAI</category><category>Configuration library</category><category>Data Bases</category><category>Subversion</category><category>2D</category><category>Aspect oriented programming</category><category>COM Objects</category><category>Industrial Automation</category><category>Machine Learning</category><category>MySQL</category><category>RTTI</category><category>Raspberry Pi</category><category>Regular Expressions</category><category>Scada</category><category>Security</category><category>Debug</category><category>Education</category><category>Excel</category><category>JBoss</category><category>SQL Server</category><category>Software configuration</category><category>communications</category><category>github</category><category>google guice</category><category>llm</category><category>neuroplasticity</category><category>.net core 3.0</category><category>3D</category><category>AI Agents</category><category>ASP.NET</category><category>Android</category><category>Chaining Method</category><category>Continuous Integration</category><category>Cryptography</category><category>Dependency Injection</category><category>Google</category><category>Google Chrome</category><category>HTML</category><category>JDBC</category><category>JSON</category><category>JavaScript</category><category>LangChain</category><category>Lazarus</category><category>Linux</category><category>Microsoft</category><category>Molecules</category><category>OpenSSL</category><category>Parse.com</category><category>Persistence</category><category>RAG</category><category>REST</category><category>Services</category><category>Ubuntu</category><category>nodejs</category><category>ruby</category><category>wetware</category><category>API</category><category>Advanced Encryption Standard</category><category>Atom table</category><category>Code Coverage</category><category>Delphi10.2Tokyo</category><category>DevExpress</category><category>Extensions</category><category>Free Pascal</category><category>IT</category><category>Icons</category><category>Industrial Control</category><category>Kernel</category><category>MVC</category><category>Messaging</category><category>Ollama</category><category>Routes</category><category>Scripts</category><category>Scrum</category><category>TDD</category><category>TeamCity</category><category>Virtual machines</category><category>Windows Mobile</category><category>XML</category><category>csv</category><category>games</category><category>portfolio-optimization</category><category>ADO</category><category>AI</category><category>BaaS</category><category>Blogger</category><category>ChatBot</category><category>Cloud</category><category>Code Quality</category><category>Code Reviews</category><category>ComfyUI</category><category>Compilers</category><category>Compliance Automation</category><category>Console</category><category>Convex Hull</category><category>DLL</category><category>DUnit</category><category>Delphi XE6</category><category>DelphiBerlin</category><category>Devops</category><category>Dijkstra</category><category>Docker</category><category>Exceptions</category><category>FIT</category><category>Facebook</category><category>Finance</category><category>Financial Regulation</category><category>Firebase</category><category>Generics</category><category>Graph Visualization</category><category>Hacking</category><category>Hibernate</category><category>Investment</category><category>Kubernetes</category><category>LINQ</category><category>LangGraph</category><category>LoRA</category><category>Mock</category><category>NDepend</category><category>Notification</category><category>OLE</category><category>Object oriented programming</category><category>Pair Programming</category><category>Parsing</category><category>Photography</category><category>Prim Algorithm</category><category>Profiling</category><category>RPi3</category><category>RSA</category><category>Redes</category><category>Refactoring</category><category>Ribbon Controls</category><category>SQL</category><category>SSL</category><category>Science</category><category>Sharing</category><category>TStringGrid</category><category>UOC</category><category>Unicode</category><category>Unit Testing</category><category>University</category><category>VSCode</category><category>WMI</category><category>Web API</category><category>WorkFlow</category><category>XML-RPC</category><category>ZeosDBO</category><category>actions</category><category>bot</category><category>deployment</category><category>efficient-frontier</category><category>flickr</category><category>gnuplot</category><category>monte-carlo</category><category>neuroscience</category><category>redmine</category><category>riskoptima</category><category>telegram</category><category>.net 5</category><category>.net 9</category><category>.net core 2.1</category><category>.net core 3.1</category><category>AG2</category><category>AI Chatbot</category><category>AI Coding Agents</category><category>AI workflow</category><category>AIAgents</category><category>AICodingAssistant</category><category>AJAX</category><category>ASCII</category><category>ASP.NET Core</category><category>AST Evaluation</category><category>Abstract Factory</category><category>Access</category><category>ActiveX</category><category>Agent Orchestration</category><category>Agentic AI</category><category>AgenticAI</category><category>Alpha-beta pruning</category><category>Analytics</category><category>AspectJ</category><category>Augmented Reality</category><category>AutoGen</category><category>BackEnd</category><category>Backup</category><category>Base64</category><category>C#</category><category>C++</category><category>CLI</category><category>CNN</category><category>CUP</category><category>Calculator Tool</category><category>CatvsDog</category><category>Certification</category><category>Chainlit</category><category>Claude Code</category><category>Clipboard</category><category>Cloud Computing</category><category>CodeLlama7B</category><category>Comparison</category><category>Compression</category><category>Concordion</category><category>ContinueExtension</category><category>CrewAI</category><category>CruiseControl</category><category>Currency</category><category>DBase</category><category>DLTK</category><category>Data Analysis</category><category>Data Recovery</category><category>Data Science</category><category>Data Visualization</category><category>Death March</category><category>Debian</category><category>Deep Learning</category><category>Delegates</category><category>Delphi XE5</category><category>Delphi XE7</category><category>Delphi10Seattle</category><category>DelphiFMX</category><category>DelphiTokyo</category><category>Deterministic AI</category><category>Developer Tooling</category><category>DeveloperProductivity</category><category>Devices</category><category>Diaspora</category><category>Digital Identity</category><category>Direct2D</category><category>EADS</category><category>ERP</category><category>ESMA</category><category>Entrepreneur</category><category>Exif</category><category>Files</category><category>Flash</category><category>FontAwesome</category><category>Forecasting</category><category>Fractals</category><category>Fuzzy</category><category>GEO</category><category>GPT-4o</category><category>GPT‑2</category><category>Game</category><category>Geek</category><category>Generative AI</category><category>GenerativeAI</category><category>Genetic Algorithms</category><category>Google Maps</category><category>Google Noop</category><category>Google SketchUp</category><category>Google insight</category><category>Google wave</category><category>Gource</category><category>GraphBasedWorkflows</category><category>HTTPS</category><category>Hardware</category><category>HuggingFace</category><category>IDE</category><category>IPTC</category><category>InceptionResnetV2</category><category>InnoSetup</category><category>Interface oriented programming</category><category>J2EE</category><category>JEDI</category><category>JUnit</category><category>Jenkins</category><category>Jflex</category><category>Jobs</category><category>Kanban</category><category>Keras</category><category>KeyStrokes</category><category>Kinvey</category><category>LLM inference</category><category>LLM integration</category><category>LLM tool calling</category><category>LSTM</category><category>Local LLM</category><category>LocalLLM</category><category>MVC5</category><category>Matlab</category><category>Matplotlib</category><category>MiFID II</category><category>MiFID II/III Updates</category><category>MiFIR</category><category>Modbus</category><category>Multi-Agent AI (CrewAI)</category><category>NAS</category><category>NUnit</category><category>Near Real-Time Reporting</category><category>NetStat</category><category>OSGi</category><category>Observability</category><category>Offline AI</category><category>OfflineCopilot</category><category>OpenAI GPT-4o</category><category>PCB</category><category>PDF</category><category>PDFSearchTool</category><category>PFOF Ban &amp; Consolidated Tape</category><category>PHP</category><category>PPL</category><category>PaaS</category><category>Pandas Agent</category><category>Pathfinding</category><category>Peaberry</category><category>Penjili</category><category>Perl</category><category>Powershell</category><category>Prezi</category><category>PrivacyFirstAI</category><category>Private AI</category><category>Processing</category><category>Prompt</category><category>Prompt Engineering</category><category>PushNotifications</category><category>Pydantic</category><category>Python CLI</category><category>QR2</category><category>R2build</category><category>RPC</category><category>RSS</category><category>ReAct</category><category>RegTech</category><category>Registro Windows</category><category>ReportBuilder</category><category>Restful</category><category>Retrieval Augmented Generation</category><category>SDK</category><category>SDXL</category><category>SMTP</category><category>SOAP</category><category>SP500</category><category>Safe Evaluation</category><category>Sequence Diagrams</category><category>Servlet</category><category>SignalR</category><category>Simuladores</category><category>Skills</category><category>Speech SDK</category><category>StableDiffusion</category><category>Steve Jobs</category><category>Storage</category><category>Streaming</category><category>Streaming Outputs</category><category>Subclipse</category><category>Syntax Highlighting</category><category>TBookMark</category><category>TCustomAttributes</category><category>TFN</category><category>TKinter</category><category>Task</category><category>Task-Centric AI</category><category>TensorFlow</category><category>Thiessen</category><category>Tool Calling</category><category>ToolsAndOrchestration</category><category>Trac</category><category>Tron</category><category>UML</category><category>UTF-8</category><category>VBA</category><category>VCL</category><category>VIX</category><category>VNC</category><category>VS Code</category><category>Visprint</category><category>Visual Studio 2019</category><category>Voronoi</category><category>WSDL</category><category>Web Service</category><category>WinRM</category><category>Windows10</category><category>Windows11Dev</category><category>Winsock</category><category>YouTube summarization</category><category>Zaluum</category><category>ai‑workflow</category><category>algebra</category><category>analysis suite</category><category>arcade</category><category>architecture</category><category>art</category><category>asset allocation</category><category>automation</category><category>budgeting</category><category>bugs</category><category>charts</category><category>chunked summarization</category><category>classification</category><category>code rush</category><category>codex</category><category>command</category><category>compliance</category><category>computing</category><category>continuous batching</category><category>copyright</category><category>csharp</category><category>dev‑environment</category><category>diagram</category><category>diff</category><category>divergenge</category><category>dotCover</category><category>dynamic portfolio</category><category>dynamic quantization</category><category>eReader</category><category>ebook</category><category>email signature</category><category>exporter</category><category>fictional personas</category><category>film</category><category>fixed chain</category><category>function calling</category><category>fundamental rights</category><category>git</category><category>greeks</category><category>group chat</category><category>heroku</category><category>index</category><category>inheritance</category><category>investment advisor</category><category>investment strategies</category><category>jqplot</category><category>large files</category><category>latency reduction</category><category>logger</category><category>lua</category><category>macroeconomics</category><category>map-reduce</category><category>market analysis</category><category>matrix</category><category>mean-variance</category><category>memory reduction</category><category>mobile</category><category>mouse</category><category>multi-agent</category><category>network</category><category>ocr</category><category>openclaw</category><category>options</category><category>orchestrator-worker pattern</category><category>owasp</category><category>parallel</category><category>password</category><category>performance</category><category>performance optimization</category><category>portfolio</category><category>private repository</category><category>protobuf</category><category>pygame</category><category>quantization</category><category>recommendation</category><category>recursive agent</category><category>reflection pattern</category><category>remoting</category><category>repositories</category><category>responsive</category><category>risk grades</category><category>risk tolerance</category><category>scripting</category><category>self-hosting</category><category>serilog</category><category>simulator</category><category>social networks</category><category>spectre.console</category><category>straddle</category><category>supercomputer</category><category>tesseract</category><category>testing</category><category>threading</category><category>throughput improvement</category><category>tutor</category><category>tutorial</category><category>twitter</category><category>vibe coding</category><category>visualization</category><category>vol</category><category>web-scraping</category><category>weight‑only quantization</category><category>whatsapp</category><category>windows service</category><category>wsl2</category><category>youtube-transcript-api</category><category>yt-dlp</category><title>Random thoughts on coding and technology</title><description>I’m Jordi Corbilla, a Senior Full-Stack &amp;amp; Quantitative Engineer working across trading, compliance, risk, and AI systems. This blog is where I write about practical engineering: Python, .NET, quantitative tooling, local/hybrid LLM workflows, and the systems thinking behind real-world software.</description><link>https://thundaxsoftware.blogspot.com/</link><managingEditor>noreply@blogger.com (Jordi Corbilla)</managingEditor><generator>Blogger</generator><openSearch:totalResults>544</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-5715822006633499128</guid><pubDate>Sun, 29 Mar 2026 15:48:00 +0000</pubDate><atom:updated>2026-03-29T15:48:08.367+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">csv</category><category domain="http://www.blogger.com/atom/ns#">large files</category><category domain="http://www.blogger.com/atom/ns#">Productivity</category><category domain="http://www.blogger.com/atom/ns#">Python</category><title>Rowlens: finding one exact CSV row in a 17 GB file without blowing up memory</title><description>&lt;p&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh65WHkeBnAjL5weuaCEMa6-79CTBp-mtp460lrmlox5wACR4yRgotXMsSr6HZg_0ygZV3Bge4m-2g6V2PpWw-4eFQ1faVecOEINkSInifyqmelIQgNnqB2v9MdEhkdkTG0YJxHK7VX_lcN0M2O4LEP860qESO1QsWqzTAzZdgGiBjDkF3Iva2wM72CeX4/s1536/ChatGPT%20Image%20Mar%2029,%202026,%2004_47_38%20PM.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1536&quot; height=&quot;213&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh65WHkeBnAjL5weuaCEMa6-79CTBp-mtp460lrmlox5wACR4yRgotXMsSr6HZg_0ygZV3Bge4m-2g6V2PpWw-4eFQ1faVecOEINkSInifyqmelIQgNnqB2v9MdEhkdkTG0YJxHK7VX_lcN0M2O4LEP860qESO1QsWqzTAzZdgGiBjDkF3Iva2wM72CeX4/s320/ChatGPT%20Image%20Mar%2029,%202026,%2004_47_38%20PM.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;Most CSV tooling assumes the file is still small enough to load, sort, or inspect interactively. That breaks down fast once the file is measured in gigabytes.&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;rowlens&lt;/code&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px;&quot;&gt;was built for the opposite case: a CLI you can point at a very large CSV when you already know the clues you are looking for and you want the exact matching rows back in a readable format.&lt;/span&gt;&lt;p&gt;&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;4&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The design constraint was simple: never read the full file into memory.&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;&lt;a href=&quot;https://github.com/JordiCorbilla/rowlens&quot;&gt;rowlens&lt;/a&gt;&lt;/code&gt;&amp;nbsp;opens the CSV as a stream, reads the header once, and then processes each record one at a time with Python&#39;s standard&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;csv&lt;/code&gt;&amp;nbsp;reader. That keeps memory usage effectively flat even when the input file is 17 GB or more. The tool does not build an index, cache rows, or attempt an in-memory dataframe workflow. It just walks the file once and stops early if you set&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;--max-results&lt;/code&gt;.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;6&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The matching model is intentionally narrow for version 1.0. Repeated&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;--keyword&lt;/code&gt;&amp;nbsp;arguments are treated as exact cell-value matches. Repeated&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;--filter&lt;/code&gt;&amp;nbsp;arguments are treated as substring checks. A row is returned only when it satisfies every supplied condition. That gives a useful two-stage search pattern in practice: use&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;--keyword&lt;/code&gt;&amp;nbsp;for the hard identifier, then&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;--filter&lt;/code&gt;&amp;nbsp;to narrow the surrounding context.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;8&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;Example:&lt;/p&gt;&lt;pre style=&quot;background-color: rgba(10, 10, 10, 0.4); border-color: rgb(48, 48, 49); border-image: none 100% / 1 / 0 stretch; border-radius: 3px; border-style: solid; border-width: 1px; font-size: 14px; margin-top: 0px; overflow: auto; padding: 16px; text-wrap-mode: wrap;&quot;&gt;&lt;code class=&quot;code-line language-bash&quot; data-line=&quot;10&quot; dir=&quot;auto&quot; style=&quot;background: none; border-radius: 4px; display: inline-block; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 0px; position: relative; tab-size: 4;&quot;&gt;rowlens --file &lt;span class=&quot;hljs-string&quot;&gt;&quot;huge.csv&quot;&lt;/span&gt; --keyword &lt;span class=&quot;hljs-string&quot;&gt;&quot;1213131&quot;&lt;/span&gt; --filter &lt;span class=&quot;hljs-string&quot;&gt;&quot;AAA&quot;&lt;/span&gt; --output &lt;span class=&quot;hljs-string&quot;&gt;&quot;results.txt&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;14&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;If a row matches, the output is not dumped as raw CSV. Instead,&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;rowlens&lt;/code&gt;&amp;nbsp;renders a bordered CLI report with summary metadata at the top and then a per-match table that lists each column name beside its value. That matters more than it sounds. When you are debugging a production extract or validating a record in an enormous export, the important part is not just finding the row. The important part is understanding the row immediately.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;16&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;That output style was inspired by terminal-first tooling such as&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;csv-stream-diff&lt;/code&gt;: strong borders, obvious sections, and tabular structure that still works in plain text when redirected to a file. The same rendered report can be written with&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;--output&lt;/code&gt;, which makes it easy to attach to a ticket, share in chat, or keep as a trace artifact from an investigation.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;18&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;Under the hood, Python was a good fit here because the problem is mostly streaming IO plus deterministic row checks. The package is structured with Poetry from the start so it can be shipped cleanly to PyPI. The CLI entry point lives behind the&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;rowlens&lt;/code&gt;&amp;nbsp;console script, dependencies are minimal, and the test suite covers the main behaviors: combined keyword and filter matching, case-insensitive search, support for extra cells beyond the header, and output-file generation.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;20&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;Version 1.0 is deliberately focused. It solves one job well: find the exact row you care about in a file too large for the usual tools, then present it cleanly. Future iterations could add column-scoped matching, JSON output, compressed input support, or richer summary stats. But the first release already hits the core operational need: streaming search for massive CSVs with output that humans can read immediately.&lt;/p&gt;</description><link>https://thundaxsoftware.blogspot.com/2026/03/rowlens-finding-one-exact-csv-row-in-17.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh65WHkeBnAjL5weuaCEMa6-79CTBp-mtp460lrmlox5wACR4yRgotXMsSr6HZg_0ygZV3Bge4m-2g6V2PpWw-4eFQ1faVecOEINkSInifyqmelIQgNnqB2v9MdEhkdkTG0YJxHK7VX_lcN0M2O4LEP860qESO1QsWqzTAzZdgGiBjDkF3Iva2wM72CeX4/s72-c/ChatGPT%20Image%20Mar%2029,%202026,%2004_47_38%20PM.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-3076618669400551702</guid><pubDate>Sat, 28 Mar 2026 22:00:00 +0000</pubDate><atom:updated>2026-03-28T22:00:09.637+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">csv</category><category domain="http://www.blogger.com/atom/ns#">exporter</category><category domain="http://www.blogger.com/atom/ns#">Python</category><category domain="http://www.blogger.com/atom/ns#">SQL Server</category><title>Building sqlcsv-exporter: a Python CLI to Stream SQL Server Data into CSV</title><description>&lt;p&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlUQQvA389uHODF4CmfDMdiRASbUCsVe8lLxJIbpR5KZENbaZWk2geoH9uDaR9UMAPv18gPioTmBfBIfOh1ITv0rTYFzIlobRdOdBS87T2MT5p_GgdxwMeV5Qc6QMYnklHDvntq_LNwZN2r2kIFoNsCrkOtJ9fZwb9m-dmh0YsKzsho61dLGdKdTk9DSc/s1536/ChatGPT%20Image%20Mar%2028,%202026,%2009_58_18%20PM.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1536&quot; height=&quot;213&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlUQQvA389uHODF4CmfDMdiRASbUCsVe8lLxJIbpR5KZENbaZWk2geoH9uDaR9UMAPv18gPioTmBfBIfOh1ITv0rTYFzIlobRdOdBS87T2MT5p_GgdxwMeV5Qc6QMYnklHDvntq_LNwZN2r2kIFoNsCrkOtJ9fZwb9m-dmh0YsKzsho61dLGdKdTk9DSc/s320/ChatGPT%20Image%20Mar%2028,%202026,%2009_58_18%20PM.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;I recently built a small Python package called&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;&lt;a href=&quot;https://github.com/JordiCorbilla/sqlcsv-exporter&quot;&gt;sqlcsv-exporter&lt;/a&gt;&lt;/code&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px;&quot;&gt;to solve a very practical problem: export the results of a SQL Server query into a CSV file, from the command line, without dragging an entire dataset into memory.&lt;/span&gt;&lt;p&gt;&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;4&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The tool is packaged as a Poetry project, designed to be publishable to PyPI, and focused on a workflow that is common in reporting and data operations teams:&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;4&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;6&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;6&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;store the SQL in a&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;.sql&lt;/code&gt;&amp;nbsp;file&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;7&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;pass connection details at runtime&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;8&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;stream rows in chunks&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;9&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;write the CSV incrementally&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;10&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;show visible progress while the job runs&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;12&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;This post walks through why I built it, how it works, and what I learned while testing it against a local SQL Server instance.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;14&quot; dir=&quot;auto&quot; id=&quot;the-problem&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;The Problem&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;16&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The starting point was familiar: a one-off Python script that connected to SQL Server, ran a query, and wrote a CSV. That works for quick experiments, but it gets messy fast.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;18&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;Typical issues show up immediately:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;20&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;20&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;query text is embedded inside Python instead of living in a&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;.sql&lt;/code&gt;&amp;nbsp;file&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;21&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;authentication logic is mixed into export logic&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;22&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;the script is hard to reuse across environments&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;23&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;large result sets become memory-heavy&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;24&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;the UX is poor when a query runs for several seconds or several minutes&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;26&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The goal was to replace that with a proper CLI package that feels like a real tool rather than a disposable script.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;28&quot; dir=&quot;auto&quot; id=&quot;the-goal&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;The Goal&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;30&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;I wanted a command like this:&lt;/p&gt;&lt;pre style=&quot;background-color: rgba(10, 10, 10, 0.4); border-color: rgb(48, 48, 49); border-image: none 100% / 1 / 0 stretch; border-radius: 3px; border-style: solid; border-width: 1px; font-size: 14px; margin-top: 0px; overflow: auto; padding: 16px; text-wrap-mode: wrap;&quot;&gt;&lt;code class=&quot;code-line language-powershell&quot; data-line=&quot;32&quot; dir=&quot;auto&quot; style=&quot;background: none; border-radius: 4px; display: inline-block; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 0px; position: relative; tab-size: 4;&quot;&gt;sqlcsv&lt;span class=&quot;hljs-literal&quot;&gt;-exporter&lt;/span&gt; `
  &lt;span class=&quot;hljs-literal&quot;&gt;--sql&lt;/span&gt; .\queries\report.sql `
  &lt;span class=&quot;hljs-literal&quot;&gt;--output&lt;/span&gt; .\exports\report.csv `
  &lt;span class=&quot;hljs-literal&quot;&gt;--server&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;DESKTOP-TTUSQLJ\SQLEXPRESS&quot;&lt;/span&gt; `
  &lt;span class=&quot;hljs-literal&quot;&gt;--database&lt;/span&gt; QuantDevTest
&lt;/code&gt;&lt;/pre&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;40&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The requirements were straightforward:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;42&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;42&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;read SQL from file&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;43&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;connect to SQL Server with trusted auth or SQL auth&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;44&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;stream the result set with&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;fetchmany()&lt;/code&gt;&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;45&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;write CSV rows incrementally&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;46&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;avoid pandas for the write path&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;47&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;provide useful progress output&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;48&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;keep the package testable and publishable&lt;/li&gt;&lt;/ul&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;50&quot; dir=&quot;auto&quot; id=&quot;the-package-structure&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;The Package Structure&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;52&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;I split the project into small modules so each concern is isolated:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;54&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;54&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;cli.py&lt;/code&gt;: argument parsing and process exit behavior&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;55&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;config.py&lt;/code&gt;: config model and validation&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;56&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;connection.py&lt;/code&gt;: ODBC connection string and connection creation&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;57&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;sql_rewriter.py&lt;/code&gt;: SQL file loading and optional variable rewriting&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;58&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;exporter.py&lt;/code&gt;: query execution, chunked fetch, CSV writing, and progress rendering&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;60&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;That separation matters because it makes the code easier to test. The CSV writer can be tested without a database. The SQL rewriting logic can be tested with simple strings. The export workflow can be tested with mocked connections and cursors.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;62&quot; dir=&quot;auto&quot; id=&quot;why-streaming-matters&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;Why Streaming Matters&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;64&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The core design choice was to stream results instead of loading everything into memory.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;66&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The exporter:&lt;/p&gt;&lt;ol class=&quot;code-line&quot; data-line=&quot;68&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;68&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;opens the SQL file&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;69&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;optionally rewrites a declared date variable such as&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;@InAsOfDate&lt;/code&gt;&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;70&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;executes the query with&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;pyodbc&lt;/code&gt;&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;71&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;fetches rows in chunks&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;72&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;writes each chunk directly to a CSV file&lt;/li&gt;&lt;/ol&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;74&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;That keeps the memory profile stable even when the result set gets large.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;76&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;Instead of doing something like this:&lt;/p&gt;&lt;pre style=&quot;background-color: rgba(10, 10, 10, 0.4); border-color: rgb(48, 48, 49); border-image: none 100% / 1 / 0 stretch; border-radius: 3px; border-style: solid; border-width: 1px; font-size: 14px; margin-top: 0px; overflow: auto; padding: 16px; text-wrap-mode: wrap;&quot;&gt;&lt;code class=&quot;code-line language-python&quot; data-line=&quot;78&quot; dir=&quot;auto&quot; style=&quot;background: none; border-radius: 4px; display: inline-block; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 0px; position: relative; tab-size: 4;&quot;&gt;rows = cursor.fetchall()
writer.writerows(rows)
&lt;/code&gt;&lt;/pre&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;83&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;the tool does this conceptually:&lt;/p&gt;&lt;pre style=&quot;background-color: rgba(10, 10, 10, 0.4); border-color: rgb(48, 48, 49); border-image: none 100% / 1 / 0 stretch; border-radius: 3px; border-style: solid; border-width: 1px; font-size: 14px; margin-top: 0px; overflow: auto; padding: 16px; text-wrap-mode: wrap;&quot;&gt;&lt;code class=&quot;code-line language-python&quot; data-line=&quot;85&quot; dir=&quot;auto&quot; style=&quot;background: none; border-radius: 4px; display: inline-block; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 0px; position: relative; tab-size: 4;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;:
    rows = cursor.fetchmany(chunk_size)
    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;not&lt;/span&gt; rows:
        &lt;span class=&quot;hljs-keyword&quot;&gt;break&lt;/span&gt;
    writer.writerows(rows)
&lt;/code&gt;&lt;/pre&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;93&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;That small change is the difference between a toy exporter and something you can trust on real datasets.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;95&quot; dir=&quot;auto&quot; id=&quot;a-better-cli-experience&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;A Better CLI Experience&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;97&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;Command-line tools that do database work have one recurring UX problem: silence. If a query takes ten seconds, users start wondering whether the command is stuck, blocked, or dead.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;99&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;So the exporter prints:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;101&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;101&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;a short run summary before execution&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;102&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;detected column count after the query starts returning metadata&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;103&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;a live progress display while rows are written&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;104&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;a final completion summary with rows, columns, file size, and elapsed time&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;106&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;I used&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;rich&lt;/code&gt;&amp;nbsp;for the terminal output instead of hand-rolled&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;print()&lt;/code&gt;&amp;nbsp;calls. It gives the tool a more deliberate interface without turning it into a full TUI.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;108&quot; dir=&quot;auto&quot; id=&quot;handling-sql-as-a-file&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;Handling SQL as a File&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;110&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;One design decision I liked immediately was keeping SQL in a separate file. That brings a few benefits:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;112&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;112&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;the query is easier to inspect and review&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;113&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;analysts or DBAs can edit SQL without touching Python code&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;114&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;the same CLI can be reused for many exports&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;115&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;long or multi-step SQL scripts remain readable&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;117&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;For example, this simple test query lives in its own file:&lt;/p&gt;&lt;pre style=&quot;background-color: rgba(10, 10, 10, 0.4); border-color: rgb(48, 48, 49); border-image: none 100% / 1 / 0 stretch; border-radius: 3px; border-style: solid; border-width: 1px; font-size: 14px; margin-top: 0px; overflow: auto; padding: 16px; text-wrap-mode: wrap;&quot;&gt;&lt;code class=&quot;code-line language-sql&quot; data-line=&quot;119&quot; dir=&quot;auto&quot; style=&quot;background: none; border-radius: 4px; display: inline-block; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 0px; position: relative; tab-size: 4;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;SELECT&lt;/span&gt; TOP (&lt;span class=&quot;hljs-number&quot;&gt;1000&lt;/span&gt;)
    [Id],
    [Stock],
    [LowPrice],
    [MaxPrice],
    [AvgPrice]
&lt;span class=&quot;hljs-keyword&quot;&gt;FROM&lt;/span&gt; [QuantDevTest].[dbo].[StockMetrics];
&lt;/code&gt;&lt;/pre&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;129&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;That file can be passed directly to the CLI without changing the Python package at all.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;131&quot; dir=&quot;auto&quot; id=&quot;optional-sql-rewriting&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;Optional SQL Rewriting&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;133&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;Some teams keep parameter values in SQL scripts using a declared variable like:&lt;/p&gt;&lt;pre style=&quot;background-color: rgba(10, 10, 10, 0.4); border-color: rgb(48, 48, 49); border-image: none 100% / 1 / 0 stretch; border-radius: 3px; border-style: solid; border-width: 1px; font-size: 14px; margin-top: 0px; overflow: auto; padding: 16px; text-wrap-mode: wrap;&quot;&gt;&lt;code class=&quot;code-line language-sql&quot; data-line=&quot;135&quot; dir=&quot;auto&quot; style=&quot;background: none; border-radius: 4px; display: inline-block; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 0px; position: relative; tab-size: 4;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;DECLARE&lt;/span&gt; &lt;span class=&quot;hljs-variable&quot;&gt;@InAsOfDate&lt;/span&gt; &lt;span class=&quot;hljs-type&quot;&gt;DATE&lt;/span&gt; &lt;span class=&quot;hljs-operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&#39;2026-03-01&#39;&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;139&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;To support that style, the tool includes a lightweight SQL rewriting step. If the variable exists, the CLI can replace its value based on&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;--date&lt;/code&gt;. If the variable does not exist, the SQL runs unchanged.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;141&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;This keeps the interface flexible:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;143&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;143&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;SQL files can remain mostly static&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;144&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;operators can adjust date-driven exports without manually editing the file&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;146&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;It is intentionally narrow in scope. It is not trying to become a generic SQL templating engine.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;148&quot; dir=&quot;auto&quot; id=&quot;a-real-world-bug-caught-by-live-testing&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;A Real-World Bug Caught by Live Testing&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;150&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The first end-to-end run against my local SQL Server instance did not succeed.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;152&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The CLI connected successfully, but then failed with:&lt;/p&gt;&lt;pre style=&quot;background-color: rgba(10, 10, 10, 0.4); border-color: rgb(48, 48, 49); border-image: none 100% / 1 / 0 stretch; border-radius: 3px; border-style: solid; border-width: 1px; font-size: 14px; margin-top: 0px; overflow: auto; padding: 16px; text-wrap-mode: wrap;&quot;&gt;&lt;code class=&quot;code-line language-text&quot; data-line=&quot;154&quot; dir=&quot;auto&quot; style=&quot;background: none; border-radius: 4px; display: inline-block; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 0px; position: relative; tab-size: 4;&quot;&gt;AttributeError: &#39;pyodbc.Cursor&#39; object has no attribute &#39;timeout&#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;158&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;That was useful. The mocked tests passed, but the live run exposed a compatibility assumption in the code. Some&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;pyodbc&lt;/code&gt;&amp;nbsp;environments expose timeout behavior differently.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;160&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The fix was simple:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;162&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;162&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;try setting the timeout on the cursor if that attribute exists&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;163&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;otherwise fall back to the connection object&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;165&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;That is exactly why live testing matters. A tool can look clean in unit tests and still fail in a real environment for reasons that only surface with the actual driver and actual database connection.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;167&quot; dir=&quot;auto&quot; id=&quot;stress-testing-with-a-slower-query&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;Stress Testing with a Slower Query&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;169&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;A fast export is not a great demo for progress reporting, so I added a dedicated slow test query.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;171&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;It:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;173&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;173&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;copies base rows into a temp table&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;174&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;creates two multiplier temp tables&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;175&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;cross joins them to expand the result set&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;176&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;adds a short&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;WAITFOR DELAY&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;178&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;That makes it easy to simulate a longer-running export without needing a large production-sized table.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;180&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;Conceptually, the query looks like this:&lt;/p&gt;&lt;pre style=&quot;background-color: rgba(10, 10, 10, 0.4); border-color: rgb(48, 48, 49); border-image: none 100% / 1 / 0 stretch; border-radius: 3px; border-style: solid; border-width: 1px; font-size: 14px; margin-top: 0px; overflow: auto; padding: 16px; text-wrap-mode: wrap;&quot;&gt;&lt;code class=&quot;code-line language-sql&quot; data-line=&quot;182&quot; dir=&quot;auto&quot; style=&quot;background: none; border-radius: 4px; display: inline-block; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 0px; position: relative; tab-size: 4;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;SELECT&lt;/span&gt; TOP (&lt;span class=&quot;hljs-number&quot;&gt;1000&lt;/span&gt;) ... &lt;span class=&quot;hljs-keyword&quot;&gt;INTO&lt;/span&gt; #BaseStockMetrics ...
&lt;span class=&quot;hljs-keyword&quot;&gt;SELECT&lt;/span&gt; TOP (&lt;span class=&quot;hljs-number&quot;&gt;200&lt;/span&gt;) ... &lt;span class=&quot;hljs-keyword&quot;&gt;INTO&lt;/span&gt; #MultiplierA ...
&lt;span class=&quot;hljs-keyword&quot;&gt;SELECT&lt;/span&gt; TOP (&lt;span class=&quot;hljs-number&quot;&gt;200&lt;/span&gt;) ... &lt;span class=&quot;hljs-keyword&quot;&gt;INTO&lt;/span&gt; #MultiplierB ...

WAITFOR DELAY &lt;span class=&quot;hljs-string&quot;&gt;&#39;00:00:03&#39;&lt;/span&gt;;

&lt;span class=&quot;hljs-keyword&quot;&gt;SELECT&lt;/span&gt; ...
&lt;span class=&quot;hljs-keyword&quot;&gt;FROM&lt;/span&gt; #BaseStockMetrics &lt;span class=&quot;hljs-keyword&quot;&gt;AS&lt;/span&gt; b
&lt;span class=&quot;hljs-keyword&quot;&gt;CROSS&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;JOIN&lt;/span&gt; #MultiplierA &lt;span class=&quot;hljs-keyword&quot;&gt;AS&lt;/span&gt; a
&lt;span class=&quot;hljs-keyword&quot;&gt;CROSS&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;JOIN&lt;/span&gt; #MultiplierB &lt;span class=&quot;hljs-keyword&quot;&gt;AS&lt;/span&gt; c;
&lt;/code&gt;&lt;/pre&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;195&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;With a small base table, this is enough to create a large CSV and make the progress output visible. With a large base table, it can scale very aggressively, so it is the kind of test query you use carefully.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;197&quot; dir=&quot;auto&quot; id=&quot;testing-strategy&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;Testing Strategy&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;199&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The package includes pytest coverage around the parts that are most likely to break:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;201&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;201&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;config validation&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;202&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;date resolution&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;203&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;SQL file loading&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;204&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;SQL variable rewriting&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;205&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;connection string generation&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;206&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;CSV writing&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;207&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;mocked end-to-end export behavior&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;209&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;That balance is important. Not every code path needs a deep integration test, but the components that define correctness should have coverage.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;211&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The live SQL Server run complements those tests by exercising:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;213&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;213&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;the actual ODBC driver&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;214&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;the actual SQL Server instance&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;215&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;actual filesystem writes&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;216&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;real CLI behavior&lt;/li&gt;&lt;/ul&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;218&quot; dir=&quot;auto&quot; id=&quot;packaging-for-pypi&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;Packaging for PyPI&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;220&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The project is set up as a Poetry package and versioned as&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;1.0.1&lt;/code&gt;.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;222&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;Build artifacts are created with:&lt;/p&gt;&lt;pre style=&quot;background-color: rgba(10, 10, 10, 0.4); border-color: rgb(48, 48, 49); border-image: none 100% / 1 / 0 stretch; border-radius: 3px; border-style: solid; border-width: 1px; font-size: 14px; margin-top: 0px; overflow: auto; padding: 16px; text-wrap-mode: wrap;&quot;&gt;&lt;code class=&quot;code-line language-powershell&quot; data-line=&quot;224&quot; dir=&quot;auto&quot; style=&quot;background: none; border-radius: 4px; display: inline-block; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 0px; position: relative; tab-size: 4;&quot;&gt;poetry build
&lt;/code&gt;&lt;/pre&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;228&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;That produces:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;230&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;230&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;a source distribution&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;231&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;a wheel&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;233&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;Those are the exact artifacts needed for publishing to PyPI.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;235&quot; dir=&quot;auto&quot; id=&quot;example-usage&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;Example Usage&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;237&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;Trusted connection:&lt;/p&gt;&lt;pre style=&quot;background-color: rgba(10, 10, 10, 0.4); border-color: rgb(48, 48, 49); border-image: none 100% / 1 / 0 stretch; border-radius: 3px; border-style: solid; border-width: 1px; font-size: 14px; margin-top: 0px; overflow: auto; padding: 16px; text-wrap-mode: wrap;&quot;&gt;&lt;code class=&quot;code-line language-powershell&quot; data-line=&quot;239&quot; dir=&quot;auto&quot; style=&quot;background: none; border-radius: 4px; display: inline-block; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 0px; position: relative; tab-size: 4;&quot;&gt;poetry run sqlcsv&lt;span class=&quot;hljs-literal&quot;&gt;-exporter&lt;/span&gt; `
  &lt;span class=&quot;hljs-literal&quot;&gt;--sql&lt;/span&gt; .\queries\stock_metrics_top_1000.sql `
  &lt;span class=&quot;hljs-literal&quot;&gt;--output&lt;/span&gt; .\exports\stock_metrics_top_1000.csv `
  &lt;span class=&quot;hljs-literal&quot;&gt;--server&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;DESKTOP-TTUSQLJ\SQLEXPRESS&quot;&lt;/span&gt; `
  &lt;span class=&quot;hljs-literal&quot;&gt;--database&lt;/span&gt; QuantDevTest
&lt;/code&gt;&lt;/pre&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;247&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;SQL authentication:&lt;/p&gt;&lt;pre style=&quot;background-color: rgba(10, 10, 10, 0.4); border-color: rgb(48, 48, 49); border-image: none 100% / 1 / 0 stretch; border-radius: 3px; border-style: solid; border-width: 1px; font-size: 14px; margin-top: 0px; overflow: auto; padding: 16px; text-wrap-mode: wrap;&quot;&gt;&lt;code class=&quot;code-line language-powershell&quot; data-line=&quot;249&quot; dir=&quot;auto&quot; style=&quot;background: none; border-radius: 4px; display: inline-block; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 0px; position: relative; tab-size: 4;&quot;&gt;poetry run sqlcsv&lt;span class=&quot;hljs-literal&quot;&gt;-exporter&lt;/span&gt; `
  &lt;span class=&quot;hljs-literal&quot;&gt;--sql&lt;/span&gt; .\queries\stock_metrics_top_1000.sql `
  &lt;span class=&quot;hljs-literal&quot;&gt;--output&lt;/span&gt; .\exports\stock_metrics_top_1000.csv `
  &lt;span class=&quot;hljs-literal&quot;&gt;--server&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;my-server&quot;&lt;/span&gt; `
  &lt;span class=&quot;hljs-literal&quot;&gt;--database&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;Reporting&quot;&lt;/span&gt; `
  &lt;span class=&quot;hljs-literal&quot;&gt;--sql-auth&lt;/span&gt; `
  &lt;span class=&quot;hljs-literal&quot;&gt;--username&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;report_user&quot;&lt;/span&gt; `
  &lt;span class=&quot;hljs-literal&quot;&gt;--password&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;secret&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;260&quot; dir=&quot;auto&quot; id=&quot;final-thoughts&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;Final Thoughts&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;262&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;This is not a huge project, but it is a useful example of turning a script into a tool.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;264&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The interesting part was not the CSV writing itself. It was the engineering around it:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;266&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;266&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;separating concerns into modules&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;267&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;choosing streaming over eager loading&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;268&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;designing a usable CLI&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;269&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;writing tests around the core behavior&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;270&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;validating the tool against a real SQL Server environment&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;272&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;That is usually the difference between “something that works on my machine” and “something I would actually hand to another team.”&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;274&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;If I extend it further, the next areas I would look at are:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;276&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;276&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;environment-variable support for connection defaults&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;277&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;optional gzip output&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;278&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;row count estimates before execution when possible&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;279&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;better handling for very wide result sets&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;280&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;structured logging for scheduled runs&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;282&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;For now,&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;sqlcsv-exporter&lt;/code&gt;&amp;nbsp;does the job it was meant to do: run a SQL Server query from a file and stream the results into CSV in a way that is practical, testable, and ready to package.&lt;/p&gt;</description><link>https://thundaxsoftware.blogspot.com/2026/03/building-sqlcsv-exporter-python-cli-to.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlUQQvA389uHODF4CmfDMdiRASbUCsVe8lLxJIbpR5KZENbaZWk2geoH9uDaR9UMAPv18gPioTmBfBIfOh1ITv0rTYFzIlobRdOdBS87T2MT5p_GgdxwMeV5Qc6QMYnklHDvntq_LNwZN2r2kIFoNsCrkOtJ9fZwb9m-dmh0YsKzsho61dLGdKdTk9DSc/s72-c/ChatGPT%20Image%20Mar%2028,%202026,%2009_58_18%20PM.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-6634847053541739349</guid><pubDate>Sun, 22 Mar 2026 20:30:25 +0000</pubDate><atom:updated>2026-03-27T18:53:25.924+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">csv</category><category domain="http://www.blogger.com/atom/ns#">diff</category><category domain="http://www.blogger.com/atom/ns#">Python</category><title>Building csv-stream-diff: A Fast, Streaming CSV Comparison Tool for Very Large Files</title><description>&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiZghSoBVzhRlA-BOo-Az9CEfg3bybMp6CB9LbAXCfabBM8IFlfB8ZHm__cRmPlhubeqk_eLVf4cdJir9-nZCZOEJI6_NIsFuBOBS5bCSNo3CiiNZMmvxa4UJHVczhsCFRMnN6mPvM0ONpsUMn8IhECvZPGrUXKWBBU7kK2j2oAqNEter_j4jlHAsr65qM&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1536&quot; height=&quot;213&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiZghSoBVzhRlA-BOo-Az9CEfg3bybMp6CB9LbAXCfabBM8IFlfB8ZHm__cRmPlhubeqk_eLVf4cdJir9-nZCZOEJI6_NIsFuBOBS5bCSNo3CiiNZMmvxa4UJHVczhsCFRMnN6mPvM0ONpsUMn8IhECvZPGrUXKWBBU7kK2j2oAqNEter_j4jlHAsr65qM&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;span face=&quot;-apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif&quot; style=&quot;font-size: 14px;&quot;&gt;Comparing two CSV files sounds simple until the files are no longer small.&lt;/span&gt;&lt;p&gt;&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;4&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;Once the datasets move into the millions of rows, the usual approaches start to fall apart. Loading both files into memory is expensive. Spreadsheet tools stop being useful. Even many ad hoc scripts become slow, fragile, or impossible to run reliably in production-like environments.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;6&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;That is the problem I wanted to solve with&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;csv-stream-diff&lt;/code&gt;.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;8&quot; dir=&quot;auto&quot; id=&quot;the-problem&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;The Problem&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;10&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;In real systems, CSV comparison is rarely just &quot;diff these two files.&quot;&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;12&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;Usually the job looks more like this:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;14&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;14&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;The files are large enough that a full in-memory load is risky or impossible&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;15&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;The key columns on the left and right files do not use the same names&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;16&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;The comparison should only consider a selected subset of columns&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;17&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Duplicate keys may exist and need to be reported clearly&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;18&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Sometimes a full comparison is required, but sometimes a statistically useful sample is enough&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;19&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;The output needs to be machine-readable so it can feed downstream validation or remediation workflows&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;21&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;I wanted a tool that could handle that cleanly, with minimal dependencies, and still be easy to package and run anywhere.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;23&quot; dir=&quot;auto&quot; id=&quot;what-csv-stream-diff-does&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;What&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;csv-stream-diff&lt;/code&gt;&amp;nbsp;Does&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;25&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;&lt;a href=&quot;https://github.com/JordiCorbilla/csv-stream-diff&quot;&gt;csv-stream-diff&lt;/a&gt;&lt;/code&gt;&amp;nbsp;is a Python CLI tool for comparing very large CSV files using:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;27&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;27&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;streaming reads&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;28&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;hash-based partitioning&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;29&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;multiprocessing&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;30&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;YAML-driven configuration&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;32&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;It produces structured output files for:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;34&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;34&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;rows only on the left&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;35&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;rows only on the right&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;36&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;rows with cell-level differences&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;37&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;duplicate keys&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;38&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;summary metadata&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;40&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;It is designed to be practical rather than clever.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;42&quot; dir=&quot;auto&quot; id=&quot;the-core-design&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;The Core Design&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;44&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The main design constraint was memory.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;46&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;If a tool tries to build a single giant in-memory index for both files, it will eventually hit a limit. So instead of comparing the full files at once,&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;csv-stream-diff&lt;/code&gt;&amp;nbsp;uses a two-phase approach.&lt;/p&gt;&lt;h3 class=&quot;code-line&quot; data-line=&quot;48&quot; dir=&quot;auto&quot; id=&quot;1-partition-both-files-into-hashed-buckets&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 1.25em; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; position: relative;&quot;&gt;1. Partition both files into hashed buckets&lt;/h3&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;50&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;Each row key is normalized and hashed into a bucket. The left and right files are streamed row by row and written into matching bucket files on disk.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;52&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;That matters because rows with the same normalized key always land in the same bucket. Once that is true, each bucket can be compared independently.&lt;/p&gt;&lt;h3 class=&quot;code-line&quot; data-line=&quot;54&quot; dir=&quot;auto&quot; id=&quot;2-compare-buckets-in-parallel&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 1.25em; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; position: relative;&quot;&gt;2. Compare buckets in parallel&lt;/h3&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;56&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;After partitioning, the tool compares bucket pairs using multiple worker processes. Each worker only needs to index one bucket of the left file at a time, not the entire dataset.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;58&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;This keeps memory bounded while still taking advantage of all available CPU cores.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;60&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The result is a design that scales much better for heavy loads than a naïve single-process implementation.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;62&quot; dir=&quot;auto&quot; id=&quot;why-yaml-configuration&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;Why YAML Configuration&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;64&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;I did not want the CLI to become a wall of flags.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;66&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The comparison usually needs several pieces of information:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;68&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;68&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;the left and right file paths&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;69&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;the left and right key columns&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;70&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;the left and right comparison columns&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;71&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;CSV dialect options&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;72&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;output paths&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;73&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;sampling settings&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;74&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;performance settings&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;76&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;That is much easier to manage in a YAML file than on the command line.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;78&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The CLI still supports overrides, but the YAML file is the primary contract. That makes runs reproducible and easier to version alongside data validation jobs.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;80&quot; dir=&quot;auto&quot; id=&quot;exact-sampling-for-large-validation-runs&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;Exact Sampling for Large Validation Runs&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;82&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;Sometimes you do not want to compare every row.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;84&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;For example, if the source files contain tens of millions of records, you may want to run a fast validation pass against an exact random sample of keys before committing to a full comparison.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;86&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;csv-stream-diff&lt;/code&gt;&amp;nbsp;supports that:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;88&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;88&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;sampling.size: 0&lt;/code&gt;&amp;nbsp;means compare everything&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;89&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;sampling.size &amp;gt; 0&lt;/code&gt;&amp;nbsp;means compare an exact random sample of left-side unique keys&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;90&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;sampling.seed&lt;/code&gt;&amp;nbsp;makes the sample reproducible&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;92&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;This gives you a useful middle ground between tiny spot checks and full heavy-load comparisons.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;94&quot; dir=&quot;auto&quot; id=&quot;handling-duplicate-keys&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;Handling Duplicate Keys&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;96&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;Duplicate keys are one of the most annoying edge cases in file comparison work.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;98&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;If a key appears multiple times, the comparison becomes ambiguous. Instead of failing silently or hiding the problem, the tool reports duplicate keys explicitly and continues using the first occurrence for the main comparison.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;100&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;That behaviour is deliberate:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;102&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;102&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;you get a warning&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;103&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;you get a separate duplicate-key artifact&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;104&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;you still get a usable comparison result&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;106&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;This makes the tool better suited for messy real-world data.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;108&quot; dir=&quot;auto&quot; id=&quot;keeping-dependencies-small&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;Keeping Dependencies Small&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;110&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;I wanted the runtime dependency footprint to stay minimal.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;112&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The tool is built mostly with the Python standard library. The only runtime dependency is&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;PyYAML&lt;/code&gt;, which is used for configuration loading.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;114&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;That keeps installation simple and reduces operational friction when the tool needs to run in different environments.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;116&quot; dir=&quot;auto&quot; id=&quot;outputs-that-are-actually-useful&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;Outputs That Are Actually Useful&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;118&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;One important goal was to avoid producing a human-only report.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;120&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The tool writes separate output files for each class of result, which makes it easier to automate downstream processing:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;122&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;122&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;only_in_left.csv&lt;/code&gt;&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;123&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;only_in_right.csv&lt;/code&gt;&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;124&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;differences.csv&lt;/code&gt;&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;125&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;duplicate_keys.csv&lt;/code&gt;&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;126&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;summary.json&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;128&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;differences.csv&lt;/code&gt;&amp;nbsp;file is especially useful because it reports cell-level differences with both the left and right column names and values.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;130&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;That means you can do more than say &quot;this row changed.&quot; You can say exactly how it changed.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;132&quot; dir=&quot;auto&quot; id=&quot;testing-the-tool-properly&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;Testing the Tool Properly&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;134&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;I also wanted the project to be easy to validate.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;136&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;So the repository includes:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;138&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;138&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;unit tests with&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;pytest&lt;/code&gt;&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;139&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;BDD-style acceptance tests with&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;behave&lt;/code&gt;&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;140&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;a fixture generator that creates two baseline-identical CSV files and then introduces controlled differences&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;142&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The generator makes it easy to create realistic comparison scenarios involving:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;144&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;144&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;changed values&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;145&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;left-only rows&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;146&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;right-only rows&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;147&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;duplicate keys&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;149&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;That is useful both for development and for demonstrating the tool to others.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;151&quot; dir=&quot;auto&quot; id=&quot;a-few-practical-lessons&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;A Few Practical Lessons&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;153&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;Building this reinforced a few engineering lessons:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;155&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;155&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;For large-file tooling, streaming and partitioning beat clever in-memory shortcuts&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;156&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Exact sampling is worth implementing properly because it gives a fast validation mode without becoming a toy feature&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;157&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Duplicate handling should be explicit, not implicit&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;158&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Machine-readable outputs matter as much as console output&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;159&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Minimal dependencies make utility tools easier to adopt&lt;/li&gt;&lt;/ul&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;161&quot; dir=&quot;auto&quot; id=&quot;example-usage&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;Example Usage&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;163&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;With a config file in place, the tool is intentionally simple to run:&lt;/p&gt;&lt;pre style=&quot;background-color: rgba(10, 10, 10, 0.4); border-color: rgb(48, 48, 49); border-image: none 100% / 1 / 0 stretch; border-radius: 3px; border-style: solid; border-width: 1px; font-size: 14px; margin-top: 0px; overflow: auto; padding: 16px; text-wrap-mode: wrap;&quot;&gt;&lt;code class=&quot;code-line language-bash&quot; data-line=&quot;165&quot; dir=&quot;auto&quot; style=&quot;background: none; border-radius: 4px; display: inline-block; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 0px; position: relative; tab-size: 4;&quot;&gt;python -m csvstreamdiff.cli --config config.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;169&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;You can also override selected settings from the CLI:&lt;/p&gt;&lt;pre style=&quot;background-color: rgba(10, 10, 10, 0.4); border-color: rgb(48, 48, 49); border-image: none 100% / 1 / 0 stretch; border-radius: 3px; border-style: solid; border-width: 1px; font-size: 14px; margin-top: 0px; overflow: auto; padding: 16px; text-wrap-mode: wrap;&quot;&gt;&lt;code class=&quot;code-line language-bash&quot; data-line=&quot;171&quot; dir=&quot;auto&quot; style=&quot;background: none; border-radius: 4px; display: inline-block; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 0px; position: relative; tab-size: 4;&quot;&gt;python -m csvstreamdiff.cli --config config.yaml --sample-size 100000 --workers 8
&lt;/code&gt;&lt;/pre&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;175&quot; dir=&quot;auto&quot; id=&quot;why-i-built-it&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;Why I Built It&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;177&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;This project came from a practical need: compare large CSV datasets reliably, with clear outputs, and without depending on heavy frameworks or fragile one-off scripts.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;179&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The result is a tool that is meant to be packaged, published, and reused anywhere.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;181&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;That was the bar from the start.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;181&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEi-UWpU84tI9yoDSNNHULk29CaZ6E8EdS3qNp69LsLP0PNVQqk0Xd9tXOSENa5zJ8zbndRX98VyK_xh2PacjfBLRtva8Z0C-k3qguzi9cP8FeEmRrFM5RRaKQwc2eEMe4kDZGaDOPpJVWr7UR9VOlCFdDi8lKdJSkdsIq4sYJWQJa-5XGLyoKd03azh5t0&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;510&quot; data-original-width=&quot;1136&quot; height=&quot;288&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEi-UWpU84tI9yoDSNNHULk29CaZ6E8EdS3qNp69LsLP0PNVQqk0Xd9tXOSENa5zJ8zbndRX98VyK_xh2PacjfBLRtva8Z0C-k3qguzi9cP8FeEmRrFM5RRaKQwc2eEMe4kDZGaDOPpJVWr7UR9VOlCFdDi8lKdJSkdsIq4sYJWQJa-5XGLyoKd03azh5t0=w640-h288&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h2 class=&quot;code-line code-active-line&quot; data-line=&quot;183&quot; dir=&quot;auto&quot; id=&quot;closing&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;Closing&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;185&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;If you work with large exports, migration validation, reconciliation jobs, or data quality checks, CSV comparison becomes infrastructure very quickly.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;187&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;csv-stream-diff&lt;/code&gt;&amp;nbsp;is my attempt to make that infrastructure solid:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;189&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;189&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;reproducible&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;190&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;scalable&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;191&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;explicit&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;192&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;easy to automate&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;194&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;If you want to explore the project, the repository includes the CLI, example configuration, test generator, and packaging setup needed to build and publish it.&lt;/p&gt;</description><link>https://thundaxsoftware.blogspot.com/2026/03/building-csv-stream-diff-fast-streaming.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEiZghSoBVzhRlA-BOo-Az9CEfg3bybMp6CB9LbAXCfabBM8IFlfB8ZHm__cRmPlhubeqk_eLVf4cdJir9-nZCZOEJI6_NIsFuBOBS5bCSNo3CiiNZMmvxa4UJHVczhsCFRMnN6mPvM0ONpsUMn8IhECvZPGrUXKWBBU7kK2j2oAqNEter_j4jlHAsr65qM=s72-c" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-1971339674031673041</guid><pubDate>Sun, 15 Mar 2026 12:44:00 +0000</pubDate><atom:updated>2026-03-15T21:31:04.725+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">github</category><category domain="http://www.blogger.com/atom/ns#">Ollama</category><category domain="http://www.blogger.com/atom/ns#">openclaw</category><category domain="http://www.blogger.com/atom/ns#">telegram</category><title>Coding from Telegram: my OpenClaw + Ollama setup</title><description>&lt;p&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiASW05dU0Rk56B7PU3aTBSHNzeF_BDwT7a157Ke7U7oPwvjslYTkGV7FuYBTL7u9V-hMgvB5GTRERIvB9YZnDG0kTW0oBW7b-HrDtTJVcb1qIKHqWjYpeoVIRd5g_0i9S2xhprxKO-g5KN4xQX7Rkjv5DQGMLVA4KLcZ5L0X2mWwhviS-TkNLY9dIa1k/s1536/ChatGPT%20Image%20Mar%2015,%202026,%2012_08_56%20PM.png&quot; style=&quot;clear: left; display: inline; float: left; margin-bottom: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1536&quot; data-original-width=&quot;1024&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiASW05dU0Rk56B7PU3aTBSHNzeF_BDwT7a157Ke7U7oPwvjslYTkGV7FuYBTL7u9V-hMgvB5GTRERIvB9YZnDG0kTW0oBW7b-HrDtTJVcb1qIKHqWjYpeoVIRd5g_0i9S2xhprxKO-g5KN4xQX7Rkjv5DQGMLVA4KLcZ5L0X2mWwhviS-TkNLY9dIa1k/s320/ChatGPT%20Image%20Mar%2015,%202026,%2012_08_56%20PM.png&quot; width=&quot;213&quot; /&gt;&lt;/a&gt;&lt;/p&gt;&lt;div style=&quot;text-align: left;&quot;&gt;I finally got a setup working that feels a bit like the future.&lt;/div&gt;&lt;p data-end=&quot;471&quot; data-start=&quot;446&quot;&gt;I launched OpenClaw with:&lt;/p&gt;&lt;pre class=&quot;overflow-visible! px-0!&quot; data-end=&quot;531&quot; data-start=&quot;473&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-bash&quot;&gt;&lt;i&gt;ollama launch openclaw --model kimi-k2.5:cloud&lt;/i&gt;
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;p data-end=&quot;636&quot; data-start=&quot;533&quot;&gt;and connected it to &lt;b&gt;Telegram&lt;/b&gt;, which means I can now talk to my coding assistant directly from my phone.&lt;/p&gt;&lt;p data-end=&quot;749&quot; data-start=&quot;638&quot;&gt;What makes this especially satisfying is that the whole thing is running on a very inexpensive mini PC at home:&lt;/p&gt;&lt;p data-end=&quot;895&quot; data-start=&quot;751&quot;&gt;&lt;strong data-end=&quot;895&quot; data-start=&quot;751&quot;&gt;Mini PC, 12GB RAM + 128GB ROM, Intel Celeron J4125 (up to 2.7 GHz), Windows 10 Pro, dual Wi-Fi 2.4/5G, Bluetooth 4.2, 4K HD, 2 HDMI + 1 VGA.&lt;/strong&gt;&lt;/p&gt;&lt;p data-end=&quot;1039&quot; data-start=&quot;897&quot;&gt;Nothing exotic. Nothing expensive. Just a small, affordable box quietly sitting there, acting as the bridge between my workspace and my phone.&lt;/p&gt;&lt;p data-end=&quot;1211&quot; data-start=&quot;1041&quot;&gt;That is part of the appeal. This setup feels surprisingly capable for something that costs very little and, beyond the hardware I already own, is effectively free to run.&lt;/p&gt;&lt;p data-end=&quot;1589&quot; data-start=&quot;1213&quot;&gt;The interesting part is not just that it chats back. It has access to my workspace, can help create projects, work with files, and act more like a real development companion than a simple question-answer bot. That changes the experience completely. It stops being something I occasionally open in a browser and starts feeling more like an always-available engineering copilot.&lt;/p&gt;&lt;p data-end=&quot;1656&quot; data-start=&quot;1591&quot;&gt;What I like most is the shift in how and where coding can happen.&lt;/p&gt;&lt;p data-end=&quot;1982&quot; data-start=&quot;1658&quot;&gt;I can be away from my desk, out doing groceries, commuting, or just walking around, and still make progress. I can send a quick message to scaffold an idea, create a project structure, inspect something in the workspace, or prepare the next task before I am back in front of the machine. It turns dead time into useful time.&lt;/p&gt;&lt;p data-end=&quot;2055&quot; data-start=&quot;1984&quot;&gt;That is the part that feels powerful: development becomes more ambient.&lt;/p&gt;&lt;p data-end=&quot;2385&quot; data-start=&quot;2057&quot;&gt;Instead of waiting for the perfect focused block at a desk, I can stay connected to the flow of a project throughout the day. A thought appears, I message it. A small task comes to mind, I delegate it. A project idea starts forming, I capture it immediately. By the time I sit down again, some of the groundwork is already done.&lt;/p&gt;&lt;p data-end=&quot;2728&quot; data-start=&quot;2387&quot;&gt;There is also something elegant about the stack itself. Ollama provides a clean way to launch the model workflow, OpenClaw gives the agent a useful operating surface, and Telegram becomes the lightweight interface that makes it all feel natural. No heavy setup on the phone, no friction, no need to open a laptop just to move a task forward.&lt;/p&gt;&lt;p data-end=&quot;3042&quot; data-start=&quot;2730&quot;&gt;The other thing I genuinely like is how accessible it is. You do not need a powerful workstation, a costly hosted service, or a complex mobile setup. A cheap mini PC, a Telegram account, and the right tooling are enough to build something that feels surprisingly close to a personal remote engineering assistant.&lt;/p&gt;&lt;p data-end=&quot;3255&quot; data-start=&quot;3044&quot;&gt;It is still early, of course, and this kind of workflow needs trust, guardrails, and careful handling when it comes to file access and automation. But even in its current form, it already feels genuinely useful.&lt;/p&gt;&lt;p data-end=&quot;3402&quot; data-start=&quot;3257&quot;&gt;A coding assistant in Telegram sounds like a gimmick until it starts helping you build real things while you are standing in a supermarket queue.&lt;/p&gt;&lt;p data-end=&quot;3492&quot; data-start=&quot;3404&quot;&gt;That is when it stops sounding like a demo and starts feeling like a new way of working.&lt;/p&gt;&lt;p&gt;

















&lt;br /&gt;&lt;/p&gt;</description><link>https://thundaxsoftware.blogspot.com/2026/03/coding-from-telegram-my-openclaw-ollama.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiASW05dU0Rk56B7PU3aTBSHNzeF_BDwT7a157Ke7U7oPwvjslYTkGV7FuYBTL7u9V-hMgvB5GTRERIvB9YZnDG0kTW0oBW7b-HrDtTJVcb1qIKHqWjYpeoVIRd5g_0i9S2xhprxKO-g5KN4xQX7Rkjv5DQGMLVA4KLcZ5L0X2mWwhviS-TkNLY9dIa1k/s72-c/ChatGPT%20Image%20Mar%2015,%202026,%2012_08_56%20PM.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-3790779292704483243</guid><pubDate>Sun, 08 Mar 2026 15:39:00 +0000</pubDate><atom:updated>2026-03-09T19:37:46.205+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">arcade</category><category domain="http://www.blogger.com/atom/ns#">pygame</category><category domain="http://www.blogger.com/atom/ns#">Python</category><title>Tina the Destroyer: Turning Family Photos Into a Playable Arcade Game</title><description>&lt;p&gt;&lt;span face=&quot;-apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif&quot; style=&quot;font-size: 14px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiWxpR8M85-Z7rR_RINd8sg8PG-BcvWSRqCeey3NJQ4haZhbjBKrQgezS7wJwXdi1sdsxXkSpOGc0nWBVcUjMXCZYKgUFvrvv08TlfLVLBk2QbFAVGcJDVjeREXoY_5y5DfsxHkNMmyMrBEYHNDHh-3l2U39bMpuvP60474TeAHq-yf2-NNSnh2TDdombc&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1536&quot; height=&quot;213&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiWxpR8M85-Z7rR_RINd8sg8PG-BcvWSRqCeey3NJQ4haZhbjBKrQgezS7wJwXdi1sdsxXkSpOGc0nWBVcUjMXCZYKgUFvrvv08TlfLVLBk2QbFAVGcJDVjeREXoY_5y5DfsxHkNMmyMrBEYHNDHh-3l2U39bMpuvP60474TeAHq-yf2-NNSnh2TDdombc&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;I built a small local arcade game called&lt;span face=&quot;-apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif&quot; style=&quot;font-size: 14px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;strong style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px;&quot;&gt;Tina the Destroyer&lt;/strong&gt;&lt;span face=&quot;-apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif&quot; style=&quot;font-size: 14px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span face=&quot;-apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif&quot; style=&quot;font-size: 14px;&quot;&gt;with Python and Pygame.&lt;/span&gt;&lt;p&gt;&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;4&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The idea was simple and personal: I took pictures of my daughter, converted them into game-ready pixel sprites, and built a one-screen game around that character. The result is a fast arcade loop where Tina smashes falling rats before they hit the ground.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;6&quot; dir=&quot;auto&quot; id=&quot;the-core-idea&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;&lt;br /&gt;&lt;/h2&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;6&quot; dir=&quot;auto&quot; id=&quot;the-core-idea&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;The Core Idea&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;8&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;I wanted a game that was:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;10&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;10&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Fast to play&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;11&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Easy to understand&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;12&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Personal and fun&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;14&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;So I designed a tight gameplay loop:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;16&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;16&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Move left and right&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;17&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Press&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;Space&lt;/code&gt;&amp;nbsp;to perform a ground smash&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;18&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Squash rats for points&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;19&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Lose a life when a rat reaches the bottom&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;20&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Game over after 3 missed rats&lt;/li&gt;&lt;/ul&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;22&quot; dir=&quot;auto&quot; id=&quot;from-photos-to-sprites&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;From Photos to Sprites&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;24&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The visual pipeline started with real photos. I cleaned and adapted the character images to work as sprite sheets and exported key poses:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;26&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;26&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;player_idle.png&lt;/code&gt;&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;27&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;player_smash.png&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;29&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;I also prepared enemy and effect assets:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;31&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;31&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;rat.png&lt;/code&gt;&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;32&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;rat_squashed.png&lt;/code&gt;&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;33&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;impact.png&lt;/code&gt;&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;34&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;title.png&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;36&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;For audio, I added smash, miss, and looped music effects, with fallback loading so the game still runs if a file is missing.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;38&quot; dir=&quot;auto&quot; id=&quot;building-the-game&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;Building the Game&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;40&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The game is organized into small modules:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;42&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;42&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;main.py&lt;/code&gt;&amp;nbsp;entrypoint&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;43&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;game.py&lt;/code&gt;&amp;nbsp;state machine and game loop&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;44&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;entities.py&lt;/code&gt;&amp;nbsp;player, rats, and impact effects&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;45&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;settings.py&lt;/code&gt;&amp;nbsp;tuning values&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;46&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;utils.py&lt;/code&gt;&amp;nbsp;asset loading and fallbacks&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;48&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;Key features I focused on:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;50&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;50&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Clean state flow (&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;TITLE&lt;/code&gt;,&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;PLAYING&lt;/code&gt;,&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;GAME_OVER&lt;/code&gt;,&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;QUITTING&lt;/code&gt;)&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;51&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Responsive movement and smash timing&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;52&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Difficulty scaling over time&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;53&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Score/lives HUD&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;54&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Best-score persistence to JSON&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;55&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Graceful fallback behavior for missing assets&lt;/li&gt;&lt;/ul&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;57&quot; dir=&quot;auto&quot; id=&quot;vibe-coding-the-project&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;Vibe Coding the Project&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;59&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;I vibe coded this with&amp;nbsp;&lt;strong&gt;gpt-5-3.codex&lt;/strong&gt;, iterating quickly on gameplay feel, tuning values, and production details (asset fallbacks, restart flow, audio handling, and UI polish).&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;59&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;Source code:&amp;nbsp;&lt;a href=&quot;https://github.com/JordiCorbilla/tina-the-destroyer&quot;&gt;JordiCorbilla/tina-the-destroyer: tina-the-destroyer&lt;/a&gt;&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;61&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;That pairing made it easy to keep momentum: prototype fast, test, adjust, repeat.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;63&quot; dir=&quot;auto&quot; id=&quot;why-i-loved-this-build&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;Why I Loved This Build&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;65&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;This project sits at the intersection of family creativity and engineering. It started as a fun idea with photos and ended up as a fully playable local arcade game I can run anytime with:&lt;/p&gt;&lt;pre style=&quot;background-color: rgba(10, 10, 10, 0.4); border-color: rgb(48, 48, 49); border-image: none 100% / 1 / 0 stretch; border-radius: 3px; border-style: solid; border-width: 1px; font-size: 14px; margin-top: 0px; overflow: auto; padding: 16px; text-wrap-mode: wrap;&quot;&gt;&lt;code class=&quot;code-line language-bash&quot; data-line=&quot;67&quot; dir=&quot;auto&quot; style=&quot;background: none; border-radius: 4px; display: inline-block; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 0px; position: relative; tab-size: 4;&quot;&gt;pip install pygame
python main.py
&lt;/code&gt;&lt;/pre&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;72&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;Small scope, real personality, and a complete game loop. Exactly the kind of build I enjoy.&lt;/p&gt;</description><link>https://thundaxsoftware.blogspot.com/2026/03/tina-destroyer-turning-family-photos.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEiWxpR8M85-Z7rR_RINd8sg8PG-BcvWSRqCeey3NJQ4haZhbjBKrQgezS7wJwXdi1sdsxXkSpOGc0nWBVcUjMXCZYKgUFvrvv08TlfLVLBk2QbFAVGcJDVjeREXoY_5y5DfsxHkNMmyMrBEYHNDHh-3l2U39bMpuvP60474TeAHq-yf2-NNSnh2TDdombc=s72-c" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>London, UK</georss:featurename><georss:point>51.5072178 -0.1275862</georss:point><georss:box>23.196983963821154 -35.2838362 79.817451636178845 35.0286638</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-6132393536646830344</guid><pubDate>Sun, 15 Feb 2026 21:09:00 +0000</pubDate><atom:updated>2026-03-15T21:35:19.084+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">.net 9</category><category domain="http://www.blogger.com/atom/ns#">CLI</category><category domain="http://www.blogger.com/atom/ns#">spectre.console</category><category domain="http://www.blogger.com/atom/ns#">vibe coding</category><title>Building a Portfolio CLI in 2026 (and Why CLIs Feel Cool Again)</title><description>&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px;&quot;&gt;Over a single weekend, I decided to build a portfolio tracker that lives entirely in the terminal. Not a web app. Not a spreadsheet. A real, interactive CLI with panels, charts, and daily tracking.&lt;/span&gt;&lt;/div&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;2&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgasgqBfoOzXHRnLxyRi7lG_Mo-SBXednHuueBekIGEqwKjbAHk5eGauvXYbwv6QU47HatMtlit6cz-2qACJUL4bKPoiT2Ilih2su0WeLWUux8yCTb_xKAALbPwAQt0ehRJ9KnPDWJ-m-uw8Lkh7UoLa8diDQp2rzJdazRzwRU_xe1STcJuRleY6sXGm8w&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;968&quot; data-original-width=&quot;1896&quot; height=&quot;326&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgasgqBfoOzXHRnLxyRi7lG_Mo-SBXednHuueBekIGEqwKjbAHk5eGauvXYbwv6QU47HatMtlit6cz-2qACJUL4bKPoiT2Ilih2su0WeLWUux8yCTb_xKAALbPwAQt0ehRJ9KnPDWJ-m-uw8Lkh7UoLa8diDQp2rzJdazRzwRU_xe1STcJuRleY6sXGm8w=w640-h326&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;I also wanted it to fit how I actually work: Excel as the source of truth, but with a console experience that makes daily updates fast and visual.&lt;p&gt;&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;6&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;This was vibe-coded with Chat GPT-5-2-Codex over a weekend, which made it surprisingly fun to iterate on UI tweaks, edge cases, and the overall feel.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;8&quot; dir=&quot;auto&quot; id=&quot;why-a-cli&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;Why a CLI?&lt;/h2&gt;&lt;p class=&quot;code-line code-active-line&quot; data-line=&quot;9&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;CLIs are back. Not because they&#39;re nostalgic, but because they&#39;re fast, distraction-free, and feel like tools instead of products. A good CLI lets you focus on the data and decisions rather than the UI chrome.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;11&quot; dir=&quot;auto&quot; id=&quot;what-i-built&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;What I Built&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;12&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The result is&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;&lt;a href=&quot;https://github.com/JordiCorbilla/my-portfolio-cli&quot;&gt;my-portfolio-cli&lt;/a&gt;&lt;/code&gt;, an interactive portfolio dashboard that:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;13&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;13&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Reads data from an Excel workbook.&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;14&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Renders a rich TUI with Spectre.Console.&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;15&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Shows daily PnL, MTD performance, and FY summaries.&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;16&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Displays a simple PnL bar chart right in the terminal.&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;17&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Lets you add a new daily snapshot instantly.&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;18&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Creates new monthly sheets when you need them.&lt;/li&gt;&lt;/ul&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;20&quot; dir=&quot;auto&quot; id=&quot;the-flow&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;The Flow&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;21&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;Start it and it opens directly in interactive mode. Use arrow keys to move across days and months. Press&amp;nbsp;&lt;code style=&quot;background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 1em; line-height: 1.357em; padding: 1px 3px;&quot;&gt;A&lt;/code&gt;&amp;nbsp;to add a new entry. If the month doesn&#39;t exist yet, it creates it automatically.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;23&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;If you&#39;re starting from scratch (no workbook or no month sheets), the CLI prompts you to create the first month, add accounts, and enter initial values.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;25&quot; dir=&quot;auto&quot; id=&quot;design-notes&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;Design Notes&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;26&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;The key was making it feel like a real console app:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;27&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;27&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Clean layout with strong visual hierarchy.&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;28&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Color-coded gains and losses.&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;29&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;A compact FY summary that&#39;s readable at a glance.&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;30&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;A minimal bar chart for quick trend cues.&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;32&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;It isn&#39;t about being fancy. It&#39;s about being useful and enjoyable to use every day.&lt;/p&gt;&lt;h2 class=&quot;code-line&quot; data-line=&quot;34&quot; dir=&quot;auto&quot; id=&quot;whats-next&quot; style=&quot;border-bottom: 1px solid rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-top-color: rgba(255, 255, 255, 0.18); font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; line-height: 1.25; margin-bottom: 16px; margin-top: 24px; padding-bottom: 0.3em; position: relative;&quot;&gt;What&#39;s Next&lt;/h2&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;35&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;This project is going online soon, which means:&lt;/p&gt;&lt;ul class=&quot;code-line&quot; data-line=&quot;36&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 0.7em; margin-top: 0px; position: relative;&quot;&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;36&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;A demo workbook and screenshots.&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;37&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;A short walkthrough.&lt;/li&gt;&lt;li class=&quot;code-line&quot; data-line=&quot;38&quot; dir=&quot;auto&quot; style=&quot;position: relative;&quot;&gt;Possibly a minimal installer or single-file publish.&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;40&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;For now, it&#39;s a tight little tool that does exactly what I need.&lt;/p&gt;&lt;p class=&quot;code-line&quot; data-line=&quot;42&quot; dir=&quot;auto&quot; style=&quot;font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe WPC&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Ubuntu, &amp;quot;Droid Sans&amp;quot;, sans-serif; font-size: 14px; margin-bottom: 16px; margin-top: 0px; position: relative;&quot;&gt;If you want to build your own CLI tool, I highly recommend it. The feedback loop is short, and it&#39;s easy to keep refining the experience until it feels right.&lt;/p&gt;</description><link>https://thundaxsoftware.blogspot.com/2026/02/building-portfolio-cli-in-2026-and-why.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEgasgqBfoOzXHRnLxyRi7lG_Mo-SBXednHuueBekIGEqwKjbAHk5eGauvXYbwv6QU47HatMtlit6cz-2qACJUL4bKPoiT2Ilih2su0WeLWUux8yCTb_xKAALbPwAQt0ehRJ9KnPDWJ-m-uw8Lkh7UoLa8diDQp2rzJdazRzwRU_xe1STcJuRleY6sXGm8w=s72-w640-h326-c" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-7888794576508862265</guid><pubDate>Sun, 25 Jan 2026 17:37:00 +0000</pubDate><atom:updated>2026-01-25T17:37:53.845+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">AI Coding Agents</category><category domain="http://www.blogger.com/atom/ns#">Claude Code</category><category domain="http://www.blogger.com/atom/ns#">Developer Tooling</category><category domain="http://www.blogger.com/atom/ns#">Local LLM</category><category domain="http://www.blogger.com/atom/ns#">Offline AI</category><category domain="http://www.blogger.com/atom/ns#">Ollama</category><category domain="http://www.blogger.com/atom/ns#">Private AI</category><category domain="http://www.blogger.com/atom/ns#">VS Code</category><title>Running Claude Code Fully Locally with Ollama (Yes, It’s Possible — With Caveats)</title><description>&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9WeixbvB2oF-WOzD69_uFgmOsJ0xiUFPaV5rjs39NGGc1-0axBdzGYBQBpMNL3Nh4SXifIJOZCaYeja16D9YLWvRXk2xRfrtx0FNWIIZCx9dMgZatSXY7BiFPUz0z9gZpI-YhgNfgLbjqisB6y6ZUW1qnNjyywe5KZTvYWjzFPFKEE-xK5td5rAaYh-Q/s1536/ChatGPT%20Image%20Jan%2025,%202026,%2005_20_49%20PM.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1536&quot; height=&quot;213&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9WeixbvB2oF-WOzD69_uFgmOsJ0xiUFPaV5rjs39NGGc1-0axBdzGYBQBpMNL3Nh4SXifIJOZCaYeja16D9YLWvRXk2xRfrtx0FNWIIZCx9dMgZatSXY7BiFPUz0z9gZpI-YhgNfgLbjqisB6y6ZUW1qnNjyywe5KZTvYWjzFPFKEE-xK5td5rAaYh-Q/s320/ChatGPT%20Image%20Jan%2025,%202026,%2005_20_49%20PM.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;Claude Code is designed as a &lt;strong data-end=&quot;448&quot; data-start=&quot;406&quot;&gt;cloud-first, tool-centric coding agent&lt;/strong&gt; tightly coupled to Anthropic’s infrastructure. Out of the box, it &lt;strong data-end=&quot;548&quot; data-start=&quot;515&quot;&gt;does not support local models&lt;/strong&gt;.&lt;p&gt;&lt;/p&gt;
&lt;p data-end=&quot;582&quot; data-start=&quot;551&quot;&gt;However, with a combination of:&lt;/p&gt;
&lt;ul data-end=&quot;672&quot; data-start=&quot;583&quot;&gt;
&lt;li data-end=&quot;606&quot; data-start=&quot;583&quot;&gt;
&lt;p data-end=&quot;606&quot; data-start=&quot;585&quot;&gt;endpoint redirection,&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;638&quot; data-start=&quot;607&quot;&gt;
&lt;p data-end=&quot;638&quot; data-start=&quot;609&quot;&gt;model aliasing inside Ollama,&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;672&quot; data-start=&quot;639&quot;&gt;
&lt;p data-end=&quot;672&quot; data-start=&quot;641&quot;&gt;and a tool-capable local model,&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;791&quot; data-start=&quot;674&quot;&gt;you &lt;em data-end=&quot;683&quot; data-start=&quot;678&quot;&gt;can&lt;/em&gt; run Claude Code &lt;strong data-end=&quot;720&quot; data-start=&quot;700&quot;&gt;entirely locally&lt;/strong&gt; against Ollama — including &lt;strong data-end=&quot;790&quot; data-start=&quot;748&quot;&gt;file edits, refactors, and shell tools&lt;/strong&gt;.&lt;/p&gt;
&lt;p data-end=&quot;918&quot; data-start=&quot;793&quot;&gt;This article documents the &lt;strong data-end=&quot;845&quot; data-start=&quot;820&quot;&gt;exact path that works&lt;/strong&gt;, the &lt;strong data-end=&quot;881&quot; data-start=&quot;851&quot;&gt;failure modes you will hit&lt;/strong&gt;, and the &lt;strong data-end=&quot;917&quot; data-start=&quot;891&quot;&gt;engineering trade-offs&lt;/strong&gt;.&lt;/p&gt;&lt;h2 data-end=&quot;966&quot; data-start=&quot;925&quot;&gt;Why Claude Code Is Hard to Run Locally&lt;/h2&gt;&lt;p data-end=&quot;1011&quot; data-start=&quot;968&quot;&gt;Claude Code makes three strong assumptions:&lt;/p&gt;&lt;ol data-end=&quot;1146&quot; data-start=&quot;1013&quot;&gt;
&lt;li data-end=&quot;1064&quot; data-start=&quot;1013&quot;&gt;
&lt;p data-end=&quot;1064&quot; data-start=&quot;1016&quot;&gt;&lt;strong data-end=&quot;1064&quot; data-start=&quot;1016&quot;&gt;Anthropic authentication is always available&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1113&quot; data-start=&quot;1065&quot;&gt;
&lt;p data-end=&quot;1113&quot; data-start=&quot;1068&quot;&gt;&lt;strong data-end=&quot;1093&quot; data-start=&quot;1068&quot;&gt;Model names are fixed&lt;/strong&gt; (&lt;code data-end=&quot;1112&quot; data-start=&quot;1095&quot;&gt;claude-sonnet-*&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1146&quot; data-start=&quot;1114&quot;&gt;
&lt;p data-end=&quot;1146&quot; data-start=&quot;1117&quot;&gt;&lt;strong data-end=&quot;1146&quot; data-start=&quot;1117&quot;&gt;Tool calling is mandatory&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;p data-end=&quot;1221&quot; data-start=&quot;1148&quot;&gt;Most “local Claude” guides fail because they only solve &lt;strong data-end=&quot;1211&quot; data-start=&quot;1204&quot;&gt;one&lt;/strong&gt; of these.&lt;/p&gt;&lt;p data-end=&quot;1264&quot; data-start=&quot;1223&quot;&gt;To succeed, you must solve &lt;strong data-end=&quot;1263&quot; data-start=&quot;1250&quot;&gt;all three&lt;/strong&gt;.&lt;/p&gt;&lt;hr data-end=&quot;1269&quot; data-start=&quot;1266&quot; /&gt;&lt;h2 data-end=&quot;1306&quot; data-start=&quot;1271&quot;&gt;Architecture That Actually Works&lt;/h2&gt;&lt;pre class=&quot;overflow-visible! px-0!&quot; data-end=&quot;1539&quot; data-start=&quot;1308&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-[calc(--spacing(9)+var(--header-height))] @w-xl/main:top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre!&quot;&gt;VS &lt;span class=&quot;hljs-selector-tag&quot;&gt;Code&lt;/span&gt;
 └─ Claude &lt;span class=&quot;hljs-selector-tag&quot;&gt;Code&lt;/span&gt; extension
     ├─ hardcoded model name (claude-sonnet-*)
     ├─ mandatory tools
     └─ Anthropic-style requests
           ↓
      Ollama (localhost)
           ↓
   Tool-capable local model (Qwen)
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;p data-end=&quot;1555&quot; data-start=&quot;1541&quot;&gt;Key insight:&lt;/p&gt;&lt;p data-end=&quot;918&quot; data-start=&quot;793&quot;&gt;








&lt;/p&gt;&lt;blockquote data-end=&quot;1633&quot; data-start=&quot;1556&quot;&gt;
&lt;p data-end=&quot;1633&quot; data-start=&quot;1558&quot;&gt;You don’t change Claude Code — you &lt;strong data-end=&quot;1632&quot; data-start=&quot;1593&quot;&gt;adapt Ollama to look like Anthropic&lt;/strong&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;h2 data-end=&quot;1681&quot; data-start=&quot;1640&quot;&gt;Step 1: Redirect Claude Code to Ollama&lt;/h2&gt;&lt;p data-end=&quot;1710&quot; data-start=&quot;1683&quot;&gt;In VS Code &lt;code data-end=&quot;1709&quot; data-start=&quot;1694&quot;&gt;Command palette -&amp;gt; Preferences: Open User Settings (JSON)&lt;/code&gt;:&lt;/p&gt;&lt;pre class=&quot;overflow-visible! px-0!&quot; data-end=&quot;1974&quot; data-start=&quot;1712&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-[calc(--spacing(9)+var(--header-height))] @w-xl/main:top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;&quot;claudeCode.environmentVariables&quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;hljs-attr&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;ANTHROPIC_BASE_URL&quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;hljs-attr&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;http://127.0.0.1:11434&quot;&lt;/span&gt;
    &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;hljs-attr&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;ANTHROPIC_AUTH_TOKEN&quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;hljs-attr&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;ollama&quot;&lt;/span&gt;
    &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;&quot;claudeCode.disableLoginPrompt&quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;p data-end=&quot;2038&quot; data-start=&quot;1976&quot;&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgJBceCONKj-YQIyfSWW2Soc2jhAUO3v7HNCC868hhWA6Wc3baPSU9y8WNMtK456kkMIp8gUXzJ5ME87WBujBtdUt2mMqVcixMr8GWdteFPR2cEjSTvgruZxRl2rdsIoGxuI7Vhqf_NYezIidHLdslbGtlmVW_Re2U9Bh7y3V3GErCQaf9CchF8jOMbSsE&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;326&quot; data-original-width=&quot;666&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgJBceCONKj-YQIyfSWW2Soc2jhAUO3v7HNCC868hhWA6Wc3baPSU9y8WNMtK456kkMIp8gUXzJ5ME87WBujBtdUt2mMqVcixMr8GWdteFPR2cEjSTvgruZxRl2rdsIoGxuI7Vhqf_NYezIidHLdslbGtlmVW_Re2U9Bh7y3V3GErCQaf9CchF8jOMbSsE=s16000&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;p data-end=&quot;2038&quot; data-start=&quot;1976&quot;&gt;This bypasses cloud calls and sends &lt;strong data-end=&quot;2037&quot; data-start=&quot;2012&quot;&gt;all traffic to Ollama&lt;/strong&gt;.&lt;/p&gt;&lt;p data-end=&quot;2054&quot; data-start=&quot;2040&quot;&gt;At this point:&lt;/p&gt;&lt;ul data-end=&quot;2124&quot; data-start=&quot;2055&quot;&gt;
&lt;li data-end=&quot;2065&quot; data-start=&quot;2055&quot;&gt;
&lt;p data-end=&quot;2065&quot; data-start=&quot;2057&quot;&gt;UI works&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;2081&quot; data-start=&quot;2066&quot;&gt;
&lt;p data-end=&quot;2081&quot; data-start=&quot;2068&quot;&gt;Requests flow&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;2124&quot; data-start=&quot;2082&quot;&gt;
&lt;p data-end=&quot;2124&quot; data-start=&quot;2084&quot;&gt;But you will get &lt;strong data-end=&quot;2124&quot; data-start=&quot;2101&quot;&gt;404 model not found&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;p data-end=&quot;918&quot; data-start=&quot;793&quot;&gt;





&lt;/p&gt;&lt;p data-end=&quot;2143&quot; data-start=&quot;2126&quot;&gt;That is expected.&lt;/p&gt;&lt;h2 data-end=&quot;2194&quot; data-start=&quot;2150&quot;&gt;Step 2: Understand the Model Name Problem&lt;/h2&gt;&lt;p data-end=&quot;2242&quot; data-start=&quot;2196&quot;&gt;Claude Code &lt;strong data-end=&quot;2218&quot; data-start=&quot;2208&quot;&gt;always&lt;/strong&gt; sends model names like:&lt;/p&gt;&lt;pre class=&quot;overflow-visible! px-0!&quot; data-end=&quot;2278&quot; data-start=&quot;2244&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-[calc(--spacing(9)+var(--header-height))] @w-xl/main:top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre!&quot;&gt;claude-sonnet-4-5-20250929
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;p data-end=&quot;2317&quot; data-start=&quot;2280&quot;&gt;Ollama does &lt;strong data-end=&quot;2299&quot; data-start=&quot;2292&quot;&gt;not&lt;/strong&gt; have such models.&lt;/p&gt;&lt;p data-end=&quot;2396&quot; data-start=&quot;2319&quot;&gt;You cannot configure Claude Code to use &lt;code data-end=&quot;2366&quot; data-start=&quot;2359&quot;&gt;gemma&lt;/code&gt;, &lt;code data-end=&quot;2375&quot; data-start=&quot;2368&quot;&gt;llama&lt;/code&gt;, or &lt;code data-end=&quot;2386&quot; data-start=&quot;2380&quot;&gt;qwen&lt;/code&gt; directly.&lt;/p&gt;&lt;h3 data-end=&quot;2426&quot; data-start=&quot;2398&quot;&gt;Solution: Model aliasing&lt;/h3&gt;&lt;p data-end=&quot;2510&quot; data-start=&quot;2428&quot;&gt;Ollama lets you create &lt;strong data-end=&quot;2474&quot; data-start=&quot;2451&quot;&gt;lightweight aliases&lt;/strong&gt; that map one model name to another.&lt;/p&gt;&lt;hr data-end=&quot;2515&quot; data-start=&quot;2512&quot; /&gt;&lt;h2 data-end=&quot;2555&quot; data-start=&quot;2517&quot;&gt;Step 3: Why Gemma Fails (Important)&lt;/h2&gt;&lt;p data-end=&quot;2624&quot; data-start=&quot;2557&quot;&gt;Gemma models (including &lt;code data-end=&quot;2590&quot; data-start=&quot;2581&quot;&gt;gemma3n&lt;/code&gt;) &lt;strong data-end=&quot;2623&quot; data-start=&quot;2592&quot;&gt;do not support tool calling&lt;/strong&gt;.&lt;/p&gt;&lt;p data-end=&quot;2670&quot; data-start=&quot;2626&quot;&gt;Claude Code &lt;em data-end=&quot;2646&quot; data-start=&quot;2638&quot;&gt;always&lt;/em&gt; sends a &lt;code data-end=&quot;2662&quot; data-start=&quot;2655&quot;&gt;tools&lt;/code&gt; schema.&lt;/p&gt;&lt;p data-end=&quot;2688&quot; data-start=&quot;2672&quot;&gt;Resulting error:&lt;/p&gt;&lt;pre class=&quot;overflow-visible! px-0!&quot; data-end=&quot;2720&quot; data-start=&quot;2690&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-[calc(--spacing(9)+var(--header-height))] @w-xl/main:top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre!&quot;&gt;&lt;span class=&quot;hljs-attribute&quot;&gt;does&lt;/span&gt; not support tools
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;p data-end=&quot;2780&quot; data-start=&quot;2722&quot;&gt;This is a &lt;strong data-end=&quot;2748&quot; data-start=&quot;2732&quot;&gt;hard failure&lt;/strong&gt;, not a timeout or config issue.&lt;/p&gt;&lt;h3 data-end=&quot;2796&quot; data-start=&quot;2782&quot;&gt;Conclusion&lt;/h3&gt;&lt;blockquote data-end=&quot;2846&quot; data-start=&quot;2798&quot;&gt;
&lt;p data-end=&quot;2846&quot; data-start=&quot;2800&quot;&gt;Gemma works for chat, &lt;strong data-end=&quot;2829&quot; data-start=&quot;2822&quot;&gt;not&lt;/strong&gt; for Claude Code.&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr data-end=&quot;2851&quot; data-start=&quot;2848&quot; /&gt;&lt;h2 data-end=&quot;2888&quot; data-start=&quot;2853&quot;&gt;Step 4: Use a Tool-Capable Model&lt;/h2&gt;&lt;p data-end=&quot;2921&quot; data-start=&quot;2890&quot;&gt;You need a model that supports:&lt;/p&gt;&lt;ul data-end=&quot;2982&quot; data-start=&quot;2922&quot;&gt;
&lt;li data-end=&quot;2945&quot; data-start=&quot;2922&quot;&gt;
&lt;p data-end=&quot;2945&quot; data-start=&quot;2924&quot;&gt;structured tool calls&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;2962&quot; data-start=&quot;2946&quot;&gt;
&lt;p data-end=&quot;2962&quot; data-start=&quot;2948&quot;&gt;JSON responses&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;2982&quot; data-start=&quot;2963&quot;&gt;
&lt;p data-end=&quot;2982&quot; data-start=&quot;2965&quot;&gt;streaming + edits&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;h3 data-end=&quot;2999&quot; data-start=&quot;2984&quot;&gt;Recommended&lt;/h3&gt;&lt;pre class=&quot;overflow-visible! px-0!&quot; data-end=&quot;3041&quot; data-start=&quot;3001&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-[calc(--spacing(9)+var(--header-height))] @w-xl/main:top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-bash&quot;&gt;ollama pull qwen2.5-coder:7b
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;p data-end=&quot;3111&quot; data-start=&quot;3043&quot;&gt;This is the &lt;strong data-end=&quot;3082&quot; data-start=&quot;3055&quot;&gt;smallest reliable model&lt;/strong&gt; that works with Claude Code.&lt;/p&gt;&lt;hr data-end=&quot;3116&quot; data-start=&quot;3113&quot; /&gt;&lt;h2 data-end=&quot;3161&quot; data-start=&quot;3118&quot;&gt;Step 5: Create Claude-Compatible Aliases&lt;/h2&gt;&lt;p data-end=&quot;3184&quot; data-start=&quot;3163&quot;&gt;Create a &lt;code data-end=&quot;3183&quot; data-start=&quot;3172&quot;&gt;Modelfile&lt;/code&gt;:&lt;/p&gt;&lt;pre class=&quot;overflow-visible! px-0!&quot; data-end=&quot;3215&quot; data-start=&quot;3186&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-[calc(--spacing(9)+var(--header-height))] @w-xl/main:top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre!&quot;&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;FROM&lt;/span&gt; qwen2.&lt;span class=&quot;hljs-number&quot;&gt;5&lt;/span&gt;-coder:&lt;span class=&quot;hljs-number&quot;&gt;7&lt;/span&gt;b
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;p data-end=&quot;3257&quot; data-start=&quot;3217&quot;&gt;Then create aliases Claude Code expects:&lt;/p&gt;&lt;pre class=&quot;overflow-visible! px-0!&quot; data-end=&quot;3393&quot; data-start=&quot;3259&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-[calc(--spacing(9)+var(--header-height))] @w-xl/main:top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-bash&quot;&gt;ollama create claude-sonnet -f Modelfile
ollama create claude-opus   -f Modelfile
ollama create claude-haiku  -f Modelfile
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;p data-end=&quot;3449&quot; data-start=&quot;3395&quot;&gt;No weights are duplicated.&lt;br data-end=&quot;3424&quot; data-start=&quot;3421&quot; /&gt;
Only metadata is created.&lt;/p&gt;&lt;p data-end=&quot;2143&quot; data-start=&quot;2126&quot;&gt;





























&lt;/p&gt;&lt;p data-end=&quot;3504&quot; data-start=&quot;3451&quot;&gt;Now Ollama can answer requests for &lt;code data-end=&quot;3503&quot; data-start=&quot;3486&quot;&gt;claude-sonnet-*&lt;/code&gt;.&lt;/p&gt;&lt;h2 data-end=&quot;3991&quot; data-start=&quot;3957&quot;&gt;Result: Fully Local Claude Code&lt;/h2&gt;&lt;p data-end=&quot;4024&quot; data-start=&quot;3993&quot;&gt;At this point, Claude Code can:&lt;/p&gt;&lt;ul data-end=&quot;4111&quot; data-start=&quot;4026&quot;&gt;
&lt;li data-end=&quot;4040&quot; data-start=&quot;4026&quot;&gt;
&lt;p data-end=&quot;4040&quot; data-start=&quot;4028&quot;&gt;create files&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;4053&quot; data-start=&quot;4041&quot;&gt;
&lt;p data-end=&quot;4053&quot; data-start=&quot;4043&quot;&gt;edit files&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;4074&quot; data-start=&quot;4054&quot;&gt;
&lt;p data-end=&quot;4074&quot; data-start=&quot;4056&quot;&gt;run shell commands&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;4090&quot; data-start=&quot;4075&quot;&gt;
&lt;p data-end=&quot;4090&quot; data-start=&quot;4077&quot;&gt;refactor code&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;4111&quot; data-start=&quot;4091&quot;&gt;
&lt;p data-end=&quot;4111&quot; data-start=&quot;4093&quot;&gt;reason about repos&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;p data-end=&quot;3504&quot; data-start=&quot;3451&quot;&gt;


&lt;/p&gt;&lt;p data-end=&quot;4142&quot; data-start=&quot;4113&quot;&gt;All &lt;strong data-end=&quot;4141&quot; data-start=&quot;4117&quot;&gt;without cloud access&lt;/strong&gt;.&lt;/p&gt;&lt;p data-end=&quot;4142&quot; data-start=&quot;4113&quot;&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiSfEZPwYWlCqJEwYENT6QW5PD-Bv7Xhw91DcI0yCsJNILt9NSwUeISmRhLRn945hhJl0MifA1eZ1LncwuiI3jODNdg5hRYKwp4Iqc6h-Pwnm8EcH2uHTfneP-tjCSeKpv7iPRTy96DapQ6tjwKI3HTF4krOl6d98EJb2bclRc1M9LkyqK_ra-Rge9ab5o&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;880&quot; data-original-width=&quot;1437&quot; height=&quot;392&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiSfEZPwYWlCqJEwYENT6QW5PD-Bv7Xhw91DcI0yCsJNILt9NSwUeISmRhLRn945hhJl0MifA1eZ1LncwuiI3jODNdg5hRYKwp4Iqc6h-Pwnm8EcH2uHTfneP-tjCSeKpv7iPRTy96DapQ6tjwKI3HTF4krOl6d98EJb2bclRc1M9LkyqK_ra-Rge9ab5o=w640-h392&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgy8YSIlu5-MQgrqg-0s4_CMnLFkCWs3pDxIK_eGSc24exhoiYBN94Xrp4nw2-eWfKxEavd_mig2wOqwybj9o22BNb5UkGmEZdgB6jR2s3E5S-pZegxeFv7wrW4lKJQNHvJugD77bCUOZGtDcsGOdjhK8gMjZtxVNQJWzfwBaHzJsmFh7DZnqv0JNVx-MY&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;991&quot; data-original-width=&quot;1902&quot; height=&quot;334&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgy8YSIlu5-MQgrqg-0s4_CMnLFkCWs3pDxIK_eGSc24exhoiYBN94Xrp4nw2-eWfKxEavd_mig2wOqwybj9o22BNb5UkGmEZdgB6jR2s3E5S-pZegxeFv7wrW4lKJQNHvJugD77bCUOZGtDcsGOdjhK8gMjZtxVNQJWzfwBaHzJsmFh7DZnqv0JNVx-MY=w640-h334&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;/p&gt;</description><link>https://thundaxsoftware.blogspot.com/2026/01/running-claude-code-fully-locally-with.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9WeixbvB2oF-WOzD69_uFgmOsJ0xiUFPaV5rjs39NGGc1-0axBdzGYBQBpMNL3Nh4SXifIJOZCaYeja16D9YLWvRXk2xRfrtx0FNWIIZCx9dMgZatSXY7BiFPUz0z9gZpI-YhgNfgLbjqisB6y6ZUW1qnNjyywe5KZTvYWjzFPFKEE-xK5td5rAaYh-Q/s72-c/ChatGPT%20Image%20Jan%2025,%202026,%2005_20_49%20PM.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-6190973653015376234</guid><pubDate>Sun, 12 Oct 2025 19:23:00 +0000</pubDate><atom:updated>2026-03-15T21:35:57.801+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">AG2</category><category domain="http://www.blogger.com/atom/ns#">AI</category><category domain="http://www.blogger.com/atom/ns#">asset allocation</category><category domain="http://www.blogger.com/atom/ns#">AutoGen</category><category domain="http://www.blogger.com/atom/ns#">budgeting</category><category domain="http://www.blogger.com/atom/ns#">Chainlit</category><category domain="http://www.blogger.com/atom/ns#">ChatBot</category><category domain="http://www.blogger.com/atom/ns#">compliance</category><category domain="http://www.blogger.com/atom/ns#">Finance</category><category domain="http://www.blogger.com/atom/ns#">group chat</category><category domain="http://www.blogger.com/atom/ns#">Investment</category><category domain="http://www.blogger.com/atom/ns#">llm</category><category domain="http://www.blogger.com/atom/ns#">macroeconomics</category><category domain="http://www.blogger.com/atom/ns#">market analysis</category><category domain="http://www.blogger.com/atom/ns#">multi-agent</category><category domain="http://www.blogger.com/atom/ns#">portfolio</category><category domain="http://www.blogger.com/atom/ns#">Python</category><category domain="http://www.blogger.com/atom/ns#">recommendation</category><category domain="http://www.blogger.com/atom/ns#">risk tolerance</category><title>Building a Multi‑Agent Finance Chatbot with AG2</title><description>&lt;p&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg14JYKYf79T4ii7Dw86A3QqUwnV2Qmk-XRd0ylU6TvzO_pbmDwgLxOXFXoUFO7RQaA9BoB2Sauim8pPRugTLgZMGlot1gzigjLiMcBzn_qxJ-uFuqlJVyVeXl-FjMmffDKKg1d6xUvxW6XshSNlkDnF55_UIc8irs7MeM5Rmwvo3qafDQthN4Jox8XpNo/s1258/post.png&quot; style=&quot;clear: left; display: inline !important; float: left; margin-bottom: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1258&quot; height=&quot;260&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg14JYKYf79T4ii7Dw86A3QqUwnV2Qmk-XRd0ylU6TvzO_pbmDwgLxOXFXoUFO7RQaA9BoB2Sauim8pPRugTLgZMGlot1gzigjLiMcBzn_qxJ-uFuqlJVyVeXl-FjMmffDKKg1d6xUvxW6XshSNlkDnF55_UIc8irs7MeM5Rmwvo3qafDQthN4Jox8XpNo/s320/post.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&amp;nbsp;Financial conversations often touch on complex markets, risk preferences and compliance constraints.  A single large‑language model may struggle to handle every nuance, so &lt;strong data-end=&quot;230&quot; data-start=&quot;223&quot;&gt;AG2&lt;/strong&gt; (formerly AutoGen) offers a multi‑agent architecture where each agent specializes in part of the workflow.  This article demonstrates how to create a &lt;em data-end=&quot;390&quot; data-start=&quot;381&quot;&gt;finance&lt;/em&gt; chatbot using AG2 and Python.  We will define specialised agents, orchestrate them via a group chat and show a sample interaction.  The approach is inspired by multi‑agent healthcare chatbots but adapted to investment discussions.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 data-end=&quot;651&quot; data-start=&quot;623&quot;&gt;Why multi‑agent chatbots?&lt;/h2&gt;&lt;p data-end=&quot;1508&quot; data-start=&quot;653&quot;&gt;Recent tutorials explain that multi‑agent chatbots divide complex interactions into specialised roles.  Each agent focuses on one task—answering questions, providing recommendations or analysing data—so the overall system can handle nuanced conversations more accurately (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.analyticsvidhya.com/blog/2024/11/multi-agent-chatbots-with-autogen/#:~:text=Multi,responses%20compared%20to%20a%20single&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.analyticsvidhya.com/blog/2024/11/multi-agent-chatbots-with-autogen/#:~:text=Multi,responses%20compared%20to%20a%20single&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;analyticsvidhya.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  The AG2 documentation notes that multi‑agent architectures are easier to maintain and extend; they allow developers to add or remove functionality without redesigning the entire system (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://docs.ag2.ai/0.8.7/docs/blog/2024/05/24/Agent/#:~:text=The%20multi,agents%2C%20one%20can%20compose%20and&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://docs.ag2.ai/0.8.7/docs/blog/2024/05/24/Agent/#:~:text=The%20multi,agents%2C%20one%20can%20compose%20and&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;docs.ag2.ai&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&amp;nbsp; Multi‑agent designs also support different conversation patterns (sequential, group or nested chat) and enable tool use and code execution (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://docs.ag2.ai/0.8.7/docs/blog/2024/05/24/Agent/#:~:text=combining%20models%20and%20agents,&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://docs.ag2.ai/0.8.7/docs/blog/2024/05/24/Agent/#:~:text=combining%20models%20and%20agents,&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;docs.ag2.ai&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  In regulated industries such as healthcare or finance, this modularity improves accuracy and reduces risk (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://docs.ag2.ai/0.8.7/docs/blog/2024/05/24/Agent/#:~:text=There%20are%20at%20least%20two,agent%20systems&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://docs.ag2.ai/0.8.7/docs/blog/2024/05/24/Agent/#:~:text=There%20are%20at%20least%20two,agent%20systems&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;docs.ag2.ai&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;&lt;p data-end=&quot;2074&quot; data-start=&quot;1510&quot;&gt;Another advantage is practicality.  Building a chatbot for stock‑market questions is complex, but packages like &lt;strong data-end=&quot;1634&quot; data-start=&quot;1622&quot;&gt;Chainlit&lt;/strong&gt; and &lt;strong data-end=&quot;1646&quot; data-start=&quot;1639&quot;&gt;AG2&lt;/strong&gt; simplify the process by handling chat orchestration and tool calls (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://tinztwinshub.com/investment-research/build-a-multi-agent-stock-market-analyst-to-compare-stock-price-performance/#:~:text=Building%20a%20chatbot%20app%20for,AG2%20make%20it%20much%20easier&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://tinztwinshub.com/investment-research/build-a-multi-agent-stock-market-analyst-to-compare-stock-price-performance/#:~:text=Building%20a%20chatbot%20app%20for,AG2%20make%20it%20much%20easier&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;tinztwinshub.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  These frameworks allow you to run large‑language models locally or through an API and to register Python functions as tools (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://tinztwinshub.com/investment-research/build-a-multi-agent-stock-market-analyst-to-compare-stock-price-performance/#:~:text=,the%20function%20and%20its%20registration&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://tinztwinshub.com/investment-research/build-a-multi-agent-stock-market-analyst-to-compare-stock-price-performance/#:~:text=,the%20function%20and%20its%20registration&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;tinztwinshub.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  As open‑source models like Meta’s Llama 3.1 gain popularity, developers can deploy multi‑agent systems on local machines (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://tinztwinshub.com/investment-research/build-a-multi-agent-stock-market-analyst-to-compare-stock-price-performance/#:~:text=Our%20chatbot%20uses%20Meta%E2%80%99s%20,or%20tools%20to%20the%20application&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://tinztwinshub.com/investment-research/build-a-multi-agent-stock-market-analyst-to-compare-stock-price-performance/#:~:text=Our%20chatbot%20uses%20Meta%E2%80%99s%20,or%20tools%20to%20the%20application&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;tinztwinshub.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;&lt;h3 data-end=&quot;2093&quot; data-start=&quot;2076&quot;&gt;Pros and cons&lt;/h3&gt;&lt;div class=&quot;_tableContainer_1rjym_1&quot;&gt;&lt;div class=&quot;group _tableWrapper_1rjym_13 flex w-fit flex-col-reverse&quot; tabindex=&quot;-1&quot;&gt;&lt;table class=&quot;w-fit min-w-(--thread-content-width)&quot; data-end=&quot;3548&quot; data-start=&quot;2095&quot;&gt;&lt;thead data-end=&quot;2299&quot; data-start=&quot;2095&quot;&gt;&lt;tr data-end=&quot;2299&quot; data-start=&quot;2095&quot;&gt;&lt;th data-col-size=&quot;sm&quot; data-end=&quot;2119&quot; data-start=&quot;2095&quot;&gt;Aspect&lt;/th&gt;&lt;th data-col-size=&quot;lg&quot; data-end=&quot;2203&quot; data-start=&quot;2119&quot;&gt;Pros&lt;/th&gt;&lt;th data-col-size=&quot;md&quot; data-end=&quot;2299&quot; data-start=&quot;2203&quot;&gt;Cons &amp;amp; Considerations&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody data-end=&quot;3548&quot; data-start=&quot;2505&quot;&gt;&lt;tr data-end=&quot;2746&quot; data-start=&quot;2505&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2528&quot; data-start=&quot;2505&quot;&gt;&lt;strong data-end=&quot;2521&quot; data-start=&quot;2507&quot;&gt;Modularity&lt;/strong&gt;&lt;/td&gt;&lt;td data-col-size=&quot;lg&quot; data-end=&quot;2644&quot; data-start=&quot;2528&quot;&gt;Each agent has a focused role, making the system easier to extend or modify (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://docs.ag2.ai/0.8.7/docs/blog/2024/05/24/Agent/#:~:text=The%20multi,It%27s%20very&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://docs.ag2.ai/0.8.7/docs/blog/2024/05/24/Agent/#:~:text=The%20multi,It%27s%20very&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;docs.ag2.ai&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/td&gt;&lt;td data-col-size=&quot;md&quot; data-end=&quot;2746&quot; data-start=&quot;2644&quot;&gt;More agents increase the number of interactions to manage and may introduce coordination overhead.&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;2979&quot; data-start=&quot;2747&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2770&quot; data-start=&quot;2747&quot;&gt;&lt;strong data-end=&quot;2767&quot; data-start=&quot;2749&quot;&gt;Specialisation&lt;/strong&gt;&lt;/td&gt;&lt;td data-col-size=&quot;lg&quot; data-end=&quot;2881&quot; data-start=&quot;2770&quot;&gt;Domain‑specific agents can outperform a monolithic model on their task (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.analyticsvidhya.com/blog/2024/11/multi-agent-chatbots-with-autogen/#:~:text=Multi,responses%20compared%20to%20a%20single&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.analyticsvidhya.com/blog/2024/11/multi-agent-chatbots-with-autogen/#:~:text=Multi,responses%20compared%20to%20a%20single&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;analyticsvidhya.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/td&gt;&lt;td data-col-size=&quot;md&quot; data-end=&quot;2979&quot; data-start=&quot;2881&quot;&gt;Requires careful prompt design to keep agents within their remit and avoid conflicting advice.&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;3258&quot; data-start=&quot;2980&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3003&quot; data-start=&quot;2980&quot;&gt;&lt;strong data-end=&quot;3000&quot; data-start=&quot;2982&quot;&gt;Resource usage&lt;/strong&gt;&lt;/td&gt;&lt;td data-col-size=&quot;lg&quot; data-end=&quot;3156&quot; data-start=&quot;3003&quot;&gt;Multi‑agent architectures can run cheaper models for some roles while leveraging larger models only where needed (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://docs.ag2.ai/0.8.7/docs/blog/2024/05/24/Agent/#:~:text=,4%20agent&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://docs.ag2.ai/0.8.7/docs/blog/2024/05/24/Agent/#:~:text=,4%20agent&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;docs.ag2.ai&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/td&gt;&lt;td data-col-size=&quot;md&quot; data-end=&quot;3258&quot; data-start=&quot;3156&quot;&gt;Running multiple models in parallel increases latency and can be costly for high‑volume workloads.&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;3548&quot; data-start=&quot;3259&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3283&quot; data-start=&quot;3259&quot;&gt;&lt;strong data-end=&quot;3277&quot; data-start=&quot;3261&quot;&gt;Transparency&lt;/strong&gt;&lt;/td&gt;&lt;td data-col-size=&quot;lg&quot; data-end=&quot;3444&quot; data-start=&quot;3283&quot;&gt;By exposing distinct agent responses, users can understand how conclusions are derived (e.g., one agent summarises market data, another suggests allocations).&lt;/td&gt;&lt;td data-col-size=&quot;md&quot; data-end=&quot;3548&quot; data-start=&quot;3444&quot;&gt;In some contexts users may prefer a single, unified answer; too much detail may confuse non‑experts.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2 data-end=&quot;3588&quot; data-start=&quot;3550&quot;&gt;Defining the finance chatbot agents&lt;/h2&gt;&lt;p data-end=&quot;3895&quot; data-start=&quot;3590&quot;&gt;Our finance chatbot will engage the user to understand their goals, analyse market conditions and provide general asset‑allocation suggestions.  It is &lt;strong data-end=&quot;3748&quot; data-start=&quot;3741&quot;&gt;not&lt;/strong&gt; a licensed financial adviser; recommendations are for educational purposes.  We adapt the roles from a mental‑health chatbot example into finance:&lt;/p&gt;&lt;ol data-end=&quot;4353&quot; data-start=&quot;3897&quot;&gt;
&lt;li data-end=&quot;3996&quot; data-start=&quot;3897&quot;&gt;
&lt;p data-end=&quot;3996&quot; data-start=&quot;3900&quot;&gt;&lt;strong data-end=&quot;3916&quot; data-start=&quot;3900&quot;&gt;Client&amp;nbsp;Agent&lt;/strong&gt; – Captures the user’s financial situation, risk tolerance and investment goals.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;4181&quot; data-start=&quot;3997&quot;&gt;
&lt;p data-end=&quot;4181&quot; data-start=&quot;4000&quot;&gt;&lt;strong data-end=&quot;4025&quot; data-start=&quot;4000&quot;&gt;Market&amp;nbsp;Analysis&amp;nbsp;Agent&lt;/strong&gt; – Analyses the market based on user input.  It summarises macro‑economic trends, asset‑class performance and volatility without giving personalised advice.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;4353&quot; data-start=&quot;4182&quot;&gt;
&lt;p data-end=&quot;4353&quot; data-start=&quot;4185&quot;&gt;&lt;strong data-end=&quot;4219&quot; data-start=&quot;4185&quot;&gt;Portfolio&amp;nbsp;Recommendation&amp;nbsp;Agent&lt;/strong&gt; – Suggests a high‑level asset allocation based on the analysis and user profile.  It does not trade or recommend specific securities.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;p data-end=&quot;4394&quot; data-start=&quot;4355&quot;&gt;The roles can be summarized as follows:&lt;/p&gt;&lt;div class=&quot;_tableContainer_1rjym_1&quot;&gt;&lt;div class=&quot;group _tableWrapper_1rjym_13 flex w-fit flex-col-reverse&quot; tabindex=&quot;-1&quot;&gt;&lt;table class=&quot;w-fit min-w-(--thread-content-width)&quot; data-end=&quot;5281&quot; data-start=&quot;4396&quot;&gt;&lt;thead data-end=&quot;4452&quot; data-start=&quot;4396&quot;&gt;&lt;tr data-end=&quot;4452&quot; data-start=&quot;4396&quot;&gt;&lt;th data-col-size=&quot;sm&quot; data-end=&quot;4422&quot; data-start=&quot;4396&quot;&gt;Agent&lt;/th&gt;&lt;th data-col-size=&quot;lg&quot; data-end=&quot;4432&quot; data-start=&quot;4422&quot;&gt;Purpose&lt;/th&gt;&lt;th data-col-size=&quot;lg&quot; data-end=&quot;4452&quot; data-start=&quot;4432&quot;&gt;Key instructions&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody data-end=&quot;5281&quot; data-start=&quot;4509&quot;&gt;&lt;tr data-end=&quot;4725&quot; data-start=&quot;4509&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4535&quot; data-start=&quot;4509&quot;&gt;&lt;strong data-end=&quot;4527&quot; data-start=&quot;4511&quot;&gt;Client&amp;nbsp;Agent&lt;/strong&gt;&lt;/td&gt;&lt;td data-col-size=&quot;lg&quot; data-end=&quot;4631&quot; data-start=&quot;4535&quot;&gt;Collects information about the user’s financial goals, investment horizon and risk tolerance.&lt;/td&gt;&lt;td data-col-size=&quot;lg&quot; data-end=&quot;4725&quot; data-start=&quot;4631&quot;&gt;Ask the user about their objectives, constraints and any particular interests or concerns.&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;5003&quot; data-start=&quot;4726&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4754&quot; data-start=&quot;4726&quot;&gt;&lt;strong data-end=&quot;4753&quot; data-start=&quot;4728&quot;&gt;Market&amp;nbsp;Analysis&amp;nbsp;Agent&lt;/strong&gt;&lt;/td&gt;&lt;td data-col-size=&quot;lg&quot; data-end=&quot;4873&quot; data-start=&quot;4754&quot;&gt;Summarises market conditions, including recent trends in equities, bonds, commodities and macro‑economic indicators.&lt;/td&gt;&lt;td data-col-size=&quot;lg&quot; data-end=&quot;5003&quot; data-start=&quot;4873&quot;&gt;Do not provide personal investment advice; instead, report high‑level insights such as whether markets are volatile or stable.&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;5281&quot; data-start=&quot;5004&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5041&quot; data-start=&quot;5004&quot;&gt;&lt;strong data-end=&quot;5040&quot; data-start=&quot;5006&quot;&gt;Portfolio&amp;nbsp;Recommendation&amp;nbsp;Agent&lt;/strong&gt;&lt;/td&gt;&lt;td data-col-size=&quot;lg&quot; data-end=&quot;5141&quot; data-start=&quot;5041&quot;&gt;Provides general asset‑allocation suggestions based on the user’s profile and the market summary.&lt;/td&gt;&lt;td data-col-size=&quot;lg&quot; data-end=&quot;5281&quot; data-start=&quot;5141&quot;&gt;Recommend broad categories (e.g., 60&amp;nbsp;% equities, 30&amp;nbsp;% bonds, 10&amp;nbsp;% alternatives); include a disclaimer that this is not financial advice.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2 data-end=&quot;5312&quot; data-start=&quot;5283&quot;&gt;Setting up the environment&lt;/h2&gt;&lt;p data-end=&quot;5751&quot; data-start=&quot;5314&quot;&gt;To follow along you need Python 3.11+ and the &lt;code data-end=&quot;5365&quot; data-start=&quot;5360&quot;&gt;ag2&lt;/code&gt; library.  You can install AG2 via pip (&lt;code data-end=&quot;5430&quot; data-start=&quot;5405&quot;&gt;pip install ag2[openai]&lt;/code&gt;).  If you plan to run local models, install an API wrapper like &lt;code data-end=&quot;5503&quot; data-start=&quot;5495&quot;&gt;ollama&lt;/code&gt; and download a model such as Llama&amp;nbsp;3.1 (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://tinztwinshub.com/investment-research/build-a-multi-agent-stock-market-analyst-to-compare-stock-price-performance/#:~:text=Our%20chatbot%20uses%20Meta%E2%80%99s%20,or%20tools%20to%20the%20application&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://tinztwinshub.com/investment-research/build-a-multi-agent-stock-market-analyst-to-compare-stock-price-performance/#:~:text=Our%20chatbot%20uses%20Meta%E2%80%99s%20,or%20tools%20to%20the%20application&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;tinztwinshub.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  You will also need API keys if using cloud models.  In this example we configure the LLM via &lt;code data-end=&quot;5689&quot; data-start=&quot;5677&quot;&gt;llm_config&lt;/code&gt; without specifying an API key; supply your own key as needed.&lt;/p&gt;&lt;h2 data-end=&quot;5779&quot; data-start=&quot;5753&quot;&gt;Implementing the agents&lt;/h2&gt;&lt;p data-end=&quot;6000&quot; data-start=&quot;5781&quot;&gt;The following Python code illustrates how to create the finance chatbot.  It borrows the group‑chat structure from the mental‑health example and uses AG2’s &lt;code data-end=&quot;5955&quot; data-start=&quot;5937&quot;&gt;ConversableAgent&lt;/code&gt;, &lt;code data-end=&quot;5968&quot; data-start=&quot;5957&quot;&gt;GroupChat&lt;/code&gt; and &lt;code data-end=&quot;5991&quot; data-start=&quot;5973&quot;&gt;GroupChatManager&lt;/code&gt; classes:&lt;/p&gt;&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;8899&quot; data-start=&quot;6002&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; autogen &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; ConversableAgent, GroupChat, GroupChatManager

&lt;span class=&quot;hljs-comment&quot;&gt;# Replace None with your OpenAI or local model API key if required&lt;/span&gt;
llm_config = {&lt;span class=&quot;hljs-string&quot;&gt;&quot;config_list&quot;&lt;/span&gt;: [{&lt;span class=&quot;hljs-string&quot;&gt;&quot;model&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;gpt-4o&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;api_key&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;None&lt;/span&gt;}]}

&lt;span class=&quot;hljs-comment&quot;&gt;# 1. Client Agent: collects user goals and risk tolerance&lt;/span&gt;
client_agent = ConversableAgent(
    name=&lt;span class=&quot;hljs-string&quot;&gt;&quot;client&quot;&lt;/span&gt;,
    system_message=(
        &lt;span class=&quot;hljs-string&quot;&gt;&quot;You are the client interface.\n&quot;&lt;/span&gt;
        &lt;span class=&quot;hljs-string&quot;&gt;&quot;Your job is to describe your financial goals, risk tolerance, investment horizon, &quot;&lt;/span&gt;
        &lt;span class=&quot;hljs-string&quot;&gt;&quot;and any budget constraints or ethical considerations.&quot;&lt;/span&gt;
    ),
    llm_config=llm_config
)

&lt;span class=&quot;hljs-comment&quot;&gt;# 2. Market Analysis Agent: summarises current market conditions&lt;/span&gt;
market_analysis_agent = ConversableAgent(
    name=&lt;span class=&quot;hljs-string&quot;&gt;&quot;market_analysis&quot;&lt;/span&gt;,
    system_message=(
        &lt;span class=&quot;hljs-string&quot;&gt;&quot;You analyse the current market based on the client&#39;s input.&quot;&lt;/span&gt;
        &lt;span class=&quot;hljs-string&quot;&gt;&quot;Summarise macroeconomic factors (interest rates, inflation), sector trends and recent market volatility. &quot;&lt;/span&gt;
        &lt;span class=&quot;hljs-string&quot;&gt;&quot;Do not provide personalised investment advice; simply report high-level observations.&quot;&lt;/span&gt;
    ),
    llm_config=llm_config
)

&lt;span class=&quot;hljs-comment&quot;&gt;# 3. Portfolio Recommendation Agent: proposes a high-level allocation&lt;/span&gt;
portfolio_recommendation_agent = ConversableAgent(
    name=&lt;span class=&quot;hljs-string&quot;&gt;&quot;portfolio_recommendation&quot;&lt;/span&gt;,
    system_message=(
        &lt;span class=&quot;hljs-string&quot;&gt;&quot;You suggest a general asset allocation based on the analysis from the Market Analysis Agent.&quot;&lt;/span&gt;
        &lt;span class=&quot;hljs-string&quot;&gt;&quot;Consider the client&#39;s risk tolerance and goals.&quot;&lt;/span&gt;
        &lt;span class=&quot;hljs-string&quot;&gt;&quot;Recommend proportions across asset classes such as equities, bonds, and alternatives.&quot;&lt;/span&gt;
        &lt;span class=&quot;hljs-string&quot;&gt;&quot;Include a disclaimer that the suggestions are educational and not financial advice.&quot;&lt;/span&gt;
    ),
    llm_config=llm_config
)

&lt;span class=&quot;hljs-comment&quot;&gt;# Configure a group chat where the analysis and recommendation agents converse&lt;/span&gt;
&lt;span class=&quot;hljs-comment&quot;&gt;# The manager orchestrates turn-taking in round‑robin fashion&lt;/span&gt;
finance_groupchat = GroupChat(
    agents=[market_analysis_agent, portfolio_recommendation_agent],
    messages=[],
    max_round=&lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;,
    speaker_selection_method=&lt;span class=&quot;hljs-string&quot;&gt;&quot;round_robin&quot;&lt;/span&gt;
)

manager = GroupChatManager(name=&lt;span class=&quot;hljs-string&quot;&gt;&quot;manager&quot;&lt;/span&gt;, groupchat=finance_groupchat)

&lt;span class=&quot;hljs-comment&quot;&gt;# Function to start the finance chatbot&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;start_finance_chat&lt;/span&gt;():
    &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;\nWelcome to the AI Finance Chatbot!&quot;&lt;/span&gt;)
    user_input = &lt;span class=&quot;hljs-built_in&quot;&gt;input&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Please describe your financial goals and risk tolerance: &quot;&lt;/span&gt;)

    &lt;span class=&quot;hljs-comment&quot;&gt;# The client agent initiates the conversation with the manager&lt;/span&gt;
    &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;\nAnalysing your profile and market conditions...&quot;&lt;/span&gt;)
    response = client_agent.initiate_chat(
        manager,
        message=(
            &lt;span class=&quot;hljs-string&quot;&gt;f&quot;My financial goals and risk tolerance are: &lt;span class=&quot;hljs-subst&quot;&gt;{user_input}&lt;/span&gt;&lt;/span&gt;. &quot;
            &lt;span class=&quot;hljs-string&quot;&gt;&quot;Based on this, what insights and recommendations can you provide?&quot;&lt;/span&gt;
        )
    )

    &lt;span class=&quot;hljs-comment&quot;&gt;# Fallback in case the first response is empty&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;not&lt;/span&gt; response:
        response = portfolio_recommendation_agent.initiate_chat(
            manager,
            message=&lt;span class=&quot;hljs-string&quot;&gt;&quot;Based on the client&#39;s goals, please provide portfolio recommendations.&quot;&lt;/span&gt;
        )

&lt;span class=&quot;hljs-comment&quot;&gt;# Run the chatbot&lt;/span&gt;
start_finance_chat()
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;h3 data-end=&quot;8917&quot; data-start=&quot;8901&quot;&gt;How it works&lt;/h3&gt;&lt;ol data-end=&quot;9766&quot; data-start=&quot;8919&quot;&gt;
&lt;li data-end=&quot;9082&quot; data-start=&quot;8919&quot;&gt;
&lt;p data-end=&quot;9082&quot; data-start=&quot;8922&quot;&gt;&lt;strong data-end=&quot;8936&quot; data-start=&quot;8922&quot;&gt;User input&lt;/strong&gt; – The &lt;code data-end=&quot;8957&quot; data-start=&quot;8943&quot;&gt;client_agent&lt;/code&gt; collects the investor’s goals and risk tolerance.  In a real deployment you might ask follow‑up questions to ensure clarity.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;9330&quot; data-start=&quot;9083&quot;&gt;
&lt;p data-end=&quot;9330&quot; data-start=&quot;9086&quot;&gt;&lt;strong data-end=&quot;9105&quot; data-start=&quot;9086&quot;&gt;Market analysis&lt;/strong&gt; – The &lt;code data-end=&quot;9135&quot; data-start=&quot;9112&quot;&gt;market_analysis_agent&lt;/code&gt; reviews the input and provides a concise summary of current market conditions.  It does not provide personalised advice, reflecting a common compliance requirement for general market commentary.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;9532&quot; data-start=&quot;9331&quot;&gt;
&lt;p data-end=&quot;9532&quot; data-start=&quot;9334&quot;&gt;&lt;strong data-end=&quot;9358&quot; data-start=&quot;9334&quot;&gt;Portfolio suggestion&lt;/strong&gt; – The &lt;code data-end=&quot;9397&quot; data-start=&quot;9365&quot;&gt;portfolio_recommendation_agent&lt;/code&gt; uses the market summary and client profile to recommend an asset allocation.  It emphasises diversification and includes a disclaimer.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;9766&quot; data-start=&quot;9533&quot;&gt;
&lt;p data-end=&quot;9766&quot; data-start=&quot;9536&quot;&gt;&lt;strong data-end=&quot;9561&quot; data-start=&quot;9536&quot;&gt;Group chat management&lt;/strong&gt; – The &lt;code data-end=&quot;9586&quot; data-start=&quot;9568&quot;&gt;GroupChatManager&lt;/code&gt; coordinates the exchange.  We use a round‑robin method to ensure both agents contribute (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.analyticsvidhya.com/blog/2024/11/multi-agent-chatbots-with-autogen/#:~:text=import%20autogen&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.analyticsvidhya.com/blog/2024/11/multi-agent-chatbots-with-autogen/#:~:text=import%20autogen&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;analyticsvidhya.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  The &lt;code data-end=&quot;9731&quot; data-start=&quot;9720&quot;&gt;max_round&lt;/code&gt; parameter prevents infinite loops.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;h4 data-end=&quot;9791&quot; data-start=&quot;9768&quot;&gt;Sample interaction&lt;/h4&gt;&lt;p data-end=&quot;9953&quot; data-start=&quot;9793&quot;&gt;Suppose a user says: &lt;em data-end=&quot;9915&quot; data-start=&quot;9814&quot;&gt;“I’m saving for retirement in 20&amp;nbsp;years, have moderate risk tolerance and want to invest ethically.”&lt;/em&gt;  The agents might respond as follows:&lt;/p&gt;&lt;blockquote data-end=&quot;10703&quot; data-start=&quot;9955&quot;&gt;
&lt;p data-end=&quot;10300&quot; data-start=&quot;9957&quot;&gt;&lt;strong data-end=&quot;9983&quot; data-start=&quot;9957&quot;&gt;Market&amp;nbsp;Analysis&amp;nbsp;Agent:&lt;/strong&gt; “Based on the current environment, central banks are signalling that interest rates may remain high to curb inflation, equities have been volatile with technology and renewable‑energy sectors outperforming, and bond yields offer moderate returns.  Commodities like gold have appreciated due to geopolitical tension.”&lt;/p&gt;
&lt;p data-end=&quot;10703&quot; data-start=&quot;10305&quot;&gt;&lt;strong data-end=&quot;10340&quot; data-start=&quot;10305&quot;&gt;Portfolio&amp;nbsp;Recommendation&amp;nbsp;Agent:&lt;/strong&gt; “Given your 20‑year horizon and moderate risk tolerance, a diversified allocation could be 60&amp;nbsp;% equities (with a tilt towards ESG‑focused funds), 30&amp;nbsp;% bonds (combining government and investment‑grade corporate bonds), and 10&amp;nbsp;% alternatives such as real‑estate investment trusts or commodities.  This is general information and not personalised financial advice.”&lt;/p&gt;
&lt;/blockquote&gt;&lt;p data-end=&quot;10955&quot; data-start=&quot;10705&quot;&gt;In a real‑world application you could register functions that fetch real‑time market data or compute portfolio statistics using AG2’s &lt;code data-end=&quot;10858&quot; data-start=&quot;10839&quot;&gt;register_function&lt;/code&gt; API (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://tinztwinshub.com/investment-research/build-a-multi-agent-stock-market-analyst-to-compare-stock-price-performance/#:~:text=,the%20function%20and%20its%20registration&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://tinztwinshub.com/investment-research/build-a-multi-agent-stock-market-analyst-to-compare-stock-price-performance/#:~:text=,the%20function%20and%20its%20registration&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;tinztwinshub.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;, but our simple example relies on the language model.&lt;/p&gt;&lt;h2 data-end=&quot;10990&quot; data-start=&quot;10957&quot;&gt;Pros and cons of this approach&lt;/h2&gt;&lt;ul data-end=&quot;12200&quot; data-start=&quot;10992&quot;&gt;
&lt;li data-end=&quot;11623&quot; data-start=&quot;10992&quot;&gt;
&lt;p data-end=&quot;11003&quot; data-start=&quot;10994&quot;&gt;&lt;strong data-end=&quot;11003&quot; data-start=&quot;10994&quot;&gt;Pros:&lt;/strong&gt;&lt;/p&gt;
&lt;ul data-end=&quot;11623&quot; data-start=&quot;11006&quot;&gt;
&lt;li data-end=&quot;11265&quot; data-start=&quot;11006&quot;&gt;
&lt;p data-end=&quot;11265&quot; data-start=&quot;11008&quot;&gt;&lt;em data-end=&quot;11039&quot; data-start=&quot;11008&quot;&gt;Modularity and specialisation&lt;/em&gt; – Each agent has a clear purpose, which can improve the relevance and quality of responses (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://docs.ag2.ai/0.8.7/docs/blog/2024/05/24/Agent/#:~:text=The%20multi,agents%2C%20one%20can%20compose%20and&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://docs.ag2.ai/0.8.7/docs/blog/2024/05/24/Agent/#:~:text=The%20multi,agents%2C%20one%20can%20compose%20and&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;docs.ag2.ai&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  For example, one agent summarises macro data while another focuses on portfolio construction.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;11461&quot; data-start=&quot;11268&quot;&gt;
&lt;p data-end=&quot;11461&quot; data-start=&quot;11270&quot;&gt;&lt;em data-end=&quot;11289&quot; data-start=&quot;11270&quot;&gt;Ease of extension&lt;/em&gt; – You can add agents to handle regulatory checks, tax considerations or portfolio rebalancing without rewriting existing components (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://docs.ag2.ai/0.8.7/docs/blog/2024/05/24/Agent/#:~:text=The%20multi,agents%2C%20one%20can%20compose%20and&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://docs.ag2.ai/0.8.7/docs/blog/2024/05/24/Agent/#:~:text=The%20multi,agents%2C%20one%20can%20compose%20and&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;docs.ag2.ai&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;11623&quot; data-start=&quot;11464&quot;&gt;
&lt;p data-end=&quot;11623&quot; data-start=&quot;11466&quot;&gt;&lt;em data-end=&quot;11480&quot; data-start=&quot;11466&quot;&gt;Transparency&lt;/em&gt; – Users can see how the system arrives at recommendations because each agent’s contribution is visible (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://docs.ag2.ai/0.8.7/docs/blog/2024/05/24/Agent/#:~:text=Benefits%20of%20multi&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://docs.ag2.ai/0.8.7/docs/blog/2024/05/24/Agent/#:~:text=Benefits%20of%20multi&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;docs.ag2.ai&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;12200&quot; data-start=&quot;11625&quot;&gt;
&lt;p data-end=&quot;11636&quot; data-start=&quot;11627&quot;&gt;&lt;strong data-end=&quot;11636&quot; data-start=&quot;11627&quot;&gt;Cons:&lt;/strong&gt;&lt;/p&gt;
&lt;ul data-end=&quot;12200&quot; data-start=&quot;11639&quot;&gt;
&lt;li data-end=&quot;11794&quot; data-start=&quot;11639&quot;&gt;
&lt;p data-end=&quot;11794&quot; data-start=&quot;11641&quot;&gt;&lt;em data-end=&quot;11664&quot; data-start=&quot;11641&quot;&gt;Coordination overhead&lt;/em&gt; – More agents mean more messages and potential latency.  Tuning &lt;code data-end=&quot;11740&quot; data-start=&quot;11729&quot;&gt;max_round&lt;/code&gt; and speaker selection helps avoid long conversations.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;11971&quot; data-start=&quot;11797&quot;&gt;
&lt;p data-end=&quot;11971&quot; data-start=&quot;11799&quot;&gt;&lt;em data-end=&quot;11818&quot; data-start=&quot;11799&quot;&gt;Prompt management&lt;/em&gt; – Agents must be carefully prompted to avoid stepping outside their roles.  If the market‑analysis agent gives advice, it could cause compliance issues.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;12200&quot; data-start=&quot;11974&quot;&gt;
&lt;p data-end=&quot;12200&quot; data-start=&quot;11976&quot;&gt;&lt;em data-end=&quot;11992&quot; data-start=&quot;11976&quot;&gt;Resource usage&lt;/em&gt; – Running multiple models or using powerful models for each agent can be costly.  Techniques like mixing cheap and expensive models or limiting context length can help (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://docs.ag2.ai/0.8.7/docs/blog/2024/05/24/Agent/#:~:text=,4%20agent&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://docs.ag2.ai/0.8.7/docs/blog/2024/05/24/Agent/#:~:text=,4%20agent&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;docs.ag2.ai&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;h2 data-end=&quot;12224&quot; data-start=&quot;12202&quot;&gt;Future enhancements&lt;/h2&gt;&lt;ul data-end=&quot;13221&quot; data-start=&quot;12226&quot;&gt;
&lt;li data-end=&quot;12539&quot; data-start=&quot;12226&quot;&gt;
&lt;p data-end=&quot;12539&quot; data-start=&quot;12228&quot;&gt;&lt;strong data-end=&quot;12259&quot; data-start=&quot;12228&quot;&gt;Real‑time data integration:&lt;/strong&gt; Register functions to call APIs (e.g., yfinance, Bloomberg, or custom data feeds) so the analysis agent can deliver up‑to‑the‑minute market summaries.  The Tinz&amp;nbsp;Twins example shows how to plot year‑to‑date gains using a registered function (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://tinztwinshub.com/investment-research/build-a-multi-agent-stock-market-analyst-to-compare-stock-price-performance/#:~:text=,the%20function%20and%20its%20registration&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://tinztwinshub.com/investment-research/build-a-multi-agent-stock-market-analyst-to-compare-stock-price-performance/#:~:text=,the%20function%20and%20its%20registration&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;tinztwinshub.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;12741&quot; data-start=&quot;12540&quot;&gt;
&lt;p data-end=&quot;12741&quot; data-start=&quot;12542&quot;&gt;&lt;strong data-end=&quot;12568&quot; data-start=&quot;12542&quot;&gt;Regulatory compliance:&lt;/strong&gt; Add an agent that checks whether recommendations adhere to regional regulations (e.g., MIFID&amp;nbsp;II or SEC guidelines).  It could flag issues before suggestions reach the user.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;13022&quot; data-start=&quot;12742&quot;&gt;
&lt;p data-end=&quot;13022&quot; data-start=&quot;12744&quot;&gt;&lt;strong data-end=&quot;12779&quot; data-start=&quot;12744&quot;&gt;Advanced conversation patterns:&lt;/strong&gt; Use nested or constrained group chats to handle multi‑step workflows, such as gathering risk questionnaires, performing Monte‑Carlo simulations and then generating a report.  AG2 supports these patterns (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://docs.ag2.ai/0.8.7/docs/blog/2024/05/24/Agent/#:~:text=combining%20models%20and%20agents,&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://docs.ag2.ai/0.8.7/docs/blog/2024/05/24/Agent/#:~:text=combining%20models%20and%20agents,&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;docs.ag2.ai&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;13221&quot; data-start=&quot;13023&quot;&gt;
&lt;p data-end=&quot;13221&quot; data-start=&quot;13025&quot;&gt;&lt;strong data-end=&quot;13048&quot; data-start=&quot;13025&quot;&gt;Multi‑modal output:&lt;/strong&gt; Integrate charting tools to visualise asset allocations or risk/return trade‑offs.  For instance, register a function that plots allocation pie charts and returns an image.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;h2 data-end=&quot;13236&quot; data-start=&quot;13223&quot;&gt;Conclusion&lt;/h2&gt;&lt;p&gt;



























&lt;/p&gt;&lt;p data-end=&quot;14083&quot; data-start=&quot;13238&quot;&gt;AG2’s multi‑agent architecture makes it straightforward to build tailored chatbots for complex domains such as finance.  By dividing the conversation into specialised roles, developers can achieve greater accuracy, transparency and extensibility compared with monolithic chatbots (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.analyticsvidhya.com/blog/2024/11/multi-agent-chatbots-with-autogen/#:~:text=Multi,responses%20compared%20to%20a%20single&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.analyticsvidhya.com/blog/2024/11/multi-agent-chatbots-with-autogen/#:~:text=Multi,responses%20compared%20to%20a%20single&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;analyticsvidhya.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  The simple example above illustrates how you can adapt a mental‑health chatbot pattern to a personal‑finance advisor.  Although multi‑agent systems introduce coordination challenges and resource overhead, careful design and modern frameworks like AG2 or Chainlit can mitigate these issues (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://docs.ag2.ai/0.8.7/docs/blog/2024/05/24/Agent/#:~:text=The%20multi,It%27s%20very&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://docs.ag2.ai/0.8.7/docs/blog/2024/05/24/Agent/#:~:text=The%20multi,It%27s%20very&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;docs.ag2.ai&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  As open‑source models and tooling evolve, we can expect multi‑agent finance chatbots to become more capable—potentially integrating real‑time data, compliance checks and sophisticated analytics.&lt;/p&gt;</description><link>https://thundaxsoftware.blogspot.com/2025/10/building-multiagent-finance-chatbot.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg14JYKYf79T4ii7Dw86A3QqUwnV2Qmk-XRd0ylU6TvzO_pbmDwgLxOXFXoUFO7RQaA9BoB2Sauim8pPRugTLgZMGlot1gzigjLiMcBzn_qxJ-uFuqlJVyVeXl-FjMmffDKKg1d6xUvxW6XshSNlkDnF55_UIc8irs7MeM5Rmwvo3qafDQthN4Jox8XpNo/s72-c/post.png" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>London, UK</georss:featurename><georss:point>51.5072178 -0.1275862</georss:point><georss:box>23.196983963821154 -35.2838362 79.817451636178845 35.0286638</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-6691039309025967621</guid><pubDate>Sat, 11 Oct 2025 18:41:00 +0000</pubDate><atom:updated>2026-03-15T21:41:43.688+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Agentic AI</category><category domain="http://www.blogger.com/atom/ns#">AI Agents</category><category domain="http://www.blogger.com/atom/ns#">AI Chatbot</category><category domain="http://www.blogger.com/atom/ns#">Compliance Automation</category><category domain="http://www.blogger.com/atom/ns#">CrewAI</category><category domain="http://www.blogger.com/atom/ns#">ESMA</category><category domain="http://www.blogger.com/atom/ns#">Financial Regulation</category><category domain="http://www.blogger.com/atom/ns#">GPT-4o</category><category domain="http://www.blogger.com/atom/ns#">HuggingFace</category><category domain="http://www.blogger.com/atom/ns#">MiFID II</category><category domain="http://www.blogger.com/atom/ns#">MiFIR</category><category domain="http://www.blogger.com/atom/ns#">openAI</category><category domain="http://www.blogger.com/atom/ns#">PDFSearchTool</category><category domain="http://www.blogger.com/atom/ns#">RegTech</category><category domain="http://www.blogger.com/atom/ns#">Task-Centric AI</category><title>Building a MiFID II Compliance Chatbot with CrewAI</title><description>&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKVOPmJuVHpPEhxkDQ2Sj6ingI7wZ0Ws9zVFQcLCDR1qkPnEy1-yP-g5giG3WC1Gw2GfjRj6UJphLVY275G07DoGmsBtye8REn3q7NhHc7hJ2GGAT2E5yRsxoqRPkZqJzMlIMNCREUFrWKvAzvH1mWTstt1u_jhcK8uhl-vk6mp3dt2ChWacW2azK5b34/s1024/ChatGPT%20Image%20Oct%2010,%202025,%2007_11_05%20PM.png&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKVOPmJuVHpPEhxkDQ2Sj6ingI7wZ0Ws9zVFQcLCDR1qkPnEy1-yP-g5giG3WC1Gw2GfjRj6UJphLVY275G07DoGmsBtye8REn3q7NhHc7hJ2GGAT2E5yRsxoqRPkZqJzMlIMNCREUFrWKvAzvH1mWTstt1u_jhcK8uhl-vk6mp3dt2ChWacW2azK5b34/s320/ChatGPT%20Image%20Oct%2010,%202025,%2007_11_05%20PM.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Financial regulation is notoriously complex. Investment firms must interpret and comply with lengthy documents such as the&amp;nbsp;&lt;em data-end=&quot;1090&quot; data-start=&quot;1046&quot;&gt;Markets in Financial Instruments Directive&lt;/em&gt;&amp;nbsp;(MiFID&amp;nbsp;II) and its companion regulation (MiFIR), while preparing for MiFID&amp;nbsp;III. Compliance officers routinely pore over hundreds of pages of policy statements, ESMA Q&amp;amp;As and Regulatory Technical Standards (RTS). At the same time, regulators are tightening expectations: by September&amp;nbsp;2025 firms will need to implement the MiFID&amp;nbsp;III amendments and be ready to demonstrate that surveillance and inducement controls work across every channel&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://luware.com/blog/mifid-iii-a-compliance-guide-for-financial-businesses#:~:text=9%3A12&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://luware.com/blog/mifid-iii-a-compliance-guide-for-financial-businesses#:~:text=9%3A12&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;&amp;nbsp;(luware.com&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://luware.com/blog/mifid-iii-a-compliance-guide-for-financial-businesses#:~:text=communications%20that%20may%20lead%20to,auditable%20communications%20across%20all%20channels&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://luware.com/blog/mifid-iii-a-compliance-guide-for-financial-businesses#:~:text=communications%20that%20may%20lead%20to,auditable%20communications%20across%20all%20channels&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;luware.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;. Manual search is inefficient and error‑prone.&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;/p&gt;&lt;p data-end=&quot;2068&quot; data-start=&quot;1654&quot;&gt;This article shows how to build a&amp;nbsp;&lt;strong data-end=&quot;1719&quot; data-start=&quot;1688&quot;&gt;MiFID&amp;nbsp;II compliance chatbot&lt;/strong&gt;&amp;nbsp;using CrewAI, a multi‑agent framework. You will learn how to organise the workflow, assign tools at the agent or task level, avoid common validation errors, and anticipate upcoming regulatory changes. The techniques apply beyond finance; they illustrate a pattern for building reliable agentic systems that combine retrieval with language models.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div class=&quot;_tableContainer_1rjym_1&quot;&gt;&lt;div class=&quot;group _tableWrapper_1rjym_13 flex w-fit flex-col-reverse&quot; tabindex=&quot;-1&quot;&gt;&lt;table class=&quot;w-fit min-w-(--thread-content-width)&quot; data-end=&quot;903&quot; data-start=&quot;66&quot;&gt;&lt;thead data-end=&quot;86&quot; data-start=&quot;66&quot;&gt;&lt;tr data-end=&quot;86&quot; data-start=&quot;66&quot;&gt;&lt;th data-col-size=&quot;sm&quot; data-end=&quot;75&quot; data-start=&quot;66&quot;&gt;&lt;/th&gt;&lt;th data-col-size=&quot;lg&quot; data-end=&quot;86&quot; data-start=&quot;75&quot;&gt;&lt;br /&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody data-end=&quot;903&quot; data-start=&quot;97&quot;&gt;&lt;tr data-end=&quot;213&quot; data-start=&quot;97&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;113&quot; data-start=&quot;97&quot;&gt;&lt;br /&gt;&lt;/td&gt;&lt;td data-col-size=&quot;lg&quot; data-end=&quot;213&quot; data-start=&quot;113&quot;&gt;&lt;strong data-end=&quot;112&quot; data-start=&quot;99&quot;&gt;Framework&lt;/strong&gt;&lt;br /&gt;CrewAI orchestrates multiple AI agents to search MiFID-related PDFs and draft compliance answers&lt;br /&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;321&quot; data-start=&quot;214&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;229&quot; data-start=&quot;214&quot;&gt;&lt;br /&gt;&lt;/td&gt;&lt;td data-col-size=&quot;lg&quot; data-end=&quot;321&quot; data-start=&quot;229&quot;&gt;&lt;strong data-end=&quot;228&quot; data-start=&quot;216&quot;&gt;Approach&lt;/strong&gt;&lt;br /&gt;Compare agent‑centric vs task‑centric tool assignment for reliability and predictability&lt;br /&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;453&quot; data-start=&quot;322&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;336&quot; data-start=&quot;322&quot;&gt;&lt;br /&gt;&lt;/td&gt;&lt;td data-col-size=&quot;lg&quot; data-end=&quot;453&quot; data-start=&quot;336&quot;&gt;&lt;strong data-end=&quot;335&quot; data-start=&quot;324&quot;&gt;Key Fix&lt;/strong&gt;&lt;br /&gt;Teach the language model to include the mandatory &lt;code data-end=&quot;409&quot; data-start=&quot;388&quot;&gt;{&quot;query&quot;: &quot;&amp;lt;text&amp;gt;&quot;}&lt;/code&gt; argument when calling the PDF search tool&lt;br /&gt;&lt;br /&gt;&lt;strong data-end=&quot;478&quot; data-start=&quot;456&quot;&gt;Regulatory Horizon&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;660&quot; data-start=&quot;454&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;479&quot; data-start=&quot;454&quot;&gt;&lt;br /&gt;&lt;/td&gt;&lt;td data-col-size=&quot;lg&quot; data-end=&quot;660&quot; data-start=&quot;479&quot;&gt;MiFID&amp;nbsp;III amendments enter force March&amp;nbsp;28&amp;nbsp;2024 with a main implementation deadline of September&amp;nbsp;29&amp;nbsp;2025 (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://luware.com/blog/mifid-iii-a-compliance-guide-for-financial-businesses#:~:text=9%3A12&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://luware.com/blog/mifid-iii-a-compliance-guide-for-financial-businesses#:~:text=9%3A12&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;luware.com&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://luware.com/blog/mifid-iii-a-compliance-guide-for-financial-businesses#:~:text=MiFID%20III%20follows%20a%20structured,compliance%20strategies%20with%20these%20milestones&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://luware.com/blog/mifid-iii-a-compliance-guide-for-financial-businesses#:~:text=MiFID%20III%20follows%20a%20structured,compliance%20strategies%20with%20these%20milestones&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;luware.com&lt;/span&gt;&lt;/a&gt;)&lt;br /&gt;&lt;br /&gt;&lt;table class=&quot;w-fit min-w-(--thread-content-width)&quot; data-end=&quot;903&quot; data-start=&quot;66&quot;&gt;&lt;tbody data-end=&quot;903&quot; data-start=&quot;97&quot;&gt;&lt;tr data-end=&quot;903&quot; data-start=&quot;661&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;680&quot; data-start=&quot;661&quot;&gt;&lt;strong data-end=&quot;679&quot; data-start=&quot;663&quot;&gt;New Features&lt;/strong&gt;&lt;/td&gt;&lt;td data-col-size=&quot;lg&quot; data-end=&quot;903&quot; data-start=&quot;680&quot;&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;903&quot; data-start=&quot;661&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;680&quot; data-start=&quot;661&quot;&gt;&lt;/td&gt;&lt;td data-col-size=&quot;lg&quot; data-end=&quot;903&quot; data-start=&quot;680&quot;&gt;CrewAI’s MCP standardises tool access and data sources (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=MCP%20&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=MCP%20&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;blog.crewai.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt; while guardrails and event buses increase reliability (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=Guardrails&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=Guardrails&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;blog.crewai.com&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=CrewAI%20now%20includes%20an%20event,the%20following%20events%20are%20emitted&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=CrewAI%20now%20includes%20an%20event,the%20following%20events%20are%20emitted&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;blog.crewai.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2 data-end=&quot;920&quot; data-start=&quot;905&quot;&gt;Agent‑centric vs Task‑centric Tool Assignment&lt;/h2&gt;&lt;p data-end=&quot;2466&quot; data-start=&quot;2261&quot;&gt;CrewAI allows tools—such as a PDF searcher or a web search API—to be provided either to the &lt;em data-end=&quot;2360&quot; data-start=&quot;2353&quot;&gt;agent&lt;/em&gt; as a global toolbox or to individual &lt;em data-end=&quot;2405&quot; data-start=&quot;2398&quot;&gt;tasks&lt;/em&gt;.  These two patterns affect reliability and maintainability.&lt;/p&gt;&lt;h3 data-end=&quot;2498&quot; data-start=&quot;2468&quot;&gt;Agent‑centric (Generalist)&lt;/h3&gt;&lt;p data-end=&quot;2926&quot; data-start=&quot;2500&quot;&gt;In this common setup the agent receives a list of tools that it may call at any time.  The agent decides which tool to use based on the prompt and its internal reasoning.  This is flexible and quick to prototype, but it can lead to mis‑calls.  For example, the PDF search tool expects a JSON argument with a &lt;code data-end=&quot;2815&quot; data-start=&quot;2808&quot;&gt;query&lt;/code&gt; string.  Without explicit instructions, the LLM may omit the argument, leading to the familiar Pydantic error:&lt;/p&gt;&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;2994&quot; data-start=&quot;2928&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre!&quot;&gt;&lt;span class=&quot;hljs-title class_&quot;&gt;Error&lt;/span&gt;: &lt;span class=&quot;hljs-title class_&quot;&gt;Arguments&lt;/span&gt; validation &lt;span class=&quot;hljs-attr&quot;&gt;failed&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;`query`&lt;/span&gt; field required
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;p data-end=&quot;3262&quot; data-start=&quot;2996&quot;&gt;When the agent owns the entire toolbox, every task must encode tool‑calling rules in its prompt to avoid such errors.  In our experience this reduces predictability as the system grows; even seasoned engineers forget to repeat the tool signature in each description.&lt;/p&gt;&lt;h3 data-end=&quot;3293&quot; data-start=&quot;3264&quot;&gt;Task‑centric (Specialist)&lt;/h3&gt;&lt;p data-end=&quot;3632&quot; data-start=&quot;3295&quot;&gt;The task‑centric approach grants tools &lt;strong data-end=&quot;3342&quot; data-start=&quot;3334&quot;&gt;only&lt;/strong&gt; to the tasks that need them.  When the agent begins a task it is temporarily given the required tools and cannot access others.  This design leads to more predictable tool calls because the agent knows &lt;em data-end=&quot;3552&quot; data-start=&quot;3545&quot;&gt;which&lt;/em&gt; tools are available for that job and the prompt can focus on &lt;em data-end=&quot;3619&quot; data-start=&quot;3614&quot;&gt;how&lt;/em&gt; to use them.&lt;/p&gt;&lt;p data-end=&quot;3947&quot; data-start=&quot;3634&quot;&gt;In regulated domains this scoping is critical.  It reduces the chance that a general‑purpose LLM will call a web search when only document evidence is allowed, and it makes unit testing straightforward: each task can be executed in isolation with its own tools and guardrails (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=Guardrails&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=Guardrails&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;blog.crewai.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;&lt;h3 data-end=&quot;3966&quot; data-start=&quot;3949&quot;&gt;Pros and Cons&lt;/h3&gt;&lt;ul data-end=&quot;4452&quot; data-start=&quot;3968&quot;&gt;
&lt;li data-end=&quot;4207&quot; data-start=&quot;3968&quot;&gt;
&lt;p data-end=&quot;3990&quot; data-start=&quot;3970&quot;&gt;&lt;strong data-end=&quot;3987&quot; data-start=&quot;3970&quot;&gt;Agent‑centric&lt;/strong&gt;:&lt;/p&gt;
&lt;ul data-end=&quot;4207&quot; data-start=&quot;3993&quot;&gt;
&lt;li data-end=&quot;4079&quot; data-start=&quot;3993&quot;&gt;
&lt;p data-end=&quot;4079&quot; data-start=&quot;3995&quot;&gt;&lt;em data-end=&quot;4001&quot; data-start=&quot;3995&quot;&gt;Pros&lt;/em&gt;: Simple configuration; fewer moving parts; agent decides which tool to use.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;4207&quot; data-start=&quot;4082&quot;&gt;
&lt;p data-end=&quot;4207&quot; data-start=&quot;4084&quot;&gt;&lt;em data-end=&quot;4090&quot; data-start=&quot;4084&quot;&gt;Cons&lt;/em&gt;: LLM may mis‑call tools without guidance; prompts must repeat tool signatures; difficult to test tasks in isolation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;4452&quot; data-start=&quot;4208&quot;&gt;
&lt;p data-end=&quot;4229&quot; data-start=&quot;4210&quot;&gt;&lt;strong data-end=&quot;4226&quot; data-start=&quot;4210&quot;&gt;Task‑centric&lt;/strong&gt;:&lt;/p&gt;
&lt;ul data-end=&quot;4452&quot; data-start=&quot;4232&quot;&gt;
&lt;li data-end=&quot;4367&quot; data-start=&quot;4232&quot;&gt;
&lt;p data-end=&quot;4367&quot; data-start=&quot;4234&quot;&gt;&lt;em data-end=&quot;4240&quot; data-start=&quot;4234&quot;&gt;Pros&lt;/em&gt;: Predictable and secure; tools scoped to tasks; easier to unit‑test and update; better adherence to compliance restrictions.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;4452&quot; data-start=&quot;4370&quot;&gt;
&lt;p data-end=&quot;4452&quot; data-start=&quot;4372&quot;&gt;&lt;em data-end=&quot;4378&quot; data-start=&quot;4372&quot;&gt;Cons&lt;/em&gt;: Slightly more verbose setup; requires careful mapping of tasks to tools.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;h2 data-end=&quot;4473&quot; data-start=&quot;4454&quot;&gt;Code Walkthrough&lt;/h2&gt;&lt;p data-end=&quot;4735&quot; data-start=&quot;4475&quot;&gt;Let’s examine the core elements of the chatbot.  The code below instantiates an LLM, sets up PDF search tools for each regulatory document, defines an agent with a system prompt and tool‑calling guide, and creates two tasks orchestrated sequentially by a crew. Code can be found here: (&lt;a href=&quot;https://github.com/JordiCorbilla/langgraph-cookbook/tree/main/09%20-%20Mifid%20CrewAI%20Chatbot&quot;&gt;langgraph-cookbook/09 - Mifid CrewAI Chatbot at main · JordiCorbilla/langgraph-cookbook&lt;/a&gt;)&lt;/p&gt;&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;7152&quot; data-start=&quot;4737&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; crewai &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; Agent, Task, Crew, Process
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; crewai &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; LLM
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; crewai_tools &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; PDFSearchTool, SerperDevTool

llm = LLM(model=&lt;span class=&quot;hljs-string&quot;&gt;&quot;openai/gpt-4o-mini&quot;&lt;/span&gt;)

web_search_tool = SerperDevTool()

pdf_search_tool = PDFSearchTool(
    pdf=&lt;span class=&quot;hljs-string&quot;&gt;&quot;https://www.fca.org.uk/publication/policy/ps17-14.pdf&quot;&lt;/span&gt;,
    config={ &lt;span class=&quot;hljs-string&quot;&gt;&#39;embedder&#39;&lt;/span&gt;: { &lt;span class=&quot;hljs-string&quot;&gt;&#39;provider&#39;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&#39;huggingface&#39;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&#39;config&#39;&lt;/span&gt;: { &lt;span class=&quot;hljs-string&quot;&gt;&#39;model&#39;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&#39;sentence-transformers/all-MiniLM-L6-v2&#39;&lt;/span&gt; } } }
)
pdf_search_tool2 = PDFSearchTool(
    pdf=&lt;span class=&quot;hljs-string&quot;&gt;&quot;https://www.fca.org.uk/publication/consultation/cp16-19.pdf&quot;&lt;/span&gt;,
    config={ ... }
)
pdf_search_tool3 = PDFSearchTool(
    pdf=&lt;span class=&quot;hljs-string&quot;&gt;&quot;https://www.esma.europa.eu/sites/default/files/library/2016-1452_guidelines_mifid_ii_transaction_reporting.pdf&quot;&lt;/span&gt;,
    config={ ... }
)

COMPLIANCE_SYSTEM_PROMPT = &lt;span class=&quot;hljs-string&quot;&gt;&quot;&quot;&quot;
You are a MiFID II / MiFIR compliance analyst. Answer ONLY with information grounded in the provided PDFs.  Always include article‑level citations.  Prefer the latest ESMA RTS for conflicts.  No speculation.
&quot;&quot;&quot;&lt;/span&gt;

CITATION_FORMAT_PROMPT = &lt;span class=&quot;hljs-string&quot;&gt;&quot;&quot;&quot;
Format citations as markdown footnotes: [MiFID II&amp;nbsp;Art&amp;nbsp;X(Y)](SOURCE_URL#page=?), [MiFIR&amp;nbsp;Art&amp;nbsp;X(Y)](SOURCE_URL#page=?).
&quot;&quot;&quot;&lt;/span&gt;

TOOL_CALLING_GUIDE = &lt;span class=&quot;hljs-string&quot;&gt;&quot;&quot;&quot;
When using &quot;Search a PDF&#39;s content&quot;, you MUST pass a JSON argument like {&quot;query&quot;: &quot;&amp;lt;short search string&amp;gt;&quot;}.  Never omit &#39;query&#39;.
&quot;&quot;&quot;&lt;/span&gt;

agent_centric_agent = Agent(
    role=&lt;span class=&quot;hljs-string&quot;&gt;&quot;MiFID Compliance Analyst&quot;&lt;/span&gt;,
    goal=&lt;span class=&quot;hljs-string&quot;&gt;&quot;Answer MiFID II / MiFIR questions with exact citations.&quot;&lt;/span&gt;,
    backstory=&lt;span class=&quot;hljs-string&quot;&gt;&quot;Seasoned regulatory analyst trained on MiFID II, MiFIR and FCA/ESMA RTS/Q&amp;amp;A.&quot;&lt;/span&gt;,
    tools=[pdf_search_tool, pdf_search_tool2, pdf_search_tool3, web_search_tool],
    llm=llm,
    system_prompt=COMPLIANCE_SYSTEM_PROMPT + CITATION_FORMAT_PROMPT + TOOL_CALLING_GUIDE
)

&lt;span class=&quot;hljs-comment&quot;&gt;# Define tasks and crew&lt;/span&gt;
agent_centric_task = Task(
    description=(
        &lt;span class=&quot;hljs-string&quot;&gt;&quot;Answer &#39;{customer_query}&#39;. First, search the PDFs using &#39;Search a PDF&#39;s content&#39; with {\&quot;query\&quot;: \&quot;&amp;lt;your search string&amp;gt;\&quot;}. Extract relevant passages and synthesise the answer.&quot;&lt;/span&gt;),
    tools=[pdf_search_tool, pdf_search_tool2, pdf_search_tool3],
    agent=agent_centric_agent
)

response_drafting_task = Task(
    description=&lt;span class=&quot;hljs-string&quot;&gt;&quot;Use the gathered information to draft a comprehensive response to &#39;{customer_query}&#39;.&quot;&lt;/span&gt;,
    agent=agent_centric_agent,
    context=[agent_centric_task]
)

task_centric_crew = Crew(
    agents=[agent_centric_agent],
    tasks=[agent_centric_task, response_drafting_task],
    process=Process.sequential
)
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;p data-end=&quot;7507&quot; data-start=&quot;7154&quot;&gt;The &lt;strong data-end=&quot;7175&quot; data-start=&quot;7158&quot;&gt;system prompt&lt;/strong&gt; instructs the agent to answer using only information from the PDFs, include citations, and avoid speculation.  We append a &lt;strong data-end=&quot;7321&quot; data-start=&quot;7299&quot;&gt;tool‑calling guide&lt;/strong&gt; that explicitly defines the JSON argument required by the &lt;code data-end=&quot;7395&quot; data-start=&quot;7380&quot;&gt;PDFSearchTool&lt;/code&gt;.  Without this guide the agent might call the tool incorrectly, causing the validation error described earlier.&lt;/p&gt;&lt;p data-end=&quot;7783&quot; data-start=&quot;7509&quot;&gt;The first task instructs the agent to search the PDFs for relevant text using a JSON query.  The second task uses the extracted passages to draft the final answer.  By assigning the PDF search tools only to the search task, we prevent accidental web queries during drafting.&lt;/p&gt;&lt;h2 data-end=&quot;7820&quot; data-start=&quot;7785&quot;&gt;Preparing for MiFID&amp;nbsp;III&lt;/h2&gt;&lt;p data-end=&quot;9451&quot; data-start=&quot;8642&quot;&gt;Our chatbot currently answers questions under MiFID&amp;nbsp;II and MiFIR.  However, regulatory evolution continues.  MiFID&amp;nbsp;III amendments were published in February&amp;nbsp;2024, entered into force in March&amp;nbsp;2024 and must be implemented by September&amp;nbsp;29&amp;nbsp;2025 (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://luware.com/blog/mifid-iii-a-compliance-guide-for-financial-businesses#:~:text=MiFID%20III%20follows%20a%20structured,compliance%20strategies%20with%20these%20milestones&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://luware.com/blog/mifid-iii-a-compliance-guide-for-financial-businesses#:~:text=MiFID%20III%20follows%20a%20structured,compliance%20strategies%20with%20these%20milestones&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;luware.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  The third iteration does not overhaul the framework but raises the bar on surveillance and inducements: compliance teams must prepare for heightened expectations around off‑channel communications and record‑keeping (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://luware.com/blog/mifid-iii-a-compliance-guide-for-financial-businesses#:~:text=9%3A12&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://luware.com/blog/mifid-iii-a-compliance-guide-for-financial-businesses#:~:text=9%3A12&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;luware.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  MiFID&amp;nbsp;III builds on MiFID&amp;nbsp;II by widening the focus to behavioural risks such as conflicts of interest and pricing opacity (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://luware.com/blog/mifid-iii-a-compliance-guide-for-financial-businesses#:~:text=communications%20that%20may%20lead%20to,auditable%20communications%20across%20all%20channels&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://luware.com/blog/mifid-iii-a-compliance-guide-for-financial-businesses#:~:text=communications%20that%20may%20lead%20to,auditable%20communications%20across%20all%20channels&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;luware.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;, and regulators expect active monitoring rather than passive archiving (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://luware.com/blog/mifid-iii-a-compliance-guide-for-financial-businesses#:~:text=The%20foundational%20requirement%20to%20record,III%E2%80%94but%20regulators%20will%20expect%20more&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://luware.com/blog/mifid-iii-a-compliance-guide-for-financial-businesses#:~:text=The%20foundational%20requirement%20to%20record,III%E2%80%94but%20regulators%20will%20expect%20more&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;luware.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;&lt;p data-end=&quot;9467&quot; data-start=&quot;9453&quot;&gt;To stay ahead:&lt;/p&gt;&lt;ul data-end=&quot;10554&quot; data-start=&quot;9469&quot;&gt;
&lt;li data-end=&quot;9648&quot; data-start=&quot;9469&quot;&gt;
&lt;p data-end=&quot;9648&quot; data-start=&quot;9471&quot;&gt;&lt;strong data-end=&quot;9509&quot; data-start=&quot;9471&quot;&gt;Integrate multi‑channel monitoring&lt;/strong&gt;: capture voice, chat, video and messaging across platforms; apply AI to detect misconduct patterns (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://luware.com/blog/mifid-iii-a-compliance-guide-for-financial-businesses#:~:text=2,requirements&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://luware.com/blog/mifid-iii-a-compliance-guide-for-financial-businesses#:~:text=2,requirements&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;luware.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;9769&quot; data-start=&quot;9649&quot;&gt;
&lt;p data-end=&quot;9769&quot; data-start=&quot;9651&quot;&gt;&lt;strong data-end=&quot;9680&quot; data-start=&quot;9651&quot;&gt;Extend the knowledge base&lt;/strong&gt;: ingest ESMA Q&amp;amp;As, RTS documents and MiFID&amp;nbsp;III draft texts; update embeddings regularly.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;9932&quot; data-start=&quot;9770&quot;&gt;
&lt;p data-end=&quot;9932&quot; data-start=&quot;9772&quot;&gt;&lt;strong data-end=&quot;9799&quot; data-start=&quot;9772&quot;&gt;Use CrewAI’s guardrails&lt;/strong&gt;: implement output‑length checks or hallucination detection to ensure answers remain grounded (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=Guardrails&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=Guardrails&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;blog.crewai.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;10132&quot; data-start=&quot;9933&quot;&gt;
&lt;p data-end=&quot;10132&quot; data-start=&quot;9935&quot;&gt;&lt;strong data-end=&quot;9959&quot; data-start=&quot;9935&quot;&gt;Leverage event buses&lt;/strong&gt; for audit logs and compliance evidence: CrewAI’s event bus emits events such as task started, tool use error, and LLM call completed (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=CrewAI%20now%20includes%20an%20event,the%20following%20events%20are%20emitted&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=CrewAI%20now%20includes%20an%20event,the%20following%20events%20are%20emitted&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;blog.crewai.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;10369&quot; data-start=&quot;10133&quot;&gt;
&lt;p data-end=&quot;10369&quot; data-start=&quot;10135&quot;&gt;&lt;strong data-end=&quot;10160&quot; data-start=&quot;10135&quot;&gt;Adopt query rewriting&lt;/strong&gt; and &lt;em data-end=&quot;10178&quot; data-start=&quot;10165&quot;&gt;agentic RAG&lt;/em&gt; to improve retrieval.  CrewAI now supports query rewriting to focus on relevant keywords and integrates with vector databases like Qdrant and Weaviate (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=Agentic%20RAG&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=Agentic%20RAG&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;blog.crewai.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;10554&quot; data-start=&quot;10370&quot;&gt;
&lt;p data-end=&quot;10554&quot; data-start=&quot;10372&quot;&gt;&lt;strong data-end=&quot;10394&quot; data-start=&quot;10372&quot;&gt;Plan for MiFID&amp;nbsp;III&lt;/strong&gt; deadlines: ensure your system can adapt to new PFOF bans, stricter inducement rules, and expanded communication capture (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://luware.com/blog/mifid-iii-a-compliance-guide-for-financial-businesses#:~:text=communications%20that%20may%20lead%20to,auditable%20communications%20across%20all%20channels&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://luware.com/blog/mifid-iii-a-compliance-guide-for-financial-businesses#:~:text=communications%20that%20may%20lead%20to,auditable%20communications%20across%20all%20channels&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;luware.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;h2 data-end=&quot;10594&quot; data-start=&quot;10556&quot;&gt;Beyond Compliance – General Lessons&lt;/h2&gt;&lt;p data-end=&quot;10698&quot; data-start=&quot;10596&quot;&gt;While this capstone project addresses a specific regulatory domain, the design patterns are broadly applicable:&lt;/p&gt;&lt;ol data-end=&quot;11856&quot; data-start=&quot;10700&quot;&gt;
&lt;li data-end=&quot;10941&quot; data-start=&quot;10700&quot;&gt;
&lt;p data-end=&quot;10941&quot; data-start=&quot;10703&quot;&gt;&lt;strong data-end=&quot;10740&quot; data-start=&quot;10703&quot;&gt;Be explicit about tool signatures&lt;/strong&gt;: LLMs cannot infer parameter names; include examples and constraints in prompts.  For production systems, consider using structured tool calling (JSON Schema) or wrappers that handle input validation.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;11105&quot; data-start=&quot;10942&quot;&gt;
&lt;p data-end=&quot;11105&quot; data-start=&quot;10945&quot;&gt;&lt;strong data-end=&quot;10969&quot; data-start=&quot;10945&quot;&gt;Scope tools to tasks&lt;/strong&gt;: Minimises misuse and aids debugging.  For high‑impact domains (finance, healthcare) this can be critical for legal and safety reasons.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;11305&quot; data-start=&quot;11106&quot;&gt;
&lt;p data-end=&quot;11305&quot; data-start=&quot;11109&quot;&gt;&lt;strong data-end=&quot;11135&quot; data-start=&quot;11109&quot;&gt;Instrument your agents&lt;/strong&gt;: Use CrewAI’s event bus to collect telemetry on tool use and LLM calls.  This data helps debug issues and demonstrates compliance (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=CrewAI%20now%20includes%20an%20event,the%20following%20events%20are%20emitted&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=CrewAI%20now%20includes%20an%20event,the%20following%20events%20are%20emitted&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;blog.crewai.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;11590&quot; data-start=&quot;11306&quot;&gt;
&lt;p data-end=&quot;11590&quot; data-start=&quot;11309&quot;&gt;&lt;strong data-end=&quot;11328&quot; data-start=&quot;11309&quot;&gt;Stay up to date&lt;/strong&gt;: Regulations change.  Regularly ingest new documents and search the web to supplement your knowledge base.  MiFID&amp;nbsp;III emphasises behaviour monitoring, so your system must adapt to detect tone and sentiment across channels (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://luware.com/blog/mifid-iii-a-compliance-guide-for-financial-businesses#:~:text=2,requirements&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://luware.com/blog/mifid-iii-a-compliance-guide-for-financial-businesses#:~:text=2,requirements&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;luware.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;11856&quot; data-start=&quot;11591&quot;&gt;
&lt;p data-end=&quot;11856&quot; data-start=&quot;11594&quot;&gt;&lt;strong data-end=&quot;11624&quot; data-start=&quot;11594&quot;&gt;Embrace guardrails and RAG&lt;/strong&gt;: Guardrails catch excessive or irrelevant outputs; agentic RAG (retrieval augmented generation) tailors the knowledge retrieval to each query, increasing precision and reducing hallucinations (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=Agentic%20RAG&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=Agentic%20RAG&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;blog.crewai.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;h2 data-end=&quot;11871&quot; data-start=&quot;11858&quot;&gt;Conclusion&lt;/h2&gt;&lt;p data-end=&quot;12430&quot; data-start=&quot;11873&quot;&gt;By combining CrewAI’s multi‑agent orchestration with dedicated tools for PDF and web search, we built a robust MiFID&amp;nbsp;II compliance chatbot.  Assigning tools at the &lt;strong data-end=&quot;12045&quot; data-start=&quot;12037&quot;&gt;task&lt;/strong&gt; level rather than the &lt;strong data-end=&quot;12077&quot; data-start=&quot;12068&quot;&gt;agent&lt;/strong&gt; level yields more predictable and testable workflows.  The critical missing‑&lt;code data-end=&quot;12161&quot; data-start=&quot;12154&quot;&gt;query&lt;/code&gt; error was resolved by teaching the LLM to call tools correctly.  Looking ahead, MiFID&amp;nbsp;III will demand even greater surveillance and behavioural oversight; our modular architecture can adapt by adding new documents, query‑rewriting agents, guardrails and event logging.&lt;/p&gt;&lt;p data-end=&quot;2068&quot; data-start=&quot;1654&quot;&gt;




























&lt;/p&gt;&lt;p data-end=&quot;12881&quot; data-start=&quot;12432&quot;&gt;Agentic AI is evolving rapidly.  CrewAI’s adoption of the MCP, guardrails and event bus underscores this progress (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=MCP%20&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=MCP%20&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;blog.crewai.com&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=Guardrails&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=Guardrails&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;blog.crewai.com&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=CrewAI%20now%20includes%20an%20event,the%20following%20events%20are%20emitted&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://blog.crewai.com/how-crewai-is-evolving-beyond-orchestration-to-create-the-most-powerful-agentic-ai-platform/#:~:text=CrewAI%20now%20includes%20an%20event,the%20following%20events%20are%20emitted&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;blog.crewai.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  With careful design, developers can harness these advances to build trustworthy, compliance‑ready chatbots that empower experts rather than replace them.  Start small, iterate quickly, and always validate your tools.&lt;/p&gt;</description><link>https://thundaxsoftware.blogspot.com/2025/10/building-mifid-iicompliance-chatbot.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKVOPmJuVHpPEhxkDQ2Sj6ingI7wZ0Ws9zVFQcLCDR1qkPnEy1-yP-g5giG3WC1Gw2GfjRj6UJphLVY275G07DoGmsBtye8REn3q7NhHc7hJ2GGAT2E5yRsxoqRPkZqJzMlIMNCREUFrWKvAzvH1mWTstt1u_jhcK8uhl-vk6mp3dt2ChWacW2azK5b34/s72-c/ChatGPT%20Image%20Oct%2010,%202025,%2007_11_05%20PM.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-8738696650963479179</guid><pubDate>Sat, 04 Oct 2025 19:04:00 +0000</pubDate><atom:updated>2026-03-15T21:42:17.275+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Compliance Automation</category><category domain="http://www.blogger.com/atom/ns#">Financial Regulation</category><category domain="http://www.blogger.com/atom/ns#">MiFID II/III Updates</category><category domain="http://www.blogger.com/atom/ns#">Multi-Agent AI (CrewAI)</category><category domain="http://www.blogger.com/atom/ns#">Near Real-Time Reporting</category><category domain="http://www.blogger.com/atom/ns#">PFOF Ban &amp; Consolidated Tape</category><title>Automating MiFID Compliance with CrewAI</title><description>&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOr8-FWpQA-zYy0LaIni5kpGsvWvvr5pu0pAnfxR0W6DpjSKQTBURDXutYc5PPF013_tRLxzdjvLIinrNfwbd8yC7-ZyePvGFjHvJ3JM9Z_lU_rcMOlrbgPdl4ijPc70ah2pN1kUVP_TbRWKWkNr9NpP4U7YAOvE_h2l0RDgcj3tL_hQBfpgp42JRFG0U/s1024/59aa09fa-33f8-4483-a4f9-78c2723cdbdd.png&quot; style=&quot;display: inline; margin-left: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOr8-FWpQA-zYy0LaIni5kpGsvWvvr5pu0pAnfxR0W6DpjSKQTBURDXutYc5PPF013_tRLxzdjvLIinrNfwbd8yC7-ZyePvGFjHvJ3JM9Z_lU_rcMOlrbgPdl4ijPc70ah2pN1kUVP_TbRWKWkNr9NpP4U7YAOvE_h2l0RDgcj3tL_hQBfpgp42JRFG0U/s320/59aa09fa-33f8-4483-a4f9-78c2723cdbdd.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;h1 style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/h1&gt;&lt;p&gt;&lt;/p&gt;
&lt;div class=&quot;_tableContainer_1rjym_1&quot;&gt;&lt;div class=&quot;group _tableWrapper_1rjym_13 flex w-fit flex-col-reverse&quot; tabindex=&quot;-1&quot;&gt;&lt;table class=&quot;w-fit min-w-(--thread-content-width)&quot; data-end=&quot;2287&quot; data-start=&quot;43&quot;&gt;&lt;thead data-end=&quot;68&quot; data-start=&quot;43&quot;&gt;&lt;/thead&gt;&lt;tbody data-end=&quot;2287&quot; data-start=&quot;79&quot;&gt;&lt;tr data-end=&quot;618&quot; data-start=&quot;79&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;104&quot; data-start=&quot;79&quot;&gt;&lt;br /&gt;&lt;/td&gt;&lt;td data-col-size=&quot;xl&quot; data-end=&quot;618&quot; data-start=&quot;104&quot;&gt;&lt;strong data-end=&quot;103&quot; data-start=&quot;81&quot;&gt;Regulatory context&lt;/strong&gt;&lt;br /&gt;The MiFID II/III reviews introduce a single 7&amp;nbsp;% dark‑trading volume cap, ban payment for order flow (PFOF) and require Member States to ensure data‑quality standards for consolidated tape providers (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://legal.pwc.de/en/news/articles/mifir-mifid-ii-review-making-sense-of-the-key-amendments#:~:text=In%20more%20detail%2C%20the%20Review%3A&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://legal.pwc.de/en/news/articles/mifir-mifid-ii-review-making-sense-of-the-key-amendments#:~:text=In%20more%20detail%2C%20the%20Review%3A&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;legal.pwc.de&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  MiFID&amp;nbsp;III mandates extended data fields and near‑real‑time reporting by September&amp;nbsp;2025 (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=September%202025%20marks%20the%20deadline,authorities%20in%20near%20real%20time&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=September%202025%20marks%20the%20deadline,authorities%20in%20near%20real%20time&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;asctechnologies.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  PFOF will be fully banned and a consolidated tape for equities, ETFs and derivatives will launch in June&amp;nbsp;2026 (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=In%20June%202026%2C%20the%20PFOF,for%20routing%20customer%20orders%20there&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=In%20June%202026%2C%20the%20PFOF,for%20routing%20customer%20orders%20there&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;asctechnologies.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;&lt;strong data-end=&quot;638&quot; data-start=&quot;621&quot;&gt;Key deadlines&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;987&quot; data-start=&quot;619&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;639&quot; data-start=&quot;619&quot;&gt;&lt;br /&gt;&lt;/td&gt;&lt;td data-col-size=&quot;xl&quot; data-end=&quot;987&quot; data-start=&quot;639&quot;&gt;Member States must transpose the MiFID&amp;nbsp;II/MiFIR review into national law by 28&amp;nbsp;September&amp;nbsp;2025 (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://legal.pwc.de/en/news/articles/mifir-mifid-ii-review-making-sense-of-the-key-amendments#:~:text=1,into%20force%20in%20late%202025&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://legal.pwc.de/en/news/articles/mifir-mifid-ii-review-making-sense-of-the-key-amendments#:~:text=1,into%20force%20in%20late%202025&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;legal.pwc.de&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  September&amp;nbsp;2025 is also the deadline for MiFID&amp;nbsp;III reporting obligations (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=September%202025%20marks%20the%20deadline,authorities%20in%20near%20real%20time&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=September%202025%20marks%20the%20deadline,authorities%20in%20near%20real%20time&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;asctechnologies.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;; the PFOF ban and consolidated tape come into force in June&amp;nbsp;2026 (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=In%20June%202026%2C%20the%20PFOF,for%20routing%20customer%20orders%20there&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=In%20June%202026%2C%20the%20PFOF,for%20routing%20customer%20orders%20there&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;asctechnologies.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;&lt;strong data-end=&quot;1004&quot; data-start=&quot;990&quot;&gt;Challenges&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;1517&quot; data-start=&quot;988&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1005&quot; data-start=&quot;988&quot;&gt;&lt;br /&gt;&lt;/td&gt;&lt;td data-col-size=&quot;xl&quot; data-end=&quot;1517&quot; data-start=&quot;1005&quot;&gt;Firms face increased operational complexity: they must implement a single‑volume cap, monitor dark trading, ensure data quality for consolidated tapes and update policies and procedures (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://legal.pwc.de/en/news/articles/mifir-mifid-ii-review-making-sense-of-the-key-amendments&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://legal.pwc.de/en/news/articles/mifir-mifid-ii-review-making-sense-of-the-key-amendments&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;legal.pwc.de&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  Near‑real‑time reporting and expanded data fields demand modern IT infrastructure (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=Extended%20reporting%20requirements%20and%20more,control%20by%20ESMA&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=Extended%20reporting%20requirements%20and%20more,control%20by%20ESMA&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;asctechnologies.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  All customer conversations across channels must be recorded and analysed for misconduct, requiring AI‑powered risk detection (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=Impact%20on%20recording%20requirements&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=Impact%20on%20recording%20requirements&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;asctechnologies.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;&lt;strong data-end=&quot;1537&quot; data-start=&quot;1520&quot;&gt;Opportunities&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;1917&quot; data-start=&quot;1518&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1538&quot; data-start=&quot;1518&quot;&gt;&lt;br /&gt;&lt;/td&gt;&lt;td data-col-size=&quot;xl&quot; data-end=&quot;1917&quot; data-start=&quot;1538&quot;&gt;Greater transparency and harmonised data help investors and regulators.  The EU‑wide consolidated tape offers real‑time market data (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=Kategorie%20MiFID%20II%20,for%20data%20and%20volume%20violations&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=Kategorie%20MiFID%20II%20,for%20data%20and%20volume%20violations&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;asctechnologies.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  AI and automation can transform compliance from a cost centre into a strategic differentiator: multi‑agent systems can handle large volumes of data, detect compliance risks and produce accessible reports.&lt;br /&gt;&lt;br /&gt;&lt;strong data-end=&quot;1943&quot; data-start=&quot;1920&quot;&gt;Automation approach&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;2287&quot; data-start=&quot;1918&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1944&quot; data-start=&quot;1918&quot;&gt;&lt;br /&gt;&lt;/td&gt;&lt;td data-col-size=&quot;xl&quot; data-end=&quot;2287&quot; data-start=&quot;1944&quot;&gt;CrewAI is a framework for building teams of autonomous agents that work together.  A research agent gathers the latest MiFID updates using web‑search tools and summarises them; a writer agent turns this research into an engaging blog post.  Tasks are executed sequentially, ensuring that the output of research feeds directly into writing.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 data-end=&quot;2316&quot; data-start=&quot;2289&quot;&gt;Why MiFID Updates Matter&lt;/h2&gt;
&lt;p data-end=&quot;2953&quot; data-start=&quot;2318&quot;&gt;MiFID&amp;nbsp;II was the EU’s flagship reform for securities markets.  Seven years after its introduction, financial markets look very different: algorithmic trading, retail investing apps and AI‑assisted research have become mainstream.  The European Commission therefore adopted a &lt;strong data-end=&quot;2618&quot; data-start=&quot;2593&quot;&gt;MiFID&amp;nbsp;II/MiFIR review&lt;/strong&gt; in February&amp;nbsp;2024.  The review aims to boost transparency and investor protection by removing redundant provisions, creating a consolidated tape and banning PFOF (payment&amp;nbsp; for order flow) (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://legal.pwc.de/en/news/articles/mifir-mifid-ii-review-making-sense-of-the-key-amendments#:~:text=In%20more%20detail%2C%20the%20Review%3A&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://legal.pwc.de/en/news/articles/mifir-mifid-ii-review-making-sense-of-the-key-amendments#:~:text=In%20more%20detail%2C%20the%20Review%3A&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;legal.pwc.de&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  Member States have until &lt;strong data-end=&quot;2867&quot; data-start=&quot;2846&quot;&gt;28&amp;nbsp;September&amp;nbsp;2025&lt;/strong&gt; to transpose the amendments into national law (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://legal.pwc.de/en/news/articles/mifir-mifid-ii-review-making-sense-of-the-key-amendments#:~:text=1,into%20force%20in%20late%202025&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://legal.pwc.de/en/news/articles/mifir-mifid-ii-review-making-sense-of-the-key-amendments#:~:text=1,into%20force%20in%20late%202025&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;legal.pwc.de&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;p data-end=&quot;3509&quot; data-start=&quot;2955&quot;&gt;In parallel, the so‑called &lt;strong data-end=&quot;2995&quot; data-start=&quot;2982&quot;&gt;MiFID&amp;nbsp;III&lt;/strong&gt; package—an informal term for amendments to MiFID&amp;nbsp;II and MiFIR—modernises the rulebook to reflect digitalisation.  It requires firms to document transactions in greater detail and report them to supervisory authorities in near real time (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=September%202025%20marks%20the%20deadline,authorities%20in%20near%20real%20time&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=September%202025%20marks%20the%20deadline,authorities%20in%20near%20real%20time&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;asctechnologies.com&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;), introduces a single 7&amp;nbsp;% dark‑trading cap (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=Kategorie%20MiFID%20II%20,for%20data%20and%20volume%20violations&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=Kategorie%20MiFID%20II%20,for%20data%20and%20volume%20violations&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;asctechnologies.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;, bans PFOF (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=In%20June%202026%2C%20the%20PFOF,for%20routing%20customer%20orders%20there&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=In%20June%202026%2C%20the%20PFOF,for%20routing%20customer%20orders%20there&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;asctechnologies.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt; and launches a consolidated tape for equities, ETFs and derivatives (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=In%20June%202026%2C%20the%20PFOF,for%20routing%20customer%20orders%20there&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=In%20June%202026%2C%20the%20PFOF,for%20routing%20customer%20orders%20there&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;asctechnologies.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;p data-end=&quot;3961&quot; data-start=&quot;3511&quot;&gt;These changes present both challenges and opportunities for financial institutions.  On the one hand, compliance will demand investments in data governance, monitoring tools and process redesign (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://legal.pwc.de/en/news/articles/mifir-mifid-ii-review-making-sense-of-the-key-amendments&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://legal.pwc.de/en/news/articles/mifir-mifid-ii-review-making-sense-of-the-key-amendments&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;legal.pwc.de&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  On the other hand, harmonised data and advanced analytics can enhance risk management and investor confidence.  Adopting &lt;strong data-end=&quot;3894&quot; data-start=&quot;3868&quot;&gt;multi‑agent AI systems&lt;/strong&gt; such as CrewAI can help firms navigate this landscape efficiently.&lt;/p&gt;
&lt;h2 data-end=&quot;4014&quot; data-start=&quot;3963&quot;&gt;Building a Multi‑Agent MiFID&amp;nbsp;Compliance Workflow&lt;/h2&gt;
&lt;p data-end=&quot;4358&quot; data-start=&quot;4016&quot;&gt;The traditional approach to regulatory change management involves human analysts reading lengthy legislative texts, summarising them and drafting internal or external communications.  This process is time‑consuming and error‑prone.  &lt;strong data-end=&quot;4259&quot; data-start=&quot;4249&quot;&gt;CrewAI&lt;/strong&gt; offers an alternative: you can orchestrate multiple AI agents to collaborate on complex workflows.&lt;/p&gt;
&lt;p data-end=&quot;4445&quot; data-start=&quot;4360&quot;&gt;Below is a high‑level example of how to automate MiFID&amp;nbsp;research and content creation:&lt;/p&gt;
&lt;ol data-end=&quot;5625&quot; data-start=&quot;4447&quot;&gt;
&lt;li data-end=&quot;4987&quot; data-start=&quot;4447&quot;&gt;
&lt;p data-end=&quot;4987&quot; data-start=&quot;4450&quot;&gt;&lt;strong data-end=&quot;4468&quot; data-start=&quot;4450&quot;&gt;Research agent&lt;/strong&gt; – A specialist agent queries real‑time web search tools (such as Serper) for the latest MiFID&amp;nbsp;II/III developments.  It extracts key facts—deadlines, major amendments, compliance obligations—and generates a structured report.  The report highlights the 28&amp;nbsp;September&amp;nbsp;2025 deadline for transposition (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://legal.pwc.de/en/news/articles/mifir-mifid-ii-review-making-sense-of-the-key-amendments#:~:text=1,into%20force%20in%20late%202025&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://legal.pwc.de/en/news/articles/mifir-mifid-ii-review-making-sense-of-the-key-amendments#:~:text=1,into%20force%20in%20late%202025&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;legal.pwc.de&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;, the September&amp;nbsp;2025 start of near‑real‑time reporting (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=September%202025%20marks%20the%20deadline,authorities%20in%20near%20real%20time&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=September%202025%20marks%20the%20deadline,authorities%20in%20near%20real%20time&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;asctechnologies.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;, and the June&amp;nbsp;2026 PFOF ban and consolidated tape (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=In%20June%202026%2C%20the%20PFOF,for%20routing%20customer%20orders%20there&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=In%20June%202026%2C%20the%20PFOF,for%20routing%20customer%20orders%20there&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;asctechnologies.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;5416&quot; data-start=&quot;4988&quot;&gt;
&lt;p data-end=&quot;5416&quot; data-start=&quot;4991&quot;&gt;&lt;strong data-end=&quot;5013&quot; data-start=&quot;4991&quot;&gt;Content strategist&lt;/strong&gt; – This agent receives the research report and crafts a reader‑friendly article.  It explains why the single‑volume cap and consolidated tape matter, discusses the pros and cons of the new regime, and includes practical advice for compliance teams.  It also emphasises how AI and automation can help firms handle increased data volumes and monitoring requirements (&lt;span data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=Impact%20on%20recording%20requirements&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.asctechnologies.com/blog/post/mifid-iii-regulatory-changes-and-investor-protection-in-capital-markets/#:~:text=Impact%20on%20recording%20requirements&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;asctechnologies.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;5625&quot; data-start=&quot;5417&quot;&gt;
&lt;p data-end=&quot;5625&quot; data-start=&quot;5420&quot;&gt;&lt;strong data-end=&quot;5446&quot; data-start=&quot;5420&quot;&gt;Workflow orchestration&lt;/strong&gt; – A Crew object ties these agents and tasks together in a sequential process.  The research task must finish before the writing task begins, ensuring that the output is coherent.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-end=&quot;5652&quot; data-start=&quot;5627&quot;&gt;Sample Implementation&lt;/h3&gt;
&lt;p data-end=&quot;5881&quot; data-start=&quot;5654&quot;&gt;Below is a simplified Python script (see &lt;code data-end=&quot;5720&quot; data-start=&quot;5695&quot;&gt;&lt;a href=&quot;https://github.com/JordiCorbilla/langgraph-cookbook/tree/main/08%20-%20Mifid%20CrewAI%20Agent&quot;&gt;crewai_mifid_example.py&lt;/a&gt;&lt;/code&gt;) that demonstrates this workflow.  It uses the CrewAI framework and assumes you have set a valid Serper API key and configured a supported large language model.&lt;/p&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;7606&quot; data-start=&quot;5883&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; os
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; crewai &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; Agent, Task, Crew, Process, LLM
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; crewai_tools &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; SerperDevTool

&lt;span class=&quot;hljs-comment&quot;&gt;# Set your Serper API key&lt;/span&gt;
os.environ[&lt;span class=&quot;hljs-string&quot;&gt;&#39;SERPER_API_KEY&#39;&lt;/span&gt;] = &lt;span class=&quot;hljs-string&quot;&gt;&#39;your_serper_api_key&#39;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;# Configure your LLM&lt;/span&gt;
llm = LLM(
    model=&lt;span class=&quot;hljs-string&quot;&gt;&quot;gtp-4&quot;&lt;/span&gt;,
    max_tokens=&lt;span class=&quot;hljs-number&quot;&gt;2000&lt;/span&gt;,
)

&lt;span class=&quot;hljs-comment&quot;&gt;# Define the research agent&lt;/span&gt;
research_agent = Agent(
    role=&lt;span class=&quot;hljs-string&quot;&gt;&quot;MiFID Research Analyst&quot;&lt;/span&gt;,
    goal=&lt;span class=&quot;hljs-string&quot;&gt;&quot;Investigate and summarize recent MiFID II and III updates&quot;&lt;/span&gt;,
    backstory=&lt;span class=&quot;hljs-string&quot;&gt;&quot;Expert in EU financial regulation&quot;&lt;/span&gt;,
    verbose=&lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;,
    allow_delegation=&lt;span class=&quot;hljs-literal&quot;&gt;False&lt;/span&gt;,
    llm=llm,
    tools=[SerperDevTool()],
)

&lt;span class=&quot;hljs-comment&quot;&gt;# Define the writer agent&lt;/span&gt;
writer_agent = Agent(
    role=&lt;span class=&quot;hljs-string&quot;&gt;&quot;RegTech Content Strategist&quot;&lt;/span&gt;,
    goal=&lt;span class=&quot;hljs-string&quot;&gt;&quot;Write an engaging article about MiFID updates and AI compliance&quot;&lt;/span&gt;,
    backstory=&lt;span class=&quot;hljs-string&quot;&gt;&quot;Skilled communicator for tech audiences&quot;&lt;/span&gt;,
    verbose=&lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;,
    allow_delegation=&lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;,
    llm=llm,
)

&lt;span class=&quot;hljs-comment&quot;&gt;# Define tasks&lt;/span&gt;
research_task = Task(
    description=&lt;span class=&quot;hljs-string&quot;&gt;&quot;Gather and analyse MiFID II/III deadlines, reporting requirements and PFOF ban.&quot;&lt;/span&gt;,
    agent=research_agent,
    expected_output=&lt;span class=&quot;hljs-string&quot;&gt;&quot;A structured report on MiFID updates&quot;&lt;/span&gt;,
)
writer_task = Task(
    description=&lt;span class=&quot;hljs-string&quot;&gt;&quot;Draft a 4‑paragraph blog post based on the research.&quot;&lt;/span&gt;,
    agent=writer_agent,
    expected_output=&lt;span class=&quot;hljs-string&quot;&gt;&quot;A blog post summarising MiFID updates and the role of AI in compliance.&quot;&lt;/span&gt;,
)

crew = Crew(
    agents=[research_agent, writer_agent],
    tasks=[research_task, writer_task],
    process=Process.sequential,
    verbose=&lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;,
)

&lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; __name__ == &lt;span class=&quot;hljs-string&quot;&gt;&#39;__main__&#39;&lt;/span&gt;:
    result = crew.kickoff(inputs={&lt;span class=&quot;hljs-string&quot;&gt;&#39;topic&#39;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&#39;MiFID II and MiFID III updates 2025&#39;&lt;/span&gt;})
    &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(result)
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 data-end=&quot;7667&quot; data-start=&quot;7608&quot;&gt;Pros and Cons of Using CrewAI for Regulatory Compliance&lt;/h3&gt;
&lt;ul data-end=&quot;8950&quot; data-start=&quot;7669&quot;&gt;
&lt;li data-end=&quot;8321&quot; data-start=&quot;7669&quot;&gt;
&lt;p data-end=&quot;7679&quot; data-start=&quot;7671&quot;&gt;&lt;strong data-end=&quot;7679&quot; data-start=&quot;7671&quot;&gt;Pros&lt;/strong&gt;&lt;/p&gt;
&lt;ul data-end=&quot;8321&quot; data-start=&quot;7682&quot;&gt;
&lt;li data-end=&quot;7867&quot; data-start=&quot;7682&quot;&gt;
&lt;p data-end=&quot;7867&quot; data-start=&quot;7684&quot;&gt;&lt;em data-end=&quot;7706&quot; data-start=&quot;7684&quot;&gt;Efficiency and scale&lt;/em&gt;: AI agents can monitor regulatory updates 24/7, quickly summarising amendments like the MiFID reviews.  This reduces manual labour and speeds up response times.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;7993&quot; data-start=&quot;7870&quot;&gt;
&lt;p data-end=&quot;7993&quot; data-start=&quot;7872&quot;&gt;&lt;em data-end=&quot;7885&quot; data-start=&quot;7872&quot;&gt;Consistency&lt;/em&gt;: Automated pipelines deliver uniform analysis and writing quality, avoiding variability in human reporting.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;8159&quot; data-start=&quot;7996&quot;&gt;
&lt;p data-end=&quot;8159&quot; data-start=&quot;7998&quot;&gt;&lt;em data-end=&quot;8012&quot; data-start=&quot;7998&quot;&gt;Adaptability&lt;/em&gt;: Changing the topic (e.g., from MiFID to ESG or AI regulation) simply requires passing a different input string; no need to redesign the workflow.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;8321&quot; data-start=&quot;8162&quot;&gt;
&lt;p data-end=&quot;8321&quot; data-start=&quot;8164&quot;&gt;&lt;em data-end=&quot;8183&quot; data-start=&quot;8164&quot;&gt;Integrative power&lt;/em&gt;: Agents can be equipped with multiple tools—web search, document parsing, API connectors—allowing them to pull data from diverse sources.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;8950&quot; data-start=&quot;8323&quot;&gt;
&lt;p data-end=&quot;8333&quot; data-start=&quot;8325&quot;&gt;&lt;strong data-end=&quot;8333&quot; data-start=&quot;8325&quot;&gt;Cons&lt;/strong&gt;&lt;/p&gt;
&lt;ul data-end=&quot;8950&quot; data-start=&quot;8336&quot;&gt;
&lt;li data-end=&quot;8488&quot; data-start=&quot;8336&quot;&gt;
&lt;p data-end=&quot;8488&quot; data-start=&quot;8338&quot;&gt;&lt;em data-end=&quot;8359&quot; data-start=&quot;8338&quot;&gt;Context limitations&lt;/em&gt;: AI models may misinterpret nuanced legal language or miss implicit regulatory interactions.  Human oversight remains essential.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;8637&quot; data-start=&quot;8491&quot;&gt;
&lt;p data-end=&quot;8637&quot; data-start=&quot;8493&quot;&gt;&lt;em data-end=&quot;8518&quot; data-start=&quot;8493&quot;&gt;Data‑source constraints&lt;/em&gt;: Tools like Serper rely on publicly available information.  Some proprietary regulatory databases may be inaccessible.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;8764&quot; data-start=&quot;8640&quot;&gt;
&lt;p data-end=&quot;8764&quot; data-start=&quot;8642&quot;&gt;&lt;em data-end=&quot;8657&quot; data-start=&quot;8642&quot;&gt;Initial setup&lt;/em&gt;: Configuring secure LLM access and API keys requires careful attention to privacy and compliance policies.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;8950&quot; data-start=&quot;8767&quot;&gt;
&lt;p data-end=&quot;8950&quot; data-start=&quot;8769&quot;&gt;&lt;em data-end=&quot;8791&quot; data-start=&quot;8769&quot;&gt;Regulatory liability&lt;/em&gt;: Over‑reliance on automated interpretations could pose risks if the system outputs incorrect advice.  Always validate AI‑generated content with legal experts.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-end=&quot;8972&quot; data-start=&quot;8952&quot;&gt;Conda Environment&lt;/h2&gt;
&lt;p data-end=&quot;9110&quot; data-start=&quot;8974&quot;&gt;To reproduce the workflow locally, create a &lt;strong data-end=&quot;9039&quot; data-start=&quot;9018&quot;&gt;conda environment&lt;/strong&gt; named&amp;nbsp;&lt;code data-end=&quot;9052&quot; data-start=&quot;9046&quot;&gt;crew&lt;/code&gt; with the following specification (see &lt;code data-end=&quot;9108&quot; data-start=&quot;9091&quot;&gt;environment.yml&lt;/code&gt;):&lt;/p&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;9415&quot; data-start=&quot;9112&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;crew&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;channels:&lt;/span&gt;
  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;defaults&lt;/span&gt;
  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;conda-forge&lt;/span&gt;
&lt;span class=&quot;hljs-attr&quot;&gt;dependencies:&lt;/span&gt;
  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;python=3.10&lt;/span&gt;
  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;pip&lt;/span&gt;
  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;pip:&lt;/span&gt;
      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;crewai==0.193.2&lt;/span&gt;
      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;crewai-tools==0.38.0&lt;/span&gt;
      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;langchain==0.3.20&lt;/span&gt;
      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;langchain-community==0.3.19&lt;/span&gt;
      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;databricks-sdk==0.57.0&lt;/span&gt;
      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;jupyter&lt;/span&gt;
      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ipykernel&lt;/span&gt;
      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;openai&lt;/span&gt;
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;9445&quot; data-start=&quot;9417&quot;&gt;Create the environment with:&lt;/p&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;9514&quot; data-start=&quot;9447&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-bash&quot;&gt;conda &lt;span class=&quot;hljs-built_in&quot;&gt;env&lt;/span&gt; create -f environment.yml
conda activate crew
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;9621&quot; data-start=&quot;9516&quot;&gt;Install any additional models or API keys (e.g., for Serper or your chosen LLM) as environment variables.&lt;/p&gt;
&lt;h2 data-end=&quot;9650&quot; data-start=&quot;9623&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p data-end=&quot;10466&quot; data-start=&quot;10085&quot;&gt;The MiFID II/MiFIR review and the forthcoming MiFID&amp;nbsp;III rules represent the biggest shake‑up of EU securities regulation since 2018.  Firms must prepare for tighter reporting timelines, a 7&amp;nbsp;% dark‑trading cap and the end of PFOF.  At the same time, greater transparency and harmonised data open new opportunities for data‑driven trading strategies and improved investor protection.&lt;/p&gt;
&lt;p data-end=&quot;10874&quot; data-start=&quot;10468&quot;&gt;Leveraging frameworks like &lt;strong data-end=&quot;10505&quot; data-start=&quot;10495&quot;&gt;CrewAI&lt;/strong&gt; allows compliance teams to automate the tedious parts of regulatory change management.  By delegating research and writing tasks to specialised agents, practitioners can focus on high‑level analysis and strategic decision‑making.  As always, technology is a tool—not a substitute for legal judgement—but used wisely it can turn regulation into a competitive advantage.&lt;/p&gt;</description><link>https://thundaxsoftware.blogspot.com/2025/10/automating-mifid-compliance-with-crewai.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOr8-FWpQA-zYy0LaIni5kpGsvWvvr5pu0pAnfxR0W6DpjSKQTBURDXutYc5PPF013_tRLxzdjvLIinrNfwbd8yC7-ZyePvGFjHvJ3JM9Z_lU_rcMOlrbgPdl4ijPc70ah2pN1kUVP_TbRWKWkNr9NpP4U7YAOvE_h2l0RDgcj3tL_hQBfpgp42JRFG0U/s72-c/59aa09fa-33f8-4483-a4f9-78c2723cdbdd.png" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>London, UK</georss:featurename><georss:point>51.5072178 -0.1275862</georss:point><georss:box>23.196983963821154 -35.2838362 79.817451636178845 35.0286638</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-2904676072726619968</guid><pubDate>Sun, 28 Sep 2025 18:14:00 +0000</pubDate><atom:updated>2025-09-28T18:14:08.949+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">AI workflow</category><category domain="http://www.blogger.com/atom/ns#">dynamic portfolio</category><category domain="http://www.blogger.com/atom/ns#">fictional personas</category><category domain="http://www.blogger.com/atom/ns#">investment advisor</category><category domain="http://www.blogger.com/atom/ns#">investment strategies</category><category domain="http://www.blogger.com/atom/ns#">LLM integration</category><category domain="http://www.blogger.com/atom/ns#">orchestrator-worker pattern</category><category domain="http://www.blogger.com/atom/ns#">reflection pattern</category><category domain="http://www.blogger.com/atom/ns#">risk grades</category><title>Reflection Pattern for Investment Advisory: A Fictional Take</title><description>&lt;p&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOt6jmB2FyPTB8Xb-m6aO1etGbOAhQzU2I-XUOClZEgjEEWJbVq47-r0URqC5IjkSucUxHHGqJB4bf0s2u-tUvDDdj4LOYlST5yYZw5UMIWgNYRdKKmT2X4oZ_mnsaDp7-R9TsxgNc3XiJjJfPFRlihWNnHb9qgQywVtOM41v9FZ5tyJEESFgT1583O2Q/s1024/fe69c127-4310-47ee-8e47-436d8fd58eb1.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; display: inline !important; float: left; margin-bottom: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOt6jmB2FyPTB8Xb-m6aO1etGbOAhQzU2I-XUOClZEgjEEWJbVq47-r0URqC5IjkSucUxHHGqJB4bf0s2u-tUvDDdj4LOYlST5yYZw5UMIWgNYRdKKmT2X4oZ_mnsaDp7-R9TsxgNc3XiJjJfPFRlihWNnHb9qgQywVtOM41v9FZ5tyJEESFgT1583O2Q/w320-h320/fe69c127-4310-47ee-8e47-436d8fd58eb1.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/p&gt;&lt;h1 style=&quot;text-align: left;&quot;&gt;Reflection Pattern for Investment Advisory: A Fictional Take&lt;/h1&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;This article explores how the &lt;strong data-end=&quot;572&quot; data-start=&quot;550&quot;&gt;reflection pattern,&amp;nbsp;&lt;/strong&gt;an &lt;b&gt;agentic AI &lt;/b&gt;design pattern in which a model iteratively generates, critiques and refines its own work, can be applied to investment advisory.  We’ll introduce fictional superstar advisors inspired by pop culture, explain how the reflection loop fits within an &lt;strong data-end=&quot;857&quot; data-start=&quot;834&quot;&gt;orchestrator–worker&lt;/strong&gt; architecture, and provide a full Python implementation.  Along the way, we highlight benefits, drawbacks and speculative future applications.&lt;/p&gt;&lt;h2 data-end=&quot;86&quot; data-start=&quot;64&quot;&gt;Summary at a Glance&lt;/h2&gt;&lt;p&gt;
&lt;/p&gt;&lt;div class=&quot;_tableContainer_1rjym_1&quot;&gt;&lt;div class=&quot;group _tableWrapper_1rjym_13 flex w-fit flex-col-reverse&quot; tabindex=&quot;-1&quot;&gt;&lt;table class=&quot;w-fit min-w-(--thread-content-width)&quot; data-end=&quot;518&quot; data-start=&quot;88&quot;&gt;&lt;thead data-end=&quot;176&quot; data-start=&quot;88&quot;&gt;&lt;tr data-end=&quot;176&quot; data-start=&quot;88&quot;&gt;&lt;th data-col-size=&quot;sm&quot; data-end=&quot;98&quot; data-start=&quot;88&quot;&gt;Persona&lt;/th&gt;&lt;th data-col-size=&quot;md&quot; data-end=&quot;106&quot; data-start=&quot;98&quot;&gt;Style&lt;/th&gt;&lt;th data-col-size=&quot;md&quot; data-end=&quot;176&quot; data-start=&quot;106&quot;&gt;Typical Allocation (Equities / Alternatives / Fixed‑Income / Cash)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody data-end=&quot;518&quot; data-start=&quot;191&quot;&gt;&lt;tr data-end=&quot;294&quot; data-start=&quot;191&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;211&quot; data-start=&quot;191&quot;&gt;&lt;strong data-end=&quot;210&quot; data-start=&quot;193&quot;&gt;Bobby Axelrod&lt;/strong&gt;&lt;/td&gt;&lt;td data-col-size=&quot;md&quot; data-end=&quot;271&quot; data-start=&quot;211&quot;&gt;Aggressive hedge‑fund maverick willing to ride volatility&lt;/td&gt;&lt;td data-col-size=&quot;md&quot; data-end=&quot;294&quot; data-start=&quot;271&quot;&gt;70% / 20% / 5% / 5%&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;402&quot; data-start=&quot;295&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;314&quot; data-start=&quot;295&quot;&gt;&lt;strong data-end=&quot;313&quot; data-start=&quot;297&quot;&gt;Gordon Gekko&lt;/strong&gt;&lt;/td&gt;&lt;td data-col-size=&quot;md&quot; data-end=&quot;379&quot; data-start=&quot;314&quot;&gt;Opportunistic raider who loves leverage, options and arbitrage&lt;/td&gt;&lt;td data-col-size=&quot;md&quot; data-end=&quot;402&quot; data-start=&quot;379&quot;&gt;75% / 15% / 5% / 5%&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;518&quot; data-start=&quot;403&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;421&quot; data-start=&quot;403&quot;&gt;&lt;strong data-end=&quot;420&quot; data-start=&quot;405&quot;&gt;Richie&amp;nbsp;Rich&lt;/strong&gt;&lt;/td&gt;&lt;td data-col-size=&quot;md&quot; data-end=&quot;482&quot; data-start=&quot;421&quot;&gt;Cautious wealth‑preserver who favours blue chips and bonds&lt;/td&gt;&lt;td data-col-size=&quot;md&quot; data-end=&quot;518&quot; data-start=&quot;482&quot;&gt;30–60% / 10–20% / 15–45% / 5–15%&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2 data-end=&quot;1036&quot; data-start=&quot;1001&quot;&gt;The Reflection Pattern Explained&lt;/h2&gt;&lt;p data-end=&quot;1855&quot; data-start=&quot;1038&quot;&gt;The reflection pattern originates in cognitive science and has recently been adopted for large language models (LLMs).  The idea is simple yet powerful: rather than trusting a single draft, the model runs a &lt;strong data-end=&quot;1273&quot; data-start=&quot;1245&quot;&gt;generate–critique–refine&lt;/strong&gt; loop.  According to Analytics&amp;nbsp;Vidhya, the pattern “is a method where the model generates, critiques and refines its outputs through an iterative self‑assessment process” (&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.analyticsvidhya.com/blog/2024/10/agentic-ai-reflection-pattern/#:~:text=Overview&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.analyticsvidhya.com/blog/2024/10/agentic-ai-reflection-pattern/#:~:text=Overview&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;analyticsvidhya.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  This loop mimics human editing, catching mistakes, clarifying ambiguities and improving quality across multiple passes (&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.analyticsvidhya.com/blog/2024/10/agentic-ai-reflection-pattern/#:~:text=Overview&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.analyticsvidhya.com/blog/2024/10/agentic-ai-reflection-pattern/#:~:text=Overview&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;analyticsvidhya.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  A second LLM (or the same model with a different prompt) acts as a &lt;b&gt;critic&lt;/b&gt;, evaluating the initial output against well‑defined criteria and feeding feedback back to the generator (&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.philschmid.de/agentic-pattern#:~:text=Reflection%20Pattern&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.philschmid.de/agentic-pattern#:~:text=Reflection%20Pattern&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;philschmid.de&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;&lt;p data-end=&quot;1901&quot; data-start=&quot;1857&quot;&gt;Key steps in the reflection pattern include:&lt;/p&gt;&lt;ol data-end=&quot;2618&quot; data-start=&quot;1903&quot;&gt;
&lt;li data-end=&quot;2090&quot; data-start=&quot;1903&quot;&gt;
&lt;p data-end=&quot;2090&quot; data-start=&quot;1906&quot;&gt;&lt;strong data-end=&quot;1921&quot; data-start=&quot;1906&quot;&gt;Generation:&lt;/strong&gt; produce a first draft based on the user’s prompt.  This can be done in a zero‑shot manner, generating a candidate without examples (&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.analyticsvidhya.com/blog/2024/10/agentic-ai-reflection-pattern/#:~:text=1&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.analyticsvidhya.com/blog/2024/10/agentic-ai-reflection-pattern/#:~:text=1&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;analyticsvidhya.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;2263&quot; data-start=&quot;2091&quot;&gt;
&lt;p data-end=&quot;2263&quot; data-start=&quot;2094&quot;&gt;&lt;strong data-end=&quot;2114&quot; data-start=&quot;2094&quot;&gt;Self‑reflection:&lt;/strong&gt; the model reviews its own work, identifies errors, missing details or stylistic issues, and generates feedback (&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.analyticsvidhya.com/blog/2024/10/agentic-ai-reflection-pattern/#:~:text=The%20reflection%20step%20is%20a,This%20step%20involves&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.analyticsvidhya.com/blog/2024/10/agentic-ai-reflection-pattern/#:~:text=The%20reflection%20step%20is%20a,This%20step%20involves&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;analyticsvidhya.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;2618&quot; data-start=&quot;2264&quot;&gt;
&lt;p data-end=&quot;2618&quot; data-start=&quot;2267&quot;&gt;&lt;strong data-end=&quot;2294&quot; data-start=&quot;2267&quot;&gt;Iteration &amp;amp; refinement:&lt;/strong&gt; the feedback is incorporated into a new draft, and the cycle repeats until the output &lt;u&gt;meets quality thresholds&lt;/u&gt; or a maximum number of iterations is reached (&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://tpiros.dev/blog/building-with-reflection-a-practical-agentic-ai-workflow/#:~:text=The%20Reflection%20Loop%20Pattern&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://tpiros.dev/blog/building-with-reflection-a-practical-agentic-ai-workflow/#:~:text=The%20Reflection%20Loop%20Pattern&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;tpiros.dev&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;).&amp;nbsp; Stopping conditions prevent infinite loops and include quality targets or iteration limits (&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.analyticsvidhya.com/blog/2024/10/agentic-ai-reflection-pattern/#:~:text=,loops%20in%20the%20reflection%20process&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.analyticsvidhya.com/blog/2024/10/agentic-ai-reflection-pattern/#:~:text=,loops%20in%20the%20reflection%20process&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;analyticsvidhya.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;p data-end=&quot;3040&quot; data-start=&quot;2620&quot;&gt;Practical applications of the reflection pattern include code generation, content drafting, data analysis and decision‑making.  Because the model critiques itself, it can catch inconsistencies and gradually improve the output.  However, iterative generation consumes time and compute, and repeated prompts can induce drift or hallucination.  A disciplined stopping criterion and robust evaluation criteria are essential.&lt;/p&gt;&lt;h2 data-end=&quot;3076&quot; data-start=&quot;3042&quot;&gt;The Orchestrator–Worker Pattern&lt;/h2&gt;&lt;p data-end=&quot;3606&quot; data-start=&quot;3078&quot;&gt;The reflection loop fits naturally into an &lt;strong data-end=&quot;3157&quot; data-start=&quot;3121&quot;&gt;orchestrator–worker architecture&lt;/strong&gt;, another agentic design pattern.  In this setup, a central orchestrator decomposes a complex task into subtasks and dispatches them to specialized worker agents.  The orchestrator maintains global oversight, monitors worker performance and synthesizes partial results into a final answer (&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://dzone.com/articles/ai-agent-architectures-patterns-applications-guide#:~:text=1.%20Orchestrator&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://dzone.com/articles/ai-agent-architectures-patterns-applications-guide#:~:text=1.%20Orchestrator&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;dzone.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  Workers operate as domain‑specific experts, focusing solely on their assigned tasks (&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://dzone.com/articles/ai-agent-architectures-patterns-applications-guide#:~:text=1.%20Orchestrator&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://dzone.com/articles/ai-agent-architectures-patterns-applications-guide#:~:text=1.%20Orchestrator&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;dzone.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;&lt;p data-end=&quot;3660&quot; data-start=&quot;3608&quot;&gt;This separation of concerns brings several benefits:&lt;/p&gt;&lt;ul data-end=&quot;4243&quot; data-start=&quot;3662&quot;&gt;
&lt;li data-end=&quot;3790&quot; data-start=&quot;3662&quot;&gt;
&lt;p data-end=&quot;3790&quot; data-start=&quot;3664&quot;&gt;&lt;strong data-end=&quot;3695&quot; data-start=&quot;3664&quot;&gt;Modularity &amp;amp; extensibility:&lt;/strong&gt; new worker agents can be added without rewriting the orchestrator, making the system scalable.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;3880&quot; data-start=&quot;3791&quot;&gt;
&lt;p data-end=&quot;3880&quot; data-start=&quot;3793&quot;&gt;&lt;strong data-end=&quot;3809&quot; data-start=&quot;3793&quot;&gt;Parallelism:&lt;/strong&gt; tasks can run concurrently, reducing latency and improving throughput.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;4039&quot; data-start=&quot;3881&quot;&gt;
&lt;p data-end=&quot;4039&quot; data-start=&quot;3883&quot;&gt;&lt;strong data-end=&quot;3898&quot; data-start=&quot;3883&quot;&gt;Resilience:&lt;/strong&gt; if one worker fails, the orchestrator can still produce a meaningful result from the remaining outputs (&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://pytrick.medium.com/agentic-ai-design-pattern-orchestrator-worker-6d76ffc09f0c#:~:text=Think%20of%20the%20Orchestrator%20as,manager%20and%20Workers%20as%20specialists&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://pytrick.medium.com/agentic-ai-design-pattern-orchestrator-worker-6d76ffc09f0c#:~:text=Think%20of%20the%20Orchestrator%20as,manager%20and%20Workers%20as%20specialists&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;pytrick.medium.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;4243&quot; data-start=&quot;4040&quot;&gt;
&lt;p data-end=&quot;4243&quot; data-start=&quot;4042&quot;&gt;&lt;strong data-end=&quot;4062&quot; data-start=&quot;4042&quot;&gt;Central control:&lt;/strong&gt; centralized monitoring ensures that quality standards are enforced across workers and that fallback strategies are applied when errors occur (&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://dzone.com/articles/ai-agent-architectures-patterns-applications-guide#:~:text=1.%20Orchestrator&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://dzone.com/articles/ai-agent-architectures-patterns-applications-guide#:~:text=1.%20Orchestrator&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;dzone.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;p data-end=&quot;4880&quot; data-start=&quot;4245&quot;&gt;The orchestrator–worker pattern is particularly useful for workflows that combine planning, data retrieval, analysis and synthesis.  For example, in a technical stock analysis system, an orchestrator fetches data once, sends it to workers computing RSI, MACD and other indicators, then aggregates the signals into a report (&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://pytrick.medium.com/agentic-ai-design-pattern-orchestrator-worker-6d76ffc09f0c#:~:text=Think%20of%20the%20Orchestrator%20as,manager%20and%20Workers%20as%20specialists&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://pytrick.medium.com/agentic-ai-design-pattern-orchestrator-worker-6d76ffc09f0c#:~:text=Think%20of%20the%20Orchestrator%20as,manager%20and%20Workers%20as%20specialists&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;pytrick.medium.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  Our fictional investment advisor will reuse this structure: the orchestrator iterates through different personas (workers), each generating an investment plan; an evaluator agent critiques the plan; and the orchestrator decides whether to accept or request another draft.&lt;/p&gt;&lt;h2 data-end=&quot;4916&quot; data-start=&quot;4882&quot;&gt;Fictional Investment Superstars&lt;/h2&gt;&lt;p data-end=&quot;5201&quot; data-start=&quot;4918&quot;&gt;Real investment icons like Cathie&amp;nbsp;Wood or Warren&amp;nbsp;Buffett offer recognisable strategies, but they also introduce brand‑specific bias.  To avoid conflating advice with real identities, and to show that agentic workflows can model a variety of styles, we propose three fictional personas:&lt;/p&gt;&lt;ul data-end=&quot;6007&quot; data-start=&quot;5203&quot;&gt;
&lt;li data-end=&quot;5494&quot; data-start=&quot;5203&quot;&gt;
&lt;p data-end=&quot;5494&quot; data-start=&quot;5205&quot;&gt;&lt;strong data-end=&quot;5223&quot; data-start=&quot;5205&quot;&gt;Bobby&amp;nbsp;Axelrod:&lt;/strong&gt; loosely modelled on a hedge‑fund manager character.  Bobby favours high‑growth sectors and alternative assets.  In our template he allocates ~70 % to equities and 20 % to alternatives, leaving minimal fixed income and cash.  This results in an &lt;strong data-end=&quot;5482&quot; data-start=&quot;5468&quot;&gt;aggressive&lt;/strong&gt; risk grade.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;5761&quot; data-start=&quot;5495&quot;&gt;
&lt;p data-end=&quot;5761&quot; data-start=&quot;5497&quot;&gt;&lt;strong data-end=&quot;5514&quot; data-start=&quot;5497&quot;&gt;Gordon&amp;nbsp;Gekko:&lt;/strong&gt; inspired by the 1980s corporate raider, Gordon prioritises concentrated bets, options and arbitrage.  He rarely recommends conservative portfolios, pushing equities to 75 % and leaving a token 5 % to fixed income.  Another &lt;strong data-end=&quot;5752&quot; data-start=&quot;5738&quot;&gt;aggressive&lt;/strong&gt; profile.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;6007&quot; data-start=&quot;5762&quot;&gt;
&lt;p data-end=&quot;6007&quot; data-start=&quot;5764&quot;&gt;&lt;strong data-end=&quot;5780&quot; data-start=&quot;5764&quot;&gt;Richie&amp;nbsp;Rich:&lt;/strong&gt; a wealthy heir who values security.  Richie’s allocation varies by target grade: 60–30 % equities, 10–20 % alternatives, 15–45 % fixed income and the rest cash.  He can satisfy &lt;strong data-end=&quot;5974&quot; data-start=&quot;5958&quot;&gt;conservative&lt;/strong&gt; or &lt;strong data-end=&quot;5990&quot; data-start=&quot;5978&quot;&gt;moderate&lt;/strong&gt; risk tolerances.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;p data-end=&quot;6470&quot; data-start=&quot;6009&quot;&gt;These personas serve as “workers” in our orchestrator.  They each provide a distinct draft plan based on the investor’s profile and desired risk grade.  A simple evaluator reads the plan, calculates the equity allocation and assigns a risk grade (conservative, moderate or aggressive).  If the assigned grade doesn’t match the target, the orchestrator calls another persona to propose a new plan.  After a preset number of iterations, the best plan is returned.&lt;/p&gt;&lt;h2 data-end=&quot;6505&quot; data-start=&quot;6472&quot;&gt;LLM Integration with LangChain&lt;/h2&gt;&lt;p data-end=&quot;7204&quot; data-start=&quot;6507&quot;&gt;While deterministic templates offer repeatable outputs, they lack the spontaneity and nuance of natural language generation.  To make the advisor more generative and human‑like, the latest version integrates an optional language model using &lt;strong data-end=&quot;6774&quot; data-start=&quot;6748&quot;&gt;LangChain’s ChatOpenAI&lt;/strong&gt; interface.  At the top of the code we attempt to import &lt;code data-end=&quot;6843&quot; data-start=&quot;6831&quot;&gt;ChatOpenAI&lt;/code&gt; and &lt;code data-end=&quot;6868&quot; data-start=&quot;6848&quot;&gt;ChatPromptTemplate&lt;/code&gt;.  A helper function &lt;code data-end=&quot;6900&quot; data-start=&quot;6889&quot;&gt;get_llm()&lt;/code&gt; tries to instantiate a &lt;strong data-end=&quot;6939&quot; data-start=&quot;6924&quot;&gt;gpt‑4o‑mini&lt;/strong&gt; model with a moderate temperature (0.7).  When this succeeds, each persona constructs a structured prompt containing the investor’s profile and target grade; the model then generates a bespoke Markdown plan with asset allocations, rationale and rebalancing advice.&lt;/p&gt;&lt;p data-end=&quot;7717&quot; data-start=&quot;7206&quot;&gt;The beauty of this design is its &lt;strong data-end=&quot;7260&quot; data-start=&quot;7239&quot;&gt;graceful fallback&lt;/strong&gt;: if LangChain isn’t installed or the model fails to initialize (for instance, due to missing API keys), the code reverts to deterministic templates.  The reflection loop and evaluator remain unchanged, so the orchestrator–worker architecture stays intact.  Only the plan generation mechanism becomes dynamic when an LLM is available.  This modularity demonstrates how real‑world systems can incrementally adopt generative AI without sacrificing robustness.&lt;/p&gt;&lt;h3 data-end=&quot;7742&quot; data-start=&quot;7719&quot;&gt;Code Implementation&lt;/h3&gt;&lt;p data-end=&quot;8201&quot; data-start=&quot;7744&quot;&gt;To put these ideas into practice, we provide a fully working Python module that mirrors the reflection loop described in the Skills&amp;nbsp;Network lab while swapping the real investors for our fictional superstars.  The module defines a shared &lt;strong data-end=&quot;7990&quot; data-start=&quot;7981&quot;&gt;state&lt;/strong&gt; dictionary, prompts for each persona, an evaluator inspired by Richie&amp;nbsp;Rich’s prudence, and a simple Python loop that iteratively generates and critiques investment plans until the risk grade matches the target.&lt;/p&gt;&lt;p data-end=&quot;8226&quot; data-start=&quot;8203&quot;&gt;Key components include:&lt;/p&gt;&lt;ul data-end=&quot;9004&quot; data-start=&quot;8228&quot;&gt;
&lt;li data-end=&quot;8343&quot; data-start=&quot;8228&quot;&gt;
&lt;p data-end=&quot;8343&quot; data-start=&quot;8230&quot;&gt;&lt;strong data-end=&quot;8258&quot; data-start=&quot;8230&quot;&gt;&lt;code data-end=&quot;8256&quot; data-start=&quot;8232&quot;&gt;determine_target_grade&lt;/code&gt;&lt;/strong&gt; – uses an LLM to pick a suitable risk grade for the investor based on their profile.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;8502&quot; data-start=&quot;8344&quot;&gt;
&lt;p data-end=&quot;8502&quot; data-start=&quot;8346&quot;&gt;&lt;strong data-end=&quot;8377&quot; data-start=&quot;8346&quot;&gt;&lt;code data-end=&quot;8375&quot; data-start=&quot;8348&quot;&gt;investment_plan_generator&lt;/code&gt;&lt;/strong&gt; – produces an initial Bobby&amp;nbsp;Axelrod–style plan and refines it using Gordon&amp;nbsp;Gekko’s opportunistic rules when feedback exists.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;8667&quot; data-start=&quot;8503&quot;&gt;
&lt;p data-end=&quot;8667&quot; data-start=&quot;8505&quot;&gt;&lt;strong data-end=&quot;8524&quot; data-start=&quot;8505&quot;&gt;&lt;code data-end=&quot;8522&quot; data-start=&quot;8507&quot;&gt;evaluate_plan&lt;/code&gt;&lt;/strong&gt; – calls a Richie&amp;nbsp;Rich–style evaluator to classify the risk level and provide feedback on diversification, volatility and capital preservation.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;8804&quot; data-start=&quot;8668&quot;&gt;
&lt;p data-end=&quot;8804&quot; data-start=&quot;8670&quot;&gt;&lt;strong data-end=&quot;8692&quot; data-start=&quot;8670&quot;&gt;&lt;code data-end=&quot;8690&quot; data-start=&quot;8672&quot;&gt;route_investment&lt;/code&gt;&lt;/strong&gt; – decides whether to accept the current plan or request another iteration, enforcing a maximum number of loops.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;9004&quot; data-start=&quot;8805&quot;&gt;
&lt;p data-end=&quot;9004&quot; data-start=&quot;8807&quot;&gt;&lt;strong data-end=&quot;8836&quot; data-start=&quot;8807&quot;&gt;&lt;code data-end=&quot;8834&quot; data-start=&quot;8809&quot;&gt;run_reflection_workflow&lt;/code&gt;&lt;/strong&gt; – orchestrates the entire generate–evaluate–route cycle in plain Python without relying on LangGraph, returning the final plan along with feedback and iteration count.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;p data-end=&quot;9098&quot; data-start=&quot;9006&quot;&gt;Here is a high‑level sketch of the module’s structure (see&amp;nbsp;&lt;a href=&quot;https://github.com/JordiCorbilla/langgraph-cookbook/tree/main/06%20-%20Investment%20Agent&quot;&gt;langgraph-cookbook/06 - Investment Agent at main · JordiCorbilla/langgraph-cookbook&lt;/a&gt;&amp;nbsp;for complete code):&lt;/p&gt;&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;10435&quot; data-start=&quot;9100&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; typing &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; TypedDict, &lt;span class=&quot;hljs-type&quot;&gt;Dict&lt;/span&gt;, &lt;span class=&quot;hljs-type&quot;&gt;Literal&lt;/span&gt;, Annotated
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; langchain_openai &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; ChatOpenAI
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; langchain_core.prompts &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; ChatPromptTemplate

grades = &lt;span class=&quot;hljs-type&quot;&gt;Literal&lt;/span&gt;[&lt;span class=&quot;hljs-string&quot;&gt;&quot;ultra-conservative&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;conservative&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;moderate&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;aggressive&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;high risk&quot;&lt;/span&gt;]

&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;State&lt;/span&gt;(&lt;span class=&quot;hljs-title class_ inherited__&quot;&gt;TypedDict&lt;/span&gt;):
    investment_plan: &lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;
    investor_profile: &lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;
    target_grade: grades
    feedback: &lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;
    grade: grades
    n: &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;# Initialise the LLM (gpt-4o-mini) and build prompt templates for each persona&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;# … Bobby&amp;nbsp;Axelrod prompt and pipeline&lt;/span&gt;
&lt;span class=&quot;hljs-comment&quot;&gt;# … Gordon&amp;nbsp;Gekko prompt and pipeline with adaptation rules&lt;/span&gt;
&lt;span class=&quot;hljs-comment&quot;&gt;# … Richie&amp;nbsp;Rich evaluator prompt and structured output&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;determine_target_grade&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;state: State&lt;/span&gt;) -&amp;gt; &lt;span class=&quot;hljs-type&quot;&gt;Dict&lt;/span&gt;[&lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;, grades]:
    &lt;span class=&quot;hljs-comment&quot;&gt;# Ask the LLM to choose the target risk grade based on the profile&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;pass&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;investment_plan_generator&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;state: State&lt;/span&gt;) -&amp;gt; &lt;span class=&quot;hljs-type&quot;&gt;Dict&lt;/span&gt;[&lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;, &lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;]:
    &lt;span class=&quot;hljs-comment&quot;&gt;# Generate or refine the plan using Bobby or Gordon prompts&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;pass&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;evaluate_plan&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;state: State&lt;/span&gt;) -&amp;gt; &lt;span class=&quot;hljs-type&quot;&gt;Dict&lt;/span&gt;[&lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;, &lt;span class=&quot;hljs-built_in&quot;&gt;object&lt;/span&gt;]:
    &lt;span class=&quot;hljs-comment&quot;&gt;# Evaluate the plan with the Richie Rich evaluator&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;pass&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;route_investment&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;state: State, iteration_limit: &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt;&lt;/span&gt; = &lt;span class=&quot;hljs-number&quot;&gt;5&lt;/span&gt;) -&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;:
    &lt;span class=&quot;hljs-comment&quot;&gt;# Decide whether to accept the plan or loop again&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;pass&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;run_reflection_workflow&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;profile: &lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;&lt;/span&gt;) -&amp;gt; State:
    &lt;span class=&quot;hljs-comment&quot;&gt;# Orchestrate the reflection loop until the target grade is met or the iteration limit is reached&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;p data-end=&quot;10763&quot; data-start=&quot;10437&quot;&gt;The complete implementation can be downloaded as &lt;code data-end=&quot;10519&quot; data-start=&quot;10486&quot;&gt;custom_investment_reflection.py&lt;/code&gt; (file&amp;nbsp;ID &lt;code data-end=&quot;10565&quot; data-start=&quot;10529&quot;&gt; :agentCitation{citationIndex=&#39;0&#39;}&lt;/code&gt;).  This script uses LangChain’s ChatOpenAI to generate and evaluate plans, falls back gracefully if the LLM isn’t available, and demonstrates how to adapt the reflection pattern to custom personas.&lt;/p&gt;&lt;h3 data-end=&quot;10785&quot; data-start=&quot;10765&quot;&gt;Sample Execution&lt;/h3&gt;&lt;p data-end=&quot;11234&quot; data-start=&quot;10787&quot;&gt;As an example, suppose you run &lt;code data-end=&quot;10845&quot; data-start=&quot;10818&quot;&gt;run_reflection_workflow()&lt;/code&gt; from the &lt;code data-end=&quot;10888&quot; data-start=&quot;10855&quot;&gt;custom_investment_reflection.py&lt;/code&gt; module with a 35‑year‑old investor earning $100 k, holding $100 k in assets, aiming to retire by 55 with a generous travel budget, and tolerating high risk.  The workflow determines an &lt;strong data-end=&quot;11088&quot; data-start=&quot;11074&quot;&gt;aggressive&lt;/strong&gt; target grade and iteratively refines the plan.  One possible output (actual results vary slightly with the generative model) is summarised below:&lt;/p&gt;&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;12619&quot; data-start=&quot;11236&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre!&quot;&gt;🎯 Final Investment Plan Summary
========================================

📌 Investor Profile:
&lt;span class=&quot;hljs-symbol&quot;&gt;Age:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;35&lt;/span&gt;
&lt;span class=&quot;hljs-symbol&quot;&gt;Salary:&lt;/span&gt; $&lt;span class=&quot;hljs-number&quot;&gt;100&lt;/span&gt;,&lt;span class=&quot;hljs-number&quot;&gt;000&lt;/span&gt;
&lt;span class=&quot;hljs-symbol&quot;&gt;Assets:&lt;/span&gt; $&lt;span class=&quot;hljs-number&quot;&gt;100&lt;/span&gt;,&lt;span class=&quot;hljs-number&quot;&gt;000&lt;/span&gt;
&lt;span class=&quot;hljs-symbol&quot;&gt;Goal:&lt;/span&gt; Retire comfortably &lt;span class=&quot;hljs-keyword&quot;&gt;by&lt;/span&gt; age &lt;span class=&quot;hljs-number&quot;&gt;55&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;with&lt;/span&gt; ample travel budget
Risk tolerance: high

📈 Target Risk Grade: aggressive
📊 Final Assigned Grade: aggressive
🔁 Iterations Taken: &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;

📝 Evaluator Feedback:
----------------------------------
This portfolio &lt;span class=&quot;hljs-built_in&quot;&gt;is&lt;/span&gt; classified &lt;span class=&quot;hljs-keyword&quot;&gt;as&lt;/span&gt; aggressive due &lt;span class=&quot;hljs-keyword&quot;&gt;to&lt;/span&gt; its heavy allocation &lt;span class=&quot;hljs-keyword&quot;&gt;to&lt;/span&gt; equities (&lt;span class=&quot;hljs-number&quot;&gt;75%&lt;/span&gt;) &lt;span class=&quot;hljs-built_in&quot;&gt;and&lt;/span&gt; lower commitment &lt;span class=&quot;hljs-keyword&quot;&gt;to&lt;/span&gt; fixed income.  Ensure the investor &lt;span class=&quot;hljs-built_in&quot;&gt;is&lt;/span&gt; comfortable &lt;span class=&quot;hljs-keyword&quot;&gt;with&lt;/span&gt; higher volatility &lt;span class=&quot;hljs-built_in&quot;&gt;and&lt;/span&gt; periodic drawdowns.

📃 Final Investment Plan:
----------------------------------
Gordon Gekko advises the investor &lt;span class=&quot;hljs-keyword&quot;&gt;to&lt;/span&gt; pursue an assertive course:

### Asset Allocation
- **Equities (&lt;span class=&quot;hljs-number&quot;&gt;75%&lt;/span&gt;)**: Concentrated bets &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; blue chips &lt;span class=&quot;hljs-built_in&quot;&gt;and&lt;/span&gt; cyclical industries, supplemented &lt;span class=&quot;hljs-keyword&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;option&lt;/span&gt; strategies.
- **Alternatives (&lt;span class=&quot;hljs-number&quot;&gt;15%&lt;/span&gt;)**: Distressed debt, merger arbitrage &lt;span class=&quot;hljs-built_in&quot;&gt;and&lt;/span&gt; real‑estate investment trusts.
- **Fixed Income (&lt;span class=&quot;hljs-number&quot;&gt;5%&lt;/span&gt;)**: Opportunistic &lt;span class=&quot;hljs-type&quot;&gt;short&lt;/span&gt;‑dated corporate notes.
- **Cash (&lt;span class=&quot;hljs-number&quot;&gt;5%&lt;/span&gt;)**: Dry powder &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; takeover opportunities.

### Rationale
Prioritizing blue chip equities &lt;span class=&quot;hljs-built_in&quot;&gt;and&lt;/span&gt; sector ETFs &lt;span class=&quot;hljs-keyword&quot;&gt;with&lt;/span&gt; active options overlays, complemented &lt;span class=&quot;hljs-keyword&quot;&gt;by&lt;/span&gt; distressed debt &lt;span class=&quot;hljs-built_in&quot;&gt;and&lt;/span&gt; merger arbitrage funds.  Minimal fixed income ensures capital &lt;span class=&quot;hljs-built_in&quot;&gt;is&lt;/span&gt; working hard.

### Risk Management
Use &lt;span class=&quot;hljs-keyword&quot;&gt;stop&lt;/span&gt;‑loss orders &lt;span class=&quot;hljs-built_in&quot;&gt;and&lt;/span&gt; hedges; rebalance monthly &lt;span class=&quot;hljs-keyword&quot;&gt;to&lt;/span&gt; maintain leverage discipline.
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;p data-end=&quot;12916&quot; data-start=&quot;12621&quot;&gt;The first iteration used the Bobby&amp;nbsp;Axelrod persona, which produced a 70 % equity allocation (moderate grade).  The evaluator recommended increasing risk exposure, and the orchestrator rotated to Gordon Gekko, whose 75 % equity allocation met the target.  The system stopped after two iterations.&lt;/p&gt;&lt;h2 data-end=&quot;12971&quot; data-start=&quot;12918&quot;&gt;Pros and Cons of the Reflection Pattern in Finance&lt;/h2&gt;&lt;h3 data-end=&quot;12987&quot; data-start=&quot;12973&quot;&gt;Advantages&lt;/h3&gt;&lt;ul data-end=&quot;14307&quot; data-start=&quot;12989&quot;&gt;
&lt;li data-end=&quot;13259&quot; data-start=&quot;12989&quot;&gt;
&lt;p data-end=&quot;13259&quot; data-start=&quot;12991&quot;&gt;&lt;strong data-end=&quot;13034&quot; data-start=&quot;12991&quot;&gt;Improved quality through self‑critique:&lt;/strong&gt; By iteratively evaluating its own recommendations, the advisor reduces the likelihood of omissions or contradictory allocations.  It mirrors the human practice of drafting and revising (&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.philschmid.de/agentic-pattern#:~:text=Reflection%20Pattern&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.philschmid.de/agentic-pattern#:~:text=Reflection%20Pattern&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;philschmid.de&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;13463&quot; data-start=&quot;13260&quot;&gt;
&lt;p data-end=&quot;13463&quot; data-start=&quot;13262&quot;&gt;&lt;strong data-end=&quot;13282&quot; data-start=&quot;13262&quot;&gt;Personalisation:&lt;/strong&gt; Different personas allow the system to explore varied strategies.  Investors can compare aggressive, moderate and conservative approaches without requiring multiple human advisors.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;13728&quot; data-start=&quot;13464&quot;&gt;
&lt;p data-end=&quot;13728&quot; data-start=&quot;13466&quot;&gt;&lt;strong data-end=&quot;13492&quot; data-start=&quot;13466&quot;&gt;Generative creativity:&lt;/strong&gt; When an LLM is available, each persona can craft bespoke investment narratives instead of fixed templates.  This results in plans that read more naturally and incorporate subtle nuances while still adhering to the requested risk grade.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;13931&quot; data-start=&quot;13729&quot;&gt;
&lt;p data-end=&quot;13931&quot; data-start=&quot;13731&quot;&gt;&lt;strong data-end=&quot;13754&quot; data-start=&quot;13731&quot;&gt;Reduced human bias:&lt;/strong&gt; Using fictional characters avoids overreliance on real gurus.  The system still captures different risk appetites but does not misappropriate any celebrity’s actual statements.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;14128&quot; data-start=&quot;13932&quot;&gt;
&lt;p data-end=&quot;14128&quot; data-start=&quot;13934&quot;&gt;&lt;strong data-end=&quot;13952&quot; data-start=&quot;13934&quot;&gt;Extensibility:&lt;/strong&gt; New personas or evaluators can be added without changing the core loop, demonstrating the modularity of the orchestrator–worker pattern (&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://dzone.com/articles/ai-agent-architectures-patterns-applications-guide#:~:text=1.%20Orchestrator&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://dzone.com/articles/ai-agent-architectures-patterns-applications-guide#:~:text=1.%20Orchestrator&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;dzone.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;14307&quot; data-start=&quot;14129&quot;&gt;
&lt;p data-end=&quot;14307&quot; data-start=&quot;14131&quot;&gt;&lt;strong data-end=&quot;14160&quot; data-start=&quot;14131&quot;&gt;Autonomy and scalability:&lt;/strong&gt; The loop can be run asynchronously, and evaluation metrics can be refined over time to align with regulatory guidelines or individual preferences.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;h3 data-end=&quot;14324&quot; data-start=&quot;14309&quot;&gt;Limitations&lt;/h3&gt;&lt;ul data-end=&quot;15606&quot; data-start=&quot;14326&quot;&gt;
&lt;li data-end=&quot;14655&quot; data-start=&quot;14326&quot;&gt;
&lt;p data-end=&quot;14655&quot; data-start=&quot;14328&quot;&gt;&lt;strong data-end=&quot;14352&quot; data-start=&quot;14328&quot;&gt;Heuristic evaluator:&lt;/strong&gt; Our evaluation function parses simple percentages and assigns grades.  Real‑world risk analysis is far more complex, involving volatility, correlation and drawdown metrics.  More sophisticated evaluators (e.g., risk‑parity or Monte Carlo simulators) could improve accuracy but also increase complexity.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;14859&quot; data-start=&quot;14656&quot;&gt;
&lt;p data-end=&quot;14859&quot; data-start=&quot;14658&quot;&gt;&lt;strong data-end=&quot;14681&quot; data-start=&quot;14658&quot;&gt;Computational cost:&lt;/strong&gt; Iterative generation and evaluation consume more compute cycles and may require multiple LLM calls.  For high‑frequency use cases, latency and API cost could become prohibitive.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;15098&quot; data-start=&quot;14860&quot;&gt;
&lt;p data-end=&quot;15098&quot; data-start=&quot;14862&quot;&gt;&lt;strong data-end=&quot;14883&quot; data-start=&quot;14862&quot;&gt;Unpredictability:&lt;/strong&gt; Generative models may occasionally omit required allocation details or introduce inconsistent formatting.  Although the evaluator catches gross mismatches, manual review is advisable when using LLM‑generated plans.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;15357&quot; data-start=&quot;15099&quot;&gt;
&lt;p data-end=&quot;15357&quot; data-start=&quot;15101&quot;&gt;&lt;strong data-end=&quot;15128&quot; data-start=&quot;15101&quot;&gt;Potential oscillations:&lt;/strong&gt; Without careful stopping rules, the system might ping‑pong between personas or produce contradictory adjustments.  Clear termination criteria and penalizing repeated mistakes are essential (&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://www.analyticsvidhya.com/blog/2024/10/agentic-ai-reflection-pattern/#:~:text=,loops%20in%20the%20reflection%20process&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://www.analyticsvidhya.com/blog/2024/10/agentic-ai-reflection-pattern/#:~:text=,loops%20in%20the%20reflection%20process&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;analyticsvidhya.com&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;15606&quot; data-start=&quot;15358&quot;&gt;
&lt;p data-end=&quot;15606&quot; data-start=&quot;15360&quot;&gt;&lt;strong data-end=&quot;15389&quot; data-start=&quot;15360&quot;&gt;Lack of external context:&lt;/strong&gt; Our fictional personas rely on templates rather than real market data.  Integrating live data feeds and dynamic modelling would make the advice more actionable but also raises regulatory and liability considerations.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2 data-end=&quot;17406&quot; data-start=&quot;17393&quot;&gt;Conclusion&lt;/h2&gt;&lt;p&gt;



































&lt;/p&gt;&lt;p data-end=&quot;18087&quot; data-start=&quot;17408&quot;&gt;The reflection pattern offers a simple yet powerful mechanism for improving AI outputs by letting the model serve as its own critic.  When combined with an orchestrator–worker architecture, it provides a modular and extensible framework for tasks like investment advice, where balancing risk and return requires careful iteration.  By experimenting with fictional personas, we avoid real‑world bias while illustrating how different risk appetites can be encoded in code.  Although our current implementation uses heuristic evaluators and templated advice, it lays the foundation for more sophisticated systems that combine generative AI, financial models and ethical constraints.&lt;/p&gt;</description><link>https://thundaxsoftware.blogspot.com/2025/09/reflection-pattern-for-investment.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOt6jmB2FyPTB8Xb-m6aO1etGbOAhQzU2I-XUOClZEgjEEWJbVq47-r0URqC5IjkSucUxHHGqJB4bf0s2u-tUvDDdj4LOYlST5yYZw5UMIWgNYRdKKmT2X4oZ_mnsaDp7-R9TsxgNc3XiJjJfPFRlihWNnHb9qgQywVtOM41v9FZ5tyJEESFgT1583O2Q/s72-w320-h320-c/fe69c127-4310-47ee-8e47-436d8fd58eb1.png" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>London, UK</georss:featurename><georss:point>51.5072178 -0.1275862</georss:point><georss:box>23.196983963821154 -35.2838362 79.817451636178845 35.0286638</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-6441961157422215659</guid><pubDate>Sat, 27 Sep 2025 11:44:00 +0000</pubDate><atom:updated>2025-09-27T11:44:11.843+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">continuous batching</category><category domain="http://www.blogger.com/atom/ns#">dynamic quantization</category><category domain="http://www.blogger.com/atom/ns#">GPT‑2</category><category domain="http://www.blogger.com/atom/ns#">latency reduction</category><category domain="http://www.blogger.com/atom/ns#">LLM inference</category><category domain="http://www.blogger.com/atom/ns#">memory reduction</category><category domain="http://www.blogger.com/atom/ns#">performance optimization</category><category domain="http://www.blogger.com/atom/ns#">quantization</category><category domain="http://www.blogger.com/atom/ns#">throughput improvement</category><category domain="http://www.blogger.com/atom/ns#">weight‑only quantization</category><title>Benchmarking Dynamic Quantization for Larger Language Models</title><description>&lt;p&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgU2q3_2nAM97mJ16i75w4dOy2Pizqsb504QDWm83pzRUpjNRsHF-JsOsifyEW0zb9fHnYFYGqqndT_PDB8jlYiJtMNQVceRLM89Bi3gVAbCepYpYMKGq6bVevrlc8MHf_niLJiPHEtU7LiVQVi0G_z739NtTeQ98Lg47gAe-c0c3qWnsYNBeUmA6LPpEc&quot; style=&quot;clear: left; display: inline !important; float: left; margin-bottom: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgU2q3_2nAM97mJ16i75w4dOy2Pizqsb504QDWm83pzRUpjNRsHF-JsOsifyEW0zb9fHnYFYGqqndT_PDB8jlYiJtMNQVceRLM89Bi3gVAbCepYpYMKGq6bVevrlc8MHf_niLJiPHEtU7LiVQVi0G_z739NtTeQ98Lg47gAe-c0c3qWnsYNBeUmA6LPpEc=w320-h320&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Benchmarking Dynamic Quantization for Larger Language Models&lt;/h2&gt;&lt;p&gt;&lt;/p&gt;&lt;p data-end=&quot;817&quot; data-start=&quot;64&quot;&gt;Modern language models can answer complex queries and generate high‑quality text, but they are heavy: hundreds of megabytes of weights have to be loaded into memory and millions of multiply and accumulate operations run per query.  To reduce latency and cost, practitioners use optimisation techniques such as &lt;strong data-end=&quot;392&quot; data-start=&quot;370&quot;&gt;model distillation&lt;/strong&gt;, &lt;strong data-end=&quot;410&quot; data-start=&quot;394&quot;&gt;quantization&lt;/strong&gt;, &lt;strong data-end=&quot;423&quot; data-start=&quot;412&quot;&gt;pruning&lt;/strong&gt;, &lt;strong data-end=&quot;456&quot; data-start=&quot;425&quot;&gt;dynamic/continuous batching&lt;/strong&gt;, and &lt;strong data-end=&quot;480&quot; data-start=&quot;462&quot;&gt;KV‑cache reuse&lt;/strong&gt;.  When these methods are combined, costs can be slashed by up to &lt;strong data-end=&quot;554&quot; data-start=&quot;546&quot;&gt;80&amp;nbsp;%&lt;/strong&gt; while maintaining acceptable quality&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://latitude-blog.ghost.io/blog/llm-inference-optimization-speed-scale-and-savings/#:~:text=,up%20processing%20for%20long%20sequences&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://latitude-blog.ghost.io/blog/llm-inference-optimization-speed-scale-and-savings/#:~:text=,up%20processing%20for%20long%20sequences&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;latitude-blog.ghost.io&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;.  This post focuses on &lt;strong data-end=&quot;681&quot; data-start=&quot;652&quot;&gt;dynamic INT8 quantization&lt;/strong&gt; for a medium‑sized GPT‑2 model, shows the performance difference between full‑precision and quantised models, and discusses trade‑offs.&lt;/p&gt;&lt;h2 data-end=&quot;839&quot; data-start=&quot;819&quot;&gt;Why Quantization?&lt;/h2&gt;&lt;p data-end=&quot;1474&quot; data-start=&quot;841&quot;&gt;Quantization reduces the precision of a model’s weights from 32‑bit floating‑point values to 8‑bit (or lower) integers.  PyTorch’s quantization documentation notes that &lt;strong data-end=&quot;1154&quot; data-start=&quot;1010&quot;&gt;INT8 quantization can shrink the model size and memory bandwidth by roughly&amp;nbsp;4× and that INT8 operations are typically 2–4&amp;nbsp;× faster than FP32 (&lt;/strong&gt;&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://docs.pytorch.org/docs/stable/quantization.html#:~:text=Quantization%20refers%20to%20techniques%20for,only%20the%20forward%20pass%20is&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://docs.pytorch.org/docs/stable/quantization.html#:~:text=Quantization%20refers%20to%20techniques%20for,only%20the%20forward%20pass%20is&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;docs.pytorch.org&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  For example, a 500‑million‑parameter model occupying &lt;strong data-end=&quot;1263&quot; data-start=&quot;1247&quot;&gt;2&amp;nbsp;GB in FP32&lt;/strong&gt; can be reduced to &lt;strong data-end=&quot;1292&quot; data-start=&quot;1282&quot;&gt;0.5&amp;nbsp;GB&lt;/strong&gt; when quantized to INT8 (&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://latitude-blog.ghost.io/blog/llm-inference-optimization-speed-scale-and-savings/#:~:text=limited%20edge%20device%2C%20quantization%20makes,models%20to%20run%20efficiently%20on&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://latitude-blog.ghost.io/blog/llm-inference-optimization-speed-scale-and-savings/#:~:text=limited%20edge%20device%2C%20quantization%20makes,models%20to%20run%20efficiently%20on&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;latitude-blog.ghost.io&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  This smaller footprint allows large language models to run on memory‑constrained devices and reduces inference latency.&lt;/p&gt;&lt;p data-end=&quot;1980&quot; data-start=&quot;1476&quot;&gt;Weight‑only quantization (e.g. 4‑bit AWQ or GPTQ) can improve speed even more.  In real deployments, quantized LLMs show dramatic throughput gains: DeepSeek‑7B’s throughput on an NVIDIA&amp;nbsp;RTX&amp;nbsp;4090 increases from &lt;strong data-end=&quot;1717&quot; data-start=&quot;1686&quot;&gt;52 tokens/s to 130 tokens/s&lt;/strong&gt; using AWQ, while Mistral‑7B on an AWS G5.xlarge instance jumps from &lt;strong data-end=&quot;1816&quot; data-start=&quot;1786&quot;&gt;28&amp;nbsp;tokens/s to 88&amp;nbsp;tokens/s (&lt;/strong&gt;&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://latitude-blog.ghost.io/blog/llm-inference-optimization-speed-scale-and-savings/#:~:text=Quantized%20models%20are%20game%20changers,to%2088%20tokens%20per%20second&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://latitude-blog.ghost.io/blog/llm-inference-optimization-speed-scale-and-savings/#:~:text=Quantized%20models%20are%20game%20changers,to%2088%20tokens%20per%20second&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;latitude-blog.ghost.io&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  A 4‑bit GPTQ version of Llama‑3.2&amp;nbsp;1B maintained its F1 score and gained &lt;strong data-end=&quot;1936&quot; data-start=&quot;1928&quot;&gt;30&amp;nbsp;%&lt;/strong&gt; speed (&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://latitude-blog.ghost.io/blog/llm-inference-optimization-speed-scale-and-savings/#:~:text=Databricks%20also%20reported%20impressive%20results,Similarly&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://latitude-blog.ghost.io/blog/llm-inference-optimization-speed-scale-and-savings/#:~:text=Databricks%20also%20reported%20impressive%20results,Similarly&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;latitude-blog.ghost.io&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;&lt;p data-end=&quot;2416&quot; data-start=&quot;1982&quot;&gt;Despite these benefits, quantization can degrade output quality if not applied carefully.  Lower‑precision representations introduce rounding errors and remove representational capacity.  Advanced techniques like AWQ/GPTQ mitigate this by weighting channel scaling per layer and calibrating on sample data.  Our example uses PyTorch’s simple &lt;strong data-end=&quot;2348&quot; data-start=&quot;2324&quot;&gt;dynamic quantization&lt;/strong&gt;, which is less powerful than weight‑only methods but easy to apply.&lt;/p&gt;&lt;h2 data-end=&quot;2463&quot; data-start=&quot;2418&quot;&gt;The Example: Measuring FP32 vs. INT8 GPT‑2&lt;/h2&gt;&lt;p data-end=&quot;2671&quot; data-start=&quot;2465&quot;&gt;We built a script (&lt;code data-end=&quot;2512&quot; data-start=&quot;2484&quot;&gt;inference_example_large.py&lt;/code&gt;) that loads GPT‑2 (124M parameters), processes a handful of prompts, quantizes the model dynamically, and then processes the same prompts.  The key steps are:&lt;/p&gt;&lt;ol data-end=&quot;3416&quot; data-start=&quot;2673&quot;&gt;
&lt;li data-end=&quot;2958&quot; data-start=&quot;2673&quot;&gt;
&lt;p data-end=&quot;2958&quot; data-start=&quot;2676&quot;&gt;&lt;strong data-end=&quot;2705&quot; data-start=&quot;2676&quot;&gt;Load model and tokenizer.&lt;/strong&gt;  We use Hugging&amp;nbsp;Face’s &lt;code data-end=&quot;2751&quot; data-start=&quot;2729&quot;&gt;AutoModelForCausalLM&lt;/code&gt; with the GPT‑2 checkpoint and its tokenizer.  Because GPT‑2 lacks a padding token, we assign the end‑of‑sequence token (&lt;code data-end=&quot;2883&quot; data-start=&quot;2872&quot;&gt;eos_token&lt;/code&gt;) as the pad token and set &lt;code data-end=&quot;2931&quot; data-start=&quot;2910&quot;&gt;padding_side=&#39;left&#39;&lt;/code&gt; to avoid runtime warnings.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;3089&quot; data-start=&quot;2959&quot;&gt;
&lt;p data-end=&quot;3089&quot; data-start=&quot;2962&quot;&gt;&lt;strong data-end=&quot;2985&quot; data-start=&quot;2962&quot;&gt;Run FP32 inference.&lt;/strong&gt;  The script encodes a list of prompts, generates up to 50 new tokens for each, and measures total time.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;3347&quot; data-start=&quot;3090&quot;&gt;
&lt;p data-end=&quot;3347&quot; data-start=&quot;3093&quot;&gt;&lt;strong data-end=&quot;3124&quot; data-start=&quot;3093&quot;&gt;Apply dynamic quantization.&lt;/strong&gt;  &lt;code data-end=&quot;3163&quot; data-start=&quot;3126&quot;&gt;torch.quantization.quantize_dynamic&lt;/code&gt; quantizes only linear layers in the model (attention projections and feed‑forward layers).  Embedding and layer‑norm parameters remain in FP32, so the model size reduction is limited.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;3416&quot; data-start=&quot;3348&quot;&gt;
&lt;p data-end=&quot;3416&quot; data-start=&quot;3351&quot;&gt;&lt;strong data-end=&quot;3373&quot; data-start=&quot;3351&quot;&gt;Run INT8 inference&lt;/strong&gt; with the quantized model and measure time.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;p data-end=&quot;3471&quot; data-start=&quot;3418&quot;&gt;Here is a condensed version of the code’s core logic (full code here:&amp;nbsp;&lt;a href=&quot;https://github.com/JordiCorbilla/langgraph-cookbook&quot;&gt;JordiCorbilla/langgraph-cookbook: langgraph-cookbook&lt;/a&gt;):&lt;/p&gt;&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;4989&quot; data-start=&quot;3473&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; transformers &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; AutoModelForCausalLM, AutoTokenizer
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; torch, time, psutil

model_name = &lt;span class=&quot;hljs-string&quot;&gt;&quot;gpt2&quot;&lt;/span&gt;
dev = torch.device(&lt;span class=&quot;hljs-string&quot;&gt;&quot;cpu&quot;&lt;/span&gt;)

&lt;span class=&quot;hljs-comment&quot;&gt;# Load and patch tokenizer (GPT-2 has no pad token)&lt;/span&gt;
tokenizer = AutoTokenizer.from_pretrained(model_name)
&lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; tokenizer.pad_token_id &lt;span class=&quot;hljs-keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;None&lt;/span&gt;:
    tokenizer.pad_token = tokenizer.eos_token
    tokenizer.padding_side = &lt;span class=&quot;hljs-string&quot;&gt;&quot;left&quot;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;# Load full-precision model&lt;/span&gt;
model = AutoModelForCausalLM.from_pretrained(model_name).to(dev)
model.&lt;span class=&quot;hljs-built_in&quot;&gt;eval&lt;/span&gt;()

prompts = [&lt;span class=&quot;hljs-string&quot;&gt;&quot;Write a poem about quantization.&quot;&lt;/span&gt;,
           &lt;span class=&quot;hljs-string&quot;&gt;&quot;Why is dynamic batching useful?&quot;&lt;/span&gt;,  &lt;span class=&quot;hljs-comment&quot;&gt;# 4 prompts total...&lt;/span&gt;
          ]
max_new_tokens = &lt;span class=&quot;hljs-number&quot;&gt;50&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;# FP32 inference&lt;/span&gt;
start = time.time()
&lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; p &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; prompts:
    inp = tokenizer(p, return_tensors=&lt;span class=&quot;hljs-string&quot;&gt;&quot;pt&quot;&lt;/span&gt;).to(dev)
    out = model.generate(
        input_ids=inp[&lt;span class=&quot;hljs-string&quot;&gt;&quot;input_ids&quot;&lt;/span&gt;],
        attention_mask=inp[&lt;span class=&quot;hljs-string&quot;&gt;&quot;attention_mask&quot;&lt;/span&gt;],
        max_new_tokens=max_new_tokens,
        do_sample=&lt;span class=&quot;hljs-literal&quot;&gt;False&lt;/span&gt;,
        pad_token_id=tokenizer.eos_token_id,
    )
end = time.time()
fp32_time = end - start

&lt;span class=&quot;hljs-comment&quot;&gt;# Dynamically quantize linear layers&lt;/span&gt;
model_int8 = torch.quantization.quantize_dynamic(
    model, {torch.nn.Linear}, dtype=torch.qint8
)

&lt;span class=&quot;hljs-comment&quot;&gt;# INT8 inference&lt;/span&gt;
start = time.time()
&lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; p &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; prompts:
    inp = tokenizer(p, return_tensors=&lt;span class=&quot;hljs-string&quot;&gt;&quot;pt&quot;&lt;/span&gt;).to(dev)
    out = model_int8.generate(
        input_ids=inp[&lt;span class=&quot;hljs-string&quot;&gt;&quot;input_ids&quot;&lt;/span&gt;],
        attention_mask=inp[&lt;span class=&quot;hljs-string&quot;&gt;&quot;attention_mask&quot;&lt;/span&gt;],
        max_new_tokens=max_new_tokens,
        do_sample=&lt;span class=&quot;hljs-literal&quot;&gt;False&lt;/span&gt;,
        pad_token_id=tokenizer.eos_token_id,
    )
end = time.time()
int8_time = end - start
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;h3 data-end=&quot;5002&quot; data-start=&quot;4991&quot;&gt;Results&lt;/h3&gt;&lt;p data-end=&quot;5069&quot; data-start=&quot;5004&quot;&gt;Our run on a Windows laptop (CPU) produced the following numbers:&lt;/p&gt;&lt;div class=&quot;_tableContainer_1rjym_1&quot;&gt;&lt;div class=&quot;group _tableWrapper_1rjym_13 flex w-fit flex-col-reverse&quot; tabindex=&quot;-1&quot;&gt;&lt;table class=&quot;w-fit min-w-(--thread-content-width)&quot; data-end=&quot;5313&quot; data-start=&quot;5071&quot;&gt;&lt;thead data-end=&quot;5143&quot; data-start=&quot;5071&quot;&gt;&lt;tr data-end=&quot;5143&quot; data-start=&quot;5071&quot;&gt;&lt;th data-col-size=&quot;sm&quot; data-end=&quot;5079&quot; data-start=&quot;5071&quot;&gt;Model&lt;/th&gt;&lt;th data-col-size=&quot;sm&quot; data-end=&quot;5091&quot; data-start=&quot;5079&quot;&gt;Precision&lt;/th&gt;&lt;th data-col-size=&quot;sm&quot; data-end=&quot;5105&quot; data-start=&quot;5091&quot;&gt;Weight size&lt;/th&gt;&lt;th data-col-size=&quot;sm&quot; data-end=&quot;5132&quot; data-start=&quot;5105&quot;&gt;Total time for 4 prompts&lt;/th&gt;&lt;th data-col-size=&quot;sm&quot; data-end=&quot;5143&quot; data-start=&quot;5132&quot;&gt;Speedup&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody data-end=&quot;5313&quot; data-start=&quot;5214&quot;&gt;&lt;tr data-end=&quot;5255&quot; data-start=&quot;5214&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5222&quot; data-start=&quot;5214&quot;&gt;GPT‑2&lt;/td&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5229&quot; data-start=&quot;5222&quot;&gt;FP32&lt;/td&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5239&quot; data-start=&quot;5229&quot;&gt;~474&amp;nbsp;MB&lt;/td&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5249&quot; data-start=&quot;5239&quot;&gt;~6.57&amp;nbsp;s&lt;/td&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5255&quot; data-start=&quot;5249&quot;&gt;1×&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;5313&quot; data-start=&quot;5256&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5264&quot; data-start=&quot;5256&quot;&gt;GPT‑2&lt;/td&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5279&quot; data-start=&quot;5264&quot;&gt;INT8 dynamic&lt;/td&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5290&quot; data-start=&quot;5279&quot;&gt;~474&amp;nbsp;MB*&lt;/td&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5300&quot; data-start=&quot;5290&quot;&gt;~5.78&amp;nbsp;s&lt;/td&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5313&quot; data-start=&quot;5300&quot;&gt;&lt;strong data-end=&quot;5311&quot; data-start=&quot;5302&quot;&gt;1.14×&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;/div&gt;&lt;p data-end=&quot;5634&quot; data-start=&quot;5315&quot;&gt;*Dynamic quantization only converts &lt;code data-end=&quot;5363&quot; data-start=&quot;5352&quot;&gt;nn.Linear&lt;/code&gt; layers.  Embedding and layer‑norm weights remain in FP32, so the overall model footprint does not shrink.  Weight‑only quantization or AWQ/GPTQ can compress all weights and yield 4×–8× reductions (&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://docs.pytorch.org/docs/stable/quantization.html#:~:text=Quantization%20refers%20to%20techniques%20for,only%20the%20forward%20pass%20is&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://docs.pytorch.org/docs/stable/quantization.html#:~:text=Quantization%20refers%20to%20techniques%20for,only%20the%20forward%20pass%20is&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;docs.pytorch.org&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://latitude-blog.ghost.io/blog/llm-inference-optimization-speed-scale-and-savings/#:~:text=limited%20edge%20device%2C%20quantization%20makes,models%20to%20run%20efficiently%20on&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://latitude-blog.ghost.io/blog/llm-inference-optimization-speed-scale-and-savings/#:~:text=limited%20edge%20device%2C%20quantization%20makes,models%20to%20run%20efficiently%20on&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;latitude-blog.ghost.io&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;&lt;h3 data-end=&quot;5654&quot; data-start=&quot;5636&quot;&gt;Interpretation&lt;/h3&gt;&lt;ul data-end=&quot;6904&quot; data-start=&quot;5656&quot;&gt;
&lt;li data-end=&quot;6055&quot; data-start=&quot;5656&quot;&gt;
&lt;p data-end=&quot;6055&quot; data-start=&quot;5658&quot;&gt;&lt;strong data-end=&quot;5691&quot; data-start=&quot;5658&quot;&gt;Modest gains on small models.&lt;/strong&gt;  On a 124M‑parameter GPT‑2, dynamic quantization offered only a &lt;strong data-end=&quot;5764&quot; data-start=&quot;5756&quot;&gt;14&amp;nbsp;%&lt;/strong&gt; speedup and no memory savings.  This is because the model’s largest tensors are the embeddings and LM head, which were not quantized.  Dynamic quantization shines on CPU‑bound workloads with many linear operations but yields diminishing returns on small models or GPU‑accelerated inference.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;6448&quot; data-start=&quot;6056&quot;&gt;
&lt;p data-end=&quot;6448&quot; data-start=&quot;6058&quot;&gt;&lt;strong data-end=&quot;6089&quot; data-start=&quot;6058&quot;&gt;Larger models benefit more.&lt;/strong&gt;  Weight‑only quantization of big models (e.g., Llama‑3&amp;nbsp;70B) can reduce memory from 2&amp;nbsp;GB to 0.5&amp;nbsp;GB and double token throughput (&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://latitude-blog.ghost.io/blog/llm-inference-optimization-speed-scale-and-savings/#:~:text=limited%20edge%20device%2C%20quantization%20makes,models%20to%20run%20efficiently%20on&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://latitude-blog.ghost.io/blog/llm-inference-optimization-speed-scale-and-savings/#:~:text=limited%20edge%20device%2C%20quantization%20makes,models%20to%20run%20efficiently%20on&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;latitude-blog.ghost.io&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://latitude-blog.ghost.io/blog/llm-inference-optimization-speed-scale-and-savings/#:~:text=Quantized%20models%20are%20game%20changers,to%2088%20tokens%20per%20second&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://latitude-blog.ghost.io/blog/llm-inference-optimization-speed-scale-and-savings/#:~:text=Quantized%20models%20are%20game%20changers,to%2088%20tokens%20per%20second&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;latitude-blog.ghost.io&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.  AWQ and GPTQ compress all weights, producing 2×–4× speedups while preserving or even improving some evaluation metrics (&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://latitude-blog.ghost.io/blog/llm-inference-optimization-speed-scale-and-savings/#:~:text=Databricks%20also%20reported%20impressive%20results,5%C3%97%20on%20A6000%20GPUs&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://latitude-blog.ghost.io/blog/llm-inference-optimization-speed-scale-and-savings/#:~:text=Databricks%20also%20reported%20impressive%20results,5%C3%97%20on%20A6000%20GPUs&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;latitude-blog.ghost.io&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;6904&quot; data-start=&quot;6449&quot;&gt;
&lt;p data-end=&quot;6904&quot; data-start=&quot;6451&quot;&gt;&lt;strong data-end=&quot;6475&quot; data-start=&quot;6451&quot;&gt;Accuracy trade‑offs.&lt;/strong&gt;  Lower‑bit quantization introduces rounding error.  While our 8‑bit dynamic model produced sensible text, 4‑bit quantization must be applied with care (e.g., per‑channel scaling, calibration) to avoid harming output quality.  The Latitude article emphasises that quantization maintains accuracy “good enough for most applications” (&lt;span class=&quot;&quot; data-state=&quot;closed&quot;&gt;&lt;span class=&quot;ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]&quot; data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;a alt=&quot;https://latitude-blog.ghost.io/blog/llm-inference-optimization-speed-scale-and-savings/#:~:text=Model%20quantization%20is%20all%20about,good%20enough%20for%20most%20applications&quot; class=&quot;flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!&quot; href=&quot;https://latitude-blog.ghost.io/blog/llm-inference-optimization-speed-scale-and-savings/#:~:text=Model%20quantization%20is%20all%20about,good%20enough%20for%20most%20applications&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;&lt;span class=&quot;relative start-0 bottom-0 flex h-full w-full items-center&quot;&gt;latitude-blog.ghost.io&lt;/span&gt;&lt;/a&gt;)&lt;/span&gt;&lt;/span&gt;, but domain‑specific tasks may require careful evaluation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;h2 data-end=&quot;6940&quot; data-start=&quot;6906&quot;&gt;Continuous and Dynamic Batching&lt;/h2&gt;&lt;p data-end=&quot;7385&quot; data-start=&quot;6942&quot;&gt;Quantization isn’t the only lever for faster LLMs.  &lt;strong data-end=&quot;7028&quot; data-start=&quot;6994&quot;&gt;Dynamic or continuous batching&lt;/strong&gt; groups multiple inference requests arriving close in time into a single batch.  Instead of running one forward pass per request, the server merges them into micro‑batches and runs a single forward pass.  Systems like vLLM achieve &lt;strong data-end=&quot;7289&quot; data-start=&quot;7259&quot;&gt;up to 23× throughput gains&lt;/strong&gt; by continuously adding new prompts mid‑generation and sharing key/value caches across requests.&lt;/p&gt;&lt;p data-end=&quot;7710&quot; data-start=&quot;7387&quot;&gt;In our earlier example (with a tiny MLP), we saw that the first request took ~23&amp;nbsp;ms, while subsequent requests processed within 0.5–2&amp;nbsp;ms because they piggy‑backed on the existing batch.  Continuous batching is especially powerful when serving interactive chat applications where many users send short messages concurrently.&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Conclusion&lt;/h2&gt;&lt;p data-end=&quot;9299&quot; data-is-only-node=&quot;&quot; data-start=&quot;8766&quot;&gt;Dynamic quantization is an easy way to reduce the computational cost of LLM inference, but its benefits scale with model size.  Our GPT‑2 experiment shows only modest gains because embeddings dominate memory usage and remain in FP32.  For significant improvements, practitioners should adopt weight‑only quantization and pair it with continuous batching, KV‑cache optimisation, and model distillation.  These methods, when applied thoughtfully, can make deploying large language models feasible even in cost‑constrained environments.&lt;/p&gt;</description><link>https://thundaxsoftware.blogspot.com/2025/09/benchmarking-dynamic-quantization-for.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEgU2q3_2nAM97mJ16i75w4dOy2Pizqsb504QDWm83pzRUpjNRsHF-JsOsifyEW0zb9fHnYFYGqqndT_PDB8jlYiJtMNQVceRLM89Bi3gVAbCepYpYMKGq6bVevrlc8MHf_niLJiPHEtU7LiVQVi0G_z739NtTeQ98Lg47gAe-c0c3qWnsYNBeUmA6LPpEc=s72-w320-h320-c" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>London, UK</georss:featurename><georss:point>51.5072178 -0.1275862</georss:point><georss:box>23.196983963821154 -35.2838362 79.817451636178845 35.0286638</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-5551546921138613405</guid><pubDate>Sat, 20 Sep 2025 13:07:00 +0000</pubDate><atom:updated>2025-09-20T13:07:44.772+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Agent Orchestration</category><category domain="http://www.blogger.com/atom/ns#">AI Agents</category><category domain="http://www.blogger.com/atom/ns#">AST Evaluation</category><category domain="http://www.blogger.com/atom/ns#">Calculator Tool</category><category domain="http://www.blogger.com/atom/ns#">Deterministic AI</category><category domain="http://www.blogger.com/atom/ns#">LangChain</category><category domain="http://www.blogger.com/atom/ns#">LangGraph</category><category domain="http://www.blogger.com/atom/ns#">Observability</category><category domain="http://www.blogger.com/atom/ns#">openAI</category><category domain="http://www.blogger.com/atom/ns#">Prompt Engineering</category><category domain="http://www.blogger.com/atom/ns#">Python</category><category domain="http://www.blogger.com/atom/ns#">ReAct</category><category domain="http://www.blogger.com/atom/ns#">Safe Evaluation</category><category domain="http://www.blogger.com/atom/ns#">Streaming Outputs</category><category domain="http://www.blogger.com/atom/ns#">Tool Calling</category><title>ReAct, For Real: Building Deterministic Tool-Using Agents with LangGraph</title><description>&lt;h1 style=&quot;text-align: left;&quot;&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKxkA-9Wcigqx224NpjGb5mGJWreFqkrm8T8ZqUs71-6wfDp5MorUDOwo9klqkZjGnwNM05Uka_12GY7vGwAc6vfnmT5k0dplSgoU_cOTP1sJAr8QDlxw6ToS-aSyOi7uzyKYj2xi9nTVPycYyCbplUr4h_kW9kRkH0zb6zzsTO-pjm2bCmGJk91JZ9W4/s1536/ChatGPT%20Image%20Sep%2019,%202025,%2007_48_35%20AM.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1536&quot; height=&quot;213&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKxkA-9Wcigqx224NpjGb5mGJWreFqkrm8T8ZqUs71-6wfDp5MorUDOwo9klqkZjGnwNM05Uka_12GY7vGwAc6vfnmT5k0dplSgoU_cOTP1sJAr8QDlxw6ToS-aSyOi7uzyKYj2xi9nTVPycYyCbplUr4h_kW9kRkH0zb6zzsTO-pjm2bCmGJk91JZ9W4/s320/ChatGPT%20Image%20Sep%2019,%202025,%2007_48_35%20AM.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;ReAct, For Real: Building Deterministic Tool-Using Agents with LangGraph&lt;/h1&gt;
&lt;p data-end=&quot;766&quot; data-start=&quot;735&quot;&gt;&lt;em data-end=&quot;766&quot; data-start=&quot;735&quot;&gt;(feat. a Safe AST Calculator)&lt;/em&gt;&lt;/p&gt;
&lt;blockquote data-end=&quot;965&quot; data-start=&quot;768&quot;&gt;
&lt;p data-end=&quot;965&quot; data-start=&quot;770&quot;&gt;A practical guide to wiring a &lt;strong data-end=&quot;816&quot; data-start=&quot;800&quot;&gt;reason + act&lt;/strong&gt; loop that’s auditable, stable, and observable. We use &lt;strong data-end=&quot;884&quot; data-start=&quot;871&quot;&gt;LangGraph&lt;/strong&gt; for orchestration and a &lt;strong data-end=&quot;936&quot; data-start=&quot;909&quot;&gt;hardened AST calculator&lt;/strong&gt; for math, no evals, no vibes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-end=&quot;986&quot; data-start=&quot;967&quot;&gt;Why this pattern&lt;/h2&gt;
&lt;ul data-end=&quot;1383&quot; data-start=&quot;988&quot;&gt;
&lt;li data-end=&quot;1107&quot; data-start=&quot;988&quot;&gt;
&lt;p data-end=&quot;1107&quot; data-start=&quot;990&quot;&gt;&lt;strong data-end=&quot;1017&quot; data-start=&quot;990&quot;&gt;Determinism over vibes.&lt;/strong&gt; The calculator is pure Python AST (whitelisted ops), so results are exact and testable.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1254&quot; data-start=&quot;1108&quot;&gt;
&lt;p data-end=&quot;1254&quot; data-start=&quot;1110&quot;&gt;&lt;strong data-end=&quot;1130&quot; data-start=&quot;1110&quot;&gt;Controlled loop.&lt;/strong&gt; First model step is &lt;strong data-end=&quot;1161&quot; data-start=&quot;1151&quot;&gt;forced&lt;/strong&gt; to call the tool; the second &lt;strong data-end=&quot;1201&quot; data-start=&quot;1191&quot;&gt;cannot&lt;/strong&gt; call tools and must answer. No infinite ping-pong.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1383&quot; data-start=&quot;1255&quot;&gt;
&lt;p data-end=&quot;1383&quot; data-start=&quot;1257&quot;&gt;&lt;strong data-end=&quot;1275&quot; data-start=&quot;1257&quot;&gt;Observability.&lt;/strong&gt; Streamed console output shows precisely when the &lt;strong data-end=&quot;1332&quot; data-start=&quot;1325&quot;&gt;LLM&lt;/strong&gt; runs vs. when the &lt;strong data-end=&quot;1359&quot; data-start=&quot;1351&quot;&gt;tool&lt;/strong&gt; runs, with token usage.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1388&quot; data-start=&quot;1385&quot; /&gt;
&lt;h2 data-end=&quot;1417&quot; data-start=&quot;1390&quot;&gt;Architecture at a glance&lt;/h2&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;2154&quot; data-start=&quot;1419&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre!&quot;&gt;┌──────────────┐     (tools_condition)      ┌────────────┐
│   agent LLM  │ ─────────────────────────► │  ToolNode  │
│ (forced &lt;span class=&quot;hljs-number&quot;&gt;1s&lt;/span&gt;t) │                            │ calculator │
└──────┬───────┘                            └─────┬──────┘
       │                                          │ ToolMessage
       │ no tool calls → END                      ▼
       │                                    ┌──────────────┐
       └─────────────────────────────────── │  agent LLM   │
                                            │ (free finish)│
                                            └──────┬───────┘
                                                   │ AiMessage (final)
                                                   ▼ END
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;2171&quot; data-start=&quot;2156&quot;&gt;Key primitives:&lt;/p&gt;
&lt;ul data-end=&quot;2497&quot; data-start=&quot;2172&quot;&gt;
&lt;li data-end=&quot;2259&quot; data-start=&quot;2172&quot;&gt;
&lt;p data-end=&quot;2259&quot; data-start=&quot;2174&quot;&gt;&lt;strong data-end=&quot;2193&quot; data-start=&quot;2174&quot;&gt;&lt;code data-end=&quot;2191&quot; data-start=&quot;2176&quot;&gt;MessagesState&lt;/code&gt;&lt;/strong&gt;: appends messages, preserving the OpenAI tool-calling protocol.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;2304&quot; data-start=&quot;2260&quot;&gt;
&lt;p data-end=&quot;2304&quot; data-start=&quot;2262&quot;&gt;&lt;strong data-end=&quot;2276&quot; data-start=&quot;2262&quot;&gt;&lt;code data-end=&quot;2274&quot; data-start=&quot;2264&quot;&gt;ToolNode&lt;/code&gt;&lt;/strong&gt;: executes declared tools.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;2392&quot; data-start=&quot;2305&quot;&gt;
&lt;p data-end=&quot;2392&quot; data-start=&quot;2307&quot;&gt;&lt;strong data-end=&quot;2328&quot; data-start=&quot;2307&quot;&gt;&lt;code data-end=&quot;2326&quot; data-start=&quot;2309&quot;&gt;tools_condition&lt;/code&gt;&lt;/strong&gt;: routes to tools only when the assistant produced tool calls.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;2497&quot; data-start=&quot;2393&quot;&gt;
&lt;p data-end=&quot;2497&quot; data-start=&quot;2395&quot;&gt;&lt;strong data-end=&quot;2419&quot; data-start=&quot;2395&quot;&gt;One-tool-call policy&lt;/strong&gt;: first LLM is &lt;strong data-end=&quot;2443&quot; data-start=&quot;2434&quot;&gt;bound&lt;/strong&gt; to the calculator; the finisher LLM is &lt;strong data-end=&quot;2496&quot; data-start=&quot;2483&quot;&gt;tool-free&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;2502&quot; data-start=&quot;2499&quot; /&gt;
&lt;h2 data-end=&quot;2536&quot; data-start=&quot;2504&quot;&gt;The safe calculator (snippet)&lt;/h2&gt;
&lt;p data-end=&quot;2609&quot; data-start=&quot;2538&quot;&gt;&lt;strong data-end=&quot;2547&quot; data-start=&quot;2538&quot;&gt;Goal:&lt;/strong&gt; evaluate math deterministically, with a tiny NL preprocessor.&lt;/p&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;4244&quot; data-start=&quot;2611&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-python&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Whitelisted ops only&lt;/span&gt;
_ALLOWED_OPS = {ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul,
                ast.Div: op.truediv, ast.Pow: op.&lt;span class=&quot;hljs-built_in&quot;&gt;pow&lt;/span&gt;, ast.Mod: op.mod,
                ast.UAdd: op.pos, ast.USub: op.neg}

&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;_preprocess&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;s: &lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;&lt;/span&gt;) -&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;:
    s = s.strip().lower()
    s = re.sub(&lt;span class=&quot;hljs-string&quot;&gt;r&quot;\b(please|thanks|thank you|what\s*is|what&#39;s|calculate|compute|equals?)\b&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;&quot;&lt;/span&gt;, s)
    s = re.sub(&lt;span class=&quot;hljs-string&quot;&gt;r&quot;\bthe\b&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;&quot;&lt;/span&gt;, s)
    s = re.sub(&lt;span class=&quot;hljs-string&quot;&gt;r&quot;\bplus\b&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;+&quot;&lt;/span&gt;, s); s = re.sub(&lt;span class=&quot;hljs-string&quot;&gt;r&quot;\bminus\b&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;-&quot;&lt;/span&gt;, s)
    s = re.sub(&lt;span class=&quot;hljs-string&quot;&gt;r&quot;\btimes\b&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;*&quot;&lt;/span&gt;, s); s = re.sub(&lt;span class=&quot;hljs-string&quot;&gt;r&quot;\bdivided by\b&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;/&quot;&lt;/span&gt;, s)
    s = re.sub(&lt;span class=&quot;hljs-string&quot;&gt;r&quot;\b(?:the\s+)?square root of\b&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;sqrt(&quot;&lt;/span&gt;, s)
    s = re.sub(&lt;span class=&quot;hljs-string&quot;&gt;r&quot;√\s*&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;sqrt(&quot;&lt;/span&gt;, s)
    s = re.sub(&lt;span class=&quot;hljs-string&quot;&gt;r&quot;sqrt\(\s*([0-9\.]+)\s*\)?&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;r&quot;sqrt(\1)&quot;&lt;/span&gt;, s)
    s = re.sub(&lt;span class=&quot;hljs-string&quot;&gt;r&quot;(\d+(?:\.\d+)?)\s*%\s*of\s*([0-9\.]+)&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;r&quot;(\1/100*\2)&quot;&lt;/span&gt;, s)
    s = re.sub(&lt;span class=&quot;hljs-string&quot;&gt;r&quot;(\d+(?:\.\d+)?)\s*%&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;r&quot;(\1/100)&quot;&lt;/span&gt;, s)
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; s.replace(&lt;span class=&quot;hljs-string&quot;&gt;&quot;^&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;**&quot;&lt;/span&gt;).strip()

&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;_eval_ast&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;node: ast.AST&lt;/span&gt;) -&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;float&lt;/span&gt;:
    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;isinstance&lt;/span&gt;(node, ast.Constant) &lt;span class=&quot;hljs-keyword&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;isinstance&lt;/span&gt;(node.value, (&lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt;, &lt;span class=&quot;hljs-built_in&quot;&gt;float&lt;/span&gt;)):
        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;float&lt;/span&gt;(node.value)
    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;isinstance&lt;/span&gt;(node, ast.UnaryOp) &lt;span class=&quot;hljs-keyword&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;type&lt;/span&gt;(node.op) &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; _ALLOWED_OPS:
        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; _ALLOWED_OPS[&lt;span class=&quot;hljs-built_in&quot;&gt;type&lt;/span&gt;(node.op)](_eval_ast(node.operand))
    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;isinstance&lt;/span&gt;(node, ast.BinOp) &lt;span class=&quot;hljs-keyword&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;type&lt;/span&gt;(node.op) &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; _ALLOWED_OPS:
        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; _ALLOWED_OPS[&lt;span class=&quot;hljs-built_in&quot;&gt;type&lt;/span&gt;(node.op)](_eval_ast(node.left), _eval_ast(node.right))
    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;isinstance&lt;/span&gt;(node, ast.Call) &lt;span class=&quot;hljs-keyword&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;isinstance&lt;/span&gt;(node.func, ast.Name) &lt;span class=&quot;hljs-keyword&quot;&gt;and&lt;/span&gt; node.func.&lt;span class=&quot;hljs-built_in&quot;&gt;id&lt;/span&gt; == &lt;span class=&quot;hljs-string&quot;&gt;&quot;sqrt&quot;&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;len&lt;/span&gt;(node.args) == &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;:
        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; math.sqrt(_eval_ast(node.args[&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;]))
    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;isinstance&lt;/span&gt;(node, ast.Expr):
        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; _eval_ast(node.value)
    &lt;span class=&quot;hljs-keyword&quot;&gt;raise&lt;/span&gt; ValueError(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Unsupported syntax.&quot;&lt;/span&gt;)
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;4259&quot; data-start=&quot;4246&quot;&gt;Tool wrapper:&lt;/p&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;4540&quot; data-start=&quot;4261&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-python&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;@tool(&lt;span class=&quot;hljs-params&quot;&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;calculator_tool&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;calculator_tool&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;expression: &lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;&lt;/span&gt;) -&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;:
    &lt;span class=&quot;hljs-keyword&quot;&gt;try&lt;/span&gt;:
        pre = _preprocess(expression)
        value = _eval_ast(ast.parse(pre, mode=&lt;span class=&quot;hljs-string&quot;&gt;&quot;eval&quot;&lt;/span&gt;).body)
        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;(&lt;span class=&quot;hljs-built_in&quot;&gt;float&lt;/span&gt;(value))
    &lt;span class=&quot;hljs-keyword&quot;&gt;except&lt;/span&gt; Exception &lt;span class=&quot;hljs-keyword&quot;&gt;as&lt;/span&gt; e:
        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;f&quot;Error: &lt;span class=&quot;hljs-subst&quot;&gt;{e}&lt;/span&gt;&lt;/span&gt;&quot;
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;4680&quot; data-start=&quot;4542&quot;&gt;&lt;strong data-end=&quot;4563&quot; data-start=&quot;4542&quot;&gt;Why this is safe:&lt;/strong&gt; Only numeric literals and whitelisted operators/functions are allowed; no names, attrs, or imports can ever execute.&lt;/p&gt;
&lt;hr data-end=&quot;4685&quot; data-start=&quot;4682&quot; /&gt;
&lt;h2 data-end=&quot;4716&quot; data-start=&quot;4687&quot;&gt;LangGraph wiring (snippet)&lt;/h2&gt;
&lt;p data-end=&quot;4816&quot; data-start=&quot;4718&quot;&gt;Bind two LLM phases: &lt;strong data-end=&quot;4755&quot; data-start=&quot;4739&quot;&gt;forced first&lt;/strong&gt; (must call calculator) and &lt;strong data-end=&quot;4798&quot; data-start=&quot;4783&quot;&gt;free finish&lt;/strong&gt; (no tools bound).&lt;/p&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;5808&quot; data-start=&quot;4818&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-python&quot;&gt;llm_forced_first = ChatOpenAI(...).bind_tools(
    [calculator_tool],
    tool_choice={&lt;span class=&quot;hljs-string&quot;&gt;&quot;type&quot;&lt;/span&gt;:&lt;span class=&quot;hljs-string&quot;&gt;&quot;function&quot;&lt;/span&gt;,&lt;span class=&quot;hljs-string&quot;&gt;&quot;function&quot;&lt;/span&gt;:{&lt;span class=&quot;hljs-string&quot;&gt;&quot;name&quot;&lt;/span&gt;:&lt;span class=&quot;hljs-string&quot;&gt;&quot;calculator_tool&quot;&lt;/span&gt;}}
)
llm_free = ChatOpenAI(...)

&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;_has_calc_tool_call&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;msgs&lt;/span&gt;) -&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;bool&lt;/span&gt;:
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;any&lt;/span&gt;(&lt;span class=&quot;hljs-built_in&quot;&gt;isinstance&lt;/span&gt;(m, ToolMessage) &lt;span class=&quot;hljs-keyword&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;getattr&lt;/span&gt;(m,&lt;span class=&quot;hljs-string&quot;&gt;&quot;name&quot;&lt;/span&gt;,&lt;span class=&quot;hljs-string&quot;&gt;&quot;&quot;&lt;/span&gt;)==&lt;span class=&quot;hljs-string&quot;&gt;&quot;calculator_tool&quot;&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; m &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; msgs)

&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;agent_node&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;state: MessagesState&lt;/span&gt;):
    first_phase = &lt;span class=&quot;hljs-keyword&quot;&gt;not&lt;/span&gt; _has_calc_tool_call(state[&lt;span class=&quot;hljs-string&quot;&gt;&quot;messages&quot;&lt;/span&gt;])
    llm = llm_forced_first &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; first_phase &lt;span class=&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; llm_free
    ai = llm.invoke([SystemMessage(content=SYSTEM_RULE), *state[&lt;span class=&quot;hljs-string&quot;&gt;&quot;messages&quot;&lt;/span&gt;]],
                    config={&lt;span class=&quot;hljs-string&quot;&gt;&quot;tags&quot;&lt;/span&gt;: [&lt;span class=&quot;hljs-string&quot;&gt;&quot;agent_llm&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;forced_first&quot;&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; first_phase &lt;span class=&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;free_finish&quot;&lt;/span&gt;]})
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; {&lt;span class=&quot;hljs-string&quot;&gt;&quot;messages&quot;&lt;/span&gt;: [ai]}

tool_node = ToolNode([calculator_tool])

g = StateGraph(MessagesState)
g.add_node(&lt;span class=&quot;hljs-string&quot;&gt;&quot;agent&quot;&lt;/span&gt;, agent_node)
g.add_node(&lt;span class=&quot;hljs-string&quot;&gt;&quot;tools&quot;&lt;/span&gt;, tool_node)
g.add_conditional_edges(&lt;span class=&quot;hljs-string&quot;&gt;&quot;agent&quot;&lt;/span&gt;, tools_condition, {&lt;span class=&quot;hljs-string&quot;&gt;&quot;tools&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;tools&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;__end__&quot;&lt;/span&gt;: END})
g.add_edge(&lt;span class=&quot;hljs-string&quot;&gt;&quot;tools&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;agent&quot;&lt;/span&gt;)
g.set_entry_point(&lt;span class=&quot;hljs-string&quot;&gt;&quot;agent&quot;&lt;/span&gt;)
graph = g.&lt;span class=&quot;hljs-built_in&quot;&gt;compile&lt;/span&gt;()
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;5986&quot; data-start=&quot;5810&quot;&gt;&lt;strong data-end=&quot;5834&quot; data-start=&quot;5810&quot;&gt;Why it ends cleanly:&lt;/strong&gt; After tools run, the graph goes back to the agent &lt;strong data-end=&quot;5893&quot; data-start=&quot;5885&quot;&gt;once&lt;/strong&gt; to produce the final answer; &lt;code data-end=&quot;5940&quot; data-start=&quot;5923&quot;&gt;tools_condition&lt;/code&gt; routes to &lt;code data-end=&quot;5956&quot; data-start=&quot;5951&quot;&gt;END&lt;/code&gt; if no tool calls are present.&lt;/p&gt;
&lt;hr data-end=&quot;5991&quot; data-start=&quot;5988&quot; /&gt;
&lt;h2 data-end=&quot;6044&quot; data-start=&quot;5993&quot;&gt;Observability: know exactly when the LLM is used&lt;/h2&gt;
&lt;p data-end=&quot;6132&quot; data-start=&quot;6046&quot;&gt;Add a tiny callback to print LLM start/end and token usage; gate with &lt;code data-end=&quot;6131&quot; data-start=&quot;6116&quot;&gt;DEBUG_AGENT=1&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;6764&quot; data-start=&quot;6134&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;ConsoleLLMHandler&lt;/span&gt;(&lt;span class=&quot;hljs-title class_ inherited__&quot;&gt;BaseCallbackHandler&lt;/span&gt;):
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;on_llm_start&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self, serialized, prompts, **kw&lt;/span&gt;):
        &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; os.getenv(&lt;span class=&quot;hljs-string&quot;&gt;&quot;DEBUG_AGENT&quot;&lt;/span&gt;)==&lt;span class=&quot;hljs-string&quot;&gt;&quot;1&quot;&lt;/span&gt;:
            &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;f&quot;[LLM START] &lt;span class=&quot;hljs-subst&quot;&gt;{serialized.get(&lt;span class=&quot;hljs-string&quot;&gt;&#39;name&#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;,&lt;span class=&quot;hljs-string&quot;&gt;&#39;LLM&#39;&lt;/span&gt;)} | tags=&lt;span class=&quot;hljs-subst&quot;&gt;{kw.get(&lt;span class=&quot;hljs-string&quot;&gt;&#39;tags&#39;&lt;/span&gt;&lt;/span&gt;,[])}&quot;, file=sys.stderr)
    &lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;on_llm_end&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;self, result, **kw&lt;/span&gt;):
        &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; os.getenv(&lt;span class=&quot;hljs-string&quot;&gt;&quot;DEBUG_AGENT&quot;&lt;/span&gt;)==&lt;span class=&quot;hljs-string&quot;&gt;&quot;1&quot;&lt;/span&gt;:
            &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;f&quot;[LLM END] tags=&lt;span class=&quot;hljs-subst&quot;&gt;{kw.get(&lt;span class=&quot;hljs-string&quot;&gt;&#39;tags&#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;,[])} | usage=&lt;span class=&quot;hljs-subst&quot;&gt;{(result.llm_output &lt;span class=&quot;hljs-keyword&quot;&gt;or&lt;/span&gt;&lt;/span&gt; {}).get(&#39;token_usage&#39;,&lt;span class=&quot;hljs-subst&quot;&gt;{}&lt;/span&gt;)}&quot;, file=sys.stderr)

handler = ConsoleLLMHandler()
llm_forced_first = ChatOpenAI(..., callbacks=[handler]).bind_tools(...)
llm_free = ChatOpenAI(..., callbacks=[handler])
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;6847&quot; data-start=&quot;6766&quot;&gt;&lt;strong data-end=&quot;6779&quot; data-start=&quot;6766&quot;&gt;Optional:&lt;/strong&gt; &lt;code data-end=&quot;6794&quot; data-start=&quot;6780&quot;&gt;DEBUG_CALC=1&lt;/code&gt; prints NL → normalized math expression for the tool.&lt;/p&gt;
&lt;hr data-end=&quot;6852&quot; data-start=&quot;6849&quot; /&gt;
&lt;h2 data-end=&quot;6867&quot; data-start=&quot;6854&quot;&gt;Running it&lt;/h2&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;7045&quot; data-start=&quot;6869&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;export&lt;/span&gt; OPENAI_API_KEY=sk-...
&lt;span class=&quot;hljs-comment&quot;&gt;# telemetry on:&lt;/span&gt;
&lt;span class=&quot;hljs-built_in&quot;&gt;export&lt;/span&gt; DEBUG_AGENT=1
&lt;span class=&quot;hljs-built_in&quot;&gt;export&lt;/span&gt; DEBUG_CALC=1

python calculator_agent.py &lt;span class=&quot;hljs-string&quot;&gt;&quot;Calculate 12% of 255 plus the square root of 244&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;7071&quot; data-start=&quot;7047&quot;&gt;Sample (trimmed) output:&lt;/p&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;7375&quot; data-start=&quot;7073&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;================================ Human Message =================================

Calculate 12% of 255 plus the square root of 244

[LLM START] ChatOpenAI | tags=[&#39;seq:step:1&#39;, &#39;agent_llm&#39;, &#39;forced_first&#39;]
[LLM END] tags=[&#39;seq:step:1&#39;, &#39;agent_llm&#39;, &#39;forced_first&#39;] | usage={&#39;completion_tokens&#39;: 14, &#39;prompt_tokens&#39;: 161, &#39;total_tokens&#39;: 175, &#39;completion_tokens_details&#39;: {&#39;accepted_prediction_tokens&#39;: 0, &#39;audio_tokens&#39;: 0, &#39;reasoning_tokens&#39;: 0, &#39;rejected_prediction_tokens&#39;: 0}, &#39;prompt_tokens_details&#39;: {&#39;audio_tokens&#39;: 0, &#39;cached_tokens&#39;: 0}}
================================== Ai Message ==================================
Tool Calls:
  calculator_tool (call_134y7dqSU2udc2rr0OTNHKQs)
  Args:
    expression: 12% of 255 plus sqrt(244)

================================= Tool Message =================================
Name: calculator_tool

46.2204993518133

[LLM START] ChatOpenAI | tags=[&#39;seq:step:1&#39;, &#39;agent_llm&#39;, &#39;free_finish&#39;]
[LLM END] tags=[&#39;seq:step:1&#39;, &#39;agent_llm&#39;, &#39;free_finish&#39;] | usage={&#39;completion_tokens&#39;: 23, &#39;prompt_tokens&#39;: 121, &#39;total_tokens&#39;: 144, &#39;completion_tokens_details&#39;: {&#39;accepted_prediction_tokens&#39;: 0, &#39;audio_tokens&#39;: 0, &#39;reasoning_tokens&#39;: 0, &#39;rejected_prediction_tokens&#39;: 0}, &#39;prompt_tokens_details&#39;: {&#39;audio_tokens&#39;: 0, &#39;cached_tokens&#39;: 0}}
================================== Ai Message ==================================

&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre!&quot;&gt;The result of 12% of 255 plus the square root of 244 is approximately 46.22.
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;hr data-end=&quot;7380&quot; data-start=&quot;7377&quot; /&gt;
&lt;h2 data-end=&quot;7412&quot; data-start=&quot;7382&quot;&gt;Design choices: pros &amp;amp; cons&lt;/h2&gt;
&lt;p data-end=&quot;7422&quot; data-start=&quot;7414&quot;&gt;&lt;strong data-end=&quot;7422&quot; data-start=&quot;7414&quot;&gt;Pros&lt;/strong&gt;&lt;/p&gt;
&lt;ul data-end=&quot;7650&quot; data-start=&quot;7423&quot;&gt;
&lt;li data-end=&quot;7488&quot; data-start=&quot;7423&quot;&gt;
&lt;p data-end=&quot;7488&quot; data-start=&quot;7425&quot;&gt;&lt;strong data-end=&quot;7442&quot; data-start=&quot;7425&quot;&gt;Deterministic&lt;/strong&gt; math; no LLM hallucinations for arithmetic.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;7554&quot; data-start=&quot;7489&quot;&gt;
&lt;p data-end=&quot;7554&quot; data-start=&quot;7491&quot;&gt;&lt;strong data-end=&quot;7511&quot; data-start=&quot;7491&quot;&gt;Single tool call&lt;/strong&gt; guarantees predictable latency and flow.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;7650&quot; data-start=&quot;7555&quot;&gt;
&lt;p data-end=&quot;7650&quot; data-start=&quot;7557&quot;&gt;&lt;strong data-end=&quot;7569&quot; data-start=&quot;7557&quot;&gt;Great DX&lt;/strong&gt;: streaming, token accounting, and NL conveniences (&lt;code data-end=&quot;7627&quot; data-start=&quot;7621&quot;&gt;% of&lt;/code&gt;, &lt;code data-end=&quot;7632&quot; data-start=&quot;7629&quot;&gt;√&lt;/code&gt;, word operators).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;7660&quot; data-start=&quot;7652&quot;&gt;&lt;strong data-end=&quot;7660&quot; data-start=&quot;7652&quot;&gt;Cons&lt;/strong&gt;&lt;/p&gt;
&lt;ul data-end=&quot;8433&quot; data-start=&quot;7923&quot;&gt;
&lt;li data-end=&quot;7751&quot; data-start=&quot;7661&quot;&gt;
&lt;p data-end=&quot;7751&quot; data-start=&quot;7663&quot;&gt;Narrow by design (&lt;code data-end=&quot;7687&quot; data-start=&quot;7681&quot;&gt;sqrt&lt;/code&gt;, basic ops). Extend via whitelist if you need &lt;code data-end=&quot;7748&quot; data-start=&quot;7734&quot;&gt;log/exp/pi/e&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;7821&quot; data-start=&quot;7752&quot;&gt;
&lt;p data-end=&quot;7821&quot; data-start=&quot;7754&quot;&gt;Requires an LLM key for orchestration (the math itself is local).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;7884&quot; data-start=&quot;7822&quot;&gt;
&lt;p data-end=&quot;7884&quot; data-start=&quot;7824&quot;&gt;NL preprocessor is conservative to keep parsing predictable.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;8438&quot; data-start=&quot;8435&quot; /&gt;
&lt;h2 data-end=&quot;8456&quot; data-start=&quot;8440&quot;&gt;Code &amp;amp; assets&lt;/h2&gt;
&lt;ul data-end=&quot;8618&quot; data-start=&quot;8458&quot;&gt;
&lt;li data-end=&quot;8513&quot; data-start=&quot;8458&quot;&gt;
&lt;p data-end=&quot;8513&quot; data-start=&quot;8460&quot;&gt;&lt;strong data-end=&quot;8471&quot; data-start=&quot;8460&quot;&gt;Source:&lt;/strong&gt; &lt;span data-end=&quot;8511&quot; data-start=&quot;8472&quot;&gt;&lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;&lt;a href=&quot;https://github.com/JordiCorbilla/langgraph-cookbook&quot;&gt;https://github.com/JordiCorbilla/langgraph-cookbook&lt;/a&gt;&lt;/mi&gt;&lt;/mrow&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;8623&quot; data-start=&quot;8620&quot; /&gt;
&lt;h3 data-end=&quot;8645&quot; data-start=&quot;8625&quot;&gt;Snippet appendix&lt;/h3&gt;
&lt;p data-end=&quot;8687&quot; data-start=&quot;8647&quot;&gt;&lt;strong data-end=&quot;8687&quot; data-start=&quot;8647&quot;&gt;System rule (keeps the agent honest)&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;8915&quot; data-start=&quot;8689&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-python&quot;&gt;SYSTEM_RULE = (
  &lt;span class=&quot;hljs-string&quot;&gt;&quot;You are a math assistant. For ANY numeric computation, &quot;&lt;/span&gt;
  &lt;span class=&quot;hljs-string&quot;&gt;&quot;call `calculator_tool` exactly once, then return the final answer. &quot;&lt;/span&gt;
  &lt;span class=&quot;hljs-string&quot;&gt;&quot;Do not call tools again after you have the numeric result.&quot;&lt;/span&gt;
)
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;8936&quot; data-start=&quot;8917&quot;&gt;&lt;strong data-end=&quot;8936&quot; data-start=&quot;8917&quot;&gt;Smoke test idea&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;9042&quot; data-start=&quot;8938&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-python&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Expect: ≥2 AI messages (forced+final), exactly 1 ToolMessage, final AI has no tool_calls&lt;/span&gt;&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;</description><link>https://thundaxsoftware.blogspot.com/2025/09/react-for-real-building-deterministic.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKxkA-9Wcigqx224NpjGb5mGJWreFqkrm8T8ZqUs71-6wfDp5MorUDOwo9klqkZjGnwNM05Uka_12GY7vGwAc6vfnmT5k0dplSgoU_cOTP1sJAr8QDlxw6ToS-aSyOi7uzyKYj2xi9nTVPycYyCbplUr4h_kW9kRkH0zb6zzsTO-pjm2bCmGJk91JZ9W4/s72-c/ChatGPT%20Image%20Sep%2019,%202025,%2007_48_35%20AM.png" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>London, UK</georss:featurename><georss:point>51.5072178 -0.1275862</georss:point><georss:box>23.196983963821154 -35.2838362 79.817451636178845 35.0286638</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-3226449771189446065</guid><pubDate>Sun, 14 Sep 2025 11:57:00 +0000</pubDate><atom:updated>2025-09-14T11:57:48.577+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">AI Agents</category><category domain="http://www.blogger.com/atom/ns#">chunked summarization</category><category domain="http://www.blogger.com/atom/ns#">fixed chain</category><category domain="http://www.blogger.com/atom/ns#">function calling</category><category domain="http://www.blogger.com/atom/ns#">LangChain</category><category domain="http://www.blogger.com/atom/ns#">LLM tool calling</category><category domain="http://www.blogger.com/atom/ns#">map-reduce</category><category domain="http://www.blogger.com/atom/ns#">OpenAI GPT-4o</category><category domain="http://www.blogger.com/atom/ns#">Python CLI</category><category domain="http://www.blogger.com/atom/ns#">RAG</category><category domain="http://www.blogger.com/atom/ns#">recursive agent</category><category domain="http://www.blogger.com/atom/ns#">YouTube summarization</category><category domain="http://www.blogger.com/atom/ns#">youtube-transcript-api</category><category domain="http://www.blogger.com/atom/ns#">yt-dlp</category><title>A Practical, Production-Ready Tool for Summarizing YouTube Videos with LLMs and Agents</title><description>&lt;p&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgF1o_DfQwCZlI9sxzkHy3dpf97E_J4dmJQiePAiWVxtZDoqVsYVdxOoEgaB6_02ukpe_lZDBFAEY2m9KDBXjUsDS0PqTuDsuobkyMmXsV1oOBtssw32A5NZnWz3oUCwR43Kn7GahuG_86ohEcPc2xIhOlAM1PVOClcaZJp5iIg5uTeJEfec4NJbLh2LSc/s1536/ChatGPT%20Image%20Sep%2013,%202025,%2001_20_50%20PM.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; display: inline !important; float: left; margin-bottom: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1536&quot; height=&quot;213&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgF1o_DfQwCZlI9sxzkHy3dpf97E_J4dmJQiePAiWVxtZDoqVsYVdxOoEgaB6_02ukpe_lZDBFAEY2m9KDBXjUsDS0PqTuDsuobkyMmXsV1oOBtssw32A5NZnWz3oUCwR43Kn7GahuG_86ohEcPc2xIhOlAM1PVOClcaZJp5iIg5uTeJEfec4NJbLh2LSc/s320/ChatGPT%20Image%20Sep%2013,%202025,%2001_20_50%20PM.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;A Practical, Production-Ready Tool for Summarizing YouTube Videos with LLMs and Agents&lt;/h2&gt;&lt;p&gt;&lt;/p&gt;
&lt;blockquote data-end=&quot;352&quot; data-start=&quot;90&quot;&gt;
&lt;p data-end=&quot;352&quot; data-start=&quot;92&quot;&gt;This post is about a &lt;strong data-end=&quot;121&quot; data-start=&quot;113&quot;&gt;tool&lt;/strong&gt;. It’s a CLI that uses &lt;strong data-end=&quot;175&quot; data-start=&quot;155&quot;&gt;LLM tool calling&lt;/strong&gt; and a tiny agent loop to extract IDs, fetch transcripts/metadata, and produce high-quality summaries of &lt;strong data-end=&quot;288&quot; data-start=&quot;280&quot;&gt;long&lt;/strong&gt; YouTube videos without tripping rate limits or context windows.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-end=&quot;357&quot; data-start=&quot;354&quot; /&gt;
&lt;h2 data-end=&quot;367&quot; data-start=&quot;359&quot;&gt;TL;DR&lt;/h2&gt;
&lt;div class=&quot;_tableContainer_1rjym_1&quot;&gt;&lt;div class=&quot;group w-fit _tableWrapper_1rjym_13 flex flex-col-reverse&quot; tabindex=&quot;-1&quot;&gt;&lt;table class=&quot;w-fit min-w-(--thread-content-width)&quot; data-end=&quot;830&quot; data-start=&quot;369&quot;&gt;&lt;thead data-end=&quot;429&quot; data-start=&quot;369&quot;&gt;&lt;tr data-end=&quot;429&quot; data-start=&quot;369&quot;&gt;&lt;th data-col-size=&quot;md&quot; data-end=&quot;382&quot; data-start=&quot;369&quot;&gt;What it is&lt;/th&gt;&lt;th data-col-size=&quot;md&quot; data-end=&quot;400&quot; data-start=&quot;382&quot;&gt;Why it’s useful&lt;/th&gt;&lt;th data-col-size=&quot;lg&quot; data-end=&quot;415&quot; data-start=&quot;400&quot;&gt;How it works&lt;/th&gt;&lt;th data-col-size=&quot;md&quot; data-end=&quot;429&quot; data-start=&quot;415&quot;&gt;How to run&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody data-end=&quot;830&quot; data-start=&quot;448&quot;&gt;&lt;tr data-end=&quot;830&quot; data-start=&quot;448&quot;&gt;&lt;td data-col-size=&quot;md&quot; data-end=&quot;531&quot; data-start=&quot;448&quot;&gt;A &lt;strong data-end=&quot;464&quot; data-start=&quot;452&quot;&gt;CLI tool&lt;/strong&gt; that summarizes YouTube videos using an &lt;strong data-end=&quot;530&quot; data-start=&quot;505&quot;&gt;LLM with tools/agents&lt;/strong&gt;&lt;/td&gt;&lt;td data-col-size=&quot;md&quot; data-end=&quot;607&quot; data-start=&quot;531&quot;&gt;Handles &lt;strong data-end=&quot;556&quot; data-start=&quot;541&quot;&gt;long videos&lt;/strong&gt; and real-world edge cases (no transcript, quotas)&lt;/td&gt;&lt;td data-col-size=&quot;lg&quot; data-end=&quot;752&quot; data-start=&quot;607&quot;&gt;LangChain &lt;strong data-end=&quot;659&quot; data-start=&quot;619&quot;&gt;tool calling&lt;/strong&gt;+ fixed/recursive chains + &lt;strong data-end=&quot;709&quot; data-start=&quot;687&quot;&gt;chunked map-reduce&lt;/strong&gt; summarization + &lt;strong data-end=&quot;751&quot; data-start=&quot;726&quot;&gt;bare-LLM finalization&lt;/strong&gt;&lt;/td&gt;&lt;td data-col-size=&quot;md&quot; data-end=&quot;830&quot; data-start=&quot;752&quot;&gt;&lt;code data-end=&quot;828&quot; data-start=&quot;754&quot;&gt;python youtube_tool_agent.py summarize --url &quot;&amp;lt;video-url&amp;gt;&quot; --language en&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;/div&gt;
&lt;hr data-end=&quot;835&quot; data-start=&quot;832&quot; /&gt;
&lt;h2 data-end=&quot;859&quot; data-start=&quot;837&quot;&gt;What this tool does&lt;/h2&gt;
&lt;ul data-end=&quot;1451&quot; data-start=&quot;861&quot;&gt;
&lt;li data-end=&quot;939&quot; data-start=&quot;861&quot;&gt;
&lt;p data-end=&quot;939&quot; data-start=&quot;863&quot;&gt;&lt;strong data-end=&quot;875&quot; data-start=&quot;863&quot;&gt;Extracts&lt;/strong&gt; YouTube video IDs (robust to &lt;code data-end=&quot;915&quot; data-start=&quot;905&quot;&gt;watch?v=&lt;/code&gt;, &lt;code data-end=&quot;928&quot; data-start=&quot;917&quot;&gt;youtu.be/&lt;/code&gt;, &lt;code data-end=&quot;938&quot; data-start=&quot;930&quot;&gt;embed/&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1041&quot; data-start=&quot;940&quot;&gt;
&lt;p data-end=&quot;1041&quot; data-start=&quot;942&quot;&gt;&lt;strong data-end=&quot;965&quot; data-start=&quot;942&quot;&gt;Fetches transcripts&lt;/strong&gt; (and can be extended to fall back to &lt;strong data-end=&quot;1027&quot; data-start=&quot;1003&quot;&gt;captions/description&lt;/strong&gt; via &lt;code data-end=&quot;1040&quot; data-start=&quot;1032&quot;&gt;yt-dlp&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1133&quot; data-start=&quot;1042&quot;&gt;
&lt;p data-end=&quot;1133&quot; data-start=&quot;1044&quot;&gt;&lt;strong data-end=&quot;1075&quot; data-start=&quot;1044&quot;&gt;Pulls metadata &amp;amp; thumbnails&lt;/strong&gt; via &lt;code data-end=&quot;1088&quot; data-start=&quot;1080&quot;&gt;yt-dlp&lt;/code&gt; (title, views, likes, chapters, image sizes)&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1174&quot; data-start=&quot;1134&quot;&gt;
&lt;p data-end=&quot;1174&quot; data-start=&quot;1136&quot;&gt;&lt;strong data-end=&quot;1148&quot; data-start=&quot;1136&quot;&gt;Searches&lt;/strong&gt; YouTube by query (PyTube)&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1213&quot; data-start=&quot;1175&quot;&gt;
&lt;p data-end=&quot;1213&quot; data-start=&quot;1177&quot;&gt;&lt;strong data-end=&quot;1203&quot; data-start=&quot;1177&quot;&gt;(Best-effort) Trending&lt;/strong&gt; by region&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1359&quot; data-start=&quot;1214&quot;&gt;
&lt;p data-end=&quot;1359&quot; data-start=&quot;1216&quot;&gt;Generates &lt;strong data-end=&quot;1265&quot; data-start=&quot;1226&quot;&gt;succinct, expert-oriented summaries&lt;/strong&gt; of &lt;strong data-end=&quot;1277&quot; data-start=&quot;1269&quot;&gt;long&lt;/strong&gt; videos using a &lt;strong data-end=&quot;1315&quot; data-start=&quot;1293&quot;&gt;chunked map-reduce&lt;/strong&gt; flow that avoids &lt;strong data-end=&quot;1348&quot; data-start=&quot;1333&quot;&gt;TPM/context&lt;/strong&gt; explosions&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1451&quot; data-start=&quot;1360&quot;&gt;
&lt;p data-end=&quot;1451&quot; data-start=&quot;1362&quot;&gt;Includes a &lt;strong data-end=&quot;1380&quot; data-start=&quot;1373&quot;&gt;CLI&lt;/strong&gt; and &lt;strong data-end=&quot;1404&quot; data-start=&quot;1385&quot;&gt;verbose tracing&lt;/strong&gt; so you can see exactly what the agent is doing&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1456&quot; data-start=&quot;1453&quot; /&gt;
&lt;h2 data-end=&quot;1480&quot; data-start=&quot;1458&quot;&gt;Install &amp;amp; first run&lt;/h2&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;1818&quot; data-start=&quot;1482&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-bash&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Python 3.11+ recommended&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
pip install -r requirements.txt

&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Set your model key (OpenAI shown)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;export&lt;/span&gt;&lt;/span&gt;&lt;span&gt; OPENAI_API_KEY=&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;sk-...&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Windows PowerShell:  $env:OPENAI_API_KEY=&quot;sk-...&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;

&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Summarize a video&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
python youtube_tool_agent.py --verbose summarize \
  --url &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;https://www.youtube.com/watch?v=8TJQhQ2GZ0Y&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; \
  --language en
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;1845&quot; data-start=&quot;1820&quot;&gt;You’ll see progress like:&lt;/p&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;2058&quot; data-start=&quot;1846&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre!&quot;&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-meta&quot;&gt;LLM&lt;/span&gt;&lt;/span&gt;&lt;span&gt;] OpenAI provider model=gpt&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-number&quot;&gt;-4&lt;/span&gt;&lt;/span&gt;&lt;span&gt;o provider=openai
&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-meta&quot;&gt;SUM&lt;/span&gt;&lt;/span&gt;&lt;span&gt;] Chunk &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-number&quot;&gt;30000&lt;/span&gt;&lt;/span&gt;&lt;span&gt; chars)
[&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-meta&quot;&gt;SUM&lt;/span&gt;&lt;/span&gt;&lt;span&gt;] Chunk &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-number&quot;&gt;22007&lt;/span&gt;&lt;/span&gt;&lt;span&gt; chars)
&amp;lt;final polished summary printed here&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;hr data-end=&quot;2063&quot; data-start=&quot;2060&quot; /&gt;
&lt;h2 data-end=&quot;2096&quot; data-start=&quot;2065&quot;&gt;Why agents for this problem?&lt;/h2&gt;
&lt;p data-end=&quot;2166&quot; data-start=&quot;2098&quot;&gt;A “just call an API and prompt” script breaks on real-world YouTube:&lt;/p&gt;
&lt;ul data-end=&quot;2461&quot; data-start=&quot;2168&quot;&gt;
&lt;li data-end=&quot;2246&quot; data-start=&quot;2168&quot;&gt;
&lt;p data-end=&quot;2246&quot; data-start=&quot;2170&quot;&gt;Some videos have &lt;strong data-end=&quot;2213&quot; data-start=&quot;2187&quot;&gt;no official transcript&lt;/strong&gt; (only auto-captions or nothing).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;2335&quot; data-start=&quot;2247&quot;&gt;
&lt;p data-end=&quot;2335&quot; data-start=&quot;2249&quot;&gt;Transcripts can be &lt;strong data-end=&quot;2276&quot; data-start=&quot;2268&quot;&gt;huge&lt;/strong&gt;, exceeding model &lt;strong data-end=&quot;2305&quot; data-start=&quot;2294&quot;&gt;context&lt;/strong&gt; or your org’s &lt;strong data-end=&quot;2327&quot; data-start=&quot;2320&quot;&gt;TPM&lt;/strong&gt; limits.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;2461&quot; data-start=&quot;2336&quot;&gt;
&lt;p data-end=&quot;2461&quot; data-start=&quot;2338&quot;&gt;Tool-calling models often respond with &lt;strong data-end=&quot;2394&quot; data-start=&quot;2377&quot;&gt;empty content&lt;/strong&gt; + a request to call another tool, unless you orchestrate correctly.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2506&quot; data-start=&quot;2463&quot;&gt;An &lt;strong data-end=&quot;2475&quot; data-start=&quot;2466&quot;&gt;agent&lt;/strong&gt; (even a tiny one) solves this:&lt;/p&gt;
&lt;ul data-end=&quot;2814&quot; data-start=&quot;2508&quot;&gt;
&lt;li data-end=&quot;2607&quot; data-start=&quot;2508&quot;&gt;
&lt;p data-end=&quot;2607&quot; data-start=&quot;2510&quot;&gt;It &lt;strong data-end=&quot;2524&quot; data-start=&quot;2513&quot;&gt;decides&lt;/strong&gt; which tool to call and &lt;strong data-end=&quot;2565&quot; data-start=&quot;2548&quot;&gt;in what order&lt;/strong&gt; (ID → transcript → metadata → summarize).&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;2699&quot; data-start=&quot;2608&quot;&gt;
&lt;p data-end=&quot;2699&quot; data-start=&quot;2610&quot;&gt;It &lt;strong data-end=&quot;2622&quot; data-start=&quot;2613&quot;&gt;loops&lt;/strong&gt; until it has what it needs (recursive chain), then &lt;strong data-end=&quot;2687&quot; data-start=&quot;2674&quot;&gt;finalizes&lt;/strong&gt; the answer.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;2814&quot; data-start=&quot;2700&quot;&gt;
&lt;p data-end=&quot;2814&quot; data-start=&quot;2702&quot;&gt;You get &lt;strong data-end=&quot;2726&quot; data-start=&quot;2710&quot;&gt;traceability&lt;/strong&gt;: every tool call is logged with payload sizes so you can reason about cost and latency.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;2819&quot; data-start=&quot;2816&quot; /&gt;
&lt;h2 data-end=&quot;2870&quot; data-start=&quot;2821&quot;&gt;Core ideas (architectural choices that matter)&lt;/h2&gt;
&lt;h3 data-end=&quot;2903&quot; data-start=&quot;2872&quot;&gt;1) Tool calling (LangChain)&lt;/h3&gt;
&lt;p data-end=&quot;2977&quot; data-start=&quot;2904&quot;&gt;We expose capabilities as &lt;strong data-end=&quot;2949&quot; data-start=&quot;2930&quot;&gt;annotated tools&lt;/strong&gt; the model can call by name:&lt;/p&gt;
&lt;ul data-end=&quot;3099&quot; data-start=&quot;2978&quot;&gt;
&lt;li data-end=&quot;3099&quot; data-start=&quot;2978&quot;&gt;
&lt;p data-end=&quot;3099&quot; data-start=&quot;2980&quot;&gt;&lt;code data-end=&quot;2998&quot; data-start=&quot;2980&quot;&gt;extract_video_id&lt;/code&gt;, &lt;code data-end=&quot;3018&quot; data-start=&quot;3000&quot;&gt;fetch_transcript&lt;/code&gt;, &lt;code data-end=&quot;3039&quot; data-start=&quot;3020&quot;&gt;get_full_metadata&lt;/code&gt;, &lt;code data-end=&quot;3057&quot; data-start=&quot;3041&quot;&gt;get_thumbnails&lt;/code&gt;, &lt;code data-end=&quot;3075&quot; data-start=&quot;3059&quot;&gt;search_youtube&lt;/code&gt;, &lt;code data-end=&quot;3098&quot; data-start=&quot;3077&quot;&gt;get_trending_videos&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3204&quot; data-start=&quot;3101&quot;&gt;These functions return &lt;strong data-end=&quot;3146&quot; data-start=&quot;3124&quot;&gt;plain JSON/strings&lt;/strong&gt;, which keeps the glue simple and makes debugging obvious.&lt;/p&gt;
&lt;blockquote data-end=&quot;3362&quot; data-start=&quot;3206&quot;&gt;
&lt;p data-end=&quot;3362&quot; data-start=&quot;3208&quot;&gt;Source code can be found here:&amp;nbsp;&lt;/p&gt;&lt;a href=&quot;https://github.com/JordiCorbilla/langgraph-cookbook&quot;&gt;JordiCorbilla/langgraph-cookbook: langgraph-cookbook&lt;/a&gt;
&lt;/blockquote&gt;
&lt;h3 data-end=&quot;3394&quot; data-start=&quot;3364&quot;&gt;2) Two orchestration modes&lt;/h3&gt;
&lt;ul data-end=&quot;3667&quot; data-start=&quot;3396&quot;&gt;
&lt;li data-end=&quot;3532&quot; data-start=&quot;3396&quot;&gt;
&lt;p data-end=&quot;3446&quot; data-start=&quot;3398&quot;&gt;&lt;strong data-end=&quot;3413&quot; data-start=&quot;3398&quot;&gt;Fixed chain&lt;/strong&gt; for &lt;strong data-end=&quot;3445&quot; data-start=&quot;3418&quot;&gt;deterministic summarize&lt;/strong&gt;:&lt;/p&gt;
&lt;ol data-end=&quot;3532&quot; data-start=&quot;3449&quot;&gt;
&lt;li data-end=&quot;3532&quot; data-start=&quot;3449&quot;&gt;
&lt;p data-end=&quot;3532&quot; data-start=&quot;3452&quot;&gt;Extract ID → 2) Fetch transcript → 3) Summarize (chunked) → 4) Polish (bare LLM)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li data-end=&quot;3667&quot; data-start=&quot;3533&quot;&gt;
&lt;p data-end=&quot;3586&quot; data-start=&quot;3535&quot;&gt;&lt;strong data-end=&quot;3566&quot; data-start=&quot;3535&quot;&gt;Universal (recursive) chain&lt;/strong&gt; for &lt;strong data-end=&quot;3585&quot; data-start=&quot;3571&quot;&gt;open “ask”&lt;/strong&gt;:&lt;/p&gt;
&lt;ul data-end=&quot;3667&quot; data-start=&quot;3589&quot;&gt;
&lt;li data-end=&quot;3667&quot; data-start=&quot;3589&quot;&gt;
&lt;p data-end=&quot;3667&quot; data-start=&quot;3591&quot;&gt;Keep calling tools while the model requests them. Stop when it returns text.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;3708&quot; data-start=&quot;3669&quot;&gt;3) Chunked map-reduce summarization&lt;/h3&gt;
&lt;p data-end=&quot;3918&quot; data-start=&quot;3710&quot;&gt;&lt;strong data-end=&quot;3718&quot; data-start=&quot;3710&quot;&gt;Why:&lt;/strong&gt; long transcripts blow up &lt;strong data-end=&quot;3755&quot; data-start=&quot;3744&quot;&gt;context&lt;/strong&gt; and &lt;strong data-end=&quot;3767&quot; data-start=&quot;3760&quot;&gt;TPM&lt;/strong&gt;.&lt;br data-end=&quot;3771&quot; data-start=&quot;3768&quot; /&gt;
&lt;strong data-end=&quot;3779&quot; data-start=&quot;3771&quot;&gt;How:&lt;/strong&gt; split the transcript into overlapping chunks → summarize each chunk (map) → merge the bullet lists (reduce) → optional polish for clarity.&lt;/p&gt;
&lt;p data-end=&quot;3933&quot; data-start=&quot;3920&quot;&gt;Key tunables:&lt;/p&gt;
&lt;ul data-end=&quot;4086&quot; data-start=&quot;3934&quot;&gt;
&lt;li data-end=&quot;3996&quot; data-start=&quot;3934&quot;&gt;
&lt;p data-end=&quot;3996&quot; data-start=&quot;3936&quot;&gt;Chunk size: &lt;strong data-end=&quot;3963&quot; data-start=&quot;3948&quot;&gt;3k–4k chars&lt;/strong&gt; (or larger if you have headroom)&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;4025&quot; data-start=&quot;3997&quot;&gt;
&lt;p data-end=&quot;4025&quot; data-start=&quot;3999&quot;&gt;Overlap: &lt;strong data-end=&quot;4025&quot; data-start=&quot;4008&quot;&gt;200–300 chars&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;4086&quot; data-start=&quot;4026&quot;&gt;
&lt;p data-end=&quot;4086&quot; data-start=&quot;4028&quot;&gt;Short &lt;code data-end=&quot;4041&quot; data-start=&quot;4034&quot;&gt;sleep&lt;/code&gt; between chunk calls to smooth &lt;strong data-end=&quot;4079&quot; data-start=&quot;4072&quot;&gt;TPM&lt;/strong&gt; bursts&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;4116&quot; data-start=&quot;4088&quot;&gt;4) Bare-LLM finalization&lt;/h3&gt;
&lt;p data-end=&quot;4320&quot; data-start=&quot;4118&quot;&gt;Tool-calling models often return &lt;strong data-end=&quot;4168&quot; data-start=&quot;4151&quot;&gt;empty content&lt;/strong&gt; when they want another tool. The final step uses a &lt;strong data-end=&quot;4232&quot; data-start=&quot;4220&quot;&gt;bare LLM&lt;/strong&gt; (no tools bound). That forces a textual answer and &lt;strong data-end=&quot;4319&quot; data-start=&quot;4284&quot;&gt;prevents “one more tool?” loops&lt;/strong&gt;.&lt;/p&gt;&lt;h2 data-end=&quot;3554&quot; data-start=&quot;3527&quot;&gt;Architecture at a glance&lt;/h2&gt;&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;4499&quot; data-start=&quot;3556&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre!&quot;&gt;┌──────────────┐      ┌───────────────────────┐
│  CLI (args)  ├────▶ |  Orchestrator Chain   │
└─────┬────────┘      └──────────┬────────────┘
      │                           │  tool_calls
      │                           ▼
      │                 ┌─────────────────────┐
      │                 │  Tools (LangChain)  │
      │                 │  - extract_video_id │
      │                 │  - fetch_transcript │
      │                 │  - search_youtube   │
      │                 │  - get_full_metadata│
      │                 │  - get_thumbnails   │
      │                 └──────────┬──────────┘
      │                            │ results (text/JSON)
      │                            ▼
      │                   ┌─────────────────┐
      │                   │  Bare LLM Call  │  ← no-tools finalization
      │                   │ (map→reduce→polish)
      ▼                   └─────────────────┘
  stdout (summary)
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;p data-end=&quot;4320&quot; data-start=&quot;4118&quot;&gt;

&lt;/p&gt;&lt;ul data-end=&quot;4680&quot; data-start=&quot;4501&quot;&gt;
&lt;li data-end=&quot;4680&quot; data-start=&quot;4501&quot;&gt;
&lt;p data-end=&quot;4680&quot; data-start=&quot;4503&quot;&gt;&lt;strong data-end=&quot;4533&quot; data-start=&quot;4503&quot;&gt;Why “bare” LLM at the end?&lt;/strong&gt; Tool-calling models often return &lt;strong data-end=&quot;4586&quot; data-start=&quot;4567&quot;&gt;empty &lt;code data-end=&quot;4584&quot; data-start=&quot;4575&quot;&gt;content&lt;/code&gt;&lt;/strong&gt; when they still want to call tools. Finalizing with a &lt;strong data-end=&quot;4653&quot; data-start=&quot;4641&quot;&gt;no-tools&lt;/strong&gt; model guarantees &lt;strong data-end=&quot;4679&quot; data-start=&quot;4671&quot;&gt;text&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;hr data-end=&quot;4325&quot; data-start=&quot;4322&quot; /&gt;
&lt;h2 data-end=&quot;4353&quot; data-start=&quot;4327&quot;&gt;CLI you’ll actually use&lt;/h2&gt;
&lt;p data-end=&quot;4382&quot; data-start=&quot;4355&quot;&gt;Point tools (fast, no LLM):&lt;/p&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;4697&quot; data-start=&quot;4383&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-bash&quot;&gt;&lt;span&gt;&lt;span&gt;python youtube_tool_agent.py metadata   --url &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;https://youtu.be/8TJQhQ2GZ0Y&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
python youtube_tool_agent.py thumbnails --url &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;https://youtu.be/8TJQhQ2GZ0Y&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
python youtube_tool_agent.py search     --query &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;Retrieval-Augmented Generation&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
python youtube_tool_agent.py trending   --region GB&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;4730&quot; data-start=&quot;4699&quot;&gt;Deterministic, chunked summary:&lt;/p&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;4865&quot; data-start=&quot;4731&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-bash&quot;&gt;&lt;span&gt;&lt;span&gt;python youtube_tool_agent.py --verbose summarize \
  --url &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;https://www.youtube.com/watch?v=8TJQhQ2GZ0Y&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; \
  --language en
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;4894&quot; data-start=&quot;4867&quot;&gt;Agentic “do what it takes”:&lt;/p&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;5078&quot; data-start=&quot;4895&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-bash&quot;&gt;&lt;span&gt;&lt;span&gt;python youtube_tool_agent.py --verbose ask \
  --query &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;Summarize this YouTube video and explain the leveraging strategy (en): https://www.youtube.com/watch?v=8TJQhQ2GZ0Y&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;hr data-end=&quot;5083&quot; data-start=&quot;5080&quot; /&gt;
&lt;h2 data-end=&quot;5115&quot; data-start=&quot;5085&quot;&gt;Under the hood (high-level)&lt;/h2&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;5568&quot; data-start=&quot;5117&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre!&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;CLI&lt;/span&gt;&lt;/span&gt;&lt;span&gt; → &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;Orchestrator&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
  ├─ &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;Fixed&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;chain&lt;/span&gt;&lt;/span&gt;&lt;span&gt; (summarize): &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;ID&lt;/span&gt;&lt;/span&gt;&lt;span&gt; → &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;transcript&lt;/span&gt;&lt;/span&gt;&lt;span&gt; → &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;map&lt;/span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;reduce&lt;/span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;polish&lt;/span&gt;&lt;/span&gt;&lt;span&gt; (bare LLM)
  └─ &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;Universal&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;chain&lt;/span&gt;&lt;/span&gt;&lt;span&gt; (ask): &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;recurse&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;while&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;the&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;model&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;returns&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;tool_calls&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;Tools&lt;/span&gt;&lt;/span&gt;&lt;span&gt; (LangChain &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-variable&quot;&gt;@tool&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)
  ├─ &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;extract_video_id&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
  ├─ &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;fetch_transcript&lt;/span&gt;&lt;/span&gt;&lt;span&gt;  (YouTubeTranscriptAPI; optional yt-dlp captions fallback)
  ├─ &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;get_full_metadata&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;get_thumbnails&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
  ├─ &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;search_youtube&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;get_trending_videos&lt;/span&gt;&lt;/span&gt;&lt;span&gt; (best-effort)
&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;Finalization&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
  └─ &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;Bare&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;LLM&lt;/span&gt;&lt;/span&gt;&lt;span&gt; (no tools) → &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;guaranteed&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;text&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;5784&quot; data-start=&quot;5570&quot;&gt;&lt;strong data-end=&quot;5591&quot; data-start=&quot;5570&quot;&gt;Why the fallback?&lt;/strong&gt; In production you’ll meet videos with no official transcript. Add a &lt;code data-end=&quot;5668&quot; data-start=&quot;5660&quot;&gt;yt-dlp&lt;/code&gt; captions fallback and, as a last resort, summarize metadata/description so you &lt;strong data-end=&quot;5758&quot; data-start=&quot;5748&quot;&gt;always&lt;/strong&gt; produce something useful.&lt;/p&gt;
&lt;hr data-end=&quot;5789&quot; data-start=&quot;5786&quot; /&gt;
&lt;h2 data-end=&quot;5839&quot; data-start=&quot;5791&quot;&gt;Operational tips (so it stays fast and cheap)&lt;/h2&gt;
&lt;ul data-end=&quot;7470&quot; data-start=&quot;6950&quot;&gt;
&lt;li data-end=&quot;5992&quot; data-start=&quot;5841&quot;&gt;
&lt;p data-end=&quot;5992&quot; data-start=&quot;5843&quot;&gt;&lt;strong data-end=&quot;5862&quot; data-start=&quot;5843&quot;&gt;TPM discipline:&lt;/strong&gt; throttle chunk calls with short sleeps; prefer &lt;strong data-end=&quot;5928&quot; data-start=&quot;5910&quot;&gt;smaller models&lt;/strong&gt; for map steps, then a slightly stronger model for merge/polish.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;6138&quot; data-start=&quot;5993&quot;&gt;
&lt;p data-end=&quot;6138&quot; data-start=&quot;5995&quot;&gt;&lt;strong data-end=&quot;6023&quot; data-start=&quot;5995&quot;&gt;Chapters-aware chunking:&lt;/strong&gt; if &lt;code data-end=&quot;6035&quot; data-start=&quot;6027&quot;&gt;yt-dlp&lt;/code&gt; returns chapters, chunk on &lt;strong data-end=&quot;6085&quot; data-start=&quot;6063&quot;&gt;chapter boundaries&lt;/strong&gt; first—better topical coherence and fewer duplicates.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;6251&quot; data-start=&quot;6139&quot;&gt;
&lt;p data-end=&quot;6251&quot; data-start=&quot;6141&quot;&gt;&lt;strong data-end=&quot;6150&quot; data-start=&quot;6141&quot;&gt;Cache&lt;/strong&gt; transcripts/metadata to disk (simple JSON files). You’ll save both &lt;strong data-end=&quot;6226&quot; data-start=&quot;6218&quot;&gt;time&lt;/strong&gt; and &lt;strong data-end=&quot;6240&quot; data-start=&quot;6231&quot;&gt;money&lt;/strong&gt; on reruns.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;6345&quot; data-start=&quot;6252&quot;&gt;
&lt;p data-end=&quot;6345&quot; data-start=&quot;6254&quot;&gt;&lt;strong data-end=&quot;6272&quot; data-start=&quot;6254&quot;&gt;Observability:&lt;/strong&gt; log tool names and &lt;strong data-end=&quot;6309&quot; data-start=&quot;6292&quot;&gt;payload sizes&lt;/strong&gt;. When costs drift, you’ll know why.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;7475&quot; data-start=&quot;7472&quot; /&gt;
&lt;h2 data-end=&quot;7507&quot; data-start=&quot;7477&quot;&gt;&lt;/h2&gt;
&lt;h2 data-end=&quot;8143&quot; data-start=&quot;8124&quot;&gt;Closing thoughts&lt;/h2&gt;
&lt;p data-end=&quot;8463&quot; data-start=&quot;8145&quot;&gt;This is a &lt;strong data-end=&quot;8163&quot; data-start=&quot;8155&quot;&gt;tool&lt;/strong&gt; designed for the messiness of real YouTube content. The combination of &lt;strong data-end=&quot;8251&quot; data-start=&quot;8235&quot;&gt;tool calling&lt;/strong&gt;, a minimal &lt;strong data-end=&quot;8277&quot; data-start=&quot;8263&quot;&gt;agent loop&lt;/strong&gt;, &lt;strong data-end=&quot;8304&quot; data-start=&quot;8279&quot;&gt;chunked summarization&lt;/strong&gt;, and &lt;strong data-end=&quot;8335&quot; data-start=&quot;8310&quot;&gt;bare-LLM finalization&lt;/strong&gt; makes it predictable, debuggable, and resilient. You can drop it into a pipeline today and get reliable results on long videos.&lt;/p&gt;</description><link>https://thundaxsoftware.blogspot.com/2025/09/a-practical-production-ready-tool-for.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgF1o_DfQwCZlI9sxzkHy3dpf97E_J4dmJQiePAiWVxtZDoqVsYVdxOoEgaB6_02ukpe_lZDBFAEY2m9KDBXjUsDS0PqTuDsuobkyMmXsV1oOBtssw32A5NZnWz3oUCwR43Kn7GahuG_86ohEcPc2xIhOlAM1PVOClcaZJp5iIg5uTeJEfec4NJbLh2LSc/s72-c/ChatGPT%20Image%20Sep%2013,%202025,%2001_20_50%20PM.png" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>United Kingdom</georss:featurename><georss:point>55.378051 -3.435973</georss:point><georss:box>27.067817163821154 -38.592223 83.688284836178838 31.720277</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-1683967296355320681</guid><pubDate>Sun, 31 Aug 2025 20:17:00 +0000</pubDate><atom:updated>2025-08-31T20:17:08.843+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">AI Agents</category><category domain="http://www.blogger.com/atom/ns#">Artificial Intelligence</category><category domain="http://www.blogger.com/atom/ns#">Data Analysis</category><category domain="http://www.blogger.com/atom/ns#">Data Science</category><category domain="http://www.blogger.com/atom/ns#">Data Visualization</category><category domain="http://www.blogger.com/atom/ns#">Generative AI</category><category domain="http://www.blogger.com/atom/ns#">LangChain</category><category domain="http://www.blogger.com/atom/ns#">llm</category><category domain="http://www.blogger.com/atom/ns#">Machine Learning</category><category domain="http://www.blogger.com/atom/ns#">Matplotlib</category><category domain="http://www.blogger.com/atom/ns#">openAI</category><category domain="http://www.blogger.com/atom/ns#">Pandas Agent</category><category domain="http://www.blogger.com/atom/ns#">Prompt</category><category domain="http://www.blogger.com/atom/ns#">Python</category><category domain="http://www.blogger.com/atom/ns#">RAG</category><category domain="http://www.blogger.com/atom/ns#">Retrieval Augmented Generation</category><title>Querying and Plotting Data with LangChain’s Pandas Agent + OpenAI</title><description>&lt;h1 data-end=&quot;225&quot; data-start=&quot;158&quot;&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJMe4Gyqi7yGzZvvxjqCT22C4W9JSdTqrAGQxlvIg-GxB9rmSPkXO0-WB3LtEklawcmSEsQs-2QBLLDPCPUSBd1-dx2PWjmbxbsebcGZ4F0Eh7_VZ6H5ZnCPT3shSw4pus6_b4llSFNa1Faxs8VV2syL3LOVk_Ke9yCtQ9mkzx0FZlSqaEjcJA4CjAOJM/s1024/ChatGPT%20Image%20Aug%2031,%202025,%2009_13_33%20PM.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJMe4Gyqi7yGzZvvxjqCT22C4W9JSdTqrAGQxlvIg-GxB9rmSPkXO0-WB3LtEklawcmSEsQs-2QBLLDPCPUSBd1-dx2PWjmbxbsebcGZ4F0Eh7_VZ6H5ZnCPT3shSw4pus6_b4llSFNa1Faxs8VV2syL3LOVk_Ke9yCtQ9mkzx0FZlSqaEjcJA4CjAOJM/s320/ChatGPT%20Image%20Aug%2031,%202025,%2009_13_33%20PM.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;Querying and Plotting Data with LangChain’s Pandas Agent + OpenAI&lt;/h1&gt;&lt;p data-end=&quot;553&quot; data-start=&quot;227&quot;&gt;Natural language interfaces for data analysis are moving from research into everyday engineering practice.&lt;br data-end=&quot;336&quot; data-start=&quot;333&quot; /&gt;
With &lt;a class=&quot;decorated-link cursor-pointer&quot; data-end=&quot;383&quot; data-start=&quot;341&quot; rel=&quot;noopener&quot; target=&quot;_new&quot;&gt;LangChain&amp;nbsp;&lt;/a&gt;and OpenAI, you can now &lt;strong data-end=&quot;449&quot; data-start=&quot;408&quot;&gt;query a DataFrame directly in English&lt;/strong&gt;, let the model write and execute the Pandas/Matplotlib code, and even plot results, all inside Python.&lt;/p&gt;&lt;p data-end=&quot;659&quot; data-start=&quot;555&quot;&gt;In this post, I’ll show you how I built a &lt;strong data-end=&quot;623&quot; data-start=&quot;597&quot;&gt;Pandas DataFrame Agent&lt;/strong&gt; with LangChain + OpenAI that can:&lt;/p&gt;&lt;ul data-end=&quot;802&quot; data-start=&quot;660&quot;&gt;
&lt;li data-end=&quot;705&quot; data-start=&quot;660&quot;&gt;
&lt;p data-end=&quot;705&quot; data-start=&quot;662&quot;&gt;Answer tabular questions about a dataset.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;754&quot; data-start=&quot;706&quot;&gt;
&lt;p data-end=&quot;754&quot; data-start=&quot;708&quot;&gt;Generate Python plotting code automatically.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;802&quot; data-start=&quot;755&quot;&gt;
&lt;p data-end=&quot;802&quot; data-start=&quot;757&quot;&gt;Execute that code safely to produce charts.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;hr data-end=&quot;807&quot; data-start=&quot;804&quot; /&gt;&lt;h2 data-end=&quot;820&quot; data-start=&quot;809&quot;&gt;🔧 Setup&lt;/h2&gt;&lt;p data-end=&quot;845&quot; data-start=&quot;822&quot;&gt;We need a few packages:&lt;/p&gt;&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;938&quot; data-start=&quot;847&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-bash&quot;&gt;pip install langchain langchain-openai langchain-experimental pandas matplotlib
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;p data-end=&quot;975&quot; data-start=&quot;940&quot;&gt;And of course, set your OpenAI key:&lt;/p&gt;&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;1017&quot; data-start=&quot;977&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;export&lt;/span&gt; OPENAI_API_KEY=...
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;hr data-end=&quot;1022&quot; data-start=&quot;1019&quot; /&gt;&lt;h2 data-end=&quot;1041&quot; data-start=&quot;1024&quot;&gt;📊 The Dataset&lt;/h2&gt;&lt;p data-end=&quot;1092&quot; data-start=&quot;1043&quot;&gt;For demo purposes, let’s mock up some sales data:&lt;/p&gt;&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;1438&quot; data-start=&quot;1094&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; pandas &lt;span class=&quot;hljs-keyword&quot;&gt;as&lt;/span&gt; pd

data = {
    &lt;span class=&quot;hljs-string&quot;&gt;&quot;month&quot;&lt;/span&gt;: [&lt;span class=&quot;hljs-string&quot;&gt;&quot;2025-01&quot;&lt;/span&gt;,&lt;span class=&quot;hljs-string&quot;&gt;&quot;2025-02&quot;&lt;/span&gt;,&lt;span class=&quot;hljs-string&quot;&gt;&quot;2025-03&quot;&lt;/span&gt;,&lt;span class=&quot;hljs-string&quot;&gt;&quot;2025-04&quot;&lt;/span&gt;,&lt;span class=&quot;hljs-string&quot;&gt;&quot;2025-05&quot;&lt;/span&gt;,&lt;span class=&quot;hljs-string&quot;&gt;&quot;2025-06&quot;&lt;/span&gt;],
    &lt;span class=&quot;hljs-string&quot;&gt;&quot;region&quot;&lt;/span&gt;: [&lt;span class=&quot;hljs-string&quot;&gt;&quot;EMEA&quot;&lt;/span&gt;,&lt;span class=&quot;hljs-string&quot;&gt;&quot;EMEA&quot;&lt;/span&gt;,&lt;span class=&quot;hljs-string&quot;&gt;&quot;EMEA&quot;&lt;/span&gt;,&lt;span class=&quot;hljs-string&quot;&gt;&quot;AMER&quot;&lt;/span&gt;,&lt;span class=&quot;hljs-string&quot;&gt;&quot;AMER&quot;&lt;/span&gt;,&lt;span class=&quot;hljs-string&quot;&gt;&quot;APAC&quot;&lt;/span&gt;],
    &lt;span class=&quot;hljs-string&quot;&gt;&quot;units&quot;&lt;/span&gt;:  [&lt;span class=&quot;hljs-number&quot;&gt;120&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;150&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;130&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;200&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;180&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;160&lt;/span&gt;],
    &lt;span class=&quot;hljs-string&quot;&gt;&quot;price&quot;&lt;/span&gt;:  [&lt;span class=&quot;hljs-number&quot;&gt;10.0&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;10.0&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;10.0&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;12.0&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;12.0&lt;/span&gt;,  &lt;span class=&quot;hljs-number&quot;&gt;9.0&lt;/span&gt;],
}
df = pd.DataFrame(data)
df[&lt;span class=&quot;hljs-string&quot;&gt;&quot;revenue&quot;&lt;/span&gt;] = df[&lt;span class=&quot;hljs-string&quot;&gt;&quot;units&quot;&lt;/span&gt;] * df[&lt;span class=&quot;hljs-string&quot;&gt;&quot;price&quot;&lt;/span&gt;]
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;hr data-end=&quot;1443&quot; data-start=&quot;1440&quot; /&gt;&lt;h2 data-end=&quot;1469&quot; data-start=&quot;1445&quot;&gt;🤖 Creating the Agent&lt;/h2&gt;&lt;p data-end=&quot;1599&quot; data-start=&quot;1471&quot;&gt;The magic comes from &lt;code data-end=&quot;1523&quot; data-start=&quot;1492&quot;&gt;create_pandas_dataframe_agent&lt;/code&gt;.&lt;br data-end=&quot;1527&quot; data-start=&quot;1524&quot; /&gt;
This wraps the DataFrame in a tool the LLM can call with code execution:&lt;/p&gt;&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;1927&quot; data-start=&quot;1601&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; langchain_openai &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; ChatOpenAI
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; langchain_experimental.agents.agent_toolkits &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; create_pandas_dataframe_agent

llm = ChatOpenAI(model=&lt;span class=&quot;hljs-string&quot;&gt;&quot;gpt-4o-mini&quot;&lt;/span&gt;, temperature=&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;)

agent = create_pandas_dataframe_agent(
    llm=llm,
    df=df,
    agent_type=&lt;span class=&quot;hljs-string&quot;&gt;&quot;openai-tools&quot;&lt;/span&gt;,
    verbose=&lt;span class=&quot;hljs-literal&quot;&gt;False&lt;/span&gt;,  &lt;span class=&quot;hljs-comment&quot;&gt;# cleaner logs&lt;/span&gt;
)
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;hr data-end=&quot;1932&quot; data-start=&quot;1929&quot; /&gt;&lt;h2 data-end=&quot;1967&quot; data-start=&quot;1934&quot;&gt;🔎 Asking Questions in English&lt;/h2&gt;&lt;p data-end=&quot;2020&quot; data-start=&quot;1969&quot;&gt;Now you can query the data without touching Pandas:&lt;/p&gt;&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;2164&quot; data-start=&quot;2022&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-python&quot;&gt;resp = agent.invoke({
    &lt;span class=&quot;hljs-string&quot;&gt;&quot;input&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;Compute total revenue by region as a tidy table sorted descending.&quot;&lt;/span&gt;
})
&lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(resp[&lt;span class=&quot;hljs-string&quot;&gt;&quot;output&quot;&lt;/span&gt;])
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;p data-end=&quot;2173&quot; data-start=&quot;2166&quot;&gt;Output:&lt;/p&gt;&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;2287&quot; data-start=&quot;2175&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre!&quot;&gt;&lt;span class=&quot;hljs-string&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;region&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;revenue&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;hljs-string&quot;&gt;|--------|---------|&lt;/span&gt;
&lt;span class=&quot;hljs-string&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;AMER&lt;/span&gt;   &lt;span class=&quot;hljs-string&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;4560.0&lt;/span&gt;  &lt;span class=&quot;hljs-string&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;hljs-string&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;EMEA&lt;/span&gt;   &lt;span class=&quot;hljs-string&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;4000.0&lt;/span&gt;  &lt;span class=&quot;hljs-string&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;hljs-string&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;APAC&lt;/span&gt;   &lt;span class=&quot;hljs-string&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;1440.0&lt;/span&gt;  &lt;span class=&quot;hljs-string&quot;&gt;|&lt;/span&gt;
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;p data-end=&quot;2353&quot; data-start=&quot;2289&quot;&gt;The agent generated and executed the Pandas code under the hood.&lt;/p&gt;&lt;hr data-end=&quot;2358&quot; data-start=&quot;2355&quot; /&gt;&lt;h2 data-end=&quot;2383&quot; data-start=&quot;2360&quot;&gt;📈 Asking for a Plot&lt;/h2&gt;&lt;p data-end=&quot;2456&quot; data-start=&quot;2385&quot;&gt;We can go one step further — ask the model to &lt;strong data-end=&quot;2455&quot; data-start=&quot;2431&quot;&gt;plot monthly revenue&lt;/strong&gt;:&lt;/p&gt;&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;3080&quot; data-start=&quot;2458&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-python&quot;&gt;plot_code = &lt;span class=&quot;hljs-string&quot;&gt;&quot;&quot;&quot;
import pandas as pd
import matplotlib.pyplot as plt
from pathlib import Path

df[&#39;month&#39;] = pd.to_datetime(df[&#39;month&#39;])
monthly_revenue = df.groupby(&#39;month&#39;)[&#39;revenue&#39;].sum()

plt.figure(figsize=(10, 5))
plt.plot(monthly_revenue.index, monthly_revenue.values, marker=&#39;o&#39;)
plt.title(&#39;Monthly Revenue Over Time&#39;)
plt.xlabel(&#39;Month&#39;)
plt.ylabel(&#39;Total Revenue&#39;)
plt.grid()
plt.xticks(rotation=45)
plt.tight_layout()

out = Path(&#39;monthly_revenue.png&#39;)
plt.savefig(out)
print(f&#39;Chart saved to: {out.resolve()}&#39;)
&quot;&quot;&quot;&lt;/span&gt;
agent.invoke({&lt;span class=&quot;hljs-string&quot;&gt;&quot;input&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;f&quot;Execute this exact code:\n```python\n&lt;span class=&quot;hljs-subst&quot;&gt;{plot_code}&lt;/span&gt;&lt;/span&gt;\n```&quot;})
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;p data-end=&quot;3096&quot; data-start=&quot;3082&quot;&gt;This produces:&lt;/p&gt;&lt;p data-end=&quot;3143&quot; data-start=&quot;3098&quot;&gt;&lt;/p&gt;&lt;div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;hr data-end=&quot;3148&quot; data-start=&quot;3145&quot; /&gt;&lt;br /&gt;&lt;h2 data-end=&quot;3556&quot; data-start=&quot;3534&quot;&gt;🚀 Why This Matters&lt;/h2&gt;&lt;p data-end=&quot;3581&quot; data-start=&quot;3558&quot;&gt;This pattern unlocks:&lt;/p&gt;&lt;ul data-end=&quot;3843&quot; data-start=&quot;3582&quot;&gt;
&lt;li data-end=&quot;3658&quot; data-start=&quot;3582&quot;&gt;
&lt;p data-end=&quot;3658&quot; data-start=&quot;3584&quot;&gt;Rapid &lt;strong data-end=&quot;3610&quot; data-start=&quot;3590&quot;&gt;ad-hoc analytics&lt;/strong&gt;: Ask questions in English, get code + charts.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;3727&quot; data-start=&quot;3659&quot;&gt;
&lt;p data-end=&quot;3727&quot; data-start=&quot;3661&quot;&gt;&lt;strong data-end=&quot;3684&quot; data-start=&quot;3661&quot;&gt;Non-technical users&lt;/strong&gt; can explore data without writing Pandas.&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;3843&quot; data-start=&quot;3728&quot;&gt;
&lt;p data-end=&quot;3843&quot; data-start=&quot;3730&quot;&gt;Bridges &lt;strong data-end=&quot;3757&quot; data-start=&quot;3738&quot;&gt;RAG + analytics&lt;/strong&gt;: Instead of only text retrieval, you can augment LLMs with structured data queries.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;





































&lt;/p&gt;&lt;p data-end=&quot;4026&quot; data-start=&quot;3845&quot;&gt;Of course, for production you’d want &lt;strong data-end=&quot;3896&quot; data-start=&quot;3882&quot;&gt;guardrails&lt;/strong&gt; (e.g., AST linting before execution, schema checks, sandboxing). But as a prototyping tool, this workflow is incredibly powerful.&lt;/p&gt;&lt;p data-end=&quot;4026&quot; data-start=&quot;3845&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p data-end=&quot;4026&quot; data-start=&quot;3845&quot;&gt;Source code can be found here:&amp;nbsp;&lt;a href=&quot;https://github.com/JordiCorbilla/langgraph-cookbook&quot;&gt;JordiCorbilla/langgraph-cookbook: langgraph-cookbook&lt;/a&gt;&lt;/p&gt;</description><link>https://thundaxsoftware.blogspot.com/2025/08/querying-and-plotting-data-with.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJMe4Gyqi7yGzZvvxjqCT22C4W9JSdTqrAGQxlvIg-GxB9rmSPkXO0-WB3LtEklawcmSEsQs-2QBLLDPCPUSBd1-dx2PWjmbxbsebcGZ4F0Eh7_VZ6H5ZnCPT3shSw4pus6_b4llSFNa1Faxs8VV2syL3LOVk_Ke9yCtQ9mkzx0FZlSqaEjcJA4CjAOJM/s72-c/ChatGPT%20Image%20Aug%2031,%202025,%2009_13_33%20PM.png" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>London, UK</georss:featurename><georss:point>51.5072178 -0.1275862</georss:point><georss:box>23.196983963821154 -35.2838362 79.817451636178845 35.0286638</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-2016849911793834103</guid><pubDate>Sun, 31 Aug 2025 19:57:00 +0000</pubDate><atom:updated>2025-08-31T20:17:46.460+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">AgenticAI</category><category domain="http://www.blogger.com/atom/ns#">AIAgents</category><category domain="http://www.blogger.com/atom/ns#">GenerativeAI</category><category domain="http://www.blogger.com/atom/ns#">GraphBasedWorkflows</category><category domain="http://www.blogger.com/atom/ns#">LangChain</category><category domain="http://www.blogger.com/atom/ns#">LangGraph</category><category domain="http://www.blogger.com/atom/ns#">openAI</category><category domain="http://www.blogger.com/atom/ns#">Pydantic</category><category domain="http://www.blogger.com/atom/ns#">Python</category><category domain="http://www.blogger.com/atom/ns#">RAG</category><category domain="http://www.blogger.com/atom/ns#">ToolsAndOrchestration</category><title>Building a Tiny LangGraph Agent with a Tool: A Weather-Aware Trip Planner</title><description>&lt;h1 data-end=&quot;392&quot; data-start=&quot;317&quot;&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcrYJdRvDeC2ZuKf4ltRhE7s2ksZButKFHFmB_Tw5D-Qr-n3a-OJ7ld3QXGIp4pxi2gZFYvNsWqC_tdgm1CN3dkr7NnzhFpGjDEC9tbbsZ9XTPdPvIqwJXd0M62Qbzs0JMAL83l8OANq9v8frFMvBvJJ1UySHklT8tRP86JpwMdDaxa5F7cphqrDf1rQk/s1024/ChatGPT%20Image%20Aug%2031,%202025,%2008_48_17%20PM.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcrYJdRvDeC2ZuKf4ltRhE7s2ksZButKFHFmB_Tw5D-Qr-n3a-OJ7ld3QXGIp4pxi2gZFYvNsWqC_tdgm1CN3dkr7NnzhFpGjDEC9tbbsZ9XTPdPvIqwJXd0M62Qbzs0JMAL83l8OANq9v8frFMvBvJJ1UySHklT8tRP86JpwMdDaxa5F7cphqrDf1rQk/s320/ChatGPT%20Image%20Aug%2031,%202025,%2008_48_17%20PM.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;Building a Tiny LangGraph Agent with a Tool: A Weather-Aware Trip Planner&lt;/h1&gt;
&lt;p data-end=&quot;686&quot; data-start=&quot;394&quot;&gt;Most agent demos are either too simple (toy calculators) or too complex (enterprise-scale pipelines).&lt;br data-end=&quot;498&quot; data-start=&quot;495&quot; /&gt;
Here’s a &lt;strong data-end=&quot;524&quot; data-start=&quot;507&quot;&gt;middle ground&lt;/strong&gt;: a &lt;strong data-end=&quot;554&quot; data-start=&quot;528&quot;&gt;5-node LangGraph agent&lt;/strong&gt; that builds a half-day itinerary for a city, adapts to weather, respects user preferences, and &lt;strong data-end=&quot;671&quot; data-start=&quot;650&quot;&gt;calls a real tool&lt;/strong&gt; along the way.&lt;/p&gt;
&lt;hr data-end=&quot;691&quot; data-start=&quot;688&quot; /&gt;
&lt;h2 data-end=&quot;713&quot; data-start=&quot;693&quot;&gt;Why this example?&lt;/h2&gt;
&lt;ul data-end=&quot;1129&quot; data-start=&quot;715&quot;&gt;
&lt;li data-end=&quot;799&quot; data-start=&quot;715&quot;&gt;
&lt;p data-end=&quot;799&quot; data-start=&quot;717&quot;&gt;&lt;strong data-end=&quot;746&quot; data-start=&quot;717&quot;&gt;Small, visualizable graph&lt;/strong&gt;: &lt;code data-end=&quot;799&quot; data-start=&quot;748&quot;&gt;plan → weather → search → pick → draft → finalize&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;870&quot; data-start=&quot;800&quot;&gt;
&lt;p data-end=&quot;870&quot; data-start=&quot;802&quot;&gt;&lt;strong data-end=&quot;825&quot; data-start=&quot;802&quot;&gt;Conditional routing&lt;/strong&gt;: rainy → indoor picks, sunny → outdoor picks&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;964&quot; data-start=&quot;871&quot;&gt;
&lt;p data-end=&quot;964&quot; data-start=&quot;873&quot;&gt;&lt;strong data-end=&quot;892&quot; data-start=&quot;873&quot;&gt;Real tool usage&lt;/strong&gt;: &lt;code data-end=&quot;910&quot; data-start=&quot;894&quot;&gt;random_weather&lt;/code&gt; is a LangChain tool, called explicitly from the graph&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;999&quot; data-start=&quot;965&quot;&gt;
&lt;p data-end=&quot;999&quot; data-start=&quot;967&quot;&gt;&lt;strong data-end=&quot;982&quot; data-start=&quot;967&quot;&gt;Typed state&lt;/strong&gt; with Pydantic v2&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1062&quot; data-start=&quot;1000&quot;&gt;
&lt;p data-end=&quot;1062&quot; data-start=&quot;1002&quot;&gt;&lt;strong data-end=&quot;1018&quot; data-start=&quot;1002&quot;&gt;Optional LLM&lt;/strong&gt; for nicer itinerary text (but not required), this example used OpenAI&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1129&quot; data-start=&quot;1063&quot;&gt;
&lt;p data-end=&quot;1129&quot; data-start=&quot;1065&quot;&gt;&lt;strong data-end=&quot;1091&quot; data-start=&quot;1065&quot;&gt;Two runnable scenarios&lt;/strong&gt;: London (rainy) and Barcelona (sunny)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1134&quot; data-start=&quot;1131&quot; /&gt;
&lt;h2 data-end=&quot;1151&quot; data-start=&quot;1136&quot;&gt;Architecture&lt;/h2&gt;
&lt;p data-end=&quot;1230&quot; data-start=&quot;1153&quot;&gt;&lt;/p&gt;&lt;div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;ul data-end=&quot;1685&quot; data-start=&quot;1232&quot;&gt;
&lt;li data-end=&quot;1543&quot; data-start=&quot;1232&quot;&gt;
&lt;p data-end=&quot;1245&quot; data-start=&quot;1234&quot;&gt;&lt;strong data-end=&quot;1243&quot; data-start=&quot;1234&quot;&gt;Nodes&lt;/strong&gt;&lt;/p&gt;
&lt;ul data-end=&quot;1543&quot; data-start=&quot;1248&quot;&gt;
&lt;li data-end=&quot;1279&quot; data-start=&quot;1248&quot;&gt;
&lt;p data-end=&quot;1279&quot; data-start=&quot;1250&quot;&gt;&lt;code data-end=&quot;1256&quot; data-start=&quot;1250&quot;&gt;plan&lt;/code&gt;: log initial context&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1340&quot; data-start=&quot;1282&quot;&gt;
&lt;p data-end=&quot;1340&quot; data-start=&quot;1284&quot;&gt;&lt;code data-end=&quot;1293&quot; data-start=&quot;1284&quot;&gt;weather&lt;/code&gt;: calls &lt;code data-end=&quot;1317&quot; data-start=&quot;1301&quot;&gt;random_weather&lt;/code&gt; tool → Weather state&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1373&quot; data-start=&quot;1343&quot;&gt;
&lt;p data-end=&quot;1373&quot; data-start=&quot;1345&quot;&gt;&lt;code data-end=&quot;1353&quot; data-start=&quot;1345&quot;&gt;search&lt;/code&gt;: load city places&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1432&quot; data-start=&quot;1376&quot;&gt;
&lt;p data-end=&quot;1432&quot; data-start=&quot;1378&quot;&gt;&lt;code data-end=&quot;1384&quot; data-start=&quot;1378&quot;&gt;pick&lt;/code&gt;: filter by weather &amp;amp; prefs (budget, dislikes)&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1471&quot; data-start=&quot;1435&quot;&gt;
&lt;p data-end=&quot;1471&quot; data-start=&quot;1437&quot;&gt;&lt;code data-end=&quot;1444&quot; data-start=&quot;1437&quot;&gt;draft&lt;/code&gt;: generate itinerary text&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1519&quot; data-start=&quot;1474&quot;&gt;
&lt;p data-end=&quot;1519&quot; data-start=&quot;1476&quot;&gt;&lt;code data-end=&quot;1486&quot; data-start=&quot;1476&quot;&gt;fallback&lt;/code&gt;: relax prefs if nothing chosen&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1543&quot; data-start=&quot;1522&quot;&gt;
&lt;p data-end=&quot;1543&quot; data-start=&quot;1524&quot;&gt;&lt;code data-end=&quot;1534&quot; data-start=&quot;1524&quot;&gt;finalize&lt;/code&gt;: wrap up&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1685&quot; data-start=&quot;1545&quot;&gt;
&lt;p data-end=&quot;1558&quot; data-start=&quot;1547&quot;&gt;&lt;strong data-end=&quot;1556&quot; data-start=&quot;1547&quot;&gt;Edges&lt;/strong&gt;&lt;/p&gt;
&lt;ul data-end=&quot;1685&quot; data-start=&quot;1561&quot;&gt;
&lt;li data-end=&quot;1629&quot; data-start=&quot;1561&quot;&gt;
&lt;p data-end=&quot;1629&quot; data-start=&quot;1563&quot;&gt;Normal path: &lt;code data-end=&quot;1627&quot; data-start=&quot;1576&quot;&gt;plan → weather → search → pick → draft → finalize&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1685&quot; data-start=&quot;1632&quot;&gt;
&lt;p data-end=&quot;1685&quot; data-start=&quot;1634&quot;&gt;Fallback path: &lt;code data-end=&quot;1685&quot; data-start=&quot;1649&quot;&gt;pick → fallback → draft → finalize&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1690&quot; data-start=&quot;1687&quot; /&gt;
&lt;h2 data-end=&quot;1711&quot; data-start=&quot;1692&quot;&gt;The Weather Tool&lt;/h2&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;2167&quot; data-start=&quot;1713&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-python&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt;&lt;/span&gt;&lt;span&gt; langchain_core.tools &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt;&lt;/span&gt;&lt;span&gt; tool
&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt;&lt;/span&gt;&lt;span&gt; random

&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-meta&quot;&gt;@tool&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-title function_&quot;&gt;random_weather&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-params&quot;&gt;city: &lt;span class=&quot;hljs-built_in&quot;&gt;str&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;dict&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:
    &lt;span class=&quot;hljs-string&quot;&gt;&quot;&quot;&quot;
    Return randomized weather for the city.
    &quot;&quot;&quot;&lt;/span&gt;
    conditions = [&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;sunny&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;cloudy&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;rain&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;storm&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;]
    cond = random.choice(conditions)
    low, high = (&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-number&quot;&gt;12&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-number&quot;&gt;30&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)
    &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt;&lt;/span&gt;&lt;span&gt; {
        &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;city&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;: city,
        &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;condition&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;: cond,
        &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;high_c&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;float&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(random.randint(&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-number&quot;&gt;20&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, high)),
        &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;low_c&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;float&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(random.randint(low, &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-number&quot;&gt;19&lt;/span&gt;&lt;/span&gt;&lt;span&gt;))
    }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;2195&quot; data-start=&quot;2169&quot;&gt;And in the &lt;code data-end=&quot;2194&quot; data-start=&quot;2180&quot;&gt;weather_node&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;2496&quot; data-start=&quot;2197&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-python&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-title function_&quot;&gt;weather_node&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-params&quot;&gt;s: GraphState&lt;/span&gt;&lt;/span&gt;&lt;span&gt;) -&amp;gt; GraphState:
    w = random_weather.invoke({&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;city&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;: s.city})  &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# tool call&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
    s.weather = Weather(**w)
    s.audit.append(
        &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-string&quot;&gt;f&quot;weather: &lt;span class=&quot;hljs-subst&quot;&gt;{s.weather.city}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-subst&quot;&gt;{s.weather.condition}&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &quot;
        &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-string&quot;&gt;f&quot;&lt;span class=&quot;hljs-subst&quot;&gt;{s.weather.high_c}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-subst&quot;&gt;{s.weather.low_c}&lt;/span&gt;&lt;/span&gt;&lt;span&gt;°C&quot;
    )
    &lt;/span&gt;&lt;span&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt;&lt;/span&gt;&lt;span&gt; s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-python&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-python&quot;&gt;&lt;span&gt;&lt;span&gt;Full code can be found here -&amp;gt; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;a href=&quot;https://github.com/JordiCorbilla/langgraph-cookbook&quot;&gt;JordiCorbilla/langgraph-cookbook: langgraph-cookbook&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;hr data-end=&quot;2501&quot; data-start=&quot;2498&quot; /&gt;
&lt;h2 data-end=&quot;2517&quot; data-start=&quot;2503&quot;&gt;Example Run&lt;/h2&gt;
&lt;h3 data-end=&quot;2577&quot; data-start=&quot;2519&quot;&gt;Scenario A: London, dislikes museums, free-only budget&lt;/h3&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;2932&quot; data-start=&quot;2579&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre!&quot;&gt;&lt;span&gt;&lt;span&gt;=== Scenario A: London + dislikes=[&#39;museum&#39;], free_only ===
[Node] plan
[Node] weather
[Tool] random_weather(London) -&amp;gt; {&#39;city&#39;: &#39;London&#39;, &#39;condition&#39;: &#39;sunny&#39;, &#39;high_c&#39;: 30.0, &#39;low_c&#39;: 28.0}
[Node] search
[Node] pick
[Node] draft
[Node] finalize
plan: city=London, half_day=morning, date=2025-08-31
weather: London sunny 30.0/28.0°C
search: 5 candidates
pick: Thames Riverside Walk, Hyde Park Loop, Sky Garden
draft: itinerary ready
finalize: done

Itinerary:
 - **Time:** 9:00 AM - 10:00 AM
 - **Tip:** Begin your day with a refreshing stroll along the river. Enjoy the views and the morning sun. Bring water and wear sunscreen!
 - **Transport:** Take the Tube from Embankment Station to Lancaster Gate (Circle or District Line).
 - **Tip:** This journey takes about 15 minutes. Consider grabbing a coffee from a nearby café before heading to the park.
 - **Time:** 10:15 AM - 11:30 AM
 - **Tip:** Enjoy the beautiful greenery and perhaps find a shaded spot to relax. Look out for the Serpentine Lake and the Diana Memorial Fountain.
 - **Transport:** Take the Tube from Lancaster Gate to Monument Station (Circle or District Line).
 - **Tip:** Allocate about 30 minutes for travel and check-in. Arrive by 11:45 AM to enjoy the stunning views of London.
 - **Time:** 12:00 PM - 12:45 PM
 - **Tip:** Enjoy a drink or snack at the café while taking in panoramic views of the city. Make sure to book your free entry in advance to avoid long waits!
Notes: Weather: sunny&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;2951&quot; data-start=&quot;2934&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-end=&quot;3004&quot; data-start=&quot;2953&quot;&gt;&lt;/p&gt;&lt;div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;hr data-end=&quot;3009&quot; data-start=&quot;3006&quot; /&gt;
&lt;h3 data-end=&quot;3063&quot; data-start=&quot;3011&quot;&gt;Scenario B: Barcelona, no dislikes, mixed budget&lt;/h3&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;3431&quot; data-start=&quot;3065&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre!&quot;&gt;&lt;span&gt;&lt;span&gt;=== Scenario B: Barcelona + no dislikes, mixed budget ===
[Node] plan
[Node] weather
[Tool] random_weather(Barcelona) -&amp;gt; {&#39;city&#39;: &#39;Barcelona&#39;, &#39;condition&#39;: &#39;sunny&#39;, &#39;high_c&#39;: 29.0, &#39;low_c&#39;: 26.0}
[Node] search
[Node] pick
[Node] draft
[Node] finalize
plan: city=Barcelona, half_day=afternoon, date=2025-08-31
weather: Barcelona sunny 29.0/26.0°C
search: 5 candidates
pick: La Sagrada Família (Exterior), Gothic Quarter Stroll, Park Güell
draft: itinerary ready
finalize: done

Itinerary:
 - **Start at La Sagrada Família (3:00 PM - 3:45 PM)**
 - Arrive early to admire the stunning exterior of this iconic basilica. Take photos and enjoy the intricate details of Gaudí&#39;s masterpiece.
 - **Gothic Quarter Stroll (4:00 PM - 5:00 PM)**
 - Take a short taxi or tram ride (about 10-15 minutes) to the Gothic Quarter. Wander through the narrow streets, explore hidden squares, and admire the medieval architecture.
 - **Park Güell (5:15 PM - 6:45 PM)**
 - Head to Park Güell via taxi or public transport (approximately 15-20 minutes). Enjoy a leisurely walk through the park, taking in Gaudí&#39;s colorful mosaics and panoramic views of the city.
 - **Transport:** Use taxis or rideshare apps for quick transfers between locations, especially in the heat.
 - **Hydration:** Bring water to stay hydrated, as temperatures can be high.
 - **Footwear:** Wear comfortable shoes for walking on cobblestone streets and park paths.
Notes: Weather: sunny&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;3450&quot; data-start=&quot;3433&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-end=&quot;3509&quot; data-start=&quot;3452&quot;&gt;&lt;/p&gt;&lt;div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;hr data-end=&quot;3514&quot; data-start=&quot;3511&quot; /&gt;
&lt;h2 data-end=&quot;3535&quot; data-start=&quot;3516&quot;&gt;Why this matters&lt;/h2&gt;
&lt;ul data-end=&quot;3790&quot; data-start=&quot;3537&quot;&gt;
&lt;li data-end=&quot;3592&quot; data-start=&quot;3537&quot;&gt;
&lt;p data-end=&quot;3592&quot; data-start=&quot;3539&quot;&gt;&lt;strong data-end=&quot;3561&quot; data-start=&quot;3539&quot;&gt;Graph, not guesses&lt;/strong&gt;: deterministic orchestration&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;3664&quot; data-start=&quot;3593&quot;&gt;
&lt;p data-end=&quot;3664&quot; data-start=&quot;3595&quot;&gt;&lt;strong data-end=&quot;3614&quot; data-start=&quot;3595&quot;&gt;Tools in action&lt;/strong&gt;: explicit &lt;code data-end=&quot;3636&quot; data-start=&quot;3625&quot;&gt;.invoke()&lt;/code&gt; calls with visible output&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;3730&quot; data-start=&quot;3665&quot;&gt;
&lt;p data-end=&quot;3730&quot; data-start=&quot;3667&quot;&gt;&lt;strong data-end=&quot;3685&quot; data-start=&quot;3667&quot;&gt;Policy in code&lt;/strong&gt;: dislikes, budget filters, weather routing&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;3790&quot; data-start=&quot;3731&quot;&gt;
&lt;p data-end=&quot;3790&quot; data-start=&quot;3733&quot;&gt;&lt;strong data-end=&quot;3749&quot; data-start=&quot;3733&quot;&gt;Verbose logs&lt;/strong&gt;: you can follow the agent step by step&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;3795&quot; data-start=&quot;3792&quot; /&gt;
&lt;h2 data-end=&quot;3811&quot; data-start=&quot;3797&quot;&gt;Pros &amp;amp; Cons&lt;/h2&gt;
&lt;p data-end=&quot;3821&quot; data-start=&quot;3813&quot;&gt;&lt;strong data-end=&quot;3821&quot; data-start=&quot;3813&quot;&gt;Pros&lt;/strong&gt;&lt;/p&gt;
&lt;ul data-end=&quot;3962&quot; data-start=&quot;3822&quot;&gt;
&lt;li data-end=&quot;3849&quot; data-start=&quot;3822&quot;&gt;
&lt;p data-end=&quot;3849&quot; data-start=&quot;3824&quot;&gt;Compact, easy-to-follow&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;3891&quot; data-start=&quot;3850&quot;&gt;
&lt;p data-end=&quot;3891&quot; data-start=&quot;3852&quot;&gt;Demonstrates both &lt;strong data-end=&quot;3889&quot; data-start=&quot;3870&quot;&gt;nodes and tools&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;3924&quot; data-start=&quot;3892&quot;&gt;
&lt;p data-end=&quot;3924&quot; data-start=&quot;3894&quot;&gt;Works with or without an LLM&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;3962&quot; data-start=&quot;3925&quot;&gt;
&lt;p data-end=&quot;3962&quot; data-start=&quot;3927&quot;&gt;Great for teaching graph concepts&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3972&quot; data-start=&quot;3964&quot;&gt;&lt;strong data-end=&quot;3972&quot; data-start=&quot;3964&quot;&gt;Cons&lt;/strong&gt;&lt;/p&gt;
&lt;ul data-end=&quot;4079&quot; data-start=&quot;3973&quot;&gt;
&lt;li data-end=&quot;4005&quot; data-start=&quot;3973&quot;&gt;
&lt;p data-end=&quot;4005&quot; data-start=&quot;3975&quot;&gt;Weather is mocked/randomized&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;4047&quot; data-start=&quot;4006&quot;&gt;
&lt;p data-end=&quot;4047&quot; data-start=&quot;4008&quot;&gt;Places are static, not from real APIs&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;4079&quot; data-start=&quot;4048&quot;&gt;
&lt;p data-end=&quot;4079&quot; data-start=&quot;4050&quot;&gt;No travel-time optimization&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;4084&quot; data-start=&quot;4081&quot; /&gt;
&lt;h2 data-end=&quot;4099&quot; data-start=&quot;4086&quot;&gt;How to run&lt;/h2&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;4139&quot; data-start=&quot;4101&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;sticky top-9&quot;&gt;&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;whitespace-pre! language-bash&quot;&gt;&lt;span&gt;&lt;span&gt;python trip_agent.py
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;ul data-end=&quot;4260&quot; data-start=&quot;4141&quot;&gt;
&lt;li data-end=&quot;4206&quot; data-start=&quot;4141&quot;&gt;
&lt;p data-end=&quot;4206&quot; data-start=&quot;4143&quot;&gt;Scenario A: London, free-only, dislikes museums → rainy picks&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;4260&quot; data-start=&quot;4207&quot;&gt;
&lt;p data-end=&quot;4260&quot; data-start=&quot;4209&quot;&gt;Scenario B: Barcelona, mixed budget → sunny picks&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;</description><link>https://thundaxsoftware.blogspot.com/2025/08/building-tiny-langgraph-agent-with-tool.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcrYJdRvDeC2ZuKf4ltRhE7s2ksZButKFHFmB_Tw5D-Qr-n3a-OJ7ld3QXGIp4pxi2gZFYvNsWqC_tdgm1CN3dkr7NnzhFpGjDEC9tbbsZ9XTPdPvIqwJXd0M62Qbzs0JMAL83l8OANq9v8frFMvBvJJ1UySHklT8tRP86JpwMdDaxa5F7cphqrDf1rQk/s72-c/ChatGPT%20Image%20Aug%2031,%202025,%2008_48_17%20PM.png" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>London, UK</georss:featurename><georss:point>51.5072178 -0.1275862</georss:point><georss:box>23.196983963821154 -35.2838362 79.817451636178845 35.0286638</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-4458032808663823019</guid><pubDate>Tue, 22 Apr 2025 08:24:00 +0000</pubDate><atom:updated>2025-04-22T08:24:32.513+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">AICodingAssistant</category><category domain="http://www.blogger.com/atom/ns#">CodeLlama7B</category><category domain="http://www.blogger.com/atom/ns#">ContinueExtension</category><category domain="http://www.blogger.com/atom/ns#">DeveloperProductivity</category><category domain="http://www.blogger.com/atom/ns#">LocalLLM</category><category domain="http://www.blogger.com/atom/ns#">OfflineCopilot</category><category domain="http://www.blogger.com/atom/ns#">Ollama</category><category domain="http://www.blogger.com/atom/ns#">PrivacyFirstAI</category><category domain="http://www.blogger.com/atom/ns#">VSCode</category><category domain="http://www.blogger.com/atom/ns#">Windows11Dev</category><title>Build Your Own Local Copilot in VS Code with Continue + Ollama (CodeLlama‑7B)</title><description>&lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;strong&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJe701ODOhBsFeeJBrKPqWh1xfVZEZXcm1RB7qfpnYfa3Stgx4dC8P7iLF1ZJhIrkquemJOKRKmlwmHMKm7Lsp12V02QGNTupoldQYF2BvKEAT4uVs72rpdcbUjW4kx_jMXAjCO23ccDW0BKV5JfOAGDeZNqaVhdhfCFH-0Hrois1GChXsmT1C2NJZRqo/s1024/ChatGPT%20Image%20Apr%2022,%202025,%2009_08_48%20AM.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJe701ODOhBsFeeJBrKPqWh1xfVZEZXcm1RB7qfpnYfa3Stgx4dC8P7iLF1ZJhIrkquemJOKRKmlwmHMKm7Lsp12V02QGNTupoldQYF2BvKEAT4uVs72rpdcbUjW4kx_jMXAjCO23ccDW0BKV5JfOAGDeZNqaVhdhfCFH-0Hrois1GChXsmT1C2NJZRqo/s320/ChatGPT%20Image%20Apr%2022,%202025,%2009_08_48%20AM.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/strong&gt;&lt;/div&gt;&lt;h1 data-pm-slice=&quot;1 1 []&quot;&gt;&lt;strong&gt;Build Your Own Local Copilot in VS&amp;nbsp;Code with Continue&amp;nbsp;+&amp;nbsp;Ollama (CodeLlama‑7B)&lt;/strong&gt;&lt;/h1&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&amp;nbsp;— In under 30 minutes you can have a fully‑offline, ChatGPT‑style coding assistant that &lt;em&gt;reads&lt;/em&gt; your entire codebase and never leaks a byte to the cloud.&lt;p&gt;&lt;/p&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;h2&gt;&lt;br /&gt;&lt;/h2&gt;&lt;h2&gt;&lt;br /&gt;&lt;/h2&gt;&lt;h2&gt;1&amp;nbsp;·&amp;nbsp;Why bother?&lt;/h2&gt;&lt;ul data-spread=&quot;false&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Privacy first&lt;/strong&gt;&amp;nbsp;— No proprietary code leaves your laptop.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Low latency&lt;/strong&gt;&amp;nbsp;— Replies stream in milliseconds, not seconds.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Free&lt;/strong&gt;&amp;nbsp;— No API keys, no per‑token billing.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hackable&lt;/strong&gt;&amp;nbsp;— Swap models, add custom prompts, or fine‑tune later.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;h2&gt;2&amp;nbsp;·&amp;nbsp;What we’ll build&lt;/h2&gt;&lt;p&gt;A local stack where:&lt;/p&gt;&lt;ol data-spread=&quot;false&quot; start=&quot;1&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ollama&lt;/strong&gt; runs the &lt;code&gt;codellama:7b&lt;/code&gt; model at &lt;code&gt;http://localhost:11434&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Continue&lt;/strong&gt; in VS&amp;nbsp;Code chats with that model.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Continue indexes &lt;em&gt;all&lt;/em&gt; your repositories so you can ask questions like:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;“Where do we create the AWS&amp;nbsp;SQS client?”&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;h2&gt;3&amp;nbsp;·&amp;nbsp;Prerequisites&lt;/h2&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;Item&lt;/th&gt;&lt;th&gt;Minimum&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;OS&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Windows&amp;nbsp;11&amp;nbsp;22H2+&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;RAM&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;16&amp;nbsp;GB (32&amp;nbsp;GB recommended)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;GPU (optional)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;NVIDIA card with ≥ 8&amp;nbsp;GB VRAM &amp;amp; latest CUDA driver&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;WSL&amp;nbsp;2&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Auto‑installed by Ollama if missing&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;VS&amp;nbsp;Code&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;1.88 or newer&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;h2&gt;4&amp;nbsp;·&amp;nbsp;Install Ollama&lt;/h2&gt;&lt;ol data-spread=&quot;false&quot; start=&quot;1&quot;&gt;&lt;li&gt;&lt;p&gt;Download the Windows installer from &lt;a disabled=&quot;true&quot;&gt;https://ollama.ai/download/windows&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Run the MSI and accept defaults. &lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Verify the CLI:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;ollama --version   # e.g. v0.1.32&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;blockquote&gt;&lt;p&gt;**Heads‑up&amp;nbsp;**&amp;nbsp;— The installer registers &lt;strong&gt;Ollama&lt;/strong&gt; as a Windows service and listens on port &lt;strong&gt;11434&lt;/strong&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;h2&gt;5&amp;nbsp;·&amp;nbsp;Pull CodeLlama‑7B&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;ollama pull codellama:7b    # 4‑bit Q4_0 fits in 8&amp;nbsp;GB RAM&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You’ll see a 100&amp;nbsp;% progress bar; the model lives in &lt;code&gt;%USERPROFILE%\.ollama\models&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgrqFEPHFstQD8rxjDNi-YUtBso-dTH-Gt5eV9V37Rr-tuZAW4yQ5nDJpHm_rxAwbfMYnUvRaBN8K3sXutjpz9PaoeDp0aoyEQzMvAFG1jG7EZX2LmJUE4MQh4aBbjjehy70E2zeYVfiTQWFxnNfz_B75M4e7qh77SWTy9WTyc3fAKZLwU2_325TmHks_M&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;203&quot; data-original-width=&quot;1662&quot; height=&quot;78&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgrqFEPHFstQD8rxjDNi-YUtBso-dTH-Gt5eV9V37Rr-tuZAW4yQ5nDJpHm_rxAwbfMYnUvRaBN8K3sXutjpz9PaoeDp0aoyEQzMvAFG1jG7EZX2LmJUE4MQh4aBbjjehy70E2zeYVfiTQWFxnNfz_B75M4e7qh77SWTy9WTyc3fAKZLwU2_325TmHks_M=w640-h78&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEhiEEOsjFkpHzagHrPWRrKZbU39yh9epLNBAlacjFiSd38xupArnHCffBvZkBLpFdeLVomfrpiuI6dbXvK4SUeMXylAIge1sWUodk_IL48xlgi2e2b4sZunQ3KJoUYdfBsHu_snzc8h-weSOGiTNlWpPinViv-c30DuPiBxlA9Fhy9Qo8zRts9ra2hlMZg&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;168&quot; data-original-width=&quot;1889&quot; height=&quot;56&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEhiEEOsjFkpHzagHrPWRrKZbU39yh9epLNBAlacjFiSd38xupArnHCffBvZkBLpFdeLVomfrpiuI6dbXvK4SUeMXylAIge1sWUodk_IL48xlgi2e2b4sZunQ3KJoUYdfBsHu_snzc8h-weSOGiTNlWpPinViv-c30DuPiBxlA9Fhy9Qo8zRts9ra2hlMZg=w640-h56&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;h2&gt;6&amp;nbsp;·&amp;nbsp;Install Continue for VS&amp;nbsp;Code&lt;/h2&gt;&lt;ol data-spread=&quot;false&quot; start=&quot;1&quot;&gt;&lt;li&gt;&lt;p&gt;Open VS&amp;nbsp;Code&amp;nbsp;→&amp;nbsp;&lt;strong&gt;Extensions&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Search &lt;strong&gt;“Continue”&lt;/strong&gt; by &lt;em&gt;Continue.dev&lt;/em&gt; and click &lt;em&gt;Install&lt;/em&gt;.&lt;br /&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgcoM60V1HHvXaFWc-MTYYQpM6pZx0dsMivSLg_m5vfmVs8iiS6Qcvx84sywBjlaNu0caQNHkposMCJ-OW7hjgqOqHAy6-nlCRP-xqDVwo2vJxEtMoWxw_GLLUQhmWArSAoIJ8Z7_flRZJqzeVWutnDIVSHZYaKEpgXXJgdQ2osdZZr6lgHPVFvtSgdTko&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;504&quot; data-original-width=&quot;803&quot; height=&quot;251&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgcoM60V1HHvXaFWc-MTYYQpM6pZx0dsMivSLg_m5vfmVs8iiS6Qcvx84sywBjlaNu0caQNHkposMCJ-OW7hjgqOqHAy6-nlCRP-xqDVwo2vJxEtMoWxw_GLLUQhmWArSAoIJ8Z7_flRZJqzeVWutnDIVSHZYaKEpgXXJgdQ2osdZZr6lgHPVFvtSgdTko=w400-h251&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Reload VS&amp;nbsp;Code; a sunflower icon shows up in the Activity Bar.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;h2&gt;7&amp;nbsp;·&amp;nbsp;Point Continue at Ollama&lt;/h2&gt;&lt;ol data-spread=&quot;false&quot; start=&quot;1&quot;&gt;&lt;li&gt;&lt;p&gt;Click the &lt;strong&gt;sunflower icon&lt;/strong&gt; → gear ⚙ →&amp;nbsp;&lt;em&gt;Settings&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Choose &lt;strong&gt;Model Provider:&amp;nbsp;Custom&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Fill the fields:&lt;/p&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Field&lt;/td&gt;&lt;td&gt;Value&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Provider&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;ollama&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Base&amp;nbsp;URL&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;http://localhost:11434&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Model&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;codellama:7b&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Press &lt;strong&gt;Save&lt;/strong&gt;. &lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;em&gt;(This writes the same keys to your &lt;/em&gt;&lt;code&gt;&lt;em&gt;settings.json&lt;/em&gt;&lt;/code&gt;&lt;em&gt;.)&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;/em&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;em&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEhamFyImT73-ucOGEoiBaAfv41UH2j_6U2ajgoKpxnqzfHdjYtrMVWWk5iNjcr4yk7OdkAJ6XMdbHMZB3G4fxZbEmakJGpuaFRe0E1qz2q6Rw2d5nJkn_PPrjDnJ9v9mo8CcoCjvQbEFaLqF5vhMUKY4s5GgBLqGLVyKE2kTeWy1V1OiV0oO31R2Zktq4Y&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;982&quot; data-original-width=&quot;1485&quot; height=&quot;424&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEhamFyImT73-ucOGEoiBaAfv41UH2j_6U2ajgoKpxnqzfHdjYtrMVWWk5iNjcr4yk7OdkAJ6XMdbHMZB3G4fxZbEmakJGpuaFRe0E1qz2q6Rw2d5nJkn_PPrjDnJ9v9mo8CcoCjvQbEFaLqF5vhMUKY4s5GgBLqGLVyKE2kTeWy1V1OiV0oO31R2Zktq4Y=w640-h424&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/em&gt;&lt;/div&gt;&lt;em&gt;&lt;br /&gt;&lt;/em&gt;&lt;p&gt;&lt;/p&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;h2&gt;8&amp;nbsp;·&amp;nbsp;Index your repositories (no training required)&lt;/h2&gt;&lt;p&gt;Add one line to &lt;em&gt;User&lt;/em&gt; or &lt;em&gt;Workspace&lt;/em&gt; settings:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&quot;continue.directoryContext&quot;: [
  { &quot;path&quot;: &quot;D:/code&quot;, &quot;depth&quot;: 4, &quot;maxFiles&quot;: 25000 }
]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Continue walks the tree once, stores embeddings in &lt;code&gt;%APPDATA%\continue\index&lt;/code&gt;, and keeps them fresh in the background.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;h2&gt;9&amp;nbsp;·&amp;nbsp;Smoke‑test the setup&lt;/h2&gt;&lt;ol data-spread=&quot;false&quot; start=&quot;1&quot;&gt;&lt;li&gt;&lt;p&gt;Press &lt;strong&gt;Ctrl&amp;nbsp;+&amp;nbsp;Shift&amp;nbsp;+&amp;nbsp;L&lt;/strong&gt; to open chat.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Type:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;@Codebase Which file defines the KafkaConsumer settings?&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;The reply should include a &lt;em&gt;Context&lt;/em&gt; block listing real file paths.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Optional: enable the &lt;strong&gt;Continue Console&lt;/strong&gt; (Settings → &lt;em&gt;Enable&amp;nbsp;Console&lt;/em&gt;) to watch retrieved files live.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;h2&gt;10&amp;nbsp;·&amp;nbsp;Tame hallucinations (optional but recommended)&lt;/h2&gt;&lt;p&gt;Add this to &lt;strong&gt;settings.json&lt;/strong&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&quot;continue.systemMessage&quot;: &quot;You are a senior full‑stack engineer. Answer concisely and reference only provided context. If unsure, say &#39;IDK&#39;.&quot;,
&quot;continue.modelOptions&quot;: {
  &quot;temperature&quot;: 0.2,
  &quot;top_p&quot;: 0.9,
  &quot;repeat_penalty&quot;: 1.1
}&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;h2&gt;11&amp;nbsp;·&amp;nbsp;Daily cheat‑sheet&lt;/h2&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Task&lt;/td&gt;&lt;td&gt;How&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Explain selected code&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Select → right‑click → &lt;em&gt;Continue: Explain&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Generate tests&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Same menu → &lt;em&gt;Generate&amp;nbsp;Tests&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Inline autocomplete&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Toggle &lt;em&gt;Tab Autocomplete&lt;/em&gt; in Continue footer&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Force retrieval&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Prefix prompt with &lt;code&gt;@Codebase&lt;/code&gt;, &lt;code&gt;@File&lt;/code&gt;, or &lt;code&gt;@Folder&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;h2&gt;12&amp;nbsp;·&amp;nbsp;Troubleshooting&lt;/h2&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Symptom&lt;/td&gt;&lt;td&gt;Fix&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;connect ECONNREFUSED 11434&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Tray → &lt;em&gt;Start&amp;nbsp;Ollama Service&lt;/em&gt; or &lt;code&gt;Start‑Service Ollama&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Model OOM on pull&lt;/td&gt;&lt;td&gt;Use quantised tag: &lt;code&gt;codellama:7b:Q4_0&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;No &lt;em&gt;Context&lt;/em&gt; block&lt;/td&gt;&lt;td&gt;Check &lt;code&gt;directoryContext&lt;/code&gt; path / depth; rebuild index&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;h2&gt;13&amp;nbsp;·&amp;nbsp;Next steps&lt;/h2&gt;&lt;ul data-spread=&quot;false&quot;&gt;&lt;li&gt;&lt;p&gt;Swap in &lt;strong&gt;StarCoder2‑15B&lt;/strong&gt; or &lt;strong&gt;DeepSeek‑Coder‑33B&lt;/strong&gt; for higher quality.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Drop temperature to&amp;nbsp;0 and let it draft unit tests you tweak.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Graduate to &lt;strong&gt;Tabby LoRA&lt;/strong&gt; if you need inline completions that mimic your exact style.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;</description><link>https://thundaxsoftware.blogspot.com/2025/04/build-your-own-local-copilot-in-vscode.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJe701ODOhBsFeeJBrKPqWh1xfVZEZXcm1RB7qfpnYfa3Stgx4dC8P7iLF1ZJhIrkquemJOKRKmlwmHMKm7Lsp12V02QGNTupoldQYF2BvKEAT4uVs72rpdcbUjW4kx_jMXAjCO23ccDW0BKV5JfOAGDeZNqaVhdhfCFH-0Hrois1GChXsmT1C2NJZRqo/s72-c/ChatGPT%20Image%20Apr%2022,%202025,%2009_08_48%20AM.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-7533761867427191768</guid><pubDate>Mon, 21 Apr 2025 10:53:00 +0000</pubDate><atom:updated>2025-04-22T08:29:18.026+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">ai‑workflow</category><category domain="http://www.blogger.com/atom/ns#">codex</category><category domain="http://www.blogger.com/atom/ns#">dev‑environment</category><category domain="http://www.blogger.com/atom/ns#">github</category><category domain="http://www.blogger.com/atom/ns#">Linux</category><category domain="http://www.blogger.com/atom/ns#">openAI</category><category domain="http://www.blogger.com/atom/ns#">Python</category><category domain="http://www.blogger.com/atom/ns#">tutorial</category><category domain="http://www.blogger.com/atom/ns#">Ubuntu</category><category domain="http://www.blogger.com/atom/ns#">VSCode</category><category domain="http://www.blogger.com/atom/ns#">Windows</category><category domain="http://www.blogger.com/atom/ns#">wsl2</category><title>From Zero to AI‑Ready: Python on Windows via WSL 2 and VS Code (GitHub + Codex Integration)</title><description>&lt;h2 style=&quot;text-align: left;&quot;&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgx_fg7Vlt3hPj5JXnRo2MUEmOoNc2ucA47nrCzGDZCZ-C-gXHwA1KKA3VkMstyhLe_iuTKEujEhN-J0D9gdL4oBNttWRJFxivQX0o3d4AUI6U7nKMmJaGp7sjOFOTLY6Be6LzKwa8GVr0M0uPQBiC5Q72Jy1vKPVEx2c6Ww5zg4ji_q7p7zRnlr5_TnSk/s1536/ChatGPT%20Image%20Apr%2021,%202025,%2011_25_34%20AM.png&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1536&quot; height=&quot;213&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgx_fg7Vlt3hPj5JXnRo2MUEmOoNc2ucA47nrCzGDZCZ-C-gXHwA1KKA3VkMstyhLe_iuTKEujEhN-J0D9gdL4oBNttWRJFxivQX0o3d4AUI6U7nKMmJaGp7sjOFOTLY6Be6LzKwa8GVr0M0uPQBiC5Q72Jy1vKPVEx2c6Ww5zg4ji_q7p7zRnlr5_TnSk/s320/ChatGPT%20Image%20Apr%2021,%202025,%2011_25_34%20AM.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;From Zero to AI‑Ready: Python on Windows via WSL&amp;nbsp;2 and VS&amp;nbsp;Code (GitHub + Codex Integration)&lt;/h2&gt;&lt;blockquote data-pm-slice=&quot;2 1 []&quot;&gt;&lt;p&gt;&lt;strong&gt;Why bother?&lt;/strong&gt; Because production workloads still lean on Linux, and Windows Subsystem for Linux&amp;nbsp;2 (WSL 2) lets you run a full kernel without giving up Windows’ desktop conveniences. Follow this playbook and you’ll be writing, testing, and committing AI‑infused Python in under an hour—no VM gymnastics required.&lt;/p&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;h2&gt;Prerequisites at a Glance&lt;/h2&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;Tool&lt;/th&gt;&lt;th&gt;Minimum Version&lt;/th&gt;&lt;th&gt;Why You Need It&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Windows&amp;nbsp;10 &lt;strong&gt;2004&lt;/strong&gt; / Windows 11&lt;/td&gt;&lt;td&gt;with &lt;strong&gt;WSL&amp;nbsp;2&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Provides the real Linux kernel&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Ubuntu&amp;nbsp;22.04&amp;nbsp;LTS&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;or newer&lt;/td&gt;&lt;td&gt;Stable, long‑term support&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;VS&amp;nbsp;Code&lt;/strong&gt;&amp;nbsp;1.90 +&lt;/td&gt;&lt;td&gt;Editor with first‑class WSL remote support&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Git&lt;/strong&gt;&amp;nbsp;2.40 +&lt;/td&gt;&lt;td&gt;Version control muscle memory&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;GitHub account &amp;amp; SSH key&lt;/td&gt;&lt;td&gt;Passwordless pushes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;OpenAI API key&lt;/td&gt;&lt;td&gt;Fuel for Codex AI and GitHub Copilot&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;h2&gt;1&amp;nbsp;&amp;nbsp;Enable WSL&amp;nbsp;2 and Install Ubuntu&lt;/h2&gt;&lt;p&gt;Open &lt;strong&gt;PowerShell as Administrator&lt;/strong&gt; and run:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;wsl --install                 # Installs WSL + default Ubuntu distro
wsl --set-default-version 2   # Make all future distros WSL&amp;nbsp;2&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Reboot when prompted.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Sanity check:&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code&gt;wsl --status                  # Kernel&amp;nbsp;≥&amp;nbsp;5.x, Default&amp;nbsp;=&amp;nbsp;2&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;h2&gt;2&amp;nbsp;&amp;nbsp;Update and Harden the Linux Side&lt;/h2&gt;&lt;p&gt;Launch &lt;strong&gt;Ubuntu&lt;/strong&gt; from the Start&amp;nbsp;Menu:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;sudo apt update &amp;amp;&amp;amp; sudo apt dist-upgrade -y
sudo apt install -y build-essential python3 python3-pip python3-venv git&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Need multiple Python versions? Install &lt;strong&gt;pyenv&lt;/strong&gt; the reliable way:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;curl https://pyenv.run | bash
# Follow the shell‑config instructions printed to the terminal&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;h2&gt;3&amp;nbsp;&amp;nbsp;Wire VS&amp;nbsp;Code into WSL&lt;/h2&gt;&lt;ol data-spread=&quot;false&quot; start=&quot;1&quot;&gt;&lt;li&gt;&lt;p&gt;Install &lt;strong&gt;VS&amp;nbsp;Code for Windows&lt;/strong&gt; (normal installer).&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Launch VS&amp;nbsp;Code → &lt;code&gt;Ctrl+Shift+X&lt;/code&gt; → install &lt;strong&gt;Remote&amp;nbsp;—&amp;nbsp;WSL&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Back in Ubuntu:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;mkdir -p ~/code/my-python-project
cd ~/code/my-python-project
code .                     # VS&amp;nbsp;Code opens inside WSL context&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The status bar should now read &lt;strong&gt;“WSL:&amp;nbsp;Ubuntu”&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;h2&gt;4&amp;nbsp;&amp;nbsp;Bootstrap a GitHub Repository&amp;nbsp;(Properly)&lt;/h2&gt;&lt;ol data-spread=&quot;false&quot; start=&quot;1&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Generate an SSH key inside WSL&lt;/strong&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;ssh-keygen -t ed25519 -C &quot;your.email@example.com&quot;
cat ~/.ssh/id_ed25519.pub   # Copy the public key&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Add the key to GitHub → &lt;em&gt;Settings&amp;nbsp;→ SSH&amp;nbsp;and&amp;nbsp;GPG keys&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Create a &lt;strong&gt;blank repo&lt;/strong&gt; on GitHub (no README/LICENCE).&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clone into WSL&lt;/strong&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;git clone git@github.com:your‑org/my-python-project.git ~/code/my-python-project
cd ~/code/my-python-project&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;First commit&lt;/strong&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;python3 -m venv .venv &amp;amp;&amp;amp; source .venv/bin/activate
echo &quot;# My Python Project&quot; &amp;gt; README.md
echo -e &quot;venv/\n__pycache__/&quot; &amp;gt; .gitignore
git add README.md .gitignore
git commit -m &quot;chore: initial commit&quot;
git push -u origin main&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt; keep your SSH keys in WSL, not in the Windows &lt;code&gt;ssh-agent&lt;/code&gt;—safer separation.&lt;/p&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;h2 data-pm-slice=&quot;1 1 []&quot;&gt;5&amp;nbsp;&amp;nbsp;Add Codex&amp;nbsp;AI to Your Workflow&lt;/h2&gt;&lt;h3&gt;5.1&amp;nbsp;&amp;nbsp;Install Node&amp;nbsp;18 LTS &amp;amp; the Codex&amp;nbsp;CLI&lt;/h3&gt;&lt;p&gt;Inside WSL:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# Install Node via the Node Version Manager (nvm)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install --lts      # e.g. Node 18.x
nvm use --lts

# Install Codex CLI globally
npm install -g @openai/codex
codex --version        # should echo a semver like 0.2.x&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgdJAp-CGzfLsk2_ak7qpfXt_V_tTwKxpZ-4tDbwVw3Ly_s8C-P0wk-v1UQa5o1BOgoX8GrSGoStlEARkhbo7PpleIBN84yaQmagaQN8kz5zp8-Yw76H2KqOCuGUodTwuuiswZlflCOscsa26ULVKE3m6DiY50jeBSP9oxeMyoWZX9A24Rplm36vOUC978&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;243&quot; data-original-width=&quot;748&quot; height=&quot;208&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgdJAp-CGzfLsk2_ak7qpfXt_V_tTwKxpZ-4tDbwVw3Ly_s8C-P0wk-v1UQa5o1BOgoX8GrSGoStlEARkhbo7PpleIBN84yaQmagaQN8kz5zp8-Yw76H2KqOCuGUodTwuuiswZlflCOscsa26ULVKE3m6DiY50jeBSP9oxeMyoWZX9A24Rplm36vOUC978=w640-h208&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;5.2&amp;nbsp;&amp;nbsp;Authenticate&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;export OPENAI_API_KEY=&quot;&amp;lt;your‑key&amp;gt;&quot;   # add the line above to ~/.bashrc for permanence&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;5.3&amp;nbsp;&amp;nbsp;First Run &amp;amp; Modes&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;cd ~/code/my-python-project
codex &quot;Explain this repo to me.&quot;&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjHajkx69-CZinNGnZ5GJ7kTxSE3WKDXeSBR0b-ITJtRZGEcuWvETNlj10ACb4f_mqwqnh8RpY-uwNa64CB9d5bw24gpydnNLxxQ-C2q6Zs9JdkTrNGFhsy1SdjKK2rbPIRiQmAKrOKrJyt2tZFqgtM3GFw9mVGDTYvbkJWsnZbwPbh7HxcetyK3mRHPVg&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;778&quot; data-original-width=&quot;1421&quot; height=&quot;350&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjHajkx69-CZinNGnZ5GJ7kTxSE3WKDXeSBR0b-ITJtRZGEcuWvETNlj10ACb4f_mqwqnh8RpY-uwNa64CB9d5bw24gpydnNLxxQ-C2q6Zs9JdkTrNGFhsy1SdjKK2rbPIRiQmAKrOKrJyt2tZFqgtM3GFw9mVGDTYvbkJWsnZbwPbh7HxcetyK3mRHPVg=w640-h350&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By default Codex runs in &lt;strong&gt;Suggest&lt;/strong&gt; mode (read‑only). Use &lt;code&gt;--auto-edit&lt;/code&gt; to let it write files, or &lt;code&gt;--full-auto&lt;/code&gt; for an autonomous agent that can run shell commands inside a sandbox. citeturn0search0&lt;/p&gt;&lt;h3&gt;5.4&amp;nbsp;&amp;nbsp;(Optional) Python SDK&lt;/h3&gt;&lt;p&gt;If you also want to call the API directly from scripts:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;pip install --upgrade openai&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The same &lt;code&gt;OPENAI_API_KEY&lt;/code&gt; environment variable is reused.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 data-pm-slice=&quot;1 1 []&quot;&gt;6&amp;nbsp;&amp;nbsp;Commit the AI Helpers (Without Leaking Keys)&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;# Requirements
echo &quot;# AI helpers\nopenai&amp;gt;=1.0&quot; &amp;gt; requirements.txt
pip freeze --local &amp;gt; requirements.lock

git add requirements.txt requirements.lock codex_smoketest.py
# Double‑check no key files are staged
if git status | grep -q api_key; then echo &quot;✗ Key leak detected&quot;; exit 1; fi

git commit -m &quot;feat: add OpenAI Codex integration&quot;

git push&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;h2&gt;7&amp;nbsp;&amp;nbsp;Best Practices Going Forward&lt;/h2&gt;&lt;ul data-spread=&quot;false&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;One virtual environment per repo&lt;/strong&gt;—time‑honoured and still right.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Use &lt;strong&gt;pre‑commit&lt;/strong&gt; hooks for &lt;code&gt;black&lt;/code&gt;, &lt;code&gt;ruff&lt;/code&gt;, and &lt;code&gt;isort&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Tune WSL filesystem performance: create &lt;code&gt;/etc/wsl.conf&lt;/code&gt; inside Ubuntu:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;[automount]
options = &quot;metadata,umask=22,fmask=11&quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then &lt;code&gt;wsl --shutdown&lt;/code&gt; from PowerShell to reload.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;CI/CD parity: GitHub Actions’ &lt;code&gt;ubuntu‑latest&lt;/code&gt; runner mirrors WSL—build once, run everywhere.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;With WSL&amp;nbsp;2, Ubuntu, VS&amp;nbsp;Code, GitHub, and Codex AI all humming together, you’ve fused a decades‑proven Linux toolchain with tomorrow’s AI‑driven coding assistants—without ever leaving Windows. Time to ship something awesome.&lt;/p&gt;&lt;/div&gt;</description><link>https://thundaxsoftware.blogspot.com/2025/04/from-zero-to-aiready-python-on-windows.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgx_fg7Vlt3hPj5JXnRo2MUEmOoNc2ucA47nrCzGDZCZ-C-gXHwA1KKA3VkMstyhLe_iuTKEujEhN-J0D9gdL4oBNttWRJFxivQX0o3d4AUI6U7nKMmJaGp7sjOFOTLY6Be6LzKwa8GVr0M0uPQBiC5Q72Jy1vKPVEx2c6Ww5zg4ji_q7p7zRnlr5_TnSk/s72-c/ChatGPT%20Image%20Apr%2021,%202025,%2011_25_34%20AM.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-1544571025036113280</guid><pubDate>Sun, 20 Apr 2025 19:15:00 +0000</pubDate><atom:updated>2025-04-20T19:15:32.747+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">analysis suite</category><category domain="http://www.blogger.com/atom/ns#">greeks</category><category domain="http://www.blogger.com/atom/ns#">options</category><category domain="http://www.blogger.com/atom/ns#">riskoptima</category><category domain="http://www.blogger.com/atom/ns#">simulator</category><category domain="http://www.blogger.com/atom/ns#">straddle</category><title>RiskOptima Options Analysis Suite</title><description>&lt;h3 style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqMPUtdcG-wlgECSM4HP3IBPXwZF2Qk_xT7BpBSd6ZygIIjtHZnV8tWbiIiy9V0NdxiN0w2NBXM7J548Vmuj-8s8CN_ORsvPtICUksxQkHrnSSKv_2-ZaYi5InA3HGSvAossoHX5de61dSh7a9FB_Y7JhycLHKTCJYyibczHkjMk0Sk7DdljP0FLJ4568/s1024/ChatGPT%20Image%20Apr%2020,%202025,%2008_07_14%20PM.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqMPUtdcG-wlgECSM4HP3IBPXwZF2Qk_xT7BpBSd6ZygIIjtHZnV8tWbiIiy9V0NdxiN0w2NBXM7J548Vmuj-8s8CN_ORsvPtICUksxQkHrnSSKv_2-ZaYi5InA3HGSvAossoHX5de61dSh7a9FB_Y7JhycLHKTCJYyibczHkjMk0Sk7DdljP0FLJ4568/s320/ChatGPT%20Image%20Apr%2020,%202025,%2008_07_14%20PM.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;Analyze IV, Simulate Greeks, and Backtest Strategies — All in One File&lt;/h3&gt;
&lt;blockquote data-end=&quot;401&quot; data-start=&quot;339&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;401&quot; data-start=&quot;341&quot;&gt;Built for speed. Tuned for precision. Engineered to empower.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p class=&quot;&quot; data-end=&quot;479&quot; data-start=&quot;403&quot;&gt;This post lays out a fully functional options trading toolkit that combines:&lt;/p&gt;
&lt;ul data-end=&quot;565&quot; data-start=&quot;480&quot;&gt;
&lt;li class=&quot;&quot; data-end=&quot;509&quot; data-start=&quot;480&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;509&quot; data-start=&quot;482&quot;&gt;🔍 &lt;strong data-end=&quot;509&quot; data-start=&quot;485&quot;&gt;Volatility screening&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;535&quot; data-start=&quot;510&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;535&quot; data-start=&quot;512&quot;&gt;🧠 &lt;strong data-end=&quot;535&quot; data-start=&quot;515&quot;&gt;Greek simulation&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;565&quot; data-start=&quot;536&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;565&quot; data-start=&quot;538&quot;&gt;📊 &lt;strong data-end=&quot;565&quot; data-start=&quot;541&quot;&gt;Straddle backtesting&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;&quot; data-end=&quot;738&quot; data-start=&quot;567&quot;&gt;We use live market data (via &lt;code data-end=&quot;606&quot; data-start=&quot;596&quot;&gt;yfinance&lt;/code&gt;), visualize critical metrics, and ensure all functions are &lt;strong data-end=&quot;698&quot; data-start=&quot;666&quot;&gt;contained in a single script&lt;/strong&gt; — just the way traders like us operate.&lt;/p&gt;
&lt;hr class=&quot;&quot; data-end=&quot;743&quot; data-start=&quot;740&quot; /&gt;
&lt;h2 class=&quot;&quot; data-end=&quot;789&quot; data-start=&quot;745&quot;&gt;🔍 Option Moneyness: Know Where You Stand&lt;/h2&gt;
&lt;p class=&quot;&quot; data-end=&quot;932&quot; data-start=&quot;791&quot;&gt;Before trading any option, you must understand &lt;strong data-end=&quot;900&quot; data-start=&quot;838&quot;&gt;where its strike sits relative to the current market price&lt;/strong&gt; — this is called &lt;strong data-end=&quot;931&quot; data-start=&quot;918&quot;&gt;moneyness&lt;/strong&gt;.&lt;/p&gt;
&lt;p class=&quot;&quot; data-end=&quot;1019&quot; data-start=&quot;934&quot;&gt;&lt;/p&gt;&lt;div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3 class=&quot;&quot; data-end=&quot;1033&quot; data-start=&quot;1021&quot;&gt;✔️ Calls&lt;/h3&gt;
&lt;ul data-end=&quot;1245&quot; data-start=&quot;1034&quot;&gt;
&lt;li class=&quot;&quot; data-end=&quot;1107&quot; data-start=&quot;1034&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;1107&quot; data-start=&quot;1036&quot;&gt;&lt;strong data-end=&quot;1059&quot; data-start=&quot;1036&quot;&gt;In the Money (ITM):&lt;/strong&gt; Strike &amp;lt; Spot — already profitable if exercised&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;1180&quot; data-start=&quot;1108&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;1180&quot; data-start=&quot;1110&quot;&gt;&lt;strong data-end=&quot;1133&quot; data-start=&quot;1110&quot;&gt;At the Money (ATM):&lt;/strong&gt; Strike ≈ Spot — highest gamma, most responsive&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;1245&quot; data-start=&quot;1181&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;1245&quot; data-start=&quot;1183&quot;&gt;&lt;strong data-end=&quot;1210&quot; data-start=&quot;1183&quot;&gt;Out of the Money (OTM):&lt;/strong&gt; Strike &amp;gt; Spot — cheap, speculative&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 class=&quot;&quot; data-end=&quot;1258&quot; data-start=&quot;1247&quot;&gt;✔️ Puts&lt;/h3&gt;
&lt;ul data-end=&quot;1447&quot; data-start=&quot;1259&quot;&gt;
&lt;li class=&quot;&quot; data-end=&quot;1316&quot; data-start=&quot;1259&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;1316&quot; data-start=&quot;1261&quot;&gt;&lt;strong data-end=&quot;1278&quot; data-start=&quot;1261&quot;&gt;In the Money:&lt;/strong&gt; Strike &amp;gt; Spot — pays off below market&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;1377&quot; data-start=&quot;1317&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;1377&quot; data-start=&quot;1319&quot;&gt;&lt;strong data-end=&quot;1336&quot; data-start=&quot;1319&quot;&gt;At the Money:&lt;/strong&gt; Strike ≈ Spot — greatest extrinsic value&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;1447&quot; data-start=&quot;1378&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;1447&quot; data-start=&quot;1380&quot;&gt;&lt;strong data-end=&quot;1401&quot; data-start=&quot;1380&quot;&gt;Out of the Money:&lt;/strong&gt; Strike &amp;lt; Spot — directional downside exposure&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;&quot; data-end=&quot;1521&quot; data-start=&quot;1449&quot;&gt;Use this mental map before entering any directional or volatility trade.&lt;/p&gt;
&lt;hr class=&quot;&quot; data-end=&quot;1526&quot; data-start=&quot;1523&quot; /&gt;
&lt;h2 class=&quot;&quot; data-end=&quot;1564&quot; data-start=&quot;1528&quot;&gt;📈 1. Implied Volatility Screener&lt;/h2&gt;
&lt;h3 class=&quot;&quot; data-end=&quot;1585&quot; data-start=&quot;1566&quot;&gt;❓ What It Does:&lt;/h3&gt;
&lt;p class=&quot;&quot; data-end=&quot;1719&quot; data-start=&quot;1586&quot;&gt;This function retrieves &lt;strong data-end=&quot;1620&quot; data-start=&quot;1610&quot;&gt;ATM IV&lt;/strong&gt; across expirations and compares it to historical volatility over a 30-day period. It helps answer:&lt;/p&gt;
&lt;blockquote data-end=&quot;1773&quot; data-start=&quot;1721&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;1773&quot; data-start=&quot;1723&quot;&gt;“Are options overpriced or underpriced right now?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 class=&quot;&quot; data-end=&quot;1792&quot; data-start=&quot;1775&quot;&gt;✅ Use It For:&lt;/h3&gt;
&lt;ul data-end=&quot;1917&quot; data-start=&quot;1793&quot;&gt;
&lt;li class=&quot;&quot; data-end=&quot;1838&quot; data-start=&quot;1793&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;1838&quot; data-start=&quot;1795&quot;&gt;Deciding whether to &lt;strong data-end=&quot;1838&quot; data-start=&quot;1815&quot;&gt;buy or sell premium&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;1877&quot; data-start=&quot;1839&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;1877&quot; data-start=&quot;1841&quot;&gt;Structuring spreads vs naked options&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;1917&quot; data-start=&quot;1878&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;1917&quot; data-start=&quot;1880&quot;&gt;Understanding term structure and skew&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 class=&quot;&quot; data-end=&quot;1941&quot; data-start=&quot;1919&quot;&gt;📊 Example Output:&lt;/h3&gt;
&lt;p class=&quot;&quot; data-end=&quot;2015&quot; data-start=&quot;1943&quot;&gt;&lt;/p&gt;&lt;div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3 class=&quot;&quot; data-end=&quot;2037&quot; data-start=&quot;2017&quot;&gt;🔍 Observations:&lt;/h3&gt;
&lt;ul data-end=&quot;2332&quot; data-start=&quot;2038&quot;&gt;
&lt;li class=&quot;&quot; data-end=&quot;2134&quot; data-start=&quot;2038&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;2134&quot; data-start=&quot;2040&quot;&gt;Near-term IV (May 2025) is &lt;strong data-end=&quot;2093&quot; data-start=&quot;2067&quot;&gt;significantly elevated&lt;/strong&gt; — likely due to earnings or macro event.&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;2220&quot; data-start=&quot;2135&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;2220&quot; data-start=&quot;2137&quot;&gt;IV sharply declines across the curve, bottoming around 42% in late 2025/early 2026.&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;2332&quot; data-start=&quot;2221&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;2332&quot; data-start=&quot;2223&quot;&gt;The red dashed line = 30-day realized volatility (HV), sitting around 56.7% → options are currently &lt;strong data-end=&quot;2331&quot; data-start=&quot;2323&quot;&gt;rich&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;&quot; data-end=&quot;2452&quot; data-start=&quot;2334&quot;&gt;This chart helps identify &lt;strong data-end=&quot;2380&quot; data-start=&quot;2360&quot;&gt;when to sell vol&lt;/strong&gt; (e.g. short strangles or spreads) or &lt;strong data-end=&quot;2429&quot; data-start=&quot;2418&quot;&gt;buy vol&lt;/strong&gt; (e.g. long straddles).&lt;/p&gt;&lt;p class=&quot;&quot; data-end=&quot;2452&quot; data-start=&quot;2334&quot;&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEhVCUnohzSDJiRUg9zgIYCjigYJxJJOl2W4Ejn_eDD9ubp_qiBXBAMdFlPbAjHDnS7Q-OQj4PmDUL3uFczvDv36hgbHp1Z2fdzfp4xwGheqW4vQDBus8EHBZmuuZZXKbosMDSE4NQlytBUHoO1TBhEzq4qviMsOwYbJoyZ9WNJBKvJkuY2-GYAiX_6bSfA&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;551&quot; data-original-width=&quot;922&quot; height=&quot;382&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEhVCUnohzSDJiRUg9zgIYCjigYJxJJOl2W4Ejn_eDD9ubp_qiBXBAMdFlPbAjHDnS7Q-OQj4PmDUL3uFczvDv36hgbHp1Z2fdzfp4xwGheqW4vQDBus8EHBZmuuZZXKbosMDSE4NQlytBUHoO1TBhEzq4qviMsOwYbJoyZ9WNJBKvJkuY2-GYAiX_6bSfA=w640-h382&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;p&gt;&lt;/p&gt;
&lt;hr class=&quot;&quot; data-end=&quot;2457&quot; data-start=&quot;2454&quot; /&gt;
&lt;h2 class=&quot;&quot; data-end=&quot;2496&quot; data-start=&quot;2459&quot;&gt;🧪 2. Straddle Earnings Backtester&lt;/h2&gt;
&lt;h3 class=&quot;&quot; data-end=&quot;2517&quot; data-start=&quot;2498&quot;&gt;❓ What It Does:&lt;/h3&gt;
&lt;p class=&quot;&quot; data-end=&quot;2555&quot; data-start=&quot;2518&quot;&gt;Backtests a simple earnings strategy:&lt;/p&gt;
&lt;ul data-end=&quot;2675&quot; data-start=&quot;2556&quot;&gt;
&lt;li class=&quot;&quot; data-end=&quot;2595&quot; data-start=&quot;2556&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;2595&quot; data-start=&quot;2558&quot;&gt;Enter straddle 5 days before earnings&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;2623&quot; data-start=&quot;2596&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;2623&quot; data-start=&quot;2598&quot;&gt;Exit 1 day after earnings&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;2675&quot; data-start=&quot;2624&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;2675&quot; data-start=&quot;2626&quot;&gt;Estimate payoff using an assumed 5% straddle cost&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 class=&quot;&quot; data-end=&quot;2694&quot; data-start=&quot;2677&quot;&gt;✅ Use It For:&lt;/h3&gt;
&lt;ul data-end=&quot;2851&quot; data-start=&quot;2695&quot;&gt;
&lt;li class=&quot;&quot; data-end=&quot;2755&quot; data-start=&quot;2695&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;2755&quot; data-start=&quot;2697&quot;&gt;Gauging whether &lt;strong data-end=&quot;2726&quot; data-start=&quot;2713&quot;&gt;vol crush&lt;/strong&gt; post-earnings destroys value&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;2809&quot; data-start=&quot;2756&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;2809&quot; data-start=&quot;2758&quot;&gt;Validating if the &lt;strong data-end=&quot;2794&quot; data-start=&quot;2776&quot;&gt;move justifies&lt;/strong&gt; option premium&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;2851&quot; data-start=&quot;2810&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;2851&quot; data-start=&quot;2812&quot;&gt;Spotting &lt;strong data-end=&quot;2851&quot; data-start=&quot;2821&quot;&gt;overpriced earnings setups&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 class=&quot;&quot; data-end=&quot;2875&quot; data-start=&quot;2853&quot;&gt;📊 Example Output:&lt;/h3&gt;
&lt;p class=&quot;&quot; data-end=&quot;2949&quot; data-start=&quot;2877&quot;&gt;&lt;/p&gt;&lt;div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p class=&quot;&quot; data-end=&quot;2976&quot; data-start=&quot;2951&quot;&gt;&lt;strong data-end=&quot;2976&quot; data-start=&quot;2951&quot;&gt;Summary Stats (AMZN):&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;overflow-visible!&quot; data-end=&quot;3064&quot; data-start=&quot;2977&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between h-9 bg-token-sidebar-surface-primary dark:bg-token-main-surface-secondary select-none rounded-t-[5px]&quot;&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEh6LX3vstxbvxbBbGPEVJSpalY9mmYZ2AlxCgYv8HbyAqqsKxgS_1jd2-eQlGEJ4d_eZ1z4k45_dln7jCPdgNM5zmcIxcelN3yD5VJm9VHrT_uT8gCPhP6kM8IwUkGxkPK0RVvE1akJYhk8272uKb2wiDK2d5HwQn18YhyEECaG62hyyO3GSn4mtx22W7g&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;705&quot; data-original-width=&quot;924&quot; height=&quot;488&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEh6LX3vstxbvxbBbGPEVJSpalY9mmYZ2AlxCgYv8HbyAqqsKxgS_1jd2-eQlGEJ4d_eZ1z4k45_dln7jCPdgNM5zmcIxcelN3yD5VJm9VHrT_uT8gCPhP6kM8IwUkGxkPK0RVvE1akJYhk8272uKb2wiDK2d5HwQn18YhyEECaG62hyyO3GSn4mtx22W7g=w640-h488&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 class=&quot;&quot; data-end=&quot;3086&quot; data-start=&quot;3066&quot;&gt;🔍 Observations:&lt;/h3&gt;
&lt;ul data-end=&quot;3378&quot; data-start=&quot;3087&quot;&gt;
&lt;li class=&quot;&quot; data-end=&quot;3183&quot; data-start=&quot;3087&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;3183&quot; data-start=&quot;3089&quot;&gt;&lt;strong data-end=&quot;3132&quot; data-start=&quot;3089&quot;&gt;All 8 backtested trades were net losses&lt;/strong&gt; — the moves were too small to justify the premium.&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;3271&quot; data-start=&quot;3184&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;3271&quot; data-start=&quot;3186&quot;&gt;Volatility is overpriced before earnings, and the subsequent move doesn’t compensate.&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;3378&quot; data-start=&quot;3272&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;3378&quot; data-start=&quot;3274&quot;&gt;&lt;strong data-end=&quot;3297&quot; data-start=&quot;3274&quot;&gt;Actionable Insight:&lt;/strong&gt; Favor selling premium (like iron condors or ratio spreads) before AMZN earnings.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr class=&quot;&quot; data-end=&quot;3383&quot; data-start=&quot;3380&quot; /&gt;
&lt;h2 class=&quot;&quot; data-end=&quot;3410&quot; data-start=&quot;3385&quot;&gt;🧠 3. Greeks Simulator&lt;/h2&gt;
&lt;h3 class=&quot;&quot; data-end=&quot;3431&quot; data-start=&quot;3412&quot;&gt;❓ What It Does:&lt;/h3&gt;
&lt;p class=&quot;&quot; data-end=&quot;3535&quot; data-start=&quot;3432&quot;&gt;Calculates and visualizes &lt;strong data-end=&quot;3482&quot; data-start=&quot;3458&quot;&gt;option sensitivities&lt;/strong&gt; across strikes for a specific expiry and volatility.&lt;/p&gt;
&lt;h3 class=&quot;&quot; data-end=&quot;3554&quot; data-start=&quot;3537&quot;&gt;✅ Use It For:&lt;/h3&gt;
&lt;ul data-end=&quot;3674&quot; data-start=&quot;3555&quot;&gt;
&lt;li class=&quot;&quot; data-end=&quot;3597&quot; data-start=&quot;3555&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;3597&quot; data-start=&quot;3557&quot;&gt;Understanding where &lt;strong data-end=&quot;3597&quot; data-start=&quot;3577&quot;&gt;gamma is peaking&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;3624&quot; data-start=&quot;3598&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;3624&quot; data-start=&quot;3600&quot;&gt;Managing &lt;strong data-end=&quot;3624&quot; data-start=&quot;3609&quot;&gt;theta decay&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;3674&quot; data-start=&quot;3625&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;3674&quot; data-start=&quot;3627&quot;&gt;Spotting &lt;strong data-end=&quot;3653&quot; data-start=&quot;3636&quot;&gt;vega exposure&lt;/strong&gt; for volatility plays&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 class=&quot;&quot; data-end=&quot;3698&quot; data-start=&quot;3676&quot;&gt;📊 Example Output:&lt;/h3&gt;&lt;div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjc03qhQkNRsgjtVqFqurVsE7tWNptiFCG2D2jG0cCHtAgVOaChBP8QB8wknETwwpArvkNm0P2FQy4mqzAJK5CZs7rGmIZNi1EUsAlD5OFPiAhTEyxsFG4oZyvWsiA9Y45mcJTPzEjD7MSOHW7PYuug0iZJHjGqH10a2BP_CBRiQQIVNN_GoQCmbZJnMuw&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;763&quot; data-original-width=&quot;922&quot; height=&quot;530&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjc03qhQkNRsgjtVqFqurVsE7tWNptiFCG2D2jG0cCHtAgVOaChBP8QB8wknETwwpArvkNm0P2FQy4mqzAJK5CZs7rGmIZNi1EUsAlD5OFPiAhTEyxsFG4oZyvWsiA9Y45mcJTPzEjD7MSOHW7PYuug0iZJHjGqH10a2BP_CBRiQQIVNN_GoQCmbZJnMuw=w640-h530&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;
&lt;p class=&quot;&quot; data-end=&quot;3772&quot; data-start=&quot;3700&quot;&gt;&lt;/p&gt;&lt;div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3 class=&quot;&quot; data-end=&quot;3794&quot; data-start=&quot;3774&quot;&gt;🔍 Observations:&lt;/h3&gt;
&lt;ul data-end=&quot;4017&quot; data-start=&quot;3795&quot;&gt;
&lt;li class=&quot;&quot; data-end=&quot;3880&quot; data-start=&quot;3795&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;3880&quot; data-start=&quot;3797&quot;&gt;&lt;strong data-end=&quot;3848&quot; data-start=&quot;3797&quot;&gt;Gamma and Vega peak at-the-money (Strike ≈ 193)&lt;/strong&gt; — confirming the standard shape&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;3945&quot; data-start=&quot;3881&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;3945&quot; data-start=&quot;3883&quot;&gt;&lt;strong data-end=&quot;3916&quot; data-start=&quot;3883&quot;&gt;Theta is most negative at ATM&lt;/strong&gt; — time decay is fastest here&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;4017&quot; data-start=&quot;3946&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;4017&quot; data-start=&quot;3948&quot;&gt;&lt;strong data-end=&quot;3983&quot; data-start=&quot;3948&quot;&gt;Delta slope confirms call curve&lt;/strong&gt; — higher strikes have lower delta&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;&quot; data-end=&quot;4128&quot; data-start=&quot;4019&quot;&gt;This simulator lets you align trades with your expectations on &lt;strong data-end=&quot;4091&quot; data-start=&quot;4082&quot;&gt;price&lt;/strong&gt;, &lt;strong data-end=&quot;4107&quot; data-start=&quot;4093&quot;&gt;volatility&lt;/strong&gt;, and &lt;strong data-end=&quot;4127&quot; data-start=&quot;4113&quot;&gt;time decay&lt;/strong&gt;.&lt;/p&gt;
&lt;hr class=&quot;&quot; data-end=&quot;4133&quot; data-start=&quot;4130&quot; /&gt;
&lt;h2 class=&quot;&quot; data-end=&quot;4180&quot; data-start=&quot;4135&quot;&gt;🎯 When to Use Calls vs Puts (Quick Guide)&lt;/h2&gt;
&lt;div class=&quot;group pointer-events-none relative flex justify-center *:pointer-events-auto&quot;&gt;&lt;span class=&quot;pointer-events-none absolute start-full top-4 z-10 hidden h-full w-fit ps-2 md:block&quot; data-state=&quot;closed&quot;&gt;&lt;button class=&quot;hover:bg-token-main-surface-secondary text-token-text-secondary pointer-events-auto rounded-lg px-1 py-1 opacity-0 transition-opacity duration-200 group-focus-within:opacity-100 group-hover:opacity-100&quot;&gt;&lt;svg class=&quot;icon-md-heavy&quot; fill=&quot;none&quot; height=&quot;24&quot; viewbox=&quot;0 0 24 24&quot; width=&quot;24&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;path clip-rule=&quot;evenodd&quot; d=&quot;M7 5C7 3.34315 8.34315 2 10 2H19C20.6569 2 22 3.34315 22 5V14C22 15.6569 20.6569 17 19 17H17V19C17 20.6569 15.6569 22 14 22H5C3.34315 22 2 20.6569 2 19V10C2 8.34315 3.34315 7 5 7H7V5ZM9 7H14C15.6569 7 17 8.34315 17 10V15H19C19.5523 15 20 14.5523 20 14V5C20 4.44772 19.5523 4 19 4H10C9.44772 4 9 4.44772 9 5V7ZM5 9C4.44772 9 4 9.44772 4 10V19C4 19.5523 4.44772 20 5 20H14C14.5523 20 15 19.5523 15 19V10C15 9.44772 14.5523 9 14 9H5Z&quot; fill-rule=&quot;evenodd&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/span&gt;&lt;div class=&quot;tableContainer horzScrollShadows relative&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6whMmMo9WfiLw1H4obEEHXVv7SMmCe4z2bcCY6tr6zFgivggNoWI6bpmWhqP9v-UQQHcxsTs99zxztzuS1a3UvSCQ1YKRF4xw6Kz_-sGuZrJ9gf3tUMelHOYjwVUQsXsGCo6qHax2GDTR7F_R9kdBFjtal610pCZw-R44dQOwb4AivsdF5PxCizkBM1c/s1024/ChatGPT%20Image%20Apr%2020,%202025,%2008_03_01%20PM.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6whMmMo9WfiLw1H4obEEHXVv7SMmCe4z2bcCY6tr6zFgivggNoWI6bpmWhqP9v-UQQHcxsTs99zxztzuS1a3UvSCQ1YKRF4xw6Kz_-sGuZrJ9gf3tUMelHOYjwVUQsXsGCo6qHax2GDTR7F_R9kdBFjtal610pCZw-R44dQOwb4AivsdF5PxCizkBM1c/w400-h400/ChatGPT%20Image%20Apr%2020,%202025,%2008_03_01%20PM.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;tableContainer horzScrollShadows relative&quot;&gt;&lt;br /&gt;&lt;table class=&quot;min-w-full&quot; data-end=&quot;4734&quot; data-start=&quot;4182&quot;&gt;&lt;thead data-end=&quot;4260&quot; data-start=&quot;4182&quot;&gt;&lt;tr data-end=&quot;4260&quot; data-start=&quot;4182&quot;&gt;&lt;th data-end=&quot;4210&quot; data-start=&quot;4182&quot;&gt;&lt;br /&gt;Market View&lt;/th&gt;&lt;th data-end=&quot;4229&quot; data-start=&quot;4210&quot;&gt;Strategy&lt;/th&gt;&lt;th data-end=&quot;4260&quot; data-start=&quot;4229&quot;&gt;Example&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody data-end=&quot;4734&quot; data-start=&quot;4340&quot;&gt;&lt;tr data-end=&quot;4418&quot; data-start=&quot;4340&quot;&gt;&lt;td class=&quot;max-w-[calc(var(--thread-content-max-width)*2/3)]&quot; data-end=&quot;4368&quot; data-start=&quot;4340&quot;&gt;Bullish breakout&lt;/td&gt;&lt;td class=&quot;max-w-[calc(var(--thread-content-max-width)*2/3)]&quot; data-end=&quot;4387&quot; data-start=&quot;4368&quot;&gt;Buy Call&lt;/td&gt;&lt;td class=&quot;max-w-[calc(var(--thread-content-max-width)*2/3)]&quot; data-end=&quot;4418&quot; data-start=&quot;4387&quot;&gt;Long 195C if AMZN ≈ 192&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;4497&quot; data-start=&quot;4419&quot;&gt;&lt;td class=&quot;max-w-[calc(var(--thread-content-max-width)*2/3)]&quot; data-end=&quot;4447&quot; data-start=&quot;4419&quot;&gt;Bearish catalyst&lt;/td&gt;&lt;td class=&quot;max-w-[calc(var(--thread-content-max-width)*2/3)]&quot; data-end=&quot;4466&quot; data-start=&quot;4447&quot;&gt;Buy Put&lt;/td&gt;&lt;td class=&quot;max-w-[calc(var(--thread-content-max-width)*2/3)]&quot; data-end=&quot;4497&quot; data-start=&quot;4466&quot;&gt;Long 190P for downside&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;4576&quot; data-start=&quot;4498&quot;&gt;&lt;td class=&quot;max-w-[calc(var(--thread-content-max-width)*2/3)]&quot; data-end=&quot;4526&quot; data-start=&quot;4498&quot;&gt;Big move (unknown dir)&lt;/td&gt;&lt;td class=&quot;max-w-[calc(var(--thread-content-max-width)*2/3)]&quot; data-end=&quot;4545&quot; data-start=&quot;4526&quot;&gt;Buy Straddle&lt;/td&gt;&lt;td class=&quot;max-w-[calc(var(--thread-content-max-width)*2/3)]&quot; data-end=&quot;4576&quot; data-start=&quot;4545&quot;&gt;195C + 195P&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;4655&quot; data-start=&quot;4577&quot;&gt;&lt;td class=&quot;max-w-[calc(var(--thread-content-max-width)*2/3)]&quot; data-end=&quot;4605&quot; data-start=&quot;4577&quot;&gt;Mild bullish move&lt;/td&gt;&lt;td class=&quot;max-w-[calc(var(--thread-content-max-width)*2/3)]&quot; data-end=&quot;4624&quot; data-start=&quot;4605&quot;&gt;Bull Call Spread&lt;/td&gt;&lt;td class=&quot;max-w-[calc(var(--thread-content-max-width)*2/3)]&quot; data-end=&quot;4655&quot; data-start=&quot;4624&quot;&gt;195C / short 200C&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;4734&quot; data-start=&quot;4656&quot;&gt;&lt;td class=&quot;max-w-[calc(var(--thread-content-max-width)*2/3)]&quot; data-end=&quot;4684&quot; data-start=&quot;4656&quot;&gt;Mild bearish move&lt;/td&gt;&lt;td class=&quot;max-w-[calc(var(--thread-content-max-width)*2/3)]&quot; data-end=&quot;4703&quot; data-start=&quot;4684&quot;&gt;Bear Put Spread&lt;/td&gt;&lt;td class=&quot;max-w-[calc(var(--thread-content-max-width)*2/3)]&quot; data-end=&quot;4734&quot; data-start=&quot;4703&quot;&gt;195P / short 190P&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;/div&gt;
&lt;hr class=&quot;&quot; data-end=&quot;4739&quot; data-start=&quot;4736&quot; /&gt;
&lt;h2 class=&quot;&quot; data-end=&quot;4783&quot; data-start=&quot;4741&quot;&gt;🧰 Toolkit Functions (Copy-Paste Ready)&lt;/h2&gt;
&lt;ul data-end=&quot;4961&quot; data-start=&quot;4785&quot;&gt;
&lt;li class=&quot;&quot; data-end=&quot;4815&quot; data-start=&quot;4785&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;4815&quot; data-start=&quot;4787&quot;&gt;&lt;code data-end=&quot;4815&quot; data-start=&quot;4787&quot;&gt;RiskOptima.implied_volatility_screener(symbol=&#39;AMZN&#39;)&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;4887&quot; data-start=&quot;4816&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;4887&quot; data-start=&quot;4818&quot;&gt;&lt;code data-end=&quot;4887&quot; data-start=&quot;4818&quot;&gt;RiskOptima.straddle_backtester(symbol=&#39;AMZN&#39;, window_before=5, window_after=1)&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;4961&quot; data-start=&quot;4888&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;4961&quot; data-start=&quot;4890&quot;&gt;&lt;code data-end=&quot;4961&quot; data-start=&quot;4890&quot;&gt;RiskOptima.greeks_simulator(S=192.82, T_days=20, sigma=0.35, option_type=&#39;call&#39;)&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;&quot; data-end=&quot;5031&quot; data-start=&quot;4963&quot;&gt;All wrapped in a single file. Minimal dependencies. Maximum insight.&lt;/p&gt;
&lt;hr class=&quot;&quot; data-end=&quot;5036&quot; data-start=&quot;5033&quot; /&gt;
&lt;h2 class=&quot;&quot; data-end=&quot;5055&quot; data-start=&quot;5038&quot;&gt;🚀 What’s Next&lt;/h2&gt;
&lt;ul data-end=&quot;5251&quot; data-start=&quot;5057&quot;&gt;
&lt;li class=&quot;&quot; data-end=&quot;5105&quot; data-start=&quot;5057&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;5105&quot; data-start=&quot;5059&quot;&gt;Add &lt;code data-end=&quot;5074&quot; data-start=&quot;5063&quot;&gt;run_all()&lt;/code&gt; orchestrator for batch testing&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;5149&quot; data-start=&quot;5106&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;5149&quot; data-start=&quot;5108&quot;&gt;Plug into &lt;code data-end=&quot;5130&quot; data-start=&quot;5118&quot;&gt;riskoptima&lt;/code&gt; portfolio overlays&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;5190&quot; data-start=&quot;5150&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;5190&quot; data-start=&quot;5152&quot;&gt;Build a Streamlit dashboard (optional)&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;&quot; data-end=&quot;5251&quot; data-start=&quot;5191&quot;&gt;
&lt;p class=&quot;&quot; data-end=&quot;5251&quot; data-start=&quot;5193&quot;&gt;Add edge detection: where historical P&amp;amp;L exceeds breakeven&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;</description><link>https://thundaxsoftware.blogspot.com/2025/04/riskoptima-options-analysis-suite.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqMPUtdcG-wlgECSM4HP3IBPXwZF2Qk_xT7BpBSd6ZygIIjtHZnV8tWbiIiy9V0NdxiN0w2NBXM7J548Vmuj-8s8CN_ORsvPtICUksxQkHrnSSKv_2-ZaYi5InA3HGSvAossoHX5de61dSh7a9FB_Y7JhycLHKTCJYyibczHkjMk0Sk7DdljP0FLJ4568/s72-c/ChatGPT%20Image%20Apr%2020,%202025,%2008_07_14%20PM.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-4612096356937651925</guid><pubDate>Sun, 16 Mar 2025 20:21:00 +0000</pubDate><atom:updated>2025-03-16T20:24:30.340+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">divergenge</category><category domain="http://www.blogger.com/atom/ns#">index</category><category domain="http://www.blogger.com/atom/ns#">riskoptima</category><category domain="http://www.blogger.com/atom/ns#">SP500</category><category domain="http://www.blogger.com/atom/ns#">VIX</category><category domain="http://www.blogger.com/atom/ns#">vol</category><title>Catching Market Turns with SPY &amp; VIX Divergence</title><description>&lt;p&gt;&lt;strong data-end=&quot;69&quot; data-start=&quot;53&quot;&gt;&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;strong data-end=&quot;69&quot; data-start=&quot;53&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj75ZTHT9hMZYeEpO_Ps6vEJ8vTQWTrxQzoGpPEr_oIeN-FteSbMHrrld4aiKNVVTu2XxZR3qdwS7ZvJgGcxM6ey0iMGTBKTaInkOg-424mY-2HC2Rv_uWSSwX2tY_5VbxiG2H4PVxzPF2WnK05UJ4Qhov5q6T7zmOYj42GNPpEjI60W9m4i60-yreWw9I/s1024/74af8554-8758-4e6b-8ec8-581a9edced72.webp&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj75ZTHT9hMZYeEpO_Ps6vEJ8vTQWTrxQzoGpPEr_oIeN-FteSbMHrrld4aiKNVVTu2XxZR3qdwS7ZvJgGcxM6ey0iMGTBKTaInkOg-424mY-2HC2Rv_uWSSwX2tY_5VbxiG2H4PVxzPF2WnK05UJ4Qhov5q6T7zmOYj42GNPpEjI60W9m4i60-yreWw9I/s320/74af8554-8758-4e6b-8ec8-581a9edced72.webp&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/strong&gt;&lt;/div&gt;&lt;strong data-end=&quot;69&quot; data-start=&quot;53&quot;&gt;Introduction&lt;/strong&gt;&lt;p&gt;&lt;/p&gt;&lt;p data-end=&quot;386&quot; data-start=&quot;53&quot;&gt;
Ever wondered how to spot potential turning points in the stock market? One powerful signal emerges when the S&amp;amp;P 500 (SPY) makes a second lower low, but the Volatility Index (VIX) climbs higher than before. This suggests that while prices are pushing lower, fear is mounting—a perfect storm for an oversold bounce.&lt;/p&gt;
&lt;p data-end=&quot;403&quot; data-start=&quot;388&quot;&gt;&lt;strong data-end=&quot;401&quot; data-start=&quot;388&quot;&gt;The Setup&lt;/strong&gt;&lt;/p&gt;
&lt;ol data-end=&quot;695&quot; data-start=&quot;404&quot;&gt;
&lt;li data-end=&quot;500&quot; data-start=&quot;404&quot;&gt;&lt;strong data-end=&quot;422&quot; data-start=&quot;407&quot;&gt;SPY and VIX&lt;/strong&gt;: We monitor both the S&amp;amp;P 500 ETF (SPY) and the CBOE Volatility Index (VIX).&lt;/li&gt;
&lt;li data-end=&quot;695&quot; data-start=&quot;501&quot;&gt;&lt;strong data-end=&quot;542&quot; data-start=&quot;504&quot;&gt;Moving Average and Bollinger Bands&lt;/strong&gt;: We use a 30-day moving average (MA) of SPY plus Bollinger bands set at ±2 standard deviations. These bands define a “reasonable” trading range for SPY.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;713&quot; data-start=&quot;697&quot;&gt;&lt;strong data-end=&quot;711&quot; data-start=&quot;697&quot;&gt;The Signal&lt;/strong&gt;&lt;/p&gt;
&lt;ul data-end=&quot;1201&quot; data-start=&quot;714&quot;&gt;
&lt;li data-end=&quot;788&quot; data-start=&quot;714&quot;&gt;&lt;strong data-end=&quot;732&quot; data-start=&quot;716&quot;&gt;Local Minima&lt;/strong&gt;: Identify points where SPY forms a short-term trough.&lt;/li&gt;
&lt;li data-end=&quot;899&quot; data-start=&quot;789&quot;&gt;&lt;strong data-end=&quot;811&quot; data-start=&quot;791&quot;&gt;Second Lower Low&lt;/strong&gt;: If SPY forms another trough lower than the first, it’s a sign of continued weakness.&lt;/li&gt;
&lt;li data-end=&quot;1034&quot; data-start=&quot;900&quot;&gt;&lt;strong data-end=&quot;916&quot; data-start=&quot;902&quot;&gt;Higher VIX&lt;/strong&gt;: Confirm that the second low in SPY aligns with a higher VIX reading than at the first low, signalling growing fear.&lt;/li&gt;
&lt;li data-end=&quot;1201&quot; data-start=&quot;1035&quot;&gt;&lt;strong data-end=&quot;1051&quot; data-start=&quot;1037&quot;&gt;Within ±2σ&lt;/strong&gt;: Ensure SPY hasn’t broken too far beyond its Bollinger bands. If it’s still within the ±2σ envelope, the market might be primed for a reversion move.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1293&quot; data-start=&quot;1203&quot;&gt;&lt;strong data-end=&quot;1224&quot; data-start=&quot;1203&quot;&gt;Exiting the Trade&lt;/strong&gt;&lt;br data-end=&quot;1227&quot; data-start=&quot;1224&quot; /&gt;
Once a position is entered, exit conditions are straightforward:&lt;/p&gt;
&lt;ol data-end=&quot;1615&quot; data-start=&quot;1294&quot;&gt;
&lt;li data-end=&quot;1466&quot; data-start=&quot;1294&quot;&gt;&lt;strong data-end=&quot;1321&quot; data-start=&quot;1297&quot;&gt;SPY Rallies Above MA&lt;/strong&gt;: If SPY closes above its 30-day moving average, momentum might be shifting back up—take the profit (or limit losses if it didn’t bounce much).&lt;/li&gt;
&lt;li data-end=&quot;1615&quot; data-start=&quot;1467&quot;&gt;&lt;strong data-end=&quot;1501&quot; data-start=&quot;1470&quot;&gt;SPY Breaks Below Lower Band&lt;/strong&gt;: If SPY drops below the lower Bollinger band, the market could be in freefall, so exit to limit further downside.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;1736&quot; data-start=&quot;1617&quot;&gt;&lt;strong data-end=&quot;1645&quot; data-start=&quot;1617&quot;&gt;Visualizing the Strategy&lt;/strong&gt;&lt;br data-end=&quot;1648&quot; data-start=&quot;1645&quot; /&gt;
Below, you can see two charts illustrating how this Index Vol Divergence Strategy works.&lt;/p&gt;
&lt;ol data-end=&quot;2248&quot; data-start=&quot;1738&quot;&gt;
&lt;li data-end=&quot;2085&quot; data-start=&quot;1738&quot;&gt;&lt;strong data-end=&quot;1752&quot; data-start=&quot;1741&quot;&gt;Chart 1&lt;/strong&gt; shows only the &lt;strong data-end=&quot;1785&quot; data-start=&quot;1768&quot;&gt;Entry Signals&lt;/strong&gt;. SPY (blue line) is plotted against its Bollinger bands (dotted orange lines) and 30-day moving average (solid orange line). The VIX is displayed in green at the bottom. Green triangles indicate entry points when the second lower low plus higher VIX condition is met, while SPY remains within ±2σ.&lt;br /&gt;&lt;br /&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8i1hn1EdMZEezXWjnc8YmGVHQsuU8vdrMXvlN8I8Mbpj1NbUkgkprILD70I_Ye_W6N6dHDho2Mqc9Bd3wJcNsr4lOEAxOC_BrgSWkrsTFU6V5TTB3lGozNDkwr6br1f2FW09SmVL4P7uPYmL1ZokaKHnpRs_IHr_aNWmL9lViGurYmB9CzHlmCD6IJxU/s2983/riskoptima_index_vol_divergence_signals_entry_20250316_200414.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1800&quot; data-original-width=&quot;2983&quot; height=&quot;386&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8i1hn1EdMZEezXWjnc8YmGVHQsuU8vdrMXvlN8I8Mbpj1NbUkgkprILD70I_Ye_W6N6dHDho2Mqc9Bd3wJcNsr4lOEAxOC_BrgSWkrsTFU6V5TTB3lGozNDkwr6br1f2FW09SmVL4P7uPYmL1ZokaKHnpRs_IHr_aNWmL9lViGurYmB9CzHlmCD6IJxU/w640-h386/riskoptima_index_vol_divergence_signals_entry_20250316_200414.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li data-end=&quot;2248&quot; data-start=&quot;2086&quot;&gt;&lt;strong data-end=&quot;2100&quot; data-start=&quot;2089&quot;&gt;Chart 2&lt;/strong&gt; includes both &lt;strong data-end=&quot;2141&quot; data-start=&quot;2115&quot;&gt;Entry and Exit Signals&lt;/strong&gt;. The red triangles mark exit points when SPY closes above the 30-day MA or below the lower Bollinger band.&lt;br /&gt;&lt;br /&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizxlhPTxYBdMXRGh-TQXnPwbfD5dUCjpX0J1M_9M1M3B46uLMdscT8ohTvmaCQ7_tgaGM6DbPHmL3XgkCvv-0c4fA9jyHFsF7BIVcKJDa-qFLNofIb8dDYjYEctXtDPwf22fzWpFj2rAo-yDxlLAVCvnsj2aji8DvipT01KbRr6-t9EMQBvPKhl91t8PA/s2984/riskoptima_index_vol_divergence_signals_entry_exit_20250316_200415.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1800&quot; data-original-width=&quot;2984&quot; height=&quot;386&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizxlhPTxYBdMXRGh-TQXnPwbfD5dUCjpX0J1M_9M1M3B46uLMdscT8ohTvmaCQ7_tgaGM6DbPHmL3XgkCvv-0c4fA9jyHFsF7BIVcKJDa-qFLNofIb8dDYjYEctXtDPwf22fzWpFj2rAo-yDxlLAVCvnsj2aji8DvipT01KbRr6-t9EMQBvPKhl91t8PA/w640-h386/riskoptima_index_vol_divergence_signals_entry_exit_20250316_200415.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;2393&quot; data-start=&quot;2250&quot;&gt;These visuals help traders quickly see how the strategy identifies potential “buy-the-dip” opportunities and determines logical points to exit.&lt;/p&gt;
&lt;p data-end=&quot;2714&quot; data-start=&quot;2395&quot;&gt;&lt;strong data-end=&quot;2415&quot; data-start=&quot;2395&quot;&gt;Why This Matters&lt;/strong&gt;&lt;br data-end=&quot;2418&quot; data-start=&quot;2415&quot; /&gt;
This method combines price action (lower lows) with volatility sentiment (rising VIX). It aims to catch the moment when fear peaks and the market could bounce back. By integrating two different signals—price structure and implied volatility—it provides a unique edge for timing entries and exits.&lt;/p&gt;
&lt;p data-end=&quot;2846&quot; data-start=&quot;2716&quot;&gt;&lt;strong data-end=&quot;2735&quot; data-start=&quot;2716&quot;&gt;Getting Started&lt;/strong&gt;&lt;br data-end=&quot;2738&quot; data-start=&quot;2735&quot; /&gt;
We’ve encapsulated this logic in a Python function using &lt;code data-end=&quot;2805&quot; data-start=&quot;2795&quot;&gt;yfinance&lt;/code&gt;, &lt;code data-end=&quot;2815&quot; data-start=&quot;2807&quot;&gt;pandas&lt;/code&gt;, and &lt;code data-end=&quot;2833&quot; data-start=&quot;2821&quot;&gt;matplotlib&lt;/code&gt;. Simply run:&lt;/p&gt;
&lt;pre class=&quot;!overflow-visible&quot; data-end=&quot;2968&quot; data-start=&quot;2848&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary&quot;&gt;&lt;div class=&quot;flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between h-9 bg-token-sidebar-surface-primary dark:bg-token-main-surface-secondary select-none rounded-t-[5px]&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; riskoptima &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; RiskOptima&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;!whitespace-pre&quot;&gt;
df_signals, df_exits, &lt;span class=&quot;hljs-keyword&quot;&gt;returns&lt;/span&gt; = RiskOptima.run_index_vol_divergence_signals()
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;2997&quot; data-start=&quot;2970&quot;&gt;The function automatically:&lt;/p&gt;
&lt;ul data-end=&quot;3279&quot; data-start=&quot;2998&quot;&gt;
&lt;li data-end=&quot;3065&quot; data-start=&quot;2998&quot;&gt;Fetches SPY and VIX data from the specified start to end dates.&lt;/li&gt;
&lt;li data-end=&quot;3112&quot; data-start=&quot;3066&quot;&gt;Identifies entry signals and exit signals.&lt;/li&gt;
&lt;li data-end=&quot;3136&quot; data-start=&quot;3113&quot;&gt;Calculates returns.&lt;/li&gt;
&lt;li data-end=&quot;3192&quot; data-start=&quot;3137&quot;&gt;Saves the final chart images and prints out the resulting DataFrames in the console.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3652&quot; data-start=&quot;3281&quot;&gt;&lt;strong data-end=&quot;3295&quot; data-start=&quot;3281&quot;&gt;Conclusion&lt;/strong&gt;&lt;br data-end=&quot;3298&quot; data-start=&quot;3295&quot; /&gt;
The SPY &amp;amp; VIX Divergence Strategy is a practical way to detect potential buy-the-dip moments in an environment of increasing volatility. While no method is foolproof, combining price patterns with volatility insights can give you a clear, rules-based approach to timing the market. Try it out, tweak the parameters, and see if it fits your trading style!&lt;/p&gt;
&lt;p data-end=&quot;7459&quot; data-start=&quot;7420&quot;&gt;Happy Investing!&lt;br data-end=&quot;7439&quot; data-start=&quot;7436&quot; /&gt;—&amp;nbsp;&lt;em data-end=&quot;7457&quot; data-start=&quot;7441&quot;&gt;Jordi Corbilla&lt;/em&gt;&lt;/p&gt;&lt;p data-end=&quot;7579&quot; data-is-last-node=&quot;&quot; data-start=&quot;7461&quot;&gt;&lt;em data-end=&quot;7493&quot; data-start=&quot;7461&quot;&gt;Check out the repository here:&lt;/em&gt;&lt;br data-end=&quot;7496&quot; data-start=&quot;7493&quot; /&gt;&lt;a data-end=&quot;7579&quot; data-is-last-node=&quot;&quot; data-start=&quot;7496&quot; href=&quot;https://github.com/JordiCorbilla/index-vol-divergence-signals&quot; rel=&quot;noopener&quot; target=&quot;_new&quot;&gt;&lt;strong data-end=&quot;7533&quot; data-start=&quot;7497&quot;&gt;GitHub: JordiCorbilla/index-vol-divergence-signals&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;</description><link>https://thundaxsoftware.blogspot.com/2025/03/catching-market-turns-with-spy-vix.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj75ZTHT9hMZYeEpO_Ps6vEJ8vTQWTrxQzoGpPEr_oIeN-FteSbMHrrld4aiKNVVTu2XxZR3qdwS7ZvJgGcxM6ey0iMGTBKTaInkOg-424mY-2HC2Rv_uWSSwX2tY_5VbxiG2H4PVxzPF2WnK05UJ4Qhov5q6T7zmOYj42GNPpEjI60W9m4i60-yreWw9I/s72-c/74af8554-8758-4e6b-8ec8-581a9edced72.webp" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>London, UK</georss:featurename><georss:point>51.5072178 -0.1275862</georss:point><georss:box>23.196983963821154 -35.2838362 79.817451636178845 35.0286638</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-4867774177469046623</guid><pubDate>Wed, 12 Feb 2025 10:58:00 +0000</pubDate><atom:updated>2025-02-12T11:10:24.696+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">efficient-frontier</category><category domain="http://www.blogger.com/atom/ns#">monte-carlo</category><category domain="http://www.blogger.com/atom/ns#">portfolio-optimization</category><category domain="http://www.blogger.com/atom/ns#">Python</category><title>Introducing RiskOptima: Your New Go-To Library for Portfolio Optimization and Risk Management</title><description>&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgyKfDh_trheQmwnB0edokFqizV7bmb4tb3xSyBx1aUumQg16DQbF0mjORbujVE3rs53PVvbwJS1BUCv_SIAFNtCMHgH4ZNdzFe-CVhQ_l3dOyV6SlQDfjIIloPqLKc7VOJUC09_9Pj0JtNVKjM7N5xQutO6cCctDzyCD5Az4c0hwi0PMt7rZuc5q5xlI0&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;125&quot; data-original-width=&quot;457&quot; height=&quot;88&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgyKfDh_trheQmwnB0edokFqizV7bmb4tb3xSyBx1aUumQg16DQbF0mjORbujVE3rs53PVvbwJS1BUCv_SIAFNtCMHgH4ZNdzFe-CVhQ_l3dOyV6SlQDfjIIloPqLKc7VOJUC09_9Pj0JtNVKjM7N5xQutO6cCctDzyCD5Az4c0hwi0PMt7rZuc5q5xlI0&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;I’m thrilled to announce the launch of &lt;strong data-end=&quot;152&quot; data-start=&quot;138&quot;&gt;RiskOptima&lt;/strong&gt;, a Python library designed to help investors, analysts, and data scientists optimize their portfolios and manage risk with ease. Whether you’re a seasoned quant or just getting started with investment analytics, &lt;strong data-end=&quot;379&quot; data-start=&quot;365&quot;&gt;RiskOptima&lt;/strong&gt; provides a powerful yet flexible framework for portfolio construction, performance benchmarking, and advanced visualization.&lt;p&gt;&lt;/p&gt;&lt;p data-end=&quot;695&quot; data-start=&quot;507&quot;&gt;In this post, I’ll walk you through the motivation behind creating &lt;strong data-end=&quot;588&quot; data-start=&quot;574&quot;&gt;RiskOptima&lt;/strong&gt;, highlight some of its core features, and give a quick tour of the outputs it can generate. Let’s dive in!&lt;/p&gt;&lt;p data-end=&quot;695&quot; data-start=&quot;507&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 data-end=&quot;720&quot; data-start=&quot;702&quot;&gt;Why RiskOptima?&lt;/h2&gt;&lt;ol data-end=&quot;2103&quot; data-start=&quot;722&quot;&gt;&lt;li data-end=&quot;1095&quot; data-start=&quot;722&quot;&gt;&lt;p data-end=&quot;1095&quot; data-start=&quot;725&quot;&gt;&lt;strong data-end=&quot;767&quot; data-start=&quot;725&quot;&gt;Portfolio Optimization Made Accessible&lt;/strong&gt;&lt;br data-end=&quot;770&quot; data-start=&quot;767&quot; /&gt;Traditional portfolio optimization methods (like Markowitz’s Modern Portfolio Theory) can be difficult to implement correctly—especially for those who are new to Python or finance. &lt;strong data-end=&quot;968&quot; data-start=&quot;954&quot;&gt;RiskOptima&lt;/strong&gt; aims to lower the barrier by providing straightforward, well-documented functions to create, optimize, and analyze portfolios.&lt;/p&gt;&lt;/li&gt;&lt;li data-end=&quot;1439&quot; data-start=&quot;1097&quot;&gt;&lt;p data-end=&quot;1439&quot; data-start=&quot;1100&quot;&gt;&lt;strong data-end=&quot;1153&quot; data-start=&quot;1100&quot;&gt;Combining Machine Learning and Statistical Models&lt;/strong&gt;&lt;br data-end=&quot;1156&quot; data-start=&quot;1153&quot; /&gt;As machine learning continues to transform quantitative finance, &lt;strong data-end=&quot;1238&quot; data-start=&quot;1224&quot;&gt;RiskOptima&lt;/strong&gt; incorporates a variety of models to help you compare and contrast results from both statistical and ML-driven approaches. This allows you to find the best method for your specific investment strategy.&lt;/p&gt;&lt;/li&gt;&lt;li data-end=&quot;1798&quot; data-start=&quot;1441&quot;&gt;&lt;p data-end=&quot;1798&quot; data-start=&quot;1444&quot;&gt;&lt;strong data-end=&quot;1490&quot; data-start=&quot;1444&quot;&gt;Interactive and Informative Visualizations&lt;/strong&gt;&lt;br data-end=&quot;1493&quot; data-start=&quot;1490&quot; /&gt;A picture is worth a thousand words. &lt;strong data-end=&quot;1547&quot; data-start=&quot;1533&quot;&gt;RiskOptima&lt;/strong&gt; comes packed with robust visualization tools that bring your data and optimization results to life. From probability distributions to efficient frontiers, the library delivers clear, professional-looking charts to help you interpret outcomes quickly.&lt;/p&gt;&lt;/li&gt;&lt;li data-end=&quot;2103&quot; data-start=&quot;1800&quot;&gt;&lt;p data-end=&quot;2103&quot; data-start=&quot;1803&quot;&gt;&lt;strong data-end=&quot;1833&quot; data-start=&quot;1803&quot;&gt;Open Source and Extensible&lt;/strong&gt;&lt;br data-end=&quot;1836&quot; data-start=&quot;1833&quot; /&gt;Hosted on &lt;a data-end=&quot;1902&quot; data-start=&quot;1849&quot; href=&quot;https://github.com/JordiCorbilla/RiskOptima&quot; rel=&quot;noopener&quot; target=&quot;_new&quot;&gt;GitHub&lt;/a&gt;, &lt;strong data-end=&quot;1918&quot; data-start=&quot;1904&quot;&gt;RiskOptima&lt;/strong&gt; is open to contributions. We welcome bug reports, feature requests, and pull requests. The goal is to build a collaborative ecosystem around portfolio optimization and risk management.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;h2 data-end=&quot;2125&quot; data-start=&quot;2110&quot;&gt;Key Features&lt;/h2&gt;&lt;ul data-end=&quot;3239&quot; data-start=&quot;2127&quot;&gt;&lt;li data-end=&quot;2343&quot; data-start=&quot;2127&quot;&gt;&lt;p data-end=&quot;2343&quot; data-start=&quot;2129&quot;&gt;&lt;strong data-end=&quot;2172&quot; data-start=&quot;2129&quot;&gt;Multiple Portfolio Construction Methods&lt;/strong&gt;&lt;br data-end=&quot;2175&quot; data-start=&quot;2172&quot; /&gt;Compare different portfolio strategies (e.g., equal weighting, market benchmarks, or machine-learning-optimized) to see which one best suits your risk-return profile.&lt;/p&gt;&lt;/li&gt;&lt;li data-end=&quot;2556&quot; data-start=&quot;2345&quot;&gt;&lt;p data-end=&quot;2556&quot; data-start=&quot;2347&quot;&gt;&lt;strong data-end=&quot;2373&quot; data-start=&quot;2347&quot;&gt;Monte Carlo Simulation&lt;/strong&gt;&lt;br data-end=&quot;2376&quot; data-start=&quot;2373&quot; /&gt;Generate thousands of potential outcomes for your portfolio allocations to visualize the distribution of returns and identify optimal weightings under different market scenarios.&lt;/p&gt;&lt;/li&gt;&lt;li data-end=&quot;2727&quot; data-start=&quot;2558&quot;&gt;&lt;p data-end=&quot;2727&quot; data-start=&quot;2560&quot;&gt;&lt;strong data-end=&quot;2591&quot; data-start=&quot;2560&quot;&gt;Efficient Frontier Analysis&lt;/strong&gt;&lt;br data-end=&quot;2594&quot; data-start=&quot;2591&quot; /&gt;Plot your portfolios against the theoretical efficient frontier to see how they compare in terms of expected return and volatility.&lt;/p&gt;&lt;/li&gt;&lt;li data-end=&quot;2917&quot; data-start=&quot;2729&quot;&gt;&lt;p data-end=&quot;2917&quot; data-start=&quot;2731&quot;&gt;&lt;strong data-end=&quot;2759&quot; data-start=&quot;2731&quot;&gt;Performance Benchmarking&lt;/strong&gt;&lt;br data-end=&quot;2762&quot; data-start=&quot;2759&quot; /&gt;Evaluate your portfolios against standard market indices like the S&amp;amp;P 500 (SPY) and compare key metrics such as Sharpe Ratio, Beta, and alpha generation.&lt;/p&gt;&lt;/li&gt;&lt;li data-end=&quot;3087&quot; data-start=&quot;2919&quot;&gt;&lt;p data-end=&quot;3087&quot; data-start=&quot;2921&quot;&gt;&lt;strong data-end=&quot;2958&quot; data-start=&quot;2921&quot;&gt;Risk Decomposition and Allocation&lt;/strong&gt;&lt;br data-end=&quot;2961&quot; data-start=&quot;2958&quot; /&gt;Get detailed breakdowns of how each asset contributes to the overall portfolio risk, helping you fine-tune your allocations.&lt;/p&gt;&lt;/li&gt;&lt;li data-end=&quot;3239&quot; data-start=&quot;3089&quot;&gt;&lt;p data-end=&quot;3239&quot; data-start=&quot;3091&quot;&gt;&lt;strong data-end=&quot;3122&quot; data-start=&quot;3091&quot;&gt;Professional Visualizations&lt;/strong&gt;&lt;br data-end=&quot;3125&quot; data-start=&quot;3122&quot; /&gt;Automatically generate publication-ready plots and charts that can be easily shared in reports or presentations.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;h2 data-end=&quot;3276&quot; data-start=&quot;3246&quot;&gt;A Quick Tour of the Outputs&lt;/h2&gt;&lt;p data-end=&quot;3423&quot; data-start=&quot;3278&quot;&gt;Below are a few examples of the kinds of visualizations &lt;strong data-end=&quot;3348&quot; data-start=&quot;3334&quot;&gt;RiskOptima&lt;/strong&gt; can produce. These charts were generated from a sample portfolio analysis:&lt;/p&gt;&lt;ol data-end=&quot;4765&quot; data-start=&quot;3425&quot;&gt;&lt;li data-end=&quot;3760&quot; data-start=&quot;3425&quot;&gt;&lt;p data-end=&quot;3760&quot; data-start=&quot;3428&quot;&gt;&lt;strong data-end=&quot;3479&quot; data-start=&quot;3428&quot;&gt;Probability Distributions of Final Four Returns&lt;/strong&gt;&lt;br data-end=&quot;3482&quot; data-start=&quot;3479&quot; /&gt;Here, multiple distributions show the potential returns for different portfolio strategies. Each colored curve (green, blue, red, orange) represents a portfolio’s return distribution after a given period. The library also highlights key statistics (mean, VaR, etc.) for each.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj37FgYAnYPjMkHlAP6hDblb2INVzb7T8yxazz9t53x_ZxjMWRX8EZncIbBp2_L5r3JFcPiAmzZQ3LoOOxjf22YNnrDIs0y-4Q1I-uDYMWYywutnRZLHSzHG0LrSS_HO-YayQpRte55N3DX30Y79oUBXOOnt4JicaZcWdHYjEwhTO7Zpr1SZhDNwhCvHEM/s6875/probability_distributions_of_final_fund_returns20250212_095931.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;2559&quot; data-original-width=&quot;6875&quot; height=&quot;238&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj37FgYAnYPjMkHlAP6hDblb2INVzb7T8yxazz9t53x_ZxjMWRX8EZncIbBp2_L5r3JFcPiAmzZQ3LoOOxjf22YNnrDIs0y-4Q1I-uDYMWYywutnRZLHSzHG0LrSS_HO-YayQpRte55N3DX30Y79oUBXOOnt4JicaZcWdHYjEwhTO7Zpr1SZhDNwhCvHEM/w640-h238/probability_distributions_of_final_fund_returns20250212_095931.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;/li&gt;&lt;li data-end=&quot;4118&quot; data-start=&quot;3762&quot;&gt;&lt;p data-end=&quot;4118&quot; data-start=&quot;3765&quot;&gt;&lt;strong data-end=&quot;3808&quot; data-start=&quot;3765&quot;&gt;Portfolio Optimization and Benchmarking&lt;/strong&gt;&lt;br data-end=&quot;3811&quot; data-start=&quot;3808&quot; /&gt;This time-series chart compares the performance of various portfolios over time, alongside benchmarks like the S&amp;amp;P 500. A summary table highlights the Sharpe Ratios, Betas, and other risk-adjusted performance metrics, making it easy to identify which strategy outperforms under certain market conditions.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi024USMEGcX0yeKilUoL0eHb_dCZR0JEEH8EE0oazGyE8_jRtsUPyVvBl3tly13_rrELieYmTzCf5plphYwieCJz71AZgyiAP3LYCsCF4zSp6c4EaWEcIKyUaklgJIIND0hBkU3avu4b3ff7gP8Q01I7q8U6SF2fXTFT1CrbXQQ8dQhSByT7seYrLNGqU/s7830/machine_learning_optimization_20250212_095822.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;3247&quot; data-original-width=&quot;7830&quot; height=&quot;266&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi024USMEGcX0yeKilUoL0eHb_dCZR0JEEH8EE0oazGyE8_jRtsUPyVvBl3tly13_rrELieYmTzCf5plphYwieCJz71AZgyiAP3LYCsCF4zSp6c4EaWEcIKyUaklgJIIND0hBkU3avu4b3ff7gP8Q01I7q8U6SF2fXTFT1CrbXQQ8dQhSByT7seYrLNGqU/w640-h266/machine_learning_optimization_20250212_095822.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;/li&gt;&lt;li data-end=&quot;4493&quot; data-start=&quot;4120&quot;&gt;&lt;p data-end=&quot;4493&quot; data-start=&quot;4123&quot;&gt;&lt;strong data-end=&quot;4170&quot; data-start=&quot;4123&quot;&gt;Efficient Frontier (Monte Carlo Simulation)&lt;/strong&gt;&lt;br data-end=&quot;4173&quot; data-start=&quot;4170&quot; /&gt;A scatter plot of simulated portfolios appears in purple, with the efficient frontier in blue. Different star or dot markers represent specific portfolios (e.g., current vs. optimized). This allows you to see where your portfolio lies relative to the theoretical boundary of maximum returns for a given level of risk.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkzxe65DdUy3cTE0qoE8LODvdfJ3aXozNLeagabvsByKMT7GfbQ-ioF-AqGDjtCOMMhqb3el986o9avNDP8-ZsO22aidVFC-QjyiCdkLi0JDdvPollhPC2epqdjHwZth7cdSdVOopMAvSTrMkWGlWXPSgvmv1NUuviGaI5Vxav0HEoz7ypHU3i-UAhQrE/s6718/efficient_frontier_monter_carlo_20250212_095715.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;2976&quot; data-original-width=&quot;6718&quot; height=&quot;284&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkzxe65DdUy3cTE0qoE8LODvdfJ3aXozNLeagabvsByKMT7GfbQ-ioF-AqGDjtCOMMhqb3el986o9avNDP8-ZsO22aidVFC-QjyiCdkLi0JDdvPollhPC2epqdjHwZth7cdSdVOopMAvSTrMkWGlWXPSgvmv1NUuviGaI5Vxav0HEoz7ypHU3i-UAhQrE/w640-h284/efficient_frontier_monter_carlo_20250212_095715.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;/li&gt;&lt;li data-end=&quot;4765&quot; data-start=&quot;4495&quot;&gt;&lt;p data-end=&quot;4765&quot; data-start=&quot;4498&quot;&gt;&lt;strong data-end=&quot;4522&quot; data-start=&quot;4498&quot;&gt;Portfolio Area Chart&lt;/strong&gt;&lt;br data-end=&quot;4525&quot; data-start=&quot;4522&quot; /&gt;This treemap visualization provides a snapshot of your holdings and their recent returns. It’s a quick way to see which assets are driving performance (or detracting from it) and how much each holding contributes to the total allocation.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIgdSkhks1XyiAbZ9vP1UIGndIxiIrcjEA2IO8yJ3-x0eZOE7puYq4bjAjNQwgqUi8oEbzEdEarNdPf0id5NUsBkXPomlXCrr4Rx0SYtb8neO6SHYQAhBBjCjc8y3pBjXQQq88Dt-OiEZs97h7ePB64lhAdVFMPOpo4q-rXi2kfPlpvTS0YCbOfRVbCBY/s4581/portfolio_area_chart_20250212_095626.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;2914&quot; data-original-width=&quot;4581&quot; height=&quot;408&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIgdSkhks1XyiAbZ9vP1UIGndIxiIrcjEA2IO8yJ3-x0eZOE7puYq4bjAjNQwgqUi8oEbzEdEarNdPf0id5NUsBkXPomlXCrr4Rx0SYtb8neO6SHYQAhBBjCjc8y3pBjXQQq88Dt-OiEZs97h7ePB64lhAdVFMPOpo4q-rXi2kfPlpvTS0YCbOfRVbCBY/w640-h408/portfolio_area_chart_20250212_095626.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;&lt;h2 data-end=&quot;4790&quot; data-start=&quot;4772&quot;&gt;Getting Started&lt;/h2&gt;&lt;p data-end=&quot;4857&quot; data-start=&quot;4792&quot;&gt;Ready to give &lt;strong data-end=&quot;4820&quot; data-start=&quot;4806&quot;&gt;RiskOptima&lt;/strong&gt; a try? Here’s how you can set it up:&lt;/p&gt;&lt;ol data-end=&quot;5842&quot; data-start=&quot;4859&quot;&gt;&lt;li data-end=&quot;5072&quot; data-start=&quot;4859&quot;&gt;&lt;p data-end=&quot;4886&quot; data-start=&quot;4862&quot;&gt;&lt;strong data-end=&quot;4884&quot; data-start=&quot;4862&quot;&gt;Install via GitHub&lt;/strong&gt;&lt;/p&gt;&lt;pre class=&quot;!overflow-visible&quot; data-end=&quot;5017&quot; data-start=&quot;4890&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary dark:bg-gray-950&quot;&gt;&lt;div class=&quot;flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between rounded-t-[5px] h-9 bg-token-sidebar-surface-primary dark:bg-token-main-surface-secondary select-none&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;!whitespace-pre language-bash&quot;&gt;git &lt;span class=&quot;hljs-built_in&quot;&gt;clone&lt;/span&gt; https://github.com/JordiCorbilla/RiskOptima.git
&lt;span class=&quot;hljs-built_in&quot;&gt;cd&lt;/span&gt; RiskOptima
pip install -r requirements.txt&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li data-end=&quot;5357&quot; data-start=&quot;5074&quot;&gt;&lt;p data-end=&quot;5104&quot; data-start=&quot;5077&quot;&gt;&lt;strong data-end=&quot;5102&quot; data-start=&quot;5077&quot;&gt;Import and Initialize&lt;/strong&gt;&lt;/p&gt;&lt;pre class=&quot;!overflow-visible&quot; data-end=&quot;5357&quot; data-start=&quot;5108&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary dark:bg-gray-950&quot;&gt;&lt;div class=&quot;flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between rounded-t-[5px] h-9 bg-token-sidebar-surface-primary dark:bg-token-main-surface-secondary select-none&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; riskoptima &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; riskoptima&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;!whitespace-pre language-python&quot;&gt;
&lt;span class=&quot;hljs-comment&quot;&gt;# Initialize your data&lt;/span&gt;
&lt;span class=&quot;hljs-comment&quot;&gt;# For example, price data or returns data for each asset&lt;/span&gt;
&lt;span class=&quot;hljs-comment&quot;&gt;# Then pass it to riskoptima for analysis&lt;/span&gt;
portfolio = riskoptima.YourPortfolioFunction(data=your_data)
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li data-end=&quot;5645&quot; data-start=&quot;5359&quot;&gt;&lt;p data-end=&quot;5384&quot; data-start=&quot;5362&quot;&gt;&lt;strong data-end=&quot;5382&quot; data-start=&quot;5362&quot;&gt;Run Optimization&lt;/strong&gt;&lt;/p&gt;&lt;pre class=&quot;!overflow-visible&quot; data-end=&quot;5645&quot; data-start=&quot;5388&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary dark:bg-gray-950&quot;&gt;&lt;div class=&quot;flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between rounded-t-[5px] h-9 bg-token-sidebar-surface-primary dark:bg-token-main-surface-secondary select-none&quot;&gt;# Example function calls (subject to your actual implementation)&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;!whitespace-pre language-python&quot;&gt;optimal_portfolio = portfolio.optimize(method=&lt;span class=&quot;hljs-string&quot;&gt;&quot;machine_learning&quot;&lt;/span&gt;)
optimal_portfolio.run_monte_carlo_simulation(n=&lt;span class=&quot;hljs-number&quot;&gt;10000&lt;/span&gt;)
optimal_portfolio.plot_efficient_frontier()
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li data-end=&quot;5842&quot; data-start=&quot;5647&quot;&gt;&lt;p data-end=&quot;5673&quot; data-start=&quot;5650&quot;&gt;&lt;strong data-end=&quot;5671&quot; data-start=&quot;5650&quot;&gt;Visualize Results&lt;/strong&gt;&lt;/p&gt;&lt;pre class=&quot;!overflow-visible&quot; data-end=&quot;5842&quot; data-start=&quot;5677&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary dark:bg-gray-950&quot;&gt;&lt;div class=&quot;flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between rounded-t-[5px] h-9 bg-token-sidebar-surface-primary dark:bg-token-main-surface-secondary select-none&quot;&gt;# Generate and save plots&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;!whitespace-pre language-python&quot;&gt;portfolio.plot_probability_distributions()
portfolio.plot_performance_comparison()
portfolio.plot_area_chart()
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;hr data-end=&quot;5847&quot; data-start=&quot;5844&quot; /&gt;&lt;h2 data-end=&quot;5864&quot; data-start=&quot;5849&quot;&gt;What’s Next?&lt;/h2&gt;&lt;p data-end=&quot;5942&quot; data-start=&quot;5866&quot;&gt;&lt;strong data-end=&quot;5880&quot; data-start=&quot;5866&quot;&gt;RiskOptima&lt;/strong&gt; is just getting started! In the coming weeks, I’ll be adding:&lt;/p&gt;&lt;ul data-end=&quot;6372&quot; data-start=&quot;5944&quot;&gt;&lt;li data-end=&quot;6043&quot; data-start=&quot;5944&quot;&gt;&lt;strong data-end=&quot;5968&quot; data-start=&quot;5946&quot;&gt;Enhanced ML Models&lt;/strong&gt;: Additional regression/classification techniques for return forecasting.&lt;/li&gt;&lt;li data-end=&quot;6137&quot; data-start=&quot;6044&quot;&gt;&lt;strong data-end=&quot;6067&quot; data-start=&quot;6046&quot;&gt;Scenario Analysis&lt;/strong&gt;: Stress testing portfolios under different macroeconomic scenarios.&lt;/li&gt;&lt;li data-end=&quot;6240&quot; data-start=&quot;6138&quot;&gt;&lt;strong data-end=&quot;6166&quot; data-start=&quot;6140&quot;&gt;Live Data Integrations&lt;/strong&gt;: Hooks for popular financial data APIs to automate your data pipelines.&lt;/li&gt;&lt;li data-end=&quot;6372&quot; data-start=&quot;6241&quot;&gt;&lt;strong data-end=&quot;6274&quot; data-start=&quot;6243&quot;&gt;Documentation and Tutorials&lt;/strong&gt;: Step-by-step guides, notebooks, and sample projects to help you get the most out of the library.&lt;/li&gt;&lt;/ul&gt;&lt;hr data-end=&quot;6377&quot; data-start=&quot;6374&quot; /&gt;&lt;h2 data-end=&quot;6400&quot; data-start=&quot;6379&quot;&gt;Join the Community&lt;/h2&gt;&lt;p data-end=&quot;6502&quot; data-start=&quot;6402&quot;&gt;I’d love your feedback on &lt;strong data-end=&quot;6442&quot; data-start=&quot;6428&quot;&gt;RiskOptima&lt;/strong&gt;. If you have questions, suggestions, or want to contribute:&lt;/p&gt;&lt;ul data-end=&quot;6958&quot; data-start=&quot;6504&quot;&gt;&lt;li data-end=&quot;6625&quot; data-start=&quot;6504&quot;&gt;&lt;strong data-end=&quot;6523&quot; data-start=&quot;6506&quot;&gt;Star the Repo&lt;/strong&gt;: Show your support by starring &lt;a data-end=&quot;6622&quot; data-start=&quot;6555&quot; href=&quot;https://github.com/JordiCorbilla/RiskOptima&quot; rel=&quot;noopener&quot; target=&quot;_new&quot;&gt;RiskOptima on GitHub&lt;/a&gt;.&lt;/li&gt;&lt;li data-end=&quot;6758&quot; data-start=&quot;6626&quot;&gt;&lt;strong data-end=&quot;6643&quot; data-start=&quot;6628&quot;&gt;Open Issues&lt;/strong&gt;: Encounter a bug or have a feature request? &lt;a data-end=&quot;6755&quot; data-start=&quot;6688&quot; href=&quot;https://github.com/JordiCorbilla/RiskOptima/issues&quot; rel=&quot;noopener&quot; target=&quot;_new&quot;&gt;Open an issue&lt;/a&gt;.&lt;/li&gt;&lt;li data-end=&quot;6850&quot; data-start=&quot;6759&quot;&gt;&lt;strong data-end=&quot;6785&quot; data-start=&quot;6761&quot;&gt;Submit Pull Requests&lt;/strong&gt;: Got a great idea? Help the library grow by contributing code.&lt;/li&gt;&lt;li data-end=&quot;6958&quot; data-start=&quot;6851&quot;&gt;&lt;strong data-end=&quot;6872&quot; data-start=&quot;6853&quot;&gt;Spread the Word&lt;/strong&gt;: Share this project with friends and colleagues interested in portfolio optimization.&lt;/li&gt;&lt;/ul&gt;&lt;hr data-end=&quot;6963&quot; data-start=&quot;6960&quot; /&gt;&lt;h2 data-end=&quot;6978&quot; data-start=&quot;6965&quot;&gt;Conclusion&lt;/h2&gt;&lt;p data-end=&quot;7259&quot; data-start=&quot;6980&quot;&gt;&lt;strong data-end=&quot;6994&quot; data-start=&quot;6980&quot;&gt;RiskOptima&lt;/strong&gt; is designed to simplify the complex world of portfolio optimization and risk management by providing intuitive tools, clear visualizations, and powerful algorithms. With more features on the way, I’m excited to see how the community uses and improves this library.&lt;/p&gt;&lt;p data-end=&quot;7418&quot; data-start=&quot;7261&quot;&gt;&lt;strong data-end=&quot;7275&quot; data-start=&quot;7261&quot;&gt;Try it out&lt;/strong&gt;, explore the charts, and let me know what you think. Together, we can make &lt;strong data-end=&quot;7365&quot; data-start=&quot;7351&quot;&gt;RiskOptima&lt;/strong&gt; the go-to library for modern portfolio optimization!&lt;/p&gt;&lt;p data-end=&quot;7459&quot; data-start=&quot;7420&quot;&gt;Happy Investing!&lt;br data-end=&quot;7439&quot; data-start=&quot;7436&quot; /&gt;— &lt;em data-end=&quot;7457&quot; data-start=&quot;7441&quot;&gt;Jordi Corbilla&lt;/em&gt;&lt;/p&gt;&lt;p data-end=&quot;7579&quot; data-is-last-node=&quot;&quot; data-start=&quot;7461&quot;&gt;&lt;em data-end=&quot;7493&quot; data-start=&quot;7461&quot;&gt;Check out the repository here:&lt;/em&gt;&lt;br data-end=&quot;7496&quot; data-start=&quot;7493&quot; /&gt;&lt;a data-end=&quot;7579&quot; data-is-last-node=&quot;&quot; data-start=&quot;7496&quot; href=&quot;https://github.com/JordiCorbilla/RiskOptima&quot; rel=&quot;noopener&quot; target=&quot;_new&quot;&gt;&lt;strong data-end=&quot;7533&quot; data-start=&quot;7497&quot;&gt;GitHub: JordiCorbilla/RiskOptima&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;</description><link>https://thundaxsoftware.blogspot.com/2025/02/introducing-riskoptima-your-new-go-to.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEgyKfDh_trheQmwnB0edokFqizV7bmb4tb3xSyBx1aUumQg16DQbF0mjORbujVE3rs53PVvbwJS1BUCv_SIAFNtCMHgH4ZNdzFe-CVhQ_l3dOyV6SlQDfjIIloPqLKc7VOJUC09_9Pj0JtNVKjM7N5xQutO6cCctDzyCD5Az4c0hwi0PMt7rZuc5q5xlI0=s72-c" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-6455393185674257614</guid><pubDate>Sat, 01 Feb 2025 21:06:00 +0000</pubDate><atom:updated>2025-02-12T11:11:07.476+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">ChatBot</category><category domain="http://www.blogger.com/atom/ns#">llm</category><category domain="http://www.blogger.com/atom/ns#">openAI</category><category domain="http://www.blogger.com/atom/ns#">PDF</category><category domain="http://www.blogger.com/atom/ns#">RAG</category><title>Building an AI-Powered PDF Chatbot Using OpenAI and Retrieval-Augmented Generation (RAG)</title><description>&lt;h2&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5Cw3rrG8FIlVxOD5zoV9JElKDXg4ehX0azkwjKSIPpzpJpIPzYpddVDCzVxhf15pxZZMEa98K4qZq3UlOIzms903Klyf17JYb4j2_ol6uKC9fN-WejFEJvXonDYLP4EZjsABj67O0sjPYJds3lW1a1uHGhQfh2n87-sgt-DxgFIwb6C1pDWf4nKt7d2I/s1024/DALL%C2%B7E%202025-02-01%2021.02.43%20-%20A%20conceptual%20illustration%20of%20a%20Retrieval-Augmented%20Generation%20(RAG)%20pipeline%20for%20querying%20a%20PDF%20document.%20The%20image%20should%20depict%20a%20structured%20workflo.webp&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5Cw3rrG8FIlVxOD5zoV9JElKDXg4ehX0azkwjKSIPpzpJpIPzYpddVDCzVxhf15pxZZMEa98K4qZq3UlOIzms903Klyf17JYb4j2_ol6uKC9fN-WejFEJvXonDYLP4EZjsABj67O0sjPYJds3lW1a1uHGhQfh2n87-sgt-DxgFIwb6C1pDWf4nKt7d2I/s320/DALL%C2%B7E%202025-02-01%2021.02.43%20-%20A%20conceptual%20illustration%20of%20a%20Retrieval-Augmented%20Generation%20(RAG)%20pipeline%20for%20querying%20a%20PDF%20document.%20The%20image%20should%20depict%20a%20structured%20workflo.webp&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Have you ever struggled to find a specific piece of information buried deep inside a long PDF document? Whether it’s a financial report, a legal document, or a research paper, manually searching for answers can be frustrating and time-consuming.&lt;/p&gt;&lt;p&gt;What if you could simply ask a question and get an AI-powered answer instantly—without reading the entire document?&lt;/p&gt;&lt;p&gt;In this article, we’ll explore how to build a &lt;strong&gt;Retrieval-Augmented Generation (RAG) pipeline&lt;/strong&gt; using &lt;strong&gt;OpenAI’s GPT models&lt;/strong&gt; and &lt;strong&gt;vector databases&lt;/strong&gt; to create a chatbot that can answer questions from any PDF document.&lt;/p&gt;&lt;hr /&gt;&lt;h2&gt;&lt;strong&gt;Why Not Just Use GPT-4 Alone?&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Large Language Models (LLMs) like GPT-4 are powerful but &lt;strong&gt;lack context about specific documents&lt;/strong&gt; unless that information is included in the prompt. If you were to feed an entire PDF into a GPT-4 prompt, you’d quickly hit &lt;strong&gt;token limits&lt;/strong&gt; and face &lt;strong&gt;high API costs&lt;/strong&gt;.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;The Solution: Retrieval-Augmented Generation (RAG)&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Instead of passing entire PDFs to GPT-4, we &lt;strong&gt;split, store, and retrieve&lt;/strong&gt; only the most relevant sections of a document before asking the model to generate a response.&lt;/p&gt;&lt;p&gt;A &lt;strong&gt;RAG pipeline&lt;/strong&gt; consists of three key steps:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Extracting and Splitting Text&lt;/strong&gt;: Convert a PDF into manageable chunks.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Vectorising &amp;amp; Storing the Chunks&lt;/strong&gt;: Convert text into embeddings and store them in a vector database for quick retrieval.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Querying &amp;amp; Response Generation&lt;/strong&gt;: Retrieve relevant chunks and use GPT-4 to answer the user’s query.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;This approach drastically &lt;strong&gt;reduces token usage&lt;/strong&gt;, making queries faster and more cost-effective.&lt;/p&gt;&lt;hr /&gt;&lt;h2&gt;&lt;strong&gt;Building the AI-Powered PDF Chatbot&lt;/strong&gt;&lt;/h2&gt;&lt;h3&gt;&lt;strong&gt;Tools and Technologies Used&lt;/strong&gt;&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;LangChain&lt;/strong&gt;: For orchestrating LLM calls, vector search, and retrieval.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;OpenAI GPT-4&lt;/strong&gt;: For generating responses based on retrieved content.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;FAISS / ChromaDB&lt;/strong&gt;: To store and retrieve relevant document chunks efficiently.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;PyPDFLoader&lt;/strong&gt;: To extract text from PDFs.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;&lt;strong&gt;Step 1: Install Dependencies&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;First, install the necessary Python libraries:&lt;/p&gt;&lt;pre class=&quot;!overflow-visible&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary dark:bg-gray-950&quot;&gt;&lt;div class=&quot;flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between rounded-t-md h-9 bg-token-sidebar-surface-primary dark:bg-token-main-surface-secondary select-none&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;!whitespace-pre hljs language-bash&quot;&gt;pip install langchain openai chromadb faiss-cpu tiktoken PyPDF2
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;h3&gt;&lt;strong&gt;Step 2: Load and Process the PDF&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;We use &lt;strong&gt;PyPDFLoader&lt;/strong&gt; to extract text and &lt;strong&gt;RecursiveCharacterTextSplitter&lt;/strong&gt; to split it into chunks that are easier to search.&lt;/p&gt;&lt;pre class=&quot;!overflow-visible&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary dark:bg-gray-950&quot;&gt;&lt;div class=&quot;flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between rounded-t-md h-9 bg-token-sidebar-surface-primary dark:bg-token-main-surface-secondary select-none&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;!whitespace-pre hljs language-python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; langchain.document_loaders &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; PyPDFLoader
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; langchain.text_splitter &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; RecursiveCharacterTextSplitter

&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;load_and_preprocess_pdf&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;pdf_path&lt;/span&gt;):
    loader = PyPDFLoader(pdf_path)
    documents = loader.load()

    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=&lt;span class=&quot;hljs-number&quot;&gt;1000&lt;/span&gt;, chunk_overlap=&lt;span class=&quot;hljs-number&quot;&gt;200&lt;/span&gt;
    )
    chunks = text_splitter.split_documents(documents)
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; chunks
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;h3&gt;&lt;strong&gt;Step 3: Store and Retrieve Document Chunks&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;We convert text chunks into &lt;strong&gt;embeddings&lt;/strong&gt; and store them in &lt;strong&gt;ChromaDB&lt;/strong&gt; for retrieval.&lt;/p&gt;&lt;pre class=&quot;!overflow-visible&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary dark:bg-gray-950&quot;&gt;&lt;div class=&quot;flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between rounded-t-md h-9 bg-token-sidebar-surface-primary dark:bg-token-main-surface-secondary select-none&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;!whitespace-pre hljs language-python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; langchain.vectorstores &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; Chroma
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; langchain.embeddings.openai &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; OpenAIEmbeddings

&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;create_or_load_vector_store&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;chunks&lt;/span&gt;):
    embeddings = OpenAIEmbeddings()
    persist_directory = &lt;span class=&quot;hljs-string&quot;&gt;&quot;chroma_vector_store&quot;&lt;/span&gt;

    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; os.path.exists(persist_directory):
        &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Loading existing vector store...&quot;&lt;/span&gt;)
        vectorstore = Chroma(persist_directory=persist_directory, embedding_function=embeddings)
    &lt;span class=&quot;hljs-keyword&quot;&gt;else&lt;/span&gt;:
        &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Creating new vector store...&quot;&lt;/span&gt;)
        vectorstore = Chroma.from_documents(chunks, embeddings, persist_directory=persist_directory)
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; vectorstore
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;h3&gt;&lt;strong&gt;Step 4: Query GPT-4 with Retrieved Chunks&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;We retrieve &lt;strong&gt;only the most relevant document chunks&lt;/strong&gt; and pass them to GPT-4 for an intelligent response.&lt;/p&gt;&lt;pre class=&quot;!overflow-visible&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary dark:bg-gray-950&quot;&gt;&lt;div class=&quot;flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between rounded-t-md h-9 bg-token-sidebar-surface-primary dark:bg-token-main-surface-secondary select-none&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;!whitespace-pre hljs language-python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; langchain.chains &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; RetrievalQA
&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; langchain.chat_models &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; ChatOpenAI

&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;create_rag_pipeline&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;vectorstore&lt;/span&gt;):
    retriever = vectorstore.as_retriever(search_type=&lt;span class=&quot;hljs-string&quot;&gt;&quot;similarity&quot;&lt;/span&gt;, search_kwargs={&lt;span class=&quot;hljs-string&quot;&gt;&quot;k&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;})
    qa_chain = RetrievalQA.from_chain_type(
        llm=ChatOpenAI(model=&lt;span class=&quot;hljs-string&quot;&gt;&quot;gpt-4&quot;&lt;/span&gt;, streaming=&lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;),
        retriever=retriever,
        return_source_documents=&lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;
    )
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; qa_chain
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;h3&gt;&lt;strong&gt;Step 5: Track Token Usage and Costs&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;To measure the cost efficiency of RAG, we log token usage:&lt;/p&gt;&lt;pre class=&quot;!overflow-visible&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary dark:bg-gray-950&quot;&gt;&lt;div class=&quot;flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between rounded-t-md h-9 bg-token-sidebar-surface-primary dark:bg-token-main-surface-secondary select-none&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;!whitespace-pre hljs language-python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; langchain.callbacks &lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; get_openai_callback

&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;query_rag_pipeline&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;rag_pipeline, query&lt;/span&gt;):
    &lt;span class=&quot;hljs-keyword&quot;&gt;with&lt;/span&gt; get_openai_callback() &lt;span class=&quot;hljs-keyword&quot;&gt;as&lt;/span&gt; callback:
        result = rag_pipeline({&lt;span class=&quot;hljs-string&quot;&gt;&quot;query&quot;&lt;/span&gt;: query})
        
        &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;\nAnswer:&quot;&lt;/span&gt;)
        &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(result[&lt;span class=&quot;hljs-string&quot;&gt;&quot;result&quot;&lt;/span&gt;])

        &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;\nToken Usage:&quot;&lt;/span&gt;)
        &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;f&quot;- Prompt tokens: &lt;span class=&quot;hljs-subst&quot;&gt;{callback.prompt_tokens}&lt;/span&gt;&quot;&lt;/span&gt;)
        &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;f&quot;- Completion tokens: &lt;span class=&quot;hljs-subst&quot;&gt;{callback.completion_tokens}&lt;/span&gt;&quot;&lt;/span&gt;)
        &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;f&quot;- Total tokens: &lt;span class=&quot;hljs-subst&quot;&gt;{callback.total_tokens}&lt;/span&gt;&quot;&lt;/span&gt;)
        &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;f&quot;- Estimated cost: $&lt;span class=&quot;hljs-subst&quot;&gt;{callback.total_cost:&lt;span class=&quot;hljs-number&quot;&gt;.5&lt;/span&gt;f}&lt;/span&gt;&quot;&lt;/span&gt;)
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; result
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;h3&gt;&lt;strong&gt;Step 6: Run the Chatbot&lt;/strong&gt;&lt;/h3&gt;&lt;pre class=&quot;!overflow-visible&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary dark:bg-gray-950&quot;&gt;&lt;div class=&quot;flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between rounded-t-md h-9 bg-token-sidebar-surface-primary dark:bg-token-main-surface-secondary select-none&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;!whitespace-pre hljs language-python&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;main&lt;/span&gt;():
    pdf_path = &lt;span class=&quot;hljs-string&quot;&gt;&quot;example.pdf&quot;&lt;/span&gt;
    &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Loading and processing PDF...&quot;&lt;/span&gt;)
    chunks = load_and_preprocess_pdf(pdf_path)

    &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Creating/loading vector store...&quot;&lt;/span&gt;)
    vectorstore = create_or_load_vector_store(chunks)

    &lt;span class=&quot;hljs-built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Setting up RAG pipeline...&quot;&lt;/span&gt;)
    rag_pipeline = create_rag_pipeline(vectorstore)

    &lt;span class=&quot;hljs-keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;True&lt;/span&gt;:
        query = &lt;span class=&quot;hljs-built_in&quot;&gt;input&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;\nAsk a question (or type &#39;exit&#39; to quit): &quot;&lt;/span&gt;)
        &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; query.lower() == &lt;span class=&quot;hljs-string&quot;&gt;&quot;exit&quot;&lt;/span&gt;:
            &lt;span class=&quot;hljs-keyword&quot;&gt;break&lt;/span&gt;
        query_rag_pipeline(rag_pipeline, query)

&lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; __name__ == &lt;span class=&quot;hljs-string&quot;&gt;&quot;__main__&quot;&lt;/span&gt;:
    main()
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;hr /&gt;&lt;h2&gt;&lt;strong&gt;How This Improves Performance and Reduces Cost&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Instead of passing &lt;strong&gt;entire PDFs&lt;/strong&gt; to GPT-4, this chatbot:&lt;br /&gt;✅ &lt;strong&gt;Retrieves only relevant parts&lt;/strong&gt;, reducing token usage.&lt;br /&gt;✅ &lt;strong&gt;Persists embeddings&lt;/strong&gt;, avoiding redundant recomputation.&lt;br /&gt;✅ &lt;strong&gt;Uses vector search&lt;/strong&gt;, making queries faster and scalable.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;Example Cost Comparison&lt;/strong&gt;&lt;/h3&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Approach&lt;/th&gt;&lt;th&gt;Tokens Used&lt;/th&gt;&lt;th&gt;Cost per Query&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Full PDF in Prompt&lt;/td&gt;&lt;td&gt;~20,000&lt;/td&gt;&lt;td&gt;&lt;strong&gt;$0.10&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;RAG-based Retrieval&lt;/td&gt;&lt;td&gt;~350&lt;/td&gt;&lt;td&gt;&lt;strong&gt;$0.0021&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;🚀 &lt;strong&gt;95%+ cost savings&lt;/strong&gt; while maintaining accuracy!&lt;/p&gt;&lt;hr /&gt;&lt;h2&gt;&lt;strong&gt;Potential Applications&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;This RAG-based PDF chatbot can be used for:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;📜 &lt;strong&gt;Legal Document Analysis&lt;/strong&gt; – Quickly retrieve case laws, contracts, and compliance details.&lt;/li&gt;&lt;li&gt;📚 &lt;strong&gt;Educational Use&lt;/strong&gt; – Answer questions from textbooks or research papers.&lt;/li&gt;&lt;li&gt;🏦 &lt;strong&gt;Finance &amp;amp; Regulations&lt;/strong&gt; – Automate MiFID II, GDPR, and financial document inquiries.&lt;/li&gt;&lt;li&gt;🎓 &lt;strong&gt;Corporate Knowledge Management&lt;/strong&gt; – Search internal company policies instantly.&lt;/li&gt;&lt;/ol&gt;&lt;hr /&gt;&lt;h2&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;The &lt;strong&gt;RAG-PDF chatbot&lt;/strong&gt; is a practical solution for &lt;strong&gt;efficiently querying large documents&lt;/strong&gt; using GPT-4 while significantly &lt;strong&gt;reducing token costs&lt;/strong&gt;. By integrating vector search with OpenAI’s LLMs, we can build &lt;strong&gt;scalable and cost-effective AI applications&lt;/strong&gt; for real-world business use cases.&lt;/p&gt;&lt;p&gt;👉 &lt;strong&gt;Try the full implementation on GitHub:&lt;/strong&gt; &lt;a href=&quot;https://github.com/JordiCorbilla/RAG-PDF-Chatbot&quot; rel=&quot;noopener&quot; target=&quot;_new&quot;&gt;RAG-PDF-Chatbot&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Let me know in the comments—what would you use this for? 🚀&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;</description><link>https://thundaxsoftware.blogspot.com/2025/02/building-ai-powered-pdf-chatbot-using.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5Cw3rrG8FIlVxOD5zoV9JElKDXg4ehX0azkwjKSIPpzpJpIPzYpddVDCzVxhf15pxZZMEa98K4qZq3UlOIzms903Klyf17JYb4j2_ol6uKC9fN-WejFEJvXonDYLP4EZjsABj67O0sjPYJds3lW1a1uHGhQfh2n87-sgt-DxgFIwb6C1pDWf4nKt7d2I/s72-c/DALL%C2%B7E%202025-02-01%2021.02.43%20-%20A%20conceptual%20illustration%20of%20a%20Retrieval-Augmented%20Generation%20(RAG)%20pipeline%20for%20querying%20a%20PDF%20document.%20The%20image%20should%20depict%20a%20structured%20workflo.webp" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-3044550104861117279</guid><pubDate>Wed, 08 Jan 2025 21:10:00 +0000</pubDate><atom:updated>2025-01-09T19:48:58.320+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Investment</category><category domain="http://www.blogger.com/atom/ns#">Machine Learning</category><category domain="http://www.blogger.com/atom/ns#">mean-variance</category><category domain="http://www.blogger.com/atom/ns#">portfolio-optimization</category><category domain="http://www.blogger.com/atom/ns#">Python</category><title>Optimizing Investment Portfolios with Machine Learning and Mean-Variance Analysis</title><description>&lt;h2 style=&quot;text-align: left;&quot;&gt;&lt;strong&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiASi4he1EjPZqkHqUG-BN2eWsV3PYjb-Ow7Y4A5oRCuJW6wK_TKes-13Bg4RsIJhIH9f4eWHR3VTNgFLzhH-hVa5y0QwNSWWXiQCs0pCK17glTFb1JMTTEtCzQ0LEugh5E04j0wDhSnXCGanWywR5Penp9hPghunXkcX9HMdRLf5o-djsZPiuOCe3myK8/s1024/DALL%C2%B7E%202025-01-09%2019.34.18%20-%20An%20illustration%20that%20combines%20elements%20of%20machine%20learning%20and%20investment%20portfolio%20optimization.%20It%20features%20a%20financial%20chart%20with%20the%20mean-variance.webp&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;268&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiASi4he1EjPZqkHqUG-BN2eWsV3PYjb-Ow7Y4A5oRCuJW6wK_TKes-13Bg4RsIJhIH9f4eWHR3VTNgFLzhH-hVa5y0QwNSWWXiQCs0pCK17glTFb1JMTTEtCzQ0LEugh5E04j0wDhSnXCGanWywR5Penp9hPghunXkcX9HMdRLf5o-djsZPiuOCe3myK8/w268-h268/DALL%C2%B7E%202025-01-09%2019.34.18%20-%20An%20illustration%20that%20combines%20elements%20of%20machine%20learning%20and%20investment%20portfolio%20optimization.%20It%20features%20a%20financial%20chart%20with%20the%20mean-variance.webp&quot; width=&quot;268&quot; /&gt;&lt;/a&gt;&lt;/div&gt;Optimizing Investment Portfolios with Machine Learning and Mean-Variance Analysis&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Investing has always been about balancing risk and reward. Whether you&#39;re managing a personal portfolio or overseeing millions in institutional investments, finding the right allocation of assets to maximize returns while minimizing risk is crucial. In this post, we’ll dive into how &lt;strong&gt;Machine Learning (ML)&lt;/strong&gt; and &lt;strong&gt;Mean-Variance Optimization (MVO)&lt;/strong&gt; can revolutionize portfolio management, using &lt;strong&gt;Dividend Kings&lt;/strong&gt; as our investment universe.&lt;/p&gt;&lt;hr /&gt;&lt;h3&gt;&lt;strong&gt;Why Dividend Kings?&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;By default, the repository focuses on &lt;strong&gt;Dividend Kings&lt;/strong&gt;—stocks that have increased dividends for at least 50 consecutive years. These companies are renowned for their financial stability, consistent returns, and shareholder-friendly policies. Dividend Kings offer a realistic and relatively conservative set of stocks for applying portfolio optimization techniques.&lt;/p&gt;&lt;p&gt;This choice ensures that the portfolio is built on reliable, historically strong performers while leaving room to explore the benefits of optimization. Of course, the code is flexible—you can specify any set of tickers if you prefer growth stocks, ETFs, or other instruments.&lt;/p&gt;&lt;p&gt;For more on Dividend Kings, check out the full list here: &lt;a href=&quot;https://www.dividendgrowthinvestor.com/p/dividend-kings.html?m=1&quot; rel=&quot;noopener&quot; target=&quot;_new&quot;&gt;Dividend Kings List&lt;/a&gt;.&lt;/p&gt;&lt;hr /&gt;&lt;h3&gt;&lt;strong&gt;Introduction to Portfolio Optimization&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;At the heart of portfolio management lies the concept of the &lt;strong&gt;Efficient Frontier&lt;/strong&gt;. This theoretical boundary identifies the optimal trade-off between portfolio risk (volatility) and return. Harry Markowitz&#39;s &lt;strong&gt;Mean-Variance Optimization (MVO)&lt;/strong&gt; introduced a systematic approach to achieving this balance, and it remains one of the most influential financial models.&lt;/p&gt;&lt;p&gt;However, traditional methods rely heavily on historical data and fixed assumptions. Markets, on the other hand, are dynamic and complex. This is where Machine Learning enters the equation—by forecasting future returns and adjusting to changing conditions, ML models can augment MVO for better decision-making.&lt;/p&gt;&lt;hr /&gt;&lt;h3&gt;&lt;strong&gt;Blending Machine Learning with MVO&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Our project combines the power of &lt;strong&gt;predictive modeling&lt;/strong&gt; with the rigor of &lt;strong&gt;statistical optimization&lt;/strong&gt;. By using ML models like &lt;strong&gt;Linear Regression&lt;/strong&gt;, we estimate asset returns based on historical data and trends. These predicted returns are then fed into the MVO algorithm to calculate the &lt;strong&gt;optimal allocation&lt;/strong&gt; of assets.&lt;/p&gt;&lt;h4&gt;Key Features:&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Black-Litterman Framework&lt;/strong&gt;: Adjusts market returns based on investor views and confidence levels.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Efficient Frontier Analysis&lt;/strong&gt;: Visualizes the best possible portfolios for various risk levels.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Performance Metrics&lt;/strong&gt;: Includes &lt;strong&gt;Sharpe Ratio&lt;/strong&gt;, &lt;strong&gt;Sortino Ratio&lt;/strong&gt;, and &lt;strong&gt;Information Ratio&lt;/strong&gt; to measure portfolio efficiency.&lt;/li&gt;&lt;/ul&gt;&lt;hr /&gt;&lt;h3&gt;&lt;strong&gt;Portfolio Details&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;For this project, we used the following Dividend Kings as our investment universe:&lt;/p&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Symbol&lt;/th&gt;&lt;th&gt;Company Name&lt;/th&gt;&lt;th&gt;Weight&lt;/th&gt;&lt;th&gt;Sector&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;MO&lt;/td&gt;&lt;td&gt;Altria Group Inc.&lt;/td&gt;&lt;td&gt;4%&lt;/td&gt;&lt;td&gt;Consumer Staples&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;NWN&lt;/td&gt;&lt;td&gt;Northwest Natural Gas&lt;/td&gt;&lt;td&gt;14%&lt;/td&gt;&lt;td&gt;Utilities&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;BKH&lt;/td&gt;&lt;td&gt;Black Hills Corp.&lt;/td&gt;&lt;td&gt;1%&lt;/td&gt;&lt;td&gt;Utilities&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ED&lt;/td&gt;&lt;td&gt;Con Edison&lt;/td&gt;&lt;td&gt;1%&lt;/td&gt;&lt;td&gt;Utilities&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;PEP&lt;/td&gt;&lt;td&gt;PepsiCo Inc.&lt;/td&gt;&lt;td&gt;9%&lt;/td&gt;&lt;td&gt;Consumer Staples&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;NFG&lt;/td&gt;&lt;td&gt;National Fuel Gas&lt;/td&gt;&lt;td&gt;16%&lt;/td&gt;&lt;td&gt;Utilities&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;KO&lt;/td&gt;&lt;td&gt;Coca-Cola Company&lt;/td&gt;&lt;td&gt;6%&lt;/td&gt;&lt;td&gt;Consumer Staples&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;FRT&lt;/td&gt;&lt;td&gt;Federal Realty Inv. Trust&lt;/td&gt;&lt;td&gt;28%&lt;/td&gt;&lt;td&gt;Real Estate&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;GPC&lt;/td&gt;&lt;td&gt;Genuine Parts Co.&lt;/td&gt;&lt;td&gt;16%&lt;/td&gt;&lt;td&gt;Consumer Discretionary&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MSEX&lt;/td&gt;&lt;td&gt;Middlesex Water Co.&lt;/td&gt;&lt;td&gt;5%&lt;/td&gt;&lt;td&gt;Utilities&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;The total portfolio value is $100,000, with weights translating into dollar amounts. For instance, a 4% weight in &lt;strong&gt;MO&lt;/strong&gt; means $4,000 invested in Altria Group.&lt;/p&gt;&lt;hr /&gt;&lt;h3&gt;&lt;strong&gt;How It Works&lt;/strong&gt;&lt;/h3&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Input Portfolio&lt;/strong&gt;: Define the initial portfolio with weights and market caps.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Predict Returns&lt;/strong&gt;: Use Machine Learning models to predict future returns for each asset.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Optimize Allocation&lt;/strong&gt;: Use MVO to find the optimal weights for the portfolio based on predicted returns, minimizing risk while maximizing returns.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Backtest&lt;/strong&gt;: Simulate the optimized portfolio’s performance against historical data.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Visualize Results&lt;/strong&gt;: Generate the Efficient Frontier, plot portfolio performance, and compare with the benchmark (S&amp;amp;P 500).&lt;/li&gt;&lt;/ol&gt;&lt;hr /&gt;&lt;h3&gt;&lt;strong&gt;Results&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Our project provides several outputs to help investors make informed decisions:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Efficient Frontier&lt;/strong&gt;: A curve showing the best portfolios for different risk levels.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Capital Market Line (CML)&lt;/strong&gt;: A line representing portfolios that combine the risk-free asset with the market portfolio.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Performance Metrics&lt;/strong&gt;:&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Sharpe Ratio&lt;/strong&gt;: How much return you earn for every unit of risk.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Sortino Ratio&lt;/strong&gt;: Similar to Sharpe but focuses only on downside risk.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Information Ratio&lt;/strong&gt;: Compares portfolio performance against a benchmark.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Below is a sample visualization generated by our project:&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5m_mxGQ4fwi_ZZgI7MLT96hvqU-0AfVbTEMJbY5N6OOXVmJKvRjocjsZfKxAjYBLuRtK3iNdcmbfLmlyViWvDyU_2L8thaC7lTSfKD5-_AsbQokOmgb9Pa_7PyFfiNwLaxO0ijUCcTv11ZqjxUqbvHx-vOQMwVC-0MGQTqFXWrYmVGX0hK_Rc67o31cI/s7830/machine_learning_optimization_20250107_214207.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;3193&quot; data-original-width=&quot;7830&quot; height=&quot;260&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5m_mxGQ4fwi_ZZgI7MLT96hvqU-0AfVbTEMJbY5N6OOXVmJKvRjocjsZfKxAjYBLuRtK3iNdcmbfLmlyViWvDyU_2L8thaC7lTSfKD5-_AsbQokOmgb9Pa_7PyFfiNwLaxO0ijUCcTv11ZqjxUqbvHx-vOQMwVC-0MGQTqFXWrYmVGX0hK_Rc67o31cI/w640-h260/machine_learning_optimization_20250107_214207.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;hr /&gt;&lt;h3&gt;&lt;strong&gt;Why This Matters&lt;/strong&gt;&lt;/h3&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Customizable&lt;/strong&gt;: Investors can adjust parameters like risk tolerance, minimum/maximum asset weights, and the choice of ML models.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Adaptability&lt;/strong&gt;: The use of ML ensures that the portfolio adapts to changing market conditions.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Transparency&lt;/strong&gt;: By visualizing the Efficient Frontier and other metrics, investors gain clarity on how decisions impact performance.&lt;/li&gt;&lt;/ol&gt;&lt;hr /&gt;&lt;h3&gt;&lt;strong&gt;How to Get Started&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Want to try it yourself? Here’s how:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Clone the GitHub repository:&lt;pre class=&quot;!overflow-visible&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary dark:bg-gray-950&quot;&gt;&lt;div class=&quot;flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between rounded-t-md h-9 bg-token-sidebar-surface-primary dark:bg-token-main-surface-secondary select-none&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;!whitespace-pre hljs language-bash&quot;&gt;git &lt;span class=&quot;hljs-built_in&quot;&gt;clone&lt;/span&gt; https://github.com/JordiCorbilla/machine-learning-optimization-portfolio.git
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Install dependencies:&lt;pre class=&quot;!overflow-visible&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary dark:bg-gray-950&quot;&gt;&lt;div class=&quot;flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between rounded-t-md h-9 bg-token-sidebar-surface-primary dark:bg-token-main-surface-secondary select-none&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;!whitespace-pre hljs language-bash&quot;&gt;pip install -r requirements.txt
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Define your portfolio in the &lt;code&gt;fetch_portfolio_data()&lt;/code&gt; method.&lt;/li&gt;&lt;li&gt;Run the &lt;code&gt;main()&lt;/code&gt; function:&lt;pre class=&quot;!overflow-visible&quot;&gt;&lt;div class=&quot;contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary dark:bg-gray-950&quot;&gt;&lt;div class=&quot;flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between rounded-t-md h-9 bg-token-sidebar-surface-primary dark:bg-token-main-surface-secondary select-none&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;overflow-y-auto p-4&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;!whitespace-pre hljs language-bash&quot;&gt;python portfolio_optimization.py
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;The outputs will include:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Optimized portfolio weights.&lt;/li&gt;&lt;li&gt;Performance metrics and tables.&lt;/li&gt;&lt;li&gt;Visualization of the Efficient Frontier and portfolio performance.&lt;/li&gt;&lt;/ul&gt;&lt;hr /&gt;&lt;h3&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Combining &lt;strong&gt;Machine Learning&lt;/strong&gt; with &lt;strong&gt;Mean-Variance Optimization&lt;/strong&gt; bridges the gap between modern predictive analytics and traditional portfolio theory. By applying this to Dividend Kings, we focus on reliable, stable companies while exploring innovative techniques to maximize returns and minimize risks.&lt;/p&gt;&lt;hr /&gt;&lt;h3&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/h3&gt;&lt;ol&gt;&lt;li&gt;Markowitz, H. (1952). Portfolio Selection. The Journal of Finance.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.dividendgrowthinvestor.com/p/dividend-kings.html?m=1&quot; rel=&quot;noopener&quot; target=&quot;_new&quot;&gt;Dividend Kings List&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;hr /&gt;&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: This project is for educational purposes only and does not constitute financial advice. Always consult a financial advisor before making investment decisions.&lt;/p&gt;</description><link>https://thundaxsoftware.blogspot.com/2025/01/optimizing-investment-portfolios-with.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiASi4he1EjPZqkHqUG-BN2eWsV3PYjb-Ow7Y4A5oRCuJW6wK_TKes-13Bg4RsIJhIH9f4eWHR3VTNgFLzhH-hVa5y0QwNSWWXiQCs0pCK17glTFb1JMTTEtCzQ0LEugh5E04j0wDhSnXCGanWywR5Penp9hPghunXkcX9HMdRLf5o-djsZPiuOCe3myK8/s72-w268-h268-c/DALL%C2%B7E%202025-01-09%2019.34.18%20-%20An%20illustration%20that%20combines%20elements%20of%20machine%20learning%20and%20investment%20portfolio%20optimization.%20It%20features%20a%20financial%20chart%20with%20the%20mean-variance.webp" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-1823044896840537279</guid><pubDate>Fri, 27 Dec 2024 19:02:00 +0000</pubDate><atom:updated>2025-02-02T19:41:36.380+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">efficient-frontier</category><category domain="http://www.blogger.com/atom/ns#">monte-carlo</category><category domain="http://www.blogger.com/atom/ns#">portfolio-optimization</category><title>Optimizing a Portfolio with Monte Carlo Simulations and the Efficient Frontier</title><description>&lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;strong&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidlvSKogxpIQRFu_Rasq7Ec-jAdYuWo_xNsDQGC18PCYZI5xjGUh_Mk2g1y_LSK5xl0rxUkDJePUtbsqXevbtI0vOiqyb6aGzYLLYQgMfuStfRXvWLyI8njywycnva3G84w7wXtZWIqU-e0VbvIClPu_y2Gji-YOpuHJ4TR_xDNRee4dJQKNXYvqFU_MA/s1024/DALL%C2%B7E%202025-01-09%2019.32.37%20-%20A%20visually%20appealing%20illustration%20representing%20financial%20portfolio%20optimization,%20featuring%20an%20abstract%20financial%20graph%20with%20the%20efficient%20frontier%20cur.webp&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;258&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidlvSKogxpIQRFu_Rasq7Ec-jAdYuWo_xNsDQGC18PCYZI5xjGUh_Mk2g1y_LSK5xl0rxUkDJePUtbsqXevbtI0vOiqyb6aGzYLLYQgMfuStfRXvWLyI8njywycnva3G84w7wXtZWIqU-e0VbvIClPu_y2Gji-YOpuHJ4TR_xDNRee4dJQKNXYvqFU_MA/w258-h258/DALL%C2%B7E%202025-01-09%2019.32.37%20-%20A%20visually%20appealing%20illustration%20representing%20financial%20portfolio%20optimization,%20featuring%20an%20abstract%20financial%20graph%20with%20the%20efficient%20frontier%20cur.webp&quot; width=&quot;258&quot; /&gt;&lt;/a&gt;&lt;/strong&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Welcome to my latest project—an exploration of &lt;strong&gt;portfolio optimization&lt;/strong&gt; using both &lt;strong&gt;Monte Carlo simulations&lt;/strong&gt; and &lt;strong&gt;Modern Portfolio Theory&lt;/strong&gt;. I’ve just open-sourced the code on &lt;a href=&quot;https://github.com/JordiCorbilla/efficient-frontier-monte-carlo-portfolio-optimization&quot; rel=&quot;noopener&quot; target=&quot;_new&quot;&gt;GitHub&lt;/a&gt;. In this blog post, I’ll walk through what the project does, why it’s interesting, and how you can try it yourself.&lt;/p&gt;&lt;p&gt;Project is hosted here:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://efficient-frontier-monte-carlo-portfolio-optimization.streamlit.app/&quot;&gt;Portfolio Optimisation · Streamlit&lt;/a&gt;&lt;/p&gt;&lt;hr /&gt;&lt;h2&gt;&lt;br /&gt;&lt;/h2&gt;&lt;h2&gt;&lt;strong&gt;What is Portfolio Optimization?&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Portfolio optimization is all about finding the ideal blend of assets (stocks, funds, or other instruments) that &lt;strong&gt;maximizes returns&lt;/strong&gt; while &lt;strong&gt;minimizing risk&lt;/strong&gt;. One classic method to visualize this is the &lt;strong&gt;Efficient Frontier&lt;/strong&gt;, a concept introduced by Nobel laureate Harry Markowitz. The Efficient Frontier is the curve of “optimal” portfolios offering the highest return per unit of risk.&lt;/p&gt;&lt;h4&gt;&lt;strong&gt;Risk&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;We often measure risk by &lt;strong&gt;volatility&lt;/strong&gt; (standard deviation) of returns.&lt;/p&gt;&lt;h4&gt;&lt;strong&gt;Return&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;Typically measured by &lt;strong&gt;annualized mean&lt;/strong&gt; of returns.&lt;/p&gt;&lt;h4&gt;&lt;strong&gt;Sharpe Ratio&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;A measure of risk-adjusted return, calculated as the (portfolio return – risk-free rate) / (portfolio volatility).&lt;/p&gt;&lt;hr /&gt;&lt;h2&gt;&lt;strong&gt;Project Overview&lt;/strong&gt;&lt;/h2&gt;&lt;h3&gt;&lt;strong&gt;Monte Carlo Simulation Approach&lt;/strong&gt;&lt;/h3&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Random Weights&lt;/strong&gt;&lt;br /&gt;We generate thousands (often 100K+) of &lt;strong&gt;random weight allocations&lt;/strong&gt; across our chosen assets.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Performance Metrics&lt;/strong&gt;&lt;br /&gt;For each allocation, we calculate the &lt;strong&gt;annualized return&lt;/strong&gt;, &lt;strong&gt;annualized volatility&lt;/strong&gt;, and &lt;strong&gt;Sharpe ratio&lt;/strong&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Best Found Portfolio&lt;/strong&gt;&lt;br /&gt;We highlight the random portfolio that yields the &lt;strong&gt;highest Sharpe ratio&lt;/strong&gt; among all simulations (i.e., the “green star” in the resulting plot).&lt;/li&gt;&lt;/ol&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEicLpuke4S5uCcqcOP7DBJGMmuta17Cxrv9t4b7zMTZIg-x_wgUiedFJW6T978g5sBtshb6NFiC_xr51eHtmJaDp8SgtLjpb_JzgKRvVYj7QMNRxJs4CulvXtCYL60DggUbAM9_xDbwMtLGdQWbgyqXroQJIDCJybnbjxZDt-r4rytZUt1mjQw4cnJXd6s&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;2981&quot; data-original-width=&quot;6609&quot; height=&quot;288&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEicLpuke4S5uCcqcOP7DBJGMmuta17Cxrv9t4b7zMTZIg-x_wgUiedFJW6T978g5sBtshb6NFiC_xr51eHtmJaDp8SgtLjpb_JzgKRvVYj7QMNRxJs4CulvXtCYL60DggUbAM9_xDbwMtLGdQWbgyqXroQJIDCJybnbjxZDt-r4rytZUt1mjQw4cnJXd6s=w640-h288&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;strong&gt;Analytical Efficient Frontier&lt;/strong&gt;&lt;/div&gt;&lt;p&gt;In contrast to random allocations, we also compute the &lt;strong&gt;Efficient Frontier&lt;/strong&gt; by &lt;strong&gt;systematic optimization&lt;/strong&gt;. We use optimization routines (like &lt;code&gt;scipy.optimize&lt;/code&gt;) to find the portfolio with the &lt;strong&gt;minimum volatility&lt;/strong&gt; for each target return. This yields a &lt;strong&gt;smooth curve&lt;/strong&gt; which is the theoretical solution to Markowitz’s Modern Portfolio Theory.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;Visualizing the Results&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;The script generates a &lt;strong&gt;scatter plot&lt;/strong&gt; of all random portfolios, color-coded by Sharpe ratio. It then overlays the &lt;strong&gt;Efficient Frontier&lt;/strong&gt; curve and highlights notable points:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Market Benchmark (S&amp;amp;P 500)&lt;/strong&gt; performance (red dot)&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Optimal Portfolio&lt;/strong&gt; from Monte Carlo (green star)&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Potentially&lt;/strong&gt; other reference portfolios like the &lt;strong&gt;Global Minimum Volatility&lt;/strong&gt; or an &lt;strong&gt;Equal-Weighted&lt;/strong&gt; portfolio.&lt;/li&gt;&lt;/ul&gt;&lt;hr /&gt;&lt;h2&gt;&lt;strong&gt;Why Dividend Kings?&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;By default, the repository focuses on the &lt;strong&gt;Dividend Kings&lt;/strong&gt;—stocks that have &lt;strong&gt;increased dividends for at least 50 consecutive years&lt;/strong&gt;. I wanted to apply the technique to a &lt;strong&gt;realistic, relatively conservative&lt;/strong&gt; set of stocks. Of course, you can specify any set of tickers if you’d prefer growth stocks, ETFs, or other instruments.&lt;/p&gt;&lt;hr /&gt;&lt;h2&gt;&lt;strong&gt;Getting Started&lt;/strong&gt;&lt;/h2&gt;&lt;strong&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;strong&gt;Clone or Download&lt;/strong&gt; the repository:&amp;nbsp;&lt;/li&gt;&lt;/ol&gt;&lt;/strong&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;katex-html&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;mord text&quot;&gt;&lt;span class=&quot;mord texttt&quot;&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; git&amp;nbsp;clone&amp;nbsp;https://github.com/JordiCorbilla/monte-carlo-portfolio-optimization.git&lt;/div&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;strong&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;strong&gt;Install Dependencies&lt;/strong&gt;:&lt;/li&gt;&lt;/ol&gt;&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;pip install -r requirements.txt&lt;br /&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;strong&gt;Run the Script&lt;/strong&gt;:
&lt;span class=&quot;katex-error&quot; title=&quot;ParseError: KaTeX parse error: Expected &#39;EOF&#39;, got &#39;_&#39; at position 23: …tt{python monte_̲carlo_portfolio…&quot;&gt;python monte_carlo_portfolio.py --assets AAPL MSFT TSLA --start 2020-01-01 --end 2025-01-01 or &lt;b&gt;use the app here&lt;/b&gt;:&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://efficient-frontier-monte-carlo-portfolio-optimization.streamlit.app/&quot;&gt;Portfolio Optimisation · Streamlit&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;ul&gt;&lt;li&gt;By default, it saves a &lt;strong&gt;PNG&lt;/strong&gt; of the Efficient Frontier (&lt;code&gt;efficient_frontier_monter_carlo.png&lt;/code&gt;) and a &lt;strong&gt;CSV&lt;/strong&gt; of the historical data (&lt;code&gt;market_data.csv&lt;/code&gt;).&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Pro Tip&lt;/strong&gt;: Adjust parameters like &lt;strong&gt;&lt;code&gt;risk_free_rate&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;portfolios&lt;/code&gt;&lt;/strong&gt;, or the date range in the command line to see how different assumptions impact the results.&lt;/p&gt;&lt;hr /&gt;&lt;h2&gt;&lt;strong&gt;Key Highlights&lt;/strong&gt;&lt;/h2&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Modular Code&lt;/strong&gt;&lt;ul&gt;&lt;li&gt;The repository separates data fetching, Monte Carlo simulations, and plotting into distinct functions for clarity.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Argument Parsing&lt;/strong&gt;&lt;ul&gt;&lt;li&gt;You can easily change inputs via &lt;code&gt;--assets&lt;/code&gt;, &lt;code&gt;--start&lt;/code&gt;, &lt;code&gt;--end&lt;/code&gt;, and other flags.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Analytical Frontier + Monte Carlo&lt;/strong&gt;&lt;ul&gt;&lt;li&gt;See how a purely random approach might miss the theoretical optimum (the frontier line) unless it samples very densely.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Saving Outputs&lt;/strong&gt;&lt;ul&gt;&lt;li&gt;The script automatically saves the final chart. Perfect for including in reports or presentations.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ol&gt;&lt;hr /&gt;&lt;h2&gt;&lt;strong&gt;Results: Example Chart&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Below is a &lt;strong&gt;sample&lt;/strong&gt; chart from the script, showing:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;A cloud of random portfolio allocations color-coded by Sharpe.&lt;/li&gt;&lt;li&gt;A &lt;strong&gt;black line&lt;/strong&gt; representing the Efficient Frontier.&lt;/li&gt;&lt;li&gt;Markers for the &lt;strong&gt;market&lt;/strong&gt; (in red) and the &lt;strong&gt;optimal&lt;/strong&gt; Monte Carlo portfolio (green star).&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;In a real market scenario, you might see how dividend-paying stocks cluster toward lower volatility with moderate returns, while certain growth stocks occupy a higher-risk, higher-return domain.&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgrKfmZCocL4l28BlvkYLN2MkeLISdbaKrKDRwhFoAvBSG-3ufyz2VLKQVOuqVlPMZmy_QMHqSqdfYaJwiFciCH3U4TwPp0zv2438RVedn-l7KUuvIQoIegsggYEPbMSEBqHMS0J3an5FR5zZ0fDIKw2qt6yrgzffzTdPxJxF6G6oazJByRB_UcYIP7vnc&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;2981&quot; data-original-width=&quot;6603&quot; height=&quot;288&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgrKfmZCocL4l28BlvkYLN2MkeLISdbaKrKDRwhFoAvBSG-3ufyz2VLKQVOuqVlPMZmy_QMHqSqdfYaJwiFciCH3U4TwPp0zv2438RVedn-l7KUuvIQoIegsggYEPbMSEBqHMS0J3an5FR5zZ0fDIKw2qt6yrgzffzTdPxJxF6G6oazJByRB_UcYIP7vnc=w640-h288&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;&lt;strong&gt;Results: Example via Streamlit APP&lt;/strong&gt;&lt;/h2&gt;&lt;p style=&quot;text-align: left;&quot;&gt;Below is a&amp;nbsp;&lt;strong&gt;sample&lt;/strong&gt;&amp;nbsp;chart from the app, showing:&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjIJReyaSkym7_XN4XPGkPCXBAYMn5PtQa9E6iEE_jOKcRtE0RoXAZpNNOhriMlQm5edDSnAL1wZiJ6liV8hzPuo5VJH12swrDN9cJYgW92wtTTICNu7DeVlSggA9sxowOEOcRVS9X3WIHL-Kp_4Wqp2oNdILodRySnh_EEg907qI5WQKJlARz6qTcqxnU&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;1079&quot; data-original-width=&quot;996&quot; height=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjIJReyaSkym7_XN4XPGkPCXBAYMn5PtQa9E6iEE_jOKcRtE0RoXAZpNNOhriMlQm5edDSnAL1wZiJ6liV8hzPuo5VJH12swrDN9cJYgW92wtTTICNu7DeVlSggA9sxowOEOcRVS9X3WIHL-Kp_4Wqp2oNdILodRySnh_EEg907qI5WQKJlARz6qTcqxnU=w592-h640&quot; width=&quot;592&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;hr /&gt;&lt;h2&gt;&lt;strong&gt;Potential Next Steps&lt;/strong&gt;&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Incorporate Additional Factors&lt;/strong&gt;&lt;br /&gt;You could add constraints like “no short selling” or “limit to 20% in any single asset” to your optimization.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Use Different Benchmarks&lt;/strong&gt;&lt;br /&gt;Compare your portfolio to an international index (like IXUS), or to bonds (like AGG).&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Evaluate Performance Over Time&lt;/strong&gt;&lt;br /&gt;Extend the script to backtest the chosen “optimal” allocation through time, rebalancing periodically.&lt;/li&gt;&lt;/ul&gt;&lt;hr /&gt;&lt;h2&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;With this project, I’ve combined two powerful approaches—&lt;strong&gt;Monte Carlo&lt;/strong&gt; and &lt;strong&gt;analytical optimization&lt;/strong&gt;—to &lt;strong&gt;visualize and compare&lt;/strong&gt; different portfolio allocations. By default, it focuses on &lt;strong&gt;Dividend Kings&lt;/strong&gt;, but you can easily switch to any assets you like. Whether you’re an aspiring quant investor or just curious about portfolio construction, I hope this provides a helpful &lt;strong&gt;practical example&lt;/strong&gt; of Modern Portfolio Theory in action.&lt;/p&gt;&lt;p&gt;For more details, check out the &lt;strong&gt;&lt;a href=&quot;https://github.com/JordiCorbilla/efficient-frontier-monte-carlo-portfolio-optimization&quot; rel=&quot;noopener&quot; target=&quot;_new&quot;&gt;GitHub repository&lt;/a&gt;&lt;/strong&gt;. Feel free to &lt;strong&gt;fork&lt;/strong&gt; it, play with the parameters, and share your results. Happy investing!&lt;/p&gt;&lt;hr /&gt;&lt;h3&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Markowitz, H. (1952). &lt;em&gt;Portfolio Selection&lt;/em&gt;. The Journal of Finance.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.dividendgrowthinvestor.com/p/dividend-kings.html?m=1&quot; rel=&quot;noopener&quot; target=&quot;_new&quot;&gt;Dividend Kings List&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;hr /&gt;&lt;p&gt;&lt;strong&gt;Thank you for reading!&lt;/strong&gt; If you have any questions or suggestions, feel free to &lt;a href=&quot;https://github.com/JordiCorbilla/monte-carlo-portfolio-optimization/issues&quot; rel=&quot;noopener&quot; target=&quot;_new&quot;&gt;open an issue&lt;/a&gt; or comment below.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: This project is for educational purposes only and does not constitute financial advice. Always consult a financial advisor before making investment decisions.&lt;/p&gt;</description><link>https://thundaxsoftware.blogspot.com/2024/12/optimizing-portfolio-with-monte-carlo.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidlvSKogxpIQRFu_Rasq7Ec-jAdYuWo_xNsDQGC18PCYZI5xjGUh_Mk2g1y_LSK5xl0rxUkDJePUtbsqXevbtI0vOiqyb6aGzYLLYQgMfuStfRXvWLyI8njywycnva3G84w7wXtZWIqU-e0VbvIClPu_y2Gji-YOpuHJ4TR_xDNRee4dJQKNXYvqFU_MA/s72-w258-h258-c/DALL%C2%B7E%202025-01-09%2019.32.37%20-%20A%20visually%20appealing%20illustration%20representing%20financial%20portfolio%20optimization,%20featuring%20an%20abstract%20financial%20graph%20with%20the%20efficient%20frontier%20cur.webp" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>London, UK</georss:featurename><georss:point>51.5072178 -0.1275862</georss:point><georss:box>23.196983963821154 -35.2838362 79.817451636178845 35.0286638</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7022755517551007355.post-1509893843762155259</guid><pubDate>Sun, 29 Sep 2024 16:18:00 +0000</pubDate><atom:updated>2024-10-17T18:41:27.521+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">bot</category><category domain="http://www.blogger.com/atom/ns#">ocr</category><category domain="http://www.blogger.com/atom/ns#">openAI</category><category domain="http://www.blogger.com/atom/ns#">Python</category><category domain="http://www.blogger.com/atom/ns#">telegram</category><category domain="http://www.blogger.com/atom/ns#">tesseract</category><category domain="http://www.blogger.com/atom/ns#">tutor</category><title>AI Tutor Telegram Bot Project</title><description>&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgayUyOQZOW_Rjx6pyrMiXtMn6hYvp78m56L9LcXlnEiKhDqJBrynignYqD96grATvtUgJDIswuSM9eIXa964EMu4TmXUYAxgI21ZI4ATjRHQOiS71f2c6cyKG4wrTKx3971afBTo6iuQdOUw4xh1I4stGS7S2vyA68Juv7E91phDnOPJFf_yF2aJjfqLU&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgayUyOQZOW_Rjx6pyrMiXtMn6hYvp78m56L9LcXlnEiKhDqJBrynignYqD96grATvtUgJDIswuSM9eIXa964EMu4TmXUYAxgI21ZI4ATjRHQOiS71f2c6cyKG4wrTKx3971afBTo6iuQdOUw4xh1I4stGS7S2vyA68Juv7E91phDnOPJFf_yF2aJjfqLU&quot; width=&quot;240&quot; /&gt;&lt;/a&gt;&lt;/div&gt;It wasn&#39;t too long ago when I started experimenting with Bots using &lt;a href=&quot;https://github.com/JordiCorbilla/OpenAI-Whatsapp-Bot&quot;&gt;WhatsApp&lt;/a&gt;&amp;nbsp;an I recently discovered that&#39;s way easier using &lt;b&gt;telegram&lt;/b&gt; instead. Most of the communication layer is done for you so you don&#39;t have to worry about using web hooks and external proxies.&amp;nbsp;&lt;p&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;This time I bring you a quick project on creating a simple AI Tutor using Telegram and OpenAI. The idea is to use telegram to relay the information to a backend siting on my home server that will serve the data and communicate with OpenAI api to consume the prompts and reply the appropriate response. The idea is to use something that might help me with my children&#39;s homework. We&#39;ll also store the conversation details on a DB and because we are using telegram, we can share this bot with several people.&amp;nbsp;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;div class=&quot;markdown-heading&quot; dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-top: 0px; position: relative;&quot;&gt;&lt;h1 class=&quot;heading-element&quot; dir=&quot;auto&quot; style=&quot;border-bottom: 1px solid var(--borderColor-muted, var(--color-border-muted)); box-sizing: border-box; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25; margin-bottom: var(--base-size-16); margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0.3em;&quot; tabindex=&quot;-1&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;AI Tutor Telegram Bot Project&lt;/span&gt;&lt;/h1&gt;&lt;a aria-label=&quot;Permalink: AI Tutor Telegram Bot Project&quot; class=&quot;anchor&quot; href=&quot;https://github.com/JordiCorbilla/AI-Tutor/blob/main/README.md#ai-tutor-telegram-bot-project&quot; id=&quot;user-content-ai-tutor-telegram-bot-project&quot; style=&quot;align-items: center; border-radius: var(--borderRadius-medium); box-sizing: border-box; display: flex; float: left; height: 28px; justify-content: center; left: -28px; line-height: 1; margin: auto; opacity: 0; padding-right: var(--base-size-4); position: absolute; text-underline-offset: 0.2rem; top: 25.2969px; transform: translateY(calc(-50% - 0.3rem)); width: 28px;&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; class=&quot;octicon octicon-link&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewbox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;/svg&gt;&lt;span style=&quot;color: black;&quot;&gt;&lt;path d=&quot;m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z&quot;&gt;&lt;/path&gt;&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;markdown-heading&quot; dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; position: relative;&quot;&gt;&lt;h2 class=&quot;heading-element&quot; dir=&quot;auto&quot; style=&quot;border-bottom: 1px solid var(--borderColor-muted, var(--color-border-muted)); box-sizing: border-box; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25; margin-bottom: var(--base-size-16); margin-top: var(--base-size-24); padding-bottom: 0.3em;&quot; tabindex=&quot;-1&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Overview&lt;/span&gt;&lt;/h2&gt;&lt;a aria-label=&quot;Permalink: Overview&quot; class=&quot;anchor&quot; href=&quot;https://github.com/JordiCorbilla/AI-Tutor/blob/main/README.md#overview&quot; id=&quot;user-content-overview&quot; style=&quot;align-items: center; border-radius: var(--borderRadius-medium); box-sizing: border-box; display: flex; float: left; height: 28px; justify-content: center; left: -28px; line-height: 1; margin: auto; opacity: 0; padding-right: var(--base-size-4); position: absolute; text-underline-offset: 0.2rem; top: 19.0938px; transform: translateY(calc(-50% - 0.3rem)); width: 28px;&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; class=&quot;octicon octicon-link&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewbox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;/svg&gt;&lt;span style=&quot;color: black;&quot;&gt;&lt;path d=&quot;m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z&quot;&gt;&lt;/path&gt;&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;p dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px; text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;This project is an AI-powered Telegram bot named Merlin, designed to assist users by providing helpful and succinct answers. The bot can handle text messages, voice messages, images, and can generate images based on user prompts. The bot now supports user authorization based on a list defined in the config.ini file. Only authorized users can interact with the bot.&lt;/span&gt;&lt;/p&gt;&lt;div class=&quot;markdown-heading&quot; dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; position: relative;&quot;&gt;&lt;h3 class=&quot;heading-element&quot; dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-size: 1.25em; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25; margin-bottom: var(--base-size-16); margin-top: var(--base-size-24);&quot; tabindex=&quot;-1&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Features&lt;/span&gt;&lt;/h3&gt;&lt;a aria-label=&quot;Permalink: Features&quot; class=&quot;anchor&quot; href=&quot;https://github.com/JordiCorbilla/AI-Tutor/blob/main/README.md#features&quot; id=&quot;user-content-features&quot; style=&quot;align-items: center; border-radius: var(--borderRadius-medium); box-sizing: border-box; display: flex; float: left; height: 28px; justify-content: center; left: -28px; line-height: 1; margin: auto; opacity: 0; padding-right: var(--base-size-4); position: absolute; text-underline-offset: 0.2rem; top: 12.5px; transform: translateY(-50%); width: 28px;&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; class=&quot;octicon octicon-link&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewbox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;/svg&gt;&lt;span style=&quot;color: black;&quot;&gt;&lt;path d=&quot;m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z&quot;&gt;&lt;/path&gt;&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;p dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;&lt;b&gt;1 User Authorization:&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Access Control: Only users listed in the&amp;nbsp;&lt;code style=&quot;border-radius: 6px; box-sizing: border-box; font-family: var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace); font-size: 13.6px; margin: 0px; padding: 0.2em 0.4em; white-space-collapse: break-spaces;&quot;&gt;&lt;b&gt;AUTHORIZED_USERS&lt;/b&gt;&lt;/code&gt;&amp;nbsp;configuration can use the bot.&lt;/span&gt;&lt;/li&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiXvdjjKQnVR7hAcG64YQ7RCy4TWZgrbAEUm1mLy2AO85_AYJuslNWTUQTRo36oZGkbBOr8yBXRZmO0pq_rhrWzd-I1RkG0K_tWUaBjLuK6Tkr76omH91VX_WSztHbfH2x2FhxLwXLQBsCePnfgdNn5wgrAz9WlBZVXcOOAxjf52Enm6lJMMS7tPeZiAhM&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;91&quot; data-original-width=&quot;418&quot; height=&quot;70&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiXvdjjKQnVR7hAcG64YQ7RCy4TWZgrbAEUm1mLy2AO85_AYJuslNWTUQTRo36oZGkbBOr8yBXRZmO0pq_rhrWzd-I1RkG0K_tWUaBjLuK6Tkr76omH91VX_WSztHbfH2x2FhxLwXLQBsCePnfgdNn5wgrAz9WlBZVXcOOAxjf52Enm6lJMMS7tPeZiAhM&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Unauthorized Access: If an unauthorized user tries to interact with the bot, they receive a message saying &quot;You are not authorized.&quot;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;&lt;b&gt;2 Text Interaction:&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Users can send text messages, and Merlin will respond with helpful answers generated using OpenAI&#39;s GPT-4 model.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style=&quot;background-color: white; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px;&quot;&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEh1hsCzrEdMAANunZ6le8d87PCcONCV_lb76u42Cdga2Yc5lZNA5qM2ZkHYAaCeE4q9Tkl6d89XGPm2z2SHMyZ-PpvuS2qF9GA0_Kw7v6q6gHSHMAPW_D0h9h2SzTZVYzpUegTmNT8aK4u-EWyfAxBW5Ep1xvwqytx1WVgStgh5EEJi6_WzNKyCWducSNw&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;690&quot; data-original-width=&quot;419&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEh1hsCzrEdMAANunZ6le8d87PCcONCV_lb76u42Cdga2Yc5lZNA5qM2ZkHYAaCeE4q9Tkl6d89XGPm2z2SHMyZ-PpvuS2qF9GA0_Kw7v6q6gHSHMAPW_D0h9h2SzTZVYzpUegTmNT8aK4u-EWyfAxBW5Ep1xvwqytx1WVgStgh5EEJi6_WzNKyCWducSNw=s16000&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;p dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;&lt;b&gt;3 Voice Interaction:&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Users can send voice messages.&lt;/span&gt;&lt;/li&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiLHr5r4u18vea6SqyEKlElWdmBNiXRD5LCaHkrGkcVa0oBcqiIgH0-4-1fk0GBep_78tr4nU_W5SwvQKEyb-i72mBHp7R_5VHaO5IvLuxKDQpFbFb9oNpUuj09NOQJBMoQdzLiOzG041biyEpcAAtpXw-kn2i5cNFmu8CfnoyTPcOuNebO8SqZVo0L_Po&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;494&quot; data-original-width=&quot;422&quot; height=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiLHr5r4u18vea6SqyEKlElWdmBNiXRD5LCaHkrGkcVa0oBcqiIgH0-4-1fk0GBep_78tr4nU_W5SwvQKEyb-i72mBHp7R_5VHaO5IvLuxKDQpFbFb9oNpUuj09NOQJBMoQdzLiOzG041biyEpcAAtpXw-kn2i5cNFmu8CfnoyTPcOuNebO8SqZVo0L_Po=w342-h400&quot; width=&quot;342&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;The bot transcribes the voice message using OpenAI&#39;s Whisper model.&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Generates a response using GPT-4.&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Sends back both the text response and an audio file of the response using text-to-speech.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;&lt;b&gt;4 Image Interaction:&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Users can send images with or without captions.&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;The bot extracts text from the image using OCR (Tesseract).&lt;/span&gt;&lt;/li&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgCzRNAgEhUEd7kr1mZpwFQ7XnMFyoht82DoGioQWRxUkP_SpBrFJ8Xuft43YJLdGZtebSYnf-9dHgpJy5L2nx_DBwcyOLt3zrEadqWnsYZuKxUQn8UbVOU4gjjNbqpSmS9pTCLcvtB90qNJ9HSeXBRCq779VhLiC1si_eBOJYSwnSeoAnyyXu45uah9Bg&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;236&quot; data-original-width=&quot;413&quot; height=&quot;229&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgCzRNAgEhUEd7kr1mZpwFQ7XnMFyoht82DoGioQWRxUkP_SpBrFJ8Xuft43YJLdGZtebSYnf-9dHgpJy5L2nx_DBwcyOLt3zrEadqWnsYZuKxUQn8UbVOU4gjjNbqpSmS9pTCLcvtB90qNJ9HSeXBRCq779VhLiC1si_eBOJYSwnSeoAnyyXu45uah9Bg=w400-h229&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Includes any caption text provided by the user.&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Combines the extracted text and caption, and generates a response using GPT-4.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;&lt;b&gt;5 Image Generation:&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Users can request image generation by sending a message starting with&amp;nbsp;&lt;code style=&quot;border-radius: 6px; box-sizing: border-box; font-family: var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace); font-size: 13.6px; margin: 0px; padding: 0.2em 0.4em; white-space-collapse: break-spaces;&quot;&gt;generate image:&lt;/code&gt;.&lt;/span&gt;&lt;/li&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgGoMoU02jFIqD7ox3qCXkY-4NESxLvWcvLQy4ARsdjBgKBdUX0nOo0w5EYYIEYOiwTa5M2m_SWTbkHUP1gYEGbYbYusqxouOHgzRUhveEf1DCAG4zjW34-0Qgn33RmJPsuNcRudCFPgvldSPUDYCnwZiRbJkczOpCPRQFSCRaJSbgYO6-JKMLDfASmguA&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;485&quot; data-original-width=&quot;421&quot; height=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgGoMoU02jFIqD7ox3qCXkY-4NESxLvWcvLQy4ARsdjBgKBdUX0nOo0w5EYYIEYOiwTa5M2m_SWTbkHUP1gYEGbYbYusqxouOHgzRUhveEf1DCAG4zjW34-0Qgn33RmJPsuNcRudCFPgvldSPUDYCnwZiRbJkczOpCPRQFSCRaJSbgYO6-JKMLDfASmguA=w347-h400&quot; width=&quot;347&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;The bot uses OpenAI&#39;s image generation API to create an image based on the provided prompt.&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Sends the generated image back to the user.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;&lt;b&gt;6 Text Extraction from Images:&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Users can extract text from images without involving OpenAI by sending an image with the caption starting with extract text:.&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;The bot processes the image using Tesseract OCR and sends back the extracted text.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;&lt;b&gt;7 Logging and Database Interaction:&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;All interactions are logged into a&amp;nbsp;&lt;code style=&quot;border-radius: 6px; box-sizing: border-box; font-family: var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace); font-size: 13.6px; margin: 0px; padding: 0.2em 0.4em; white-space-collapse: break-spaces;&quot;&gt;SQL Server&lt;/code&gt;&amp;nbsp;database for record-keeping and analysis.&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Includes user ID, username, message type, user message, and bot response.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;&lt;b&gt;8 Configuration File:&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Sensitive information like API keys, tokens, and authorized users are stored in a&amp;nbsp;&lt;code style=&quot;border-radius: 6px; box-sizing: border-box; font-family: var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace); font-size: 13.6px; margin: 0px; padding: 0.2em 0.4em; white-space-collapse: break-spaces;&quot;&gt;config.ini&lt;/code&gt;&amp;nbsp;file.&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Makes it easier to manage configurations without altering the main script.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;&lt;b&gt;9 Enhanced Logging and Error Handling:&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;The bot includes detailed logging for easier debugging and monitoring.&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Exception handling ensures the bot remains operational even when unexpected errors occur.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class=&quot;markdown-heading&quot; dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; position: relative;&quot;&gt;&lt;h2 class=&quot;heading-element&quot; dir=&quot;auto&quot; style=&quot;border-bottom: 1px solid var(--borderColor-muted, var(--color-border-muted)); box-sizing: border-box; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25; margin-bottom: var(--base-size-16); margin-top: var(--base-size-24); padding-bottom: 0.3em;&quot; tabindex=&quot;-1&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Setup Instructions&lt;/span&gt;&lt;/h2&gt;&lt;a aria-label=&quot;Permalink: Setup Instructions&quot; class=&quot;anchor&quot; href=&quot;https://github.com/JordiCorbilla/AI-Tutor/blob/main/README.md#setup-instructions&quot; id=&quot;user-content-setup-instructions&quot; style=&quot;align-items: center; border-radius: var(--borderRadius-medium); box-sizing: border-box; display: flex; float: left; height: 28px; justify-content: center; left: -28px; line-height: 1; margin: auto; opacity: 0; padding-right: var(--base-size-4); position: absolute; text-underline-offset: 0.2rem; top: 19.0938px; transform: translateY(calc(-50% - 0.3rem)); width: 28px;&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; class=&quot;octicon octicon-link&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewbox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;/svg&gt;&lt;span style=&quot;color: black;&quot;&gt;&lt;path d=&quot;m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z&quot;&gt;&lt;/path&gt;&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;markdown-heading&quot; dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; position: relative;&quot;&gt;&lt;h3 class=&quot;heading-element&quot; dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-size: 1.25em; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25; margin-bottom: var(--base-size-16); margin-top: var(--base-size-24);&quot; tabindex=&quot;-1&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Prerequisites&lt;/span&gt;&lt;/h3&gt;&lt;a aria-label=&quot;Permalink: Prerequisites&quot; class=&quot;anchor&quot; href=&quot;https://github.com/JordiCorbilla/AI-Tutor/blob/main/README.md#prerequisites&quot; id=&quot;user-content-prerequisites&quot; style=&quot;align-items: center; border-radius: var(--borderRadius-medium); box-sizing: border-box; display: flex; float: left; height: 28px; justify-content: center; left: -28px; line-height: 1; margin: auto; opacity: 0; padding-right: var(--base-size-4); position: absolute; text-underline-offset: 0.2rem; top: 12.5px; transform: translateY(-50%); width: 28px;&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; class=&quot;octicon octicon-link&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewbox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;/svg&gt;&lt;span style=&quot;color: black;&quot;&gt;&lt;path d=&quot;m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z&quot;&gt;&lt;/path&gt;&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Python 3.x&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Telegram account and a bot token from BotFather&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;OpenAI API key with access to GPT-4 and image generation&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;SQL Server instance for logging interactions&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Tesseract OCR installed on your system&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Required Python packages:&lt;/span&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; margin-bottom: 0px; margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;python-telegram-bot&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;openai&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;gTTS&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;pytesseract&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;pyodbc&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;pydub&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;requests&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;configparser&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Installation Steps&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;1 Clone the Repository:&lt;/span&gt;&lt;/p&gt;&lt;div class=&quot;snippet-clipboard-content notranslate position-relative overflow-auto&quot; style=&quot;box-sizing: border-box; display: flex; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; justify-content: space-between; margin-bottom: var(--base-size-16); overflow: auto; position: relative;&quot;&gt;&lt;pre class=&quot;notranslate&quot; style=&quot;border-radius: 6px; box-sizing: border-box; font-family: var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace); font-size: 13.6px; line-height: 1.45; margin-bottom: 0px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: var(--base-size-16);&quot;&gt;&lt;code style=&quot;background-attachment: initial; background-clip: initial; background-color: white; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; background: white; border-radius: 6px; border: 0px; box-sizing: border-box; display: inline; font-family: var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace); font-size: 13.6px; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;&quot;&gt;&lt;blockquote&gt;git clone https://github.com/JordiCorbilla/AI-Tutor.git
cd ai-tutor
&lt;/blockquote&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;zeroclipboard-container&quot; style=&quot;animation: auto ease 0s 1 normal none running none; box-sizing: border-box;&quot;&gt;&lt;clipboard-copy aria-label=&quot;Copy&quot; class=&quot;ClipboardButton btn btn-invisible js-clipboard-copy m-2 p-0 d-flex flex-justify-center flex-items-center&quot; data-copy-feedback=&quot;Copied!&quot; data-tooltip-direction=&quot;w&quot; role=&quot;button&quot; style=&quot;align-items: center; appearance: none; border-radius: 6px; border: 0px; box-shadow: none; box-sizing: border-box; cursor: pointer; display: flex; font-size: 14px; font-weight: var(--base-text-weight-medium, 500); height: var(--control-small-size, 28px); justify-content: center; line-height: 20px; margin: var(--base-size-8, 8px) !important; padding: 0px; position: relative; text-wrap: nowrap; transition: color 80ms cubic-bezier(0.33, 1, 0.68, 1), background-color, box-shadow, border-color; user-select: none; vertical-align: middle; width: var(--control-small-size, 28px);&quot; tabindex=&quot;0&quot; value=&quot;git clone https://github.com/JordiCorbilla/AI-Tutor.git
cd ai-tutor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; class=&quot;octicon octicon-copy js-clipboard-copy-icon&quot; data-view-component=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewbox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;/svg&gt;&lt;span style=&quot;color: black;&quot;&gt;&lt;path d=&quot;M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z&quot;&gt;&lt;/path&gt;&lt;/span&gt;&lt;/clipboard-copy&gt;&lt;/div&gt;&lt;/div&gt;&lt;p dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;2 Install Required Packages:&lt;/span&gt;&lt;/p&gt;&lt;div class=&quot;snippet-clipboard-content notranslate position-relative overflow-auto&quot; style=&quot;box-sizing: border-box; display: flex; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; justify-content: space-between; margin-bottom: var(--base-size-16); overflow: auto; position: relative;&quot;&gt;&lt;pre class=&quot;notranslate&quot; style=&quot;border-radius: 6px; box-sizing: border-box; font-family: var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace); font-size: 13.6px; line-height: 1.45; margin-bottom: 0px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: var(--base-size-16);&quot;&gt;&lt;code style=&quot;background-attachment: initial; background-clip: initial; background-color: white; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; background: white; border-radius: 6px; border: 0px; box-sizing: border-box; display: inline; font-family: var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace); font-size: 13.6px; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;&quot;&gt;&lt;blockquote&gt;pip install -r requirements.txt
&lt;/blockquote&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;zeroclipboard-container&quot; style=&quot;animation: auto ease 0s 1 normal none running none; box-sizing: border-box;&quot;&gt;&lt;clipboard-copy aria-label=&quot;Copy&quot; class=&quot;ClipboardButton btn btn-invisible js-clipboard-copy m-2 p-0 d-flex flex-justify-center flex-items-center&quot; data-copy-feedback=&quot;Copied!&quot; data-tooltip-direction=&quot;w&quot; role=&quot;button&quot; style=&quot;align-items: center; appearance: none; border-radius: 6px; border: 0px; box-shadow: none; box-sizing: border-box; cursor: pointer; display: flex; font-size: 14px; font-weight: var(--base-text-weight-medium, 500); height: var(--control-small-size, 28px); justify-content: center; line-height: 20px; margin: var(--base-size-8, 8px) !important; padding: 0px; position: relative; text-wrap: nowrap; transition: color 80ms cubic-bezier(0.33, 1, 0.68, 1), background-color, box-shadow, border-color; user-select: none; vertical-align: middle; width: var(--control-small-size, 28px);&quot; tabindex=&quot;0&quot; value=&quot;pip install -r requirements.txt&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; class=&quot;octicon octicon-copy js-clipboard-copy-icon&quot; data-view-component=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewbox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;/svg&gt;&lt;span style=&quot;color: black;&quot;&gt;&lt;path d=&quot;M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z&quot;&gt;&lt;/path&gt;&lt;/span&gt;&lt;/clipboard-copy&gt;&lt;/div&gt;&lt;/div&gt;&lt;p dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;3 Set Up Configuration File:&lt;/span&gt;&lt;/p&gt;&lt;div class=&quot;snippet-clipboard-content notranslate position-relative overflow-auto&quot; style=&quot;box-sizing: border-box; display: flex; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; justify-content: space-between; margin-bottom: var(--base-size-16); overflow: auto; position: relative;&quot;&gt;&lt;pre class=&quot;notranslate&quot; style=&quot;border-radius: 6px; box-sizing: border-box; font-family: var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace); font-size: 13.6px; line-height: 1.45; margin-bottom: 0px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: var(--base-size-16);&quot;&gt;&lt;code style=&quot;background-attachment: initial; background-clip: initial; background-color: white; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; background: white; border-radius: 6px; border: 0px; box-sizing: border-box; display: inline; font-family: var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace); font-size: 13.6px; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;&quot;&gt;&lt;blockquote&gt;[TELEGRAM]
BOT_TOKEN = your-telegram-bot-token
AUTHORIZED_USERS = user1,user2,user3

[OPENAI]
API_KEY = your-openai-api-key

[DATABASE]
SERVER = your-database-server
DATABASE = your-database-name
DRIVER = your-database-driver
&lt;/blockquote&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;zeroclipboard-container&quot; style=&quot;animation: auto ease 0s 1 normal none running none; box-sizing: border-box;&quot;&gt;&lt;clipboard-copy aria-label=&quot;Copy&quot; class=&quot;ClipboardButton btn btn-invisible js-clipboard-copy m-2 p-0 d-flex flex-justify-center flex-items-center&quot; data-copy-feedback=&quot;Copied!&quot; data-tooltip-direction=&quot;w&quot; role=&quot;button&quot; style=&quot;align-items: center; appearance: none; border-radius: 6px; border: 0px; box-shadow: none; box-sizing: border-box; cursor: pointer; display: flex; font-size: 14px; font-weight: var(--base-text-weight-medium, 500); height: var(--control-small-size, 28px); justify-content: center; line-height: 20px; margin: var(--base-size-8, 8px) !important; padding: 0px; position: relative; text-wrap: nowrap; transition: color 80ms cubic-bezier(0.33, 1, 0.68, 1), background-color, box-shadow, border-color; user-select: none; vertical-align: middle; width: var(--control-small-size, 28px);&quot; tabindex=&quot;0&quot; value=&quot;[TELEGRAM]
BOT_TOKEN = your-telegram-bot-token
AUTHORIZED_USERS = user1,user2,user3

[OPENAI]
API_KEY = your-openai-api-key

[DATABASE]
SERVER = your-database-server
DATABASE = your-database-name
DRIVER = your-database-driver&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; class=&quot;octicon octicon-copy js-clipboard-copy-icon&quot; data-view-component=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewbox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;/svg&gt;&lt;span style=&quot;color: black;&quot;&gt;&lt;path d=&quot;M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z&quot;&gt;&lt;/path&gt;&lt;/span&gt;&lt;/clipboard-copy&gt;&lt;/div&gt;&lt;/div&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Replace placeholders with your actual configuration values.&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;AUTHORIZED_USERS: Provide a comma-separated list of authorized usernames, full names, or user IDs.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;4 Install Tesseract OCR:&lt;/span&gt;&lt;/p&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Windows: Download and install from&amp;nbsp;&lt;a href=&quot;https://github.com/UB-Mannheim/tesseract/wiki&quot; style=&quot;box-sizing: border-box; text-underline-offset: 0.2rem;&quot;&gt;https://github.com/UB-Mannheim/tesseract/wiki&lt;/a&gt;.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;5 Run the bot:&lt;/span&gt;&lt;/p&gt;&lt;div class=&quot;snippet-clipboard-content notranslate position-relative overflow-auto&quot; style=&quot;box-sizing: border-box; display: flex; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; justify-content: space-between; margin-bottom: var(--base-size-16); overflow: auto; position: relative;&quot;&gt;&lt;pre class=&quot;notranslate&quot; style=&quot;border-radius: 6px; box-sizing: border-box; font-family: var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace); font-size: 13.6px; line-height: 1.45; margin-bottom: 0px; margin-top: 0px; overflow-wrap: normal; overflow: auto; padding: var(--base-size-16);&quot;&gt;&lt;code style=&quot;background-attachment: initial; background-clip: initial; background-color: white; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; background: white; border-radius: 6px; border: 0px; box-sizing: border-box; display: inline; font-family: var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace); font-size: 13.6px; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;&quot;&gt;PS C:\repo\AI-Tutor\venv\Scripts&amp;gt; &amp;amp; c:/repo/AI-Tutor/venv/Scripts/python.exe c:/repo/AI-Tutor/ai_tutor_telegram_bot.py
&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;zeroclipboard-container&quot; style=&quot;animation: auto ease 0s 1 normal none running none; box-sizing: border-box;&quot;&gt;&lt;clipboard-copy aria-label=&quot;Copy&quot; class=&quot;ClipboardButton btn btn-invisible js-clipboard-copy m-2 p-0 d-flex flex-justify-center flex-items-center&quot; data-copy-feedback=&quot;Copied!&quot; data-tooltip-direction=&quot;w&quot; role=&quot;button&quot; style=&quot;align-items: center; appearance: none; border-radius: 6px; border: 0px; box-shadow: none; box-sizing: border-box; cursor: pointer; display: flex; font-size: 14px; font-weight: var(--base-text-weight-medium, 500); height: var(--control-small-size, 28px); justify-content: center; line-height: 20px; margin: var(--base-size-8, 8px) !important; padding: 0px; position: relative; text-wrap: nowrap; transition: color 80ms cubic-bezier(0.33, 1, 0.68, 1), background-color, box-shadow, border-color; user-select: none; vertical-align: middle; width: var(--control-small-size, 28px);&quot; tabindex=&quot;0&quot; value=&quot;PS C:\repo\AI-Tutor\venv\Scripts&amp;gt; &amp;amp; c:/repo/AI-Tutor/venv/Scripts/python.exe c:/repo/AI-Tutor/ai_tutor_telegram_bot.py&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; class=&quot;octicon octicon-copy js-clipboard-copy-icon&quot; data-view-component=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewbox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;/svg&gt;&lt;span style=&quot;color: black;&quot;&gt;&lt;path d=&quot;M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z&quot;&gt;&lt;/path&gt;&lt;/span&gt;&lt;/clipboard-copy&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;markdown-heading&quot; dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; position: relative;&quot;&gt;&lt;h3 class=&quot;heading-element&quot; dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-size: 1.25em; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25; margin-bottom: var(--base-size-16); margin-top: var(--base-size-24);&quot; tabindex=&quot;-1&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Usage&lt;/span&gt;&lt;/h3&gt;&lt;a aria-label=&quot;Permalink: Usage&quot; class=&quot;anchor&quot; href=&quot;https://github.com/JordiCorbilla/AI-Tutor/blob/main/README.md#usage&quot; id=&quot;user-content-usage&quot; style=&quot;align-items: center; border-radius: var(--borderRadius-medium); box-sizing: border-box; display: flex; float: left; height: 28px; justify-content: center; left: -28px; line-height: 1; margin: auto; opacity: 0; padding-right: var(--base-size-4); position: absolute; text-underline-offset: 0.2rem; top: 12.5px; transform: translateY(-50%); width: 28px;&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; class=&quot;octicon octicon-link&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewbox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;/svg&gt;&lt;span style=&quot;color: black;&quot;&gt;&lt;path d=&quot;m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z&quot;&gt;&lt;/path&gt;&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Start the Bot:&lt;/span&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; margin-bottom: 0px; margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Open Telegram and start a chat with your bot.&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Send /start to receive a greeting message.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;User Authorization:&lt;/span&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; margin-bottom: 0px; margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Only users listed in the AUTHORIZED_USERS can interact with the bot.&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Unauthorized users will receive &quot;You are not authorized.&quot;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Text Messages:&lt;/span&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; margin-bottom: 0px; margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Send any text message, and the bot will reply with an AI-generated response.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Voice Messages:&lt;/span&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; margin-bottom: 0px; margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Send a voice message.&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;The bot will transcribe it, generate a response, and send back both text and audio.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Images with Optional Captions:&lt;/span&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; margin-bottom: 0px; margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Send an image.&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Optionally include a caption.&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;The bot will extract text from the image and combine it with the caption to generate a response.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Image Generation:&lt;/span&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; margin-bottom: 0px; margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Send a message starting with generate image: followed by your prompt.&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Example: generate image: a futuristic cityscape at sunset&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;The bot will generate an image based on your prompt and send it back.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Text Extraction from Images:&lt;/span&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; margin-bottom: 0px; margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Send an image with the caption starting with extract text:.&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;The bot will extract text from the image and send it back to you.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class=&quot;markdown-heading&quot; dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; position: relative;&quot;&gt;&lt;h3 class=&quot;heading-element&quot; dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-size: 1.25em; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25; margin-bottom: var(--base-size-16); margin-top: var(--base-size-24);&quot; tabindex=&quot;-1&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Logging and Monitoring&lt;/span&gt;&lt;/h3&gt;&lt;a aria-label=&quot;Permalink: Logging and Monitoring&quot; class=&quot;anchor&quot; href=&quot;https://github.com/JordiCorbilla/AI-Tutor/blob/main/README.md#logging-and-monitoring&quot; id=&quot;user-content-logging-and-monitoring&quot; style=&quot;align-items: center; border-radius: var(--borderRadius-medium); box-sizing: border-box; display: flex; float: left; height: 28px; justify-content: center; left: -28px; line-height: 1; margin: auto; opacity: 0; padding-right: var(--base-size-4); position: absolute; text-underline-offset: 0.2rem; top: 12.5px; transform: translateY(-50%); width: 28px;&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; class=&quot;octicon octicon-link&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewbox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;/svg&gt;&lt;span style=&quot;color: black;&quot;&gt;&lt;path d=&quot;m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z&quot;&gt;&lt;/path&gt;&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Logs:&lt;/span&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; margin-bottom: 0px; margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;All logs are written to bot.log in the project directory.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Database:&lt;/span&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; margin-bottom: 0px; margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Interactions are logged into the Interactions table in your specified SQL Server database.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Unauthorized Access Attempts:&lt;/span&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; margin-bottom: 0px; margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Unauthorized access attempts are logged with user details for security auditing.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class=&quot;markdown-heading&quot; dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; position: relative;&quot;&gt;&lt;h3 class=&quot;heading-element&quot; dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-size: 1.25em; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25; margin-bottom: var(--base-size-16); margin-top: var(--base-size-24);&quot; tabindex=&quot;-1&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Error Handling&lt;/span&gt;&lt;/h3&gt;&lt;a aria-label=&quot;Permalink: Error Handling&quot; class=&quot;anchor&quot; href=&quot;https://github.com/JordiCorbilla/AI-Tutor/blob/main/README.md#error-handling&quot; id=&quot;user-content-error-handling&quot; style=&quot;align-items: center; border-radius: var(--borderRadius-medium); box-sizing: border-box; display: flex; float: left; height: 28px; justify-content: center; left: -28px; line-height: 1; margin: auto; opacity: 0; padding-right: var(--base-size-4); position: absolute; text-underline-offset: 0.2rem; top: 12.5px; transform: translateY(-50%); width: 28px;&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; class=&quot;octicon octicon-link&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewbox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;/svg&gt;&lt;span style=&quot;color: black;&quot;&gt;&lt;path d=&quot;m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z&quot;&gt;&lt;/path&gt;&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: var(--base-size-16); margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;The bot is designed to handle exceptions gracefully.&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;In case of errors, appropriate messages are sent to the user without exposing technical details.&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Detailed error information is logged for developers to review.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class=&quot;markdown-heading&quot; dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; position: relative;&quot;&gt;&lt;h3 class=&quot;heading-element&quot; dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-size: 1.25em; font-weight: var(--base-text-weight-semibold, 600); line-height: 1.25; margin-bottom: var(--base-size-16); margin-top: var(--base-size-24);&quot; tabindex=&quot;-1&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Contributing&lt;/span&gt;&lt;/h3&gt;&lt;a aria-label=&quot;Permalink: Contributing&quot; class=&quot;anchor&quot; href=&quot;https://github.com/JordiCorbilla/AI-Tutor/blob/main/README.md#contributing&quot; id=&quot;user-content-contributing&quot; style=&quot;align-items: center; border-radius: var(--borderRadius-medium); box-sizing: border-box; display: flex; float: left; height: 28px; justify-content: center; left: -28px; line-height: 1; margin: auto; opacity: 0; padding-right: var(--base-size-4); position: absolute; text-underline-offset: 0.2rem; top: 12.5px; transform: translateY(-50%); width: 28px;&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; class=&quot;octicon octicon-link&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewbox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;/svg&gt;&lt;span style=&quot;color: black;&quot;&gt;&lt;path d=&quot;m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z&quot;&gt;&lt;/path&gt;&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;ul dir=&quot;auto&quot; style=&quot;box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;Noto Sans&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;; font-size: 16px; margin-bottom: 0px; margin-top: 0px; padding-left: 2em;&quot;&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Contributions are welcome! Feel free to open issues or submit pull requests.&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;&lt;a href=&quot;https://github.com/JordiCorbilla/AI-Tutor/tree/main&quot;&gt;JordiCorbilla/AI-Tutor: Building my own AI Tutor (github.com)&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;&lt;li style=&quot;box-sizing: border-box; margin-top: 0.25em;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Please ensure that your code adheres to the existing style and includes necessary logging and error handling.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;</description><link>https://thundaxsoftware.blogspot.com/2024/09/ai-tutor-telegram-bot-project.html</link><author>noreply@blogger.com (Jordi Corbilla)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEgayUyOQZOW_Rjx6pyrMiXtMn6hYvp78m56L9LcXlnEiKhDqJBrynignYqD96grATvtUgJDIswuSM9eIXa964EMu4TmXUYAxgI21ZI4ATjRHQOiS71f2c6cyKG4wrTKx3971afBTo6iuQdOUw4xh1I4stGS7S2vyA68Juv7E91phDnOPJFf_yF2aJjfqLU=s72-c" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>London, UK</georss:featurename><georss:point>51.5072178 -0.1275862</georss:point><georss:box>23.196983963821154 -35.2838362 79.817451636178845 35.0286638</georss:box></item></channel></rss>