AI Agent Without Framework: Vanilla Python with OpenAI SDK and No Dependencies

TL;DR — AI agent without framework: vanilla Python, no LangChain/CrewAI/AutoGen. OyeLabs: "Building from scratch achieves explainability for regulated industries. Simple stack — Python, OpenAI SDK, custom logic — beats overengineered frameworks. Framework-free empowers you to own your architecture." Firecrawl: "Many teams drop LangChain layer for less indirection. OpenAI Agents SDK: lightweight, 26,900+ stars." LangChain: "LangSmith is framework-agnostic — works with LangChain, custom code, or OpenAI Agents SDK." BrightData: "CrewAI: lean, fast, independent of LangChain. But framework-free means you build everything yourself." Learn more with what is AI agent, build agent, tool loop, and LangChain agent.

OyeLabs makes the case: "Building from scratch helps achieve explainability, crucial for regulated industries. A simple stack — Python, OpenAI SDK, and custom logic — beats overengineered frameworks. Tailored architectures allow precise tool use and flexible multi-agent collaboration. Framework-free design empowers you to own your agent architecture."

Firecrawl observes the trend: "As projects scale, many teams find it easier to reach for LangGraph directly or drop the LangChain layer altogether in favor of frameworks with less indirection."

No-Framework Architecture

flowchart TD subgraph Dependencies["Dependencies"] OpenAI["openai package
ONLY dependency
pip install openai"] StdLib["Python Standard Library
json, typing
dataclasses, enum"] end subgraph Agent["Vanilla Agent (~100 lines)"] System["System Prompt
plain string
role, tools, constraints"] Tools["Tool Functions
plain Python functions
type hints, docstrings"] Schemas["Tool Schemas
JSON schema dicts
name, description, parameters"] Loop["Agent Loop
for step in range(max_steps)
call LLM → check tools → execute → repeat"] Memory["Memory
messages list
system + user + assistant + tool"] Errors["Error Handling
try/except per tool
return error as result"] Logging["Logging
print or logging module
step, tool, args, result"] end subgraph Flow["Execution Flow"] Init["1. Initialize
messages = [system, user]"] Call["2. Call LLM
client.chat.completions.create
with tools and messages"] Check{"3. Tool Calls?"} Exec["4. Execute Tools
func(**args)
try/except"] Append["5. Append Results
role: tool messages
with tool_call_id"] Return["6. Return Answer
no tool_calls
return content"] MaxStep["7. Max Steps
return fallback"] end subgraph Comparison["Framework Comparison"] NoFW["No Framework
1 dep, 100 lines
full control, manual"] LangChain["LangChain
rich ecosystem
middleware, persistence"] CrewAI["CrewAI
role-based crews
opinionated, simple"] OpenAISDK["OpenAI Agents SDK
lightweight
handoff, tracing"] end OpenAI --> Agent StdLib --> Agent System --> Init Tools --> Exec Schemas --> Call Init --> Call Call --> Check Check -->|Yes| Exec Exec --> Append Append --> Call Check -->|No| Return Call --> MaxStep Memory --> Call Errors --> Exec Logging --> Loop style Dependencies fill:#39FF14,color:#000 style NoFW fill:#4169E1,color:#fff

Framework Comparison

Aspect No Framework LangChain CrewAI OpenAI Agents SDK
Dependencies 1 (openai) Many Moderate Few
Lines of code ~100 Abstracted Abstracted ~50
Control Full Limited by abstraction Opinionated High
Persistence Manual Built-in (checkpointer) Built-in Manual
Tracing Manual (LangSmith) Built-in (LangSmith) Limited Built-in
Middleware Manual Built-in system No Limited
Multi-agent Manual LangGraph Built-in crews Handoff primitive
Lock-in None LangChain ecosystem CrewAI ecosystem OpenAI ecosystem
Learning curve Low (just Python) High (framework concepts) Medium Low

Implementation

from dataclasses import dataclass
from typing import Optional
from enum import Enum

class Framework(Enum):
    NONE = "none"
    LANGCHAIN = "langchain"
    CREWAI = "crewai"
    OPENAI_SDK = "openai_sdk"

@dataclass
class NoFrameworkAgentGuide:
    """AI agent without framework implementation guide."""

    def get_complete_agent(self) -> str:
        """Complete no-framework agent (~100 lines)."""
        return (
            "import json\n"
            "from openai import OpenAI\n"
            "\n"
            "client = OpenAI()\n"
            "\n"
            "# === TOOL FUNCTIONS ===\n"
            "# Plain Python functions\n"
            "# with type hints\n"
            "\n"
            "def search(query: str) -> str:\n"
            "    '''Search the web.'''\n"
            "    # Implement search\n"
            "    return f'Results: {query}'\n"
            "\n"
            "def calculate(expression: str) -> str:\n"
            "    '''Evaluate math.'''\n"
            "    try:\n"
            "        return str(eval(\n"
            "            expression))\n"
            "    except Exception as e:\n"
            "        return f'Error: {e}'\n"
            "\n"
            "def file_read(path: str) -> str:\n"
            "    '''Read a file.'''\n"
            "    try:\n"
            "        with open(path) as f:\n"
            "            return f.read()\n"
            "    except Exception as e:\n"
            "        return f'Error: {e}'\n"
            "\n"
            "TOOL_MAP = {\n"
            "    'search': search,\n"
            "    'calculate': calculate,\n"
            "    'file_read': file_read,\n"
            "}\n"
            "\n"
            "# === TOOL SCHEMAS ===\n"
            "# JSON schema dicts\n"
            "# (same format OpenAI\n"
            "#  expects)\n"
            "\n"
            "TOOLS = [\n"
            "    {\n"
            "        'type': 'function',\n"
            "        'function': {\n"
            "            'name': 'search',\n"
            "            'description':\n"
            "                'Search the web',\n"
            "            'parameters': {\n"
            "                'type': 'object',\n"
            "                'properties': {\n"
            "                    'query': {\n"
            "                        'type': 'string',\n"
            "                        'description':\n"
            "                            'Query'\n"
            "                    }\n"
            "                },\n"
            "                'required': ['query']\n"
            "            }\n"
            "        }\n"
            "    },\n"
            "    {\n"
            "        'type': 'function',\n"
            "        'function': {\n"
            "            'name': 'calculate',\n"
            "            'description':\n"
            "                'Do math',\n"
            "            'parameters': {\n"
            "                'type': 'object',\n"
            "                'properties': {\n"
            "                    'expression': {\n"
            "                        'type': 'string'\n"
            "                    }\n"
            "                },\n"
            "                'required':\n"
            "                    ['expression']\n"
            "            }\n"
            "        }\n"
            "    },\n"
            "    {\n"
            "        'type': 'function',\n"
            "        'function': {\n"
            "            'name': 'file_read',\n"
            "            'description':\n"
            "                'Read file',\n"
            "            'parameters': {\n"
            "                'type': 'object',\n"
            "                'properties': {\n"
            "                    'path': {\n"
            "                        'type': 'string'\n"
            "                    }\n"
            "                },\n"
            "                'required': ['path']\n"
            "            }\n"
            "        }\n"
            "    }\n"
            "]\n"
            "\n"
            "# === SYSTEM PROMPT ===\n"
            "# Plain string\n"
            "\n"
            "SYSTEM = '''You are an\n"
            "autonomous agent. Use tools\n"
            "to help the user. Break tasks\n"
            "into steps. Reason before\n"
            "acting. Stop when done.\n"
            "'''\n"
            "\n"
            "# === AGENT LOOP ===\n"
            "# ~30 lines, no framework\n"
            "\n"
            "def run_agent(\n"
            "    user_msg: str,\n"
            "    max_steps: int = 10\n"
            ") -> str:\n"
            "    messages = [\n"
            "        {'role': 'system',\n"
            "         'content': SYSTEM},\n"
            "        {'role': 'user',\n"
            "         'content': user_msg}\n"
            "    ]\n"
            "    \n"
            "    for step in range(max_steps):\n"
            "        print(f'Step {step+1}')\n"
            "        \n"
            "        resp = client.chat\\\n"
            "            .completions.create(\n"
            "            model='gpt-4o',\n"
            "            messages=messages,\n"
            "            tools=TOOLS)\n"
            "        \n"
            "        msg = resp\\\n"
            "            .choices[0].message\n"
            "        messages.append(msg)\n"
            "        \n"
            "        if not msg.tool_calls:\n"
            "            return msg.content\n"
            "        \n"
            "        for call in \\\n"
            "            msg.tool_calls:\n"
            "            name = call.function\\\n"
            "                .name\n"
            "            args = json.loads(\n"
            "                call.function\n"
            "                .arguments)\n"
            "            print(f'  {name}({args})')\n"
            "            \n"
            "            func = TOOL_MAP.get(\n"
            "                name)\n"
            "            if func:\n"
            "                try:\n"
            "                    result = func(\n"
            "                        **args)\n"
            "                except Exception as e:\n"
            "                    result = f'Err: {e}'\n"
            "            else:\n"
            "                result = (\n"
            "                    f'Unknown: {name}')\n"
            "            \n"
            "            messages.append({\n"
            "                'role': 'tool',\n"
            "                'tool_call_id':\n"
            "                    call.id,\n"
            "                'content': str(\n"
            "                    result)\n"
            "            })\n"
            "    \n"
            "    return 'Max steps'\n"
            "\n"
            "# === RUN ===\n"
            "result = run_agent(\n"
            "    'Calculate 15 * 23 then'\n"
            "    ' search for Python 3.13')\n"
            "print(result)"
        )

    def get_persistence(self) -> str:
        """Manual persistence without framework."""
        return (
            "import json\n"
            "from openai import OpenAI\n"
            "\n"
            "client = OpenAI()\n"
            "\n"
            "class AgentPersistence:\n"
            "    '''Manual persistence\n"
            "    without framework.'''\n"
            "    \n"
            "    def __init__(self):\n"
            "        self.sessions = {}\n"
            "    \n"
            "    def save(self, thread_id,\n"
            "             messages):\n"
            "        '''Save to file or DB.'''\n"
            "        self.sessions[thread_id] \\\n"
            "            = messages\n"
            "        # Or persist to file:\n"
            "        with open(\n"
            "            f'{thread_id}.json',\n"
            "            'w') as f:\n"
            "            json.dump(\n"
            "                [self._serialize(m)\n"
            "                 for m in messages],\n"
            "                f)\n"
            "    \n"
            "    def load(self, thread_id):\n"
            "        '''Load from file.'''\n"
            "        try:\n"
            "            with open(\n"
            "                f'{thread_id}.json'\n"
            "            ) as f:\n"
            "                data = json.load(f)\n"
            "                return [\n"
            "                    self._deserialize(m)\n"
            "                    for m in data]\n"
            "        except FileNotFoundError:\n"
            "            return []\n"
            "    \n"
            "    def _serialize(self, msg):\n"
            "        '''Convert message\n"
            "        to dict.'''\n"
            "        if hasattr(msg, 'model_dump'):\n"
            "            return msg.model_dump()\n"
            "        return msg\n"
            "    \n"
            "    def _deserialize(self, d):\n"
            "        '''Convert dict to\n"
            "        message.'''\n"
            "        return d\n"
            "\n"
            "# Use persistence\n"
            "persist = AgentPersistence()\n"
            "\n"
            "def run_with_persistence(\n"
            "    thread_id, user_msg\n"
            "):\n"
            "    messages = persist.load(\n"
            "        thread_id)\n"
            "    if not messages:\n"
            "        messages = [\n"
            "            {'role': 'system',\n"
            "             'content': SYSTEM}\n"
            "        ]\n"
            "    messages.append({\n"
            "        'role': 'user',\n"
            "        'content': user_msg\n"
            "    })\n"
            "    \n"
            "    # ... agent loop ...\n"
            "    \n"
            "    persist.save(\n"
            "        thread_id, messages)"
        )

    def get_tracing(self) -> str:
        """Manual tracing with LangSmith (framework-agnostic)."""
        return (
            "# LangSmith works without\n"
            "# LangChain — just set\n"
            "# env vars\n"
            "import os\n"
            "\n"
            "os.environ[\n"
            "    'LANGSMITH_TRACING'] = 'true'\n"
            "os.environ[\n"
            "    'LANGSMITH_API_KEY'] = 'ls__...'\n"
            "os.environ[\n"
            "    'LANGSMITH_PROJECT'] = 'my-agent'\n"
            "\n"
            "# Now all OpenAI calls\n"
            "# are automatically traced\n"
            "# by LangSmith — no\n"
            "# LangChain needed\n"
            "# LangSmith is framework-\n"
            "# agnostic per LangChain\n"
            "# docs: 'works with custom\n"
            "# code or OpenAI Agents SDK'"
        )

    def get_pros_cons(self) -> dict:
        """Pros and cons of no framework."""
        return {
            "full_control": (
                "No hidden behavior, no "
                "abstraction leaks. Every "
                "line is yours. Critical "
                "for regulated industries."
            ),
            "explainability": (
                "Every step visible. No "
                "framework internals to "
                "debug. Easy to audit."
            ),
            "minimal_deps": (
                "Only openai package. "
                "No transitive dependencies. "
                "Smaller attack surface, "
                "fewer vulnerabilities."
            ),
            "no_lock_in": (
                "Switch providers by "
                "changing client. No "
                "framework-specific code "
                "to rewrite."
            ),
            "faster_debug": (
                "No framework internals "
                "to learn. Just Python "
                "and OpenAI API docs."
            ),
            "con_no_persistence": (
                "Must implement manually. "
                "File-based or database. "
                "No built-in checkpointer."
            ),
            "con_no_middleware": (
                "Must build hooks manually. "
                "No middleware system. "
                "More boilerplate for "
                "cross-cutting concerns."
            ),
            "con_no_tracing": (
                "Must add LangSmith manually "
                "(just env vars). No "
                "built-in tracing. But "
                "LangSmith is framework-"
                "agnostic."
            ),
            "con_no_multi_agent": (
                "Must build coordination "
                "manually. No handoff "
                "primitive. No crew system."
            ),
        }

    def get_when_to_use(self) -> dict:
        """When to use no framework vs framework."""
        return {
            "no_framework": (
                "Use when: regulated "
                "industries (explainability), "
                "simple agents (<5 tools), "
                "minimal deps required, "
                "full control needed, "
                "team knows OpenAI API well."
            ),
            "langchain": (
                "Use when: complex workflows "
                "(state graphs), need "
                "middleware, persistence, "
                "tracing, multi-agent "
                "coordination, large "
                "ecosystem."
            ),
            "crewai": (
                "Use when: role-based "
                "teams, simple setup, "
                "customer service, "
                "marketing automation, "
                "want opinionated framework."
            ),
            "openai_sdk": (
                "Use when: lightweight "
                "orchestration, handoff "
                "primitive, built-in "
                "tracing, OpenAI ecosystem, "
                "minimal abstraction."
            ),
        }

AI Agent Without Framework Checklist

  • [ ] No framework needed — just OpenAI SDK and Python standard library
  • [ ] Only dependency: pip install openai
  • [ ] No LangChain, no CrewAI, no AutoGen, no LangGraph
  • [ ] Framework-free gives full control, explainability, and minimal dependencies
  • [ ] Building from scratch helps achieve explainability — crucial for regulated industries
  • [ ] Simple stack (Python, OpenAI SDK, custom logic) beats overengineered frameworks
  • [ ] Tailored architectures allow precise tool use and flexible multi-agent collaboration
  • [ ] Framework-free design empowers you to own your agent architecture
  • [ ] Many teams drop LangChain layer in favor of less indirection
  • [ ] Tool functions: plain Python functions with type hints and docstrings
  • [ ] Tool schemas: JSON schema dicts (same format OpenAI expects)
  • [ ] System prompt: plain string defining agent role and behavior
  • [ ] Agent loop: for step in range(max_steps) — call LLM, check tools, execute, repeat
  • [ ] Memory: messages list (system + user + assistant + tool)
  • [ ] Error handling: try/except per tool, return error as tool result
  • [ ] Logging: print or logging module — step, tool, args, result
  • [ ] Total: ~100 lines of Python, no imports except openai and json
  • [ ] Pros: full control — no hidden behavior, no abstraction leaks
  • [ ] Pros: explainability — every step visible, easy to audit
  • [ ] Pros: minimal deps — only openai package, no transitive deps
  • [ ] Pros: no lock-in — switch providers by changing client
  • [ ] Pros: smaller attack surface — fewer packages = fewer vulnerabilities
  • [ ] Pros: faster debugging — no framework internals to learn
  • [ ] Pros: tailored — exact fit for your use case
  • [ ] Cons: no built-in persistence — implement file-based or database manually
  • [ ] Cons: no middleware system — manual hooks for cross-cutting concerns
  • [ ] Cons: no built-in tracing — add LangSmith via env vars (framework-agnostic)
  • [ ] Cons: no multi-agent coordination — build handoff/crew yourself
  • [ ] Cons: no community tools — write everything yourself
  • [ ] Cons: more boilerplate — but cleaner and more transparent
  • [ ] LangSmith is framework-agnostic — works with custom code, no LangChain needed
  • [ ] LangSmith: just set LANGSMITH_TRACING, LANGSMITH_API_KEY, LANGSMITH_PROJECT env vars
  • [ ] Manual persistence: save messages to file or database, load on resume
  • [ ] Manual persistence: thread_id scopes conversations, save/load per thread
  • [ ] Comparison: no-framework (1 dep, 100 lines, full control) vs LangChain (ecosystem, middleware)
  • [ ] Comparison: no-framework vs CrewAI (role-based, opinionated) vs OpenAI SDK (lightweight, handoff)
  • [ ] Use no-framework when: regulated industries, simple agents, minimal deps, full control
  • [ ] Use LangChain when: complex workflows, middleware, persistence, multi-agent
  • [ ] Use CrewAI when: role-based teams, simple setup, customer service
  • [ ] Use OpenAI SDK when: lightweight, handoff primitive, tracing, OpenAI ecosystem
  • [ ] CrewAI is lean, fast, independent of LangChain — built from scratch
  • [ ] OpenAI Agents SDK: lightweight, 26,900+ GitHub stars, minimal abstraction
  • [ ] Read what is AI agent for architecture
  • [ ] Read build agent for full implementation
  • [ ] Read tool loop for ReAct pattern
  • [ ] Read LangChain agent for framework approach
  • [ ] Test: agent completes multi-step task with tools
  • [ ] Test: max_steps prevents infinite loops
  • [ ] Test: tool errors handled gracefully
  • [ ] Test: persistence saves and loads conversations correctly
  • [ ] Test: LangSmith tracing captures all steps (env vars only)
  • [ ] Test: agent works with only openai package installed
  • [ ] Test: switching providers requires only client change
  • [ ] Document tools, schemas, system prompt, max_steps, persistence strategy, tracing setup

FAQ

Why build an AI agent without a framework?

Framework-free agents give you full control, explainability, and minimal dependencies. OyeLabs: "Building from scratch helps achieve explainability, crucial for regulated industries. A simple stack — Python, OpenAI SDK, and custom logic — beats overengineered frameworks. Tailored architectures allow precise tool use and flexible multi-agent collaboration. Framework-free design empowers you to own your agent architecture." Firecrawl: "As projects scale, many teams find it easier to reach for LangGraph directly or drop the LangChain layer altogether in favor of frameworks with less indirection. The OpenAI Agents SDK is a lightweight Python framework with over 26,900 GitHub stars." Reasons: (1) Full control: no hidden behavior, no abstraction leaks. (2) Explainability: every step visible, crucial for regulated industries. (3) Minimal dependencies: only openai package, no transitive deps. (4) No framework lock-in: switch providers easily. (5) Smaller attack surface: fewer packages = fewer vulnerabilities. (6) Faster debugging: no framework internals to learn. (7) Tailored architecture: exact fit for your use case.

What do you need to build an AI agent without a framework?

Just the OpenAI Python SDK and standard library — no LangChain, CrewAI, or AutoGen. OyeLabs: "A simple stack — Python, OpenAI SDK, and custom logic — beats overengineered frameworks." Firecrawl: "OpenAI Agents SDK is lightweight. Many teams drop the LangChain layer in favor of frameworks with less indirection." Requirements: (1) Python 3.10+. (2) pip install openai (only dependency). (3) OPENAI_API_KEY environment variable. (4) Tool functions: plain Python functions with type hints. (5) Tool schemas: JSON schema dicts (same format OpenAI expects). (6) Agent loop: while loop with max_steps. (7) System prompt: string defining agent behavior. (8) Messages list: conversation memory. (9) Error handling: try/except per tool. (10) Logging: print or logging module. No framework abstractions — just Python functions and API calls.

How does a no-framework agent compare to LangChain or CrewAI?

No-framework gives full control and minimal deps; frameworks give convenience and ecosystem. OyeLabs: "Framework-free design empowers you to own your agent architecture. Tailored architectures allow precise tool use and flexible multi-agent collaboration." LangChain: "LangSmith provides tracing, evaluation, and debugging for production AI applications regardless of whether the underlying application uses LangChain, LangGraph, Deep Agents, the OpenAI Agents SDK, or custom code. Teams using LangChain get native integration, but LangSmith does not require it and is framework-agnostic." BrightData: "CrewAI is a lean, lightning-fast Python framework built entirely from scratch. Completely independent of LangChain or any other agent tools." Comparison: No-framework: 1 dependency (openai), full control, 100 lines, no lock-in, manual everything. LangChain: rich ecosystem, middleware, persistence, tracing, but abstraction overhead. CrewAI: role-based crews, simple API, but opinionated. OpenAI Agents SDK: lightweight, handoff primitive, tracing, minimal abstraction. Choose: no-framework for control and simplicity, framework for ecosystem and speed.

What are the trade-offs of building without a framework?

Full control vs convenience, simplicity vs ecosystem, manual work vs built-in features. OyeLabs: "Building from scratch helps achieve explainability. A simple stack beats overengineered frameworks. But framework-free means you build everything yourself." Firecrawl: "Many teams drop the LangChain layer in favor of less indirection. But frameworks provide built-in features you would need to build manually." Pros: (1) Full control — no hidden behavior. (2) Explainability — every line is yours. (3) Minimal deps — only openai package. (4) No lock-in — switch providers easily. (5) Smaller surface — fewer vulnerabilities. (6) Faster debugging — no framework internals. (7) Tailored — exact fit. Cons: (1) No built-in persistence — implement yourself. (2) No middleware system — manual hooks. (3) No tracing — add LangSmith manually. (4) No structured output helpers — manual schema. (5) No multi-agent coordination — build yourself. (6) No community tools — write everything. (7) More boilerplate — but cleaner.

How do you implement a no-framework AI agent in Python?

Use OpenAI SDK with function calling, a tool loop, and conversation memory — all in vanilla Python. OyeLabs: "Python, OpenAI SDK, and custom logic. Tailored architectures for precise tool use." Implementation: (1) pip install openai. (2) Define tool functions as plain Python functions. (3) Define tool schemas as JSON schema dicts. (4) Create system prompt string. (5) Initialize messages list with system + user. (6) Agent loop: for step in range(max_steps): call client.chat.completions.create with tools. (7) If no tool_calls: return content. (8) For each tool_call: execute function, append result as tool message. (9) Error handling: try/except per tool. (10) Logging: print step, tool, args, result. Total: ~100 lines of Python. No imports except openai and json.


Want a self-hosted AI company brain that does all of this out of the box?
Book a demo →