Главная / Статьи / Практические заметки: Java AI в 2026 году, LangChain4j превращает агентов в обычные программы

Практические заметки: Java AI в 2026 году, LangChain4j превращает агентов в обычные программы

Пошаговое руководство по использованию «Практические заметки: Java AI в 2026 году», LangChain4j превращает агентов в обычные компоненты: контракты, проверки и готовые блоки кода для команд, внедряющих эту модель.

1587 слов

В этом руководстве показано, как пройти путь от сырья до рабочей системы для проектов: Java AI в 2026 году и LangChain4j, превращающий агентов в обычные интерфейсы. Основное внимание уделяется практическим шагам, четкой проверке и коду, который можно просто добавить в репозиторий без необходимости догадываться о его назначении. На этапе обзора необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Рядом с функциональными результатами следует записывать время выполнения и стоимость токенов или запросов. Отображение затрат с самого начала помогает избежать неожиданных счетов при переходе от демо-версии к общедоступным средам.

Начните с самого сложного этапа: позвольте модели вызывать ваши бизнес-методы

При работе над этапом «Начните с самого важного» сначала запишите условия контракта: необходимые входные данные, сигнал успешного выполнения и действия при частичной неудаче. Такой чек-лист помогает сохранять честность при последующих изменениях кода. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Храните в кэше стабильные инструкции системы и схемы инструментов. Пересылка одинаковых данных является распространенной причиной избыточных затрат.

<dependency>
  <groupId>dev.langchain4j</groupId>
  <artifactId>langchain4j-open-ai</artifactId>
  <version>1.19.0</version>
</dependency>
<dependency>
  <groupId>dev.langchain4j</groupId>
  <artifactId>langchain4j-agentic</artifactId>
  <version>1.19.0</version>
</dependency>
<dependency>
  <groupId>dev.langchain4j</groupId>
  <artifactId>langchain4j-skills</artifactId>
  <version>1.19.0-beta29</version>
</dependency>
class OrderTools {

    @Tool("Look up the status of an order by ID")
    String queryOrder(@P("Order ID") String orderId) {
        return "Order " + orderId + " has shipped and should arrive tomorrow";
    }
    @Tool("Refund the specified order")
    String refund(@P("Order ID") String orderId, @P("Refund reason") String reason) {
        return "Order " + orderId + " refund initiated. Reason: " + reason;
    }
}
interface CustomerServiceAssistant {

@UserMessage("You are an e-commerce customer service assistant. Answer questions about orders and refunds. The customer's question is {{question}}")
    String answer(String question);
}
CustomerServiceAssistant assistant = AiServices.builder(CustomerServiceAssistant.class)
        .chatModel(model)
        .tools(new OrderTools())
        .chatMemory(MessageWindowChatMemory.withMaxMessages(20))
        .build();
String reply = assistant.answer("Where is order 20260907001?");

Агент — это интерфейс плюс общий диапазон действия

При разработке этапа «Агент» сначала запишите контракт: необходимые входные данные, сигнал успешного выполнения и действия при частичной неудаче. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Документируйте одновременно путь успешного выполнения и путь восстановления. Повторные попытки, проверки человеком и обработка неработоспособных сообщений являются частью продукта, а не элементами последующей доработки. Создавайте контрольные точки после дорогостоящих шагов. Механизм возобновления работы не должен повторно взимать плату за один и тот же вызов LLM, когда оператор пытается выполнить следующий узел заново.

public interface DraftWriter {

@UserMessage("""
            You are a technical editor. Write an opening of no more than 200 words on the topic {{topic}}.
            Return only the opening, with no explanation.
            """)
    @Agent(outputKey = "draft", description = "Writes an opening based on a topic")
    String write(@V("topic") String topic);
}

public interface Sharpener {
    @UserMessage("""
            You are a reviewer. Make the following opening more specific and remove filler.
            The opening is {{draft}}.
            Return only the revised text.
            """)
    @Agent(outputKey = "draft", description = "Makes the opening more specific")
    String polish(@V("draft") String draft);
}

public interface TitlePicker {
    @UserMessage("""
            Write 3 headlines suitable for a technical blog for the following content, one per line.
            The content is {{draft}}.
            """)
    @Agent(outputKey = "titles", description = "Writes headlines for the content")
    String pickTitle(@V("draft") String draft);
}
DraftWriter writer = AgenticServices
        .agentBuilder(DraftWriter.class)
        .chatModel(model)
        .build();


Sharpener sharpener = AgenticServices
        .agentBuilder(Sharpener.class)
        .chatModel(model)
        .build();
TitlePicker titlePicker = AgenticServices
        .agentBuilder(TitlePicker.class)
        .chatModel(model)
        .build();
UntypedAgent editor = AgenticServices
        .sequenceBuilder()
        .subAgents(writer, sharpener, titlePicker)
        .outputKey("titles")
        .build();
Object result = editor.invoke(Map.of("topic", "Building agents with LangChain4j"));

Навыки — это инструкции по запросу, а не аннотация @Skill

На этапе реализации инструкций типа «навыки по запросу» сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Предпочитайте небольшие, тестируемые модули большим скриптам. Если какой-то шаг не сработает, причина должна быть связана с конкретной функцией, а не с запутанной цепочкой операций. Вводите контрольные точки после дорогостоящих шагов. Система не должна снова взимать плату за один и тот же вызов большой языковой модели, когда оператор пытается выполнить следующий этап. На этапе реализации инструкций типа «навыки по запросу» сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Записывайте время выполнения и стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные счета при переходе от демо-среды к общедоступным средам.

skills/refund/SKILL.md

---
name: refund
description: Handle refund requests. Look up the order, check the conditions, then either refund or escalate to a human.
---
When a customer requests a refund:
1. First call queryOrder to check the order status.
2. Only call refund when the order has shipped or is complete.
3. If the order is still awaiting payment, tell the customer to cancel it.
4. If the amount exceeds 500, escalate to a human instead of refunding directly.
List<FileSystemSkill> loadedSkills = FileSystemSkillLoader.loadSkills(Path.of("skills"));
Skills skillSet = Skills.from(loadedSkills);

CustomerServiceAssistant assistant = AiServices.builder(CustomerServiceAssistant.class)
        .chatModel(model)
        .chatMemory(MessageWindowChatMemory.withMaxMessages(20))
        .tools(new OrderTools())
        .toolProvider(skillSet.toolProvider())
        .systemMessage("You have access to the following skills. When a request relates to one of them, activate it first.\n"
                + skillSet.formatAvailableSkills())
        .build();

Длинные рабочие процессы должны поддерживать паузу и возобновление

Длинные рабочие процессы лучше всего функционируют, если их рассматривать как измеримую структуру. Соберите один образец успешной работы, один пример сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая всю структуру. Сохраняйте состояние структуры простым и типизированным. Вложенные объекты скрывают информацию о том, какой узел заполнил тот или иной поле, что приводит к невозможности возобновления работы после перерывов.

public interface OrderWorkflow extends AgenticScopeAccess {

    @Agent
    String processOrder(@MemoryId String orderId, @V("order") String orderDetails);
}
AgenticScopeAction validateOrder = AgenticServices.agentAction(scope -> {
    String order = scope.readState("order", "");
    scope.writeState("validated_order", "VALIDATED: " + order);
});


HumanInTheLoop approvalGate = AgenticServices.humanInTheLoopBuilder()
        .description("Large orders require manual approval")
        .outputKey("approval")
        .responseProvider(scope -> new SuspendedResponse<>("manager-approval"))
        .build();
AgenticScopeAction shipOrder = AgenticServices.agentAction(scope -> {
    String validated = scope.readState("validated_order", "");
    String approval = scope.readState("approval", "");
    scope.writeState("result", "Order processed " + approval);
});
OrderWorkflow workflow = AgenticServices.sequenceBuilder(OrderWorkflow.class)
        .subAgents(validateOrder, approvalGate, shipOrder)
        .outputKey("result")
        .build();
ResultWithAgenticScope<String> result =
        workflow.processOrder("order-10001", "1000 units");

if (result.suspended()) {
    result = result.completePendingResponse("Manager approved");
}
AgenticScopePersister.setStore(new MyAgenticScopeStore());

A2A позволяет удалённым агентам работать как локальные составляющие

A2A позволяет удаленным агентам наилучшим образом выполнять свои задачи, когда его рассматривают как измеримую структуру. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ.

<dependency>
  <groupId>dev.langchain4j</groupId>
  <artifactId>langchain4j-agentic-a2a</artifactId>
  <version>1.19.0</version>
</dependency>
public interface RemoteRiskAgent {


@Agent
    String check(@V("transaction") String transaction);
}
UntypedAgent riskAgent = AgenticServices
        .a2aBuilder("https://risk.internal/a2a", RemoteRiskAgent.class)
        .outputKey("risk_result")
        .build();
public interface ChatAgent {


@A2AClientAgent(a2aServerUrl = "http://localhost:8080", outputKey = "response")
    ResultWithAgenticScope<String> chat(
            @V("question") String question,
            @A2AContextId @V("contextId") String contextId,
            @A2ATaskId @V("taskId") String taskId);
}
ResultWithAgenticScope<String> first = chatAgent.chat("Hello", null, null);
String contextId = (String) first.agenticScope().readState("contextId");
String taskId = (String) first.agenticScope().readState("taskId");
ResultWithAgenticScope<String> second = chatAgent.chat("Continue", contextId, taskId);

Три практических рекомендации для команд, работающих с Java

Три практических рекомендации для этапной разработки наилучшим образом работают, когда их рассматривают как измеримую поверхность. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объема работ. Предпочитайте небольшие, тестируемые единицы кода вместо обширных скриптов. Когда какой-то шаг сбивается, причина сбоя должна указывать на конкретную ответственность, а не на запутанную цепочку операций. Сохраняйте структуру графа простой и типизированной. Вложенные структуры скрывают информацию о том, какой узел заполнил тот или иной поле, и мешают возобновлению работы после прерываний. Три практических рекомендации для этапной разработки наилучшим образом работают, когда их рассматривают как измеримую поверхность. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объема работ. Записывайте временные показатели, а также стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные расходы при переходе от демо-версии к общедоступным средам.

Чек-лист операций

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

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

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

При наличии бюджета добавляйте тесты на базовую работоспособность, которые проверяют критически важные этапы в рамках CI с использованием фикстчеров, а не реальных платных API.

Записывайте временные показатели, а также стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные счета при переходе от демо-среды к общедоступным средам.

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

Перед внедрением стека заморозьте версии, сделайте копию «золотого» отчета для критической цепочки операций и уточните шаги возврата к предыдущему состоянию. В совместных средах необходимы ограничения по частоте запросов, проверки принадлежности пользователя и четко определенный ответственный за обновление секретов. Лучше добиваться простой надежности, чем создавать креативные одноразовые демонстрации.

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