> ## Documentation Index
> Fetch the complete documentation index at: https://qitor.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# 第 1 课：ReAct

> 构建第一个真正的 QitOS 智能体，并完整理解提示词、解析器、工具与归约的闭环。

这是整条课程里第一个完整的智能体。

它故意保持小巧，但并不玩具化。这一课里你会看到一个真正可运行的编码智能体，它具备完整的组件链：

* 强类型 state
* 真实的大模型适配层
* 真实的系统提示词
* 真实的解析器（parser）
* 真实的工具
* 真实的 `reduce()`（归约，将观测结果与决策归约回状态）循环
* 真实的 `qita` 追踪记录

你会结合 `examples/patterns/react.py` 来学习，但这节课本身会把设计解释清楚，不要求你先反向阅读源码。

## 你要构建什么

任务非常小：

* 打开 `buggy_module.py`
* 修复 `add(a, b)`，让它返回 `a + b`
* 运行验证命令

正因为任务小，它特别适合拿来观察完整 QitOS 执行内核，而不会被额外编排噪音掩盖。

## 本课的设计选择

| 设计分支  | 本课选择                                      | 为什么这是最好的起点                    |
| ----- | ----------------------------------------- | ----------------------------- |
| 任务形态  | 单文件缺陷修复 + 单个验证命令                          | 易验证、易追踪                       |
| State | `scratchpad`、`target_file`、`test_command` | 足够影响下一步决策，但不引入冗余状态            |
| 模型适配层 | 返回文本的 `OpenAICompatibleModel`             | 简单、可迁移、与供应商无关                 |
| 提示词契约 | `REACT_SYSTEM_PROMPT`                     | 明确的一次一工具文本协议                  |
| 解析器   | `ReActTextParser`                         | 与 `Thought:` / `Action:` 完整对应 |
| 工具    | 手工 `ToolRegistry` + 紧凑 `CodingToolSet`    | 先学清楚工具界面，再学预设                 |
| 记忆    | 不使用                                       | 运行很短，没有引入单独记忆的必要              |
| 历史    | 使用 Engine 默认行为                            | 在理解内核之前不引入额外上下文控制             |
| 追踪能力  | `qita board`                              | 从第一课就学会检查内核                   |

## 为什么从文本 ReAct 适配层开始

本课使用：

```python theme={null}
OpenAICompatibleModel(...)
```

以及：

```python theme={null}
model_parser=ReActTextParser()
```

这样你能看到最透明的一条路径：

`messages -> text model output -> ReAct parser -> Decision -> tool execution`

其中解析器负责将原始模型输出解析为决策（智能体每步的结构化决策），而协议（模型输出的格式契约）定义了输出的格式约定。我们不从原生工具调用、XML、JSON 或模型专属适配层开始，因为那些选择会在你还没理解核心循环之前，过早引入协议耦合。

## 系统提示词是协议，不是装饰

本课使用的 ReAct 提示词大致是：

```text theme={null}
You are a reliable ReAct agent.

Rules:
- Use at most one tool call per response.
- Never invent tool names or arguments.
- If a tool result is enough to conclude, output final answer directly.

Output contract (strict):
Thought: <one concise reasoning sentence>
Action: <tool_name>(arg=value, ...)
or
Final Answer: <final answer only>
```

在代码里通常表现为：

```python theme={null}
def build_system_prompt(self, state: ReactState) -> str | None:
    return render_prompt(
        REACT_SYSTEM_PROMPT,
        {"tool_schema": self.tool_registry.get_tool_descriptions()},
    )
```

这一点很关键。`ReActTextParser` 不是在猜模型输出，它是在消费这份明确约定好的协议。

本课最重要的第一条经验就是：

* 提示词格式与解析器选择，本质上是一项联合设计决策
* 改了其中一个，通常就要连另一个一起改

## 本课的模型适配层

示例里的模型构造通常长这样：

```python theme={null}
def build_model() -> OpenAICompatibleModel:
    return OpenAICompatibleModel(
        model=MODEL_NAME,
        api_key=api_key,
        base_url=MODEL_BASE_URL,
        temperature=0.2,
        max_tokens=2048,
    )
```

为什么这里选这个适配层：

* 它适用于 OpenAI 兼容端点
* 模型返回纯文本，便于观察
* 与基于提示词注入工具模式的路径天然兼容
* 让课程可以在不同供应商与本地网关间迁移

这节课你不是在选择最强模型，而是在选择最能暴露内核结构的适配层。

<Steps>
  <Step title="围绕下一步决策来设计 state">
    本课的 state 故意很小：

    ```python theme={null}
    @dataclass
    class ReactState(StateSchema):
        scratchpad: list[str] = field(default_factory=list)
        target_file: str = "buggy_module.py"
        test_command: str = (
            'python -c "import buggy_module; assert buggy_module.add(20, 22) == 42"'
        )
    ```

    为什么是这三个字段：

    * `scratchpad`：保存压缩后的最近轨迹，供下一步决策使用
    * `target_file`：让智能体始终围绕单一工件行动
    * `test_command`：把任务完成变成一个可执行成功条件

    这是第一条 QitOS 习惯：

    **只给 state 加那些会改变未来决策的字段。**
  </Step>

  <Step title="暴露尽量小的工具界面">
    示例使用手工注册表，让你能精确看到暴露给模型的能力：

    ```python theme={null}
    registry = ToolRegistry()
    registry.include(
        CodingToolSet(
            workspace_root=workspace_root,
            include_notebook=False,
            enable_lsp=False,
            enable_tasks=False,
            enable_web=False,
            expose_modern_names=False,
        )
    )
    ```

    `CodingToolSet` 本身是捆绑包，但它的界面仍是你主动裁剪出来的。

    对第 1 课来说，最合适的工具集合只需要覆盖：

    * 查看文件
    * 编辑文件
    * 运行验证命令

    在任务没有提出需求之前，不要暴露更大的工具面。
  </Step>

  <Step title="把提示词与解析器明确绑在一起">
    智能体构造函数里通常会这样写：

    ```python theme={null}
    super().__init__(
        tool_registry=registry,
        llm=llm,
        model_parser=ReActTextParser(),
    )
    ```

    要把这句话读成一个整体：

    这个智能体要求模型输出 ReAct 文本，并由 ReAct 解析器负责解析。

    在 QitOS 里，这个绑定本身就是适配层的一部分。
  </Step>

  <Step title="prepare 只组织下一步真正需要的上下文">
    `prepare()` 负责把 state 变成提示词就绪的文本：

    ```python theme={null}
    def prepare(self, state: ReactState) -> str:
        lines = [
            f"Task: {state.task}",
            f"Target file: {state.target_file}",
            f"Verification command: {state.test_command}",
            f"Step: {state.current_step}/{state.max_steps}",
        ]
        if state.scratchpad:
            lines.append("Recent trajectory:")
            lines.extend(state.scratchpad[-8:])
        return "\n".join(lines)
    ```

    第二条核心习惯就是：

    **`prepare()` 不是 state 的全量转储，而是提示词视图。**
  </Step>

  <Step title="让归约决定智能体记住什么">
    ReAct 的学习发生在 `reduce()` 里：

    ```python theme={null}
    def reduce(
        self,
        state: ReactState,
        observation: dict[str, Any],
        decision: Decision[Action],
    ) -> ReactState:
        action_results = (
            observation.get("action_results", [])
            if isinstance(observation, dict)
            else []
        )
        if decision.rationale:
            state.scratchpad.append(f"Thought: {decision.rationale}")
        if decision.actions:
            state.scratchpad.append(f"Action: {format_action(decision.actions[0])}")
        if action_results:
            first = action_results[0]
            state.scratchpad.append(f"Observation: {first}")
            if isinstance(first, dict) and int(first.get("returncode", 1)) == 0:
                state.final_result = "Patch applied and verification passed."
        state.scratchpad = state.scratchpad[-30:]
        return state
    ```

    这一个函数里藏着三条原则：

    * 不是每条观测结果（每步后智能体接收的结构化观察结果）都值得回灌进未来上下文
    * state 是压缩工作记忆，而不是全量日志
    * `final_result` 是成功的显式信号
  </Step>

  <Step title="理解本课还没有引入什么">
    第 1 课不使用：

    * `decide()` 覆写
    * 显式规划
    * 记忆适配器
    * 自定义历史
    * 上下文压缩（compaction）
    * 模型专属协议覆写

    不是因为 QitOS 没有这些能力，而是因为在你看清默认主路径之前，过早引入反而会模糊边界。
  </Step>

  <Step title="运行示例，并用 qita 检查内核">
    运行：

    ```bash theme={null}
    python examples/patterns/react.py
    ```

    然后查看：

    ```bash theme={null}
    qita board --logdir runs
    ```

    在 `qita` 中重点检查：

    * 发送给模型的提示词是否符合预期
    * 解析器是否干净地产生了 `Thought` 与 `Action`
    * 工具输出是否让成功条件变得明确
    * `final_result` 是否在真正成功的第一时间被设置
  </Step>
</Steps>

## 为什么这一课不引入记忆或上下文压缩

本课最正确的记忆选择就是不用。

原因很简单：

* 运行很短
* `scratchpad` 已足够容纳有效上下文
* 过早引入检索或上下文压缩会模糊架构边界

在 QitOS 中，记忆不是高级智能体的标配，它是为真实长时运行问题准备的答案。

## 完整示例

完整可运行代码位于：

* [`examples/patterns/react.py`](https://github.com/Qitor/qitos/blob/main/examples/patterns/react.py)

## 第 2 课会引入什么

第 2 课会保持相同的模型适配层和执行解析器，但引入一个新的设计点：

**规划应该变成显式状态与显式控制边界，而不是藏在更长的思考里。**

<CardGroup cols={2}>
  <Card title="下一课：PlanAct" icon="list-check" href="/zh/tutorials/planact">
    加入规划器、游标与 `decide()` 门控，但不更换核心运行时
  </Card>

  <Card title="相关参考：Kit" icon="toolbox" href="/zh/reference/kit">
    回看本课使用的 `ReActTextParser`、提示词模板与编码工具界面
  </Card>
</CardGroup>
