Skip to content

Commit dad1235

Browse files
committed
fix: try to fix FunctionDefinition for Gemini pro's MALFORMED_FUNCTION_CALL
1 parent d74e577 commit dad1235

File tree

3 files changed

+193
-3
lines changed

3 files changed

+193
-3
lines changed

anda_engine/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "anda_engine"
33
description = "Agents engine for Anda -- an AI agent framework built with Rust, powered by ICP and TEEs."
44
repository = "https://github.com/ldclabs/anda/tree/main/anda_engine"
55
publish = true
6-
version = "0.9.13"
6+
version = "0.9.14"
77
edition.workspace = true
88
keywords.workspace = true
99
categories.workspace = true

anda_engine/src/memory.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ pub static FUNCTION_DEFINITION: LazyLock<FunctionDefinition> = LazyLock::new(||
4646
},
4747
"parameters": {
4848
"type": "object",
49-
"description": "An optional JSON object of key-value pairs used for safe substitution of placeholders in the command string(s). Placeholders should start with ':' (e.g., :name, :limit). IMPORTANT: A placeholder must represent a complete JSON value token (e.g., name: :name). Do not embed placeholders inside quoted strings (e.g., \"Hello :name\"), because substitution uses JSON serialization."
49+
"description": "JSON object of key-value pairs for safe placeholder substitution (placeholders start with ':', e.g., :name). Must be complete JSON tokens; do not embed in quoted strings."
5050
}
5151
},
5252
"required": ["command", "parameters"]

anda_engine/src/model/gemini/types.rs

Lines changed: 191 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ impl fmt::Display for Role {
404404
}
405405

406406
#[derive(Debug, Clone, Serialize, Deserialize)]
407-
#[serde(untagged, rename_all = "camelCase")]
407+
#[serde(untagged, rename_all = "camelCase", rename_all_fields = "camelCase")]
408408
pub enum Tool {
409409
FunctionDeclaration {
410410
function_declarations: Vec<FunctionDeclaration>,
@@ -413,6 +413,21 @@ pub enum Tool {
413413
CodeExecution {
414414
code_execution: CodeExecution,
415415
},
416+
417+
/// Enable Google Search grounding with (optional) dynamic retrieval settings.
418+
GoogleSearchRetrieval {
419+
google_search_retrieval: GoogleSearchRetrieval,
420+
},
421+
422+
/// Enable Google Search tool.
423+
GoogleSearch {
424+
google_search: GoogleSearch,
425+
},
426+
427+
/// Enable URL context tool.
428+
UrlContext {
429+
url_context: UrlContext,
430+
},
416431
}
417432

418433
impl From<Vec<FunctionDefinition>> for Tool {
@@ -435,6 +450,37 @@ impl From<Vec<FunctionDefinition>> for Tool {
435450
#[serde(rename_all = "camelCase")]
436451
pub struct CodeExecution {}
437452

453+
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
454+
#[serde(rename_all = "camelCase")]
455+
pub struct GoogleSearch {}
456+
457+
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
458+
#[serde(rename_all = "camelCase")]
459+
pub struct UrlContext {}
460+
461+
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
462+
#[serde(rename_all = "camelCase")]
463+
pub struct GoogleSearchRetrieval {
464+
#[serde(skip_serializing_if = "Option::is_none")]
465+
pub dynamic_retrieval_config: Option<DynamicRetrievalConfig>,
466+
467+
#[serde(flatten)]
468+
pub extra: Map<String, Value>,
469+
}
470+
471+
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
472+
#[serde(rename_all = "camelCase")]
473+
pub struct DynamicRetrievalConfig {
474+
#[serde(skip_serializing_if = "Option::is_none")]
475+
pub mode: Option<String>,
476+
477+
#[serde(skip_serializing_if = "Option::is_none")]
478+
pub dynamic_threshold: Option<f64>,
479+
480+
#[serde(flatten)]
481+
pub extra: Map<String, Value>,
482+
}
483+
438484
#[derive(Debug, Serialize, Deserialize, Clone)]
439485
#[serde(rename_all = "camelCase")]
440486
pub struct ToolConfig {
@@ -1028,4 +1074,148 @@ mod tests {
10281074
// let val = into_parts(json!(vec![string_value, complex_value])).unwrap();
10291075
// assert_eq!(val, vec![content_part, content_part2]);
10301076
}
1077+
1078+
#[test]
1079+
fn test_tool_serde() {
1080+
let tool = Tool::FunctionDeclaration {
1081+
function_declarations: vec![FunctionDeclaration {
1082+
name: "get_weather".to_string(),
1083+
description: "Get current weather".to_string(),
1084+
parameters: Some(json!({
1085+
"type": "object",
1086+
"properties": {
1087+
"location": {"type": "string"}
1088+
},
1089+
"required": ["location"]
1090+
})),
1091+
response: None,
1092+
}],
1093+
};
1094+
1095+
let json_value = serde_json::to_value(&tool).unwrap();
1096+
assert_eq!(
1097+
json_value,
1098+
json!({
1099+
"functionDeclarations": [
1100+
{
1101+
"name": "get_weather",
1102+
"description": "Get current weather",
1103+
"parameters": {
1104+
"type": "object",
1105+
"properties": {
1106+
"location": {"type": "string"}
1107+
},
1108+
"required": ["location"]
1109+
}
1110+
}
1111+
]
1112+
})
1113+
);
1114+
1115+
let deserialized: Tool = serde_json::from_value(json_value).unwrap();
1116+
assert_eq!(
1117+
serde_json::to_value(deserialized).unwrap(),
1118+
serde_json::to_value(tool).unwrap()
1119+
);
1120+
1121+
let code_tool = Tool::CodeExecution {
1122+
code_execution: CodeExecution {},
1123+
};
1124+
let json_value = serde_json::to_value(&code_tool).unwrap();
1125+
assert_eq!(json_value, json!({ "codeExecution": {} }));
1126+
let deserialized: Tool = serde_json::from_value(json_value).unwrap();
1127+
assert_eq!(
1128+
serde_json::to_value(deserialized).unwrap(),
1129+
serde_json::to_value(code_tool).unwrap()
1130+
);
1131+
1132+
let search_retrieval_tool = Tool::GoogleSearchRetrieval {
1133+
google_search_retrieval: GoogleSearchRetrieval {
1134+
dynamic_retrieval_config: Some(DynamicRetrievalConfig {
1135+
mode: Some("MODE_DYNAMIC".to_string()),
1136+
dynamic_threshold: Some(0.3),
1137+
extra: Map::new(),
1138+
}),
1139+
extra: Map::new(),
1140+
},
1141+
};
1142+
let json_value = serde_json::to_value(&search_retrieval_tool).unwrap();
1143+
assert_eq!(
1144+
json_value,
1145+
json!({
1146+
"googleSearchRetrieval": {
1147+
"dynamicRetrievalConfig": {
1148+
"mode": "MODE_DYNAMIC",
1149+
"dynamicThreshold": 0.3
1150+
}
1151+
}
1152+
})
1153+
);
1154+
let deserialized: Tool = serde_json::from_value(json_value).unwrap();
1155+
assert_eq!(
1156+
serde_json::to_value(deserialized).unwrap(),
1157+
serde_json::to_value(search_retrieval_tool).unwrap()
1158+
);
1159+
1160+
let search_tool = Tool::GoogleSearch {
1161+
google_search: GoogleSearch {},
1162+
};
1163+
let json_value = serde_json::to_value(&search_tool).unwrap();
1164+
assert_eq!(json_value, json!({ "googleSearch": {} }));
1165+
let deserialized: Tool = serde_json::from_value(json_value).unwrap();
1166+
assert_eq!(
1167+
serde_json::to_value(deserialized).unwrap(),
1168+
serde_json::to_value(search_tool).unwrap()
1169+
);
1170+
1171+
let url_context_tool = Tool::UrlContext {
1172+
url_context: UrlContext {},
1173+
};
1174+
let json_value = serde_json::to_value(&url_context_tool).unwrap();
1175+
assert_eq!(json_value, json!({ "urlContext": {} }));
1176+
let deserialized: Tool = serde_json::from_value(json_value).unwrap();
1177+
assert_eq!(
1178+
serde_json::to_value(deserialized).unwrap(),
1179+
serde_json::to_value(url_context_tool).unwrap()
1180+
);
1181+
}
1182+
1183+
#[test]
1184+
fn test_tool_from_function_definitions() {
1185+
let defs = vec![FunctionDefinition {
1186+
name: "sum".to_string(),
1187+
description: "Sum two integers".to_string(),
1188+
parameters: json!({
1189+
"type": "object",
1190+
"properties": {
1191+
"a": {"type": "integer"},
1192+
"b": {"type": "integer"}
1193+
},
1194+
"required": ["a", "b"]
1195+
}),
1196+
strict: None,
1197+
}];
1198+
1199+
let tool: Tool = defs.into();
1200+
let json_value = serde_json::to_value(&tool).unwrap();
1201+
assert_eq!(
1202+
json_value,
1203+
json!({
1204+
"functionDeclarations": [
1205+
{
1206+
"name": "sum",
1207+
"description": "Sum two integers",
1208+
"parameters": {
1209+
"type": "object",
1210+
"properties": {
1211+
"a": {"type": "integer"},
1212+
"b": {"type": "integer"}
1213+
},
1214+
"required": ["a", "b"]
1215+
}
1216+
}
1217+
]
1218+
})
1219+
);
1220+
}
10311221
}

0 commit comments

Comments
 (0)