Skip to main content

locus_sdk/interface/
dto.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use locus_core_rs::domain::models::{AvecState, PsiRange, SttpNode};
5
6use crate::application::memory_composition::{
7    CompositeInputItem, CompositeNodeFromTextOptions, CompositeNodeFromTextRequest,
8    CompositeNodeFromTextResult, CompositeRole, CompositeRoleAvecOverrides,
9    MemoryDailyRollupRequest, MemoryRecallWithExplainResult, MemoryTransformThenRecallRequest,
10    MemoryTransformThenRecallResult,
11};
12use crate::domain::memory::{
13    FallbackPolicy, MemoryAggregateRequest, MemoryAggregateResult, MemoryExplainRequest,
14    MemoryExplainResult, MemoryFilter,
15    MemoryFindRequest, MemoryFindResult, MemoryGroupBy, MemoryPage, MemoryRecallRequest,
16    MemoryRecallResult, MemorySchemaResult, MemoryScope, MemoryScoring, MemorySort,
17    MemoryTransformOperation,
18    MemoryTransformRequest, MemoryTransformResult, MetricRange, NumericStats, RetrievalPath,
19    StrictnessMode,
20};
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23#[serde(rename_all = "camelCase")]
24pub struct AvecStateDto {
25    pub stability: f32,
26    pub friction: f32,
27    pub logic: f32,
28    pub autonomy: f32,
29    pub psi: f32,
30}
31
32impl From<AvecStateDto> for AvecState {
33    fn from(value: AvecStateDto) -> Self {
34        Self {
35            stability: value.stability,
36            friction: value.friction,
37            logic: value.logic,
38            autonomy: value.autonomy,
39        }
40    }
41}
42
43impl From<AvecState> for AvecStateDto {
44    fn from(value: AvecState) -> Self {
45        Self {
46            stability: value.stability,
47            friction: value.friction,
48            logic: value.logic,
49            autonomy: value.autonomy,
50            psi: value.psi(),
51        }
52    }
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56#[serde(rename_all = "camelCase")]
57pub struct PsiRangeDto {
58    pub min: f32,
59    pub max: f32,
60    pub average: f32,
61}
62
63impl From<PsiRange> for PsiRangeDto {
64    fn from(value: PsiRange) -> Self {
65        Self {
66            min: value.min,
67            max: value.max,
68            average: value.average,
69        }
70    }
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct MemoryScopeDto {
76    pub tenant_id: Option<String>,
77    pub session_ids: Option<Vec<String>>,
78    pub tiers: Option<Vec<String>>,
79    pub from_utc: Option<DateTime<Utc>>,
80    pub to_utc: Option<DateTime<Utc>>,
81}
82
83impl From<MemoryScopeDto> for MemoryScope {
84    fn from(value: MemoryScopeDto) -> Self {
85        Self {
86            tenant_id: value.tenant_id,
87            session_ids: value.session_ids,
88            tiers: value.tiers,
89            from_utc: value.from_utc,
90            to_utc: value.to_utc,
91        }
92    }
93}
94
95impl From<MemoryScope> for MemoryScopeDto {
96    fn from(value: MemoryScope) -> Self {
97        Self {
98            tenant_id: value.tenant_id,
99            session_ids: value.session_ids,
100            tiers: value.tiers,
101            from_utc: value.from_utc,
102            to_utc: value.to_utc,
103        }
104    }
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
108#[serde(rename_all = "camelCase")]
109pub struct MemoryFilterDto {
110    pub has_embedding: Option<bool>,
111    pub embedding_model: Option<String>,
112    pub psi: Option<MetricRange>,
113    pub rho: Option<MetricRange>,
114    pub kappa: Option<MetricRange>,
115    pub text_contains: Option<String>,
116}
117
118impl From<MemoryFilterDto> for MemoryFilter {
119    fn from(value: MemoryFilterDto) -> Self {
120        Self {
121            has_embedding: value.has_embedding,
122            embedding_model: value.embedding_model,
123            psi: value.psi,
124            rho: value.rho,
125            kappa: value.kappa,
126            text_contains: value.text_contains,
127        }
128    }
129}
130
131impl From<MemoryFilter> for MemoryFilterDto {
132    fn from(value: MemoryFilter) -> Self {
133        Self {
134            has_embedding: value.has_embedding,
135            embedding_model: value.embedding_model,
136            psi: value.psi,
137            rho: value.rho,
138            kappa: value.kappa,
139            text_contains: value.text_contains,
140        }
141    }
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
145#[serde(rename_all = "camelCase")]
146pub struct MemoryPageDto {
147    pub limit: usize,
148    pub cursor: Option<String>,
149}
150
151impl From<MemoryPageDto> for MemoryPage {
152    fn from(value: MemoryPageDto) -> Self {
153        Self {
154            limit: value.limit,
155            cursor: value.cursor,
156        }
157    }
158}
159
160impl From<MemoryPage> for MemoryPageDto {
161    fn from(value: MemoryPage) -> Self {
162        Self {
163            limit: value.limit,
164            cursor: value.cursor,
165        }
166    }
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
170#[serde(rename_all = "camelCase")]
171pub struct MemoryScoringDto {
172    pub resonance_weight: f32,
173    pub semantic_weight: f32,
174    pub lexical_weight: f32,
175    pub alpha: f32,
176    pub beta: f32,
177    pub fallback_policy: FallbackPolicy,
178    pub strictness: StrictnessMode,
179}
180
181impl From<MemoryScoringDto> for MemoryScoring {
182    fn from(value: MemoryScoringDto) -> Self {
183        Self {
184            resonance_weight: value.resonance_weight,
185            semantic_weight: value.semantic_weight,
186            lexical_weight: value.lexical_weight,
187            alpha: value.alpha,
188            beta: value.beta,
189            fallback_policy: value.fallback_policy,
190            strictness: value.strictness,
191        }
192    }
193}
194
195impl From<MemoryScoring> for MemoryScoringDto {
196    fn from(value: MemoryScoring) -> Self {
197        Self {
198            resonance_weight: value.resonance_weight,
199            semantic_weight: value.semantic_weight,
200            lexical_weight: value.lexical_weight,
201            alpha: value.alpha,
202            beta: value.beta,
203            fallback_policy: value.fallback_policy,
204            strictness: value.strictness,
205        }
206    }
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
210#[serde(rename_all = "camelCase")]
211pub struct MemoryFindRequestDto {
212    pub scope: MemoryScopeDto,
213    pub filter: MemoryFilterDto,
214    pub page: MemoryPageDto,
215    pub sort: MemorySort,
216}
217
218impl From<MemoryFindRequestDto> for MemoryFindRequest {
219    fn from(value: MemoryFindRequestDto) -> Self {
220        Self {
221            scope: value.scope.into(),
222            filter: value.filter.into(),
223            page: value.page.into(),
224            sort: value.sort,
225        }
226    }
227}
228
229#[derive(Debug, Clone, Serialize, Deserialize)]
230#[serde(rename_all = "camelCase")]
231pub struct MemoryRecallRequestDto {
232    pub scope: MemoryScopeDto,
233    pub filter: MemoryFilterDto,
234    pub page: MemoryPageDto,
235    pub scoring: MemoryScoringDto,
236    pub current_avec: Option<AvecStateDto>,
237    pub query_text: Option<String>,
238    pub query_embedding: Option<Vec<f32>>,
239}
240
241impl From<MemoryRecallRequestDto> for MemoryRecallRequest {
242    fn from(value: MemoryRecallRequestDto) -> Self {
243        Self {
244            scope: value.scope.into(),
245            filter: value.filter.into(),
246            page: value.page.into(),
247            scoring: value.scoring.into(),
248            current_avec: value.current_avec.map(Into::into),
249            query_text: value.query_text,
250            query_embedding: value.query_embedding,
251        }
252    }
253}
254
255#[derive(Debug, Clone, Serialize)]
256#[serde(rename_all = "camelCase")]
257pub struct MemoryNodeDto {
258    pub raw: String,
259    pub session_id: String,
260    pub tier: String,
261    pub timestamp: DateTime<Utc>,
262    pub compression_depth: i32,
263    pub parent_node_id: Option<String>,
264    pub sync_key: String,
265    pub context_summary: Option<String>,
266    pub embedding_model: Option<String>,
267    pub embedding_dimensions: Option<usize>,
268    pub embedded_at: Option<DateTime<Utc>>,
269    pub rho: f32,
270    pub kappa: f32,
271    pub psi: f32,
272    pub user_avec: AvecStateDto,
273    pub model_avec: AvecStateDto,
274    pub compression_avec: Option<AvecStateDto>,
275    pub updated_at: DateTime<Utc>,
276}
277
278impl From<SttpNode> for MemoryNodeDto {
279    fn from(value: SttpNode) -> Self {
280        Self {
281            raw: value.raw,
282            session_id: value.session_id,
283            tier: value.tier,
284            timestamp: value.timestamp,
285            compression_depth: value.compression_depth,
286            parent_node_id: value.parent_node_id,
287            sync_key: value.sync_key,
288            context_summary: value.context_summary,
289            embedding_model: value.embedding_model,
290            embedding_dimensions: value.embedding_dimensions,
291            embedded_at: value.embedded_at,
292            rho: value.rho,
293            kappa: value.kappa,
294            psi: value.psi,
295            user_avec: value.user_avec.into(),
296            model_avec: value.model_avec.into(),
297            compression_avec: value.compression_avec.map(Into::into),
298            updated_at: value.updated_at,
299        }
300    }
301}
302
303#[derive(Debug, Clone, Serialize)]
304#[serde(rename_all = "camelCase")]
305pub struct MemoryFindResponseDto {
306    pub nodes: Vec<MemoryNodeDto>,
307    pub retrieved: usize,
308    pub has_more: bool,
309    pub next_cursor: Option<String>,
310}
311
312impl From<MemoryFindResult> for MemoryFindResponseDto {
313    fn from(value: MemoryFindResult) -> Self {
314        Self {
315            nodes: value.nodes.into_iter().map(Into::into).collect(),
316            retrieved: value.retrieved,
317            has_more: value.has_more,
318            next_cursor: value.next_cursor,
319        }
320    }
321}
322
323#[derive(Debug, Clone, Serialize)]
324#[serde(rename_all = "camelCase")]
325pub struct MemoryRecallResponseDto {
326    pub nodes: Vec<MemoryNodeDto>,
327    pub retrieved: usize,
328    pub psi_range: PsiRangeDto,
329    pub retrieval_path: RetrievalPath,
330    pub has_more: bool,
331    pub next_cursor: Option<String>,
332}
333
334impl From<MemoryRecallResult> for MemoryRecallResponseDto {
335    fn from(value: MemoryRecallResult) -> Self {
336        Self {
337            nodes: value.nodes.into_iter().map(Into::into).collect(),
338            retrieved: value.retrieved,
339            psi_range: value.psi_range.into(),
340            retrieval_path: value.retrieval_path,
341            has_more: value.has_more,
342            next_cursor: value.next_cursor,
343        }
344    }
345}
346
347#[derive(Debug, Clone, Serialize, Deserialize)]
348#[serde(rename_all = "camelCase")]
349pub struct NumericStatsDto {
350    pub min: f32,
351    pub max: f32,
352    pub average: f32,
353}
354
355impl From<NumericStats> for NumericStatsDto {
356    fn from(value: NumericStats) -> Self {
357        Self {
358            min: value.min,
359            max: value.max,
360            average: value.average,
361        }
362    }
363}
364
365#[derive(Debug, Clone, Serialize, Deserialize)]
366#[serde(rename_all = "camelCase")]
367pub struct MemoryAggregateRequestDto {
368    pub scope: MemoryScopeDto,
369    pub filter: MemoryFilterDto,
370    pub group_by: MemoryGroupBy,
371    pub max_groups: usize,
372    pub max_nodes: usize,
373}
374
375impl From<MemoryAggregateRequestDto> for MemoryAggregateRequest {
376    fn from(value: MemoryAggregateRequestDto) -> Self {
377        Self {
378            scope: value.scope.into(),
379            filter: value.filter.into(),
380            group_by: value.group_by,
381            max_groups: value.max_groups,
382            max_nodes: value.max_nodes,
383        }
384    }
385}
386
387#[derive(Debug, Clone, Serialize)]
388#[serde(rename_all = "camelCase")]
389pub struct MemoryAggregateGroupDto {
390    pub key: String,
391    pub node_count: usize,
392    pub embedding_coverage: f32,
393    pub avg_user_avec: AvecStateDto,
394    pub avg_model_avec: AvecStateDto,
395    pub avg_compression_avec: Option<AvecStateDto>,
396    pub psi_stats: NumericStatsDto,
397    pub rho_stats: NumericStatsDto,
398    pub kappa_stats: NumericStatsDto,
399}
400
401#[derive(Debug, Clone, Serialize)]
402#[serde(rename_all = "camelCase")]
403pub struct MemoryAggregateResponseDto {
404    pub groups: Vec<MemoryAggregateGroupDto>,
405    pub total_groups: usize,
406    pub scanned_nodes: usize,
407}
408
409impl From<MemoryAggregateResult> for MemoryAggregateResponseDto {
410    fn from(value: MemoryAggregateResult) -> Self {
411        Self {
412            groups: value
413                .groups
414                .into_iter()
415                .map(|group| MemoryAggregateGroupDto {
416                    key: group.key,
417                    node_count: group.node_count,
418                    embedding_coverage: group.embedding_coverage,
419                    avg_user_avec: group.avg_user_avec.into(),
420                    avg_model_avec: group.avg_model_avec.into(),
421                    avg_compression_avec: group.avg_compression_avec.map(Into::into),
422                    psi_stats: group.psi_stats.into(),
423                    rho_stats: group.rho_stats.into(),
424                    kappa_stats: group.kappa_stats.into(),
425                })
426                .collect(),
427            total_groups: value.total_groups,
428            scanned_nodes: value.scanned_nodes,
429        }
430    }
431}
432
433#[derive(Debug, Clone, Serialize, Deserialize)]
434#[serde(rename_all = "camelCase")]
435pub struct MemoryTransformRequestDto {
436    pub scope: MemoryScopeDto,
437    pub filter: MemoryFilterDto,
438    pub operation: MemoryTransformOperation,
439    pub dry_run: bool,
440    pub batch_size: usize,
441    pub max_nodes: usize,
442    pub provider_id: Option<String>,
443    pub model: Option<String>,
444}
445
446impl From<MemoryTransformRequestDto> for MemoryTransformRequest {
447    fn from(value: MemoryTransformRequestDto) -> Self {
448        Self {
449            scope: value.scope.into(),
450            filter: value.filter.into(),
451            operation: value.operation,
452            dry_run: value.dry_run,
453            batch_size: value.batch_size,
454            max_nodes: value.max_nodes,
455            provider_id: value.provider_id,
456            model: value.model,
457        }
458    }
459}
460
461#[derive(Debug, Clone, Serialize)]
462#[serde(rename_all = "camelCase")]
463pub struct MemoryTransformResponseDto {
464    pub scanned: usize,
465    pub selected: usize,
466    pub updated: usize,
467    pub skipped: usize,
468    pub failed: usize,
469    pub duplicate: usize,
470    pub started_at: DateTime<Utc>,
471    pub completed_at: DateTime<Utc>,
472    pub failures: Vec<String>,
473}
474
475impl From<MemoryTransformResult> for MemoryTransformResponseDto {
476    fn from(value: MemoryTransformResult) -> Self {
477        Self {
478            scanned: value.scanned,
479            selected: value.selected,
480            updated: value.updated,
481            skipped: value.skipped,
482            failed: value.failed,
483            duplicate: value.duplicate,
484            started_at: value.started_at,
485            completed_at: value.completed_at,
486            failures: value.failures,
487        }
488    }
489}
490
491#[derive(Debug, Clone, Serialize, Deserialize)]
492#[serde(rename_all = "camelCase")]
493pub struct MemoryDailyRollupRequestDto {
494    pub scope: MemoryScopeDto,
495    pub filter: MemoryFilterDto,
496    pub max_days: usize,
497    pub max_nodes: usize,
498}
499
500impl From<MemoryDailyRollupRequestDto> for MemoryDailyRollupRequest {
501    fn from(value: MemoryDailyRollupRequestDto) -> Self {
502        Self {
503            scope: value.scope.into(),
504            filter: value.filter.into(),
505            max_days: value.max_days,
506            max_nodes: value.max_nodes,
507        }
508    }
509}
510
511#[derive(Debug, Clone, Serialize)]
512#[serde(rename_all = "camelCase")]
513pub struct MemoryRecallWithExplainResponseDto {
514    pub recall: MemoryRecallResponseDto,
515    pub explain: MemoryExplainResponseDto,
516}
517
518impl From<MemoryRecallWithExplainResult> for MemoryRecallWithExplainResponseDto {
519    fn from(value: MemoryRecallWithExplainResult) -> Self {
520        Self {
521            recall: value.recall.into(),
522            explain: value.explain.into(),
523        }
524    }
525}
526
527#[derive(Debug, Clone, Serialize, Deserialize)]
528#[serde(rename_all = "camelCase")]
529pub struct MemoryTransformThenRecallRequestDto {
530    pub transform: MemoryTransformRequestDto,
531    pub recall: MemoryRecallRequestDto,
532}
533
534impl From<MemoryTransformThenRecallRequestDto> for MemoryTransformThenRecallRequest {
535    fn from(value: MemoryTransformThenRecallRequestDto) -> Self {
536        Self {
537            transform: value.transform.into(),
538            recall: value.recall.into(),
539        }
540    }
541}
542
543#[derive(Debug, Clone, Serialize)]
544#[serde(rename_all = "camelCase")]
545pub struct MemoryTransformThenRecallResponseDto {
546    pub transform: MemoryTransformResponseDto,
547    pub recall: MemoryRecallResponseDto,
548}
549
550impl From<MemoryTransformThenRecallResult> for MemoryTransformThenRecallResponseDto {
551    fn from(value: MemoryTransformThenRecallResult) -> Self {
552        Self {
553            transform: value.transform.into(),
554            recall: value.recall.into(),
555        }
556    }
557}
558
559#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
560#[serde(rename_all = "snake_case")]
561pub enum CompositeRoleDto {
562    User,
563    Model,
564    Document,
565    Conversation,
566}
567
568impl From<CompositeRoleDto> for CompositeRole {
569    fn from(value: CompositeRoleDto) -> Self {
570        match value {
571            CompositeRoleDto::User => CompositeRole::User,
572            CompositeRoleDto::Model => CompositeRole::Model,
573            CompositeRoleDto::Document => CompositeRole::Document,
574            CompositeRoleDto::Conversation => CompositeRole::Conversation,
575        }
576    }
577}
578
579impl From<CompositeRole> for CompositeRoleDto {
580    fn from(value: CompositeRole) -> Self {
581        match value {
582            CompositeRole::User => CompositeRoleDto::User,
583            CompositeRole::Model => CompositeRoleDto::Model,
584            CompositeRole::Document => CompositeRoleDto::Document,
585            CompositeRole::Conversation => CompositeRoleDto::Conversation,
586        }
587    }
588}
589
590#[derive(Debug, Clone, Serialize, Deserialize)]
591#[serde(rename_all = "camelCase")]
592pub struct CompositeInputItemDto {
593    pub role: CompositeRoleDto,
594    pub text: String,
595    pub avec_override: Option<AvecStateDto>,
596    #[serde(default)]
597    pub context: Vec<CompositeInputItemDto>,
598}
599
600impl From<CompositeInputItemDto> for CompositeInputItem {
601    fn from(value: CompositeInputItemDto) -> Self {
602        Self {
603            role: value.role.into(),
604            text: value.text,
605            avec_override: value.avec_override.map(Into::into),
606            context: value.context.into_iter().map(Into::into).collect(),
607        }
608    }
609}
610
611impl From<CompositeInputItem> for CompositeInputItemDto {
612    fn from(value: CompositeInputItem) -> Self {
613        Self {
614            role: value.role.into(),
615            text: value.text,
616            avec_override: value.avec_override.map(Into::into),
617            context: value.context.into_iter().map(Into::into).collect(),
618        }
619    }
620}
621
622#[derive(Debug, Clone, Default, Serialize, Deserialize)]
623#[serde(rename_all = "camelCase")]
624pub struct CompositeRoleAvecOverridesDto {
625    pub user: Option<AvecStateDto>,
626    pub model: Option<AvecStateDto>,
627    pub document: Option<AvecStateDto>,
628    pub conversation: Option<AvecStateDto>,
629}
630
631impl From<CompositeRoleAvecOverridesDto> for CompositeRoleAvecOverrides {
632    fn from(value: CompositeRoleAvecOverridesDto) -> Self {
633        Self {
634            user: value.user.map(Into::into),
635            model: value.model.map(Into::into),
636            document: value.document.map(Into::into),
637            conversation: value.conversation.map(Into::into),
638        }
639    }
640}
641
642impl From<CompositeRoleAvecOverrides> for CompositeRoleAvecOverridesDto {
643    fn from(value: CompositeRoleAvecOverrides) -> Self {
644        Self {
645            user: value.user.map(Into::into),
646            model: value.model.map(Into::into),
647            document: value.document.map(Into::into),
648            conversation: value.conversation.map(Into::into),
649        }
650    }
651}
652
653#[derive(Debug, Clone, Serialize, Deserialize)]
654#[serde(rename_all = "camelCase")]
655pub struct CompositeNodeFromTextOptionsDto {
656    pub role_avec: CompositeRoleAvecOverridesDto,
657    pub global_avec: Option<AvecStateDto>,
658    pub allow_llm_avec_fallback: bool,
659    pub max_recursion_depth: usize,
660}
661
662impl From<CompositeNodeFromTextOptionsDto> for CompositeNodeFromTextOptions {
663    fn from(value: CompositeNodeFromTextOptionsDto) -> Self {
664        Self {
665            role_avec: value.role_avec.into(),
666            global_avec: value.global_avec.map(Into::into),
667            allow_llm_avec_fallback: value.allow_llm_avec_fallback,
668            max_recursion_depth: value.max_recursion_depth,
669        }
670    }
671}
672
673impl From<CompositeNodeFromTextOptions> for CompositeNodeFromTextOptionsDto {
674    fn from(value: CompositeNodeFromTextOptions) -> Self {
675        Self {
676            role_avec: value.role_avec.into(),
677            global_avec: value.global_avec.map(Into::into),
678            allow_llm_avec_fallback: value.allow_llm_avec_fallback,
679            max_recursion_depth: value.max_recursion_depth,
680        }
681    }
682}
683
684#[derive(Debug, Clone, Serialize, Deserialize)]
685#[serde(rename_all = "camelCase")]
686pub struct CompositeNodeFromTextRequestDto {
687    pub items: Vec<CompositeInputItemDto>,
688    pub options: CompositeNodeFromTextOptionsDto,
689}
690
691impl From<CompositeNodeFromTextRequestDto> for CompositeNodeFromTextRequest {
692    fn from(value: CompositeNodeFromTextRequestDto) -> Self {
693        Self {
694            items: value.items.into_iter().map(Into::into).collect(),
695            options: value.options.into(),
696        }
697    }
698}
699
700#[derive(Debug, Clone, Serialize)]
701#[serde(rename_all = "camelCase")]
702pub struct CompositeNodeFromTextResponseDto {
703    pub content: Value,
704    pub resolved_avec_count: usize,
705    pub unresolved_avec_count: usize,
706    pub requires_llm_avec: bool,
707}
708
709impl From<CompositeNodeFromTextResult> for CompositeNodeFromTextResponseDto {
710    fn from(value: CompositeNodeFromTextResult) -> Self {
711        Self {
712            content: value.content,
713            resolved_avec_count: value.resolved_avec_count,
714            unresolved_avec_count: value.unresolved_avec_count,
715            requires_llm_avec: value.requires_llm_avec,
716        }
717    }
718}
719
720#[derive(Debug, Clone, Serialize, Deserialize)]
721#[serde(rename_all = "camelCase")]
722pub struct MemoryExplainRequestDto {
723    pub recall: MemoryRecallRequestDto,
724}
725
726impl From<MemoryExplainRequestDto> for MemoryExplainRequest {
727    fn from(value: MemoryExplainRequestDto) -> Self {
728        Self {
729            recall: value.recall.into(),
730        }
731    }
732}
733
734#[derive(Debug, Clone, Serialize)]
735#[serde(rename_all = "camelCase")]
736pub struct MemoryExplainStageDto {
737    pub stage: String,
738    pub count: usize,
739}
740
741#[derive(Debug, Clone, Serialize)]
742#[serde(rename_all = "camelCase")]
743pub struct MemoryExplainResponseDto {
744    pub retrieval_path: RetrievalPath,
745    pub fallback_triggered: bool,
746    pub fallback_reason: Option<String>,
747    pub stages: Vec<MemoryExplainStageDto>,
748    pub scoring: MemoryScoringDto,
749}
750
751impl From<MemoryExplainResult> for MemoryExplainResponseDto {
752    fn from(value: MemoryExplainResult) -> Self {
753        Self {
754            retrieval_path: value.retrieval_path,
755            fallback_triggered: value.fallback_triggered,
756            fallback_reason: value.fallback_reason,
757            stages: value
758                .stages
759                .into_iter()
760                .map(|stage| MemoryExplainStageDto {
761                    stage: stage.stage,
762                    count: stage.count,
763                })
764                .collect(),
765            scoring: value.scoring.into(),
766        }
767    }
768}
769
770#[derive(Debug, Clone, Serialize)]
771#[serde(rename_all = "camelCase")]
772pub struct MemorySchemaResponseDto {
773    pub schema_version: String,
774    pub sort_fields: Vec<String>,
775    pub filter_fields: Vec<String>,
776    pub group_by_fields: Vec<String>,
777    pub fallback_policies: Vec<String>,
778    pub strictness_modes: Vec<String>,
779    pub transform_operations: Vec<String>,
780}
781
782impl From<MemorySchemaResult> for MemorySchemaResponseDto {
783    fn from(value: MemorySchemaResult) -> Self {
784        Self {
785            schema_version: value.schema_version,
786            sort_fields: value.sort_fields,
787            filter_fields: value.filter_fields,
788            group_by_fields: value.group_by_fields,
789            fallback_policies: value.fallback_policies,
790            strictness_modes: value.strictness_modes,
791            transform_operations: value.transform_operations,
792        }
793    }
794}