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}'

实测结果

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 的实时数据,不是占位符。词元计数使用 tiktokeno200k_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.pyagent.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_jobsget_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-only20/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 — 每个模块,逐个函数。

-
license - not tested
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jaideepdnaik/mcp-job-intel'

If you have feedback or need assistance with the MCP directory API, please join our Discord server