Inspiration

Every B2B sales team starts the day on the same four questions: Who do we go after? Who do we talk to? Is now the right time? And what do we say? The usual tools answer them from a static database that was scraped months ago — so reps pitch a company that was just acquired, email a contact who left two quarters ago, and open with a "signal" that is no longer true. Prospects spot the stale template instantly.

I wanted to build the opposite: a go-to-market agent that does the research live, at the moment you ask, reasons about what it finds, and only writes outreach anchored to something that is actually true about the prospect this week. No pre-scraped lead list, no hardcoded company database — you hand it an Ideal Customer Profile, and it goes and finds the market in real time.

The result is Ideal Customer Finder: an AI-native prospecting agent that turns an ICP into a set of personalized outreach drafts awaiting human approval. It runs on NVIDIA Nemotron models via Nebius Token Factory for all reasoning and Tavily for all live web evidence — the two working as one loop, so every draft is grounded in something true about the prospect right now.

What it does

You start from an Ideal Customer Profile. Two come pre-seeded ("Series B Fintech Scaleup — $50M–$500M raised, 201–1000 employees" and "Enterprise SaaS — Series B+, 201–1000 employees"), or you can define your own on the ICPs page by describing your target customer in plain language to a Nemotron-powered chat builder that turns the conversation into a structured ICP.

Then, on the Prospect page, you pick an ICP and click Run Prospect Agent. In under a minute it runs ten steps end to end and hands back outreach drafts ready for review:

  1. Load ICP — reads the structured targeting criteria.
  2. Translate ICP → search parameters — Nemotron-Nano converts fuzzy criteria (industry, size band, geography, funding stage) into concrete, machine-executable search parameters.
  3. Source accounts — Tavily finds real, named companies that match, live.
  4. Persist and dedupe — writes them to a unified data model.
  5. Enrich contacts — Tavily finds the actual decision-makers at each account (CEO, CRO, VP Sales, President) and the agent ranks them by seniority. Contacts are identified by name and title (no email addresses are scraped, since LinkedIn does not publish them).
  6. Detect signals — Tavily scans each account for funding rounds, hiring surges, exec changes, and expansion news.
  7. Score accounts — Nemotron-Nano reasoning is blended with Qwen3-Embedding cosine similarity (via pgvector) to score each account against the ICP (0–1, tiered).
  8. Generate value hypotheses — Nemotron-Super reasons about why each account would care (shown as the Value Hypothesis for Prospect on each draft).
  9. Generate personalized outreach — Nemotron-Super drafts a subject and body per account, anchored to a specific, real signal and the specific contact.
  10. Human-in-the-loop handoff — on the Outreach Approval page every draft sits as Awaiting Approval; no email is sent until a human reviews and approves it. Rejecting a draft with feedback (e.g. "lead with the funding signal, keep it shorter") sends it back to Nemotron-Super, which rewrites the email around that feedback and returns a new draft linked to the original — so review is a feedback loop, not just a gate.

The results populate the Accounts, Signals, and Outreach pages, and every step of the run is streamed to the Agent Traces view — each tool call, each model call, token counts, and timings in real time, so the whole run is glass-box, not a black box.

How I built it

The models — NVIDIA Nemotron on Nebius Token Factory. The pipeline is a deliberate two-tier model strategy served entirely through Nebius Token Factory's OpenAI-compatible endpoint:

  • Nemotron Nano handles the fast, high-frequency, structured calls — ICP→search-parameter translation and per-account ICP fit scoring. These are JSON-in/JSON-out tasks where latency and cost matter, and Nano keeps the run responsive.
  • Nemotron Super handles the heavy lifting — the value hypotheses and the outreach drafting, where quality of argument and grounding matter most.

Routing the cheap, frequent calls to Nano and reserving Super for the reasoning-heavy steps is exactly the balance the agent needs: measured on live runs, a full pipeline completes in under a minute for under a cent (~15k tokens). I run Nemotron with thinking disabled by default: I measured it ~3–4× faster and, critically, it kept the final JSON inside each call's token budget (with thinking on, the 120B overran max_tokens and truncated the JSON). The prompts already enforce JSON-only, fact-grounded output, so the thinking phase was pure overhead for this workload — output quality did not degrade.

The embeddings — Qwen3‑Embedding‑8B on Token Factory. Account and ICP fit isn't left to Nemotron alone. Each ICP and each sourced account is embedded with Qwen3‑Embedding‑8B (also via Token Factory), and the scoring step blends Nemotron's judgment with Qwen3‑Embedding cosine similarity (via pgvector) over those vectors. The 4096‑dim output is Matryoshka‑truncated to 4000 and re‑normalized to fit pgvector's halfvec HNSW cap, verified to preserve ranking.

The live intelligence — Tavily. Every piece of real-world evidence — the companies, the contacts, the signals — comes from Tavily at request time, via its Search and Extract APIs.

The stack. FastAPI backbone, a Celery worker for the agent runs, Postgres + pgvector for the unified data model and vector scoring, a Next.js frontend, all behind nginx in a single container. The agent itself is a node graph where each node reads state, calls Tavily or Nemotron, updates state, and logs a trace.

How I used Tavily (this is the heart of the project)

Ideal Customer Finder has no lead database. Tavily IS the data layer. There is no step of the pipeline that touches a canned list — accounts, contacts, and signals are all discovered live through Tavily's Search and Extract APIs. Getting that to produce real, named, prospectable companies instead of generic junk took real engineering, and every decision below was tested by me against two live seeded ICPs:

1. Sourcing real companies, not category pages. Tavily doesn't support Google's site: operator, so I engineered the query shape instead. What separates real company pages from generic junk is the employee-count band written as a quoted phrase ("201-500 employees") — that exact text appears on real LinkedIn company profiles but never on category pages. I pair it with include_domains=["linkedin.com"] and linkedin.com/company as a literal phrase to steer ranking toward profiles. Without the size phrase, queries came back with placeholder pages literally named "SaaS Company" and "Fintech Company"; with it, I got real, named companies. I learned to keep the industry term unquoted — quoting it collapsed the results to a single company literally named "Fintech." And I treat exact_match as an optional refinement, not a dependency — in testing it sometimes returned zero results for the same query. The real work is done by the domain filter and the phrasing.

2. A counterintuitive scoring insight. For this query shape, Tavily assigns genuinely-relevant real companies low absolute relevance scores (~0.1–0.3), while generic placeholder pages score as high as ~0.86 because their name is a verbatim keyword match. I learned to keep the pre-extract score threshold low (0.05) — a "sensible" 0.5 cutoff discarded every real company and kept only a page named "Fintech." I also added deterministic filters that derive from the ICP's own industry term (dropping pages whose name is the keyword, and unambiguous event/media brands like Money20/20 identified by their self-declared LinkedIn "Industry: Events Services" line), while deliberately leaving borderline judgment to the downstream Nemotron score.

3. Finding the actual decision-makers. I anchor contact search on the company name, not the domain — because the domain is often a wrong last-resort guess (e.g. superdotcom.com for Super.com), and quoting a wrong domain returned 0 profiles for 4 of 5 accounts. I add linkedin.com/in as a literal phrase to steer toward profile pages whose "Name - Title - Company | LinkedIn" title my parser depends on, and I request Tavily's max max_results=20 (a smaller page can fill entirely with non-profile LinkedIn URLs). The biggest lesson: I do not put a persona filter in the query. A "CEO" OR "CRO" OR ... group matches page body content, but LinkedIn is login-walled so titles are often absent from the searchable body — the filter suppressed the exact people I wanted (Imprint returned two CEOs with no filter, zero with it). Instead I query broadly, then classify each profile's seniority from its title and keep the most senior. The fix recovered accounts that had previously returned zero contacts (e.g. Imprint, which the persona filter had been suppressing) — in the seeded demo runs, all 5 sourced accounts come back with decision-maker contacts.

4. Signal detection tuned per signal type. Funding signals use topic="general" (news-only filtering excluded Crunchbase/PitchBook/TechCrunch where funding history actually lives), while time-sensitive hiring/exec/expansion signals use topic="news". Following the pattern of Tavily's own get_company_info() SDK method, the funding query fires general, news, and finance topics concurrently via asyncio and merges by score, surfacing finance-specific sources neither alone would return. Every signal query tries time_range="month" and falls back to "year" when a quieter company returns too little. And I detect generic single-word domains (super, unlimit) and fall back to the full quoted domain so the query doesn't match unrelated results.

5. Agent-run session grouping. Per Tavily's multi-step-agent best practices, every request in a single run carries a consistent X-Session-Id (the agent run ID), grouping all of a pipeline's calls together.

6. Extract for firmographics — and being honest about where it can't help. Beyond Search, I call Tavily's Extract API on each confirmed LinkedIn company URL with a relevance query ("company industry website headquarters founded employees") so Extract reranks the page's chunks toward the firmographic facts I actually need. This is where I hit — and respect — a real limit: LinkedIn is login-walled, so the public page Tavily sees hides the structured Website/HQ fields. Rather than pretend otherwise, I parse what the page genuinely exposes and fall back to the ICP-sourced size band when a field is absent, tagging the provenance so a reviewer can tell a parsed fact from an inferred one. Knowing what a source can't give you is as much a part of using Tavily well as the queries that work.

7. Cleaning Tavily's chunked output for two different consumers. When you request chunks (chunks_per_source plus a query), Tavily returns only the most relevant snippets joined by its [...] separator — marking where it skipped less-relevant text. I normalize that separator into a paragraph break before storing, so the same content reads correctly for both consumers: Nemotron sees clean prose instead of a literal [...], and the account/signal views render cleanly in the UI.

8. Making the Tavily layer resilient, not just correct. Live web search has operational edges, and I engineered for them. Every Tavily call is wrapped in a hard per-call deadline (via asyncio.wait_for) so a single slow response can't stall a pipeline that fans many searches out concurrently — I added this after a run where one /search took 54s and froze the whole signal batch. And because Tavily occasionally returns an empty result set for an account query that normally returns results, the account search retries once on empty — turning an occasional zero-account run into a normal one. The happy path is untouched; the guards only fire on the edges.

The payoff: each outreach draft cites real, Tavily-sourced facts — exact funding rounds, named partners, actual hiring moves — because the entire chain from "who" to "why now" to "what to say" is grounded in live Tavily evidence, not a template. A human sends from the approved draft.

How Tavily and Nemotron work together

Tavily and Nemotron are not two features bolted onto the same app. They form one closed perception→reasoning→action loop, and neither works without the other. Nemotron is the reasoning; Tavily is the senses. Every Nemotron call is grounded in Tavily output, and several Tavily calls are shaped by Nemotron output:

  1. Nemotron turns the ICP into a Tavily query. The ICP is fuzzy human criteria ("Series B fintech, 201–1000 employees"). Nemotron‑Nano translates it into the concrete parameters the Tavily account search is built from — the industry term, the exact-match employee-size band, the geography. So Tavily's first query is authored by Nemotron, not hardcoded.

  2. Tavily sources the accounts; Nemotron and the embeddings judge them. Tavily returns real companies with firmographics parsed from its Search/Extract snippets. Those go to the scoring step, where Nemotron‑Nano reasons about ICP fit and Qwen3‑Embedding vectors scored by pgvector cosine similarity capture semantic match — the two are blended. This is deliberate division of labor: Tavily supplies ground truth, Nemotron and Qwen3-Embedding supply judgment. Tavily's own relevance score is explicitly not trusted for fit (I found a placeholder page scores 0.86 while real companies score ~0.1); fit is decided by Nemotron and the embeddings over Tavily's extracted facts.

  3. Each account's real name drives the next Tavily call. The contact and signal searches are anchored on the account name/domain that came out of step 2 — so Tavily is called again, per account, with inputs derived from earlier Tavily results. The pipeline fans Tavily out across accounts (contacts and multi-topic signal queries run concurrently).

  4. Tavily's signals become Nemotron's argument. The funding, hiring, and exec signals Tavily surfaces are handed to Nemotron‑Super, which writes the value hypothesis and the outreach draft around one specific real signal. A named funding round Tavily found becomes the opening line's "why now." Nemotron is forbidden by prompt from inventing facts, so every claim in the draft traces back to a Tavily result. When Tavily finds too little for a quiet company, Nemotron‑Nano can infer a soft signal from the profile, and the UI flags it as AI-inferred so the reviewer can tell evidence-backed signals from model-inferred ones.

In short: remove Tavily and Nemotron has nothing true to reason about; remove Nemotron and Tavily's raw results never become a targeted query or a grounded draft. What makes the agent work isn't either piece alone — it's how carefully the Tavily layer is tuned to return real prospects, and how tightly Nemotron's reasoning is wired to what Tavily returns.

Two real runs (measured from live logs)

Series B Fintech Scaleup — Tavily sourced 5 named companies, enriched ~20 decision-maker contacts, and surfaced 60+ company-specific buying signals (keeping those while its filters dropped ~100 off-topic/junk results); Nemotron scored all 5, wrote 5 value hypotheses and 5 outreach drafts — in under a minute, for under a cent.

Enterprise SaaS (Series B+) — Tavily sourced 5 companies, enriched ~20 contacts, surfaced 40+ company-specific signals (dropping ~140 junk results); Nemotron produced 5 scored accounts and 5 drafts, again in under a minute for under a cent.

Every account, contact, and signal in those runs came from Tavily at request time — all of those numbers are zero without Tavily, and the signals never become a sentence a prospect would read without Nemotron. The dropped-junk figures (~100 and ~140 results discarded) are the Tavily query-and-filter engineering doing its job: the agent keeps only what is genuinely about the target company.

Challenges I ran into

  • Making Tavily return prospects, not noise. The single hardest problem was coaxing Tavily into reliably returning real named companies and real decision-makers from a login-walled source like LinkedIn. Every fix above came from a real failure in a run — placeholder "Fintech" pages, zero-contact accounts, persona filters that hid the CEOs. I solved it by treating relevance score, query phrasing, and post-filtering as a tuned system and verifying each change against live ICPs.
  • Keeping Nemotron grounded and inside budget. Nemotron's thinking phase initially overran max_tokens and truncated my JSON; disabling thinking made runs ~3–4× faster and more reliable, with no loss of grounding because the prompts enforce fact-anchored JSON.
  • Blending two notions of "fit." Pure Nemotron scoring and pure embedding similarity each have blind spots, so scoring combines Nemotron's reasoning with Qwen3‑Embedding cosine similarity (via pgvector) — which meant fitting Qwen3's 4096‑dim vectors into pgvector's indexable limit (truncating to 4000 dims) without hurting ranking quality.

Accomplishments that I'm proud of

  • A prospecting agent with zero canned data — every account, contact, and signal is discovered live through Tavily at request time.
  • Outreach that is genuinely grounded: Nemotron anchors each draft to a specific real buying signal and a specific real decision-maker, not a template.
  • A disciplined, measured Nemotron two-tier model strategy that keeps a full run under a cent and under a minute.
  • A fully glass-box agent — every tool call, model call, token count, and timing is streamed to an Agent Traces view.
  • Tavily tuning that turned "generic search junk" into "real named prospects" and is documented decision-by-decision, each verified against live ICPs.

What I learned

  • Tavily's relevance score is not a universal "quality" signal — for a login-walled, exact-phrase query shape, the real answers can score low and the junk can score high. You have to characterize Tavily's behavior for your query, not trust defaults.
  • Query phrasing (what you quote, what you leave unquoted, what literal phrases you include) is as important as any Tavily filter parameter.
  • With Nemotron, "more thinking" isn't free — disabling the thinking phase was faster, cheaper, and more reliable for structured JSON tasks.
  • Right-sizing models per task (Nano for frequent structured calls, Super for heavy reasoning) is what keeps output quality high while holding cost and latency down.

What's next for Ideal Customer Finder

  • Wire the approved drafts to real send + reply tracking, closing the loop from ICP to booked meeting.
  • Let the agent learn from which drafts get approved vs. edited, feeding that back into the hypothesis and outreach prompts.
  • Expand signal types (product launches, tech-stack changes, leadership hires) and let users define custom signals per ICP.
  • Multi-ICP campaigns and scheduled re-runs so the market is continuously re-scanned as new signals appear.

Built With

Share this project:

Updates

Submission history