DEV Community

郑沛沛
郑沛沛

Posted on

Building AI Agents That Actually Work: Patterns and Pitfalls

AI agents — LLMs that can use tools and take actions — are the hottest topic in AI. Here's how to build ones that are reliable, not just impressive demos.

What Is an AI Agent?

An agent is an LLM in a loop: observe, think, act, repeat.

def agent_loop(task: str, tools: dict, max_steps: int = 10):
    messages = [
        {"role": "system", "content": f"You have these tools: {list(tools.keys())}"},
        {"role": "user", "content": task}
    ]
    for step in range(max_steps):
        response = call_llm(messages, tools=tools)
        if response.tool_calls:
            for call in response.tool_calls:
                result = tools[call.function.name](**call.arguments)
                messages.append({"role": "tool", "content": str(result), "tool_call_id": call.id})
        else:
            return response.content
    return "Max steps reached"
Enter fullscreen mode Exit fullscreen mode

Building Tools

import json, subprocess, requests

def search_web(query: str) -> str:
    response = requests.get("https://api.search.com/search", params={"q": query, "limit": 5})
    return json.dumps(response.json()["results"])

def run_python(code: str) -> str:
    try:
        result = subprocess.run(["python3", "-c", code], capture_output=True, text=True, timeout=30)
        return result.stdout or result.stderr
    except subprocess.TimeoutExpired:
        return "Error: Code execution timed out"

def read_file(path: str) -> str:
    try:
        with open(path) as f:
            return f.read()
    except FileNotFoundError:
        return f"Error: File {path} not found"
Enter fullscreen mode Exit fullscreen mode

OpenAI Function Calling

from openai import OpenAI
client = OpenAI()

tools_schema = [{
    "type": "function",
    "function": {
        "name": "search_web",
        "description": "Search the web for information",
        "parameters": {
            "type": "object",
            "properties": {"query": {"type": "string", "description": "Search query"}},
            "required": ["query"]
        }
    }
}]

response = client.chat.completions.create(model="gpt-4", messages=messages, tools=tools_schema, tool_choice="auto")
Enter fullscreen mode Exit fullscreen mode

The ReAct Pattern

Reasoning + Acting — the most reliable agent pattern:

Thought: I need to find the current Python version
Action: run_python("import sys; print(sys.version)")
Observation: 3.11.5 (main, Sep 11 2023)
Thought: I have the answer
Final Answer: The current Python version is 3.11.5
Enter fullscreen mode Exit fullscreen mode

Guardrails

def safe_agent(task: str, tools: dict):
    if len(task) > 10000:
        return "Task too long"
    MAX_TOOL_OUTPUT = 5000
    total_tokens = 0
    MAX_TOKENS = 50000

    for step in range(10):
        response = call_llm(messages, tools=tools)
        total_tokens += response.usage.total_tokens
        if total_tokens > MAX_TOKENS:
            return "Token budget exceeded"
        if response.tool_calls:
            for call in response.tool_calls:
                if call.function.name not in tools:
                    continue
                result = tools[call.function.name](**call.arguments)[:MAX_TOOL_OUTPUT]
                messages.append({"role": "tool", "content": result})
        else:
            return response.content
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  1. Start simple: one tool, one loop, clear instructions
  2. Use the ReAct pattern for reliable reasoning
  3. Always set max steps and token budgets
  4. Handle tool errors gracefully
  5. Log everything — agent debugging is hard without traces

6. Test with adversarial inputs before production

🚀 Level up your AI workflow! Check out my AI Developer Mega Prompt Pack — 80 battle-tested prompts for developers. $9.99

Top comments (0)