Skip to content

@tool_span

Wraps a tool or function call with an OpenInference TOOL span. Captures the tool name, description, and parameters as span attributes.

Decorator only

@tool_span is a decorator only -- there is no context manager alternative. The attribute-setting logic is straightforward (name, description, and serialized kwargs), so a context manager is not needed.


Signature

def tool_span(
    name: str | None = None,
    description: str | None = None,
) -> Callable
interface ToolSpanOptions {
    name: string;
    description?: string;
}

function toolSpan<A extends unknown[], R>(
    options: ToolSpanOptions,
    fn: (...args: A) => R | Promise<R>,
): (...args: A) => Promise<R>

Wrapper function pattern

In TypeScript, toolSpan() wraps a function and returns a new function with the same signature. The name option is required (not optional like in Python).


Parameters

Parameter Type Default Description
name str \| None None Tool name and span name. Defaults to the decorated function's name.
description str \| None None Human-readable description of what the tool does. Recorded as a span attribute.

Span Attributes

Attribute Value
openinference.span.kind "TOOL"
tool.name The tool name (from name parameter or function name)
tool.description Tool description (set when description parameter is provided)
tool.parameters JSON-encoded keyword arguments passed to the function

Parameter serialization

Only keyword arguments are captured in tool.parameters. Positional arguments are not recorded. If serialization fails (e.g., non-JSON-serializable types), the attribute is silently skipped.


Examples

Basic tool call

from coalex.ext.tool import tool_span

@tool_span(name="calculate_bmi", description="Calculate Body Mass Index from height and weight")
def calculate_bmi(weight_kg: float, height_m: float) -> float:
    return weight_kg / (height_m ** 2)

# Usage
bmi = calculate_bmi(weight_kg=75.0, height_m=1.80)
# Span "calculate_bmi" is emitted with:
#   openinference.span.kind = "TOOL"
#   tool.name = "calculate_bmi"
#   tool.description = "Calculate Body Mass Index from height and weight"
#   tool.parameters = '{"weight_kg": 75.0, "height_m": 1.8}'
import { toolSpan } from "@coalex-ai/sdk/ext";

const calculateBmi = toolSpan(
    { name: "calculate_bmi", description: "Calculate Body Mass Index from height and weight" },
    async (weightKg: number, heightM: number): Promise<number> => {
        return weightKg / (heightM ** 2);
    },
);

// Usage
const bmi = await calculateBmi(75.0, 1.80);
// Span "calculate_bmi" is emitted with:
//   openinference.span.kind = "TOOL"
//   tool.name = "calculate_bmi"
//   tool.description = "Calculate Body Mass Index from height and weight"
//   tool.parameters = '[75, 1.8]'

API call tool

@tool_span(name="get_patient_record", description="Fetch patient record from EHR system")
def get_patient_record(patient_id: str, include_history: bool = True) -> dict:
    response = ehr_client.get(f"/patients/{patient_id}", params={"history": include_history})
    return response.json()

record = get_patient_record(patient_id="P-12345", include_history=True)
# tool.parameters = '{"patient_id": "P-12345", "include_history": true}'
const getPatientRecord = toolSpan(
    { name: "get_patient_record", description: "Fetch patient record from EHR system" },
    async (patientId: string, includeHistory = true): Promise<Record<string, unknown>> => {
        const resp = await ehrClient.get(`/patients/${patientId}`, { params: { history: includeHistory } });
        return resp.data;
    },
);

const record = await getPatientRecord("P-12345", true);
// tool.parameters = '["P-12345", true]'

Database query tool

@tool_span(name="query_claims", description="Search insurance claims database")
def query_claims(policy_number: str, date_from: str, date_to: str) -> list[dict]:
    return db.execute(
        "SELECT * FROM claims WHERE policy = ? AND date BETWEEN ? AND ?",
        [policy_number, date_from, date_to],
    ).fetchall()
const queryClaims = toolSpan(
    { name: "query_claims", description: "Search insurance claims database" },
    async (policyNumber: string, dateFrom: string, dateTo: string): Promise<Record<string, unknown>[]> => {
        return db.query(
            "SELECT * FROM claims WHERE policy = $1 AND date BETWEEN $2 AND $3",
            [policyNumber, dateFrom, dateTo],
        );
    },
);

Async tool

@tool_span(name="fetch_weather", description="Get current weather from API")
async def fetch_weather(city: str, units: str = "metric") -> dict:
    async with httpx.AsyncClient() as client:
        resp = await client.get(f"https://api.weather.com/current?city={city}&units={units}")
        return resp.json()
const fetchWeather = toolSpan(
    { name: "fetch_weather", description: "Get current weather from API" },
    async (city: string, units = "metric"): Promise<Record<string, unknown>> => {
        const resp = await fetch(`https://api.weather.com/current?city=${city}&units=${units}`);
        return resp.json();
    },
);

Agent Tool Pattern

A common pattern is to use @tool_span / toolSpan() with LLM agents that call tools based on model output:

import coalex
from coalex.ext.tool import tool_span
from openai import OpenAI

coalex.register(api_key="your-key")
coalex.auto_instrument()

client = OpenAI()

@tool_span(name="search_knowledge_base", description="Search internal knowledge base")
def search_kb(query: str) -> str:
    results = kb_client.search(query, top_k=3)
    return "\n".join(r.content for r in results)

@tool_span(name="get_account_balance", description="Get customer account balance")
def get_balance(account_id: str) -> dict:
    return billing_api.get_balance(account_id)

@tool_span(name="create_support_ticket", description="Create a support ticket")
def create_ticket(subject: str, description: str, priority: str = "medium") -> str:
    ticket = ticketing_api.create(subject=subject, description=description, priority=priority)
    return ticket.id

# Map tool names to functions
tools = {
    "search_knowledge_base": search_kb,
    "get_account_balance": get_balance,
    "create_support_ticket": create_ticket,
}

with coalex.coalex_context(agent_id="support-agent", request_id="req-001"):
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": "What is my account balance?"}],
        tools=[...],  # OpenAI function calling schema
    )

    # Execute the tool call
    tool_call = response.choices[0].message.tool_calls[0]
    tool_fn = tools[tool_call.function.name]
    result = tool_fn(**json.loads(tool_call.function.arguments))

    # Trace:
    #   coalex.invocation
    #     ├── openai.chat.completions (LLM)
    #     └── get_account_balance (TOOL)
    #           tool.parameters = '{"account_id": "ACC-789"}'
import { register, coalexContext, autoInstrument } from "@coalex-ai/sdk";
import { toolSpan } from "@coalex-ai/sdk/ext";
import OpenAI from "openai";

register({ apiKey: "your-key" });
autoInstrument();

const client = new OpenAI();

const searchKb = toolSpan(
    { name: "search_knowledge_base", description: "Search internal knowledge base" },
    async (query: string): Promise<string> => {
        const results = await kbClient.search(query, { topK: 3 });
        return results.map(r => r.content).join("\n");
    },
);

const getBalance = toolSpan(
    { name: "get_account_balance", description: "Get customer account balance" },
    async (accountId: string): Promise<Record<string, unknown>> => {
        return billingApi.getBalance(accountId);
    },
);

const createTicket = toolSpan(
    { name: "create_support_ticket", description: "Create a support ticket" },
    async (subject: string, description: string, priority = "medium"): Promise<string> => {
        const ticket = await ticketingApi.create({ subject, description, priority });
        return ticket.id;
    },
);

// Map tool names to functions
const tools: Record<string, (...args: any[]) => Promise<any>> = {
    search_knowledge_base: searchKb,
    get_account_balance: getBalance,
    create_support_ticket: createTicket,
};

await coalexContext({ agentId: "support-agent", requestId: "req-001" }, async () => {
    const response = await client.chat.completions.create({
        model: "gpt-4o",
        messages: [{ role: "user", content: "What is my account balance?" }],
        tools: [...], // OpenAI function calling schema
    });

    // Execute the tool call
    const toolCall = response.choices[0].message.tool_calls![0];
    const toolFn = tools[toolCall.function.name];
    const args = JSON.parse(toolCall.function.arguments);
    const result = await toolFn(...Object.values(args));

    // Trace:
    //   coalex.invocation
    //     ├── openai.chat.completions (LLM)
    //     └── get_account_balance (TOOL)
    //           tool.parameters = '["ACC-789"]'
});

API Reference

coalex.ext.tool

Tool span decorator.

Functions

tool_span

tool_span(
    name: str | None = None, description: str | None = None
) -> Callable[[F], F]

Decorator that wraps a tool function with an OpenInference TOOL span.

Supports both sync and async functions. The span captures: - openinference.span.kind = TOOL - tool.name — span name - tool.description — if provided - tool.parameters — JSON-encoded kwargs passed to the function

Parameters:

Name Type Description Default
name str | None

Tool name and span name. Defaults to the function name.

None
description str | None

Human-readable tool description.

None
Source code in coalex/ext/tool.py
def tool_span(
    name: str | None = None,
    description: str | None = None,
) -> Callable[[F], F]:
    """Decorator that wraps a tool function with an OpenInference TOOL span.

    Supports both sync and async functions. The span captures:
    - ``openinference.span.kind`` = ``TOOL``
    - ``tool.name`` — span name
    - ``tool.description`` — if provided
    - ``tool.parameters`` — JSON-encoded kwargs passed to the function

    Args:
        name: Tool name and span name. Defaults to the function name.
        description: Human-readable tool description.
    """

    def decorator(fn: F) -> F:
        span_name = name or fn.__name__

        if asyncio.iscoroutinefunction(fn):

            @functools.wraps(fn)
            async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
                tracer = _get_tracer()
                parent_ctx = _get_parent_context()
                with tracer.start_as_current_span(span_name, context=parent_ctx) as span:
                    span.set_attribute("openinference.span.kind", "TOOL")
                    span.set_attribute("tool.name", span_name)
                    if description:
                        span.set_attribute("tool.description", description)
                    if kwargs:
                        with contextlib.suppress(TypeError, ValueError):
                            span.set_attribute("tool.parameters", json.dumps(kwargs))
                    return await fn(*args, **kwargs)

            return async_wrapper  # type: ignore[return-value]

        @functools.wraps(fn)
        def wrapper(*args: Any, **kwargs: Any) -> Any:
            tracer = _get_tracer()
            parent_ctx = _get_parent_context()
            with tracer.start_as_current_span(span_name, context=parent_ctx) as span:
                span.set_attribute("openinference.span.kind", "TOOL")
                span.set_attribute("tool.name", span_name)
                if description:
                    span.set_attribute("tool.description", description)
                if kwargs:
                    with contextlib.suppress(TypeError, ValueError):
                        span.set_attribute("tool.parameters", json.dumps(kwargs))
                return fn(*args, **kwargs)

        return wrapper  # type: ignore[return-value]

    return decorator