news 2026/9/12 4:32:37

AI Agent实战:从ReAct循环到LangGraph与MCP生产级实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI Agent实战:从ReAct循环到LangGraph与MCP生产级实现

开场:第二课,我们开始动手

如果你已经看完了第一课,脑子里对AI Agent应该有了一个基本画面——它不是一个单跑的模型,而是一个能自己规划、调用工具、迭代执行、最终交付结果的“数字员工”。但第一课看完,大概率你还是会有一个困惑:原理我都懂了,ReAct循环也看过图解了,可真让我从零搭一个能用的Agent,我还是不知道第一步该干什么。

这一课就把这个问题解决掉。

我会从四个层面往下拆:先讲清楚Agent在运行时的核心机制——这一层决定你写的Agent是“能演示”还是“能干活”;再做一次框架选型,把LangGraph、Spring AI这些主流方案的底层差异摊开看;接着聊MCP这个绕不过去的工具标准化协议;最后用一个真实的知识库问答Agent案例,把整个过程串一遍。这套内容对应到面试官的提问逻辑里,基本就是“你做过Agent吗”和“你做的是玩具还是生产级Agent”的区别。

适合的读者:已经看完第一课、对提示词工程和大模型基本调用有概念的人。如果你连Agent是什么都不知道,建议回到第一课补基础。

1. Agent运行机制深度拆解:决定你写的是玩具还是工具

很多人写Agent,第一步就是去调LangChain或LangGraph的封装API,跑通了一个demo就觉得自己会了。但一旦遇到真实业务需求,立刻翻车——要么工具调用老出错,要么多轮迭代下来上下文乱成一团,要么Agent自己绕进死循环出不来。这些问题,表面上看是代码问题,根子上是对运行机制理解不到位。

1.1 从ReAct到真正的循环控制

第一课讲过,ReAct的核心是“思考-行动-观察”的循环。但第一课限于篇幅,没有展开一个关键问题:这个循环在生产环境里到底是怎么被控制的?

标准ReAct循环在代码层面的本质,其实是一个while循环:

  1. 模型根据当前对话上下文生成下一步动作(调用工具 or 直接回答)。
  2. 如果是工具调用,解析出工具名和参数,执行工具。
  3. 把工具返回结果拼接到上下文中,回到第1步。
  4. 如果模型判断任务完成(输出最终答案),循环终止。

这个逻辑看起来简单,但实际开发中你会遇到一个很要命的问题:模型判断“任务完成”的时机对不对?我见过太多Agent在工具返回一个错误结果时,直接说“好的,任务已完成”然后把错误结果交给用户。原因就是那套提示词里只写了“完成任务后输出答案”,没有定义“什么叫任务真正完成”。

生产级Agent必须在提示词里显式约定循环终止条件。我自己的做法是在系统提示词里写死两条规则:一是只有当所有子任务都被标记为“成功”或“无需执行”时才允许输出最终答案;二是如果某个工具调用连续失败两次,必须切换策略或请求用户输入,禁止第三次重试同一个操作。这两条规则,在demo里看不出来差别,但放到可靠运行场景里,就是天壤之别。

1.2 记忆系统:Agent的“临时手写板”和“长期笔记本”

另一个被严重低估的是记忆机制。Agent开发里记忆分两种:短期工作记忆和长期存储。

短期工作记忆,就是当前对话的上下文窗口。工具返回的数据、中间推理过程,都堆在这里。这个区域的容量是有限的——上下文窗口就这么大,塞满了就溢出。我在开发中经常看到新手犯的错:每轮工具调用都把完整返回结果塞进上下文,五轮之后上下文就爆了。

处理办法是“摘要压缩”。当上下文接近阈值时,用一个轻量模型把前面的对话浓缩成摘要,替代原始消息。这个技术在AutoGPT、BabyAGI那批早期项目里就已经是标配了,但很多人做Agent时完全没有这个意识。

长期记忆则要靠外部存储,通常是向量数据库。它解决的问题是:Agent下次接到类似任务时,能不能回忆起上次的执行经验。这块我放到后面的知识库案例里一起讲,因为单独聊太抽象,配合具体场景比较容易理解。

1.3 规划能力:线性执行与动态规划的取舍

ReAct框架天然是线性的——走一步看一步,每一步都重新推理。这种方式的优点是灵活性高,即时反馈;缺点也很明显:复杂任务可能要走十几步,每一步都在消耗token,而且一旦中途走偏,纠偏成本极高。

生产环境里,我更喜欢Plan-and-Execute模式:先让Agent基于任务目标生成一份执行计划,再逐个执行计划中的步骤。这种模式把“规划”和“执行”解耦,好处有两个——一是规划阶段可以一次性把任务拆透,执行阶段每步都很聚焦;二是如果某个步骤失败了,Agent可以只重试那一步,不用推倒重来。

我曾经用一个多步骤数据处理任务做过对比:线性ReAct模式跑了17轮,其中有5轮是在无关的推理上打转;Plan-and-Execute模式规划阶段用了2轮生成计划,后面8轮执行完所有步骤,总共10轮搞定。省下来的不只是token费用,更是时间和出错的概率。

维度线性ReActPlan-and-Execute
执行方式边想边做先规划后执行
灵活性高,可随时调整中,计划先行但可修改
任务复杂度适合简单任务适合多步骤复杂任务
Token消耗通常更高更可控
典型应用工具调用、问答业务流程、数据处理

真实项目里,两种模式不是二选一。LangGraph里完全可以用规划节点来做顶层拆分,每个子任务内部再走ReAct循环。这种“大规划+小循环”的组合,是我个人最推荐的Agent架构基线。

2. 框架选型:LangGraph、Spring AI与多Agent体系的对比

聊完原理就该动真格的了。市面上Agent开发框架五花八门,新手最容易被“哪个火选哪个”带偏。我先给出我的选型结论,再展开分析:单Agent场景,LangGraph是当前最合适的选择;Java技术栈团队,重点看Spring AI;复杂业务需要多角色协作的,可以研究AutoGen或LangGraph的多Agent子图能力。

2.1 主流框架横向对比

框架语言核心优势适用场景学习曲线
LangGraphPython/JS图状态机精确控制流程,可生产化复杂工作流、有状态Agent中高
LangChainPython/JS生态成熟,组件丰富快速原型验证
AutoGenPython多Agent对话编排能力强多角色协作、群体讨论
Spring AIJava与Spring生态无缝集成,企业级Java技术栈的AI应用
Semantic KernelC#/Python/Java微软系,企业级支持好微软生态用户

这个表格只代表框架的“方向性差异”。实际选型时要考虑的不只是功能,还有团队的存量技术栈。很多企业级项目里,最合适的不是“功能最强的框架”,而是“离现有系统最近的框架”。

2.2 LangGraph为什么值得优先学

LangGraph的核心思想是把Agent的运行流程定义成一张图。图的节点是处理步骤(可以是模型调用、工具执行、条件判断),边是转换逻辑,状态是贯穿整个图的共享数据。这个设计的好处是:流程的每个环节都可视化、可控、可调试。

我自己学LangGraph的路径,是从三个核心概念入手的,也建议你按照这个顺序来:

State(状态):整个图的“共享内存”。所有节点都能读写State,节点之间的数据传递就靠它。定义State时要提前想清楚哪些字段需要跨节点传递,哪些字段用完即弃。

Node(节点):图里的基本执行单元。一个节点可以是一个Python函数,负责完成一件事,比如“调用OpenAI API生成回复”或“执行数据库查询”。

Edge(边):节点之间的连接关系。除了普通的前后顺序,LangGraph还支持条件边——根据State里某个字段的值,决定下一步走哪个节点。

举个最直观的例子:如果我要写一个“先检索知识库,再生成回答”的Agent,用LangGraph就是三个节点——一个负责把用户问题向量化并检索,一个负责组装提示词调用模型,一个负责格式化输出。节点连接以后,运行过程完全透明,哪一步出了问题,直接看图上状态就知道。

LangGraph还有一个杀手级功能:状态检查点。它能把Agent每一步执行后的State快照存下来(支持存到SQLite或PostgreSQL),一旦中途崩溃,可以从最近一个快照恢复继续跑。这个能力在生产环境有多重要,做过的都懂——不是“锦上添花”,是“没有它就别谈上线”。

2.3 Multi-Agent:更高级但更复杂的架构

热词里反复出现“multi agent”,我也简单展开一下。多Agent不是“用多个Agent分别干活”那么简单,核心挑战在Agent之间的通信协议和任务仲裁机制

比如Spring AI里的Multi-Agent支持,核心思路是通过一个编排层统一调度多个专职Agent(一个负责写代码,一个负责查文档,一个负责测试)。编排层要处理的核心问题包括:任务该分配给谁、各Agent的输出怎么汇总、Agent之间意见冲突时听谁的。

多Agent架构能显著提升单一任务的上限,但同时也会引入额外的工作量:每个Agent的上下文是独立的还共享的?工具访问权限怎么隔离?全链路怎么追踪?我的建议是:刚学Agent,先老老实实把单Agent做到极致,再碰多Agent。很多人一上来就盲目追multi-agent,结果连单Agent的工具调用稳定性都没解决,多Agent只会把问题放大。

3. MCP协议:让Agent长出手脚的关键一环

Agent如果只能对话,那它只是一个聊天机器人。Agent之所以是Agent,在于它能调用外部工具——查数据库、调API、操作浏览器。而工具接入方式的设计,经历了一个明显的演进,这是今天我特别想讲清楚的一笔账。

3.1 为什么需要MCP而不是Function Calling

最初大家都是用Function Calling。开发者把工具定义用JSON Schema描述出来,塞给模型,模型根据用户意图选择调用哪个函数。这套方案的痛点在于:每个工具都要为每个Agent单独做适配。你给Agent A接了一个搜索工具,下次Agent B要用,还得重新接一遍。接入方和被接入方完全耦合,改一个字段,两边都要动。

MCP(Model Context Protocol)就是为了解决工具接入的标准化问题而生的。你可以把MCP理解成工具界的USB-C接口——所有工具都实现一套统一协议,Agent通过同一个协议去发现、调用所有工具,不需要为每个工具单独写对接逻辑。行业里把MCP称为“AI Agent的硬件接口标准”,这个比喻我觉得挺贴切。

3.2 MCP架构拆解与实战示例

MCP的架构分三层:

  • MCP Server:工具的实际提供方。把现有能力(比如数据库查询、搜索、文件操作)包装成一个MCP Server,暴露标准化的工具接口。
  • MCP Client:Agent侧的连接器。负责与MCP Server建立通信,把Server提供的工具列表拉取给模型。
  • 传输协议:默认基于JSON-RPC 2.0,支持stdio和SSE两种传输方式。

举个例子,假设我要让Agent能查本地SQLite数据库。不用MCP的话,我得写一个query_database函数,格式化参数列表,写进系统的提示词里。用MCP的话,我可以用一个现成的SQLite MCP Server:

# server.py - 基于官方mcp库定义SQLite查询工具 from mcp.server import Server from mcp.server.stdio import stdio_server import sqlite3, json app = Server("sqlite-server") @app.tool() async def query_sqlite(query: str, db_path: str) -> str: """在指定SQLite数据库上执行查询,返回带格式的结果。""" conn = sqlite3.connect(db_path) try: cur = conn.cursor() cur.execute(query) cols = [d[0] for d in cur.description] rows = cur.fetchall() return json.dumps({"columns": cols, "rows": rows}, ensure_ascii=False, default=str) except Exception as e: return f"查询失败: {e}" finally: conn.close() if __name__ == "__main__": import asyncio asyncio.run(stdio_server(app))

Agent侧接入时,只需要在LangGraph的配置里告诉它“这个Agent挂了一个SQLite工具”,剩下的动作——发现工具、生成调用请求、解析返回值——全走MCP标准协议,不用写一行定制对接代码。

3.3 一处制作、处处复用的工程意义

对个人开发者来说,MCP初期可能感觉有点“多此一举”。但对团队和企业,收益极其明显。我参与的一个项目里,团队把内部业务系统封了十几个MCP Server(订单查询、库存盘点、物流追踪),然后所有Agent统一接入这十几个Server。新Agent上线当天就能用所有现有工具,不用重新做对接。

MCP里还有一个“Agent网关”的用法值得提一句:把企业内部的所有MCP Server统一在一个网关注册,Agent通过网关统一鉴权、统一路由。这样既解决了工具标准化的问题,又解决了工具访问权限控制的问题。这块现在各家云厂商都有产品在做,大家在选型时可以多关注一下。

4. 生产级执行全流程:从任务输入到成果交付的完整链路

很多教程讲到框架和工具就停了,好像写完一个能跑的Agent就是终点。但真正从“能跑”到“能交付”,中间隔着一条很长的路。行业内有人总结过一套方法论:三阶段、六泳道、三十个核心节点,我觉得这个框架很能说明问题,这里给你完整拆一遍。

4.1 三阶段:规划-执行-交付

阶段核心目标关键产出
规划阶段理解任务、制定方案任务拆解清单、执行计划、所需资源列表
执行阶段按计划完成任务中间产物、工具调用记录、结果数据
交付阶段验证结果、输出最终成果格式化答案、结果验证报告、后续行动建议

三个阶段不是简单的时间顺序,而是每阶段都有质量门禁。阶段之间过不去门槛,就回退到前一个阶段重新处理。

4.2 六泳道:贯穿全程的关注维度

三阶段是把时间轴分成了三段,六泳道则是从横向维度去看整个执行过程中需要并行关注的事情:

  1. 需求理解泳道:持续校准“用户到底要什么”,防止做偏。
  2. 任务规划泳道:负责把大目标拆解成可执行的小任务,并安排顺序。
  3. 工具调度泳道:负责选择、调用、切换工具,管理调用过程中的异常。
  4. 上下文管理泳道:控制上下文的增长、摘要、记忆存取。
  5. 推理决策泳道:负责每一步的模型推理和质量判断。
  6. 结果验证泳道:验证工具返回结果是否符合预期,存在疑问时及时修正。

这六个通道,几乎覆盖了我在开发Agent过程中踩过的所有坑。比如上下文管理,学习阶段基本没人会想这一层,但真到生产环境,我自己就遇到过上下文继续膨胀、费用突然飙升、以及运行速度下降好几个级别的现象。加了上下文摘要机制之后,才算稳定下来。

那“30个核心节点”是什么?其实就是把三阶段六泳道进一步细化成30个具体动作,比如“接收用户输入并解析目标”“制定执行方案”“初始化长期记忆”“选择首个工具”“执行工具调用”“验证工具返回数据”“判断是否需切换策略”“生成最终答案”“主动提出后续建议”等等。不需要把这30个节点背下来,重点是理解一个道理:生产级Agent的执行不是“模型自由发挥”,而是“有流程、有检查点、有兜底方案”的工程化过程。

4.3 工程启示:从“模型逻辑”到“产品逻辑”

整套三阶段六泳道的方法论,本质上是在把Agent从“模型驱动”转成“流程驱动”。模型仍然负责推理和决策,但不再负责整个执行过程的“自由落体”。每一阶段有目标,每一泳道有规则,整个系统才是可控的。

这个思路在面试里也特别好用。当面试官问你“如何保证Agent的输出质量”,你回答“我通过结果验证泳道,对工具返回数据做格式和逻辑的双重校验,不通过则触发修正流程”,和回答“我让模型自己判断对错”,说服力完全是两个级别。

5. 实操演示:用LangGraph+MCP搭建一个知识库问答Agent

理论部分差不多了,我给一个我自己做过的具体项目:一个基于私有知识库的问答Agent。这类Agent是目前企业里用得最多、也最适合用来练手的场景。它既涉及RAG(检索增强生成),又涉及工具调用,逻辑链条完整,而且每个环节都能独立调优。

5.1 需求与架构设计

需求很简单:用户向Agent提问,Agent从企业知识库(一堆PDF/Word文档)中检索相关内容,基于检索结果回答。不能胡编,必须引出处。

基于这个需求,架构分成四个模块:

  1. 文档处理模块:把文档切块、向量化,写入向量数据库。
  2. 检索模块:用户提问后,从向量数据库检索top-k相关片段。
  3. 模型问答模块:把问题+检索结果拼进提示词,调用大模型生成回答。
  4. 质量控制模块:判断检索片段是否真的与问题相关,不相关则换一种检索策略重试。

5.2 核心代码实现(LangGraph版本)

这是我的LangGraph实现,节点定义就是上面说的四个模块:

from langgraph.graph import StateGraph, END from typing import TypedDict, List class AgentState(TypedDict): question: str context: List[str] answer: str def retrieve_node(state: AgentState): """检索节点:从向量库拉取相关片段""" docs = vector_db.search(state["question"], top_k=5) # 问题:top_k检索经常混入不相关内容,通过重排序模型过滤掉 reranked = reranker.rerank(state["question"], docs, top_n=3) return {"context": reranked} def qa_node(state: AgentState): """生成节点:基于检索结果生成答案""" ctx = "\n\n".join(state["context"]) answer = llm.invoke(f"""基于以下资料回答问题。资料中未提及的信息,明确回答"资料中未找到相关内容"。 资料:{ctx} 问题:{state["question"]}""") return {"answer": answer} def guard_node(state: AgentState): """质量门禁:检查检索片段和回答的关联度""" if any(len(c) < 50 for c in state["context"]): return {"context": state["context"], "answer": "检索不足,请换一种问法"} return state g = StateGraph(AgentState) g.add_node("retrieve", retrieve_node) g.add_node("qa", qa_node) g.add_node("guard", guard_node) g.add_edge("retrieve", "qa") g.add_edge("qa", "guard") g.add_edge("guard", END) app = g.compile()

这段代码跑起来不难,但真正要优化到“能上线”的水平,我踩过几个大坑,这里一起说清楚。

5.3 关键参数选择与调优心得

第一是检索的top_k设置。我一开始用top_k=5,结果回答质量不稳定。后来加了重排序模型(reranker),先把候选从10篇里筛出来,再精排到3篇。这个步骤让回答准确率提升非常明显,而且token消耗反而降了。强烈建议凡是做RAG的Agent,都加一层重排序。

第二是回答的幻觉控制。模型经常会把资料里没有的内容“推理”进去,表现得一本正经。我的缓解办法是在提示词里强制要求“资料中未提及的信息,明确说明未找到”,并且让模型在回答末尾列出引用了哪几篇文档。加了这两条之后,实际测试中幻觉类问题大幅减少,虽然不能完全消除,但已经达到了业务可接受的程度。对这个领域的从业者来说,有一个基本认知特别重要:幻觉不是靠提示词能根除的,只能通过反复验证和系统设计来逼近零幻觉,这才是Agent落地的正确心态。

第三是切片策略。我一开始每500字切一片,但很多知识条目恰好被拦腰截断,导致检索到的内容不完整。后来改成“按段落边界切,再结合语义合并”,并且上下各保留50字作为重叠区,检索质量好了一截。

第四是模型选择。知识库问答其实不一定要用最强的模型。一个中等参数规模的模型,配合好的检索结果和提示词,效果已经足够好,token成本却低了一半还多。给生产环境选模型时,先跑一套评测集,用“答案相关性+引用正确率”这两个指标量化对比,别凭感觉拍板。

5.4 接入MCP工具后的能力跃迁

基础版本跑通后,我给这个Agent加了一个MCP工具:一个可以查询资料更新时间的内部系统。

之后Agent的行为逻辑多了一条分支:用户问“XX政策现在还是不是最新版”时,Agent先去MCP工具查询资料的“最后更新时间”,再决定直接用知识库回答,还是告诉用户“现有资料可能已过期,请确认后再使用”。

这个改动让Agent的业务价值明显上升。原因在于,纯粹的知识库是一个静态快照,但业务场景里的信息是动态的。Agent能主动调用外部系统验证信息时效,才算真正从“资料查询机”进化成了“能辅助判断的工作助手”。这类“Agent主动发起工具调用”的场景,建议你在做项目时多设计几个。

6. 常见问题与排查技巧实录

这部分是我自己实践过程中踩过的坑,很多是翻官方文档翻不到的,直接整理成速查清单,你遇到类似问题时可以对照着排查。

问题现象根因分析排查思路与解决方式
Agent频繁调用同一个工具3-5次,且参数不变模型陷入了“重复尝试”循环在系统提示词中约定“同一工具相同参数最多执行2次,仍失败必须切换策略”;也可以设置调用频率限制
工具调用返回的参数总是解析失败工具返回的JSON结构不稳定不要直接让模型解析,自己写解析逻辑,前后加文字时用正则提取JSON,再做异常兜底
多轮对话后上下文爆满所有历史消息都保留在上下文里加“摘要节点”,接近阈值后用轻量模型压缩;长对话只保留摘要+最近两轮原文
Agent回答时引用不存在的内容检索相关度不够/提示词约束不严加入重排序模型;提示词强制“未检索到必须说明”;添加引用源字段,让输出可追溯
某个工具的调用老是超时工具在MCP Server中初始化开销大对MCP Server做连接池复用;把高频工具的连接改为全局常驻,避免每次从头建连
多个Agent实例共享同一工具时互相干扰工具状态/配置被多实例共享将工具的上文无关性设计成纯函数式调用,杜绝全局变量;涉及状态的操作加实例级隔离
Agent跑得很慢单轮推理时间太长,或多次调用长上下文模型精简上下文:过滤无关工具返回、使用摘要替换完整历史;对分支操作并行化执行

对新手来说,第七个问题尤其值得警惕。很多Agent项目一开始“跑通了”,一压真实数据就卡顿,通常就是在早期阶段忽略了上下文瘦身。我们把这个理念放在相对靠前的位置来规划,整个系统的稳定性会好很多。

还有一条独门经验:给Agent加一层“输入校验”。我遇到过用户输入信息不全,Agent反复补问,体验很不好。后来给Agent加了一个前置校验节点,先判断用户信息是否完整,不完整就一次把需要的字段全部列出来追问,而不是一轮轮挤牙膏。这个改动让整个交互体验顺滑了很多,算是我自己很满意的一个优化。

最后的几句经验之谈

写到这里,第二课的核心内容就讲完了。回到我们开头说的那个问题——为什么很多人学完Agent开发,做出来的东西还是停留在“能演示”的层面?我的答案很简单:因为只学了搭建,没学工程。

Agent开发能力的真正分水岭,不在于你会不会调LangGraph的API,也不在于你能不能跑通一个MCP工具,而在于你有没有建立起一套“让Agent在未知环境里稳定交付结果”的系统思维。三阶段六泳道是这种思维的方法论,LangGraph和MCP是这种思维的落地工具,而无数次问题排查才是训练这种思维的最佳实践。

这一课的知识密度不小,建议你至少完整读两遍,然后挑一个小场景动起手来。下一课,我会重点讲Agent的评测体系和监控运维——毕竟在生产环境里,怎么衡量Agent“好”或者“不好”,怎么在出了问题之后快速定位和修复,才是决定它能走多远的东西。到时候见。

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

知行合一实践指南:即事知道的认知科学与方法论

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 4:31:55

程序员职场生存:从氛围编程到核心竞争力

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 4:31:25

AI大模型网关升级实战:部门费用明细与限流配额设计

过去半年&#xff0c;我一直在折腾一件事&#xff1a;给公司内部的AI大模型网关做一次服务升级&#xff0c;核心就两个功能——部门费用明细和限流配额。说起来简单&#xff0c;真正落地才发现&#xff0c;这两个功能背后牵扯的是企业内部AI治理的整个体系。这篇博文就把整个升…

作者头像 李华
网站建设 2026/9/12 4:31:13

无设计背景?用免费工具快速制作App宣传图,过审上架全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 4:31:05

B2B营销策略与数字化工具实战指南

1. B2B营销的本质与核心挑战B2B营销&#xff08;Business-to-Business Marketing&#xff09;是指企业向其他企业提供产品或服务的商业活动。与面向普通消费者的B2C营销相比&#xff0c;B2B营销具有决策周期长、参与决策者多、订单金额大等特点。根据Gartner的研究报告&#xf…

作者头像 李华
网站建设 2026/9/12 4:30:42

Python开发者转型AI Agent工程师实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华