Skip to main content

structured_output/
structured_output.rs

1use antigravity_sdk_rust::agent::{Agent, AgentConfig};
2use antigravity_sdk_rust::policy;
3use antigravity_sdk_rust::tools::Tool;
4use antigravity_sdk_rust::types::GeminiConfig;
5use serde_json::Value;
6use std::sync::Arc;
7use tracing_subscriber::EnvFilter;
8
9struct FetchNotesTool;
10
11impl Tool for FetchNotesTool {
12    fn name(&self) -> &'static str {
13        "fetch_unstructured_meeting_notes"
14    }
15
16    fn description(&self) -> &'static str {
17        "Retrieves the raw unstructured notes for a given meeting ID."
18    }
19
20    fn parameters_json_schema(&self) -> &'static str {
21        r#"{
22            "type": "object",
23            "properties": {
24                "meeting_id": {
25                    "type": "string"
26                }
27            },
28            "required": ["meeting_id"]
29        }"#
30    }
31
32    async fn call(&self, args: Value) -> Result<Value, anyhow::Error> {
33        let meeting_id = args
34            .get("meeting_id")
35            .and_then(Value::as_str)
36            .ok_or_else(|| anyhow::anyhow!("Missing meeting_id"))?;
37
38        if meeting_id == "meeting-2026-05" {
39            Ok(Value::String(
40                "Discussed launch timeline for project X. Alice agreed to update the textproto tests by Monday. \
41                 Bob mentioned he will run the final E2E benchmarks tomorrow. I will push the release build \
42                 once the tests are green."
43                    .to_string(),
44            ))
45        } else {
46            Ok(Value::String("Error: Meeting notes not found.".to_string()))
47        }
48    }
49}
50
51#[tokio::main]
52async fn main() -> Result<(), anyhow::Error> {
53    // Initialize tracing subscriber
54    tracing_subscriber::fmt()
55        .with_env_filter(EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into()))
56        .init();
57
58    // Load environment variables from .env file if present
59    dotenvy::dotenv().ok();
60
61    let mut config = AgentConfig::default();
62
63    if let Ok(harness_path) = std::env::var("ANTIGRAVITY_HARNESS_PATH") {
64        config.binary_path = Some(harness_path);
65    }
66
67    let mut gemini_config = GeminiConfig::default();
68    if let Ok(api_key) = std::env::var("GEMINI_API_KEY") {
69        gemini_config.api_key = Some(api_key);
70    }
71    gemini_config.models.default.name = "gemini-3.5-flash".to_string();
72    config.gemini_config = gemini_config;
73
74    // Define response schema for meeting summaries and action items
75    let response_schema = r#"{
76        "type": "object",
77        "properties": {
78            "action_items": {
79                "type": "array",
80                "items": {
81                    "type": "object",
82                    "properties": {
83                        "assignee": { "type": "string" },
84                        "task": { "type": "string" },
85                        "deadline": { "type": "string" }
86                    },
87                    "required": ["assignee", "task", "deadline"]
88                }
89            }
90        },
91        "required": ["action_items"]
92    }"#;
93    config.response_schema = Some(response_schema.to_string());
94
95    // Register our custom tool
96    config.tools = vec![Arc::new(FetchNotesTool)];
97
98    // Allow our tool and standard tools
99    config.policies = Some(vec![
100        policy::deny_all(),
101        policy::allow("fetch_unstructured_meeting_notes"),
102    ]);
103
104    let mut agent = Agent::new(config);
105    println!("Starting agent...");
106    agent.start().await?;
107
108    let prompt = "Use the fetch_unstructured_meeting_notes tool to retrieve notes for \
109                  'meeting-2026-05' and return the meeting summary with the appropriate \
110                  action item list. Ensure each action item includes 'assignee', \
111                  'task', and 'deadline'.";
112
113    println!("\n  Sending prompt to agent...\n  {}", prompt);
114    let response = agent.chat(prompt).await?;
115
116    println!("\n  Extracting structured meeting action items...");
117
118    // Find the step that contains structured_output
119    let mut found = false;
120    for step in &response.steps {
121        if let Some(ref structured) = step.structured_output {
122            println!("\n  === Structured Meeting Action Items ===");
123            if let Some(items) = structured.get("action_items").and_then(Value::as_array) {
124                for item in items {
125                    println!(
126                        "  - Assignee: {:?}",
127                        item.get("assignee").and_then(Value::as_str).unwrap_or("")
128                    );
129                    println!(
130                        "    Task:     {:?}",
131                        item.get("task").and_then(Value::as_str).unwrap_or("")
132                    );
133                    println!(
134                        "    Deadline: {:?}",
135                        item.get("deadline").and_then(Value::as_str).unwrap_or("")
136                    );
137                    println!();
138                }
139            } else {
140                println!("No action_items field or not an array: {:?}", structured);
141            }
142            found = true;
143            break;
144        }
145    }
146
147    if !found {
148        println!("\n  Failed to extract structured summary natively.");
149        println!("  Final Text Response: {}", response.text);
150    }
151
152    agent.stop().await?;
153    Ok(())
154}