Skip to main content

Overview

This guide walks you through building tools that work well with:
  • Schema-driven function calling
  • react_agent(...)
  • Tracing and debugging
The goal is to make tools:
  • Deterministic where possible
  • Easy to validate
  • Easy to observe

Defining tool schemas

Define a Pydantic schema for tool arguments. This schema becomes the JSON schema that the LLM sees.
from pydantic import BaseModel, Field

class SearchArgs(BaseModel):
    query: str = Field(..., description="Search query")
    top_k: int = Field(5, ge=1, le=20, description="Number of results")

Wrapping functions with tool_step

Tool functions typically read arguments from state (e.g. state["tool_args"]) and return a value.
from coevolved.core import tool_step

def search(state: dict) -> list[dict]:
    args = state["tool_args"]
    return [{"title": "example", "query": args["query"]}]

search_tool = tool_step(
    search,
    name="search",
    tool_schema=SearchArgs,
    result_key="tool_result",
)
For ReAct, result_key="tool_result" is the simplest choice because it matches the default react_agent contract.

Generating tool specs

Convert tool steps into tool specs and attach them to the planner’s LLMConfig:
from coevolved.core import LLMConfig, tool_specs_from_dict

tools = {"search": search_tool}
config = LLMConfig(model="gpt-4o-mini", tools=tool_specs_from_dict(tools))

Next steps