Research date · May 25, 2026
Generated with AI

How AI Agent Frameworks Wire In Tools and Agents

A code-first reference across ten AI agent frameworks — spanning Python, TypeScript, .NET, Java, Go, and Rust — covering the documented mechanisms for declaring tools, defining their schemas, and composing multiple agents. Every code snippet is taken from the framework's own documentation.

The universal wiring pattern

Underneath the differences in syntax, every framework on this list ships the same three primitives: a way to describe a callable to the model (name, description, JSON schema), a way to bind those callables to an agent, and a way to compose multiple agents — by exposing one agent as a tool to another, by handing off control via a structured action, or by orchestrating them with a parent process.

1 · DescribeTool definition

A decorator, a class, a macro, or a builder produces a four-field object: a unique name, a description, a JSON schema for arguments, and the callable itself.

2 · BindAttach to an agent

The agent receives a list (or map) of tools at construction time. Some frameworks bind tools to the model directly; others register them on a kernel, a graph node, or a tool server.

3 · ComposeMulti-agent wiring

Three composition patterns appear across the ten frameworks: agent-as-tool, transfer-of-control, and hierarchical composition. Each framework supports at least one; several support more than one.

4 · LoopWho runs the call

The agent loop is run by the framework (most), by the kernel (Semantic Kernel), or by the application code with a convenience opt-out (Genkit Go, Rig).

Ten frameworks, ten dialects

Same primitives, ten different ways of expressing them. Each card pairs a canonical tool-wiring snippet from the framework's own docs with a prose breakdown and the agent-wiring snippet that goes with it.

Highlighted spans mark exactly where a tool or sub-agent is hooked into the caller agent.


Lc

LangChain · the @tool decorator + create_agent Python

Type-hint-driven schemas, looped by the prebuilt agent factory.
Tool definition
from langchain.tools import tool

@tool
def search_database(query: str, limit: int = 10) -> str:
    """Search the customer database for records matching the query.

    Args:
        query: Search terms to look for
        limit: Maximum number of results to return
    """
    return f"Found {limit} results for '{query}'"
Bind to an agent
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI

agent = create_agent(
    ChatOpenAI(model="gpt-5.4"),
    tools=[search_database],
    system_prompt="You are a helpful assistant.",
)
agent.invoke({"messages": [
    {"role": "user", "content": "find acme orders"},
]})
A Python function decorated with @tool is passed in a list to create_agent, which binds the tools to the model and runs the loop.

How tool wiring works

The @tool decorator turns a Python function into a LangChain tool. Type hints become the JSON schema the model sees; the docstring becomes the description used for selection. For more control, subclass BaseTool or pass a Pydantic args_schema.

How agent wiring works

create_agent (the v1 successor to AgentExecutor) binds the tools to the model and runs the loop. Multi-agent in LangChain is "agent-as-tool" — wrap a sub-agent and add it to the parent's tools list.

Mechanism

Function-level decorators; the agent factory manages the tool-call loop internally.

Notes

Multi-agent is not a first-class primitive; the documented path for composition is LangGraph.

Lg

LangGraph · ToolNode in a state graph (or prebuilt create_react_agent) Python

Tools become a graph node; multi-agent is graph composition.
Prebuilt ReAct agent
from langgraph.prebuilt import create_react_agent

def check_weather(location: str) -> str:
    '''Return the weather forecast for the specified location.'''
    return f"It's always sunny in {location}"

graph = create_react_agent(
    "anthropic:claude-3-7-sonnet-latest",
    tools=[check_weather],
    prompt="You are a helpful assistant",
)
for chunk in graph.stream(
    {"messages": [{"role": "user", "content": "weather in sf"}]},
    stream_mode="updates",
):
    print(chunk)
Custom graph with ToolNode
from langgraph.graph import StateGraph, MessagesState, START
from langgraph.prebuilt import ToolNode, tools_condition

builder = StateGraph(MessagesState)
builder.add_node("llm", call_llm)
builder.add_node("tools", ToolNode([check_weather]))
builder.add_edge(START, "llm")
builder.add_conditional_edges("llm", tools_condition)
builder.add_edge("tools", "llm")
graph = builder.compile()
Tools are executed by a graph node. ToolNode fans out every tool_call on the last AIMessage in parallel and writes the results back to the messages channel.

How tool wiring works

LangGraph reuses LangChain's @tool definitions. To dispatch them, you drop a prebuilt ToolNode into a StateGraph — it reads the last AIMessage, executes every tool_call (in parallel), and writes ToolMessages back into the messages channel.

How agent wiring works

The prebuilt create_react_agent constructs a two-node graph (agent ↔ tools) and terminates when the model returns no tool_calls. Multi-agent composition is expressed by wiring sub-agent graphs as nodes within a supervisor graph.

Mechanism

A graph node executes every tool call; state is checkpointable and human-in-the-loop interrupts are supported.

Notes

Logic is expressed as nodes, edges, and reducers rather than as imperative scripts.

Cr

CrewAI · Pydantic-typed BaseTool, agents inside a Crew Python

Per-agent toolbelt; outer Crew/Process orchestrates the team.
Tool definition
from crewai.tools import BaseTool
from pydantic import BaseModel, Field
from typing import Type

class MyToolInput(BaseModel):
    """Input schema for MyCustomTool."""
    argument: str = Field(..., description="Description of the argument.")

class MyCustomTool(BaseTool):
    name: str = "Name of my tool"
    description: str = "What this tool does. It's vital for effective utilization."
    args_schema: Type[BaseModel] = MyToolInput

    def _run(self, argument: str) -> str:
        return "Tool's result"
Crew & Agent wiring
from crewai import Agent, Task, Crew
from crewai_tools import SerperDevTool, WebsiteSearchTool

researcher = Agent(
    role='Market Research Analyst',
    goal='Provide up-to-date market analysis.',
    backstory='An expert analyst with a keen eye for trends.',
    tools=[SerperDevTool(), WebsiteSearchTool()],
)

research = Task(
    description='Research the latest trends in the AI industry.',
    expected_output='Top 3 trending developments with significance.',
    agent=researcher,
)

crew = Crew(agents=[researcher], tasks=[research], planning=True)
crew.kickoff()
Tools are BaseTool subclasses attached to each Agent's tools= list. A Crew owns multiple agents and tasks; a Process (sequential or hierarchical) determines execution order.

How tool wiring works

A custom tool is a BaseTool subclass with a Pydantic args_schema and a _run method. There's also a lighter @tool decorator and an automatic tool-result cache; you can tune the cache with cache_function.

How agent wiring works

Tools are attached to an Agent's tools=[...] field. A Crew owns multiple agents and tasks; the Process (sequential or hierarchical) decides who runs when. In hierarchical mode a manager LLM gets DelegateWorkTools and picks the next worker.

Mechanism

Each agent has a role, goal, and backstory. An outer crew loop runs the per-agent inner loop.

Notes

Hierarchical Process mode invokes a manager LLM once per delegation.

Ad

Google ADK · plain functions auto-wrapped as FunctionTool, sub-agents via transfer_to_agent Python

Agent algebra (Sequential / Parallel / Loop) + structured transfer of control.
Tool definition + LlmAgent
from google.adk.agents import LlmAgent

def get_weather(location: str) -> dict:
    """Return the current weather for a city."""
    return {"location": location, "tempC": 22, "conditions": "sunny"}

weather_agent = LlmAgent(
    name="weather_agent",
    model="gemini-2.0-flash",
    instruction="Use the get_weather tool to answer weather questions.",
    tools=[get_weather],   # auto-wrapped as FunctionTool
)
Multi-agent: transfer + composition
from google.adk.agents import LlmAgent, SequentialAgent
from google.adk.tools import ToolContext

def check_and_transfer(query: str, tool_context: ToolContext) -> str:
    """Inspect the query and delegate to support if urgent."""
    if "urgent" in query.lower():
        tool_context.actions.transfer_to_agent = "support_agent"
        return "Transferring to support."
    return "No transfer needed."

coordinator = LlmAgent(
    name="coordinator",
    model="gemini-2.0-flash",
    tools=[check_and_transfer],
    sub_agents=[support_agent],
)

pipeline = SequentialAgent(
    name="pipeline",
    sub_agents=[coordinator, summariser_agent],
)
A native Python function passed to LlmAgent(tools=[…]) is auto-wrapped as a FunctionTool. Agents are composed into trees; tools can hand off control to siblings via tool_context.actions.transfer_to_agent.

How tool wiring works

Any native function (or method) passed to LlmAgent(tools=[...]) is automatically wrapped as a FunctionTool. The function signature, type hints, and docstring become the schema and description the model sees. ADK also ships prebuilt tools (search, code execution, …) and supports OpenAPI/MCP tool servers.

How agent wiring works

ADK ships an agent algebra: SequentialAgent, ParallelAgent, LoopAgent, and the sub_agents= field on LlmAgent for delegate-on-demand. A tool can transfer control by setting tool_context.actions.transfer_to_agent.

Mechanism

Agents are composed into a tree; each LLM turn passes through a processor pipeline that emits typed Event records.

Notes

The processor pipeline has its own lifecycle and ordering rules that callers must follow when extending it.

Sk

Microsoft Semantic Kernel · @kernel_function on a plugin class Python

The Kernel auto-invokes plugin functions; multi-agent via AgentGroupChat.
Tool (plugin) definition + Kernel
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
from semantic_kernel.functions import kernel_function

class WeatherPlugin:
    @kernel_function(name="get_weather", description="Gets the weather for a city")
    def get_weather(self, city: str) -> str:
        return f"It is sunny in {city}."

kernel = Kernel()
kernel.add_service(OpenAIChatCompletion(service_id="default"))
kernel.add_plugin(WeatherPlugin(), plugin_name="weather")
Agent with auto function-calling
from semantic_kernel.agents import ChatCompletionAgent
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior

agent = ChatCompletionAgent(
    kernel=kernel,
    name="WeatherAssistant",
    instructions="Help the user with weather questions.",
    function_choice_behavior=FunctionChoiceBehavior.Auto(),
)
Plugins are classes of @kernel_function-decorated methods registered on the Kernel. With FunctionChoiceBehavior.Auto(), the Kernel resolves and invokes the requested function when the model emits a function call.

How tool wiring works

Tools in SK are @kernel_function-decorated methods grouped into a plugin class. Calling kernel.add_plugin(...) exposes every function on that class as a callable the model can request via native function-calling.

How agent wiring works

A ChatCompletionAgent wraps the Kernel; with FunctionChoiceBehavior.Auto(), the Kernel itself runs the function-call loop until no more calls are emitted or maximum_auto_invoke_attempts is hit. Multi-agent uses AgentGroupChat with a SelectionStrategy and a TerminationStrategy.

Mechanism

The Kernel is a dependency-injection container; plugins register both native and prompt-based functions.

Notes

Loop control is encapsulated in the Kernel; intervention points are exposed through filters and middleware.

Ma

Mastra · createTool with Zod schemas, agents via new Agent({ tools }) TypeScript

Subagents auto-exposed as agent-<key> tools.
Tool definition
import { createTool } from '@mastra/core/tools'
import { z } from 'zod'

export const weatherTool = createTool({
  id: 'weather-tool',
  description: 'Fetches weather for a location',
  inputSchema:  z.object({ location: z.string() }),
  outputSchema: z.object({ weather:  z.string() }),
  execute: async (inputData) => {
    const { location } = inputData
    const res = await fetch(`https://wttr.in/${location}?format=3`)
    return { weather: await res.text() }
  },
})
Supervisor with sub-agent
import { Agent } from '@mastra/core/agent'
import { weatherTool } from '../tools/weather-tool'

const writer = new Agent({
  id: 'writer', name: 'Writer',
  description: 'Drafts and edits written content',
  instructions: 'You are a skilled writer.',
  model: 'openai/gpt-5.4',
})

export const supervisor = new Agent({
  id: 'supervisor', name: 'Supervisor',
  instructions: 'Coordinate the writer and fetch weather.',
  model: 'openai/gpt-5.4',
  tools:  { weatherTool },
  agents: { writer },   // auto-exposed as "agent-writer"
})
Tools are typed object literals produced by createTool. Agents are Agent instances with tools and agents maps; subagents are auto-exposed as tools named agent-<key>.

How tool wiring works

Tools are created with createTool from @mastra/core/tools — an id, description, inputSchema, outputSchema, and an execute function. Schemas come from any Standard JSON Schema library (Zod, Valibot, ArkType).

How agent wiring works

Tools attach via the tools field on an Agent. Sub-agents attach via the agents field — Mastra automatically exposes each as a tool named agent-<key>, giving you a supervisor pattern out of the box. Workflows wire in identically as workflow-<key>.

Mechanism

Built on the Vercel AI SDK; supports deployment to edge runtimes (Cloudflare Workers, Vercel Edge, Deno Deploy).

Notes

Tool name in stream responses is derived from the object key, not the tool's id property; refactoring the key changes the name the model sees.

Mf

Microsoft Agent Framework · AIFunctionFactory.Create + ChatClientAgent .NET

Attribute-driven schemas, agent-on-any-IChatClient.
Tool definition
using System.ComponentModel;

[Description("Get the weather for a given location.")]
static string GetWeather(
    [Description("The location to get the weather for.")] string location)
    => $"The weather in {location} is cloudy with a high of 15°C.";
Agent wiring
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

AIAgent agent = new AIProjectClient(
        new Uri("<your-foundry-project-endpoint>"),
        new DefaultAzureCredential())
    .AsAIAgent(
        model: "gpt-4o-mini",
        instructions: "You are a helpful assistant",
        tools: [ AIFunctionFactory.Create(GetWeather) ]);

Console.WriteLine(
    await agent.RunAsync("What is the weather like in Amsterdam?"));
A C# method is annotated with [Description] and wrapped as an AIFunction via AIFunctionFactory.Create. The resulting list is passed to a ChatClientAgent (or to AsAIAgent) via the tools parameter.

How tool wiring works

Any C# method can be turned into a function tool by wrapping it with AIFunctionFactory.Create. [Description] attributes on the method and its parameters become the schema and the prose the model sees.

How agent wiring works

A ChatClientAgent (or the AsAIAgent convenience on a chat client) takes a tools list of AIFunction instances. The agent handles the function-calling loop and multi-turn history natively, integrating with ASP.NET Core hosting and DI.

Mechanism

Schemas are derived from [Description] attributes and reflection. Native integration with .NET dependency injection and OpenTelemetry.

Notes

Function tools are supported by ChatClientAgent; some other agent types only support built-in tools per the current documentation.

L4j

LangChain4j · @Tool-annotated methods, generated by AiServices Java

The agent is an interface; the framework supplies the implementation.
Tool definition
import dev.langchain4j.agent.tool.Tool;

static class Calculator {

    @Tool("Calculates the length of a string")
    int stringLength(String s) {
        return s.length();
    }

    @Tool("Calculates the sum of two numbers")
    int add(int a, int b) {
        return a + b;
    }
}
Agent wiring (interface + AiServices)
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.service.AiServices;

interface Assistant {
    String chat(String userMessage);
}

Assistant assistant = AiServices.builder(Assistant.class)
    .chatModel(model)
    .tools(new Calculator())
    .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
    .build();

String answer = assistant.chat(
    "What is the square root of the length of 'hello'?");
Tools are Java methods annotated with @Tool on any POJO. The agent is defined as a Java interface; AiServices generates the implementation and wires in the tool-bearing objects.

How tool wiring works

Tools are annotated methods on a regular Java class. The annotation value is the model-facing description; method parameters become the schema (optionally enriched with @P per-parameter descriptions). Tools can also be loaded dynamically via a ToolProvider.

How agent wiring works

Define your agent as a plain Java interface; AiServices generates the implementation, wires in @Tool-annotated objects, and manages chat memory. Agents-as-tools is the canonical multi-agent pattern.

Mechanism

Ships Spring Boot and Quarkus integration modules; ~80 vendor integration modules.

Notes

Tool schemas are derived via reflection; mismatches surface at runtime rather than at compile time.

Gk

Genkit · genkit.DefineTool with a typed input struct Go

Pass the tool reference into ai.WithTools; you can keep or hand back the loop.
Tool definition + use
package main

import (
    "context"
    "fmt"
    "log"

    "github.com/firebase/genkit/go/ai"
    "github.com/firebase/genkit/go/genkit"
    "github.com/firebase/genkit/go/plugins/googlegenai"
)

type WeatherInput struct {
    Location string `json:"location" jsonschema_description:"Location to get weather for"`
}

func main() {
    ctx := context.Background()
    g := genkit.Init(ctx,
        genkit.WithPlugins(&googlegenai.GoogleAI{}),
        genkit.WithDefaultModel("googleai/gemini-1.5-flash"),
    )

    getWeatherTool := genkit.DefineTool(
        g, "getWeather", "Gets the current weather in a given location",
        func(ctx *ai.ToolContext, input WeatherInput) (string, error) {
            return fmt.Sprintf("It is 63°F and sunny in %s.", input.Location), nil
        },
    )

    resp, err := genkit.Generate(ctx, g,
        ai.WithPrompt("What is the weather in San Francisco?"),
        ai.WithTools(getWeatherTool),
    )
    if err != nil { log.Fatal(err) }
    fmt.Println(resp.Text())
}
Tools are declared with genkit.DefineTool using a typed input struct. The returned tool reference is passed into genkit.Generate via ai.WithTools.

How tool wiring works

Tools are declared with genkit.DefineTool — a name, description, and a handler whose typed input struct provides the JSON schema via jsonschema_description struct tags. .prompt files can declare tool sets statically.

How agent wiring works

Genkit doesn't ship a dedicated "agent" type — agentic patterns are composed from flows, prompts, and tools. WithReturnToolRequests(true) hands the tool-call loop back to your code, which is the standard way to build multi-agent workflows.

Mechanism

Plain Go runtime; goroutine-based concurrency; native OpenTelemetry and slog structured logging.

Notes

No dedicated multi-agent primitives; agentic patterns are assembled from flows, prompts, and tools.

Rg

Rig · the Tool trait or #[rig_tool] macro, attached via the agent builder Rust

Share a ToolServer handle across multiple agents.
Tool definition (macro)
use rig_derive::rig_tool;

#[rig_tool(
    description = "Perform basic arithmetic operations",
    required(x, y, operation)
)]
fn calculator(
    x: i32, y: i32, operation: String,
) -> Result<i32, rig::tool::ToolError> {
    match operation.as_str() {
        "add"      => Ok(x + y),
        "subtract" => Ok(x - y),
        "multiply" => Ok(x * y),
        "divide" if y != 0 => Ok(x / y),
        "divide"   => Err(rig::tool::ToolError::ToolCallError(
            "Division by zero".into())),
        _ => Err(rig::tool::ToolError::ToolCallError(
            format!("Unknown operation: {operation}").into())),
    }
}
Agent wiring + shared ToolServer
let agent = client
    .agent("gpt-5.2")
    .preamble("You are a calculator.")
    .tool(Adder)
    .tool(Subtract)
    .build();

// Share a tool server between multiple agents
use rig::tool::server::{ToolServer, ToolServerHandle};

let tool_server: ToolServerHandle = ToolServer::new()
    .tool(Adder)
    .run();
Tools either implement the Tool trait directly or are generated by the #[rig_tool] macro. They are attached to an agent via .tool(...) on the agent builder, or shared across agents through a ToolServer handle.

How tool wiring works

For simple tools the rig_derive crate exposes #[rig_tool], which transforms a function into a rig::tool::Tool. For full control, implement the Tool trait by hand; schemars::JsonSchema can derive the parameter schema for you.

How agent wiring works

Tools attach to an agent through the fluent builder on a provider client (.tool(MyTool)). Multi-agent collaboration is done by sharing a ToolServerHandle across agents — a Tokio task that owns the tools and avoids shared-state locks.

Mechanism

Trait-based; compiles to a native Rust binary; runs on the Tokio async runtime.

Notes

Tool schemas are written manually or generated via the schemars::JsonSchema derive macro; both require more declaration than type-hint-based approaches.

Side-by-side · tool & agent wiring

Same primitives, ten dialects. Read across rows to see how each framework answers the same wiring question.

LangChain LangGraph CrewAI Google ADK Semantic Kernel Mastra MS Agent Fw LangChain4j Genkit Go Rig
Tool definition @tool on a function Inherits from LangChain BaseTool subclass Plain function @kernel_function on a plugin class createTool({...}) AIFunctionFactory.Create @Tool on a POJO method genkit.DefineTool(...) impl Tool / #[rig_tool]
Schema source Type hints + docstring Inherits from LangChain Pydantic args_schema Function sig + docstring Decorator metadata + type hints Zod / Valibot / ArkType [Description] + reflection Annotation values + reflection Struct tags (jsonschema_description) Manual JSON or schemars derive
Bind to agent create_agent(model, tools=[...]) create_react_agent / ToolNode Agent(tools=[...]) LlmAgent(tools=[...]) kernel.add_plugin(...) new Agent({ tools }) .AsAIAgent(..., tools) AiServices.builder(...).tools(...) ai.WithTools(t1, t2, ...) Agent builder .tool(MyTool)
Multi-agent Wrap an agent as a tool Compose StateGraphs Crew + Process (sequential / hierarchical) sub_agents + transfer_to_agent AgentGroupChat + selection / termination agents: { ... } auto-exposed Workflow primitives + DI Agents-as-tools; dynamic ToolProvider Compose flows + WithReturnToolRequests Shared ToolServer handle
Parallel tool calls Provider-dependent Native via ToolNode Serial Native (async) Optional via AllowParallelCalls Native (provider) Native (provider) Java 21+ virtual threads Goroutines Serial by default
MCP support Built-in Built-in Built-in Built-in (tool server) kernel.as_mcp_server Built-in (consume + serve) Built-in Built-in Built-in Built-in (via rmcp)
Doc anchor langchain/tools.py chat_agent_executor.py crewai/tools/base_tool.py function_tool.py kernel_function_decorator.py @mastra/core/tools AIFunctionFactory dev.langchain4j.service.AiServices genkit/tools.go rig::tool::Tool

Summary observations

Six factual observations drawn from the per-framework data above.

SHAPE

1 · The tool object has the same four fields in every framework

Decorators, macros, builders, and reflection all produce the same four-field object: a unique name, a description, a JSON schema for arguments, and a callable. The differences across frameworks are in how those four fields are declared, not in what the model receives.

SCHEMA

2 · Five distinct schema-source patterns are in use

Type hints plus docstring (LangChain, LangGraph, ADK); Pydantic or Zod model (CrewAI, Mastra); reflection plus attribute or annotation metadata (LangChain4j, MAF); struct tags (Genkit Go); derive macros or manual JSON (Rig).

CONTROL

3 · The tool-call loop is run by one of three actors

By the framework (LangChain, LangGraph, CrewAI, ADK, Mastra, MAF, LangChain4j); by the Kernel (Semantic Kernel); or by the application code with a convenience opt-out (Genkit Go via WithReturnToolRequests, Rig). Frameworks in the third category expose more intervention points to caller code.

SCALE

4 · Multi-agent composition uses one of three patterns

Agent-as-tool (LangChain, Mastra, LangChain4j) — a sub-agent is wrapped as a tool. Transfer-of-control (ADK, CrewAI hierarchical) — an active agent signals a switch to a named sibling. Composition (LangGraph, ADK workflow agents, SK AgentGroupChat) — a parent orchestrator schedules children.

PARALLELISM

5 · Parallel tool execution is available in four frameworks by default

LangGraph's ToolNode, ADK (async generators), and LangChain4j (with Java 21+ virtual threads) execute multiple tool calls concurrently by default. Mastra and MAF inherit parallelism from their provider SDK. CrewAI, Semantic Kernel, and Rig execute tool calls serially under default configuration.

PORTABILITY

6 · All ten frameworks support MCP as a tool source

Every framework on this list ships first-class MCP consumer support. Several also support exposing their own tools as an MCP server (e.g. Semantic Kernel's kernel.as_mcp_server). Tools defined as MCP servers are therefore portable across all ten frameworks without modification.

Mini-glossary

A few terms used above that mean different things in different frameworks.

Tool / Function / Plugin function — a callable the model can ask the framework to run, exposed by a JSON schema.
Tool call — the LLM's structured request to run a tool with named arguments (provider-native function calling).
FunctionChoiceBehavior (SK) — Auto / Required / None; tells the Kernel whether the model picks functions and whether to auto-invoke them.
ToolNode (LangGraph) — prebuilt node that executes every tool_call on the last AIMessage in parallel.
FunctionTool (ADK) — wrapper that turns a native function (or method) into a model-callable tool; auto-applied when you pass a function into LlmAgent(tools=[...]).
AIFunctionFactory (MAF) — .NET helper that turns any C# method into an AIFunction the agent can call.
AiServices (LangChain4j) — facade that generates an agent implementation from a Java interface and wires in @Tool-annotated objects.
ToolServer (Rig) — Tokio task that owns a set of tools and serves them to one or many agents via a handle, avoiding shared-state locks.
transfer_to_agent (ADK) — structured action a tool can emit to hand control to a named sibling agent.
Crew / Process (CrewAI) — outer-loop primitives: a Crew owns agents and tasks; a Process (sequential or hierarchical) decides the execution order.