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

This commit is contained in:
wehub-resource-sync
2026-07-13 13:38:45 +08:00
commit a2578d7ff1
3635 changed files with 832123 additions and 0 deletions
+34
View File
@@ -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
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
*.onnx filter=lfs diff=lfs merge=lfs -text
+15
View File
@@ -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']
+31
View File
@@ -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. -->
+20
View File
@@ -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. -->
+42
View File
@@ -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/**'
+45
View File
@@ -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
+57
View File
@@ -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
+379
View File
@@ -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
+226
View File
@@ -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'
+226
View File
@@ -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'
+222
View File
@@ -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'
+121
View File
@@ -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'
+190
View File
@@ -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"
+49
View File
@@ -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"
+5
View File
@@ -0,0 +1,5 @@
extends: default
rules:
line-length: disable
commas: disable
+50
View File
@@ -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/
+3
View File
@@ -0,0 +1,3 @@
--add-opens=java.base/java.lang=ALL-UNNAMED
--add-opens=java.base/java.io=ALL-UNNAMED
-Djava.awt.headless=true
+3
View File
@@ -0,0 +1,3 @@
-Dkotlin.compiler.incremental=true
-Dmaven.wagon.http.retryHandler.count=5
-Dmaven.wagon.http.retryHandler.requestSentEnabled=true
BIN
View File
Binary file not shown.
+3
View File
@@ -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
+4
View File
@@ -0,0 +1,4 @@
{
"tabWidth": 4,
"printWidth": 120
}
+128
View File
@@ -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.
+65
View File
@@ -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.
+201
View File
@@ -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.
+26
View File
@@ -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 projects identity
and avoid confusion about what is or isnt 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.
+15
View File
@@ -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
+85
View File
@@ -0,0 +1,85 @@
# LangChain4j: idiomatic, open-source Java library for building LLM-powered applications on the JVM
[![Build Status](https://img.shields.io/github/actions/workflow/status/langchain4j/langchain4j/main.yaml?branch=main&style=for-the-badge&label=CI%20BUILD&logo=github)](https://github.com/langchain4j/langchain4j/actions/workflows/main.yaml)
[![Nightly Build](https://img.shields.io/github/actions/workflow/status/langchain4j/langchain4j/nightly_jdk17.yaml?branch=main&style=for-the-badge&label=NIGHTLY%20BUILD&logo=github)](https://github.com/langchain4j/langchain4j/actions/workflows/nightly_jdk17.yaml)
[![CODACY](https://img.shields.io/badge/Codacy-Dashboard-blue?style=for-the-badge&logo=codacy)](https://app.codacy.com/gh/langchain4j/langchain4j/dashboard)
[![Discord](https://img.shields.io/discord/1156626270772269217?logo=discord&style=for-the-badge)](https://discord.gg/JzTFvyjG6R)
[![BlueSky](https://img.shields.io/badge/@langchain4j-follow-blue?logo=bluesky&style=for-the-badge)](https://bsky.app/profile/langchain4j.dev)
[![X](https://img.shields.io/badge/@langchain4j-follow-blue?logo=x&style=for-the-badge)](https://x.com/langchain4j)
[![Maven Version](https://img.shields.io/maven-central/v/dev.langchain4j/langchain4j?logo=apachemaven&style=for-the-badge)](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).
+7
View File
@@ -0,0 +1,7 @@
# WeHub 来源说明
- 原始项目:`langchain4j/langchain4j`
- 原始仓库:https://github.com/langchain4j/langchain4j
- 导入方式:上游默认分支的最新快照
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
+39
View File
@@ -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-----
```
+70
View File
@@ -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>
@@ -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());
}
}
}
@@ -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>
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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"));
}
}
@@ -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"));
}
}
@@ -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");
}
}
@@ -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>
@@ -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;
}
}
@@ -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);
}
}
}
@@ -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;
}
}
}
@@ -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);
}
}
}
@@ -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);");
}
}
@@ -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");
}
}
+8
View File
@@ -0,0 +1,8 @@
style:
active: true
MethodName:
active: true
ignoreOverriddenFunctions: true
excludeAnnotatedMethodsOrClasses:
- "org.junit.jupiter.api.Test"
ignoreTestFiles: true
+20
View File
@@ -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*
+1
View File
@@ -0,0 +1 @@
20
+7
View File
@@ -0,0 +1,7 @@
build-docs:
npm ci; \
npm run build
run-docs:
npm ci; \
npm run start
+42
View File
@@ -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
```
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};
Executable
+5
View File
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
npm ci
npm run build
+145
View File
@@ -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
```
+8
View File
@@ -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