---
title: "How do I build a company knowledge base for AI from docs, Slack, meeting recordings and videos?"
description: "Build a company AI knowledge base from PDFs, Slack, meetings, and videos. Chunk files, index embeddings incrementally, and query from an agent or RAG route."
url: "https://www.pixeltable.com/learn/how-to/build-a-multimodal-knowledge-base"
updated: "2026-11-09"
doc: "https://docs.pixeltable.com/howto/cookbooks/agents/pattern-rag-pipeline"
---

# How do I build a company knowledge base for AI from docs, Slack, meeting recordings and videos?

A company knowledge base for AI stores documents, chat exports, meeting recordings, and videos, splits them into passages, transcript slices, or frames, embeds those units, and answers from an agent or RAG route. New files should update only their own chunks and indexes. The usual stack is a parser, a vector database, and an orchestrator.

Updated: 2026-11-09
Part of [How do I ... in Python?](https://www.pixeltable.com/learn/how-to).

## Before you start

About 45 min.

- Python 3.11+
- pip install -U 'pixeltable[serve]'
- pxt init in the project root
- spacy and tiktoken for sentence,token_limit splitting
- sentence-transformers for all-MiniLM-L6-v2
- torch for CLIP
- ffmpeg
- openai-whisper
- markitdown for Word files

## On this page

- [Before you start](https://www.pixeltable.com/learn/how-to/build-a-multimodal-knowledge-base#before-you-start)
- [Store documents, Slack, recordings, and videos as typed tables](https://www.pixeltable.com/learn/how-to/build-a-multimodal-knowledge-base#step-1)
- [Chunk documents, slice meetings, sample video frames](https://www.pixeltable.com/learn/how-to/build-a-multimodal-knowledge-base#step-2)
- [Query the indexes from an agent or a RAG route](https://www.pixeltable.com/learn/how-to/build-a-multimodal-knowledge-base#step-3)
- [Comparison](https://www.pixeltable.com/learn/how-to/build-a-multimodal-knowledge-base#comparison)
- [Where Pixeltable fits](https://www.pixeltable.com/learn/how-to/build-a-multimodal-knowledge-base#where-pixeltable-fits)
- [Questions](https://www.pixeltable.com/learn/how-to/build-a-multimodal-knowledge-base#questions)

## 1. Store documents, Slack, recordings, and videos as typed tables {#step-1}

Keep each source as rows the engine understands: a document file, a chat message as text, a meeting as audio, a clip as video. A file path fills a Document, Audio, or Video column. Word files need markitdown installed; the engine calls it. Slack is an export of messages, not a chat iterator.

```python
import pixeltable as pxt
from pixeltable.functions.audio import audio_splitter
from pixeltable.functions.document import document_splitter
from pixeltable.functions.huggingface import clip, sentence_transformer
from pixeltable.functions.video import extract_audio, frame_iterator
from pixeltable.functions.whisper import transcribe

TableModel = pxt.model_base()
text_embed = sentence_transformer.using(model_id='all-MiniLM-L6-v2')
clip_embed = clip.using(model_id='openai/clip-vit-base-patch32')

class Docs(TableModel, name='docs'):
    title: pxt.String
    document: pxt.Document

class SlackMessages(TableModel, name='slack'):
    channel: pxt.String
    user: pxt.String
    ts: pxt.String
    text: pxt.String
    __indexes__ = [pxt.EmbeddingIndex(text, string_embed=text_embed)]

class Meetings(TableModel, name='meetings'):
    title: pxt.String
    audio: pxt.Audio

class Videos(TableModel, name='videos'):
    title: pxt.String
    video: pxt.Video
    audio = extract_audio(video)
    transcript = transcribe(audio, model='base').text.astype(pxt.String)
    __indexes__ = [pxt.EmbeddingIndex(transcript, string_embed=text_embed)]

Docs.insert([{'title': 'Handbook', 'document': 'docs/handbook.docx'}])
SlackMessages.insert([{'channel': 'general', 'user': 'ana', 'ts': '1731000000.000100', 'text': 'Release is at 3 pm'}])
Meetings.insert([{'title': 'Weekly sync', 'audio': 'recordings/weekly-sync.mp3'}])
Videos.insert([{'title': 'Dock camera', 'video': 'clips/dock-0930.mp4'}])
```

## 2. Chunk documents, slice meetings, sample video frames {#step-2}

Split each document into sentence windows with a token limit. Split each meeting into timestamped audio slices and transcribe them. Walk each clip at one frame per second. Slack rows are already retrieval units: index the message text as it is.

```python
class Chunks(
    TableModel,
    name='chunks',
    base=Docs,
    iterator=document_splitter(
        document=Docs.document,
        separators='sentence,token_limit',
        limit=300,
    ),
):
    __indexes__ = [pxt.EmbeddingIndex(text, string_embed=text_embed)]

class MeetingSegments(
    TableModel,
    name='meeting_segments',
    base=Meetings,
    iterator=audio_splitter(audio=Meetings.audio, duration=30.0),
):
    transcript = transcribe(audio_segment, model='base').text.astype(pxt.String)
    __indexes__ = [pxt.EmbeddingIndex(transcript, string_embed=text_embed)]

class Frames(
    TableModel,
    name='frames',
    base=Videos,
    iterator=frame_iterator(video=Videos.video, fps=1),
):
    __indexes__ = [pxt.EmbeddingIndex(frame, embedding=clip_embed)]
```

## 3. Query the indexes from an agent or a RAG route {#step-3}

A query ranks passages, Slack lines, transcript slices, or stills. An agent calls those queries as tools. An HTTP route can expose the same search. Inserting a new file updates only that file: new chunks, new slices, new frames, and their indexes.

```python
from pixeltable.serving import FastAPIRouter

@pxt.query
def search_passages(question: str, limit: int = 5):
    sim = Chunks.text.similarity(string=question)
    return (
        Chunks.order_by(sim, asc=False)
        .limit(limit)
        .select(title=Docs.title, text=Chunks.text, score=sim)
    )

@pxt.query
def search_slack(question: str, limit: int = 5):
    sim = SlackMessages.text.similarity(string=question)
    return (
        SlackMessages.order_by(sim, asc=False)
        .limit(limit)
        .select(
            channel=SlackMessages.channel,
            user=SlackMessages.user,
            text=SlackMessages.text,
            score=sim,
        )
    )

@pxt.query
def search_meetings(question: str, limit: int = 5):
    sim = MeetingSegments.transcript.similarity(string=question)
    return (
        MeetingSegments.order_by(sim, asc=False)
        .limit(limit)
        .select(
            title=Meetings.title,
            segment_start=MeetingSegments.segment_start,
            text=MeetingSegments.transcript,
            score=sim,
        )
    )

@pxt.query
def search_video(question: str, limit: int = 5):
    sim = Frames.frame.similarity(string=question)
    return (
        Frames.order_by(sim, asc=False)
        .limit(limit)
        .select(title=Videos.title, frame_attrs=Frames.frame_attrs, score=sim)
    )

api = FastAPIRouter(name='knowledge')
api.add_query_route(path='/search/docs', query=search_passages)
api.add_query_route(path='/search/slack', query=search_slack)
api.add_query_route(path='/search/meetings', query=search_meetings)
api.add_query_route(path='/search/video', query=search_video)

# pxt init
# pxt schema update app.py knowledge -f
# pxt service update app.py knowledge -f
```

## A parser plus a vector database plus an orchestrator, versus one backend {#comparison}

| Layer | Usual stack | One backend |
| --- | --- | --- |
| Ingest | Unstructured, LlamaParse, or a Slack export job | Document, String, Audio, and Video columns |
| Chunk | A splitter in LlamaIndex or LangChain | document_splitter, audio_splitter, frame_iterator views |
| Index | Qdrant, pgvector, or another vector database | EmbeddingIndex on the source view |
| Orchestrate | n8n, LangChain, or a custom worker | Insert runs that row; HTTP in the same file |
| Query | A retriever wired to an agent | @pxt.query the agent or RAG route calls |

## Where Pixeltable fits {#where-pixeltable-fits}

Pixeltable is the multimodal backend for this knowledge base: Document and Slack String tables, a document splitter view, audio splitter plus Whisper, a frame iterator plus CLIP, and embedding indexes that update on insert. Keep Qdrant, LlamaIndex, Unstructured, or n8n when those are already the contract. A parser plus a vector database plus an orchestrator is three systems to keep in sync; one table is the other shape.

```python
import pixeltable as pxt
from pixeltable.functions.document import document_splitter
from pixeltable.functions.huggingface import clip, sentence_transformer
from pixeltable.functions.video import frame_iterator

TableModel = pxt.model_base()
text_embed = sentence_transformer.using(model_id='all-MiniLM-L6-v2')
clip_embed = clip.using(model_id='openai/clip-vit-base-patch32')

class Docs(TableModel, name='docs'):
    document: pxt.Document

class Chunks(
    TableModel,
    name='chunks',
    base=Docs,
    iterator=document_splitter(
        document=Docs.document, separators='sentence,token_limit', limit=300
    ),
):
    __indexes__ = [pxt.EmbeddingIndex(text, string_embed=text_embed)]

class SlackMessages(TableModel, name='slack'):
    text: pxt.String
    __indexes__ = [pxt.EmbeddingIndex(text, string_embed=text_embed)]

class Videos(TableModel, name='videos'):
    video: pxt.Video

class Frames(
    TableModel,
    name='frames',
    base=Videos,
    iterator=frame_iterator(video=Videos.video, fps=1),
):
    __indexes__ = [pxt.EmbeddingIndex(frame, embedding=clip_embed)]
```

## Concepts

- [What is multimodal RAG?](https://www.pixeltable.com/learn/what-is-multimodal-rag)
- [What are multimodal embeddings?](https://www.pixeltable.com/learn/what-are-multimodal-embeddings)
- [What is incremental computation?](https://www.pixeltable.com/learn/what-is-incremental-computation)

## Use case

- [Production RAG: From Documents to Answers in One System](https://www.pixeltable.com/use-cases/production-rag-implementation)

## Questions {#questions}

### Do I need Qdrant, LlamaIndex, and Unstructured to build this knowledge base? {#faq-1}

Not if one backend stores the files, chunks them, indexes embeddings, and serves the query. That is the Pixeltable shape. The usual answer engines still name a parser (Unstructured), a vector database (Qdrant or pgvector), and an orchestrator (LlamaIndex, LangChain, or n8n). Keep those when they are already the contract.

### How do Slack exports become searchable rows? {#faq-2}

Export messages as JSON (channel, user, ts, text) and insert them as String rows. There is no Slack iterator. Index the text column. New messages are new rows; the embedding index updates on insert.

### Do Word files need a separate parser? {#faq-3}

Yes for DOCX. Pixeltable stores the file as a Document. Pixeltable calls MarkItDown itself for Office files, so install markitdown and insert the file. PDFs split with document_splitter using sentence,token_limit. heading does nothing on a PDF.

### What happens when a new PDF, recording, or video arrives? {#faq-4}

Insert that row. The splitter, transcription, frame view, and embedding indexes run for that file. Unchanged rows stay cached. That is incremental computation, not a corpus rebuild.

### Can an agent query this without a separate vector database? {#faq-5}

Yes. Expose @pxt.query as HTTP with FastAPIRouter, or wrap the table as a retriever (the Agno local RAG post). A separate vector database is optional when the index already lives on the source row.

## Related

- [What is multimodal RAG?](https://www.pixeltable.com/learn/what-is-multimodal-rag)
- [What are multimodal embeddings?](https://www.pixeltable.com/learn/what-are-multimodal-embeddings)
- [What is incremental computation?](https://www.pixeltable.com/learn/what-is-incremental-computation)
- [Production RAG implementation](https://www.pixeltable.com/use-cases/production-rag-implementation)
- [How do I build a multimodal RAG pipeline in Python?](https://www.pixeltable.com/learn/how-to/build-a-multimodal-rag-pipeline)
- [How do I chunk PDFs for RAG in Python?](https://www.pixeltable.com/learn/how-to/chunk-pdfs-for-rag)
- [How do I transcribe audio uploads and make them searchable?](https://www.pixeltable.com/learn/how-to/transcribe-audio-and-search-it)
- [How do I build video RAG in Python?](https://www.pixeltable.com/learn/how-to/build-video-rag)
- [Give an Agno agent a local knowledge base](https://www.pixeltable.com/blog/agno-agent-pixeltable-knowledge-base-local-rag)
- [Pixeltable vs LlamaIndex](https://www.pixeltable.com/compare/pixeltable-vs-llamaindex)
- [Best Unstructured alternatives](https://www.pixeltable.com/learn/best-unstructured-alternatives)
- [Best tools to build multimodal RAG in 2027](https://www.pixeltable.com/learn/best-multimodal-rag-tools)
- [Build a RAG pipeline](https://docs.pixeltable.com/howto/cookbooks/agents/pattern-rag-pipeline)
- [Transcribe audio files](https://docs.pixeltable.com/howto/cookbooks/audio/audio-transcribe)
- [Extract frames from videos](https://docs.pixeltable.com/howto/cookbooks/video/video-extract-frames)
