स्वायत्त एजेंट सिस्टम में डिबगिंग और ऑब्जर्वेबिलिटी
चुपचाप विफल होने वाला स्वायत्त एजेंट किसी एजेंट के न होने से भी बुरा है। जब कोई पारंपरिक फ़ंक्शन एक्सेप्शन थ्रो करता है, तो आपको स्टैक ट्रेस मिलता है। जब एजेंट बीस टूल कॉल्स और तीन मॉडल इनवोकेशन में गलत रास्ते पर चला जाता है, तो आपको एक गलत जवाब मिलता है — और कोई स्पष्ट स्पष्टीकरण नहीं।
एजेंट्स को डिबग करने के लिए एक अलग मानसिक मॉडल की जरूरत होती है। सिस्टम एक निश्चित पथ निष्पादित नहीं कर रहा; यह निर्णयों की एक श्रृंखला ले रहा है। ऑब्जर्वेबिलिटी का मतलब उन निर्णयों को कैप्चर करना है — न केवल इनपुट और आउटपुट, बल्कि वह तर्क जो उन्हें जोड़ता है।
एजेंट्स के लिए पारंपरिक डिबगिंग क्यों विफल होती है
स्टैंडर्ड लॉगिंग कैप्चर करती है कि क्या हुआ। एजेंट ऑब्जर्वेबिलिटी को कैप्चर करना होगा क्यों — मॉडल ने क्या निष्कर्ष निकाला, उसने कौन सा टूल चुना और क्यों, और वह किस इंटरमीडिएट स्टेट से काम कर रहा था।
विफलता के तरीके भी अलग हैं:
- साइलेंट हैलुसिनेशन: एजेंट बिना अनिश्चितता का संकेत दिए आत्मविश्वास से गलत जवाब देता है।
- डिसीजन ड्रिफ्ट: प्रत्येक कदम स्थानीय रूप से उचित लगता है, लेकिन अनुक्रम लक्ष्य से भटक जाता है।
- टूल का गलत उपयोग: एजेंट सही टूल को सूक्ष्म रूप से गलत पैरामीटर के साथ कॉल करता है।
- अनंत लूप: एजेंट विफल दृष्टिकोण को दोबारा आजमाने में फंस जाता है।
- कॉन्टेक्स्ट पॉइजनिंग: किसी शुरुआती चरण की खराब आउटपुट बाद के सभी तर्कों को दूषित कर देती है।
इनमें से कोई भी एक्सेप्शन नहीं उत्पन्न करता। ये गलत व्यवहार उत्पन्न करते हैं जो केवल तब दिखाई देता है जब आप पूरे एक्सीक्यूशन ट्रेस को पुनर्निर्मित करते हैं।
एजेंट निर्णयों के लिए स्ट्रक्चर्ड लॉगिंग
पहला कदम है प्रत्येक एजेंट इंटरेक्शन को स्ट्रक्चर्ड लॉग्स में लपेटना। कच्चे API रिस्पॉन्स लॉग न करें — सेमेंटिक इवेंट्स लॉग करें।
import jsonimport timeimport uuidfrom dataclasses import dataclass, asdictfrom typing import Anyimport anthropic
client = anthropic.Anthropic()
@dataclassclass AgentEvent: trace_id: str step: int event_type: str # "llm_call", "tool_call", "tool_result", "decision", "error" model: str | None input_tokens: int | None output_tokens: int | None latency_ms: float | None content: dict[str, Any] timestamp: float
def log_event(event: AgentEvent): print(json.dumps(asdict(event))) # अपने लॉग डेस्टिनेशन से बदलें
class TracedAgent: def __init__(self, trace_id: str | None = None): self.trace_id = trace_id or str(uuid.uuid4()) self.step = 0 self.tools = []
def add_tool(self, name: str, description: str, input_schema: dict): self.tools.append({ "name": name, "description": description, "input_schema": input_schema })
def call(self, messages: list[dict], system: str = "") -> str: self.step += 1 start = time.monotonic()
response = client.messages.create( model="claude-opus-4-6", max_tokens=4096, system=system, tools=self.tools, messages=messages )
latency_ms = (time.monotonic() - start) * 1000
log_event(AgentEvent( trace_id=self.trace_id, step=self.step, event_type="llm_call", model="claude-opus-4-6", input_tokens=response.usage.input_tokens, output_tokens=response.usage.output_tokens, latency_ms=latency_ms, content={ "stop_reason": response.stop_reason, "text_blocks": [b.text for b in response.content if b.type == "text"], "tool_calls": [ {"name": b.name, "input": b.input} for b in response.content if b.type == "tool_use" ] }, timestamp=time.time() ))
return responseपूर्ण ट्रेस बनाना
एक लॉग लाइन पर्याप्त नहीं है — आपको पूरे एक्सीक्यूशन ट्रेस की जरूरत है जो प्रत्येक निर्णय को उसके परिणाम से जोड़ता है:
from typing import Callable
def run_traced_agent( task: str, tools: dict[str, Callable], tool_schemas: list[dict], system: str, max_steps: int = 20,) -> dict: agent = TracedAgent() for schema in tool_schemas: agent.add_tool(**schema)
messages = [{"role": "user", "content": task}] trace = {"trace_id": agent.trace_id, "task": task, "steps": []} step_count = 0
while step_count < max_steps: step_count += 1 response = agent.call(messages, system=system)
step_record = { "step": step_count, "stop_reason": response.stop_reason, "model_output": [], "tool_results": [] }
if response.stop_reason == "end_turn": for block in response.content: if block.type == "text": step_record["model_output"].append(block.text) trace["steps"].append(step_record) trace["final_answer"] = step_record["model_output"][-1] if step_record["model_output"] else "" break
tool_results = [] for block in response.content: if block.type == "tool_use": step_record["model_output"].append({ "tool": block.name, "input": block.input })
tool_fn = tools.get(block.name) if not tool_fn: result = f"त्रुटि: अज्ञात टूल '{block.name}'" else: try: result = tool_fn(**block.input) except Exception as e: result = f"टूल त्रुटि: {e}"
tool_results.append({ "type": "tool_result", "tool_use_id": block.id, "content": str(result) }) step_record["tool_results"].append({ "tool": block.name, "result_preview": str(result)[:200] })
trace["steps"].append(step_record) messages.append({"role": "assistant", "content": response.content}) messages.append({"role": "user", "content": tool_results})
else: trace["error"] = f"अधिकतम चरण संख्या पार ({max_steps})"
return traceलूप डिटेक्शन
अनंत लूप एक सामान्य विफलता मोड है। प्रत्येक LLM कॉल के टूल इनवोकेशन पैटर्न का फिंगरप्रिंट लेकर उन्हें डिटेक्ट करें:
def detect_loop(trace: dict, window: int = 4) -> bool: steps = trace["steps"] if len(steps) < window: return False
def step_signature(step: dict) -> str: tools_called = sorted( t["tool"] if isinstance(t, dict) else t for t in step.get("model_output", []) if isinstance(t, dict) and "tool" in t ) return "|".join(tools_called)
recent = [step_signature(s) for s in steps[-window:]] if len(set(recent)) == 1 and recent[0]: return True
if len(steps) >= 4: pattern = [step_signature(s) for s in steps[-4:]] if pattern[0] == pattern[2] and pattern[1] == pattern[3]: return True
return Falseप्रोडक्शन में ट्रैक करने योग्य मेट्रिक्स
from collections import Counter
def compute_trace_metrics(trace: dict) -> dict: steps = trace["steps"] errors = [s for s in steps if "error" in s]
tool_calls_by_name: Counter = Counter() for step in steps: for output in step.get("model_output", []): if isinstance(output, dict) and "tool" in output: tool_calls_by_name[output["tool"]] += 1
return { "trace_id": trace["trace_id"], "total_steps": len(steps), "error_steps": len(errors), "tool_call_distribution": dict(tool_calls_by_name), "completed": "final_answer" in trace, "loop_detected": detect_loop(trace), }अलर्ट के लिए मुख्य संकेत:
- लूप दर > 5% — एजेंट फंस रहा है
- प्रति टूल एरर दर > थ्रेशोल्ड — कोई टूल टूटा है
- औसत चरण संख्या बढ़ती प्रवृत्ति — कार्य कठिन होते जा रहे हैं
- p99 लेटेंसी स्पाइक — मॉडल एंडपॉइंट धीमा है
OpenTelemetry इंटीग्रेशन
पहले से OpenTelemetry उपयोग करने वाली टीमों के लिए, एजेंट ट्रेस को स्पैन के रूप में emit करें:
from opentelemetry import trace as otel_trace
tracer = otel_trace.get_tracer("agent")
def run_with_otel(task: str, tools: dict, tool_schemas: list, system: str): with tracer.start_as_current_span("agent.run") as root_span: root_span.set_attribute("agent.task", task[:200])
agent = TracedAgent() for schema in tool_schemas: agent.add_tool(**schema)
messages = [{"role": "user", "content": task}]
for step in range(20): with tracer.start_as_current_span(f"agent.step.{step}") as step_span: response = agent.call(messages, system=system) step_span.set_attribute("llm.stop_reason", response.stop_reason) step_span.set_attribute("llm.input_tokens", response.usage.input_tokens)
if response.stop_reason == "end_turn": breakलॉग में PII रिडेक्शन
एजेंट लॉग्स में अक्सर संवेदनशील डेटा होता है। किसी भी बाहरी सिस्टम को emit करने से पहले उन्हें redact करें:
import re
PII_PATTERNS = [ (re.compile(r'\b[\w.+-]+@[\w-]+\.[a-z]{2,}\b'), '[ईमेल]'), (re.compile(r'\b[6-9]\d{9}\b'), '[फोन]'), (re.compile(r'\bsk-[a-zA-Z0-9]{20,}\b'), '[API_KEY]'),]
def redact(text: str) -> str: for pattern, replacement in PII_PATTERNS: text = pattern.sub(replacement, text) return textतीन सबसे महत्वपूर्ण मेट्रिक्स
टास्क पूर्णता दर — कितने अनुपात में रन final_answer तक पहुंचते हैं बनाम max_steps या किसी त्रुटि के। प्रति टास्क प्रकार बेसलाइन स्थापित करें।
प्रति टास्क टोकन लागत — सभी चरणों में input_tokens + output_tokens का योग। 20% लागत वृद्धि बिना पूर्णता दर बदले आमतौर पर प्रॉम्प्ट डिग्रेडेशन का संकेत है।
टूल एरर दर — error_steps / total_steps। इस मेट्रिक में स्पाइक सीधे किसी टूटे टूल या API की ओर इशारा करता है।
एजेंट सिस्टम में ऑब्जर्वेबिलिटी वैकल्पिक नहीं है — यह उस सिस्टम के बीच का अंतर है जिसे आप iterate कर सकते हैं और उसके बीच जिसे आप केवल तब restart कर सकते हैं जब यह टूट जाता है। स्ट्रक्चर्ड इवेंट्स और ट्रेस IDs से शुरू करें। लूप डिटेक्शन जोड़ें। मेट्रिक्स पुश करें। यह निवेश तब अदा होता है जब पहली बार प्रोडक्शन में कोई विफलता आती है और आप अनुमान लगाने के बजाय ठीक-ठीक पुनर्निर्माण कर सकते हैं कि क्या हुआ था।
संबंधित लेख
- एजेंट एरर रिकवरी: प्रोडक्शन विश्वसनीयता के लिए 5 पैटर्न
- मल्टी-एजेंट पैटर्न: ऑर्केस्ट्रेटर, Workers और पाइपलाइन
- टूल उपयोग पैटर्न: विश्वसनीय एजेंट-टूल इंटरफेस बनाना
- स्टेट मशीन और एजेंट: LangGraph के साथ विश्वसनीय वर्कफ्लो बनाना