- 1
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] - 2
- 3
//! Capacity feedback (docs/design/68-context-engine.md §1 "Feedback"): a - 4
//! provider that reports no prefill duration of its own (everyone but - 5
//! Ollama) must still feed `prefill_tps`, from the wall-clock time between - 6
//! sending the request and the first `StreamEvent` off the wire, measured - 7
//! by `Agent::complete_with_reliability`'s own stream-consuming loop and - 8
//! threaded through `StepLedger::last_first_token_ms`. - 9
- 10
use std::path::Path; - 11
use std::sync::Arc; - 12
use std::time::Duration; - 13
- 14
use tempfile::tempdir; - 15
use tokio::sync::mpsc; - 16
use tokio_util::sync::CancellationToken; - 17
- 18
use vak_agent::{Agent, AgentConfig, AutoApprove, SteeringQueues, TurnOutcome}; - 19
use vak_context::capacity::{ - 20
CacheBehaviour, CapacityProfile, Horizon, ProbeProvenance, ProfileKey, - 21
}; - 22
use vak_llm::stream; - 23
use vak_llm::types::{AssistantMessage, ChatRequest, ContentBlock, StopReason, Usage}; - 24
use vak_llm::{EventStream, LlmError, Provider}; - 25
use vak_permission::{Mode, PermissionEngine}; - 26
use vak_session::types::{FrozenContract, SessionHeader}; - 27
use vak_session::{SessionLog, SessionPath}; - 28
- 29
/// Answers after a deliberate delay so the first-token latency the harness - 30
/// measures is unambiguously non-zero, and reports no `prefill_ms` of its - 31
/// own (unlike Ollama) so the only way `prefill_tps` gets a sample is via - 32
/// the caller-measured wall-clock latency. - 33
struct SlowNoPrefillMs; - 34
- 35
#[async_trait::async_trait] - 36
impl Provider for SlowNoPrefillMs { - 37
fn name(&self) -> &str { - 38
"slow-no-prefill" - 39
} - 40
- 41
async fn stream( - 42
&self, - 43
_request: ChatRequest, - 44
_cancel: CancellationToken, - 45
) -> Result<EventStream, LlmError> { - 46
let (mut sink, rx) = stream::channel(64); - 47
tokio::time::sleep(Duration::from_millis(30)).await; - 48
let message = AssistantMessage { - 49
content: vec![ContentBlock::text("done")], - 50
stop_reason: StopReason::EndTurn, - 51
usage: Usage { - 52
input_tokens: 500, - 53
output_tokens: 3, - 54
..Default::default() - 55
}, - 56
model: "test-model".into(), - 57
response_id: None, - 58
}; - 59
sink.push(stream::StreamEvent::Start { - 60
partial: message.clone(), - 61
}); - 62
sink.close_message(message).await; - 63
Ok(rx) - 64
} - 65
} - 66
- 67
fn session_paths(dir: &Path) -> (std::path::PathBuf, std::path::PathBuf) { - 68
let cwd = dir.to_path_buf(); - 69
let home = cwd.join(".vak-home"); - 70
( - 71
home.clone(), - 72
SessionPath::new_session_file(&home, &cwd, "capacity-feedback"), - 73
) - 74
} - 75
- 76
fn flat_profile(declared_window: u64) -> CapacityProfile { - 77
CapacityProfile::from_probe( - 78
declared_window, - 79
None, - 80
Horizon { - 81
tokens: declared_window, - 82
confidence: 0.9, - 83
last_confirmed: std::time::SystemTime::now(), - 84
}, - 85
CacheBehaviour::Unknown, - 86
1_024, - 87
ProbeProvenance { - 88
probed_at: std::time::SystemTime::now(), - 89
rungs: Vec::new(), - 90
signals: Vec::new(), - 91
metadata_digest: "digest".into(), - 92
quantisation: None, - 93
}, - 94
) - 95
} - 96
- 97
#[tokio::test(flavor = "multi_thread", worker_threads = 4)] - 98
async fn non_ollama_provider_still_gets_a_prefill_tps_sample_from_wall_clock_latency() { - 99
let dir = tempdir().unwrap(); - 100
let (home, path) = session_paths(dir.path()); - 101
std::fs::create_dir_all(&home).unwrap(); - 102
let provider: Arc<dyn Provider> = Arc::new(SlowNoPrefillMs); - 103
let header = SessionHeader { - 104
agent: None, - 105
session_id: "capacity-feedback".into(), - 106
created_at: chrono::Utc::now(), - 107
cwd: dir.path().to_path_buf(), - 108
parent_session_id: None, - 109
contract_id: None, - 110
work_item_id: None, - 111
conversation: None, - 112
contract: FrozenContract { - 113
app_version: "0".into(), - 114
provider: provider.name().to_string(), - 115
model: "test-model".into(), - 116
route_ladder: Vec::new(), - 117
route_objective: String::new(), - 118
route_annotations: Vec::new(), - 119
system_prompt: "sys".into(), - 120
permission_mode: "workspace-write".into(), - 121
capabilities: Vec::new(), - 122
prompt_layers: Vec::new(), - 123
}, - 124
}; - 125
let log = SessionLog::create(path, header).unwrap(); - 126
let mut cfg = AgentConfig::new("sys"); - 127
cfg.model = "test-model".into(); - 128
cfg.mode = Mode::FullAccess; - 129
cfg.permission = Some(Arc::new(PermissionEngine::default())); - 130
cfg.approver = Some(Arc::new(AutoApprove)); - 131
cfg.capacity = Some(flat_profile(128_000)); - 132
- 133
let mut agent = Agent::new(provider, log, cfg); - 134
- 135
let (ev_tx, mut ev_rx) = mpsc::channel(256); - 136
tokio::spawn(async move { while ev_rx.recv().await.is_some() {} }); - 137
let cancel = CancellationToken::new(); - 138
let steering = SteeringQueues::new(); - 139
let outcome = agent.run("hello", &steering, cancel, ev_tx).await; - 140
assert!(matches!(outcome, TurnOutcome::Completed { .. })); - 141
- 142
let session = agent.into_session().await; - 143
let key = ProfileKey { - 144
provider: "slow-no-prefill".into(), - 145
model: "test-model".into(), - 146
quantisation: None, - 147
}; - 148
let profile: CapacityProfile = session - 149
.latest_capacity_profile(&key) - 150
.expect("capacity feedback activity recorded with a matching profile"); - 151
assert_eq!( - 152
profile.prefill_tps.samples, 1, - 153
"wall-clock first-token latency must feed prefill_tps even though \ - 154
this provider never reports usage.prefill_ms itself" - 155
); - 156
assert!( - 157
profile.prefill_tps.value > 0.0, - 158
"prefill_tps value should be positive: {}", - 159
profile.prefill_tps.value - 160
); - 161
} - 162
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.