Skip to main content

nemo_flow_adaptive/acg/
plugin.rs

1// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Provider plugin trait and input/output types for the Adaptive Cache Governor
5//! (ACG) system.
6//!
7//! The [`ProviderPlugin`] trait defines the contract between ACG's
8//! provider-agnostic optimization pipeline and backend-specific
9//! translation logic. Plugins receive a [`PluginInput`] containing
10//! the original request, Prompt IR, intent bundle, and agent identity,
11//! and produce a [`PluginOutput`] with the translated request and a
12//! [`TranslationReport`].
13//!
14//! # Design
15//!
16//! - **Synchronous**: `translate` is a pure data transform (JSON
17//!   restructuring), not an I/O operation. This matches the `LlmCodec`
18//!   pattern in `crates/core/src/codec/traits.rs`.
19//! - **Compatibility facade**: provider plugins keep their existing
20//!   synchronous trait surface, but can internally split translation into
21//!   semantic hint planning plus request-surface application.
22//! - **`Send + Sync`**: Required for storage as `Arc<dyn ProviderPlugin>`
23//!   in concurrent contexts.
24//! - **Object-safe**: The trait is designed to be used as a trait object.
25
26use nemo_flow::api::llm::LlmRequest;
27
28use crate::acg::capability::BackendCapabilities;
29use crate::acg::prompt_ir::PromptIR;
30use crate::acg::request_surfaces::RequestSurfaceApplier;
31use crate::acg::translation::{HintPlan, HintTranslation, HintTranslator};
32use crate::acg::types::{AgentIdentity, OptimizationIntentBundle, TranslationReport};
33
34// ===================================================================
35// Plugin input / output types
36// ===================================================================
37
38/// Input data provided to a plugin for translation.
39///
40/// All fields are borrowed references to avoid unnecessary cloning.
41/// The lifetime `'a` ties the input to the caller's data.
42#[derive(Debug)]
43pub struct PluginInput<'a> {
44    /// The original (pre-rewrite) request.
45    pub original_request: &'a LlmRequest,
46    /// The rewritten request (may be identical to original in early phases).
47    pub rewritten_request: &'a LlmRequest,
48    /// The Prompt IR decomposition of the request.
49    pub prompt_ir: &'a PromptIR,
50    /// The optimization intent bundle from the policy engine.
51    pub intent_bundle: &'a OptimizationIntentBundle,
52    /// The agent identity for context.
53    pub agent_identity: &'a AgentIdentity,
54}
55
56/// Output produced by a plugin after translation.
57///
58/// Contains the translated request in the backend's native API format
59/// and a [`TranslationReport`] describing what happened to each intent.
60#[derive(Debug)]
61pub struct PluginOutput {
62    /// The final request in the backend's native API format.
63    pub translated_request: LlmRequest,
64    /// Report describing what happened to each intent.
65    pub translation_report: TranslationReport,
66}
67
68/// Internal adapter boundary used while the provider-plugin facade remains stable.
69///
70/// Translators produce surface-agnostic semantic plans. Appliers remain
71/// responsible for delegating raw request mutation to dedicated request-surface
72/// modules while the public provider-plugin facade remains stable.
73pub(crate) trait HintPlanApplier {
74    fn apply_hint_plan(
75        &self,
76        request: &LlmRequest,
77        prompt_ir: &PromptIR,
78        hint_plan: &HintPlan,
79    ) -> crate::acg::error::Result<LlmRequest>;
80}
81
82impl<T> RequestSurfaceApplier for T
83where
84    T: HintPlanApplier + Send + Sync + ?Sized,
85{
86    fn apply(
87        &self,
88        request: &LlmRequest,
89        prompt_ir: &PromptIR,
90        plan: &HintPlan,
91    ) -> crate::acg::Result<LlmRequest> {
92        self.apply_hint_plan(request, prompt_ir, plan)
93    }
94}
95
96/// Run the internal translator -> applier split behind the stable plugin facade.
97pub(crate) fn translate_with_hint_plan<T, A>(
98    translator: &T,
99    applier: &A,
100    input: &PluginInput<'_>,
101) -> crate::acg::error::Result<PluginOutput>
102where
103    T: HintTranslator,
104    A: HintPlanApplier + Send + Sync,
105{
106    let HintTranslation {
107        hint_plan,
108        translation_report,
109    } = translator.translate(input)?;
110
111    let translated_request = RequestSurfaceApplier::apply(
112        applier,
113        input.rewritten_request,
114        input.prompt_ir,
115        &hint_plan,
116    )?;
117
118    Ok(PluginOutput {
119        translated_request,
120        translation_report,
121    })
122}
123
124// ===================================================================
125// ProviderPlugin trait
126// ===================================================================
127
128/// Provider plugin trait for backend-specific translation.
129///
130/// Plugins are stateless on the forward path. They translate
131/// provider-agnostic intents into backend-native API parameters.
132///
133/// # Object Safety
134///
135/// This trait is object-safe and can be stored as `Arc<dyn ProviderPlugin>`.
136///
137/// # Thread Safety
138///
139/// The `Send + Sync` bounds allow plugins to be shared across async
140/// tasks and threads.
141pub trait ProviderPlugin: Send + Sync {
142    /// Unique identifier for this plugin (e.g., "anthropic", "openai", "passthrough").
143    fn plugin_id(&self) -> &str;
144
145    /// Human-readable name for this plugin.
146    fn plugin_name(&self) -> &str;
147
148    /// Translate intents into backend-native API parameters.
149    fn translate(&self, input: &PluginInput<'_>) -> crate::acg::error::Result<PluginOutput>;
150
151    /// Report the capabilities of the backend this plugin targets.
152    fn capabilities(&self) -> BackendCapabilities;
153}
154
155#[cfg(test)]
156#[path = "../../tests/unit/acg/plugin_tests.rs"]
157mod tests;