惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Visual Studio Blog
量子位
大猫的无限游戏
大猫的无限游戏
Hugging Face - Blog
Hugging Face - Blog
S
SegmentFault 最新的问题
Blog — PlanetScale
Blog — PlanetScale
月光博客
月光博客
Google DeepMind News
Google DeepMind News
小众软件
小众软件
WordPress大学
WordPress大学
宝玉的分享
宝玉的分享
MongoDB | Blog
MongoDB | Blog
B
Blog RSS Feed
博客园 - Franky
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
B
Blog
博客园 - 聂微东
The GitHub Blog
The GitHub Blog
Recent Announcements
Recent Announcements
Y
Y Combinator Blog
Microsoft Security Blog
Microsoft Security Blog
雷峰网
雷峰网
Jina AI
Jina AI
酷 壳 – CoolShell
酷 壳 – CoolShell

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
AI Agents
Ugur Aslim · 2026-06-13 · via DEV Community

Ugur Aslim

AI Agents: Why Simple Chains Beat Complex Orchestration

I've built nine AI features into CitizenApp, and I keep seeing the same pattern: developers get seduced by "agentic" architectures when a straightforward chain of function calls would work better.

Let me be direct: most AI agent frameworks are over-engineered. They look impressive in demos, but they introduce latency, unpredictability, and debugging nightmares in production. I prefer explicit chains with clear control flow because I can reason about them at 3am when something breaks.

What People Mean by "AI Agents"

When folks say "agents," they usually mean one of two things:

  1. Autonomous decision-making loops – An LLM decides what tool to call, calls it, sees the result, decides the next step
  2. Function calling with retry logic – Structured tool use with error handling and fallback strategies

The first one sounds magical. It's also fragile.

Here's why: every decision loop adds latency and a chance for the model to hallucinate. If you're building a user-facing feature, you can't afford to have your AI agent decide to call the wrong endpoint three times before giving up.

The CitizenApp Approach: Explicit Chains

In CitizenApp, I use what I call "orchestrated chains" – the developer defines the flow, the AI fills in the details.

Here's a real example from our document classification feature:

async function classifyAndExtractDocument(
  documentText: string,
  userId: string
): Promise<ClassificationResult> {
  // Step 1: Extract structured data
  const extracted = await extractWithClaude(documentText, {
    fields: ['documentType', 'issueDate', 'amount', 'parties'],
  });

  // Step 2: Validate against known schema
  const validated = validateSchema(extracted, documentType);

  // Step 3: If validation fails, ask Claude to correct
  if (!validated.success) {
    const corrected = await extractWithClaude(documentText, {
      fields: ['documentType', 'issueDate', 'amount', 'parties'],
      instructions: `Previous attempt failed validation: ${validated.errors.join(', ')}. Please re-extract with these constraints in mind.`,
    });
    return corrected;
  }

  // Step 4: Enrich with business logic
  const enriched = await enrichDocumentData(validated.data, userId);

  return enriched;
}

Notice: no loops, no tool-calling framework, no "let the AI figure it out." The developer controls the flow. Claude does what it's good at—understanding text and extracting meaning.

When (Rarely) You Need Real Agents

I use actual agentic loops in exactly one place in CitizenApp: our research assistant. Here's why it works there:

  • The user doesn't expect a response in < 500ms
  • The task is inherently exploratory (the AI discovers what it needs to know)
  • Failure is recoverable (the assistant can try another search or re-frame the question)
async def research_assistant(query: str, user_id: str, max_iterations: int = 5):
    """
    Actual agent loop. Used sparingly. Only when the problem
    is exploratory and latency isn't critical.
    """
    conversation_history = []

    for iteration in range(max_iterations):
        # Get model's decision
        response = await claude.messages.create(
            model="claude-3-5-sonnet-20241022",
            max_tokens=1024,
            system=RESEARCH_SYSTEM_PROMPT,
            tools=RESEARCH_TOOLS,
            messages=conversation_history
        )

        # Check if done
        if response.stop_reason == "end_turn":
            return extract_final_answer(response)

        # Process tool calls
        tool_results = []
        for block in response.content:
            if block.type == "tool_use":
                result = await execute_research_tool(block.name, block.input)
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": result
                })

        # Add to history and continue
        conversation_history.append({"role": "assistant", "content": response.content})
        conversation_history.append({"role": "user", "content": tool_results})

    return {"error": "Max iterations reached"}

This works because the research assistant runs async in the background. The user gets told "researching..." and waits. Not ideal for API responses.

The Performance Cost

Here's what burned me: I started with LangChain's agent executor for a simpler use case. On paper, it looked elegant. In practice:

  • Latency: Each agent loop added 200-400ms just for the API call round-trip
  • Costs: Agentic loops meant more API calls. A task that could be done in one smart prompt became 3-4 model calls
  • Debugging: When the agent did something unexpected (and it will), tracing why was like debugging a black box

I switched back to explicit chains. Same capabilities, 70% less latency, fraction of the cost.

My Rules of Thumb

Use explicit chains when:

  • Response latency matters (most user-facing features)
  • The flow is somewhat predictable
  • You're building a feature, not a research tool
  • Costs are a concern (spoiler: they always are)

Use agent loops when:

  • The problem is genuinely exploratory
  • Latency is acceptable (> 2-3 seconds)
  • The task naturally requires multiple decision points
  • You have a good error budget

The Right Tool

For most SaaS features, Claude + structured outputs + explicit orchestration beats "agentic" frameworks every time.

const result = await extractStructuredData(
  input,
  zodSchema(MyOutputShape)
);

This is less "AI" (less autonomous), but more reliable. And in production, reliability beats magic.

Gotcha: Tool Use Isn't the Same as Agents

I lumped these together early on. They're not the same thing.

Tool use = the model can call functions. Developers still control the loop.

Agents = the model decides what to do, including whether to use tools and when to stop.

Tool use is great. Tool use + explicit orchestration is my preferred pattern. Agents make me nervous. Your mileage may vary depending on your risk tolerance and latency requirements.

The unsexy truth: most winning AI features aren't "agents" at all. They're Claude doing one thing very well, wrapped in clear application logic.