广告位 · 页头(后台-模块管理-新建模块后替换此区域)

Agent总是无响应、工具调用失败?这份故障排查手册与解决方案请收好

Agent故障排查最忌一上来就重启服务或者直接更换模型,很多偶发问题其实藏在上下文窗口、工具契约和超时配置里。本文从实际生产案例出发,把常见Agent故障拆成链路层、工具层、状态层三类,并给出可复用的排查步骤和代码示例。

Agent总是无响应、工具调用失败?这份故障排查手册与解决方案请收好

一、先定位故障层级:别急着重启,从调用链和状态入手

Agent故障表现差异很大:可能无响应、重复输出、工具调用失败、回答到一半中断。排查的第一步是确定故障发生在哪一层。可以按外部模型API、Agent调度逻辑、工具执行、本地状态存储四段来切分。每段都有典型信号:如果模型API返回正常但Agent没有下一步动作,问题多在调度层;如果工具报错或返回空数据,问题在工具层;如果重启后会话暂时恢复但过一会儿又异常,通常是状态层。

快速定位时,可以先查看最近一次运行日志,过滤错误和工具调用关键词。下面这条命令假设日志存放在标准目录,实际路径可按部署环境调整。

tail -n 200 /var/log/agent/agent.log | grep -E "ERROR|WARN|tool_call|timeout"

通过日志可以观察模型是否正常返回、工具是否被调用、异常栈指向哪个模块。建议在Agent入口处统一打印会话ID、模型名、token消耗、工具名和耗时,这样后续排查不用反复加日志。

不要过早下结论。曾经有一个生产环境Agent连续三次调用天气接口都超时,团队重启进程后短暂恢复,半小时后再次出现,最后定位到是工具描述里把参数名写成了city_name,而后端接口接收的是city,导致每次调用都携带错误参数并触发重试。这类问题只有对照调用链才能暴露,盲目重启反而掩盖根因。

二、高频故障类型与排查方案

1. Agent无响应或推理中断

Agent无响应通常分两种:请求已到达模型但长时间未返回;或者调度循环卡在某个条件分支。先检查模型API的超时设置和最大token限制。部分框架在流式输出中断后没有清理事件循环,导致后续请求挂起。可以用curl直接打模型接口验证基础连通性。

curl -m 30 https://api.ipipp.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"agent-model","messages":[{"role":"user","content":"ping"}]}'

如果模型接口正常,接着检查Agent循环是否设置了最大迭代次数。没有上限的ReAct循环在工具返回异常时可能无限重试,表面看就像无响应。建议在调度代码里增加硬超时和步数上限,下面是一个简单示例。

max_steps = 10
for step in range(max_steps):
    action = agent.plan(observation)
    if action.type == "finish":
        break
    observation = execute_tool(action)
else:
    raise RuntimeError("agent exceeded max steps")

这段代码里的for...else结构可以在循环未被break时抛错,避免Agent无限挂起。实际项目中还要结合超时控制,防止单步工具调用长期占用。

2. 工具调用失败或参数错配

工具调用失败最常见的原因是契约不一致:工具描述声明参数A,实际后端接收参数B;或者类型不匹配,比如JSON里给了字符串但接口要整数。排查时先打印模型生成的原始工具调用参数,与工具schema逐项对比。

from langchain.tools import tool
@tool
def get_weather(city: str, date: str = "today") -> str:
    """查询指定城市天气,city为城市名,date为日期。"""
    return f"{city} {date} 晴"

如果模型输出的是 {"city_name": "Beijing"} 而工具参数是city,就会触发校验失败。可以在工具入口做参数归一化,或者让模型根据错误信息重写一次参数。不要静默吞掉错误,否则排查成本会翻倍。

另一个问题是超时与重试策略。很多Agent框架默认工具超时很短,比如5秒,复杂接口根本执行不完。建议按工具耗时设置分级超时,不要一刀切。外部接口还要区分超时和业务错误,业务错误不应盲目重试。下面是一个封装示例。

import time, requests
def call_tool(url, payload, timeout=15, retries=2):
    for i in range(retries + 1):
        try:
            resp = requests.post(url, json=payload, timeout=timeout)
            if resp.status_code == 200:
                return resp.json()
            raise RuntimeError(f"business error: {resp.status_code}")
        except requests.Timeout:
            if i == retries:
                raise
            time.sleep(2 ** i)

这里把超时异常和HTTP错误分开处理,可以避免因为业务错误反复重试造成雪崩。重试次数和退避时间需要根据下游接口容量调整。

3. 上下文丢失与多轮记忆失效

多轮对话中Agent突然忘记前面的约束,多半是上下文窗口被历史消息挤占,或者会话存储键错误。先统计每次请求实际发送给模型的token数,确认是否触顶截断。不同模型的上下文窗口不同,消息越长,后段遗忘越明显。可以将长对话压缩摘要,或使用滑动窗口只保留最近N轮,并在系统提示里固化关键约束。

另一个常见原因是多实例部署时会话状态存到本地内存,负载均衡把下一轮请求打到另一台机器,自然读不到历史。这类问题需要把会话状态迁移到Redis或数据库。下面是一个Redis存储示例。

import redis
import json
r = redis.Redis(host="127.0.0.1", port=6379, db=0)
def save_state(session_id, messages):
    r.set(f"agent:session:{session_id}", json.dumps(messages), ex=3600)
def load_state(session_id):
    data = r.get(f"agent:session:{session_id}")
    return json.loads(data) if data else []

存储到集中式缓存后,重启或切换实例都不会丢失状态。需要注意的是,如果消息体很大,Redis的读写也可能成为新瓶颈,此时可以只存摘要或最近若干轮消息。

4. 输出格式不稳定与解析失败

当Agent需要输出JSON或特定结构供下游系统使用时,模型偶尔会多输出一些解释文字、漏掉大括号,导致解析崩溃。可以通过提示词约束加解析代码容错双管齐下。在提示中明确只输出一个JSON对象,不要输出Markdown代码块。解析时用json.loads,失败后再用正则提取第一个平衡的花括号片段。

import json, re
def safe_parse_json(text: str):
    text = text.strip()
    try:
        return json.loads(text)
    except json.JSONDecodeError:
        match = re.search(r"\{.*\}", text, re.S)
        if match:
            return json.loads(match.group(0))
        raise

如果模型返回的字段类型不稳定,例如有时是列表有时是字符串,可以在解析后做归一化处理。更稳健的做法是使用模型提供的结构化输出能力或函数调用约束,但并非所有模型都支持。若输出格式要求严格,建议在Agent流程最后增加校验节点,不合格时请求模型重新生成一次。

三、建立可复用的Agent故障排查手册

单独一次排障成功并不能避免后续复发,建议把排查动作固化成手册。手册至少包含四部分:故障现象与匹配关键词、排查命令、常见根因、恢复动作。可以把Agent运行的关键指标接入监控,例如请求延迟、工具成功率、token消耗、空响应率。当指标超过阈值时主动告警,而不是等用户反馈。

下面是一个简单的健康检查脚本,可以定时探测Agent是否正常。

import requests, time
def health_check(endpoint):
    start = time.time()
    try:
        resp = requests.post(endpoint, json={"message": "ping"}, timeout=10)
        latency = time.time() - start
        if resp.status_code == 200 and "pong" in resp.text:
            print(f"ok, latency={latency:.2f}s")
        else:
            print(f"bad status: {resp.status_code}")
    except Exception as exc:
        print(f"health check failed: {exc}")
if __name__ == "__main__":
    health_check("https://agent.ipipp.com/echo")

这个脚本可以接入cron或监控平台,快速发现Agent不可用。需要注意的是,健康检查的message和pong需要与Agent实际响应格式一致,否则容易误报。

团队内部还可以做一张排查速查表,方便快速匹配:

现象优先检查常见原因
无响应模型接口、步数上限流式中断、无限重试
工具调用失败参数schema、超时契约不一致、超时过短
多轮记忆丢失会话存储、token截断本地内存、窗口爆满
输出解析失败提示词、解析代码格式不约束、缺容错

最后要强调,Agent故障排查的核心不是找模型问题,而是把不确定性收敛到可验证的系统行为。每次发布工具变更或调整提示词时,保留变更记录和回滚方案,能显著降低排障时间。可以在CI中增加工具契约测试,确保模型输出参数与后端schema一致。这样Agent故障处理就从救火变成体系化运维,团队的稳定性也会随之提升。

广告位 · 文章底部(后台-模块管理替换此区域)