Skip to main content
Tools like NotebookLM changed what people expect from a pile of research. You add sources, you ask questions and get answers that point back at the material, and then you generate a conversation between two hosts that you can listen to on a walk. This guide builds that, in about two hundred lines of Python, on five Venice endpoints. Nothing is stored outside your machine except the requests themselves, and Venice does not retain those.

Run this notebook in Google Colab

Every step below as an executable notebook, with the overview playing inline. Nothing to install.

How It Works

Five endpoints, each doing one job: The retrieval here is deliberately plain: vectors in a Python list, cosine similarity in a loop. That is the right amount of machinery for a few dozen sources and it keeps the moving parts visible. When you outgrow it, Building a Private RAG Bot covers the same pipeline with a real vector database and a re-ranking pass.

Setting Up

One dependency, and a key from the API settings page.
Create notebook.py and start with the imports and configuration. The two lists at the bottom are the whole state of the notebook: sources records what you added, and chunks holds the searchable pieces.
HOSTS maps a host name to a voice. Both voices come from tts-xai-v1, and that matters: voices belong to models, and sending a voice from one family to a model from another is the most common first mistake with the speech endpoint.

Choosing a Model That Will Not Go Stale

Hardcoding a chat model into a project guarantees the project ages. Venice publishes which model currently holds each role through /models/traits, so you can ask for the current default instead of naming one.
Other traits are available if this notebook is not the shape you want. most_intelligent buys you a stronger model for the reasoning-heavy summarization, and default_reasoning gives you one that thinks in the open. See Models for the full list.

Adding Sources

A source is either a URL or a file on disk, and Venice has an endpoint for each. Both return plain text, which is the point: the rest of the notebook does not care where a source came from.
/augment/scrape returns Markdown rather than raw HTML, so there is no boilerplate stripping to write. /augment/text-parser accepts PDF, Word, Excel, and plain text up to 25 MB, and reports a token count alongside the text. Document Processing covers its options in full.

Chunking and Embedding

Embedding a whole document produces one vector that is an average of everything it says, which is too blunt to retrieve a specific claim. Splitting it produces vectors that each mean something. Split on paragraph boundaries rather than a fixed character count. A chunk that stops mid-sentence retrieves badly, because the embedding is of a fragment.
embed batches because the endpoint takes a list, and one request for sixty-four chunks is far cheaper in wall time than sixty-four requests. text-embedding-bge-m3 returns 1024 dimensions and handles multilingual sources well. Adding a source is now read, split, embed, and record. The magnitude of each vector gets stored alongside it, because it never changes and recomputing it inside the similarity loop is wasted work.
The number is what makes citation possible later. Every chunk remembers which source it came from, so an answer can point back at it.

Retrieving the Right Passages

Cosine similarity between the question vector and every chunk vector, sorted, top k. For a few thousand chunks this runs faster than the network call that produced the question vector.

Answering with Citations

The difference between a grounded answer and a confident guess is entirely in the prompt. Two instructions do the work: answer only from the notes, and say so when the notes fall short. Without the second one a model will quietly fill the gap from memory, which is the failure mode you are trying to design out. Numbering the notes in the prompt gives the model a citation vocabulary. It writes [2], and you can resolve that back to a source.
Parsing the brackets back out is worth the one line. It tells you which sources actually carried the answer, which is how you notice that a source you thought was central never gets cited.

Writing the Overview Script

Here is where the notebook stops being a search box. A summary is something you read; an overview is something you listen to, and the two want different prose. Dialogue works better in audio because the turn-taking does the pacing for you, and a question from one host is a natural way to introduce the next idea. Three constraints matter, and all three come from the audio rather than the text:
  • No markdown, no URLs. A speech model reads https://docs.venice.ai one character at a time.
  • Spell out abbreviations. T E E the first time, not tee.
  • Vary turn length. Evenly sized turns sound like two people reading a list at each other.
Asking for JSON with a schema is what makes the result renderable. Free-form text would need parsing, and speaker labels are exactly the thing a model gets creative about. The enum on speaker means every turn maps to a voice you have.
The overview covers the sources broadly rather than answering one question, so spread samples chunks across the whole collection instead of retrieving by similarity. Taking every nth chunk is crude and works well: it reaches the end of long documents, which taking the first twelve never would. Treat the turn count as a hint rather than an instruction. Asking for sixteen has produced anywhere from sixteen to twenty-eight here, depending on how much the sources have to say. If you need a hard ceiling, truncate turns before rendering rather than arguing with the prompt.

Rendering Two Voices into One Track

Each turn becomes one speech request, with the voice chosen by who is speaking.
Reading the frames out of each clip, rather than saving twenty files and stitching them afterwards, is what keeps the join clean. Concatenating encoded audio such as MP3 does not work reliably, because every file carries its own headers. Decoded frames are just samples, so joining them is appending bytes. Two details make the result sound intentional. The header for the output comes from the first clip rather than from constants, so the sample rate is always right for whichever model you chose. And a quarter second of silence between turns gives the ear a beat to register that the speaker changed. Without it the hosts talk over each other’s endings.
pool.map preserves input order, so the turns come back in the order they were written no matter which finishes first. Four workers is a deliberate ceiling rather than a maximum: more concurrency will start returning 429s on lower tiers, and the job is already dominated by the longest single turn.

Running It

Ingesting and answering takes a few seconds. The audio is the slow part, and it varies with load: about six minutes of speech takes anywhere from half a minute to three minutes to render.

Making It Yours

The sources are the whole game. Everything downstream is bounded by what you put in. Scraped pages bring their navigation and footers along, which is harmless for answering but shows up in an overview as a host earnestly discussing a documentation index. If that happens, drop chunks below a length threshold or filter obvious furniture before embedding. Swap the voices. HOSTS is two entries in a dictionary. tts-xai-v1 ships twenty-six voices, and other families have their own; GET /models?type=tts lists voices per model. Two voices that contrast clearly are easier to follow than two that are merely different. Clone your own. Voice Cloning turns a short sample into a voice handle you can drop straight into HOSTS. Add a third participant. Nothing in the pipeline assumes two speakers except the schema enum. Adding an interviewer who only asks questions changes the feel considerably. Keep the script. Writing turns to a JSON file next to the audio costs two lines and saves a re-render every time you want to tweak one sentence.

Where to Go Next

Private RAG Bot

The same retrieval pipeline with a real vector database and re-ranking.

Cited Answers with Web Search

Find the sources automatically instead of naming them yourself.

Text-to-Speech

Reference for the speech endpoint, its voices, and streaming.

Document Processing

Everything the text parser accepts, and what it returns.