Skip to main content
Glama

Agentic Job Intelligence Pipeline (MCP + LLM)

一个代理驱动的流水线,使用 Model Context Protocol 编排外部工具进行结构化数据检索,并带有一个 LLM 评分层,根据候选人画像对非结构化的职位描述进行排序。上下文窗口压力通过分阶段的元数据优先检索策略来处理(pipeline.py),同样的工具也暴露给一个真正的工具调用代理,该代理有自己的规划循环(agent.py),并暴露给 REST + WebSocket API(api.py)。完整文档见 docs/。

运行

pip install -r requirements.txt

python pipeline.py --benchmark     # token comparison, zero API calls
python pipeline.py --dry-run       # real MCP subprocess handshake, no LLM
export OPENAI_API_KEY=sk-...
python pipeline.py --top 8         # fixed 3-stage pipeline
python agent.py --dry-run          # agent tool discovery, no LLM calls
export OPENAI_API_KEY=sk-...
python agent.py --top 8            # tool-calling agent with a planning loop
python eval.py --prefilter-only    # stage-1 recall, deterministic half, no key needed
pytest -q                          # in-process MCP server, no key needed

uvicorn api:app --reload           # REST + WebSocket layer, http://localhost:8000
curl localhost:8000/health
curl -X POST localhost:8000/rank -H 'content-type: application/json' -d '{"use_llm": false}'

Related MCP server: trackly-cli

实测结果

150 个职位的语料,最终短名单 8 个。prefilter() 应用标签/标题、职级(当候选人年限 < 4 时丢弃 senior)和地点(候选人首选城市,别名规范化,或远程)门控,将幸存者数量从 150 削减到 31:

策略

Prompt 词元

与朴素方法相比

A — 发送全部 150 个完整描述

67,360

—

B — 元数据优先,然后取 8 个

13,802

4.9× 更便宜

C — 预过滤 → 元数据 → 取 8 个

6,258

10.8× 更便宜

使用 python pipeline.py --benchmark 复现。数字来自本仓库 data/jobs.json 的实时数据,不是占位符。词元计数使用 tiktoken 的 o200k_base 编码器(gpt-4o / gpt-4o-mini 实际使用的编码器)——精确,而非估算。旧的“字符数 ÷ 4”启发式方法在此语料上将朴素策略的成本高估了 17.4%;benchmark() 的 heuristic_vs_real_tokens 字段可复现该对比。

架构

   MCP SERVER (stdio subprocess)              MCP CLIENT / pipeline.py
   ---------------------------------          ------------------------------------
   tool  list_jobs        -> metadata  <----  Stage 0  prefilter()   [0 tokens]
   tool  get_job_details  -> full text        Stage 1  shortlist     [~5k tokens]
   tool  get_candidate_profile                Stage 2  score         [~4k tokens]
   tool  corpus_stats
   resource  jobs://schema                    Meter tracks tokens per stage
   prompt    rank_jobs

分阶段检索的理由

朴素方法:把每个完整描述都交给模型,让它排序。三个问题。

  1. 成本 — 每次运行 79k prompt 词元,并且随语料规模线性增长。

  2. 上限 — 超过几百个职位就会完全超出上下文窗口。不是变慢:是不可能。

  3. 质量 — 长上下文召回会在大型 prompt 的中段退化,所以添加更多候选人时排序会变得更差。

分阶段检索,最便宜的过滤器优先:

阶段

机制

成本

为什么放在这里

0

Python 中的确定性标签/标题/地点过滤器

免费

永远不要让模型读取 if 就能丢弃的内容。150 → 91。

1

LLM 每个职位只看 ~55 个元数据词元,选出前 8 个

~5k

高召回筛选。指示其过度包含,因为第 2 阶段可以拒绝。

2

只取 8 个幸存者的完整描述

~4k

完全保真,只付一次费,而且只在会影响答案的地方使用。

可推广的原则——也是面试时要大声说出来的——是按成本级联:过滤器按最便宜优先排序,并且每个阶段的阈值设定应以召回率而非精确率为目标,因为后面的阶段仍可拒绝,但没有任何办法能恢复早期阶段丢弃的内容。

代理层(agent.py)

pipeline.py 是一个固定的脚本:预过滤,然后总是短名单,然后总是评分。agent.py 通过 OpenAI function calling 将相同的 MCP 工具交给模型,让它规划自己的路径——一个真正的工具调用代理,而不是硬编码的序列:

  • 结构化最终答案作为工具调用。 代理并不是“希望式地以 JSON 作答”——完成意味着调用一个合成的 submit_rankings 工具,其参数模式 就是 schemas.RankingResult。无效参数会以模型可以读取并纠正的验证错误返回,并有有限的重试次数。

  • 工具失败会降级,不会崩溃。 任何 MCP 工具异常都会变成普通的 {"error": ...} 工具结果反馈给模型,因此它可以绕过错误的调用,而不是让整个运行崩溃。

  • 永不收敛的模型仍会返回一些东西。 如果它在没有有效输出的情况下耗尽了步骤/重试预算,代理会回退到与 pipeline.py 相同的确定性 prefilter → shortlist → score 逻辑,并报告 fallback_used: true。

  • 瞬态 API 错误有自己的重试,通过 tenacity,与上述模式重试循环分开——连接问题和答案错误是两种不同的故障模式。

逐步循环见 docs/CODE_WALKTHROUGH.md。

REST + WebSocket 层(api.py)

一个 FastAPI 服务包装了 MCP 工具、pipeline.py 和 agent.py,使它们可以通过 HTTP 访问,而不仅仅是 CLI 脚本:

端点

作用

GET /health

存活检查

GET /jobs, GET /jobs/{id}

元数据列表 / 完整详情,与 MCP 工具一样坚持不返回描述的不变式

GET /stats

语料统计

POST /rank

运行排名流水线(默认是 agentic 规划循环,或固定的分阶段流水线);use_llm: false 只运行免费的确定性那一半

WS /ws/rank

与 POST /rank 相同,但每发生一个代理步骤就流式推送一个事件,而不是在最后返回单个响应

一个 MCP stdio 会话在启动时打开一次,并在锁后面共享(api.MCPSession),而不是每个请求都生成一个子进程——这是对真实连接池的刻意简化,在 api.py 的模块 docstring 中这样记录,而不是吹嘘成真正的分布式系统。每个请求都获得一个关联 id(request_id),贯穿日志和每个流式事件,因此可以在异步跳跃中追踪一次运行。

值得烂熟于心的 MCP 要点

  • 为什么存在:N 个模型 × M 个集成变成 N + M。一种协议,基于 stdio 或 Streamable HTTP 的 JSON-RPC 2.0。

  • 工具 vs 资源 vs 提示:模型控制 / 应用控制 / 用户控制。把这三种概念搞对是面试中常见的区分点。

  • CallToolResult 结构:content(块)、structured_content(有类型,非对象返回时包装为 {"result": ...})、is_error。参见 pipeline.call()。

  • 工具设计就是面向非人类调用者的 API 设计。 list_jobs 和 get_job_details 被拆开,因为正是这种拆分才让分阶段检索成为可能。Docstring 就是 模型读取的工具描述——docstring 含糊,工具就会选错。

  • 批量参数优于标量参数:get_job_details(job_ids: list[str]) 只需一次往返;get_job_detail(job_id: str) 需要八次。

已知限制

  • 短名单召回率(第 1 阶段是否保留了通过预过滤的、被标记为相关的职位?)需要真正的 OPENAI_API_KEY 才能衡量——python eval.py --top 8 可运行;出于成本原因未在此运行。

  • 语料是合成的。真实职位更杂乱——HTML、重复、过期列表。

  • 跨运行没有缓存,因此重复调用会再次支付第 1 阶段的成本。

第 1 阶段召回率——实测,而非臆测

data/relevance_labels.json 中有 20 个人类会认为与候选人相关的职位 ID,这些 ID 是根据一个有文档记录、可复现的规则(见该文件)挑选的。eval.py 分别检查两件事:

  • prefilter_recall——在 20 个标记相关的职位中,有多少能通过确定性预过滤?免费,无需 API key:python eval.py --prefilter-only → 20/20,召回率 1.0。标签规则是预过滤自身门控的严格子集,因此这证实了预过滤并没有静默丢弃目标职位,而不是想当然。

  • shortlist_recall——在通过预过滤的职位中,有多少也能在你实际使用的 top 参数下通过 LLM 短名单筛选?python eval.py --top 8——需要 OPENAI_API_KEY,调用真实模型,因此本仓库未运行;你有 key 时自行运行。

你的待办事项

  1. prefilter() — 增加职级和地点门控。重新运行 --benchmark,记录数字。 已完成:150 → 31 个幸存者,相比朴素方法减少 10.8×。

  2. 把 approx_tokens 换成真正的 tiktoken 计数;记录 ÷4 启发式偏差有多大。 已完成:启发式高估了 17.4%。

  3. 构建一个 20 个职位的标记相关集合并衡量第 1 阶段召回率。 免费部分已完成(prefilter_recall = 1.0);付费部分(shortlist_recall)已在 eval.py --top 8 中接好,用你自己的 key 运行。

  4. 将服务器接入 Claude Desktop 的 MCP 配置并手动调用。 配置片段和重启说明在 docs/OVERVIEW.md 中——实际注册发生在你自己的 Claude Desktop 应用里,这不是本仓库能替你完成的事。

文档

  • docs/OVERVIEW.md — 这个项目是什么、解决什么问题以及为什么、架构、Claude Desktop 接入、本地可测性、已知限制。

  • docs/CODE_WALKTHROUGH.md — 每个模块,逐个函数。

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    MCP server that exposes job search data from multiple boards, enabling clients to query and manage job listings via natural language.
    7
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    MCP server for job search and application tracking, enabling AI agents to search jobs, get details, manage applications, and find contacts across 128K+ jobs and 1,900+ companies.
    781 npm
    3
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    A custom MCP server that exposes a jobs database to any MCP-compatible LLM client, allowing users to ask in plain English to search, filter, and match job openings.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    MCP server that aggregates and deduplicates job listings from multiple public sources, ranks them against a user's resume, and exposes tools for searching, viewing details, explaining fit, and tracking applications.
    MIT