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
11 changes: 11 additions & 0 deletions crates/jp_cli/src/cmd/conversation/summarize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use jp_llm::{
event_builder::EventBuilder,
model::ModelDetails,
provider,
query::Truncation,
retry::{RetryConfig, collect_with_retry},
window,
};
Expand Down Expand Up @@ -184,6 +185,10 @@ async fn summarize_stream(
},
tools: vec![],
tool_choice: jp_config::assistant::tool_choice::ToolChoice::default(),
// The summary is stored as standing for every turn in the range,
// so a provider that quietly dropped part of the request would
// hand back a summary covering less than it claims.
truncation: Truncation::Forbidden,
};

let llm_events = collect_with_retry(provider, model_details, query, &retry_config).await?;
Expand Down Expand Up @@ -241,6 +246,12 @@ enum StreamOutcome {
/// truncated or declined response would otherwise be stored as the summary and
/// replace the turns it was meant to stand in for, silently dropping whatever
/// the model never got to.
///
/// This covers a response the model cut short.
/// A request the *provider* cut short arrives here as an ordinary
/// [`FinishReason::Completed`] and is indistinguishable from a complete one, so
/// that half is prevented when the request is built, by asking for
/// [`Truncation::Forbidden`].
fn summarize_events(events: Vec<Event>) -> StreamOutcome {
let mut builder = EventBuilder::new();
let mut flushed = Vec::new();
Expand Down
3 changes: 2 additions & 1 deletion crates/jp_cli/src/cmd/query/tool/inquiry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use jp_llm::{
Provider,
event_builder::structured_data,
model::ModelDetails,
query::ChatQuery,
query::{ChatQuery, Truncation},
retry::{RetryConfig, collect_with_retry},
tool::ToolDefinition,
window,
Expand Down Expand Up @@ -310,6 +310,7 @@ impl InquiryBackend for LlmInquiryBackend {
thread,
tools: self.tools.clone(),
tool_choice: ToolChoice::None,
truncation: Truncation::Allowed,
};

let retry_config =
Expand Down
6 changes: 5 additions & 1 deletion crates/jp_cli/src/cmd/query/turn_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use jp_llm::{
event::{Event, EventPart, FinishReason, ToolCallPart},
model::ModelDetails,
provider::get_provider,
query::ChatQuery,
query::{ChatQuery, Truncation},
tool::{InvocationContext, ToolDefinition, executor::Executor},
with_idle_timeout, with_output_limit,
};
Expand Down Expand Up @@ -310,6 +310,10 @@ pub(super) async fn run_turn_loop(
thread,
tools: tools.to_vec(),
tool_choice: tool_choice.clone(),
// Nothing fits the stream to the window on this path, so a
// conversation that outgrows it stays answerable only if
// the provider is allowed to drop what it cannot hold.
truncation: Truncation::Allowed,
};

// Claim the waiting region BEFORE the HTTP request. Dropping
Expand Down
1 change: 1 addition & 0 deletions crates/jp_llm/src/provider/anthropic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,7 @@ fn create_request(
thread,
tools,
mut tool_choice,
..
} = query;

let mut builder = types::CreateMessagesRequestBuilder::default();
Expand Down
33 changes: 32 additions & 1 deletion crates/jp_llm/src/provider/anthropic_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ use serde_json::Map;
use test_log::test;

use super::*;
use crate::test::{TestRequest, run_test};
use crate::{
query::Truncation,
test::{TestRequest, run_test},
};

const MAGIC_STRING: &str = "ANTHROPIC_MAGIC_STRING_TRIGGER_REDACTED_THINKING_46C9A13E193C177646C7398A98432ECCCE4C1253D5E2D82641AC0E52CC2876CB";

Expand Down Expand Up @@ -278,6 +281,7 @@ fn test_opus_4_6_request_uses_adaptive_thinking() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -334,6 +338,7 @@ fn test_opus_4_7_xhigh_effort_mapping() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -385,6 +390,7 @@ fn test_opus_4_6_xhigh_falls_back_to_high() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -429,6 +435,7 @@ fn test_opus_4_6_max_effort_mapping() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -734,6 +741,7 @@ fn test_unknown_model_requests_summarized_thinking() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -811,6 +819,7 @@ fn test_unknown_reasoning_infers_adaptive_thinking() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -848,6 +857,7 @@ fn tier_request(
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

create_request(&model, query, true, &BetaFeatures(vec![])).map(|(request, ..)| request)
Expand Down Expand Up @@ -953,6 +963,7 @@ fn test_off_on_unknown_model_attempts_disable() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -1070,6 +1081,7 @@ fn test_fable_5_reasoning_off_omits_disabled_thinking() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -1105,6 +1117,7 @@ fn test_opus_4_5_uses_budgetted_thinking() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -1157,6 +1170,7 @@ fn test_structured_output_sets_format() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -1231,6 +1245,7 @@ fn test_schema_ignored_when_last_event_is_not_chat_request() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -1271,6 +1286,7 @@ fn test_adaptive_thinking_with_structured_output() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -1333,6 +1349,7 @@ fn test_forced_tool_with_reasoning_returns_fallback() {
parameters: json!({ "type": "object", "properties": {} }),
}],
tool_choice: ToolChoice::Function("my_tool".into()),
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -1405,6 +1422,7 @@ fn test_forced_tool_thinking_always_on_uses_escalating_nudge() {
parameters: json!({ "type": "object", "properties": {} }),
}],
tool_choice: ToolChoice::Function("my_tool".into()),
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -1489,6 +1507,7 @@ fn test_forced_tool_thinking_always_on_reasoning_off_still_soft_forces() {
parameters: json!({ "type": "object", "properties": {} }),
}],
tool_choice: ToolChoice::Function("my_tool".into()),
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -1554,6 +1573,7 @@ fn test_forced_tool_function_multi_tool_preserves_name() {
},
],
tool_choice: ToolChoice::Function("commit".into()),
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -1621,6 +1641,7 @@ fn test_forced_tool_without_reasoning_no_fallback() {
parameters: json!({ "type": "object", "properties": {} }),
}],
tool_choice: ToolChoice::Required,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -1660,6 +1681,7 @@ fn test_auto_tool_choice_with_reasoning_no_fallback() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -1802,6 +1824,7 @@ fn test_continue_injected_when_prefill_unsupported() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -1851,6 +1874,7 @@ fn test_prefill_preserved_for_supported_models() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -1890,6 +1914,7 @@ fn test_no_injection_when_last_message_is_user() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -1933,6 +1958,7 @@ fn test_create_request_resends_signed_thinking_as_native_block() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -1989,6 +2015,7 @@ fn test_create_request_resends_redacted_thinking_as_native_block() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -2041,6 +2068,7 @@ fn test_create_request_falls_back_to_think_tags_without_signature() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -2099,6 +2127,7 @@ fn test_create_request_drops_empty_reasoning_instead_of_empty_think_tags() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -2155,6 +2184,7 @@ fn test_create_request_downgrades_trailing_assistant_thinking() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down Expand Up @@ -2220,6 +2250,7 @@ fn test_create_request_drops_trailing_redacted_thinking() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let beta = BetaFeatures(vec![]);
Expand Down
1 change: 1 addition & 0 deletions crates/jp_llm/src/provider/cerebras.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ fn create_request(model: &ModelDetails, query: ChatQuery) -> Result<(Value, bool
thread,
tools,
tool_choice,
..
} = query;

let structured_schema = thread.events.schema();
Expand Down
5 changes: 4 additions & 1 deletion crates/jp_llm/src/provider/cerebras_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use jp_conversation::{ConversationEvent, event::ToolCallRequest};
use reqwest_eventsource::Error as SseError;

use super::*;
use crate::provider::openai_compat::StreamChunk;
use crate::{provider::openai_compat::StreamChunk, query::Truncation};

/// Regression: a model absent from the table must still request the parsed
/// reasoning format.
Expand All @@ -25,6 +25,7 @@ fn test_unknown_model_requests_parsed_reasoning() {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
};

let (body, _) = create_request(&model, query).unwrap();
Expand All @@ -51,6 +52,7 @@ fn reasoning_query(reasoning: jp_config::model::parameters::PartialReasoningConf
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
}
}

Expand All @@ -70,6 +72,7 @@ fn tier_query(tier: Option<ServiceTier>) -> ChatQuery {
},
tools: vec![],
tool_choice: ToolChoice::Auto,
truncation: Truncation::default(),
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/jp_llm/src/provider/google.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ fn create_request(
thread,
tools,
tool_choice,
..
} = query;

// Extract schema and config before into_parts() consumes the thread.
Expand Down
Loading
Loading