Skip to main content

reflex/semantic/
schema.rs

1//! Schema definitions for LLM responses
2
3use serde::{Deserialize, Serialize};
4
5/// LLM response containing rfx query commands
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct QueryResponse {
8    /// Array of rfx commands to execute. Most queries should have 1 command.
9    /// Only use multiple commands when absolutely necessary (e.g., cross-language search).
10    pub queries: Vec<QueryCommand>,
11    /// Optional human-readable message. Set when queries is empty to explain why
12    /// (e.g., question is outside rfx scope).
13    #[serde(default)]
14    pub message: Option<String>,
15}
16
17/// Enhanced response for agentic mode containing both queries and results
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct AgenticQueryResponse {
20    /// Generated query commands
21    pub queries: Vec<QueryCommand>,
22
23    /// Executed search results (file-grouped)
24    #[serde(skip_serializing_if = "Vec::is_empty")]
25    pub results: Vec<crate::models::FileGroupedResult>,
26
27    /// Total count of matches across all results
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub total_count: Option<usize>,
30
31    /// Context gathered from tools (documentation, codebase structure, etc.)
32    /// Used for answer generation when query results are insufficient
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub gathered_context: Option<String>,
35
36    /// Tools that were executed during context gathering (for UI display)
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub tools_executed: Option<Vec<String>>,
39
40    /// Conversational answer synthesized from results (only when --answer is used)
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub answer: Option<String>,
43}
44
45/// A single rfx query command with execution metadata
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct QueryCommand {
48    /// The rfx query command WITHOUT the 'rfx' prefix
49    ///
50    /// Examples:
51    /// - `query "TODO"`
52    /// - `query "async" --symbols --kind function --lang typescript`
53    pub command: String,
54
55    /// Execution order (1-based). Commands execute sequentially by order.
56    pub order: i32,
57
58    /// Whether to include this result in final output
59    /// - `false`: context-only (used to inform subsequent queries)
60    /// - `true`: include in merged results shown to user
61    pub merge: bool,
62}
63
64/// JSON schema for LLM prompt (OpenAI structured output)
65pub const RESPONSE_SCHEMA: &str = r#"{
66  "type": "object",
67  "properties": {
68    "queries": {
69      "type": "array",
70      "description": "Array of rfx commands to execute. Most queries should have 1 command.",
71      "items": {
72        "type": "object",
73        "properties": {
74          "command": {
75            "type": "string",
76            "description": "The rfx query command WITHOUT the 'rfx' prefix"
77          },
78          "order": {
79            "type": "integer",
80            "description": "Execution order (1-based, sequential)"
81          },
82          "merge": {
83            "type": "boolean",
84            "description": "Whether to include in final results (false = context-only)"
85          }
86        },
87        "required": ["command", "order", "merge"]
88      }
89    }
90  },
91  "required": ["queries"]
92}"#;
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_deserialize_single_query() {
100        let json = r#"{
101            "queries": [{
102                "command": "query \"TODO\"",
103                "order": 1,
104                "merge": true
105            }]
106        }"#;
107
108        let response: QueryResponse = serde_json::from_str(json).unwrap();
109        assert_eq!(response.queries.len(), 1);
110        assert_eq!(response.queries[0].command, "query \"TODO\"");
111        assert_eq!(response.queries[0].order, 1);
112        assert_eq!(response.queries[0].merge, true);
113    }
114
115    #[test]
116    fn test_deserialize_multiple_queries() {
117        let json = r#"{
118            "queries": [
119                {
120                    "command": "query \"User\" --symbols --kind struct --lang rust",
121                    "order": 1,
122                    "merge": false
123                },
124                {
125                    "command": "query \"User\" --lang rust",
126                    "order": 2,
127                    "merge": true
128                }
129            ]
130        }"#;
131
132        let response: QueryResponse = serde_json::from_str(json).unwrap();
133        assert_eq!(response.queries.len(), 2);
134        assert_eq!(response.queries[0].order, 1);
135        assert_eq!(response.queries[0].merge, false);
136        assert_eq!(response.queries[1].order, 2);
137        assert_eq!(response.queries[1].merge, true);
138    }
139}