慣性聚合 関心のあるブログ、ニュース、テクノロジーを効率的に追跡
原文を読む 慣性聚合で開く

おすすめ購読元

雷峰网
雷峰网
让小产品的独立变现更简单 - 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

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 Language-Agnostic Code Generation: The Driver Plugin Model Why We Rewrote Our Python CLI in Go (and What We Gained) I added up everything Google gives developers for free after I/O 2026. It's kind of absurd The Dawn of Smarter Apps: My Take on Google I/O 2026 AI Announcements Why AI Agents Like Hermes Need a Semantic Execution Layer for the Physical World
あなたのエージェントが同じツールを47回呼び出しました。こちらが20行の検出器です。
Gabriel Anha · 2026-05-24 · via DEV Community

ドル47Kのループ

A LangChainのユーザーが一週末で約4万7千ドルを燃費悪い理由で使い切った。この話は2023年にTwitterとHNで広まり、失敗の形は変わっていない。エージェントは同じ検索ツールを同じ引数で何度も呼び出し、フレームワークは喜んで各結果を次のプロンプトにフィードバックし、各ラウンドに請求した。

トレースで10秒見ればわかる。47回連続で、同じtool_name、同じargsペイロードだが、タイムスタンプは違う。誰もそんなコードを書かない。どのモデルもそんなコードを書こうとはしない。しかし、少し壊れたツールを使うエージェントに曖昧な質問を突きつけると、同じコールを何度も繰り返し、何かが終了するまで続ける。

殺してやるべきものはPythonの20行です。それはエージェントに存在しません。トレースパイプラインに存在するので、フレームワークの交換、モデルのアップグレード、そして金曜日の午後4時に行うチームの次のリファクタリングを生き延びます.

なぜ max_iterations は間違ったノブです

Googleの最初のページで受け取るアドバイスは「設定してください」というものですmax_iterations=10". これは、住宅街の速度制限が高速道路で間違っているのと同じ理由で間違っている。これは正当な作業を罰している。

同じ製品で実行されている二つのエージェントを考慮してください。

エージェントAは深いリサーチアシスタントです。PDFを取得し、検索を実行し、要約し、3つの引用をフォローし、さらに3つの検索を実行し、検出結果を重複排除し、メモを書きます。80回のツール呼び出し、すべて異なる、すべて有用です。ユーザーはその深さのために支払いました。

エージェントBは不安定なベクトルインデックスを持つ質問応答エージェントです。クエリ#1ではsearch_docs(query="refund policy")を呼び出します。結果が空であるのは、古い埋め込み 때문です。エージェントは「もう一度試すべきだ」と考え、search_docs(query="refund policy")を2回目に呼び出し、その後3回目に呼び出します。ステップ7までに、エージェントは同じツールを同じ引数で7回連続で呼び出しています。

10回の深さ制限でエージェントAが完了する前に切断され、エージェントBが6回の反復を燃焼させる前にトリップする。逆を望む:エージェント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

を起こす。 フルスクリーンモードを終了する

これが全ての検出器です。インポートの数によっては20行程度です。これを投入し、各ツール呼び出しの後observe()を呼び出し、LoopDetectedをキャッチし、何か有用なことをする。

ハッシュは16進数文字16文字に切り詰められます。衝突はここでは問題ではありません。偽陽性(同じハッシュを計算する2つの異なる呼び出し)は何の損害もありませんなぜならループは実在せず次の正当な呼び出しがパターンを破ります。偽陰性(ハッシュが衝突したため実際のループが抜け出す)は統計的に無視できるレベルです16進数文字16文字を10回の呼び出しウィンドウで見ると

どこに置くべきですか

あなたには三つの選択肢があります。最悪から最善までランク付けされています。

エージェントループの内部。 あなたはLoopDetectorをエージェントランナーにインポートし、呼び出します。observe() それぞれのツール呼び出しの後に。簡単だ。しかし脆い。LangChainをLangGraphに置き換える日や、四半期の途中で別のフレームワークに移行する日、検出器は古いコードに付いていく。新しいエージェントをインストゥメント化するのを忘れないようにする必要もある。チームが急いでリリースする3番目のエージェントにはそれがない。

フレームワークコールバック. LangChainにはBaseCallbackHandler、LangGraphにはノードフックがあり、OpenAIのAgents SDKにはライフサイクルイベントがあります。observe()を呼び出すコールバックを一つ書きます。インラインより良いですが、まだフレームワーク固有です。交換するとまだ動かなくなります。

OTel spanエクスポートャー。これが適切な場所です。トレースはすでにエクスポートャーを通じて流れています。追加してください。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.namegen_ai.tool.call.arguments)に従っているため、このコードはそれらのスパンを発行するものと互換性があります.

ウィンドウと閾値の調整

実際に機能するデフォルト値: window=10threshold=4.

その理由。初めての結果が不明瞭だったため、行動を起こすエージェントがツールを再度訪れる場合、2回、場合によっては3回、少し異なる引数でヒットします。10ステップで4回同じ呼び出しがあると、それが探索しているわけではなく、行き詰まっていることを意味します。閾値を3に押し上げると、ループを1ステップ早く捉えることができますが、一部の正当な再試行もフラグが立つ可能性があります。閾値を5に押し上げると、ループごとに1回の無駄な呼び出しが通過するため、GPT-4クラスのトークンレートでは実際のお金になります。

エージェントに指数的バックオフが組み込まれている場合(呼び出し、待機、再度呼び出し、より長く待機)、ウィンドウを15-20に広げ、閾値を4に保ってください。バックオフは繰り返しをより多くのステップに広げるので、より広いウィンドウが正当な再試行を捉えることができ、正当な再試行で過剰にトリガーされないようにします.

ツールカタログが小さい場合(3-5つのツール)で、エージェントが正当に多くのツールを再訪問する場合、例えばread_fileコーディングエージェントであればsearch_web は研究エージェントにおいて、(tool_name, args_hash) に重点を置くだけでなく、tool_name も重要です。args ハッシュが "search_web を8回異なるクエリで呼び出す"(問題ない)と "search_web を8回同じクエリで呼び出す"(問題がある)を区別するものです。

検知された場合にどうするか

三つの選択肢、あなたのエージェントをどれだけ信頼するかの順で並べます。

クイックスイッチ。 デフォルト。例外を発生させ、ループを記録し、呼び出し元に構造化されたエラーを返す。安価で安全。ユーザーが再試行する。

プロンプトでダウングレードする。 システムメッセージを挿入する:「同じ引数でsearch_docsを4回呼び出しました。ツールは同じ結果を返しています。異なるアプローチを試すか、停止して発見したことを報告してください。」 モデルは通常、エラーを発生させます。時にはそうでなく、その後、次の観測時にキルスイッチが作動します。

当番ページ。 ループが実際の停電を意味するエージェント(例えば、ユーザーリトライがない内部自律ツールなど)では、LoopDetected PagerDutyに接続します。まれですが、ループすべきでないエージェントにとって、ページは適切な形です。

キルスイッチから始めてください。回復可能なループに関するデータがある場合にのみ、ダウングレードを促すモードに移行してください.

誤解を招く二つのエッジケース

非決定的な引数. ツールの引数にタイムスタンプ、リクエストID、またはノンスが含まれている場合、ハッシュが各呼び出しで分岐します。上記のコンカナライザーは、一連の変動キー(timestamprequest_id)を削除しますtrace_idspan_idnoncenow_tscorrelation_idをハッシュ化する前に追加してください。ご自身のツールスキーマで新しいvolatileフィールドに到達したときにそのセットに追加します。created_at: <now>をそのargsに仕込むエージェントこそ、そうでなければ捕まらないであろうエージェントです。

ストリーミングツールコール。 いくつかのフレームワークは、ツール呼び出しがまだ実行中である間に部分的なスパンを発行します。gen_ai.tool.call.id でフィルタリングし、呼び出しがまだストリーミング中のものは無視します。そうしないと、一つの遅いツール呼び出しを複数の観察としてカウントし、自分自身に偽陽性を報告することになります。

これはあなたのスタックにどのように組み込まれますか

この検出器は、プロダクションエージェントが持つべき3つの実行時ガードの1つです。

トークンバジェット。各エージェント呼び出しごとに累積的な入力+出力トークンの硬い上限。ループ検知が捉えられない「プロンプトが20万トークンに成長した」という失敗モードを捉える。

ループ検知器。この投稿に記載されているもの。固着した繰り返しを捉える。

目標完了検証器。最後に別途小さなLLM呼び出しを行い、「このエージェントが実際にユーザーの要求通りに行動したか、それとも要点を外れた自信満々な出力を生成したか?」を確認する。最初の二つに見逃される「30ステップ実行したが、ゴミを出力した」という失敗を検出する。

トレースパイプライン内で全ての三つを実行し、エージェント内ではなく。エージェントは信頼性の低い部分である。パイプラインがガードが行われる場所である。

本番環境にデプロイした最もひどいエージェントループは何ですか?トレースをコメントに載せてください。47を超えた人はいるか知りたいです。


これは役に立った면

ランタイムガードの三要素(トークンバジェット、ループ検知器、目標検証器)は、AIエージェントポケットガイド:LLMで自律システムを構築するためのパターンの一つです。。本書は残りの生産チェックリストをカバーしています:ツールカタログの規範、サブエージェントの境界、リプレイとドリフト検出、そしてそれらを観測可能にするトレースレイヤーのインストゥルメンテーション。エージェントをリリースし、一箇所でパターンを整理したいなら、それが本です。

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