M00B: Hello World — Three Ways to Build an Agent

Before you spend 30 modules on agent theory, build the same tiny agent three different ways — raw OpenAI SDK, CrewAI, and LangChain — in one sitting. By the end, you'll know which open-source stack each later module is teaching you, and why.

Learning Objectives

  • Build the same "time helper" agent three different ways: raw OpenAI SDKThe openai Python/JS package pointed at a local Ollama server — zero cloud costs, same interface as the OpenAI API., CrewAI, and LangChain
  • Write the tool-use loopA while-loop that calls the model, checks if it requested a tool, runs that tool, appends the result as a "tool" role message, and loops until the model returns a final answer with no tool calls. by hand — then see what CrewAI and LangChain abstract away
  • Register a @tool decorated function with both CrewAI and LangChain and compare the API differences
  • Identify which approach matches your situation: learning, shipping fast, or building composable pipelines
  • Map the three levels (Raw → Framework → Chain) to the modules you'll meet later in this course

Why Three Approaches?

Everyday Analogy

BEFORE: Imagine three ways to make morning coffee. You can grind beans by hand, heat water on the stove, and pour-over with a kettle — full control, every variable yours. Or you press a button on an espresso machine — the machine handles temperature, pressure, and timing, you just pick the bean and the cup size. Or you tell your phone "make my usual latte before 7am" and a smart appliance follows the recipe you wrote last week.

PAIN: If you only ever pressed the button, you'd never understand why your coffee tastes bitter when the grind is wrong. If you only ever ground by hand, you'd never ship coffee at any reasonable scale. And if you only ever wrote recipes, you'd be lost the day the appliance breaks and someone hands you raw beans.

MAPPING: Building agents has the same three modes. Raw OpenAI SDK is grinding by hand — you write the loop, manage state, parse tool calls. CrewAI is the espresso machine — you declare agents, tasks, and tools, CrewAI runs the loop. LangChain is the recipe — you compose a chain with LCEL, and the executor handles the agent loop. This module shows you all three with the same toy agent so the differences are obvious, not abstract.

Technical Definition — The Three Tiers

Level 1 (Raw OpenAI SDK): You write the tool-use loop yourself. Modules M01–M11 use this so you can see every moving part.

Level 2 (CrewAI): Declare agents, tasks, and tools — CrewAI handles orchestration, tool dispatch, and retries. From M12+ in this course.

Level 3 (LangChain): LCEL chains compose tools, prompts, and models declaratively. AgentExecutor runs the loop. Used in advanced pipelines.

The Abstraction Ladder

Each approach hides more of the machinery. The animation below climbs the ladder one rung at a time. Watch the red boxes (the parts you have to write) shrink, and the cyan/pink boxes (the parts the SDK or generator handles) grow.

The Abstraction Ladder — What You Write vs What's Provided
Approach 1
Raw OpenAI SDK
Tool-use loop (you)
Tool dispatch (you)
JSON tool schemas (you)
Message accumulation (you)
HTTP — chat.completions.create
Lines of code: ~50 · Mental load: high
Approach 2
CrewAI
Crew.kickoff() loop (CrewAI)
Tool dispatch (CrewAI)
@tool decorator (you)
Agent + Task config (you)
HTTP — ollama/mistral
Lines of code: ~25 · Mental load: medium
Approach 3
LangChain
LCEL prompt chain (you)
AgentExecutor loop (LangChain)
@tool decorator (you)
ChatOllama wrapper (LangChain)
HTTP — ollama/mistral
Lines of code: ~20 · Mental load: composable
Why It Matters

If you skip Level 1 and start with CrewAI or LangChain, you'll panic the first time a tool call fails or the loop hangs — you won't know what's actually happening underneath. If you stay at Level 1 forever, you'll re-implement the same boilerplate in every project and miss out on retry logic, task orchestration, and chain composability. The course walks the ladder in order. This module is your first peek at every rung at once.

Setup (5 minutes)

You'll build all three versions in a single project folder. Each version is self-contained so you can run them independently.

STEP 1Create the project and a virtual environment.

bash
mkdir hello-agent && cd hello-agent
python -m venv .venv
# macOS / Linux:
source .venv/bin/activate
# Windows PowerShell:
# .venv\Scripts\Activate.ps1

STEP 2Install dependencies for all three approaches. The raw approach needs only openai. CrewAI needs crewai with tools extras. LangChain needs the community + core packages.

bash
# Python packages
pip install openai                          # Approach 1 — raw OpenAI SDK
pip install "crewai[tools]"                 # Approach 2 — CrewAI
pip install langchain langchain-community langchain-core  # Approach 3 — LangChain

# Node.js packages
npm install openai                          # Approach 1
npm install langchain @langchain/ollama @langchain/core zod  # Approach 3

STEP 3Start Ollama and confirm Mistral is running. All three approaches point to your local Ollama server — no API key needed.

bash
# Start Ollama (if not already running as a service)
ollama serve &

# Confirm mistral is available
ollama list | grep mistral        # should show "mistral:latest"

# Alternative: use Groq cloud (free tier, no GPU needed)
# export GROQ_BASE_URL="https://api.groq.com/openai/v1"
# export GROQ_API_KEY="gsk_..."
✅ Checkpoint: Run python -c "import openai, crewai, langchain; print('ok')" and ollama list. If the first prints ok and the second lists mistral, you're ready.

Approach 1 — Raw OpenAI SDK + Mistral

What you're building: An agent that answers "what time is it in [city]?" by calling a get_time tool. You write the entire loop using the openai Python package pointed at a local Ollama server. Everything you'll learn in M01–M11 lives inside this file.

The Tool-Use Loop — In Plain English

Every agent does the same five-step dance:

  1. Send the conversation so far to Mistral.
  2. Check the response. If msg.tool_calls is None, the agent is done — return msg.content.
  3. If tool_calls is not None, loop over each call, look up the function by name, and run it.
  4. Append the assistant message and a role: "tool" message for every result.
  5. Loop back to step 1.

The Code

Save this as raw_agent.py. Read the comments — they label every step of the loop above.

"""raw_agent.py — Hello World, hand-rolled tool-use loop with OpenAI SDK + Mistral."""
import json
from datetime import datetime
from zoneinfo import ZoneInfo
from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:11434/v1",  # Ollama local endpoint
    api_key="ollama",                      # required by SDK but ignored by Ollama
)

# ---- Tool implementation (plain Python) ----
TIMEZONES = {
    "new york": "America/New_York",
    "london": "Europe/London",
    "tokyo": "Asia/Tokyo",
    "sydney": "Australia/Sydney",
    "san francisco": "America/Los_Angeles",
}

def get_time(city: str) -> str:
    tz_name = TIMEZONES.get(city.lower())
    if tz_name is None:
        return f"Unknown city: {city}. Known cities: {', '.join(TIMEZONES)}."
    now = datetime.now(ZoneInfo(tz_name))
    return now.strftime("%H:%M on %A, %d %b %Y")

# ---- Tool schema (OpenAI function-calling format) ----
TOOLS = [{
    "type": "function",
    "function": {
        "name": "get_time",
        "description": "Get the current local time in a major city.",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "City name, e.g. Tokyo"}
            },
            "required": ["city"],
        },
    },
}]

# ---- The loop ----
def run_agent(user_message: str) -> str:
    messages = [{"role": "user", "content": user_message}]
    while True:
        resp = client.chat.completions.create(
            model="mistral",
            messages=messages,
            tools=TOOLS,
            tool_choice="auto",
        )
        msg = resp.choices[0].message

        # Step 2: did Mistral finish?
        if msg.tool_calls is None:
            return msg.content

        # Step 3+4: Mistral requested tool calls — dispatch each one.
        messages.append(msg)
        for tc in msg.tool_calls:
            args = json.loads(tc.function.arguments)
            if tc.function.name == "get_time":
                result_text = get_time(args["city"])
            else:
                result_text = f"Unknown tool: {tc.function.name}"
            messages.append({
                "role": "tool",
                "tool_call_id": tc.id,
                "content": result_text,
            })
        # Step 5: loop back

if __name__ == "__main__":
    print(run_agent("What time is it in Tokyo right now?"))
// raw_agent.mjs — Hello World, hand-rolled tool-use loop with OpenAI SDK + Ollama.
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "http://localhost:11434/v1",  // Ollama local
  apiKey: "ollama",
});

const TIMEZONES = {
  "new york": "America/New_York",
  "london": "Europe/London",
  "tokyo": "Asia/Tokyo",
  "sydney": "Australia/Sydney",
  "san francisco": "America/Los_Angeles",
};

function getTime(city) {
  const tz = TIMEZONES[city.toLowerCase()];
  if (!tz) return `Unknown city: ${city}. Known cities: ${Object.keys(TIMEZONES).join(", ")}.`;
  return new Intl.DateTimeFormat("en-GB", {
    timeZone: tz, hour: "2-digit", minute: "2-digit",
    weekday: "long", day: "2-digit", month: "short", year: "numeric",
  }).format(new Date());
}

const TOOLS = [{
  type: "function",
  function: {
    name: "get_time",
    description: "Get the current local time in a major city.",
    parameters: {
      type: "object",
      properties: { city: { type: "string", description: "City name, e.g. Tokyo" } },
      required: ["city"],
    },
  },
}];

async function runAgent(userMessage) {
  const messages = [{ role: "user", content: userMessage }];
  while (true) {
    const resp = await client.chat.completions.create({
      model: "mistral",
      messages,
      tools: TOOLS,
      tool_choice: "auto",
    });
    const msg = resp.choices[0].message;

    if (!msg.tool_calls) return msg.content;

    messages.push(msg);
    for (const tc of msg.tool_calls) {
      const args = JSON.parse(tc.function.arguments);
      const text = tc.function.name === "get_time"
        ? getTime(args.city)
        : `Unknown tool: ${tc.function.name}`;
      messages.push({ role: "tool", tool_call_id: tc.id, content: text });
    }
  }
}

console.log(await runAgent("What time is it in Tokyo right now?"));
What Just Happened?

Two things went out to Mistral (the user message, then a role: "tool" result) and two things came back (a tool_calls list, then final text). You wrote ~50 lines, of which exactly one is the actual API call. The other 49 are loop, dispatch, and message bookkeeping. That bookkeeping is what CrewAI and LangChain abstract away in Approaches 2 and 3 — but knowing it exists is the difference between "the agent froze" and "I forgot to append the tool result message."

Run It

bash
# Python
python raw_agent.py

# Node.js
node raw_agent.mjs
Expected output (yours will differ by minute)
The current local time in Tokyo is 14:37 on Friday, 09 May 2026.

Approach 2CrewAI

Same agent, fewer lines. CrewAI hides the loop, message accumulation, and tool dispatch. You declare agents as role-playing entities with goals, register tools with the @tool decorator, define tasks, and call crew.kickoff(). CrewAI handles everything else.

What CrewAI Adds That Raw Doesn't
  • @tool decorator — turns a Python function into a typed tool with auto-generated schema from the docstring.
  • Agent — a role-playing entity with a goal, backstory, and tool list. The LLM is configured here.
  • Task — describes work to do, expected output, and which agent handles it.
  • Crew — orchestrates agents and tasks. crew.kickoff() runs the full loop.
  • Retry & memory — CrewAI handles failed tool calls and short-term memory across tasks.

The Code

Save this as crewai_agent.py. The entire agent loop is replaced by crew.kickoff().

"""crewai_agent.py — Hello World, CrewAI + Mistral via Ollama."""
from datetime import datetime
from zoneinfo import ZoneInfo
from crewai import Agent, Task, Crew
from crewai.tools import tool

TIMEZONES = {
    "new york": "America/New_York",
    "london": "Europe/London",
    "tokyo": "Asia/Tokyo",
    "sydney": "Australia/Sydney",
    "san francisco": "America/Los_Angeles",
}

# ---- Tool (docstring becomes the tool description) ----
@tool("Get City Time")
def get_time(city: str) -> str:
    """Get the current local time in a major city such as Tokyo or London."""
    tz_name = TIMEZONES.get(city.lower())
    if tz_name is None:
        return f"Unknown city: {city}. Known cities: {', '.join(TIMEZONES)}."
    now = datetime.now(ZoneInfo(tz_name))
    return now.strftime("%H:%M on %A, %d %b %Y")

# ---- Agent (role + goal + tool list) ----
time_agent = Agent(
    role="World Clock Assistant",
    goal="Answer natural-language questions about the current time in any major city.",
    backstory="You help travellers and remote teams find the local time anywhere in the world.",
    tools=[get_time],
    llm="ollama/mistral",   # LiteLLM format: provider/model
    verbose=False,
)

# ---- Task ----
task = Task(
    description="What time is it in Tokyo right now?",
    expected_output="The current local time in Tokyo, including the day of the week.",
    agent=time_agent,
)

# ---- Crew (orchestration) ----
crew = Crew(agents=[time_agent], tasks=[task], verbose=False)
result = crew.kickoff()
print(result)
// CrewAI is Python-only. For Node.js, use a thin wrapper around the OpenAI SDK.
// The pattern below mimics CrewAI's role/goal/tool structure manually.
import OpenAI from "openai";
import { z } from "zod";

const client = new OpenAI({ baseURL: "http://localhost:11434/v1", apiKey: "ollama" });

// Declarative agent config (like CrewAI's Agent class)
const agent = {
  role: "World Clock Assistant",
  goal: "Answer questions about the current time in any major city.",
  tools: {
    get_time: {
      description: "Get the current local time in a major city.",
      schema: z.object({ city: z.string() }),
      fn: ({ city }) => {
        const tz = { "new york":"America/New_York","london":"Europe/London","tokyo":"Asia/Tokyo","sydney":"Australia/Sydney","san francisco":"America/Los_Angeles" }[city.toLowerCase()];
        if (!tz) return `Unknown city: ${city}.`;
        return new Intl.DateTimeFormat("en-GB",{timeZone:tz,hour:"2-digit",minute:"2-digit",weekday:"long",day:"2-digit",month:"short",year:"numeric"}).format(new Date());
      },
    },
  },
};

async function kickoff(task) {
  const messages = [
    { role: "system", content: `You are a ${agent.role}. Goal: ${agent.goal}` },
    { role: "user", content: task },
  ];
  const tools = Object.entries(agent.tools).map(([name, t]) => ({
    type: "function",
    function: { name, description: t.description, parameters: { type:"object", properties:{ city:{type:"string"} }, required:["city"] } },
  }));
  while (true) {
    const resp = await client.chat.completions.create({ model: "mistral", messages, tools, tool_choice: "auto" });
    const msg = resp.choices[0].message;
    if (!msg.tool_calls) return msg.content;
    messages.push(msg);
    for (const tc of msg.tool_calls) {
      const args = JSON.parse(tc.function.arguments);
      messages.push({ role: "tool", tool_call_id: tc.id, content: agent.tools[tc.function.name].fn(args) });
    }
  }
}

console.log(await kickoff("What time is it in Tokyo right now?"));
What Just Happened?

There is no while loop. There is no tool_calls is None check. There is no manual message appending. You declared what role the agent plays, what tools it has, and what task to complete — CrewAI handled when to call tools and how to pass results back. The agent loop went from ~50 lines to a crew.kickoff(). This is the pattern used in Capstone C4 of this course.

Run It

bash
# Python only (CrewAI has no official JS package)
python crewai_agent.py
Expected output
The current local time in Tokyo is 14:37 on Friday, 09 May 2026.
✅ Checkpoint: All three scripts — raw_agent.py, crewai_agent.py — should produce semantically identical answers. If they do, you've just built the same agent at two different abstraction layers.

Approach 3LangChain

LangChain gives you a composable chain approach. Instead of writing the loop (raw) or declaring role-based agents (CrewAI), you build a pipeline: prompt | llm | agent. The AgentExecutor handles the tool-use loop, and LCEL (LangChain Expression Language) lets you compose steps like Unix pipes.

Why LangChain Feels Different

In CrewAI you define who does the work (agents with roles). In LangChain you define how the work flows (chains and steps). Both hide the raw loop, but LangChain's LCEL operator (|) makes it easy to swap components — change the model, add a retriever, or inject a structured output parser — without touching the agent logic. LangChain dominates RAG pipelines and is the most widely used agent framework in Python.

The Code

Save this as langchain_agent.py. The key difference: you compose the agent as a pipeline using LCEL, then hand it to AgentExecutor to run.

"""langchain_agent.py — Hello World, LangChain + ChatOllama."""
from datetime import datetime
from zoneinfo import ZoneInfo
from langchain_community.chat_models import ChatOllama
from langchain.agents import create_openai_tools_agent, AgentExecutor
from langchain_core.tools import tool
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

TIMEZONES = {
    "new york": "America/New_York",
    "london": "Europe/London",
    "tokyo": "Asia/Tokyo",
    "sydney": "Australia/Sydney",
    "san francisco": "America/Los_Angeles",
}

# ---- Tool (docstring becomes the schema description) ----
@tool
def get_time(city: str) -> str:
    """Get the current local time in a major city such as Tokyo or London."""
    tz_name = TIMEZONES.get(city.lower())
    if tz_name is None:
        return f"Unknown city: {city}. Known cities: {', '.join(TIMEZONES)}."
    now = datetime.now(ZoneInfo(tz_name))
    return now.strftime("%H:%M on %A, %d %b %Y")

# ---- LLM (ChatOllama = local Mistral) ----
llm = ChatOllama(model="mistral")
tools = [get_time]

# ---- Prompt (LCEL: system + human + scratchpad placeholder) ----
prompt = ChatPromptTemplate.from_messages([
    ("system", "You help users find the current local time in any city. Always use the get_time tool."),
    ("human", "{input}"),
    MessagesPlaceholder("agent_scratchpad"),  # LangChain injects tool call history here
])

# ---- Agent = LCEL chain: prompt | llm | tool dispatcher ----
agent = create_openai_tools_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=False)

result = executor.invoke({"input": "What time is it in Tokyo right now?"})
print(result["output"])
// langchain_agent.mjs — Hello World, LangChain.js + Ollama.
import { ChatOllama } from "@langchain/ollama";
import { createOpenAIToolsAgent, AgentExecutor } from "langchain/agents";
import { tool } from "@langchain/core/tools";
import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
import { z } from "zod";

const TIMEZONES = {
  "new york": "America/New_York", "london": "Europe/London",
  "tokyo": "Asia/Tokyo", "sydney": "Australia/Sydney",
  "san francisco": "America/Los_Angeles",
};

const getTime = tool(
  ({ city }) => {
    const tz = TIMEZONES[city.toLowerCase()];
    if (!tz) return `Unknown city: ${city}. Known: ${Object.keys(TIMEZONES).join(", ")}.`;
    return new Intl.DateTimeFormat("en-GB", {
      timeZone: tz, hour: "2-digit", minute: "2-digit",
      weekday: "long", day: "2-digit", month: "short", year: "numeric",
    }).format(new Date());
  },
  {
    name: "get_time",
    description: "Get the current local time in a major city.",
    schema: z.object({ city: z.string().describe("City name, e.g. Tokyo") }),
  }
);

const llm = new ChatOllama({ model: "mistral" });
const prompt = ChatPromptTemplate.fromMessages([
  ["system", "You help users find the current local time in any city."],
  ["human", "{input}"],
  new MessagesPlaceholder("agent_scratchpad"),
]);

const agent = await createOpenAIToolsAgent({ llm, tools: [getTime], prompt });
const executor = new AgentExecutor({ agent, tools: [getTime], verbose: false });
const result = await executor.invoke({ input: "What time is it in Tokyo right now?" });
console.log(result.output);

Run It

Run your LangChain agent:

bash
# Python
python langchain_agent.py

# Node.js
node langchain_agent.mjs
Expected output
The current local time in Tokyo is 14:37 on Friday, 09 May 2026.
What Just Happened?

You composed a chain: a ChatPromptTemplate piped into a ChatOllama model, piped into a tool dispatcher — all wired by LCEL. Then AgentExecutor ran the loop. To change behaviour — swap the model, add a retriever, parse structured output — you swap one component in the chain without touching the rest. This composability is why LangChain dominates RAG and multi-step pipeline use cases.

⚠ Ollama Tool Calling Support

Not all Ollama models support function/tool calling. mistral does. If you get errors with other models, try ollama pull mistral and set model="mistral" explicitly. Models known to support tool calling in Ollama: mistral, llama3.1, qwen2.5, command-r.

Side-by-Side

The same agent, three abstractions. Numbers below come from the actual files you just wrote.

Dimension Raw OpenAI SDK CrewAI LangChain
Lines of code you wrote~50~25~20
Where the agent loop livesYour whileCrew.kickoff()AgentExecutor
Tool definitionHand-written JSON schema@tool decorator + docstring@tool decorator + docstring
Multi-agent supportBuild yourselfBuilt-in (Crew of Agents)Via LangGraph or agents
RAG / chainsBuild yourselfLimitedFirst-class LCEL support
Best forLearning the protocolRole-based multi-agent systemsComposable pipelines & RAG
Course useM01–M11 (foundations)Capstone C4 onwardM09–M11 (RAG modules)

When to Use Which

Use Raw OpenAI SDK when…

You're learning. The first time you build an agent, write the loop yourself — once. Then you'll never confuse "agent magic" with "framework convenience".

You need fine-grained control: custom retry logic, unusual streaming, or embedding the call in an existing event loop.

You're debugging a CrewAI or LangChain problem and want to see exactly what the model is (or isn't) returning.

Use CrewAI when…

You're building a multi-agent system where agents collaborate on tasks — a researcher, a writer, a reviewer all working in sequence or in parallel.

You think in terms of roles and responsibilities. CrewAI's role/goal/backstory model maps naturally to team structures.

You want built-in retry, memory, and task delegation without writing the orchestration yourself.

Use LangChain when…

You're building a RAG pipeline, a document QA system, or any flow where data passes through multiple transformation steps.

You want to swap components (model, retriever, parser) without rewriting the agent logic — LCEL's pipe operator makes this easy.

You need the broadest ecosystem: thousands of integrations, extensive docs, and the largest community of any agent framework.

The Bigger Picture

These approaches complement each other. By the time you reach Capstone C4, you'll start with raw debugging to understand a failure, redesign with CrewAI for multi-agent coordination, and consider LangChain for the RAG retrieval steps. M00B was the preview. M01 starts the film.

Knowledge Check

Five quick questions to lock in the difference between the three approaches before you move on to M01.

Q1: In the raw OpenAI SDK approach, what tells the agent loop to stop?

AAn empty choices array on the response
Bmsg.tool_calls is None — no tools requested, return msg.content
CA finished: true field on the last message
DCrewAI fires a stop event automatically
Correct! With the OpenAI tool-calling format, you check msg.tool_calls. If it's None (or empty), the model is done. If it's populated, dispatch the calls and loop back.

Q2: Which single line in the CrewAI version replaces the entire while loop from the raw version?

AAgent(...)
B@tool("Get City Time")
Ccrew.kickoff()
DTask(...)
Correct! crew.kickoff() runs the full orchestration: it sends messages, dispatches tools, handles retries, and returns the final result. The decorator and config classes are setup; kickoff is execution.

Q3: You're building a system where a document gets retrieved, summarised, and then fact-checked. Which framework fits best?

ALangChain — LCEL chains compose steps naturally
BRaw OpenAI SDK — most control over each step
CCrewAI — best for role-based multi-agent systems
DAll three are equally suitable
Correct! A retrieve → summarise → verify pipeline is exactly what LCEL chains are designed for. Each step is a composable component. CrewAI would model this as three agents, which is also valid but more ceremony for a linear pipeline.

Q4: Your CrewAI agent is not calling the right tool. What's the fastest way to debug it?

AIncrease max_iterations in the Crew config
BSwitch to LangChain and see if that fixes it
CRewrite the task description to be more specific
DReproduce the same prompt with the raw OpenAI SDK and inspect the tool_calls response directly
Correct! Dropping to the raw API lets you see exactly what the model returns — specifically whether tool_calls is populated and what function name and arguments it contains. CrewAI's abstraction can hide the raw model output; the raw API reveals it.

Q5: Which statement about Ollama tool calling is true?

AAll Ollama models support tool calling out of the box
Bmistral, llama3.1, and qwen2.5 support tool calling; smaller or older models may not
CTool calling only works when using the Groq cloud endpoint
DYou must set tool_choice="required" for Ollama models
Correct! Tool calling support depends on the model's training, not just the framework. Always test your chosen model with a simple tool-call example before building a larger agent. mistral is the safest default for this course.

Module Summary

Key Takeaways

  • Same agent, three frameworks. A "what time is it?" agent is ~50 lines raw (OpenAI SDK), ~25 lines with CrewAI, and ~20 lines with LangChain.
  • The tool-use loop is the heart of every agent. CrewAI and LangChain don't make it disappear — they hide the bookkeeping. Knowing it exists makes you a better debugger.
  • Framework choice depends on your use case. Raw for learning and debugging. CrewAI for role-based multi-agent systems. LangChain for RAG pipelines and composable chains.
  • Ollama gives you local, free inference. The same code runs against Groq cloud (free tier) by changing one URL. No API cost either way.
  • You'll use all three in this course. M01–M11 use the raw OpenAI SDK. Capstone C4 uses CrewAI. M09–M11 (RAG) use LangChain patterns.

Next Module Preview: M01 — The LLM Mental Model

You've seen how an agent talks to Mistral. Module 1 zooms in on the model itself: how it predicts text, why temperature matters, and the "thinker, not calculator" mental model that shapes every design decision in the rest of the course. After that, M05 is where you'll build your first real production-style raw-API tool-use loop end-to-end.