<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mark McDonald</title>
    <description>The latest articles on DEV Community by Mark McDonald (@macd0).</description>
    <link>https://dev.to/macd0</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3598661%2F072b666d-3973-4f79-9565-02fca9092eae.png</url>
      <title>DEV Community: Mark McDonald</title>
      <link>https://dev.to/macd0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/macd0"/>
    <language>en</language>
    <item>
      <title>Closing the knowledge gap with agent skills</title>
      <dc:creator>Mark McDonald</dc:creator>
      <pubDate>Tue, 31 Mar 2026 04:46:52 +0000</pubDate>
      <link>https://dev.to/macd0/closing-the-knowledge-gap-with-agent-skills-50c6</link>
      <guid>https://dev.to/macd0/closing-the-knowledge-gap-with-agent-skills-50c6</guid>
      <description>&lt;p&gt;Large language models (LLMs) have fixed knowledge, being trained at a specific point in time. Software engineering practices are fast paced and change often, where new libraries are launched every day and best practices evolve quickly.&lt;/p&gt;

&lt;p&gt;This leaves a knowledge gap that language models can't solve on their own. At Google DeepMind we see this in a few ways: our models don't know about themselves when they're trained, and they aren't necessarily aware of subtle changes in best practices (like &lt;a href="https://ai.google.dev/gemini-api/docs/thinking#signatures" rel="noopener noreferrer"&gt;thought circulation&lt;/a&gt;) or SDK changes.&lt;/p&gt;

&lt;p&gt;Many solutions exist, from web search tools to dedicated MCP services, but more recently, &lt;a href="https://agentskills.io/" rel="noopener noreferrer"&gt;agent skills&lt;/a&gt; have surfaced as an extremely lightweight but potentially effective way to close this gap.&lt;/p&gt;

&lt;p&gt;While there are strategies that we, as model builders, can implement, we wanted to explore what is possible for any SDK maintainer. Read on for what we did to build the &lt;a href="https://github.com/google-gemini/gemini-skills/" rel="noopener noreferrer"&gt;Gemini API developer skill&lt;/a&gt; and the results it had on performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we built
&lt;/h2&gt;

&lt;p&gt;To help coding agents building with the Gemini API, we built a skill that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;explains the high-level feature set of the API,
&lt;/li&gt;
&lt;li&gt;describes the current models and SDKs for each language,
&lt;/li&gt;
&lt;li&gt;demonstrates basic sample code for each SDK, and
&lt;/li&gt;
&lt;li&gt;lists the documentation entry points (as sources of truth).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a basic set of primitive instructions that guide an agent towards using our latest models and SDKs, but importantly also refers to the documentation to encourage retrieving fresh information from the source of truth.&lt;/p&gt;

&lt;p&gt;The skill is available on &lt;a href="https://github.com/google-gemini/gemini-skills/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; or install it directly into your project with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install with Vercel skills&lt;/span&gt;
npx skills add google-gemini/gemini-skills &lt;span class="nt"&gt;--skill&lt;/span&gt; gemini-api-dev &lt;span class="nt"&gt;--global&lt;/span&gt;

&lt;span class="c"&gt;# Install with Context7 skills&lt;/span&gt;
npx ctx7 skills &lt;span class="nb"&gt;install&lt;/span&gt; /google-gemini/gemini-skills gemini-api-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Skill tester
&lt;/h2&gt;

&lt;p&gt;We created an evaluation harness with 117 prompts that generate Python or TypeScript code using the Gemini SDKs that are used to evaluate skill performance.&lt;/p&gt;

&lt;p&gt;The prompts evaluate across different categories, including agentic coding tasks, building chatbots, document processing, streaming content and a number of specific SDK features.&lt;/p&gt;

&lt;p&gt;We ran these tests both in "vanilla" mode (directly prompting the model) and with the skill enabled. To enable the skill, the model is given the &lt;a href="https://github.com/google-gemini/gemini-cli/blob/bb7bb11736c363f3368a61f4bc4557ab8bb660a2/packages/core/src/prompts/snippets.ts#L213-L233" rel="noopener noreferrer"&gt;same system instruction&lt;/a&gt; that the Gemini CLI uses, and two tools: &lt;code&gt;activate_skill&lt;/code&gt; and &lt;code&gt;fetch_url&lt;/code&gt; (for downloading the docs).&lt;/p&gt;

&lt;p&gt;A prompt is considered a failure if it uses one of our old SDKs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skills work, but they need reasoning
&lt;/h3&gt;

&lt;p&gt;The top-line results:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;Vanilla&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;gemini-3.1-pro-preview&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;96.6%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;28.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;gemini-3-flash-preview&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;87.2%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;gemini-3.1-flash-lite-preview&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;84.6%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemini-2.5-flash&lt;/td&gt;
&lt;td&gt;52.1%&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemini-2.5-pro&lt;/td&gt;
&lt;td&gt;24.8%&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemini-2.5-flash-lite&lt;/td&gt;
&lt;td&gt;17.1%&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;The latest Gemini 3 series of models achieve excellent results with the addition of the &lt;code&gt;gemini-api-dev&lt;/code&gt; skill, notably coming from a low baseline without it (6.8% for both 3.0 Pro and Flash, 28% for 3.1 Pro).
&lt;/li&gt;
&lt;li&gt;The older 2.5 series of models also benefit, but nowhere near as much. Using modern models with strong reasoning support makes a difference.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  All categories performed well
&lt;/h3&gt;

&lt;p&gt;Adding the skill was effective across almost all domains for the top-performing model (&lt;code&gt;gemini-3.1-pro-preview&lt;/code&gt;).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;Vanilla&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agentic&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;14.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Chat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;96.3%&lt;/td&gt;
&lt;td&gt;25.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Document processing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SDK usage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;94.6%&lt;/td&gt;
&lt;td&gt;35.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Image generation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;SDK Usage&lt;/em&gt; had the lowest pass rate, at 95%. There is no stand-out reason for this; the failed prompts cover a range of tasks that include some difficult or unclear requests, but notably they include prompts that explicitly request Gemini 2.0 models.&lt;/p&gt;

&lt;p&gt;Here's an example from the &lt;em&gt;SDK usage&lt;/em&gt; category that failed across all models.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;When I use the Python api with the gemini 2.0 flash model, and when the output is quite long, the returned content will be an array of output chunks instead of the whole thing. i guess it was doing some kind of streaming type of input. how to turn this off and get the whole output together
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Skill issues
&lt;/h2&gt;

&lt;p&gt;These initial results are quite encouraging, but we know from &lt;a href="https://vercel.com/blog/agents-md-outperforms-skills-in-our-agent-evals" rel="noopener noreferrer"&gt;Vercel's work&lt;/a&gt; that direct instruction through &lt;code&gt;AGENTS.md&lt;/code&gt; can be more effective than using skills, so we are exploring other ways to supply live knowledge of SDKs, such as directly using MCPs for documentation.&lt;/p&gt;

&lt;p&gt;Skill simplicity is a huge benefit, but right now there isn't a great skill update story, other than requiring users to update manually. In the long term this could leave old skill information in user's workspaces, doing more harm than good.&lt;/p&gt;

&lt;p&gt;Despite these minor issues we’re still excited to start using skills in our workflows. The Gemini API skill is still fairly new, but we’re keeping it maintained as we push model updates, and we will be exploring different avenues for improving it. Follow &lt;a href="https://x.com/m4rkmc" rel="noopener noreferrer"&gt;Mark&lt;/a&gt; and &lt;a href="https://x.com/_philschmid" rel="noopener noreferrer"&gt;Phil&lt;/a&gt; for updates as we tune the skill, and don’t forget to &lt;a href="https://github.com/google-gemini/gemini-skills/" rel="noopener noreferrer"&gt;try it out&lt;/a&gt; and &lt;a href="https://github.com/google-gemini/gemini-skills/issues" rel="noopener noreferrer"&gt;let us know&lt;/a&gt; your feedback!&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>llm</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Mark McDonald</dc:creator>
      <pubDate>Fri, 28 Nov 2025 03:19:26 +0000</pubDate>
      <link>https://dev.to/macd0/-40d7</link>
      <guid>https://dev.to/macd0/-40d7</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/googleai" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F11026%2F386b14d3-cc9a-4270-aba0-3e41cdfb9d85.jpg" alt="Google AI" width="400" height="400"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3623117%2Ff8bd55b3-1b3d-4c3f-a763-db2cab3cc02d.jpeg" alt="" width="391" height="391"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/googleai/nano-banana-pro-prompting-guide-strategies-1h9n" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Nano-Banana Pro: Prompting Guide &amp;amp; Strategies&lt;/h2&gt;
      &lt;h3&gt;Guillaume Vernade for Google AI ・ Nov 27&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#ai&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#gemini&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#nanobanana&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#genmedia&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>ai</category>
      <category>gemini</category>
      <category>nanobanana</category>
      <category>genmedia</category>
    </item>
    <item>
      <title>Tutorial: Building a Podcast Knowledge Base with the Gemini File Search Tool</title>
      <dc:creator>Mark McDonald</dc:creator>
      <pubDate>Wed, 12 Nov 2025 22:05:00 +0000</pubDate>
      <link>https://dev.to/googleai/building-a-podcast-ai-with-the-gemini-file-search-tool-1d4l</link>
      <guid>https://dev.to/googleai/building-a-podcast-ai-with-the-gemini-file-search-tool-1d4l</guid>
      <description>&lt;p&gt;Recently we launched a new &lt;a href="https://ai.google.dev/gemini-api/docs/file-search" rel="noopener noreferrer"&gt;&lt;strong&gt;File Search Tool&lt;/strong&gt;&lt;/a&gt;, a fully managed RAG system built directly into the Gemini API that abstracts away the retrieval pipeline so you can focus on building. For all of the details, check out the &lt;a href="https://blog.google/technology/developers/file-search-gemini-api/" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;, or read on for a tutorial &lt;strong&gt;from &lt;a href="http://linktree.com/markmcd" rel="noopener noreferrer"&gt;Mark McDonald&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine effortlessly recalling specific details from your favorite podcasts or getting a quick recap when returning to a long series' storyline. An AI conversation, equipped with the full context of your podcasts, makes these easy.&lt;/p&gt;

&lt;p&gt;In this tutorial, we'll build a tool for this exact case. We'll create a Python app that ingests a podcast RSS feed, transcribes the episodes, and indexes them using the &lt;a href="https://ai.google.dev/gemini-api/docs/file-search" rel="noopener noreferrer"&gt;&lt;strong&gt;File Search Tool&lt;/strong&gt;&lt;/a&gt;. This allows us to ask natural language questions and get answers based on the actual content of the podcasts, complete with citations pointing back to the specific episodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview of the Solution
&lt;/h2&gt;

&lt;p&gt;The application consists of two main parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ingestion (&lt;code&gt;ingest.py&lt;/code&gt;):&lt;/strong&gt; Downloads episodes, transcribes them, and uploads the transcripts to a File Search Store.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Querying (&lt;code&gt;query.py&lt;/code&gt;):&lt;/strong&gt; Takes a user question, searches the File Search Store, and generates an answer using Gemini.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 1: Create a file search store
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;File Search Store&lt;/strong&gt; is the container for scoping your documents. In this example, we are using a single store for all of our podcasts so we can search across them all at once.&lt;/p&gt;

&lt;p&gt;First, get set up with the &lt;a href="https://ai.google.dev/gemini-api/docs/libraries#python" rel="noopener noreferrer"&gt;Python SDK&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.genai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To create a new store, we use &lt;a href="https://github.com/markmcd/podcast-search/blob/100c4208c99779a0fcfc83f8e08c65d6d3dccb25/ingest.py#L70-L74" rel="noopener noreferrer"&gt;&lt;code&gt;client.file_search_stores.create&lt;/code&gt;&lt;/a&gt;. We’ll use the optional display name to identify our podcast index.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file_search_stores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;display_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;My Podcast Store&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 2: Transcribe episodes
&lt;/h2&gt;

&lt;p&gt;To index the content, we need to turn the audio into text. We download the audio file and then use the &lt;strong&gt;Gemini 2.5 Flash-Lite&lt;/strong&gt; model to transcribe it. We use Flash-Lite as it's extremely fast and cost-effective for this task.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;ingest.py&lt;/code&gt;, the &lt;a href="https://github.com/markmcd/podcast-search/blob/100c4208c99779a0fcfc83f8e08c65d6d3dccb25/ingest.py#L40" rel="noopener noreferrer"&gt;&lt;code&gt;transcribe_audio&lt;/code&gt;&lt;/a&gt; function handles this, and you can add any prompt direction to Gemini to help manage the quality of your transcripts, e.g. to skip the intro or label the speakers.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gemini-2.5-flash-lite&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;file_uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;audio_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;mime_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;audio_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mime_type&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Transcribe this audio. Output only the transcription. Label the speakers. Do not include any obvious ad-reads or promotional segments in the transcription (if unsure, leave them in).&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 3: Upload transcripts with metadata
&lt;/h2&gt;

&lt;p&gt;Once we have the transcript, we can upload it to our store. A powerful feature of the File Search tool is that you can provide &lt;strong&gt;custom metadata&lt;/strong&gt; that can be used to filter at generation-time, allowing us to restrict the source data to a specific podcast or date range.&lt;/p&gt;

&lt;p&gt;To upload a file, we use &lt;a href="https://github.com/markmcd/podcast-search/blob/100c4208c99779a0fcfc83f8e08c65d6d3dccb25/ingest.py#L146-L153" rel="noopener noreferrer"&gt;&lt;code&gt;client.file_search_stores.upload_to_file_search_store&lt;/code&gt;&lt;/a&gt;. This handles uploading the file content and attaches the custom metadata in the same call.&lt;/p&gt;

&lt;p&gt;Here's a sample of how we prepare the metadata and upload the file in &lt;code&gt;ingest.py&lt;/code&gt;. The full code adds a number of other fields.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;string_value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;podcast&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;string_value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;feed_info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Bring any tags from the feed itself
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tags&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ep&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tag&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;string_value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file_search_stores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload_to_file_search_store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;file_search_store_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;store_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;transcript_filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;custom_metadata&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;display_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 4: Query the store
&lt;/h2&gt;

&lt;p&gt;Now for the fun part: asking questions!&lt;/p&gt;

&lt;p&gt;To enable file search in a generation request, we pass the &lt;code&gt;FileSearch&lt;/code&gt; tool that defines the file store to search, along with any filtering we need.&lt;/p&gt;

&lt;p&gt;From &lt;a href="https://github.com/markmcd/podcast-search/blob/100c4208c99779a0fcfc83f8e08c65d6d3dccb25/query.py#L44-L57" rel="noopener noreferrer"&gt;&lt;code&gt;query.py&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;podcast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;metadata_filter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;podcast = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;podcast&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;file_search&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FileSearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;file_search_store_names&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;metadata_filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;metadata_filter&lt;/span&gt;  &lt;span class="c1"&gt;# Optional filter
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_search&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;file_search&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gemini-2.5-flash&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GenerateContentConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When we call &lt;code&gt;client.models.generate_content&lt;/code&gt; with this tool, Gemini will automatically search our store for relevant information to answer the user's question.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 5: Display results and citations
&lt;/h2&gt;

&lt;p&gt;The response from Gemini includes not just the answer, but also &lt;strong&gt;citations&lt;/strong&gt; showing exactly which parts of the uploaded files were used.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Answer:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Citations:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;grounding_metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grounding_chunks&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retrieved_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retrieved_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown Episode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Citation &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Episode: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Text: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retrieved_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This allows users to verify the answer and explore the source material further.&lt;/p&gt;
&lt;h2&gt;
  
  
  Run the application
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ingest a podcast:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python ingest.py &lt;span class="s2"&gt;"https://feeds.example.com/podcast.rss"&lt;/span&gt; &lt;span class="nt"&gt;--limit&lt;/span&gt; 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This will download the last 5 episodes, transcribe them, and upload them to the "Podcasts" store.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ask a question:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python query.py &lt;span class="s2"&gt;"Why are red delicious apples so bad?"&lt;/span&gt; &lt;span class="nt"&gt;--podcast&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Gemini will retrieve relevant chunks from the indexed transcripts, pass them in the input context with the query, and provide an answer with citations.&lt;/p&gt;
&lt;h2&gt;
  
  
  What next?
&lt;/h2&gt;

&lt;p&gt;By using the Gemini File Search API, we've turned a collection of audio files into a rich, searchable knowledge base. We didn't have to worry about chunking, embedding, or setting up a vector database - the API handled it all. With the addition of metadata, we built a powerful search tool with minimal code.&lt;/p&gt;

&lt;p&gt;For more content, check out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ai.google.dev/gemini-api/docs/file-search" rel="noopener noreferrer"&gt;&lt;strong&gt;Official Documentation&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;:&lt;/strong&gt; Dive deeper into the File Search API capabilities.
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aistudio.google.com/apps/bundled/ask_the_manual" rel="noopener noreferrer"&gt;&lt;strong&gt;Demo Applet&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;:&lt;/strong&gt; Try out a live demo of a similar application in Google AI Studio (or vibe-code your own!).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article was brought to you by &lt;a href="http://linktree.com/markmcd" rel="noopener noreferrer"&gt;Mark McDonald&lt;/a&gt;. Grab the code here:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/markmcd" rel="noopener noreferrer"&gt;
        markmcd
      &lt;/a&gt; / &lt;a href="https://github.com/markmcd/podcast-search" rel="noopener noreferrer"&gt;
        podcast-search
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Index and search podcasts using the Gemini API's File Search tools
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Podcast Search with Gemini File Search API&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;This project is a simple command-line tool that demonstrates how to build a searchable podcast knowledge base using the &lt;a href="https://ai.google.dev/gemini-api/docs/file-search" rel="nofollow noopener noreferrer"&gt;Gemini File Search API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It consists of two main scripts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ingest.py&lt;/code&gt;: Ingests a podcast RSS feed, downloads audio, transcribes episodes using the fast Gemini 2.5 Flash-Lite model, and uploads them to a File Search Store.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;query.py&lt;/code&gt;: Allows you to ask natural language questions about the ingested podcast content and receive grounded answers with citations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br&gt;
  &amp;lt;div class="js-render-enrichment-target" data-json='{"data":"---\nconfig:\n  themeVariables:\n    fontFamily: monospace\n    fontSize: 12px\n  flowchart:\n    wrappingWidth: 300\n    subGraphTitleMargin:\n      top: 5\n      bottom: 10\n  layout: elk\n  look: handDrawn\n---\nflowchart LR\n  Start(\"&lt;strong&gt;📻 Podcast RSS Feed&lt;/strong&gt;\") --&amp;amp;gt; Ingest\n  subgraph Ingest[\"&lt;strong&gt;INGESTION PHASE&lt;/strong&gt; (ingest.py)\"]\n    direction TB\n    Download(\"&lt;strong&gt;Download Episodes&lt;/strong&gt;&amp;amp;lt;br&amp;amp;gt;• Audio files from RSS&amp;amp;lt;br&amp;amp;gt;• Last N episodes\")\n    Transcribe(\"&lt;strong&gt;Transcribe Audio&lt;/strong&gt;&amp;amp;lt;br&amp;amp;gt;• Gemini 2.5 Flash-Lite&amp;amp;lt;br&amp;amp;gt;• Skip ads/promos&amp;amp;lt;br&amp;amp;gt;• Label speakers\")\n    Upload(\"&lt;strong&gt;Upload to Store&lt;/strong&gt;&amp;amp;lt;br&amp;amp;gt;• Add custom metadata&amp;amp;lt;br&amp;amp;gt;• Title, podcast, tags&amp;amp;lt;br&amp;amp;gt;• Date range info\")\n    Download --&amp;amp;gt; Transcribe\n    Transcribe --&amp;amp;gt; Upload\n  end\n  Ingest --&amp;amp;gt; Store[(&lt;strong&gt;File Search Store&lt;/strong&gt;&amp;amp;lt;br&amp;amp;gt;Managed RAG system)]\n  Store --&amp;amp;gt; Query\n  subgraph Query[\"&lt;strong&gt;QUERY PHASE&lt;/strong&gt; (query.py)\"]\n    direction TB\n    Question(\"&lt;strong&gt;User Question&lt;/strong&gt;&amp;amp;lt;br&amp;amp;gt;Natural language\")\n    Search(\"&lt;strong&gt;File Search Tool&lt;/strong&gt;&amp;amp;lt;br&amp;amp;gt;• Optional metadata filters&amp;amp;lt;br&amp;amp;gt;• Automatic retrieval\")\n    Generate(\"&lt;strong&gt;Gemini 2.5 Flash&lt;/strong&gt;&amp;amp;lt;br&amp;amp;gt;• Generate answer&amp;amp;lt;br&amp;amp;gt;• Include citations\")\n    Question --&amp;amp;gt; Search\n    Search --&amp;amp;gt; Generate\n  end\n  Query --&amp;amp;gt; Results(\"&lt;strong&gt;✓ Results&lt;/strong&gt;&amp;amp;lt;br&amp;amp;gt;• Answer with context&amp;amp;lt;br&amp;amp;gt;• Episode citations&amp;amp;lt;br&amp;amp;gt;• Source verification\")    \n  classDef default text-align:left\n"}' data-plain='---&lt;br&gt;
config&lt;br&gt;
  themeVariables&lt;br&gt;
    fontFamily: monospace&lt;br&gt;
    fontSize: 12px&lt;br&gt;
  flowchart&lt;br&gt;
    wrappingWidth: 300&lt;br&gt;
    subGraphTitleMargin:&lt;br&gt;
      top: 5&lt;br&gt;
      bottom: 10&lt;br&gt;
  layout: elk&lt;/p&gt;
&lt;h2&gt;
  
  
    look: handDrawn
&lt;/h2&gt;

&lt;p&gt;flowchart LR&lt;br&gt;
  Start("&lt;strong&gt;📻 Podcast RSS Feed&lt;/strong&gt;") --&amp;gt; Ingest&lt;br&gt;
  subgraph Ingest["&lt;strong&gt;INGESTION PHASE&lt;/strong&gt; (ingest.py)"]&lt;br&gt;
    direction TB&lt;br&gt;
    Download("&lt;strong&gt;Download Episodes&lt;/strong&gt;&amp;lt;br&amp;gt;• Audio files from RSS&amp;lt;br&amp;gt;• Last N episodes")&lt;br&gt;
    Transcribe("&lt;strong&gt;Transcribe Audio&lt;/strong&gt;&amp;lt;br&amp;gt;• Gemini 2.5 Flash-Lite&amp;lt;br&amp;gt;• Skip ads/promos&amp;lt;br&amp;gt;• Label speakers")&lt;br&gt;
    Upload("&lt;strong&gt;Upload to Store&lt;/strong&gt;&amp;lt;br&amp;gt;• Add custom metadata&amp;lt;br&amp;gt;• Title, podcast, tags&amp;lt;br&amp;gt;• Date range info")&lt;br&gt;
    Download --&amp;gt; Transcribe&lt;br&gt;
    Transcribe --&amp;gt; Upload&lt;br&gt;
  end&lt;br&gt;
  Ingest --&amp;gt; Store[(&lt;strong&gt;File Search Store&lt;/strong&gt;&amp;lt;br&amp;gt;Managed RAG system)]&lt;br&gt;
  Store --&amp;gt; Query&lt;br&gt;
  subgraph Query["&lt;strong&gt;QUERY PHASE&lt;/strong&gt; (query.py)"]&lt;br&gt;
    direction TB&lt;br&gt;
    Question("&lt;strong&gt;User Question&lt;/strong&gt;&amp;lt;br&amp;gt;Natural language")&lt;br&gt;
    Search("&lt;strong&gt;File Search Tool&lt;/strong&gt;&amp;lt;br&amp;gt;• Optional metadata filters&amp;lt;br&amp;gt;• Automatic retrieval")&lt;br&gt;
    Generate("&lt;strong&gt;Gemini 2.5 Flash&lt;/strong&gt;&amp;lt;br&amp;gt;• Generate answer&amp;lt;br&amp;gt;• Include citations")&lt;br&gt;
    Question --&amp;gt; Search&lt;br&gt;
    Search --&amp;gt; Generate&lt;br&gt;
  end&lt;br&gt;
  Query --&amp;gt; Results("&lt;strong&gt;✓ Results&lt;/strong&gt;&amp;lt;br&amp;gt;• Answer with context&amp;lt;br&amp;gt;• Episode citations&amp;lt;br&amp;gt;• Source verification")&lt;br&gt;&lt;br&gt;
  classDef default text-align:left&lt;br&gt;
' dir="auto"&amp;gt;&lt;br&gt;
    &lt;/p&gt;
&lt;div class="render-plaintext-hidden"&gt;
&lt;br&gt;
      &lt;pre&gt;---&lt;br&gt;
config&lt;br&gt;
  themeVariables&lt;br&gt;
    fontFamily: monospace&lt;br&gt;
    fontSize: 12px&lt;br&gt;
  flowchart:&lt;br&gt;
    wrappingWidth: 300&lt;br&gt;
    subGraphTitleMargin:&lt;br&gt;
      top: 5&lt;br&gt;
      bottom: 10&lt;br&gt;
  layout: elk
&lt;h2&gt;
  
  
    look: handDrawn
&lt;/h2&gt;

&lt;p&gt;flowchart LR&lt;br&gt;
  Start("&lt;strong&gt;📻 Podcast RSS Feed&lt;/strong&gt;") --&amp;gt; Ingest&lt;br&gt;
  subgraph Ingest["&lt;strong&gt;INGESTION PHASE&lt;/strong&gt; (ingest.py)"]&lt;br&gt;
    direction TB&lt;br&gt;
    Download("&lt;strong&gt;Download Episodes&lt;/strong&gt;&amp;lt;br&amp;gt;• Audio files from RSS&amp;lt;br&amp;gt;• Last N episodes")&lt;br&gt;
    Transcribe("&lt;strong&gt;Transcribe Audio&lt;/strong&gt;&amp;lt;br&amp;gt;• Gemini 2.5 Flash-Lite&amp;lt;br&amp;gt;• Skip ads/promos&amp;lt;br&amp;gt;• Label speakers")&lt;br&gt;
    Upload("&lt;strong&gt;Upload to Store&lt;/strong&gt;&amp;lt;br&amp;gt;• Add custom metadata&amp;lt;br&amp;gt;• Title, podcast, tags&amp;lt;br&amp;gt;• Date range info")&lt;br&gt;
    Download --&amp;gt; Transcribe&lt;/p&gt;&lt;/pre&gt;…&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/markmcd/podcast-search" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;






</description>
      <category>ai</category>
      <category>gemini</category>
      <category>google</category>
    </item>
  </channel>
</rss>
