Build multi-agent AI bằng Java với LangChain4j và AgenticScope

Tách một prompt hoàn tiền thành ba agent Java (order, refund, response) chia sẻ dữ liệu qua AgenticScope và chạy tuần tự bằng LangChain4j.

Đã duyệt độc lập · cập nhật 25/10/2026 · 50 min

1. Java developer và LangChain4j: nhắc lại flow của một agent

Basanta mở đầu bằng một mong muốn: sau video LangChain4j trước của anh, nỗi lo "mình có cần Python để làm AI không" đã bớt đi phần nào, và một Java developer giờ thấy tự tin hơn rằng mình cũng có thể bắt đầu build ứng dụng có AI bằng LangChain4j. Nhưng anh muốn nói rõ một điều trước: mục đích của anh không phải là so sánh Java với Python. Anh chỉ muốn cho thấy Java developer cũng có những thư viện mạnh như LangChain4j để build ứng dụng AI hiện đại mà không phải rời khỏi hệ sinh thái Java (theo anh, trong video đăng ngày 24/10/2026).

Hình minh hoạ LangChain4j ở giữa, xung quanh là Order Agent, Refund Agent, Response Agent, Support Agent và Search Agent
Hình mở đầu video (đăng ngày 24/10/2026): LangChain4j đứng giữa, nối với nhiều agent chuyên biệt như Order Agent, Refund Agent, Response Agent; trên khối giữa có ghi AI Agents, Tools, Memory, RAG, Orchestration.

Rồi anh quay lại chủ đề chính. Trong video trước, anh đã build một ứng dụng có AI bằng LangChain4j, trong đó LLM hiểu được prompt của người dùng và gọi đúng method Java của mình thông qua cơ chế tool calling. Để bài hôm nay (video đăng ngày 24/10/2026) dễ theo hơn, anh nhắc lại thật nhanh flow ở mức high level.

Flow rất đơn giản. Người dùng đưa prompt vào ứng dụng. Ứng dụng có một order agent, agent này nhận prompt và chuyển nó cho LangChain4j, tức là dependency đã thêm vào trong ví dụ order agent lần trước. Khi request tới LangChain4j, anh nhấn mạnh: LangChain4j chỉ là một thư viện, nó không biết cách tự thực thi prompt rồi trả lời. Việc nó làm là điều phối (orchestrate) request đó, kèm theo mọi tool bạn đã định nghĩa, sang large language model. Trong ví dụ của anh, LLM là OpenAI (video đăng ngày 24/10/2026).

Sơ đồ: User Request vào Application, Prompt qua Agent, rồi LangChain4j gửi Prompt + Tool Definitions tới LLM; LLM gửi Tool Request tới Tools, Tools chạy Execute Logic, Response quay về LLM rồi về Application
Application chuyển prompt cho Agent, Agent chuyển cho LangChain4j, LangChain4j gửi prompt cùng định nghĩa tool cho LLM; LLM yêu cầu gọi tool, tool chạy logic và trả kết quả về LLM, rồi câu trả lời quay lại Application.

Tiếp theo, chính LLM quyết định nên gọi tool nào cho phù hợp và gọi tool đó. Tool thực thi logic của nó: có thể kết nối database, có thể gọi web search, có thể gọi một hệ thống bên ngoài nào đó. Tool lấy kết quả rồi trả lại cho LLM. Lúc này LLM chuyển kết quả đó thành ngôn ngữ con người hiểu được, cái anh gọi là natural language. Đó là flow đơn giản đã bàn trong ví dụ trước (theo video đăng ngày 24/10/2026).

Hôm nay anh sẽ build nhiều AI agent, và xem nhiều agent có thể làm việc "parallelly" (song song) ra sao, hay nói cách khác LangChain4j điều phối nhiều AI agent trong cùng một ứng dụng như thế nào (lời anh trong video đăng ngày 24/10/2026; phần demo về sau chạy ba agent theo thứ tự, xem mục 11).

2. Một prompt, ba trách nhiệm

Kiến trúc một agent ở trên chạy tốt cho use case đơn giản. Nhưng trong một ứng dụng enterprise thật, theo Basanta, giữ đúng một AI agent không phải lúc nào cũng đủ (video đăng ngày 24/10/2026). Anh đưa ra một use case: người dùng gõ prompt "This is my order ORD-1005. Can I get a refund?" (đây là đơn hàng ORD-1005 của tôi, tôi có được hoàn tiền không?).

Nhìn kỹ request này thì có nhiều trách nhiệm nằm trong cùng một prompt. Thứ nhất, cần lấy chi tiết đơn hàng ORD-1005. Thứ hai, cần lấy thông tin chính sách hoàn tiền (refund policy) áp dụng cho đúng đơn hàng đó. Sau khi có hai thông tin này, thứ ba, cần build một câu trả lời cuối cùng cho người dùng. Anh hỏi lại người xem: có bao nhiêu trách nhiệm? Ba: lấy đơn hàng, lấy refund policy ứng với đơn hàng đó, rồi viết câu trả lời cuối mà người dùng hiểu được (ví dụ trong video đăng ngày 24/10/2026).

"This is my order ORD-1005. Can I get a refund?" 1. Fetch order detailsđơn ORD-1005 2. Fetch refund policytheo trạng thái đơn hàng 3. Build final responsecâu trả lời cho người dùng ba trách nhiệm trong một prompt (ví dụ trong video đăng ngày 24/10/2026)
Hình 2.1Prompt ví dụ của Basanta tách ra thành ba việc riêng (video đăng ngày 24/10/2026): lấy chi tiết đơn hàng, lấy refund policy cho đơn đó, rồi viết câu trả lời cuối.

Anh đoán người xem sẽ hỏi: không thể dồn cả ba trách nhiệm vào một AI agent mà mình đã build sao? Đưa prompt này vào, để nó tự làm cả ba việc? Anh trả lời: có, làm được, không có vấn đề gì. Nhưng khi ứng dụng lớn lên, cách đó có thể trở nên khó bảo trì (theo anh, video đăng ngày 24/10/2026). Bây giờ bạn mới chỉ lo lấy chi tiết đơn hàng và lấy refund policy, nhưng sau này nếu cần tích hợp thêm việc lấy thông tin tồn kho (inventory), hay thực hiện các việc liên quan tới thanh toán (payment) thì sao? Ứng dụng càng lớn thì trách nhiệm càng nhiều.

Vì vậy, thay vì giữ mọi thứ trong một AI agent, anh đề xuất tạo nhiều AI agent chuyên biệt (specialized). Một agent chỉ để lấy chi tiết đơn hàng, đặt tên là order agent. Tương tự, một agent khác chỉ lấy thông tin hoàn tiền, đặt tên là refund agent. Và agent thứ ba, agent cuối, là response agent. Order agent chỉ lo thông tin đơn hàng, refund agent chỉ lo refund policy, response agent chỉ lo viết câu trả lời cuối cho người dùng. Cả ba việc được giao cho từng agent chuyên biệt, mỗi agent lo phần việc của mình (cách chia trong video đăng ngày 24/10/2026).

Bước tiếp theo là để LangChain4j điều phối tất cả các agent đã tạo: ba agent, mỗi agent một trách nhiệm, và mọi thứ do LangChain4j quản lý. Đó chính là thứ anh sẽ build trong video đăng ngày 24/10/2026: hiểu multi-agent AI chạy thế nào với LangChain4j, các agent chia sẻ thông tin với nhau ra sao, và implement toàn bộ flow bằng Java.

3. Ba agent chuyên biệt trên bảng vẽ

Trước khi vào code, Basanta vẽ ý tưởng trên một bảng vẽ tay. Trước hết là ứng dụng (application). Agent đầu tiên là order agent. Theo flow đã nhắc, order agent chuyển prompt cho LangChain4j, LangChain4j chuyển cho LLM. Để vẽ cho gọn, ngay dưới order agent anh đặt luôn phần tools, gọi là order tools: order agent nhờ LangChain4j và LLM gọi đúng tool. Sau khi chạy xong, order tools trả về chi tiết đơn hàng, anh ghi là order details (anh nói bạn có thể đặt tên gì cũng được; bản vẽ trong video đăng ngày 24/10/2026).

Khi ứng dụng nhận prompt, hay input, hay request của người dùng (anh đặt tên là request), request đi vào ứng dụng, ứng dụng chuyển cho order agent. Order agent nhờ LangChain4j và LLM gọi order tools, order tools chạy logic và trả order details. Tương tự, anh tạo agent thứ hai là refund agent, sửa nhãn thành refund tools và refund details; request cũng có thể đi tới agent này, chỉ cần đổi hướng mũi tên (bản vẽ trong video đăng ngày 24/10/2026).

Bảng vẽ tay: request vào application, rồi tới ba cột ORDER-AGENT, REFUND-AGENT, RESPONSE-AGENT, mỗi cột có TOOLS và kết quả ORDER-DETAILS, REFUND-DETAILS, FINAL RESPONSE
Bảng vẽ tay trong video (đăng ngày 24/10/2026): request vào application, application chuyển tới ba agent; mỗi cột có agent, tools và kết quả của nó (order details, refund details, final response).

Agent thứ ba là response agent: anh copy nguyên flow, dán sang bên cạnh và đổi tên thành response. Vậy là có ba agent: agent thứ nhất, thứ hai, thứ ba. Anh nói bạn có thể có n agent trong ứng dụng tuỳ use case. Prompt hay request đi vào ứng dụng, ứng dụng nhờ LangChain4j và LLM điều hướng request tới đúng agent. Trông có vẻ đơn giản (cách anh mô tả trong video đăng ngày 24/10/2026).

4. Agent sau làm sao biết agent trước đã làm gì?

Rồi Basanta đặt câu hỏi đầu tiên: refund agent làm sao biết order agent đã lấy đơn hàng nào? Đơn hàng có trạng thái gì? Phải dùng chi tiết đơn hàng nào để kiểm tra refund policy? Muốn refund agent kiểm tra refund policy thì phải đưa thông tin đơn hàng cho nó, nhưng refund agent không có chút thông tin nào về việc agent trước đã chạy gì (theo anh, video đăng ngày 24/10/2026).

Về mặt kỹ thuật, vậy thì phải tìm cách chuyển kết quả order details sang refund agent. Anh vẽ thêm một mũi tên: bạn có thể định nghĩa nó như một key, ví dụ key là order details, và chuyển key đó cho refund agent. Tương tự, refund agent đã có order details, nó kiểm tra refund policy và tạo ra refund details. Nhưng response agent làm sao biết đơn hàng đó áp dụng refund policy nào, và đó là đơn hàng nào? (bản vẽ và câu hỏi trong video đăng ngày 24/10/2026)

Cũng như vậy, refund agent phải chuyển output của mình cho agent kế tiếp. Còn response agent, để viết câu trả lời cuối, cần hai thông tin: refund policy của đơn hàng, và chính xác đơn hàng đã lấy là gì. Vậy phải chuyển hai key khác nhau cho response agent. Anh đánh số 1, 2, 3 cho ba agent và vẽ các mũi tên key: một key cho refund agent, hai key cho response agent, để mọi agent được đồng bộ (sync) với nhau (bản vẽ trong video đăng ngày 24/10/2026).

order agentrefund agentresponse agent keykeykey order details 10, 20 agent trở lên: mỗi agent phải chờ output của agent khác tight coupling, khó quản lý (theo video đăng ngày 24/10/2026)
Hình 4.1Cách chuyển key thủ công mà Basanta vẽ (video đăng ngày 24/10/2026): với ba agent vẫn ổn, nhưng khi số agent tăng thì các agent dính chặt vào nhau.

Với hai hay ba agent, cách này vẫn trông đơn giản. Nhưng anh bảo hãy tưởng tượng ứng dụng có 10, 20 hay hơn nữa agent, không chỉ order, refund và response. Chuyển output của một agent sang nhiều agent khác bằng tay sẽ trở nên khó quản lý. Các agent cũng bắt đầu phụ thuộc chặt vào nhau: refund agent không chạy được nếu chưa có kết quả từ order agent, và response agent cũng cần thông tin từ cả refund agent lẫn order agent. Điều đó tạo ra tight coupling giữa từng agent (theo anh, video đăng ngày 24/10/2026).

Đây là chỗ, theo Basanta, LangChain4j cho một giải pháp gọn hơn, gọi là AgenticScope (video đăng ngày 24/10/2026).

5. AgenticScope: shared context cho cả workflow

Anh bảo hãy nghĩ về AgenticScope như một shared context, một ngữ cảnh dùng chung cho toàn bộ multi-agent workflow. Trên bảng vẽ, anh thêm một ô lớn bên phải, ghi "AGENTIC SCOPE" và bên dưới là "shared context" (bản vẽ trong video đăng ngày 24/10/2026).

Bảng vẽ: ba cột agent đánh số 1, 2, 3 với các mũi tên Key màu xanh nối order details tới refund agent và response agent; bên phải là ô AGENTIC SCOPE, shared context
Bảng vẽ ở thời điểm anh giới thiệu AgenticScope (video đăng ngày 24/10/2026): bên trái là các mũi tên key chuyển tay giữa ba agent, bên phải là ô AGENTIC SCOPE mang nhãn shared context.

AgenticScope giúp thế nào? Theo cách Basanta giải thích, mỗi agent (order agent, refund agent, response agent) có thể lưu output của mình, tức là key mà nó trả về, thẳng vào AgenticScope (video đăng ngày 24/10/2026). Trước tiên, request hay prompt của người dùng được đưa vào. Tiếp theo order agent chạy bằng order tools và trả về order details. Order details này không cần bạn tự tay chuyển cho các agent khác nữa: AgenticScope sẽ giữ thông tin order details, output của agent tự động đi vào AgenticScope.

Đến lượt refund agent chạy. Nó không lấy input từ agent trước nữa, mà lấy order details thẳng từ shared context, tức AgenticScope. Refund agent chạy qua refund tools và trả về refund details như một key, key đó cũng được lưu vào đây. Mọi agent đều chạy và output của chúng được lưu trong AgenticScope, nên agent nào cần kết quả của agent nào thì tra thẳng trong AgenticScope (theo mô tả trong video đăng ngày 24/10/2026).

Response agent cần hai input: kết quả của order agent và kết quả của refund agent. Nó lấy cả hai thẳng từ context này. Rồi kết quả của response agent cũng được lưu vào đây, theo key bạn định nghĩa: nếu bạn đặt key là final response thì nó lưu dưới key final response, nếu chỉ đặt là response thì nó lưu dưới key response. Nói gọn lại, output của mỗi agent chính là một key do bạn định nghĩa, nằm trong AgenticScope: order details, refund details, final response. Agent nào cần input từ agent khác thì tra trực tiếp ở đây. Đó là lý do anh gọi AgenticScope là shared context trong một multi-AI-agent workflow (video đăng ngày 24/10/2026).

6. Sơ đồ LangChain4j Multi-Agent Workflow

Để có bức tranh rõ hơn, Basanta mở một sơ đồ hoàn chỉnh mang tên "LangChain4j Multi-Agent Workflow" (video đăng ngày 24/10/2026). Anh đi qua từng bước: có user prompt, request đi tới order agent. Order agent chạy order tool, tool lấy thông tin từ database (MySQL trên sơ đồ). Rồi order details được lưu vào AgenticScope. Tiếp theo, order details được đưa tự động từ AgenticScope sang refund agent.

Sơ đồ LangChain4j Multi-Agent Workflow: User Prompt và request lưu vào AgenticScope; OrderAgent gọi Order Tool, MySQL, ra orderDetails; RefundAgent gọi Refund Tool, Refund Policy DB, ra refundDecision; ResponseAgent ra finalResponse tới USER; AgenticScope chứa request, orderDetails, refundDecision, finalResponse
Ở giữa là AgenticScope chứa bốn key request, orderDetails, refundDecision, finalResponse; mỗi agent ghi kết quả của mình vào đó ("stored in AgenticScope") và đọc từ đó những gì nó cần.

Refund agent chạy, lấy refund details từ database (Refund Policy DB trên sơ đồ) và trả về một key tên là refund decision. Output của refund agent, tức key đó, lại được lưu về AgenticScope. Lúc này AgenticScope có thông tin về order details và refund decision. Hai input này được đưa vào response agent; response agent xử lý mọi input và build câu trả lời cuối, rồi final response lại quay về nằm trong AgenticScope. Anh giải thích có thể có những agent khác cần tra final response này (theo video đăng ngày 24/10/2026).

Theo Basanta, đó là lý do LangChain4j tạo cơ chế này sao cho output của mỗi agent mặc định được ánh xạ thành một key trong AgenticScope (video đăng ngày 24/10/2026). Đây là kiến trúc đơn giản để làm multi-agent workflow với LangChain4j: các agent không phụ thuộc trực tiếp vào nhau, chúng chỉ đọc và ghi dữ liệu qua một shared context là AgenticScope, và điều đó, theo anh, làm cho multi-agent workflow gọn gàng và dễ quản lý hơn nhiều. Anh hy vọng kiến trúc đã rõ, và chuyển sang implement đúng flow này.

7. Thêm langchain4j-agentic, viết OrderAgent và đăng ký bean

Anh tiếp tục với chính ví dụ của video trước, nơi đã build order agent. Lần này anh mở rộng nó bằng hai agent nữa là refund agent và response agent, để thấy một multi-agent workflow đầy đủ chạy thế nào trong LangChain4j. Anh mở IntelliJ IDEA: đây là project order agent cũ, có một controller class, và trong controller có một agent tên là order support agent (OrderSupportAIAgent trên màn hình). Request được chuyển cho agent này, agent kết nối tới LLM và gọi tool phù hợp. Anh nhắc: mình chỉ định nghĩa agent, không gì khác; thư viện LangChain4j chuyển prompt hay request cùng định nghĩa mọi tool sang LLM, LLM chạy và trả lời lại (demo trong video đăng ngày 24/10/2026).

Bước đầu tiên để làm multi-AI-agent với LangChain4j là thêm dependency cho phần agentic. Trong pom.xml đã có sẵn LangChain4j OpenAI Spring Boot starter và LangChain4j Spring Boot starter; anh thêm một dependency nữa là langchain4j-agentic, "cùng version" như anh nói. Trên màn hình, version của dependency này là 1.21.0-beta31 (video đăng ngày 24/10/2026). Thêm xong, anh update project.

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-agentic</artifactId>
    <version>1.21.0-beta31</version>
</dependency>

Anh không đụng vào agent cũ. Thay vào đó anh tạo một package riêng tên agents, rồi tạo một interface tên OrderAgent, định nghĩa method String checkOrder nhận request. Tham số request được gắn annotation @V, annotation đến từ dev.langchain4j.service. Theo anh, mục đích của @V là đọc từ AgenticScope, nên bạn phải ghi trong @V đúng key bạn muốn tra trong AgenticScope (video đăng ngày 24/10/2026). Nhìn lại flow: order agent nhận request trước tiên, vì vậy anh đặt @V("request"); order agent sẽ lấy key này từ AgenticScope.

Tiếp theo phải khai báo agent này trả về gì, output key là gì. Anh dùng annotation @Agent, đặt outputKey là orderDetails và thêm một description. Output key chính là kết quả của agent, tức order details, và cũng chính key đó được đưa vào AgenticScope. Tóm lại theo cách anh nói trong video đăng ngày 24/10/2026: output key ghi vào AgenticScope, còn @V đọc từ AgenticScope.

Để agent làm việc tốt hơn, anh thêm một user message. Anh nói phần này là tuỳ chọn, để agent hiệu quả hơn: bạn mô tả agent này sẽ làm gì hay chạy thế nào. Lời dặn của anh: yêu cầu của khách là thế này, hãy tách order ID từ request, dùng tool có sẵn để lấy chi tiết đơn hàng thật, và chỉ trả về đầy đủ chi tiết đơn hàng. Đây là interface OrderAgent như trên màn hình (code trong video đăng ngày 24/10/2026):

IntelliJ IDEA mở OrderAgent.java: @UserMessage với The customer request is: {{request}}, rồi @Agent với outputKey orderDetails
File OrderAgent.java trong IntelliJ IDEA (video đăng ngày 24/10/2026): khối @UserMessage chèn biến {{request}}, ngay dưới là @Agent với outputKey = "orderDetails"; cây thư mục bên trái có package mới agents.
public interface OrderAgent {

    @UserMessage("""
            The customer request is:

            {{request}}

            Extract the order ID from the request.

            Use the available tool to get
            the real order details.

            Return the complete order details only.
            """)
    @Agent(
            outputKey = "orderDetails",
            description = "Fetches current order details"
    )
    String checkOrder(@V("request") String request);
}

Build xong agent đầu tiên, bước tiếp theo là đăng ký nó thành bean. Anh tạo package config, trong đó tạo class Java AgentConfig gắn @Configuration. Rồi anh tạo một bean cho order agent: @Bean, public OrderAgent orderAgent(...), một method trả về object order agent, nhận tham số là ChatModel và các tool dành cho việc đơn hàng, tức OrderTools đã định nghĩa từ trước. Bên trong, anh build object bằng builder của AgenticServices: báo đây là order agent, build object cho nó, truyền chat model, truyền tools, và nhớ rằng orderDetails là key muốn trả về AgenticScope (code trong video đăng ngày 24/10/2026).

@Configuration
public class AgentConfig {

    @Bean
    public OrderAgent orderAgent(
            ChatModel chatModel,
            OrderTools orderTools) {

        return AgenticServices
                .agentBuilder(OrderAgent.class)
                .chatModel(chatModel)
                .tools(orderTools)
                .outputKey("orderDetails")
                .build();
    }
}

Anh dặn hãy nhớ key này, vì phải đưa nó cho refund agent: theo flow, response (kết quả) mang key order details phải đi tới refund agent ở chỗ @V. Anh nói lát nữa sẽ hiểu rõ (video đăng ngày 24/10/2026).

8. Bảng refund_policy, entity, service và RefundTool

Giờ đến refund agent. Để làm nó, Basanta đã tạo sẵn một bảng tên refund_policy. Kết quả truy vấn bảng có vài dòng ứng với các trạng thái đơn hàng khác nhau: trạng thái nào thì có được hoàn tiền không (cột eligible), và message đi kèm. Anh nói đây chỉ là một bảng đơn giản tạo cho demo này (video đăng ngày 24/10/2026).

DBeaver: SELECT * FROM refund_policy rp trả về 5 dòng DELIVERED, SHIPPED, PROCESSING, DELAYED, CANCELLED với cột eligible và message
Bảng refund_policy trong video đăng ngày 24/10/2026: năm trạng thái DELIVERED, SHIPPED, PROCESSING, DELAYED, CANCELLED; chỉ DELAYED có eligible = 1 với message "Delayed orders are eligible for a refund.", các trạng thái khác là 0 (ví dụ SHIPPED: "The order is already shipped. Wait until the promised delivery date.").

Anh tạo nhanh entity cho refund policy, rồi repository, service và tool. Trong package model đã có entity đơn hàng, anh thêm entity RefundPolicy với các field đúng như đã thấy trong database: order status, eligible và message. Rồi tạo repository: vào package repository, dán file RefundPolicyRepository vào và sửa lại package (demo trong video đăng ngày 24/10/2026).

Phần đầu của entity như trên màn hình (code trong video đăng ngày 24/10/2026), các getter và setter phía dưới được thu gọn trong IDE:

@Entity
@Table(name = "refund_policy")
public class RefundPolicy {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String orderStatus;

    private boolean eligible;

    private String message;

Có repository rồi thì tạo service tên RefundService, gắn @Service. Anh inject repository (gọi gọn là repository) bằng constructor injection. Sau đó định nghĩa một method lấy refund policy theo order status: public, trả về RefundPolicy, tên checkEligibility. Anh thêm một câu lệnh in ra màn hình (System.out.println), rồi gọi repository.findByOrderStatus, method đã định nghĩa sẵn trong repository nên gọi thẳng. Nếu không tìm thấy thì ném exception với thông báo không tìm thấy refund policy cho order status này (code trong video đăng ngày 24/10/2026).

Method trong RefundService như trên màn hình; phần cuối dòng orElseThrow bị khuất ở mép phải, ở đây lấy theo repo GitHub của video (đọc ngày 25/10/2026):

public RefundPolicy checkEligibility(String orderStatus) {
    System.out.println("\nREFUND SERVICE -> Checking status: " + orderStatus);
    return repository.findByOrderStatus(orderStatus)
            .orElseThrow(() -> new RuntimeException("Refund policy not found for: " + orderStatus));
}

Tiếp theo là refund tool. Trong package tool, anh tạo class Java mới RefundTool, gắn @Component, inject RefundService qua constructor. Rồi định nghĩa tool. Anh nhắc: bạn đã biết cách định nghĩa order tools rồi; tool chẳng qua là thứ gọi service của bạn, và tool này sẽ được AI của bạn gọi tự động (theo anh, video đăng ngày 24/10/2026). Cũng như vậy, bạn định nghĩa tool cho refund policy check eligibility: viết method public String checkRefundEligibility, gọi service, và gắn annotation @Tool.

Anh lưu ý: mọi import phải đến từ dev.langchain4j.agent.tool; nếu bạn import từ chỗ khác thì nó sẽ không chạy (lời anh trong video đăng ngày 24/10/2026). Mô tả của tool là "check whether an order is eligible for a refund based on the order status" (kiểm tra một đơn hàng có được hoàn tiền không dựa trên trạng thái đơn hàng). Vậy là xong một tool đơn giản. Bản trong repo GitHub của video (đọc ngày 25/10/2026) còn có thêm một dòng in ra console khi tool được gọi:

@Tool("Check whether an order is eligible for a refund based on order status")
public String checkRefundEligibility(String orderStatus) {
    System.out.println("\n>>> REFUND TOOL CALLED -> " + orderStatus);
    return refundService.checkEligibility(orderStatus).toString();
}

9. RefundAgent: đọc orderDetails, ghi refundDecision

Đã có tool, service, entity và repo, anh bắt đầu tạo refund agent giống cách đã làm order agent: tạo class Java mới, chọn kiểu interface, đặt tên RefundAgent. Trong đó định nghĩa method String checkRefund. Input cần truyền là gì? Request, và order details (demo trong video đăng ngày 24/10/2026).

Anh nhấn mạnh chỗ này: refund agent cần order details, vậy nó lấy từ đâu? Từ AgenticScope. Bất cứ thứ gì bạn muốn lấy từ AgenticScope thì phải gắn @V, anh nói đó là điều đơn giản cần hiểu. Và phải ghi đúng key: key ở đây phải giống hệt order details. Nếu bạn ghi khác đi, bạn sẽ không lấy được giá trị đó từ AgenticScope. Output của order agent được publish vào AgenticScope dưới key nào thì refund agent phải đọc đúng key đó (theo anh, video đăng ngày 24/10/2026). Tham số request cũng gắn @V("request").

Khi refund agent chạy xong, nó trả về gì? Nó trả về refund decision. Refund decision chính là key, là output của refund agent, và nó lại quay về nằm trong AgenticScope. Cách khai báo giống như ở order agent: dùng annotation @Agent, đặt output key và description cho thứ agent trả về. Anh nhắc lại: thứ agent trả về là output key, thứ agent đọc là @V; anh cố ý lặp lại hai annotation này để người xem hiểu flow chạy thế nào trong multi-AI-agent với LangChain4j (video đăng ngày 24/10/2026).

RefundAgent.java: user message có {{request}}, Order information: {{orderDetails}}, các bước kiểm tra, và @Agent với outputKey refundDecision, description Checks refund eligibility
RefundAgent.java trong video đăng ngày 24/10/2026: user message chèn cả {{request}} lẫn {{orderDetails}} (đọc từ AgenticScope), và @Agent khai báo outputKey = "refundDecision".

Anh cũng thêm user message: đây là yêu cầu của khách; thông tin đơn hàng thì đã lấy từ AgenticScope; hãy xác định trạng thái hiện tại của đơn hàng từ thông tin đó, dùng refund tool để kiểm tra đơn có được hoàn tiền không, và chỉ trả về quyết định hoàn tiền cùng giải thích theo policy. Phần interface như sau; dòng đầu của user message bị cuộn khuất trên màn hình, ở đây lấy theo repo GitHub của video (đọc ngày 25/10/2026):

public interface RefundAgent {

    @UserMessage("""
            Customer request:

            {{request}}

            Order information:

            {{orderDetails}}

            Determine the current order status
            from the order information.

            Use the refund tool to check
            whether this order is eligible for refund.

            Return only the refund decision
            and policy explanation.
            """)
    @Agent(
            outputKey = "refundDecision",
            description = "Checks refund eligibility"
    )
    String checkRefund(@V("request") String request, @V("orderDetails") String orderDetails);
}

Bước tiếp theo là đăng ký refund agent trong config, giống hệt cách đã làm với order agent: một bean public RefundAgent refundAgent(...), nhận chat model và refund tool, rồi khai báo key mà agent này trả về. Anh nói mọi thông tin đều được định nghĩa ngay lúc đăng ký agent (code trong video đăng ngày 24/10/2026):

@Bean
public RefundAgent refundAgent(
        ChatModel chatModel,
        RefundTool refundTool) {

    return AgenticServices
            .agentBuilder(RefundAgent.class)
            .chatModel(chatModel)
            .tools(refundTool)
            .outputKey("refundDecision")
            .build();
}

10. ResponseAgent: agent cuối, không gọi tool nào

Agent cuối cùng là response agent. Anh nhắc: response agent cần order details, là output của order agent, và refund decision, là output của refund agent. Cả hai output này phải được response agent đọc từ AgenticScope, bằng annotation @V (video đăng ngày 24/10/2026).

Anh vào package agents, tạo class Java mới, chọn interface, đặt tên ResponseAgent. Rồi định nghĩa method String createResponse. Agent này cần gì? Prompt hay request của người dùng, order details và refund decision. Lấy order details và refund decision từ đâu? Từ AgenticScope. Lấy bằng cách nào? Bằng annotation @V; anh hỏi người xem giờ đã nhớ chưa. Anh thêm annotation cho từng tham số và dặn phải sửa key cho đúng, nếu không thì sẽ không chạy, kể cả với request (lời anh trong video đăng ngày 24/10/2026).

Trước khi thêm user message, cũng phải khai báo response agent trả về gì, output nào, key nào ánh xạ vào AgenticScope. Anh dùng @Agent, đặt output key là finalResponse và thêm description. Rồi đến user message. Anh nói phần này là tuỳ chọn, nhưng anh luôn khuyên dùng, để agent chạy đúng cách bạn muốn và build câu trả lời đúng cách bạn muốn (theo anh, video đăng ngày 24/10/2026). Lời dặn của anh: yêu cầu gốc của khách là thế này, chi tiết đơn hàng là thế này, quyết định hoàn tiền là thế này; hãy viết một câu trả lời ngắn, rõ ràng và hữu ích cho khách; không được bịa thêm thông tin. Anh nói thêm: "đây là thứ tôi muốn, boss, anh cứ build cho tôi".

Đoạn tạo user message của ResponseAgent không hiện trọn trên màn hình; bản dưới đây lấy từ repo GitHub mà video dẫn (đọc ngày 25/10/2026), khớp với những gì anh đọc trong video:

public interface ResponseAgent {

    @UserMessage("""
            Original customer request:

            {{request}}

            Order details:

            {{orderDetails}}

            Refund decision:

            {{refundDecision}}

            Create a short, clear and helpful
            response for the customer.

            Do not invent any information.
            """)
    @Agent(
            outputKey = "finalResponse",
            description = "Creates final customer response"
    )
    String createResponse(
            @V("request") String request,
            @V("orderDetails") String orderDetails,
            @V("refundDecision") String refundDecision
    );
}

Rồi anh đăng ký response agent thành bean trong AgentConfig. Anh nói response agent là agent đơn giản: nó nhận input order details và refund decision rồi build câu trả lời. Nó không kết nối tool nào, không kết nối database hay nguồn ngoài nào; anh cố ý giữ nó đơn giản. Bean là public ResponseAgent responseAgent(...), và anh dặn nhớ khai báo output key của agent, rồi gắn @Bean (demo trong video đăng ngày 24/10/2026).

OrderAgent@V: requestoutputKey: orderDetailstool: OrderTools RefundAgent@V: request, orderDetailsoutputKey: refundDecisiontool: RefundTool ResponseAgent@V: request, orderDetails,refundDecisionoutputKey: finalResponsekhông có tool @V đọc từ AgenticScope · outputKey ghi vào AgenticScope ba agent như trong code của video đăng ngày 24/10/2026
Hình 10.1Tóm tắt ba agent theo code trong video đăng ngày 24/10/2026: mỗi agent đọc những key cần thiết bằng @V và ghi kết quả của mình bằng output key.

11. WorkflowSupportAgent và sequenceBuilder

Đã có ba agent, cả ba đã đăng ký thành bean. Vậy ai quản lý cả ba? Theo Basanta, LangChain4j quản lý (video đăng ngày 24/10/2026). Để điều phối ba agent, anh tạo thêm một agent cha (parent agent), đặt tên là workflow support agent. Ba bean (order agent, refund agent, response agent) đã có, nhưng còn phải nói với framework, tức LangChain4j, rằng mình muốn ba agent chạy theo cách nào: order agent chạy trước, kết quả của nó đi sang refund agent; refund agent chạy thứ hai; rồi kết quả của cả order agent và refund agent đi sang response agent, và response agent build câu trả lời cuối. Muốn điều phối như vậy thì cần một agent cha.

Agent cha này rất đơn giản. Anh định nghĩa một method public String handle, truyền request vào; request này lấy từ AgenticScope nên dùng @V. Chỉ cần thêm @Agent là xong. Agent này không làm gì cả, theo lời anh: nó không ghi gì vào AgenticScope, không đọc gì từ AgenticScope ngoài request, vì nó là thứ điều phối flow; nó chỉ đọc request và chuyển đi (code trong video đăng ngày 24/10/2026).

public interface WorkflowSupportAgent {

    @Agent
    public String handle(@V("request") String request);

}

Rồi trong AgentConfig, anh định nghĩa thứ tự thực thi của các agent bằng một bean public WorkflowSupportAgent workflowSupportAgent(...). Agent này nhận cả ba agent làm input: order agent, refund agent và response agent. Nó không build object trực tiếp: anh dùng AgenticServices.sequenceBuilder(WorkflowSupportAgent.class), nơi bạn định nghĩa trình tự muốn chạy. Bạn khai báo các sub agent bằng subAgents, nhận một danh sách đối số: order agent, refund agent và response agent. Output key là final response. Cuối cùng là build object (code trong video đăng ngày 24/10/2026).

AgentConfig.java: @Bean workflowSupportAgent nhận OrderAgent, RefundAgent, ResponseAgent; return AgenticServices.sequenceBuilder(WorkflowSupportAgent.class).subAgents(orderAgent, refundAgent, responseAgent).outputKey(finalResponse).listener(new DebugAgentListener()).build()
Bean workflowSupportAgent hoàn chỉnh trong video đăng ngày 24/10/2026: sequenceBuilder nhận ba sub agent theo đúng thứ tự order, refund, response; output key là finalResponse; dòng .listener(new DebugAgentListener()) được thêm ở bước sau (mục 12).
@Bean
public WorkflowSupportAgent workflowSupportAgent(OrderAgent orderAgent,
                                                 RefundAgent refundAgent,
                                                 ResponseAgent responseAgent) {

    return AgenticServices
            .sequenceBuilder(WorkflowSupportAgent.class)
            .subAgents(orderAgent, refundAgent, responseAgent)
            .outputKey("finalResponse")
            .listener(new DebugAgentListener())
            .build();

}

12. DebugAgentListener: chứng minh AgenticScope có thật

Anh đoán người xem có thể gọi tên anh và nghĩ: "những gì anh giải thích rất hay, nhưng cái AgenticScope này có thật không, hay anh chỉ đang nói lý thuyết?" Anh bảo đừng lo, anh sẽ chứng minh: sẽ cho thấy có một thứ tên là AgenticScope, và bất cứ thứ gì agent của bạn trả về thì chính giá trị đó được lưu thành một key trong AgenticScope (video đăng ngày 24/10/2026).

Để làm vậy, anh tạo một listener, chỉ cho mục đích debug. Anh nói listener là tuỳ chọn, anh thêm vào chỉ để chứng minh khái niệm AgenticScope đã hiểu trên lý thuyết. Anh tạo một package tên listener, trong đó thêm class DebugAgentListener. Anh gọi đây là một class khá đơn giản dựng sẵn; nhìn các import sẽ thấy nó chỉ để theo dõi (trace) flow của agent. Bạn implement interface AgentListener, đến từ module agentic của LangChain4j; anh chỉ vào import có chữ agentic.scope.AgenticScope (code trong video đăng ngày 24/10/2026).

DebugAgentListener.java implements AgentListener với afterAgenticScopeCreated in SCOPE CREATED, beforeAgentInvocation in AGENT START, afterAgentInvocation in AGENT END
DebugAgentListener trong video đăng ngày 24/10/2026: override afterAgenticScopeCreated, beforeAgentInvocation và afterAgentInvocation, mỗi chỗ in một dòng đánh dấu (SCOPE CREATED, AGENT START, AGENT END) rồi in object tương ứng.

Theo Basanta, listener này cho bạn biết mọi thứ: sau khi AgenticScope được tạo, trước khi agent được gọi và sau khi agent được gọi; trước đó chuyện gì đã xảy ra và sau đó chuyện gì xảy ra; lúc bạn ghi gì đó vào AgenticScope và lúc bạn đọc gì đó từ AgenticScope. Anh nói đó là lý do phía LangChain4j cung cấp listener để trace flow (video đăng ngày 24/10/2026). Phần đầu của class như trên màn hình:

public class DebugAgentListener implements AgentListener {

    @Override
    public void afterAgenticScopeCreated(AgenticScope scope) {
        System.out.println("=== SCOPE CREATED ===");
        System.out.println(scope);
    }

    @Override
    public void beforeAgentInvocation(AgentRequest request) {
        System.out.println("\n=== AGENT START ===");
        System.out.println(request);
    }

    @Override
    public void afterAgentInvocation(AgentResponse response) {
        System.out.println("\n=== AGENT END ===");
        System.out.println(response);
        ...

Rồi anh đăng ký listener vào workflow: thêm .listener(new DebugAgentListener()) trong bean ở mục 11. "Đơn giản chưa?", anh hỏi. Bạn định nghĩa ba agent, rồi nói rõ với agent cha: "workflow support agent, hãy chạy theo đúng cách tôi đưa sub agent cho anh; anh là agent cha, là bộ điều phối của framework LangChain4j; anh nhận trách nhiệm và chạy theo thứ tự này: order agent trước, rồi refund agent, rồi response agent". Đây là final response anh muốn trả về, có thêm một listener để trace flow, cuối cùng build object (demo trong video đăng ngày 24/10/2026).

Trước khi restart app, anh muốn cho xem phần AgenticScope, nên tắt log request và response; nếu không, console sẽ đầy log và khó theo dõi những dòng mà listener in ra. Rồi anh restart ứng dụng (demo trong video đăng ngày 24/10/2026).

13. Nối workflow vào controller và gọi bằng Postman

App đã chạy, nhưng anh dừng lại: chưa gọi agent này từ controller class. Agent đã tạo, nhưng phải truyền input vào và test thì mới được, nên phải dùng agent này trong controller (video đăng ngày 24/10/2026).

Anh mở controller có sẵn, khai báo field private WorkflowSupportAgent workflowSupportAgent và truyền nó vào constructor. Rồi định nghĩa một method public String workflowSupport, truyền input vào và gọi workflow support agent. Thế là xong; anh restart app lần nữa để gọi endpoint này và kiểm tra multi-agent workflow (code trong video đăng ngày 24/10/2026).

AiSupportController.java: ba endpoint chat, /order và /workflow; /workflow gọi workflowSupportAgent.handle(request)
AiSupportController trong video đăng ngày 24/10/2026: bên cạnh endpoint chat và /order của ví dụ cũ, endpoint mới /workflow nhận @RequestParam String request và gọi workflowSupportAgent.handle(request).
@GetMapping("/workflow")
public String workflowSupport(
        @RequestParam String request) {
    return workflowSupportAgent.handle(request);
}

App khởi động xong, anh mở Postman và sửa URL. Anh kiểm lại controller: đường dẫn là /api/ai-support rồi /workflow, và request parameter tên là request, nên trong Postman anh đổi tên tham số thành request. Giá trị anh nhập vẫn là câu "This is my order ORD-1005 Can I get a refund?". Anh nói giờ chạy thử để kiểm tra flow có thật sự chạy đúng như đã bàn không, và LangChain4j có quản lý được nhiều agent trong cùng một ứng dụng không (demo trong video đăng ngày 24/10/2026).

http://localhost:8080/api/ai-support/workflow?request=This is my order ORD-1005 Can I get a refund?

Anh gửi request và nhận về một câu trả lời bắt đầu bằng "Dear" kèm tên khách hàng, cảm ơn khách đã liên hệ về đơn hàng. Để kiểm tra dữ liệu có đúng không, anh mở bảng orders: đơn ORD-1005 thuộc đúng khách hàng đó, đúng người dùng. Anh đọc thấy cột payment status là PAID; trên màn hình, cột status của đơn này là DELAYED, sản phẩm là Dell XPS 14 (demo trong video đăng ngày 24/10/2026).

Bảng orders trong demo có mười đơn mẫu, từ ORD-1001 tới ORD-1010, với các cột như vị trí hiện tại, tên khách, ngày giao dự kiến, mã đơn, trạng thái thanh toán, tên sản phẩm, trạng thái đơn và tổng tiền. Dòng ORD-1005 ghi vị trí Delhi Distribution Center, ngày giao dự kiến 2026-10-08, trạng thái DELAYED và tổng tiền 149,999 (dữ liệu mẫu trên màn hình, video đăng ngày 24/10/2026).

Câu trả lời gọi đúng tên khách, cảm ơn vì đã liên hệ về đơn ORD-1005 cho chiếc Dell XPS 14; vì đơn hàng hiện đang bị trễ (delayed), nó đủ điều kiện hoàn tiền theo policy; để bắt đầu quy trình hoàn tiền, khách hãy xác nhận muốn tiếp tục, và phía cửa hàng sẽ xử lý ngay. Postman báo 200 OK sau 10.23 s (số trên màn hình trong video đăng ngày 24/10/2026). Cuối thư có các chỗ giữ chỗ chưa điền như [Your Name], [Your Position], [Your Company]. Anh nói cuối cùng đã có câu trả lời, và nó đi qua ba agent khác nhau. Anh sẽ kiểm chứng điều đó trước, rồi thử với một order ID khác.

14. Đọc log: AgenticScope lớn dần qua từng agent

Để kiểm chứng, anh vào console và đọc từ đầu. Đầu tiên là dòng scope được tạo. Anh hỏi: tên đó có phải anh tự gõ tay không? Không. Đây là implementation của AgenticScope, có một memory ID, và state đang rỗng (log trong video đăng ngày 24/10/2026).

Console IntelliJ: === SCOPE CREATED === AgenticScope{memoryId=..., state={}}, rồi === AGENT START === hai lần, TOOL CALLED -> getOrderStatus(ORD-1005), câu select của Hibernate, === AGENT END ===
Console trong video đăng ngày 24/10/2026: SCOPE CREATED với state={}, rồi AGENT START, dòng TOOL CALLED gọi getOrderStatus(ORD-1005), câu select của Hibernate vào bảng orders, và AGENT END.

Rồi agent bắt đầu. Lúc đầu AgenticScope chỉ chứa request của bạn: "This is my order, can I get refund or not". Không có gì khác. Request này được giữ (persist) trong AgenticScope, rồi được chuyển cho order agent. Order agent chạy và trả về order details như một key, lưu vào AgenticScope. Anh kiểm tra: agent start, nó gọi order tool, nhận kết quả, rồi agent end (log trong video đăng ngày 24/10/2026).

Ở trạng thái của agent response, anh chỉ ra state chứa request, rồi output: chi tiết đầy đủ của đơn hàng (order ID, tên khách, sản phẩm, trạng thái DELAYED, vị trí hiện tại, ngày giao dự kiến, tổng tiền, trạng thái thanh toán). Đó là toàn bộ order details, kết quả trả về sau khi tool chạy cho đúng câu hỏi đó (log trong video đăng ngày 24/10/2026).

Tiếp theo là agent start của agent kế tiếp. Anh chỉ ra nó chứa request và, "can you see here", kết quả order details. Anh nói log sẽ in những đoạn dài vì lần nào cũng in order details, refund decision, nên cứ bỏ qua phần lặp đó. Rồi nó gọi refund tool; trong lượt refund tool có request và order details. Ở lượt agent sau, anh chỉ ra AgenticScope giờ chứa request, order details (một đoạn rất dài) và refund decision, chính là output key tiếp theo từ refund agent. Agent execution end (log trong video đăng ngày 24/10/2026).

Anh nói vì kết quả của agent này là input của agent kia nên log cứ lặp lại cùng một chuỗi, khó lọc, nhưng anh tìm được: đây là final response, với các key trước đó là refund decision, request và order details. Đó là những gì được đưa cho agent thứ ba, response agent: nó nhận request, order details, refund decision, và cuối cùng ghi lại vào AgenticScope key final response. Anh hỏi người xem đã rõ flow chưa: không chỉ bàn lý thuyết, mà đã chứng minh bằng thực tế. Bạn có thể debug để hiểu thêm (demo trong video đăng ngày 24/10/2026).

scope createdworkflow startsau OrderAgentsau RefundAgentsau ResponseAgent state={} request requestorderDetails requestorderDetailsrefundDecision requestorderDetailsrefundDecisionfinalResponse các key Basanta chỉ ra trong log (video đăng ngày 24/10/2026)
Hình 14.1Những gì AgenticScope chứa ở từng bước, theo các dòng log anh đọc trong video đăng ngày 24/10/2026: mỗi agent thêm đúng một key của mình.

15. Thử một đơn hàng khác và tổng kết

Giờ anh thử với một người dùng khác. Anh tìm trong bảng orders một đơn có trạng thái delayed, rồi xem refund policy cho trạng thái delayed: "Delayed orders are eligible for a refund". Chỉ đơn bị trễ mới được hoàn tiền; không trạng thái nào khác được hoàn tiền (theo bảng refund_policy trong demo, video đăng ngày 24/10/2026). Anh gửi request với order ID đó (trên màn hình là ORD-1010), và nói bạn sẽ thấy agent start, agent end, kết quả nhận được và cách nó được chuyển sang agent kế tiếp qua AgenticScope.

Câu trả lời có tiêu đề "Your Refund Request for Order ORD-1010", gọi tên khách hàng, cảm ơn khách đã liên hệ về đơn hàng chiếc AirPods Pro; vì đơn hiện bị trễ và chưa được giao trong khung thời gian dự kiến nên đủ điều kiện hoàn tiền. Màn hình ghi sẽ sớm xử lý khoản hoàn tiền ₹24,900.00, và khách sẽ nhận email xác nhận khi hoàn tiền xong. Postman báo 200 OK sau 9.09 s (số trên màn hình, demo trong video đăng ngày 24/10/2026).

Trong log của lượt này, phần order details hiện ORD-1010 với sản phẩm AirPods Pro, trạng thái DELAYED, vị trí Bengaluru Distribution Center, ngày giao dự kiến 2026-10-07, tổng tiền ₹24,900.00 và trạng thái thanh toán PAID; ngay sau đó là refund decision ghi đơn này đủ điều kiện hoàn tiền vì bị trễ (log trên màn hình, video đăng ngày 24/10/2026).

Anh gửi lại lần nữa để xem câu trả lời. Lần này câu chữ khác đi: cảm ơn khách đã liên hệ; vì đơn hiện được đánh dấu là delayed nên đủ điều kiện hoàn tiền theo policy; để tiến hành, khách hãy xác nhận yêu cầu và phía cửa hàng sẽ bắt đầu quy trình. Anh kết luận nó chạy đúng như mong đợi (demo trong video đăng ngày 24/10/2026).

OrderAgentorder details từ database RefundAgentrefund policy từ database ResponseAgentbuild final response chạy tuần tự theo thứ tự khai báo trong sequenceBuilder (video đăng ngày 24/10/2026)
Hình 15.1Tổng kết của Basanta trong video đăng ngày 24/10/2026: ba agent cùng làm ra một câu trả lời, chạy lần lượt theo thứ tự đã định nghĩa.

Anh tổng kết: đã tạo bao nhiêu agent? Ba agent, và để build ra câu trả lời này thì thực sự có ba agent cùng làm việc. Order agent lấy order details từ database qua order tools; refund agent lấy thông tin hoàn tiền, tức refund policy, từ database; response agent nhận output order details và refund decision rồi build câu trả lời cuối. Cả ba agent chạy tuần tự (sequentially), đúng theo thứ tự thực thi đã định nghĩa (demo trong video đăng ngày 24/10/2026).

Cuối video đăng ngày 24/10/2026, anh hy vọng bài này cho người xem một ý niệm rõ ràng về cách hệ thống multi-agent chạy trong Java với LangChain4j, và chào tạm biệt: "keep learning, keep coding".

Nguồn và liên kết 7 nguồn

  1. Tên gốc: 🚀 Build Multi-Agent AI in Java with LangChain4j | Agentic AI Explained
  2. Sự kiện: YouTube
  3. Video gốc: https://www.youtube.com/watch?v=v3hiMm6aGi8 youtube.com
  4. Kênh YouTube Java Techie (Basanta Kumar Hota): youtube.com/channel/UCORuRdpN2QTCKnsuEaeK-kQ youtube.com
  5. Repo code của video, gồm OrderAgent, RefundAgent, ResponseAgent, WorkflowSupportAgent, AgentConfig và DebugAgentListener (đọc ngày 25/10/2026): github.com/Java-Techie-jt/multi-ai-agent github.com
  6. Tài liệu LangChain4j, mục Agents and Agentic AI: trang mô tả module langchain4j-agentic, AgenticScope là tập dữ liệu dùng chung giữa các agent, cùng agentBuilder, sequenceBuilder và outputKey; trang ghi toàn bộ module này vẫn được coi là experimental và có thể thay đổi ở các bản sau (đọc ngày 25/10/2026): docs.langchain4j.dev/tutorials/agents docs.langchain4j.dev
  7. Repo LangChain4j: github.com/langchain4j/langchain4j github.com