45天实战复盘:从零开发 AI 系统的血泪史

51 阅读5分钟

引言

最近 AI 圈子很热闹,各种新模型层出不穷。但作为一个一直在写代码的开发者,我越来越感觉到:跑通 Demo 和做一个能上线的业务系统,中间隔着巨大的鸿沟。

这段时间我一直在死磕一个基于大模型的智能就业服务系统(包含简历深度解析、人岗精准匹配、AI 模拟面试官)。在这个过程中,我踩了无数个坑,重构了三版代码。

今天不想发什么“震惊体”新闻,只想以一个开发者的身份,和大家聊聊真东西。不管你是正在做毕设的同学,还是想转型 AI 开发的同行,希望能通过这次复盘,和大家在这个浮躁的圈子里做一次纯粹的技术交流。


一、 为什么传统的 RAG 在“人岗匹配”里完全行不通?

在项目初期,我天真地以为:把 JD(职位描述)存进向量数据库,把简历 Embedding 一下,算个 Cosine Similarity(余弦相似度)不就完事了吗?

现实狠狠打了我的脸。

1. 语义相似度的陷阱

当我搜“Java 高级工程师”时,向量数据库经常给我召回“Java 培训班讲师”。

原因在于: 在向量空间里,“写 Java 代码”和“教 Java 代码”在语义上极度接近,但在业务逻辑上却是南辕北辙。通用的大模型 Embedding(如 OpenAI 的 text-embedding-3)很难理解这种垂直领域的细微差异。

2. 我的解决方案:两段式检索 + 业务重排

为了解决这个问题,我把检索流程重构成了一个“漏斗”:

  • 第一层:硬性过滤 (SQL Where)

    在 Embedding 之前,先提取元数据(Metadata)。利用 LLM 把非结构化的 JD 转化为 JSON:

    {
      "min_salary": 15000,
      "location": "Hangzhou",
      "degree": "Bachelor"
    }
    

    检索时,直接把不符合硬性条件的数据 Pass 掉。这步不靠 AI,靠传统数据库,稳准狠。

  • 第二层:混合检索 (Hybrid Search)

    同时通过 BM25(关键词匹配)和 Vector(语义匹配)去召回。因为在招聘领域,“SpringBoot”、“Vue3”这些专有名词的精确匹配权重,往往高于语义理解。

  • 第三层:BGE Rerank (重排序)

    这是最关键的一步。我接入了一个专门的 Rerank 模型,把前 50 个召回结果重新打分。它能精准识别出:“虽然这个简历里提到了 Java,但他主要经历是做销售”,从而把它排到最后。

👉 交流点: 大家在做垂直领域 RAG 时,是用什么策略解决“幻觉检索”的?有没有试过 GraphRAG(知识图谱检索)?最近我也在研究这个方向,欢迎探讨。


二、 让 AI 稳定输出:从“调教 Prompt”到“类型工程”

简历解析模块时,我最大的痛苦是:LLM 太不可控了。

同一个 Prompt,第一次它输出完美的 JSON,第二次它就在 JSON 外面给你加了一句:“这是您要的结果,注意查收哦~”。这直接导致后端解析失败。

我深刻体会到:Prompt Engineering(提示词工程)的尽头是 Type Engineering(类型工程)。

放弃“自然语言乞求”,拥抱 Schema

后来我全面转向了 Pydantic + LangChain Structured Output 的模式。

我不告诉模型“请输出 JSON”,我直接把 Python 的 Class 定义传给它:

class ProjectExperience(BaseModel):
    name: str = Field(..., description="项目名称")
    role: str = Field(..., description="担任角色,如:后端负责人")
    tech_stack: List[str] = Field(..., description="提取出的技术栈列表,标准化命名")
    difficulty_score: int = Field(..., description="根据项目描述,评估技术难度(1-10)")

这样做的好处是,模型不仅知道要输出什么格式,甚至能帮我做数据清洗(比如把“Vue.js”、“Vue2”都标准化为“Vue”)。

👉 交流点: 你们在处理长文本(比如超过 10k token 的超长简历或论文)提取时,是采用 MapReduce 分段提取,还是由长窗口模型(如 Gemini-1.5-Pro)直接硬吞?我很想听听大家的实测体验。


三、 Agent 开发:当 Chain 变成了 Graph

项目后期开发“模拟面试官”时,线性的 Chain(链式调用)已经不够用了。

面试是一个动态过程:

  • 如果用户回答得好 -> 追问底层原理(难度 Upgrade)
  • 如果用户回答不上来 -> 换个话题或给提示(难度 Downgrade)
  • 如果用户偏题 -> 纠正方向

这实际上是一个状态机(State Machine)

于是我把架构从 LangChain 迁移到了 LangGraph。通过定义 State(状态)和 Edge(流转条件),让 AI 能够根据用户的实时反馈动态决定下一步动作,而不是傻傻地按顺序提问。

虽然 LangGraph 的上手门槛有点高,代码量也上去了,但它让 Agent 真正有了“脑子”,而不是复读机。


四、 一些心里话与资料分享

在这个系统的开发过程中,我最大的感触是:AI 只是核心引擎,软件工程才是底盘。 即使模型再强,没有好的架构设计、错误处理和数据清洗,做出来的也就是个玩具。

因为篇幅有限,很多代码细节(比如怎么做 PDF 表格还原、Rerank 的参数微调、Agent 的内存管理机制)没法在这里一一展开。

既然是技术交流,我也想把我的经验分享出来。

我花了两天时间,把自己在这个项目中的技术架构图RAG 优化清单以及核心模块的脱敏代码整理成了一份 AI 工程化实战笔记

如果你是:

  1. 正在为 AI 毕设/论文发愁的学生;
  2. 想自己动手写一个 AI 应用的开发者;
  3. 或者单纯想找人聊聊 AI 技术落地的同行;

欢迎在评论区留言“交流”,或者直接后台滴滴我。

我不是什么大神,只是一个喜欢折腾代码的普通开发者。发这篇文章的初衷,就是想在这个快节奏的时代,找几个能一起静下心来聊技术、聊代码的朋友。

希望能和大家在 AI 的路上结伴而行。