v3.0 跑通的第二天,我发现它只有我自己能用 —— ai_collector v3.1 把求职 Agent 接进 MCP 生态的两个晚上

项目:AI Collector v3.1(在 v3.0 LangGraph Agent 基础上加 MCP Server + Bad Case 闭环)
仓库:https://github.com/nakajimamiyuki/ai_collector_project
作者:minjie / @nakajimamiyuki
标签:MCP LangGraph Agent生态 Bad Case闭环 转型


楔子:我造了个 Agent,然后把它锁在了硬盘里

v3.0 完工那晚我跑通了第一条端到端:

python scripts/find_jobs.py "杭州 AI Agent 1-3 年 15K+"
# → 反思一次 → 6 条命中 → Top 3 推荐

合上电脑的那一刻挺爽的。「我用 LangGraph 写了一个能自主决策的 Agent,它在帮我找工作。

第二天早上一打开终端,我盯着 README 看了几分钟,意识到一个尴尬的事实:

这个 Agent 只有我自己能用。

它躲在 scripts/find_jobs.py 里,必须 cd 到项目目录、激活 venv、敲一行 CLI。Hermes Agent 不知道它存在,Claude Desktop 不知道它存在,Cursor 也不知道。我造了一个能力很强的工具,然后把它锁在了自己的硬盘里。

更扎心的事在下午。我数了一下 6 月份采集到的 192 条 Boss 直聘 AI 岗位 JD ——

14 条明确写了「熟悉 MCP」「基于 MCP 封装业务工具」。

杭州 2 条、郑州 1 条,正好是我心仪的目标城市。

这 14 条 JD 在向我喊:会 MCP 的人很稀缺。

而我手里的 v3.0 项目,恰好就应该是一个 MCP server,但它现在不是

这就是 v3.1 的起点 ——

把 v3.0 从「自己能跑的 demo」,升级成「AI 生态原生公民」。

时间:两个晚上。昨晚摸黑装 mcp SDK + 写第一个 Tool,今晚一口气扩到 3 个 Tool、加 Bad Case 闭环、画架构图、写简历级 README。


一、为什么是 v3.1,不是 v4.0?

打开 v3.0 的代码画一遍当前架构,盯着图看了 10 分钟:

Boss / Bilibili / arXiv 采集 → SQLite + Milvus
                                  ↓
                       LangGraph Agent(5 节点 DAG)
                                  ↓
                          find_jobs.py CLI
                                  ↓
                          只有我自己能用

得出一个对我自己有点反直觉的结论:

v3.0 已经是一个能跑通的 Agent,没必要推倒重来。
我要做的是让它「长出三根新手脚」,分别接进三个新维度。

v3.1 要给 v3.0 加的三件事
  ① MCP Server 化   → 让 AI 生态调用我(核心)
  ② Bad Case 闭环   → 让 Agent 自己变好(工程闭环)
  ③ 简历级打磨      → 让 HR 30 秒看懂我(求职闭环)

v4.0 留给真正改架构的版本——多 Agent supervisor、商用 LLM 路由、端到端微调。

v3.1 是「加肉」,v4.0 才是「换骨」。

这个决策今天看起来理所当然,但实际上是花了 20 分钟自己说服自己的——转型期手痒,总想堆更多新功能,越堆越没法收尾。

一个能投出去的 demo,比五个半完成的功能值钱。


二、F1:让 MCP Server 从「只有一个工具」长出灵魂

2.1 昨晚的起点

昨天晚上 9 点开始动手。装 mcp SDK、写最小 server 骨架:

from mcp.server.fastmcp import FastMCP
mcp = FastMCP("ai-collector")

@mcp.tool()
def search_jobs(keyword: str, city: str = "", top_k: int = 5) -> str:
    """搜索已采集的 Boss 直聘岗位(字面匹配)。"""
    ...

if __name__ == "__main__":
    mcp.run(transport="stdio")

接进 Hermes Agent,能跑:

我:杭州有哪些提到 MCP 的岗位?
Hermes:(自动调用 mcp_ai_collector_search_jobs)返回 5 条匹配

爽,但简历摆出来就尴尬:

「我自研了一个 MCP Server,只有一个工具。」

面试官追问「就一个?」我能讲什么?必须扩。

2.2 该扩什么?三条标准

候选列了 5 个工具,我用三条标准筛:

  1. 面试演示价值——能不能在面试现场跑一段 demo 让面试官点头?
  2. 跟现有工具的对照关系——能不能形成「为什么需要这个」的故事?
  3. 真实数据卖点——能不能输出一段让我背下来当谈资的数字?

按这三条标准选出 2 个:

工具 演示价值 对照关系 真实数据
query_rag ⭐⭐⭐ 跟 search_jobs 形成「字面 vs 语义」对照 bge-m3 + Milvus 召回
get_skill_gap ⭐⭐⭐ 跟 search_jobs 形成「找岗 vs 找方向」对照 192 条 JD 跑出的市场热度

2.3 query_rag —— 让 MCP server 听人话

字面匹配的弱点很明显:

查:"会用 LangGraph 做反思决策的 Agent 岗位"
search_jobs → 0 条(没人 JD 里写「反思决策」四个字)

但实际数据里有一条 JD 写了:

“基于 LangGraph、LangChain、AutoGen 等框架开发 Agent 应用,运用 CoT(Chain-of-Thought) 实现复杂业务流程的自动化”

「运用 CoT」就是反思决策的语义近义词。这种召回,只有向量检索能做到

query_rag 直接复用 v3.0 已有的 vector_search_jobs

@mcp.tool()
def query_rag(question: str, top_k: int = 5) -> str:
    """用自然语言对 Boss 岗位做向量检索(bge-m3 + Milvus)。"""
    from src.agent.tools import vector_search_jobs
    try:
        records = vector_search_jobs(question, top_k=top_k)
    except Exception as e:
        # 关键设计:基础设施故障要翻译成 JSON 错误,不能让异常穿透
        return json.dumps({
            "error": f"{type(e).__name__}: {e}",
            "hint": "确认 ollama 在运行(ollama serve),bge-m3 已 pull",
            "hits": [],
        })
    ...

这里有一个细节藏着设计取舍:

基础设施故障(ollama 没起 / vector.db 不存在)要翻译成 JSON 错误,不能抛异常。

为什么?MCP 客户端拿到 isError=True 的 content 时,用户体验是「工具坏了不知道为啥」。返回带 hint 的 JSON,客户端能直接告诉用户「去启动 ollama」。

跑出来的真实例子:

query_rag("会用 LangGraph 做反思决策的 Agent 岗位", top_k=3)

[0.7974] Python(LangChain/LightRAG/AI Agent) | 深圳中迈 | 20-25K | 杭州
[0.7872] 高级 AI Agent 开发工程师            | 堃垚集团 | 13-25K | 郑州
[0.7851] agent开发-乾健科技                  | 武汉添才 | 20-35K | 济南

第三条 JD 写了「基于 LangGraph、LangChain、AutoGen 框架开发 Agent + 运用 CoT」 —— 字面搜不到「反思决策」,但语义检索召回了。这一条数据本身就是面试现场最好的 demo。

2.4 get_skill_gap —— 让 Agent 告诉我该学什么

这个工具的核心是三个步骤:

  1. 在 192 条 Boss 真实 JD 上做技能词频聚合
  2. 对照 my_profile.yaml 里我标的 already_have / learning / want_to_avoid
  3. 输出三张表:市场 Top N / 我的缺口 / 我学习方向的市场热度

跑出来的结果给我看傻了:

total_jobs_analyzed = 192

market_top_skills:                  skill_gap (我没掌握但市场要的):
  Python        108                   Agent           106
  Agent         106                   Prompt           72
  RAG            82                   Java             48  ← 转 Java 系会大幅扩盘
  Prompt         72                   微调             43
  LangChain      66                   LlamaIndex       32

learning_hits (我正在学的市场热度):
  多模态          41   ← 学这个 ROI 最高
  Dify          25
  Coze          16
  LangGraph     16
  MCP           14   ← 学这个稀缺度最高
  LoRA          11
  QLoRA          5
  RLHF           3

这张表的杀伤力在哪?

它告诉我:

  • 接下来 3 个月应该先攻多模态(市场热度 41 > LangGraph 16 > MCP 14),而不是凭感觉学
  • MCP 虽然只有 14 条但稀缺度极高,学它简历差异化大
  • Agent 这个标签市场上 106 条——这就是为什么我要把项目名从「AI Collector」改叫「求职 Agent」

面试时这张表是金句:

「我接下来三个月学什么?我让自己的 Agent 在 192 条真实 JD 上跑了一遍技能聚合,结果告诉我先攻多模态,其次是 LangGraph 和 MCP——你看,我连「我该学什么」都是数据驱动的。」


三、两个让我抓狂的报错

中间踩了两个非常有代表性的坑。值得单独写——因为它们能讲清楚一件事:

写代码的工程师能力分水岭,不在算法,在能不能 5 分钟内把故障定位到这一层。

3.1 坑 1:ModuleNotFoundError: No module named 'src'

写完三个 Tool,单元测试全绿。跑真实的 stdio MCP client smoke test:

✅ search_jobs 跑通  (5 matches)
❌ get_skill_gap   Error executing tool: No module named 'src'
❌ query_rag       Error executing tool: No module named 'src'

单元测试全绿、其中一个工具能跑、另外两个就死。

抓出来看,挂掉的两个工具里有这一行:

from src.agent.tools import compute_skill_gap, load_profile

而 search_jobs 没用这种延迟 import。

为什么 venv 里直接 import 是好的,子进程里就不行?

python src/mcp_server/ai_collector_mcp.py 当脚本运行时,Python 默认只把脚本所在目录(src/mcp_server/)加进 sys.path。顶层 src 包根本不在 import path 里。

单元测试不暴露这个坑,是因为 pytest 启动时会把项目根加进 sys.path。只有真实子进程模式才会原形毕露

修复一行:

PROJECT_ROOT = Path(__file__).resolve().parents[2]
if str(PROJECT_ROOT) not in sys.path:
    sys.path.insert(0, str(PROJECT_ROOT))

教训:写 MCP server / cron 脚本 / 任何「会被以子进程启动」的模块,永远要在文件顶部手动注入项目根。

3.2 坑 2:macOS 上的 OpenMP 双库 abort

修完坑 1,再跑:

✅ search_jobs    OK
✅ get_skill_gap  OK(纯 SQLite,不走 milvus)
❌ query_rag      McpError: Connection closed

为什么连接被关掉?看 stderr:

INFO     Loading faiss.
INFO     MilvusLite server started on port 61160
OMP: Error #15: Initializing libomp.dylib, but found libomp.dylib already initialized.
OMP: Hint This means that multiple copies of the OpenMP runtime have been linked into the program.

这个错我 v2.1 时就踩过,所以 v3.0 的 scripts/*.py 顶部都设了 KMP_DUPLICATE_LIB_OK=TRUE但今晚新写的 MCP server 入口我忘了设。

根因:

macOS 上 milvus-lite 内嵌的 faiss 链了一份 libomp.dylibnumpy / torch 各自再链一份。同一进程加载多份会触发 OpenMP 的运行时安全检查,直接 abort。 abort 之后 stdout 断了,stdio MCP client 看到的就是 Connection closed

根本想不到是 OpenMP 的问题,只能从 stderr 一行一行往回追溯。

修复就一行,但必须在 import faiss/milvus 之前:

import os
os.environ.setdefault("KMP_DUPLICATE_LIB_OK", "TRUE")  # 必须在所有 import 之前

3.3 这两个坑想说的事

转型期最浪费时间的不是「不知道答案的问题」,是「答案被 5 层抽象隐藏的问题」:

  • MCP client 报 Connection closed,怎么也想不到是 OpenMP 库
  • No module named 'src',怎么也想不到跟 pytest rootdir 机制有关

「懂 LangGraph 框架」对面试有用,但「能 5 分钟从 stderr 追到第三层抽象底下的根因」对真实工作更值钱。

这两个坑都是我从 stderr 一行一行往回追的,每一步都没快捷方式。我把它们写下来,不是炫"我修好了",是想告诉转型路上的同行人:这种排错能力没法速成,只能在踩坑中长出来


四、F2:Bad Case 闭环 —— 让 Agent 自己变好

4.1 这个想法是怎么来的

v3.0 跑通那天我跑了两个 query:

Run 1: "LangChain RAG 15K+ 1-3 年" → 6 条命中 ✅
Run 2: "郑州 25K+ LangGraph"      → 3 轮反思后 0 命中(故意稀有)❌

Run 2 看着很有「反思决策」的戏剧感(错配诊断 + 替代方案)。但是 ——

这两次跑完,下次我打开项目就忘了。没有任何持久化记录。

下次再跑同一个 query 时:

  • 我不知道上次跑过没
  • 我不知道上次结果是不是空
  • 我不知道上次空了之后我有没有修过什么
  • 我不知道这次修完之后是不是真的修好了

这就是典型的没有 bad case 闭环的 Agent 项目。

市面上 90% 的 LangGraph demo 都死在这一步——能跑一次给视频录屏用,但不能形成「跑 → 复盘 → 修 → 验证」的工程闭环。

而 bad case 闭环这一项,是我最不该缺的能力——我做了 2 年第三方测评,军工/航天级 CNAS+CMA 测评的核心就是这套。

只是之前对象是硬件,现在换成 LLM。

4.2 三个关键设计决策

决策 1:bad case 库要不要塞进 collector.db

不。两个 SQLite,物理隔离

collector.db        → v1/v2 pipeline 状态机(采集 / 清洗 / 入库)
agent_runs.db       → v3+ Agent 维度数据(运行记录 / bad case)

为什么?因为 v1/v2 的 schema 已经稳定,加新表会污染语义边界。两个库都是 SQLite,但解耦——未来想把 agent_runs 单独导出 / 迁移 / 对外暴露,零成本。

决策 2:零结果是不是自动 bad?

是。

if result_count == 0 and status == "unreviewed":
    status = "bad"
    auto_bad_reason = "zero_result"

为什么?因为「什么算 bad case」这个问题,大部分情况下不需要人来判断。Agent 跑出来 0 条结果,95% 的概率就是 bad case。

让机器先标,人只复核。 这跟我以前做测评写自动化测试用例的习惯一模一样:先让测试用例自己判生死,人只看 fail 的。

决策 3:必须有 replay 命令

python scripts/agent_runs.py replay --status bad --limit 5

这一条是整个闭环的灵魂——你以为修好的 bad case,必须用同一份 query 重跑一遍验证。

不重跑就不算修好。

这条经验也是从测评工作里被反复教育出来的:「regress 不是说说的,是要重新跑测试用例的。」

4.3 CLI 长这样

$ python scripts/agent_runs.py list
ID | when                | cnt | sec  | refl | status     | cause       | query
   3 | 2026-06-26 22:49   |   2 |  4.1 |    0 | unreviewed |             | AI 测试 大模型评估 北京
   2 | 2026-06-26 22:49   |   0 | 12.5 |    3 | bad        | zero_result | 郑州 50K AGI 内核岗
   1 | 2026-06-26 22:49   |   4 |  3.2 |    0 | unreviewed |             | 杭州 AI Agent MCP 1-3年

$ python scripts/agent_runs.py mark 2 --root-cause filter_too_strict \
      --fix-commit abc1234 --fix-notes "把 salary_min 从 20K 降到 15K"
✅ Run #2 已更新

$ python scripts/agent_runs.py replay --status bad --limit 5
🔁 Replay Run #2: "郑州 50K AGI 内核岗"
   旧结果: result_count=0 status=bad
   新结果: id=4 result_count=2 (↑2) status=unreviewed
✅ 1 条之前零结果的 query 这次有命中了。

4.4 它在简历上能讲什么

「我自研的 Agent 项目里实现了完整 bad case 闭环:每次跑自动落库,零结果自动 bad,replay 做回归。
这个习惯是从 2 年军工/航天级 CNAS+CMA 测评带过来的。
同样的字段在投流广告场景里就是 ROI 反馈闭环,在对话陪练里就是 bad case 回流,在评测体系里就是回归测试套件。」

—— 这就是为什么前一篇博客里我说的:

我的测评经验不是包袱,是独特竞争力。

15 个新单元测试,全绿。


五、F3:让 HR 30 秒看懂你

代码再好,HR 30 秒扫不到关键词就废了。今晚最后一段时间,做了三件给 HR 看的工程——README 顶部电梯版、架构图、JD 对照表。

我想单独讲其中一个观点:

大部分技术人最大的认知盲区,是以为「项目做完」=「项目能投」。

不是。

「项目做完」是工程能力的终点,但是「项目投得出去」的起点。中间还隔着——

  • README 顶部 5 行能不能让 HR 30 秒拿到「是什么/怎么做/数据/可调用方」
  • 简历附件里能不能有一张能直接用的架构图 PNG
  • 面试前能不能 5 分钟过一遍「JD 关键词 → 我项目能力」的映射表

这三件事每一件都不写新代码,但每一件都决定项目能不能用进求职。

我今晚做了:

F3  README 30 秒电梯版    标题下加 5 行关键词高密度框
F4  架构图 SVG           docs/assets/architecture.svg 矢量图
F6  JD 对照表            docs/JD_MAPPING.md 三家 JD vs 我的项目能力

F4 这里值得多说一句。第一直觉是用 mermaid-cli 出图,但它要拉 Chromium 内核,国内拉镜像可能要几分钟。避坑思路:手写 SVG。GitHub README 原生支持 SVG,矢量、文件 14KB、简历用时一行 qlmanage -t -s 1920 命令导 PNG。

能让自己 5 分钟内可重做的方案,永远优先于「装套工具一劳永逸」的方案。

写文档、画图、做小工具,这条原则比技术选型重要得多。


六、写在合上电脑前

再看一眼 git log:

v3.0 (2026-06-24) 一天 5 阶段写完 LangGraph Agent
v3.1 (2026-06-26) 两个晚上加 3 Tool + Bad Case 闭环 + 简历级打磨

但我心里清楚——

v3.0 → v3.1 这一步的工作量,跟 v0 → v3.0 几乎一样大。

v3.0 之前我做的是「让项目长出能力」:采集层、RAG 层、Agent 层、反思循环。每一步都看得见、摸得着。

v3.1 这两个晚上我做的是:

  • MCP server 让别的 AI 客户端能调用我
  • Bad case 闭环让我能持续把 Agent 调得更准
  • 简历级 README + 架构图 + JD 对照表让 HR 30 秒看懂

这些工作没有一行代码「能跑出新功能」,但它们决定了项目能不能用进面试、用进简历、用进真实求职。

这个对照让我想起一句话:

95% 的开源项目死在「能跑但讲不清楚」。

而我现在 26 岁,从测评转 AI 应用开发,时间窗口只有今年下半年。项目骨架在 v3.0 已经建完,v3.1 这两个晚上做的就是把它打磨到「投得出去」的程度。


接下来要做的事,我的 Agent 已经告诉我了

get_skill_gap 跑出来的真实数字:

学习方向 ROI 排序:

  多模态          41    ← 市场最大
  LangGraph      16    ← 项目已用,需深入
  MCP            14    ← 项目已用,稀缺度最高
  LoRA           11

三个月学完这四样,我手里就有四张牌:

  • v3.0/v3.1 能讲(已完成)
  • LangGraph 深讲(v3.0 已用,需深入)
  • MCP 稀缺讲(v3.1 已用,市场会的人少)
  • 多模态 市场讲(市场最缺)

够了。

9 月份去新城市投简历,这四张牌打出去,AI 应用 / Agent 开发岗能进面试。


简历金句(v3.1 完工版)

自研一个端到端的 AI 求职 Agent:用 LangGraph 编排 5 节点 DAG(含反思循环),
bge-m3 + Milvus 做 RAG 语义检索(192 条真实 Boss 直聘 JD),
CDP 接管真实 Chrome 绕过 Canvas 反爬。
把核心能力封装成 3 个 MCP Tool,Hermes Agent / Claude Desktop / Cursor 三个客户端均可调用。
每次运行自动落 Bad Case 库,形成「跑 → 复盘 → 修 → replay」闭环。
81 个 pytest 全绿 + GitHub Actions CI。

代码:github.com/nakajimamiyuki/ai_collector_project


项目演进时间轴

版本 日期 主题 一句话
v1.0 2026-06-16 能跑 基础采集 + LLM 清洗
v1.1 2026-06-17 稳定 反爬 + 重试 + 调度
v2.0 2026-06-20 可扩展 插件式多源(B 站 + arXiv)
v2.1 2026-06-23 能查询 + RAG 语义检索(Milvus + bge-m3)
v3.0 2026-06-24 真 Agent + LangGraph + 自主决策 + 反思循环
v3.1 2026-06-26 ★ 生态原生 + MCP Server + Bad Case 闭环

给读到这里的你

如果你也在转型 AI 应用开发,记一句话:

别等项目「完美」再投简历——让项目「投得出去」,本身就是一项工程能力。

把 v3.0 打磨成 v3.1 这两个晚上,跟从 0 写到 v3.0 一样难,也一样值。

Repo: github.com/nakajimamiyuki/ai_collector_project

Cheers,
@nakajimamiyuki / minjie

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐