From 062e6ddf4793ea8ad19bfff0cff2b466826088f2 Mon Sep 17 00:00:00 2001
From: songyongtan <271667068@qq.com>
Date: Sat, 12 Apr 2025 16:52:01 +0800
Subject: [PATCH 01/42] [fel] chore: merge fel codebase
---
.../java/fel-community/model-openai/pom.xml | 15 +-
.../community/model/openai/OpenAiModel.java | 138 +++-
.../community/model/openai/api/OpenAiApi.java | 5 +
.../openai/entity/chat/OpenAiToolCall.java | 14 +-
.../openai/entity/image/OpenAiImage.java | 40 +
.../entity/image/OpenAiImageRequest.java | 34 +
.../entity/image/OpenAiImageResponse.java | 34 +
.../model/openai/OpenAiModelTest.java | 95 +++
.../model/openai/TestModelController.java | 85 +++
.../entity/image/OpenAiImageEntityTest.java | 48 ++
framework/fel/java/fel-community/pom.xml | 1 +
.../fel-community/tokenizer-hanlp/pom.xml | 90 +++
.../tokenizer/hanlp/HanlpTokenizer.java | 42 ++
.../src/main/resources/application.yml | 4 +
.../tokenizer/hanlp/HanlpTokenizerTest.java | 31 +
framework/fel/java/fel-core/pom.xml | 19 +
.../modelengine/fel/core/chat/ChatOption.java | 55 +-
.../fel/core/chat/MessageType.java | 18 +
.../fel/core/chat/support/ChatMessages.java | 12 +
.../core/chat/support/FlatChatMessage.java | 83 +++
.../fel/core/document/Measurable.java | 10 +
.../fel/core/document/MeasurableDocument.java | 49 +-
.../fel/core/document/support/RerankApi.java | 20 +
.../support/RerankDocumentProcessor.java | 99 +++
.../core/document/support/RerankOption.java | 109 +++
.../core/document/support/RerankRequest.java | 42 ++
.../core/document/support/RerankResponse.java | 58 ++
.../postprocessor/RrfPostProcessor.java | 104 +++
.../postprocessor/RrfScoreStrategyEnum.java | 25 +
.../fel/core/image/ImageModel.java | 28 +
.../fel/core/image/ImageOption.java | 98 +++
.../fel/core/model/BlockModel.java | 17 +
.../fel/core/model/http/SecureConfig.java | 128 ++++
.../support/DefaultStringTemplate.java | 2 +-
.../fel/core/tokenizer/Tokenizer.java | 8 +
.../modelengine/fel/core/tool/ToolCall.java | 17 +-
.../fel/core/tool/ToolCallChunk.java | 22 +
.../modelengine/fel/core/tool/ToolInfo.java | 10 +-
.../tool/support/DefaultToolCallChunk.java | 71 ++
.../support/RerankDocumentProcessorTest.java | 108 +++
.../support/RrfPostProcessorTest.java | 90 +++
.../support/TestRerankModelController.java | 55 ++
.../splitter/support/SimpleTokenizer.java | 8 +
.../fel/engine/activities/AiStart.java | 17 +
.../modelengine/fel/engine/flows/AiFlows.java | 14 +
.../fel/engine/flows/Conversation.java | 20 +
.../operators/models/ChatBlockModel.java | 90 +++
.../engine/operators/models/ChatChunk.java | 125 ++++
.../operators/models/ChatFlowModel.java | 2 +-
.../engine/operators/models/LlmEmitter.java | 46 +-
.../operators/models/StreamingConsumer.java | 24 +
.../fel/engine/operators/sources/Source.java | 57 ++
.../modelengine/fel/engine/util/StateKey.java | 20 +
.../fel/engine/operators/ModelTest.java | 4 +-
.../fel/java/fel-jacoco-aggregator/pom.xml | 79 ++
framework/fel/java/fel-pipeline-core/pom.xml | 86 +++
.../modelengine/fel/pipeline/Pipeline.java | 19 +
.../fel/pipeline/PipelineInput.java | 15 +
.../huggingface/ExplicitPipeline.java | 49 ++
.../pipeline/huggingface/GeneralPipeline.java | 54 ++
.../pipeline/huggingface/PipelineTask.java | 120 +++
.../pipeline/huggingface/asr/AsrInput.java | 51 ++
.../pipeline/huggingface/asr/AsrOutput.java | 31 +
.../huggingface/asr/AsrOutputChunk.java | 39 +
.../pipeline/huggingface/asr/AsrPipeline.java | 29 +
.../huggingface/img2img/Image2ImageInput.java | 35 +
.../img2img/Image2ImagePipeline.java | 32 +
.../huggingface/text2img/Text2ImageInput.java | 35 +
.../text2img/Text2ImagePipeline.java | 32 +
.../pipeline/huggingface/tts/TtsInput.java | 40 +
.../pipeline/huggingface/tts/TtsOutput.java | 28 +
.../pipeline/huggingface/tts/TtsPipeline.java | 29 +
.../pipeline/huggingface/type/Constant.java | 26 +
.../pipeline/huggingface/PipelineFactory.java | 52 ++
.../pipeline/huggingface/PipelineTest.java | 73 ++
.../huggingface/PipelineTestCase.java | 23 +
.../src/test/resources/test_case.json | 114 +++
.../plugins/fel-langchain-runnable/pom.xml | 97 +++
.../LangChainRunnableServiceImpl.java | 44 ++
.../src/main/resources/application.yml | 4 +
framework/fel/java/plugins/pom.xml | 1 +
.../services/fel-langchain-service/pom.xml | 65 ++
.../langchain/LangChainRunnableService.java | 28 +
.../services/fel-pipeline-service/pom.xml | 75 ++
.../pipeline/HuggingFacePipelineService.java | 36 +
framework/fel/java/services/pom.xml | 2 +
framework/fel/python/fel_core/__init__.py | 0
.../fel/python/fel_core/types/__init__.py | 0
.../fel/python/fel_core/types/document.py | 19 +
framework/fel/python/fel_core/types/media.py | 15 +
.../fel/python/fel_core/types/serializable.py | 22 +
.../fel/python/fel_langchain/__init__.py | 0
.../fel_langchain/langchain_registers.py | 93 +++
.../fel_langchain/langchain_schema_helper.py | 27 +
.../fel/python/fel_llama_index/__init__.py | 0
.../fel_llama_index/llama_schema_helper.py | 110 +++
.../fel/python/fel_llama_index/node_utils.py | 46 ++
.../fit_py_code_node_tools/python_repl.py | 6 +-
.../python_repl_impl.py | 7 +-
.../fit_py_code_node_tools/safe_global.py | 6 +-
.../test_python_repl_impl.py | 5 +-
.../fit_py_code_node_tools/tools.json | 37 +
.../callable_registers.py | 26 +
.../document_util.py | 8 +
.../langchain_loader_tools.py | 100 +++
.../fel_langchain_loader_tools/tools.json | 418 +++++++++++
.../types/__init__.py | 0
.../types/document.py | 19 +
.../fel_langchain_loader_tools/types/media.py | 15 +
.../types/serializable.py | 22 +
.../callable_registers.py | 26 +
.../langchain_network_tool.py | 180 +++++
.../langchain_registers.py | 93 +++
.../langchain_schema_helper.py | 27 +
.../fel_langchain_tools/langchain_tools.py | 189 +++++
.../plugins/fel_langchain_tools/tools.json | 353 +++++++++
.../callable_registers.py | 26 +
.../llama_rag_basic_toolkit.py | 155 ++++
.../llama_schema_helper.py | 123 ++++
.../fel_llama_index_tools/node_utils.py | 55 ++
.../plugins/fel_llama_index_tools/tools.json | 685 ++++++++++++++++++
.../fel_llama_index_tools/types/__init__.py | 0
.../fel_llama_index_tools/types/document.py | 19 +
.../fel_llama_index_tools/types/media.py | 15 +
.../types/serializable.py | 22 +
.../callable_registers.py | 26 +
.../llama_selector.py | 45 ++
.../fel_llama_selector_tools/tools.json | 91 +++
.../callable_registers.py | 26 +
.../llama_splitter_tool.py | 118 +++
.../fel_llama_splitter_tools/node_utils.py | 59 ++
.../fel_llama_splitter_tools/tools.json | 368 ++++++++++
.../types/__init__.py | 0
.../types/document.py | 19 +
.../fel_llama_splitter_tools/types/media.py | 15 +
.../types/serializable.py | 22 +
.../callable_registers.py | 26 +
.../llamaindex_network_tool.py | 23 +
framework/fel/python/setup.py | 27 +
framework/waterflow/java/pom.xml | 15 +-
.../waterflow/java/waterflow-common/pom.xml | 61 ++
.../modelengine/fit/waterflow/ErrorCodes.java | 298 ++++++++
.../entity/DefaultOperationContext.java | 140 ++++
.../waterflow/entity/OperationContext.java | 123 ++++
.../exceptions/BadRequestException.java | 6 +-
.../exceptions/ServerInternalException.java | 38 +
.../exceptions/WaterflowException.java | 6 +-
.../exceptions/WaterflowParamException.java | 6 +-
.../fit/waterflow/utils/Dates.java | 84 +++
.../fit/waterflow/utils/Entities.java | 283 ++++++++
.../waterflow/java/waterflow-core/pom.xml | 3 +-
.../repo/flowcontext/FlowContextRepo.java | 4 +-
.../domain/enums/FlowDefinitionStatus.java | 4 +-
.../waterflow/domain/enums/FlowNodeType.java | 4 +-
.../domain/enums/FlowTraceStatus.java | 4 +-
.../waterflow/domain/enums/ParallelMode.java | 4 +-
.../waterflow/domain/stream/nodes/From.java | 5 +-
.../fit/waterflow/domain/stream/nodes/To.java | 6 +-
.../java/waterflow-dependency/pom.xml | 2 +-
.../pom.xml | 15 +-
.../waterflow-bridge-fit-reactor/pom.xml | 0
.../bridge/fitflow/FitBoundedEmitter.java | 13 +-
.../bridge/fitflow/FitBoundedEmitterTest.java | 5 +
.../fit/waterflow/common/ErrorCodes.java | 119 ---
.../spi/WaterflowExceptionNotify.java | 35 -
.../waterflow/spi/WaterflowNodeNotify.java | 33 -
.../waterflow/spi/WaterflowTaskHandler.java | 29 -
167 files changed, 8763 insertions(+), 319 deletions(-)
create mode 100644 framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/entity/image/OpenAiImage.java
create mode 100644 framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/entity/image/OpenAiImageRequest.java
create mode 100644 framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/entity/image/OpenAiImageResponse.java
create mode 100644 framework/fel/java/fel-community/model-openai/src/test/java/modelengine/fel/community/model/openai/OpenAiModelTest.java
create mode 100644 framework/fel/java/fel-community/model-openai/src/test/java/modelengine/fel/community/model/openai/TestModelController.java
create mode 100644 framework/fel/java/fel-community/model-openai/src/test/java/modelengine/fel/community/model/openai/entity/image/OpenAiImageEntityTest.java
create mode 100644 framework/fel/java/fel-community/tokenizer-hanlp/pom.xml
create mode 100644 framework/fel/java/fel-community/tokenizer-hanlp/src/main/java/modelengine/fel/community/tokenizer/hanlp/HanlpTokenizer.java
create mode 100644 framework/fel/java/fel-community/tokenizer-hanlp/src/main/resources/application.yml
create mode 100644 framework/fel/java/fel-community/tokenizer-hanlp/src/test/java/modelengine/fel/community/tokenizer/hanlp/HanlpTokenizerTest.java
create mode 100644 framework/fel/java/fel-core/src/main/java/modelengine/fel/core/chat/support/FlatChatMessage.java
create mode 100644 framework/fel/java/fel-core/src/main/java/modelengine/fel/core/document/support/RerankApi.java
create mode 100644 framework/fel/java/fel-core/src/main/java/modelengine/fel/core/document/support/RerankDocumentProcessor.java
create mode 100644 framework/fel/java/fel-core/src/main/java/modelengine/fel/core/document/support/RerankOption.java
create mode 100644 framework/fel/java/fel-core/src/main/java/modelengine/fel/core/document/support/RerankRequest.java
create mode 100644 framework/fel/java/fel-core/src/main/java/modelengine/fel/core/document/support/RerankResponse.java
create mode 100644 framework/fel/java/fel-core/src/main/java/modelengine/fel/core/document/support/postprocessor/RrfPostProcessor.java
create mode 100644 framework/fel/java/fel-core/src/main/java/modelengine/fel/core/document/support/postprocessor/RrfScoreStrategyEnum.java
create mode 100644 framework/fel/java/fel-core/src/main/java/modelengine/fel/core/image/ImageModel.java
create mode 100644 framework/fel/java/fel-core/src/main/java/modelengine/fel/core/image/ImageOption.java
create mode 100644 framework/fel/java/fel-core/src/main/java/modelengine/fel/core/model/BlockModel.java
create mode 100644 framework/fel/java/fel-core/src/main/java/modelengine/fel/core/model/http/SecureConfig.java
create mode 100644 framework/fel/java/fel-core/src/main/java/modelengine/fel/core/tool/ToolCallChunk.java
create mode 100644 framework/fel/java/fel-core/src/main/java/modelengine/fel/core/tool/support/DefaultToolCallChunk.java
create mode 100644 framework/fel/java/fel-core/src/test/java/modelengine/fel/core/document/support/RerankDocumentProcessorTest.java
create mode 100644 framework/fel/java/fel-core/src/test/java/modelengine/fel/core/document/support/RrfPostProcessorTest.java
create mode 100644 framework/fel/java/fel-core/src/test/java/modelengine/fel/core/document/support/TestRerankModelController.java
create mode 100644 framework/fel/java/fel-flow/src/main/java/modelengine/fel/engine/operators/models/ChatBlockModel.java
create mode 100644 framework/fel/java/fel-flow/src/main/java/modelengine/fel/engine/operators/models/ChatChunk.java
create mode 100644 framework/fel/java/fel-flow/src/main/java/modelengine/fel/engine/operators/models/StreamingConsumer.java
create mode 100644 framework/fel/java/fel-flow/src/main/java/modelengine/fel/engine/operators/sources/Source.java
create mode 100644 framework/fel/java/fel-jacoco-aggregator/pom.xml
create mode 100644 framework/fel/java/fel-pipeline-core/pom.xml
create mode 100644 framework/fel/java/fel-pipeline-core/src/main/java/modelengine/fel/pipeline/Pipeline.java
create mode 100644 framework/fel/java/fel-pipeline-core/src/main/java/modelengine/fel/pipeline/PipelineInput.java
create mode 100644 framework/fel/java/fel-pipeline-core/src/main/java/modelengine/fel/pipeline/huggingface/ExplicitPipeline.java
create mode 100644 framework/fel/java/fel-pipeline-core/src/main/java/modelengine/fel/pipeline/huggingface/GeneralPipeline.java
create mode 100644 framework/fel/java/fel-pipeline-core/src/main/java/modelengine/fel/pipeline/huggingface/PipelineTask.java
create mode 100644 framework/fel/java/fel-pipeline-core/src/main/java/modelengine/fel/pipeline/huggingface/asr/AsrInput.java
create mode 100644 framework/fel/java/fel-pipeline-core/src/main/java/modelengine/fel/pipeline/huggingface/asr/AsrOutput.java
create mode 100644 framework/fel/java/fel-pipeline-core/src/main/java/modelengine/fel/pipeline/huggingface/asr/AsrOutputChunk.java
create mode 100644 framework/fel/java/fel-pipeline-core/src/main/java/modelengine/fel/pipeline/huggingface/asr/AsrPipeline.java
create mode 100644 framework/fel/java/fel-pipeline-core/src/main/java/modelengine/fel/pipeline/huggingface/img2img/Image2ImageInput.java
create mode 100644 framework/fel/java/fel-pipeline-core/src/main/java/modelengine/fel/pipeline/huggingface/img2img/Image2ImagePipeline.java
create mode 100644 framework/fel/java/fel-pipeline-core/src/main/java/modelengine/fel/pipeline/huggingface/text2img/Text2ImageInput.java
create mode 100644 framework/fel/java/fel-pipeline-core/src/main/java/modelengine/fel/pipeline/huggingface/text2img/Text2ImagePipeline.java
create mode 100644 framework/fel/java/fel-pipeline-core/src/main/java/modelengine/fel/pipeline/huggingface/tts/TtsInput.java
create mode 100644 framework/fel/java/fel-pipeline-core/src/main/java/modelengine/fel/pipeline/huggingface/tts/TtsOutput.java
create mode 100644 framework/fel/java/fel-pipeline-core/src/main/java/modelengine/fel/pipeline/huggingface/tts/TtsPipeline.java
create mode 100644 framework/fel/java/fel-pipeline-core/src/main/java/modelengine/fel/pipeline/huggingface/type/Constant.java
create mode 100644 framework/fel/java/fel-pipeline-core/src/test/java/modelengine/fel/pipeline/huggingface/PipelineFactory.java
create mode 100644 framework/fel/java/fel-pipeline-core/src/test/java/modelengine/fel/pipeline/huggingface/PipelineTest.java
create mode 100644 framework/fel/java/fel-pipeline-core/src/test/java/modelengine/fel/pipeline/huggingface/PipelineTestCase.java
create mode 100644 framework/fel/java/fel-pipeline-core/src/test/resources/test_case.json
create mode 100644 framework/fel/java/plugins/fel-langchain-runnable/pom.xml
create mode 100644 framework/fel/java/plugins/fel-langchain-runnable/src/main/java/modelengine/fel/plugin/langchain/LangChainRunnableServiceImpl.java
create mode 100644 framework/fel/java/plugins/fel-langchain-runnable/src/main/resources/application.yml
create mode 100644 framework/fel/java/services/fel-langchain-service/pom.xml
create mode 100644 framework/fel/java/services/fel-langchain-service/src/main/java/modelengine/fel/service/langchain/LangChainRunnableService.java
create mode 100644 framework/fel/java/services/fel-pipeline-service/pom.xml
create mode 100644 framework/fel/java/services/fel-pipeline-service/src/main/java/modelengine/fel/service/pipeline/HuggingFacePipelineService.java
create mode 100644 framework/fel/python/fel_core/__init__.py
create mode 100644 framework/fel/python/fel_core/types/__init__.py
create mode 100644 framework/fel/python/fel_core/types/document.py
create mode 100644 framework/fel/python/fel_core/types/media.py
create mode 100644 framework/fel/python/fel_core/types/serializable.py
create mode 100644 framework/fel/python/fel_langchain/__init__.py
create mode 100644 framework/fel/python/fel_langchain/langchain_registers.py
create mode 100644 framework/fel/python/fel_langchain/langchain_schema_helper.py
create mode 100644 framework/fel/python/fel_llama_index/__init__.py
create mode 100644 framework/fel/python/fel_llama_index/llama_schema_helper.py
create mode 100644 framework/fel/python/fel_llama_index/node_utils.py
create mode 100644 framework/fel/python/plugins/builtins/fit_py_code_node_tools/tools.json
create mode 100644 framework/fel/python/plugins/fel_langchain_loader_tools/callable_registers.py
create mode 100644 framework/fel/python/plugins/fel_langchain_loader_tools/document_util.py
create mode 100644 framework/fel/python/plugins/fel_langchain_loader_tools/langchain_loader_tools.py
create mode 100644 framework/fel/python/plugins/fel_langchain_loader_tools/tools.json
create mode 100644 framework/fel/python/plugins/fel_langchain_loader_tools/types/__init__.py
create mode 100644 framework/fel/python/plugins/fel_langchain_loader_tools/types/document.py
create mode 100644 framework/fel/python/plugins/fel_langchain_loader_tools/types/media.py
create mode 100644 framework/fel/python/plugins/fel_langchain_loader_tools/types/serializable.py
create mode 100644 framework/fel/python/plugins/fel_langchain_network_tools/callable_registers.py
create mode 100644 framework/fel/python/plugins/fel_langchain_network_tools/langchain_network_tool.py
create mode 100644 framework/fel/python/plugins/fel_langchain_tools/langchain_registers.py
create mode 100644 framework/fel/python/plugins/fel_langchain_tools/langchain_schema_helper.py
create mode 100644 framework/fel/python/plugins/fel_langchain_tools/langchain_tools.py
create mode 100644 framework/fel/python/plugins/fel_langchain_tools/tools.json
create mode 100644 framework/fel/python/plugins/fel_llama_index_tools/callable_registers.py
create mode 100644 framework/fel/python/plugins/fel_llama_index_tools/llama_rag_basic_toolkit.py
create mode 100644 framework/fel/python/plugins/fel_llama_index_tools/llama_schema_helper.py
create mode 100644 framework/fel/python/plugins/fel_llama_index_tools/node_utils.py
create mode 100644 framework/fel/python/plugins/fel_llama_index_tools/tools.json
create mode 100644 framework/fel/python/plugins/fel_llama_index_tools/types/__init__.py
create mode 100644 framework/fel/python/plugins/fel_llama_index_tools/types/document.py
create mode 100644 framework/fel/python/plugins/fel_llama_index_tools/types/media.py
create mode 100644 framework/fel/python/plugins/fel_llama_index_tools/types/serializable.py
create mode 100644 framework/fel/python/plugins/fel_llama_selector_tools/callable_registers.py
create mode 100644 framework/fel/python/plugins/fel_llama_selector_tools/llama_selector.py
create mode 100644 framework/fel/python/plugins/fel_llama_selector_tools/tools.json
create mode 100644 framework/fel/python/plugins/fel_llama_splitter_tools/callable_registers.py
create mode 100644 framework/fel/python/plugins/fel_llama_splitter_tools/llama_splitter_tool.py
create mode 100644 framework/fel/python/plugins/fel_llama_splitter_tools/node_utils.py
create mode 100644 framework/fel/python/plugins/fel_llama_splitter_tools/tools.json
create mode 100644 framework/fel/python/plugins/fel_llama_splitter_tools/types/__init__.py
create mode 100644 framework/fel/python/plugins/fel_llama_splitter_tools/types/document.py
create mode 100644 framework/fel/python/plugins/fel_llama_splitter_tools/types/media.py
create mode 100644 framework/fel/python/plugins/fel_llama_splitter_tools/types/serializable.py
create mode 100644 framework/fel/python/plugins/fel_llamaindex_network_tools/callable_registers.py
create mode 100644 framework/fel/python/plugins/fel_llamaindex_network_tools/llamaindex_network_tool.py
create mode 100644 framework/fel/python/setup.py
create mode 100644 framework/waterflow/java/waterflow-common/pom.xml
create mode 100644 framework/waterflow/java/waterflow-common/src/main/java/modelengine/fit/waterflow/ErrorCodes.java
create mode 100644 framework/waterflow/java/waterflow-common/src/main/java/modelengine/fit/waterflow/entity/DefaultOperationContext.java
create mode 100644 framework/waterflow/java/waterflow-common/src/main/java/modelengine/fit/waterflow/entity/OperationContext.java
rename framework/waterflow/java/{waterflow-genericable/src/main/java/modelengine/fit/waterflow/common => waterflow-common/src/main/java/modelengine/fit/waterflow}/exceptions/BadRequestException.java (84%)
create mode 100644 framework/waterflow/java/waterflow-common/src/main/java/modelengine/fit/waterflow/exceptions/ServerInternalException.java
rename framework/waterflow/java/{waterflow-genericable/src/main/java/modelengine/fit/waterflow/common => waterflow-common/src/main/java/modelengine/fit/waterflow}/exceptions/WaterflowException.java (90%)
rename framework/waterflow/java/{waterflow-genericable/src/main/java/modelengine/fit/waterflow/common => waterflow-common/src/main/java/modelengine/fit/waterflow}/exceptions/WaterflowParamException.java (85%)
create mode 100644 framework/waterflow/java/waterflow-common/src/main/java/modelengine/fit/waterflow/utils/Dates.java
create mode 100644 framework/waterflow/java/waterflow-common/src/main/java/modelengine/fit/waterflow/utils/Entities.java
rename framework/waterflow/java/{waterflow-genericable => waterflow-eco}/pom.xml (66%)
rename framework/waterflow/java/{ => waterflow-eco}/waterflow-bridge-fit-reactor/pom.xml (100%)
rename framework/waterflow/java/{ => waterflow-eco}/waterflow-bridge-fit-reactor/src/main/java/modelengine/fit/waterflow/bridge/fitflow/FitBoundedEmitter.java (85%)
rename framework/waterflow/java/{ => waterflow-eco}/waterflow-bridge-fit-reactor/src/test/java/modelengine/fit/waterflow/bridge/fitflow/FitBoundedEmitterTest.java (98%)
delete mode 100644 framework/waterflow/java/waterflow-genericable/src/main/java/modelengine/fit/waterflow/common/ErrorCodes.java
delete mode 100644 framework/waterflow/java/waterflow-genericable/src/main/java/modelengine/fit/waterflow/spi/WaterflowExceptionNotify.java
delete mode 100644 framework/waterflow/java/waterflow-genericable/src/main/java/modelengine/fit/waterflow/spi/WaterflowNodeNotify.java
delete mode 100644 framework/waterflow/java/waterflow-genericable/src/main/java/modelengine/fit/waterflow/spi/WaterflowTaskHandler.java
diff --git a/framework/fel/java/fel-community/model-openai/pom.xml b/framework/fel/java/fel-community/model-openai/pom.xml
index 8bfe6306..b2f7538e 100644
--- a/framework/fel/java/fel-community/model-openai/pom.xml
+++ b/framework/fel/java/fel-community/model-openai/pom.xml
@@ -29,6 +29,10 @@
org.fitframework
fit-util
+
+ org.fitframework.service
+ fit-security
+
@@ -53,6 +57,15 @@
org.assertj
assertj-core
+
+ org.fitframework
+ fit-test-framework
+
+
+ com.h2database
+ h2
+ test
+
@@ -90,7 +103,7 @@
+ todir="../../../../../framework/fit/java/target/plugins"/>
diff --git a/framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/OpenAiModel.java b/framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/OpenAiModel.java
index c1c0e57a..de9729da 100644
--- a/framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/OpenAiModel.java
+++ b/framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/OpenAiModel.java
@@ -17,6 +17,8 @@
import modelengine.fel.community.model.openai.entity.embed.OpenAiEmbedding;
import modelengine.fel.community.model.openai.entity.embed.OpenAiEmbeddingRequest;
import modelengine.fel.community.model.openai.entity.embed.OpenAiEmbeddingResponse;
+import modelengine.fel.community.model.openai.entity.image.OpenAiImageRequest;
+import modelengine.fel.community.model.openai.entity.image.OpenAiImageResponse;
import modelengine.fel.community.model.openai.util.HttpUtils;
import modelengine.fel.core.chat.ChatMessage;
import modelengine.fel.core.chat.ChatModel;
@@ -25,21 +27,39 @@
import modelengine.fel.core.embed.EmbedModel;
import modelengine.fel.core.embed.EmbedOption;
import modelengine.fel.core.embed.Embedding;
+import modelengine.fel.core.image.ImageModel;
+import modelengine.fel.core.image.ImageOption;
+import modelengine.fel.core.model.http.SecureConfig;
+import modelengine.fit.http.client.HttpClassicClient;
import modelengine.fit.http.client.HttpClassicClientFactory;
import modelengine.fit.http.client.HttpClassicClientRequest;
import modelengine.fit.http.client.HttpClassicClientResponse;
import modelengine.fit.http.entity.ObjectEntity;
import modelengine.fit.http.protocol.HttpRequestMethod;
+import modelengine.fit.security.Decryptor;
import modelengine.fitframework.annotation.Component;
+import modelengine.fitframework.annotation.Fit;
+import modelengine.fitframework.conf.Config;
import modelengine.fitframework.exception.FitException;
import modelengine.fitframework.flowable.Choir;
+import modelengine.fitframework.ioc.BeanContainer;
+import modelengine.fitframework.ioc.BeanFactory;
+import modelengine.fitframework.log.Logger;
import modelengine.fitframework.resource.UrlUtils;
+import modelengine.fitframework.resource.web.Media;
import modelengine.fitframework.serialization.ObjectSerializer;
import modelengine.fitframework.util.CollectionUtils;
+import modelengine.fitframework.util.LazyLoader;
+import modelengine.fitframework.util.MapBuilder;
+import modelengine.fitframework.util.ObjectUtils;
import modelengine.fitframework.util.StringUtils;
import java.io.IOException;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
/**
* 表示 openai 模型服务。
@@ -48,31 +68,52 @@
* @since 2024-08-07
*/
@Component
-public class OpenAiModel implements EmbedModel, ChatModel {
+public class OpenAiModel implements EmbedModel, ChatModel, ImageModel {
+ private static final Logger log = Logger.get(OpenAiModel.class);
+ private static final Map HTTPS_CONFIG_KEY_MAPS = MapBuilder.get()
+ .put("client.http.secure.ignore-trust", Boolean.FALSE)
+ .put("client.http.secure.ignore-hostname", Boolean.FALSE)
+ .put("client.http.secure.trust-store-file", Boolean.FALSE)
+ .put("client.http.secure.trust-store-password", Boolean.TRUE)
+ .put("client.http.secure.key-store-file", Boolean.FALSE)
+ .put("client.http.secure.key-store-password", Boolean.TRUE)
+ .build();
+
private final HttpClassicClientFactory httpClientFactory;
- private final HttpClassicClientFactory.Config config;
+ private final HttpClassicClientFactory.Config clientConfig;
private final String baseUrl;
private final String defaultApiKey;
private final ObjectSerializer serializer;
+ private final Config config;
+ private final Decryptor decryptor;
+ private final LazyLoader httpClient;
/**
* 创建 openai 嵌入模型服务的实例。
*
* @param httpClientFactory 表示 http 客户端工厂的 {@link HttpClassicClientFactory}。
- * @param config 表示 openai http 配置的 {@link OpenAiConfig}。
+ * @param clientConfig 表示 openai http 配置的 {@link OpenAiConfig}。
* @param serializer 表示对象序列化器的 {@link ObjectSerializer}。
+ * @param config 表示配置信息的 {@link Config}。
+ * @param container 表示 bean 容器的 {@link BeanContainer}。
* @throws IllegalArgumentException 当 {@code httpClientFactory}、{@code config} 为 {@code null} 时。
*/
- public OpenAiModel(HttpClassicClientFactory httpClientFactory, OpenAiConfig config, ObjectSerializer serializer) {
- notNull(config, "The config cannot be null.");
+ public OpenAiModel(HttpClassicClientFactory httpClientFactory, OpenAiConfig clientConfig,
+ @Fit(alias = "json") ObjectSerializer serializer, Config config, BeanContainer container) {
+ notNull(clientConfig, "The config cannot be null.");
this.httpClientFactory = notNull(httpClientFactory, "The http client factory cannot be null.");
- this.config = HttpClassicClientFactory.Config.builder()
- .connectTimeout(config.getConnectTimeout())
- .socketTimeout(config.getReadTimeout())
+ this.clientConfig = HttpClassicClientFactory.Config.builder()
+ .connectTimeout(clientConfig.getConnectTimeout())
+ .socketTimeout(clientConfig.getReadTimeout())
.build();
this.serializer = notNull(serializer, "The serializer cannot be null.");
- this.baseUrl = config.getApiBase();
- this.defaultApiKey = config.getApiKey();
+ this.baseUrl = clientConfig.getApiBase();
+ this.defaultApiKey = clientConfig.getApiKey();
+ this.httpClient = new LazyLoader<>(this::getHttpClient);
+ this.config = config;
+ this.decryptor = container.lookup(Decryptor.class)
+ .map(BeanFactory::get)
+ .orElseGet(() -> encrypted -> encrypted);
}
@Override
@@ -80,7 +121,7 @@ public List generate(List inputs, EmbedOption option) {
notEmpty(inputs, "The input cannot be empty.");
notNull(option, "The embed option cannot be null.");
notBlank(option.model(), "The embed model name cannot be null.");
- HttpClassicClientRequest request = this.httpClientFactory.create(this.config)
+ HttpClassicClientRequest request = this.httpClient.get()
.createRequest(HttpRequestMethod.POST, UrlUtils.combine(this.baseUrl, OpenAiApi.EMBEDDING_ENDPOINT));
HttpUtils.setBearerAuth(request, StringUtils.blankIf(option.apiKey(), this.defaultApiKey));
request.jsonEntity(new OpenAiEmbeddingRequest(inputs, option.model()));
@@ -98,13 +139,33 @@ public List generate(List inputs, EmbedOption option) {
public Choir generate(Prompt prompt, ChatOption chatOption) {
notNull(prompt, "The prompt cannot be null.");
notNull(chatOption, "The chat option cannot be null.");
- HttpClassicClientRequest request = this.httpClientFactory.create(this.config)
- .createRequest(HttpRequestMethod.POST, UrlUtils.combine(this.baseUrl, OpenAiApi.CHAT_ENDPOINT));
+ String modelSource = StringUtils.blankIf(chatOption.baseUrl(), this.baseUrl);
+ HttpClassicClientRequest request = this.getHttpClient(chatOption.secureConfig())
+ .createRequest(HttpRequestMethod.POST, UrlUtils.combine(modelSource, OpenAiApi.CHAT_ENDPOINT));
HttpUtils.setBearerAuth(request, StringUtils.blankIf(chatOption.apiKey(), this.defaultApiKey));
request.jsonEntity(new OpenAiChatCompletionRequest(prompt, chatOption));
return chatOption.stream() ? this.createChatStream(request) : this.createChatCompletion(request);
}
+ @Override
+ public List generate(String prompt, ImageOption option) {
+ notNull(prompt, "The prompt cannot be null.");
+ notNull(option, "The image option cannot be null.");
+ String modelSource = StringUtils.blankIf(option.baseUrl(), this.baseUrl);
+ HttpClassicClientRequest request = this.httpClient.get()
+ .createRequest(HttpRequestMethod.POST, UrlUtils.combine(modelSource, OpenAiApi.IMAGE_ENDPOINT));
+ HttpUtils.setBearerAuth(request, StringUtils.blankIf(option.apiKey(), this.defaultApiKey));
+ request.jsonEntity(new OpenAiImageRequest(option.model(), option.size(), prompt));
+ Class clazz = OpenAiImageResponse.class;
+ try (HttpClassicClientResponse response = request.exchange(clazz)) {
+ return response.objectEntity()
+ .map(entity -> entity.object().media())
+ .orElseThrow(() -> new FitException("The response body is abnormal."));
+ } catch (IOException e) {
+ throw new FitException(e);
+ }
+ }
+
private Choir createChatStream(HttpClassicClientRequest request) {
return request.exchangeStream(String.class)
.filter(str -> !StringUtils.equals(str, "[DONE]"))
@@ -124,4 +185,55 @@ private Choir createChatCompletion(HttpClassicClientRequest request
throw new FitException(e);
}
}
+
+ private HttpClassicClient getHttpClient() {
+ Map custom = HTTPS_CONFIG_KEY_MAPS.keySet()
+ .stream()
+ .filter(sslKey -> this.config.keys().contains(Config.canonicalizeKey(sslKey)))
+ .collect(Collectors.toMap(sslKey -> sslKey, sslKey -> {
+ Object value = this.config.get(sslKey, Object.class);
+ if (HTTPS_CONFIG_KEY_MAPS.get(sslKey)) {
+ value = this.decryptor.decrypt(ObjectUtils.cast(value));
+ }
+ return value;
+ }));
+
+ log.info("Create custom HTTPS config: {}", this.serializer.serialize(custom));
+ return this.httpClientFactory.create(HttpClassicClientFactory.Config.builder()
+ .socketTimeout(this.clientConfig.socketTimeout())
+ .connectTimeout(this.clientConfig.connectTimeout())
+ .custom(custom)
+ .build());
+ }
+
+ private HttpClassicClient getHttpClient(SecureConfig secureConfig) {
+ if (secureConfig == null) {
+ return getHttpClient();
+ }
+
+ Map custom = buildHttpsConfig(secureConfig);
+ log.info("Create custom HTTPS config: {}", this.serializer.serialize(custom));
+ return this.httpClientFactory.create(HttpClassicClientFactory.Config.builder()
+ .socketTimeout(this.clientConfig.socketTimeout())
+ .connectTimeout(this.clientConfig.connectTimeout())
+ .custom(custom)
+ .build());
+ }
+
+ private Map buildHttpsConfig(SecureConfig secureConfig) {
+ Map result = new HashMap<>();
+ putConfigIfNotNull(secureConfig.ignoreTrust(), "client.http.secure.ignore-trust", result);
+ putConfigIfNotNull(secureConfig.ignoreHostName(), "client.http.secure.ignore-hostname", result);
+ putConfigIfNotNull(secureConfig.trustStoreFile(), "client.http.secure.trust-store-file", result);
+ putConfigIfNotNull(secureConfig.trustStorePassword(), "client.http.secure.trust-store-password", result);
+ putConfigIfNotNull(secureConfig.keyStoreFile(), "client.http.secure.key-store-file", result);
+ putConfigIfNotNull(secureConfig.keyStorePassword(), "client.http.secure.key-store-password", result);
+ return result;
+ }
+
+ private static void putConfigIfNotNull(Object value, String key, Map result) {
+ if (value != null) {
+ result.put(key, value);
+ }
+ }
}
\ No newline at end of file
diff --git a/framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/api/OpenAiApi.java b/framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/api/OpenAiApi.java
index 1b64f02b..e59b4014 100644
--- a/framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/api/OpenAiApi.java
+++ b/framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/api/OpenAiApi.java
@@ -24,6 +24,11 @@ public interface OpenAiApi {
*/
String EMBEDDING_ENDPOINT = "/embeddings";
+ /**
+ * 图像生成请求的端点。
+ */
+ String IMAGE_ENDPOINT = "/images/generations";
+
/**
* 请求头模型密钥字段。
*/
diff --git a/framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/entity/chat/OpenAiToolCall.java b/framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/entity/chat/OpenAiToolCall.java
index 5f7eeb98..1675adc9 100644
--- a/framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/entity/chat/OpenAiToolCall.java
+++ b/framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/entity/chat/OpenAiToolCall.java
@@ -8,6 +8,7 @@
import modelengine.fel.core.tool.ToolCall;
import modelengine.fitframework.inspection.Nonnull;
+import modelengine.fitframework.serialization.annotation.SerializeStrategy;
/**
* 表示 {@link ToolCall} 的 openai 实现。
@@ -15,10 +16,12 @@
* @author 易文渊
* @since 2024-08-17
*/
+@SerializeStrategy(include = SerializeStrategy.Include.NON_NULL)
public class OpenAiToolCall implements ToolCall {
private String id;
private final String type = "function";
private FunctionCall function;
+ private Integer index;
/**
* 使用 {@link ToolCall} 构造一个新的 {@link OpenAiToolCall}。
@@ -33,6 +36,7 @@ public static OpenAiToolCall from(ToolCall toolCall) {
OpenAiToolCall openAiToolCall = new OpenAiToolCall();
openAiToolCall.id = toolCall.id();
openAiToolCall.function = functionCall;
+ openAiToolCall.index = toolCall.index();
return openAiToolCall;
}
@@ -42,6 +46,12 @@ public String id() {
return this.id;
}
+ @Nonnull
+ @Override
+ public Integer index() {
+ return this.index;
+ }
+
@Nonnull
@Override
public String name() {
@@ -64,7 +74,7 @@ public static class FunctionCall {
@Override
public String toString() {
- return "ToolCall{" + "id='" + id + '\'' + ", name='" + this.function.name + '\'' + ", arguments='"
- + this.function.arguments + '\'' + '}';
+ return "ToolCall{" + "id='" + id + '\'' + "index='" + index + '\'' + ", name='" + this.function.name + '\''
+ + ", arguments='" + this.function.arguments + '\'' + '}';
}
}
\ No newline at end of file
diff --git a/framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/entity/image/OpenAiImage.java b/framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/entity/image/OpenAiImage.java
new file mode 100644
index 00000000..07149d22
--- /dev/null
+++ b/framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/entity/image/OpenAiImage.java
@@ -0,0 +1,40 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
+ * This file is a part of the ModelEngine Project.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+package modelengine.fel.community.model.openai.entity.image;
+
+import modelengine.fitframework.annotation.Property;
+import modelengine.fitframework.exception.FitException;
+import modelengine.fitframework.resource.web.Media;
+import modelengine.fitframework.util.StringUtils;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * 表示 OpenAi 格式的图片。
+ *
+ * @author 何嘉斌
+ * @since 2024-12-17
+ */
+public class OpenAiImage {
+ @Property(name = "b64_json")
+ private String b64Json;
+ private String url;
+
+ /**
+ * 获取图片媒体资源。
+ *
+ * @return 表示图片媒体资源的 {@link Media}。
+ */
+ public Media media() {
+ try {
+ return StringUtils.isNotBlank(b64Json) ? new Media("image/jpeg", b64Json) : new Media(new URL(url));
+ } catch (MalformedURLException ex) {
+ throw new FitException(ex);
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/entity/image/OpenAiImageRequest.java b/framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/entity/image/OpenAiImageRequest.java
new file mode 100644
index 00000000..f428a68c
--- /dev/null
+++ b/framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/entity/image/OpenAiImageRequest.java
@@ -0,0 +1,34 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
+ * This file is a part of the ModelEngine Project.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+package modelengine.fel.community.model.openai.entity.image;
+
+import static modelengine.fitframework.inspection.Validation.notBlank;
+
+/**
+ * 表示 OpenAi Api 格式的图片生成请求。
+ *
+ * @author 何嘉斌
+ * @since 2024-12-17
+ */
+public class OpenAiImageRequest {
+ private final String model;
+ private final String size;
+ private final String prompt;
+
+ /**
+ * 创建一个新的 OpenAi API 格式的图片生成请求。
+ *
+ * @param model 表示调用的模型名称的 {@link String}。
+ * @param size 表示生成图片规格的 {@link String}。
+ * @param prompt 表示用户输入提示词的 {@link String}。
+ */
+ public OpenAiImageRequest(String model, String size, String prompt) {
+ this.model = notBlank(model, "The model cannot be blank.");
+ this.size = notBlank(size, "The image size cannot be blank.");
+ this.prompt = notBlank(prompt, "The prompt cannot be blank.");
+ }
+}
\ No newline at end of file
diff --git a/framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/entity/image/OpenAiImageResponse.java b/framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/entity/image/OpenAiImageResponse.java
new file mode 100644
index 00000000..a5911c0f
--- /dev/null
+++ b/framework/fel/java/fel-community/model-openai/src/main/java/modelengine/fel/community/model/openai/entity/image/OpenAiImageResponse.java
@@ -0,0 +1,34 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
+ * This file is a part of the ModelEngine Project.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+package modelengine.fel.community.model.openai.entity.image;
+
+import modelengine.fitframework.resource.web.Media;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 表示 OpenAi API 格式的图片生成响应。
+ *
+ * @author 何嘉斌
+ * @since 2024-12-17
+ */
+public class OpenAiImageResponse {
+ /**
+ * 模型生成的 Image 列表。
+ */
+ private List data;
+
+ /**
+ * 获取模型生成的图片列表。
+ *
+ * @return 表示模型嵌入向量列表的 {@link List}{@code <}{@link Media}{@code >}。
+ */
+ public List media() {
+ return this.data.stream().map(OpenAiImage::media).collect(Collectors.toList());
+ }
+}
\ No newline at end of file
diff --git a/framework/fel/java/fel-community/model-openai/src/test/java/modelengine/fel/community/model/openai/OpenAiModelTest.java b/framework/fel/java/fel-community/model-openai/src/test/java/modelengine/fel/community/model/openai/OpenAiModelTest.java
new file mode 100644
index 00000000..35c79d1e
--- /dev/null
+++ b/framework/fel/java/fel-community/model-openai/src/test/java/modelengine/fel/community/model/openai/OpenAiModelTest.java
@@ -0,0 +1,95 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
+ * This file is a part of the ModelEngine Project.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+package modelengine.fel.community.model.openai;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import modelengine.fel.community.model.openai.config.OpenAiConfig;
+import modelengine.fel.core.chat.ChatMessage;
+import modelengine.fel.core.chat.ChatOption;
+import modelengine.fel.core.chat.support.ChatMessages;
+import modelengine.fel.core.chat.support.HumanMessage;
+import modelengine.fel.core.embed.EmbedOption;
+import modelengine.fel.core.embed.Embedding;
+import modelengine.fel.core.image.ImageOption;
+import modelengine.fit.http.client.HttpClassicClientFactory;
+import modelengine.fitframework.annotation.Fit;
+import modelengine.fitframework.conf.Config;
+import modelengine.fitframework.flowable.Choir;
+import modelengine.fitframework.ioc.BeanContainer;
+import modelengine.fitframework.resource.web.Media;
+import modelengine.fitframework.serialization.ObjectSerializer;
+import modelengine.fitframework.test.annotation.MvcTest;
+import modelengine.fitframework.test.domain.mvc.MockMvc;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * {@link OpenAiModel} 的模型测试。
+ *
+ * @author 刘信宏
+ * @since 2024-09-23
+ */
+@MvcTest(classes = TestModelController.class)
+public class OpenAiModelTest {
+ private OpenAiModel openAiModel;
+
+ @Fit
+ private HttpClassicClientFactory httpClientFactory;
+
+ @Fit
+ private ObjectSerializer serializer;
+
+ @Fit
+ private Config config;
+
+ @Fit
+ private BeanContainer container;
+
+ @Fit
+ private MockMvc mockMvc;
+
+ @BeforeEach
+ public void setUp() {
+ OpenAiConfig openAiConfig = new OpenAiConfig();
+ openAiConfig.setApiBase("http://localhost:" + mockMvc.getPort());
+ this.openAiModel = new OpenAiModel(this.httpClientFactory, openAiConfig, this.serializer, config, container);
+ }
+
+ @Test
+ @DisplayName("测试聊天流式返回")
+ void testOpenAiChatModelStreamService() {
+ List contents = Arrays.asList("1", "2", "3");
+ Choir choir = this.openAiModel.generate(ChatMessages.from(new HumanMessage("hello")),
+ ChatOption.custom().stream(true).model("model").build());
+ List response = choir.blockAll();
+ assertThat(response).extracting(ChatMessage::text).isEqualTo(contents);
+ }
+
+ @Test
+ @DisplayName("测试嵌入模型返回")
+ void testOpenAiEmbeddingModel() {
+ Embedding embedding = this.openAiModel.generate("1", EmbedOption.custom().model("model").build());
+ assertThat(embedding.embedding()).containsExactly(1f, 2f, 3f);
+ }
+
+ @Test
+ @DisplayName("测试图片生成模型返回")
+ void testOpenAiImageModel() {
+ List images =
+ this.openAiModel.generate("prompt", ImageOption.custom().model("model").size("256x256").build());
+ assertThat(images.stream().map(Media::getData).collect(Collectors.toList())).containsExactly("123",
+ "456",
+ "789");
+ }
+}
\ No newline at end of file
diff --git a/framework/fel/java/fel-community/model-openai/src/test/java/modelengine/fel/community/model/openai/TestModelController.java b/framework/fel/java/fel-community/model-openai/src/test/java/modelengine/fel/community/model/openai/TestModelController.java
new file mode 100644
index 00000000..d32edff4
--- /dev/null
+++ b/framework/fel/java/fel-community/model-openai/src/test/java/modelengine/fel/community/model/openai/TestModelController.java
@@ -0,0 +1,85 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
+ * This file is a part of the ModelEngine Project.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+package modelengine.fel.community.model.openai;
+
+import static modelengine.fel.community.model.openai.api.OpenAiApi.CHAT_ENDPOINT;
+import static modelengine.fel.community.model.openai.api.OpenAiApi.EMBEDDING_ENDPOINT;
+import static modelengine.fel.community.model.openai.api.OpenAiApi.IMAGE_ENDPOINT;
+
+import modelengine.fel.community.model.openai.entity.embed.OpenAiEmbeddingResponse;
+import modelengine.fel.community.model.openai.entity.image.OpenAiImageResponse;
+import modelengine.fit.http.annotation.PostMapping;
+import modelengine.fitframework.annotation.Component;
+import modelengine.fitframework.flowable.Choir;
+import modelengine.fitframework.serialization.ObjectSerializer;
+
+/**
+ * 表示测试使用的聊天接口。
+ *
+ * @author 易文渊
+ * @since 2024-09-24
+ */
+@Component
+public class TestModelController {
+ private final ObjectSerializer serializer;
+
+ /**
+ * 创建 {@link TestModelController} 的实例。
+ *
+ * @param serializer 表示对象序列化器的 {@link ObjectSerializer}。
+ */
+ public TestModelController(ObjectSerializer serializer) {
+ this.serializer = serializer;
+ }
+
+ /**
+ * 测试用聊天接口。
+ *
+ * @return 表示流式返回结果的 {@link Choir}{@code <}{@link String}{@code >}。
+ */
+ @PostMapping(CHAT_ENDPOINT)
+ public Choir chat() {
+ return Choir.create(emitter -> {
+ for (int i = 1; i <= 3; ++i) {
+ emitter.emit(getMockStreamResponseChunk(String.valueOf(i)));
+ }
+ emitter.emit("[DONE]");
+ emitter.complete();
+ });
+ }
+
+ /**
+ * 测试用嵌入接口。
+ *
+ * @return 表示嵌入响应的 {@link OpenAiEmbeddingResponse}。
+ */
+ @PostMapping(EMBEDDING_ENDPOINT)
+ public OpenAiEmbeddingResponse embed() {
+ String json = "{\"object\":\"list\","
+ + "\"data\":[{\"index\":0,\"object\":\"embedding\",\"embedding\":[1.0,2.0,3.0]}],"
+ + "\"usage\":{\"prompt_tokens\":1,\"total_tokens\":2}}";
+ return this.serializer.deserialize(json, OpenAiEmbeddingResponse.class);
+ }
+
+ private String getMockStreamResponseChunk(String content) {
+ return "{\"id\": \"0\"," + "\"object\": \"chat.completion.chunk\"," + "\"created\": 0,"
+ + "\"model\": \"test_model\"," + "\"choices\": [{\"index\": 0,\"delta\": {\"content\": \"" + content
+ + "\"}," + "\"finish_reason\": null}]}";
+ }
+
+ /**
+ * 测试用图片生成接口。
+ *
+ * @return 表示嵌入响应的 {@link OpenAiImageResponse}。
+ */
+ @PostMapping(IMAGE_ENDPOINT)
+ public OpenAiImageResponse image() {
+ String json = "{\"object\":\"list\","
+ + "\"data\":[{\"b64_json\":\"123\"}, {\"b64_json\":\"456\"}, {\"b64_json\":\"789\"}]}";
+ return this.serializer.deserialize(json, OpenAiImageResponse.class);
+ }
+}
\ No newline at end of file
diff --git a/framework/fel/java/fel-community/model-openai/src/test/java/modelengine/fel/community/model/openai/entity/image/OpenAiImageEntityTest.java b/framework/fel/java/fel-community/model-openai/src/test/java/modelengine/fel/community/model/openai/entity/image/OpenAiImageEntityTest.java
new file mode 100644
index 00000000..577e7937
--- /dev/null
+++ b/framework/fel/java/fel-community/model-openai/src/test/java/modelengine/fel/community/model/openai/entity/image/OpenAiImageEntityTest.java
@@ -0,0 +1,48 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
+ * This file is a part of the ModelEngine Project.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+package modelengine.fel.community.model.openai.entity.image;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import modelengine.fit.serialization.json.jackson.JacksonObjectSerializer;
+import modelengine.fitframework.resource.web.Media;
+import modelengine.fitframework.serialization.ObjectSerializer;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+/**
+ * 测试 {@link modelengine.fel.community.model.openai.entity.image} 下对象的序列化和反序列化。
+ *
+ * @author 何嘉斌
+ * @since 2024-12-18
+ */
+public class OpenAiImageEntityTest {
+ private static final ObjectSerializer SERIALIZER = new JacksonObjectSerializer(null, null, null);
+
+ @Test
+ @DisplayName("测试序列化图片生成请求成功")
+ void giveOpenAiImageRequestThenSerializeOk() {
+ OpenAiImageRequest request = new OpenAiImageRequest("model", "256x256", "prompt");
+ String excepted = "{\"model\":\"model\",\"size\":\"256x256\",\"prompt\":\"prompt\"}";
+ assertThat(SERIALIZER.serialize(request)).isEqualTo(excepted);
+ }
+
+ @Test
+ @DisplayName("测试反序列化图片生成响应成功")
+ void giveOpenAiImageResponseThenDeserializeToMediaOk() {
+ String json = "{\"object\":\"list\"," + "\"data\":[{\"url\":\"https://huawei.com\"}, {\"b64_json\":\"456\"}]}";
+ OpenAiImageResponse response = SERIALIZER.deserialize(json, OpenAiImageResponse.class);
+ assertThat(response).extracting(r -> r.media().stream().map(Media::getMime).collect(Collectors.toList()))
+ .isEqualTo(Arrays.asList(null, "image/jpeg"));
+ assertThat(response).extracting(r -> r.media().stream().map(Media::getData).collect(Collectors.toList()))
+ .isEqualTo(Arrays.asList("https://huawei.com", "456"));
+ }
+}
\ No newline at end of file
diff --git a/framework/fel/java/fel-community/pom.xml b/framework/fel/java/fel-community/pom.xml
index b775aa49..5a056e15 100644
--- a/framework/fel/java/fel-community/pom.xml
+++ b/framework/fel/java/fel-community/pom.xml
@@ -14,5 +14,6 @@
model-openai
+ tokenizer-hanlp
\ No newline at end of file
diff --git a/framework/fel/java/fel-community/tokenizer-hanlp/pom.xml b/framework/fel/java/fel-community/tokenizer-hanlp/pom.xml
new file mode 100644
index 00000000..d92ea68e
--- /dev/null
+++ b/framework/fel/java/fel-community/tokenizer-hanlp/pom.xml
@@ -0,0 +1,90 @@
+
+
+ 4.0.0
+
+
+ org.fitframework.fel
+ fel-community-parent
+ 1.0.0-SNAPSHOT
+
+
+ fel-tokenizer-hanlp-plugin
+
+
+
+
+ org.fitframework
+ fit-api
+
+
+
+
+ org.fitframework.fel
+ fel-core
+
+
+
+
+ com.hankcs
+ hanlp
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+
+
+ org.assertj
+ assertj-core
+
+
+
+
+
+
+ org.fitframework
+ fit-build-maven-plugin
+ ${fit.version}
+
+ user
+ 1
+
+
+
+ build-plugin
+
+ build-plugin
+
+
+
+ package-plugin
+
+ package-plugin
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-antrun-plugin
+ ${maven.antrun.version}
+
+
+ package
+
+
+
+
+
+
+ run
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/framework/fel/java/fel-community/tokenizer-hanlp/src/main/java/modelengine/fel/community/tokenizer/hanlp/HanlpTokenizer.java b/framework/fel/java/fel-community/tokenizer-hanlp/src/main/java/modelengine/fel/community/tokenizer/hanlp/HanlpTokenizer.java
new file mode 100644
index 00000000..7a3061be
--- /dev/null
+++ b/framework/fel/java/fel-community/tokenizer-hanlp/src/main/java/modelengine/fel/community/tokenizer/hanlp/HanlpTokenizer.java
@@ -0,0 +1,42 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
+ * This file is a part of the ModelEngine Project.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+package modelengine.fel.community.tokenizer.hanlp;
+
+import com.hankcs.hanlp.HanLP;
+import com.hankcs.hanlp.seg.Segment;
+
+import modelengine.fel.core.tokenizer.Tokenizer;
+import modelengine.fitframework.annotation.Component;
+import modelengine.fitframework.util.StringUtils;
+
+import java.util.List;
+
+/**
+ * 表示 {@link Tokenizer} 的 hanlp 实现。
+ *
+ * @author 易文渊
+ * @since 2024-09-24
+ */
+@Component
+public class HanlpTokenizer implements Tokenizer {
+ private final Segment segment = HanLP.newSegment().enablePartOfSpeechTagging(false).enableOffset(false);
+
+ @Override
+ public List encode(String text) {
+ throw new UnsupportedOperationException("The operator encode is not support.");
+ }
+
+ @Override
+ public String decode(List tokens) {
+ throw new UnsupportedOperationException("The operator decode is not support.");
+ }
+
+ @Override
+ public int countToken(String text) {
+ return StringUtils.isBlank(text) ? 0 : segment.seg(text).size();
+ }
+}
\ No newline at end of file
diff --git a/framework/fel/java/fel-community/tokenizer-hanlp/src/main/resources/application.yml b/framework/fel/java/fel-community/tokenizer-hanlp/src/main/resources/application.yml
new file mode 100644
index 00000000..037b4809
--- /dev/null
+++ b/framework/fel/java/fel-community/tokenizer-hanlp/src/main/resources/application.yml
@@ -0,0 +1,4 @@
+fit:
+ beans:
+ packages:
+ - 'modelengine.fel.community.tokenizer.hanlp'
\ No newline at end of file
diff --git a/framework/fel/java/fel-community/tokenizer-hanlp/src/test/java/modelengine/fel/community/tokenizer/hanlp/HanlpTokenizerTest.java b/framework/fel/java/fel-community/tokenizer-hanlp/src/test/java/modelengine/fel/community/tokenizer/hanlp/HanlpTokenizerTest.java
new file mode 100644
index 00000000..b771f399
--- /dev/null
+++ b/framework/fel/java/fel-community/tokenizer-hanlp/src/test/java/modelengine/fel/community/tokenizer/hanlp/HanlpTokenizerTest.java
@@ -0,0 +1,31 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
+ * This file is a part of the ModelEngine Project.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+package modelengine.fel.community.tokenizer.hanlp;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import modelengine.fitframework.util.StringUtils;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/**
+ * 表示 {@link HanlpTokenizer} 的测试集。
+ *
+ * @author 易文渊
+ * @since 2024-09-24
+ */
+@DisplayName("测试 hanlpTokenizer")
+public class HanlpTokenizerTest {
+ @Test
+ @DisplayName("测试分词")
+ void testCountToken() {
+ HanlpTokenizer tokenizer = new HanlpTokenizer();
+ assertThat(tokenizer.countToken(StringUtils.EMPTY)).isEqualTo(0);
+ assertThat(tokenizer.countToken("你好")).isEqualTo(1);
+ }
+}
\ No newline at end of file
diff --git a/framework/fel/java/fel-core/pom.xml b/framework/fel/java/fel-core/pom.xml
index 192c8fe4..6229ed0c 100644
--- a/framework/fel/java/fel-core/pom.xml
+++ b/framework/fel/java/fel-core/pom.xml
@@ -25,6 +25,10 @@
org.fitframework
fit-util
+
+ org.fitframework.service
+ fit-http-classic
+
@@ -43,6 +47,21 @@
org.assertj
assertj-core
+
+ org.fitframework
+ fit-test-framework
+
+
+ com.h2database
+ h2
+ test
+
+
+
+
+ org.projectlombok
+ lombok
+
diff --git a/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/chat/ChatOption.java b/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/chat/ChatOption.java
index fcb04a0a..d524f794 100644
--- a/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/chat/ChatOption.java
+++ b/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/chat/ChatOption.java
@@ -7,6 +7,7 @@
package modelengine.fel.core.chat;
import modelengine.fel.core.tool.ToolInfo;
+import modelengine.fel.core.model.http.SecureConfig;
import modelengine.fitframework.inspection.Nonnull;
import modelengine.fitframework.pattern.builder.BuilderFactory;
@@ -39,6 +40,13 @@ public interface ChatOption {
@Nonnull
Boolean stream();
+ /**
+ * 大模型服务端地址。
+ *
+ * @return 表示大模型服务端地址的 {@link String}。
+ */
+ String baseUrl();
+
/**
* 获取模型接口秘钥。
*
@@ -122,6 +130,13 @@ public interface ChatOption {
*/
List tools();
+ /**
+ * 获取调用大模型服务的安全配置。
+ *
+ * @return 表示调用大模型服务安全配置的 {@link SecureConfig}。
+ */
+ SecureConfig secureConfig();
+
/**
* {@link ChatOption} 的构建器。
*/
@@ -137,10 +152,18 @@ interface Builder {
/**
* 设置是否使用流式接口。
*
- * @param stream 表示是否使用流式接口的 {@code boolean}。
+ * @param stream 表示是否使用流式接口的 {@code Boolean}。
+ * @return 表示当前构建器的 {@link Builder}。
+ */
+ Builder stream(Boolean stream);
+
+ /**
+ * 设置模型服务端地址。
+ *
+ * @param baseUrl 表示大模型服务端地址的 {@link String}。
* @return 表示当前构建器的 {@link Builder}。
*/
- Builder stream(boolean stream);
+ Builder baseUrl(String baseUrl);
/**
* 设置模型接口秘钥。
@@ -153,26 +176,26 @@ interface Builder {
/**
* 设置生成文本的最大长度。
*
- * @param maxTokens 表示生成文本最大长度的 {@code int}。
+ * @param maxTokens 表示生成文本最大长度的 {@code Integer}。
* @return 表示当前构建器的 {@link Builder}。
*/
- Builder maxTokens(int maxTokens);
+ Builder maxTokens(Integer maxTokens);
/**
* 设置频率惩罚系数。
*
- * @param frequencyPenalty 表示频率惩罚系数的 {@code double}。
+ * @param frequencyPenalty 表示频率惩罚系数的 {@code Double}。
* @return 表示当前构建器的 {@link Builder}。
*/
- Builder frequencyPenalty(double frequencyPenalty);
+ Builder frequencyPenalty(Double frequencyPenalty);
/**
* 设置文本出现惩罚系数。
*
- * @param presencePenalty 表示文本出现惩罚系数的 {@code double}。
+ * @param presencePenalty 表示文本出现惩罚系数的 {@code Double}。
* @return 表示当前构建器的 {@link Builder}。
*/
- Builder presencePenalty(double presencePenalty);
+ Builder presencePenalty(Double presencePenalty);
/**
* 设置停止字符串列表。
@@ -185,18 +208,18 @@ interface Builder {
/**
* 设置采样温度。
*
- * @param temperature 表示采样温度的 {@code double}。
+ * @param temperature 表示采样温度的 {@code Double}。
* @return 表示当前构建器的 {@link Builder}。
*/
- Builder temperature(double temperature);
+ Builder temperature(Double temperature);
/**
* 设置采样率。
*
- * @param topP 表示采样率的 {@code double}。
+ * @param topP 表示采样率的 {@code Double}。
* @return 表示当前构建器的 {@link Builder}。
*/
- Builder topP(double topP);
+ Builder topP(Double topP);
/**
* 设置模型能使用的工具列表。
@@ -206,6 +229,14 @@ interface Builder {
*/
Builder tools(List tools);
+ /**
+ * 设置调用大模型服务的安全配置。
+ *
+ * @param secureConfig 表示调用大模型服务安全配置的 {@link SecureConfig}。
+ * @return 表示当前构建器的 {@link Builder}。
+ */
+ Builder secureConfig(SecureConfig secureConfig);
+
/**
* 构建对象。
*
diff --git a/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/chat/MessageType.java b/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/chat/MessageType.java
index 5bc4fdb0..9ddea524 100644
--- a/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/chat/MessageType.java
+++ b/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/chat/MessageType.java
@@ -6,6 +6,11 @@
package modelengine.fel.core.chat;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
/**
* 表示消息类型的枚举。
*
@@ -35,6 +40,9 @@ public enum MessageType {
private final String role;
+ private static final Map RELATIONSHIP =
+ Arrays.stream(MessageType.values()).collect(Collectors.toMap(MessageType::getRole, Function.identity()));
+
MessageType(String role) {
this.role = role;
}
@@ -47,4 +55,14 @@ public enum MessageType {
public String getRole() {
return role;
}
+
+ /**
+ * 根据字符串获取 {@link MessageType} 的实例。
+ *
+ * @param role 表示消息角色的 {@link String}。
+ * @return 表示消息类型的 {@link MessageType}。
+ */
+ public static MessageType parse(String role) {
+ return RELATIONSHIP.getOrDefault(role, MessageType.HUMAN);
+ }
}
\ No newline at end of file
diff --git a/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/chat/support/ChatMessages.java b/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/chat/support/ChatMessages.java
index 5744f94b..d11f3efd 100644
--- a/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/chat/support/ChatMessages.java
+++ b/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/chat/support/ChatMessages.java
@@ -35,6 +35,18 @@ public static ChatMessages from(ChatMessage... message) {
return from(Arrays.asList(message));
}
+ /**
+ * 使用聊天消息数组创建 {@link ChatMessages} 的实例。
+ *
+ * @param messages 表示聊天消息的 {@link List}{@code extends }{@link ChatMessage}{@code >}。
+ * @return 表示创建成功的 {@link ChatMessages}。
+ */
+ public static ChatMessages fromList(List extends ChatMessage> messages) {
+ ChatMessages chatMessages = new ChatMessages();
+ chatMessages.messages().addAll(messages);
+ return chatMessages;
+ }
+
/**
* 从给定的提示中创建 {@link ChatMessages} 的实例。
*
diff --git a/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/chat/support/FlatChatMessage.java b/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/chat/support/FlatChatMessage.java
new file mode 100644
index 00000000..593db067
--- /dev/null
+++ b/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/chat/support/FlatChatMessage.java
@@ -0,0 +1,83 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
+ * This file is a part of the ModelEngine Project.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+package modelengine.fel.core.chat.support;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import modelengine.fel.core.chat.ChatMessage;
+import modelengine.fel.core.chat.MessageType;
+import modelengine.fel.core.tool.ToolCall;
+import modelengine.fitframework.inspection.Validation;
+import modelengine.fitframework.resource.web.Media;
+import modelengine.fitframework.util.ObjectUtils;
+import modelengine.fitframework.util.StringUtils;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * 表示聊天消息的传输实现。
+ *
+ * @author 易文渊
+ * @since 2024-04-12
+ */
+@Data
+@NoArgsConstructor
+public class FlatChatMessage implements ChatMessage {
+ private String id;
+ private String type;
+ private String text;
+ private List medias;
+ private List toolCalls;
+
+ /**
+ * 根据{@link ChatMessage} 构造消息传输对象。
+ *
+ * @param chatMessage 提供构造参数的 {@link ChatMessage}。
+ * @return 表示创建成功的 {@link FlatChatMessage}。
+ */
+ public static FlatChatMessage from(ChatMessage chatMessage) {
+ Validation.notNull(chatMessage, "The chat message cannot be null.");
+ if (chatMessage instanceof FlatChatMessage) {
+ return (FlatChatMessage) chatMessage;
+ }
+ Validation.notNull(chatMessage.type(), "The message type cannot be null.");
+ FlatChatMessage flatMessage = new FlatChatMessage();
+ flatMessage.id = chatMessage.id().orElse(null);
+ flatMessage.type = chatMessage.type().getRole();
+ flatMessage.text = chatMessage.text();
+ flatMessage.medias = chatMessage.medias();
+ flatMessage.toolCalls = chatMessage.toolCalls();
+ return flatMessage;
+ }
+
+ @Override
+ public Optional id() {
+ return Optional.ofNullable(this.id);
+ }
+
+ @Override
+ public MessageType type() {
+ return MessageType.parse(this.type);
+ }
+
+ @Override
+ public String text() {
+ return ObjectUtils.nullIf(this.text, StringUtils.EMPTY);
+ }
+
+ @Override
+ public List medias() {
+ return ObjectUtils.nullIf(this.medias, Collections.emptyList());
+ }
+
+ @Override
+ public List toolCalls() {
+ return ObjectUtils.nullIf(this.toolCalls, Collections.emptyList());
+ }
+}
\ No newline at end of file
diff --git a/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/document/Measurable.java b/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/document/Measurable.java
index 24d6fb15..9d660fa1 100644
--- a/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/document/Measurable.java
+++ b/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/document/Measurable.java
@@ -6,6 +6,8 @@
package modelengine.fel.core.document;
+import modelengine.fitframework.inspection.Nonnull;
+
/**
* 表示具有量化能力的对象。
*
@@ -19,4 +21,12 @@ public interface Measurable {
* @return 表示当前对象的量化分数的 {@code double}。
*/
double score();
+
+ /**
+ * 获取文档的分组标识。
+ *
+ * @return 表示文档分组标识的 {@link String}。
+ */
+ @Nonnull
+ String group();
}
\ No newline at end of file
diff --git a/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/document/MeasurableDocument.java b/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/document/MeasurableDocument.java
index e3091e6e..e796090f 100644
--- a/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/document/MeasurableDocument.java
+++ b/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/document/MeasurableDocument.java
@@ -9,7 +9,9 @@
import static modelengine.fitframework.inspection.Validation.notNull;
import modelengine.fitframework.inspection.Nonnull;
+import modelengine.fitframework.inspection.Validation;
import modelengine.fitframework.resource.web.Media;
+import modelengine.fitframework.util.UuidUtils;
import java.util.List;
import java.util.Map;
@@ -22,8 +24,12 @@
* @since 2024-08-06
*/
public class MeasurableDocument implements Document, Measurable {
- private final Document document;
+ private final String id;
+ private final String text;
+ private final String groupId;
private final double score;
+ private final Map metadata;
+ private final List medias;
/**
* 创建 {@link MeasurableDocument} 的实体。
@@ -33,30 +39,47 @@ public class MeasurableDocument implements Document, Measurable {
* @throws IllegalArgumentException 当 {@code document} 为 {@code null} 时。
*/
public MeasurableDocument(Document document, double score) {
- this.document = notNull(document, "The document cannot be null.");
+ this(document, score, UuidUtils.randomUuidString());
+ }
+
+ /**
+ * 创建 {@link MeasurableDocument} 的实体。
+ *
+ * @param document 表示原始文档的 {@link Document}。
+ * @param score 表示文档评分的 {@code double}。
+ * @param groupId 表示文档的分组标识的 {@link String}。
+ * @throws IllegalArgumentException 当 {@code document} 为 {@code null} 时。
+ */
+ public MeasurableDocument(Document document, double score, String groupId) {
+ notNull(document, "The document cannot be null.");
+ this.id = document.id();
+ this.text = document.text();
this.score = score;
+ this.groupId = Validation.notBlank(groupId, "The groupId cannot be null.");
+ this.metadata = document.metadata();
+ this.medias = document.medias();
}
@Override
@Nonnull
public String text() {
- return this.document.text();
+ return this.text;
}
@Override
public List medias() {
- return this.document.medias();
+ return this.medias;
}
@Override
public String id() {
- return this.document.id();
+ return this.id;
}
@Nonnull
@Override
public Map metadata() {
- return this.document.metadata();
+ return this.metadata;
}
@Override
@@ -64,6 +87,12 @@ public double score() {
return this.score;
}
+ @Override
+ @Nonnull
+ public String group() {
+ return this.groupId;
+ }
+
@Override
public boolean equals(Object object) {
if (this == object) {
@@ -73,16 +102,18 @@ public boolean equals(Object object) {
return false;
}
MeasurableDocument that = (MeasurableDocument) object;
- return Double.compare(this.score, that.score) == 0 && Objects.equals(this.document, that.document);
+ return Double.compare(this.score, that.score) == 0 && Objects.equals(this.id, that.id);
}
@Override
public int hashCode() {
- return Objects.hash(this.document, this.score);
+ return Objects.hash(this.id, this.score);
}
@Override
public String toString() {
- return "DocumentWithScore{" + "document=" + document + ", score=" + score + '}';
+ return "MeasurableDocument{" + "id='" + this.id + '\'' + ", text='" + this.text + '\'' + ", groupId='"
+ + this.groupId + '\'' + ", score=" + this.score + ", metadata=" + this.metadata + ", medias="
+ + this.medias + '}';
}
}
\ No newline at end of file
diff --git a/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/document/support/RerankApi.java b/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/document/support/RerankApi.java
new file mode 100644
index 00000000..c23c6c77
--- /dev/null
+++ b/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/document/support/RerankApi.java
@@ -0,0 +1,20 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
+ * This file is a part of the ModelEngine Project.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+package modelengine.fel.core.document.support;
+
+/**
+ * 提供 Rerank 客户端接口:发送 Rerank API 格式的请求并接收响应。
+ *
+ * @author 马朝阳
+ * @since 2024-09-27
+ */
+public interface RerankApi {
+ /**
+ * Rerank 模型请求的端点。
+ */
+ String RERANK_ENDPOINT = "/rerank";
+}
\ No newline at end of file
diff --git a/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/document/support/RerankDocumentProcessor.java b/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/document/support/RerankDocumentProcessor.java
new file mode 100644
index 00000000..aaa342bb
--- /dev/null
+++ b/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/document/support/RerankDocumentProcessor.java
@@ -0,0 +1,99 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
+ * This file is a part of the ModelEngine Project.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+package modelengine.fel.core.document.support;
+
+import modelengine.fel.core.document.DocumentPostProcessor;
+import modelengine.fel.core.document.MeasurableDocument;
+import modelengine.fit.http.client.HttpClassicClient;
+import modelengine.fit.http.client.HttpClassicClientFactory;
+import modelengine.fit.http.client.HttpClassicClientRequest;
+import modelengine.fit.http.client.HttpClassicClientResponse;
+import modelengine.fit.http.entity.Entity;
+import modelengine.fit.http.entity.ObjectEntity;
+import modelengine.fit.http.protocol.HttpRequestMethod;
+import modelengine.fit.http.protocol.HttpResponseStatus;
+import modelengine.fitframework.exception.FitException;
+import modelengine.fitframework.inspection.Validation;
+import modelengine.fitframework.log.Logger;
+import modelengine.fitframework.resource.UrlUtils;
+import modelengine.fitframework.util.CollectionUtils;
+import modelengine.fitframework.util.LazyLoader;
+import modelengine.fitframework.util.ObjectUtils;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 表示检索文档的后置重排序接口。
+ *
+ * @author 马朝阳
+ * @since 2024-09-14
+ */
+public class RerankDocumentProcessor implements DocumentPostProcessor {
+ private static final Logger log = Logger.get(RerankDocumentProcessor.class);
+
+ private final LazyLoader httpClient;
+ private final RerankOption rerankOption;
+
+ /**
+ * 创建 {@link RerankDocumentProcessor} 的实体。
+ *
+ * @param httpClientFactory 表示 {@link HttpClassicClientFactory} 的实例。
+ * @param rerankOption 表示 rerank 模型参数的 {@link RerankOption}
+ */
+ public RerankDocumentProcessor(HttpClassicClientFactory httpClientFactory, RerankOption rerankOption) {
+ Validation.notNull(httpClientFactory, "The httpClientFactory cannot be null.");
+ this.httpClient =
+ new LazyLoader<>(() -> httpClientFactory.create(HttpClassicClientFactory.Config.builder().build()));
+ this.rerankOption = Validation.notNull(rerankOption, "The rerankOption cannot be null.");
+ }
+
+ /**
+ * 对检索结果进行重排序。
+ *
+ * @param documents 表示输入文档的 {@link List}{@code <}{@link MeasurableDocument}{@code >}。
+ * @return 表示处理后文档的 {@link List}{@code <}{@link MeasurableDocument}{@code >}。
+ */
+ public List process(List documents) {
+ if (CollectionUtils.isEmpty(documents)) {
+ return Collections.emptyList();
+ }
+ List docs = documents.stream().map(MeasurableDocument::text).collect(Collectors.toList());
+ RerankRequest fields = new RerankRequest(this.rerankOption, docs);
+
+ HttpClassicClientRequest request = this.httpClient.get()
+ .createRequest(HttpRequestMethod.POST,
+ UrlUtils.combine(this.rerankOption.baseUri(), RerankApi.RERANK_ENDPOINT));
+ request.entity(Entity.createObject(request, fields));
+ RerankResponse rerankResponse = this.rerankExchange(request);
+
+ return rerankResponse.results()
+ .stream()
+ .map(result -> new MeasurableDocument(documents.get(result.index()), result.relevanceScore()))
+ .sorted((document1, document2) -> (int) (document2.score() - document1.score()))
+ .collect(Collectors.toList());
+ }
+
+ private RerankResponse rerankExchange(HttpClassicClientRequest request) {
+ try (HttpClassicClientResponse
+ *
+ * @author 易文渊
+ * @since 2024-06-04
+ */
+public class GeneralPipeline implements Pipeline