skip to content
WNLee's Blog

大语言模型应用层学习笔记

/ 42 min read

大语言模型应用快速发展,但是这个过程是接地气的。当我们沉下心来会发觉是以往计算机各领域的重新应用,...

大语言模型应用层学习笔记

前言

经过快速的发展,人工智能已经不再遥不可及,就在我们的身边触手可及。特别是2026年,特殊的年份。喜欢看国外科幻片的人一定对人工智能很熟悉。一幕幕神奇的场面,或已经发生,或在不久的将来得以实现。大语言模型应用是人工智能的一个分支。揭开大语音模型应用神秘的面纱,发现技术的发展是一步一个脚印。大语言模型应用实现接地气,在各领域的知识上进行构建。让人觉得更加亲近。

大语言模型本质是基于海量文本数据训练的自回归 Transformer,通过学习词语间的概率分布,依据上文预测下一个最合理的 token,从而生成连贯文本。大语言模型应用有个专有名称是 Agent。Agent 是具备感知、规划、调用工具并循环迭代,自主完成复杂目标的智能体。模型底座、工具、知识库等工具构建大语言模型应用。

Image
  1. 大语言模型领域的名词概念
名称中文解释场景
基础概念
LLM(Large Language Model)大语言模型基于 Transformer 架构、海量文本预训练的生成式 AI 模型,能理解、生成人类自然语言聊天机器人、文案生成、代码编写、问答系统、文档总结
ReAct(Reason + Act)思考 - 行动智能体范式交替执行思考 (Reason)、调用工具行动 (Act),循环迭代获取信息再回答,解决复杂推理任务需要多轮搜索、多工具调用的复杂问答、数学计算、数据调研 Agent
OODA观察 - 判断 - 决策 - 行动循环Agent 决策循环框架:Observe (观察环境)→Orient (分析信息)→Decide (制定计划)→Act (执行动作),循环迭代自主规划类 Agent、自动化运维机器人、长期多步骤复杂任务智能体
Reasoning逻辑推理模型链式思考、逻辑推导、计算、因果分析,解决复杂、多步骤问题(CoT 核心)数学解题、逻辑分析、复杂问题拆解、法律 / 医疗专业问答
Planning任务规划Agent 自主拆解复杂目标,生成分步执行计划,按顺序调度工具与子任务项目自动化、长流程办公任务、多工具协同复杂任务
Agent Loop智能体自主思考循环Agent 核心执行逻辑:接收任务→思考→调用工具→更新记忆→再次判断,直到任务完成一切自主智能体、自动化工作流、多步骤复杂任务处理
Multi-Agent多智能体协同系统多个独立 Agent 分工、通信、协作完成超复杂综合任务(一个负责搜索、一个编码、一个审核)AI 研发团队模拟、完整自动化业务流水线、复杂项目自主执行系统
SubAgent子智能体 / 附属智能体主 Agent 拆分出的专项小 Agent,各司其职(搜索子 Agent、代码子 Agent、总结子 Agent)大型复杂任务拆解、专项能力分离、分工协作自动化
Agent智能体指能够感知环境、自主决策并执行动作以实现特定目标的智能实体-
推理部署层
Ollama
本地大模型一键运行工具开箱即用本地 LLM 管理工具,一键拉取、运行、量化各类开源模型,提供 API 接口个人本地离线 AI、本地开发调试、客户端离线 Agent、局域网私有大模型服务
vLLM高性能大模型推理引擎基于 PagedAttention 的高吞吐推理框架,大幅提升并发、降低显存占用,做服务部署云端 API 服务、企业高并发私有模型、多模型批量推理、生产级 LLM 服务
llama.cpp纯 C/C++ 跨平台轻量化推理库无 GPU 依赖,CPU / 移动端可运行量化 GGUF 模型,极致轻量化本地推理底层库低配置电脑离线推理、嵌入式 / 手机端 AI、极简 CLI 模型程序、本地轻量化 Agent 底层
OpenClaw
开源工具调用专用基座模型
专门优化 Function Calling、多工具调用的开源 LLM,擅长复杂工具链式调用离线本地 Agent、私有工具自动化、无联网本地函数调用场景
Hermes AgentHermes 开源对话大模型Mistral/Llama 基座微调的对话模型,强指令遵循、Function Calling、多轮对话能力本地离线聊天机器人、轻量化私有问答、本地智能体基座
基础能力层
SSE(Server-Sent Events)服务器推送事件单向长连接流式传输协议,服务端持续分段输出文本,前端实时逐字展示 AI 输出。网页对话实时打字效果、流式问答、日志实时推送、大模型输出分片返回
Stream流式推理 / 分片输出模型不等待完整结果生成,逐 Token 分段返回文本,降低首字延迟在线聊天页面、语音实时转写、实时代码补全、长文本边生成边展示
LLM API大模型服务接口标准化 HTTP 接口,程序通过网络请求调用云端 / 本地大模型推理服务应用接入 AI、前后端 AI 功能、第三方 AI 工具集成、多服务统一调用入口
ASR(Automatic Speech Recognition)自动语音识别语音转文字,将人声音频识别为文本,输入给 LLM语音对话 AI、实时会议转写、语音助手、音频文档解析
VAD(Voice Activity Detection)语音活动检测识别音频中人声起止,区分静音与人声片段,节省推理算力实时语音通话 AI、低功耗语音设备、语音对话打断检测
TTS(Text To Speech)文本转语音将 LLM 输出文字合成自然人声音频语音播报、AI 数字人、语音对话机器人、有声文档朗读
KV Cache(Key-Value Cache)键值缓存,LLM 核心推理优化技术缓存上下文 Transformer 注意力计算 KV 向量,避免重复计算历史 Token,大幅加速生成所有在线 LLM 服务、长对话场景、高并发推理、超长上下文文档问答
工具技能层
Tool Use工具调用能力(广义 Function Calling)模型主动调用外部工具、接口、脚本扩展自身能力,弥补静态知识库局限联网、文件、数据库、计算器、第三方 API 接入 AI
Function Calling工具 / 函数调用能力
大模型识别用户需求后,自动格式化参数调用外部自定义工具、接口,获取实时数据再整合回答联网搜索、查数据库、调用计算器、操作本地文件、API 接口自动化
MCP(Model Context Protocol)模型上下文协议标准化协议,统一工具、本地文件、数据库与大模型的上下文交互规范,实现模型跨资源调用本地 Agent、客户端 AI 工具、IDE 智能助手、多数据源对接大模型
A2A(Agent to Agent)智能体到智能体协议
ANP(Agent Network Protocol)智能体网络协议
AG-UI(Agent-User Interface Protocol)智能体-用户交互协议
DID(Decentralized Identifier)去中心化标识符
SkillsAI 技能集封装好的可复用工具函数集合,Agent 可按需加载启用(搜索、文件读写、计算、绘图都属于 Skill)模块化 Agent 开发、按需扩展 AI 能力、企业定制私有工具库
SDD (Spec-Driven Development)规则驱动开发
专为AI Agent 自动编码、大模型工程落地诞生的标准化开发范式,是解决「Vibe Coding(氛围感即兴编码)」缺陷的核心方法论AI 代码智能体、多 Agent 协作系统、长期复杂项目迭代、Agent 工具开发、大模型工程交付流水线
其他
Human-in-the-loop人工介入
名称中文解释场景
知识库
RAG(Retrieval‑Augmented Generation)检索增强生成从外部知识库检索相关资料,再把检索内容 + 用户问题一起交给大模型生成答案。解决大模型幻觉、知识过时、私有数据无法访问三大痛点企业知识库问答、文档助手、客服机器人、内部资料查询
Chunking分块 / 切片把长文档切成小段文本。策略:固定长度切块、语义切块、标题分层切块
Metadata元数据文档来源、页码、时间、标签,用于溯源、过滤检索结果
Embedding嵌入模型把文本转换成高维向量text‑embedding、BGE、m3e
Vector DB向量库 / 向量数据库存储、快速搜索向量Chroma、FAISS、Milvus、Pinecone、Qdrant
Index向量索引向量库构建的加速检索结构IVF‑Flat、HNSW
-Query 向量化用户问题同样转为向量
-相似度检索 / 语义搜索
向量库匹配最相似的文本块,Top‑K 取出 K 条结果
Rerank重排序用 Reranker 模型对初次检索结果二次打分,提升精度;解决语义检索召回不准
Dense稠密检索
BM25(Best Matching 25)最佳匹配 25,经典词法稀疏检索算法
Hybrid Search混合检索向量检索 + 关键词检索 (BM25),结果合并,召回效果更强
Retrieval Filter检索过滤通过元数据限定文档范围,缩小检索域
Jev(TypeSafe AI Jev)Jev 判定模型,System One 判定模型
Context Window上下文窗口拼接检索片段 + 用户 Query + 系统提示词合并送入 LLM
Context Compression上下文压缩精简检索出来的冗余内容,节省 token
Citation引用溯源 / 引用标注输出答案附带来源页码、文档名,可校验真实性
Agentic‑RAG智能体检索Agent 自主判断是否需要检索、循环检索、迭代追问,RAG 是 Agent 最常用工具

本地部署模型

以 ollama 为例

  1. 下载并安装 ollama

  2. 通过 ollama 下载模型

    ### 下载模型,以 qwen3.5 9b 为例,模型未下载的情况下 ollama 会先下载模型
    ollama run qwen3.5:9b
  3. 判断模型是否安装成功

    ### 列举大模型信息
    ollama show qwen3.5:9b

展示结果如下,表示模型安装成功

Image
  1. 运行 qwen3.5:9b 模型

    ### 运行大模型
    ollama run qwen3.5:9b
  • 与大模型对话

    • 输入问题
    Image
    • 回答问题
    Image
  1. 使用 ollama cli 运行 llm 的时候,ollama 会运行一套 llm api,基于这一点我们可以用这套 api 做一些验证
  • 验证 llm api 正常运行

    Image
  • 验证 SSE 能力

    • 输入
    Image
    • 输出
    Image
  • 验证 Stream 能力

    • 输入
    Image
    • 输出
    Image
  • 验证 tool_call 能力

    • 输入
    Image
    • 输出
    Image
  1. 替换模型,使用生图模型生图
  • 输入
Image
  • 输出
Image

搭建模型工作流

使用 dify 平台作为载体进行工作流搭建。选择 dify 的原因是 dify 面向技术人员专业化表达、可视化操作可以先屏蔽技术细节快速实现效果。

搭建一个工作流

搭建一个《说一个笑话》的工作流

Image

输入:小明

结果:

Image

输入:杨梅梅

结果:

Image

输入:李雷

结果:

Image

构建一个知识库

构建知识库并搭建一个人工智能领域问答机器人。使用模型拆解用户问题,然后到《人工智能概论》书籍搜集答案,最终交由大模型汇总回复

Image Image Image

Agent 工程

Image

Harness Engineering

  • 中文释义:驾驭工程 / 基座工程

  • 详细定义:指为大模型智能体(Agent)构建运行环境、约束机制、验证闭环与反馈系统的系统工程实践

  1. Harness Engineering 演变

    Harness Engineering 不是凭空提出而是从 Prompt Engineering、Context Engineering 中逐步演进而来。Harness 的翻译是马具/驾驭,意思是通过约束让大模型可以正确的运行。不再是精心设计提示词、构建上下文资料,而是通过设计一套规范来驾驭大模型。

Image

Prompt Engineering:

  • 中文释义:提示词工程

  • 详细定义:针对单次模型调用,设计、组织、调优指令文本(System Prompt、用户指令、Few‑shot 示例、输出格式约束),引导大模型输出符合预期结果的技术学科

Context Engineering:

  • 中文释义:上下文工程

  • 详细定义:大模型每一轮推理时,筛选、组装、维护进入上下文窗口的全部 Token 信息集合;决定「哪些信息在此时应该交给模型,哪些应该丢弃」,管理模型完整信息运行环境的工程实践

  1. SDD (Spec-Driven Development)

    SDD 是 Harness Engineering 在编码场景的实现,常见的 SDD 有 OpenSpec、Spec-kit。SDD 会在开始执行任务之前与用户先制定规范,然后再让模型执行。

Loop Engineering

  • 中文释义:循环 / 单智能体闭环工程

  • 详细定义:聚焦单个 Agent 自主迭代闭环:规划→执行→观测校验→纠错重试→判断终止,核心解决 AI 自我反思、自动纠错、任务自推进,代表框架 ReAct、Self-Refine

Image

Loop 工程类型:

  1. 回合制循环,由用户提示触发。

智能体在一个回合内收集上下文、执行操作并检查结果,然后由人工审核输出并撰写下一个提示。当需求仍在形成阶段,且每次输出都会影响下一次提问内容时,使用此模式。

  1. 目标导向循环,由包含成功标准和预算限制的 /goal 命令触发,例如“将首页Lighthouse评分提升至90分,最多尝试5次”。

当智能体尝试停止时,评估模型会判断目标是否达成,若未完成则将其重新投入工作。当结果可衡量,但执行路径无需人工干预时使用此方式。

  1. 基于时间的循环,由定时器触发。

定时器触发后,代理执行固定指令,例如“检查 PR,修复 CI”,然后等待下一次触发。/loop 命令在本地运行,而 /schedule 则将其移至云端,确保笔记本电脑关闭后任务仍能继续。 适用于事先已知任务内容、仅需重复执行时间安排的周期性工作。

  1. 主动式循环,由事件或计划触发,且无需人工在场。

一个例行程序监控某个通道,当有事项需要处理时便启动工作流。该工作流依次运行分类代理、修复代理以及评审员,后者以对抗性方式评估工作成果,直到任务完成。适用于持续性职责场景,即无法预知具体输入内容,但可以确定会有任务到来的情况。

每种类型都比前一种多移交一项任务:

  • 轮次制:两项任务均由人类承担

  • 目标制:自动化检查环节

  • 时间制:自动化触发机制

  • 主动式:同时实现自动化,并在运行时动态决定工作流结构

Graph Engineering

  • 中文释义:图 / 多智能体拓扑工程

  • 详细定义:用显式状态图(节点 + 边 + 全局状态) 管控多 Agent、并行分支、人工审批、子流程嵌套,把流程拓扑固化为可执行状态机,代表框架 LangGraph、MetaGPT

Image

Graph Engineering 以上图为例,把 Agent 任务抽象成状态图,节点是角色 Agent,边代表流转逻辑,共享状态存储全部上下文信息。相比于 Loop Engineering 是单智能体仅有一条边指向自己,Graph Engineering 是多智能体的相互协作。

节点(Node)类型(不同角色):

  • Researcher 调研 Agent

负责搜集信息、整理素材,输出调研笔记,写入共享状态。

  • Writer 写作 Agent

读取调研笔记,产出初稿 draft;收到反馈后负责修订内容。

  • Reviewer 评审 Agent

对产出的草稿做审核、校验,输出评审意见 verdict。

主流智能体实现

Openclaw

Openclaw 的推广引起了一阵 AI 潮流,领养小龙虾成为一个时髦。小龙虾是一个个人智能助手。用户通过任一终端与网关连接,与大模型对话完成各项任务。Openclaw 主要包含网关架构、渠道管理、Agent Runtime、会话管理、Memory 管理、skills管理等模块。实现一套插件机制提供能力扩展。

整体设计

运行流程图

Image

OpenClaw 架构图

Image

渠道及渠道实现

Channel协议/库特性
WhatsAppBaileys(WhatsApp Web 协议)QR 登录、媒体支持、群组提及网关
TelegramgrammY(Bot API)流式传输、Webhook 支持
Discorddiscord.js原生命令、DM 策略
SlackBoltDM 配对策略、频道白名单
Signalsignal-cli需本地安装 signal-cli
iMessageimsg CLI仅 macOS
Google ChatChat API扩展渠道
Matrix、Teams扩展插件社区支持

网关架构

网关架构使用的是 Websocket 协议实现端到端的长连接。采用了命令模式、分发器、中间件等设计模式。

Image
  • 请求上下文
// Context ==> Request State
interface RequestContext {
  requestId: string;

  connectionId: string;

  user?: User;

  signal: AbortSignal;

  logger: Logger;

  metadata: {
    ip?: string;
    userAgent?: string;
  };

  services: {
    room: RoomService;
    chat: ChatService;
    user: UserService;
  };
}

// ConnectionState
interface ConnectionState {
  connectionId: string;

  userId?: string;

  rooms: Set<string>;

  subscriptions: Set<string>;
}

// GlobalState
interface GlobalState {
  rooms: Map<string, Room>;

  connections: Map<string, Connection>;

  sessions: Map<string, Session>;
}
  • 路由/分发器
// 路由
interface Route<T = unknown, R = unknown> {
  auth?: boolean;
  permission?: string;

  validate?: (
    params: unknown
  ) => T;

  handler: (
    ctx: Context,
    params: T
  ) => Promise<R>;
}

// 触发器
class Dispatcher {
  private routes = new Map<
    string,
    Route
  >();

  register<T, R>(
    method: string,
    route: Route<T, R>
  ) {
    if (this.routes.has(method)) {
      throw new Error(
        `Duplicate method: ${method}`
      );
    }

    this.routes.set(method, route);
  }

  async dispatch(
    ctx: Context,
    request: Request
  ) {
    const route =
      this.routes.get(request.method);

      if (!route) {
        throw new MethodNotFoundError();
      }
    
      if (route.auth && !ctx.user) {
        throw new UnauthorizedError();
      }
    
  if (route.permission) {
    checkPermission(
      ctx.user,
      route.permission
    );
  }
    
    const params = route.validate
      ? route.validate(request.params)
      : request.params;

    return route.handler(ctx, params);
  }
}
  • 调用示例
// 事件注册
dispatcher.register(
  "chat.send",
  {
    auth: true,

    permission: "chat:write",

    validate: params => {
      if (
        !params ||
        typeof params !== "object"
      ) {
        throw new Error(
          "Invalid params"
        );
      }

      return params;
    },

    handler: async (ctx, params) => {
      return chatService.send(
        ctx.user!.id,
        params
      );
    }
  }
);

// 时间监听,事件触发器
ws.on("message", async raw => {
  try {
    const request = JSON.parse(raw.toString());

    const result = await dispatcher.dispatch(
      ctx,
      request
    );

    ws.send(JSON.stringify({
      id: request.id,
      success: true,
      result
    }));
  } catch (error) {
    ws.send(JSON.stringify({
      id: request.id,
      success: false,
      error: {
        code: "INTERNAL_ERROR",
        message: error.message
      }
    }));
  }
});

插件机制

插件机制实现了插件发现、插件加载、插件注册等能力。避免过度加载插件。插件支持渠道、工具、模型等几个模块的契约。支持用户针对这些模块扩展。

Image
  • 基础类型
// plugin/types.ts
export type PluginStatus =
  | "discovered"
  | "registering"
  | "registered"
  | "starting"
  | "active"
  | "stopping"
  | "stopped"
  | "failed";

export interface PluginManifest {
  id: string;
  name: string;
  version: string;
  description?: string;

  /**
   * 插件依赖。
   * 这里约定依赖项必须先于当前插件完成注册。
   */
  dependencies?: string[];
}

export interface PluginContext {
  pluginId: string;

  /**
   * 一次业务执行的关联 ID。
   * 不同请求不应该共享同一个可变 Context。
   */
  requestId?: string;

  /**
   * 只读的请求级元数据。
   */
  metadata?: Readonly<Record<string, unknown>>;

  /**
   * 日志接口由宿主提供。
   */
  logger: Logger;
}

export interface Logger {
  info(message: string, data?: unknown): void;
  warn(message: string, data?: unknown): void;
  error(message: string, error?: unknown): void;
}

export interface PluginService {
  id: string;

  start(context: PluginContext): void | Promise<void>;

  stop?(): void | Promise<void>;
}

export interface PluginTool {
  name: string;
  description: string;

  execute(
    params: unknown,
    context: PluginContext,
  ): unknown | Promise<unknown>;
}

export interface PluginHook {
  event: string;

  handler(
    payload: unknown,
    context: PluginContext,
  ): void | Promise<void>;
}

export interface PluginAPI {
  readonly pluginId: string;
  readonly context: PluginContext;

  registerTool(tool: PluginTool): void;

  registerHook(hook: PluginHook): void;

  registerService(service: PluginService): void;
}

export interface PluginDefinition {
  manifest: PluginManifest;

  register(api: PluginAPI): void | Promise<void>;
}
  • 插件注册能力
// plugin registry
import type {
  PluginHook,
  PluginManifest,
  PluginService,
  PluginStatus,
  PluginTool,
} from "./types.js";

export interface PluginRecord {
  manifest: PluginManifest;
  status: PluginStatus;
  error?: unknown;
}

export interface Owned<T> {
  pluginId: string;
  value: T;
}

export class PluginRegistry {
  private plugins = new Map<string, PluginRecord>();

  private tools = new Map<string, Owned<PluginTool>>();

  private hooks: Owned<PluginHook>[] = [];

  private services = new Map<string, Owned<PluginService>>();

  addPlugin(record: PluginRecord): void {
    const id = record.manifest.id;

    if (this.plugins.has(id)) {
      throw new Error(`Plugin already exists: ${id}`);
    }

    this.plugins.set(id, record);
  }

  getPlugin(id: string): PluginRecord | undefined {
    return this.plugins.get(id);
  }

  setStatus(
    id: string,
    status: PluginStatus,
    error?: unknown,
  ): void {
    const record = this.plugins.get(id);

    if (!record) {
      throw new Error(`Unknown plugin: ${id}`);
    }

    record.status = status;
    record.error = error;
  }

  registerTool(pluginId: string, tool: PluginTool): void {
    if (this.tools.has(tool.name)) {
      throw new Error(`Tool already exists: ${tool.name}`);
    }

    this.tools.set(tool.name, {
      pluginId,
      value: tool,
    });
  }

  registerHook(pluginId: string, hook: PluginHook): void {
    this.hooks.push({
      pluginId,
      value: hook,
    });
  }

  registerService(
    pluginId: string,
    service: PluginService,
  ): void {
    if (this.services.has(service.id)) {
      throw new Error(`Service already exists: ${service.id}`);
    }

    this.services.set(service.id, {
      pluginId,
      value: service,
    });
  }

  getTool(name: string): Owned<PluginTool> | undefined {
    return this.tools.get(name);
  }

  getHooks(event: string): Owned<PluginHook>[] {
    return this.hooks.filter(
      (item) => item.value.event === event,
    );
  }

  getServices(pluginId: string): Owned<PluginService>[] {
    return [...this.services.values()].filter(
      (item) => item.pluginId === pluginId,
    );
  }

  /**
   * 撤销某个插件拥有的所有注册项。
   */
  removePluginContributions(pluginId: string): void {
    for (const [name, item] of this.tools) {
      if (item.pluginId === pluginId) {
        this.tools.delete(name);
      }
    }

    for (let i = this.hooks.length - 1; i >= 0; i--) {
      if (this.hooks[i].pluginId === pluginId) {
        this.hooks.splice(i, 1);
      }
    }

    for (const [id, item] of this.services) {
      if (item.pluginId === pluginId) {
        this.services.delete(id);
      }
    }
  }

  removePluginRecord(pluginId: string): void {
    this.plugins.delete(pluginId);
  }
}
  • 插件 API
// plugin/plugin-api.ts
import type {
  Logger,
  PluginAPI,
  PluginContext,
  PluginHook,
  PluginService,
  PluginTool,
} from "./types.js";

import { PluginRegistry } from "./registry.js";

export class PluginAPIImpl implements PluginAPI {
  constructor(
    readonly pluginId: string,
    readonly context: PluginContext,
    private readonly registry: PluginRegistry,
    private readonly onRegistration: (
      rollback: () => void,
    ) => void,
  ) {}

  registerTool(tool: PluginTool): void {
    this.registry.registerTool(this.pluginId, tool);

    this.onRegistration(() => {
      // 具体回滚由注册表按插件所有权完成。
      this.registry.removePluginContributions(this.pluginId);
    });
  }

  registerHook(hook: PluginHook): void {
    this.registry.registerHook(this.pluginId, hook);

    this.onRegistration(() => {
      this.registry.removePluginContributions(this.pluginId);
    });
  }

  registerService(service: PluginService): void {
    this.registry.registerService(this.pluginId, service);

    this.onRegistration(() => {
      this.registry.removePluginContributions(this.pluginId);
    });
  }
}
  • 插件管理
// plugin/plugin-manager.ts
import type {
  Logger,
  PluginContext,
  PluginDefinition,
  PluginManifest,
  PluginService,
} from "./types.js";

import { PluginRegistry } from "./registry.js";
import { PluginAPIImpl } from "./plugin-api.js";

interface PluginInstance {
  definition: PluginDefinition;
  context: PluginContext;
  services: PluginService[];
}

export class PluginManager {
  readonly registry = new PluginRegistry();

  private instances = new Map<string, PluginInstance>();

  constructor(private readonly logger: Logger) {}

  async load(definition: PluginDefinition): Promise<void> {
    const { manifest } = definition;
    const { id } = manifest;

    if (this.instances.has(id)) {
      throw new Error(`Plugin already loaded: ${id}`);
    }

    this.assertDependencies(manifest);

    const context: PluginContext = {
      pluginId: id,
      logger: this.logger,
    };

    this.registry.addPlugin({
      manifest,
      status: "registering",
    });

    const api = new PluginAPIImpl(
      id,
      context,
      this.registry,
    );

    try {
      // 第一阶段:注册能力。
      await definition.register(api);

      this.registry.setStatus(id, "registered");

      const instance: PluginInstance = {
        definition,
        context,
        services: this.registry
          .getServices(id)
          .map((item) => item.value),
      };

      this.instances.set(id, instance);

      // 第二阶段:启动后台服务。
      this.registry.setStatus(id, "starting");

      for (const service of instance.services) {
        await service.start(context);
      }

      this.registry.setStatus(id, "active");

      this.logger.info(`Plugin loaded: ${id}`);
    } catch (error) {
      this.registry.setStatus(id, "failed", error);

      await this.rollback(id);

      this.logger.error(
        `Plugin load failed: ${id}`,
        error,
      );

      throw error;
    }
  }

  async unload(id: string): Promise<void> {
    const instance = this.instances.get(id);

    if (!instance) {
      return;
    }

    this.registry.setStatus(id, "stopping");

    const errors: unknown[] = [];

    // 先停止后台服务,避免卸载后继续产生工作。
    for (const service of [...instance.services].reverse()) {
      try {
        await service.stop?.();
      } catch (error) {
        errors.push(error);
      }
    }

    // 无论停止是否成功,都撤销该插件的注册项。
    this.registry.removePluginContributions(id);
    this.instances.delete(id);

    if (errors.length > 0) {
      const error = new AggregateError(
        errors,
        `Plugin cleanup failed: ${id}`,
      );

      this.registry.setStatus(id, "failed", error);

      throw error;
    }

    this.registry.setStatus(id, "stopped");

    this.logger.info(`Plugin unloaded: ${id}`);
  }

  private async rollback(id: string): Promise<void> {
    const instance = this.instances.get(id);

    if (instance) {
      for (const service of [...instance.services].reverse()) {
        try {
          await service.stop?.();
        } catch (error) {
          this.logger.error(
            `Plugin rollback cleanup failed: ${id}`,
            error,
          );
        }
      }
    }

    this.instances.delete(id);
    this.registry.removePluginContributions(id);
  }

  private assertDependencies(
    manifest: PluginManifest,
  ): void {
    for (const dependency of manifest.dependencies ?? []) {
      const record = this.registry.getPlugin(dependency);

      if (!record || record.status !== "active") {
        throw new Error(
          `Plugin ${manifest.id} requires active dependency: ${dependency}`,
        );
      }
    }
  }
}
  • 工具注册
// plugin/tool-executor.ts
import type { PluginContext } from "./types.js";
import { PluginRegistry } from "./registry.js";

export class ToolExecutor {
  constructor(
    private readonly registry: PluginRegistry,
    private readonly logger: PluginContext["logger"],
  ) {}

  async execute(
    name: string,
    params: unknown,
    requestId?: string,
  ): Promise<unknown> {
    const registered = this.registry.getTool(name);

    if (!registered) {
      throw new Error(`Tool not found: ${name}`);
    }

    const plugin = this.registry.getPlugin(
      registered.pluginId,
    );

    if (!plugin || plugin.status !== "active") {
      throw new Error(
        `Plugin is not active: ${registered.pluginId}`,
      );
    }

    const context: PluginContext = {
      pluginId: registered.pluginId,
      requestId,
      logger: this.logger,
    };

    return registered.value.execute(params, context);
  }
}
  • Hook bus
// plugin/hook-bus.ts
import type {
  PluginContext,
  PluginHook,
} from "./types.js";

import { PluginRegistry } from "./registry.js";

export class HookBus {
  constructor(
    private readonly registry: PluginRegistry,
    private readonly logger: PluginContext["logger"],
  ) {}

  async emit(
    event: string,
    payload: unknown,
  ): Promise<void> {
    const hooks = this.registry.getHooks(event);

    for (const registered of hooks) {
      const plugin = this.registry.getPlugin(
        registered.pluginId,
      );

      if (!plugin || plugin.status !== "active") {
        continue;
      }

      const context: PluginContext = {
        pluginId: registered.pluginId,
        logger: this.logger,
      };

      try {
        await registered.value.handler(
          payload,
          context,
        );
      } catch (error) {
        // 单个 Hook 失败不阻断其他订阅者。
        this.logger.error(
          `Hook failed: ${event}, plugin: ${registered.pluginId}`,
          error,
        );
      }
    }
  }
}
  • 插件示例
// plugins/logger/index.ts
import type { PluginDefinition } from "../../plugin/types.js";

export const loggerPlugin: PluginDefinition = {
  manifest: {
    id: "logger",
    name: "Logger Plugin",
    version: "1.0.0",
  },

  register(api) {
    api.registerHook({
      event: "app.started",

      handler(_payload, context) {
        context.logger.info(
          "Application started",
          {
            pluginId: context.pluginId,
          },
        );
      },
    });
  },
};

Agent Turn 实现

Agent Turn 指的使用一轮 Agent 调用,可能是一次大模型调用,也可能是多次。因为设计思路的差异,Agent Turn 会有不同,但是与 LLM 的交互不同 Agent 间会有差异。

Image
  • 类型定义
// contracts.ts
import type {
  Message,
  ModelResponse,
  ToolCall,
  TurnContext,
} from "./types";

export interface ModelAdapter {
  generate(input: {
    messages: Message[];
    tools: ToolDefinition[];
    context: TurnContext;
  }): Promise<ModelResponse>;
}

export interface ToolDefinition {
  name: string;
  description: string;

  execute(
    args: Record<string, unknown>,
    context: TurnContext,
  ): Promise<string>;
}

export interface ToolRegistry {
  get(name: string): ToolDefinition | undefined;

  list(): ToolDefinition[];
}

export interface SessionStore {
  getMessages(sessionId: string): Promise<Message[]>;

  appendMessages(
    sessionId: string,
    messages: Message[],
  ): Promise<void>;
}

export interface EventBus {
  emit(event: import("./types").TurnEvent): void;
}

export interface AgentHook {
  beforeTurn?(context: TurnContext): Promise<void>;

  beforeToolCall?(
    call: ToolCall,
    context: TurnContext,
  ): Promise<void>;

  afterToolCall?(
    call: ToolCall,
    result: string,
    context: TurnContext,
  ): Promise<void>;

  afterTurn?(
    result: string,
    context: TurnContext,
  ): Promise<void>;
}
  • 控制队列
// keyed-queue.ts
export class KeyedQueue {
  private tails = new Map<string, Promise<void>>();

  async run<T>(
    key: string,
    task: () => Promise<T>,
  ): Promise<T> {
    const previous =
      this.tails.get(key) ?? Promise.resolve();

    let release!: () => void;

    const current = new Promise<void>((resolve) => {
      release = resolve;
    });

    this.tails.set(key, current);

    // 前一个任务无论成功还是失败,都允许后一个任务继续。
    await previous;

    try {
      return await task();
    } finally {
      release();

      // 只清理当前队列尾节点,避免误删后续任务。
      if (this.tails.get(key) === current) {
        this.tails.delete(key);
      }
    }
  }
}
  • Agent Runtime 实现
// 在 Message 类型中增加可选字段
export interface Message {
  role: "system" | "user" | "assistant" | "tool";
  content: string;

  toolCallId?: string;
  toolName?: string;

  toolCalls?: ToolCall[];
}

// agent-runtime.ts
import { randomUUID } from "node:crypto";

import type {
  AgentHook,
  ModelAdapter,
  SessionStore,
  ToolRegistry,
} from "./contracts";

import type {
  Message,
  ToolCall,
  TurnContext,
  TurnEvent,
  TurnResult,
} from "./types";

import { KeyedQueue } from "./keyed-queue";
import { HookManager } from "./hook-manager";

interface AgentRuntimeOptions {
  model: ModelAdapter;
  sessions: SessionStore;
  tools: ToolRegistry;

  emit: (event: TurnEvent) => void;

  maxIterations?: number;
  timeoutMs?: number;
}

export class AgentRuntime {
  private queue = new KeyedQueue();
  private hooks = new HookManager();

  private readonly maxIterations: number;
  private readonly timeoutMs: number;

  constructor(
    private readonly options: AgentRuntimeOptions,
  ) {
    this.maxIterations =
      options.maxIterations ?? 10;

    this.timeoutMs =
      options.timeoutMs ?? 60_000;
  }

  registerHook(hook: AgentHook): void {
    this.hooks.register(hook);
  }

  async run(input: {
    sessionId: string;
    agentId: string;
    message: string;
    signal?: AbortSignal;
  }): Promise<TurnResult> {
    return this.queue.run(
      input.sessionId,
      async () => this.runTurn(input),
    );
  }

  private async runTurn(input: {
    sessionId: string;
    agentId: string;
    message: string;
    signal?: AbortSignal;
  }): Promise<TurnResult> {
    const runId = randomUUID();

    const controller = new AbortController();

    const context: TurnContext = {
      runId,
      sessionId: input.sessionId,
      agentId: input.agentId,
      signal: controller.signal,
      metadata: {},
      startedAt: Date.now(),
    };

    const abortFromCaller = () => {
      controller.abort(input.signal?.reason);
    };

    if (input.signal?.aborted) {
      abortFromCaller();
    } else {
      input.signal?.addEventListener(
        "abort",
        abortFromCaller,
        { once: true },
      );
    }

    const timeout = setTimeout(() => {
      controller.abort(
        new Error("Agent turn timed out"),
      );
    }, this.timeoutMs);

    const emit = (
      event: Omit<
        TurnEvent,
        "runId" | "sessionId" | "timestamp"
      >,
    ) => {
      this.options.emit({
        ...event,
        runId,
        sessionId: input.sessionId,
        timestamp: Date.now(),
      });
    };

    let terminalPublished = false;

    const publishTerminal = (
      phase: "end" | "error" | "aborted",
    ) => {
      if (terminalPublished) return;

      terminalPublished = true;

      emit({
        type: "lifecycle",
        phase,
      });
    };

    const assertNotAborted = () => {
      if (controller.signal.aborted) {
        throw controller.signal.reason ??
          new Error("Agent turn aborted");
      }
    };

    try {
      emit({
        type: "lifecycle",
        phase: "start",
      });

      assertNotAborted();

      await this.hooks.beforeTurn(context);

      assertNotAborted();

      const messages =
        await this.options.sessions.getMessages(
          input.sessionId,
        );

      messages.push({
        role: "user",
        content: input.message,
      });

      const systemMessage: Message = {
        role: "system",
        content:
          "You are a helpful assistant. " +
          "Use available tools when necessary.",
      };

      const prompt = [
        systemMessage,
        ...messages,
      ];

      let finalContent = "";

      for (
        let iteration = 0;
        iteration < this.maxIterations;
        iteration++
      ) {
        assertNotAborted();

        const response =
          await this.options.model.generate({
            messages: prompt,
            tools: this.options.tools.list(),
            context,
          });

        assertNotAborted();

        // 模型给出最终回复,结束本轮。
        if (response.type === "final") {
          finalContent = response.content;

          prompt.push({
            role: "assistant",
            content: finalContent,
          });

          emit({
            type: "assistant",
            content: finalContent,
          });

          break;
        }

        // 模型请求调用工具。
        prompt.push({
          role: "assistant",
          content: "",
          toolCalls: response.calls,
        });

        for (const call of response.calls) {
          assertNotAborted();

          const result = await this.executeTool(
            call,
            context,
            assertNotAborted,
            emit,
          );

          prompt.push({
            role: "tool",
            content: result,
            toolCallId: call.id,
            toolName: call.name,
          });
        }

        // 下一轮将工具结果一起交给模型。
      }

      assertNotAborted();

      if (!finalContent) {
        throw new Error(
          `Agent exceeded ${this.maxIterations} iterations ` +
          "without producing a final answer",
        );
      }

      emit({
        type: "lifecycle",
        phase: "finishing",
      });

      // 保存本轮新增的消息,而非把全部历史重复写入。
      const newMessages = prompt.slice(messages.length);

      await this.options.sessions.appendMessages(
        input.sessionId,
        newMessages,
      );

      await this.hooks.afterTurn(
        finalContent,
        context,
      );

      publishTerminal("end");

      return {
        runId,
        sessionId: input.sessionId,
        status: "completed",
        content: finalContent,
      };
    } catch (error) {
      const aborted = controller.signal.aborted;

      publishTerminal(
        aborted ? "aborted" : "error",
      );

      throw error;
    } finally {
      clearTimeout(timeout);

      input.signal?.removeEventListener(
        "abort",
        abortFromCaller,
      );
    }
  }

  private async executeTool(
    call: ToolCall,
    context: TurnContext,
    assertNotAborted: () => void,
    emit: (
      event: Omit<
        TurnEvent,
        "runId" | "sessionId" | "timestamp"
      >,
    ) => void,
  ): Promise<string> {
    assertNotAborted();

    const tool = this.options.tools.get(call.name);

    if (!tool) {
      throw new Error(
        `Tool not found: ${call.name}`,
      );
    }

    await this.hooks.beforeToolCall(
      call,
      context,
    );

    assertNotAborted();

    emit({
      type: "tool",
      phase: "start",
      toolCallId: call.id,
      toolName: call.name,
    });

    try {
      const result = await tool.execute(
        call.arguments,
        context,
      );

      assertNotAborted();

      await this.hooks.afterToolCall(
        call,
        result,
        context,
      );

      emit({
        type: "tool",
        phase: "end",
        toolCallId: call.id,
        toolName: call.name,
        content: result,
      });

      return result;
    } catch (error) {
      emit({
        type: "tool",
        phase: "error",
        toolCallId: call.id,
        toolName: call.name,
      });

      throw error;
    }
  }
}

Deepseek-Harness

Deepseek Harness 的口号是 “Everything is a Plugin”。我认为 Deepseek Harness 是可以实现自进化的。想象一下:一个乐高智能机器人,他具备普通智能机器人的能力外还具备生产乐高模块能力,生产的乐高模块可以用于改造自己。

整体设计

Deepseek Harness 整体架构

Image

Fiber 生命周期

Image

Fiber 实现

  • 基础类型
type Cleanup = () => void | Promise<void>;

type Effect = () => Cleanup | void | Promise<Cleanup | void>;

type FiberState =
  | "PENDING"
  | "LOADING"
  | "ACTIVE"
  | "FAILED"
  | "UNLOADING"
  | "DISPOSED";

interface Plugin {
  name: string;

  apply(ctx: PluginContext): void | Promise<void>;
}

interface EffectRecord {
  label: string;
  cleanup: Cleanup;
  disposed: boolean;
}
  • 插件上下文
class PluginContext {
  constructor(
    readonly fiber: Fiber,
  ) {}

  effect(
    execute: Effect,
    label = "anonymous",
  ): () => Promise<void> {
    return this.fiber.effect(execute, label);
  }

  plugin(plugin: Plugin): Fiber {
    return this.fiber.mount(plugin);
  }

  get state(): FiberState {
    return this.fiber.state;
  }
}
  • Fiber 实现
class Fiber {
  state: FiberState = "PENDING";

  readonly ctx = new PluginContext(this);

  private effects: EffectRecord[] = [];

  private children: Fiber[] = [];

  private disposePromise?: Promise<void>;

  constructor(
    readonly plugin: Plugin,
    readonly parent?: Fiber,
  ) {}

  async load(): Promise<void> {
    if (this.state !== "PENDING") {
      throw new Error(
        `Cannot load fiber in state ${this.state}`,
      );
    }

    this.state = "LOADING";

    try {
      await this.plugin.apply(this.ctx);

      this.state = "ACTIVE";
    } catch (error) {
      this.state = "FAILED";

      try {
        await this.disposeResources();
      } catch (cleanupError) {
        throw new AggregateError(
          [error, cleanupError],
          `Plugin ${this.plugin.name} failed to load and cleanup`,
        );
      }

      throw error;
    }
  }

  effect(
    execute: Effect,
    label = "anonymous",
  ): () => Promise<void> {
    if (
      this.state !== "LOADING" &&
      this.state !== "ACTIVE"
    ) {
      throw new Error(
        `Cannot register effect in state ${this.state}`,
      );
    }

    let record: EffectRecord | undefined;

    const dispose = async (): Promise<void> => {
      if (!record || record.disposed) {
        return;
      }

      record.disposed = true;

      await record.cleanup();
    };

    const register = async () => {
      const cleanup = await execute();

      record = {
        label,
        cleanup: cleanup ?? (() => {}),
        disposed: false,
      };

      this.effects.push(record);
    };

    // execute may be asynchronous, so registration must
    // finish before the plugin starts using the resource.
    //
    // For this compact implementation, async registration
    // is exposed separately below.
    throw new Error(
      "Use effectAsync() for asynchronous effect acquisition",
    );
  }

  async effectAsync(
    execute: Effect,
    label = "anonymous",
  ): Promise<() => Promise<void>> {
    if (
      this.state !== "LOADING" &&
      this.state !== "ACTIVE"
    ) {
      throw new Error(
        `Cannot register effect in state ${this.state}`,
      );
    }

    const cleanup = await execute();

    const record: EffectRecord = {
      label,
      cleanup: cleanup ?? (() => {}),
      disposed: false,
    };

    this.effects.push(record);

    return async () => {
      if (record.disposed) {
        return;
      }

      record.disposed = true;

      await record.cleanup();
    };
  }

  mount(plugin: Plugin): Fiber {
    if (
      this.state !== "LOADING" &&
      this.state !== "ACTIVE"
    ) {
      throw new Error(
        `Cannot mount child in state ${this.state}`,
      );
    }

    const child = new Fiber(plugin, this);

    this.children.push(child);

    return child;
  }

  async startChild(child: Fiber): Promise<void> {
    if (!this.children.includes(child)) {
      throw new Error("Child does not belong to this fiber");
    }

    await child.load();
  }

  dispose(): Promise<void> {
    if (this.disposePromise) {
      return this.disposePromise;
    }

    this.disposePromise = this.disposeInternal();

    return this.disposePromise;
  }

  private async disposeInternal(): Promise<void> {
    if (this.state === "DISPOSED") {
      return;
    }

    this.state = "UNLOADING";

    try {
      await this.disposeResources();
    } finally {
      this.state = "DISPOSED";
    }
  }

  private async disposeResources(): Promise<void> {
    const errors: unknown[] = [];

    // Dispose children in reverse mounting order.
    for (const child of [...this.children].reverse()) {
      try {
        await child.dispose();
      } catch (error) {
        errors.push(error);
      }
    }

    // Dispose effects in reverse registration order.
    for (const effect of [...this.effects].reverse()) {
      if (effect.disposed) {
        continue;
      }

      effect.disposed = true;

      try {
        await effect.cleanup();
      } catch (error) {
        errors.push(
          new Error(
            `Effect "${effect.label}" cleanup failed`,
            { cause: error },
          ),
        );
      }
    }

    this.children = [];
    this.effects = [];

    if (errors.length > 0) {
      throw new AggregateError(
        errors,
        `Failed to dispose plugin ${this.plugin.name}`,
      );
    }
  }
}
  • 使用示例
const loggerPlugin: Plugin = {
  name: "logger",

  async apply(ctx) {
    console.log("logger loading");

    await ctx.fiber.effectAsync(
      () => {
        console.log("logger resource acquired");

        return () => {
          console.log("logger resource released");
        };
      },
      "logger-resource",
    );
  },
};

Agent Turn 实现

  • 基础类型
type MessageRole =
  | "system"
  | "user"
  | "assistant"
  | "tool";

interface ToolCall {
  id: string;

  name: string;

  arguments: unknown;
}

interface Message {
  role: MessageRole;

  content: string;

  toolCalls?: ToolCall[];

  toolCallId?: string;
}
interface ModelResponse {
  content: string;

  toolCalls: ToolCall[];
}

interface LLM {
  generate(
    messages: Message[],
    tools: ToolDefinition[],
    signal?: AbortSignal,
  ): Promise<ModelResponse>;
}
  • 工具上下文
interface ToolContext {
  turnId: string;

  step: number;

  signal?: AbortSignal;
}

interface ToolDefinition {
  name: string;

  description: string;

  execute(
    args: unknown,
    context: ToolContext,
  ): Promise<unknown>;
}

class ToolRegistry {
  private tools = new Map<
    string,
    ToolDefinition
  >();

  register(tool: ToolDefinition): () => void {
    if (this.tools.has(tool.name)) {
      throw new Error(
        `Tool already exists: ${tool.name}`,
      );
    }

    this.tools.set(tool.name, tool);

    return () => {
      if (this.tools.get(tool.name) === tool) {
        this.tools.delete(tool.name);
      }
    };
  }

  get(name: string): ToolDefinition {
    const tool = this.tools.get(name);

    if (!tool) {
      throw new Error(`Unknown tool: ${name}`);
    }

    return tool;
  }

  list(): ToolDefinition[] {
    return [...this.tools.values()];
  }
}
  • Session log 实现
type SessionEvent =
  | {
      type: "turn/start";
      turnId: string;
    }
  | {
      type: "step/start";
      turnId: string;
      step: number;
    }
  | {
      type: "user/message";
      turnId: string;
      message: string;
    }
  | {
      type: "assistant/message";
      turnId: string;
      content: string;
    }
  | {
      type: "tool/call";
      turnId: string;
      toolCall: ToolCall;
    }
  | {
      type: "tool/result";
      turnId: string;
      toolCallId: string;
      result: unknown;
    }
  | {
      type: "step/end";
      turnId: string;
      step: number;
    }
  | {
      type: "turn/end";
      turnId: string;
      reason: "completed" | "max_steps" | "aborted";
    };

class SessionLog {
  private events: SessionEvent[] = [];

  append(event: SessionEvent): void {
    this.events.push(event);
  }

  read(): readonly SessionEvent[] {
    return this.events;
  }
}
  • Agent Runtime 实现
interface TurnOptions {
  maxSteps?: number;

  signal?: AbortSignal;
}

interface TurnResult {
  turnId: string;

  output: string;

  steps: number;

  reason: "completed" | "max_steps" | "aborted";
}

class AgentRuntime {
  constructor(
    private llm: LLM,
    private tools: ToolRegistry,
    private session: SessionLog,
  ) {}

  async runTurn(
    input: string,
    options: TurnOptions = {},
  ): Promise<TurnResult> {
    const turnId = crypto.randomUUID();

    const maxSteps = options.maxSteps ?? 10;

    const signal = options.signal;

    const messages: Message[] = [
      {
        role: "system",
        content:
          "You are a helpful agent. Use tools when necessary.",
      },
      {
        role: "user",
        content: input,
      },
    ];

    this.session.append({
      type: "turn/start",
      turnId,
    });

    this.session.append({
      type: "user/message",
      turnId,
      message: input,
    });

    let output = "";

    let completedSteps = 0;

    let reason: TurnResult["reason"] = "max_steps";

    try {
      for (
        let step = 1;
        step <= maxSteps;
        step++
      ) {
        signal?.throwIfAborted();

        this.session.append({
          type: "step/start",
          turnId,
          step,
        });

        const response = await this.llm.generate(
          messages,
          this.tools.list(),
          signal,
        );

        signal?.throwIfAborted();

        const assistantMessage: Message = {
          role: "assistant",
          content: response.content,
          toolCalls: response.toolCalls,
        };

        messages.push(assistantMessage);

        output = response.content;

        this.session.append({
          type: "assistant/message",
          turnId,
          content: response.content,
        });

        // No tool calls means the model has finished.
        if (response.toolCalls.length === 0) {
          completedSteps = step;

          this.session.append({
            type: "step/end",
            turnId,
            step,
          });

          reason = "completed";

          break;
        }

        // Execute tool calls produced by the model.
        for (const toolCall of response.toolCalls) {
          signal?.throwIfAborted();

          this.session.append({
            type: "tool/call",
            turnId,
            toolCall,
          });

          let result: unknown;

          try {
            const tool = this.tools.get(
              toolCall.name,
            );

            result = await tool.execute(
              toolCall.arguments,
              {
                turnId,
                step,
                signal,
              },
            );
          } catch (error) {
            // Convert tool errors into model-visible results.
            result = {
              error:
                error instanceof Error
                  ? error.message
                  : String(error),
            };
          }

          this.session.append({
            type: "tool/result",
            turnId,
            toolCallId: toolCall.id,
            result,
          });

          messages.push({
            role: "tool",
            content: JSON.stringify(result),
            toolCallId: toolCall.id,
          });
        }

        this.session.append({
          type: "step/end",
          turnId,
          step,
        });

        completedSteps = step;
      }
    } catch (error) {
      if (signal?.aborted) {
        reason = "aborted";
      } else {
        throw error;
      }
    } finally {
      this.session.append({
        type: "turn/end",
        turnId,
        reason,
      });
    }

    return {
      turnId,
      output,
      steps: completedSteps,
      reason,
    };
  }
}

参考资料