Tool family¶
Wrap any callable as a Tool for an Agent. The Tool.wrap()
classmethod is the canonical multi-input factory (callable / Agent
/ existing Tool); Tool(...) is the explicit constructor used when
you want to set every field by hand. ToolProvider is the protocol
for expandable tool catalogues (MCP servers etc.). NativeTool
enumerates provider-hosted server-side tools.
The module-level lazybridge.tool (lowercase) is a thin
backwards-compat alias for Tool.wrap — existing imports keep
working, new code should prefer the classmethod.
For narrative usage see Guides → Basic → Tool and Guides → Basic → Native tools.
Timeouts¶
Tool(timeout=N) bounds one tool; Agent(tool_timeout=N) supplies a
default to every tool that sets none of its own; LLMEngine(tool_timeout=N)
does the same at engine level. The most specific one set wins, and a tool
that exceeds its bound raises ToolTimeoutError, which the engine reports
to the model as a failed tool result rather than aborting the run.
Bound the tool, not just the run. Agent(timeout=N) can only fire at an
await, and a synchronous tool never yields one — a blocking
time.sleep/requests.get inside a tool will run past the agent deadline
indefinitely. Tool(timeout=N) instead runs the call on a daemon thread and
abandons it when the time is out: the caller is freed immediately, the
work itself keeps running until it returns on its own, and its result is
discarded. Anything with a side effect may therefore still complete after
the timeout — for work that must actually stop, give the underlying library
its own deadline (requests.get(..., timeout=)) or run it in a subprocess.
An async tool is cancelled rather than abandoned, but cancelling is a
request and not a guarantee: a coroutine may catch CancelledError and carry
on, or spend a long time in cleanup. It gets Tool.cancel_grace_seconds
(1.0) to unwind, after which it too is abandoned.
One case no deadline can reach: a coroutine that blocks the event loop —
CPU-bound work or a synchronous call inside async def, whether in the body
or in cancellation cleanup. Nothing else runs while it does, including the
clock that would end it. That is a property of asyncio, not of this bound;
the fix is to keep blocking work out of async def (declare the tool def
and let Tool(timeout=) put it on its own thread, or use
run_in_executor).
The bound is on the call, not on process exit. An abandoned task still
belongs to its event loop, and asyncio.run cancels and gathers every
pending task on the way out — so an async tool that swallows CancelledError
outright can delay shutdown even though the call itself returned on time.
run_sync() is unaffected: LazyBridge owns that loop and skips draining what
it has already abandoned.
lazybridge.Tool ¶
Tool(func: Callable, *, name: str | None = None, description: str | None = None, mode: Literal['signature', 'llm', 'hybrid'] = 'signature', schema_llm: Any | None = None, strict: bool = False, returns_envelope: bool = False, agent_memory: Any | None = None, agent_store: Any | None = None, timeout: float | None = None)
Wraps any Python callable as an LLM-accessible tool.
Pass raw functions directly; Tool auto-wraps them on the agent level. Use Tool(fn, ...) only when you need explicit configuration.
Source code in lazybridge/tools.py
from_schema
classmethod
¶
from_schema(name: str, description: str, parameters: dict[str, Any], func: Callable[..., Any], *, strict: bool = False, returns_envelope: bool = False, timeout: float | None = None) -> Tool
Create a Tool with a pre-built JSON Schema for parameters.
Use this when the schema is already known (from MCP, OpenAPI, a third-party tool registry, ...) and signature introspection would either be unavailable or produce the wrong shape.
parameters must be a JSON Schema object (the same shape that
ToolDefinition.parameters carries).
Source code in lazybridge/tools.py
run_sync ¶
Blocking tool invocation.
Handles two cases so that callers never see a stray coroutine:
- plain sync function → called directly.
- async function → driven to completion through the shared
sync↔async bridge (:func:
lazybridge._asyncbridge.run_coroutine_blocking), which handles nest_asyncio, contextvars propagation, and loop-closed cleanup identically toAgent.__call__. Needed because :meth:Agent.as_toolwraps the agent's.run()coroutine intoTool.func—SupervisorEngine/ REPL callers were previously getting"<coroutine object _run at 0x...>"instead of the result.
Source code in lazybridge/tools.py
wrap
classmethod
¶
wrap(obj: Any, *, name: str | None = None, description: str | None = None, mode: Literal['signature', 'hybrid', 'llm'] = 'signature', schema_llm: Any | None = None, strict: bool = _UNSET_BOOL, timeout: float | None = None) -> Tool
Canonical multi-input factory — accepts a callable, an Agent, or an
existing :class:Tool, and returns a properly wrapped Tool.
For Python functions — name is required so Plan steps, tool
maps, and LLM calls all share the same stable identifier::
search = Tool.wrap(search_web, name="search", description="Search the web.")
researcher = Agent(name="research", engine=LLMEngine(...), tools=[search])
For Agents — the canonical path is tools=[agent] directly;
Tool.wrap is useful when you need a local alias::
Tool.wrap(researcher, name="deep_research")
For existing Tools — returns the object unchanged (no overrides) or clones it with the specified overrides (non-mutating)::
search_v2 = Tool.wrap(search, name="web_search")
Parameters¶
obj:
A callable, :class:Agent, or existing :class:Tool to wrap.
name:
Required for callables. Optional alias for agents and Tools.
description:
Human-readable description forwarded to the LLM.
mode:
Schema generation mode. "signature" (default) introspects the
function signature and docstring deterministically. Pass
"hybrid" (signature + LLM-enriched descriptions) or "llm"
(full LLM-inferred schema) explicitly when the signature alone
is insufficient — both require schema_llm= to be set.
schema_llm:
Engine used when mode="hybrid" or mode="llm".
strict:
Enable JSON Schema strict mode.
timeout:
Seconds this tool may take before the caller gives up on it.
None leaves the tool unbounded, or keeps the bound a wrapped
Tool already carries.
Notes¶
Module-level :func:tool is a thin alias for backwards compatibility
and is kept indefinitely; new code should prefer Tool.wrap.
Source code in lazybridge/tools.py
493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 | |
lazybridge.tool ¶
tool(obj: Any, *, name: str | None = None, description: str | None = None, mode: Literal['signature', 'hybrid', 'llm'] = 'signature', schema_llm: Any | None = None, strict: bool = _UNSET_BOOL, timeout: float | None = None) -> Tool
Backwards-compatibility alias for :meth:Tool.wrap.
New code should call Tool.wrap(obj, name=...) — it lives on the class
alongside the explicit constructor, mirroring Python stdlib factories
like :meth:dict.fromkeys and :meth:datetime.datetime.fromisoformat.
The lowercase :func:tool is kept indefinitely so existing imports
(from lazybridge import tool) continue to work; no deprecation
timer is set.
Source code in lazybridge/tools.py
lazybridge.ToolTimeoutError ¶
Bases: Exception
A tool ran past the time it was given and the caller stopped waiting.
Reported to the model loop as a failed tool result, not raised out of the
run: the model sees the timeout and carries on with what it does have.
Raised by Tool.timeout and, for the outer per-call bound, by
LLMEngine.tool_timeout. For a synchronous tool the work is abandoned
rather than stopped -- see :meth:Tool._run_bounded.
Source code in lazybridge/tools.py
lazybridge.ToolProvider ¶
Bases: Protocol
A tools=[...] entry that expands itself into one or more Tools.
Implementors set _is_lazy_tool_provider = True and define
as_tools() -> list[Tool]. MCPServer and
ExternalToolProvider both satisfy this protocol structurally;
custom providers (OpenAPI imports, internal tool registries, etc.)
can do the same — drop the instance into Agent(tools=[provider])
and build_tool_map will expand it on construction.
lazybridge.NativeTool ¶
Bases: StrEnum
Provider-native server-side tools (run on provider infrastructure).