chore: import upstream snapshot with attribution
Documentation: build and deploy / deploy (push) Waiting to run
langchain4j-github-bot.yml lint / langchain4j-github-bot.yml validation (push) Waiting to run
Java CI / compile_and_unit_test (17) (push) Waiting to run
Java CI / compile_and_unit_test (21) (push) Waiting to run
Java CI / integration_test (21) (push) Blocked by required conditions
Java CI / compliance (push) Waiting to run
Java CI / spotless (push) Waiting to run
snapshot_release / snapshot_release (push) Waiting to run
Split Package Detection / check-split-packages (push) Waiting to run
Java CI / compile_and_unit_test (25) (push) Waiting to run
Documentation: build and deploy / deploy (push) Waiting to run
langchain4j-github-bot.yml lint / langchain4j-github-bot.yml validation (push) Waiting to run
Java CI / compile_and_unit_test (17) (push) Waiting to run
Java CI / compile_and_unit_test (21) (push) Waiting to run
Java CI / integration_test (21) (push) Blocked by required conditions
Java CI / compliance (push) Waiting to run
Java CI / spotless (push) Waiting to run
snapshot_release / snapshot_release (push) Waiting to run
Split Package Detection / check-split-packages (push) Waiting to run
Java CI / compile_and_unit_test (25) (push) Waiting to run
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "LangChain4j",
|
||||
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/java:1": {
|
||||
"version": "25",
|
||||
"jdkDistro": "tem",
|
||||
"installMaven": true,
|
||||
"mavenVersion": "latest"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/azure-cli:1": {},
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
|
||||
"ghcr.io/devcontainers/features/github-cli:1": {}
|
||||
},
|
||||
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"java.configuration.updateBuildConfiguration": "automatic",
|
||||
"java.compile.nullAnalysis.mode": "automatic"
|
||||
},
|
||||
"extensions": [
|
||||
"redhat.vscode-xml",
|
||||
"redhat.vscode-yaml",
|
||||
"visualstudioexptteam.vscodeintellicode",
|
||||
"vscjava.vscode-java-pack",
|
||||
"editorconfig.editorconfig"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"postCreateCommand": "java -version && mvn -version",
|
||||
"remoteUser": "vscode"
|
||||
}
|
||||
+942
@@ -0,0 +1,942 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
max_line_length = 120
|
||||
tab_width = 4
|
||||
ij_continuation_indent_size = 8
|
||||
ij_formatter_off_tag = @formatter:off
|
||||
ij_formatter_on_tag = @formatter:on
|
||||
|
||||
[*.java]
|
||||
ij_java_align_consecutive_assignments = false
|
||||
ij_java_align_consecutive_variable_declarations = false
|
||||
ij_java_align_group_field_declarations = false
|
||||
ij_java_align_multiline_annotation_parameters = false
|
||||
ij_java_align_multiline_array_initializer_expression = false
|
||||
ij_java_align_multiline_assignment = false
|
||||
ij_java_align_multiline_binary_operation = false
|
||||
ij_java_align_multiline_chained_methods = false
|
||||
ij_java_align_multiline_deconstruction_list_components = true
|
||||
ij_java_align_multiline_extends_list = false
|
||||
ij_java_align_multiline_for = true
|
||||
ij_java_align_multiline_method_parentheses = false
|
||||
ij_java_align_multiline_parameters = true
|
||||
ij_java_align_multiline_parameters_in_calls = false
|
||||
ij_java_align_multiline_parenthesized_expression = false
|
||||
ij_java_align_multiline_records = true
|
||||
ij_java_align_multiline_resources = true
|
||||
ij_java_align_multiline_ternary_operation = false
|
||||
ij_java_align_multiline_text_blocks = false
|
||||
ij_java_align_multiline_throws_list = false
|
||||
ij_java_align_subsequent_simple_methods = false
|
||||
ij_java_align_throws_keyword = false
|
||||
ij_java_align_types_in_multi_catch = true
|
||||
ij_java_annotation_parameter_wrap = off
|
||||
ij_java_array_initializer_new_line_after_left_brace = false
|
||||
ij_java_array_initializer_right_brace_on_new_line = false
|
||||
ij_java_array_initializer_wrap = off
|
||||
ij_java_assert_statement_colon_on_next_line = false
|
||||
ij_java_assert_statement_wrap = off
|
||||
ij_java_assignment_wrap = off
|
||||
ij_java_binary_operation_sign_on_next_line = false
|
||||
ij_java_binary_operation_wrap = off
|
||||
ij_java_blank_lines_after_anonymous_class_header = 0
|
||||
ij_java_blank_lines_after_class_header = 0
|
||||
ij_java_blank_lines_after_imports = 1
|
||||
ij_java_blank_lines_after_package = 1
|
||||
ij_java_blank_lines_around_class = 1
|
||||
ij_java_blank_lines_around_field = 0
|
||||
ij_java_blank_lines_around_field_in_interface = 0
|
||||
ij_java_blank_lines_around_field_with_annotations = 0
|
||||
ij_java_blank_lines_around_initializer = 1
|
||||
ij_java_blank_lines_around_method = 1
|
||||
ij_java_blank_lines_around_method_in_interface = 1
|
||||
ij_java_blank_lines_before_class_end = 0
|
||||
ij_java_blank_lines_before_imports = 1
|
||||
ij_java_blank_lines_before_method_body = 0
|
||||
ij_java_blank_lines_before_package = 0
|
||||
ij_java_block_brace_style = end_of_line
|
||||
ij_java_block_comment_add_space = false
|
||||
ij_java_block_comment_at_first_column = true
|
||||
ij_java_builder_methods =
|
||||
ij_java_call_parameters_new_line_after_left_paren = false
|
||||
ij_java_call_parameters_right_paren_on_new_line = false
|
||||
ij_java_call_parameters_wrap = off
|
||||
ij_java_case_statement_on_separate_line = true
|
||||
ij_java_catch_on_new_line = false
|
||||
ij_java_class_annotation_wrap = split_into_lines
|
||||
ij_java_class_brace_style = end_of_line
|
||||
ij_java_class_count_to_use_import_on_demand = 999
|
||||
ij_java_class_names_in_javadoc = 1
|
||||
ij_java_deconstruction_list_wrap = normal
|
||||
ij_java_do_not_indent_top_level_class_members = false
|
||||
ij_java_do_not_wrap_after_single_annotation = false
|
||||
ij_java_do_not_wrap_after_single_annotation_in_parameter = false
|
||||
ij_java_do_while_brace_force = never
|
||||
ij_java_doc_add_blank_line_after_description = true
|
||||
ij_java_doc_add_blank_line_after_param_comments = false
|
||||
ij_java_doc_add_blank_line_after_return = false
|
||||
ij_java_doc_add_p_tag_on_empty_lines = true
|
||||
ij_java_doc_align_exception_comments = true
|
||||
ij_java_doc_align_param_comments = true
|
||||
ij_java_doc_do_not_wrap_if_one_line = false
|
||||
ij_java_doc_enable_formatting = true
|
||||
ij_java_doc_enable_leading_asterisks = true
|
||||
ij_java_doc_indent_on_continuation = false
|
||||
ij_java_doc_keep_empty_lines = true
|
||||
ij_java_doc_keep_empty_parameter_tag = true
|
||||
ij_java_doc_keep_empty_return_tag = true
|
||||
ij_java_doc_keep_empty_throws_tag = true
|
||||
ij_java_doc_keep_invalid_tags = true
|
||||
ij_java_doc_param_description_on_new_line = false
|
||||
ij_java_doc_preserve_line_breaks = false
|
||||
ij_java_doc_use_throws_not_exception_tag = true
|
||||
ij_java_else_on_new_line = false
|
||||
ij_java_enum_constants_wrap = off
|
||||
ij_java_enum_field_annotation_wrap = off
|
||||
ij_java_extends_keyword_wrap = off
|
||||
ij_java_extends_list_wrap = off
|
||||
ij_java_field_annotation_wrap = split_into_lines
|
||||
ij_java_field_name_prefix =
|
||||
ij_java_field_name_suffix =
|
||||
ij_java_filter_class_prefix =
|
||||
ij_java_filter_class_suffix =
|
||||
ij_java_filter_dd_prefix =
|
||||
ij_java_filter_dd_suffix =
|
||||
ij_java_finally_on_new_line = false
|
||||
ij_java_for_brace_force = never
|
||||
ij_java_for_statement_new_line_after_left_paren = false
|
||||
ij_java_for_statement_right_paren_on_new_line = false
|
||||
ij_java_for_statement_wrap = off
|
||||
ij_java_generate_final_locals = true
|
||||
ij_java_generate_final_parameters = true
|
||||
ij_java_generate_use_type_annotation_before_type = true
|
||||
ij_java_if_brace_force = never
|
||||
ij_java_imports_layout = $*,|,javax.**,java.**,*
|
||||
ij_java_indent_case_from_switch = true
|
||||
ij_java_insert_inner_class_imports = false
|
||||
ij_java_insert_override_annotation = true
|
||||
ij_java_keep_blank_lines_before_right_brace = 2
|
||||
ij_java_keep_blank_lines_between_package_declaration_and_header = 2
|
||||
ij_java_keep_blank_lines_in_code = 2
|
||||
ij_java_keep_blank_lines_in_declarations = 2
|
||||
ij_java_keep_builder_methods_indents = false
|
||||
ij_java_keep_control_statement_in_one_line = true
|
||||
ij_java_keep_first_column_comment = true
|
||||
ij_java_keep_indents_on_empty_lines = false
|
||||
ij_java_keep_line_breaks = true
|
||||
ij_java_keep_multiple_expressions_in_one_line = false
|
||||
ij_java_keep_simple_blocks_in_one_line = false
|
||||
ij_java_keep_simple_classes_in_one_line = false
|
||||
ij_java_keep_simple_lambdas_in_one_line = false
|
||||
ij_java_keep_simple_methods_in_one_line = false
|
||||
ij_java_label_indent_absolute = false
|
||||
ij_java_label_indent_size = 0
|
||||
ij_java_lambda_brace_style = end_of_line
|
||||
ij_java_layout_static_imports_separately = true
|
||||
ij_java_line_comment_add_space = false
|
||||
ij_java_line_comment_add_space_on_reformat = false
|
||||
ij_java_line_comment_at_first_column = true
|
||||
ij_java_listener_class_prefix =
|
||||
ij_java_listener_class_suffix =
|
||||
ij_java_local_variable_name_prefix =
|
||||
ij_java_local_variable_name_suffix =
|
||||
ij_java_method_annotation_wrap = split_into_lines
|
||||
ij_java_method_brace_style = end_of_line
|
||||
ij_java_method_call_chain_wrap = off
|
||||
ij_java_method_parameters_new_line_after_left_paren = false
|
||||
ij_java_method_parameters_right_paren_on_new_line = false
|
||||
ij_java_method_parameters_wrap = off
|
||||
ij_java_modifier_list_wrap = false
|
||||
ij_java_multi_catch_types_wrap = normal
|
||||
ij_java_names_count_to_use_import_on_demand = 999
|
||||
ij_java_new_line_after_lparen_in_annotation = false
|
||||
ij_java_new_line_after_lparen_in_deconstruction_pattern = true
|
||||
ij_java_new_line_after_lparen_in_record_header = false
|
||||
ij_java_new_line_when_body_is_presented = false
|
||||
ij_java_packages_to_use_import_on_demand = java.awt.*, javax.swing.*
|
||||
ij_java_parameter_annotation_wrap = off
|
||||
ij_java_parameter_name_prefix =
|
||||
ij_java_parameter_name_suffix =
|
||||
ij_java_parentheses_expression_new_line_after_left_paren = false
|
||||
ij_java_parentheses_expression_right_paren_on_new_line = false
|
||||
ij_java_place_assignment_sign_on_next_line = false
|
||||
ij_java_prefer_longer_names = true
|
||||
ij_java_prefer_parameters_wrap = false
|
||||
ij_java_record_components_wrap = normal
|
||||
ij_java_repeat_annotations =
|
||||
ij_java_repeat_synchronized = true
|
||||
ij_java_replace_instanceof_and_cast = false
|
||||
ij_java_replace_null_check = true
|
||||
ij_java_replace_sum_lambda_with_method_ref = true
|
||||
ij_java_resource_list_new_line_after_left_paren = false
|
||||
ij_java_resource_list_right_paren_on_new_line = false
|
||||
ij_java_resource_list_wrap = off
|
||||
ij_java_rparen_on_new_line_in_annotation = false
|
||||
ij_java_rparen_on_new_line_in_deconstruction_pattern = true
|
||||
ij_java_rparen_on_new_line_in_record_header = false
|
||||
ij_java_servlet_class_prefix =
|
||||
ij_java_servlet_class_suffix =
|
||||
ij_java_servlet_dd_prefix =
|
||||
ij_java_servlet_dd_suffix =
|
||||
ij_java_session_dd_prefix =
|
||||
ij_java_session_dd_suffix = EJB
|
||||
ij_java_session_eb_prefix =
|
||||
ij_java_session_eb_suffix = Bean
|
||||
ij_java_session_hi_prefix =
|
||||
ij_java_session_hi_suffix = Home
|
||||
ij_java_session_lhi_prefix = Local
|
||||
ij_java_session_lhi_suffix = Home
|
||||
ij_java_session_li_prefix = Local
|
||||
ij_java_session_li_suffix =
|
||||
ij_java_session_ri_prefix =
|
||||
ij_java_session_ri_suffix =
|
||||
ij_java_session_si_prefix =
|
||||
ij_java_session_si_suffix = Service
|
||||
ij_java_space_after_closing_angle_bracket_in_type_argument = false
|
||||
ij_java_space_after_colon = true
|
||||
ij_java_space_after_comma = true
|
||||
ij_java_space_after_comma_in_type_arguments = true
|
||||
ij_java_space_after_for_semicolon = true
|
||||
ij_java_space_after_quest = true
|
||||
ij_java_space_after_type_cast = true
|
||||
ij_java_space_before_annotation_array_initializer_left_brace = false
|
||||
ij_java_space_before_annotation_parameter_list = false
|
||||
ij_java_space_before_array_initializer_left_brace = false
|
||||
ij_java_space_before_catch_keyword = true
|
||||
ij_java_space_before_catch_left_brace = true
|
||||
ij_java_space_before_catch_parentheses = true
|
||||
ij_java_space_before_class_left_brace = true
|
||||
ij_java_space_before_colon = true
|
||||
ij_java_space_before_colon_in_foreach = true
|
||||
ij_java_space_before_comma = false
|
||||
ij_java_space_before_deconstruction_list = false
|
||||
ij_java_space_before_do_left_brace = true
|
||||
ij_java_space_before_else_keyword = true
|
||||
ij_java_space_before_else_left_brace = true
|
||||
ij_java_space_before_finally_keyword = true
|
||||
ij_java_space_before_finally_left_brace = true
|
||||
ij_java_space_before_for_left_brace = true
|
||||
ij_java_space_before_for_parentheses = true
|
||||
ij_java_space_before_for_semicolon = false
|
||||
ij_java_space_before_if_left_brace = true
|
||||
ij_java_space_before_if_parentheses = true
|
||||
ij_java_space_before_method_call_parentheses = false
|
||||
ij_java_space_before_method_left_brace = true
|
||||
ij_java_space_before_method_parentheses = false
|
||||
ij_java_space_before_opening_angle_bracket_in_type_parameter = false
|
||||
ij_java_space_before_quest = true
|
||||
ij_java_space_before_switch_left_brace = true
|
||||
ij_java_space_before_switch_parentheses = true
|
||||
ij_java_space_before_synchronized_left_brace = true
|
||||
ij_java_space_before_synchronized_parentheses = true
|
||||
ij_java_space_before_try_left_brace = true
|
||||
ij_java_space_before_try_parentheses = true
|
||||
ij_java_space_before_type_parameter_list = false
|
||||
ij_java_space_before_while_keyword = true
|
||||
ij_java_space_before_while_left_brace = true
|
||||
ij_java_space_before_while_parentheses = true
|
||||
ij_java_space_inside_one_line_enum_braces = false
|
||||
ij_java_space_within_empty_array_initializer_braces = false
|
||||
ij_java_space_within_empty_method_call_parentheses = false
|
||||
ij_java_space_within_empty_method_parentheses = false
|
||||
ij_java_spaces_around_additive_operators = true
|
||||
ij_java_spaces_around_annotation_eq = true
|
||||
ij_java_spaces_around_assignment_operators = true
|
||||
ij_java_spaces_around_bitwise_operators = true
|
||||
ij_java_spaces_around_equality_operators = true
|
||||
ij_java_spaces_around_lambda_arrow = true
|
||||
ij_java_spaces_around_logical_operators = true
|
||||
ij_java_spaces_around_method_ref_dbl_colon = false
|
||||
ij_java_spaces_around_multiplicative_operators = true
|
||||
ij_java_spaces_around_relational_operators = true
|
||||
ij_java_spaces_around_shift_operators = true
|
||||
ij_java_spaces_around_type_bounds_in_type_parameters = true
|
||||
ij_java_spaces_around_unary_operator = false
|
||||
ij_java_spaces_inside_block_braces_when_body_is_present = false
|
||||
ij_java_spaces_within_angle_brackets = false
|
||||
ij_java_spaces_within_annotation_parentheses = false
|
||||
ij_java_spaces_within_array_initializer_braces = false
|
||||
ij_java_spaces_within_braces = false
|
||||
ij_java_spaces_within_brackets = false
|
||||
ij_java_spaces_within_cast_parentheses = false
|
||||
ij_java_spaces_within_catch_parentheses = false
|
||||
ij_java_spaces_within_deconstruction_list = false
|
||||
ij_java_spaces_within_for_parentheses = false
|
||||
ij_java_spaces_within_if_parentheses = false
|
||||
ij_java_spaces_within_method_call_parentheses = false
|
||||
ij_java_spaces_within_method_parentheses = false
|
||||
ij_java_spaces_within_parentheses = false
|
||||
ij_java_spaces_within_record_header = false
|
||||
ij_java_spaces_within_switch_parentheses = false
|
||||
ij_java_spaces_within_synchronized_parentheses = false
|
||||
ij_java_spaces_within_try_parentheses = false
|
||||
ij_java_spaces_within_while_parentheses = false
|
||||
ij_java_special_else_if_treatment = true
|
||||
ij_java_static_field_name_prefix =
|
||||
ij_java_static_field_name_suffix =
|
||||
ij_java_subclass_name_prefix =
|
||||
ij_java_subclass_name_suffix = Impl
|
||||
ij_java_switch_expressions_wrap = normal
|
||||
ij_java_ternary_operation_signs_on_next_line = false
|
||||
ij_java_ternary_operation_wrap = off
|
||||
ij_java_test_name_prefix =
|
||||
ij_java_test_name_suffix = Test
|
||||
ij_java_throws_keyword_wrap = off
|
||||
ij_java_throws_list_wrap = off
|
||||
ij_java_use_external_annotations = false
|
||||
ij_java_use_fq_class_names = false
|
||||
ij_java_use_relative_indents = false
|
||||
ij_java_use_single_class_imports = true
|
||||
ij_java_variable_annotation_wrap = off
|
||||
ij_java_visibility = public
|
||||
ij_java_while_brace_force = never
|
||||
ij_java_while_on_new_line = false
|
||||
ij_java_wrap_comments = false
|
||||
ij_java_wrap_first_method_in_call_chain = false
|
||||
ij_java_wrap_long_lines = false
|
||||
ij_java_wrap_semicolon_after_call_chain = false
|
||||
|
||||
[*.less]
|
||||
indent_size = 2
|
||||
ij_less_align_closing_brace_with_properties = false
|
||||
ij_less_blank_lines_around_nested_selector = 1
|
||||
ij_less_blank_lines_between_blocks = 1
|
||||
ij_less_block_comment_add_space = false
|
||||
ij_less_brace_placement = 0
|
||||
ij_less_enforce_quotes_on_format = false
|
||||
ij_less_hex_color_long_format = false
|
||||
ij_less_hex_color_lower_case = false
|
||||
ij_less_hex_color_short_format = false
|
||||
ij_less_hex_color_upper_case = false
|
||||
ij_less_keep_blank_lines_in_code = 2
|
||||
ij_less_keep_indents_on_empty_lines = false
|
||||
ij_less_keep_single_line_blocks = false
|
||||
ij_less_line_comment_add_space = false
|
||||
ij_less_line_comment_at_first_column = false
|
||||
ij_less_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow
|
||||
ij_less_space_after_colon = true
|
||||
ij_less_space_before_opening_brace = true
|
||||
ij_less_use_double_quotes = true
|
||||
ij_less_value_alignment = 0
|
||||
|
||||
[*.sass]
|
||||
indent_size = 2
|
||||
ij_sass_align_closing_brace_with_properties = false
|
||||
ij_sass_blank_lines_around_nested_selector = 1
|
||||
ij_sass_blank_lines_between_blocks = 1
|
||||
ij_sass_brace_placement = 0
|
||||
ij_sass_enforce_quotes_on_format = false
|
||||
ij_sass_hex_color_long_format = false
|
||||
ij_sass_hex_color_lower_case = false
|
||||
ij_sass_hex_color_short_format = false
|
||||
ij_sass_hex_color_upper_case = false
|
||||
ij_sass_keep_blank_lines_in_code = 2
|
||||
ij_sass_keep_indents_on_empty_lines = false
|
||||
ij_sass_keep_single_line_blocks = false
|
||||
ij_sass_line_comment_add_space = false
|
||||
ij_sass_line_comment_at_first_column = false
|
||||
ij_sass_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow
|
||||
ij_sass_space_after_colon = true
|
||||
ij_sass_space_before_opening_brace = true
|
||||
ij_sass_use_double_quotes = true
|
||||
ij_sass_value_alignment = 0
|
||||
|
||||
[*.scss]
|
||||
indent_size = 2
|
||||
ij_scss_align_closing_brace_with_properties = false
|
||||
ij_scss_blank_lines_around_nested_selector = 1
|
||||
ij_scss_blank_lines_between_blocks = 1
|
||||
ij_scss_block_comment_add_space = false
|
||||
ij_scss_brace_placement = 0
|
||||
ij_scss_enforce_quotes_on_format = false
|
||||
ij_scss_hex_color_long_format = false
|
||||
ij_scss_hex_color_lower_case = false
|
||||
ij_scss_hex_color_short_format = false
|
||||
ij_scss_hex_color_upper_case = false
|
||||
ij_scss_keep_blank_lines_in_code = 2
|
||||
ij_scss_keep_indents_on_empty_lines = false
|
||||
ij_scss_keep_single_line_blocks = false
|
||||
ij_scss_line_comment_add_space = false
|
||||
ij_scss_line_comment_at_first_column = false
|
||||
ij_scss_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow
|
||||
ij_scss_space_after_colon = true
|
||||
ij_scss_space_before_opening_brace = true
|
||||
ij_scss_use_double_quotes = true
|
||||
ij_scss_value_alignment = 0
|
||||
|
||||
[.editorconfig]
|
||||
ij_editorconfig_align_group_field_declarations = false
|
||||
ij_editorconfig_space_after_colon = false
|
||||
ij_editorconfig_space_after_comma = true
|
||||
ij_editorconfig_space_before_colon = false
|
||||
ij_editorconfig_space_before_comma = false
|
||||
ij_editorconfig_spaces_around_assignment_operators = true
|
||||
|
||||
[{*.ant,*.fxml,*.gpx,*.jhm,*.jnlp,*.jrxml,*.pom,*.rng,*.sdef,*.tld,*.wadl,*.wsdd,*.wsdl,*.xcscheme,*.xjb,*.xml,*.xsd,*.xsl,*.xslt,*.xul}]
|
||||
ij_xml_align_attributes = true
|
||||
ij_xml_align_text = false
|
||||
ij_xml_attribute_wrap = normal
|
||||
ij_xml_block_comment_add_space = false
|
||||
ij_xml_block_comment_at_first_column = true
|
||||
ij_xml_keep_blank_lines = 2
|
||||
ij_xml_keep_indents_on_empty_lines = false
|
||||
ij_xml_keep_line_breaks = true
|
||||
ij_xml_keep_line_breaks_in_text = true
|
||||
ij_xml_keep_whitespaces = false
|
||||
ij_xml_keep_whitespaces_around_cdata = preserve
|
||||
ij_xml_keep_whitespaces_inside_cdata = false
|
||||
ij_xml_line_comment_at_first_column = true
|
||||
ij_xml_space_after_tag_name = false
|
||||
ij_xml_space_around_equals_in_attribute = false
|
||||
ij_xml_space_inside_empty_tag = false
|
||||
ij_xml_text_wrap = normal
|
||||
ij_xml_use_custom_settings = false
|
||||
|
||||
[{*.ats,*.cts,*.mts,*.ts}]
|
||||
ij_continuation_indent_size = 4
|
||||
ij_typescript_align_imports = false
|
||||
ij_typescript_align_multiline_array_initializer_expression = false
|
||||
ij_typescript_align_multiline_binary_operation = false
|
||||
ij_typescript_align_multiline_chained_methods = false
|
||||
ij_typescript_align_multiline_extends_list = false
|
||||
ij_typescript_align_multiline_for = true
|
||||
ij_typescript_align_multiline_parameters = true
|
||||
ij_typescript_align_multiline_parameters_in_calls = false
|
||||
ij_typescript_align_multiline_ternary_operation = false
|
||||
ij_typescript_align_object_properties = 0
|
||||
ij_typescript_align_union_types = false
|
||||
ij_typescript_align_var_statements = 0
|
||||
ij_typescript_array_initializer_new_line_after_left_brace = false
|
||||
ij_typescript_array_initializer_right_brace_on_new_line = false
|
||||
ij_typescript_array_initializer_wrap = off
|
||||
ij_typescript_assignment_wrap = off
|
||||
ij_typescript_binary_operation_sign_on_next_line = false
|
||||
ij_typescript_binary_operation_wrap = off
|
||||
ij_typescript_blacklist_imports = rxjs/Rx, node_modules/**, **/node_modules/**, @angular/material, @angular/material/typings/**
|
||||
ij_typescript_blank_lines_after_imports = 1
|
||||
ij_typescript_blank_lines_around_class = 1
|
||||
ij_typescript_blank_lines_around_field = 0
|
||||
ij_typescript_blank_lines_around_field_in_interface = 0
|
||||
ij_typescript_blank_lines_around_function = 1
|
||||
ij_typescript_blank_lines_around_method = 1
|
||||
ij_typescript_blank_lines_around_method_in_interface = 1
|
||||
ij_typescript_block_brace_style = end_of_line
|
||||
ij_typescript_block_comment_add_space = false
|
||||
ij_typescript_block_comment_at_first_column = true
|
||||
ij_typescript_call_parameters_new_line_after_left_paren = false
|
||||
ij_typescript_call_parameters_right_paren_on_new_line = false
|
||||
ij_typescript_call_parameters_wrap = off
|
||||
ij_typescript_catch_on_new_line = false
|
||||
ij_typescript_chained_call_dot_on_new_line = true
|
||||
ij_typescript_class_brace_style = end_of_line
|
||||
ij_typescript_comma_on_new_line = false
|
||||
ij_typescript_do_while_brace_force = never
|
||||
ij_typescript_else_on_new_line = false
|
||||
ij_typescript_enforce_trailing_comma = keep
|
||||
ij_typescript_enum_constants_wrap = on_every_item
|
||||
ij_typescript_extends_keyword_wrap = off
|
||||
ij_typescript_extends_list_wrap = off
|
||||
ij_typescript_field_prefix = _
|
||||
ij_typescript_file_name_style = relaxed
|
||||
ij_typescript_finally_on_new_line = false
|
||||
ij_typescript_for_brace_force = never
|
||||
ij_typescript_for_statement_new_line_after_left_paren = false
|
||||
ij_typescript_for_statement_right_paren_on_new_line = false
|
||||
ij_typescript_for_statement_wrap = off
|
||||
ij_typescript_force_quote_style = false
|
||||
ij_typescript_force_semicolon_style = false
|
||||
ij_typescript_function_expression_brace_style = end_of_line
|
||||
ij_typescript_if_brace_force = never
|
||||
ij_typescript_import_merge_members = global
|
||||
ij_typescript_import_prefer_absolute_path = global
|
||||
ij_typescript_import_sort_members = true
|
||||
ij_typescript_import_sort_module_name = false
|
||||
ij_typescript_import_use_node_resolution = true
|
||||
ij_typescript_imports_wrap = on_every_item
|
||||
ij_typescript_indent_case_from_switch = true
|
||||
ij_typescript_indent_chained_calls = true
|
||||
ij_typescript_indent_package_children = 0
|
||||
ij_typescript_jsdoc_include_types = false
|
||||
ij_typescript_jsx_attribute_value = braces
|
||||
ij_typescript_keep_blank_lines_in_code = 2
|
||||
ij_typescript_keep_first_column_comment = true
|
||||
ij_typescript_keep_indents_on_empty_lines = false
|
||||
ij_typescript_keep_line_breaks = true
|
||||
ij_typescript_keep_simple_blocks_in_one_line = false
|
||||
ij_typescript_keep_simple_methods_in_one_line = false
|
||||
ij_typescript_line_comment_add_space = true
|
||||
ij_typescript_line_comment_at_first_column = false
|
||||
ij_typescript_method_brace_style = end_of_line
|
||||
ij_typescript_method_call_chain_wrap = off
|
||||
ij_typescript_method_parameters_new_line_after_left_paren = false
|
||||
ij_typescript_method_parameters_right_paren_on_new_line = false
|
||||
ij_typescript_method_parameters_wrap = off
|
||||
ij_typescript_object_literal_wrap = on_every_item
|
||||
ij_typescript_object_types_wrap = on_every_item
|
||||
ij_typescript_parentheses_expression_new_line_after_left_paren = false
|
||||
ij_typescript_parentheses_expression_right_paren_on_new_line = false
|
||||
ij_typescript_place_assignment_sign_on_next_line = false
|
||||
ij_typescript_prefer_as_type_cast = false
|
||||
ij_typescript_prefer_explicit_types_function_expression_returns = false
|
||||
ij_typescript_prefer_explicit_types_function_returns = false
|
||||
ij_typescript_prefer_explicit_types_vars_fields = false
|
||||
ij_typescript_prefer_parameters_wrap = false
|
||||
ij_typescript_property_prefix =
|
||||
ij_typescript_reformat_c_style_comments = false
|
||||
ij_typescript_space_after_colon = true
|
||||
ij_typescript_space_after_comma = true
|
||||
ij_typescript_space_after_dots_in_rest_parameter = false
|
||||
ij_typescript_space_after_generator_mult = true
|
||||
ij_typescript_space_after_property_colon = true
|
||||
ij_typescript_space_after_quest = true
|
||||
ij_typescript_space_after_type_colon = true
|
||||
ij_typescript_space_after_unary_not = false
|
||||
ij_typescript_space_before_async_arrow_lparen = true
|
||||
ij_typescript_space_before_catch_keyword = true
|
||||
ij_typescript_space_before_catch_left_brace = true
|
||||
ij_typescript_space_before_catch_parentheses = true
|
||||
ij_typescript_space_before_class_lbrace = true
|
||||
ij_typescript_space_before_class_left_brace = true
|
||||
ij_typescript_space_before_colon = true
|
||||
ij_typescript_space_before_comma = false
|
||||
ij_typescript_space_before_do_left_brace = true
|
||||
ij_typescript_space_before_else_keyword = true
|
||||
ij_typescript_space_before_else_left_brace = true
|
||||
ij_typescript_space_before_finally_keyword = true
|
||||
ij_typescript_space_before_finally_left_brace = true
|
||||
ij_typescript_space_before_for_left_brace = true
|
||||
ij_typescript_space_before_for_parentheses = true
|
||||
ij_typescript_space_before_for_semicolon = false
|
||||
ij_typescript_space_before_function_left_parenth = true
|
||||
ij_typescript_space_before_generator_mult = false
|
||||
ij_typescript_space_before_if_left_brace = true
|
||||
ij_typescript_space_before_if_parentheses = true
|
||||
ij_typescript_space_before_method_call_parentheses = false
|
||||
ij_typescript_space_before_method_left_brace = true
|
||||
ij_typescript_space_before_method_parentheses = false
|
||||
ij_typescript_space_before_property_colon = false
|
||||
ij_typescript_space_before_quest = true
|
||||
ij_typescript_space_before_switch_left_brace = true
|
||||
ij_typescript_space_before_switch_parentheses = true
|
||||
ij_typescript_space_before_try_left_brace = true
|
||||
ij_typescript_space_before_type_colon = false
|
||||
ij_typescript_space_before_unary_not = false
|
||||
ij_typescript_space_before_while_keyword = true
|
||||
ij_typescript_space_before_while_left_brace = true
|
||||
ij_typescript_space_before_while_parentheses = true
|
||||
ij_typescript_spaces_around_additive_operators = true
|
||||
ij_typescript_spaces_around_arrow_function_operator = true
|
||||
ij_typescript_spaces_around_assignment_operators = true
|
||||
ij_typescript_spaces_around_bitwise_operators = true
|
||||
ij_typescript_spaces_around_equality_operators = true
|
||||
ij_typescript_spaces_around_logical_operators = true
|
||||
ij_typescript_spaces_around_multiplicative_operators = true
|
||||
ij_typescript_spaces_around_relational_operators = true
|
||||
ij_typescript_spaces_around_shift_operators = true
|
||||
ij_typescript_spaces_around_unary_operator = false
|
||||
ij_typescript_spaces_within_array_initializer_brackets = false
|
||||
ij_typescript_spaces_within_brackets = false
|
||||
ij_typescript_spaces_within_catch_parentheses = false
|
||||
ij_typescript_spaces_within_for_parentheses = false
|
||||
ij_typescript_spaces_within_if_parentheses = false
|
||||
ij_typescript_spaces_within_imports = false
|
||||
ij_typescript_spaces_within_interpolation_expressions = false
|
||||
ij_typescript_spaces_within_method_call_parentheses = false
|
||||
ij_typescript_spaces_within_method_parentheses = false
|
||||
ij_typescript_spaces_within_object_literal_braces = false
|
||||
ij_typescript_spaces_within_object_type_braces = true
|
||||
ij_typescript_spaces_within_parentheses = false
|
||||
ij_typescript_spaces_within_switch_parentheses = false
|
||||
ij_typescript_spaces_within_type_assertion = false
|
||||
ij_typescript_spaces_within_union_types = true
|
||||
ij_typescript_spaces_within_while_parentheses = false
|
||||
ij_typescript_special_else_if_treatment = true
|
||||
ij_typescript_ternary_operation_signs_on_next_line = false
|
||||
ij_typescript_ternary_operation_wrap = off
|
||||
ij_typescript_union_types_wrap = on_every_item
|
||||
ij_typescript_use_chained_calls_group_indents = false
|
||||
ij_typescript_use_double_quotes = true
|
||||
ij_typescript_use_explicit_js_extension = auto
|
||||
ij_typescript_use_import_type = auto
|
||||
ij_typescript_use_path_mapping = always
|
||||
ij_typescript_use_public_modifier = false
|
||||
ij_typescript_use_semicolon_after_statement = true
|
||||
ij_typescript_var_declaration_wrap = normal
|
||||
ij_typescript_while_brace_force = never
|
||||
ij_typescript_while_on_new_line = false
|
||||
ij_typescript_wrap_comments = false
|
||||
|
||||
[{*.bash,*.sh,*.zsh}]
|
||||
indent_size = 2
|
||||
tab_width = 2
|
||||
ij_shell_binary_ops_start_line = false
|
||||
ij_shell_keep_column_alignment_padding = false
|
||||
ij_shell_minify_program = false
|
||||
ij_shell_redirect_followed_by_space = false
|
||||
ij_shell_switch_cases_indented = false
|
||||
ij_shell_use_unix_line_separator = true
|
||||
|
||||
[{*.cjs,*.js}]
|
||||
ij_continuation_indent_size = 4
|
||||
ij_javascript_align_imports = false
|
||||
ij_javascript_align_multiline_array_initializer_expression = false
|
||||
ij_javascript_align_multiline_binary_operation = false
|
||||
ij_javascript_align_multiline_chained_methods = false
|
||||
ij_javascript_align_multiline_extends_list = false
|
||||
ij_javascript_align_multiline_for = true
|
||||
ij_javascript_align_multiline_parameters = true
|
||||
ij_javascript_align_multiline_parameters_in_calls = false
|
||||
ij_javascript_align_multiline_ternary_operation = false
|
||||
ij_javascript_align_object_properties = 0
|
||||
ij_javascript_align_union_types = false
|
||||
ij_javascript_align_var_statements = 0
|
||||
ij_javascript_array_initializer_new_line_after_left_brace = false
|
||||
ij_javascript_array_initializer_right_brace_on_new_line = false
|
||||
ij_javascript_array_initializer_wrap = off
|
||||
ij_javascript_assignment_wrap = off
|
||||
ij_javascript_binary_operation_sign_on_next_line = false
|
||||
ij_javascript_binary_operation_wrap = off
|
||||
ij_javascript_blacklist_imports = rxjs/Rx, node_modules/**, **/node_modules/**, @angular/material, @angular/material/typings/**
|
||||
ij_javascript_blank_lines_after_imports = 1
|
||||
ij_javascript_blank_lines_around_class = 1
|
||||
ij_javascript_blank_lines_around_field = 0
|
||||
ij_javascript_blank_lines_around_function = 1
|
||||
ij_javascript_blank_lines_around_method = 1
|
||||
ij_javascript_block_brace_style = end_of_line
|
||||
ij_javascript_block_comment_add_space = false
|
||||
ij_javascript_block_comment_at_first_column = true
|
||||
ij_javascript_call_parameters_new_line_after_left_paren = false
|
||||
ij_javascript_call_parameters_right_paren_on_new_line = false
|
||||
ij_javascript_call_parameters_wrap = off
|
||||
ij_javascript_catch_on_new_line = false
|
||||
ij_javascript_chained_call_dot_on_new_line = true
|
||||
ij_javascript_class_brace_style = end_of_line
|
||||
ij_javascript_comma_on_new_line = false
|
||||
ij_javascript_do_while_brace_force = never
|
||||
ij_javascript_else_on_new_line = false
|
||||
ij_javascript_enforce_trailing_comma = keep
|
||||
ij_javascript_extends_keyword_wrap = off
|
||||
ij_javascript_extends_list_wrap = off
|
||||
ij_javascript_field_prefix = _
|
||||
ij_javascript_file_name_style = relaxed
|
||||
ij_javascript_finally_on_new_line = false
|
||||
ij_javascript_for_brace_force = never
|
||||
ij_javascript_for_statement_new_line_after_left_paren = false
|
||||
ij_javascript_for_statement_right_paren_on_new_line = false
|
||||
ij_javascript_for_statement_wrap = off
|
||||
ij_javascript_force_quote_style = false
|
||||
ij_javascript_force_semicolon_style = false
|
||||
ij_javascript_function_expression_brace_style = end_of_line
|
||||
ij_javascript_if_brace_force = never
|
||||
ij_javascript_import_merge_members = global
|
||||
ij_javascript_import_prefer_absolute_path = global
|
||||
ij_javascript_import_sort_members = true
|
||||
ij_javascript_import_sort_module_name = false
|
||||
ij_javascript_import_use_node_resolution = true
|
||||
ij_javascript_imports_wrap = on_every_item
|
||||
ij_javascript_indent_case_from_switch = true
|
||||
ij_javascript_indent_chained_calls = true
|
||||
ij_javascript_indent_package_children = 0
|
||||
ij_javascript_jsx_attribute_value = braces
|
||||
ij_javascript_keep_blank_lines_in_code = 2
|
||||
ij_javascript_keep_first_column_comment = true
|
||||
ij_javascript_keep_indents_on_empty_lines = false
|
||||
ij_javascript_keep_line_breaks = true
|
||||
ij_javascript_keep_simple_blocks_in_one_line = false
|
||||
ij_javascript_keep_simple_methods_in_one_line = false
|
||||
ij_javascript_line_comment_add_space = true
|
||||
ij_javascript_line_comment_at_first_column = false
|
||||
ij_javascript_method_brace_style = end_of_line
|
||||
ij_javascript_method_call_chain_wrap = off
|
||||
ij_javascript_method_parameters_new_line_after_left_paren = false
|
||||
ij_javascript_method_parameters_right_paren_on_new_line = false
|
||||
ij_javascript_method_parameters_wrap = off
|
||||
ij_javascript_object_literal_wrap = on_every_item
|
||||
ij_javascript_object_types_wrap = on_every_item
|
||||
ij_javascript_parentheses_expression_new_line_after_left_paren = false
|
||||
ij_javascript_parentheses_expression_right_paren_on_new_line = false
|
||||
ij_javascript_place_assignment_sign_on_next_line = false
|
||||
ij_javascript_prefer_as_type_cast = false
|
||||
ij_javascript_prefer_explicit_types_function_expression_returns = false
|
||||
ij_javascript_prefer_explicit_types_function_returns = false
|
||||
ij_javascript_prefer_explicit_types_vars_fields = false
|
||||
ij_javascript_prefer_parameters_wrap = false
|
||||
ij_javascript_property_prefix =
|
||||
ij_javascript_reformat_c_style_comments = false
|
||||
ij_javascript_space_after_colon = true
|
||||
ij_javascript_space_after_comma = true
|
||||
ij_javascript_space_after_dots_in_rest_parameter = false
|
||||
ij_javascript_space_after_generator_mult = true
|
||||
ij_javascript_space_after_property_colon = true
|
||||
ij_javascript_space_after_quest = true
|
||||
ij_javascript_space_after_type_colon = true
|
||||
ij_javascript_space_after_unary_not = false
|
||||
ij_javascript_space_before_async_arrow_lparen = true
|
||||
ij_javascript_space_before_catch_keyword = true
|
||||
ij_javascript_space_before_catch_left_brace = true
|
||||
ij_javascript_space_before_catch_parentheses = true
|
||||
ij_javascript_space_before_class_lbrace = true
|
||||
ij_javascript_space_before_class_left_brace = true
|
||||
ij_javascript_space_before_colon = true
|
||||
ij_javascript_space_before_comma = false
|
||||
ij_javascript_space_before_do_left_brace = true
|
||||
ij_javascript_space_before_else_keyword = true
|
||||
ij_javascript_space_before_else_left_brace = true
|
||||
ij_javascript_space_before_finally_keyword = true
|
||||
ij_javascript_space_before_finally_left_brace = true
|
||||
ij_javascript_space_before_for_left_brace = true
|
||||
ij_javascript_space_before_for_parentheses = true
|
||||
ij_javascript_space_before_for_semicolon = false
|
||||
ij_javascript_space_before_function_left_parenth = true
|
||||
ij_javascript_space_before_generator_mult = false
|
||||
ij_javascript_space_before_if_left_brace = true
|
||||
ij_javascript_space_before_if_parentheses = true
|
||||
ij_javascript_space_before_method_call_parentheses = false
|
||||
ij_javascript_space_before_method_left_brace = true
|
||||
ij_javascript_space_before_method_parentheses = false
|
||||
ij_javascript_space_before_property_colon = false
|
||||
ij_javascript_space_before_quest = true
|
||||
ij_javascript_space_before_switch_left_brace = true
|
||||
ij_javascript_space_before_switch_parentheses = true
|
||||
ij_javascript_space_before_try_left_brace = true
|
||||
ij_javascript_space_before_type_colon = false
|
||||
ij_javascript_space_before_unary_not = false
|
||||
ij_javascript_space_before_while_keyword = true
|
||||
ij_javascript_space_before_while_left_brace = true
|
||||
ij_javascript_space_before_while_parentheses = true
|
||||
ij_javascript_spaces_around_additive_operators = true
|
||||
ij_javascript_spaces_around_arrow_function_operator = true
|
||||
ij_javascript_spaces_around_assignment_operators = true
|
||||
ij_javascript_spaces_around_bitwise_operators = true
|
||||
ij_javascript_spaces_around_equality_operators = true
|
||||
ij_javascript_spaces_around_logical_operators = true
|
||||
ij_javascript_spaces_around_multiplicative_operators = true
|
||||
ij_javascript_spaces_around_relational_operators = true
|
||||
ij_javascript_spaces_around_shift_operators = true
|
||||
ij_javascript_spaces_around_unary_operator = false
|
||||
ij_javascript_spaces_within_array_initializer_brackets = false
|
||||
ij_javascript_spaces_within_brackets = false
|
||||
ij_javascript_spaces_within_catch_parentheses = false
|
||||
ij_javascript_spaces_within_for_parentheses = false
|
||||
ij_javascript_spaces_within_if_parentheses = false
|
||||
ij_javascript_spaces_within_imports = false
|
||||
ij_javascript_spaces_within_interpolation_expressions = false
|
||||
ij_javascript_spaces_within_method_call_parentheses = false
|
||||
ij_javascript_spaces_within_method_parentheses = false
|
||||
ij_javascript_spaces_within_object_literal_braces = false
|
||||
ij_javascript_spaces_within_object_type_braces = true
|
||||
ij_javascript_spaces_within_parentheses = false
|
||||
ij_javascript_spaces_within_switch_parentheses = false
|
||||
ij_javascript_spaces_within_type_assertion = false
|
||||
ij_javascript_spaces_within_union_types = true
|
||||
ij_javascript_spaces_within_while_parentheses = false
|
||||
ij_javascript_special_else_if_treatment = true
|
||||
ij_javascript_ternary_operation_signs_on_next_line = false
|
||||
ij_javascript_ternary_operation_wrap = off
|
||||
ij_javascript_union_types_wrap = on_every_item
|
||||
ij_javascript_use_chained_calls_group_indents = false
|
||||
ij_javascript_use_double_quotes = true
|
||||
ij_javascript_use_explicit_js_extension = auto
|
||||
ij_javascript_use_import_type = auto
|
||||
ij_javascript_use_path_mapping = always
|
||||
ij_javascript_use_public_modifier = false
|
||||
ij_javascript_use_semicolon_after_statement = true
|
||||
ij_javascript_var_declaration_wrap = normal
|
||||
ij_javascript_while_brace_force = never
|
||||
ij_javascript_while_on_new_line = false
|
||||
ij_javascript_wrap_comments = false
|
||||
|
||||
|
||||
[{*.har,*.jsb2,*.jsb3,*.json,*.jsonc,*.postman_collection,*.postman_collection.json,*.postman_environment,*.postman_environment.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,.ws-context,jest.config}]
|
||||
indent_size = 2
|
||||
ij_json_array_wrapping = split_into_lines
|
||||
ij_json_keep_blank_lines_in_code = 0
|
||||
ij_json_keep_indents_on_empty_lines = false
|
||||
ij_json_keep_line_breaks = true
|
||||
ij_json_keep_trailing_comma = false
|
||||
ij_json_object_wrapping = split_into_lines
|
||||
ij_json_property_alignment = do_not_align
|
||||
ij_json_space_after_colon = true
|
||||
ij_json_space_after_comma = true
|
||||
ij_json_space_before_colon = false
|
||||
ij_json_space_before_comma = false
|
||||
ij_json_spaces_within_braces = false
|
||||
ij_json_spaces_within_brackets = false
|
||||
ij_json_wrap_long_lines = false
|
||||
|
||||
[{*.htm,*.html,*.sht,*.shtm,*.shtml}]
|
||||
ij_html_add_new_line_before_tags = body, div, p, form, h1, h2, h3
|
||||
ij_html_align_attributes = true
|
||||
ij_html_align_text = false
|
||||
ij_html_attribute_wrap = normal
|
||||
ij_html_block_comment_add_space = false
|
||||
ij_html_block_comment_at_first_column = true
|
||||
ij_html_do_not_align_children_of_min_lines = 0
|
||||
ij_html_do_not_break_if_inline_tags = title, h1, h2, h3, h4, h5, h6, p
|
||||
ij_html_do_not_indent_children_of_tags = html, body, thead, tbody, tfoot
|
||||
ij_html_enforce_quotes = false
|
||||
ij_html_inline_tags = a, abbr, acronym, b, basefont, bdo, big, br, cite, cite, code, dfn, em, font, i, img, input, kbd, label, q, s, samp, select, small, span, strike, strong, sub, sup, textarea, tt, u, var
|
||||
ij_html_keep_blank_lines = 2
|
||||
ij_html_keep_indents_on_empty_lines = false
|
||||
ij_html_keep_line_breaks = true
|
||||
ij_html_keep_line_breaks_in_text = true
|
||||
ij_html_keep_whitespaces = false
|
||||
ij_html_keep_whitespaces_inside = span, pre, textarea
|
||||
ij_html_line_comment_at_first_column = true
|
||||
ij_html_new_line_after_last_attribute = never
|
||||
ij_html_new_line_before_first_attribute = never
|
||||
ij_html_quote_style = double
|
||||
ij_html_remove_new_line_before_tags = br
|
||||
ij_html_space_after_tag_name = false
|
||||
ij_html_space_around_equality_in_attribute = false
|
||||
ij_html_space_inside_empty_tag = false
|
||||
ij_html_text_wrap = normal
|
||||
|
||||
[{*.http,*.rest}]
|
||||
indent_size = 0
|
||||
ij_continuation_indent_size = 4
|
||||
ij_http-request_call_parameters_wrap = normal
|
||||
ij_http-request_method_parameters_wrap = split_into_lines
|
||||
ij_http-request_space_before_comma = true
|
||||
ij_http-request_spaces_around_assignment_operators = true
|
||||
|
||||
[{*.kt,*.kts}]
|
||||
ij_kotlin_align_in_columns_case_branch = false
|
||||
ij_kotlin_align_multiline_binary_operation = false
|
||||
ij_kotlin_align_multiline_extends_list = false
|
||||
ij_kotlin_align_multiline_method_parentheses = false
|
||||
ij_kotlin_align_multiline_parameters = true
|
||||
ij_kotlin_align_multiline_parameters_in_calls = false
|
||||
ij_kotlin_allow_trailing_comma = false
|
||||
ij_kotlin_allow_trailing_comma_on_call_site = false
|
||||
ij_kotlin_assignment_wrap = normal
|
||||
ij_kotlin_blank_lines_after_class_header = 0
|
||||
ij_kotlin_blank_lines_around_block_when_branches = 0
|
||||
ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1
|
||||
ij_kotlin_block_comment_add_space = false
|
||||
ij_kotlin_block_comment_at_first_column = true
|
||||
ij_kotlin_call_parameters_new_line_after_left_paren = true
|
||||
ij_kotlin_call_parameters_right_paren_on_new_line = true
|
||||
ij_kotlin_call_parameters_wrap = on_every_item
|
||||
ij_kotlin_catch_on_new_line = false
|
||||
ij_kotlin_class_annotation_wrap = split_into_lines
|
||||
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
|
||||
ij_kotlin_continuation_indent_for_chained_calls = false
|
||||
ij_kotlin_continuation_indent_for_expression_bodies = false
|
||||
ij_kotlin_continuation_indent_in_argument_lists = false
|
||||
ij_kotlin_continuation_indent_in_elvis = false
|
||||
ij_kotlin_continuation_indent_in_if_conditions = false
|
||||
ij_kotlin_continuation_indent_in_parameter_lists = false
|
||||
ij_kotlin_continuation_indent_in_supertype_lists = false
|
||||
ij_kotlin_else_on_new_line = false
|
||||
ij_kotlin_enum_constants_wrap = off
|
||||
ij_kotlin_extends_list_wrap = normal
|
||||
ij_kotlin_field_annotation_wrap = split_into_lines
|
||||
ij_kotlin_finally_on_new_line = false
|
||||
ij_kotlin_if_rparen_on_new_line = true
|
||||
ij_kotlin_import_nested_classes = false
|
||||
ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^
|
||||
ij_kotlin_indent_before_arrow_on_new_line = true
|
||||
ij_kotlin_insert_whitespaces_in_simple_one_line_method = true
|
||||
ij_kotlin_keep_blank_lines_before_right_brace = 2
|
||||
ij_kotlin_keep_blank_lines_in_code = 2
|
||||
ij_kotlin_keep_blank_lines_in_declarations = 2
|
||||
ij_kotlin_keep_first_column_comment = true
|
||||
ij_kotlin_keep_indents_on_empty_lines = false
|
||||
ij_kotlin_keep_line_breaks = true
|
||||
ij_kotlin_lbrace_on_next_line = false
|
||||
ij_kotlin_line_break_after_multiline_when_entry = true
|
||||
ij_kotlin_line_comment_add_space = false
|
||||
ij_kotlin_line_comment_add_space_on_reformat = false
|
||||
ij_kotlin_line_comment_at_first_column = true
|
||||
ij_kotlin_method_annotation_wrap = split_into_lines
|
||||
ij_kotlin_method_call_chain_wrap = normal
|
||||
ij_kotlin_method_parameters_new_line_after_left_paren = true
|
||||
ij_kotlin_method_parameters_right_paren_on_new_line = true
|
||||
ij_kotlin_method_parameters_wrap = on_every_item
|
||||
ij_kotlin_name_count_to_use_star_import = 999
|
||||
ij_kotlin_name_count_to_use_star_import_for_members = 999
|
||||
ij_kotlin_packages_to_use_import_on_demand = unset
|
||||
ij_kotlin_parameter_annotation_wrap = off
|
||||
ij_kotlin_space_after_comma = true
|
||||
ij_kotlin_space_after_extend_colon = true
|
||||
ij_kotlin_space_after_type_colon = true
|
||||
ij_kotlin_space_before_catch_parentheses = true
|
||||
ij_kotlin_space_before_comma = false
|
||||
ij_kotlin_space_before_extend_colon = true
|
||||
ij_kotlin_space_before_for_parentheses = true
|
||||
ij_kotlin_space_before_if_parentheses = true
|
||||
ij_kotlin_space_before_lambda_arrow = true
|
||||
ij_kotlin_space_before_type_colon = false
|
||||
ij_kotlin_space_before_when_parentheses = true
|
||||
ij_kotlin_space_before_while_parentheses = true
|
||||
ij_kotlin_spaces_around_additive_operators = true
|
||||
ij_kotlin_spaces_around_assignment_operators = true
|
||||
ij_kotlin_spaces_around_equality_operators = true
|
||||
ij_kotlin_spaces_around_function_type_arrow = true
|
||||
ij_kotlin_spaces_around_logical_operators = true
|
||||
ij_kotlin_spaces_around_multiplicative_operators = true
|
||||
ij_kotlin_spaces_around_range = false
|
||||
ij_kotlin_spaces_around_relational_operators = true
|
||||
ij_kotlin_spaces_around_unary_operator = false
|
||||
ij_kotlin_spaces_around_when_arrow = true
|
||||
ij_kotlin_use_custom_formatting_for_modifiers = true
|
||||
ij_kotlin_variable_annotation_wrap = off
|
||||
ij_kotlin_while_on_new_line = false
|
||||
ij_kotlin_wrap_elvis_expressions = 1
|
||||
ij_kotlin_wrap_expression_body_functions = 1
|
||||
ij_kotlin_wrap_first_method_in_call_chain = false
|
||||
ktlint_standard = enabled
|
||||
|
||||
[{*Test.kt,*IT.kt}]
|
||||
ktlint_standard_ignore_back_ticked_identifier = true
|
||||
ktlint_standard_function-naming = disabled
|
||||
|
||||
[{*.markdown,*.md}]
|
||||
ij_markdown_force_one_space_after_blockquote_symbol = true
|
||||
ij_markdown_force_one_space_after_header_symbol = true
|
||||
ij_markdown_force_one_space_after_list_bullet = true
|
||||
ij_markdown_force_one_space_between_words = true
|
||||
ij_markdown_format_tables = true
|
||||
ij_markdown_insert_quote_arrows_on_wrap = true
|
||||
ij_markdown_keep_indents_on_empty_lines = false
|
||||
ij_markdown_keep_line_breaks_inside_text_blocks = true
|
||||
ij_markdown_max_lines_around_block_elements = 1
|
||||
ij_markdown_max_lines_around_header = 1
|
||||
ij_markdown_max_lines_between_paragraphs = 1
|
||||
ij_markdown_min_lines_around_block_elements = 1
|
||||
ij_markdown_min_lines_around_header = 1
|
||||
ij_markdown_min_lines_between_paragraphs = 1
|
||||
ij_markdown_wrap_text_if_long = true
|
||||
ij_markdown_wrap_text_inside_blockquotes = true
|
||||
|
||||
[{*.properties,spring.handlers,spring.schemas}]
|
||||
ij_properties_align_group_field_declarations = false
|
||||
ij_properties_keep_blank_lines = false
|
||||
ij_properties_key_value_delimiter = equals
|
||||
ij_properties_spaces_around_key_value_delimiter = false
|
||||
|
||||
[{*.toml,Cargo.lock,Cargo.toml.orig,Gopkg.lock,Pipfile,poetry.lock}]
|
||||
ij_toml_keep_indents_on_empty_lines = false
|
||||
|
||||
[{*.yaml,*.yml}]
|
||||
indent_size = 2
|
||||
ij_yaml_align_values_properties = do_not_align
|
||||
ij_yaml_autoinsert_sequence_marker = true
|
||||
ij_yaml_block_mapping_on_new_line = false
|
||||
ij_yaml_indent_sequence_value = true
|
||||
ij_yaml_keep_indents_on_empty_lines = false
|
||||
ij_yaml_keep_line_breaks = true
|
||||
ij_yaml_line_comment_add_space = false
|
||||
ij_yaml_line_comment_add_space_on_reformat = false
|
||||
ij_yaml_line_comment_at_first_column = true
|
||||
ij_yaml_sequence_on_new_line = false
|
||||
ij_yaml_space_before_colon = false
|
||||
ij_yaml_spaces_within_braces = true
|
||||
ij_yaml_spaces_within_brackets = true
|
||||
@@ -0,0 +1 @@
|
||||
*.onnx filter=lfs diff=lfs merge=lfs -text
|
||||
@@ -0,0 +1,15 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: langchain4j
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
polar: # Replace with a single Polar username
|
||||
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
||||
thanks_dev: # Replace with a single thanks.dev username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG] "
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- Please provide as many details as possible, this will help us to deliver a fix as soon as possible. Thank you! -->
|
||||
|
||||
**Describe the bug**
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**Log and Stack trace**
|
||||
<!-- Please provide a log and a stack trace (with exception), if applicable. -->
|
||||
|
||||
**To Reproduce**
|
||||
<!-- Please provide a relevant code snippets to reproduce this bug. -->
|
||||
|
||||
**Expected behavior**
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
**Please complete the following information:**
|
||||
- LangChain4j version:
|
||||
- LLM(s) used:
|
||||
- Java version:
|
||||
- Spring Boot version (if applicable):
|
||||
|
||||
**Additional context**
|
||||
<!-- Add any other context about the problem here. -->
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FEATURE] "
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
**Describe the solution you'd like**
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
**Additional context**
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
# The format of this file is documented here:
|
||||
# https://github.com/langchain4j/langchain4j-github-bot
|
||||
features: [TRIAGE_ISSUES_AND_PULL_REQUESTS]
|
||||
triage:
|
||||
rules:
|
||||
- id: azure
|
||||
labels: [Azure]
|
||||
title: "azure"
|
||||
notify: [jdubois, agoncal]
|
||||
notifyInPullRequest: true
|
||||
files:
|
||||
- '*-azure-*/**'
|
||||
- id: gemini
|
||||
labels: [Gemini]
|
||||
title: "gemini|vertex|google"
|
||||
notify: [glaforge]
|
||||
notifyInPullRequest: true
|
||||
files:
|
||||
- '*-vertex-*/**'
|
||||
- '*-google-*/**'
|
||||
- id: elasticsearch
|
||||
labels: [Elasticsearch]
|
||||
title: "elasticsearch"
|
||||
notify: [dadoonet]
|
||||
notifyInPullRequest: true
|
||||
files:
|
||||
- 'langchain4j-elasticsearch/**'
|
||||
- id: mistral
|
||||
labels: [Mistral]
|
||||
title: "mistral"
|
||||
notify: [czelabueno]
|
||||
notifyInPullRequest: true
|
||||
files:
|
||||
- 'langchain4j-mistral-ai/**'
|
||||
- id: mcp
|
||||
labels: [MCP]
|
||||
title: "mcp"
|
||||
notify: [jmartisk]
|
||||
notifyInPullRequest: true
|
||||
files:
|
||||
- 'langchain4j-mcp/**'
|
||||
@@ -0,0 +1,45 @@
|
||||
<!--
|
||||
Thank you so much for your contribution!
|
||||
|
||||
Please fill in all the sections below.
|
||||
Please open the PR as ready for review (not as a draft), with tests and documentation already included.
|
||||
Please note that PRs with breaking changes, or without tests and documentation, will be rejected.
|
||||
|
||||
Please note that PRs will be reviewed based on the priority of the issues they address.
|
||||
We ask for your patience. We are doing our best to review your PR as quickly as possible.
|
||||
Please refrain from pinging and asking when it will be reviewed. Thank you for understanding!
|
||||
-->
|
||||
|
||||
## Issue
|
||||
<!-- Please specify the ID of the issue this PR is addressing. For example: "Closes #1234" or "Fixes #1234" -->
|
||||
Closes #
|
||||
|
||||
## Change
|
||||
<!-- Please describe the changes you made. -->
|
||||
|
||||
|
||||
## General checklist
|
||||
<!-- Please double-check the following points and mark them like this: [X] -->
|
||||
- [ ] There are no breaking changes (API, behaviour)
|
||||
- [ ] I have added unit and/or integration tests for my change
|
||||
- [ ] The tests cover both positive and negative cases
|
||||
- [ ] I have manually run all the unit and integration tests in the module I have added/changed, and they are all green
|
||||
- [ ] I have manually run all the unit and integration tests in the [core](https://github.com/langchain4j/langchain4j/tree/main/langchain4j-core) and [main](https://github.com/langchain4j/langchain4j/tree/main/langchain4j) modules, and they are all green
|
||||
- [ ] I have added/updated the [documentation](https://github.com/langchain4j/langchain4j/tree/main/docs/docs)
|
||||
- [ ] I have added an example in the [examples repo](https://github.com/langchain4j/langchain4j-examples) (only for "big" features)
|
||||
- [ ] I have added/updated [Spring Boot starter(s)](https://github.com/langchain4j/langchain4j-spring) (if applicable)
|
||||
|
||||
|
||||
## Checklist for adding new maven module
|
||||
<!-- Please double-check the following points and mark them like this: [X] -->
|
||||
- [ ] I have added my new module in the root `pom.xml` and `langchain4j-bom/pom.xml`
|
||||
|
||||
|
||||
## Checklist for adding new embedding store integration
|
||||
<!-- Please double-check the following points and mark them like this: [X] -->
|
||||
- [ ] I have added a `{NameOfIntegration}EmbeddingStoreIT` that extends from either `EmbeddingStoreIT` or `EmbeddingStoreWithFilteringIT`
|
||||
- [ ] I have added a `{NameOfIntegration}EmbeddingStoreRemovalIT` that extends from `EmbeddingStoreWithRemovalIT`
|
||||
|
||||
## Checklist for changing existing embedding store integration
|
||||
<!-- Please double-check the following points and mark them like this: [X] -->
|
||||
- [ ] I have manually verified that the `{NameOfIntegration}EmbeddingStore` works correctly with the data persisted using the latest released version of LangChain4j
|
||||
@@ -0,0 +1,26 @@
|
||||
name: "Documentation: update chatbot"
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [ trigger-docs-update-chatbot ]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
- name: Build and run
|
||||
run: |
|
||||
mvn -pl internal/langchain4j-docu-chatbot-updater clean compile exec:java
|
||||
env:
|
||||
GOOGLE_AI_GEMINI_API_KEY: ${{ secrets.GOOGLE_AI_GEMINI_API_KEY }}
|
||||
MILVUS_API_KEY: ${{ secrets.MILVUS_API_KEY }}
|
||||
MILVUS_URI: ${{ secrets.MILVUS_URI }}
|
||||
@@ -0,0 +1,91 @@
|
||||
name: "Documentation: update versions"
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [ trigger-docs-update-version ]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
stableVersion:
|
||||
description: "Stable release version (e.g., 1.8.0)"
|
||||
required: true
|
||||
betaVersion:
|
||||
description: "Beta release version (e.g., 1.8.0-beta15)"
|
||||
required: true
|
||||
|
||||
env:
|
||||
STABLE_VERSION: ${{ github.event.inputs.stableVersion || github.event.client_payload.stableVersion }}
|
||||
BETA_VERSION: ${{ github.event.inputs.betaVersion || github.event.client_payload.betaVersion }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update-docs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
token: ${{ secrets.GH_RELEASE_AUTOMATION }}
|
||||
|
||||
- name: Extract current stable and beta versions from docs metadata
|
||||
id: extract-current-versions
|
||||
run: |
|
||||
STABLE_CURRENT=$(grep -E "^stableVersion:" docs/docs/get-started.md | sed 's/stableVersion: *//')
|
||||
BETA_CURRENT=$(grep -E "^betaVersion:" docs/docs/get-started.md | sed 's/betaVersion: *//')
|
||||
echo "stableCurrent=$STABLE_CURRENT" >> $GITHUB_OUTPUT
|
||||
echo "betaCurrent=$BETA_CURRENT" >> $GITHUB_OUTPUT
|
||||
echo "Found stable version in docs: $STABLE_CURRENT"
|
||||
echo "Found beta version in docs: $BETA_CURRENT"
|
||||
|
||||
- name: Replace versions in docs recursively
|
||||
run: |
|
||||
echo "Replacing all occurrences of stable and beta versions recursively under docs/docs/..."
|
||||
|
||||
# Replace beta versions
|
||||
grep -rl "${{ steps.extract-current-versions.outputs.betaCurrent }}" docs/docs | \
|
||||
xargs sed -i "s/${{ steps.extract-current-versions.outputs.betaCurrent }}/$BETA_VERSION/g" || true
|
||||
|
||||
# Replace stable versions
|
||||
grep -rl "${{ steps.extract-current-versions.outputs.stableCurrent }}" docs/docs | \
|
||||
xargs sed -i "s/${{ steps.extract-current-versions.outputs.stableCurrent }}/$STABLE_VERSION/g" || true
|
||||
|
||||
- name: Update metadata in get-started.md
|
||||
run: |
|
||||
sed -i "s/betaVersion: .*/betaVersion: $BETA_VERSION/" docs/docs/get-started.md
|
||||
sed -i "s/stableVersion: .*/stableVersion: $STABLE_VERSION/" docs/docs/get-started.md
|
||||
|
||||
- name: Check if any changes were made
|
||||
id: git-check
|
||||
run: |
|
||||
if git diff --quiet; then
|
||||
echo "changed=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "changed=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Commit and push changes
|
||||
if: steps.git-check.outputs.changed == 'true'
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add docs/docs
|
||||
git commit -m "docu: update versions to $STABLE_VERSION and $BETA_VERSION"
|
||||
git push origin main
|
||||
|
||||
- name: Trigger documentation build and deploy
|
||||
if: steps.git-check.outputs.changed == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh api repos/langchain4j/langchain4j/dispatches \
|
||||
-f event_type=trigger-docs-build-and-deploy
|
||||
|
||||
- name: Trigger documentation chatbot update
|
||||
if: steps.git-check.outputs.changed == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh api repos/langchain4j/langchain4j/dispatches \
|
||||
-f event_type=trigger-docs-update-chatbot
|
||||
@@ -0,0 +1,57 @@
|
||||
name: "Documentation: build and deploy"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'docs/**'
|
||||
repository_dispatch:
|
||||
types: [ trigger-docs-build-and-deploy ]
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
|
||||
- name: Set up JDK 25
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
|
||||
with:
|
||||
java-version: '25'
|
||||
distribution: 'temurin'
|
||||
cache: 'maven'
|
||||
|
||||
- name: Generate Javadoc
|
||||
run: mvn javadoc:aggregate -DskipTests
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
- run: cd docs && npm ci
|
||||
- run: cd docs && npm run build
|
||||
- run: cp -R target/reports/apidocs docs/build/
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@1f0c5cde4bc74cd7e1254d0cb4de8d49e9068c7d # v4
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
|
||||
with:
|
||||
# Upload entire repository
|
||||
path: './docs/build/.'
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4
|
||||
@@ -0,0 +1,25 @@
|
||||
name: langchain4j-github-bot.yml lint
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.github/langchain4j-github-bot.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/langchain4j-github-bot.yml'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: "langchain4j-github-bot.yml validation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- name: install yamllint
|
||||
shell: bash
|
||||
run: sudo apt-get install -y yamllint
|
||||
- name: run yamllint for langchain4j-github-bot.yml
|
||||
shell: bash
|
||||
run: yamllint -c .github/yamllint.conf .github/langchain4j-github-bot.yml
|
||||
@@ -0,0 +1,379 @@
|
||||
name: Java CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- 'release/**'
|
||||
paths-ignore:
|
||||
- '.gitignore'
|
||||
- '*.md'
|
||||
- 'LICENSE'
|
||||
- '.github/*.md'
|
||||
- '.github/*.yml'
|
||||
- '.github/*.conf'
|
||||
- '.github/ISSUE_TEMPLATE/*.md'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- 'release/**'
|
||||
paths-ignore:
|
||||
- '.gitignore'
|
||||
- '*.md'
|
||||
- 'LICENSE'
|
||||
- '.github/*.md'
|
||||
- '.github/*.yml'
|
||||
- '.github/*.conf'
|
||||
- '.github/ISSUE_TEMPLATE/*.md'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
|
||||
compile_and_unit_test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
java_version:
|
||||
- 17
|
||||
- 21
|
||||
- 25
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK ${{ matrix.java_version }}
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
|
||||
with:
|
||||
java-version: ${{ matrix.java_version }}
|
||||
distribution: 'temurin'
|
||||
cache: 'maven'
|
||||
|
||||
- name: Compile and unit test with JDK ${{ matrix.java_version }}
|
||||
run: |
|
||||
set -o pipefail
|
||||
mvn -B -U -DembeddingsSkipDownload -T8C test javadoc:aggregate 2>&1 | tee maven-output.log
|
||||
|
||||
- name: Publish test summary
|
||||
if: always()
|
||||
env:
|
||||
SUMMARY_TITLE: 'Test Results (JDK ${{ matrix.java_version }})'
|
||||
run: |
|
||||
python3 << 'PYEOF'
|
||||
import xml.etree.ElementTree as ET
|
||||
import glob, os, re
|
||||
|
||||
files = glob.glob('**/target/*-reports/TEST-*.xml', recursive=True)
|
||||
if not files:
|
||||
raise SystemExit(0) # e.g. compilation failed before any test ran
|
||||
|
||||
total = failures = errors = skipped = 0
|
||||
failed_tests = []
|
||||
|
||||
for path in files:
|
||||
try:
|
||||
root = ET.parse(path).getroot()
|
||||
total += int(root.get('tests', 0))
|
||||
failures += int(root.get('failures', 0))
|
||||
errors += int(root.get('errors', 0))
|
||||
skipped += int(root.get('skipped', 0))
|
||||
for tc in root.findall('.//testcase'):
|
||||
fail = tc.find('failure')
|
||||
if fail is None:
|
||||
fail = tc.find('error')
|
||||
if fail is not None:
|
||||
cls = tc.get('classname', '')
|
||||
name = tc.get('name', '')
|
||||
trace = fail.text or ''
|
||||
m = re.search(r'\bat ' + re.escape(cls) + r'[.$][^\n(]*\(([^)]+\.java:\d+)\)', trace)
|
||||
loc = f'`{m.group(1)}`' if m else ''
|
||||
msg = (fail.get('message', '') or '').replace('\n', '<br>').replace('|', '\\|')
|
||||
if len(msg) > 500:
|
||||
msg = f"{msg[:500]}<details><summary>show more</summary>{msg[500:]}</details>"
|
||||
failed_tests.append(f"| `{cls}` | `{name}` | {loc} | {msg} |")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
passed = total - failures - errors - skipped
|
||||
title = os.environ.get('SUMMARY_TITLE', 'Test Results')
|
||||
summary = os.environ.get('GITHUB_STEP_SUMMARY', '/dev/null')
|
||||
with open(summary, 'a') as f:
|
||||
f.write(f'## {title}\n\n')
|
||||
if failures == 0 and errors == 0:
|
||||
f.write(f':white_check_mark: **{passed} passed**, {skipped} skipped out of {total} tests\n')
|
||||
else:
|
||||
f.write(f':x: **{failures} failed, {errors} errors**, {passed} passed, {skipped} skipped out of {total} tests\n')
|
||||
if failed_tests:
|
||||
f.write('\n### Failures\n\n')
|
||||
f.write('| Class | Test | Location | Error Message |\n')
|
||||
f.write('|-------|------|----------|---------------|\n')
|
||||
for line in failed_tests[:50]:
|
||||
f.write(line + '\n')
|
||||
if len(failed_tests) > 50:
|
||||
f.write(f'\n*... and {len(failed_tests) - 50} more*\n')
|
||||
PYEOF
|
||||
|
||||
- name: Surface build errors
|
||||
if: failure()
|
||||
run: |
|
||||
{
|
||||
echo '## Build Errors (JDK ${{ matrix.java_version }})'
|
||||
echo ''
|
||||
echo '```'
|
||||
# Only Maven's own errors (compilation, javadoc, plugins). Exclude forked
|
||||
# test-JVM output that Surefire re-emits with an [ERROR] prefix on channel
|
||||
# corruption (e.g. mock-server stub dumps) — that is test noise, not a build error.
|
||||
grep '^\[ERROR\]' maven-output.log \
|
||||
| grep -vE '^\[ERROR\]( +"|[[:space:]]*(Stub|DEBUG:)|[[:space:]]*$)' \
|
||||
| head -50
|
||||
echo '```'
|
||||
} >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
integration_test:
|
||||
needs: compile_and_unit_test
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
env:
|
||||
GCP_CREDENTIALS_JSON: ${{ secrets.GCP_CREDENTIALS_JSON }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
java_version:
|
||||
- 21
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Create branch from commit by event name
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
BEFORE_SHA: ${{ github.event.before }}
|
||||
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
run: |
|
||||
if [[ "$EVENT_NAME" == 'push' ]]; then
|
||||
git branch __branch_before "$BEFORE_SHA"
|
||||
elif [[ "$EVENT_NAME" == 'pull_request' ]]; then
|
||||
git branch __branch_before "$PR_BASE_SHA"
|
||||
fi
|
||||
|
||||
- name: Set up JDK ${{ matrix.java_version }}
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
|
||||
with:
|
||||
java-version: ${{ matrix.java_version }}
|
||||
distribution: 'temurin'
|
||||
cache: 'maven'
|
||||
|
||||
- name: Authenticate to Google Cloud
|
||||
# Required for Google modules (e.g., langchain4j-vertex-ai)
|
||||
# Skipped for PRs from forks where secrets are not available
|
||||
if: ${{ env.GCP_CREDENTIALS_JSON != '' }}
|
||||
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3
|
||||
with:
|
||||
project_id: ${{ secrets.GCP_PROJECT_ID }}
|
||||
credentials_json: ${{ secrets.GCP_CREDENTIALS_JSON }}
|
||||
|
||||
- name: Setup JBang
|
||||
# Required for MCP module
|
||||
uses: jbangdev/setup-jbang@2b1b465a7b75f4222b81426f23a01e013aa7b95c # v0.1.1
|
||||
continue-on-error: true
|
||||
|
||||
- name: Integration test with JDK ${{ matrix.java_version }}
|
||||
run: |
|
||||
set -o pipefail
|
||||
|
||||
## Compile and install ALL modules to ensure that modules selected by GIB in the next step
|
||||
## reference the latest code (e.g., langchain4j-core and langchain4j)
|
||||
mvn -B -U -T8C -DskipTests -DskipITs -DembeddingsSkipCache install 2>&1 | tee maven-output.log
|
||||
|
||||
mvn -B -U verify \
|
||||
-Dgib.disable=false -Dgib.referenceBranch=__branch_before \
|
||||
-Djunit.jupiter.extensions.autodetection.enabled=true \
|
||||
-DskipAzureAiSearchITs -DskipJlamaITs -DskipLocalAiITs -DskipMilvusITs -DskipOllamaITs -DskipOracleITs -DskipVespaITs \
|
||||
-DembeddingsSkipCache \
|
||||
-Dtinylog.writer.level=info 2>&1 | tee maven-output.log
|
||||
env:
|
||||
LC4J_GLOBAL_TEST_RETRY_ENABLED: true
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
ANTHROPIC_CACHING_BASE_URL: ${{ secrets.ANTHROPIC_CACHING_BASE_URL }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AZURE_OPENAI_AUDIO_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_AUDIO_DEPLOYMENT_NAME }}
|
||||
AZURE_OPENAI_AUDIO_ENDPOINT: ${{ secrets.AZURE_OPENAI_AUDIO_ENDPOINT }}
|
||||
AZURE_OPENAI_AUDIO_KEY: ${{ secrets.AZURE_OPENAI_AUDIO_KEY }}
|
||||
AZURE_OPENAI_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_DEPLOYMENT_NAME }}
|
||||
AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }}
|
||||
AZURE_OPENAI_KEY: ${{ secrets.AZURE_OPENAI_KEY }}
|
||||
AZURE_SEARCH_ENDPOINT: ${{ secrets.AZURE_SEARCH_ENDPOINT }}
|
||||
AZURE_SEARCH_KEY: ${{ secrets.AZURE_SEARCH_KEY }}
|
||||
COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
|
||||
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
|
||||
ELASTICSEARCH_CLOUD_API_KEY: ${{ secrets.ELASTICSEARCH_CLOUD_API_KEY }}
|
||||
ELASTICSEARCH_CLOUD_URL: ${{ secrets.ELASTICSEARCH_CLOUD_URL }}
|
||||
GCP_CREDENTIALS_JSON: ${{ secrets.GCP_CREDENTIALS_JSON }}
|
||||
GCP_LOCATION: ${{ secrets.GCP_LOCATION }}
|
||||
GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
|
||||
GCP_PROJECT_NUM: ${{ secrets.GCP_PROJECT_NUM }}
|
||||
GCP_VERTEXAI_ENDPOINT: ${{ secrets.GCP_VERTEXAI_ENDPOINT }}
|
||||
GOOGLE_AI_GEMINI_API_KEY: ${{ secrets.GOOGLE_AI_GEMINI_API_KEY }}
|
||||
HF_API_KEY: ${{ secrets.HF_API_KEY }}
|
||||
JINA_API_KEY: ${{ secrets.JINA_API_KEY }}
|
||||
MICROSOFT_FOUNDRY_API_KEY: ${{ secrets.MICROSOFT_FOUNDRY_API_KEY }}
|
||||
MICROSOFT_FOUNDRY_ENDPOINT: ${{ secrets.MICROSOFT_FOUNDRY_ENDPOINT }}
|
||||
MILVUS_API_KEY: ${{ secrets.MILVUS_API_KEY }}
|
||||
MILVUS_URI: ${{ secrets.MILVUS_URI }}
|
||||
MISTRAL_AI_API_KEY: ${{ secrets.MISTRAL_AI_API_KEY }}
|
||||
NOMIC_API_KEY: ${{ secrets.NOMIC_API_KEY }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
||||
PINECONE_API_KEY: ${{ secrets.PINECONE_API_KEY }}
|
||||
RAPID_API_KEY: ${{ secrets.RAPID_API_KEY }}
|
||||
TAVILY_API_KEY: ${{ secrets.TAVILY_API_KEY }}
|
||||
VOYAGE_API_KEY: ${{ secrets.VOYAGE_API_KEY }}
|
||||
WEAVIATE_API_KEY: ${{ secrets.WEAVIATE_API_KEY }}
|
||||
WEAVIATE_HOST: ${{ secrets.WEAVIATE_HOST }}
|
||||
|
||||
- name: Publish test summary
|
||||
if: always()
|
||||
env:
|
||||
SUMMARY_TITLE: 'Test Results (Integration Test, JDK ${{ matrix.java_version }})'
|
||||
run: |
|
||||
python3 << 'PYEOF'
|
||||
import xml.etree.ElementTree as ET
|
||||
import glob, os, re
|
||||
|
||||
files = glob.glob('**/target/*-reports/TEST-*.xml', recursive=True)
|
||||
if not files:
|
||||
raise SystemExit(0) # e.g. compilation failed before any test ran
|
||||
|
||||
total = failures = errors = skipped = 0
|
||||
failed_tests = []
|
||||
|
||||
for path in files:
|
||||
try:
|
||||
root = ET.parse(path).getroot()
|
||||
total += int(root.get('tests', 0))
|
||||
failures += int(root.get('failures', 0))
|
||||
errors += int(root.get('errors', 0))
|
||||
skipped += int(root.get('skipped', 0))
|
||||
for tc in root.findall('.//testcase'):
|
||||
fail = tc.find('failure')
|
||||
if fail is None:
|
||||
fail = tc.find('error')
|
||||
if fail is not None:
|
||||
cls = tc.get('classname', '')
|
||||
name = tc.get('name', '')
|
||||
trace = fail.text or ''
|
||||
m = re.search(r'\bat ' + re.escape(cls) + r'[.$][^\n(]*\(([^)]+\.java:\d+)\)', trace)
|
||||
loc = f'`{m.group(1)}`' if m else ''
|
||||
msg = (fail.get('message', '') or '').replace('\n', '<br>').replace('|', '\\|')
|
||||
if len(msg) > 500:
|
||||
msg = f"{msg[:500]}<details><summary>show more</summary>{msg[500:]}</details>"
|
||||
failed_tests.append(f"| `{cls}` | `{name}` | {loc} | {msg} |")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
passed = total - failures - errors - skipped
|
||||
title = os.environ.get('SUMMARY_TITLE', 'Test Results')
|
||||
summary = os.environ.get('GITHUB_STEP_SUMMARY', '/dev/null')
|
||||
with open(summary, 'a') as f:
|
||||
f.write(f'## {title}\n\n')
|
||||
if failures == 0 and errors == 0:
|
||||
f.write(f':white_check_mark: **{passed} passed**, {skipped} skipped out of {total} tests\n')
|
||||
else:
|
||||
f.write(f':x: **{failures} failed, {errors} errors**, {passed} passed, {skipped} skipped out of {total} tests\n')
|
||||
if failed_tests:
|
||||
f.write('\n### Failures\n\n')
|
||||
f.write('| Class | Test | Location | Error Message |\n')
|
||||
f.write('|-------|------|----------|---------------|\n')
|
||||
for line in failed_tests[:50]:
|
||||
f.write(line + '\n')
|
||||
if len(failed_tests) > 50:
|
||||
f.write(f'\n*... and {len(failed_tests) - 50} more*\n')
|
||||
PYEOF
|
||||
|
||||
- name: Surface build errors
|
||||
if: failure()
|
||||
run: |
|
||||
{
|
||||
echo '## Build Errors (Integration Test, JDK ${{ matrix.java_version }})'
|
||||
echo ''
|
||||
echo '```'
|
||||
# Only Maven's own errors (compilation, javadoc, plugins). Exclude forked
|
||||
# test-JVM output that Surefire re-emits with an [ERROR] prefix on channel
|
||||
# corruption (e.g. mock-server stub dumps) — that is test noise, not a build error.
|
||||
grep '^\[ERROR\]' maven-output.log \
|
||||
| grep -vE '^\[ERROR\]( +"|[[:space:]]*(Stub|DEBUG:)|[[:space:]]*$)' \
|
||||
| head -50
|
||||
echo '```'
|
||||
} >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Clean Docker
|
||||
run: docker system prune -af || true
|
||||
|
||||
# For checking some compliance things (require a recent JDK due to plugins so in a separate step)
|
||||
compliance:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
cache: 'maven'
|
||||
# Check we only rely on permissive licenses in the main parts of the library:
|
||||
- name: License Compliance
|
||||
run: |
|
||||
set -o pipefail
|
||||
mvn -P compliance org.honton.chas:license-maven-plugin:compliance 2>&1 | tee maven-output.log
|
||||
|
||||
- name: Surface build errors
|
||||
if: failure()
|
||||
run: |
|
||||
{
|
||||
echo '## Build Errors (License Compliance)'
|
||||
echo ''
|
||||
echo '```'
|
||||
# Exclude forked-JVM output re-emitted with an [ERROR] prefix (test noise).
|
||||
grep '^\[ERROR\]' maven-output.log \
|
||||
| grep -vE '^\[ERROR\]( +"|[[:space:]]*(Stub|DEBUG:)|[[:space:]]*$)' \
|
||||
| head -50
|
||||
echo '```'
|
||||
} >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
spotless:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
cache: 'maven'
|
||||
- name: Spotless Check
|
||||
run: |
|
||||
set -o pipefail
|
||||
mvn -Pspotless validate 2>&1 | tee maven-output.log
|
||||
|
||||
- name: Surface build errors
|
||||
if: failure()
|
||||
run: |
|
||||
{
|
||||
echo '## Build Errors (Spotless)'
|
||||
echo ''
|
||||
echo '```'
|
||||
# Exclude forked-JVM output re-emitted with an [ERROR] prefix (test noise).
|
||||
grep '^\[ERROR\]' maven-output.log \
|
||||
| grep -vE '^\[ERROR\]( +"|[[:space:]]*(Stub|DEBUG:)|[[:space:]]*$)' \
|
||||
| head -50
|
||||
echo '```'
|
||||
} >> $GITHUB_STEP_SUMMARY
|
||||
@@ -0,0 +1,226 @@
|
||||
name: Nightly Build JDK 17
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 22 * * *' # daily at 22:00 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
discover-modules:
|
||||
if: github.repository == 'langchain4j/langchain4j'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
|
||||
- id: set-matrix
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Discovering Maven modules..."
|
||||
modules=$(find . -type f -name pom.xml -exec dirname {} \; | sort | uniq | sed 's|^\./||' | grep -v '^$' | grep -v '^.$')
|
||||
echo "Detected modules:"
|
||||
echo "$modules"
|
||||
|
||||
# number of parallel groups
|
||||
N=7
|
||||
|
||||
# round-robin grouping
|
||||
matrix=$(echo "$modules" | awk -v N="$N" '
|
||||
{
|
||||
g = (NR - 1) % N
|
||||
arr[g] = (arr[g] ? arr[g] "," "\"" $0 "\"" : "\"" $0 "\"")
|
||||
}
|
||||
END {
|
||||
printf("{\"modules\": [");
|
||||
for (i = 0; i < N; i++) {
|
||||
if (i > 0) printf(",");
|
||||
printf("[%s]", arr[i])
|
||||
}
|
||||
print "]}"
|
||||
}
|
||||
')
|
||||
|
||||
echo "Generated matrix: $matrix"
|
||||
echo "matrix=$matrix" >> $GITHUB_OUTPUT
|
||||
|
||||
build:
|
||||
needs: discover-modules
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.discover-modules.outputs.matrix) }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: 'temurin'
|
||||
cache: 'maven'
|
||||
|
||||
- name: Authenticate to Google Cloud
|
||||
# Required for Google modules (e.g., langchain4j-vertex-ai)
|
||||
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3
|
||||
with:
|
||||
project_id: ${{ secrets.GCP_PROJECT_ID }}
|
||||
credentials_json: ${{ secrets.GCP_CREDENTIALS_JSON }}
|
||||
|
||||
- name: Setup JBang
|
||||
# Required for MCP module
|
||||
uses: jbangdev/setup-jbang@2b1b465a7b75f4222b81426f23a01e013aa7b95c # v0.1.1
|
||||
continue-on-error: true
|
||||
|
||||
- name: Build with JDK 17
|
||||
run: |
|
||||
modules="${{ join(matrix.modules, ',') }}"
|
||||
exclude_modules="langchain4j-jlama,langchain4j-gpu-llama3"
|
||||
for ex in ${exclude_modules//,/ }; do
|
||||
modules=$(echo "$modules" | sed "s/\b${ex}\b//g" | sed 's/,,/,/g' | sed 's/^,//' | sed 's/,$//')
|
||||
done
|
||||
echo "Building modules: $modules"
|
||||
|
||||
## compile and install ALL modules to avoid running integration tests on dependent modules in the step below
|
||||
mvn -B -U -T8C -DskipTests -DskipITs -DembeddingsSkipCache install
|
||||
|
||||
mvn -B -U verify \
|
||||
-pl "${modules}" \
|
||||
-DskipAzureAiSearchITs -DskipJlamaITs -DskipLocalAiITs -DskipMilvusITs -DskipOllamaITs -DskipOracleITs -DskipVespaITs \
|
||||
--fail-at-end \
|
||||
-Djunit.jupiter.extensions.autodetection.enabled=true \
|
||||
-DembeddingsSkipCache \
|
||||
-Dtinylog.writer.level=info
|
||||
|
||||
env:
|
||||
LC4J_GLOBAL_TEST_RETRY_ENABLED: true
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
ANTHROPIC_CACHING_BASE_URL: ${{ secrets.ANTHROPIC_CACHING_BASE_URL }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AZURE_OPENAI_AUDIO_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_AUDIO_DEPLOYMENT_NAME }}
|
||||
AZURE_OPENAI_AUDIO_ENDPOINT: ${{ secrets.AZURE_OPENAI_AUDIO_ENDPOINT }}
|
||||
AZURE_OPENAI_AUDIO_KEY: ${{ secrets.AZURE_OPENAI_AUDIO_KEY }}
|
||||
AZURE_OPENAI_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_DEPLOYMENT_NAME }}
|
||||
AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }}
|
||||
AZURE_OPENAI_KEY: ${{ secrets.AZURE_OPENAI_KEY }}
|
||||
AZURE_SEARCH_ENDPOINT: ${{ secrets.AZURE_SEARCH_ENDPOINT }}
|
||||
AZURE_SEARCH_KEY: ${{ secrets.AZURE_SEARCH_KEY }}
|
||||
COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
|
||||
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
|
||||
ELASTICSEARCH_CLOUD_API_KEY: ${{ secrets.ELASTICSEARCH_CLOUD_API_KEY }}
|
||||
ELASTICSEARCH_CLOUD_URL: ${{ secrets.ELASTICSEARCH_CLOUD_URL }}
|
||||
GCP_CREDENTIALS_JSON: ${{ secrets.GCP_CREDENTIALS_JSON }}
|
||||
GCP_LOCATION: ${{ secrets.GCP_LOCATION }}
|
||||
GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
|
||||
GCP_PROJECT_NUM: ${{ secrets.GCP_PROJECT_NUM }}
|
||||
GCP_VERTEXAI_ENDPOINT: ${{ secrets.GCP_VERTEXAI_ENDPOINT }}
|
||||
GOOGLE_AI_GEMINI_API_KEY: ${{ secrets.GOOGLE_AI_GEMINI_API_KEY }}
|
||||
HF_API_KEY: ${{ secrets.HF_API_KEY }}
|
||||
JINA_API_KEY: ${{ secrets.JINA_API_KEY }}
|
||||
MICROSOFT_FOUNDRY_API_KEY: ${{ secrets.MICROSOFT_FOUNDRY_API_KEY }}
|
||||
MICROSOFT_FOUNDRY_ENDPOINT: ${{ secrets.MICROSOFT_FOUNDRY_ENDPOINT }}
|
||||
MILVUS_API_KEY: ${{ secrets.MILVUS_API_KEY }}
|
||||
MILVUS_URI: ${{ secrets.MILVUS_URI }}
|
||||
MISTRAL_AI_API_KEY: ${{ secrets.MISTRAL_AI_API_KEY }}
|
||||
NOMIC_API_KEY: ${{ secrets.NOMIC_API_KEY }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
||||
OVHAI_AI_API_KEY: ${{ secrets.OVHAI_AI_API_KEY }}
|
||||
PINECONE_API_KEY: ${{ secrets.PINECONE_API_KEY }}
|
||||
RAPID_API_KEY: ${{ secrets.RAPID_API_KEY }}
|
||||
TAVILY_API_KEY: ${{ secrets.TAVILY_API_KEY }}
|
||||
VOYAGE_API_KEY: ${{ secrets.VOYAGE_API_KEY }}
|
||||
WEAVIATE_API_KEY: ${{ secrets.WEAVIATE_API_KEY }}
|
||||
WEAVIATE_HOST: ${{ secrets.WEAVIATE_HOST }}
|
||||
CI_DELAY_SECONDS_AZURE_AI_SEARCH: 4
|
||||
CI_DELAY_SECONDS_AZURE_OPENAI: 3
|
||||
CI_DELAY_SECONDS_BEDROCK: 3
|
||||
CI_DELAY_SECONDS_GITHUB_MODELS: 7
|
||||
CI_DELAY_SECONDS_GOOGLE_AI_GEMINI: 20
|
||||
CI_DELAY_SECONDS_GOOGLE_AI_GEMINI_BATCH: 20
|
||||
CI_DELAY_SECONDS_VERTEX_AI: 5
|
||||
CI_DELAY_SECONDS_VERTEX_AI_ANTHROPIC: 12
|
||||
CI_DELAY_SECONDS_VERTEX_AI_GEMINI: 12
|
||||
CI_DELAY_SECONDS_VOYAGE_AI: 22
|
||||
|
||||
- name: Clean Docker
|
||||
run: docker system prune -af || true
|
||||
|
||||
- name: Publish Test Summary
|
||||
if: always()
|
||||
run: |
|
||||
python3 << 'PYEOF'
|
||||
import xml.etree.ElementTree as ET
|
||||
import glob, os, re
|
||||
|
||||
files = glob.glob('**/target/*-reports/TEST-*.xml', recursive=True)
|
||||
total = failures = errors = skipped = 0
|
||||
failed_tests = []
|
||||
|
||||
for path in files:
|
||||
try:
|
||||
root = ET.parse(path).getroot()
|
||||
total += int(root.get('tests', 0))
|
||||
failures += int(root.get('failures', 0))
|
||||
errors += int(root.get('errors', 0))
|
||||
skipped += int(root.get('skipped', 0))
|
||||
for tc in root.findall('.//testcase'):
|
||||
fail = tc.find('failure')
|
||||
if fail is None:
|
||||
fail = tc.find('error')
|
||||
if fail is not None:
|
||||
cls = tc.get('classname', '')
|
||||
name = tc.get('name', '')
|
||||
trace = fail.text or ''
|
||||
m = re.search(r'\bat ' + re.escape(cls) + r'[.$][^\n(]*\(([^)]+\.java:\d+)\)', trace)
|
||||
loc = f'`{m.group(1)}`' if m else ''
|
||||
msg = (fail.get('message', '') or '').replace('\n', '<br>').replace('|', '\\|')
|
||||
if len(msg) > 500:
|
||||
msg = f"{msg[:500]}<details><summary>show more</summary>{msg[500:]}</details>"
|
||||
failed_tests.append(f"| `{cls}` | `{name}` | {loc} | {msg} |")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
passed = total - failures - errors - skipped
|
||||
summary = os.environ.get('GITHUB_STEP_SUMMARY', '/dev/null')
|
||||
with open(summary, 'a') as f:
|
||||
f.write('## Test Results\n\n')
|
||||
if failures == 0 and errors == 0:
|
||||
f.write(f':white_check_mark: **{passed} passed**, {skipped} skipped out of {total} tests\n')
|
||||
else:
|
||||
f.write(f':x: **{failures} failed, {errors} errors**, {passed} passed, {skipped} skipped out of {total} tests\n')
|
||||
if failed_tests:
|
||||
f.write('\n### Failures\n\n')
|
||||
f.write('| Class | Test | Location | Error Message |\n')
|
||||
f.write('|-------|------|----------|---------------|\n')
|
||||
for line in failed_tests[:50]:
|
||||
f.write(line + '\n')
|
||||
if len(failed_tests) > 50:
|
||||
f.write(f'\n*... and {len(failed_tests) - 50} more*\n')
|
||||
PYEOF
|
||||
|
||||
- name: Upload Test Reports
|
||||
if: always() # always run even if the previous step failed or was cancelled
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: Test-Reports-${{ strategy.job-index }}
|
||||
path: '**/target/*-reports/*'
|
||||
|
||||
- name: Upload JaCoCo Reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: jacoco-${{ strategy.job-index }}
|
||||
path: '**/target/site/jacoco/**'
|
||||
|
||||
- name: Upload JaCoCo Execution Data
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: jacoco-exec-${{ strategy.job-index }}
|
||||
path: '**/target/jacoco.exec'
|
||||
@@ -0,0 +1,226 @@
|
||||
name: Nightly Build JDK 21
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 1 * * *' # daily at 01:00 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
discover-modules:
|
||||
if: github.repository == 'langchain4j/langchain4j'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
|
||||
- id: set-matrix
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Discovering Maven modules..."
|
||||
modules=$(find . -type f -name pom.xml -exec dirname {} \; | sort | uniq | sed 's|^\./||' | grep -v '^$' | grep -v '^.$')
|
||||
echo "Detected modules:"
|
||||
echo "$modules"
|
||||
|
||||
# number of parallel groups
|
||||
N=7
|
||||
|
||||
# round-robin grouping
|
||||
matrix=$(echo "$modules" | awk -v N="$N" '
|
||||
{
|
||||
g = (NR - 1) % N
|
||||
arr[g] = (arr[g] ? arr[g] "," "\"" $0 "\"" : "\"" $0 "\"")
|
||||
}
|
||||
END {
|
||||
printf("{\"modules\": [");
|
||||
for (i = 0; i < N; i++) {
|
||||
if (i > 0) printf(",");
|
||||
printf("[%s]", arr[i])
|
||||
}
|
||||
print "]}"
|
||||
}
|
||||
')
|
||||
|
||||
echo "Generated matrix: $matrix"
|
||||
echo "matrix=$matrix" >> $GITHUB_OUTPUT
|
||||
|
||||
build:
|
||||
needs: discover-modules
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.discover-modules.outputs.matrix) }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
|
||||
with:
|
||||
java-version: 21
|
||||
distribution: 'temurin'
|
||||
cache: 'maven'
|
||||
|
||||
- name: Authenticate to Google Cloud
|
||||
# Required for Google modules (e.g., langchain4j-vertex-ai)
|
||||
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3
|
||||
with:
|
||||
project_id: ${{ secrets.GCP_PROJECT_ID }}
|
||||
credentials_json: ${{ secrets.GCP_CREDENTIALS_JSON }}
|
||||
|
||||
- name: Setup JBang
|
||||
# Required for MCP module
|
||||
uses: jbangdev/setup-jbang@2b1b465a7b75f4222b81426f23a01e013aa7b95c # v0.1.1
|
||||
continue-on-error: true
|
||||
|
||||
- name: Build with JDK 21
|
||||
run: |
|
||||
modules="${{ join(matrix.modules, ',') }}"
|
||||
exclude_modules="langchain4j-gpu-llama3"
|
||||
for ex in ${exclude_modules//,/ }; do
|
||||
modules=$(echo "$modules" | sed "s/\b${ex}\b//g" | sed 's/,,/,/g' | sed 's/^,//' | sed 's/,$//')
|
||||
done
|
||||
echo "Building modules: $modules"
|
||||
|
||||
## compile and install ALL modules to avoid running integration tests on dependent modules in the step below
|
||||
mvn -B -U -T8C -DskipTests -DskipITs -DembeddingsSkipCache install
|
||||
|
||||
mvn -B -U verify \
|
||||
-pl "${modules}" \
|
||||
-DskipAzureAiSearchITs -DskipJlamaITs -DskipLocalAiITs -DskipMilvusITs -DskipOllamaITs -DskipOracleITs -DskipVespaITs \
|
||||
--fail-at-end \
|
||||
-Djunit.jupiter.extensions.autodetection.enabled=true \
|
||||
-DembeddingsSkipCache \
|
||||
-Dtinylog.writer.level=info
|
||||
|
||||
env:
|
||||
LC4J_GLOBAL_TEST_RETRY_ENABLED: true
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
ANTHROPIC_CACHING_BASE_URL: ${{ secrets.ANTHROPIC_CACHING_BASE_URL }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AZURE_OPENAI_AUDIO_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_AUDIO_DEPLOYMENT_NAME }}
|
||||
AZURE_OPENAI_AUDIO_ENDPOINT: ${{ secrets.AZURE_OPENAI_AUDIO_ENDPOINT }}
|
||||
AZURE_OPENAI_AUDIO_KEY: ${{ secrets.AZURE_OPENAI_AUDIO_KEY }}
|
||||
AZURE_OPENAI_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_DEPLOYMENT_NAME }}
|
||||
AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }}
|
||||
AZURE_OPENAI_KEY: ${{ secrets.AZURE_OPENAI_KEY }}
|
||||
AZURE_SEARCH_ENDPOINT: ${{ secrets.AZURE_SEARCH_ENDPOINT }}
|
||||
AZURE_SEARCH_KEY: ${{ secrets.AZURE_SEARCH_KEY }}
|
||||
COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
|
||||
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
|
||||
ELASTICSEARCH_CLOUD_API_KEY: ${{ secrets.ELASTICSEARCH_CLOUD_API_KEY }}
|
||||
ELASTICSEARCH_CLOUD_URL: ${{ secrets.ELASTICSEARCH_CLOUD_URL }}
|
||||
GCP_CREDENTIALS_JSON: ${{ secrets.GCP_CREDENTIALS_JSON }}
|
||||
GCP_LOCATION: ${{ secrets.GCP_LOCATION }}
|
||||
GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
|
||||
GCP_PROJECT_NUM: ${{ secrets.GCP_PROJECT_NUM }}
|
||||
GCP_VERTEXAI_ENDPOINT: ${{ secrets.GCP_VERTEXAI_ENDPOINT }}
|
||||
GOOGLE_AI_GEMINI_API_KEY: ${{ secrets.GOOGLE_AI_GEMINI_API_KEY }}
|
||||
HF_API_KEY: ${{ secrets.HF_API_KEY }}
|
||||
JINA_API_KEY: ${{ secrets.JINA_API_KEY }}
|
||||
MICROSOFT_FOUNDRY_API_KEY: ${{ secrets.MICROSOFT_FOUNDRY_API_KEY }}
|
||||
MICROSOFT_FOUNDRY_ENDPOINT: ${{ secrets.MICROSOFT_FOUNDRY_ENDPOINT }}
|
||||
MILVUS_API_KEY: ${{ secrets.MILVUS_API_KEY }}
|
||||
MILVUS_URI: ${{ secrets.MILVUS_URI }}
|
||||
MISTRAL_AI_API_KEY: ${{ secrets.MISTRAL_AI_API_KEY }}
|
||||
NOMIC_API_KEY: ${{ secrets.NOMIC_API_KEY }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
||||
OVHAI_AI_API_KEY: ${{ secrets.OVHAI_AI_API_KEY }}
|
||||
PINECONE_API_KEY: ${{ secrets.PINECONE_API_KEY }}
|
||||
RAPID_API_KEY: ${{ secrets.RAPID_API_KEY }}
|
||||
TAVILY_API_KEY: ${{ secrets.TAVILY_API_KEY }}
|
||||
VOYAGE_API_KEY: ${{ secrets.VOYAGE_API_KEY }}
|
||||
WEAVIATE_API_KEY: ${{ secrets.WEAVIATE_API_KEY }}
|
||||
WEAVIATE_HOST: ${{ secrets.WEAVIATE_HOST }}
|
||||
CI_DELAY_SECONDS_AZURE_AI_SEARCH: 4
|
||||
CI_DELAY_SECONDS_AZURE_OPENAI: 3
|
||||
CI_DELAY_SECONDS_BEDROCK: 3
|
||||
CI_DELAY_SECONDS_GITHUB_MODELS: 7
|
||||
CI_DELAY_SECONDS_GOOGLE_AI_GEMINI: 20
|
||||
CI_DELAY_SECONDS_GOOGLE_AI_GEMINI_BATCH: 20
|
||||
CI_DELAY_SECONDS_VERTEX_AI: 5
|
||||
CI_DELAY_SECONDS_VERTEX_AI_ANTHROPIC: 12
|
||||
CI_DELAY_SECONDS_VERTEX_AI_GEMINI: 12
|
||||
CI_DELAY_SECONDS_VOYAGE_AI: 22
|
||||
|
||||
- name: Clean Docker
|
||||
run: docker system prune -af || true
|
||||
|
||||
- name: Publish Test Summary
|
||||
if: always()
|
||||
run: |
|
||||
python3 << 'PYEOF'
|
||||
import xml.etree.ElementTree as ET
|
||||
import glob, os, re
|
||||
|
||||
files = glob.glob('**/target/*-reports/TEST-*.xml', recursive=True)
|
||||
total = failures = errors = skipped = 0
|
||||
failed_tests = []
|
||||
|
||||
for path in files:
|
||||
try:
|
||||
root = ET.parse(path).getroot()
|
||||
total += int(root.get('tests', 0))
|
||||
failures += int(root.get('failures', 0))
|
||||
errors += int(root.get('errors', 0))
|
||||
skipped += int(root.get('skipped', 0))
|
||||
for tc in root.findall('.//testcase'):
|
||||
fail = tc.find('failure')
|
||||
if fail is None:
|
||||
fail = tc.find('error')
|
||||
if fail is not None:
|
||||
cls = tc.get('classname', '')
|
||||
name = tc.get('name', '')
|
||||
trace = fail.text or ''
|
||||
m = re.search(r'\bat ' + re.escape(cls) + r'[.$][^\n(]*\(([^)]+\.java:\d+)\)', trace)
|
||||
loc = f'`{m.group(1)}`' if m else ''
|
||||
msg = (fail.get('message', '') or '').replace('\n', '<br>').replace('|', '\\|')
|
||||
if len(msg) > 500:
|
||||
msg = f"{msg[:500]}<details><summary>show more</summary>{msg[500:]}</details>"
|
||||
failed_tests.append(f"| `{cls}` | `{name}` | {loc} | {msg} |")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
passed = total - failures - errors - skipped
|
||||
summary = os.environ.get('GITHUB_STEP_SUMMARY', '/dev/null')
|
||||
with open(summary, 'a') as f:
|
||||
f.write('## Test Results\n\n')
|
||||
if failures == 0 and errors == 0:
|
||||
f.write(f':white_check_mark: **{passed} passed**, {skipped} skipped out of {total} tests\n')
|
||||
else:
|
||||
f.write(f':x: **{failures} failed, {errors} errors**, {passed} passed, {skipped} skipped out of {total} tests\n')
|
||||
if failed_tests:
|
||||
f.write('\n### Failures\n\n')
|
||||
f.write('| Class | Test | Location | Error Message |\n')
|
||||
f.write('|-------|------|----------|---------------|\n')
|
||||
for line in failed_tests[:50]:
|
||||
f.write(line + '\n')
|
||||
if len(failed_tests) > 50:
|
||||
f.write(f'\n*... and {len(failed_tests) - 50} more*\n')
|
||||
PYEOF
|
||||
|
||||
- name: Upload Test Reports
|
||||
if: always() # always run even if the previous step failed or was cancelled
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: Test-Reports-${{ strategy.job-index }}
|
||||
path: '**/target/*-reports/*'
|
||||
|
||||
- name: Upload JaCoCo Reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: jacoco-${{ strategy.job-index }}
|
||||
path: '**/target/site/jacoco/**'
|
||||
|
||||
- name: Upload JaCoCo Execution Data
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: jacoco-exec-${{ strategy.job-index }}
|
||||
path: '**/target/jacoco.exec'
|
||||
@@ -0,0 +1,222 @@
|
||||
name: Nightly Build JDK 25
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 4 * * *' # daily at 04:00 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
discover-modules:
|
||||
if: github.repository == 'langchain4j/langchain4j'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
|
||||
- id: set-matrix
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Discovering Maven modules..."
|
||||
modules=$(find . -type f -name pom.xml -exec dirname {} \; | sort | uniq | sed 's|^\./||' | grep -v '^$' | grep -v '^.$')
|
||||
echo "Detected modules:"
|
||||
echo "$modules"
|
||||
|
||||
# number of parallel groups
|
||||
N=7
|
||||
|
||||
# round-robin grouping
|
||||
matrix=$(echo "$modules" | awk -v N="$N" '
|
||||
{
|
||||
g = (NR - 1) % N
|
||||
arr[g] = (arr[g] ? arr[g] "," "\"" $0 "\"" : "\"" $0 "\"")
|
||||
}
|
||||
END {
|
||||
printf("{\"modules\": [");
|
||||
for (i = 0; i < N; i++) {
|
||||
if (i > 0) printf(",");
|
||||
printf("[%s]", arr[i])
|
||||
}
|
||||
print "]}"
|
||||
}
|
||||
')
|
||||
|
||||
echo "Generated matrix: $matrix"
|
||||
echo "matrix=$matrix" >> $GITHUB_OUTPUT
|
||||
|
||||
build:
|
||||
needs: discover-modules
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.discover-modules.outputs.matrix) }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
|
||||
- name: Set up JDK 25
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
|
||||
with:
|
||||
java-version: 25
|
||||
distribution: 'temurin'
|
||||
cache: 'maven'
|
||||
|
||||
- name: Authenticate to Google Cloud
|
||||
# Required for Google modules (e.g., langchain4j-vertex-ai)
|
||||
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3
|
||||
with:
|
||||
project_id: ${{ secrets.GCP_PROJECT_ID }}
|
||||
credentials_json: ${{ secrets.GCP_CREDENTIALS_JSON }}
|
||||
|
||||
- name: Setup JBang
|
||||
# Required for MCP module
|
||||
uses: jbangdev/setup-jbang@2b1b465a7b75f4222b81426f23a01e013aa7b95c # v0.1.1
|
||||
continue-on-error: true
|
||||
|
||||
- name: Build with JDK 25
|
||||
run: |
|
||||
modules="${{ join(matrix.modules, ',') }}"
|
||||
echo "Building modules: $modules"
|
||||
|
||||
## compile and install ALL modules to avoid running integration tests on dependent modules in the step below
|
||||
mvn -B -U -T8C -DskipTests -DskipITs -DembeddingsSkipCache install
|
||||
|
||||
mvn -B -U verify \
|
||||
-pl "${modules}" \
|
||||
-DskipAzureAiSearchITs -DskipJlamaITs -DskipLocalAiITs -DskipMilvusITs -DskipOllamaITs -DskipOracleITs -DskipVespaITs \
|
||||
--fail-at-end \
|
||||
-Djunit.jupiter.extensions.autodetection.enabled=true \
|
||||
-DembeddingsSkipCache \
|
||||
-Dtinylog.writer.level=info
|
||||
|
||||
env:
|
||||
LC4J_GLOBAL_TEST_RETRY_ENABLED: true
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
ANTHROPIC_CACHING_BASE_URL: ${{ secrets.ANTHROPIC_CACHING_BASE_URL }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AZURE_OPENAI_AUDIO_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_AUDIO_DEPLOYMENT_NAME }}
|
||||
AZURE_OPENAI_AUDIO_ENDPOINT: ${{ secrets.AZURE_OPENAI_AUDIO_ENDPOINT }}
|
||||
AZURE_OPENAI_AUDIO_KEY: ${{ secrets.AZURE_OPENAI_AUDIO_KEY }}
|
||||
AZURE_OPENAI_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_DEPLOYMENT_NAME }}
|
||||
AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }}
|
||||
AZURE_OPENAI_KEY: ${{ secrets.AZURE_OPENAI_KEY }}
|
||||
AZURE_SEARCH_ENDPOINT: ${{ secrets.AZURE_SEARCH_ENDPOINT }}
|
||||
AZURE_SEARCH_KEY: ${{ secrets.AZURE_SEARCH_KEY }}
|
||||
COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
|
||||
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
|
||||
ELASTICSEARCH_CLOUD_API_KEY: ${{ secrets.ELASTICSEARCH_CLOUD_API_KEY }}
|
||||
ELASTICSEARCH_CLOUD_URL: ${{ secrets.ELASTICSEARCH_CLOUD_URL }}
|
||||
GCP_CREDENTIALS_JSON: ${{ secrets.GCP_CREDENTIALS_JSON }}
|
||||
GCP_LOCATION: ${{ secrets.GCP_LOCATION }}
|
||||
GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
|
||||
GCP_PROJECT_NUM: ${{ secrets.GCP_PROJECT_NUM }}
|
||||
GCP_VERTEXAI_ENDPOINT: ${{ secrets.GCP_VERTEXAI_ENDPOINT }}
|
||||
GOOGLE_AI_GEMINI_API_KEY: ${{ secrets.GOOGLE_AI_GEMINI_API_KEY }}
|
||||
HF_API_KEY: ${{ secrets.HF_API_KEY }}
|
||||
JINA_API_KEY: ${{ secrets.JINA_API_KEY }}
|
||||
MICROSOFT_FOUNDRY_API_KEY: ${{ secrets.MICROSOFT_FOUNDRY_API_KEY }}
|
||||
MICROSOFT_FOUNDRY_ENDPOINT: ${{ secrets.MICROSOFT_FOUNDRY_ENDPOINT }}
|
||||
MILVUS_API_KEY: ${{ secrets.MILVUS_API_KEY }}
|
||||
MILVUS_URI: ${{ secrets.MILVUS_URI }}
|
||||
MISTRAL_AI_API_KEY: ${{ secrets.MISTRAL_AI_API_KEY }}
|
||||
NOMIC_API_KEY: ${{ secrets.NOMIC_API_KEY }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
||||
OVHAI_AI_API_KEY: ${{ secrets.OVHAI_AI_API_KEY }}
|
||||
PINECONE_API_KEY: ${{ secrets.PINECONE_API_KEY }}
|
||||
RAPID_API_KEY: ${{ secrets.RAPID_API_KEY }}
|
||||
TAVILY_API_KEY: ${{ secrets.TAVILY_API_KEY }}
|
||||
VOYAGE_API_KEY: ${{ secrets.VOYAGE_API_KEY }}
|
||||
WEAVIATE_API_KEY: ${{ secrets.WEAVIATE_API_KEY }}
|
||||
WEAVIATE_HOST: ${{ secrets.WEAVIATE_HOST }}
|
||||
CI_DELAY_SECONDS_AZURE_AI_SEARCH: 4
|
||||
CI_DELAY_SECONDS_AZURE_OPENAI: 3
|
||||
CI_DELAY_SECONDS_BEDROCK: 3
|
||||
CI_DELAY_SECONDS_GITHUB_MODELS: 7
|
||||
CI_DELAY_SECONDS_GOOGLE_AI_GEMINI: 20
|
||||
CI_DELAY_SECONDS_GOOGLE_AI_GEMINI_BATCH: 20
|
||||
CI_DELAY_SECONDS_VERTEX_AI: 5
|
||||
CI_DELAY_SECONDS_VERTEX_AI_ANTHROPIC: 12
|
||||
CI_DELAY_SECONDS_VERTEX_AI_GEMINI: 12
|
||||
CI_DELAY_SECONDS_VOYAGE_AI: 22
|
||||
|
||||
- name: Clean Docker
|
||||
run: docker system prune -af || true
|
||||
|
||||
- name: Publish Test Summary
|
||||
if: always()
|
||||
run: |
|
||||
python3 << 'PYEOF'
|
||||
import xml.etree.ElementTree as ET
|
||||
import glob, os, re
|
||||
|
||||
files = glob.glob('**/target/*-reports/TEST-*.xml', recursive=True)
|
||||
total = failures = errors = skipped = 0
|
||||
failed_tests = []
|
||||
|
||||
for path in files:
|
||||
try:
|
||||
root = ET.parse(path).getroot()
|
||||
total += int(root.get('tests', 0))
|
||||
failures += int(root.get('failures', 0))
|
||||
errors += int(root.get('errors', 0))
|
||||
skipped += int(root.get('skipped', 0))
|
||||
for tc in root.findall('.//testcase'):
|
||||
fail = tc.find('failure')
|
||||
if fail is None:
|
||||
fail = tc.find('error')
|
||||
if fail is not None:
|
||||
cls = tc.get('classname', '')
|
||||
name = tc.get('name', '')
|
||||
trace = fail.text or ''
|
||||
m = re.search(r'\bat ' + re.escape(cls) + r'[.$][^\n(]*\(([^)]+\.java:\d+)\)', trace)
|
||||
loc = f'`{m.group(1)}`' if m else ''
|
||||
msg = (fail.get('message', '') or '').replace('\n', '<br>').replace('|', '\\|')
|
||||
if len(msg) > 500:
|
||||
msg = f"{msg[:500]}<details><summary>show more</summary>{msg[500:]}</details>"
|
||||
failed_tests.append(f"| `{cls}` | `{name}` | {loc} | {msg} |")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
passed = total - failures - errors - skipped
|
||||
summary = os.environ.get('GITHUB_STEP_SUMMARY', '/dev/null')
|
||||
with open(summary, 'a') as f:
|
||||
f.write('## Test Results\n\n')
|
||||
if failures == 0 and errors == 0:
|
||||
f.write(f':white_check_mark: **{passed} passed**, {skipped} skipped out of {total} tests\n')
|
||||
else:
|
||||
f.write(f':x: **{failures} failed, {errors} errors**, {passed} passed, {skipped} skipped out of {total} tests\n')
|
||||
if failed_tests:
|
||||
f.write('\n### Failures\n\n')
|
||||
f.write('| Class | Test | Location | Error Message |\n')
|
||||
f.write('|-------|------|----------|---------------|\n')
|
||||
for line in failed_tests[:50]:
|
||||
f.write(line + '\n')
|
||||
if len(failed_tests) > 50:
|
||||
f.write(f'\n*... and {len(failed_tests) - 50} more*\n')
|
||||
PYEOF
|
||||
|
||||
- name: Upload Test Reports
|
||||
if: always() # always run even if the previous step failed or was cancelled
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: Test-Reports-${{ strategy.job-index }}
|
||||
path: '**/target/*-reports/*'
|
||||
|
||||
- name: Upload JaCoCo Reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: jacoco-${{ strategy.job-index }}
|
||||
path: '**/target/site/jacoco/**'
|
||||
|
||||
- name: Upload JaCoCo Execution Data
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: jacoco-exec-${{ strategy.job-index }}
|
||||
path: '**/target/jacoco.exec'
|
||||
@@ -0,0 +1,121 @@
|
||||
name: Nightly Build Oracle
|
||||
|
||||
# Oracle module is tested separately because it requires a lot of disk space
|
||||
# and does not fit in one job together with other modules
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 6 * * *' # daily at 06:00 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository == 'langchain4j/langchain4j'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: 'temurin'
|
||||
cache: 'maven'
|
||||
|
||||
- name: Build with JDK 17
|
||||
run: |
|
||||
## compile and install ALL modules to avoid running integration tests on dependent modules in the step below
|
||||
mvn -B -U -T8C -DskipTests -DskipITs -DembeddingsSkipCache install
|
||||
|
||||
mvn -B -U verify \
|
||||
-pl langchain4j-oracle \
|
||||
--fail-at-end \
|
||||
-Djunit.jupiter.extensions.autodetection.enabled=true \
|
||||
-DembeddingsSkipCache \
|
||||
-Dtinylog.writer.level=info
|
||||
|
||||
env:
|
||||
LC4J_GLOBAL_TEST_RETRY_ENABLED: true
|
||||
|
||||
- name: Clean Docker
|
||||
run: docker system prune -af || true
|
||||
|
||||
- name: Publish Test Summary
|
||||
if: always()
|
||||
run: |
|
||||
python3 << 'PYEOF'
|
||||
import xml.etree.ElementTree as ET
|
||||
import glob, os, re
|
||||
|
||||
files = glob.glob('**/target/*-reports/TEST-*.xml', recursive=True)
|
||||
total = failures = errors = skipped = 0
|
||||
failed_tests = []
|
||||
|
||||
for path in files:
|
||||
try:
|
||||
root = ET.parse(path).getroot()
|
||||
total += int(root.get('tests', 0))
|
||||
failures += int(root.get('failures', 0))
|
||||
errors += int(root.get('errors', 0))
|
||||
skipped += int(root.get('skipped', 0))
|
||||
for tc in root.findall('.//testcase'):
|
||||
fail = tc.find('failure')
|
||||
if fail is None:
|
||||
fail = tc.find('error')
|
||||
if fail is not None:
|
||||
cls = tc.get('classname', '')
|
||||
name = tc.get('name', '')
|
||||
trace = fail.text or ''
|
||||
m = re.search(r'\bat ' + re.escape(cls) + r'[.$][^\n(]*\(([^)]+\.java:\d+)\)', trace)
|
||||
loc = f'`{m.group(1)}`' if m else ''
|
||||
msg = (fail.get('message', '') or '').replace('\n', '<br>').replace('|', '\\|')
|
||||
if len(msg) > 500:
|
||||
msg = f"{msg[:500]}<details><summary>show more</summary>{msg[500:]}</details>"
|
||||
failed_tests.append(f"| `{cls}` | `{name}` | {loc} | {msg} |")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
passed = total - failures - errors - skipped
|
||||
summary = os.environ.get('GITHUB_STEP_SUMMARY', '/dev/null')
|
||||
with open(summary, 'a') as f:
|
||||
f.write('## Test Results\n\n')
|
||||
if failures == 0 and errors == 0:
|
||||
f.write(f':white_check_mark: **{passed} passed**, {skipped} skipped out of {total} tests\n')
|
||||
else:
|
||||
f.write(f':x: **{failures} failed, {errors} errors**, {passed} passed, {skipped} skipped out of {total} tests\n')
|
||||
if failed_tests:
|
||||
f.write('\n### Failures\n\n')
|
||||
f.write('| Class | Test | Location | Error Message |\n')
|
||||
f.write('|-------|------|----------|---------------|\n')
|
||||
for line in failed_tests[:50]:
|
||||
f.write(line + '\n')
|
||||
if len(failed_tests) > 50:
|
||||
f.write(f'\n*... and {len(failed_tests) - 50} more*\n')
|
||||
PYEOF
|
||||
|
||||
- name: Upload Test Reports
|
||||
if: always() # always run even if the previous step failed or was cancelled
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: Test-Reports-${{ strategy.job-index }}
|
||||
path: '**/target/*-reports/*'
|
||||
|
||||
- name: Upload JaCoCo Reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: jacoco-${{ strategy.job-index }}
|
||||
path: '**/target/site/jacoco/**'
|
||||
|
||||
- name: Upload JaCoCo Execution Data
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: jacoco-exec-${{ strategy.job-index }}
|
||||
path: '**/target/jacoco.exec'
|
||||
@@ -0,0 +1,190 @@
|
||||
name: release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
stableVersion:
|
||||
description: "Stable release version (e.g., 1.8.0)"
|
||||
required: true
|
||||
betaVersion:
|
||||
description: "Beta release version (e.g., 1.8.0-beta15)"
|
||||
required: true
|
||||
branch:
|
||||
description: "Branch to release from (e.g., 'main' for minor releases, 'release/x.y.z' for patch releases)"
|
||||
required: false
|
||||
default: "main"
|
||||
startFrom:
|
||||
description: "Phase to start from (use to resume after a failed release)"
|
||||
required: false
|
||||
default: "core-and-parent"
|
||||
type: choice
|
||||
options:
|
||||
- core-and-parent
|
||||
- in-process-embeddings
|
||||
- rest-of-modules
|
||||
- commit-and-tag
|
||||
|
||||
env:
|
||||
STABLE_VERSION: ${{ github.event.inputs.stableVersion || github.event.client_payload.stableVersion }}
|
||||
BETA_VERSION: ${{ github.event.inputs.betaVersion || github.event.client_payload.betaVersion }}
|
||||
BRANCH: ${{ github.event.inputs.branch || github.event.client_payload.branch || 'main' }}
|
||||
START_FROM: ${{ github.event.inputs.startFrom || github.event.client_payload.startFrom || 'core-and-parent' }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: release
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: ${{ env.BRANCH }}
|
||||
fetch-depth: 0
|
||||
# PAT is required to push the version commit and release tag to a protected branch
|
||||
token: ${{ secrets.GH_RELEASE_AUTOMATION }}
|
||||
|
||||
- name: Set up JDK 25
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
|
||||
with:
|
||||
java-version: '25'
|
||||
distribution: 'temurin'
|
||||
cache: maven
|
||||
server-id: central
|
||||
server-username: MAVEN_CENTRAL_USERNAME
|
||||
server-password: MAVEN_CENTRAL_PASSWORD
|
||||
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
gpg-passphrase: GPG_PASSPHRASE
|
||||
|
||||
- name: Show inputs
|
||||
run: |
|
||||
echo "Stable version: $STABLE_VERSION"
|
||||
echo "Beta version: $BETA_VERSION"
|
||||
echo "Branch to release from: $BRANCH"
|
||||
echo "Start from phase: $START_FROM"
|
||||
|
||||
- name: Compute start phase number
|
||||
id: phase
|
||||
run: |
|
||||
case "$START_FROM" in
|
||||
core-and-parent) echo "num=1" >> "$GITHUB_OUTPUT" ;;
|
||||
in-process-embeddings) echo "num=2" >> "$GITHUB_OUTPUT" ;;
|
||||
rest-of-modules) echo "num=3" >> "$GITHUB_OUTPUT" ;;
|
||||
commit-and-tag) echo "num=4" >> "$GITHUB_OUTPUT" ;;
|
||||
*) echo "Unknown startFrom: $START_FROM" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
- name: Extract current stable SNAPSHOT version from POM property
|
||||
id: extract-stable-snapshot-version
|
||||
run: |
|
||||
REVISION=$(mvn help:evaluate -Dexpression=langchain4j.stable.version -q -DforceStdout)
|
||||
echo "stableVersionSnapshot=$REVISION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Update stable versions
|
||||
run: mvn versions:set -DnewVersion="$STABLE_VERSION" -DoldVersion=${{ steps.extract-stable-snapshot-version.outputs.stableVersionSnapshot }} -DgroupId=* -DartifactId=* -DgenerateBackupPoms=false
|
||||
|
||||
- name: Update stable version property
|
||||
run: mvn versions:set-property -Dproperty=langchain4j.stable.version -DnewVersion="$STABLE_VERSION" -DgenerateBackupPoms=false
|
||||
|
||||
- name: Extract current beta SNAPSHOT version from POM property
|
||||
id: extract-beta-snapshot-version
|
||||
run: |
|
||||
REVISION=$(mvn help:evaluate -Dexpression=langchain4j.beta.version -q -DforceStdout)
|
||||
echo "betaVersionSnapshot=$REVISION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Update beta versions
|
||||
run: mvn versions:set -DnewVersion="$BETA_VERSION" -DoldVersion=${{ steps.extract-beta-snapshot-version.outputs.betaVersionSnapshot }} -DgroupId=* -DartifactId=* -DgenerateBackupPoms=false
|
||||
|
||||
- name: Update beta version property
|
||||
run: mvn versions:set-property -Dproperty=langchain4j.beta.version -DnewVersion="$BETA_VERSION" -DgenerateBackupPoms=false
|
||||
|
||||
- name: Release core and parent modules
|
||||
if: ${{ fromJSON(steps.phase.outputs.num) <= 1 }}
|
||||
run: mvn -B -U -pl internal/langchain4j-internal-test-retry,langchain4j-core,langchain4j-parent -Psign clean deploy
|
||||
env:
|
||||
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||
MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
|
||||
MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
|
||||
|
||||
- name: Release in-process embeddings modules
|
||||
if: ${{ fromJSON(steps.phase.outputs.num) <= 2 }}
|
||||
run: |
|
||||
EMBEDDING_MODULES=$(find embeddings -mindepth 1 -maxdepth 1 -type d -exec test -f "{}/pom.xml" \; -print | paste -sd , -)
|
||||
|
||||
mvn -B -U --fail-at-end \
|
||||
-DskipTests -DskipITs \
|
||||
-pl "$EMBEDDING_MODULES" \
|
||||
-Psign clean deploy
|
||||
env:
|
||||
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||
MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
|
||||
MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
|
||||
|
||||
- name: Release the rest of the modules
|
||||
if: ${{ fromJSON(steps.phase.outputs.num) <= 3 }}
|
||||
run: |
|
||||
EMBEDDING_MODULES=$(find embeddings -mindepth 1 -maxdepth 1 -type d -exec test -f "{}/pom.xml" \; -print | paste -sd , -)
|
||||
|
||||
EXCLUDES="!internal/langchain4j-internal-test-retry,!langchain4j-core,!langchain4j-parent"
|
||||
|
||||
# Append embeddings exclusions (converted to !path syntax)
|
||||
if [ -n "$EMBEDDING_MODULES" ]; then
|
||||
EMBEDDING_MODULES_EXCLUDES=$(echo "$EMBEDDING_MODULES" | sed 's/[^,]*/!\0/g')
|
||||
EXCLUDES="$EXCLUDES,$EMBEDDING_MODULES_EXCLUDES"
|
||||
fi
|
||||
|
||||
# Exclude integration-tests and all its submodules
|
||||
INTEGRATION_TEST_MODULES=$(find integration-tests -mindepth 1 -maxdepth 2 -type d -exec test -f "{}/pom.xml" \; -print | paste -sd , -)
|
||||
EXCLUDES="$EXCLUDES,!integration-tests"
|
||||
if [ -n "$INTEGRATION_TEST_MODULES" ]; then
|
||||
INTEGRATION_TEST_EXCLUDES=$(echo "$INTEGRATION_TEST_MODULES" | sed 's/[^,]*/!\0/g')
|
||||
EXCLUDES="$EXCLUDES,$INTEGRATION_TEST_EXCLUDES"
|
||||
fi
|
||||
|
||||
# Exclude internal modules
|
||||
EXCLUDES="$EXCLUDES,!internal/langchain4j-docu-chatbot-updater"
|
||||
|
||||
echo "Excluding modules: $EXCLUDES"
|
||||
|
||||
mvn -B -U --fail-at-end \
|
||||
-DskipTests -DskipITs \
|
||||
-pl "$EXCLUDES" \
|
||||
-Psign clean deploy
|
||||
env:
|
||||
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||
MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
|
||||
MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
|
||||
|
||||
- name: Commit version changes
|
||||
if: ${{ fromJSON(steps.phase.outputs.num) <= 4 }}
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add pom.xml '**/pom.xml'
|
||||
git commit -m "Release versions $STABLE_VERSION and $BETA_VERSION"
|
||||
|
||||
- name: Push
|
||||
if: ${{ fromJSON(steps.phase.outputs.num) <= 4 }}
|
||||
run: |
|
||||
git push origin "$BRANCH"
|
||||
|
||||
- name: Tag release
|
||||
if: ${{ fromJSON(steps.phase.outputs.num) <= 4 }}
|
||||
run: |
|
||||
git tag "$STABLE_VERSION"
|
||||
git push origin "$STABLE_VERSION"
|
||||
|
||||
- name: Trigger release in the langchain4j/langchain4j-spring repo
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_RELEASE_AUTOMATION }}
|
||||
run: |
|
||||
gh api repos/langchain4j/langchain4j-spring/dispatches \
|
||||
-f event_type=trigger-release \
|
||||
-f "client_payload[stableVersion]=$STABLE_VERSION" \
|
||||
-f "client_payload[betaVersion]=$BETA_VERSION" \
|
||||
-f "client_payload[branch]=$BRANCH"
|
||||
@@ -0,0 +1,49 @@
|
||||
name: snapshot_release
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [ trigger-snapshot-release ]
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: snapshot-release
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
snapshot_release:
|
||||
if: github.repository == 'langchain4j/langchain4j'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- name: Set up JDK 25
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
|
||||
with:
|
||||
# Modules specify the oldest java version that they support:
|
||||
# even if we build with version 25, produced jars should be compatible with
|
||||
# JVMs running the version specified in each module's maven.compiler.target property
|
||||
java-version: '25'
|
||||
distribution: 'temurin'
|
||||
server-id: central
|
||||
server-username: MAVEN_CENTRAL_USERNAME
|
||||
server-password: MAVEN_CENTRAL_PASSWORD
|
||||
|
||||
- name: snapshot_release
|
||||
env:
|
||||
MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
|
||||
MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
|
||||
run: |
|
||||
mvn -B -U --fail-at-end -DskipTests -DskipITs clean deploy
|
||||
|
||||
- name: Trigger SNAPSHOT release for langchain4j-spring repo
|
||||
if: ${{ github.event_name == 'repository_dispatch' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_RELEASE_AUTOMATION }}
|
||||
run: |
|
||||
gh api repos/langchain4j/langchain4j-spring/dispatches \
|
||||
-f event_type=trigger-snapshot-release
|
||||
@@ -0,0 +1,44 @@
|
||||
name: Split Package Detection
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master, 'release/**' ]
|
||||
pull_request:
|
||||
branches: [ main, master, 'release/**' ]
|
||||
# Optional: Run manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check-split-packages:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '25'
|
||||
|
||||
- name: Build with Maven
|
||||
run: mvn package -Dmaven.javadoc.skip=true -DskipTests -DskipITs -DembeddingsSkipDownload
|
||||
|
||||
- name: Make script executable
|
||||
run: chmod +x ./check-split-packages.sh
|
||||
|
||||
- name: Run split package detection
|
||||
run: ./check-split-packages.sh
|
||||
|
||||
- name: Upload results as artifact if failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: split-package-report
|
||||
path: |
|
||||
check-split-packages.sh
|
||||
# You could save the output to a file and include that too
|
||||
@@ -0,0 +1,104 @@
|
||||
name: Update versions for next dev iteration
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [ trigger-update-versions-next-dev-iteration ]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
nextStableVersion:
|
||||
description: "Next stable SNAPSHOT version (e.g., 1.9.0-SNAPSHOT)"
|
||||
required: true
|
||||
nextBetaVersion:
|
||||
description: "Next beta SNAPSHOT version (e.g., 1.9.0-beta16-SNAPSHOT)"
|
||||
required: true
|
||||
|
||||
env:
|
||||
NEXT_STABLE_VERSION: ${{ github.event.inputs.nextStableVersion || github.event.client_payload.nextStableVersion }}
|
||||
NEXT_BETA_VERSION: ${{ github.event.inputs.nextBetaVersion || github.event.client_payload.nextBetaVersion }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: update-versions
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
update_versions:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GH_RELEASE_AUTOMATION }}
|
||||
|
||||
- name: Set up JDK 25
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
|
||||
with:
|
||||
java-version: '25'
|
||||
distribution: 'temurin'
|
||||
cache: maven
|
||||
|
||||
- name: Show inputs
|
||||
run: |
|
||||
echo "Next stable version: $NEXT_STABLE_VERSION"
|
||||
echo "Next beta version: $NEXT_BETA_VERSION"
|
||||
|
||||
- name: Extract current stable version property
|
||||
id: extract-current-stable
|
||||
run: |
|
||||
REVISION=$(mvn help:evaluate -Dexpression=langchain4j.stable.version -q -DforceStdout)
|
||||
echo "currentStableVersion=$REVISION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Extract current beta version property
|
||||
id: extract-current-beta
|
||||
run: |
|
||||
REVISION=$(mvn help:evaluate -Dexpression=langchain4j.beta.version -q -DforceStdout)
|
||||
echo "currentBetaVersion=$REVISION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Update stable versions
|
||||
run: |
|
||||
mvn versions:set \
|
||||
-DnewVersion="$NEXT_STABLE_VERSION" \
|
||||
-DoldVersion=${{ steps.extract-current-stable.outputs.currentStableVersion }} \
|
||||
-DgroupId=* -DartifactId=* -DgenerateBackupPoms=false
|
||||
|
||||
- name: Update stable version property
|
||||
run: |
|
||||
mvn versions:set-property \
|
||||
-Dproperty=langchain4j.stable.version \
|
||||
-DnewVersion="$NEXT_STABLE_VERSION" \
|
||||
-DgenerateBackupPoms=false
|
||||
|
||||
- name: Update beta versions
|
||||
run: |
|
||||
mvn versions:set \
|
||||
-DnewVersion="$NEXT_BETA_VERSION" \
|
||||
-DoldVersion=${{ steps.extract-current-beta.outputs.currentBetaVersion }} \
|
||||
-DgroupId=* -DartifactId=* -DgenerateBackupPoms=false
|
||||
|
||||
- name: Update beta version property
|
||||
run: |
|
||||
mvn versions:set-property \
|
||||
-Dproperty=langchain4j.beta.version \
|
||||
-DnewVersion="$NEXT_BETA_VERSION" \
|
||||
-DgenerateBackupPoms=false
|
||||
|
||||
- name: Commit and push changes
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
git add pom.xml '**/pom.xml'
|
||||
git commit -m "Update versions to $NEXT_STABLE_VERSION and $NEXT_BETA_VERSION"
|
||||
git push origin main
|
||||
|
||||
- name: Trigger the same job in the langchain4j-spring repo
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_RELEASE_AUTOMATION }}
|
||||
run: |
|
||||
gh api repos/langchain4j/langchain4j-spring/dispatches \
|
||||
-f event_type=trigger-update-versions-next-dev-iteration \
|
||||
-f "client_payload[nextStableVersion]=$NEXT_STABLE_VERSION" \
|
||||
-f "client_payload[nextBetaVersion]=$NEXT_BETA_VERSION"
|
||||
@@ -0,0 +1,5 @@
|
||||
extends: default
|
||||
|
||||
rules:
|
||||
line-length: disable
|
||||
commas: disable
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
target/
|
||||
**/.flattened-pom.xml
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/*
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
|
||||
### .env files contain local environment variables ###
|
||||
.env
|
||||
langchain4j-core/target_test-classes/
|
||||
|
||||
## AI Coding Assistants ###
|
||||
CLAUDE.md
|
||||
.claude
|
||||
|
||||
### Quarkus stuff ###
|
||||
.quarkus
|
||||
|
||||
jdk/
|
||||
@@ -0,0 +1,3 @@
|
||||
--add-opens=java.base/java.lang=ALL-UNNAMED
|
||||
--add-opens=java.base/java.io=ALL-UNNAMED
|
||||
-Djava.awt.headless=true
|
||||
@@ -0,0 +1,3 @@
|
||||
-Dkotlin.compiler.incremental=true
|
||||
-Dmaven.wagon.http.retryHandler.count=5
|
||||
-Dmaven.wagon.http.retryHandler.requestSentEnabled=true
|
||||
Vendored
BIN
Binary file not shown.
+3
@@ -0,0 +1,3 @@
|
||||
wrapperVersion=3.3.4
|
||||
distributionType=only-script
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.15/apache-maven-3.9.15-bin.zip
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"tabWidth": 4,
|
||||
"printWidth": 120
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
info@langchain4j.dev.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
@@ -0,0 +1,65 @@
|
||||
Thank you for investing your time and effort in contributing to our project, we appreciate it a lot! 🤗
|
||||
|
||||
# General guidelines
|
||||
|
||||
- For new integrations, please consider adding it in [community repo](https://github.com/langchain4j/langchain4j-community) first.
|
||||
- If you want to contribute a bug fix or a new feature that isn't listed in the [issues](https://github.com/langchain4j/langchain4j/issues) yet, please open a new issue for it. We will triage it shortly.
|
||||
- Follow [Google's Best Practices for Java Libraries](https://jlbp.dev/)
|
||||
- Keep the code compatible with Java 17.
|
||||
- When integrating third-party services, use the official SDK whenever possible. If no official SDK is available, implement the client using `langchain4j-http-client` and Jackson.
|
||||
- Avoid adding new dependencies as much as possible (new dependencies with test scope are OK). If absolutely necessary, try to use the same libraries which are already used in the project. Make sure you run `mvn dependency:analyze` to identify unnecessary dependencies.
|
||||
- Write unit and/or integration tests for your code. This is critical: no tests, no review!
|
||||
- The tests should cover both positive and negative cases.
|
||||
- Make sure you run all unit tests on all modules with `mvn clean test`. Some integration tests need the API token (key) to be set up as an environment variable in order to communicate with the configured model provider (look for "EnabledIfEnvironmentVariable" annotation to find out the name of this token).
|
||||
- Avoid making breaking changes. Always keep backward compatibility in mind. For example, instead of removing fields/methods/etc, mark them `@Deprecated` and make sure they still work as before.
|
||||
- Follow existing naming conventions.
|
||||
- Add Javadoc where necessary. There's no need to duplicate Javadoc from the implemented interfaces.
|
||||
- Follow existing code style present in the project. Run `make lint` and `make format` before commit.
|
||||
- Large features should be discussed with maintainers before implementation.
|
||||
|
||||
# Opening an issue
|
||||
|
||||
- Please fill in all sections of the issue template.
|
||||
|
||||
# Opening a PR
|
||||
|
||||
- Please open the PR as ready for review, not as a draft.
|
||||
- Before opening the PR, please make sure it is complete:
|
||||
- Add unit and/or integration tests for your change (see the testing guidelines above). This is critical: no tests, no review!
|
||||
- Add [documentation](https://github.com/langchain4j/langchain4j/tree/main/docs/docs) (if required).
|
||||
- Run `./mvnw spotless:check` and `./mvnw spotless:apply` to ensure compliance with the source code formatting of the project.
|
||||
- Fill in all the sections of the PR template.
|
||||
- Please make it easier to review your PR:
|
||||
- Keep changes as small as possible.
|
||||
- Do not combine refactoring with changes in a single PR.
|
||||
- Avoid reformatting existing code.
|
||||
|
||||
Please note that we do not have the capacity to review PRs immediately. We ask for your patience. We are doing our best to review your PR as quickly as possible.
|
||||
|
||||
# Guidelines on adding a new model integration
|
||||
|
||||
- Please open PRs with new model integrations in the [langchain4j-community](https://github.com/langchain4j/langchain4j-community) repository
|
||||
- [Integration with OpenAI](https://github.com/langchain4j/langchain4j/tree/main/langchain4j-open-ai) is a good example.
|
||||
- Create integration test classes that extend from [`AbstractChatModelIT`](https://github.com/langchain4j/langchain4j/blob/main/langchain4j-core/src/test/java/dev/langchain4j/model/chat/common/AbstractChatModelIT.java), [`AbstractStreamingChatModelIT`](https://github.com/langchain4j/langchain4j/blob/main/langchain4j-core/src/test/java/dev/langchain4j/model/chat/common/AbstractStreamingChatModelIT.java), [`AbstractChatModelListenerIT`](https://github.com/langchain4j/langchain4j/blob/main/langchain4j-core/src/test/java/dev/langchain4j/model/chat/common/AbstractChatModelListenerIT.java), [`AbstractStreamingChatModelListenerIT`](https://github.com/langchain4j/langchain4j/blob/main/langchain4j-core/src/test/java/dev/langchain4j/model/chat/common/AbstractStreamingChatModelListenerIT.java) and [`AbstractStreamingAiServiceIT`](https://github.com/langchain4j/langchain4j/blob/main/langchain4j/src/test/java/dev/langchain4j/service/common/AbstractStreamingAiServiceIT.java). There are many examples in existing modules.
|
||||
- If the model provider supports embeddings, implement `EmbeddingModel` using the request/response API (`embed(EmbeddingRequest)`, `supportedParameters()`, `supportedContentTypes()`, `listeners()`), and create an integration test class that extends from [`AbstractEmbeddingModelIT`](https://github.com/langchain4j/langchain4j/blob/main/langchain4j-core/src/test/java/dev/langchain4j/model/embedding/common/AbstractEmbeddingModelIT.java). Override the `supports*()` methods to declare the model's capabilities (per-call parameters such as `input_type`/`dimensions`, image/multimodal inputs); the base test then verifies `embed(EmbeddingRequest)`, the convenience methods, listeners, and the fail-fast behavior for unsupported parameters/modalities. See the [OpenAI module](https://github.com/langchain4j/langchain4j/blob/main/langchain4j-open-ai/src/test/java/dev/langchain4j/model/openai/common/OpenAiEmbeddingModelIT.java) for an example.
|
||||
- If model provider supports tools, create an integration test class that extends from [`AbstractAiServiceWithToolsIT`](https://github.com/langchain4j/langchain4j/blob/main/langchain4j/src/test/java/dev/langchain4j/service/common/AbstractAiServiceWithToolsIT.java). There are many examples in existing modules.
|
||||
- If model provider supports structured outputs, create an integration test class that extends from [`AbstractAiServiceWithJsonSchemaIT`](https://github.com/langchain4j/langchain4j/blob/main/langchain4j/src/test/java/dev/langchain4j/service/common/AbstractAiServiceWithJsonSchemaIT.java). There are many examples in existing modules.
|
||||
- Document the new integration [here](https://github.com/langchain4j/langchain4j/blob/main/README.md), [here](https://github.com/langchain4j/langchain4j/tree/main/docs/docs/integrations/language-models) and [here](https://github.com/langchain4j/langchain4j/blob/main/docs/docs/integrations/language-models/index.md).
|
||||
- Add an example to the [examples repository](https://github.com/langchain4j/langchain4j-examples), similar to [this](https://github.com/langchain4j/langchain4j-examples/tree/main/anthropic-examples).
|
||||
- Add a new module to the appropriate section of the [BOM](https://github.com/langchain4j/langchain4j/blob/main/langchain4j-bom/pom.xml).
|
||||
- It would be great if you could add a [Spring Boot starter](https://github.com/langchain4j/langchain4j-spring).
|
||||
|
||||
# Guidelines on adding a new embedding store integration
|
||||
|
||||
- Please open PRs with new embedding store integrations in the [langchain4j-community](https://github.com/langchain4j/langchain4j-community) repository
|
||||
- [Integration with Chroma](https://github.com/langchain4j/langchain4j/tree/main/langchain4j-chroma) is a good example.
|
||||
- Add a `{IntegrationName}EmbeddingStoreIT`. It should extend from `EmbeddingStoreWithFilteringIT` (when store supports metadata filtering) or `EmbeddingStoreIT` and pass all tests.
|
||||
- Add a `{IntegrationName}EmbeddingStoreRemovalIT`. It should extend from `EmbeddingStoreWithRemovalIT` and pass all tests.
|
||||
- Document the new integration [here](https://github.com/langchain4j/langchain4j/blob/main/README.md), [here](https://github.com/langchain4j/langchain4j/tree/main/docs/docs/integrations/embedding-stores) and [here](https://github.com/langchain4j/langchain4j/blob/main/docs/docs/integrations/embedding-stores/index.md).
|
||||
- Add an example to the [examples repository](https://github.com/langchain4j/langchain4j-examples), similar to [this](https://github.com/langchain4j/langchain4j-examples/tree/main/chroma-example).
|
||||
- Add a new module to the appropriate section of the [BOM](https://github.com/langchain4j/langchain4j/blob/main/langchain4j-bom/pom.xml).
|
||||
- It would be great if you could add a [Spring Boot starter](https://github.com/langchain4j/langchain4j-spring). (after
|
||||
|
||||
# Guidelines on changing an existing embedding store integration
|
||||
|
||||
- Ensure that your changes are backwards compatible. `Embedding`s and `TextSegment`s persisted with the latest released version of LangChain4j should still work.
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,26 @@
|
||||
# LangChain4j Logo License
|
||||
|
||||
The LangChain4j logo (a cup of coffee with a parrot next to it) is a trademark of the LangChain4j project.
|
||||
|
||||
We want the community to freely use the logo in ways that support adoption,
|
||||
learning, and discussion of LangChain4j.
|
||||
|
||||
At the same time, we need to protect the project’s identity
|
||||
and avoid confusion about what is or isn’t officially part of LangChain4j.
|
||||
|
||||
## Permitted Uses
|
||||
|
||||
You may use the logo without additional permission in the following contexts:
|
||||
|
||||
- Books, tutorials, talks, videos, courses, and other educational content about LangChain4j.
|
||||
- Articles, blog posts, and presentations discussing or teaching LangChain4j.
|
||||
- Community events, meetups, and hackathons.
|
||||
- Informational graphics, blog headers, or social media posts referencing LangChain4j.
|
||||
|
||||
No attribution is required, though mentioning the LangChain4j project is appreciated.
|
||||
|
||||
## Requesting Exceptions
|
||||
|
||||
If you would like to use the logo in a way not covered above,
|
||||
please open an issue in the LangChain4j GitHub repository
|
||||
or contact the maintainers through community channels to request permission.
|
||||
@@ -0,0 +1,15 @@
|
||||
.PHONY: build lint format # always run
|
||||
|
||||
build:
|
||||
mvn --version
|
||||
mvn -U -T12C clean package
|
||||
|
||||
# Analyze code for errors, potential issues, and coding standard violations.
|
||||
# Reports problems but does not modify the code.
|
||||
lint:
|
||||
mvn -T12C -Pspotless spotless:check
|
||||
|
||||
# Automatically format the code to conform to a style guide.
|
||||
# Modifies the code to ensure consistent formatting.
|
||||
format:
|
||||
mvn -T12C -Pspotless spotless:apply
|
||||
@@ -0,0 +1,85 @@
|
||||
# LangChain4j: idiomatic, open-source Java library for building LLM-powered applications on the JVM
|
||||
|
||||
[](https://github.com/langchain4j/langchain4j/actions/workflows/main.yaml)
|
||||
[](https://github.com/langchain4j/langchain4j/actions/workflows/nightly_jdk17.yaml)
|
||||
[](https://app.codacy.com/gh/langchain4j/langchain4j/dashboard)
|
||||
|
||||
[](https://discord.gg/JzTFvyjG6R)
|
||||
[](https://bsky.app/profile/langchain4j.dev)
|
||||
[](https://x.com/langchain4j)
|
||||
[](https://search.maven.org/#search|gav|1|g:"dev.langchain4j"%20AND%20a:"langchain4j")
|
||||
|
||||
|
||||
## Introduction
|
||||
|
||||
Welcome!
|
||||
|
||||
The goal of LangChain4j is to simplify integrating LLMs into Java applications.
|
||||
|
||||
Here's how:
|
||||
1. **Unified APIs:**
|
||||
LLM providers (like OpenAI or Google Vertex AI) and embedding (vector) stores (such as Pinecone or Milvus)
|
||||
use proprietary APIs. LangChain4j offers a unified API to avoid the need for learning and implementing specific APIs for each of them.
|
||||
To experiment with different LLMs or embedding stores, you can easily switch between them without the need to rewrite your code.
|
||||
LangChain4j currently supports [20+ popular LLM providers](https://docs.langchain4j.dev/integrations/language-models/)
|
||||
and [30+ embedding stores](https://docs.langchain4j.dev/integrations/embedding-stores/).
|
||||
2. **Comprehensive Toolbox:**
|
||||
Since early 2023, the community has been building numerous LLM-powered applications,
|
||||
identifying common abstractions, patterns, and techniques. LangChain4j has refined these into practical code.
|
||||
Our toolbox includes tools ranging from low-level prompt templating, chat memory management, and function calling
|
||||
to high-level patterns like Agents and RAG.
|
||||
For each abstraction, we provide an interface along with multiple ready-to-use implementations based on common techniques.
|
||||
Whether you're building a chatbot or developing a RAG with a complete pipeline from data ingestion to retrieval,
|
||||
LangChain4j offers a wide variety of options.
|
||||
3. **Numerous Examples:**
|
||||
These [examples](https://github.com/langchain4j/langchain4j-examples) showcase how to begin creating various LLM-powered applications,
|
||||
providing inspiration and enabling you to start building quickly.
|
||||
|
||||
LangChain4j began development in early 2023 amid the ChatGPT hype.
|
||||
We noticed a lack of Java counterparts to the numerous Python and JavaScript LLM libraries and frameworks,
|
||||
and we had to fix that!
|
||||
|
||||
**Despite the name, LangChain4j is not a Java port of LangChain (Python) — it is built for Java, not ported to it.**
|
||||
It is an idiomatic Java library designed from the ground up around Java conventions:
|
||||
type safety, POJOs, annotations, interfaces, dependency injection, fluent APIs, and first-class integrations with Quarkus, Spring Boot, Helidon, and Micronaut.
|
||||
Its API, internals, and release cycle are independent of the Python LangChain project.
|
||||
|
||||
We actively monitor community developments, aiming to quickly incorporate new techniques and integrations,
|
||||
ensuring you stay up-to-date.
|
||||
The library is under active development. While some features are still being worked on,
|
||||
the core functionality is in place, allowing you to start building LLM-powered apps now!
|
||||
|
||||
|
||||
## Documentation
|
||||
Documentation can be found [here](https://docs.langchain4j.dev).
|
||||
|
||||
The documentation chatbot (experimental) can be found [here](https://chat.langchain4j.dev/).
|
||||
|
||||
|
||||
## Getting Started
|
||||
Getting started guide can be found [here](https://docs.langchain4j.dev/get-started).
|
||||
|
||||
|
||||
## Code Examples
|
||||
Please see examples of how LangChain4j can be used in [langchain4j-examples](https://github.com/langchain4j/langchain4j-examples) repo:
|
||||
- [Examples in plain Java](https://github.com/langchain4j/langchain4j-examples/tree/main/other-examples/src/main/java)
|
||||
- [Examples with Quarkus](https://github.com/quarkiverse/quarkus-langchain4j/tree/main/samples) (uses [quarkus-langchain4j](https://github.com/quarkiverse/quarkus-langchain4j) dependency)
|
||||
- [Example with Spring Boot](https://github.com/langchain4j/langchain4j-examples/tree/main/spring-boot-example/src/main/java/dev/langchain4j/example)
|
||||
- [Examples with Helidon](https://github.com/helidon-io/helidon-examples/tree/helidon-4.x/examples/integrations/langchain4j) (uses [io.helidon.integrations.langchain4j](https://mvnrepository.com/artifact/io.helidon.integrations.langchain4j) dependency)
|
||||
- [Examples with Micronaut](https://github.com/micronaut-projects/micronaut-langchain4j/tree/0.3.x/doc-examples/example-openai-java) (uses [micronaut-langchain4j](https://micronaut-projects.github.io/micronaut-langchain4j/latest/guide/) dependency)
|
||||
|
||||
## Useful Materials
|
||||
Useful materials can be found [here](https://docs.langchain4j.dev/useful-materials).
|
||||
|
||||
|
||||
## Get Help
|
||||
Please use [Discord](https://discord.gg/JzTFvyjG6R) or [GitHub discussions](https://github.com/langchain4j/langchain4j/discussions)
|
||||
to get help.
|
||||
|
||||
|
||||
## Request Features
|
||||
Please let us know what features you need by [opening an issue](https://github.com/langchain4j/langchain4j/issues/new/choose).
|
||||
|
||||
|
||||
## Contribute
|
||||
Contribution guidelines can be found [here](https://github.com/langchain4j/langchain4j/blob/main/CONTRIBUTING.md).
|
||||
@@ -0,0 +1,7 @@
|
||||
# WeHub 来源说明
|
||||
|
||||
- 原始项目:`langchain4j/langchain4j`
|
||||
- 原始仓库:https://github.com/langchain4j/langchain4j
|
||||
- 导入方式:上游默认分支的最新快照
|
||||
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
|
||||
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Only the latest stable version of LangChain4j is supported with security updates.
|
||||
|
||||
| Version | Supported |
|
||||
| --------- | ------------------ |
|
||||
| 1.0.x | :white_check_mark: |
|
||||
| < 1.0.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please do not report security issues to the public issue tracker.
|
||||
If you think you have found a security vulnerability, please send security issues to [info@langchain4j.dev](mailto:info@langchain4j.dev).
|
||||
We will do our best to get back to you as soon as possible.
|
||||
|
||||
## Public key used to sign LangChain4j releases
|
||||
|
||||
- **Fingerprint:** `2553 29E5 0411 A7BC 7238 74C0 CF11 8D1B B973 026E`
|
||||
- **Key ID:** `CF118D1BB973026E`
|
||||
- **User ID:** LangChain4j <info@langchain4j.dev>
|
||||
- **Keyserver:** https://keyserver.ubuntu.com/pks/lookup?search=CF118D1BB973026E&fingerprint=on&op=index
|
||||
|
||||
```
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mDMEaBSzjxYJKwYBBAHaRw8BAQdAM/2/Y9vimDqlD5rq6W9wv/3S3XJBxp3L7LmI
|
||||
rasKHOu0IkxhbmdDaGFpbjRqIDxpbmZvQGxhbmdjaGFpbjRqLmRldj6IkwQTFgoA
|
||||
OxYhBCVTKeUEEae8cjh0wM8RjRu5cwJuBQJoFLOPAhsDBQsJCAcCAiICBhUKCQgL
|
||||
AgQWAgMBAh4HAheAAAoJEM8RjRu5cwJu9ZAA/386qRNBrOmuyivyKKLnw9TLI39m
|
||||
03akxSOy1V8i/JuyAP4gbeyGjnTrXB2TD1/ZMz2LbCF+dAXGshi6mZZ9BkEfDbg4
|
||||
BGgUs48SCisGAQQBl1UBBQEBB0Ao2acTNRUOJ3o+zQs4gX5HSi3CuaHJQvxGuume
|
||||
W6wIfAMBCAeIeAQYFgoAIBYhBCVTKeUEEae8cjh0wM8RjRu5cwJuBQJoFLOPAhsM
|
||||
AAoJEM8RjRu5cwJugCAA+gO5uhtyLQTo+zEclAqQY7+dsJhpO3jf0mzj4n1OTx6d
|
||||
AQCd+f/ty5wml6ACroRZHYr2LQPjt/uqz5CW/3gTyl9mDg==
|
||||
=py+h
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
```
|
||||
Executable
+70
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
# Usage: ./check-split-packages.sh /path/to/your/root/dir
|
||||
ROOT_DIR="${1:-.}" # default to current dir if no argument passed
|
||||
|
||||
# Use process substitution instead of pipes to avoid subshell issues
|
||||
declare -A package_map
|
||||
declare -A seen
|
||||
declare -A last_conflict_jar # Track the last conflicting jar for each package
|
||||
|
||||
echo "🔍 Scanning all JARs under: $ROOT_DIR (excluding test JARs)"
|
||||
|
||||
# Get all JAR files first, excluding test JARs
|
||||
jar_files=()
|
||||
while IFS= read -r jar; do
|
||||
# Skip JAR files ending with "-tests.jar" and anything in the integration-tests directory
|
||||
if [[ "$jar" != *"-tests.jar" ]] && [[ "$jar" != "./integration-tests/"* ]]; then
|
||||
jar_files+=("$jar")
|
||||
fi
|
||||
done < <(find "$ROOT_DIR" -type f -name "*.jar")
|
||||
|
||||
echo "Found ${#jar_files[@]} non-test JAR files to analyze"
|
||||
|
||||
# Process each JAR file
|
||||
for jar in "${jar_files[@]}"; do
|
||||
jarname=$(realpath "$jar")
|
||||
jarbasename=$(basename "$jarname") # Get just the filename without path
|
||||
tmpdir=$(mktemp -d)
|
||||
unzip -qq "$jar" -d "$tmpdir"
|
||||
|
||||
# Get all class files
|
||||
class_files=()
|
||||
while IFS= read -r classfile; do
|
||||
class_files+=("$classfile")
|
||||
done < <(find "$tmpdir" -type f -name "*.class")
|
||||
|
||||
# Process each class file
|
||||
for classfile in "${class_files[@]}"; do
|
||||
pkg=$(dirname "${classfile#$tmpdir/}" | tr '/' '.')
|
||||
[[ "$pkg" == "." ]] && continue # skip default package
|
||||
|
||||
if [ -n "${package_map[$pkg]}" ]; then
|
||||
if [ "${package_map[$pkg]}" != "$jarbasename" ]; then
|
||||
if [[ -z "${seen[$pkg]}" ]]; then
|
||||
# First time seeing this conflict
|
||||
echo "🚨 Split package detected: $pkg"
|
||||
echo " ↳ in: ${package_map[$pkg]}"
|
||||
echo " ↳ and: $jarbasename"
|
||||
seen[$pkg]=1
|
||||
last_conflict_jar[$pkg]="$jarbasename"
|
||||
elif [[ "${last_conflict_jar[$pkg]}" != "$jarbasename" ]]; then
|
||||
# New jar with same conflict
|
||||
echo " ↳ also in: $jarbasename"
|
||||
last_conflict_jar[$pkg]="$jarbasename"
|
||||
fi
|
||||
# If it's the same jar as last time, we don't print anything
|
||||
fi
|
||||
else
|
||||
package_map[$pkg]=$jarbasename;
|
||||
fi
|
||||
done
|
||||
|
||||
rm -rf "$tmpdir"
|
||||
done
|
||||
|
||||
if [[ ${#seen[@]} -eq 0 ]]; then
|
||||
echo "✅ No split packages found!"
|
||||
else
|
||||
echo "❌ Split packages detected — please fix before modularizing."
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-parent</artifactId>
|
||||
<version>1.18.0-beta28-SNAPSHOT</version>
|
||||
<relativePath>../../langchain4j-parent/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>langchain4j-code-execution-engine-azure-acads</artifactId>
|
||||
<version>1.18.0-beta28-SNAPSHOT</version>
|
||||
<name>LangChain4j :: Integration :: Azure ACADS</name>
|
||||
<description>Implementation of a code execution engine using Azure ACA Dynamic Sessions</description>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-bom</artifactId>
|
||||
<version>4.1.132.Final</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna</artifactId>
|
||||
<version>5.17.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna-platform</artifactId>
|
||||
<version>5.17.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microsoft.azure</groupId>
|
||||
<artifactId>msal4j</artifactId>
|
||||
<version>1.23.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-core</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-http-client</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-http-client-jdk</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-identity</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+587
@@ -0,0 +1,587 @@
|
||||
package dev.langchain4j.code.azure.acads;
|
||||
|
||||
import static dev.langchain4j.http.client.HttpClientBuilderLoader.loadHttpClientBuilder;
|
||||
|
||||
import com.azure.core.credential.AccessToken;
|
||||
import com.azure.core.credential.TokenRequestContext;
|
||||
import com.azure.identity.DefaultAzureCredential;
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import dev.langchain4j.agent.tool.Tool;
|
||||
import dev.langchain4j.code.CodeExecutionEngine;
|
||||
import dev.langchain4j.http.client.HttpClient;
|
||||
import dev.langchain4j.http.client.HttpMethod;
|
||||
import dev.langchain4j.http.client.HttpRequest;
|
||||
import dev.langchain4j.http.client.SuccessfulHttpResponse;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.regex.Pattern;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A tool for executing code in Azure ACA dynamic sessions.
|
||||
*
|
||||
* Overview:
|
||||
* SessionsREPLTool provides a mechanism to execute code snippets within an Azure
|
||||
* Container Apps (ACA) dynamic session. It facilitates interaction with a remote code execution environment,
|
||||
* allowing for tasks such as performing calculations, running scripts, and managing files.
|
||||
* This class implements {@link CodeExecutionEngine}, making it compatible with the standard
|
||||
* code execution interfaces in langchain4j.
|
||||
*
|
||||
* Key Components:
|
||||
* USER_AGENT: A static string defining the User-Agent header for HTTP requests.
|
||||
* API_VERSION: A static string specifying the API version for interacting with the ACA dynamic sessions endpoint.
|
||||
* SANITIZE_PATTERN_START and SANITIZE_PATTERN_END: Static patterns used to sanitize input code by removing leading/trailing whitespace or keywords.
|
||||
* sanitizeInput: A boolean flag indicating whether to sanitize the input code before execution.
|
||||
* poolManagementEndpoint: The URL of the pool management endpoint for the ACA dynamic sessions.
|
||||
* sessionId: The unique identifier for the ACA dynamic session.
|
||||
* nativeHttpClient: A standard Java HttpClient instance for multipart form data operations.
|
||||
* langchainHttpClient: A langchain4j HttpClient abstraction for standard HTTP operations.
|
||||
* credential: A DefaultAzureCredential instance for authenticating with Azure services.
|
||||
* accessTokenRef: An AtomicReference to store and manage the access token.
|
||||
* FileUploader, FileDownloader, FileLister: Interfaces defining file management operations.
|
||||
* DefaultFileUploader, DefaultFileDownloader, DefaultFileLister: Implementations of the file management interfaces.
|
||||
* RemoteFileMetadata: A class representing metadata for remote files.
|
||||
*
|
||||
* Functionality:
|
||||
* The SessionsREPLTool class provides the following core functionalities:
|
||||
* Code Execution: Executes code snippets within the ACA dynamic session and returns the results.
|
||||
* - Implements {@link CodeExecutionEngine#execute(String)} for standardized execution API
|
||||
* - Provides a {@link Tool} annotated {@link #use(String)} method for integration with AI services
|
||||
* File Management: Allows uploading, downloading, and listing files within the session's environment.
|
||||
* Authentication: Handles authentication with Azure services using the DefaultAzureCredential.
|
||||
* Input Sanitization: Provides an option to sanitize input code for security and compatibility.
|
||||
*
|
||||
* Usage:
|
||||
* To use the SessionsREPLTool, you need to:
|
||||
* Create an instance of the class, providing the pool management endpoint.
|
||||
* Call the use() or execute() method to execute code, passing the code snippet as a string.
|
||||
* Use the file management interfaces and implementations to interact with files in the session.
|
||||
* Ensure proper error handling and resource management.
|
||||
* For testing, use the protected constructor that accepts pre-configured dependencies.
|
||||
*/
|
||||
public class SessionsREPLTool implements CodeExecutionEngine {
|
||||
|
||||
private static final String USER_AGENT = "langchain4j-azure-dynamic-sessions";
|
||||
private static final String API_VERSION = "2024-02-02-preview";
|
||||
private static final Logger log = LoggerFactory.getLogger(SessionsREPLTool.class);
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
private static final TypeReference<Map<String, Object>> MAP_TYPE_REF = new TypeReference<Map<String, Object>>() {};
|
||||
private static final TypeReference<List<Map<String, Object>>> LIST_MAP_TYPE_REF =
|
||||
new TypeReference<List<Map<String, Object>>>() {};
|
||||
|
||||
private static final Pattern SANITIZE_PATTERN_START = Pattern.compile("^(\\s|`)*(?i:python)?\\s*");
|
||||
private static final Pattern SANITIZE_PATTERN_END = Pattern.compile("(\\s|`)*$");
|
||||
|
||||
private final boolean sanitizeInput;
|
||||
private final String poolManagementEndpoint;
|
||||
private final String sessionId;
|
||||
private final java.net.http.HttpClient nativeHttpClient;
|
||||
private final HttpClient langchainHttpClient;
|
||||
private final DefaultAzureCredential credential;
|
||||
private final AtomicReference<AccessToken> accessTokenRef = new AtomicReference<>();
|
||||
|
||||
/**
|
||||
* Constructs a new SessionsREPLTool with the specified endpoint.
|
||||
*
|
||||
* @param poolManagementEndpoint the pool management endpoint URL
|
||||
*/
|
||||
public SessionsREPLTool(String poolManagementEndpoint) {
|
||||
this(poolManagementEndpoint, UUID.randomUUID().toString(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for uploading files to ACA.
|
||||
*/
|
||||
public interface FileUploader {
|
||||
/**
|
||||
* Uploads a local file to Azure Container Apps.
|
||||
*
|
||||
* @param localFilePath the path to the local file to upload
|
||||
* @return metadata about the uploaded file
|
||||
*/
|
||||
RemoteFileMetadata uploadFileToAca(Path localFilePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for downloading files from ACA.
|
||||
*/
|
||||
public interface FileDownloader {
|
||||
/**
|
||||
* Downloads a file from Azure Container Apps.
|
||||
*
|
||||
* @param remoteFilePath the path of the file to download
|
||||
* @return the content of the downloaded file
|
||||
*/
|
||||
String downloadFile(String remoteFilePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for listing files in ACA.
|
||||
*/
|
||||
public interface FileLister {
|
||||
/**
|
||||
* Lists all files in the Azure Container Apps session.
|
||||
*
|
||||
* @return a string representation of the file listing
|
||||
*/
|
||||
String listFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of CodeExecutionEngine.execute method
|
||||
* @param code The code to execute.
|
||||
* @return The result of the execution as a String in JSON format.
|
||||
*/
|
||||
@Override
|
||||
public String execute(String code) {
|
||||
return use(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new SessionsREPLTool with specified parameters.
|
||||
*
|
||||
* @param poolManagementEndpoint the pool management endpoint URL
|
||||
* @param sessionId the session ID
|
||||
* @param sanitizeInput whether to sanitize the input code
|
||||
*/
|
||||
public SessionsREPLTool(String poolManagementEndpoint, String sessionId, boolean sanitizeInput) {
|
||||
this.poolManagementEndpoint = poolManagementEndpoint;
|
||||
this.sessionId = sessionId;
|
||||
this.sanitizeInput = sanitizeInput;
|
||||
this.nativeHttpClient = java.net.http.HttpClient.newBuilder().build();
|
||||
this.langchainHttpClient = loadHttpClientBuilder().build();
|
||||
this.credential = new DefaultAzureCredentialBuilder().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Protected constructor for testing purposes that allows injecting dependencies.
|
||||
*
|
||||
* @param poolManagementEndpoint the pool management endpoint URL
|
||||
* @param sessionId the session ID
|
||||
* @param sanitizeInput whether to sanitize the input code
|
||||
* @param httpClient a pre-configured HTTP client
|
||||
* @param credential a pre-configured credential
|
||||
*/
|
||||
protected SessionsREPLTool(
|
||||
String poolManagementEndpoint,
|
||||
String sessionId,
|
||||
boolean sanitizeInput,
|
||||
HttpClient httpClient,
|
||||
DefaultAzureCredential credential) {
|
||||
this.poolManagementEndpoint = poolManagementEndpoint;
|
||||
this.sessionId = sessionId;
|
||||
this.sanitizeInput = sanitizeInput;
|
||||
this.nativeHttpClient = java.net.http.HttpClient.newBuilder().build();
|
||||
this.langchainHttpClient = httpClient;
|
||||
this.credential = credential;
|
||||
}
|
||||
|
||||
// Method is now a no-op as langchain4j HTTP client handles resource cleanup internally
|
||||
// public void shutdown() {
|
||||
// No manual cleanup needed for the HTTP client
|
||||
// }
|
||||
|
||||
/**
|
||||
* Executes the input code and returns the result.
|
||||
*
|
||||
* @param input the code or query to execute
|
||||
* @return the execution result as a JSON string
|
||||
*/
|
||||
@Tool(name = "sessions_REPL")
|
||||
public String use(String input) {
|
||||
Map<String, Object> response = executeCode(input);
|
||||
|
||||
Object result = response.get("result");
|
||||
if (result instanceof Map<?, ?>) {
|
||||
Map<?, ?> resultMap = (Map<?, ?>) result;
|
||||
if ("image".equals(resultMap.get("type")) && resultMap.containsKey("base64_data")) {
|
||||
resultMap.remove("base64_data");
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> contentMap = new HashMap<>();
|
||||
contentMap.put("result", result);
|
||||
contentMap.put("stdout", response.get("stdout"));
|
||||
contentMap.put("stderr", response.get("stderr"));
|
||||
|
||||
try {
|
||||
return OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(contentMap);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to serialize response.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the tool.
|
||||
*
|
||||
* @return an array containing the tool name
|
||||
*/
|
||||
public String[] value() {
|
||||
return new String[] {"REPL Tool"};
|
||||
}
|
||||
|
||||
private String getAccessToken() {
|
||||
AccessToken token = accessTokenRef.get();
|
||||
if (token == null || token.isExpired()) {
|
||||
try {
|
||||
TokenRequestContext context =
|
||||
new TokenRequestContext().addScopes("https://dynamicsessions.io/.default");
|
||||
token = credential.getToken(context).block();
|
||||
if (token != null) {
|
||||
accessTokenRef.set(token);
|
||||
} else {
|
||||
throw new RuntimeException("Failed to acquire access token.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to acquire access token.", e);
|
||||
}
|
||||
}
|
||||
return token.getToken();
|
||||
}
|
||||
|
||||
private String buildUrl(String path) {
|
||||
String endpoint = poolManagementEndpoint;
|
||||
if (!endpoint.endsWith("/")) {
|
||||
endpoint += "/";
|
||||
}
|
||||
String encodedSessionId = URLEncoder.encode(sessionId, StandardCharsets.UTF_8);
|
||||
String query = "identifier=" + encodedSessionId + "&api-version=" + API_VERSION;
|
||||
return endpoint + path + "?" + query;
|
||||
}
|
||||
|
||||
private String sanitizeInput(String input) {
|
||||
input = SANITIZE_PATTERN_START.matcher(input).replaceAll("");
|
||||
input = SANITIZE_PATTERN_END.matcher(input).replaceAll("");
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a query to the code interpreter.
|
||||
*
|
||||
* @param sessionCode the code or query to execute
|
||||
* @return a map containing the execution results
|
||||
*/
|
||||
public Map<String, Object> executeCode(String sessionCode) {
|
||||
if (sanitizeInput) {
|
||||
sessionCode = sanitizeInput(sessionCode);
|
||||
}
|
||||
|
||||
String accessToken = getAccessToken();
|
||||
String apiUrl = buildUrl("code/execute");
|
||||
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put("codeInputType", "inline");
|
||||
properties.put("executionType", "synchronous");
|
||||
properties.put("code", sessionCode);
|
||||
body.put("properties", properties);
|
||||
|
||||
try {
|
||||
String requestBody = OBJECT_MAPPER.writeValueAsString(body);
|
||||
|
||||
HttpRequest request = HttpRequest.builder()
|
||||
.method(HttpMethod.POST)
|
||||
.url(apiUrl)
|
||||
.addHeader("Authorization", "Bearer " + accessToken)
|
||||
.addHeader("Content-Type", "application/json")
|
||||
.addHeader("User-Agent", USER_AGENT)
|
||||
.body(requestBody)
|
||||
.build();
|
||||
|
||||
SuccessfulHttpResponse response = langchainHttpClient.execute(request);
|
||||
|
||||
if (response.statusCode() >= 200 && response.statusCode() < 300) {
|
||||
Map<String, Object> responseJson = OBJECT_MAPPER.readValue(response.body(), MAP_TYPE_REF);
|
||||
return getNestedMapProperty(responseJson, "properties");
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"Request failed with status code " + response.statusCode() + ": " + response.body());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to execute code.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of the FileUploader interface that uploads files to Azure Container Apps.
|
||||
*/
|
||||
public class DefaultFileUploader implements FileUploader {
|
||||
|
||||
/**
|
||||
* Constructs a new DefaultFileUploader.
|
||||
* Uses the parent SessionsREPLTool's configuration for authentication and endpoint access.
|
||||
*/
|
||||
public DefaultFileUploader() {
|
||||
// Default constructor - uses parent SessionsREPLTool's configuration
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoteFileMetadata uploadFileToAca(Path localFilePath) {
|
||||
String remoteFilePath = localFilePath.getFileName().toString();
|
||||
log.debug("Found file at local path: " + remoteFilePath);
|
||||
String accessToken = getAccessToken();
|
||||
String apiUrl = buildUrl("files/upload");
|
||||
log.debug("Uploading: API URL:" + apiUrl);
|
||||
|
||||
File file = localFilePath.toFile(); // Convert Path to File
|
||||
|
||||
try {
|
||||
// Prepare multipart form data
|
||||
|
||||
String boundary = "----" + System.currentTimeMillis();
|
||||
String contentDisposition =
|
||||
"Content-Disposition: form-data; name=\"file\"; filename=\"" + file.getName() + "\"";
|
||||
|
||||
byte[] fileBytes = Files.readAllBytes(localFilePath);
|
||||
String separator = "--" + boundary + "\r\n";
|
||||
String contentType = "Content-Type: application/json\r\n\r\n";
|
||||
String end = "\r\n--" + boundary + "--\r\n";
|
||||
|
||||
byte[] formDataBytes =
|
||||
(separator + contentDisposition + "\r\n" + contentType).getBytes(StandardCharsets.UTF_8);
|
||||
byte[] endBytes = end.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
outputStream.write(formDataBytes);
|
||||
outputStream.write(fileBytes);
|
||||
outputStream.write(endBytes);
|
||||
byte[] requestBody = outputStream.toByteArray();
|
||||
|
||||
java.net.http.HttpRequest request = java.net.http.HttpRequest.newBuilder()
|
||||
.uri(URI.create(apiUrl))
|
||||
.header("Authorization", "Bearer " + accessToken)
|
||||
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
|
||||
.POST(java.net.http.HttpRequest.BodyPublishers.ofByteArray(requestBody))
|
||||
.build();
|
||||
|
||||
java.net.http.HttpResponse<String> response =
|
||||
nativeHttpClient.send(request, java.net.http.HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if (response.statusCode() < 200 || response.statusCode() >= 300) {
|
||||
throw new IOException("Unexpected code " + response);
|
||||
}
|
||||
|
||||
Map<String, Object> responseJson = OBJECT_MAPPER.readValue(response.body(), MAP_TYPE_REF);
|
||||
List<Map<String, Object>> valueList =
|
||||
OBJECT_MAPPER.convertValue(responseJson.get("value"), LIST_MAP_TYPE_REF);
|
||||
Map<String, Object> fileMetadataMap = valueList.get(0);
|
||||
return RemoteFileMetadata.fromDict(fileMetadataMap);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to upload file: " + e.getMessage() + " API URL: " + apiUrl, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of the FileDownloader interface that downloads files from Azure Container Apps.
|
||||
*/
|
||||
public class DefaultFileDownloader implements FileDownloader {
|
||||
|
||||
/**
|
||||
* Constructs a new DefaultFileDownloader.
|
||||
* Uses the parent SessionsREPLTool's configuration for authentication and endpoint access.
|
||||
*/
|
||||
public DefaultFileDownloader() {
|
||||
// Default constructor - uses parent SessionsREPLTool's configuration
|
||||
}
|
||||
|
||||
@Override
|
||||
public String downloadFile(String remoteFilePath) {
|
||||
String accessToken = getAccessToken();
|
||||
String apiUrl = buildUrl("files/content/" + remoteFilePath);
|
||||
log.debug("Downloading: API URL:" + apiUrl);
|
||||
|
||||
HttpRequest request = HttpRequest.builder()
|
||||
.method(HttpMethod.GET)
|
||||
.url(apiUrl)
|
||||
.addHeader("Authorization", "Bearer " + accessToken)
|
||||
.build();
|
||||
|
||||
try {
|
||||
SuccessfulHttpResponse response = langchainHttpClient.execute(request);
|
||||
|
||||
if (response.statusCode() == 404) {
|
||||
return "File not found: " + remoteFilePath;
|
||||
}
|
||||
|
||||
// Convert response body to Base64 from the canonical raw bytes.
|
||||
// Using response.body() would round-trip through a decoded String and corrupt
|
||||
// binary files (images, xlsx, etc.) whose bytes are not valid UTF-8.
|
||||
return Base64.getEncoder().encodeToString(response.bodyBytes());
|
||||
|
||||
} catch (Exception e) {
|
||||
if (e.getMessage().contains("404")) {
|
||||
return "File not found: " + remoteFilePath;
|
||||
}
|
||||
throw new RuntimeException("Failed to download file: " + e.getMessage() + " API URL: " + apiUrl, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of the FileLister interface that lists files in Azure Container Apps.
|
||||
*/
|
||||
public class DefaultFileLister implements FileLister {
|
||||
|
||||
/**
|
||||
* Constructs a new DefaultFileLister.
|
||||
* Uses the parent SessionsREPLTool's configuration for authentication and endpoint access.
|
||||
*/
|
||||
public DefaultFileLister() {
|
||||
// Default constructor - uses parent SessionsREPLTool's configuration
|
||||
}
|
||||
|
||||
@Override
|
||||
public String listFiles() {
|
||||
String accessToken = getAccessToken();
|
||||
String apiUrl = buildUrl("files");
|
||||
|
||||
HttpRequest request = HttpRequest.builder()
|
||||
.method(HttpMethod.GET)
|
||||
.url(apiUrl)
|
||||
.addHeader("Authorization", "Bearer " + accessToken)
|
||||
.build();
|
||||
|
||||
try {
|
||||
SuccessfulHttpResponse response = langchainHttpClient.execute(request);
|
||||
|
||||
// Parse the response body as JSON using Jackson
|
||||
JsonNode json = OBJECT_MAPPER.readTree(response.body());
|
||||
|
||||
// Create a StringBuilder to store the filenames
|
||||
StringBuilder filenames = new StringBuilder();
|
||||
|
||||
// Get the "value" array from the JSON object
|
||||
JsonNode valueArray = json.get("value");
|
||||
|
||||
// Check if the "value" array is empty
|
||||
if (valueArray.isEmpty()) {
|
||||
return "No files were found at " + apiUrl;
|
||||
}
|
||||
|
||||
// Loop through each object in the "value" array
|
||||
for (int i = 0; i < valueArray.size(); i++) {
|
||||
// Get the current object
|
||||
JsonNode currentObject = valueArray.get(i);
|
||||
|
||||
// Get the "properties" object from the current object
|
||||
JsonNode properties = currentObject.get("properties");
|
||||
|
||||
// Get the filename from the "properties" object
|
||||
String filename = properties.get("filename").asText();
|
||||
|
||||
// Append the filename to the StringBuilder
|
||||
filenames.append(filename);
|
||||
|
||||
// If this is not the last filename, append a comma and a space
|
||||
if (i < valueArray.size() - 1) {
|
||||
filenames.append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
// Return the string representation of the StringBuilder
|
||||
return filenames.toString();
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to list files: " + e.getMessage() + " API URL: " + apiUrl, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, Object> getNestedMapProperty(Map<String, Object> map, String key) {
|
||||
Object value = map.get(key);
|
||||
if (value instanceof Map) {
|
||||
// This is safe because we know the structure from Azure API
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> result = (Map<String, Object>) value;
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents metadata of a remote file.
|
||||
*/
|
||||
public static class RemoteFileMetadata {
|
||||
private final String filename;
|
||||
private final long sizeInBytes;
|
||||
|
||||
/**
|
||||
* Constructs a RemoteFileMetadata instance.
|
||||
*
|
||||
* @param filename the name of the file
|
||||
* @param sizeInBytes the size of the file in bytes
|
||||
*/
|
||||
public RemoteFileMetadata(String filename, long sizeInBytes) {
|
||||
this.filename = filename;
|
||||
this.sizeInBytes = sizeInBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the filename.
|
||||
*
|
||||
* @return the filename
|
||||
*/
|
||||
public String getFilename() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of the file in bytes.
|
||||
*
|
||||
* @return the size in bytes
|
||||
*/
|
||||
public long getSizeInBytes() {
|
||||
return sizeInBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full path of the file.
|
||||
*
|
||||
* @return the full file path
|
||||
*/
|
||||
public String getFullPath() {
|
||||
return "/mnt/data/" + filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a RemoteFileMetadata instance from a map.
|
||||
*
|
||||
* @param data the map containing file properties
|
||||
* @return a RemoteFileMetadata instance
|
||||
*/
|
||||
public static RemoteFileMetadata fromDict(Map<String, Object> data) {
|
||||
Object propertiesObj = data.get("properties");
|
||||
if (!(propertiesObj instanceof Map)) {
|
||||
throw new IllegalArgumentException("Data does not contain a valid properties map");
|
||||
}
|
||||
|
||||
// This cast is safe because we checked the type above
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> properties = (Map<String, Object>) propertiesObj;
|
||||
|
||||
String filename = (String) properties.get("filename");
|
||||
Number size = (Number) properties.get("size");
|
||||
return new RemoteFileMetadata(filename, size.longValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
package dev.langchain4j.code.azure.acads;
|
||||
|
||||
/**
|
||||
* Test Suite for SessionsREPLTool
|
||||
*
|
||||
* Testing Strategy:
|
||||
* -----------------
|
||||
* This test suite focuses on unit testing the SessionsREPLTool class which implements
|
||||
* the CodeExecutionEngine interface. The tests utilize a mock-based approach to isolate
|
||||
* the class under test from external dependencies like Azure services.
|
||||
*
|
||||
* Key test areas covered:
|
||||
* 1. Interface contract verification - Ensuring SessionsREPLTool correctly implements CodeExecutionEngine
|
||||
* 2. Input sanitization - Verifying code snippets are properly sanitized before execution
|
||||
* 3. HTTP request formation - Confirming requests to the Azure Container Apps service are properly formed
|
||||
* 4. Response handling - Testing that responses are correctly parsed and transformed
|
||||
*
|
||||
* Implementation approach:
|
||||
* - Uses a custom TestSessionsREPLTool subclass that allows dependency injection via reflection
|
||||
* - Mocks HTTP client responses to simulate Azure Container Apps service responses
|
||||
* - Uses ArgumentCaptor to inspect request details for verification
|
||||
*
|
||||
* This approach allows comprehensive testing of the SessionsREPLTool's functionality
|
||||
* without requiring actual Azure credentials or incurring costs from real service calls.
|
||||
*/
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import com.azure.core.credential.AccessToken;
|
||||
import com.azure.core.credential.TokenRequestContext;
|
||||
import com.azure.identity.DefaultAzureCredential;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import dev.langchain4j.code.CodeExecutionEngine;
|
||||
import dev.langchain4j.http.client.HttpClient;
|
||||
import dev.langchain4j.http.client.HttpRequest;
|
||||
import dev.langchain4j.http.client.SuccessfulHttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
class SessionsREPLToolTest {
|
||||
|
||||
private SessionsREPLTool sessionsREPLTool;
|
||||
private DefaultAzureCredential mockCredential;
|
||||
private HttpClient mockHttpClient;
|
||||
private static final String TEST_ENDPOINT = "https://test-endpoint.com";
|
||||
private static final String TEST_CODE = "print('Hello World')";
|
||||
private static final String TEST_TOKEN = "test-token";
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
// Create mocks
|
||||
mockCredential = mock(DefaultAzureCredential.class);
|
||||
mockHttpClient = mock(HttpClient.class);
|
||||
|
||||
// Set up the mock credential
|
||||
AccessToken accessToken =
|
||||
new AccessToken(TEST_TOKEN, OffsetDateTime.now().plusHours(1));
|
||||
when(mockCredential.getToken(any(TokenRequestContext.class))).thenReturn(Mono.just(accessToken));
|
||||
|
||||
// Create a custom SessionsREPLTool with mocked dependencies
|
||||
sessionsREPLTool = new TestSessionsREPLTool(TEST_ENDPOINT, mockCredential, mockHttpClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecuteMethodImplementsCodeExecutionEngineInterface() {
|
||||
// Verify that SessionsREPLTool implements CodeExecutionEngine
|
||||
assertThat(sessionsREPLTool).isInstanceOf(CodeExecutionEngine.class);
|
||||
|
||||
// Set up mock response for execute call
|
||||
setupMockHttpResponse();
|
||||
|
||||
// Execute code using the CodeExecutionEngine interface method
|
||||
String result = sessionsREPLTool.execute(TEST_CODE);
|
||||
|
||||
// Verify result contains expected JSON structure
|
||||
assertThat(result).contains("\"result\"");
|
||||
assertThat(result).contains("\"stdout\"");
|
||||
assertThat(result).contains("\"stderr\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUseMethodCallsExecuteCode() {
|
||||
// Set up mock response
|
||||
setupMockHttpResponse();
|
||||
|
||||
// Call the use method
|
||||
String result = sessionsREPLTool.use(TEST_CODE);
|
||||
|
||||
// Verify HTTP request was made
|
||||
verify(mockHttpClient).execute(any(HttpRequest.class));
|
||||
|
||||
// Verify result contains expected JSON structure
|
||||
assertThat(result).contains("\"result\"");
|
||||
assertThat(result).contains("\"stdout\"");
|
||||
assertThat(result).contains("\"stderr\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecuteCodeSanitizesInput() {
|
||||
// Set up mock response
|
||||
setupMockHttpResponse();
|
||||
|
||||
// Input with whitespace and python prefix
|
||||
String inputWithPrefix = "```python\nprint('Hello World')\n```";
|
||||
|
||||
// Execute code
|
||||
Map<String, Object> result = sessionsREPLTool.executeCode(inputWithPrefix);
|
||||
|
||||
// Capture HTTP request to verify sanitized input
|
||||
ArgumentCaptor<HttpRequest> requestCaptor = ArgumentCaptor.forClass(HttpRequest.class);
|
||||
verify(mockHttpClient).execute(requestCaptor.capture());
|
||||
|
||||
// Verify that the request body contains the sanitized code
|
||||
String requestBody = requestCaptor.getValue().body();
|
||||
assertThat(requestBody).contains("print('Hello World')");
|
||||
assertThat(requestBody).doesNotContain("```python");
|
||||
assertThat(requestBody).doesNotContain("```");
|
||||
|
||||
// Verify result structure
|
||||
assertThat(result).containsKey("result");
|
||||
assertThat(result).containsKey("stdout");
|
||||
assertThat(result).containsKey("stderr");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDownloadFilePreservesBinaryBytes() {
|
||||
// A blob with bytes that are not valid UTF-8 (a fake PNG-ish header).
|
||||
byte[] binaryBytes = new byte[] {(byte) 0xFF, (byte) 0xFE, (byte) 0x00, (byte) 0x01, (byte) 0xC0};
|
||||
|
||||
// Return a real SuccessfulHttpResponse so body()/bodyBytes() behave as in production.
|
||||
SuccessfulHttpResponse response = SuccessfulHttpResponse.builder()
|
||||
.statusCode(200)
|
||||
.body(binaryBytes)
|
||||
.headers(Collections.emptyMap())
|
||||
.build();
|
||||
when(mockHttpClient.execute(any(HttpRequest.class))).thenReturn(response);
|
||||
|
||||
SessionsREPLTool.DefaultFileDownloader downloader = sessionsREPLTool.new DefaultFileDownloader();
|
||||
|
||||
String result = downloader.downloadFile("some/file.png");
|
||||
|
||||
// Base64 must be computed from the canonical raw bytes, not a lossy String round-trip.
|
||||
assertThat(result).isEqualTo(Base64.getEncoder().encodeToString(binaryBytes));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDownloadFileRoundTripsValidUtf8Text() {
|
||||
byte[] textBytes = "hello, world".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
SuccessfulHttpResponse response = SuccessfulHttpResponse.builder()
|
||||
.statusCode(200)
|
||||
.body(textBytes)
|
||||
.headers(Collections.emptyMap())
|
||||
.build();
|
||||
when(mockHttpClient.execute(any(HttpRequest.class))).thenReturn(response);
|
||||
|
||||
SessionsREPLTool.DefaultFileDownloader downloader = sessionsREPLTool.new DefaultFileDownloader();
|
||||
|
||||
String result = downloader.downloadFile("notes.txt");
|
||||
|
||||
assertThat(result).isEqualTo(Base64.getEncoder().encodeToString(textBytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to set up mock HTTP response
|
||||
*/
|
||||
private void setupMockHttpResponse() {
|
||||
// Create mock response data
|
||||
Map<String, Object> responseProperties = new HashMap<>();
|
||||
responseProperties.put("result", "Hello World");
|
||||
responseProperties.put("stdout", "Hello World");
|
||||
responseProperties.put("stderr", "");
|
||||
|
||||
Map<String, Object> responseMap = new HashMap<>();
|
||||
responseMap.put("properties", responseProperties);
|
||||
|
||||
// Set up mock HTTP response
|
||||
SuccessfulHttpResponse mockResponse = mock(SuccessfulHttpResponse.class);
|
||||
when(mockResponse.statusCode()).thenReturn(200);
|
||||
when(mockResponse.body())
|
||||
.thenReturn(new ObjectMapper().valueToTree(responseMap).toString());
|
||||
when(mockResponse.headers()).thenReturn(Collections.emptyMap());
|
||||
|
||||
// Set up mock HTTP client
|
||||
when(mockHttpClient.execute(any(HttpRequest.class))).thenReturn(mockResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom SessionsREPLTool for testing that uses mocked dependencies
|
||||
*/
|
||||
private static class TestSessionsREPLTool extends SessionsREPLTool {
|
||||
public TestSessionsREPLTool(
|
||||
String poolManagementEndpoint, DefaultAzureCredential credential, HttpClient mockHttpClient) {
|
||||
// Use the protected constructor for testing that accepts pre-configured dependencies
|
||||
super(poolManagementEndpoint, "test-session-id", true, mockHttpClient, credential);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-parent</artifactId>
|
||||
<version>1.18.0-beta28-SNAPSHOT</version>
|
||||
<relativePath>../../langchain4j-parent/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>langchain4j-code-execution-engine-graalvm-polyglot</artifactId>
|
||||
<name>LangChain4j :: Integration :: GraalVM Polyglot/Truffle</name>
|
||||
<description>Implementation of JavaScript and Python code execution engines and tools using GraalVM Polyglot/Truffle</description>
|
||||
|
||||
<properties>
|
||||
<graalvm.version>24.1.1</graalvm.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-core</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.graalvm.polyglot</groupId>
|
||||
<artifactId>polyglot</artifactId>
|
||||
<version>${graalvm.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.graalvm.polyglot</groupId>
|
||||
<artifactId>js</artifactId>
|
||||
<version>${graalvm.version}</version>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.graalvm.polyglot</groupId>
|
||||
<artifactId>python</artifactId>
|
||||
<version>${graalvm.version}</version>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-open-ai</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.honton.chas</groupId>
|
||||
<artifactId>license-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<!-- org.graalvm.polyglot has a UPL-1 license -->
|
||||
<skipCompliance>true</skipCompliance>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package dev.langchain4j.agent.tool.graalvm;
|
||||
|
||||
import dev.langchain4j.agent.tool.P;
|
||||
import dev.langchain4j.agent.tool.Tool;
|
||||
import dev.langchain4j.code.CodeExecutionEngine;
|
||||
import dev.langchain4j.code.graalvm.GraalVmJavaScriptExecutionEngine;
|
||||
|
||||
/**
|
||||
* A tool that executes provided JavaScript code using GraalVM Polyglot/Truffle.
|
||||
* Attention! It might be dangerous to execute the code, see {@link GraalVmJavaScriptExecutionEngine} for more details.
|
||||
*/
|
||||
public class GraalVmJavaScriptExecutionTool {
|
||||
|
||||
private final CodeExecutionEngine engine = new GraalVmJavaScriptExecutionEngine();
|
||||
|
||||
@Tool("MUST be used for accurate calculations: math, sorting, filtering, aggregating, string processing, etc")
|
||||
public String executeJavaScriptCode(@P("JavaScript code to execute, result MUST be returned by the code") String code) {
|
||||
return engine.execute(code);
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package dev.langchain4j.agent.tool.graalvm;
|
||||
|
||||
import dev.langchain4j.agent.tool.P;
|
||||
import dev.langchain4j.agent.tool.Tool;
|
||||
import dev.langchain4j.code.CodeExecutionEngine;
|
||||
import dev.langchain4j.code.graalvm.GraalVmPythonExecutionEngine;
|
||||
|
||||
/**
|
||||
* A tool that executes provided Python code using GraalVM Polyglot/Truffle.
|
||||
* Attention! It might be dangerous to execute the code, see {@link GraalVmPythonExecutionEngine} for more details.
|
||||
*/
|
||||
public class GraalVmPythonExecutionTool {
|
||||
|
||||
private final CodeExecutionEngine engine = new GraalVmPythonExecutionEngine();
|
||||
|
||||
@Tool("MUST be used for accurate calculations: math, sorting, filtering, aggregating, string processing, etc")
|
||||
public String executePythonCode(@P("Python code to execute, result MUST be returned by the code") String code) {
|
||||
return engine.execute(code);
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package dev.langchain4j.code.graalvm;
|
||||
|
||||
import dev.langchain4j.code.CodeExecutionEngine;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.HostAccess;
|
||||
import org.graalvm.polyglot.SandboxPolicy;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import static org.graalvm.polyglot.HostAccess.UNTRUSTED;
|
||||
import static org.graalvm.polyglot.SandboxPolicy.CONSTRAINED;
|
||||
|
||||
/**
|
||||
* {@link CodeExecutionEngine} that uses GraalVM Polyglot/Truffle to execute provided JavaScript code.
|
||||
* Attention! It might be dangerous to execute the code, see {@link SandboxPolicy#CONSTRAINED}
|
||||
* and {@link HostAccess#UNTRUSTED} for more details.
|
||||
*/
|
||||
public class GraalVmJavaScriptExecutionEngine implements CodeExecutionEngine {
|
||||
|
||||
@Override
|
||||
public String execute(String code) {
|
||||
OutputStream outputStream = new ByteArrayOutputStream();
|
||||
try (Context context = Context.newBuilder("js")
|
||||
.sandbox(CONSTRAINED)
|
||||
.allowHostAccess(UNTRUSTED)
|
||||
.out(outputStream)
|
||||
.err(outputStream)
|
||||
.build()) {
|
||||
Object result = context.eval("js", code).as(Object.class);
|
||||
return String.valueOf(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package dev.langchain4j.code.graalvm;
|
||||
|
||||
import dev.langchain4j.code.CodeExecutionEngine;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.HostAccess;
|
||||
import org.graalvm.polyglot.SandboxPolicy;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import static org.graalvm.polyglot.HostAccess.UNTRUSTED;
|
||||
import static org.graalvm.polyglot.SandboxPolicy.TRUSTED;
|
||||
|
||||
/**
|
||||
* {@link CodeExecutionEngine} that uses GraalVM Polyglot/Truffle to execute provided Python code.
|
||||
* Attention! It might be dangerous to execute the code, see {@link SandboxPolicy#TRUSTED}
|
||||
* and {@link HostAccess#UNTRUSTED} for more details.
|
||||
*/
|
||||
public class GraalVmPythonExecutionEngine implements CodeExecutionEngine {
|
||||
|
||||
@Override
|
||||
public String execute(String code) {
|
||||
OutputStream outputStream = new ByteArrayOutputStream();
|
||||
try (Context context = Context.newBuilder("python")
|
||||
.sandbox(TRUSTED)
|
||||
.allowHostAccess(UNTRUSTED)
|
||||
.out(outputStream)
|
||||
.err(outputStream)
|
||||
.build()) {
|
||||
Object result = context.eval("python", code).as(Object.class);
|
||||
return String.valueOf(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package dev.langchain4j.agent.tool.graalvm;
|
||||
|
||||
import static dev.langchain4j.model.openai.OpenAiChatModelName.GPT_4_O_MINI;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
|
||||
import dev.langchain4j.model.openai.OpenAiChatModel;
|
||||
import dev.langchain4j.service.AiServices;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
|
||||
@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+")
|
||||
class GraalVmJavaScriptExecutionToolIT {
|
||||
|
||||
OpenAiChatModel model = OpenAiChatModel.builder()
|
||||
.baseUrl(System.getenv("OPENAI_BASE_URL"))
|
||||
.apiKey(System.getenv("OPENAI_API_KEY"))
|
||||
.organizationId(System.getenv("OPENAI_ORGANIZATION_ID"))
|
||||
.modelName(GPT_4_O_MINI)
|
||||
.logRequests(true)
|
||||
.logResponses(true)
|
||||
.build();
|
||||
|
||||
interface Assistant {
|
||||
|
||||
String chat(String userMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_execute_tool() {
|
||||
|
||||
GraalVmJavaScriptExecutionTool tool = spy(new GraalVmJavaScriptExecutionTool());
|
||||
|
||||
Assistant assistant = AiServices.builder(Assistant.class)
|
||||
.chatModel(model)
|
||||
.tools(tool)
|
||||
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
|
||||
.build();
|
||||
|
||||
String answer = assistant.chat("What is the square root of 485906798473894056 in scientific notation?");
|
||||
|
||||
assertThat(answer).contains("6.97");
|
||||
|
||||
verify(tool).executeJavaScriptCode(contains("485906798473894056"));
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package dev.langchain4j.agent.tool.graalvm;
|
||||
|
||||
import static dev.langchain4j.model.openai.OpenAiChatModelName.GPT_4_O_MINI;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
|
||||
import dev.langchain4j.model.openai.OpenAiChatModel;
|
||||
import dev.langchain4j.service.AiServices;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
|
||||
@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+")
|
||||
class GraalVmPythonExecutionToolIT {
|
||||
|
||||
OpenAiChatModel model = OpenAiChatModel.builder()
|
||||
.baseUrl(System.getenv("OPENAI_BASE_URL"))
|
||||
.apiKey(System.getenv("OPENAI_API_KEY"))
|
||||
.organizationId(System.getenv("OPENAI_ORGANIZATION_ID"))
|
||||
.modelName(GPT_4_O_MINI)
|
||||
.logRequests(true)
|
||||
.logResponses(true)
|
||||
.build();
|
||||
|
||||
interface Assistant {
|
||||
|
||||
String chat(String userMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_execute_tool() {
|
||||
|
||||
GraalVmPythonExecutionTool tool = spy(new GraalVmPythonExecutionTool());
|
||||
|
||||
Assistant assistant = AiServices.builder(Assistant.class)
|
||||
.chatModel(model)
|
||||
.tools(tool)
|
||||
.chatMemory(MessageWindowChatMemory.withMaxMessages(100))
|
||||
.build();
|
||||
|
||||
String answer = assistant.chat("What is the square root of 485906798473894056 in scientific notation?");
|
||||
|
||||
assertThat(answer).contains("6.97");
|
||||
|
||||
verify(tool).executePythonCode(contains("485906798473894056"));
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package dev.langchain4j.code.graalvm;
|
||||
|
||||
import dev.langchain4j.code.CodeExecutionEngine;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class GraalVmJavaScriptExecutionEngineTest {
|
||||
|
||||
CodeExecutionEngine engine = new GraalVmJavaScriptExecutionEngine();
|
||||
|
||||
@Test
|
||||
void should_execute_code() {
|
||||
|
||||
String code = """
|
||||
function fibonacci(n) {
|
||||
if (n <= 1) return n;
|
||||
return fibonacci(n - 1) + fibonacci(n - 2);
|
||||
}
|
||||
|
||||
fibonacci(10)
|
||||
""";
|
||||
|
||||
String result = engine.execute(code);
|
||||
|
||||
assertThat(result).isEqualTo("55");
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package dev.langchain4j.code.graalvm;
|
||||
|
||||
import dev.langchain4j.code.CodeExecutionEngine;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class GraalVmPythonExecutionEngineTest {
|
||||
|
||||
CodeExecutionEngine engine = new GraalVmPythonExecutionEngine();
|
||||
|
||||
@Test
|
||||
void should_execute_code() {
|
||||
|
||||
String code = """
|
||||
def fibonacci(n):
|
||||
if n <= 1:
|
||||
return n
|
||||
else:
|
||||
return fibonacci(n-1) + fibonacci(n-2)
|
||||
|
||||
fibonacci(10)
|
||||
""";
|
||||
|
||||
String result = engine.execute(code);
|
||||
|
||||
assertThat(result).isEqualTo("55");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-parent</artifactId>
|
||||
<version>1.18.0-beta28-SNAPSHOT</version>
|
||||
<relativePath>../../langchain4j-parent/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>langchain4j-code-execution-engine-judge0</artifactId>
|
||||
<name>LangChain4j :: Integration :: Judge0</name>
|
||||
<description>Implementation of JavaScript code execution engine and tool using Judge0</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-core</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- test dependencies -->
|
||||
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-open-ai</artifactId>
|
||||
<version>1.18.0-SNAPSHOT</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package dev.langchain4j.code.judge0;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
class JavaScriptCodeFixer {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(JavaScriptCodeFixer.class);
|
||||
|
||||
static String fixIfNoLogToConsole(String code) {
|
||||
if (code.contains("\n")) {
|
||||
return fixIfNoLogToConsole(code, "\n");
|
||||
}
|
||||
return fixSingleLineIfNoLogToConsole(code);
|
||||
}
|
||||
|
||||
private static String fixIfNoLogToConsole(String code, String separator) {
|
||||
String[] parts = code.split(separator);
|
||||
String lastPart = parts[parts.length - 1];
|
||||
if (lastPart.startsWith("console.log")) {
|
||||
return code;
|
||||
}
|
||||
|
||||
parts[parts.length - 1] = "console.log(" + lastPart.replace(";", "") + ");";
|
||||
String fixedCode = String.join(separator, parts);
|
||||
log.debug("The following code \"{}\" was fixed: \"{}\"", code, fixedCode);
|
||||
return fixedCode;
|
||||
}
|
||||
|
||||
private static String fixSingleLineIfNoLogToConsole(String code) {
|
||||
int lastSemicolon = code.lastIndexOf(';');
|
||||
String trailing = code.substring(lastSemicolon + 1);
|
||||
String statement = trailing.trim();
|
||||
if (statement.isEmpty() || statement.startsWith("console.log")) {
|
||||
return code;
|
||||
}
|
||||
|
||||
String prefix = code.substring(0, lastSemicolon + 1);
|
||||
String leadingWhitespace = trailing.substring(0, trailing.indexOf(statement));
|
||||
String fixedCode = prefix + leadingWhitespace + "console.log(" + statement + ");";
|
||||
log.debug("The following code \"{}\" was fixed: \"{}\"", code, fixedCode);
|
||||
return fixedCode;
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package dev.langchain4j.code.judge0;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
|
||||
|
||||
class Json {
|
||||
|
||||
static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
|
||||
.enable(INDENT_OUTPUT)
|
||||
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
|
||||
static String toJson(Object o) {
|
||||
try {
|
||||
return OBJECT_MAPPER.writeValueAsString(o);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
static <T> T fromJson(String json, Class<T> type) {
|
||||
try {
|
||||
return OBJECT_MAPPER.readValue(json, type);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+314
@@ -0,0 +1,314 @@
|
||||
package dev.langchain4j.code.judge0;
|
||||
|
||||
import static dev.langchain4j.internal.Utils.isNullOrBlank;
|
||||
|
||||
import dev.langchain4j.code.CodeExecutionEngine;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import okhttp3.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A JavaScript code execution engine that uses Judge0 API for code execution.
|
||||
* <p>
|
||||
* This class connects to the Judge0 API via RapidAPI to execute JavaScript code.
|
||||
*/
|
||||
class Judge0JavaScriptEngine implements CodeExecutionEngine {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Judge0JavaScriptEngine.class);
|
||||
|
||||
// HTTP Constants
|
||||
private static final MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json");
|
||||
private static final String RAPID_API_HOST = "judge0-ce.p.rapidapi.com";
|
||||
private static final String API_URL =
|
||||
"https://judge0-ce.p.rapidapi.com/submissions?base64_encoded=true&wait=true&fields=*";
|
||||
|
||||
// Judge0 status codes
|
||||
private static final int STATUS_ACCEPTED = 3;
|
||||
|
||||
// Error messages
|
||||
private static final String ERROR_RATE_LIMIT = "Rate limit exceeded. Please try again later.";
|
||||
private static final String ERROR_FORBIDDEN = "Access forbidden. Please check your API key.";
|
||||
private static final String ERROR_NOT_FOUND = "Resource not found. Please check the endpoint URL.";
|
||||
private static final String ERROR_SERVER = "Internal server error. Please try again later.";
|
||||
private static final String ERROR_UNAVAILABLE = "Service unavailable. Please try again later.";
|
||||
private static final String ERROR_NULL_RESPONSE = "Response body is null";
|
||||
private static final String ERROR_NO_STDOUT = "No result: nothing was printed out to the console";
|
||||
|
||||
// Retry settings
|
||||
private static final int MAX_RETRIES = 3;
|
||||
private static final long RETRY_DELAY_MS = 1000;
|
||||
|
||||
private final String apiKey;
|
||||
private final int languageId;
|
||||
private final OkHttpClient client;
|
||||
|
||||
/**
|
||||
* Creates a new Judge0JavaScriptEngine with the specified API key, language ID, and timeout.
|
||||
*
|
||||
* @param apiKey The RapidAPI key for Judge0
|
||||
* @param languageId The language ID for the Judge0 API (e.g., 102 for JavaScript)
|
||||
* @param timeout The timeout duration for HTTP requests
|
||||
* @throws IllegalArgumentException if apiKey is null or blank
|
||||
* @throws NullPointerException if timeout is null
|
||||
*/
|
||||
Judge0JavaScriptEngine(String apiKey, int languageId, Duration timeout) {
|
||||
if (isNullOrBlank(apiKey)) {
|
||||
throw new IllegalArgumentException("API key must not be null or blank");
|
||||
}
|
||||
Objects.requireNonNull(timeout, "Timeout must not be null");
|
||||
|
||||
this.apiKey = apiKey;
|
||||
this.languageId = languageId;
|
||||
this.client = new OkHttpClient.Builder()
|
||||
.connectTimeout(timeout)
|
||||
.readTimeout(timeout)
|
||||
.writeTimeout(timeout)
|
||||
.callTimeout(timeout)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the provided code using the Judge0 API.
|
||||
*
|
||||
* @param code The code to execute
|
||||
* @return The result of code execution or an error message
|
||||
*/
|
||||
@Override
|
||||
public String execute(String code) {
|
||||
if (isNullOrBlank(code)) {
|
||||
return "Error: Cannot execute empty or null code";
|
||||
}
|
||||
|
||||
String base64EncodedCode = Base64.getEncoder().encodeToString(code.getBytes(StandardCharsets.UTF_8));
|
||||
Submission submission = new Submission(languageId, base64EncodedCode);
|
||||
RequestBody requestBody = RequestBody.create(Json.toJson(submission), MEDIA_TYPE_JSON);
|
||||
Request request = buildRequest(requestBody);
|
||||
|
||||
return executeWithRetry(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a request to the Judge0 API.
|
||||
*
|
||||
* @param requestBody The request body
|
||||
* @return The built request
|
||||
*/
|
||||
private Request buildRequest(RequestBody requestBody) {
|
||||
return new Request.Builder()
|
||||
.url(API_URL)
|
||||
.addHeader("x-rapidapi-host", RAPID_API_HOST)
|
||||
.addHeader("x-rapidapi-key", apiKey)
|
||||
.post(requestBody)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a request with retry logic.
|
||||
*
|
||||
* @param request The request to execute
|
||||
* @return The result of the request or an error message
|
||||
*/
|
||||
private String executeWithRetry(Request request) {
|
||||
Exception lastException = null;
|
||||
|
||||
for (int attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
||||
if (attempt > 0) {
|
||||
try {
|
||||
log.info("Retrying request, attempt {} of {}", attempt + 1, MAX_RETRIES);
|
||||
TimeUnit.MILLISECONDS.sleep(RETRY_DELAY_MS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return "Request interrupted: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return processRequest(request);
|
||||
} catch (IOException e) {
|
||||
lastException = e;
|
||||
log.warn("Request failed (attempt {}/{}): {}", attempt + 1, MAX_RETRIES, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
log.error("All retry attempts failed", lastException);
|
||||
return "Failed after " + MAX_RETRIES + " attempts: " + lastException.getMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a single request to the Judge0 API.
|
||||
*
|
||||
* @param request The request to process
|
||||
* @return The result of the request or an error message
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
private String processRequest(Request request) throws IOException {
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
if (!response.isSuccessful()) {
|
||||
return handleErrorResponse(response);
|
||||
}
|
||||
|
||||
if (response.body() == null) {
|
||||
log.warn(ERROR_NULL_RESPONSE);
|
||||
return ERROR_NULL_RESPONSE;
|
||||
}
|
||||
|
||||
String responseBody = response.body().string();
|
||||
return processResponseBody(responseBody);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles error responses from the Judge0 API.
|
||||
*
|
||||
* @param response The error response
|
||||
* @return An appropriate error message
|
||||
*/
|
||||
private String handleErrorResponse(Response response) {
|
||||
String errorMessage =
|
||||
switch (response.code()) {
|
||||
case 429 -> ERROR_RATE_LIMIT;
|
||||
case 403 -> ERROR_FORBIDDEN;
|
||||
case 404 -> ERROR_NOT_FOUND;
|
||||
case 500 -> ERROR_SERVER;
|
||||
case 503 -> ERROR_UNAVAILABLE;
|
||||
default -> "Unexpected error code " + response.code() + ": " + response.message();
|
||||
};
|
||||
|
||||
log.warn(errorMessage);
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the response body from a successful request.
|
||||
*
|
||||
* @param responseBody The response body as a string
|
||||
* @return The processed result or an error message
|
||||
*/
|
||||
private String processResponseBody(String responseBody) {
|
||||
SubmissionResult result = Json.fromJson(responseBody, SubmissionResult.class);
|
||||
|
||||
if (result.status.id != STATUS_ACCEPTED) {
|
||||
return formatErrorResult(result);
|
||||
}
|
||||
|
||||
String base64EncodedStdout = result.stdout;
|
||||
if (base64EncodedStdout == null) {
|
||||
return ERROR_NO_STDOUT;
|
||||
}
|
||||
|
||||
try {
|
||||
return new String(Base64.getMimeDecoder().decode(base64EncodedStdout.trim()), StandardCharsets.UTF_8)
|
||||
.trim();
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("Failed to decode base64 output", e);
|
||||
return "Error decoding result: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an error result from Judge0.
|
||||
*
|
||||
* @param result The submission result containing error information
|
||||
* @return A formatted error message
|
||||
*/
|
||||
private String formatErrorResult(SubmissionResult result) {
|
||||
StringBuilder error = new StringBuilder(result.status.description);
|
||||
|
||||
if (!isNullOrBlank(result.compile_output)) {
|
||||
error.append("\n");
|
||||
try {
|
||||
error.append(new String(Base64.getMimeDecoder().decode(result.compile_output), StandardCharsets.UTF_8));
|
||||
} catch (IllegalArgumentException e) {
|
||||
error.append("(Error decoding compile output: ")
|
||||
.append(e.getMessage())
|
||||
.append(")");
|
||||
}
|
||||
}
|
||||
|
||||
return error.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a code submission to the Judge0 API.
|
||||
*/
|
||||
private static class Submission {
|
||||
private final int language_id;
|
||||
private final String source_code;
|
||||
|
||||
Submission(int languageId, String sourceCode) {
|
||||
this.language_id = languageId;
|
||||
this.source_code = sourceCode;
|
||||
}
|
||||
|
||||
public int getLanguage_id() {
|
||||
return language_id;
|
||||
}
|
||||
|
||||
public String getSource_code() {
|
||||
return source_code;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the result of a code submission.
|
||||
*/
|
||||
private static class SubmissionResult {
|
||||
private String stdout;
|
||||
private Status status;
|
||||
private String compile_output;
|
||||
|
||||
public String getStdout() {
|
||||
return stdout;
|
||||
}
|
||||
|
||||
public void setStdout(String stdout) {
|
||||
this.stdout = stdout;
|
||||
}
|
||||
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Status status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getCompile_output() {
|
||||
return compile_output;
|
||||
}
|
||||
|
||||
public void setCompile_output(String compile_output) {
|
||||
this.compile_output = compile_output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the status of a code submission.
|
||||
*/
|
||||
private static class Status {
|
||||
private int id;
|
||||
private String description;
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
package dev.langchain4j.code.judge0;
|
||||
|
||||
import static dev.langchain4j.code.judge0.JavaScriptCodeFixer.fixIfNoLogToConsole;
|
||||
import static dev.langchain4j.internal.Utils.isNullOrBlank;
|
||||
|
||||
import dev.langchain4j.agent.tool.P;
|
||||
import dev.langchain4j.agent.tool.Tool;
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A tool that executes JS code using the Judge0 service, hosted by Rapid API.
|
||||
*/
|
||||
public class Judge0JavaScriptExecutionTool {
|
||||
|
||||
/** The language ID for JavaScript in Judge0's API */
|
||||
public static final int JAVASCRIPT = 102;
|
||||
|
||||
/** Default timeout for Judge0 code execution (10 seconds) */
|
||||
private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(10);
|
||||
|
||||
private final Judge0JavaScriptEngine engine;
|
||||
private final boolean fixCodeIfNeeded;
|
||||
|
||||
/**
|
||||
* Constructs a new instance with the provided Rapid API key.
|
||||
* The code fixing feature is enabled by default.
|
||||
* Default timeout is 10 seconds.
|
||||
*
|
||||
* @param apiKey The Rapid API key. You can subscribe to the free plan (Basic) here: https://rapidapi.com/judge0-official/api/judge0-ce/pricing
|
||||
* @throws IllegalArgumentException if the API key is null, empty, or blank
|
||||
*/
|
||||
public Judge0JavaScriptExecutionTool(String apiKey) {
|
||||
this(apiKey, true, DEFAULT_TIMEOUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance with the provided Rapid API key and a timeout.
|
||||
* The code fixing feature is enabled by default.
|
||||
*
|
||||
* @param apiKey The Rapid API key
|
||||
* @param timeout Timeout for calling Judge0
|
||||
* @throws IllegalArgumentException if the API key is null, empty, or blank
|
||||
* @throws NullPointerException if timeout is null
|
||||
*/
|
||||
public Judge0JavaScriptExecutionTool(String apiKey, Duration timeout) {
|
||||
this(apiKey, true, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance with the provided Rapid API key and code fixing flag.
|
||||
* Uses default timeout of 10 seconds.
|
||||
*
|
||||
* @param apiKey The Rapid API key
|
||||
* @param fixCodeIfNeeded Whether to automatically fix code if needed
|
||||
* @throws IllegalArgumentException if the API key is null, empty, or blank
|
||||
*/
|
||||
public Judge0JavaScriptExecutionTool(String apiKey, boolean fixCodeIfNeeded) {
|
||||
this(apiKey, fixCodeIfNeeded, DEFAULT_TIMEOUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance with the provided Rapid API key, a flag to control whether to fix the code, and a timeout.
|
||||
*
|
||||
* @param apiKey Rapid API key. You can subscribe to the free plan (Basic) here: https://rapidapi.com/judge0-official/api/judge0-ce/pricing
|
||||
* @param fixCodeIfNeeded Judge0 can return result of an execution if it was printed to the console.
|
||||
* If provided JS code does not print result to the console, attempt will be made to fix it.
|
||||
* @param timeout Timeout for calling Judge0.
|
||||
* @throws IllegalArgumentException if the API key is null, empty, or blank
|
||||
* @throws NullPointerException if timeout is null
|
||||
*/
|
||||
public Judge0JavaScriptExecutionTool(String apiKey, boolean fixCodeIfNeeded, Duration timeout) {
|
||||
if (isNullOrBlank(apiKey)) {
|
||||
throw new IllegalArgumentException("Please provide a valid Rapid API key");
|
||||
}
|
||||
Objects.requireNonNull(timeout, "Timeout must not be null");
|
||||
|
||||
this.engine = new Judge0JavaScriptEngine(apiKey, JAVASCRIPT, timeout);
|
||||
this.fixCodeIfNeeded = fixCodeIfNeeded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the provided JavaScript code using the Judge0 service.
|
||||
*
|
||||
* If code fixing is enabled (default), the method will attempt to add console.log statements
|
||||
* if the code doesn't already output results to the console.
|
||||
*
|
||||
* @param javaScriptCode JavaScript code to execute
|
||||
* @return The result of the JavaScript code execution
|
||||
* @throws IllegalArgumentException if javaScriptCode is null, empty or blank
|
||||
* @throws RuntimeException if code execution fails
|
||||
*/
|
||||
@Tool("MUST be used for accurate calculations: math, sorting, filtering, aggregating, string processing, etc")
|
||||
public String executeJavaScriptCode(
|
||||
@P("JavaScript code to execute, result MUST be printed to console") String javaScriptCode) {
|
||||
if (isNullOrBlank(javaScriptCode)) {
|
||||
throw new IllegalArgumentException("JavaScript code must not be null or empty");
|
||||
}
|
||||
|
||||
if (fixCodeIfNeeded) {
|
||||
javaScriptCode = fixIfNoLogToConsole(javaScriptCode);
|
||||
}
|
||||
|
||||
try {
|
||||
return engine.execute(javaScriptCode);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to execute JavaScript code: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether code fixing is enabled for this tool.
|
||||
*
|
||||
* @return true if code fixing is enabled, false otherwise
|
||||
*/
|
||||
public boolean isCodeFixingEnabled() {
|
||||
return fixCodeIfNeeded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for configuring a Judge0JavaScriptExecutionTool.
|
||||
*
|
||||
* @return a new builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for creating a configured Judge0JavaScriptExecutionTool.
|
||||
*/
|
||||
public static class Builder {
|
||||
private String apiKey;
|
||||
private boolean fixCodeIfNeeded = true;
|
||||
private Duration timeout = DEFAULT_TIMEOUT;
|
||||
|
||||
/**
|
||||
* Sets the Rapid API key for Judge0.
|
||||
*
|
||||
* @param apiKey the API key
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder apiKey(String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to fix code if needed.
|
||||
*
|
||||
* @param fixCodeIfNeeded whether to fix code
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder fixCodeIfNeeded(boolean fixCodeIfNeeded) {
|
||||
this.fixCodeIfNeeded = fixCodeIfNeeded;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeout for code execution.
|
||||
*
|
||||
* @param timeout the timeout duration
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder timeout(Duration timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new Judge0JavaScriptExecutionTool with the configured properties.
|
||||
*
|
||||
* @return a new Judge0JavaScriptExecutionTool
|
||||
* @throws IllegalArgumentException if apiKey is null, empty or blank
|
||||
*/
|
||||
public Judge0JavaScriptExecutionTool build() {
|
||||
return new Judge0JavaScriptExecutionTool(apiKey, fixCodeIfNeeded, timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package dev.langchain4j.code.judge0;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class JavaScriptCodeFixerTest {
|
||||
|
||||
@Test
|
||||
void should_fix_multi_line_code_when_result_is_not_logged_to_console() {
|
||||
String code =
|
||||
"const startDate = new Date('Feb 21, 1988 17:00:00');\nconst endDate = new Date('Apr 12, 2014 04:00:00');\nconst diff = endDate - startDate;\nconst result = diff / (1000*60*60);\n;const startDate = new Date('Feb 21, 1988 17:00:00');\nconst endDate = new Date('Apr 12, 2014 04:00:00');\nconst diff = endDate - startDate;\nconst result = diff / (1000*60*60);\n";
|
||||
|
||||
String result = JavaScriptCodeFixer.fixIfNoLogToConsole(code + "result");
|
||||
|
||||
assertThat(result).isEqualTo(code + "console.log(result);");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_not_fix_multi_line_code_when_result_is_logged_to_console() {
|
||||
String code =
|
||||
"const startDate = new Date('Feb 21, 1988 17:00:00');\nconst endDate = new Date('Apr 12, 2014 04:00:00');\nconst diff = endDate - startDate;\nconst result = diff / (1000*60*60);\nconsole.log(result);";
|
||||
|
||||
String result = JavaScriptCodeFixer.fixIfNoLogToConsole(code);
|
||||
|
||||
assertThat(result).isEqualTo(code);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_fix_one_line_code_when_result_is_not_logged_to_console() {
|
||||
String code =
|
||||
"const start = new Date('1988-02-21T17:00:00'); const end = new Date('2014-04-12T04:00:00'); const hours = (end - start) / (1000 * 60 * 60); ";
|
||||
|
||||
String result = JavaScriptCodeFixer.fixIfNoLogToConsole(code + "hours");
|
||||
|
||||
assertThat(result).isEqualTo(code + "console.log(hours);");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_not_fix_one_line_code_when_result_is_logged_to_console() {
|
||||
String code =
|
||||
"const start = new Date('1988-02-21T17:00:00'); const end = new Date('2014-04-12T04:00:00'); const hours = (end - start) / (1000 * 60 * 60); console.log(hours);";
|
||||
|
||||
String result = JavaScriptCodeFixer.fixIfNoLogToConsole(code);
|
||||
|
||||
assertThat(result).isEqualTo(code);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_fix_one_statement_when_result_is_not_logged_to_console() {
|
||||
String code = "Math.sqrt(49506838032859)";
|
||||
|
||||
String result = JavaScriptCodeFixer.fixIfNoLogToConsole(code);
|
||||
|
||||
assertThat(result).isEqualTo("console.log(Math.sqrt(49506838032859));");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_not_fix_one_statement_when_result_is_logged_to_console() {
|
||||
String code = "console.log(Math.sqrt(49506838032859));";
|
||||
|
||||
String result = JavaScriptCodeFixer.fixIfNoLogToConsole(code);
|
||||
|
||||
assertThat(result).isEqualTo(code);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_wrap_full_expression_for_single_line_code_with_spaces() {
|
||||
assertThat(JavaScriptCodeFixer.fixIfNoLogToConsole("2 + 3")).isEqualTo("console.log(2 + 3);");
|
||||
assertThat(JavaScriptCodeFixer.fixIfNoLogToConsole("Math.min(3, 7)")).isEqualTo("console.log(Math.min(3, 7));");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_wrap_full_last_expression_when_single_line_has_preceding_statements() {
|
||||
String code = "const a = 2; const b = 3; a + b";
|
||||
assertThat(JavaScriptCodeFixer.fixIfNoLogToConsole(code))
|
||||
.isEqualTo("const a = 2; const b = 3; console.log(a + b);");
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package dev.langchain4j.code.judge0;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import dev.langchain4j.code.CodeExecutionEngine;
|
||||
import java.time.Duration;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
|
||||
/**
|
||||
* Integration tests for the Judge0JavaScriptEngine.
|
||||
*
|
||||
* Note: These tests require a valid RAPID_API_KEY environment variable to be set.
|
||||
*/
|
||||
@EnabledIfEnvironmentVariable(named = "RAPID_API_KEY", matches = ".+")
|
||||
class Judge0JavaScriptEngineIT {
|
||||
|
||||
private static final Duration TEST_TIMEOUT = Duration.ofSeconds(60);
|
||||
private CodeExecutionEngine codeExecutionEngine;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
String apiKey = System.getenv("RAPID_API_KEY");
|
||||
codeExecutionEngine =
|
||||
new Judge0JavaScriptEngine(apiKey, Judge0JavaScriptExecutionTool.JAVASCRIPT, TEST_TIMEOUT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_execute_simple_code() {
|
||||
// when
|
||||
String result = codeExecutionEngine.execute("console.log('hello world');");
|
||||
|
||||
// then
|
||||
assertThat(result).isEqualTo("hello world");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_execute_mathematical_calculations() {
|
||||
// when
|
||||
String result =
|
||||
codeExecutionEngine.execute("function calculateCompoundInterest(principal, rate, time, compounds) {\n"
|
||||
+ " const amount = principal * Math.pow(1 + rate/compounds, compounds * time);\n"
|
||||
+ " const interest = amount - principal;\n"
|
||||
+ " return { principal, amount, interest };\n"
|
||||
+ "}\n\n"
|
||||
+ "const result = calculateCompoundInterest(1000, 0.05, 5, 12);\n"
|
||||
+ "console.log(JSON.stringify(result));");
|
||||
|
||||
assertThat(result)
|
||||
.contains("\"principal\":1000")
|
||||
.contains("\"amount\":1283.3586785035118")
|
||||
.contains("\"interest\":283.3586785035118");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
style:
|
||||
active: true
|
||||
MethodName:
|
||||
active: true
|
||||
ignoreOverriddenFunctions: true
|
||||
excludeAnnotatedMethodsOrClasses:
|
||||
- "org.junit.jupiter.api.Test"
|
||||
ignoreTestFiles: true
|
||||
@@ -0,0 +1,20 @@
|
||||
# Dependencies
|
||||
/node_modules
|
||||
|
||||
# Production
|
||||
/build
|
||||
|
||||
# Generated files
|
||||
.docusaurus
|
||||
.cache-loader
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
@@ -0,0 +1 @@
|
||||
20
|
||||
@@ -0,0 +1,7 @@
|
||||
build-docs:
|
||||
npm ci; \
|
||||
npm run build
|
||||
|
||||
run-docs:
|
||||
npm ci; \
|
||||
npm run start
|
||||
@@ -0,0 +1,42 @@
|
||||
## LangChain4j Documentation
|
||||
|
||||
### Table of Contents
|
||||
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Installation](#installation)
|
||||
|
||||
This is built using [Docusaurus](https://docusaurus.io/), a modern static docs/website generator.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [Node.JS](https://nodejs.org/en/download) (20+)
|
||||
|
||||
If Node.js is managed by Node Version Manager (nvm), run:
|
||||
```shell
|
||||
nvm use
|
||||
```
|
||||
|
||||
### Installation
|
||||
|
||||
This command installs all the Node dependencies.
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
This command builds the deployable static artifacts (i.e., html, js, css, etc) under the `build` directory. This static content can then be deployed to `GitHub pages` or `Vercel`, etc.
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Local Development
|
||||
|
||||
This command starts a local development server and opens up a browser window. Most changes are reflected live without
|
||||
having to restart the server.
|
||||
|
||||
```
|
||||
npm run start
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
|
||||
};
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
npm ci
|
||||
|
||||
npm run build
|
||||
@@ -0,0 +1,145 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
stableVersion: 1.17.2
|
||||
betaVersion: 1.17.2-beta27
|
||||
---
|
||||
|
||||
# Get Started
|
||||
|
||||
:::note
|
||||
If you are using Quarkus, see [Quarkus Integration](/tutorials/quarkus-integration/).
|
||||
|
||||
If you are using Spring Boot, see [Spring Boot Integration](/tutorials/spring-boot-integration).
|
||||
|
||||
If you are using Helidon, see [Helidon Integration](/tutorials/helidon-integration)
|
||||
:::
|
||||
|
||||
LangChain4j offers integrations with many [LLM providers](/integrations/language-models/),
|
||||
[embedding/vector stores](/integrations/embedding-stores), etc.
|
||||
Each integration has its own maven dependency.
|
||||
|
||||
The minimum supported JDK version is 17.
|
||||
|
||||
As an example, let's import the OpenAI dependency:
|
||||
|
||||
- For Maven in `pom.xml`:
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-open-ai</artifactId>
|
||||
<version>1.17.2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
If you wish to use a high-level [AI Services](/tutorials/ai-services) API, you will also need to add
|
||||
the following dependency:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j</artifactId>
|
||||
<version>1.17.2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
- For Gradle in `build.gradle`:
|
||||
```groovy
|
||||
implementation 'dev.langchain4j:langchain4j-open-ai:1.17.2'
|
||||
implementation 'dev.langchain4j:langchain4j:1.17.2'
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Bill of Materials (BOM)</summary>
|
||||
|
||||
```xml
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-bom</artifactId>
|
||||
<version>1.17.2</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
```
|
||||
|
||||
:::note
|
||||
Please note that `langchain4j-bom` always contains the latest versions of all LangChain4j modules.
|
||||
:::
|
||||
|
||||
:::note
|
||||
Please note that while the `langchain4j-bom` version is `1.17.2`,
|
||||
many of the modules still have version `1.17.2-beta27`,
|
||||
so there might be some breaking changes for these modules in the future.
|
||||
:::
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>SNAPSHOT dependencies (newest features)</summary>
|
||||
|
||||
If you'd like to test the newest features before their official release,
|
||||
you can use the most recent `SNAPSHOT` dependency:
|
||||
```xml
|
||||
<repositories>
|
||||
<repository>
|
||||
<name>Central Portal Snapshots</name>
|
||||
<id>central-portal-snapshots</id>
|
||||
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j</artifactId>
|
||||
<version>1.17.2-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
</details>
|
||||
|
||||
Then, import your OpenAI API key.
|
||||
It's recommended to store your API keys in environment variables to reduce the risk of exposing them publicly.
|
||||
```java
|
||||
String apiKey = System.getenv("OPENAI_API_KEY");
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>What if I don't have an API key?</summary>
|
||||
|
||||
If you don't have your own OpenAI API key, don't worry.
|
||||
You can temporarily use `demo` key, which we provide for free for demonstration purposes.
|
||||
Be aware that when using the `demo` key, all requests to the OpenAI API need to go through our proxy,
|
||||
which injects the real key before forwarding your request to the OpenAI API.
|
||||
We do not collect or use your data in any way.
|
||||
The `demo` key has a quota, is restricted to the `gpt-4o-mini` model, and should only be used for demonstration purposes.
|
||||
|
||||
```java
|
||||
OpenAiChatModel model = OpenAiChatModel.builder()
|
||||
.baseUrl("http://langchain4j.dev/demo/openai/v1")
|
||||
.apiKey("demo")
|
||||
.modelName("gpt-4o-mini")
|
||||
.build();
|
||||
```
|
||||
</details>
|
||||
|
||||
Once you've set up the key, let's create an instance of an `OpenAiChatModel`:
|
||||
```java
|
||||
OpenAiChatModel model = OpenAiChatModel.builder()
|
||||
.apiKey(apiKey)
|
||||
.modelName("gpt-4o-mini")
|
||||
.build();
|
||||
```
|
||||
Now, it is time to chat!
|
||||
```java
|
||||
String answer = model.chat("Say 'Hello World'");
|
||||
System.out.println(answer); // Hello World
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "Integrations",
|
||||
"position": 7,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Integrations"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "Browser Execution Engines",
|
||||
"position": 20,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Browser Execution Engines"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Playwright
|
||||
`BrowserExecutionEngine` represents a browser execution engine that can be used to perform actions on the browser in response to a user action.
|
||||
`PlaywrightBrowserExecutionEngine` is an implementation of a `BrowserExecutionEngine` that uses <a href="https://playwright.dev/java/">Playwright Java API</a> for performing browser actions.
|
||||
`BrowserUseTool` executes browser actions using `BrowserExecutionEngine`, that can be useful for **Browser-Use** Agents. You can control your browser using natural language, something like:
|
||||
* `open page 'https://docs.langchain4j.dev/', and summary the page text`
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-community-browser-execution-engine-playwright</artifactId>
|
||||
<version>${latest version here}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-community-tool-browser-use</artifactId>
|
||||
<version>${latest version here}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
## APIs
|
||||
|
||||
- `BrowserExecutionEngine`
|
||||
- `PlaywrightBrowserExecutionEngine`
|
||||
- `BrowserUseTool`
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
```java
|
||||
Playwright playwright = Playwright.create();
|
||||
BrowserType.LaunchOptions options = new BrowserType.LaunchOptions()
|
||||
.setHeadless(false)
|
||||
.setChannel("chrome")
|
||||
.setChromiumSandbox(true)
|
||||
.setSlowMo(500);
|
||||
Browser browser = playwright.chromium().launch(options);
|
||||
|
||||
Assistant assistant = AiServices.builder(Assistant.class)
|
||||
.chatModel(model)
|
||||
.tools(BrowserUseTool.from(PlaywrightBrowserExecutionEngine.builder().browser(browser).build()))
|
||||
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
|
||||
.build();
|
||||
|
||||
String question = "open page 'https://docs.langchain4j.dev/', and summary the page text";
|
||||
System.out.println(assistant.chat(question));
|
||||
```
|
||||
|
||||
- [PlaywrightBrowserExecutionEngineIT](https://github.com/langchain4j/langchain4j-community/blob/main/browser-execution-engines/langchain4j-community-browser-execution-engine-playwright/src/test/java/dev/langchain4j/community/browser/playwright/PlaywrightBrowserExecutionEngineIT.java)
|
||||
- [BrowserUseToolIT](https://github.com/langchain4j/langchain4j-community/blob/main/tools/langchain4j-community-tool-browser-use/src/test/java/dev/langchain4j/community/tool/browseruse/BrowserUseToolIT.java)
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "Chat Memory Stores",
|
||||
"position": 4,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Chat Memory Stores"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: Comparison table of all supported Chat Memory Stores
|
||||
hide_title: false
|
||||
sidebar_position: 0
|
||||
---
|
||||
|
||||
| Chat Memory Stores | Persistence | Default |
|
||||
|---------------------------------------------------------------------------|-------------|---------|
|
||||
| [In-memory](https://github.com/langchain4j/langchain4j/blob/main/langchain4j-core/src/test/java/dev/langchain4j/store/memory/chat/InMemoryChatMemoryStoreTest.java) | | |
|
||||
| [Azure CosmosDB NoSQL](https://github.com/langchain4j/langchain4j/blob/main/langchain4j-azure-cosmos-nosql/src/test/java/dev/langchain4j/store/memory/azure/cosmos/nosql/AzureCosmosDBNoSqlMemoryStoreIT.java) | ✅ | |
|
||||
| [Cassandra](https://github.com/langchain4j/langchain4j/blob/main/langchain4j-cassandra/src/test/java/dev/langchain4j/store/memory/chat/cassandra/CassandraChatMemoryStoreDockerIT.java) | ✅ | |
|
||||
| [Coherence](https://github.com/langchain4j/langchain4j/blob/main/langchain4j-coherence/src/test/java/dev/langchain4j/store/memory/chat/coherence/CoherenceChatMemoryStoreIT.java) | ✅ | |
|
||||
| [Hazelcast (IMap)](https://github.com/langchain4j/langchain4j-community/blob/main/chat-memory-stores/langchain4j-community-hazelcast/src/test/java/dev/langchain4j/community/store/memory/chat/hazelcast/HazelcastChatMemoryStoreIT.java) | ✅ | |
|
||||
| [Hazelcast (CPMap)](https://github.com/langchain4j/langchain4j-community/blob/main/embedding-stores/langchain4j-community-hazelcast-enterprise/src/test/java/dev/langchain4j/community/store/memory/chat/hazelcast/HazelcastCPMapChatMemoryStoreIT.java) | ✅ | |
|
||||
| [Neo4j](https://github.com/langchain4j/langchain4j-community/blob/main/embedding-stores/langchain4j-community-neo4j/src/test/java/dev/langchain4j/community/store/memory/chat/neo4j/Neo4jChatMemoryStoreIT.java) | ✅ | |
|
||||
| [Oracle](https://github.com/langchain4j/langchain4j/blob/main/langchain4j-oracle/src/test/java/dev/langchain4j/store/memory/chat/oracle/OracleChatMemoryStoreIT.java) | ✅ | |
|
||||
| [Redis](https://github.com/langchain4j/langchain4j-community/blob/main/embedding-stores/langchain4j-community-redis/src/test/java/dev/langchain4j/community/store/memory/chat/redis/RedisChatMemoryStoreIT.java) | ✅ | |
|
||||
| [Tablestore](https://github.com/langchain4j/langchain4j/blob/main/langchain4j-tablestore/src/test/java/dev/langchain4j/store/memory/chat/tablestore/TablestoreChatMemoryStoreIT.java) | ✅ | |
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "Code Execution Engines",
|
||||
"position": 15,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Code Execution Engines"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Azure ACA Dynamic Sessions
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-code-execution-engine-azure-acads</artifactId>
|
||||
<version>1.17.2-beta27</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## APIs
|
||||
|
||||
- `SessionsREPLTool`
|
||||
|
||||
## Examples
|
||||
|
||||
- [SessionsREPLToolTest](https://github.com/langchain4j/langchain4j/blob/main/code-execution-engines/langchain4j-code-execution-engine-azure-acads/src/test/java/dev/langchain4j/code/azure/acads/SessionsREPLToolTest.java)
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# GraalVM Polyglot/Truffle
|
||||
|
||||
:::danger
|
||||
⚠️ Security Warning: High-Risk Code Execution
|
||||
|
||||
This module enables execution of arbitrary Python/JavaScript code via GraalVM and is inherently dangerous.
|
||||
|
||||
❗ Do NOT use in production environments!
|
||||
:::
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-code-execution-engine-graalvm-polyglot</artifactId>
|
||||
<version>1.17.2-beta27</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## APIs
|
||||
|
||||
- `GraalVmJavaScriptExecutionEngine`
|
||||
- `GraalVmJavaScriptExecutionTool`
|
||||
- `GraalVmPythonExecutionEngine`
|
||||
- `GraalVmPythonExecutionTool`
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
- [GraalVmJavaScriptExecutionEngineTest](https://github.com/langchain4j/langchain4j/blob/main/code-execution-engines/langchain4j-code-execution-engine-graalvm-polyglot/src/test/java/dev/langchain4j/code/graalvm/GraalVmJavaScriptExecutionEngineTest.java)
|
||||
- [GraalVmJavaScriptExecutionToolIT](https://github.com/langchain4j/langchain4j/blob/main/code-execution-engines/langchain4j-code-execution-engine-graalvm-polyglot/src/test/java/dev/langchain4j/agent/tool/graalvm/GraalVmJavaScriptExecutionToolIT.java)
|
||||
- [GraalVmPythonExecutionEngineTest](https://github.com/langchain4j/langchain4j/blob/main/code-execution-engines/langchain4j-code-execution-engine-graalvm-polyglot/src/test/java/dev/langchain4j/code/graalvm/GraalVmPythonExecutionEngineTest.java)
|
||||
- [GraalVmPythonExecutionToolIT](https://github.com/langchain4j/langchain4j/blob/main/code-execution-engines/langchain4j-code-execution-engine-graalvm-polyglot/src/test/java/dev/langchain4j/agent/tool/graalvm/GraalVmPythonExecutionToolIT.java)
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Judge0
|
||||
|
||||
https://github.com/judge0/judge0
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-code-execution-engine-judge0</artifactId>
|
||||
<version>1.17.2-beta27</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## APIs
|
||||
|
||||
- `Judge0JavaScriptEngine`
|
||||
- `Judge0JavaScriptExecutionTool`
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
- [ServiceWithDynamicToolsExample](https://github.com/langchain4j/langchain4j-examples/blob/main/other-examples/src/main/java/ServiceWithDynamicToolsExample.java)
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Local
|
||||
|
||||
`CommandLineExecutionEngine` uses the local computer env to execute provided command line code.
|
||||
There are `CommandLineTool` and `LocalScriptExecutionTool` two implemented tools,
|
||||
that can be useful for **Desktop Automation** or **Computer-Use** Agents
|
||||
(such as File Management, Application Control). You can control your computer using natural language, something like:
|
||||
- `set my mac output volume 50`
|
||||
- `list all running applications in my mac`
|
||||
- `tell a story and then read it out loud`
|
||||
- `tell a story about moon and save it into a text file`
|
||||
- ...
|
||||
|
||||
:::danger
|
||||
⚠️ Security Warning: High-Risk Code Execution
|
||||
|
||||
Attention! It might be *dangerous* to execute the code in a production online serving environment.
|
||||
It needs to be executed through security sandbox environment if used in online serving.
|
||||
|
||||
❗ Do NOT use in production environments!
|
||||
:::
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-community-code-execution-engine-local</artifactId>
|
||||
<version>${latest version here}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## APIs
|
||||
|
||||
- `CommandLineExecutionEngine`
|
||||
- `CommandLineTool`
|
||||
- `LocalScriptExecutionTool`
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
```java
|
||||
LocalScriptExecutionTool tool = new LocalScriptExecutionTool();
|
||||
|
||||
Assistant assistant = AiServices.builder(Assistant.class)
|
||||
.chatModel(model)
|
||||
.tools(tool)
|
||||
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
|
||||
.build();
|
||||
|
||||
String answer = assistant.chat("list all running applications in my mac");
|
||||
System.out.println(answer);
|
||||
```
|
||||
|
||||
- [CommandLineExecutionEngineTest](https://github.com/langchain4j/langchain4j-community/blob/main/code-execution-engines/langchain4j-community-code-execution-engine-local/src/test/java/dev/langchain4j/community/code/local/CommandLineExecutionEngineTest.java)
|
||||
- [CommandLineToolIT](https://github.com/langchain4j/langchain4j-community/blob/main/code-execution-engines/langchain4j-community-code-execution-engine-local/src/test/java/dev/langchain4j/community/code/local/CommandLineToolIT.java)
|
||||
- [CommandLineToolTest](https://github.com/langchain4j/langchain4j-community/blob/main/code-execution-engines/langchain4j-community-code-execution-engine-local/src/test/java/dev/langchain4j/community/code/local/CommandLineToolTest.java)
|
||||
- [LocalScriptExecutionToolIT](https://github.com/langchain4j/langchain4j-community/blob/main/code-execution-engines/langchain4j-community-code-execution-engine-local/src/test/java/dev/langchain4j/community/code/local/LocalScriptExecutionToolIT.java)
|
||||
- [LocalScriptExecutionToolTest](https://github.com/langchain4j/langchain4j-community/blob/main/code-execution-engines/langchain4j-community-code-execution-engine-local/src/test/java/dev/langchain4j/community/code/local/LocalScriptExecutionToolTest.java)
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "Document Loaders",
|
||||
"position": 7,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Document Loaders"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Amazon S3
|
||||
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-document-loader-amazon-s3</artifactId>
|
||||
<version>1.17.2-beta27</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
## APIs
|
||||
|
||||
- `AmazonS3DocumentLoader`
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
- [AmazonS3DocumentLoaderIT](https://github.com/langchain4j/langchain4j/blob/main/document-loaders/langchain4j-document-loader-amazon-s3/src/test/java/dev/langchain4j/data/document/loader/amazon/s3/AmazonS3DocumentLoaderIT.java)
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Azure Blob Storage
|
||||
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-document-loader-azure-storage-blob</artifactId>
|
||||
<version>1.17.2-beta27</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
## APIs
|
||||
|
||||
- `AzureBlobStorageDocumentLoader`
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
- [AzureBlobStorageDocumentLoaderIT](https://github.com/langchain4j/langchain4j/blob/main/document-loaders/langchain4j-document-loader-azure-storage-blob/src/test/java/dev/langchain4j/data/document/loader/azure/storage/blob/AzureBlobStorageDocumentLoaderIT.java)
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Classpath
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j</artifactId>
|
||||
<version>1.17.2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
## APIs
|
||||
|
||||
- `ClassPathSource` / `ClassPathDocumentLoader`
|
||||
|
||||
## Examples
|
||||
|
||||
- [ClassPathSourceTests](https://github.com/langchain4j/langchain4j/blob/main/langchain4j/src/test/java/dev/langchain4j/data/document/source/ClassPathSourceTests.java)
|
||||
- [ClassPathDocumentLoaderTest](https://github.com/langchain4j/langchain4j/blob/main/langchain4j/src/test/java/dev/langchain4j/data/document/loader/ClassPathDocumentLoaderTest.java)
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# File System
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j</artifactId>
|
||||
<version>1.17.2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
## APIs
|
||||
|
||||
- `FileSystemSource` / `FileSystemDocumentLoader`
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
- [FileSystemDocumentLoaderTest](https://github.com/langchain4j/langchain4j/blob/main/langchain4j/src/test/java/dev/langchain4j/data/document/loader/FileSystemDocumentLoaderTest.java)
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# GitHub
|
||||
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-document-loader-github</artifactId>
|
||||
<version>1.17.2-beta27</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
## APIs
|
||||
|
||||
- `GitHubDocumentLoader`
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
- [GitHubDocumentLoaderIT](https://github.com/langchain4j/langchain4j/blob/main/document-loaders/langchain4j-document-loader-github/src/test/java/dev/langchain4j/data/document/loader/github/GitHubDocumentLoaderIT.java)
|
||||
@@ -0,0 +1,83 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Google Cloud Storage
|
||||
|
||||
A Google Cloud Storage (GCS) document loader that allows you to load documents from storage buckets.
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-document-loader-google-cloud-storage</artifactId>
|
||||
<version>1.17.2-beta27</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## APIs
|
||||
|
||||
- `GoogleCloudStorageDocumentLoader`
|
||||
|
||||
## Authentication
|
||||
|
||||
The authentication should be handled transparently for you:
|
||||
* If your application is running on Google Cloud Platform (Cloud Run, App Engine, Compute Engine, etc)
|
||||
* When running locally on your machine, if you are already authenticated via Google's `gcloud` SDK
|
||||
|
||||
You should just create a loader specifying just your project ID:
|
||||
|
||||
```java
|
||||
GoogleCloudStorageDocumentLoader gcsLoader = GoogleCloudStorageDocumentLoader.builder()
|
||||
.project(System.getenv("GCP_PROJECT_ID"))
|
||||
.build();
|
||||
```
|
||||
|
||||
Otherwise, it's possible to specify `Credentials`, if you have downloaded a service account key, and exported an environment variable pointing to it:
|
||||
|
||||
```java
|
||||
GoogleCloudStorageDocumentLoader gcsLoader = GoogleCloudStorageDocumentLoader.builder()
|
||||
.project(System.getenv("GCP_PROJECT_ID"))
|
||||
.credentials(GoogleCredentials.fromStream(new FileInputStream(System.getenv("GOOGLE_APPLICATION_CREDENTIALS"))))
|
||||
.build();
|
||||
```
|
||||
|
||||
Learn more about [credentials](https://cloud.google.com/docs/authentication/application-default-credentials).
|
||||
|
||||
When accessing a public bucket, you shouldn't need to authenticate.
|
||||
|
||||
## Examples
|
||||
|
||||
### Load a single file from a GCS bucket
|
||||
|
||||
```java
|
||||
GoogleCloudStorageDocumentLoader gcsLoader = GoogleCloudStorageDocumentLoader.builder()
|
||||
.project(System.getenv("GCP_PROJECT_ID"))
|
||||
.build();
|
||||
|
||||
Document document = gcsLoader.loadDocument("BUCKET_NAME", "FILE_NAME.txt", new TextDocumentParser());
|
||||
```
|
||||
|
||||
### Load all files from a GCS bucket
|
||||
|
||||
```java
|
||||
GoogleCloudStorageDocumentLoader gcsLoader = GoogleCloudStorageDocumentLoader.builder()
|
||||
.project(System.getenv("GCP_PROJECT_ID"))
|
||||
.build();
|
||||
|
||||
List<Document> documents = gcsLoader.loadDocuments("BUCKET_NAME", new TextDocumentParser());
|
||||
```
|
||||
|
||||
### Load all files from a GCS bucket with a glob pattern
|
||||
|
||||
```java
|
||||
GoogleCloudStorageDocumentLoader gcsLoader = GoogleCloudStorageDocumentLoader.builder()
|
||||
.project(System.getenv("GCP_PROJECT_ID"))
|
||||
.build();
|
||||
|
||||
List<Document> documents = gcsLoader.loadDocuments("BUCKET_NAME", "*.txt", new TextDocumentParser());
|
||||
```
|
||||
|
||||
For more code samples, please have a look at the integration test class:
|
||||
- [GoogleCloudStorageDocumentLoaderIT](https://github.com/langchain4j/langchain4j/blob/main/document-loaders/langchain4j-document-loader-google-cloud-storage/src/test/java/dev/langchain4j/data/document/loader/gcs/GoogleCloudStorageDocumentLoaderIT.java)
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# Playwright
|
||||
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-document-loader-playwright</artifactId>
|
||||
<version>1.17.2-beta27</version>
|
||||
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
## APIs
|
||||
|
||||
- `PlaywrightDocumentLoader`
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
- [PlaywrightDocumentLoaderIT](https://github.com/langchain4j/langchain4j/blob/main/document-loaders/langchain4j-document-loader-playwright/src/test/java/dev/langchain4j/data/document/loader/playwright/PlaywrightDocumentLoaderIT.java)
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# Selenium
|
||||
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-document-loader-selenium</artifactId>
|
||||
<version>1.17.2-beta27</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
## APIs
|
||||
|
||||
- `SeleniumDocumentLoader`
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
- [SeleniumDocumentLoaderIT](https://github.com/langchain4j/langchain4j/blob/main/document-loaders/langchain4j-document-loader-selenium/src/test/java/dev/langchain4j/data/document/loader/selenium/SeleniumDocumentLoaderIT.java)
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
# Tencent COS
|
||||
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-document-loader-tencent-cos</artifactId>
|
||||
<version>1.17.2-beta27</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
## APIs
|
||||
|
||||
- `TencentCosDocumentLoader`
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
- [TencentCosDocumentLoaderIT](https://github.com/langchain4j/langchain4j/blob/main/document-loaders/langchain4j-document-loader-tencent-cos/src/test/java/dev/langchain4j/data/document/loader/tencent/cos/TencentCosDocumentLoaderIT.java)
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
sidebar_position: 7
|
||||
---
|
||||
|
||||
# URL
|
||||
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j</artifactId>
|
||||
<version>1.17.2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
## APIs
|
||||
|
||||
- `UrlDocumentLoader`
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
- [UrlDocumentLoaderTest](https://github.com/langchain4j/langchain4j/blob/main/langchain4j/src/test/java/dev/langchain4j/data/document/loader/UrlDocumentLoaderTest.java)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "Document Parsers",
|
||||
"position": 7,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Document Parsers"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Apache PDFBox
|
||||
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
|
||||
<version>1.17.2-beta27</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
## APIs
|
||||
|
||||
- `ApachePdfBoxDocumentParser`
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
- [ApachePdfBoxDocumentParserTest](https://github.com/langchain4j/langchain4j/blob/main/document-parsers/langchain4j-document-parser-apache-pdfbox/src/test/java/dev/langchain4j/data/document/parser/apache/pdfbox/ApachePdfBoxDocumentParserTest.java)
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Apache POI
|
||||
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-document-parser-apache-poi</artifactId>
|
||||
<version>1.17.2-beta27</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
## APIs
|
||||
|
||||
- `ApachePoiDocumentParser`
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
- [ApachePoiDocumentParserTest](https://github.com/langchain4j/langchain4j/blob/main/document-parsers/langchain4j-document-parser-apache-poi/src/test/java/dev/langchain4j/data/document/parser/apache/poi/ApachePoiDocumentParserTest.java)
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Apache Tika
|
||||
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-document-parser-apache-tika</artifactId>
|
||||
<version>1.17.2-beta27</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
## APIs
|
||||
|
||||
- `ApacheTikaDocumentParser`
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
- [ApacheTikaDocumentParserTest](https://github.com/langchain4j/langchain4j/blob/main/document-parsers/langchain4j-document-parser-apache-tika/src/test/java/dev/langchain4j/data/document/parser/apache/tika/ApacheTikaDocumentParserTest.java)
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
sidebar_position: 7
|
||||
---
|
||||
|
||||
# Docling
|
||||
|
||||
[Docling](https://docling.ai) is an IBM Research document processing engine that extracts text and structure from various document formats including PDF, DOCX, PPTX, and more. It provides advanced capabilities such as OCR, table extraction, and layout analysis.
|
||||
|
||||
This integration communicates with a running [docling-serve](https://github.com/docling-project/docling-serve) instance via REST API and is built using the [official Docling Java library](https://docling-project.github.io/docling-java/current/).
|
||||
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-document-parser-docling</artifactId>
|
||||
<version>1.17.2-beta27</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
This module depends on `docling-serve-api` (the interface) and includes `docling-serve-client` (the reference HTTP client) as an optional runtime dependency.
|
||||
|
||||
**If you are not using Spring Boot or Quarkus** (which may provide their own `DoclingServeApi` implementation), you must also add the reference client explicitly:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>ai.docling</groupId>
|
||||
<artifactId>docling-serve-client</artifactId>
|
||||
<version>0.5.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Frameworks such as [Quarkus](https://quarkus.io) or [Spring Boot](https://spring.io/projects/spring-boot) provide their own integrations with Docling. See the [Docling Java Documentation](https://docling-project.github.io/docling-java/dev/docling-serve/serve-client/#when-to-use-this-module) for how to wire in those specific implementations.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Start a `docling-serve` instance (see [docling-serve docs](https://github.com/docling-project/docling-serve)), then build a `DoclingServeApi` client and pass it to the parser:
|
||||
|
||||
```java
|
||||
DoclingServeApi api = DoclingServeApi.builder()
|
||||
.baseUrl("http://localhost:5001")
|
||||
.build();
|
||||
|
||||
DoclingDocumentParser parser = new DoclingDocumentParser(api);
|
||||
Document document = parser.parse(inputStream);
|
||||
String text = document.text();
|
||||
```
|
||||
|
||||
To customize Docling processing, use the constructor that also accepts a [`ConvertDocumentOptions`](https://docling-project.github.io/docling-java/dev/docling-serve/serve-api/#requests-convertdocumentrequest):
|
||||
|
||||
```java
|
||||
ConvertDocumentOptions options = ConvertDocumentOptions.builder()
|
||||
// configure options here
|
||||
.build();
|
||||
|
||||
DoclingDocumentParser parser = new DoclingDocumentParser(api, options);
|
||||
```
|
||||
|
||||
## APIs
|
||||
|
||||
- `DoclingDocumentParser`
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
- [DoclingDocumentParserTest](https://github.com/langchain4j/langchain4j/blob/main/document-parsers/langchain4j-document-parser-docling/src/test/java/dev/langchain4j/data/document/parser/docling/DoclingDocumentParserTest.java)
|
||||
- [DoclingDocumentParserIT](https://github.com/langchain4j/langchain4j/blob/main/document-parsers/langchain4j-document-parser-docling/src/test/java/dev/langchain4j/data/document/parser/docling/DoclingDocumentParserIT.java)
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Markdown
|
||||
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-document-parser-markdown</artifactId>
|
||||
<version>1.17.2-beta27</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
## APIs
|
||||
|
||||
- `MarkdownDocumentParser`
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
- [MarkdownDocumentParserTest](https://github.com/langchain4j/langchain4j/blob/main/document-parsers/langchain4j-document-parser-markdown/src/test/java/dev/langchain4j/data/document/parser/markdown/MarkdownDocumentParserTest.java)
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Text
|
||||
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j</artifactId>
|
||||
<version>1.17.2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
## APIs
|
||||
|
||||
- `TextDocumentParser`
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
- [TextDocumentParserTest](https://github.com/langchain4j/langchain4j/blob/main/langchain4j/src/test/java/dev/langchain4j/data/document/parser/TextDocumentParserTest.java)
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# YAML
|
||||
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-document-parser-yaml</artifactId>
|
||||
<version>1.17.2-beta27</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## APIs
|
||||
|
||||
- `YamlDocumentParser`
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
- [YamlDocumentParserTest](https://github.com/langchain4j/langchain4j/blob/main/document-parsers/langchain4j-document-parser-yaml/src/test/java/dev/langchain4j/data/document/parser/yaml/YamlDocumentParserTest.java)
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# In-process (ONNX)
|
||||
|
||||
LangChain4j provides a few popular local embedding models packaged as maven dependencies.
|
||||
They are powered by [ONNX runtime](https://onnxruntime.ai/docs/get-started/with-java.html)
|
||||
and are running in the same java process.
|
||||
|
||||
Each model is provided in 2 flavours: original and quantized (has a `-q` suffix in maven artifact name and `Quantized` in the class name).
|
||||
|
||||
For example:
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-embeddings-all-minilm-l6-v2</artifactId>
|
||||
<version>1.17.2-beta27</version>
|
||||
</dependency>
|
||||
```
|
||||
```java
|
||||
EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel();
|
||||
Response<Embedding> response = embeddingModel.embed("test");
|
||||
Embedding embedding = response.content();
|
||||
```
|
||||
|
||||
Or quantized:
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-embeddings-all-minilm-l6-v2-q</artifactId>
|
||||
<version>1.17.2-beta27</version>
|
||||
</dependency>
|
||||
```
|
||||
```java
|
||||
EmbeddingModel embeddingModel = new AllMiniLmL6V2QuantizedEmbeddingModel();
|
||||
Response<Embedding> response = embeddingModel.embed("test");
|
||||
Embedding embedding = response.content();
|
||||
```
|
||||
|
||||
The complete list of all embedding models can be found [here](https://github.com/langchain4j/langchain4j/tree/main/embeddings).
|
||||
|
||||
|
||||
## Parallelization
|
||||
|
||||
By default, the embedding process is parallelized using all available CPU cores,
|
||||
so each `TextSegment` is embedded in a separate thread.
|
||||
|
||||
The parallelization is done by using an `Executor`.
|
||||
By default, in-process embedding models use a cached thread pool
|
||||
with the number of threads equal to the number of available processors.
|
||||
Threads are cached for 1 second.
|
||||
|
||||
You can provide a custom instance of the `Executor` when creating a model:
|
||||
```java
|
||||
Executor = ...;
|
||||
EmbeddingModel embeddingModel = new AllMiniLmL6V2QuantizedEmbeddingModel(executor);
|
||||
```
|
||||
|
||||
Embedding using GPU is not supported yet.
|
||||
|
||||
## Custom models
|
||||
|
||||
Many models (e.g., from [Hugging Face](https://huggingface.co/)) can be used,
|
||||
as long as they are in the ONNX format.
|
||||
|
||||
Information on how to convert models into ONNX format can be found [here](https://huggingface.co/docs/optimum/exporters/onnx/usage_guides/export_a_model).
|
||||
|
||||
Many models already converted to ONNX format are available [here](https://huggingface.co/Xenova).
|
||||
|
||||
Example of using custom embedding model:
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-embeddings</artifactId>
|
||||
<version>1.17.2-beta27</version>
|
||||
</dependency>
|
||||
```
|
||||
```java
|
||||
String pathToModel = "/home/langchain4j/model.onnx";
|
||||
String pathToTokenizer = "/home/langchain4j/tokenizer.json";
|
||||
PoolingMode poolingMode = PoolingMode.MEAN;
|
||||
EmbeddingModel embeddingModel = new OnnxEmbeddingModel(pathToModel, pathToTokenizer, poolingMode);
|
||||
|
||||
Response<Embedding> response = embeddingModel.embed("test");
|
||||
Embedding embedding = response.content();
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
- [InProcessEmbeddingModelExamples](https://github.com/langchain4j/langchain4j-examples/blob/main/other-examples/src/main/java/embedding/model/InProcessEmbeddingModelExamples.java)
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "Embedding Models",
|
||||
"position": 2,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Embedding Models"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Amazon Bedrock
|
||||
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-bedrock</artifactId>
|
||||
<version>1.17.2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
## AWS credentials
|
||||
In order to use Amazon Bedrock embeddings, you need to configure AWS credentials.
|
||||
One of the options is to set the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables.
|
||||
More information can be found [here](https://docs.aws.amazon.com/bedrock/latest/userguide/security-iam.html).
|
||||
|
||||
## Cohere Models
|
||||
- `BedrockCohereEmbeddingModel`
|
||||
|
||||
## Cohere Embedding Models
|
||||
Support is provided for Bedrock Cohere embedding models, enabling the use of the following versions:
|
||||
|
||||
- **`cohere.embed-english-v3`**
|
||||
- **`cohere.embed-multilingual-v3`**
|
||||
|
||||
These models are ideal for generating high-quality text embeddings for English and multilingual text processing tasks.
|
||||
|
||||
### Implementation Example
|
||||
|
||||
Below is an example of how to configure and use a Bedrock embedding model:
|
||||
|
||||
```
|
||||
BedrockCohereEmbeddingModel embeddingModel = BedrockCohereEmbeddingModel
|
||||
.builder()
|
||||
.region(Region.US_EAST_1)
|
||||
.model("cohere.embed-multilingual-v3")
|
||||
.inputType(BedrockCohereEmbeddingModel.InputType.SEARCH_QUERY)
|
||||
.truncation(BedrockCohereEmbeddingModel.Truncate.NONE)
|
||||
.build();
|
||||
```
|
||||
|
||||
## APIs
|
||||
|
||||
- `BedrockTitanEmbeddingModel`
|
||||
- `BedrockCohereEmbeddingModel`
|
||||
|
||||
## Titan Multimodal Embeddings
|
||||
|
||||
`BedrockTitanEmbeddingModel` supports Amazon Titan Multimodal Embeddings (`amazon.titan-embed-image-v1`): it
|
||||
embeds text and/or a single image into one (fused) embedding.
|
||||
|
||||
```java
|
||||
EmbeddingModel model = BedrockTitanEmbeddingModel.builder()
|
||||
.model("amazon.titan-embed-image-v1")
|
||||
.region(Region.US_EAST_1)
|
||||
.build();
|
||||
|
||||
EmbeddingResponse response = model.embed(EmbeddingRequest.builder()
|
||||
.input(TextContent.from("a photo of a cat"), ImageContent.from(base64Data, "image/png"))
|
||||
.build());
|
||||
```
|
||||
|
||||
Titan requires **base64** image data (a URL is not supported). Listeners can be configured via
|
||||
`.listeners(...)`. See [Embedding Model](/tutorials/rag#embedding-model) for the request/response API.
|
||||
|
||||
## Examples
|
||||
|
||||
- [BedrockEmbeddingIT](https://github.com/langchain4j/langchain4j/blob/main/langchain4j-bedrock/src/test/java/dev/langchain4j/model/bedrock/BedrockEmbeddingIT.java)
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Azure OpenAI
|
||||
|
||||
:::note
|
||||
|
||||
This is the documentation for the `Azure OpenAI` integration, that uses the Azure SDK from Microsoft, and works best if you are using the Microsoft Java stack, including advanced Azure authentication mechanisms.
|
||||
|
||||
LangChain4j provides 3 different integrations with OpenAI for using embedding models, and this is #3 :
|
||||
|
||||
- [OpenAI](/integrations/language-models/open-ai) uses a custom Java implementation of the OpenAI REST API, that works best with Quarkus (as it uses the Quarkus REST client) and Spring (as it uses Spring's RestClient).
|
||||
- [OpenAI Official SDK](/integrations/language-models/open-ai-official) uses the official OpenAI Java SDK.
|
||||
- [Azure OpenAI](/integrations/language-models/azure-open-ai) uses the Azure SDK from Microsoft, and works best if you are using the Microsoft Java stack, including advanced Azure authentication mechanisms.
|
||||
|
||||
:::
|
||||
|
||||
Azure OpenAI provides a few embedding models (`text-embedding-3-small`, `text-embedding-ada-002`, etc.)
|
||||
that can be used to transforms text or images into a dimensional vector space.
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
### Plain Java
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-azure-open-ai</artifactId>
|
||||
<version>1.17.2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### Spring Boot
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-azure-open-ai-spring-boot-starter</artifactId>
|
||||
<version>1.17.2-beta27</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
## Creating `AzureOpenAiEmbeddingModel`
|
||||
|
||||
### Plain Java
|
||||
```java
|
||||
EmbeddingModel model = AzureOpenAiEmbeddingModel.builder()
|
||||
.apiKey(System.getenv("AZURE_OPENAI_KEY"))
|
||||
.deploymentName("text-embedding-3-small")
|
||||
.endpoint("https://langchain4j.openai.azure.com/")
|
||||
...
|
||||
.build();
|
||||
```
|
||||
|
||||
### Spring Boot
|
||||
Add to the `application.properties`:
|
||||
```properties
|
||||
langchain4j.azure-open-ai.embedding-model.endpoint=https://langchain4j.openai.azure.com/
|
||||
langchain4j.azure-open-ai.embedding-model.service-version=...
|
||||
langchain4j.azure-open-ai.embedding-model.api-key=${AZURE_OPENAI_KEY}
|
||||
langchain4j.azure-open-ai.embedding-model.deployment-name=text-embedding-3-small
|
||||
langchain4j.azure-open-ai.embedding-model.timeout=...
|
||||
langchain4j.azure-open-ai.embedding-model.max-retries=...
|
||||
langchain4j.azure-open-ai.embedding-model.log-requests-and-responses=...
|
||||
langchain4j.azure-open-ai.embedding-model.user-agent-suffix=...
|
||||
langchain4j.azure-open-ai.embedding-model.dimensions=...
|
||||
langchain4j.azure-open-ai.embedding-model.customHeaders=...
|
||||
```
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
- [AzureOpenAiEmbeddingModelExamples](https://github.com/langchain4j/langchain4j-examples/blob/main/azure-open-ai-examples/src/main/java/AzureOpenAiEmbeddingModelExamples.java)
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Cohere
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-cohere</artifactId>
|
||||
<version>1.17.2-beta27</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## APIs
|
||||
|
||||
- `CohereEmbeddingModel`
|
||||
|
||||
## Capabilities
|
||||
|
||||
- **Multimodal** (`embed-v4.0`): embeds text and images into a shared vector space; interleaved text + image is
|
||||
fused into a single embedding. Provide image inputs as `ImageContent` (URL or base64) in an `EmbeddingRequest`.
|
||||
- **Per-call parameters**: `input_type` — embed queries and documents differently
|
||||
(`EmbeddingInputType.QUERY` / `DOCUMENT`, mapped to Cohere's `search_query` / `search_document`).
|
||||
- **Listeners**: configure via `CohereEmbeddingModel.builder().listeners(...)`.
|
||||
|
||||
See [Embedding Model](/tutorials/rag#embedding-model) for the request/response API and multimodal usage.
|
||||
|
||||
## Examples
|
||||
|
||||
- [CohereEmbeddingModelIT](https://github.com/langchain4j/langchain4j/blob/main/langchain4j-cohere/src/test/java/dev/langchain4j/model/cohere/CohereEmbeddingModelIT.java)
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# DashScope
|
||||
|
||||
[DashScope](https://dashscope.aliyun.com/) is a platform developed by [Alibaba Cloud](https://www.alibabacloud.com/).
|
||||
It provides an interface for model visualization, monitoring, and debugging, particularly when working with AI/ML
|
||||
models in production environments. The platform allows users to visualize performance metrics, track model behavior, and
|
||||
identify potential issues early on in the deployment cycle.
|
||||
|
||||
[Qwen](https://tongyi.aliyun.com/) models are a series of generative AI models developed
|
||||
by [Alibaba Cloud](https://www.alibabacloud.com/). The Qwen family of models are specifically designed for tasks like
|
||||
text generation, summarization, question answering, and various NLP tasks.
|
||||
|
||||
You can refer
|
||||
to [DashScope Document](https://help.aliyun.com/zh/model-studio/getting-started/?spm=a2c4g.11186623.help-menu-2400256.d_0.6655453aLIyxGp)
|
||||
for more details. LangChain4j integrates with DashScope by
|
||||
Using [DashScope Java SDK](https://help.aliyun.com/zh/dashscope/java-sdk-best-practices?spm=a2c4g.11186623.0.0.272a1507Ne69ja)
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
:::note
|
||||
Since `1.0.0-alpha1`, `langchain4j-dashscope` has migrated to `langchain4j-community` and is renamed to
|
||||
`langchain4j-community-dashscope`.
|
||||
:::
|
||||
|
||||
Before `1.0.0-alpha1`:
|
||||
|
||||
```xml
|
||||
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-dashscope</artifactId>
|
||||
<version>${previous version here}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
`1.0.0-alpha1` and later:
|
||||
|
||||
```xml
|
||||
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-community-dashscope</artifactId>
|
||||
<version>${latest version here}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Or, you can use BOM to manage dependencies consistently:
|
||||
|
||||
```xml
|
||||
|
||||
<dependencyManagement>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-community-bom</artifactId>
|
||||
<version>${latest version here}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencyManagement>
|
||||
```
|
||||
|
||||
## Configurable Parameters
|
||||
|
||||
`QwenEmbeddingModel` has following parameters to configure when you initialize it:
|
||||
|
||||
| Property | Description | Default Value |
|
||||
|-----------|------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|
|
||||
| baseUrl | The URL to connect to. You can use HTTP or websocket to connect to DashScope | https://dashscope.aliyuncs.com/api/v1/services/embeddings/text-embedding/text-embedding |
|
||||
| apiKey | The API Key | |
|
||||
| modelName | The model to use. | text-embedding-v2 |
|
||||
|
||||
## Examples
|
||||
|
||||
- [QwenEmbeddingModelIT](https://github.com/langchain4j/langchain4j-community/blob/main/models/langchain4j-community-dashscope/src/test/java/dev/langchain4j/community/model/dashscope/QwenEmbeddingModelIT.java)
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
# GitHub Models [Deprecated]
|
||||
|
||||
This module is deprecated, please use the [OpenAI Official SDK module](/integrations/language-models/open-ai-official) instead.
|
||||
|
||||
:::note
|
||||
|
||||
This is the documentation for the `GitHub Models` integration, that uses the Azure AI Inference API to access GitHub Models.
|
||||
|
||||
LangChain4j provides 4 different integrations with OpenAI for using embedding models, and this is #4 :
|
||||
|
||||
- [OpenAI](/integrations/language-models/open-ai) uses a custom Java implementation of the OpenAI REST API, that works best with Quarkus (as it uses the Quarkus REST client) and Spring (as it uses Spring's RestClient).
|
||||
- [OpenAI Official SDK](/integrations/language-models/open-ai-official) uses the official OpenAI Java SDK.
|
||||
- [Azure OpenAI](/integrations/language-models/azure-open-ai) uses the Azure SDK from Microsoft, and works best if you are using the Microsoft Java stack, including advanced Azure authentication mechanisms.
|
||||
- [GitHub Models](/integrations/language-models/github-models) uses the Azure AI Inference API to access GitHub Models.
|
||||
|
||||
:::
|
||||
|
||||
If you want to develop a generative AI application, you can use GitHub Models to find and experiment with AI models for free.
|
||||
Once you are ready to bring your application to production, you can switch to a token from a paid Azure account.
|
||||
|
||||
## GitHub Models Documentation
|
||||
|
||||
- [GitHub Models Documentation](https://docs.github.com/en/github-models)
|
||||
- [GitHub Models Marketplace](https://github.com/marketplace/models)
|
||||
|
||||
## Maven Dependency
|
||||
|
||||
### Plain Java
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-github-models</artifactId>
|
||||
<version>1.17.2-beta27</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## GitHub token
|
||||
|
||||
To use GitHub Models, you need to use a GitHub token for authentication.
|
||||
|
||||
Token are created and managed in [GitHub Developer Settings > Personal access tokens](https://github.com/settings/tokens).
|
||||
|
||||
Once you have a token, you can set it as an environment variable and use it in your code:
|
||||
|
||||
```bash
|
||||
export GITHUB_TOKEN="<your-github-token-goes-here>"
|
||||
```
|
||||
|
||||
## Creating a `GitHubModelsEmbeddingModel` with a GitHub token
|
||||
|
||||
```java
|
||||
GitHubModelsEmbeddingModel model = GitHubModelsEmbeddingModel.builder()
|
||||
.gitHubToken(System.getenv("GITHUB_TOKEN"))
|
||||
.modelName(TEXT_EMBEDDING_3_SMALL)
|
||||
.logRequestsAndResponses(true)
|
||||
.build();
|
||||
```
|
||||
|
||||
This will create an instance of `GitHubModelsEmbeddingModel`.
|
||||
|
||||
## Using the model
|
||||
|
||||
```java
|
||||
Response<Embedding> response = model.embed("Please embed this sentence.");
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
- [GitHub Models Examples](https://github.com/langchain4j/langchain4j-examples/tree/main/github-models-examples/src/main/java)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user