LangGraph 客服 agent 死循环一夜烧 OpenAI 3000 美金:tool-call 失控复盘 + 4 层熔断设计

LangGraph 客服 agent 上线两周后某夜账单 200(平时 0)。某 VIP 用户对话触发 search_kb 死循环,12 小时调 OpenAI 4.7 万次。根因:recursion_limit bug + LLM 工具偏好。完整复盘 + 四层熔断 + cost guard 设计。

2026 年 3 月 14 日早上 8:42,我在通勤的地铁里被财务老板的电话叫起来:"昨天晚上 OpenAI 平台你们花了 3247 美金,平时一天 80 美金左右,是不是接口被人盗刷了?"我当时心里咯噔一下——昨晚我们刚把客服 agent 的新版本(基于 LangGraph 状态机重构,替换掉旧的 LangChain ReAct 实现)灰度到 20% 流量,21:00 上线,看了半小时数据正常我就下班了。账单跳变就发生在我下班后的 11 个小时内。

到工位第一件事是把灰度流量切回 0,翻 OpenAI Usage Dashboard。看到的画面让人脊背发凉:从 22:30 开始,某个特定的会话 ID 在 12 小时内调用了 OpenAI API 4 万 7 千次,消耗 token 1.2 亿,其中 input 1.1 亿、output 1100 万。换算下来,这一个会话单独烧掉了 2800 美金。后面 6 天我们带着 ML 平台组把 LangGraph + tool calling 的整套机制翻了个底朝天,定位到一个被所有 agent 框架文档都低估的问题:工具调用循环没有 cost 维度的硬性熔断,只要 LLM 持续输出新的 tool_call,循环就会一直跑下去,直到把模型上下文窗口打满才被动停止。这篇是完整复盘。

服务背景:这个客服 agent 在做什么

维度 数值
业务 SaaS 客服 agent,接入用户对话,自动检索知识库 / 查订单 / 转人工
规模 日均会话 8 万,平均每会话 4 轮交互,峰值 QPS 35
技术栈 Python 3.12 + FastAPI + LangGraph 0.2 + OpenAI GPT-4o
工具集 5 个 tool:search_kb / get_order / refund_estimate / escalate_to_human / get_user_profile
正常成本 $80 ~ $120 / 天(基于平均 4 轮 × 平均 800 input + 200 output token)
事故当天 $3247(增长 38 倍),其中 88% 来自单一会话
事故前架构 LangChain AgentExecutor(经典 ReAct)+ max_iterations=15
事故触发架构 LangGraph StateGraph + 工具节点 + 条件边路由

需要交代的背景是:从 ReAct AgentExecutor 迁到 LangGraph 是去年底就立项的事,主要动机是 LangChain 的 ReAct 实现"黑盒"程度高,不好做状态追踪和工具熔断。LangGraph 把整个 agent 流程显式化成状态机,理论上更可控。但我们在迁移时犯了一个致命的疏忽——把 LangChain 的 max_iterations=15 这个隐式护栏给"忘了"。

事故时间线:从财务电话到根因落地的 6 天

时刻 事件
03-13 21:00 LangGraph 版客服 agent 灰度 20% 流量上线,首批 30 分钟数据正常
03-13 22:31 某个 VIP 客户会话进入异常状态(具体触发条件后面分析),agent 开始反复 tool call
03-13 22:31 ~ 03-14 08:30 该会话持续运行 10 小时,期间共调用 OpenAI API 4.7 万次,消耗 1.2 亿 token
03-14 08:42 财务通过 OpenAI 账单告警发现异常,呼叫我
03-14 09:15 我到工位,把 LangGraph 版灰度流量切回 0,锁定异常会话 ID
03-14 上午 拉这个会话的完整 LangSmith trace,看到 4700 次循环全是同一个 tool 调用
03-14 下午 本地复现:构造类似输入,LangGraph 5 分钟跑了 80 次循环还没停,确认是结构性问题
03-15 ~ 03-16 读 LangGraph 源码 + OpenAI tool calling 文档,理解 tool_choice / parallel_tool_calls 的语义
03-17 设计修复方案——三层防御:状态机 max_steps + 工具调用频次限制 + cost budget guard
03-18 实现 + 单元测试 + 压力测试(故意构造死循环输入)
03-19 预发跑 24 小时模拟流量,无死循环 + cost 在预算内,重新灰度上线
03-20 全量切换,事后写《AI Agent 成本与可靠性纪律》

第一反应:"是不是被人盗刷了 API key"

财务电话挂掉后,我在路上脑子里转的全是"API key 泄漏"——这是 OpenAI 账单异常最常见的解释,Github 上每天都有人不小心把 key push 出去被矿工挖。直到上班登 OpenAI 后台,看到"Usage by API Key"页面,发现所有调用都来自我们生产环境的那个 key,IP 也都是 K8s 出口 IP,没有外部访问。再切到"Usage by Model"看,全部是 gpt-4o,不是被挖矿常用的 dall-e 或 whisper。基本可以排除盗刷,问题在我们自己。

第二个怀疑是"是不是有人写了死循环 + 一直在压测"。查了一下我们的内部压测平台,昨晚没人跑过任何 load test。最后看 OpenAI 后台的 request log,发现 4.7 万次请求全集中在一个 user_id对应的 session,而且 prompt 模板是我们 LangGraph agent 的固定格式——这才把方向锁定到 agent 本身。

真凶 1:LangSmith trace 揭穿的 tool-call 死循环

LangSmith 是 LangChain 公司的 trace 工具,会把每次 agent run 的所有 step 录下来,包括 LLM 调用、tool 调用、状态转移。我们一直接入着,这次救了大命。打开那个异常会话的 trace,看到的画面非常典型:

Session ID: sess_8f3a... (用户: VIP 客户)
Total steps: 4731
Total cost: $2812.40

Step 1: HumanMessage("我上周买的那个 XX 产品订单号是多少来着,帮我查一下退款的")
Step 2: AIMessage(tool_calls=[{name: 'search_kb', args: {query: '退款政策'}}])
Step 3: ToolMessage(name='search_kb', content='{"results": [], "total": 0}')
Step 4: AIMessage(tool_calls=[{name: 'search_kb', args: {query: '退款流程'}}])
Step 5: ToolMessage(name='search_kb', content='{"results": [], "total": 0}')
Step 6: AIMessage(tool_calls=[{name: 'search_kb', args: {query: '退款条件'}}])
Step 7: ToolMessage(name='search_kb', content='{"results": [], "total": 0}')
Step 8: AIMessage(tool_calls=[{name: 'search_kb', args: {query: '如何退款'}}])
... (持续 4724 步, 全是 search_kb 调用, 全返回空)

清晰得令人发指:LLM 反复换搜索词调 search_kb,知识库一直返回空,LLM 仍然不放弃,继续换关键词重试。每一步都是一次完整的 LLM 调用,把所有历史 context 喂回去——而 context 是单调增长的(每步加一个 AIMessage + 一个 ToolMessage),所以单次调用的 input token 也在线性增长。10 小时下来,最后几次调用的 input 已经超过 80K token。

更要命的是,OpenAI gpt-4o 的 input price 是 $2.50 / 1M token,但我们当时还开了 prompt cache 优化,正常情况下重复 prefix 是命中缓存的,但这种"每次都加新内容"的模式让 cache hit rate 几乎为 0,每次都按全价计费。

真凶 2:LangGraph 缺省没有 step limit

翻 LangGraph 源码,我们的 StateGraph 编译时确实可以传 recursion_limit 参数控制最大步数,但这个参数默认是 25——理论上 25 步就应该停。可我们的会话跑了 4731 步,这意味着 recursion_limit 在我们的实现里没起作用。

仔细看了 LangGraph 的源码,发现 recursion_limit 是个比较"温柔"的机制——它只在 graph 的 invoke / stream 调用层计数,只要单次 invoke 的步数没超就放过。我们的实现是用 app.stream() 持续消费输出,每个 user message 进来都新启动一次 invoke,所以每次 invoke 内部的步数限制能用上;但我们的客服 agent 在 ToolMessage 之后会自动循环回到 agent 节点继续判断,这本质上是一次 invoke 内部的多步——recursion_limit 应该能管住才对。

查到第三天才搞明白真相:LangGraph 的 recursion_limit 在我们用的 0.2.16 版本里有个已知 issue——当 graph 包含一个"工具节点 → agent 节点 → 工具节点"的循环边时,如果工具节点是 ToolNode(LangGraph 自带的标准实现),计数会被某条边的内部分发重置。这个 issue 在 0.2.18 修了,我们的版本停留在 0.2.16,刚好踩中。

版本 recursion_limit 行为
0.2.16(我们用的) ToolNode 内部会重置计数,导致 limit 失效
0.2.18 起 修复,任何节点都计入总步数
0.3.x API 升级,默认 recursion_limit=25 + 显式 RecursionError 抛出

这就是升级框架时不锁定补丁号的代价。我们 pyproject.toml 里写的是 langgraph = "^0.2.0",pip resolve 时刚好挑了 0.2.16(那是去年底的稳定版),错过了 0.2.18 的修复。

真凶 3:为什么 LLM "不愿意放弃"

修第二个问题之前我们想搞清楚一个事:就算 framework 有 bug,LLM 自己为什么会陷入"换词重试"的死循环?按理说调几次空结果之后,LLM 应该意识到"知识库没有这个内容",转而给用户兜底回复或者 escalate to human。

我们做了两个实验:

实验 设置 结果
实验 1 同样的初始 prompt,手动构造 2 次空结果后停止 LLM 第 3 次仍然选择换关键词重试 search_kb
实验 2 5 次空结果后,system prompt 追加"如果搜索 3 次都为空,使用 escalate_to_human" 第 4 次 LLM 终于选择 escalate
实验 3 把模型从 gpt-4o 换成 gpt-4o-mini,同样输入 2 次空结果就 escalate(更早放弃)
实验 4 同样 prompt,改用 Claude Sonnet 4.6 2 次空结果后转为用自然语言回复用户,不再 tool call

结论非常有意思:大模型(尤其是优化过 tool use 的版本)有"工具偏好"——倾向于继续用工具而不是放弃。这在大多数场景是好事(避免 hallucinate),但在"工具一直返空"的边缘情况会导致死循环。Claude 比 GPT 在这点上更"克制",但也不能完全依赖模型自觉,必须在 framework 层强制熔断。

翻 OpenAI 的官方 cookbook 关于 tool calling 那一节,确实有一句话:"You should always set a maximum number of tool calls per session, regardless of model behavior." 但藏在第三屏,谁会去读完。

修法:三层防御 + 一层兜底

事后我们设计了一套"洋葱型"防御,从内到外:

机制 触发条件 动作
L1 (最内) 同名工具重复调用熔断 同 session 同 tool 名连续调用 ≥ 3 次 强制注入 system message 提示 "Tool returned no useful result 3 times, switch strategy or escalate"
L2 step limit 单 session 总 step 数 ≥ 12 强制走 escalate_to_human 节点
L3 session token budget 单 session 累计 token ≥ 50,000 截断对话,提示用户"请换个问题或转人工"
L4 (最外) 租户级 daily cost cap 单租户单日花费 ≥ $20 整个租户当天 fallback 到关键词匹配模式(不调 LLM)

关键代码(简化版):

from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
from operator import add

class AgentState(TypedDict):
    messages: Annotated[list, add]
    tool_call_history: Annotated[list, add]   # 记录每次 tool 调用
    total_tokens: int
    session_id: str
    tenant_id: str

# L1: 工具节点 wrapper, 重复调用熔断
def tool_node_with_guard(state: AgentState):
    last_msg = state["messages"][-1]
    if not getattr(last_msg, "tool_calls", None):
        return {"messages": []}

    tool_call = last_msg.tool_calls[0]
    tool_name = tool_call["name"]

    # 检查最近 3 次是否都是同一个 tool
    recent = state["tool_call_history"][-3:]
    if len(recent) == 3 and all(t == tool_name for t in recent):
        # 注入提示, 不再执行 tool
        guard_msg = SystemMessage(
            content=f"Tool '{tool_name}' has been called 3 times consecutively. "
                    "Stop calling it. Either answer the user directly, or use 'escalate_to_human'."
        )
        return {"messages": [guard_msg], "tool_call_history": [tool_name]}

    # 正常执行 tool
    result = execute_tool(tool_call)
    return {
        "messages": [ToolMessage(content=result, tool_call_id=tool_call["id"])],
        "tool_call_history": [tool_name],
    }

# L2: 路由函数, 检查 step 数
def should_continue(state: AgentState):
    if len(state["messages"]) >= 24:    # 等价于 step ≥ 12 (每 step 2 条 message)
        return "escalate"
    last = state["messages"][-1]
    if getattr(last, "tool_calls", None):
        return "tools"
    return END

# L3: agent 节点 wrapper, 检查 token budget
def agent_node_with_budget(state: AgentState):
    if state["total_tokens"] >= 50000:
        return {
            "messages": [AIMessage(
                content="抱歉,本次对话上下文已超出最佳处理范围,建议您新开会话或转人工继续。"
            )]
        }
    response = llm.invoke(state["messages"])
    new_tokens = response.usage_metadata["input_tokens"] + response.usage_metadata["output_tokens"]
    return {"messages": [response], "total_tokens": new_tokens}

# L4: tenant 级 daily cap, 在入口处检查
def chat_endpoint(req: ChatRequest):
    spent = redis.get(f"cost:{req.tenant_id}:{today()}") or 0
    if float(spent) >= 20.0:
        return fallback_keyword_search(req)
    # 正常走 agent
    result = app.invoke({"messages": [HumanMessage(req.text)], ...})
    redis.incrbyfloat(f"cost:{req.tenant_id}:{today()}", estimate_cost(result))
    return result

这套机制上线后我们故意在预发构造死循环输入,验证四层防御依次触发:L1 在第 3 次重复工具调用时打断;万一 L1 没生效(比如 LLM 在不同工具间循环),L2 在 step 12 时强制 escalate;再万一,L3 在 50K token 时截断;最后还有 L4 兜底,单租户单天最多 $20——就算前面全失效,也不会出现单租户烧几千美金的情况。

顺手做的几件防御性工作

1. LangSmith 实时告警

原来 LangSmith 只用于事后查 trace,事故后我们接了它的 alerts API,配三个告警:

  • 单 session step 数 ≥ 15:告警通知 oncall
  • 单 session 累计 token ≥ 30K:告警 + 自动截断
  • 单 session 累计 cost ≥ $5:电话呼叫 + 立即 kill session

2. OpenAI Usage API 接入到内部 metrics

OpenAI 提供了 Usage API,可以每分钟拉一次本组织的实时消耗。我们做了一个 cron 每分钟拉一次,推到内部 Prometheus,Grafana 上能看到 token 消耗的实时曲线。任何陡升都会告警——之前我们只看 OpenAI 自己的账单,有 24 小时滞后。

3. Agent prompt 改进:显式声明 tool 调用边界

我们在 system prompt 里加了一段:

You have access to 5 tools. Important rules:
1. NEVER call the same tool more than 2 times in a row with similar arguments.
2. If a tool returns empty or no useful information twice, STOP using that tool.
3. After 8 total tool calls in this conversation, you MUST give the user a final answer
   (even if incomplete) or use escalate_to_human.
4. If you find yourself unsure how to proceed, use escalate_to_human IMMEDIATELY.

这段 prompt 改进让模型在大多数情况下能"自觉"早停,但我们仍然保留了上面四层硬性机制——prompt 是软约束,只能依赖模型理解,不能作为唯一防线。

4. 框架版本锁定 + 升级 review 流程

把 pyproject.toml 里的 ^ 全部改成 ~(只允许 patch 版本浮动),并在 README 写明:任何 langgraph / langchain / openai SDK 的 minor 版本升级,必须有"4 小时 chaos 测试 + 50 个边缘 case 跑通"的证据。这次事故部分就是因为我们对依赖版本掉以轻心。

这次事故让我们重新理解的几件事

1. "max_iterations" 是 agent 框架最重要的一个参数

LangChain 的 AgentExecutor 默认 max_iterations=15,这个值看起来不起眼,实际上是整个 ReAct 范式的安全网。LangChain 早期的设计者很有先见之明地加了这个保险,但 LangGraph 把范式从"ReAct 循环"改成了"显式状态机"后,这个隐式保险就被弱化成了 recursion_limit——而且默认 25(比 15 还宽松)、还有 bug。这是典型的"重构丢了关键不变量"。

给所有用 agent 框架的同学一条建议:无论用什么框架,都在你的代码里显式 assert 一个"最大 LLM 调用次数"和"最大 token 消耗"的硬性上限,不依赖框架默认。这是几乎不可能错的护栏。

2. LLM 的"工具偏好"是系统性问题,不是偶然

实验里我们看到 GPT-4o 比 GPT-4o-mini 更倾向于继续 tool call,Claude 比 GPT 更克制。这个差异背后是 RLHF 训练时的偏好设置——大厂在 RLHF 时奖励"信息充分再回答"的行为,但奖励信号不够精细,容易过度激励"重复尝试工具"。

这意味着模型选型也要把"边界行为"考虑进去。我们事后跑了一个小评测,在一组刻意触发死循环的输入上对比了 8 个模型,Claude Sonnet 4.6 的"自觉早停"比例最高,GPT-4o 最低。这个数据让我们部分场景下重新评估了模型选型。

3. "成本"必须是 agent 系统的一级监控指标

我们以前的监控只关注"延迟"和"成功率",成本是按月算账。事故后我们把实时成本提升到和"P99 延迟"同一优先级的指标:每分钟统计、Grafana 实时看、超过阈值自动熔断。这套机制上线后,曾经多次抓到"某个 prompt 模板的 token 用量异常增长"——比如有同学不小心把整个用户历史塞进了 context,token 翻 3 倍,在告警里 5 分钟就看到了。

立的《AI Agent 成本与可靠性纪律》

  • 任何 agent 必须有 4 层熔断:工具重复调用熔断 + 总 step 上限 + session token 上限 + 租户日 cost cap。缺一不可。
  • 框架依赖必须锁 patch 版本,升级前必须跑"边缘 case 套件"(死循环输入、超长 context、工具一直失败、工具一直返回空 等 20+ 场景)。
  • 实时成本监控:OpenAI / Anthropic Usage API 接入 metrics,每分钟刷新,异常增长自动告警 + 熔断。
  • LangSmith 或同类 trace 工具必须在生产环境开启(可采样,不影响性能),事故复盘的核心证据。
  • system prompt 必须显式声明工具调用边界:最大次数、空结果重试上限、何时强制 escalate。这是"软约束",和硬性熔断配合用。
  • 模型选型要包含"边界行为"评测,不只是看主流 benchmark,自己跑一套针对死循环 / 长 context / 模糊输入的小评测。
  • 成本预算可视化给业务方:每个租户的日 / 月成本图表透传给业务,业务清楚 agent 的"贵在哪里"才能配合优化。

给读者的自查清单

  1. 打开你的 agent 入口代码,搜一下"max_iterations"、"recursion_limit"、"max_steps"——如果没显式设置,赶紧加上,值不超过 15。
  2. 在你的 tool 执行 wrapper 里,加一个"最近 3 次同 tool 同参数"的去重检查,触发时强制注入 stop hint。
  3. 在你的 LLM 调用 wrapper 里,累加 input + output token,超过 50K 直接截断并提示用户。
  4. OpenAI / Anthropic 后台开启 spending limit,设到你能承受的最大损失值(比如月成本预算的 1.5 倍)——这是最后的最后保险。
  5. 构造几个"死循环输入"加进你的 CI 测试集:模糊问题、知识库一定查不到的问题、工具一定失败的问题。每次发布都跑一下。
  6. 如果你用 LangChain / LangGraph,锁定到 patch 版本,升级前在变更日志里搜"recursion"、"loop"、"max"——这几类修复经常被淹没在 release notes 里。

这次事故让我对 AI agent 的稳定性建设有了新的认知:过去我们说"AI 系统要可靠",更多是说准确性 / 一致性 / 不 hallucinate,但 agent 时代多了一个全新的维度——"可控性"。一个会自动决策、自动调工具的系统,如果没有硬性边界,它的失败模式比传统软件更难预测、代价更高。这次烧的 3000 美金不算大数,但如果是个体开发者、如果发现晚一天、如果触发的是 OpenAI 限额内的更贵模型,数字可以轻松涨 10 倍。

所以下次有人在群里贴一段 agent 代码问"这样可以上生产吗",别只看 happy path——逼问他:"如果工具一直失败,会发生什么?"如果对方答不出来,那就是定时炸弹。

问题本质:agent 失控的传播链路

整个事件的根本机制可以这样画——LLM 的"工具偏好"+ framework 缺失的护栏 + cost 监控的滞后,三者叠加放大成一次烧钱事故:

给读者的 agent 成本守护清单

看完这篇,如果你的团队也在做 LLM agent,可以按这个清单立刻自查。每一条都是从事故里换来的:

第一步:打开你的 agent 入口代码,搜 max_iterations / recursion_limit / max_steps。如果没显式设置,赶紧加上,值不超过 15。这是 agent 系统最基础的护栏。

第二步:在你的 tool 执行 wrapper 里,加一个"最近 3 次同 tool 同参数"的去重检查,触发时强制注入 stop hint。这能挡住绝大多数"换关键词死循环"的场景。

第三步:在 LLM 调用 wrapper 里累加 input + output token,超过 50K 直接截断并提示用户。这是单 session 的 token 上限,防止上下文无限增长。

第四步:OpenAI / Anthropic 后台开启 monthly spending limit,设到你能承受的最大损失值。这是最后的最后保险,出问题不会让公司破产。

第五步:在 agent system prompt 里显式声明"工具调用边界":最大次数、空结果重试上限、何时强制 escalate。这是"软约束",和硬性熔断配合用。

第六步:构造"死循环输入"测试集加进 CI:模糊问题、知识库一定查不到的问题、工具一定失败的问题。每次发布都跑,任何回归就 block 合并。

第七步:接入实时成本监控。OpenAI Usage API 可以分钟级拉,推到 Prometheus,异常陡升立即告警。不要等月度账单,那时已经晚了 30 天。

团队事故复盘的几个关键决策

事故后的线上会议有几个让我印象深刻的决策值得分享:

第一个决策:全公司所有 agent 项目必须有"四层熔断"才能上生产。这不是建议,是硬性 gate。架构组评审,缺一项不让上。这种"硬门槛"让"我们改改 prompt 就上"这种轻率心态不再可能。

第二个决策:OpenAI / Anthropic 的 spending limit 设到全年预算的 1/10。意思是即使某一天完全失控,损失也限制在年预算 10% 内。这是 CFO 提的,把"技术稳定性"翻译成了"财务风险控制"——这种翻译能让管理层重视。

第三个决策:模型选型必须有"边界行为"评测,不只是看主流 benchmark。我们建了一个"adversarial eval set",专门测各模型在极端 case 下的行为(死循环 / 幻觉 / 越权)。Claude 在这套 eval 里表现明显比 GPT 好,我们部分场景切到了 Claude。

第四个决策:LangSmith 类的 trace 工具必须在生产环境开启,任何 session 都能事后追溯每一步。这次事故的快速定位完全靠 trace,如果没有,光找根因就要几天。

过去 6 个月后续观察

事故后 6 个月,我们再没出过类似规模的成本失控事件。但确实在三层熔断的某一层"挡下"过几次小规模的失控——大约每月 1-2 次,通常是某个用户的极端输入触发某个 tool 进入异常循环。三层熔断让影响范围控制在"单 session 30 美金以内",完全可接受。

更重要的副产品是团队对 agent 系统的"心智模型"更加成熟。过去大家把 agent 当成"智能服务调用",事故后开始把它当成"一个有自主决策能力的执行体"——任何时候它都可能做出预料之外的事。这种 mindset 的转变让设计决策更保守、更防御性,长期看是个巨大的提升。

如果你正在做 LLM agent 项目,这种"用事故换来的认知升级"我希望本文能帮你不用亲身经历就能 internalize。3000 美金看着不多,但它代表的是"你的系统在某个边缘场景下会失控到什么程度"——这种问题不修,迟早会以更大的代价来找你。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

TypeScript 5.5 升级把 VSCode 智能提示卡到 8 秒:类型实例化爆炸 6 天复盘

2026-5-26 11:16:30

技术教程

FastAPI 每隔 6 小时变僵尸:SQLAlchemy async 连接池静默泄漏 4 天复盘

2026-5-26 11:19:32

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索