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
8 changes: 7 additions & 1 deletion crates/libsy-llm-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2652,7 +2652,9 @@ mod tests {
{"type": "reasoning", "encrypted_content": ""},
{"type": "reasoning"},
{"type": "function_call", "call_id": "call_1", "name": "shell", "arguments": "{}"},
{"type": "function_call_output", "call_id": "call_1", "output": "ok"}
{"type": "function_call_output", "call_id": "call_1", "output": "ok"},
{"type": "web_search_call", "id": "ws_1", "status": "completed", "action": {"type": "search", "query": "switchyard"}},
{"type": "message", "role": "assistant", "content": [{"type": "output_text", "text": "found it"}]}
]);
for store in [Some(true), Some(false), None] {
for is_drop in [false, true] {
Expand Down Expand Up @@ -2711,6 +2713,10 @@ mod tests {
}
}
expected.extend([history[6].clone(), history[7].clone()]);
if !is_drop {
expected.push(history[8].clone());
}
expected.push(history[9].clone());
assert_eq!(
body["input"],
json!(expected),
Expand Down
11 changes: 10 additions & 1 deletion crates/libsy-llm-client/src/responses_reasoning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ pub enum ResponsesReasoningPolicy {
/// This is the default for every Responses model, regardless of its URL.
#[default]
PreserveEncrypted,
/// Drop all reasoning items while preserving messages and tool-call history.
/// Drop all reasoning items and replayed hosted web searches while preserving
/// messages and tool-call history.
///
/// Use this for local Responses-compatible servers that cannot consume
/// another provider's encrypted reasoning representation.
Expand All @@ -37,6 +38,14 @@ impl ResponsesReasoningPolicy {
let Some(object) = item.as_object_mut() else {
return true;
};
// A hosted provider's server-side web search is replayed as a `web_search_call` item,
// which a local server cannot run or type (llama.cpp answers 400 "Cannot determine type
// of 'item'"). Its findings are in the assistant message that follows it.
if self == Self::Drop
&& object.get("type").and_then(Value::as_str) == Some("web_search_call")
{
return false;
}
if object.get("type").and_then(Value::as_str) != Some("reasoning") {
return true;
}
Expand Down
6 changes: 5 additions & 1 deletion docs/reference/toml_schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@ non-empty `encrypted_content` or a stored reasoning `id` are retained with empty
`content`. ID-only reasoning is removed when the request sets `store: false`.
Switchyard's translated Anthropic reasoning is always removed.
Reasoning without either is removed. Set `responses_reasoning = "drop"` explicitly for a backend
that cannot consume provider reasoning. Both modes keep messages, tool
that cannot consume provider reasoning. `"drop"` also removes replayed
`web_search_call` items: a conversation that ran a hosted web search replays
them, and a local server such as llama.cpp rejects them with 400
`Cannot determine type of 'item'`. The search results stay in the assistant
message that follows. Both modes keep messages, tool
calls and tool results. The setting is rejected on other client formats, even
when no target uses the client. It is not inferred from the model name or URL.

Expand Down
Loading