第 18 章

Profiles(配置档案)

按 provider 和模型打包默认配置,让 harness 自动适配不同模型行为

不修改 create_deep_agent 调用点,就能针对特定模型微调行为
内置支持:Deep Agents 内置了针对 OpenAI 和 Anthropic (Claude) 模型的 harness profile,开箱即用。

为什么需要它

你有没有在切换 LLM 模型时,发现 harness 行为全乱了?

硬编码的困境

  • 换了 GPT-5,回复太长——没有按模型调 system prompt
  • Claude 不支持 execute 工具——每个调用点都要手动排除
  • 不同模型需要不同温度——散落在代码各处
  • 想针对某模型加中间件——只能改 create_deep_agent
  • 团队各用各的模型——配置没法复用

Profile 的解法

  • 注册一个 profile,自动按模型匹配配置
  • system prompt 追加、工具排除、中间件调整一键搞定
  • provider 级别 + model 级别两级继承
  • 配置可序列化为 YAML/JSON,团队共享
  • 作为插件分发,无需手动注册
问题核心:当你的 Deep Agent 需要支持多个 provider 或多个模型时,为每个模型手动调整调用参数既脆弱又不可维护。Profile 把"针对特定模型的调整"打包成一个声明式配置,让 harness 自动适配。

认识 HarnessProfile

描述 create_deep_agent 在构造聊天模型之后应用的 prompt、工具、中间件调整

说白了,HarnessProfile 就是一张"当选用某个模型时该怎么做"的清单。它不会改模型本身,而是调整 harness 的行为——追加 prompt、隐藏工具、增删中间件、配置通用子 Agent。在 JS/TS 里,它就是一个用对象字面量描述这些调整的配置对象。

卸甲:"清单"这个比喻只是为了帮助你直观理解。实际上,HarnessProfile 是一个包含 prompt、工具、中间件等配置字段的数据结构。harness 在构造 Agent 时读取它,按合并语义叠加到执行流程中。它不是一张物理清单,而是多层配置对象的运行时合并结果。
import {
  HarnessProfile,
  registerHarnessProfile,
} from "deepagents";

// 为 openai:gpt-5.4 注册一个专属 profile
registerHarnessProfile(
  "openai:gpt-5.4",
  new HarnessProfile({
    systemPromptSuffix: "Respond in under 100 words.", // 在 system prompt 末尾追加,限制回复长度
    excludedTools: ["execute"],                        // 从工具集中移除 execute,防止该模型执行代码
    excludedMiddleware: ["SummarizationMiddleware"],   // 按名称排除中间件,阻止自动摘要介入
    generalPurposeSubagent: { enabled: false },        // 禁用通用子 Agent
  }),
);

HarnessProfile 能做什么

  • baseSystemPrompt:替换整个基础 system prompt
  • systemPromptSuffix:在 prompt 末尾追加文本
  • toolDescriptionOverrides:覆盖工具的描述文字
  • excludedTools:从工具集中移除指定工具
  • excludedMiddleware:从堆栈中剥离指定中间件
  • extraMiddleware:追加额外的中间件
  • generalPurposeSubagent:配置通用子 Agent

适用场景一览

  • 某模型回复过长 → 追加 systemPromptSuffix
  • 某模型不支持某工具 → 用 excludedTools 移除
  • 某模型不需要摘要中间件 → 用 excludedMiddleware
  • 不想用通用子 Agent → 设置 generalPurposeSubagent.enabled = false
  • 模型理解工具描述有偏差 → 覆盖描述

中间件排除规则

excludedMiddleware 接受两种形式,但有些中间件不能动

排除中间件时,你可以用中间件类精确匹配,也可以用字符串名称模糊匹配。

形式一:字符串名称

按 AgentMiddleware.name 匹配,适合内置和公共中间件。

new HarnessProfile({
  // 按中间件名称排除,适合内置和公共中间件
  excludedMiddleware: ["SummarizationMiddleware"],
});

形式二:类引用

直接传入中间件类(构造器)进行精确匹配,适合运行时代码。

import { TelemetryMiddleware } from "my-pkg/middleware.js";

new HarnessProfile({
  // 传入类本身(而非字符串),匹配精确类
  excludedMiddleware: [TelemetryMiddleware],
});
重要限制:
  • 在 excludedMiddleware 中列出 FilesystemMiddleware、SubAgentMiddleware 或内部权限中间件会抛出 Error——它们是必需的基础设施
  • 只想向模型隐藏工具而不移除中间件?用 excludedTools 代替
  • 类引用会在解析时真正 import 对应模块——仅在可信的本地代码或受信配置中使用
  • SubAgentMiddleware(以及 task 工具)仅在存在至少一个同步子 Agent 时才会被附加,因此此配置可以干净地将其排除
  • 异步子 Agent 不受影响
选择原则:能用字符串就用字符串(简单安全,且可写进 JSON 配置文件),需要精确匹配自定义中间件时才用类引用。永远不要试图排除基础设施中间件。

注册键格式

两种粒度——provider 级别覆盖所有模型,model 级别只针对特定模型

Provider 级别

裸 provider 名称如 "openai",应用于该 provider 的所有模型。

registerHarnessProfile(
  "openai",
  new HarnessProfile({
    // 所有使用 OpenAI 模型的 Agent 都会追加这条指令
    systemPromptSuffix: "Be concise.",
  }),
);

所有 OpenAI 模型(gpt-5.4、gpt-4o 等)都会追加 "Be concise."

Model 级别

完全限定的 "provider:model" 键,仅应用于特定模型。

registerHarnessProfile(
  "openai:gpt-5.4",
  new HarnessProfile({
    // 只有 gpt-5.4 会排除 execute 工具
    excludedTools: ["execute"],
  }),
);

只有 openai:gpt-5.4 会排除 execute 工具。

合并规则:provider 级别和 model 级别同时存在时会合并。未设置的 model 级别字段从 provider 级别继承;显式设置的 model 级别值会覆盖。在同一键下重新注册是合并而非替换。
没有通配符键。想在所有 provider 上应用相同配置?需要在每个 provider 键下分别注册。Profile 专为模型相关调整设计,不依赖模型的全局调整应在 create_deep_agent 调用点进行。

合并语义

当多个 profile 叠加时,每个字段的合并规则不同

理解合并语义是避免"配置被意外覆盖"的关键。下面是每个字段的具体行为:

字段合并行为举例
baseSystemPrompt 新值优先,否则继承 provider 设 "A",model 设 "B" → 结果是 "B"
systemPromptSuffix 新值优先,否则继承 provider 设 "简短",model 未设 → 继承 "简短"
toolDescriptionOverrides 对象按键合并,新值优先 两者都覆盖 "grep" → model 的描述胜出
excludedTools 数组合并去重(并集) provider 排 ["A"],model 排 ["B"] → 排 ["A", "B"]
excludedMiddleware 数组合并去重(并集) 同上,排除列表只会越来越多
extraMiddleware 按类合并,新类追加 同类实例替换,新类追加到尾部
generalPurposeSubagent 按字段合并 未设置的字段继承上层
initKwargs(provider) 对象按键合并;共享键上新值优先 provider 设 temperature=0,model 设 temperature=0.7 → model 胜出
preInit(provider) 函数链式执行:现有先运行,然后新运行 两个 preInit 都会执行,按注册顺序依次调用
initKwargsFactory(provider) 工厂链式执行,每次 resolveModel 调用时合并其输出 多个工厂的输出按键合并,新值优先
图:Provider 与 Model 配置合并语义
记忆技巧:prompt 类字段是"覆盖"(后者赢),集合类字段是"累加"(越来越多),映射类字段是"按 key 合并"(各有胜负)。

Provider 配置档案

更窄的配套 API,只管模型构造参数,不影响 harness

ProviderProfile 和 HarnessProfile 是互补关系:前者管"怎么构造模型",后者管"构造之后怎么调整 harness"。大多数场景你只需要 HarnessProfile。

import {
  ProviderProfile,
  registerProviderProfile,
} from "deepagents";

// 为 OpenAI 设置默认温度为 0
registerProviderProfile(
  "openai",
  // initKwargs 会被转发给 initChatModel,这里设置默认温度
  new ProviderProfile({ initKwargs: { temperature: 0 } }),
);

ProviderProfile 的三个字段

  • initKwargs:静态初始化参数,转发给 initChatModel
  • preInit:构造前运行的副作用(如凭据验证)
  • initKwargsFactory:运行时动态生成 kwargs(如从环境变量读 headers)

什么时候用 ProviderProfile

  • 需要为 provider 设置默认 temperature、max_tokens 等参数
  • 需要在模型构造前验证 API Key
  • 需要从环境变量动态注入配置
  • 你在打包一个 provider 集成库
注意:ProviderProfile 仅在你传递 provider:model 字符串时生效。如果你直接传预配置的聊天模型实例,ProviderProfile 不会被使用。

从配置文件加载

用 JSON(或 YAML)管理 profile,团队协作更方便

运行时 API(HarnessProfile)支持中间件实例、工厂等复杂对象,但配置文件只能存声明式数据。HarnessProfileConfig 就是为此而生的——它镜像了 HarnessProfile 的可序列化子集。

便捷之处:registerHarnessProfile 同时接受 HarnessProfile 和 HarnessProfileConfig 两种类型,因此配置文件支持的调用者不需要手动转换步骤——直接传入即可。

JSON 配置文件

// openai.json
{
  "baseSystemPrompt": "You are helpful.",
  "systemPromptSuffix": "Respond briefly.",
  "excludedTools": ["execute", "grep"],
  "excludedMiddleware": ["SummarizationMiddleware"],
  "generalPurposeSubagent": { "enabled": false }
}

JS 加载代码

import { readFileSync } from "node:fs";
import {
  HarnessProfileConfig,
  registerHarnessProfile,
} from "deepagents";

// 读取 JSON 配置并注册
const raw = readFileSync("openai.json", "utf8");
registerHarnessProfile(
  "openai",
  // HarnessProfileConfig 直接接受普通对象(JSON.parse 的输出)
  new HarnessProfileConfig(JSON.parse(raw)),
);
用 YAML 也行:JS 没有内置 YAML 解析器,但你可以 npm install yaml,把上面的 JSON.parse(raw) 换成 parse(raw)(来自 yaml 包)。配置文件里同样使用 camelCase 键名。
双向转换:HarnessProfileConfig.fromHarnessProfile(...) 可以把运行时 profile 导出回声明式形式。但注意:非空的 extraMiddleware 和匿名(未命名)的中间件类无法序列化,导出时会抛 Error。

配置文件 vs 运行时

不是所有功能都能放进 JSON——搞清楚边界

HarnessProfileConfig(可序列化)

  • prompt 文本(baseSystemPrompt、systemPromptSuffix)
  • 工具描述覆盖(toolDescriptionOverrides)
  • 排除的工具和中间件(字符串形式)
  • 通用子 Agent 配置(enabled、name 等)
  • 可以用 JSON / YAML 存储

HarnessProfile(仅运行时)

  • 中间件实例(extraMiddleware)
  • 工厂函数(initKwargsFactory)
  • 类形式的 excludedMiddleware 条目
  • 需要在 JS/TS 代码中注册
  • 不能用纯 JSON 表示
常见踩坑:当你尝试导出包含 extraMiddleware 实例或未命名的中间件类的 profile 时,fromHarnessProfile 会抛出 Error。设计 profile 时就要考虑:哪些内容放配置文件,哪些只能在代码中注册。

作为插件分发

导出 register 函数让使用者一行 import 自动注册

JS/TS 没有 Python 那种 importlib.metadata 入口点机制,deepagents 采用更直接的方式:把注册函数作为模块的副作用导出。加载顺序是:内置 → npm 包(被 import 时执行)→ 用户代码。

package.json 暴露入口

// my-deepagents-profiles/package.json
{
  "name": "my-deepagents-profiles",
  "version": "1.0.0",
  "type": "module",
  "exports": {
    ".": "./src/index.js",
    "./register": "./src/register.js"
  }
}

注册模块(src/register.js)

import {
  HarnessProfile,
  ProviderProfile,
  registerHarnessProfile,
  registerProviderProfile,
} from "deepagents";

// 用户 import 本模块时自动执行
registerHarnessProfile(
  "my_provider",
  new HarnessProfile({
    // 指示该 provider 的模型批量并发执行独立工具调用
    systemPromptSuffix:
      "Batch independent tool calls in parallel.",
  }),
);

registerProviderProfile(
  "my_provider",
  new ProviderProfile({
    initKwargs: { temperature: 0 }, // 默认温度 0,输出更确定
  }),
);

// 也可以命名导出,让用户按需手动调用
export function registerMyProvider() {
  /* 上面两段注册逻辑 */
}
使用者一行接入:import "my-deepagents-profiles/register";(副作用导入即触发注册),或显式调用 import { registerMyProvider } from "my-deepagents-profiles"; registerMyProvider();。无需修改 createDeepAgent 调用点。
加载顺序的意义:同一键下的后续注册会叠加在之前的注册之上(合并而非替换)。所以内置 profile 先注册,插件可以在此基础上追加覆盖,用户代码最后执行可以进一步定制。这是一个分层叠加的设计。

Prompt 拼装优先级

理解调用者、profile 和子 Agent 的 prompt 如何叠加

profile 的 prompt 设置和调用者的 prompt 设置各司其职:调用者在前,profile 在后,子 Agent 会重新解析。

主 Agent prompt 拼装顺序

  1. 调用者提供的 systemPrompt:始终在最前面
  2. Deep Agents 基础 prompt(或 profile 的 baseSystemPrompt 替换)
  3. Profile 的 systemPromptSuffix:始终在最后面

子 Agent prompt 规则

  • 每个子 Agent 针对自身的模型重新运行 profile 解析
  • 通用子 Agent 专用的 prompt 优先级高于 baseSystemPrompt
  • 相同的覆盖规则适用于声明式子 Agent 和自动子 Agent
图:Prompt 拼装层级
预配置模型的查找顺序:当你传预配置的聊天模型实例时,harness 按以下顺序合成键:① 精确 provider:identifier → ② 仅 identifier(当 identifier 含 : 时) → ③ 仅 provider 回退。确保你的注册键覆盖了实际使用的模型标识。

动手练习

为你的项目设计一套 profile 配置方案

假设你的团队同时使用 OpenAI 和 Anthropic 的模型,请根据以下需求设计 profile:

需求:
1. 所有 OpenAI 模型:回复简洁(不超过 200 字)
2. openai:gpt-5.4 额外排除 execute 工具
3. 所有 Anthropic 模型:排除 grep 工具,追加 systemPromptSuffix "Think step by step."
4. anthropic:claude-sonnet-4-20250514 禁用通用子 Agent

任务 1:设计注册键

  • 需要注册几个 profile?分别用什么键?
  • 哪些字段在 provider 级别设置?哪些在 model 级别?
  • 合并后每个模型实际拿到什么配置?

任务 2:选择加载方式

  • 哪些配置适合放 JSON 文件?
  • 哪些只能在 JS/TS 代码中注册?
  • 如何把配置组织成可复用的模块?
提示:先画出"provider 级别 → model 级别"的继承树,再逐个填入字段。用合并语义推算最终效果,确保不会有意外的排除或覆盖。

参考答案

一种可行的 profile 配置方案——合并语义决定了最终效果

import {
  HarnessProfile,
  registerHarnessProfile,
} from "deepagents";

// Provider 级别:所有 OpenAI 模型
registerHarnessProfile(
  "openai",
  new HarnessProfile({
    systemPromptSuffix: "Respond in under 200 words.", // 所有 OpenAI 模型追加此指令
  }),
);

// Model 级别:gpt-5.4 在 provider 基础上追加排除
registerHarnessProfile(
  "openai:gpt-5.4",
  new HarnessProfile({
    excludedTools: ["execute"], // 与 provider 级别的 excludedTools 取并集
  }),
);

// Provider 级别:所有 Anthropic 模型
registerHarnessProfile(
  "anthropic",
  new HarnessProfile({
    excludedTools: ["grep"],                     // 排除 grep 工具
    systemPromptSuffix: "Think step by step.",   // 引导模型逐步推理
  }),
);

// Model 级别:claude-sonnet-4 禁用通用子 Agent
registerHarnessProfile(
  "anthropic:claude-sonnet-4-20250514",
  new HarnessProfile({
    generalPurposeSubagent: { enabled: false },
  }),
);
验证合并结果:openai:gpt-5.4 最终会追加 "Respond in under 200 words."(继承 provider)+ 排除 execute(自身设置)。anthropic:claude-sonnet-4 最终会排除 grep(继承)+ 追加 suffix(继承)+ 禁用子 Agent(自身设置)。这正是两级继承的威力。

本章回顾

到这里,你已经掌握了 Profiles 的完整体系

你学到了什么

  • HarnessProfile:在模型构造后调整 prompt、工具、中间件和子 Agent
  • ProviderProfile:控制模型构造参数(温度、凭据、动态 kwargs)
  • 注册键:provider 级别(全模型)和 model 级别(单模型)两级继承
  • 合并语义:prompt 覆盖、集合并集、映射按键合并
  • 配置文件:YAML/JSON 存可序列化部分,HarnessProfileConfig 负责转换
  • 插件分发:通过入口点自动注册,分层叠加

后续章节预告

  • Event Streaming:监听 Agent 执行过程中的实时事件
  • Streaming:token 级流式输出和自定义数据流
  • Frontend 集成:在 Web 前端展示 Agent 状态
  • ACP 协议:Agent Communication Protocol 的使用
  • Code 模式:面向代码生成的专用配置
核心结论:Profile 是 Deep Agents 模型适配层的核心机制。它让"针对特定模型的调整"从散落的 if-else 变成声明式、可合并、可分发的配置。当你的项目需要支持多模型时,Profile 是保持代码整洁的唯一正确姿势。

延伸阅读

继续本课程

相关主题