In Module 1, you built a chatbot — it reads text and responds. Now you build an agent — it decides what actions to take, runs them, reads the results, and keeps going until it has an answer.
- Chatbot vs Agent — a chatbot answers questions. An agent takes actions. Our agent runs Docker commands on its own to diagnose problems.
- Tool calling — the LLM doesn't just generate text. It picks which Python function to call and with what arguments. We give it 3 Docker tools and it decides which ones to use.
- ReAct pattern — Reason ("I should check the logs"), Act (call
get_logs), Observe (read the output), repeat until done. LangChain handles this loop for us.
One file: agent.py
Three tools defined as plain Python functions:
@tool
def list_containers() -> str:
"""List all Docker containers (running and stopped)."""
result = subprocess.run(["docker", "ps", "-a"], capture_output=True, text=True)
return result.stdout or result.stderrThe @tool decorator tells LangChain "the LLM can call this". The docstring becomes the tool's description — the LLM reads it to decide when to use it.
The agent is created in two lines:
llm = ChatOllama(model="gemma4", temperature=0)
agent = create_react_agent(llm, [list_containers, get_logs, inspect_container])That's it. LangChain wires up the ReAct loop — the LLM reasons, picks a tool, we run it, feed the result back, repeat.
First, create a broken container:
docker run -d --name broken-app nginx:alpine sh -c "echo 'app starting...' && sleep 2 && exit 1"Then run the agent:
python3 module-2/agent.pyAsk it:
- "Why is broken-app crashing?"
- "What containers are running?"
- "Show me the logs for broken-app"
Watch it decide which tools to call on its own.
Clean up when done:
docker rm -f broken-app- Add a 4th tool — maybe
stop_container(name)orrestart_container(name) - Run multiple broken containers and ask "which containers have problems?"
- Look at the agent's reasoning — it prints what it's thinking before each tool call