---
title: "模型路由怎么做？先判断难度，再分给小模型或大模型（TypeScript）"
description: "所有请求都交给最贵的模型太浪费，用大模型判断路由又太慢。本文讲清常见的路由策略，以及如何用决策模型低成本判断难度、按阈值分流和升级。"
canonical: "https://system-one.dev/zh-CN/blog/model-routing"
language: "zh-CN"
---

# 模型路由怎么做？先判断难度，再分给小模型或大模型（TypeScript）

模型路由是在请求到达模型之前，先判断它的类型、难度和风险，再把它交给合适的模型：简单请求用小模型，复杂或高风险请求用大模型或推理模型。难点在于谁来判断难度：规则太粗，再调用一次大模型来判断又会抵消省下的钱。用决策模型把这一步写成一个 Choice 或 Score 问题，可以得到带概率的结果，由代码按阈值分流；小模型回答之后，再用是非问题（Noul）检查结果，不达标就升级到更强的模型。

system-one.dev 提供 Jev、Clef Flash 和 Clef 模型服务，这三个模型**都是决策模型**，只负责判断（打难度分、回答是非题），不负责回答用户的问题，也不是路由的目标。真正回答问题的小模型和大模型由你的应用自己接入，System One 不提供文本生成接口。决策模型的基础知识见 [决策模型是什么？](https://system-one.dev/zh-CN/blog/what-is-a-decision-model)。

## 模型路由是什么，和工具路由有什么区别

两者都是从预设选项中做一次选择，都可以用决策模型实现，但选项不同、指标也不同：

- **工具路由**决定这一步调用哪个工具（或者不调用）。主要指标是选择准确率、漏判率和参数有效率。
- **模型路由**决定把请求交给哪个模型处理。Microsoft 在 Microsoft-Decision-1 的发布博客中把它描述为“按质量、成本和延迟要求选出最合适的模型”（厂商说法），主要指标是质量、成本和延迟。

工具路由的完整做法见 [Agent 工具太多总选错？用 Choice 做工具路由](https://system-one.dev/zh-CN/blog/agent-tool-routing)。

## 常见的路由方式

- **规则路由**：按长度、关键词、用户等级、请求类型等规则分流。便宜、可预测，但规则写不全，容易漏判。
- **先判断再分流**：先由一个判断步骤估计难度或类型，再按结果选模型。本文讲的就是这一种，判断步骤用决策模型完成。
- **级联**：先让小模型回答，再检查结果，不达标才升级到大模型。省钱的前提是检查本身足够便宜、足够可靠。
- **托管路由器**：把路由交给平台。例如 Microsoft Foundry 的 [model router](https://learn.microsoft.com/en-us/azure/foundry/openai/concepts/model-router) 是一个“训练过的语言模型”，实时把提示路由到最合适的 LLM，作为一个单独的部署使用（厂商说法）。

实际系统里，这几种方式经常组合使用：规则先挡掉明显的情况，判断步骤处理剩下的，级联兜底。

## 谁来判断难度：四种做法对比

下文的“判断方式对比表”列出四种做法的来源。概括起来：

- **规则**：没有额外的模型调用，但只能覆盖你想到的情况。
- **再调一次生成式 LLM**：灵活，但这次调用本身就有成本和延迟。如果判断模型和回答模型一样贵，路由就省不了多少钱。
- **决策模型**：只返回选项、分数或概率，不生成文本。Microsoft Learn 把“为请求选择模型、工具或 Agent”列为 Microsoft-Decision-1 的用途之一，并建议先用决策模型做路由或门禁，再把请求交给生成式模型（厂商说法）。
- **托管路由器**：比如 Foundry model router，省去自己写路由逻辑；代价是路由规则不由你定义。Microsoft 文档提醒，它的有效上下文窗口受限于池中最小的模型，并建议与当前基线对比，确认真的改善了你关心的结果。

## 用决策模型做路由判断

### Choice：选项 = 模型档位

把模型档位作为选项，在描述里写清“什么情况下用它”，例如：

- `small`：事实查询、格式转换、简短问答
- `large`：多步推理、长文档、需要综合多方信息
- `review`：涉及金钱、法律、医疗等高风险内容，需要人工或最强模型

措辞仅为示例。Microsoft Learn 建议把每个选项描述清楚，并把“最高概率偏低”当作升级的信号，交给人或另一个模型。TypeSafe 的 Choice 文档也建议在选项可能覆盖不全时加 `other` 或 `none of the above`。

### Score：难度分档，再按阈值映射

如果“难度”更像一个连续的程度，可以用 Score 把难度分成几档（比如“一句话能答 / 需要查资料 / 需要多步推理 / 需要专业判断”），再在代码里把分数映射到模型。Microsoft Learn 写明，Score 返回的是各档索引的概率加权平均，可能落在两档之间；建议把分数用于相对排序和阈值判断，不要当作绝对、已校准的评分。

### 加一个“无法判断”选项

Microsoft Learn 建议，在不应该强制模型做选择时，加入一个弃权选项，比如 `cannot tell`。路由场景里，“无法判断”可以直接走默认的大模型或人工复核。微软还建议措辞保持清晰中立，可以考虑随机化选项顺序，并测试顺序变化会不会影响结果。

### 置信度与阈值

对于 Choice 和 Score，TypeSafe 返回的置信度是从概率分布算出来的统计量，不等于“选对的概率”；阈值应随风险变化。路由场景的默认取向是：**不确定时往贵的方向走**。把简单请求误送给大模型，只是多花钱；把难题误送给小模型，可能直接答错。阈值怎么用本地标注样本选、再用留出数据复核，见 [用 Score 替代 LLM 当裁判](https://system-one.dev/zh-CN/blog/llm-as-a-judge-alternative)。那篇文章也附有开发团队“高置信采纳、低置信升级给大模型”的 TypeScript 示例。

## 级联：小模型先答，Noul 校验，必要时升级

另一种做法是先不判断难度，直接让小模型回答，再用几个是非问题检查结果，例如：

- “回答是否完整回应了用户的请求？”
- “回答中是否含有拒答、明显的不确定或自相矛盾？”

每个问题用 Noul 得到 P(true)，代码按阈值决定采用小模型的回答，还是升级到大模型。这和 [Agent 完成判断](https://system-one.dev/zh-CN/blog/agent-stop-condition)里的做法相同：一个问题只问一件事，能用代码直接检查的（比如 JSON 能否解析、测试是否通过）就不交给模型。

级联有一个前提要注意。[大模型当裁判又慢又贵？用决策模型给 Agent 评估做第一道关](https://system-one.dev/zh-CN/blog/llm-as-a-judge-alternative)引用的研究发现，当第二道关会重复第一道关的错误时，级联主要是省钱，准确率提升有限。路由也一样：如果校验步骤和小模型在同一类问题上一起出错，被放过的坏回答不会少。所以上线前要用标注数据单独看“被放过的错误回答”有多少。

## 怎么证明路由真的省钱、没掉质量

Microsoft 关于[评估 model router](https://learn.microsoft.com/en-us/azure/foundry/openai/how-to/evaluate-model-router) 的文档给出的维度，同样适用于自己写的路由（这一点是本文的推论）：

- **质量**：可接受的最低得分或胜率，包括准确、完整、清晰和有用。
- **成本**：每个请求的预估成本上限，或者相对基线的最低节省幅度。
- **延迟**：可接受的中位延迟和尾部延迟（例如 p90 或 p95）。
- **策略**：组织允许使用哪些模型、区域和部署配置。

另外几点建议（本文建议）：

- **端到端算账**：把路由判断、级联校验、升级后的重复调用、人工兜底都算进成本，不要只比较单次调用的价格。
- **看升级率**：升级比例越高，省下的钱越少。
- **按类别看**：Microsoft 文档也建议，某一类请求表现不同，就先单独评估这一类。高风险类别不要只按成本做路由。
- **换模型就重新验证**：换了做判断的决策模型、应用接入的作答模型、选项描述或阈值，都要重跑评估。

本站没有做过路由前后的实测对比，所以本文不给出节省比例。

## TypeScript 示例：Score 判断难度，Noul 检查小模型的回答

下面这段代码来自开发团队提供的示例（`examples/decision-articles/07-model-routing.ts`）。流程分三步：

1. 用一个决策模型给请求打难度分（Score，0 到 2 三档）。分数达到阈值，直接交给应用的大模型。
2. 否则先让应用的小模型作答，再用同一个决策模型回答一个是非题（`booleanQuestion`）：“这个草稿是否完整、正确地回应了请求，没有含糊或拒答？”
3. P(合格) 达到阈值就采用小模型的回答，否则升级给大模型。

```ts
/**
 * Article 6 — Model routing: Score difficulty, then cascade LLMs.
 *
 * System One estimates request difficulty (Score) and whether a small-model
 * draft is adequate (Noul via `booleanQuestion`). It does not run chat
 * completions. Small/large generation stays in application callbacks.
 *
 * The decision-model id comes from GET {baseURL}/models (hosted: GET /v1/models).
 * When catalog entries include `pricing.creditsPerBlock`, this file defaults to
 * the cheapest name (on the current hosted catalog that is typically
 * `typesafe/jev`). It does not invent that id if the catalog omits pricing.
 *
 * Packages: @system-one-ai/core, adapter-system-one, transport-fetch.
 * Evaluate: POST {baseURL}/systemone. Catalog listing is not an SDK method.
 */
import { createSystemOne, score, booleanQuestion } from '@system-one-ai/core';
import type { EvaluationClient } from '@system-one-ai/core';
import { systemOneAdapter } from '@system-one-ai/adapter-system-one';
import { createFetchTransport } from '@system-one-ai/transport-fetch';

export const difficultyQuestion = score('How much generative capacity does this request need?', [
  'A short factual or formatting answer a small model can handle',
  'Some reasoning or multi-step wording; try a small model first',
  'Specialized, high-stakes, or long-form work that needs a larger model',
]);

export const adequacyQuestion = booleanQuestion(
  'Does this draft fully and correctly address the request without hedging or refusal?',
  {
    true: 'The user can use this answer as-is',
    false: 'The answer is incomplete, wrong, hedged, or a refusal',
  },
);

/** Inclusive Score threshold on the 0..2 rubric. Tune on labeled traffic. */
export const LARGE_IF_DIFFICULTY_AT_LEAST = 2;
/** Keep the small draft only when P(adequate) meets this application threshold. */
export const KEEP_SMALL_IF_ADEQUATE_AT_LEAST = 0.7;

export interface CatalogModel {
  name: string;
  aliases: readonly string[];
  pricing?: { creditsPerBlock: number; bytesPerBlock: number };
}

export type LlmSize = 'small' | 'large';

export interface LlmCallbacks {
  completeWithSmallLlm: (prompt: string) => Promise<string>;
  completeWithLargeLlm: (prompt: string) => Promise<string>;
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}

/** Parse the published `{ models: [...] }` envelope. Extra fields are ignored. */
export function parseCatalogModels(payload: unknown): CatalogModel[] {
  if (!isRecord(payload) || !Array.isArray(payload.models)) {
    throw new Error('GET /v1/models must return { models: [...] }.');
  }
  return payload.models.map((entry, index) => {
    if (!isRecord(entry) || typeof entry.name !== 'string' || entry.name.trim() === '') {
      throw new Error(`Catalog model ${index} is missing name.`);
    }
    let pricing: CatalogModel['pricing'];
    if (
      isRecord(entry.pricing)
      && typeof entry.pricing.creditsPerBlock === 'number'
      && typeof entry.pricing.bytesPerBlock === 'number'
    ) {
      pricing = {
        creditsPerBlock: entry.pricing.creditsPerBlock,
        bytesPerBlock: entry.pricing.bytesPerBlock,
      };
    }
    const aliases = Array.isArray(entry.aliases)
      ? entry.aliases.filter((alias): alias is string => typeof alias === 'string' && alias.length > 0)
      : [];
    return { name: entry.name, aliases, pricing };
  });
}

export async function fetchCatalogModels(input: {
  baseURL: string;
  apiKey: string;
  fetchImpl?: typeof fetch;
}) {
  const response = await (input.fetchImpl ?? fetch)(`${input.baseURL.replace(/\/$/, '')}/models`, {
    headers: { Authorization: `Bearer ${input.apiKey}`, Accept: 'application/json' },
  });
  if (!response.ok) {
    throw new Error(`GET /v1/models failed with HTTP ${response.status}.`);
  }
  return parseCatalogModels(await response.json());
}

/** Lowest `pricing.creditsPerBlock`, then name. Undefined when no entry is priced. */
export function pickCheapestCatalogModel(models: readonly CatalogModel[]) {
  const priced = models.filter(model => model.pricing !== undefined);
  if (priced.length === 0) return undefined;
  return priced.reduce((lowest, model) => {
    const current = model.pricing!.creditsPerBlock;
    const best = lowest.pricing!.creditsPerBlock;
    if (current < best) return model;
    if (current === best && model.name < lowest.name) return model;
    return lowest;
  });
}

export function findCatalogModel(models: readonly CatalogModel[], requested: string) {
  const name = requested.trim();
  return models.find(model => model.name === name || model.aliases.includes(name));
}

/**
 * Prefer an explicit catalog name/alias (SYSTEM_ONE_MODEL). Otherwise the
 * cheapest priced entry. Do not invent a hosted id such as typesafe/jev.
 */
export function resolveDecisionModelId(models: readonly CatalogModel[], preferred?: string) {
  if (preferred && preferred.trim() !== '') {
    const match = findCatalogModel(models, preferred);
    if (!match) {
      throw new Error(`SYSTEM_ONE_MODEL ${preferred} is not in GET /v1/models.`);
    }
    return match.name;
  }
  const cheapest = pickCheapestCatalogModel(models);
  if (!cheapest) {
    throw new Error('Set SYSTEM_ONE_MODEL to a name from GET /v1/models (catalog has no pricing to pick a cheapest default).');
  }
  return cheapest.name;
}

export function routeByDifficulty(difficultyScore: number): LlmSize {
  return difficultyScore >= LARGE_IF_DIFFICULTY_AT_LEAST ? 'large' : 'small';
}

export function keepSmallAnswer(adequacyProbability: number) {
  return adequacyProbability >= KEEP_SMALL_IF_ADEQUATE_AT_LEAST;
}

export async function createDecisionClientFromCatalog(options?: { fetchImpl?: typeof fetch }) {
  const apiKey = process.env.SYSTEM_ONE_API_KEY;
  const baseURL = process.env.SYSTEM_ONE_BASE_URL;
  if (!apiKey || !baseURL) {
    throw new Error('Set SYSTEM_ONE_API_KEY and SYSTEM_ONE_BASE_URL (…/v1). Model id is read from GET /v1/models.');
  }
  const models = await fetchCatalogModels({ apiKey, baseURL, fetchImpl: options?.fetchImpl });
  const model = resolveDecisionModelId(models, process.env.SYSTEM_ONE_MODEL);
  return {
    model,
    models,
    client: createSystemOne({
      apiKey,
      baseURL,
      model,
      adapter: systemOneAdapter,
      transport: createFetchTransport(),
      timeoutMs: 30_000,
      maxRetries: 0,
    }),
  };
}

export async function estimateDifficulty(client: EvaluationClient, prompt: string) {
  const result = await client.evaluate({
    state: { prompt },
    questions: { difficulty: difficultyQuestion },
  });
  return {
    difficulty: result.answers.difficulty.score,
    model: result.model,
  };
}

export async function checkSmallAnswer(client: EvaluationClient, prompt: string, draft: string) {
  const result = await client.evaluate({
    state: { prompt, draft },
    questions: { adequate: adequacyQuestion },
  });
  return {
    adequate: result.answers.adequate.probability,
    model: result.model,
  };
}

/**
 * @param llms Your existing small/large completion paths.
 *        System One has no chat/completions URL for either callback.
 */
export async function routeToLlm(
  client: EvaluationClient,
  prompt: string,
  llms: LlmCallbacks,
) {
  const estimated = await estimateDifficulty(client, prompt);
  const lane = routeByDifficulty(estimated.difficulty);
  if (lane === 'large') {
    const text = await llms.completeWithLargeLlm(prompt);
    return {
      action: 'used_large' as const,
      reason: 'difficulty_at_or_above_threshold' as const,
      difficulty: estimated.difficulty,
      decisionModel: estimated.model,
      text,
    };
  }

  const small = await llms.completeWithSmallLlm(prompt);
  const checked = await checkSmallAnswer(client, prompt, small);
  if (keepSmallAnswer(checked.adequate)) {
    return {
      action: 'used_small' as const,
      reason: 'small_answer_adequate' as const,
      difficulty: estimated.difficulty,
      adequate: checked.adequate,
      decisionModel: checked.model,
      text: small,
    };
  }

  const text = await llms.completeWithLargeLlm(prompt);
  return {
    action: 'escalated_large' as const,
    reason: 'small_answer_below_threshold' as const,
    difficulty: estimated.difficulty,
    adequate: checked.adequate,
    decisionModel: checked.model,
    smallDraft: small,
    text,
  };
}

async function main() {
  const prompt = process.env.EXAMPLE_MESSAGE ?? 'How many credits does a single Score question cost?';
  const { client, model } = await createDecisionClientFromCatalog();
  const decision = await routeToLlm(client, prompt, {
    completeWithSmallLlm: async () => {
      throw new Error('No System One LLM-completion endpoint. Wire your small-model call here.');
    },
    completeWithLargeLlm: async () => {
      throw new Error('No System One LLM-completion endpoint. Wire your large-model call here.');
    },
  });
  console.log(JSON.stringify({ catalogModel: model, ...decision }, null, 2));
}

if (process.argv[1] && import.meta.url.endsWith(process.argv[1].split(/[\\/]/).pop() ?? '')) {
  main().catch((error: unknown) => {
    console.error(error instanceof Error ? error.message : error);
    process.exitCode = 1;
  });
}
```

需要说明几点（均出自示例代码和注释）：

- **决策模型 ID 不写死。** 示例从 `GET /v1/models` 读取模型目录。设置了 `SYSTEM_ONE_MODEL` 时，用目录中匹配的名称；没有设置时，取 `pricing.creditsPerBlock` 最低的一个。目录里没有价格信息时，示例会报错，而不是自己编一个 ID。
- **作答的模型由应用接入。** `completeWithSmallLlm` 和 `completeWithLargeLlm` 是你自己的小模型、大模型调用。System One 没有 chat/completions 接口，示例在没有接入时会直接抛错。
- **阈值是示例值。** `LARGE_IF_DIFFICULTY_AT_LEAST = 2`（0 到 2 三档，含等号）和 `KEEP_SMALL_IF_ADEQUATE_AT_LEAST = 0.7` 都要在你自己带标注的流量上调。
- **示例没有覆盖的部分**：Choice 选模型档位的写法、“无法判断”选项、端到端成本统计。

用 System One 托管 API 计费时，费用按“问题数 × max(1, ⌈请求字节数 ÷ 模型计费块大小⌉) × 模型费率”扣积分。API 页面列出的费率是：Jev 每问题每 4096 字节计费块 1 积分，Clef Flash 3 积分，Clef 6 积分。积分价格按[定价页](https://system-one.dev/zh-CN/pricing)所示，以预付积分包出售：10,000 积分 $10、60,000 积分 $50、300,000 积分 $200，均为一次性购买、不自动续费。注意，示例里每个请求最多调用两次决策模型（难度评分 + 是非检查），算账时都要计入。
> 表中不比较各方案的准确率和延迟：所核来源中只有厂商自测数字，本站也没有实测。
## 延伸阅读

- [决策模型是什么？](https://system-one.dev/zh-CN/blog/what-is-a-decision-model)
- [Jev 开源替代有哪些？](https://system-one.dev/zh-CN/blog/jev-open-source-alternative)
- [用 Score 替代 LLM 当裁判](https://system-one.dev/zh-CN/blog/llm-as-a-judge-alternative)
- [Agent 工具太多总选错？用 Choice 做工具路由](https://system-one.dev/zh-CN/blog/agent-tool-routing)
- [Agent 什么时候算完成？](https://system-one.dev/zh-CN/blog/agent-stop-condition)
- [System One 决策原语](https://docs.system-one.dev/zh-CN/docs/primitives)

---


## 判断方式对比表（核验日期 2026-10-11）

| 做法 | 怎么判断 | 额外调用 | 可控性 | 来源 |
| --- | --- | --- | --- | --- |
| 规则路由 | 长度、关键词、请求类型等规则 | 无 | 规则由你定义，但覆盖不全 | 本文概括 |
| 生成式 LLM 判断 | 让一个 LLM 读请求后说该用哪个模型 | 一次生成式调用 | 输出是自由文本，需要解析 | 本文概括 |
| 决策模型（例如 Microsoft-Decision-1）<br><small>[Microsoft Learn](https://learn.microsoft.com/en-us/azure/foundry/foundry-models/how-to/use-foundry-models-microsoft-decision) · 2026-10-11</small> | 用 choice 选模型，或用 score 分档；返回每个选项的概率<br><small>[Microsoft Learn](https://learn.microsoft.com/en-us/azure/foundry/foundry-models/how-to/use-foundry-models-microsoft-decision) · 2026-10-11</small> | 一次决策调用；Microsoft-Decision-1 输入每百万 token $0.042，输出免费<br><small>[Microsoft 发布博客](https://commandline.microsoft.com/microsoft-decision-1-model-foundry/) · 2026-10-11</small> | 选项、阈值和弃权规则由你定义 | 官方文档（厂商说法） |
| 托管路由器（Foundry model router）<br><small>[Microsoft Learn: model router](https://learn.microsoft.com/en-us/azure/foundry/openai/concepts/model-router) · 2026-10-11</small> | 训练过的语言模型实时选择底层 LLM；Balanced（默认）、Cost、Quality 三种模式<br><small>[Microsoft Learn: model router](https://learn.microsoft.com/en-us/azure/foundry/openai/concepts/model-router) · 2026-10-11</small> | 由平台处理，作为一个部署调用 | 可选择路由模式和模型子集；有效上下文窗口受限于最小的模型<br><small>[Microsoft Learn: model router](https://learn.microsoft.com/en-us/azure/foundry/openai/concepts/model-router) · 2026-10-11</small> | 官方文档（厂商说法） |
| System One 托管决策 API<br><small>[System One 托管 API 页](https://system-one.dev/zh-CN/api) · 2026-10-11</small> | 用 SDK 的 `choice()`、`score()`、`booleanQuestion()` 定义判断 | 一次决策调用，按积分计费<br><small>[System One 托管 API 页](https://system-one.dev/zh-CN/api) · 2026-10-11</small> | system-one.dev 提供 Jev、Clef Flash 和 Clef 模型服务，三者都是决策模型，作答模型由应用接入<br><small>[System One 托管 API 页](https://system-one.dev/zh-CN/api) · 2026-10-11</small> | 官方页面 |
## 常见问题

### 什么是模型路由？

在请求到达模型之前，先判断它的类型、难度和风险，再交给合适的模型：简单请求用小模型，复杂或高风险请求用大模型。它和工具路由一样，都是从预设选项中做选择，但衡量指标是质量、成本和延迟。

### 用一个模型来判断该用哪个模型，划算吗？

取决于判断步骤有多贵。如果判断用的是和回答一样贵的生成式模型，就很难省钱。决策模型只返回选项和概率，不生成文本，单次调用很便宜。例如微软公布的 Microsoft-Decision-1 价格是输入每百万 token $0.042、输出免费（厂商博客）。到底划不划算，要用你自己的流量把端到端成本算出来。

### 怎么用 Choice 或 Score 做路由？

Choice 的选项写成模型档位（如 small / large / review），每个选项附一句“何时使用”；Score 则把难度分成几档，再在代码里映射到模型。两种做法都建议加一个“无法判断”选项，并在不确定时把请求送往更强的模型。

### 小模型答完之后怎么决定要不要升级？

用几个是非问题（Noul）检查回答，比如“是否完整回应了请求”“是否含有拒答或明显的不确定”，每个问题得到一个概率，由代码按阈值决定采用还是升级。能用代码直接验证的（如格式、测试），优先用代码。

### 高风险请求可以为了省钱路由到小模型吗？

不建议只按成本决定。Microsoft 建议，涉及人的重大决定（信贷、就业、住房、医疗、法律）要有实质性的人工复核。路由时可以给高风险类别单独设规则，直接走更强的模型或人工。

### 托管路由器和自己写路由怎么选？

托管路由器（例如 Foundry model router）省去路由逻辑的开发，但规则不由你定义，且受平台的模型池和上下文窗口限制。自己写路由时，选项、阈值和升级策略都在你的代码里，可以按业务风险细调。无论选哪种，都要和当前基线对比质量、成本和延迟。

### 换了模型之后，阈值要重新验证吗？

要。换了做判断的决策模型、应用接入的作答模型、选项描述或阈值，都要在标注数据上重新评估。不同决策模型的置信度算法也不一样。
