Cookbook: LLM Tool Integration
On this page
Practical recipes for wiring plugins into an LLM agent loop, all built strictly from what the source
supports.
Recipe: Anthropic tool-use loop
import anthropic
from zerda.plugins import PluginManager, ExecutionContext
manager = PluginManager(plugin_dirs=["./plugins"])
await manager.startup()
client = anthropic.Anthropic()
tools = manager.anthropic_tools() # PluginRegistry.to_anthropic_tools(), enabled plugins only
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "Search for recent RAG papers."}],
)
for block in response.content:
if block.type == "tool_use":
result = await manager.execute(block.name, block.input, ExecutionContext())
# feed `result` back as a tool_result content block in your next message
Since PluginRegistry.to_anthropic_tools() filters to config.enabled plugins only, disabling a plugin
(plugin.config.enabled = False) is enough to hide it from the LLM without unregistering it.
Recipe: let the framework pick the plugin instead of the LLM
If you don't want the LLM to see raw tool schemas at all — e.g. a simpler assistant that just routes a
free-text request to the right plugin — use auto_execute instead of exposing tools:
result = await manager.auto_execute("summarize this document for me")
This is select_one() + execute() in one call, using the keyword (and optionally semantic) scoring
described in AIPluginSelector. It returns None (not an
exception) if no plugin scores above zero for the query, so always check for None before using the
result.
Recipe: cost-aware selection
from zerda.plugins import CostTier
cheap_plugin = manager.select_one("search the web", max_cost=CostTier.CHEAP)
AIPluginSelector.select() filters candidates by CostTier enum ordering
(FREE < CHEAP < MODERATE < EXPENSIVE) before scoring, so this guarantees you never select a plugin
more expensive than the tier you specify.
Recipe: semantic re-ranking with your own embedding model
def my_embed_fn(text: str) -> list[float]:
return my_embedding_model.embed(text) # any function returning a fixed-length float vector
manager = PluginManager(plugin_dirs=["./plugins"], embed_fn=my_embed_fn)
With embed_fn supplied, AIPluginSelector._semantic_rerank blends keyword_score * 0.4 + cosine_similarity * 0.6 for every candidate. If embed_fn raises for any reason (network error, rate
limit, etc.), the selector logs a warning and silently falls back to keyword-only scoring for that call —
your application does not need to handle embedding failures itself.
Recipe: observability dashboard for a health-check endpoint
@app.get("/plugins/health")
async def plugin_health():
return manager.dashboard()
dashboard() returns a JSON-serializable dict combining per-plugin stats (get_stats()), per-plugin
health scores (get_health()), and manager-wide execution metrics (metrics.summary()) — suitable to
return directly from a health-check or admin endpoint.
Recipe: safe permission bootstrapping for untrusted plugin sources
manager = PluginManager(plugin_dirs=["./third_party_plugins"], safe_mode=True)
await manager.startup() # nothing auto-granted; every discovered plugin starts with zero permissions
for plugin in manager.registry.all():
print(plugin.name, plugin.metadata.permissions) # inspect before granting anything
# Explicitly review and grant only what you've decided to trust:
manager.grant_permission("trusted_search_plugin", "network_access")
This pattern — discover first, inspect declared permissions, grant selectively — is the only path in
the source for auditing third-party plugin permission requests before they take effect, since
safe_mode=False grants everything automatically at startup().