# How ReActAgent Handles Tool Selection and Execution in AgentScope

> Discover how AgentScope's ReActAgent expertly handles tool selection and execution. Learn about its reasoning acting loop, tool choice parameter, and dynamic function resolution for efficient agent workflows.

- Repository: [AgentScope-AI/agentscope](https://github.com/agentscope-ai/agentscope)
- Tags: how-to-guide
- Published: 2026-03-09

---

**The ReActAgent orchestrates tool selection and execution through a reasoning-acting loop that uses the `tool_choice` parameter to control LLM behavior, extracts `tool_use` blocks from model responses, and delegates execution to the Toolkit class for dynamic function resolution and streaming result processing.**

The `ReActAgent` in the [agentscope-ai/agentscope](https://github.com/agentscope-ai/agentscope) repository implements the classic ReAct (Reasoning and Acting) pattern, enabling large language models to interleave analytical steps with external tool calls. Understanding how this agent handles **tool selection and execution in AgentScope** requires examining the interaction between the agent's reasoning loop, the LLM's tool-choice configuration, and the Toolkit's function registry.

## ReActAgent Architecture and Core Components

The implementation spans several key modules within the codebase. The primary logic resides in [`src/agentscope/agent/_react_agent.py`](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/agent/_react_agent.py), which extends `ReActAgentBase` from [`src/agentscope/agent/_react_agent_base.py`](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/agent/_react_agent_base.py). Tool management is centralized in [`src/agentscope/tool/_toolkit.py`](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/tool/_toolkit.py), while message structures including `ToolUseBlock` and `ToolResultBlock` are defined in [`src/agentscope/message.py`](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/message.py).

At the heart of the agent lie the `_reasoning` and `_acting` methods. The `_reasoning` method interfaces with the LLM to determine whether tool usage is necessary, while `_acting` handles the actual invocation and result processing. This separation allows the agent to maintain coherent memory while executing arbitrary tool functions.

## Controlling Tool Selection with tool_choice

Tool selection begins before the LLM generates a response through the `tool_choice` parameter. This local variable dictates whether the model must, may, or must not emit tool calls.

### Structured Output Enforcement

When the agent is initialized with a **structured-output model**, the code forces `tool_choice` to `"required"` to ensure the model calls a specific finish function that returns structured data. This mechanism appears in [`src/agentscope/agent/_react_agent.py`](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/agent/_react_agent.py) between lines 405 and 423:

```python
tool_choice: Literal["auto", "none", "required"] | None = None
...
if structured_model:
    self.toolkit.register_tool_function(getattr(self, self.finish_function_name))
    self.toolkit.set_extended_model(self.finish_function_name, structured_model)
    tool_choice = "required"
else:
    self.toolkit.remove_tool_function(self.finish_function_name)

```

### Default Auto Mode

Without structured output requirements, `tool_choice` defaults to `None` (equivalent to "auto"), allowing the LLM to decide whether to emit `tool_use` blocks based on the conversation context. The value is passed directly to the model API via `self.model(..., tool_choice=tool_choice)` within the `_reasoning` method.

## The Reasoning-Acting Loop Execution

Each iteration of the agent's main loop follows a strict sequence to process tool calls efficiently.

### Parallel and Sequential Tool Execution

After obtaining a response from `_reasoning`, the agent extracts all `tool_use` blocks and dispatches them to `_acting`. The execution mode depends on `self.parallel_tool_calls`, which determines whether tools run concurrently or sequentially. This orchestration occurs in [`src/agentscope/agent/_react_agent.py`](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/agent/_react_agent.py) around lines 428-445:

```python
msg_reasoning = await self._reasoning(tool_choice)
futures = [
    self._acting(tool_call)
    for tool_call in msg_reasoning.get_content_blocks("tool_use")
]

```

### The _acting Method Implementation

The `_acting` method constructs a `ToolResultBlock` to track the execution state, then delegates the actual function call to the Toolkit. It also handles special logic for the finish function, extracting structured metadata when the task completes. The implementation in [`src/agentscope/agent/_react_agent.py`](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/agent/_react_agent.py) (lines 555-573) appears as follows:

```python
async def _acting(self, tool_call: ToolUseBlock) -> dict | None:
    tool_res_msg = Msg(
        "system",
        [ToolResultBlock(type="tool_result", id=tool_call["id"],
                        name=tool_call["name"], output=[])],
        "system",
    )
    tool_res = await self.toolkit.call_tool_function(tool_call)
    async for chunk in tool_res:
        tool_res_msg.content[0]["output"] = chunk.content
        await self.print(tool_res_msg, chunk.is_last)
        if (tool_call["name"] == self.finish_function_name
            and chunk.metadata and chunk.metadata.get("success", False)):
            return chunk.metadata.get("structured_output")

```

## Toolkit Responsibilities for Tool Execution

The `Toolkit` class in [`src/agentscope/tool/_toolkit.py`](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/tool/_toolkit.py) serves as the execution engine, managing registration, validation, and invocation of tool functions.

### Function Registration and Schema Generation

When tools are added via `register_tool_function`, the Toolkit parses the function's docstring to generate a JSON schema compatible with OpenAI-style function calling. These definitions are stored in `self.tools` for runtime lookup.

### Runtime Execution and Validation

The `call_tool_function` method (lines 93-111) handles the complete execution pipeline:

- **Validation**: Verifies the requested tool exists in the registry
- **Group Checking**: Ensures the tool's assigned group is currently active (non-basic groups require explicit activation)
- **Parameter Merging**: Combines preset kwargs defined at registration with runtime arguments from the LLM
- **Execution**: Handles synchronous, asynchronous, or generator-based functions uniformly
- **Streaming**: Returns an async generator of `ToolResponse` objects to support real-time output

```python
async def call_tool_function(self, tool_call: ToolUseBlock) -> AsyncGenerator[ToolResponse, None]:
    if tool_call["name"] not in self.tools:
        return _object_wrapper(ToolResponse(...), None)
    tool_func = self.tools[tool_call["name"]]
    if tool_func.group != "basic" and not self.groups[tool_func.group].active:
        return _object_wrapper(ToolResponse(...), None)
    kwargs = {**tool_func.preset_kwargs, **(tool_call.get("input", {}) or {})}
    ...

```

## Dynamic Tool Group Management

AgentScope supports runtime modification of available tools through the `reset_equipped_tools` meta-tool. This function, defined in [`src/agentscope/tool/_toolkit.py`](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/tool/_toolkit.py) (lines 27-63), enables agents to activate or deactivate tool groups based on context:

```python
def reset_equipped_tools(self, **kwargs) -> ToolResponse:
    self.update_tool_groups(list(self.groups.keys()), active=False)
    to_activate = [k for k, v in kwargs.items() if v]
    self.update_tool_groups(to_activate, active=True)

```

When groups are toggled, the system regenerates the tool descriptions injected into the LLM's system prompt, ensuring the model only attempts to call currently available functions.

## Complete Implementation Example

The following example demonstrates registering a custom tool, configuring the ReActAgent with structured output support, and executing a query:

```python
from agentscope.agent import ReActAgent
from agentscope.tool import Toolkit
from agentscope.model import OpenAIChatModel   # any ChatModelBase implementation

from agentscope.formatter import OpenAIFormatter

# 1️⃣  Create a toolkit with a simple tool

toolkit = Toolkit()
def get_current_time():
    """Return the current UTC time as a string."""
    import datetime
    return datetime.datetime.utcnow().isoformat()
toolkit.register_tool_function(get_current_time)

# 2️⃣  Build the ReAct agent

agent = ReActAgent(
    name="assistant",
    sys_prompt="You are a helpful assistant.",
    model=OpenAIChatModel(model="gpt-4o-mini"),
    formatter=OpenAIFormatter(),
    toolkit=toolkit,
    max_iters=5,
)

# 3️⃣  Ask a question that requires the tool

reply = await agent.reply(
    Msg("user", "What time is it now?"),
)

print(reply.get_text_content())   # → the LLM will call `get_current_time` and embed the result

```

This workflow illustrates how the agent automatically selects the appropriate tool through the reasoning loop and streams the result back to the conversation.

## Summary

- **The ReActAgent** in AgentScope implements the reasoning-acting pattern through coordinated `_reasoning` and `_acting` methods in [`src/agentscope/agent/_react_agent.py`](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/agent/_react_agent.py).
- **Tool selection** is controlled via the `tool_choice` parameter, which defaults to "auto" but becomes "required" when structured output models are configured.
- **Parallel execution** is supported through `parallel_tool_calls`, allowing multiple tools to run simultaneously when the LLM requests them.
- **The Toolkit** in [`src/agentscope/tool/_toolkit.py`](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/tool/_toolkit.py) manages function registration, group-based activation, and unified execution across sync, async, and generator-based tools.
- **Dynamic tool management** allows runtime modification of available tool groups via `reset_equipped_tools`, with automatic updates to the system prompt context.

## Frequently Asked Questions

### How does ReActAgent decide when to use a tool?

The agent relies on the LLM's judgment when `tool_choice` is set to `None` (auto mode). The model analyzes the conversation context and emits `tool_use` blocks when it determines external data or computation is necessary. For structured output scenarios, the code forces `tool_choice` to `"required"`, mandating a tool call to ensure valid response formatting.

### What happens if a tool belongs to an inactive group?

The `Toolkit.call_tool_function` method checks the activation status of non-basic tool groups before execution. If a tool's group is inactive, the method returns an error response wrapped in a `ToolResponse` object without invoking the function, effectively preventing unauthorized tool usage during specific agent states.

### Can ReActAgent execute multiple tools simultaneously?

Yes. When `self.parallel_tool_calls` is enabled, the agent collects all `tool_use` blocks from a reasoning step and dispatches them concurrently. The results stream back individually through async generators, allowing the agent to process outputs as they complete rather than sequentially.

### How are tool results formatted for the LLM?

The `_acting` method constructs `ToolResultBlock` objects that encapsulate the tool's output, execution ID, and name. These blocks are wrapped in `Msg` objects with role "system" and streamed back to the model conversation history, enabling subsequent reasoning steps to reference the tool's output naturally.