Эксперимент с локальной Qwen на Go-сервисе

от автора

После выхода новых локальных моделей мне захотелось проверить не абстрактный бенчмарк, а более приземленную вещь: можно ли отдать маленькой модели обычную задачу из backend-разработки и получить рабочий результат.

Не «построй мне стартап за вечер». Не «спроектируй идеальную архитектуру». А нормальную небольшую задачу: Go-сервис с регистрацией, логином, JWT, PostgreSQL, Docker Compose и тестами.

Главный вывод эксперимента: простые вещи уже можно отдавать дешевым моделям. Но не в формате «вот тебе проект, разбирайся». Рабочая схема оказалась другой: сильная модель готовит постановку и план, а дешевая модель выполняет маленькие хорошо описанные шаги.

Коротко

Параметр

Значение

Задача

Demo auth service

Стек

Go, PostgreSQL, Docker

Агент

Opencode

Модель

qwen/qwen3.5-9b

Запуск

LM Studio

Железо

RTX 5080 16 GB, 64 GB RAM, i5-12600K

Скорость

примерно 120 токенов/сек при контексте около 200K

Результат

рабочий backend-скелет, 19 unit-тестов

Что я проверял

Задача была стандартная, сервис работы с пользователями:

  • регистрация пользователя;

  • авторизация по email/password;

  • выдача JWT access token;

  • endpoint для получения текущего пользователя;

  • смена пароля;

  • хранение пользователей в PostgreSQL;

  • миграции;

  • запуск через Docker Compose;

  • тесты через go test ./....

Стек: Go, PostgreSQL, Docker.

Модель qwen/qwen3.5-9b я запускал через LM Studio. Подбирал ее через canirun.ai: хотелось, чтобы модель полностью помещалась в GPU-память и при этом оставалось место под большой контекст. Для агентской работы скорость важна не меньше качества. Если генерация слишком медленная, экономия быстро превращается в раздражение.

В качестве агента я использовал Opencode.

Главная идея эксперимента

Я не просил локальную модель «сделай весь проект». Это почти гарантированный способ получить хаос, особенно с небольшой моделью.

Вместо этого процесс был таким:

GPT-5.5 -> подробное ТЗ и план разработки        -> разбиение на маленькие сессии        -> Qwen 3.5 9B через Opencode        -> одна сессия = одна ограниченная задача        -> go test ./... как критерий готовности        -> ручной review результата

То есть дорогая модель работала как архитектор и постановщик задачи. Локальная модель работала как исполнитель.

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

Методика

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

Потом весь проект был разбит на маленькие сессии:

  1. Скелет проекта, доменные структуры, bcrypt и JWT.

  2. Сервисный слой и fake repository для unit-тестов.

  3. HTTP API и middleware.

  4. PostgreSQL repository, config и миграции.

  5. Dockerfile, Docker Compose и README.

  6. Финальная стабилизация.

У каждой сессии были:

  • одна понятная цель;

  • список ожидаемых файлов или компонентов;

  • ограничения, что не нужно делать;

  • критерий готовности;

  • команда проверки.

Пример одной сессии

Вот пример задачи для локального агента:

Работай как аккуратный coding agent. Делай небольшие изменения. После каждого крупного шага запускай релевантные тесты. Не добавляй функциональность сверх задачи. Если тесты падают, исправляй причину. Не завершай работу, пока проверка для текущей задачи не проходит или пока не появится внешний блокер.Контекст:Мы делаем demo auth service на Go. Полные требования описаны в plan.md.Задача этой сессии:1. Изучи текущий код.2. Создай repository interface для пользователей.3. Создай service layer для auth/user logic.4. Реализуй методы:   - Register(ctx, email, password, name)   - Login(ctx, email, password)   - GetCurrentUser(ctx, userID)   - ChangePassword(ctx, userID, oldPassword, newPassword)5. В сервисе:   - валидируй email, password, name;   - password минимум 8 символов;   - email не должен быть пустым;   - name не должен быть пустым;   - duplicate email должен возвращать отдельную доменную ошибку;   - неверный login должен возвращать ошибку unauthorized;   - неверный old_password должен возвращать ошибку forbidden;   - password_hash не должен выходить наружу через response model.6. Напиши fake in-memory repository для тестов.7. Напиши service tests:   - успешная регистрация;   - duplicate email;   - register с коротким паролем;   - успешный login;   - login с неверным паролем;   - получение пользователя по ID;   - смена пароля работает;   - после смены пароля старый пароль не работает;   - после смены пароля новый пароль работает.8. Запусти:   gofmt   go test ./...Критерий готовности:go test ./... проходит успешно.

Именно такой формат оказался рабочим. Не «сделай auth service», а «сделай сервисный слой, не трогай HTTP, проверь тестами».

Необходимые знания и навыки

Отдельно я подобрал skills под проект:

  • golang-patterns;

  • golang-testing;

  • docker-patterns;

  • database-migrations;

  • security-best-practices;

  • supabase-postgres-best-practices.

Это важный момент. Маленькая модель хуже держит в голове специфичные практики: как лучше структурировать Go-код, как писать тесты, как не накосячить с Docker Compose, как аккуратно обращаться с JWT и паролями.

У большой облачной модели часть этих знаний чаще уже «внутри». С локальной 9B-моделью лучше не надеяться на это. Ей нужно положить рядом справочники, правила проекта и критерии готовности.

По сути, skills превращают маленькую модель из «модели обо всем» в более узкого исполнителя под конкретную задачу.

Что получилось

В итоге локальная модель собрала demo auth service.

Структура проекта получилась примерно такой:

cmd/server/main.gointernal/auth/jwt.gointernal/auth/jwt_test.gointernal/auth/password.gointernal/auth/password_test.gointernal/config/config.gointernal/domain/errors.gointernal/domain/user.gointernal/http/handlers.gointernal/http/middleware.gointernal/http/response.gointernal/http/routes.gointernal/repository/fake_repository_test.gointernal/repository/postgres_user_repository.gointernal/repository/user_repository.gointernal/service/auth_service.gointernal/service/auth_service_test.gomigrations/001_create_users.up.sqlmigrations/001_create_users.down.sqldocker-compose.ymlDockerfileREADME.md

Пример теста, который сгенерировала модель:

func TestRegisterSuccess(t *testing.T) {userRepo := repository.NewMockUserRepository()authService := service.NewAuthService(userRepo, "test-secret")user, err := authService.Register(context.Background(),"user@example.com","password123","John Doe",)require.NoError(t, err)assert.Equal(t, "user@example.com", user.Email)assert.Equal(t, "John Doe", user.Name)}

go test ./... проходит.

Получилось 19 unit-тестов: для auth helper’ов, сервисного слоя и fake repository. docker compose config тоже проходит.

Для эксперимента это неплохой результат. Особенно если помнить, что реализацию делала маленькая локальная модель.

Что получилось, а что нет

Область

Результат

Auth helpers

Сделано, покрыто тестами

Service layer

Сделано, покрыто тестами

PostgreSQL repository

Сделано

HTTP handlers

Сделано

Docker Compose

docker compose config валиден

README

Есть, но с артефактами форматирования

Миграции

Достаточно для demo, требуют review перед production

Отладка

Долго не могла проверить сервис по http. Она пталась использовать curl которго нет на win11, пришлось подсказывать.

Где дешевая модель экономит

Лучше всего она справилась с тем, что было хорошо ограничено:

  • реализовать bcrypt helper;

  • написать JWT generate/parse;

  • сделать сервисный слой;

  • покрыть бизнес-логику unit-тестами;

  • добавить repository interface;

  • подготовить PostgreSQL repository;

  • собрать базовый Docker Compose.

То есть все, что похоже на понятную инженерную рутину, маленькая модель тянет нормально.

И это главный вывод эксперимента: простые вещи уже можно отдавать дешевым моделям.

Дешевая не обязательно значит локальная

В этой статье я использовал локальную модель, но вывод не только про локальный запуск.

Речь шире: про весь класс дешевых моделей. Это могут быть локальные модели, small/mini cloud-модели или более дешевые inference endpoints.

Их не обязательно противопоставлять сильным моделям. На практике полезнее разделять роли:

  • сильная модель лучше подходит для постановки задачи, декомпозиции и review;

  • дешевая модель лучше подходит для выполнения маленьких понятных шагов;

  • разработчик остается ответственным за итоговое решение.

Такой подход позволяет не выбирать между «все делает дорогая модель» и «все делаем руками». Можно тратить сильную модель там, где нужна сила, а не там, где нужен аккуратный boilerplate.

Практический вывод

Я все больше думаю, что рабочая схема будет не «одна большая модель делает все», а распределение задач по стоимости.

Сильная модель = постановка задачи + декомпозиция + review.Дешевая модель = реализация маленьких понятных шагов.

Если хотите попробовать похожий подход:

  1. Не отдавайте маленькой модели весь проект целиком.

  2. Сначала сделайте подробный план сильной моделью.

  3. Разбейте работу на маленькие сессии.

  4. Для каждой сессии задайте критерий готовности.

  5. Добавьте skills под стек проекта.

  6. Просите модель запускать проверки.

  7. Делайте финальный review руками.

  8. Не верьте README, пока сами не запустили команды.

Мой вывод после эксперимента простой: локальные и дешевые модели уже полезны в разработке, если не требовать от них невозможного.

Они не должны быть архитекторами. Им достаточно быть аккуратными исполнителями для хорошо поставленных задач. А это уже закрывает заметную часть повседневной разработки.

ссылка на оригинал статьи https://habr.com/ru/articles/1043090/