Akke · 资料入库 · 2026-08-05

资料上传,从甲方手机到 AI 嘴里
一共要过 6 个落脚点

甲方交上来的一张检测报告、一份配置清单、一张实拍图,中间要过 国内中转 → 私有桶 → 台账 → 分档 → 公网图库/知识库 → 向量 才轮得到 AI 说出口。 这页把三个入口、每一次中转、每一个岔路都摆在一张图上——东西现在停在哪、下一步谁来推,看图就知道。

4
个上传入口(门户 / 案例截图 / 运营批量 / 门店工作台)
2
处国内中转(Fly worker 代页面、代传文件)
15min
分档 cron 节拍,每轮最多 12 份
1
道人工闸:只有绿灯自动入库
01

怎么来:甲方门户的一份资料是怎么落地的

主线是「甲方点开一条 token 链接 → 传文件 → 我们记一笔台账」。看着简单,但国内网络逼出了两处绕行: 页面要 Fly 代开,文件也常常要 Fly 代传。

④ 文件本体:浏览器直接 PUT 到 Storage,绕开 Vercel 的 4.5MB 请求体上限 客户端 甲方手机 · 微信里点开 一条 token 四件事 reply_annotation_tokens 中转 · 国内可达 Fly worker · 东京 portal_relay.py 纯透传,不持任何密钥 业务层 Vercel · Next.js 校 token / 类目 / 大小 / sha 路径服务端拼,不收前端 落脚 · 原件与台账 Supabase 桶 portal-materials(私有) client_material_submissions ① 打开链接 fly.dev/portal/… ② 透传 GET 页面 + /_next ③ 签直传地址 ⑤ 回头写台账 国内:PUT /api/portal-storage(X-Upload-Target 头) Fly 代 PUT 签名地址 · 只放行 https + *.supabase.co + /storage/v1/object/upload/sign/
两条绕行都是被网络逼出来的。上面那条弧是设计本意:文件不经过我们的 API,避开 Vercel serverless 4.5MB 的请求体上限(手机照片普遍 3–8MB,超了平台直接掐线、浏览器永远转圈)。 下面那条紫线是国内变体:*.supabase.co 在国内连 DNS 都解析不了,所以文件先 PUT 给 Fly、由 Fly 代传——签名地址必须过三条校验才转发,不是开放代理。 文字类资料(价目口径这种甲方手上没现成文件的)不走弧线,直接走 ⑤ 写台账。
02

怎么去:进了台账之后,谁来推它

台账不是终点,是停车场。每 15 分钟有一趟 cron 来把车分成绿黄红三档: 只有绿灯自动开走,黄红两档原地不动、只推一张 Lark 卡点名让人来处理。

回填 ingested_to / ingested_ref —— 不填,甲方页面上永远显示「已收到」 台账 · 人工闸的入口 client_material_submissions status=received · 刻意不自动进库 每 15 分钟来一趟 节拍 /api/cron/portal-material-watch 每轮 12 份 · 只取 triaged_at 还空着的 判断 VL 读图 → 分档(两道硬闸:PII、价格) qwen3-vl-235b · 签 10 分钟私有 URL 给模型看 判据跑在读出来的内容上,不看文件名 绿灯 · 自动开走 实拍图,读过、干净、不打架 无个人信息 · 无价格 · 与既有库不冲突 autoIngestGreen() 黄灯 · 原地不动 读到价格,或可能覆盖既有口径 钱的事机器不敢拍板 只推一张 Lark 卡点名 红灯 · 原地不动 个人信息 / 聊天截图 / 外部链接 自动传公网 = 隐私上公网 只推一张 Lark 卡点名 落脚 · 公网 + 向量 R2 图库 → kb_entries → kb_chunks 私有桶链接会过期,AI 发不出去,所以要上 R2 worker /embed → 512 维 → 写 kb_chunks 落脚 · 人工核过再搬 运营收件 scripts/portal-inbox.ts → 一条条搬进正式库 事实类 → knowledge_documents | 话术类 → conversation_examples(三个 tag 齐了才算数) 图片类 → R2 图库 | 品牌字段 → brand_profile | 退回写 reject_reason,甲方看得见 消费端 kb_search() 余弦召回 → AI 发得出这张图 只写 entry 不写 chunk,等于没传 消费端 getOrgContext 拼进 system prompt | approved-replies 命中就原文直发 客户再问「有检测报告吗」,AI 不再把活推给设计师
三档的分界不是文件类型,是风险。绿灯只覆盖「读过、没隐私、没价格、跟既有口径不打架」的实拍图; 只要 VL 在图里读到价格,或者这份可能盖掉两周前的口径,就一律停在台账里等人。 每份处理完不管什么颜色都盖 triaged_at——只读一次、只提醒一次; 代价是黄红档不会重复催,没人处理就是没人处理,要重新提醒得手工把 triaged_at 清空。
03

第二个入口:案例截图,这条是全自动的

/upload/<token> 是另一条独立链路——销售传聊天截图,当场 OCR、当场给反馈、当场蒸馏成知识/话术候选。 和门户共用一个 Fly 中转的招数,但用的是另一张 token 表、另一个桶、另一套闸

客户端 销售 / 甲方 浏览器 fly.dev/upload/<token> 多图串行,一张一张给反馈 中转 · 国内可达 Fly upload_relay.py 页面 + 静态资源 + 表单三条路 超时给到 90 秒,等 OCR 跑完 业务层 · 三道闸 /api/upload/[token] 配额上限 · 同图去重 · 10MB/三种图 enterprise_upload_tokens 落脚 · 原件与台账 case-uploads 桶 enterprise_case_uploads status=pending ① 打开 ② 透传 ③ 收图 ④ 同步跑,不让上传的人干等——单张 3~15 秒,跑完当场回一句「读到了什么」 识别 VL OCR 读整段对话 读不出东西 → rejected_empty 读崩了 → failed,原图还在可后补 底座 · 不直接上线 enterprise_sales_cases case_id 由 sha 派生 → 重传幂等 只当蒸馏原料 蒸馏 · 出事不影响上传 runDistillPipeline + AI 判官 标反说话人、口径冲突都会被标出来 deepseek 分析模型 分级落库 case_distill_candidates 判官过 + 不涉价 → 自动进正式库 涉价 / 冲突 / 标反 → 留人工
这条敢全自动,是因为它只收一种东西。门户九类什么都可能来(价目表、检测报告、聊天截图、实拍图), 案例截图这条只收聊天截图、只干一件事:读成结构化案例。即便如此,钱相关的候选照样不自动上线—— 涉价、口径冲突、说话人标反三种一律留人工。蒸馏是 best-effort:它挂了上传照样成功,底座已经存下,候选可以后补。
04

中转站清单:东西可能停在哪一格

「我传的资料到底在哪」——按这张表从上往下找,每一格都能查得到、也都能说清为什么在这停。

停靠点存的是什么为什么在这停怎么查 / 怎么推走
Fly worker
portal_relay · upload_relay
什么都不存(纯过路) Vercel 和 Supabase 在国内打不开,页面和文件都要有人代跑一趟 只转发固定上游单一 host,不是开放代理;出问题看 Fly 日志
Supabase 私有桶
portal-materials
甲方交的原件
<org>/<类目>/<sha256>.<ext>
原件必须留底,日后口径打架要拿它核对 私有,链接会过期;portal-inbox.ts --download 拉到本地
台账表
client_material_submissions
谁在什么时候交了哪一类、状态走到哪 刻意不自动进库——一份写错的价目表进库,线上所有报价一起错 pnpm tsx scripts/portal-inbox.ts 看待处理
案例台账
enterprise_case_uploads
每张案例截图的处理结果
pending / ingested / rejected_empty / failed
OCR 可能读空、可能读崩,得有地方记一笔好重跑 按 status 查;原图还在桶里,可以后补识别
候选暂存
case_distill_candidates
蒸馏出来还没上线的知识/话术 涉价、口径冲突、说话人标反的一律不敢自动上 运营审过才进正式库;判官驳回的记 rejected
门店事实08-05 补
sw_documents · sw_facts
店长在工作台屏 01 粘进来的口径,抽成的 125 条事实 这一格没有闸——抽完直接可用,原件和上传人都没记(见第 07 节) 屏 01 逐条看;停用改 status=expired
话术标注池08-05 补
reply_annotation_cases
红蓝对抗低分、实盘 AI 打太极、店长标「这句不对」的问答,现 100 条 要经人工复核才准进范例池——改错一句污染全线上 甲方标注台 /annotate/<token>;工作台屏 03 / 06 也往这里写
R2 图库
公网 URL
实拍图、效果图的长期可访问地址 私有桶链接会过期,AI 发给客户的图必须一直打得开 红灯档(含个人信息的)绝不允许进这一格
正式库
kb_chunks · knowledge_documents · conversation_examples
AI 真正读得到的那一份 到这一格才算「资料变成了 AI 会说的话」 图靠向量召回;事实拼进 system prompt;话术靠三个 tag 命中
05

这条链路上踩过的坑(都是真金白银换的)

下面每一条都对应线上真实发生过一次,改动这条链路之前先扫一眼。

4.5MB

大文件不能过我们自家 API

Vercel serverless 请求体上限 4.5MB,超了平台直接掐断连接、不返回任何 body——用户看到的就是「点了一直转圈」。所以文件一律走签名地址直传。

桶设置

真正的闸在桶上,不在代码里

API 白名单放宽到 20 种格式,桶的 allowed_mime_types 只开了三种图,照样全退。代码怎么读都是对的,甲方看到的却是「存的时候出错了」。桶设置不归 migration 管,换环境要手工建。

召回

只写 entry 不写 chunk = 白传

检索查的是 kb_chunkskb_entries.embedding 那一列根本没人查。库里一度有 88 条孤儿条目,拿「有没有检测报告」去问一条都召不回。

幂等

一份资料只读一次、只提醒一次

甲方一口气交 60 份全判黄灯,老写法每 15 分钟把这 60 份重新送进 VL 读一遍图(烧真钱、结论一样),Lark 还每 15 分钟重推一张卡。现在处理完就盖 triaged_at

分档判据

文件名是最不可信的那一层

叫「498配置」的图,里面印的是 798;标「同户型完工案例」的九张其实是高端样板间。所以分档必须跑在 VL 读出来的内容上。

隐私

聊天截图永远不自动上公网

客户姓名、手机号、车牌、住址、支付单号都在截图里。自动传 R2 就等于隐私上公网,这类一律红灯留人工,价值改以脱敏文字形式进话术库。

08-05 补 · 没人读的资料

存了不用的资料,错了也没人发现

enterprise_knowledge 里那份价格档位把「其余地区」写成 798/498(正确是 868/568),躺了两个多月。因为没有任何代码读它,错也不会有客户来问。一接线就全暴露——是运营逐条核才抓出来的。

08-05 补 · 冲突检测

同名才算冲突,换个名字就检测不到

判据是「同一个 key、两条 value 不同」,而 key 是模型每份资料现取的名字。同一个「568」被 5 个不同 key 登记了 5 遍,一条都没报冲突;798/498 那个真冲突也因为 key 不同溜过去了。「待处理冲突 0 条」不等于没冲突。

06

第三个入口,和几件容易搞混的事

第三个入口是运营自己灌的scripts/r2/upload-kb.ts 这类批量脚本,把 22G 的家装宝典 / 效果图 / 知识图集直接推到 R2 再登记进库。它不经过门户、不进台账,属于「我们自己准备的料」,不是甲方交的。

  • 「资料上传」不包括图文发布时传的那 6 张图——那是发抖音的素材,走云电脑/手机 GUI,是出稿链路不是入库链路。
  • 也不包括 Cookie 导入、号源批量导入——那两个上传的是凭据和名单,不进知识库。
  • 门户和标注台共用一张 token:同一条链接换个路径就是另一件事,吊销时两个页面一起失效。
  • 话术进 conversation_examples 必须带三个 tag(甲方审定 / 只给企微 / 可原文直发),少一个就是静默永不命中——读取方在 wecom-chat 那个仓,改一边不改另一边查都查不出来。
以下为 08-05 对着代码核过之后补的四节
07

第四个入口:门店工作台自己也在收资料

前面三条都是「甲方或运营把东西交上来」。还有一条走的是完全不同的门: 店长在门店工作台屏 01 直接把口径粘进去,过一次模型抽成事实, 再由「桥」喂给 AI。它和门户那条互不知道对方存在

客户端 店长 · 门店链接 /t/<token> 屏 01 粘正文,不传文件 台账 · 只有半张 sw_documents 有:类目 / 标题 / 正文 / 状态 缺:原件 · 上传人 · 落库回填 加工 extractFacts deepseek · maxTokens 8000 一份资料抽 10~40 条说法 落脚 · 无人工闸 sw_facts 125 条 · 抽完直接 active 冲突检测只认同名 key ① 粘正文 ② 过模型 ③ 直接可用 桥 · 默认关 WECOM_STORE_WORKBENCH_BRIDGE=1 开了才把 sw_facts 并进 knowledgeDocs、把说话规则拼进 system prompt 关掉即回滚:不改代码、不重新部署 对照 · 门户那条(第 01 节) 原件留底 → 台账 → VL 分档 → 绿灯才入库 → 回填去向 同样一份价目表,走门户会被扣在黄灯等人核; 走屏 01 粘进去,抽完就是 AI 能引用的事实。 对照
这条入口没有第 01、02 节里建的任何一道防护。 原件不留底(file_url 5 份全空,因为是粘正文进来的)、没有「谁上传的」这一列、 抽完事实直接就是 active——没有绿黄红分档,也没有人工闸。 2026-08-05 逐条核对时抓出 14 处错(798/498、免费量房、终身免费保养、内部纪律混进对客资料……), 全靠人一条条看出来,系统一条都没拦住。 「谁上传-什么原件-哪些处理-落到哪」这件事,门户那条已经全有了,缺的正是这一条。
08

同一批口径,现在存了三份

这是上面那条平行入口带来的直接后果,也是08-05 所有错的总根源: 同一件事(比如「其余地区多少钱一平」)在三张表里各有一份,而三份不一定一致。

这一份多少条谁在读它现在是什么角色
knowledge_documents 74 线上 AI 每一轮都读 事实上的「准的那一份」——但没人正式这么定过
sw_facts 125 桥打开才读(现在关着) 门店工作台自己抽的,出处指向粘进去的正文,不是原件
enterprise_knowledge 5 份 JSON 没有任何代码读 2026-06-01 存进来的甲方原始档,建表注释写着「暂不直接接进线上,需要时再单独蒸馏导入」——那一步两个多月没人做

798/498 那个错就是这么发生的:

  • enterprise_knowledge 里躺着一份写错的价格档位(其余地区 798/498)
  • 从来没被 AI 读过,所以错了两个月也没人发现
  • 做屏 01 时把它抄进了 sw_facts,还顺手写进了机器人的说话规则
  • 桥一开就会说给真实客户听——是运营看到才发现的,冲突检测一条都没抓到

所以第一件该做的事不是搬数据,是先定「哪一份是准的」。不定这个,后面每一步都在搬可能是错的东西。

09

话术这一侧:08-05 新接的三条回流

前面几节讲的都是「资料怎么进来」。还有一类东西是用出来的—— 店长在日常检查里做的判断,本身就是最好的话术素材。这三条今天才接上,之前写完没有任何下游会读

店长做了什么接到哪然后呢
屏 07 改常问问题的标准答案
改完跑 3 种问法测试
conversation_examples
打 client-approved / verbatim-ok
线上命中相似度 ≥0.88 就原文直发、跳过模型——问法变体一起送,不然换个说法就命中不了
屏 06 标某段聊天「答得不对,应该这么说」 reply_annotation_cases 接甲方审定话术那条已有通路:人工复核conversation_examples。刻意不直接进——改错一句污染全线上
屏 03 给机器人回答打分 + 写正确说法 回写题目的来源行
题本来就是从标注池挑的
同上,走人工复核那道闸

还缺一格:东西进错了怎么撤

前面所有格子都是单向的,只有进没有退。而 08-05 这一天就撤了 24 条。

已撤 24 条

事实层:改 status 就够

错的、过时的、内部纪律混进来的、重复的,改成 expired 即从可用集消失。原件和历史都还在,能追回来是谁什么时候撤的。

最要命的一格

话术层:进了范例池就直接影响客户

打了 client-approved 的问答会被原文直发。撤回靠的是 tag 能一条 SQL 全删——所以 tag 必须打全(来源屏、批次、通道),否则撤都不知道撤哪些。

开关级

桥:关掉即回滚

fly secrets unset 秒级生效,不改代码也不重新部署。这是整条链路上唯一一个「一键全撤」的位置。