← 工程跃迁 / 第 02 章
架构定位

基础认知

建立模型输入、上下文和工程基础,让后续系统拥有清楚、可验证的起点。

模型接收什么信息,开发者如何建立可靠输入与工程基础?

概念校准

Agent

它是什么
围绕目标和状态,在约束下选择动作、执行、观察并验证结果的运行系统。
它不是什么
不是独立人格,也不是把模型放进无限循环。

工具

它是什么
Agent 可通过明确参数、权限和结果合同调用的受约束能力。
它不是什么
工具不等于 MCP;本地函数、命令或 HTTP 客户端也可以是工具。

验证与评测

它是什么
用测试、Schema、引用、状态和验收规则检查单次结果,并用数据集衡量系统表现。
它不是什么
不等于让模型对自己的输出再给一次意见;验证必须落到外部可检查证据。

AI 工具、模型与任务判断

用基准任务比较模型、工具、成本、延迟和上下文边界。

本章只做一件事实现可解释的任务—模型路由器。
开始前,Forge 已经具备已完成 Module 01 的“入学诊断与证据地图”,其通过验收的 solution 是本章起点。
完成后,Forge 将能够实现任务—模型路由器和可解释选择报告。
卡住时的最小恢复点只保留一个文本模型和确定性 Mock,先跑通选择报告。
下载本章实验仓库Python 3.12 · pytest · Pydantic · SQLite · deterministic Mock
阶段能力链
01根据输出合同和证据要求区分抽取、生成、检索与执行任务02把候选模型的模态、结构化输出和工具调用能力与任务要求逐项匹配03在最低质量门内按预计成本选择候选,而不是只选最贵或最便宜模型04用上下文容量、输出余量和延迟预算淘汰不可行候选05把推理、外部事实读取和副作用执行分配给不同受控部件06用同一组带权测试比较候选并拒绝缺失关键任务结果的模型07输出包含入选理由、淘汰原因、假设和复测条件的确定性选择报告
每一步只增加一种可验证能力;后一步建立在前一步已经通过的代码和测试上。

概念校准\n\n在开始操作代码前,先把本章涉及的概念放回正确的工程边界。\n\n
\n\n### Agent agent\n\n- 它是什么: 围绕目标和当前状态,在约束下选择动作、执行、观察并验证结果的运行系统。核心运行环是:目标/状态 → 选择动作 → 执行工具 → 观察结果 → 验证 → 停止或重新规划。\n- 它不是什么: 它不是独立人格,也不是让模型无期限循环。模型只是决策组件,动作、权限、状态和停止条件由外部运行环境共同约束。\n- 与相邻概念的关系: Context 提供可见信息,Tool 提供动作,Harness 控制权限和副作用,Verification 判断结果,Planning 只在复杂任务需要时加入。\n- 在 HeatStack Forge 中的位置: Forge 读取任务合同与状态,选择一个受许可动作,记录工具结果,再用测试、Schema 或验收规则决定成功、失败、预算耗尽、重复动作、人工介入或重新规划。\n- 典型误用与修正: 误用是把“模型继续生成”当成进展。修正方法是让每轮产生可观察的状态变化,并为所有停止原因建立确定性检查。\n\n### 工具 tool\n\n- 它是什么: Agent 可通过明确参数、权限、执行边界和结果合同调用的受约束能力,例如函数、命令、数据库查询或 HTTP 客户端。\n- 它不是什么: 工具不等于 MCP,也不等于任意脚本。协议可以暴露能力,但 Harness 仍决定是否允许调用及如何处理副作用。\n- 与相邻概念的关系: Agent Loop 选择工具,Schema 约束参数,Harness 执行权限检查和隔离,Verification 检查结果是否推进目标。\n- 在 HeatStack Forge 中的位置: Forge 的 tool registry 保存合同与风险等级;执行前生成变更计划,高风险写操作等待确认,执行后记录结果与恢复信息。\n- 典型误用与修正: 误用是让模型根据工具名称自由拼参数。修正方法是严格 Schema、确定性校验、超时、幂等键和拒绝分支测试。\n\n### 验证与评测 evaluation\n\n- 它是什么: Verification 用测试、Schema、引用、状态或验收规则检查一次结果;Evaluation 用数据集和指标衡量多次运行表现。\n- 它不是什么: 它不是让模型对自己的回答再给一次意见;通过与否必须落到外部可检查证据。\n- 与相邻概念的关系: Agent Loop 依赖 Verification 决定停止,RAG 需要分层评测,Planning 需要节点验收,Observability 提供运行记录。\n- 在 HeatStack Forge 中的位置: Forge 为单次运行保存合同测试与 evidence,再用固定场景集比较质量、延迟、成本、权限和恢复能力。\n- 典型误用与修正: 误用是把一个综合分数当成全部结论。修正方法是定义分层指标、失败样本和版本基线,并结合产品风险评审。\n\n

STAGE 01

根据输出合同和证据要求区分抽取、生成、检索与执行任务

先看一个具体问题

如何仅凭三个布尔特征对任务进行分类?

你正在构建一个任务路由器,它需要根据任务的客观属性决定后续处理方式。当前仓库中还没有 forge/task_types.py 文件,因此测试无法导入 classify_task 函数。

测试要求 classify_task 接收三个布尔参数:mutates_state(是否改变状态)、needs_sources(是否需要来源证据)、output_schema(是否要求结构化输出),并返回正确的 TaskType 枚举成员。

关键决策在于:当多个特征同时为真时,必须确定优先级。例如,一个任务既改变状态又需要来源,应该被分类为 ACT 而不是 RETRIEVE。

先做判断

如果 classify_task 收到 mutates_state=True、needs_sources=True、output_schema=True,它应该返回哪个 TaskType?

  • TaskType.ACT
  • TaskType.RETRIEVE
  • TaskType.EXTRACT
  • TaskType.GENERATE

判断依据: 正确答案是 TaskType.ACT。因为 mutates_state 表示任务会改变外部状态,这类任务具有副作用,必须优先识别为执行类任务,即使它同时需要来源和结构化输出。

它是什么

任务分类是一个纯函数决策树

classify_task 是一个纯函数:给定三个布尔输入,它按照固定顺序检查条件并返回一个 TaskType 枚举值。

决策顺序是:先检查 mutates_state,再检查 needs_sources,然后检查 output_schema,最后默认返回 GENERATE。

这个顺序体现了任务的风险等级:改变状态的任务风险最高,需要优先处理;仅需要来源的任务次之;仅要求结构化输出的任务再次之;其余任务归为生成类。

它不是什么

任务分类不依赖模型能力或用户意图

分类只依据三个布尔输入,不涉及模型选择、成本、延迟或用户偏好。

它不判断任务是否“困难”或“重要”,也不考虑执行任务的具体工具或环境。

分类结果不包含任何置信度或概率,它是一个确定性的映射。

它与相邻概念的关系

TaskType 枚举与 classify_task 的关系

TaskType 定义了四个可能的类别:EXTRACT、GENERATE、RETRIEVE、ACT。

classify_task 根据输入特征返回其中一个枚举成员,测试使用 is 运算符验证返回的是同一个枚举对象。

枚举继承自 str,因此每个成员既是枚举又是字符串,便于序列化和比较。

本阶段的边界决定: 当 mutates_state 为 True 时,无论其他特征如何,都返回 TaskType.ACT;否则,如果 needs_sources 为 True,返回 TaskType.RETRIEVE;否则,如果 output_schema 为 True,返回 TaskType.EXTRACT;否则返回 TaskType.GENERATE。

STAGE 02

把候选模型的模态、结构化输出和工具调用能力与任务要求逐项匹配

先看一个具体问题

为什么一个不支持工具调用的模型会被错误地选中?

在 Stage 01 中,我们已经能够把任务分类为不同的类型,例如需要工具调用的动作任务。现在我们需要为每个任务选择一个合适的模型,但候选模型的能力各不相同。

假设我们有一个文本模型,它只支持文本模态,不支持工具调用。如果我们把任务需求定义为包含 modality:texttool_calls,那么当前代码会错误地认为这个模型满足需求,因为它只检查了模态,而忽略了工具调用能力。

这种错误会导致系统选择一个无法执行动作的模型,最终任务失败。我们需要实现一个能力匹配函数,确保模型声明的能力覆盖所有任务需求。

先做判断

在下面的代码中,supports 函数目前只检查了 structured_output 和模态需求。如果需求集合包含 tool_calls,但模型 tool_calls=False,函数会返回什么?

  • 返回 True,因为模态检查通过了
  • 返回 False,因为缺少工具调用能力
  • 抛出异常,因为需求无法识别
  • 返回 None,因为函数没有处理该情况

判断依据: 正确答案是“返回 True,因为模态检查通过了”。当前代码没有检查 tool_calls 需求,所以只要模态匹配就会返回 True。这会导致不支持工具调用的模型被错误地认为满足需求。

它是什么

能力匹配是集合覆盖检查

能力匹配的核心是检查模型声明的能力集合是否覆盖任务需求集合。每个需求都是一个字符串,例如 modality:text 表示需要文本模态,tool_calls 表示需要工具调用能力。

模型能力由 ModelCapability 数据类表示,包含 modalities(模态集合)、structured_output(是否支持结构化输出)和 tool_calls(是否支持工具调用)。

supports 函数遍历需求集合,对每个需求检查模型是否具备相应能力。如果所有需求都满足,返回 True;否则返回 False。

它不是什么

能力匹配不评估质量、成本或延迟

能力匹配只关心模型是否具备完成任务所需的基本能力,不涉及模型输出的质量、推理速度、成本或其他非功能性指标。

例如,一个模型支持工具调用,但工具调用的准确率可能很低,这不在能力匹配的考虑范围内。能力匹配只回答“能不能做”,不回答“做得好不好”。

同样,能力匹配不检查模型是否适合特定领域,例如医学或法律,除非这些领域要求被显式地表示为需求。

它与相邻概念的关系

能力匹配与任务分类、模型选择的关系

任务分类(Stage 01)确定了任务的需求集合,例如动作任务需要 tool_calls。能力匹配使用这个需求集合来筛选候选模型。

模型选择是在通过能力匹配的候选模型中进一步考虑质量、成本等因素。能力匹配是模型选择的前置过滤步骤。

ModelCapability 数据类封装了模型的静态能力声明,supports 函数是能力匹配的具体实现。两者共同构成了能力矩阵的基础。

本阶段的边界决定: 能力匹配只检查模型声明的能力是否覆盖需求集合,不涉及质量、成本或延迟。如果需求包含 tool_calls,模型必须显式声明 tool_calls=True 才能通过匹配。

STAGE 03

在最低质量门内按预计成本选择候选,而不是只选最贵或最便宜模型

先看一个具体问题

为什么最便宜的模型不一定是最佳选择?

在任务路由中,如果只按成本选择模型,可能会选到质量不达标的候选,导致任务失败或输出不可用。

本阶段需要实现一个函数,它先过滤掉质量分数低于最低质量门的候选,再从剩余候选中选择预计成本最低的模型。

当前起始代码中 forge/economics.py 文件不存在,因此测试在导入 CandidateScorechoose_with_quality_floor 时会直接报错。

你需要创建该文件并实现数据类和选择函数,使两个测试通过:一个测试验证低于质量门的便宜候选被拒绝,另一个测试验证没有候选满足质量门时抛出明确异常。

先做判断

在实现 choose_with_quality_floor 时,以下哪种顺序是正确的?

  • 先按成本排序,再检查质量是否达标
  • 先过滤质量低于质量门的候选,再按成本选择
  • 同时按质量和成本加权排序
  • 随机选择一个候选

判断依据: 正确顺序是先过滤质量低于质量门的候选,再按成本选择。如果先按成本排序,可能会选到质量不达标的便宜模型,违反质量约束。

它是什么

质量成本前沿是什么

质量成本前沿是一种决策方法:在满足最低质量要求的前提下,选择预计成本最低的候选。

它由两个步骤组成:可行性过滤(质量 >= 质量门)和优化(最小化预计成本)。

可行性过滤确保所有进入成本比较的候选都满足质量约束,优化则在可行集合中寻找成本最低者。

它不是什么

质量成本前沿不是什么

它不是单纯的成本最小化,因为成本最低的候选可能质量不达标。

它也不是质量最大化,因为质量最高的候选可能成本过高,超出预算。

它不涉及上下文窗口或延迟等其他约束,本阶段只考虑质量分数和成本估算。

它与相邻概念的关系

与其他概念的关系

CandidateScore 数据类封装了模型名称、质量分数、输入成本和输出成本,并提供 estimated_cost 方法计算预计成本。

choose_with_quality_floor 函数接收候选列表、质量门、输入 token 数和输出 token 数,返回选中的候选。

质量过滤必须在成本计算之前进行,否则优化可能选择不可行的候选。

本阶段的边界决定: 当没有候选满足质量门时,函数必须抛出 ValueError,而不是返回 None 或任意候选,因为调用方需要明确知道没有可行选项。

STAGE 04

用上下文容量、输出余量和延迟预算淘汰不可行候选

先看一个具体问题

为什么一个看似可行的模型会被上下文窗口拒绝?

在 Stage 03 中,你根据质量和成本筛选出了候选模型,但还没有检查它们是否真的能处理给定的请求。

一个模型即使质量高、成本低,如果它的上下文窗口装不下输入和输出,或者延迟超过预算,也不能用于该任务。

本阶段你将实现 is_feasible 函数,它根据上下文容量和延迟预算判断模型是否可行。

当前 forge/constraints.py 文件尚未创建,因此测试在导入时失败,你需要从零开始实现该模块。

先做判断

在实现 is_feasible 之前,请预测:如果 input_tokens=3800output_reserve=500,而模型的 context_tokens=4096,该模型是否可行?

  • 可行,因为输入 token 数 3800 小于上下文窗口 4096
  • 不可行,因为输入加输出预留的总 token 数 4300 超过上下文窗口 4096
  • 可行,因为输出预留不占用上下文窗口
  • 不可行,因为输出预留必须单独计算,不能与输入相加

判断依据: 正确答案是“不可行,因为输入加输出预留的总 token 数 4300 超过上下文窗口 4096”。上下文窗口必须同时容纳输入和输出,输出预留是模型生成响应所需的空间,必须计入总 token 数。

它是什么

可行性检查是什么

可行性检查是一个二元判断:给定模型配置和请求参数,模型是否能在约束内完成推理。

它只考虑硬性约束:上下文窗口容量和延迟预算,不涉及质量或成本。

is_feasible 函数接收 RuntimeProfile(包含 context_tokensp95_latency_ms)以及请求参数 input_tokensoutput_reservelatency_budget_ms,返回布尔值。

它不是什么

可行性检查不是什么

它不是质量评估:即使模型可行,也可能输出质量差,但这由其他阶段处理。

它不是成本计算:可行性检查不关心价格,只关心能否在约束内完成。

它不是概率判断:可行性是确定性的,只要违反任一约束就返回 False

它与相邻概念的关系

与其他概念的关系

可行性检查是模型选择流程中的一道过滤器,位于质量-成本前沿之后。

它使用 RuntimeProfile 数据类,该数据类封装了模型的运行时属性。

is_feasible 的结果会影响后续的路由决策,只有可行的模型才能进入最终选择。

本阶段的边界决定: 可行性检查只回答“能否在约束内完成”,不回答“是否值得选择”。如果上下文窗口或延迟预算不满足,模型必须被淘汰,即使它在其他方面很优秀。

STAGE 05

把推理、外部事实读取和副作用执行分配给不同受控部件

先看一个具体问题

一个需要新鲜数据且会改变外部状态的任务,应该交给谁执行?

在上一阶段,你已经能够根据延迟和上下文约束筛选模型;现在要决定的是:一个任务到底应该由模型直接推理,还是调用只读工具,或者调用写工具。

当前代码库中还没有 forge/boundaries.py,因此测试文件 tests/test_stage.py 在导入 Executorchoose_executor 时会直接失败,错误为 ModuleNotFoundError: No module named 'forge.boundaries'

你需要创建这个模块,并实现 choose_executor 函数:它接收两个布尔参数 needs_fresh_datachanges_external_state,返回一个 Executor 枚举成员。

关键决策是:当任务既需要新鲜数据又改变外部状态时,必须优先考虑副作用,返回 Executor.WRITE_TOOL,而不是 READ_TOOL

先做判断

如果 needs_fresh_data=Truechanges_external_state=Truechoose_executor 应该返回哪个执行者?

  • Executor.READ_TOOL,因为需要新鲜数据
  • Executor.WRITE_TOOL,因为改变外部状态必须由写工具执行
  • Executor.MODEL,因为模型可以同时处理两者
  • 直接抛出异常,因为两个条件冲突

判断依据: 正确答案是 Executor.WRITE_TOOL。改变外部状态意味着任务会产生副作用,必须由具备写权限的工具执行;只读工具无法安全地完成这类任务,即使它也需要新鲜数据。

它是什么

执行者选择是一个优先级决策

choose_executor 是一个纯函数,它根据任务的两个布尔属性决定执行者类型:是否改变外部状态、是否需要新鲜数据。

决策顺序是:先检查副作用,再检查数据新鲜度,最后默认使用模型。这样保证任何有副作用的操作都不会被错误地分配给只读工具或模型。

它不是什么

它不是具体工具实现或权限系统

本阶段只返回一个枚举值,表示应该使用哪类执行者;它不负责实际调用工具、检查权限或处理错误。

Executor 枚举只是标签,不代表具体的函数或 API;真正的工具实现和权限控制由后续阶段或外部 Harness 完成。

它与相邻概念的关系

与已有模块的关系

forge/boundaries.py 依赖之前阶段定义的 task_types.pycapabilities.pyeconomics.pyconstraints.py 中的概念,但本阶段只使用两个布尔输入,不直接导入那些模块。

测试文件 tests/test_stage.pyforge.boundaries 导入 Executorchoose_executor,因此模块必须存在且函数签名正确。

本阶段的边界决定: 本阶段只决定执行者类型(模型、读工具、写工具),不涉及具体工具实现或权限;如果任务改变外部状态,无论是否需要新鲜数据,都必须返回 Executor.WRITE_TOOL

STAGE 06

用同一组带权测试比较候选并拒绝缺失关键任务结果的模型

先看一个具体问题

如何公平比较候选模型并拒绝缺失关键结果的候选?

你已经在前几个阶段建立了任务分类、能力矩阵、质量成本前沿、延迟约束和模型工具边界。现在需要实现一个基准测试套件,用同一组带权测试案例比较不同候选模型,并确保关键任务结果缺失时直接拒绝该候选。

当前 forge/benchmark.py 文件不存在,测试 test_missing_critical_case_rejects_candidatetest_weighted_score_uses_measured_cases 会因导入失败而报错。你需要创建该文件,定义 BenchmarkCase 数据类和 score_candidate 函数,使两个测试通过。

具体来说,score_candidate 接收案例列表和结果字典,必须检查所有标记为 critical=True 的案例是否都有结果;如果缺失任何一个关键案例,就抛出包含 ‘critical’ 的 ValueError。同时,对于有结果的案例,使用案例权重计算加权平均分。

先做判断

在实现 score_candidate 时,如果某个关键案例的结果缺失,函数应该怎么做?

  • 忽略缺失的关键案例,只对已有结果计算加权平均
  • 抛出 ValueError,提示缺失关键基准案例
  • 将缺失的关键案例得分视为 0 并继续计算
  • 返回 None 表示无法评分

判断依据: 正确做法是抛出 ValueError。关键案例是必须覆盖的任务,缺失结果意味着候选模型没有完成必要评估,不能给出有效分数。忽略或视为 0 都会错误地让候选通过。

它是什么

基准评分是一个带完整性检查的加权平均计算

基准评分函数接收一组案例定义(每个案例有 id、权重和是否关键)以及候选模型在这些案例上的得分结果。它首先验证所有关键案例都有结果,然后只对已有结果的案例计算加权平均分。

这个模型的核心是:关键案例的完整性是评分的先决条件,权重用于反映不同案例的重要性。缺失关键案例意味着候选模型没有完成必要评估,因此必须拒绝。

它不是什么

它不是简单的平均,也不是忽略缺失数据的容错计算

基准评分不是对所有案例简单求平均,因为不同案例的重要性不同,必须使用权重。它也不是容错计算,不能因为某些案例缺失就静默跳过,特别是关键案例。

它不涉及模型能力或成本的评估,只基于提供的案例和结果进行数学计算。任何缺失关键案例的情况都必须显式报错,而不是给出一个看似合理的分数。

它与相邻概念的关系

与任务分类、能力矩阵和约束的关系

基准案例的 id 通常对应任务类型(如 ‘schema’、‘summary’),这些任务类型来自 Stage 01 的任务分类。案例的权重可能反映任务的重要性或频率,与 Stage 02 的能力矩阵和 Stage 03 的质量成本前沿相关。

关键案例的标记可能来自 Stage 04 的约束条件,例如某些任务必须满足最低性能。基准评分的结果可以用于后续的模型选择,因此它必须可靠地拒绝不完整的候选。

本阶段的边界决定: 当关键案例缺失时,必须抛出 ValueError 拒绝候选;当没有关键案例缺失但所有案例都缺失结果时,也应抛出 ValueError 避免除零错误。

STAGE 07

输出包含入选理由、淘汰原因、假设和复测条件的确定性选择报告

先看一个具体问题

为什么选择报告不能只写“最佳模型”?

在上一阶段,你已经用基准测试得到每个候选模型的分数,并选出了当前任务下的最优模型。现在需要把选择结果写成一份报告,交给团队审查。

如果报告只写“我们选择了 balanced 模型”,审查者无法知道其他模型为什么被淘汰,也无法判断这个选择在什么条件下需要重新评估。

本阶段的目标是编写 build_report 函数,把选中的模型、基准分数、淘汰原因、假设和复测条件都记录下来,形成一份可审计的决策文档。

先做判断

在开始实现之前,请预测:如果 build_report 函数只返回选中的模型名称和一句“best model”,会发生什么?

  • 测试会通过,因为报告已经说明了选择结果。
  • 测试会失败,因为报告缺少淘汰原因和复测条件。
  • 测试会失败,因为报告没有包含基准分数。
  • 测试会通过,因为审查者可以自己推断淘汰原因。

判断依据: 正确答案是第二个选项。测试 test_report_preserves_rejections_and_retest_triggers 明确要求报告包含 rejected 字典中的淘汰原因和 retest_when 列表中的复测条件。只写“best model”会导致 KeyError,因为报告中没有 cheap 这个键。

它是什么

选择报告是什么

选择报告是一个不可变的数据结构,它把决策过程中的关键证据固定下来,包括选中的模型、基准分数、每个被淘汰模型的原因、做出选择时的假设,以及未来需要重新评估的触发条件。

报告中的每个字段都来自 build_report 的输入参数,函数本身不做新的计算,只负责整理和验证这些信息。

它不是什么

选择报告不是什么

选择报告不是一段自由文本,不能只写一句“我们选择了最好的模型”。它必须结构化地记录所有淘汰原因和复测条件,让审查者能够重建决策过程。

报告也不负责重新计算分数或引入外部数据,它只使用调用者提供的分数和原因。

它与相邻概念的关系

与其他组件的关系

build_report 接收来自基准测试阶段的 scores 字典,以及任务约束阶段产生的 rejected 原因和 retest_when 条件。

报告中的 reasons 字段必须包含选中模型的基准分数,格式为 benchmark_score=0.870,这样审查者可以核对分数来源。

本阶段的边界决定: 报告构建只使用提供的分数、淘汰原因、假设和复测条件,不涉及新的计算或外部数据。如果选中的模型没有基准分数,函数必须抛出 ValueError,因为无法验证选择依据。

完成本章

实现任务—模型路由器和可解释选择报告。

FORGE / LOCAL CHECK

本地实验自检

  1. 未开始
  2. 2阅读中
  3. 3实验已下载
  4. 4测试结果已读取
  5. 5本地自检通过

verification.json 只在当前浏览器中解析,不会上传。这里验证的是实验合同,不是服务器认证或第三方背书。

概念校准与一周复习

三道题检查你是否掌握了本章边界、交付证据和恢复方法。答案只保存在当前浏览器。

  1. 1哪一项最能证明你真正完成了「AI 工具、模型与任务判断」?
  2. 2关于「Agent」,哪一种理解最准确?
  3. 3实验卡住时,哪个动作是本章建议的最小恢复点?