news 2026/10/18 1:45:46

重试与降级:为什么兜底从来没被触发过

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
重试与降级:为什么兜底从来没被触发过

版权与内容来源声明
本文为原创整理。文中涉及官方文档、开源仓库、论文与公开报道的内容,均在附表 A 中标注来源;引用官方原文保持原样,不作改写。文中命令、版本号与界面截图以本文成文时的实测/核验结果为准,标注「待验证」的部分请以你本地环境实际输出为判断依据。本文不推荐任何不合规的软件获取方式,也不对任何收益结果作承诺。转载请注明出处。

第 1 章 为什么你的兜底一次都没被触发过

给一条链挂上兜底分支,上线后一个月,兜底那行日志一次都没出现过。有人因此觉得「兜底白写了」,也有人反过来发现:真出事那天,兜底照样没触发。这两种情况指向的是同一件事——你没弄清兜底到底在什么条件下才被调用。

1.1 重试、降级、人工:三层防线不是一件事

先把三个词定清楚,后面才不会互相串。

  • 重试(英文 retry):同一个步骤原样再执行一次。
  • 降级(也叫兜底,英文 fallback):「重试也救不回来」之后,换一条备选路径。
  • 人工:连兜底给出的结果也不可信时,才轮到人。

三层的分工和触发条件完全不同:

层解决什么问题什么时候起作用本文对应的实测位置
重试同一动作的偶发失败(网络抖动、限流)上一次抛异常,且异常类型在重试名单里第 2 章:不传参默认调 3 次
降级(兜底)主路径整体不可用重试次数耗尽后仍然抛异常第 3 章:只有抛异常才轮到它
人工兜底给出的结果也不可信兜底命中后仍需要人复核第 7 章:让兜底「响」一次

把这三层混成一句「我加了容错」,就是你后来判断不了兜底为什么不触发的根源。

1.2 兜底没触发,只可能是三种情况

  1. 主链真的没失败。这是绝大多数情况。第 3 章的实测会给出一个很干脆的数字:主链成功时,兜底调用次数是0。
  2. 失败了,但异常不在名单里。你写exceptions_to_handle=(ValueError,),而实际抛出来的是别的类型,异常会直接冒泡上去,兜底一次都不会执行。
  3. 你以为失败了,程序认为成功。主链「正常返回」None、空串、空字典,在程序眼里是一次成功,兜底不会接住。

第 3 种最贵。接口返回 200、内容是空的,这种假成功既没有异常、也不会有重试,你只会在下游某一步发现结果不对,然后回头怀疑模型。

1.3 本文的实测口径

下面所有行为结论都来自本机实测,不是转述别人的经验:

  • 环境:Python 3.13.14 + langchain-core 1.6.6,实测日期 2026-10-15;
  • 脚本:work/2026-10-15_重试降级实测.py,配套日志work/2026-10-15_重试降级实测.log;
  • 口径:只依赖 langchain-core,不联网、不调用任何模型接口,用一个计数器记录每一次真实调用,所以结论可复现,不依赖你的密钥和额度;
  • 交叉核验:方法语义另与官方 reference 对照(reference.langchain.com,本机可直连,截至 2026-10-15),参数默认值与本机实测一致。

下文凡写「实测」,指的就是上面这份脚本与日志里的原样输出;本文按这些结论给出的判据与写法属于归纳,会在附表 A 里单独标出。

第 2 章 重试这一层:不传参就是 3 次

2.1 「重试 3 次」到底是几次调用

先把这个最容易算错的地方钉死。下面这段是最小复现,它只做一件事:数一数.with_retry()不传任何参数时,函数体真的被执行了几次。

"""数一数 .with_retry() 不传参时,真实调用了几次。"""fromlangchain_core.runnablesimportRunnableLambda counter={"n":0}defflaky(x):counter["n"]+=1raiseRuntimeError(f"第{counter['n']}次调用失败")chain=RunnableLambda(flaky).with_retry()# 不传任何参数try:chain.invoke("q")exceptExceptionase:print("抛出:",type(e).__name__)print("真实调用次数 =",counter["n"])

本机实测输出是抛出: RuntimeError与真实调用次数 = 3。

结论只有一句:stop_after_attempt=3是「尝试次数上限」,不是「额外重试 3 次」。你如果按 1 + 3 = 4 次去算超时预算,就少算了一次调用。

2.2 四个入参,先看哪个

with_retry的签名不长,但有四个入参会直接改变你的线上行为。

参数默认值(官方 reference,截至 2026-10-15)本机实测你该做什么
stop_after_attempt3不传参真实调用 3 次;写 1 则只调 1 次显式写出来,别吃默认值
wait_exponential_jitterTrue3 次尝试总耗时 3.55 s结合超时预算决定留不留
retry_if_exception_type(Exception,)未单独实测默认会把编程错误一起重试,见第 6 章
exponential_jitter_paramsNone未单独实测要收紧退避时改这里(initial / max / exp_base / jitter)

第二行值得多说一句:等待时间是真实存在的开销,不是装饰。默认情况下它几乎一定大于你单次调用的耗时。

2.3 退避是真在等:3.55 秒 对 0.001 秒

把上面那个开关关掉,再量一次,差异就非常直观。

"""量一遍退避耗时:默认 对 关掉指数退避。"""importtimefromlangchain_core.runnablesimportRunnableLambdadefflaky(x):raiseRuntimeError("失败")forkwargsin[{"stop_after_attempt":3},{"stop_after_attempt":3,"wait_exponential_jitter":False}]:chain=RunnableLambda(flaky).with_retry(**kwargs)t0=time.time()try:chain.invoke("q")exceptException:passprint(kwargs.get("wait_exponential_jitter",True),"%.3fs"%(time.time()-t0))

本机实测:3 次尝试总耗时 = 3.55s(含指数退避 + 抖动);而关掉指数退避之后是总耗时 = 0.001s。

指数退避的意思是:第一次失败后等一小会儿再试,第二次失败等得更久,等待时间按倍数增长;抖动(jitter)是在这个等待时间上加一点随机量,避免所有请求在同一时刻一起重试。这两件事共同构成了那 3.55 秒。

判据可以直接写成一条算式:一次失败调用的最坏耗时 ≈ 单次超时 × 尝试次数 + 退避总时长。如果你的接口本身在 95 分位就要几百毫秒,默认参数下的一次失败会挂住 3 秒以上,这笔账要先放进你上游的超时预算里再决定参数。

还有一个方向相反的坑:重试会把你的请求量成倍放大到上游。一次失败变成 3 次调用,如果上游已经因为过载而变慢,重试只会让它更慢——这就是俗称的重试风暴。两条纪律:只对幂等操作开重试(幂等指同一个请求重复执行多次,结果和只执行一次相同,比如查询;而「下单」「扣款」这类改动状态的操作不是);以及在重试之外,给上游留一条限流或备用通道,而不是让 3 次尝试都砸向同一个地址。

第 3 章 降级这一层:只有抛异常才轮到它

3.1 顺序:先耗尽重试,再按顺序试兜底,落第一个

"""重试耗尽后才轮到兜底;多个兜底落第一个;主链成功时兜底 0 次。"""fromlangchain_core.runnablesimportRunnableLambda counter={"n":0}defflaky(x):counter["n"]+=1raiseRuntimeError("失败")chain=RunnableLambda(flaky).with_retry(stop_after_attempt=2).with_fallbacks([RunnableLambda(lambdax:{"from":"fallback","got":x}),RunnableLambda(lambdax:{"from":"fallback2","got":x}),])print("重试耗尽 ->",chain.invoke("q"),"| 主链调用次数 =",counter["n"])c2={"n":0}defshould_not_run(x):c2["n"]+=1raiseAssertionError("兜底不应该被执行")chain2=RunnableLambda(lambdax:{"from":"main","got":x}).with_fallbacks([RunnableLambda(should_not_run)])print("主链成功 ->",chain2.invoke("q"),"| 兜底调用次数 =",c2["n"])

本机实测的两行输出是:重试耗尽 -> {'from': 'fallback', 'got': 'q'} | 主链调用次数 = 2,以及主链成功 -> {'from': 'main', 'got': 'q'} | 兜底调用次数 = 0。

两句话读完:重试 2 次都失败后才轮到兜底,两个兜底里落的是第一个(第一个成功就不再试第二个,第二个兜底只有第一个也抛异常时才轮得到);主链成功时,兜底的调用次数是 0。所以「兜底没触发」有一半以上的概率是它本来就不该触发。官方 reference 对触发条件的表述也是这个意思:新的 Runnable 会先试原始的 Runnable,失败后按顺序尝试每一个兜底(截至 2026-10-15)。

3.2 十组实测行为总表

下面这张表是全文的主干。左列是你写的写法,中列是本机实测结果,右列是据此该做的事。建议把它存成你自己的排障清单。

场景实测结果该怎么做
.with_retry()不传参抛 RuntimeError,真实调用3次按「3 次调用」算预算,别按「1 + 3」
stop_after_attempt=1真实调用1次不想重试就显式写 1,别靠默认值
重试 2 次 + 两个兜底落第一个兜底,主链调用 2 次兜底按「从好到差」排序,越可信的越靠前
主链成功兜底调用0次别用兜底日志判断链路是否健康
主链返回None/ 空串 / 空字典原样返回None、''、{},兜底不触发空结果要自己判,见第 4 章
整条序列挂兜底兜底收到原始输入{'order_id': 'A-1'}兜底里不要假设输入已被处理过
只给失败的那一步挂兜底后续步骤继续跑,收到兜底的返回值看后续步骤能不能吃兜底输出,见第 5 章
exceptions_to_handle=(ValueError,)遇到RuntimeError异常直接冒泡,兜底不触发名单要么写全,要么先记清会抛什么
exception_key+ 非字典输入抛 ValueError,要求输入是字典用exception_key就全链传字典
默认退避(3 次尝试)总耗时3.55 s;关掉退避0.001 s先量一遍再定额度

最后一行是唯一一条「不是关于触发、而是关于代价」的结论,但它决定了前面九条在你系统里是否可接受。

第 4 章 最容易踩的一条:把「空结果」当成失败

4.1 程序眼里的失败只有一种

在 Runnable 序列里,失败的定义是「抛出了异常」,没有第二种。返回值是什么,框架不评价。这就解释了第 3 章那条看着奇怪的输出:主链返回None、空串、空字典时,兜底调用次数都是 0——因为从框架的角度看,它们都是正常返回。

4.2 「假成功」为什么不会触发兜底

"""空结果不是失败:要么自己判空,要么把它转成异常。"""fromlangchain_core.runnablesimportRunnableLambda fb=RunnableLambda(lambdax:{"from":"fallback","got":x})# 主链「正常返回」空串:兜底不会接住print("空串 ->",repr(RunnableLambda(lambdax:"").with_fallbacks([fb]).invoke("q")))# 判据:把空结果显式转成异常,兜底才有机会被调用EMPTY=(None,"",{},[])defguard(fn):def_run(x):out=fn(x)ifoutinEMPTY:raiseValueError("空结果")returnoutreturnRunnableLambda(_run)print("加护栏 ->",guard(lambdax:"").with_fallbacks([fb]).invoke("q"))

实测行为:三种空值原样返回None、''、{},兜底一律不触发;加上上面这层护栏(把空结果显式转成异常的小包装)之后,同一个返回值才会落到兜底上(护栏写法的输出为本机复跑核验,环境同上)。

这一段就是「别只兜异常,还要兜空结果」的完整含义:你要先把「空」定义成失败,框架才可能把它当失败处理。

线上常见的空结果有三种来源,判空时最好都覆盖到:接口层——请求成功、返回体是空的(HTTP 200,但内容为空);模型层——模型正常返回,但文本内容没有或只有空白;解析层——结构化输出解析器返回了空列表或空字典,而不是抛错。这三种在日志里都长得像一次成功调用,只有你自己加的那层判断能拦住它们。

4.3 判空放在哪一层

判空位置能挡住什么代价与风险
主链出口(判空后转异常)假成功,兜底能接到要改主链,或加一层包装
兜底入口只能挡兜底自己返回空挡不住主链的假成功,兜底根本没被调用
最外层统一包装不改主链,集中定义「空」「空」的定义必须写死,见下

判据是「先把定义写下来,再决定放哪」:空字典算成功还是失败?0算不算空?空列表算不算空?这三问答不出来,判空放哪一层都会出错。答得出来,就把这段判断放在最外层统一包装里。

第 5 章 兜底放整链还是放单步

5.1 整链兜底:收到的是原始输入

5.2 单步兜底:后续步骤会继续跑

这两条是同一段代码的两个变体,一起看差异最清楚。

"""整链挂兜底 对 只给失败那一步挂兜底。"""fromlangchain_core.runnablesimportRunnableLambdadefboom(x):raiseRuntimeError("中间步骤炸了")defstep2(x):return{"stage":"step2_ran","input_was":x}fb=RunnableLambda(lambdax:{"from":"fallback","got":x})# A. 整条序列挂兜底whole=(RunnableLambda(boom)|RunnableLambda(step2)).with_fallbacks([fb])print("整链 ->",whole.invoke({"order_id":"A-1"}))# B. 只给失败的那一步挂兜底step1=RunnableLambda(boom).with_fallbacks([RunnableLambda(lambdax:{"from":"step1_fallback","got":x})])print("单步 ->",(step1|RunnableLambda(step2)).invoke({"order_id":"A-1"}))

本机实测的两行输出是整链 -> {'from': 'fallback', 'got': {'order_id': 'A-1'}},以及单步 -> {'stage': 'step2_ran', 'input_was': {'from': 'step1_fallback', 'got': {'order_id': 'A-1'}}}。

第一行里兜底拿到的是{'order_id': 'A-1'},也就是原始输入,不是中间步骤处理过的结果。第二行里,后面的step2照常执行,它拿到的是兜底的返回值。这就是「兜底放错位置」最常见的两种翻车方式:兜底代码若直接假设「输入已经被前面清洗过」,整链场景下会立刻报错;而单步场景下你要额外保证兜底返回的形状能被后面的步骤吃下去。

5.3 该放整链还是单步

维度放整条链只放失败的那一步
兜底收到的输入原始输入该步骤的输入(原始输入或上一步输出)
后面的步骤全部跳过继续执行,拿到兜底返回值
适用情形后面步骤吃不下兜底输出;或需要整段重来保证一致后面步骤能吃兜底输出;只补其中一段
典型例子多步生成里的关键步骤失败,且不完整就不发出检索失败就给一段空上下文,继续往下走
主要风险兜底里假设输入已处理过,直接报错兜底输出形状不对,后面步骤静默出错
日志能看出什么只知道整段降级了能看出是哪一步降级的

5.4 一句话判据

后续步骤能不能吃兜底输出——能吃,就把兜底挂在失败的那一步上;不能吃,或者兜底输出与主链中间结果的形状对不上,就挂在整条链上,让后面的步骤整体跳过。另加一条硬要求:兜底输出的形状必须与它替代的那一步完全一致,否则你只是把报错换成了更难查的错值。

大模型学习路线图:第 5 章讲的是兜底该挂在哪一层,如果你希望把「从环境搭建到能自己动手做 Agent」的路径系统过一遍,这份路线图按阶段标了每一步该学什么、哪些可以先跳过。放在资料包里,扫码即可获取:

第 6 章 兜底没触发,还有两个原因:异常名单与输入形状

6.1 exceptions_to_handle:名单之外一律冒泡

"""兜底不触发的两个原因:异常类型不在名单里、输入不是字典。"""fromlangchain_core.runnablesimportRunnableLambdadefraise_runtime(x):raiseRuntimeError("运行时错误")# 名单里只有 ValueError,实际抛的是 RuntimeErrorchain=RunnableLambda(raise_runtime).with_fallbacks([RunnableLambda(lambdax:{"from":"fallback"})],exceptions_to_handle=(ValueError,),)try:chain.invoke("q")exceptExceptionase:print("名单外 ->",type(e).__name__)# exception_key 要求输入是字典chain2=RunnableLambda(raise_runtime).with_fallbacks([RunnableLambda(lambdax:{"saw":x})],exception_key="err")try:chain2.invoke("q")# 传进去的是字符串exceptExceptionase:print("非字典输入 ->",type(e).__name__)# 传字典时,兜底能拿到异常对象defcatch(x):return{"fallback_saw_type":type(x.get("err")).__name__}chain3=RunnableLambda(raise_runtime).with_fallbacks([RunnableLambda(catch)],exception_key="err")print("字典输入 ->",chain3.invoke({"q":"q"}))

本机实测的三行输出是名单外 -> RuntimeError、非字典输入 -> ValueError、字典输入 -> {'fallback_saw_type': 'RuntimeError'}。

三行读出来的结论:exceptions_to_handle只兜它名单里的类型,名单外的一律直接冒泡;exception_key想要把异常对象交给兜底,前提是输入本身是字典,否则连兜底都还没轮到就先报错了(本机实测到的提示原文片段是input must be a dictionary);输入是字典时,兜底里能从该键上取到异常对象本身,而不再是「出错了」这三个字。

官方 reference 对这两条有明确表述:exceptions_to_handle默认(Exception,),exception_key若指定字符串,被处理的异常会作为输入的一部分在指定键下传给兜底,「基础 Runnable 及其兜底必须接受字典作为输入」(截至 2026-10-15)。

6.2 例外名单怎么定

默认(Exception,)意味着什么都会重试、什么都会被兜底接住——包括你代码里的拼写错误。这是把 bug 藏起来的最快方式。名单要显式写,并且逐个想清楚:网络超时该进(换个路由或再试一次确实可能成功)、限流该进(退避后重试,或切到备用通道)、数据解析失败视情况进(换个模型可能成功,但每一次都要留记录),而代码错误(键名拼错、类型不对)和配置问题(鉴权失败)都不该进——前者被兜住只会让 bug 藏得更深,后者应该先把配置修对。

6.3 名单与字典,一次定死

判据很简单:名单里只放「换一条路确实可能成功」的异常,其余一律让它冒泡;一旦用了exception_key,就把整条链的输入统一成字典,别让某一步偷偷改成字符串。

第 7 章 上线前量一遍:次数、退避、空结果

7.1 先量退避,再定次数

第 2 章那两个数字值得单独拎出来:默认退避下 3 次尝试总耗时3.55 秒,关掉之后0.001 秒。它们之间差三个数量级,而你的下游只给了你一个超时上限。顺序应该是:先在你的机器上按第 2 章那段计时代码量一次,再回头决定stop_after_attempt和抖动参数,不要先写死次数再祈祷不超时。

7.2 上线前的打勾清单

检查项打勾条件没打勾时怎么办
重试次数显式写过stop_after_attempt,且知道它等于几次调用补上参数,重跑计数器确认
退避预算在本机量过一次真实总耗时跑第 2 章那段计时
例外名单逐个列出预期异常,不用默认(Exception,)按 6.2 的清单列一遍
空结果主链出口有判空,或明确写了「空也算成功」加第 4 章的护栏包装
兜底位置能说清后续步骤吃不吃兜底输出按 5.4 的判据定
兜底输出形状与它替代的那一步完全一致在兜底出口加一条断言
兜底留痕触发时写日志或计数器,并带来源标记见 7.3

7.3 最后一条:让兜底「响」一次

第 3 章那张表里最容易被忽略的是「主链成功时兜底调用 0 次」。它有个直接后果:如果你不给兜底留痕,「没触发」和「触发了但同样返回了一个空结果」在你的日志里长得一模一样。

两个具体做法:兜底命中时写一条带来源标记的日志(例如返回值里多带一个来源字段,日志里打出来);同时给兜底挂一个计数器,上线后第一周每天看一眼它是 0 还是非 0。0 不一定是好事,非 0 也不一定是坏事——但你必须知道。

这个计数器怎么读,其实有两种信号:长期为 0,说明你的重试和例外名单可能把问题都消化在上一层了,这时该去看重试次数(如果重试次数也在涨,说明主路径本身不稳,只是被重试掩盖了);某天突然升高,说明上游变了(超时变多、限流变严、接口改字段),这时候兜底给出的结果还准不准,需要你人工抽看几条再决定要不要让它继续兜。

《LangChain + LangGraph + MCP 智能体开发实战》视频课:第 7 章讲的是上线前的检查与留痕,如果你准备把这些容错写法真正接到一条能跑的链上,这套课从私有化部署一路讲到 Agent 全流程,可以当作动手时的参考。放在资料包里,扫码即可获取:

附表 A:本文引用事实与出处对照表

事实出处(文档名 + 发布方 + 链接)本文位置
环境版本:langchain-core 1.6.6、Python 3.13.14,实测日期 2026-10-15本机实测(work/2026-10-15_重试降级实测.py);日志 work/2026-10-15_重试降级实测.log1.3
.with_retry()不传参真实调用 3 次后抛异常;stop_after_attempt=1只调 1 次本机实测(work/2026-10-15_重试降级实测.py);日志 work/2026-10-15_重试降级实测.log2.1、2.2
默认退避下 3 次尝试总耗时 3.55 s;wait_exponential_jitter=False时 0.001 s本机实测(work/2026-10-15_重试降级实测.py);日志 work/2026-10-15_重试降级实测.log2.3、7.1
重试耗尽后才轮到兜底;两个兜底时落到第一个(主链真实调用 2 次)本机实测(work/2026-10-15_重试降级实测.py);日志 work/2026-10-15_重试降级实测.log3.1
主链成功时兜底调用次数为 0本机实测(work/2026-10-15_重试降级实测.py);日志 work/2026-10-15_重试降级实测.log3.1、7.3
主链正常返回None、空串、空字典均不算失败,兜底不触发本机实测(work/2026-10-15_重试降级实测.py);日志 work/2026-10-15_重试降级实测.log3.2、4.1、4.2
整条序列挂兜底时,兜底收到原始输入{'order_id': 'A-1'}而非中间结果本机实测(work/2026-10-15_重试降级实测.py);日志 work/2026-10-15_重试降级实测.log5.1、5.2
只给失败的那一步挂兜底时,后续步骤继续执行并收到兜底返回值本机实测(work/2026-10-15_重试降级实测.py);日志 work/2026-10-15_重试降级实测.log5.2、5.3
exceptions_to_handle=(ValueError,)遇到RuntimeError时异常直接冒泡本机实测(work/2026-10-15_重试降级实测.py);日志 work/2026-10-15_重试降级实测.log6.1
exception_key要求输入为字典,非字典输入抛 ValueError;字典输入时兜底可拿到异常对象(实测为 RuntimeError)本机实测(work/2026-10-15_重试降级实测.py);日志 work/2026-10-15_重试降级实测.log6.1
with_retry签名与默认值:stop_after_attempt=3、wait_exponential_jitter=True、retry_if_exception_type=(Exception,)、exponential_jitter_params=None《with_retry》,LangChain Reference(reference.langchain.com,本机可直连,页面标注方法版本 v1.6.10),https://reference.langchain.com/python/langchain-core/runnables/base/Runnable/with_retry2.2
with_fallbacks签名与默认值:exceptions_to_handle=(Exception,)、exception_key=None;原始 Runnable 失败后按顺序尝试每个兜底;exception_key使用时基础 Runnable 与兜底都必须接受字典输入《with_fallbacks》,LangChain Reference(reference.langchain.com,本机可直连),https://reference.langchain.com/python/langchain-core/runnables/base/Runnable/with_fallbacks3.1、6.1
「护栏」包装(把空结果转成异常)与两处取舍表(判空位置、整链 对 单步)为本文归纳,非框架内置能力本机复跑核验(同一 langchain-core 1.6.6 / Python 3.13.14 环境);其输出与本文归纳的口径4.2、4.3、5.3、5.4

附表 B:术语速查表

术语一句话解释在本文哪里用到
重试(retry)同一个步骤原样再执行一次第 1、2 章
降级 / 兜底(fallback)重试救不回来之后,改走备选路径第 1、3、5 章
异常(exception)程序主动中断并向上抛出的错误信号,是框架唯一认可的失败形式第 3、4、6 章
假成功程序正常返回、但内容为空或不可用的结果第 1、4 章
指数退避失败后等待时间按倍数递增再重试第 2、7 章
抖动(jitter)在等待时间上加一点随机量,避免请求同时重试第 2 章
RunnableLangChain 里可被编排执行的最小单元,链和兜底都是 Runnable第 3、5 章
exceptions_to_handle兜底的例外名单,只兜名单内的类型第 6 章
exception_key把异常对象塞进输入的某个键传给兜底(要求输入是字典)第 6 章
护栏把「空结果」显式转成异常的一层包装第 4 章

写在最后:这篇用到的资料

写这篇文章时,把相关的官方文档和源码又翻了一遍,顺手也整理了几份配套的东西:

  • 大模型学习路线图:从零基础到能自己动手做 Agent,按阶段说明每一步该学什么、哪些可以先跳过
  • 《LangChain + LangGraph + MCP 智能体开发实战》视频课:7 个模块,从私有化部署、Embedding+RAG 到 MCP+Agent 全流程
  • AI 大模型知识库(在线可查):Agent Skills 从入门到落地、Claude Skills 完全指南等专题,按目录浏览即可
  • 640 套 AI 大模型行业报告 + 经典 PDF 书籍:看行业落地案例和别人怎么做的时候用得上
  • 大模型零基础到精通教学视频:跟着敲一遍,比只读文档快得多

资料是我自己整理的,放在下面这个码上,扫码即可获取:






添加时备注「AI」,优先通过。

资料按「先路线、再动手、最后查漏」的顺序整理好了,建议先看学习路线那一份,照着它挑一条适合自己当前基础的路径再往下看。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 19:12:35

Python函数进阶:闭包、装饰器与生成器详解

1. Python函数进阶概述在Python编程中,函数是最基础也是最重要的构建模块之一。当你已经掌握了函数的定义、参数传递和返回值等基础知识后,是时候深入探索函数更强大的特性了。函数进阶不仅能让你的代码更加简洁高效,还能解决许多复杂场景下的…

作者头像 李华
网站建设 2026/10/16 15:22:33

Shadow DOM + WebRTC + MQTT:零依赖去中心化实时协作实战

1. 三个看似无关的技术,为什么被塞进同一个项目第一次看到这个组合标题的时候,我的反应是:这三样东西怎么会凑到一起?Shadow DOM 是浏览器渲染层的东西,WebRTC 是实时通信协议栈,MQTT 是物联网领域的老牌消…

作者头像 李华
网站建设 2026/10/16 9:34:27

2023最新Vivado安装与License配置全指南

1. 项目概述FPGA开发环境搭建一直是初学者面临的第一道门槛。作为Xilinx旗下主流的FPGA开发工具,Vivado的安装配置过程直接影响后续开发效率。这个教程将基于2023年最新版Vivado,手把手带你完成从下载到配置的全流程,特别针对License获取这个…

作者头像 李华
网站建设 2026/10/16 13:15:30

AES-RSA混合加密在金融文件传输中的实践

1. 项目背景与核心需求最近在帮某金融机构设计内部文件交换系统时,遇到一个典型的安全传输需求:既要保证大文件传输效率,又要满足金融级数据安全要求。传统方案要么像纯RSA加密那样速度慢得让人抓狂,要么像单纯FTP传输那样安全性堪…

作者头像 李华
网站建设 2026/10/17 9:31:34

硬件测试入门:从仪器选型到纹波测量与用例设计避坑指南

简介:这是一份面向硬件测试初学者与测试工程师的PPT培训教材,系统讲解硬件测试的概念、目的、目标、意义与完整流程,覆盖测试前准备、测试种类与级别、可靠性测试、测试问题解决、效果评估、常见通信技术标准及测试规范制定,内容偏…

作者头像 李华
网站建设 2026/10/18 3:24:10

Matlab光谱转Lab/RGB/k-s值的标准化流程与避坑指南

简介:本资源是一套面向图像处理与色彩科学方向的MATLAB实践工具包,适用于高校学生、科研人员及工业领域从事颜色质量控制、光谱分析的技术人员。它聚焦于从原始光谱数据出发,系统实现k值计算、CIE三刺激值(XYZ)积分、C…

作者头像 李华