<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Mirza Mahad Baig]]></title><description><![CDATA[Mirza Mahad Baig]]></description><link>https://mahadbaig.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>Mirza Mahad Baig</title><link>https://mahadbaig.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 15 Sep 2026 09:02:16 GMT</lastBuildDate><atom:link href="https://mahadbaig.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[I Built the Boring Part of My AI Portfolio. That Is Where It Got Interesting.]]></title><description><![CDATA[Building an AI-Native Portfolio, Part 2
Series note: This is not a daily or weekly build log. I will publish an update when the project reaches a meaningful milestone, produces something measurable, o]]></description><link>https://mahadbaig.hashnode.dev/i-built-the-boring-part-of-my-ai-portfolio-that-is-where-it-got-interesting</link><guid isPermaLink="true">https://mahadbaig.hashnode.dev/i-built-the-boring-part-of-my-ai-portfolio-that-is-where-it-got-interesting</guid><category><![CDATA[ai product engineering]]></category><category><![CDATA[RAG ]]></category><category><![CDATA[FastAPI]]></category><category><![CDATA[qdrant]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[Sanity ]]></category><category><![CDATA[mlops]]></category><dc:creator><![CDATA[Mahad Baig]]></dc:creator><pubDate>Fri, 11 Sep 2026 00:19:39 GMT</pubDate><content:encoded><![CDATA[<p><em>Building an AI-Native Portfolio, Part 2</em></p>
<p><strong>Series note:</strong> This is not a daily or weekly build log. I will publish an update when the project reaches a meaningful milestone, produces something measurable, or teaches me something worth sharing.</p>
<p><strong>In short:</strong> The first article was about why I decided to turn my portfolio into an AI product. This one is about the less glamorous work that now makes that idea real: Sanity as the content source, FastAPI and PostgreSQL as the operational backbone, deterministic chunking and embeddings, Qdrant retrieval, and a test set that checks whether the system can find the right source instead of confidently improvising.</p>
<h2>The blueprint has started turning into a machine</h2>
<p>In <a href="https://mahadbaig.hashnode.dev/i-decided-my-portfolio-should-be-the-project">Part 1</a>, I wrote that the portfolio was still more blueprint than machine.</p>
<p>That is no longer completely true.</p>
<p>There is still no polished “Talk to Mahad” assistant generating answers in my voice. The ML query router is not trained yet. LangGraph is not orchestrating anything yet. Voice is still a later phase.</p>
<p>But the layer underneath those features now exists.</p>
<p>The portfolio has a managed content system. It has a FastAPI backend. It has an operational PostgreSQL schema. It can extract approved content from Sanity, normalize it, split it into traceable chunks, generate multilingual embeddings, store the canonical records in PostgreSQL, index vectors in Qdrant, retrieve candidates, hydrate the full source text, and evaluate whether the correct document appeared.</p>
<p>That is a long way of saying: it can now build and search a real knowledge base.</p>
<p>And honestly, this part has been more interesting than adding a chat box would have been.</p>
<h2>First, I had to decide what “the truth” means</h2>
<p>The portfolio uses three different storage systems, which sounds excessive until each one has a clear job.</p>
<p>Sanity is the <strong>authoring source of truth</strong>. It is where I manage projects, case studies, articles, experience, education, skills, FAQs, architecture decisions, personal stories and examples of how I write. The website reads its public content from there, and the RAG ingestion pipeline starts there too.</p>
<p>Neon PostgreSQL is the <strong>operational source of truth</strong>. It stores normalized source documents, chunk manifests, versions, hashes, ingestion runs and the relationships needed to verify what was indexed. Later it will also support consented chat sessions, redacted messages, retrieval events, feedback and model-release metadata.</p>
<p>Qdrant is the <strong>rebuildable vector index</strong>. It is optimized for similarity search, not for owning the canonical copy of my content.</p>
<p>That distinction matters.</p>
<p>If Qdrant disappeared tomorrow, the system should be able to recreate it from the approved Sanity content and the PostgreSQL manifest. Losing a search index should be annoying, not existential.</p>
<p>The relationship looks like this:</p>
<ol>
<li><p>I publish approved content in Sanity.</p>
</li>
<li><p>The ingestion pipeline extracts and normalizes it.</p>
</li>
<li><p>PostgreSQL records the canonical document, chunks, hashes and embedding metadata.</p>
</li>
<li><p>Qdrant stores the vectors and compact filtering metadata, linked by the PostgreSQL chunk UUID.</p>
</li>
<li><p>Retrieval searches Qdrant first, then loads the authoritative chunk content from PostgreSQL.</p>
</li>
</ol>
<p>This is one of those architectural choices that is invisible in a demo, but very visible the moment something fails.</p>
<h2>Sanity is not just feeding the website</h2>
<p>I originally wanted a headless CMS because I needed an easy way to keep adding projects and articles without editing code every time.</p>
<p>Once RAG entered the picture, the CMS needed to do more than store page copy.</p>
<p>Every indexable document now carries metadata such as:</p>
<ul>
<li><p>whether RAG indexing is enabled;</p>
</li>
<li><p>which audiences it is relevant to;</p>
</li>
<li><p>whether it is public or restricted;</p>
</li>
<li><p>the human-readable citation label;</p>
</li>
<li><p>its canonical website path;</p>
</li>
<li><p>the date it was last reviewed;</p>
</li>
<li><p>and whether it is published, in draft, or archived.</p>
</li>
</ul>
<p>This gives content governance a place in the architecture before the LLM ever sees a prompt.</p>
<p>For example, INDKOM can appear on the website while remaining excluded from RAG if I do not want the assistant retrieving it. Draft or restricted material should not quietly enter the public knowledge base just because it exists in the CMS.</p>
<p>It also keeps the frontend and the assistant from developing separate versions of my story. A project description should not say one thing on the website while the chatbot cites an old Markdown file stored somewhere else.</p>
<p>One approved source. Different consumers.</p>
<h2>Then came the backend plumbing</h2>
<p>The FastAPI service now has the kind of foundation that is easy to skip when building an AI demo:</p>
<ul>
<li><p>typed settings through Pydantic;</p>
</li>
<li><p>structured JSON logs and correlation IDs;</p>
</li>
<li><p>liveness, readiness and version endpoints;</p>
</li>
<li><p>an explicit CORS allowlist;</p>
</li>
<li><p>request-size limits;</p>
</li>
<li><p>stable error responses;</p>
</li>
<li><p>provider interfaces and test doubles;</p>
</li>
<li><p>and an OpenAPI artifact checked in CI.</p>
</li>
</ul>
<p>The database layer uses async SQLAlchemy and Alembic migrations. Route handlers do not directly reach into ORM models. Repositories and services own those boundaries.</p>
<p>None of this makes an LLM answer sound smarter. It does make the system easier to test, replace and debug.</p>
<p>The project also runs migrations against a non-production Neon branch and tests that a clean database can be created from the migration history. I wanted the database schema to be reproducible, not something that only exists because my local environment accumulated tables over time.</p>
<p>This was the point where the portfolio stopped feeling like a frontend with an ambitious architecture diagram.</p>
<h2>Building the ingestion pipeline properly</h2>
<p>The ingestion pipeline is offline and deterministic. That is intentional.</p>
<p>I do not need an LLM to rewrite my portfolio content before indexing it. I need predictable transformations that I can inspect and repeat.</p>
<p>The current pipeline:</p>
<ol>
<li><p>Fetches approved documents from Sanity.</p>
</li>
<li><p>Resolves their referenced content.</p>
</li>
<li><p>Converts Portable Text into structured normalized text.</p>
</li>
<li><p>Preserves headings, lists, captions, code and canonical paths.</p>
</li>
<li><p>Normalizes whitespace and Unicode without damaging Urdu text.</p>
</li>
<li><p>Computes a SHA-256 hash for the source document.</p>
</li>
<li><p>Splits it using a heading-aware recursive chunker.</p>
</li>
<li><p>Computes deterministic chunk hashes and ordering.</p>
</li>
<li><p>Generates embeddings in batches.</p>
</li>
<li><p>Writes the document and chunk manifest to PostgreSQL.</p>
</li>
<li><p>Skips unchanged content on the next run.</p>
</li>
</ol>
<p>The chunks target roughly 350 to 500 tokens with 50 to 75 tokens of overlap. A chunk retains the project identity and heading path it came from. The chunker also tries to keep a list introduction attached to its list, and it never combines content from two projects into one chunk.</p>
<p>Those details sound small until a retrieved passage begins halfway through a list, loses the name of the project it describes, or merges two unrelated case studies into one confusing piece of context.</p>
<p>Chunking is not just “split every N characters.” It is a set of information-design decisions applied to text.</p>
<p>That part felt surprisingly familiar coming from product design and UI/UX. Good interface structure helps a person understand context. Good chunk structure helps a retrieval system preserve it.</p>
<h2>Why Multilingual E5?</h2>
<p>The embedding model is <code>intfloat/multilingual-e5-small</code>, with the model and tokenizer versions pinned.</p>
<p>The multilingual requirement is not decorative. I naturally switch between English and Roman Urdu, and visitors from Pakistan may do the same. A query such as:</p>
<blockquote>
<p>CardioScan mein stress aur rest images kaise use hoti hain?</p>
</blockquote>
<p>should still retrieve the relevant English project content.</p>
<p>The implementation follows E5's expected <code>query:</code> and <code>passage:</code> prefixes and stores normalized vectors. More importantly, the same embedding model and version are used at indexing time and query time. If those drift apart, similarity scores stop meaning what we think they mean.</p>
<p>I chose the small model because the system is designed around strict free-tier and CPU constraints. The goal is not to select the largest embedding model I can name. The goal is to establish a measured baseline that fits the actual deployment environment.</p>
<p>If a larger model later produces a worthwhile improvement, I can test that claim. Until then, smaller and reproducible wins.</p>
<h2>Qdrant finds candidates. PostgreSQL verifies them.</h2>
<p>Retrieval uses a two-stage pattern.</p>
<p>First, the query is embedded and Qdrant searches for the top 12 vector candidates. Metadata filters can narrow the search by document type, project, audience, language or active version.</p>
<p>Then the service takes the returned point IDs and hydrates the canonical chunks from PostgreSQL.</p>
<p>At that stage it rejects records that are inactive, missing, or tied to the wrong embedding version. It deduplicates adjacent or near-identical chunks and selects up to five chunks within a configurable context budget.</p>
<p>This separation keeps the Qdrant payload compact. It only needs enough metadata for filtering and debugging, including a short preview. The complete source content stays in PostgreSQL.</p>
<p>It also prevents the system from treating a vector hit as unquestionable truth. Qdrant answers, “these points look similar.” PostgreSQL answers, “these are the active canonical records they refer to.”</p>
<p>Both answers are needed before context goes anywhere near an LLM.</p>
<h2>I accidentally designed a benchmark for a portfolio that did not exist</h2>
<p>The most useful lesson in this phase came from the retrieval evaluation dataset.</p>
<p>The first draft contained questions about projects, capabilities and metrics that were not actually present in my approved portfolio content. Some facts were assumed. Some projects were not published. Some questions described CardioScan as ECG analysis when it actually works with myocardial perfusion images.</p>
<p>The JSON looked professional. It was also testing fiction.</p>
<p>That is a dangerous failure mode because an evaluation pipeline can produce very respectable numbers while measuring the wrong thing.</p>
<p>So I rebuilt the dataset around the content that is genuinely published and RAG-enabled.</p>
<p>The current benchmark contains 60 queries:</p>
<ul>
<li><p>50 in-domain questions;</p>
</li>
<li><p>10 out-of-domain or refusal cases;</p>
</li>
<li><p>43 English queries;</p>
</li>
<li><p>17 natural Roman Urdu queries;</p>
</li>
<li><p>coverage across the portfolio itself, CardioScan AI, Busyfile, Legal AI and the first article in this series.</p>
</li>
</ul>
<p>It includes straightforward questions, technical questions, limitations, implementation status, product decisions, privacy requests and prompt-injection attempts.</p>
<p>The evaluation script measures Hit@1, Hit@3, Hit@5 and mean reciprocal rank across several similarity thresholds. For out-of-domain questions, it also measures whether the retriever correctly returns no citations instead of attaching an unrelated project to a question about weather, recipes or private information.</p>
<p>This is not yet an evaluation of final answer quality. There is no LangGraph answer-generation path at this stage. It is specifically a retrieval benchmark: did the correct source appear, how high did it rank, and did irrelevant questions stay out?</p>
<p>That narrower claim is important.</p>
<h2>Rebuildability is part of the feature</h2>
<p>Qdrant's free cluster can be suspended or deleted. Instead of pretending that cannot happen, the project treats recovery as a normal operating scenario.</p>
<p>There is a guarded full-rebuild command, explicit confirmation flags, batch upserts and deletion retries. The local/test collection was deliberately removed and rebuilt to verify that PostgreSQL and the ingestion pipeline could restore it.</p>
<p>There is also a retrieval inspector CLI that shows a query, filters, ranks and sources. That gives me a way to debug retrieval directly instead of blaming “the AI” when the wrong chunk appears.</p>
<p>This is becoming a recurring theme in the project: if I cannot inspect it, reproduce it or recover it, I do not want to call it production-oriented.</p>
<h2>What exists today</h2>
<p>As of this update, the following phases are complete and gated:</p>
<ul>
<li><p>the product scope, personas, success criteria and architecture decisions;</p>
</li>
<li><p>the Next.js portfolio foundation and quality checks;</p>
</li>
<li><p>the Sanity Studio schemas and live CMS integration;</p>
</li>
<li><p>the FastAPI and Neon PostgreSQL operational foundation;</p>
</li>
<li><p>deterministic extraction, normalization, chunking and embedding;</p>
</li>
<li><p>Qdrant indexing, two-stage retrieval and PostgreSQL hydration;</p>
</li>
<li><p>the 60-query retrieval benchmark, threshold evaluation and index-recovery path.</p>
</li>
</ul>
<p>The public interface is intentionally plain: white, near-black, neutral grays, Plus Jakarta Sans and a lot of breathing room. I am not trying to distract from incomplete engineering with animation.</p>
<p>The repository currently contains the working knowledge layer, but not the finished conversational product.</p>
<h2>What comes next</h2>
<p>The next milestone is the automated CMS-to-index lifecycle.</p>
<p>Right now, the ingestion pipeline works, but a published Sanity change still needs a robust incremental path. The next phase will handle create, update, unpublish and delete events; preserve the previous active version when a run fails; reconcile differences across Sanity, PostgreSQL and Qdrant; and trigger targeted ingestion through a signed webhook and CI workflow.</p>
<p>After that comes the in-process ML query router:</p>
<ul>
<li><p>a reviewed label system;</p>
</li>
<li><p>English and Roman Urdu training examples;</p>
</li>
<li><p>group-aware dataset splits to reduce leakage;</p>
</li>
<li><p>a TF-IDF plus Logistic Regression baseline;</p>
</li>
<li><p>MLflow experiment tracking and error analysis;</p>
</li>
<li><p>later comparison with a compact transformer;</p>
</li>
<li><p>and ONNX export only if the candidate earns promotion.</p>
</li>
</ul>
<p>Then LangGraph can sit on top of a retrieval system and router that have already been tested independently.</p>
<p>That order matters to me. I do not want an agent graph hiding weak retrieval behind a fluent answer. I want each layer to prove that it works before the next layer makes the system more complicated.</p>
<h2>The main thing I learned</h2>
<p>The first article was about a big idea: make the portfolio itself demonstrate the work.</p>
<p>This phase was about accepting what that idea actually requires.</p>
<p>RAG is not a vector database plus an LLM call. It is content ownership, access rules, normalization, chunk boundaries, versioned embeddings, canonical records, filtering, hydration, evaluation, recovery and honest limits. The generated answer is almost the last part.</p>
<p>That is probably why I am enjoying this build so much. It combines the system thinking I developed in product design and UI/UX with the engineering discipline I am now building in AI systems.</p>
<p>The portfolio still cannot talk like me.</p>
<p>But it can finally find the right things to talk about.</p>
<p>That feels like the correct place to end Part 2.</p>
<hr />
<p><strong>Repository:</strong> <a href="https://github.com/mahadbaig2/mahad-ai-portfolio">mahadbaig2/mahad-ai-portfoli</a>o</p>
]]></content:encoded></item><item><title><![CDATA[I Decided My Portfolio Should Be the Project]]></title><description><![CDATA[💡
This is going to be a first long article....so if you do not want to read it all and get bored, just know this: I am turning my portfolio into a complete AI product that demonstrates my journey fro]]></description><link>https://mahadbaig.hashnode.dev/i-decided-my-portfolio-should-be-the-project</link><guid isPermaLink="true">https://mahadbaig.hashnode.dev/i-decided-my-portfolio-should-be-the-project</guid><category><![CDATA[AI]]></category><category><![CDATA[ML]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[portfoliowebsite]]></category><category><![CDATA[Build In Public]]></category><dc:creator><![CDATA[Mahad Baig]]></dc:creator><pubDate>Fri, 04 Sep 2026 21:40:10 GMT</pubDate><content:encoded><![CDATA[<div>
<div>💡</div>
<div>This is going to be a first long article....so if you do not want to read it all and get bored, just know this: I am turning my portfolio into a complete AI product that demonstrates my journey from product design to AI Product Engineering. The plan is to build a very over-engineered portfolio that will have everything, including managed content, RAG, a custom ML router, LangGraph agents, evaluation, observability and personalized voice. I plan to documenting the honest progress, failures and decisions along the way. The project lives at <a target="_self" rel="noopener noreferrer nofollow" class="text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer" href="https://github.com/mahadbaig2/mahad-ai-portfolio" style="pointer-events:none">github.com/mahadbaig2/mahad-ai-portfolio</a>.</div>
</div>

<p>Most portfolios are wrappers around the work.</p>
<p>You build a few projects, take some screenshots, write case studies, add an About page, and put everything inside a polished website. The portfolio points toward the interesting things, but the portfolio itself is usually not one of them.</p>
<p>I wanted to try something different.</p>
<p>Instead of building a website that says I understand AI products, I am building a website that has to prove it.</p>
<p>The final portfolio will have a conversational assistant that knows my work, retrieves evidence from my actual content, explains why it chose an answer, and eventually speaks in something close to my own conversational style. Behind that relatively simple interface will be a complete RAG pipeline, a trained query-routing model, LangGraph orchestration, evaluation, observability, voice input and output, and a content system I can keep updating without rebuilding everything.</p>
<blockquote>
<p><strong>It is intentionally over-engineered (very much).</strong></p>
</blockquote>
<p>That sentence normally sounds like a confession. In this project, it is the point.</p>
<p>This is the first entry in an update-based build series. I am not committing to a daily streak or manufacturing a weekly update when nothing meaningful has happened. I will write when a phase produces something worth explaining: a decision, an experiment, a failure, a benchmark, or a working piece of the system.</p>
<h2>How I got here</h2>
<p>My route into AI engineering has not been linear.</p>
<p>I spent nearly six years working in product design and UI/UX, much of it on enterprise SaaS products. I worked across discovery, user flows, information architecture, design systems, complex dashboards, implementation handoff, and the less glamorous parts of product work that appear after a clean Figma prototype meets an actual business.</p>
<p>One of the most formative projects was <a href="https://medium.com/@mirza.mahad/busyfile-designing-a-whitelabel-saas-for-accounting-firms-0922a7ecc211?source=user_profile_page---------0-------------f7c5776e2a29------------------------">Busyfile</a>. I joined as its founding product designer and worked on a system that evolved from a consumer business-formation product into a larger B2B platform for accounting firms. The work eventually covered hundreds of screens, different entity types, state-specific flows, operations tooling, and more than twenty services.</p>
<p>That kind of project teaches you a useful lesson: the interface is never the entire product.</p>
<p>Every apparently simple screen is connected to rules, data, permissions, failure states, operational processes, and people who need the system to behave consistently. I kept finding myself interested in those connections. I wanted to understand not only what the product should do, but how it could actually do it.</p>
<p>That curiosity gradually moved me closer to engineering.</p>
<p>At Techomatrix, I worked on AI automation workflows and LLM-powered features using tools such as Python, n8n, Groq, databases, APIs, prompt workflows, and internal knowledge systems. I also started building and experimenting independently with RAG, agents, LangGraph, FastAPI, vector databases, ML and deep learning.</p>
<p>My final-year project, CardioScan AI, pulled me further into applied machine learning. We worked with rest and stress myocardial perfusion imaging, trained CNN architectures including VGG16, DenseNet121 and ResNet50V2, and explored an ensemble for coronary artery disease classification. The results were not magically perfect, which was useful in its own way. Working with limited medical data makes it very difficult to pretend that model selection is just importing a larger architecture and waiting for accuracy to happen.</p>
<p>Other projects pushed me toward agentic and retrieval-based systems: enterprise knowledge assistants, evidence-grounded question answering, workflow automations, and AQL, an enterprise AI workspace concept built around permission-aware knowledge and cross-tool actions.</p>
<p>Somewhere in that transition, “product designer learning AI” stopped describing what I was trying to become.</p>
<p>The direction that makes more sense to me is <strong>AI Product Engineering</strong>: understanding the user and product problem, designing the interaction, building the AI system, measuring whether it works, and dealing with the operational reality after the demo.</p>
<p>I am still learning large parts of that stack. This portfolio is how I plan to make that learning concrete and inspectable.</p>
<h2>The original problem with my portfolio</h2>
<p>I already had projects. I already had design case studies. I could have made a conventional portfolio, added a chatbot powered by a large prompt, and called it AI-enabled.</p>
<p>But that would demonstrate very little.</p>
<p>Anyone can send a résumé and a question to an LLM API. The difficult parts begin after that:</p>
<ul>
<li><p>How does the system know which information is trustworthy?</p>
</li>
<li><p>What happens when the answer is not present in the source material?</p>
</li>
<li><p>How is content updated without manually rebuilding prompts?</p>
</li>
<li><p>How are documents divided without destroying their meaning?</p>
</li>
<li><p>How do PostgreSQL and a vector database divide responsibilities?</p>
</li>
<li><p>How do I measure retrieval quality instead of saying it “looks good”?</p>
</li>
<li><p>Which questions need retrieval, an agent, a deterministic response, or no LLM at all?</p>
</li>
<li><p>Can a smaller trained model save latency and inference calls?</p>
</li>
<li><p>What happens when a free service is asleep, rate-limited, or unavailable?</p>
</li>
<li><p>Can someone inspect why the system produced a particular answer?</p>
</li>
<li><p>Can the whole thing remain online without giving me a surprise bill?</p>
</li>
</ul>
<p>Those questions became more interesting than the idea of the portfolio itself.</p>
<p>So the concept changed from <strong>a portfolio with an AI feature</strong> to <strong>an AI product that happens to be my portfolio</strong>.</p>
<p>The working centerpiece is called <strong>Talk to Mahad (Working name)</strong>.</p>
<p>The visitor should eventually be able to ask questions such as:</p>
<blockquote>
<p>What did Mahad actually do on Busyfile?</p>
</blockquote>
<blockquote>
<p>Which of his projects involved RAG?</p>
</blockquote>
<blockquote>
<p>Why did he move from product design into AI engineering?</p>
</blockquote>
<blockquote>
<p>Show me the architecture of this portfolio.</p>
</blockquote>
<blockquote>
<p>Explain the same answer for a recruiter instead of an engineer.</p>
</blockquote>
<p>The system should answer using approved sources, show citations, expose useful execution details, and admit when the available material does not contain an answer.</p>
<p>That last behavior matters. “I do not have enough evidence to answer that” is a product feature, not a failure.</p>
<h2>What the finished system is supposed to demonstrate</h2>
<p>The project is split into layers because I want every technical choice to demonstrate a specific production concern.</p>
<h3>1. A real content layer</h3>
<p>Projects, case studies, articles, experience, FAQs, personal stories, architecture decisions, and carefully selected writing examples will live in Sanity.</p>
<p>Sanity will be the authoring source of truth. I should be able to publish or update a project from the CMS without changing application code.</p>
<p>Not every field will automatically become AI knowledge. Content will include publishing, sensitivity, audience, review, and RAG eligibility metadata. A public page and an assistant knowledge base have overlapping requirements, but they are not exactly the same thing.</p>
<h3>2. A complete offline RAG ingestion pipeline</h3>
<p>The RAG system will not embed random page text during a user request.</p>
<p>An offline pipeline will:</p>
<ol>
<li><p>Extract approved documents from Sanity.</p>
</li>
<li><p>Normalize Portable Text while preserving headings, lists, code and source paths.</p>
</li>
<li><p>Generate deterministic document hashes.</p>
</li>
<li><p>Split documents using heading-aware recursive chunking.</p>
</li>
<li><p>Attach project, audience, language and source metadata to every chunk.</p>
</li>
<li><p>Generate multilingual embeddings.</p>
</li>
<li><p>Store canonical documents and chunk manifests in PostgreSQL.</p>
</li>
<li><p>Store searchable vectors and compact metadata in Qdrant.</p>
</li>
<li><p>Skip unchanged content on later runs.</p>
</li>
<li><p>Support deletion, re-indexing and full index reconstruction.</p>
</li>
</ol>
<p>The current embedding candidate is <code>intfloat/multilingual-e5-small</code>, partly because the assistant needs to handle English, Urdu and Roman Urdu content without introducing a large hosted embedding bill.</p>
<p>The initial chunking target is roughly 350 to 500 tokens with controlled overlap. That is a starting hypothesis, not a sacred number. Retrieval evaluation will decide whether it survives.</p>
<h3>3. PostgreSQL and Qdrant doing different jobs</h3>
<p>I do not want the vector database to become a mysterious second source of truth.</p>
<p>Neon PostgreSQL will hold operational and canonical records: source documents, chunk text, hashes, versions, ingestion runs, model releases, consented sessions, retrieval events and feedback.</p>
<p>Qdrant will hold the derived vector index used for similarity search.</p>
<p>The relationship is simple: a Qdrant point ID maps back to a PostgreSQL chunk ID. Retrieval finds candidates in Qdrant, then the API hydrates and validates the canonical chunk from PostgreSQL. If the vector collection disappears, it should be rebuildable from known source content and manifests.</p>
<p>That separation adds work, but it demonstrates the difference between durable product data and a disposable search index.</p>
<h3>4. A small ML model before the large language model</h3>
<p>One of my favorite planned parts is a custom query router.</p>
<p>Before calling an LLM, the system will classify what kind of request it received. Possible routes include portfolio retrieval, navigation, casual conversation, unsafe or irrelevant input, and questions that require clarification.</p>
<p>The plan is to start embarrassingly simple with rules and a TF-IDF baseline. (Claude recommended TF-IDF but maybe I'll use Logistic Regression as a baseline). Then I can train a compact transformer classifier, compare it against the baseline, track experiments in MLflow, export the chosen model to ONNX, and run it inside the FastAPI process.</p>
<p>The point is not to add machine learning because the architecture diagram had empty space.</p>
<p>The hypothesis is that a cheap local classifier can avoid unnecessary LLM calls, reduce latency, create more deterministic routing, and provide a proper ML lifecycle to evaluate. If the baseline performs just as well, that is also a valid result. A complicated model does not win by being complicated.</p>
<h3>5. Agentic orchestration with boundaries</h3>
<p>LangGraph will coordinate the request workflow.</p>
<p>A typical grounded question may move through classification, retrieval, context validation, answer generation, citation checking, and a limited recovery path. The graph will use typed state and explicit conditional edges so the flow is inspectable.</p>
<p>This will not be a collection of agents talking to each other until something sounds plausible.</p>
<p>Some steps should be deterministic. Some should use tools. Some should call an LLM. Some requests should stop early. “Agentic” only becomes useful when the autonomy is connected to a clear job and bounded by observable state.</p>
<h3>6. Two separate observability stories</h3>
<p>MLflow will track the traditional ML lifecycle: datasets, features, training runs, hyperparameters, classification metrics, artifacts, ONNX exports and model versions.</p>
<p>LangSmith will cover the LLM and agent workflow: traces, prompts, retrieval context, tool calls, latency, evaluation datasets and regression checks.</p>
<p>I deliberately want both because MLOps and LLMOps overlap, but they are not interchangeable.</p>
<h3>7. Voice that is more than a microphone button</h3>
<p>The voice layer will begin with push-to-talk.</p>
<p>Audio will be transcribed using Groq Whisper, passed through the same assistant graph as typed input, and returned with a visible transcript. Voice output will remain an isolated service so a failure in speech synthesis does not take down chat.</p>
<p>The experimental plan includes OpenVoice V2 for personalized speech. I will only use recordings for which I have explicit rights and consent. The assistant should also disclose that the voice is synthetic.</p>
<p>The goal is not to trick anyone into believing they are speaking to me. It is to explore a personalized, bilingual voice experience, including the English and Urdu code-switching that appears naturally in how I speak.</p>
<h3>8. A zero-surprise-bill constraint</h3>
<p>The permanent system is being designed around free or hard-capped services.</p>
<p>That constraint affects architecture. It means quotas, timeouts, retries, circuit breakers, caching, graceful degradation, service sleep behavior, and fallbacks cannot be postponed until after deployment.</p>
<p>If AI capacity is unavailable, the portfolio must still work as a portfolio. If voice fails, text chat should survive. If chat fails, the case studies should remain accessible. Complexity is acceptable here; fragility is not.</p>
<h2>What exists today</h2>
<p>Right now, the repository contains the foundation, not the finished AI system.</p>
<p>The project lives at <a href="https://github.com/mahadbaig2/mahad-ai-portfolio">github.com/mahadbaig2/mahad-ai-portfolio</a>.</p>
<p>So far I have:</p>
<ul>
<li><p>Defined and scope-locked the first version.</p>
</li>
<li><p>Written the target personas and measurable product success criteria.</p>
</li>
<li><p>Created a milestone-based <code>TASKS.md</code> that separates automated work from tasks requiring my review.</p>
</li>
<li><p>Added architecture decision records for the CMS, relational database, vector index, API deployment, ML router, observability, voice isolation and streaming approach.</p>
</li>
<li><p>Scaffolded a pnpm monorepo for the web app, Sanity Studio, FastAPI service, voice service, shared packages and offline pipelines.</p>
</li>
<li><p>Built the first Next.js portfolio pages using strict TypeScript and Tailwind CSS.</p>
</li>
<li><p>Established the intentionally plain visual direction: white, near-black, neutral gray, Plus Jakarta Sans and a lot of breathing room.</p>
</li>
<li><p>Added the initial Home, Work, case-study, Blog, About and Contact structures.</p>
</li>
<li><p>Added unit-test, Playwright, accessibility and Lighthouse foundations.</p>
</li>
<li><p>Added dependency automation and secret scanning.</p>
</li>
<li><p>Documented local PostgreSQL and Qdrant development services through Docker Compose.</p>
</li>
<li><p>Created a free-tier audit runbook because “free tier” and “impossible to bill” are not the same promise.</p>
</li>
</ul>
<p>The current repository is also in a useful, slightly uncomfortable stage: the first frontend implementation exists, but the Phase 1 quality gate is not approved yet.</p>
<p>During review, I found missing or unfinished connections, including a shared data module, a chat route that is linked before the interface exists, placeholder contact details, and a résumé asset that still needs to be added. The validation pipeline also needs to prove linting, type checking, tests and the production build before I call the phase complete.</p>
<p>This is exactly why I added gates to the build plan.</p>
<p>A checked task list is not evidence that the software works. The repository has to pass, and I have to manually approve the content and experience assigned to me. Until that happens, Phase 1 remains open.</p>
<h2>Why the interface is deliberately boring right now</h2>
<p>I have a product design background, so it would be very easy for me to spend the first month polishing cards, transitions and a heroic hero section.</p>
<p>I am refusing to do that.</p>
<p>For now, the UI is black, white and neutral gray. There are no accent colors, dramatic gradients, glass panels or decorative AI particles floating behind the heading. The layout is minimal, breathable and easy to scan.</p>
<p>That is partly a design preference and partly scope control.</p>
<p>The first job of the interface is to make the content usable and the system states understandable. It needs accessible navigation, visible citations, readable transcripts, honest loading and failure states, and progressive disclosure for people who want to inspect the engineering details.</p>
<p>There will be time to refine the visual identity later. A beautiful shell around an unreliable assistant is not the portfolio piece I want to build.</p>
<h2>How I am building it without pretending I know everything</h2>
<p>This project is also a learning system for me.</p>
<p>Each phase introduces a limited set of concepts. I learn them, implement them, test them, document what happened, and only then move forward. The roadmap starts with the conventional portfolio, then adds managed content, the API and relational model, offline ingestion, vector retrieval, the ML router, LangGraph, evaluation, chat, voice, deployment and operations.</p>
<p>The order matters.</p>
<p>It is difficult to evaluate retrieval before defining good source documents. It is difficult to build reliable agents before the underlying tools behave deterministically. It is difficult to claim MLOps knowledge if there is no dataset lineage or reproducible training run. And it is difficult to debug a voice assistant if text chat is not stable.</p>
<p>I am using Antigravity to help execute the build, but not as an excuse to remove myself from it. The task system assigns implementation work to the agent and reserves factual approval, credentials, content decisions, voice consent, evaluation judgments and milestone gates for me.</p>
<p>The objective is not to see how much code an agent can produce while I watch. The objective is to use an agentic coding workflow while remaining accountable for the architecture, learning, review and final product.</p>
<h2>What comes next</h2>
<p>The immediate next step is not LangGraph, voice cloning or an impressive chat demo.</p>
<p>First, I need to close the frontend foundation properly:</p>
<ul>
<li><p>Repair the current build blockers.</p>
</li>
<li><p>Replace placeholder personal information.</p>
</li>
<li><p>Remove or label claims about features that are not implemented yet.</p>
</li>
<li><p>Add complete CI checks.</p>
</li>
<li><p>Review the site on desktop and mobile.</p>
</li>
<li><p>Approve the Phase 1 gate manually.</p>
</li>
</ul>
<p>After that, Phase 2 is the Sanity CMS.</p>
<p>I will define schemas for projects, case studies, articles, experience, education, skills, FAQs, architecture decisions, personal stories, style examples and site-wide settings. I will add explicit controls for which content can enter the RAG pipeline, connect the Next.js pages to typed GROQ queries, and verify that a CMS edit can update the public site without a code deployment.</p>
<p>That phase should produce the next article in this series because it raises an interesting product question:</p>
<blockquote>
<p>How do you design one content system for human readers, portfolio pages, and an AI knowledge base without letting those responsibilities collapse into each other?</p>
</blockquote>
<p>Once the content foundation is reliable, I can begin the API and data model. Then the RAG pipeline becomes real rather than theoretical.</p>
<h2>The broader experiment</h2>
<p>I am building this because I need a portfolio, but I am also using it to answer a larger question about my own career.</p>
<p>Can the habits I developed in product design - understanding context, reducing friction, structuring information, handling edge cases, and thinking from the user's perspective - make me better at engineering AI products?</p>
<p>I think they can.</p>
<p>AI systems have enough technical uncertainty already. They do not also need vague goals, confusing interfaces, invisible failure states and features added because they sound impressive.</p>
<p>At the same time, product thinking without technical depth reaches a limit. If I want to make meaningful decisions about retrieval, latency, model behavior, evaluation, architecture and cost, I need to understand and build those systems myself.</p>
<p>This portfolio is where those two sides meet.</p>
<p>It is currently more blueprint than machine. That will change one verified milestone at a time.</p>
<p>If you want to inspect the work as it develops, the repository is public:</p>
<p><a href="https://github.com/mahadbaig2/mahad-ai-portfolio"><strong>mahadbaig2/mahad-ai-portfolio</strong></a></p>
<p>The next update will come when there is something real to show, measure, or break.</p>
]]></content:encoded></item></channel></rss>