AI News HubLIVE
In-site rewrite6 min read

An educational lab of AI agent architectures

An educational lab of AI agent architectures built on LangChain and local Ollama, offering multiple agent variants for chat, tool calling, RAG, hybrid, and agentic RAG modes.

SourceHacker News AIAuthor: ilia53

Notifications You must be signed in to change notification settings

Fork 0

Star 1

BranchesTags

Open more actions menu

Folders and files

NameName

Last commit message

Last commit date

Latest commit

History

8 Commits

8 Commits

data/corpus

data/corpus

rules

rules

skills

skills

src/localagent

src/localagent

.env.example

.env.example

.gitignore

.gitignore

README.md

README.md

SOUL.md

SOUL.md

mcp.json.example

mcp.json.example

png.png

png.png

pyproject.toml

pyproject.toml

uv.lock

uv.lock

Repository files navigation

An educational lab of AI agent architectures, built on LangChain and a local Ollama server. Each variant is a separate, runnable CLI so you can study one mechanism at a time and watch it work through the logs.

Model (generation): gemma4:e4b

Model (embeddings): mxbai-embed-large:latest

Host: http://10.100.102.10:11434 (configurable)

Chat provider: local Ollama by default, or OpenRouter with LLM_PROVIDER=openrouter

What is included

Category Variant Command

Chat + memory full buffer chat-buffer

Chat + memory summary buffer chat-summary

Tool calling ReAct text protocol tools-react

Tool calling native bind_tools tools-native

RAG only pure numpy cosine rag-numpy

RAG only Chroma vector DB rag-chroma

RAG only LlamaIndex (in-memory) rag-llamaindex

RAG only LlamaIndex + Chroma store rag-llamaindex-chroma

RAG only Haystack rag-haystack

RAG only hybrid BM25+dense + cross-encoder rerank rag-rerank

Hybrid (chat + RAG) semantic router hybrid-semantic

Hybrid (chat + RAG) LLM classifier router hybrid-llm

Hybrid (chat + RAG) adaptive RAG (LangGraph) hybrid-adaptive

Hybrid (chat + RAG) corrective RAG (multi-grader + rewrite) hybrid-adaptive-plus

Agentic RAG tool-calling + retrieval-as-a-tool agentic-rag

The chat commands accept --persist to save memory to SQLite across runs.

Setup

Install dependencies into a virtual environment:

uv sync

Configure the connection (copy and edit if your host differs):

copy .env.example .env

To use OpenRouter for chat generation, set LLM_PROVIDER=openrouter, OPENROUTER_API_KEY, and OPENROUTER_MODEL in .env. Embeddings still use Ollama via EMBED_MODEL, so RAG commands still need the local embedding model.

Running

Every agent is its own console script. Run any of them with uv run .

Chat + memory:

uv run chat-buffer

uv run chat-summary --persist

Tool calling:

uv run tools-react

uv run tools-native

RAG (one backend each):

uv run rag-numpy

uv run rag-chroma

uv run rag-llamaindex

uv run rag-llamaindex-chroma

uv run rag-haystack

uv run rag-rerank

Hybrid (chat + RAG):

uv run hybrid-semantic

uv run hybrid-llm

uv run hybrid-adaptive

uv run hybrid-adaptive-plus

Agentic RAG and evaluation:

uv run agentic-rag

uv run rag-eval

Common flags: --no-soul (all agents), --persist (chat), --no-index and --drop (RAG/hybrid/agentic).

What each command does (in plain words)

chat-buffer - a chatbot that remembers everything you said in this chat.

chat-summary - a chatbot that remembers a short summary when the chat gets long.

tools-react - a bot that can use tools (like a calculator) by writing them out as text.

tools-native - the same idea, but it calls tools the "official" way the model supports.

rag-numpy - answers questions by first reading your documents; the simplest version.

rag-chroma - the same, but it saves what it read so it does not re-read every time.

rag-llamaindex / rag-haystack - the same idea built with the LlamaIndex / Haystack libraries.

rag-llamaindex-chroma - LlamaIndex doing the reading, with the saved store from Chroma.

rag-rerank - a smarter search: it looks two ways and then re-sorts results to pick the best.

hybrid-semantic / hybrid-llm - decides "should I read the docs or just chat?" before answering.

hybrid-adaptive - reads the docs first, then checks "is this useful?" and chats if not.

hybrid-adaptive-plus - the careful version: checks each document, the answer, and retries if needed.

agentic-rag - the bot decides by itself when to search the documents, when to use a tool, or just answer. Searching the documents is given to it as one of its "tools".

rag-eval - not a chat. It is a small test that scores how well each RAG version finds the right document, so you can compare them with numbers.

Inside the REPL: type a message, or use slash commands.

/skills lists available skills (a numbered menu).

/ or / loads a skill into the active context.

/add [path] ingests a text file into the RAG store live, without restarting. With no path (or an invalid one), it scans data/corpus/ and adds any new files not yet indexed. Works for RAG and hybrid agents; persists for Chroma.

/win prints the full context window currently sent to the model.

/active, /clear, /rules, /help, /exit.

MCP servers (extra tools)

The tool-calling agents (tools-react, tools-native) can use tools from any Model Context Protocol server. Copy the example and edit it:

copy mcp.json.example mcp.json

Each entry is either a stdio server (command + args) or an HTTP server (url + transport). On startup the agents connect, load the servers' tools, and merge them with the built-ins (built-ins win on name clashes). If mcp.json is absent or a server fails, the agents simply run with the built-in tools.

Set MCP_CONFIG to point at a different config file if needed.

Skills and rules

SOUL.md (project root) is a global persona/identity prepended to every agent's system prompt. Every agent accepts --no-soul to skip loading it.

Agents can write durable facts into the managed ## Memory section of SOUL.md (between memory:start/memory:end markers, append-only + deduped): use the /remember command in any agent, or the remember tool in the tool-calling agents. New memories apply on the next turn (no restart).

rules/ holds always-on markdown that is injected into every agent's system prompt automatically.

skills/ holds opt-in markdown skills loaded on demand via the / menu. Drop a new .md file in skills/ (optionally with name:/description: frontmatter) and it appears in the menu.

How RAG works

All four RAG backends implement one Retriever interface (index / search) and return the same RetrievedChunk(text, source, score), so the agents are identical regardless of the engine behind them. The flow:

At startup (index once):

Load every file in data/corpus/ and split it into chunks.

Send the chunks to Ollama mxbai-embed-large to get one vector per chunk.

Store the vectors in the backend (numpy matrix, Chroma, LlamaIndex, Haystack).

Per query (search):

Embed the query with the same model.

Score every chunk by cosine similarity and take the top-k (default 4).

Answer (retrieve-then-read):

If nothing is retrieved, the agent replies "I don't know".

Otherwise it builds Context: [source] chunk... / Question: ... and sends it to gemma4:e4b with a system rule: answer only from the context and cite sources as [source].

Retrieval hits (sources + scores) and the LLM call are logged.

Two defining traits: the RAG-only agents are stateless (each query is independent, no conversation memory), and embeddings come from a different model (mxbai-embed-large) than generation (gemma4:e4b).

The backends

pure-numpy (rag-numpy): the most transparent. Chunks by character window with overlap, L2-normalizes vectors, and does a brute-force dot product (matrix @ query_vec) for cosine similarity. No database, O(N) per query - great for understanding, poor for scale.

pure-chroma (rag-chroma): same idea but vectors live in a persistent Chroma collection (.chroma/) that performs the nearest-neighbor search. It is the only backend that survives restarts: if the collection already holds data it is reused instead of re-indexed.

llamaindex (rag-llamaindex): uses LlamaIndex's VectorStoreIndex with a sentence splitter and Ollama embeddings/LLM, kept in memory.

llamaindex-chroma (rag-llamaindex-chroma): same LlamaIndex orchestration, but with a persistent Chroma vector store behind it. This shows the common production layering - a framework owns the pipeline while an external, scalable store owns the vectors. Contrast it with rag-chroma, where our own code does the orchestration and Chroma is driven directly.

haystack (rag-haystack): uses Haystack components (document splitter, in-memory store, embedding retriever) wired into the same interface.

Because the chunking differs (numpy/chroma split by characters; LlamaIndex and Haystack split by sentences/words), the same question can retrieve slightly different passages across backends - a good thing to compare.

rerank (rag-rerank): a production-style retrieval stack on a small local scale. It runs hybrid retrieval - dense (Ollama embeddings, cosine) plus sparse (BM25 keyword) - fuses the two rankings with Reciprocal Rank Fusion, then reranks the candidates with a cross-encoder (cross-encoder/ms-marco-MiniLM-L-6-v2 by default) and keeps the top-k. The cross-encoder gives much sharper relevance separation than raw cosine (clearly positive scores for the right chunk, strongly negative for the rest). The model downloads on first run; configure it via RERANK_MODEL and the candidate pool via FUSION_CANDIDATES.

RAG storage flags

Every RAG agent (and every hybrid agent) accepts:

--no-index - start without indexing/uploading the corpus. The in-memory backends start empty; Chroma uses whatever is already persisted.

--drop - drop persisted RAG storage before starting (Chroma only; a no-op for the in-memory backends), forcing a clean rebuild.

By default the in-memory backends index the corpus on every run, while Chroma reuses its persisted collection if present.

uv run rag-chroma --drop

uv run rag-chroma --no-index

Agentic RAG

agentic-rag is a different take on combining tools and retrieval: instead of a router deciding RAG-vs-chat up front, retrieval is exposed to a native tool-calling agent as a search_knowledge_base tool (backed by the hybrid BM25+dense+rerank engine). The model decides per turn whether to search the corpus, use another tool (calculator, shell, web_search, MCP), or answer directly. It is instructed to prefer the knowledge base for factual questions.

uv run agentic-rag

Production features

Human-in-the-loop: dangerous tools (currently shell) require confirmation before running. Controlled by REQUIRE_TOOL_APPROVAL (default true); the user is prompted run this? [y/N] and can deny.

Reliability: every LLM call retries on transient errors/timeouts (LLM_MAX_RETRIES) and generation/tool calls fall back to a second model (FALLBACK_MODEL, default llama3.1:8b) if the primary fails.

Observability (LangSmith): set LANGSMITH_TRACING=true and LANGSMITH_API_KEY to trace every chain/LLM/tool step in LangSmith. No-op when disabled.

Evaluation: uv run rag-eval runs a small labelled question set against the RAG backends and prints hit-rate and context-recall, for objective comparison and regression checks.

uv run rag-eval

Logging

Each agent writes two channels:

A readable, bordered console stream (no colors or emojis).

A structured JSON-lines file at logs/.jsonl capturing LLM calls (prompt, response, latency, tokens), tool calls, retrieval hits with scores, routing decisions, and errors.

Tail a log to monitor an agent, for example:

Get-Content logs/hybrid-adaptive.jsonl -Wait

Architecture

src/localagent/ config.py settings from .env (host, models, chunking, logging) llm.py ChatOllama / OllamaEmbeddings factories + token usage logging_setup.py dual console + JSON logging memory.py buffer and summary-buffer memory with SQLite persistence skills.py skill discovery for the / menu rules.py always-on rules loader cli.py REPL and slash-command engine tools/ example tools: calculator, shell, read_file, web_search (DuckDuckGo) mcp_tools.py optional Model Context Protocol tool loading (mcp.json) rag/ common Retriever interface + numpy, chroma, llamaindex, haystack agents/

[truncated for AI cost control]