Build a local, private AI agent that reads log files and explains what happened, running entirely on your machine with Ollama, orchestrated with LangGraph and LangChain.
The idea that keeps it reliable: the tool does the math, the LLM writes the story. A plain Python function counts the log levels (and is unit-tested without any LLM); the agent just calls that tool and summarizes the result, so the numbers are always right even if a small local model is imperfect at tool calls.
- What an agent is: an LLM that can decide to call tools
- Wrap a Python function as a LangChain
@tool - Run a local LLM with
ChatOllama(no API keys, no cloud) - Build an agent with
create_agent, and see the same loop by hand with a LangGraphStateGraph
Ollama running with a model pulled:
ollama serve
ollama pull llama3.2 # default (small, ~2GB)
curl http://localhost:11434/api/tags # should list llama3.2llama3.2 reliably calls the tool but its summaries can be generic. For sharper
output, pull a stronger model and set OLLAMA_MODEL:
ollama pull qwen3-coder:30b
OLLAMA_MODEL=qwen3-coder:30b python run_agent.pypython3 -m venv venv
source venv/bin/activate
pip install -r requirements.txtapp.log— sample loglog_utils.py— deterministic counting core (no LLM)run_agent.py— the agent (create_agent, plus a hand-builtStateGraph)test_log_utils.py— tests for the counting core
python -m pytest . # counting core, no Ollama needed
python run_agent.py # the agent (needs Ollama)
python run_agent.py custom # the hand-built StateGraph versionThe demo prints the deterministic counts first, then the agent's summary, so you
always see correct numbers. create_agent (from langchain.agents) is the
LangChain 1.0 API; run_agent.py custom shows the same agent/tools loop built by
hand with StateGraph, ToolNode and a conditional edge.
Wire up the agent yourself:
- Create a
ChatOllamamodel (base_url="http://localhost:11434",temperature=0). - Pass the
analyze_log_filetool tocreate_agent(model, tools=[...], system_prompt=...). - Invoke it with a
HumanMessageasking it to analyzeapp.log, and print the result. - Bonus: add a second tool (e.g. return the last N lines) and watch how it chooses.