售前报价这活儿,看着简单,实际干过的人都知道,真正花时间的不是“算”,而是“找”。销售要出一份报价,得翻历史投标文件、翻成交记录、问老同事“上次那个XX行业的项目大概什么价”,最后还得手动套模板,把清单明细一项一项填进去。我这次做的售前报价Agent,核心目标就是把这套流程压缩成一句话:输入客户需求和基础参数,Agent自动找案例、做澄清、生成报价草稿。整个项目从原型到内部试运行,最让我纠结的不是模型选型,而是七个工程决策——每一条都直接影响最终能不能从“找案例”走到“出报价”。这篇文章就把这七条决策完整复盘出来,包括当时为什么这么选、实际跑下来踩了哪些坑、如果重新做我会改哪里。适合正在做垂直领域Agent落地、或者想用大模型改造内部业务流的团队参考。
1. 项目起点:报价Agent到底要解决什么问题
1.1 售前报价的真实痛点:不是“不会算”,而是“找不到依据”
在动手之前,我花了两周时间跟着售前团队走了一遍完整报价流程。一开始我以为痛点在于“算价”——价格算错、漏项、折扣不一致,但实际观察下来发现,真正的瓶颈是“找依据”。售前工程师面对一个新客户需求,通常要做的事情是:先判断这是什么行业、什么规模的项目,然后回忆公司以前有没有类似案例,再去历史报价单里翻类似配置的价格,最后才谈得上填报价单。
问题就出在“回忆”和“翻历史”这两步。历史报价单分散在共享盘、CRM系统、个人电脑里,格式五花八门,有的含税有的不含税,有的含三年维保有的纯软硬件。一个入职不到一年的售前,找案例可能要找半天,找到之后还得肉眼判断这个历史案例的报价结构能不能复用。导致的结果是,报价速度慢、价格不统一、新人上手周期长。所以这个Agent立项的核心命题很清晰:能不能让系统自动从历史报价单里找到最相似的案例,再结合当前项目参数生成一份可编辑的报价草稿。
这决定了后续所有的技术选型方向。它不是一个“生成文案”的对话工具,而是一个“检索—理解—映射—结构化输出”的辅助决策系统。用户拿到的不是一段漂亮的报价说明文字,而是一份带字段的、可导入Excel的报价清单草稿。这个定位从一开始就定死了。
1.2 最初的原型方案与第一个教训:提示词堆叠不能解决信息缺失
立项之后我做的第一版原型,坦白讲是偷懒的。当时想用一个大模型+提示词的方案直接跑:把所有历史报价单以文本形式塞进上下文,提示词里写“请根据以下历史案例生成新报价”。跑出来的结果表面合理,一深究就露馅。模型会在历史案例没有覆盖到的项目上,自作主张地补一个“合理”的价格。
这个现象很典型:大模型的通顺性和事实性被混淆了。模型不是不想答对,而是它在信息不足时天然倾向于“推测”而不是“承认不知道”。对报价这个场景来说,一次推测就是一次定价错误。这也是我得到的第一个工程教训:提示词能够优化模型对已知信息的组织方式,但无法替代缺失的信息。必须先把“找案例”这个环节做成一个确定性的、可感知的检索模块,再把结果交给大模型处理。
所以,与其说我在做一个“智能体”,不如说我在做一个“搜索系统+生成系统”的组合体。这个认识,是我后面七个决策的共同出发点。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从“找案例”出发:检索架构的两个关键决策
2.1 决策一:案例检索优先于规则模板,让数据本身成为报价依据
第一个决策也是最根本的:案例检索优先于手工维护的规则模板。做之前我们内部讨论过另一套方案——建一套报价规则引擎,把产品线、折扣区间、行业系数全部维护成配置表,报价时按规则计算。这套方案的优点是稳定可控、每个价格都有明确出处,但缺点也很致命:维护成本高,且规则覆盖不了长尾场景。
举个例子,公司有个客户是做港口设备的,项目里既有传感器又有定制化软件开发,还附带五年运维服务。这种组合在标准产品线规则表里根本没法预置,就算预置了,价格怎么定还是得参考历史成交案例。反过来,如果检索系统能从过往案例里把类似组合的报价自动捞出来,那它就天然覆盖了长尾情况。规则模板只能回答“已知的已知”,案例检索能覆盖“已知的未知”。所以我当时做了一个判断:第一期以案例检索为主,规则引擎只做兜底校验,不参与定价推导。
这个决策的直接后果是,我必须为历史报价单建立一套可检索的数据库。这里有一个容易被低估的点:历史报价单的数字化程度。我们盘了一圈存量数据,发现至少有四成是扫描件和图片型PDF,不经过文本抽取根本没法进检索库。最后我们用OCR+人工抽检的方式做了首轮清洗,才让可用案例数量达到了能支撑检索的水平。如果当初选了规则模板路线,这批脏数据就完全用不上了。
2.2 决策二:向量召回为主、关键词路由为辅的双通道设计
确定了“检索优先”之后,下一个问题是怎么检索。我调研了一圈通用做法,最流行的方案是纯向量检索:把历史报价单的文本内容embedding化,然后对用户需求做语义相似度搜索。这个方案对“自然语言找案例”很友好,但落地时我发现了两个问题。
第一,历史报价单是半结构化数据,光用自然语言语义检索,很难表达结构化约束。比如用户说“要一个和去年那个智慧园区项目类似的报价,预算大概一千万”,语义上“智慧园区”能通过向量找到,但“一千万预算”这个数值条件向量表达就不稳定。第二,部分关键词对报价结构影响极大,比如“含税”“不含税”“纯软件”“软硬一体”,这些词一旦被语义相近的表述替换,检索结果就会跑偏。我实际测过一个例子:语义检索把“厂区安防监控系统”和“园区智能安防”判为高度相似,但前者可能只是买摄像头和录像机,后者可能包含平台软件、施工布线和三年运维,价格差好几倍。
所以最终采用了双通道召回:一路是向量语义召回,负责找“内容相似”的案例;另一路是结构化字段过滤,也就是关键词路由,专门抽取出行业、项目类型、预算区间、实施范围等标签做硬性过滤。两条通道的结果汇总后做一次融合排序,排序权重里,结构化条件匹配的优先级高于纯语义相似度。
用关键词路由做硬过滤,表面上多了一套工程,实际收益很大。它相当于给检索装了一道护栏,防止语义相似但报价结构不同的案例混进来。我后面试过把结构化过滤去掉让向量自己跑,结果Top10的准确率明显下降,尤其是跨行业案例误召回的情况变多。所以这个双通道设计,我认为是报价场景下最稳妥的默认方案。
2.3 试运行阶段的一次召回质量事故排查
双通道跑通后,内部试用阶段发生了一次很典型的召回质量问题。有个同事输入“客户需要一套楼宇自控系统,包含前端传感器、控制网关和平台软件”,系统返回的历史案例里排第一的是一个“住宅小区可视对讲项目”——它确实包含了传感器和网关,但价格参考价值很低,因为两者的实施复杂度和软硬件配比完全不同。
我排查的过程是这样的:先看结构化过滤层,发现楼宇自控的行业标签在历史库里被标记为“弱电工程”,和可视对讲在同一分类下,硬过滤没有拦下来。再看向量召回层,两个项目描述里的“传感器”“网关”确实是高共现词,语义相似度高。问题出在“分类粒度太粗”和“关键差异项没有参与检索”两个环节。修复方案是两步:第一,把案例库的品类标签细化到二级分类,弱电工程下面再拆出楼宇自控和可视对讲;第二,在检索条件里加入“实施模式”这个字段,凡是涉及施工的项目,单独作为硬过滤条件。修复后,类似误召回情况明显减少。
这个事故让我意识到一个常被忽视的点:检索质量不只是模型效果问题,很大程度上是数据标注粒度的问题。你的案例库分类粗,模型再准也拦不住概念混淆。这也是为什么后面我把数据清洗和差异项标注当成独立决策专门处理。
3. 从“找案例”到“出报价”:输出协议的工程决策
3.1 决策三:以结构化JSON作为输出协议,拒绝对话流式直出
很多做Agent的人习惯性会把输出做成“对话式”——用户问一句,模型答一段。但报价场景完全不适合。原因有两个:第一,报价单要进下游流程,比如导入CRM、生成PDF、横向对比价格,如果输出是一段自然语言文本,下游没法解析;第二,自然语言生成的报价,用户看不出哪些字段是参考了历史案例,哪些是模型自己推的,信任感建立不起来。
所以我当时做了一个反直觉的决策:让Agent的输出不是“报价单文案”,而是一个结构化JSON对象。这个JSON里包含四个核心部分:引用的历史案例ID列表、报价清单项(产品名称、规格、数量、单价、总价)、价格说明(基准价来源、调价因素)、风险提示(未覆盖项、需人工确认项)。模型只负责填充JSON里需要理解和判断的字段,所有计算性字段交给代码完成。
为了约束模型稳定输出,我设计了一个精简版JSON Schema作为输出协议,重点字段示例如下:
json复制{
"case_refs": ["CASE-2024-038", "CASE-2023-217"],
"line_items": [
{
"item_name": "数据采集网关",
"spec": "边缘计算型",
"quantity": 8,
"unit_price": 23500,
"price_basis": "参照CASE-2024-038同类项,考虑CPU配置升级加成5%"
}
],
"total_price": 2684000,
"price_notes": ["含13%增值税", "不含现场施工费用"],
"risk_flags": ["存储阵列型号未确认,报价预留偏差区间"]
}
这个决策的代价是需要花不少精力写schema约束和解析回退逻辑。模型偶尔会输出不符合schema的字段,比如把unit_price写成字符串,或者漏掉必填字段。我在解析层做了很憨但有效的兜底:解析失败就返回“需人工整理”而不是让模型强行重试。宁可让人接手,也绝不让格式错误的数据流进下游系统。
3.2 决策四:价格计算交给代码,大模型只做识别与解释
这是所有决策里我最后悔没有更早执行的。项目早期,我试图让大模型直接根据历史数据和当前需求计算总价,效果极不稳定。模型在“语言理解”和“逻辑计算”两个任务同时进行时,会牺牲计算精度。具体表现是:它能说出“这个项目和案例A很像,总价应该在300万左右”,但让它精确到个位数、并解释每一项是如何从历史价格乘以系数得到的,它就开始编。
我的处理方式是把价格计算彻底从模型推理中剥离。具体来说,Agent在拿到检索结果后,先由代码完成“价格映射”:把历史案例的清单项、单位价格、计价单位(台/套/项/人天)、税点、折扣率标准化成一个内部计算表。模型要做的只剩两件事:一是从用户原始需求中抽取参数(行业、规模、配置项、交付范围),二是把这些参数映射到历史案例的相应条目上,给出“价格调整原因”。乘法、加法、加税、折扣,全部由代码实现。
python复制def compute_total(items: list[dict]) -> float:
total = 0.0
for item in items:
base = item["base_price"] * item["quantity"]
factor = 1.0
if item.get("config_factor"):
factor *= item["config_factor"]
if item.get("delivery_factor"):
factor *= item["delivery_factor"]
total += base * factor
return round(total * item["tax_rate"], 2)
这个决定直接让报价总价的准确性从大概80%提升到了接近于100%(前提是检索到的案例确实可复用),并且每一个价格都有可追溯的推导链路。我后来在复盘文档里写了一句:在大模型应用里,永远不要让模型做它能被代码替代的事。模型的价值在于理解和解释模糊信息,不在于机械计算。
4. 数据与兜底:决定Agent上限的两个决策
4.1 决策五:历史案例清洗与“差异项标注体系”
前面提到历史报价单有四成是扫描件,但清洗不只是做OCR。真正的难点在于,不同业务线、不同时期的报价单,字段口径五花八门。比如有的报价单只写了“项目总价”,有的写“设备费+安装费+税费”,有的把“现场调试”归到“服务费”,有的单独列。如果我们把原始字段直接灌进模型和检索系统,那结果就是模型面对一堆口径不同的数据,要么理解混乱,要么自作聪明地统一口径。
为了解决这个问题,我设计了一个“差异项标注体系”,核心思路是:不为每条历史报价单做完整的字段映射,只标注对报价结果影响最大的几个差异项。这套标注包括六个维度:计价单位(台/套/项/人天/公里)、是否含税、是否含运费、实施边界(纯供货/供货+调试/交钥匙工程)、报价有效期、价格基准(成交价/挂牌价/投标价)。每个维度如果缺失,宁可把这条案例标记为“不完整”降权,也不让模型在不确定的情况下瞎猜。
这里有一个教训:一开始我们的标注是外包做的,效果不好,因为外包不熟悉业务,会把“报价有效性”和“质保期”混淆。后来我让一位做过两年售前的同事写了一份标注手册,把每个字段的业务含义和常见歧义写成选择题式的解释,再让外包按手册操作,准确率才上来。这个经历说明,领域数据的标注手册,本质上是对业务经验的一次结构化沉淀,不能随便找人代笔。
清洗后的案例库,从初始的3200条压缩到1700条可用。数量虽然下降,但检索质量的提升非常明显。后来的测试里,Top5案例的相关度从65%左右提升到85%以上。我觉得这个取舍很划算:宁可库小一点,每一条都可信,也不要库大而杂,让模型学会“从脏数据里找规律”——那不是智能,那是过拟合噪声。
4.2 决策六:多轮澄清的边界控制,问该问的,不问不该问的
我见过很多Agent案例,把“多轮对话”当作亮点:用户随便说一句,Agent就追问一堆问题。看起来互动性很强,但在企业工具里这恰恰是反面设计。售前人员用Agent是为了省时间,不是为了让Agent变成一个需要耐心教的新人。所以在澄清模块上,我做了非常强的边界控制。
首先,定义了一个“最小提问集”。必问项只有三个:客户行业、预算区间、交付范围(纯软件/软硬一体/含施工)。这三个字段直接影响检索条件和价格结构,不问清就没法往下走。其次,定义了一个条件提问集,只有当用户在第一轮输入里没有明确提及实施周期、维保年限、是否需要兼容现有系统时,Agent才针对这些提问,而且单个提问轮次最多不超过两轮。超过两轮仍然信息不足,Agent就基于已有信息生成带风险提示的报价草稿,绝不让用户陷入无限追问。
交互上还有一个容易被忽略的细节:提问不能是开放式填空,必须给选项。我测试过,让模型问“您的预算区间大概是?”和给出“A. 200万以下 B. 200-500万 C. 500万以上”的选项,后者模型理解准确率高出很多。开放式问题会诱导模型无根据猜测,选择题能约束模型的推理路径。这个设计看起来简单,但对可用性的提升是决定性的。
4.3 决策七:可观测性与人工兜底开关,从内测到上线的灰度策略
最后一个决策是关于上线方式的。当时团队里有两派意见,一派主张小范围试用之后快速面向全体售前团队开放,另一派主张必须等精度达到95%再放开。我的做法介于两者之间,但多了两个关键机制:可观测性和人工兜底开关。
所谓可观测性,即系统生成的每一个报价草稿,都必须能回放生成过程。你在界面上能看到“这个报价引用了案例A和B,其中网关价格参照A,服务器价格参照B;当前方案和案例C的不匹配点在于新增了20个物联网终端”。一旦报价被退回修改,系统会记录修改原因,形成一个新的反馈样本,自动回流到评估集里。这个过程一开始没有,是内测大概两周后加上去的,因为它直接决定了后续模型迭代速度。如果没有回放能力,你只知道系统表现不好,但说不清是检索问题、标注问题还是生成问题。
人工兜底开关则是另一个思路:Agent生成的所有报价单,默认状态是“草稿”,必须由售前工程师确认后才会转为正式报价。系统也会主动判级:如果引用的历史案例相似度低于阈值,或者风险提示项超过两个,报价单直接标记为“建议人工重审”。灰度策略上,我们先让六位资深的售前导师试用两周,重点收集两类反馈:哪些检索结果让他们觉得“根本不是同类项目”,哪些价格调整说明让他们觉得“解释得不够清楚”。这两类反馈直接驱动了检索算法和提示词的迭代,之后才逐步放大到整个售前团队。
5. 实测复盘:这套决策落地后的表现与仍存在的局限
5.1 在真实报价单上的对比测试结果
系统进入稳定期后,我们用最近半年的30个真实报价需求做了一次离线对比测试。测试方式是:让三位资深售前工程师按原有流程手工报价,同时在相同需求上跑Agent生成报价草稿,再让另外两位专家对结果盲评。
总体结果如下:
| 指标 | 手工报价 | Agent草稿(人工确认后) |
|---|---|---|
| 单份报价平均耗时 | 约4.5小时 | 约40分钟 |
| 参考历史案例覆盖率 | 约60% | 约90% |
| 报价总价偏差率(与最终成交价对比) | 约±12% | 约±8% |
| 需要人工调整的报价比例 | - | 约40% |
最让我惊喜的不是速度提升,而是参考案例覆盖率。手工报价时,售前往往只依赖自己记得住的那几个案例,而Agent不会“忘记”历史库里任何一条相似案例。这个特性在做跨行业新客户时特别有价值,客户换个行业,人工可能找不到参考,Agent却能从相似场景里捞出来。
5.2 仍存在的短板与后续扩展方向
必须承认,这套系统目前仍然有几个明显的短板。第一,历史库的覆盖度决定天花板:如果一个全新品类在库里完全没有类似报价,Agent的表现就退化为“告诉你找不到参考”,这时候它并不能帮上什么实质性的忙。第二,对“隐性成本”的感知不足:有些项目涉及复杂的现场实施条件,比如施工地点在偏远地区、客户现场需要断电改造,这类成本历史报价单里不会写,Agent自然也学不到。目前只能靠风险提示让售前工程师自行判断。
后续我计划在两个方向上扩展。一是在报价草稿生成后增加“成本敏感性分析”:当检索到多个相似案例时,展示不同案例之间的价格带分布,让售前工程师直观看到同类项目的成交价区间,而不是只给一个孤立的参考数字。二是把人工修改的数据持续回流到检索排序模型里,让“被采纳的调整”反向修正以后推荐的价格权重。我始终认为,Agent的价值不在于替代人,而在于让人觉得“这个工具懂行”,而“懂行”的前提是把人的经验不断吸收回来。
最后分享一点个人体会:如果你也在做类似的垂直领域Agent,最值得投入精力的不是怎么把模型调得更聪明,而是把你所在领域那套“说不清道不明”的经验规则梳理清楚。报价这行最值钱的不是算价能力,而是知道哪些案例可以相互比较、哪些客户需求看似相同但价格结构完全不同。把这个梳理透了,Agent就成了顺水推舟的事——模型只是把你理清的思路放进了可执行的壳里。
