Skip to content
RollingBeatlePublic

About

Spanish-language RAG over Colombian (6 months) news using Ollama, Qdrant, LangGraph. Completely locally run.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

34 Commits

Folders and files

Repository files navigation

Col-Feeds

Offline RAG agent over a corpus of Colombian news. Locally run, using Ollama.

The agent answers questions strictly from a corpus of Colombian news collected from 92 RSS local sources. The reference build spans ~120,000 articles from November 2025 to June 2026; the corpus isn't redistributed here (see Data), but the pipeline reproduces it and a sample is included.

Refusal (Agent only works with corpus, refusing to answer with its own knowledge)

What you can do

Ask it something the corpus covers, and it answers with sources and dates (in spanish):

$ colfeeds-agent "¿Quién ganó las elecciones presidenciales de 2026?"

Según los resultados disponibles en mi archivo de noticias colombianas entre enero y junio de 2026:

**Abelardo De la Espriella** fue declarado ganador del preconteo inicial de la segunda vuelta presidencial celebrada el **21 de junio de 2026**. Iván Cepeda reconoció este resultado preliminar pero anunció que pediría una impugnación y verificación de más de 33 mil mesas electorales (Metropolitano, 2026-06-21).

En la primera vuelta presidencial realizada a finales de mayo de 2026:
- Iván Cepeda obtuvo el primer lugar con **43,74%** de los votos (El Colombiano, 2026-06-01)
- Abelardo De la Espriella obtuvo **40,90%**, lo que permitió pasar a segunda vuelta

Sin embargo, no tengo información definitiva sobre el resultado final confirmado después del escrutinio completo y las impugnaciones. Los resultados preliminares indicaban ganador a Abelardo De la Espriella en la segunda vuelta (Metropolitano, 2026-06-21).

Architecture

Architecture

Stage Module Does
Collect colfeeds.scrapper.rss_news Fetch RSS feeds → timestamped JSONL
Clean colfeeds.cleaner.data_cleaning Normalize, deduplicate, recover dates, flag quality
Index colfeeds.indexing.index_builder Chunk, embed (bge-m3), load into Qdrant with payload filters
Store colfeeds.indexing.article_storage Full article text in SQLite, keyed by article id
Serve colfeeds.agent.agent LangGraph agent, one filtered search tool, strict-grounding prompt

Stack: Ollama (local inference), Qdrant (vector DB), bge-m3 (multilingual embeddings), LangChain / LangGraph, SQLite. (Local)


Quickstart

Prerequisites

  • Python ≥ 3.11
  • Ollama running natively (see note below)
  • Qdrant via Docker

Install

# fresh environment (conda or venv)
conda create -n colfeeds python=3.11 -y && conda activate colfeeds

pip install -e .

Bring up the services

ollama serve &
ollama pull {model}     
ollama pull bge-m3

docker run -d -p 6333:6333 \
  -v $(pwd)/qdrant_storage:/qdrant/storage qdrant/qdrant

Data

The full corpus (~128k articles) is not included in this repo as it is scraped third-party news content. A sample and collector is included:

  • src/data/sample-feed.jsonl — 200 real records, enough to run the entire cleaning → storage pipeline end-to-end without scraping anything.
  • The collector, so you can build a corpus of any size from the 92 sources in config/sites.json.

These generate the directories cleaned_news/ and storage/ to handle data locally.

Usage

Try it on the sample

# clean the 200-record sample into a corpus (expect: kept 200 of 200)
python -m colfeeds.cleaner.data_cleaning src/data/sample-feed.jsonl \
    cleaned_news/corpus.jsonl --report cleaned_news/report.json

# with Ollama + Qdrant up, index and query it
colfeeds-index cleaned_news/corpus.jsonl --collection news_sample
colfeeds-agent "..."       # ask about something in the sample window

Build your own corpus

# 1. Collect — fetch feeds into timestamped JSONL under data/
colfeeds-collect --output news-run
colfeeds-collect --source eltiempo --output eltiempo-only   # single source
#    scripts/auto.sh runs this on a schedule via cron

# 2. Clean — normalize + deduplicate a whole directory of raw JSONL
python -m colfeeds.cleaner.data_cleaning ./data cleaned_news/corpus.jsonl \
    --report cleaned_news/report.json

# 3. Index — embed and load into Qdrant (+ build the SQLite store)
colfeeds-index cleaned_news/corpus.jsonl
#    --limit 1000 --collection news_test   for a fast trial run

# 4. Ask
colfeeds-agent "¿Qué pasó con la vía Panamericana?"
colfeeds-agent --chat                      # interactive, remembers context

Design decisions

  • Dense-only retrieval, for now. Embeddings alone (cosine over bge-m3) handled proper nouns well enough in testing. Sparse/BM25 hybrid is a documented next step, deferred until real proper-noun misses appear rather than added speculatively.

  • bge-m3, not an English-first embedder. The corpus is 100% Spanish; a multilingual model measurably outranks English-centric ones here. Single highest-impact retrieval choice.

  • One article ≈ one vector. Median article is 390 characters (most RSS feeds do not expose complete articles), so most aren't chunked at all. Only long pieces (live blogs) are split, capped at 10 chunks so a single 74k-character live-blog can't flood results. Query-time parent deduplication is the cleaner long-term fix and is noted.

  • Strict grounding by construction. The agent has exactly one (for now) tool and a system prompt that forbids answering from parametric knowledge; empty retrieval yields a fixed refusal sentence. temperature=0 keeps tool arguments and answers deterministic.

  • Local everything. Ollama + Qdrant + SQLite.

Data challenges

Six months of real RSS is messy:

  • Dates. RSS's actual standard is RFC 822 (Sat, 24 Jan 2026 20:56:36 -0500), which datetime.fromisoformat rejects outright and dropping around 30% of records in a naive parser. A tolerant multi-format parser (news_dates.py) recovers them, with a date_source field marking whether a date was parsed or approximated so the agent can exclude approximate dates from strict chronology queries.

  • Deduplication. Beyond exact duplicates, the same wire story is republished across many outlets; a fingerprint pass collapses near-duplicates.


Evaluation

A 15-case suite (tests/evaluate_agent.py, tests/evaluation_set.py) covers five behaviors, scored by rule:

Category Cases Checks
Grounded factual 5 correct answer with source+date citation
Refusal, out-of-scope 3 refuses even when the model "knows" the answer
Refusal, out-of-window 2 refuses Colombian topics outside Nov 2025–Jun 2026
Date-filtered 2 exercises the since/until retrieval filter
Multi-turn 3 follow-ups resolve against conversation memory

Honest limits: scoring is keyword-based, so it confirms answers are on-topic and cited, not that every fact is correct. There are no adversarial "adjacent-but-insufficient" cases yet (where the corpus has related content that shouldn't be treated as an answer).

RAGAS judge evaluation is in development in branch test-ragas

Improvements

  • Hybrid search when proper-noun misses show up
  • Query-time parent deduplication
  • Context-window trimming for long multi-turn sessions
  • Ragas eval

License

MIT See LICENSE.

Acknowledgements

Built with Ollama, Qdrant, LangChain/LangGraph, and bge-m3.

About

Spanish-language RAG over Colombian (6 months) news using Ollama, Qdrant, LangGraph. Completely locally run.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages