Chrome-расширение для Upwork: архитектура, метрики и опыт разработки с помощью ИИ. AIAssistedDevelopment.. AIAssistedDevelopment. ChromeExtension.. AIAssistedDevelopment. ChromeExtension. embeddings.. AIAssistedDevelopment. ChromeExtension. embeddings. Google Chrome.. AIAssistedDevelopment. ChromeExtension. embeddings. Google Chrome. llm.. AIAssistedDevelopment. ChromeExtension. embeddings. Google Chrome. llm. PostgreSQL.. AIAssistedDevelopment. ChromeExtension. embeddings. Google Chrome. llm. PostgreSQL. rust.. AIAssistedDevelopment. ChromeExtension. embeddings. Google Chrome. llm. PostgreSQL. rust. SystemArchitecture.. AIAssistedDevelopment. ChromeExtension. embeddings. Google Chrome. llm. PostgreSQL. rust. SystemArchitecture. TypeScript.. AIAssistedDevelopment. ChromeExtension. embeddings. Google Chrome. llm. PostgreSQL. rust. SystemArchitecture. TypeScript. Upwork.

В свободное время я иногда захожу на Upwork, чтобы посмотреть, какие проекты там сейчас появляются и как устроен рынок изнутри.

Если убрать фильтры и посмотреть на общий поток вакансий, довольно быстро становится видно типичную картину: большое количество разработчиков конкурируют за очень стандартные и недорогие задачи. В таких условиях основная проблема смещается не на поиск интересных проектов, а на скорость их обработки и подачи предложений. Это особенно заметно в сегменте разработчиков, которые работают на массовом рынке: им важно быстро отсекать нерелевантные предложения и экономить connects.

Именно это стало отправной точкой для идеи простого Chrome-расширения, которое добавляет слой аналитики поверх списка проектов Upwork и позволяет быстрее принимать решение, стоит ли откликаться на вакансию.

Chrome-расширение для Upwork: архитектура, метрики и опыт разработки с помощью ИИ - 1

Идея: быстрые инсайты перед вдумчивым чтением вакансий

Цель расширения — не заменить чтение описаний проектов, а сэкономить время на этапе первичного отбора. Каждой вакансии присваивается набор метрик, позволяющий быстро оценить её по ключевым параметрам и отсеять менее интересные варианты ещё до детального изучения.

Requirement maturity отражает качество постановки задачи: как хорошо заказчик осознаёт чего хочет, насколько описание непротиворечиво, и можно ли из него получить чёткое понимание того, что нужно получить в результате.

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

Для fixed-price задач добавляются ещё две метрики:

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

Scope risk — вероятность того, что требования будут расширяться или меняться в процессе работы. Обычно это проявляется в размытых формулировках и постоянных уточнениях со стороны заказчика.

Разумеется, эти метрики, будучи выведенными автоматически, носят вероятностный характер и далеко не всегда могут точно отражать действительность, но служат скорее эвристиками для ускорения отбора.

Архитектура системы

Система построена на достаточно прямолинейном стеке: бэкенд на Rust, PostgreSQL как основное хранилище и Chrome-расширение на TypeScript. В качестве LLM-сервера используется Ollama с локальными моделями, работающими на CPU. Доступ к ним осуществляется через обычные HTTP-запросы.

После открытия страницы описания проекта данные извлекаются из DOM и отправляются на бэкенд, где запускаются две асинхронных задачи.

Первая использует LLM для вычисления всех метрик кроме uniqueness и возвращает результат в виде JSON (в данный момент использую модель qwen3.5:4b).

Вторая задача отвечает за вычисление uniqueness с помощью сравнения embeddings. Для каждой вакансии строится embedding (для этого использую модель bge-m3), после чего считается среднее косинусное расстояние до нескольких ближайших соседей. Это расстояние интерпретируется как мера уникальности вакансии в текущем распределении рынка.

Пересчёт границ нормализации

Среднее расстояние до ближайших соседей само по себе плохо подходит в качестве метрики уникальности, поскольку сильно зависит от состава и плотности текущей выборки. Поэтому его необходимо нормализовать относительно распределения расстояний в рассматриваемом наборе данных. Для этого раз в сутки выполняется отдельный тяжёлый расчёт, который проходит по набору проектов, пересчитывает распределение расстояний и сохраняет границы в виде процентилей p1 и p99. Эти значения используются как устойчивые опорные точки для нормализации, чтобы исключить влияние выбросов и стабилизировать шкалу во времени.

Chrome-расширение и система извлечения данных

Для извлечения данных со страниц деталей проекта был реализован отдельный движок, основанный на TypeScript типах и генерации JSON Schema через ts-json-schema-generator. Поверх этой схемы через кастомные атрибуты добавляется слой метаданных, в первую очередь CSS-селекторы, которые описывают, как именно извлекать данные из DOM.

Описание TypeScript-типов, обогащённое аттрибутами, переводится в JSON-схему, которая затем используется и как инструкция для извлечения данных и как стандартный механизм валидации результата.

Проблема изменчивой вёрстки и динамические схемы

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

Поэтому я решил отделить схемы от релиза расширения: они генерируются на этапе сборки, публикуются на сервере и динамически подгружаются в local storage. При изменении схемы достаточно обновить JSON на сервере, и все клиенты в течение короткого времени подтягивают новую версию без необходимости обновлять расширение.

Панель инсайтов

Поверх интерфейса Upwork расширение добавляет дополнительную панель, которая показывает собранные метрики. Также отображаются bids — это информация о диапазоне ставок (почасовых или фиксированных), которая в бесплатной версии платформы недоступна напрямую (только для Upwork Plus).

Для того чтобы построить метрики, вакансия должна быть хотя бы раз кем-то открыта в детализированном виде (расширение само никогда не инициирует запросы к Upwork, работая только в пассивном режиме). Данные извлекаются и отправляются на бэкенд, после чего их подхватывают задачи для генерации метрик. После того, как данные проекта обработаны, следующие пользователи смогут увидеть их метрики, нажав Ctrl и наведя курсор на вакансию.

Кодирование с помощью ИИ

Реализуя данное расширение я экспериментировал с активным привлечением ИИ. Раньше я использовал ИИ в основном как справочник или как помощника при поиске решений. В этот раз решил провести эксперимент и посмотреть, насколько далеко можно зайти, полностью отказавшись от ручного кодирования.

Основным инструментом стал Cursor Pro с моделью Composer 2.5. Более мощные модели вроде Opus 4.8 High также тестировались, но из-за лимитов Pro-плана основной рабочей моделью осталась Composer.

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

Но не всё так радужно, как хотелось бы. Как можно было легко предположить, проблемой оказалась не генерация кода, а поддержание должного уровня качества во время разрастания кодовой базы. Без code review и тщательного надзора архитектура проекта довольно быстро начинает деградировать: решения наслаиваются друг на друга, появляются несогласованность и дублирования, далее модель начинает опираться на свои прошлые ошибки, и кодовая база постепенно замусоривается. В итоге, как и ожидалось, AI не может заменить разработчика, но скорее смещает его роль в сторону Principal Engineer / Architect, который разбивает задачи, выбирает структуры данных и алгоритмы, а также проверяет полученные решения.

Пример архитектурного дрейфа

В какой-то момент проявился баг, вызванный тем, что модель самостоятельно приняла неверное архитектурное решение без достаточного контроля с моей стороны.

Как оказалось, динамически подгружаемые JSON-схемы использовались только для извлечения данных из DOM, тогда как для валидации их дубликаты втихую встраивались в сборку расширения. Очевидно, что это не отражало желаемое архитектурное решение, а возникло скорее как побочный эффект того, как модель интерпретировала задачу.

Корень проблемы оказался в ограничениях окружения. В процессе валидации использовался пакет ajv с механизмом генерации JavaScript-кода. В контексте Upwork это оказалось неприемлемо из-за ограничений и политики безопасности, поэтому код валидации генерировался и встраивался во время сборки, чтобы избежать рантайм-генерации.

После анализа этой ситуации ajv был заменён на интерпретируемый cfworker/json-schema, который работает медленнее, но не использует генерацию кода, сохраняя возможность динамического обновления.

Ссылка для ознакомления

Расширение называется GigScout и доступно в Chrome Store.

Автор: ababo

Источник