인셔셔RSS 관심 있는 블로그, 뉴스, 기술 정보를 효율적으로 추적하고 읽으세요
원문 읽기 InertiaRSS에서 열기

추천 피드

雷峰网
雷峰网
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
The GitHub Blog
The GitHub Blog
博客园 - Franky
Google DeepMind News
Google DeepMind News
J
Java Code Geeks
Last Week in AI
Last Week in AI
V
Visual Studio Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Blog — PlanetScale
Blog — PlanetScale
D
Docker
GbyAI
GbyAI
V
V2EX
IT之家
IT之家
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 聂微东
博客园_首页
月光博客
月光博客
量子位
罗磊的独立博客

DEV Community

Gemma 4 on Android: Tricks for Faster On-Device Inference Your AI agent has amnesia. You've just normalized it. I built an AI that reviews every PR automatically (because nobody was reviewing mine) 🌿 Git Mastery: The Complete Developer Guide Bringing Gemma 4 E2B to the Edge: Building a Privacy-First Dream Analyzer with Flutter & LiteRT Google I/O 2026 Wasn’t About Features — It Was About AI Becoming the Developer Environment Building an AI Vedic Astrology App in 25 Days — What Actually Worked (and What Didn't) Hermes Agent Has Four Memories — And That's Why It Doesn't Forget You Pressure Isn't Killing You -Your Relationship With It Is 🐳 How to Run Any Project in Docker: A Complete Guide AccessLens — a blind person's lanyard, powered by Gemma 4 on-device Glyph v0.2: the release is the joinery How I Built a Blazingly Fast, Privacy-First Batch Image Converter in the Browser Using OPFS and Web Workers Cómo solucionar \"Text content does not match server-rendered HTML\" en Next.js App Router FCoP 3.0: Why AI Agents Need a Track, Not a Brake Fibonacci: Quiz app which anyone can make revenue by viewing ads to the quiz contestants. The Subconscious Powered by Edge AI GPU Utilization Is Becoming the New Cloud Waste Crisis Cómo solucionar `docker run` con exit code 1 en Raspberry Pi JWT is a scam and your app doesn't need it 7 Agent Skill Packs That Actually Make AI Coders Better More Control, More Cost: Why Commanding AI Isn't Delegation SecureScan Synthadoc: We Built an AI Judge for Our AI Wiki Compiler - Here's What We Learned Cómo solucionar el error de permiso al ejecutar `pip.exe` en entorno virtual (Python 3.10 en Windows) Postgres-grade Serializable at 20k+ ops/s — on a laptop. Don’t try this at home. Pure Core, Imperative Shell in Rust with Stillwater Lean 4 for Programmers: Building a Todo List with Proof Trustless Bug Bounty Releases with a PoW-Gated DLC Oracle Building Autonomous DevOps Agents with MCP and LangChain Multimodal Gemma 4 Visual Regression & Patch Agent Git Time Machine — How Version Control Can Save Your Project My Dad Got an Electricity Bill He Couldn't Understand. Google I/O 2026 Just Made That Problem Solvable. My Dad Got an Electricity Bill He Couldn't Understand. Google I/O 2026 Just Made That Problem Solvable. Read Replicas Lie About Consistency. 4 Sync Modes Behind the Lie. Reviving My Coding Project with GitHub Copilot I Tried Gemini 3.5 Flash After Google I/O 2026 - Here is What I Found :)) Zero-Cost AI in VS Code Blueprints Might Be More Important Than Frameworks AI CareCompanion - Offline Health Assistant Long-Context Models Killed RAG. Except for the 6 Cases Where They Made It Worse. I Built a Neural Network Engine in C# That Runs in Your Browser - No ONNX Runtime, No JavaScript Bridge, No Native Binaries An In-Depth Overview of the Apache Iceberg 1.11.0 Release How I Built a Multi-System Astrology Bot in Python (And What Meta Banned Me For) Gemma 4 Has Four Variants. Here's How to Pick the Right One Before You Write a Single Line of Code. Log Level Strategies: Balancing Observability and Cost Why WebMCP Is the Most Important Thing Google Announced at I/O 2026 (And Nobody's Talking About It) Making LLM Calls Reliable: Retry, Semaphore, Cache, and Batch Google's 2x Energy Efficiency Claim Is Real — But Here's What They're Not Measuring What's actually going on with CORS, under the hood
귀하의 에이전트가 같은 도구를 47번 호출했습니다. 여기 20줄 디텍터입니다.
Gabriel Anha · 2026-05-24 · via DEV Community

$47K 루프

A LangChain 사용자가 한 주말에 약 4만 7천 달러를 탕진했습니다. 그 이유는 그들의 에이전트가 한 도구 호출을 계속 반복했기 때문입니다. 이 이야기는 2023년에 트위터와 HN에서 회자되었고, 실패의 모양은 여전히 변하지 않았습니다. 에이전트는 동일한 검색 도구를, 동일한 인자를 반복해서 호출했고, 프레임워크는 기쁨으로 모든 결과를 다음 프롬프트에 다시 넣고 각 라운드마다 청구했습니다.

추적을 통해 10초면 볼 수 있습니다. 연속 47개의 시퀀스, 같은 tool_name, 같은 args 페이로드, 다른 타임스탬프입니다. 아무도 그렇게 쓰지 않습니다. 아무 모델도 그렇게 쓰고 싶지 않습니다. 하지만 도구를 사용하는 에이전트를 흐릿한 질문과 약간 고장 난 도구 앞에 두면 같은 호출을 반복해서 반복해서 반복해 그것을 멈추게 할 것입니다.

사라져야 할 것을 살리는 것은 20줄의 파이썬 코드입니다. 이것은 에이전트에 있지 않습니다. 이것은 추적 파이프라인에 있으므로 프레임워크 교체, 모델 업그레이드, 그리고 금요일 오후 4시에 팀이 하는 다음 리팩토링을 생존합니다.

max_iterations는 잘못된 조절기입니다

구글의 첫 페이지에서 받는 조언은 "설정하세요"입니다max_iterations=10". 이것은 고속도로에 대한 주거지역의 속도 제한이 잘못되었던 동일한 이유로 잘못되었습니다. 이는 합법적인 작업을 처벌합니다.

두 에이전트가 동일한 제품에서 실행 중인 경우를 고려해 보세요.

에이전트 A는 심층 연구 보조원입니다. PDF를 가져오고 검색을 실행하며 요약하고, 세 가지 인용문을 따르고, 세 번 더 검색을 실행하며, 발견 결과를 중복 제거하고, 메모를 작성합니다. 80개의 도구 호출, 모두 다르고, 모두 유용합니다. 사용자는 그 깊이를 위해 지불했습니다.

에이전트 B는 불안정한 벡터 인덱스를 가진 질문-답변기입니다. 쿼리 #1에서 search_docs(query="refund policy")을 호출합니다. 결과는 최신 임베딩이 없어 비어 있습니다. 에이전트는 "다시 시도해야겠다"고 생각하고 search_docs(query="refund policy")을 두 번째로 호출합니다. 그 다음 세 번째로. 7단계까지 이르러서면 동일한 도구를 정확히 동일한 인자로 일곱 번 연속으로 호출하고 있습니다.

10번째 자르기에서는 에이전트 A를 완료하기 전에 끊고, 에이전트 B는 네 번째 반복까지 불타고 나서야 멈춘다. 반대를 원한다: 에이전트 A는 진전이 있을 때까지 계속 실행되고, 에이전트 B는 4번째 반복에서 죽는다. 반복이 신호이지, 깊이는 아니다.

20줄 안의 감지기

여기 있다. 슬라이딩-윈도우 카운터는 (tool_name, args_hash)에 대한 키로 연결된다. 모든 도구 호출을 밀어 넣다. 어떤 키가 나타나면threshold지난 시간 동안window전화를 걸다, 일으키다.

from collections import deque, Counter
from dataclasses import dataclass, field
import hashlib
import json


class LoopDetected(Exception):
    pass


@dataclass
class LoopDetector:
    window: int = 10
    threshold: int = 4
    _calls: deque = field(default_factory=deque)

    def observe(self, tool_name: str, args: dict) -> None:
        key = (tool_name, _args_hash(args))
        self._calls.append(key)
        if len(self._calls) > self.window:
            self._calls.popleft()
        counts = Counter(self._calls)
        most_common_key, hits = counts.most_common(1)[0]
        if hits >= self.threshold:
            raise LoopDetected(
                f"{most_common_key[0]} called {hits}x "
                f"in last {len(self._calls)} steps"
            )


def _args_hash(args: dict) -> str:
    canonical = json.dumps(_canonicalize(args), sort_keys=True)
    return hashlib.sha256(canonical.encode()).hexdigest()[:16]


_VOLATILE_KEYS = {
    "timestamp", "request_id", "trace_id", "span_id",
    "nonce", "now", "_ts", "correlation_id",
}


def _canonicalize(value):
    # strip keys that change every call but don't change intent
    if isinstance(value, dict):
        return {
            k: _canonicalize(v)
            for k, v in value.items()
            if k not in _VOLATILE_KEYS
        }
    if isinstance(value, list):
        return [_canonicalize(v) for v in value]
    return value

전체 화면 모드로 입력 전체 화면 모드 종료

그것이 전체 감지기입니다. 가져오기(imports)를 어떻게 세느냐에 따라 약 스물 줄 정도입니다. 넣고 호출(call)하세요observe()모든 도구 호출 후에 포착하다LoopDetected, 유용한 일을 해보세요.

해시가 16개의 16진수 문자로 잘려나갑니다. 충돌은 여기서는 중요하지 않습니다. 잘못된 긍정(두 개의 다른 호출이 동일한 해시를 생성하는 경우)은 본래의 루프가 실제가 아니기 때문에 비용이 들지 않으며 다음 정당한 호출이 패턴을 깨뜨립니다. 잘못된 부정(해시가 충돌하여 실제 루프가 통과하는 경우)은 10번의 호출 창 동안 16개의 16진수 문자로서 통계적으로 무관합니다.

어디에 넣을까요

세 가지 옵션이 있습니다. 가장 나쁜 순서대로 나열되어 있습니다.

에이전트 루프 안에. LoopDetector 를 에이전트 러너에 가져와 호출합니다.observe() 각 도구 호출 후. 쉽다. 하지만 취약하다. LangChain을 LangGraph로 바꾸거나 분기 중에 다른 프레임워크로 이동한 날, 감지기는 오래된 코드와 함께 간다. 또한 새로운 에이전트를 모든 곳에 장비화해야 한다. 팀이 급하게 출시하는 세 번째 에이전트는 이를 갖지 못할 것이다.

프레임워크 콜백. LangChain은BaseCallbackHandler은 LangGraph에 노드 훅이 있고, OpenAI의 Agents SDK에는 라이프사이클 이벤트가 있습니다. observe()을 호출하는 하나의 콜백을 작성합니다. 인라인보다 좋습니다. 여전히 프레임워크에 특화되어 있습니다. 여전히 교체하면 사라집니다.

OTel span exporter. 이곳이 그것이 속해야 할 위치입니다. 이미 트레이스가 exporter를 통해 흐르고 있습니다. 추가하면SpanProcessor는 도구 호출 범위를 모니터링하고 그 위에서 감지기를 실행합니다. 프레임워크에 독립적입니다. 잊어서는 안 됩니다. 오늘 출시되었든 지난 분기에 출시되었든 여러분의 함선에 있는 모든 에이전트를 포착합니다.

배치는 다음과 같습니다.

from opentelemetry.sdk.trace import SpanProcessor
from opentelemetry.sdk.trace.export import (
    BatchSpanProcessor,
)
from collections import defaultdict


class LoopDetectingProcessor(SpanProcessor):
    def __init__(self, inner: SpanProcessor):
        self.inner = inner
        # one detector per trace_id
        self._detectors = defaultdict(LoopDetector)

    def on_start(self, span, parent_context=None):
        self.inner.on_start(span, parent_context)

    def on_end(self, span) -> None:
        # GenAI semconv name for a tool invocation
        if span.name == "execute_tool":
            attrs = span.attributes or {}
            tool_name = attrs.get(
                "gen_ai.tool.name", "unknown"
            )
            # tool args often live under gen_ai.tool.call.arguments
            raw_args = attrs.get(
                "gen_ai.tool.call.arguments", "{}"
            )
            try:
                args = json.loads(raw_args)
            except (TypeError, ValueError):
                args = {"_raw": str(raw_args)}

            trace_id = format(span.context.trace_id, "032x")
            detector = self._detectors[trace_id]
            try:
                detector.observe(tool_name, args)
            except LoopDetected as exc:
                span.set_attribute("loop.detected", True)
                span.set_attribute("loop.reason", str(exc))
                # signal the agent runtime via your own channel:
                # Redis pub/sub, a kill flag in DB, etc.
        self.inner.on_end(span)

    def shutdown(self):
        self.inner.shutdown()

    def force_flush(self, timeout_millis=30000):
        return self.inner.force_flush(timeout_millis)

전체 화면 모드를 입력합니다. 전체 화면 모드를 종료합니다.

기존의 내보내기 도구를 감싸서 추적 제공자에 등록합니다. 이제 감지기는 플랫폼이 실행하는 모든 에이전트에서 발생하는 모든 도구 범위를 볼 수 있습니다. 속성 이름은 OpenTelemetry GenAI 의미적 규범(gen_ai.tool.name, gen_ai.tool.call.arguments)을 따르므로 이 코드는 해당 범위를 발생시키는 모든 것과 작동합니다.

튜닝 창과 임계값

실제 운영에서 유효한 기본값: window=10threshold=4.

사유. 잘 행동하는 ReAct 에이전트가 첫 결과가 불분명하다고 도구를 다시 방문할 때, 두 번을 때릴 수 있으며, 약간 다른 인자를 사용하여 세 번을 때릴 수도 있습니다. 10단계에 동일한 호출이 네 번이면 탐색하고 있지 않습니다. 멈춰 있습니다. 임계값을 3으로 밀어 넣으면 한 단계 일찍 루프를 포착하지만, 일부 정당한 재시도를 표시합니다. 5로 밀어 넣으면 루프당 한 번 더 낭비된 호출을 허용하여, GPT-4 클래스 토큰 비용으로는 실제 돈입니다.

만약 당신의 에이전트에 지수적 백오프가 내장되어 있다면 (호출, 대기, 다시 호출, 더 길게 대기), 창을 15-20으로 넓히고 임계값을 4로 유지하세요. 백오프는 반복을 더 많은 단계에 걸쳐 늘려주기 때문에, 넓은 창은 정당한 재시도에 대해 과도하게 반응하지 않고 그것을 포착할 수 있습니다.

만약 당신의 도구 카탈로그가 작다면 (3-5개 도구) 그리고 에이전트가 정당하게 많은 도구를 다시 방문한다면, 예를 들어 read_file 코드 에이전트에서처럼search_web는 연구 에이전트에서 (tool_name, args_hash)에 중점을 두지 말고 tool_name에만 집중해야 합니다. 인자 해시는 "search_web을 8번 다른 쿼리로 호출" (괜찮음)과 "search_web을 8번 동일한 쿼리로 호출" (불량)을 구분하는 것입니다.

탐지 시 해야 할 일

세 가지 옵션, 에이전트를 얼마나 신뢰하는지에 따라 증가하는 순서로.

Killswitch. 기본값. 예외를 발생시키고, 루프를 로그에 기록하고, 호출자에게 구조화된 오류를 반환합니다. 저렴하고 안전합니다. 사용자가 다시 시도합니다.

경고 메시지와 함께 강화합니다. 시스템 메시지를 주입합니다: "동일한 인자로 search_docs를 네 번 호출했습니다. 도구는 동일한 결과를 반환하고 있습니다. 다른 접근 방식을 시도하거나 중지하고 발견한 내용을 보고하세요." 모델이 일반적으로 터질 때가 있습니다. 때로는 그렇지 않고 그 다음 관찰 시 killswitch가 작동합니다.

페이지 상태입니다.루프가 실제 장애를 의미하는 에이전트에 대해 (예: 사용자 재시도가 없는 내부 자율 도구와 같은 경우) LoopDetected를 PagerDuty에 연결합니다. 드물지만, 루프를 절대로 하면 안 되는 에이전트에 대해서는 페이지가 올바른 형태입니다.

쿨리프트 스위치부터 시작하세요. 복구 가능한 루프 정보를 얻었을 때만 다운그레이드 경고 후 진행하세요.

두 가지 경계 사례가 문제를 일으킵니다

비결정적 인자. 도구 인자에 타임스탬프, 요청 ID, 또는 nonce가 포함되어 있으면 해시가 매 호출마다 다르게 나뉩니다. 위의 정규화기는 일정한 유동 키 세트(timestamp, request_id)를 제거합니다trace_id , span_id , nonce , now , _ts , correlation_id 에 전환하기 전에 추가하세요. 자신의 도구 스키마에서 새로운 변동 필드를 만나면 이 집합에 추가하세요. created_at: <now> 를 인수로 끼워 넣는 에이전트는 다른 방법으로는 잡지 못할 에이전트입니다.

스트리밍 도구 호출. 일부 프레임워크는 도구 호출이 여전히 실행 중일 때 일부 스팬을 발생시킵니다. gen_ai.tool.call.id를 가진 스팬만 필터링하고 호출이 여전히 스트리밍 중인 경우를 무시하세요. 그렇지 않으면 하나의 느린 도구 호출을 여러 관찰로 계산하여 스스로 잘못된 긍정을 당하게 될 수 있습니다.

이가 당신의 스택에서 어디에 위치하는지

이 감지기는 생산 에이전트가 가져야 할 세 가지 런타임 가드 중 하나입니다.

토큰 예산. 각 에이전트 호출당 누적 입력 + 출력 토큰에 대한 엄격한 제한. 루프 탐지가 놓치는 "프롬프트가 200K 토큰으로 커졌다"는 실패 모드를 포착합니다.

루프 감지기. 이 포스트에 언급된 것. 고정된 반복을 포착합니다.

목표 완료 검증기. 마지막에 별도의 작은 LLM 호출을 통해 "이 에이전트가 사용자의 요청을 실제로 수행했는지, 아니면 포인트를 놓치는 자신감 있는 출력을 생성했는지"를 확인합니다. 첫 두 가지를 놓치는 "30단계 동안 실행되었지만 쓰레기가 생성되었음" 오류를 포착합니다.

트레이스 파이프라인에서 모든 세 가지를 실행하세요, 에이전트 안에서는 안 됩니다. 에이전트는 신뢰할 수 없는 부분입니다. 파이프라인이 경비가 있는 곳입니다.

가장 나쁜 에이전트 루프를 생산 환경으로 배포한 적이 있나요? 트레이스를 댓글에 올려주세요. 누군가 47을 깨뜨렸는지 알고 싶어요.


이것이 유용했다면

런타임 가드 삼각형(토큰 예산, 루프 감지기, 목표 검증기)은 AI 에이전트 포켓 가이드: LLMs로 자율 시스템을 구축하는 패턴들 중 하나입니다.는 나머지 생산 체크리스트를 다루고 있습니다: 도구 카탈로그 규율, 하위 대리인 경계, 재생 및 이동 감지, 그리고 모든 것을 관찰할 수 있게 하는 추적 레이어 인스트루먼트입니다. 대리인을 배포하고 하나의 장소에서 패턴을 제시하고 싶다면 그 책입니다.

AI Agents Pocket Guide: Patterns for Building Autonomous Systems with LLMs