{"record":{"id":"fa5919abae7c301c","repo":"deepset-ai/haystack","slug":"min-tokens-must-be-at-least-0-got-min-tokens","errorCode":null,"errorMessage":"`min_tokens` must be at least 0, got {min_tokens}.","messagePattern":"`min_tokens` must be at least 0, got (.+?)\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"haystack/hooks/compaction/tool_result_pruning.py","lineNumber":75,"sourceCode":"        :param min_keep_steps: The minimum number of recent tool-calling Agent steps whose results remain untouched,\n            even when they exceed the target. Must be at least 1, which ensures the current result batch remains intact\n            until the model has acted on it.\n        :param min_tokens: Only prune tool-result messages that use more than this many tokens. Small results cost\n            little and are often the ones worth keeping.\n        :param placeholder: The text left in place of a pruned result, replacing the built-in one. May contain\n            `{tool_name}`, which is filled in with the name of the tool that produced the result.\n        :param skip_meta_keys: Results whose `meta` contains any of these keys are left alone. The default covers\n            results that a `ToolResultOffloadHook` already replaced with a reference to stored content: pruning one of\n            those would destroy the reference the model needs to read it back.\n        :raises ValueError: If `min_keep_steps` is less than 1 or `min_tokens` is negative.\n        \"\"\"\n        if min_keep_steps < 1:\n            raise ValueError(\n                f\"`min_keep_steps` must be at least 1, got {min_keep_steps}. The most recent tool-calling step \"\n                f\"contains results the model may still need.\"\n            )\n        if min_tokens < 0:\n            raise ValueError(f\"`min_tokens` must be at least 0, got {min_tokens}.\")\n        self.min_keep_steps = min_keep_steps\n        self.min_tokens = min_tokens\n        self.placeholder = placeholder\n        # Normalized to a tuple so a round trip through `to_dict`, which has to emit a list, restores the same type.\n        self.skip_meta_keys = tuple(skip_meta_keys)\n\n    def compact(\n        self, messages: list[ChatMessage], target_tokens: int, token_counter: TokenCounter\n    ) -> list[ChatMessage] | None:\n        \"\"\"\n        Replace the content of prunable tool results with a placeholder.\n\n        Results are considered oldest first and pruning stops as soon as the conversation reaches `target_tokens`.\n        This keeps as much original output as possible. Results from the most recent `min_keep_steps` tool-calling\n        Agent steps are never considered, even when the target cannot otherwise be reached. After measuring the initial\n        conversation, the running total is updated with per-result token deltas to avoid repeatedly counting the full\n        context.\n","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/hooks/compaction/tool_result_pruning.py#L57-L93","documentation":"ToolResultPruningHook requires min_tokens >= 0 in __init__ (haystack/hooks/compaction/tool_result_pruning.py:75). min_tokens is the size threshold below which tool results are left untouched; negative values are invalid.","triggerScenarios":"Constructing the hook with min_tokens < 0, typically from a subtraction-based computation or a bad config value.","commonSituations":"Computing min_tokens as a delta that goes negative; typos in config; conflating '0 means prune everything small' with negative 'disable' values.","solutions":["Pass min_tokens >= 0 (use 0 to prune any size).","Clamp: min_tokens = max(0, value).","Correct the config/env value feeding the constructor."],"exampleFix":"// before\nhook = ToolResultPruningHook(min_tokens=-100)\n// after\nhook = ToolResultPruningHook(min_tokens=max(0, configured_min_tokens))","handlingStrategy":"validation","validationCode":"def validate_min_tokens(v):\n    if not isinstance(v, int) or v < 0:\n        raise ValueError(f\"min_tokens must be >= 0, got {v!r}\")\nvalidate_min_tokens(cfg.get(\"min_tokens\", 50))","typeGuard":"def is_non_negative_int(v) -> bool:\n    return isinstance(v, int) and not isinstance(v, bool) and v >= 0","tryCatchPattern":"try:\n    hook = ToolResultPruningHook(min_tokens=t)\nexcept ValueError as e:\n    logger.error(\"bad min_tokens: %s\", e)\n    hook = ToolResultPruningHook(min_tokens=0)","preventionTips":["Use 0 (not negative) to mean 'prune regardless of size'","Clamp deltas with max(0, value)","Validate config at load time"],"tags":["validation","constructor","config","tool-results","compaction"],"backgroundTag":"invalid-constructor-argument","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}