# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Global middleware intercept registration for tools and LLMs. Request intercepts transform inputs before execution. Execution intercepts wrap the downstream callable and can observe, modify, or replace the result. Example:: import nemo_relay def add_header( name: str, request: nemo_relay.LLMRequest, annotated: nemo_relay.AnnotatedLLMRequest | None ) -> nemo_relay.LLMRequestInterceptOutcome: # The request object is immutable, however we can return a new instance with updated headers. headers = request.headers.copy() headers["X-Trace"] = "demo" return nemo_relay.LLMRequestInterceptOutcome( nemo_relay.LLMRequest(headers=headers, content=request.content), annotated ) nemo_relay.intercepts.register_llm_request("trace-header", 10, False, add_header) """ from nemo_relay import ( LlmExecutionIntercept, LlmRequestIntercept, LlmStreamExecutionIntercept, ToolExecutionIntercept, ToolRequestIntercept, ) from nemo_relay._native import ( deregister_llm_execution_intercept as _native_deregister_llm_execution, ) from nemo_relay._native import ( deregister_llm_request_intercept as _native_deregister_llm_request, ) from nemo_relay._native import ( deregister_llm_stream_execution_intercept as _native_deregister_llm_stream_execution, ) from nemo_relay._native import ( deregister_tool_execution_intercept as _native_deregister_tool_execution, ) from nemo_relay._native import ( deregister_tool_request_intercept as _native_deregister_tool_request, ) from nemo_relay._native import ( register_llm_execution_intercept as _native_register_llm_execution, ) from nemo_relay._native import ( register_llm_request_intercept as _native_register_llm_request, ) from nemo_relay._native import ( register_llm_stream_execution_intercept as _native_register_llm_stream_execution, ) from nemo_relay._native import ( register_tool_execution_intercept as _native_register_tool_execution, ) from nemo_relay._native import ( register_tool_request_intercept as _native_register_tool_request, ) # --------------------------------------------------------------------------- # Tool intercepts # --------------------------------------------------------------------------- def register_tool_request(name: str, priority: int, break_chain: bool, fn: ToolRequestIntercept) -> None: """Register an intercept that rewrites tool arguments before execution. Args: name: Unique intercept name used for later replacement or removal. priority: Execution order for the intercept. Lower values run first. break_chain: Whether to stop applying lower-priority request intercepts after this intercept runs. fn: Callable invoked as ``fn(tool_name, args)`` that returns the rewritten tool arguments. Returns: None: This function returns after the intercept is registered. Notes: Request intercepts run after conditional-execution guardrails and before sanitize-request guardrails or execution intercepts. Example:: import nemo_relay def add_trace_id(tool_name, args): return {**args, "trace_id": "req-123"} nemo_relay.intercepts.register_tool_request( "trace-id", 10, False, add_trace_id, ) """ return _native_register_tool_request(name, priority, break_chain, fn) def deregister_tool_request(name: str) -> bool: """Remove a previously registered tool request intercept. Args: name: Intercept name previously passed to ``register_tool_request()``. Returns: bool: ``True`` if an intercept was removed, otherwise ``False``. Notes: Removal affects only future executions. In-flight calls continue using the intercept chain they already resolved. """ return _native_deregister_tool_request(name) def register_tool_execution(name: str, priority: int, fn: ToolExecutionIntercept) -> None: """Register middleware around tool execution. Args: name: Unique intercept name used for later replacement or removal. priority: Execution order for the intercept. Lower values run first. fn: Callable invoked as ``fn(tool_name, args, next_call)``. The callback may await or call ``next_call(args)`` to continue the chain, modify the result, or bypass downstream execution entirely. It must return ``ToolExecutionInterceptOutcome``. Returns: None: This function returns after the intercept is registered. Notes: Execution intercepts wrap the downstream tool callback. They are the right place for timing, retries, short-circuiting, or result shaping. """ return _native_register_tool_execution(name, priority, fn) def deregister_tool_execution(name: str) -> bool: """Remove a previously registered tool execution intercept. Args: name: Intercept name previously passed to ``register_tool_execution()``. Returns: bool: ``True`` if an intercept was removed, otherwise ``False``. Notes: Removal affects only future executions. In-flight calls continue using the execution chain they already resolved. """ return _native_deregister_tool_execution(name) # --------------------------------------------------------------------------- # LLM intercepts # --------------------------------------------------------------------------- def register_llm_request(name: str, priority: int, break_chain: bool, fn: LlmRequestIntercept) -> None: """Register an intercept that rewrites an ``LLMRequest`` before execution. Args: name: Unique intercept name used for later replacement or removal. priority: Execution order for the intercept. Lower values run first. break_chain: Whether to stop applying lower-priority request intercepts after this intercept runs. fn: Callable invoked as ``fn(name, request, annotated)`` that returns an ``nemo_relay.LLMRequestInterceptOutcome`` for the next intercept or the provider callback. Returns: None: This function returns after the intercept is registered. Notes: ``annotated`` is ``None`` unless a request codec was supplied to the managed LLM call. Intercepts should preserve both values when they do not need to mutate them. Example:: import nemo_relay def add_header( name: str, request: nemo_relay.LLMRequest, annotated: nemo_relay.AnnotatedLLMRequest | None ) -> nemo_relay.LLMRequestInterceptOutcome: headers = request.headers.copy() headers["X-Trace"] = "req-123" return nemo_relay.LLMRequestInterceptOutcome( nemo_relay.LLMRequest(headers=headers, content=request.content), annotated ) nemo_relay.intercepts.register_llm_request( "trace-header", 10, False, add_header, ) """ return _native_register_llm_request(name, priority, break_chain, fn) def deregister_llm_request(name: str) -> bool: """Remove a previously registered LLM request intercept. Args: name: Intercept name previously passed to ``register_llm_request()``. Returns: bool: ``True`` if an intercept was removed, otherwise ``False``. Notes: Removal affects only future executions. In-flight calls continue using the intercept chain they already resolved. """ return _native_deregister_llm_request(name) def register_llm_execution(name: str, priority: int, fn: LlmExecutionIntercept) -> None: """Register middleware around non-streaming LLM execution. Args: name: Unique intercept name used for later replacement or removal. priority: Execution order for the intercept. Lower values run first. fn: Callable invoked as ``fn(name, request, next_call)``. The callback may call ``next_call(request)`` to continue execution, modify the result, or short-circuit the provider call. Returns: None: This function returns after the intercept is registered. Notes: Execution intercepts wrap only non-streaming LLM execution. Use ``register_llm_stream_execution()`` for streaming callbacks. """ return _native_register_llm_execution(name, priority, fn) def deregister_llm_execution(name: str) -> bool: """Remove a previously registered LLM execution intercept. Args: name: Intercept name previously passed to ``register_llm_execution()``. Returns: bool: ``True`` if an intercept was removed, otherwise ``False``. Notes: Removal affects only future executions. In-flight calls continue using the execution chain they already resolved. """ return _native_deregister_llm_execution(name) def register_llm_stream_execution( name: str, priority: int, fn: LlmStreamExecutionIntercept, ) -> None: """Register middleware around streaming LLM execution. Args: name: Unique intercept name used for later replacement or removal. priority: Execution order for the intercept. Lower values run first. fn: Callable invoked as ``fn(request, next_call)`` that returns an async iterator of JSON chunks, either by delegating to ``next_call(request)`` or by replacing the stream entirely. Returns: None: This function returns after the intercept is registered. Notes: Streaming execution intercepts wrap chunk production only. They do not replace the separate collector or finalizer callbacks. """ return _native_register_llm_stream_execution(name, priority, fn) def deregister_llm_stream_execution(name: str) -> bool: """Remove a previously registered streaming LLM execution intercept. Args: name: Intercept name previously passed to ``register_llm_stream_execution()``. Returns: bool: ``True`` if an intercept was removed, otherwise ``False``. Notes: Removal affects only future executions. In-flight streams continue using the execution chain they already resolved. """ return _native_deregister_llm_stream_execution(name) __all__ = [ "ToolRequestIntercept", "ToolExecutionIntercept", "LlmRequestIntercept", "LlmExecutionIntercept", "LlmStreamExecutionIntercept", "register_tool_request", "deregister_tool_request", "register_tool_execution", "deregister_tool_execution", "register_llm_request", "deregister_llm_request", "register_llm_execution", "deregister_llm_execution", "register_llm_stream_execution", "deregister_llm_stream_execution", ]