Skip to main content

agentic_reality/query/
intent.rs

1//! Intent declaration and scoped extraction.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
8pub enum ExtractionIntent {
9    Exists,
10    #[default]
11    IdsOnly,
12    Summary,
13    Fields(Vec<String>),
14    Full,
15}
16
17impl ExtractionIntent {
18    pub fn estimated_tokens(&self) -> u64 {
19        match self {
20            Self::Exists => 1,
21            Self::IdsOnly => 10,
22            Self::Summary => 50,
23            Self::Fields(f) => 20 * f.len() as u64,
24            Self::Full => 500,
25        }
26    }
27
28    pub fn parse_label(s: &str) -> Self {
29        match s {
30            "exists" => Self::Exists,
31            "ids" | "ids_only" => Self::IdsOnly,
32            "summary" => Self::Summary,
33            "full" => Self::Full,
34            _ => Self::Full,
35        }
36    }
37
38    pub fn is_minimal(&self) -> bool {
39        matches!(self, Self::Exists | Self::IdsOnly)
40    }
41
42    pub fn includes_content(&self) -> bool {
43        matches!(self, Self::Summary | Self::Fields(_) | Self::Full)
44    }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub enum ScopedResult {
49    Bool(bool),
50    Id(String),
51    Ids(Vec<String>),
52    Summary(String),
53    Fields(HashMap<String, Value>),
54    Full(Value),
55    Count(usize),
56}
57
58impl ScopedResult {
59    pub fn estimated_tokens(&self) -> u64 {
60        match self {
61            Self::Bool(_) => 1,
62            Self::Id(_) => 5,
63            Self::Ids(ids) => ids.len() as u64 * 5,
64            Self::Summary(_) => 50,
65            Self::Fields(f) => f.len() as u64 * 20,
66            Self::Full(v) => serde_json::to_string(v)
67                .map(|s| s.len() as u64 / 4)
68                .unwrap_or(500),
69            Self::Count(_) => 2,
70        }
71    }
72}
73
74pub trait Scopeable {
75    fn id_str(&self) -> String;
76    fn summarize(&self) -> String;
77    fn extract_fields(&self, fields: &[String]) -> HashMap<String, Value>;
78    fn to_json(&self) -> Value;
79}
80
81pub fn apply_intent<T: Scopeable>(intent: &ExtractionIntent, item: &T) -> ScopedResult {
82    match intent {
83        ExtractionIntent::Exists => ScopedResult::Bool(true),
84        ExtractionIntent::IdsOnly => ScopedResult::Id(item.id_str()),
85        ExtractionIntent::Summary => ScopedResult::Summary(item.summarize()),
86        ExtractionIntent::Fields(f) => ScopedResult::Fields(item.extract_fields(f)),
87        ExtractionIntent::Full => ScopedResult::Full(item.to_json()),
88    }
89}
90
91pub fn apply_intent_many<T: Scopeable>(intent: &ExtractionIntent, items: &[T]) -> ScopedResult {
92    match intent {
93        ExtractionIntent::Exists => ScopedResult::Bool(!items.is_empty()),
94        ExtractionIntent::IdsOnly => ScopedResult::Ids(items.iter().map(|i| i.id_str()).collect()),
95        ExtractionIntent::Summary => ScopedResult::Count(items.len()),
96        _ => ScopedResult::Full(serde_json::json!(items
97            .iter()
98            .map(|i| i.to_json())
99            .collect::<Vec<_>>())),
100    }
101}