Migrant Archive

An access layer for migrant narratives

Sebastian Lopez Largo

Ironhack - AI Engineering Bootcamp

Problem

Long videos bury the knowledge

A person may need one idea, one quote, or one moment,
but inside long videos with weak captions,
finding it by hand is slow

Solution

Find specific context in long videos

The goal is to make migrant knowledge searchable, reusable, and easy to connect with

Architecture

Process pipeline

Data foundation
S01 Ingestion
S02 Embeddings
S03 Vector store
Validation scripts
S04 Retrieval smoke test
S05 Rebuild workflow
S10 RAGAS evaluation
Agent & API
S06 Tool-calling agent
S07 FastAPI service
Delivery
S08 Web widget
S09 Demo package

Data foundation

Ingestion

YouTube captions Faster Whisper (local vs. Colab GPU) Video_data.JSON

The rest of the system does not care how a transcript was produced

❯ yt-dlp → YouTube Data API | --lang cat,esp
{
  "video_id": "…",
  "title": "…",
  "description": "…",
  "transcript_segments": [
    {
      "text": "…",
      "start": 0.0,
      "duration": 0.0,
      "speaker": "…"
    }
  ],
  "full_text": "…",
  "metadata": { "channel", "duration", "views" }
}

Data foundation

Embedding & local RAG

Gemini embedding-2 · 3072-dim ChromaDB vector store 1000-token chunks · 200 overlap

This is the decision that made the Spanish agent reliable

Validation scripts

Small scripts to fast-test

Retrieval test Reveal vectors Reveal embeddings

Verify top-k3 retrieval, similarity, and distance

Query · easy → hard simdist
FILMIG 0.920.08
Who is Santiago Tubio? 0.660.34
What emotions did "Mujeres del maiz" feel? 0.530.47
What can't it find? 0.340.66

Agent architecture

Cero: tool-calling agent

Gemini 2.5 Flash LangChain tool calling ChromaDB retrieval Session memory

Agent structure

~/backend/agent git:(main)
❯ python agent.py
def create_agent():
    llm = ChatGoogleGenerativeAI(
        model="gemini-2.5-flash",
        temperature=0.2,
        tool_config={"function_calling_config": {"mode": "ANY"}},
    )

    store = Chroma(
        collection_name="migrant_archive",
        embedding_function=GoogleGenerativeAIEmbeddings(
            model="gemini-embedding-2"
        ),
        persist_directory="data/chroma",
    )

    tools = [
        list_videos,
        get_video_info,
        search_transcripts,
    ]

    prompt = system_prompt + chat_history + user_input + agent_scratchpad

    agent = create_tool_calling_agent(llm, tools, prompt)

    executor = AgentExecutor(
        agent=agent,
        tools=tools,
        max_iterations=10,
        return_intermediate_steps=True,
    )

    return RunnableWithMessageHistory(
        executor,
        get_session_history,
        history_messages_key="chat_history",
    )

Tool registry

~/backend/agent git:(main)
❯ python tools.py
@tool
def list_videos(year=None, speaker=None, channel=None):
    """
    Browse the indexed video catalog.
    Returns titles, years, channels, speakers, chunk counts,
    video IDs, and YouTube URLs.
    """


@tool
def get_video_info(video_id):
    """
    Inspect one specific video.
    Returns title, year, channel, duration, speakers,
    chunk count, description, transcript preview, and URL.
    """


@tool
def search_transcripts(query, video_id=None, year=None, channel=None):
    """
    Search what was said inside the videos.
    Uses ChromaDB similarity search and optional filters
    to retrieve the most relevant transcript chunks.
    """

Quality scripts

RAGAS evaluation

Metric Score Meaning
Faithfulness 0.96 Claims backed by context
Answer relevancy 0.78 Answers the question
Context precision 0.90 Retrieved context is relevant
Context recall 0.73 Ground truth is covered
High faithfulness means Cero rarely invents · context recall is the next improvement target

Product

FastAPI service

The browser never runs the AI, it calls the API, and the backend runs the agent, keeping keys off the client

Ask — Grounded answer with sources Speak — Audio to text, Groq Whisper Reset — Clear conversation memory

Observability

LangSmith documents every agent run

LangSmith Zero-code tracing Auto-detection Run traces

LangSmith hooks into LangChain's callbacks when tracing is enabled, with no changes to the agent code

Implementation

What the users don't see

Back End
~/migrant-archive
❯ uv run uvicorn backend.api.main:app --reload --port 8000
Front End
~/migrant-archive
❯ cd frontend && pnpm install && pnpm dev
Start API Start Frontend Open Call Host

Future work

Where we'd take this next

Migrant Archive

An access layer for migrant narratives

Questions?