{"record":{"id":"018fb1fde68402ea","repo":"alibaba/spring-ai-alibaba","slug":"model-call-limits-exceeded-threadcount-threa","errorCode":null,"errorMessage":"Model call limits exceeded: ${threadCount}/${threadLimit} thread, ${runCount}/${runLimit} run","messagePattern":"Model call limits exceeded: (.+?)/(.+?) thread, (.+?)/(.+?) run","errorType":"exception","errorClass":"ModelCallLimitExceededException","httpStatus":null,"severity":"error","filePath":"spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/hook/modelcalllimit/ModelCallLimitHook.java","lineNumber":74,"sourceCode":"\tpublic static Builder builder() {\n\t\treturn new Builder();\n\t}\n\n\t@Override\n\tpublic CompletableFuture<Map<String, Object>> beforeModel(OverAllState state, RunnableConfig config) {\n\t\t// Read current counts from context\n\t\tint threadModelCallCount = config.context().containsKey(THREAD_COUNT_KEY)\n\t\t\t\t? (int) config.context().get(THREAD_COUNT_KEY) : 0;\n\t\tint runModelCallCount = config.context().containsKey(RUN_COUNT_KEY)\n\t\t\t\t? (int) config.context().get(RUN_COUNT_KEY) : 0;\n\n\t\t// Check if limits are already exceeded (before making the call)\n\t\tboolean threadLimitExceeded = threadLimit != null && threadModelCallCount >= threadLimit;\n\t\tboolean runLimitExceeded = runLimit != null && runModelCallCount >= runLimit;\n\n\t\tif (threadLimitExceeded || runLimitExceeded) {\n\t\t\tif (exitBehavior == ExitBehavior.ERROR) {\n\t\t\t\tthrow new ModelCallLimitExceededException(\n\t\t\t\t\t\tthreadModelCallCount,\n\t\t\t\t\t\trunModelCallCount,\n\t\t\t\t\t\tthreadLimit,\n\t\t\t\t\t\trunLimit\n\t\t\t\t);\n\t\t\t}\n\t\t\telse if (exitBehavior == ExitBehavior.END) {\n\t\t\t\t// Add message indicating limit was exceeded and jump to end\n\t\t\t\tString message = buildLimitExceededMessage(\n\t\t\t\t\t\tthreadModelCallCount,\n\t\t\t\t\t\trunModelCallCount,\n\t\t\t\t\t\tthreadLimit,\n\t\t\t\t\t\trunLimit\n\t\t\t\t);\n\n\t\t\t\tList<Message> messages = new ArrayList<>();\n\t\t\t\tmessages.add(new AssistantMessage(message));\n","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/hook/modelcalllimit/ModelCallLimitHook.java#L56-L92","documentation":"ModelCallLimitHook.beforeModel() tracks model call counts per thread and per run. When a configured limit is reached and exitBehavior is ExitBehavior.ERROR, it throws ModelCallLimitExceededException (whose message reports counts like \"3/3 thread, 5/5 run\") before another LLM call is made, protecting against runaway token spend.","triggerScenarios":"Running an agent with ModelCallLimitHook configured with threadLimit and/or runLimit and ExitBehavior.ERROR, where the model call count for the thread or current run reaches the limit before the next model invocation.","commonSituations":"Long-running conversational threads that accumulate calls across runs and hit threadLimit; loop/iterative agents that call the model many times per run and hit runLimit; setting low limits for cost control in tests then deploying unchanged to production.","solutions":["Catch ModelCallLimitExceededException and treat it as a terminal state (summarize, notify user, or reset the thread).","Raise threadLimit/runLimit in the hook builder if the limit was set too aggressively.","Switch exitBehavior to a non-ERROR value (e.g. END) if exceeding the limit should stop gracefully instead of throwing.","Reset or start a new thread when the per-thread count is exhausted."],"exampleFix":"// before\nModelCallLimitHook hook = ModelCallLimitHook.builder().threadLimit(10).runLimit(5).exitBehavior(ExitBehavior.ERROR).build();\n// after\nModelCallLimitHook hook = ModelCallLimitHook.builder().threadLimit(50).runLimit(20).exitBehavior(ExitBehavior.END).build();","handlingStrategy":"try-catch","validationCode":"if (hook instanceof ModelCallLimitHook) { /* verify limits vs expected agent loop length */ \n  int expectedCalls = estimateModelCalls(agent);\n  assert expectedCalls < configuredLimit; }","typeGuard":null,"tryCatchPattern":"try {\n    agent.invoke(inputs);\n} catch (ModelCallLimitExceededException e) {\n    log.warn(\"Limit hit: {} thread, {} run\", e.getThreadCount(), e.getRunCount());\n    // terminate gracefully / notify / checkpoint\n}","preventionTips":["Size limits from measured call counts of your agent loops","Prefer ExitBehavior.END for graceful stop in production","Alert/monitor model call counters before limits trip","Reset threads periodically for long-lived conversations"],"tags":["java","model-call-limit","rate-limit","cost-control","agent-hook"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"f82da0b50f35744c13968191be2b1cd2452ef550","analyzedAt":"2026-09-09T15:32:42.421Z","contentChangedAt":"2026-09-09T15:32:42.421Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}