Inspiration

Every distributor and manufacturer we talked to has the same quiet bottleneck. A request for quote lands in a shared inbox as a plain email or a messy PDF. Someone on the sales desk reads it line by line, guesses which catalog part each item maps to, checks stock and pricing, and types up a quote by hand. A busy desk does this dozens of times a day. It is slow, it is boring, and it is exactly the kind of work where a tired person on a Friday afternoon quotes the wrong bolt.

We did not want to build another chatbot that sits on top of that problem. We wanted to build the thing that actually does the work, reads the RFQ, matches every line to the real catalog, prices it, and only pulls a human in when it is genuinely unsure.

What it does

Distill.ai turns a raw request for quote into a structured, priced quote.

You paste an email or upload a PDF. From there it runs a seven stage pipeline:

  1. Parse the incoming document into clean text.
  2. Extract the individual line items, quantities, and specs.
  3. Classify what kind of request this is.
  4. Match each line against the product catalog using vector search over embeddings, not keyword guessing.
  5. Price the matched lines from the pricing rules.
  6. Policy checks run against the draft.
  7. Score every match with a confidence value.

The important part is what happens when the model is not confident. Any line that scores below our 0.70 match threshold does not get silently quoted. It gets routed to a human review queue, flagged with the reason. A person confirms or corrects it, and the quote goes out clean. That human in the loop step is the difference between a demo and something a real sales desk would trust.

While all of this runs, the UI streams the live trace to you over server sent events, so you watch each stage light up in real time instead of staring at a spinner.

How we built it

The backend is a NestJS modular monolith in TypeScript. An API service takes the request and hands the heavy work to a background worker over a Redis and BullMQ queue, so a slow document never blocks the request. State lives in PostgreSQL with the pgvector extension, which is what powers the catalog matching. TypeORM handles the schema and migrations. The frontend is a React and Vite app served through Nginx.

The intelligence layer runs entirely on Alibaba Cloud's Qwen models through Model Studio. We use Qwen-Plus for the extraction and classification reasoning, and text-embedding-v4 for the 1024 dimension embeddings that drive catalog matching. We went through the OpenAI compatible endpoint, which kept our provider layer clean and let us swap models without rewriting the pipeline.

The whole stack is containerized with Docker and docker-compose: API, worker, client, Postgres, and Redis. We deployed it to a single Alibaba Cloud ECS instance in the Singapore region, running Ubuntu. We put Caddy in front for automatic HTTPS with a real Let's Encrypt certificate, and pointed a domain at it. We locked SSH down to our own IP and closed the raw API port to the outside, so the only public surface is the HTTPS front door.

Challenges we ran into

The deployment was where the real fighting happened.

Our first blocker was the production image itself. Our deploy step ran the database migrations with a command that quietly depended on dev only tooling. The production image ships compiled JavaScript and production dependencies only, no TypeScript sources and no ts-node, so the migration step failed the moment it hit the real server. We fixed it by running the TypeORM CLI against the compiled data source instead, which only needs packages that actually exist in the runtime image.

The 4GB instance kept running out of memory while building images on the box, so we added swap to get the builds through. The environment validation was strict about optional URLs and refused to boot when observability keys were left empty, so we stripped those from the production env. And because our branches are protected and none of us can self merge to a deploy branch, we brought the first release up by hand on the server, then wrote the pipeline fix back as a proper reviewed pull request.

None of these were glamorous. All of them were the actual work of getting an AI pipeline to run in production instead of on a laptop.

What we learned

Two things stuck with us.

First, confidence scoring is not a nice to have, it is the product. The moment we let the pipeline say "I am not sure about this line" and hand it to a person, the whole thing went from a party trick to something you could put in front of a customer.

Second, the gap between "it runs locally" and "it runs on Alibaba Cloud with a real certificate and a locked down box" is full of small, unforgiving details. Getting the Qwen extraction and catalog matching to fire correctly against the live deployment, and watching a real RFQ flow all the way to a priced quote, was the moment it felt real.

What's next

We want to close the loop on the outbound side, generating the quote PDF and the reply email draft automatically once the human signs off. We want to widen the catalog beyond the seeded fastener data and let a distributor bring their own product list and pricing rules. And we want to push the deployment onto managed Postgres and a proper container service so it scales past a single VM.

Built With

Share this project:

Updates

Submission history