structured_output/
structured_output.rs1use 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 tracing_subscriber::fmt()
54 .with_env_filter(EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into()))
55 .init();
56
57 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 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 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}