Agent框架插件化设计:内核最小化与生命周期钩子实战

发布时间:2026/10/22 3:46:00
Agent框架插件化设计:内核最小化与生命周期钩子实战 1. 整体设计与思路拆解插件化不是“留个坑让你塞代码”1.1 为什么插件化是 Agent 框架的命门先说结论一个 Agent 框架如果不做插件化就只能撑过 demo撑不过生产环境。我把一个开源 Agent 框架从入口到扩展点拆了个遍花了三个晚上。第一晚看启动流程第二晚看工具调用链路第三晚专门盯着插件体系看了个通宵。最让我感慨的地方是它把“插件化”这件事做到了不是“给你留个扩展点让你把代码塞进去”而是整套主流程本身就可以被替换、被拦截、被扩展。Agent 框架的核心玩法其实很固定用户输入进来先做记忆检索然后丢给大语言模型做推理模型决定调用哪个工具工具返回结果后再交给模型总结最后输出给用户。这个循环听起来简单但真正做框架的人都知道难点在于变化。今天要接一个新的数据源明天要换一种记忆策略后天又要支持多 Agent 协作。如果每个新需求都要改主循环代码那框架就是一笔怎么都还不完的债。插件化解决的正是这个问题。它把所有可能变化的部分从内核里抽出来变成一个个可以独立加载、卸载、替换的插件单元。内核只管两件事把“什么该发生”的决策权交给模型的推理结果把“该怎么执行”的执行权交给插件。剩下的一切都是插件自己的事。这个开源框架给我的第一个冲击就在这里。它不是做做样子把工具调用做成插件就完事了。它连记忆引擎、提示词模板、上下文预处理、后处理校验甚至 Agent 的主循环本身都能以插件形式介入。开发者可以不改框架一行代码就实现“让 Agent 在每次回复前自动做一次敏感词检查”这种需求。传统做法是你得去改源码而在这个框架里只需要写一个插件挂上去。1.2 “内核最小化”的分层设计拆完这个框架我发现它的整体架构可以用三个词概括内核最小化、协议标准化、实现外置化。内核只保留一条精简的 Agent 循环接收输入、构造上下文、调用模型、解析输出、执行动作、产出结果。内核里几乎没有业务逻辑也没有具体的工具实现。它甚至连“记忆是怎么存取”的都不知道。它只知道有一个“记忆插件”的接口在需要的时候会去调用具体是存在内存、数据库还是向量库内核一概不管。这种设计有一个非常明显的好处框架的稳定性和复杂度是可控的。内核的代码量小bug 就少安全边界就清晰。所有容易膨胀的部分都被赶到了插件里插件之间是隔离的一个插件崩了不会拖垮整个进程。生产环境下这是保命级的优势。我可以很直白地说如果我们自己从零写一个 Agent 系统最容易犯的错误就是一开始把所有功能都塞进一个类里一个 Agent 类既管记忆、又管工具、又管调用链、又管重试。改一个功能可能牵扯到另外三个而且一旦并发量上来状态全搅在一起根本没法排查。这个开源框架的分层方式是典型的“操作系统式”思路。你可以把它理解为内核是操作系统的核心插件是运行在操作系统上的应用。应用出了问题顶多自己退出不会把整个系统拖崩。应用要调用硬件得通过操作系统提供的接口不能直接去动底层。这个比喻放到 Agent 框架里同样成立。打开项目目录能看到几个核心模块比如内核运行时、上下文构造器、插件加载器、工具协议定义剩下大量的功能都是以插件形式散落在独立目录里。有些插件是官方维护的有些是社区贡献的。目录结构本身就是一张架构图读代码之前先看目录就能猜出七八分设计意图。1.3 三个关键模型描述文件、注册表、上下文对象插件化要真正落地光有“接口”是不够的它还需要一套配套的机制。我在拆这个框架时提炼了三个关键模型理解了这三个东西基本就理解了这个框架的插件化全貌。第一个是插件描述文件。每个插件都有自己的 manifest 文件里面声明了插件名称、版本号、依赖、启用的钩子、挂载的权限。框架启动时插件加载器会扫描指定目录读取每个 manifest校验合法性。这个描述文件是插件和框架之间的“合同”你声明了什么框架就知道怎么对待你。这也是排查问题的重要线索后面我会专门讲。第二个是插件注册表。加载器把插件扫描完之后并不会直接激活它们而是先把所有信息登记到注册表里。注册表维护着每个插件的状态已发现、已加载、已启用、已禁用、运行中、异常。框架的主循环在需要某个能力的时侯会向注册表发起查询找到对应的实现。这让整个系统变得特别灵活同一类能力可以存在多个实现通过配置或运行时决策来决定到底用哪一个。第三个是上下文对象。这是插件之间通信的核心。每次用户请求进来框架会创建一个独立上下文从请求头到历史消息、从模型返回的中间结果到插件自己写入的数据全都放在这个上下文里。插件不会直接互相调用而是通过读写上下文来协作。这就像是快递运输里的面单箱子可以装什么、发到哪里、有哪些注意事项全都写在面单上各个分拣中心不需要互相联系只需要看面单就行。这三个模型配合起来效果非常惊人。以插件描述文件为例它不仅是配置还能当作扩展的“制造说明”。我写过一个插件一开始只在 manifest 里声明了“我需要在 Agent 启动时初始化数据库连接”后来想让它支持热更新只需要在 manifest 里加一个钩子声明重新加载时框架就会自动调用新的方法代码不用改任何一行。2. 核心细节解析与实操要点一个插件从加载到干活会经历什么2.1 插件接口到底长什么样一个开源 Agent 框架的插件接口通常不会搞得太复杂。核心就是一个基础的插件抽象类然后根据插件类型不同派生几个子类。我拆的这个框架插件接口大概长成下面这样class Plugin: name: str version: str def on_load(self, ctx: PluginContext) - None: 插件被加载时调用适合做资源初始化前的准备工作 def on_enable(self, ctx: PluginContext) - None: 插件被启用时调用适合注册工具、订阅事件、初始化连接 def on_disable(self, ctx: PluginContext) - None: 插件被禁用时调用适合释放资源、反注册工具 def on_unload(self, ctx: PluginContext) - None: 插件被卸载前调用做最终的清理工作你可能会问为什么需要四个方法两个不够吗这是因为一个插件在运行时可能处于不同的状态。加载代表“框架已经认识你了”但还没有真正启用启用才是“现在开始干活”。两者分离的意义在于加载阶段可以做静态检查和元信息注册而启用阶段才创建连接等重资源。禁用和卸载的分离同样是为了支持优雅退出。除了这些生命周期方法插件还可以通过装饰器暴露工具。这也是 Agent 框架插件化最核心的一部分from agent_sdk import tool class WeatherPlugin(Plugin): tool def get_weather(self, city: str) - str: 查询指定城市当前天气 ...你看出关键了吗一个普通的方法只要加上 tool 装饰器就自动变成了大语言模型可以调用的“工具”。框架会在启动时扫描插件的所有方法对标记了 tool 的方法自动生成 JSON Schema 定义包括方法名、描述、参数类型和必填项。这层能力很大程度上决定了开发体验写工具和写普通方法几乎没有区别不需要关心“模型怎么找到这个工具”这类细节。这里还有一个容易被忽略的点工具方法的 docstring 非常重要。模型不是靠猜来理解你提供的工具而是靠你写的自然语言描述以及参数名去理解。描述写得越精确、越简洁模型就越知道什么情况下该调用这个工具。我在实际使用中看到不少人写工具时方法名挺规范但 docstring 是空的结果模型死活不调用这个工具查了半天不是框架的问题就是文档不够清晰。2.2 生命周期钩子的执行时序与状态管理拆代码的时候我专门去看了插件管理器那一块的时序逻辑。它有一个明显的顺序插件加载器启动后先扫描插件目录读取 manifest 文件列表。按依赖关系排序之后第一步实例化插件的类对象拿到内存实例。然后调用 on_load 方法在这个阶段主要是让插件检查自己的配置是否完整把必要的元信息告诉框架。接下来框架会校验插件声明的工具和钩子验证没有冲突后调用 on_enable 方法让插件进入正式工作状态。这一步才会把插件的工具注册进工具表把事件订阅绑定到对应的事件总线上。顺带一提经典的 Agent 循环里还有几个更细的钩子。比如每次模型调用前会触发 before_model_call每次模型输出后会触发 after_model_call。插件可以利用这些钩子偷偷“做小动作”。我试过一个场景用 before_model_call 钩子把当前时间动态拼进系统提示词让模型具备时间感知能力这在处理“今天星期几”这种问题上效果立竿见影。这种设计带来的一个直接好处是插件可以做很多本来需要改内核才能做的事。比如我写过这样一个插件在 after_model_call 里检查模型输出的内容如果输出超过某个长度阈值就自动触发一次摘要压缩。整个过程不需要改动框架任何代码只靠钩子就能完成。不过钩子越多时序问题就越复杂。官方在文档里也特别强调不要假设同一个类型钩子之间的调用顺序是确定的。如果你有 A、B 两个插件都注册了 after_model_call那么它们的执行顺序取决于插件启用顺序。如果你依赖它们按特定顺序执行那正确做法不是赌启动顺序而是在你自己的插件里做编排或者使用优先级字段。我在初用的时候就被这个坑过后面专门写了一个协调插件专门负责编排其他几个插件的调用顺序问题才解决。2.3 上下文对象插件的共享内存与隔离屏障框架里的上下文对象基本是每个 Agent 请求的“临时口袋”。它包含了 session_id、用户原始输入、历史消息列表、当前模型回复的中间状态、token 用量统计、插件自定义数据区等。如果插件之间需要协作正统的做法是把数据塞进上下文让下游插件去读取。为什么不能直接用全局变量一个是并发安全问题。Agent 框架绝大多数都会跑并发请求如果插件用全局变量存每个用户的数据A 用户的数据就可能被 B 用户读到。这是生产事故级别的隐患。另一个问题是隔离性。上下文本质上是请求级别的每个请求一个全新上下文请求结束之后数据就被丢弃或归档天然防止跨请求串数据。上下文对象还有一个很重要的设计它对插件暴露的能力是受限的。插件可以通过上下文读取当前请求的模型配置、调用量信息但不能随意修改框架核心状态。这和操作系统的用户态、内核态区分是一个道理。插件是应用层代码需要给它能力但不能让它一言不合就把内核搞崩。实操中我建议在写插件时把上下文字段名的约定提前确定下来。你想让一个插件在运行中产出中间结果给另一个插件用一定要起一个能一眼看出含义的键名。我见过有人用列表下标来传递数据下下标一换就全乱了。最好定义一个简单的数据协议比如用 plugins.my_note.data 这种带命名空间的键不同插件各自管理自己的前缀互不干扰。2.4 工具协议与自动 Schema 生成Agent 框架里大语言模型本身不执行代码它只负责“决定调用谁”以及“生成调用参数”。真正执行的是插件里的工具方法。为了让模型能准确构造参数框架必须把工具的调用签名暴露给模型。这就是工具协议的核心。我拆的这个框架做法是扫描插件的工具方法用类型注解自动生成 JSON Schema。比如tool def search_order(self, user_id: str, status: str all) - str: ...框架会自动识别 user_id 是必填、str 类型status 是选填、带默认值。然后生成类似这样的结构{ name: search_order, description: 查询用户订单, parameters: { type: object, properties: { user_id: {type: string, description: 用户ID}, status: {type: string, enum: [all, pending, done]} }, required: [user_id] } }这一步由框架自动完成不需要手写 JSON。但这里有一个容易踩的雷参数类型别名。如果你在代码里用了复杂的自定义类型框架可能无法自动生成合适的 JSON Schema。比如参数是一个 Pydantic 模型框架可能需要额外的类型适配器。我见过有人把参数类型写成 bool 和字符串混用生成出来的 Schema 非常不可靠模型容易调错参数。所以写工具方法时参数类型一定要尽量简单、明确最好只用基础类型或者框架支持的模型类型。另外description 的生成规则通常是取装饰器方法下的 docstring 的第一个段落。如果你的工具没有写 docstring那模型看到的就是一个光秃秃的名字。我在 2.1 里强调过这个问题这里再重复一次因为它太常见了。3. 实操过程与核心环节实现从零写一个“暂存笔记”插件3.1 需求场景与插件功能定义光说不练没有意思。我从实际拆解中拿出一段完整的东西带你走一遍“从零写一个插件并在本地跑起来”的流程。场景是这样的:某公司内部知识助手用的 Agent经常在对话中提到一些临时任务用户希望它能把重要的事项“记下来”下次新开会话时仍然能回忆起来。把这个需求拆成插件功能就是两个工具一个是保存笔记一个是查询笔记。框架默认不带持久化记忆所以我们要自己做一个 memory 插件。这个插件的功能定义如下save_note(title, content)保存一条新的临时笔记list_notes()返回当前所有笔记的标题列表get_note(title)根据标题获取笔记内容存储方式先不做重活直接保存成 JSON 文件用简单的文件读写实现。如果你想在生产环境用可以把它替换成数据库或者向量存储但插件接口不变。这个例子的典型之处在于它涉及插件的文件结构、表单配置、工具注册、数据持久化和生命周期钩子几乎覆盖了插件开发的全部要点。3.2 搭建插件目录与最小代码骨架在看这个框架的官方模板时我发现它推荐的最小目录结构如下plugins/ note_memory/ __init__.py manifest.yaml plugin.py对应到具体项目plugins/ note_memory/ __init__.py manifest.yaml plugin.py先看 manifest.yaml这是插件的身份证件name: note_memory version: 1.0.0 description: 为 Agent 提供持久的临时笔记存取能力 enabled: true hooks: - on_load - on_enable - on_disable - on_unload这里声明了插件名称、版本号、启用的钩子。框架会依据这个清单判断插件在生命周期中要不要调用对应的方法。如果你没有把 on_disable 列入 hooks那么框架在禁用时不会执行你写的 on_disable 逻辑这点容易漏要注意。然后看 plugin.pyimport json import os from datetime import datetime from agent_sdk import Plugin, PluginContext, tool class NoteMemoryPlugin(Plugin): name note_memory version 1.0.0 def on_load(self, ctx: PluginContext): self.storage_path os.path.join( ctx.runtime_dir, plugins, note_memory, notes.json ) def on_enable(self, ctx: PluginContext): if not os.path.exists(self.storage_path): os.makedirs(os.path.dirname(self.storage_path), exist_okTrue) self._write_notes({}) self._notes self._read_notes() def on_disable(self, ctx: PluginContext): self._write_notes(self._notes) def on_unload(self, ctx: PluginContext): self._write_notes(self._notes) tool def save_note(self, title: str, content: str) - str: 保存一条临时笔记标题重复时覆盖旧内容返回保存结果 self._notes[title] { content: content, updated_at: datetime.now().isoformat() } self._write_notes(self._notes) return f笔记《{title}》已保存 tool def list_notes(self) - str: 列出当前所有笔记标题无笔记时返回空列表描述 if not self._notes: return 当前没有任何笔记 return \n.join(f- {title} for title in self._notes.keys()) tool def get_note(self, title: str) - str: 根据标题获取笔记内容找不到时说明情况 note self._notes.get(title) if not note: return f没有找到笔记《{title}》 return f{note[content]}\n(更新于 {note[updated_at]}) def _read_notes(self) - dict: try: with open(self.storage_path, r, encodingutf-8) as f: return json.load(f) except FileNotFoundError: return {} def _write_notes(self, notes: dict) - None: with open(self.storage_path, w, encodingutf-8) as f: json.dump(notes, f, ensure_asciiFalse, indent2)这个实现很直接。要点是 on_enable 里做“初始化存储文件并读取已有数据”on_unload 时“确保数据落盘”。工具方法全部通过 tool 暴露docstring 写清楚用途方便模型自动识别调用条件。3.3 注册配置与运行验证插件写完之后并不是丢进目录就能用。需要在框架的配置文件里显式声明要扫描的插件路径或者把插件安装到约定目录。不同框架做法略有差异但通用步骤是这样在框架主配置里把 plugins/note_memory 加入插件目录列表在插件自身的 manifest.yaml 中确保 enabled 为 true重启框架进程观察启动日志里有没有出现类似“plugin note_memory enabled”的记录启动日志是判断插件是否被正确加载的第一手资料。我用来测试的环境里日志长这样[plugin-manager] scan plugins/note_memory [plugin-manager] load note_memory v1.0.0 [plugin-manager] enable note_memory v1.0.0, tools registered: save_note, list_notes, get_note看到 tools registered 后面列出了三个工具就说明插件已经成功注册。接下来模拟一次 Agent 对话。我先在输入框里打了“帮我把 KPI 数据分析报告下周提交这件事记下来内容就是 5 月 6 日下班前提交。”模型识别到“记下来”之后就会自动调用 save_note 工具。我再发一句“我有哪些笔记”模型就会调用 list_notes 展示列表。然后重启框架进程再问一句“我的笔记还在吗”如果实现正确笔记依然可以查到。这说明持久化存储插件工作正常。整个过程最有意思的一点是插件代码没有直接参与任何模型交互逻辑。模型如何决定调用 save_note 还是 list_notes完全是模型根据工具描述和用户指令自行决策的。我们只需要把工具暴露出来并给足清晰的描述。3.4 本地验证时要注意的细节本地跑插件时有几个很容易影响结果的细节第一工具描述尽量写成“什么时候该用”。比如 get_note 的描述“根据标题获取笔记内容找不到时说明情况”。如果改成“获取笔记”模型可能在只需要列出标题时也去调用 get_note导致返回大量内容浪费 token。给工具写清楚适用场景是减少无效调用的关键。第二注意文件写入权限。插件如果跑在容器里存储路径如果没有挂载卷重启后数据就会丢。我一开始就是没注意这个问题本地直接跑没问题换到容器环境重启后笔记全没了。建议在配置或文档里明确标明持久化路径。第三并发写文件时注意锁。我们这个例子是简单文件写入生产环境一旦并发量上来两个请求同时写 notes.json 可能互相覆盖。解决方法是用 sqlite 代替 json 文件或者给读写加文件锁。这也是我在实测中发现的一个典型扩展点。4. 常见问题与排查技巧实录插件加载不生效、上下文串数据、热插拔状态残留4.1 插件不生效先查 manifest 和注册表插件不生效是新手遇到最多的一个问题。现象是日志没有任何报错但模型就是不调用新加的插件工具或者工具列表里压根没有出现新注册的方法。排查步骤可以按这个顺序来检查 manifest.yaml 里是不是把插件名写错了。插件名需要和类里的 name 字段对齐框架按 name 标识插件不一致会导致注册失败后又被覆盖容易产生“明明文件在却找不到”的错觉。检查插件目录是否在框架的扫描路径内。有些配置需要一个 list 插件目录漏加就完全不会扫描。启动日志里搜插件名。没有日志说明没有加载有 load 日志但没有 enable 日志大概率是 on_enable 里抛了异常被框架吞掉了。我之前遇到一个很奇怪的问题插件在本地测试正常切到线上环境后一直不生效。查了很久最终发现是线上配置里插件目录路径指向不对框架没有权限读取该目录扫描结果为空但日志打的是“skipped”级别不仔细看根本注意不到。然后即使插件成功启用了也要在注册表里确认注册记录。以这个框架为例可以调用管理接口查看当前已注册插件列表和各自的运行状态。如果注册表里没有记录说明加载器扫描阶段就有问题和后面的模型调用完全无关。4.2 上下文数据串了多半是用了全局变量在实际排查过程中我见过最经典的“串数据”案例是A 会话的用户在笔记本里记了一条信息B 会话的用户却能在 get_note 里查到。第一反应是缓存问题第二反应是存储路径错误结果查到最后是开发者把笔记数据放在了模块层级的全局字典里。模块级变量在单进程下共享所有请求的插件实例都会操作同一个字典。如果框架没有给每个请求创建独立的插件实例那全局状态就会被不同请求互相污染。正确做法是使用上下文对象管理请求级数据持久化数据才用文件或数据库。比如我们的 NoteMemoryPlugin把 self._notes 当实例变量用框架如果为每个请求创建一个新的插件实例内存状态天然隔离但框架如果是单例模式一个实例处理所有请求self._notes 本质上还是共享的。这种情况最稳妥的方案是每次读笔记都直接读文件或数据库或者把内存数据做成显式的状态存储插件与请求生命周期解耦。我在排查这种问题时的检查顺序是先看插件是单例还是多实例再看数据是否存在模块级变量或类变量里最后看上下文传递链条上有没有把数据不小心塞到了全局注册表。前两步能覆盖掉 80% 的问题。4.3 热插拔时的状态残留on_disable 要清理干净热插拔是这个框架的一个亮点也是隐患最多的一个功能。理论上你可以在不重启进程的情况下动态启用或禁用插件。但实现不好的插件在热插拔时会留下各种残留状态。常见症状是禁用插件后模型仍然能调用到旧工具。这个通常是因为工具反注册不彻底。插件管理器在禁用插件时会调用 on_disable但插件自身的 tool 装饰器如果只是简单把方法标记成工具框架并不知道该方法的卸载逻辑。有些框架支持在 on_disable 里调用 self._unregister_tool(...) 来反注册但很多人容易忘记。还有一个更隐蔽的残留事件订阅没有解除。比如某个插件在 after_model_call 里挂了一个回调禁用了插件但回调还被事件总线保留着。之后每次模型输出后回调依然会被执行而它内部的插件实例资源已经被释放于是一次次地抛空指针异常。这种问题极其隐蔽从框架本身看不到任何启用记录但每一次请求都会报错。我自己的处理习惯是插件里所有动态注册的东西包括工具、事件监听、临时文件句柄必须在 on_disable 中显式解除。不要依赖框架自动帮你做框架做不到那么聪明。写完 on_disable 之后至少做一次“禁用再启用”的循环测试确认功能不会因为热插拔而出现异常。4.4 循环依赖与重复加载当插件逐渐变多你可能会遇到插件 A 声明的依赖是插件 B但插件 B 又反过来依赖插件 A。框架的依赖解析器一般能检测到循环依赖并抛出一个错误。如果是自己实现的简单加载器可能就看不出来表现为启动时崩溃或者插件顺序随机。解决办法很简单重新设计插件边界。循环依赖几乎总是说明两个插件的边界切得不干净——要么是公共部分没有抽出来要么是职责划分不对。一般可以把公共逻辑挪到第三个插件或者放进内核提供的公共服务里。重复加载是另一个坑。框架在扫描插件目录时如果目录配置重复了或者一个插件被复制到两个目录就可能导致同一个工具被注册两次。注册表里会出现同名工具冲突模型调用时可能随机分发到其中一个实现另一个实现被冷落或报错。检查方式是在启动日志里搜插件名看有没有多次 load 记录。我之前就是因为目录配置里既有项目根目录又单独列了 plugins 目录导致重复加载排查了半天才发现。针对这几个典型问题我做了一个速查表方便你以后对照排查现象可能原因排查方向解决方案插件未生效、工具不存在未扫描到插件目录、manifest 插件名不一致、enable 异常启动日志、注册表记录修正目录/插件名检查 on_enable数据串会话全局变量、单例实例状态检查变量作用域、插件实例模式请求级上下文存储不写全局热插拔后旧工具仍可调用未反注册工具、事件订阅残留检查 on_disable 清理逻辑显式反注册与解绑事件启动崩溃提示循环依赖插件边界不清晰审视插件依赖图抽到公共插件或交给内核工具注册重复插件目录重复扫描启动日志 load 次数统一扫描路径配置5. 拆完之后的几点真实体会把这个框架完全拆完一遍之后我的感受是复杂的。一方面它让我重新认识了“插件化”的深度另一方面它也让我清楚了插件化在设计上要付出的代价。最直接的收益是我可以在完全不碰框架源码的情况下把一套定制化能力塞进 Agent 生命周期里的任何一个位置。无论是一段前置提示词注入还是一个全局的后置校验器插件都能优雅地接住。这种“随插随拔”的体验和我过去在主收藏堆里堆 if-else 的体验相比是截然不同两个世界。但它带来的代价也非常明显。调试难度上升了一大截。传统写法你打断点可以轻松定位代码路径插件化之后一个请求要穿过内核循环、插件加载器、多个生命周期钩子、工具注册表、事件总线任何一个环节没有日志排查起来都非常头疼。所以如果决定采用这种架构必须有配套的日志规范否则生产环境出了问题就是灾难。我的另一个体会是插件化并不是越彻底越好。这个框架的做法很聪明地平衡了“内核最小化”和“功能完备性”。你可以把所有能力都以插件形式装进来但也可以不启用任何插件框架仍然能跑基本的 Agent 循环。这种可进可退的设计才是插件化的正确打开方式。如果你正打算在公司内部搭一套 Agent 系统或者只是想深入理解 Agent 框架的设计思路我的建议是别急着抄代码先把它的插件加载流程和上下文传递链路读通。这两个地方是灵魂。读完你自然会明白为什么“插件化”这三个字本来就不该被做成一个看起来像是额外功能的东西它本来就该是整个框架的骨架。这个框架拆完了笔记倒是写了满满十几页。如果让我给这套设计打个分我会给到近乎满分的表现。剩下的几分留给我自己踩过的那些坑也算值了。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询