LobeHub Agent Runtime Hooks 生命周期机制解析:从 execAgent 注册到 HookDispatcher 分发的完整指南
【免费下载链接】lobehub🤯 LobeHub is your Chief Agent Operator, organizing your agents into 7×24 operations by hiring, scheduling, and reporting on your entire AI team.项目地址: https://gitcode.com/GitHub_Trending/lo/lobehub
导读
LobeHub 将分散的 Agent 编排为 7×24 持续运行的操作体系,而Agent Runtime Hooks(Agent 运行时生命周期钩子)正是这套体系的"可观测 + 可拦截"基础设施:它允许你在 Agent 单次执行的各个关键节点(步骤开始/结束、工具调用前后、人工审批、上下文压缩、子 Agent 调用)挂载回调,实现成本与 Token 监控、结果拦截、工具 Mock、子 Agent 调度追踪等能力。读完本文,你将掌握 LobeHub 钩子系统的完整骨架——16 种钩子如何按 5 大类组织、事件负载(Event Payload)包含哪些字段、本地内存模式与生产 Webhook 模式如何切换,以及如何用真实代码在自己的execAgent({ hooks })调用中落地一套评测 / 观测 Hook。
本文对应的权威来源是仓库内
.agents/skills/agent-runtime-hooks/SKILL.md(供 Agent 使用的运行时钩子技能文档),其关键实现均可在packages/agent-runtime与apps/server下找到一一对应的源码。
一、什么是 Agent Runtime Hooks:为"Agent 执行"量身定制的观察点
Agent 的一次execAgent执行(也被称为一次 operation)并不是一个黑盒:在内部,它会经历「思考(call_llm)→ 调用工具(call_tool)→ 请求人工审批(request_human_approve)→ 压缩上下文(compress_context)→ 调用子 Agent(callAgent)」等多个子流程。LobeHub 把每个子流程的关键转折点抽象为一组Hook 事件类型(Hook Type),并通过HookDispatcher(分发器)统一负责注册与回调。
理解这套机制要抓住三个关键词:
- Per-operation(按操作作用域):Hooks 不是全局的,而是通过
execAgent({ hooks })针对某一次 Agent 操作临时注册,操作结束即自动清理; - Observable + Interceptable(可观测且可拦截):绝大多数钩子只做观察(fire-and-forget),唯独
beforeToolCall支持通过event.mock()返回假结果,从而跳过真实工具执行; - 双模式分发:本地模式(local mode)直接以内存方式调用 JS 处理函数;生产模式(queue/webhook 模式)则把钩子序列化并通过 HTTP POST / QStash 投递给外部 Webhook 端点。
整体拓扑可以直观地用 SKILL.md 中的分层图表达:
execAgent({ hooks }) │ ├─ beforeStep ──────────── Before each step executes │ │ │ ├─ [call_llm] LLM inference │ │ │ ├─ [call_tool] │ │ ├─ beforeToolCall ── Before tool executes (supports mocking) │ │ ├─ (tool execution) │ │ ├─ afterToolCall ─── After tool completes (observation only) │ │ └─ onToolCallError ─ Tool threw an exception │ │ │ ├─ [request_human_approve] │ │ ├─ beforeHumanIntervention ── Before agent pauses │ │ ├─ afterHumanIntervention ─── After approve/reject + resume │ │ └─ onStopByHumanIntervention ── User rejected, agent halted │ │ │ ├─ [compress_context] │ │ ├─ beforeCompact ──── Before compression starts │ │ ├─ afterCompact ───── After compression completes │ │ └─ onCompactError ─── Compression failed │ │ │ ├─ [callAgent] (via execSubAgentTask) │ │ ├─ beforeCallAgent ── Before sub-agent starts │ │ ├─ afterCallAgent ─── After sub-agent completes │ │ └─ onCallAgentError ── Sub-agent failed │ │ │ └─ afterStep ──────────── After step completes │ ├─ (next step...) │ ├─ onComplete ───────────── Operation reaches terminal state └─ onError ──────────────── Error during execution二、五大类 16 种钩子总览
SKILL.md 明确给出:钩子系统共16 种 Hook 类型,划分到 5 大类别。在packages/agent-runtime/src/types/hooks.ts中,这 16 种被定义为一个字符串联合类型AgentHookType:
| 类别 | 钩子类型 | 触发时机 | 主要事件负载类型 |
|---|---|---|---|
| 步骤级 Step Level | beforeStep/afterStep | 每一步执行前 / 每步完成后 | AgentHookEvent |
| 步骤级 Step Level | onComplete | 操作到达终态(done/error/interrupted/max_steps/cost_limit) | AgentHookEvent |
| 步骤级 Step Level | onError | 执行过程中出错 | AgentHookEvent |
| 工具调用级 Tool Call | beforeToolCall | 工具执行前,支持event.mock()拦截 | ToolCallHookEvent |
| 工具调用级 Tool Call | afterToolCall | 工具执行完成后(仅观察) | AfterToolCallHookEvent |
| 工具调用级 Tool Call | onToolCallError | 工具抛出异常(catch 块,而非仅success=false) | ToolCallErrorHookEvent |
| 人工干预级 Human | beforeHumanIntervention | Agent 暂停等待审批前 | BeforeHumanInterventionHookEvent |
| 人工干预级 Human | afterHumanIntervention | 审批通过/拒绝并恢复后 | AfterHumanInterventionHookEvent |
| 人工干预级 Human | onStopByHumanIntervention | 用户拒绝、Agent 被中止 | StopByHumanInterventionHookEvent |
| 上下文压缩级 Compact | beforeCompact/afterCompact | 压缩开始前 / 完成后 | BeforeCompactHookEvent/AfterCompactHookEvent |
| 上下文压缩级 Compact | onCompactError | 压缩失败 | CompactErrorHookEvent |
| 子 Agent 级 CallAgent | beforeCallAgent/afterCallAgent | 调用子 Agent 前 / 子 Agent 完成后 | BeforeCallAgentHookEvent/AfterCallAgentHookEvent |
| 子 Agent 级 CallAgent | onCallAgentError | 子 Agent 失败 | CallAgentErrorHookEvent |
说明:5 大类别即 Step Level(4 种)+ Tool Call(3 种)+ Human Intervention(3 种)+ Context Compression(3 种)+ Sub-Agent CallAgent(3 种),合计正好 16 种,与
AgentHookType定义逐一对应。
所有事件负载(Event)类型都集中在 packages/agent-runtime/src/types/hooks.ts,服务端则通过 apps/server/src/services/agentRuntime/hooks/types.ts 对这些纯数据类型做 re-export,并追加服务端专有的AgentHook、AgentHookWebhook、SerializedHook等注册/序列化类型。事件类型放在共享的@lobechat/agent-runtime包中,注册与分发机制放在 server 层——这样保证了前端/服务端/Worker 等多端共享同一套事件契约。
三、注册到分发:一次 Hook 调用的完整生命周期
3.1 最小注册示例
SKILL.md 给出了最核心的用法——把 Hook 数组作为hooks参数传给execAgent:
const hooks: AgentHook[] = [ { id: 'my-hook', type: 'afterStep', handler: async (event) => { /* ... */ } }, ]; await aiAgentService.execAgent({ agentId, prompt, hooks });内部流程可拆解为三步(注释即 SKILL.md 给出的内部行为说明):
// Internally: hookDispatcher.register(operationId, hooks) // Cleanup: hookDispatcher.unregister(operationId)即:execAgent在内部获得一个 operationId,HookDispatcher.register按operationId → AgentHook[]的关系把钩子存入内存 Map;执行结束后由unregister(operationId)清理,避免钩子泄漏到下一次操作。
3.2 AgentHook 的四个字段
在 apps/server/src/services/agentRuntime/hooks/types.ts 中,AgentHook被定义为:
| 字段 | 类型 | 含义 |
|---|---|---|
id | string | 唯一标识,用于日志、调试与幂等判断 |
type | AgentHookType | 挂在哪个生命周期点 |
handler | (event: AgentHookEvent) => Promise<void> | 本地模式的回调函数,进程内直接调用 |
webhook(可选) | AgentHookWebhook | 生产模式下的投递配置;不配置则钩子只工作在本地模式 |
其中handler统一返回Promise<void>,这是「所有钩子都是 fire-and-forget、错误不影响主流程」这一设计约束的直接体现(详见第六节)。
3.3 HookDispatcher:注册 / 分发 / 查询 / 清理的中央枢纽
实现位于 apps/server/src/services/agentRuntime/hooks/HookDispatcher.ts,核心是一个模块级单例hookDispatcher,内部维护Map<string, AgentHook[]>(operationId → hooks)。对外暴露以下方法:
register(operationId, hooks):把钩子追加进对应 operation 的列表,并打 debug 日志Registered N hooks: type:id, ...;unregister(operationId):删除该 operation 的全部钩子(Scoped per operation的落地实现);dispatch(operationId, type, event, serializedHooks?):按事件类型过滤出钩子并逐个执行/投递;dispatchBeforeToolCall(operationId, event):专门处理beforeToolCall,返回{ isMocked: true, result } | null;hasHooks(operationId)/hasHook(operationId, hookId)/getSerializedHooks(operationId):查询与序列化辅助。
从apps/server/src/services/agentRuntime/AgentRuntimeService.ts的源码可以印证这套调用链的存在:该服务在 operation 开始时调用hookDispatcher.register(operationId, hooks)(对应源码中的 register 调用点),随后在步骤、完成等节点调用hookDispatcher.dispatch(...),最终统一执行hookDispatcher.unregister(operationId)做收尾清理。步骤执行器则集中在 apps/server/src/modules/AgentRuntime/RuntimeExecutors.ts——它通过createAgentRuntimeExecutors(buildHost(ctx))生成工具、压缩、人工干预等各类指令执行器,钩子的dispatchBeforeToolCall返回值(mock 结果或 null)正是在这里被消费,决定后续是走真实工具还是直接采用假结果。
3.4 生产模式:Webhook 序列化投递
本地模式的 handler 是 JS 函数,无法跨进程/跨机器传递,因此 LobeHub 提供了 Webhook 通道:
- 序列化:
getSerializedHooks()只保留id / type / webhook(handler 函数无法序列化),服务端将序列化结果持久化到AgentState.metadata._hooks以及topic.metadata.runningOperation.hooks; - 投递:
deliverWebhook()支持两种 delivery——fetch(普通 HTTP POST)与qstash(通过 QStash 可靠投递),且支持fallback: 'fetch' | 'none'策略。若配置了fallback: 'none',投递失败会被当作关键错误抛出CriticalHookDeliveryError,因为这类 Webhook 承载控制流(例如子 Agent 的 resume 桥接),丢失会导致消费方永久挂起; - 事件字段裁剪:
eventFields?: (keyof AgentHookEvent)[]用于控制 payload 只包含指定字段;无论是否裁剪,finalState都会被排除(它是本地模式才有的深状态,不序列化进 Webhook payload)。
// AgentHookWebhook 的核心结构(见 hooks/types.ts) interface AgentHookWebhook { url: string; delivery?: 'fetch' | 'qstash'; // 默认 'fetch' fallback?: 'fetch' | 'none'; // qstash 失败时回退策略 eventFields?: (keyof AgentHookEvent)[]; // 默认包含全部可序列化字段 body?: Record<string, unknown>; // 附加自定义 metadata }Webhook 模式不支持beforeToolCall的 Mock(Mock 依赖进程内共享的 handler 闭包),这一点也符合下一节要讲的「Local only」设计约束。
四、16 个钩子的事件负载逐个拆解
4.1 步骤级:beforeStep/afterStep/onComplete/onError
四个步骤级钩子的event统一为AgentHookEvent。它是一份非常"厚"的通用事件,在 packages/agent-runtime/src/types/hooks.ts 中定义了数十个可选字段,按用途可分成几组:
- 标识字段:
agentId、userId、operationId、topicId; - 步骤定位:
stepIndex、stepLabel(如图谱节点名)、stepType(call_llm/call_tool)、steps; - 内容字段:
content(LLM 文本输出)、reasoning(思考内容)、lastAssistantContent、lastLLMContent(上一步的 LLM 内容,用于工具执行期间展示上下文); - 统计字段:
cost/stepCost/totalCost、duration/elapsedMs(自操作开始以来的毫秒数)、executionTimeMs(本步耗时)、llmCalls、toolCalls/totalToolCalls、totalInputTokens/totalOutputTokens/totalTokens; - 决策字段:
shouldContinue、thinking(下一步是否 LLM 思考)、toolsCalling(LLM 决定调用的工具)、toolsResult(工具执行结果); - 终态字段(主要在
onComplete上):status(done | error | interrupted | waiting_for_human)、reason(done | error | interrupted | max_steps | cost_limit); - 错误字段(主要在
onError上):errorMessage、errorDetail、errorType(稳定错误码,如NoAvailableProvider、InvalidProviderAPIKey)、errorAttribution(来自 model-runtime 错误分类:user | provider | harness | system,告诉消费方"该由谁修复"); - 附件字段:
attachments(HookEventAttachment[],onComplete时从最终助手消息的多模态 content 中提取的图片/文件/音视频附件,供机器人回拨转发给 IM 平台); - 元数据:
metadata(调用方自定义,来自 webhook.body)。
各钩子职责:
beforeStep:每一步执行前触发。可结合shouldContinue之类字段做步进控制的前置判断(注意shouldContinue的语义要以具体消费方为准,SDK 未强制要求返回);afterStep:每步完成后触发,携带本步内容、工具调用、费用与累计 token 等完整快照,是做日志、成本核算、评测采样的主力钩子;onComplete:操作到达终态时触发一次,reason会区分done / error / interrupted / max_steps / cost_limit,并携带attachments与累计totalCost;onError:出错时触发,消费方应优先 switcherrorType稳定错误码,而不是对自由文本的errorMessage做模式匹配。
4.2 工具调用级:beforeToolCall(Mock)/afterToolCall/onToolCallError
beforeToolCall—— 唯一的可拦截钩子
event为ToolCallHookEvent:
{ (identifier, apiName, args, callIndex, stepIndex, operationId, mock); }字段含义:identifier(工具标识,例如插件工具xxx.web-search)、apiName(工具具体 API 名)、args(调用参数对象)、callIndex(单步内第几次工具调用)、stepIndex(第几步)、operationId。
mock是回调函数,签名在类型定义中为mock: (result: ToolRunResult) => boolean——返回 false 表示更早的钩子已经抢占了 mock 名额。它让调用方在真实工具执行前注入假结果:
event.mock({ content: '{"error":"rate limited"}' });分发层面,dispatchBeforeToolCall只会对本地内存钩子生效,返回{ isMocked: true, result } | null;执行器拿到非空结果即可跳过真实工具调用。SKILL.md 特别提醒:Mock 只在本地模式(内存钩子)有效,Webhook 模式不支持 Mock。
afterToolCall—— 纯观察
event为AfterToolCallHookEvent,在工具完成之后触发:
{ (identifier, apiName, args, callIndex, content, success, mocked, executionTimeMs, stepIndex); }其中mocked标记本次结果是否为 mock 所得,success表示工具是否成功,content为工具返回文本,executionTimeMs为真实执行耗时。
onToolCallError—— 异常专属
event为ToolCallErrorHookEvent:
{ (identifier, apiName, args, callIndex, error, stepIndex); }它和afterToolCall的区别值得强调:afterToolCall即使success=false也属于"正常返回"路径,而onToolCallError只在**工具真正抛出异常(catch 块)**时触发,字段中的error为异常信息字符串。
4.3 人工干预级:审批三连
当 Agent 需要执行高风险工具时会进入审批流程,相关钩子负责把"暂停/恢复/中止"的边界暴露给外部:
// beforeHumanIntervention:暂停前 { operationId, stepIndex, pendingTools: [{ identifier, apiName }] } // afterHumanIntervention:用户 approve/reject 并恢复后 { operationId, action: 'approve' | 'reject' | 'rejectAndContinue', toolCallId?, rejectionReason? } // onStopByHumanIntervention:用户拒绝导致 Agent 中止 { operationId, toolCallId?, rejectionReason? }三者语义递进:beforeHumanIntervention给你一次"预通知"(此时能拿到所有等待审批的工具清单pendingTools);用户做出决定后,afterHumanIntervention会携带action(approve通过 /reject拒绝 /rejectAndContinue拒绝但让 Agent 继续)以及被拒工具的toolCallId和rejectionReason;而一旦用户拒绝导致整个 Agent 被中止(halted),则只有onStopByHumanIntervention会收到通知。审批的 resume/reject 动作由apps/server/src/services/agentRuntime/AgentRuntimeService.ts承接(SKILL.md 的 Key Files 表中标注其为 "Step hooks + HumanIntervention resume/reject")。
4.4 上下文压缩级:beforeCompact/afterCompact/onCompactError
长对话逼近窗口上限时会触发上下文压缩(compact),压缩相关事件设计为"压缩前测量、压缩后报告、失败时报警":
// beforeCompact:压缩开始前 { (operationId, stepIndex, messageCount, tokenCount) } // afterCompact:压缩完成后 { (operationId, stepIndex, groupId, messagesBefore, messagesAfter, summary) } // onCompactError:压缩失败 { (operationId, stepIndex, tokenCount, error) }beforeCompact让你在压缩前记录当时的消息量与 token 量;afterCompact给出压缩组的groupId、压缩前后的消息数messagesBefore/messagesAfter以及压缩摘要summary,适合做上下文预算的统计归因;onCompactError在压缩抛出异常时触发,携带当时的tokenCount便于评估"是不是因为上下文太大而失败"。
4.5 子 Agent 级(CallAgent):在父操作上监听子任务
当主 Agent 通过execSubAgentTask调度子 Agent 时,相关钩子统一在父 operation 上触发(而不是子 operation 上):
// beforeCallAgent:子 Agent 启动前 { (operationId, agentId, instruction) } // afterCallAgent:子 Agent 完成后 { (operationId, agentId, subOperationId, threadId, success) } // onCallAgentError:子 Agent 失败 { (operationId, agentId, error) }其中operationId是父操作 ID,subOperationId是子 Agent 对应的新 operation ID,threadId为子 Agent 使用的会话线程。SKILL.md 特意给出一个使用前提:CallAgent 钩子要求ExecSubAgentTaskParams中携带parentOperationId,否则无法把子任务关联回父操作的钩子作用域。子 Agent 调度路径的实现位于 apps/server/src/services/aiAgent/subAgentRuns.ts,CallAgent 钩子的 dispatch 正是由它负责。
五、设计要点解读:这套机制的"底层契约"
SKILL.md 的 Design Notes 值得逐条展开,因为它们决定了你在实践中能做什么、不能做什么:
- Fire-and-forget(即发即弃):所有 handler 都返回
Promise<void>,单次回调抛错不会影响主执行流。实现上HookDispatcher.dispatch对每个本地 hook 都包了 try/catch,错误只进 debug 日志,注释直接写明 "Hook errors should NOT affect main execution flow"。唯一例外是fallback: 'none'的关键 Webhook——它会以CriticalHookDeliveryError的形式让队列执行失败,以便重试而不是让消费方永久失联; - 唯一的拦截点:常规钩子只是"看",
beforeToolCall是唯一能通过event.mock()篡改结果的能力。实现中dispatchBeforeToolCall()串行执行所有beforeToolCall钩子,一旦某个 handler 调用了mock(result),后续钩子不再执行并直接返回 mock 结果; - 同类型钩子按注册顺序串行执行:
register把钩子追加到数组尾部,dispatch按数组顺序 await,因此先注册的钩子优先拿到事件,也优先抢占 mock 名额; - Mock 仅本地有效:
beforeToolCall的 mock 依赖进程内 handler,Webhook 模式只能投递观察事件(类型层面也区分了ToolCallHookEvent与不带mock的BeforeToolCallObservationEvent); - 按操作作用域自动清理:
operationId → hooks的 Map 设计 + 结束时unregister(operationId),保证钩子不会跨操作泄漏、也不会因长驻服务而膨胀; - Sandbox / MCP 没有独立钩子:沙箱、MCP 工具最终都走
executeTool这条统一出口,因此beforeToolCall/afterToolCall天然覆盖它们,只需用event.identifier做过滤即可区分具体工具来源。
六、实战:在 execAgent 中注册一套可复用的 Hook
结合AgentHook结构、事件字段与上述设计约束,一个同时覆盖「评测 + 成本 + Mock」的完整示例大致如下:
import { aiAgentService } from '@/server/services/aiAgent'; const hooks = [ // 1) 每步结束后累计成本与 token(观测) { id: 'cost-observer', type: 'afterStep', handler: async (event) => { if (event.totalCost !== undefined) { console.log( `[step ${event.stepIndex}] stepCost=${event.stepCost} ` + `total=${event.totalCost} tokens=${event.totalTokens}`, ); } }, }, // 2) 终态上报(评测用例通过 onComplete 断言 reason / attachments) { id: 'completion-reporter', type: 'onComplete', handler: async (event) => { console.log(`operation ${event.operationId} finished: reason=${event.reason}`); // event.attachments 里是 onComplete 才填充的图片/文件附件 }, }, // 3) 工具失败归因(errorType 稳定码优于自由文本) { id: 'tool-error-tracer', type: 'onToolCallError', handler: async (event) => { console.error(`tool ${event.identifier}.${event.apiName} threw:`, event.error); }, }, // 4) 工具 Mock:让网络工具在测试中返回假结果(仅本地模式生效) { id: 'web-search-mock', type: 'beforeToolCall', handler: async (event) => { if (event.identifier.endsWith('web-search')) { event.mock({ content: '{"results":[]}' }); } }, }, ] satisfies AgentHook[]; await aiAgentService.execAgent({ agentId: 'my-agent', prompt: '...', hooks, });使用注意点:
hooks的静态类型就是AgentHook[](见 apps/server/src/services/agentRuntime/hooks/types.ts),satisfies AgentHook[]可以在编译期拦截"类型名写错 / handler 签名不符";- 当只关心某一个 operation 时,
hooks参数即传即用,无需手动清理——unregister由服务在收尾时统一执行; - 生产(队列)模式下,若要让远端也能收到事件,需要给对应的
AgentHook补上webhook配置(url/delivery: 'fetch' | 'qstash'/fallback/eventFields),且这类钩子无法执行 mock。
七、结合源码与测试的调用链验证
以下证据链可以帮助你在阅读仓库时快速定位:
- 事件类型契约:packages/agent-runtime/src/types/hooks.ts 定义了
AgentHookType联合类型、AgentHookEvent及各事件负载接口,顶部注释明确说明"纯数据类型的钩子生命周期事件,注册/分发机制位于 server 层"; - 服务端类型与再导出:apps/server/src/services/agentRuntime/hooks/types.ts re-export 上述所有事件类型,并定义
AgentHook、AgentHookWebhook、SerializedHook(Redis 持久化用)三个服务端类型; - 中央分发器:apps/server/src/services/agentRuntime/hooks/HookDispatcher.ts 是注册与分发的唯一枢纽,同时包含
deliverWebhook(fetch/QStash 双通道)与CriticalHookDeliveryError; - 执行器接线:apps/server/src/modules/AgentRuntime/RuntimeExecutors.ts 生成 Tool / Compact / HumanIntervention 指令执行器,消费
dispatchBeforeToolCall的 mock 结果; - 服务入口:
apps/server/src/services/agentRuntime/AgentRuntimeService.ts中可直接检索到hookDispatcher.register(...)、hookDispatcher.dispatch(...)、hookDispatcher.unregister(...)等调用点,覆盖步骤级钩子与人工干预的 resume/reject 流程; - 子 Agent 链路:apps/server/src/services/aiAgent/subAgentRuns.ts 负责 CallAgent 钩子分发,要求
parentOperationId存在于ExecSubAgentTaskParams; - 测试佐证:
apps/server/src/services/agentRuntime/AgentRuntimeService.test.ts与apps/server/src/services/agentRuntime/hooks/__tests__/HookDispatcher.test.ts覆盖了注册、按类型分发、beforeToolCallmock 返回等行为,可作为阅读时的行为级参考。
结语
Agent Runtime Hooks 是 LobeHub 把"Agent 长时运行"做成可治理产品的关键一环:用 16 个按语义分组的钩子点覆盖了一次 operation 从步骤、工具、审批、压缩到子 Agent 的全部生命周期转折,用"按 operation 注册 + 结束时自动清理"的作用域模型保证安全,再用"本地内存 handler + 生产 Webhook"双通道兼顾进程内低延迟与跨进程可靠投递。对需要监控 Agent 成本、实现工具 Mock、桥接人工审批或构建 Agent 评测体系的开发者而言,本文梳理的注册方式、事件负载与设计契约可以直接作为二次开发的起点——而每一处行为都能在上述源码文件中找到落点。
【免费下载链接】lobehub🤯 LobeHub is your Chief Agent Operator, organizing your agents into 7×24 operations by hiring, scheduling, and reporting on your entire AI team.项目地址: https://gitcode.com/GitHub_Trending/lo/lobehub
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考