Bỏ qua để đến nội dung

Graph engineering là gì và vì sao nó đang định hình lại cách xây dựng agent

Sketchnote tiêu đề về graph engineering, minh hoạ một mạng lưới các node được gắn nhãn nối với nhau bằng mũi tên

Ngày 18/7/2026, Peter Steinberger đăng một dòng tweet 12 từ: “Are we still talking loops or did we shift to graphs yet?” (Chúng ta còn nói về loop hay đã chuyển sang graph rồi?). Trong vòng 48 giờ, cụm từ “graph engineering” có ba định nghĩa cạnh tranh nhau, một làn sóng bài viết ăn theo, và cả một “nghiên cứu trị giá 3,1 triệu USD của Stanford và Anthropic” được trích dẫn khắp nơi - mà không hề tồn tại.

Ba định nghĩa đó là gì? The AI Operator liệt kê cụ thể:

  1. Orchestration graphs - thiết kế hệ thống đa-agent như một graph tường minh thay vì một loop, với node và transition có kiểu, có checkpoint. Đây là địa hạt của LangGraph và Temporal.
  2. Graph của loop (graphs of loops) - mạng lưới các chu trình tự cải thiện (self-improvement cycle) theo dõi lẫn nhau. Đây là cách hiểu trừu tượng nhất trong ba cách, và cũng ít khả thi để áp dụng ngay nhất.
  3. Graph-structured knowledge và memory - tri thức của agent được lưu dưới dạng node và edge có kiểu mà agent có thể traverse (duyệt qua) để truy xuất thông tin, thay vì tìm kiếm tương đồng trên văn bản phẳng. Đây là nhánh GraphRAG và bộ nhớ agent (Graphiti, Zep…).

Sketchnote một thuật ngữ ba nghĩa: orchestration graph là một chuỗi hộp nối tiếp, graph của loop là các vòng lặp lồng nhau, graph tri thức là một mạng lưới node - chỉ nhánh tri thức có benchmark

Chỉ có định nghĩa thứ ba là có cả một thập kỷ nghiên cứu, công cụ hoạt động thực tế, và số liệu benchmark sống sót qua đánh giá độc lập đứng sau nó. Hai định nghĩa đầu phần lớn là phần ồn ào của làn sóng buzzword tuần đó - dù bản thân LangGraph (định nghĩa 1) là một framework có thật đã tồn tại ba năm trước khi cái tên “graph engineering” xuất hiện.

Bỏ qua phần ồn ào đó, có một câu hỏi kỹ thuật thật sự đáng trả lời: khi nào bạn nên mô tả một hệ thống agent như một graph (đồ thị) thay vì một chuỗi bước tuyến tính hay một vòng lặp, và làm điều đó thực sự nghĩa là gì ở tầng code? Bài viết này tổng hợp ba góc nhìn kỹ thuật đang lưu hành: LangChain (đội đứng sau LangGraph, framework đã âm thầm làm việc này ba năm - ứng với định nghĩa 1), tác giả 0xMovez với một roadmap thực hành 14 bước dùng Claude Code (cũng thuộc nhánh orchestration graph), và The AI Operator với một khảo sát sâu về nhánh graph-structured knowledge/memory (định nghĩa 3, GraphRAG).

Bóc hết các từ thời thượng ra, một graph chỉ có hai thành phần.

Sketchnote hai nguyên tố của một graph: các vòng tròn được gắn nhãn person, project, decision là node, các mũi tên nối chúng lại là edge

Node là một đơn vị công việc hoặc một thứ bạn biết - có thể là code xác định (deterministic), một lệnh gọi LLM đơn lẻ, một tool call, hoặc cả một agent với vòng lặp nội bộ riêng của nó. Trong ngữ cảnh tri thức, node là một thực thể: một người, một quyết định, một sự cố.

Edge là một mối phụ thuộc - nó nói rằng “output của node này chảy vào input của node kia.” Không hơn không kém. Sai lầm phổ biến nhất là coi “rồi sau đó làm…” là một edge. “Tóm tắt file này, rồi cho tôi biết thời tiết hôm nay” không có edge nào giữa hai việc đó - thời tiết không tiêu thụ bản tóm tắt. Đó là hai node độc lập mà một kịch bản tuyến tính lại nối chúng lại một cách không cần thiết.

Cách kiểm tra: với mỗi “rồi sau đó” trong agent của bạn, hãy hỏi bước tiếp theo có đọc output của bước trước không? Nếu không, không có edge, và việc chờ đợi là lãng phí. Vẽ node như một hình hộp (một lệnh gọi agent()), edge như một mũi tên (một biến được truyền từ return của lệnh gọi này vào prompt của lệnh gọi khác). Nếu không vẽ được mũi tên - không có biến nào đi qua - hai hộp đó độc lập, và tính độc lập chính là thứ bạn sẽ khai thác.

Khi bạn viết một agent kiểu “làm A, rồi B, rồi C, rồi D,” bạn đã vẽ ra một graph - chỉ là một chuỗi không phân nhánh. Mọi node có đúng một edge vào và một edge ra. Nó chạy đúng, nhưng chậm và mong manh (fragile), vì một chuỗi không có dự phòng: nếu C bị kẹt, D không bao giờ chạy, và công sức của A bị mắc kẹt phía trên mà không đi đâu được. Đây chính là điểm khởi đầu của graph engineering: vẽ lại chuỗi tuyến tính đó, cắt các mũi tên không thực sự mang dữ liệu, để chuỗi “sụp” thành một hình dạng rộng hơn - vài node độc lập có thể chạy cùng lúc, đổ vào một node cần tất cả chúng.

LangChain, đội đã xây LangGraph từ ba năm trước (hiện được tải xuống hơn 65 triệu lượt/tháng), mô tả trực tiếp: trong LangGraph, node làm việc, edge định nghĩa chuyện gì xảy ra tiếp theo. Một số edge là xác định, số khác có điều kiện, dựa trên kết quả của node, trạng thái hiện tại, hoặc một tín hiệu bên ngoài. Bạn có thể coi đây là một state machine - graph định nghĩa workflow, trạng thái di chuyển xuyên qua nó, và các phép chuyển đổi giữa các bước.

Điểm quan trọng: đại đa số workflow agent trong thực tế đều có cấu trúc dự đoán được. Một agent hỗ trợ khách hàng phân loại vấn đề trước khi trả lời hoặc chuyển tiếp. Một coding agent kiểm tra repo trước khi đề xuất thay đổi. Một quy trình tuân thủ (compliance) yêu cầu phê duyệt trước khi thực hiện một hành động bên ngoài. Graph cho phép bạn mã hoá trực tiếp cấu trúc đó: đường đi hợp lệ, chỗ nào model được quyền chọn, chỗ nào hệ thống nên ép buộc hành vi xác định thay vì hy vọng model luôn chọn đúng. Nói cách khác, biểu diễn hệ thống dưới dạng graph chính là mã hoá tri thức về miền (domain knowledge) của bạn vào kiến trúc - giống hệt cách một prompt tốt chứa domain knowledge để phân biệt agent của bạn với một ChatGPT chung chung.

Ví dụ: một agent tra cứu tri thức dùng ba subagent - một GitHub agent cho code/issue/PR, một Notion agent cho tài liệu nội bộ, một Slack agent cho các thread liên quan. Workflow có ba giai đoạn cố định: phân loại, tìm kiếm, tổng hợp.

Sơ đồ workflow phân loại, tìm kiếm song song qua ba subagent (GitHub, Notion, Slack), rồi tổng hợp thành một câu trả lời

Kết quả là code và lý luận của model làm việc cùng nhau: model lý luận ở chỗ nó tạo ra giá trị, code xử lý phần còn lại, và agent trở nên rẻ hơn, nhanh hơn, dự đoán được hơn.

Ngược lại, không phải mọi tác vụ nên bị ép vào một graph cố định. Deep research tổng quát là ví dụ điển hình: một research agent cần lập kế hoạch, phân công, tìm kiếm, đọc, tổng hợp theo những cách rất khó định trước. LangChain kể lại rằng họ từng xây deep research đời đầu trên các workflow LangGraph định sẵn, rồi chuyển sang một core loop mang tính agentic hơn (Deep Agents). GPT Researcher, một implementation deep research phổ biến, cũng thực hiện đúng bước chuyển này: từ pipeline đa-agent hình graph sang Deep Agents, để việc lập kế hoạch, phân công và quản lý context nảy sinh (emerge) trong harness thay vì bị hard-code cứng trong graph.

Graph của agent trong thực tế hầu như không phải DAG (directed acyclic graph). Agent production cần chu trình (cycle): retry tool call thất bại, hỏi lại người dùng khi thiếu thông tin, sửa câu trả lời sau khi validate, gọi tool lặp đi lặp lại đến khi đủ context, tạm dừng chờ input của người rồi tiếp tục. Looping là phần cốt lõi của hệ thống agentic, nên chúng gần như chắc chắn không phải DAG thuần túy.

Loop chỉ là một dạng graph đơn giản. “Loop engineering” không phải một hướng đi thay thế cho graph, mà là một phiên bản đơn giản của nó. Như David Khourshid từng nói: một loop chỉ là một directed, cyclic graph. Thực tế, chính framework LangChain (dựa trên một agentic loop đơn giản) được xây trên nền LangGraph.

Chuyển đổi động (dynamic transitions) rất quan trọng. Bạn không phải lúc nào cũng định nghĩa trước mọi edge. Đôi khi một node quyết định tại runtime cần tạo ra bao nhiêu công việc. Map-reduce là ví dụ kinh điển: chia input thành các phần, gửi từng phần cho một worker, rồi gộp kết quả - số lượng worker phụ thuộc vào input và bạn không biết trước con số đó. LangGraph xử lý việc này bằng cơ chế Send, cho phép một node định tuyến công việc đến một hoặc nhiều node phía sau một cách động, mà không cần khai báo tĩnh mọi phép chuyển đổi. Điều này quan trọng vì các hệ thống agent hữu ích thực sự trộn lẫn cấu trúc đã biết với sự biến thiên tại runtime: bạn có thể biết bước research nên phân nhánh rồi tổng hợp, nhưng không biết trước sẽ có bao nhiêu nguồn; bạn có thể biết một supervisor nên phân công cho worker, nhưng chưa biết cụ thể worker nào cho đến khi tác vụ bắt đầu.

Bản thân việc biểu diễn hệ thống agentic như một graph không mới - LangChain đã làm việc này ba năm. Điều thay đổi là thứ bạn có thể đặt bên trong một node. Trước đây, node là code xác định hoặc một lệnh gọi LLM đơn lẻ. Giờ đây, khi bản thân agent đã đủ tin cậy để giao việc thật, một node có thể là cả một lượt chạy agent hoàn chỉnh - bạn đang điều phối các agent, không chỉ các lệnh gọi LLM. Coding agent là ví dụ rõ nhất: chúng nằm trong số các agent hiệu quả và có tác động lớn nhất hiện nay trong production, và việc nhúng một coding agent làm node bên trong một graph lớn hơn là một pattern mới khả thi.

Lấy ví dụ một docs agent biến một yêu cầu trên Slack thành một pull request sẵn sàng review:

Sơ đồ graph docs agent: từ yêu cầu Slack qua bước phân loại, các agent viết tài liệu tham chiếu và tài liệu khái niệm, đến bước tổng hợp ra một pull request sẵn sàng review

Mỗi node trong graph này nằm ở một điểm khác nhau trên thang xác định-đến-agentic. Các bước cố định (thao tác Slack, Linear) chạy bằng code và API call cứng. Các bước mô hình (classifier, bước tổng hợp) dùng một lệnh gọi LLM đơn lẻ không có tool. Các bước agent (agent viết tài liệu tham chiếu, agent viết tài liệu khái niệm) hoàn thành công việc mở hơn nhiều trong codebase liên quan. Chính sự pha trộn giữa tính xác định và tính agentic này khiến docs agent vừa dự đoán được, vừa mạnh mẽ, vừa hiệu quả.

14 kỹ thuật thực hành để xây orchestration graph

Phần tiêu đề “14 kỹ thuật thực hành để xây orchestration graph”

Nếu phần trên là lý thuyết, thì đây là phần thực hành: 0xMovez tổng hợp một bộ pattern cụ thể để xây graph agent bằng Claude Code dynamic workflows - nơi Claude viết một script điều phối JavaScript thuần, rồi triển khai một đội subagent phối hợp để thực thi nó, và bản thân việc điều phối không tốn token model nào vì nó là code, không phải hội thoại.

Một node bạn không thể suy luận về nó là một node bạn không thể chạy song song được. Cách khắc phục là cho mỗi node một contract: input giới hạn, output giới hạn, đúng một việc. Input là bất cứ thứ gì node đọc - được truyền vào tường minh, không bao giờ giả định từ một context window dùng chung. Output là một shape (cấu trúc dữ liệu) xác định, lý tưởng là được validate, để node tiếp theo tiêu thụ nó mà không cần đoán.

Trong một workflow, contract này được ép buộc bằng schema. Khi bạn đưa cho Claude một lệnh gọi agent() kèm JSON schema, subagent mà Claude triển khai buộc phải trả về dữ liệu structured đã validate - việc validate diễn ra ở tầng tool-call, nên Claude retry khi không khớp thay vì trả về free text bạn phải tự parse và cầu nguyện.

// Một node có contract thật: input giới hạn, output validate, đúng một việc.
const ITEM = {
type: 'object', additionalProperties: false,
properties: {
title: { type: 'string' },
url: { type: 'string' },
impact: { type: 'string', enum: ['high', 'medium', 'low'] },
},
required: ['title', 'url', 'impact'],
};
const result = await agent(source.prompt, {
label: `research:${source.key}`,
schema: ITEM, // ép buộc output structured đã validate
agentType: 'general-purpose',
});

Edge cũng vậy - nó không chỉ là “B chạy sau A.” Đó là một lời hứa về những gì đi qua nó: A tạo ra shape này, B được xây để tiêu thụ shape này. Khi bạn đặt tên cho edge theo dữ liệu của nó - không phải theo thứ tự - hai việc trở nên dễ dàng: bạn thấy ngay edge có thật hay không (dữ liệu có thực sự di chuyển không?), và bạn có thể tráo đổi node ở hai đầu mà không phá vỡ graph, miễn shape còn giữ nguyên.

Trong thực tế, edge sống trong plain JavaScript. Bước reduce giữa fan-out và synthesis - làm phẳng (flatten), khử trùng lặp (dedupe), lọc - chỉ là code thao tác trên các shape mà node của bạn trả về. Không cần agent. Đây là một trong những khoản lợi âm thầm nhất của tư duy graph: rất nhiều thứ người ta đốt token model để làm thực ra là một edge, và edge thì miễn phí. Sự cám dỗ là triển khai một agent để “gộp kết quả” - hãy cưỡng lại. Nếu gộp có nghĩa là làm phẳng và khử trùng lặp, đó là results.flatMap(...) và một Set - xác định, tức thời, không tốn token. Để dành agent cho phán đoán (judgment), không phải cho việc ống nước (plumbing).

Đây là nước đi trả về nhiều nhất. Khi bạn có N node độc lập - N nguồn cần kiểm tra, N file cần review, N tuyến (route) cần audit - bạn không xích chúng lại. Bạn bảo Claude phân tán (fan out) chúng ra và chạy đồng thời. Trong một workflow, đó là parallel(): Claude nhận một mảng thunk và triển khai một subagent cho mỗi thunk, tất cả thực thi đồng thời, rồi trả về mảng kết quả.

phase('Research');
// Chín nguồn, chín agent, chạy cùng lúc.
const raw = await parallel(
SOURCES.map((s) => () =>
agent(s.prompt, {
label: `research:${s.key}`,
phase: 'Research',
schema: ITEM_SCHEMA, // mỗi node trả về JSON đã validate
agentType: 'general-purpose',
}),
),
);
const collected = raw.filter(Boolean); // loại bỏ null từ agent thất bại

Có hai chi tiết khiến parallel() trở nên vững chắc. Thứ nhất, nó là một barrier (rào chắn) - chờ mọi thunk hoàn tất trước khi trả về, để giai đoạn tiếp theo thấy được tập kết quả đầy đủ. Thứ hai, một thunk ném lỗi sẽ resolve thành null thay vì làm reject cả batch, nên một agent không ổn định không thể nhấn chìm cả lượt chạy - luôn nhớ .filter(Boolean) kết quả. Độ đồng thời bị giới hạn quanh số core, phần dư sẽ xếp hàng, nên bạn có thể truyền vào hàng trăm thunk và chúng vẫn hoàn tất - chỉ là từng nhóm nhỏ một lúc. Context của Claude không bao giờ chứa chín nguồn cùng lúc - mỗi subagent mang context riêng của nó, và chỉ câu trả lời cuối cùng quay về. Đó là điều cho phép Claude mở rộng một workflow tới hàng chục hoặc hàng trăm subagent mà không nhấn chìm session.

Fan-out chỉ có ích nếu có gì đó thu gom nó lại. Fan-in là node nơi các edge hội tụ - nơi một agent (hoặc một đoạn code) thấy toàn bộ kết quả từ phía trên cùng lúc và làm điều gì đó cần cả tập hợp: khử trùng lặp xuyên nguồn, xếp hạng theo mức độ ảnh hưởng, thoát sớm nếu tổng kết quả rỗng. Đây là nơi duy nhất chi phí wall-clock của một barrier đáng giá. Quy tắc giữ cho graph nhanh: chỉ dùng barrier khi một giai đoạn thực sự cần toàn bộ kết quả trước đó cùng lúc. Chỉ làm phẳng một danh sách? Đó là edge, làm inline. Phép thử đơn giản và tàn nhẫn: nếu bạn viết parallel → transform → parallel mà bước transform ở giữa không có phụ thuộc chéo giữa các item, đáng lẽ bạn nên dùng pipeline và bỏ hẳn barrier.

Ghép fan-out và fan-in lại, bạn có topology chủ lực của mọi graph agent nghiêm túc: hình thoi (diamond). Một node chia việc, nhiều node làm việc song song, một node gộp lại. Đây là hình dạng đứng sau một market scan, một dependency audit, một code review, một báo cáo research - đổi nguồn và prompt, cùng một khung xương thích ứng. Tên gọi đáng nhớ: fan out → reduce → synthesize. Phân tán để thu thập độ rộng, reduce bằng code thuần để nén lại, synthesize bằng một agent cuối để viết câu trả lời.

Không phải graph nào cũng cố định. Đôi khi edge cần đi phụ thuộc vào những gì một node tìm thấy. Một router node kiểm tra kết quả và quyết định đường nào phía sau sẽ chạy - phân loại ticket rồi rẽ nhánh đến handler đúng; kiểm tra kích thước diff rồi hoặc review nhanh hoặc dựng một audit đầy đủ. Trong một workflow, đây chỉ là một if/switch JavaScript trên output đã validate của một node, vì luồng điều khiển sống trong code.

// Router node: agent phân loại, code chọn edge.
const { severity } = await agent(
`Classify this diff's risk:\n${diff}`,
{ schema: { type: 'object',
properties: { severity: { enum: ['low', 'high'] } },
required: ['severity'] } },
);
let review;
if (severity === 'high') {
review = await parallel(FILES.map((f) => () => agent(`Audit ${f}`)));
} else {
review = await agent(`Quick review of ${diff}`);
}

Đây là chỗ tính xác định trở thành một tính năng, không phải giới hạn. Quyết định của router có thể do Claude thực hiện (một subagent phân loại), nhưng việc định tuyến là code do Claude viết - nên nó chạy giống hệt nhau mỗi lần với cùng một kết quả phân loại. Bạn có phán đoán của Claude tại node, và độ tin cậy của một kịch bản tại edge.

Đòn bẩy thực sự của một graph không phải là nhiều agent hơn - mà là cấu trúc bạn có thể quấn quanh chúng để tạo ra sự tin cậy. Một verifier node nằm trên edge trước khi một kết quả được phép đi tiếp, và việc duy nhất nó làm là cố gắng đánh bại phát hiện đó. Nếu nó sống sót, nó qua. Nếu không, nó không bao giờ đến được câu trả lời cuối. Ba pattern đáng có trong tay: adversarial verify (với mỗi phát hiện, triển khai N “kẻ hoài nghi” độc lập được prompt để bác bỏ nó; chỉ giữ lại nếu đa số sống sót); perspective-diverse verify (mỗi verifier có một lăng kính khác nhau - tính đúng đắn, bảo mật, có tái tạo được không - vì sự đa dạng bắt được các failure mode mà N lần kiểm tra giống hệt nhau không bao giờ bắt được); judge panel (tạo N nỗ lực từ các góc độ khác nhau, chấm điểm bằng các judge song song, tổng hợp từ người thắng trong khi ghép thêm phần tốt nhất của các ứng viên còn lại).

Trong một chuỗi, lỗi lan tỏa - C chết, D không bao giờ chạy, cả hệ thống dừng lại. Trong một graph, lỗi nên bị cô lập trong node của nó. Điều này đã đúng một phần: một thunk ném lỗi bên trong parallel() resolve thành null, nên tám agent tốt vẫn trả về trong khi một agent tệ bị loại - .filter(Boolean) chính là sự cô lập đó. Hãy thiết kế mọi fan-in để chịu được input thiếu thay vì giả định một tập đầy đủ. Thất bại tinh vi hơn là các node giẫm chân lên nhau. Khi agent ghi file song song, chúng có thể va chạm - cách khắc phục là cô lập bằng “worktree”: mỗi agent chạy trong git worktree riêng, làm việc trong sandbox, rồi merge sạch sẽ. Chỉ dùng khi node thực sự ghi song song - đó là dây an toàn cho đúng một topology cần nó, không phải một khoản thuế mặc định cho mọi lượt chạy.

Chu trình có kiểm soát và phân tầng model

Phần tiêu đề “Chu trình có kiểm soát và phân tầng model”

Đôi khi bạn không biết trước quy mô công việc: khám phá kích thước chưa biết, một đợt quét bug mà tìm ra một bug lại lộ ra ba bug khác. Việc này cần một chu trình (cycle) - một edge có kiểm soát quay lại một node trước đó. Nguy hiểm rõ ràng: một chu trình không hội tụ là một vòng lặp vô hạn triển khai agent đến khi ngân sách cạn kiệt.

Pattern hội tụ được gọi là loop-until-dry: tiếp tục triển khai “finder” đến khi K vòng liên tiếp không tìm ra gì mới, rồi dừng. Chi tiết quyết định thành bại - và sai lầm gần như ai cũng mắc lần đầu - là bạn khử trùng lặp so với cái gì. Hãy khử trùng lặp so với mọi thứ đã thấy, không chỉ so với kết quả đã xác nhận. Nếu không, các phát hiện bị bác bỏ sẽ xuất hiện lại mỗi vòng, vòng lặp không bao giờ “khô,” và bạn đã xây một cỗ máy trả tiền để tái khám phá cùng những ngõ cụt mãi mãi.

const seen = new Set(); const confirmed = []; let dry = 0;
while (dry < 2) { // dừng sau 2 vòng rỗng
const found = (await parallel(
FINDERS.map((f) => () => agent(f.prompt, { schema: BUGS }))
)).filter(Boolean).flatMap((r) => r.bugs);
const fresh = found.filter((b) => !seen.has(key(b)));
if (!fresh.length) { dry++; continue; }
dry = 0;
fresh.forEach((b) => seen.add(key(b))); // khử trùng lặp so với SEEN, không phải confirmed
const judged = await parallel(fresh.map((b) => () =>
parallel(['correctness', 'security', 'repro'].map((lens) => () =>
agent(`Judge "${b.desc}" via ${lens} — real?`, { schema: VERDICT })))
.then((v) => ({ b, real: v.filter(Boolean).filter((x) => x.real).length >= 2 }))));
confirmed.push(...judged.filter((v) => v.real).map((v) => v.b));
}

Không phải node nào cũng cần model tốt nhất. Một graph khiến điều này hiển nhiên theo cách một agent đơn lẻ không bao giờ làm được: một số node giới hạn và lặp lại (trích xuất trường dữ liệu này, phân loại ticket này), một số node mang phán đoán thật sự (tổng hợp báo cáo, phân xử phát hiện). Chạy các node “nhàm chán” trên model rẻ hơn, dành token đắt cho nơi phán đoán thực sự nằm ở đó. Mỗi subagent Claude triển khai trong một workflow kế thừa model của session hiện tại trừ khi script ghi đè - nên mặc định, một lượt chạy lớn sẽ tính phí toàn bộ theo tier session của bạn. Tuỳ chọn model trên một lệnh gọi agent() đơn lẻ cho phép định tuyến riêng node đó sang nơi khác.

Hình dạng của graph không chỉ mang tính thẩm mỹ - nó là đòn bẩy lớn nhất lên thời gian thực tế (wall-clock). Lựa chọn hay khiến người ta vấp: parallel() so với pipeline(). Một barrier parallel() khiến mọi thứ chờ node chậm nhất trước khi giai đoạn tiếp theo bắt đầu. Một pipeline() chảy từng item qua mọi giai đoạn độc lập, không có barrier - item A có thể ở giai đoạn 3 trong khi item B vẫn ở giai đoạn 1. Item nhanh hoàn tất sớm thay vì đứng chờ phía sau item chậm.

Mặc định dùng pipeline(). Chỉ dùng barrier khi một giai đoạn thực sự cần toàn bộ kết quả trước đó cùng lúc - một lượt khử trùng lặp xuyên tập hợp, một điểm thoát sớm dựa trên tổng số, một prompt so sánh với “các phát hiện khác.” “Code sạch hơn” và “các giai đoạn có cảm giác tách biệt” không phải là lý do - độ trễ của barrier là thời gian thật, đo được, và lãng phí.

Nước đi cuối cùng là ngừng tự tay vẽ graph cho những việc bạn không thể lên kế hoạch trước. Với dynamic workflows, bạn mô tả mục tiêu và Claude tự viết script điều phối - phân rã tác vụ, chọn cách fan-out, triển khai một đội subagent phối hợp, và tổng hợp kết quả. Bạn nhận được một graph vừa khít với lượt chạy này, thay vì một graph cố định mà bạn hy vọng sẽ vừa.

Có ba cách vào: nói từ “workflow” trong prompt và Claude sẽ viết một cái cho tác vụ đó; chạy một workflow đã lưu hoặc có sẵn (/deep-research là một graph thật đang chạy trong production: scope → tìm kiếm song song → fetch → verify đối kháng → tổng hợp - đúng khung xương của các pattern ở trên); hoặc bật ultracode để Claude lên kế hoạch một workflow cho mọi tác vụ đáng kể trong session. Khi một lượt chạy tốt, nhấn s để lưu script của nó vào .claude/workflows/ - có version control, chạy lại được theo tên, một graph mà bất kỳ ai clone repo cũng có thể khởi chạy.

Nhánh thứ hai: graph như bộ nhớ và tri thức

Phần tiêu đề “Nhánh thứ hai: graph như bộ nhớ và tri thức”

Song song với orchestration graph là một nhánh hoàn toàn khác của “graph engineering”: dùng graph để lưu trữ và truy xuất tri thức, thay vì để điều phối agent. The AI Operator gọi đây là nhánh duy nhất trong ba cách hiểu về “graph engineering” (orchestration graph, graph-của-loop, và graph-structured knowledge/memory) thực sự có một thập kỷ nghiên cứu, công cụ hoạt động, và số liệu benchmark sống sót qua đánh giá độc lập phía sau nó.

Sketchnote dòng thời gian các buzzword AI nối tiếp nhau: prompt engineering năm 2023, context engineering giữa 2025, loop engineering tháng 6/2026, graph engineering tháng 7/2026

Thuật ngữ này nằm trên một “băng chuyền” các cái tên. Mỗi cái tên mô tả một sự dịch chuyển có thật, rồi bị biến thành nội dung ăn theo chỉ trong vài tuần: 2023 là prompt engineering (chăm chút câu chữ gửi cho model), giữa 2025 là context engineering (chọn lọc mọi thứ trong context window), tháng 6/2026 là loop engineering (thiết kế chu trình hành động-quan sát-thử lại của agent), và tháng 7/2026 là graph engineering (thiết kế những gì xảy ra giữa và xuyên suốt các loop đó). Bản thân cái tên “graph engineering” xuất hiện sớm nhất trong một bài blog lặng lẽ của Josh Simmons ngày 4/7, trước khi tweet của Steinberger châm ngòi làn sóng lan truyền hai tuần sau.

Một agent trả lời câu hỏi phải tìm đúng tri thức trước, và chỉ có ba cách để tìm: keyword search tìm ghi chú chứa một từ (thất bại khi câu trả lời dùng từ khác); vector search tìm ghi chú nói về những thứ tương tự (thất bại khi câu trả lời rải rác trên các ghi chú riêng lẻ không giống câu hỏi); graph traversal bắt đầu từ một node và đi theo các kết nối - đây là cách duy nhất có thể theo một chuỗi lý luận.

Ví dụ kinh điển: “Vì sao chúng ta bỏ Redis cho job queue?” Vector search embed câu hỏi và kéo về mười đoạn giống nó nhất - bạn nhận mười ghi chú nhắc đến Redis, nhưng không ghi chú nào giải thích quyết định, vì quyết định nằm trong cấu trúc: một bản ghi quyết định (decision record), thứ nó thay thế, và sự cố đã kích hoạt nó - ba ghi chú riêng biệt. Similarity search không có khái niệm “ba thứ này thuộc về một chuỗi nhân quả.” Graph traversal đi qua nó theo từng “hop”:

hop 1: [[Job queue]] --decided_by--> [[ADR-007 Postgres queue]]
hop 2: [[ADR-007]] --supersedes--> [[ADR-003 Redis queue]]
hop 3: [[ADR-003]] --caused--> [[Incident 2026-03-11]]

Sơ đồ traversal 3 hop: từ Job queue đến ADR-007 đến ADR-003 đến sự cố đã kích hoạt quyết định thay Redis

Ba ghi chú, khoảng 1.000 token, chuỗi nhân quả còn nguyên vẹn. Câu hỏi cần nhiều hơn một hop được gọi là câu hỏi multi-hop, và đó là nơi retrieval phẳng (flat retrieval) liên tục thất bại. Câu ngắn gọn nhất để nhớ: vector search tìm những thứ nghe giống câu hỏi của bạn. Graph tìm những thứ kết nối với câu trả lời của bạn.

Edge có kiểu (typed edge) là thứ quyết định giá trị

Phần tiêu đề “Edge có kiểu (typed edge) là thứ quyết định giá trị”

Sketchnote so sánh edge không có kiểu chỉ nói related với các edge có kiểu được gắn nhãn supersedes, depends_on, decided_by, caused

Chi tiết quyết định một graph có hữu ích hay chỉ đẹp mắt: một edge không có kiểu chỉ nói “hai ghi chú này liên quan” - một bit thông tin. Một edge có kiểu (typed edge) nói bằng cách nào: supersedes (thay thế), depends_on (phụ thuộc vào), decided_by (được quyết định bởi), caused (gây ra). Chạy lại ví dụ Redis mà không có kiểu: chuỗi traversal trở thành “queue liên quan đến một quyết định, quyết định đó liên quan đến một quyết định khác, quyết định đó liên quan đến một sự cố.” ADR-007 có thay thế ADR-003 hay ngược lại? Sự cố gây ra quyết định hay quyết định gây ra sự cố? Chuỗi vẫn còn, nhưng ý nghĩa đã mất, và agent phải đọc lại từng ghi chú rồi đoán. Edge có kiểu biến một đống liên kết thành thứ bạn có thể suy luận trên đó.

GraphRAG là tên gọi chung cho retrieval dựa trên graph. Bốn hệ thống đáng chú ý, mỗi cái dạy một bài học.

Sketchnote pipeline GraphRAG 5 bước: chia nhỏ tài liệu, trích xuất thực thể, gom cụm cộng đồng, tóm tắt mỗi cụm, trả lời truy vấn

Microsoft GraphRAG (2024) là kiến trúc tham chiếu: một LLM đọc từng đoạn văn bản và trích xuất thực thể cùng quan hệ; community detection gom các thực thể liên quan thành từng cụm; một LLM viết báo cáo tóm tắt cho mỗi cụm; tại thời điểm truy vấn, câu hỏi rộng thì map-reduce trên các báo cáo, câu hỏi cụ thể thì mở rộng từ một thực thể khớp. Nó hoạt động, nhưng cực kỳ tốn kém - một lệnh gọi LLM cho mỗi đoạn tại thời điểm indexing. Một ước tính được trích dẫn rộng rãi đưa ra con số khoảng 33.000 USD để index một bộ dữ liệu doanh nghiệp lớn. Bài học một: phiên bản “ngây thơ” quá đắt, và các edge dạng free-text của nó không kết hợp (compose) được với nhau.

LazyGraphRAG (bản sửa của chính Microsoft) đảo ngược thiết kế: chỉ xây một graph cấu trúc rẻ tiền tại thời điểm indexing, và để phần suy nghĩ đắt đỏ diễn ra tại thời điểm truy vấn. Chi phí indexing giảm xuống còn khoảng 0,1% so với bản gốc, chất lượng câu trả lời vẫn tương đương.

Sketchnote bài học LazyGraphRAG: graph cấu trúc rẻ tiền tại thời điểm indexing, phần suy nghĩ đắt đỏ dời sang thời điểm truy vấn, chi phí indexing giảm còn 0,1%

Bài học hai, và hãy nhớ điều này: bạn không cần tính trước ý nghĩa của graph. Một graph cấu trúc rẻ tiền cộng với traversal thông minh tại thời điểm truy vấn lấy được phần lớn giá trị.

HippoRAG 2 (2025) là “vua benchmark.” Nó hợp nhất cấu trúc graph với embedding và xếp hạng kết quả bằng cách lan truyền độ quan trọng dọc theo các edge từ điểm mà truy vấn chạm vào graph. Nó tốn khoảng 1.000 token mỗi truy vấn, thắng ở multi-hop, và quan trọng là không bị tệ đi ở câu hỏi đơn giản để đổi lấy việc tốt hơn ở câu hỏi khó - phần lớn hệ thống graph khác đều mắc lỗi này. Bài học ba: retrieval thắng cuộc luôn là hybrid, không bao giờ graph-only.

Xu hướng chung năm 2026 trên cả lĩnh vực: indexing “lười” (lazy), traversal mang tính agentic (agent tự quyết định đi hop nào, trực tiếp tại runtime), từ vựng edge nhỏ và có kiểm soát, và định tuyến trung thực - chỉ dùng graph cho câu hỏi thực sự cần nó.

GraphRAG trả lời câu hỏi về một kho ngữ liệu (corpus). Bộ nhớ agent khó hơn: tri thức thay đổi ngay trong lúc agent đang dùng nó. Bạn đổi việc. Một quyết định bị đảo ngược. Một sự thật đúng vào tháng 3 lại sai vào tháng 7. Đây là nơi graph làm được điều mà vector store về mặt cấu trúc không thể.

Graphiti, engine mã nguồn mở đứng sau Zep, theo dõi hai trục thời gian cho mỗi edge: khi nào sự thật đó đúng trong thế giới thực, và khi nào hệ thống biết đến nó. Khi thông tin mới mâu thuẫn với một edge cũ, edge cũ không bị xoá - khoảng thời gian hiệu lực của nó đóng lại, và edge mới tiếp quản. Agent giờ có thể trả lời cả “cô ấy làm việc ở đâu?” lẫn “năm 2024 cô ấy làm việc ở đâu?” từ cùng một graph. Sự thật bị thay thế (superseded), không bị ghi đè. Một vector store chỉ có thể ghi đè hoặc nhân bản; nó không có khái niệm gốc về “điều này đúng cho đến tháng 5.” Từ vựng cần cho việc này: supersedes, contradicts, valid_from, valid_until - lại là edge có kiểu.

Phần lớn số liệu trong lĩnh vực này do chính vendor tự báo cáo trên benchmark của họ. Đây là những gì đánh giá độc lập thực sự cho thấy tính đến giữa năm 2026.

Sketchnote bảng điểm: graph thắng multi-hop, temporal, tổng hợp; vector search thắng tra cứu đơn giản và chi phí - định tuyến theo loại câu hỏi

Graph thắng ba việc, với biên độ lớn: multi-hop reasoning (53,4% so với 42,9% cho vector RAG trên GraphRAG-Bench; HippoRAG 2 vượt một embedding model mạnh 9,5 điểm F1 trên 2WikiMultiHopQA); temporal reasoning (biến thể graph của Mem0 đạt 58,1 điểm trong khi bộ nhớ của OpenAI chỉ đạt 21,7 - số liệu chênh lệch nhất trong toàn lĩnh vực); tổng hợp xuyên corpus (64,4% so với 51,3%).

Graph thua hai việc: tra cứu sự thật đơn giản (60,9% cho vector RAG thuần so với 60,1% cho phương pháp graph tốt nhất - graph thêm context dư thừa mà không thắng được gì); chi phí (global search của Microsoft GraphRAG đốt 331.375 token mỗi truy vấn trong benchmark, vector RAG chỉ 880 - HippoRAG 2 đạt 1.008, cho thấy hiệu quả là khả thi).

Hai cảnh báo đáng giá cả phần này. LightRAG công bố những chiến thắng lớn trên benchmark của chính nó, rồi sụp xuống chỉ còn 6,6 điểm F1 trung bình (so với 59,8 của HippoRAG 2) khi bị đánh giá độc lập - đừng bao giờ tin một hệ thống chỉ được đánh giá bởi chính tác giả của nó. Và trong chính bài báo của Mem0, biến thể graph lại thua biến thể không-graph ở câu hỏi multi-hop - graph là một công cụ, không phải một tôn giáo. Đồng thuận thực dụng: định tuyến theo loại câu hỏi - vector cho tra cứu, graph cho chuỗi lý luận.

Bài toán khiến các dự án graph chết: entity resolution

Phần tiêu đề “Bài toán khiến các dự án graph chết: entity resolution”

Nếu graph thắng benchmark, vì sao không phải ai cũng chạy một cái? Vì một con số hầu như không ai nói ra thành lời. Entity resolution là việc quyết định “Dr. John Smith,” “J. Smith,” và “John” là cùng một node, và “Mercury sao Thuỷ” với “Mercury nguyên tố hoá học” là hai node khác nhau. Pipeline trích xuất liên tục sai ở việc này, và lỗi cộng dồn theo cấp số nhân qua từng hop.

Ở độ chính xác 95% mỗi hop, một chuỗi 5 hop chỉ còn đáng tin 77%. Ở 85% mỗi hop, con số đó chỉ còn 44% - việc traversal multi-hop ấn tượng của bạn thực chất là một cú tung đồng xu.

Điều này lật ngược cả thứ tự xây dựng: phần đắt đỏ của graph engineering không phải là thuật toán graph. Đó là việc quyết định cái gì là cùng một thứ. Và có một nơi bài toán đó đã được giải sẵn: wikilink do con người tự tay chọn lọc. Khi ai đó viết [[ADR-007]] trong một ghi chú, entity resolution đã xong, theo đúng cấu trúc - không cần merge mờ (fuzzy), không có lỗi cộng dồn. Phần lớn ngân sách kỹ thuật của các pipeline trích xuất được dùng để cố khôi phục lại thứ mà một wikilink cho không.

The AI Operator khảo sát các công cụ graph engineering hiện có trên vault markdown (Obsidian) và nhận thấy lĩnh vực này mới khoảng năm tháng tuổi, di chuyển nhanh, và mọi công cụ đều thiếu đúng một chân trong ba chân kiềng: edge có kiểu trong file, quy tắc/validation cho graph, và tầng retrieval cho agent.

Sketchnote bản đồ khoảng trống thị trường: mỗi công cụ hiện có chỉ phủ được hai trong ba chân kiềng edge có kiểu, quy tắc graph, và retrieval cho agent - chưa ai phủ đủ cả ba Basic Memory có quan hệ có kiểu trong markdown và một API agent thật, nhưng kiểu chỉ là chuỗi tự do, không có từ vựng hay validation. Breadcrumbs (plugin Obsidian kỳ cựu) có graph engineering thật sự - link có kiểu, đảo chiều tự động, quy tắc bắc cầu - nhưng xây hoàn toàn cho điều hướng của con người, không có bề mặt AI nào. Penfield là đội duy nhất trình bày đầy đủ tầm nhìn, nhưng định tuyến graph của bạn vào cloud của họ với giá 10-20 USD/tháng - markdown của bạn chỉ là nguồn import, không phải nơi lưu trữ chính. Hai thứ hoàn toàn chưa tồn tại ở bất kỳ đâu: một linter cho edge có kiểu (không công cụ nào validate graph của một vault: kiểu không xác định, mục tiêu treo lơ lửng, chu trình mâu thuẫn), và một eval chứng minh việc gán kiểu thực sự cải thiện câu trả lời của agent so với link thuần.

Điểm chốt: một vault được chăm chút tốt đã là 80% của một chỉ mục GraphRAG. Entity resolution đã được giải bằng wikilink. Chi phí indexing bằng 0, vì graph được xây ngay tại thời điểm viết. 20% còn thiếu chính là linter và eval nói trên.

Tổng hợp: graph engineering là gì, thực sự

Phần tiêu đề “Tổng hợp: graph engineering là gì, thực sự”

Đặt cạnh nhau, ba góc nhìn kể cùng một câu chuyện dưới hai lớp vỏ khác nhau. Lớp thứ nhất là orchestration graph: mã hoá tường minh thứ tự công việc, chỗ nào song song được, chỗ nào cần chờ toàn bộ, chỗ nào cần một router hoặc một verifier - để code xác định làm phần ống nước, và model chỉ được huy động ở nơi phán đoán thực sự cần thiết. Lớp thứ hai là knowledge/memory graph: mã hoá tường minh cách các sự thật liên hệ và thay thế nhau theo thời gian, để một agent có thể theo một chuỗi nhân quả thay vì chỉ tìm những đoạn văn bản nghe giống câu hỏi.

Cả hai đều xoay quanh cùng một nguyên lý: node và edge có kiểu, có contract, biến một mớ bước hoặc một mớ ghi chú thành thứ có thể suy luận được. Và cả hai đều không phải ý tưởng mới - LangGraph đã làm việc này ba năm, khoa học tri thức đồ thị đã có cả thập kỷ nghiên cứu. Cái mới, nếu có, là hai điều: node giờ có thể là cả một agent hoàn chỉnh thay vì một lệnh gọi LLM đơn lẻ, và các công cụ như Claude Code dynamic workflows khiến việc viết orchestration script không còn là việc riêng của kỹ sư hạ tầng - Claude tự viết được script đó, tự triển khai được đội subagent, và tự lưu lại được graph đã chứng minh hiệu quả để chạy lại sau này.

Với người đang dùng Claude Code hằng ngày, bài học thực hành gọn nhất là: mỗi khi bạn thấy mình gõ “làm A rồi B rồi C,” dừng lại hỏi bước sau có thật sự đọc output của bước trước không. Nếu không, đó là hai node độc lập đang bị xích oan uổng - và cái giá của việc xích oan đó chính là thời gian chờ bạn không cần phải trả.