Skip to content

Commit 5c6d6d9

Browse files
v0.14.7 apiproxy: slim markers (K) + intra-request hash dedup (L)
Two compression refinements, each test-guarded. The third planned feature (D — system-reminder template detection via OMCD) is honestly redundant: v0.14.6 cache_control already gets us the 90% Anthropic prompt-cache discount on stable prefixes, and v0.14.7-L deduplicates repeated content within a single request. Together these cover what template detection would have done, without the special-case parser. K — slim markers for content the LLM doesn't benefit from previewing: Old marker: <omc:ref hash_str="..." bytes="..." preview="..."/> ~150-200 bytes including the preview text New slim: <omc:ref h="..." b="N"/> ~30-40 bytes Drives a MarkerKind enum that classifies each rewrite site: ToolResult — keep preview (LLM uses it to decide expansion) HistoricalText — drop preview (already seen in prior turns) ToolUseInput — drop preview (LLM emitted it itself) L — intra-request hash dedup. When the same content appears multiple times in one request (common for system reminders and file contents), the first occurrence emits the full marker; subsequent occurrences collapse to <omc:ref h="..."/> (~25 bytes). Combined savings estimate on Claude Code traffic: K: 10-15 KB/turn (hundreds of marker-byte reductions) L: 5-10 KB/turn (system-reminder and repeated tool_result content) Tests added (now 11 total, all green): - slim_markers_drop_preview_for_historical_text - intra_request_dedup_collapses_repeats - existing 9 tests still pass Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent c3ce86a commit 5c6d6d9

1 file changed

Lines changed: 185 additions & 31 deletions

File tree

omnimcode-apiproxy/src/main.rs

Lines changed: 185 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -509,13 +509,17 @@ async fn stats_endpoint(State(state): State<AppState>) -> Response {
509509
fn rewrite_request_body(body: &[u8], state: &AppState) -> Result<(Bytes, RewriteOutcome)> {
510510
let mut v: Value = serde_json::from_slice(body)?;
511511
let mut out = RewriteOutcome::default();
512+
// v0.14.7-L: track hashes already seen this request so duplicates can
513+
// emit the bare-minimum `<omc:ref h="..."/>` form.
514+
let mut seen: std::collections::HashSet<i64> = std::collections::HashSet::new();
512515

513516
// ---- system prompt (top-level field) ----
514517
if let Some(system) = v.get_mut("system") {
515518
match system {
516519
Value::String(s) => {
517520
if s.len() >= state.rewrite_threshold {
518-
if let Ok(marker) = make_marker(s, state) {
521+
if let Ok(marker) = make_marker_with_dedup(
522+
s, state, MarkerKind::HistoricalText, Some(&mut seen)) {
519523
out.bytes_system += s.len();
520524
out.rewritten_count += 1;
521525
*system = Value::String(marker);
@@ -527,7 +531,9 @@ fn rewrite_request_body(body: &[u8], state: &AppState) -> Result<(Bytes, Rewrite
527531
if block.get("type").and_then(Value::as_str) == Some("text") {
528532
let Some(text) = block.get("text").and_then(Value::as_str) else { continue };
529533
if text.len() < state.rewrite_threshold { continue; }
530-
let Ok(marker) = make_marker(text, state) else { continue };
534+
let Ok(marker) = make_marker_with_dedup(
535+
text, state, MarkerKind::HistoricalText, Some(&mut seen))
536+
else { continue };
531537
out.bytes_system += text.len();
532538
out.rewritten_count += 1;
533539
// Mutate ONLY the `text` field; preserve cache_control + everything else
@@ -555,7 +561,8 @@ fn rewrite_request_body(body: &[u8], state: &AppState) -> Result<(Bytes, Rewrite
555561
match content {
556562
Value::String(s) => {
557563
if s.len() >= state.rewrite_threshold {
558-
if let Ok(marker) = make_marker(s, state) {
564+
if let Ok(marker) = make_marker_with_dedup(
565+
s, state, MarkerKind::HistoricalText, Some(&mut seen)) {
559566
out.bytes_messages_text += s.len();
560567
out.rewritten_count += 1;
561568
*content = Value::String(marker);
@@ -569,14 +576,16 @@ fn rewrite_request_body(body: &[u8], state: &AppState) -> Result<(Bytes, Rewrite
569576
"text" => {
570577
let Some(text) = block.get("text").and_then(Value::as_str) else { continue };
571578
if text.len() < state.rewrite_threshold { continue; }
572-
let Ok(marker) = make_marker(text, state) else { continue };
579+
let Ok(marker) = make_marker_with_dedup(
580+
text, state, MarkerKind::HistoricalText, Some(&mut seen))
581+
else { continue };
573582
out.bytes_messages_text += text.len();
574583
out.rewritten_count += 1;
575584
block["text"] = Value::String(marker);
576585
}
577586
"tool_result" => {
578587
if let Some(inner) = block.get_mut("content") {
579-
rewrite_tool_result_content(inner, state, &mut out);
588+
rewrite_tool_result_content(inner, state, &mut out, &mut seen);
580589
}
581590
}
582591
"tool_use" => {
@@ -585,7 +594,7 @@ fn rewrite_request_body(body: &[u8], state: &AppState) -> Result<(Bytes, Rewrite
585594
// LLM doesn't see (and thus copy) a fake field name
586595
// when generating fresh tool calls in later turns.
587596
if let Some(input) = block.get_mut("input") {
588-
rewrite_strings_recursive(input, state, &mut out);
597+
rewrite_strings_recursive(input, state, &mut out, &mut seen);
589598
}
590599
}
591600
_ => {}
@@ -603,7 +612,8 @@ fn rewrite_request_body(body: &[u8], state: &AppState) -> Result<(Bytes, Rewrite
603612
if let Some(desc) = tool.get_mut("description") {
604613
if let Value::String(s) = desc {
605614
if s.len() >= state.rewrite_threshold {
606-
if let Ok(marker) = make_marker(s, state) {
615+
if let Ok(marker) = make_marker_with_dedup(
616+
s, state, MarkerKind::HistoricalText, Some(&mut seen)) {
607617
out.bytes_tool_definitions += s.len();
608618
out.rewritten_count += 1;
609619
*desc = Value::String(marker);
@@ -615,7 +625,7 @@ fn rewrite_request_body(body: &[u8], state: &AppState) -> Result<(Bytes, Rewrite
615625
// property descriptions, enums, etc. — preserves schema structure.
616626
if let Some(schema) = tool.get_mut("input_schema") {
617627
let before_count = out.rewritten_count;
618-
rewrite_schema_strings(schema, state, &mut out);
628+
rewrite_schema_strings(schema, state, &mut out, &mut seen);
619629
if out.rewritten_count > before_count {
620630
// already counted via rewrite_schema_strings into the
621631
// bytes_tool_definitions field
@@ -760,22 +770,24 @@ fn insert_cache_control_on_last_block(msg: &mut Value) -> bool {
760770
/// fields, `enum` arrays of strings, and nested `properties` — all candidates.
761771
fn rewrite_schema_strings(
762772
val: &mut Value, state: &AppState, out: &mut RewriteOutcome,
773+
seen: &mut std::collections::HashSet<i64>,
763774
) {
764775
match val {
765776
Value::String(s) => {
766777
if s.len() >= state.rewrite_threshold {
767-
if let Ok(marker) = make_marker(s, state) {
778+
if let Ok(marker) = make_marker_with_dedup(
779+
s, state, MarkerKind::HistoricalText, Some(seen)) {
768780
out.bytes_tool_definitions += s.len();
769781
out.rewritten_count += 1;
770782
*val = Value::String(marker);
771783
}
772784
}
773785
}
774786
Value::Object(map) => {
775-
for (_k, v) in map.iter_mut() { rewrite_schema_strings(v, state, out); }
787+
for (_k, v) in map.iter_mut() { rewrite_schema_strings(v, state, out, seen); }
776788
}
777789
Value::Array(arr) => {
778-
for v in arr.iter_mut() { rewrite_schema_strings(v, state, out); }
790+
for v in arr.iter_mut() { rewrite_schema_strings(v, state, out, seen); }
779791
}
780792
_ => {}
781793
}
@@ -792,11 +804,13 @@ fn rewrite_schema_strings(
792804
/// 3. Array elements that are strings → same rule, in place.
793805
fn rewrite_strings_recursive(
794806
val: &mut Value, state: &AppState, out: &mut RewriteOutcome,
807+
seen: &mut std::collections::HashSet<i64>,
795808
) {
796809
match val {
797810
Value::String(s) => {
798811
if s.len() >= state.rewrite_threshold {
799-
if let Ok(marker) = make_marker(s, state) {
812+
if let Ok(marker) = make_marker_with_dedup(
813+
s, state, MarkerKind::ToolUseInput, Some(seen)) {
800814
out.bytes_tool_use_input += s.len();
801815
out.rewritten_count += 1;
802816
*val = Value::String(marker);
@@ -805,12 +819,12 @@ fn rewrite_strings_recursive(
805819
}
806820
Value::Object(map) => {
807821
for (_k, v) in map.iter_mut() {
808-
rewrite_strings_recursive(v, state, out);
822+
rewrite_strings_recursive(v, state, out, seen);
809823
}
810824
}
811825
Value::Array(arr) => {
812826
for v in arr.iter_mut() {
813-
rewrite_strings_recursive(v, state, out);
827+
rewrite_strings_recursive(v, state, out, seen);
814828
}
815829
}
816830
_ => {}
@@ -819,11 +833,13 @@ fn rewrite_strings_recursive(
819833

820834
fn rewrite_tool_result_content(
821835
inner: &mut Value, state: &AppState, out: &mut RewriteOutcome,
836+
seen: &mut std::collections::HashSet<i64>,
822837
) {
823838
match inner {
824839
Value::String(s) => {
825840
if s.len() >= state.rewrite_threshold {
826-
if let Ok(marker) = make_marker(s, state) {
841+
if let Ok(marker) = make_marker_with_dedup(
842+
s, state, MarkerKind::ToolResult, Some(seen)) {
827843
out.bytes_tool_result += s.len();
828844
out.rewritten_count += 1;
829845
*inner = Value::String(marker);
@@ -835,7 +851,9 @@ fn rewrite_tool_result_content(
835851
if part.get("type").and_then(Value::as_str) == Some("text") {
836852
let Some(text) = part.get("text").and_then(Value::as_str) else { continue };
837853
if text.len() < state.rewrite_threshold { continue; }
838-
let Ok(marker) = make_marker(text, state) else { continue };
854+
let Ok(marker) = make_marker_with_dedup(
855+
text, state, MarkerKind::ToolResult, Some(seen))
856+
else { continue };
839857
out.bytes_tool_result += text.len();
840858
out.rewritten_count += 1;
841859
part["text"] = Value::String(marker);
@@ -846,23 +864,68 @@ fn rewrite_tool_result_content(
846864
}
847865
}
848866

849-
fn make_marker(text: &str, state: &AppState) -> Result<String> {
867+
/// What category of content is being compressed. Drives whether the marker
868+
/// gets a `preview=` attribute (helpful for tool_result, wasted bytes for
869+
/// historical assistant text or tool_use inputs).
870+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
871+
enum MarkerKind {
872+
/// `tool_result.content` — LLM benefits from preview to know if it
873+
/// needs to expand. Keep the full marker.
874+
ToolResult,
875+
/// Historical assistant or user text block, system prompt, tool def.
876+
/// LLM has already "seen" this in a prior turn; preview is wasted.
877+
HistoricalText,
878+
/// `tool_use.input` field value — LLM emitted this itself, doesn't
879+
/// need to re-read its own output. Preview is wasted.
880+
ToolUseInput,
881+
}
882+
883+
fn make_marker(text: &str, state: &AppState, kind: MarkerKind) -> Result<String> {
884+
make_marker_with_dedup(text, state, kind, None)
885+
}
886+
887+
/// v0.14.7-L: intra-request dedup. `seen_hashes` is `Some(set)` when we want
888+
/// to track repeated content within a single request — first occurrence
889+
/// emits the full marker, subsequent emit the bare `<omc:ref h="..."/>`
890+
/// form (~30 bytes instead of ~150 for the duplicates).
891+
fn make_marker_with_dedup(
892+
text: &str, state: &AppState, kind: MarkerKind,
893+
seen_hashes: Option<&mut std::collections::HashSet<i64>>,
894+
) -> Result<String> {
850895
let hash = state.store.store(PROXY_CACHE_NAMESPACE, text)
851896
.map_err(anyhow::Error::msg)?;
852-
// For very large blocks the LLM almost certainly wants either:
853-
// (a) the full content (expand via tool), or (b) to move on.
854-
// The preview adds no decision-quality. Drop it past 8 KB.
855-
if text.len() >= 8192 {
856-
return Ok(format!("<omc:ref h=\"{}\" b=\"{}\"/>", hash, text.len()));
897+
898+
// v0.14.7-L: if we've already emitted a full marker for this hash this
899+
// request, the subsequent ones can be the bare-minimum form.
900+
if let Some(set) = seen_hashes {
901+
if !set.insert(hash) {
902+
// already present
903+
return Ok(format!("<omc:ref h=\"{}\"/>", hash));
904+
}
905+
}
906+
907+
// v0.14.7-K: drop preview for content the LLM doesn't benefit from
908+
// previewing. Saves ~150 bytes/marker × 100s of markers per turn.
909+
match kind {
910+
MarkerKind::HistoricalText | MarkerKind::ToolUseInput => {
911+
Ok(format!("<omc:ref h=\"{}\" b=\"{}\"/>", hash, text.len()))
912+
}
913+
MarkerKind::ToolResult => {
914+
// Keep the preview only when content is large enough that the
915+
// LLM might want to decide whether to expand.
916+
if text.len() >= 8192 {
917+
return Ok(format!("<omc:ref h=\"{}\" b=\"{}\"/>", hash, text.len()));
918+
}
919+
let preview: String = text.chars()
920+
.filter(|c| !c.is_control())
921+
.take(state.preview_bytes)
922+
.collect();
923+
Ok(format!(
924+
"<omc:ref hash_str=\"{}\" bytes=\"{}\" preview={:?}/>",
925+
hash, text.len(), preview
926+
))
927+
}
857928
}
858-
let preview: String = text.chars()
859-
.filter(|c| !c.is_control())
860-
.take(state.preview_bytes)
861-
.collect();
862-
Ok(format!(
863-
"<omc:ref hash_str=\"{}\" bytes=\"{}\" preview={:?}/>",
864-
hash, text.len(), preview
865-
))
866929
}
867930

868931
/// Add the omc_proxy_expand_ref tool to the request's tools array so the
@@ -993,7 +1056,7 @@ mod tests {
9931056
fn marker_round_trip_lossless() {
9941057
let state = test_state(256);
9951058
let original = "abc🎯 ñ é 漢字\nline2\n\tindented\n".repeat(50); // multi-byte, control chars
996-
let marker = make_marker(&original, &state).unwrap();
1059+
let marker = make_marker(&original, &state, MarkerKind::ToolResult).unwrap();
9971060
// Extract hash_str from the marker
9981061
let hash_attr = marker.split("hash_str=\"").nth(1).unwrap();
9991062
let hash_str = hash_attr.split('"').next().unwrap();
@@ -1228,6 +1291,97 @@ mod tests {
12281291
"user's cache_control preserved");
12291292
}
12301293

1294+
/// v0.14.7-K: markers for HistoricalText/ToolUseInput drop the
1295+
/// preview attribute (saves ~150 bytes/marker).
1296+
#[test]
1297+
fn slim_markers_drop_preview_for_historical_text() {
1298+
let state = test_state(256);
1299+
let big = "X".repeat(1000);
1300+
// Historical text in messages[]
1301+
let req = json!({
1302+
"model": "test", "max_tokens": 10,
1303+
"messages": [
1304+
{"role": "assistant", "content": big.clone()},
1305+
{"role": "user", "content": "ask"}
1306+
]
1307+
});
1308+
let body = serde_json::to_vec(&req).unwrap();
1309+
let (out, _) = rewrite_request_body(&body, &state).unwrap();
1310+
let v: Value = serde_json::from_slice(&out).unwrap();
1311+
let marker = v["messages"][0]["content"].as_str().unwrap();
1312+
// HistoricalText markers should be the slim form: <omc:ref h="..." b="N"/>
1313+
assert!(marker.contains(" h=\""), "slim marker should use h= not hash_str=");
1314+
assert!(marker.contains(" b=\""), "slim marker should use b= not bytes=");
1315+
assert!(!marker.contains("preview="),
1316+
"slim marker for HistoricalText must not have preview");
1317+
// tool_result markers, by contrast, keep preview (for content < 8KB)
1318+
let req2 = json!({
1319+
"model": "test", "max_tokens": 10,
1320+
"messages": [
1321+
{"role": "user", "content": [
1322+
{"type": "tool_result", "tool_use_id": "x", "content": big.clone()}
1323+
]},
1324+
{"role": "user", "content": "ask"}
1325+
]
1326+
});
1327+
let (out2, _) = rewrite_request_body(&serde_json::to_vec(&req2).unwrap(), &state).unwrap();
1328+
let v2: Value = serde_json::from_slice(&out2).unwrap();
1329+
let tr_marker = v2["messages"][0]["content"][0]["content"].as_str().unwrap();
1330+
assert!(tr_marker.contains("preview="),
1331+
"tool_result marker should keep preview (LLM needs it to decide expansion)");
1332+
}
1333+
1334+
/// v0.14.7-L: when the same content appears twice in one request,
1335+
/// the second occurrence collapses to bare `<omc:ref h="..."/>`.
1336+
#[test]
1337+
fn intra_request_dedup_collapses_repeats() {
1338+
let state = test_state(256);
1339+
let big = "REPEATING CONTENT BLOCK ".repeat(50); // ~1200 bytes
1340+
let req = json!({
1341+
"model": "test", "max_tokens": 10,
1342+
"messages": [
1343+
{"role": "assistant", "content": big.clone()},
1344+
{"role": "user", "content": big.clone()},
1345+
{"role": "assistant", "content": big.clone()},
1346+
{"role": "user", "content": "current"}
1347+
]
1348+
});
1349+
let body = serde_json::to_vec(&req).unwrap();
1350+
let (out, _) = rewrite_request_body(&body, &state).unwrap();
1351+
let v: Value = serde_json::from_slice(&out).unwrap();
1352+
// Helper: pull marker text from a message regardless of whether
1353+
// it's string-form or array-form (cache_control insertion can
1354+
// convert string-form to array-form mid-pass).
1355+
let extract = |idx: usize| -> String {
1356+
let c = &v["messages"][idx]["content"];
1357+
if let Some(s) = c.as_str() { return s.to_string(); }
1358+
if let Some(arr) = c.as_array() {
1359+
if let Some(first) = arr.first() {
1360+
if let Some(t) = first.get("text").and_then(Value::as_str) {
1361+
return t.to_string();
1362+
}
1363+
}
1364+
}
1365+
panic!("could not extract marker from messages[{}]: {}", idx, c)
1366+
};
1367+
let m0 = extract(0);
1368+
let m1 = extract(1);
1369+
let m2 = extract(2);
1370+
// First occurrence: full slim marker with b=
1371+
assert!(m0.contains(" b=\""), "first marker should be full: {}", m0);
1372+
// Second + third: bare form (no b= attr)
1373+
assert!(!m1.contains(" b=\""),
1374+
"second occurrence should be bare ref: {}", m1);
1375+
assert!(!m2.contains(" b=\""),
1376+
"third occurrence should be bare ref: {}", m2);
1377+
// All three reference the same hash
1378+
let extract_h = |m: &str| -> String {
1379+
m.split(" h=\"").nth(1).unwrap().split('"').next().unwrap().to_string()
1380+
};
1381+
assert_eq!(extract_h(&m0), extract_h(&m1));
1382+
assert_eq!(extract_h(&m0), extract_h(&m2));
1383+
}
1384+
12311385
/// Multi-turn dogfood simulation: walk a conversation, verify each turn's
12321386
/// rewrite preserves the LLM-emitted shape AND the markers expand cleanly
12331387
/// to the original bytes via the cache.

0 commit comments

Comments
 (0)