跳到正文
GitHub·· 8 天前AI 评分36

PodLens:为播客生成 AI 上下文与实体卡片

dhruvpathak1/PodLens_AI_Context_Generator

AI 导读

PodLens 是一个面向播客的 AI 上下文引擎,可转录单集节目并识别其中提到的人物、公司、地点与技术,在播放到对应时刻时展示带摘要、地图和图片的来源卡片。它还能生成按日期排列的单集时间线,并基于 LangChain + OpenAI 提供带引用的问答,点击引用可跳转到原句。

正文

PodLens logo

PodLens: AI-Powered Context for Your Podcasts

PodLens is an AI-powered context engine for podcasts. It transcribes an episode, identifies the people, companies, places and technologies mentioned, and surfaces each one as a source card with a summary, map and image, timed to the moment it is spoken. It can also build a chronological timeline of the episode and answer questions about it, with citations that jump to the exact moment.

▶ Try the live demo: https://podlens.dhruvpathak.com

Built With

Category Technologies
Languages TypeScript Python HTML5 CSS YAML Bash
Frontend React 19 Vite Web Audio API Inter (Google Fonts)
Backend FastAPI Uvicorn Pydantic HTTPX python-dotenv
Speech & audio OpenAI Whisper PyTorch FFmpeg
NLP & entities spaCy Claude (Anthropic)
LLM & AI LangChain OpenAI GPT OpenAI Embeddings
Data sources Wikipedia OpenStreetMap Unsplash
Testing & quality pytest ESLint TypeScript strict
DevOps & tooling Docker GitHub Actions GitHub Pages Node.js npm Git
Live mode (parked) OpenAI Realtime WebSockets MediaRecorder

Also used: asyncio, python-multipart, typescript-eslint, eslint-plugin-react-hooks, Docker Compose, concurrently, and Python venv. Data is stored as JSON entity exports and timestamped .txt transcripts.

See it in Action

PodLens_Context.Gen.mp4

PodLens_Live.Context.mp4


Contents


Key Features

Feature What it does Powered by
Transcription Timestamped transcript of any uploaded episode, run locally OpenAI Whisper (local)
Entity tagging Finds people, companies, places, technologies and events spaCy (local) or Anthropic Claude
Disambiguation Tells "Apple" the company from the fruit, "Amazon" the company from the river Custom context rules
AI entity review Fixes misheard names, merges duplicates, drops vague tags, and removes summaries or photos that don't match the episode LangChain + OpenAI
Source cards Summary, map and photo for every entity, shown as playback reaches it Wikipedia, OpenStreetMap, Unsplash
Episode timeline Every dated event mentioned, in date order, each with a link to the moment it is said LangChain + OpenAI
Ask the episode Grounded Q&A with citations that seek the player to the quote LangChain + OpenAI (+ embeddings)
Synced transcript Waveform scrubber; the active line highlights during playback; click any line to seek; search React, Web Audio
Dashboard Six panels on a dotted dark canvas, each scrolling on its own; intro cards on first load; light theme available CSS design tokens
Free public demo Pre-processed sample episodes on GitHub Pages, with no server and no API keys Static JSON + audio

How It Works

 Upload audio ─▶ FFmpeg ─▶ Whisper ─▶ Transcript + segments
                                          │
                                          ▼
                              NER (spaCy or Claude) ─▶ Entities with timestamps
                                          │
              ┌───────────────────────────┼───────────────────────────┐
              ▼                           ▼                           ▼
   Source cards                   Episode timeline              Ask the episode
   Wikipedia · OSM · Unsplash     LangChain + OpenAI            LangChain + OpenAI
              │                           │                           │
              └──────────────▶ React dashboard synced to playback ◀───┘

AI Entity Review

Speech recognition and entity taggers make predictable mistakes on podcasts. A review step checks every entity against the episode before (and after) the source cards are built.

Problem Example What the review does
Misheard name "Traderus 8", "Apollo Alto Garage" Fix: "Traitorous Eight", "Palo Alto" (and the right type)
Same thing, several names "SF", "San Francisco", "San Francisco, California" Merge into one entity
Not a real entity "San", the show's own name, filler Drop
Wrong sense on Wikipedia "Yerba Buena" resolves to the mint plant Writes a precise search query ("Yerba Buena San Francisco history"), then removes any summary or photo that still doesn't match

How it works

  1. Before lookups: each unique entity is sent with the transcript lines it appears in and the opening of the episode. The model returns one verdict per entity (keep, fix, merge, drop) plus a disambiguated Wikipedia search query, using strict structured output.
  2. Lookups: Wikipedia, maps and photos use the corrected names and search queries.
  3. After lookups: the model compares each card's Wikipedia summary and photo description with how the episode uses the name, and strict mismatches are removed from the card.

The Entities panel shows the result ("checked by AI, 6 corrected, 5 removed"; hover for the full list). The review fails open: if the model is unavailable, cards are built from the original entities. Turn it off with ENTITY_REVIEW=false; demo builds accept --no-review.


🧭 Episode Timeline

One click turns the episode into a chronological timeline of every company, person, product, deal and event mentioned, with real-world dates where the speakers give them.

How it works

  • The transcript is split into windows (TIMELINE_WINDOW_CHARS, default 24,000 characters) so long episodes fit the model's context.
  • Windows are processed concurrently (TIMELINE_MAX_CONCURRENCY, default 4). Each window only receives the entities tagged inside it.
  • LangChain's with_structured_output uses OpenAI strict JSON schema mode, so every event is guaranteed to match the schema; no regex parsing of model output.
  • Results from all windows are merged, de-duplicated and clamped to valid timestamps. One failed window does not fail the whole timeline.
  • In the UI, Create timeline lists every event in date order on a quiet date spine: the date on the left (in the accent colour), the event title and a Played at link to the moment it is mentioned on the right. Undated events come last; the model's description shows when you hover a title.

Event schema

type TimelineEvent = {
  id: string
  title: string
  description: string
  category: 'COMPANY' | 'PERSON' | 'PRODUCT' | 'TECHNOLOGY' | 'EVENT' | 'PLACE' | 'DEAL' | 'OTHER'
  date_label: string | null   // as stated: "1993", "late 1990s"
  year: number | null
  month: number | null
  mentioned_at_sec: number    // where it is first mentioned in the episode
  entities: string[]
  importance: 1 | 2 | 3       // passing mention, notable, central
}

Request

curl -X POST http://127.0.0.1:8000/api/timeline \
  -H "Content-Type: application/json" \
  -d '{
        "segments": [{"id": 0, "start": 12.0, "end": 23.0,
                      "text": "In 1966 researchers began studying lions in the Serengeti."}],
        "entities": [{"type": "PLACE", "text": "Serengeti", "start_sec": 13, "end_sec": 15, "chunk_id": 0}],
        "source_label": "Lions 101.mp3"
      }'

Response (abridged)

{
  "model": "gpt-5.4-mini",
  "windows": 1,
  "failed_windows": 0,
  "events": [
    {
      "id": "ev0",
      "title": "Long-term Serengeti lion study begins",
      "category": "EVENT",
      "date_label": "1966",
      "year": 1966,
      "mentioned_at_sec": 12.0,
      "entities": ["Serengeti"],
      "importance": 3
    }
  ]
}

Ask the Episode (LLM Q&A)

Ask anything about the episode and get an answer grounded only in the transcript, with citations you can click to hear the exact quote.

Hybrid context strategy

Transcript length Mode What is sent to the model
Up to ASK_FULL_CONTEXT_CHARS (default 60,000 characters) full The whole timestamped transcript
Longer retrieval Top-K (ASK_TOP_K, default 8) overlapping ~60 s chunks, ranked by OpenAI embeddings
  • Embeddings for a long episode are computed once and cached in memory, keyed by a fingerprint of the transcript, so follow-up questions are fast and cheap.
  • The last 6 turns of the conversation are sent with each question, so follow-ups like "when did he say that?" work.
  • Citation validation: every quote the model returns is checked against the transcript. Citations that do not match are dropped, and an answer whose citations all fail is flagged unverified: true.
  • If the episode does not cover the question, the model says so (found: false) instead of guessing.

Request

curl -X POST http://127.0.0.1:8000/api/ask \
  -H "Content-Type: application/json" \
  -d '{
        "question": "Who wrote The Serengeti Lion?",
        "segments": [{"id": 2, "start": 24.0, "end": 35.0,
                      "text": "George Schaller published The Serengeti Lion in 1972."}],
        "history": []
      }'

Response

{
  "answer": "George Schaller, who published The Serengeti Lion in 1972.",
  "found": true,
  "citations": [{ "start_sec": 24.0, "quote": "George Schaller published The Serengeti Lion in 1972." }],
  "mode": "full",
  "model": "gpt-5.4-mini",
  "unverified": false
}

Choosing Your Models: spaCy vs Claude vs OpenAI

PodLens uses three model families, each for a different job:

Job Options Configured by
Speech to text Whisper tiny to large (local) WHISPER_MODEL
Entity tagging (NER) spaCy sm / md / lg (local) or Claude (API) ENTITY_BACKEND, SPACY_MODEL, CLAUDE_MODEL
Entity review OpenAI chat model (API) OPENAI_REVIEW_MODEL
Timeline and Q&A OpenAI chat + embedding models (API) OPENAI_TIMELINE_MODEL, OPENAI_ASK_MODEL, OPENAI_EMBED_MODEL

OpenAI does not tag entities. It reviews what spaCy or Claude found (fix, merge, drop) and powers the timeline and Q&A.

Entity tagging: spaCy vs Claude

spaCy en_core_web_sm spaCy md / lg Claude (claude-haiku-4-5)
Runs Locally Locally Anthropic API
Cost Free Free Paid per token
Speed Fastest Fast Slower (network, batched)
Privacy Audio and text stay on your machine Same Transcript text is sent to Anthropic
Messy podcast speech Misses or confuses entities Noticeably better Best: handles slang, partial names, context
Setup Installed with requirements python -m spacy download en_core_web_md ANTHROPIC_API_KEY

How the backend is chosen: ENTITY_BACKEND wins if set. If it is unset and ANTHROPIC_API_KEY is present, Claude is used; otherwise spaCy. The UI can force one with VITE_ENTITY_BACKEND.

Whisper model sizes

Model Parameters Use when
tiny 39M Quick tests, low-RAM machines
base (default) 74M Good balance on a laptop CPU
small 244M Noticeably cleaner text, which also improves NER
medium / large 769M / 1.55B Best accuracy; slow on CPU, best with a GPU

APIs and Services Used

Service Used for Endpoint Key needed
OpenAI Whisper (local) Speech to text Runs in-process No
spaCy (local) Entity tagging Runs in-process No
Anthropic Messages API Entity tagging (Claude backend) api.anthropic.com via the anthropic SDK ANTHROPIC_API_KEY
OpenAI Chat Completions Entity review, timeline and Q&A (strict JSON schema) via langchain-openai OPENAI_API_KEY
OpenAI Embeddings Retrieval for long episodes in Ask via langchain-openai OPENAI_API_KEY
Wikipedia Action API Find the best article for an entity en.wikipedia.org/w/api.php No
Wikipedia REST API Summary and thumbnail en.wikipedia.org/api/rest_v1/page/summary No
Nominatim (OpenStreetMap) Geocoding places nominatim.openstreetmap.org/search No (set NOMINATIM_USER_AGENT)
OpenStreetMap embed Interactive map on place cards openstreetmap.org/export/embed.html No
Unsplash Search API Photo for each card (with photographer credit) api.unsplash.com/search/photos UNSPLASH_ACCESS_KEY

Every external service is optional except Whisper and spaCy. Missing keys disable only the feature that needs them, and GET /api/health reports which features are active.


PodLens REST API

Method Path Purpose
GET /api/health Status and which optional features are configured
POST /api/transcribe Upload audio (multipart); returns transcript, segments and tagged entities
POST /api/extract-entities Tag entities in existing transcript segments
POST /api/enrich-entities Review entities with AI (when segments are sent) and build source cards
POST /api/timeline Chronological timeline of everything mentioned
POST /api/ask Grounded Q&A with citations

Interactive docs are available at http://127.0.0.1:8000/docs while the API is running.

Transcribe an episode

curl -X POST http://127.0.0.1:8000/api/transcribe \
  -F "audio=@episode.mp3" \
  -F "extract_entities=true" \
  -F "entity_backend=spacy"     # optional: spacy | claude
{
  "transcript": "Lions are the only truly social big cats...",
  "segments": [{ "id": 0, "start": 0.0, "end": 11.0, "text": "Lions are the only truly social big cats..." }],
  "saved_path": ".../server/transcripts/episode_20261008T221500Z.txt",
  "document": {
    "backend": "spacy",
    "entities": [{ "type": "PLACE", "text": "Serengeti", "start_sec": 13.0, "end_sec": 15.0, "chunk_id": 1 }]
  },
  "entity_error": null
}

Build source cards

curl -X POST http://127.0.0.1:8000/api/enrich-entities \
  -H "Content-Type: application/json" \
  -d '{"entities": [{"type": "PLACE", "text": "Serengeti", "start_sec": 13, "end_sec": 15, "chunk_id": 1}]}'

Check which features are on

curl http://127.0.0.1:8000/api/health
# {"ok":true,"model":"base","entity_backend":"spacy","unsplash_configured":true,
#  "openai_configured":true,"entity_review":true,"live_mode":false,"realtime_transcription":false}

Installation and Setup

Prerequisites

  • Node.js 18+
  • Python 3.9+
  • FFmpeg on your PATH (brew install ffmpeg on macOS)

1. Clone and install the frontend

git clone https://github.com/dhruvpathak1/PodLens_AI_Context_Generator.git
cd PodLens_AI_Context_Generator
npm install

2. Set up the backend

The npm scripts expect the virtual environment at the project root (venv/).

python -m venv venv
venv/bin/python -m pip install -r server/requirements.txt   # Windows: venv\Scripts\python

3. Configure

cp .env.example .env

Then fill in what you need:

Variable Needed for
UNSPLASH_ACCESS_KEY Photos on source cards
ANTHROPIC_API_KEY Claude entity tagging
OPENAI_API_KEY AI entity review, Episode timeline and Ask the episode
NOMINATIM_USER_AGENT Your app name or contact email (requested by OpenStreetMap)

Running the App

Development

npm run dev

The first transcription downloads the Whisper model, which can take a few minutes.

Checks

npm run check        # typecheck + lint + backend tests
npm run build        # production bundle in dist/

Backend tests need the dev requirements once: venv/bin/python -m pip install -r server/requirements-dev.txt.

🐳 Docker

docker compose up --build

Open http://localhost:8080. One container serves the built UI and the API; data persists in the podlens-data volume. See DOCKER.md for sharing and deployment.



Distributed under the MIT License. See LICENSE for more information.


Created with ❤️ for the podcasting community.

来源:GitHub · github.com