langchain_rust/schemas/
tools_openai_like.rs1use crate::schemas::convert::{OpenAIFromLangchain, TryOpenAiFromLangchain};
2use crate::tools::Tool;
3use async_openai::types::chat::{
4 ChatCompletionNamedToolChoice, ChatCompletionTool, ChatCompletionToolChoiceOption,
5 ChatCompletionTools, FunctionName, FunctionObjectArgs, ToolChoiceOptions,
6};
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9use std::ops::Deref;
10
11#[derive(Clone, Debug)]
12pub enum FunctionCallBehavior {
13 None,
14 Auto,
15 Named(String),
16}
17
18impl OpenAIFromLangchain<FunctionCallBehavior> for ChatCompletionToolChoiceOption {
19 fn from_langchain(langchain: FunctionCallBehavior) -> Self {
20 match langchain {
21 FunctionCallBehavior::Auto => {
22 ChatCompletionToolChoiceOption::Mode(ToolChoiceOptions::Auto)
23 }
24 FunctionCallBehavior::None => {
25 ChatCompletionToolChoiceOption::Mode(ToolChoiceOptions::None)
26 }
27 FunctionCallBehavior::Named(name) => {
28 ChatCompletionToolChoiceOption::Function(ChatCompletionNamedToolChoice {
29 function: FunctionName { name },
30 })
31 }
32 }
33 }
34}
35
36#[derive(Clone, Debug)]
37pub struct FunctionDefinition {
38 pub name: String,
39 pub description: String,
40 pub parameters: Value,
41}
42
43impl FunctionDefinition {
44 pub fn new(name: &str, description: &str, parameters: Value) -> Self {
45 FunctionDefinition {
46 name: name.trim().replace(" ", "_"),
47 description: description.to_string(),
48 parameters,
49 }
50 }
51
52 pub fn from_langchain_tool<T>(tool: &T) -> FunctionDefinition
54 where
55 T: Deref<Target = dyn Tool> + ?Sized,
56 {
57 FunctionDefinition {
58 name: tool.name().trim().replace(" ", "_"),
59 description: tool.description(),
60 parameters: tool.parameters(),
61 }
62 }
63}
64
65impl TryOpenAiFromLangchain<FunctionDefinition> for ChatCompletionTool {
66 type Error = async_openai::error::OpenAIError;
67 fn try_from_langchain(langchain: FunctionDefinition) -> Result<Self, Self::Error> {
68 let function = FunctionObjectArgs::default()
69 .name(langchain.name)
70 .description(langchain.description)
71 .parameters(langchain.parameters)
72 .build()?;
73
74 Ok(ChatCompletionTool { function })
75 }
76}
77
78impl TryOpenAiFromLangchain<FunctionDefinition> for ChatCompletionTools {
79 type Error = async_openai::error::OpenAIError;
80 fn try_from_langchain(langchain: FunctionDefinition) -> Result<Self, Self::Error> {
81 ChatCompletionTool::try_from_langchain(langchain).map(ChatCompletionTools::Function)
82 }
83}
84
85#[derive(Serialize, Deserialize, Debug)]
86pub struct FunctionCallResponse {
87 pub id: String,
88 #[serde(rename = "type")]
89 pub type_field: String,
90 pub function: FunctionDetail,
91}
92
93#[derive(Serialize, Deserialize, Debug)]
94pub struct FunctionDetail {
95 pub name: String,
96 pub arguments: String,
99}
100
101impl FunctionDetail {
102 pub fn arguments_object(&self) -> Value {
106 if self.arguments.trim().is_empty() {
107 return Value::Object(Default::default());
108 }
109 match serde_json::from_str::<Value>(&self.arguments) {
110 Ok(v @ Value::Object(_)) => v,
111 Ok(other) => serde_json::json!({ "input": other }),
112 Err(_) => serde_json::json!({ "input": self.arguments }),
113 }
114 }
115}
116
117impl FunctionCallResponse {
118 pub fn from_str(s: &str) -> Result<Self, serde_json::Error> {
119 serde_json::from_str(s)
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use async_trait::async_trait;
127 use serde_json::json;
128 use std::{error::Error, sync::Arc};
129
130 struct MyTool;
131
132 #[async_trait]
133 impl Tool for MyTool {
134 fn name(&self) -> String {
135 " Web Search ".into()
136 }
137 fn description(&self) -> String {
138 "searches".into()
139 }
140 async fn run(&self, _input: Value) -> Result<String, Box<dyn Error>> {
141 Ok(String::new())
142 }
143 }
144
145 #[test]
146 fn function_definition_normalizes_name() {
147 let f = FunctionDefinition::new(" my cool tool ", "desc", json!({}));
148 assert_eq!(f.name, "my_cool_tool");
149 }
150
151 #[test]
152 fn function_definition_from_tool_uses_default_parameters() {
153 let tool: Arc<dyn Tool> = Arc::new(MyTool);
154 let f = FunctionDefinition::from_langchain_tool(&tool);
155 assert_eq!(f.name, "Web_Search");
156 assert_eq!(f.description, "searches");
157 assert_eq!(f.parameters["required"], json!(["input"]));
158 assert_eq!(
159 f.parameters["properties"]["input"]["description"],
160 "searches"
161 );
162 }
163
164 #[test]
165 fn function_call_response_parses() {
166 let r = FunctionCallResponse::from_str(
167 r#"{"id":"c1","type":"function","function":{"name":"calc","arguments":"{\"a\":1}"}}"#,
168 )
169 .unwrap();
170 assert_eq!(r.id, "c1");
171 assert_eq!(r.type_field, "function");
172 assert_eq!(r.function.name, "calc");
173 assert_eq!(r.function.arguments, "{\"a\":1}");
174 assert!(FunctionCallResponse::from_str("{}").is_err());
175 }
176}