Sự thật không mấy thoải mái về các AI agent là: hầu hết chúng chỉ là những chuỗi prompt được “trang phục” lên áo choàng. Tôi biết điều này vì mình đã tự xây dựng nhiều agent. Bạn chỉ cần đặt một system prompt, cung cấp một vài công cụ, có thể thêm một vòng ReAct, rồi gọi nó là “agent”. Nó hoạt động tốt trong demo, trên các trường hợp thuận lợi. Nhưng ngay khi có bất kỳ điều gì bất ngờ — công cụ trả về lỗi, dữ liệu không khớp, truy vấn yêu cầu hơn ba bước — toàn bộ hệ thống sẽ sụp đổ.
Hai tuần trước, tôi quyết định tạo một loại agent khác. Không phải một agent ReAct lảo đảo từng công cụ một. Đó là một agent lập kế hoạch: trước tiên tạo ra một kế hoạch nghiên cứu chi tiết, sau đó thực hiện từng bước, và tự sửa lại phần kế hoạch còn lại khi kết quả trung gian không đáp ứng kỳ vọng. Kết quả là một hệ thống nghiên cứu tài chính, nhận câu hỏi tiếng Anh đơn giản như “So sánh định giá của NVDA và AAPL, cho biết cái nào hiện nay tốt hơn” và tự động:
1. Tạo một kế hoạch đa bước,
2. Thực thi mỗi bước bằng các công cụ dữ liệu thực,
3. Tự điều chỉnh nếu có lỗi,
4. Xuất ra báo cáo chất lượng nhà phân tích.
Toàn bộ hệ thống được xây dựng trên LangGraph, chạy trên đám mây (Groq) hoặc mô hình cục bộ (Ollama), và toàn bộ code chỉ nằm trong khoảng bốn file.
### Vấn đề với “Chỉ dùng ReAct”
ReAct (Reason‑Act‑Observe) là mô hình mặc định cho hầu hết AI agent. LLM suy nghĩ, gọi công cụ, đọc kết quả, lại suy nghĩ, tiếp tục gọi công cụ… Đơn giản, phổ biến và phù hợp cho các nhiệm vụ nhỏ. Nhưng mô hình này có một nhược điểm căn bản: đề khấu. Agent ReAct chỉ tính đến một bước tiếp theo. Khi yêu cầu phân tích năm cổ phiếu qua ba chỉ số và tin tức mới, nó sẽ bắt đầu ngay, có thể lấy P/E của NVDA trước, rồi bị cuốn vào kết quả đầu tiên và đi sâu vào một “đường mèo”. Tôi đã chứng kiến prototype ReAct của mình dùng bảy lần gọi công cụ chỉ để lấy mọi chỉ số cho Tesla, rồi mới nhớ rằng phải so sánh nhiều cổ phiếu. Báo cáo cuối cùng chỉ là tóm tắt kết quả tài chính của Tesla, kèm một câu ngắn về các công ty còn lại.
Đây không phải là vấn đề khả năng của mô hình, mà là vấn đề kiến trúc. Khi một lần suy luận duy nhất phải đồng thời quyết định cái gì cần nghiên cứu, cách nào để nghiên cứu và cách xử lý kết quả, không có nhiệm vụ nào nhận được đủ sự tập trung.
Agent lập kế hoạch giải quyết bằng cách tách “suy nghĩ” và “thực thi”. Đầu tiên lên kế hoạch, sau đó thực hiện, rồi kiểm tra lại kế hoạch có còn hợp lý không. Ý tưởng cốt lõi: phân chia các công việc nhận thức, chạy tuần tự, để mỗi nút chỉ tập trung vào một việc một cách xuất sắc.
### Kiến trúc: Bốn nút, một vòng lặp
Agent lập kế hoạch được mô hình hoá thành một đồ thị có bốn nút, giống như một lưu đồ:
<br>START → Planner → Executor ←→ Replanner → Reporter → END<br>
– Planner: Nhận câu hỏi của người dùng, sinh ra một kế hoạch nghiên cứu có cấu trúc, chi tiết từng bước, mỗi bước chỉ rõ công cụ, tham số và mục đích.
– Executor: Lấy bước tiếp theo trong kế hoạch, gọi công cụ tương ứng, ghi lại kết quả vào scratchpad.
– Replanner: Sau mỗi lần thực thi, kiểm tra toàn bộ dữ liệu đã thu thập. Nếu công cụ trả lỗi hoặc dữ liệu bất ngờ, nó sẽ viết lại các bước còn lại. Nếu mọi thứ ổn, kế hoạch không thay đổi.
– Reporter: Tổng hợp mọi mục trong scratchpad thành một báo cáo phân tích cuối cùng, kèm số liệu và khuyến nghị.
Vòng lặp Executor ↔ Replanner là trái tim của hành vi tự sửa lỗi: sau mỗi hành động, agent luôn có cơ hội cân nhắc lại.
### Quản lý trạng thái: Xương sống của toàn bộ
Trong LangGraph, state là mọi thứ. Nó là bộ nhớ chung truyền qua các nút. Định nghĩa trạng thái đầy đủ:
python<br>import operator<br>from typing import Annotated, TypedDict<br>from pydantic import BaseModel, Field<br><br>class StrategyState(TypedDict, total=False):<br> """Trạng thái toàn cục chảy qua các nút LangGraph."""<br> query: str # Yêu cầu gốc, không đổi<br> plan: list[dict] # Danh sách các bước đã lên kế hoạch<br> scratchpad: Annotated[list[dict], operator.add] # Tích lũy kết quả công cụ<br> current_step: int # Chỉ mục bước hiện tại<br> final_report: str # Kết quả cuối cùng<br> replan_count: int # Đếm số lần tái lập kế hoạch (giới hạn an toàn)<br>
Trường scratchpad được khai báo với Annotated[..., operator.add]. Trình giảm operator.add khiến LangGraph tự động nối các mục mới vào danh sách hiện có thay vì thay thế toàn bộ, tránh lỗi quản lý danh sách và lỗi biến đổi dữ liệu. Mỗi nút chỉ trả về đóng góp của mình, LangGraph sẽ hợp nhất chúng.
### Sơ đồ đầu ra có cấu trúc: PlanStep và Plan
Planner không trả về văn bản tự do mà trả về các mô hình Pydantic, buộc LLM sinh ra kế hoạch có thể phân tích được:
python<br>AVAILABLE_TOOLS_TEXT = """<br>- get_metrics(ticker, metric?): Trả về các chỉ số tài chính. metric tùy chọn (P/E, EPS, Revenue, Market Cap, Sector).<br>- search_news(ticker): Trả về tiêu đề tin tức mới nhất.<br>- compare_metrics(tickers: list, metric): So sánh một chỉ số giữa nhiều ticker.<br>"""<br><br>class PlanStep(BaseModel):<br> step_id: int = Field(description="Số thứ tự bước")<br> tool: str = Field(description=f"Công cụ cần dùng. Phải là một trong:\n{AVAILABLE_TOOLS_TEXT}")<br> args: dict = Field(description="Đối số cho công cụ")<br> purpose: str = Field(description="Lý do thực hiện bước này")<br><br>class Plan(BaseModel):<br> goal: str = Field(description="Mục tiêu tổng thể của phân tích")<br> steps: list[PlanStep] = Field(description="Các bước được thực hiện theo thứ tự")<br>
Trường purpose thực sự quan trọng; nó cung cấp ngữ cảnh cho Replanner, giúp nó quyết định giữ, sửa hay xoá bước dựa trên mục tiêu, không chỉ dựa vào hành động kỹ thuật.
Replanner cũng xuất ra cấu trúc:
python<br>class ReplanDecision(BaseModel):<br> reasoning: str = Field(description="Phân tích tiến độ và kết quả hiện tại")<br> should_replan: bool = Field(description="Có cần sửa kế hoạch không")<br> updated_steps: list[PlanStep] = Field(default_factory=list,<br> description="Các bước còn lại nếu cần sửa")<br>
should_replan là boolean, buộc LLM phải đưa ra quyết định rõ ràng, tránh trường hợp “đúng‑có‑nghĩ” lơ mơ.
### Hệ thống công cụ: Ba công cụ, một Registry
Ba công cụ nghiên cứu tài chính (mock dữ liệu) được đăng ký trong một ToolRegistry có phân quyền namespace:
python<br>from langchain_core.tools import BaseTool<br>from typing import Iterable, Mapping, Any<br><br>class ToolRegistry:<br> """Kho công cụ theo không gian tên."""<br> def __init__(self) -> None:<br> self._tools_by_toolset: dict[str, dict[str, BaseTool]] = {}<br><br> def add_tools(self, toolset: str, tools: Iterable[BaseTool]) -> None:<br> bucket = self._tools_by_toolset.setdefault(toolset, {})<br> bucket.update({t.name: t for t in tools})<br><br> def get_tools(self, toolset: str) -> tuple[BaseTool, ...]:<br> return tuple(self._tools_by_toolset.get(toolset, {}).values())<br><br> def invoke(self, toolset: str, tool_name: str, tool_args: Mapping[str, Any]) -> Any:<br> t = self._tools_by_toolset.get(toolset, {}).get(tool_name)<br> if t is None:<br> raise ValueError(f"Unknown tool '{tool_name}' in toolset '{toolset}'")<br> return t.invoke(dict(tool_args))<br>
Các công cụ được đăng ký dưới các không gian tên: "planner", "executor", "replanner". Planner nhìn thấy mọi công cụ (để có thể lên kế hoạch cho chúng), nhưng chỉ Executor thực sự gọi chúng. Nếu Replanner vô tình tạo lệnh gọi công cụ, Registry sẽ không cho phép vì namespace của nó không có quyền thực thi. Đây là cơ chế kiểm soát truy cập dựa trên vai trò, không chỉ tốt cho kiến trúc phần mềm mà còn là lớp an toàn cho các agent, tránh việc thay đổi trạng thái không mong muốn trong giai đoạn lên kế hoạch.
Paralysis do thừa kế. Đôi khi bạn cần yêu cầu tác nhân ngừng suy nghĩ và bắt đầu hoàn thành. Node Reporter (không được hiển thị đầy đủ ở đây) nhận toàn bộ scratchpad chưa bị cắt ngắn và tổng hợp một báo cáo markdown có cấu trúc. Nó sử dụng model.invoke(prompt).content, đầu ra dạng văn bản thuần thay vì đầu ra có cấu trúc, vì chúng ta muốn LLM có tự do sáng tạo hoàn toàn trong cách trình bày phân tích. Lệnh quan trọng trong prompt: “Cụ thể, trích dẫn các con số từ dữ liệu.” Nếu không có lệnh này, LLM sẽ viết những báo cáo mơ hồ như “NVDA có nền tảng mạnh” thay vì đưa ra các tỷ lệ P/E và doanh thu thực tế.
### Kết nối Graph: LangGraph Assembly
Đây là cách bốn node được gắn lại với nhau:
python<br>from langgraph.graph import StateGraph, END<br>from enum import Enum<br><br>class AgentName(Enum):<br> PLANNER = "planner"<br> EXECUTOR = "executor"<br> REPLANNER = "replanner"<br> REPORT = "report"<br><br>def build_graph():<br> """Xây dựng và biên dịch quy trình làm việc của LangGraph."""<br> workflow = StateGraph(StrategyState)<br> workflow.add_node(AgentName.PLANNER.value, planner_node)<br> workflow.add_node(AgentName.EXECUTOR.value, executor_node)<br> workflow.add_node(AgentName.REPLANNER.value, replanner_node)<br> workflow.add_node(AgentName.REPORT.value, report_node)<br> workflow.set_entry_point(AgentName.PLANNER.value)<br> workflow.add_edge(AgentName.PLANNER.value, AgentName.EXECUTOR.value)<br> workflow.add_edge(AgentName.EXECUTOR.value, AgentName.REPLANNER.value)<br> workflow.add_conditional_edges(<br> AgentName.REPLANNER.value,<br> should_continue_execution,<br> {<br> AgentName.EXECUTOR.value: AgentName.EXECUTOR.value,<br> AgentName.REPORT.value: AgentName.REPORT.value,<br> },<br> )<br> workflow.add_edge(AgentName.REPORT.value, END)<br> return workflow.compile()<br><br>def should_continue_execution(state: StrategyState) -> str:<br> """Xác định node tiếp theo sau khi tái lập kế hoạch."""<br> if state.get("current_step", 0) >= len(state.get("plan", [])):<br> return AgentName.REPORT.value<br> return AgentName.EXECUTOR.value<br>
Graph này gần như đọc được bằng tiếng Anh: Planner → Executor → Replanner → (Executor hoặc Report) → END. Enum AgentName là chi tiết quan trọng; việc dùng chuỗi literal như “planner” ở mọi nơi dễ gây lỗi chính tả (ví dụ “plannr”) khiến node bị ngắt kết nối mà không phát hiện. Enum sẽ phát hiện lỗi này ngay khi import. Hàm định tuyến kiểm tra một điều: chúng ta đã thực hiện hết các bước chưa? Nếu current_step >= len(plan) thì chuyển sang Report, nếu không thì quay lại Executor. Vòng lặp kết thúc vì current_step tăng sau mỗi lần thực thi, và ngay cả khi Replanner thêm bước mới, MAX_STEPS = 12 đặt giới hạn cứng.
### Ví dụ thực thi thực tế: Một truy vấn
Truy vấn: “Phân tích NVDA và AMD. So sánh tỷ lệ P/E và tin tức gần đây, rồi đưa ra đề xuất.”
Giai đoạn 1 – Lập kế hoạch: Planner tạo kế hoạch năm bước: lấy số liệu đầy đủ cho NVDA, lấy số liệu đầy đủ cho AMD, so sánh tỷ lệ P/E, tìm tin tức NVDA, tìm tin tức AMD. Kế hoạch sạch, logic, bao phủ đầy đủ các khía cạnh. Đầu tiên lấy hồ sơ cá nhân để có bức tranh tổng thể, rồi mới so sánh trực tiếp; tin tức cuối cùng vì là thông tin phụ trợ.
Giai đoạn 2 – Vòng thực thi: Bước 1 và 2 trả về JSON số liệu sạch. Replanner mỗi lần xác nhận: dữ liệu ổn, kế hoạch không thay đổi. Bước 3 trả về:
json<br>{<br> "metric": "P/E",<br> "values": {<br> "NVDA": 58.3,<br> "AMD": 102.5<br> }<br>}<br>
Hai giá trị đều có và hợp lệ, không cần tái lập kế hoạch. Bước 4 và 5 lấy ba bài viết tin tức mỗi công ty. Sau bước 5, current_step bằng len(plan), nên hàm định tuyến chuyển sang Reporter.
Giai đoạn 3 – Báo cáo: Reporter nhận năm mục scratchpad đầy đủ, chưa bị cắt và tạo báo cáo có cấu trúc, trích dẫn số cụ thể: “NVDA với P/E 58.3 so với AMD ở mức 102.5 cho thấy NVDA có giá trị tương đối tốt hơn dù vốn hóa thị trường cao $2.8T.” Toàn bộ quy trình mất khoảng 8‑10 giây trên Groq, hoặc 30‑40 giây trên Ollama cục bộ với mô hình 4B.
### Hướng phát triển tiếp theo
Phiên bản hiện tại dùng dữ liệu mô phỏng, nhưng kiến trúc đã sẵn sàng cho môi trường sản xuất. Ba nâng cấp quan trọng:
1. Dữ liệu tài chính thực: Thay thế tra cứu trong từ điển bằng API Yahoo Finance hoặc Alpha Vantage. Chỉ thay đổi phần cài đặt; các công cụ được khai báo bằng @tool không cần thay đổi.
2. Bộ nhớ bền vững: Hiện scratchpad chỉ tồn tại trong một lần chạy. Thêm vector store (ChromaDB, Pinecone) để tác nhân có thể tham chiếu các phân tích trước đó – “NVDA so với tháng trước ra sao?” Điều này biến tác nhân thành một nhà phân tích có khả năng học tập và tích lũy.
3. Con người trong vòng lặp: LangGraph hỗ trợ điểm ngắt tự nhiên. Thêm một điểm dừng sau node Planner để người dùng xem xét và phê duyệt kế hoạch trước khi thực thi: “Tác nhân muốn chạy 6 bước này. Phê duyệt?” Điều này rất cần thiết trong các trường hợp thực tế khi rủi ro tài chính thực tế cao.
### Kết luận
Vòng lặp planning‑execute‑replan hoạt động tốt. Đầu ra có cấu trúc giữ tác nhân trên đúng hướng. Đăng ký công cụ ngăn hỗn loạn. Giới hạn an toàn ngăn chi phí vượt mức. Kiến trúc vững chắc và code sạch. Tuy nhiên, điều khiến tôi băn khoăn là trí tuệ toàn bộ hệ thống vẫn phụ thuộc vào chất lượng của LLM nền tảng. Một mô hình thông minh hơn sẽ lên kế hoạch tốt hơn, bắt lỗi hiệu quả hơn trong quá trình replanning và viết báo cáo sâu sắc hơn. Mô hình yếu hơn sẽ tuân thủ cấu trúc nhưng chỉ tạo ra phân tích trung bình. Graph không làm cho mô hình thông minh hơn, nó chỉ cung cấp khung làm việc tốt hơn cho trí tuệ hiện có.
Chúng ta đã xây dựng lớp quản lý dự án: quản lý trạng thái, điều phối công cụ, luồng thực thi, tự sửa lỗi – những vấn đề này đã được giải quyết. Các mẫu thiết kế đã rõ ràng và hoạt động. Điều chưa giải quyết là phán đoán. Khi nào Replanner thực sự cần tái lập kế hoạch? Khi nào một dữ liệu là bất thường hay chỉ là bất ngờ? Hiện chúng ta mã hóa các quyết định này trong prompt và hy vọng LLM tự hiểu. Đôi khi điều này hoạt động tốt, đôi khi không đủ. Đây là thách thức còn lại để nâng cao mức độ tin cậy và khả năng quyết định của hệ thống.
Paralysis. Đôi khi bạn cần yêu cầu agent dừng suy nghĩ và bắt đầu hoàn thiện. Node Reporter (không được hiển thị đầy đủ ở đây) nhận toàn bộ scratchpad chưa bị cắt ngắn và tổng hợp một báo cáo markdown có cấu trúc. Nó sử dụng model.invoke(prompt).content, xuất ra dạng văn bản thuần, vì chúng ta muốn LLM có tự do sáng tạo hoàn toàn trong cách trình bày phân tích. Lệnh quan trọng trong prompt: “Hãy cụ thể, trích dẫn các con số từ dữ liệu.” Nếu không có lệnh này, LLM sẽ viết báo cáo mơ hồ như “NVDA có nền tảng mạnh” thay vì nêu rõ các chỉ số P/E và doanh thu thực tế.
Kết nối Graph: LangGraph Assembly
Đây là nơi bốn node hội tụ:
python<br>from langgraph.graph import StateGraph, END<br>from enum import Enum<br><br>class AgentName(Enum):<br> PLANNER = "planner"<br> EXECUTOR = "executor"<br> REPLANNER = "replanner"<br> REPORT = "report"<br><br>def build_graph():<br> """Xây dựng và biên dịch luồng công việc LangGraph cho agent lập kế hoạch."""<br> workflow = StateGraph(StrategyState)<br> workflow.add_node(AgentName.PLANNER.value, planner_node)<br> workflow.add_node(AgentName.EXECUTOR.value, executor_node)<br> workflow.add_node(AgentName.REPLANNER.value, replanner_node)<br> workflow.add_node(AgentName.REPORT.value, report_node)<br> workflow.set_entry_point(AgentName.PLANNER.value)<br> workflow.add_edge(AgentName.PLANNER.value, AgentName.EXECUTOR.value)<br> workflow.add_edge(AgentName.EXECUTOR.value, AgentName.REPLANNER.value)<br> workflow.add_conditional_edges(<br> AgentName.REPLANNER.value,<br> should_continue_execution,<br> {<br> AgentName.EXECUTOR.value: AgentName.EXECUTOR.value,<br> AgentName.REPORT.value: AgentName.REPORT.value,<br> },<br> )<br> workflow.add_edge(AgentName.REPORT.value, END)<br> return workflow.compile()<br><br>def should_continue_execution(state: StrategyState) -> str:<br> """Trả về tên node tiếp theo sau khi lập kế hoạch lại."""<br> if state.get("current_step", 0) >= len(state.get("plan", [])):<br> return AgentName.REPORT.value<br> return AgentName.EXECUTOR.value<br>
Đồ thị đọc gần như tiếng Anh: Planner → Executor → Replanner → (Executor hoặc Report) → END. Enum AgentName là chi tiết quan trọng; dùng chuỗi ký tự như “planner” mọi nơi dễ gây lỗi khi gõ nhầm, ví dụ “plannr” sẽ tạo node rời rạc mà không phát hiện. Enum giúp bắt lỗi ngay khi import. Hàm định tuyến chỉ kiểm tra một điều: đã thực hiện hết các bước chưa? Nếu current_step >= len(plan) thì chuyển sang Report, nếu không thì quay lại Executor. Vòng lặp kết thúc vì current_step tăng lên sau mỗi lần thực thi, và dù Replanner có thêm bước mới, giới hạn MAX_STEPS = 12 vẫn giữ một mức trần cứng.
### Ví dụ thực thi thực tế: Một truy vấn
Truy vấn: “Phân tích NVDA và AMD. So sánh chỉ số P/E và tin tức gần đây, sau đó đề xuất một trong hai.”
Giai đoạn 1 – Lập kế hoạch: Planner tạo kế hoạch gồm năm bước: lấy đầy đủ các chỉ số của NVDA, lấy đầy đủ các chỉ số của AMD, so sánh trực tiếp chỉ số P/E, tìm tin tức về NVDA, tìm tin tức về AMD. Kế hoạch rõ ràng, logic, bao phủ mọi khía cạnh. Các hồ sơ công ty được thu thập trước để có bức tranh toàn diện, so sánh P/E là trung tâm, tin tức chỉ là thông tin phụ trợ.
Giai đoạn 2 – Vòng thực thi:
– Bước 1 và 2 trả về JSON chỉ số sạch sẽ. Replanner mỗi lần xác nhận dữ liệu ổn, không thay đổi kế hoạch.
– Bước 3 trả về:json<br>{<br> "metric": "P/E",<br> "values": {<br> "NVDA": 58.3,<br> "AMD": 102.5<br> }<br>}<br>
Hai giá trị đều hợp lệ, không cần lập kế hoạch lại.
– Bước 4 và 5 thu thập ba bài tin mỗi công ty. Khi bước 5 hoàn thành, current_step bằng len(plan), nên hàm định tuyến đưa luồng sang Reporter.
Giai đoạn 3 – Báo cáo: Reporter nhận năm mục scratchpad đầy đủ, chưa bị cắt ngắn, và tạo báo cáo có cấu trúc, trích dẫn số liệu cụ thể: “NVDA với P/E 58.3 so với AMD với 102.5 cho thấy NVDA có giá trị tương đối tốt hơn dù vốn hóa thị trường cao hơn ($2.8T).” Toàn bộ quá trình mất khoảng 8–10 giây trên Groq, hoặc 30–40 giây trên Ollama cục bộ với mô hình 4B.
### Hướng phát triển tiếp theo
Phiên bản hiện tại dùng dữ liệu giả, nhưng kiến trúc đã sẵn sàng cho môi trường sản xuất. Ba nâng cấp quan trọng:
1. Dữ liệu tài chính thực: Thay thế tra cứu trong dict bằng API Yahoo Finance hoặc Alpha Vantage. Chỉ cần thay đổi phần thực thi của các công cụ, còn lại giữ nguyên nhờ mẫu @tool decorator.
2. Bộ nhớ lâu dài: Hiện scratchpad tồn tại chỉ trong một lần chạy. Thêm vector store (ChromaDB, Pinecone) để agent có thể tham chiếu phân tích trước đó: “NVDA so với tháng trước như thế nào?” Điều này biến agent từ công cụ không trạng thái thành nhà phân tích có khả năng phát triển.
3. Con người can thiệp: LangGraph hỗ trợ điểm ngắt tự nhiên. Thêm một điểm dừng sau node Planner, cho phép người dùng xem và phê duyệt kế hoạch trước khi thực thi: “Agent muốn thực hiện 6 bước này. Bạn đồng ý?” Điều này rất cần thiết khi triển khai thực tế, nơi rủi ro tài chính thực tế rất cao.
### Kết luận
Vòng lặp lập kế hoạch – thực thi – lập kế hoạch lại hoạt động tốt. Đầu ra có cấu trúc giúp agent đi đúng hướng. Registry công cụ ngăn chặn hỗn loạn. Giới hạn an toàn ngăn chi phí nổ tung. Kiến trúc vững chắc, mã sạch. Tuy nhiên, yếu tố quan trọng vẫn là trí tuệ của LLM nền tảng. Một mô hình mạnh hơn sẽ lên kế hoạch tốt hơn, phát hiện lỗi nhanh hơn, viết báo cáo sâu sắc hơn. Graph chỉ cung cấp khung, không làm cho mô hình thông minh hơn. Chúng ta đã giải quyết được quản lý trạng thái, điều phối công cụ, luồng thực thi và tự sửa lỗi. Những vấn đề còn lại là phán đoán: Khi nào Replanner thực sự cần lập kế hoạch lại? Khi nào dữ liệu là bất thường hay chỉ là bất ngờ? Hiện chúng ta mã hoá các quyết định này trong prompt và hy vọng LLM tự hiểu. Đôi khi nó thành công, đôi khi không. Đây là thách thức còn lại cần cải thiện.
Tham khảo: medium.com
Một AI Agent có khả năng lập kế hoạch và suy luận đa bước gắn Ollama vào n8n chatbox
Agent mới sẽ hoạt động dựa trên vòng lặpPlan → Execute → Replan → Report, tự động chia nhỏ yêu cầu, thực thi từng bước và tự sửa lỗi nếu cần. Toàn bộ quá trình suy luận sẽ được chạy trên mô hình cục bộ qua Ollama, đảm bảo tính riêng tư và không phụ thuộc vào API bên ngoài. Workflow n8n mới sẽ bao gồm các node chính sau, mô phỏng kiến trúc LangGraph
| Node trong n8n | Vai trò trong Workflow |
|---|---|
| Chat Trigger | Nhận yêu cầu từ người dùng, đóng vai trò là điểm khởi đầu. |
| Think Tool | Ghi lại quá trình suy luận, các ràng buộc và kế hoạch hành động trước khi thực thi, đóng vai trò như “sổ tay nội bộ”. |
| Planner Agent | Phân tích yêu cầu và tạo ra một kế hoạch chi tiết gồm các bước thực thi (cần gọi những tool gì, với tham số nào). |
| Executor | Thực thi lần lượt từng bước trong kế hoạch bằng cách gọi các công cụ (Tools) tương ứng, ghi lại kết quả vào “scratchpad”. |
| Replanner | Đánh giá kết quả sau mỗi bước thực thi, xác định xem kế hoạch có cần thay đổi không (ví dụ: bước thực thi lỗi, phát hiện mới). |
| Reporter | Tổng hợp tất cả các phát hiện trong “scratchpad” thành một báo cáo có cấu trúc, dễ hiểu cho người dùng. |
Think Tool: Node này sẽ là một “AI Agent” được cấu hình để không gọi bất kỳ công cụ nào (tool) khác. Nó chỉ có nhiệm vụ phân tích yêu cầu và đưa ra một chuỗi suy luận, các ràng buộc, và dàn ý sơ bộ. Bạn có thể dùng prompt mẫu: “Analyze the user’s request. Think step-by-step about what tools and information you will need. Output your reasoning and a draft plan.” Load memory: Postgres / Supabase
hoặc vector DB.
Planner Agent: Node “AI Agent” này sẽ dùng output từ Think Tool và prompt được thiết kế riêng để tạo ra kế hoạch hành động cuối cùng. Prompt của nó sẽ tương tự PLAN_PROMPT trong repo gốc. Prompt cho Ollama:
You are a planning agent.
Break the user request into steps.
Return JSON:
{
“steps”: [
{“id”: 1, “action”: “…”, “tool”: “…”},
{“id”: 2, “action”: “…”, “tool”: “…”}
]
}
Output giống Planning Agent JSON
Parse Plan: Code node (JS) Convert JSON → array items
Executor & Replanner (Vòng lặp): Đây là phần phức tạp nhất. Split In Batches hoặc Loop node. Mỗi step:
IF tool = “search” → gọi API
IF tool = “db” → query
IF tool = “llm” → gọi Ollama
Đây chính là “tool calling thật”, không phải giả lập
Executor: Node này sẽ được triển khai như một “AI Agent” khác. Nó sẽ nhận kế hoạch từ Planner và thực thi từng bước một, sử dụng các “AI Tool” mà bạn đã kết nối.
Replanner: Node này sẽ đánh giá kết quả và quyết định xem có cần lặp lại bước trước đó không. Bạn có thể sử dụng Split Node để kiểm tra điều kiện và tạo vòng lặp. Sau mỗi step: Gửi lại Ollama:
Current goal: …
Completed steps: …
Result: …
Do we need more steps?
trả về:
{
“done”: false,
“next_steps”: […]
}
Reporter: Node “AI Agent” hoặc “LLM Chain” cuối cùng sẽ nhận toàn bộ nhật ký thực thi và tổng hợp thành báo cáo cuối cùng. Khi done = true: LLM tổng hợp toàn bộ thành câu trả lời.

Bài viết liên quan: