十余年,为风投构建一套情报平台
做一套风投情报平台真正需要什么——数据质量、三十多个集成、以及让系统在凌晨三点还能跑的那些无聊基础设施。
第一次会议 briefing 功能完整跑通,是一个周二早上六点五十八分。办公室里还没人,一条 Slack 消息就出现在频道里了。点进去:公司名、最新一轮融资、过去两年的员工增长、创始团队和投资团队之间的 LinkedIn 关系链、十四个月前某位同事在 Salesforce 记的一条通话记录。没有人手工整理过这些,也没有人开口要求过。系统定时触发,从六个不同的数据源各自拉了数据,打包成文档,自动发出去。
这个功能背后的每一块,我分开建了两年。Calendar 集成、关系图谱、公司数据管道、Slack bot,各自独立存在了很久。把它们接在一起,让整条链路第一次完整跑通,那个早上记得很清楚。
我们是怎么介入的
我们以技术合伙人的身份进场。客户是一家风投基金,核心问题不是信号太少,而是数据太分散、流程太碎——CRM 和邮件不互通,LinkedIn 和 Crunchbase 分开看,创始人通话的记录可能在某个人的笔记本里。
我们的工作是理顺这些流程,提升数据质量,并在合适的地方引入 AI。这件事最终做了十多年。
我们构建了什么
一句话版本:一套为风投基金设计的公司情报和关系管理平台。
长版本:一个共享的 Rails 引擎在后台持续运行,追踪数以千计的公司,从三十多个数据源拉数据,给投资团队拼出每家公司的完整画像。它驱动六个应用——主 Web 界面、独立 API、公司入库 pipeline、pipeline review 工具、数据仓库和一个 OAuth 服务。所有应用共用同一套数据层。
每天用它的分析师不需要感知这些。这正是它应该有的样子。
公司身份问题
最难设计的不是某一个具体的集成,而是一个更根本的问题:每个数据源对同一家公司的描述都不一样。
LinkedIn 说这家公司有 250 人,Crunchbase 说 180,ZoomInfo 说 310。SimilarWeb 显示网站流量在下滑,Apptopia 显示 App 下载量在涨。说的是同一家公司,数字对不上。
我设计了一套叫做 Entity–Profile–Site 的三角关系来处理这个问题。Entity 是公司的规范记录——唯一一行,唯一一个域名,唯一的事实来源。Profile 是某个 Site(数据源)对这家公司的描述视角。Crunchbase 有它自己的 profile,LinkedIn 有它自己的,每个都存储来自该数据源的原始 JSONB 数据,不做修改。
每天一次,或者每当某个 profile 更新时,一个派生数据 pipeline 跑起来——读取该公司所有 profile,按数据源权重融合,把时序数据混合处理,最终写入 Entity 的 derived_data 字段,供下游功能查询。
把这套聚合逻辑做对,花了好几年。数据库现在超过 800 次迁移,约 300 张表。我在 JSONB 字段上建了基于表达式的 GIN 索引,分析师才能在那些深层嵌套的指标上做筛选,不用等好几秒才出结果。
后台 Worker 的规模
数据不会自己跑来。我们要去取。
生产环境里同时跑着 27 个以上的 Sidekiq 进程,每个负责特定的队列。每个数据源有自己的 job namespace:Crunchbase::*、LinkedIn::*、Salesforce::*、Office365::*、Notion::*,app/jobs/ 目录下超过 50 个 namespace。
每个数据源走同一套生命周期:定时任务触发一个 discovery job,job 调用该数据源的 API client,原始数据落进 Profile,系统判断这个 profile 是否已经属于某个 Entity(如果没有,就做模糊匹配),派生数据重算,feature 和 score 更新。
让这套流程在规模下稳定运行的,是那些不会出现在 changelog 里的东西:限速时自动退让重试,Redis 单线程锁防止并发 job 踩踏同一个 entity,去重 key 阻止同一个目标在一天内被重复处理。这些年沉淀成了一套 concern 组合——job 去重、限速、单线程执行、API 凭证轮换——job 通过 include 合适的模块来获得对应行为。
没人会把这些写进 release note,但它们是系统能在凌晨三点无人值守的原因。
人的问题
做决定的不是公司,是人。
平台追踪具体的个人——创始人、高管、投资人——记录职业经历、LinkedIn 关系、邮件往来,以及和投资团队的互动历史。这里的身份识别问题真的很难。LinkedIn 上的”John Smith, CEO at Acme Corp”可能和 Salesforce 里的”Jonathan Smith”、Office365 联系人里的”J. Smith”是同一个人。我做了四种合并策略:按邮箱合并、按昵称和别名合并、按跨邮箱匹配合并、按姓名模糊相似度合并。
当它奏效的时候,投资团队能看到某位同事两年前和这位创始人喝过咖啡,另一位同事和 CTO 是 LinkedIn 一度好友。这就是冷邮件和有效引荐之间的区别。
最让我头疼的集成
三十多个集成里,Salesforce 是唯一一个我可以单独写一篇文章的。
双向同步在设计文档里看起来很清晰。实际情况是:公司 entity 推送到 Salesforce account,Salesforce 联系人被拉回来变成人的 profile,任务完成状态双向同步,account 归属变更在两个系统之间传播——与此同时,用户还在两边同时编辑记录。
我做了一个”dispatch center”模式来协调同步操作,避免触发无限更新循环。七年了,它还是会跑出新的 edge case。我已经跟这件事和解了。
AI 进场
平台大部分时间里,“情报”是结构化的——融资信号、员工增长、流量趋势、评分变化。2022 年前后,我们开始往里加 LLM,“有用”这件事的定义也跟着变了。
开篇提到的会议 briefing 功能,是这套思路最直观的体现。会议前一晚,系统拉取日历事件,识别关联的公司或人,把平台掌握的所有信息——融资历史、员工增长、最新动态、关系链路——交给 LLM 做综合,在团队进门前把简报发进 Slack。没有人调度这件事,也没有人写这些内容。AI 负责语言,结构化数据负责事实。
这成了我们此后每一个 AI 功能的设计模式:模型处理语言,pipeline 保证真实性。
Chatbot 问答。 分析师可以用自然语言问任何在库公司的问题——“他们工程团队过去一年的招聘趋势怎样?""我们团队有谁认识这家公司的人?“——答案来自实时结构化数据,不是静态文档。交互是对话式的,内容是有来源的。
AI 邮件起草。 投资团队想联系某位创始人时,系统会基于关系上下文起草开场白:共同认识的人、过去的互动记录、公司最近的里程碑事件。分析师编辑后发出去。AI 解决第一稿,人专注于真正的关系。
LLM 叙述摘要。 每个公司 profile 现在都有一段 AI 生成的文字,把结构化数据综合成可读的段落——这家公司做什么、增长态势如何、现在有什么值得关注的地方。它和原始指标并排呈现,不是替代品。
基于 pgvector 的 RAG 检索。 投资备忘、通话记录、Notion 文档全部向量化存储。用户提问时,系统先检索语义最相关的片段,再生成回答——答案基于基金自己的文档,而不是模型的通用知识。
评分模型。 增长速度、团队背景、主题契合度、关系亲疏——多维度信号汇聚成复合评分,给 pipeline 里的公司排序。这不是替代判断,而是改变什么先被看到。一个分析师一周要过两百家 inbound,不可能给每一家同等的注意力。评分模型把值得多看一眼的那些推到前面。
投资主题分类。 一个训练好的分类器读取公司 profile,预测它最符合基金哪个投资方向。用于 inbound 筛选,也用于在已有数据库里发现被忽视的标的,还给团队提供了一套跨 portfolio 做模式匹配的共同语言。
这些没有替代结构化数据 pipeline,它们叠在上面。结构化数据给 AI 准确的锚点,AI 让结构化数据变得可读、可操作。底层的 entity 记录如果是脏的,就没办法写出有用的公司摘要。AI 功能加得越多,越感谢当年花在清洗和规范化数据上的那些年,而不是少了它们。
十多年教给我的事
这一路最让我意外的:数据质量是产品问题,不只是工程问题。最难搞的 bug 不在代码里,在数据里——改了名字的公司,被悄悄收购的域名,两年换了三次工作的人。系统需要对”这是谁”有自己的判断,而这些判断需要不断被质疑和更新。再好的代码也弥补不了一条脏的规范记录。
那些枯燥的基础设施比我预期的更重要。Job 去重、Redis 锁、限速、幂等写入——不会让 changelog 好看,但它们是系统为什么可靠的原因。
还有一件事:跟一个投资团队深度合作这么多年,改变了我对技术合作本身的理解。分析师不用数据模型思考,他们用关系、模式识别、和创始人对话的感觉来思考。做出一个他们真正会用的东西——而不是技术上正确但悄悄被忽视的东西——需要始终贴近真实的工作方式,而不只是需求文档。
这里的技术复杂度是真实的。但更难的判断是:哪些复杂度值得现在建,哪些可以等。这种判断只有在一个问题里待得足够久、不再被它轻易惊到之后,才会真正长出来。