🔧 AI Engineering

Build an AI Agent in Pure Python — No Framework, ~50 Lines (2026)

📅 Aug 13, 2026 ⏱ 6 min read

Frameworks make agents easy; building one without a framework makes them make sense. Every agent — from Claude Code to enterprise automations — is the same ~50-line loop underneath. Let’s write it.

The anatomy (memorise this)

An agent = an LLM + tools + a loop:

  1. Send the conversation + tool definitions to the model.
  2. If the model requests a tool call → run the function → append the result → go to 1.
  3. If the model answers in plain text → done.

Step 1 — define tools as plain functions

import json

def get_gpa_needed(current_cgpa, target_cgpa, sems_done, sems_left):
    """GPA needed in remaining semesters to reach a target CGPA."""
    total = target_cgpa * (sems_done + sems_left)
    return round((total - current_cgpa * sems_done) / sems_left, 2)

TOOLS = {"get_gpa_needed": get_gpa_needed}

# schema the model reads to know when/how to call it
TOOL_SPECS = [{
  "name": "get_gpa_needed",
  "description": "GPA needed in remaining semesters to hit a target CGPA",
  "input_schema": {"type": "object", "properties": {
      "current_cgpa": {"type": "number"}, "target_cgpa": {"type": "number"},
      "sems_done": {"type": "integer"}, "sems_left": {"type": "integer"}},
    "required": ["current_cgpa","target_cgpa","sems_done","sems_left"]}}]

Step 2 — the loop

Shown with the Anthropic SDK; OpenAI/Gemini differ only in field names:

import anthropic
client = anthropic.Anthropic()   # uses ANTHROPIC_API_KEY

def run_agent(user_message, max_steps=8):
    messages = [{"role": "user", "content": user_message}]
    for _ in range(max_steps):                      # guardrail: no infinite loops
        resp = client.messages.create(
            model="claude-opus-5", max_tokens=1024,
            tools=TOOL_SPECS, messages=messages)

        if resp.stop_reason != "tool_use":
            return resp.content[0].text             # plain answer -> done

        messages.append({"role": "assistant", "content": resp.content})
        results = []
        for block in resp.content:                  # may request SEVERAL tools
            if block.type == "tool_use":
                out = TOOLS[block.name](**block.input)
                results.append({"type": "tool_result",
                                "tool_use_id": block.id, "content": str(out)})
        messages.append({"role": "user", "content": results})
    return "Stopped: step limit reached."

print(run_agent("My CGPA is 7.4 after 5 sems. What GPA do I need in the last 3 to reach 8.0?"))

Run it and watch the model choose to call your function, read the result, and answer with real numbers. That decision-making is the entire “agent” magic.

What you just learned (that framework users don’t)

Next upgrades: add a web-search tool, add memory, or expose your tools via MCP so any assistant can call them.

Frequently Asked Questions

Can I build an AI agent without LangChain or a framework?
Yes — an agent is just an LLM API call, tool definitions, and a loop that executes requested tools and feeds results back. The core pattern fits in about 50 lines of plain Python.
How do AI agents call functions?
The model returns a structured tool-call request (function name plus arguments) instead of text. Your code runs the real function, appends the result to the conversation, and calls the model again until it answers in plain text.
What language is best for building AI agents?
Python dominates because every SDK, framework and tutorial supports it first; JavaScript/TypeScript is a close second. The loop pattern itself is identical in any language.
← All Articles