Spaces:
Sleeping
A newer version of the Streamlit SDK is available: 1.59.1
title: IJNet Assistant
emoji: π
colorFrom: blue
colorTo: indigo
sdk: streamlit
sdk_version: 1.57.0
app_file: app.py
pinned: false
π IJNet Chatbot Assistant
A LangChain-powered RAG chatbot that helps journalists find training opportunities, fellowships, grants, tools, and expert guidance from the IJNet knowledge base.
Built as a prototype for IJNet's chatbot assistance system, using entirely free and open-source tools.
Features
- Hybrid Retrieval: Combines semantic search (FAISS) with keyword search (BM25) using Reciprocal Rank Fusion for robust document retrieval
- Smart Query Classification: Automatically detects query intent (deadline-based, region-filtered, topic-specific, newsletter, etc.) and applies appropriate retrieval strategies
- Metadata-Aware Filtering: Post-retrieval filtering on deadlines, regions, opportunity types, and topics
- Source Attribution: Every response cites its sources with titles, URLs, and metadata
- Multi-Turn Conversation: Maintains conversation history for follow-up questions
- Debug Mode: Toggle to inspect retrieval details (semantic scores, BM25 scores, query classification)
- Evaluation Suite: Automated tests for retrieval accuracy and query classification
Architecture
User Query
β
βΌ
ββββββββββββββββββββ
β Query Classifier β β Detects intent, extracts filters, identifies boost keywords
ββββββββββ¬ββββββββββ
β
ββββββ΄βββββ
βΌ βΌ
βββββββββ ββββββββ
β FAISS β β BM25 β β Parallel semantic + keyword retrieval
β(top-8)β β(top-8β
βββββ¬ββββ ββββ¬ββββ
β β
βΌ βΌ
ββββββββββββββββββββ
β Reciprocal Rank β β Fuses both rankings without tuned weights
β Fusion β
ββββββββββ¬ββββββββββ
β
βΌ
ββββββββββββββββββββ
β Metadata Filter β β Reranks by deadline proximity, region match, type match
ββββββββββ¬ββββββββββ
β
βΌ
ββββββββββββββββββββ
β Groq LLM β β Llama 3.3 70B generates grounded response with citations
β (Llama 3.3) β
ββββββββββ¬ββββββββββ
β
βΌ
Answer + Sources
Tech Stack (All Free)
| Component | Tool | Why |
|---|---|---|
| LLM | Groq (Llama 3.3 70B) | Free API, fast inference, high quality |
| Embeddings | HuggingFace all-MiniLM-L6-v2 |
Runs locally, no API cost, good quality |
| Vector Store | FAISS | In-memory, no server needed, fast similarity search |
| Keyword Search | BM25 (rank-bm25) | Complements semantic search for exact-term matching |
| Framework | LangChain | Industry-standard RAG orchestration |
| Frontend | Streamlit | Fast prototyping, built-in chat UI |
Quick Start
1. Clone and install
git clone <repo-url>
cd ijnet-assistant
pip install -r requirements.txt
2. Get a Groq API key (free)
- Go to console.groq.com
- Sign up for a free account
- Create an API key
3. Set your API key
export GROQ_API_KEY="gsk_your_key_here"
4. Build the vector index
python -m src.ingest
This loads the knowledge base, chunks documents, generates embeddings, and saves the FAISS index.
5. Run the evaluation (optional)
python evaluate.py
Validates retrieval accuracy (17 tests covering classification + retrieval).
6. Launch the chatbot
streamlit run app.py
Open http://localhost:8501 in your browser and start asking questions.
Example Queries
| Query | What it tests |
|---|---|
| "What opportunities are available for investigative journalists in Africa?" | Region + topic filtering |
| "Find fellowships with deadlines in the next 30 days" | Deadline extraction + type filtering |
| "What resources does IJNet have on AI tools for journalists?" | Topic-based retrieval across articles + opportunities |
| "Can you summarize the latest opportunities for product/design people in newsrooms?" | Cross-document synthesis |
| "Which IJNet newsletter should I subscribe to?" | Specific article retrieval |
| "What grants are available for data journalism projects?" | Type + topic filtering |
| "Tell me about digital security tools for journalists" | Article retrieval |
Project Structure
ijnet-assistant/
βββ app.py # Streamlit chat interface
βββ evaluate.py # Retrieval accuracy tests
βββ scraper.py # IJNet public page scraper (optional)
βββ requirements.txt
βββ README.md
βββ data/
β βββ knowledge_base.json # Mock IJNet corpus (20 opportunities + 6 articles)
β βββ faiss_index/ # Persisted FAISS vector store (generated)
βββ src/
βββ __init__.py
βββ ingest.py # Document loading, chunking, embedding
βββ retriever.py # Hybrid retrieval (FAISS + BM25 + RRF)
βββ chain.py # RAG chain connecting retriever to Groq LLM
Design Decisions
Why Hybrid Retrieval?
Pure semantic search misses exact keyword matches (e.g., "Pulitzer Center" might not be the top semantic match for "reporting grants"). Pure keyword search misses meaning (e.g., "accountability journalism" wouldn't match "investigative reporting"). Combining both with Reciprocal Rank Fusion gives the best of both worlds without needing to tune relative weights.
Why One-Chunk-Per-Opportunity?
Each opportunity is a self-contained record (~200 words). Splitting it would lose context (e.g., separating eligibility from the description). Keeping it whole means the LLM always gets complete opportunity details.
Why Query Classification?
A naive "embed and retrieve" approach fails on queries like "fellowships with deadlines in the next 30 days" β the embedding doesn't understand calendar math. The classifier extracts structured filters (deadline window, region, type) and applies them post-retrieval, giving much better precision.
Why Low Temperature (0.1)?
This is a factual Q&A system. Higher temperatures increase creativity but also hallucination risk. At 0.1, the LLM sticks closely to the provided context.
Limitations & Future Improvements
Current Limitations
- Static corpus: The knowledge base is a fixed JSON file; in production, this would connect to IJNet's CMS/database
- Deadline reasoning: Deadlines are compared numerically; the LLM doesn't do real date arithmetic
- No re-ranking model: A cross-encoder re-ranker (e.g.,
ms-marco-MiniLM) would improve precision further - Single language: Currently English-only; IJNet serves 8 languages
Potential Improvements
- Live data pipeline: Connect to IJNet's RSS feeds or API for real-time opportunity updates
- Cross-encoder re-ranking: Add a re-ranker between retrieval and generation for better precision
- Multi-language support: Use multilingual embeddings (
paraphrase-multilingual-MiniLM-L12-v2) and add language detection - Structured queries: For deadline/region filters, use SQL-like queries on a structured database alongside the vector search
- User personalization: Let users set preferences (region, topics, career stage) for personalized recommendations
- Evaluation on real queries: Collect user queries to build a proper evaluation set
License
MIT β built as a prototype for IJNet chatbot assistance.