[ToolNode在LangGraph中的运用-02]工具调用的拦截和异常处理

Pandas入门教程(二十二):股票数据分析实战——yfinance 获取数据、技术指标与收益率分析 把 Pandas 用在真实场景,才能体会它的价值。本文以茅台股票为例,用 yfinance 获取历史行情,完成缺失值处理与列筛选,绘制收盘价曲线,计算 SMA 移动平均线与 RSI 相对强弱指数,并通过日收益率、累计收益率和波动率完成完整的量化分析流程。 阅读详情

为了能够在LangGraph中更有效的利用ToolNode,我们对它的定义做一个详细的介绍。ToolNode继承了RunnableCallable,其name字段表示节点名称,上面我们将它称为tools节点就是因为它的默认值为tools。

1. ToolNode的构建

根据如下所示的针对__init__方法的定义,可用看出创建ToolNode时除了需要提供注册的工具序列之外,还可以初始化其他一些字段成员用于对工具的执行做相应的控制。

class ToolNode(RunnableCallable):
    name: str = "tools"
    def __init__(
        self,
        tools: Sequence[BaseTool | Callable],
        *,
        name: str = "tools",
        tags: list[str] | None = None,
        handle_tool_errors: bool
        | str
        | Callable[..., str]
        | type[Exception]
        | tuple[type[Exception], ...] = _default_handle_tool_errors,
        messages_key: str = "messages",
        wrap_tool_call: ToolCallWrapper | None = None,
        awrap_tool_call: AsyncToolCallWrapper | None = None,
    ) -> None:
        super().__init__(self._func, self._afunc, name=name, tags=tags, trace=False)
        self._tools_by_name: dict[str, BaseTool] = {}
        self._injected_args: dict[str, _InjectedArgs] = {}
        self._handle_tool_errors = handle_tool_errors
        self._messages_key = messages_key
        self._wrap_tool_call = wrap_tool_call
        self._awrap_tool_call = awrap_tool_call
        for tool in tools:
            if not isinstance(tool, BaseTool):
                tool_ = create_tool(cast("type[BaseTool]", tool))
            else:
                tool_ = tool
            self._tools_by_name[tool_.name] = tool_
            self._injected_args[tool_.name] = _get_all_injected_args(tool_)

ToolNode的__init__方法具有如下参数:

  • tools:注册的工具,如果执行的是一个Callable对象,将会转换成一个BaseTool对象,通常是一个StucturedTool对象。关于BaseTool以及从Callable向BaseTool的转换规则在“赋予Agent执行力的工具是个什么东西?”一文中具有详细介绍。所有表示工具的BaseTool对象会保存在_tools_by_name字段对应的字典中,Key为工具的名称;
  • name:当前节点的名称,默认为tools;
  • handle_tool_errors:用于指定异常处理的策略或者异常处理器;
  • messages_key:由于ToolNode的执行都是围绕着代表对话历史的消息列表进行的,所以它能工作的前提是具有如此一个表示消息历史的状态成员。该参数表示此状态成员的名称,默认值为messages;
  • wrap_tool_call/awrap_tool_call:包装工具调用(同步和异步)的包装器,利用它可以实现针对工具调用的动态拦截;

__init__方法处理将指定的参数赋值给对应的字段外,还完成了注入参数的提取,具体体现在针对_get_all_injected_args方法的调用上。该方法返回一个_InjectedArgs对象,用来表示工具函数中是否具有针对状态、表示长期存储的BaseStore和表示工具运行时的ToolRuntime的参数注入。因为ToolCall提供的参数列表不会包含这些自动注入的参数,但是在调用工具函数时却必须指定。我的文章“如何在工具函数中注入各种运行时对象?”具有对这个问题的详细介绍。

def _get_all_injected_args(tool: BaseTool) -> _InjectedArgs:
@dataclass
class _InjectedArgs:
    state: dict[str, str | None]
    store: str | None
    runtime: str | None

2. 工具调用的包装

我们知道表示中间件的AgentMiddleware定义了wrap_tool_call和awrap_tool_call方法用来工具调用的包装,我们会发现着两个方法的签名和ToolNode的wrap_tool_call/awrap_tool_call参数的声明的可执行对象具有一致的签名。当create_agent方法在创建ToolNode的时候,会提取所有注册的中间件,并创建两个Callable对象来表示体现针对它们的wrap_tool_call和awrap_tool_call方法的按序调用,这两个Callable对象会作为调用__init__方法的wrap_tool_call/awrap_tool_call参数。

ToolCallWrapper = Callable[
    [ToolCallRequest, Callable[[ToolCallRequest], ToolMessage | Command]],
    ToolMessage | Command,
]

AsyncToolCallWrapper = Callable[
    [ToolCallRequest, Callable[[ToolCallRequest], Awaitable[ToolMessage | Command]]],
    Awaitable[ToolMessage | Command],
]

class AgentMiddleware(Generic[StateT, ContextT]):
    def wrap_tool_call(
        self,
        request: ToolCallRequest,
        handler: Callable[[ToolCallRequest], ToolMessage | Command[Any]],
    ) -> ToolMessage | Command[Any]

    async def awrap_tool_call(
        self,
        request: ToolCallRequest,
        handler: Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]],
    ) -> ToolMessage | Command[Any]

如果ToolNode在LangGraph中使用,意味着我们得手工创建它。如果具有针对工具调用进行包装的需求,可以显式提供这两个参数。在如下的演示程序中,我们根据定义的工具get_weather创建了一个ToolNode,针对同步调用的包装器被设置成log函数,我们利用它输出工具调用得输入和输出。

from typing import Callable, Any
from langchain_core.tools import tool
from langgraph.types import Command
from langchain_core.messages import AIMessage,ToolCall,ToolMessage
from langgraph.prebuilt import ToolNode
from langchain.agents.middleware import ToolCallRequest
from langchain_core.runnables import RunnableConfig
from langgraph.runtime import Runtime
from langgraph._internal._constants import CONFIG_KEY_RUNTIME

@tool
def get_weather(city: str) -> str:
    """A tool to get weather information for a given city."""
    return f"It's sunny today in {city}."

def log(
        request: ToolCallRequest,
        handler: Callable[[ToolCallRequest], ToolMessage | Command[Any]],
    ) -> ToolMessage | Command[Any]:
    
    print(f"Tool call '{request.tool_call['name']}' started with args: {request.tool_call['args']}")
    result = handler(request)
    print(f"Tool call '{request.tool_call['name']}' finished with result: {result}")
    return result

tool_node = ToolNode(tools=[get_weather], wrap_tool_call=log)

tool_call = ToolCall(name="get_weather", args={"city": "Suzhou"}, id="tool_call_001")
messages = [AIMessage(content="", tool_calls=[tool_call])]
config ={
    "configurable": {
        CONFIG_KEY_RUNTIME: Runtime(),
    }
}

tool_message:ToolMessage = tool_node.invoke(messages, config=config)[0]
assert tool_message.name == "get_weather"
assert tool_message.content == "It's sunny today in Suzhou."
assert tool_message.tool_call_id == "tool_call_001"

我们提供一个AIMessage列表作为调用ToolNode的输入,此AIMessage中携带一个用于描述工具调用ToolCall对象。作为参数的RunnableConfig中添加了一个必需的Runtime对象。调用ToolNode返回一个包含一个ToolMessage对象的列表,此ToolMessage正是对get_weather工具函数返回值的封装。包装器log输出如下所示:

Tool call 'get_weather' started with args: {'city': 'Suzhou'}
Tool call 'get_weather' finished with result: content="It's sunny today in Suzhou." name='get_weather' tool_call_id='tool_call_001'

3. 异常处理

ToolNode``__init__方法的handle_tool_errors参数决定了当工具执行抛出异常时,ToolNode如何捕获并将其转化为发送回LLM的消息,而不是让整个程序直接崩溃。它具有如下几种定义方法:

  • 布尔值:
    • True: 捕获所有异常,并将错误堆栈信息作为字符串返回作为ToolMessage的内容返回给LLM;
    • False (默认): 不捕获异常。如果工具报错,程序直接抛出异常并停止运行;
  • 字符串:无论发生什么错误,都返回这段固定的字符串给LLLM,可用隐藏具体报错细节(出于安全考虑),给模型一个通用的重试建议;
  • 异常类型:定向捕获指定的异常。如果抛出的是指定异常,则将其转化为消息返回;如果抛出的是其他异常,程序依然会崩溃;
  • 自定义函数 (Callable[..., str]):异常处理函数。

该参数默认指定如下一个异常处理函数,意味着在默认情况下只处理ToolInvocationError。

def _default_handle_tool_errors(e: Exception) -> str:
    if isinstance(e, ToolInvocationError):
        return e.message
    raise e

如下的程序演示了几种异常异常处理方式,包括将参数handle_tool_errors指定为True、错误消息和异常处理函数。

from typing import Callable
from langchain_core.tools import tool
from langgraph.types import Command
from langchain_core.messages import AIMessage,ToolCall
from langgraph.prebuilt import ToolNode
from langchain_core.runnables import RunnableConfig
from langgraph.runtime import Runtime
from langgraph._internal._constants import CONFIG_KEY_RUNTIME

@tool
def fake_tool():
    """A tool that should not be called."""
    raise ValueError("This tool should not be called.")

tool_call = ToolCall(name="fake_tool", args={},  id="tool_call_001")
messages = [AIMessage(content="", tool_calls=[tool_call])]
config ={
    "configurable": {
        CONFIG_KEY_RUNTIME: Runtime(),
    }
}

def invoke(handle_tool_errors: bool
        | str
        | Callable[..., str]
        | type[Exception]
        | tuple[type[Exception], ...]) -> str:
    tool_node = ToolNode(tools=[fake_tool], handle_tool_errors=handle_tool_errors)
    print(tool_node.invoke(messages, config=config)[0].content)

print("=== handle_tool_errors=True ===")
invoke(handle_tool_errors=True) 

print("\n=== handle_tool_errors=custom message ===")
invoke(handle_tool_errors="Error happened while calling tool.")  

print("\n=== handle_tool_errors=handler function ===")
invoke(handle_tool_errors=lambda exc: f"Error happened while calling tool: {str(exc)}")  

输出:

Error: ValueError('This tool should not be called.')
 Please fix your mistakes.

=== handle_tool_errors=custom message ===
Error happened while calling tool.

=== handle_tool_errors=handler function ===
Error happened while calling tool: This tool should not be called.
【ARM AMBA5 CHI 2 -- CHI 链路层详细介绍 】 目前有六个版本的 CHI 协议,CHI-A 是 CHI 协议的第一个版本。它提供了一个传输层,具有减少拥塞的功能。新通道、CHI 术语和组件命名的定义;请求、监听过滤器和缓存状态转换的示例;事务排序、独占访问和分布式虚拟内存(DVM)操作的规则;CHI-B 问题扩展了 CHI-A,但不能直接向后兼容 CHI-A。更大的物理地址宽度;原子事务;DVM 的 VMID 扩展;通道字段、事务结构和可靠性、可用性和可维护性(RAS)特性的描述; 阅读详情

相关推荐

手把手教你理解ARM SoC中的PPU:电源管理IP的配置与实战避坑指南

本文深入解析ARM SoC中的PPU(Power Policy Unit)电源管理IP,提供详细的配置指南与实战避坑技巧。从PPU基础架构、寄存器配置到与PCSM的协同工作,全面介绍如何优化电源管理策略,解决常见配置误区,并分享高级调试与性能优化方法,助力工程师提升低功耗SoC设计效率。

weixin_28693447的博客 478

[ToolNode在LangGraph中的运用-03]ToolNode的工具如何执行?

从前面提供的实例可以看出,当我们传入携带`ToolCall`的`AIMessage`列表作为参数执行`ToolNode`后,它会返回一个`ToolMessage`的列表。其实返回类型不仅于输入参数的类型有关,还与工具函数的返回类型有关。那么`ToolNode`如何决定其返回类型,包含其中的工具又是如何执行的呢?

JaydenAI的博客 673

PayPal支付集成完全指南:开源库实践

本文介绍了如何从零开始集成PayPal支付功能。首先概述了PayPal提供的多语言SDK和开源库,包括PHP、Python、Node.js等版本。然后详细说明了准备工作,包括注册开发者账号、获取API凭证和使用沙盒环境测试。文章以Node.js为例,演示了安装SDK、基础配置和实现支付流程的完整代码,包括创建订单和捕获支付两个核心功能。最后提及了订阅支付等高级功能,为开发者提供了完整的支付集成方案。通过清晰的代码示例和步骤说明,帮助开发者快速实现PayPal支付功能集成。

2501_93501927的博客 1257

【LangGraph实战】《LangGraph实战》_53.[第3章 状态图结构] ToolNode全解:LangGraph的工具调用中心

LangGraph核心技术解析:ToolNode深度剖析与LLM工具调用实践 本文将深入探讨LangGraph中ToolNode的核心机制,帮助开发者构建健壮的生产级LLM应用。主要内容包括: ToolNode的本质定位:作为LangGraph的"工具调用中心",ToolNode实现了从LLM意图到物理世界执行的全生命周期管理,包括解析指令、并发调度工具函数和回写执行结果。 构造与初始化:详细解析工具列表配置、状态映射机制和错误处理参数,避免常见的状态键不匹配和异常处理缺失问题。 执行机制:剖析单工具调用与

429

MIKE 21 教程 1.5 基于卫星影像绘制河道边界

上一节讲解了如何输入高程数据与绘制网格本节讲解如何根据卫星影像绘制河道边界。在实际项目中,经常遇到河道图纸缺少的情况,此时可以根据卫星影像进行勾勒绘制。常用的地理信息软件均可完成此功能,这里使用大家较为熟悉的ArcGis进行教学(本文默认大家有基础ArcGis操作知识,如有不懂可留言)。

Hello Word 3282

电气论文实现:前推回代在IEEE33节点中的潮流计算实现(matlab,python)

本文首先介绍了前推回代的原理。然后基于IEEE33节点算例,分别用python和matlab运用前推回代进行了潮流计算。

总裁余(余登武)博客 8607

《LangGraph智能体设计模式与多智能体开发》深度研修心得:从“对话框”到“自动化工厂”的范式转移

本书通过12章内容系统阐述了LangGraph框架在AI应用开发中的核心价值,重点从四个维度展开:1)状态机思维,将传统线性Prompt升级为有状态图架构;2)记忆与上下文工程,解决AI遗忘与污染问题;3)React模式进阶,实现精准闭环控制;4)多智能体协作,采用Supervisor主管模式提升系统成功率。该书不仅教授框架使用,更重塑开发思维,强调模块化、结构化输出和数据契约的重要性,为构建稳健的AI系统提供了方法论指导。

feixuehuolong的博客 273

[VCS]门级仿真及SDF反标

摘要:门级仿真在STA工具普及后仍是验证的重要环节,主要用于检查SDC完备性、异步电路时序、网表完整性及为功耗分析提供波形。门级仿真分为不带时序反标的前仿和带SDF反标的后仿,后仿需加载SDF文件提供精确延时信息。VCS提供多种仿真选项控制时序检查行为,SDF反标可通过命令或系统函数实现。后仿需特别注意关闭notimingcheck选项以确保时序检查有效性,并建议检查SDF反标报告和波形验证延时一致性。

元直的博客 9787

langchain AI应用框架研究【核心概念-篇一】

langchain新版本1.1.0 出来了,老板了的api看了一些,api写法看起来有些凌乱,特别是那个构造chain的地方,先后顺序感觉不好记忆,还是SpringAI相对好好记忆一些。不知到1.x版本的会好使一些不。我们先来看看新版的langchain、langgraph、deepagents。LangChain 是一个用于开发由大语言模型(LLM)驱动的应用程序的开源框架。它的核心目标是提供一个统一的抽象层,让开发者可以方便地组合各种组件,而无需关心底层与不同模型API对接的细节。

yanghuashuiyue的专栏 597

YOLO项目部署:从Python脚本到Docker容器的企业级交付全流程

YOLO模型企业级Docker部署方案 核心挑战 环境一致性:解决"本地能跑,服务器崩掉"问题 可扩展性:支持单实例到多实例扩展 稳定性:确保99.9%可用性 部署流程 项目改造:模块化代码,分离配置 Docker镜像:支持CPU/GPU环境 API服务:封装推理逻辑 监控告警:实现自动化运维 技术方案 需求 解决方案 环境一致性 Docker镜像+依赖冻结 可扩展性 K8s+负载均衡 稳定性 健康检查+自动重启 项目结构 模块化代码组织 外部化配置管理 精确依赖版本控制 摘要重点:通过

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 1229

21、空间数据处理中的克里金方法及应用

本文介绍了空间数据处理中的克里金方法,特别是带外部漂移的克里金法(KED)及其在遥感数据融合和支持变化问题中的应用。讨论了克里金方法的基本原理、优缺点以及在不同空间分辨率遥感图像融合中的具体操作流程。同时,还涉及了地统计数据融合和支持变化问题的解决方案,包括块克里金法、面到点克里金法和多边形克里金法等。

gold的博客 86

AI Agent企业落地实战:从单体架构到多智能体协同的完整指南

在企业级AI应用中,智能体(Agent)的价值不在于“会聊天”,而在于“会干活”。从传统的RAG知识检索到复杂的跨系统操作,AI Agent通过动态规划与环境交互,正在重塑业务流程自动化的边界。本文从智能体架构的核心取舍出发,探讨单体与多智能体(Multi-Agent)的设计差异,深入解析Function Calling、MCP协议等工具调用机制,并结合LangGraph框架给出生产级实现方案。面对模型幻觉、工具误调用、安全策略拦截等工程难题,文章提供了基于实战的调优策略与参数配置经验,帮助企业将AI能力从

weixin_34160277的博客 300

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

Python+Agent入门笔记(二):列表推导式、模块和异常,这些写 Agent 前得会

接上一篇《Python+Agent入门笔记(一)》。基础语法跑通之后,这篇聊几块日常写脚本绕不开的东西。还是按 JS/Go 开发者的视角写,尽量记录我实际卡过的地方。

qq_41241612的博客 425

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

微调数据集构建:清洗、标注、格式转换完整流程

摘要解决微调数据直接抓取导致的噪声污染、模式崩溃与Token错位问题。适用于需为大模型SFT阶段构建高质量训练集的数据与算法工程师。读完可获得包含启发式清洗、MinHash去重及ChatML格式对齐的完整可运行数据流水线。关键词数据清洗、MinHash去重、数据标注、ChatML、SFT数据集、数据配比背景与问题大模型微调效果的上限由数据质量决定,直接使用未经处理的原始语料极易引发模型复读、幻觉与指令遵循能力退化。

2402_85729970的博客 468

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

详解Python条件语句

在 Python 中,if...else 是一种条件语句,可以根据给定的条件执行不同的操作。这个语句通常用于控制程序的流程。

2601_96798737的博客 204

Python3 协程:异步编程的核心机制与实战

协程是解决I/O密集型任务等待瓶颈的高效机制,相比线程更轻量、无锁开销。从yield到async/await的演进,使异步编程语义清晰、易于理解。通过async/await可实现并发执行多个任务(如gather、create_task),显著提升网络请求、爬虫等场景性能。关键在于使用异步库(如aiohttp)替代同步库,避免阻塞事件循环。合理控制超时与并发数,才能发挥协程最大优势。

Lxn2zh的博客 157

python如何控制浏览器

Python 可以通过 Selenium、Playwright 等工具实现浏览器自动化,从打开网页到执行复杂交互操作均可脚本化完成。Selenium生态成熟

2601_96797912的博客 73

torch.compile 差异仅 1e-6,边界样本预测为何翻转

torch.compile 加速后输出与 eager 不一致,部分预测翻转。逐层对比与 graph break 分析定位到动态分支翻转根因。容差回归仅适用于无分支翻转场景,零容忍场景应禁用编译。

昨日种种,皆成今我。今日种种,皆成新我。 157

二进制木马免杀-Python 分离免杀与 Web 挂马

二进制木马免杀-Python 分离免杀与 Web 挂马

2503_94637398的博客 262

Python 100讲课程导航:第08篇《Python input 与类型转换:构建可靠的交互程序》

本文详解Python中input()的返回类型为字符串,强调必须通过int()、float()等函数进行类型转换。重点讲解了输入验证的重要性,结合try-except处理ValueError等异常,实现可靠的交互程序。通过购物预算计算器实战,演示如何防范非法输入、处理边界值,并推荐使用Decimal避免浮点精度问题,提升程序健壮性。

铭渊老黄 130

【Python】国家中小学智慧教育平台电子教材批量下载与 PDF 合成实战

成果:两本语文电子课本(一年级上册 118 页 / 23MB、三年级上册 130 页 / 30.3MB),合计 248 页、约 54MB,单本下载加合成 40~60 秒,PDF 页数校验全部通过。经验教训SPA 站点的数据接口优先从 bundle 里挖,比抓页面稳得多;资源链接要按 private/public 分组看待,只取平台公开部分;页数、目录名这类“可推断”的信息,永远用实际响应验证一次;受限环境里“能不能跑浏览器”要在方案定稿前确认。后续计划:这版 PDF 是图片版,没有文字层;

木头大左的博客 114

魔塔社区微调训练Qwen2-1.5B-Instruct模型并烧录到‌RK3568开发板教程

本文基于Ubuntu 22.04 + CUDA 13.0.3环境,使用LLaMA-Factory框架对Qwen2-1.5B-Instruct模型进行LoRA微调,训练儿童故事生成专用模型。通过配置qwen模板、制作alpaca格式数据集、执行sft训练并合并LoRA权重,最终将模型转换为GGUF格式,并量化为Q4_K_M以适配RK3568嵌入式平台。全程支持国产镜像加速下载与显存优化,实现高效轻量化部署。

浅清秋慕夏的博客 182

【回眸】DeepSeek V4.1 Flash 快速上手与实战

本文介绍如何高效本地部署开源大模型,通过现代化工具链实现分钟级环境搭建。聚焦显存优化、一键安装、代码调用与多轮对话实现,解决“显存不足”“连接超时”等痛点,提供参数调优与结构化输出技巧,助力开发者在离线场景下快速构建稳定、高效的本地推理系统。

啤酒鸭的物联网开发记录博客 193

智叟云集-手机证据抓取系统的工程结构与应用入口

本文系统阐述手机证据抓取系统的工程结构与应用入口,涵盖四大层面:工程目录划分(Android、Python服务、Web页面、测试与构建工具);Android组件通过Manifest声明可启动页面,主界面由MainActivity管理;Python服务通过run.py解析参数并启动FastAPI应用,支持远程访问;浏览器页面采用HTML/CSS/JS分离架构,静态资源通过路径关联,实现动态交互。各模块分工明确,协同构建可运行的采集系统。

2301_77178996的博客 228

利用python中matplotlib.pyplot库绘制图

plt.plot(x,y) # x,y是每个点的坐标,plot()把每个点按顺序画折线。plt.subplots(2,2)表示绘制2*2的子图布局,axs[0,0]表示在第0行第0列绘制子图...coordinates_i[:,0]表示获取每一行(每一个spot)的第0列(x坐标)coordinates_i[:,1]表示获取每一行(每一个spot)的第0列(y坐标)plot.subplot(2,2,1) 表示子图布局是2*2的,开始绘制第一个。也可以导入数据绘制。可以设置color属性,设置颜色。

qq_46147236的博客 98

【随笔】KV缓存量化:省下的显存,要付出多少数值误差

用可运行量化器解释位宽、比例信息、分组和离群值的关系,核算KV缓存实际载荷,并给出Transformers配置与容量、延迟、质量验收方法。

code2cat的博客 442
上一篇: [ToolNode在LangGraph中的运用-01]LangChain和LangGraph两种编程模式的同一性
下一篇: [ToolNode在LangGraph中的运用-03]ToolNode的工具如何执行?
JaydenAI
博客等级 码龄1年 241粉丝 319原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值