पिछले महीने मेरे agent ने confidently एक tool call किया जिसका नाम search_invoices था। वह tool exist नहीं करता था। यह कभी exist नहीं किया। Model ने नाम invented किया, arguments invented किए, और इसके लिए एक perfectly-formed JSON block emit किया। मेरा harness, trusting और naive, इसे dispatch करने की कोशिश की और crash हो गया।
अगर आप agents बनाते हैं, आपने यह देखा है। Model एक tool call करता है जो आपके schema में नहीं है, या यह एक real tool call करता है लेकिन input schema से match न करने वाले arguments के साथ, या यह plain text में एक tool call narrate करता है बजाय actual tool_use block emit करने के। लोग इन सबको "tool calls hallucinate करना" कहते हैं। मैं यह explain करना चाहता हूं कि mechanism level पर ऐसा क्यों होता है, क्योंकि एक बार आप इसे देख लें, fixes superstition की तरह feel करना बंद हो जाती हैं।
Model tokens predict कर रहा है, आपका code नहीं चला रहा
यहाँ वह बात है जो कोई आपको शुरुआत में नहीं बताता। Model के side से एक tool call function invocation नहीं है। यह text है। Structured text, ज़रूर, लेकिन text। Model एक tool_use block उसी तरह emit करता है जिस तरह यह एक paragraph emit करता है: पहले की सब चीज़ को देखते हुए most likely next tokens predict करके। आपका schema उस context का हिस्सा है, लेकिन यह एक hard constraint नहीं है जब तक आप इसे एक नहीं बनाते।
तो यह कब गलत होता है? जब plausible next tokens का distribution आपके schema के बाहर की ओर point करे। अगर आपकी conversation history invoices के references से भरी है, और model decide करता है कि उसे invoice data चाहिए, तो search_invoices एक wildly plausible token sequence है — भले ही आपने जो tool दिया है उसका नाम वास्तव में query_billing है। Model हर उस चीज़ के against pattern-matching कर रहा है जो उसने देखी है, और "search_" + noun उसके training data में सबसे common tool-name shapes में से एक है।
इसीलिए hallucinated tool calls दो situations में spike करती हैं: जब आपके tool names unusual हों, और जब आपका context long और noisy हो।
Fix एक: सही call को सबसे probable call बनाएं
अपने tools का नाम उस तरह रखें जिस तरह model tools का नाम होने की expect करता है। get_weather, send_email, search_database. Boring अच्छा है। Boring predictable है, और predictable का मतलब है model का next-token guess आपके actual tool पर land करता है बजाय एक invented cousin के।
फिर descriptions लिखें जो कहें कब call करें, न सिर्फ tool क्या करता है। यह recent Claude models पर अधिक matter करता है — Opus 4.7 और 4.8 4.6 की तुलना में tools के लिए अधिक conservatively reach करते हैं, इसलिए "इसे call करें जब user current prices या recent events के बारे में पूछे" जैसी description measurably should-call rate बढ़ाती है। Trigger condition वह हिस्सा है जिस पर model condition करता है। इसे description में डालें, system prompt में नहीं।
Fix दो: bad calls को through मत आने दें
जब model एक malformed call emit करे, आपके harness को loudly reject करना चाहिए, crash नहीं होना चाहिए। is_error: true के साथ एक tool_result और एक message return करें जिसे model पढ़ सके:
{
"type": "tool_result",
"tool_use_id": "toolu_abc",
"content": "'search_invoices' नाम का कोई tool नहीं। Available: query_billing, get_customer.",
"is_error": true
}
Model अगले turn पर वह error पढ़ता है और correct करता है। मैंने Opus 4.8 को इस तरह से एक single turn में hallucinated tool name से recover करते देखा है — यह error देखता है, available list को देखता है, और सही call re-emit करता है। Failed call को silently drop मत करें। Same prompt retry मत करें और hope रखें। Error वापस feed करें।
Fix तीन: argument problem के लिए strict schemas
Name hallucination और argument hallucination अलग-अलग bugs हैं। Arguments के लिए — wrong types, missing required fields, extra fields — strict tool use उपयोग करें। Tool definition पर ही strict: true set करें (न कि tool_choice पर, वह कुछ नहीं करता), additionalProperties: false और एक required list के साथ। अब API guarantee करती है कि input आपके schema के against exactly validate होता है। Model literally एक argument shape emit नहीं कर सकता जो fit नहीं करती।
एक caveat: strict mode हर चीज़ के साथ compatible नहीं है। यह programmatic tool calling, forced tool_choice, या MCP tools के साथ काम नहीं करेगा। उनके लिए आप खुद validate करने पर वापस हैं।
Fix चार: call depend करने वाले context को shorten करें
Long-and-noisy trigger याद है? एक long agentic loop में, पुराने tool results pile up हो जाते हैं। Turn 40 तक model mostly-irrelevant output के 39 turns पर condition कर रहा है, और "कौन सा tool, कौन से args" का signal drown हो जाता है। यहाँ context editing अपना काम करती है — stale tool_use results clear करना ताकि model की next prediction जो actually relevant है उस पर based हो। यह एक beta feature है (clear_tool_uses_20250919), और यह compaction के समान नहीं है, जो summarize करता है बजाय clearing के। Tool-call accuracy के लिए, clearing summarizing को beat करती है, क्योंकि पुराने tool output का एक summary अभी भी distribution को nudge करता है।
वह जो लोगों को surprise करता है
जब model एक tool call narrate करता है बजाय इसे emit करने के — "मैं अब database में search करूंगा" बिना actual tool_use block के — वह really hallucination नहीं है। वह model early turn end कर रहा है। Long Claude runs पर यह occasionally बिना call के एक statement of intent लिखता है। Fix एक system-prompt nudge है: इसे बताएं कि एक turn end करने से पहले, अगर इसका last paragraph उस काम के बारे में एक promise है जो यह नहीं किया है, इसे अभी एक tool call के साथ करना चाहिए। एक plain "continue" इसे interactively recover करती है। Autonomous pipelines के लिए, instruction bake in करें।
मैं चाहता हूं कि आप यह takeaway लें: इनमें से कोई भी fix magic नहीं है। वे सब एक ही काम करती हैं — probability distribution को उस call की ओर bend करना जो आप चाहते हैं, या उस call को catch करना जो आप नहीं चाहते। जब model search_invoices invent करता है तो यह malfunctioning नहीं है। यह ठीक वही कर रहा है जो इसे करने के लिए बनाया गया था, एक ऐसे context के साथ जो गलत दिशा में point किया। इसे बेहतर point करें।
