
最近 Perplexity 在 AI 搜索产品中引入了一个很有意思的交互设计——让用户手动选择推理或搜索的“努力程度”。这个功能在技术圈讨论度很高因为它不再把 AI 当成一个黑盒而是把「质量与成本」的权衡交还给用户。本文从产品设计、技术拆解、工程实现三个角度分析这种“粒度化努力程度选择器”到底是什么、底层如何调度以及如果要自己实现一套类似的推理强度控制机制应该从哪些环节入手。1. 背景与核心概念1.1 什么是“努力程度选择器”“努力程度选择器”最早出现在 Perplexity 的 Pro 搜索和部分 AI 产品设置中。用户在发起提问之前可以选择这次搜索希望 AI 投入多少“算力”或“推理深度”。常见的档位包括快速回答响应速度优先适合简单事实查询。标准搜索默认档位平衡质量与速度。深度研究多轮检索、来源、交叉验证最终生成更长的综述。从产品形态来看这就像一个“速度与质量”的滑块。但从技术角度来看它真正控制的是下游一整条链路模型采样参数、搜索广度、检索轮数、上下文拼接长度、回答详细程度甚至后台任务的超时时间。之所以叫“粒度化”是因为从早期二分档快速/深度升级为更细的多档选择甚至未来可能支持按查询类型自动匹配档位。这种设计背后的目标很明确用户不是每次提问都需要模型“火力全开”通过显式控制努力程度可以显著降低服务端算力成本同时提升响应速度。1.2 它的本质是“成本-质量”权衡机制在很多大模型应用中质量和成本之间存在直接冲突。为了获得更好的答案模型需要更多的推理步数例如 chain-of-thought 更长。更多的上下文需要检索更多文档并拼接成全局长文本。更长的生成长度。多次自我校验或反思。这些都会带来更高的 Token 消耗和更长的 GPU 占用时间。如果不加控制所有请求都按最高规格执行服务商很难平衡预算用户也会因为等待过久而不满。努力程度选择器的本质是把这种「成本-质量权衡」从服务端搬到用户侧。用户在发起请求之前先用一个选择器明确本次请求的预算等级系统随后根据档位动态调整整条处理链路。1.3 为什么这是值得关注的架构方向过去很长一段时间AI 产品对用户暴露的“控制开关”非常有限。用户只能输入 Prompt期待模型调整输出。但“努力程度选择器”这类功能代表了一个新方向把推理资源按用户意图进行精细切分。从工程视角看这种选择器不是前端一个下拉框那么简单它背后至少涉及请求协议扩展。推理服务的多档配置。搜索 Agent 的动态轮次控制。流式响应策略调整。计费与配额系统联动。所以不管你是 AI 应用开发者、后端架构师还是产品经理理解这种机制都有实际价值。本文接下来会从产品定义出发逐层拆解到代码实现。2. 产品与技术定位2.1 不同档位到底在控制什么我们可以把“努力程度”拆成以下五个维度。任何一个维度都可以独立调整但产品上通常把它们打包成档位组合。维度低努力档高努力档模型采样参数temperature 偏高top-p 变小生成更直接temperature 偏低推理更聚焦生成更长搜索深度单轮搜索Top 5 结果多轮搜索逐步追问Top 20 结果上下文窗口只保留最相关片段拼接多来源长文本支持长上下文Agent 反思轮数0 到 1 次2 到 3 次自我校验输出长度100-200 字摘要2000 字以上的综合报告这种设计让产品可以用同一套模型底座服务不同场景。一个简单的“今天天气怎么样”不应该消耗深度研究档的算力而“帮我写一份开源协议对比分析报告”则需要高努力档支持。2.2 粒度选择的背后是一种“任务分诊”在传统搜索系统中有一个概念叫 query understanding也就是在检索前先判断用户意图。现在的努力程度选择器可以看作把一部分“意图分诊”的责任交给用户。不过系统的自动分诊能力也在同步进化。Perplexity 的探索方向之一是让系统自动判断当前问题是否需要深度搜索或者根据历史行为预测用户预期的努力档位。也就是说“努力程度选择器”并不一定永远是用户手动点选它完全可以作为接口的一个参数由上层策略引擎自动设置。2.3 对开发者意味着什么如果你正在开发自己的 AI 应用这种“多档控制”思想其实可以直接复用。你不一定需要做成用户可见的 UI也可以在 API 层预留一个effort_level参数。后续当你接入更强大的模型、更大的上下文或更贵的第三方服务时上层代码可以平滑切换不需要改动业务逻辑。3. 整体架构设计如果把“努力程度选择器”放到一个完整的 AI 应用架构中它并不是一个独立服务而是一个横切关注点。它会影响到网关路由、Agent 编排、模型推理、缓存策略等模块。下面先看一个整体架构图文字描述版用户请求 ↓ 前端应用选择器 UI ↓ API 网关解析 effort_level ↓ 任务分诊服务 → 配置中心拉取档位策略 ↓ 搜索 Agent / 多轮检索模块 ↓ Prompt 组装器按档位拼接上下文 ↓ LLM 推理服务多档采样参数 ↓ 流式响应控制器 ↓ 返回用户 计费记录在这个链路中effort_level至少要经历以下处理前端序列化为枚举值例如low、medium、high。网关解析并校验附加到内部请求上下文。配置中心根据档位返回对应的策略集合。Agent 编排层根据策略决定检索轮数和来源数量。推理层根据策略覆盖默认的采样参数和最大 Token 数。完成后按档位记录成本指标。3.1 前端选择器与交互层前端是用户最先接触的部分。选择器组件虽然简单但需要处理好「默认档位」「降级提示」「流式响应展示」等问题。一个好的交互方式是默认使用medium档用户点击深度档时提示“响应时间会更长”避免用户对延迟产生焦虑。如果用户上一次选择了high可以在一定时间内记住这个偏好不需要用户重复设置。3.2 API 网关与请求协议设计当用户选择了一个努力档位它需要随请求一起传到后端。比较合理的协议格式是在统一请求体中增加一个effort字段或者放入 Headers 中供网关拦截。{ query: 比较 Kubernetes 和 Docker Swarm 在生产环境的优缺点, effort: high, stream: true, session_id: user-1234 }网关侧的作用是校验枚举值并把档位信息翻译成内部的策略标签。这样做的好处是当后端档位数量变化时前端不需要跟着修改。3.3 配置驱动的多档策略集不同档位对应的策略最好放在配置中心而不是硬编码在后端代码里。这样可以做到动态调整某一档位的行为无需发版。针对不同用户群体设置不同默认档。支持 A/B 测试不同策略组合。在高峰期临时降低高努力档的资源配额。一个典型的策略 JSON 结构如下。{ low: { search_rounds: 1, top_k: 5, max_tokens: 200, temperature: 0.7, enable_reflection: false }, medium: { search_rounds: 2, top_k: 10, max_tokens: 800, temperature: 0.4, enable_reflection: true }, high: { search_rounds: 4, top_k: 20, max_tokens: 2500, temperature: 0.2, enable_reflection: true, enable_cross_verify: true } }配置中心可以基于 Spring Cloud Config、Apollo 或 Nacos 实现。关键是让后端服务能实时感知配置变更并在下一次请求时生效。4. 前端实现示例下面用一个简单的 React 示例展示努力程度选择器的交互实现。这里假设组件需要支持三档选择并且在切换高努力档时提示用户响应时间可能变长。4.1 创建组件// 文件路径src/components/EffortSelector.tsx import React, { useState } from react; type EffortLevel low | medium | high; interface EffortSelectorProps { defaultValue?: EffortLevel; onChange?: (level: EffortLevel) void; } const OPTIONS: Array{ value: EffortLevel; label: string; desc: string } [ { value: low, label: 快速回答, desc: 适合简单查询 }, { value: medium, label: 标准搜索, desc: 质量与速度平衡 }, { value: high, label: 深度研究, desc: 响应时间会更长 } ]; const EffortSelector: React.FCEffortSelectorProps ({ defaultValue medium, onChange }) { const [level, setLevel] useStateEffortLevel(defaultValue); const handleChange (value: EffortLevel) { setLevel(value); if (value high) { console.warn(用户切换到了深度研究模式注意响应延迟提示); } onChange?.(value); }; return ( div classNameeffort-selector {OPTIONS.map((option) ( label key{option.value} className{effort-option ${level option.value ? active : }} input typeradio nameeffort value{option.value} checked{level option.value} onChange{() handleChange(option.value)} / span classNameeffort-label{option.label}/span span classNameeffort-desc{option.desc}/span /label ))} /div ); }; export default EffortSelector;4.2 在页面中调用// 文件路径src/App.tsx import React, { useState } from react; import EffortSelector from ./components/EffortSelector; interface SearchRequest { query: string; effort: low | medium | high; } function App() { const [effort, setEffort] useStatelow | medium | high(medium); const buildRequest (query: string): SearchRequest ({ query, effort }); return ( div classNameapp h1AI 搜索演示/h1 EffortSelector defaultValue{effort} onChange{(level) setEffort(level)} / button onClick{() { const req buildRequest(Kubernetes vs Docker Swarm); console.log(发送请求:, req); }} 发起搜索 /button /div ); } export default App;前端部分最重要的是把「选择器状态」和「请求体字段」绑定到一起。在实际项目中这个状态可能还需要放进全局状态管理如 Redux、Zustand或者存入 URL Query 中方便分享链接时保留设置。5. 后端调度与参数映射前端把effort传过来之后后端要做的事情就多了。下面重点讲两个点如何设计一个策略加载器以及如何把策略映射到大模型推理参数上。5.1 策略加载与校验建议把档位枚举定义在后端共享模块中前端和后端通过 OpenAPI 文档保持一致。后端在收到请求后先校验枚举值合法再从配置中心加载对应策略。# 文件路径services/effort_policy.py from dataclasses import dataclass from typing import Optional dataclass class EffortPolicy: search_rounds: int top_k: int max_tokens: int temperature: float enable_reflection: bool False enable_cross_verify: bool False class EffortPolicyManager: def __init__(self, config_source): self.config_source config_source def get_policy(self, level: str) - Optional[EffortPolicy]: raw_config self.config_source.get(feffort_policy.{level}) if raw_config is None: return None return EffortPolicy( search_roundsraw_config[search_rounds], top_kraw_config[top_k], max_tokensraw_config[max_tokens], temperatureraw_config[temperature], enable_reflectionraw_config.get(enable_reflection, False), enable_cross_verifyraw_config.get(enable_cross_verify, False) )这里只展示核心思路实际项目中还需要加缓存、默认值兜底、配置变更监听等逻辑。5.2 将策略映射到推理参数拿到策略之后需要把策略中的参数注入到模型请求中。以 OpenAI SDK 风格为例# 文件路径services/search_service.py from services.effort_policy import EffortPolicyManager class SearchService: def __init__(self, policy_manager: EffortPolicyManager): self.policy_manager policy_manager def generate_answer(self, query: str, effort: str): policy self.policy_manager.get_policy(effort) if policy is None: raise ValueError(f不支持的 effort 档位: {effort}) # 根据档位设置不同的模型采样参数 llm_params { model: gpt-4o-mini, messages: [ {role: system, content: 你是一个严谨的搜索助手。}, {role: user, content: query} ], max_tokens: policy.max_tokens, temperature: policy.temperature, } # 根据多轮搜索策略动态调整检索轮次 search_results self.multi_round_search( query, roundspolicy.search_rounds, top_kpolicy.top_k ) return self.compose_answer(llm_params, search_results)需要注意的是不同模型服务的参数名可能不同例如 Anthropic 使用max_tokens和temperatureGemini 使用generation_config。因此在实际项目中参数映射层需要做适配。5.3 高努力档下的 Agent 反思机制高努力档通常不只是多搜几轮那么简单还会启用 Agent 反思机制。所谓反思就是让模型先输出一个草稿然后检查信息是否充分、是否存在矛盾再决定是否补充检索。简化版实现如下# 文件路径services/reflection_agent.py class ReflectionAgent: def __init__(self, llm_client, max_reflections: int 2): self.llm_client llm_client self.max_reflections max_reflections def run_with_reflection(self, query: str, initial_context: str): draft self.llm_client.complete( promptf基于以下资料回答问题\n{initial_context}\n\n问题{query} ) for round_idx in range(self.max_reflections): check_prompt ( 请检查以下回答是否存在信息缺失或事实矛盾。 如果存在指出需要补充检索哪些信息。\n\n f回答草稿\n{draft} ) feedback self.llm_client.complete(promptcheck_prompt) if self.is_sufficient(feedback): break # 如果有补充方向执行新一轮检索并更新草稿 additional_search self.search_with_feedback(feedback) context f{initial_context}\n{additional_search} draft self.llm_client.complete( promptf基于补充资料重新回答\n{context}\n\n问题{query} ) return draft def is_sufficient(self, feedback: str) - bool: return 信息完整 in feedback or 无需补充 in feedback def search_with_feedback(self, feedback: str): # 根据反馈提取关键词并调用搜索 API keywords self.extract_keywords(feedback) return self.search_api.search(keywords)反思机制对提升回答质量很有帮助但它会显著增加响应时间和 Token 消耗这也是为什么它只在高努力档启用。6. 搜索深度的动态调整策略努力程度选择器不仅影响模型生成还直接影响搜索链路。在传统 RAG 应用中检索通常是一次性完成的用户提问 → 向量检索 → Top K 文档 → 拼接上下文 → 生成回答。但在努力程度选择器的框架下检索应该是多轮、动态的。低努力档只检索一次高努力档则会先做广撒网检索再根据初步结果决定是否需要补充检索。6.1 多轮检索的流程第 1 轮根据原始 query 检索 Top 10 第 2 轮从 Top 10 中提取关键实体和子问题分别检索 第 3 轮交叉验证找出不同来源中的矛盾点 第 4 轮补充检索最新信息生成最终答案每一轮检索都会把新的上下文追加到记忆缓冲区中。为了让模型能在超高努力档下消费更多上下文Prompt 组装器需要按照“相关性从高到低”排序并自动截断超出 Token 上限的部分。6.2 动态 top_k 控制策略中的top_k可以动态变化。第一轮先用较大的top_k保证召回率之后每一轮逐步缩小范围聚焦到更相关的文档上。def dynamic_top_k(query: str, current_round: int): if current_round 1: return 20 elif current_round 2: return 10 else: return 5这样做的好处是既保证了信息覆盖面又不会让最终上下文过于冗长影响模型对核心问题的关注度。7. 接口层协议与数据存储7.1 统一响应格式当选择器被前端设置后后端响应应该携带实际采用的策略档位方便前端做展示和调试。一个标准的响应结构如下{ answer: Kubernetes 更适合大规模容器编排..., effort_applied: high, metrics: { search_rounds: 4, sources_used: 18, total_tokens: 3210, latency_ms: 8450 } }通过返回metrics用户可以直观看到高努力档多消耗了多少资源这也有助于后续的计费说明。7.2 成本与用量记录由于不同档位的成本差异很大在生产环境中必须按档位记录用量。建议在数据库表中额外增加effort_level字段与请求 ID 关联。-- 文件路径schema/usage_records.sql CREATE TABLE usage_records ( id BIGINT PRIMARY KEY AUTO_INCREMENT, request_id VARCHAR(64) NOT NULL, user_id VARCHAR(64) NOT NULL, effor_level VARCHAR(16) NOT NULL, search_rounds INT DEFAULT 1, sources_count INT DEFAULT 0, prompt_tokens INT DEFAULT 0, completion_tokens INT DEFAULT 0, total_tokens INT DEFAULT 0, latency_ms INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_request_id (request_id), INDEX idx_user_effort (user_id, effort_level) );通过这张表运营同学可以直接分析不同档位的使用占比、成本分布、热门时段。不建议直接把成本金额写在这张表里因为模型单价可能变化建议只记录 Token 数后期再用价格表计算成本。8. 常见问题与排查思路在实现类似“努力程度选择器”的过程中比较容易遇到下面这些问题。问题现象常见原因解决思路无论选哪个档位响应速度和内容都一样后端没有读取 effort 参数或配置中心策略未生效在日志中打印实际使用的 effort 和策略快照确认参数传递链路高努力档响应超时多轮检索反思机制时间过长增加超时控制、并发检索、限制反思最大轮数Token 消耗激增成本暴涨高努力档的 top_k 和 max_tokens 设置过大设置单次请求 Token 上限添加成本熔断策略前端选择器状态与后端实际档位不一致前端没有把 effort 序列化到请求体检查网络请求 Payload 中的 effort 字段配置中心修改策略后不生效客户端没有监听配置变更改造为启动时拉取 定时刷新 监听变更推送模型回答变散、不聚焦高努力档下拼接了过多无关上下文优化相关度排序控制最大上下文长度8.1 排查链路推荐当线上问题出现时建议按照以下顺序排查检查前端请求体确认effort字段是否传入。在网关层打印请求头与参数确认路由是否正常。查看策略加载日志确认读到的策略内容与配置一致。打印模型推理参数确认max_tokens、temperature等是否被覆盖。检查用量表对比不同档位的 Token 与延迟指标。8.2 避免策略配置混乱当配置项较多时很容易出现某个档位的参数没配全。推荐在配置中心增加一个“策略完整性校验”任务定时遍历所有档位检查必需的字段是否都存在。任何缺失或非法值都应触发告警。9. 最佳实践与工程建议下面这些建议来自实际开发 AI 搜索类产品的通用经验可以帮你少走弯路。9.1 不要把档位逻辑散落在业务代码中努力程度选择器是一个横切关注点。如果每个业务方法里都写if effort high: ... else: ...后续扩展档位会非常痛苦。建议把所有与档位相关的参数抽取到策略对象中业务代码只依赖策略对象不关心具体档位。# 推荐写法 policy policy_manager.get_policy(request.effort) answer search_service.generate(query, policy) # 不推荐写法 if request.effort high: search_rounds 4 elif request.effort medium: search_rounds 29.2 建立默认值与降级机制选择一个原则当配置中心不可用或者某个档位的配置缺失时系统应该能自动降级到medium档。不要把low作为降级目标因为low档可能因为参数过少导致答案质量不可接受。建议在后端增加一个健康检查接口返回当前生效的策略快照方便调试GET /internal/effort-policy { current_version: 2025-06-01, policies: { low: { search_rounds: 1, max_tokens: 200 }, medium: { search_rounds: 2, max_tokens: 800 }, high: { search_rounds: 4, max_tokens: 2500 } } }9.3 用户偏好持久化如果用户是高强度研究者每次搜索都要手动切换深度档位会很烦。建议在用户配置表中加入default_effort_level字段用户切换时异步写入下次请求直接使用默认档。9.4 按用户等级限制最高档高努力档消耗的资源是低努力档的几倍甚至十几倍。为了防止滥用建议对免费用户限制最高只能使用medium档付费用户可以使用high档。这个限制可以在网关层实现也可以在计费系统中做配额控制。9.5 监控与告警指标需要重点监控的指标包括各档位平均延迟。各档位 Token 消耗占比。高努力档失败率。高努力档超时率。配置变更后的错误率。如果发现high档的请求占用了过多资源但整体成功率和用户满意度没有提升就应该考虑调整该档位的策略参数或者增加用户侧的成本提示。10. 从 Perplexity 到自有产品一条可复用的落地路线如果你希望在自有 AI 应用中加入类似机制建议按照以下路线推进。10.1 第一阶段先建协议与策略模型这一阶段不急着做前端 UI先在 API 层增加effort字段后端实现策略加载和参数映射并输出日志。通过压测验证不同档位在延迟、Token 消耗、回答质量上的差异。10.2 第二阶段做前端选择器前端先做成简单的三选一按钮不涉及复杂交互。上线后观察用户点击分布。如果绝大多数请求都集中在medium说明自动档位判断比手动选择更重要。10.3 第三阶段引入自动档位预测收集历史请求数据后可以训练一个简单的分类模型根据问题类型、关键词、历史行为预测默认档位。例如问题包含“对比”“分析”“报告” → 高努力档。问题包含“时间”“天气”“定义” → 低努力档。10.4 第四阶段接入计费与配额控制生产环境必须把档位和计费打通否则高成本请求会非常危险。建议在请求完成后异步写入用量表并对单用户的高努力档次数做日配额限制。11. 总结与学习路线本文围绕 Perplexity 的“粒度努力程度选择器”展开从产品概念、技术架构、前端交互、后端调度、参数映射、成本控制、降级策略等多个角度进行了拆解。这种功能看起来是 UI 上的一个小改动实际上要求整条链路都具备“按档位调度”的能力。如果你准备在自有项目中实现类似能力建议先掌握以下知识点FastAPI 或 Spring Boot 中的请求参数校验与配置注入。LLM API 的采样参数含义如temperature、max_tokens、top_p。多轮检索 Agent 的编排方式与超时控制。配置中心的使用例如 Nacos、Apollo。用户配额与用量采集方案。下一步可以尝试把现有 RAG 应用改造成支持多档位。改动不需要很大先加一个effort字段和策略映射表再用日志和监控数据观察不同档位的真实表现。如果条件允许还可以做一个简单的 A/B 测试对比「手动选择档位」和「系统自动预测档位」两种模式下用户的满意度和资源消耗差异。把这个机制做好不仅能提升产品体验也能让你的 AI 应用在成本和体验之间找到更好的平衡点。动手改一版试试相信你会对 AI 应用的工程化有更深的理解。