跪拜 Guibai
← Back to the summary

From Job Description to Colleague: Building Your First AI Agent in 100 Lines

Writing Your First AI Agent: When a Skill Gains Memory, Role, and Initiative

Frontend AI Skill System · Part 6 (Agent Chapter)

In the previous chapter, we spent 15 minutes writing a daily report generator Skill. 30 lines of Markdown, and the AI no longer suffered amnesia every day.

But after using it for a week, I discovered a problem: The Skill wouldn't take initiative.

If I didn't say "write the daily report," it stayed silent. If I didn't say "review the code," it played dead. It was like an intern who only follows orders—you push, it moves.

What I wanted was a colleague: someone who remembers context on their own, decides the next step themselves, and proactively tells me the result when they're done.

That is an Agent. Today, we build one from scratch.


1. First, Clarify: What's the Difference Between an Agent and a Skill?

After the last chapter was published, the most common question in the comments was: "Aren't Skill and Agent just different names for the same thing?"

No. The difference is fundamental.

The One-Sentence Version

A Skill is a job description. An Agent is a living person.

The job description is posted at the workstation; whoever comes follows it. It doesn't think proactively, doesn't remember what happened yesterday, and certainly won't go coordinate with the neighboring department when it discovers a problem.

An Agent will.

Expanded

Dimension Skill Agent
Essence A Markdown instruction A runnable program
Trigger Passive—acts only when you speak Proactive—decides when to act on its own
Memory None. Each conversation starts from zero Has it. Short-term working memory + long-term persistence
Tools Describes "what should be done," AI decides freely Code-level binding, deterministic execution
Decision-making Linear process, no backtracking Observe → Think → Act → Observe again, looping
Collaboration Independent, unaware of each other Handoff to other Agents, teamwork
Deployment Just put it in the project directory Requires Python/Node runtime
Cost Zero (plain text) API call fees / local compute

When to Use Which?

My current rule of thumb is simple:

If the task requires "do one step, check the result, then decide the next step"—use an Agent. If the task is "input → fixed process → output"—use a Skill.

A few real examples:

Scenario Choice Why
Unify code style Skill Rules are fixed, no decisions needed
Generate daily report Skill Fixed process, one execution
Auto-fix bugs Agent Must observe error → locate cause → fix → verify → if not fixed, try again
Multi-file refactoring Agent Must plan order, coordinate across files, check one after changing another
Customer service Q&A Agent Must remember context, search knowledge base, decide if escalation is needed
Code review Skill + Agent Skill defines review rules, Agent executes multi-round review

They are not substitutes; they are complementary. Skills define "how to do it," Agents decide "when to do it, who to find, and what to do after it's done."

In my current system, 31 Skills define the standards, and 3 Agents handle orchestration and execution. Skills are the legal code; Agents are the judge.


2. Project Directory Structure

Simple Agent: 5 Files Get It Done

my-first-agent/
├── agent.py              # Agent definition (role + instructions + tool bindings)
├── tools.py              # Tool functions (the Agent's hands and feet)
├── main.py               # Runtime entry point
├── requirements.txt      # Dependency: openai-agents
└── .env                  # API Key (don't commit to Git)

That's it. Don't start by creating 20 directories.

Complex Agent: 5-Layer Architecture

When your Agents grow from 1 to 3 or 5, the directory needs layering:

my-agent-system/
│
├── agents/               # 🧠 Role layer: who does what
│   ├── orchestrator.py   #    Orchestrator—decides task allocation
│   ├── researcher.py     #    Researcher—searches and organizes information
│   ├── coder.py          #    Developer—writes code, fixes bugs
│   └── reviewer.py       #    Reviewer—quality gate
│
├── tools/                # 🔧 Capability layer: what can be done
│   ├── web_search.py     #    Web search
│   ├── file_ops.py       #    File read/write
│   ├── code_exec.py      #    Code execution (sandbox)
│   └── git_ops.py        #    Git operations
│
├── memory/               # 💾 Memory layer: what is remembered
│   ├── short_term.py     #    Current conversation context
│   ├── long_term.py      #    Cross-session persistence (SQLite/Redis)
│   └── vector_store.py   #    Semantic retrieval (vector database)
│
├── config/               # ⚙️ Configuration layer: which model, what parameters
│   ├── models.py         #    Model config (switchable OpenAI/Claude/Ollama)
│   ├── prompts.py        #    System Prompt templates
│   └── settings.py       #    Global settings (max_iterations, timeout)
│
├── workflows/            # 🔄 Orchestration layer: how to collaborate
│   ├── code_review.py    #    Code review workflow
│   └── feature_dev.py    #    Feature development workflow (with rollback retry)
│
├── main.py               # Entry point
├── requirements.txt
├── .env.example
└── README.md

One Sentence Per Layer

Layer One Sentence Analogy
agents/ Defines "who"—role, goal, boundaries Company org chart
tools/ Defines "what can be done"—specific capabilities Employee's toolbox
memory/ Defines "what is remembered"—context and knowledge Employee's brain
config/ Defines "which model to use" Company policy
workflows/ Defines "how to collaborate" Project process

Principle: First get the simple 5-file version running, then split into layers as needed. Don't build a 5-layer architecture right away—that's only needed after the 3rd Agent appears.


3. Five Pitfalls I Fell Into

I fell into all of these. Each one wasted at least half a day.

Pitfall 1: Thinking a Long Prompt = an Agent

My initial "Agent" was just a 3000-character System Prompt that said "You must first search, then analyze, then output."

It wasn't an Agent. It was a Chatbot with a long instruction manual.

The core of an Agent isn't prompt length; it's the loop:

# ❌ This is not an Agent; this is a function call
response = llm.call("Help me research xxx")

# ✅ The Agent's core is this loop
while not done:
    observation = agent.observe(environment)    # What did it see
    thought = agent.think(observation)          # What did it think
    action = agent.decide(thought)              # What did it decide to do
    result = agent.act(action)                  # Execute
    done = agent.evaluate(result)               # Is it enough? If not, go again

This loop is called ReAct (Reasoning + Acting). Without it, it's not an Agent.

Pitfall 2: Giving the Agent 30 Tools

"More tools means more powerful, right!"—that's what I thought at first.

The result: The Agent started having "choice paralysis." Asked to search for something, it first read a file. Asked to write code, it first searched a webpage.

Measured experience:

Number of Tools Task Accuracy (perceived)
3-5 ~90%
10-15 ~70%
30+ Starts calling tools randomly, ~50%

Principle: Each Agent gets at most 5-7 tools. Need more capabilities? Split into multiple Agents, each managing only its own set of knives.

Pitfall 3: Building an "Omnipotent Agent"

# ❌ What I initially wrote
agent = Agent(
    name="Everything",
    instructions="You can search, write code, review code, deploy, write docs, make PPTs..."
)

It did everything, and did nothing well.

Later I split it into 3: Researcher only searches, Coder only writes, Reviewer only reviews. Each Agent's instructions were under 200 characters.

Immediate effect: Single Agent output quality went from "barely usable" to "basically no changes needed."

Pitfall 4: Not Setting a Maximum Loop Count

One evening I let an Agent research a technical solution and went to dinner.

Came back to find: It was still running. Searched 47 web pages, each time thinking "information is insufficient, search again." Burned $3.2 in tokens.

# ✅ Must set an upper limit
result = await Runner.run(
    agent,
    "Research xxx",
    max_turns=10,  # Max 10 rounds of tool calls; stop if exceeded
)

Lesson: An Agent has no concept of "enough." You must set it for them.

Pitfall 5: Not Implementing Human-in-the-loop

I asked an Agent to "clean up temporary files in the project."

It diligently executed rm -rf node_modules .git dist.

.git was gone.

# ✅ Dangerous operations must pause and wait for human confirmation
from agents import Agent, function_tool

@function_tool(needs_approval=True)  # Key: pause before execution, wait for human confirmation
def delete_file(path: str) -> str:
    """Delete specified file"""
    os.remove(path)
    return f"Deleted {path}"

It's 2026, and Agents still need human oversight. Especially: delete operations, money-related actions, permission changes, externally-facing outputs.

Individual vs. Enterprise: Different Directions

Dimension Individual Developer (me) Enterprise Team
Starting point OpenAI Agents SDK, single file LangGraph / CrewAI + orchestration platform
Model OpenAI / Claude API Private deployment + API hybrid
Memory Local SQLite Redis + vector database
Deployment Run locally / simple script K8s + monitoring + alerting
Security .env manages keys Vault + RBAC + audit logs
Monthly cost $10-50 Budgeted, requires cost monitoring
Observability print + Tracing panel LangSmith / professional Tracing

If you're an individual, don't do the enterprise thing. One .py file + one .env, that's enough.


4. 15 Minutes: Build a Research Agent That Can Search and Write Files

Framework choice: OpenAI Agents SDK. Reason: Runs in 5 lines of code, built-in Tracing, supports 100+ models, most active community in 2026. The complex version later also uses it; no framework switching.

Step 1: Environment Setup (2 minutes)

Prerequisite: Python 3.10+ (check with python3 --version). For frontend folks who haven't installed Python: macOS use brew install [email protected], Windows go to python.org to download and install.

mkdir my-first-agent && cd my-first-agent
python3 -m venv .venv
source .venv/bin/activate  # Windows: .venv\Scripts\activate

pip install openai-agents python-dotenv

# Environment variables (using Alibaba Bailian/Tongyi Qianwen here, direct connection in China, no VPN needed)
# Go to https://bailian.console.aliyun.com to activate; free quota is enough for dozens of runs
cat > .env << EOF
OPENAI_API_KEY=sk-xxx          # Bailian API Key
OPENAI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
TAVILY_API_KEY=tvly-xxx        # For search tool, free registration at https://tavily.com
EOF

echo ".env" >> .gitignore

Why Bailian instead of OpenAI? Three reasons: direct connection in China without proxy, free quota enough for learning, qwen-plus has strong Chinese capability. Chapter 6 later explains how to switch back to OpenAI or local Ollama.

Step 2: Write Tools (5 minutes)

Tools are the Agent's hands and feet. An Agent without tools is just a talking mouth.

# tools.py
import subprocess
from pathlib import Path
from agents import function_tool


@function_tool
def web_search(query: str) -> str:
    """Search the web to get the latest information. Use when needing real-time data, technical docs, or news.

    Args:
        query: Search keywords, be as specific as possible. E.g., "OpenAI Agents SDK 2026 new features" is better than "AI framework".
    """
    import json, os
    api_key = os.getenv("TAVILY_API_KEY", "")
    if not api_key:
        return "Error: TAVILY_API_KEY environment variable not set. Please register for free at https://tavily.com."
    try:
        result = subprocess.run(
            ["curl", "-s", "-X", "POST", "https://api.tavily.com/search",
             "-H", "Content-Type: application/json",
             "-d", json.dumps({"api_key": api_key, "query": query, "max_results": 3})],
            capture_output=True, text=True, timeout=15
        )
        data = json.loads(result.stdout)
        summaries = [f"- {r.get('title', '')}: {r.get('content', '')[:300]}" for r in data.get("results", [])]
        return "\n".join(summaries)[:2000] if summaries else "No relevant results found"
    except (subprocess.TimeoutExpired, json.JSONDecodeError) as e:
        return f"Search failed: {e}, please try a different keyword"


@function_tool
def read_file(file_path: str) -> str:
    """Read local file content. Use when needing to view code, docs, or config files.

    Args:
        file_path: Relative or absolute path to the file
    """
    path = Path(file_path)
    if not path.exists():
        return f"File does not exist: {file_path}"
    if path.stat().st_size > 50_000:
        return f"File too large ({path.stat().st_size} bytes), please specify line number range"
    return path.read_text(encoding="utf-8")[:5000]


@function_tool
def write_file(file_path: str, content: str) -> str:
    """Write content to a file. Use when needing to save research results or generate reports.

    Args:
        file_path: Target file path
        content: Complete content to write
    """
    path = Path(file_path)
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(content, encoding="utf-8")
    return f"✅ Written to {file_path} ({len(content)} characters)"

3 details, all added after falling into pitfalls:

  1. Write "when to use" in the docstring—the Agent relies on this text to decide whether to call the tool
  2. Return values must be truncated ([:2000])—without truncation, one search eats 50% of the context
  3. Errors must return strings, not throw exceptions—the Agent sees the error message and will retry on its own

Step 3: Define the Agent (3 minutes)

# agent.py
from agents import Agent
from tools import web_search, read_file, write_file

research_agent = Agent(
    name="Research Assistant",
    model="qwen-plus",  # Bailian model name. For OpenAI, change to "gpt-4o"
    instructions="""You are a research assistant.

## Workflow
1. Understand the user's research question, break it into 2-3 sub-questions
2. Use web_search for each sub-question
3. Evaluate whether the information is sufficient—if not, change keywords and search again
4. Organize into a structured report, save to the reports/ directory using write_file

## Rules
- After each search, ask yourself: "Is the information enough to answer the question?" If not, continue
- The report must cite information source URLs
- Mark uncertain information with [To be verified]
- Search at most 5 times. If 5 times is still not enough, honestly state "Information is limited"
- Report in Chinese, keep technical terms in English""",
    tools=[web_search, read_file, write_file],
)

Note the instructions writing style:

Step 4: Run (2 minutes)

# main.py
from dotenv import load_dotenv
load_dotenv()

import os
os.environ["OPENAI_AGENTS_DISABLE_TRACING"] = "true"

import asyncio
from agents import Runner, set_default_openai_api
from agent import research_agent

# Bailian doesn't support Responses API, force Chat Completions API
set_default_openai_api("chat_completions")


async def main():
    user_input = input("Enter your research question (press Enter to use default):\n> ").strip()
    if not user_input:
        user_input = "Research the pros and cons comparison of mainstream AI Agent frameworks in 2026 (OpenAI Agents SDK, LangGraph, CrewAI)"

    result = await Runner.run(
        research_agent,
        input=user_input,
        max_turns=10,
    )
    print(result.final_output)


if __name__ == "__main__":
    asyncio.run(main())
python main.py

Two lines of "ugly code," but missing them causes errors directly:

Code Why needed When using OpenAI
OPENAI_AGENTS_DISABLE_TRACING=true Bailian doesn't support OpenAI's Tracing reporting Delete it, enjoy built-in Tracing
set_default_openai_api("chat_completions") Bailian only supports Chat Completions API Delete it, SDK default is fine

Step 5: See How It Thinks (3 minutes)

After running, you'll see the Agent's complete decision chain in the terminal:

Turn 1: Agent thinks → breaks down into 3 sub-questions
Turn 2: Calls web_search("OpenAI Agents SDK features 2026")
Turn 3: Calls web_search("LangGraph vs CrewAI comparison")
Turn 4: Calls web_search("AI agent framework benchmark 2026")
Turn 5: Agent thinks → information sufficient, starts writing report
Turn 6: Calls write_file("reports/agent-frameworks-2026.md", ...)
Turn 7: Outputs final summary

If you're using the OpenAI official API (without disabling Tracing), you can also open platform.openai.com/traces to see the visualization panel.

This is the difference between an Agent and a Chatbot: A Chatbot gives you a blob of text; an Agent gives you an auditable decision chain.

Final File List

my-first-agent/
├── agent.py          # 22 lines: Agent definition
├── tools.py          # 50 lines: 3 tools
├── main.py           # 25 lines: runtime entry
├── requirements.txt  # 2 lines: openai-agents + python-dotenv
└── .env              # 3 lines: API Key + Base URL + Tavily Key

~100 lines of code. An Agent that can search, read/write files, and has a decision loop. Direct connection in China, no proxy needed.


5. 45 Minutes: Build a 3-Person Collaborative Dev Team

Still OpenAI Agents SDK. No framework switching. The core change from 1 Agent to 3 is: Handoff (task transfer).

Goal

Input one requirement: "Implement a React useDebounce Hook."

3 Agents collaborate automatically:

  1. PM Agent analyzes the requirement → outputs a technical plan
  2. Coder Agent writes code based on the plan → outputs to src/
  3. Reviewer Agent reviews the code → pass or reject

If the review fails, automatically send back to Coder for rewrite, max 2 retries.

Environment Setup

Same as Chapter 4, requires Python 3.10+ environment. If you already ran Chapter 4, just reuse that virtual environment—dependencies are identical.

# If not installed yet:
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

This chapter reuses tools.py and requirements.txt from Chapter 4; no additional dependencies needed.

Step 1: Define 3 Agents

# agents.py
from agents import Agent, function_tool
from tools import read_file, write_file

# ---------- PM Agent ----------
pm_agent = Agent(
    name="PM",
    model="qwen-plus",
    instructions="""You are a technical product manager.

## Responsibility
Transform user requirements into executable technical plans.

## Output Format (strictly follow)
### Feature Description
One sentence explaining what to do.

### Interface Definition
- Input: parameter names, types, default values
- Output: return value type
- Usage example: ```tsx code block

### Boundary Conditions (at least 3)
1. ...
2. ...
3. ...

### Acceptance Criteria (testable checklist)
- [ ] ...
- [ ] ...

## Rules
- First ask "what does the user really want," don't design directly
- If the requirement is vague, list your assumptions
- Don't write code, only write the plan""",
    tools=[read_file],
)

# ---------- Coder Agent ----------
coder_agent = Agent(
    name="Coder",
    model="qwen-plus",
    instructions="""You are a senior frontend engineer.

## Responsibility
Write code according to the technical plan.

## Rules
- Write code to the src/ directory (use write_file tool)
- Main file: src/index.ts
- Test file: src/index.test.ts
- All functions must have JSDoc comments
- Error handling must not be omitted
- If the plan is ambiguous, mark in code comments // TODO: plan ambiguity - xxx
- Use TypeScript strict mode""",
    tools=[read_file, write_file],
)

# ---------- Reviewer Agent ----------
reviewer_agent = Agent(
    name="Reviewer",
    model="qwen-plus",
    instructions="""You are a code review expert.

## Review Dimensions (by priority)
1. Logical correctness—check against the technical plan's acceptance criteria item by item
2. Boundary conditions—are all boundary conditions listed in the plan handled
3. Error handling—are any exceptions swallowed
4. Type safety—is there any 'any'
5. Performance—are there unnecessary re-renders/repeated calculations

## Output Format
If passed:
"LGTM ✅" + one-sentence summary of highlights

If not passed:
"NEEDS_REVISION ❌" + specific problem list (precise to line number + fix suggestion)

## Rules
- The goal is to help, not to nitpick
- Style issues (semicolons, indentation) don't mention
- At most 5 problems, pick the most serious""",
    tools=[read_file],
)

Step 2: Define Handoff and Orchestration

# workflow.py
from agents import Agent, Runner, handoff
from agents.stream_events import RunItemStreamEvent, AgentUpdatedStreamEvent
from agent import pm_agent, coder_agent, reviewer_agent

# Add Handoff to PM: after analysis, hand over to Coder
pm_agent.handoffs = [handoff(coder_agent)]

# Add Handoff to Coder: after writing, hand over to Reviewer
coder_agent.handoffs = [handoff(reviewer_agent)]

# Reviewer's Handoff: send back to Coder (conditional trigger)
reviewer_agent.handoffs = [handoff(coder_agent)]

def _format_event(event):
    """Format stream events into readable output"""
    if isinstance(event, AgentUpdatedStreamEvent):
        print(f"\n🤖 [{event.new_agent.name}] Taking over...")

    elif isinstance(event, RunItemStreamEvent):
        name = event.name
        item = event.item

        if name == "message_output_created":
            text = getattr(item, 'raw_item', None)
            if text:
                content = getattr(text, 'content', '')
                if isinstance(content, list):
                    for c in content:
                        if hasattr(c, 'text') and c.text:
                            print(f"  💬 {c.text[:200]}")
                elif isinstance(content, str) and content.strip():
                    print(f"  💬 {content[:200]}")

        elif name == "tool_called":
            tool_name = getattr(item, 'name', 'unknown')
            print(f"  🔧 Calling tool: {tool_name}")

        elif name == "tool_output":
            print(f"  ✅ Tool execution complete")

        elif name == "handoff_requested":
            print(f"  ➡️  Requesting handoff...")

        elif name == "handoff_occured":
            print(f"  🔀 Handoff complete")

async def run_dev_team(requirement: str) -> str:
    """Run the dev team, displaying each Agent's execution process in real-time"""
    result = Runner.run_streamed(
        pm_agent,
        input=f"Requirement: {requirement}\n\nPlease analyze the requirement and output a technical plan, then hand over to Coder for implementation.",
        max_turns=20,
    )

    async for event in result.stream_events():
        _format_event(event)

    return result.final_output

Here Runner.run_streamed is used instead of Runner.run—the difference is you can see in real-time what each Agent is doing: who took over, what tool was called, what was output. When debugging multi-Agent collaboration, this is far better than waiting for the final result.

Step 3: Add Retry Logic

# main.py
from dotenv import load_dotenv
load_dotenv()

import os
os.environ["OPENAI_AGENTS_DISABLE_TRACING"] = "true"

import asyncio
from agents import set_default_openai_api
from workflow import run_dev_team

# Bailian/Tongyi Qianwen doesn't support Responses API, force Chat Completions API
set_default_openai_api("chat_completions")


async def main():
    default_req = "Implement a React Hook: useDebounce, supporting custom delay (default 300ms), cancel, and immediate execution (flush)"
    user_input = input(f"Enter development requirement (press Enter to use default):\n> ").strip()
    requirement = user_input if user_input else default_req

    max_retries = 2

    for attempt in range(max_retries + 1):
        print(f"\n{'='*50}")
        print(f"🚀 Round {attempt + 1}")
        print(f"{'='*50}")

        result = await run_dev_team(requirement)

        if "LGTM" in result:
            print("\n✅ Review passed! Code has been written to src/ directory")
            break
        elif attempt < max_retries:
            print(f"\n❌ Review not passed, sending back for rewrite ({max_retries - attempt} chances remaining)")
            requirement += f"\n\nPrevious round review comments:\n{result}"
        else:
            print("\n⚠️ Retry limit exhausted, manual intervention required")
            print(result)


if __name__ == "__main__":
    asyncio.run(main())

Same as Chapter 4, the trio is essential: load_dotenv() loads .env, disable Tracing, force Chat Completions API. Forgetting any line will cause errors.

Step 4: Run

python main.py

You'll see:

==================================================
🚀 Round 1
==================================================
[PM] Analyzing requirement...
  → Output technical plan: interface definition, boundary conditions, acceptance criteria
  → Handoff to Coder

[Coder] Writing code...
  → write_file("src/index.ts", ...)
  → write_file("src/index.test.ts", ...)
  → Handoff to Reviewer

[Reviewer] Reviewing code...
  → NEEDS_REVISION ❌
  → Problem 1: src/index.ts:23 - timer not cleared after flush
  → Problem 2: src/index.test.ts - missing cancel test case

❌ Review not passed, sending back for rewrite (2 chances remaining)

==================================================
🚀 Round 2
==================================================
[Coder] Modifying based on review comments...
  → Fixed flush logic
  → Added cancel test
  → Handoff to Reviewer

[Reviewer] Reviewing again...
  → LGTM ✅ Logic complete, all boundary conditions covered

✅ Review passed! Code has been written to src/ directory

Architecture Diagram

┌──────────────────────────────────────────────────────┐
│                  Dev Team Workflow                     │
│                                                       │
│  ┌────────┐  Handoff  ┌────────┐  Handoff  ┌────────┐│
│  │   PM   │─────────▶│ Coder  │─────────▶│Reviewer││
│  │Req Analysis│       │Code Impl│          │Code Review││
│  └────────┘          └────────┘          └───┬────┘│
│                            ▲                  │      │
│                            │  NEEDS_REVISION  │      │
│                            └──────────────────┘      │
│                                                       │
│  Outer loop: main.py controls max 2 retries            │
│  Exceeding 2 → manual intervention                     │
└──────────────────────────────────────────────────────┘

Differences from the simple version:

Dimension Simple (1 Agent) Complex (3 Agents)
Agent count 1 3
Tool allocation 1 Agent gets all tools Each Agent only gets what it needs
Collaboration None Handoff transfer
Quality control Self-check Independent Reviewer
Failure handling None Send back for rewrite + retry limit

6. Switching Models: From OpenAI to Local Ollama

Everything above used cloud APIs (Bailian). But some scenarios require switching:

Solution Comparison (One Table)

Solution Suitable for Monthly Cost Speed Capability
OpenAI API Rapid prototyping, complex reasoning $10-50 200+ tok/s ⭐⭐⭐⭐⭐
Claude API Long text, code review $10-50 150+ tok/s ⭐⭐⭐⭐⭐
Ollama Local Privacy, offline, high-frequency simple tasks Hardware one-time 20-40 tok/s ⭐⭐⭐
vLLM Self-deployed Enterprise privatization, high concurrency Server cost 100+ tok/s Depends on model
LiteLLM Proxy Unified entry for multiple models Pass-through Pass-through Pass-through

Switch to Ollama: 3 Steps Done

# 1. Install Ollama
curl -fsSL https://ollama.com/install.sh | sh

# 2. Pull models
ollama pull llama3.1:8b
ollama pull qwen2.5-coder:7b
# 3. Change 2 lines of code
from openai import AsyncOpenAI
from agents import Agent, Runner, set_default_openai_client

local_client = AsyncOpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama",
)
set_default_openai_client(local_client, use_for_tracing=False)

agent = Agent(
    name="Local Agent",
    instructions="You are a locally running assistant",
    model="llama3.1:8b",
)

import asyncio
result = asyncio.run(Runner.run(agent, "Write a binary search in Python"))
print(result.final_output)

That's it. Tool definitions don't change, Agent logic doesn't change, only the model connection changes.

Switch to Claude: Via LiteLLM Bridge

pip install 'openai-agents[litellm]'
agent = Agent(
    name="Claude Agent",
    instructions="...",
    model="litellm/anthropic/claude-sonnet-4-20250514",
)

Enterprise: vLLM Deployment

vllm serve meta-llama/Llama-3.1-8B-Instruct \
  --host 0.0.0.0 --port 8000 \
  --max-model-len 8192

Connection method is the same as Ollama, just change base_url to your server address.

Multi-Model Unified Entry: LiteLLM Proxy

# litellm_config.yaml
model_list:
  - model_name: fast
    litellm_params:
      model: openai/gpt-4o-mini
  - model_name: smart
    litellm_params:
      model: anthropic/claude-sonnet-4-20250514
  - model_name: private
    litellm_params:
      model: ollama/llama3.1:8b
      api_base: http://localhost:11434
litellm --config litellm_config.yaml --port 4000

Selection Decision Tree

Is your data sensitive?
├── Yes → Local deployment
│   ├── Individual use → Ollama (one command done)
│   └── Enterprise use → vLLM (high concurrency + multi-GPU)
└── Not sensitive → Is the task complex?
    ├── Complex reasoning/code → OpenAI / Claude API
    └── Simple/high-frequency → Local Ollama to save money

My actual setup: Daily use gpt-4o-mini (cheap), complex reasoning switch to gpt-4o, company code goes through local Ollama. About $15/month.


7. Q&A: The 7 Most Frequently Asked Questions

Q1: I already have 31 Skills, do I still need Agents?

Look at your pain points:

Pain Point Solution
AI doesn't follow Skill rules Optimize the Skill itself (not an Agent problem)
Must manually trigger every time Agent (proactivity)
Can't remember context across sessions Agent + Memory
Complex tasks need multi-step coordination Multi-Agent
Want to automate the entire dev workflow Agent is mandatory

Skills and Agents are not substitutes. My current system: 31 Skills set standards, 3 Agents do orchestration. Skills are the law, Agents are the judge.

Q2: Which framework is suitable for beginners?

Framework Learning Curve One Sentence
OpenAI Agents SDK ⭐ Lowest Runs in 5 lines of code
CrewAI ⭐⭐ Give an Agent a "job title"
LangGraph ⭐⭐⭐ Kubernetes for Agents
AutoGen ⭐⭐⭐⭐ Most academic flavor

Beginner path: OpenAI Agents SDK (understand concepts) → CrewAI (multi-Agent role division) → LangGraph (production-grade state machine)

Q3: How to control Agent token costs?

My measured consumption for a single Research Agent task:

Component Tokens Percentage
System Prompt ~500 5%
Tool descriptions ~300 3%
Tool return results ~5000 50%
Agent reasoning ~3000 30%
Final output ~1000 10%

The bulk is tool return values. Control strategies:

  1. Tool returns must be truncated ([:2000])
  2. Simple judgments use gpt-4o-mini, complex reasoning use gpt-4o
  3. Set max_turns to prevent infinite loops
  4. Cache repeated queries

Q4: Can local models run Agents?

Yes, but depends on model size:

Model Memory Requirement Agent Capability Suitable Scenarios
7B 8GB+ Single tool, simple ReAct File operations, format conversion
13B 16GB+ Multi-tool, simple planning Code generation, Q&A
70B 48GB+ Close to cloud Complex reasoning

Measured experience: 7B model running a 3-tool Agent, success rate about 60%. Same task with GPT-4o is 95%+.

Q5: How do multiple Agents communicate?

Three mainstream patterns:

Pattern 1: Handoff transfer (used in this article)
PM → Coder → Reviewer

Pattern 2: Central orchestration
       Orchestrator
      /     |      \
  Agent A  Agent B  Agent C

Pattern 3: Group chat negotiation (AutoGen style)
Agent A ←→ Agent B ←→ Agent C

Beginners start with Pattern 1. Patterns 2 and 3 are for when you have 5+ Agents.

Q6: What if an Agent "runs wild"?

7 lines of defense, ordered by importance:

  1. max_turns: Max N rounds of tool calls, force stop if exceeded
  2. Tool whitelist: Don't give rm -rf capability
  3. needs_approval=True: Dangerous operations pause for human confirmation
  4. Guardrails: Input/output validation (built into SDK)
  5. Sandbox execution: Code runs in isolated environment
  6. Tracing: Every step logged, can trace back when problems occur
  7. Cost circuit breaker: Auto-terminate when token threshold exceeded
# Guardrails example: intercept dangerous input
from agents import Agent, input_guardrail, GuardrailFunctionOutput, RunContextWrapper

@input_guardrail
async def block_dangerous(
    ctx: RunContextWrapper, agent: Agent, input: str | list
) -> GuardrailFunctionOutput:
    text = input if isinstance(input, str) else str(input)
    dangerous = ["delete all", "rm -rf", "drop table", "format c:"]
    is_safe = not any(word in text.lower() for word in dangerous)
    return GuardrailFunctionOutput(
        output_info={"safe": is_safe},
        tripwire_triggered=not is_safe,
    )

agent = Agent(
    name="Safe Agent",
    instructions="You are a safe assistant",
    input_guardrails=[block_dangerous],
)

Q7: Do custom GPTs count as Agents?

They count as a "trial version." They have System Prompt (≈ Skill) + knowledge base (≈ RAG) + a few tools, but:

A real Agent requires code. Custom GPTs let you experience the feeling of an Agent, but can't do what an Agent does.


Conclusion

In the previous chapter, we wrote a job description for the AI. In this chapter, we hired a person.

Skill:  "You tell me how to do it, and I'll do it that way."
Agent:  "You tell me the goal, and I'll figure out how myself."

82 lines of code, a Research Agent that can search, write files, and judge for itself whether "it's enough."

Another 100 lines, and it gained two colleagues: one writes code, one reviews code. If the review fails, it sends back for rewrite on its own.

This isn't science fiction. This is what I did on a Tuesday afternoon in 2026, sitting at my desk.

OpenAI made the Agents SDK. 100,000 developers are already using it.

Now it's your turn.

Start with that task you manually coordinate across 3 steps every day. Hand it to an Agent. Then watch it run on its own.

That feeling—it's like you finally hired a colleague who doesn't need to be prodded.


Series Review:


Reference Sources

Source Content
OpenAI Agents SDK Official docs + GitHub README (Agent / Handoff / Guardrails / Tracing)
CrewAI GitHub README (Crews + Flows dual mode)
LangGraph GitHub README (State machine / Persistent execution / Human-in-the-loop)
AutoGen Microsoft GitHub (Actor model / Event-driven)
Ollama GitHub README (Local deployment / REST API / OpenAI-compatible interface)
Anthropic Agentic Systems official docs
LiteLLM Multi-model unified proxy