ratel-ai
Raw API of ratel-ai — every exported class, function, and constant of the Python SDK.
v0.5.0 · Python · PyPI · source
Generated from the doc comments at
ratel-ai/ratel@5b6e154— do not edit by hand. Regenerate withpnpm sync:apiinapps/docs.
Classes
DimensionMismatchError
A query/corpus embedding dimension mismatch.
EmbedderError
Embedding model load / inference failure (subclass of RuntimeError).
EndpointEmbeddingConfig
OpenAI-compatible embedding endpoint configuration.
Attributes:
model: strurl: str
ExecutableTool
ExecutableTool(id: str, name: str, description: str, input_schema: dict[str, Any] = dict(), output_schema: dict[str, Any] = dict(), execute: Executor = None)A Tool plus the handler that runs it. Registered into a ToolCatalog.
Attributes:
execute: Executor = field(default=None)
HuggingFaceEmbeddingConfig
In-process HuggingFace embedding model configuration.
Attributes:
huggingface: str
LocalEmbeddingConfig
In-process local-directory embedding model configuration.
Attributes:
local: str
McpServerHandle
McpServerHandle(tool_ids: list[str], server_instructions: str | None, close: Callable[[], Awaitable[None]])What register_mcp_server returns: the registration's outcome.
Attributes:
tool_ids(list[str]) — the namespaced<server>__<tool>ids registered into the catalog, in upstream listing order.server_instructions(str | None) — theinstructionsvalue passed toregister_mcp_server(the caller reads it from its owninitializeresult), orNone.close(Callable[[], Awaitable[None]]) — async teardown — theon_closepassed toregister_mcp_server, or a no-op. The session itself stays caller-owned either way.
OllamaEmbeddingConfig
Local Ollama embedding endpoint configuration.
Attributes:
ollama: str
SearchHit
A single search result: the matched tool id and its relevance score.
Attributes:
score: float— Relevance score; higher ranks first. The scale depends on the search method: raw BM25 (unbounded) for "bm25", cosine similarity for "semantic", reciprocal-rank-fusion for "hybrid" — scores from different methods are not comparable.tool_id: str— Id of the matched tool, as passed toregister.
Skill
Skill(id: str, name: str, description: str, tags: list[str] = list(), tools: list[str] = list(), metadata: dict[str, list[str]] = dict(), body: str = '')Skill metadata: what the index ranks and the capability tools surface.
Attributes:
body: str = ''description: strid: strmetadata: dict[str, list[str]] = field(default_factory=dict)name: strtags: list[str] = field(default_factory=list)tools: list[str] = field(default_factory=list)
SkillCatalog
SkillCatalog(trace: TraceSinkConfig | None = None, method: SearchMethod = 'bm25', embedding: EmbeddingSpec | None = None)Registry of skills. Register once, then search and load bodies by id.
Create an empty skill catalog.
Parameters:
trace(TraceSinkConfig | None) — where trace events go;Nonekeeps the default no-op sink.method(SearchMethod) — default retrieval method forsearch— seeToolCatalog.__init__; a "semantic"/"hybrid" catalog embeds insideregister, and dense results come fromsearch_async.embedding(EmbeddingSpec | None) — model for semantic/hybrid retrieval — seeToolCatalog.__init__; retained and validated under "bm25" too.
drain_trace_events
drain_trace_events() -> list[dict[str, Any]]Drain captured trace envelopes; [] unless the sink is "memory".
get
get(skill_id: str) -> Skill | NoneReturn the registered Skill for an id, or None if unknown.
has
has(skill_id: str) -> boolReturn whether a skill with this id is registered.
invoke
invoke(skill_id: str) -> strReturn a skill's body for dispatch, recording a skill_invoke event.
Synchronous, unlike ToolCatalog.invoke — the body is already in
memory, there is no handler to run.
Parameters:
skill_id(str) — id of a registered skill.
Returns: str — The skill's body (Markdown), verbatim as registered.
Raises:
ValueError— on an unknown id — callers at the capability-tool boundary translate this into a structured error for the agent.
record_event
record_event(event: dict[str, Any]) -> NoneRecord a trace event into the catalog's sink.
See ToolCatalog.record_event for the event contract.
register
register(skills: Skill | Iterable[Skill]) -> Awaitable[None]Register one skill or many — the single entry point for both.
Metadata is stored synchronously when register(...) is called
(name/description/tags are indexed; tools, metadata, body are
stored but not indexed), so a forgotten await never drops the corpus.
The returned awaitable drives only the embedding pass: on a
"semantic"/"hybrid" catalog it embeds the batch off-thread and surfaces
errors when awaited; a BM25 catalog never loads a model. Always
await the result. Re-registering an id replaces it in place.
A model or dimension change is not recovered in place — construct a new catalog and re-register.
Parameters:
skills(Skill | Iterable[Skill]) — a singleSkillor an iterable of them. Pass the whole batch at once for a single embedding request.
Raises:
EmbedderError— on a semantic/hybrid catalog, if embedding fails (when awaited).RuntimeError— if a dense operation already owns the registry.
search
search(query: str, top_k: int, origin: SearchOrigin = 'direct', method: SearchMethod | None = None) -> list[SkillHit]Rank registered skills synchronously with BM25.
The skill twin of ToolCatalog.search: a dense resolved method raises
immediately with guidance to use search_async.
Returns: list[SkillHit] — Up to top_k SkillHits, best first.
search_async
async search_async(query: str, top_k: int, origin: SearchOrigin = 'direct', method: SearchMethod | None = None) -> list[SkillHit]Rank skills asynchronously with BM25, semantic, or hybrid retrieval.
Dense methods require a complete cache built explicitly beforehand.
size
size() -> intReturn the number of registered skills.
SkillHit
A single skill search result: the matched skill id and its relevance score.
The skill analogue of SearchHit (tool_id → skill_id).
Attributes:
score: float— Relevance score; higher ranks first. Same method-dependent scale asSearchHit.score, computed against the skill corpus.skill_id: str— Id of the matched skill, as passed toregister.
SkillRegistry
SkillRegistry(embedding: EmbeddingSpec | None = None, *, method: SearchMethod = 'bm25', spec: str | None = None, huggingface: str | None = None, local: str | None = None, ollama: str | None = None, url: str | None = None, model: str | None = None, revision: str | None = None, api_key_env: str | None = None, query_prefix: str | None = None, doc_prefix: str | None = None, pooling: str | None = None, download: bool | None = None)Typed Python facade over the private native skill registry.
Create a metadata registry with an optional embedding model.
A "semantic"/"hybrid" method makes register embed eagerly (inside the
call, on a worker thread); "bm25" keeps registration model-free.
drain_trace_events
drain_trace_events() -> list[dict[str, Any]]Drain captured native trace events.
record_event
record_event(event: dict[str, Any]) -> NoneRecord an SDK-layer trace event.
register
register(item: Skill | Iterable[Skill] | str, name: str | None = None, description: str | None = None, tags: list[str] | None = None, tools: list[str] | None = None, metadata: dict[str, list[str]] | None = None, body: str | None = None) -> Awaitable[None]Register one Skill, many Skills, or a flat (id, name, …) tuple.
Metadata is indexed synchronously when register(...) is called (a
forgotten await never drops the corpus); the returned awaitable drives
only the embedding pass. On a "semantic"/"hybrid" registry it embeds in
one batched, off-thread pass (errors surface when awaited); "bm25" has
nothing to embed. Always await the result.
search
search(query: str, top_k: int) -> list[SkillHit]Run synchronous, model-free BM25 retrieval.
search_async
async search_async(query: str, top_k: int, origin: SearchOrigin = 'direct', method: SearchMethod = 'bm25') -> list[SkillHit]Search immediately with BM25 or run dense retrieval on a worker thread.
search_with_method
search_with_method(query: str, top_k: int, origin: SearchOrigin, method: SearchMethod) -> list[SkillHit]Run BM25 synchronously; dense retrieval is async-only.
search_with_origin
search_with_origin(query: str, top_k: int, origin: SearchOrigin) -> list[SkillHit]Run BM25 retrieval with an explicit trace origin.
set_trace_sink
set_trace_sink(kind: str, session_id: str | None = None, path: str | None = None) -> NoneReplace the native trace sink.
Tool
Tool(id: str, name: str, description: str, input_schema: dict[str, Any] = dict(), output_schema: dict[str, Any] = dict())Tool metadata: what the index ranks and the capability tools surface.
Attributes:
description: strid: strinput_schema: dict[str, Any] = field(default_factory=dict)name: stroutput_schema: dict[str, Any] = field(default_factory=dict)
ToolCatalog
ToolCatalog(trace: TraceSinkConfig | None = None, method: SearchMethod = 'bm25', embedding: EmbeddingSpec | None = None)Registry + executors. Register tools once, then search and invoke by id.
Create an empty catalog.
Parameters:
trace(TraceSinkConfig | None) — where trace events go;Nonekeeps the default no-op sink.method(SearchMethod) — default retrieval method forsearch— "bm25" (the historical, model-free behavior), "semantic" or "hybrid". A per-callmethod=overrides it. A "semantic"/"hybrid" catalog embeds insideregister; dense results come fromsearch_async.embedding(EmbeddingSpec | None) — model for semantic/hybrid retrieval (a path string or a keyed dict — seeEmbeddingSpec). Retained and validated even under "bm25" so a later async semantic override can use it.
drain_trace_events
drain_trace_events() -> list[dict[str, Any]]Drain captured trace envelopes; [] unless the sink is "memory".
get
get(tool_id: str) -> Tool | NoneReturn the metadata-only Tool for an id, or None if unknown.
get_executable
get_executable(tool_id: str) -> ExecutableTool | NoneReturn the ExecutableTool (metadata plus handler) for an id, or None.
has
has(tool_id: str) -> boolReturn whether a tool with this id is registered.
invoke
async invoke(tool_id: str, args: dict[str, Any]) -> AnyRun a registered tool's handler and return its result.
This is the canonical place that absorbs the sync/async executor
difference: the handler is called first and the result awaited only if
it is awaitable, so plain functions and async def executors (e.g.
MCP/HTTP tools) are both supported. Callers must route invocations
here rather than re-deriving that logic. Emits invoke_start /
invoke_end / invoke_error trace events and wraps the call in an
execute_tool OTel span (ADR-0007).
Parameters:
tool_id(str) — id of a registered tool.args(dict[str, Any]) — the arguments dict passed to the handler.
Returns: Any — Whatever the handler returns (awaited if it returned an awaitable).
Raises:
ValueError— iftool_idis not registered.Exception— whatever the handler raises, re-raised after aninvoke_errortrace event is recorded.
record_event
record_event(event: dict[str, Any]) -> NoneRecord a trace event into the catalog's sink.
Parameters:
event(dict[str, Any]) — a dict matching one of the core-ownedTraceEventshapes (ADR-0007), e.g.{"type": "gateway_search", ...}.
Raises:
ValueError— if the dict doesn't match any known event shape.
register
register(tools: ExecutableTool | Iterable[ExecutableTool]) -> Awaitable[None]Register one tool or many — the single entry point for both.
Metadata and the executor handler are stored synchronously, the
instant register(...) is called, so a forgotten await can never
silently drop the corpus. The returned awaitable drives only the
embedding pass: on a "semantic"/"hybrid" catalog it embeds the batch in
one pass on a worker thread (the event loop is never blocked), and
embedding errors (model load / endpoint / auth / dimension) surface when
it is awaited. A BM25 catalog never loads a model and the awaitable is a
no-op. Always await the result — a semantic/hybrid search after an
un-awaited register raises rather than ranking an empty corpus.
Re-registering an id replaces it in place; the index never holds a
duplicate.
A model or dimension change is not recovered in place — construct a new catalog and re-register.
Parameters:
tools(ExecutableTool | Iterable[ExecutableTool]) — a singleExecutableToolor an iterable of them; eachexecutemust be set. Pass the whole batch at once for a single embedding request; separateregistercalls embed separately.
Raises:
ValueError— if anyexecuteisNone, or a schema isn't JSON-serializable.EmbedderError— on a semantic/hybrid catalog, if embedding fails (when awaited).RuntimeError— if a dense operation already owns the registry.
search
search(query: str, top_k: int, origin: SearchOrigin = 'direct', method: SearchMethod | None = None) -> list[SearchHit]Rank registered tools synchronously with BM25.
Parameters:
query(str) — what the caller wants to do.top_k(int) — max hits to return.origin(SearchOrigin) — who initiated the search — labels the trace event only.method(SearchMethod | None) — per-call override of the catalog's default retrieval method ("bm25" | "semantic" | "hybrid").
Returns: list[SearchHit] — Up to top_k SearchHits, best first.
Raises:
ValueError— ifmethodis not "bm25", "semantic" or "hybrid".RuntimeError— if the resolved method is semantic/hybrid; usesearch_asyncfor dense retrieval.
search_async
async search_async(query: str, top_k: int, origin: SearchOrigin = 'direct', method: SearchMethod | None = None) -> list[SearchHit]Rank tools asynchronously with BM25, semantic, or hybrid retrieval.
Dense methods require the corpus to have been embedded by register on a
semantic/hybrid catalog; searching never embeds missing corpus vectors.
ToolRegistry
ToolRegistry(embedding: EmbeddingSpec | None = None, *, method: SearchMethod = 'bm25', spec: str | None = None, huggingface: str | None = None, local: str | None = None, ollama: str | None = None, url: str | None = None, model: str | None = None, revision: str | None = None, api_key_env: str | None = None, query_prefix: str | None = None, doc_prefix: str | None = None, pooling: str | None = None, download: bool | None = None)Typed Python facade over the private native tool registry.
Create a metadata registry with an optional embedding model.
A "semantic"/"hybrid" method makes register embed eagerly (inside the
call, on a worker thread); "bm25" keeps registration model-free.
drain_trace_events
drain_trace_events() -> list[dict[str, Any]]Drain captured native trace events.
record_event
record_event(event: dict[str, Any]) -> NoneRecord an SDK-layer trace event.
register
register(item: Tool | Iterable[Tool] | str, name: str | None = None, description: str | None = None, input_schema: dict[str, Any] | None = None, output_schema: dict[str, Any] | None = None) -> Awaitable[None]Register one Tool, many Tools, or a flat (id, name, …) tuple.
Metadata is indexed synchronously, the instant register(...) is
called, so a forgotten await can never silently drop the corpus. The
returned awaitable drives only the embedding pass — on a
"semantic"/"hybrid" registry it embeds in one batched, off-thread pass
(embedding errors surface when awaited); "bm25" has nothing to embed and
the awaitable is a no-op. Always await the result.
search
search(query: str, top_k: int) -> list[SearchHit]Run synchronous, model-free BM25 retrieval.
search_async
async search_async(query: str, top_k: int, origin: SearchOrigin = 'direct', method: SearchMethod = 'bm25') -> list[SearchHit]Search immediately with BM25 or run dense retrieval on a worker thread.
search_with_method
search_with_method(query: str, top_k: int, origin: SearchOrigin, method: SearchMethod) -> list[SearchHit]Run BM25 synchronously; dense retrieval is async-only.
search_with_origin
search_with_origin(query: str, top_k: int, origin: SearchOrigin) -> list[SearchHit]Run BM25 retrieval with an explicit trace origin.
set_trace_sink
set_trace_sink(kind: str, session_id: str | None = None, path: str | None = None) -> NoneReplace the native trace sink.
TraceSinkConfig
TraceSinkConfig(kind: str, session_id: str | None = None, path: str | None = None)Where the catalog's trace events go. Mirrors the TS TraceSinkConfig union.
kind: "noop" | "memory" | "jsonl". session_id is required for memory/jsonl;
path is required for jsonl.
Attributes:
kind: strpath: str | None = Nonesession_id: str | None = None
UpstreamServerInfo
UpstreamServerInfo(name: str, description: str | None = None, instructions: str | None = None, tool_count: int | None = None, needs_auth: bool = False)An upstream MCP server, as advertised in the capability-tool descriptions.
Pass a list of these to search_capabilities_tool (or the deprecated
search_tools_tool) to append a "this catalog aggregates…" listing to the
tool description shown to the model, and to enrich each search result's
server group with the upstream's description and instructions.
Attributes:
name(str) — the catalog namespace of the server — the<name>prefix of its<name>__<tool>tool ids.description(str | None) — what the server offers; compacted to one line in listings.instructions(str | None) — the server's usage instructions (from its MCPinitializeresult), surfaced on matching server groups.tool_count(int | None) — number of tools the server contributed, shown in listings.needs_auth(bool) — True when the upstream rejected its boot connection with a 401 / re-auth needed; listings append "(auth required)".
Functions
configure_telemetry
configure_telemetry(*, api_key: str | None = None, endpoint: str | None = None, headers: dict[str, str] | None = None, service_name: str | None = None, capture_content: ContentCapture | str | None = None, include_span_and_events: bool | None = None) -> AnyRegister a Ratel-owned OTLP exporter (convenience wiring for the greenfield case).
Ships the spans this SDK emits to Ratel Cloud (or any OTLP endpoint) by
delegating to ratel_ai_telemetry.init, which needs the OpenTelemetry SDK —
install it with pip install 'ratel-ai[otlp]'. A host already running its
own OpenTelemetry provider should skip this (the SDK's spans flow to that
provider) and add ratel_span_processor from ratel_ai_telemetry.
capture_content / include_span_and_events opt into message/tool content
capture in code via set_content_capture: capture_content sets an exact mode,
include_span_and_events is bool sugar (True -> SPAN_AND_EVENT, False ->
NO_CONTENT); capture_content wins when both are given. A provided option
beats OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT (the env var is the
fallback when neither is given, as in OTel). The returned handle's shutdown()
clears the override, restoring env-driven behavior; the clear is
generation-scoped, so a stale handle shutting down late never clobbers an
override a newer configure_telemetry/set_content_capture call installed.
Parameters:
api_key(str | None) — Ratel Cloud API key override; defaults toRATEL_API_KEY.endpoint(str | None) — OTLP endpoint override; defaults toRATEL_URL.headers(dict[str, str] | None) — Extra headers sent with every export request.service_name(str | None) —service.nameresource attribute; defaults perinit.capture_content(ContentCapture | str | None) — Exact content-capture mode to set (see above).include_span_and_events(bool | None) — Boolean sugar forcapture_content(see above).
Returns: Any — A per-call shutdown handle (handle.shutdown() / handle.force_flush()), the same shape on every path. Attribute access delegates to the shared handle init returns; because that handle is shared across callers, shutting it down stops export for all of them.
Raises:
ModuleNotFoundError— if the OpenTelemetry exporter is not installed (pip install 'ratel-ai[otlp]').ValueError— ifcapture_contentis not a recognized mode — raised before any exporter is wired, so a bad option has no side effects.
format_upstream_line
format_upstream_line(s: UpstreamServerInfo) -> strRender one upstream server as a listing bullet for a tool description.
Format: - <name> — <description> (<N> tools) (auth required), where the
description is whitespace-collapsed and truncated to ~160 chars, and each
trailing part appears only when the corresponding field is set.
Parameters:
s(UpstreamServerInfo) — the upstream server to render.
Returns: str — A single - -prefixed line, ready to join into the "this catalog aggregates…" listing.
get_skill_content_tool
get_skill_content_tool(catalog: SkillCatalog) -> ExecutableToolBuild the get_skill_content tool: load a skill's full body by id.
The returned tool resolves skillId and answers {"body": <markdown>} via
SkillCatalog.invoke (which records a skill_invoke trace event). It
never raises into the host: an unknown or non-string id comes back as a
structured {"error": ..., "isError": True} payload the model can recover
from, mirroring invoke_tool.
Parameters:
catalog(SkillCatalog) — the skill catalog to load bodies from.
Returns: ExecutableTool — An ExecutableTool to put in the agent's direct tool list alongside search_capabilities.
invoke_tool_tool
invoke_tool_tool(catalog: ToolCatalog, *, on_unauthorized: OnUnauthorized | None = None) -> ExecutableToolBuild the invoke_tool tool: run any catalog tool by id.
The returned tool resolves toolId, forwards the nested args object to
ToolCatalog.invoke, and never raises into the host: an unknown id,
malformed args, or a tool exception all come back as a structured
{"error": ..., "isError": True} payload the model can recover from. A
flattened call (arguments at the top level instead of nested under args)
is tolerated. When the tool raises an UnauthorizedError, the payload is
{"error": "needs_auth", ...} with a re-auth hint and, for namespaced
<server>__<tool> ids, the upstream server name.
Parameters:
catalog(ToolCatalog) — the catalog to resolve and run tool ids against.on_unauthorized(OnUnauthorized | None) — called with the upstream server name when a tool raisesUnauthorizedError; may be sync or async. Skipped when the tool id has no<server>__namespace prefix.
Returns: ExecutableTool — An ExecutableTool to put in the agent's direct tool list.
register_mcp_server
async register_mcp_server(catalog: ToolCatalog, *, name: str, session: Any, transport_label: str = 'unknown', instructions: str | None = None, on_close: Callable[[], Awaitable[None]] | None = None) -> McpServerHandleIngest an initialized MCP ClientSession into the catalog.
Parameters:
catalog(ToolCatalog) — the catalog to register the upstream tools into.name(str) — namespace prefix for tool ids (<name>__<tool>).session(Any) — an initializedmcp.ClientSessionowned by the caller.transport_label(str) — recorded on theupstream_registertrace event.instructions(str | None) — the upstream's server instructions (frominitialize), if any.on_close(Callable[[], Awaitable[None]] | None) — optional async teardown invoked by the handle'sclose().
Returns: McpServerHandle — An McpServerHandle with the registered tool ids.
Raises:
ImportError— if the optionalmcppackage is not installed (pip install 'ratel-ai[mcp]').
search_capabilities_tool
search_capabilities_tool(catalog: ToolCatalog, skill_catalog: SkillCatalog | None = None, *, upstream_servers: Sequence[UpstreamServerInfo] | None = None) -> ExecutableToolBuild the search_capabilities tool: unified discovery over tools AND skills.
The returned tool ranks two independent buckets, each with its own top-K
budget — so a relevant skill is never starved out of the results by a large
number of matching tools (and the two BM25 corpora are never
score-compared). Tools land grouped by upstream server; a matched skill's
declared tools are pulled into the tools bucket additively (score 0), so
the agent gets the playbook and its toolkit in one turn. The skills
bucket (and the mention of get_skill_content in the description) is only
advertised when a non-empty skill_catalog is wired in.
Parameters:
catalog(ToolCatalog) — the tool catalog to search.skill_catalog(SkillCatalog | None) — optional skill catalog, ranked against the same query in its own bucket.upstream_servers(Sequence[UpstreamServerInfo] | None) — upstream MCP servers to advertise in the tool description and to enrich result server groups with.
Returns: ExecutableTool — An ExecutableTool to put in the agent's direct tool list.
search_tools_tool
search_tools_tool(catalog: ToolCatalog, *, upstream_servers: Sequence[UpstreamServerInfo] | None = None) -> ExecutableToolBuild the pre-0.2.0 tools-only discovery tool (id search_tools).
Keeps the original behaviour: a flat {groups} result and no skills
bucket. New code should use ratel_ai.search_capabilities_tool instead.
Registering both lets a host serve the old and new names during a
migration window.
Parameters:
catalog(ToolCatalog) — the tool catalog to search.upstream_servers(Sequence[UpstreamServerInfo] | None) — upstream MCP servers to advertise in the tool description and to enrich result server groups with.
Returns: ExecutableTool — An ExecutableTool to put in the agent's direct tool list.
Deprecated — Since 0.2.0: use
ratel_ai.search_capabilities_tool. Tracked for removal in RAT-250.
Constants
GET_SKILL_CONTENT_ID = 'get_skill_content'— Id (and name) of the skill-loading tool built byget_skill_content_tool.INVOKE_TOOL_ID = 'invoke_tool'— Id (and name) of the invocation tool built byinvoke_tool_tool.SEARCH_CAPABILITIES_ID = 'search_capabilities'— Id (and name) of the discovery tool built bysearch_capabilities_tool.SEARCH_TOOLS_ID = 'search_tools'— Id (and name) of the deprecated pre-0.2.0 discovery tool built bysearch_tools_tool; superseded byratel_ai.SEARCH_CAPABILITIES_ID.EmbeddingModelConfig = Union[HuggingFaceEmbeddingConfig, LocalEmbeddingConfig, OllamaEmbeddingConfig, EndpointEmbeddingConfig]— Mutually exclusive keyed embedding-source configurations.EmbeddingSpec = Union[str, EmbeddingModelConfig]— Embedding selection; a bare string is a local model directory path.Executor = Callable[[dict[str, Any]], Union[Awaitable[Any], Any]]— A tool handler: takes the tool's arguments dict, returns the result. May be sync or async (tool inputs are heterogeneous across the catalog);ToolCatalog.invokeabsorbs the difference.OnUnauthorized = Callable[[str], Union[Awaitable[None], None]]— Notified when the underlying tool raisesUnauthorizedError, with the upstream server name inferred from the toolId. May be sync or async.SearchMethod = str— Retrieval engine:"bm25"(lexical, model-free, the default),"semantic"(dense embeddings) or"hybrid"(both, fused).SearchOrigin = str— Who initiated a search:"direct"(host code, the default) or"agent"(a model calling a capability tool). Labels the emitted trace event only — ranking is unaffected.