langchain_rust/schemas/
tool_call.rs1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use serde_json::{json, Value};
3
4use super::{FunctionCallResponse, FunctionDetail};
5
6#[derive(Debug, Clone, PartialEq, Eq, Default)]
14pub struct ToolCall {
15 pub id: String,
16 pub name: String,
17 pub arguments: String,
18}
19
20impl ToolCall {
21 pub fn new(
22 id: impl Into<String>,
23 name: impl Into<String>,
24 arguments: impl Into<String>,
25 ) -> Self {
26 Self {
27 id: id.into(),
28 name: name.into(),
29 arguments: arguments.into(),
30 }
31 }
32
33 pub fn from_value(id: impl Into<String>, name: impl Into<String>, arguments: &Value) -> Self {
35 Self::new(id, name, arguments.to_string())
36 }
37
38 pub fn arguments_value(&self) -> Value {
42 if self.arguments.trim().is_empty() {
43 return Value::Object(Default::default());
44 }
45 match serde_json::from_str::<Value>(&self.arguments) {
46 Ok(v @ Value::Object(_)) => v,
47 Ok(other) => json!({ "input": other }),
48 Err(_) => json!({ "input": self.arguments }),
49 }
50 }
51
52 pub fn parse_list(s: &str) -> Option<Vec<ToolCall>> {
55 let trimmed = s.trim_start();
56 if !trimmed.starts_with('[') {
57 return None;
58 }
59 serde_json::from_str(trimmed).ok()
60 }
61
62 pub fn list_to_string(calls: &[ToolCall]) -> String {
64 serde_json::to_string(calls).unwrap_or_else(|_| "[]".into())
65 }
66}
67
68impl From<FunctionCallResponse> for ToolCall {
69 fn from(r: FunctionCallResponse) -> Self {
70 Self {
71 id: r.id,
72 name: r.function.name,
73 arguments: r.function.arguments,
74 }
75 }
76}
77
78impl From<ToolCall> for FunctionCallResponse {
79 fn from(c: ToolCall) -> Self {
80 FunctionCallResponse {
81 id: c.id,
82 type_field: "function".into(),
83 function: FunctionDetail {
84 name: c.name,
85 arguments: c.arguments,
86 },
87 }
88 }
89}
90
91impl Serialize for ToolCall {
92 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
93 FunctionCallResponse::from(self.clone()).serialize(serializer)
94 }
95}
96
97impl<'de> Deserialize<'de> for ToolCall {
98 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
99 FunctionCallResponse::deserialize(deserializer).map(Into::into)
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn serializes_to_the_openai_wire_format() {
109 let call = ToolCall::new("call_1", "weather", r#"{"city":"Lima"}"#);
110 let v = serde_json::to_value(&call).unwrap();
111 assert_eq!(
112 v,
113 json!({"id": "call_1", "type": "function",
114 "function": {"name": "weather", "arguments": "{\"city\":\"Lima\"}"}})
115 );
116 let back: ToolCall = serde_json::from_value(v).unwrap();
117 assert_eq!(back, call);
118 }
119
120 #[test]
121 fn parse_list_accepts_arrays_only() {
122 let calls = ToolCall::parse_list(
123 r#" [{"id":"a","type":"function","function":{"name":"f","arguments":"{}"}}]"#,
124 )
125 .unwrap();
126 assert_eq!(calls, vec![ToolCall::new("a", "f", "{}")]);
127 assert_eq!(ToolCall::parse_list("[]"), Some(vec![]));
128 assert_eq!(ToolCall::parse_list("Hello"), None);
129 assert_eq!(ToolCall::parse_list("[1, 2]"), None);
130 assert_eq!(ToolCall::parse_list(r#"{"id":"a"}"#), None);
131 }
132
133 #[test]
134 fn list_round_trip() {
135 let calls = vec![
136 ToolCall::from_value("a", "f", &json!({"x": 1})),
137 ToolCall::new("b", "g", ""),
138 ];
139 let s = ToolCall::list_to_string(&calls);
140 assert_eq!(ToolCall::parse_list(&s).unwrap(), calls);
141 }
142
143 #[test]
144 fn arguments_value_normalizes_input() {
145 assert_eq!(ToolCall::new("", "f", "").arguments_value(), json!({}));
146 assert_eq!(
147 ToolCall::new("", "f", r#"{"a":1}"#).arguments_value(),
148 json!({"a": 1})
149 );
150 assert_eq!(
151 ToolCall::new("", "f", "[1]").arguments_value(),
152 json!({"input": [1]})
153 );
154 assert_eq!(
155 ToolCall::new("", "f", "plain text").arguments_value(),
156 json!({"input": "plain text"})
157 );
158 }
159}