ragflow/personal_analyze/agent_workflow_works.md
2025-12-01 05:06:53 +07:00

55 KiB

Cách Agent Workflow Hoạt Động trong RAGFlow

Tổng Quan Hệ Thống

RAGFlow triển khai một hệ thống agent workflow mạnh mẽ cho phép người dùng định nghĩa và thực thi các quy trình làm việc phức tạp thông qua một Domain Specific Language (DSL) dạng JSON. Hệ thống này được xây dựng dựa trên kiến trúc đồ thị (graph-based architecture) với các component có thể kết nối và tương tác với nhau.


1. Kiến Trúc DSL (Domain Specific Language)

1.1. Cấu Trúc Tổng Thể của DSL

DSL trong RAGFlow là một cấu trúc JSON được thiết kế để mô tả workflow dưới dạng đồ thị có hướng (Directed Graph). Mỗi workflow được định nghĩa trong file JSON với cấu trúc:

{
  "components": {
    "component_id": {
      "obj": {
        "component_name": "ComponentType",
        "params": { /* cấu hình component */ }
      },
      "downstream": ["next_component_id"],
      "upstream": ["previous_component_id"]
    }
  },
  "globals": {
    "sys.query": "",
    "sys.user_id": "",
    "sys.conversation_turns": 0,
    "sys.files": []
  },
  "variables": { /* biến do người dùng định nghĩa */ },
  "history": [ /* lịch sử hội thoại */ ],
  "path": ["begin"],
  "retrieval": { "chunks": [], "doc_aggs": [] },
  "memory": []
}

File tham chiếu: ragflow/agent/canvas.py:36-73

1.2. Các Thành Phần Chính của DSL

a) Components - Các Node trong Đồ Thị

Mỗi component đại diện cho một bước xử lý trong workflow. Các component được định danh bằng ID duy nhất theo format ComponentType:UniqueIdentifier.

Ví dụ thực tế từ customer_service.json:

"Agent:TwelveOwlsWatch": {
  "downstream": ["VariableAggregator:FuzzyBerriesFlow"],
  "obj": {
    "component_name": "Agent",
    "params": {
      "llm_id": "deepseek-chat@DeepSeek",
      "max_rounds": 5,
      "sys_prompt": "You are a friendly and casual conversational assistant...",
      "prompts": [
        {
          "content": "The user query is {sys.query}",
          "role": "user"
        }
      ]
    }
  },
  "upstream": ["Categorize:DullFriendsThank"]
}

File tham chiếu: ragflow/agent/templates/customer_service.json:116-166

b) Downstream/Upstream - Định Nghĩa Luồng Thực Thi

  • downstream: Danh sách các component tiếp theo sẽ được thực thi
  • upstream: Danh sách các component đã thực thi trước đó

Đây là cách DSL mô tả directed graph - các node kết nối với nhau tạo thành luồng xử lý.

Logic trong code (ragflow/agent/canvas.py:543):

# Sau khi một component thực thi xong, hệ thống sẽ thêm các downstream vào path
_extend_path(cpn["downstream"])

c) Globals - Biến Hệ Thống

Các biến toàn cục được hệ thống quản lý:

  • sys.query: Câu hỏi của người dùng
  • sys.user_id: ID người dùng
  • sys.conversation_turns: Số lượt hội thoại
  • sys.files: Danh sách file đính kèm
  • env.*: Biến môi trường do người dùng định nghĩa

File tham chiếu: ragflow/agent/canvas.py:278-283, 343-348

d) Variables - Hệ Thống Tham Chiếu Biến

DSL hỗ trợ tham chiếu động giữa các component thông qua cú pháp {component_id@output_name}.

Ví dụ:

{
  "content": "The user query is {sys.query}\n\nThe relevant document are {Retrieval:ShyPumasJoke@formalized_content}"
}

Regex pattern (ragflow/agent/component/base.py:396):

variable_ref_patt = r"\{* *\{([a-zA-Z:0-9]+@[A-Za-z0-9_.]+|sys\.[A-Za-z0-9_.]+|env\.[A-Za-z0-9_.]+)\} *\}*"

Cơ chế resolve biến (ragflow/agent/canvas.py:158-183):

def get_value_with_variable(self, value: str) -> Any:
    pat = re.compile(r"\{* *\{([a-zA-Z:0-9]+@[A-Za-z0-9_.]+|sys\.[A-Za-z0-9_.]+|env\.[A-Za-z0-9_.]+)\} *\}*")
    out_parts = []
    last = 0

    for m in pat.finditer(value):
        out_parts.append(value[last:m.start()])
        key = m.group(1)
        v = self.get_variable_value(key)  # Lấy giá trị thực từ component hoặc globals
        # ... xử lý giá trị
        out_parts.append(rep)
        last = m.end()

    out_parts.append(value[last:])
    return "".join(out_parts)

1.3. Các Loại Component Có Sẵn

RAGFlow cung cấp một thư viện component phong phú:

Component File Chức Năng
Begin agent/component/begin.py Entry point của workflow
Agent agent/component/agent_with_tools.py LLM agent với khả năng gọi tool
LLM agent/component/llm.py Gọi LLM cơ bản không có tool
Retrieval agent/tools/retrieval.py Tìm kiếm trong knowledge base
Categorize agent/component/categorize.py Phân loại intent bằng LLM
Switch agent/component/switch.py Điều kiện rẽ nhánh
Iteration agent/component/iteration.py Vòng lặp for-each
Loop agent/component/loop.py Vòng lặp while
Message agent/component/message.py Format output trả về user
VariableAggregator agent/component/variable_assigner.py Gộp kết quả từ nhiều nhánh

File tham chiếu: ragflow/agent/component/__init__.py:51-58


2. Execution Engine - Cơ Chế Thực Thi Workflow

2.1. Class Graph - Core Engine

Class Graph là base class chứa logic cốt lõi để load và thực thi workflow.

File: ragflow/agent/canvas.py:34-273

Quá Trình Load DSL

def load(self):
    self.components = self.dsl["components"]

    # Duyệt qua từng component trong DSL
    for k, cpn in self.components.items():
        # Tạo object ComponentParam từ tên component
        param = component_class(cpn["obj"]["component_name"] + "Param")()
        param.update(cpn["obj"]["params"])

        # Validate parameters
        param.check()

        # Tạo object Component thực tế
        cpn["obj"] = component_class(cpn["obj"]["component_name"])(self, k, param)

    self.path = self.dsl["path"]

File tham chiếu: ragflow/agent/canvas.py:84-101

Giải thích logic:

  1. Parse JSON DSL thành dictionary
  2. Với mỗi component, tạo object Parameter tương ứng (VD: AgentParam, LLMParam)
  3. Validate parameters bằng method check()
  4. Tạo object Component thực tế (VD: Agent, LLM) và inject vào graph
  5. Component được truyền reference tới self (Canvas) để có thể truy cập biến global

2.2. Class Canvas - Agent Workflow Executor

Class Canvas kế thừa Graph và triển khai logic đặc thù cho agent workflow.

File: ragflow/agent/canvas.py:275-676

Phương Thức run() - Trái Tim của Execution Engine

Đây là async generator function thực thi workflow và yield các event real-time.

Signature:

async def run(self, **kwargs):
    # Nhận tham số: query, files, user_id, inputs

File tham chiếu: ragflow/agent/canvas.py:358-583

Chi Tiết Từng Bước Thực Thi

Bước 1: Khởi Tạo State
st = time.perf_counter()
self.message_id = get_uuid()
created_at = int(time.time())

# Lưu query vào history
self.add_user_input(kwargs.get("query"))

# Reset output của tất cả component
for k, cpn in self.components.items():
    self.components[k]["obj"].reset(True)

File tham chiếu: ragflow/agent/canvas.py:359-364

Bước 2: Set System Variables
for k in kwargs.keys():
    if k in ["query", "user_id", "files"] and kwargs[k]:
        if k == "files":
            self.globals[f"sys.{k}"] = FileService.get_files(kwargs[k])
        else:
            self.globals[f"sys.{k}"] = kwargs[k]

# Tăng conversation turn counter
self.globals["sys.conversation_turns"] += 1

File tham chiếu: ragflow/agent/canvas.py:372-380

Logic: Các tham số từ user được map vào global variables để các component có thể reference bằng {sys.query}, {sys.files}, v.v.

Bước 3: Path Initialization
if not self.path or self.path[-1].lower().find("userfillup") < 0:
    self.path.append("begin")
    self.retrieval.append({"chunks": [], "doc_aggs": []})

File tham chiếu: ragflow/agent/canvas.py:393-395

Logic: Path là một list lưu trữ thứ tự các component đã/đang/sẽ thực thi. Mọi workflow đều bắt đầu từ component begin.

Bước 4: Yield Workflow Started Event
def decorate(event, dt):
    return {
        "event": event,
        "message_id": self.message_id,
        "created_at": created_at,
        "task_id": self.task_id,
        "data": dt
    }

yield decorate("workflow_started", {"inputs": kwargs.get("inputs")})

File tham chiếu: ragflow/agent/canvas.py:382-402

Logic: Hệ thống sử dụng Server-Sent Events (SSE) để stream các event về frontend real-time. Mỗi event có format chuẩn với event, message_id, task_id, data.

Bước 5: Execute Components in Path
idx = len(self.path) - 1

while idx < len(self.path):
    to = len(self.path)

    # Yield node_started events
    for i in range(idx, to):
        yield decorate("node_started", {
            "component_id": self.path[i],
            "component_name": self.get_component_name(self.path[i]),
            "component_type": self.get_component_type(self.path[i])
        })

    # Execute batch of components
    _run_batch(idx, to)

    # ... post-processing

File tham chiếu: ragflow/agent/canvas.py:444-548

Logic:

  • path là dynamic array có thể mở rộng trong quá trình thực thi
  • Mỗi iteration thực thi một batch component từ idx đến to
  • Sau khi thực thi, các component có thể thêm downstream vào path, làm len(self.path) tăng
  • Vòng lặp tiếp tục cho đến khi không còn component nào trong path
Bước 6: Parallel Execution với ThreadPoolExecutor
def _run_batch(f, t):
    if self.is_canceled():
        raise TaskCanceledException(...)

    with ThreadPoolExecutor(max_workers=5) as executor:
        thr = []
        i = f
        while i < t:
            cpn = self.get_component_obj(self.path[i])

            if cpn.component_name.lower() in ["begin", "userfillup"]:
                thr.append(executor.submit(cpn.invoke, inputs=kwargs.get("inputs", {})))
                i += 1
            else:
                # Kiểm tra dependencies
                for _, ele in cpn.get_input_elements().items():
                    if isinstance(ele, dict) and ele.get("_cpn_id") and ele.get("_cpn_id") not in self.path[:i]:
                        # Nếu dependency chưa execute, skip component này
                        self.path.pop(i)
                        t -= 1
                        break
                else:
                    # Execute component
                    thr.append(executor.submit(cpn.invoke, **cpn.get_input()))
                    i += 1

        # Wait for all threads to complete
        for t in thr:
            t.result()

File tham chiếu: ragflow/agent/canvas.py:405-429

Logic quan trọng:

  • Hệ thống thực thi tối đa 5 component song song để tăng hiệu suất
  • Trước khi execute, kiểm tra dependencies: nếu component A reference output của component B mà B chưa execute, thì A bị remove khỏi path
  • Mỗi component được execute trong thread riêng biệt, nhưng vẫn đợi tất cả thread complete trước khi tiếp tục
Bước 7: Post-Processing & Branching Logic
for i in range(idx, to):
    cpn = self.get_component(self.path[i])
    cpn_obj = self.get_component_obj(self.path[i])

    # Xử lý streaming output cho Message component
    if cpn_obj.component_name.lower() == "message":
        if isinstance(cpn_obj.output("content"), partial):
            _m = ""
            for m in cpn_obj.output("content")():
                if m == "<think>":
                    yield decorate("message", {"content": "", "start_to_think": True})
                elif m == "</think>":
                    yield decorate("message", {"content": "", "end_to_think": True})
                else:
                    yield decorate("message", {"content": m})
                    _m += m
            cpn_obj.set_output("content", _m)
        else:
            yield decorate("message", {"content": cpn_obj.output("content")})

    # Xử lý error handling
    if cpn_obj.error():
        ex = cpn_obj.exception_handler()
        if ex and ex["goto"]:
            self.path.extend(ex["goto"])  # Jump to error handler
        elif ex and ex["default_value"]:
            yield decorate("message", {"content": ex["default_value"]})
        else:
            self.error = cpn_obj.error()

    # Branching logic
    if cpn_obj.component_name.lower() in ["categorize", "switch"]:
        # Categorize/Switch component quyết định nhánh tiếp theo
        _extend_path(cpn_obj.output("_next"))
    elif cpn_obj.component_name.lower() in ("iteration", "loop"):
        # Loop component thêm start node vào path
        _append_path(cpn_obj.get_start())
    else:
        # Component thường thêm downstream vào path
        _extend_path(cpn["downstream"])

    # Yield node_finished event
    yield _node_finished(cpn_obj)

File tham chiếu: ragflow/agent/canvas.py:459-543

Giải thích chi tiết:

  1. Streaming Output: Nếu component trả về functools.partial, hệ thống sẽ iterate và yield từng chunk text real-time
  2. Error Handling: Component có thể định nghĩa exception handler với goto (jump to error component) hoặc default_value (fallback response)
  3. Branching:
    • Categorize/Switch: Quyết định nhánh dựa trên classification result
    • Iteration/Loop: Tạo vòng lặp bằng cách thêm start node của loop vào path
    • Normal component: Thêm tất cả downstream vào path
Bước 8: Workflow Completion
if not self.error:
    yield decorate("workflow_finished", {
        "inputs": kwargs.get("inputs"),
        "outputs": self.get_component_obj(self.path[-1]).output(),
        "elapsed_time": time.perf_counter() - st,
        "created_at": st
    })

    # Lưu vào conversation history
    self.history.append(("assistant", self.get_component_obj(self.path[-1]).output()))

File tham chiếu: ragflow/agent/canvas.py:566-574


3. Component Architecture - Cách Định Nghĩa Component

3.1. ComponentParamBase - Base Class cho Parameters

Mọi component đều có một class Parameter tương ứng để validate và lưu trữ config.

File: ragflow/agent/component/base.py:37-391

Ví Dụ: AgentParam

class AgentParam(LLMParam, ToolParamBase):
    def __init__(self):
        super().__init__()
        self.function_name = "agent"
        self.tools = []
        self.mcp = []
        self.max_rounds = 5
        self.description = ""

File tham chiếu: ragflow/agent/component/agent_with_tools.py:38-79

Phương Thức Quan Trọng

def update(self, conf, allow_redundant=False):
    """
    Đệ quy update parameters từ JSON config
    Hỗ trợ nested parameters và validation
    """
    # ... implementation

def check(self):
    """
    Validate parameters
    Được gọi sau update() để đảm bảo config hợp lệ
    """
    raise NotImplementedError("Parameter Object should be checked.")

def as_dict(self):
    """
    Convert parameters object thành dict để serialize
    """
    # ... implementation

File tham chiếu: ragflow/agent/component/base.py:124-184, 54-55, 96-122

3.2. ComponentBase - Base Class cho Component

File: ragflow/agent/component/base.py:393-583

Constructor

def __init__(self, canvas, id, param: ComponentParamBase):
    from agent.canvas import Graph
    assert isinstance(canvas, Graph), "canvas must be an instance of Canvas"
    self._canvas = canvas  # Reference to workflow graph
    self._id = id          # Component ID
    self._param = param    # Parameters object
    self._param.check()    # Validate ngay khi khởi tạo

File tham chiếu: ragflow/agent/component/base.py:412-418

Logic: Mỗi component giữ reference tới Canvas để có thể:

  • Truy cập global variables: self._canvas.globals
  • Lấy output từ component khác: self._canvas.get_variable_value("other_component@output")
  • Add reference (citations): self._canvas.add_reference(chunks, doc_infos)

Phương Thức invoke() - Entry Point Execution

def invoke(self, **kwargs) -> dict[str, Any]:
    self.set_output("_created_time", time.perf_counter())

    try:
        self._invoke(**kwargs)  # Template method pattern
    except Exception as e:
        if self.get_exception_default_value():
            self.set_exception_default_value()
        else:
            self.set_output("_ERROR", str(e))
        logging.exception(e)

    self.set_output("_elapsed_time", time.perf_counter() - self.output("_created_time"))
    return self.output()

File tham chiếu: ragflow/agent/component/base.py:434-446

Logic:

  • invoke() là public method được Canvas gọi
  • Bên trong gọi _invoke() - abstract method mà subclass phải implement
  • Tự động track _created_time_elapsed_time
  • Tự động catch exception và set _ERROR output

Abstract Method: _invoke()

@timeout(int(os.environ.get("COMPONENT_EXEC_TIMEOUT", 10*60)))
def _invoke(self, **kwargs):
    raise NotImplementedError()

File tham chiếu: ragflow/agent/component/base.py:448-450

Logic:

  • Subclass phải override method này để implement logic
  • Có timeout protection (default 10 phút)
  • Nhận **kwargs là input variables đã được resolve

3.3. Ví Dụ Cụ Thể: Agent Component

File: ragflow/agent/component/agent_with_tools.py

Constructor - Load Tools

def __init__(self, canvas, id, param: AgentParam):
    LLM.__init__(self, canvas, id, param)

    # Initialize tools dictionary
    self.tools = {}

    # Load built-in tool components
    for cpn in self._param.tools:
        cpn = self._load_tool_obj(cpn)
        self.tools[cpn.get_meta()["function"]["name"]] = cpn

    # Initialize LLM with multi-round support
    self.chat_mdl = LLMBundle(
        self._canvas.get_tenant_id(),
        TenantLLMService.llm_id2llm_type(self._param.llm_id),
        self._param.llm_id,
        max_retries=self._param.max_retries,
        retry_interval=self._param.delay_after_error,
        max_rounds=self._param.max_rounds,
        verbose_tool_use=True
    )

    # Collect tool metadata for LLM
    self.tool_meta = [v.get_meta() for _, v in self.tools.items()]

    # Load MCP (Model Context Protocol) tools
    for mcp in self._param.mcp:
        _, mcp_server = MCPServerService.get_by_id(mcp["mcp_id"])
        tool_call_session = MCPToolCallSession(mcp_server, mcp_server.variables)
        for tnm, meta in mcp["tools"].items():
            self.tool_meta.append(mcp_tool_metadata_to_openai_tool(meta))
            self.tools[tnm] = tool_call_session

    # Setup callback for tool usage tracking
    self.callback = partial(self._canvas.tool_use_callback, id)
    self.toolcall_session = LLMToolPluginCallSession(self.tools, self.callback)

File tham chiếu: ragflow/agent/component/agent_with_tools.py:84-107

Logic:

  1. Load danh sách tools từ config (Retrieval, Wikipedia, TavilySearch, etc.)
  2. Khởi tạo LLMBundle với config max_rounds (số vòng ReAct tối đa)
  3. Load external tools từ MCP servers
  4. Tạo callback để track tool usage (lưu vào Redis)

_invoke() Implementation - ReAct Loop

@timeout(int(os.environ.get("COMPONENT_EXEC_TIMEOUT", 20*60)))
def _invoke(self, **kwargs):
    if self.check_if_canceled("Agent processing"):
        return

    # Xử lý nested agent calls (khi agent A gọi agent B)
    if kwargs.get("user_prompt"):
        usr_pmt = ""
        if kwargs.get("reasoning"):
            usr_pmt += "\nREASONING:\n{}\n".format(kwargs["reasoning"])
        if kwargs.get("context"):
            usr_pmt += "\nCONTEXT:\n{}\n".format(kwargs["context"])
        usr_pmt += "\nQUERY:\n{}\n".format(str(kwargs["user_prompt"]))
        self._param.prompts = [{"role": "user", "content": usr_pmt}]

    # Nếu không có tools, fallback to simple LLM
    if not self.tools:
        return LLM._invoke(self, **kwargs)

    # Prepare prompts
    prompt, msg, user_defined_prompt = self._prepare_prompt_variables()

    # Check for structured output schema
    output_schema = self._get_output_schema()
    schema_prompt = ""
    if output_schema:
        schema = json.dumps(output_schema, ensure_ascii=False, indent=2)
        schema_prompt = structured_output_prompt(schema)

    # Check if next component is Message (for streaming)
    downstreams = self._canvas.get_component(self._id)["downstream"]
    ex = self.exception_handler()

    if any([self._canvas.get_component_obj(cid).component_name.lower()=="message" for cid in downstreams]) \
       and not (ex and ex["goto"]) and not output_schema:
        # Stream output directly to Message component
        self.set_output("content", partial(self.stream_output_with_tools, prompt, msg, user_defined_prompt))
        return

    # Non-streaming mode
    _, msg = message_fit_in([{"role": "system", "content": prompt}, *msg], int(self.chat_mdl.max_length * 0.97))
    use_tools = []
    ans = ""

    # Execute ReAct loop
    for delta_ans, tk in self._react_with_tools_streamly(prompt, msg, use_tools, user_defined_prompt, schema_prompt=schema_prompt):
        if self.check_if_canceled("Agent processing"):
            return
        ans += delta_ans

    # Handle errors
    if ans.find("**ERROR**") >= 0:
        logging.error(f"Agent._chat got error. response: {ans}")
        if self.get_exception_default_value():
            self.set_output("content", self.get_exception_default_value())
        else:
            self.set_output("_ERROR", ans)
        return

    # Parse structured output if schema exists
    if output_schema:
        for _ in range(self._param.max_retries + 1):
            try:
                obj = json_repair.loads(clean_formated_answer(ans))
                self.set_output("structured", obj)
                if use_tools:
                    self.set_output("use_tools", use_tools)
                return obj
            except Exception:
                # Retry with format correction
                ans = self._force_format_to_schema(ans, schema_prompt)
        self.set_output("_ERROR", "The answer cannot be parsed as JSON")
        return

    # Normal output
    self.set_output("content", ans)
    if use_tools:
        self.set_output("use_tools", use_tools)
    return ans

File tham chiếu: ragflow/agent/component/agent_with_tools.py:164-240

ReAct Loop Implementation

def _react_with_tools_streamly(self, prompt, history: list[dict], use_tools, user_defined_prompt={}, schema_prompt: str = ""):
    token_count = 0
    tool_metas = self.tool_meta
    hist = deepcopy(history)

    # Optimize multi-turn conversation
    if len(hist) > 3:
        st = timer()
        user_request = full_question(messages=history, chat_mdl=self.chat_mdl)
        self.callback("Multi-turn conversation optimization", {}, user_request, elapsed_time=timer()-st)
    else:
        user_request = history[-1]["content"]

    def use_tool(name, args):
        """Call tool and track usage"""
        tool_response = self.toolcall_session.tool_call(name, args)
        use_tools.append({
            "name": name,
            "arguments": args,
            "results": tool_response
        })
        return name, tool_response

    def complete():
        """Generate final answer with optional citation"""
        need2cite = self._param.cite and self._canvas.get_reference()["chunks"] and self._id.find("-->") < 0
        if schema_prompt:
            need2cite = False

        cited = False
        if hist and hist[0]["role"] == "system":
            if schema_prompt:
                hist[0]["content"] += "\n" + schema_prompt
            if need2cite and len(hist) < 7:
                hist[0]["content"] += citation_prompt()
                cited = True

        yield "", token_count

        # Truncate history if too long
        _hist = hist
        if len(hist) > 12:
            _hist = [hist[0], hist[1], *hist[-10:]]

        # Stream answer
        entire_txt = ""
        for delta_ans in self._generate_streamly(_hist):
            if not need2cite or cited:
                yield delta_ans, 0
            entire_txt += delta_ans

        # Generate citations if needed
        if need2cite and not cited:
            st = timer()
            txt = ""
            for delta_ans in self._gen_citations(entire_txt):
                if self.check_if_canceled("Agent streaming"):
                    return
                yield delta_ans, 0
                txt += delta_ans
            self.callback("gen_citations", {}, txt, elapsed_time=timer()-st)

    # Analyze task first
    st = timer()
    task_desc = analyze_task(self.chat_mdl, prompt, user_request, tool_metas, user_defined_prompt)
    self.callback("analyze_task", {}, task_desc, elapsed_time=timer()-st)

    # ReAct loop
    for _ in range(self._param.max_rounds + 1):
        if self.check_if_canceled("Agent streaming"):
            return

        # LLM decides next step (which tools to call or complete)
        response, tk = next_step(self.chat_mdl, hist, tool_metas, task_desc, user_defined_prompt)
        token_count += tk
        hist.append({"role": "assistant", "content": response})

        try:
            # Parse function calls from LLM response
            functions = json_repair.loads(re.sub(r"```.*", "", response))
            if not isinstance(functions, list):
                raise TypeError(f"List should be returned, but `{functions}`")

            # Execute tools in parallel
            with ThreadPoolExecutor(max_workers=5) as executor:
                thr = []
                for func in functions:
                    name = func["name"]
                    args = func["arguments"]

                    if name == COMPLETE_TASK:
                        # LLM quyết định task hoàn thành
                        for txt, tkcnt in complete():
                            yield txt, tkcnt
                        return

                    thr.append(executor.submit(use_tool, name, args))

                # Reflect on tool results
                st = timer()
                reflection = reflect(self.chat_mdl, hist, [th.result() for th in thr], user_defined_prompt)
                hist.append({"role": "user", "content": reflection})
                self.callback("reflection", {}, str(reflection), elapsed_time=timer()-st)

        except Exception as e:
            logging.exception(msg=f"Wrong JSON argument format in LLM ReAct response: {e}")
            e = f"\nTool call error, please correct the input parameter of response format and call it again.\n *** Exception ***\n{e}"
            hist.append({"role": "user", "content": str(e)})

    # Exceed max rounds, force completion
    logging.warning(f"Exceed max rounds: {self._param.max_rounds}")
    final_instruction = f"""
    {user_request}
    IMPORTANT: You have reached the conversation limit. Based on ALL the information and research you have gathered so far, please provide a DIRECT and COMPREHENSIVE final answer...
    """
    hist.append({"role": "user", "content": final_instruction})

    for txt, tkcnt in complete():
        yield txt, tkcnt

File tham chiếu: ragflow/agent/component/agent_with_tools.py:273-406

Logic chi tiết của ReAct Loop:

  1. Analyze Task: LLM phân tích task và available tools để lập kế hoạch
  2. Loop until max_rounds:
    • LLM quyết định next step: gọi tool nào hoặc complete task
    • Parse JSON response chứa list function calls
    • Execute tất cả tool calls song song (max 5 workers)
    • LLM reflect trên tool results để quyết định bước tiếp theo
  3. Tool Call: Mỗi tool được execute và kết quả được append vào history
  4. Reflection: LLM đánh giá tool results và quyết định có cần thêm information không
  5. Completion: Khi LLM return COMPLETE_TASK, generate final answer
  6. Citation: Nếu có retrieval results, tự động generate citations

4. Deep Research - Advanced Reasoning Engine

File: ragflow/agentic_reasoning/deep_research.py

4.1. Tổng Quan

DeepResearcher là một engine cao cấp implement multi-step reasoning với iterative search. Được sử dụng trong dialog service khi enable "Deep Reasoning" mode.

File tham chiếu: ragflow/api/db/services/dialog_service.py:27, 441-463

4.2. Architecture

class DeepResearcher:
    def __init__(self,
                 chat_mdl: LLMBundle,
                 prompt_config: dict,
                 kb_retrieve: partial = None,
                 kg_retrieve: partial = None):
        self.chat_mdl = chat_mdl
        self.prompt_config = prompt_config
        self._kb_retrieve = kb_retrieve  # Knowledge base retrieval function
        self._kg_retrieve = kg_retrieve  # Knowledge graph retrieval function

File tham chiếu: ragflow/agentic_reasoning/deep_research.py:27-37

4.3. Thinking Loop

def thinking(self, chunk_info, question):
    """
    Main reasoning loop với iterative search

    Args:
        chunk_info: Dictionary để lưu retrieved chunks (for citation)
        question: Câu hỏi của user

    Returns:
        Generator yield từng reasoning step
    """
    msg_history = [{"role": "user", "content": question}]
    all_reasoning_steps = []

    for step_index in range(MAX_SEARCH_LIMIT):  # Thường 3-5 steps
        # Step 1: Generate reasoning với LLM
        query_think = ""
        for ans in self._generate_reasoning(msg_history):
            query_think = ans
            yield query_think

        # Step 2: Extract search queries từ reasoning
        queries = self._extract_search_queries(query_think, question, step_index)

        if not queries:
            # Không còn query nào, reasoning complete
            break

        # Step 3: Execute searches
        for search_query in queries:
            # Retrieve from KB, Web, KG
            kbinfos = self._retrieve_information(search_query)

            # Update chunk_info for citation
            self._update_chunk_info(chunk_info, kbinfos)

            # Summarize relevant information
            summary_think = ""
            for ans in self._extract_relevant_info(
                self._truncate_previous_reasoning(all_reasoning_steps),
                search_query,
                kbinfos
            ):
                summary_think = ans
                yield summary_think

            # Append search result to reasoning
            query_think += f"\n{BEGIN_SEARCH_RESULT}\n{summary_think}\n{END_SEARCH_RESULT}"

        # Step 4: Save reasoning step
        all_reasoning_steps.append(query_think)
        msg_history.append({"role": "assistant", "content": query_think})

File tham chiếu: ragflow/agentic_reasoning/deep_research.py (method thinking)

Logic:

  1. Generate Reasoning: LLM tạo chain-of-thought reasoning step
  2. Extract Queries: Parse reasoning text để tìm <|begin_search_query|>...<|end_search_query|>
  3. Multi-source Retrieval:
    • Knowledge Base (RAG)
    • Web Search (Tavily API)
    • Knowledge Graph
  4. Summarize: LLM extract relevant info từ search results
  5. Iterate: Append results vào history và continue reasoning
  6. Stop Condition: Khi LLM không generate thêm search query

4.4. Prompt Engineering

File: ragflow/agentic_reasoning/prompts.py

REASON_PROMPT = """
You are a research assistant performing deep reasoning to answer complex questions.

Instructions:
1. Break down the question into logical steps
2. For each step that requires external information, wrap search queries in tags:
   <|begin_search_query|>your search query here<|end_search_query|>
3. Use previous search results (wrapped in <|begin_search_result|>...<|end_search_result|>) to inform next steps
4. When you have enough information, provide final answer without additional searches

Current question: {question}

Previous reasoning:
{previous_steps}

Continue reasoning:
"""

RELEVANT_EXTRACTION_PROMPT = """
Given the following search results for query "{query}":

{search_results}

Extract and summarize ONLY the information directly relevant to answering:
{context}

Focus on facts, numbers, and specific details. Ignore irrelevant content.
"""

File tham chiếu: ragflow/agentic_reasoning/prompts.py


5. Branching & Control Flow Components

5.1. Categorize Component - LLM-based Intent Classification

File: ragflow/agent/component/categorize.py

Cách Hoạt Động

class CategorizeParam(ComponentParamBase):
    def __init__(self):
        super().__init__()
        self.category_description = {
            "category_name": {
                "description": "Mô tả category",
                "examples": ["example 1", "example 2"],
                "to": ["next_component_id"]
            }
        }
        self.llm_id = ""
        self.query = "sys.query"

Ví dụ từ customer_service.json:

{
  "category_description": {
    "1. contact": {
      "description": "User provides contact information",
      "examples": ["My phone is 123456", "john@email.com"],
      "to": ["Message:BreezyDonutsHeal"]
    },
    "2. casual": {
      "description": "Casual chat, not product related",
      "examples": ["How are you?", "What's your name?"],
      "to": ["Agent:TwelveOwlsWatch"]
    },
    "4. product related": {
      "description": "Questions about product usage",
      "examples": ["How to install?", "Why it doesn't work?"],
      "to": ["Retrieval:ShyPumasJoke"]
    }
  }
}

File tham chiếu: ragflow/agent/templates/customer_service.json:177-213

_invoke() Implementation

def _invoke(self, **kwargs):
    # Get query from variable reference
    query = self._canvas.get_value_with_variable(self._param.query)

    # Build prompt with categories and examples
    prompt = "Classify the following query into one of these categories:\n\n"
    for cat_name, cat_info in self._param.category_description.items():
        prompt += f"{cat_name}: {cat_info['description']}\n"
        prompt += f"Examples: {', '.join(cat_info['examples'])}\n\n"

    prompt += f"Query: {query}\n\nCategory:"

    # Call LLM for classification
    response = self.chat_mdl.chat(prompt, [], {"temperature": 0.1})

    # Find matching category
    for cat_name, cat_info in self._param.category_description.items():
        if cat_name in response:
            self.set_output("category_name", cat_name)
            self.set_output("_next", cat_info["to"])
            return

    # Default to first category
    first_cat = list(self._param.category_description.values())[0]
    self.set_output("_next", first_cat["to"])

Logic:

  1. LLM classify user query vào một trong các category
  2. Set _next output = downstream của category đó
  3. Canvas engine sẽ đọc _next và append vào path

5.2. Switch Component - Conditional Branching

File: ragflow/agent/component/switch.py

Example Configuration

{
  "component_name": "Switch",
  "params": {
    "cases": [
      {
        "condition": "{sys.conversation_turns} > 5",
        "to": ["Agent:SuggestEnd"]
      },
      {
        "condition": "{User:Profile@premium} == true",
        "to": ["Agent:PremiumSupport"]
      }
    ],
    "default": ["Agent:StandardSupport"]
  }
}

Logic

def _invoke(self, **kwargs):
    for case in self._param.cases:
        # Resolve variables in condition
        condition = self._canvas.get_value_with_variable(case["condition"])

        # Evaluate condition
        if eval(condition):
            self.set_output("_next", case["to"])
            return

    # Default branch
    self.set_output("_next", self._param.default)

5.3. Iteration & Loop Components

Files:

  • ragflow/agent/component/iteration.py
  • ragflow/agent/component/loop.py

Iteration - For-Each Loop

{
  "component_name": "Iteration",
  "params": {
    "items": "{DataProcessor@results}",
    "item_var": "current_item"
  }
}

Logic:

def _invoke(self, **kwargs):
    items = self._canvas.get_variable_value(self._param.items)

    if not isinstance(items, list):
        items = [items]

    for idx, item in enumerate(items):
        # Set item variable
        self._canvas.set_variable_value(self._param.item_var, item)

        # Add loop body to path
        self._canvas.path.append(self.get_start())  # Start of loop body

Loop - While Loop

{
  "component_name": "Loop",
  "params": {
    "condition": "{attempt_count} < 3",
    "max_iterations": 10
  }
}

6. API Integration - Cách User Tương Tác với Workflow

6.1. REST API Endpoint

File: ragflow/api/apps/canvas_app.py:124-178

Endpoint: POST /completion

@manager.route('/completion', methods=['POST'])
@validate_request("id")
@login_required
async def run():
    req = await request_json()
    query = req.get("query", "")
    files = req.get("files", [])
    inputs = req.get("inputs", {})
    user_id = req.get("user_id", current_user.id)

    # Permission check
    if not UserCanvasService.accessible(req["id"], current_user.id):
        return get_json_result(
            data=False,
            message='Only owner of canvas authorized for this operation.',
            code=RetCode.OPERATING_ERROR
        )

    # Load canvas DSL from database
    e, cvs = UserCanvasService.get_by_id(req["id"])
    if not e:
        return get_data_error_result(message="canvas not found.")

    if not isinstance(cvs.dsl, str):
        cvs.dsl = json.dumps(cvs.dsl, ensure_ascii=False)

    # Create Canvas instance
    try:
        canvas = Canvas(cvs.dsl, current_user.id)
    except Exception as e:
        return server_error_response(e)

    # Server-Sent Events (SSE) stream
    async def sse():
        nonlocal canvas, user_id
        try:
            # Execute workflow và stream events
            async for ans in canvas.run(query=query, files=files, user_id=user_id, inputs=inputs):
                yield "data:" + json.dumps(ans, ensure_ascii=False) + "\n\n"

            # Save updated DSL (với updated history, variables, etc.)
            cvs.dsl = json.loads(str(canvas))
            UserCanvasService.update_by_id(req["id"], cvs.to_dict())

        except Exception as e:
            logging.exception(e)
            canvas.cancel_task()
            yield "data:" + json.dumps({
                "code": 500,
                "message": str(e),
                "data": False
            }, ensure_ascii=False) + "\n\n"

    # Return SSE response
    resp = Response(sse(), mimetype="text/event-stream")
    resp.headers.add_header("Cache-control", "no-cache")
    resp.headers.add_header("Connection", "keep-alive")
    resp.headers.add_header("X-Accel-Buffering", "no")
    resp.headers.add_header("Content-Type", "text/event-stream; charset=utf-8")
    return resp

File tham chiếu: ragflow/api/apps/canvas_app.py:124-178

6.2. Event Stream Format

Frontend nhận stream các event theo format:

data:{"event":"workflow_started","message_id":"uuid","task_id":"uuid","data":{"inputs":{}}}

data:{"event":"node_started","message_id":"uuid","data":{"component_id":"begin","component_name":"Begin"}}

data:{"event":"node_finished","message_id":"uuid","data":{"component_id":"begin","outputs":{},"elapsed_time":0.001}}

data:{"event":"node_started","message_id":"uuid","data":{"component_id":"Agent:xxx","component_name":"Agent"}}

data:{"event":"message","message_id":"uuid","data":{"content":"Hello"}}
data:{"event":"message","message_id":"uuid","data":{"content":" there"}}
data:{"event":"message","message_id":"uuid","data":{"content":"!"}}

data:{"event":"message_end","message_id":"uuid","data":{"reference":{"chunks":[],"doc_aggs":[]}}}

data:{"event":"node_finished","message_id":"uuid","data":{"component_id":"Agent:xxx"}}

data:{"event":"workflow_finished","message_id":"uuid","data":{"outputs":{"content":"Hello there!"},"elapsed_time":2.5}}

Frontend có thể:

  • Track progress real-time
  • Display streaming responses
  • Show which component đang execute
  • Handle errors gracefully

7. Cách Định Nghĩa Custom DSL - Hướng Dẫn Thực Hành

7.1. Bước 1: Thiết Kế Workflow Graph

Vẽ sơ đồ workflow với các node và edge:

[Begin]
   ↓
[Categorize Intent]
   ├→ "Order Status" → [Retrieval:OrderDB] → [Agent:OrderSupport] → [Message]
   ├→ "Product Info" → [Retrieval:ProductKB] → [Agent:ProductExpert] → [Message]
   └→ "General Chat" → [Agent:CasualChat] → [Message]

7.2. Bước 2: Viết JSON DSL

Template Cơ Bản

{
  "components": {
    "begin": {
      "obj": {
        "component_name": "Begin",
        "params": {
          "prologue": "Welcome! How can I help you?",
          "mode": "conversational"
        }
      },
      "downstream": ["Categorize:IntentClassifier"],
      "upstream": []
    },

    "Categorize:IntentClassifier": {
      "obj": {
        "component_name": "Categorize",
        "params": {
          "llm_id": "deepseek-chat@DeepSeek",
          "query": "sys.query",
          "category_description": {
            "order_status": {
              "description": "User asking about order tracking or delivery status",
              "examples": [
                "Where is my order?",
                "When will my package arrive?"
              ],
              "to": ["Retrieval:OrderDB"]
            },
            "product_info": {
              "description": "Questions about product features, specs, or usage",
              "examples": [
                "What are the features?",
                "How to use this product?"
              ],
              "to": ["Retrieval:ProductKB"]
            },
            "general_chat": {
              "description": "Casual conversation not related to orders or products",
              "examples": [
                "Hello",
                "How are you?"
              ],
              "to": ["Agent:CasualChat"]
            }
          }
        }
      },
      "downstream": [
        "Retrieval:OrderDB",
        "Retrieval:ProductKB",
        "Agent:CasualChat"
      ],
      "upstream": ["begin"]
    },

    "Retrieval:OrderDB": {
      "obj": {
        "component_name": "Retrieval",
        "params": {
          "kb_ids": ["order_database_kb_id"],
          "query": "sys.query",
          "top_n": 5,
          "similarity_threshold": 0.3
        }
      },
      "downstream": ["Agent:OrderSupport"],
      "upstream": ["Categorize:IntentClassifier"]
    },

    "Agent:OrderSupport": {
      "obj": {
        "component_name": "Agent",
        "params": {
          "llm_id": "deepseek-chat@DeepSeek",
          "max_rounds": 3,
          "sys_prompt": "You are an order support specialist. Help users track their orders based on the database information provided.",
          "prompts": [
            {
              "role": "user",
              "content": "User question: {sys.query}\n\nOrder database results: {Retrieval:OrderDB@formalized_content}"
            }
          ],
          "tools": []
        }
      },
      "downstream": ["Message:FinalResponse"],
      "upstream": ["Retrieval:OrderDB"]
    },

    "Retrieval:ProductKB": {
      "obj": {
        "component_name": "Retrieval",
        "params": {
          "kb_ids": ["product_kb_id"],
          "query": "sys.query",
          "top_n": 8
        }
      },
      "downstream": ["Agent:ProductExpert"],
      "upstream": ["Categorize:IntentClassifier"]
    },

    "Agent:ProductExpert": {
      "obj": {
        "component_name": "Agent",
        "params": {
          "llm_id": "deepseek-chat@DeepSeek",
          "sys_prompt": "You are a product expert. Answer questions based on official product documentation.",
          "prompts": [
            {
              "role": "user",
              "content": "{sys.query}\n\nProduct docs: {Retrieval:ProductKB@formalized_content}"
            }
          ]
        }
      },
      "downstream": ["Message:FinalResponse"],
      "upstream": ["Retrieval:ProductKB"]
    },

    "Agent:CasualChat": {
      "obj": {
        "component_name": "Agent",
        "params": {
          "llm_id": "deepseek-chat@DeepSeek",
          "sys_prompt": "You are a friendly assistant for casual conversation.",
          "prompts": [
            {
              "role": "user",
              "content": "{sys.query}"
            }
          ]
        }
      },
      "downstream": ["Message:FinalResponse"],
      "upstream": ["Categorize:IntentClassifier"]
    },

    "Message:FinalResponse": {
      "obj": {
        "component_name": "Message",
        "params": {
          "content": [
            "{Agent:OrderSupport@content}",
            "{Agent:ProductExpert@content}",
            "{Agent:CasualChat@content}"
          ]
        }
      },
      "downstream": [],
      "upstream": [
        "Agent:OrderSupport",
        "Agent:ProductExpert",
        "Agent:CasualChat"
      ]
    }
  },

  "globals": {
    "sys.query": "",
    "sys.user_id": "",
    "sys.conversation_turns": 0,
    "sys.files": []
  },

  "variables": {},
  "history": [],
  "path": [],
  "retrieval": [],
  "memory": []
}

7.3. Bước 3: Variable References

Các Pattern Tham Chiếu

  1. System Variables:

    "{sys.query}"              // User's question
    "{sys.user_id}"            // User ID
    "{sys.conversation_turns}" // Conversation count
    "{sys.files}"              // Uploaded files
    
  2. Component Outputs:

    "{ComponentID@output_name}"
    
    // Examples:
    "{Retrieval:OrderDB@formalized_content}"
    "{Agent:ProductExpert@content}"
    "{Categorize:IntentClassifier@category_name}"
    
  3. Nested Access:

    "{Agent:Analysis@structured.summary}"
    "{DataProcessor@results.0.score}"
    

7.4. Bước 4: Upload và Test

# Upload canvas via API
curl -X POST http://localhost:9380/api/canvas/set \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "My Custom Workflow",
    "dsl": { ... your JSON DSL ... }
  }'

# Execute workflow
curl -X POST http://localhost:9380/api/canvas/completion \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "canvas_id_from_previous_response",
    "query": "Where is my order #12345?"
  }'

8. Advanced Features

8.1. Nested Agent Composition

Agent có thể gọi agent khác như một tool:

{
  "component_name": "Agent",
  "params": {
    "llm_id": "deepseek-chat@DeepSeek",
    "tools": [
      {
        "component_name": "Agent",
        "name": "product_specialist",
        "params": {
          "llm_id": "deepseek-chat@DeepSeek",
          "sys_prompt": "You are a product specialist...",
          "user_prompt": "Answer this product question: {user_input}"
        }
      },
      {
        "component_name": "Agent",
        "name": "order_specialist",
        "params": {
          "llm_id": "deepseek-chat@DeepSeek",
          "sys_prompt": "You are an order tracking specialist..."
        }
      }
    ]
  }
}

Cách hoạt động:

  • Supervisor agent phân tích query
  • Quyết định gọi sub-agent nào
  • Sub-agent execute và return result
  • Supervisor synthesize final answer

File tham chiếu: ragflow/agent/component/agent_with_tools.py:109-119

8.2. Model Context Protocol (MCP) Integration

RAGFlow hỗ trợ external tools qua MCP:

{
  "component_name": "Agent",
  "params": {
    "mcp": [
      {
        "mcp_id": "github_mcp_server_id",
        "tools": {
          "search_code": {
            "name": "search_code",
            "description": "Search code in GitHub repositories",
            "parameters": {
              "query": { "type": "string" },
              "repo": { "type": "string" }
            }
          }
        }
      }
    ]
  }
}

File tham chiếu: ragflow/agent/component/agent_with_tools.py:99-104

8.3. Structured Output Schema

Force agent return JSON theo schema:

{
  "component_name": "Agent",
  "params": {
    "outputs": {
      "structured": {
        "type": "object",
        "properties": {
          "product_name": { "type": "string" },
          "price": { "type": "number" },
          "features": {
            "type": "array",
            "items": { "type": "string" }
          },
          "recommendation": { "type": "boolean" }
        },
        "required": ["product_name", "price"]
      }
    }
  }
}

Logic (ragflow/agent/component/agent_with_tools.py:141-154, 215-235):

  1. Inject schema vào system prompt
  2. LLM generate JSON
  3. Validate và parse bằng json_repair
  4. Retry nếu invalid JSON (max retries configurable)

8.4. Exception Handling

{
  "component_name": "Retrieval",
  "params": {
    "kb_ids": ["kb_123"],
    "exception_method": "goto",
    "exception_goto": ["Agent:Fallback"],
    "exception_default_value": "I couldn't find information in our database."
  }
}

Modes:

  • goto: Jump to specific component khi error
  • comment: Return default value và continue
  • null: Raise error và stop workflow

File tham chiếu: ragflow/agent/component/base.py:565-579


9. Tổng Kết Flow Hoàn Chỉnh

User Request → Response Journey

1. User sends request
   POST /api/canvas/completion
   { "id": "canvas_123", "query": "Where is my order?" }

2. API loads DSL from database
   UserCanvasService.get_by_id(canvas_123)
   → DSL JSON

3. Canvas initialization
   canvas = Canvas(dsl_json, tenant_id)
   → Parse JSON
   → Create component objects
   → Validate parameters

4. Execute workflow
   async for event in canvas.run(query="Where is my order?"):

   4.1. Initialize state
       - Set sys.query = "Where is my order?"
       - Reset component outputs
       - path = ["begin"]

   4.2. Execute "begin"
       - Yield workflow_started event
       - Yield node_started event
       - invoke() → set prologue output
       - Yield node_finished event
       - Append downstream ["Categorize:IntentClassifier"] to path

   4.3. Execute "Categorize:IntentClassifier"
       - Yield node_started event
       - invoke():
         * Call LLM with query and categories
         * LLM returns "order_status"
         * Set output._next = ["Retrieval:OrderDB"]
       - Yield node_finished event
       - Append output._next to path

   4.4. Execute "Retrieval:OrderDB"
       - Yield node_started event
       - invoke():
         * Resolve query = sys.query
         * Search in knowledge base
         * Return chunks
         * Set output.formalized_content = formatted chunks
       - Yield node_finished event
       - Append downstream ["Agent:OrderSupport"] to path

   4.5. Execute "Agent:OrderSupport"
       - Yield node_started event
       - invoke():
         * Resolve prompts:
           - sys.query → "Where is my order?"
           - Retrieval:OrderDB@formalized_content → retrieved chunks
         * Start ReAct loop:
           Step 1: LLM analyze task
           Step 2: LLM decides no tools needed
           Step 3: LLM returns COMPLETE_TASK
         * Stream answer: "Your order #12345 is..."
       - Yield message events (streaming)
       - Yield node_finished event
       - Append downstream ["Message:FinalResponse"] to path

   4.6. Execute "Message:FinalResponse"
       - Yield node_started event
       - invoke():
         * Resolve content variables:
           - Agent:OrderSupport@content → "Your order #12345 is..."
           - Other agents → empty (not executed)
         * Return first non-empty content
       - Yield message event
       - Yield message_end event
       - Yield node_finished event
       - No downstream → workflow complete

   4.7. Workflow completion
       - Yield workflow_finished event
       - Save updated DSL to database (with history, path, etc.)

5. SSE stream to frontend
   Frontend displays:
   - Progress indicators
   - Streaming response
   - Citations (if any)

6. User sees response
   "Your order #12345 is currently in transit. Expected delivery: tomorrow."

Phụ Lục: File References Chính

Component File Path Line Numbers Mục Đích
DSL Schema ragflow/agent/canvas.py 36-73 Định nghĩa cấu trúc DSL
Graph Engine ragflow/agent/canvas.py 34-273 Core workflow execution
Canvas Executor ragflow/agent/canvas.py 275-676 Agent workflow runner
Component Base ragflow/agent/component/base.py 37-583 Base class cho component
Agent Component ragflow/agent/component/agent_with_tools.py 81-437 LLM agent với ReAct
Deep Researcher ragflow/agentic_reasoning/deep_research.py 27-150 Multi-step reasoning
API Endpoint ragflow/api/apps/canvas_app.py 124-178 REST API
Component Discovery ragflow/agent/component/__init__.py 51-58 Plugin system
Templates ragflow/agent/templates/ - Pre-built workflows

Tài liệu này mô tả chi tiết cách RAGFlow implement agent workflow system. Hệ thống cho phép người dùng định nghĩa complex multi-agent workflows thông qua JSON DSL, với execution engine mạnh mẽ hỗ trợ branching, looping, tool calling, và streaming responses.