diff --git a/objectiveai-rs/src/functions/quality/check_branch_scalar_function_tests.rs b/objectiveai-rs/src/functions/quality/check_branch_scalar_function_tests.rs index 8b12ae60..829a1962 100644 --- a/objectiveai-rs/src/functions/quality/check_branch_scalar_function_tests.rs +++ b/objectiveai-rs/src/functions/quality/check_branch_scalar_function_tests.rs @@ -6,8 +6,9 @@ use crate::chat::completions::request::{ RichContentExpression, RichContentPartExpression, UserMessageExpression, }; use crate::functions::expression::{ - ArrayInputSchema, BooleanInputSchema, Expression, InputSchema, - IntegerInputSchema, ObjectInputSchema, StringInputSchema, WithExpression, + AnyOfInputSchema, ArrayInputSchema, BooleanInputSchema, Expression, + InputSchema, IntegerInputSchema, ObjectInputSchema, StringInputSchema, + WithExpression, }; use crate::functions::quality::check_branch_scalar_function; use crate::functions::{Remote, @@ -1134,3 +1135,50 @@ fn all_tasks_skipped() { }; test_err(&f, "CV42"); } +#[test] +fn no_example_inputs() { + let f = RemoteFunction::Scalar { + description: "test".to_string(), + input_schema: InputSchema::AnyOf(AnyOfInputSchema { any_of: vec![] }), + input_maps: None, + tasks: vec![TaskExpression::ScalarFunction( + ScalarFunctionTaskExpression { + owner: "test".to_string(), + repository: "test".to_string(), + commit: "abc123".to_string(), + skip: None, + map: None, + input: WithExpression::Expression(Expression::Starlark( + "input".to_string(), + )), + output: Expression::Starlark("output".to_string()), + }, + )], + }; + test_err(&f, "QI01"); +} + +#[test] +fn placeholder_scalar_field_validation_fails() { + let f = RemoteFunction::Scalar { + description: "test".to_string(), + input_schema: InputSchema::Integer(IntegerInputSchema { + description: None, + minimum: Some(1), + maximum: Some(10), + }), + input_maps: None, + tasks: vec![TaskExpression::PlaceholderScalarFunction( + PlaceholderScalarFunctionTaskExpression { + input_schema: InputSchema::AnyOf(AnyOfInputSchema { any_of: vec![] }), + skip: None, + map: None, + input: WithExpression::Expression(Expression::Starlark( + "input".to_string(), + )), + output: Expression::Starlark("output".to_string()), + }, + )], + }; + test_err(&f, "CV04"); +} diff --git a/objectiveai-rs/src/functions/quality/check_branch_vector_function_tests.rs b/objectiveai-rs/src/functions/quality/check_branch_vector_function_tests.rs index 3b130aec..77cef96d 100644 --- a/objectiveai-rs/src/functions/quality/check_branch_vector_function_tests.rs +++ b/objectiveai-rs/src/functions/quality/check_branch_vector_function_tests.rs @@ -7,7 +7,7 @@ use crate::chat::completions::request::{ UserMessageExpression, }; use crate::functions::expression::{ - ArrayInputSchema, BooleanInputSchema, Expression, InputMaps, InputSchema, + AnyOfInputSchema, ArrayInputSchema, BooleanInputSchema, Expression, InputMaps, InputSchema, IntegerInputSchema, ObjectInputSchema, StringInputSchema, WithExpression, }; use crate::functions::quality::check_branch_vector_function; @@ -2218,3 +2218,263 @@ fn output_length_less_than_2() { }; test_err(&f, "VF03"); } +#[test] +fn input_maps_compilation_fails() { + let f = RemoteFunction::Vector { + description: "test".to_string(), + input_schema: InputSchema::Object(ObjectInputSchema { + description: None, + properties: index_map! { + "items" => InputSchema::Array(ArrayInputSchema { + description: None, + min_items: Some(2), + max_items: Some(2), + items: Box::new(InputSchema::String(StringInputSchema { + description: None, + r#enum: None, + })), + }), + "label" => InputSchema::String(StringInputSchema { + description: None, + r#enum: None, + }) + }, + required: Some(vec!["items".to_string(), "label".to_string()]), + }), + input_maps: Some(InputMaps::Many(vec![ + Expression::Starlark("1 + ".to_string()), // Syntax error + ])), + tasks: vec![TaskExpression::ScalarFunction(ScalarFunctionTaskExpression { + owner: "test".to_string(), + repository: "test".to_string(), + commit: "abc123".to_string(), + skip: None, + map: Some(0), + input: WithExpression::Expression(Expression::Starlark("map".to_string())), + output: Expression::Starlark("output".to_string()), + })], + output_length: WithExpression::Expression(Expression::Starlark("len(input['items'])".to_string())), + input_split: WithExpression::Expression(Expression::Starlark("[{'items': [x], 'label': input['label']} for x in input['items']]".to_string())), + input_merge: WithExpression::Expression(Expression::Starlark("{'items': [x['items'][0] for x in input], 'label': input[0]['label']}".to_string())), + }; + test_err(&f, "BV08"); +} + +#[test] +fn input_merge_fails_on_subset() { + let f = RemoteFunction::Vector { + description: "test".to_string(), + input_schema: InputSchema::Object(ObjectInputSchema { + description: None, + properties: index_map! { + "items" => InputSchema::Array(ArrayInputSchema { + description: None, + min_items: Some(3), + max_items: Some(3), + items: Box::new(InputSchema::String(StringInputSchema { + description: None, + r#enum: None, + })), + }), + "label" => InputSchema::String(StringInputSchema { + description: None, + r#enum: None, + }) + }, + required: Some(vec!["items".to_string(), "label".to_string()]), + }), + input_maps: None, + tasks: vec![TaskExpression::VectorFunction(VectorFunctionTaskExpression { + owner: "test".to_string(), + repository: "test".to_string(), + commit: "abc123".to_string(), + skip: None, + map: None, + input: WithExpression::Expression(Expression::Starlark("input".to_string())), + output: Expression::Starlark("output".to_string()), + })], + output_length: WithExpression::Expression(Expression::Starlark("len(input['items'])".to_string())), + input_split: WithExpression::Expression(Expression::Starlark("[{'items': [x], 'label': input['label']} for x in input['items']]".to_string())), + input_merge: WithExpression::Expression(Expression::Starlark("{'items': [x['items'][0] for x in input], 'label': 1/0}".to_string())), + }; + test_err(&f, "VF10"); +} + +#[test] +fn no_example_inputs() { + let f = RemoteFunction::Vector { + description: "test".to_string(), + input_schema: InputSchema::AnyOf(AnyOfInputSchema { any_of: vec![] }), + input_maps: None, + tasks: vec![TaskExpression::VectorFunction(VectorFunctionTaskExpression { + owner: "test".to_string(), + repository: "test".to_string(), + commit: "abc123".to_string(), + skip: None, + map: None, + input: WithExpression::Expression(Expression::Starlark("input".to_string())), + output: Expression::Starlark("output".to_string()), + })], + output_length: WithExpression::Expression(Expression::Starlark("1".to_string())), + input_split: WithExpression::Expression(Expression::Starlark("[input]".to_string())), + input_merge: WithExpression::Expression(Expression::Starlark("input[0]".to_string())), + }; + test_err(&f, "QI01"); +} + +#[test] +fn fixed_mapped_input() { + let f = RemoteFunction::Vector { + description: "test".to_string(), + input_schema: InputSchema::Object(ObjectInputSchema { + description: None, + properties: index_map! { + "items" => InputSchema::Array(ArrayInputSchema { + description: None, + min_items: Some(2), + max_items: Some(2), + items: Box::new(InputSchema::String(StringInputSchema { + description: None, + r#enum: None, + })), + }), + "label" => InputSchema::String(StringInputSchema { + description: None, + r#enum: None, + }) + }, + required: Some(vec!["items".to_string(), "label".to_string()]), + }), + input_maps: Some(InputMaps::Many(vec![ + Expression::Starlark("['fixed_val', 'fixed_val']".to_string()), + ])), + tasks: vec![TaskExpression::ScalarFunction(ScalarFunctionTaskExpression { + owner: "test".to_string(), + repository: "test".to_string(), + commit: "abc123".to_string(), + skip: None, + map: Some(0), + input: WithExpression::Expression(Expression::Starlark("map".to_string())), + output: Expression::Starlark("output".to_string()), + })], + output_length: WithExpression::Expression(Expression::Starlark("len(input['items'])".to_string())), + input_split: WithExpression::Expression(Expression::Starlark("[{'items': [x], 'label': input['label']} for x in input['items']]".to_string())), + input_merge: WithExpression::Expression(Expression::Starlark("{'items': [x['items'][0] for x in input], 'label': input[0]['label']}".to_string())), + }; + test_err(&f, "BV08"); +} + +#[test] +fn all_mapped_inputs_equal() { + let f = RemoteFunction::Vector { + description: "test".to_string(), + input_schema: InputSchema::Object(ObjectInputSchema { + description: None, + properties: index_map! { + "items" => InputSchema::Array(ArrayInputSchema { + description: None, + min_items: Some(2), + max_items: Some(2), + items: Box::new(InputSchema::String(StringInputSchema { + description: None, + r#enum: None, + })), + }), + "label" => InputSchema::String(StringInputSchema { + description: None, + r#enum: None, + }) + }, + required: Some(vec!["items".to_string(), "label".to_string()]), + }), + input_maps: Some(InputMaps::Many(vec![ + Expression::Starlark("[input['label'], input['label']]".to_string()), + ])), + tasks: vec![TaskExpression::ScalarFunction(ScalarFunctionTaskExpression { + owner: "test".to_string(), + repository: "test".to_string(), + commit: "abc123".to_string(), + skip: None, + map: Some(0), + input: WithExpression::Expression(Expression::Starlark("map".to_string())), + output: Expression::Starlark("output".to_string()), + })], + output_length: WithExpression::Expression(Expression::Starlark("len(input['items'])".to_string())), + input_split: WithExpression::Expression(Expression::Starlark("[{'items': [x], 'label': input['label']} for x in input['items']]".to_string())), + input_merge: WithExpression::Expression(Expression::Starlark("{'items': [x['items'][0] for x in input], 'label': input[0]['label']}".to_string())), + }; + test_err(&f, "BV08"); +} + +#[test] +fn placeholder_scalar_field_fails() { + let f = RemoteFunction::Vector { + description: "test".to_string(), + input_schema: InputSchema::Object(ObjectInputSchema { + description: None, + properties: index_map! { + "items" => InputSchema::Array(ArrayInputSchema { + description: None, + min_items: Some(2), + max_items: Some(2), + items: Box::new(InputSchema::String(StringInputSchema { + description: None, + r#enum: None, + })), + }) + }, + required: Some(vec!["items".to_string()]), + }), + input_maps: Some(InputMaps::Many(vec![ + Expression::Starlark("input['items']".to_string()), + ])), + tasks: vec![TaskExpression::PlaceholderScalarFunction(PlaceholderScalarFunctionTaskExpression { + input_schema: InputSchema::AnyOf(AnyOfInputSchema { any_of: vec![] }), + skip: None, + map: Some(0), + input: WithExpression::Expression(Expression::Starlark("map".to_string())), + output: Expression::Starlark("output".to_string()), + })], + output_length: WithExpression::Expression(Expression::Starlark("len(input['items'])".to_string())), + input_split: WithExpression::Expression(Expression::Starlark("[{'items': [x]} for x in input['items']]".to_string())), + input_merge: WithExpression::Expression(Expression::Starlark("{'items': [x['items'][0] for x in input]}".to_string())), + }; + test_err(&f, "BV08"); +} + +#[test] +fn placeholder_vector_field_fails() { + let f = RemoteFunction::Vector { + description: "test".to_string(), + input_schema: InputSchema::Object(ObjectInputSchema { + description: None, + properties: index_map! { + "items" => InputSchema::Array(ArrayInputSchema { + description: None, + min_items: Some(2), + max_items: Some(2), + items: Box::new(InputSchema::String(StringInputSchema { + description: None, + r#enum: None, + })), + }) + }, + required: Some(vec!["items".to_string()]), + }), + input_maps: None, + tasks: vec![TaskExpression::PlaceholderVectorFunction(PlaceholderVectorFunctionTaskExpression { + input_schema: InputSchema::AnyOf(AnyOfInputSchema { any_of: vec![] }), + output_length: WithExpression::Expression(Expression::Starlark("len(input['items'])".to_string())), + input_split: WithExpression::Expression(Expression::Starlark("[{'items': [x]} for x in input['items']]".to_string())), + input_merge: WithExpression::Expression(Expression::Starlark("{'items': [x['items'][0] for x in input]}".to_string())), + skip: None, + map: None, + input: WithExpression::Expression(Expression::Starlark("input".to_string())), + output: Expression::Starlark("output".to_string()), + })], + output_length: WithExpression::Expression(Expression::Starlark("len(input['items'])".to_string())), + input_split: WithExpression::Expression(Expression::Starlark("[{'items': [x]} for x in input['items']]".to_string())), + input_merge: WithExpression::Expression(Expression::Starlark("{'items': [x['items'][0] for x in input]}".to_string())), + }; + test_err(&f, "CV05"); +} diff --git a/objectiveai-rs/src/functions/quality/check_leaf_scalar_function_tests.rs b/objectiveai-rs/src/functions/quality/check_leaf_scalar_function_tests.rs index 0ee4ddd2..18a5a1a7 100644 --- a/objectiveai-rs/src/functions/quality/check_leaf_scalar_function_tests.rs +++ b/objectiveai-rs/src/functions/quality/check_leaf_scalar_function_tests.rs @@ -9,9 +9,9 @@ use crate::chat::completions::request::{ SystemMessageExpression, ToolMessageExpression, UserMessageExpression, }; use crate::functions::expression::{ - ArrayInputSchema, BooleanInputSchema, Expression, ImageInputSchema, - InputSchema, IntegerInputSchema, ObjectInputSchema, StringInputSchema, - WithExpression, + AnyOfInputSchema, ArrayInputSchema, BooleanInputSchema, Expression, + ImageInputSchema, InputSchema, IntegerInputSchema, ObjectInputSchema, + StringInputSchema, WithExpression, }; use crate::functions::quality::check_leaf_scalar_function; use crate::functions::{Remote, @@ -2313,3 +2313,49 @@ fn all_tasks_skipped() { }; test_err(&f, "CV42"); } + +#[test] +fn no_example_inputs() { + let f = RemoteFunction::Scalar { + description: "test".to_string(), + input_schema: InputSchema::AnyOf(AnyOfInputSchema { any_of: vec![] }), + input_maps: None, + tasks: vec![TaskExpression::VectorCompletion( + VectorCompletionTaskExpression { + skip: None, + map: None, + messages: WithExpression::Value(vec![WithExpression::Value( + MessageExpression::User(UserMessageExpression { + content: WithExpression::Value( + RichContentExpression::Parts(vec![ + WithExpression::Value( + RichContentPartExpression::Text { + text: WithExpression::Value( + "Hello".to_string(), + ), + }, + ), + ]), + ), + name: None, + }), + )]), + tools: None, + responses: WithExpression::Value(vec![ + WithExpression::Value(RichContentExpression::Parts(vec![ + WithExpression::Value(RichContentPartExpression::Text { + text: WithExpression::Value("Yes".to_string()), + }), + ])), + WithExpression::Value(RichContentExpression::Parts(vec![ + WithExpression::Value(RichContentPartExpression::Text { + text: WithExpression::Value("No".to_string()), + }), + ])), + ]), + output: Expression::Starlark("output['scores'][0]".to_string()), + }, + )], + }; + test_err(&f, "QI01"); +} diff --git a/objectiveai-rs/src/functions/quality/check_leaf_vector_function_tests.rs b/objectiveai-rs/src/functions/quality/check_leaf_vector_function_tests.rs index 0ea035b8..b4574cea 100644 --- a/objectiveai-rs/src/functions/quality/check_leaf_vector_function_tests.rs +++ b/objectiveai-rs/src/functions/quality/check_leaf_vector_function_tests.rs @@ -8,7 +8,7 @@ use crate::chat::completions::request::{ SystemMessageExpression, UserMessageExpression, }; use crate::functions::expression::{ - ArrayInputSchema, BooleanInputSchema, Expression, ImageInputSchema, + AnyOfInputSchema, ArrayInputSchema, BooleanInputSchema, Expression, ImageInputSchema, InputSchema, IntegerInputSchema, ObjectInputSchema, StringInputSchema, WithExpression, }; @@ -3028,3 +3028,103 @@ fn job_application_ranker_3() { }; test(&f); } + +#[test] +fn input_merge_fails_on_subset() { + let f = RemoteFunction::Vector { + description: "test".to_string(), + input_schema: InputSchema::Object(ObjectInputSchema { + description: None, + properties: index_map! { + "items" => InputSchema::Array(ArrayInputSchema { + description: None, + min_items: Some(3), + max_items: Some(3), + items: Box::new(InputSchema::String(StringInputSchema { + description: None, + r#enum: None, + })), + }), + "label" => InputSchema::String(StringInputSchema { + description: None, + r#enum: None, + }) + }, + required: Some(vec!["items".to_string(), "label".to_string()]), + }), + input_maps: None, + tasks: vec![TaskExpression::VectorCompletion( + VectorCompletionTaskExpression { + skip: None, + map: None, + messages: WithExpression::Value(vec![WithExpression::Value( + MessageExpression::User(UserMessageExpression { + content: WithExpression::Value( + RichContentExpression::Parts(vec![ + WithExpression::Value( + RichContentPartExpression::Text { + text: WithExpression::Value( + "Hello".to_string(), + ), + }, + ), + ]), + ), + name: None, + }), + )]), + tools: None, + responses: WithExpression::Expression(Expression::Starlark( + "[[{'type': 'text', 'text': x}] for x in input]" + .to_string(), + )), + output: Expression::Starlark("output['scores']".to_string()), + }, + )], + output_length: WithExpression::Expression(Expression::Starlark("len(input['items'])".to_string())), + input_split: WithExpression::Expression(Expression::Starlark("[{'items': [x], 'label': input['label']} for x in input['items']]".to_string())), + input_merge: WithExpression::Expression(Expression::Starlark("{'items': [x['items'][0] for x in input], 'label': input[0]['label'] if len(input) == 3 else 1/0}".to_string())), + }; + test_err(&f, "CV14"); +} + +#[test] +fn no_example_inputs() { + let f = RemoteFunction::Vector { + description: "test".to_string(), + input_schema: InputSchema::AnyOf(AnyOfInputSchema { any_of: vec![] }), + input_maps: None, + tasks: vec![TaskExpression::VectorCompletion( + VectorCompletionTaskExpression { + skip: None, + map: None, + messages: WithExpression::Value(vec![WithExpression::Value( + MessageExpression::User(UserMessageExpression { + content: WithExpression::Value( + RichContentExpression::Parts(vec![ + WithExpression::Value( + RichContentPartExpression::Text { + text: WithExpression::Value( + "Hello".to_string(), + ), + }, + ), + ]), + ), + name: None, + }), + )]), + tools: None, + responses: WithExpression::Expression(Expression::Starlark( + "[[{'type': 'text', 'text': x}] for x in input]" + .to_string(), + )), + output: Expression::Starlark("output['scores']".to_string()), + }, + )], + output_length: WithExpression::Expression(Expression::Starlark("1".to_string())), + input_split: WithExpression::Expression(Expression::Starlark("[input]".to_string())), + input_merge: WithExpression::Expression(Expression::Starlark("input[0]".to_string())), + }; + test_err(&f, "QI01"); +} diff --git a/objectiveai-rs/src/functions/quality/check_scalar_fields_tests.rs b/objectiveai-rs/src/functions/quality/check_scalar_fields_tests.rs index 010e3ff3..3231200e 100644 --- a/objectiveai-rs/src/functions/quality/check_scalar_fields_tests.rs +++ b/objectiveai-rs/src/functions/quality/check_scalar_fields_tests.rs @@ -4,7 +4,7 @@ use super::check_scalar_fields::{ScalarFieldsValidation, check_scalar_fields}; use crate::functions::expression::{ - InputSchema, IntegerInputSchema, ObjectInputSchema, StringInputSchema, + AnyOfInputSchema, InputSchema, IntegerInputSchema, ObjectInputSchema, StringInputSchema, }; use crate::util::index_map; @@ -64,3 +64,13 @@ fn valid_object_schema() { }), }); } + +#[test] +fn no_example_inputs() { + test_err( + ScalarFieldsValidation { + input_schema: InputSchema::AnyOf(AnyOfInputSchema { any_of: vec![] }), + }, + "QI01", + ); +} diff --git a/objectiveai-rs/src/functions/quality/check_vector_fields_tests.rs b/objectiveai-rs/src/functions/quality/check_vector_fields_tests.rs index 8a99566e..767f2323 100644 --- a/objectiveai-rs/src/functions/quality/check_vector_fields_tests.rs +++ b/objectiveai-rs/src/functions/quality/check_vector_fields_tests.rs @@ -6,7 +6,7 @@ use super::check_vector_fields::{ VectorFieldsValidation, check_vector_fields, inputs_equal, random_subsets, }; use crate::functions::expression::{ - ArrayInputSchema, Expression, Input, InputSchema, IntegerInputSchema, + AnyOfInputSchema, ArrayInputSchema, Expression, Input, InputSchema, IntegerInputSchema, ObjectInputSchema, StringInputSchema, WithExpression, }; use crate::util::index_map; @@ -552,3 +552,195 @@ fn job_application_ranker_1() { )), }, "VF03") } + +#[test] +fn output_length_fails_for_split() { + test_err( + VectorFieldsValidation { + input_schema: InputSchema::Array(ArrayInputSchema { + description: None, + min_items: Some(2), + max_items: Some(5), + items: Box::new(InputSchema::String(StringInputSchema { + description: None, + r#enum: None, + })), + }), + output_length: WithExpression::Expression(Expression::Starlark( + "len(input) if len(input) > 5 else 1/0".to_string(), + )), + input_split: WithExpression::Expression(Expression::Starlark( + "[[x] for x in input]".to_string(), + )), + input_merge: WithExpression::Expression(Expression::Starlark( + "[x[0] for x in input]".to_string(), + )), + }, + "VF01", + ); +} + +#[test] +fn input_merge_fails_for_subset() { + test_err( + VectorFieldsValidation { + input_schema: InputSchema::Array(ArrayInputSchema { + description: None, + min_items: Some(3), + max_items: Some(3), + items: Box::new(InputSchema::String(StringInputSchema { + description: None, + r#enum: None, + })), + }), + output_length: WithExpression::Expression(Expression::Starlark( + "len(input)".to_string(), + )), + input_split: WithExpression::Expression(Expression::Starlark( + "[[x] for x in input]".to_string(), + )), + input_merge: WithExpression::Expression(Expression::Starlark( + "[x[0] for x in input] if len(input) == 3 else 1/0".to_string(), + )), + }, + "VF16", + ); +} + +#[test] +fn output_length_fails_for_merged_subset() { + test_err( + VectorFieldsValidation { + input_schema: InputSchema::Array(ArrayInputSchema { + description: None, + min_items: Some(3), + max_items: Some(3), + items: Box::new(InputSchema::String(StringInputSchema { + description: None, + r#enum: None, + })), + }), + output_length: WithExpression::Expression(Expression::Starlark( + "len(input) if len(input) == 3 or len(input) == 1 else 1/0".to_string(), + )), + input_split: WithExpression::Expression(Expression::Starlark( + "[[x] for x in input]".to_string(), + )), + input_merge: WithExpression::Expression(Expression::Starlark( + "[x[0] for x in input]".to_string(), + )), + }, + "VF18", + ); +} + +#[test] +fn output_length_wrong_for_merged_subset() { + test_err( + VectorFieldsValidation { + input_schema: InputSchema::Array(ArrayInputSchema { + description: None, + min_items: Some(3), + max_items: Some(3), + items: Box::new(InputSchema::String(StringInputSchema { + description: None, + r#enum: None, + })), + }), + output_length: WithExpression::Expression(Expression::Starlark( + "len(input) if len(input) == 3 or len(input) == 1 else 999".to_string(), + )), + input_split: WithExpression::Expression(Expression::Starlark( + "[[x] for x in input]".to_string(), + )), + input_merge: WithExpression::Expression(Expression::Starlark( + "[x[0] for x in input]".to_string(), + )), + }, + "VF20", + ); +} + +#[test] +fn no_example_inputs() { + test_err( + VectorFieldsValidation { + input_schema: InputSchema::AnyOf(AnyOfInputSchema { any_of: vec![] }), + output_length: WithExpression::Expression(Expression::Starlark( + "len(input)".to_string(), + )), + input_split: WithExpression::Expression(Expression::Starlark( + "[[x] for x in input]".to_string(), + )), + input_merge: WithExpression::Expression(Expression::Starlark( + "[x[0] for x in input]".to_string(), + )), + }, + "QI01", + ); +} + +#[test] +fn array_violates_min_items() { + test_err( + VectorFieldsValidation { + input_schema: InputSchema::Array(ArrayInputSchema { + description: None, + min_items: Some(3), + max_items: Some(3), + items: Box::new(InputSchema::String(StringInputSchema { + description: None, + r#enum: None, + })), + }), + output_length: WithExpression::Expression(Expression::Starlark( + "len(input)".to_string(), + )), + input_split: WithExpression::Expression(Expression::Starlark( + "[[x] for x in input]".to_string(), + )), + input_merge: WithExpression::Expression(Expression::Starlark( + "[x[0] for x in input]".to_string(), + )), + }, + "VF23", + ); +} + +#[test] +fn array_violates_max_items() { + test_err( + VectorFieldsValidation { + input_schema: InputSchema::Array(ArrayInputSchema { + description: None, + min_items: Some(2), + max_items: Some(4), + items: Box::new(InputSchema::Object(ObjectInputSchema { + description: None, + properties: index_map! { + "inner" => InputSchema::Array(ArrayInputSchema { + description: None, + min_items: Some(1), + max_items: Some(1), + items: Box::new(InputSchema::String(StringInputSchema { + description: None, + r#enum: None, + })), + }) + }, + required: Some(vec!["inner".to_string()]), + })), + }), + output_length: WithExpression::Expression(Expression::Starlark( + "len(input) if type(input) == 'list' else 1".to_string(), + )), + input_split: WithExpression::Expression(Expression::Starlark( + "[{'val': x, 'orig_len': len(input)} for x in input]".to_string(), + )), + input_merge: WithExpression::Expression(Expression::Starlark( + "[{'inner': x['val']['inner'] * 2} if len(input) != x['orig_len'] else x['val'] for x in input]".to_string(), + )), + }, + "VF24", + ); +} diff --git a/objectiveai-web/app/functions/[...slug]/page.tsx b/objectiveai-web/app/functions/[...slug]/page.tsx index d74bcf29..a7761045 100644 --- a/objectiveai-web/app/functions/[...slug]/page.tsx +++ b/objectiveai-web/app/functions/[...slug]/page.tsx @@ -11,6 +11,7 @@ import { loadReasoningModels } from "../../../lib/reasoning-models"; import { useIsMobile } from "../../../hooks/useIsMobile"; import { useObjectiveAI } from "../../../hooks/useObjectiveAI"; import { InputBuilder } from "../../../components/InputBuilder"; +import { LoadingSpinner, ErrorAlert, EmptyState, SkeletonFunctionDetails } from "../../../components/ui"; import SchemaFormBuilder from "../../../components/SchemaForm/SchemaFormBuilder"; import type { InputSchema, InputValue } from "../../../components/SchemaForm/types"; import SplitItemDisplay from "../../../components/SplitItemDisplay"; @@ -1115,136 +1116,136 @@ export default function FunctionDetailPage({ params }: { params: Promise<{ slug:
+ {fn.description} +
+- {fn.description} -
-