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

推荐订阅源

G
Google Developers Blog
Google DeepMind News
Google DeepMind News
Hugging Face - Blog
Hugging Face - Blog
D
Docker
F
Fortinet All Blogs
博客园 - 三生石上(FineUI控件)
Project Zero
Project Zero
Engineering at Meta
Engineering at Meta
J
Java Code Geeks
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Simon Willison's Weblog
Simon Willison's Weblog
S
Security Affairs
NISL@THU
NISL@THU
T
Tor Project blog
A
About on SuperTechFans
宝玉的分享
宝玉的分享
腾讯CDC
S
Schneier on Security
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
P
Privacy & Cybersecurity Law Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Stack Overflow Blog
Stack Overflow Blog
P
Privacy International News Feed
雷峰网
雷峰网
C
Cyber Attacks, Cyber Crime and Cyber Security
Vercel News
Vercel News
Cisco Talos Blog
Cisco Talos Blog
D
DataBreaches.Net
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Google Online Security Blog
Google Online Security Blog
Recorded Future
Recorded Future
L
LINUX DO - 热门话题
Microsoft Security Blog
Microsoft Security Blog
Latest news
Latest news
C
Check Point Blog
有赞技术团队
有赞技术团队
T
The Exploit Database - CXSecurity.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
云风的 BLOG
云风的 BLOG
SecWiki News
SecWiki News
Application and Cybersecurity Blog
Application and Cybersecurity Blog
爱范儿
爱范儿
月光博客
月光博客
V
Vulnerabilities – Threatpost
T
Threat Research - Cisco Blogs
P
Palo Alto Networks Blog
T
The Blog of Author Tim Ferriss
C
Cisco Blogs
Webroot Blog
Webroot Blog
S
Security @ Cisco Blogs

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
Stop Your AI Agents From Crashing, Looping, and Burning Through Tokens
pavelgj · 2026-05-05 · via DEV Community

If you've built agentic workflows with LLMs — the kind where a model calls tools, reasons over results, and loops back for more — you've hit the wall. Not the conceptual wall. The very real, very expensive wall where your agent crashes at turn 47 because a model returned a 503, or silently burns $12 calling the same search tool in an infinite loop, or stuffs 200K tokens of context into a request that could've been 20K.

These aren't edge cases. They're the default behavior of every agentic loop that runs long enough. And until now, the fix was the same every time: wrap everything in try/catch, add a turn counter, pray.

There's a better way now. Google's Genkit just shipped a generateMiddleware() API that lets you intercept and modify AI generation at every level — model calls, tool execution, and the entire generate loop. Think of it as Express middleware, but for LLM inference. And it changes how you build resilient agents.

I built three middleware on top of it — softFail, smartMaxTurns, and contextCompression — that solve the three problems I kept hitting in production. This article walks through the middleware API itself, why it matters, and how each middleware works.


The Middleware API: Intercept Everything

Genkit's generateMiddleware() gives you hooks into three layers of the generation process:

import { generateMiddleware } from 'genkit/beta';

const myMiddleware = generateMiddleware(
  {
    name: 'myMiddleware',
    configSchema: z.object({ /* per-call config */ }),
  },
  ({ config, pluginConfig, ai }) => ({
    // Wraps each model call (request → response)
    model: async (req, ctx, next) => {
      // modify request, call next(), modify response
      return next(req, ctx);
    },

    // Wraps each turn of the generate loop
    generate: async (envelope, ctx, next) => {
      // access envelope.currentTurn, modify messages, tools, etc.
      return next(envelope, ctx);
    },

    // Wraps each tool execution
    tool: async (req, ctx, next) => {
      // intercept tool calls, modify inputs/outputs
      return next(req, ctx);
    },
  })
);

Enter fullscreen mode Exit fullscreen mode

Three hooks, three levels:

  • model — fires for every model API call. You see the raw request and response. Perfect for catching errors, tracking token usage, or modifying model config on the fly.
  • generate — fires for each turn of the agentic loop. You get the full conversation, the current turn number, and can modify messages, tools, or short-circuit the loop entirely.
  • tool — fires for every tool execution. You can catch errors, modify inputs/outputs, or skip tools entirely.

The key insight: the generate hook is recursive. In a multi-turn agentic loop, each turn's generate hook runs inside the previous turn's next() call. The model hook, on the other hand, runs before the framework processes tool results and calls the next turn. Understanding this execution order is what makes powerful middleware possible.

You also register the middleware as a plugin so it shows up in the Genkit Dev UI:

const ai = genkit({
  plugins: [myMiddleware.plugin({ /* plugin-level options */ })],
});

// Then use it per-call:
const response = await ai.generate({
  model: 'googleai/gemini-flash-latest',
  prompt: 'Research this topic',
  tools: [searchTool, analyzeTool],
  use: [myMiddleware({ /* per-call config */ })],
});

Enter fullscreen mode Exit fullscreen mode

This is genuinely powerful. You're not monkey-patching or wrapping ai.generate() with utility functions. You're composing behavior at the framework level, with type-safe config schemas, proper lifecycle management, and full access to the Genkit runtime.

Now let's look at the three problems and their middleware solutions.


softFail: Stop Crashing, Start Recovering

The Problem

Your agent is 15 turns into a complex research task. It's called six tools, accumulated useful results, and is about to synthesize an answer. Then the model API returns a 503. ai.generate() throws. All that accumulated context and tool output? Gone. Your flow crashes and the user gets an error.

Or maybe a tool throws — a database query times out, an API returns an unexpected format. Same result: the whole agentic loop crashes.

Or the agent hits maxTurns. The framework throws a GenerationResponseError. The model's last response — which might contain useful partial results — is buried inside the error object.

The Solution

softFail catches all three failure modes and returns a clean GenerateResponse with finishReason: 'aborted' instead of throwing:

import { softFail } from 'genkitx-misc/soft-fail';

const response = await ai.generate({
  model: 'googleai/gemini-flash-latest',
  prompt: 'Do something complex',
  tools: [riskyTool],
  use: [softFail()],
});

if (response.finishReason === 'aborted') {
  // No crash. No lost context. Just a clean signal.
  const details = (response.custom as any)?.softFail;
  console.log(`Failed: ${details.reason}${details.error}`);
}

Enter fullscreen mode Exit fullscreen mode

How It Works

softFail uses all three middleware hooks:

  • Model hook: Wraps the model call in a try/catch. If the model throws, it returns a synthetic response with finishReason: 'aborted' and stashes the error details in response.custom.softFail. You can optionally filter by error status — only catch UNAVAILABLE and RESOURCE_EXHAUSTED, for instance, and let validation errors throw normally.

  • Tool hook: Wraps each tool execution. If a tool throws, the error message is returned to the model as a normal tool response ("Tool 'search' failed: connection timeout"). The model sees this and can recover — retry the tool, skip it, or wrap up with what it has. ToolInterruptErrors are never caught; those are intentional control flow.

  • Generate hook: Catches the GenerationResponseError that the framework throws when maxTurns is exceeded. Instead of losing the model's last response, it extracts it from the error and returns it with finishReason: 'aborted'. It also acts as a safety net for the model hook — if a synthetic aborted response triggers a downstream schema validation error, the generate hook re-surfaces the original aborted response.

// Only catch specific model errors
use: [softFail({ modelStatuses: ['UNAVAILABLE', 'RESOURCE_EXHAUSTED'] })]

// Don't catch tool errors — let them throw
use: [softFail({ tools: false })]

// Only handle max turns gracefully
use: [softFail({ model: false, tools: false })]

Enter fullscreen mode Exit fullscreen mode

What Can You Do With an Aborted Response?

The key insight is that an aborted response is still a valid GenerateResponse. The conversation history — response.messages — contains everything the agent accumulated up to the failure point: all the tool calls, tool responses, and model messages. You can feed that right back into ai.generate() to pick up where you left off:

const response = await ai.generate({
  model: 'googleai/gemini-flash-latest',
  prompt: 'Research this topic thoroughly',
  tools: [searchTool, analyzeTool],
  use: [softFail()],
});

if (response.finishReason === 'aborted') {
  const details = (response.custom as any)?.softFail;

  if (details?.reason === 'model-error') {
    // Model had a transient error — retry with the full conversation intact
    console.log('Model failed, retrying with accumulated context...');
    const retryResponse = await ai.generate({
      model: 'googleai/gemini-flash-latest',
      messages: response.messages, // All prior context preserved
      tools: [searchTool, analyzeTool],
      use: [softFail()],
    });
  }

  if (details?.reason === 'max-turns') {
    // Agent ran out of turns — prompt user or continue later
    console.log('Agent needs more turns. Continue?');
    // ... prompt user, then resume:
    const continued = await ai.generate({
      model: 'googleai/gemini-flash-latest',
      messages: response.messages,
      tools: [searchTool, analyzeTool],
      use: [softFail()],
    });
  }
}

Enter fullscreen mode Exit fullscreen mode

No accumulated work is lost. The agent's 15 turns of tool calls and reasoning are all in response.messages, ready to be continued immediately, after a delay, or after prompting the user to check their connection.

Composing with Retry and Fallback

softFail composes naturally with retry and fallback middleware. Put softFail outermost so it catches anything that still throws after retries are exhausted:

use: [
  softFail(),        // Last line of defense
  retry({ ... }),    // Retry transient errors first
  fallback({ ... }), // Try alternate models
]

Enter fullscreen mode Exit fullscreen mode


smartMaxTurns: Detect Loops, Not Just Count Turns

The Problem

maxTurns: 10 is a blunt instrument. Set it too low and your agent can't finish complex tasks. Set it too high and a looping agent burns through tokens calling the same tool with the same arguments 47 times before hitting the limit. There's no way to say "stop when you're stuck, not when you've used N turns."

The Solution

smartMaxTurns replaces the rigid counter with intelligent loop detection. It watches the conversation and terminates when it detects the agent is stuck — not when an arbitrary number is reached:

import { smartMaxTurns } from 'genkitx-misc/smart-max-turns';

const response = await ai.generate({
  model: 'googleai/gemini-flash-latest',
  prompt: 'Research and summarize...',
  tools: [searchTool, analyzeTool],
  use: [smartMaxTurns()],
});

const meta = (response.custom as any)?.smartMaxTurns;
if (meta) {
  console.log(`Terminated: ${meta.reason} after ${meta.turnsUsed} turns`);
}

Enter fullscreen mode Exit fullscreen mode

How It Works

smartMaxTurns takes ownership of turn management. It overrides the framework's maxTurns to effectively infinite, then uses its generate hook to apply intelligent checks on every turn:

Two heuristic detectors (enabled by default, zero cost):

  • Exact loop detection — Hashes tool calls across consecutive turns. If the agent calls the same tools with the same arguments N times in a row (default: 2), it's looping.
  • Response repetition — Detects when tools return identical outputs across consecutive turns. If the same tool keeps returning the same result, the agent isn't making progress.

One optional LLM judge (opt-in):

  • Sends the conversation to a separate model and asks: "Is this agent making progress or stuck?" The judge responds PROGRESSING or STUCK. You can configure how often it checks (every: 3 = every 3 turns after minTurns).

Three termination strategies:

// Abort immediately (default) — return aborted response
use: [smartMaxTurns({ onDetection: 'abort' })]

// Wrap up — remove tools, ask model for a final answer
use: [smartMaxTurns({ onDetection: 'wrapUp' })]

// Prune — remove only the looping tools, let the agent continue with others
use: [smartMaxTurns({ onDetection: 'pruneTools' })]

Enter fullscreen mode Exit fullscreen mode

The wrapUp strategy is particularly useful. Instead of hard-stopping, it strips all tools from the request and injects a message: "You have spent several turns working on this task. Please provide your best final answer now based on what you have learned so far." The model gets one final toolless turn to synthesize everything it's gathered.

pruneTools is even more nuanced — it only removes the tools that were involved in the loop. If the agent was looping on searchTool but also has analyzeTool available, it removes searchTool and lets the agent continue with analyzeTool.

use: [smartMaxTurns({
  maxTurns: 25,          // Hard ceiling (safety net)
  minTurns: 5,           // Don't check until turn 5
  onDetection: 'wrapUp', // Ask for a final answer
  detect: {
    exactLoops: { threshold: 3 },       // 3 identical calls to trigger
    responseRepetition: { threshold: 3 }, // 3 identical responses to trigger
    llmJudge: { every: 2 },             // Check every 2 turns after minTurns
  },
})]

Enter fullscreen mode Exit fullscreen mode


contextCompression: Shrink the Conversation, Keep the Knowledge

The Problem

Long-running agents accumulate context fast. Each tool call adds a request and response to the message history. By turn 20, you might have 150K tokens of context — most of which is verbose tool output from early turns that the model doesn't need anymore. You're paying for all of it on every subsequent turn. And eventually you hit the model's context window limit.

The Solution

contextCompression monitors token usage and automatically compresses the conversation when it gets too large. It triggers based on the actual inputTokens reported by the model — no custom tokenizer needed:

import { contextCompression } from 'genkitx-misc/context-compression';

const response = await ai.generate({
  model: 'googleai/gemini-flash-latest',
  prompt: 'Research and summarize...',
  tools: [searchTool],
  use: [contextCompression({
    maxInputTokens: 80000,
    toolResponses: { maxChars: 2000 },
    summarize: {
      model: { name: 'googleai/gemini-flash-lite-latest' },
    },
  })],
});

Enter fullscreen mode Exit fullscreen mode

How It Works

contextCompression uses both the model and generate hooks in a coordinated dance:

  • Model hook: After each model call, records inputTokens from the response usage metadata. This is what the generate hook checks on the next turn to decide whether to compress. The model hook also attaches compression metadata to response.custom so it propagates through to the final GenerateResponse.

  • Generate hook: On each turn, checks if the previous turn's inputTokens exceeded maxInputTokens. If so, applies compression strategies in order:

Three composable strategies:

  1. Tool response truncation — The cheapest option. Truncates verbose tool outputs to a character limit (maxChars: 2000), preserving the N most recent tool responses untouched. No LLM call needed. A 50KB API response becomes a 2KB excerpt with a …[truncated] marker.

  2. Message truncation — Drops the oldest messages beyond a hard cap (maxMessages: 30), always preserving system messages and recent messages. Blunt but effective when you just need to stay under a token limit.

  3. LLM summarization — Replaces older messages with a condensed summary generated by a (cheap, fast) model. The summary preserves important facts, decisions, and tool results while dramatically reducing token count. Summaries are cached across turns — if no new messages have shifted into the summarization window, the cached summary is reused without another LLM call.

use: [contextCompression({
  maxInputTokens: 80000,

  // Strategy 1: Truncate tool responses beyond 2000 chars
  // (keep last 2 tool responses intact)
  toolResponses: { maxChars: 2000, preserveRecent: 2 },

  // Strategy 2: Hard cap at 40 messages
  maxMessages: 40,

  // Strategy 3: Summarize old messages with a cheap model
  summarize: {
    model: { name: 'googleai/gemini-flash-lite-latest' },
    preserveRecent: 6, // Keep last 6 messages un-summarized
  },
})]

Enter fullscreen mode Exit fullscreen mode

The strategies compose. On a compression trigger, tool responses get truncated first, then messages are capped, then remaining old messages are summarized. You can use any combination — just tool response truncation for a zero-LLM-cost option, or the full pipeline for maximum compression.

The summary caching is worth highlighting: after the first summarization, the middleware tracks which messages have been summarized. On subsequent turns, if the summary message is still the oldest non-system message, the cached summary is reused. Only when new messages shift into the summarization window does it regenerate — and even then, it uses incremental summarization ([Previous summary] + [New messages]) rather than re-summarizing everything.


Composing Middleware

These three middleware are designed to work together:

const response = await ai.generate({
  model: 'googleai/gemini-flash-latest',
  prompt: 'Research and write a comprehensive report on...',
  tools: [searchTool, analyzeTool, writeTool],
  use: [
    softFail(),                                    // Catch crashes
    smartMaxTurns({ onDetection: 'wrapUp' }),      // Detect loops
    contextCompression({                           // Manage context size
      maxInputTokens: 80000,
      toolResponses: { maxChars: 2000 },
      summarize: { model: { name: 'googleai/gemini-flash-lite-latest' } },
    }),
  ],
});

Enter fullscreen mode Exit fullscreen mode

With this stack:

  • The agent won't crash if the model or a tool throws
  • It won't loop forever calling the same tool
  • It won't burn through tokens with ever-growing context
  • If it does get stuck, it'll wrap up with a final answer instead of hard-stopping

All of this with zero changes to your tools, prompts, or flow logic. Just use: [...].


First-Party Middleware

Genkit also ships a set of middleware out of the box in the @genkit-ai/middleware package:

  • retry — Automatic retries with exponential backoff on transient errors (RESOURCE_EXHAUSTED, UNAVAILABLE, etc.)
  • fallback — Switch to a backup model when the primary fails on specific error codes
  • toolApproval — Restrict tool execution to an approved list; unapproved tools trigger a ToolInterruptError for human-in-the-loop confirmation
  • filesystem — Grant the model access to the local filesystem with sandboxed file manipulation tools
  • skills — Auto-inject SKILL.md files into the system prompt and provide a use_skill tool for on-demand skill retrieval

These compose with the middleware in this article. For example, softFail + retry + fallback is a natural stack: retry transient errors, fall back to a cheaper model, and if everything still fails, return a clean aborted response instead of crashing.

Learn more: Genkit Middleware docs · @genkit-ai/middleware on npm


Getting Started

npm install genkitx-misc

Enter fullscreen mode Exit fullscreen mode

import { softFail } from 'genkitx-misc/soft-fail';
import { smartMaxTurns } from 'genkitx-misc/smart-max-turns';
import { contextCompression } from 'genkitx-misc/context-compression';

Enter fullscreen mode Exit fullscreen mode

The genkitx-misc package also includes quota, cache, and router middleware — all built on the same generateMiddleware() API.

Full docs, examples, and source: github.com/pavelgj/genkitx-misc


The generateMiddleware() API is available in genkit/beta. These middleware work with any Genkit-compatible model — Gemini, Claude, OpenAI, Ollama, or any custom model plugin.