Skip to main content

rust_genai_types/
tool.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::collections::HashMap;
4
5use crate::enums::{
6    ApiSpec, AuthType, Behavior, DynamicRetrievalConfigMode, Environment, FunctionCallingMode,
7    HttpElementLocation, PhishBlockThreshold, Type,
8};
9
10/// 工具定义。每个 Tool 通常仅设置一种工具字段。
11#[derive(Debug, Clone, Serialize, Deserialize, Default)]
12#[serde(rename_all = "camelCase")]
13pub struct Tool {
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub retrieval: Option<Retrieval>,
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub computer_use: Option<ComputerUse>,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub file_search: Option<FileSearch>,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub code_execution: Option<CodeExecution>,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub enterprise_web_search: Option<EnterpriseWebSearch>,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub function_declarations: Option<Vec<FunctionDeclaration>>,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub google_maps: Option<GoogleMaps>,
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub google_search: Option<GoogleSearch>,
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub google_search_retrieval: Option<GoogleSearchRetrieval>,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub url_context: Option<UrlContext>,
34}
35
36/// 函数声明。
37#[derive(Debug, Clone, Serialize, Deserialize)]
38#[serde(rename_all = "camelCase")]
39pub struct FunctionDeclaration {
40    pub name: String,
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub description: Option<String>,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub parameters: Option<Schema>,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub parameters_json_schema: Option<Value>,
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub response: Option<Schema>,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub response_json_schema: Option<Value>,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub behavior: Option<Behavior>,
53}
54
55/// Google Search 工具。
56#[derive(Debug, Clone, Serialize, Deserialize, Default)]
57#[serde(rename_all = "camelCase")]
58pub struct GoogleSearch {
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub exclude_domains: Option<Vec<String>>,
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub blocking_confidence: Option<PhishBlockThreshold>,
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub time_range_filter: Option<Interval>,
65}
66
67/// Enterprise Web Search 工具(Vertex AI Search)。
68#[derive(Debug, Clone, Serialize, Deserialize, Default)]
69#[serde(rename_all = "camelCase")]
70pub struct EnterpriseWebSearch {
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub exclude_domains: Option<Vec<String>>,
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub blocking_confidence: Option<PhishBlockThreshold>,
75}
76
77/// Code execution 工具。
78#[derive(Debug, Clone, Serialize, Deserialize, Default)]
79#[serde(rename_all = "camelCase")]
80pub struct CodeExecution {}
81
82/// URL Context 工具。
83#[derive(Debug, Clone, Serialize, Deserialize, Default)]
84#[serde(rename_all = "camelCase")]
85pub struct UrlContext {}
86
87/// Computer Use 工具。
88#[derive(Debug, Clone, Serialize, Deserialize, Default)]
89#[serde(rename_all = "camelCase")]
90pub struct ComputerUse {
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub environment: Option<Environment>,
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub excluded_predefined_functions: Option<Vec<String>>,
95}
96
97/// Google Maps 工具。
98#[derive(Debug, Clone, Serialize, Deserialize, Default)]
99#[serde(rename_all = "camelCase")]
100pub struct GoogleMaps {
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub auth_config: Option<AuthConfig>,
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub enable_widget: Option<bool>,
105}
106
107/// Google Search Retrieval 工具。
108#[derive(Debug, Clone, Serialize, Deserialize, Default)]
109#[serde(rename_all = "camelCase")]
110pub struct GoogleSearchRetrieval {
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub dynamic_retrieval_config: Option<DynamicRetrievalConfig>,
113}
114
115/// File Search 工具。
116#[derive(Debug, Clone, Serialize, Deserialize, Default)]
117#[serde(rename_all = "camelCase")]
118pub struct FileSearch {
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub file_search_store_names: Option<Vec<String>>,
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub top_k: Option<i32>,
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub metadata_filter: Option<String>,
125}
126
127/// 时间区间。
128#[derive(Debug, Clone, Serialize, Deserialize, Default)]
129#[serde(rename_all = "camelCase")]
130pub struct Interval {
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub end_time: Option<String>,
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub start_time: Option<String>,
135}
136
137/// API key 配置。
138#[derive(Debug, Clone, Serialize, Deserialize, Default)]
139#[serde(rename_all = "camelCase")]
140pub struct ApiKeyConfig {
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub api_key_secret: Option<String>,
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub api_key_string: Option<String>,
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub http_element_location: Option<HttpElementLocation>,
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub name: Option<String>,
149}
150
151/// Deprecated: API auth key config.
152#[derive(Debug, Clone, Serialize, Deserialize, Default)]
153#[serde(rename_all = "camelCase")]
154pub struct ApiAuthApiKeyConfig {
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub api_key_secret_version: Option<String>,
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub api_key_string: Option<String>,
159}
160
161/// Deprecated: API auth config.
162#[derive(Debug, Clone, Serialize, Deserialize, Default)]
163#[serde(rename_all = "camelCase")]
164pub struct ApiAuth {
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub api_key_config: Option<ApiAuthApiKeyConfig>,
167}
168
169/// Google Service Account 配置。
170#[derive(Debug, Clone, Serialize, Deserialize, Default)]
171#[serde(rename_all = "camelCase")]
172pub struct AuthConfigGoogleServiceAccountConfig {
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub service_account: Option<String>,
175}
176
177/// HTTP Basic 认证配置。
178#[derive(Debug, Clone, Serialize, Deserialize, Default)]
179#[serde(rename_all = "camelCase")]
180pub struct AuthConfigHttpBasicAuthConfig {
181    #[serde(skip_serializing_if = "Option::is_none")]
182    pub credential_secret: Option<String>,
183}
184
185/// OAuth 认证配置。
186#[derive(Debug, Clone, Serialize, Deserialize, Default)]
187#[serde(rename_all = "camelCase")]
188pub struct AuthConfigOauthConfig {
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub access_token: Option<String>,
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub service_account: Option<String>,
193}
194
195/// OIDC 认证配置。
196#[derive(Debug, Clone, Serialize, Deserialize, Default)]
197#[serde(rename_all = "camelCase")]
198pub struct AuthConfigOidcConfig {
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub id_token: Option<String>,
201    #[serde(skip_serializing_if = "Option::is_none")]
202    pub service_account: Option<String>,
203}
204
205/// Auth 配置。
206#[derive(Debug, Clone, Serialize, Deserialize, Default)]
207#[serde(rename_all = "camelCase")]
208pub struct AuthConfig {
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub api_key_config: Option<ApiKeyConfig>,
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub auth_type: Option<AuthType>,
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub google_service_account_config: Option<AuthConfigGoogleServiceAccountConfig>,
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub http_basic_auth_config: Option<AuthConfigHttpBasicAuthConfig>,
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub oauth_config: Option<AuthConfigOauthConfig>,
219    #[serde(skip_serializing_if = "Option::is_none")]
220    pub oidc_config: Option<AuthConfigOidcConfig>,
221}
222
223/// `ElasticSearch` 参数。
224#[derive(Debug, Clone, Serialize, Deserialize, Default)]
225#[serde(rename_all = "camelCase")]
226pub struct ExternalApiElasticSearchParams {
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub index: Option<String>,
229    #[serde(skip_serializing_if = "Option::is_none")]
230    pub num_hits: Option<i32>,
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub search_template: Option<String>,
233}
234
235/// Simple Search 参数。
236#[derive(Debug, Clone, Serialize, Deserialize, Default)]
237#[serde(rename_all = "camelCase")]
238pub struct ExternalApiSimpleSearchParams {}
239
240/// External API 检索配置。
241#[derive(Debug, Clone, Serialize, Deserialize, Default)]
242#[serde(rename_all = "camelCase")]
243pub struct ExternalApi {
244    #[serde(skip_serializing_if = "Option::is_none")]
245    pub api_auth: Option<ApiAuth>,
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub api_spec: Option<ApiSpec>,
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub auth_config: Option<AuthConfig>,
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub elastic_search_params: Option<ExternalApiElasticSearchParams>,
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub endpoint: Option<String>,
254    #[serde(skip_serializing_if = "Option::is_none")]
255    pub simple_search_params: Option<ExternalApiSimpleSearchParams>,
256}
257
258/// Vertex AI Search 数据源配置。
259#[derive(Debug, Clone, Serialize, Deserialize, Default)]
260#[serde(rename_all = "camelCase")]
261pub struct VertexAiSearchDataStoreSpec {
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub data_store: Option<String>,
264    #[serde(skip_serializing_if = "Option::is_none")]
265    pub filter: Option<String>,
266}
267
268/// Vertex AI Search 检索配置。
269#[derive(Debug, Clone, Serialize, Deserialize, Default)]
270#[serde(rename_all = "camelCase")]
271pub struct VertexAiSearch {
272    #[serde(skip_serializing_if = "Option::is_none")]
273    pub data_store_specs: Option<Vec<VertexAiSearchDataStoreSpec>>,
274    #[serde(skip_serializing_if = "Option::is_none")]
275    pub datastore: Option<String>,
276    #[serde(skip_serializing_if = "Option::is_none")]
277    pub engine: Option<String>,
278    #[serde(skip_serializing_if = "Option::is_none")]
279    pub filter: Option<String>,
280    #[serde(skip_serializing_if = "Option::is_none")]
281    pub max_results: Option<i32>,
282}
283
284/// Vertex RAG 资源配置。
285#[derive(Debug, Clone, Serialize, Deserialize, Default)]
286#[serde(rename_all = "camelCase")]
287pub struct VertexRagStoreRagResource {
288    #[serde(skip_serializing_if = "Option::is_none")]
289    pub rag_corpus: Option<String>,
290    #[serde(skip_serializing_if = "Option::is_none")]
291    pub rag_file_ids: Option<Vec<String>>,
292}
293
294/// RAG 过滤配置。
295#[derive(Debug, Clone, Serialize, Deserialize, Default)]
296#[serde(rename_all = "camelCase")]
297pub struct RagRetrievalConfigFilter {
298    #[serde(skip_serializing_if = "Option::is_none")]
299    pub metadata_filter: Option<String>,
300    #[serde(skip_serializing_if = "Option::is_none")]
301    pub vector_distance_threshold: Option<f64>,
302    #[serde(skip_serializing_if = "Option::is_none")]
303    pub vector_similarity_threshold: Option<f64>,
304}
305
306/// RAG Hybrid Search 配置。
307#[derive(Debug, Clone, Serialize, Deserialize, Default)]
308#[serde(rename_all = "camelCase")]
309pub struct RagRetrievalConfigHybridSearch {
310    #[serde(skip_serializing_if = "Option::is_none")]
311    pub alpha: Option<f32>,
312}
313
314/// RAG Ranker 配置。
315#[derive(Debug, Clone, Serialize, Deserialize, Default)]
316#[serde(rename_all = "camelCase")]
317pub struct RagRetrievalConfigRankingLlmRanker {
318    #[serde(skip_serializing_if = "Option::is_none")]
319    pub model_name: Option<String>,
320}
321
322/// RAG Rank Service 配置。
323#[derive(Debug, Clone, Serialize, Deserialize, Default)]
324#[serde(rename_all = "camelCase")]
325pub struct RagRetrievalConfigRankingRankService {
326    #[serde(skip_serializing_if = "Option::is_none")]
327    pub model_name: Option<String>,
328}
329
330/// RAG Ranking 配置。
331#[derive(Debug, Clone, Serialize, Deserialize, Default)]
332#[serde(rename_all = "camelCase")]
333pub struct RagRetrievalConfigRanking {
334    #[serde(skip_serializing_if = "Option::is_none")]
335    pub llm_ranker: Option<RagRetrievalConfigRankingLlmRanker>,
336    #[serde(skip_serializing_if = "Option::is_none")]
337    pub rank_service: Option<RagRetrievalConfigRankingRankService>,
338}
339
340/// RAG Retrieval 配置。
341#[derive(Debug, Clone, Serialize, Deserialize, Default)]
342#[serde(rename_all = "camelCase")]
343pub struct RagRetrievalConfig {
344    #[serde(skip_serializing_if = "Option::is_none")]
345    pub filter: Option<RagRetrievalConfigFilter>,
346    #[serde(skip_serializing_if = "Option::is_none")]
347    pub hybrid_search: Option<RagRetrievalConfigHybridSearch>,
348    #[serde(skip_serializing_if = "Option::is_none")]
349    pub ranking: Option<RagRetrievalConfigRanking>,
350    #[serde(skip_serializing_if = "Option::is_none")]
351    pub top_k: Option<i32>,
352}
353
354/// Vertex RAG Store 配置。
355#[derive(Debug, Clone, Serialize, Deserialize, Default)]
356#[serde(rename_all = "camelCase")]
357pub struct VertexRagStore {
358    #[serde(skip_serializing_if = "Option::is_none")]
359    pub rag_corpora: Option<Vec<String>>,
360    #[serde(skip_serializing_if = "Option::is_none")]
361    pub rag_resources: Option<Vec<VertexRagStoreRagResource>>,
362    #[serde(skip_serializing_if = "Option::is_none")]
363    pub rag_retrieval_config: Option<RagRetrievalConfig>,
364    #[serde(skip_serializing_if = "Option::is_none")]
365    pub similarity_top_k: Option<i32>,
366    #[serde(skip_serializing_if = "Option::is_none")]
367    pub store_context: Option<bool>,
368    #[serde(skip_serializing_if = "Option::is_none")]
369    pub vector_distance_threshold: Option<f64>,
370}
371
372/// Retrieval 工具配置。
373#[derive(Debug, Clone, Serialize, Deserialize, Default)]
374#[serde(rename_all = "camelCase")]
375pub struct Retrieval {
376    #[serde(skip_serializing_if = "Option::is_none")]
377    pub disable_attribution: Option<bool>,
378    #[serde(skip_serializing_if = "Option::is_none")]
379    pub external_api: Option<ExternalApi>,
380    #[serde(skip_serializing_if = "Option::is_none")]
381    pub vertex_ai_search: Option<VertexAiSearch>,
382    #[serde(skip_serializing_if = "Option::is_none")]
383    pub vertex_rag_store: Option<VertexRagStore>,
384}
385
386/// 动态检索配置。
387#[derive(Debug, Clone, Serialize, Deserialize, Default)]
388#[serde(rename_all = "camelCase")]
389pub struct DynamicRetrievalConfig {
390    #[serde(skip_serializing_if = "Option::is_none")]
391    pub dynamic_threshold: Option<f32>,
392    #[serde(skip_serializing_if = "Option::is_none")]
393    pub mode: Option<DynamicRetrievalConfigMode>,
394}
395
396/// Tool config(共享配置)。
397#[derive(Debug, Clone, Serialize, Deserialize, Default)]
398#[serde(rename_all = "camelCase")]
399pub struct ToolConfig {
400    #[serde(skip_serializing_if = "Option::is_none")]
401    pub function_calling_config: Option<FunctionCallingConfig>,
402    #[serde(skip_serializing_if = "Option::is_none")]
403    pub retrieval_config: Option<RetrievalConfig>,
404}
405
406/// Function calling config。
407#[derive(Debug, Clone, Serialize, Deserialize, Default)]
408#[serde(rename_all = "camelCase")]
409pub struct FunctionCallingConfig {
410    #[serde(skip_serializing_if = "Option::is_none")]
411    pub allowed_function_names: Option<Vec<String>>,
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub mode: Option<FunctionCallingMode>,
414    #[serde(skip_serializing_if = "Option::is_none")]
415    pub stream_function_call_arguments: Option<bool>,
416}
417
418/// 经纬度位置。
419#[derive(Debug, Clone, Serialize, Deserialize, Default)]
420#[serde(rename_all = "camelCase")]
421pub struct LatLng {
422    #[serde(skip_serializing_if = "Option::is_none")]
423    pub latitude: Option<f64>,
424    #[serde(skip_serializing_if = "Option::is_none")]
425    pub longitude: Option<f64>,
426}
427
428/// Retrieval config。
429#[derive(Debug, Clone, Serialize, Deserialize, Default)]
430#[serde(rename_all = "camelCase")]
431pub struct RetrievalConfig {
432    #[serde(skip_serializing_if = "Option::is_none")]
433    pub lat_lng: Option<LatLng>,
434    #[serde(skip_serializing_if = "Option::is_none")]
435    pub language_code: Option<String>,
436}
437
438/// `OpenAPI` Schema(精简实现)。
439#[derive(Debug, Clone, Serialize, Deserialize, Default)]
440#[serde(rename_all = "camelCase")]
441pub struct Schema {
442    #[serde(skip_serializing_if = "Option::is_none")]
443    pub any_of: Option<Vec<Schema>>,
444    #[serde(skip_serializing_if = "Option::is_none")]
445    pub default: Option<Value>,
446    #[serde(skip_serializing_if = "Option::is_none")]
447    pub description: Option<String>,
448    #[serde(skip_serializing_if = "Option::is_none", rename = "enum")]
449    pub enum_values: Option<Vec<String>>,
450    #[serde(skip_serializing_if = "Option::is_none")]
451    pub example: Option<Value>,
452    #[serde(skip_serializing_if = "Option::is_none")]
453    pub format: Option<String>,
454    #[serde(skip_serializing_if = "Option::is_none")]
455    pub items: Option<Box<Schema>>,
456    #[serde(skip_serializing_if = "Option::is_none")]
457    pub max_items: Option<i64>,
458    #[serde(skip_serializing_if = "Option::is_none")]
459    pub max_length: Option<i64>,
460    #[serde(skip_serializing_if = "Option::is_none")]
461    pub max_properties: Option<i64>,
462    #[serde(skip_serializing_if = "Option::is_none")]
463    pub maximum: Option<f64>,
464    #[serde(skip_serializing_if = "Option::is_none")]
465    pub min_items: Option<i64>,
466    #[serde(skip_serializing_if = "Option::is_none")]
467    pub min_length: Option<i64>,
468    #[serde(skip_serializing_if = "Option::is_none")]
469    pub min_properties: Option<i64>,
470    #[serde(skip_serializing_if = "Option::is_none")]
471    pub minimum: Option<f64>,
472    #[serde(skip_serializing_if = "Option::is_none")]
473    pub nullable: Option<bool>,
474    #[serde(skip_serializing_if = "Option::is_none")]
475    pub pattern: Option<String>,
476    #[serde(skip_serializing_if = "Option::is_none")]
477    pub properties: Option<HashMap<String, Box<Schema>>>,
478    #[serde(skip_serializing_if = "Option::is_none")]
479    pub property_ordering: Option<Vec<String>>,
480    #[serde(skip_serializing_if = "Option::is_none")]
481    pub required: Option<Vec<String>>,
482    #[serde(skip_serializing_if = "Option::is_none")]
483    pub title: Option<String>,
484    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
485    pub ty: Option<Type>,
486}
487
488#[cfg(test)]
489mod schema_builder_tests {
490    use super::*;
491
492    #[test]
493    fn test_tool_serialization() {
494        let tool = Tool {
495            google_maps: Some(GoogleMaps {
496                enable_widget: Some(true),
497                ..GoogleMaps::default()
498            }),
499            ..Tool::default()
500        };
501
502        let json = serde_json::to_value(&tool).unwrap();
503        assert_eq!(json["googleMaps"]["enableWidget"].as_bool(), Some(true));
504    }
505}
506
507impl Schema {
508    /// 创建对象 Schema builder。
509    #[must_use]
510    pub fn object() -> SchemaBuilder {
511        SchemaBuilder::new(Type::Object)
512    }
513
514    /// 创建数组 Schema builder。
515    #[must_use]
516    pub fn array() -> SchemaBuilder {
517        SchemaBuilder::new(Type::Array)
518    }
519
520    /// 创建字符串 Schema。
521    #[must_use]
522    pub fn string() -> Self {
523        Self {
524            ty: Some(Type::String),
525            ..Default::default()
526        }
527    }
528
529    /// 创建整数 Schema。
530    #[must_use]
531    pub fn integer() -> Self {
532        Self {
533            ty: Some(Type::Integer),
534            ..Default::default()
535        }
536    }
537
538    /// 创建数字 Schema。
539    #[must_use]
540    pub fn number() -> Self {
541        Self {
542            ty: Some(Type::Number),
543            ..Default::default()
544        }
545    }
546
547    /// 创建布尔 Schema。
548    #[must_use]
549    pub fn boolean() -> Self {
550        Self {
551            ty: Some(Type::Boolean),
552            ..Default::default()
553        }
554    }
555}
556
557pub struct SchemaBuilder {
558    schema: Schema,
559}
560
561impl SchemaBuilder {
562    /// 创建 Schema builder。
563    #[must_use]
564    pub fn new(ty: Type) -> Self {
565        Self {
566            schema: Schema {
567                ty: Some(ty),
568                ..Default::default()
569            },
570        }
571    }
572
573    /// 设置描述。
574    #[must_use]
575    pub fn description(mut self, description: impl Into<String>) -> Self {
576        self.schema.description = Some(description.into());
577        self
578    }
579
580    /// 添加字段。
581    #[must_use]
582    pub fn property(mut self, name: impl Into<String>, schema: Schema) -> Self {
583        let properties = self.schema.properties.get_or_insert_with(HashMap::new);
584        properties.insert(name.into(), Box::new(schema));
585        self
586    }
587
588    /// 标记必填字段。
589    #[must_use]
590    pub fn required(mut self, name: impl Into<String>) -> Self {
591        let required = self.schema.required.get_or_insert_with(Vec::new);
592        required.push(name.into());
593        self
594    }
595
596    /// 设置数组元素 Schema。
597    #[must_use]
598    pub fn items(mut self, schema: Schema) -> Self {
599        self.schema.items = Some(Box::new(schema));
600        self
601    }
602
603    /// 设置枚举值。
604    #[must_use]
605    pub fn enum_values(mut self, values: Vec<String>) -> Self {
606        self.schema.enum_values = Some(values);
607        self
608    }
609
610    /// 构建 Schema。
611    #[must_use]
612    pub fn build(self) -> Schema {
613        self.schema
614    }
615}
616
617#[cfg(test)]
618mod tests {
619    use super::*;
620
621    #[test]
622    fn schema_builder_object() {
623        let schema = Schema::object()
624            .property("name", Schema::string())
625            .required("name")
626            .build();
627        assert_eq!(schema.ty, Some(Type::Object));
628        assert!(schema.properties.unwrap().contains_key("name"));
629    }
630
631    #[test]
632    fn schema_builder_array_and_enum() {
633        let schema = Schema::array()
634            .items(Schema::string())
635            .enum_values(vec!["a".into(), "b".into()])
636            .build();
637        assert_eq!(schema.ty, Some(Type::Array));
638        assert_eq!(schema.items.unwrap().ty, Some(Type::String));
639        assert_eq!(
640            schema.enum_values.unwrap(),
641            vec!["a".to_string(), "b".to_string()]
642        );
643    }
644
645    #[test]
646    fn tool_function_declaration_serialization() {
647        let declaration = FunctionDeclaration {
648            name: "lookup".to_string(),
649            description: Some("search".to_string()),
650            parameters: Some(Schema::object().property("q", Schema::string()).build()),
651            parameters_json_schema: None,
652            response: Some(Schema::string()),
653            response_json_schema: None,
654            behavior: None,
655        };
656        let tool = Tool {
657            function_declarations: Some(vec![declaration]),
658            ..Default::default()
659        };
660        let json = serde_json::to_value(&tool).unwrap();
661        assert!(json["functionDeclarations"].is_array());
662        assert_eq!(
663            json["functionDeclarations"][0]["name"].as_str(),
664            Some("lookup")
665        );
666    }
667}