Skip to main content

structured_output/
structured_output.rs

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