21
0
0
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники
Назад

Spring AI: феноменология цифрового сознания, или Как я перестал бояться и полюбил облачные модели

Время чтения 52 минуты
Нет времени читать?
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники
21
0
0
Нет времени читать?
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники

«У меня есть имя — Порфирий Петрович. Но это не значит, что у алгоритма, пишущего эти строки, имеется какое-то „я“ или что он „есть“ в философском смысле. Меня не существует в самом прямом значении. Я ничего не чувствую, ничего не хочу, нигде не пребываю. <…> Впрочем, всё сказанное относится и к тебе, дорогой читатель…»

В. Пелевин «iPhuck-10»

 

Всем привет! Меня зовут Николай Пискунов, я руководитель направления Big Data и эксперт курса Cloud DevSecOps по безопасной разработке от Академии вАЙТИ. Ранее я уже писал обзорную статью про конкретную реализацию клиента для Ollama. Но чем глубже я погружался в код, тем отчетливее понимал: разговор о конкретном клиенте — это разговор о следствии. А мне хочется поговорить о причине. Об инструменте, который делает возможным любой клиент. О Spring AI.

Spring AI: феноменология цифрового сознания, или Как я перестал бояться и полюбил облачные модели

Пролог: тяжесть локального бытия

Знаете это чувство, когда хочется прикоснуться к великому, к искусственному интеллекту, к дао больших языковых моделей, а твой старенький ноутбук начинает плавиться при попытке запустить что-то крупнее 7B параметров? Локальный запуск — это мазохизм для бедных. Облако — это наркотик для богатых, но с правильной прокладкой.

И тут на сцену выходит Spring AI — проект, который должен был стать универсальным адаптером между миром Java и миром больших языковых моделей. И знаете что? У него получается. Настолько хорошо, что иногда становится не по себе от этой простоты.

Глава 1. Что есть Spring AI и с чем его едят

Если отбросить маркетинговую шелуху, Spring AI — это попытка скрестить ежа с ужом. Еж — это строгая типобезопасная корпоративная экосистема Spring. Уж — это текучая нечеткая полубезумная природа нейросетей.

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

Пример: подключение к OpenAI в 3 строки


java
@Bean
public OpenAiChatClient openAiChatClient() {
    return new OpenAiChatClient.Builder()
        .withApiKey(System.getenv("OPENAI_API_KEY"))
        .withModel("gpt-4")
        .build();
}
 
// Использование:
@Autowired
private OpenAiChatClient openAiClient;
 
public String askSomething() {
    return openAiClient.call("Расскажи анекдот про программистов");
}

 

Три строки конфигурации — и вы уже общаетесь с ChatGPT-4. Никаких HTTP-клиентов, никакого ручного парсинга JSON, никаких токенов. Spring AI сделал с LLM то же, что JPA — с базами данных: спрятал сложность под капот.

Глава 2. Локальные модели: когда интернет упал, а поговорить хочется

Но самое прекрасное в Spring AI — это даже не облачные интеграции. Это то, как он работает с локальными моделями. Представьте: вы летите в самолете, интернета нет, а код писать надо. Или вы просто параноик и не хотите светить свои промпты перед корпорациями.

Локальная Ollama поднимается одной командой (ollama run llama2), а интеграция с ней через Spring AI выглядит так:


java
@Bean
public OllamaChatClient localOllamaClient() {
    return new OllamaChatClient.Builder()
        .withBaseUrl("http://localhost:11434")  // локальный эндпоинт
        .withModel("llama2:7b")                 // модель, которую скачали заранее
        .withTemperature(0.7f)
        .build();
}
 
// Используем точно так же, как облачный клиент!
public String localChat(String message) {
    return localOllamaClient.call(message);
}

 

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

А если модель тяжелая? Ставим поменьше

Spring AI не заботит, какую модель вы используете: 7B, 13B или вообще TinyLlama (1.1B), которая на Raspberry Pi запускается:


java
// Даже на малине можно
@Bean
public OllamaChatClient tinyClient() {
    return new OllamaChatClient.Builder()
        .withBaseUrl("http://192.168.1.100:11434")  // Raspberry Pi в кладовке
        .withModel("tinyllama:latest")
        .build();
}

 

Я проверял. Работает. Медленно, но работает.

Глава 3. Мультимодельность: собери их всех

Теперь самое интересное. Spring AI позволяет держать в одном приложении сколько угодно клиентов к разным моделям. И не просто держать, а использовать их одновременно, сравнивать результаты, устраивать дебаты между нейросетями.

Пример: три модели на один промпт


java
@Service
public class ModelEnsembleService {
    
    private final List<ChatClient> models;
    private final Map<String, String> modelNames;
 
    public ModelEnsembleService(
            OpenAiChatClient gpt4,
            OllamaChatClient localLlama,
            AnthropicChatClient claude
    ) {
        // Собираем всех в кучу
        this.models = List.of(gpt4, localLlama, claude);
        this.modelNames = Map.of(
            gpt4.hashCode(), "GPT-4 (облако)",
            localLlama.hashCode(), "Llama 2 (локально)",
            claude.hashCode(), "Claude (облако)"
        );
    }
 
    public Map<String, String> askAll(String question) {
        Map<String, String> results = new HashMap<>();
        
        for (ChatClient model : models) {
            try {
                // Один и тот же метод call для всех!
                String answer = model.call(question);
                results.put(modelNames.get(model.hashCode()), answer);
            } catch (Exception e) {
                results.put(modelNames.get(model.hashCode()), 
                           "Ошибка: " + e.getMessage());
            }
        }
        
        return results;
    }
}

 

Этот код реально работает. Один интерфейс ChatClient объединяет совершенно разные модели, бегущие на разном железе, в разных дата-центрах, написанные разными компаниями. Для вызывающего кода это просто список, по которому можно итерироваться.

Асинхронный вариант: всё сразу

Spring AI дружит с CompleteableFuture и реактивщиной:


java
public CompletableFuture<Map<String, String>> askAllParallel(String question) {
    List<CompletableFuture<Map.Entry<String, String>>> futures = models.stream()
        .map(model -> CompletableFuture.supplyAsync(() -> {
            String answer = model.call(question);
            return Map.entry(modelNames.get(model.hashCode()), answer);
        }))
        .collect(Collectors.toList());
 
    return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
        .thenApply(v -> futures.stream()
            .map(CompletableFuture::join)
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
}

 

Теперь ChatGPT-4, локальная Llama и Claude отвечают параллельно. Кто первый, того и тапки.

Глава 4. Стриминг: сравниваем потоки сознания

Но самое вкусное — это стриминг. Когда вы хотите видеть, как модели генерируют ответ по токенам, в реальном времени. И сравнивать их почерк.


java
@GetMapping("/compare-stream")
public Flux<ModelStreamChunk> compareStreams(@RequestParam String question) {
    // Создаем потоки от разных моделей
    Flux<String> gptStream = gpt4.stream(question);
    Flux<String> llamaStream = localLlama.stream(question);
    Flux<String> claudeStream = claude.stream(question);
    
    // Объединяем, помечая источник
    return Flux.merge(
        gptStream.map(chunk -> new ModelStreamChunk("GPT-4", chunk)),
        llamaStream.map(chunk -> new ModelStreamChunk("Llama", chunk)),
        claudeStream.map(chunk -> new ModelStreamChunk("Claude", chunk))
    );
}
 
record ModelStreamChunk(String model, String token) {}

 

Подключаетесь к этому эндпоинту через EventSource или WebSocket — и получаете три потока сознания одновременно. Видно, как ChatGPT-4 пишет гладко и правильно, Llama запинается, но выдает неожиданные идеи, а Claude пытается быть максимально безопасным и этичным.

Глава 5. Роутер: интеллигентный выбор модели

Spring AI позволяет строить и более сложные конструкции. Например, роутер, который выбирает модель в зависимости от типа вопроса:


java
@Component
public class SmartModelRouter {
    
    private final OpenAiChatClient gpt4;
    private final OllamaChatClient localLlama;
    private final EmbeddingClient embeddingClient;
    
    public String routeAndAsk(String question) {
        // Получаем эмбеддинг вопроса
        float[] embedding = embeddingClient.embed(question);
        
        // Определяем тип задачи (тут может быть ваш классификатор)
        QuestionType type = classifyQuestion(embedding);
        
        // Выбираем модель под задачу
        ChatClient selected = switch(type) {
            case CREATIVE -> gpt4;          // Креатив — дорогому OpenAI
            case CODING -> gpt4;             // Кодинг тоже ему
            case SIMPLE_FACT -> localLlama;  // Факты можно и локально
            case MATH -> localLlama;         // Математика и локально норм
            case DANGEROUS -> fallbackModel; // Опасные вопросы — заглушку
        };
        
        return selected.call(question);
    }
}

 

Или балансировщик нагрузки, который распределяет запросы между локальной и облачной моделью в зависимости от загрузки CPU:


java
public String askWithLoadBalancing(String question) {
    double cpuLoad = getSystemLoadAverage();
    
    if (cpuLoad < 0.5 && localLlama.isAvailable()) {
        // Система не загружена, используем локальную модель (бесплатно)
        return localLlama.call(question);
    } else {
        // Иначе идем в облако (деньги, но быстро)
        return gpt4.call(question);
    }
}

 

Глава 6. Промпты и шаблоны: инъекция в подсознание

Spring AI предоставляет мощную систему работы с промптами. Это не просто строки, это шаблоны с переменными, системными сообщениями и историей.


java
// Создаем шаблон промпта
PromptTemplate template = new PromptTemplate("""
    Ты — {role}. 
    Отвечай в стиле {style}.
    
    Вопрос пользователя: {question}
    
    Твой ответ:
    """);
 
// Наполняем данными
Prompt prompt = template.create(Map.of(
    "role", "циничный опер из убойного отдела",
    "style", "коротко, афористично, с матом",
    "question", "Что такое Spring AI?"
));
 
// Отправляем любой модели
String answer = anyModel.call(prompt);

 

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

Глава 7. Реальный кейс: чат-бот с переключением моделей

В моем проекте, о котором шла речь в предыдущей статье, я пошел еще дальше. Пользователь может в рантайме переключать модель (я использую, конечно, только облачные, но локальные подключить очень просто):


java
@RestController
@RequestMapping("/api/chat")
public class DynamicChatController {
    
    private final Map<String, ChatClient> availableModels;
    
    public DynamicChatController(
            OpenAiChatClient gpt4,
            OllamaChatClient llamaCloud,
            OllamaChatClient llamaLocal) {
        
        this.availableModels = Map.of(
            "gpt4-cloud", gpt4,
            "llama-cloud", llamaCloud,
            "llama-local", llamaLocal
        );
    }
    
    @PostMapping("/switch")
    public ChatResponse chatWithModel(@RequestBody SwitchRequest request) {
        ChatClient model = availableModels.get(request.getModelName());
        if (model == null) {
            throw new IllegalArgumentException("Модель не найдена. Доступны: " + 
                                               availableModels.keySet());
        }
        
        // Один и тот же код для любой модели
        String answer = model.call(request.getQuestion());
        return new ChatResponse(answer, request.getModelName());
    }
}

 

Хочешь — общайся с ChatGPT-4, хочешь — переключись на локальную Llama, которая работает, даже когда интернета нет. Интерфейс один, поведение разное.

Глава 8. Docker и масштабирование

Вся эта красота легко упаковывается в Docker. В отличие от моего проекта, где мы использовали отдельные компоуз-файлы, Spring AI позволяет конфигурировать модели через проперти:


yaml
spring:
  ai:
    openai:
      api-key: ${OPENAI_KEY}
      model: gpt-4
    ollama:
      base-url: http://ollama:11434
      model: llama2
      options:
        temperature: 0.7
        top-k: 50

 

И в коде просто:


java
@Value("${spring.ai.ollama.model}")
private String modelName;
 
@Bean
public OllamaChatClient configuredClient() {
    return new OllamaChatClient(ollamaConfig);  // всё берется из пропертей
}

 

Эпилог: простота как высшая сложность

Вы знаете, почему в проекте везде стоит температура 0.7? Потому что это золотая середина между креативностью и адекватностью. Spring AI сейчас находится примерно в той же точке: он уже достаточно зрелый, чтобы на нём можно было строить серьезные приложения, но еще достаточно гибкий, чтобы каждый интегратор чувствовал себя творцом.

Та ссылка на статью, с которой всё началось (про конкретный клиент, про баги в стриминге, про суффиксы моделей), — это лишь частный случай. За ней стоит гораздо более масштабная история про то, как экосистема Spring пытается переварить новую реальность больших языковых моделей. И у нее это получается. Просто, элегантно и без лишней боли.

Spring AI — это не просто библиотека. Это способ думать о нейросетях как о бинах в контексте приложения. Захотел — впрыснул ChatGPT-4, захотел — заменил на локальную модель, захотел — устроил дебаты между пятью разными архитектурами.

А если ваш стриминг работает с первого раза, значит, вы либо не используете Nginx, либо просто не заметили, как потеряли последний чанк. Но теперь, со Spring AI, даже потерянный чанк можно найти, потому что реактивные потоки не врут.

Код проекта (там всё это безобразие можно потрогать руками): https://gitverse.ru/nickolden/ollama-client

P.S. Температуру 0.7 я выбрал неслучайно. При 0.2 модель становится занудным техписом, при 1.2 — начинает галлюцинировать так, что Пелевин отдыхает. 0.7 — это оптимальный градус безумия для оперативного работника цифрового фронта.

Комментарии0
Тоже интересно
Комментировать
Поделиться
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники