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

# Tool calls

> Record tool invocations as child spans with arguments and results

A **tool call** is a single tool invocation inside a trace — its name, arguments, result, and success or failure. Typing a span as a tool tells Lemma to show it as a tool execution with its input and output.

Tool execution usually happens in your application code, so framework auto-instrumentation often cannot see it. Recording tool calls explicitly is one of the highest-value things you can do.

Create tool spans **inside** the trace root callback so they nest under the trace.

## Record a tool call

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    import { startActiveObservation } from "@langfuse/tracing";

    const docs = await startActiveObservation(
      "search_docs",
      async (tool) => {
        const result = await searchDocs(query);
        tool.update({ input: { query }, output: result });
        return result;
      },
      { asType: "tool" },
    );
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    from langfuse import get_client

    langfuse = get_client()

    with langfuse.start_as_current_observation(name="search_docs", as_type="tool") as tool:
        result = search_docs(query)
        tool.update(input={"query": query}, output=result)
    ```

    The observation name is the tool name.
  </Tab>
</Tabs>

The observation **name** is the tool name. Record the arguments as `input` and the result as `output`.

## Record failures

A tool that fails is exactly what you will want to debug later. Mark it:

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    await startActiveObservation(
      "lookup_customer",
      async (tool) => {
        tool.update({ input: { customerId } });
        try {
          const customer = await lookupCustomer(customerId);
          tool.update({ output: customer });
          return customer;
        } catch (error) {
          tool.update({
            level: "ERROR",
            statusMessage: error instanceof Error ? error.message : String(error),
          });
          throw error;
        }
      },
      { asType: "tool" },
    );
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    with langfuse.start_as_current_observation(name="lookup_customer", as_type="tool") as tool:
        tool.update(input={"customer_id": customer_id})
        try:
            customer = lookup_customer(customer_id)
            tool.update(output=customer)
        except Exception as error:
            tool.update(level="ERROR", status_message=str(error))
            raise
    ```
  </Tab>
</Tabs>

## Wrapping a tool registry

If your agent runs many tools, wrap the executor once so every tool call is traced consistently:

```typescript theme={null}
import { startActiveObservation } from "@langfuse/tracing";

async function runTool<T>(name: string, args: unknown, fn: () => Promise<T>): Promise<T> {
  return startActiveObservation(
    name,
    async (tool) => {
      tool.update({ input: args });
      const output = await fn();
      tool.update({ output });
      return output;
    },
    { asType: "tool" },
  );
}

// usage
const docs = await runTool("search_docs", { query }, () => searchDocs(query));
```

<Warning>
  Capture arguments and results only when safe. Redact secrets, credentials, and sensitive user data before passing them to `input` / `output`.
</Warning>

## Next steps

<CardGroup cols={2}>
  <Card title="Spans" icon="box" href="/tracing/instrumentation/spans">
    Trace retrieval, ranking, and app logic.
  </Card>

  <Card title="Threads & context" icon="users" href="/tracing/instrumentation/context">
    Group conversations and attach users and metadata.
  </Card>
</CardGroup>
