Skip to content

Commit cb97149

Browse files
ferhimedamineSDK LeadPaperclip-Paperclipclaude
authored
fix(recall): prefer smart_score as ranking key in RecalledMemory (#154)
* fix(recall): prefer smart_score as ranking key in RecalledMemory deserialization Server sorts recall results by smart_score (fallback weighted_score → score) but RecalledMemory::deserialize only read score and weighted_score, causing .score to differ from the actual rank order. Fix prioritizes smart_score, exposing the raw smart_score and weighted_score as Option<f32> fields for analytics consumers. Tests: 4 unit tests covering smart_score priority, weighted_score fallback, raw score fallback, and flat-format decode. Co-Authored-By: Paperclip <noreply@paperclip.ing> * fix(fmt): apply rustfmt — multi-line option chains and assert_eq wraps Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: SDK Lead <sdk-lead@dakera.ai> Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent dbd3880 commit cb97149

2 files changed

Lines changed: 85 additions & 4 deletions

File tree

src/memory.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,14 @@ pub struct RecalledMemory {
442442
pub content: String,
443443
pub memory_type: MemoryType,
444444
pub importance: f32,
445+
/// The ranking score — equals `smart_score` when present, then `weighted_score`, then raw `score`.
445446
pub score: f32,
447+
/// Raw smart_score from the server (the primary ranking key).
448+
#[serde(skip_serializing_if = "Option::is_none")]
449+
pub smart_score: Option<f32>,
450+
/// Raw weighted_score from the server.
451+
#[serde(skip_serializing_if = "Option::is_none")]
452+
pub weighted_score: Option<f32>,
446453
#[serde(default)]
447454
pub tags: Vec<String>,
448455
#[serde(skip_serializing_if = "Option::is_none")]
@@ -465,12 +472,20 @@ impl<'de> serde::Deserialize<'de> for RecalledMemory {
465472
let val = serde_json::Value::deserialize(deserializer)?;
466473

467474
// Server wraps recall results as {memory:{...}, score, weighted_score, smart_score}.
475+
// smart_score is the actual ranking key (server sorts by it); prefer it.
468476
// Fall back to flat format for direct memory-get responses.
469-
let score = val
470-
.get("score")
477+
let smart_score = val
478+
.get("smart_score")
479+
.and_then(|v| v.as_f64())
480+
.map(|v| v as f32);
481+
let weighted_score = val
482+
.get("weighted_score")
471483
.and_then(|v| v.as_f64())
472-
.or_else(|| val.get("weighted_score").and_then(|v| v.as_f64()))
473-
.unwrap_or(0.0) as f32;
484+
.map(|v| v as f32);
485+
let score = smart_score
486+
.or(weighted_score)
487+
.or_else(|| val.get("score").and_then(|v| v.as_f64()).map(|v| v as f32))
488+
.unwrap_or(0.0);
474489

475490
let mem = val.get("memory").unwrap_or(&val);
476491

@@ -518,6 +533,8 @@ impl<'de> serde::Deserialize<'de> for RecalledMemory {
518533
memory_type,
519534
importance,
520535
score,
536+
smart_score,
537+
weighted_score,
521538
tags,
522539
session_id,
523540
metadata,

tests/smart_score_test.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//! Tests for RecalledMemory score field priority (smart_score > weighted_score > score).
2+
3+
use dakera_client::RecalledMemory;
4+
5+
fn deserialize(json: &str) -> RecalledMemory {
6+
serde_json::from_str(json).expect("deserialization failed")
7+
}
8+
9+
#[test]
10+
fn test_smart_score_takes_priority() {
11+
let json = r#"{
12+
"memory": {"id": "m1", "content": "test", "memory_type": "episodic", "importance": 0.8, "created_at": 1000, "last_accessed_at": 1000, "access_count": 0, "tags": []},
13+
"score": 0.5,
14+
"weighted_score": 0.7,
15+
"smart_score": 0.9
16+
}"#;
17+
let m = deserialize(json);
18+
assert_eq!(m.score, 0.9, ".score must equal smart_score when present");
19+
assert_eq!(m.smart_score, Some(0.9));
20+
assert_eq!(m.weighted_score, Some(0.7));
21+
}
22+
23+
#[test]
24+
fn test_weighted_score_used_when_no_smart_score() {
25+
let json = r#"{
26+
"memory": {"id": "m2", "content": "test", "memory_type": "episodic", "importance": 0.8, "created_at": 1000, "last_accessed_at": 1000, "access_count": 0, "tags": []},
27+
"score": 0.5,
28+
"weighted_score": 0.7
29+
}"#;
30+
let m = deserialize(json);
31+
assert_eq!(
32+
m.score, 0.7,
33+
".score must equal weighted_score when smart_score absent"
34+
);
35+
assert_eq!(m.smart_score, None);
36+
assert_eq!(m.weighted_score, Some(0.7));
37+
}
38+
39+
#[test]
40+
fn test_raw_score_fallback() {
41+
let json = r#"{
42+
"memory": {"id": "m3", "content": "test", "memory_type": "episodic", "importance": 0.8, "created_at": 1000, "last_accessed_at": 1000, "access_count": 0, "tags": []},
43+
"score": 0.5
44+
}"#;
45+
let m = deserialize(json);
46+
assert_eq!(
47+
m.score, 0.5,
48+
".score must equal raw score when no smart_score or weighted_score"
49+
);
50+
assert_eq!(m.smart_score, None);
51+
assert_eq!(m.weighted_score, None);
52+
}
53+
54+
#[test]
55+
fn test_flat_format_score_fallback() {
56+
// Flat format (direct memory-get, no envelope)
57+
let json = r#"{
58+
"id": "m4", "content": "flat", "memory_type": "episodic", "importance": 0.5,
59+
"score": 0.6, "created_at": 1000, "last_accessed_at": 1000, "access_count": 0, "tags": []
60+
}"#;
61+
let m = deserialize(json);
62+
assert_eq!(m.score, 0.6);
63+
assert_eq!(m.smart_score, None);
64+
}

0 commit comments

Comments
 (0)