第一单交付完,我等 3 个月没复购:不是交付差,是这 3 件事没做

第一单交付完,我等 3 个月没复购:不是交付差,是这 3 件事没做

第一个企业 AI 项目验收那天,挺顺的。甲方把验收单上的每一条都打了勾,尾款按合同在验收后十个工作日内到账。我当时想法很简单:做得这么顺,二期应该会来。

然后我等了三个月,一条消息都没有。

那段等待期和我做另外两个项目是重叠的,三个项目合计营收 50 万+,但第一个客户始终没动静。我一开始把原因归到交付质量上,回头把验收单翻出来看,每一条都是勾。后来我跟甲方对接人聊了一次,他的意思大概是:系统一直在跑,确实没出过事,但内部没人说得清它到底帮上了什么忙,所以也没人提二期。

这是我做 ToB 之后最贵的一课:**项目交付完,不等于合作开始。**验收单上的勾只证明功能在,不证明价值被看见。

后面我把三件事补上,同一个客户先追加了运维,又追加了第二个场景。今天把这三件事拆开讲,配一个我每周在跑的脚本。

一、质保期里,我给客户的不是承诺,是一张运行账

第一单的质保期是 30 天。我的做法是「有问题随时找我」。听起来负责任,实际等于把 30 天过成静默期:系统没崩,我也就没有任何主动输出。客户内部的体感是「好像没什么变化」。

改法很土:每周五下午发一段运行台账,不需要我手写,脚本跑一遍就出来。字段就五个,一周多少次调用、成功率、P95 多少毫秒、人工介入几次、兜底触发几次。

为什么要这五个?因为甲方关心的事只有三类:有没有人真的在用(调用量)、靠不靠得住(成功率 + P95)、是不是还得人盯着(人工介入 + 兜底)。五个字段正好把三类都盖住。

口径得先说死,不然周报发出去一定被追问。我用的是这一套:成功率指最终拿到了可用答案的比例,走了兜底的也算,因为兜底本身也是一种答案;兜底率和转人工率是单独统计的质量构成,不要求和成功率加起来等于一百;P95 统计的是全部有返回的调用,含兜底,谁想只看主链路,把 fallback 为 true 的记录过滤掉再算一遍就行。

日志我统一落成 JSONL,一行一次调用,脚本直接读这个文件:

go 复制代码
package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"math"
	"os"
	"sort"
)

type Record struct {
	TS       int64 `json:"ts"`             // Unix 秒
	OK       bool  `json:"ok"`             // 是否成功返回
	Latency  int   `json:"latency_ms"`     // 端到端耗时
	Fallback bool  `json:"fallback"`       // 是否走了兜底
	Human    bool  `json:"human_takeover"` // 是否转人工
}

func p95(xs []int) int {
	if len(xs) == 0 {
		return 0
	}
	cp := append([]int(nil), xs...)
	sort.Ints(cp)
	idx := int(math.Ceil(float64(len(cp))*0.95)) - 1
	if idx < 0 {
		idx = 0
	}
	if idx >= len(cp) {
		idx = len(cp) - 1
	}
	return cp[idx]
}

func main() {
	if len(os.Args) < 2 {
		fmt.Println("usage: weekly <calls.jsonl>")
		os.Exit(1)
	}
	f, err := os.Open(os.Args[1])
	if err != nil {
		fmt.Println("open failed:", err)
		os.Exit(1)
	}
	defer f.Close()

	var rs []Record
	var bad int
	sc := bufio.NewScanner(f)
	sc.Buffer(make([]byte, 1024*1024), 1024*1024)
	for sc.Scan() {
		line := sc.Bytes()
		if len(line) == 0 {
			continue
		}
		var r Record
		if err := json.Unmarshal(line, &r); err != nil {
			bad++
			continue // 脏行跳过,不让一条坏数据把周报搞挂
		}
		rs = append(rs, r)
	}
	if err := sc.Err(); err != nil {
		fmt.Println("读取失败:", err)
		os.Exit(1)
	}
	if len(rs) == 0 {
		fmt.Println("本周无调用记录,请确认采集端是否正常上报")
		os.Exit(2)
	}

	var okCnt, fbCnt, humanCnt int
	lat := make([]int, 0, len(rs))
	for _, r := range rs {
		if r.OK {
			okCnt++
		}
		if r.Fallback {
			fbCnt++
		}
		if r.Human {
			humanCnt++
		}
		lat = append(lat, r.Latency)
	}

	total := len(rs)
	fmt.Printf("本周调用 %d 次\n", total)
	fmt.Printf("成功率 %.1f%%\n", float64(okCnt)/float64(total)*100)
	fmt.Printf("P95 耗时 %d ms\n", p95(lat))
	fmt.Printf("兜底触发 %d 次(%.1f%%)\n", fbCnt, float64(fbCnt)/float64(total)*100)
	fmt.Printf("转人工 %d 次(%.1f%%)\n", humanCnt, float64(humanCnt)/float64(total)*100)
	if bad > 0 {
		fmt.Printf("跳过解析失败的行 %d 条,本周数据不完整\n", bad)
		os.Exit(3)
	}
}

跑出来的结果是这样的(示例数据,字段口径按你的场景调):

erlang 复制代码
本周调用 1842 次
成功率 96.4%
P95 耗时 2380 ms
兜底触发 61 次(3.3%)
转人工 17 次(0.9%)

这段东西看着粗,但它是甲方内部唯一能转发给领导的证据。我后来才知道,第二笔预算能过,是靠其中一位业务同事把这段台账贴进了周会材料。

**别小看「可被转发」这件事。**你写的东西如果只能你自己看懂,它就出不了对接人那一层。

二、第一单没沉淀出底座,第二个场景就得重做一遍

第二个场景报价高,是我自己造成的。第一单做完,检索、权限、审计、兜底这四块是直接写死在业务代码里的。换一个场景,这四块要重来。

客户算过账:第一期 12 万,第二期如果还是这个量级,两年内很难批。真正让第二个场景谈下来的是我把这四块抽出来之后,第二期只算场景增量,底座复用不重复收钱。

我在验收会上补了一张表,专门讲这件事:

模块 第一单做的 第二个场景能不能复用 复用后省掉什么
检索 按业务文档切分 + 混合召回 能,换知识库即可 不用再调召回策略
权限 按角色过滤可见文档 能,换角色定义 不用再做一遍越权测试
审计 全链路调用留痕 能,直接接 不用再设计留痕字段
兜底 超时降级 + 转人工 部分能,阈值要重定 不用重搭降级链路

这张表的作用不是技术归档,是给客户一个「第二个场景会更便宜」的预期。没有它,客户默认第二期等于第一期。

三、验收会上我没说清二期怎么算钱

这一条最容易被忽略。我当时的验收会只讲「这一期做完了」,没讲「下一期怎么计价」。结果客户内部默认二期要走一次完整采购流程,流程一长,人就懒得提了。

后来我在验收材料最后固定加一页,只有三句话:

  1. 底座的复用清单(就是上面那张表),二期不重复计价;
  2. 二期按场景增量报价,给出计价口径(按场景数 / 按问答量档位,选一个,别两个都写);
  3. 运维单独算,按月或按季度,写清包含哪些响应(响应时限、可用率、每月变更次数上限)。

三句话写完,客户才知道「继续做下去」这件事的门槛有多低。

补上三件事之后的差别

没做的时候 补上之后
质保期 有问题找我,30 天静默 每周一份运行台账,5 个字段
第二个场景 从零重做,报价接近第一单 复用底座,只算增量
二期计价 客户以为要重新招标 验收会上就给了口径
客户内部 没人说得清帮上了什么 台账可直接转发给领导

第一单 12 万,做的是企业知识问答;后面追加的运维和第二个场景,是同一条线上长出来的。三个项目三个月,合计营收 50 万+,利润率 75% 以上,成本主要压在「一个人 + 三个 AI 员工」这个结构上,不是压在报价上。

这里要说清楚一件事:我不是把 ToC 砍了去做 ToB。训练营一直在招,ToB 是另外长出来的第二条线。只是 ToB 这条线有个 ToC 没有的特点,它靠复购,不靠新客。一个客户做完不续,你就要再花一遍获客成本。

顺手说一个我踩过的坑

最早的运行台账我是手写的,写了两周就断了。原因不是懒,是「这周没什么可写的」。后来改成脚本生成,才发现没有可写的那几周,恰恰是调用量掉下去的那几周。

**能被自动生成的指标,就不要靠人记。**人会自我美化,脚本不会。


你现在手上的项目,交付完之后有没有一份能发给甲方领导的运行台账?如果没有,你打算先加哪五个字段?评论区说说,我挑典型的单独写一篇。

相关推荐
晨间晓1 小时前
图片里的关键点怎么找?用华为云码道开发 OpenCV FAST 特征点检测工具
人工智能·opencv·华为云
技术小结-李爽1 小时前
【工具】怎么在终端中启动springboot项目?
java·spring boot·后端·maven
科创致远1 小时前
科创致远 ESD 静电监控全景落地指南
人工智能·信息可视化·制造·精益工程
晨间晓1 小时前
二值图里的噪点怎么去掉?用华为云码道开发 OpenCV 形态学操作工具
人工智能·opencv·华为云
今年下半年1 小时前
AI智能体项目实战记录(05):RAG项目实战之向量知识库milvus部署
人工智能·llm·milvus·rag
haluhalu.1 小时前
深入理解 AI Agent(1.2):Harness 工程——模型之外的竞争力
前端·数据结构·人工智能·python·langchain·个人开发
fpcc1 小时前
AI和大模型—矩阵相似
人工智能·线性代数·矩阵
data analyse 4561 小时前
服务端统计时区切换的日期归并
大数据·前端·数据库·人工智能·数据分析
DongQiShanRen1 小时前
六根硬件映射(下):物理闭环五步法,抽一条已实装的看
大数据·jvm·人工智能·算法·rust·哈希算法·集成学习
鸿翼Macrowing1 小时前
鸿翼入选 2026 年上海市 “AI + 制造” 服务商能力图谱
人工智能·制造·opencontent ai·ai+制造