Writing Tools¶
The @symfonic_tool decorator turns a plain Python function into a
framework-native tool — schema, description, and symfonic metadata included —
that ToolRegistry accepts with no extra plumbing.
Quick start¶
from symfonic.core import symfonic_tool
@symfonic_tool()
async def get_weather(city: str) -> str:
"""Return the current weather for a city.
Args:
city: Name of the city to look up.
"""
return f"Sunny in {city}."
That's it. The decorated object is a LangChain StructuredTool, so it works
anywhere a tool is expected:
No feature flag is required — @symfonic_tool is GA.
What the decorator does for you¶
- Derives the JSON schema from your type hints and docstring (delegated to
LangChain's
StructuredTool.from_function(parse_docstring=True)). Primitives,PydanticBaseModelparameters,Optional/Union, andAnnotated[T, "description"]all work. - Uses the docstring as the tool description and lifts
Args:entries into per-field descriptions — the text the model reads to decide how to call it. - Preserves async / sync / async-generator semantics. Async generators
remain detectable by
inspect.isasyncgenfunction, so streaming tools keep working.
from typing import Annotated
@symfonic_tool()
async def search(
topic: Annotated[str, "What to search for."],
top_k: int = 5,
) -> list[str]:
"""Search the knowledge base."""
...
Symfonic metadata¶
Beyond the schema, the decorator attaches symfonic-specific metadata that the
registry lifts into the tool's ToolRegistration:
@symfonic_tool(
scope="admin", # "public" (default) | "internal" | "admin"
routing_mode="enforce", # "observe" (default) | "enforce" | "off"
visibility="hidden", # "visible" (default) | "hidden"
name="rotate_keys", # defaults to the function name
description="...", # defaults to the docstring
)
async def rotate_keys() -> None:
"""Rotate the tenant's API keys."""
...
| Field | Effect |
|---|---|
scope |
Logical visibility scope. Captured for policy code; not yet enforced at the invocation tier. |
routing_mode |
Per-tool override for dynamic tool routing. enforce lets the router narrow the manifest; off opts the tool out of routing. See Intent & Tool Routing. |
visibility |
hidden sets visible_to_agents=False — the tool is bound to the runtime and callable by orchestration code but never offered to the model. Useful for telemetry emitters or feature-flag readers. |
Introspect metadata on any decorated tool:
from symfonic.core.tools import is_symfonic_tool, get_symfonic_metadata
is_symfonic_tool(rotate_keys) # True
get_symfonic_metadata(rotate_keys).scope # "admin"
Relationship to plain LangChain tools¶
@symfonic_tool is additive, not a replacement. Vanilla LangChain @tool
functions still register and run unchanged — the registry only lifts extra
metadata when it is present. Reach for @symfonic_tool when you want
symfonic's scope/routing/visibility controls; use plain @tool when you don't.
Deprecated flag¶
Earlier releases gated the decorator behind
FrameworkConfig.experimental_tool_decorator. That flag is now a deprecated
no-op — its value is ignored and the decorator is always available. It remains
on the config only so existing constructions do not error, and will be removed
in a future major version.
Related¶
- Capabilities — the tool/capability model
- Structured Output — typed final answers
- Intent & Tool Routing — how
routing_modeparticipates in per-turn tool selection