Skip to content
This repository was archived by the owner on Sep 2, 2026. It is now read-only.

Commit d5de60c

Browse files
Trace model settings without request extras
Co-authored-by: anode-agent[bot] <283895490+anode-agent[bot]@users.noreply.github.com>
1 parent 08fcfcd commit d5de60c

2 files changed

Lines changed: 140 additions & 1 deletion

File tree

crates/agents-core/src/model_settings.rs

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::collections::BTreeMap;
22

33
use schemars::JsonSchema;
44
use serde::{Deserialize, Serialize};
5-
use serde_json::Value;
5+
use serde_json::{Value, json};
66

77
/// Provider-agnostic model tuning parameters.
88
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
@@ -41,6 +41,60 @@ pub struct ReasoningSettings {
4141
}
4242

4343
impl ModelSettings {
44+
/// Return the model settings that are safe to attach to trace metadata.
45+
///
46+
/// Provider-specific request extras can contain credentials or other request-scoped
47+
/// sensitive data, so they are intentionally excluded.
48+
pub fn to_traceable_map(&self) -> BTreeMap<String, Value> {
49+
let mut traceable = BTreeMap::new();
50+
if let Some(value) = self.temperature {
51+
traceable.insert("temperature".to_owned(), json!(value));
52+
}
53+
if let Some(value) = self.top_p {
54+
traceable.insert("top_p".to_owned(), json!(value));
55+
}
56+
if let Some(value) = self.max_output_tokens {
57+
traceable.insert("max_output_tokens".to_owned(), json!(value));
58+
}
59+
if let Some(value) = self.frequency_penalty {
60+
traceable.insert("frequency_penalty".to_owned(), json!(value));
61+
}
62+
if let Some(value) = self.presence_penalty {
63+
traceable.insert("presence_penalty".to_owned(), json!(value));
64+
}
65+
if let Some(value) = &self.tool_choice {
66+
traceable.insert("tool_choice".to_owned(), json!(value));
67+
}
68+
if let Some(value) = self.parallel_tool_calls {
69+
traceable.insert("parallel_tool_calls".to_owned(), json!(value));
70+
}
71+
if let Some(value) = &self.truncation {
72+
traceable.insert("truncation".to_owned(), json!(value));
73+
}
74+
if let Some(value) = self.store {
75+
traceable.insert("store".to_owned(), json!(value));
76+
}
77+
if let Some(value) = self.include_usage {
78+
traceable.insert("include_usage".to_owned(), json!(value));
79+
}
80+
if !self.response_include.is_empty() {
81+
traceable.insert("response_include".to_owned(), json!(self.response_include));
82+
}
83+
if let Some(value) = self.top_logprobs {
84+
traceable.insert("top_logprobs".to_owned(), json!(value));
85+
}
86+
if let Some(value) = &self.reasoning {
87+
traceable.insert("reasoning".to_owned(), json!(value));
88+
}
89+
if let Some(value) = &self.verbosity {
90+
traceable.insert("verbosity".to_owned(), json!(value));
91+
}
92+
if !self.metadata.is_empty() {
93+
traceable.insert("metadata".to_owned(), json!(self.metadata));
94+
}
95+
traceable
96+
}
97+
4498
pub fn resolve(&self, override_settings: Option<&Self>) -> Self {
4599
let Some(override_settings) = override_settings else {
46100
return self.clone();
@@ -212,4 +266,32 @@ mod tests {
212266
assert_eq!(resolved.extra_args.get("timeout"), Some(&json!(10)));
213267
assert_eq!(resolved.extra_args.get("retry"), Some(&json!(2)));
214268
}
269+
270+
#[test]
271+
fn traceable_model_settings_omit_request_extras() {
272+
let settings = ModelSettings {
273+
temperature: Some(0.5),
274+
metadata: BTreeMap::from([("purpose".to_owned(), json!("trace"))]),
275+
extra_query: BTreeMap::from([("api-key".to_owned(), json!("query-secret"))]),
276+
extra_body: BTreeMap::from([("secret".to_owned(), json!("body-secret"))]),
277+
extra_headers: BTreeMap::from([(
278+
"authorization".to_owned(),
279+
json!("Bearer header-secret"),
280+
)]),
281+
extra_args: BTreeMap::from([("api_key".to_owned(), json!("arg-secret"))]),
282+
..Default::default()
283+
};
284+
285+
let traceable = settings.to_traceable_map();
286+
287+
assert_eq!(traceable.get("temperature"), Some(&json!(0.5)));
288+
assert_eq!(
289+
traceable.get("metadata"),
290+
Some(&json!({"purpose": "trace"}))
291+
);
292+
assert!(!traceable.contains_key("extra_query"));
293+
assert!(!traceable.contains_key("extra_body"));
294+
assert!(!traceable.contains_key("extra_headers"));
295+
assert!(!traceable.contains_key("extra_args"));
296+
}
215297
}

crates/agents-core/src/run.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,6 +1312,12 @@ impl Runner {
13121312
let settings = get_default_model_settings(requested_model.as_deref())
13131313
.resolve(agent.model_settings.as_ref())
13141314
.resolve(self.config.model_settings.as_ref());
1315+
let traceable_settings = settings.to_traceable_map();
1316+
if !traceable_settings.is_empty()
1317+
&& let SpanData::Generation(data) = &mut span.data
1318+
{
1319+
data.model_config = Some(traceable_settings);
1320+
}
13151321
let tools = internal_turn_preparation::get_all_tools(agent, context).await?;
13161322

13171323
if let Some(model_provider) = self
@@ -3555,6 +3561,57 @@ mod tests {
35553561
assert_eq!(exported_span["metadata"]["nested"], json!({"value": 1}));
35563562
}
35573563

3564+
#[tokio::test]
3565+
async fn generation_span_model_config_omits_request_extras() {
3566+
let _trace_guard = crate::tracing::setup::trace_provider_test_lock()
3567+
.lock()
3568+
.await;
3569+
let previous_provider = crate::tracing::get_trace_provider();
3570+
let _provider_reset = TraceProviderReset(previous_provider);
3571+
let provider = Arc::new(crate::tracing::DefaultTraceProvider::default())
3572+
as Arc<dyn crate::tracing::TraceProvider>;
3573+
crate::tracing::set_trace_provider(provider);
3574+
let processor = Arc::new(RecordingTraceProcessor::default());
3575+
crate::tracing::get_trace_provider().register_processor(processor.clone());
3576+
3577+
Runner::new()
3578+
.with_model_provider(Arc::new(StaticProvider {
3579+
model: Arc::new(RequestCaptureModel::default()),
3580+
}))
3581+
.with_config(RunConfig {
3582+
model_settings: Some(crate::ModelSettings {
3583+
temperature: Some(0.5),
3584+
extra_headers: BTreeMap::from([(
3585+
"authorization".to_owned(),
3586+
json!("Bearer provider-token"),
3587+
)]),
3588+
extra_query: BTreeMap::from([("api-key".to_owned(), json!("query-token"))]),
3589+
extra_body: BTreeMap::from([("secret".to_owned(), json!("body-token"))]),
3590+
extra_args: BTreeMap::from([("api_key".to_owned(), json!("arg-token"))]),
3591+
..Default::default()
3592+
}),
3593+
..RunConfig::default()
3594+
})
3595+
.run(&Agent::builder("assistant").build(), "hello")
3596+
.await
3597+
.expect("run should succeed");
3598+
3599+
let model_config = processor
3600+
.spans()
3601+
.into_iter()
3602+
.find_map(|span| match span.data {
3603+
crate::tracing::SpanData::Generation(data) => data.model_config,
3604+
_ => None,
3605+
})
3606+
.expect("generation span should carry model config");
3607+
3608+
assert_eq!(model_config.get("temperature"), Some(&json!(0.5)));
3609+
assert!(!model_config.contains_key("extra_headers"));
3610+
assert!(!model_config.contains_key("extra_query"));
3611+
assert!(!model_config.contains_key("extra_body"));
3612+
assert!(!model_config.contains_key("extra_args"));
3613+
}
3614+
35583615
#[tokio::test]
35593616
async fn multiprovider_forwards_harness_metadata_hooks() {
35603617
struct HarnessMetadataProvider {

0 commit comments

Comments
 (0)