news 2026/4/16 15:06:07

小白也能懂:langGraph三大要素之Nodes与Edges,含完整邮件起草Bot实现(收藏级教程)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
小白也能懂:langGraph三大要素之Nodes与Edges,含完整邮件起草Bot实现(收藏级教程)

本文详细介绍了langGraph框架的Nodes和Edges两大核心要素。Nodes作为处理State的"加工站点",应遵循单一职责原则;Edges作为连接节点的"传送带",分为普通边和条件边两种类型。通过邮件起草Bot实例,展示了如何使用Pydantic结构化LLM输出,并将人类反馈融入Agent系统,帮助读者构建具有交互能力的AI应用,值得收藏学习。


  1. FOMO时代,应该学习什么?
    ==================

当我想要手搓自己的Agent,我开始研究最受欢迎的Agent开发框架之一langGraph。虽然说对于很多生活中的小项目,使用langGraph框架属于是**“杀鸡用牛刀”了,但是我发现了解一些这个知名开源框架的设计思路**,还是很有启发性——了解它们的过程就是向“最聪明的一批人”借鉴和学习的过程

在AI能力如此强大的今天,**“语法”真的不重要。**因为不管你怎么用力的学习,在几个月之后,你学会的知识都会“失效”新的框架、新的语言、新的能力层出不穷,他们在“社区”这个群体层面永无止境的进化着,在我眼里,早就已经超过了“个体”可以追赶的体量

那么为什么我还会在这里写一下我关于langGraph的学习经历呢?

也许是我还觉得在这种洪流之下,依然存在着一些“不变”吧。技术的进化就跟生物的进化很像,生态快速暴力穷举了很多个“变异形态”,但是再复杂的变异都是以最初的几种基础模块作为起点的。我想要了解的就是那些基础的模块。这些基本的了解,能够带来向上探索复杂形态的视野。

上一篇文章写了怎么理解langGraph里面的State,这篇文章继续讲解Graph剩下的两大要素Nodes和Edges。还会举一个“起草邮件Bot”的例子,来讲述怎么把人类的反馈也加入。

这篇文章会包含:

  • Nodes
  • Normal Edges & Conditional Edges
  • 利用Pydantic结构化LLM的输出
  • 怎么做一个有人类反馈的“邮件起草Bot”

本篇文章包含一些Python基础知识,需要理解类、实例、函数、列表、字典等概念,字数6k+,阅读时间比较长,感兴趣的朋友可以收藏或者转发~

  1. Nodes
    =========

Graph有3大基本要素:State、Nodes和Edges。

其中State我在上一篇文章讲过,它是一个贯穿全场的信息传递的媒介,如果把Graph看成一个大公司,那么State就是这个公司所有部门都可以读也可以写的**“共享记事本”**。因为有这些共享信息,公司所有的员工才能够有条不紊的互相配合。也可以理解为,整个Graph这个处理工厂,处理的产品就是这个State

详情请见:手搓 Agent 必备:langGraph概念入门,一层一层剥开State是什么?

那么Node是什么呢?用比喻来说,可以把Node想象成一个**“机械臂/加工站点”**,它接收State处理State把处理结果更新到State里面去

我有的时候会纠结,究竟什么时候应该设定一个Node。请教了Gemini老师,它是这样说的:

  • 当你要更新State的时候,你需要设定一个Node。
  • Node应该尽量去承担单一的职责,这就跟我们写代码的时候要去考虑可复用性可维护性一样,只有职责拆分比较明确,才能够不断用“基本模块”去构造“复杂模块”。
  • Node可以调用LLM,也可以不调用(Node本质是function嘛,千变万化不受限制)。
  • 如果要调用LLM,可以用**“单一人设”这样的原则来设置Node。也就是说,如果你在写提示词的时候发现,这个Node很精分,一会儿关注重点是A,一会儿关注重点是B,你就应该拆分Node了**。

在代码实现上,Node可以理解为是一个Function(实际上是Runnable)既然是Function就会有输入和输出。Node输入一般(包括但是不限于)State。那么输出是什么呢?

这里归功于langGraph这个底层框架的努力,让我们在写Node的时候可以只输出需要更新的**“增量部分”**,而不必把整个State重新再抄写一遍。

举一个例子,假如我的State是这样的:

在上篇文章里面解释过,Annotated里面的第二个参数,会被解析出来作为归并函数(reducer function),State字段的更新方式由这个归并函数决定

这里字段“message”和“count”都会被**“累加(add)”**。而“memberTier”则会被不断“覆盖”。

那么我的Node可以写成这样:

sayHello函数只输出了“message”和“count”两个key——langGraph底层就会找到State里面这两个“key”,把新的信息给**“累加上去”**。

假如我们的初始化State是下面这样:

在经过sayHello这个Node之后,“message”会增加一条,“count”也会+1。

**如果这个Node没有输出跟State重叠的key,那么它做了什么都不会被更新到State上面去。**比如有的时候我就是用一个Node占位一下print一个句子,State不会被改变。

在Graph中还有两个特殊的Node:就是标志着“开始”的START,和标志着“结束”的END

这里用大写的英文字母表示,因为这两个Node就是**“常量”**,不会有改变,也不用你自己去定义,你只需要import langgraph里面已经定义好的这两个“常量”就可以

无论你画的是多么简单的图,你都需要“规定”这两个Node。在实际上这两个Node什么其他影响也没有,就只是标记着“开始”和“结束”。

为什么会需要这两个Node?因为我们在运行一张图的时候(也就是调用了invoke函数),数据是有方向的、有顺序的。

不定义“开始”,我们无法定义“顺序”。在执行过程中,langGraph底部一直在用**“循环”**搜索下一个Node是什么,不定义“结束”,这个“循环”就没有结束的条件。

  1. Edges
    =========

那么什么又是Edges呢?它实际上就是一个**“调度字典”**。他就是把各个不同的“机械臂”链接起来的“传送带”。

在langGraph中有两种基础Edge,一种是Normal Edge,一种是Conditional Edge。它们在本质上都是一个**“字典”。**模拟一下大概是这样的:

我们都知道“字典”无非储存的是一组Key: Value对应关系。

**普通边(Normal Edges):**储存的是固定的映射。

逻辑:“如果A结束,永远去B。”

这个字典的Key是上一个Node的“名字”,Value是下一个Node的“名字”。

这里会使用**“名字”,是因为它存的是一个“字符串”,这个字符串是你显式的通过add_node函数**(由langGraph提供的)去定义的,而不是直接的function name

为什么要这样干呢?还是为了解耦,假如你突然想改这个Node了,只要改一个名字的映射就可以了,不用到处去改代码引用。

04. Conditional Edges

**条件边(Conditional Edges):**依据不同的条件有不同的映射。

逻辑:它接收一个函数(Router)的输出,并根据这个**“函数的输出”**在字典中查找下一站。

当你要在一个Graph里面添加条件边,它的语法是:

你需要传递:上一个Node的“名字”,一个用来判断条件的函数,和一个映射地图

  • 上一个Node的名字:是为了指出从哪里出发;遵循关注点分离的原则,这里用的是你显式定义的**“名字字符串”而不是function name**。
  • Router函数(路由函数):就是用来做判断的函数,它的作用是依据XXX条件,决定下一个Node是什么。我们用是否获得VIP服务来举个例子,需要判断“用户的身份”,如果用户是VIP,则走到VIP服务的Node,否则就走到普通服务的Node。

我们写一个“判断用户身份”的函数checkVIP:

注意这个Router函数返回的是一个**“字符串”,这个“字符串”往往代表着“意图”**,不是直接返回一个Node的function name。这也是我们需要在add_conditional_edges函数里面去传递path Map的原因。

  • path Map:按照标准的写法这也是一个“字典”,这个“字典”是把Router函数返回的**“意图字符串”给映射到对应的“Node名字”**上面,这样就由“Router函数”决定了“下一个Node”是什么。

不过在langGraph的官网上举的例子,这里传递的不是一个Dict而是一个List。

这是一种简写——把“意图字符串->Node名字”这一层映射给省略了。在langGraph框架里,会默默给你做成一个**“恒等映射(Key和Value相等)”**,然后路由到对应的节点里面去。

  1. Graph
    =========

在了解langGraph的三大基本元素:State、Nodes和Edges之后,把这几个组合在一起,就能够定义图(Graph)了。

定义图会分为以下几个步骤:

  • 把State Class传递进去:相当于告诉Graph这个大型工厂,你可以加工的产品长得什么样子,你可以读写的“共享记事本”长得什么样子;
  • 添加Node:相当于在Graph工厂把各种不同的“机械臂”给安装上去。在底层其实就是Graph默默把Node的“名字”和“函数”对应关系记录好,存起来。
  • 添加Edges:相当于把不同“机械臂”之间“传送带”给连接起来,这样才知道“下一步应该往哪里去”。知道哪里是开始,哪里是结束。

定义完图之后,需要把这个图compile一遍:

这样做的原因之一是在解析“字符串”,把他们还原为“真实的函数对象”,比如解析在上一篇文章里面讲过的“Annotated的第二个参数”。

接下来你就可以走入主程序逻辑了,也就是“调用你编译好的图”。这句的语法是:

注意要区分什么逻辑应该放在“主程序里面”什么逻辑应该放在“Graph里面”

最为简单的使用方法:定义一个初始的State,然后传递给你定义好的Graph,再打印出处理后的结果:

  1. 做一个帮忙起草邮件的Bot:
    ==================

在了解Graph相关的基本概念之后,我们可以看一个简单的例子,怎么创造一个帮我们“起草邮件的Bot”?这里就涉及:

  • 定义跟LLM交互的Node;
  • 把人类的反馈融入进去;

这个Graph非常简单,就只有1个自定义的Node,这个Node是用来跟LLM对话的。

第一步,我们来定义State:

思考一下如果要让LLM帮我们起草邮件,那么一定要有的字段是什么?

  • messages:用来传递消息,无论是来自人的消息还是来自AI的回答。
  • category:用来区分邮件的类别,这里就定义只能够从[Business,Notification,Casual]中间列举出一个。
  • draft:起草的邮件内容。
  • isConfirmed:表明用户的意图,如果用户确认这个邮件不需要修改了,这里就写True,否则就写False。默认值为False。这样可以形成一个不断依据用户建议修改的循环。

第二步,我们来定义LLM的输出结构:

定义了State之后,我们一定要LLM返回的内容跟我们需要的字段是相符合的,不然的话没有办法顺滑的更新State。这里就需要引入一个新的工具——Pydantic

Pydantic是一个基于Python类型提示(Type Hints)的数据验证和设置管理工具。在上篇文章中我们强调过,它跟TypedDict非常相似,但是它比TypedDict对于数据的约束力要更强。

它定义的是一个Python类(Class),但它具备特殊的“容器”属性。

  • 普通字典(Dict):就像一个大塑料袋。你往里装苹果、梨、甚至鞋子,它都不会管。取用时容易出错(比如想拿苹果却摸到了鞋子)。
  • Pydantic类(BaseModel):就像一个带格子的精密收纳盒。圆形的格子只能放整数(int),方形的格子只能放字符串(str)。如果你试图强行把方形物体塞进圆形格子,盒子会直接把你的手弹开并报警(抛出ValidationError)。

Pydantic通过定义模型(Models)来实现,具有下面的优势:

  • **运行时验证:**当数据进入模型时,Pydantic会自动检查类型。如果数据不合法,它会抛出清晰的错误。
  • **数据解析(Parsing):**它不只是验证,还会尝试强制转换类型(Coercion)。比如你要求int但传入了“123”,它会自动帮你转成数字。
  • 与IDE深度集成:模型是标准的Python类,因此在开发时可以享受完美的自动补全和类型检查
  • **JSON序列化:**可以轻松地将复杂的对象转换为字典或JSON字符串。

在AI工程中,Pydantic是连接“不可控的LLM对话”与“严谨的代码逻辑”之间的护城河。

总而言之,我们可以这样记忆:Pydantic用来定义“数据结构”,能够尽量去“强迫LLM”返回的内容符合要求。

我们需要LLM返回的数据结构是跟State对应的,这里我们定义的数据结构叫做ProposerOutput

这里注意最好要写description,这个description不仅是给人看的,还是给LLM看的,写明确的description能够让LLM更加明确的理解需要生成的内容

第三步,定义系统提示词:

我们需要一个系统提示词,来设定这个LLM的人设,由于系统提示词一般都会不断调整,我会把它定义在Node外面,方便集中微调

第四步,定义Node:

  • 人类的反馈:

首先我要做一个大逆不道的操作,——那就是把人类的反馈放在这个Node节点里面,使用input函数就可以形成一个“可交互”的节点。这是为了简化逻辑,实际生产中不会这么用!!!

用input函数承接“人类的输入”,然后使用langGraph的Message组件**“HumanMessage”**形成“标准化的消息”,加入到State的messages字段。

(后面还会用到SystemMessage和AIMessage。这些Message类,主要的作用还是**“包装”——就是把真实的内容,包装成AI API需要的格式。让用户可以不用关注解析格式问题**,只用把内容(str)写进去就好。)

之所以生产不这么用,是因为一旦把input写在Node里面,只要人类不回应,这个线程就会被卡住了,后面的一切都无法运行,还不会释放内存,**这样非常不妙,**下篇文章会讲解一下到底应该怎么避免这种情况发生。

  • 接下来是关于LLM的逻辑:

怎么写其实取决于你调用的是什么LLM API,因为各家的API格式不完全一样。

不过好在langGraph已经在底层帮忙把一些主流供应商的API包装了一下,让你可以用**“非常简单可读的语言”**去调用他们,不用担心解析API回答的问题。

(但是遇到langGraph没有包装过,或者是供应商对langGraph语法适配不够好的时候,你需要自己去写解析逻辑。)

这里我用Gemini API来举例子。可以import针对于Gemini API定制的包

初始化LLM:

接下来最重要的一步是,使用with_structured_output函数,来形成一个返回结构化回答的LLM实例

(记得我们定义的输出数据结构proposalOutput吗?就是放在这里传进去的)

with_structured_output函数是langGraph提供的,它的本质是把你已经定义的数据结构的要求,通过prompt注入给LLM,要求它遵循这个结构。

并且利用Pydantic的“运行时检查”特性,检查LLM是否返回了正确的数据结构,如果没有则报错。

为什么langGraph这种包装方式就管用?跟我们自己写一个要求LLM生成json的提示词有什么不同?

langGraph的包装方式,也不一定是百分百管用,它也只是一种prompt形式的**“强烈暗示”,LLM依然有可能叛逆。**

**with_structured_output函数是一个“多态函数”。**也就是说,不管你调用什么供应商提供的API接口,都可以使用这个with_structured_output函数,但是它在底层实现上是不同的。

对于ChatGPT和Gemini这种非常成熟的AI API,他们本身在训练的时候就会特别注意让LLM服从结构化输出的约束。

对于这种API,langGraph会在底层**“假装”我们约定好的数据结构(JSON schema),是一个tool。然后让AI误以为自己是要调用这个tool**——既然要调用tool,那么就必须要严格按照tool定义的参数来进行输出。这样做**对于LLM的暗示会更加强烈。**尤其是一些专门针对tool call训练过的模型。

最后一重保险就是with_structured_output这个函数会自动从API返回的tool_calls中提取JSON字符串,塞回Pydantic进行校验,并实例化为Python对象。

这样如果输出错了不会无声无息让它过去,会给你返回一个报错。就形成了一套比较靠谱的结构化机制

**不过报错并不是终点,**报错之后怎么处理也需要定义,这里就先不讲那么复杂了。

还有一点要记住的就是,如果使用的是一些不支持with_structured_output函数的AI API,这一套就会失效,就需要自己想办法去引导AI进行结构化输出了。

  • message处理:

继续回到Node函数上面来,现在我们有了可以结构化输出的LLM实例structured_llm,我们需要传递给TA消息,这样TA才可以回答我们。

除了刚刚已经设定过的“系统提示词”之外,还需要把**“消息列表”**传递给LLM,也就是储存在State里面的messages字段。

为什么这里选择传递整个messages字段呢?因为可能会需要LLM不断依据人类的反馈修正邮件内容,所以TA需要知道**“历史消息”**。

当然历史消息可能会累计得太长以至于超出了LLM的上下文长度,在真实场景中肯定会选择处理得更加精细一点,比如使用最后几条消息或者是加一个“总结”Node。这里就选择最简单粗暴的办法,把历史消息全部传递过去。

现在我们需要传递给LLM的是final_messages。调用invoke函数,就可以拿到LLM的回答,这个回答就是我们要求的proposerOutput类实例

这里还涉及到对用户的“消息展示”,我们没有前端界面,就直接让Node把消息给print出来:

  • Node的输出:

到了return的环节,这个Node要输出的是什么?我们在前面分析过,这个Node只需要return每个字段**“增量”的内容**就可以了。langGraph会依据“归并函数”把“增量”更新到对应的State字段上面。

由于这个AI需要依据人类的反馈不断修正自己给出的邮件草稿(draft),TA肯定是需要看到人类与自己的历史聊天,**我们需要把AI的回答也加入到messages字段里面。**剩下的字段就单独return。

最后的结果是这样:

第四步,定义Edges:

我们知道这个Graph里面只有一个我们自定义的Node,但是由于要依据人类的反馈不断修正邮件,这个Graph里面必定存在一个循环和条件边。只有这样才能够实现:不满足条件就回到emailDraft,满足条件就去END。

现在来定义这个条件边的Router函数:

Router函数的判断规则为:检查State的isConfirmed字段,如果是True,则走到END;如果是False,则返回到emailDraft。

第五步,定义图:

把上面的信息整合起来,可以定义一个有循环的图。

第六步:定义main函数

在这一步里面我们需要定义主程序,主程序是一个**“循环”**,除非用户打断,或者调用达到了门槛值(防止无限调用),否则一直触发Graph。初始化State放在主程序逻辑里面:

最后运行的结果为下面这个样子。可以看见这个邮件起草Bot,是能够记忆历史对话,并且依据用户的反馈不断修改的。

  1. 总结
    ======

Node就是一个**“加工站点”,它加工的内容是State。归功于langGraph的包装,我们只需要返回需要更新的“增量内容”**,langGraph会帮我们依据“归并函数”追加到State上面去。我们一般遵循“**单一职责”**原则来设定Node,这样做有助于提高Node的复用性。

Edge是连接“加工站点”的**“传送带”,分为两种类型Normal EdgeConditional Edge**。Normal Edge储存的是**“不变的映射”:A结束就去B。Conditional Edge储存的是“按照条件判断的映射”**:A结束之后,C条件满足就去B,不满足就去D。

为了逻辑上解耦,Edge这个“导航”依据的是Node的**“字符串名字”**做为参照,不可以直接写function name。

利用State、Nodes和Edges这三大要素可以组成一个Graph,Graph需要编译(compile),这一步是在进行解析“字符串”等准备工作。调用Graph的时候用invoke函数。

利用Pydantic这个精密收纳盒,我们可以把不可控的LLM输出,转变成严谨的Python对象。通过with_structured_output这种“假装调工具”的强暗示,让LLM输出我们想要的JSON格式。

我们搞出了一个会记忆历史对话、会听取人类反馈并不断迭代的“邮件起草Bot”。虽然在Node里直接用input()有点“大逆不道”,但它确实让我们看清了人类反馈是如何进入循环的。

​最后

我在一线科技企业深耕十二载,见证过太多因技术更迭而跃迁的案例。那些率先拥抱 AI 的同事,早已在效率与薪资上形成代际优势,我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在大模型的学习中的很多困惑。

我整理出这套 AI 大模型突围资料包:

  • ✅AI大模型学习路线图
  • ✅Agent行业报告
  • ✅100集大模型视频教程
  • ✅大模型书籍PDF
  • ✅DeepSeek教程
  • ✅AI产品经理入门资料

完整的大模型学习和面试资料已经上传带到CSDN的官方了,有需要的朋友可以扫描下方二维码免费领取【保证100%免费】👇👇
​​

为什么说现在普通人就业/升职加薪的首选是AI大模型?

人工智能技术的爆发式增长,正以不可逆转之势重塑就业市场版图。从DeepSeek等国产大模型引发的科技圈热议,到全国两会关于AI产业发展的政策聚焦,再到招聘会上排起的长队,AI的热度已从技术领域渗透到就业市场的每一个角落。


智联招聘的最新数据给出了最直观的印证:2025年2月,AI领域求职人数同比增幅突破200%,远超其他行业平均水平;整个人工智能行业的求职增速达到33.4%,位居各行业榜首,其中人工智能工程师岗位的求职热度更是飙升69.6%。

AI产业的快速扩张,也让人才供需矛盾愈发突出。麦肯锡报告明确预测,到2030年中国AI专业人才需求将达600万人,人才缺口可能高达400万人,这一缺口不仅存在于核心技术领域,更蔓延至产业应用的各个环节。

​​

资料包有什么?

①从入门到精通的全套视频教程⑤⑥

包含提示词工程、RAG、Agent等技术点

② AI大模型学习路线图(还有视频解说)

全过程AI大模型学习路线

③学习电子书籍和技术文档

市面上的大模型书籍确实太多了,这些是我精选出来的

④各大厂大模型面试题目详解

⑤ 这些资料真的有用吗?

这份资料由我和鲁为民博士共同整理,鲁为民博士先后获得了北京清华大学学士和美国加州理工学院博士学位,在包括IEEE Transactions等学术期刊和诸多国际会议上发表了超过50篇学术论文、取得了多项美国和中国发明专利,同时还斩获了吴文俊人工智能科学技术奖。目前我正在和鲁博士共同进行人工智能的研究。

所有的视频教程由智泊AI老师录制,且资料与智泊AI共享,相互补充。这份学习大礼包应该算是现在最全面的大模型学习资料了。

资料内容涵盖了从入门到进阶的各类视频教程和实战项目,无论你是小白还是有些技术基础的,这份资料都绝对能帮助你提升薪资待遇,转行大模型岗位。


智泊AI始终秉持着“让每个人平等享受到优质教育资源”的育人理念‌,通过动态追踪大模型开发、数据标注伦理等前沿技术趋势‌,构建起"前沿课程+智能实训+精准就业"的高效培养体系。

课堂上不光教理论,还带着学员做了十多个真实项目。学员要亲自上手搞数据清洗、模型调优这些硬核操作,把课本知识变成真本事‌!

​​​​

如果说你是以下人群中的其中一类,都可以来智泊AI学习人工智能,找到高薪工作,一次小小的“投资”换来的是终身受益!

应届毕业生‌:无工作经验但想要系统学习AI大模型技术,期待通过实战项目掌握核心技术。

零基础转型‌:非技术背景但关注AI应用场景,计划通过低代码工具实现“AI+行业”跨界‌。

业务赋能 ‌突破瓶颈:传统开发者(Java/前端等)学习Transformer架构与LangChain框架,向AI全栈工程师转型‌。

👉获取方式:

😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓**

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

深度解析79.5k星AI代理Clawdbot的持久记忆系统架构与实现

文章介绍了开源AI代理Clawdbot的持久记忆系统。该系统采用本地Markdown存储,结合向量搜索和BM25关键字检索,构建双层记忆架构(每日日志长期记忆)。系统支持自动压缩、记忆刷新和会话管理,确保信息持久性和上下文连贯性…

作者头像 李华
网站建设 2026/4/16 14:05:45

云计算第四次作业

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8" /><title>微博发布</title><style>* { margin: 0; padding: 0; }ul { list-style: none; }.w { width: 900px; margin: 0 auto; }.controls texta…

作者头像 李华
网站建设 2026/4/16 11:08:33

基于深度学习YOLOv11的食物检测系统(YOLOv11+YOLO数据集+UI界面+登录注册界面+Python项目源码+模型)

一、项目介绍 本文介绍了一个基于深度学习YOLOv11算法的食物检测系统&#xff0c;能够准确识别30类常见食物及饮品。系统整合了完整的YOLO数据集、用户友好的UI界面&#xff08;含登录注册功能&#xff09;以及Python项目源码与预训练模型。该模型在包含14,661张图像的数据集上…

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

《唐朝诡事录之西行》——独孤羊放妻春条书

前年暑期&#xff0c;电视剧《唐朝诡事录之西行》播出&#xff0c;其中“仵作之死”单元令我印象深刻&#xff0c;尤其是独孤羊写给妻子春条的那封休书。基于这份触动&#xff0c;我使用 Unity3D 引擎制作了一个小项目&#xff0c;通过 TextMeshPro 实现文本横竖排显示&#xf…

作者头像 李华
网站建设 2026/4/16 12:22:01

一文讲清楚Java中的抽象类、接口和内部类三大特性

目录 第一章 抽象类 1.1 概述 1.1.1 抽象类引入 1.2 abstract使用格式 1.2.1 抽象方法 1.2.2 抽象类 1.2.3 抽象类的使用 1.3 抽象类的特征 1.4 抽象类的细节 1.5 抽象类存在的意义 第二章 接口 2.1 概述 2.2 定义格式 2.3 接口成分的特点 2.3.1.抽象方法 2.3.…

作者头像 李华
网站建设 2026/4/16 10:41:38

基于深度学习的可视化植物病害检测系统(YOLOv8+YOLO数据集+UI界面+Python项目+模型)

一、项目介绍 摘要 本项目开发了一套基于YOLOv8目标检测算法的可视化植物病害智能检测系统&#xff0c;专门用于识别和分类30种不同的植物叶片病害。系统训练数据集包含2009张训练图像和246张验证图像&#xff0c;涵盖了苹果、蓝莓、樱桃、玉米、桃子、土豆、大豆、草莓、番茄…

作者头像 李华