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

推荐订阅源

Apple Machine Learning Research
Apple Machine Learning Research
Recent Announcements
Recent Announcements
IT之家
IT之家
人人都是产品经理
人人都是产品经理
G
Google Developers Blog
Microsoft Azure Blog
Microsoft Azure Blog
博客园_首页
大猫的无限游戏
大猫的无限游戏
U
Unit 42
罗磊的独立博客
博客园 - Franky
WordPress大学
WordPress大学
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
M
MIT News - Artificial intelligence
SecWiki News
SecWiki News
V
Vulnerabilities – Threatpost
P
Privacy International News Feed
P
Palo Alto Networks Blog
F
Fortinet All Blogs
P
Proofpoint News Feed
博客园 - 叶小钗
C
CERT Recently Published Vulnerability Notes
T
Tor Project blog
Spread Privacy
Spread Privacy
S
Securelist
C
Cisco Blogs
I
Intezer
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Cyberwarzone
Cyberwarzone
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
P
Privacy & Cybersecurity Law Blog
宝玉的分享
宝玉的分享
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Engineering at Meta
Engineering at Meta
S
Schneier on Security
C
CXSECURITY Database RSS Feed - CXSecurity.com
GbyAI
GbyAI
T
Troy Hunt's Blog
T
Threatpost
博客园 - 司徒正美
Y
Y Combinator Blog
Hugging Face - Blog
Hugging Face - Blog
AWS News Blog
AWS News Blog
T
The Blog of Author Tim Ferriss
G
GRAHAM CLULEY
N
Netflix TechBlog - Medium
酷 壳 – CoolShell
酷 壳 – CoolShell
Google DeepMind News
Google DeepMind News
Know Your Adversary
Know Your Adversary
S
SegmentFault 最新的问题

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 Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Agent Series (2): ReAct — The Most Important Agent Reasoning Paradigm
WonderLab · 2026-05-23 · via DEV Community

You Think Your Agent Is "Thinking." It's Actually Just Predicting Tokens.

Here's a scenario that happens more often than you'd think.

You ask an Agent to write a competitive analysis report. It confidently outputs three professional-looking pages — complete with data, conclusions, and strategic recommendations.

There's just one problem: every number comes from its training data, which may be a year old. It didn't search. It didn't verify. It just generated text that sounds authoritative.

That's not thinking. That's fluent hallucination.

Chain-of-Thought (CoT) has the same fundamental problem. CoT prompting tells the model to "reason step by step" before answering, and it genuinely does improve accuracy on many tasks. But the model is still reasoning entirely within language space. It can generate a very coherent chain of thought that leads to a completely wrong answer — because its only information source is training data.

ReAct was built to solve this.


ReAct: Reasoning + Acting, Interleaved

In 2022, researchers from Princeton and Google published ReAct: Synergizing Reasoning and Acting in Language Models.

The core idea is elegantly simple: let the model alternate between reasoning and acting, rather than reasoning first then acting, or acting without reasoning.

The concrete form is a three-part loop:

Thought  →  Action  →  Observation
   ↑                         │
   └─────────────────────────┘

Enter fullscreen mode Exit fullscreen mode

  • Thought: What the model is "thinking" — current analysis, what to do next, why
  • Action: The actual tool call and parameters
  • Observation: The real result returned by the tool

The critical mechanism: Observation is fed back into the model as new context, allowing it to reason based on actual results. This creates the "think → act → observe → think again" loop.

This one loop fixes CoT's core flaw: the model is no longer reasoning in isolation. It can interact with the real world and update its reasoning based on real feedback.


A Concrete Example: Watching an Agent "Think"

I built a complete ReAct Agent demo using LangGraph + GLM-4-Flash with two tools: calculator (safe math evaluator) and web_search (Bing search).

Code: agent-01-react-agent/react_agent.py

Here's an actual execution trace — Demo 3: search for the areas of Beijing and Shanghai, then calculate the difference.

════════════════════════════════════════════════════════════
  Demo 3 ▸ Multi-Round Search (Same Tool, Multiple Calls)
════════════════════════════════════════════════════════════

[User Question]
  First search for Beijing's area, then Shanghai's area,
  then calculate how much larger Beijing is in km².
────────────────────────────────────────────────────────────

[Step 1] THOUGHT → ACTION
  Action  : web_search(query='北京面积 平方公里')

  Observation : • Beijing area: Total area 16,410.54 km²...
────────────────────────────────────────────────────────────

[Step 2] THOUGHT → ACTION
  Action  : web_search(query='上海面积 平方公里')

  Observation : • Shanghai area: Land area approximately 6,340.5 km²...
────────────────────────────────────────────────────────────

[Step 3] THOUGHT → ACTION
  Action  : calculator(expression='16410.54 - 6340.5')

  Observation : 10070.04
────────────────────────────────────────────────────────────

[Final Answer]
  Beijing's area is approximately 16,410.54 km², Shanghai's is
  approximately 6,340.5 km². Beijing is about 10,070.04 km² larger.
════════════════════════════════════════════════════════════

Enter fullscreen mode Exit fullscreen mode

Notice what happened here:

  1. The Agent decided on its own to search Beijing first, then Shanghai, then calculate — no hardcoded execution order
  2. Each search result (Observation) was read by the model and used to determine the next step
  3. The final calculation used real numbers extracted from real searches

This is ReAct's value: the execution path is planned dynamically at runtime, not hardcoded by the developer in advance.


ReAct vs. Chain-of-Thought: A Direct Comparison

Aspect Chain-of-Thought ReAct
Information source Training data only Training data + tool results
Execution path Reasoning in language space Think → real action → observe results
Can access real-time data ✓ (via tools)
Can execute computation/code ✓ (via tools)
Reasoning verifiable Hard to verify Each Observation is a real result
Risk of side effects Low (no actions) High (requires safety boundaries)

One sentence summary: CoT makes the model think clearly. ReAct makes it think while doing.


Building a ReAct Agent with LangGraph

Here's the core implementation. The code uses LangGraph's create_react_agent — one of the cleanest ReAct implementations available.

1. Safe Calculator Tool

import ast
import operator
from typing import Any
from langchain_core.tools import tool

_SAFE_OPS: dict[type, Any] = {
    ast.Add:  operator.add,
    ast.Sub:  operator.sub,
    ast.Mult: operator.mul,
    ast.Div:  operator.truediv,
    ast.Pow:  operator.pow,
    ast.Mod:  operator.mod,
    ast.USub: operator.neg,
}

def _eval_ast(node: ast.AST) -> float:
    if isinstance(node, ast.Constant):
        return float(node.value)
    if isinstance(node, ast.BinOp):
        op_fn = _SAFE_OPS.get(type(node.op))
        if op_fn is None:
            raise ValueError(f"Unsupported operator: {type(node.op).__name__}")
        return op_fn(_eval_ast(node.left), _eval_ast(node.right))
    if isinstance(node, ast.UnaryOp):
        op_fn = _SAFE_OPS.get(type(node.op))
        return op_fn(_eval_ast(node.operand))
    raise ValueError(f"Unsupported AST node: {type(node).__name__}")

@tool
def calculator(expression: str) -> str:
    """Evaluate a math expression. Supports + - * / ** % and parentheses."""
    try:
        tree = ast.parse(expression.strip(), mode="eval")
        result = _eval_ast(tree.body)
        if result == int(result):
            return str(int(result))
        return f"{result:.6g}"
    except (ValueError, SyntaxError, ZeroDivisionError) as e:
        return f"Calculation error: {e}"

Enter fullscreen mode Exit fullscreen mode


Why not just use eval()?

eval("__import__('os').system('rm -rf /')") — that line will execute a deletion on your machine. Tools are the Agent's "hands." Once an attacker manipulates the LLM through prompt injection, eval() becomes a direct path to your system.

AST parsing only allows math operation nodes — everything else is rejected. This is the foundational principle of safe tool design.

2. Web Search Tool

import requests
from bs4 import BeautifulSoup
from urllib.parse import quote

_BING_HEADERS = {
    "User-Agent": (
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
        "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
    ),
    "Accept-Language": "en-US,en;q=0.9",
}

@tool
def web_search(query: str) -> str:
    """Search the web and return the 3 most relevant snippets."""
    try:
        url = f"https://www.bing.com/search?q={quote(query)}&setlang=zh-CN"
        resp = requests.get(url, headers=_BING_HEADERS, timeout=10)
        resp.raise_for_status()

        soup = BeautifulSoup(resp.text, "html.parser")
        snippets = []
        for li in soup.find_all("li", class_="b_algo")[:4]:
            h2 = li.find("h2")
            title = h2.get_text(strip=True) if h2 else ""
            p = li.find("p")
            body = p.get_text(strip=True) if p else ""
            if title or body:
                snippets.append(f"{title}: {body}"[:200])

        return "\n".join(snippets[:3]) if snippets else "No results found."
    except requests.RequestException as e:
        return f"Search failed: {e}"

Enter fullscreen mode Exit fullscreen mode

3. Building the Agent

import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
# LangGraph V1.0 moved create_react_agent to chat_agent_executor submodule
from langgraph.prebuilt.chat_agent_executor import create_react_agent

load_dotenv()

llm = ChatOpenAI(
    base_url="https://open.bigmodel.cn/api/paas/v4",
    api_key=os.getenv("LLM_API_KEY"),
    model="glm-4-flash",
    temperature=0,
)

agent = create_react_agent(
    model=llm,
    tools=[calculator, web_search],
)

result = agent.invoke(
    {"messages": [("user", "How much larger is Beijing than Shanghai in km²? Search and calculate.")]},
    config={"recursion_limit": 20},
)
print(result["messages"][-1].content)

Enter fullscreen mode Exit fullscreen mode

Three core lines: define tools → bind LLM → run. LangGraph handles all the message routing, tool call dispatch, result injection, and loop control under the hood.


The correct import path for create_react_agent

LangGraph V1.0 moved this function to langgraph.prebuilt.chat_agent_executor. Importing from langgraph.prebuilt triggers a LangGraphDeprecatedSinceV10 warning. Use the new path:

# ✅ Recommended
from langgraph.prebuilt.chat_agent_executor import create_react_agent

# ⚠️ Triggers deprecation warning
from langgraph.prebuilt import create_react_agent

Enter fullscreen mode Exit fullscreen mode


How the Message Flow Actually Works

To truly understand ReAct, you need to see the underlying message sequence. Here's what the LLM receives at the start of each cycle:

Context passed to LLM at round N:
┌─────────────────────────────────────────────────────┐
│ [System]  You are an assistant with these tools:    │
│           calculator, web_search                    │
│                                                     │
│ [Human]   Question: How much larger is Beijing?     │
│                                                     │
│ [AI]      (tool call) web_search("Beijing area")   │  ← Round 1 Action
│ [Tool]    Beijing area: 16,410 km²                 │  ← Round 1 Observation
│                                                     │
│ [AI]      (tool call) web_search("Shanghai area")  │  ← Round 2 Action
│ [Tool]    Shanghai area: 6,340 km²                 │  ← Round 2 Observation
│                                                     │
│ ← LLM decides what to do next here →               │
└─────────────────────────────────────────────────────┘

Enter fullscreen mode Exit fullscreen mode

Each cycle, the entire history is passed to the LLM. The model "sees" all previous thoughts and observations, then decides:

  • Continue calling tools (more information needed)
  • Stop and deliver a final answer (enough information gathered)

This is why it's called a loop — the model itself is the loop's termination condition. It decides when to stop.


When Things Go Wrong: Failure Modes and Guards

The same "decide when to stop" design that makes ReAct powerful also introduces a risk: if the model misjudges, the loop never terminates.

Common runaway scenarios:

Scenario 1: Tool keeps failing, model keeps retrying

Action: web_search("vague ambiguous query")
Observation: No results found
Thought: Let me try different keywords
Action: web_search("different keywords")
Observation: No results found
Thought: Maybe one more variation...
(infinite loop)

Enter fullscreen mode Exit fullscreen mode

Scenario 2: Model misunderstands the task and pursues the wrong direction

Thought: I need the exact value of X
Action: calculator("...")
Observation: Approximate result
Thought: Not precise enough, I need more decimal places
Action: calculator("...")
(infinite pursuit of "precision")

Enter fullscreen mode Exit fullscreen mode

Scenario 3: Tools form a circular dependency

Thought: I need to know A before I can look up B
Action: search(A)
Observation: Requires knowing B first
Thought: I need to know B before I can look up A
(circular dependency)

Enter fullscreen mode Exit fullscreen mode

LangGraph's recursion_limit parameter is the hard safety net:

result = agent.invoke(
    {"messages": [("user", question)]},
    config={"recursion_limit": 5},  # Force-stop after 5 steps
)

Enter fullscreen mode Exit fullscreen mode

When the step count exceeds the limit, LangGraph raises GraphRecursionError:

[recursion_limit triggered]
  Exception type: GraphRecursionError
  Message: Recursion limit of 5 reached without hitting a stop condition...

→ Conclusion: Always set a reasonable recursion_limit in production (15~25 recommended)
→ Too low: legitimate tasks get cut off; Too high: runaway Agent burns massive tokens

Enter fullscreen mode Exit fullscreen mode


How to set recursion_limit

  • Simple tasks (single tool call): 5–8 steps is enough
  • Medium tasks (multi-tool, multi-step): 10–15 steps
  • Complex research tasks: 20–25 steps
  • Tasks requiring 30+ steps should reconsider architecture — you may need multi-Agent collaboration (covered in a later article)

The rule of thumb: set it to roughly 2× the number of steps a successful execution needs. Room to breathe, but a real ceiling.


Five Demo Scenarios: From Simple to Complex

The complete code includes 5 progressive demos covering the main ReAct usage patterns:

Demo 1: Pure Calculation (single tool, single step)

Question: Calculate (1024 * 768) + (1920 * 1080)
Steps: calculator('(1024 * 768) + (1920 * 1080)') → 2860032

Enter fullscreen mode Exit fullscreen mode

Validates the basic tool-calling pipeline.

Demo 2: Search + Calculate (multi-tool, multi-step)

Question: What year were Python and JavaScript first released? Calculate the difference.
Steps: web_search("Python release year") → web_search("JavaScript release year") → calculator

Enter fullscreen mode Exit fullscreen mode

Shows the Agent autonomously orchestrating different tools in the right order.

Demo 3: Multi-round Search (same tool, multiple calls)

Question: How much larger is Beijing than Shanghai in km²?
Steps: web_search("Beijing area") → web_search("Shanghai area") → calculator → 10070.04

Enter fullscreen mode Exit fullscreen mode

Shows the Agent deciding what to search second based on what it found first.

Demo 4: No Tools Needed (direct answer)

Question: Explain the ReAct paradigm in one sentence.
Steps: No tool calls — direct answer

Enter fullscreen mode Exit fullscreen mode

Shows the Agent knowing when not to call tools. This matters as much as knowing when to call them.

Demo 5: Trigger recursion_limit (safety net demo)

Question: Search Python/Java/C release years, calculate the sum (~10 steps needed)
Limit: recursion_limit=5
Result: GraphRecursionError (correctly triggered)

Enter fullscreen mode Exit fullscreen mode

Production safety mechanism verification.


An Interesting Observation: Agents Can "Luck Into" Correct Answers

Demo 2 produced a result worth documenting carefully.

The Agent searched for JavaScript's release year. The Bing snippet it received came from an article published in 2023 that mentioned Python's 1991 origin. The model appears to have confused "2023" (article publication date) with JavaScript's release year. The calculation step ran 2023 - 1991 = 32, returning 32.

But the final answer was correct: "Python was released in 1991, JavaScript in 1995 — a 4-year difference."

The model overrode its (incorrect) calculation result with its internal training knowledge and delivered the right answer.

This reveals a subtle property of ReAct: an Agent's reasoning chain and its final answer can be decoupled. The model may make errors during tool calls, then "self-correct" in the final answer generation using built-in knowledge.

As an outcome, this is fine — you got the right answer. From an engineering perspective, it's a problem. If you need traceable, verifiable conclusions, "it happened to be correct" isn't sufficient. This is one of the challenges that Harness Engineering addresses (covered in a later article in this series).


Trace Visualization: Making Agent Reasoning Observable

A common production pain point: when something goes wrong, you don't know which step failed, because only the final answer is visible by default.

Good practice: print the full Thought/Action/Observation sequence as a readable Trace:

from langchain_core.messages import AIMessage, HumanMessage, ToolMessage

def print_trace(result: dict) -> None:
    for msg in result["messages"]:
        if isinstance(msg, HumanMessage):
            print(f"[USER] {msg.content}")

        elif isinstance(msg, AIMessage):
            content = msg.content if isinstance(msg.content, str) else ""
            if msg.tool_calls:
                for tc in msg.tool_calls:
                    args = ", ".join(f"{k}={repr(v)}" for k, v in tc["args"].items())
                    print(f"[ACTION] {tc['name']}({args})")
            else:
                print(f"[FINAL ANSWER] {content.strip()}")

        elif isinstance(msg, ToolMessage):
            obs = msg.content if isinstance(msg.content, str) else str(msg.content)
            print(f"[OBSERVATION] {obs.strip()[:300]}")

Enter fullscreen mode Exit fullscreen mode


GLM-4-Flash content field pollution

When using GLM-4-Flash, you may occasionally see raw JSON in AIMessage.content — something like {"index": 0, "delta": ...}. This is the model leaking internal streaming delta data into the content field.

Fix: detect when content starts with { or [ and can be parsed by json.loads(), then discard it.

def _clean_thought(text: str) -> str:
    stripped = text.strip()
    if stripped and stripped[0] in ("{", "["):
        try:
            json.loads(stripped)
            return ""  # leaked JSON, discard
        except json.JSONDecodeError:
            pass
    return text

Enter fullscreen mode Exit fullscreen mode

The complete demo code already includes this handling.


The Limitations of ReAct

ReAct is powerful, but it's not a silver bullet. Knowing its limits helps you use it correctly.

1. Context window fills up fast

Each cycle packs the entire history into context. Step count grows, token consumption spikes. Complex tasks (20+ steps) may fail on models with limited context windows.

2. Tool descriptions drive everything — write them well

ReAct relies entirely on the LLM understanding tool documentation to decide which tool to call and with what parameters. Vague docstrings lead to wrong tool selection. Tool descriptions are the invisible API of a ReAct system — treat them like API documentation.

3. No global planning capability

Standard ReAct is greedy: each step only looks at the current state to decide the next move, with no "plan the whole thing first, then execute" capability. For tasks requiring long-horizon planning (like writing an entire codebase), this can get stuck in local optima. This is what the Plan-and-Solve paradigm addresses (Article 3 in this series).

4. Poor fault tolerance for tool failures

When a tool returns an error, the model has to infer the next step from the error message alone. There's no predefined retry strategy or fallback logic. This needs to be handled at the tool design level and the Harness layer.


Interview Prep: Articulate How Your Agent "Thinks"

Common question: How does your Agent decide its next action?

Many candidates answer "it calls tools." But what the interviewer actually wants to hear is: who decides which tool to call, and when does it stop?

A clear answer framework:

"We use the ReAct paradigm. The core is a Thought → Action → Observation loop. At each step, the LLM looks at the full context — user question plus all previous Observations — and decides the next Action. The tool runs, its result is injected as a ToolMessage, and the model reasons again.

The loop terminates when the LLM judges it has enough information and stops calling tools, generating the final answer directly.

To prevent runaway loops, we set recursion_limit (typically 15–25). When it's exceeded, we catch the exception and fall back to a degraded response. We also log the full Trace — every Action and Observation — so we can replay the entire reasoning chain when debugging."

Key differentiators: mentioning Trace observability and recursion_limit shows you've thought beyond demos and considered production stability.


Summary

Three things from this article:

  1. ReAct = Reasoning + Acting, interleaved: The Thought → Action → Observation loop lets Agents update their reasoning based on real-world feedback. The fundamental difference from CoT: actions produce real results that feed back into the reasoning process.

  2. Tool design is ReAct's invisible interface: Docstring quality directly determines how accurately the LLM selects tools. Safe implementation (AST instead of eval) determines whether the system boundary holds.

  3. recursion_limit is a required production setting: The model decides when to stop — that's inherently risky. recursion_limit is the last line of defense. Recommended value: roughly 2× the steps needed for successful completion.


Next up: Agent Series Article 3 — Plan-and-Solve: When ReAct Isn't Enough, How Agents Plan Before Acting. We'll see where ReAct's greedy strategy hits its ceiling on complex tasks, and how introducing an explicit planning layer breaks through it.


References


Welcome to visit my personal homepage for more useful knowledge and interesting products