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() применяет фильтры по тегам/заголовку, уровню (отбрасывает senior, если опыт кандидата < 4 лет) и местоположению (предпочтительный город кандидата, нормализованные алиасы или удалёнка), что сокращает число выживших с 150 до 31:

Стратегия

Промпт-токены

против наивного

A — отправить все 150 полных описаний

67,360

B — сначала метаданные, затем получить 8

13,802

4.9× дешевле

C — prefilter → метаданные → получить 8

6,258

10.8× дешевле

Воспроизводится командой python pipeline.py --benchmark. Реальные числа из data/jobs.json этого репозитория на сегодняшний день, не заглушки. Подсчёт токенов использует энкодер o200k_base из tiktoken (тот, что на самом деле используют gpt-4o / gpt-4o-mini) — точный, не оценочный. Старая эвристика «символы ÷ 4» завышала стоимость наивной стратегии на 17.4% на этом корпусе; поле heuristic_vs_real_tokens в benchmark() воспроизводит это сравнение.

Архитектура

   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 промпт-токенов за запуск, и она растёт линейно с ростом корпуса.

  2. Потолок — после нескольких сотен вакансий это просто превышает окно контекста. Не медленно: невозможно.

  3. Качество — полнота при длинном контексте ухудшается в середине большого промпта, поэтому ранжирование становится хуже с добавлением новых кандидатов.

Поэтапное извлечение, сначала самый дешёвый фильтр:

Стадия

Механизм

Стоимость

Почему здесь

0

Детерминированный фильтр по тегам/заголовку/местоположению на Python

бесплатно

Не позволяйте модели читать то, что if может отбросить. 150 → 91.

1

LLM видит ~55 токенов метаданных на каждую вакансию, выбирает топ-8

~5k

Отсев с высокой полнотой. Предписано включать с запасом, потому что стадия 2 может отклонить.

2

Полные описания только для 8 выживших

~4k

Полная точность, оплачивается один раз, только там, где это меняет ответ.

Обобщаемый принцип — и то, что стоит произнести вслух на собеседовании — каскад по стоимости: располагайте фильтры от самых дешёвых к дорогим и настраивайте порог каждой стадии на полноту, а не на точность, потому что более поздняя стадия ещё может отклонить, но ничто не вернёт то, что было отброшено на ранней стадии.

Агентный уровень (agent.py)

pipeline.py — это фиксированный скрипт: сначала prefilter, затем всегда shortlist, затем всегда score. agent.py передаёт модели те же MCP-инструменты через OpenAI function calling и позволяет ей планировать собственный путь — настоящий агент с вызовом инструментов, а не жёстко заданная последовательность:

  • Структурированный финальный ответ в виде вызова инструмента. Агент не «отвечает в JSON и надеется» — завершение означает вызов синтетического инструмента submit_rankings, схема параметров которого является schemas.RankingResult. Неверные аргументы возвращаются как ошибка валидации, которую модель может прочитать и исправить, в течение ограниченного числа повторных попыток.

  • Сбои инструментов деградируют, а не роняют систему. Любое исключение MCP-инструмента становится обычным результатом инструмента {"error": ...}, который возвращается модели, чтобы она могла обойти неудачный вызов, а не обрушить весь запуск.

  • Модель, которая так и не сходится, всё равно возвращает что-то. Если она исчерпывает бюджет шагов/повторов без корректного вывода, агент откатывается к той же детерминированной логике prefilter → shortlist → score, что и pipeline.py, и сообщает fallback_used: true.

  • Временные ошибки API получают собственные повторные попытки через tenacity, отдельно от цикла повторов по схеме выше — плохое соединение и плохой ответ это разные виды сбоев.

Пошаговый цикл описан в docs/CODE_WALKTHROUGH.md.

Уровень REST + WebSocket (api.py)

Сервис FastAPI оборачивает MCP-инструменты, pipeline.py и agent.py, чтобы они были доступны по HTTP, а не только как CLI-скрипты:

Endpoint

Что делает

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), а не порождает подпроцесс на каждый запрос — это осознанное упрощение по сравнению с настоящим пулом соединений, о чём сказано в docstring модуля api.py, и это не выдаётся за реальную распределённую систему. Каждый запрос получает корреляционный идентификатор (request_id), проходящий через логи и все потоковые события, чтобы запуск можно было проследить через асинхронные переходы.

Заметки по MCP, которые стоит знать назубок

  • Зачем это существует: N моделей × M интеграций превращается в N + M. Один протокол, JSON-RPC 2.0 поверх stdio или Streamable HTTP.

  • Инструменты против ресурсов против промптов: управляемые моделью / приложением / пользователем. Правильно разобраться с этим трио — частый способ выделиться на собеседовании.

  • Форма 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 размеченные как релевантные вакансии, пережившие prefilter?) требует настоящего OPENAI_API_KEY для измерения — python eval.py --top 8 запускает его; здесь не запускалось из-за стоимости.

  • Корпус синтетический. Реальные вакансии грязнее — HTML, дубликаты, устаревшие объявления.

  • Нет кэширования между запусками, поэтому повторные вызовы снова платят за стадию 1.

Полнота стадии 1 — измеренная, а не предполагаемая

В data/relevance_labels.json — 20 ID вакансий, которые человек счёл бы релевантными кандидату, выбранные по документированным воспроизводимым критериям (см. файл). eval.py проверяет две вещи отдельно:

  • prefilter_recall — сколько из 20 размеченных релевантных вакансий переживают детерминированный prefilter? Бесплатно, без API-ключа: python eval.py --prefilter-only20/20, recall 1.0. Критерии разметки — строгое подмножество собственных фильтров prefilter, так что это подтверждает, что prefilter не молча отбрасывает целевую роль, а не просто предполагает это.

  • shortlist_recall — сколько из них также переживают шорт-лист LLM при том top, с которым вы реально запускаете? python eval.py --top 8 — требует OPENAI_API_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, запустите со своим ключом.

  4. Подключите сервер в MCP-конфиг Claude Desktop и вызовите его вручную. Пример конфига и инструкции по перезапуску — в 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