Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ serde_yaml = { package = "yaml_serde", version = "0.10.4" }
sqlx = { version = "0.9.0", default-features = false, features = ["runtime-tokio", "tls-rustls-ring", "sqlite", "postgres"] }
tempfile = "3.27.0"
thiserror = "2.0.18"
tiktoken-rs = "0.12.0"
tikv-jemallocator = "0.7.0"
tokio = { version = "1.52.3", features = ["macros", "rt"] }
tokio-rustls = "0.26.4"
Expand Down
1 change: 1 addition & 0 deletions apis/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ serde_json = { workspace = true }
serde_yaml = { workspace = true }
sqlx = { workspace = true, optional = true }
thiserror = { workspace = true }
tiktoken-rs = { workspace = true }
tokio = { workspace = true, features = ["rt", "time", "net"] }
tracing = { workspace = true }
zeroize = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion apis/src/openai/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub(crate) mod translation;

pub use conversations::OpenaiConversationsFilter;
pub use responses::{
DocExtractFilter, FileResolveFilter, McpDispatchFilter, McpToolResolveFilter, ModelRewriteFilter,
CompactFilter, DocExtractFilter, FileResolveFilter, McpDispatchFilter, McpToolResolveFilter, ModelRewriteFilter,
OpenaiResponsesValidateFilter, RehydrateFilter, ResponseStoreFilter, ResponsesFormatFilter, ToolParseFilter,
WebSearchFilter, openai_responses_proxy::ResponsesProxyFilter, stream_events::OpenaiStreamEventsFilter,
};
111 changes: 111 additions & 0 deletions apis/src/openai/responses/compact/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Praxis Contributors

//! Configuration for the `openai_responses_compact` filter.

use praxis_filter::FilterError;
use serde::Deserialize;

use crate::openai::responses::config_validation::{self, CalloutSettings, FailureMode};

/// Default callout timeout (30 seconds — summarization can be slow).
const DEFAULT_TIMEOUT_MS: u64 = 30_000;

/// Default HTTP status when the summarization callout fails in closed mode.
const DEFAULT_STATUS_ON_ERROR: u16 = 502;

// -----------------------------------------------------------------------------
// CompactFilterConfig (YAML deserialization)
// -----------------------------------------------------------------------------

/// Raw YAML config, deserialized then validated.
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub(super) struct CompactFilterConfig {
/// URL of the inference backend for summarization calls.
/// E.g., `"http://localhost:11434/v1/chat/completions"`
pub inference_url: String,

/// Default model for summarization when not overridden
/// in the request's `context_management`.
#[serde(default = "default_model")]
pub default_model: String,

/// Tiktoken encoding name for local token estimation.
/// Used as fallback when `previous_usage` is unavailable.
#[serde(default = "default_tiktoken_encoding")]
pub tiktoken_encoding: String,

/// Callout timeout in milliseconds.
#[serde(default)]
pub timeout_ms: Option<u64>,

/// Failure mode for the inference callout.
#[serde(default)]
pub failure_mode: Option<FailureMode>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think this setting can currently be populated from filter YAML: parse_filter_config removes failure_mode as a pipeline structural key before deserializing the filter-specific config, leaving this as None and defaulting the callout to closed mode. Could we use a non-reserved name such as callout_failure_mode, or explicitly pass the pipeline policy into the filter, with coverage for both open and closed behavior?


/// HTTP status code to return when rejecting on error.
#[serde(default)]
pub status_on_error: Option<u16>,
}

/// Default summarization model when not overridden per-request.
fn default_model() -> String {
"gpt-4o-mini".to_owned()
}

/// Default tiktoken encoding for local token estimation.
fn default_tiktoken_encoding() -> String {
"cl100k_base".to_owned()
}

// -----------------------------------------------------------------------------
// ValidatedConfig (post-validation)
// -----------------------------------------------------------------------------

/// Validated configuration with defaults applied.
pub(super) struct ValidatedConfig {
/// URL of the inference backend for summarization calls.
pub inference_url: String,

/// Default model for summarization.
pub default_model: String,

/// Tiktoken encoding name.
pub tiktoken_encoding: String,

/// Shared callout settings (timeout, failure mode, status).
pub callout: CalloutSettings,
}

/// Validate raw config and apply defaults.
///
/// # Errors
///
/// Returns [`FilterError`] if `inference_url` is empty,
/// `timeout_ms` is zero, or `status_on_error` is out of range.
pub(super) fn build_config(raw: &CompactFilterConfig) -> Result<ValidatedConfig, FilterError> {
if raw.inference_url.is_empty() {
return Err(FilterError::from("openai_responses_compact: inference_url is empty"));
}

let timeout_ms =
config_validation::validate_timeout_ms("openai_responses_compact", raw.timeout_ms, DEFAULT_TIMEOUT_MS)?;

let status_on_error = config_validation::validate_status_on_error(
"openai_responses_compact",
raw.status_on_error,
DEFAULT_STATUS_ON_ERROR,
)?;

Ok(ValidatedConfig {
inference_url: raw.inference_url.clone(),
default_model: raw.default_model.clone(),
tiktoken_encoding: raw.tiktoken_encoding.clone(),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Medium] tiktoken_encoding is passed through without validation. Only cl100k_base and o200k_base are supported at runtime (see get_token_count), but an invalid value like gpt4 silently disables compaction -- get_token_count returns None, should_compact returns None, and the filter does nothing with only a warn! at runtime.

Validate the encoding name in build_config against the supported set and return a FilterError at startup, matching how timeout_ms and status_on_error are validated here. This also makes the # Errors doc on this function accurate (it should mention invalid encoding).

callout: CalloutSettings {
timeout_ms,
failure_mode: raw.failure_mode.unwrap_or(FailureMode::Closed),
status_on_error,
},
})
}
Loading
Loading