Skip to content

jamesng16/sage

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Sage — Internal Slack RAG Assistant

Sage is an internal Slack bot that answers employee questions by retrieving from company documents (RAG), remembers context per conversation thread, and decides on its own when to call external tools (web search, dynamic data lookups) once a question goes beyond the docs.

This is a portfolio project demonstrating AI Engineer skills: designing automation workflows with n8n, integrating an LLM through an AI Agent, building a RAG pipeline with a vector DB, and wiring it all into Slack via the Events API.

Why this matters

An internal Q&A bot sounds simple, but doing it right means solving three problems most AI demos skip:

  • Hallucination resistance: when a question falls outside the documents, Sage says "I couldn't find this in our internal docs" instead of making up a plausible-sounding answer. This is the biggest risk of putting an LLM in front of real employees.
  • Knowing when NOT to use RAG: questions about dynamic data (who's on-call, how many tasks are left in the sprint) would be answered wrong by vector search — Sage classifies the question and calls the right tool instead.
  • Remembering context without bloating tokens: conversation history is kept per thread without re-fetching the entire Slack history on every message.

Architecture

flowchart TD
    U["👤 Employee @mentions Sage in a thread"] --> T["⚡ Slack Trigger"]
    T --> F{"🛑 Check Not Bot"}
    F -- human message --> AGENT

    subgraph Agent["🧠 AI Agent — Sage"]
        AGENT(["Decides: answer directly,<br/>search docs, or call a tool"])
    end

    subgraph Model["💬 Language Model"]
        D["Chat Model<br/>OpenClaude · mimo-v2.5-pro"]
    end

    subgraph Memory["🗂️ Conversation Memory"]
        E["Window Buffer Memory<br/>key = thread_ts, last 10 turns"]
    end

    subgraph RAG["📚 Internal Knowledge — RAG"]
        Fdoc["Search Internal Docs<br/>Supabase pgvector"]
        G["Embeddings · Ollama<br/>nomic-embed-text"]
        Fdoc --- G
    end

    subgraph Tools["🛠️ Live Data & External Tools"]
        H["🌐 Web Search<br/>Tavily API"]
        I["👥 Employee Directory"]
        J["📅 On-call Schedule"]
        K["📊 Sprint Status"]
    end

    D -.model.-> AGENT
    E -.memory.-> AGENT
    Fdoc -.tool.-> AGENT
    H -.tool.-> AGENT
    I -.tool.-> AGENT
    J -.tool.-> AGENT
    K -.tool.-> AGENT

    AGENT --> L["🤖 Slack Reply — posted in the same thread"]

    classDef trigger fill:#36C5F0,color:#000,stroke:#0b7ba7,stroke-width:1px
    classDef agent fill:#ECB22E,color:#000,stroke:#a3760a,stroke-width:2px
    classDef rag fill:#2EB67D,color:#fff,stroke:#1a7a52,stroke-width:1px
    classDef tool fill:#E01E5A,color:#fff,stroke:#9c1440,stroke-width:1px
    classDef model fill:#4A90D9,color:#fff,stroke:#2f5f8f,stroke-width:1px
    classDef endpoint fill:#4A154B,color:#fff,stroke:#2e0e30,stroke-width:1px

    class U,L endpoint
    class T,F trigger
    class AGENT agent
    class Fdoc,G rag
    class H,I,J,K tool
    class D,E model
Loading
Component Technology Role
Slack App Slack Events API (app_mention) Receives mentions, forwards to n8n via webhook
Orchestration n8n (AI Agent node) Drives the whole flow: decides RAG vs. tool, calls the LLM, replies
Vector DB Supabase pgvector Stores internal doc embeddings, retrieval via match_documents
Embedding model Ollama nomic-embed-text (local, 768-dim) Embeds questions and documents — same model at ingest-time and query-time
Chat model OpenClaude (mimo-v2.5-pro, OpenAI-compatible) Generates answers, tool-use, decision making
Dynamic data n8n Data Table (employee directory, on-call, sprint status) Answers questions that need constantly-changing info, which doesn't belong in RAG
Web search Tavily API Public/real-time information outside the scope of internal docs
Memory n8n Window Buffer Memory, key = thread_ts Remembers the last 10 exchanges within the same thread

Project structure

slack-ai-assistant/
├── n8n/workflows/slack-rag-bot.json   # exported workflow (credentials stripped)
├── docs-source/                       # internal documents used for RAG
├── dynamic-data/                      # dynamic data for the Lookup tools (employee, on-call, sprint)
├── ingestion/
│   ├── chunk_and_embed.py             # chunks + embeds docs, pushes to Supabase
│   └── requirements.txt
├── slack-app/manifest.yml             # Slack App manifest (scopes, event subscriptions)
├── tests/sample_questions.md          # manual test question set, used for the demo recording
└── .env.example                       # environment variable template (no real secrets)

Setup

1. Create the vector table in Supabase

In your Supabase project's SQL Editor, run:

create extension if not exists vector;

create table documents (
  id bigserial primary key,
  content text,
  metadata jsonb,
  embedding vector(768)
);

create or replace function match_documents (
  query_embedding vector(768),
  match_count int default 5,
  filter jsonb default '{}'
) returns table (
  id bigint,
  content text,
  metadata jsonb,
  similarity float
)
language plpgsql
as $$
begin
  return query
  select
    documents.id,
    documents.content,
    documents.metadata,
    1 - (documents.embedding <=> query_embedding) as similarity
  from documents
  where documents.metadata @> filter
  order by documents.embedding <=> query_embedding
  limit match_count;
end;
$$;

create index on documents using hnsw (embedding vector_cosine_ops);

The 768 dimension matches Ollama's nomic-embed-text embedding model — if you switch embedding models, update this dimension in both the table and the ingestion script.

2. Ingest documents into the vector DB

cd ingestion
pip install -r requirements.txt
cp ../.env.example ../.env   # fill in real values in .env, do NOT commit this file
python chunk_and_embed.py

This script requires Ollama running locally (ollama pull nomic-embed-text first) and the SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY environment variables.

3. Create the Slack App

Go to api.slack.com/appsCreate New AppFrom an app manifest → paste the contents of slack-app/manifest.yml. After creation, install the app to your workspace and grab the Bot User OAuth Token (xoxb-...) and Signing Secret.

4. Import the workflow into n8n

Import n8n/workflows/slack-rag-bot.json into n8n. After importing, reconfigure the 3 credentials (stripped on export):

  • Slack API — the Bot Token + Signing Secret from step 3
  • Chat Model (OpenAI-compatible) — OpenClaude's API key + base URL
  • Supabase API — Project URL + service_role key
  • Ollama — local base URL (default http://localhost:11434)

Activate the workflow, then mention the bot in Slack to test.

Demo

Use the sample questions in tests/sample_questions.md, split into 4 groups for the video/GIF recording:

  1. RAG questions answered correctly from the docs
  2. Out-of-scope questions (verify the bot refuses to make up an answer)
  3. Questions needing external tools (web search / dynamic data)
  4. Follow-up questions within the same thread (verify context memory)

Limitations (out of scope)

  • No complex multi-language handling (basic Vietnamese/English only)
  • No SSO, just basic Slack OAuth
  • No queue system — suited for demo/small-scale internal use, not optimized for production scale

About

Internal Slack RAG bot (n8n + Supabase pgvector + Ollama) — AI Engineer portfolio project

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages